pax_global_header00006660000000000000000000000064137006565100014515gustar00rootroot0000000000000052 comment=369effe75c9833d243c96d1cbd19a59b7a50f145 psi-plus-snapshots-1.4.1456/000077500000000000000000000000001370065651000155535ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/.clang-format000066400000000000000000000003751370065651000201330ustar00rootroot00000000000000BasedOnStyle: WebKit BreakConstructorInitializers: AfterColon PointerAlignment: Right AlignAfterOpenBracket: Align AlignConsecutiveAssignments: true AlignConsecutiveDeclarations: true AlignTrailingComments: true ColumnLimit: 120 CompactNamespaces: true psi-plus-snapshots-1.4.1456/.gitignore000066400000000000000000000000661370065651000175450ustar00rootroot00000000000000CMakeLists.txt.user* builddir/ myspell/ translations/ psi-plus-snapshots-1.4.1456/.pre-commit-config.yaml000066400000000000000000000016411370065651000220360ustar00rootroot00000000000000# See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks exclude: '^3rdparty' repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.5.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-added-large-files args: ['--maxkb=1024'] - id: check-merge-conflict - repo: https://github.com/doublify/pre-commit-clang-format # for clang-tidy we can take github.com/pocc/pre-commit-hooks rev: f4c4ac5948aff384af2b439bfabb2bdd65d2b3ac hooks: - id: clang-format files: \.(cpp|h)$ - repo: https://github.com/Lucas-C/pre-commit-hooks rev: v1.1.7 hooks: - id: forbid-crlf - id: remove-crlf - id: forbid-tabs - id: remove-tabs - repo: https://github.com/openstack-dev/bashate rev: 2.0.0 hooks: - id: bashate args: ['--ignore', 'E006'] psi-plus-snapshots-1.4.1456/.qmake.cache.in000066400000000000000000000001321370065651000203150ustar00rootroot00000000000000top_srcdir="@source_dir@" top_builddir="@build_dir@" top_iris_builddir="@build_dir@/iris" psi-plus-snapshots-1.4.1456/.qmake.conf000066400000000000000000000001311370065651000175710ustar00rootroot00000000000000top_srcdir=$$PWD top_builddir=$$shadowed($$PWD) top_iris_builddir=$$shadowed($$PWD)/iris psi-plus-snapshots-1.4.1456/.travis.yml000066400000000000000000000023321370065651000176640ustar00rootroot00000000000000language: cpp sudo: required cache: ccache if: tag IS blank jobs: include: - env: TARGET=linux64 ENABLE_WEBKIT=OFF DIST=xenial os: linux dist: xenial - env: TARGET=linux64 ENABLE_WEBKIT=OFF DIST=bionic os: linux dist: bionic - env: TARGET=linux64 ENABLE_WEBKIT=OFF DIST=focal os: linux dist: focal - env: TARGET=linux64 ENABLE_WEBKIT=ON DIST=xenial os: linux dist: xenial - env: TARGET=linux64 ENABLE_WEBKIT=ON DIST=bionic os: linux dist: bionic - env: TARGET=linux64 ENABLE_WEBKIT=ON DIST=focal os: linux dist: focal - env: TARGET=macos64 ENABLE_WEBENGINE=OFF os: osx osx_image: xcode10 - env: TARGET=macos64 ENABLE_WEBENGINE=OFF os: osx osx_image: xcode11.3 - env: TARGET=macos64 ENABLE_WEBENGINE=OFF os: osx osx_image: xcode12 - env: TARGET=macos64 ENABLE_WEBENGINE=ON os: osx osx_image: xcode10 - env: TARGET=macos64 ENABLE_WEBENGINE=ON os: osx osx_image: xcode11.3 - env: TARGET=macos64 ENABLE_WEBENGINE=ON os: osx osx_image: xcode12 install: - ./tests/travis-ci/install-build-depends.sh script: - ./tests/travis-ci/build-and-test.sh psi-plus-snapshots-1.4.1456/3rdparty/000077500000000000000000000000001370065651000173235ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/3rdparty/CMakeLists.txt000066400000000000000000000001431370065651000220610ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.1.0) if( UNIX OR IS_WEBENGINE ) include(qhttp.cmake) endif() psi-plus-snapshots-1.4.1456/3rdparty/http-parser/000077500000000000000000000000001370065651000215745ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/3rdparty/http-parser/.mailmap000066400000000000000000000007401370065651000232160ustar00rootroot00000000000000# update AUTHORS with: # git log --all --reverse --format='%aN <%aE>' | perl -ne 'BEGIN{print "# Authors ordered by first contribution.\n"} print unless $h{$_}; $h{$_} = 1' > AUTHORS Ryan Dahl Salman Haq Simon Zimmermann Thomas LE ROUX LE ROUX Thomas Thomas LE ROUX Thomas LE ROUX Fedor Indutny psi-plus-snapshots-1.4.1456/3rdparty/http-parser/.travis.yml000066400000000000000000000002041370065651000237010ustar00rootroot00000000000000language: c compiler: - clang - gcc script: - "make" notifications: email: false irc: - "irc.freenode.net#node-ci" psi-plus-snapshots-1.4.1456/3rdparty/http-parser/AUTHORS000066400000000000000000000047061370065651000226530ustar00rootroot00000000000000# Authors ordered by first contribution. Ryan Dahl Jeremy Hinegardner Sergey Shepelev Joe Damato tomika Phoenix Sol Cliff Frey Ewen Cheslack-Postava Santiago Gala Tim Becker Jeff Terrace Ben Noordhuis Nathan Rajlich Mark Nottingham Aman Gupta Tim Becker Sean Cunningham Peter Griess Salman Haq Cliff Frey Jon Kolb Fouad Mardini Paul Querna Felix Geisendörfer koichik Andre Caron Ivo Raisr James McLaughlin David Gwynne Thomas LE ROUX Randy Rizun Andre Louis Caron Simon Zimmermann Erik Dubbelboer Martell Malone Bertrand Paquet BogDan Vatra Peter Faiman Corey Richardson Tóth Tamás Cam Swords Chris Dickinson Uli Köhler Charlie Somerville Patrik Stutz Fedor Indutny runner Alexis Campailla David Wragg Vinnie Falco Alex Butum Rex Feng Alex Kocharin Mark Koopman Helge Heß Alexis La Goutte George Miroshnykov Maciej Małecki Marc O'Morain Jeff Pinner Timothy J Fontaine Akagi201 Romain Giraud Jay Satiro Arne Steen Kjell Schubert Olivier Mengué psi-plus-snapshots-1.4.1456/3rdparty/http-parser/LICENSE-MIT000066400000000000000000000020651370065651000232330ustar00rootroot00000000000000Copyright Joyent, Inc. and other Node contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. psi-plus-snapshots-1.4.1456/3rdparty/http-parser/Makefile000066400000000000000000000122501370065651000232340ustar00rootroot00000000000000# Copyright Joyent, Inc. and other Node contributors. All rights reserved. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to # deal in the Software without restriction, including without limitation the # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or # sell copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. PLATFORM ?= $(shell sh -c 'uname -s | tr "[A-Z]" "[a-z]"') HELPER ?= BINEXT ?= SOLIBNAME = libhttp_parser SOMAJOR = 2 SOMINOR = 9 SOREV = 4 ifeq (darwin,$(PLATFORM)) SOEXT ?= dylib SONAME ?= $(SOLIBNAME).$(SOMAJOR).$(SOMINOR).$(SOEXT) LIBNAME ?= $(SOLIBNAME).$(SOMAJOR).$(SOMINOR).$(SOREV).$(SOEXT) else ifeq (wine,$(PLATFORM)) CC = winegcc BINEXT = .exe.so HELPER = wine else SOEXT ?= so SONAME ?= $(SOLIBNAME).$(SOEXT).$(SOMAJOR).$(SOMINOR) LIBNAME ?= $(SOLIBNAME).$(SOEXT).$(SOMAJOR).$(SOMINOR).$(SOREV) endif CC?=gcc AR?=ar CPPFLAGS ?= LDFLAGS ?= CPPFLAGS += -I. CPPFLAGS_DEBUG = $(CPPFLAGS) -DHTTP_PARSER_STRICT=1 CPPFLAGS_DEBUG += $(CPPFLAGS_DEBUG_EXTRA) CPPFLAGS_FAST = $(CPPFLAGS) -DHTTP_PARSER_STRICT=0 CPPFLAGS_FAST += $(CPPFLAGS_FAST_EXTRA) CPPFLAGS_BENCH = $(CPPFLAGS_FAST) CFLAGS += -Wall -Wextra -Werror CFLAGS_DEBUG = $(CFLAGS) -O0 -g $(CFLAGS_DEBUG_EXTRA) CFLAGS_FAST = $(CFLAGS) -O3 $(CFLAGS_FAST_EXTRA) CFLAGS_BENCH = $(CFLAGS_FAST) -Wno-unused-parameter CFLAGS_LIB = $(CFLAGS_FAST) -fPIC LDFLAGS_LIB = $(LDFLAGS) -shared INSTALL ?= install PREFIX ?= /usr/local LIBDIR = $(PREFIX)/lib INCLUDEDIR = $(PREFIX)/include ifeq (darwin,$(PLATFORM)) LDFLAGS_LIB += -Wl,-install_name,$(LIBDIR)/$(SONAME) else # TODO(bnoordhuis) The native SunOS linker expects -h rather than -soname... LDFLAGS_LIB += -Wl,-soname=$(SONAME) endif test: test_g test_fast $(HELPER) ./test_g$(BINEXT) $(HELPER) ./test_fast$(BINEXT) test_g: http_parser_g.o test_g.o $(CC) $(CFLAGS_DEBUG) $(LDFLAGS) http_parser_g.o test_g.o -o $@ test_g.o: test.c http_parser.h Makefile $(CC) $(CPPFLAGS_DEBUG) $(CFLAGS_DEBUG) -c test.c -o $@ http_parser_g.o: http_parser.c http_parser.h Makefile $(CC) $(CPPFLAGS_DEBUG) $(CFLAGS_DEBUG) -c http_parser.c -o $@ test_fast: http_parser.o test.o http_parser.h $(CC) $(CFLAGS_FAST) $(LDFLAGS) http_parser.o test.o -o $@ test.o: test.c http_parser.h Makefile $(CC) $(CPPFLAGS_FAST) $(CFLAGS_FAST) -c test.c -o $@ bench: http_parser.o bench.o $(CC) $(CFLAGS_BENCH) $(LDFLAGS) http_parser.o bench.o -o $@ bench.o: bench.c http_parser.h Makefile $(CC) $(CPPFLAGS_BENCH) $(CFLAGS_BENCH) -c bench.c -o $@ http_parser.o: http_parser.c http_parser.h Makefile $(CC) $(CPPFLAGS_FAST) $(CFLAGS_FAST) -c http_parser.c test-run-timed: test_fast while(true) do time $(HELPER) ./test_fast$(BINEXT) > /dev/null; done test-valgrind: test_g valgrind ./test_g libhttp_parser.o: http_parser.c http_parser.h Makefile $(CC) $(CPPFLAGS_FAST) $(CFLAGS_LIB) -c http_parser.c -o libhttp_parser.o library: libhttp_parser.o $(CC) $(LDFLAGS_LIB) -o $(LIBNAME) $< package: http_parser.o $(AR) rcs libhttp_parser.a http_parser.o url_parser: http_parser.o contrib/url_parser.c $(CC) $(CPPFLAGS_FAST) $(CFLAGS_FAST) $^ -o $@ url_parser_g: http_parser_g.o contrib/url_parser.c $(CC) $(CPPFLAGS_DEBUG) $(CFLAGS_DEBUG) $^ -o $@ parsertrace: http_parser.o contrib/parsertrace.c $(CC) $(CPPFLAGS_FAST) $(CFLAGS_FAST) $^ -o parsertrace$(BINEXT) parsertrace_g: http_parser_g.o contrib/parsertrace.c $(CC) $(CPPFLAGS_DEBUG) $(CFLAGS_DEBUG) $^ -o parsertrace_g$(BINEXT) tags: http_parser.c http_parser.h test.c ctags $^ install: library $(INSTALL) -D http_parser.h $(DESTDIR)$(INCLUDEDIR)/http_parser.h $(INSTALL) -D $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(LIBNAME) ln -sf $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(SONAME) ln -sf $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(SOLIBNAME).$(SOEXT) install-strip: library $(INSTALL) -D http_parser.h $(DESTDIR)$(INCLUDEDIR)/http_parser.h $(INSTALL) -D -s $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(LIBNAME) ln -sf $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(SONAME) ln -sf $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(SOLIBNAME).$(SOEXT) uninstall: rm $(DESTDIR)$(INCLUDEDIR)/http_parser.h rm $(DESTDIR)$(LIBDIR)/$(SOLIBNAME).$(SOEXT) rm $(DESTDIR)$(LIBDIR)/$(SONAME) rm $(DESTDIR)$(LIBDIR)/$(LIBNAME) clean: rm -f *.o *.a tags test test_fast test_g \ http_parser.tar libhttp_parser.so.* \ url_parser url_parser_g parsertrace parsertrace_g \ *.exe *.exe.so contrib/url_parser.c: http_parser.h contrib/parsertrace.c: http_parser.h .PHONY: clean package test-run test-run-timed test-valgrind install install-strip uninstall psi-plus-snapshots-1.4.1456/3rdparty/http-parser/README.md000066400000000000000000000221771370065651000230640ustar00rootroot00000000000000HTTP Parser =========== [![Build Status](https://api.travis-ci.org/nodejs/http-parser.svg?branch=master)](https://travis-ci.org/nodejs/http-parser) This is a parser for HTTP messages written in C. It parses both requests and responses. The parser is designed to be used in performance HTTP applications. It does not make any syscalls nor allocations, it does not buffer data, it can be interrupted at anytime. Depending on your architecture, it only requires about 40 bytes of data per message stream (in a web server that is per connection). Features: * No dependencies * Handles persistent streams (keep-alive). * Decodes chunked encoding. * Upgrade support * Defends against buffer overflow attacks. The parser extracts the following information from HTTP messages: * Header fields and values * Content-Length * Request method * Response status code * Transfer-Encoding * HTTP version * Request URL * Message body Usage ----- One `http_parser` object is used per TCP connection. Initialize the struct using `http_parser_init()` and set the callbacks. That might look something like this for a request parser: ```c http_parser_settings settings; settings.on_url = my_url_callback; settings.on_header_field = my_header_field_callback; /* ... */ http_parser *parser = malloc(sizeof(http_parser)); http_parser_init(parser, HTTP_REQUEST); parser->data = my_socket; ``` When data is received on the socket execute the parser and check for errors. ```c size_t len = 80*1024, nparsed; char buf[len]; ssize_t recved; recved = recv(fd, buf, len, 0); if (recved < 0) { /* Handle error. */ } /* Start up / continue the parser. * Note we pass recved==0 to signal that EOF has been received. */ nparsed = http_parser_execute(parser, &settings, buf, recved); if (parser->upgrade) { /* handle new protocol */ } else if (nparsed != recved) { /* Handle error. Usually just close the connection. */ } ``` `http_parser` needs to know where the end of the stream is. For example, sometimes servers send responses without Content-Length and expect the client to consume input (for the body) until EOF. To tell `http_parser` about EOF, give `0` as the fourth parameter to `http_parser_execute()`. Callbacks and errors can still be encountered during an EOF, so one must still be prepared to receive them. Scalar valued message information such as `status_code`, `method`, and the HTTP version are stored in the parser structure. This data is only temporally stored in `http_parser` and gets reset on each new message. If this information is needed later, copy it out of the structure during the `headers_complete` callback. The parser decodes the transfer-encoding for both requests and responses transparently. That is, a chunked encoding is decoded before being sent to the on_body callback. The Special Problem of Upgrade ------------------------------ `http_parser` supports upgrading the connection to a different protocol. An increasingly common example of this is the WebSocket protocol which sends a request like GET /demo HTTP/1.1 Upgrade: WebSocket Connection: Upgrade Host: example.com Origin: http://example.com WebSocket-Protocol: sample followed by non-HTTP data. (See [RFC6455](https://tools.ietf.org/html/rfc6455) for more information the WebSocket protocol.) To support this, the parser will treat this as a normal HTTP message without a body, issuing both on_headers_complete and on_message_complete callbacks. However http_parser_execute() will stop parsing at the end of the headers and return. The user is expected to check if `parser->upgrade` has been set to 1 after `http_parser_execute()` returns. Non-HTTP data begins at the buffer supplied offset by the return value of `http_parser_execute()`. Callbacks --------- During the `http_parser_execute()` call, the callbacks set in `http_parser_settings` will be executed. The parser maintains state and never looks behind, so buffering the data is not necessary. If you need to save certain data for later usage, you can do that from the callbacks. There are two types of callbacks: * notification `typedef int (*http_cb) (http_parser*);` Callbacks: on_message_begin, on_headers_complete, on_message_complete. * data `typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);` Callbacks: (requests only) on_url, (common) on_header_field, on_header_value, on_body; Callbacks must return 0 on success. Returning a non-zero value indicates error to the parser, making it exit immediately. For cases where it is necessary to pass local information to/from a callback, the `http_parser` object's `data` field can be used. An example of such a case is when using threads to handle a socket connection, parse a request, and then give a response over that socket. By instantiation of a thread-local struct containing relevant data (e.g. accepted socket, allocated memory for callbacks to write into, etc), a parser's callbacks are able to communicate data between the scope of the thread and the scope of the callback in a threadsafe manner. This allows `http_parser` to be used in multi-threaded contexts. Example: ```c typedef struct { socket_t sock; void* buffer; int buf_len; } custom_data_t; int my_url_callback(http_parser* parser, const char *at, size_t length) { /* access to thread local custom_data_t struct. Use this access save parsed data for later use into thread local buffer, or communicate over socket */ parser->data; ... return 0; } ... void http_parser_thread(socket_t sock) { int nparsed = 0; /* allocate memory for user data */ custom_data_t *my_data = malloc(sizeof(custom_data_t)); /* some information for use by callbacks. * achieves thread -> callback information flow */ my_data->sock = sock; /* instantiate a thread-local parser */ http_parser *parser = malloc(sizeof(http_parser)); http_parser_init(parser, HTTP_REQUEST); /* initialise parser */ /* this custom data reference is accessible through the reference to the parser supplied to callback functions */ parser->data = my_data; http_parser_settings settings; /* set up callbacks */ settings.on_url = my_url_callback; /* execute parser */ nparsed = http_parser_execute(parser, &settings, buf, recved); ... /* parsed information copied from callback. can now perform action on data copied into thread-local memory from callbacks. achieves callback -> thread information flow */ my_data->buffer; ... } ``` In case you parse HTTP message in chunks (i.e. `read()` request line from socket, parse, read half headers, parse, etc) your data callbacks may be called more than once. `http_parser` guarantees that data pointer is only valid for the lifetime of callback. You can also `read()` into a heap allocated buffer to avoid copying memory around if this fits your application. Reading headers may be a tricky task if you read/parse headers partially. Basically, you need to remember whether last header callback was field or value and apply the following logic: (on_header_field and on_header_value shortened to on_h_*) ------------------------ ------------ -------------------------------------------- | State (prev. callback) | Callback | Description/action | ------------------------ ------------ -------------------------------------------- | nothing (first call) | on_h_field | Allocate new buffer and copy callback data | | | | into it | ------------------------ ------------ -------------------------------------------- | value | on_h_field | New header started. | | | | Copy current name,value buffers to headers | | | | list and allocate new buffer for new name | ------------------------ ------------ -------------------------------------------- | field | on_h_field | Previous name continues. Reallocate name | | | | buffer and append callback data to it | ------------------------ ------------ -------------------------------------------- | field | on_h_value | Value for current header started. Allocate | | | | new buffer and copy callback data to it | ------------------------ ------------ -------------------------------------------- | value | on_h_value | Value continues. Reallocate value buffer | | | | and append callback data to it | ------------------------ ------------ -------------------------------------------- Parsing URLs ------------ A simplistic zero-copy URL parser is provided as `http_parser_parse_url()`. Users of this library may wish to use it to parse URLs constructed from consecutive `on_url` callbacks. See examples of reading in headers: * [partial example](http://gist.github.com/155877) in C * [from http-parser tests](http://github.com/joyent/http-parser/blob/37a0ff8/test.c#L403) in C * [from Node library](http://github.com/joyent/node/blob/842eaf4/src/http.js#L284) in Javascript psi-plus-snapshots-1.4.1456/3rdparty/http-parser/bench.c000066400000000000000000000073121370065651000230220ustar00rootroot00000000000000/* Copyright Fedor Indutny. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include "http_parser.h" #include #include #include #include #include /* 8 gb */ static const int64_t kBytes = 8LL << 30; static const char data[] = "POST /joyent/http-parser HTTP/1.1\r\n" "Host: github.com\r\n" "DNT: 1\r\n" "Accept-Encoding: gzip, deflate, sdch\r\n" "Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4\r\n" "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/39.0.2171.65 Safari/537.36\r\n" "Accept: text/html,application/xhtml+xml,application/xml;q=0.9," "image/webp,*/*;q=0.8\r\n" "Referer: https://github.com/joyent/http-parser\r\n" "Connection: keep-alive\r\n" "Transfer-Encoding: chunked\r\n" "Cache-Control: max-age=0\r\n\r\nb\r\nhello world\r\n0\r\n"; static const size_t data_len = sizeof(data) - 1; static int on_info(http_parser* p) { return 0; } static int on_data(http_parser* p, const char *at, size_t length) { return 0; } static http_parser_settings settings = { .on_message_begin = on_info, .on_headers_complete = on_info, .on_message_complete = on_info, .on_header_field = on_data, .on_header_value = on_data, .on_url = on_data, .on_status = on_data, .on_body = on_data }; int bench(int iter_count, int silent) { struct http_parser parser; int i; int err; struct timeval start; struct timeval end; if (!silent) { err = gettimeofday(&start, NULL); assert(err == 0); } fprintf(stderr, "req_len=%d\n", (int) data_len); for (i = 0; i < iter_count; i++) { size_t parsed; http_parser_init(&parser, HTTP_REQUEST); parsed = http_parser_execute(&parser, &settings, data, data_len); assert(parsed == data_len); } if (!silent) { double elapsed; double bw; double total; err = gettimeofday(&end, NULL); assert(err == 0); fprintf(stdout, "Benchmark result:\n"); elapsed = (double) (end.tv_sec - start.tv_sec) + (end.tv_usec - start.tv_usec) * 1e-6f; total = (double) iter_count * data_len; bw = (double) total / elapsed; fprintf(stdout, "%.2f mb | %.2f mb/s | %.2f req/sec | %.2f s\n", (double) total / (1024 * 1024), bw / (1024 * 1024), (double) iter_count / elapsed, elapsed); fflush(stdout); } return 0; } int main(int argc, char** argv) { int64_t iterations; iterations = kBytes / (int64_t) data_len; if (argc == 2 && strcmp(argv[1], "infinite") == 0) { for (;;) bench(iterations, 1); return 0; } else { return bench(iterations, 0); } } psi-plus-snapshots-1.4.1456/3rdparty/http-parser/contrib/000077500000000000000000000000001370065651000232345ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/3rdparty/http-parser/contrib/parsertrace.c000066400000000000000000000101341370065651000257120ustar00rootroot00000000000000/* Copyright Joyent, Inc. and other Node contributors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ /* Dump what the parser finds to stdout as it happen */ #include "http_parser.h" #include #include #include int on_message_begin(http_parser* _) { (void)_; printf("\n***MESSAGE BEGIN***\n\n"); return 0; } int on_headers_complete(http_parser* _) { (void)_; printf("\n***HEADERS COMPLETE***\n\n"); return 0; } int on_message_complete(http_parser* _) { (void)_; printf("\n***MESSAGE COMPLETE***\n\n"); return 0; } int on_url(http_parser* _, const char* at, size_t length) { (void)_; printf("Url: %.*s\n", (int)length, at); return 0; } int on_header_field(http_parser* _, const char* at, size_t length) { (void)_; printf("Header field: %.*s\n", (int)length, at); return 0; } int on_header_value(http_parser* _, const char* at, size_t length) { (void)_; printf("Header value: %.*s\n", (int)length, at); return 0; } int on_body(http_parser* _, const char* at, size_t length) { (void)_; printf("Body: %.*s\n", (int)length, at); return 0; } void usage(const char* name) { fprintf(stderr, "Usage: %s $type $filename\n" " type: -x, where x is one of {r,b,q}\n" " parses file as a Response, reQuest, or Both\n", name); exit(EXIT_FAILURE); } int main(int argc, char* argv[]) { enum http_parser_type file_type; if (argc != 3) { usage(argv[0]); } char* type = argv[1]; if (type[0] != '-') { usage(argv[0]); } switch (type[1]) { /* in the case of "-", type[1] will be NUL */ case 'r': file_type = HTTP_RESPONSE; break; case 'q': file_type = HTTP_REQUEST; break; case 'b': file_type = HTTP_BOTH; break; default: usage(argv[0]); } char* filename = argv[2]; FILE* file = fopen(filename, "r"); if (file == NULL) { perror("fopen"); goto fail; } fseek(file, 0, SEEK_END); long file_length = ftell(file); if (file_length == -1) { perror("ftell"); goto fail; } fseek(file, 0, SEEK_SET); char* data = malloc(file_length); if (fread(data, 1, file_length, file) != (size_t)file_length) { fprintf(stderr, "couldn't read entire file\n"); free(data); goto fail; } http_parser_settings settings; memset(&settings, 0, sizeof(settings)); settings.on_message_begin = on_message_begin; settings.on_url = on_url; settings.on_header_field = on_header_field; settings.on_header_value = on_header_value; settings.on_headers_complete = on_headers_complete; settings.on_body = on_body; settings.on_message_complete = on_message_complete; http_parser parser; http_parser_init(&parser, file_type); size_t nparsed = http_parser_execute(&parser, &settings, data, file_length); free(data); if (nparsed != (size_t)file_length) { fprintf(stderr, "Error: %s (%s)\n", http_errno_description(HTTP_PARSER_ERRNO(&parser)), http_errno_name(HTTP_PARSER_ERRNO(&parser))); goto fail; } return EXIT_SUCCESS; fail: fclose(file); return EXIT_FAILURE; } psi-plus-snapshots-1.4.1456/3rdparty/http-parser/contrib/url_parser.c000066400000000000000000000021771370065651000255650ustar00rootroot00000000000000#include "http_parser.h" #include #include void dump_url (const char *url, const struct http_parser_url *u) { unsigned int i; printf("\tfield_set: 0x%x, port: %u\n", u->field_set, u->port); for (i = 0; i < UF_MAX; i++) { if ((u->field_set & (1 << i)) == 0) { printf("\tfield_data[%u]: unset\n", i); continue; } printf("\tfield_data[%u]: off: %u, len: %u, part: %.*s\n", i, u->field_data[i].off, u->field_data[i].len, u->field_data[i].len, url + u->field_data[i].off); } } int main(int argc, char ** argv) { struct http_parser_url u; int len, connect, result; if (argc != 3) { printf("Syntax : %s connect|get url\n", argv[0]); return 1; } len = strlen(argv[2]); connect = strcmp("connect", argv[1]) == 0 ? 1 : 0; printf("Parsing %s, connect %d\n", argv[2], connect); http_parser_url_init(&u); result = http_parser_parse_url(argv[2], len, connect, &u); if (result != 0) { printf("Parse error : %d\n", result); return result; } printf("Parse ok, result : \n"); dump_url(argv[2], &u); return 0; } psi-plus-snapshots-1.4.1456/3rdparty/http-parser/fuzzers/000077500000000000000000000000001370065651000233045ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/3rdparty/http-parser/fuzzers/fuzz_parser.c000066400000000000000000000011311370065651000260160ustar00rootroot00000000000000#include #include #include #include "http_parser.h" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { static const http_parser_settings settings_null = { .on_message_begin = 0 , .on_header_field = 0 ,.on_header_value = 0 ,.on_url = 0 ,.on_status = 0 ,.on_body = 0 ,.on_headers_complete = 0 ,.on_message_complete = 0 ,.on_chunk_header = 0 ,.on_chunk_complete = 0 }; http_parser parser; http_parser_init(&parser, HTTP_BOTH); http_parser_execute(&parser, &settings_null, (char*)data, size); return 0; } psi-plus-snapshots-1.4.1456/3rdparty/http-parser/http_parser.c000066400000000000000000002231701370065651000243000ustar00rootroot00000000000000/* Copyright Joyent, Inc. and other Node contributors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include "http_parser.h" #include #include #include #include #include static uint32_t max_header_size = HTTP_MAX_HEADER_SIZE; #ifndef ULLONG_MAX # define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */ #endif #ifndef MIN # define MIN(a,b) ((a) < (b) ? (a) : (b)) #endif #ifndef ARRAY_SIZE # define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #endif #ifndef BIT_AT # define BIT_AT(a, i) \ (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \ (1 << ((unsigned int) (i) & 7)))) #endif #ifndef ELEM_AT # define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v)) #endif #define SET_ERRNO(e) \ do { \ parser->nread = nread; \ parser->http_errno = (e); \ } while(0) #define CURRENT_STATE() p_state #define UPDATE_STATE(V) p_state = (enum state) (V); #define RETURN(V) \ do { \ parser->nread = nread; \ parser->state = CURRENT_STATE(); \ return (V); \ } while (0); #define REEXECUTE() \ goto reexecute; \ #ifdef __GNUC__ # define LIKELY(X) __builtin_expect(!!(X), 1) # define UNLIKELY(X) __builtin_expect(!!(X), 0) #else # define LIKELY(X) (X) # define UNLIKELY(X) (X) #endif /* Run the notify callback FOR, returning ER if it fails */ #define CALLBACK_NOTIFY_(FOR, ER) \ do { \ assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ \ if (LIKELY(settings->on_##FOR)) { \ parser->state = CURRENT_STATE(); \ if (UNLIKELY(0 != settings->on_##FOR(parser))) { \ SET_ERRNO(HPE_CB_##FOR); \ } \ UPDATE_STATE(parser->state); \ \ /* We either errored above or got paused; get out */ \ if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ return (ER); \ } \ } \ } while (0) /* Run the notify callback FOR and consume the current byte */ #define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1) /* Run the notify callback FOR and don't consume the current byte */ #define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data) /* Run data callback FOR with LEN bytes, returning ER if it fails */ #define CALLBACK_DATA_(FOR, LEN, ER) \ do { \ assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ \ if (FOR##_mark) { \ if (LIKELY(settings->on_##FOR)) { \ parser->state = CURRENT_STATE(); \ if (UNLIKELY(0 != \ settings->on_##FOR(parser, FOR##_mark, (LEN)))) { \ SET_ERRNO(HPE_CB_##FOR); \ } \ UPDATE_STATE(parser->state); \ \ /* We either errored above or got paused; get out */ \ if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ return (ER); \ } \ } \ FOR##_mark = NULL; \ } \ } while (0) /* Run the data callback FOR and consume the current byte */ #define CALLBACK_DATA(FOR) \ CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1) /* Run the data callback FOR and don't consume the current byte */ #define CALLBACK_DATA_NOADVANCE(FOR) \ CALLBACK_DATA_(FOR, p - FOR##_mark, p - data) /* Set the mark FOR; non-destructive if mark is already set */ #define MARK(FOR) \ do { \ if (!FOR##_mark) { \ FOR##_mark = p; \ } \ } while (0) /* Don't allow the total size of the HTTP headers (including the status * line) to exceed max_header_size. This check is here to protect * embedders against denial-of-service attacks where the attacker feeds * us a never-ending header that the embedder keeps buffering. * * This check is arguably the responsibility of embedders but we're doing * it on the embedder's behalf because most won't bother and this way we * make the web a little safer. max_header_size is still far bigger * than any reasonable request or response so this should never affect * day-to-day operation. */ #define COUNT_HEADER_SIZE(V) \ do { \ nread += (uint32_t)(V); \ if (UNLIKELY(nread > max_header_size)) { \ SET_ERRNO(HPE_HEADER_OVERFLOW); \ goto error; \ } \ } while (0) #define PROXY_CONNECTION "proxy-connection" #define CONNECTION "connection" #define CONTENT_LENGTH "content-length" #define TRANSFER_ENCODING "transfer-encoding" #define UPGRADE "upgrade" #define CHUNKED "chunked" #define KEEP_ALIVE "keep-alive" #define CLOSE "close" static const char *method_strings[] = { #define XX(num, name, string) #string, HTTP_METHOD_MAP(XX) #undef XX }; /* Tokens as defined by rfc 2616. Also lowercases them. * token = 1* * separators = "(" | ")" | "<" | ">" | "@" * | "," | ";" | ":" | "\" | <"> * | "/" | "[" | "]" | "?" | "=" * | "{" | "}" | SP | HT */ static const char tokens[256] = { /* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ 0, 0, 0, 0, 0, 0, 0, 0, /* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ 0, 0, 0, 0, 0, 0, 0, 0, /* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ 0, 0, 0, 0, 0, 0, 0, 0, /* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ 0, 0, 0, 0, 0, 0, 0, 0, /* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ ' ', '!', 0, '#', '$', '%', '&', '\'', /* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ 0, 0, '*', '+', 0, '-', '.', 0, /* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ '0', '1', '2', '3', '4', '5', '6', '7', /* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ '8', '9', 0, 0, 0, 0, 0, 0, /* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', /* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', /* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', /* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ 'x', 'y', 'z', 0, 0, 0, '^', '_', /* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', /* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', /* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', /* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ 'x', 'y', 'z', 0, '|', 0, '~', 0 }; static const int8_t unhex[256] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1 ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }; #if HTTP_PARSER_STRICT # define T(v) 0 #else # define T(v) v #endif static const uint8_t normal_url_char[32] = { /* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, /* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0, /* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, /* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, /* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128, /* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, /* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, }; #undef T enum state { s_dead = 1 /* important that this is > 0 */ , s_start_req_or_res , s_res_or_resp_H , s_start_res , s_res_H , s_res_HT , s_res_HTT , s_res_HTTP , s_res_http_major , s_res_http_dot , s_res_http_minor , s_res_http_end , s_res_first_status_code , s_res_status_code , s_res_status_start , s_res_status , s_res_line_almost_done , s_start_req , s_req_method , s_req_spaces_before_url , s_req_schema , s_req_schema_slash , s_req_schema_slash_slash , s_req_server_start , s_req_server , s_req_server_with_at , s_req_path , s_req_query_string_start , s_req_query_string , s_req_fragment_start , s_req_fragment , s_req_http_start , s_req_http_H , s_req_http_HT , s_req_http_HTT , s_req_http_HTTP , s_req_http_I , s_req_http_IC , s_req_http_major , s_req_http_dot , s_req_http_minor , s_req_http_end , s_req_line_almost_done , s_header_field_start , s_header_field , s_header_value_discard_ws , s_header_value_discard_ws_almost_done , s_header_value_discard_lws , s_header_value_start , s_header_value , s_header_value_lws , s_header_almost_done , s_chunk_size_start , s_chunk_size , s_chunk_parameters , s_chunk_size_almost_done , s_headers_almost_done , s_headers_done /* Important: 's_headers_done' must be the last 'header' state. All * states beyond this must be 'body' states. It is used for overflow * checking. See the PARSING_HEADER() macro. */ , s_chunk_data , s_chunk_data_almost_done , s_chunk_data_done , s_body_identity , s_body_identity_eof , s_message_done }; #define PARSING_HEADER(state) (state <= s_headers_done) enum header_states { h_general = 0 , h_C , h_CO , h_CON , h_matching_connection , h_matching_proxy_connection , h_matching_content_length , h_matching_transfer_encoding , h_matching_upgrade , h_connection , h_content_length , h_content_length_num , h_content_length_ws , h_transfer_encoding , h_upgrade , h_matching_transfer_encoding_token_start , h_matching_transfer_encoding_chunked , h_matching_transfer_encoding_token , h_matching_connection_token_start , h_matching_connection_keep_alive , h_matching_connection_close , h_matching_connection_upgrade , h_matching_connection_token , h_transfer_encoding_chunked , h_connection_keep_alive , h_connection_close , h_connection_upgrade }; enum http_host_state { s_http_host_dead = 1 , s_http_userinfo_start , s_http_userinfo , s_http_host_start , s_http_host_v6_start , s_http_host , s_http_host_v6 , s_http_host_v6_end , s_http_host_v6_zone_start , s_http_host_v6_zone , s_http_host_port_start , s_http_host_port }; /* Macros for character classes; depends on strict-mode */ #define CR '\r' #define LF '\n' #define LOWER(c) (unsigned char)(c | 0x20) #define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') #define IS_NUM(c) ((c) >= '0' && (c) <= '9') #define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) #define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f')) #define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \ (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \ (c) == ')') #define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \ (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ (c) == '$' || (c) == ',') #define STRICT_TOKEN(c) ((c == ' ') ? 0 : tokens[(unsigned char)c]) #if HTTP_PARSER_STRICT #define TOKEN(c) STRICT_TOKEN(c) #define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c)) #define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') #else #define TOKEN(c) tokens[(unsigned char)c] #define IS_URL_CHAR(c) \ (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80)) #define IS_HOST_CHAR(c) \ (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') #endif /** * Verify that a char is a valid visible (printable) US-ASCII * character or %x80-FF **/ #define IS_HEADER_CHAR(ch) \ (ch == CR || ch == LF || ch == 9 || ((unsigned char)ch > 31 && ch != 127)) #define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res) #if HTTP_PARSER_STRICT # define STRICT_CHECK(cond) \ do { \ if (cond) { \ SET_ERRNO(HPE_STRICT); \ goto error; \ } \ } while (0) # define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead) #else # define STRICT_CHECK(cond) # define NEW_MESSAGE() start_state #endif /* Map errno values to strings for human-readable output */ #define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s }, static struct { const char *name; const char *description; } http_strerror_tab[] = { HTTP_ERRNO_MAP(HTTP_STRERROR_GEN) }; #undef HTTP_STRERROR_GEN int http_message_needs_eof(const http_parser *parser); /* Our URL parser. * * This is designed to be shared by http_parser_execute() for URL validation, * hence it has a state transition + byte-for-byte interface. In addition, it * is meant to be embedded in http_parser_parse_url(), which does the dirty * work of turning state transitions URL components for its API. * * This function should only be invoked with non-space characters. It is * assumed that the caller cares about (and can detect) the transition between * URL and non-URL states by looking for these. */ static enum state parse_url_char(enum state s, const char ch) { if (ch == ' ' || ch == '\r' || ch == '\n') { return s_dead; } #if HTTP_PARSER_STRICT if (ch == '\t' || ch == '\f') { return s_dead; } #endif switch (s) { case s_req_spaces_before_url: /* Proxied requests are followed by scheme of an absolute URI (alpha). * All methods except CONNECT are followed by '/' or '*'. */ if (ch == '/' || ch == '*') { return s_req_path; } if (IS_ALPHA(ch)) { return s_req_schema; } break; case s_req_schema: if (IS_ALPHA(ch)) { return s; } if (ch == ':') { return s_req_schema_slash; } break; case s_req_schema_slash: if (ch == '/') { return s_req_schema_slash_slash; } break; case s_req_schema_slash_slash: if (ch == '/') { return s_req_server_start; } break; case s_req_server_with_at: if (ch == '@') { return s_dead; } /* fall through */ case s_req_server_start: case s_req_server: if (ch == '/') { return s_req_path; } if (ch == '?') { return s_req_query_string_start; } if (ch == '@') { return s_req_server_with_at; } if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') { return s_req_server; } break; case s_req_path: if (IS_URL_CHAR(ch)) { return s; } switch (ch) { case '?': return s_req_query_string_start; case '#': return s_req_fragment_start; } break; case s_req_query_string_start: case s_req_query_string: if (IS_URL_CHAR(ch)) { return s_req_query_string; } switch (ch) { case '?': /* allow extra '?' in query string */ return s_req_query_string; case '#': return s_req_fragment_start; } break; case s_req_fragment_start: if (IS_URL_CHAR(ch)) { return s_req_fragment; } switch (ch) { case '?': return s_req_fragment; case '#': return s; } break; case s_req_fragment: if (IS_URL_CHAR(ch)) { return s; } switch (ch) { case '?': case '#': return s; } break; default: break; } /* We should never fall out of the switch above unless there's an error */ return s_dead; } size_t http_parser_execute (http_parser *parser, const http_parser_settings *settings, const char *data, size_t len) { char c, ch; int8_t unhex_val; const char *p = data; const char *header_field_mark = 0; const char *header_value_mark = 0; const char *url_mark = 0; const char *body_mark = 0; const char *status_mark = 0; enum state p_state = (enum state) parser->state; const unsigned int lenient = parser->lenient_http_headers; uint32_t nread = parser->nread; /* We're in an error state. Don't bother doing anything. */ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { return 0; } if (len == 0) { switch (CURRENT_STATE()) { case s_body_identity_eof: /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if * we got paused. */ CALLBACK_NOTIFY_NOADVANCE(message_complete); return 0; case s_dead: case s_start_req_or_res: case s_start_res: case s_start_req: return 0; default: SET_ERRNO(HPE_INVALID_EOF_STATE); return 1; } } if (CURRENT_STATE() == s_header_field) header_field_mark = data; if (CURRENT_STATE() == s_header_value) header_value_mark = data; switch (CURRENT_STATE()) { case s_req_path: case s_req_schema: case s_req_schema_slash: case s_req_schema_slash_slash: case s_req_server_start: case s_req_server: case s_req_server_with_at: case s_req_query_string_start: case s_req_query_string: case s_req_fragment_start: case s_req_fragment: url_mark = data; break; case s_res_status: status_mark = data; break; default: break; } for (p=data; p != data + len; p++) { ch = *p; if (PARSING_HEADER(CURRENT_STATE())) COUNT_HEADER_SIZE(1); reexecute: switch (CURRENT_STATE()) { case s_dead: /* this state is used after a 'Connection: close' message * the parser will error out if it reads another message */ if (LIKELY(ch == CR || ch == LF)) break; SET_ERRNO(HPE_CLOSED_CONNECTION); goto error; case s_start_req_or_res: { if (ch == CR || ch == LF) break; parser->flags = 0; parser->extra_flags = 0; parser->content_length = ULLONG_MAX; if (ch == 'H') { UPDATE_STATE(s_res_or_resp_H); CALLBACK_NOTIFY(message_begin); } else { parser->type = HTTP_REQUEST; UPDATE_STATE(s_start_req); REEXECUTE(); } break; } case s_res_or_resp_H: if (ch == 'T') { parser->type = HTTP_RESPONSE; UPDATE_STATE(s_res_HT); } else { if (UNLIKELY(ch != 'E')) { SET_ERRNO(HPE_INVALID_CONSTANT); goto error; } parser->type = HTTP_REQUEST; parser->method = HTTP_HEAD; parser->index = 2; UPDATE_STATE(s_req_method); } break; case s_start_res: { if (ch == CR || ch == LF) break; parser->flags = 0; parser->extra_flags = 0; parser->content_length = ULLONG_MAX; if (ch == 'H') { UPDATE_STATE(s_res_H); } else { SET_ERRNO(HPE_INVALID_CONSTANT); goto error; } CALLBACK_NOTIFY(message_begin); break; } case s_res_H: STRICT_CHECK(ch != 'T'); UPDATE_STATE(s_res_HT); break; case s_res_HT: STRICT_CHECK(ch != 'T'); UPDATE_STATE(s_res_HTT); break; case s_res_HTT: STRICT_CHECK(ch != 'P'); UPDATE_STATE(s_res_HTTP); break; case s_res_HTTP: STRICT_CHECK(ch != '/'); UPDATE_STATE(s_res_http_major); break; case s_res_http_major: if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_major = ch - '0'; UPDATE_STATE(s_res_http_dot); break; case s_res_http_dot: { if (UNLIKELY(ch != '.')) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } UPDATE_STATE(s_res_http_minor); break; } case s_res_http_minor: if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_minor = ch - '0'; UPDATE_STATE(s_res_http_end); break; case s_res_http_end: { if (UNLIKELY(ch != ' ')) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } UPDATE_STATE(s_res_first_status_code); break; } case s_res_first_status_code: { if (!IS_NUM(ch)) { if (ch == ' ') { break; } SET_ERRNO(HPE_INVALID_STATUS); goto error; } parser->status_code = ch - '0'; UPDATE_STATE(s_res_status_code); break; } case s_res_status_code: { if (!IS_NUM(ch)) { switch (ch) { case ' ': UPDATE_STATE(s_res_status_start); break; case CR: case LF: UPDATE_STATE(s_res_status_start); REEXECUTE(); break; default: SET_ERRNO(HPE_INVALID_STATUS); goto error; } break; } parser->status_code *= 10; parser->status_code += ch - '0'; if (UNLIKELY(parser->status_code > 999)) { SET_ERRNO(HPE_INVALID_STATUS); goto error; } break; } case s_res_status_start: { MARK(status); UPDATE_STATE(s_res_status); parser->index = 0; if (ch == CR || ch == LF) REEXECUTE(); break; } case s_res_status: if (ch == CR) { UPDATE_STATE(s_res_line_almost_done); CALLBACK_DATA(status); break; } if (ch == LF) { UPDATE_STATE(s_header_field_start); CALLBACK_DATA(status); break; } break; case s_res_line_almost_done: STRICT_CHECK(ch != LF); UPDATE_STATE(s_header_field_start); break; case s_start_req: { if (ch == CR || ch == LF) break; parser->flags = 0; parser->extra_flags = 0; parser->content_length = ULLONG_MAX; if (UNLIKELY(!IS_ALPHA(ch))) { SET_ERRNO(HPE_INVALID_METHOD); goto error; } parser->method = (enum http_method) 0; parser->index = 1; switch (ch) { case 'A': parser->method = HTTP_ACL; break; case 'B': parser->method = HTTP_BIND; break; case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break; case 'D': parser->method = HTTP_DELETE; break; case 'G': parser->method = HTTP_GET; break; case 'H': parser->method = HTTP_HEAD; break; case 'L': parser->method = HTTP_LOCK; /* or LINK */ break; case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH, MKCALENDAR */ break; case 'N': parser->method = HTTP_NOTIFY; break; case 'O': parser->method = HTTP_OPTIONS; break; case 'P': parser->method = HTTP_POST; /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ break; case 'R': parser->method = HTTP_REPORT; /* or REBIND */ break; case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH, SOURCE */ break; case 'T': parser->method = HTTP_TRACE; break; case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE, UNBIND, UNLINK */ break; default: SET_ERRNO(HPE_INVALID_METHOD); goto error; } UPDATE_STATE(s_req_method); CALLBACK_NOTIFY(message_begin); break; } case s_req_method: { const char *matcher; if (UNLIKELY(ch == '\0')) { SET_ERRNO(HPE_INVALID_METHOD); goto error; } matcher = method_strings[parser->method]; if (ch == ' ' && matcher[parser->index] == '\0') { UPDATE_STATE(s_req_spaces_before_url); } else if (ch == matcher[parser->index]) { ; /* nada */ } else if ((ch >= 'A' && ch <= 'Z') || ch == '-') { switch (parser->method << 16 | parser->index << 8 | ch) { #define XX(meth, pos, ch, new_meth) \ case (HTTP_##meth << 16 | pos << 8 | ch): \ parser->method = HTTP_##new_meth; break; XX(POST, 1, 'U', PUT) XX(POST, 1, 'A', PATCH) XX(POST, 1, 'R', PROPFIND) XX(PUT, 2, 'R', PURGE) XX(CONNECT, 1, 'H', CHECKOUT) XX(CONNECT, 2, 'P', COPY) XX(MKCOL, 1, 'O', MOVE) XX(MKCOL, 1, 'E', MERGE) XX(MKCOL, 1, '-', MSEARCH) XX(MKCOL, 2, 'A', MKACTIVITY) XX(MKCOL, 3, 'A', MKCALENDAR) XX(SUBSCRIBE, 1, 'E', SEARCH) XX(SUBSCRIBE, 1, 'O', SOURCE) XX(REPORT, 2, 'B', REBIND) XX(PROPFIND, 4, 'P', PROPPATCH) XX(LOCK, 1, 'I', LINK) XX(UNLOCK, 2, 'S', UNSUBSCRIBE) XX(UNLOCK, 2, 'B', UNBIND) XX(UNLOCK, 3, 'I', UNLINK) #undef XX default: SET_ERRNO(HPE_INVALID_METHOD); goto error; } } else { SET_ERRNO(HPE_INVALID_METHOD); goto error; } ++parser->index; break; } case s_req_spaces_before_url: { if (ch == ' ') break; MARK(url); if (parser->method == HTTP_CONNECT) { UPDATE_STATE(s_req_server_start); } UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); if (UNLIKELY(CURRENT_STATE() == s_dead)) { SET_ERRNO(HPE_INVALID_URL); goto error; } break; } case s_req_schema: case s_req_schema_slash: case s_req_schema_slash_slash: case s_req_server_start: { switch (ch) { /* No whitespace allowed here */ case ' ': case CR: case LF: SET_ERRNO(HPE_INVALID_URL); goto error; default: UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); if (UNLIKELY(CURRENT_STATE() == s_dead)) { SET_ERRNO(HPE_INVALID_URL); goto error; } } break; } case s_req_server: case s_req_server_with_at: case s_req_path: case s_req_query_string_start: case s_req_query_string: case s_req_fragment_start: case s_req_fragment: { switch (ch) { case ' ': UPDATE_STATE(s_req_http_start); CALLBACK_DATA(url); break; case CR: case LF: parser->http_major = 0; parser->http_minor = 9; UPDATE_STATE((ch == CR) ? s_req_line_almost_done : s_header_field_start); CALLBACK_DATA(url); break; default: UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); if (UNLIKELY(CURRENT_STATE() == s_dead)) { SET_ERRNO(HPE_INVALID_URL); goto error; } } break; } case s_req_http_start: switch (ch) { case ' ': break; case 'H': UPDATE_STATE(s_req_http_H); break; case 'I': if (parser->method == HTTP_SOURCE) { UPDATE_STATE(s_req_http_I); break; } /* fall through */ default: SET_ERRNO(HPE_INVALID_CONSTANT); goto error; } break; case s_req_http_H: STRICT_CHECK(ch != 'T'); UPDATE_STATE(s_req_http_HT); break; case s_req_http_HT: STRICT_CHECK(ch != 'T'); UPDATE_STATE(s_req_http_HTT); break; case s_req_http_HTT: STRICT_CHECK(ch != 'P'); UPDATE_STATE(s_req_http_HTTP); break; case s_req_http_I: STRICT_CHECK(ch != 'C'); UPDATE_STATE(s_req_http_IC); break; case s_req_http_IC: STRICT_CHECK(ch != 'E'); UPDATE_STATE(s_req_http_HTTP); /* Treat "ICE" as "HTTP". */ break; case s_req_http_HTTP: STRICT_CHECK(ch != '/'); UPDATE_STATE(s_req_http_major); break; case s_req_http_major: if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_major = ch - '0'; UPDATE_STATE(s_req_http_dot); break; case s_req_http_dot: { if (UNLIKELY(ch != '.')) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } UPDATE_STATE(s_req_http_minor); break; } case s_req_http_minor: if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_minor = ch - '0'; UPDATE_STATE(s_req_http_end); break; case s_req_http_end: { if (ch == CR) { UPDATE_STATE(s_req_line_almost_done); break; } if (ch == LF) { UPDATE_STATE(s_header_field_start); break; } SET_ERRNO(HPE_INVALID_VERSION); goto error; break; } /* end of request line */ case s_req_line_almost_done: { if (UNLIKELY(ch != LF)) { SET_ERRNO(HPE_LF_EXPECTED); goto error; } UPDATE_STATE(s_header_field_start); break; } case s_header_field_start: { if (ch == CR) { UPDATE_STATE(s_headers_almost_done); break; } if (ch == LF) { /* they might be just sending \n instead of \r\n so this would be * the second \n to denote the end of headers*/ UPDATE_STATE(s_headers_almost_done); REEXECUTE(); } c = TOKEN(ch); if (UNLIKELY(!c)) { SET_ERRNO(HPE_INVALID_HEADER_TOKEN); goto error; } MARK(header_field); parser->index = 0; UPDATE_STATE(s_header_field); switch (c) { case 'c': parser->header_state = h_C; break; case 'p': parser->header_state = h_matching_proxy_connection; break; case 't': parser->header_state = h_matching_transfer_encoding; break; case 'u': parser->header_state = h_matching_upgrade; break; default: parser->header_state = h_general; break; } break; } case s_header_field: { const char* start = p; for (; p != data + len; p++) { ch = *p; c = TOKEN(ch); if (!c) break; switch (parser->header_state) { case h_general: { size_t left = data + len - p; const char* pe = p + MIN(left, max_header_size); while (p+1 < pe && TOKEN(p[1])) { p++; } break; } case h_C: parser->index++; parser->header_state = (c == 'o' ? h_CO : h_general); break; case h_CO: parser->index++; parser->header_state = (c == 'n' ? h_CON : h_general); break; case h_CON: parser->index++; switch (c) { case 'n': parser->header_state = h_matching_connection; break; case 't': parser->header_state = h_matching_content_length; break; default: parser->header_state = h_general; break; } break; /* connection */ case h_matching_connection: parser->index++; if (parser->index > sizeof(CONNECTION)-1 || c != CONNECTION[parser->index]) { parser->header_state = h_general; } else if (parser->index == sizeof(CONNECTION)-2) { parser->header_state = h_connection; } break; /* proxy-connection */ case h_matching_proxy_connection: parser->index++; if (parser->index > sizeof(PROXY_CONNECTION)-1 || c != PROXY_CONNECTION[parser->index]) { parser->header_state = h_general; } else if (parser->index == sizeof(PROXY_CONNECTION)-2) { parser->header_state = h_connection; } break; /* content-length */ case h_matching_content_length: parser->index++; if (parser->index > sizeof(CONTENT_LENGTH)-1 || c != CONTENT_LENGTH[parser->index]) { parser->header_state = h_general; } else if (parser->index == sizeof(CONTENT_LENGTH)-2) { parser->header_state = h_content_length; } break; /* transfer-encoding */ case h_matching_transfer_encoding: parser->index++; if (parser->index > sizeof(TRANSFER_ENCODING)-1 || c != TRANSFER_ENCODING[parser->index]) { parser->header_state = h_general; } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) { parser->header_state = h_transfer_encoding; parser->extra_flags |= F_TRANSFER_ENCODING >> 8; } break; /* upgrade */ case h_matching_upgrade: parser->index++; if (parser->index > sizeof(UPGRADE)-1 || c != UPGRADE[parser->index]) { parser->header_state = h_general; } else if (parser->index == sizeof(UPGRADE)-2) { parser->header_state = h_upgrade; } break; case h_connection: case h_content_length: case h_transfer_encoding: case h_upgrade: if (ch != ' ') parser->header_state = h_general; break; default: assert(0 && "Unknown header_state"); break; } } if (p == data + len) { --p; COUNT_HEADER_SIZE(p - start); break; } COUNT_HEADER_SIZE(p - start); if (ch == ':') { UPDATE_STATE(s_header_value_discard_ws); CALLBACK_DATA(header_field); break; } SET_ERRNO(HPE_INVALID_HEADER_TOKEN); goto error; } case s_header_value_discard_ws: if (ch == ' ' || ch == '\t') break; if (ch == CR) { UPDATE_STATE(s_header_value_discard_ws_almost_done); break; } if (ch == LF) { UPDATE_STATE(s_header_value_discard_lws); break; } /* fall through */ case s_header_value_start: { MARK(header_value); UPDATE_STATE(s_header_value); parser->index = 0; c = LOWER(ch); switch (parser->header_state) { case h_upgrade: parser->flags |= F_UPGRADE; parser->header_state = h_general; break; case h_transfer_encoding: /* looking for 'Transfer-Encoding: chunked' */ if ('c' == c) { parser->header_state = h_matching_transfer_encoding_chunked; } else { parser->header_state = h_matching_transfer_encoding_token; } break; /* Multi-value `Transfer-Encoding` header */ case h_matching_transfer_encoding_token_start: break; case h_content_length: if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); goto error; } if (parser->flags & F_CONTENTLENGTH) { SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH); goto error; } parser->flags |= F_CONTENTLENGTH; parser->content_length = ch - '0'; parser->header_state = h_content_length_num; break; /* when obsolete line folding is encountered for content length * continue to the s_header_value state */ case h_content_length_ws: break; case h_connection: /* looking for 'Connection: keep-alive' */ if (c == 'k') { parser->header_state = h_matching_connection_keep_alive; /* looking for 'Connection: close' */ } else if (c == 'c') { parser->header_state = h_matching_connection_close; } else if (c == 'u') { parser->header_state = h_matching_connection_upgrade; } else { parser->header_state = h_matching_connection_token; } break; /* Multi-value `Connection` header */ case h_matching_connection_token_start: break; default: parser->header_state = h_general; break; } break; } case s_header_value: { const char* start = p; enum header_states h_state = (enum header_states) parser->header_state; for (; p != data + len; p++) { ch = *p; if (ch == CR) { UPDATE_STATE(s_header_almost_done); parser->header_state = h_state; CALLBACK_DATA(header_value); break; } if (ch == LF) { UPDATE_STATE(s_header_almost_done); COUNT_HEADER_SIZE(p - start); parser->header_state = h_state; CALLBACK_DATA_NOADVANCE(header_value); REEXECUTE(); } if (!lenient && !IS_HEADER_CHAR(ch)) { SET_ERRNO(HPE_INVALID_HEADER_TOKEN); goto error; } c = LOWER(ch); switch (h_state) { case h_general: { size_t left = data + len - p; const char* pe = p + MIN(left, max_header_size); for (; p != pe; p++) { ch = *p; if (ch == CR || ch == LF) { --p; break; } if (!lenient && !IS_HEADER_CHAR(ch)) { SET_ERRNO(HPE_INVALID_HEADER_TOKEN); goto error; } } if (p == data + len) --p; break; } case h_connection: case h_transfer_encoding: assert(0 && "Shouldn't get here."); break; case h_content_length: if (ch == ' ') break; h_state = h_content_length_num; /* fall through */ case h_content_length_num: { uint64_t t; if (ch == ' ') { h_state = h_content_length_ws; break; } if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); parser->header_state = h_state; goto error; } t = parser->content_length; t *= 10; t += ch - '0'; /* Overflow? Test against a conservative limit for simplicity. */ if (UNLIKELY((ULLONG_MAX - 10) / 10 < parser->content_length)) { SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); parser->header_state = h_state; goto error; } parser->content_length = t; break; } case h_content_length_ws: if (ch == ' ') break; SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); parser->header_state = h_state; goto error; /* Transfer-Encoding: chunked */ case h_matching_transfer_encoding_token_start: /* looking for 'Transfer-Encoding: chunked' */ if ('c' == c) { h_state = h_matching_transfer_encoding_chunked; } else if (STRICT_TOKEN(c)) { /* TODO(indutny): similar code below does this, but why? * At the very least it seems to be inconsistent given that * h_matching_transfer_encoding_token does not check for * `STRICT_TOKEN` */ h_state = h_matching_transfer_encoding_token; } else if (c == ' ' || c == '\t') { /* Skip lws */ } else { h_state = h_general; } break; case h_matching_transfer_encoding_chunked: parser->index++; if (parser->index > sizeof(CHUNKED)-1 || c != CHUNKED[parser->index]) { h_state = h_matching_transfer_encoding_token; } else if (parser->index == sizeof(CHUNKED)-2) { h_state = h_transfer_encoding_chunked; } break; case h_matching_transfer_encoding_token: if (ch == ',') { h_state = h_matching_transfer_encoding_token_start; parser->index = 0; } break; case h_matching_connection_token_start: /* looking for 'Connection: keep-alive' */ if (c == 'k') { h_state = h_matching_connection_keep_alive; /* looking for 'Connection: close' */ } else if (c == 'c') { h_state = h_matching_connection_close; } else if (c == 'u') { h_state = h_matching_connection_upgrade; } else if (STRICT_TOKEN(c)) { h_state = h_matching_connection_token; } else if (c == ' ' || c == '\t') { /* Skip lws */ } else { h_state = h_general; } break; /* looking for 'Connection: keep-alive' */ case h_matching_connection_keep_alive: parser->index++; if (parser->index > sizeof(KEEP_ALIVE)-1 || c != KEEP_ALIVE[parser->index]) { h_state = h_matching_connection_token; } else if (parser->index == sizeof(KEEP_ALIVE)-2) { h_state = h_connection_keep_alive; } break; /* looking for 'Connection: close' */ case h_matching_connection_close: parser->index++; if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) { h_state = h_matching_connection_token; } else if (parser->index == sizeof(CLOSE)-2) { h_state = h_connection_close; } break; /* looking for 'Connection: upgrade' */ case h_matching_connection_upgrade: parser->index++; if (parser->index > sizeof(UPGRADE) - 1 || c != UPGRADE[parser->index]) { h_state = h_matching_connection_token; } else if (parser->index == sizeof(UPGRADE)-2) { h_state = h_connection_upgrade; } break; case h_matching_connection_token: if (ch == ',') { h_state = h_matching_connection_token_start; parser->index = 0; } break; case h_transfer_encoding_chunked: if (ch != ' ') h_state = h_matching_transfer_encoding_token; break; case h_connection_keep_alive: case h_connection_close: case h_connection_upgrade: if (ch == ',') { if (h_state == h_connection_keep_alive) { parser->flags |= F_CONNECTION_KEEP_ALIVE; } else if (h_state == h_connection_close) { parser->flags |= F_CONNECTION_CLOSE; } else if (h_state == h_connection_upgrade) { parser->flags |= F_CONNECTION_UPGRADE; } h_state = h_matching_connection_token_start; parser->index = 0; } else if (ch != ' ') { h_state = h_matching_connection_token; } break; default: UPDATE_STATE(s_header_value); h_state = h_general; break; } } parser->header_state = h_state; if (p == data + len) --p; COUNT_HEADER_SIZE(p - start); break; } case s_header_almost_done: { if (UNLIKELY(ch != LF)) { SET_ERRNO(HPE_LF_EXPECTED); goto error; } UPDATE_STATE(s_header_value_lws); break; } case s_header_value_lws: { if (ch == ' ' || ch == '\t') { if (parser->header_state == h_content_length_num) { /* treat obsolete line folding as space */ parser->header_state = h_content_length_ws; } UPDATE_STATE(s_header_value_start); REEXECUTE(); } /* finished the header */ switch (parser->header_state) { case h_connection_keep_alive: parser->flags |= F_CONNECTION_KEEP_ALIVE; break; case h_connection_close: parser->flags |= F_CONNECTION_CLOSE; break; case h_transfer_encoding_chunked: parser->flags |= F_CHUNKED; break; case h_connection_upgrade: parser->flags |= F_CONNECTION_UPGRADE; break; default: break; } UPDATE_STATE(s_header_field_start); REEXECUTE(); } case s_header_value_discard_ws_almost_done: { STRICT_CHECK(ch != LF); UPDATE_STATE(s_header_value_discard_lws); break; } case s_header_value_discard_lws: { if (ch == ' ' || ch == '\t') { UPDATE_STATE(s_header_value_discard_ws); break; } else { switch (parser->header_state) { case h_connection_keep_alive: parser->flags |= F_CONNECTION_KEEP_ALIVE; break; case h_connection_close: parser->flags |= F_CONNECTION_CLOSE; break; case h_connection_upgrade: parser->flags |= F_CONNECTION_UPGRADE; break; case h_transfer_encoding_chunked: parser->flags |= F_CHUNKED; break; case h_content_length: /* do not allow empty content length */ SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); goto error; break; default: break; } /* header value was empty */ MARK(header_value); UPDATE_STATE(s_header_field_start); CALLBACK_DATA_NOADVANCE(header_value); REEXECUTE(); } } case s_headers_almost_done: { STRICT_CHECK(ch != LF); if (parser->flags & F_TRAILING) { /* End of a chunked request */ UPDATE_STATE(s_message_done); CALLBACK_NOTIFY_NOADVANCE(chunk_complete); REEXECUTE(); } /* Cannot us transfer-encoding and a content-length header together per the HTTP specification. (RFC 7230 Section 3.3.3) */ if ((parser->extra_flags & (F_TRANSFER_ENCODING >> 8)) && (parser->flags & F_CONTENTLENGTH)) { /* Allow it for lenient parsing as long as `Transfer-Encoding` is * not `chunked` */ if (!lenient || (parser->flags & F_CHUNKED)) { SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH); goto error; } } UPDATE_STATE(s_headers_done); /* Set this here so that on_headers_complete() callbacks can see it */ if ((parser->flags & F_UPGRADE) && (parser->flags & F_CONNECTION_UPGRADE)) { /* For responses, "Upgrade: foo" and "Connection: upgrade" are * mandatory only when it is a 101 Switching Protocols response, * otherwise it is purely informational, to announce support. */ parser->upgrade = (parser->type == HTTP_REQUEST || parser->status_code == 101); } else { parser->upgrade = (parser->method == HTTP_CONNECT); } /* Here we call the headers_complete callback. This is somewhat * different than other callbacks because if the user returns 1, we * will interpret that as saying that this message has no body. This * is needed for the annoying case of recieving a response to a HEAD * request. * * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so * we have to simulate it by handling a change in errno below. */ if (settings->on_headers_complete) { switch (settings->on_headers_complete(parser)) { case 0: break; case 2: parser->upgrade = 1; /* fall through */ case 1: parser->flags |= F_SKIPBODY; break; default: SET_ERRNO(HPE_CB_headers_complete); RETURN(p - data); /* Error */ } } if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { RETURN(p - data); } REEXECUTE(); } case s_headers_done: { int hasBody; STRICT_CHECK(ch != LF); parser->nread = 0; nread = 0; hasBody = parser->flags & F_CHUNKED || (parser->content_length > 0 && parser->content_length != ULLONG_MAX); if (parser->upgrade && (parser->method == HTTP_CONNECT || (parser->flags & F_SKIPBODY) || !hasBody)) { /* Exit, the rest of the message is in a different protocol. */ UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); RETURN((p - data) + 1); } if (parser->flags & F_SKIPBODY) { UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); } else if (parser->flags & F_CHUNKED) { /* chunked encoding - ignore Content-Length header, * prepare for a chunk */ UPDATE_STATE(s_chunk_size_start); } else if (parser->extra_flags & (F_TRANSFER_ENCODING >> 8)) { if (parser->type == HTTP_REQUEST && !lenient) { /* RFC 7230 3.3.3 */ /* If a Transfer-Encoding header field * is present in a request and the chunked transfer coding is not * the final encoding, the message body length cannot be determined * reliably; the server MUST respond with the 400 (Bad Request) * status code and then close the connection. */ SET_ERRNO(HPE_INVALID_TRANSFER_ENCODING); RETURN(p - data); /* Error */ } else { /* RFC 7230 3.3.3 */ /* If a Transfer-Encoding header field is present in a response and * the chunked transfer coding is not the final encoding, the * message body length is determined by reading the connection until * it is closed by the server. */ UPDATE_STATE(s_body_identity_eof); } } else { if (parser->content_length == 0) { /* Content-Length header given but zero: Content-Length: 0\r\n */ UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); } else if (parser->content_length != ULLONG_MAX) { /* Content-Length header given and non-zero */ UPDATE_STATE(s_body_identity); } else { if (!http_message_needs_eof(parser)) { /* Assume content-length 0 - read the next */ UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); } else { /* Read body until EOF */ UPDATE_STATE(s_body_identity_eof); } } } break; } case s_body_identity: { uint64_t to_read = MIN(parser->content_length, (uint64_t) ((data + len) - p)); assert(parser->content_length != 0 && parser->content_length != ULLONG_MAX); /* The difference between advancing content_length and p is because * the latter will automaticaly advance on the next loop iteration. * Further, if content_length ends up at 0, we want to see the last * byte again for our message complete callback. */ MARK(body); parser->content_length -= to_read; p += to_read - 1; if (parser->content_length == 0) { UPDATE_STATE(s_message_done); /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte. * * The alternative to doing this is to wait for the next byte to * trigger the data callback, just as in every other case. The * problem with this is that this makes it difficult for the test * harness to distinguish between complete-on-EOF and * complete-on-length. It's not clear that this distinction is * important for applications, but let's keep it for now. */ CALLBACK_DATA_(body, p - body_mark + 1, p - data); REEXECUTE(); } break; } /* read until EOF */ case s_body_identity_eof: MARK(body); p = data + len - 1; break; case s_message_done: UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); if (parser->upgrade) { /* Exit, the rest of the message is in a different protocol. */ RETURN((p - data) + 1); } break; case s_chunk_size_start: { assert(nread == 1); assert(parser->flags & F_CHUNKED); unhex_val = unhex[(unsigned char)ch]; if (UNLIKELY(unhex_val == -1)) { SET_ERRNO(HPE_INVALID_CHUNK_SIZE); goto error; } parser->content_length = unhex_val; UPDATE_STATE(s_chunk_size); break; } case s_chunk_size: { uint64_t t; assert(parser->flags & F_CHUNKED); if (ch == CR) { UPDATE_STATE(s_chunk_size_almost_done); break; } unhex_val = unhex[(unsigned char)ch]; if (unhex_val == -1) { if (ch == ';' || ch == ' ') { UPDATE_STATE(s_chunk_parameters); break; } SET_ERRNO(HPE_INVALID_CHUNK_SIZE); goto error; } t = parser->content_length; t *= 16; t += unhex_val; /* Overflow? Test against a conservative limit for simplicity. */ if (UNLIKELY((ULLONG_MAX - 16) / 16 < parser->content_length)) { SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); goto error; } parser->content_length = t; break; } case s_chunk_parameters: { assert(parser->flags & F_CHUNKED); /* just ignore this shit. TODO check for overflow */ if (ch == CR) { UPDATE_STATE(s_chunk_size_almost_done); break; } break; } case s_chunk_size_almost_done: { assert(parser->flags & F_CHUNKED); STRICT_CHECK(ch != LF); parser->nread = 0; nread = 0; if (parser->content_length == 0) { parser->flags |= F_TRAILING; UPDATE_STATE(s_header_field_start); } else { UPDATE_STATE(s_chunk_data); } CALLBACK_NOTIFY(chunk_header); break; } case s_chunk_data: { uint64_t to_read = MIN(parser->content_length, (uint64_t) ((data + len) - p)); assert(parser->flags & F_CHUNKED); assert(parser->content_length != 0 && parser->content_length != ULLONG_MAX); /* See the explanation in s_body_identity for why the content * length and data pointers are managed this way. */ MARK(body); parser->content_length -= to_read; p += to_read - 1; if (parser->content_length == 0) { UPDATE_STATE(s_chunk_data_almost_done); } break; } case s_chunk_data_almost_done: assert(parser->flags & F_CHUNKED); assert(parser->content_length == 0); STRICT_CHECK(ch != CR); UPDATE_STATE(s_chunk_data_done); CALLBACK_DATA(body); break; case s_chunk_data_done: assert(parser->flags & F_CHUNKED); STRICT_CHECK(ch != LF); parser->nread = 0; nread = 0; UPDATE_STATE(s_chunk_size_start); CALLBACK_NOTIFY(chunk_complete); break; default: assert(0 && "unhandled state"); SET_ERRNO(HPE_INVALID_INTERNAL_STATE); goto error; } } /* Run callbacks for any marks that we have leftover after we ran out of * bytes. There should be at most one of these set, so it's OK to invoke * them in series (unset marks will not result in callbacks). * * We use the NOADVANCE() variety of callbacks here because 'p' has already * overflowed 'data' and this allows us to correct for the off-by-one that * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p' * value that's in-bounds). */ assert(((header_field_mark ? 1 : 0) + (header_value_mark ? 1 : 0) + (url_mark ? 1 : 0) + (body_mark ? 1 : 0) + (status_mark ? 1 : 0)) <= 1); CALLBACK_DATA_NOADVANCE(header_field); CALLBACK_DATA_NOADVANCE(header_value); CALLBACK_DATA_NOADVANCE(url); CALLBACK_DATA_NOADVANCE(body); CALLBACK_DATA_NOADVANCE(status); RETURN(len); error: if (HTTP_PARSER_ERRNO(parser) == HPE_OK) { SET_ERRNO(HPE_UNKNOWN); } RETURN(p - data); } /* Does the parser need to see an EOF to find the end of the message? */ int http_message_needs_eof (const http_parser *parser) { if (parser->type == HTTP_REQUEST) { return 0; } /* See RFC 2616 section 4.4 */ if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ parser->status_code == 204 || /* No Content */ parser->status_code == 304 || /* Not Modified */ parser->flags & F_SKIPBODY) { /* response to a HEAD request */ return 0; } /* RFC 7230 3.3.3, see `s_headers_almost_done` */ if ((parser->extra_flags & (F_TRANSFER_ENCODING >> 8)) && (parser->flags & F_CHUNKED) == 0) { return 1; } if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) { return 0; } return 1; } int http_should_keep_alive (const http_parser *parser) { if (parser->http_major > 0 && parser->http_minor > 0) { /* HTTP/1.1 */ if (parser->flags & F_CONNECTION_CLOSE) { return 0; } } else { /* HTTP/1.0 or earlier */ if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { return 0; } } return !http_message_needs_eof(parser); } const char * http_method_str (enum http_method m) { return ELEM_AT(method_strings, m, ""); } const char * http_status_str (enum http_status s) { switch (s) { #define XX(num, name, string) case HTTP_STATUS_##name: return #string; HTTP_STATUS_MAP(XX) #undef XX default: return ""; } } void http_parser_init (http_parser *parser, enum http_parser_type t) { void *data = parser->data; /* preserve application data */ memset(parser, 0, sizeof(*parser)); parser->data = data; parser->type = t; parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res)); parser->http_errno = HPE_OK; } void http_parser_settings_init(http_parser_settings *settings) { memset(settings, 0, sizeof(*settings)); } const char * http_errno_name(enum http_errno err) { assert(((size_t) err) < ARRAY_SIZE(http_strerror_tab)); return http_strerror_tab[err].name; } const char * http_errno_description(enum http_errno err) { assert(((size_t) err) < ARRAY_SIZE(http_strerror_tab)); return http_strerror_tab[err].description; } static enum http_host_state http_parse_host_char(enum http_host_state s, const char ch) { switch(s) { case s_http_userinfo: case s_http_userinfo_start: if (ch == '@') { return s_http_host_start; } if (IS_USERINFO_CHAR(ch)) { return s_http_userinfo; } break; case s_http_host_start: if (ch == '[') { return s_http_host_v6_start; } if (IS_HOST_CHAR(ch)) { return s_http_host; } break; case s_http_host: if (IS_HOST_CHAR(ch)) { return s_http_host; } /* fall through */ case s_http_host_v6_end: if (ch == ':') { return s_http_host_port_start; } break; case s_http_host_v6: if (ch == ']') { return s_http_host_v6_end; } /* fall through */ case s_http_host_v6_start: if (IS_HEX(ch) || ch == ':' || ch == '.') { return s_http_host_v6; } if (s == s_http_host_v6 && ch == '%') { return s_http_host_v6_zone_start; } break; case s_http_host_v6_zone: if (ch == ']') { return s_http_host_v6_end; } /* fall through */ case s_http_host_v6_zone_start: /* RFC 6874 Zone ID consists of 1*( unreserved / pct-encoded) */ if (IS_ALPHANUM(ch) || ch == '%' || ch == '.' || ch == '-' || ch == '_' || ch == '~') { return s_http_host_v6_zone; } break; case s_http_host_port: case s_http_host_port_start: if (IS_NUM(ch)) { return s_http_host_port; } break; default: break; } return s_http_host_dead; } static int http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { enum http_host_state s; const char *p; size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len; assert(u->field_set & (1 << UF_HOST)); u->field_data[UF_HOST].len = 0; s = found_at ? s_http_userinfo_start : s_http_host_start; for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) { enum http_host_state new_s = http_parse_host_char(s, *p); if (new_s == s_http_host_dead) { return 1; } switch(new_s) { case s_http_host: if (s != s_http_host) { u->field_data[UF_HOST].off = (uint16_t)(p - buf); } u->field_data[UF_HOST].len++; break; case s_http_host_v6: if (s != s_http_host_v6) { u->field_data[UF_HOST].off = (uint16_t)(p - buf); } u->field_data[UF_HOST].len++; break; case s_http_host_v6_zone_start: case s_http_host_v6_zone: u->field_data[UF_HOST].len++; break; case s_http_host_port: if (s != s_http_host_port) { u->field_data[UF_PORT].off = (uint16_t)(p - buf); u->field_data[UF_PORT].len = 0; u->field_set |= (1 << UF_PORT); } u->field_data[UF_PORT].len++; break; case s_http_userinfo: if (s != s_http_userinfo) { u->field_data[UF_USERINFO].off = (uint16_t)(p - buf); u->field_data[UF_USERINFO].len = 0; u->field_set |= (1 << UF_USERINFO); } u->field_data[UF_USERINFO].len++; break; default: break; } s = new_s; } /* Make sure we don't end somewhere unexpected */ switch (s) { case s_http_host_start: case s_http_host_v6_start: case s_http_host_v6: case s_http_host_v6_zone_start: case s_http_host_v6_zone: case s_http_host_port_start: case s_http_userinfo: case s_http_userinfo_start: return 1; default: break; } return 0; } void http_parser_url_init(struct http_parser_url *u) { memset(u, 0, sizeof(*u)); } int http_parser_parse_url(const char *buf, size_t buflen, int is_connect, struct http_parser_url *u) { enum state s; const char *p; enum http_parser_url_fields uf, old_uf; int found_at = 0; if (buflen == 0) { return 1; } u->port = u->field_set = 0; s = is_connect ? s_req_server_start : s_req_spaces_before_url; old_uf = UF_MAX; for (p = buf; p < buf + buflen; p++) { s = parse_url_char(s, *p); /* Figure out the next field that we're operating on */ switch (s) { case s_dead: return 1; /* Skip delimeters */ case s_req_schema_slash: case s_req_schema_slash_slash: case s_req_server_start: case s_req_query_string_start: case s_req_fragment_start: continue; case s_req_schema: uf = UF_SCHEMA; break; case s_req_server_with_at: found_at = 1; /* fall through */ case s_req_server: uf = UF_HOST; break; case s_req_path: uf = UF_PATH; break; case s_req_query_string: uf = UF_QUERY; break; case s_req_fragment: uf = UF_FRAGMENT; break; default: assert(!"Unexpected state"); return 1; } /* Nothing's changed; soldier on */ if (uf == old_uf) { u->field_data[uf].len++; continue; } u->field_data[uf].off = (uint16_t)(p - buf); u->field_data[uf].len = 1; u->field_set |= (1 << uf); old_uf = uf; } /* host must be present if there is a schema */ /* parsing http:///toto will fail */ if ((u->field_set & (1 << UF_SCHEMA)) && (u->field_set & (1 << UF_HOST)) == 0) { return 1; } if (u->field_set & (1 << UF_HOST)) { if (http_parse_host(buf, u, found_at) != 0) { return 1; } } /* CONNECT requests can only contain "hostname:port" */ if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) { return 1; } if (u->field_set & (1 << UF_PORT)) { uint16_t off; uint16_t len; const char* p; const char* end; unsigned long v; off = u->field_data[UF_PORT].off; len = u->field_data[UF_PORT].len; end = buf + off + len; /* NOTE: The characters are already validated and are in the [0-9] range */ assert(off + len <= buflen && "Port number overflow"); v = 0; for (p = buf + off; p < end; p++) { v *= 10; v += *p - '0'; /* Ports have a max value of 2^16 */ if (v > 0xffff) { return 1; } } u->port = (uint16_t) v; } return 0; } void http_parser_pause(http_parser *parser, int paused) { /* Users should only be pausing/unpausing a parser that is not in an error * state. In non-debug builds, there's not much that we can do about this * other than ignore it. */ if (HTTP_PARSER_ERRNO(parser) == HPE_OK || HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) { uint32_t nread = parser->nread; /* used by the SET_ERRNO macro */ SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK); } else { assert(0 && "Attempting to pause parser in error state"); } } int http_body_is_final(const struct http_parser *parser) { return parser->state == s_message_done; } unsigned long http_parser_version(void) { return HTTP_PARSER_VERSION_MAJOR * 0x10000 | HTTP_PARSER_VERSION_MINOR * 0x00100 | HTTP_PARSER_VERSION_PATCH * 0x00001; } void http_parser_set_max_header_size(uint32_t size) { max_header_size = size; } psi-plus-snapshots-1.4.1456/3rdparty/http-parser/http_parser.gyp000066400000000000000000000054471370065651000246620ustar00rootroot00000000000000# This file is used with the GYP meta build system. # http://code.google.com/p/gyp/ # To build try this: # svn co http://gyp.googlecode.com/svn/trunk gyp # ./gyp/gyp -f make --depth=`pwd` http_parser.gyp # ./out/Debug/test { 'target_defaults': { 'default_configuration': 'Debug', 'configurations': { # TODO: hoist these out and put them somewhere common, because # RuntimeLibrary MUST MATCH across the entire project 'Debug': { 'defines': [ 'DEBUG', '_DEBUG' ], 'cflags': [ '-Wall', '-Wextra', '-O0', '-g', '-ftrapv' ], 'msvs_settings': { 'VCCLCompilerTool': { 'RuntimeLibrary': 1, # static debug }, }, }, 'Release': { 'defines': [ 'NDEBUG' ], 'cflags': [ '-Wall', '-Wextra', '-O3' ], 'msvs_settings': { 'VCCLCompilerTool': { 'RuntimeLibrary': 0, # static release }, }, } }, 'msvs_settings': { 'VCCLCompilerTool': { }, 'VCLibrarianTool': { }, 'VCLinkerTool': { 'GenerateDebugInformation': 'true', }, }, 'conditions': [ ['OS == "win"', { 'defines': [ 'WIN32' ], }] ], }, 'targets': [ { 'target_name': 'http_parser', 'type': 'static_library', 'include_dirs': [ '.' ], 'direct_dependent_settings': { 'defines': [ 'HTTP_PARSER_STRICT=0' ], 'include_dirs': [ '.' ], }, 'defines': [ 'HTTP_PARSER_STRICT=0' ], 'sources': [ './http_parser.c', ], 'conditions': [ ['OS=="win"', { 'msvs_settings': { 'VCCLCompilerTool': { # Compile as C++. http_parser.c is actually C99, but C++ is # close enough in this case. 'CompileAs': 2, }, }, }] ], }, { 'target_name': 'http_parser_strict', 'type': 'static_library', 'include_dirs': [ '.' ], 'direct_dependent_settings': { 'defines': [ 'HTTP_PARSER_STRICT=1' ], 'include_dirs': [ '.' ], }, 'defines': [ 'HTTP_PARSER_STRICT=1' ], 'sources': [ './http_parser.c', ], 'conditions': [ ['OS=="win"', { 'msvs_settings': { 'VCCLCompilerTool': { # Compile as C++. http_parser.c is actually C99, but C++ is # close enough in this case. 'CompileAs': 2, }, }, }] ], }, { 'target_name': 'test-nonstrict', 'type': 'executable', 'dependencies': [ 'http_parser' ], 'sources': [ 'test.c' ] }, { 'target_name': 'test-strict', 'type': 'executable', 'dependencies': [ 'http_parser_strict' ], 'sources': [ 'test.c' ] } ] } psi-plus-snapshots-1.4.1456/3rdparty/http-parser/http_parser.h000066400000000000000000000454711370065651000243130ustar00rootroot00000000000000/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef http_parser_h #define http_parser_h #ifdef __cplusplus extern "C" { #endif /* Also update SONAME in the Makefile whenever you change these. */ #define HTTP_PARSER_VERSION_MAJOR 2 #define HTTP_PARSER_VERSION_MINOR 9 #define HTTP_PARSER_VERSION_PATCH 4 #include #if defined(_WIN32) && !defined(__MINGW32__) && \ (!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__) #include typedef __int8 int8_t; typedef unsigned __int8 uint8_t; typedef __int16 int16_t; typedef unsigned __int16 uint16_t; typedef __int32 int32_t; typedef unsigned __int32 uint32_t; typedef __int64 int64_t; typedef unsigned __int64 uint64_t; #else #include #endif /* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run * faster */ #ifndef HTTP_PARSER_STRICT # define HTTP_PARSER_STRICT 1 #endif /* Maximium header size allowed. If the macro is not defined * before including this header then the default is used. To * change the maximum header size, define the macro in the build * environment (e.g. -DHTTP_MAX_HEADER_SIZE=). To remove * the effective limit on the size of the header, define the macro * to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff) */ #ifndef HTTP_MAX_HEADER_SIZE # define HTTP_MAX_HEADER_SIZE (80*1024) #endif typedef struct http_parser http_parser; typedef struct http_parser_settings http_parser_settings; /* Callbacks should return non-zero to indicate an error. The parser will * then halt execution. * * The one exception is on_headers_complete. In a HTTP_RESPONSE parser * returning '1' from on_headers_complete will tell the parser that it * should not expect a body. This is used when receiving a response to a * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: * chunked' headers that indicate the presence of a body. * * Returning `2` from on_headers_complete will tell parser that it should not * expect neither a body nor any futher responses on this connection. This is * useful for handling responses to a CONNECT request which may not contain * `Upgrade` or `Connection: upgrade` headers. * * http_data_cb does not return data chunks. It will be called arbitrarily * many times for each string. E.G. you might get 10 callbacks for "on_url" * each providing just a few characters more data. */ typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); typedef int (*http_cb) (http_parser*); /* Status Codes */ #define HTTP_STATUS_MAP(XX) \ XX(100, CONTINUE, Continue) \ XX(101, SWITCHING_PROTOCOLS, Switching Protocols) \ XX(102, PROCESSING, Processing) \ XX(200, OK, OK) \ XX(201, CREATED, Created) \ XX(202, ACCEPTED, Accepted) \ XX(203, NON_AUTHORITATIVE_INFORMATION, Non-Authoritative Information) \ XX(204, NO_CONTENT, No Content) \ XX(205, RESET_CONTENT, Reset Content) \ XX(206, PARTIAL_CONTENT, Partial Content) \ XX(207, MULTI_STATUS, Multi-Status) \ XX(208, ALREADY_REPORTED, Already Reported) \ XX(226, IM_USED, IM Used) \ XX(300, MULTIPLE_CHOICES, Multiple Choices) \ XX(301, MOVED_PERMANENTLY, Moved Permanently) \ XX(302, FOUND, Found) \ XX(303, SEE_OTHER, See Other) \ XX(304, NOT_MODIFIED, Not Modified) \ XX(305, USE_PROXY, Use Proxy) \ XX(307, TEMPORARY_REDIRECT, Temporary Redirect) \ XX(308, PERMANENT_REDIRECT, Permanent Redirect) \ XX(400, BAD_REQUEST, Bad Request) \ XX(401, UNAUTHORIZED, Unauthorized) \ XX(402, PAYMENT_REQUIRED, Payment Required) \ XX(403, FORBIDDEN, Forbidden) \ XX(404, NOT_FOUND, Not Found) \ XX(405, METHOD_NOT_ALLOWED, Method Not Allowed) \ XX(406, NOT_ACCEPTABLE, Not Acceptable) \ XX(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required) \ XX(408, REQUEST_TIMEOUT, Request Timeout) \ XX(409, CONFLICT, Conflict) \ XX(410, GONE, Gone) \ XX(411, LENGTH_REQUIRED, Length Required) \ XX(412, PRECONDITION_FAILED, Precondition Failed) \ XX(413, PAYLOAD_TOO_LARGE, Payload Too Large) \ XX(414, URI_TOO_LONG, URI Too Long) \ XX(415, UNSUPPORTED_MEDIA_TYPE, Unsupported Media Type) \ XX(416, RANGE_NOT_SATISFIABLE, Range Not Satisfiable) \ XX(417, EXPECTATION_FAILED, Expectation Failed) \ XX(421, MISDIRECTED_REQUEST, Misdirected Request) \ XX(422, UNPROCESSABLE_ENTITY, Unprocessable Entity) \ XX(423, LOCKED, Locked) \ XX(424, FAILED_DEPENDENCY, Failed Dependency) \ XX(426, UPGRADE_REQUIRED, Upgrade Required) \ XX(428, PRECONDITION_REQUIRED, Precondition Required) \ XX(429, TOO_MANY_REQUESTS, Too Many Requests) \ XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \ XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, Unavailable For Legal Reasons) \ XX(500, INTERNAL_SERVER_ERROR, Internal Server Error) \ XX(501, NOT_IMPLEMENTED, Not Implemented) \ XX(502, BAD_GATEWAY, Bad Gateway) \ XX(503, SERVICE_UNAVAILABLE, Service Unavailable) \ XX(504, GATEWAY_TIMEOUT, Gateway Timeout) \ XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported) \ XX(506, VARIANT_ALSO_NEGOTIATES, Variant Also Negotiates) \ XX(507, INSUFFICIENT_STORAGE, Insufficient Storage) \ XX(508, LOOP_DETECTED, Loop Detected) \ XX(510, NOT_EXTENDED, Not Extended) \ XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required) \ enum http_status { #define XX(num, name, string) HTTP_STATUS_##name = num, HTTP_STATUS_MAP(XX) #undef XX }; /* Request Methods */ #define HTTP_METHOD_MAP(XX) \ XX(0, DELETE, DELETE) \ XX(1, GET, GET) \ XX(2, HEAD, HEAD) \ XX(3, POST, POST) \ XX(4, PUT, PUT) \ /* pathological */ \ XX(5, CONNECT, CONNECT) \ XX(6, OPTIONS, OPTIONS) \ XX(7, TRACE, TRACE) \ /* WebDAV */ \ XX(8, COPY, COPY) \ XX(9, LOCK, LOCK) \ XX(10, MKCOL, MKCOL) \ XX(11, MOVE, MOVE) \ XX(12, PROPFIND, PROPFIND) \ XX(13, PROPPATCH, PROPPATCH) \ XX(14, SEARCH, SEARCH) \ XX(15, UNLOCK, UNLOCK) \ XX(16, BIND, BIND) \ XX(17, REBIND, REBIND) \ XX(18, UNBIND, UNBIND) \ XX(19, ACL, ACL) \ /* subversion */ \ XX(20, REPORT, REPORT) \ XX(21, MKACTIVITY, MKACTIVITY) \ XX(22, CHECKOUT, CHECKOUT) \ XX(23, MERGE, MERGE) \ /* upnp */ \ XX(24, MSEARCH, M-SEARCH) \ XX(25, NOTIFY, NOTIFY) \ XX(26, SUBSCRIBE, SUBSCRIBE) \ XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ /* RFC-5789 */ \ XX(28, PATCH, PATCH) \ XX(29, PURGE, PURGE) \ /* CalDAV */ \ XX(30, MKCALENDAR, MKCALENDAR) \ /* RFC-2068, section 19.6.1.2 */ \ XX(31, LINK, LINK) \ XX(32, UNLINK, UNLINK) \ /* icecast */ \ XX(33, SOURCE, SOURCE) \ enum http_method { #define XX(num, name, string) HTTP_##name = num, HTTP_METHOD_MAP(XX) #undef XX }; enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; /* Flag values for http_parser.flags field */ enum flags { F_CHUNKED = 1 << 0 , F_CONNECTION_KEEP_ALIVE = 1 << 1 , F_CONNECTION_CLOSE = 1 << 2 , F_CONNECTION_UPGRADE = 1 << 3 , F_TRAILING = 1 << 4 , F_UPGRADE = 1 << 5 , F_SKIPBODY = 1 << 6 , F_CONTENTLENGTH = 1 << 7 , F_TRANSFER_ENCODING = 1 << 8 /* Never set in http_parser.flags */ }; /* Map for errno-related constants * * The provided argument should be a macro that takes 2 arguments. */ #define HTTP_ERRNO_MAP(XX) \ /* No error */ \ XX(OK, "success") \ \ /* Callback-related errors */ \ XX(CB_message_begin, "the on_message_begin callback failed") \ XX(CB_url, "the on_url callback failed") \ XX(CB_header_field, "the on_header_field callback failed") \ XX(CB_header_value, "the on_header_value callback failed") \ XX(CB_headers_complete, "the on_headers_complete callback failed") \ XX(CB_body, "the on_body callback failed") \ XX(CB_message_complete, "the on_message_complete callback failed") \ XX(CB_status, "the on_status callback failed") \ XX(CB_chunk_header, "the on_chunk_header callback failed") \ XX(CB_chunk_complete, "the on_chunk_complete callback failed") \ \ /* Parsing-related errors */ \ XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ XX(HEADER_OVERFLOW, \ "too many header bytes seen; overflow detected") \ XX(CLOSED_CONNECTION, \ "data received after completed connection: close message") \ XX(INVALID_VERSION, "invalid HTTP version") \ XX(INVALID_STATUS, "invalid HTTP status code") \ XX(INVALID_METHOD, "invalid HTTP method") \ XX(INVALID_URL, "invalid URL") \ XX(INVALID_HOST, "invalid host") \ XX(INVALID_PORT, "invalid port") \ XX(INVALID_PATH, "invalid path") \ XX(INVALID_QUERY_STRING, "invalid query string") \ XX(INVALID_FRAGMENT, "invalid fragment") \ XX(LF_EXPECTED, "LF character expected") \ XX(INVALID_HEADER_TOKEN, "invalid character in header") \ XX(INVALID_CONTENT_LENGTH, \ "invalid character in content-length header") \ XX(UNEXPECTED_CONTENT_LENGTH, \ "unexpected content-length header") \ XX(INVALID_CHUNK_SIZE, \ "invalid character in chunk size header") \ XX(INVALID_CONSTANT, "invalid constant string") \ XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ XX(STRICT, "strict mode assertion failed") \ XX(PAUSED, "parser is paused") \ XX(UNKNOWN, "an unknown error occurred") \ XX(INVALID_TRANSFER_ENCODING, \ "request has invalid transfer-encoding") \ /* Define HPE_* values for each errno value above */ #define HTTP_ERRNO_GEN(n, s) HPE_##n, enum http_errno { HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) }; #undef HTTP_ERRNO_GEN /* Get an http_errno value from an http_parser */ #define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) struct http_parser { /** PRIVATE **/ unsigned int type : 2; /* enum http_parser_type */ unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */ unsigned int state : 7; /* enum state from http_parser.c */ unsigned int header_state : 7; /* enum header_state from http_parser.c */ unsigned int index : 5; /* index into current matcher */ unsigned int extra_flags : 2; unsigned int lenient_http_headers : 1; uint32_t nread; /* # bytes read in various scenarios */ uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ /** READ-ONLY **/ unsigned short http_major; unsigned short http_minor; unsigned int status_code : 16; /* responses only */ unsigned int method : 8; /* requests only */ unsigned int http_errno : 7; /* 1 = Upgrade header was present and the parser has exited because of that. * 0 = No upgrade header present. * Should be checked when http_parser_execute() returns in addition to * error checking. */ unsigned int upgrade : 1; /** PUBLIC **/ void *data; /* A pointer to get hook to the "connection" or "socket" object */ }; struct http_parser_settings { http_cb on_message_begin; http_data_cb on_url; http_data_cb on_status; http_data_cb on_header_field; http_data_cb on_header_value; http_cb on_headers_complete; http_data_cb on_body; http_cb on_message_complete; /* When on_chunk_header is called, the current chunk length is stored * in parser->content_length. */ http_cb on_chunk_header; http_cb on_chunk_complete; }; enum http_parser_url_fields { UF_SCHEMA = 0 , UF_HOST = 1 , UF_PORT = 2 , UF_PATH = 3 , UF_QUERY = 4 , UF_FRAGMENT = 5 , UF_USERINFO = 6 , UF_MAX = 7 }; /* Result structure for http_parser_parse_url(). * * Callers should index into field_data[] with UF_* values iff field_set * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and * because we probably have padding left over), we convert any port to * a uint16_t. */ struct http_parser_url { uint16_t field_set; /* Bitmask of (1 << UF_*) values */ uint16_t port; /* Converted UF_PORT string */ struct { uint16_t off; /* Offset into buffer in which field starts */ uint16_t len; /* Length of run in buffer */ } field_data[UF_MAX]; }; /* Returns the library version. Bits 16-23 contain the major version number, * bits 8-15 the minor version number and bits 0-7 the patch level. * Usage example: * * unsigned long version = http_parser_version(); * unsigned major = (version >> 16) & 255; * unsigned minor = (version >> 8) & 255; * unsigned patch = version & 255; * printf("http_parser v%u.%u.%u\n", major, minor, patch); */ unsigned long http_parser_version(void); void http_parser_init(http_parser *parser, enum http_parser_type type); /* Initialize http_parser_settings members to 0 */ void http_parser_settings_init(http_parser_settings *settings); /* Executes the parser. Returns number of parsed bytes. Sets * `parser->http_errno` on error. */ size_t http_parser_execute(http_parser *parser, const http_parser_settings *settings, const char *data, size_t len); /* If http_should_keep_alive() in the on_headers_complete or * on_message_complete callback returns 0, then this should be * the last message on the connection. * If you are the server, respond with the "Connection: close" header. * If you are the client, close the connection. */ int http_should_keep_alive(const http_parser *parser); /* Returns a string version of the HTTP method. */ const char *http_method_str(enum http_method m); /* Returns a string version of the HTTP status code. */ const char *http_status_str(enum http_status s); /* Return a string name of the given error */ const char *http_errno_name(enum http_errno err); /* Return a string description of the given error */ const char *http_errno_description(enum http_errno err); /* Initialize all http_parser_url members to 0 */ void http_parser_url_init(struct http_parser_url *u); /* Parse a URL; return nonzero on failure */ int http_parser_parse_url(const char *buf, size_t buflen, int is_connect, struct http_parser_url *u); /* Pause or un-pause the parser; a nonzero value pauses */ void http_parser_pause(http_parser *parser, int paused); /* Checks if this is the final chunk of the body. */ int http_body_is_final(const http_parser *parser); /* Change the maximum header size provided at compile time. */ void http_parser_set_max_header_size(uint32_t size); #ifdef __cplusplus } #endif #endif psi-plus-snapshots-1.4.1456/3rdparty/http-parser/test.c000066400000000000000000003606331370065651000227320ustar00rootroot00000000000000/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include "http_parser.h" #include #include #include #include /* rand */ #include #include #if defined(__APPLE__) # undef strlncpy #endif /* defined(__APPLE__) */ #undef TRUE #define TRUE 1 #undef FALSE #define FALSE 0 #define MAX_HEADERS 13 #define MAX_ELEMENT_SIZE 2048 #define MAX_CHUNKS 16 #define MIN(a,b) ((a) < (b) ? (a) : (b)) #define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x)) static http_parser parser; struct message { const char *name; // for debugging purposes const char *raw; enum http_parser_type type; enum http_method method; int status_code; char response_status[MAX_ELEMENT_SIZE]; char request_path[MAX_ELEMENT_SIZE]; char request_url[MAX_ELEMENT_SIZE]; char fragment[MAX_ELEMENT_SIZE]; char query_string[MAX_ELEMENT_SIZE]; char body[MAX_ELEMENT_SIZE]; size_t body_size; const char *host; const char *userinfo; uint16_t port; int num_headers; enum { NONE=0, FIELD, VALUE } last_header_element; char headers [MAX_HEADERS][2][MAX_ELEMENT_SIZE]; int should_keep_alive; int num_chunks; int num_chunks_complete; int chunk_lengths[MAX_CHUNKS]; const char *upgrade; // upgraded body unsigned short http_major; unsigned short http_minor; int message_begin_cb_called; int headers_complete_cb_called; int message_complete_cb_called; int status_cb_called; int message_complete_on_eof; int body_is_final; }; static int currently_parsing_eof; static struct message messages[5]; static int num_messages; static http_parser_settings *current_pause_parser; /* * R E Q U E S T S * */ const struct message requests[] = #define CURL_GET 0 { {.name= "curl get" ,.type= HTTP_REQUEST ,.raw= "GET /test HTTP/1.1\r\n" "User-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\n" "Host: 0.0.0.0=5000\r\n" "Accept: */*\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET ,.query_string= "" ,.fragment= "" ,.request_path= "/test" ,.request_url= "/test" ,.num_headers= 3 ,.headers= { { "User-Agent", "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1" } , { "Host", "0.0.0.0=5000" } , { "Accept", "*/*" } } ,.body= "" } #define FIREFOX_GET 1 , {.name= "firefox get" ,.type= HTTP_REQUEST ,.raw= "GET /favicon.ico HTTP/1.1\r\n" "Host: 0.0.0.0=5000\r\n" "User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\n" "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" "Accept-Language: en-us,en;q=0.5\r\n" "Accept-Encoding: gzip,deflate\r\n" "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" "Keep-Alive: 300\r\n" "Connection: keep-alive\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET ,.query_string= "" ,.fragment= "" ,.request_path= "/favicon.ico" ,.request_url= "/favicon.ico" ,.num_headers= 8 ,.headers= { { "Host", "0.0.0.0=5000" } , { "User-Agent", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0" } , { "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" } , { "Accept-Language", "en-us,en;q=0.5" } , { "Accept-Encoding", "gzip,deflate" } , { "Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7" } , { "Keep-Alive", "300" } , { "Connection", "keep-alive" } } ,.body= "" } #define DUMBLUCK 2 , {.name= "dumbluck" ,.type= HTTP_REQUEST ,.raw= "GET /dumbluck HTTP/1.1\r\n" "aaaaaaaaaaaaa:++++++++++\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET ,.query_string= "" ,.fragment= "" ,.request_path= "/dumbluck" ,.request_url= "/dumbluck" ,.num_headers= 1 ,.headers= { { "aaaaaaaaaaaaa", "++++++++++" } } ,.body= "" } #define FRAGMENT_IN_URI 3 , {.name= "fragment in url" ,.type= HTTP_REQUEST ,.raw= "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET ,.query_string= "page=1" ,.fragment= "posts-17408" ,.request_path= "/forums/1/topics/2375" /* XXX request url does include fragment? */ ,.request_url= "/forums/1/topics/2375?page=1#posts-17408" ,.num_headers= 0 ,.body= "" } #define GET_NO_HEADERS_NO_BODY 4 , {.name= "get no headers no body" ,.type= HTTP_REQUEST ,.raw= "GET /get_no_headers_no_body/world HTTP/1.1\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE /* would need Connection: close */ ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET ,.query_string= "" ,.fragment= "" ,.request_path= "/get_no_headers_no_body/world" ,.request_url= "/get_no_headers_no_body/world" ,.num_headers= 0 ,.body= "" } #define GET_ONE_HEADER_NO_BODY 5 , {.name= "get one header no body" ,.type= HTTP_REQUEST ,.raw= "GET /get_one_header_no_body HTTP/1.1\r\n" "Accept: */*\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE /* would need Connection: close */ ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET ,.query_string= "" ,.fragment= "" ,.request_path= "/get_one_header_no_body" ,.request_url= "/get_one_header_no_body" ,.num_headers= 1 ,.headers= { { "Accept" , "*/*" } } ,.body= "" } #define GET_FUNKY_CONTENT_LENGTH 6 , {.name= "get funky content length body hello" ,.type= HTTP_REQUEST ,.raw= "GET /get_funky_content_length_body_hello HTTP/1.0\r\n" "conTENT-Length: 5\r\n" "\r\n" "HELLO" ,.should_keep_alive= FALSE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 0 ,.method= HTTP_GET ,.query_string= "" ,.fragment= "" ,.request_path= "/get_funky_content_length_body_hello" ,.request_url= "/get_funky_content_length_body_hello" ,.num_headers= 1 ,.headers= { { "conTENT-Length" , "5" } } ,.body= "HELLO" } #define POST_IDENTITY_BODY_WORLD 7 , {.name= "post identity body world" ,.type= HTTP_REQUEST ,.raw= "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\n" "Accept: */*\r\n" "Content-Length: 5\r\n" "\r\n" "World" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_POST ,.query_string= "q=search" ,.fragment= "hey" ,.request_path= "/post_identity_body_world" ,.request_url= "/post_identity_body_world?q=search#hey" ,.num_headers= 2 ,.headers= { { "Accept", "*/*" } , { "Content-Length", "5" } } ,.body= "World" } #define POST_CHUNKED_ALL_YOUR_BASE 8 , {.name= "post - chunked body: all your base are belong to us" ,.type= HTTP_REQUEST ,.raw= "POST /post_chunked_all_your_base HTTP/1.1\r\n" "Transfer-Encoding: chunked\r\n" "\r\n" "1e\r\nall your base are belong to us\r\n" "0\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_POST ,.query_string= "" ,.fragment= "" ,.request_path= "/post_chunked_all_your_base" ,.request_url= "/post_chunked_all_your_base" ,.num_headers= 1 ,.headers= { { "Transfer-Encoding" , "chunked" } } ,.body= "all your base are belong to us" ,.num_chunks_complete= 2 ,.chunk_lengths= { 0x1e } } #define TWO_CHUNKS_MULT_ZERO_END 9 , {.name= "two chunks ; triple zero ending" ,.type= HTTP_REQUEST ,.raw= "POST /two_chunks_mult_zero_end HTTP/1.1\r\n" "Transfer-Encoding: chunked\r\n" "\r\n" "5\r\nhello\r\n" "6\r\n world\r\n" "000\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_POST ,.query_string= "" ,.fragment= "" ,.request_path= "/two_chunks_mult_zero_end" ,.request_url= "/two_chunks_mult_zero_end" ,.num_headers= 1 ,.headers= { { "Transfer-Encoding", "chunked" } } ,.body= "hello world" ,.num_chunks_complete= 3 ,.chunk_lengths= { 5, 6 } } #define CHUNKED_W_TRAILING_HEADERS 10 , {.name= "chunked with trailing headers. blech." ,.type= HTTP_REQUEST ,.raw= "POST /chunked_w_trailing_headers HTTP/1.1\r\n" "Transfer-Encoding: chunked\r\n" "\r\n" "5\r\nhello\r\n" "6\r\n world\r\n" "0\r\n" "Vary: *\r\n" "Content-Type: text/plain\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_POST ,.query_string= "" ,.fragment= "" ,.request_path= "/chunked_w_trailing_headers" ,.request_url= "/chunked_w_trailing_headers" ,.num_headers= 3 ,.headers= { { "Transfer-Encoding", "chunked" } , { "Vary", "*" } , { "Content-Type", "text/plain" } } ,.body= "hello world" ,.num_chunks_complete= 3 ,.chunk_lengths= { 5, 6 } } #define CHUNKED_W_NONSENSE_AFTER_LENGTH 11 , {.name= "with nonsense after the length" ,.type= HTTP_REQUEST ,.raw= "POST /chunked_w_nonsense_after_length HTTP/1.1\r\n" "Transfer-Encoding: chunked\r\n" "\r\n" "5; ilovew3;whattheluck=aretheseparametersfor\r\nhello\r\n" "6; blahblah; blah\r\n world\r\n" "0\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_POST ,.query_string= "" ,.fragment= "" ,.request_path= "/chunked_w_nonsense_after_length" ,.request_url= "/chunked_w_nonsense_after_length" ,.num_headers= 1 ,.headers= { { "Transfer-Encoding", "chunked" } } ,.body= "hello world" ,.num_chunks_complete= 3 ,.chunk_lengths= { 5, 6 } } #define WITH_QUOTES 12 , {.name= "with quotes" ,.type= HTTP_REQUEST ,.raw= "GET /with_\"stupid\"_quotes?foo=\"bar\" HTTP/1.1\r\n\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET ,.query_string= "foo=\"bar\"" ,.fragment= "" ,.request_path= "/with_\"stupid\"_quotes" ,.request_url= "/with_\"stupid\"_quotes?foo=\"bar\"" ,.num_headers= 0 ,.headers= { } ,.body= "" } #define APACHEBENCH_GET 13 /* The server receiving this request SHOULD NOT wait for EOF * to know that content-length == 0. * How to represent this in a unit test? message_complete_on_eof * Compare with NO_CONTENT_LENGTH_RESPONSE. */ , {.name = "apachebench get" ,.type= HTTP_REQUEST ,.raw= "GET /test HTTP/1.0\r\n" "Host: 0.0.0.0:5000\r\n" "User-Agent: ApacheBench/2.3\r\n" "Accept: */*\r\n\r\n" ,.should_keep_alive= FALSE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 0 ,.method= HTTP_GET ,.query_string= "" ,.fragment= "" ,.request_path= "/test" ,.request_url= "/test" ,.num_headers= 3 ,.headers= { { "Host", "0.0.0.0:5000" } , { "User-Agent", "ApacheBench/2.3" } , { "Accept", "*/*" } } ,.body= "" } #define QUERY_URL_WITH_QUESTION_MARK_GET 14 /* Some clients include '?' characters in query strings. */ , {.name = "query url with question mark" ,.type= HTTP_REQUEST ,.raw= "GET /test.cgi?foo=bar?baz HTTP/1.1\r\n\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET ,.query_string= "foo=bar?baz" ,.fragment= "" ,.request_path= "/test.cgi" ,.request_url= "/test.cgi?foo=bar?baz" ,.num_headers= 0 ,.headers= {} ,.body= "" } #define PREFIX_NEWLINE_GET 15 /* Some clients, especially after a POST in a keep-alive connection, * will send an extra CRLF before the next request */ , {.name = "newline prefix get" ,.type= HTTP_REQUEST ,.raw= "\r\nGET /test HTTP/1.1\r\n\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET ,.query_string= "" ,.fragment= "" ,.request_path= "/test" ,.request_url= "/test" ,.num_headers= 0 ,.headers= { } ,.body= "" } #define UPGRADE_REQUEST 16 , {.name = "upgrade request" ,.type= HTTP_REQUEST ,.raw= "GET /demo HTTP/1.1\r\n" "Host: example.com\r\n" "Connection: Upgrade\r\n" "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" "Sec-WebSocket-Protocol: sample\r\n" "Upgrade: WebSocket\r\n" "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" "Origin: http://example.com\r\n" "\r\n" "Hot diggity dogg" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET ,.query_string= "" ,.fragment= "" ,.request_path= "/demo" ,.request_url= "/demo" ,.num_headers= 7 ,.upgrade="Hot diggity dogg" ,.headers= { { "Host", "example.com" } , { "Connection", "Upgrade" } , { "Sec-WebSocket-Key2", "12998 5 Y3 1 .P00" } , { "Sec-WebSocket-Protocol", "sample" } , { "Upgrade", "WebSocket" } , { "Sec-WebSocket-Key1", "4 @1 46546xW%0l 1 5" } , { "Origin", "http://example.com" } } ,.body= "" } #define CONNECT_REQUEST 17 , {.name = "connect request" ,.type= HTTP_REQUEST ,.raw= "CONNECT 0-home0.netscape.com:443 HTTP/1.0\r\n" "User-agent: Mozilla/1.1N\r\n" "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" "\r\n" "some data\r\n" "and yet even more data" ,.should_keep_alive= FALSE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 0 ,.method= HTTP_CONNECT ,.query_string= "" ,.fragment= "" ,.request_path= "" ,.request_url= "0-home0.netscape.com:443" ,.num_headers= 2 ,.upgrade="some data\r\nand yet even more data" ,.headers= { { "User-agent", "Mozilla/1.1N" } , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } } ,.body= "" } #define REPORT_REQ 18 , {.name= "report request" ,.type= HTTP_REQUEST ,.raw= "REPORT /test HTTP/1.1\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_REPORT ,.query_string= "" ,.fragment= "" ,.request_path= "/test" ,.request_url= "/test" ,.num_headers= 0 ,.headers= {} ,.body= "" } #define NO_HTTP_VERSION 19 , {.name= "request with no http version" ,.type= HTTP_REQUEST ,.raw= "GET /\r\n" "\r\n" ,.should_keep_alive= FALSE ,.message_complete_on_eof= FALSE ,.http_major= 0 ,.http_minor= 9 ,.method= HTTP_GET ,.query_string= "" ,.fragment= "" ,.request_path= "/" ,.request_url= "/" ,.num_headers= 0 ,.headers= {} ,.body= "" } #define MSEARCH_REQ 20 , {.name= "m-search request" ,.type= HTTP_REQUEST ,.raw= "M-SEARCH * HTTP/1.1\r\n" "HOST: 239.255.255.250:1900\r\n" "MAN: \"ssdp:discover\"\r\n" "ST: \"ssdp:all\"\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_MSEARCH ,.query_string= "" ,.fragment= "" ,.request_path= "*" ,.request_url= "*" ,.num_headers= 3 ,.headers= { { "HOST", "239.255.255.250:1900" } , { "MAN", "\"ssdp:discover\"" } , { "ST", "\"ssdp:all\"" } } ,.body= "" } #define LINE_FOLDING_IN_HEADER 21 , {.name= "line folding in header value" ,.type= HTTP_REQUEST ,.raw= "GET / HTTP/1.1\r\n" "Line1: abc\r\n" "\tdef\r\n" " ghi\r\n" "\t\tjkl\r\n" " mno \r\n" "\t \tqrs\r\n" "Line2: \t line2\t\r\n" "Line3:\r\n" " line3\r\n" "Line4: \r\n" " \r\n" "Connection:\r\n" " close\r\n" "\r\n" ,.should_keep_alive= FALSE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET ,.query_string= "" ,.fragment= "" ,.request_path= "/" ,.request_url= "/" ,.num_headers= 5 ,.headers= { { "Line1", "abc\tdef ghi\t\tjkl mno \t \tqrs" } , { "Line2", "line2\t" } , { "Line3", "line3" } , { "Line4", "" } , { "Connection", "close" }, } ,.body= "" } #define QUERY_TERMINATED_HOST 22 , {.name= "host terminated by a query string" ,.type= HTTP_REQUEST ,.raw= "GET http://hypnotoad.org?hail=all HTTP/1.1\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET ,.query_string= "hail=all" ,.fragment= "" ,.request_path= "" ,.request_url= "http://hypnotoad.org?hail=all" ,.host= "hypnotoad.org" ,.num_headers= 0 ,.headers= { } ,.body= "" } #define QUERY_TERMINATED_HOSTPORT 23 , {.name= "host:port terminated by a query string" ,.type= HTTP_REQUEST ,.raw= "GET http://hypnotoad.org:1234?hail=all HTTP/1.1\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET ,.query_string= "hail=all" ,.fragment= "" ,.request_path= "" ,.request_url= "http://hypnotoad.org:1234?hail=all" ,.host= "hypnotoad.org" ,.port= 1234 ,.num_headers= 0 ,.headers= { } ,.body= "" } #define SPACE_TERMINATED_HOSTPORT 24 , {.name= "host:port terminated by a space" ,.type= HTTP_REQUEST ,.raw= "GET http://hypnotoad.org:1234 HTTP/1.1\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET ,.query_string= "" ,.fragment= "" ,.request_path= "" ,.request_url= "http://hypnotoad.org:1234" ,.host= "hypnotoad.org" ,.port= 1234 ,.num_headers= 0 ,.headers= { } ,.body= "" } #define PATCH_REQ 25 , {.name = "PATCH request" ,.type= HTTP_REQUEST ,.raw= "PATCH /file.txt HTTP/1.1\r\n" "Host: www.example.com\r\n" "Content-Type: application/example\r\n" "If-Match: \"e0023aa4e\"\r\n" "Content-Length: 10\r\n" "\r\n" "cccccccccc" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_PATCH ,.query_string= "" ,.fragment= "" ,.request_path= "/file.txt" ,.request_url= "/file.txt" ,.num_headers= 4 ,.headers= { { "Host", "www.example.com" } , { "Content-Type", "application/example" } , { "If-Match", "\"e0023aa4e\"" } , { "Content-Length", "10" } } ,.body= "cccccccccc" } #define CONNECT_CAPS_REQUEST 26 , {.name = "connect caps request" ,.type= HTTP_REQUEST ,.raw= "CONNECT HOME0.NETSCAPE.COM:443 HTTP/1.0\r\n" "User-agent: Mozilla/1.1N\r\n" "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" "\r\n" ,.should_keep_alive= FALSE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 0 ,.method= HTTP_CONNECT ,.query_string= "" ,.fragment= "" ,.request_path= "" ,.request_url= "HOME0.NETSCAPE.COM:443" ,.num_headers= 2 ,.upgrade="" ,.headers= { { "User-agent", "Mozilla/1.1N" } , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } } ,.body= "" } #if !HTTP_PARSER_STRICT #define UTF8_PATH_REQ 27 , {.name= "utf-8 path request" ,.type= HTTP_REQUEST ,.raw= "GET /δ¶/δt/pope?q=1#narf HTTP/1.1\r\n" "Host: github.com\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET ,.query_string= "q=1" ,.fragment= "narf" ,.request_path= "/δ¶/δt/pope" ,.request_url= "/δ¶/δt/pope?q=1#narf" ,.num_headers= 1 ,.headers= { {"Host", "github.com" } } ,.body= "" } #define HOSTNAME_UNDERSCORE 28 , {.name = "hostname underscore" ,.type= HTTP_REQUEST ,.raw= "CONNECT home_0.netscape.com:443 HTTP/1.0\r\n" "User-agent: Mozilla/1.1N\r\n" "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" "\r\n" ,.should_keep_alive= FALSE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 0 ,.method= HTTP_CONNECT ,.query_string= "" ,.fragment= "" ,.request_path= "" ,.request_url= "home_0.netscape.com:443" ,.num_headers= 2 ,.upgrade="" ,.headers= { { "User-agent", "Mozilla/1.1N" } , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } } ,.body= "" } #endif /* !HTTP_PARSER_STRICT */ /* see https://github.com/ry/http-parser/issues/47 */ #define EAT_TRAILING_CRLF_NO_CONNECTION_CLOSE 29 , {.name = "eat CRLF between requests, no \"Connection: close\" header" ,.raw= "POST / HTTP/1.1\r\n" "Host: www.example.com\r\n" "Content-Type: application/x-www-form-urlencoded\r\n" "Content-Length: 4\r\n" "\r\n" "q=42\r\n" /* note the trailing CRLF */ ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_POST ,.query_string= "" ,.fragment= "" ,.request_path= "/" ,.request_url= "/" ,.num_headers= 3 ,.upgrade= 0 ,.headers= { { "Host", "www.example.com" } , { "Content-Type", "application/x-www-form-urlencoded" } , { "Content-Length", "4" } } ,.body= "q=42" } /* see https://github.com/ry/http-parser/issues/47 */ #define EAT_TRAILING_CRLF_WITH_CONNECTION_CLOSE 30 , {.name = "eat CRLF between requests even if \"Connection: close\" is set" ,.raw= "POST / HTTP/1.1\r\n" "Host: www.example.com\r\n" "Content-Type: application/x-www-form-urlencoded\r\n" "Content-Length: 4\r\n" "Connection: close\r\n" "\r\n" "q=42\r\n" /* note the trailing CRLF */ ,.should_keep_alive= FALSE ,.message_complete_on_eof= FALSE /* input buffer isn't empty when on_message_complete is called */ ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_POST ,.query_string= "" ,.fragment= "" ,.request_path= "/" ,.request_url= "/" ,.num_headers= 4 ,.upgrade= 0 ,.headers= { { "Host", "www.example.com" } , { "Content-Type", "application/x-www-form-urlencoded" } , { "Content-Length", "4" } , { "Connection", "close" } } ,.body= "q=42" } #define PURGE_REQ 31 , {.name = "PURGE request" ,.type= HTTP_REQUEST ,.raw= "PURGE /file.txt HTTP/1.1\r\n" "Host: www.example.com\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_PURGE ,.query_string= "" ,.fragment= "" ,.request_path= "/file.txt" ,.request_url= "/file.txt" ,.num_headers= 1 ,.headers= { { "Host", "www.example.com" } } ,.body= "" } #define SEARCH_REQ 32 , {.name = "SEARCH request" ,.type= HTTP_REQUEST ,.raw= "SEARCH / HTTP/1.1\r\n" "Host: www.example.com\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_SEARCH ,.query_string= "" ,.fragment= "" ,.request_path= "/" ,.request_url= "/" ,.num_headers= 1 ,.headers= { { "Host", "www.example.com" } } ,.body= "" } #define PROXY_WITH_BASIC_AUTH 33 , {.name= "host:port and basic_auth" ,.type= HTTP_REQUEST ,.raw= "GET http://a%12:b!&*$@hypnotoad.org:1234/toto HTTP/1.1\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET ,.fragment= "" ,.request_path= "/toto" ,.request_url= "http://a%12:b!&*$@hypnotoad.org:1234/toto" ,.host= "hypnotoad.org" ,.userinfo= "a%12:b!&*$" ,.port= 1234 ,.num_headers= 0 ,.headers= { } ,.body= "" } #define LINE_FOLDING_IN_HEADER_WITH_LF 34 , {.name= "line folding in header value" ,.type= HTTP_REQUEST ,.raw= "GET / HTTP/1.1\n" "Line1: abc\n" "\tdef\n" " ghi\n" "\t\tjkl\n" " mno \n" "\t \tqrs\n" "Line2: \t line2\t\n" "Line3:\n" " line3\n" "Line4: \n" " \n" "Connection:\n" " close\n" "\n" ,.should_keep_alive= FALSE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET ,.query_string= "" ,.fragment= "" ,.request_path= "/" ,.request_url= "/" ,.num_headers= 5 ,.headers= { { "Line1", "abc\tdef ghi\t\tjkl mno \t \tqrs" } , { "Line2", "line2\t" } , { "Line3", "line3" } , { "Line4", "" } , { "Connection", "close" }, } ,.body= "" } #define CONNECTION_MULTI 35 , {.name = "multiple connection header values with folding" ,.type= HTTP_REQUEST ,.raw= "GET /demo HTTP/1.1\r\n" "Host: example.com\r\n" "Connection: Something,\r\n" " Upgrade, ,Keep-Alive\r\n" "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" "Sec-WebSocket-Protocol: sample\r\n" "Upgrade: WebSocket\r\n" "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" "Origin: http://example.com\r\n" "\r\n" "Hot diggity dogg" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET ,.query_string= "" ,.fragment= "" ,.request_path= "/demo" ,.request_url= "/demo" ,.num_headers= 7 ,.upgrade="Hot diggity dogg" ,.headers= { { "Host", "example.com" } , { "Connection", "Something, Upgrade, ,Keep-Alive" } , { "Sec-WebSocket-Key2", "12998 5 Y3 1 .P00" } , { "Sec-WebSocket-Protocol", "sample" } , { "Upgrade", "WebSocket" } , { "Sec-WebSocket-Key1", "4 @1 46546xW%0l 1 5" } , { "Origin", "http://example.com" } } ,.body= "" } #define CONNECTION_MULTI_LWS 36 , {.name = "multiple connection header values with folding and lws" ,.type= HTTP_REQUEST ,.raw= "GET /demo HTTP/1.1\r\n" "Connection: keep-alive, upgrade\r\n" "Upgrade: WebSocket\r\n" "\r\n" "Hot diggity dogg" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET ,.query_string= "" ,.fragment= "" ,.request_path= "/demo" ,.request_url= "/demo" ,.num_headers= 2 ,.upgrade="Hot diggity dogg" ,.headers= { { "Connection", "keep-alive, upgrade" } , { "Upgrade", "WebSocket" } } ,.body= "" } #define CONNECTION_MULTI_LWS_CRLF 37 , {.name = "multiple connection header values with folding and lws" ,.type= HTTP_REQUEST ,.raw= "GET /demo HTTP/1.1\r\n" "Connection: keep-alive, \r\n upgrade\r\n" "Upgrade: WebSocket\r\n" "\r\n" "Hot diggity dogg" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET ,.query_string= "" ,.fragment= "" ,.request_path= "/demo" ,.request_url= "/demo" ,.num_headers= 2 ,.upgrade="Hot diggity dogg" ,.headers= { { "Connection", "keep-alive, upgrade" } , { "Upgrade", "WebSocket" } } ,.body= "" } #define UPGRADE_POST_REQUEST 38 , {.name = "upgrade post request" ,.type= HTTP_REQUEST ,.raw= "POST /demo HTTP/1.1\r\n" "Host: example.com\r\n" "Connection: Upgrade\r\n" "Upgrade: HTTP/2.0\r\n" "Content-Length: 15\r\n" "\r\n" "sweet post body" "Hot diggity dogg" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_POST ,.request_path= "/demo" ,.request_url= "/demo" ,.num_headers= 4 ,.upgrade="Hot diggity dogg" ,.headers= { { "Host", "example.com" } , { "Connection", "Upgrade" } , { "Upgrade", "HTTP/2.0" } , { "Content-Length", "15" } } ,.body= "sweet post body" } #define CONNECT_WITH_BODY_REQUEST 39 , {.name = "connect with body request" ,.type= HTTP_REQUEST ,.raw= "CONNECT foo.bar.com:443 HTTP/1.0\r\n" "User-agent: Mozilla/1.1N\r\n" "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" "Content-Length: 10\r\n" "\r\n" "blarfcicle" ,.should_keep_alive= FALSE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 0 ,.method= HTTP_CONNECT ,.request_url= "foo.bar.com:443" ,.num_headers= 3 ,.upgrade="blarfcicle" ,.headers= { { "User-agent", "Mozilla/1.1N" } , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } , { "Content-Length", "10" } } ,.body= "" } /* Examples from the Internet draft for LINK/UNLINK methods: * https://tools.ietf.org/id/draft-snell-link-method-01.html#rfc.section.5 */ #define LINK_REQUEST 40 , {.name = "link request" ,.type= HTTP_REQUEST ,.raw= "LINK /images/my_dog.jpg HTTP/1.1\r\n" "Host: example.com\r\n" "Link: ; rel=\"tag\"\r\n" "Link: ; rel=\"tag\"\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_LINK ,.request_path= "/images/my_dog.jpg" ,.request_url= "/images/my_dog.jpg" ,.query_string= "" ,.fragment= "" ,.num_headers= 3 ,.headers= { { "Host", "example.com" } , { "Link", "; rel=\"tag\"" } , { "Link", "; rel=\"tag\"" } } ,.body= "" } #define UNLINK_REQUEST 41 , {.name = "unlink request" ,.type= HTTP_REQUEST ,.raw= "UNLINK /images/my_dog.jpg HTTP/1.1\r\n" "Host: example.com\r\n" "Link: ; rel=\"tag\"\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_UNLINK ,.request_path= "/images/my_dog.jpg" ,.request_url= "/images/my_dog.jpg" ,.query_string= "" ,.fragment= "" ,.num_headers= 2 ,.headers= { { "Host", "example.com" } , { "Link", "; rel=\"tag\"" } } ,.body= "" } #define SOURCE_REQUEST 42 , {.name = "source request" ,.type= HTTP_REQUEST ,.raw= "SOURCE /music/sweet/music HTTP/1.1\r\n" "Host: example.com\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_SOURCE ,.request_path= "/music/sweet/music" ,.request_url= "/music/sweet/music" ,.query_string= "" ,.fragment= "" ,.num_headers= 1 ,.headers= { { "Host", "example.com" } } ,.body= "" } #define SOURCE_ICE_REQUEST 42 , {.name = "source request" ,.type= HTTP_REQUEST ,.raw= "SOURCE /music/sweet/music ICE/1.0\r\n" "Host: example.com\r\n" "\r\n" ,.should_keep_alive= FALSE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 0 ,.method= HTTP_SOURCE ,.request_path= "/music/sweet/music" ,.request_url= "/music/sweet/music" ,.query_string= "" ,.fragment= "" ,.num_headers= 1 ,.headers= { { "Host", "example.com" } } ,.body= "" } #define POST_MULTI_TE_LAST_CHUNKED 43 , {.name= "post - multi coding transfer-encoding chunked body" ,.type= HTTP_REQUEST ,.raw= "POST / HTTP/1.1\r\n" "Transfer-Encoding: deflate, chunked\r\n" "\r\n" "1e\r\nall your base are belong to us\r\n" "0\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_POST ,.query_string= "" ,.fragment= "" ,.request_path= "/" ,.request_url= "/" ,.num_headers= 1 ,.headers= { { "Transfer-Encoding" , "deflate, chunked" } } ,.body= "all your base are belong to us" ,.num_chunks_complete= 2 ,.chunk_lengths= { 0x1e } } #define POST_MULTI_LINE_TE_LAST_CHUNKED 44 , {.name= "post - multi line coding transfer-encoding chunked body" ,.type= HTTP_REQUEST ,.raw= "POST / HTTP/1.1\r\n" "Transfer-Encoding: deflate,\r\n" " chunked\r\n" "\r\n" "1e\r\nall your base are belong to us\r\n" "0\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_POST ,.query_string= "" ,.fragment= "" ,.request_path= "/" ,.request_url= "/" ,.num_headers= 1 ,.headers= { { "Transfer-Encoding" , "deflate, chunked" } } ,.body= "all your base are belong to us" ,.num_chunks_complete= 2 ,.chunk_lengths= { 0x1e } } }; /* * R E S P O N S E S * */ const struct message responses[] = #define GOOGLE_301 0 { {.name= "google 301" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 301 Moved Permanently\r\n" "Location: http://www.google.com/\r\n" "Content-Type: text/html; charset=UTF-8\r\n" "Date: Sun, 26 Apr 2009 11:11:49 GMT\r\n" "Expires: Tue, 26 May 2009 11:11:49 GMT\r\n" "X-$PrototypeBI-Version: 1.6.0.3\r\n" /* $ char in header field */ "Cache-Control: public, max-age=2592000\r\n" "Server: gws\r\n" "Content-Length: 219 \r\n" "\r\n" "\n" "301 Moved\n" "

301 Moved

\n" "The document has moved\n" "here.\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 301 ,.response_status= "Moved Permanently" ,.num_headers= 8 ,.headers= { { "Location", "http://www.google.com/" } , { "Content-Type", "text/html; charset=UTF-8" } , { "Date", "Sun, 26 Apr 2009 11:11:49 GMT" } , { "Expires", "Tue, 26 May 2009 11:11:49 GMT" } , { "X-$PrototypeBI-Version", "1.6.0.3" } , { "Cache-Control", "public, max-age=2592000" } , { "Server", "gws" } , { "Content-Length", "219 " } } ,.body= "\n" "301 Moved\n" "

301 Moved

\n" "The document has moved\n" "here.\r\n" "\r\n" } #define NO_CONTENT_LENGTH_RESPONSE 1 /* The client should wait for the server's EOF. That is, when content-length * is not specified, and "Connection: close", the end of body is specified * by the EOF. * Compare with APACHEBENCH_GET */ , {.name= "no content-length response" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 200 OK\r\n" "Date: Tue, 04 Aug 2009 07:59:32 GMT\r\n" "Server: Apache\r\n" "X-Powered-By: Servlet/2.5 JSP/2.1\r\n" "Content-Type: text/xml; charset=utf-8\r\n" "Connection: close\r\n" "\r\n" "\n" "\n" " \n" " \n" " SOAP-ENV:Client\n" " Client Error\n" " \n" " \n" "" ,.should_keep_alive= FALSE ,.message_complete_on_eof= TRUE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 200 ,.response_status= "OK" ,.num_headers= 5 ,.headers= { { "Date", "Tue, 04 Aug 2009 07:59:32 GMT" } , { "Server", "Apache" } , { "X-Powered-By", "Servlet/2.5 JSP/2.1" } , { "Content-Type", "text/xml; charset=utf-8" } , { "Connection", "close" } } ,.body= "\n" "\n" " \n" " \n" " SOAP-ENV:Client\n" " Client Error\n" " \n" " \n" "" } #define NO_HEADERS_NO_BODY_404 2 , {.name= "404 no headers no body" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 404 Not Found\r\n\r\n" ,.should_keep_alive= FALSE ,.message_complete_on_eof= TRUE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 404 ,.response_status= "Not Found" ,.num_headers= 0 ,.headers= {} ,.body_size= 0 ,.body= "" } #define NO_REASON_PHRASE 3 , {.name= "301 no response phrase" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 301\r\n\r\n" ,.should_keep_alive = FALSE ,.message_complete_on_eof= TRUE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 301 ,.response_status= "" ,.num_headers= 0 ,.headers= {} ,.body= "" } #define TRAILING_SPACE_ON_CHUNKED_BODY 4 , {.name="200 trailing space on chunked body" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 200 OK\r\n" "Content-Type: text/plain\r\n" "Transfer-Encoding: chunked\r\n" "\r\n" "25 \r\n" "This is the data in the first chunk\r\n" "\r\n" "1C\r\n" "and this is the second one\r\n" "\r\n" "0 \r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 200 ,.response_status= "OK" ,.num_headers= 2 ,.headers= { {"Content-Type", "text/plain" } , {"Transfer-Encoding", "chunked" } } ,.body_size = 37+28 ,.body = "This is the data in the first chunk\r\n" "and this is the second one\r\n" ,.num_chunks_complete= 3 ,.chunk_lengths= { 0x25, 0x1c } } #define NO_CARRIAGE_RET 5 , {.name="no carriage ret" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 200 OK\n" "Content-Type: text/html; charset=utf-8\n" "Connection: close\n" "\n" "these headers are from http://news.ycombinator.com/" ,.should_keep_alive= FALSE ,.message_complete_on_eof= TRUE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 200 ,.response_status= "OK" ,.num_headers= 2 ,.headers= { {"Content-Type", "text/html; charset=utf-8" } , {"Connection", "close" } } ,.body= "these headers are from http://news.ycombinator.com/" } #define PROXY_CONNECTION 6 , {.name="proxy connection" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 200 OK\r\n" "Content-Type: text/html; charset=UTF-8\r\n" "Content-Length: 11\r\n" "Proxy-Connection: close\r\n" "Date: Thu, 31 Dec 2009 20:55:48 +0000\r\n" "\r\n" "hello world" ,.should_keep_alive= FALSE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 200 ,.response_status= "OK" ,.num_headers= 4 ,.headers= { {"Content-Type", "text/html; charset=UTF-8" } , {"Content-Length", "11" } , {"Proxy-Connection", "close" } , {"Date", "Thu, 31 Dec 2009 20:55:48 +0000"} } ,.body= "hello world" } #define UNDERSTORE_HEADER_KEY 7 // shown by // curl -o /dev/null -v "http://ad.doubleclick.net/pfadx/DARTSHELLCONFIGXML;dcmt=text/xml;" , {.name="underscore header key" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 200 OK\r\n" "Server: DCLK-AdSvr\r\n" "Content-Type: text/xml\r\n" "Content-Length: 0\r\n" "DCLK_imp: v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o\r\n\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 200 ,.response_status= "OK" ,.num_headers= 4 ,.headers= { {"Server", "DCLK-AdSvr" } , {"Content-Type", "text/xml" } , {"Content-Length", "0" } , {"DCLK_imp", "v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o" } } ,.body= "" } #define BONJOUR_MADAME_FR 8 /* The client should not merge two headers fields when the first one doesn't * have a value. */ , {.name= "bonjourmadame.fr" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.0 301 Moved Permanently\r\n" "Date: Thu, 03 Jun 2010 09:56:32 GMT\r\n" "Server: Apache/2.2.3 (Red Hat)\r\n" "Cache-Control: public\r\n" "Pragma: \r\n" "Location: http://www.bonjourmadame.fr/\r\n" "Vary: Accept-Encoding\r\n" "Content-Length: 0\r\n" "Content-Type: text/html; charset=UTF-8\r\n" "Connection: keep-alive\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 0 ,.status_code= 301 ,.response_status= "Moved Permanently" ,.num_headers= 9 ,.headers= { { "Date", "Thu, 03 Jun 2010 09:56:32 GMT" } , { "Server", "Apache/2.2.3 (Red Hat)" } , { "Cache-Control", "public" } , { "Pragma", "" } , { "Location", "http://www.bonjourmadame.fr/" } , { "Vary", "Accept-Encoding" } , { "Content-Length", "0" } , { "Content-Type", "text/html; charset=UTF-8" } , { "Connection", "keep-alive" } } ,.body= "" } #define RES_FIELD_UNDERSCORE 9 /* Should handle spaces in header fields */ , {.name= "field underscore" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 200 OK\r\n" "Date: Tue, 28 Sep 2010 01:14:13 GMT\r\n" "Server: Apache\r\n" "Cache-Control: no-cache, must-revalidate\r\n" "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" ".et-Cookie: PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com\r\n" "Vary: Accept-Encoding\r\n" "_eep-Alive: timeout=45\r\n" /* semantic value ignored */ "_onnection: Keep-Alive\r\n" /* semantic value ignored */ "Transfer-Encoding: chunked\r\n" "Content-Type: text/html\r\n" "Connection: close\r\n" "\r\n" "0\r\n\r\n" ,.should_keep_alive= FALSE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 200 ,.response_status= "OK" ,.num_headers= 11 ,.headers= { { "Date", "Tue, 28 Sep 2010 01:14:13 GMT" } , { "Server", "Apache" } , { "Cache-Control", "no-cache, must-revalidate" } , { "Expires", "Mon, 26 Jul 1997 05:00:00 GMT" } , { ".et-Cookie", "PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com" } , { "Vary", "Accept-Encoding" } , { "_eep-Alive", "timeout=45" } , { "_onnection", "Keep-Alive" } , { "Transfer-Encoding", "chunked" } , { "Content-Type", "text/html" } , { "Connection", "close" } } ,.body= "" ,.num_chunks_complete= 1 ,.chunk_lengths= {} } #define NON_ASCII_IN_STATUS_LINE 10 /* Should handle non-ASCII in status line */ , {.name= "non-ASCII in status line" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 500 Oriëntatieprobleem\r\n" "Date: Fri, 5 Nov 2010 23:07:12 GMT+2\r\n" "Content-Length: 0\r\n" "Connection: close\r\n" "\r\n" ,.should_keep_alive= FALSE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 500 ,.response_status= "Oriëntatieprobleem" ,.num_headers= 3 ,.headers= { { "Date", "Fri, 5 Nov 2010 23:07:12 GMT+2" } , { "Content-Length", "0" } , { "Connection", "close" } } ,.body= "" } #define HTTP_VERSION_0_9 11 /* Should handle HTTP/0.9 */ , {.name= "http version 0.9" ,.type= HTTP_RESPONSE ,.raw= "HTTP/0.9 200 OK\r\n" "\r\n" ,.should_keep_alive= FALSE ,.message_complete_on_eof= TRUE ,.http_major= 0 ,.http_minor= 9 ,.status_code= 200 ,.response_status= "OK" ,.num_headers= 0 ,.headers= {} ,.body= "" } #define NO_CONTENT_LENGTH_NO_TRANSFER_ENCODING_RESPONSE 12 /* The client should wait for the server's EOF. That is, when neither * content-length nor transfer-encoding is specified, the end of body * is specified by the EOF. */ , {.name= "neither content-length nor transfer-encoding response" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 200 OK\r\n" "Content-Type: text/plain\r\n" "\r\n" "hello world" ,.should_keep_alive= FALSE ,.message_complete_on_eof= TRUE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 200 ,.response_status= "OK" ,.num_headers= 1 ,.headers= { { "Content-Type", "text/plain" } } ,.body= "hello world" } #define NO_BODY_HTTP10_KA_200 13 , {.name= "HTTP/1.0 with keep-alive and EOF-terminated 200 status" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.0 200 OK\r\n" "Connection: keep-alive\r\n" "\r\n" ,.should_keep_alive= FALSE ,.message_complete_on_eof= TRUE ,.http_major= 1 ,.http_minor= 0 ,.status_code= 200 ,.response_status= "OK" ,.num_headers= 1 ,.headers= { { "Connection", "keep-alive" } } ,.body_size= 0 ,.body= "" } #define NO_BODY_HTTP10_KA_204 14 , {.name= "HTTP/1.0 with keep-alive and a 204 status" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.0 204 No content\r\n" "Connection: keep-alive\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 0 ,.status_code= 204 ,.response_status= "No content" ,.num_headers= 1 ,.headers= { { "Connection", "keep-alive" } } ,.body_size= 0 ,.body= "" } #define NO_BODY_HTTP11_KA_200 15 , {.name= "HTTP/1.1 with an EOF-terminated 200 status" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 200 OK\r\n" "\r\n" ,.should_keep_alive= FALSE ,.message_complete_on_eof= TRUE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 200 ,.response_status= "OK" ,.num_headers= 0 ,.headers={} ,.body_size= 0 ,.body= "" } #define NO_BODY_HTTP11_KA_204 16 , {.name= "HTTP/1.1 with a 204 status" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 204 No content\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 204 ,.response_status= "No content" ,.num_headers= 0 ,.headers={} ,.body_size= 0 ,.body= "" } #define NO_BODY_HTTP11_NOKA_204 17 , {.name= "HTTP/1.1 with a 204 status and keep-alive disabled" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 204 No content\r\n" "Connection: close\r\n" "\r\n" ,.should_keep_alive= FALSE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 204 ,.response_status= "No content" ,.num_headers= 1 ,.headers= { { "Connection", "close" } } ,.body_size= 0 ,.body= "" } #define NO_BODY_HTTP11_KA_CHUNKED_200 18 , {.name= "HTTP/1.1 with chunked endocing and a 200 response" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 200 OK\r\n" "Transfer-Encoding: chunked\r\n" "\r\n" "0\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 200 ,.response_status= "OK" ,.num_headers= 1 ,.headers= { { "Transfer-Encoding", "chunked" } } ,.body_size= 0 ,.body= "" ,.num_chunks_complete= 1 } #if !HTTP_PARSER_STRICT #define SPACE_IN_FIELD_RES 19 /* Should handle spaces in header fields */ , {.name= "field space" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 200 OK\r\n" "Server: Microsoft-IIS/6.0\r\n" "X-Powered-By: ASP.NET\r\n" "en-US Content-Type: text/xml\r\n" /* this is the problem */ "Content-Type: text/xml\r\n" "Content-Length: 16\r\n" "Date: Fri, 23 Jul 2010 18:45:38 GMT\r\n" "Connection: keep-alive\r\n" "\r\n" "hello" /* fake body */ ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 200 ,.response_status= "OK" ,.num_headers= 7 ,.headers= { { "Server", "Microsoft-IIS/6.0" } , { "X-Powered-By", "ASP.NET" } , { "en-US Content-Type", "text/xml" } , { "Content-Type", "text/xml" } , { "Content-Length", "16" } , { "Date", "Fri, 23 Jul 2010 18:45:38 GMT" } , { "Connection", "keep-alive" } } ,.body= "hello" } #endif /* !HTTP_PARSER_STRICT */ #define AMAZON_COM 20 , {.name= "amazon.com" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 301 MovedPermanently\r\n" "Date: Wed, 15 May 2013 17:06:33 GMT\r\n" "Server: Server\r\n" "x-amz-id-1: 0GPHKXSJQ826RK7GZEB2\r\n" "p3p: policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"\r\n" "x-amz-id-2: STN69VZxIFSz9YJLbz1GDbxpbjG6Qjmmq5E3DxRhOUw+Et0p4hr7c/Q8qNcx4oAD\r\n" "Location: http://www.amazon.com/Dan-Brown/e/B000AP9DSU/ref=s9_pop_gw_al1?_encoding=UTF8&refinementId=618073011&pf_rd_m=ATVPDKIKX0DER&pf_rd_s=center-2&pf_rd_r=0SHYY5BZXN3KR20BNFAY&pf_rd_t=101&pf_rd_p=1263340922&pf_rd_i=507846\r\n" "Vary: Accept-Encoding,User-Agent\r\n" "Content-Type: text/html; charset=ISO-8859-1\r\n" "Transfer-Encoding: chunked\r\n" "\r\n" "1\r\n" "\n\r\n" "0\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 301 ,.response_status= "MovedPermanently" ,.num_headers= 9 ,.headers= { { "Date", "Wed, 15 May 2013 17:06:33 GMT" } , { "Server", "Server" } , { "x-amz-id-1", "0GPHKXSJQ826RK7GZEB2" } , { "p3p", "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" } , { "x-amz-id-2", "STN69VZxIFSz9YJLbz1GDbxpbjG6Qjmmq5E3DxRhOUw+Et0p4hr7c/Q8qNcx4oAD" } , { "Location", "http://www.amazon.com/Dan-Brown/e/B000AP9DSU/ref=s9_pop_gw_al1?_encoding=UTF8&refinementId=618073011&pf_rd_m=ATVPDKIKX0DER&pf_rd_s=center-2&pf_rd_r=0SHYY5BZXN3KR20BNFAY&pf_rd_t=101&pf_rd_p=1263340922&pf_rd_i=507846" } , { "Vary", "Accept-Encoding,User-Agent" } , { "Content-Type", "text/html; charset=ISO-8859-1" } , { "Transfer-Encoding", "chunked" } } ,.body= "\n" ,.num_chunks_complete= 2 ,.chunk_lengths= { 1 } } #define EMPTY_REASON_PHRASE_AFTER_SPACE 20 , {.name= "empty reason phrase after space" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 200 \r\n" "\r\n" ,.should_keep_alive= FALSE ,.message_complete_on_eof= TRUE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 200 ,.response_status= "" ,.num_headers= 0 ,.headers= {} ,.body= "" } #define CONTENT_LENGTH_X 21 , {.name= "Content-Length-X" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 200 OK\r\n" "Content-Length-X: 0\r\n" "Transfer-Encoding: chunked\r\n" "\r\n" "2\r\n" "OK\r\n" "0\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 200 ,.response_status= "OK" ,.num_headers= 2 ,.headers= { { "Content-Length-X", "0" } , { "Transfer-Encoding", "chunked" } } ,.body= "OK" ,.num_chunks_complete= 2 ,.chunk_lengths= { 2 } } #define HTTP_101_RESPONSE_WITH_UPGRADE_HEADER 22 , {.name= "HTTP 101 response with Upgrade header" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 101 Switching Protocols\r\n" "Connection: upgrade\r\n" "Upgrade: h2c\r\n" "\r\n" "proto" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 101 ,.response_status= "Switching Protocols" ,.upgrade= "proto" ,.num_headers= 2 ,.headers= { { "Connection", "upgrade" } , { "Upgrade", "h2c" } } } #define HTTP_101_RESPONSE_WITH_UPGRADE_HEADER_AND_CONTENT_LENGTH 23 , {.name= "HTTP 101 response with Upgrade and Content-Length header" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 101 Switching Protocols\r\n" "Connection: upgrade\r\n" "Upgrade: h2c\r\n" "Content-Length: 4\r\n" "\r\n" "body" "proto" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 101 ,.response_status= "Switching Protocols" ,.body= "body" ,.upgrade= "proto" ,.num_headers= 3 ,.headers= { { "Connection", "upgrade" } , { "Upgrade", "h2c" } , { "Content-Length", "4" } } } #define HTTP_101_RESPONSE_WITH_UPGRADE_HEADER_AND_TRANSFER_ENCODING 24 , {.name= "HTTP 101 response with Upgrade and Transfer-Encoding header" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 101 Switching Protocols\r\n" "Connection: upgrade\r\n" "Upgrade: h2c\r\n" "Transfer-Encoding: chunked\r\n" "\r\n" "2\r\n" "bo\r\n" "2\r\n" "dy\r\n" "0\r\n" "\r\n" "proto" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 101 ,.response_status= "Switching Protocols" ,.body= "body" ,.upgrade= "proto" ,.num_headers= 3 ,.headers= { { "Connection", "upgrade" } , { "Upgrade", "h2c" } , { "Transfer-Encoding", "chunked" } } ,.num_chunks_complete= 3 ,.chunk_lengths= { 2, 2 } } #define HTTP_200_RESPONSE_WITH_UPGRADE_HEADER 25 , {.name= "HTTP 200 response with Upgrade header" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 200 OK\r\n" "Connection: upgrade\r\n" "Upgrade: h2c\r\n" "\r\n" "body" ,.should_keep_alive= FALSE ,.message_complete_on_eof= TRUE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 200 ,.response_status= "OK" ,.body= "body" ,.upgrade= NULL ,.num_headers= 2 ,.headers= { { "Connection", "upgrade" } , { "Upgrade", "h2c" } } } #define HTTP_200_RESPONSE_WITH_UPGRADE_HEADER_AND_CONTENT_LENGTH 26 , {.name= "HTTP 200 response with Upgrade and Content-Length header" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 200 OK\r\n" "Connection: upgrade\r\n" "Upgrade: h2c\r\n" "Content-Length: 4\r\n" "\r\n" "body" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 200 ,.response_status= "OK" ,.num_headers= 3 ,.body= "body" ,.upgrade= NULL ,.headers= { { "Connection", "upgrade" } , { "Upgrade", "h2c" } , { "Content-Length", "4" } } } #define HTTP_200_RESPONSE_WITH_UPGRADE_HEADER_AND_TRANSFER_ENCODING 27 , {.name= "HTTP 200 response with Upgrade and Transfer-Encoding header" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 200 OK\r\n" "Connection: upgrade\r\n" "Upgrade: h2c\r\n" "Transfer-Encoding: chunked\r\n" "\r\n" "2\r\n" "bo\r\n" "2\r\n" "dy\r\n" "0\r\n" "\r\n" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 200 ,.response_status= "OK" ,.num_headers= 3 ,.body= "body" ,.upgrade= NULL ,.headers= { { "Connection", "upgrade" } , { "Upgrade", "h2c" } , { "Transfer-Encoding", "chunked" } } ,.num_chunks_complete= 3 ,.chunk_lengths= { 2, 2 } } #define HTTP_200_MULTI_TE_NOT_LAST_CHUNKED 28 , {.name= "HTTP 200 response with `chunked` being *not last* Transfer-Encoding" ,.type= HTTP_RESPONSE ,.raw= "HTTP/1.1 200 OK\r\n" "Transfer-Encoding: chunked, identity\r\n" "\r\n" "2\r\n" "OK\r\n" "0\r\n" "\r\n" ,.should_keep_alive= FALSE ,.message_complete_on_eof= TRUE ,.http_major= 1 ,.http_minor= 1 ,.status_code= 200 ,.response_status= "OK" ,.num_headers= 1 ,.headers= { { "Transfer-Encoding", "chunked, identity" } } ,.body= "2\r\nOK\r\n0\r\n\r\n" ,.num_chunks_complete= 0 } }; /* strnlen() is a POSIX.2008 addition. Can't rely on it being available so * define it ourselves. */ size_t strnlen(const char *s, size_t maxlen) { const char *p; p = memchr(s, '\0', maxlen); if (p == NULL) return maxlen; return p - s; } size_t strlncat(char *dst, size_t len, const char *src, size_t n) { size_t slen; size_t dlen; size_t rlen; size_t ncpy; slen = strnlen(src, n); dlen = strnlen(dst, len); if (dlen < len) { rlen = len - dlen; ncpy = slen < rlen ? slen : (rlen - 1); memcpy(dst + dlen, src, ncpy); dst[dlen + ncpy] = '\0'; } assert(len > slen + dlen); return slen + dlen; } size_t strlncpy(char *dst, size_t len, const char *src, size_t n) { size_t slen; size_t ncpy; slen = strnlen(src, n); if (len > 0) { ncpy = slen < len ? slen : (len - 1); memcpy(dst, src, ncpy); dst[ncpy] = '\0'; } assert(len > slen); return slen; } int request_url_cb (http_parser *p, const char *buf, size_t len) { assert(p == &parser); strlncat(messages[num_messages].request_url, sizeof(messages[num_messages].request_url), buf, len); return 0; } int header_field_cb (http_parser *p, const char *buf, size_t len) { assert(p == &parser); struct message *m = &messages[num_messages]; if (m->last_header_element != FIELD) m->num_headers++; strlncat(m->headers[m->num_headers-1][0], sizeof(m->headers[m->num_headers-1][0]), buf, len); m->last_header_element = FIELD; return 0; } int header_value_cb (http_parser *p, const char *buf, size_t len) { assert(p == &parser); struct message *m = &messages[num_messages]; strlncat(m->headers[m->num_headers-1][1], sizeof(m->headers[m->num_headers-1][1]), buf, len); m->last_header_element = VALUE; return 0; } void check_body_is_final (const http_parser *p) { if (messages[num_messages].body_is_final) { fprintf(stderr, "\n\n *** Error http_body_is_final() should return 1 " "on last on_body callback call " "but it doesn't! ***\n\n"); assert(0); abort(); } messages[num_messages].body_is_final = http_body_is_final(p); } int body_cb (http_parser *p, const char *buf, size_t len) { assert(p == &parser); strlncat(messages[num_messages].body, sizeof(messages[num_messages].body), buf, len); messages[num_messages].body_size += len; check_body_is_final(p); // printf("body_cb: '%s'\n", requests[num_messages].body); return 0; } int count_body_cb (http_parser *p, const char *buf, size_t len) { assert(p == &parser); assert(buf); messages[num_messages].body_size += len; check_body_is_final(p); return 0; } int message_begin_cb (http_parser *p) { assert(p == &parser); assert(!messages[num_messages].message_begin_cb_called); messages[num_messages].message_begin_cb_called = TRUE; return 0; } int headers_complete_cb (http_parser *p) { assert(p == &parser); messages[num_messages].method = parser.method; messages[num_messages].status_code = parser.status_code; messages[num_messages].http_major = parser.http_major; messages[num_messages].http_minor = parser.http_minor; messages[num_messages].headers_complete_cb_called = TRUE; messages[num_messages].should_keep_alive = http_should_keep_alive(&parser); return 0; } int message_complete_cb (http_parser *p) { assert(p == &parser); if (messages[num_messages].should_keep_alive != http_should_keep_alive(&parser)) { fprintf(stderr, "\n\n *** Error http_should_keep_alive() should have same " "value in both on_message_complete and on_headers_complete " "but it doesn't! ***\n\n"); assert(0); abort(); } if (messages[num_messages].body_size && http_body_is_final(p) && !messages[num_messages].body_is_final) { fprintf(stderr, "\n\n *** Error http_body_is_final() should return 1 " "on last on_body callback call " "but it doesn't! ***\n\n"); assert(0); abort(); } messages[num_messages].message_complete_cb_called = TRUE; messages[num_messages].message_complete_on_eof = currently_parsing_eof; num_messages++; return 0; } int response_status_cb (http_parser *p, const char *buf, size_t len) { assert(p == &parser); messages[num_messages].status_cb_called = TRUE; strlncat(messages[num_messages].response_status, sizeof(messages[num_messages].response_status), buf, len); return 0; } int chunk_header_cb (http_parser *p) { assert(p == &parser); int chunk_idx = messages[num_messages].num_chunks; messages[num_messages].num_chunks++; if (chunk_idx < MAX_CHUNKS) { messages[num_messages].chunk_lengths[chunk_idx] = p->content_length; } return 0; } int chunk_complete_cb (http_parser *p) { assert(p == &parser); /* Here we want to verify that each chunk_header_cb is matched by a * chunk_complete_cb, so not only should the total number of calls to * both callbacks be the same, but they also should be interleaved * properly */ assert(messages[num_messages].num_chunks == messages[num_messages].num_chunks_complete + 1); messages[num_messages].num_chunks_complete++; return 0; } /* These dontcall_* callbacks exist so that we can verify that when we're * paused, no additional callbacks are invoked */ int dontcall_message_begin_cb (http_parser *p) { if (p) { } // gcc fprintf(stderr, "\n\n*** on_message_begin() called on paused parser ***\n\n"); abort(); } int dontcall_header_field_cb (http_parser *p, const char *buf, size_t len) { if (p || buf || len) { } // gcc fprintf(stderr, "\n\n*** on_header_field() called on paused parser ***\n\n"); abort(); } int dontcall_header_value_cb (http_parser *p, const char *buf, size_t len) { if (p || buf || len) { } // gcc fprintf(stderr, "\n\n*** on_header_value() called on paused parser ***\n\n"); abort(); } int dontcall_request_url_cb (http_parser *p, const char *buf, size_t len) { if (p || buf || len) { } // gcc fprintf(stderr, "\n\n*** on_request_url() called on paused parser ***\n\n"); abort(); } int dontcall_body_cb (http_parser *p, const char *buf, size_t len) { if (p || buf || len) { } // gcc fprintf(stderr, "\n\n*** on_body_cb() called on paused parser ***\n\n"); abort(); } int dontcall_headers_complete_cb (http_parser *p) { if (p) { } // gcc fprintf(stderr, "\n\n*** on_headers_complete() called on paused " "parser ***\n\n"); abort(); } int dontcall_message_complete_cb (http_parser *p) { if (p) { } // gcc fprintf(stderr, "\n\n*** on_message_complete() called on paused " "parser ***\n\n"); abort(); } int dontcall_response_status_cb (http_parser *p, const char *buf, size_t len) { if (p || buf || len) { } // gcc fprintf(stderr, "\n\n*** on_status() called on paused parser ***\n\n"); abort(); } int dontcall_chunk_header_cb (http_parser *p) { if (p) { } // gcc fprintf(stderr, "\n\n*** on_chunk_header() called on paused parser ***\n\n"); exit(1); } int dontcall_chunk_complete_cb (http_parser *p) { if (p) { } // gcc fprintf(stderr, "\n\n*** on_chunk_complete() " "called on paused parser ***\n\n"); exit(1); } static http_parser_settings settings_dontcall = {.on_message_begin = dontcall_message_begin_cb ,.on_header_field = dontcall_header_field_cb ,.on_header_value = dontcall_header_value_cb ,.on_url = dontcall_request_url_cb ,.on_status = dontcall_response_status_cb ,.on_body = dontcall_body_cb ,.on_headers_complete = dontcall_headers_complete_cb ,.on_message_complete = dontcall_message_complete_cb ,.on_chunk_header = dontcall_chunk_header_cb ,.on_chunk_complete = dontcall_chunk_complete_cb }; /* These pause_* callbacks always pause the parser and just invoke the regular * callback that tracks content. Before returning, we overwrite the parser * settings to point to the _dontcall variety so that we can verify that * the pause actually did, you know, pause. */ int pause_message_begin_cb (http_parser *p) { http_parser_pause(p, 1); *current_pause_parser = settings_dontcall; return message_begin_cb(p); } int pause_header_field_cb (http_parser *p, const char *buf, size_t len) { http_parser_pause(p, 1); *current_pause_parser = settings_dontcall; return header_field_cb(p, buf, len); } int pause_header_value_cb (http_parser *p, const char *buf, size_t len) { http_parser_pause(p, 1); *current_pause_parser = settings_dontcall; return header_value_cb(p, buf, len); } int pause_request_url_cb (http_parser *p, const char *buf, size_t len) { http_parser_pause(p, 1); *current_pause_parser = settings_dontcall; return request_url_cb(p, buf, len); } int pause_body_cb (http_parser *p, const char *buf, size_t len) { http_parser_pause(p, 1); *current_pause_parser = settings_dontcall; return body_cb(p, buf, len); } int pause_headers_complete_cb (http_parser *p) { http_parser_pause(p, 1); *current_pause_parser = settings_dontcall; return headers_complete_cb(p); } int pause_message_complete_cb (http_parser *p) { http_parser_pause(p, 1); *current_pause_parser = settings_dontcall; return message_complete_cb(p); } int pause_response_status_cb (http_parser *p, const char *buf, size_t len) { http_parser_pause(p, 1); *current_pause_parser = settings_dontcall; return response_status_cb(p, buf, len); } int pause_chunk_header_cb (http_parser *p) { http_parser_pause(p, 1); *current_pause_parser = settings_dontcall; return chunk_header_cb(p); } int pause_chunk_complete_cb (http_parser *p) { http_parser_pause(p, 1); *current_pause_parser = settings_dontcall; return chunk_complete_cb(p); } int connect_headers_complete_cb (http_parser *p) { headers_complete_cb(p); return 1; } int connect_message_complete_cb (http_parser *p) { messages[num_messages].should_keep_alive = http_should_keep_alive(&parser); return message_complete_cb(p); } static http_parser_settings settings_pause = {.on_message_begin = pause_message_begin_cb ,.on_header_field = pause_header_field_cb ,.on_header_value = pause_header_value_cb ,.on_url = pause_request_url_cb ,.on_status = pause_response_status_cb ,.on_body = pause_body_cb ,.on_headers_complete = pause_headers_complete_cb ,.on_message_complete = pause_message_complete_cb ,.on_chunk_header = pause_chunk_header_cb ,.on_chunk_complete = pause_chunk_complete_cb }; static http_parser_settings settings = {.on_message_begin = message_begin_cb ,.on_header_field = header_field_cb ,.on_header_value = header_value_cb ,.on_url = request_url_cb ,.on_status = response_status_cb ,.on_body = body_cb ,.on_headers_complete = headers_complete_cb ,.on_message_complete = message_complete_cb ,.on_chunk_header = chunk_header_cb ,.on_chunk_complete = chunk_complete_cb }; static http_parser_settings settings_count_body = {.on_message_begin = message_begin_cb ,.on_header_field = header_field_cb ,.on_header_value = header_value_cb ,.on_url = request_url_cb ,.on_status = response_status_cb ,.on_body = count_body_cb ,.on_headers_complete = headers_complete_cb ,.on_message_complete = message_complete_cb ,.on_chunk_header = chunk_header_cb ,.on_chunk_complete = chunk_complete_cb }; static http_parser_settings settings_connect = {.on_message_begin = message_begin_cb ,.on_header_field = header_field_cb ,.on_header_value = header_value_cb ,.on_url = request_url_cb ,.on_status = response_status_cb ,.on_body = dontcall_body_cb ,.on_headers_complete = connect_headers_complete_cb ,.on_message_complete = connect_message_complete_cb ,.on_chunk_header = chunk_header_cb ,.on_chunk_complete = chunk_complete_cb }; static http_parser_settings settings_null = {.on_message_begin = 0 ,.on_header_field = 0 ,.on_header_value = 0 ,.on_url = 0 ,.on_status = 0 ,.on_body = 0 ,.on_headers_complete = 0 ,.on_message_complete = 0 ,.on_chunk_header = 0 ,.on_chunk_complete = 0 }; void parser_init (enum http_parser_type type) { num_messages = 0; http_parser_init(&parser, type); memset(&messages, 0, sizeof messages); } size_t parse (const char *buf, size_t len) { size_t nparsed; currently_parsing_eof = (len == 0); nparsed = http_parser_execute(&parser, &settings, buf, len); return nparsed; } size_t parse_count_body (const char *buf, size_t len) { size_t nparsed; currently_parsing_eof = (len == 0); nparsed = http_parser_execute(&parser, &settings_count_body, buf, len); return nparsed; } size_t parse_pause (const char *buf, size_t len) { size_t nparsed; http_parser_settings s = settings_pause; currently_parsing_eof = (len == 0); current_pause_parser = &s; nparsed = http_parser_execute(&parser, current_pause_parser, buf, len); return nparsed; } size_t parse_connect (const char *buf, size_t len) { size_t nparsed; currently_parsing_eof = (len == 0); nparsed = http_parser_execute(&parser, &settings_connect, buf, len); return nparsed; } static inline int check_str_eq (const struct message *m, const char *prop, const char *expected, const char *found) { if ((expected == NULL) != (found == NULL)) { printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name); printf("expected %s\n", (expected == NULL) ? "NULL" : expected); printf(" found %s\n", (found == NULL) ? "NULL" : found); return 0; } if (expected != NULL && 0 != strcmp(expected, found)) { printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name); printf("expected '%s'\n", expected); printf(" found '%s'\n", found); return 0; } return 1; } static inline int check_num_eq (const struct message *m, const char *prop, int expected, int found) { if (expected != found) { printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name); printf("expected %d\n", expected); printf(" found %d\n", found); return 0; } return 1; } #define MESSAGE_CHECK_STR_EQ(expected, found, prop) \ if (!check_str_eq(expected, #prop, expected->prop, found->prop)) return 0 #define MESSAGE_CHECK_NUM_EQ(expected, found, prop) \ if (!check_num_eq(expected, #prop, expected->prop, found->prop)) return 0 #define MESSAGE_CHECK_URL_EQ(u, expected, found, prop, fn) \ do { \ char ubuf[256]; \ \ if ((u)->field_set & (1 << (fn))) { \ memcpy(ubuf, (found)->request_url + (u)->field_data[(fn)].off, \ (u)->field_data[(fn)].len); \ ubuf[(u)->field_data[(fn)].len] = '\0'; \ } else { \ ubuf[0] = '\0'; \ } \ \ check_str_eq(expected, #prop, expected->prop, ubuf); \ } while(0) int message_eq (int index, int connect, const struct message *expected) { int i; struct message *m = &messages[index]; MESSAGE_CHECK_NUM_EQ(expected, m, http_major); MESSAGE_CHECK_NUM_EQ(expected, m, http_minor); if (expected->type == HTTP_REQUEST) { MESSAGE_CHECK_NUM_EQ(expected, m, method); } else { MESSAGE_CHECK_NUM_EQ(expected, m, status_code); MESSAGE_CHECK_STR_EQ(expected, m, response_status); assert(m->status_cb_called); } if (!connect) { MESSAGE_CHECK_NUM_EQ(expected, m, should_keep_alive); MESSAGE_CHECK_NUM_EQ(expected, m, message_complete_on_eof); } assert(m->message_begin_cb_called); assert(m->headers_complete_cb_called); assert(m->message_complete_cb_called); MESSAGE_CHECK_STR_EQ(expected, m, request_url); /* Check URL components; we can't do this w/ CONNECT since it doesn't * send us a well-formed URL. */ if (*m->request_url && m->method != HTTP_CONNECT) { struct http_parser_url u; if (http_parser_parse_url(m->request_url, strlen(m->request_url), 0, &u)) { fprintf(stderr, "\n\n*** failed to parse URL %s ***\n\n", m->request_url); abort(); } if (expected->host) { MESSAGE_CHECK_URL_EQ(&u, expected, m, host, UF_HOST); } if (expected->userinfo) { MESSAGE_CHECK_URL_EQ(&u, expected, m, userinfo, UF_USERINFO); } m->port = (u.field_set & (1 << UF_PORT)) ? u.port : 0; MESSAGE_CHECK_URL_EQ(&u, expected, m, query_string, UF_QUERY); MESSAGE_CHECK_URL_EQ(&u, expected, m, fragment, UF_FRAGMENT); MESSAGE_CHECK_URL_EQ(&u, expected, m, request_path, UF_PATH); MESSAGE_CHECK_NUM_EQ(expected, m, port); } if (connect) { check_num_eq(m, "body_size", 0, m->body_size); } else if (expected->body_size) { MESSAGE_CHECK_NUM_EQ(expected, m, body_size); } else { MESSAGE_CHECK_STR_EQ(expected, m, body); } if (connect) { check_num_eq(m, "num_chunks_complete", 0, m->num_chunks_complete); } else { assert(m->num_chunks == m->num_chunks_complete); MESSAGE_CHECK_NUM_EQ(expected, m, num_chunks_complete); for (i = 0; i < m->num_chunks && i < MAX_CHUNKS; i++) { MESSAGE_CHECK_NUM_EQ(expected, m, chunk_lengths[i]); } } MESSAGE_CHECK_NUM_EQ(expected, m, num_headers); int r; for (i = 0; i < m->num_headers; i++) { r = check_str_eq(expected, "header field", expected->headers[i][0], m->headers[i][0]); if (!r) return 0; r = check_str_eq(expected, "header value", expected->headers[i][1], m->headers[i][1]); if (!r) return 0; } if (!connect) { MESSAGE_CHECK_STR_EQ(expected, m, upgrade); } return 1; } /* Given a sequence of varargs messages, return the number of them that the * parser should successfully parse, taking into account that upgraded * messages prevent all subsequent messages from being parsed. */ size_t count_parsed_messages(const size_t nmsgs, ...) { size_t i; va_list ap; va_start(ap, nmsgs); for (i = 0; i < nmsgs; i++) { struct message *m = va_arg(ap, struct message *); if (m->upgrade) { va_end(ap); return i + 1; } } va_end(ap); return nmsgs; } /* Given a sequence of bytes and the number of these that we were able to * parse, verify that upgrade bodies are correct. */ void upgrade_message_fix(char *body, const size_t nread, const size_t nmsgs, ...) { va_list ap; size_t i; size_t off = 0; va_start(ap, nmsgs); for (i = 0; i < nmsgs; i++) { struct message *m = va_arg(ap, struct message *); off += strlen(m->raw); if (m->upgrade) { off -= strlen(m->upgrade); /* Check the portion of the response after its specified upgrade */ if (!check_str_eq(m, "upgrade", body + off, body + nread)) { abort(); } /* Fix up the response so that message_eq() will verify the beginning * of the upgrade */ *(body + nread + strlen(m->upgrade)) = '\0'; messages[num_messages -1 ].upgrade = body + nread; va_end(ap); return; } } va_end(ap); printf("\n\n*** Error: expected a message with upgrade ***\n"); abort(); } static void print_error (const char *raw, size_t error_location) { fprintf(stderr, "\n*** %s ***\n\n", http_errno_description(HTTP_PARSER_ERRNO(&parser))); int this_line = 0, char_len = 0; size_t i, j, len = strlen(raw), error_location_line = 0; for (i = 0; i < len; i++) { if (i == error_location) this_line = 1; switch (raw[i]) { case '\r': char_len = 2; fprintf(stderr, "\\r"); break; case '\n': fprintf(stderr, "\\n\n"); if (this_line) goto print; error_location_line = 0; continue; default: char_len = 1; fputc(raw[i], stderr); break; } if (!this_line) error_location_line += char_len; } fprintf(stderr, "[eof]\n"); print: for (j = 0; j < error_location_line; j++) { fputc(' ', stderr); } fprintf(stderr, "^\n\nerror location: %u\n", (unsigned int)error_location); } void test_preserve_data (void) { char my_data[] = "application-specific data"; http_parser parser; parser.data = my_data; http_parser_init(&parser, HTTP_REQUEST); if (parser.data != my_data) { printf("\n*** parser.data not preserved accross http_parser_init ***\n\n"); abort(); } } struct url_test { const char *name; const char *url; int is_connect; struct http_parser_url u; int rv; }; const struct url_test url_tests[] = { {.name="proxy request" ,.url="http://hostname/" ,.is_connect=0 ,.u= {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH) ,.port=0 ,.field_data= {{ 0, 4 } /* UF_SCHEMA */ ,{ 7, 8 } /* UF_HOST */ ,{ 0, 0 } /* UF_PORT */ ,{ 15, 1 } /* UF_PATH */ ,{ 0, 0 } /* UF_QUERY */ ,{ 0, 0 } /* UF_FRAGMENT */ ,{ 0, 0 } /* UF_USERINFO */ } } ,.rv=0 } , {.name="proxy request with port" ,.url="http://hostname:444/" ,.is_connect=0 ,.u= {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PORT) | (1 << UF_PATH) ,.port=444 ,.field_data= {{ 0, 4 } /* UF_SCHEMA */ ,{ 7, 8 } /* UF_HOST */ ,{ 16, 3 } /* UF_PORT */ ,{ 19, 1 } /* UF_PATH */ ,{ 0, 0 } /* UF_QUERY */ ,{ 0, 0 } /* UF_FRAGMENT */ ,{ 0, 0 } /* UF_USERINFO */ } } ,.rv=0 } , {.name="CONNECT request" ,.url="hostname:443" ,.is_connect=1 ,.u= {.field_set=(1 << UF_HOST) | (1 << UF_PORT) ,.port=443 ,.field_data= {{ 0, 0 } /* UF_SCHEMA */ ,{ 0, 8 } /* UF_HOST */ ,{ 9, 3 } /* UF_PORT */ ,{ 0, 0 } /* UF_PATH */ ,{ 0, 0 } /* UF_QUERY */ ,{ 0, 0 } /* UF_FRAGMENT */ ,{ 0, 0 } /* UF_USERINFO */ } } ,.rv=0 } , {.name="CONNECT request but not connect" ,.url="hostname:443" ,.is_connect=0 ,.rv=1 } , {.name="proxy ipv6 request" ,.url="http://[1:2::3:4]/" ,.is_connect=0 ,.u= {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH) ,.port=0 ,.field_data= {{ 0, 4 } /* UF_SCHEMA */ ,{ 8, 8 } /* UF_HOST */ ,{ 0, 0 } /* UF_PORT */ ,{ 17, 1 } /* UF_PATH */ ,{ 0, 0 } /* UF_QUERY */ ,{ 0, 0 } /* UF_FRAGMENT */ ,{ 0, 0 } /* UF_USERINFO */ } } ,.rv=0 } , {.name="proxy ipv6 request with port" ,.url="http://[1:2::3:4]:67/" ,.is_connect=0 ,.u= {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PORT) | (1 << UF_PATH) ,.port=67 ,.field_data= {{ 0, 4 } /* UF_SCHEMA */ ,{ 8, 8 } /* UF_HOST */ ,{ 18, 2 } /* UF_PORT */ ,{ 20, 1 } /* UF_PATH */ ,{ 0, 0 } /* UF_QUERY */ ,{ 0, 0 } /* UF_FRAGMENT */ ,{ 0, 0 } /* UF_USERINFO */ } } ,.rv=0 } , {.name="CONNECT ipv6 address" ,.url="[1:2::3:4]:443" ,.is_connect=1 ,.u= {.field_set=(1 << UF_HOST) | (1 << UF_PORT) ,.port=443 ,.field_data= {{ 0, 0 } /* UF_SCHEMA */ ,{ 1, 8 } /* UF_HOST */ ,{ 11, 3 } /* UF_PORT */ ,{ 0, 0 } /* UF_PATH */ ,{ 0, 0 } /* UF_QUERY */ ,{ 0, 0 } /* UF_FRAGMENT */ ,{ 0, 0 } /* UF_USERINFO */ } } ,.rv=0 } , {.name="ipv4 in ipv6 address" ,.url="http://[2001:0000:0000:0000:0000:0000:1.9.1.1]/" ,.is_connect=0 ,.u= {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH) ,.port=0 ,.field_data= {{ 0, 4 } /* UF_SCHEMA */ ,{ 8, 37 } /* UF_HOST */ ,{ 0, 0 } /* UF_PORT */ ,{ 46, 1 } /* UF_PATH */ ,{ 0, 0 } /* UF_QUERY */ ,{ 0, 0 } /* UF_FRAGMENT */ ,{ 0, 0 } /* UF_USERINFO */ } } ,.rv=0 } , {.name="extra ? in query string" ,.url="http://a.tbcdn.cn/p/fp/2010c/??fp-header-min.css,fp-base-min.css," "fp-channel-min.css,fp-product-min.css,fp-mall-min.css,fp-category-min.css," "fp-sub-min.css,fp-gdp4p-min.css,fp-css3-min.css,fp-misc-min.css?t=20101022.css" ,.is_connect=0 ,.u= {.field_set=(1<field_set, u->port); for (i = 0; i < UF_MAX; i++) { if ((u->field_set & (1 << i)) == 0) { printf("\tfield_data[%u]: unset\n", i); continue; } printf("\tfield_data[%u]: off: %u len: %u part: \"%.*s\n\"", i, u->field_data[i].off, u->field_data[i].len, u->field_data[i].len, url + u->field_data[i].off); } } void test_parse_url (void) { struct http_parser_url u; const struct url_test *test; unsigned int i; int rv; for (i = 0; i < (sizeof(url_tests) / sizeof(url_tests[0])); i++) { test = &url_tests[i]; memset(&u, 0, sizeof(u)); rv = http_parser_parse_url(test->url, test->url ? strlen(test->url) : 0, test->is_connect, &u); if (test->rv == 0) { if (rv != 0) { printf("\n*** http_parser_parse_url(\"%s\") \"%s\" test failed, " "unexpected rv %d ***\n\n", test->url, test->name, rv); abort(); } if (memcmp(&u, &test->u, sizeof(u)) != 0) { printf("\n*** http_parser_parse_url(\"%s\") \"%s\" failed ***\n", test->url, test->name); printf("target http_parser_url:\n"); dump_url(test->url, &test->u); printf("result http_parser_url:\n"); dump_url(test->url, &u); abort(); } } else { /* test->rv != 0 */ if (rv == 0) { printf("\n*** http_parser_parse_url(\"%s\") \"%s\" test failed, " "unexpected rv %d ***\n\n", test->url, test->name, rv); abort(); } } } } void test_method_str (void) { assert(0 == strcmp("GET", http_method_str(HTTP_GET))); assert(0 == strcmp("", http_method_str(1337))); } void test_status_str (void) { assert(0 == strcmp("OK", http_status_str(HTTP_STATUS_OK))); assert(0 == strcmp("Not Found", http_status_str(HTTP_STATUS_NOT_FOUND))); assert(0 == strcmp("", http_status_str(1337))); } void test_message (const struct message *message) { size_t raw_len = strlen(message->raw); size_t msg1len; for (msg1len = 0; msg1len < raw_len; msg1len++) { parser_init(message->type); size_t read; const char *msg1 = message->raw; const char *msg2 = msg1 + msg1len; size_t msg2len = raw_len - msg1len; if (msg1len) { assert(num_messages == 0); messages[0].headers_complete_cb_called = FALSE; read = parse(msg1, msg1len); if (!messages[0].headers_complete_cb_called && parser.nread != read) { assert(parser.nread == read); print_error(msg1, read); abort(); } if (message->upgrade && parser.upgrade && num_messages > 0) { messages[num_messages - 1].upgrade = msg1 + read; goto test; } if (read != msg1len) { print_error(msg1, read); abort(); } } read = parse(msg2, msg2len); if (message->upgrade && parser.upgrade) { messages[num_messages - 1].upgrade = msg2 + read; goto test; } if (read != msg2len) { print_error(msg2, read); abort(); } read = parse(NULL, 0); if (read != 0) { print_error(message->raw, read); abort(); } test: if (num_messages != 1) { printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name); abort(); } if(!message_eq(0, 0, message)) abort(); } } void test_message_count_body (const struct message *message) { parser_init(message->type); size_t read; size_t l = strlen(message->raw); size_t i, toread; size_t chunk = 4024; for (i = 0; i < l; i+= chunk) { toread = MIN(l-i, chunk); read = parse_count_body(message->raw + i, toread); if (read != toread) { print_error(message->raw, read); abort(); } } read = parse_count_body(NULL, 0); if (read != 0) { print_error(message->raw, read); abort(); } if (num_messages != 1) { printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name); abort(); } if(!message_eq(0, 0, message)) abort(); } void test_simple_type (const char *buf, enum http_errno err_expected, enum http_parser_type type) { parser_init(type); enum http_errno err; parse(buf, strlen(buf)); err = HTTP_PARSER_ERRNO(&parser); parse(NULL, 0); /* In strict mode, allow us to pass with an unexpected HPE_STRICT as * long as the caller isn't expecting success. */ #if HTTP_PARSER_STRICT if (err_expected != err && err_expected != HPE_OK && err != HPE_STRICT) { #else if (err_expected != err) { #endif fprintf(stderr, "\n*** test_simple expected %s, but saw %s ***\n\n%s\n", http_errno_name(err_expected), http_errno_name(err), buf); abort(); } } void test_simple (const char *buf, enum http_errno err_expected) { test_simple_type(buf, err_expected, HTTP_REQUEST); } void test_invalid_header_content (int req, const char* str) { http_parser parser; http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); size_t parsed; const char *buf; buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.1 200 OK\r\n"; parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); assert(parsed == strlen(buf)); buf = str; size_t buflen = strlen(buf); parsed = http_parser_execute(&parser, &settings_null, buf, buflen); if (parsed != buflen) { assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_HEADER_TOKEN); return; } fprintf(stderr, "\n*** Error expected but none in invalid header content test ***\n"); abort(); } void test_invalid_header_field_content_error (int req) { test_invalid_header_content(req, "Foo: F\01ailure"); test_invalid_header_content(req, "Foo: B\02ar"); } void test_invalid_header_field (int req, const char* str) { http_parser parser; http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); size_t parsed; const char *buf; buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.1 200 OK\r\n"; parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); assert(parsed == strlen(buf)); buf = str; size_t buflen = strlen(buf); parsed = http_parser_execute(&parser, &settings_null, buf, buflen); if (parsed != buflen) { assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_HEADER_TOKEN); return; } fprintf(stderr, "\n*** Error expected but none in invalid header token test ***\n"); abort(); } void test_invalid_header_field_token_error (int req) { test_invalid_header_field(req, "Fo@: Failure"); test_invalid_header_field(req, "Foo\01\test: Bar"); } void test_double_content_length_error (int req) { http_parser parser; http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); size_t parsed; const char *buf; buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.1 200 OK\r\n"; parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); assert(parsed == strlen(buf)); buf = "Content-Length: 0\r\nContent-Length: 1\r\n\r\n"; size_t buflen = strlen(buf); parsed = http_parser_execute(&parser, &settings_null, buf, buflen); if (parsed != buflen) { assert(HTTP_PARSER_ERRNO(&parser) == HPE_UNEXPECTED_CONTENT_LENGTH); return; } fprintf(stderr, "\n*** Error expected but none in double content-length test ***\n"); abort(); } void test_chunked_content_length_error (int req) { http_parser parser; http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); size_t parsed; const char *buf; buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.1 200 OK\r\n"; parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); assert(parsed == strlen(buf)); buf = "Transfer-Encoding: anything\r\nContent-Length: 1\r\n\r\n"; size_t buflen = strlen(buf); parsed = http_parser_execute(&parser, &settings_null, buf, buflen); if (parsed != buflen) { assert(HTTP_PARSER_ERRNO(&parser) == HPE_UNEXPECTED_CONTENT_LENGTH); return; } fprintf(stderr, "\n*** Error expected but none in chunked content-length test ***\n"); abort(); } void test_header_cr_no_lf_error (int req) { http_parser parser; http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); size_t parsed; const char *buf; buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.1 200 OK\r\n"; parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); assert(parsed == strlen(buf)); buf = "Foo: 1\rBar: 1\r\n\r\n"; size_t buflen = strlen(buf); parsed = http_parser_execute(&parser, &settings_null, buf, buflen); if (parsed != buflen) { assert(HTTP_PARSER_ERRNO(&parser) == HPE_LF_EXPECTED); return; } fprintf(stderr, "\n*** Error expected but none in header whitespace test ***\n"); abort(); } void test_no_overflow_parse_url (void) { int rv; struct http_parser_url u; http_parser_url_init(&u); rv = http_parser_parse_url("http://example.com:8001", 22, 0, &u); if (rv != 0) { fprintf(stderr, "\n*** test_no_overflow_parse_url invalid return value=%d\n", rv); abort(); } if (u.port != 800) { fprintf(stderr, "\n*** test_no_overflow_parse_url invalid port number=%d\n", u.port); abort(); } } void test_header_overflow_error (int req) { http_parser parser; http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); size_t parsed; const char *buf; buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.0 200 OK\r\n"; parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); assert(parsed == strlen(buf)); buf = "header-key: header-value\r\n"; size_t buflen = strlen(buf); int i; for (i = 0; i < 10000; i++) { parsed = http_parser_execute(&parser, &settings_null, buf, buflen); if (parsed != buflen) { //fprintf(stderr, "error found on iter %d\n", i); assert(HTTP_PARSER_ERRNO(&parser) == HPE_HEADER_OVERFLOW); return; } } fprintf(stderr, "\n*** Error expected but none in header overflow test ***\n"); abort(); } void test_header_nread_value () { http_parser parser; http_parser_init(&parser, HTTP_REQUEST); size_t parsed; const char *buf; buf = "GET / HTTP/1.1\r\nheader: value\nhdr: value\r\n"; parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); assert(parsed == strlen(buf)); assert(parser.nread == strlen(buf)); } static void test_content_length_overflow (const char *buf, size_t buflen, int expect_ok) { http_parser parser; http_parser_init(&parser, HTTP_RESPONSE); http_parser_execute(&parser, &settings_null, buf, buflen); if (expect_ok) assert(HTTP_PARSER_ERRNO(&parser) == HPE_OK); else assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_CONTENT_LENGTH); } void test_header_content_length_overflow_error (void) { #define X(size) \ "HTTP/1.1 200 OK\r\n" \ "Content-Length: " #size "\r\n" \ "\r\n" const char a[] = X(1844674407370955160); /* 2^64 / 10 - 1 */ const char b[] = X(18446744073709551615); /* 2^64-1 */ const char c[] = X(18446744073709551616); /* 2^64 */ #undef X test_content_length_overflow(a, sizeof(a) - 1, 1); /* expect ok */ test_content_length_overflow(b, sizeof(b) - 1, 0); /* expect failure */ test_content_length_overflow(c, sizeof(c) - 1, 0); /* expect failure */ } void test_chunk_content_length_overflow_error (void) { #define X(size) \ "HTTP/1.1 200 OK\r\n" \ "Transfer-Encoding: chunked\r\n" \ "\r\n" \ #size "\r\n" \ "..." const char a[] = X(FFFFFFFFFFFFFFE); /* 2^64 / 16 - 1 */ const char b[] = X(FFFFFFFFFFFFFFFF); /* 2^64-1 */ const char c[] = X(10000000000000000); /* 2^64 */ #undef X test_content_length_overflow(a, sizeof(a) - 1, 1); /* expect ok */ test_content_length_overflow(b, sizeof(b) - 1, 0); /* expect failure */ test_content_length_overflow(c, sizeof(c) - 1, 0); /* expect failure */ } void test_no_overflow_long_body (int req, size_t length) { http_parser parser; http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); size_t parsed; size_t i; char buf1[3000]; size_t buf1len = sprintf(buf1, "%s\r\nConnection: Keep-Alive\r\nContent-Length: %lu\r\n\r\n", req ? "POST / HTTP/1.0" : "HTTP/1.0 200 OK", (unsigned long)length); parsed = http_parser_execute(&parser, &settings_null, buf1, buf1len); if (parsed != buf1len) goto err; for (i = 0; i < length; i++) { char foo = 'a'; parsed = http_parser_execute(&parser, &settings_null, &foo, 1); if (parsed != 1) goto err; } parsed = http_parser_execute(&parser, &settings_null, buf1, buf1len); if (parsed != buf1len) goto err; return; err: fprintf(stderr, "\n*** error in test_no_overflow_long_body %s of length %lu ***\n", req ? "REQUEST" : "RESPONSE", (unsigned long)length); abort(); } void test_multiple3 (const struct message *r1, const struct message *r2, const struct message *r3) { int message_count = count_parsed_messages(3, r1, r2, r3); char total[ strlen(r1->raw) + strlen(r2->raw) + strlen(r3->raw) + 1 ]; total[0] = '\0'; strcat(total, r1->raw); strcat(total, r2->raw); strcat(total, r3->raw); parser_init(r1->type); size_t read; read = parse(total, strlen(total)); if (parser.upgrade) { upgrade_message_fix(total, read, 3, r1, r2, r3); goto test; } if (read != strlen(total)) { print_error(total, read); abort(); } read = parse(NULL, 0); if (read != 0) { print_error(total, read); abort(); } test: if (message_count != num_messages) { fprintf(stderr, "\n\n*** Parser didn't see 3 messages only %d *** \n", num_messages); abort(); } if (!message_eq(0, 0, r1)) abort(); if (message_count > 1 && !message_eq(1, 0, r2)) abort(); if (message_count > 2 && !message_eq(2, 0, r3)) abort(); } /* SCAN through every possible breaking to make sure the * parser can handle getting the content in any chunks that * might come from the socket */ void test_scan (const struct message *r1, const struct message *r2, const struct message *r3) { char total[80*1024] = "\0"; char buf1[80*1024] = "\0"; char buf2[80*1024] = "\0"; char buf3[80*1024] = "\0"; strcat(total, r1->raw); strcat(total, r2->raw); strcat(total, r3->raw); size_t read; int total_len = strlen(total); int total_ops = 2 * (total_len - 1) * (total_len - 2) / 2; int ops = 0 ; size_t buf1_len, buf2_len, buf3_len; int message_count = count_parsed_messages(3, r1, r2, r3); int i,j,type_both; for (type_both = 0; type_both < 2; type_both ++ ) { for (j = 2; j < total_len; j ++ ) { for (i = 1; i < j; i ++ ) { if (ops % 1000 == 0) { printf("\b\b\b\b%3.0f%%", 100 * (float)ops /(float)total_ops); fflush(stdout); } ops += 1; parser_init(type_both ? HTTP_BOTH : r1->type); buf1_len = i; strlncpy(buf1, sizeof(buf1), total, buf1_len); buf1[buf1_len] = 0; buf2_len = j - i; strlncpy(buf2, sizeof(buf1), total+i, buf2_len); buf2[buf2_len] = 0; buf3_len = total_len - j; strlncpy(buf3, sizeof(buf1), total+j, buf3_len); buf3[buf3_len] = 0; assert(num_messages == 0); messages[0].headers_complete_cb_called = FALSE; read = parse(buf1, buf1_len); if (!messages[0].headers_complete_cb_called && parser.nread != read) { print_error(buf1, read); goto error; } if (parser.upgrade) goto test; if (read != buf1_len) { print_error(buf1, read); goto error; } read += parse(buf2, buf2_len); if (parser.upgrade) goto test; if (read != buf1_len + buf2_len) { print_error(buf2, read); goto error; } read += parse(buf3, buf3_len); if (parser.upgrade) goto test; if (read != buf1_len + buf2_len + buf3_len) { print_error(buf3, read); goto error; } parse(NULL, 0); test: if (parser.upgrade) { upgrade_message_fix(total, read, 3, r1, r2, r3); } if (message_count != num_messages) { fprintf(stderr, "\n\nParser didn't see %d messages only %d\n", message_count, num_messages); goto error; } if (!message_eq(0, 0, r1)) { fprintf(stderr, "\n\nError matching messages[0] in test_scan.\n"); goto error; } if (message_count > 1 && !message_eq(1, 0, r2)) { fprintf(stderr, "\n\nError matching messages[1] in test_scan.\n"); goto error; } if (message_count > 2 && !message_eq(2, 0, r3)) { fprintf(stderr, "\n\nError matching messages[2] in test_scan.\n"); goto error; } } } } puts("\b\b\b\b100%"); return; error: fprintf(stderr, "i=%d j=%d\n", i, j); fprintf(stderr, "buf1 (%u) %s\n\n", (unsigned int)buf1_len, buf1); fprintf(stderr, "buf2 (%u) %s\n\n", (unsigned int)buf2_len , buf2); fprintf(stderr, "buf3 (%u) %s\n", (unsigned int)buf3_len, buf3); abort(); } // user required to free the result // string terminated by \0 char * create_large_chunked_message (int body_size_in_kb, const char* headers) { int i; size_t wrote = 0; size_t headers_len = strlen(headers); size_t bufsize = headers_len + (5+1024+2)*body_size_in_kb + 6; char * buf = malloc(bufsize); memcpy(buf, headers, headers_len); wrote += headers_len; for (i = 0; i < body_size_in_kb; i++) { // write 1kb chunk into the body. memcpy(buf + wrote, "400\r\n", 5); wrote += 5; memset(buf + wrote, 'C', 1024); wrote += 1024; strcpy(buf + wrote, "\r\n"); wrote += 2; } memcpy(buf + wrote, "0\r\n\r\n", 6); wrote += 6; assert(wrote == bufsize); return buf; } /* Verify that we can pause parsing at any of the bytes in the * message and still get the result that we're expecting. */ void test_message_pause (const struct message *msg) { char *buf = (char*) msg->raw; size_t buflen = strlen(msg->raw); size_t nread; parser_init(msg->type); do { nread = parse_pause(buf, buflen); // We can only set the upgrade buffer once we've gotten our message // completion callback. if (messages[0].message_complete_cb_called && msg->upgrade && parser.upgrade) { messages[0].upgrade = buf + nread; goto test; } if (nread < buflen) { // Not much do to if we failed a strict-mode check if (HTTP_PARSER_ERRNO(&parser) == HPE_STRICT) { return; } assert (HTTP_PARSER_ERRNO(&parser) == HPE_PAUSED); } buf += nread; buflen -= nread; http_parser_pause(&parser, 0); } while (buflen > 0); nread = parse_pause(NULL, 0); assert (nread == 0); test: if (num_messages != 1) { printf("\n*** num_messages != 1 after testing '%s' ***\n\n", msg->name); abort(); } if(!message_eq(0, 0, msg)) abort(); } /* Verify that body and next message won't be parsed in responses to CONNECT */ void test_message_connect (const struct message *msg) { char *buf = (char*) msg->raw; size_t buflen = strlen(msg->raw); parser_init(msg->type); parse_connect(buf, buflen); if (num_messages != 1) { printf("\n*** num_messages != 1 after testing '%s' ***\n\n", msg->name); abort(); } if(!message_eq(0, 1, msg)) abort(); } int main (void) { unsigned i, j, k; unsigned long version; unsigned major; unsigned minor; unsigned patch; version = http_parser_version(); major = (version >> 16) & 255; minor = (version >> 8) & 255; patch = version & 255; printf("http_parser v%u.%u.%u (0x%06lx)\n", major, minor, patch, version); printf("sizeof(http_parser) = %u\n", (unsigned int)sizeof(http_parser)); assert(sizeof(http_parser) == 4 + 4 + 8 + 2 + 2 + 4 + sizeof(void *)); //// API test_preserve_data(); test_parse_url(); test_method_str(); test_status_str(); //// NREAD test_header_nread_value(); //// OVERFLOW CONDITIONS test_no_overflow_parse_url(); test_header_overflow_error(HTTP_REQUEST); test_no_overflow_long_body(HTTP_REQUEST, 1000); test_no_overflow_long_body(HTTP_REQUEST, 100000); test_header_overflow_error(HTTP_RESPONSE); test_no_overflow_long_body(HTTP_RESPONSE, 1000); test_no_overflow_long_body(HTTP_RESPONSE, 100000); test_header_content_length_overflow_error(); test_chunk_content_length_overflow_error(); //// HEADER FIELD CONDITIONS test_double_content_length_error(HTTP_REQUEST); test_chunked_content_length_error(HTTP_REQUEST); test_header_cr_no_lf_error(HTTP_REQUEST); test_invalid_header_field_token_error(HTTP_REQUEST); test_invalid_header_field_content_error(HTTP_REQUEST); test_double_content_length_error(HTTP_RESPONSE); test_chunked_content_length_error(HTTP_RESPONSE); test_header_cr_no_lf_error(HTTP_RESPONSE); test_invalid_header_field_token_error(HTTP_RESPONSE); test_invalid_header_field_content_error(HTTP_RESPONSE); test_simple_type( "POST / HTTP/1.1\r\n" "Content-Length:\r\n" // empty "\r\n", HPE_INVALID_CONTENT_LENGTH, HTTP_REQUEST); test_simple_type( "POST / HTTP/1.1\r\n" "Content-Length: 42 \r\n" // Note the surrounding whitespace. "\r\n", HPE_OK, HTTP_REQUEST); test_simple_type( "POST / HTTP/1.1\r\n" "Content-Length: 4 2\r\n" "\r\n", HPE_INVALID_CONTENT_LENGTH, HTTP_REQUEST); test_simple_type( "POST / HTTP/1.1\r\n" "Content-Length: 13 37\r\n" "\r\n", HPE_INVALID_CONTENT_LENGTH, HTTP_REQUEST); test_simple_type( "POST / HTTP/1.1\r\n" "Content-Length: 42\r\n" " Hello world!\r\n", HPE_INVALID_CONTENT_LENGTH, HTTP_REQUEST); test_simple_type( "POST / HTTP/1.1\r\n" "Content-Length: 42\r\n" " \r\n", HPE_OK, HTTP_REQUEST); //// RESPONSES test_simple_type("HTP/1.1 200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE); test_simple_type("HTTP/01.1 200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE); test_simple_type("HTTP/11.1 200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE); test_simple_type("HTTP/1.01 200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE); test_simple_type("HTTP/1.1\t200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE); test_simple_type("\rHTTP/1.1\t200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE); for (i = 0; i < ARRAY_SIZE(responses); i++) { test_message(&responses[i]); } for (i = 0; i < ARRAY_SIZE(responses); i++) { test_message_pause(&responses[i]); } for (i = 0; i < ARRAY_SIZE(responses); i++) { test_message_connect(&responses[i]); } for (i = 0; i < ARRAY_SIZE(responses); i++) { if (!responses[i].should_keep_alive) continue; for (j = 0; j < ARRAY_SIZE(responses); j++) { if (!responses[j].should_keep_alive) continue; for (k = 0; k < ARRAY_SIZE(responses); k++) { test_multiple3(&responses[i], &responses[j], &responses[k]); } } } test_message_count_body(&responses[NO_HEADERS_NO_BODY_404]); test_message_count_body(&responses[TRAILING_SPACE_ON_CHUNKED_BODY]); // test very large chunked response { char * msg = create_large_chunked_message(31337, "HTTP/1.0 200 OK\r\n" "Transfer-Encoding: chunked\r\n" "Content-Type: text/plain\r\n" "\r\n"); struct message large_chunked = {.name= "large chunked" ,.type= HTTP_RESPONSE ,.raw= msg ,.should_keep_alive= FALSE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 0 ,.status_code= 200 ,.response_status= "OK" ,.num_headers= 2 ,.headers= { { "Transfer-Encoding", "chunked" } , { "Content-Type", "text/plain" } } ,.body_size= 31337*1024 ,.num_chunks_complete= 31338 }; for (i = 0; i < MAX_CHUNKS; i++) { large_chunked.chunk_lengths[i] = 1024; } test_message_count_body(&large_chunked); free(msg); } printf("response scan 1/2 "); test_scan( &responses[TRAILING_SPACE_ON_CHUNKED_BODY] , &responses[NO_BODY_HTTP10_KA_204] , &responses[NO_REASON_PHRASE] ); printf("response scan 2/2 "); test_scan( &responses[BONJOUR_MADAME_FR] , &responses[UNDERSTORE_HEADER_KEY] , &responses[NO_CARRIAGE_RET] ); puts("responses okay"); /// REQUESTS test_simple("GET / IHTTP/1.0\r\n\r\n", HPE_INVALID_CONSTANT); test_simple("GET / ICE/1.0\r\n\r\n", HPE_INVALID_CONSTANT); test_simple("GET / HTP/1.1\r\n\r\n", HPE_INVALID_VERSION); test_simple("GET / HTTP/01.1\r\n\r\n", HPE_INVALID_VERSION); test_simple("GET / HTTP/11.1\r\n\r\n", HPE_INVALID_VERSION); test_simple("GET / HTTP/1.01\r\n\r\n", HPE_INVALID_VERSION); test_simple("GET / HTTP/1.0\r\nHello: w\1rld\r\n\r\n", HPE_INVALID_HEADER_TOKEN); test_simple("GET / HTTP/1.0\r\nHello: woooo\2rld\r\n\r\n", HPE_INVALID_HEADER_TOKEN); // Extended characters - see nodejs/test/parallel/test-http-headers-obstext.js test_simple("GET / HTTP/1.1\r\n" "Test: Düsseldorf\r\n", HPE_OK); // Well-formed but incomplete test_simple("GET / HTTP/1.1\r\n" "Content-Type: text/plain\r\n" "Content-Length: 6\r\n" "\r\n" "fooba", HPE_OK); // Unknown Transfer-Encoding in request test_simple("GET / HTTP/1.1\r\n" "Transfer-Encoding: unknown\r\n" "\r\n", HPE_INVALID_TRANSFER_ENCODING); static const char *all_methods[] = { "DELETE", "GET", "HEAD", "POST", "PUT", //"CONNECT", //CONNECT can't be tested like other methods, it's a tunnel "OPTIONS", "TRACE", "COPY", "LOCK", "MKCOL", "MOVE", "PROPFIND", "PROPPATCH", "SEARCH", "UNLOCK", "BIND", "REBIND", "UNBIND", "ACL", "REPORT", "MKACTIVITY", "CHECKOUT", "MERGE", "M-SEARCH", "NOTIFY", "SUBSCRIBE", "UNSUBSCRIBE", "PATCH", "PURGE", "MKCALENDAR", "LINK", "UNLINK", 0 }; const char **this_method; for (this_method = all_methods; *this_method; this_method++) { char buf[200]; sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method); test_simple(buf, HPE_OK); } static const char *bad_methods[] = { "ASDF", "C******", "COLA", "GEM", "GETA", "M****", "MKCOLA", "PROPPATCHA", "PUN", "PX", "SA", "hello world", 0 }; for (this_method = bad_methods; *this_method; this_method++) { char buf[200]; sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method); test_simple(buf, HPE_INVALID_METHOD); } // illegal header field name line folding test_simple("GET / HTTP/1.1\r\n" "name\r\n" " : value\r\n" "\r\n", HPE_INVALID_HEADER_TOKEN); const char *dumbluck2 = "GET / HTTP/1.1\r\n" "X-SSL-Nonsense: -----BEGIN CERTIFICATE-----\r\n" "\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n" "\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n" "\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n" "\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n" "\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n" "\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n" "\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n" "\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n" "\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n" "\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n" "\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n" "\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n" "\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n" "\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n" "\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n" "\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n" "\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n" "\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n" "\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n" "\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n" "\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n" "\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n" "\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n" "\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n" "\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n" "\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n" "\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n" "\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n" "\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n" "\tRA==\r\n" "\t-----END CERTIFICATE-----\r\n" "\r\n"; test_simple(dumbluck2, HPE_OK); const char *corrupted_connection = "GET / HTTP/1.1\r\n" "Host: www.example.com\r\n" "Connection\r\033\065\325eep-Alive\r\n" "Accept-Encoding: gzip\r\n" "\r\n"; test_simple(corrupted_connection, HPE_INVALID_HEADER_TOKEN); const char *corrupted_header_name = "GET / HTTP/1.1\r\n" "Host: www.example.com\r\n" "X-Some-Header\r\033\065\325eep-Alive\r\n" "Accept-Encoding: gzip\r\n" "\r\n"; test_simple(corrupted_header_name, HPE_INVALID_HEADER_TOKEN); #if 0 // NOTE(Wed Nov 18 11:57:27 CET 2009) this seems okay. we just read body // until EOF. // // no content-length // error if there is a body without content length const char *bad_get_no_headers_no_body = "GET /bad_get_no_headers_no_body/world HTTP/1.1\r\n" "Accept: */*\r\n" "\r\n" "HELLO"; test_simple(bad_get_no_headers_no_body, 0); #endif /* TODO sending junk and large headers gets rejected */ /* check to make sure our predefined requests are okay */ for (i = 0; i < ARRAY_SIZE(requests); i++) { test_message(&requests[i]); } for (i = 0; i < ARRAY_SIZE(requests); i++) { test_message_pause(&requests[i]); } for (i = 0; i < ARRAY_SIZE(requests); i++) { if (!requests[i].should_keep_alive) continue; for (j = 0; j < ARRAY_SIZE(requests); j++) { if (!requests[j].should_keep_alive) continue; for (k = 0; k < ARRAY_SIZE(requests); k++) { test_multiple3(&requests[i], &requests[j], &requests[k]); } } } printf("request scan 1/4 "); test_scan( &requests[GET_NO_HEADERS_NO_BODY] , &requests[GET_ONE_HEADER_NO_BODY] , &requests[GET_NO_HEADERS_NO_BODY] ); printf("request scan 2/4 "); test_scan( &requests[POST_CHUNKED_ALL_YOUR_BASE] , &requests[POST_IDENTITY_BODY_WORLD] , &requests[GET_FUNKY_CONTENT_LENGTH] ); printf("request scan 3/4 "); test_scan( &requests[TWO_CHUNKS_MULT_ZERO_END] , &requests[CHUNKED_W_TRAILING_HEADERS] , &requests[CHUNKED_W_NONSENSE_AFTER_LENGTH] ); printf("request scan 4/4 "); test_scan( &requests[QUERY_URL_WITH_QUESTION_MARK_GET] , &requests[PREFIX_NEWLINE_GET ] , &requests[CONNECT_REQUEST] ); puts("requests okay"); return 0; } psi-plus-snapshots-1.4.1456/3rdparty/qhttp.cmake000066400000000000000000000050611370065651000214670ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.1.0) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Release") endif() project(qhttp) add_definitions( -DQHTTP_MEMORY_LOG=0 ) if(WIN32) add_definitions( -D_WINDOWS -DWIN32_LEAN_AND_MEAN -DNOMINMAX -DQHTTP_EXPORT ) endif() include_directories( . ./qhttp/src ./qhttp/src/private ) find_package(Qt5 REQUIRED Core Network) find_package(HttpParser 2.2 QUIET) if(NOT HttpParser_FOUND) include_directories(./http-parser) set(http_parser_srcs http-parser/http_parser.c ) set(http_parser_hdrs http-parser/http_parser.h ) else() FIND_PACKAGE_MESSAGE(HttpParser "Found HttpParser-${HttpParser_VERSION}: ${HttpParser_LIBRARY}" "[${HttpParser_LIBRARY}][${X11_INCLUDE_DIR}][${HttpParser_VERSION}]") include_directories(${HttpParser_INCLUDE_DIR}) list(APPEND EXTRA_LIBS ${HttpParser_LIBRARY}) endif() set(qhttp_srcs qhttp/src/qhttpabstracts.cpp qhttp/src/qhttpserverconnection.cpp qhttp/src/qhttpserver.cpp qhttp/src/qhttpserverrequest.cpp qhttp/src/qhttpserverresponse.cpp ) set(qhttp_hdrs qhttp/src/qhttpfwd.hpp qhttp/src/qhttpabstracts.hpp qhttp/src/qhttpserverconnection.hpp qhttp/src/qhttpserver.hpp qhttp/src/qhttpserverrequest.hpp qhttp/src/qhttpserverresponse.hpp ) set(qhttp_priv_hdrs qhttp/src/private/httpparser.hxx qhttp/src/private/qhttpclient_private.hpp qhttp/src/private/qhttpserver_private.hpp qhttp/src/private/httpreader.hxx qhttp/src/private/qhttpclientrequest_private.hpp qhttp/src/private/qhttpserverrequest_private.hpp qhttp/src/private/httpwriter.hxx qhttp/src/private/qhttpclientresponse_private.hpp qhttp/src/private/qhttpserverresponse_private.hpp qhttp/src/private/qhttpbase.hpp qhttp/src/private/qhttpserverconnection_private.hpp qhttp/src/private/qsocket.hpp ) qt5_wrap_cpp(MOCS qhttp/src/qhttpabstracts.hpp qhttp/src/qhttpserverconnection.hpp qhttp/src/qhttpserver.hpp qhttp/src/qhttpserverrequest.hpp qhttp/src/qhttpserverresponse.hpp ) add_library(qhttp STATIC ${http_parser_srcs} ${http_parser_hdrs} ${qhttp_srcs} ${MOCS} ) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) if(NOT MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") endif() target_link_libraries(qhttp Qt5::Core Qt5::Network ${EXTRA_LIBS}) target_include_directories(qhttp PUBLIC ./qhttp/src ./qhttp/src/private ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ) psi-plus-snapshots-1.4.1456/3rdparty/qhttp.pri000066400000000000000000000023361370065651000212030ustar00rootroot00000000000000PRJDIR = $$PWD/qhttp QHTTPSRC = $$PRJDIR/src INCLUDEPATH *= $$PRJDIR/src $$PWD win32 { DEFINES *= _WINDOWS WIN32_LEAN_AND_MEAN NOMINMAX } DEFINES *= QHTTP_MEMORY_LOG=0 win32:DEFINES *= QHTTP_EXPORT # To enable client # DEFINES *= QHTTP_HAS_CLIENT include($$top_builddir/conf.pri) # Joyent http_parser bundled_http_parser { SOURCES += $$PWD/http-parser/http_parser.c HEADERS += $$PWD/http-parser/http_parser.h INCLUDEPATH += $$PWD/http-parser } SOURCES += \ $$QHTTPSRC/qhttpabstracts.cpp \ $$QHTTPSRC/qhttpserverconnection.cpp \ $$QHTTPSRC/qhttpserverrequest.cpp \ $$QHTTPSRC/qhttpserverresponse.cpp \ $$QHTTPSRC/qhttpserver.cpp HEADERS += \ $$QHTTPSRC/qhttpfwd.hpp \ $$QHTTPSRC/qhttpabstracts.hpp \ $$QHTTPSRC/qhttpserverconnection.hpp \ $$QHTTPSRC/qhttpserverrequest.hpp \ $$QHTTPSRC/qhttpserverresponse.hpp \ $$QHTTPSRC/qhttpserver.hpp contains(DEFINES, QHTTP_HAS_CLIENT) { SOURCES += \ $$QHTTPSRC/qhttpclientrequest.cpp \ $$QHTTPSRC/qhttpclientresponse.cpp \ $$QHTTPSRC/qhttpclient.cpp HEADERS += \ $$QHTTPSRC/qhttpclient.hpp \ $$QHTTPSRC/qhttpclientresponse.hpp \ $$QHTTPSRC/qhttpclientrequest.hpp } psi-plus-snapshots-1.4.1456/3rdparty/qhttp/000077500000000000000000000000001370065651000204635ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/3rdparty/qhttp/LICENSE000066400000000000000000000020701370065651000214670ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2014 Amir Zamani Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. psi-plus-snapshots-1.4.1456/3rdparty/qhttp/README.md000066400000000000000000000211131370065651000217400ustar00rootroot00000000000000# QHttp This project is a backport of [azadkuh/qhttp](https://github.com/azadkuh/qhttp) for Qt5.3+ and C++11 (instead of Qt5.5+ and C++14). It fits the last version of azadkuh/qhttp and no issue has been reported at the moment. Read more about [motivations here](https://github.com/azadkuh/qhttp/issues/35). ### Table of contents - [About](#about) - [Sample codes](#sample-codes) - [Features](#features) - [Setup](#setup) - [Multi-threading](#multi-threading) - [Source tree](#source-tree) - [Disclaimer](#disclaimer) - [License](#license) ## About [TOC](#table-of-contents) `QHttp` is a lightweight, asynchronous and fast HTTP library in `C++11 / Qt5.3+`, containing both server and client side classes for managing connections, parsing and building HTTP requests and responses. - the objective of `QHttp` is being light weight with a simple API for Qt developers to implement RESTful web services in private (internal) zones. [more](#disclaimer) - by using `std::function` and `C++11 lambda`, the API is intentionally similar to the [Node.js' http module](http://nodejs.org/api/http.html). Asynchronous and non-blocking HTTP programming is quite easy with `QHttp`. have a look at [sample codes](#sample-codes). - the fantastic [nodejs/http-parser](https://github.com/nodejs/http-parser) (which is a single pair of `*.h/*.c` files) is the only dependency of the `QHttp`. This project was inspired by [nikhilm/qhttpserver](https://github.com/nikhilm/qhttpserver) effort to implement a Qt HTTP server. `QHttp` pushes the idea further by implementing client side classes, better memory management, a lot more Node.js-like API, ... ## Sample codes [TOC](#table-of-contents) a HelloWorld **HTTP server** by `QHttp` looks like: ```cpp int main(int argc, char** argv) { QCoreApplication app(argc, argv); using namespace qhttp::server; QHttpServer server(&app); server.listen( // listening on 0.0.0.0:8080 QHostAddress::Any, 8080, [](QHttpRequest* req, QHttpResponse* res) { // http status 200 res->setStatusCode(qhttp::ESTATUS_OK); // the response body data res->end("Hello World!\n"); // automatic memory management for req/res }); if ( !server.isListening() ) { qDebug("failed to listen"); return -1; } return app.exec(); } ``` to request weather information by **HTTP client**: ```cpp int main(int argc, char** argv) { QCoreApplication app(argc, argv); using namespace qhttp::client; QHttpClient client(&app); QUrl weatherUrl("http://wttr.in/tehran"); client.request(qhttp::EHTTP_GET, weatherUrl, [](QHttpResponse* res) { // response handler, called when the incoming HTTP headers are ready // gather HTTP response data (HTTP body) res->collectData(); // when all data in HTTP response have been read: res->onEnd([&]() { writeTo("weather.html", res->collectedData()); // done! now quit the application qApp->quit(); }); // just for fun! print incoming headers: qDebug("\n[Headers:]"); res->headers().forEach([](auto cit) { qDebug("%s : %s", cit.key().constData(), cit.value().constData()); }); }); // set a timeout for the http connection client.setConnectingTimeOut(10000, []{ qDebug("connecting to HTTP server timed out!"); qApp->quit(); }); return app.exec(); } ``` ## Features [TOC](#table-of-contents) - the only dependencies are: `Qt5.3 minimum`, `C++11` and the `http-parser` - both TCP and UNIX (local) sockets are supported as backend. - separate `namespace`s for server and client classes. - HTTP server classes: [QHttpServer](./src/qhttpserver.hpp), [QHttpConnection](./src/qhttpserverconnection.hpp), [QHttpRequest](./src/qhttpserverrequest.hpp) and [QHttpResponse](./src/qhttpserverresponse.hpp). - **optional** HTTP client classes: [QHttpClient](./src/qhttpclient.hpp), [QHttpRequest](./src/qhttpclientrequest.hpp) and [QHttpResponse](./src/qhttpclientresponse.hpp). the client classes can be disabled at build time by commenting `QHTTP_HAS_CLIENT` in [common.dir](./commondir.pri) - **automatic memory management** of objects. Instances of connections, requests and replies will be deleted automatically when socket drops or *disconnected*. - **PIMPL** (Private implementaion) to achieve better ABI compatibility and cleaner API and faster compile time. - **Asynchronous** and **non-blocking**. You can handle thousands of concurrent HTTP connections efficiently by a single thread, although a multi-threaded HTTP server is easy to implement. - **high throughput**, I have tried the `QHttp` and [gason++](https://github.com/azadkuh/gason--) to implement a REST/Json web service on an Ubuntu VPS (dual core + 512MB ram) with more than **5800** connections per second (stress test). On a MacBook Pro (i5 4258U, 8GB ram), `QHttp` easily reaches to more than **11700** connections / second. Generally `QHttp` is **1.5x ~ 3x** faster than `Node.js` depending on your machine / OS. - Easily portable where ever `Qt5 / c++14` works. Tested under: - **Linux** Ubuntu 12.04 ~ 16.04 LTS, g++ 5.3+ - **OS X** 10.9+, clang 3.7+ - **Windows** 7/8.1, msvs2015 / mingw (g++ 6.1) ## Setup [TOC](#table-of-contents) instructions: ```bash # first clone this repository: $> git clone https://github.com/azadkuh/qhttp.git $> cd qhttp # prepare dependencies: $qhttp/> qompoter install *Note: to install Qompoter on your machine, use `npm install -g qompoter` or check [Qompoter documentation](https://github.com/Fylhan/qompoter/blob/master/README.md#installation).* # now build the library and the examples $qhttp/> qmake -r qhttp.pro $qhttp/> make -j 8 ``` ## Multi-threading [TOC](#table-of-contents) As `QHttp` is **asynchronous** and **non-blocking**, your app can handle thousands of concurrent HTTP connections by a single thread. in some rare scenarios you may want to use multiple handler threads (although it's not always the best solution): * there are some blocking APIs (QSql, system calls, ...) in your connection handler (adopting asynchronous layer over the blocking API is a better approach). * the hardware has lots of free cores and the measurement shows that the load on the main `QHttp` thread is close to highest limit. There you can spawn some other handler threads. ## Source tree [TOC](#table-of-contents) - **`src/`**: holds the source code of `QHttp`. server classes are prefixed by `qhttpserver*` and client classes by `qhttpclient*`. - **`private/`**: Private classes of the library. - **`vendor/`**: will contain `http-parser` source tree as the only dependency. this directory is created by setup. see also: [setup](#setup). - **`example/`**: contains some sample applications representing the `QHttp` usage: - **`helloworld/`**: the HelloWorld example of `QHttp`, both server + client are represented. see: [README@helloworld](./example/helloworld/README.md) - **`basic-server/`**: a basic HTTP server shows how to collect the request body, and respond to the clients. see: [README@basic-server](./example/basic-server/README.md) - **`keep-alive`**: shows how to keep an http connection open and transmitting many requests/responses. see: [README@keep-alive](./example/keep-alive/README.md) - **`post-collector`**: another server example shows how to collect large data by POST requests. see: [README@post-collector](./example/postcollector/README.md) - **`tmp/`**: a temporary directory which is created while `make`ing the library and holds all the `.o`, `moc files`, etc. * **`xbin/`**: all the executable and libraries will be placed on this folder by build system. ## Disclaimer [TOC](#table-of-contents) - Implementing a lightweight and simple HTTP server/client in Qt with Node.js like API, is the main purpose of `QHttp`. - There are lots of features in a full blown HTTP server which are out of scope of this small library, although those can be added on top of `QHttp`. - The client classes are by no mean designed as a `QNetworkAccessManager` replacement. `QHttpClient` is simpler and lighter, for serious scenarios just use `QNetworkAccessManager` which supports proxy, redirections, authentication, cookie jar, ssl, ... - I'm a busy person. > If you have any ideas, critiques, suggestions or whatever you want to call > it, please open an issue. I'll be happy to hear different ideas, will think > about them, and I try to add those that make sense. ## License [TOC](#table-of-contents) Distributed under the MIT license. Copyright (c) 2014, Amir Zamani. Distributed under the MIT license. Copyright (c) 2017, Olivier Maridat. psi-plus-snapshots-1.4.1456/3rdparty/qhttp/commondir.pri000066400000000000000000000012661370065651000231730ustar00rootroot00000000000000# specifying common dirs # comment following line to build the lib as static library DEFINES *= QHTTP_DYNAMIC_LIB # comment following line to trim client classes from build DEFINES *= QHTTP_HAS_CLIENT CONFIG += c++11 unix { TEMPDIR = $$PRJDIR/tmp/unix/$$TARGET macx:TEMPDIR = $$PRJDIR/tmp/osx/$$TARGET } win32 { TEMPDIR = $$PRJDIR/tmp/win32/$$TARGET DEFINES += _WINDOWS WIN32_LEAN_AND_MEAN NOMINMAX } DESTDIR = $$OUT_PWD/$$PRJDIR/xbin MOC_DIR = $$TEMPDIR OBJECTS_DIR = $$TEMPDIR RCC_DIR = $$TEMPDIR UI_DIR = $$TEMPDIR/Ui LIBS += -L$$OUT_PWD/$$PRJDIR/xbin INCLUDEPATH += . $$PRJDIR/src CONFIG += qhttp include($$PWD/vendor/vendor.pri) psi-plus-snapshots-1.4.1456/3rdparty/qhttp/example/000077500000000000000000000000001370065651000221165ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/3rdparty/qhttp/example/basic-server/000077500000000000000000000000001370065651000245035ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/3rdparty/qhttp/example/basic-server/README.md000066400000000000000000000017551370065651000257720ustar00rootroot00000000000000# basic-server this example shows the basic usage of `QHttp` server classes. `basic-server` collects the incoming request data (body of the a POST request) and prints it, then responds to the client. ##usage ```bash $> ./basic-server ``` now the server is listening on `address=QHostAddress::Any`, `port=8080` (press `ctrl+c` to stop the server).
then test the server by pointing your browser to [localhost:8080](localhost:8080) or try any other HTTP client app: ```bash # GET $> curl localhost:8080 # POST $> curl --data 'Hello QHttp server!' localhost:8080/dummyPath/id ``` you shall see the server logs as: ```text connection (#2): request from 127.0.0.1:62808 url: /dummyPath/id body: "Hello QHttp server!" ``` and the client shall prints: ```text Hello World packet count = 2 time = 2014-07-20 14:06:23 ``` to close the server by a client, send a "command: quit" header as: ```bash $> curl --data 'I want to finish you!' localhost:8080/dummyPath/id -H "command: quit" ``` psi-plus-snapshots-1.4.1456/3rdparty/qhttp/example/basic-server/basic-server.pro000066400000000000000000000005331370065651000276130ustar00rootroot00000000000000QT += core network QT -= gui CONFIG += console osx:CONFIG -= app_bundle TARGET = basic-server TEMPLATE = app PRJDIR = ../.. include($$PRJDIR/commondir.pri) HEADERS += SOURCES += main.cpp include($$PWD/../../vendor/qompote.pri) LIBS += -L$${OUT_PWD}/../../src -l$$getLibName(qhttp, "Qt") psi-plus-snapshots-1.4.1456/3rdparty/qhttp/example/basic-server/main.cpp000066400000000000000000000072541370065651000261430ustar00rootroot00000000000000#include "qhttpserver.hpp" #include "qhttpserverconnection.hpp" #include "qhttpserverrequest.hpp" #include "qhttpserverresponse.hpp" #include #include #include #include "../include/unixcatcher.hpp" namespace { /////////////////////////////////////////////////////////////////////////////// using namespace qhttp::server; /** connection class for gathering incoming chunks of data from HTTP client. * @warning please note that the incoming request instance is the parent of * this QObject instance. Thus this class will be deleted automatically. * */ class ClientHandler : public QObject { public: explicit ClientHandler(quint64 id, QHttpRequest* req, QHttpResponse* res) : QObject(req /* as parent*/), iconnectionId(id) { qDebug("connection #%llu ..." , id); // automatically collect http body(data) upto 1KB req->collectData(1024); // when all the incoming data are gathered, send some response to client. req->onEnd([this, req, res]() { qDebug(" connection (#%llu): request from %s:%d\n method: %s url: %s", iconnectionId, req->remoteAddress().toUtf8().constData(), req->remotePort(), qhttp::Stringify::toString(req->method()), qPrintable(req->url().toString()) ); if ( req->collectedData().size() > 0 ) qDebug(" body (#%llu): %s", iconnectionId, req->collectedData().constData() ); QString message = QString("Hello World\n packet count = %1\n time = %2\n") .arg(iconnectionId) .arg(QLocale::c().toString(QDateTime::currentDateTime(), "yyyy-MM-dd hh:mm:ss") ); res->setStatusCode(qhttp::ESTATUS_OK); res->addHeaderValue("content-length", message.size()); res->end(message.toUtf8()); if ( req->headers().keyHasValue("command", "quit") ) { qDebug("\n\na quit has been requested!\n"); QCoreApplication::instance()->quit(); } }); } virtual ~ClientHandler() { qDebug(" ~ClientHandler(#%llu): I've being called automatically!", iconnectionId ); } protected: quint64 iconnectionId; }; /////////////////////////////////////////////////////////////////////////////// } // namespace anon /////////////////////////////////////////////////////////////////////////////// int main(int argc, char ** argv) { QCoreApplication app(argc, argv); #if defined(Q_OS_UNIX) catchUnixSignals({SIGQUIT, SIGINT, SIGTERM, SIGHUP}); #endif // dumb (trivial) connection counter quint64 iconnectionCounter = 0; QString portOrUnixSocket("10022"); // default: TCP port 10022 if ( argc > 1 ) portOrUnixSocket = argv[1]; QHttpServer server(&app); server.listen(portOrUnixSocket, [&](QHttpRequest* req, QHttpResponse* res){ new ClientHandler(++iconnectionCounter, req, res); // this ClientHandler object will be deleted automatically when: // socket disconnects (automatically after data has been sent to the client) // -> QHttpConnection destroys // -> QHttpRequest destroys -> ClientHandler destroys // -> QHttpResponse destroys // all by parent-child model of QObject. }); if ( !server.isListening() ) { fprintf(stderr, "can not listen on %s!\n", qPrintable(portOrUnixSocket)); return -1; } app.exec(); } psi-plus-snapshots-1.4.1456/3rdparty/qhttp/example/example.pro000066400000000000000000000002371370065651000242750ustar00rootroot00000000000000TEMPLATE = subdirs SUBDIRS += helloworld SUBDIRS += postcollector SUBDIRS += basic-server contains(DEFINES, QHTTP_HAS_CLIENT) { SUBDIRS += keep-alive } psi-plus-snapshots-1.4.1456/3rdparty/qhttp/example/helloworld/000077500000000000000000000000001370065651000242715ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/3rdparty/qhttp/example/helloworld/README.md000066400000000000000000000025721370065651000255560ustar00rootroot00000000000000# helloworld this example shows the usage of `QHttp` as an HTTP server or a client. ##usage ```bash $> ./helloworld -h #Usage: $> ./helloworld {mode} [options] ``` three different modes are available: ### server to start a new http server: ```bash $> ./helloworld server --listen 8080 ``` to stop the server just press the `ctrl+c` or send a `command: quit` as an http header to this server (by an http client). sample clients: ```bash # GET by curl $> curl 127.0.0.1:8080/login/?username=admin # send an http header to stop the server $> curl -H "command: quit" 127.0.0.1:8080 # POST by curl, custom headers, custom body (data) $> curl -X POST -H "my_key: my_value" -H "connection: close" \ -d "this is http body of POST request" \ 127.0.0.1:8080/path/?cmd=have_fun ``` ### client if the `qhttp` has been configured by `QHTTP_HAS_CLIENT` (in `commondir.pri`): to fetch a custom http url: ```bash $> ./helloworld client --url https://www.google.com/?gws_rd=ssl#q=qt+c%2B%2B11 $> ./helloworld client --url 127.0.0.1:8080 ``` the client mode dumps both headers and body data. ### weather to fetch the weather information of your favorite city (by awesome [wttr.in](http://wttr.in) service): ```bash $> ./helloworld weather --geolocation Tehran $> ./helloworld weather --geolocation Paris,Fr ``` the result will be save into `weather.html` file, you can open it by a browser. psi-plus-snapshots-1.4.1456/3rdparty/qhttp/example/helloworld/helloworld.pro000066400000000000000000000005241370065651000271670ustar00rootroot00000000000000QT += core network QT -= gui CONFIG += console osx:CONFIG -= app_bundle TARGET = helloworld TEMPLATE = app PRJDIR = ../.. include($$PRJDIR/commondir.pri) HEADERS += SOURCES += main.cpp include($$PWD/../../vendor/qompote.pri) LIBS += -L$${OUT_PWD}/../../src -l$$getLibName(qhttp, "Qt") psi-plus-snapshots-1.4.1456/3rdparty/qhttp/example/helloworld/main.cpp000066400000000000000000000155761370065651000257370ustar00rootroot00000000000000#include "qhttpserver.hpp" #include "qhttpserverresponse.hpp" #include "qhttpserverrequest.hpp" #include "qhttpclient.hpp" #include "qhttpclientrequest.hpp" #include "qhttpclientresponse.hpp" #include "../include/unixcatcher.hpp" #include #include #include #include #include #include #include /////////////////////////////////////////////////////////////////////////////// namespace { /////////////////////////////////////////////////////////////////////////////// void runServer(const QString& portOrPath) { using namespace qhttp::server; QHttpServer server(qApp); // listening tcp port or Unix path server.listen(portOrPath, [](QHttpRequest* req, QHttpResponse* res) { req->collectData(); req->onEnd([req, res](){ res->setStatusCode(qhttp::ESTATUS_OK); // status 200 res->addHeader("connection", "close"); // optional(default) header int size = req->collectedData().size(); auto message = [size]() -> QByteArray { if ( size == 0 ) return QByteArray("Hello World!\n"); char buffer[65] = {0}; qsnprintf(buffer, 64, "Hello!\nyou've sent me %d bytes!\n", size); return QByteArray(buffer); }; res->end(message()); // reponse body data }); const auto& h = req->headers(); // optionally let the clients to shut down the server if ( h.keyHasValue("command", "quit") ) { printf("a client sends a quit command.\nserver quits.\n"); QCoreApplication::quit(); return; } // just for fun! print meta information: qDebug("\n--> %s : %s", qhttp::Stringify::toString(req->method()), qPrintable(req->url().toString().toUtf8()) ); qDebug("[Headers (%d)]", h.size()); // h.forEach([](auto iter) { // qDebug(" %s : %s", // iter.key().constData(), // iter.value().constData() // ); // }); }); if ( !server.isListening() ) { fprintf(stderr, "failed. can not listen at port %s!\n", qPrintable(portOrPath)); return; } qApp->exec(); // application's main event loop } #if defined(QHTTP_HAS_CLIENT) void runClient(QString url) { using namespace qhttp::client; // ensure there the url has http:// if ( !url.startsWith("http://") && !url.startsWith("https://") ) url.prepend("http://"); QHttpClient client(qApp); bool success = client.request(qhttp::EHTTP_GET, url, [](QHttpResponse* res) { // response handler, called when the HTTP headers of the response are ready res->collectData(); // called when all data in HTTP response have been read. res->onEnd([res]() { // print the XML body of the response qDebug("\nreceived %d bytes of http body:\n%s\n", res->collectedData().size(), res->collectedData().constData() ); qApp->quit(); }); // just for fun! print headers: qDebug("\n[Headers:]"); // res->headers().forEach([](auto cit) { // qDebug("%s : %s", cit.key().constData(), cit.value().constData()); // }); }); if ( !success ) { qDebug("failed: can not send a request to %s\n", qPrintable(url)); return; } qApp->exec(); } void runWeatherClient(const QString& cityName) { using namespace qhttp::client; QHttpClient client(qApp); QByteArray httpBody; QUrl weatherUrl(QString("http://wttr.in/%1").arg(cityName)); client.request(qhttp::EHTTP_GET, weatherUrl, [&httpBody](QHttpResponse* res) { // response handler, called when the HTTP headers of the response are ready // gather HTTP response data manually res->onData([&httpBody](const QByteArray& chunk) { httpBody.append(chunk); }); // called when all data in HTTP response have been read. res->onEnd([&]() { // print the XML body of the response qDebug(" %d bytes of body's been written in weather.html\n", httpBody.size()); QFile f("weather.html"); if ( f.open(QFile::WriteOnly) ) f.write(httpBody); qApp->quit(); }); // just for fun! print headers: qDebug("\n[Headers:]"); // res->headers().forEach([](auto cit) { // qDebug("%s : %s", cit.key().constData(), cit.value().constData()); // }); }); // set a timeout for making the request client.setConnectingTimeOut(10000, []{ qDebug("connecting to HTTP server timed out!"); qApp->quit(); }); qApp->exec(); } #endif // QHTTP_HAS_CLIENT /////////////////////////////////////////////////////////////////////////////// } // namespace anon /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// int main(int argc, char ** argv) { QCoreApplication app(argc, argv); #if defined(Q_OS_UNIX) catchUnixSignals({SIGQUIT, SIGINT, SIGTERM, SIGHUP}); #endif app.setApplicationName("helloworld"); app.setApplicationVersion("1.0.0"); QCommandLineParser parser; parser.setApplicationDescription("a HelloWorld example for http client and server."); parser.addHelpOption(); parser.addVersionOption(); parser.addPositionalArgument("mode", "working mode: server, client or weather. default: server"); // parser.addOption({ // {"l", "listen"}, // "listening tcp port number in server mode (default 8080)", // "portNo", "8080"}); // parser.addOption({ // {"u", "url"}, // "fetch url data in client mode", // "address", "http://www.google.com"}); // parser.addOption({ // {"g", "geolocation"}, // "a city name [,country name] in weather mode, default: Tehran", // "city", "Tehran"}); parser.process(app); QStringList posArgs = parser.positionalArguments(); if ( posArgs.size() != 1 ) { parser.showHelp(0); } else { const auto& mode = posArgs.at(0); if ( mode == QLatin1Literal("server") ) runServer(parser.value("listen")); #if defined(QHTTP_HAS_CLIENT) else if ( mode == QLatin1Literal("client") ) runClient(parser.value("url")); else if ( mode == QLatin1Literal("weather") ) runWeatherClient(parser.value("geolocation")); #else else if ( mode == QLatin1Literal("client") || mode == QLatin1Literal("weather") ) qDebug("qhttp::client has not been enabled at build time"); #endif // QHTTP_HAS_CLIENT } return 0; } psi-plus-snapshots-1.4.1456/3rdparty/qhttp/example/include/000077500000000000000000000000001370065651000235415ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/3rdparty/qhttp/example/include/ticktock.hxx000066400000000000000000000023241370065651000261060ustar00rootroot00000000000000#ifndef TICKTOCK_HXX #define TICKTOCK_HXX #include /////////////////////////////////////////////////////////////////////////////// namespace am { class TickTock { public: using time_point = std::chrono::high_resolution_clock::time_point; using milliseconds = std::chrono::milliseconds; using microseconds = std::chrono::microseconds; public: explicit TickTock(bool initialize) { if (initialize) tick(); } TickTock()=default; ~TickTock()=default; static auto now() { return std::chrono::high_resolution_clock::now(); } void tick() { itick = now(); } template auto tock() -> typename T::rep { return std::chrono::duration_cast(now() - itick).count(); } template auto tockf() -> double { return static_cast( std::chrono::duration_cast(now() - itick).count() ); } protected: time_point itick; }; /////////////////////////////////////////////////////////////////////////////// } // namespace am /////////////////////////////////////////////////////////////////////////////// #endif psi-plus-snapshots-1.4.1456/3rdparty/qhttp/example/include/unixcatcher.hpp000066400000000000000000000014241370065651000265700ustar00rootroot00000000000000#ifndef UNIX_CATCHER_HPP #define UNIX_CATCHER_HPP #if defined(Q_OS_UNIX) #include #include #include /////////////////////////////////////////////////////////////////////////////// void catchUnixSignals(const std::vector& quitSignals, const std::vector& ignoreSignals = std::vector()) { auto handler = [](int sig) ->void { printf("\nquit the application (user request signal = %d).\n", sig); QCoreApplication::quit(); }; for ( int sig : ignoreSignals ) signal(sig, SIG_IGN); for ( int sig : quitSignals ) signal(sig, handler); } /////////////////////////////////////////////////////////////////////////////// #endif // Q_OS_UNIX #endif // UNIX_CATCHER_HPP psi-plus-snapshots-1.4.1456/3rdparty/qhttp/example/keep-alive/000077500000000000000000000000001370065651000241405ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/3rdparty/qhttp/example/keep-alive/README.md000066400000000000000000000006141370065651000254200ustar00rootroot00000000000000# keep-alive this example shows the usage of `QHttp` and `keep-alive` connections. ##usage ```bash $xbin/> ./keepalive --help ``` server mode: ```bash $xbin/> ./keepalive server --port 8090 ``` client mode: ```bash $xbin/> ./keepalive client -p 8090 --count 1000 ``` transimts 1000 packets in a single HTTP connection, which is definitly a lot more efficient than making 1000 connection. psi-plus-snapshots-1.4.1456/3rdparty/qhttp/example/keep-alive/keep-alive.pro000066400000000000000000000004751370065651000267120ustar00rootroot00000000000000QT += core network QT -= gui osx:CONFIG -= app_bundle TARGET = keep-alive TEMPLATE = app PRJDIR = ../.. include($$PRJDIR/commondir.pri) HEADERS += SOURCES += main.cpp include($$PWD/../../vendor/qompote.pri) LIBS += -L$${OUT_PWD}/../../src -l$$getLibName(qhttp, "Qt") psi-plus-snapshots-1.4.1456/3rdparty/qhttp/example/keep-alive/main.cpp000066400000000000000000000152311370065651000255720ustar00rootroot00000000000000#include "qhttpclient.hpp" #include "qhttpclientrequest.hpp" #include "qhttpclientresponse.hpp" #include "qhttpserver.hpp" #include "qhttpserverconnection.hpp" #include "qhttpserverrequest.hpp" #include "qhttpserverresponse.hpp" #include "../include/ticktock.hxx" #include "../include/unixcatcher.hpp" #include #include #include #include #include #include #include /////////////////////////////////////////////////////////////////////////////// namespace { /////////////////////////////////////////////////////////////////////////////// namespace client { using namespace qhttp::client; /////////////////////////////////////////////////////////////////////////////// struct Client { int start(quint16 port, int count) { QObject::connect(&iclient, &QHttpClient::disconnected, [this]() { finalize(); }); QUrl url; url.setScheme("http"); url.setHost("localhost"); url.setPort(port); iurl = url; icount = count; itick.tick(); send(); return qApp->exec(); } void send() { iclient.request( qhttp::EHTTP_POST, iurl, [this](QHttpRequest* req){ QJsonObject root{ {"name", "add"}, {"stan", ++istan}, {"args", QJsonArray{10, 14, -12}} // server computes sum of these values }; auto body = QJsonDocument(root).toJson(); req->addHeader("connection", "keep-alive"); req->addHeaderValue("content-length", body.length()); req->end(body); }, [this](QHttpResponse* res) { res->collectData(512); res->onEnd([this, res](){ onIncomingData(res->collectedData()); }); }); } void onIncomingData(const QByteArray& data) { auto root = QJsonDocument::fromJson(data).object(); if ( root.isEmpty() ) { qDebug("the result is an invalid json\n"); finalize(); return; } if ( root.value("stan").toInt() != istan ) { qDebug("invalid stan number, %d != %d\n", istan, root.value("stan").toInt()); } if ( istan >= icount ) finalize(); else send(); } void finalize() { qDebug("totally %d request/response pairs have been transmitted in %lld [mSec].\n", istan, itick.tock() ); QCoreApplication::quit(); } public: int icount = 0; int istan = 0; // system trace audit number QUrl iurl; QHttpClient iclient; am::TickTock itick; }; // struct client /////////////////////////////////////////////////////////////////////////////// } // namespace client /////////////////////////////////////////////////////////////////////////////// namespace server { using namespace qhttp::server; /////////////////////////////////////////////////////////////////////////////// struct Server : public QHttpServer { using QHttpServer::QHttpServer; int start(quint16 port) { connect(this, &QHttpServer::newConnection, [this](QHttpConnection*){ printf("a new connection has been come!\n"); }); bool isListening = listen( QString::number(port), [this](QHttpRequest* req, QHttpResponse* res){ req->collectData(512); req->onEnd([this, req, res](){ process(req, res); }); }); if ( !isListening ) { qDebug("can not listen on %d!\n", port); return -1; } return qApp->exec(); } void process(QHttpRequest* req, QHttpResponse* res) { auto root = QJsonDocument::fromJson(req->collectedData()).object(); if ( root.isEmpty() || root.value("name").toString() != QLatin1Literal("add") ) { const static char KMessage[] = "Invalid json format!"; res->setStatusCode(qhttp::ESTATUS_BAD_REQUEST); res->addHeader("connection", "close"); res->addHeaderValue("content-length", strlen(KMessage)); res->end(KMessage); return; } int total = 0; auto args = root.value("args").toArray(); for ( const auto jv : args ) { total += jv.toInt(); } root["args"] = total; QByteArray body = QJsonDocument(root).toJson(); res->addHeader("connection", "keep-alive"); res->addHeaderValue("content-length", body.length()); res->setStatusCode(qhttp::ESTATUS_OK); res->end(body); } }; // struct server /////////////////////////////////////////////////////////////////////////////// } // namespace server /////////////////////////////////////////////////////////////////////////////// } // namespace anon /////////////////////////////////////////////////////////////////////////////// int main(int argc, char ** argv) { QCoreApplication app(argc, argv); #if defined(Q_OS_UNIX) catchUnixSignals({SIGQUIT, SIGINT, SIGTERM, SIGHUP}); #endif app.setApplicationName("keep-alive"); app.setApplicationVersion("1.0.0"); QCommandLineParser parser; parser.setApplicationDescription("a server/client application to test the" " performance of keep-alive connections."); parser.addHelpOption(); parser.addVersionOption(); parser.addPositionalArgument( "mode", "mode: server or client. default: local" ); parser.addOption({ {"p", "port"}, "tcp port number or UNIX socket path. default: 10022", "number/path", "10022" }); parser.addOption({ {"c", "count"}, "count of request/response pairs (only in client mode). default: 100", "number", "100" }); parser.process(app); QStringList posList = parser.positionalArguments(); if ( posList.size() == 1 ) { const auto& mode = posList.at(0); if ( mode == QLatin1Literal("server") ) { server::Server server; return server.start(parser.value("port").toUShort()); } else if ( mode == QLatin1Literal("client") ) { client::Client client; return client.start( parser.value("port").toUShort(), parser.value("count").toInt() ); } } parser.showHelp(0); return 0; } psi-plus-snapshots-1.4.1456/3rdparty/qhttp/example/postcollector/000077500000000000000000000000001370065651000250125ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/3rdparty/qhttp/example/postcollector/README.md000066400000000000000000000027601370065651000262760ustar00rootroot00000000000000# POST (data) collector this example shows how to collect data from clients in POST/PUT/... commands ## usage to start the http server: ```bash $> ./postcollector 8090 #listening port ``` to stop the server just press the `ctrl+c`. the server will dump each POST data into a file (`dump.bin`): ```cpp server.listen(port, [](QHttpRequest* req, QHttpResponse* res) { req->collectData(8*1024*1024); // maximum 8MB of data for each post request // the better approach is to use req->onData(...) req->onEnd([req, res](){ res->setStatusCode(qhttp::ESTATUS_OK); res->addHeader("connection", "close"); // optional header (added by default) int size = req->collectedData().size(); auto message = [size]() -> QByteArray { if ( size == 0 ) return "Hello World!\n"; char buffer[65] = {0}; qsnprintf(buffer, 64, "Hello!\nyou've sent me %d bytes!\n", size); return buffer; }; res->end(message()); // response body data if ( size > 0 ) { // dump the incoming data into a file QFile f("dump.bin"); if ( f.open(QFile::WriteOnly) ) f.write(req->collectedData()); } }); }); ``` to POST some data in a handy way: ```bash $> curl -X POST --data-binary "@sample.jpeg" ip:port ``` where `sample.jpeg` should be in current working directory. to check the integrity of transmitted data: `SHA1(dump.bin) == SHA1(sample.jpeg)` shall return `true`. psi-plus-snapshots-1.4.1456/3rdparty/qhttp/example/postcollector/main.cpp000066400000000000000000000034341370065651000264460ustar00rootroot00000000000000#include "qhttpserver.hpp" #include "qhttpserverresponse.hpp" #include "qhttpserverrequest.hpp" #include #include #include #include "../include/unixcatcher.hpp" /////////////////////////////////////////////////////////////////////////////// int main(int argc, char ** argv) { QString port = "8080"; if ( argc == 2 ) { port = argv[1]; // override default port } QCoreApplication app(argc, argv); #if defined(Q_OS_UNIX) catchUnixSignals({SIGQUIT, SIGINT, SIGTERM, SIGHUP}); #endif using namespace qhttp::server; QHttpServer server(&app); server.listen(port, [](QHttpRequest* req, QHttpResponse* res) { req->collectData(8*1024*1024); // maximum 8MB of data for each post request // the better approach is to use req->onData(...) req->onEnd([req, res](){ res->setStatusCode(qhttp::ESTATUS_OK); res->addHeader("connection", "close"); // optional header (added by default) int size = req->collectedData().size(); auto message = [size]() -> QByteArray { if ( size == 0 ) return QByteArray("Hello World!\n"); char buffer[65] = {0}; qsnprintf(buffer, 64, "Hello!\nyou've sent me %d bytes!\n", size); return QByteArray(buffer); }; res->end(message()); // response body data if ( size > 0 ) { // dump the incoming data into a file QFile f("dump.bin"); if ( f.open(QFile::WriteOnly) ) f.write(req->collectedData()); } }); }); if ( !server.isListening() ) { qDebug("failed: listening at %s!\n", qPrintable(port)); return -1; } return app.exec(); } psi-plus-snapshots-1.4.1456/3rdparty/qhttp/example/postcollector/postcollector.pro000066400000000000000000000005261370065651000304330ustar00rootroot00000000000000QT += core network QT -= gui CONFIG += console osx:CONFIG -= app_bundle TARGET = postcollector TEMPLATE = app PRJDIR = ../.. include($$PRJDIR/commondir.pri) HEADERS += SOURCES += main.cpp include($$PWD/../../vendor/qompote.pri) LIBS += -L$${OUT_PWD}/../../src -l$$getLibName(qhttp, "Qt") psi-plus-snapshots-1.4.1456/3rdparty/qhttp/qhttp.pro000066400000000000000000000005111370065651000223420ustar00rootroot00000000000000TEMPLATE = subdirs APPNAME=qhttp SUBDIRS += src SUBDIRS += example example.depends = src OTHER_FILES += \ build.properties \ build.xml \ qompoter.json \ qompoter.pri \ README.md \ changelogs.md \ include($$PWD/vendor/qompote.pri) $$setBuildDir() message("$$APPNAME [ build folder is $$OBJECTS_DIR ]") psi-plus-snapshots-1.4.1456/3rdparty/qhttp/qompoter.json000066400000000000000000000010301370065651000232160ustar00rootroot00000000000000{ "name": "oliviermaridat/qhttp", "description": "a light-weight and asynchronous HTTP library (both server & client) in Qt5.3+ and C++11", "keywords": ["Qt", "C++", "HTTP"], "authors": [ { "name": "azadkuh", "homepage": "https://github.com/azadkuh" }, { "name": "Olivier Maridat", "email": "olivier.maridat@trialog.com", "company": "Trialog" } ], "require": { "oliviermaridat/http-parser-wrapper": "v2.7.*" } } psi-plus-snapshots-1.4.1456/3rdparty/qhttp/qompoter.pri000066400000000000000000000036541370065651000230550ustar00rootroot00000000000000qhttp|qhttp-server{ HEADERS += \ $$PWD/qhttp/src/qhttpserverconnection.hpp \ $$PWD/qhttp/src/qhttpserverrequest.hpp \ $$PWD/qhttp/src/qhttpserverresponse.hpp \ $$PWD/qhttp/src/qhttpserver.hpp \ $$PWD/qhttp/src/private/qhttpserver_private.hpp \ $$PWD/qhttp/src/private/qhttpserverconnection_private.hpp \ $$PWD/qhttp/src/private/qhttpserverrequest_private.hpp \ $$PWD/qhttp/src/private/qhttpserverresponse_private.hpp SOURCES += \ $$PWD/qhttp/src/qhttpserverconnection.cpp \ $$PWD/qhttp/src/qhttpserverrequest.cpp \ $$PWD/qhttp/src/qhttpserverresponse.cpp \ $$PWD/qhttp/src/qhttpserver.cpp !qhttp{ CONFIG += qhttp } } qhttp|qhttp-client|contains(DEFINES, QHTTP_HAS_CLIENT){ HEADERS += \ $$PWD/qhttp/src/qhttpclient.hpp \ $$PWD/qhttp/src/qhttpclientresponse.hpp \ $$PWD/qhttp/src/qhttpclientrequest.hpp \ $$PWD/qhttp/src/private/qhttpclient_private.hpp \ $$PWD/qhttp/src/private/qhttpclientrequest_private.hpp \ $$PWD/qhttp/src/private/qhttpclientresponse_private.hpp SOURCES += \ $$PWD/qhttp/src/qhttpclientrequest.cpp \ $$PWD/qhttp/src/qhttpclientresponse.cpp \ $$PWD/qhttp/src/qhttpclient.cpp !qhttp{ CONFIG += qhttp } } qhttp{ DEFINES += QOMP_OLIVIERMARIDAT_QHTTP HEADERS += \ $$PWD/qhttp/src/qhttpfwd.hpp \ $$PWD/qhttp/src/qhttpabstracts.hpp \ $$PWD/qhttp/src/private/httpparser.hxx \ $$PWD/qhttp/src/private/httpreader.hxx \ $$PWD/qhttp/src/private/httpwriter.hxx \ $$PWD/qhttp/src/private/qhttpbase.hpp \ $$PWD/qhttp/src/private/qsocket.hpp SOURCES += \ $$PWD/qhttp/src/qhttpabstracts.cpp INCLUDEPATH += \ $$PWD/qhttp \ $$PWD/qhttp/src QT += network DEFINES *= QHTTP_MEMORY_LOG=0 CONFIG += c++11 CONFIG += http-parser } psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/000077500000000000000000000000001370065651000212525ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/private/000077500000000000000000000000001370065651000227245ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/private/httpparser.hxx000066400000000000000000000071021370065651000256510ustar00rootroot00000000000000/** @file httpparser.hxx * * @copyright (C) 2016 * @date 2016.05.26 * @version 1.0.0 * @author amir zamani * */ #ifndef __QHTTP_HTTPPARSER_HXX__ #define __QHTTP_HTTPPARSER_HXX__ #include "qhttpbase.hpp" /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace details { /////////////////////////////////////////////////////////////////////////////// /// base HttpParser based on joyent http_parser template class HttpParser { public: explicit HttpParser(http_parser_type type) { // create http_parser object iparser.data = static_cast(this); http_parser_init(&iparser, type); memset(&iparserSettings, 0, sizeof(http_parser_settings)); iparserSettings.on_message_begin = onMessageBegin; iparserSettings.on_url = onUrl; iparserSettings.on_status = onStatus; iparserSettings.on_header_field = onHeaderField; iparserSettings.on_header_value = onHeaderValue; iparserSettings.on_headers_complete = onHeadersComplete; iparserSettings.on_body = onBody; iparserSettings.on_message_complete = onMessageComplete; } size_t parse(const char* data, size_t length) { return http_parser_execute(&iparser, &iparserSettings, data, length); } public: // callback functions for http_parser_settings static int onMessageBegin(http_parser* p) { return me(p)->messageBegin(p); } static int onUrl(http_parser* p, const char* at, size_t length) { return me(p)->url(p, at, length); } static int onStatus(http_parser* p, const char* at, size_t length) { return me(p)->status(p, at, length); } static int onHeaderField(http_parser* p, const char* at, size_t length) { return me(p)->headerField(p, at, length); } static int onHeaderValue(http_parser* p, const char* at, size_t length) { return me(p)->headerValue(p, at, length); } static int onHeadersComplete(http_parser* p) { return me(p)->headersComplete(p); } static int onBody(http_parser* p, const char* at, size_t length) { return me(p)->body(p, at, length); } static int onMessageComplete(http_parser* p) { return me(p)->messageComplete(p); } protected: // The ones we are reading in from the parser QByteArray itempHeaderField; QByteArray itempHeaderValue; // if connection has a timeout, these fields will be used quint32 itimeOut = 0; QBasicTimer itimer; // uniform socket object QSocket isocket; // if connection should persist bool ikeepAlive = false; // joyent http_parser http_parser iparser; http_parser_settings iparserSettings; static TImpl* me(http_parser* p) { return static_cast(p->data); } }; // /// basic request parser (server) template struct HttpRequestParser : public HttpParser { HttpRequestParser() : HttpParser(HTTP_REQUEST) {} }; /// basic response parser (clinet) template struct HttpResponseParser : public HttpParser { HttpResponseParser() : HttpParser(HTTP_RESPONSE) {} }; /////////////////////////////////////////////////////////////////////////////// } // namespace details } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// #endif // __QHTTP_HTTPPARSER_HXX__ psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/private/httpreader.hxx000066400000000000000000000037541370065651000256300ustar00rootroot00000000000000/** @file httpreader.hxx * * @copyright (C) 2016 * @date 2016.05.26 * @version 1.0.0 * @author amir zamani * */ #ifndef __QHTTP_HTTPREADER_HXX__ #define __QHTTP_HTTPREADER_HXX__ #include "qhttpbase.hpp" /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace details { /////////////////////////////////////////////////////////////////////////////// // usage in client::QHttpResponse, server::QHttpRequest template class HttpReader : public TBase { public: enum TReadState { EEmpty, EPartial, EComplete, ESent }; public: void collectData(int atMost) { icollectRequired = true; icollectCapacity = atMost; icollectedData.clear(); if ( atMost > 0 ) icollectedData.reserve(atMost); } bool append(const char* data, size_t length) { if ( !icollectRequired ) // not allowed to collect data return false; int newLength = icollectedData.length() + (int) length; if ( icollectCapacity > 0 && newLength > icollectCapacity ) return false; // the capacity is full icollectedData.append(data, length); return true; } // call cb if the message is not finalized yet template void finalizeSending(Func cb) { if ( ireadState != EComplete ) { ireadState = EComplete; isuccessful = true; cb(); } } public: bool isuccessful = false; TReadState ireadState = EEmpty; /// shall I collect incoming body data by myself? bool icollectRequired = false; int icollectCapacity = 0; QByteArray icollectedData; }; /////////////////////////////////////////////////////////////////////////////// } // namespace details } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// #endif // __QHTTP_HTTPREADER_HXX__ psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/private/httpwriter.hxx000066400000000000000000000055511370065651000256770ustar00rootroot00000000000000/** @file httpwriter.hxx * * @copyright (C) 2016 * @date 2016.05.26 * @version 1.0.0 * @author amir zamani * */ #ifndef __QHTTP_HTTPWRITER_HXX__ #define __QHTTP_HTTPWRITER_HXX__ #include "qhttpbase.hpp" #include /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace details { /////////////////////////////////////////////////////////////////////////////// // usage in client::QHttpRequest, server::QHttpResponse template class HttpWriter : public TBase { public: bool addHeader(const QByteArray &field, const QByteArray &value) { if ( ifinished ) return false; TBase::iheaders.insert(field.toLower(), value); return true; } bool writeHeader(const QByteArray& field, const QByteArray& value) { if ( ifinished ) return false; QByteArray buffer = QByteArray(field) .append(": ") .append(value) .append("\r\n"); isocket.writeRaw(buffer); return true; } bool writeData(const QByteArray& data) { if ( ifinished ) return false; ensureWritingHeaders(); isocket.writeRaw(data); return true; } bool endPacket(const QByteArray& data) { if ( !writeData(data) ) return false; isocket.flush(); ifinished = true; return true; } void ensureWritingHeaders() { if ( ifinished || iheaderWritten ) return; TImpl* me = static_cast(this); isocket.writeRaw(me->makeTitle()); writeHeaders(); iheaderWritten = true; } void writeHeaders(bool doFlush = false) { if ( ifinished || iheaderWritten ) return; if ( TBase::iheaders.keyHasValue("connection", "keep-alive") ) ikeepAlive = true; else TBase::iheaders.insert("connection", "close"); TImpl* me = static_cast(this); me->prepareHeadersToWrite(); QHashIterator cit(TBase::iheaders); while (cit.hasNext()) { cit.next(); const QByteArray& field = cit.key(); const QByteArray& value = cit.value(); this->writeHeader(field, value); } isocket.writeRaw("\r\n"); if ( doFlush ) isocket.flush(); } public: QSocket isocket; bool ifinished = false; bool iheaderWritten = false; bool ikeepAlive = false; }; /////////////////////////////////////////////////////////////////////////////// } // namespace details } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// #endif // __QHTTP_HTTPWRITER_HXX__ psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/private/qhttpbase.hpp000066400000000000000000000023731370065651000254350ustar00rootroot00000000000000/** base classes for private implementations. * https://github.com/azadkuh/qhttp * * @author amir zamani * @version 2.0.0 * @date 2014-07-11 */ #ifndef QHTTPBASE_HPP #define QHTTPBASE_HPP #include "qhttpfwd.hpp" #include "qsocket.hpp" #include #include #include "http_parser.h" /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace details { /////////////////////////////////////////////////////////////////////////////// struct HttpBase { QString iversion; THeaderHash iheaders; }; // struct HttpBase /////////////////////////////////////////////////////////////////////////////// struct HttpRequestBase : public HttpBase { QUrl iurl; THttpMethod imethod; }; // HttpRequestBase /////////////////////////////////////////////////////////////////////////////// struct HttpResponseBase : public HttpBase { TStatusCode istatus = ESTATUS_BAD_REQUEST; HttpResponseBase() { iversion = "1.1"; } }; // HttpResponseBase /////////////////////////////////////////////////////////////////////////////// } // namespace details } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// #endif // QHTTPBASE_HPP psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/private/qhttpclient_private.hpp000066400000000000000000000132131370065651000275260ustar00rootroot00000000000000/** private imeplementation. * https://github.com/azadkuh/qhttp * * @author amir zamani * @version 2.0.0 * @date 2014-07-11 */ #ifndef QHTTPCLIENT_PRIVATE_HPP #define QHTTPCLIENT_PRIVATE_HPP /////////////////////////////////////////////////////////////////////////////// #include "qhttpclient.hpp" #include "httpparser.hxx" #include "qhttpclientrequest_private.hpp" #include "qhttpclientresponse_private.hpp" /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace client { /////////////////////////////////////////////////////////////////////////////// class QHttpClientPrivate : public details::HttpResponseParser { Q_DECLARE_PUBLIC(QHttpClient) public: explicit QHttpClientPrivate(QHttpClient* q) : q_ptr(q) { QObject::connect( q_func(), &QHttpClient::disconnected, [this](){ release(); } ); QHTTP_LINE_DEEPLOG } virtual ~QHttpClientPrivate() { QHTTP_LINE_DEEPLOG } void release() { // if socket drops and http_parser can not call messageComplete, // dispatch the ilastResponse finalizeConnection(); isocket.disconnectAllQtConnections(); isocket.release(); if ( ilastRequest ) { ilastRequest->deleteLater(); ilastRequest = nullptr; } if ( ilastResponse ) { ilastResponse->deleteLater(); ilastResponse = nullptr; } // must be called! or the later http_parser_execute() may fail http_parser_init(&iparser, HTTP_RESPONSE); } void initializeSocket() { if ( isocket.isOpen() ) { // no need to reconnect. do nothing and simply return if ( ikeepAlive ) return; // close previous connection now! // instead being called by emitted disconnected signal release(); } ikeepAlive = false; // create a tcp connection if ( isocket.ibackendType == ETcpSocket ) { initTcpSocket(); } else if ( isocket.ibackendType == ELocalSocket ) { initLocalSocket(); } } public: int messageBegin(http_parser* parser); int url(http_parser*, const char*, size_t) { return 0; // not used in parsing incoming respone. } int status(http_parser* parser, const char* at, size_t length) ; int headerField(http_parser* parser, const char* at, size_t length); int headerValue(http_parser* parser, const char* at, size_t length); int headersComplete(http_parser* parser); int body(http_parser* parser, const char* at, size_t length); int messageComplete(http_parser* parser); protected: void onConnected() { iconnectingTimer.stop(); if ( itimeOut > 0 ) itimer.start(itimeOut, Qt::CoarseTimer, q_func()); if ( ireqHandler ) ireqHandler(ilastRequest); else q_func()->onRequestReady(ilastRequest); } void onReadyRead() { while ( isocket.bytesAvailable() > 0 ) { char buffer[4097] = {0}; size_t readLength = (size_t) isocket.readRaw(buffer, 4096); parse(buffer, readLength); } } void finalizeConnection() { if ( ilastResponse == nullptr ) return; ilastResponse->d_func()->finalizeSending([this]{ emit ilastResponse->end(); }); } private: void initTcpSocket() { QTcpSocket* sok = new QTcpSocket(q_func()); isocket.itcpSocket = sok; QObject::connect( sok, &QTcpSocket::connected, [this](){ onConnected(); } ); QObject::connect( sok, &QTcpSocket::readyRead, [this](){ onReadyRead(); } ); QObject::connect( sok, &QTcpSocket::bytesWritten, [this](qint64){ const auto& ts = isocket.itcpSocket; if ( ts->bytesToWrite() == 0 && ilastRequest ) emit ilastRequest->allBytesWritten(); }); QObject::connect( sok, &QTcpSocket::disconnected, q_func(), &QHttpClient::disconnected ); } void initLocalSocket() { QLocalSocket* sok = new QLocalSocket(q_func()); isocket.ilocalSocket = sok; QObject::connect( sok, &QLocalSocket::connected, [this](){ onConnected(); } ); QObject::connect( sok, &QLocalSocket::readyRead, [this](){ onReadyRead(); } ); QObject::connect( sok, &QLocalSocket::bytesWritten, [this](qint64){ const auto* ls = isocket.ilocalSocket; if ( ls->bytesToWrite() == 0 && ilastRequest ) emit ilastRequest->allBytesWritten(); }); QObject::connect( sok, &QLocalSocket::disconnected, q_func(), &QHttpClient::disconnected ); } protected: QHttpClient* const q_ptr; QHttpRequest* ilastRequest = nullptr; QHttpResponse* ilastResponse = nullptr; TRequstHandler ireqHandler; TResponseHandler irespHandler; QBasicTimer iconnectingTimer; }; /////////////////////////////////////////////////////////////////////////////// } // namespace client } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// #endif // QHTTPCLIENT_PRIVATE_HPP psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/private/qhttpclientrequest_private.hpp000066400000000000000000000030711370065651000311400ustar00rootroot00000000000000/** private imeplementation. * https://github.com/azadkuh/qhttp * * @author amir zamani * @version 2.0.0 * @date 2014-07-11 */ #ifndef QHTTPCLIENT_REQUEST_PRIVATE_HPP #define QHTTPCLIENT_REQUEST_PRIVATE_HPP /////////////////////////////////////////////////////////////////////////////// #include "httpwriter.hxx" #include "qhttpclient.hpp" #include "qhttpclientrequest.hpp" #include /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace client { /////////////////////////////////////////////////////////////////////////////// class QHttpRequestPrivate : public details::HttpWriter { Q_DECLARE_PUBLIC(QHttpRequest) public: explicit QHttpRequestPrivate(QHttpClient* cli, QHttpRequest* q) : q_ptr(q), iclient(cli) { QHTTP_LINE_DEEPLOG } virtual ~QHttpRequestPrivate() { QHTTP_LINE_DEEPLOG } void initialize() { iversion = "1.1"; isocket.ibackendType = iclient->backendType(); isocket.itcpSocket = iclient->tcpSocket(); isocket.ilocalSocket = iclient->localSocket(); } QByteArray makeTitle(); void prepareHeadersToWrite(); protected: QHttpRequest* const q_ptr; QHttpClient* const iclient; }; /////////////////////////////////////////////////////////////////////////////// } // namespace client } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// #endif // QHTTPCLIENT_REQUEST_PRIVATE_HPP psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/private/qhttpclientresponse_private.hpp000066400000000000000000000025241370065651000313100ustar00rootroot00000000000000/** private imeplementation. * https://github.com/azadkuh/qhttp * * @author amir zamani * @version 2.0.0 * @date 2014-07-11 */ #ifndef QHTTPCLIENT_RESPONSE_PRIVATE_HPP #define QHTTPCLIENT_RESPONSE_PRIVATE_HPP /////////////////////////////////////////////////////////////////////////////// #include "httpreader.hxx" #include "qhttpclient.hpp" #include "qhttpclientresponse.hpp" /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace client { /////////////////////////////////////////////////////////////////////////////// class QHttpResponsePrivate : public details::HttpReader { Q_DECLARE_PUBLIC(QHttpResponse) QHttpResponse* const q_ptr; public: explicit QHttpResponsePrivate(QHttpClient* cli, QHttpResponse* q) : q_ptr(q), iclient(cli) { QHTTP_LINE_DEEPLOG } virtual ~QHttpResponsePrivate() { QHTTP_LINE_DEEPLOG } void initialize() { } public: QString icustomStatusMessage; protected: QHttpClient* const iclient; }; /////////////////////////////////////////////////////////////////////////////// } // namespace client } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// #endif // QHTTPCLIENT_RESPONSE_PRIVATE_HPP psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/private/qhttpserver_private.hpp000066400000000000000000000046131370065651000275620ustar00rootroot00000000000000/** private imeplementation. * https://github.com/azadkuh/qhttp * * @author amir zamani * @version 2.0.0 * @date 2014-07-11 */ #ifndef QHTTPSERVER_PRIVATE_HPP #define QHTTPSERVER_PRIVATE_HPP /////////////////////////////////////////////////////////////////////////////// #include "qhttpserver.hpp" #include "qhttpserverconnection.hpp" #include "qhttpserverrequest.hpp" #include "qhttpserverresponse.hpp" #include #include /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace server { /////////////////////////////////////////////////////////////////////////////// class QHttpServerPrivate { public: template class BackendServer : public TServer { public: QHttpServer* iserver; explicit BackendServer(QHttpServer* s) : TServer(s), iserver(s) { } protected: // if it's a QTcpServer virtual void incomingConnection(qintptr socketDescriptor) { iserver->incomingConnection(socketDescriptor); } // if it's a QLocalServer virtual void incomingConnection(quintptr socketDescriptor) { iserver->incomingConnection((qintptr) socketDescriptor); } }; using TTcpServer = QScopedPointer>; using TLocalServer = QScopedPointer>; public: quint32 itimeOut = 0; TServerHandler ihandler = nullptr; TBackend ibackend = ETcpSocket; TTcpServer itcpServer; TLocalServer ilocalServer; public: explicit QHttpServerPrivate() { QHTTP_LINE_DEEPLOG } virtual ~QHttpServerPrivate() { QHTTP_LINE_DEEPLOG } void initialize(TBackend backend, QHttpServer* parent) { ibackend = backend; if ( ibackend == ETcpSocket ) { itcpServer.reset( new BackendServer(parent) ); ilocalServer.reset( nullptr ); } else if ( ibackend == ELocalSocket ) { itcpServer.reset( nullptr ); ilocalServer.reset( new BackendServer(parent) ); } } }; /////////////////////////////////////////////////////////////////////////////// } // namespace server } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// #endif // QHTTPSERVER_PRIVATE_HPP psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/private/qhttpserverconnection_private.hpp000066400000000000000000000122261370065651000316410ustar00rootroot00000000000000/** private imeplementation. * https://github.com/azadkuh/qhttp * * @author amir zamani * @version 2.0.0 * @date 2014-07-11 */ #ifndef QHTTPSERVER_CONNECTION_PRIVATE_HPP #define QHTTPSERVER_CONNECTION_PRIVATE_HPP /////////////////////////////////////////////////////////////////////////////// #include "qhttpserverconnection.hpp" #include "httpparser.hxx" #include "qhttpserverrequest.hpp" #include "qhttpserverresponse.hpp" #include "private/qhttpserverrequest_private.hpp" #include "private/qhttpserverresponse_private.hpp" #include #include /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace server { /////////////////////////////////////////////////////////////////////////////// class QHttpConnectionPrivate : public details::HttpRequestParser { Q_DECLARE_PUBLIC(QHttpConnection) public: explicit QHttpConnectionPrivate(QHttpConnection* q) : q_ptr(q) { QObject::connect( q_func(), &QHttpConnection::disconnected, [this](){ release(); } ); QHTTP_LINE_DEEPLOG } virtual ~QHttpConnectionPrivate() { QHTTP_LINE_DEEPLOG } void createSocket(qintptr sokDesc, TBackend bend) { isocket.ibackendType = bend; if ( bend == ETcpSocket ) { initTcpSocket(sokDesc); } else if ( bend == ELocalSocket ) { initLocalSocket(sokDesc); } } void release() { // if socket drops and http_parser can not call // messageComplete, dispatch the ilastRequest finalizeConnection(); isocket.disconnectAllQtConnections(); isocket.release(); if ( ilastRequest ) { ilastRequest->deleteLater(); ilastRequest = nullptr; } if ( ilastResponse ) { ilastResponse->deleteLater(); ilastResponse = nullptr; } q_func()->deleteLater(); } public: void onReadyRead() { while ( isocket.bytesAvailable() > 0 ) { char buffer[4097] = {0}; size_t readLength = (size_t) isocket.readRaw(buffer, 4096); parse(buffer, readLength); } } void finalizeConnection() { if ( ilastRequest == nullptr ) return; ilastRequest->d_func()->finalizeSending([this]{ emit ilastRequest->end(); }); } public: int messageBegin(http_parser* parser); int url(http_parser* parser, const char* at, size_t length); int status(http_parser*, const char*, size_t) { return 0; // not used in parsing incoming request. } int headerField(http_parser* parser, const char* at, size_t length); int headerValue(http_parser* parser, const char* at, size_t length); int headersComplete(http_parser* parser); int body(http_parser* parser, const char* at, size_t length); int messageComplete(http_parser* parser); private: void initTcpSocket(qintptr sokDesc) { QTcpSocket* sok = new QTcpSocket( q_func() ); isocket.itcpSocket = sok; sok->setSocketDescriptor(sokDesc); QObject::connect( sok, &QTcpSocket::readyRead, [this](){ onReadyRead(); } ); QObject::connect( sok, &QTcpSocket::bytesWritten, [this](){ auto btw = isocket.itcpSocket->bytesToWrite(); if ( btw == 0 && ilastResponse ) emit ilastResponse->allBytesWritten(); }); QObject::connect( sok, &QTcpSocket::disconnected, q_func(), &QHttpConnection::disconnected, Qt::QueuedConnection ); } void initLocalSocket(qintptr sokDesc) { QLocalSocket* sok = new QLocalSocket( q_func() ); isocket.ilocalSocket = sok; sok->setSocketDescriptor(sokDesc); QObject::connect( sok, &QLocalSocket::readyRead, [this](){ onReadyRead(); } ); QObject::connect( sok, &QLocalSocket::bytesWritten, [this](){ auto btw = isocket.ilocalSocket->bytesToWrite(); if ( btw == 0 && ilastResponse ) emit ilastResponse->allBytesWritten(); }); QObject::connect( sok, &QLocalSocket::disconnected, q_func(), &QHttpConnection::disconnected, Qt::QueuedConnection ); } protected: QHttpConnection* const q_ptr; QByteArray itempUrl; // Since there can only be one request/response pair per connection at any // time even with pipelining. QHttpRequest* ilastRequest = nullptr; QHttpResponse* ilastResponse = nullptr; TServerHandler ihandler = nullptr; }; /////////////////////////////////////////////////////////////////////////////// } // namespace server } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// #endif // QHTTPSERVER_CONNECTION_PRIVATE_HPP psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/private/qhttpserverrequest_private.hpp000066400000000000000000000025641370065651000311760ustar00rootroot00000000000000/** private imeplementation. * https://github.com/azadkuh/qhttp * * @author amir zamani * @version 2.0.0 * @date 2014-07-11 */ #ifndef QHTTPSERVER_REQUEST_PRIVATE_HPP #define QHTTPSERVER_REQUEST_PRIVATE_HPP /////////////////////////////////////////////////////////////////////////////// #include "httpreader.hxx" #include "qhttpserverrequest.hpp" #include "qhttpserverconnection.hpp" /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace server { /////////////////////////////////////////////////////////////////////////////// class QHttpRequestPrivate : public details::HttpReader { protected: Q_DECLARE_PUBLIC(QHttpRequest) QHttpRequest* const q_ptr; public: explicit QHttpRequestPrivate(QHttpConnection* conn, QHttpRequest* q) : q_ptr(q), iconnection(conn) { QHTTP_LINE_DEEPLOG } virtual ~QHttpRequestPrivate() { QHTTP_LINE_DEEPLOG } void initialize() { } public: QString iremoteAddress; quint16 iremotePort = 0; QHttpConnection* const iconnection = nullptr; }; /////////////////////////////////////////////////////////////////////////////// } // namespace server } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// #endif // QHTTPSERVER_REQUEST_PRIVATE_HPP psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/private/qhttpserverresponse_private.hpp000066400000000000000000000034451370065651000313430ustar00rootroot00000000000000/** private imeplementation. * https://github.com/azadkuh/qhttp * * @author amir zamani * @version 2.0.0 * @date 2014-07-11 */ #ifndef QHTTPSERVER_RESPONSE_PRIVATE_HPP #define QHTTPSERVER_RESPONSE_PRIVATE_HPP /////////////////////////////////////////////////////////////////////////////// #include "httpwriter.hxx" #include "qhttpserverresponse.hpp" #include "qhttpserver.hpp" #include "qhttpserverconnection.hpp" #include #include #include /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace server { /////////////////////////////////////////////////////////////////////////////// class QHttpResponsePrivate : public details::HttpWriter { Q_DECLARE_PUBLIC(QHttpResponse) public: explicit QHttpResponsePrivate(QHttpConnection* conn, QHttpResponse* q) : q_ptr(q), iconnection(conn) { QHTTP_LINE_DEEPLOG } virtual ~QHttpResponsePrivate() { QHTTP_LINE_DEEPLOG } void initialize() { isocket.ibackendType = iconnection->backendType(); isocket.ilocalSocket = iconnection->localSocket(); isocket.itcpSocket = iconnection->tcpSocket(); QObject::connect(iconnection, &QHttpConnection::disconnected, q_func(), &QHttpResponse::deleteLater); } QByteArray makeTitle(); void prepareHeadersToWrite(); protected: QHttpResponse* const q_ptr; QHttpConnection* const iconnection; }; /////////////////////////////////////////////////////////////////////////////// } // namespace server } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// #endif // QHTTPSERVER_RESPONSE_PRIVATE_HPP psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/private/qsocket.hpp000066400000000000000000000061171370065651000251130ustar00rootroot00000000000000/** @file qsocket.hpp * * @copyright (C) 2016 * @date 2016.05.26 * @version 1.0.0 * @author amir zamani * */ #ifndef __QHTTP_SOCKET_HPP__ #define __QHTTP_SOCKET_HPP__ #include "qhttpfwd.hpp" #include #include #include /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace details { /////////////////////////////////////////////////////////////////////////////// /** an adapter for different socket types. * the main purpose of QHttp was to create a small HTTP server with ability to * support UNIX sockets (QLocalSocket) */ class QSocket { public: void close() { if ( itcpSocket ) itcpSocket->close(); if ( ilocalSocket ) ilocalSocket->close(); } void release() { close(); if ( itcpSocket ) itcpSocket->deleteLater(); if ( ilocalSocket ) ilocalSocket->deleteLater(); itcpSocket = nullptr; ilocalSocket = nullptr; } void flush() { if ( itcpSocket ) itcpSocket->flush(); else if ( ilocalSocket ) ilocalSocket->flush(); } bool isOpen() const { if ( ibackendType == ETcpSocket && itcpSocket ) return itcpSocket->isOpen() && itcpSocket->state() == QTcpSocket::ConnectedState; else if ( ibackendType == ELocalSocket && ilocalSocket ) return ilocalSocket->isOpen() && ilocalSocket->state() == QLocalSocket::ConnectedState; return false; } void connectTo(const QUrl& url) { if ( ilocalSocket ) ilocalSocket->connectToServer(url.path()); } void connectTo(const QString& host, quint16 port) { if ( itcpSocket ) itcpSocket->connectToHost(host, port); } qint64 readRaw(char* buffer, int maxlen) { if ( itcpSocket ) return itcpSocket->read(buffer, maxlen); else if ( ilocalSocket ) return ilocalSocket->read(buffer, maxlen); return 0; } void writeRaw(const QByteArray& data) { if ( itcpSocket ) itcpSocket->write(data); else if ( ilocalSocket ) ilocalSocket->write(data); } qint64 bytesAvailable() { if ( itcpSocket ) return itcpSocket->bytesAvailable(); else if ( ilocalSocket ) return ilocalSocket->bytesAvailable(); return 0; } void disconnectAllQtConnections() { if ( itcpSocket ) QObject::disconnect(itcpSocket, 0, 0, 0); if ( ilocalSocket ) QObject::disconnect(ilocalSocket, 0, 0, 0); } public: TBackend ibackendType = ETcpSocket; QTcpSocket* itcpSocket = nullptr; QLocalSocket* ilocalSocket = nullptr; }; // class QSocket /////////////////////////////////////////////////////////////////////////////// } // namespace details } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// #endif // __QHTTP_SOCKET_HPP__ psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/qhttpabstracts.cpp000066400000000000000000000107431370065651000250320ustar00rootroot00000000000000#include "qhttpabstracts.hpp" #include "http_parser.h" /////////////////////////////////////////////////////////////////////////////// namespace qhttp { /////////////////////////////////////////////////////////////////////////////// #if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0)) # error "to compile QHttp classes, Qt 5.0 or later is needed." #endif #define HTTP_STATUS_MAP(XX) \ XX(100, "Continue") \ XX(101, "Switching Protocols") \ /* RFC 2518) obsoleted by RFC 4918 */ \ XX(102, "Processing") \ XX(200, "OK") \ XX(201, "Created") \ XX(202, "Accepted") \ XX(203, "Non-Authoritative Information") \ XX(204, "No Content") \ XX(205, "Reset Content") \ XX(206, "Partial Content") \ /* RFC 4918 */ \ XX(207, "Multi-Status") \ XX(300, "Multiple Choices") \ XX(301, "Moved Permanently") \ XX(302, "Moved Temporarily") \ XX(303, "See Other") \ XX(304, "Not Modified") \ XX(305, "Use Proxy") \ XX(307, "Temporary Redirect") \ XX(400, "Bad Request") \ XX(401, "Unauthorized") \ XX(402, "Payment Required") \ XX(403, "Forbidden") \ XX(404, "Not Found") \ XX(405, "Method Not Allowed") \ XX(406, "Not Acceptable") \ XX(407, "Proxy Authentication Required") \ XX(408, "Request Time-out") \ XX(409, "Conflict") \ XX(410, "Gone") \ XX(411, "Length Required") \ XX(412, "Precondition Failed") \ XX(413, "Request Entity Too Large") \ XX(414, "Request-URI Too Large") \ XX(415, "Unsupported Media Type") \ XX(416, "Requested Range Not Satisfiable") \ XX(417, "Expectation Failed") \ /* RFC 2324 */ \ XX(418, "I\"m a teapot") \ /* RFC 4918 */ \ XX(422, "Unprocessable Entity") \ /* RFC 4918 */ \ XX(423, "Locked") \ /* RFC 4918 */ \ XX(424, "Failed Dependency") \ /* RFC 4918 */ \ XX(425, "Unordered Collection") \ /* RFC 2817 */ \ XX(426, "Upgrade Required") \ XX(500, "Internal Server Error") \ XX(501, "Not Implemented") \ XX(502, "Bad Gateway") \ XX(503, "Service Unavailable") \ XX(504, "Gateway Time-out") \ XX(505, "HTTP Version not supported") \ /* RFC 2295 */ \ XX(506, "Variant Also Negotiates") \ /* RFC 4918 */ \ XX(507, "Insufficient Storage") \ XX(509, "Bandwidth Limit Exceeded") \ /* RFC 2774 */ \ XX(510, "Not Extended") #define PATCH_STATUS_CODES(n,s) {n, s}, static struct { int code; const char* message; } g_status_codes[] { HTTP_STATUS_MAP(PATCH_STATUS_CODES) }; #undef PATCH_STATUS_CODES /////////////////////////////////////////////////////////////////////////////// const char* Stringify::toString(TStatusCode code) { size_t count = sizeof(g_status_codes) / sizeof(g_status_codes[0]); for ( size_t i = 0; i < count; i++ ) { if ( g_status_codes[i].code == code ) return g_status_codes[i].message; } return nullptr; } const char* Stringify::toString(THttpMethod method) { return http_method_str(static_cast(method)); } /////////////////////////////////////////////////////////////////////////////// QHttpAbstractInput::QHttpAbstractInput(QObject* parent) : QObject(parent) { } QHttpAbstractOutput::QHttpAbstractOutput(QObject *parent) : QObject(parent) { } /////////////////////////////////////////////////////////////////////////////// } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/qhttpabstracts.hpp000066400000000000000000000152771370065651000250460ustar00rootroot00000000000000/** interfaces of QHttp' incoming and outgoing classes. * https://github.com/azadkuh/qhttp * * @author amir zamani * @version 2.0.0 * @date 2014-07-11 */ #ifndef QHTTPABSTRACTS_HPP #define QHTTPABSTRACTS_HPP /////////////////////////////////////////////////////////////////////////////// #include "qhttpfwd.hpp" #include #include /////////////////////////////////////////////////////////////////////////////// namespace qhttp { /////////////////////////////////////////////////////////////////////////////// /** a utility class to give the string representation of qhttp types. */ class QHTTP_API Stringify { public: /** returns the standard message for an HTTP status code. */ static const char* toString(TStatusCode); /** returns the standars name of an HTTP method. */ static const char* toString(THttpMethod); }; /////////////////////////////////////////////////////////////////////////////// /** an interface for input (incoming) HTTP packets. * server::QHttpRequest or client::QHttpResponse inherit from this class. */ class QHTTP_API QHttpAbstractInput : public QObject { Q_OBJECT public: /** Return all the headers in the incoming packet. * This returns a reference. If you want to store headers * somewhere else, where the request may be deleted, * make sure you store them as a copy. * @note All header names are lowercase . */ virtual const THeaderHash& headers() const=0; /** The HTTP version of the packet. * @return A string in the form of "x.x" */ virtual const QString& httpVersion() const=0; /** If this packet was successfully received. * Set before end() has been emitted, stating whether * the message was properly received. This is false * until the receiving the full request has completed. */ virtual bool isSuccessful() const=0; signals: /** Emitted when new body data has been received. * @param data Received data. * @note This may be emitted zero or more times depending on the transfer type. * @see onData(); */ void data(QByteArray data); /** Emitted when the incoming packet has been fully received. * @note The no more data() signals will be emitted after this. * @see onEnd(); */ void end(); public: /** optionally set a handler for data() signal. * @param dataHandler a std::function or lambda handler to receive incoming data. * @note if you set this handler, the data() signal won't be emitted anymore. */ template void onData(Func f) { QObject::connect(this, &QHttpAbstractInput::data, f); } /** optionally set a handler for end() signal. * @param endHandler a std::function or lambda handler to receive end notification. * @note if you set this handler, the end() signal won't be emitted anymore. */ template void onEnd(Func f) { QObject::connect(this, &QHttpAbstractInput::end, f); } public: /** tries to collect all the incoming data internally. * @note if you call this method, data() signal won't be emitted and * onData() will have no effect. * * @param atMost maximum acceptable incoming data. if the incoming data * exceeds this value, the connection won't read any more data and * end() signal will be emitted. * default value (-1) means read data as "content-length" or unlimited if * the body size is unknown. */ virtual void collectData(int atMost = -1) =0; /** returns the collected data requested by collectData(). */ virtual const QByteArray& collectedData()const =0; public: virtual ~QHttpAbstractInput() = default; explicit QHttpAbstractInput(QObject* parent); Q_DISABLE_COPY(QHttpAbstractInput) }; /////////////////////////////////////////////////////////////////////////////// /** an interface for output (outgoing) HTTP packets. * server::QHttpResponse or client::QHttpRequest inherit from this class. */ class QHTTP_API QHttpAbstractOutput : public QObject { Q_OBJECT public: /** changes the HTTP version string ex: "1.1" or "1.0". * version is "1.1" set by default. */ virtual void setVersion(const QString& versionString)=0; /** helper function. @sa addHeader */ template void addHeaderValue(const QByteArray &field, T value); /** adds an HTTP header to the packet. * @note this method does not actually write anything to socket, just prepares the headers(). */ virtual void addHeader(const QByteArray& field, const QByteArray& value)=0; /** returns all the headers that already been set. */ virtual THeaderHash& headers()=0; /** writes a block of data into the HTTP packet. * @note headers are written (flushed) before any data. * @warning after calling this method add a new header, set staus code, set Url have no effect! */ virtual void write(const QByteArray &data)=0; /** ends (finishes) the outgoing packet by calling write(). * headers and data will be flushed to the underlying socket. * * @sa write() */ virtual void end(const QByteArray &data = QByteArray())=0; signals: /** Emitted when all the data has been sent. * this signal indicates that the underlaying socket has transmitted all * of it's buffered data. */ void allBytesWritten(); /** Emitted when the packet is finished and reports if it was the last packet. * if it was the last packet (google for "Connection: keep-alive / close") * the http connection (socket) will be closed automatically. */ void done(bool wasTheLastPacket); public: virtual ~QHttpAbstractOutput() = default; protected: explicit QHttpAbstractOutput(QObject* parent); Q_DISABLE_COPY(QHttpAbstractOutput) }; template<> inline void QHttpAbstractOutput::addHeaderValue(const QByteArray &field, int value) { addHeader(field, QString::number(value).toLatin1()); } template<> inline void QHttpAbstractOutput::addHeaderValue(const QByteArray &field, size_t value) { addHeader(field, QString::number(value).toLatin1()); } template<> inline void QHttpAbstractOutput::addHeaderValue(const QByteArray &field, QString value) { addHeader(field, value.toUtf8()); } /////////////////////////////////////////////////////////////////////////////// } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// #endif // QHTTPABSTRACTS_HPP psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/qhttpclient.cpp000066400000000000000000000161501370065651000243200ustar00rootroot00000000000000#include "private/qhttpclient_private.hpp" #include /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace client { /////////////////////////////////////////////////////////////////////////////// QHttpClient::QHttpClient(QObject *parent) : QObject(parent), d_ptr(new QHttpClientPrivate(this)) { QHTTP_LINE_LOG } QHttpClient::QHttpClient(QHttpClientPrivate &dd, QObject *parent) : QObject(parent), d_ptr(&dd) { QHTTP_LINE_LOG } QHttpClient::~QHttpClient() { QHTTP_LINE_LOG } quint32 QHttpClient::timeOut() const { return d_func()->itimeOut; } void QHttpClient::setTimeOut(quint32 t) { d_func()->itimeOut = t; } bool QHttpClient::isOpen() const { return d_func()->isocket.isOpen(); } void QHttpClient::killConnection() { Q_D(QHttpClient); d->iconnectingTimer.stop(); d->itimer.stop(); d->isocket.close(); } TBackend QHttpClient::backendType() const { return d_func()->isocket.ibackendType; } QTcpSocket* QHttpClient::tcpSocket() const { return d_func()->isocket.itcpSocket; } QLocalSocket* QHttpClient::localSocket() const { return d_func()->isocket.ilocalSocket; } void QHttpClient::setConnectingTimeOut(quint32 timeout) { Q_D(QHttpClient); if ( timeout == 0 ) { d->iconnectingTimer.stop(); } else { d->iconnectingTimer.start(timeout, Qt::CoarseTimer, this ); } } bool QHttpClient::request(THttpMethod method, QUrl url, const TRequstHandler &reqHandler, const TResponseHandler &resHandler) { Q_D(QHttpClient); d->ireqHandler = nullptr; d->irespHandler = nullptr; // if url is a local file (UNIX socket) the host could be empty! if ( !url.isValid() || url.isEmpty() /*|| url.host().isEmpty()*/ ) return false; // process handlers if ( resHandler ) { d->irespHandler = resHandler; if ( reqHandler ) d->ireqHandler = reqHandler; else d->ireqHandler = [](QHttpRequest* req) ->void { req->addHeader("connection", "close"); req->end(); }; } auto requestCreator = [this, method, url]() { // create request object if ( d_ptr->ilastRequest ) d_ptr->ilastRequest->deleteLater(); d_ptr->ilastRequest = new QHttpRequest(this); QObject::connect(d_ptr->ilastRequest, &QHttpRequest::done, [this](bool wasTheLastPacket){ d_ptr->ikeepAlive = !wasTheLastPacket; }); d_ptr->ilastRequest->d_ptr->imethod = method; d_ptr->ilastRequest->d_ptr->iurl = url; }; // connecting to host/server must be the last thing. (after all function handlers and ...) // check for type if ( url.scheme().toLower() == QLatin1String("file") ) { d->isocket.ibackendType = ELocalSocket; d->initializeSocket(); requestCreator(); if ( d->isocket.isOpen() ) d->onConnected(); else d->isocket.connectTo(url); } else { d->isocket.ibackendType = ETcpSocket; d->initializeSocket(); requestCreator(); if ( d->isocket.isOpen() ) d->onConnected(); else d->isocket.connectTo(url.host(), url.port(80)); } return true; } void QHttpClient::timerEvent(QTimerEvent *e) { Q_D(QHttpClient); if ( e->timerId() == d->itimer.timerId() ) { killConnection(); } else if ( e->timerId() == d->iconnectingTimer.timerId() ) { d->iconnectingTimer.stop(); emit connectingTimeOut(); } } void QHttpClient::onRequestReady(QHttpRequest *req) { emit httpConnected(req); } void QHttpClient::onResponseReady(QHttpResponse *res) { emit newResponse(res); } /////////////////////////////////////////////////////////////////////////////// // if server closes the connection, ends the response or by any other reason // the socket disconnects, then the irequest and iresponse instances may have // been deleted. In these situations reading more http body or emitting end() // for incoming request are not possible: // if ( ilastRequest == nullptr ) // return 0; int QHttpClientPrivate::messageBegin(http_parser*) { itempHeaderField.clear(); itempHeaderValue.clear(); return 0; } int QHttpClientPrivate::status(http_parser* parser, const char* at, size_t length) { if ( ilastResponse ) ilastResponse->deleteLater(); ilastResponse = new QHttpResponse(q_func()); ilastResponse->d_func()->istatus = static_cast(parser->status_code); ilastResponse->d_func()->iversion = QString("%1.%2") .arg(parser->http_major) .arg(parser->http_minor); ilastResponse->d_func()->icustomStatusMessage = QString::fromUtf8(at, length); return 0; } int QHttpClientPrivate::headerField(http_parser*, const char* at, size_t length) { if ( ilastResponse == nullptr ) return 0; // insert the header we parsed previously // into the header map if ( !itempHeaderField.isEmpty() && !itempHeaderValue.isEmpty() ) { // header names are always lower-cased ilastResponse->d_func()->iheaders.insert( itempHeaderField.toLower(), itempHeaderValue.toLower() ); // clear header value. this sets up a nice // feedback loop where the next time // HeaderValue is called, it can simply append itempHeaderField.clear(); itempHeaderValue.clear(); } itempHeaderField.append(at, length); return 0; } int QHttpClientPrivate::headerValue(http_parser*, const char* at, size_t length) { itempHeaderValue.append(at, length); return 0; } int QHttpClientPrivate::headersComplete(http_parser*) { if ( ilastResponse == nullptr ) return 0; // Insert last remaining header ilastResponse->d_func()->iheaders.insert( itempHeaderField.toLower(), itempHeaderValue.toLower() ); if ( irespHandler ) irespHandler(ilastResponse); else q_func()->onResponseReady(ilastResponse); return 0; } int QHttpClientPrivate::body(http_parser*, const char* at, size_t length) { if ( ilastResponse == nullptr ) return 0; ilastResponse->d_func()->ireadState = QHttpResponsePrivate::EPartial; if ( ilastResponse->d_func()->icollectRequired ) { if ( !ilastResponse->d_func()->append(at, length) ) { // forcefully dispatch the ilastResponse finalizeConnection(); } return 0; } emit ilastResponse->data(QByteArray(at, length)); return 0; } int QHttpClientPrivate::messageComplete(http_parser*) { if ( ilastResponse == nullptr ) return 0; // response is done finalizeConnection(); return 0; } /////////////////////////////////////////////////////////////////////////////// } // namespace client } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/qhttpclient.hpp000066400000000000000000000155401370065651000243270ustar00rootroot00000000000000/** HTTP client class. * https://github.com/azadkuh/qhttp * * @author amir zamani * @version 2.0.0 * @date 2014-07-11 */ #ifndef QHTTPCLIENT_HPP #define QHTTPCLIENT_HPP // configured by src.pro #if defined(QHTTP_HAS_CLIENT) /////////////////////////////////////////////////////////////////////////////// #include "qhttpfwd.hpp" #include #include /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace client { /////////////////////////////////////////////////////////////////////////////// using TRequstHandler = std::function; using TResponseHandler = std::function; /** a simple and async HTTP client class which sends a request to an HTTP server and parses the * corresponding response. * This class internally handles the memory management and life cycle of QHttpRequest and * QHttpResponse instances. you do not have to manually delete or keep their pointers. * in fact the QHttpRequest and QHttpResponse object will be deleted when the internal socket * disconnects. */ class QHTTP_API QHttpClient : public QObject { Q_OBJECT Q_PROPERTY(quint32 timeOut READ timeOut WRITE setTimeOut) public: explicit QHttpClient(QObject *parent = nullptr); virtual ~QHttpClient(); /** tries to connect to a HTTP server. * when the connection is made, the reqHandler will be called * and when the response is ready, resHandler will be called. * @note httpConnected() and newResponse() won't be emitted. * * @param method an HTTP method, ex: GET, POST, ... * @param url specifies server's address, port and optional path and query strings. * if url starts with socket:// the request will be made on QLocalSocket, otherwise * normal QTcpSocket will be used. * @param resHandler response handler (a lambda, std::function object, ...) * @return true if the url is valid or false (no connection will be made). */ bool request(THttpMethod method, QUrl url, const TRequstHandler& reqHandler, const TResponseHandler& resHandler); /** tries to connect to a HTTP server. * when the connection is made, a default request handler is called automatically ( * simply calls req->end()) and when the response is ready, resHandler will be called. * @note httpConnected() and newResponse() won't be emitted. * * @param method an HTTP method, ex: GET, POST, ... * @param url specifies server's address, port and optional path and query strings. * @param resHandler response handler (a lambda, std::function object, ...) * @return true if the url is valid or false (no connection will be made). */ inline bool request(THttpMethod method, QUrl url, const TResponseHandler& resHandler) { return request(method, url, nullptr, resHandler); } /** tries to connect to a HTTP server. * when the connection is made, creates and emits a QHttpRequest instance * by @sa httpConnected(QHttpRequest*). * @note both httpConnected() and newResponse() may be emitted. * * @param method an HTTP method, ex: GET, POST, ... * @param url specifies server's address, port and optional path and query strings. * @return true if the url is valid or false (no connection will be made). */ inline bool request(THttpMethod method, QUrl url) { return request(method, url, nullptr, nullptr); } /** checks if the connetion to the server is open. */ bool isOpen() const; /** forcefully close the connection. */ void killConnection(); /** returns time-out value [mSec] for ESTABLISHED connections (sockets). * @sa setTimeOut(). */ quint32 timeOut()const; /** set time-out for ESTABLISHED connections in miliseconds [mSec]. * each (already opened) connection will be forcefully closed after this timeout. * a zero (0) value disables timer for new connections. */ void setTimeOut(quint32); /** set a time-out [mSec] for making a new connection (make a request). * if connecting to server takes more than this time-out value, * the @sa timedOut(quint32) signal will be emitted and connection will be killed. * 0 (default) timeout value means to disable this timer. */ void setConnectingTimeOut(quint32); template void setConnectingTimeOut(quint32 timeout, Handler&& func) { setConnectingTimeOut(timeout); QObject::connect(this, &QHttpClient::connectingTimeOut, std::forward(func) ); } /** returns the backend type of this client. */ TBackend backendType() const; /** returns tcp socket of the connection if backend() == ETcpSocket. */ QTcpSocket* tcpSocket() const; /** returns local socket of the connection if backend() == ELocalSocket. */ QLocalSocket* localSocket() const; signals: /** emitted when a new HTTP connection to the server is established. * if you overload onRequestReady this signal won't be emitted. * @sa onRequestReady * @sa QHttpRequest */ void httpConnected(QHttpRequest* req); /** emitted when a new response is received from the server. * if you overload onResponseReady this signal won't be emitted. * @sa onResponseReady * @sa QHttpResponse */ void newResponse(QHttpResponse* res); /** emitted when the HTTP connection drops or being disconnected. */ void disconnected(); /// emitted when fails to connect to a HTTP server. @sa setConnectingTimeOut() void connectingTimeOut(); protected: /** called when a new HTTP connection is established. * you can overload this method, the default implementaion only emits connected(). * @param req use this request instance for assinging the * request headers and sending optional body. * @see httpConnected(QHttpRequest*) */ virtual void onRequestReady(QHttpRequest* req); /** called when a new response is received from the server. * you can overload this method, the default implementaion only emits newResponse(). * @param res use this instance for reading incoming response. * @see newResponse(QHttpResponse*) */ virtual void onResponseReady(QHttpResponse* res); protected: explicit QHttpClient(QHttpClientPrivate&, QObject*); void timerEvent(QTimerEvent*) override; Q_DECLARE_PRIVATE(QHttpClient) Q_DISABLE_COPY(QHttpClient) QScopedPointer d_ptr; }; /////////////////////////////////////////////////////////////////////////////// } // namespace client } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// #endif // QHTTP_HAS_CLIENT #endif // define QHTTPCLIENT_HPP psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/qhttpclientrequest.cpp000066400000000000000000000045551370065651000257370ustar00rootroot00000000000000#include "private/qhttpclientrequest_private.hpp" #include "qhttpclient.hpp" /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace client { /////////////////////////////////////////////////////////////////////////////// QHttpRequest::QHttpRequest(QHttpClient* cli) : QHttpAbstractOutput(cli) , d_ptr(new QHttpRequestPrivate(cli, this)) { d_ptr->initialize(); QHTTP_LINE_LOG } QHttpRequest::QHttpRequest(QHttpRequestPrivate& dd, QHttpClient* cli) : QHttpAbstractOutput(cli) , d_ptr(&dd) { d_ptr->initialize(); QHTTP_LINE_LOG } QHttpRequest::~QHttpRequest() { QHTTP_LINE_LOG } void QHttpRequest::setVersion(const QString &versionString) { d_func()->iversion = versionString; } void QHttpRequest::addHeader(const QByteArray &field, const QByteArray &value) { d_func()->addHeader(field, value); } THeaderHash& QHttpRequest::headers() { return d_func()->iheaders; } void QHttpRequest::write(const QByteArray &data) { d_func()->writeData(data); } void QHttpRequest::end(const QByteArray &data) { Q_D(QHttpRequest); if ( d->endPacket(data) ) emit done(!d->ikeepAlive); } QHttpClient* QHttpRequest::connection() const { return d_func()->iclient; } /////////////////////////////////////////////////////////////////////////////// QByteArray QHttpRequestPrivate::makeTitle() { QByteArray title; title.reserve(512); title.append(qhttp::Stringify::toString(imethod)) .append(" "); QByteArray path = iurl.path(QUrl::FullyEncoded).toLatin1(); if ( path.size() == 0 ) path = "/"; title.append(path); if ( iurl.hasQuery() ) title.append("?").append(iurl.query(QUrl::FullyEncoded).toLatin1()); title.append(" HTTP/") .append(iversion.toLatin1()) .append("\r\n"); return title; } void QHttpRequestPrivate::prepareHeadersToWrite() { if ( !iheaders.contains("host") ) { quint16 port = iurl.port(); if ( port == 0 ) port = 80; iheaders.insert("host", QString("%1:%2").arg(iurl.host()).arg(port).toLatin1() ); } } /////////////////////////////////////////////////////////////////////////////// } // namespace client } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/qhttpclientrequest.hpp000066400000000000000000000040501370065651000257320ustar00rootroot00000000000000/** HTTP request from a client. * https://github.com/azadkuh/qhttp * * @author amir zamani * @version 2.0.0 * @date 2014-07-11 */ #ifndef QHTTPCLIENT_REQUEST_HPP #define QHTTPCLIENT_REQUEST_HPP // configured by src.pro #if defined(QHTTP_HAS_CLIENT) /////////////////////////////////////////////////////////////////////////////// #include "qhttpabstracts.hpp" #include /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace client { /////////////////////////////////////////////////////////////////////////////// /** a class for building a new HTTP request. * the life cycle of this class and the memory management is handled by QHttpClient. * @sa QHttpClient */ class QHTTP_API QHttpRequest : public QHttpAbstractOutput { Q_OBJECT public: virtual ~QHttpRequest(); public: // QHttpAbstractOutput methods: /** @see QHttpAbstractOutput::setVersion(). */ void setVersion(const QString& versionString) override; /** @see QHttpAbstractOutput::addHeader(). */ void addHeader(const QByteArray& field, const QByteArray& value) override; /** @see QHttpAbstractOutput::headers(). */ THeaderHash& headers() override; /** @see QHttpAbstractOutput::write(). */ void write(const QByteArray &data) override; /** @see QHttpAbstractOutput::end(). */ void end(const QByteArray &data = QByteArray()) override; public: /** returns parent QHttpClient object. */ QHttpClient* connection() const; protected: explicit QHttpRequest(QHttpClient*); explicit QHttpRequest(QHttpRequestPrivate&, QHttpClient*); friend class QHttpClient; Q_DECLARE_PRIVATE(QHttpRequest) QScopedPointer d_ptr; }; /////////////////////////////////////////////////////////////////////////////// } // namespace client } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// #endif // QHTTP_HAS_CLIENT #endif // define QHTTPCLIENT_REQUEST_HPP psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/qhttpclientresponse.cpp000066400000000000000000000030271370065651000260760ustar00rootroot00000000000000#include "private/qhttpclientresponse_private.hpp" #include "qhttpclient.hpp" /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace client { /////////////////////////////////////////////////////////////////////////////// QHttpResponse::QHttpResponse(QHttpClient *cli) : QHttpAbstractInput(cli), d_ptr(new QHttpResponsePrivate(cli, this)) { d_ptr->initialize(); QHTTP_LINE_LOG } QHttpResponse::QHttpResponse(QHttpResponsePrivate &dd, QHttpClient *cli) : QHttpAbstractInput(cli), d_ptr(&dd) { d_ptr->initialize(); QHTTP_LINE_LOG } QHttpResponse::~QHttpResponse() { QHTTP_LINE_LOG } TStatusCode QHttpResponse::status() const { return d_func()->istatus; } const QString& QHttpResponse::statusString() const { return d_func()->icustomStatusMessage; } const QString& QHttpResponse::httpVersion() const { return d_func()->iversion; } const THeaderHash& QHttpResponse::headers() const { return d_func()->iheaders; } bool QHttpResponse::isSuccessful() const { return d_func()->isuccessful; } void QHttpResponse::collectData(int atMost) { d_func()->collectData(atMost); } const QByteArray& QHttpResponse::collectedData() const { return d_func()->icollectedData; } QHttpClient* QHttpResponse::connection() const { return d_func()->iclient; } /////////////////////////////////////////////////////////////////////////////// } // namespace client } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/qhttpclientresponse.hpp000066400000000000000000000046021370065651000261030ustar00rootroot00000000000000/** HTTP response received by client. * https://github.com/azadkuh/qhttp * * @author amir zamani * @version 2.0.0 * @date 2014-07-11 */ #ifndef QHTTPCLIENT_RESPONSE_HPP #define QHTTPCLIENT_RESPONSE_HPP // configured by src.pro #if defined(QHTTP_HAS_CLIENT) /////////////////////////////////////////////////////////////////////////////// #include "qhttpabstracts.hpp" #include /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace client { /////////////////////////////////////////////////////////////////////////////// /** a class for reading incoming HTTP response from a server. * the life cycle of this class and the memory management is handled by QHttpClient. * @sa QHttpClient */ class QHTTP_API QHttpResponse : public QHttpAbstractInput { Q_OBJECT public: virtual ~QHttpResponse(); public: // QHttpAbstractInput methods: /** @see QHttpAbstractInput::headers(). */ const THeaderHash& headers() const override; /** @see QHttpAbstractInput::httpVersion(). */ const QString& httpVersion() const override; /** @see QHttpAbstractInput::isSuccessful(). */ bool isSuccessful() const override; /** @see QHttpAbstractInput::collectData(). */ void collectData(int atMost = -1) override; /** @see QHttpAbstractInput::collectedData(). */ const QByteArray& collectedData()const override; public: /** The status code of this response. */ TStatusCode status() const ; /** The server status message as string. * may be slightly different than: @code qhttp::Stringify::toString(status()); @endcode * depending on implementation of HTTP server. */ const QString& statusString() const; /** returns parent QHttpClient object. */ QHttpClient* connection() const; protected: explicit QHttpResponse(QHttpClient*); explicit QHttpResponse(QHttpResponsePrivate&, QHttpClient*); friend class QHttpClientPrivate; Q_DECLARE_PRIVATE(QHttpResponse) QScopedPointer d_ptr; }; /////////////////////////////////////////////////////////////////////////////// } // namespace client } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// #endif // QHTTP_HAS_CLIENT #endif // define QHTTPCLIENT_RESPONSE_HPP psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/qhttpfwd.hpp000077500000000000000000000177241370065651000236420ustar00rootroot00000000000000/** forward declarations and general definitions of the QHttp. * https://github.com/azadkuh/qhttp * * @author amir zamani * @version 2.0.0 * @date 2014-07-11 */ #ifndef QHTTPFWD_HPP #define QHTTPFWD_HPP /////////////////////////////////////////////////////////////////////////////// #include #include #include #include /////////////////////////////////////////////////////////////////////////////// // Qt class QTcpServer; class QTcpSocket; class QLocalServer; class QLocalSocket; // http_parser struct http_parser_settings; struct http_parser; /////////////////////////////////////////////////////////////////////////////// namespace qhttp { /////////////////////////////////////////////////////////////////////////////// /// QHash/QMap iterators are incompatibility with range for template void for_each(Iterator first, Iterator last, Func&& f) { while ( first != last ) { f( first ); ++first; } } /** A map of request or response headers. */ class THeaderHash : public QHash { public: /** checks for a header item, regardless of the case of the characters. */ bool has(const QByteArray& key) const { return contains(key.toLower()); } /** checks if a header has the specified value ignoring the case of the characters. */ bool keyHasValue(const QByteArray& key, const QByteArray& value) const { if ( !contains(key) ) return false; const QByteArray& v = QHash::value(key); return qstrnicmp(value.constData(), v.constData(), v.size()) == 0; } template void forEach(Func&& f) const { for_each(constBegin(), constEnd(), f); } }; /** Request method enumeration. * @note Taken from http_parser.h */ enum THttpMethod { EHTTP_DELETE = 0, ///< DELETE EHTTP_GET = 1, ///< GET EHTTP_HEAD = 2, ///< HEAD EHTTP_POST = 3, ///< POST EHTTP_PUT = 4, ///< PUT /* pathological */ EHTTP_CONNECT = 5, ///< CONNECT EHTTP_OPTIONS = 6, ///< OPTIONS EHTTP_TRACE = 7, ///< TRACE /* webdav */ EHTTP_COPY = 8, ///< COPY EHTTP_LOCK = 9, ///< LOCK EHTTP_MKCOL = 10, ///< MKCOL EHTTP_MOVE = 11, ///< MOVE EHTTP_PROPFIND = 12, ///< PROPFIND EHTTP_PROPPATCH = 13, ///< PROPPATCH EHTTP_SEARCH = 14, ///< SEARCH EHTTP_UNLOCK = 15, ///< UNLOCK EHTTP_BIND = 16, ///< BIND EHTTP_REBIND = 17, ///< REBIND EHTTP_UNBIND = 18, ///< UNBIND EHTTP_ACL = 19, ///< ACL /* subversion */ EHTTP_REPORT = 20, ///< REPORT EHTTP_MKACTIVITY = 21, ///< MKACTIVITY EHTTP_CHECKOUT = 22, ///< CHECKOUT EHTTP_MERGE = 23, ///< MERGE /* upnp */ EHTTP_MSEARCH = 24, ///< M-SEARCH EHTTP_NOTIFY = 25, ///< NOTIFY EHTTP_SUBSCRIBE = 26, ///< SUBSCRIBE EHTTP_UNSUBSCRIBE = 27, ///< UNSUBSCRIBE /* RFC-5789 */ EHTTP_PATCH = 28, ///< PATCH EHTTP_PURGE = 29, ///< PURGE /* CalDAV */ EHTTP_MKCALENDAR = 30, ///< MKCALENDAR /* RFC-2068, section 19.6.1.2 */ EHTTP_LINK = 31, ///< LINK EHTTP_UNLINK = 32, ///< UNLINK }; /** HTTP status codes. */ enum TStatusCode { ESTATUS_CONTINUE = 100, ESTATUS_SWITCH_PROTOCOLS = 101, ESTATUS_OK = 200, ESTATUS_CREATED = 201, ESTATUS_ACCEPTED = 202, ESTATUS_NON_AUTHORITATIVE_INFORMATION = 203, ESTATUS_NO_CONTENT = 204, ESTATUS_RESET_CONTENT = 205, ESTATUS_PARTIAL_CONTENT = 206, ESTATUS_MULTI_STATUS = 207, ESTATUS_MULTIPLE_CHOICES = 300, ESTATUS_MOVED_PERMANENTLY = 301, ESTATUS_FOUND = 302, ESTATUS_SEE_OTHER = 303, ESTATUS_NOT_MODIFIED = 304, ESTATUS_USE_PROXY = 305, ESTATUS_TEMPORARY_REDIRECT = 307, ESTATUS_BAD_REQUEST = 400, ESTATUS_UNAUTHORIZED = 401, ESTATUS_PAYMENT_REQUIRED = 402, ESTATUS_FORBIDDEN = 403, ESTATUS_NOT_FOUND = 404, ESTATUS_METHOD_NOT_ALLOWED = 405, ESTATUS_NOT_ACCEPTABLE = 406, ESTATUS_PROXY_AUTHENTICATION_REQUIRED = 407, ESTATUS_REQUEST_TIMEOUT = 408, ESTATUS_CONFLICT = 409, ESTATUS_GONE = 410, ESTATUS_LENGTH_REQUIRED = 411, ESTATUS_PRECONDITION_FAILED = 412, ESTATUS_REQUEST_ENTITY_TOO_LARGE = 413, ESTATUS_REQUEST_URI_TOO_LONG = 414, ESTATUS_REQUEST_UNSUPPORTED_MEDIA_TYPE = 415, ESTATUS_REQUESTED_RANGE_NOT_SATISFIABLE = 416, ESTATUS_EXPECTATION_FAILED = 417, ESTATUS_I_AM_A_TEAPOT = 418, ESTATUS_INTERNAL_SERVER_ERROR = 500, ESTATUS_NOT_IMPLEMENTED = 501, ESTATUS_BAD_GATEWAY = 502, ESTATUS_SERVICE_UNAVAILABLE = 503, ESTATUS_GATEWAY_TIMEOUT = 504, ESTATUS_HTTP_VERSION_NOT_SUPPORTED = 505 }; /** The backend of QHttp library. */ enum TBackend { ETcpSocket = 0, ///< client / server work on top of TCP/IP stack. (default) ESslSocket = 1, ///< client / server work on SSL/TLS tcp stack. (not implemented yet) ELocalSocket = 2 ///< client / server work on local socket (unix socket). }; /////////////////////////////////////////////////////////////////////////////// namespace server { /////////////////////////////////////////////////////////////////////////////// class QHttpServer; class QHttpConnection; class QHttpRequest; class QHttpResponse; // Privte classes class QHttpServerPrivate; class QHttpConnectionPrivate; class QHttpRequestPrivate; class QHttpResponsePrivate; using TServerHandler = std::function; /////////////////////////////////////////////////////////////////////////////// } // namespace server /////////////////////////////////////////////////////////////////////////////// namespace client { /////////////////////////////////////////////////////////////////////////////// class QHttpClient; class QHttpRequest; class QHttpResponse; // Private classes class QHttpClientPrivate; class QHttpRequestPrivate; class QHttpResponsePrivate; /////////////////////////////////////////////////////////////////////////////// } // namespace client /////////////////////////////////////////////////////////////////////////////// #ifdef Q_OS_WIN # if defined(QHTTP_EXPORT) # define QHTTP_API __declspec(dllexport) # else # define QHTTP_API __declspec(dllimport) # endif #else # define QHTTP_API #endif #if QHTTP_MEMORY_LOG > 0 # define QHTTP_LINE_LOG fprintf(stderr, "%s(): obj = %p @ %s[%d]\n",\ __FUNCTION__, this, __FILE__, __LINE__); #else # define QHTTP_LINE_LOG #endif #if QHTTP_MEMORY_LOG > 1 # define QHTTP_LINE_DEEPLOG QHTTP_LINE_LOG #else # define QHTTP_LINE_DEEPLOG #endif /////////////////////////////////////////////////////////////////////////////// } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// #endif // define QHTTPFWD_HPP psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/qhttpserver.cpp000066400000000000000000000056321370065651000243530ustar00rootroot00000000000000#include "private/qhttpserver_private.hpp" /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace server { /////////////////////////////////////////////////////////////////////////////// QHttpServer::QHttpServer(QObject *parent) : QObject(parent), d_ptr(new QHttpServerPrivate) { } QHttpServer::QHttpServer(QHttpServerPrivate &dd, QObject *parent) : QObject(parent), d_ptr(&dd) { } QHttpServer::~QHttpServer() { stopListening(); } bool QHttpServer::listen(const QString &socketOrPort, const TServerHandler &handler) { Q_D(QHttpServer); bool isNumber = false; quint16 tcpPort = socketOrPort.toUShort(&isNumber); if ( isNumber && tcpPort > 0 ) return listen(QHostAddress::Any, tcpPort, handler); d->initialize(ELocalSocket, this); d->ihandler = handler; return d->ilocalServer->listen(socketOrPort); } bool QHttpServer::listen(const QHostAddress& address, quint16 port, const qhttp::server::TServerHandler& handler) { Q_D(QHttpServer); d->initialize(ETcpSocket, this); d->ihandler = handler; return d->itcpServer->listen(address, port); } bool QHttpServer::isListening() const { const Q_D(QHttpServer); if ( d->ibackend == ETcpSocket && d->itcpServer ) return d->itcpServer->isListening(); else if ( d->ibackend == ELocalSocket && d->ilocalServer ) return d->ilocalServer->isListening(); return false; } void QHttpServer::stopListening() { Q_D(QHttpServer); if ( d->itcpServer ) d->itcpServer->close(); if ( d->ilocalServer ) { d->ilocalServer->close(); QLocalServer::removeServer( d->ilocalServer->fullServerName() ); } } quint32 QHttpServer::timeOut() const { return d_func()->itimeOut; } void QHttpServer::setTimeOut(quint32 newValue) { d_func()->itimeOut = newValue; } TBackend QHttpServer::backendType() const { return d_func()->ibackend; } QTcpServer* QHttpServer::tcpServer() const { return d_func()->itcpServer.data(); } QLocalServer* QHttpServer::localServer() const { return d_func()->ilocalServer.data(); } void QHttpServer::incomingConnection(qintptr handle) { QHttpConnection* conn = new QHttpConnection(this); conn->setSocketDescriptor(handle, backendType()); conn->setTimeOut(d_func()->itimeOut); emit newConnection(conn); Q_D(QHttpServer); if ( d->ihandler ) QObject::connect(conn, &QHttpConnection::newRequest, d->ihandler); else incomingConnection(conn); } void QHttpServer::incomingConnection(QHttpConnection *connection) { QObject::connect(connection, &QHttpConnection::newRequest, this, &QHttpServer::newRequest); } /////////////////////////////////////////////////////////////////////////////// } // namespace server } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/qhttpserver.hpp000066400000000000000000000113311370065651000243510ustar00rootroot00000000000000/** HTTP server class. * https://github.com/azadkuh/qhttp * * @author amir zamani * @version 2.0.0 * @date 2014-07-11 */ #ifndef QHTTPSERVER_HPP #define QHTTPSERVER_HPP /////////////////////////////////////////////////////////////////////////////// #include "qhttpfwd.hpp" #include #include /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace server { /////////////////////////////////////////////////////////////////////////////// /** The QHttpServer class is a fast, async (non-blocking) HTTP server. */ class QHTTP_API QHttpServer : public QObject { Q_OBJECT Q_PROPERTY(quint32 timeOut READ timeOut WRITE setTimeOut) public: /** construct a new HTTP Server. */ explicit QHttpServer(QObject *parent = nullptr); virtual ~QHttpServer(); /** starts a TCP or Local (unix domain socket) server. * if you provide a server handler, the newRequest() signal won't be emitted. * * @param socketOrPort could be a tcp port number as "8080" or a unix socket name as * "/tmp/sample.socket" or "sample.socket". * @param handler optional server handler (a lambda, std::function, ...) * @return false if listening fails. */ bool listen(const QString& socketOrPort, const TServerHandler& handler = nullptr); /** starts a TCP server on specified address and port. * if you provide a server handler, the newRequest() signal won't be emitted. * * @param address listening address as QHostAddress::Any. * @param port listening port. * @param handler optional server handler (a lambda, std::function, ...) * @return false if listening fails. */ bool listen(const QHostAddress& address, quint16 port, const TServerHandler& handler = nullptr); /** @overload listen() */ bool listen(quint16 port) { return listen(QHostAddress::Any, port); } /** returns true if server successfully listens. @sa listen() */ bool isListening() const; /** closes the server and stops from listening. */ void stopListening(); /** returns timeout value [mSec] for open connections (sockets). * @sa setTimeOut(). */ quint32 timeOut()const; /** set time-out for new open connections in miliseconds [mSec]. * new incoming connections will be forcefully closed after this time out. * a zero (0) value disables timer for new connections. */ void setTimeOut(quint32); /** returns the QHttpServer's backend type. */ TBackend backendType() const; signals: /** emitted when a client makes a new request to the server if you do not override * incomingConnection(QHttpConnection *connection); * @sa incomingConnection(). */ void newRequest(QHttpRequest *request, QHttpResponse *response); /** emitted when a new connection comes to the server if you do not override * incomingConnection(QHttpConnection *connection); * @sa incomingConnection(); */ void newConnection(QHttpConnection* connection); protected: /** returns the tcp server instance if the backend() == ETcpSocket. */ QTcpServer* tcpServer() const; /** returns the local server instance if the backend() == ELocalSocket. */ QLocalServer* localServer() const; /** is called when server accepts a new connection. * you can override this function for using a thread-pool or ... some other reasons. * * the default implementation just connects QHttpConnection::newRequest signal. * @note if you override this method, the signal won't be emitted by QHttpServer. * (perhaps, you do not need it anymore). * * @param connection New incoming connection. */ virtual void incomingConnection(QHttpConnection* connection); /** overrides QTcpServer::incomingConnection() to make a new QHttpConnection. * override this function if you like to create your derived QHttpConnection instances. * * @note if you override this method, incomingConnection(QHttpConnection*) or * newRequest(QHttpRequest *, QHttpResponse *) signal won't be called. * * @see example/benchmark/server.cpp to see how to override. */ virtual void incomingConnection(qintptr handle); private: explicit QHttpServer(QHttpServerPrivate&, QObject *parent); Q_DECLARE_PRIVATE(QHttpServer) Q_DISABLE_COPY(QHttpServer) QScopedPointer d_ptr; }; /////////////////////////////////////////////////////////////////////////////// } // namespace server } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// #endif // define QHTTPSERVER_HPP psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/qhttpserverconnection.cpp000066400000000000000000000136121370065651000264300ustar00rootroot00000000000000#include "private/qhttpserverconnection_private.hpp" /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace server { /////////////////////////////////////////////////////////////////////////////// QHttpConnection::QHttpConnection(QObject *parent) : QObject(parent), d_ptr(new QHttpConnectionPrivate(this)) { QHTTP_LINE_LOG } QHttpConnection::QHttpConnection(QHttpConnectionPrivate& dd, QObject* parent) : QObject(parent), d_ptr(&dd) { QHTTP_LINE_LOG } void QHttpConnection::setSocketDescriptor(qintptr sokDescriptor, TBackend backendType) { d_ptr->createSocket(sokDescriptor, backendType); } QHttpConnection::~QHttpConnection() { QHTTP_LINE_LOG } void QHttpConnection::setTimeOut(quint32 miliSeconds) { if ( miliSeconds != 0 ) { d_func()->itimeOut = miliSeconds; d_func()->itimer.start(miliSeconds, Qt::CoarseTimer, this); } } void QHttpConnection::killConnection() { d_func()->isocket.close(); } TBackend QHttpConnection::backendType() const { return d_func()->isocket.ibackendType; } QTcpSocket* QHttpConnection::tcpSocket() const { return d_func()->isocket.itcpSocket; } QLocalSocket* QHttpConnection::localSocket() const { return d_func()->isocket.ilocalSocket; } void QHttpConnection::onHandler(const TServerHandler &handler) { d_func()->ihandler = handler; } void QHttpConnection::timerEvent(QTimerEvent *) { killConnection(); } /////////////////////////////////////////////////////////////////////////////// // if user closes the connection, ends the response or by any other reason the // socket disconnects, then the irequest and iresponse instances may have // been deleted. In these situations reading more http body or emitting end() // for incoming request are not possible: // if ( ilastRequest == nullptr ) // return 0; int QHttpConnectionPrivate::messageBegin(http_parser*) { itempUrl.clear(); itempUrl.reserve(128); if ( ilastRequest ) ilastRequest->deleteLater(); ilastRequest = new QHttpRequest(q_func()); return 0; } int QHttpConnectionPrivate::url(http_parser*, const char* at, size_t length) { Q_ASSERT(ilastRequest); itempUrl.append(at, length); return 0; } int QHttpConnectionPrivate::headerField(http_parser*, const char* at, size_t length) { if ( ilastRequest == nullptr ) return 0; // insert the header we parsed previously // into the header map if ( !itempHeaderField.isEmpty() && !itempHeaderValue.isEmpty() ) { // header names are always lower-cased ilastRequest->d_func()->iheaders.insert( itempHeaderField.toLower(), itempHeaderValue.toLower() ); // clear header value. this sets up a nice // feedback loop where the next time // HeaderValue is called, it can simply append itempHeaderField.clear(); itempHeaderValue.clear(); } itempHeaderField.append(at, length); return 0; } int QHttpConnectionPrivate::headerValue(http_parser*, const char* at, size_t length) { if ( ilastRequest == nullptr ) return 0; itempHeaderValue.append(at, length); return 0; } int QHttpConnectionPrivate::headersComplete(http_parser* parser) { if ( ilastRequest == nullptr ) return 0; ilastRequest->d_func()->iurl = QUrl(itempUrl); // set method ilastRequest->d_func()->imethod = static_cast(parser->method); // set version ilastRequest->d_func()->iversion = QString("%1.%2") .arg(parser->http_major) .arg(parser->http_minor); // Insert last remaining header ilastRequest->d_func()->iheaders.insert( itempHeaderField.toLower(), itempHeaderValue.toLower() ); // set client information if ( isocket.ibackendType == ETcpSocket ) { ilastRequest->d_func()->iremoteAddress = isocket.itcpSocket->peerAddress().toString(); ilastRequest->d_func()->iremotePort = isocket.itcpSocket->peerPort(); } else if ( isocket.ibackendType == ELocalSocket ) { ilastRequest->d_func()->iremoteAddress = isocket.ilocalSocket->fullServerName(); ilastRequest->d_func()->iremotePort = 0; // not used in local sockets } if ( ilastResponse ) ilastResponse->deleteLater(); ilastResponse = new QHttpResponse(q_func()); if ( parser->http_major < 1 || parser->http_minor < 1 ) ilastResponse->d_func()->ikeepAlive = false; // close the connection if response was the last packet QObject::connect(ilastResponse, &QHttpResponse::done, [this](bool wasTheLastPacket){ ikeepAlive = !wasTheLastPacket; if ( wasTheLastPacket ) { isocket.flush(); isocket.close(); } }); // we are good to go! if ( ihandler ) ihandler(ilastRequest, ilastResponse); else emit q_ptr->newRequest(ilastRequest, ilastResponse); return 0; } int QHttpConnectionPrivate::body(http_parser*, const char* at, size_t length) { if ( ilastRequest == nullptr ) return 0; ilastRequest->d_func()->ireadState = QHttpRequestPrivate::EPartial; if ( ilastRequest->d_func()->icollectRequired ) { if ( !ilastRequest->d_func()->append(at, length) ) { // forcefully dispatch the ilastRequest finalizeConnection(); } return 0; } emit ilastRequest->data(QByteArray(at, length)); return 0; } int QHttpConnectionPrivate::messageComplete(http_parser*) { if ( ilastRequest == nullptr ) return 0; // request is done finalizeConnection(); return 0; } /////////////////////////////////////////////////////////////////////////////// } // namespace server } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/qhttpserverconnection.hpp000066400000000000000000000055751370065651000264460ustar00rootroot00000000000000/** HTTP connection class. * https://github.com/azadkuh/qhttp * * @author amir zamani * @version 2.0.0 * @date 2014-07-11 */ #ifndef QHTTPSERVER_CONNECTION_HPP #define QHTTPSERVER_CONNECTION_HPP /////////////////////////////////////////////////////////////////////////////// #include "qhttpfwd.hpp" #include /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace server { /////////////////////////////////////////////////////////////////////////////// /** a HTTP connection in the server. * this class controls the HTTP connetion and handles life cycle and the memory management * of QHttpRequest and QHttpResponse instances autoamtically. */ class QHTTP_API QHttpConnection : public QObject { Q_OBJECT public: virtual ~QHttpConnection(); /** set an optional timer event to close the connection. */ void setTimeOut(quint32 miliSeconds); /** forcefully kills (closes) a connection. */ void killConnection(); /** optionally set a handler for connection class. * @note if you set this handler, the newRequest() signal won't be emitted. */ void onHandler(const TServerHandler& handler); /** returns the backend type of the connection. */ TBackend backendType() const; /** returns connected socket if the backend() == ETcpSocket. */ QTcpSocket* tcpSocket() const; /** returns connected socket if the backend() == ELocalSocket. */ QLocalSocket* localSocket() const; /** creates a new QHttpConnection based on arguments. */ static QHttpConnection* create(qintptr sokDescriptor, TBackend backendType, QObject* parent) { QHttpConnection* conn = new QHttpConnection(parent); conn->setSocketDescriptor(sokDescriptor, backendType); return conn; } signals: /** emitted when a pair of HTTP request and response are ready to interact. * @param req incoming request by the client. * @param res outgoing response to the client. */ void newRequest(QHttpRequest* req, QHttpResponse* res); /** emitted when the tcp/local socket, disconnects. */ void disconnected(); protected: explicit QHttpConnection(QObject *parent); explicit QHttpConnection(QHttpConnectionPrivate&, QObject *); void setSocketDescriptor(qintptr sokDescriptor, TBackend backendType); void timerEvent(QTimerEvent*) override; Q_DISABLE_COPY(QHttpConnection) Q_DECLARE_PRIVATE(QHttpConnection) QScopedPointer d_ptr; friend class QHttpServer; }; /////////////////////////////////////////////////////////////////////////////// } // namespace server } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// #endif // #define QHTTPSERVER_CONNECTION_HPP psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/qhttpserverrequest.cpp000066400000000000000000000034161370065651000257620ustar00rootroot00000000000000#include "private/qhttpserverrequest_private.hpp" /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace server { /////////////////////////////////////////////////////////////////////////////// QHttpRequest::QHttpRequest(QHttpConnection *conn) : QHttpAbstractInput(conn), d_ptr(new QHttpRequestPrivate(conn, this)) { d_ptr->initialize(); QHTTP_LINE_LOG } QHttpRequest::QHttpRequest(QHttpRequestPrivate &dd, QHttpConnection *conn) : QHttpAbstractInput(conn), d_ptr(&dd) { d_ptr->initialize(); QHTTP_LINE_LOG } QHttpRequest::~QHttpRequest() { QHTTP_LINE_LOG } THttpMethod QHttpRequest::method() const { return d_func()->imethod; } const QString QHttpRequest::methodString() const { return http_method_str(static_cast(d_func()->imethod)); } const QUrl& QHttpRequest::url() const { return d_func()->iurl; } const QString& QHttpRequest::httpVersion() const { return d_func()->iversion; } const THeaderHash& QHttpRequest::headers() const { return d_func()->iheaders; } const QString& QHttpRequest::remoteAddress() const { return d_func()->iremoteAddress; } quint16 QHttpRequest::remotePort() const { return d_func()->iremotePort; } bool QHttpRequest::isSuccessful() const { return d_func()->isuccessful; } void QHttpRequest::collectData(int atMost) { d_func()->collectData(atMost); } const QByteArray& QHttpRequest::collectedData() const { return d_func()->icollectedData; } QHttpConnection* QHttpRequest::connection() const { return d_ptr->iconnection; } /////////////////////////////////////////////////////////////////////////////// } // namespace server } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/qhttpserverrequest.hpp000066400000000000000000000051241370065651000257650ustar00rootroot00000000000000/** HTTP request which is received by the server. * https://github.com/azadkuh/qhttp * * @author amir zamani * @version 2.0.0 * @date 2014-07-11 */ #ifndef QHTTPSERVER_REQUEST_HPP #define QHTTPSERVER_REQUEST_HPP /////////////////////////////////////////////////////////////////////////////// #include "qhttpabstracts.hpp" #include /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace server { /////////////////////////////////////////////////////////////////////////////// /** The QHttpRequest class represents the header and body data sent by the client. * The class is read-only. * @sa QHttpConnection */ class QHTTP_API QHttpRequest : public QHttpAbstractInput { Q_OBJECT public: virtual ~QHttpRequest(); public: // QHttpAbstractInput methods: /** @see QHttpAbstractInput::headers(). */ const THeaderHash& headers() const override; /** @see QHttpAbstractInput::httpVersion(). */ const QString& httpVersion() const override; /** @see QHttpAbstractInput::isSuccessful(). */ bool isSuccessful() const override; /** @see QHttpAbstractInput::collectData(). */ void collectData(int atMost = -1) override; /** @see QHttpAbstractInput::collectedData(). */ const QByteArray& collectedData()const override; public: /** The method used for the request. */ THttpMethod method() const ; /** Returns the method string for the request. * @note This will plainly transform the enum into a string HTTP_GET -> "HTTP_GET". */ const QString methodString() const; /** The complete URL for the request. * This includes the path and query string. @sa path(). */ const QUrl& url() const; /** IP Address of the client in dotted decimal format. */ const QString& remoteAddress() const; /** Outbound connection port for the client. */ quint16 remotePort() const; /** returns the parent QHttpConnection object. */ QHttpConnection* connection() const; protected: explicit QHttpRequest(QHttpConnection*); explicit QHttpRequest(QHttpRequestPrivate&, QHttpConnection*); friend class QHttpConnectionPrivate; Q_DECLARE_PRIVATE(QHttpRequest) QScopedPointer d_ptr; }; /////////////////////////////////////////////////////////////////////////////// } // namespace server } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// #endif // define QHTTPSERVER_REQUEST_HPP psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/qhttpserverresponse.cpp000066400000000000000000000044611370065651000261310ustar00rootroot00000000000000#include "private/qhttpserverresponse_private.hpp" /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace server { /////////////////////////////////////////////////////////////////////////////// QHttpResponse::QHttpResponse(QHttpConnection* conn) : QHttpAbstractOutput(conn) , d_ptr(new QHttpResponsePrivate(conn, this)) { d_ptr->initialize(); QHTTP_LINE_LOG } QHttpResponse::QHttpResponse(QHttpResponsePrivate& dd, QHttpConnection* conn) : QHttpAbstractOutput(conn) , d_ptr(&dd) { d_ptr->initialize(); QHTTP_LINE_LOG } QHttpResponse::~QHttpResponse() { QHTTP_LINE_LOG } void QHttpResponse::setStatusCode(TStatusCode code) { d_func()->istatus = code; } void QHttpResponse::setVersion(const QString &versionString) { d_func()->iversion = versionString; } void QHttpResponse::addHeader(const QByteArray &field, const QByteArray &value) { d_func()->addHeader(field, value); } THeaderHash& QHttpResponse::headers() { return d_func()->iheaders; } void QHttpResponse::write(const QByteArray &data) { d_func()->writeData(data); } void QHttpResponse::end(const QByteArray &data) { Q_D(QHttpResponse); if ( d->endPacket(data) ) emit done(!d->ikeepAlive); } QHttpConnection* QHttpResponse::connection() const { return d_func()->iconnection; } /////////////////////////////////////////////////////////////////////////////// QByteArray QHttpResponsePrivate::makeTitle() { QString title = QString("HTTP/%1 %2 %3\r\n") .arg(iversion) .arg(istatus) .arg(Stringify::toString(istatus)); return title.toLatin1(); } void QHttpResponsePrivate::prepareHeadersToWrite() { if ( !iheaders.contains("date") ) { // Sun, 06 Nov 1994 08:49:37 GMT - RFC 822. Use QLocale::c() so english is used for month and // day. QString dateString = QLocale::c(). toString(QDateTime::currentDateTimeUtc(), "ddd, dd MMM yyyy hh:mm:ss") .append(" GMT"); addHeader("date", dateString.toLatin1()); } } /////////////////////////////////////////////////////////////////////////////// } // namespace server } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/qhttpserverresponse.hpp000066400000000000000000000042221370065651000261310ustar00rootroot00000000000000/** HTTP response from a server. * https://github.com/azadkuh/qhttp * * @author amir zamani * @version 2.0.0 * @date 2014-07-11 */ #ifndef QHTTPSERVER_RESPONSE_HPP #define QHTTPSERVER_RESPONSE_HPP /////////////////////////////////////////////////////////////////////////////// #include "qhttpabstracts.hpp" /////////////////////////////////////////////////////////////////////////////// namespace qhttp { namespace server { /////////////////////////////////////////////////////////////////////////////// /** The QHttpResponse class handles sending data back to the client as a response to a request. * @sa QHttpConnection */ class QHTTP_API QHttpResponse : public QHttpAbstractOutput { Q_OBJECT public: virtual ~QHttpResponse(); public: /** set the response HTTP status code. @sa TStatusCode. * default value is ESTATUS_BAD_REQUEST. * @sa write() */ void setStatusCode(TStatusCode code); public: // QHttpAbstractOutput methods: /** @see QHttpAbstractOutput::setVersion(). */ void setVersion(const QString& versionString) override; /** @see QHttpAbstractOutput::addHeader(). */ void addHeader(const QByteArray& field, const QByteArray& value) override; /** @see QHttpAbstractOutput::headers(). */ THeaderHash& headers() override; /** @see QHttpAbstractOutput::write(). */ void write(const QByteArray &data) override; /** @see QHttpAbstractOutput::end(). */ void end(const QByteArray &data = QByteArray()) override; public: /** returns the parent QHttpConnection object. */ QHttpConnection* connection() const; protected: explicit QHttpResponse(QHttpConnection*); explicit QHttpResponse(QHttpResponsePrivate&, QHttpConnection*); friend class QHttpConnectionPrivate; Q_DECLARE_PRIVATE(QHttpResponse) QScopedPointer d_ptr; }; /////////////////////////////////////////////////////////////////////////////// } // namespace server } // namespace qhttp /////////////////////////////////////////////////////////////////////////////// #endif // define QHTTPSERVER_RESPONSE_HPP psi-plus-snapshots-1.4.1456/3rdparty/qhttp/src/src.pro000066400000000000000000000036741370065651000225750ustar00rootroot00000000000000DEPENDPATH += $$PWD INCLUDEPATH += $$PWD $$PWD/. $$PWD/.. include($$PWD/../vendor/vendor.pri) PRJDIR = .. include($$PRJDIR/commondir.pri) $$setLibPath() VENDORNAME=oliviermaridat APPNAME=qhttp TARGET = $$getLibName($$APPNAME, "Qt") TEMPLATE = lib CONFIG += staticlib #CONFIG += debug_and_release build_all QT += network QT -= gui CONFIG += c++11 VERSION = 3.1.2 defined(EXPORT_PATH_PREFIX, "var"){ EXPORT_PATH = $$EXPORT_PATH_PREFIX } else{ EXPORT_PATH = $$OUT_PWD/export } EXPORT_PATH = $${EXPORT_PATH}/$${VENDORNAME}/$${APPNAME}/v$${VERSION}-lib EXPORT_INCLUDEPATH = $$EXPORT_PATH/include EXPORT_LIBPATH = $$EXPORT_PATH/$$LIBPATH message("$$APPNAME [ export folder is $${EXPORT_LIBPATH} ]") DEFINES *= QHTTP_MEMORY_LOG=0 win32:DEFINES *= QHTTP_EXPORT # Joyent http_parser SOURCES += $$PWD/../vendor/http-parser/http_parser.c HEADERS += $$PWD/../vendor/http-parser/http_parser.h SOURCES += \ qhttpabstracts.cpp \ qhttpserverconnection.cpp \ qhttpserverrequest.cpp \ qhttpserverresponse.cpp \ qhttpserver.cpp PUBLIC_HEADERS += \ qhttpfwd.hpp \ qhttpabstracts.hpp \ qhttpserverconnection.hpp \ qhttpserverrequest.hpp \ qhttpserverresponse.hpp \ qhttpserver.hpp contains(DEFINES, QHTTP_HAS_CLIENT) { SOURCES += \ qhttpclientrequest.cpp \ qhttpclientresponse.cpp \ qhttpclient.cpp PUBLIC_HEADERS += \ qhttpclient.hpp \ qhttpclientresponse.hpp \ qhttpclientrequest.hpp } HEADERS += $${PUBLIC_HEADERS} # Lib QMAKE_STRIP = echo # Avoid striping header files (which will not work) target.extra = strip $(TARGET) applibs.files = $${DESTDIR}/*.a applibs.path = $$EXPORT_LIBPATH INSTALLS += applibs # Include files headers.files = $$PUBLIC_HEADERS headers.path = $$EXPORT_INCLUDEPATH INSTALLS += headers ## qompoter.pri qompoter.files = $$PWD/../qompoter.pri qompoter.files += $$PWD/../qompoter.json qompoter.path = $$EXPORT_PATH INSTALLS += qompoter psi-plus-snapshots-1.4.1456/3rdparty/qhttp/utils.sh000077500000000000000000000023501370065651000221620ustar00rootroot00000000000000#!/bin/bash CTAGS_EXCLUDES_BASE="--exclude=.git --exclude=tmp --exclude=xbin --exclude=res" CTAGS_OPTIONS_BASE="--c++-kinds=+cefgnps --fields=+iaS --extra=+fq -R ." CLOC_EXCLUDES_BASE="--exclude-dir=.git,xbin,tmp,res" CLOC_EXCLUDES_LANGS="--exclude-lang=Prolog,make" function fn_cloc() { echo "cloc (exclusive) ..." cloc $CLOC_EXCLUDES_LANGS $CLOC_EXCLUDES_BASE,3rdparty . } function fn_cloc_all() { echo "cloc all (inclusive) ..." cloc $CLOC_EXCLUDES_LANGS $CLOC_EXCLUDES_BASE . } function fn_ctags() { echo "ctags (exclusive) ..." ctags --exclude=3rdparty $CTAGS_EXCLUDES_BASE $CTAGS_OPTIONS_BASE } function fn_ctags_all() { echo "ctags all (inclusive) ..." ctags $CTAGS_EXCLUDES_BASE $CTAGS_OPTIONS_BASE } if [[ $# -eq 0 ]]; then fn_ctags else while [[ $# > 0 ]]; do arg="$1" case $arg in tags|ctags|tag|ctag) fn_ctags ;; tags_all|ctags_all|tag_all|fn_ctags_all) fn_ctags_all ;; cloc_all) fn_cloc_all ;; cloc) fn_cloc ;; *) echo "unknown args as $arg" ;; esac shift done fi psi-plus-snapshots-1.4.1456/3rdparty/qite/000077500000000000000000000000001370065651000202655ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/3rdparty/qite/.pre-commit-config.yaml000066400000000000000000000015231370065651000245470ustar00rootroot00000000000000# See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.5.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-added-large-files - id: check-merge-conflict - repo: https://github.com/doublify/pre-commit-clang-format # for clang-tidy we can take github.com/pocc/pre-commit-hooks rev: f4c4ac5948aff384af2b439bfabb2bdd65d2b3ac hooks: - id: clang-format - repo: https://github.com/Lucas-C/pre-commit-hooks rev: v1.1.7 hooks: - id: forbid-crlf - id: remove-crlf - id: forbid-tabs - id: remove-tabs - repo: https://github.com/openstack-dev/bashate rev: 2.0.0 hooks: - id: bashate args: ['--ignore', 'E006'] psi-plus-snapshots-1.4.1456/3rdparty/qite/LICENSE000066400000000000000000000261201370065651000212730ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2018 Sergey Ilinykh Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. psi-plus-snapshots-1.4.1456/3rdparty/qite/NOTICE000066400000000000000000000003041370065651000211660ustar00rootroot00000000000000Apache QITE - Qt Interactive Text Elements Copyright 2018-2018 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (http://www.apache.org/). psi-plus-snapshots-1.4.1456/3rdparty/qite/README.md000066400000000000000000000001231370065651000215400ustar00rootroot00000000000000# Qt Interactive Text Element Allows to manage interactive elements on QTextEdit. psi-plus-snapshots-1.4.1456/3rdparty/qite/libqite/000077500000000000000000000000001370065651000217165ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/3rdparty/qite/libqite/CMakeLists.txt000066400000000000000000000011601370065651000244540ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.1.0) project(qite) include(GNUInstallDirs) # Find includes in corresponding build directories set(CMAKE_INCLUDE_CURRENT_DIR ON) # Instruct CMake to run moc automatically when needed set(CMAKE_AUTOMOC ON) # Find the QtWidgets library find_package(Qt5 REQUIRED Widgets Multimedia) include(libqite.cmake) add_library(qite STATIC ${qite_SOURCES}) set_property(TARGET qite PROPERTY CXX_STANDARD 14) target_link_libraries(qite Qt5::Widgets Qt5::Multimedia) install(TARGETS qite DESTINATION ${CMAKE_INSTALL_LIBDIR}) install(FILES ${qite_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/qite) psi-plus-snapshots-1.4.1456/3rdparty/qite/libqite/libqite.cmake000066400000000000000000000007521370065651000243550ustar00rootroot00000000000000# Qt5 or above is required. set(qite_SOURCES ${CMAKE_CURRENT_LIST_DIR}/qite.cpp ${CMAKE_CURRENT_LIST_DIR}/qiteaudio.cpp ${CMAKE_CURRENT_LIST_DIR}/qiteprogress.cpp ${CMAKE_CURRENT_LIST_DIR}/qiteaudiorecorder.cpp ) set(qite_HEADERS ${CMAKE_CURRENT_LIST_DIR}/qite.h ${CMAKE_CURRENT_LIST_DIR}/qiteaudio.h ${CMAKE_CURRENT_LIST_DIR}/qiteprogress.h ${CMAKE_CURRENT_LIST_DIR}/qiteaudiorecorder.h ) include_directories( ${CMAKE_CURRENT_LIST_DIR} ) psi-plus-snapshots-1.4.1456/3rdparty/qite/libqite/libqite.pri000066400000000000000000000005041370065651000240620ustar00rootroot00000000000000!greaterThan(QT_MAJOR_VERSION, 4):error(Qt5 or above is required) SOURCES += \ $$PWD/qite.cpp \ $$PWD/qiteaudio.cpp \ $$PWD/qiteprogress.cpp \ $$PWD/qiteaudiorecorder.cpp HEADERS += \ $$PWD/qite.h \ $$PWD/qiteaudio.h \ $$PWD/qiteprogress.h \ $$PWD/qiteaudiorecorder.h INCLUDEPATH += $$PWD psi-plus-snapshots-1.4.1456/3rdparty/qite/libqite/libqite.pro000066400000000000000000000001661370065651000240740ustar00rootroot00000000000000TEMPLATE = lib QT += core gui multimedia widgets CONFIG += c++14 static TARGET = qite include($$PWD/libqite.pri) psi-plus-snapshots-1.4.1456/3rdparty/qite/libqite/qite.cpp000066400000000000000000000275411370065651000233750ustar00rootroot00000000000000/* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #include "qite.h" #include #include #include #include #include #include #include #include //----------------------------------// // InteractiveTextElementController // //----------------------------------// InteractiveTextElementController::InteractiveTextElementController(InteractiveText *it, QObject *parent) : QObject(parent), itc(it) { objectType = itc->registerController(this); } InteractiveTextElementController::~InteractiveTextElementController() { itc->unregisterController(this); } void InteractiveTextElementController::drawObject(QPainter *painter, const QRectF &rect, QTextDocument *doc, int posInDocument, const QTextFormat &format) { Q_UNUSED(doc) auto elementId = InteractiveTextFormat::id(format); itc->markVisible(elementId); drawITE(painter, rect, posInDocument, format); } bool InteractiveTextElementController::mouseEvent(const Event &event, const QRect &rect, QTextCursor &selected) { Q_UNUSED(event) Q_UNUSED(rect) Q_UNUSED(selected) return false; } void InteractiveTextElementController::hideEvent(QTextCursor &selected) { Q_UNUSED(selected) } QCursor InteractiveTextElementController::cursor() { return QCursor(Qt::IBeamCursor); } //---------------------------// // InteractiveTextController // //---------------------------// InteractiveText::InteractiveText(QTextEdit *textEdit, int baseObjectType) : QObject(textEdit), _textEdit(textEdit), _baseObjectType(baseObjectType), _objectType(baseObjectType) { textEdit->installEventFilter(this); textEdit->viewport()->installEventFilter(this); connect( textEdit->verticalScrollBar(), &QScrollBar::valueChanged, this, [this](int) { trackVisibility(); }, Qt::QueuedConnection); connect( textEdit->horizontalScrollBar(), &QScrollBar::valueChanged, this, [this](int) { trackVisibility(); }, Qt::QueuedConnection); connect(textEdit, &QTextEdit::textChanged, this, &InteractiveText::trackVisibility, Qt::QueuedConnection); } int InteractiveText::registerController(InteractiveTextElementController *elementController) { auto objectType = _objectType++; QTextDocument *doc = _textEdit->document(); doc->documentLayout()->registerHandler(objectType, elementController); _controllers.insert(objectType, elementController); return objectType; } void InteractiveText::unregisterController(InteractiveTextElementController *elementController) { textEdit()->document()->documentLayout()->unregisterHandler(elementController->objectType, elementController); _controllers.remove(elementController->objectType); } InteractiveTextFormat::ElementId InteractiveText::nextId() { return ++_uniqueElementId; } void InteractiveText::insert(const InteractiveTextFormat &fmt) { _textEdit->textCursor().insertText(QString(QChar::ObjectReplacementCharacter), fmt); // TODO check if mouse is already on the element } QTextCursor InteractiveText::findElement(quint32 elementId, int cursorPositionHint) { QTextCursor cursor(_textEdit->document()); cursor.setPosition(cursorPositionHint); cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); QString selectedText = cursor.selectedText(); if (selectedText.size() && selectedText[0] == QChar::ObjectReplacementCharacter) { QTextCharFormat fmt = cursor.charFormat(); auto otype = fmt.objectType(); if (otype >= _baseObjectType && otype < _objectType && fmt.property(InteractiveTextFormat::Id).toUInt() == elementId) { return cursor; } } cursor.setPosition(0); QString elText(QChar::ObjectReplacementCharacter); while (!(cursor = _textEdit->document()->find(elText, cursor)).isNull()) { QTextCharFormat fmt = cursor.charFormat(); auto otype = fmt.objectType(); if (otype >= _baseObjectType && otype < _objectType && fmt.property(InteractiveTextFormat::Id).toUInt() == elementId) { break; } } return cursor; } bool InteractiveText::eventFilter(QObject *obj, QEvent *event) { if (obj == _textEdit && event->type() == QEvent::Resize) { trackVisibility(); return false; } bool ourEvent = (obj == _textEdit && (event->type() == QEvent::HoverEnter || event->type() == QEvent::HoverMove || event->type() == QEvent::HoverLeave)) || (obj == _textEdit->viewport() && event->type() == QEvent::MouseButtonPress); if (!ourEvent) { return false; } bool ret = false; bool leaveHandled = false; QPoint pos; // relative to visible part. if (event->type() == QEvent::MouseButtonPress) { pos = static_cast(event)->pos(); } else { pos = static_cast(event)->pos(); } if (event->type() == QEvent::HoverEnter || event->type() == QEvent::HoverMove || event->type() == QEvent::MouseButtonPress) { QPoint viewportOffset(_textEdit->horizontalScrollBar()->value(), _textEdit->verticalScrollBar()->value()); int docLPos = _textEdit->document()->documentLayout()->hitTest(pos + viewportOffset, Qt::ExactHit); if (docLPos != -1) { QTextCursor cursor(_textEdit->document()); cursor.setPosition(docLPos); cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); if (cursor.selectedText()[0] == QChar::ObjectReplacementCharacter) { auto format = cursor.charFormat(); auto elementId = format.property(InteractiveTextFormat::Id).toUInt(); auto ot = format.objectType(); auto *elementController = _controllers.value(ot); if (elementController) { // we are definitely on a known interactive element. // first we have to check what was before to generate proper events. bool isEnter = !_lastMouseHandled || _lastElementId != elementId; if (isEnter && _lastMouseHandled) { // jump from another element checkAndGenerateLeaveEvent(event); } leaveHandled = true; QRect rect = elementRect(cursor); rect.translate(-viewportOffset); InteractiveTextElementController::Event iteEvent; iteEvent.qevent = event; iteEvent.pos = QPoint(pos.x() - rect.left(), pos.y() - rect.top()); // qDebug() << "mouse" << pos << "event rel pos" << iteEvent.pos << rect; if (event->type() == QEvent::MouseButtonPress) { iteEvent.type = InteractiveTextElementController::EventType::Click; } else { iteEvent.type = isEnter ? InteractiveTextElementController::EventType::Enter : InteractiveTextElementController::EventType::Move; } ret = elementController->mouseEvent(iteEvent, rect, cursor); if (ret) { _lastCursorPositionHint = cursor.position(); _lastElementId = elementId; _textEdit->viewport()->setCursor(elementController->cursor()); } else { _textEdit->viewport()->setCursor(Qt::IBeamCursor); } } } } } if (!leaveHandled) { // not checked yet if we need leave event.This also means we are not on an element. checkAndGenerateLeaveEvent(event); if (_lastMouseHandled) { _textEdit->viewport()->setCursor(Qt::IBeamCursor); } } _lastMouseHandled = ret; return ret; } void InteractiveText::checkAndGenerateLeaveEvent(QEvent *event) { if (!_lastMouseHandled) { return; } QTextCursor cursor = findElement(_lastElementId, _lastCursorPositionHint); if (!cursor.isNull()) { auto fmt = cursor.charFormat(); InteractiveTextElementController *controller = _controllers.value(fmt.objectType()); if (!controller) { return; } InteractiveTextElementController::Event iteEvent; iteEvent.qevent = event; iteEvent.type = InteractiveTextElementController::EventType::Leave; controller->mouseEvent(iteEvent, QRect(), cursor); } } void InteractiveText::markVisible(const InteractiveTextFormat::ElementId &id) { _visibleElements.insert(id); } // returns rect of the interactive selected (from left to right) element in global coords. // So consider coverting into viewport coordinates if needed. QRect InteractiveText::elementRect(const QTextCursor &cursor) const { QRect ret; auto block = cursor.block(); auto controller = _controllers.value(cursor.charFormat().objectType()); QTextCursor anchorCursor(cursor); anchorCursor.movePosition(QTextCursor::Left); if (controller && block.isValid() && block.isVisible()) { // qDebug() << "block pos" << block.position() << "lines" << block.lineCount() << "layout lines" << // block.layout()->lineCount(); auto posInBlock = anchorCursor.position() - block.position(); QTextLine line = block.layout()->lineForTextPosition(posInBlock); if (line.isValid()) { // qDebug() << " line rect" << line.rect(); auto x = line.cursorToX(posInBlock); auto s = controller->intrinsicSize(_textEdit->document(), anchorCursor.position(), cursor.charFormat()); ret = QRect(QPoint(0, 0), s.toSize()); ret.moveBottomLeft(QPoint(int(x), int(line.rect().bottom()))); ret.translate(_textEdit->document()->documentLayout()->blockBoundingRect(block).topLeft().toPoint()); } } return ret; } void InteractiveText::trackVisibility() { // qDebug() << "check visibility"; QMutableSetIterator it(_visibleElements); QPoint viewportOffset(_textEdit->horizontalScrollBar()->value(), _textEdit->verticalScrollBar()->value()); // auto startCursor = _textEdit->cursorForPosition(QPoint(0,0)); QRect viewPort(QPoint(0, 0), _textEdit->viewport()->size()); while (it.hasNext()) { auto id = it.next(); auto cursor = findElement( id); // FIXME this call is not optimal. but internally it uses qtextdocument, so it won't slowdoan that much if (!cursor.isNull()) { auto cr = elementRect(cursor); cr.translate(-viewportOffset); // now we can check if it's still on the screen if (cr.isNull() || !viewPort.intersects(cr)) { auto c = _controllers.value(cursor.charFormat().objectType()); if (c) { c->hideEvent(cursor); it.remove(); } } } } } psi-plus-snapshots-1.4.1456/3rdparty/qite/libqite/qite.h000066400000000000000000000111241370065651000230300ustar00rootroot00000000000000/* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #ifndef QITE_H #define QITE_H #include #include class QTextEdit; class InteractiveText; #ifndef QITE_FIRST_USER_PROPERTY #define QITE_FIRST_USER_PROPERTY 0 #endif class InteractiveTextFormat : public QTextCharFormat { public: using QTextCharFormat::QTextCharFormat; enum Property { Id = QTextFormat::UserProperty + QITE_FIRST_USER_PROPERTY, UserProperty }; typedef quint32 ElementId; // just make it public. no reason to keep it protected inline explicit InteractiveTextFormat(const QTextCharFormat &fmt) : QTextCharFormat(fmt) { } inline InteractiveTextFormat(int objectType, ElementId id) { setObjectType(objectType); setProperty(Id, id); } inline ElementId id() const { return id(*this); } static inline ElementId id(const QTextFormat &format) { return ElementId(format.property(Id).toUInt()); } }; class InteractiveTextElementController : public QObject, public QTextObjectInterface { Q_OBJECT Q_INTERFACES(QTextObjectInterface) public: enum class EventType { Enter, Leave, Move, Click }; class Event { public: QEvent * qevent; EventType type; QPoint pos; // relative to element. last position for "Leave" }; InteractiveTextElementController(InteractiveText *itc, QObject *parent = nullptr); virtual ~InteractiveTextElementController(); virtual QCursor cursor(); void drawObject(QPainter *painter, const QRectF &rect, QTextDocument *doc, int posInDocument, const QTextFormat &format); // subclasses should implement drawITE instead of drawObject virtual void drawITE(QPainter *painter, const QRectF &rect, int posInDocument, const QTextFormat &format) = 0; protected: friend class InteractiveText; InteractiveText *itc; int objectType; virtual bool mouseEvent(const Event &event, const QRect &rect, QTextCursor &selected); virtual void hideEvent(QTextCursor &selected); }; class InteractiveText : public QObject { Q_OBJECT public: InteractiveText(QTextEdit *_textEdit, int baseObjectType = QTextFormat::UserObject); inline QTextEdit *textEdit() const { return _textEdit; } int registerController(InteractiveTextElementController *elementController); void unregisterController(InteractiveTextElementController *elementController); void insert(const InteractiveTextFormat &fmt); QTextCursor findElement(quint32 elementId, int cursorPositionHint = 0); void markVisible(const InteractiveTextFormat::ElementId &id); InteractiveTextFormat::ElementId nextId(); protected: bool eventFilter(QObject *obj, QEvent *event); private: void checkAndGenerateLeaveEvent(QEvent *event); QRect elementRect(const QTextCursor &selected) const; private slots: void trackVisibility(); private: QTextEdit * _textEdit = nullptr; int _baseObjectType; int _objectType; quint32 _uniqueElementId = 0; // just a sequence number quint32 _lastElementId; // last which had mouse event int _lastCursorPositionHint; // wrt mouse event QMap _controllers; QSet _visibleElements; bool _lastMouseHandled = false; }; class ITEMediaOpener { public: virtual QIODevice *open(QUrl &url) = 0; virtual void close(QIODevice *dev) = 0; virtual QVariant metadata(const QUrl &url) = 0; }; #endif // QITE_H psi-plus-snapshots-1.4.1456/3rdparty/qite/libqite/qiteaudio.cpp000066400000000000000000000635101370065651000244130ustar00rootroot00000000000000/* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #include "qiteaudio.h" #include #include #include #include #include #include #include #include #include #include #include class AudioMessageFormat : public InteractiveTextFormat { public: enum Property { Url = InteractiveTextFormat::UserProperty, MediaOpener, PlayPosition, /* in pixels */ State, MetadataState, Metadata }; enum MDState { NotRequested, RequestInProgress, Finished }; enum Flag { Playing = 0x1, MouseOnButton = 0x2, MouseOnTrackbar = 0x4 }; Q_DECLARE_FLAGS(Flags, Flag) using InteractiveTextFormat::InteractiveTextFormat; AudioMessageFormat(int objectType, ElementId id, const QUrl &url, ITEMediaOpener *mediaOpener = nullptr, quint32 position = 0, const Flags &state = Flags()); Flags state() const; void setState(const Flags &state); quint32 playPosition() const; void setPlayPosition(quint32 position); QUrl url() const; ITEMediaOpener *mediaOpener() const; QVariant metaData() const; void setMetaData(const QVariant &v); MDState metaDataState() const; void setMetaDataState(MDState state); static AudioMessageFormat fromCharFormat(const QTextCharFormat &fmt) { return static_cast(fmt); } }; Q_DECLARE_OPERATORS_FOR_FLAGS(AudioMessageFormat::Flags) AudioMessageFormat::AudioMessageFormat(int objectType, ElementId id, const QUrl &url, ITEMediaOpener *mediaOpener, quint32 position, const Flags &state) : InteractiveTextFormat(objectType, id) { setProperty(Url, url); setProperty(MediaOpener, QVariant::fromValue(mediaOpener)); setProperty(PlayPosition, position); setState(state); } AudioMessageFormat::Flags AudioMessageFormat::state() const { return Flags(property(AudioMessageFormat::State).toUInt()); } void AudioMessageFormat::setState(const AudioMessageFormat::Flags &state) { setProperty(State, unsigned(state)); } quint32 AudioMessageFormat::playPosition() const { return property(AudioMessageFormat::PlayPosition).toUInt(); } void AudioMessageFormat::setPlayPosition(quint32 position) { setProperty(AudioMessageFormat::PlayPosition, position); } QUrl AudioMessageFormat::url() const { return property(AudioMessageFormat::Url).toUrl(); } ITEMediaOpener *AudioMessageFormat::mediaOpener() const { return static_cast(property(AudioMessageFormat::MediaOpener).value()); } QVariant AudioMessageFormat::metaData() const { return property(AudioMessageFormat::Metadata); } void AudioMessageFormat::setMetaData(const QVariant &v) { setProperty(AudioMessageFormat::Metadata, v); setProperty(AudioMessageFormat::MetadataState, int(Finished)); } AudioMessageFormat::MDState AudioMessageFormat::metaDataState() const { return AudioMessageFormat::MDState(property(AudioMessageFormat::MetadataState).value()); } void AudioMessageFormat::setMetaDataState(MDState state) { setProperty(AudioMessageFormat::MetadataState, int(state)); } //---------------------------------------------------------------------------- // ITEAudioController //---------------------------------------------------------------------------- QSizeF ITEAudioController::intrinsicSize(QTextDocument *doc, int posInDocument, const QTextFormat &format) { Q_UNUSED(doc); Q_UNUSED(posInDocument) const QTextCharFormat charFormat = format.toCharFormat(); auto psize = QFontMetrics(charFormat.font()).height(); if (lastFontSize != psize) { lastFontSize = psize; updateGeomtry(); } return elementSize; } void ITEAudioController::updateGeomtry() { // compute geomtry of player baseSize = lastFontSize / 12.0; int elementPadding = int(baseSize * 4); bgOutlineWidth = baseSize < 2 ? 2 : int(baseSize); btnRadius = int(baseSize * 10); int elementHeight = btnRadius * 2 + int(elementPadding * 2); int amplitudesColumnWidth = qRound(baseSize); if (!amplitudesColumnWidth) { amplitudesColumnWidth = 1; } auto rightPadding = int(baseSize * 5); // elementHeight already includes 2 paddings: to the lest and to the right of button elementSize = QSize(elementHeight + amplitudesColumnWidth * HistogramCompressedSize + rightPadding, elementHeight); bgRect = QRect(QPoint(0, 0), elementSize); bgRect.adjust(bgOutlineWidth / 2, bgOutlineWidth / 2, -bgOutlineWidth / 2, -bgOutlineWidth / 2); // outline should fit the format rect. bgRectRadius = bgRect.height() / 5; btnCenter = QPoint(elementSize.height() / 2, elementSize.height() / 2); signSize = btnRadius / 2; // next to the button we need histgram/title and scale. int left = elementHeight; int right = elementSize.width() - rightPadding; metaRect = QRect(QPoint(left, bgRect.top() + int(baseSize * 3)), QPoint(right, bgRect.top() + int(bgRect.height() * 0.5))); // draw scale scaleOutlineWidth = bgOutlineWidth; QPointF scaleTopLeft( left, metaRect.bottom() + baseSize * 4); // = bgRect.topLeft() + QPointF(left, bgRect.height() * 0.7); QPointF scaleBottomRight(right, scaleTopLeft.y() + baseSize * 4); scaleRect = QRectF(scaleTopLeft, scaleBottomRight); scaleFillRect = scaleRect.adjusted(scaleOutlineWidth / 2, scaleOutlineWidth / 2, -scaleOutlineWidth / 2, -scaleOutlineWidth / 2); } void ITEAudioController::drawITE(QPainter *painter, const QRectF &rect, int posInDocument, const QTextFormat &format) { const AudioMessageFormat audioFormat = AudioMessageFormat::fromCharFormat(format.toCharFormat()); // qDebug() << audioFormat.id(); painter->setRenderHints(QPainter::HighQualityAntialiasing); QPen bgPen(QColor(100, 200, 100)); // TODO name all the magic colors bgPen.setWidth(bgOutlineWidth); painter->setPen(bgPen); painter->setBrush(QColor(150, 250, 150)); painter->drawRoundedRect(bgRect.translated(int(rect.left()), int(rect.top())), bgRectRadius, bgRectRadius); // draw button if (audioFormat.state() & AudioMessageFormat::MouseOnButton) { painter->setBrush(QColor(130, 230, 130)); } else { painter->setBrush(QColor(120, 220, 120)); } auto xBtnCenter = btnCenter + rect.topLeft(); painter->drawEllipse(xBtnCenter, btnRadius, btnRadius); // draw pause/play QPen signPen((QColor(Qt::white))); signPen.setWidth(bgOutlineWidth); painter->setPen(signPen); painter->setBrush(QColor(Qt::white)); bool isPlaying = audioFormat.state() & AudioMessageFormat::Playing; if (isPlaying) { QRectF bar(0, 0, signSize / 3, signSize * 2); bar.moveCenter(xBtnCenter - QPointF(signSize / 2, 0)); painter->drawRect(bar); bar.moveCenter(xBtnCenter + QPointF(signSize / 2, 0)); painter->drawRect(bar); } else { QPointF play[3] = { xBtnCenter - QPoint(signSize / 2, signSize), xBtnCenter - QPoint(signSize / 2, -signSize), xBtnCenter + QPoint(signSize, 0) }; painter->drawConvexPolygon(play, 3); } // draw scale QPen scalePen(QColor(100, 200, 100)); scalePen.setWidth(scaleOutlineWidth); painter->setPen(scalePen); painter->setBrush(QColor(120, 220, 120)); QRectF xScaleRect(scaleRect.translated(rect.topLeft())); painter->drawRoundedRect(xScaleRect, scaleRect.height() / 2, scaleRect.height() / 2); // draw played part auto playPos = audioFormat.playPosition(); if (playPos) { painter->setPen(Qt::NoPen); painter->setBrush(QColor(170, 255, 170)); QRectF playedRect(scaleFillRect.translated(rect.topLeft())); // to the width of the scale border playedRect.setWidth(playPos); painter->drawRoundedRect(playedRect, playedRect.height() / 2, playedRect.height() / 2); } // check metadata. maybe it's ready or we need to query it auto mdState = audioFormat.metaDataState(); if (mdState != AudioMessageFormat::Finished) { auto opener = audioFormat.mediaOpener(); if (opener) { QVariant metadata = opener->metadata(audioFormat.url()); if (metadata.isValid()) { auto id = audioFormat.id(); QTimer::singleShot(0, this, [this, id, posInDocument, metadata]() { QTextCursor cursor = itc->findElement(id, posInDocument); if (cursor.isNull()) return; // was deleted so quickly? auto audioFormat = AudioMessageFormat::fromCharFormat(cursor.charFormat()); if (audioFormat.metaDataState() == AudioMessageFormat::Finished) return; QVariantMap vm = metadata.toMap(); audioFormat.setMetaData(vm.value(QLatin1String("amplitudes"))); audioFormat.setMetaDataState(AudioMessageFormat::Finished); cursor.setCharFormat(audioFormat); }); } } if (!autoFetchMetadata || mdState == AudioMessageFormat::RequestInProgress) { return; } // we need t query amplitudes. Let's check if it makes sense first. if (audioFormat.url().path().endsWith(".mka")) { // we use mka for audio messages. so it may have amplitudes auto id = audioFormat.id(); // use deleayed call since it's not that good to chage docs from drawing func. QTimer::singleShot(0, this, [this, id, posInDocument]() { QTextCursor cursor = itc->findElement(id, posInDocument); if (cursor.isNull()) { return; // was deleted so quickly? } auto audioFormat = AudioMessageFormat::fromCharFormat(cursor.charFormat()); if (audioFormat.metaDataState() != AudioMessageFormat::NotRequested) { return; // likely duplicate query, while previous one wasn't finished it. } // time to query amplitudes file if (!nam) { nam = new QNetworkAccessManager(this); } QUrl metaUrl(audioFormat.url()); metaUrl.setPath(metaUrl.path() + ".amplitudes"); auto reply = nam->get(QNetworkRequest(metaUrl)); audioFormat.setMetaDataState(AudioMessageFormat::RequestInProgress); cursor.setCharFormat(audioFormat); auto pos = cursor.anchor(); connect(reply, &QNetworkReply::finished, this, [this, id, pos, reply]() { QTextCursor cursor = itc->findElement(id, pos); if (!cursor.isNull()) { Histogram hm; hm.reserve(HistogramCompressedSize); auto amplitudes = QString::fromLatin1(reply->readAll()).split(','); for (auto v : amplitudes) { auto fv = v.toFloat() / 255.0f; hm.push_back(fv > 1.0f ? 1.0f : fv); } auto afmt = AudioMessageFormat::fromCharFormat(cursor.charFormat()); afmt.setMetaData(QVariant::fromValue(hm)); cursor.setCharFormat(afmt); } reply->close(); reply->deleteLater(); }); }); } } auto hg = audioFormat.metaData(); if (hg.canConvert>()) { // amplitudes auto hglist = hg.value>(); auto step = metaRect.width() / float(hglist.size()); auto tmetaRect = metaRect.translated(rect.topLeft().toPoint()); painter->setPen(QColor(70, 150, 70)); painter->setBrush(QColor(120, 220, 120)); for (int i = 0; i < hglist.size(); i++) { // values from 0 to 1.0 (including) int left = int(i * step); int right = int((i + 1) * step); int height = int(metaRect.height() * hglist[i]); if (height) { // int top = int(metaRect.height() * (1.0f - hglist[i])); QRect hcolRect(QPoint(left, metaRect.height() - height), QSize(right - left, height)); hcolRect.translate(tmetaRect.topLeft()); painter->drawRect(hcolRect); } } } else if (hg.type() == QVariant::String) { painter->setPen(QColor(70, 150, 70)); painter->drawText(metaRect.translated(rect.topLeft().toPoint()), hg.toString()); } // runner // QRectF runnerRect(playedRect); // runnerRect.set } QTextCharFormat ITEAudioController::makeFormat(const QUrl &audioSrc, ITEMediaOpener *mediaOpener) const { AudioMessageFormat fmt(objectType, itc->nextId(), audioSrc, mediaOpener); fmt.setFontPointSize(itc->textEdit()->currentFont().pointSize()); return fmt; } void ITEAudioController::insert(const QUrl &audioSrc, ITEMediaOpener *mediaOpener) { auto fmt = makeFormat(audioSrc, mediaOpener); itc->insert(static_cast(fmt)); } bool ITEAudioController::mouseEvent(const Event &event, const QRect &rect, QTextCursor &selected) { Q_UNUSED(rect); bool onButton = false; bool onTrackbar = false; if (event.type != EventType::Leave) { onButton = isOnButton(event.pos, bgRect); if (!onButton) { onTrackbar = scaleRect.contains(event.pos); } } if (onButton || onTrackbar) { _cursor = QCursor(Qt::PointingHandCursor); } else { _cursor = QCursor(Qt::ArrowCursor); } AudioMessageFormat format = AudioMessageFormat::fromCharFormat(selected.charFormat()); AudioMessageFormat::Flags state = format.state(); bool onButtonChanged = bool(state & AudioMessageFormat::MouseOnButton) != onButton; bool onTrackbarChanged = bool(state & AudioMessageFormat::MouseOnTrackbar) != onTrackbar; bool playStateChanged = false; bool positionSet = false; if (onButtonChanged) { state ^= AudioMessageFormat::MouseOnButton; } if (onTrackbarChanged) { state ^= AudioMessageFormat::MouseOnTrackbar; } auto playerId = format.id(); if (event.type == EventType::Click) { if (onButton) { playStateChanged = true; state ^= AudioMessageFormat::Playing; auto player = activePlayers.value(playerId); if (state & AudioMessageFormat::Playing) { if (!player) { player = new QMediaPlayer(this); player->setProperty("playerId", playerId); player->setProperty("cursorPos", selected.anchor()); activePlayers.insert(playerId, player); ITEMediaOpener *opener = format.mediaOpener(); QUrl url = format.url(); QIODevice * stream = opener ? opener->open(url) : nullptr; if (stream) connect(player, &QMediaPlayer::destroyed, this, [opener, stream]() { opener->close(stream); }); player->setMedia(url, stream); auto part = double(format.playPosition()) / double(scaleFillRect.width()); if (player->duration() > 0) { player->setPosition(qint64(player->duration() * part)); player->setNotifyInterval(int(player->duration() / double(metaRect.width()) * 3.0)); // 3 px connect(player, SIGNAL(positionChanged(qint64)), this, SLOT(playerPositionChanged(qint64))); } else { connect(player, &QMediaPlayer::durationChanged, [player, part, this](qint64 duration) { // the timer is a workaround for some Qt bug QTimer::singleShot(0, [player, part, this, duration]() { if (part > 0) { // don't jump back if event came quite late player->setPosition(qint64(duration * part)); } // qDebug() << int(duration / double(metaRect.width())); player->setNotifyInterval(int(duration / 1000.0 / double(metaRect.width()) * 3.0)); connect(player, SIGNAL(positionChanged(qint64)), this, SLOT(playerPositionChanged(qint64))); }); }); player->setNotifyInterval(50); // while we don't know duration, lets use quite small value } // check for title in metadata connect(player, &QMediaPlayer::metaDataAvailableChanged, this, [this, player](bool available) { if (available) { auto title = player->metaData(QMediaMetaData::Title).toString(); if (title.isEmpty()) { return; } quint32 playerId = player->property("playerId").toUInt(); int textCursorPos = player->property("cursorPos").toInt(); QTextCursor cursor = itc->findElement(playerId, textCursorPos); if (cursor.isNull()) { return; } auto format = AudioMessageFormat::fromCharFormat(cursor.charFormat().toCharFormat()); if (format.metaData().type() == QVariant::List) { return; // seems we have amplitudes already } format.setMetaData(title); cursor.setCharFormat(format); } }); // try to extract from metadata and store amplitudes connect(player, static_cast( &QMediaPlayer::metaDataChanged), [=](const QString &key, const QVariant &value) { QString comment; int index = 0; if (key != QMediaMetaData::Comment || (comment = value.toString()).isEmpty() || !comment.startsWith(QLatin1String("AMPLDIAGSTART")) || (index = comment.indexOf("AMPLDIAGEND")) == -1) { return; // In comment we keep amplitudes. We don't expect anything else } auto sl = comment .mid(int(sizeof("AMPLDIAGSTART")), index - int(sizeof("AMPLDIAGSTART")) - 1) .split(","); QList amplitudes; amplitudes.reserve(sl.size()); std::transform(sl.constBegin(), sl.constEnd(), std::back_inserter(amplitudes), [](const QString &v) { auto fv = v.toFloat() / float(255.0); if (fv > 1) { return 1.0f; } return fv; }); quint32 playerId = player->property("playerId").toUInt(); int textCursorPos = player->property("cursorPos").toInt(); QTextCursor cursor = itc->findElement(playerId, textCursorPos); if (cursor.isNull()) { return; } auto format = AudioMessageFormat::fromCharFormat(cursor.charFormat().toCharFormat()); format.setMetaData(QVariant::fromValue(amplitudes)); cursor.setCharFormat(format); }); connect(player, SIGNAL(stateChanged(QMediaPlayer::State)), this, SLOT(playerStateChanged(QMediaPlayer::State))); QObject::connect(player, &QMediaPlayer::mediaStatusChanged, [=]() { qDebug() << "Media status changed:" << player->mediaStatus(); }); QObject::connect(player, static_cast(&QMediaPlayer::error), [=](QMediaPlayer::Error error) { qDebug() << "Error occurred:" << error; }); } // player->setVolume(0); player->play(); } else { if (player) { player->pause(); // player->disconnect(this);activePlayers.take(playerId)->deleteLater(); } } } else if (onTrackbar) { // include outline to clickable area but compute only for inner part double part; if (event.pos.x() < scaleFillRect.left()) { part = 0; } else if (event.pos.x() >= scaleFillRect.right()) { part = 1; } else { part = double(event.pos.x() - scaleFillRect.left()) / double(scaleFillRect.width()); } auto player = activePlayers.value(playerId); if (player) { qDebug("Set position to %d", int(part * 100)); player->setPosition(qint64(player->duration() * part)); } // else it's not playing likely format.setPlayPosition(quint32(double(scaleFillRect.width()) * part)); positionSet = true; } } if (onButtonChanged || playStateChanged || positionSet) { format.setState(state); selected.setCharFormat(format); } return true; } void ITEAudioController::hideEvent(QTextCursor &selected) { auto fmt = AudioMessageFormat::fromCharFormat(selected.charFormat()); auto player = activePlayers.value(fmt.id()); // qDebug() << "hiding player" << fmt.id(); if (player) { player->stop(); } } bool ITEAudioController::isOnButton(const QPoint &pos, const QRect &rect) { QPoint rel = pos - rect.topLeft(); return QVector2D(btnCenter).distanceToPoint(QVector2D(rel)) <= btnRadius; } void ITEAudioController::playerPositionChanged(qint64 newPos) { auto player = static_cast(sender()); quint32 playerId = player->property("playerId").toUInt(); int textCursorPos = player->property("cursorPos").toInt(); QTextCursor cursor = itc->findElement(playerId, textCursorPos); if (!cursor.isNull()) { auto audioFormat = AudioMessageFormat::fromCharFormat(cursor.charFormat()); auto lastPixelPos = audioFormat.playPosition(); auto duration = player->duration(); double part = 0.0; if (!duration || newPos > duration) { // workarund for https://bugreports.qt.io/browse/QTBUG-79282 part = newPos ? 1.0 : 0.0; } else { part = double(newPos) / double(duration); } auto newPixelPos = decltype(lastPixelPos)(scaleFillRect.width() * part); if (newPixelPos != lastPixelPos) { // qDebug("pos %lld of %lld", newPos, duration); audioFormat.setPlayPosition(newPixelPos); cursor.setCharFormat(audioFormat); } } } void ITEAudioController::playerStateChanged(QMediaPlayer::State state) { if (state == QMediaPlayer::StoppedState) { auto player = static_cast(sender()); quint32 playerId = player->property("playerId").toUInt(); int textCursorPos = player->property("cursorPos").toInt(); QTextCursor cursor = itc->findElement(playerId, textCursorPos); if (!cursor.isNull()) { auto audioFormat = AudioMessageFormat::fromCharFormat(cursor.charFormat()); audioFormat.setState(audioFormat.state() & ~AudioMessageFormat::Playing); audioFormat.setPlayPosition(0); cursor.setCharFormat(audioFormat); } qDebug("deleting player"); activePlayers.take(playerId)->deleteLater(); } } ITEAudioController::ITEAudioController(InteractiveText *itc, QObject *parent) : InteractiveTextElementController(itc, parent) { } QCursor ITEAudioController::cursor() { return _cursor; } psi-plus-snapshots-1.4.1456/3rdparty/qite/libqite/qiteaudio.h000066400000000000000000000054721370065651000240630ustar00rootroot00000000000000/* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #ifndef QITEAUDIO_H #define QITEAUDIO_H #include #include #include #include "qite.h" class QMediaPlayer; class QNetworkAccessManager; class AudioMessageFormat; class ITEAudioController : public InteractiveTextElementController { Q_OBJECT QCursor _cursor; QMap activePlayers; QNetworkAccessManager * nam = nullptr; // geometry QSize elementSize; QRect bgRect; QRect metaRect; int bgOutlineWidth; double baseSize; double bgRectRadius; QPointF btnCenter; int btnRadius; int signSize; int scaleOutlineWidth; QRectF scaleRect, scaleFillRect; int lastFontSize = 0; bool autoFetchMetadata = false; bool isOnButton(const QPoint &pos, const QRect &rect); void updateGeomtry(); public: typedef QList Histogram; // can be fetched via DeviceOpener::metadata()[amplitudes] static const int HistogramCompressedSize = 100; // amount of drawn columns ITEAudioController(InteractiveText *itc, QObject *parent); QSizeF intrinsicSize(QTextDocument *doc, int posInDocument, const QTextFormat &format); void drawITE(QPainter *painter, const QRectF &rect, int posInDocument, const QTextFormat &format); QTextCharFormat makeFormat(const QUrl &audioSrc, ITEMediaOpener *mediaOpener) const; void insert(const QUrl & audioSrc, ITEMediaOpener *mediaOpener = nullptr); // add new media to textedit. see QMediaPlayer::setMedia QCursor cursor(); // cursor form after last mose events inline void setAutoFetchMetadata(bool fetch = true) { autoFetchMetadata = fetch; } protected: bool mouseEvent(const InteractiveTextElementController::Event &event, const QRect &rect, QTextCursor &selected); void hideEvent(QTextCursor &selected); private slots: void playerPositionChanged(qint64); void playerStateChanged(QMediaPlayer::State); }; #endif // QITEAUDIO_H psi-plus-snapshots-1.4.1456/3rdparty/qite/libqite/qiteaudiorecorder.cpp000066400000000000000000000267621370065651000261510ustar00rootroot00000000000000/* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #include "qiteaudiorecorder.h" #include "qiteaudio.h" #include #include #include #include #include #include #include #include #include #include #include template struct SoloFrameDefault { enum { Default = 0 }; }; template struct SoloFrame { SoloFrame() : data(T(SoloFrameDefault::Default)) { } SoloFrame(T sample) : data(sample) { } SoloFrame &operator=(const SoloFrame &other) { data = other.data; return *this; } T data; T average() const { return data; } void clear() { data = T(SoloFrameDefault::Default); } }; template struct PeakValue { static const T value = std::numeric_limits::max(); }; template <> struct PeakValue { static constexpr float value = float(1.00003); }; template void handle(const QAudioBuffer &buffer, AudioRecorder::Quantum &quantum, QByteArray &collector, quint8 &maxVal) { const T *data = buffer.constData(); auto peakvalue = qreal(PeakValue::value); auto format = buffer.format(); int countLeft = format.framesForDuration(quantum.timeLeft); Q_ASSERT(countLeft > 0); for (int i = 0; i < buffer.frameCount(); i++) { quantum.sum += qreal(qAbs(data[i].average())) / peakvalue; quantum.count++; countLeft--; if (!countLeft) { auto value = quint8((quantum.sum / qreal(quantum.count)) * 255.0); if (value > maxVal) { maxVal = value; } // qDebug() << int((quantum.sum / qreal(quantum.count)) * 255.0); collector.append(char(value)); if (collector.size() == collector.capacity()) { collector.reserve(collector.capacity() + AudioRecorder::HistogramMemSize); } quantum = AudioRecorder::Quantum(); countLeft = format.framesForDuration(quantum.timeLeft); } } if (countLeft) { quantum.timeLeft = format.durationForFrames(countLeft); } } AudioRecorder::AudioRecorder(QObject *parent) : QObject(parent) { _recorder = new QAudioRecorder(this); // qDebug() << "supported codecs for recorder:" << _recorder->supportedAudioCodecs(); // qDebug() << "supported containers for recorder:" << _recorder->supportedContainers(); _probe = new QAudioProbe(this); _probe->setSource(_recorder); QAudioEncoderSettings audioSettings; audioSettings.setCodec("audio/x-opus"); audioSettings.setQuality(QMultimedia::HighQuality); //_recorder->setEncodingSettings(audioSettings, QVideoEncoderSettings(), "audio/ogg"); _recorder->setEncodingSettings(audioSettings, QVideoEncoderSettings(), "video/quicktime, variant=(string)iso"); connect(_recorder, &QAudioRecorder::durationChanged, this, [this](qint64 duration) { _duration = duration; }); connect(_recorder, &QAudioRecorder::stateChanged, this, [this]() { if (_recorder->state() == QAudioRecorder::StoppedState && _maxVolume) { // compress amplitudes.. auto volumeK = 255.0 / double(_maxVolume); // amplificator if (volumeK > 8) { volumeK = 8; // don't be mad on showing silence } auto step = _amplitudes.size() / double(ITEAudioController::HistogramCompressedSize); _compressedHistorgram.reserve(ITEAudioController::HistogramCompressedSize); for (int i = 0; i < ITEAudioController::HistogramCompressedSize; i++) { int prev = int(step * i); int curr = int(step * (i + 1)); if (curr == _amplitudes.size()) { curr = _amplitudes.size() - 1; } int sum = 0; for (int j = prev; j <= curr; j++) { sum += quint8(_amplitudes[j]); } _compressedHistorgram.append(int(sum / double(curr - prev + 1) * volumeK)); } QStringList columns; std::transform(_compressedHistorgram.begin(), _compressedHistorgram.end(), std::back_inserter(columns), [](auto const &v) { return QString::number(v); }); // qDebug() << columns.join(","); _amplitudes.clear(); _amplitudes.squeeze(); #ifdef ITE_EMBED_HISTOGRAM // it's somewhat buggy with Qt since it not always writes metainfo at least in 5.11.2 QFile f(_recorder->outputLocation().toLocalFile()); QByteArray buffer; buffer.resize(4096 + 1024); qint64 lastPos = 0; if (f.open(QIODevice::ReadWrite)) { qint64 bytes; while ((bytes = f.read(buffer.data(), qint64(buffer.size()))) > 0) { auto index = QByteArray::fromRawData(buffer.data(), int(bytes)).indexOf("AMPLDIAGSTART"); if (index >= 0) { f.seek( lastPos + index + int(sizeof("AMPLDIAGSTART["))); // it's not a mistake with sizeof. It's [ is just escaped f.write(columns.join(",").toLatin1().replace(',', "\\,")); f.write("\\]AMPLDIAGEND"); f.flush(); break; } lastPos += 4096; f.seek(lastPos); } f.close(); } #else if (_isTmpFile) { QString fn = _recorder->outputLocation().toLocalFile(); QFile f(fn); f.open(QIODevice::ReadOnly); _audioData = f.readAll(); f.close(); f.remove(); } else { QFile metaFile(_recorder->outputLocation().toLocalFile()+".amplitudes"); if (metaFile.open(QIODevice::WriteOnly)) { metaFile.write(columns.join(",").toLatin1()); metaFile.close(); } } #endif emit recorded(); } if (_recorder->state() == QAudioRecorder::StoppedState && _maxDurationTimer && _maxDurationTimer->isActive()) { delete _maxDurationTimer; _maxDurationTimer = nullptr; } if (!_destroying) emit stateChanged(); }); connect(_probe, &QAudioProbe::audioBufferProbed, this, [this](const QAudioBuffer &buffer) { auto format = buffer.format(); if (format.channelCount() > 2) { qWarning("unsupported amount of channels: %d", format.channelCount()); return; } if (format.sampleType() == QAudioFormat::SignedInt) { switch (format.sampleSize()) { case 8: if (format.channelCount() == 2) handle(buffer, _quantum, _amplitudes, _maxVolume); else handle>(buffer, _quantum, _amplitudes, _maxVolume); break; case 16: if (format.channelCount() == 2) handle(buffer, _quantum, _amplitudes, _maxVolume); else handle>(buffer, _quantum, _amplitudes, _maxVolume); break; } } else if (format.sampleType() == QAudioFormat::UnSignedInt) { switch (format.sampleSize()) { case 8: if (format.channelCount() == 2) handle(buffer, _quantum, _amplitudes, _maxVolume); else handle>(buffer, _quantum, _amplitudes, _maxVolume); break; case 16: if (format.channelCount() == 2) handle(buffer, _quantum, _amplitudes, _maxVolume); else handle>(buffer, _quantum, _amplitudes, _maxVolume); break; } } else if (format.sampleType() == QAudioFormat::Float) { if (format.channelCount() == 2) handle(buffer, _quantum, _amplitudes, _maxVolume); else handle>(buffer, _quantum, _amplitudes, _maxVolume); } else { qWarning("unsupported audio sample type: %d", int(format.sampleType())); } }); connect(_recorder, static_cast(&QMediaRecorder::error), this, [this](QMediaRecorder::Error error) { if (error != QMediaRecorder::Error::NoError) { emit this->error(_recorder->errorString()); } }); connect(_recorder, &QMediaRecorder::statusChanged, this, [this](QMediaRecorder::Status status) { if (status == QMediaRecorder::RecordingStatus) { emit recordingStarted(); } }); } void AudioRecorder::record() { cleanup(); _isTmpFile = true; QTemporaryFile *tmpFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/qite-record-XXXXXX.ogg"), this); tmpFile->setAutoRemove(false); tmpFile->open(); QString fn = tmpFile->fileName(); delete tmpFile; recordToFile(fn); } void AudioRecorder::record(const QString &fileName) { cleanup(); recordToFile(fileName); } void AudioRecorder::recordToFile(const QString &fileName) { _quantum = Quantum(); _amplitudes.clear(); _amplitudes.reserve(HistogramMemSize); _maxVolume = 0; _recorder->setOutputLocation(QUrl::fromLocalFile(fileName)); #ifdef ITE_EMBED_HISTOGRAM if (_recorder->isMetaDataWritable()) { auto reserved = QLatin1String("AMPLDIAGSTART[000") + QString(",000").repeated(ITEAudioController::HistogramCompressedSize - 1) + QLatin1String("]AMPLDIAGEND"); _recorder->setMetaData(QMediaMetaData::Comment, reserved); } #endif if (_maxDuration != -1) { _maxDurationTimer = new QTimer(this); _maxDurationTimer->setSingleShot(true); _maxDurationTimer->setInterval(_maxDuration); connect(_maxDurationTimer, &QTimer::timeout, this, &AudioRecorder::stop); } _recorder->record(); } void AudioRecorder::stop() { _duration = _recorder->duration(); _recorder->stop(); } void AudioRecorder::cleanup() { _destroying = true; if (_recorder->state() == QAudioRecorder::RecordingState) _recorder->stop(); _destroying = false; _isTmpFile = false; _compressedHistorgram.clear(); _audioData.clear(); _audioData.squeeze(); if (_maxDurationTimer) { delete _maxDurationTimer; _maxDurationTimer = nullptr; } } psi-plus-snapshots-1.4.1456/3rdparty/qite/libqite/qiteaudiorecorder.h000066400000000000000000000052561370065651000256110ustar00rootroot00000000000000/* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #ifndef QITEAUDIORECORDER_H #define QITEAUDIORECORDER_H #include class QAudioRecorder; class QAudioProbe; class QTemporaryFile; class QTimer; class AudioRecorder : public QObject { Q_OBJECT public: static const qint64 HistogramQuantumSize = 10000; // 10ms. 100 values per second static const int HistogramMemSize = int(1e6) / HistogramQuantumSize * 20; // for 20 secs. ~ 2Kb struct Quantum { qint64 timeLeft = HistogramQuantumSize; // to generate next value for aplitude amplitudes qreal sum = 0.0; int count = 0; }; explicit AudioRecorder(QObject *parent = nullptr); void record(); // for short-term records void record(const QString &fileName); void stop(); inline void setMaxDuration(int ms) { _maxDuration = ms; } // set it before record() call or don't set at all inline auto recorder() const { return _recorder; } inline auto maxVolume() const { return _maxVolume; } // peak value of vlume over all the recording. inline auto amplitudes() const { return _compressedHistorgram; } inline auto data() const { return _audioData; } inline quint64 duration() const { return _duration; } private: void cleanup(); void recordToFile(const QString &fileName); signals: void stateChanged(); void recordingStarted(); void recorded(); void error(const QString &message); public slots: private: QAudioRecorder *_recorder = nullptr; QAudioProbe * _probe; Quantum _quantum; QByteArray _amplitudes; QByteArray _compressedHistorgram; QByteArray _audioData; QTimer * _maxDurationTimer = nullptr; qint64 _duration; int _maxDuration = -1; quint8 _maxVolume; bool _destroying = false; bool _isTmpFile = false; }; #endif // QITEAUDIORECORDER_H psi-plus-snapshots-1.4.1456/3rdparty/qite/libqite/qiteprogress.cpp000066400000000000000000000430251370065651000251550ustar00rootroot00000000000000/* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #include "qiteprogress.h" #include #include #include #include #include #include #include class ProgressMessageFormat : public InteractiveTextFormat { public: enum Property { Text, MinValue, MaxValue, CurrentValue, /* in pixels */ State, }; enum Flag { Playing = 0x1, MouseOnButton = 0x2, MouseOnTrackbar = 0x4 }; Q_DECLARE_FLAGS(Flags, Flag) using InteractiveTextFormat::InteractiveTextFormat; static ProgressMessageFormat fromCharFormat(const QTextCharFormat &fmt) { return static_cast(fmt); } QString text() const { return property(ProgressMessageFormat::Text).toString(); } Flags state() const { return Flags(property(ProgressMessageFormat::State).toInt()); } void setState(const Flags &state) { setProperty(State, int(state)); } double currentValue() const { return property(ProgressMessageFormat::CurrentValue).toDouble(); } void setCurrentValue(double position) { setProperty(ProgressMessageFormat::CurrentValue, position); } double minValue() const { return property(ProgressMessageFormat::MinValue).toDouble(); } void setMinValue(double position) { setProperty(ProgressMessageFormat::MinValue, position); } double maxValue() const { return property(ProgressMessageFormat::MaxValue).toDouble(); } void setMaxValue(double position) { setProperty(ProgressMessageFormat::MaxValue, position); } }; Q_DECLARE_OPERATORS_FOR_FLAGS(ProgressMessageFormat::Flags) //---------------------------------------------------------------------------- // ITEProgressController //---------------------------------------------------------------------------- QSizeF ITEProgressController::intrinsicSize(QTextDocument *doc, int posInDocument, const QTextFormat &format) { Q_UNUSED(doc) Q_UNUSED(posInDocument) const QTextCharFormat charFormat = format.toCharFormat(); auto psize = QFontMetrics(charFormat.font()).height(); if (lastFontSize != psize) { lastFontSize = psize; updateGeomtry(); } return elementSize; } void ITEProgressController::updateGeomtry() { // compute geomtry of player baseSize = lastFontSize / 12.0; int elementPadding = int(baseSize * 4); bgOutlineWidth = baseSize < 2 ? 2 : int(baseSize); btnRadius = int(baseSize * 10); int elementHeight = btnRadius * 2 + int(elementPadding * 2); auto rightPadding = int(baseSize * 5); // elementHeight already includes 2 paddings: to the lest and to the right of button elementSize = QSize(elementHeight + int(100 * baseSize) + rightPadding, elementHeight); bgRect = QRect(QPoint(0, 0), elementSize); bgRect.adjust(bgOutlineWidth / 2, bgOutlineWidth / 2, -bgOutlineWidth / 2, -bgOutlineWidth / 2); // outline should fit the format rect. bgRectRadius = bgRect.height() / 5; btnCenter = QPoint(elementSize.height() / 2, elementSize.height() / 2); signSize = btnRadius / 2; // next to the button we need histgram/title and scale. int left = elementHeight; int right = elementSize.width() - rightPadding; metaRect = QRect(QPoint(left, bgRect.top() + int(baseSize * 3)), QPoint(right, bgRect.top() + int(bgRect.height() * 0.5))); // draw scale scaleOutlineWidth = bgOutlineWidth; QPointF scaleTopLeft( left, metaRect.bottom() + baseSize * 4); // = bgRect.topLeft() + QPointF(left, bgRect.height() * 0.7); QPointF scaleBottomRight(right, scaleTopLeft.y() + baseSize * 4); scaleRect = QRectF(scaleTopLeft, scaleBottomRight); scaleFillRect = scaleRect.adjusted(scaleOutlineWidth / 2, scaleOutlineWidth / 2, -scaleOutlineWidth / 2, -scaleOutlineWidth / 2); } void ITEProgressController::drawITE(QPainter *painter, const QRectF &rect, int posInDocument, const QTextFormat &format) { const ProgressMessageFormat audioFormat = ProgressMessageFormat::fromCharFormat(format.toCharFormat()); // qDebug() << audioFormat.id(); painter->setRenderHints(QPainter::HighQualityAntialiasing); QPen bgPen(QColor(100, 200, 100)); // TODO name all the magic colors bgPen.setWidth(bgOutlineWidth); painter->setPen(bgPen); painter->setBrush(QColor(150, 250, 150)); painter->drawRoundedRect(bgRect.translated(int(rect.left()), int(rect.top())), bgRectRadius, bgRectRadius); // draw button if (audioFormat.state() & ProgressMessageFormat::MouseOnButton) { painter->setBrush(QColor(130, 230, 130)); } else { painter->setBrush(QColor(120, 220, 120)); } auto xBtnCenter = btnCenter + rect.topLeft(); painter->drawEllipse(xBtnCenter, btnRadius, btnRadius); // draw pause/play QPen signPen((QColor(Qt::white))); signPen.setWidth(bgOutlineWidth); painter->setPen(signPen); painter->setBrush(QColor(Qt::white)); bool isPlaying = audioFormat.state() & ProgressMessageFormat::Playing; if (isPlaying) { QRectF bar(0, 0, signSize / 3, signSize * 2); bar.moveCenter(xBtnCenter - QPointF(signSize / 2, 0)); painter->drawRect(bar); bar.moveCenter(xBtnCenter + QPointF(signSize / 2, 0)); painter->drawRect(bar); } else { QPointF play[3] = { xBtnCenter - QPoint(signSize / 2, signSize), xBtnCenter - QPoint(signSize / 2, -signSize), xBtnCenter + QPoint(signSize, 0) }; painter->drawConvexPolygon(play, 3); } // draw scale QPen scalePen(QColor(100, 200, 100)); scalePen.setWidth(scaleOutlineWidth); painter->setPen(scalePen); painter->setBrush(QColor(120, 220, 120)); QRectF xScaleRect(scaleRect.translated(rect.topLeft())); painter->drawRoundedRect(xScaleRect, scaleRect.height() / 2, scaleRect.height() / 2); // draw played part auto playPos = audioFormat.currentValue(); if (playPos) { painter->setPen(Qt::NoPen); painter->setBrush(QColor(170, 255, 170)); QRectF playedRect(scaleFillRect.translated(rect.topLeft())); // to the width of the scale border playedRect.setWidth(playPos); painter->drawRoundedRect(playedRect, playedRect.height() / 2, playedRect.height() / 2); } painter->setPen(QColor(70, 150, 70)); painter->drawText(metaRect.translated(rect.topLeft().toPoint()), audioFormat.text()); } QTextCharFormat ITEProgressController::makeFormat() const { ProgressMessageFormat fmt(objectType, itc->nextId()); fmt.setFontPointSize(itc->textEdit()->currentFont().pointSize()); return std::move(fmt); } void ITEProgressController::insert(double min, double max, const QString &text) { auto fmt = makeFormat(); itc->insert(static_cast(fmt)); } bool ITEProgressController::mouseEvent(const Event &event, const QRect &rect, QTextCursor &selected) { Q_UNUSED(rect); bool onButton = false; bool onTrackbar = false; if (event.type != EventType::Leave) { onButton = isOnButton(event.pos, bgRect); if (!onButton) { onTrackbar = scaleRect.contains(event.pos); } } if (onButton || onTrackbar) { _cursor = QCursor(Qt::PointingHandCursor); } else { _cursor = QCursor(Qt::ArrowCursor); } ProgressMessageFormat format = ProgressMessageFormat::fromCharFormat(selected.charFormat()); ProgressMessageFormat::Flags state = format.state(); bool onButtonChanged = bool(state & ProgressMessageFormat::MouseOnButton) != onButton; bool onTrackbarChanged = bool(state & ProgressMessageFormat::MouseOnTrackbar) != onTrackbar; bool playStateChanged = false; bool positionSet = false; if (onButtonChanged) { state ^= ProgressMessageFormat::MouseOnButton; } if (onTrackbarChanged) { state ^= ProgressMessageFormat::MouseOnTrackbar; } #if 0 auto playerId = format.id(); if (event.type == EventType::Click) { if (onButton) { playStateChanged = true; state ^= ProgressMessageFormat::Playing; auto player = activePlayers.value(playerId); if (state & ProgressMessageFormat::Playing) { if (!player) { player = new QMediaPlayer(this); player->setProperty("playerId", playerId); player->setProperty("cursorPos", selected.anchor()); activePlayers.insert(playerId, player); ITEMediaOpener *opener = format.mediaOpener(); QUrl url = format.url(); QIODevice * stream = opener ? opener->open(url) : nullptr; if (stream) connect(player, &QMediaPlayer::destroyed, this, [opener, stream]() { opener->close(stream); }); player->setMedia(url, stream); auto part = double(format.playPosition()) / double(scaleFillRect.width()); if (player->duration() > 0) { player->setPosition(qint64(player->duration() * part)); player->setNotifyInterval(int(player->duration() / double(metaRect.width()) * 3.0)); // 3 px connect(player, SIGNAL(positionChanged(qint64)), this, SLOT(playerPositionChanged(qint64))); } else { connect(player, &QMediaPlayer::durationChanged, [player, part, this](qint64 duration) { // the timer is a workaround for some Qt bug QTimer::singleShot(0, [player, part, this, duration]() { if (part > 0) { // don't jump back if event came quite late player->setPosition(qint64(duration * part)); } // qDebug() << int(duration / double(metaRect.width())); player->setNotifyInterval(int(duration / 1000.0 / double(metaRect.width()) * 3.0)); connect(player, SIGNAL(positionChanged(qint64)), this, SLOT(playerPositionChanged(qint64))); }); }); player->setNotifyInterval(50); // while we don't know duration, lets use quite small value } // check for title in metadata connect(player, &QMediaPlayer::metaDataAvailableChanged, this, [this, player](bool available) { if (available) { auto title = player->metaData(QMediaMetaData::Title).toString(); if (title.isEmpty()) { return; } quint32 playerId = player->property("playerId").toUInt(); int textCursorPos = player->property("cursorPos").toInt(); QTextCursor cursor = itc->findElement(playerId, textCursorPos); if (cursor.isNull()) { return; } auto format = ProgressMessageFormat::fromCharFormat(cursor.charFormat().toCharFormat()); if (format.metaData().type() == QVariant::List) { return; // seems we have amplitudes already } format.setMetaData(title); cursor.setCharFormat(format); } }); // try to extract from metadata and store amplitudes connect(player, static_cast( &QMediaPlayer::metaDataChanged), [=](const QString &key, const QVariant &value) { QString comment; int index = 0; if (key != QMediaMetaData::Comment || (comment = value.toString()).isEmpty() || !comment.startsWith(QLatin1String("AMPLDIAGSTART")) || (index = comment.indexOf("AMPLDIAGEND")) == -1) { return; // In comment we keep amplitudes. We don't expect anything else } auto sl = comment .mid(int(sizeof("AMPLDIAGSTART")), index - int(sizeof("AMPLDIAGSTART")) - 1) .split(","); QList amplitudes; amplitudes.reserve(sl.size()); std::transform(sl.constBegin(), sl.constEnd(), std::back_inserter(amplitudes), [](const QString &v) { auto fv = v.toFloat() / float(255.0); if (fv > 1) { return 1.0f; } return fv; }); quint32 playerId = player->property("playerId").toUInt(); int textCursorPos = player->property("cursorPos").toInt(); QTextCursor cursor = itc->findElement(playerId, textCursorPos); if (cursor.isNull()) { return; } auto format = ProgressMessageFormat::fromCharFormat(cursor.charFormat().toCharFormat()); format.setMetaData(QVariant::fromValue(amplitudes)); cursor.setCharFormat(format); }); connect(player, SIGNAL(stateChanged(QMediaPlayer::State)), this, SLOT(playerStateChanged(QMediaPlayer::State))); QObject::connect(player, &QMediaPlayer::mediaStatusChanged, [=]() { qDebug() << "Media status changed:" << player->mediaStatus(); }); QObject::connect(player, static_cast(&QMediaPlayer::error), [=](QMediaPlayer::Error error) { qDebug() << "Error occurred:" << error; }); } // player->setVolume(0); player->play(); } else { if (player) { player->pause(); // player->disconnect(this);activePlayers.take(playerId)->deleteLater(); } } } else if (onTrackbar) { // include outline to clickable area but compute only for inner part double part; if (event.pos.x() < scaleFillRect.left()) { part = 0; } else if (event.pos.x() >= scaleFillRect.right()) { part = 1; } else { part = double(event.pos.x() - scaleFillRect.left()) / double(scaleFillRect.width()); } auto player = activePlayers.value(playerId); if (player) { qDebug("Set position to %d", int(part * 100)); player->setPosition(qint64(player->duration() * part)); } // else it's not playing likely format.setCurrentValue(quint32(double(scaleFillRect.width()) * part)); positionSet = true; } } if (onButtonChanged || playStateChanged || positionSet) { format.setState(state); selected.setCharFormat(format); } #endif return true; } void ITEProgressController::hideEvent(QTextCursor &selected) { auto fmt = ProgressMessageFormat::fromCharFormat(selected.charFormat()); #if 0 auto player = activePlayers.value(fmt.id()); // qDebug() << "hiding player" << fmt.id(); if (player) { player->stop(); } #endif } bool ITEProgressController::isOnButton(const QPoint &pos, const QRect &rect) { QPoint rel = pos - rect.topLeft(); return QVector2D(btnCenter).distanceToPoint(QVector2D(rel)) <= btnRadius; } ITEProgressController::ITEProgressController(InteractiveText *itc) : InteractiveTextElementController(itc) { } QCursor ITEProgressController::cursor() { return _cursor; } psi-plus-snapshots-1.4.1456/3rdparty/qite/libqite/qiteprogress.h000066400000000000000000000037521370065651000246250ustar00rootroot00000000000000/* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #ifndef QITEPROGRESS_H #define QITEPROGRESS_H #include #include #include "qite.h" class ITEProgressController : public InteractiveTextElementController { Q_OBJECT QCursor _cursor; // geometry QSize elementSize; QRect bgRect; QRect metaRect; int bgOutlineWidth; double baseSize; double bgRectRadius; QPointF btnCenter; int btnRadius; int signSize; int scaleOutlineWidth; QRectF scaleRect, scaleFillRect; int lastFontSize = 0; bool isOnButton(const QPoint &pos, const QRect &rect); void updateGeomtry(); public: ITEProgressController(InteractiveText *itc); QSizeF intrinsicSize(QTextDocument *doc, int posInDocument, const QTextFormat &format); void drawITE(QPainter *painter, const QRectF &rect, int posInDocument, const QTextFormat &format); QTextCharFormat makeFormat() const; void insert(double min, double max, const QString &text); QCursor cursor(); // cursor form after last mose events protected: bool mouseEvent(const InteractiveTextElementController::Event &event, const QRect &rect, QTextCursor &selected); void hideEvent(QTextCursor &selected); }; #endif // QITEPROGRESS_H psi-plus-snapshots-1.4.1456/3rdparty/qite/main.cpp000066400000000000000000000003071370065651000217150ustar00rootroot00000000000000#include "mainwindow.h" #include int main(int argc, char *argv[]) { QApplication a(argc, argv); Q_INIT_RESOURCE(main); MainWindow w; w.show(); return a.exec(); } psi-plus-snapshots-1.4.1456/3rdparty/qite/main.qrc000066400000000000000000000001521370065651000217160ustar00rootroot00000000000000 recorder-microphone.png psi-plus-snapshots-1.4.1456/3rdparty/qite/mainwindow.cpp000066400000000000000000000102411370065651000231430ustar00rootroot00000000000000/* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #include "mainwindow.h" #include "qite.h" #include "qiteaudio.h" #include "qiteaudiorecorder.h" #include "ui_mainwindow.h" #include #include #include #include #include #include #include #include #include #include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); recordAction = new QAction(QIcon(":/icon/recorder-microphone.png"), "Record", this); ui->mainToolBar->addAction(recordAction); connect(recordAction, &QAction::triggered, this, &MainWindow::recordMic); auto fontCombo = new QComboBox(this); for (int i = 8; i < 26; i++) { fontCombo->addItem(QString::number(i)); auto comboAct = ui->mainToolBar->addWidget(fontCombo); fontCombo->setCurrentText(QString::number(ui->textEdit->fontPointSize())); comboAct->setVisible(true); connect(fontCombo, &QComboBox::currentTextChanged, this, [this](const QString &text) { ui->textEdit->setFontPointSize(text.toInt()); }); } auto itc = new InteractiveText(ui->textEdit); atc = new ITEAudioController(itc); atc->setAutoFetchMetadata(true); auto musicDir = QStandardPaths::writableLocation(QStandardPaths::MusicLocation); auto nameFilters = QStringList() << "*.aac" << "*.flac" << "*.mp3" << "*.ogg" << "*.webm"; QDirIterator it(musicDir, nameFilters, QDir::Files | QDir::NoSymLinks, QDirIterator::Subdirectories); QStringList files; int count = 2000; while (it.hasNext() && count--) files << it.next(); std::srand(unsigned(std::time(nullptr))); std::random_shuffle(files.begin(), files.end()); while (files.size()) { QString fileToPlay = files.takeLast(); QFileInfo fi(fileToPlay); auto url = QUrl::fromLocalFile(fileToPlay); ui->textEdit->append(fi.fileName() + "
"); atc->insert(url); } } MainWindow::~MainWindow() { delete ui; } void MainWindow::recordMic() { if (!recorder) { recorder = new AudioRecorder(this); connect(recorder, &AudioRecorder::stateChanged, this, [this]() { if (recorder->recorder()->state() == QAudioRecorder::StoppedState) { recordAction->setIcon(QIcon(":/icon/recorder-microphone.png")); } if (recorder->recorder()->state() == QAudioRecorder::RecordingState) { recordAction->setIcon(style()->standardIcon(QStyle::SP_MediaStop)); } }); connect(recorder, &AudioRecorder::recorded, this, [this]() { if (recorder->maxVolume() / double(std::numeric_limitsmaxVolume())>::max()) > 0.1) { auto fileName = QFileInfo(recorder->recorder()->outputLocation().toLocalFile()).absoluteFilePath(); qDebug("file=%s", qPrintable(fileName)); atc->insert(QUrl::fromLocalFile(fileName)); } else { ui->textEdit->append("Prefer silence?"); } }); } if (recorder->recorder()->state() == QAudioRecorder::StoppedState) { recorder->record(QString("test-%1.ogg").arg(QDateTime::currentSecsSinceEpoch())); } else { recorder->stop(); } } psi-plus-snapshots-1.4.1456/3rdparty/qite/mainwindow.h000066400000000000000000000023541370065651000226160ustar00rootroot00000000000000/* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #ifndef MAINWINDOW_H #define MAINWINDOW_H #include namespace Ui { class MainWindow; } class AudioRecorder; class ITEAudioController; class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void recordMic(); private: Ui::MainWindow * ui; QAction * recordAction; AudioRecorder * recorder = nullptr; ITEAudioController *atc; }; #endif // MAINWINDOW_H psi-plus-snapshots-1.4.1456/3rdparty/qite/mainwindow.ui000066400000000000000000000021261370065651000230010ustar00rootroot00000000000000 MainWindow 0 0 400 300 Interactive QTextEdit 0 0 400 23 TopToolBarArea false psi-plus-snapshots-1.4.1456/3rdparty/qite/qite.pro000066400000000000000000000020751370065651000217550ustar00rootroot00000000000000#------------------------------------------------- # # Project created by QtCreator 2018-10-20T20:42:43 # #------------------------------------------------- QT += core gui multimedia CONFIG += c++14 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = qite TEMPLATE = app # The following define makes your compiler emit warnings if you use # any feature of Qt which has been marked as deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if you use deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 include(libqite/libqite.pri) SOURCES += \ main.cpp \ mainwindow.cpp HEADERS += \ mainwindow.h FORMS += \ mainwindow.ui RESOURCES += \ main.qrc psi-plus-snapshots-1.4.1456/3rdparty/qite/recorder-microphone.png000066400000000000000000000005601370065651000247420ustar00rootroot00000000000000PNG  IHDR sgAMA a'IDATH-KQXG`XL,blYÆ݅‚ ² 1A4(h "dŏlra^_ms|_<9<6‰At7C΄SClGɔ)%;¶|wplra~ F4ye@xʀm KD"s*IENDB`psi-plus-snapshots-1.4.1456/CHANGELOG000066400000000000000000000101721370065651000167660ustar00rootroot00000000000000New in 2.0 (UNRELEASED) - Program now requires Qt >= 5.5.x and compilers with support of C++14 - Rewritten code related to spell checkers support (hunspell, aspell) - Improved saving of program settings on exit - Significantly improved options dialog - Multiple fixes and optimizations in UI - Added (optional) support of QtKeychain: allows to store passwords in more secure way (using special mechanisms from your operating system) - Added support of multi-language topics in MUC - Added HTTP File Upload (XEP-0363) implementation - Added Result Set Management (XEP-0059) implementation - Fixed crashes on Wayland due to usage of X11 specific calls - Fixed build for macOS using cmake + different macOS specific improvements - Different MS Windows specific improvements - Added official support of Haiku operating system - PsiMedia (Psi Multimedia) library was rewritten and now is loaded as usual Psi plugin instead of loading library directly - Changed plugins API (plugins from old releases of Psi will not work): + changes for implementation of OMEMO plugin + changes for moving of OpenPGP implementation from program core to special OpenPGP plugin (GnuPG Key Manager plugin is now part of OpenPGP plugin) + changes for updated Psi Multimedia plugin (audio and video calls) + changes for getting rid of dependency from QtWebKit in few plugins + some minor changes - Rewritten file transfers dialog - Added support of Jingle File Transfer (XEP-0234). Old (deprecated) XEPs for file exchange are still supported in Psi, but may be deleted in future releases - Disable usage of system proxies (it was added and enabled inside Qt library by Qt developers but does not work properly and conflicts with Psi settings) - Grand code refactoring New in 1.4 - Fixed reconnection on Stream Management resume failure - Fixed compatibility with Qt >= 5.10 - Improved media contents previews for WebKit version (YouTube and files) - CMake-based build fixes New in 1.3 - Fixed connection to Openfire server - Fixed connections by IP (fixes socks file transfer as well) - Fixed compilation with new version of Enchant - Fixed broken file transfer after reconnection - Minor UI fixes New in 1.2 - Removed AIM and MSN icons. - Fixed debug build on Visual Studio. - Font and windows sizes adjusted for regular fullhd displays. - Fixed crashes online account remove and contact delete from another resource. - History DB and dialog improvements. - Fixed crash on unrecognized dictionary encodings in hunspell checker. - Added option to disable previews in webkit builds. - Fixed Youtube links recognition. New in 1.1 - Fixed a few crashes. Seems to be pretty stable now. - Contact list was rewritten once again. - Improved stream management (no more disconnects). - Better support for HIDPI displays. - More Psi+ patches merged. - JDNS was replaced with QDnsLookup for Qt5 builds. - CMake support. - A lot of minor UI fixes. New in 1.0 - Almost all Psi+ patches were merged (a lot of features). - Plugins support is enabled by default. New in 0.15 - Merge many changes from Psi+. - New message history browser. - New, fast contact list window. - TURN proxying for voice calls. - Store data in more standardized locations based on the platform. - No longer dependent on the Qt3Support library. - Domains ending in .local now always work, whether via DNS server or mdns. - Windows 64-bit and Mac 64-bit now supported. Mac PPC deprecated. - Legacy SSL port probe feature removed. - Various small features and bugfixes. New in 0.14 - Added color options to the chat window. - Can now specify a reason for kick/ban in groupchat. - Improved User Info window, to show more fields and photo view/save. - Support for Enchant as an alternative to Aspell. - Commandline interface now supports choosing profile and setting status. - D-BUS interface now supports setting status and indicating sleep/wake. - Fixed voice calling compatibility bugs with Pidgin and Empathy. - Various other minor improvements and bugfixes. New in 0.13 - Voice calls (Jingle RTP). - Basic XMPP URI handler. - Ability to permanently trust certificates at connect time. - Mini command system (Ctrl+7 in chat window). - Various bugfixes. psi-plus-snapshots-1.4.1456/CMakeLists.txt000066400000000000000000000264121370065651000203200ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.1.0) # Set automoc and autouic policy if(POLICY CMP0071) if(${CMAKE_VERSION} VERSION_LESS "3.10.0") cmake_policy(SET CMP0071 OLD) message(STATUS "CMP0071 policy set to OLD") else() cmake_policy(SET CMP0071 NEW) message(STATUS "CMP0071 policy set to NEW") endif() endif() if(POLICY CMP0074) cmake_policy(SET CMP0074 NEW) message(STATUS "CMP0074 policy set to NEW") endif() if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") endif() # Psi or Psi+ detection option(PSI_PLUS "Build Psi+ client instead of Psi" ON) if(PSI_PLUS) add_definitions(-DPSI_PLUS) project(psi-plus) message(STATUS "===Building Psi+===") else() project(psi) message(STATUS "===Building Psi===") endif() # Define LINUX on Linux like as WIN32 on Windows and APPLE on macOS if(UNIX AND NOT (APPLE OR HAIKU)) set(LINUX ON) endif() # Check for submodules set(SBM_LIST ${PROJECT_SOURCE_DIR}/3rdparty/http-parser/http_parser.h ${PROJECT_SOURCE_DIR}/3rdparty/qhttp/qhttp.pro ${PROJECT_SOURCE_DIR}/3rdparty/qite/qite.pro ${PROJECT_SOURCE_DIR}/iris/CMakeLists.txt ${PROJECT_SOURCE_DIR}/src/libpsi/tools/CMakeLists.txt ) foreach(submodule ${SBM_LIST}) if(NOT EXISTS "${submodule}") message(FATAL_ERROR "Psi submodules not found.\nPlease run:\n====\ncd ${PROJECT_SOURCE_DIR}\ngit submodule init\ngit submodule update\n====\nbefore run cmake again") endif() endforeach() # Common options option( BUNDLED_IRIS "Build iris library bundled" ON ) option( ONLY_PLUGINS "Build psi plugins only" OFF ) option( USE_HUNSPELL "Build psi with hunspell spellcheck" ON ) option( USE_ENCHANT "Build psi with enchant spellcheck" OFF ) option( USE_ASPELL "Build psi with aspell spellcheck" OFF ) option( ENABLE_PLUGINS "Enable plugins" OFF ) set( CHAT_TYPE "BASIC" CACHE STRING "Type of chatlog engine. WEBKIT | WEBENGINE | BASIC") option( USE_CCACHE "Use ccache utility if found" ON ) option( VERBOSE_PROGRAM_NAME "Verbose output binary name" OFF ) #Experimental option( USE_CRASH "Enable builtin sigsegv handling" OFF ) option( USE_KEYCHAIN "Enable Qt5Keychain support" ON ) option( BUILD_PSIMEDIA "Build psimedia plugin if its code is found in sources tree. Works only with ENABLE_PLUGINS=ON" OFF ) option( ONLY_BINARY "Build and install only binary file" OFF ) option( INSTALL_EXTRA_FILES "Install sounds, iconsets, certs, client_icons.txt, themes" ON ) option( INSTALL_PLUGINS_SDK "Install sdk files to build plugins outside of project" OFF ) option( DEV_MODE "Enable prepare-bin-libs target for Windows OS only. Set PSI_DATADIR and PSI_LIBDIR to CMAKE_RUNTIME_OUTPUT_DIRECTORY to debug plugins for Linux only" OFF ) option( USE_DBUS "Enable DBUS support" ON ) # Iris options option( USE_QJDNS "Use qjdns/jdns library. Disabled by default for Qt5" OFF ) option( SEPARATE_QJDNS "Build qjdns as separate lib" OFF ) option( PRODUCTION "Build production version" OFF ) # Windows or MXE only option( USE_MXE "Use MXE cross-compilation" OFF ) option( ENABLE_PORTABLE "Create portable version of Psi+ in win32" OFF ) if(LINUX) option( USE_X11 "Enable X11 features support" ON ) option( USE_XSS "Enable Xscreensaver support" ON ) option( LIMIT_X11_USAGE "Disable usage of X11 features which may crash program" OFF ) endif() # Apple only if(APPLE) option( USE_SPARKLE "Use Sparkle for APPLE builds" ON ) option( USE_GROWL "Use growl for macOS builds" OFF ) option( USE_MAC_DOC "Use macOS dock" OFF ) endif() if(WIN32 AND ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug" OR ("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo"))) option( NO_DEBUG_OPTIMIZATION "Disable optimization for debug builds" OFF) endif() if( USE_HUNSPELL AND (USE_ENCHANT AND USE_ASPELL) ) message(FATAL_ERROR "Flags USE_HUNSPELL, USE_ASPELL and USE_ENCHANT cannot be enabled at the same time.\nPlease enable only one of them") elseif( USE_HUNSPELL AND USE_ASPELL ) message(FATAL_ERROR "Both flags USE_HUNSPELL and USE_ASPELL cannot be enabled at the same time.\nPlease enable only one of them") elseif( USE_ASPELL AND USE_ENCHANT ) message(FATAL_ERROR "Both flags USE_ASPELL and USE_ENCHANT cannot be enabled at the same time.\nPlease enable only one of them") elseif( USE_HUNSPELL AND USE_ENCHANT ) message(FATAL_ERROR "Both flags USE_HUNSPELL and USE_ENCHANT cannot be enabled at the same time.\nPlease enable only one of them") endif() if( ONLY_BINARY AND INSTALL_EXTRA_FILES ) message(FATAL_ERROR "Both flags ONLY_BINARY and INSTALL_EXTRA_FILES cannot be enabled at the same time.\nPlease enable only one of them") endif() set( GLOBAL_DEPENDS_DEBUG_MODE ON ) set( CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules" ) set(IS_WEBKIT OFF CACHE BOOL "Use webkit. Internal variable") set(IS_WEBENGINE OFF CACHE BOOL "Use webengine. Internal variable") string(TOLOWER "${CHAT_TYPE}" LCHAT_TYPE) if("${LCHAT_TYPE}" STREQUAL "webkit") add_definitions( -DWEBKIT ) set(IS_WEBKIT ON) message(STATUS "Chatlog type - QtWebKit") elseif("${LCHAT_TYPE}" STREQUAL "webengine") set(IS_WEBENGINE ON) find_package( Qt5Core 5.6.0 REQUIRED ) add_definitions( -DWEBKIT -DWEBENGINE=1 ) message(STATUS "Chatlog type - QtWebEngine") else() set(IS_WEBKIT OFF) set(IS_WEBENGINE OFF) message(STATUS "Chatlog type - Basic") endif() message(STATUS "System name - ${CMAKE_SYSTEM_NAME}") if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug" OR ("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo")) set(ISDEBUG ON) endif() if(ONLY_PLUGINS) set(ENABLE_PLUGINS ON) endif() if(ENABLE_PLUGINS AND BUILD_PSIMEDIA) if(PSI_PLUS) option(USE_PSI "use psi" OFF) else() option(USE_PSI "use psi" ON) endif() option(BUILD_DEMO "build demo" OFF) elseif(NOT ENABLE_PLUGINS AND BUILD_PSIMEDIA) message(FATAL_ERROR "BUILD_PSIMEDIA flag not works without ENABLE_PLUGINS flag.\nPlease enable ENABLE_PLUGINS flag or disable BUILD_PSIMEDIA flag") endif() if(USE_CRASH) add_definitions(-DUSE_CRASH) endif() # Detect MXE cross-compilation if( (CMAKE_CROSSCOMPILING) AND (DEFINED MSYS) ) message(STATUS "MXE environment detected") set(USE_MXE ON) message(STATUS "MXE root path: ${CMAKE_PREFIX_PATH}") if(IS_WEBENGINE) message(FATAL_ERROR "Webengine is not available in MXE. Please set the CHAT_TYPE variable to Webkit or Basic") endif() endif() # For GNU/Linux and *BSD systems: if(UNIX AND NOT (APPLE OR HAIKU)) if(USE_X11) add_definitions( -DHAVE_X11 ) message(STATUS "X11 features support - ENABLED") endif() if(LIMIT_X11_USAGE) add_definitions( -DLIMIT_X11_USAGE ) message(STATUS "Unsafe X11 features support - DISABLED") endif() add_definitions( -DHAVE_FREEDESKTOP -DAPP_PREFIX=${CMAKE_INSTALL_PREFIX} -DAPP_BIN_NAME=${PROJECT_NAME} ) if(USE_XSS) add_definitions( -DHAVE_XSS ) message(STATUS "Xscreensaver support - ENABLED") endif() if(USE_DBUS) message(STATUS "DBus support - ENABLED") endif() endif() # Detect *BSD systems STRING (REGEX MATCH "BSD" PROJECT_OS_BSD ${CMAKE_SYSTEM_NAME}) if(PROJECT_OS_BSD) add_definitions(-DIOAPI_NO_64) endif() # Qt dependencies make building very slow # Track only .h files include_regular_expression("^.*\\.h$") # Put executable in build root dir set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/psi" ) # Set CXX and C Flags if(APPLE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-overloaded-virtual") elseif(WIN32) include("${CMAKE_CURRENT_SOURCE_DIR}/win32/win32_definitions.cmake") endif() if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") message(STATUS "CXX debug flags: ${CMAKE_CXX_FLAGS_DEBUG}") message(STATUS "C debug flags: ${CMAKE_C_FLAGS_DEBUG}") elseif("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo") message(STATUS "CXX debug flags: ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") message(STATUS "C debug flags: ${CMAKE_C_FLAGS_RELWITHDEBINFO}") else() message(STATUS "CXX flags: ${CMAKE_CXX_FLAGS}") message(STATUS "C flags: ${CMAKE_C_FLAGS}") endif() if(NOT WIN32 AND ENABLE_PORTABLE) message(WARNING "Portable version creation possible only for Windows OS") set(ENABLE_PORTABLE OFF) elseif(WIN32 AND ENABLE_PORTABLE) message(STATUS "Portable version - ENABLED") endif() if( PRODUCTION ) message(STATUS "Production version - ENABLED") endif() if( DEV_MODE OR ENABLE_PORTABLE ) message(STATUS "prepare-bin-libs target - ENABLED") endif() # Copy a list of files from one directory to another. Only full paths. function(copy SOURCE DEST TARGET) if(EXISTS ${SOURCE}) set(OUT_TARGET_FILE "${CMAKE_BINARY_DIR}/${TARGET}.cmake") string(REGEX REPLACE "\\\\+" "/" DEST "${DEST}") string(REGEX REPLACE "\\\\+" "/" SOURCE "${SOURCE}") if(NOT TARGET ${TARGET}) file(REMOVE "${OUT_TARGET_FILE}") add_custom_target(${TARGET} COMMAND ${CMAKE_COMMAND} -P "${OUT_TARGET_FILE}") endif() if(IS_DIRECTORY ${SOURCE}) # copy directory file(GLOB_RECURSE FILES "${SOURCE}/*") get_filename_component(SOURCE_DIR_NAME ${SOURCE} NAME) foreach(FILE ${FILES}) file(RELATIVE_PATH REL_PATH ${SOURCE} ${FILE}) set(REL_PATH "${SOURCE_DIR_NAME}/${REL_PATH}") get_filename_component(REL_PATH ${REL_PATH} DIRECTORY) set(DESTIN "${DEST}/${REL_PATH}") string(REGEX REPLACE "/+" "/" DESTIN ${DESTIN}) string(REGEX REPLACE "/+" "/" FILE ${FILE}) file(APPEND "${OUT_TARGET_FILE}" "file(INSTALL \"${FILE}\" DESTINATION \"${DESTIN}\" USE_SOURCE_PERMISSIONS)\n") endforeach() else() string(REPLACE "//" "/" DEST ${DEST}) if(DEST MATCHES "/$") set(DIR "${DEST}") string(REGEX REPLACE "^(.+)/$" "\\1" DIR ${DIR}) else() # need to copy and rename get_filename_component(DIR ${DEST} DIRECTORY) get_filename_component(FILENAME ${DEST} NAME) get_filename_component(SOURCE_FILENAME ${SOURCE} NAME) endif() file(APPEND "${OUT_TARGET_FILE}" "file(INSTALL \"${SOURCE}\" DESTINATION \"${DIR}\" USE_SOURCE_PERMISSIONS)\n") if(DEFINED FILENAME) file(APPEND "${OUT_TARGET_FILE}" "file(RENAME \"${DIR}/${SOURCE_FILENAME}\" \"${DIR}/${FILENAME}\")\n") endif() endif() endif() endfunction() if(USE_CCACHE) # Configure CCache if available find_program(CCACHE_PATH ccache DOC "Path to ccache") if(CCACHE_PATH) message(STATUS "Found ccache at ${CCACHE_PATH}") set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_PATH}) set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE_PATH}) endif() endif() if(NOT ONLY_PLUGINS) add_subdirectory( 3rdparty ) if(BUNDLED_IRIS) add_subdirectory( iris ) else() find_package( Iris REQUIRED ) include_directories(${Iris_INCLUDE_DIR}) endif() set( iris_LIB iris ) add_subdirectory(src) if(ENABLE_PLUGINS) add_subdirectory(plugins) endif() else() add_subdirectory(plugins) endif() psi-plus-snapshots-1.4.1456/COPYING000066400000000000000000000367661370065651000166300ustar00rootroot00000000000000 As a special exception, the copyright holder(s) give permission to link this program with the Qt Library (commercial or non-commercial edition), and distribute the resulting executable, without including the source code for the Qt library in the source distribution. As a special exception, the copyright holder(s) give permission to link this program with any other library, and distribute the resulting executable, without including the source code for the library in the source distribution, provided that the library interfaces with this program only via the following plugin interfaces: 1. The Qt Plugin APIs, only as authored by Trolltech 2. The QCA Plugin API, only as authored by Justin Karneges GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS psi-plus-snapshots-1.4.1456/ChangeLog.Psi+.txt000066400000000000000000020122641370065651000207570ustar00rootroot0000000000000020180306 tehnick v1.3 ! More patches were merged to Psi ! Multiple bug fixes and optimizations 20170726 tehnick v1.2 ! More patches were merged to Psi ! Support of Qt4 is stopped 20170720 rion v1.1 ! More patches were merged to Psi 20170529 rion v1.0 ! The project is mostly merged to Psi 2014-05-02 zet v0.16.361 ! [коммит в upstream] psi: разрешено автозаполнение поля с паролем в форме присоединения к конференции ! [коммит в upstream] psi: исправлено некорректное поведение при перетаскивании объектов в ростере конференции ! [коммит в upstream] psi: исправлена работа поиска в ростере (задача 107, https://github.com/psi-im/psi/issues/107) ! [коммит в upstream] psi: сворачивание/разворачивание групп ростера при одиночном клике на иконке группы ! [коммит в upstream] psi: более эффективная работа с опциями ! [коммит в upstream] psi: некоторые улучшения при перерисовке элементов ростера ! [коммит в upstream] psi: небольшие исправления для диалоговых окон с историей переписки (задача 109, https://github.com/psi-im/psi/issues/109 и другие) ! [коммит в upstream] psi: решена задача 108, https://github.com/psi-im/psi/issues/108 ! [коммит в upstream] psi: 2014 год в окне "О программе" ! [коммит в upstream] psi: попытка исправить некорректное отображение иконки в трее ! [коммит в upstream] psi: решена задача 102, https://github.com/psi-im/psi/issues/102 ! [коммит в upstream] psi: всегда отображать в ростере контакт с непрочитанным сообщением/событием (https://github.com/psi-im/psi/issues/42) ! [коммит в upstream] psi: исправлена ошибка при смене ника после кика в конференции ! [коммит в upstream] psi: сглаживание шрифтов в WebKit-версии ! [коммит в upstream] psi: фикс получения имени клиента в случае когда в капсах содержится 'https' ! [коммит в upstream] psi: теперь используется внешний qjdns ! [коммит в upstream] psi: возможность сделать масштабируемую область видеоокна для видеозвонков ! [коммит в upstream] psi: показывать продолжительность вызова в строке состояния ! [коммит в upstream] iris: добавлен qcm-модуль для проверки наличия внешнего qjdns. если внешний qjdns не найден, то будет использоваться встроенная версия. также встроенная версии qjdns будет использоваться в Windows и в Mac OS X без проверки наличия внешнего qjdns + добавлен новый патч для решения задачи 524, https://code.google.com/p/psi-dev/issues/detail?id=524 (psi-fix-selection-behavior-groupchat-dlg.diff) + добавлен новый патч для решения задачи 574, https://code.google.com/p/psi-dev/issues/detail?id=574 (fix-muc-roster-drag-and-drop.diff) - убран патч fix-muc-roster-drag-and-drop.diff -> принят в upstream + добавлен новый патч для поддержки проверки правописания при использовании нескольких словарей в случае когда Psi+ скомпилирован с enchant (psi-enchant-multidicts.diff) + добавлен новый патч для решения задачи 575, https://code.google.com/p/psi-dev/issues/detail?id=575 для работы фичи необходимо опцию "options.ui.muc.allow-highlight-events" установить в положение "true" (psi-muc-highlight-events.diff) * исправлена утечка памяти при очистке очереди событий. память текла при удалении контакта, если у него были непрочитанные сообщения или другие необработанные события (psi-fix-memory-leaks.diff) * исправлена ошибка, в результате которой отображалось событие composing для конференций (если какой нибудь клиент его отправлял), также теперь используется одинаковая иконка для события и для чатов (psi-muc-highlight-events.diff) * решена проблема с повреждением открытых вкладок, содержащих приватные чаты конференций, при появлении нового события для конференции. ник и заголовок вкладки приватного чата менялись на название конференции (psi-muc-highlight-events.diff) * теперь события конференции подгружаются при запуске Psi+ (если таковые существуют) [psi-muc-highlight-events.diff] - убраны патчи psi-more-effective-work-with-options.diff и some-improvments-for-contactlist-repaiting.diff -> приняты в upstream * попытка исправить патч psi-improve-tray-tooltip.diff * обновлён патч psi-hide-any-group.diff * решена задача 578, https://code.google.com/p/psi-dev/issues/detail?id=578 (psi-reasons-for-roles-affiliations.diff) + добавлен новый патч psi-disable-messages-correct-behavior.diff: патч меняет поведение опции "options.message.enabled". теперь при включённой опции входящие сообщения и другие события отображаются в диалоге, события не "зависают". исходящие сообщения запрещены и отключены в меню. также решена задача 560, https://code.google.com/p/psi-dev/issues/detail?id=560 + добавлен новый патч psi-transfer-dlg-improvement.diff: доработка диалогового окна передачи файлов, подробнее https://github.com/psi-plus/main/commit/1d5659b572e5a5dae39a245a66b5c71553adfa67#commitcomment-5454136 + добавлены новые патчи для ОС Haiku (спасибо Diger) [fix-psiplus-build-in-haiku.patch, fix-iconselect-buttons-in-haiku.patch, fix-screenshotplugin-build-in-haiku.patch] * добавлены новые капсы и ресурсы для Jabber-клиентов из списка отсюда: https://code.google.com/p/psi-dev/issues/detail?id=3#c157 (спасибо aon17@mail.ru) [psi-client-icons.diff] * обновлены наборы иконок Jabber-клиентов "fingerprint.jisp" и "fingerprint-22.jisp" (спасибо aon17@mail.ru) * добавлена поддержка действия detach при клике средней кнопкой мыши по табу. для неизвестных значений настройки теперь используется действие по умолчанию "none" (спасибо Hamper) [psi-tab-action-on-mouse-click.diff] * исправлена недоработка, в результате которой в консоль выводились предупреждения об отсутствующей опции (задача 563, пункт 0) [psi-tunable-chattoolbar.diff] * исправлено поведение при выделении элементов в обычных чатах. теперь это происходит таким же способом, как и в MUC (https://code.google.com/p/psi-dev/issues/detail?id=524) [psi-fix-selection-behavior-chat-dlg.diff] * убраны ненужные переменные в патчах psi-modern-roster.diff и psi-options-reset-button.diff * добавлена проверка на пустые аватары в патче psi-roster-avatar-frame.diff * чистка лишних отладочных сообщений в коде для решения задачи 563, https://code.google.com/p/psi-dev/issues/detail?id=563 (psi-extend-plugins-interface.diff) * теперь центральная панель инструментов в чатах включена по умолчанию (psi-default-application-settings.diff, psi-tunable-chattoolbar.diff) * решена проблема, описанная здесь: http://code.google.com/p/psi-dev/issues/detail?id=106#c35. теперь в диалоге чата, запущенном по команде из контекстного меню, будет выбран правильный ресурс. если ресурс у контакта только один, то режим autojid останется включённым. если несколько ресурсов - то даже, если был выбран ресурс с максимальным приоритетом, этот режим будет отключён (psi-one-chat-for-many-resources.diff) * исправлен двойной вызов vcard из контекстного меню при клике на jid в ростере (psi-muc-nickclick-chat.diff) * исправлен регистр в названиях ресурсов, т.к. для данного патча в переменной name не может быть символов верхнего регистра (psi-client-icons.diff) * обновлена ссылка для скачивания свежего инсталлятора Psi+, а также обновлены прямые ссылки на changelog и version в патче на автоматическое обнаружение новых версий Psi+ (psi-dirty-check.diff) * обновлён OTR plugin до версии 1.0.2, https://raw.githubusercontent.com/psi-plus/plugins/master/generic/otrplugin/changelog.txt * обновлён extended menu plugin до версии 0.1.3, https://raw.githubusercontent.com/psi-plus/plugins/master/generic/extendedmenuplugin/changelog.txt * обновлён video status plugin до версии 0.2.5, https://raw.githubusercontent.com/psi-plus/plugins/master/generic/videostatusplugin/changelog.txt * обновлён extended options plugin до версии 0.4.0, https://raw.githubusercontent.com/psi-plus/plugins/master/generic/extendedoptionsplugin/changelog.txt * обновлён content downloader plugin до версии 0.2.5, https://raw.githubusercontent.com/psi-plus/plugins/master/generic/contentdownloaderplugin/changelog.txt ! обновлены библиотеки шифрования OpenSSL до версии 1.0.1g, http://www.openssl.org/news/changelog.html ! обновлены файлы локализации для поддержки различных языков в Psi+ (версия локализации 0.16.352) https://github.com/psi-plus/psi-plus-l10n -- ! [upstream commit] psi: allowed autofill password for muc join dialog ! [upstream commit] psi: fixed drag and drop action in the muc roster ! [upstream commit] psi: fix search in roster (https://github.com/psi-im/psi/issues/107) ! [upstream commit] psi: expand/collapse roster groups on group icon singleclick ! [upstream commit] psi: more effenctive work with options ! [upstream commit] psi: some improvements for contactlist repainting ! [upstream commit] psi: some fixes for history (https://github.com/psi-im/psi/issues/109 and other) ! [upstream commit] psi: fixed https://github.com/psi-im/psi/issues/108 ! [upstream commit] psi: 2014 at aboutdlg ! [upstream commit] psi: try to fix wrong tray icon ! [upstream commit] psi: fix handling + at url (https://github.com/psi-im/psi/issues/102) ! [upstream commit] psi: always show contacts with events (https://github.com/psi-im/psi/issues/42) ! [upstream commit] psi: fix nick changing after kick from muc ! [upstream commit] psi: round pixel font size in WebKit ! [upstream commit] psi: fixed to obtain the client's name, if the caps node contains https ! [upstream commit] psi: use external qjdns ! [upstream commit] psi: make scalable video area for video calls ! [upstream commit] psi: show call duration in status string ! [upstream commit] iris: added qcm module to check for external qjdns. if external qjdns not found will be used bundled version. also bundled qjdns will be used on Windows and Mac OS X without checking for external qjdns + added new psi-fix-selection-behavior-groupchat-dlg.diff patch + added new patch for fix issue 574, https://code.google.com/p/psi-dev/issues/detail?id=574 (fix-muc-roster-drag-and-drop.diff) - removed fix-muc-roster-drag-and-drop.diff patch -> went to upstream + added new patch for support check spelling with multiple dictionaries when Psi+ compiled with enchant (psi-enchant-multidicts.diff) + added new patch for fix issue 575, https://code.google.com/p/psi-dev/issues/detail?id=575. for enable this feature you need 'options.ui.muc.allow-highlight-events' option switch to 'true' position (psi-muc-highlight-events.diff) * fixed a memory leak (psi-fix-memory-leaks.diff) * updated psi-muc-highlight-events.diff patch - removed psi-more-effective-work-with-options.diff and some-improvments-for-contactlist-repaiting.diff patches -> went to upstream * try to fix psi-improve-tray-tooltip.diff patch * updated psi-hide-any-group.diff patch * fixed issue 578, https://code.google.com/p/psi-dev/issues/detail?id=578 (psi-reasons-for-roles-affiliations.diff) + added new psi-disable-messages-correct-behavior.diff patch + added new psi-transfer-dlg-improvement.diff patch + added patches for Haiku (thanks to Diger) [fix-psiplus-build-in-haiku.patch, fix-iconselect-buttons-in-haiku.patch, fix-screenshotplugin-build-in-haiku.patch] * added new client caps and resources to src/userlist.cpp from https://code.google.com/p/psi-dev/issues/detail?id=3#c157 (thanks to aon17@mail.ru) [psi-client-icons.diff] * updated client iconsets 'fingerprint.jisp' and 'fingerprint-22.jisp' (thanks to aon17@mail.ru) * added some actions on the mouse middle button (thanks to Hamper) [psi-tab-action-on-mouse-click.diff] * updated psi-tunable-chattoolbar.diff patch * fixed a selection behavior in the psichatdlg (psi-fix-selection-behavior-chat-dlg.diff) * removed unused variables in psi-modern-roster.diff and psi-options-reset-button.diff patches * updated psi-roster-avatar-frame.diff patch * updated psi-extend-plugins-interface.diff patch for fix issue 563, https://code.google.com/p/psi-dev/issues/detail?id=563 * enable central toolbar by default (psi-default-application-settings.diff, psi-tunable-chattoolbar.diff) * updated psi-one-chat-for-many-resources.diff patch * fixed double vcards from context menu on jid (psi-muc-nickclick-chat.diff) * fixed capital letters in comparisions in psi-client-icons.diff patch * updated downloads location and changelog/version raw files locations for auto updater (psi-dirty-check.diff) * updated OTR plugin to v1.0.2, https://raw.githubusercontent.com/psi-plus/plugins/master/generic/otrplugin/changelog.txt * updated extended menu plugin to v0.1.3, https://raw.githubusercontent.com/psi-plus/plugins/master/generic/extendedmenuplugin/changelog.txt * updated video status plugin to v0.2.5, https://raw.githubusercontent.com/psi-plus/plugins/master/generic/videostatusplugin/changelog.txt * updated extended options plugin to v0.4.0, https://raw.githubusercontent.com/psi-plus/plugins/master/generic/extendedoptionsplugin/changelog.txt * updated content downloader plugin to v0.2.5, https://raw.githubusercontent.com/psi-plus/plugins/master/generic/contentdownloaderplugin/changelog.txt ! updated OpenSSL win32 binary files to v1.0.1g, http://www.openssl.org/news/changelog.html ! updated translations to v0.16.355 (https://github.com/psi-plus/psi-plus-l10n) 2013-11-24 zet v0.16.261 ! [коммит в upstream] psi: dbus отключён в MS Windows по умолчанию ! [коммит в upstream] psi: теперь не показывается лишняя история сообщений при перезаходе в конференцию (спасибо Dmitriy Deshevoy) ! [коммит в upstream] psi: исправлен порядок расположения панелей инструментов в настройках приложения. Теперь новые панели появляются всегда в конце списка (снизу) ! [коммит в upstream] psi: улучшена раскраска ников в чатлоге конференции. Патч от команды Psi+ ! [коммит в upstream] psi: исправлено падение приложения при вызове AlertManager ! [коммит в upstream] psi: улучшено определение версии ОС семейства MS Windows в SystemInfo + добавлен новый патч psi-tunable-chattoolbar.diff * теперь используется слово "Groupchat" вместо "MUC" или "Conference" (psi-muc-and-user-topic-context-menu.diff) * убрана ненужная опция "disable-paste-and-send". Теперь настройка тулбара осуществляется через соответствующую вкладку в настройках приложения (psi-send-button-context-menu.diff, psi-muc-minimize-to-roster.diff, psi-tunable-chattoolbar.diff) * исправлено падение приложения при вызове findOnlineAccountForContact когда *account = 0 (psi-extend-plugins-interface.diff) * фиксы для локализации в патче psi-tunable-chattoolbar.diff + добавлен новый патч для коррекции dpi в webkit-версии (psi-webkit-dpi.diff) + добавлен новый патч для корректной сортировки панелей инструментов в настройках приложения (psi-toolbars-sorted.diff) + добавлен новый патч psi-x11-class-name.diff - убран патч psi-x11-class-name.diff -> принят в upstream * улучшена раскраска ников в чатлоге конференции (psi-muc-nick-hash-color.diff) - убран патч psi-muc-nick-hash-color.diff -> принят в upstream - убран патч psi-webkit-dpi.diff -> принят в upstream * правильная установка цветовой палитры для всплывающих подсказок (psi-coloring-tooltip.diff) + добавлен новый патч psi-unity-menubar.diff + добавлен новый плагин для Psi+ -- Off-The-Record-Messaging plugin, подробнее по ссылке: https://github.com/psi-plus/plugins/tree/master/generic/otrplugin * обновлены некоторые плагины: теперь используется слово "Groupchat" вместо "MUC" или "Conference" (client switcher plugin, conference logger plugin, extended options plugin, gomoku game plugin, stop spam plugin) * обновлён extended options plugin: убраны опции, которые отображают тулбар чата и кнопку "Вставить и отправить" * обновлён juick plugin: добавлена обработка изображений в формате *.png * обновлён watcher plugin: убрана опция "Отображать на панели чата". Теперь это настраивается в настройках приложения (вкладка "Панель инструментов") * обновлён GnuPG plugin: уменьшен размер иконки плагина, убрана опция "Отображать на панели чата" ! обновлены файлы локализации для поддержки различных языков в Psi+ (версия локализации 0.16.257) https://github.com/psi-plus/psi-plus-l10n -- ! [upstream commit] psi: disable dbus on MS Windows by default ! [upstream commit] psi: MUC history since (thx:Dmitriy Deshevoy) ! [upstream commit] psi: fixed toolbars order in Toolbar Options. New toolbars added to the bottom. After reopen options a new toolbar was on the top. Now new toolbars always in the bottom ! [upstream commit] psi: improve palette for nicknames coloring in group chats. Patch from Psi+ ! [upstream commit] psi: fixed crash in AlertManager (double free) ! [upstream commit] psi: allow no text in html message content. for example if it's image ! [upstream commit] psi: update checks of MS Windows versions in SystemInfo ! [upstream commit] iris: ignore /[\r\n]+/ in vcard base64 photo + added new psi-tunable-chattoolbar.diff patch * 'Groupchat' instead of 'MUC' or 'Conference' (psi-muc-and-user-topic-context-menu.diff) * no disable-paste-and-send option (psi-send-button-context-menu.diff, psi-muc-minimize-to-roster.diff, psi-tunable-chattoolbar.diff) * don't crash in findOnlineAccountForContact when *account = 0 (psi-extend-plugins-interface.diff) * split Toolbar from Chat Toolbar (psi-tunable-chattoolbar.diff, psi-extend-plugins-interface.diff) * fix with translations of Chat Toolbar (psi-tunable-chattoolbar.diff) * fix text strings in psi-tunable-chattoolbar.diff patch + added new patch for fix webkit's dpi (psi-webkit-dpi.diff) + added new patch for fix Toolbar Options. Options internally uses QHash so they has no order. Before putting toolbars in combobox they must be sorted (psi-toolbars-sorted.diff) + added new patch for fix bug with changing of X11 window class name. Such class name is used for grouping windows and for some WM (psi-x11-class-name.diff) - removed psi-x11-class-name.diff patch -> went to upstream * updated psi-muc-nick-hash-color.diff patch. Now nicknames should look much better in group chats * fixed color palette in psi-muc-nick-hash-color.diff patch * improved psi-muc-nick-hash-color.diff patch - removed psi-muc-nick-hash-color.diff patch -> went to upstream - removed psi-webkit-dpi.diff patch -> went to upstream * set palette colors for tooltips proper way (psi-coloring-tooltip.diff) + added new psi-unity-menubar.diff patch. This workaround. Need to fix Unity bug with Status Menu in indicator. When a menu is used on the application menu bar it can no be shown anywhere else. Need to use clone of the menu on the menu bar + added new plugin - Off-The-Record-Messaging plugin for Psi+, see the link for details https://github.com/psi-plus/plugins/tree/master/generic/otrplugin * updated some plugins: use Groupchat word instead of MUC or Conference (client switcher plugin, conference logger plugin, extended options plugin, gomoku game plugin, stop spam plugin) * updated extended options plugin: dropped two chat options * updated juick plugin: handle *.png photos * updated watcher plugin: dropped show no chat toolbar option. Now there is a way to setup chat toolbar. That option has no any sense anymore * updated GnuPG plugin: shrinked plugin icon; dropped show no chat toolbar option ! updated translations to v0.16.257 (https://github.com/psi-plus/psi-plus-l10n) 2013-09-01 zet v0.16.204 ! [коммит в upstream] psi: добавлена поддержка Webkit от команды Psi+ (со встроенной поддержкой adium) * [коммит в upstream] psi: улучшенное окно для ввода текста при установке топика конференции + [коммит в upstream] psi: добавлено отображение уведомлений о доставке сообщений * [коммит в upstream] psi: добавлена иконка для отображения уведомлений о доставке сообщений * [коммит в upstream] psi: фикс для работы через proxy-сервер + [коммит в upstream] psi: добавлена иконка для отображения уведомления о написании сообщения * [коммит в upstream] psi: фикс для growlnotifier * [коммит в upstream] psi: чтение файлов тем adium вне зависимости от регистра файла + [коммит в upstream] psi: добавлена поддержка мониторов Retina + [коммит в upstream] psi: начальная поддержка adium messageClasses + [коммит в upstream] psi: добавлена тема new_classic psi (с аватарами) * [коммит в upstream] iris: не показывать способность в передаче файлов в обзоре сервисов, если поддержка передачи файлов отключена * [коммит в upstream] iris: исправлена ошибка в механизме передачи файлов методом s5b * добавлено опеределение клиента Xabber (psi-client-icons.diff, fingerprint.jisp) + добавлен новый патч для улучшения перерисовки ростера (some-improvments-for-contactlist-repaiting.diff) - убраны патчи psi-webkit.diff, psi-muc-topic.diff and psi-receipts.diff -> приняты в upstream * фикс в патче psi-fix-pep-retraction.diff * небольшие улучшения в патче psi-extend-plugins-interface.diff * иконка для уведомления о написании сообщения принята в upstream (psi-iconsets.diff) * улучшенные иконки для клиентов jTalk и Xabber (спасибо vados) [fingerprint.jisp, fingerprint-22.jisp] * иконка 'theme_title_texture.png' принята в upstream (psi-iconsets.diff) * обновлён патч psi-extend-plugins-interface.diff: добавлена поддержка отображения иконки для каждого плагина в выпадающем списке выбора плагина (настройки приложения -> вкладка Плагины) + добавлен новый патч для исправления отображения аватарки во всплывающей подсказке на приватных чатах конференции в основном ростере (psi-fix-tooltip-avatar-on-privates-in-main-roster.diff) * серая (бледная) иконка для отключенных плагинов в выпадающем списке плагинов на странице настроек (psi-extend-plugins-interface.diff) * добавлена поддержка плагинного интерфейса в табах чатов и конференций, также объединены ChatTabAccessor и GCTabAccessor (psi-extend-plugins-interface.diff) * juick plugin: попытка исправить заползание длинной ссылки за пределы видимой области экрана + content downloader plugin: добавлен набор смайлов 'Nimbuzz' (Nimbuzz22.jisp) + content downloader plugin: добавлен набор смайлов 'Android 4 blue' (Android4blue.jisp) * content downloader plugin (v0.2.3): кликабельные ссылки на странице описания набора иконок * watcher plugin (v0.4.2): исправлен выбор Jid'а для удаления из списка наблюдения * watcher plugin (v0.4.3): + добавлена кнопка в панель инструментов чата для добавления контакта в список слежения * изменена иконка в контекстном меню контакта * теперь в настройках 1-ая колонка используется для включения правила + добавлена настройка для отображения кнопок "Следить за JID'ом" + к каждому плагину Psi+ добавлена его иконка (для отображения в выпадающем списке плагинов в настройках приложения) - убран yandex narod plugin, т.к. Яндекс закрыл свой сервис "Яндекс.Народ" * gnupg plugin (v0.3.4): теперь используется основная иконка плагина размером 16x16 * translate plugin: теперь используется новый плагинный интерфейс ChatTabAccessor * juick plugin (v0.11.4): изменения в интерфейсе плагинов ! обновлены файлы локализации для поддержки различных языков в Psi+ (версия локализации 0.16.179) https://github.com/psi-plus/psi-plus-l10n -- ! [upstream commit] psi: added Webkit support from Psi+ (support for adium themes inside) * [upstream commit] psi: improved dialog for setting MUC topic + [upstream commit] psi: added receipts support to ui * [upstream commit] psi: updated iconset to support message icons * [upstream commit] psi: fix for proxy + [upstream commit] psi: add typing icon * [upstream commit] psi: fix for growlnotifier * [upstream commit] psi: case insensitive read of adium theme files + [upstream commit] psi: retina displays support + [upstream commit] psi: basic support for adium messageClasses + [upstream commit] psi: added new_classic psi theme (with avatars). slotted scrollUp/Down * [upstream commit] iris: don't show filetransfer features in disco if filetransfer disabled * [upstream commit] iris: make all ByteStream based classes. fixes bug in s5b file transfer * added Xabber detection (psi-client-icons.diff, fingerprint.jisp) + added new patch for some improvments for contactlist repaiting (some-improvments-for-contactlist-repaiting.diff) - removed psi-webkit.diff, psi-muc-topic.diff and psi-receipts.diff patches -> went to upstream * fixed psi-fix-pep-retraction.diff patch * small improvement for psi-extend-plugins-interface.diff patch * typing icon went to upstream (psi-iconsets.diff) * better jTalk and Xabber client icons (from vados) [iconsets/clients/fingerprint.jisp, fingerprint-22.jisp] * moved 'theme_title_texture.png' icon to upstream (psi-iconsets.diff) * updated psi-extend-plugins-interface.diff patch: added plugin icon. added new function icon to PsiPlugin. also added that icon to plugins combobox + added new patch for fix tooltip avatar on privates in main roster (psi-fix-tooltip-avatar-on-privates-in-main-roster.diff) * grayscale icons for disabled plugins in plugins combobox (psi-extend-plugins-interface.diff) * added plugin interfaces for chat tabs and groupchat tabs, merged ChatTabAccessor and GCTabAccessor (psi-extend-plugins-interface.diff) * juick plugin: try to fix out of screen long links + content downloader plugin: added 'Nimbuzz' emoticons (Nimbuzz22.jisp) + content downloader plugin: added 'Android 4 blue' emoticons (Android4blue.jisp) * content downloader plugin (v0.2.3): clickable links in iconpack description * watcher plugin (v0.4.2): fix with jid checking for deleting from watcher list * watcher plugin (v0.4.3): + added Watch for JID button on chat toolbar * new icon for Watch for JID button * first column in settings now uses for enable/disable rule + added setting to show/hide Watch for JID buttons + added plugin icon for all Psi+ Plugins - removed yandex narod plugin * gnupg plugin (v0.3.4): use 16x16 image for plugin icon * translate plugin: use new plugin interface ChatTabAccessor, tabify sources * juick plugin (v0.11.4): changes in plugin interface ! updated translations to v0.16.179 (https://github.com/psi-plus/psi-plus-l10n) 2013-07-28 zet v0.16.150 * [коммит в upstream] psi: оптимизация кода в HistoryDlg::displayResult * [коммит в upstream] psi: исправлено описание OS в system info. актуально только для *nix-систем, в которых не установлен lsb_release * [коммит в upstream] psi: добавлено определение OS RFRemix в методе unixHeuristicDetect(). актуально только для *nix-систем, в которых не установлен lsb_release * [коммит в upstream] psi: обновлён psi.desktop * [коммит в upstream] psi: добавлено несколько патчей от команды Kopete * [коммит в upstream] psi: PongServer перемещён в iris * [коммит в upstream] psi: добавлена поддержка XEP-0264: File Transfer Thumbnails * [коммит в upstream] psi: исправлено поведение при двойном щелчке на элементе ростера для KDE. оригинальный патч от Petr Gregor * [коммит в upstream] psi: новые всплывающие окна (патч от команды Psi+): добавлена поддержка dbus-popups, в настройках приложения добавлена новая вкладка с настройками всплывающих окон, добавлена поддержка аватаров во всплывающих окнах и много других улучшений и исправлений * [коммит в upstream] iris: добавлена поддержка XEP-0184 v1.2 (спасибо команде Kopete) * [коммит в upstream] iris: добавлена поддержка XEP-0199 (urn:xmpp:ping) ! [коммит в upstream] iris: теперь в комплекте нет libidn * [коммит в upstream] iris: много фиксов от команд Kadu и Kopete * [коммит в upstream] iris: добавлена поддержка XEP-0264: File Transfer Thumbnails * [коммит в upstream] iris: возвращена поддержка уведомлений о доставке сообщений старого образца * небольшие исправления в патче small fix for psi-presets-in-status-menu.diff - убран патч psi-rfremix-sysinfo.diff -> принят в upstream * обновлён патч psiplus-application-info.diff: небольшой фикс для psi.desktop * плагинная система: стабилизация про поиске контактов в аккаунтах (psi-presets-in-status-menu.diff, psi-extend-plugins-interface.diff) * реализован метод findOnlieAccountForContact (требуется тестирование) [psi-extend-plugins-interface.diff] * исправлен патч psi-chatview-quote-feature.diff * добавлено определение клиента Google Hangouts (psi-client-icons.diff) * обновлён патч psi-receipts.diff для поддержки нового API * небольшие фиксы для классической темы webkit (psi-webkit.diff, psi-receipts.diff) * исправления в способах соединения для Mac OS X (psi-webkit.diff) * webkit: убраны лишние пробелы и переносы строк при копирование текста (psi-webkit.diff) * webkit: исправлен копипаст отдельных иконок (psi-webkit.diff) * webkit: убрана излишняя высота строки для классической темы. не работает с крупными шрифтами (psi-webkit.diff) - убран патч psi-KDEfix-doubleclick-on-roster.diff -> принят в upstream - убран патч psi-extend-popups.diff -> убран в upstream * gnupg plugin (v0.3.1): добавлен диалог подтверждения при удалении ключа/ключей * gnupg plugin (v0.3.2): косметические фиксы в окне диалога создания ключевой пары * gnupg plugin (v0.3.3): улучшен алгоритм поиска gpg бинарника, иконка критической ошибки в окне информации в случае ошибки ! в состав дистрибутива включён новый необходимый компонент - библиотеки LibIDN, v1.28 (взяты отсюда http://ftp.gnu.org/gnu/libidn/libidn-1.28-win32.zip) ! обновлена системная библиотека Zlib, v1.2.8 (скомпилировано в MinGW из исходников, взятых отсюда http://zlib.net/zlib-1.2.8.tar.gz) ! пропатчены и скомпилированы заново библиотеки QCA (патч взят отсюда http://websvn.kde.org/?view=revision&revision=1357590) ! обновлены системные библиотеки Qt, v4.8.5 (включая QtWebKit4.dll), подробнее - http://blog.qt.digia.com/blog/2013/07/02/qt-4-8-5-released/ -- * [upstream commit] psi: code optimization in HistoryDlg::displayResult * [upstream commit] psi: fixed OS description in system info. affects only *nix systems where lsb_release is not installed * [upstream commit] psi: added detection of RFRemix into unixHeuristicDetect(). affects only *nix systems where lsb_release is not installed * [upstream commit] psi: updated psi.desktop * [upstream commit] psi: added few patches from kopete * [upstream commit] psi: PongServer moved to iris * [upstream commit] psi: added XEP-0264: File Transfer Thumbnails support * [upstream commit] psi: fixed doubleclick roster items activation for KDE. original patch by Petr Gregor * [upstream commit] psi: new popups. add dbus popups, add new tab at settings, add avatars at popups, many other changes * [upstream commit] iris: XEP-0184 v1.2 implementation (thanks to kopete team) * [upstream commit] iris: don't hide Qt's virtual reset functions (thanks to kopete team) * [upstream commit] iris: added abstract socket access function (thanks to kopete team) * [upstream commit] iris: added XEP-0199 (urn:xmpp:ping) implementation * [upstream commit] iris: handle jabber:x:signed tin message stanzas as well (thanks to kopete team) ! [upstream commit] iris: removed bundled libidn * [upstream commit] iris: more fixes from kadu and kopete * [upstream commit] iris: added XEP-0264: File Transfer Thumbnails support * [upstream commit] iris: now understands a received receipts of the old format * small fix for psi-presets-in-status-menu.diff patch - removed psi-rfremix-sysinfo.diff patch -> went to upstream * updated psiplus-application-info.diff patch. small fix for psi.desktop * plugins: stub for search for contact in accounts (psi-presets-in-status-menu.diff, psi-extend-plugins-interface.diff) * implemented findOnlieAccountForContact. probably buggy a bit (psi-extend-plugins-interface.diff) * fixed psi-chatview-quote-feature.diff patch * updated psi-client-icons.diff patch ("android.com" res) * updated receipts patch to follow new API (psi-receipts.diff) * small fixes to webkit cassic theme (psi-webkit.diff, psi-receipts.diff) * selectionChanged connection commented out on mac (psi-webkit.diff) * webkit: fixed spaces collapse while copying (psi-webkit.diff) * webkit: fixed copypaste of single icon (psi-webkit.diff) * removed line-height from classic theme. doesn't work with big fonts (psi-webkit.diff) - removed psi-KDEfix-doubleclick-on-roster.diff patch -> went to upstream - removed psi-extend-popups.diff -> went to upstream * gnupg plugin: added a confirmation dialog for keys deleting * gnupg plugin: cosmetics in 'A new key pair' dialog * updated gnupg plugin to v0.3.3: improved gpg binary search. error icon in "Info" dialog when error ! added new required Psi+ component - LibIDN binary libs v1.28 (based on http://ftp.gnu.org/gnu/libidn/libidn-1.28-win32.zip) ! updated Zlib Libs to v1.2.8 (compiled by MinGW from sources at http://zlib.net/zlib-1.2.8.tar.gz) ! patched Psi+ Crypto Libs (QCA + patch http://websvn.kde.org/?view=revision&revision=1357590) ! updated Qt libs to v4.8.5 (QtWebKit4.dll included) 2013-05-01 zet v0.16.116 * [коммит в upstream] psi: работать с хэшем паролей, если сервер поддерживает аутентификацию SCRAM * [коммит в upstream] psi: найден обходной путь для решения проблемы QTBUG-26351 в диалоге с историей переписки (спасибо taurus) * [коммит в upstream] psi: небольшие исправления в форме редактора аккаунта: улучшения для отображения текста и элементов * [коммит в upstream] iris: установить состояние ReadOnly для SockClient при дисконнекте tcp-соединения (разрешить чтение из буфера) * [коммит в upstream] iris: исправлен сломанный ранее механизм передачи файлов (IBB) после портирования на QIODevice * [коммит в upstream] iris: исправлены ошибки в работе DIGEST-MD5 аутентификации * [коммит в upstream] iris: исправлен текст лицензии в некоторых файлах * обновлена иконка jabber-клиента digsby * обновлены иконки для клиента leechcraft-azoth * обновлён патч psi-client-icons.diff для поддержки свежих иконок клиентов * обновлены наборы иконок для отображения jabber-клиентов в ростере Psi+ (fingerprint.jisp, fingerprint-22.jisp) * исправлена ошибка при открытии чата по правой кнопке мыши на контакте в случае использования открытия чата по одиночному клику (psi-KDEfix-doubleclick-on-roster.diff) - убран патч qca_qt5.patch -> принят в upstream http://websvn.kde.org/?view=revision&revision=1347515 * исправлен показ версии для Mac OS X 10.8 (patches/mac/3060-psi-update-mac-versions.diff) * новый механизм определения версии для Mac OS (patches/mac/3060-psi-update-mac-versions.diff) - убран патч psi-history-dlg-fix-keyboard.diff -> принят в upstream * обновлён gnupg plugin до версии 0.3.0: добавлена функция автоимпорта ключа из тела сообщения, в панель инструментов чата добавлена кнопка отправки публичного ключа, в диалоге генерации ключевой пары по умолчанию срок действия ключа 1 год. подробнее - https://raw.github.com/psi-plus/plugins/master/generic/gnupgplugin/changelog.txt + добавлена персидская локализация Psi+ (translations/psi_fa.qm) ! обновлены файлы локализации для поддержки различных языков в Psi+ (версия локализации 0.16.115) https://github.com/psi-plus/psi-plus-l10n -- * [upstream commit] psi: salted hash passwords when scram is available * [upstream commit] psi: get rid of couple compilation warnings * [upstream commit] psi: disabled 822 encoding/decoding since jids are already passed stringprep and double encoding/decoding breaks jids. probably bad solution * [upstream commit] psi: workaround for QTBUG-26351 in history dialog (thx: taurus) * [upstream commit] psi: small fixes in Account Properties Dialog: improved text and layouts of elements * [upstream commit] iris: set ReadOnly state for SockClient on tcp disconnect (allow read from buffer) * [upstream commit] iris: fixed IBB regression after portig to QIODevice * [upstream commit] iris: no qca-static anymore * [upstream commit] iris: fixed regression in DIGEST-MD5 auth * [upstream commit] iris: fix license descriptions * updated digsby client icon * updated leechcraft-azoth clients icon * updated psi-client-icons.diff patch * updated 'fingerprint.jisp' and 'fingerprint-22.jisp' clients iconsets * fix opening chat on rightclick on contact when use singlclick option is enabled (psi-KDEfix-doubleclick-on-roster.diff) - removed qca_qt5.patch -> went to upstream http://websvn.kde.org/?view=revision&revision=1347515 * corrected OS X 10.8 version (patches/mac/3060-psi-update-mac-versions.diff) * a new approach to get Mac OS VERSION (patches/mac/3060-psi-update-mac-versions.diff) - removed psi-history-dlg-fix-keyboard.diff patch -> went to upstream * updated gnupg plugin auto import function: button to send publik key on chat toolbar, expiration data in an year by default * updated gnupg plugin: do not handle incoming stanza if plugin is disabled + added persian translation (translations/psi_fa.qm) ! updated translations to version 0.16.115 (https://github.com/psi-plus/psi-plus-l10n) 2013-03-17 zet v0.16.105 * [коммит в upstream] psi: небольшой фикс для filetunecontroller * [коммит в upstream] psi: фикс xhtml-im formatting * [коммит в upstream] psi: фиксы для совместимости с Qt5 * [коммит в upstream] psi: частичная поддержка mediaelement + фиксы совместимости с Qt4 * [коммит в upstream] psi: обновлён год в диалоге Меню -> Справка -> О программе * [коммит в upstream] psi: добавлено корректное определение MS Windows 8 * [коммит в upstream] psi: исправления багов при переименовании профиля * [коммит в upstream] iris: фиксы для совместимости с Qt5 * [коммит в upstream] iris: исправлены ошибки для поддержки socks proxy * [коммит в upstream] iris: некоторые фиксы в s5b * [коммит в upstream] libpsi: фиксы для совместимости с Qt5 * [коммит в upstream] libpsi: исправлена сломанная ранее совместимость с Qt4 * изменён алгоритм раскраски ников в конференциях (psi-muc-nick-hash-color.diff) - убран патч psi-filetunecontroller-fix.diff -> принят в upstream * возврат к прежнему алгоритму winamptunecontroller (из upstream) [psi-fix-tunes.diff] + добавлен новый патч psi-fix-display-status-icon.diff + добавлен новый патч psi-fix-showing-xml-chars-in-contacts-popup.diff * исправлен патч psi-small-tooltip-fix.diff + добавлен новый патч psi-fix-transports-group.diff * фиксы в патче psi-modern-roster.diff - убран патч fix-showing-xml-chars-in-contacts-popup.diff -> принят в upstream * часть патча psi-send-xhtml-im.diff принята в upstream - убран патч psi-data-forms-media-element.diff -> принят в upstream * обновлён состав команды Psi+ (Меню -> Справка -> О программе -> О Psi+) [psiplus-aboutdlg.diff] * решена задача 532 (http://code.google.com/p/psi-dev/issues/detail?id=532) [psi-autostart-pgp.diff] * обновлён патч psi-one-chat-for-many-resources.diff: исправлено отображение xml-символов в SysMsg * исправлена ошибочная опция в options/macosx.xml (psi-extend-popups.diff) * обновлён патч psi-muc-and-user-topic-context-menu.diff: добавлена команда Copy user JID для меню на списке ресурсов в окне чата + добавлен новый патч для вызова выпадающего списка с известными stun-серверами в редакторе аккаунта (psi-stun-hosts-combobox.diff) * показывать команду Copy user JID только для пользователей с реальными JID (psi-muc-and-user-topic-context-menu.diff) + добавлен новый патч fix-showing-xml-chars-in-group-popup.diff * исправления в патче psi-fix-transports-group.diff + добавлен новый патч для устранения утечки памяти (psi-fix-memory-leaks.diff) * небольшие фиксы в патчах psi-always-visible-contacts.diff и psi-presets-in-status-menu.diff + добавлен новый патч для исправления вызова элементов ростера по двойному клику в KDE. автор патча - Petr Gregor (psi-KDEfix-doubleclick-on-roster.diff) + добавлен новый патч для поддержки нового дистрибутива семейства Linux в systeminfo (psi-rfremix-sysinfo.diff) + добавлен новый патч psi-switch-tab-on-new-message.diff * корректная работа с изменяемыми полями в редакторе закладок (psi-muc-bookmark-toolbar-button.diff) * корректировка в написании команды в меню цитирования в окне чата (psi-chatview-quote-feature.diff) * обновлён client switcher plugin (v0.0.16) * обновлён screenshot plugin (v0.6.4) + добавлен новый плагин -- GnuPG Key Manager (v0.2.1), подробнее - https://raw.github.com/psi-plus/plugins/master/generic/gnupgplugin/changelog.txt * фикс в коде icq die plugin (v0.1.5) ! обновлены системные библиотеки Qt до версии 4.8.4 (http://qt.digia.com/Release-Notes/Release-Notes-Qt-484/) ! обновлены библиотеки OpenSSL до версии 1.0.1e (http://www.openssl.org/news/changelog.html) ! перекомпилирована библиотека Zlib (v1.2.7) ! добавлены новые и обновлены существующие файлы локализации для поддержки различных языков в Psi+ (версия локализации 0.16.103.1) [https://github.com/psi-plus/psi-plus-l10n] -- * [upstream commit] psi: small fix for filetunecontroller * [upstream commit] psi: some escaping at contacts tooltip * [upstream commit] psi: fix xhtml-im formatting * [upstream commit] psi: Qt5 compatibility fixes * [upstream commit] psi: incomplete support for mediaelement + Qt4 compat fixes * [upstream commit] psi: update years in dialog about program * [upstream commit] psi: add correct checks of MS Windows 8 in SystemInfo * [upstream commit] psi: fix profile renaming * [upstream commit] iris: Qt5 compatibility fixes * [upstream commit] iris: use QIODevice as base class for ByteStream. started some work on NetworkAccessManager. added partial support for mediaelement * [upstream commit] iris: NAM in progress. dropped idea with socket factory * [upstream commit] iris: fixed socks proxy support * [upstream commit] iris: fixed double memory free in StunTransactionPool destructor * [upstream commit] iris: the previous fix for double free was wrong. so here is another fix * [upstream commit] iris: fixed regression in http proxy handling code * [upstream commit] iris: a try to fix socks regression after porting to qiodevice * [upstream commit] iris: more s5b fixes * [upstream commit] libpsi: Qt5 compatibility fixes * [upstream commit] libpsi: fixed broken Qt4 compatibility * [upstream commit] libpsi: use PWD for x11info in *.pri * [upstream commit] libpsi: added forgotten license to x11info.* files * [upstream commit] libpsi: partial rollback of x11 global shortcut manager code * changed nick hashing algo (psi-muc-nick-hash-color.diff) - removed psi-filetunecontroller-fix.diff patch -> went to upstream * new winamptunecontroller moved to dev. reverted winamptunecontroller from upstream (psi-fix-tunes.diff) + added new psi-fix-display-status-icon.diff patch + added new psi-fix-showing-xml-chars-in-contacts-popup.diff patch * fixed psi-small-tooltip-fix.diff patch + added new psi-fix-transports-group.diff patch * removed extra comma from psi-modern-roster.diff patch - removed psi-fix-showing-xml-chars-in-contacts-popup.diff patch -> went to upstream * part of psi-send-xhtml-im.diff patch went to upstream - removed psi-data-forms-media-element.diff patch -> went to upstream * updated Psi+ about dialog (psiplus-aboutdlg.diff) * fixed issue 532 (http://code.google.com/p/psi-dev/issues/detail?id=532) [psi-autostart-pgp.diff] * updated psi-one-chat-for-many-resources.diff patch. fixed showing xml chars in SysMsg * fixed invalid options/macosx.xml (psi-extend-popups.diff) * updated psi-muc-and-user-topic-context-menu.diff patch: added Copy user JID from user JID context menu + added new psi-stun-hosts-combobox.diff patch * show copy user JID only for normal users (psi-muc-and-user-topic-context-menu.diff) + added new fix-showing-xml-chars-in-group-popup.diff patch * fixed psi-fix-transports-group.diff patch + added new psi-fix-memory-leaks.diff patch * small fix for psi-always-visible-contacts.diff patch * small fix for psi-presets-in-status-menu.diff patch + added new patch for fix activation roster items by doubleclick on KDE. patch by Petr Gregor (psi-KDEfix-doubleclick-on-roster.diff) + added new patch for support new Linux distro in systeminfo (psi-rfremix-sysinfo.diff) + added new psi-switch-tab-on-new-message.diff patch * must be Edit Bookmark (psi-muc-bookmark-toolbar-button.diff) * corrected English grammitcs (psi-chatview-quote-feature.diff) * updated client switcher plugin (v0.0.16) * updated screenshot plugin (v0.6.4) + added new plugin -- GnuPG Key Manager (v0.2.1), https://raw.github.com/psi-plus/plugins/master/generic/gnupgplugin/changelog.txt * fixed icq die plugin (v0.1.5) ! updated Qt libs to version 4.8.4 (http://qt.digia.com/Release-Notes/Release-Notes-Qt-484/) ! updated OpenSSL libs to version 1.0.1e (http://www.openssl.org/news/changelog.html) ! recompiled windows zlib (v1.2.7) ! updated translations to version 0.16.103.1 (https://github.com/psi-plus/psi-plus-l10n) 2012-10-28 zet v0.16.25 * улучшения в патче psi-autostart-pgp.diff: запрет автоматического включения PGP для чата, если PGP был включён/отключён вручную * обновлена информация об активных участниках проекта Psi+ (psiplus-aboutdlg.diff) * попытка наладить корректную сборку Psi+ под Ms Windows x64 (psi-fix-tunes.diff) * обновлены файлы переводов Psi+ на различные языки (поддержка более 30 языков), от 27.10.2012 (спасибо команде переводчиков https://github.com/psi-plus/psi-plus-l10n ) ! установщик windows: восстановлена работа механизма проверки орфографии (были изменены пути расположения словарей aspell в официальной версии Psi) -- * fixed fuzess; improve psi-autostart-pgp.diff patch: disable autopgp for chat if pgp enabled/disabled manually (psi-receipts.diff, psi-hide-muc-auto-join.diff, psi-autostart-pgp.diff) * updated Psi+ about info (psiplus-aboutdlg.diff) * try to fix build of win64 binary (psi-fix-tunes.diff) * updated Psi+ translations (2012-10-27) by https://github.com/psi-plus/psi-plus-l10n ! windows installer: fixed aspell path 2012-10-21 zet v0.16.19 ! состоялся долгожданный релиз Psi 0.15! http://lists.affinix.com/pipermail/psi-devel-affinix.com/2012-October/009348.html * [upstream commit] psi: небольшой фикс для корректного отображения истории переписки (отображение ников со спецсимволами) * [upstream commit] psi: некоторые фиксы при переименовании групп и контактов ростера (спасибо liuch) * [upstream commit] psi: фикс обработки первого сообщения в новом чате (для некоторых случаев) * [upstream commit] psi: деактивация некоторых элементов в редакторе аккаунта и во всплывающем меню на контакте ростера * [upstream commit] psi: спрятана ненужная опция "use-singleclick" * [upstream commit] psi: спрятана опция "hide show-away action" * [upstream commit] psi: запрет скрытия команды меню "Выполнить команду" для оффлайн-контактов (требуется для xmpp-транспорта Mrim) * [upstream commit] psi: создание xml-консоли только если это необходимо * [upstream commit] psi: запрет на удаление аккаунтов, содержащих непрочитанные события * [upstream commit] psi: исправлена возможная утечка памяти при удалении событий * [upstream commit] psi: исправлено падение приложения при удалении аккаунта * [upstream commit] psi: действие по умолчанию для средней кнопки мыши при клике на контакте * [upstream commit] psi: фикс при отрисовке статусного сообщения в режиме отображения контактов ростера в виде одной строки * [upstream commit] psi: фикс отрисовки ростера конференции при включенной опции "slim-groups" ! [upstream commit] psi: новая ветка версий Psi 0.16-dev (после состоявшегося релиза Psi 0.15) * [upstream commit] psi: фикс таймера в eventdlg (никогда не сбрасывался) * [upstream commit] psi: некоторые фиксы для winamp tune controller (by KukuRuzo) * [upstream commit] psi: настраиваемые цвета текста в окне истории переписки. патч для задачи #21 * [upstream commit] psi: попытка использовать по умолчанию ник аккаунта при подсоединении к конференции (вместо полного jid-а) * [upstream commit] psi: исправлен двойной запрос features при вызове окна service discovery * [upstream commit] psi: небольшой фикс для редактора настроек * [upstream commit] psi: фикс заголовка окна с рисовальной доской * [upstream commit] iris: добавлено временное решение проблем с ipv6 в режиме http-poll * [upstream commit] iris: отключен tls для режима http-poll в AdvancedConnector, так как http-poll имеет свой собственный обработчик tls * [upstream commit] iris: фикс заголовка grepshortcutdlg * обновлён патч psi-one-chat-for-many-resources.diff: исправлена ошибка, в результате которой при активации вкладки с непрочитанными сообщениями счётчик непрочитанных сообщений не обновлялся в случае если контакт ушёл в оффлайн и на этой вкладке включён режим autojid * исправления в обработке станзы для Entity Time (задача #512) [psi-entity-time.diff] * обновлён патч psi-safe-storage-fix.diff (задача #515): исправлена ошибка, в результате которой терялись файлы конфигурации (точнее, один из вариантов потери файлов конфигурации). при неудачной записи файла или его недозаписи в результате сбоя в следующем запуске Psi+ использует .backup копию того же файла. но при двух последовательных сбоях получалось так, что backup копия затиралась битым файлом от первого сбоя. таким образом можно получить битую конфигурацию и битую резервную копию одновременно. эта же функция записи и чтения используется не только для accounts.xml и options.xml, но и для events*.xml, т.е. могли теряться также и события + добавлен новый патч psi-fix-historydlg.diff: исправлено отображение ника в окне отображения истории переписки. ошибка связана с тем, что в нике не экранируются спецсимволы html. пример: переименовываем контакт в ник вида: "nic k" открываем историю переписки, смотрим результат. после применения патча ник будет отображаться правильно * исправлен патч psiplus-application-info.diff - qcm/certstore.qcm убран из Psi+ - убран патч psi-fix-historydlg.diff -> принят в upstream * модифицирован патч psi-send-xhtml-im.diff: формат блока для нового сообщения копировался из предыдущего (а в нашем случае - с последнего сообщения), т.е. если входящее сообщение было форматировано с использованием параграфов (pidgin, например), то все последующие сообщения отображались с отступами. данный баг актуален для конференции и для обычных чатов в НЕвебкит версии. в вебкит версии патч не проверялся. - убраны некоторые патчи -> приняты в upstream (psi-fix-rename-contact-in-roster.diff, psi-fix-rename-groups.diff, psi-fix-proccess-first-message.diff) - убран патч psi-fix-crash-when-going-offline.diff (перемещён в dev), т.к. не актуален - убраны некоторые патчи -> приняты в upstream (psi-tmp-hide-useless-roster-options.diff, psi-hide-show-away-action.diff) - убрано ещё больше патчей -> приняты в upstream (psi-do-not-disable-execute-command-menu-item.diff, psi-create-xmlconsole-when-need-it.diff, psi-dont-remove-accounts-with-events.diff, psi-fix-event-removing.diff) * исправлен патч psiplus-new-popups.diff - убраны ненужные патчи (psi-disable-alt-n-switch-tabs.diff, psi-fix-reconnecting-when-account-changed.diff) * исправлен патч psi-mac-sparkle.diff * патч psi-fix-tunes.diff разделён на два отдельных патча psi-fix-winampcontroller.diff и psi-fix-tunes.diff * фиксы патча psi-mac-sparkle.diff * поддержка всплывающих окон в ОС Lion и выше (psiplus-new-popups.diff) - убран патч psi-fix-encoding-of-command-line-arguments.diff (перемещён в dev) * небольшой фикс в патче psi-muc-nick-hash-color.diff - убран патч psi-fix-timer-in-eventdlg.diff -> принят в upstream * объединены патчи на всплывающие окна в один патч psi-extend-popups.diff * патч psi-dont-send-composing-events.diff выделен в отдельный патч. некоторые изменения в патче psi-extend-popups.diff * исправлен патч psi-extend-popups.diff patch под MAC OSX - убраны патчи psi-fix-winampcontroller.diff и psi-fix-sending-disco-items-request-twice.diff -> приняты в upstream + добавлен новый патч на корректное поведение для события приглашения порисовать на рисовальной доске (psi-whiteboard-invitation-event.diff) ! обновлены капсы (caps) до версии 0.16 (psiplus-application-info.diff) * исправлено деление на ноль в juick plugin (v0.11.2) * обновлён client switcher plugin: добавлена возможность игнора запроса версии, обновлён список ОС (v0.0.13) * принят патч для плагинов от https://github.com/FlorianFieber/plugins (client switcher plugin, juick plugin) * исправлен watcher plugin (v0.4.1) * обновлён extended options plugin до версии 0.3.7 * принят патч для плагинов от (pull request #3) fk0/master (extended options plugin, client switcher plugin, watcher plugin) + добавлены иконки аффиляций (lamps-affiliations.jisp и futurama-robots-affiliations.jisp) ! обновлены системные библиотеки qt до версии 4.8.2 http://qt.digia.com/Product/changes/Changes-482/ ! обновлены библиотеки openssl до версии 1.0.1c http://www.openssl.org/news/changelog.html ! обновлена библиотека windows zlib до версии 1.2.7 ! добавлены новые и обновлены существующие файлы переводов для Psi+ от команды переводчиков (от 19.10.2012) https://github.com/psi-plus/psi-plus-l10n -- ! Psi 0.15 released! http://lists.affinix.com/pipermail/psi-devel-affinix.com/2012-October/009348.html * [upstream commit] psi: small fix for historydlg (displaying nicks with special symbols) * [upstream commit] psi: some fixes for contacts and group renaming (tnx liuch) * [upstream commit] psi: fix processing first chat message for some cases * [upstream commit] psi: deactivation of some items at account and contact popup menu * [upstream commit] psi: hide useless use-singleclick option * [upstream commit] psi: hide show-away action * [upstream commit] psi: don't disable execute-command menu item for offline contacts (needed for mrim transport) * [upstream commit] psi: create xml console only if it needed * [upstream commit] psi: don't remove accounts with events * [upstream commit] psi: fix possible memory leaks when removing events * [upstream commit] psi: fixed crash on account remove * [upstream commit] psi: default action on middleclick on contact * [upstream commit] psi: fix drawing status messages in single line * [upstream commit] psi: fix muc roster painting when slim-groups option is enabled ! [upstream commit] psi: bump version number to 0.16-dev * [upstream commit] psi: fix whois timer at eventdlg never stops * [upstream commit] psi: some fixes for winamp tune controller (by KukuRuzo) * [upstream commit] psi: configurable text colors in history dialog. patch for issue #21 * [upstream commit] psi: try use accounts nick when join muc * [upstream commit] psi: fix disco items request sent twice when diskodlg opens * [upstream commit] psi: small fix for options editor * [upstream commit] psi: fix wbdlg title * [upstream commit] iris: added stupid hack to workaround ipv6 problem in http-poll * [upstream commit] iris: disable tls for http-poll in AdvancedConnector since http-poll has its own tls handler * [upstream commit] iris: fix grepshortcutdlg title * updated psi-one-chat-for-many-resources.diff patch. fix unread messages * fixed Entity Time stanza (issue #512) [psi-entity-time.diff] * updated psi-safe-storage-fix.diff patch (issue #515) + added psi-fix-historydlg.diff patch. fixed display of nick * fixed patch - qcm/certstore.qcm removed from psi (psiplus-application-info.diff) - removed psi-fix-historydlg.diff patch -> went to upstream * modified psi-send-xhtml-im.diff patch. reset margin properties for new text blocks - removed some patches -> went to upstream (psi-fix-rename-contact-in-roster.diff, psi-fix-rename-groups.diff, psi-fix-proccess-first-message.diff) - move psi-fix-crash-when-going-offline.diff patch to dev because of useless - removed some patches -> went to upstream (psi-tmp-hide-useless-roster-options.diff, psi-hide-show-away-action.diff) - removed more patches -> went to upstream (psi-do-not-disable-execute-command-menu-item.diff, psi-create-xmlconsole-when-need-it.diff, psi-dont-remove-accounts-with-events.diff, psi-fix-event-removing.diff) * fixed psiplus-new-popups.diff patch - removed some patches because of useless (psi-disable-alt-n-switch-tabs.diff, psi-fix-reconnecting-when-account-changed.diff) * fixed psi-mac-sparkle.diff patch * psi-fix-tunes.diff patch has been splitted on two separate patches psi-fix-winampcontroller.diff and psi-fix-tunes.diff * fixes for psi-mac-sparkle.diff patch * enabled growl popups on Lion and higher (psiplus-new-popups.diff) - moved psi-fix-encoding-of-command-line-arguments.diff patch to dev * small fix in work with constants in psi-muc-nick-hash-color.diff patch - removed psi-fix-timer-in-eventdlg.diff patch -> went to upstream * merged popup patches and move it top. fix other patches (psi-extend-popups.diff) * splited psi-dont-send-composing-events.diff patch into separated patch. some work with popups patch (psi-extend-popups.diff) * fixed psi-extend-popups.diff patch for mac - removed psi-fix-winampcontroller.diff patch -> went to upstream - removed psi-fix-sending-disco-items-request-twice.diff patch -> went to upstream + added new patch for whiteboard invitation events (psi-whiteboard-invitation-event.diff) ! caps version to 0.16 (psiplus-application-info.diff) * fixed division by 0 in juick plugin (v0.11.2) * updated client switcher plugin: added feature for ignoring a request version, updated the OS template (v0.0.13) * merged https://github.com/FlorianFieber/plugins (client switcher plugin, juick plugin) * fixed watcher plugin (v0.4.1) * updated extended options plugin: more clear description for "hiding on auto-join conferences" option (v0.3.7) * merged pull request #3 from fk0/master (extended options plugin, client switcher plugin, watcher plugin) + added lamps affiliations icons (iconsets/affiliations/lamps-affiliations.jisp) + added futurama affiliation icons (iconsets/affiliations/futurama-robots-affiliations.jisp) ! updated qt libs to version 4.8.2, http://qt.digia.com/Product/changes/Changes-482/ ! updated openssl libs to version 1.0.1c, http://www.openssl.org/news/changelog.html ! updated windows zlib to version 1.2.7 ! added new Psi+ translations and updated existing translations (2012-10-19) by https://github.com/psi-plus/psi-plus-l10n 2012-05-22 zet v0.15.5337 * некоторые исправления и улучшения для juick plugin (v0.11.1) + в пакет дистрибутива под windows добавлена необходимая системная библиотека "QtWebKit4.dll" ! обновлены файлы переводов от команды https://github.com/tehnick/psi-plus-i18n -- * some fixes for juick plugin (v0.11.1) + win32 installer: added required system lib 'QtWebKit4.dll' ! updated some Psi+ translations from https://github.com/tehnick/psi-plus-i18n 2012-05-20 zet v0.15.5335 + [коммит в upstream] psi: добавлены временные метки в дебаг-лог * [коммит в upstream] psi: исправлено неоткрывание основного окна в билдах под MAC и Qt Cocoa * [коммит в upstream] psi: исправлено отображение сообщения "no version available" в групчатах + [коммит в upstream] psi: вложенный каталог "translations" теперь используется для хранения файлов переводов (локализаций). старые пути расположения файлов переводов также поддерживаются (для обеспечения обратной совместимости) - [коммит в upstream] psi: убран ненужный флаг dynamic_cast в psicon + [коммит в upstream] psi: теперь используется shared zlib в сборках под MS Windows * [коммит в upstream] iris: более подробные сообщения в дебаг-логе для bsocket и connector * [коммит в upstream] iris: исправлено поведение при SSL handshake после интеграции newdns + [коммит в upstream] libpsi: теперь используется shared zlib в сборках под MS Windows * исправления для хидеров в плагинном интерфейсе (спасибо Hugin) [psi-extend-plugins-interface.diff] - убран патч psi-mac-dock-click.diff -> принят в upstream + добавлен новый патч для удаления некоторых событий (исправлены возможные утечки памяти) [psi-fix-event-removing.diff] + добавлен новый патч для обеспечения совместимости с XEP-0172 (psi-remove-nick-entry-from-muc-stanzas.diff) * поправлен патч rename-contact-in-roster. теперь допустимо указывать пустое имя контакта (psi-fix-rename-contact-in-roster.diff) * убран ненужный код из third-party в файлах wa_ipc.h и AIMPSDKRemote.h (psi-fix-tunes.diff) + добавлен новый патч для использования absoluteFilePath в PsiRiachText (psi-use-absolutefilepath-in-psireachtext.diff) * улучшения в патче psi-improve-tray-tooltip.diff * фикс при обновлении состояния плагинов (psi-extend-plugins-interface.diff) + добавлен новый патч для решения задачи 508 ( http://code.google.com/p/psi-dev/issues/detail?id=508 ) [psi-fix-reconnecting-when-account-changed.diff] + добавлены отсутствующие инклюды для некоторых плагинов (спасибо Florian Fieber) * обновлён birthday reminder plugin до версии 0.4.0 * небольшой фикс для extended menu plugin (v0.1.1) ! начата работа над новой реализацией juick plugin (v0.11.0) ! обновлены системные библиотеки qt до версии 4.8.1, http://qt.nokia.com/products/changes/changes-4.8.1 ! теперь инсталлятор Psi+ под MS Windows содержит 30+ переводов (локализаций) интерфейса приложения на различные языки (спасибо tehnick), https://github.com/tehnick/psi-plus-i18n -- + [upstream commit] psi: added timestamps into debug log * [upstream commit] psi: fix main window not reopening on MAC and Qt Cocoa builds * [upstream commit] psi: fix 'no version available' message in groupchatdlg + [upstream commit] psi: subdirectory 'translations' is now used for translation files. old paths are also checked for back compatibility - [upstream commit] psi: remove useless dynamic_cast in psicon + [upstream commit] psi: use shared zlib on windows * [upstream commit] iris: better debug messaging in bsocket and connector * [upstream commit] iris: fixed regression with SSL handshake after merging newdns + [upstream commit] libpsi: use shared zlib on windows * fixed plugin interface headers (tnx to Hugin) [psi-extend-plugins-interface.diff] - removed psi-mac-dock-click.diff patch -> went to upstream + added new patch for remove some events (fixed possible memory leaks) [psi-fix-event-removing.diff] + added new patch for accordance with XEP-0172 revoved the "nick" entry from muc stanzas (psi-remove-nick-entry-from-muc-stanzas.diff) * fixed rename-contact-in-roster patch. allowed blank contact name (psi-fix-rename-contact-in-roster.diff) * removed unused code from third-party wa_ipc.h and AIMPSDKRemote.h files (psi-fix-tunes.diff) + added new patch for use absoluteFilePath in PsiRiachText (psi-use-absolutefilepath-in-psireachtext.diff) * improved psi-improve-tray-tooltip.diff patch * fixed plugins updating (psi-extend-plugins-interface.diff) + added new patch to solve issue 508 ( http://code.google.com/p/psi-dev/issues/detail?id=508 ) [psi-fix-reconnecting-when-account-changed.diff] + added missing includes for some plugins (thx to Florian Fieber) * updated birthday reminder plugin to v0.4.0 * small fix for extended menu plugin (v0.1.1) ! start works on new juick plugin implementation (v0.11.0) ! updated qt libs to version 4.8.1, http://qt.nokia.com/products/changes/changes-4.8.1 ! win32 installer: added 30+ translations for Psi+ (thx to tehnick), https://github.com/tehnick/psi-plus-i18n 2012-04-08 zet v0.15.5320 * [коммит в upstream] psi: исправлен некорректный адрес fsf * [коммит в upstream] psi: фиксы для сервера Idle: теперь PsiCon контроллирует автостатус для аккаунтов; улучшена работа с опциями; счётчик Idle останавливается если автостатусы не используются * [коммит в upstream] psi: исправлена утечка памяти в диалоге истории переписки (патч от команды Psi+, спасибо liuch) * [коммит в upstream] psi: исправлена работа опций "contact-sort-style", "contactlist.use-single-click", "contactlist.use-outlined-group-headings" и "contactlist.use-slim-group-headings" * [коммит в upstream] psi: адаптирован патч от команды Psi+ на работу с директориями freedekstop * [коммит в upstream] psi: изменены некоторые строки при работе мастера импорта профиля Psi * [коммит в upstream] psi: фикс миграции профиля Psi в ОС Windows * [коммит в upstream] psi: исправлено определение местоположения файлов конфига в профиле Psi в ОС Windows * [коммит в upstream] psi: ещё один фикс определения местоположения файлов профиля Psi+ в ОС Windows * [коммит в upstream] psi: исправлена работа опции "animate online contacts" * [коммит в upstream] psi: исправлена работа всплывающего окна при использовании поиска по ростеру * [коммит в upstream] psi: исправлена работа опции "roster autoresize" * [коммит в upstream] psi: исправлена работа по передаче файлов при перетаскивании файла на контакт ростера * [коммит в upstream] psi: исправлена работа действий по переименованию и удалению в меню контакта ростера * [коммит в upstream] psi: некоторые исправления в отрисовке ростера * [коммит в upstream] psi: исправлен drag-n-drop в ростере конференции * [коммит в upstream] libpsi: исправлен некорректный адрес fsf * [коммит в upstream] libpsi: небольшой фикс для grepshotcuddlg * [коммит в upstream] libpsi: обновлён GrowlNotifier для использования нового фреймворка growl - убраны некоторые патчи -> приняты в upstream (psi-improve-idle.diff, psi-fix-memory-leak.diff, psi-fix-grepshotcutkeydlg.diff) * перемещены патчи для Mac OC X из репозитория maintenance в репозиторий main (brushed-metal.diff, psi-psiwindowheader-left-button.diff, psi-mac-Makefile.diff, psi-mac-sparkle.diff, psi-chat-theme-mac_native.diff, psi-mac-dock-click.diff, psi-new-growl-interface.diff) * исправлен баг в plopt_plugins (psi-extend-plugins-interface.diff) - убраны некоторые патчи -> приняты в upstream (psi-fix-roster-accounts-and-groups-drawing.diff, psi-fix-contact-sorting-style-option.diff, psi-fix-use-single-click.diff) + добавлен новый патч для исправления поведения при переименовании контакта в ростере (psi-fix-rename-contact-in-roster.diff) * исправлен патч fixed psi-extend-plugins-interface.diff * фикс патча psiplus-binary.diff. далее необходимо разделить данный патч и переместить некоторые его части в другие патчи * отделена часть патча psiplus-binary.diff и перемещена в патч psi-extend-plugins-interface.diff * продолжение работ по разделению патча psiplus-binary.diff (psi-options-language-select.diff, psi-autostart.diff) * исправлен патч fixed psi-bookmark-skip.diff * завершено разделение патча psiplus-binary.diff (psi-webkit.diff, psiplus-application-info.diff) - убран ненужный патч psiplus-binary.diff * подготовка некоторых патчей к отправке в upstream (psi-modern-roster.diff, psi-muc-minimize-to-roster.diff, psi-block-contact-from-menu.diff, psi-fix-pgp-icon-in-roster.diff) - убраны некоторые патчи -> приняты в upstream (psi-fix-online-contacts-animation.diff, psi-fix-tooltip-when-searching-in-roster.diff) * доработка патча psi-multilang-spelling-for-aspell.diff - теперь существует проверка на наличие переменной LANG - убраны некоторые патчи -> приняты в upstream (psi-fix-roster-autoresize.diff, psi-send-file-when-drop-to-roster.diff, psi-fix-remove-action.diff) * обновлён патч psi-fix-rename-contact-in-roster.diff + добавлен новый патч для исправления поведения при переименовании групп ростера (psi-fix-rename-groups.diff) * исправлен патч psi-modern-roster.diff после коммитов в upstream - убраны некоторые патчи -> приняты в upstream (mac/psi-new-growl-interface.diff, psi-fix-drag-contacts-in-muc-roster.diff) * небольшие исправления в init при нажатии кнопки мыши в верхней области поля окна (psiplus-decorate-windows.diff) * PsiWindowHeader теперь поддерживает и Mac OS X. некоторые оптимизации кода в PsiWindowHeader (psiplus-decorate-windows.diff) * убрана ненужная часть патча mac/psi-psiwindowheader-left-button.diff * завершено разделение патча mac/psi-psiwindowheader-left-button.diff (psi-default-application-settings.diff) - убран ненужный патч mac/psi-psiwindowheader-left-button.diff patch * исправлен регресс в патче psi-fix-rename-groups.diff - ошибка при удалении контакта * обновлён список активных участников проекта Psi+ (psiplus-aboutdlg.diff) * обновлён extended options plugin до версии 0.3.7 -- * [upstream commit] psi: fixed incorrect fsf address * [upstream commit] psi: fixed Idle: now PsiCon controls auto-away status for accounts; improved work with options; Idle stops if auto-statuses does not use * [upstream commit] psi: fixed memory leaks in history (patch from Psi+, thx liuch) * [upstream commit] psi: fixed 'contact-sort-style', 'contactlist.use-single-click', 'contactlist.use-outlined-group-headings' and 'contactlist.use-slim-group-headings' options * [upstream commit] psi: backported Psi+ patch for freedekstop dirs * [upstream commit] psi: changed some strings in import wizard * [upstream commit] psi: fixed migration on windows * [upstream commit] psi: fixed detection of config data dirs on windows * [upstream commit] psi: one yet fix for windows profile dir * [upstream commit] psi: fixed animate online contacts option * [upstream commit] psi: fixed tooltip on contacts when search in roster * [upstream commit] psi: fixed roster autoresize option * [upstream commit] psi: fixed send files when drop to roster * [upstream commit] psi: fixed rename and remove actions at contacts menu * [upstream commit] psi: some fixes for roster painting * [upstream commit] psi: fixed drag-n-drop in muc roster * [upstream commit] iris: relink psi when iris sources changes. use qDebug instead of printf to make messages visible in debug view of qtreator * [upstream commit] libpsi: fixed incorrect fsf address * [upstream commit] libpsi: small fix for grepshotcuddlg * [upstream commit] libpsi: update GrowlNotifier for using new growl framework - removed some patches -> went to upstream (psi-improve-idle.diff, psi-fix-memory-leak.diff, psi-fix-grepshotcutkeydlg.diff) * moved mac patches from maintenance to main (brushed-metal.diff, psi-psiwindowheader-left-button.diff, psi-mac-Makefile.diff, psi-mac-sparkle.diff, psi-chat-theme-mac_native.diff, psi-mac-dock-click.diff, psi-new-growl-interface.diff) * fixed bug in plopt_plugins (psi-extend-plugins-interface.diff) - removed some patches -> went to upstream (psi-fix-roster-accounts-and-groups-drawing.diff, psi-fix-contact-sorting-style-option.diff, psi-fix-use-single-click.diff) + added new patch for fix rename contact in roster (psi-fix-rename-contact-in-roster.diff) * fixed psi-extend-plugins-interface.diff patch * fixed brending patch. still needs to move its parts into other patches (psiplus-binary.diff) * moved some part of psiplus-binary.diff patch into psi-extend-plugins-interface.diff patch * more split of psiplus-binary.diff patch (psi-options-language-select.diff, psi-autostart.diff) * fixed psi-bookmark-skip.diff patch * spliting psiplus-binary.diff patch is completed (psi-webkit.diff, psiplus-application-info.diff) - removed useless psiplus-binary.diff patch * prepared some patches for upstream (psi-modern-roster.diff, psi-muc-minimize-to-roster.diff, psi-block-contact-from-menu.diff, psi-fix-pgp-icon-in-roster.diff) - removed some patches -> went to upstream (psi-fix-online-contacts-animation.diff, psi-fix-tooltip-when-searching-in-roster.diff) * improved psi-multilang-spelling-for-aspell.diff patch - check LANG variable - removed some patches -> went to upstream (psi-fix-roster-autoresize.diff, psi-send-file-when-drop-to-roster.diff, psi-fix-remove-action.diff) * updated psi-fix-rename-contact-in-roster.diff patch + added new patch for fix rename groups (psi-fix-rename-groups.diff) * fixed psi-modern-roster.diff patch after upstream commit - removed some patches -> went to upstream (mac/psi-new-growl-interface.diff, psi-fix-drag-contacts-in-muc-roster.diff) * small fix in mouse press init at the top region of the borderless window (psiplus-decorate-windows.diff) * PsiWindowHeader now supports Mac OS X. some code optimizations in PsiWindowHeader (psiplus-decorate-windows.diff) * removed a part of mac/psi-psiwindowheader-left-button.diff patch * spliting psi-psiwindowheader-left-button.diff patch is complited (psi-default-application-settings.diff) - removed useless mac/psi-psiwindowheader-left-button.diff patch * fixed regression in psi-fix-rename-groups.diff patch. error when deleting a contact * updated psiplus-aboutdlg.diff patch * updated extended options plugin to v0.3.7 2012-04-01 zet v0.15.5284 * [коммит в upstream] psi: убрана ненужная анимация из меню на нике в чатлоге + [коммит в upstream] psi: добавлена поддержка распознавания ссылок типа "magnet:" в чатлогах + [коммит в upstream] psi: поддержка распознавания ссылок типа "xmpp:" в freedesktop * [коммит в upstream] iris: фикс передачи файлов большого размера, подробнее - http://code.google.com/p/psi-dev/issues/detail?id=499 * [коммит в upstream] iris: исправлено возможное падение приложения при закрытии приложения в момент получения входящей станзы * возвращена ежесекундная проверка состояния idle (psi-improve-idle.diff) + добавлен новый патч для запрета мгновенного обновления счётчика онлайн-контактов в ростере - небольшое увеличение скорости запуска приложения (psi-fix-online-contacts-count-update.diff) * улучшения в плагинной системе - не используются отладочные символы в релизных сборках; исправлено включение/отключение плагинов из настроек приложения; автоматическая установка автозагрузки плагина после первого использования (psi-extend-plugins-interface.diff) + добавлен новый патч, убирающий ненужную анимацию из некоторых меню приложения (psi-remove-anim-from-urlmenu.diff) * переименован патч psi-remove-anim-from-urlmenu.diff, а также убрана ненужная анимация из меню iconactions (psi-remove-animation-from-iconactions.diff) - убран патч psi-remove-animation-from-iconactions.diff -> принят в upstream + добавлен новый патч append_sys_message от Hugin (объединён с патчем psi-extend-plugins-interface.diff) * улучшения в dbus-попапах: проверка возможностей сервера (psiplus-new-popups.diff) * переписан idle патч и перемещён в начало очереди применения патчей - подготовка к передаче в upstream (psi-improve-idle.diff) * исправлены некоторые патчи (psi-presets-in-status-menu.diff, psiplus-new-popups.diff) * исправления для сервера idle (psi-idle-server.diff) * исправлены прочие патчи (psi-ignoring-global-statuses.diff, psi-fix-online-contacts-count-update.diff, psi-extend-plugins-interface.diff, psi-more-effective-work-with-options.diff, psiplus-binary.diff) * фикс патча psiplus-binary.diff после коммита в upstream * обновлён image plugin до версии 0.1.2 * обновлён gnome3 support plugin (только для *nix-систем) * [psimedia+] исправлен некорректный адрес FSF * [psimedia+] фиксы сборки с glib >= 2.32 ! обновлены системные библиотеки openssl до версии 1.0.1, http://www.openssl.org/news/changelog.html -- * [upstream commit] psi: remove crazy animation from iconactions + [upstream commit] psi: added stupid magnet links support + [upstream commit] psi: added some freedesktop magic into desktop file * [upstream commit] iris: fixed int overflow in filetransfer when ranged transfer used (Psi+ Issue 499, http://code.google.com/p/psi-dev/issues/detail?id=499 ) * [upstream commit] iris: fixed possible crash when Client::close called during incoming stanza processing * reverted every second idle checking (psi-improve-idle.diff) + added new patch for deny update online contacts count instantly - a bit faster application start (psi-fix-online-contacts-count-update.diff) * improved plugins patch - dont use qdebug in release builds; fix loading-unloading plugins from options; automatically set auto-load option at first accessing (psi-extend-plugins-interface.diff) + added new patch for fix animation never stops (psi-remove-anim-from-urlmenu.diff) * renamed psi-remove-anim-from-urlmenu.diff patch and removed animation from iconactions (psi-remove-animation-from-iconactions.diff) - removed psi-remove-animation-from-iconactions.diff patch -> went to upstream + added append_sys_message patch by Hugin (merged with psi-extend-plugins-interface.diff patch) * improved dbus popups: check server capabilities (psiplus-new-popups.diff) * rewritten idle patch and moved to top of patches - prepared for moving to upstream (psi-improve-idle.diff) * fixed some patches (psi-presets-in-status-menu.diff, psiplus-new-popups.diff) * fixed idle server (psi-idle-server.diff) * fixed other patches (psi-ignoring-global-statuses.diff, psi-fix-online-contacts-count-update.diff, psi-extend-plugins-interface.diff, psi-more-effective-work-with-options.diff, psiplus-binary.diff) * fixed psiplus-binary.diff patch after upstream commit * updated image plugin to v0. * updated gnome3 support plugin (*nix only) * [psimedia+] fixed incorrect FSF address * [psimedia+] fixed build with glib >= 2.32 ! updated openssl libs to version 1.0.1, http://www.openssl.org/news/changelog.html 2012-03-25 zet v0.15.5268 * [коммит в upstream] psi: теперь используется static_cast вместо dynamic_cast в contactlistproxymodel * теперь используется (jid) вместо в dbus-попапах (psiplus-new-popups.diff) + добавлен новый патч для проверки орфографии для нескольких языков одновременно. основан на патче отсюда http://forum.psi-plus.com/viewtopic.php?f=8&t=94 . взят только базовый функционал. для проверки используются все доступные в системе языки. необходимые языки указываются через пробел в advanced-опции "options.ui.spell-check.langs" (psi-multilang-spelling-for-aspell.diff) + добавлен новый патч для исправления бага, из-за которого при поиске в списке контактов использовался ресурс контакта. вследствие этого контакт дублировался в группе "Not in list". например, актуально для плагинов, использующих ссылки (psi-chat-not-in-list-fix.diff) * исправлен патч psiplus-new-popups.diff + добавлен новый патч, запрещающий проверку орфографии при наборе цифр ( http://code.google.com/p/psi-dev/issues/detail?id=396 ) [psi-dont-spellcheck-digits.diff] + добавлен новый патч для grepshorcutkeydlg. исправлена утечка памяти. теперь работает в Mac OS X [psi-fix-grepshotcutkeydlg.diff] + улучшения в патче psi-fix-eventnotifier.diff - панель уведомлений не будет скрываться, если этого не требуется. ранее панель с количеством пропущенных сообщений в ростере всегда пряталась после прочтения сообщения, независимо от настроек тулбаров в опциях * переименована опция в меню ростера (Blocked -> Ignore global actions) [psi-ignoring-global-statuses.diff] + добавлена новая иконка "ignore_global_actions.png" в системный иконпак (psi-iconsets.diff, psi-one-chat-for-many-resources.diff) * обновлён патч psi-ignoring-global-statuses.diff - используется новая иконка и пункт меню перенесён на уровень глубже, в меню статусов + добавлен новый патч для уменьшения количества вызовов при работе с настройками приложения (psi-improve-idle.diff) * небольшая оптимизация в некоторых патчах. userAccount() -> accountOptions() [psi-presets-in-status-menu.diff, psi-ignoring-global-statuses.diff, psi-extend-plugins-interface.diff] + добавлен новый патч для более эффективной работы с настройками - исключение вызова в одном и том же месте по 2-3-4 раза одной и той же опции. также добавлено кэширование некоторых настроек в делегате ростера (должна возрасти скорость отрисовки) [psi-more-effective-work-with-options.diff] * idle - опрашивать каждую минуту вместо каждой секунды (psi-improve-idle.diff) * некоторые фиксы в некоторых патчах (psi-improve-idle.diff, psi-extend-plugins-interface.diff, psi-more-effective-work-with-options.diff) * переписан патч psi-more-effective-work-with-options.diff * улучшения в патче psi-hide-muc-auto-join.diff * исправлен патч psi-always-visible-contacts.diff после коммита в upstream * фикс компиляции в Mac OS X (psi-more-effective-work-with-options.diff) + добавлен новый патч для growl-интерфейса (scripts/macosx/patches/3060-psi-new-growl-interface.diff) * обновлён storage notes plugin до версии 0.1.8 * обновлён translate plugin до версии 0.4.3 * обновлён gmail service plugin до версии 0.7.4 * обновлён video status changer plugin до версии 0.2.3 * обновлён screenshot plugin до версии 0.6.4 * исправлен content downloader plugin (убран ненужный редирект), v0.2.2 -- * [upstream commit] psi: use static_cast instead of dynamic_cast in contactlistproxymodel * use (jid) instead of in dbus popups (psiplus-new-popups.diff) + added new patch for spell checking for multiple languages at once. based on patch from here http://forum.psi-plus.com/viewtopic.php?f=8&t=94 . only basic functionality. for check uses all available languages in the system. use 'options.ui.spell-check.langs' to specify langs (no commas, just through space) [psi-multilang-spelling-for-aspell.diff] + added new patch for fix a bug due to which the search in the roster used by the contact resource. contact is duplicated in group 'Not in list'. for example, in the relevant plugins that use the urls (psi-chat-not-in-list-fix.diff) * fixed psiplus-new-popups.diff patch + added new patch for deny spellcheck of digits ( http://code.google.com/p/psi-dev/issues/detail?id=396 ) [psi-dont-spellcheck-digits.diff] + added new patch for fix grepshorcutkeydlg. fixed memory leak. now works on Mac OS X [psi-fix-grepshotcutkeydlg.diff] + improved psi-fix-eventnotifier.diff patch - eventnotifier will not hiding if it shouldnt. the panel with the number of missed messages in the roster is always hiding after reading the message. this occurred regardless of the settings in the options toolbar * renamed menu item (Blocked -> Ignore global actions) [psi-ignoring-global-statuses.diff] + added 'ignore_global_actions.png' icon into iconsets (psi-iconsets.diff, psi-one-chat-for-many-resources.diff) * updated psi-ignoring-global-statuses.diff patch - the menu item is moved to a level deeper in the status menu + added new patch for less options calling every second in secondsIdle() [psi-improve-idle.diff] * small optimization for some patches. userAccount() -> accountOptions() [psi-presets-in-status-menu.diff, psi-ignoring-global-statuses.diff, psi-extend-plugins-interface.diff] + added new patch for more effective work with options - exception to the call in the same place at 2-3-4 times the same option. also added some caching settings on the delegate roster (should increase rendering speed) [psi-more-effective-work-with-options.diff] * idle - poll every minute instead of every seccond (psi-improve-idle.diff) * some fixes for some patches (psi-improve-idle.diff, psi-extend-plugins-interface.diff, psi-more-effective-work-with-options.diff) * improved psi-more-effective-work-with-options.diff patch * improved psi-hide-muc-auto-join.diff patch * fixed psi-always-visible-contacts.diff patch after upstream commit * fixed compilations on mac (psi-more-effective-work-with-options.diff) + added new growl interface patch (scripts/macosx/patches/3060-psi-new-growl-interface.diff) * updated storage notes plugin to v0.1.8 * updated translate plugin to v0.4.3 * updated gmail service plugin to v0.7.4 * updated video status changer plugin to v0.2.3 * updated screenshot plugin to v0.6.4 * fixed content downloader plugin (removed unnecessary redirect), v0.2.2 2012-03-09 zet v0.15.5242 * [коммит в upstream] psi: исправлено падение приложения при использовании service discovery + добавлен новый патч, исправляющий несколько утечек памяти в истории сообщений (поиск, навигация, экспорт) [psi-fix-memory-leak.diff] + добавлен новый патч, запрещающий удаление аккаунта, содержащего непрочитанные сообщения/события (решена задача 492, http://code.google.com/p/psi-dev/issues/detail?id=492) [psi-dont-remove-accounts-with-events.diff] + добавлен новый патч, который позволяет указывать аккаунты, для которых будут игнорироваться глобальные установки статуса. а именно: автостатус, настроения, занятия, геолокация, статус из главного окна, в том числе, текст статуса. исключение сделано для глобального offline - при этом отключатся все аккаунты (как обычно) [psi-ignoring-global-statuses.diff] + добавлен новый патч - попытка исправить баг, в результате которого иногда самопроизвольно обнуляются accounts.xml и options.xml. при открытии файла явно указано о его обнулении. необходимо в случае если предыдущая запись в файл не была корректно завершена. после применения патча QTextStream и QFile закрываются явно. в случае QFile теперь проверяется не только наличие ошибок записи, но и ошибки при сбросе буферов перед закрытием файла (psi-safe-storage-fix.diff) * обновлён патч psi-ignoring-global-statuses.diff. опция перенесена из диалога настроек аккаунта в меню аккаунта * некоторые фиксы в отрисовке основного ростера и ростера конференции (psi-modern-roster.diff, psi-custom-icons-size.diff, psi-modern-muc-roster.diff) * небольшие исправления для growl-уведомлений (psiplus-new-popups.diff) * исправлена ошибка, приводившая к пустому потоку в tune-контроллере AIMP (psi-fix-tunes.diff) * исправлена работа с именами файлов в tune-контроллере AIMP (psi-fix-tunes.diff) * контроллер Winamp теперь генерирует правильный tune (psi-fix-tunes.diff) * исправлено определение времени в tune-контроллере AIMP (psi-fix-tunes.diff) + добавлен новый патч для исправления переоткрытия ростера в MAC OS X (решена задача 403, http://code.google.com/p/psi-dev/issues/detail?id=403) [scripts/macosx/patches/3050-psi-mac-dock-click.diff] * исправлена опечатка в тексте предупреждение при закрытии несохранённого скриншота (screenshot plugin, v0.6.2) * исправлен translate plugin (v0.4.2) * обновлён storage notes plugin до версии 0.1.8 -- * [upstream commit] psi: fixed random crash when closing discodlg + added new patch for fix several memory leaks in history of messages (search, navigation, exports) [psi-fix-memory-leak.diff] + added new patch for deny to remove accounts with unread events (closed issue 492, http://code.google.com/p/psi-dev/issues/detail?id=492) [psi-dont-remove-accounts-with-events.diff] + added mew patch for allows you to specify which accounts for the global settings will be ignored status. namely avtostatus, moods, activities, geolocation, the status of the main window, including the status text. an exception is made for the global offline - shut off all the accounts as usual (psi-ignoring-global-statuses.diff) + added new patch for try to fix a bug in which sometimes spontaneously reset accounts.xml and options.xml. when you open the file explicitly reset it. necessary if the previous record in the file has not been properly completed. after patch QTextStream and QFile closed explicitly. in the case of QFile is now checked for errors, not only writing, but when you reset the error buffer before you close the file (psi-safe-storage-fix.diff) * updated psi-ignoring-global-statuses.diff patch. option moved from the account settings dialog menu to the account context menu * some fixes for painting roster and muc roster (psi-modern-roster.diff, psi-custom-icons-size.diff, psi-modern-muc-roster.diff) * small fix for growl notifications (psiplus-new-popups.diff) * fixed empty tunes flood in AIMP controller (psi-fix-tunes.diff) * fixed work with filenames in AIMP tunecontroller (psi-fix-tunes.diff) * Winamp tunecontroller now can generate correct tune (psi-fix-tunes.diff) * fixed time detection in AIMP tunecontroller (psi-fix-tunes.diff) + added new patch for fix reopen roster when dock clicked on cocoa in MAC OS X (closed issue 403, http://code.google.com/p/psi-dev/issues/detail?id=403) [scripts/macosx/patches/3050-psi-mac-dock-click.diff] * fixed typo in screenshot plugin (v0.6.2) * fixed translate plugin (v0.4.2) * updated storage notes plugin to v0.1.8 2012-02-25 zet v0.15.5225 * [коммит в upstream] psi: теперь ошибка "Unable to retrieve your account information." не отображается для собственного вкарда при логине если опция "options.vcard.query-own-vcard-on-login" выключена * [коммит в upstream] psi: исправлен метод soundPlay(const QString &s) - при пустой строке в настройках звука теперь используется тишина (вместо проигрывания звука по умолчанию из системы) * [коммит в upstream] psi: добавлена возможность установить отдельный звук для подсвеченных сообщений из конференций (патч от команды Psi+) * исправлен патч psi-typeahead-find.diff * исправлен патч psi-chatview-quote-feature.diff ! рефакторинг всплывающих окон (попапов) - добавлена поддержка freedesktop.Notifications (только для *NIX), фиксы Growl-нотификаций (MAC OS X), прочие исправления и улучшения [psiplus-new-popups.diff, psi-extend-plugins-interface.diff] + добавлен новый патч на установку отдельного звука для подсвеченных сообщений из конференций (psi-sound-for-muc-messages.diff) [by smartly] * улучшения в dbus-попапах: теперь при закрытии попапов пользователем будет вызываться действие по умолчанию (psiplus-new-popups.diff) * некоторые исправления в стиле кода для новых попапов Psi+, исправлена возможная утечка памяти в попапах Psi+, добавлены новые advanced-опции "options.ui.notifications.passive-popups.suppress-while-dnd" и "options.ui.notifications.passive-popups.suppress-while-away" (psiplus-new-popups.diff) * добавлены подсказки для dbus-попапов (psiplus-new-popups.diff) * проверка статуса при показе попапов для всех типов попапов (psiplus-new-popups.diff, psi-extend-plugins-interface.diff) - убран патч psi-sound-for-muc-messages.diff -> принят в upstream * объединены патчи psi-muc-notify-highlight.diff и psiplus-new-popups.diff * улучшения в работе попапов для плагинного интерфейса (psiplus-new-popups.diff, psi-extend-plugins-interface.diff) * попытка исправить падение приложения при работе dbus-попапов (psiplus-new-popups.diff) * фиксы описаний в dbus-попапах, исправлена утечка памяти при работе попапов из конференций, теперь попапы из конференций кликабельны (psiplus-new-popups.diff) * убрана команда Block из меню на контакте конференции в ростере (решена задача 481, http://code.google.com/p/psi-dev/issues/detail?id=481) [psi-block-contact-from-menu.diff] * исправлен патч fixed psi-send-custom-status.diff (решена задача 489, http://code.google.com/p/psi-dev/issues/detail?id=489) * исправлена последовательность переключения между полями на странице настройки шаблонов статусов (решена задача 483, http://code.google.com/p/psi-dev/issues/detail?id=483) [psi-presets-in-status-menu.diff] + добавлен новый патч для поддержки отображения аватаров в попапах из конференций (psi-muc-avatars-at-popups.diff) * оптимизация плагинной системы. при отключении плагина его опция удаляется из попапов (имеется ввиду настройка интервала отображения) * больше переводимых строк в extended options plugin * обновлены updated attention plugin, birthday reminder plugin, client switcher plugin, extended menu plugin, gmail service plugin, pep change notify plugin, stop spam plugin, storage notes plugin * обновлён watcher plugin, некоторые фиксы для attention plugin и pep change notify plugin * исправлен client switcher plugin * исправлен и обновлён yandex narod plugin * исправлен regexp для omploader в screenshot plugin * обновлён video status plugin * исправлен патч scripts/macosx/patches/3030-psi-mac-sparkle.diff -- * [upstream commit] psi: don't show 'Unable to retrieve your account information.' error for own vcard on login if 'options.vcard.query-own-vcard-on-login' was disabled * [upstream commit] psi: fix void soundPlay(const QString &s) method if s is empty string * [upstream commit] psi: add ability to set sound for muc highlightes (from Psi+ patch) * fixed psi-typeahead-find.diff patch * fixed psi-chatview-quote-feature.diff patch ! more works with popups - added freedesktop.Notifications (*NIX only), fixes for Growl notifications (MAC OS X), other fixes (psiplus-new-popups.diff, psi-extend-plugins-interface.diff) + added a new patch to be able to set a separate sound for messages from the conference (psi-sound-for-muc-messages.diff) [by smartly] * improved dbus popups: now when popup closes by user - default action will invoke (psiplus-new-popups.diff) * some fixes for codestyle of Psi+ popups, fixed possible memory leak in Psi+ popups, added options 'options.ui.notifications.passive-popups.suppress-while-dnd' and 'options.ui.notifications.passive-popups.suppress-while-away' (psiplus-new-popups.diff) * added transient hint for dbus popups (psiplus-new-popups.diff) * do status check when show popups for all popup types (psiplus-new-popups.diff, psi-extend-plugins-interface.diff) - removed psi-sound-for-muc-messages.diff patch -> went to upstream * merged psi-muc-notify-highlight.diff and psiplus-new-popups.diff patches * improved popup plugin interface (psiplus-new-popups.diff, psi-extend-plugins-interface.diff) * try to fix crash on awesome with new dbus popups (psiplus-new-popups.diff) * fixed descriptions in dbus popups, fixed memory leak for groupchat popups, groupchat popups now can be clicked (psiplus-new-popups.diff) * dont block mucs (closed issue 481, http://code.google.com/p/psi-dev/issues/detail?id=481) [psi-block-contact-from-menu.diff] * fixed psi-send-custom-status.diff patch (closed issue 489, http://code.google.com/p/psi-dev/issues/detail?id=489) * fixed tabstop at opt_statusauto (closed issue 483, http://code.google.com/p/psi-dev/issues/detail?id=483) [psi-presets-in-status-menu.diff] + added avatars for muc popups (psi-muc-avatars-at-popups.diff) * optimization for the plugin system. if you disable the plugin, it removes the option of the popups (meaning set the display interval) * more translatable strings in extended options plugin * updated attention plugin, birthday reminder plugin, client switcher plugin, extended menu plugin, gmail service plugin, pep change notify plugin, stop spam plugin, storage notes plugin * updated watcher plugin, some fixes for attention plugin, pep change notify plugin * fixed client switcher plugin * fixed yandex narod plugin * fixed regexp for omploader in screenshot plugin * updated video status plugin * fixed scripts/macosx/patches/3030-psi-mac-sparkle.diff patch 2012-02-12 zet v0.15.5195 * [коммит в upstream] psi: исправлены лицензионные заголовки в файлах * [коммит в upstream] iris: исправлен адрес fsf - [коммит в upstream] psi: убраны пустые и ненужные файлы tune-контроллера * [коммит в upstream] psi: исправлен баг в ContactListView::rename() * changelog.txt теперь кодируется в UTF-8 with BOM - убран ненужный changelog_cp1251.txt * фиксы в патче fixed psi-send-xhtml-im.diff: убраны отступы, возникающие вследствии применения html-форматирования сообщений. не всегда удаётся разместить форматированное сообщение не с новой строки. например, если его вставить в строку ввода посредством Ctrl+ArrowUp. в этом случае формируется несколько \ тегов * исправлен год в копирайте диалога "О программе" (psiplus-aboutdlg.diff) * объединены некоторые патчи на всплывающие окошки (psiplus-new-popups.diff) * фиксы сдвига в патче psi-geolocation.diff * обновлён yandex narod plugin to v0.1.1 * исправлен адрес fsf в файлах некоторых плагинов (client switcher plugin, gomoku game plugin) * исправлен stop spam plugin (v0.5.4) * обновлён video status plugin to v0.1.8 * обновлён content downloader plugin to v0.2.2 ! обновлены библиотеки qt до версии 4.8.0, http://labs.qt.nokia.com/2011/12/15/qt-4-8-0-released/ ! обновлены библиотеки openssl до версии 1.0.0g, http://www.openssl.org/news/changelog.html ! обновлена русская локализация от ivan101, https://github.com/ivan101/psi-plus-ru (2011-12-26) -- * [upstream commit] psi: fixed lincense headers * [upstream commit] iris: fixed fsf address - [upstream commit] psi: removed empty tune controller files * [upstream commit] psi: fix bug in ContactListView::rename() * changelog.txt now encoded in UTF-8 with BOM - removed useless changelog_cp1251.txt * fixed psi-send-xhtml-im.diff patch: unindented arise as a consequence of html formatted messages. it's not always possible to place the formatted message with a new line. for example, if you insert it into the input line by trl + ArrowUp. in this case, a few \ tags * updated year in aboutdlg (psiplus-aboutdlg.diff) * merged some popup patches (psiplus-new-popups.diff) * fixed fuzz in psi-geolocation.diff patch * updated yandex narod plugin to v0.1.1 * corrected fsf address for some Psi+ plugins (client switcher plugin, gomoku game plugin) * fixed stop spam plugin (v0.5.4) * updated video status plugin to v0.1.8 * updated content downloader plugin to v0.2.2 ! updated qt libs to version 4.8.0, http://labs.qt.nokia.com/2011/12/15/qt-4-8-0-released/ ! updated openssl libs to version 1.0.0g, http://www.openssl.org/news/changelog.html ! updated russian localization by ivan101, https://github.com/ivan101/psi-plus-ru (2011-12-26) 2012-01-22 zet v0.15.5185 ! [коммит в upstream] обновление для iris ! [коммит в upstream] добавлен флаг OTHER_FILES в файл проекта psi.pro для удобства ! [коммит в upstream] исправлена работа dbus при старте приложения. устранено падение приложения при открытии приватного сообщения из конференции ! [коммит в upstream] улучшен механизм загрузки иконсетов при работе с ними в настройках приложения - убран патч psi-gcc-4.6-compatibility.diff -> принят в upstream * исправлен патч psi-muc-minimize-to-roster.diff - отображение разделителя неактивных сессий при сворачивании конференции в ростер * исправлен вылет Psi+ в случае когда список пользователей для чата был пуст. например, при попытке открытия чата с конференцией psi-dev, если сама конференция уже активна (psi-one-chat-for-many-resources.diff) * устранено падение приложения при деактивации в настройках приложения флажка выбора цвета дополнительного текста в чатах/конференциях (psi-webkit.diff) + добавлен новый смайлпак Android12x12.jisp * обновлён системный иконпак oxygen_sys.jisp (iconsets/system/oxygen_sys.jisp) * обновлены скины для Mac OSX (skins/mac/native_mac/NativeMac_tt.skn, skins/mac/native_mac/images/qcombobox.png) * обновлены системные иконпаки blacksys.jisp, whitesys.jisp (iconsets/system/blacksys.jisp, iconsets/system/whitesys.jisp) + добавлен новый ростерный иконпак mrim2012.jisp (iconsets/roster/mrim2012.jisp) * обновлён ростерный иконпак icq.jisp (iconsets/roster/icq.jisp) - убраны устаревшие ростерные иконпаки для mrim-транспорта (iconsets/roster/mrim2006.jisp, iconsets/roster/mrim2008.jisp) * win32 installer: экспериментально добавлена проверка на запущенный процесс "psi-plus.exe" при инсталляции/деинсталляции приложения (KillProc) -- ! [upstream commit] iris: prefer null strings to empty ! [upstream commit] added OTHER_FILES to psi.pro for convenience ! [upstream commit] fixed dbus warning on start. fixed crash on opening muc private chat ! [upstream commit] improved loading of iconsets in options - removed psi-gcc-4.6-compatibility.diff patch -> went to upstream * fixed psi-muc-minimize-to-roster.diff patch - do trackbar when muc hidden to roster * fixed a bug that caused the crash, in some cases (psi-one-chat-for-many-resources.diff) * fixed crash on deactivating color checkbox of additional text (psi-webkit.diff) + added Android12x12.jisp emoticons iconset * updated oxygen_sys.jisp (iconsets/system/oxygen_sys.jisp) * updated mac skins (skins/mac/native_mac/NativeMac_tt.skn, skins/mac/native_mac/images/qcombobox.png) * updated blacksys.jisp, whitesys.jisp (iconsets/system/blacksys.jisp, iconsets/system/whitesys.jisp) + added mrim2012.jisp (iconsets/roster/mrim2012.jisp) * updated icq.jisp (iconsets/roster/icq.jisp) - removed ugly mrim iconsets (iconsets/roster/mrim2006.jisp, iconsets/roster/mrim2008.jisp) * win32 installer: added 'KillProcDLL::KillProc' 2011-12-25 zet v0.15.5160 ! [коммит в upstream] теперь по умолчанию всегда выключен горизонтальный скролл-бар в ростере конференции ! [коммит в upstream] исправлена регрессия в zlib.qcm - убран патч psi-fix-filetransfer.diff -> патч принят в upstream + добавлен новый патч для решения задачи 466, http://code.google.com/p/psi-dev/issues/detail?id=466 (psi-send-file-when-drop-to-roster.diff) * обновлён патч psi-send-file-when-drop-to-roster.diff - убран патч psi-muc-roster-disable-horizontal-scrollbar.diff -> патч принят в upstream + добавлен новый патч, исправляющий работу таймера в eventdlg (psi-fix-timer-in-eventdlg.diff) * включение плагина на этапе старта приложения (если плагин был включен в настройках приложения) [psi-extend-plugins-interface.diff] * запрет блокировки самого себя в контекстном меню self-contact (psi-block-contact-from-menu.diff) + добавлен новый патч, исключающий возможность удаления собственного контакта (self-contact) [psi-fix-remove-action.diff] * больше строк для локализации (psi-one-chat-for-many-resources.diff) * исправлена отрисовка иконок в чатах webkit-версии (psi-webkit.diff) * исправлено падение приложения при попытке изменить роль/афиляцию вышедшему участнику конференции (psi-reasons-for-roles-affiliations.diff). решена задача 476, http://code.google.com/p/psi-dev/issues/detail?id=476 * обновлён video status changer plugin (v0.1.7): добавлена поддержка определения полноэкранного режима для MS Windows, http://psi-plus.com/wiki/plugins#video_status_changer_plugin * обновлён extended options plugin до версии 0.3.6 * обновлён gmail service plugin до версии 0.7.2 ! в win32-инсталлятор добавлен video status changer plugin ! обновлена русская локализация от ivan101, https://github.com/ivan101/psi-plus-ru (2011-12-25) -- ! [upstream commit] always off for muc roster's horizontal scrollbar ! [upstream commit] fixed regression in zlib.qcm - removed psi-fix-filetransfer.diff patch -> went to upstream + added a new patch to solve the issue 466, http://code.google.com/p/psi-dev/issues/detail?id=466 (psi-send-file-when-drop-to-roster.diff) * updated psi-send-file-when-drop-to-roster.diff patch - removed psi-muc-roster-disable-horizontal-scrollbar.diff patch -> went to upstream + added new patch for fix timer in eventdlg (psi-fix-timer-in-eventdlg.diff) * enable new plugin if it enabled in options (psi-extend-plugins-interface.diff) * don't block yourself in self-contact context menu (psi-block-contact-from-menu.diff) + added a new patch for fix remove action in contacts menu (psi-fix-remove-action.diff) * updated messages for translate (psi-one-chat-for-many-resources.diff) * fixed icons drawing in webkit chat (psi-webkit.diff) * fixed psi-reasons-for-roles-affiliations.diff patch (solved the issue 476, http://code.google.com/p/psi-dev/issues/detail?id=476) * updated video status changer plugin (v0.1.7): added support for determining fullscreen mode for MS Windows, http://psi-plus.com/wiki/plugins#video_status_changer_plugin * updated extended options plugin to v0.3.6 * updated gmail service plugin to v0.7.2 ! win32 installer: added video status changer plugin ! updated russian localization by ivan101, https://github.com/ivan101/psi-plus-ru (2011-12-25) 2011-11-13 zet v0.15.5145 ! [коммит в upstream] исправлена работа при выборе способа передачи файлов (по приоритету) * некоторые исправления в контроллере MPRIS (psi-fix-tunes.diff) + добавлен детект клиента qip 2012 (psi-client-icons.diff) + добавлен новый патч на автостарт pgp-соединений. теперь будет автоматически стартовать в том случае, если контакту присвоен pgp-ключ. т.е. будет автостарт даже для оффлайн-контактов. используйте advanced-опцию "options.pgp.auto-start" для включения данной функции. также должна быть включена опция автовыбора ресурса при переписке с pgp-контактом (psi-autostart-pgp.diff) * ещё один фикс в контроллере MPRIS (psi-fix-tunes.diff) + добавлен новый патч для исправления механизма работы передачи файлов, http://code.google.com/p/psi-dev/issues/detail?id=464 (psi-fix-filetransfer.diff) * некоторые исправления в патче psi-autostart-pgp.diff * фикс патча psi-fix-filetransfer.diff - убраны некоторые патчи -> приняты в upstream: psi-selectable-application-version-in-aboutdlg.diff psi-fix-online-contacts-count.diff psi-disable-rename-of-general-group.diff psi-reset-not-complete-connection-on-status-change.diff psi-reset-selection-in-muc-roster.diff psi-sort-by-jid-at-discodlg.diff psi-fix-privates-group-in-roster.diff * обновлён video status plugin (v0.1.1): новый механизм работы с видеоплеерами (только для *nix) * обновлён captcha forms plugin до версии 0.1.0 * обновлён yandex narod plugin до версии 0.1.0, http://psi-plus.com/wiki/plugins#yandex_narod_plugin * обновлён screenshot plugin до версии 0.6.2 -- ! [upstream commit] fixed choose filetransfer transport by priority * some fixes in MPRIS controller (psi-fix-tunes.diff) + added detection of qip 2012 client (psi-client-icons.diff) + added new patch for autostart pgp. use 'options.pgp.auto-start' for enable this feature. also you should start chat for some contact's resource or enable autojid option (psi-autostart-pgp.diff) * one more fix in MPRIS controller (psi-fix-tunes.diff) + added new patch for fix filetransfer, http://code.google.com/p/psi-dev/issues/detail?id=464 (psi-fix-filetransfer.diff) * some fixes for psi-autostart-pgp.diff patch * fixed psi-fix-filetransfer.diff patch - removed some patches -> went to upstream (psi-selectable-application-version-in-aboutdlg.diff, psi-fix-online-contacts-count.diff, psi-disable-rename-of-general-group.diff) - removed more patches -> went to upstream (psi-reset-not-complete-connection-on-status-change.diff, psi-reset-selection-in-muc-roster.diff, psi-sort-by-jid-at-discodlg.diff) * removed psi-fix-privates-group-in-roster.diff patch -> went to upstream * updated video status plugin (v0.1.1): new mechanism of work with video players (*nix only) * updated captcha forms plugin to v0.1.0 * updated yandex narod plugin to v0.1.0, http://psi-plus.com/wiki/plugins#yandex_narod_plugin * updated screenshot plugin to v0.6.2 2011-10-29 zet v0.15.5130 ! [коммит в upstream] исправлен возможное падение приложения в диалоге истории переписки ! [коммит в upstream] исправлено поведение при перезаходе в защищённую паролем конференцию после реконнекта ( http://code.google.com/p/psi-dev/issues/detail?id=461 ) ! [коммит в upstream] исправлена некорректная работа меню настроек цвета (решена задача http://code.google.com/p/psi-dev/issues/detail?id=436 ) + добавлен новый патч для запрета на переименование основной группы ростера General (psi-disable-rename-of-general-group.diff) * исправлены некоторые патчи после комитта в upstream (psi-muc-minimize-to-roster.diff, psi-hide-muc-auto-join.diff) * некоторые улучшения в патче psi-always-visible-contacts.diff. теперь если контакту поставили метку "всегда виден", а потом этот контакт удалили, то метка "всегда виден" будет удаляться из опций * улучшения в патче psi-icon-actions-shortcuts.diff (psi-pep-toolbar-buttons.diff, psi-presets-in-status-menu.diff) * наведение порядка с горячими клавишами в приложении, http://code.google.com/p/psi-dev/issues/detail?id=437 (psi-typeahead-find.diff, psi-pep-toolbar-buttons.diff, psi-presets-in-status-menu.diff, psi-active-contacts-tool-button.diff) * наведение порядка с горячими клавишами, часть 2 (psi-icon-actions-shortcuts.diff, psi-presets-in-status-menu.diff, psi-active-contacts-tool-button.diff) * обновлён client switcher plugin: скроллирование в диалоге настроек плагина (v0.0.11) + добавлен новый плагин -- Yandex Narod Plugin (портирован из qutIm plugin), подробнее -- https://raw.github.com/psi-plus/plugins/master/generic/yandexnarodplugin/changelog.txt (v0.0.9) * обновлён history keeper plugin до версии 0.0.6 * обновлён watcher plugin до версии 0.4.0 * обновлён cleaner plugin до версии 0.3.1 (не забывайте делать резервные копии файлов профиля перед использованием чистильщика) -- ! [upstream commit] fixed possible crash in historydlg ! [upstream commit] fixed rejoining to the password protected groupchat when reconnecting ( http://code.google.com/p/psi-dev/issues/detail?id=461 ) ! [upstream commit] fixed color button in opt_appearance ( http://code.google.com/p/psi-dev/issues/detail?id=436 ) ! [upstream commit] fixed QLabel CVE ! [upstream commit] fixed ui tab-stop compile warning + added new patch for disable renaming of general group (psi-disable-rename-of-general-group.diff) * fixed some patches after upstream commit (psi-muc-minimize-to-roster.diff, psi-hide-muc-auto-join.diff) * some improvements for psi-always-visible-contacts.diff patch * improved psi-icon-actions-shortcuts.diff patch (psi-pep-toolbar-buttons.diff, psi-presets-in-status-menu.diff) * improved some patches (add comments for shortcuts), http://code.google.com/p/psi-dev/issues/detail?id=437 [psi-typeahead-find.diff, psi-pep-toolbar-buttons.diff, psi-presets-in-status-menu.diff, psi-active-contacts-tool-button.diff] * improved some patches (add comments for shortcuts), part 2 [psi-icon-actions-shortcuts.diff, psi-presets-in-status-menu.diff, psi-active-contacts-tool-button.diff] * updated client switcher plugin: added scrolling area (v0.0.11) + added new Psi+ Plugin -- Yandex Narod Plugin (ported from qutIm plugin), see at https://raw.github.com/psi-plus/plugins/master/generic/yandexnarodplugin/changelog.txt (v0.0.9) * get rid of QTextCodec::setCodecForLocale() in plugins [birthdayreminderplugin, chessplugin, cleanerplugin, clientswitcherplugin, conferenceloggerplugin, extendedoptionsplugin, gomokugameplugin, skinsplugin, stopspamplugin] * updated history keeper plugin to v0.0.6 * updated watcher plugin to v0.4.0 * updated cleaner plugin to v0.3.1 (make backup of profile files before use the cleaner) 2011-10-02 zet v0.15.5122 * исправлено поведение патча psi-one-chat-for-many-resources.diff при наличии offline ресурса в списке: исправлено отображение статуса при переключении, удалено дублирование иконки клиента одного из активных ресурсов на неактивный (psi-one-chat-for-many-resources.diff) * исправлено исчезнование области уведомлений в ростере при использовании темы oxygen, http://api.kde.org/4.6-api/kdebase-workspace-apidocs/kstyles/html/classOxygen_1_1TransitionWidget.html theme (psi-fix-eventnotifier.diff) * некоторые исправления в патче на сохранение геометрии окон приложения (psi-save-windows-size.diff, psi-xmpp-uri-in-chat-mode.diff, psi-customize-dialogs-title-hint.diff, psi-muc-minimize-to-roster.diff, psiplus-decorate-windows.diff, psi-always-visible-contacts.diff) - убран патч psi-save-windows-size.diff -> принят в upstream ! обновлены библиотеки шифрования win32 openssl до версии 1.0.0e, подробнее -- http://openssl.org/news/changelog.html ! обновлена русская локализация от ivan101, https://github.com/ivan101/psi-plus-ru (2011-09-24) -- * fixed psi-one-chat-for-many-resources patch for offline resources (psi-one-chat-for-many-resources.diff) * fixed hiding event notifier for the oxygen theme (psi-fix-eventnotifier.diff) * some fixes for save-sizes patch (psi-save-windows-size.diff, psi-xmpp-uri-in-chat-mode.diff, psi-customize-dialogs-title-hint.diff, psi-muc-minimize-to-roster.diff, psiplus-decorate-windows.diff, psi-always-visible-contacts.diff) - removed psi-save-windows-size.diff patch -> went to upstream ! updated win32 openssl libs to v1.0.0e, http://openssl.org/news/changelog.html ! updated russian localization by ivan101, https://github.com/ivan101/psi-plus-ru (2011-09-24) 2011-09-11 zet v0.15.5116 - убран патч psi-proxy-settings-in-opt.diff -> принят в upstream * улучшения в патче на сохранение геометрии окон с чатами/табами приложения (psi-muc-minimize-to-roster.diff, psi-save-windows-size.diff) + запоминание размеров окна с историей переписки (psi-save-windows-size.diff) * фиксы в патче psi-one-chat-for-many-resources.diff -- исправлен показ пустого поля джида в привате конференции при открытии окна чата с сообщением в тот момент, когда отправитель уже вышел из конференции * исправлен патч для работы с менеджером прокси-сервера после коммитов в upstream (psi-extend-plugins-interface.diff) * подготовка патча на запоминание размеров окон с чатами/табами для отправки в upstream (psi-save-windows-size.diff) * исправлен патч psi-muc-roster-size-and-location.diff * исправлен патч psi-webkit.diff * исправлен патч на поведение окон с чатами/табами после предыдущего коммита (psiplus-decorate-windows.diff) ! обновлены системные библиотеки Qt до версии 4.7.4, подробнее -- http://qt.gitorious.org/+qt-developers/qt/releases/blobs/v4.7.4/dist/changes-4.7.4 -- - removed psi-proxy-settings-in-opt.diff patch -> went to upstream * improved windows size saving (psi-muc-minimize-to-roster.diff, psi-save-windows-size.diff) + saved historydlg size in the same way as used for other widgets (psi-save-windows-size.diff) * small fix for psi-one-chat-for-many-resources.diff patch * fixed proxy manager after upstream commits (psi-extend-plugins-interface.diff) * prepare save-sizes patch for moving to upstream (psi-save-windows-size.diff) * fixed psi-muc-roster-size-and-location.diff patch * fixed psi-webkit.diff patch * fixed works with tabdlg after previous commit (psiplus-decorate-windows.diff) ! updated Qt libs to v4.7.4, http://qt.gitorious.org/+qt-developers/qt/releases/blobs/v4.7.4/dist/changes-4.7.4 2011-08-28 zet v0.15.5106 * изменена ссылка на файл version.txt для проверки наличия новых версий Psi+ (psi-dirty-check.diff) + добавлен новый патч для исправления поведения области событий в ростере -- теперь можно скрывать тулбар, который содержит область уведомления, если кроме этой самой области нет других кнопок (psi-fix-eventnotifier.diff) * исправлено запоминание позиции ширины основного ростера и ростера конференции для режима "всё-в-одном-окне" (psi-all-in-one-window.diff, psi-improve-tray-tooltip.diff, psi-add-view-groups-to-tray-menu.diff, psiplus-more-compatibility-with-skinsplugin.diff, psi-pep-toolbar-buttons.diff, psi-roster-autohide.diff, psi-presets-in-status-menu.diff, psiplus-decorate-windows.diff, psi-one-chat-for-many-resources.diff, psi-active-contacts-tool-button.diff) * улучшения в патче psi-muc-roster-size-and-location.diff * исправлен патч psi-all-in-one-window.diff после предыдущего коммита, также исправлены некоторые другие патчи (psi-muc-roster-size-and-location.diff, psi-improve-tray-tooltip.diff, psi-minimize-chat-hotkey.diff, psiplus-decorate-windows.diff, psi-vert-splitter-position.diff, psi-modern-muc-roster.diff) * исправлено поведение при закрытии активного таба для режима "всё-в-одном-окне" (ранее по хоткею "Close the current window/tab" не закрывался таб, если он остался один) [psi-all-in-one-window.diff, psi-muc-minimize-to-roster.diff] * небольшой фикс в патче psi-hide-muc-auto-join.diff * небольшой фикс в патче psi-muc-roster-size-and-location.diff + добавлен новый патч для решения задачи 456, http://code.google.com/p/psi-dev/issues/detail?id=456 (psi-selectable-application-version-in-aboutdlg.diff) * переписан патч на правильный подсчёт онлайн-контактов в ростере, http://code.google.com/p/psi-dev/issues/detail?id=455 (psi-fix-online-contacts-count.diff) * обновлён extended options plugin до версии 0.3.5 -- * changed urls for update checker (psi-dirty-check.diff) + added new patch for fix event notifier (psi-fix-eventnotifier.diff) * fixed splitter size saving and position when all-in-one (psi-all-in-one-window.diff, psi-improve-tray-tooltip.diff, psi-add-view-groups-to-tray-menu.diff, psiplus-more-compatibility-with-skinsplugin.diff, psi-pep-toolbar-buttons.diff, psi-roster-autohide.diff, psi-presets-in-status-menu.diff, psiplus-decorate-windows.diff, psi-one-chat-for-many-resources.diff, psi-active-contacts-tool-button.diff) * improved psi-muc-roster-size-and-location.diff patch * fixed psi-all-in-one-window.diff patch after last commit, fixed some patches (psi-muc-roster-size-and-location.diff, psi-improve-tray-tooltip.diff, psi-minimize-chat-hotkey.diff, psiplus-decorate-windows.diff, psi-vert-splitter-position.diff, psi-modern-muc-roster.diff) * fixed close action for current tab when all-in-one (psi-all-in-one-window.diff, psi-muc-minimize-to-roster.diff) * small fix for psi-hide-muc-auto-join.diff patch * small fix for psi-muc-roster-size-and-location.diff patch + added new patch for fix issue 456, http://code.google.com/p/psi-dev/issues/detail?id=456 (psi-selectable-application-version-in-aboutdlg.diff) * fixed online-contacts-count patch and move in to work, http://code.google.com/p/psi-dev/issues/detail?id=455 (psi-fix-online-contacts-count.diff) * updated extended options plugin to v0.3.5 2011-08-21 zet v0.15.5091 + добавлен новый патч, добавляющий пункт "Выполнить команду" в контекстное меню на контакте в ростере конференции (psi-execute-command-in-muc-roster-menu.diff) + добавлен новый патч, добавляющий кнопку "Активные контакты" на панель кнопок ростера приложения. активные контакты -- это контакты, с которыми ведётся переписка в открытых или свёрнутых табах/чатах (при условии, что при сворачивании история текущего чата не уничтожается) [psi-active-contacts-tool-button.diff] + добавлен новый патч для отображения в чате специальной иконки при включенном pgp/gpg-шифровании (psi-pgp-chat-icons.diff) * теперь в списке активных контактов отображаются ники контактов (вместо jid) [psi-active-contacts-tool-button.diff] * исправлено отображение в чатах иконок уведомлений о доставке сообщений при включенном pgp/gpg-шифровании (psi-receipts.diff, psi-pgp-chat-icons.diff) * сохранение размеров окон приложения: полностью переписан патч psi-save-windows-size.diff * сохранение размеров окон приложения: убрана ненужная опция "remember-window-sizes"; некоторые улучшения и исправление в коде патча (psi-save-windows-size.diff) * улучшения в патче на активные контакты: добавлена соответствующая кнопка на панель инструментов в чатах; статусная иконка в списке активных контактов отображает текущий статус каждого из активных контактов (psi-active-contacts-tool-button.diff) + добавлен новый патч для решения задачи 450, http://code.google.com/p/psi-dev/issues/detail?id=450 (psi-fix-groupchat-options.diff) * исправления в патче psi-one-chat-for-many-resources.diff: * теперь иконки отображаются и для контактов-транспортов * теперь корректно отображаются статусные сообщения в чатах * если имеется только один jid в списке и нет иконки клиента (например, у неопределившегося клиента в привате конференции или если открыт чат с клиентом из оффлайна), то пустого места вместо иконки теперь не будет * если отключён autojid для чата, то сообщения о переключении ресурса не отображаются (psi-one-chat-for-many-resources.diff, psi-active-contacts-tool-button.diff) -- + added new patch for adds ability execute command from popup menu on contact in muc roster (psi-execute-command-in-muc-roster-menu.diff) + added new patch for adds active contacts tool button in roster toolbar (psi-active-contacts-tool-button.diff) + added new patch for adds pgp/gpg notifications chat icons (psi-pgp-chat-icons.diff) * show nicks in active contacts menu (psi-active-contacts-tool-button.diff) * fixed receipts when pgp/gpg is enabled (psi-receipts.diff, psi-pgp-chat-icons.diff) * fixed saving windows sizes: rewritten psi-save-windows-size.diff patch * fixed saving windows sizes: removed useless stupid 'remember-window-sizes' option; some improvements and code cleanup (psi-save-windows-size.diff) * improved active-contacts patch: added action to chat toolbar; icons in popup menu now shows of current contacts status (psi-active-contacts-tool-button.diff) + added new patch for fix issue 450, http://code.google.com/p/psi-dev/issues/detail?id=450 (psi-fix-groupchat-options.diff) * improved one-chat-for-many-resources patch: fixed display of icons clients show status messages and text on switching (psi-one-chat-for-many-resources.diff, psi-active-contacts-tool-button.diff) 2011-08-07 zet v0.15.5074 + добавлен новый патч для отображения причин изменения ролей/рангов в списках участников конференции (настройки комнаты) [psi-reasons-in-muc-config-dlg.diff] * обновлён автоджид-патч: добавлены более строгие правила для переключения между ресурсами при исходящих сообщениях, исправлено отображение джида в приватных чатах конференции (psi-one-chat-for-many-resources.diff) + добавлен новый патч для отображения аватарки собеседника в чатах с контактами ростера (psi-chat-avatar.diff) + добавлен новый патч с фиксами для информационных полей в vCard (fix-contact-info-dialog.diff) - убран патч fix-contact-info-dialog.diff -> патч принят в upstream + добавлен новый патч для решения задачи 446, http://code.google.com/p/psi-dev/issues/detail?id=446 (psi-fix-editing-roster-element.diff) - убран патч psi-fix-editing-roster-element.diff -> патч принят в upstream * оптимизация кода в патче psi-chat-avatar.diff * исправлено отображение аватарок во всплывающих подсказках на контактах конференции, имеющих спецсимволы в никах (psi-modern-muc-roster.diff) * решена задача 445, http://code.google.com/p/psi-dev/issues/detail?id=445 (psi-muc-minimize-to-roster.diff) + добавлен новый патч, добавляющий возможность переименования списка приватности в редакторе списков приватности (psi-rename-privacy-list.diff) * обновлён extended options plugin до версии 0.3.4 * обновлён client switcher plugin до версии 0.0.10 * фиксы для translate plugin (v0.4.0) -- + added new patch for show reasons in muc config dialog (psi-reasons-in-muc-config-dlg.diff) * changed autojid patch: added stricter rules on switching output messages, fixed display jid in private conference (psi-one-chat-for-many-resources.diff) + added new patch for show avatar in chatdlg (psi-chat-avatar.diff) + added new patch for several fixes to the infodlg (fix-contact-info-dialog.diff) - removed fix-contact-info-dialog.diff patch -> went to upstream + added new patch for fix error described in the issue 446, http://code.google.com/p/psi-dev/issues/detail?id=446 (psi-fix-editing-roster-element.diff) - removed psi-fix-editing-roster-element.diff patch -> went to upstream * code optimization for psi-chat-avatar.diff patch * fixed showing avatars in tooltips for muc contacts with special symbols in the nick (psi-modern-muc-roster.diff) * fixed issue 445, http://code.google.com/p/psi-dev/issues/detail?id=445 (psi-muc-minimize-to-roster.diff) + added new patch for adds ability for rename privacy lists (psi-rename-privacy-list.diff) * updated extended options plugin to v0.3.4 * updated client switcher plugin to v0.0.10 * fixes for translate plugin (v0.4.0) 2011-07-17 zet v0.15.5062 + добавлен новый патч, добавляющий команду Unregister в меню на транспорте в service discovery (psi-action-unregister-in-discodlg.diff) + добавлен новый патч, позволяющий сортировать по алфавиту элементы в столбце jid в service discovery (psi-sort-by-jid-at-discodlg.diff) * обновлён патч psi-entity-time.diff: добавлена регистрация entity time в капсах + добавлен новый патч для поддержки XEP-0012: Last Activity. используйте advanced-опцию "options.service-discovery.last-activity" для включения/отключения данной функции. требуется рестарт приложения (psi-idle-server.diff) * фиксы сдвигов в некоторых патчах (psi-muc-topic-context-menu.diff, psi-idle-server.diff, psi-extend-plugins-interface.diff) * исправлен поиск и обнаружение тем в домашнем каталоге (psiplus-binary.diff) + добавлен новый патч для корректной отрисовки окна редактора правила в списках приватности при изменении размеров окна по вертикали и горизонтали (psi-privacy-rule-editor-dialog.diff) - убран патч psi-privacy-rule-editor-dialog.diff -> принят в upstream * обновлён gmail service plugin до версии 0.7.1 * обновлён client switcher plugin до версии 0.0.9 -- изменён цвет подсвеченных слов при поиске * обновлён conference logger plugin до версии 0.2.0 -- изменён цвет подсвеченных слов при поиске * обновлён gomoku game plugin до версии 0.1.0 -- + added new patch to adds 'unregister' command in discodlg (psi-action-unregister-in-discodlg.diff) + added new patch to sort by jid in discodlg (psi-sort-by-jid-at-discodlg.diff) * updated psi-entity-time.diff patch: added entity time caps registration + added new patch to support of XEP-0012: Last Activity. use option 'options.service-discovery.last-activity' for enabling or disabling. needs application restart (psi-idle-server.diff) * fix fuzzes for some patches (psi-muc-topic-context-menu.diff, psi-idle-server.diff, psi-extend-plugins-interface.diff) * fixed themes search at home folder (psiplus-binary.diff) + added new patch to correct privacy rule editor dialog resizing (psi-privacy-rule-editor-dialog.diff) - removed psi-privacy-rule-editor-dialog.diff patch -> went to upstream * updated gmail service plugin to v0.7.1 * updated client switcher plugin to v0.0.9 -- changed highlighting color of the found text * updated conference logger plugin to v0.2.0 -- changed highlighting color of the found text * updated gomoku game plugin to v0.1.0 2011-07-10 zet v0.15.5050 * поправлен патч psi-fix-entity-time-in-vcard-for-muc-users.diff и объединён с патчем psi-entity-time.diff (deleted psi-fix-entity-time-in-vcard-for-muc-users.diff, modified psi-entity-time.diff) * исправлена ошибка с неудаляемым muc-контактом из основного ростера приложения после запроса vCard muc-контакта (psiplus-decorate-windows.diff, psi-hide-muc-auto-join.diff) + добавлен новый патч, автоматически добавляющий символ "_" к нику если выбранный ник уже занят на сервере для режима автовхода в конференции-закладки (psi-bookmark-nick-auto-comlition.diff) + добавлен новый патч для реализации импорта/экспорта списка конференций-закладок (psi-import-export-bookmarks.diff) + добавлен новый патч, исправляющий поведение всплывающих подсказок при поиске в ростере (http://code.google.com/p/psi-dev/issues/detail?id=432) [psi-fix-tooltip-when-searching-in-roster.diff] * фиксы в патче psi-muc-minimize-to-roster.diff: в некоторых ситуациях не работали уведомления для спрятанных табов (psi-muc-minimize-to-roster.diff) * разделён и модифицирован патч psi-action-line-edit-context-menu.diff (добавлен psi-action-line-edit-context-menu.diff, удалён psi-action-line-edit-context-menu.diff, добавлен psi-muc-topic-context-menu.diff) + добавлен новый патч, улучшающий работу с мини-командами в конференции. добавлены команды kick, ban, topic, leave, invite. для kick и ban выполняется автодополнение по табу как для списка ников, так и для ризонов. для topic - автодополняется до текущего топика. повторное нажатие хоткея (Ctrl+Space) закрывает режим мини-команд (http://code.google.com/p/psi-dev/issues/detail?id=22) [psi-improve-mini-comands.diff] * оптимизация в патче psi-action-line-edit-context-menu.diff - убран патч psi-action-line-edit-context-menu.diff -> принят в upstream * обновлён screenshot plugin до версии 0.6.0 * обновлён gmail service plugin до версии 0.7.0 -- * fixed psi-fix-entity-time-in-vcard-for-muc-users.diff patch and merged with psi-entity-time.diff patch (deleted psi-fix-entity-time-in-vcard-for-muc-users.diff, modified psi-entity-time.diff) * fixed psi-hide-muc-auto-join.diff patch, fix fuzz (psiplus-decorate-windows.diff, psi-hide-muc-auto-join.diff) + added new patch to automatically append symbol '_' if nickname already used in groupchat when autojoin to bookmark (psi-bookmark-nick-auto-comlition.diff) + added new patch to adds ability import and export bookmarks (psi-import-export-bookmarks.diff) + added new patch to fix tooltip in contactlist when searching for someone (http://code.google.com/p/psi-dev/issues/detail?id=432) [psi-fix-tooltip-when-searching-in-roster.diff] * some fixes for psi-muc-minimize-to-roster.diff patch: alerting for hidden tabs was broken in some cases (psi-muc-minimize-to-roster.diff) * separated and modified psi-action-line-edit-context-menu.diff patch (added psi-action-line-edit-context-menu.diff, deleted psi-action-line-edit-context-menu.diff, added psi-muc-topic-context-menu.diff) + added new patch to improve mini commands system (http://code.google.com/p/psi-dev/issues/detail?id=22) [psi-improve-mini-comands.diff] * optimization for psi-action-line-edit-context-menu.diff patch - removed psi-action-line-edit-context-menu.diff patch -> went to upstream * updated screenshot plugin to v0.6.0 * updated gmail service plugin to v0.7.0 2011-07-03 zet v0.15.5031 * попытка решить задачу 424, http://code.google.com/p/psi-dev/issues/detail?id=424 (спасибо AvadoN) [psi-improve-adhoc-forms.diff] * imageplugin: добавлена возможность отправки картинок в общий чат конференции * исправлен патч psi-tab-status-icon.diff - баг со статусной иконкой в заголовке таба * улучшения в пачте psi-modern-muc-roster.diff - добавлено отображение аватаров во всплывающих подсказках на элементах ростера конференции + добавлен новый патч для возможности копирования ника участника конференции перетаскиванием в поле ввода (psi-fix-drag-contacts-in-muc-roster.diff) * улучшения в патче psi-modern-muc-roster.diff - добавлено отображение аватаров в приватных чатах конференции * изменён размер аватара по умолчанию (24 пкс) [psi-modern-muc-roster.diff] * обновлён client switcher plugin до версии 0.0.8 + добавлен новый патч для поддержки функции всегда видимых контактов (always visible contacts) в основном ростере приложения (psi-always-visible-contacts.diff) * решена задача 380, http://code.google.com/p/psi-dev/issues/detail?id=380 и задача 163, http://code.google.com/p/psi-dev/issues/detail?id=163 (psi-receipts.diff, psi-webkit.diff) * очистка кода от qt3-зависимостей (psi-muc-nickclick-chat.diff, psi-muc-minimize-to-roster.diff, psi-chatview-quote-feature.diff, psi-typed-history.diff, psi-send-button-context-menu.diff, psi-implements-iq-version-stuff-in-discodlg.diff, psi-typeahead-find.diff, psi-minimize-chat-hotkey.diff, psi-send-xhtml-im.diff) + добавлен новый патч для корректной работы виртуальных групп ростера с открытыми приватными чатами конференции (psi-fix-privates-group-in-roster.diff) * windows-инсталлятор: исправлено создание ярлыков для текущего пользователя, http://code.google.com/p/psi-dev/issues/detail?id=399#c7 (scripts/win32/psiplus-install.nsi) + добавлен новый патч, дающий возможность скрыть любую группу ростера (psi-hide-any-group.diff) + добавлен новый патч для более удобного выбора цветовых решений и раскраски различных элементов приложения в дополнительных настройках (Advanced Options) [psi-optionseditor-choose-color.diff] - убран патч psi-optionseditor-choose-color.diff -> принят в upstream + добавлен новый патч для показа иконки приложения на различных окнах при настройке приложения (psi-set-window-icons.diff) + добавлен новый патч для сокращения многострочных параметров в редакторе настроек приложения (psi-optionseditor-show-only-first-line.diff) - убран патч psi-less-warnings.diff -> принят в upstream * корректная прорисовка иконки приложения на отделённых окнах advanced-опций. также поправлено имя в заголовке окна (psi-set-window-icons.diff) + добавлен новый патч для решения задачи 155, http://code.google.com/p/psi-dev/issues/detail?id=155 (psi-custom-chat-window-caption.diff) + добавлен новый патч для надлежащего выделения элемента в ростере конференции (psi-mucroster-highlighted-text.diff) * патч psi-mucroster-highlighted-text.diff объединён с патчем psi-modern-muc-roster.diff + добавлен новый патч для возможности копирования jid'а конференции по нажатию правой кнопки мыши на поле с топиком конференции (psi-action-line-edit-context-menu.diff) * переписан патч psi-action-line-edit-context-menu.diff patch + добавлена возможность скрыть статусную иконку контакта в ростере конференции (psi-modern-muc-roster.diff) - убран патч psi-fix-contact-sorting-in-muc-roster.diff -> принят в upstream * фиксы в обработке tunes-событий (psi-fix-tunes.diff) * фиксы для PollingController (psi-fix-tunes.diff) * раздельная опция для настройки варианта сортировки контактов в ростере конференции (psi-modern-muc-roster.diff) + добавлен новый патч, добавляющий возможность указать причину при смене аффиляции и/или роли участника конференции. показ причины в общем чате конференции (psi-reasons-for-roles-affiliations.diff) + добавлен новый патч для поиска псимедии в любой директории с плагинами (psi-search-for-psimedia-in-all-plugins-dirs.diff) + добавлены иконки jabber-клиентов "smack-api" и "emess" (iconsets/clients/fingerprint-22.jisp, fingerprint.jisp) * добавлен детект новых jabber-клиентов (psi-client-icons.diff) * фикс отображения иконки окна с конференцией и иконки закладок в режиме без табов (psi-set-window-icons.diff) + добавлен новый патч, добавляющий фильтр при поиске в диалоге "Обзор сервисов" (discodlg) [psi-filter-in-discodlg.diff] + добавлен новый патч для предупреждения двойной посылки запроса disco#items при открытии диалога "Обзор сервисов" (psi-fix-sending-disco-items-request-twice.diff) * попытка фикса ошибки при работе с tune-фильтрами (psi-fix-tunes.diff) * теперь по умолчанию выключен автоматический запрос информации при открытии диалога "Обзор сервисов" (psi-default-application-settings.diff) + добавлен новый патч для фикса entity time при открытии vcard'ов участников конференции (psi-fix-entity-time-in-vcard-for-muc-users.diff) + добавлены иконки для поля со списком ресурсов контакта (iconsets/system/default/psiplus/autojid.png, autojid_en.png) * обновлён патч psi-one-chat-for-many-resources.diff ! обновлена русская локализация (psi-ru svn, r205, 2011-06-21) ! репозиторий проекта переехал из svn на git ( https://github.com/psi-plus ) -- * try to fix issue 424, http://code.google.com/p/psi-dev/issues/detail?id=424 (thanks to AvadoN) [psi-improve-adhoc-forms.diff] * imageplugin: added extremally useful muc button * fixed psi-tab-status-icon.diff patch - Qwest's bug with muc tab status icon * improved psi-modern-muc-roster.diff patch - added avatars in tooltips and at main roster + added new patch for fix drag contacts in muc roster (psi-fix-drag-contacts-in-muc-roster.diff) * improved psi-modern-muc-roster.diff patch - added avatars for private chats dialogs * changed muc avatar default size (set to 24) [psi-modern-muc-roster.diff] * updated client switcher plugin to v0.0.8 + added new patch for support the always visible contacts (psi-always-visible-contacts.diff) * fixed issue 380, http://code.google.com/p/psi-dev/issues/detail?id=380 and issue 163, http://code.google.com/p/psi-dev/issues/detail?id=163 (psi-receipts.diff, psi-webkit.diff) * more qt3 cleanup (psi-muc-nickclick-chat.diff, psi-muc-minimize-to-roster.diff, psi-chatview-quote-feature.diff, psi-typed-history.diff, psi-send-button-context-menu.diff, psi-implements-iq-version-stuff-in-discodlg.diff, psi-typeahead-find.diff, psi-minimize-chat-hotkey.diff, psi-send-xhtml-im.diff) + added new patch for fix roster group of private contacts (psi-fix-privates-group-in-roster.diff) * windows installer: fixed create shortcuts for all users, http://code.google.com/p/psi-dev/issues/detail?id=399#c7 (scripts/win32/psiplus-install.nsi) + added new patch for support of hiding any roster group (psi-hide-any-group.diff) + added new patch for provide a way for choosing color in Advanced Options (psi-optionseditor-choose-color.diff) - removed psi-optionseditor-choose-color.diff patch -> went to upstream + added new patch for showing options icon (psi-set-window-icons.diff) + added new patch for shorten multiline options in options editor (psi-optionseditor-show-only-first-line.diff) - removed psi-less-warnings.diff patch -> went to upstream * setup icons for many windows (psi-set-window-icons.diff) * set proper icon for detached Advanced. also corrected window name (psi-set-window-icons.diff) + added new patch for fix issue 155, http://code.google.com/p/psi-dev/issues/detail?id=155 (psi-custom-chat-window-caption.diff) * more windows in set-window-icons (thanks to Hugin) [psi-set-window-icons.diff] + added new patch for proper highlighting text in muc roster (psi-mucroster-highlighted-text.diff) * merged mucroster-highlighted-text patch with modern-muc-roster patch (psi-modern-muc-roster.diff) + added new patch for copying conference jid by right click on topic (psi-action-line-edit-context-menu.diff) * rewritten psi-action-line-edit-context-menu.diff patch + added ability to hide status icons in muc roster (psi-modern-muc-roster.diff) - removed psi-fix-contact-sorting-in-muc-roster.diff patch -> went to upstream * some fixes in tunes processing (psi-fix-tunes.diff) * PollingController code fixes (psi-fix-tunes.diff) * use separated option for contact sort style in muc roster (psi-modern-muc-roster.diff) + added new patch for adds ability to set reason for affiliations and roles changes; show reasons in chatlog (psi-reasons-for-roles-affiliations.diff) + added new patch for searching psimedia in all plugins dirs (psi-search-for-psimedia-in-all-plugins-dirs.diff) + added 'smack-api' and 'emess' client icons (iconsets/clients/fingerprint-22.jisp, fingerprint.jisp) * updated client icons patch (psi-client-icons.diff) * start-chat icon for MUC window in no tabs mode. star icon for bookmark MUC window (psi-set-window-icons.diff) + added new patch for adds filter by jid in discodlg (psi-filter-in-discodlg.diff) * availability of some fields to the connected resource (psi-temp-show-caps.diff) + added new patch for preventing disco#items request sends twice when open discodlg (psi-fix-sending-disco-items-request-twice.diff) * try to fix bug in work with tune-filters (psi-fix-tunes.diff) * disabled automatically get disco info by default (psi-default-application-settings.diff) + added new patch for fix entity time on vcards for muc users (psi-fix-entity-time-in-vcard-for-muc-users.diff) + added autojid icons (iconsets/system/default/psiplus/autojid.png, autojid_en.png) * updated psi-one-chat-for-many-resources.diff patch ! updated russian localization (psi-ru svn, r205, 2011-06-21) ! project repository has moved from svn to git ( https://github.com/psi-plus ) 2011-06-19 zet v0.15.4062 + добавлен детект некоторых jabber-клиентов: irssi-xmpp, weonlydo, kadu, ekg2, wtw (psi-client-icons.diff) * обновлены иконпаки с иконками клиентов (iconsets/clients/fingerprint-22.jisp, fingerprint.jisp) * фикс обработки css в редакторе шаблонов (psi-send-button-context-menu.diff) * psiplus-install.nsi: исправлена домашняя папка для ярлыков (scripts/win32/psiplus-install.nsi) * фиксы для патча psi-one-chat-for-many-resources.diff * исправлен шрифт в редакторе топика конференции (psi-muc-topic.diff) + добавлена возможность сортировки по статусу списка участников в ростере конференции (psi-fix-contact-sorting-in-muc-roster.diff) + добавлен новый патч для снижения количества предупреждений при компиляции Psi+ (psi-less-warnings.diff) * меньше предупредений при компиляции Psi+ (psi-proxy-settings-in-opt.diff, psi-data-forms-media-element.diff, psi-webkit.diff, psi-receipts.diff, psi-entity-time.diff, psi-xmpp-uri-in-chat-mode.diff, psi-adhoc-leave-muc.diff, psi-typeahead-find.diff, psi-modern-roster.diff, psi-contact-manager.diff, psi-presets-in-status-menu.diff, psi-add-contact-from-chat-dialog.diff, psi-one-chat-for-many-resources.diff, psiplus-binary.diff) - убраны лишние "tr" из POPUP_OPTION_NAME (attention plugin, birthday reminder plugin, extended menu plugin, gmail service plugin, pep change notify plugin, stop spam plugin, watcher plugin) * меньше предупреждений при компиляции Psi+ (psi-muc-nickclick-chat.diff, psi-typed-history.diff, psi-typeahead-find.diff, psi-muc-minimize-to-roster.diff, psi-tray-act-bring-to-front.diff, psi-send-xhtml-im.diff, psi-workaround-adhoc-form-closes.diff, psi-auto-capitalizer.diff) * переписан патч psi-change-password-dialog.diff * исправлен патч psi-fix-contact-sorting-in-muc-roster.diff * переписан патч psi-tab-highlight-color.diff * поправлен стиль кода в патче psi-muc-nickclick-chat.diff (psi-muc-roster-size-and-location.diff, psi-chatview-quote-feature.diff) * объединены некоторые патчи для ростера конференции в один общий патч. добавлена поддержка отображения аватарок в ростере конференции. настройки ростера конференции осуществляются группой advanced-опций "options.ui.muc.userlist..." (psi-modern-muc-roster.diff, psi-presets-in-status-menu.diff, psi-extend-plugins-interface.diff) * некоторые фиксы в патче psi-modern-roster.diff (psi-muc-minimize-to-roster.diff, psi-modern-muc-roster.diff) * домашний каталог Psi+: поправлен стиль кода в HomeDirMigration. корректное копирование директорий с кэшем в Mac OSX и Linux (psiplus-binary.diff) * libpsibuild: force --libdir param (scripts/posix/libpsibuild.sh) + добавлен новый патч на создание и использование специального хранилища для аватарок (psi-filecache-for-avatars.diff) * libpsibuild: added PLUGINS_PREFIXES var, which is space separated list of prefixes like generic unix dev. by default its generic and updated later according to platform (scripts/posix/libpsibuild.sh) * исправлен патч psi-filecache-for-avatars.diff * обновлён патч psi-one-chat-for-many-resources.diff: добавлена advanced-опция "options.ui.chat.default-jid-mode-ignorelist", предназначенная для перечисления исключений в виде JID-ов, к которым не будет применяться новый режим работы убрана функция Qt::NoFocus из поля jid в виджете * поправлен скрипт compileallplugins.py (scripts/posix/compileallplugins.py) * обновлён extended options plugin до версии 0.3.3 * content downloader plugin: поправлен стиль кода ! возвращена поддержка PsiMedia в версии для Mac OSX 10.6 (scripts/macosx/psibuild.command) * кнопка закладок в конференции: изменён метод activated -> triggered (psi-muc-bookmark-toolbar-button.diff) * фиксы в патче psi-modern-muc-roster.diff * небольшие изменения в редакторе геолокации (psi-geolocation.diff) * исправлен патч psi-fix-contact-sorting-style-option.diff + добавлен win32-скрипт make-psi-ru-lang-files.cmd (scripts/win32/make-psi-ru-lang-files.cmd) ! обновлена русская локализация (psi-ru svn, r201, 2011-06-18) -- + added detection of some clients: irssi-xmpp, weonlydo, kadu, ekg2, wtw (psi-client-icons.diff) * updated fingerprint iconsets (iconsets/clients/fingerprint-22.jisp, fingerprint.jisp) * fixed css in template's editor dialog (psi-send-button-context-menu.diff) * psiplus-install.nsi: corrected home directory for shortcuts (scripts/win32/psiplus-install.nsi) * small fix to one-chat-for-many-resources patch (psi-one-chat-for-many-resources.diff) * fixed font setting in groupchat topic dialog (psi-muc-topic.diff) + add ability for sorting contacts in muc roster by status (psi-fix-contact-sorting-in-muc-roster.diff) + added new patch for less warnings (psi-less-warnings.diff) * less warnings (psi-proxy-settings-in-opt.diff, psi-data-forms-media-element.diff, psi-webkit.diff, psi-receipts.diff, psi-entity-time.diff, psi-xmpp-uri-in-chat-mode.diff, psi-adhoc-leave-muc.diff, psi-typeahead-find.diff, psi-modern-roster.diff, psi-contact-manager.diff, psi-presets-in-status-menu.diff, psi-add-contact-from-chat-dialog.diff, psi-one-chat-for-many-resources.diff, psiplus-binary.diff) - dropped useless tr from POPUP_OPTION_NAME (attention plugin, birthday reminder plugin, extended menu plugin, gmail service plugin, pep change notify plugin, stop spam plugin, watcher plugin) * less warnings (psi-muc-nickclick-chat.diff, psi-typed-history.diff, psi-typeahead-find.diff, psi-muc-minimize-to-roster.diff, psi-tray-act-bring-to-front.diff, psi-send-xhtml-im.diff, psi-workaround-adhoc-form-closes.diff, psi-auto-capitalizer.diff) * rewritten psi-change-password-dialog.diff patch * fixed psi-fix-contact-sorting-in-muc-roster.diff patch * rewritten psi-tab-highlight-color.diff patch * fixed codestyle for psi-muc-nickclick-chat.diff patch (psi-muc-roster-size-and-location.diff, psi-chatview-quote-feature.diff) * merged some muc roster patches into one new patch. added avatars in muc roster. use options 'options.ui.muc.userlist...' for more muc-roster configure (psi-modern-muc-roster.diff, psi-presets-in-status-menu.diff, psi-extend-plugins-interface.diff) * some fixes for psi-modern-roster.diff patch (psi-muc-minimize-to-roster.diff, psi-modern-muc-roster.diff) * psiplus-binary patch. corrected codestyle of HomeDirMigration. copying cache for Mac OSX and Linux (psiplus-binary.diff) * libpsibuild: force --libdir param (scripts/posix/libpsibuild.sh) + added new patch for using filecache for cached avatars (psi-filecache-for-avatars.diff) * libpsibuild: added PLUGINS_PREFIXES var, which is space separated list of prefixes like generic unix dev. by default its generic and updated later according to platform (scripts/posix/libpsibuild.sh) * fixed psi-filecache-for-avatars.diff patch * updated psi-one-chat-for-many-resources patch: added 'options.ui.chat.default-jid-mode-ignorelist' option, removed Qt::NoFocus from jid address widget (psi-one-chat-for-many-resources.diff) * fixed compileallplugins.py (scripts/posix/compileallplugins.py) * updated extended options plugin to v0.3.3 * content downloader plugin: corrected codestyle ! return PsiMedia on Mac OSX 10.6 (scripts/macosx/psibuild.command) * bookmark button: activated -> triggered (psi-muc-bookmark-toolbar-button.diff) * fixed regression in psi-modern-muc-roster.diff patch * modified geolocation dialog (psi-geolocation.diff) * fixed psi-fix-contact-sorting-style-option.diff patch + added make-psi-ru-lang-files.cmd win32 script (scripts/win32/make-psi-ru-lang-files.cmd) ! updated russian localization (psi-ru svn, r201, 2011-06-18) 2011-06-12 zet v0.15.4018 * локализация мастера импорта профиля (psiplus-binary.diff) ! исправлен скрипт psibuild.sh (для *nix-систем). настоятельно рекомендуется обновить данный скрипт вручную из svn, http://psi-dev.googlecode.com/svn/trunk/scripts/posix/psibuild.sh - убран патч psi-muc-leave-status.diff -> принят в upstream + добавлена лицензионная информация для менеджера контактов (psi-contact-manager.diff) * домашний каталог Psi+: корректное удаление пустых каталогов в хранилище старого профиля после завершения процесса импорта данных (psiplus-binary.diff) + добавлен новый патч для избавления кода Psi (в части ростера конференции) от qt3-зависимостей (psi-remove-qt3-from-muc-roster.patch) - убран патч psi-remove-qt3-from-muc-roster.patch -> принят в upstream * обновлён патч psi-avcalls-addons.diff: setProperty -> setData - убран патч psi-insert-nick-on-shift-plus-leftclick.diff -> принят в upstream * очистка кода от qt3-зависимостей (psi-roster-disable-scrollbar.diff, psi-muc-roster-disable-horizontal-scrollbar.diff) - убран патч psi-historydlg.diff -> принят в upstream + добавлен новый патч для исправления отображения названий групп в ростере конференции (psi-fix-group-header-drawing-in-muc-roster.diff) - windows-инсталлятор: из состава дистрибутива убрана ненужная библиотека Qt3Support4.dll (trunk/scripts/win32/psiplus-install.nsi) + добавлен новый патч для сброса фокуса курсора с контакта в ростере конференции после клика на пустой области ростера (psi-reset-selection-in-muc-roster.diff) + добавлен новый патч, сдвигающий вправо название группы в ростере конференции (psi-move-group-text-in-muc-roster-to-right.diff) * исправлен патч psi-mac-makefile.diff и скрипт psibuild.command для Mac OSX (trunk/scripts/macosx/patches/3020-psi-mac-Makefile.diff, trunk/scripts/macosx/psibuild.command) + добавлен новый патч, который добавляет кнопку "Save" в редактор причин для кика/бана (psi-save-button-for-muc-kickban-reasons.diff) * некоторые исправления поведения ростера Psi+ при включенной опции "автоматически изменять высоту ростера" (psi-fix-roster-autoresize.diff) + добавлен новый патч, позволяющий вести чат с контактом в одном окне/табе независимо от имени ресурса. поведение управляется advanced-опцией "options.ui.chat.default-jid-mode", которая может принимать значения " barejid" и "auto". подробнее -- http://forum.psi-plus.com/viewtopic.php?f=8&t=40 (psi-one-chat-for-many-resources.diff) * решена задача 431, http://code.google.com/p/psi-dev/issues/detail?id=431 (psi-muc-minimize-to-roster.diff) + добавлен новый патч, исправляющий сортировку контактов в ростере конференции (psi-fix-contact-sorting-in-muc-roster.diff) * домашний каталог Psi+: фикс для HomedirMigration::exec (psiplus-binary.diff) * исправлена ссылка на форум Psi+ в меню Help -> Psi+ Forum (psiplus-join-to-support-muc.diff) * обновлён skins plugin до версии 0.3.2 * массированное обновление скина/темы LunnaCat (v1.1) * попытка детекта jabber-клииента LeechCraft (psi-client-icons.diff) * исправлен детект клиента SIP Communicator (Jitsi). спасибо Qwеst (psi-client-icons.diff) ! обновлена русская локализация от ivan101 (psi-ru svn, r198, 2011-06-10) -- * translate migration manager (psiplus-binary.diff) ! fixed psibuild.sh. its highly recommended to update this script manually (trunk/scripts/posix/psibuild.sh) - removed psi-muc-leave-status.diff patch -> went to upstream + added license to contact manager (psi-contact-manager.diff) * psiplus-binary patch. proper removing of empty folders (psiplus-binary.diff) + added new patch for get rid of qt3 dependence in muc roster (psi-remove-qt3-from-muc-roster.patch) - removed psi-remove-qt3-from-muc-roster.patch -> went to upstream * updated psi-avcalls-addons.diff patch: setProperty -> setData - removed psi-insert-nick-on-shift-plus-leftclick.diff patch -> went to upstream * more qt3 cleanup (psi-roster-disable-scrollbar.diff, psi-muc-roster-disable-horizontal-scrollbar.diff) - removed psi-historydlg.diff patch -> went to upstream + added new patch for fix of groups drawing in muc roster (psi-fix-group-header-drawing-in-muc-roster.diff) - windows installer: removed useless Qt3Support4.dll (trunk/scripts/win32/psiplus-install.nsi) + added new patch for reset selection in muc roster when clicking at free space (psi-reset-selection-in-muc-roster.diff) + added new patch for move groups text in muc roster to the right (psi-move-group-text-in-muc-roster-to-right.diff) * fixed psi-mac-makefile.diff patch and psibuild.command (trunk/scripts/macosx/patches/3020-psi-mac-Makefile.diff, trunk/scripts/macosx/psibuild.command) + added new patch for adds save button for muc kick and ban reasons (psi-save-button-for-muc-kickban-reasons.diff) * some fixes for roster autoresize feature (psi-fix-roster-autoresize.diff) + added new patch that allows to chat with contact in one window/tab regardless of resource name. behavior is controlled by advanced option 'options.ui.chat.default-jid-mode' which can take values 'barejid' and 'auto'. more information at http://forum.psi-plus.com/viewtopic.php?f=8&t=40 (psi-one-chat-for-many-resources.diff) * fixed issue 431, http://code.google.com/p/psi-dev/issues/detail?id=431 (psi-muc-minimize-to-roster.diff) + added new patch for fix contact sorting in muc roster (psi-fix-contact-sorting-in-muc-roster.diff) * psiplus-binary patch. fixed returning from HomedirMigration::exec (psiplus-binary.diff) * updated Psi+ Forum url (psiplus-join-to-support-muc.diff) * updated skins plugin to v0.3.2 * massive update of LunnaCat Skin/Theme (v1.1) * update for LeechCraft detection (psi-client-icons.diff) * SIP Communicator was renamed to Jitsi and we detect it. thanks to Qwеst (psi-client-icons.diff) ! updated russian localization by ivan101 (psi-ru svn, r198, 2011-06-10) 2011-06-05 zet v0.15.3969 ! переписан патч psiplus-binary.diff. теперь все данные профиля Psi+ (настройки, история переписки, логи, аватары и пр.) хранятся в домашнем каталоге пользователя (в Mac OSX для хранения настроек и данных используется каталог "~/Library/Application Support/Psi+", для хранения кэша используется каталог "~/Library/Caches/Psi+"; в Windows для хранения любых файлов используется папка "%appdata%\Psi+"). также была изменена системная переменная -- вместо "PSIDATADIR" теперь используется переменная "PSIPLUSDATADIR"). будьте бдительны при запуске и работе мастера импорта данных и внимательно читайте всплывающие подсказки мастера импорта [psiplus-binary.diff] * обновлён патч psiplus-nix-application-icon-and-name.diff. теперь используется иконка Psi+ вместо устаревшей иконки Psi Plus * инсталлятор Psi+: попытка решить проблему первого запуска Psi+ от имени пользователя без прав администратора в ОС Windows 7 (psiplus-install.nsi) [trunk/scripts/win32/psiplus-install.nsi] * wiki: добавлена информация о форуме Psi+, http://forum.psi-plus.com/ * обновлён video status plugin (актуально только для *nix-систем) + добавлена новая опция "options.muc.leave-status-message" для установки статусного сообщения при выходе из конференции (psi-presets-in-status-menu.diff) * обновлены некоторые плагины для совместимости с новым домашним каталогом Psi+ (juick plugin, stop spam plugin, cleaner plugin, skins plugin, client switcher plugin, content downloader plugin) * домашний каталог Psi+: возможность копирования и удаления скрытых файлов и папок (psiplus-binary.diff) * домашний каталог Psi+: создание папки для данных (если не существует) [psiplus-binary.diff] * домашний каталог Psi+: в Linux вместо каталога "~/.local/share/data" используется каталог "~/.local/share" (psiplus-binary.diff) * домашний каталог Psi+: фикс пути для установки плагинов (вместо каталога "~/.local/share/data/Psi+/plugins" используется каталог "~/.local/share/Psi+/plugins") [psiplus-binary.diff] * исправлено цитирование в диалоговых окнах juick plugin (psi-chatview-quote-feature.diff) + добавлена новая команда "Paste As Quotation" для контекстного меню в поле ввода сообщения (psi-chatview-quote-feature.diff, psi-auto-capitalizer.diff) * домашний каталог Psi+: небольшие исправления для всплывающих подсказок в мастере импорта (psiplus-binary.diff) * обновлён screenshot plugin до версии 0.5.9 (настройки авторизации на некоторых хостингах можно найти здесь: http://code.google.com/p/qscreenshot/wiki/Authorization) * домашний каталог Psi+: фикс отображения пути в диалоге при удалении профиля Psi+ в Windows (psiplus-binary.diff) * домашний каталог Psi+: копируются только те файлы, которые доступны для копирования и удаляются только те файлы, которые разрешено удалять (psiplus-binary.diff) * попытка исправить поведение ростера при включенной опции "автоматически изменять высоту ростера" в Windows (psi-fix-roster-autoresize.diff) * домашний каталог Psi+: корректная работа с каталогом данных в Mac OSX (psiplus-binary.diff) * gomoku game plugin: добавлена информация о лицензиях * gomoku game plugin: исправлена опечатка в меню (issue #429, http://code.google.com/p/psi-dev/issues/detail?id=429) ! в дистрибутив инсталлятора под Windows добавлена русская локализация от taurus (основана на локализации от ivan101) -- ! rewritten psiplus-binary.diff patch. at now all profile data of Psi+ (settings, history, logs, avatars, etc.) are stored in the user's home directory (on Mac OSX for store the settings and data using '~/Library/Application Support/Psi+' directory, for cache storage using '~/Library/Caches/Psi+' directory; on Windows for store of any files types using '%appdata%\Psi+' folder). also changed a system variable for Psi+. at now used 'PSIPLUSDATADIR' instead of 'PSIDATADIR'). be careful when you run the Import Wizard and carefully read the tooltips of the Import Wizard [psiplus-binary.diff]. * updated psiplus-nix-application-icon-and-name.diff patch. use Psi+ icon name instead ugly Psi Plus * psiplus-install.nsi: workaround for first start under win7 (trunk/scripts/win32/psiplus-install.nsi) * wiki: added information about Psi+ Forum, http://forum.psi-plus.com/ * updated video status plugin (*nix only). added status setting timer to plugin * updated psi-presets-in-status-menu.diff patch. added 'options.muc.leave-status-message' option * rewritten psiplus-binary patch. added new appHomeDir (psiplus-binary.diff) * updated juick plugin, stop spam plugin, cleaner plugin, skins plugin, client switcher plugin and content downloader plugin to compile with new appHomeDir * psiplus-binary patch. copy and remove hidden files and folders (psiplus-binary.diff) * psiplus-binary patch. preventing from all profile dirs (psiplus-binary.diff) * psiplus-binary patch. creating data dir if not exist (psiplus-binary.diff) * psiplus-binary patch. use ~/.local/share insted ~/.local/share/data on Linux (psiplus-binary.diff) * psiplus-binary. fixed plugins install path. ~/.local/share/data/Psi+/plugins => ~/.local/share/Psi+/plugins (psiplus-binary.diff) + added new psimedia patch (psimedia-drop-v4lsrc-gst-plugin.diff) * trying to fix quoting for juick plugin (psi-chatview-quote-feature.diff) + added new 'Paste As Quotation' option in context menu of ChatEdit (psi-chatview-quote-feature.diff, psi-auto-capitalizer.diff) * psiplus-binary patch. corrected tooltips (psiplus-binary.diff) * updated screenshot plugin to v0.5.9 (authorization settings for some hosting services can be found here: http://code.google.com/p/qscreenshot/wiki/Authorization) * psiplus-binary patch. fixed showing paths when delete profile on Windows (psiplus-binary.diff) * corrected FSF address for homedirmigration (psiplus-binary.diff) * psiplus-binary patch. fixed copying and removing of hidden files and folders (psiplus-binary.diff) * psiplus-binary patch. copying only for readable file and removing only for writable files (psiplus-binary.diff) * fixed roster autoresize for OS Windows (psi-fix-roster-autoresize.diff) * psiplus-binary patch. corrected dataDir for Mac OSX (psiplus-binary.diff) * gomoku game plugin: added license block * gomoku game plugin: fix typing error in window menu (issue #429, http://code.google.com/p/psi-dev/issues/detail?id=429) ! Psi+ Windows Installer: added russian localization by taurus (based on localization by ivan101) 2011-05-22 zet v0.15.3910 + добавлен новый патч, исправляющий поведение анимации вновь подключившихся контактов в ростере (psi-fix-online-contacts-animation.diff) * исправлен screenshot plugin (v0.5.6) + добавлен новый патч, исправляющий работу авторесайза размера ростера по высоте (при включении соответствующей опции в настройках приложения) [psi-fix-roster-autoresize.diff] * обновлён storage notes plugin до версии 0.1.6 * обновлён патч psi-fix-tunes.diff. добавлена поддержка работы контроллера PsiFile с QCA::FileWatch * исправлена работа gomoku game plugin (v0.0.8) * обновлены регулярные выражения для правильного детекта иконок конференций/комнат (psi-add-standard-transports.diff) * исправлена утечка памяти в парсере сообщений (psi-receipts.diff) * некоторые исправления и фикс утечки памяти в патче psi-muc-minimize-to-roster.diff + добавлен новый патч, исправляющий поведение всплывающих окон (psi-fix-popups.diff) * обновлён патч psi-fix-tunes.diff. убран ненужный плагин xmms и мусор в файлах psifilecontroller + добавлен новый патч, исправляющий отображение иконки зашифрованного соединения в ростере (psi-fix-pgp-icon-in-roster.diff) * обновлён extended menu plugin до версии 0.1.0 * меньше предупреждений при при компиляции Psi+ (psi-fix-online-contacts-animation.diff) + добавлен новый патч для запоминания ключа pgp-шифрования при рестарте приложения. примечание: не для параноиков! :) [psi-remember-pgp-key.diff] * рефакторинг в патче psi-muc-minimize-to-roster.diff. возможна регрессия, требуется много тестов [psi-muc-minimize-to-roster.diff] * некоторые фиксы в патче psi-hide-muc-auto-join.diff * временный фикс в патче psi-save-maximized-window-state.diff + добавлен новый патч для отключения функции переключения между табами по комбинации "alt+n" (psi-disable-alt-n-switch-tabs.diff) ! запущен форум Psi+, http://forum.psi-plus.com/ (спасибо diSabler). добро пожаловать! :) -- + added new patch for fix animation of roster contacts (psi-fix-online-contacts-animation.diff) * fixed screenshot plugin (v0.5.6) + added new patch for fix roster autoresize (psi-fix-roster-autoresize.diff) * updated storage notes plugin to v0.1.6 * updated psi-fix-tunes.diff patch. added PsiFile controller work with QCA::FileWatch * fixed gomoku game plugin (v0.0.8) * updated regexp for groupchats (psi-add-standard-transports.diff) * fixed memory leak in message receipts (psi-receipts.diff) * some fixes for psi-muc-minimize-to-roster.diff patch + added new patch for fix popups (psi-fix-popups.diff) * updated psi-fix-tunes.diff patch. removed useless xmms plugin and psifilecontroller files + added new patch for fix pgp incrypted connection icon in roster (psi-fix-pgp-icon-in-roster.diff) * updated extended menu plugin to v0.1.0 * less warnings for psi-fix-online-contacts-animation.diff patch + added new patch for remember pgp key. not for paranoids :) [psi-remember-pgp-key.diff] * refactoring for psi-muc-minimize-to-roster.diff patch. regressions are possible, needed many tests :) [psi-muc-minimize-to-roster.diff] * some fixes for psi-hide-muc-auto-join.diff patch * fixed pgp icon in roster (psi-fix-pgp-icon-in-roster.diff) * temporary fix for psi-save-maximized-window-state.diff patch + added new patch for disable alt+n switching tabs (psi-disable-alt-n-switch-tabs.diff) ! launched Psi+ Forum, http://forum.psi-plus.com/ (thanks to diSabler). welcome! :) 2011-05-09 zet v0.15.3861 * исправлена работа gmail service plugin (v0.6.9) + добавлен новый tune-контроллер. теперь оба tune-контроллера (старый и новый) могут работать вместе. модуль "psifilecontroller" более не нужен (psi-fix-tunes.diff) * скрипт сборки psibuild теперь полностью совместим с FreeBSD (trunk/scripts/posix/libpsibuild.sh, psibuild.sh) + добавлен новый патч для возможности компиляции Psi+ в системах без поддержки sslv2 (psi-qca-no-ossl2.diff) - убрана метка "Beta" из полного номера версии Psi+ (psiplus-application-info.diff) - убран патч psi-qca-no-ossl2.diff -> принят в upstream + добавлен новый патч для поддержки новой advanced-опции "options.ui.chat.auto-scroll-to-bottom", которая позволяет отключить автоматическое скроллирование (автофокус) чатлога к самому свежему (нижнему) отправленному сообщению (psi-disable-auto-scroll-to-bottom.diff) * обновления безопасности в некоторых плагинах (attention plugin, autoreply plugin, captcha forms plugin, chess plugin, extended menu plugin, storage notes plugin) * обновлён screenshot plugin до версии 0.5.5 ! обновлена русская локализация от ivan101 (psi-ru svn, r195, 2011-05-08) -- * fixed gmail service plugin (v0.6.9) + added new psifiletunecontroller. at now works new and old controllers together. psifilecontroller module is no longer needed (psi-fix-tunes.diff) * psibuild is now fully compatible with FreeBSD (trunk/scripts/posix/libpsibuild.sh, psibuild.sh) + added new patch psi-qca-no-ossl2.diff (openssl 1.0 not support ssl2) - removed 'Beta' from version name (psiplus-application-info.diff) - removed psi-qca-no-ossl2.diff patch -> went to upstream + added new patch for adds option options.ui.chat.auto-scroll-to-bottom (psi-disable-auto-scroll-to-bottom.diff) * security updates for some plugins (attention plugin, autoreply plugin, captcha forms plugin, chess plugin, extended menu plugin, storage notes plugin) * updated screenshot plugin to v0.5.5 * psibuild is now fully compatible with FreeBSD (scripts/posix/libpsibuild.sh, psibuild.sh) ! updated russian localization by ivan101 (psi-ru svn, r195, 2011-05-08) 2011-04-21 zet v0.15.3800 Beta * обновлён gmail service plugin до версии 0.6.8 * обновления для шаблонов - добавлены подменю и разделители (psi-send-button-context-menu.diff) + добавлена новая иконка Print (Печать) в патч psi-iconsets.diff, теперь screenshot plugin использует данную иконку для команды вывода скриншота на печать + добавлен новый патч для деактивации вызова рисовальной доски из тулбара и из меню в приватах конференций (не работает в текущей реализации) [psi-remove-whiteboard-action-from-groupchat.diff] * исправлена обработка pep-событий (спасибо Hugin) [psi-fix-pep-retraction.diff] * поправлен патч psi-extend-plugins-interface.diff * исправлен chess plugin (v0.2.5) * исправлен патч psi-muc-notify-highlight.diff. теперь хайлайт в конференциях не будет работать для пользователей со статусом "dnd" ("занят") * обновлён gomoku game plugin до версии 0.0.7 * переписан и значительно расширен функционал screenshot plugin (v0.5.3) * решена задача 422, http://code.google.com/p/psi-dev/issues/detail?id=422 (psi-roster-avatar-frame.diff) -- * updated gmail service plugin to v0.6.8 * updated for templates - the submenu and separators are added (psi-send-button-context-menu.diff) + added print icon to psi-iconsets.diff patch, fix screenshotplugin for use this icon + added new patch for remove whiteboard action from toolbar and menu in groupchat cos it doesnt work (psi-remove-whiteboard-action-from-groupchat.diff) * fixed pep retraction by Hugin (psi-fix-pep-retraction.diff) * small fix for psi-extend-plugins-interface.diff patch * fixed chess plugin (v0.2.5) * fixed psi-muc-notify-highlight.diff patch. now highlights from mucs are disabled when status is dnd * updated gomoku game plugin to v0.0.7 * rewritten screenshot plugin (v0.5.3) * workaround to fix issue 422, http://code.google.com/p/psi-dev/issues/detail?id=422 (psi-roster-avatar-frame.diff) 2011-03-08 zet v0.15.3755 Beta + добавлен новый плагин - jabber disk plugin (v0.0.3), подробнее - http://psi-plus.com/wiki/plugins#jabber_disk_plugin * обновлён client switcher plugin до версии 0.0.6 * улучшен патч для рисовальной доски и перемещён в рабочий каталог с патчами. добавлен ключ "--enable-whiteboarding" для конфигурирования проекта перед сборкой, другие графические улучшения в интерфейсе (psi-wb.diff) - убран патч для рисовальной доски -> принят в upstream (psi-wb.diff) - убран патч для псимедии asterisk-hack-skip-connection-negotiation-check.diff - данный патч больше не нужен, т.к. он, по мнению автора патчей для псимедии Z_God, приводит к аварийной ситуации с падением приложения "ASSERT: "!iceA_status.channelsReady[index]" in file avcall/jinglertp.cpp, line 1368" в билдах PPA в Ubuntu (у Z_God не воспроизводится данная ошибка на его собственной сборке). процедура установления соединения может стать немного медленее, чем это было до этого * фикс устаревшего метода использования Jid::setResource в патче psi-add-bookmarks-to-join-conference-dlg.diff * фикс устаревшего метода использования Jid::userHost в патче psi-block-contact-from-menu.diff * фикс устаревшего метода использования Jid::setDomain в патче psi-muc-notify-highlight.diff + добавлен новый патч для очистки кода диалога передачи файлов от устаревших методов qt3 (psi-filetransdlg-to-qt4.diff) * объединены патчи psi-improve-filetransfer-dialog.diff и psi-filetransdlg-to-qt4.diff * небольшие исправления в патче psi-improve-filetransfer-dialog.diff * исправлена возможная утечка памяти в патче psi-extend-plugins-interface.diff - убран патч psi-improve-filetransfer-dialog.diff -> принят в upstream ! обновлены системные библиотеки Qt до версии 4.7.2, подробнее - http://qt.nokia.com/developer/changes/changes-4.7.2 ! обновлены библиотеки win32 openssl до версии 0.9.8r, подробнее - http://cvs.openssl.org/getfile?f=openssl/CHANGES&v=OpenSSL_0_9_8r -- + added new plugin - jabber disk plugin (v0.0.3), http://psi-plus.com/wiki/plugins#jabber_disk_plugin * updated client switcher plugin to v0.0.6 * improved whiteboarding patch and move it to work dir. added '--enable-whiteboarding' configure key, some ui fixes (psi-wb.diff) - removed whiteboarding patch -> went to upstream (psi-wb.diff) - removed asterisk-hack-skip-connection-negotiation-check.diff patch - this patch should not be needed anymore and i think it causes a crash with 'ASSERT: "!iceA_status.channelsReady[index]" in file avcall/jinglertp.cpp, line 1368' in the PPA build (i cannot reproduce it with my own build). connection negotiation may be a bit slower now though [commented by Z_God] * libpsibuild: enable whiteboarding by default (scripts/posix/libpsibuild.sh) * updated some win32 scripts - enable whiteboarding by default (scripts/win32/make-psiplus-webkit.cmd, scripts/win32/make-psiplus.cmd) * fixed deprecated usage of Jid::setResource in psi-add-bookmarks-to-join-conference-dlg.diff patch * fixed deprecated usage of Jid::userHost in psi-block-contact-from-menu.diff patch * fixed deprecated usage of Jid::setDomain in psi-muc-notify-highlight.diff patch + added new patch for cleaning qt3 from filetransdialog (psi-filetransdlg-to-qt4.diff) * merged psi-improve-filetransfer-dialog.diff and psi-filetransdlg-to-qt4.diff patches * small fix for psi-improve-filetransfer-dialog.diff patch * fixed possible memory leak (psi-extend-plugins-interface.diff) - removed psi-improve-filetransfer-dialog.diff patch -> went to upstream ! updated Qt libs to v4.7.2, http://qt.nokia.com/developer/changes/changes-4.7.2 ! updated win32 openssl libs to v0.9.8r, http://cvs.openssl.org/getfile?f=openssl/CHANGES&v=OpenSSL_0_9_8r 2011-02-27 zet v0.15.3730 Beta * инсталлятор Psi+: убраны неиспользуемые крипто-библиотеки (qca2.dll, qca-gnupg2.dll, qca-ossl2.dll) * исправлен патч psi-add-options-color-highliting.diff + добавлен новый патч - рабочая реализация IBB в приложении (psi-fix-ibb.diff) * убрана отладочная информация из патчей (psi-fix-ibb.diff, psi-extend-plugins-interface.diff) * обновлён патч на ibb: теперь клиент заявляет в disco о поддержке ibb. начата работа по исправлению работоспособности функции передачи файлов в конференциях (psi-fix-ibb.diff) + добавлен новый плагин - Gomoku game plugin (v0.0.4), подробнее - http://psi-plus.com/wiki/plugins#gomoku_game_plugin * обновлён chess plugin до версии 0.2.4 - убран патч psi-fix-ibb.diff -> принят в upstream + добавлен новый патч, добавляющий новую опцию "ibb only" в редактор настроек аккаунта (psi-ibb-only-option.diff) * обновлён gmail service plugin до версии 0.6.6 * исправлен порядок обработки входящих станз в менеджере плагинной системы (psi-extend-plugins-interface.diff) * исправлен stop spam plugin (v0.5.2) - убран патч psi-ibb-only-option.diff -> принят в upstream * 2011 год в меню Help->About (psiplus-aboutdlg.diff) -- * psiplus-install.nsi: removed useless crypto libs (qca2.dll, qca-gnupg2.dll, qca-ossl2.dll) * fixed psi-add-options-color-highliting.diff patch + added new patch - started IBB rewrite (psi-fix-ibb.diff) * removed debug info (1930-psi-fix-ibb.diff, psi-extend-plugins-interface.diff) * updated ibb patch: ibb added to disco. start fixing filetransfer in mucs (psi-fix-ibb.diff) + added new plugin - Gomoku game plugin (v0.0.4), http://psi-plus.com/wiki/plugins#gomoku_game_plugin * updated chess plugin to v0.2.4 - removed psi-fix-ibb.diff patch -> went to upstream + added new patch for adds 'ibb only' option in account manager (psi-ibb-only-option.diff) * updated gmail service plugin to v0.6.6 * fixed order of processing incoming stanzas by plugin manager (psi-extend-plugins-interface.diff) * fixed stop spam plugin (v0.5.2) - removed psi-ibb-only-option.diff patch -> went to upstream * updated copyright year in Help->About menu (psiplus-aboutdlg.diff) 2011-02-13 zet v0.15.3690 Beta * исправлены роли некоторых активных участников проекта (psiplus-aboutdlg.diff) + добавлен новый патч, создающий решение по исправлению падения приложения при переходе в оффлайн при наличии непрочитанных сообщений (psi-fix-crash-when-going-offline.diff) * обновлены некоторые плагины (attention plugin, chess plugin, pep change notify plugin) * обновлён watcher plugin до версии 0.3.9 * обновлён birthday reminder plugin до версии 0.3.3 * обновления в патче psi-popups-options-tab.diff * фиксы смещения в патчах psi-contact-manager.diff и psi-add-join-groupchat-item-to-account-menu.diff * обновлён gmail service plugin до версии 0.6.2 + добавлен файл звукового сопровождения при получении новых/непрочитанных писем в gmail service plugin (sound/email.wav) * обновлён extended menu plugin до версии 0.0.7 * исправлен патч psi-movable-tabs-qt45-and-custom-style.diff * улучшения в патче psi-xmpp-uri-in-chat-mode.diff и исправления в связанных с ним патчах (psi-tab-status-icon.diff, psi-movable-tabs-qt45-and-custom-style.diff, psi-muc-minimize-to-roster.diff, psi-presets-in-status-menu.diff, psiplus-decorate-windows.diff, psi-save-maximized-window-state.diff, psi-extend-plugins-interface.diff) * обновлён extended options plugin до версии 0.3.2 -- * fixed active project members list (psiplus-aboutdlg.diff) * small fix for psiplus-install.nsi + added new patch for сreate a workaround to fix crash when going to offline with some unread messages (psi-fix-crash-when-going-offline.diff) * updated some plugins (attention plugin, chess plugin, pep change notify plugin) * updated watcher plugin to v0.3.9 * updated birthday reminder plugin to v0.3.3 * updated psi-popups-options-tab.diff patch * fixed fuzzes (psi-contact-manager.diff, psi-add-join-groupchat-item-to-account-menu.diff) * update gmail service plugin to v0.6.2 + added sound for gmail service plugin (sound/email.wav) * updated extended menu plugin to v0.0.7 * fixed psi-movable-tabs-qt45-and-custom-style.diff patch * improved psi-xmpp-uri-in-chat-mode.diff patch and fix other patches after changes (psi-tab-status-icon.diff, psi-movable-tabs-qt45-and-custom-style.diff, psi-muc-minimize-to-roster.diff, psi-presets-in-status-menu.diff, psiplus-decorate-windows.diff, psi-save-maximized-window-state.diff, psi-extend-plugins-interface.diff) * updated extended options plugin to v0.3.2 2011-02-06 zet v0.15.3670 Beta * обновлён список активных участников проекта (psiplus-aboutdlg.diff) ! устранена утечка памяти в менеджере настройки прокси-сервера (psi-proxy-settings-in-opt.diff) + добавлен новый патч для сохранения памяти путём сокращения количества ненужных вызовов xml-консоли (psi-create-xmlconsole-when-need-it.diff) - убраны некоторые патчи -> приняты в upstream (less-includes.diff, psi-boss-key.diff) * обновлён chess plugin до версии 0.2.1 + добавлен новый патч для исправления поведения при отзыве mood-события (psi-fix-mood-retract.diff) * исправлена чрезмерная интенсивность отправки tune-событий в контроллере MPRIS (psi-fix-tunes.diff) + добавлена поддержка css в контекстном меню для url-объектов (psi-css-style-sheet.diff) + добавлена новая advanced-опция "options.ui.look.css" (psi-css-style-sheet.diff) + добавлен новый патч для устранения утечек памяти в variant tree (psi-fix-variant-tree-diff) - убран патч psi-fix-variant-tree-diff -> принят в upstream + добавлен новый патч, создающий новую отдельную вкладку в настройках приложения для регулировки параметров работы всплывающих окон (psi-popups-options-tab.diff) * улучшения в патче psi-popups-options-tab.diff: добавлен класс для контроля длительности показа всплывающих окон * расширение функциональности метода PopupAccessor (psi-extend-plugins-interface.diff) * обновлён stop spam plugin до версии 0.5.1 * патч на цитирование: теперь команда цитирования отключается при открытии нового чата (psi-chatview-quote-feature.diff) * фиксы в работе контроллера MPRIS (psi-fix-tunes.diff) * исправлен gmail service plugin (v0.6.1) * улучшения в патче psi-popups-options-tab.diff * обновлён attention plugin до версии 0.1.6 * обновлён интерфейс плагинной системы: добавлены методы appOsName(), appCurrentProfileDir() [psi-extend-plugins-interface.diff] * улучшения в патче на цитирование для невебкит-версии (psi-chatview-quote-feature.diff) * обновлён client switcher plugin до версии 0.0.5 * улучшения в патче psi-extend-plugins-interface.diff * улучшения в патче psi-popup-avatar.diff * улучшения в патче psi-popups-options-tab.diff * патч на цитирование: исправлено зависание приложения при клике по url-объекту (psi-chatview-quote-feature.diff) * улучшения в скрипте инсталлятора под win32: работа с ярлыками (psiplus-install.nsi) -- * updated project members list (psiplus-aboutdlg.diff) ! fixed memory leak in proxy manager (psi-proxy-settings-in-opt.diff) + added new patch for save some memory with useless xml-consoles (psi-create-xmlconsole-when-need-it.diff) - removed some patches -> went to upstream (less-includes.diff, psi-boss-key.diff) * updated chess plugin to v0.2.1 + added new patch for fix mood retract (psi-fix-mood-retract.diff) * fixed excessive tune sending in MPRIS controller (psi-fix-tunes.diff) + added css for url object context menu (psi-css-style-sheet.diff) + added empty option 'options.ui.look.css' (psi-css-style-sheet.diff) + added new patch for fix memory leaks in variant tree (psi-fix-variant-tree-diff) - removed psi-fix-variant-tree-diff patch -> went to upstream + added new patch for create popups options tab (psi-popups-options-tab.diff) * improved psi-popups-options-tab.diff patch: added class to control popups durations * updated PopupAccessor (psi-extend-plugins-interface.diff) * updated stop spam plugin to v0.5.1 * corrected quote feature. now quote menu is disabled after opening chatview (psi-chatview-quote-feature.diff) * fixed MPRIS controller (psi-fix-tunes.diff) * fixed gmail service plugin (v0.6.1) * improved psi-popups-options-tab.diff patch * updated attention plugin to v0.1.6 * updated plugins interface: added appOsName(), appCurrentProfileDir() [psi-extend-plugins-interface.diff] * improved quote patch for non-webkit version (psi-chatview-quote-feature.diff) * updated client switcher plugin to v0.0.5 * improved psi-extend-plugins-interface.diff patch * improved psi-popup-avatar.diff patch * improved psi-popups-options-tab.diff patch * quote patch: fixed hangup (psi-chatview-quote-feature.diff) * improved win32 install script: working with shortcuts (psiplus-install.nsi) 2011-01-30 zet v0.15.3630 Beta * исправлена обработка css для меню со смайлами. теперь изменения вступают в силу без перезапуска Psi+ (psi-css-style-sheet.diff) * improved Bits Of Binary (XEP-0231) support (psi-bits-of-binary.diff) + добавлена возможность скрывать и восстанавливать показ нескольких табов (psi-muc-minimize-to-roster.diff) - теперь удаляется неиспользуемый иконсет при выгрузке плагина (psi-extend-plugins-interface.diff) * улучшена работа методов active tab accessor (psi-extend-plugins-interface.diff) * небольшие улучшение и исправления для патча psi-extend-plugins-interface.diff * некоторые фиксы стиля кода и меньше предупреждений при компиляции Psi+ (psi-mood-icons-and-description.diff, psi-activity-icons-and-description.diff, psi-muc-roster-icons.diff) + добавлен новый патч на фиксы стиля кода и снижение количества предупреждений при компиляции Psi+ (psi-fix-opt-iconset-codestyle-and-warnings.diff) - убран патч psi-bits-of-binary.diff -> принят в upstream * улучшения в шаблонах сообщений: фикс обновления меню с шаблонами и команды paste&send. добавлены команды для редактирования и перемещения шаблонов в списке, возможность перетаскивания шаблонов по списку мышкой, а также запрос при выходе из редактора шаблонов без сохранения (psi-send-button-context-menu.diff) * патч psi-fix-opt-iconset-codestyle-and-warnings.diff объединён с патчем psi-client-icons.diff * меньше предупреждений при компиляции Psi+ (psi-send-button-context-menu.diff) * меньше предупреждений при компиляции Psi+ (psi-roster-avatar-frame.diff) * обновлён watcher plugin до версии 0.3.8 * некоторые фиксы стиля кода и меньше предупреждений при компиляции Psi+ (psi-popup-avatar.diff, psi-xmpp-uri-in-chat-mode.diff, psi-highlighted-color.diff, psi-tab-status-icon.diff, psi-typed-history.diff, psi-roster-settings.diff, psi-all-in-one-window.diff, psi-movable-tabs-qt45-and-custom-style.diff, psi-typeahead-find.diff, psi-improve-tray-tooltip.diff, psi-css-style-sheet.diff, psi-muc-minimize-to-roster.diff, psi-pep-toolbar-buttons.diff, psi-presets-in-status-menu.diff, psi-minimize-chat-hotkey.diff, psi-send-xhtml-im.diff, psi-add-contact-from-chat-dialog.diff, psi-typing-notify-popups.diff, psi-internal-changes-tabbablewidget.diff, psi-auto-capitalizer.diff, psi-extend-plugins-interface.diff) * исправлено возможное падение приложения в менеджере плагинов при удалении аккаунта (psi-extend-plugins-interface.diff) * попытка исправить падение приложения в менеджере плагинов при инициализации попапа (psi-extend-plugins-interface.diff) * обновлён pep change notify plugin до версии 0.0.7 + добавлен новый патч, исправляющий поведение PsiTooltipHandler (psi-fix-psitooltiphandler.diff) * решена задача 328, http://code.google.com/p/psi-dev/issues/detail?id=328 (psi-contact-manager.diff) + добавлен новый патч для быстрого скрытия окон и иконки приложения с рабочего стола, из трея и из панели задач по нажатию горячей кнопки (анти-босс режим). по умолчанию назначена комбинация Ctrl+Alt+B (psi-boss-key.diff) -- * fixed css for smiles menu. now there is no necessity of restarting Psi+ (psi-css-style-sheet.diff) * improved Bits Of Binary (XEP-0231) support (psi-bits-of-binary.diff) + added ability to hide and restore several tabs (psi-muc-minimize-to-roster.diff) - removed iconset when unload plugin (psi-extend-plugins-interface.diff) * improved active tab accessor methods (psi-extend-plugins-interface.diff) * small improvement, less wornings for psi-extend-plugins-interface.diff * some code style fixes (psi-mood-icons-and-description.diff, psi-activity-icons-and-description.diff, psi-muc-roster-icons.diff) + added new patch for fix codestyle and less warnings (psi-fix-opt-iconset-codestyle-and-warnings.diff) - removed patch psi-bits-of-binary.diff -> went to upstream * improved templates: fix update templates menu, paste&send action. added actions for editing, relocation of the context menu into edit dialog (psi-send-button-context-menu.diff) * merged psi-fix-opt-iconset-codestyle-and-warnings.diff and psi-client-icons.diff patches * less warnings for psi-send-button-context-menu.diff * less warnings for psi-roster-avatar-frame.diff * updated watcher plugin to v0.3.8 * some codestyle and warnings fixes (psi-popup-avatar.diff, psi-xmpp-uri-in-chat-mode.diff, psi-highlighted-color.diff, psi-tab-status-icon.diff, psi-typed-history.diff, psi-roster-settings.diff, psi-all-in-one-window.diff, psi-movable-tabs-qt45-and-custom-style.diff, psi-typeahead-find.diff, psi-improve-tray-tooltip.diff, psi-css-style-sheet.diff, psi-muc-minimize-to-roster.diff, psi-pep-toolbar-buttons.diff, psi-presets-in-status-menu.diff, psi-minimize-chat-hotkey.diff, psi-send-xhtml-im.diff, psi-add-contact-from-chat-dialog.diff, psi-typing-notify-popups.diff, psi-internal-changes-tabbablewidget.diff, psi-auto-capitalizer.diff, psi-extend-plugins-interface.diff) * fixed possible crash in plugin manager when psi account was deleted (psi-extend-plugins-interface.diff) * try to fix possible crash in plugin manager when init popup (psi-extend-plugins-interface.diff) * updated pep change notify plugin to v0.0.7 + added new patch for fix to PsiTooltipHandler (psi-fix-psitooltiphandler.diff) * fixed issue 328, http://code.google.com/p/psi-dev/issues/detail?id=328 (psi-contact-manager.diff) + added new patch that adds a boss key. ctrl+alt+b is default hotkey (psi-boss-key.diff) 2011-01-23 zet v0.15.3600 Beta * обновлена иконка ping.png для extended menu plugin * улучшения в патче на цветность фона тултипов/попапов (psi-coloring-tooltip.diff) * переписан патч psi-send-button-context-menu.diff (автор - liuch) + добавлен новый патч, выводящий новую кнопку Reset на панель управления advanced-опциями приложения (psi-options-reset-button.diff) * обновлён pep change notify plugin до версии 0.0.5 * добавлен новый патч, исправляющий утечки памяти в диалоге добавления нового пользователя (psi-fix-adduserdlg.diff) - убран патч psi-fix-adduserdlg.diff -> принят в upstream + добавлен новый патч, исправляющий утечки памяти в data_widget (psi-fix-xdata-widget.diff) - убран патч psi-fix-xdata-widget.diff -> принят в upstream * чистка мусора в патче psi-options-reset-button.diff + добавлен новый патч, исправляющий утечку памяти в edb (psi-fix-leak-in-edb.diff) * исправлены возможные утечки памяти в патче на новую историю переписки (psi-historydlg.diff) * изменён алгоритм работы с tunes-контроллерами. улучшена очистка тюнсов (psi-fix-tunes.diff) * исправлена поддержка css в меню с шаблонами сообщений (psi-send-button-context-menu.diff) + добавлен новый патч, исправляющий утечки памяти в главном меню (psi-fix-memory-leak-in-main-menu.diff) * исправлен экспорт и убраны magic numbers в патче новой истории (psi-historydlg.diff) + добавлен новый патч, исправляющий утечки памяти в optionseditor (psi-fix-memory-leaks-in-psioptionseditor.diff) - убран патч psi-fix-leak-in-edb.diff -> принят в upstream * изменены разделители в списке исключений tunes-контроллеров. вместо запятых теперь используются пробелы (psi-fix-tunes.diff) * исправлена перерисовка меню с шаблонами сообщений (psi-send-button-context-menu.diff) * возвращена кнопка Delete в панель управления advanced-опциями (psi-options-reset-button.diff) * исправлена работа опции автостарта приложения в ОС семейства Linux (psi-autostart.diff) * исправлена обработка пустых элементов в списке исключений tunes-контроллеров (psi-fix-tunes.diff) * исправлен патч psi-fix-memory-leak-in-main-menu.diff * обновлён chess plugin до версии 0.2.0 * обновлён gmail service plugin до версии 0.5.9 * исправлена привязка положения меню с шаблонами сообщений (psi-send-button-context-menu.diff) + добавлен новый патч, исправляющий утечки памяти в виджетах табов (psi-fix-leaks-in-tab-menus.diff) * исправлен патч psi-movable-tabs-qt45-and-custom-style.diff * переименована advanced-опция "can_close_inactive_tab" в "can-close-inactive-tab" (psi-movable-tabs-qt45-and-custom-style.diff) - убраны некоторые патчи -> приняты в upstream (psi-fix-leaks-in-tab-menus.diff, psi-fix-memory-leak-in-main-menu.diff, psi-fix-memory-leaks-in-psioptionseditor.diff) * исправлен патч psi-fix-possible-memory-leaks.diff - убран патч psi-fix-possible-memory-leaks.diff -> принят в upstream * некоторые исправления в стиле кода в патче psi-muc-minimize-to-roster.diff * мелкий фикс для меню с шаблонами сообщений (psi-send-button-context-menu.diff) * обновлён системный иконпак qipinfium_sys.jisp (спасибо Yuri Gural). оптимизированы все системные иконпаки (iconsets/system/blacksys.jisp, lunnacat.jisp, nuvola-system.jisp, oxygen_sys.jisp, oxygen_sys_22.jisp, qipinfium_sys.jisp, summer-system.jisp, tango_system.jisp, whitesys.jisp) * больше строк для локализации в cleaner plugin * запрет локализации строк в html для content downloader plugin + добавлена поддержка css для формы со смайлами (psi-css-style-sheet.diff) * больше строк для локализации менеджера контактов (psi-contact-manager.diff) * исправлено запоминание размера максимизированного окна конференции в режиме табов (psi-save-maximized-window-state.diff) ! обновлена русская локализация от ivan101 (psi-ru svn, r185, 2011-01-23) -- * updated ping.png icon for extended menu plugin * improved psi-coloring-tooltip.diff * rewritten psi-send-button-context-menu.diff (by liuch) + added new patch for add reset button to advanced options control panel (psi-options-reset-button.diff) * updated pep change notify plugin to v0.0.5 * added new patch for fix memory leaks (psi-fix-adduserdlg.diff) - removed psi-fix-adduserdlg.diff -> went to upstream + added new patch for fix leaks in data_widget (psi-fix-xdata-widget.diff) - removed psi-fix-xdata-widget.diff -> went to upstream * cleaned garbage from psi-options-reset-button.diff + added new patch for fix memory leak in edb (psi-fix-leak-in-edb.diff) * fixed possible memory leaks in psi-historydlg.diff * changed operation with controllers. better tune cleaning (psi-fix-tunes.diff) * fixed css for templates menu (psi-send-button-context-menu.diff) + added new patch for fix memory leaks in main menu (psi-fix-memory-leak-in-main-menu.diff) * fixed export, removed magic numbers in psi-historydlg.diff + added new patch for fix leaks in optionseditor (psi-fix-memory-leaks-in-psioptionseditor.diff) - removed psi-fix-leak-in-edb.diff -> went to upstream * changed exstensions separator from comma to space (psi-fix-tunes.diff) * fixed template's menu redraw (psi-send-button-context-menu.diff) * returned delete button to advanced options control panel (psi-options-reset-button.diff) * fixed psi-autostart.diff on linux * fixed empty items in controller-blacklist (psi-fix-tunes.diff) * fixed psi-fix-memory-leak-in-main-menu.diff * removeSubmenus -> clearMenu (psi-fix-memory-leak-in-main-menu.diff) * updated chess plugin to v0.2.0 * updated gmail service plugin to v0.5.9 * fixed temptate's menu show position (psi-send-button-context-menu.diff) + added new patch for fix some memory leaks in tab widget menus (psi-fix-leaks-in-tab-menus.diff) * fixed psi-movable-tabs-qt45-and-custom-style.diff * 'can_close_inactive_tab' -> 'can-close-inactive-tab' (psi-movable-tabs-qt45-and-custom-style.diff) - removed some patches -> went to upstream (psi-fix-leaks-in-tab-menus.diff, psi-fix-memory-leak-in-main-menu.diff, psi-fix-memory-leaks-in-psioptionseditor.diff) * fixed psi-fix-possible-memory-leaks.diff - removed psi-fix-possible-memory-leaks.diff -> went to upstream * some codestyle fixes for psi-muc-minimize-to-roster.diff * small fix for template menu (psi-send-button-context-menu.diff) * updated qipinfium_sys.jisp (thanks to Yuri Gural). optimized all systems jisps (iconsets/system/blacksys.jisp, lunnacat.jisp, nuvola-system.jisp, oxygen_sys.jisp, oxygen_sys_22.jisp, qipinfium_sys.jisp, summer-system.jisp, tango_system.jisp, whitesys.jisp) * more translatable for cleaner plugin * not translatable html for content downloader plugin + added css for smiles menu (psi-css-style-sheet.diff) * more translatable for contact manager (psi-contact-manager.diff) * save maximized groupchat window state in tabmode fix (psi-save-maximized-window-state.diff) ! updated russian localization by ivan101 (psi-ru svn, r185, 2011-01-23) 2011-01-16 zet v0.15.3522 Beta + добавлен новый патч, добавляющий опцию в настройки приложения "автостарт Psi+ при запуске операционной системы" (psi-autostart.diff) + добавлена поддержка автостарта Psi+ в ОС семейства Linux (psi-autostart.diff) * снижено количество запоминаемых историй ввода сообщений до 50 (psi-typed-history.diff) + добавлен новый патч для исправления возможной утечки памяти (psi-fix-possible-memory-leaks.diff) * некоторые фиксы translate plugin (v0.3.2) + добавлен новый патч, исправляющий поведение анимированной иконки с fancylabel и устраняющий возможную утечку памяти (psi-fix-fancylabel.diff) - убран патч psi-fix-fancylabel.diff. патч принят в upstream * попытка исправить ошибочный детект клиента Talkonaut (google.com 1.0.0.84) [psi-client-icons.diff] * небольшие фиксы conference logger plugin (v0.1.9). спасибо liuch ! начат рефакторинг патчей плагинной системы, часть 1: все плагинные патчи объединены в один общий патч + фиксы кодестайлинга (psi-extend-plugins-interface.diff) ! рефакторинг патчей плагинной системы, часть 2: улучшения и исправления общего плагинного патча (psi-extend-plugins-interface.diff) ! рефакторинг патчей плагинной системы, часть 3: исправлена работа плагинной системы, требуется перекомпилировать все плагины Psi+ * исправлен pep plugin (v0.0.4) * обновлён attention plugin до версии 0.1.5 + добавлен новый плагин - extended menu plugin, подробнее - http://psi-plus.com/wiki/plugins#extended_menu_plugin * фикс утечки памяти в патче psi-send-button-context-menu.diff (автор - liuch) * фиксы некоторых патчей (psi-avcalls-addons.diff, psi-muc-bookmark-toolbar-button.diff, psi-typeahead-find.diff, psi-muc-minimize-to-roster.diff, psi-add-join-groupchat-item-to-account-menu.diff, psi-send-xhtml-im.diff, psi-fix-tunes.diff) + добавлена возможность отключать определённые контроллеры трансляции музыки (трансляция через tune-файл и/или через Winamp-контроллер). комбинированный контроллер трансляции музыки больше не используется (psi-fix-tunes.diff) * некоторые улучшения в патче psi-extend-plugins-interface.diff * обновлён патч psi-extend-plugins-interface.diff. теперь кнопка "plugin info" доступна даже при отключенном плагине + добавлен новый патч для переименования бинарного исполняемого файла из psi.exe (psi) в psi-plus.exe (psi-plus) и разрешения конфликтов с пакетами оригинального Psi в некоторых ОС семейства Linux (psiplus-binary.diff) -- экспериментально! * обновлены списки ресурсов для content downloader plugin * исправлен watcher plugin (v.0.3.7) * исправлена утечка памяти в патче psi-send-xhtml-im.diff * исправлена ещё одна утечка памяти в патче psi-send-xhtml-im.diff * исправлен патч psiplus-binary.diff + добавлен детект некоторых jabber-клиентов. спасибо Qwеst (psi-client-icons.diff) * обновлён extended menu plugin до версии 0.0.5 * обновлён birthday reminder plugin до версии 0.3.2 * исправлен детект клиента sim communicator (psi-client-icons.diff) * обновлены иконпаки с иконками jabber-клиентов (iconsets/clients/fingerprint.jisp, fingerprint-22.jisp) -- + added new patch for adds autostart application option 'automatically launch application when OS starts' (psi-autostart.diff) + added support for linux (psi-autostart.diff) * decreased typed history size to 50 messages (psi-typed-history.diff) + added new patch for fix possible memory leaks (psi-fix-possible-memory-leaks.diff) * some fixes for translate plugin (v0.3.2) + added new patch for fix fancylabel (psi-fix-fancylabel.diff) - removed psi-fix-fancylabel.diff. patch moved to upstream * try to fix for Talkonaut detection (google.com 1.0.0.84) [psi-client-icons.diff] * small fixes for conference logger plugin (v0.1.9). tnx to liuch ! begining of refactoring plugins patches, part 1: merge all plugins patches into new one, some fixes to codestyle (psi-extend-plugins-interface.diff) ! refactoring, part2: improvement for main plugins patch. all the plugins are broken, wait for fixes (psi-extend-plugins-interface.diff) ! refactoring, part3: fixes for plugins. note: you must recompile all the plugins * fixed pep plugin (v0.0.4) * updated attention plugin (v0.1.5) + added new plugin - extended menu plugin, http://psi-plus.com/wiki/plugins#extended_menu_plugin * fixes for psi-send-button-context-menu.diff (by liuch) * fixed some patches (psi-avcalls-addons.diff, psi-muc-bookmark-toolbar-button.diff, psi-typeahead-find.diff, psi-muc-minimize-to-roster.diff, psi-add-join-groupchat-item-to-account-menu.diff, psi-send-xhtml-im.diff, psi-fix-tunes.diff) + added ability to disable tune controllers. combined controller is no longer used (psi-fix-tunes.diff) * some progress with psi-extend-plugins-interface.diff * updated psi-extend-plugins-interface.diff. for now 'plugin info' button available even when plugin is disabled + added new patch for rename binary executable file from psi.exe (psi) to psi-plus.exe (psi-plus) and avoid conflicts with original psi package on a Linux (psiplus-binary.diff) -- experimental! * updated resource lists for content downloader plugin * fixed watcher plugin (v.0.3.7) * fixed memory leak in psi-send-xhtml-im.diff * one more fix memory leak in psi-send-xhtml-im.diff * fixed psiplus-binary.diff + added some clients detection. thanks to Qwеst (psi-client-icons.diff) * updated extended menu plugin (v0.0.5) * updated birthday reminder plugin * fixed sim communicator detection (psi-client-icons.diff) * updated fingerprint jisps (iconsets/clients/fingerprint.jisp, fingerprint-22.jisp) 2011-01-09 zet v0.15.3465 Beta * обновлён client switcher plugin до версии 0.0.4 (автор - liuch) * фикс метода currentTune, незначительные фиксы для mpris (psi-fix-tunes.diff) * улучшен метод changeCase() в механизме автокапитализации (psi-auto-capitalizer.diff) + добавлен новый патч, создающий новую advanced-опцию options.ui.chat.show-status-changes для управления отображением статусных сообщений в ростерном чате (psi-disable-show-status-changes-in-chat.diff) * временно отключена обработка капчи в ядре приложения. для прохождения капчи используйте captcha forms plugin (psi-data-forms-media-element.diff) + добавлен новый универсальный скин LunnaCat от majik (skins/universal/LunnaCat) + добавлен новый системный иконпак lunnacat.jisp (iconsets/system/lunnacat.jisp) * обновлён список ресурсов для content downloader plugin (добавлен lunnacat.jisp) * пересортировка списков ресурсов в алфавитном порядке (для content downloader plugin) * обновлён attention plugin до версии 0.1.4 * убран мусор в коде группчатов. решена задача is369, http://code.google.com/p/psi-dev/issues/detail?id=369 (psi-save-maximized-window-state.diff) + добавлен фильтр транслируемых источников (psi-fix-tunes.diff) * поправлен патч psi-muc-nick-hash-color: исключена возможность отображения чёрных ников на чёрном фоне в чатлоге конференции (psi-muc-nick-hash-color.diff) -- * updated client switcher plugin to v0.0.4, by liuch * currentTune method fix, minor mpris fix (psi-fix-tunes.diff) * improve changeCase() method in auto-capitalizer (psi-auto-capitalizer.diff) + added new patch for create new option options.ui.chat.show-status-changes (psi-disable-show-status-changes-in-chat.diff) * temporary disable captcha in messages. use captcha forms plugin (psi-data-forms-media-element.diff) + added new universal skin LunnaCat (skins/universal/LunnaCat) + added new system iconset lunnacat.jisp (iconsets/system/lunnacat.jisp) * updated resources list for content downloader plugin (lunnacat.jisp) * reordered content.list (alphabetical) * updated attention plugin to v0.1.4 * removed garbage in groupchat dlg. fixed is369, http://code.google.com/p/psi-dev/issues/detail?id=369 (psi-save-maximized-window-state.diff) + added tune-content filters (psi-fix-tunes.diff) * fixed psi-muc-nick-hash-color patch for no more black for snowgirl and dead moroz. also corrected codestyle (psi-muc-nick-hash-color.diff) 2010-12-26 zet v0.15.3450 Beta * некоторые исправления в патче автосмены регистра psi-auto-capitalizer.diff * обновлён gmail service plugin до версии 0.5.7 + добавлено определение некоторых jabber-клиентов: Dictbot, SoFServer, Spectrum, ICQ Mobile (psi-client-icons.diff) * обновлены иконпаки с иконками клиентов (iconsets/clients/fingerprint.jisp, fingerprint-22.jisp) + добавлен новый плагин -- pep change notify plugin, подробнее -- http://psi-plus.com/wiki/plugins#pep_change_notify_plugin * изменения в процедуре getPluginOption (psi-plugins-options-and-fix-plugins-loading-and-unloading.diff, psi-fix-optionChanged-method-in-plugins.diff) * мелкие фиксы в патче psi-send-button-context-menu.diff * pep change notify plugin: добавлена поддержка mingw * мелкие фиксы в патче psi-typed-history.diff * исправлена функция getJid() для плагинной системы (psi-get-account-info-from-plugins.diff) * content downloader plugin: добавлена кнопка ручного запроса списка доступных ресурсов * content downloader plugin: теперь по умолчанию плагин автоматически не проверяет список доступных ресурсов (v0.1.10) * расширена функциональность accountinfoaccessor. спасибо liuch (psi-get-account-info-from-plugins.diff, psi-add-toolbar-button-from-plugins.diff, psi-access-to-menus-from-plugins.diff, psi-change-contacts-status-from-plugins.diff) + добавлен новый плагин -- client switcher plugin (by liuch), подробнее -- http://psi-plus.com/wiki/plugins#client_switcher_plugin * обновлён pep change notify plugin до версии 0.0.3 * обновлён патч на цитирование -- добавлен локальный хоткей Ctrl+S для цитирования выделенного фрагмента текста (psi-chatview-quote-feature.diff) * улучшена точность срабатывания механизма автосмены регистра (psi-auto-capitalizer.diff) -- * some updates for psi-auto-capitalizer.diff * updated gmail service plugin, v0.5.7 + added detection of Dictbot, SoFServer, Spectrum, ICQ Mobile. updated both fingerprint.jisp (psi-client-icons.diff, iconsets/clients/fingerprint.jisp, iconsets/clients/fingerprint-22.jisp) + added new plugin -- pep change notify plugin, http://psi-plus.com/wiki/plugins#pep_change_notify_plugin * changed getPluginOption procedure (psi-plugins-options-and-fix-plugins-loading-and-unloading.diff, psi-fix-optionChanged-method-in-plugins.diff) * less warnings (psi-send-button-context-menu.diff) * make pep change notify plugin compile with mingw * less warnings (psi-typed-history.diff) * updated downloads.wiki (pep change notify plugin) * fixed getJid() function for plugins (psi-get-account-info-from-plugins.diff) * content downloader plugin: added download content list button * content downloader plugin: download content list is now disabling when i click it (v0.1.10) * extend accountinfoaccessor. thx to liuch (psi-get-account-info-from-plugins.diff, psi-add-toolbar-button-from-plugins.diff, psi-access-to-menus-from-plugins.diff, psi-change-contacts-status-from-plugins.diff) + added new plugin -- client switcher plugin (by liuch), http://psi-plus.com/wiki/plugins#client_switcher_plugin * updated pep change notify plugin, v0.0.3 * improved quote feature. added Ctrl+S shortcut (psi-chatview-quote-feature.diff) * improved auto-capitalize accurracy (psi-auto-capitalizer.diff) 2010-12-19 zet v0.15.3414 Beta * обновлён патч для истории сообщений (psi-historydlg.diff) * исправлено возможное падение приложения при удалении аккаунтов (psi-roster-avatar-frame.diff) * небольшие исправления в conference logger plugin * обновлён extended options plugin (добавлена обработка options.ui.muc.hide-on-autojoin, options.ui.look.colors.chat.unread-message-color, options.ui.look.colors.chat.composing-color) * решена задача 383, http://code.google.com/p/psi-dev/issues/detail?id=383 (psi-activity-icons-and-description.diff) - патч psi-fix-online-contact-count.diff убран из рабочего каталога и не применяется вместе с остальными, подробнее -- задача 384, http://code.google.com/p/psi-dev/issues/detail?id=384 + добавлен новый патч для фикса qWarning сообщений (psi-fix-qWarning-message.diff) * теперь билды Psi+ выпускаются только под MacOS 10.6 (trunk/scripts/macosx/patches/3020-psi-mac-Makefile.diff, trunk/scripts/macosx/psibuild.command) - убран патч psi-fix-qWarning-message.diff, т.к. принят в upstream + добавлен новый ЭКСПЕРИМЕНТАЛЬНЫЙ патч для автоматического переключения регистра при составлении сообщения. автосмена регистра выключена по умолчанию. автосмена включается advanced-опцией options.ui.chat.auto-capitalize, ручное переключение регистра - Ctrl+Alt+X (psi-auto-capitalizer.diff) * обновлён stop spam plugin * исправлены возможные утечки памяти в juick plugin * исправлены возможные утечки памяти в некоторых плагинах (birthday reminder plugin, captcha forms plugin, image plugin) * исправлены возможные утечки памяти в некоторых плагинах, часть 2 (chess plugin, cleaner plugin, skins plugin) + в инсталлятор psiplus-install.nsi добавлена опция для старта Psi+ при запуске windows * обновлена ссылка для скачивания свежей версии инсталлятора Psi+ (psi-dirty-check.diff) ! обновлены библиотеки win32 openssl до версии 0.9.8q -- * updated history patch (psi-historydlg.diff) * fixed possible crash when removing accounts (psi-roster-avatar-frame.diff) * small fixes to conference logger plugin * updated extended options plugin (added options.ui.muc.hide-on-autojoin, options.ui.look.colors.chat.unread-message-color, options.ui.look.colors.chat.composing-color) * fixed issue 383, http://code.google.com/p/psi-dev/issues/detail?id=383 (psi-activity-icons-and-description.diff) - moved psi-fix-online-contact-count.diff patch to dev cos of issue 384, http://code.google.com/p/psi-dev/issues/detail?id=384 + added new patch to fix qWarning message (psi-fix-qWarning-message.diff) * back to MacOS 10.6 support only (trunk/scripts/macosx/patches/3020-psi-mac-Makefile.diff, trunk/scripts/macosx/psibuild.command) - removed psi-fix-qWarning-message.diff. patch moved to upstream + added new auto-capitalizer EXPERIMENTAL patch. disabled by default. enable - options.ui.chat.auto-capitalize, manual change case - Ctrl+Alt+X (psi-auto-capitalizer.diff) * updated stop spam plugin * fixed possible memory leaks in juick plugin * fixed possible memory leaks in some plugins (birthday reminder plugin, captcha forms plugin, image plugin) * fixed possible memory leaks in some plugins, part2 (chess plugin, cleaner plugin, skins plugin) + psiplus-install.nsi: added optional running Psi+ on windows start * updated url for windows installer (psi-dirty-check.diff) ! updated win32 openssl libs to v0.9.8q 2010-12-12 zet v0.15.3385 Beta * патч для истории сообщений синхронизирован с git и внесены некоторые фиксы, подробнее -- http://code.google.com/p/psi-dev/issues/detail?id=353 (psi-historydlg.diff) + добавлен новый патч для исправления поведения контакта ростера при его удалении из пустой группы (psi-tmp-fix-removing-contacts-with-empty-group.diff) * более корректная работа с MPRIS (psi-nix-mpris.diff) * icq must die plugin - добавлена лицензия для debian * content downloader plugin. теперь плагин умеет проверять наличие ресурсов в папке установки Psi+ * обновлён патч для истории сообщений - добавлен календарь (psi-historydlg.diff) * попытка решить задачу 337, http://code.google.com/p/psi-dev/issues/detail?id=337 (psi-change-password-dialog.diff) * в патче на цитирование теперь используется символ "»" вместо ">>" (psi-chatview-quote-feature.diff) * патч на цитирование: вставка цитаты на позицию курсора (psi-chatview-quote-feature.diff) + добавлена поддержка множественных подключений (psi-nix-mpris.diff) + добавлена поддержка множественных подключений на старте Psi+ (psi-nix-mpris.diff) - убран патч psi-nix-mpris.diff, т.к. он принят в upstream + добавлен новый патч для решения задачи 377, http://code.google.com/p/psi-dev/issues/detail?id=377 (psi-fix-closing-inactive-tabs-when-apply-button-pressed.diff) * объединены патчи merged psi-muc-minimize-to-roster.diff и psi-fix-closing-inactive-tabs-when-apply-button-pressed.diff - убран патч psi-tmp-fix-removing-contacts-with-empty-group.diff, т.к. он принят в upstream - убраны некоторые патчи, т.к. они приняты в upstream (psi-unnamed-xmpp-uri-param.diff, psi-fix-avatar-transparency.diff, psi-vcard-open-homepage.diff) * обновлён патч для истории сообщений - добавлена команда для удаления истории переписки с выбранным контактом (вызывается через контекстное меню) [psi-historydlg.diff] * поправлена ссылка на багтрекер проекта в меню Help (psiplus-join-to-support-muc.diff) - убран патч psi-img-src-file-and-data-protos.diff. патч переписан и принят в upstream - убран патч psi-add-detection-some-os.diff, т.к. он принят в upstream * исправлен патч psi-entity-time.diff * исправлено падение приложения в Linux при попытке поиска с пустой поисковой строкой (psi-historydlg.diff) + добавлено определение клиента QIP Mobile, обновлены иконпаки с иконками клиентов (psi-client-icons.diff, iconsets/clients/fingerprint.jisp, fingerprint-22.jisp) * обновлён патч для истории сообщений - восстановлена команда экспорта истории переписки в текстовый файл (вызывается через контекстное меню) [psi-historydlg.diff] * исправлено расположение скриншотов Psi+ на главной странице проекта после смены её дизайна (http://psi-dev.googlecode.com/) * исправлена возможность блокировки исходящих сообщений из плагинов (0570-psi-extend-eventfilter.diff) * восстановлено отображение иконок доставки сообщений в чатлогах (psi-receipts.diff) * обновлён патч для истории сообщений - добавлены иконки для команд контекстного меню (psi-historydlg.diff) * processOutgoingStanza event filter method (psi-extend-eventfilter.diff) * исправлены опечатки в интерфейсе cleaner plugin (спасибо tehnick.alive) * исправлены опечатки в патче psi-implements-iq-version-stuff-in-discodlg.diff (спасибо tehnick.alive) * processOutgoingStanza переименован в outgoingStanza и перемещён в StanzaFilter (psi-extend-eventfilter.diff) * исправлена работа некоторых плагинов (gmail service plugin, juick plugin и stopspam plugin) после изменений в StazaFilter * фиксы для psi-add-options-color-highlighting.diff * исправлена работа остальных плагинов после изменений в StazaFilter (attention plugin, autoreply plugin, birthday reminder plugin, captcha forms plugin, chess plugin, conference logger plugin, icq must die plugin, qip x-statuses plugin, storage notes plugin, watcher plugin) * обновлён watcher plugin: оптимизация определения плеера для воспроизведения звуковых событий + добавлено определение некоторых клиентов (bombusng-md, emess, movamessenger и libpurple), обновлены иконпаки с иконками клиентов (psi-client-icons.diff, iconsets/clients/fingerprint.jisp, fingerprint-22.jisp) -- * psi-historydlg.diff sync with git and some fixes, http://code.google.com/p/psi-dev/issues/detail?id=353 + added new patch to fix removing contacts with empty group (psi-tmp-fix-removing-contacts-with-empty-group.diff) * updated new history patch (psi-historydlg.diff) * more correct work with MPRIS (psi-nix-mpris.diff) * icq must die plugin - licence for debian * content downloader plugin. now also looking resources in resources dir * updated history patch - added calendar (psi-historydlg.diff) * try to fix is337, http://code.google.com/p/psi-dev/issues/detail?id=337 (psi-change-password-dialog.diff) * using '»' instead '>>' for quote feature (psi-chatview-quote-feature.diff) * quote feature: insert quote in the cursor position (psi-chatview-quote-feature.diff) * quote feature: corrected quoute symbol (psi-chatview-quote-feature.diff) + added multiply connections support (psi-nix-mpris.diff) + added support of multiply mpris connections on psi+ start (psi-nix-mpris.diff) * fixed excessive addition to list (psi-nix-mpris.diff) - removed psi-nix-mpris.diff. patch applyed by upstream + added new patch to fix is377, http://code.google.com/p/psi-dev/issues/detail?id=377 (psi-fix-closing-inactive-tabs-when-apply-button-pressed.diff) * merged psi-muc-minimize-to-roster.diff and psi-fix-closing-inactive-tabs-when-apply-button-pressed.diff patches - removed psi-tmp-fix-removing-contacts-with-empty-group.diff. patch applyed by upstream - removed some patches. patches applyed by upstream (psi-unnamed-xmpp-uri-param.diff, psi-fix-avatar-transparency.diff, psi-vcard-open-homepage.diff) * updated history patch - added delete history button, some other fixes (psi-historydlg.diff) * updated link to psi+ bugtracker in Help menu (psiplus-join-to-support-muc.diff) - removed psi-img-src-file-and-data-protos.diff. patch reworked and moved to upstream - removed psi-add-detection-some-os.diff. patch moved to upstream * fixed psi-entity-time.diff * fixed crash history search (psi-historydlg.diff) + added detection of QIP Mobile, updated jisps with clients icons (psi-client-icons.diff, iconsets/clients/fingerprint.jisp, fingerprint-22.jisp) * updated history patch - restore history export (use context menu) [psi-historydlg.diff] * fixed typo in psi-historydlg.diff * fixed pictures resolution for new googlecode site desigh * fixed ability block outgoing messages from plugins (0570-psi-extend-eventfilter.diff) * fixed receipt icons (psi-receipts.diff) * updated history patch - icons at popup menu (psi-historydlg.diff) * fixed psi-muc-nickclick-chat.diff * processOutgoingStanza event filter method (psi-extend-eventfilter.diff) * fixed typos in cleaner plugin (thx to tehnick.alive) * fixed typos in psi-implements-iq-version-stuff-in-discodlg.diff (thx to tehnick.alive) * processOutgoingStanza renamed to outgoingStanza and moved to StanzaFilter (psi-extend-eventfilter.diff) * fixed gmail service plugin, juick plugin and stopspam plugin after StazaFilter changes * fixed psi-add-options-color-highlighting.diff * fixed other plugins after StazaFilter changes (attention plugin, autoreply plugin, birthday reminder plugin, captcha forms plugin, chess plugin, conference logger plugin, icq must die plugin, qip x-statuses plugin, storage notes plugin, watcher plugin) * updated watcher plugin: sound player detect optimization + added detection of bombusng-md, emess, movamessenger and libpurple clients, updated fingeprint jisps (psi-client-icons.diff, iconsets/clients/fingerprint.jisp, fingerprint-22.jisp) 2010-12-05 zet v0.15.3318 Beta - убраны патчи psi-fix-grepshortcutkeydialog.diff, psi-fix-adhoc-set-status.diff, psi-fix-empty-name-for-bookmark.diff, psi-fix-submenu-hide.diff и psi-fix-iconsets-when-changing-profiles.diff. патчи приняты в upstream * продолжена работа над медиа-элементами (psi-data-forms-media-element.diff) * значительно переработан gmail notification plugin. теперь он называется gmail service plugin (v0.5.6) * фиксы для psi-hide-muc-auto-join.diff + добавлен новый патч, исправляющий подсчёт количества онлайн-контактов в заголовке аккаунта в ростере при уходе одного из контактов в оффлайн (psi-fix-online-contact-count.diff) + добавлены приоритеты для плагинной системы (psi-plugins-options-and-fix-plugins-loading-and-unloading.diff, psi-access-to-iconfactory-from-plugins.diff, psi-access-to-activetab-from-plugins.diff, psi-add-toolbar-button-from-plugins.diff, psi-access-to-menus-from-plugins.diff, psi-change-contacts-status-from-plugins.diff, psi-set-account-status-from-plugins.diff) * stop spam plugin: имя бинарного файла плагина изменено с "aaastopspamplugin.dll" на "stopspamplugin.dll" * теперь stop spam plugin имеет наивысший приоритет среди плагинов + добавлен новый патч для решения задачи 368, http://code.google.com/p/psi-dev/issues/detail?id=368 (psi-fix-is368.diff) - убран патч psi-fix-is368.diff. патч принят в upstream + добавлен новый патч на улучшение поведения элементов ростера при их перетаскивании. теперь возможно скопировать контакт ростера в несколько групп (с нажатым Ctrl) [psi-fix-drag-n-drop.diff] - убран патч psi-fix-drag-n-drop.diff. патч принят в upstream - убран ненужный патч psiplus-fix-application-info-defines.diff + добавлены ростерные иконпаки (crystal-aim.jisp, crystal-gadu.jisp, crystal-icq.jisp, crystal-msn.jisp, crystal-roster.jisp, crystal-service.jisp, crystal-sms.jisp, crystal-yahoo.jisp, stellar-1.jisp, GreenWeb.jisp, thomas.jisp, wpkontakt2.jisp, wpkontakt3.jisp) + добавлен новый патч для поддержки MPRIS. актуален ТОЛЬКО для *nix-систем (psi-nix-mpris.diff) * обновлён screenshot plugin до версии 0.4.4. плагин теперь использует настройки прокси со страницы настроек приложения * обновлён состав участников проекта Psi+ (psiplus-aboutdlg.diff) * исправлена излишняя активность посылки событий tunes (psi-nix-mpris.diff) + добавлена поддержка MPRIS v2 (psi-nix-mpris.diff) - убран патч psi-openssl-1.0-defaults-compatibility.diff. патч принят в upstream * обновлён content downloader plugin. исправлены проблемы с кодировками (v0.1.8) * исправлен ростерный иконпак weatheraqua.jisp * обновлён juick plugin до версии 0.10.4. добавлена возможность указать список джидов, для которых будет происходить обработка сообщений (экспериментально) -- - removed patches psi-fix-grepshortcutkeydialog.diff, psi-fix-adhoc-set-status.diff, psi-fix-empty-name-for-bookmark.diff, psi-fix-submenu-hide.diff, psi-fix-iconsets-when-changing-profiles.diff. patches applyed by upstream * some progress on media element (psi-data-forms-media-element.diff) * rewritten gmail notification plugin and renamed to gmail service plugin (v0.5.6) * small fix to psi-hide-muc-auto-join.diff + added new patch to fix online contact count in account header when one of contacts goes offline (psi-fix-online-contact-count.diff) * small fix to gmail service pluign * stop spam plugin: the filename of binary file 'aaastopspamplugin.dll' renamed to 'stopspamplugin.dll' + added priority to plugins (psi-plugins-options-and-fix-plugins-loading-and-unloading.diff, psi-access-to-iconfactory-from-plugins.diff, psi-access-to-activetab-from-plugins.diff, psi-add-toolbar-button-from-plugins.diff, psi-access-to-menus-from-plugins.diff, psi-change-contacts-status-from-plugins.diff, psi-set-account-status-from-plugins.diff) * make stop spam plugin highest priority plugins * psiplus-install.nsi: aaastopspamplugin -> stopspamplugin + added new patch to fix issue 368, http://code.google.com/p/psi-dev/issues/detail?id=368 (psi-fix-is368.diff) - removed psi-fix-is368.diff. patch applyed by upstream + added new patch to some fixes to drag-n-drop of contacts. for now is possible to copy contact to several groups (psi-fix-drag-n-drop.diff) - removed psi-fix-drag-n-drop.diff. patch applyed by upstream - removed more unnecessary patch psiplus-fix-application-info-defines.diff + added more roster iconsets for content downloader plugin (crystal-aim.jisp, crystal-gadu.jisp, crystal-icq.jisp, crystal-msn.jisp, crystal-roster.jisp, crystal-service.jisp, crystal-sms.jisp, crystal-yahoo.jisp, stellar-1.jisp, GreenWeb.jisp, thomas.jisp, wpkontakt2.jisp, wpkontakt3.jisp) + added new patch to MPRIS support. for *nix systems only (psi-nix-mpris.diff) * updated screenshot plugin to v0.4.4 * updated project members list (psiplus-aboutdlg.diff) * fixed excessive tunes sending (psi-nix-mpris.diff) + added MPRIS v2 support (psi-nix-mpris.diff) - removed psi-openssl-1.0-defaults-compatibility.diff. patch applyed by upstream * updated content downloader plugin. fixed encoding problems (v0.1.8) * fixed roster iconset weatheraqua.jisp * updated juick plugin to v0.10.4 2010-11-28 zet v0.15.3275 Beta + менеджер аккаунтов: добавлена возможность сортировки аккаунтов в списке аккаунтов (psi-allow-accounts-reordering-in-manager.diff) + добавлен новый патч для решения задачи 91, http://code.google.com/p/psi-dev/issues/detail?id=91, используется advanced-опция options.ui.muc.hide-on-autojoin (psi-hide-muc-auto-join.diff) * фиксы для патча psi-roster-avatar-frame.diff * улучшена сортировка аккаунтов в списке аккаунтов (по алфавиту + перетаскивание) [psi-allow-accounts-reordering-in-manager.diff] * убраны ненужные вызовы после фиксов из предыдущего коммита (psi-allow-accounts-reordering-in-manager.diff) * фиксы для режима "окна без границ" (psiplus-decorate-windows.diff, psi-save-maximized-window-state.diff, psi-vert-splitter-position.diff) + добавлен новый патч для вызова из меню Help формы присоединения к конференции Psi+ вместо формы присоединения к конференции официальной Psi (psiplus-join-to-support-muc.diff) * gmail notification plugin переименован в gmail servic eplugin (http://psi-plus.com/wiki/plugins#gmail_service_plugin) * updated gmailserviceplugin. added support of Off The Record chats extension * исправления для cleaner plugin (корректная работа при смене профиля) + добавлен новый патч, добавляющий возможность перехода по ссылке, указанной в соответствующем поле vCard (psi-vcard-open-homepage.diff) * улучшения процесса открытия домашней странички из vCard. добавлена поддержка протокола omit (psi-vcard-open-homepage.diff) -- + allow account sorting in account manager (psi-allow-accounts-reordering-in-manager.diff) + added new patch to fix issue 91, http://code.google.com/p/psi-dev/issues/detail?id=91 , use options.ui.muc.hide-on-autojoin (psi-hide-muc-auto-join.diff) * fixed psi-roster-avatar-frame.diff * a little of sorting magic in account manager (psi-allow-accounts-reordering-in-manager.diff) * remove unnecessary calls from previous fixes (psi-allow-accounts-reordering-in-manager.diff) * changed borderless mode (psiplus-decorate-windows.diff, psi-save-maximized-window-state.diff, psi-vert-splitter-position.diff) * no template error fix (psiplus-decorate-windows.diff) + added new patch to change muc action from help menu to join to psi+ conference instead of upstreams one (psiplus-join-to-support-muc.diff) * updated gmail notify plugin (http://psi-plus.com/wiki/plugins#gmail_service_plugin) * updated gmailplugin. add support of Off the record chats extension * fixes for cleaner plugin * gmailnotifyplugin -> gmailserviceplugin + added new patch to open homepage from vcard (psi-vcard-open-homepage.diff) * improved homepage opener from previous commit. allows to omit protocol (psi-vcard-open-homepage.diff) 2010-11-21 zet v0.15.3252 Beta + добавлен новый патч для управления питанием, подробнее -- http://lists.affinix.com/pipermail/psi-devel-affinix.com/2010-November/009129.html (psi-nix-systemwatch-over-upower-service.diff) [актуален только для *nix-систем] * исправлен баг при вызове диалога с настройками приложения (psi-fix-bug-in-options.diff) * исправлено падение приложения при смене профиля (psi-plugins-options-and-fix-plugins-loading-and-unloading.diff) * фикс патча psi-entity-time.diff - убраны патчи psi-fix-removing-sorted-contacts.diff и psi-fix-bug-in-options.diff. данные патчи приняты в upstream + добавлен новый патч, рисующий разделитель неактивных сессий в чатлогах (не в конференциях) [psi-add-trackbar-to-chats.diff] - убран патч psi-tr-admin.diff. патч принят в upstream * обновлён иконпак настроений silk.jisp, v1.2 (iconsets/moods/silk.jisp) + добавлен новый патч, устраняющий проблемы работы иконпаков после смены профиля (psi-fix-iconsets-when-changing-profiles.diff), исправлен патч psi-access-to-iconfactory-from-plugins.diff для работы с новым патчем * опция "use-default-avatar" теперь включена по умолчанию для нового профиля (psi-modern-roster.diff) * фиксы отображения градиента в ростере (psi-muc-minimize-to-roster.diff) * исправлено отображение события от "пустого" контакта. подробнее -- http://code.google.com/p/psi-dev/issues/detail?id=49#c11 (psi-muc-minimize-to-roster.diff) + добавлен новый патч для запоминания положения разделителя области чатлога и поля ввода сообщения (psi-vert-splitter-position.diff) * решена задача 365, http://code.google.com/p/psi-dev/issues/detail?id=365 (psi-roster-autohide.diff) * решена задача 354, http://code.google.com/p/psi-dev/issues/detail?id=354 (psi-roster-avatar-frame.diff, psi-presets-in-status-menu.diff) * обновлён skins plugin до версии 0.2.9 + добавлен новый патч для корректного отображения прозрачности аватара (psi-fix-avatar-transparency.diff) - убраны неиспользумые скины для Mac OS X из скрипта сборки инсталлятора для win32 (psiplus-install.nsi) - убраны неиспользуемые скины для Mac OS X из скрипта сборки билдов для *nix-систем (trunk/scripts/posix/psibuild-deb.sh) + добавлен новый патч, позволяющий сортировать аккаунты в списке менеджера аккаунтов. первый аккаунт в списке является аккаунтом по умолчанию (psi-allow-accounts-reordering-in-manager.diff) -- + added upower systemwatch patch from http://lists.affinix.com/pipermail/psi-devel-affinix.com/2010-November/009129.html (psi-nix-systemwatch-over-upower-service.diff) [for *nix only] * split proxy-settings patch (psi-proxy-settings-in-opt.diff) * fixed stupid bug in options (psi-fix-bug-in-options.diff) * media element updated to current bob changes (psi-bits-of-binary.diff, psi-data-forms-media-element.diff, psi-entity-time.diff, psiplus-fix-application-info-defines.diff, psiplus-os-hide.diff, psi-improve-adhoc-forms.diff, psi-change-password-dialog.diff) * fixed crash when changing profiles (psi-plugins-options-and-fix-plugins-loading-and-unloading.diff) * fixed psi-entity-time.diff - removed psi-fix-removing-sorted-contacts.diff and psi-fix-bug-in-options.diff. patches applyed by upstream + added new patch for adds trckbar to chats (not muc) [psi-add-trackbar-to-chats.diff] - removed psi-tr-admin.diff. patch applyed by upstream * updated silk.jisp, v1.2 (iconsets/moods/silk.jisp) + added new patch to fix problems with iconsets when changing profiles (psi-fix-iconsets-when-changing-profiles.diff), fix psi-access-to-iconfactory-from-plugins.diff for working with new patch * use-default-avatar=true for new profile (psi-modern-roster.diff) * some fixes to painting gradient in roster (psi-muc-minimize-to-roster.diff) * fixed opening event from empty contact (http://code.google.com/p/psi-dev/issues/detail?id=49#c11) [psi-muc-minimize-to-roster.diff] + added new patch to remember the vertical splitter position (psi-vert-splitter-position.diff) * fixed http://code.google.com/p/psi-dev/issues/detail?id=365 (psi-roster-autohide.diff) * fixed http://code.google.com/p/psi-dev/issues/detail?id=354 (psi-roster-avatar-frame.diff, psi-presets-in-status-menu.diff) * fix compillation failure with disabled dbus (psi-nix-systemwatch-over-upower-service.diff) * updated skins plugin to v0.2.9 + added new patch to fix avatar transparency (psi-fix-avatar-transparency.diff) - removed mac skin from psiplus-install.nsi for win32 installers - killing unused mac skins (trunk/scripts/posix/psibuild-deb.sh) + added new patch which allows to set order of accounts in account manager. first account is usually default accounts, so its affected (psi-allow-accounts-reordering-in-manager.diff) 2010-11-14 zet v0.15.3200 Beta + добавлен новый патч psi-show-activity-and-mood-descriptions.diff * разделён патч psi-show-activity-and-mood-descriptions и объединён с патчами psi-mood-icons и psi-activity-icons (psi-mood-icons-and-description.diff, psi-activity-icons-and-description.diff) + добавлен новый патч psi-chatview-quote-feature.diff * фикс TabDlg closeTab. фикс максимизации EventDlg (psi-save-maximized-window-state.diff) * поправлен патч psi-chatview-quote-feature для поддержки копирования смайлов (psi-chatview-quote-feature.diff) + добавлен новый патч, решающий задачу http://code.google.com/p/psi-dev/issues/detail?id=147 (psi-resolve-nickname-by-N-for-adduserdlg.diff) + добавлен новый патч, включающий по умолчанию фильтры при вызове xml-консоли (psi-xmlconsole-filter-group-always-enable.diff) + добавлен новый патч для поддержки капчи при регистрации аккаунта непосредственно из Psi+ (psi-captcha.diff) + добавлен новый патч psi-internal-changes-tabbablewidget.diff (экспериментально) - убран патч psi-fix-dont-parse-disco-register-form-twice.diff, т.к. он принят в upstream + добавлен новый патч для возможности настройки прокси-сервера для разных сущностей. патч добавляет настройки на вкладку Application (Приложение) в настройках приложения (psi-proxy-settings-in-opt.diff) * обновлён juick plugin. теперь можно настраивать использование прокси-сервера непосредственно в настройках приложения на вкладке Application (Приложение) * фикс для options.ui.look.colors.messages.highlighting (psi-add-options-color-highlighting.diff) * обновлён juick plugin: исправлена обработка превью фотографий в сообщениях от бота juiсk * обновлён birthday reminder plugin до версии 0.3.0 + добавлен новый патч для исправления поведения метода optionChanged() в плагинной системе (psi-fix-optionChanged-method-in-plugins.diff) * добавлены команды вызова некоторых плагинов из меню чата в компактном режиме когда скрыт центральный тулбар (psi-add-toolbar-button-from-plugins.diff) * поправлен патч psiplus-decorate-windows для улучшения работы окон/табов в ОС Haiku (спасибо Diger) [psiplus-decorate-windows.diff] * обновлён content downloader plugin до версии 0.1.7. теперь плагин не зависит от библиотеки libproxy * улучшения в патче psi-access-to-contact-info-from-plugins и небольшие обновления для stop spam plugin (psi-access-to-contact-info-from-plugins.diff) * обновлён watcher plugin до версии 0.3.5 * обновлён список активных участников проекта на вкладке Help -> About Psi+ (psiplus-aboutdlg.diff) ! обновлены системные библиотеки Qt до версии 4.7.1, подробнее -- http://qt.nokia.com/developer/changes/changes-4.7.1 -- + added new patch psi-show-activity-and-mood-descriptions.diff * splited psi-show-activity-and-mood-descriptions and merged with psi-mood-icons and psi-activity-icons patches (psi-mood-icons-and-description.diff, psi-activity-icons-and-description.diff) + added new patch psi-chatview-quote-feature.diff * TabDlg closeTab fix. EventDlg maximize fix (psi-save-maximized-window-state.diff) * fixed psi-chatview-quote-feature patch to support smiles (psi-chatview-quote-feature.diff) + added new patch to fix http://code.google.com/p/psi-dev/issues/detail?id=147 (psi-resolve-nickname-by-N-for-adduserdlg.diff) + added new patch psi-xmlconsole-filter-group-always-enable.diff + added new patch for support captcha registration (psi-captcha.diff) + added new patch psi-internal-changes-tabbablewidget.diff (experimental) - removed psi-fix-dont-parse-disco-register-form-twice.diff. patch applyed by upstream + added new patch psi-proxy-settings-in-opt.diff * updated juick plugin. now it uses proxy settings from application options tab * fixed options.ui.look.colors.messages.highlighting (psi-add-options-color-highlighting.diff) * updated juick plugin: fixed preview * fixed psi-internal-changes-tabbablewidget.diff * updated birthday reminder plugin to v0.3.0 + added new patch to fix optionChanged() method in plugins (psi-fix-optionChanged-method-in-plugins.diff) * copied plugins actions from central toolbar to the chat menu when toolbar is hidden (psi-add-toolbar-button-from-plugins.diff) * fixed psiplus-decorate-windows patch to working fine with Haiku, by Diger (psiplus-decorate-windows.diff) * updated content downloader plugin to v0.1.7. at now this plugin does not depend on the libproxy library * improved psi-access-to-contact-info-from-plugins patch and small update for stop spam plugin (psi-access-to-contact-info-from-plugins.diff) * updated watcher plugin to v0.3.5 * updated active Psi+ Project members (psiplus-aboutdlg.diff) ! updated Qt libs to v4.7.1, http://qt.nokia.com/developer/changes/changes-4.7.1 2010-10-31 zet v0.15.3135 Beta * обновлён патч на отрисовку иконки статуса в заголовке таба. теперь события composing и иконка сообщения корректно отображаются в заголовке таба (psi-tab-status-icon.diff, psi-muc-roster-size-and-location.diff, psi-muc-minimize-to-roster.diff, psiplus-decorate-windows.diff, psi-colors-for-tabs.diff) - убран патч psi-fix-crash-in-muc-configurator.diff. принят в основную ветвь Psi + добавлен новый патч для добавления в графический интерфейс настроек опции на включение приёма/передачи видео при совершении голосовых вызовов. ранее этот параметр задавался в переменных окружения (psi-video-option.diff) * обновлены некоторые патчи (psi-iconsets.diff, psi-tab-status-icon.diff, psi-colors-for-tabs.diff) + добавлен новый патч для корректировки поведения всплывающих уведомлений: добавлена возможность настройки некоторых параметров всплывающих уведомлений через графический интерфейс CSS. см. fancypopup.ui для подробностей (psiplus-new-popups.diff) * больше опций для CSS: теперь возможно задать основной цвет для таба через TabDlg { color: ... ;} [psi-colors-for-tabs.diff] + добавлен репозиторий для хранения описаний существующих ресурсов приложения (trunk/resources) * обновлён content downloader plugin: поправлена ссылка на хранилище описаний ресурсов (v0.1.6) * больше отступов во всплывающих окнах (psiplus-new-popups.diff) * RosterAvatarFrame теперь настраиваем через CSS (psi-roster-avatar-frame.diff) * обновлён attention plugin до версии 0.1.3 * исправлена отрисовка CSS на табах после сворачивания/разворачивания (psi-muc-minimize-to-roster.diff) + добавлен новый патч для добавления функционала всплывающих уведомлений о написании сообщения (psi-typing-notify-popups.diff) + добавлена возможность включения только приёма событий composing (без отправки соответствующих уведомлений) [psi-typing-notify-popups.diff] -- * updated tab-status-icons patch. now composing and message icons are shown on tabs (psi-tab-status-icon.diff, psi-muc-roster-size-and-location.diff, psi-muc-minimize-to-roster.diff, psiplus-decorate-windows.diff, psi-colors-for-tabs.diff) - removed patch psi-fix-crash-in-muc-configurator.diff. goes to upstream + added new patch for enable video support in options - not in ENV (psi-video-option.diff) * updated some patches (psi-iconsets.diff, psi-tab-status-icon.diff, psi-colors-for-tabs.diff) + added new patch for some fixes to popups: add ui. now easy configuring via css. see fancypopup.ui for details (psiplus-new-popups.diff) * more options for CSS: now can set general color for tabs via TabDlg { color: ... ;} [psi-colors-for-tabs.diff] + added repository for descriptions of some resource files (trunk/resources) * updated content downloader plugin: fixed link to file of resources (v0.1.6) * more space in popups (psiplus-new-popups.diff) * RosterAvatarFrame now more configurable via css (psi-roster-avatar-frame.diff) * updated attention plugin to v0.1.3 * correct works of CSS on tab after hiding/showing (psi-muc-minimize-to-roster.diff) + added new patch for adds typing notify popups to core (psi-typing-notify-popups.diff) + added ability for composing events receive only (psi-typing-notify-popups.diff) 2010-10-18 zet v0.15.3102 Beta + добавлены лицензия и копирайт в файлы плагина content downloader plugin + добавлены лицензия и копирайт в файлы плагина icq must die plugin * некоторые фиксы патча psi-plugins-options-and-fix-plugins-loading-and-unloading.diff * поправлен патч psi-plugin-info-button.diff + добавлен новый вариант отображения диалогов с историей переписки (автор - Piotrek Okonski ) [psi-historydlg.diff] + добавлен новый патч для совместимости с mingw-w64 (psi-mingw-w64-compatibility.diff) * синхронизация патча совместимости с upstream-репозиторием (psi-gcc-4.6-compatibility.diff) * обновлён content downloader plugin до версии 0.1.5. теперь кнопка "Download and Install" неактивна при открытии окна с настройками плагина (не выбран ни один ресурс), подробнее -- http://psi-dev.googlecode.com/svn/trunk/plugins/generic/contentdownloaderplugin/changelog.txt + добавлен новый набор с иконками аффиляций stellar1-affiliations.jisp (iconsets/affiliations/stellar1-affiliations.jisp) * обновлён icq must die plugin до версии 0.1.5 (настраиваемое количество сообщений пользователю) + добавлен новый патч для решения задачи 357, http://code.google.com/p/psi-dev/issues/detail?id=357 (psi-workaround-adhoc-form-closes.diff) * обновлён системный иконпак nuvola-system.jisp (iconsets/system/nuvola-system.jisp) + добавлены лицензия и копирайт в файлы некоторых плагинов Psi+ (attention plugin, auto reply plugin, birthday reminder plugin, captcha forms plugin, chess plugin, cleaner plugin, conference logger plugin, extended options plugin, gmail notification plugin, history keeper plugin, image plugin, juick plugin, qip x-statuses plugin, screenshot plugin, skins plugin, stop spam plugin, storage notes plugin, translate plugin, video status plugin, watcher plugin) + добавлен новый патч для поддержки декоративного обрамления диалоговых окон в Psi+ ("режим окна без границ") [psiplus-decorate-windows.diff, автор - KukuRuzo] * исправлено восстановление окна в режиме без границ (psiplus-decorate-windows.diff) * обновлён skins plugin до версии 0.2.8 + добавлен новый патч для сохранения положения окна на рабочем столе при перезапуске Psi+ (psi-save-maximized-window-state.diff, авторы - Dealer_WeARE и KukuRuzo) * обновлён night skin (v.0.4) * обновлён stop spam plugin до версии 0.4.4 * исправлен патч psi-workaround-adhoc-form-closes (psi-workaround-adhoc-form-closes.diff) - убран патч psi-fix-hidden-group. принят в upstream-репозиторий (psi-fix-hidden-group.diff) * исправлен заголовок окна в режиме без границ (psiplus-decorate-windows.diff) * решена задача 346 (падение приложения при вызове конфигуратора конференции, http://code.google.com/p/psi-dev/issues/detail?id=346 (psi-fix-crash-in-muc-configurator.diff) + добавлен новый патч, добавляющий две новые advanced-опции приложения: options.ui.look.colors.chat.composing-color и options.ui.look.colors.chat.unread-message-color (psi-colors-for-tabs.diff) * обновлены некоторые ростерные иконпаки (iconsets/roster/blackroster.jisp, icq.jisp, nuvola-roster.jisp, oxygen-roster-22.jisp, oxygen.jisp, qipinfium.jisp, white_theme.jisp) ! обновлены системные библиотеки Qt до версии 4.7.0, подробнее -- http://qt.nokia.com/developer/changes/changes-4.7.0 -- + added license and copyright to content downloader plugin + added license and copyright to icq must die plugin * some fixes to psi-plugins-options-and-fix-plugins-loading-and-unloading.diff * get rid of fuzzes (psi-plugin-info-button.diff) * updated content downloader plugin to v0.1.5, http://psi-dev.googlecode.com/svn/trunk/plugins/generic/contentdownloaderplugin/changelog.txt + added new history from Piotrek Okonski (psi-historydlg.diff) + added mingw-w64 compatibility patch (psi-mingw-w64-compatibility.diff) * sync with upstream (psi-gcc-4.6-compatibility.diff) * updated content downloader plugin. "Download and Install" button now disabled on open plugin + added stellar1-affiliations.jisp (iconsets/affiliations/stellar1-affiliations.jisp) * updated icq must die plugin to v0.1.5 (message count) + added new patch to fix issue 357, http://code.google.com/p/psi-dev/issues/detail?id=357 (psi-workaround-adhoc-form-closes.diff) * updated nuvola-system.jisp (iconsets/system/nuvola-system.jisp) + added license and copyright for some Psi+ plugins (attention plugin, auto reply plugin, birthday reminder plugin, captcha forms plugin, chess plugin, cleaner plugin, conference logger plugin, extended options plugin, gmail notification plugin, history keeper plugin, image plugin, juick plugin, qip x-statuses plugin, screenshot plugin, skins plugin, stop spam plugin, storage notes plugin, translate plugin, video status plugin, watcher plugin) + added new patch psiplus-decorate-windows.diff by KukuRuzo * fixed restore window in borderless mode (psiplus-decorate-windows.diff) * updated skins plugin to v0.2.8 + added new patch psi-save-maximized-window-state.diff by Dealer_WeARE and KukuRuzo * updated night skin (v.0.4) * updated stop spam plugin to v0.4.4 * fixed psi-workaround-adhoc-form-closes patch (psi-workaround-adhoc-form-closes.diff) - removed psi-fix-hidden-group patch. applyed by upstream (psi-fix-hidden-group.diff) * fixed chat window header in borderless mode (psiplus-decorate-windows.diff) * fixed issue 346 (crash in groupchat configurator), http://code.google.com/p/psi-dev/issues/detail?id=346 (psi-fix-crash-in-muc-configurator.diff) + added new patch to add 2 new options 'options.ui.look.colors.chat.composing-color' and 'options.ui.look.colors.chat.unread-message-color' (psi-colors-for-tabs.diff) * updated some roster iconsets (iconsets/roster/blackroster.jisp, icq.jisp, nuvola-roster.jisp, oxygen-roster-22.jisp, oxygen.jisp, qipinfium.jisp, white_theme.jisp) ! updated Qt libs to v4.7.0, http://qt.nokia.com/developer/changes/changes-4.7.0 2010-09-19 zet v0.15.3026 Beta * исправлена ошибка отображения аватар при использовании прокси-сервера в настройках juick plugin + команда Block работает теперь и для транспортов в ростере (psi-block-contact-from-menu.diff) + добавлен новый патч для корректной выгрузки виджета перед выгрузкой плагина при завершении работы приложения (psi-remove-widget-before-removing-plugin.diff) * attention plugin - фикс текста сообщения в алерте + добавлен новый патч для корректного отображения размера виджета с настройками плагина (psi-correct-plugin-widget-size.diff) * обновлён the_orange.png (skins/universal/orange) * attention plugin: корректное отображение размера виджета с настройками плагина * qip x-statuses plugin: корректное отображение размера виджета с настройками плагина * stop spam plugin: корректное отображение размера виджета с настройками плагина * обновлена ссылка на wiki для всех плагинов Psi+ (в окне с настройками плагинов) + добавлен новый патч для совместимости с openssl v1.0 (psi-openssl-1.0-defaults-compatibility.diff) * storage notes plugin: корректное отображение размера виджета с настройками плагина * image plugin: корректное отображение размера виджета с настройками плагина * объединены патчи psi-plugins-options-and-fix-plugins-loading-and-unloading.diff, psi-remove-widget-before-removing-plugin.diff и psi-correct-plugin-widget-size.diff в один общий патч psi-plugins-options-and-fix-plugins-loading-and-unloading.diff + добавлен новый патч для исправления падения приложения при выборе смайлпака. подробнее -- http://code.google.com/p/psi-dev/issues/detail?id=348 (psi-fixed-kde-crash.diff) - патч psi-fixed-kde-crash.diff принят в официальную ветвь Psi (upstream) + добавлен новый патч для psimedia: этот приём упрощает инициализацию камеры, также работает с uvcvideo-камерами. проверено на uvcvideo, gspca-камерах (spca561 и sq905) (trunk/patches/psimedia/0030-psimedia-uvcvideo-fix.diff) * captcha forms plugin: фикс при использовании прокси-сервера + добавлен новый плагин -- content downloader plugin (v0.1.4), подробнее -- http://psi-plus.com/wiki/plugins#content_downloader_plugin ! Psi+ wiki: подготовка к переезду на http://psi-plus.com/wiki/ -- * corrected info in juickplugin.cpp, small fix to juick plugin + add block menu item for agents (psi-block-contact-from-menu.diff) + added new patch psi-remove-widget-before-removing-plugin.diff * updated attention plugin - fix translation + added new patch which make to correct plugin widget size (psi-correct-plugin-widget-size.diff) * updated the_orange.png (skins/universal/orange) * updated attention plugin: correct layout on options widget * updated qip x-statuses plugin: correct layout on options widget * updated stop spam plugin: optimized size of options widget * updated wiki url for Psi+ plugins options window + added patch for openssl-1.0 compatibility (psi-openssl-1.0-defaults-compatibility.diff) * updated storage notes plugin: correct stretch on options widget * updated image plugin: correct stretch on options widget * merged psi-plugins-options-and-fix-plugins-loading-and-unloading.diff, psi-remove-widget-before-removing-plugin.diff and psi-correct-plugin-widget-size.diff to psi-plugins-options-and-fix-plugins-loading-and-unloading.diff + added patch for fix crash Psi+ on select and apply iconsets. look at issue 348 (psi-fixed-kde-crush.diff) - psi-fixed-kde-crush.diff went to upstream + added new patch for psimedia: this hack simplifies the camera initialization to something that also works with uvcvideo cameras. verified with uvcvideo, gspca (spca561 and sq905) cameras (trunk/patches/psimedia/0030-psimedia-uvcvideo-fix.diff) * fixed captcha forms plugin + added new plugin -- content downloader plugin (v0.1.4), see at http://psi-plus.com/wiki/plugins#content_downloader_plugin ! Psi+ wiki: preparation for migration to http://psi-plus.com/wiki/ 2010-09-05 zet v0.15.2911 Beta + добавлен детект jabber-бота из семейства Talisman (psi-client-icons.diff) + добавлен детект jabber-бота JabRSS и vkontakte j2j-транспорта (psi-client-icons.diff) + добавлен новый набор иконок аффиляций "Kingdom" (iconsets/affiliations/kingdom-affiliations.jisp) * попытка фикса проверяльщика обновлений под win32. требуется тестирование на win32 при подключении через прокси-сервер (psi-dirty-check.diff) * обновлён juick plugin до версии 0.9.16 + добавлен новый патч для возможности блокировки контактов непосредственно из контекстного меню ростера. бОльшая часть кода взята из Япси. требуется тестирование (psi-block-contact-from-menu.diff) * обновлён патч для быстрой блокировки контактов: команда "Block" неактивна если сервер не поддерживает "XEP-0016: Privacy Lists", команда "Block" спрятана для контактов-приватов конференций и транспортов, команда "Block" доступна в контекстном меню на контакте с конференцией в основном ростере (psi-block-contact-from-menu.diff) * обновлён логотип Psi+ в наборах системных иконок (iconsets/system/blacksys.jisp, nuvola-system.jisp, oxygen_sys.jisp, oxygen_sys_22.jisp, qipinfium_sys.jisp, summer-system.jisp, tango_system.jisp, whitesys.jisp) + добавлено 2 новых набора иконок аффиляций (iconsets/affiliations/award-affiliations.jisp, people_of_color-affiliations.jisp) * обновлён stop spam plugin до версии 0.4.2 ! обновлены библиотеки win32 openssl до версии 0.9.8o (скомпилировано в mingw gcc из исходников http://www.openssl.org/source/openssl-0.9.8o.tar.gz) -- + added bot detection from Talisman's family (psi-client-icons.diff) + added detection of JabRSS bot, vkontakte j2j transport (psi-client-icons.diff) + added "Kingdom" affiliations iconset (iconsets/affiliations/kingdom-affiliations.jisp) * try to fix dirty check patch. test it on Windows with network connection through proxy (psi-dirty-check.diff) * updated juick plugin to v0.9.16 + added new patch for the possibility of blocking contacts from the roster contact menu. most of the code is taken from Yapsi. needs to test (psi-block-contact-from-menu.diff) * updated blocking patch: inactive block item if 'XEP-0016: Privacy Lists' not supported by server; hide block item for privates and agents; add block item for conferences (psi-block-contact-from-menu.diff) * updated Psi+ logo (iconsets/system/blacksys.jisp, nuvola-system.jisp, oxygen_sys.jisp, oxygen_sys_22.jisp, qipinfium_sys.jisp, summer-system.jisp, tango_system.jisp, whitesys.jisp) + added new affiliations iconsets (iconsets/affiliations/award-affiliations.jisp, people_of_color-affiliations.jisp) * updated stop spam plugin to v0.4.2 ! updated win32 openssl libs to v0.9.8o (compiled by mingw gcc compiler from source http://www.openssl.org/source/openssl-0.9.8o.tar.gz) 2010-08-29 zet v0.15.2862 Beta + добавлены 3 новых ростерных иконпака (iconsets/roster/Lamps_green.jisp, iconsets/roster/Lamps_blue.jisp, iconsets/roster/Lamps_red.jisp) * решена задача 330, http://code.google.com/p/psi-dev/issues/detail?id=330 (psi-add-contact-from-chat-dialog.diff) + добавлен новый ростерный иконпак (iconsets/roster/Hearts-for-iSida-bot.jisp) * исправлено падение приложения при попытке просмотра информации об иконпаке VKontakte.ru.jisp (iconsets/roster/VKontakte.ru.jisp) * теперь кнопка "add contact" отображается динамически (psi-add-contact-from-chat-dialog.diff) * фиксы некоторых иконпаков (Hearts-for-iSida-bot.jisp, VKontakte.ru.jisp) * обновлён storage notes plugin до версии 0.1.3 + добавлен новый патч, добавляющиий опцию "options.ui.tabs.mouse-doubleclick-action". опция поддерживает три значения: "hide", "close" и "detach" [psi-hide-or-close-tab-on-doubleclick.diff] + добавлен новый патч, решающий задачу 214, http://code.google.com/p/psi-dev/issues/detail?id=214 (psi-do-not-disable-execute-command-menu-item.diff) * пункт меню 'execute command' теперь доступен и для транспорта в оффлайн-состоянии (psi-do-not-disable-execute-command-menu-item.diff) + добавлен новый патч, добавляющий возможность вставки в поле ввода ника участника конференции по нажатию комбинации "leftclick+shift" на элементе в ростере конференции (спасибо taurus) [psi-insert-nick-on-shift-plus-leftclick.diff] + добавлены 2 новых ростерных иконпака (iconsets/roster/Facebook.jisp, iconsets/roster/VKontakte.jisp) * обновлён состав участников проекта Psi+ (psiplus-aboutdlg.diff) + добавлен детект jabber-клиента Imov im-client (psi-client-icons.diff) * css-стиль для меню xhtml-im (psi-send-xhtml-im.diff) + добавлен новый патч для плагинной системы (psi-extend-eventfilter.diff) * начата работа над расширением функционала плагинной системы для взаимодействия с OTR-плагином (psi-add-toolbar-button-from-plugins.diff, psi-access-to-menus-from-plugins.diff, psi-change-contacts-status-from-plugins.diff) * обновлены некоторые плагины (gmail notify plugin, juickp lugin, attention plugin, chess plugin, conference logger plugin, image plugin) + добавлен новый патч для решения задачи 32, http://code.google.com/p/psi-dev/issues/detail?id=32 (psi-remove-event-when-popup-rightclicked.diff) + добавлен новый патч для решения задачи 339, http://code.google.com/p/psi-dev/issues/detail?id=339 (psi-fix-encoding-of-command-line-arguments.diff) + добавлен новый патч для решения задачи 338, http://code.google.com/p/psi-dev/issues/detail?id=338 (psi-fix-removing-sorted-contacts.diff) * фиксы для psi-get-account-info-from-plugins.diff + добавлен новый патч, расширяющий возможности доступа к различной информации о контакте непосредственно из плагина (psi-access-to-contact-info-from-plugins.diff) * обновлён stop spam plugin до версии 0.4.0 * попытка детекта jabber-бота Talisman (psi-client-icons.diff) * обновлён orange skin, v.0.7 (skins/universal/orange/the_orange.skn) -- + added 3 new roster iconsets (iconsets/roster/Lamps_green.jisp, iconsets/roster/Lamps_blue.jisp, iconsets/roster/Lamps_red.jisp) * fixed issue 330, http://code.google.com/p/psi-dev/issues/detail?id=330 (psi-add-contact-from-chat-dialog.diff) + added new roster iconset (iconsets/roster/Hearts-for-iSida-bot.jisp) * fixed crash at view about info for VKontakte.ru.jisp under windows (iconsets/roster/VKontakte.ru.jisp) * make chatdlg's 'add contact' button visibility dynamic (psi-add-contact-from-chat-dialog.diff) * fixed some roster iconsets (Hearts-for-iSida-bot.jisp, VKontakte.ru.jisp) * updated storage notes plugin to v0.1.3 + added new patch for add option 'options.ui.tabs.mouse-doubleclick-action'. supported values are 'hide', 'close' and 'detach' (psi-hide-or-close-tab-on-doubleclick.diff) + added new patch for fix issue 214, http://code.google.com/p/psi-dev/issues/detail?id=214 (psi-do-not-disable-execute-command-menu-item.diff) * 'blank' resource for offline transports at 'execute command' menu (psi-do-not-disable-execute-command-menu-item.diff) + added new patch for insert nick to muc chatlog by leftclick+shift (thanks to taurus) [psi-insert-nick-on-shift-plus-leftclick.diff] + added 2 new roster iconsets (iconsets/roster/Facebook.jisp, iconsets/roster/VKontakte.jisp) * updated list of participants of the Psi+ project (psiplus-aboutdlg.diff) + added detection of Imov im-client (psi-client-icons.diff) * css style for xhtml-im menu (psi-send-xhtml-im.diff) + added new patch for extend plugin system (psi-extend-eventfilter.diff) * extend plugin interface for work with OTR plugin (psi-add-toolbar-button-from-plugins.diff, psi-access-to-menus-from-plugins.diff, psi-change-contacts-status-from-plugins.diff) * updated some plugins (gmail notify plugin, juick plugin, attention plugin, chess plugin, conference logger plugin, image plugin) + added new patch for fix issue 32, http://code.google.com/p/psi-dev/issues/detail?id=32 (psi-remove-event-when-popup-rightclicked.diff) + added new patch for fix issue 339, http://code.google.com/p/psi-dev/issues/detail?id=339 (psi-fix-encoding-of-command-line-arguments.diff) + added new patch for fix issue 338, http://code.google.com/p/psi-dev/issues/detail?id=338 (psi-fix-removing-sorted-contacts.diff) * some fixes for psi-get-account-info-from-plugins.diff + added new patch for access to different info about contact from plugins (psi-access-to-contact-info-from-plugins.diff) * updated stop spam plugin to v0.4.0 * try to detect Talisman bot (psi-client-icons.diff) * updated orange skin, v.0.7 (skins/universal/orange/the_orange.skn) 2010-08-15 zet v0.15.2822 Beta * мелкое исправление для psi-small-tooltip-fix.diff (спасибо Hugin) * возможный фикс для сборки Psi+ под Haiku OS (psi-tray-act-bring-to-front.diff) * сохранение статуса при работе с формой "Set status" (psi-presets-in-status-menu.diff) + добавлена возможность опциональной перезаписи последнего выбранного статуса простым статусом и шаблоном (psi-presets-in-status-menu.diff) + добавлена команда Reconnect в статусные меню (psi-presets-in-status-menu.diff) + добавлена возможность опционального отображение приоритета jabber-клиента собеседника в информационном сообщении в чате (psi-presets-in-status-menu.diff) * теперь сообщение "Logged out" может быть локализовано (psi-presets-in-status-menu.diff) + добавлен новый патч для поддержки xmpp uri (psi-unnamed-xmpp-uri-param.diff) * исправлен баг с отображением приоритета для оффлайн-статуса в чате (psi-presets-in-status-menu.diff) + добавлена возможность отображения статусного сообщения и приоритета для вновь присоединившихся пользователей в общем чате конференции (psi-presets-in-status-menu.diff) * корректное отображение ширины полей ввода значений приоритетов для различных статусов (psi-presets-in-status-menu.diff) + добавлена опция "Restore last status" в менеджер аккаунта (psi-presets-in-status-menu.diff) + добавлена опция "options.ui.muc.show-initial-joins" для вкл/выкл отображения информационных сообщений в общем чате при входе в конференцию (psi-presets-in-status-menu.diff) * фикс для приоритета по умолчанию (psi-presets-in-status-menu.diff) * обновлена иконка для кнопки выбора формата текста в центральном тулбаре (iconsets/system/default/psiplus/text.png) * обновлён watcher plugin до версии 0.3.3, http://psi-plus.com/wiki/plugins#watcher_plugin + добавлен патч для добавления нового контакта непосредственно из окна чата или приватного окна конференции (psi-add-contact-from-chat-dialog.diff) * кнопка добавления нового контакта смещена в крайнее правое положение на центральном тулбаре (psi-add-contact-from-chat-dialog.diff) * наведён порядок с иконпаками смайлов: + добавлен iconsets/emoticons/kolobok_pidgin.jisp + добавлен iconsets/emoticons/kolobok_qip_dark.jisp + добавлен iconsets/emoticons/tasha_bombus.jisp * обновлён iconsets/emoticons/bombus2psi.jisp * обновлён iconsets/emoticons/GTalk-smiles.jisp * обновлён iconsets/emoticons/yaemomidget.jisp - удалён iconsets/emoticons/kolobok.jisp - удалён iconsets/emoticons/kolobok_dark.jisp - удалён iconsets/emoticons/kolobok_light.jisp - удалён iconsets/emoticons/lk_35x35x138_tasha.jisp - удалён iconsets/emoticons/puzazBox.jisp - удалён iconsets/emoticons/qip_bigpack.jisp - удалён iconsets/emoticons/quip.jisp - удалён iconsets/emoticons/tasha_18x18x93.jisp * обновлён ростерный иконпак bctango_roster.jisp (iconsets/roster/bctango_roster.jisp) + добавлен детект jabber-клиента Jappix (psi-client-icons.diff) * обновлены иконпаки fingerprint с иконками jabber-клиентов (iconsets/clients/fingerprint.jisp, iconsets/clients/fingerprint-22.jisp) * хотфикс для fingerprint-22.jisp (iconsets/clients/fingerprint-22.jisp) * небольшой фикс для кнопки добавления нового контакта из приватного окна конференции (psi-add-contact-from-chat-dialog.diff) ! обновлена русская локализация от ivan101 (psi-ru svn, r181, 2010-08-09) -- * updated psi-small-tooltip-fix.diff (thx to Hugin) * possible fix for building on Haiku OS (psi-tray-act-bring-to-front.diff) * also save status on "Set status" dialog accept (psi-presets-in-status-menu.diff) + added optional last status overwriting by simple status and template (psi-presets-in-status-menu.diff) + added Reconnect action (psi-presets-in-status-menu.diff) + optional display priority in chat log (psi-presets-in-status-menu.diff) * translatable "Logged out" status message (psi-presets-in-status-menu.diff) + added patch to handle xmpp uri commandline param w/o param name (psi-unnamed-xmpp-uri-param.diff) * fixed bug with offline status priority in chat log (psi-presets-in-status-menu.diff) + also show status and priority in chat log when someone joins MUC (psi-presets-in-status-menu.diff) * fixed wrong default priority options width (psi-presets-in-status-menu.diff) + added account option "Restore last status" (psi-presets-in-status-menu.diff) + added hidden option "options.ui.muc.show-initial-joins" (psi-presets-in-status-menu.diff) * fixed regression with default priority (psi-presets-in-status-menu.diff) * updated format text icon (iconsets/system/default/psiplus/text.png) * updated watcher plugin to v0.3.3, http://psi-plus.com/wiki/plugins#watcher_plugin + added patch to add contacts from chat dialog (psi-add-contact-from-chat-dialog.diff) * moved add contact icon to the right (psi-add-contact-from-chat-dialog.diff) * ordering for emoticons iconsets: + added iconsets/emoticons/kolobok_pidgin.jisp + added iconsets/emoticons/kolobok_qip_dark.jisp + added iconsets/emoticons/tasha_bombus.jisp * modified iconsets/emoticons/bombus2psi.jisp * modified iconsets/emoticons/GTalk-smiles.jisp * modified iconsets/emoticons/yaemomidget.jisp - deleted iconsets/emoticons/kolobok.jisp - deleted iconsets/emoticons/kolobok_dark.jisp - deleted iconsets/emoticons/kolobok_light.jisp - deleted iconsets/emoticons/lk_35x35x138_tasha.jisp - deleted iconsets/emoticons/puzazBox.jisp - deleted iconsets/emoticons/qip_bigpack.jisp - deleted iconsets/emoticons/quip.jisp - deleted iconsets/emoticons/tasha_18x18x93.jisp * updated bctango_roster.jisp (iconsets/roster/bctango_roster.jisp) + added detection of Jappix client (psi-client-icons.diff) * updated fingeprint packs (iconsets/clients/fingerprint.jisp, iconsets/clients/fingerprint-22.jisp) * fixed fingerprint-22.jisp (iconsets/clients/fingerprint-22.jisp) * small fix for add button for muc contacts (psi-add-contact-from-chat-dialog.diff) ! updated russian localization by ivan101 (psi-ru svn, r181, 2010-08-09) 2010-08-08 zet v0.15.2801 Beta * обновлена краткая справка по некоторым плагинам (captchaformsplugin, videostatusplugin, watcherplugin) + добавлен новый патч для корректного отображения различных форм для команд удалённого управления, управления транспортами, а также поисковых форм (psi-improve-adhoc-forms.diff) + добавлен новый патч для вызова формы ввода нового (правильного) пароля аккаунта непосредственно на этапе установления соединения [psi-change-password-dialog.diff] (thanks to Virnik) * более корректное отображение поисковых форм (psi-improve-adhoc-forms.diff) + добавлен новый патч для исправления отображения вертикального скролла в ростере конференции. теперь скролл не закрывает иконки клиентов и аффиляций [psi-fix-muc-roster-sizing.diff] (by taurus) * изменена комбинация горячих клавиш по умолчанию для действия по вызову быстрых команд для конференций и чатов. теперь по умолчанию используется сочетание "ctrl+space" (ранее было "ctrl+7") [psi-default-application-settings.diff] * более корректное отображение форм ad-hoc (psi-improve-adhoc-forms.diff) + добавлен новый патч для быстрого вызова конференций из закладок в форме присоединения к конференции (psi-add-bookmarks-to-join-conference-dlg.diff) -- * updated plugins info (captchaformsplugin, videostatusplugin, watcherplugin) + added new patch psi-improve-adhoc-forms.diff + added new patch psi-change-password-dialog.diff (thanks to Virnik) * updated search forms (psi-improve-adhoc-forms.diff) + added new patch psi-fix-muc-roster-sizing.diff (by taurus) * changed default shortcut for command mode to ctrl+space (psi-default-application-settings.diff) * updated ad-hoc forms (psi-improve-adhoc-forms.diff) + added new patch for bookmarks to join conference dialog (psi-add-bookmarks-to-join-conference-dlg.diff) * removed some garbage from psi-default-application-settings.diff * updated psi-add-bookmarks-to-join-conference-dlg.diff 2010-08-01 zet v0.15.2780 Beta * очистка мусора в medals-affiliations.jisp (iconsets/affiliations/medals-affiliations.jisp) * фикс форматирования текста во всплывающей подсказке над элементом ростера (psi-small-tooltip-fix.diff) * обновлён video status plugin до версии 0.0.4 (работает только в ОС семейства Linux) * обновлены все официальные скины приложения (skins/default_skin.skn, skins/universal/black_theme/black_theme.skn, skins/universal/night/night.skn, skins/universal/orange/the_orange.skn, skins/universal/qip_infium/qip_infium.skn, skins/universal/sky/sky.skn, skins/universal/tkabber/tkabber.skn) * обновлён watcher plugin до версии 0.3.1 * updated screenshot plugin до версии 0.4.2 * фикс для psi-small-tooltip-fix.diff (спасибо Hugin) + добавлен новый патч для поддержки отправки xhtml-im-сообщений (psi-send-xhtml-im.diff) * фиксы рендеринга при использовании xhtml-im-сообщений, исправлено отображение команды "/me" в xhtml-im-сообщениях (psi-send-xhtml-im.diff) + добавлен новый смайлпак свежей версии Mail.Ru Agent (сконвертированы tux-den) [iconsets/emoticons/mra-emoticons-5.7.3639.jisp] ! обновлена русская локализация от ivan101 (psi-ru svn, r179, 2010-07-27) -- ! Если Вы хотите поддержать развитие проекта Psi+, то можете внести небольшое пожертвование на один из нижеприведённых счетов: http://passport.webmoney.ru/asp/certview.asp?wmid=351281428776 WMZ: Z217989559723 WMR: R347278107127 WME: E196199593868 Яндекс.Деньги: 41001348342725 -- * cleanup for medals-affiliations.jisp (iconsets/affiliations/medals-affiliations.jisp) * updated contact tooltip (psi-small-tooltip-fix.diff) * updated video status plugin to v0.0.4 (works for Linux only) * updated all skins (skins/default_skin.skn, skins/universal/black_theme/black_theme.skn, skins/universal/night/night.skn, skins/universal/orange/the_orange.skn, skins/universal/qip_infium/qip_infium.skn, skins/universal/sky/sky.skn, skins/universal/tkabber/tkabber.skn) * updated watcher plugin to v0.3.1 * updated screenshot plugin to v0.4.2 * fixed signed presence (thx to Hugin) [psi-small-tooltip-fix.diff] + added new patch for ability to send xhtml-im messages (psi-send-xhtml-im.diff) * fixed xhtml-im rendering, fixed /me command in xhtml-im messages (psi-send-xhtml-im.diff) + added new mra-emoticons-5.7.3639.jisp (converted by tux-den) [iconsets/emoticons/mra-emoticons-5.7.3639.jisp] ! updated russian localization by ivan101 (psi-ru svn, r179, 2010-07-27) -- ! If you want to support the development of the Psi+ Project, you can make a small donation to one of the following accounts: http://passport.webmoney.ru/asp/certview.asp?wmid=351281428776 WMZ: Z217989559723 WMR: R347278107127 WME: E196199593868 Yandex.Money: 41001348342725 2010-07-25 zet v0.15.2750 Beta * обновлены иконпаки активностей, v.0.3 (psiplus-activities-16.jisp, psiplus-activities-22.jisp) * обновлён скрипт сборки psibuild.sh. теперь работает и для FreeBSD, спасибо Kibab (scripts/posix/psibuild.sh) * фикс для корректного детекта контактов сервера vk.com (psi-add-standard-transports.diff) * обновлён extended options plugin до версии 0.2.9 + добавлен новый патч для корректного отображения всплывающей подсказки над контактами ростера (small-tooltip-fix.diff) + обновлён chess plugin до версии 0.1.6 + добавлен новый иконпак с иконками аффиляций (iconsets/affiliations/psiplus-affiliations.jisp) * исправлен вывод информации о размере иконок (iconsets/clients/fingerprint.jisp) * фикс для стиля контекстного меню (psi-css-style-sheet.diff) + добавлен новый патч для возможности создания событий плагинами и передачи таких событий в приложение (psi-create-events-from-plugins.diff) * исправлен патч psi-fix-hidden-group.diff * исправлено отображение информации о PGP во всплывающей подсказке над контактами ростера (psi-small-tooltip-fix.diff) * обновлён screenshot plugin до версии 0.4.0 + добавлен новый патч для скрытия неработающего show-away действия (psi-hide-show-away-action.diff) * обновлён патч psi-get-account-info-from-plugins.diff * обновлён video status changer plugin to v0.0.3 (*nix only) + добавлен новый плагин для обработки капчи captcha forms plugin (v0.0.4), подробнее -- http://psi-plus.com/wiki/plugins#captcha_forms_plugin + добавлен скрипт для массовой сборки плагинов Psi+, спасибо KukuRuzo (scripts/posix/compileallplugins.py) + добавлена поддержка дефолтной локали системы (psi-options-language-select.diff) * обновлён патч psi-presets-in-status-menu.diff + добавлен новый патч для поддержки горячей кнопки на сворачивание активного таба/окна (psi-minimize-chat-hotkey.diff) * исправлен патч psi-tray-act-bring-to-front.diff * актуализация информации об участниках проекта (psiplus-aboutdlg.diff) -- * updated activities iconsets, v.0.3 (psiplus-activities-16.jisp, psiplus-activities-22.jisp) * improved psibuild.sh, it now works for FreeBSD too, thx Kibab (scripts/posix/psibuild.sh) * updated vkontakte regexp (psi-add-standard-transports.diff) * updated extended options plugin to v0.2.9 + added new patch psi-small-tooltip-fix.diff + updated chess plugin to v0.1.6 + added psi-plus affilations iconset (iconsets/affiliations/psiplus-affiliations.jisp) * fixed fingerprint.jisp (iconsets/clients/fingerprint.jisp) * fixed submenu style (psi-css-style-sheet.diff) + added new patch for creating events from plugins (psi-create-events-from-plugins.diff) * fixed psi-fix-hidden-group.diff * fixed show PGP in tooltip (psi-small-tooltip-fix.diff) * 0920 moved into 1420 (psi-small-tooltip-fix.diff) * updated screenshot plugin to v0.4.0 + added new patch for hiding useless show-away action (psi-hide-show-away-action.diff) * updated psi-get-account-info-from-plugins.diff * updated video status changer plugin to v0.0.3 + added new plugin - captcha forms plugin (v0.0.4), http://psi-plus.com/wiki/plugins#captcha_forms_plugin + added compile_all_plugins script by KukuRuzo (scripts/posix/compileallplugins.py) + added default lang, depends of locale os (psi-options-language-select.diff) * updated psi-presets-in-status-menu.diff + added new patch psi-minimize-chat-hotkey.diff * fixed hotkey "show/hide the application" on windows (hiding was broken) (psi-tray-act-bring-to-front.diff) * updated project members info (psiplus-aboutdlg.diff) 2010-07-18 zet v0.15.2690 Beta + добавлен новый патч для корректного отображения заголовков и разделителей групп в ростере (psi-fix-roster-accounts-and-groups-drawing.diff) * обновлён screenshot plugin до версии 0.3.9 * обновлены системные иконпаки (blacksys.jisp, nuvola-system.jisp, oxygen_sys.jisp, oxygen_sys_22.jisp, whitesys.jisp) + добавлен новый патч для скрытия неактивного элемента "доска для рисования" в меню контакта (psi-fix-submenu-hide.diff) * фикс для скрытия неактивных элементов в подменю контакта (psi-access-to-menus-from-plugins.diff, psi-fix-submenu-hide.diff) + добавлен новый иконпак с иконками jabber-клиентов размером 22x22 (iconsets/clients/fingerprint-22.jisp) + добавлен новый патч для исправления сортировки контактов в ростере (по статусу/по алфавиту) (psi-fix-contact-sorting-style-option.diff) * обновлён extended options plugin до версии 0.2.8 * исправлен перенос неактивных окон контакт-менеджера и редактора списков приватности на передний план при повторном их вызове (psi-account-menu-privacy-item.diff, psi-contact-manager.diff) + добавлен новый патч для исправления работы с группой Hidden в ростере (psi-fix-hidden-group.diff) + добавлена утилита для восстановления пароля от аккаунта по информации из %userprofile%\PsiData\accounts.xml. запускать утилиту можно непосредственно отсюда: http://psi-dev.googlecode.com/svn/trunk/scripts/passwordrecovery.html + добавлен новый патч для корректной работы с элементами ростера в режиме одиночного клика мышью (psi-fix-use-single-click.diff) * обновлены настройки приложения по умолчанию (psi-default-application-settings.diff, psi-add-options-color-highlighting.diff, psi-add-standard-transports.diff) + добавлен новый патч для скрытия неработающих опций ростера (psi-tmp-hide-useless-roster-options.diff) * исправлен regexp для корректного отображения иконок контактов сервиса vkontakte (psi-add-standard-transports.diff) + добавлено определение некоторых jabber-ботов: jame, pako bot, fatal-bot, storm, sulci, sleekbot, neutrina, yamaneko, talisman (psi-client-icons.diff) * обновлён иконпак с иконками jabber-клиентов (fingerprint.jisp, v0.2.10) -- + added new patch for fix options outline headings and slim roster group headings (psi-fix-roster-accounts-and-groups-drawing.diff) * updated screenshot plugin to v0.3.9 * updated system iconsets (blacksys.jisp, nuvola-system.jisp, oxygen_sys.jisp, oxygen_sys_22.jisp, whitesys.jisp) + added new patch for fix disabling whiteboard menu item at contact menu (psi-fix-submenu-hide.diff) * fixed submenu hide (psi-access-to-menus-from-plugins.diff, psi-fix-submenu-hide.diff) * updated psi-client-icons.diff, fingerprint.jisp + added icons clients size 22x22 (iconsets/clients/fingerprint-22.jisp) + added new patch for fix roster contact sorting option (psi-fix-contact-sorting-style-option.diff) * updated extended options plugin to v0.2.8 * fixed bring to front for contact manager and privacy dlg (psi-account-menu-privacy-item.diff, psi-contact-manager.diff) + added new patch for fix roster hidden group (psi-fix-hidden-group.diff) + added password recovery utility by rion, http://psi-dev.googlecode.com/svn/trunk/scripts/passwordrecovery.html * fixed psi-fix-contact-sorting-style-option.diff + added new patch for fix activation roster items on single click (psi-fix-use-single-click.diff) * updated default options (psi-default-application-settings.diff, psi-add-options-color-highlighting.diff, psi-add-standard-transports.diff) + added new patch for hide autoresize and left-button-menu options (psi-tmp-hide-useless-roster-options.diff) * fixed vkontakte regexp (psi-add-standard-transports.diff) + added bots detection: jame, pako bot, fatal-bot, storm, sulci, sleekbot, neutrina, yamaneko, talisman (psi-client-icons.diff) 2010-07-11 zet v0.15.2664 Beta -- Известные проблемы (после r1784): 1. в ростере не работает группа hidden (баг upstream-версии) -- Changelog: * исправлено сворачивание окна конференции в режиме без табов (psi-muc-minimize-to-roster.diff) + добавлена опция "сворачивать конференцию при нажатии на кнопку закрытия" (psi-muc-minimize-to-roster.diff) ! переписана бОльшая часть кода плагина screenshot plugin (v0.3.0), подробнее - http://psi-plus.com/wiki/plugins#screenshot_plugin - убран ненужный более патч psi-save-last-priority-in-statusdlg.diff (данный код теперь используется в патче psi-presets-in-status-menu.diff) + добавлена wiki-страница по сборке Psi+ под Meego (en, ru) * обновлён skins plugin до версии v0.2.6 * обновлён патч psi-presets-in-status-menu.diff (by Dmitriy.trt), подробнее - http://code.google.com/p/psi-dev/issues/detail?id=284 + добавлен новый патч psi-fix-adhoc-set-status.diff, решена задача is305 ( http://code.google.com/p/psi-dev/issues/detail?id=305 ) + добавлены новые иконки для screenshot plugin (psi-iconsets.diff) * обновлены некоторые системные иконпаки (blacksys.jisp, nuvola-system.jisp, oxygen_sys.jisp, oxygen_sys_22.jisp, whitesys.jisp) * обновлён screenshot plugin до версии 0.3.6 + добавлен новый патч psi-fix-empty-name-for-bookmark.diff, решена задача is45 ( http://code.google.com/p/psi-dev/issues/detail?id=45 ) -- Known issues (after r1784): 1. hidden roster group does not work (upstream version bug) -- Changelog: * fixed hiding muc for non tabbed mode (psi-muc-minimize-to-roster.diff) + added hide when closing window options in ui (psi-muc-minimize-to-roster.diff) ! rewritten screenshot plugin (v0.3.0), http://psi-plus.com/wiki/plugins#screenshot_plugin - removed useless patch psi-save-last-priority-in-statusdlg.diff (used in psi-presets-in-status-menu.diff) + wiki: added Meego's builds wiki page (en, ru) * updated skins plugin to v0.2.6 * updated patch psi-presets-in-status-menu.diff (by Dmitriy.trt), http://code.google.com/p/psi-dev/issues/detail?id=284 + added new patch psi-fix-adhoc-set-status.diff, fixed is305 ( http://code.google.com/p/psi-dev/issues/detail?id=305 ) + added some icons for screenshot plugin (psi-iconsets.diff) * updated some system iconsets (blacksys.jisp, nuvola-system.jisp, oxygen_sys.jisp, oxygen_sys_22.jisp, whitesys.jisp) * updated screenshot plugin to v0.3.6 + added new patch psi-fix-empty-name-for-bookmark.diff, fixed is45 ( http://code.google.com/p/psi-dev/issues/detail?id=45 ) 2010-06-27 zet v0.15.2600 Beta -- Известные проблемы (после r1784): 1. в ростере не работает группа hidden (баг upstream-версии) -- Changelog: + добавлена возможность вывести на панель инструментов ростера кнопки для установки глобальных настроений, активностей и геолокаций (psi-pep-toolbar-buttons.diff) * фикс для psi-reset-not-complete-connection-on-status-change.diff * обновлён скрипт инсталлятора psiplus-install.nsi (теперь не удаляются пользовательские ресурсы из папки Psi+) * обновлён патч psi-tray-act-bring-to-front.diff (by Dmitriy.trt) * фиксы для for psi-roster-avatar-frame.diff * установлен отступ в 1 px для фрейма с аватаром в ростере (psi-roster-avatar-frame.diff) * скорректирована информация в диалоге About Psi+ (psiplus-aboutdlg.diff) * обновлён blue skin (skins/universal/blue_skin/blue_skin.skn) * изменены расстояния и отступы для фрейма с аватаром в ростере (psi-roster-avatar-frame.diff) * обновлён копирайт для иконпаков qip (iconsets/roster/qipinfium.jisp, iconsets/system/qipinfium_sys.jisp) * заданы умолчания в css для фрейма с аватаром в ростере (psi-css-style-sheet.diff) * исправлен шрифт ника для фрейма с аватаром в ростере (psi-roster-avatar-frame.diff) * исправлен шрифт статусного сообщения для фрейма с аватаром в ростере (psi-roster-avatar-frame.diff) + добавлена опция для возможности изменения размера собственного аватара в ростере (psi-roster-avatar-frame.diff) + добавлена опция для возможности сворачивания окна с конференцией в ростер при нажатии на кнопку закрытия окна (psi-muc-minimize-to-roster.diff) * исправлен дубль pep-диалога (psi-pep-toolbar-buttons.diff) * обновлён orange skin, v0.6.2(skins/universal/orange/the_orange.skn) * обновлены некоторые скины (skins/default_skin.skn, skins/universal/black_theme/black_theme.skn, skins/universal/orange/the_orange.skn, skins/universal/qip_infium/qip_infium.skn, skins/universal/sky/sky.skn, skins/universal/tkabber/tkabber.skn) * попытка решить задачу is310, http://code.google.com/p/psi-dev/issues/detail?id=310 (psi-options-language-select.diff) * фикс счётчика непрочитанных сообщений в заголовке окна/таба (psi-muc-minimize-to-roster.diff) + добавлен опциональный размер отступа для фрейма с аватаром в ростере (psi-roster-avatar-frame.diff) * обновлён skins plugin до версии 0.2.5 * обновлён brushed metal skin (skins/mac/brushed_metal/brushed_metal.skn) + добавлен новый скин Night, v0.2 (skins/universal/night/night.skn) -- Known issues (after r1784): 1. hidden roster group does not work (upstream version bug) -- Changelog: + added new patch: ability to add toolbar buttons for set the roster of global moods, activity and geolocation (psi-pep-toolbar-buttons.diff) * fixed psi-reset-not-complete-connection-on-status-change.diff * updated installer script psiplus-install.nsi (softer mode for psi+ resources) * update tray bring to front patch by Dmitriy.trt (psi-tray-act-bring-to-front.diff) * fix for psi-roster-avatar-frame.diff * set margin to 1 for roster avatar frame (psi-roster-avatar-frame.diff) * corrected about Psi+ info (psiplus-aboutdlg.diff) * updated blue skin (skins/universal/blue_skin/blue_skin.skn) * fix for mac (psi-tray-act-bring-to-front.diff) * changed margin and spacing in avatar's frame (psi-roster-avatar-frame.diff) * fixed copyright for qip icons (iconsets/roster/qipinfium.jisp, iconsets/system/qipinfium_sys.jisp) * set default css settings for avatar's frame (psi-css-style-sheet.diff) * fixed nick font (psi-roster-avatar-frame.diff) * fixed status message font (psi-roster-avatar-frame.diff) + added options for change avatar size (psi-roster-avatar-frame.diff) * small fix frame size policy (psi-roster-avatar-frame.diff) + added options hide conference when closing tabs (psi-muc-minimize-to-roster.diff) * fixed duplicate pep dialogs (psi-pep-toolbar-buttons.diff) * fixed size for avatar label (psi-roster-avatar-frame.diff) * updated orange skin, v0.6.2(skins/universal/orange/the_orange.skn) * major update for skins (skins/default_skin.skn, skins/universal/black_theme/black_theme.skn, skins/universal/orange/the_orange.skn, skins/universal/qip_infium/qip_infium.skn, skins/universal/sky/sky.skn, skins/universal/tkabber/tkabber.skn) * try to fix is310, http://code.google.com/p/psi-dev/issues/detail?id=310 (psi-options-language-select.diff) * fixed counter unread messages in window title (psi-muc-minimize-to-roster.diff) + added optional margin for avatar frame (psi-roster-avatar-frame.diff) * updated skins plugin to v.0.2.5 * updated brushed metal skin (skins/mac/brushed_metal/brushed_metal.skn) + added new skin Night, v0.2 (skins/universal/night/night.skn) 2010-06-15 zet v0.15.2555 Beta -- Известные проблемы (после r1784): 1. в ростере не работает группа hidden (баг upstream-версии) -- Changelog: * обновлён ростерный иконпак stafex_psi_mod.jisp (iconsets/roster/stafex_psi_mod.jisp) * изменены некоторые иконки в форме просмотра истории переписки (psi-improve-historydlg.diff) * правый клик мыши на статусных меню и другие улучшения для статусных шаблонов (автор - Dmitriy.trt) [psi-presets-in-status-menu.diff] + добавлены новые ростерные иконпаки из коллекции majik (archlinux.jisp, boss.jisp, debian.jisp, dictionary.jisp, email.jisp, gentoo.jisp, hearts.jisp, hoodats.jisp, linux.jisp, maj-mail3.jisp, nuvola-smtp.jisp, oxygen-roster-22.jisp, rss-classic.jisp, slackware.jisp, stellar-crystalized-roster.jisp, suse.jisp) + добавлен новый системный иконпак tango_system.jisp (iconsets/system/tango_system.jisp) * обновлены некоторые ростерные иконпаки (iconsets/roster/stellar.jisp, iconsets/roster/tkabber.jisp) - убраны ненужные ростерные иконпаки (iconsets/roster/dictionary_es.jisp, iconsets/roster/lightbulb.jisp) * фиксы и улучшения для формы просмотра истории переписки (psi-improve-historydlg.diff) + добавлен новый патч для установки статуса непосредственно из плагина (psi-set-account-status-from-plugins.diff) * обновлён системный иконпак 'oxygen system size 22px' (iconsets/system/oxygen_sys_22.jisp) + добавлены новые иконпаки настроений с иконками различных размеров + добавлена новая опция в настройки приложения для выбора affiliations-иконпака (psi-muc-roster-icons.diff) + добавлены новые affiliations-иконпаки (adium-affiliations.jisp, black-affiliations.jisp, oxygen-affiliations-22.jisp, oxygen-affiliations.jisp, qipinfium-affiliations.jisp, white-affiliations.jisp) + добавлены новые affiliations-иконпаки, часть 2 (balls-affiliations.jisp, balls-medium-affiliations.jisp, balls-small-affiliations.jisp, medals-affiliations.jisp, planets-affiliations.jisp, smileys-affiliations.jisp) * обновлён orange skin, v.0.6.1 (skins/universal/orange/the_orange.skn) * поправлены системные иконпаки (blacksys.jisp, nuvola-system.jisp, oxygen_sys.jisp, oxygen_sys_22.jisp, qipinfium_sys.jisp, summer-system.jisp, whitesys.jisp) + добавлен новый патч для сохранения последнего выставленного приоритета в диалоге установки статуса (psi-save-last-priority-in-statusdlg.diff) + добавлен новый патч для корректного сброса неустановившегося соединения при ручной смене статуса (psi-reset-not-complete-connection-on-status-change.diff) + добавлены новые affiliations-иконпаки, часть 3 (bombus-nostalgic-affiliations.jisp, tango-emotes-affiliations.jisp, vista-halloween-affiliations.jisp) + добавлен новый ростерный иконпак graphite.jisp (iconsets/roster/graphite.jisp) * улучшены параметры значений по умолчанию для macosx (psi-default-application-settings.diff) ! начата работа над немецкой локализацией wiki-страниц (спасибо Torsten) ! теперь для работы с инсталлятором Psi+ используется NSIS, http://nsis.sourceforge.net/ + добавлен скрипт для инсталлятора Psi+ (psiplus-install.nsi) ! обновлены системные библиотеки Qt до версии 4.6.3, http://qt.nokia.com/developer/changes/changes-4.6.3 ! обновлены библиотеки Psimedia до версии 1.0.3.737, http://delta.affinix.com/svn/trunk/psimedia/ ! обновлён GStreamer до версии 0.10.28 -- Known issues (after r1784): 1. hidden roster group does not work (upstream version bug) -- Changelog: * updated stafex_psi_mod.jisp (iconsets/roster/stafex_psi_mod.jisp) * changed some icons for psi-improve-historydlg.diff * right click mouse on statuses menus and other improvements (by Dmitriy.trt) [psi-presets-in-status-menu.diff] + added some roster iconsets from majik collection (archlinux.jisp, boss.jisp, debian.jisp, dictionary.jisp, email.jisp, gentoo.jisp, hearts.jisp, hoodats.jisp, linux.jisp, maj-mail3.jisp, nuvola-smtp.jisp, oxygen-roster-22.jisp, rss-classic.jisp, slackware.jisp, stellar-crystalized-roster.jisp, suse.jisp) + added tango_system.jisp (iconsets/system/tango_system.jisp) * updated some roster iconsets (iconsets/roster/stellar.jisp, iconsets/roster/tkabber.jisp) - removed useless roster iconsets (iconsets/roster/dictionary_es.jisp, iconsets/roster/lightbulb.jisp) * some code cleanings and fixes for psi-improve-historydlg.diff + added new patch for set account status from plugin (psi-set-account-status-from-plugins.diff) * updated system iconset 'oxygen system size 22px' (iconsets/system/oxygen_sys_22.jisp) + added some sized moods iconsets + added options for change affiliations iconsets (psi-muc-roster-icons.diff) + added affiliations iconsets (adium-affiliations.jisp, black-affiliations.jisp, oxygen-affiliations-22.jisp, oxygen-affiliations.jisp, qipinfium-affiliations.jisp, white-affiliations.jisp) + added more affiliations iconsets (balls-affiliations.jisp, balls-medium-affiliations.jisp, balls-small-affiliations.jisp, medals-affiliations.jisp, planets-affiliations.jisp, smileys-affiliations.jisp) * updated orange skin, v.0.6.1 (skins/universal/orange/the_orange.skn) * fixed system iconsets (blacksys.jisp, nuvola-system.jisp, oxygen_sys.jisp, oxygen_sys_22.jisp, qipinfium_sys.jisp, summer-system.jisp, whitesys.jisp) + added new patch: save last priority in status dialog (psi-save-last-priority-in-statusdlg.diff) + added new patch: reset not complete connection on status change (psi-reset-not-complete-connection-on-status-change.diff) + added Bombus nostalgic and other affiliations iconsets (bombus-nostalgic-affiliations.jisp, tango-emotes-affiliations.jisp, vista-halloween-affiliations.jisp) + added new roster iconset graphite.jisp * some changes for default options on macosx (psi-default-application-settings.diff) ! started german localization (thanks to Torsten) ! now we use NSIS for Psi+ installers, http://nsis.sourceforge.net/ + added installation script (psiplus-install.nsi) ! updated Qt libs to v4.6.3, http://qt.nokia.com/developer/changes/changes-4.6.3 ! updated Psimedia libs to v1.0.3.737, http://delta.affinix.com/svn/trunk/psimedia/ ! updated GStreamer to v0.10.28 2010-06-06 zet v0.15.2514 Beta -- Известные проблемы (после r1784): 1. не работает группа hidden (баг upstream-версии) -- Changelog: * обновлён brushed metal skin, v.0.2.1 (skins/mac/brushed_metal/brushed_metal.skn) * обновлён chess plugin до версии 0.1.5 * обновлён icq must die plugin до версии 0.1.3 * фикс по задаче 301, http://code.google.com/p/psi-dev/issues/detail?id=301 (psi-typed-history.diff) + добавлен детект jabber-клиента Swift-IM (psi-client-icons.diff) + добавлен новый патч, добавляющий вызов шаблонов в статусном меню (патч от Dmitriy.trt, http://code.google.com/p/psi-dev/issues/detail?id=284) [psi-presets-in-status-menu.diff] * если расцветка ников в конференции выключена, то не используется и раскраска по hash (psi-muc-nick-hash-color.diff) + добавлен детект jabber-клиента Bombus Avalon (psi-client-icons.diff) + добавлена новая опция options.ui.muc.roster-nick-coloring на включение/выключение раскраски ников в ростере конференции psi-muc-roster-icons.diff * обновлён extended options plugin до версии 0.2.7 + добавлен новый патч psi-tray-act-bring-to-front.diff (патч от Dmitriy.trt, http://code.google.com/p/psi-dev/issues/detail?id=300) * фиксы дефолтных цветов ников в ростере конференции (psi-muc-roster-icons.diff) + добавлен новый ростерный иконпак stafex_psi_mod.jisp (авторы - Dragonizer и majik) [iconsets/roster/stafex_psi_mod.jisp] * величина радиуса углов собственного аватара в ростере теперь зависит от соответствующей опции в настройках приложения (psi-roster-avatar-frame.diff) + добавлена новая опция для включения/отключения отображения кнопки отправки сообщения (psi-send-button-arrow.diff) * небольшая оптимизация для патча psi-modern-roster.diff + добвлен новый патч на поддержку ростерных/системных иконок различного размера (экспериментально) (psi-custom-icons-size.diff) + добавлен новый патч на добавление меню переключения языка приложения непосредственно из настроек Psi+ (psi-options-language-select.diff) * откат к предыдущей версии диалога с историей сообщений (psi-improve-historydlg.diff, r2290) * обновлён skins plugin до версии 0.2.3 * исправления и оптимизация activities-иконпака (iconsets/activities/psiplus-activities-16.jisp) + добавлен новый activities-иконпак (iconsets/activities/rederick-activities-16.jisp) + добавлен новый иконпак настроений rederick-moods-16.jisp (новая версия иконпака gajim1108.jisp) * обновлён watcher plugin до версии 0.2.7 * небольшое обновление русской локализации от ivan101 (psi-ru svn, r174, 2010-05-27) -- Known issues (after r1784): 1. hidden group does not work (upstream version bug) -- Changelog: * updated brushed metal skin, v.0.2.1 (skins/mac/brushed_metal/brushed_metal.skn) * updated chess plugin to v0.1.5 * updated icq must die plugin to v0.1.3 * try to fix issue 301, http://code.google.com/p/psi-dev/issues/detail?id=301 (psi-typed-history.diff) + added detection Swift-IM jabber client (psi-client-icons.diff) + added new patch: presets in status menu (patch from Dmitriy.trt, http://code.google.com/p/psi-dev/issues/detail?id=284) [psi-presets-in-status-menu.diff] * if nick coloring is disabled then not use hash coloring (psi-muc-nick-hash-color.diff) + added detection Bombus Avalon jabber client (psi-client-icons.diff) + added new options.ui.muc.roster-nick-coloring to psi-muc-roster-icons.diff * updated extended options plugin to v0.2.7 + added new patch psi-tray-act-bring-to-front.diff (patch from Dmitriy.trt, http://code.google.com/p/psi-dev/issues/detail?id=300) * some fixes for default muc roster nick colors (psi-muc-roster-icons.diff) + added roster iconset stafex_psi_mod.jisp by Dragonizer, majik (iconsets/roster/stafex_psi_mod.jisp) * improved psi-roster-avatar-frame.diff + added options for disable send button (psi-send-button-arrow.diff) * some optimizations for psi-modern-roster.diff + added 2 new patches: custom icons size patch and language select patch (psi-custom-icons-size.diff, psi-options-language-select.diff) * reverted psi-improve-historydlg.diff to r2290 * updated skins plugin to v.0.2.3 * fixing and optimizing "Psi+ Activities" iconset (iconsets/activities/psiplus-activities-16.jisp) + added rederick-activities-16.jisp iconset + added rederick-moods-16.jisp iconset (renewed version of gajim1108.jisp) * updated watcher plugin to v0.2.7 * small updates of russian localization by ivan101 (psi-ru svn, r174, 2010-05-27) 2010-05-23 zet v0.15.2460 Beta -- Известные проблемы (после r1784): 1. не работает группа hidden (баг upstream-версии) -- Changelog: + добавлен новый патч для автоматического сворачивания по таймауту главного окна Psi+ (psi-roster-autohide.diff) + добавлен вызов краткой справки по плагину непосредственно из окна с настройками плагина, часть 1: attention plugin, autoreply plugin, chess plugin, cleaner plugin, conference logger plugin, extended options plugin + добавлен вызов краткой справки по плагину, часть 2: birthday reminder plugin, gmail notification plugin, history keeper plugin, icq die plugin, image plugin, juick plugin, qip x-statuses plugin + добавлена возможность отключения вертикального скроллбара в ростере конференции (psi-roster-disable-scrollbar.diff) + добавлен вызов краткой справки по плагину, часть 3: screenshot plugin, skins plugin, stop spam plugin, storage notes plugin, translate plugin, watcher plugin + добавлена иконка для кнопки вызова краткой справки по плагину (psi-plugin-info-button.diff) * улучшения для панели поиска по чатлогу (psi-typeahead-find.diff) * улучшения в css для чатов. использование TabDlg {} (psi-css-style-sheet.diff) + добавлен новый патч для исправления запоминания размеров и положения окна с headline-сообщениями (psi-fix-eventdlg-resizing.diff) + добавлен новый патч на опциональное расположение всплывающих окон. используются опции options.ui.notifications.passive-popups.at-left-corner и options.ui.notifications.passive-popups.top-to-bottom options + добавлены англоязычные страницы wiki/en-US/alt_linux.wiki и wiki/en-US/arch_linux.wiki + добавлен новый патч для изменения стандартной иконки приложения в unix на логотип Psi+ (psiplus-nix-application-icon-and-name.diff) - убран патч psi-fix-eventdlg-resizing.diff. патч принят upstream-разработчиками * изменён размер панели поиска по чатлогу и фикс нажатия кнопки поиска (psi-typeahead-find.diff) + добавлен детект клиента BombusKlub, обновлён fingerprint.jisp (спасибо westsibe) * поправлена иконка для клиента bombus-avalon (fingerprint.jisp, v0.2.9) * обновлён birthday reminder plugin до версии 0.2.5 * обновлён icq must die plugin до версии 0.1.1 * настройки приложения по умолчанию: теперь используется сочетание кнопок "Shift+Del" в качестве горячей кнопки для удаления выбранного контакта из ростера Psi+. ранее для этого использовалась кнопка "Del" (psi-default-application-settings.diff) * chess plugin: больше строк для локализации (v0.1.5) * обновлён orange skin, v.0.5.2 (skins/universal/orange/the_orange.skn) ! обновлена русская локализация от ivan101 (psi-ru svn, r173, 2010-05-23) -- Known issues (after r1784): 1. hidden group does not work (upstream version bug) -- Changelog: + added new patch - roster autohide patch (psi-roster-autohide.diff) + added plugins information, part 1 (attention plugin, autoreply plugin, chess plugin, cleaner plugin, conference logger plugin, extended options plugin) + added plugins information, part 2 (birthday reminder plugin, gmail notification plugin, history keeper plugin, icq die plugin, image plugin, juick plugin, qip x-statuses plugin) + disable scrollbar for muc roster (psi-roster-disable-scrollbar.diff) + added plugins information, part 3 (screenshot plugin, skins plugin, stop spam plugin, storage notes plugin, translate plugin, watcher plugin) + added icons for psi-plugin-info-button.diff * some changes for findbar style (psi-typeahead-find.diff) * more css for chats. use TabDlg {} (psi-css-style-sheet.diff) + added new patch to fix headline messages resizing (psi-fix-eventdlg-resizing.diff) + added new patch for optional location of popups. options.ui.notifications.passive-popups.at-left-corner and options.ui.notifications.passive-popups.top-to-bottom options + added wiki/en-US/alt_linux.wiki, wiki/en-US/arch_linux.wiki + added new patch to change unix icon to Psi+ logo (psiplus-nix-application-icon-and-name.diff) - removed psi-fix-eventdlg-resizing.diff. implemented in upstream * resized find line and fixed toggle button (psi-typeahead-find.diff) + added BombusKlub client detection, updated fingerprint.jisp (thanks to westsibe) * fixed bombus-avalon icon in fingerprint.jisp (v0.2.9) * updated birthday reminder plugin to v0.2.5 * updated icq must die plugin to v0.1.1 * defaults: use Shift+Del shortcut to delete the selected contact (psi-default-application-settings.diff) * more translatable for chess plugin (v0.1.5) * updated orange skin, v.0.5.2 (skins/universal/orange/the_orange.skn) ! updated russian localization by ivan101 (psi-ru svn, r173, 2010-05-23) 2010-05-16 zet v0.15.2417 Beta -- Известные проблемы (после r1784): 1. не работает группа hidden (баг upstream-версии) -- Changelog: * обновлена информация об участниках проекта, Psi -> Help -> About -> About Psi+ (psiplus-aboutdlg.diff) + добавлена статья faq.wiki на английском языке, http://code.google.com/p/psi-dev/wiki/faq?wl=en-US * фикс загрузки/выгрузки плагинов при смене профиля Psi+, оптимизация некоторых плагинных патчей * обновлён stop spam plugin до версии 0.3.5, http://psi-dev.googlecode.com/svn/trunk/plugins/generic/stopspamplugin/changelog.txt (UTF-8) * поправлен патч psi-get-your-jid-for-activetab-from-plugins.diff + добавлена статья features.wiki на английском языке, http://code.google.com/p/psi-dev/wiki/features?wl=en-US - убран ненужный патч psi-debianize.diff ( git://github.com/zerkalica/psi-plus-debian.git ) * поправлен патч psi-get-account-info-from-plugins.diff * улучшения для psi-plugins-options.diff. теперь стало возможно обновлять плагины без рестарта Psi+. достаточно отключить плагин в настройках приложения, обновить файл плагина и потом вновь включить плагин в настройках приложения * обновлён attention plugin до версии 0.1.0, http://psi-dev.googlecode.com/svn/trunk/plugins/generic/attentionplugin/changelog.txt (UTF-8) * исправлен цвет счётчика пропущенных сообщений в конференции при условии, что статусное сообщение не пустое (psi-muc-minimize-to-roster.diff) * объединены некоторые зависящие друг от друга плагинные патчи в один патч (psi-plugins-options-and-fix-plugins-loading-and-unloading.diff) * большое обновление статьи av_calls.wiki, http://code.google.com/p/psi-dev/wiki/av_calls * завершена английская локализация статьи plugins.wiki, http://psi-plus.com/wiki/en:plugins * обновлён иконпак для activities: gajim-activities.jisp -> rederick-activities.jisp * обновлён скин orange skin, v.0.4 (skins/universal/orange/the_orange.skn) * обновлён иконпак с иконками jabber-клиентов fingerprint.jisp, v0.2.7 (добавлена иконка мобильного jabber-клиента Jimm Aspro) * обновлён патч psi-client-icons.diff (Jimm Aspro) -- Known issues (after r1784): 1. hidden group does not work (upstream version bug) -- Changelog: * updated about psi+ tab, Psi -> Help -> About -> About Psi+ (psiplus-aboutdlg.diff) + added faq.wiki in english, http://code.google.com/p/psi-dev/wiki/faq?wl=en-US * fixed loading and unloading plugins when profile changes, cleanup plugins patches * updated stop spam plugin to v0.3.5, http://psi-dev.googlecode.com/svn/trunk/plugins/generic/stopspamplugin/changelog.txt (UTF-8) * fixed psi-get-your-jid-for-activetab-from-plugins.diff + added features.wiki in english, http://code.google.com/p/psi-dev/wiki/features?wl=en-US - removed psi-debianize.diff. it not needed, for debian specs use git://github.com/zerkalica/psi-plus-debian.git * fixed psi-get-account-info-from-plugins.diff * improved psi-plugins-options.diff. for now possible to update the plugin without restarting the application. just disable it in options, update file, than enable it * updated attention plugin to v0.1.0, http://psi-dev.googlecode.com/svn/trunk/plugins/generic/attentionplugin/changelog.txt (UTF-8) * fixed color of the conference messages counter if the status message is not empty (psi-muc-minimize-to-roster.diff) * merged some patches for plugin system into one patch because these patches do not work separately (psi-plugins-options-and-fix-plugins-loading-and-unloading.diff) * big update to the av_calls page (wiki/av_calls.wiki, wiki/en-US/av_calls.wiki), http://code.google.com/p/psi-dev/wiki/av_calls * plugins.wiki: completed English localization, http://psi-plus.com/wiki/en:plugins * correction for activities iconsets: gajim-activities.jisp -> rederick-activities.jisp * updated orange skin, v.0.4 (skins/universal/orange/the_orange.skn) * updated fingerprint.jisp, v0.2.7 (added icon for Jimm Aspro) * updated psi-client-icons.diff (Jimm Aspro) 2010-05-11 zet v0.15.2378 Beta -- Известные проблемы (после r1784): 1. не работает группа hidden (баг upstream-версии) -- Changelog: + добавлен новый системный иконпак White (iconsets/roster/white_theme.jisp, iconsets/system/whitesys.jisp) * исправления в скинах (black_theme.skn, qip_infium.skn, sky.skn, tkabber.skn, default_skin.skn) * поправлены ссылки на Wiki в настройках плагинов Psi+ * попытка решить задачу 68, http://code.google.com/p/psi-dev/issues/detail?id=68 . теперь при запуске Psi+ не должен производить проверку наличия новых версий при отключенной опции options.auto-update.check-on-startup в дополнительных настройках приложения (psi-dirty-check.diff) * обновлён скин Brushed Metal, v0.2 (skins/mac/brushed_metal.skn) * обновлён смайлпак Android.jisp: добавлено 4 смайла, изменено 2 смайла (iconsets/emoticons/Android.jisp) * решена задача 296, http://code.google.com/p/psi-dev/issues/detail?id=296 (psi-add-client-icon-to-chatdlg.diff) + добавлена кнопка сброса координат местоположения в форме управления геолокацией (psi-geolocation.diff) * исправлена обработка некоторых полей в форме геолокации (psi-geolocation.diff) + добавлен новый патч для загрузки плагинов в алфавитном порядке при старте приложения (psi-use-plugins-in-alphabetical-order.diff) + на wiki-страницу скачиваний добавлены ссылки на репозитории для Ubuntu (downloads.wiki) * исправлена обработка символа "&" при работе с шаблонами (psi-send-button-context-menu.diff) * обновлён skins plugin до версии 0.2.0, http://psi-dev.googlecode.com/svn/trunk/plugins/generic/skinsplugin/changelog.txt (UTF-8) + добавлен новый скин Orange, v0.3 (skins/universal/orange/the_orange.skn) * изменены размеры по умолчанию для окна редактора топика конференции (psi-muc-topic.diff) + добавлен ростерный иконпак Android (iconsets/roster/Android.jisp) * исправлена очистка ростера от виртуального контакта конференции при отключении от сети, is187, http://code.google.com/p/psi-dev/issues/detail?id=187 (psi-muc-minimize-to-roster.diff) * исправлено падение приложения при открытии окна с пустой историей переписки с контактом (psi-all-in-one-window.diff, psi-improve-historydlg.diff) * изменён порядок следования записей в окне с историей переписки (psi-all-in-one-window.diff, psi-improve-historydlg.diff) + добавлена кнопка сворачивания окна с историей переписки (psi-improve-historydlg.diff) + добавлен новый патч для вызова дополнительной информации о плагинах Psi+ непосредственно из настроек плагинов (задел на будущее) (psi-plugin-info-button.diff) * менеджер контактов: совместимость с draft-ietf-xmpp-3921bis-06 (psi-contact-manager.diff) ! начата работа над английской версией wiki-страниц проекта -- Known issues (after r1784): 1. hidden group does not work (upstream version bug) -- Changelog: + added new system icons White (iconsets/roster/white_theme.jisp, iconsets/system/whitesys.jisp) * fixed skins (black_theme.skn, qip_infium.skn, sky.skn, tkabber.skn, default_skin.skn) * added Q_OBJECT macro into ContactManagerView (psi-contact-manager.diff) * fixed wiki urls for psi+ plugins * try to fix is68, http://code.google.com/p/psi-dev/issues/detail?id=68 (psi-dirty-check.diff) * updated brushed metal skin to v0.2 (skins/mac/brushed_metal_(mac).skn) * updated Android.jisp: +4 emoticons, changed 2 emoticons (iconsets/emoticons/Android.jisp) * fixed is296, http://code.google.com/p/psi-dev/issues/detail?id=296 (psi-add-client-icon-to-chatdlg.diff) + added reset button to geolocationdlg (psi-geolocation.diff) * try to fix psi-add-client-icon-to-chatdlg.diff * fixed psi-geolocation.diff + added new patch psi-use-plugins-in-alphabetical-order.diff + added links to repository for Ubuntu version (downloads.wiki) + added change status from dbus (psi patch from debian), psi-add-dbus-status.diff * fixed '&' char in templates menu (psi-send-button-context-menu.diff) * updated skins plugin to v0.2.0, http://psi-dev.googlecode.com/svn/trunk/plugins/generic/skinsplugin/changelog.txt (UTF-8) + added orange skin, v0.3 (skins/universal/orange/the_orange.skn) * resized topic window (psi-muc-topic.diff) + added Android roster iconset (iconsets/roster/Android.jisp) * fixed remove muc items when offline, is187, http://code.google.com/p/psi-dev/issues/detail?id=187 (psi-muc-minimize-to-roster.diff) * fixed crash on empty history (psi-all-in-one-window.diff, psi-improve-historydlg.diff) * proper sorting of history (psi-all-in-one-window.diff, psi-improve-historydlg.diff) + minimizable history (psi-improve-historydlg.diff) + added new patch: plugin info button to plugins settings window. need plugins support (psi-plugin-info-button.diff) * contactmanager more compatible with draft-ietf-xmpp-3921bis-06 (psi-contact-manager.diff) ! started english localization for wiki content 2010-05-03 zet v0.15.2296 Beta -- Известные проблемы (после r1784): 1. поиск контактов в ростере работает нестабильно (баг upstream-версии) 2. не работает группа hidden (баг upstream-версии) -- Changelog: + добавлен патч на исправление поведения окна чата при открытии первого непрочитанного сообщения (psi-fix-proccess-first-message.diff) * обновлён stop spam plugin до версии 0.3.2, http://psi-dev.googlecode.com/svn/trunk/plugins/generic/stopspamplugin/changelog.txt (UTF-8) + добавлено отображение иконки jabber-клиента собеседника в окне чата (psi-add-client-icon-to-chatdlg.diff) * исправлено поведение разделителей в окне конференции (psi-muc-roster-size-and-location.diff) + добавлен патч для решения задачи 98 (psi-add-join-groupchat-item-to-account-menu.diff) + добавлен патч на отображение в ростере собственной аватары, кнопки выбора статуса и поле для быстрого ввода статусного сообщения (psi-roster-avatar-frame.diff) * обновлён skins plugin до версии 0.1.1, http://psi-dev.googlecode.com/svn/trunk/plugins/generic/skinsplugin/changelog.txt (UTF-8) * обновлён watcher plugin до версии 0.2.2, http://psi-dev.googlecode.com/svn/trunk/plugins/generic/watcherplugin/changelog.txt (UTF-8) - убран патч psi-fix-alt+N-crash.diff (реализовано upstream-разработчиками) * обновлён extended options plugin до версии 0.2.4, http://psi-dev.googlecode.com/svn/trunk/plugins/generic/extendedoptionsplugin/changelog.txt (UTF-8) * исправлен порядок расположения групп в ростере (psi-muc-minimize-to-roster.diff) + добавлен ростерный иконпак Berlin Traffic Lights (iconsets/roster/berlin.jisp) * обновлены некоторые скины -- Known issues (after r1784): 1. contacts search in roster works unstable (upstream version bug) 2. hidden group does not work (upstream version bug) -- Changelog: + added patch to fix proccess first message (psi-fix-proccess-first-message.diff) * updated stop spam plugin to v0.3.2, http://psi-dev.googlecode.com/svn/trunk/plugins/generic/stopspamplugin/changelog.txt (UTF-8) + added client icon to chat dialog (psi-add-client-icon-to-chatdlg.diff) * fixed muc splitters position (psi-muc-roster-size-and-location.diff) + added patch to fix issues 98 (psi-add-join-groupchat-item-to-account-menu.diff) + added roster-avatar-frame patch (psi-roster-avatar-frame.diff) * updated skins plugin to v0.1.1, http://psi-dev.googlecode.com/svn/trunk/plugins/generic/skinsplugin/changelog.txt (UTF-8) + added missing options to default.xml for some patches (psi-all-in-one-window.diff, psi-movable-tabs-qt45-and-custom-style.diff, psi-typeahead-find.diff) * fixed status menu for psi-roster-avatar-frame.diff * updated watcher plugin to v0.2.2, http://psi-dev.googlecode.com/svn/trunk/plugins/generic/watcherplugin/changelog.txt (UTF-8) - removed psi-fix-alt+N-crash.diff. implemented by upstream * updated extended options plugin to v0.2.4, http://psi-dev.googlecode.com/svn/trunk/plugins/generic/extendedoptionsplugin/changelog.txt (UTF-8) * fixed roster position groups (psi-muc-minimize-to-roster.diff) + added roster iconset Berlin Traffic Lights (iconsets/roster/berlin.jisp) * updated some skins 2010-04-25 zet v0.15.2215 Beta -- Известные проблемы (после r1784): 1. не работает поиск контактов в ростере (баг upstream-версии) 2. не работает группа hidden (баг upstream-версии) -- Changelog: * поправлен смайлпак yahoo_gif.jisp + добавлен системный иконпак в стиле QIP Infium (iconsets/system/qipinfium_sys.jisp) + добавлен скин в стиле QIP Infium (skins/win32/qip_infium.skn) * фикс повторного входа в уже открытую конференцию (psi-muc-minimize-to-roster.diff) + добавлена горячая кнопка для управления табом с конференцией (свернуть, развернуть, покинуть) (psi-muc-minimize-to-roster.diff) + добавлена опция options.ui.muc.show-client-icons на показ иконок jabber-клиентов в ростере конференции (psi-muc-roster-icons.diff) + добавлены универсальные скины (skins/universal/black_theme.skn, skins/universal/qip_infium.skn, skins/universal/sky.skn, skins/universal/tkabber.skn) * обновлён chess plugin до версии 0.1.2, http://psi-dev.googlecode.com/svn/trunk/plugins/generic/chessplugin/changelog.txt (UTF-8) * обновлены некоторые системные иконпаки (blacksys.jisp, nuvola-system.jisp, oxygen_sys.jisp, qipinfium_sys.jisp, summer-system.jisp) + добавлен новый смайлпак GTalk smiles.jisp, v0.1 (iconsets/emoticons/GTalk-smiles.jisp) + добавлен детект следующих jabber-клиентов (psi-client-icons.diff): QIP 2010, AQQ, asterisk, buddydroid, ovi-chat, weather2jabber, yandexmail, radio-t * обновлён иконпак с иконками клиентов fingerprint.jisp, v0.2.6 (iconsets/clients/fingerprint.jisp) * обновлён conference logger plugin до версии 0.1.3, http://psi-dev.googlecode.com/svn/trunk/plugins/generic/conferenceloggerplugin/changelog.txt (UTF-8) ! обновлена русская локализация (автор - ivan101) -- Known issues (after r1784): 1. contacts search in roster does not work (upstream version bug) 2. hidden group does not work (upstream version bug) -- Changelog: * fixed iconsets/emoticons/yahoo_gif.jisp + added qip infium system iconset (iconsets/system/qipinfium_sys.jisp) + added qip infium skin (skins/win32/qip_infium.skn) * fixed empty contact after join in already opened groupchat (psi-muc-minimize-to-roster.diff) + added shortcuts for manage muc tab (minimize, restore, leave) (psi-muc-minimize-to-roster.diff) + added options.ui.muc.show-client-icons (psi-muc-roster-icons.diff) + added universal skins (skins/universal/black_theme.skn, skins/universal/qip_infium.skn, skins/universal/sky.skn, skins/universal/tkabber.skn) * updated chess plugin to v0.1.2, http://psi-dev.googlecode.com/svn/trunk/plugins/generic/chessplugin/changelog.txt (UTF-8) * updated some system iconsets (blacksys.jisp, nuvola-system.jisp, oxygen_sys.jisp, qipinfium_sys.jisp, summer-system.jisp) + added GTalk smiles.jisp, v0.1 (iconsets/emoticons/GTalk-smiles.jisp) + added some jabber clients detection (psi-client-icons.diff): QIP 2010, AQQ, asterisk, buddydroid, ovi-chat, weather2jabber, yandexmail, radio-t * updated fingerprint.jisp to v0.2.6 (iconsets/clients/fingerprint.jisp) * updated conference logger plugin to v0.1.3, http://psi-dev.googlecode.com/svn/trunk/plugins/generic/conferenceloggerplugin/changelog.txt (UTF-8) * updated russian localization (by ivan101) 2010-04-19 zet v0.15.2173 Beta -- Известные проблемы (после r1784): 1. не работает поиск контактов в ростере (баг upstream-версии) 2. не работает группа hidden (баг upstream-версии) -- Changelog: + добавлен скин "black theme", v0.2 (black_theme_(nix).skn, black_theme_(win32).skn) * исправлено поведение приложения после кика из конференции (psi-muc-minimize-to-roster.diff) * обновлён skins plugin до версии 0.0.9, http://psi-plus.com/wiki/plugins#skins_plugin * обновлён storage notes plugin до версии 0.1.0, http://psi-plus.com/wiki/plugins#storage_notes_plugin * обновлён скин "blue skin", v0.8 (blue_skin.skn) * обновлён qip x-statuses plugin до версии 0.0.4, http://psi-plus.com/wiki/plugins#qip_x-statuses_plugin + добавлена опция на скрытие иконки статуса в заголовке таба (спасибо Nexor) (psi-tab-status-icon.diff) * фикс иконки статуса на табе с конференцией (psi-muc-minimize-to-roster.diff, psi-tab-status-icon.diff) + в папку sound добавлены некоторые музыкальные файлы для звукового сопровождения работы плагинов (attention.wav, chess_error.wav, chess_finish.wav, chess_move.wav, chess_start.wav, reminder.wav, watcher.wav) + добавлен скин "sky", v0.1 (sky_(nix).skn, sky_(win32).skn) * обновлён attention plugin до версии 0.0.8, http://psi-plus.com/wiki/plugins#attention_plugin * обновлён birthday reminder plugin до версии 0.2.2, http://psi-plus.com/wiki/plugins#birthday_reminder_plugin * обновлён watcher plugin до версии 0.2.1, http://psi-plus.com/wiki/plugins#watcher_plugin + добавлен патч для поддержки видеообмена при голосовых вызовах (by Arseniy Lartsev, http://lists.affinix.com/pipermail/psi-devel-affinix.com/2010-March/009046.html (psi-avcalls-enable-video.diff) - убран скин "shine mac" * обновлён extended options plugin до версии 0.2.3, http://psi-plus.com/wiki/plugins#extended_options_plugin + восстановлен патч psi-deactivation-some-menu-items.diff + добавлен скин "minimal theme", v0.1 (minimal_theme_(nix).skn) + добавлен скин "tkabber", v0.2 (tkabber_(nix).skn, tkabber_(win32).skn) + добавлен ростерный иконпак tkabber.jisp + добавлен системный иконпак tkabber_sys.jisp + добавлен показ иконок jabber-клиентов в ростере конференции (psi-muc-roster-icons.diff) + добавлен патч на исправение показа версии jabber-клиента во всплывающей подсказке над контактами в ростере конференции (psi-fix-client-version-in-muc-roster-tooltip.diff) + добавлен скин "brushed metal", v0.1 (brushed_metal_(mac).skn) * обновлён stop spam plugin до версии 0.3.1, http://psi-plus.com/wiki/plugins#stop_spam_plugin * обновлён смайлпак yaemomidget.jisp + добавлен новый плагин Шахматы (chess plugin, v0.0.3), подробнее -- http://psi-plus.com/wiki/plugins#chess_plugin * обновлён список участников проекта Psi+ (psiplus-aboutdlg.diff) * решена задача issue 136 (psi-send-custom-status.diff) * обновлён смайлпак mra-emoticons.jisp (v1.0) ! обновлена русская локализация (автор - ivan101) -- Known issues (after r1784): 1. contacts search in roster does not work (upstream version bug) 2. hidden group does not work (upstream version bug) -- Changelog: + added 'black theme' skin, v0.2 (black_theme_(nix).skn, black_theme_(win32).skn) * fixed item conference after kick (psi-muc-minimize-to-roster.diff) * some fix muc join mechanism (psi-muc-minimize-to-roster.diff) * updated skins plugin to v0.0.9, http://psi-plus.com/wiki/plugins#skins_plugin * updated storage notes plugin to v0.1.0, http://psi-plus.com/wiki/plugins#storage_notes_plugin * updated 'blue skin', v0.8 (blue_skin.skn) * updated qip x-statuses plugin to v0.0.4, http://psi-plus.com/wiki/plugins#qip_x-statuses_plugin + added options to hide icons on tabs (thx to Nexor) (psi-tab-status-icon.diff) * fix muc status icons (psi-muc-minimize-to-roster.diff, psi-tab-status-icon.diff) + added some plugins sounds (attention.wav, chess_error.wav, chess_finish.wav, chess_move.wav, chess_start.wav, reminder.wav, watcher.wav) to sound dir + added 'sky' skin, v0.1 (sky_(nix).skn, sky_(win32).skn) * updated attention plugin to v0.0.8, http://psi-plus.com/wiki/plugins#attention_plugin * updated birthday reminder plugin to v0.2.2, http://psi-plus.com/wiki/plugins#birthday_reminder_plugin * updated watcher plugin to v0.2.1, http://psi-plus.com/wiki/plugins#watcher_plugin + added video support patch by Arseniy Lartsev, copied exactly from http://lists.affinix.com/pipermail/psi-devel-affinix.com/2010-March/009046.html (psi-avcalls-enable-video.diff) * fixed accessing missing option for psi-roster-groups-disable.diff - removed 'shine mac' skin * updated extendedoptionsplugin to v0.2.3, http://psi-plus.com/wiki/plugins#extended_options_plugin * some fix muc items (psi-muc-minimize-to-roster.diff) + restored psi-deactivation-some-menu-items.diff + added 'minimal theme' skin, v0.1 (minimal_theme_(nix).skn) + added 'tkabber' skin, v0.2 (tkabber_(nix).skn, tkabber_(win32).skn) + added iconsets/roster/tkabber.jisp + added iconsets/system/tkabber_sys.jisp + added display of jabber client icons in muc roster (psi-muc-roster-icons.diff) * fixed client version in muc tooltip (psi-fix-client-version-in-muc-roster-tooltip.diff) + added brushed metal skin, v0.1 (brushed_metal_(mac).skn) * updated stop spam plugin to v0.3.1, http://psi-plus.com/wiki/plugins#stop_spam_plugin * update iconsets/emoticons/yaemomidget.jisp + added chess plugin (v0.0.3), http://psi-plus.com/wiki/plugins#chess_plugin * updated about psi+ info (psiplus-aboutdlg.diff) * fixed issue 136 (psi-send-custom-status.diff) * updated mra-emoticons.jisp to v1.0 ! updated russian localization (by ivan101) 2010-04-05 zet v0.15.2095 Beta -- Известные проблемы (после r1784): 1. не работает поиск контактов в ростере 2. не работает отключение пунктов меню 3. не работает группа hidden 4. устаревшая русская локализация -- Changelog: + добавлена возможность вывода кнопки вкл/выкл отображения групп в ростере на панель кнопок (psi-roster-groups-disable.diff) * исправления для системных иконпаков (oxygen_sys.jisp, summer-system.jisp) * исправлено падение приложения при попытке присоединения к конференциям, защищённым капчей (psi-muc-minimize-to-roster.diff) - убран патч psi-roster-fix-online-contacts-coloring.diff (реализовано в upstream-версии) - убран патч psi-paint-roster-background.diff (реализовано в upstream-версии) + добавлен новый плагин qip xstatuses plugin (v0.0.3), http://psi-plus.com/wiki/plugins#qip_x-statuses_plugin и патч для работы данного плагина (psi-change-contacts-status-from-plugins.diff) + добавлен показ кнопки "enable-groups" в главном меню Psi+ и в контекстном меню в трее (psi-roster-groups-disable.diff) * ростерные патчи объединены в один общий патч (psi-modern-roster.diff) * исправлено падение приложения при использовании неподдерживаемой версии psimedia при попытке вызова через контекстное меню ростера + добавлен счётчик пропущенных сообщений от контакта со свёрнутой в ростер конференцией (psi-muc-minimize-to-roster.diff) * убрана лишняя информация из тултипа на контакте конференции в ростере (psi-muc-minimize-to-roster.diff) + добавлен детект jabber-бота osiris rss/atom feed bot (psi-client-icons.diff) + попытка детекта клиента jabbin * обновлён иконпак с иконками jabber-клиентов fingerprint.jisp до версии 0.2.3 + в дистрибутив добавлена папка с примерами скинов ! обновлены библиотеки win32 openssl до версии 0.9.8n (теперь мы сами компилируем openssl в mingw gcc из исходников http://www.openssl.org/source/openssl-0.9.8n.tar.gz) ! теперь используются скомпилированные в mingw библиотеки aspell отсюда: ftp://ftp.lyx.org/pub/lyx/contrib/lyx-windows-deps-mingw.zip ! из дистрибутива убран ненужный пакет библиотек vcredist_x86.exe -- Known issues (after r1784): 1. contacts search in roster does not work 2. disabling items in menu does not work 3. hidden group does not work 4. outdated russian localization -- Changelog: + added toolbar button to psi-roster-groups-disable.diff * some improvements to psi-css-style-sheet.diff, update blue_skin * fixed translation in psi-roster-groups-disable.diff * fixed system iconsets (oxygen_sys.jisp, summer-system.jisp) * fixed crash when join in captcha protected conferences (psi-muc-minimize-to-roster.diff) * small fixes for psi-activity-icons.diff and psi-roster-icons.diff - removed psi-roster-fix-online-contacts-coloring.diff (already in upstream) - removed psi-paint-roster-background.diff (already in upstream) + added qip xstatuses plugin (v0.0.3), http://psi-plus.com/wiki/plugins#qip_x-statuses_plugin and patch to make this plugin work (psi-change-contacts-status-from-plugins.diff) + added enable-groups button to view menu (psi-roster-groups-disable.diff) * merged roster patches into one patch (psi-modern-roster.diff) * fixed crash when using unsupported psimedia from context menu in roster + added counter messages in conference item (psi-muc-minimize-to-roster.diff) * some cleaning in tooltip conference item (psi-muc-minimize-to-roster.diff) * small fix brackets (psi-muc-minimize-to-roster.diff) + added osiris rss/atom feed bot detection (psi-client-icons.diff) + try to detect jabbin client * updated fingerprint.jisp to v0.2.3 + added skins folder with skins to installer ! updated win32 openssl libs to v0.9.8n (compiled by mingw gcc compiler from source http://www.openssl.org/source/openssl-0.9.8n.tar.gz) ! replaced aspell libs (now using libs from ftp://ftp.lyx.org/pub/lyx/contrib/lyx-windows-deps-mingw.zip) ! removed useless vcredist_x86.exe from installer 2010-03-29 zet v0.15.2060 Beta -- Известные проблемы (после r1784): 1. не работает сортировка контактов в ростере 2. не работает поиск контактов в ростере 3. не работает отключение пунктов меню 4. не работает группа hidden -- Changelog: + добавлен новый патч psi-muc-add-affiliation-icons.diff + добавлен объединённый смайлпак mra-emoticons.jisp - убраны ненужные смайлпаки mra_set03.jisp и mrim.jisp * настраиваемые цвета для контактов с различными рангами и ролями в ростере конференции (psi-muc-add-affiliation-icons.diff) + добавлена опция включения/выключения показа иконок аффиляций в ростере конференции (psi-muc-add-affiliation-icons.diff) + добавлена кнопка максимизации окна обзора сервисов, поиска контактов и некоторых других окон и диалогов (psi-customize-dialogs-title-hint.diff) * issue 11: сохранение размера (ширины) ростера конференции (psi-muc-roster-size.diff) * переписан патч psi-muc-nicklist-at-left.diff * фикс запоминания размера ростера конференции для положения "ростер конференции слева" (psi-muc-roster-size.diff) * решена issue 191 (psi-muc-notify-highlight.diff, psi-passive-popups-delays.diff) * решена issue 167 (psi-deactivation-of-some-contact-menu-items.diff) * теперь тулбары включены по умолчанию (psi-default-application-settings.diff) * опция "воспроизводить звуковые события для всех сообщений в конференции" выключена по умолчанию (psi-default-application-settings.diff) + добавления опция на отключение кнопки "вставить и отправить" (psi-send-button-context-menu.diff) * обновлён birthday reminder plugin до версии 0.2.1 (подробнее в wiki) * уменьшен размер формы ввода данных о местоположении (psi-geolocation.diff) + добавлен новый патч psi-paint-roster-background.diff + добавлен новый смайлпак yahoo_gif.jisp - убран ненужный смайлпак yahoo_png.jisp + добавлен патч на вызов в меню трея команды View Groups (psi-add-view-groups-to-tray-menu.diff) + добавлена опция options.ui.contactlist.avatars.avatars-at-left для psi-roster-avatars.diff + добавлен новый патч psi-fix-alt+N-crash.diff + добавлена опция options.ui.contactlist.avatars.use-default-avatar для psi-roster-avatars.diff + добавлен новый патч для доступа к различным меню приложения из плагина (psi-access-to-menus-from-plugins.diff) + добавлен новый плагин storage notes plugin (v0.0.9), подробнее - http://psi-plus.com/wiki/plugins#storage_notes_plugin * выравнивание неквадратных аватаров по центру (psi-roster-avatars.diff) + добавлен новый патч psi-hide-roster-status-icons.diff на обработку опции options.ui.contactlist.show-status-icons * добавлены +3 пикселя между аватаром и ником контакта в ростере (psi-hide-roster-status-icons.diff) + добавлена опция на показ статусной иконки контакта поверх аватара (psi-roster-avatars.diff, psi-hide-roster-status-icons.diff) * исправлено дублирование кнопок тулбаров в режиме "всё-в-одном-окне" (psi-all-in-one-window.diff) + добавлены новые иконки oxygen для ростера и системный иконпак (oxygen.jisp, oxygen_sys.jisp) + добавлен новый патч на показ статусного сообщения контакта ростера в одной строке с ником контакта (psi-roster-single-line-status-messages.diff) * 7 - минимальный размер шрифта для текста статусного сообщения (psi-roster-single-line-status-messages.diff) * теперь по умолчанию отключен toolwindow в главном окне Psi+ (psi-default-application-settings.diff) * обновлён juick plugin (v0.9.12) - добавлена возможность указания прокси-сервера и авторизации на нём + добавлен новый патч psi-roster-fix-online-contacts-coloring.diff + добавлен новый плагин history keeper plugin (v0.0.2), подробнее - http://psi-plus.com/wiki/plugins#history_keeper_plugin * фикс для варианта отображения аватаров справа в ростере (psi-roster-single-line-status-messages.diff) * множественные обновления системных иконпаков (blacksys.jisp, nuvola-system.jisp, oxygen_sys.jisp, summer-system.jisp) * обновлён attention plugin до версии 0.0.7 * обновлён cleaner plugin до версии 0.2.4 + добавлен новый патч psi-iconsets.diff. теперь иконки Psi+ интегрированы в исходный код - убран ненужный иконсет psiplus.jisp - убран ненужный иконсет crystal_system.jisp * обновлён watcher plugin до версии 0.2.0 + добавлен новый патч для настройки цветов тултипа (цвет шрифта, цвет фона) (psi-coloring-tooltip.diff) + добавлен новый плагин skins plugin (v0.0.8), подробнее - http://psi-plus.com/wiki/plugins#skins_plugin + добавлен новый патч psi-fix-grepshortcutkeydialog.diff + добавлен новый патч на совместимость с плагином skins plugin (psiplus-more-compatibility-with-skinsplugin.diff) * обновлён conference logger plugin до версии 0.1.1 + добавлена англоязычная страничка wiki по компиляции Psi+ (спасибо Z_God за контроль ошибок) * обновлён autoreply plugin до версии 0.2.7 + добавлен патч на обработку опции options.ui.contactlist.css (psi-mainwindow-css.diff) * обновлён extended options plugin до версии 0.2.2 + добавлен объединённый патч для обработки css в приложении (psi-css-style-sheet.diff) * обновлён stop spam plugin до версии 0.2.1 + добавлен новый патч psi-roster-groups-disable.diff на отключение показа групп в ростере. пока настраивается через опцию options.ui.contactlist.enable-groups, в дальнейшем будет сделана соответствующая кнопка на тулбаре * переписан патч на сворачивание конференций в ростер (psi-muc-minimize-to-roster.diff) ! обновлены системные библиотеки Qt до версии 4.6.2, http://qt.nokia.com/developer/changes/changes-4.6.2 ! обновлены библиотеки Psimedia до версии 1.0.3.737, http://delta.affinix.com/svn/trunk/psimedia/ ! обновлён GStreamer до версии 0.10.28 (спасибо Z_God) ! обновлены библиотеки Win32OpenSSL до версии 0.9.8m [0.9.8.13], http://www.slproweb.com/products/Win32OpenSSL.html -- Known issues (after r1784): 1. contacts sorting in roster does not work 2. contacts search in roster does not work 3. disabling items in menu does not work 4. hidden group does not work -- Changelog: + added psi-muc-add-affiliation-icons.diff + added combined mra-emoticons.jisp - removed useless mra_set03.jisp, mrim.jisp * customizable colors for psi-muc-add-affiliation-icons.diff + added option enable/disable affiliation icons for psi-muc-add-affiliation-icons.diff + maximize discovery and some another dialogs (psi-customize-dialogs-title-hint.diff) * issue 11: save/restore roster size in MUC (psi-muc-roster-size.diff) * rewritten psi-muc-nicklist-at-left.diff * psi-muc-roster-size.diff: fix size when roster at left in MUC * issue 191 (psi-muc-notify-highlight.diff, psi-passive-popups-delays.diff) * issue 167 (psi-deactivation-of-some-contact-menu-items.diff) * psi-default-application-settings.diff: default toolbars is enabled * psi-default-application-settings.diff: play sounds for all messages in groupchat is disabled + added options for disable 'paste and send' (psi-send-button-context-menu.diff) * updated birthday reminder plugin to v0.2.1 * maximize searchdlg * reduced geolocation form size (psi-geolocation.diff) + added psi-paint-roster-background.diff + added emoticons/yahoo_gif.jisp - removed useless emoticons/yahoo_png.jisp * updated yahoo_gif.jisp: added hidden emoticons + added psi-add-view-groups-to-tray-menu.diff + psi-roster-avatars.diff: added options.ui.contactlist.avatars.avatars-at-left + added psi-fix-alt+N-crash.diff: fix crash on pressing Alt+number when number is out of range + psi-roster-avatars.diff: added options.ui.contactlist.avatars.use-default-avatar + added psi-access-to-menus-from-plugins.diff + added storage notes plugin, v0.0.9, http://psi-plus.com/wiki/plugins#storage_notes_plugin * psi-roster-avatars.diff: alignment of non-square avatars in the center + added psi-hide-roster-status-icons.diff (option options.ui.contactlist.show-status-icons) * psi-hide-roster-status-icons.diff: +3 pixels between the avatar and nickname + added option status-icon-over-avatar (psi-roster-avatars.diff, psi-hide-roster-status-icons.diff) * fixed duplicate toolbar buttons in all in one mode (psi-all-in-one-window.diff) * killed fat arrow specifically for maj (psi-toolbar-button-kill-arrow.diff) * less calls to the options (psi-roster-icons.diff, psi-roster-avatars.diff, psi-hide-roster-status-icons.diff) + added oxygen roster and system icons (oxygen.jisp, oxygen_sys.jisp) + added psi-roster-single-line-status-messages.diff patch * try to fix clipping of statusmessages for psi-roster-single-line-status-messages.diff * psi-roster-single-line-status-messages.diff: o.font.setPointSize(o.font.pointSize()-1); * 7 - minimal font size for status (psi-roster-single-line-status-messages.diff) * disable buggy toolwindow by default (psi-default-application-settings.diff) * updated juick plugin - added proxy settings and proxy authorizations settings (v0.9.12) + added fix-online-contacts-coloring patch (psi-roster-fix-online-contacts-coloring.diff) + added history keeper plugin, v0.0.2, http://psi-plus.com/wiki/plugins#history_keeper_plugin * try to fix patch when avatars at right (psi-roster-single-line-status-messages.diff) * many updates for system iconsets (blacksys.jisp, nuvola-system.jisp, oxygen_sys.jisp, summer-system.jisp) * changed roster toolbar buttons * added delta from default iconset * updated attention plugin to v0.0.7 * updated cleaner plugin to v0.2.4 + added new iconsets patch (psi-iconsets.diff) - removed useless psiplus.jisp - removed useless crystal_system.jisp * fixed psibuild script with regards to new icons (trunk/scripts/posix/libpsibuild.sh, trunk/scripts/posix/psibuild.sh) * fixed win32 scripts * updated watcher plugin to v0.2.0 + added psi-coloring-tooltip.diff + added skins plugin, v0.0.8, http://psi-plus.com/wiki/plugins#skins_plugin * css for whole chat ui (psi-chat-css.diff) + added fix-grepshortcutkeydialog patch (psi-fix-grepshortcutkeydialog.diff) * changed default colors settings for psi-coloring-tooltip.diff + added patch for more compatibility with skinsplugin (psiplus-more-compatibility-with-skinsplugin.diff) * updated conference logger plugin to v0.1.1 * fixed psi-iconsets.diff * fixed system iconsets (blacksys.jisp, nuvola-system.jisp, oxygen_sys.jisp, summer-system.jisp) + added mingw32_en.wiki for English-speaking users (thanks to Z_God for translation fixes) * updated autoreply plugin to v0.2.7 + add options.ui.contactlist.css option patch (psi-mainwindow-css.diff) * updated extended options plugin to v0.2.2 + added css the world (psi-css-style-sheet.diff) * updated stop spam plugin to v0.2.1 + added add roster-groups-disable patch. option options.ui.contactlist.enable-groups, (psi-roster-groups-disable.diff) * rewritten patch psi-muc-minimize-to-roster.diff ! updated Qt libs to v4.6.2, http://qt.nokia.com/developer/changes/changes-4.6.2 ! updated Psimedia libs to v1.0.3.737, http://delta.affinix.com/svn/trunk/psimedia/ ! updated GStreamer to v0.10.28 (thanks to Z_God) ! updated Win32OpenSSL libs to v0.9.8m [0.9.8.13], http://www.slproweb.com/products/Win32OpenSSL.html 2010-01-31 zet v0.15.1757 Beta * обновлён плагин extended options plugin до версии 0.0.9, http://psi-plus.com/wiki/plugins#extended_options_plugin + добавлен детект jabber-клиента sim (psi-client-icons.diff) + добавлена возможность загрузки/выгрузки плагинов без перезапуска Psi+ (psi-plugins-options.diff) * обновлён плагин stop spam plugin до версии 0.1.7, http://psi-plus.com/wiki/plugins#stop_spam_plugin * обновлён плагин birthday reminder plugin до версии 0.2.0, http://psi-plus.com/wiki/plugins#birthday_reminder_plugin * обновлён плагин attention plugin до версии 0.0.5, http://psi-plus.com/wiki/plugins#attention_plugin + добавлен новый патч psi-add-standard-transports.diff на автоматическое определение иконок для контактов, подключаемых через транспорты * обновлён плагин cleaner plugin до версии 0.1.1, http://psi-plus.com/wiki/plugins#cleaner_plugin * обновлён иконпак для activity (psiplus-activities.jisp) * возвращёно подтверждение на очистку лога конференции (опционально) + добавлен новый патч на показ иконки прослушиваемой музыкальной композиции во всплывающем окне на контакте ростера (psi-tune-icon-in-tooltip.diff) * режим "всё-в-одном": возвращён показ меню View (Вид) (psi-all-in-one-window.diff) * режим "всё-в-одном": исправлено запоминание размера ростера по ширине после рестарта Psi+ (psi-all-in-one-window.diff) * обновлён плагин watcher plugin до версии 0.1.2, http://psi-plus.com/wiki/plugins#watcher_plugin * фикс по мотивам задачи http://code.google.com/p/psi-dev/issues/detail?id=247 + добавлен новый патч psi-improve-historydlg.diff, добавляющий календарь в окно истории переписки (на основе патча от lexa_), также меняющий расположение фрейма с логом истории и добавляющий форму быстрого поиска по логу переписки (тестовый вариант) * обновлён системный иконпак summer-system.jisp до версии 0.5 -- * updated extended options plugin to v0.0.9, http://psi-plus.com/wiki/plugins#extended_options_plugin + added sim jabber client detection (psi-client-icons.diff) + try to load-unload plugins without restart Psi+ (psi-plugins-options.diff) * updated stop spam plugin to v0.1.7, http://psi-plus.com/wiki/plugins#stop_spam_plugin * improved psi-access-to-iconfactory-from-plugins.diff - added getIcon() to iconfactoryaccessor * updated birthday reminder plugin to v0.2.0, http://psi-plus.com/wiki/plugins#birthday_reminder_plugin * updated attention plugin to v0.0.5, http://psi-plus.com/wiki/plugins#attention_plugin + added new patch psi-add-standard-transports.diff * updated cleaner plugin to v0.1.1, http://psi-plus.com/wiki/plugins#cleaner_plugin * updated activity iconset (psiplus-activities.jisp) * returned enable/disable to clear log confirmation for muc + added new patch - adds tune icon in tooltip (psi-tune-icon-in-tooltip.diff) * all-in-one: return View menu item back (psi-all-in-one-window.diff) * all-in-one: fix saving/restoring splitter position (psi-all-in-one-window.diff) * updated watcher plugin to v0.1.2, http://psi-plus.com/wiki/plugins#watcher_plugin * fixed http://code.google.com/p/psi-dev/issues/detail?id=247 + added new patch psi-improve-historydlg.diff (unfinished), based on patch by lexa_ * updated summer-system.jisp to v0.5 2010-01-24 zet v0.15.1685 Beta + добавлен новый плагин attention plugin (v0.0.3), http://psi-plus.com/wiki/plugins#attention_plugin * обновлены системные иконпаки: добавлены иконки для attention plugin (blacksys.jisp, summer-system.jisp, nuvola-system.jisp) * фикс для accountinfoaccessor (psi-get-account-info-from-plugins.diff) и плагинов (attention plugin, image plugin) * возвращён показ капсов у контактов конференции (psi-temp-show-caps.diff) + добавлен новый плагин extended options plugin (v0.0.8), http://psi-plus.com/wiki/plugins#extended_options_plugin * исправлен системный иконпак (nuvola-system.jisp) + добавлен новый патч для отображения иконок активности (psi-activity-icons.diff) * исправлен иконпак с иконками активности (gajim-activities.jisp) * фикс Issue 225 (psi-muc-notify-highlight.diff) * исправлено определение jabber-бота JuBo (psi-client-icons.diff), спасибо maj * фикс для psi-movable-tabs-qt45-and-custom-style.diff * обновлён birthday reminder plugin до версии 0.1.9, http://psi-plus.com/wiki/plugins#birthday_reminder_plugin * обновлён cleaner plugin до версии 0.1.0, http://psi-plus.com/wiki/plugins#cleaner_plugin + добавлены специфичные иконки в иконпак активности (gajim-activities.jisp) * фикс для диалога выбора определённого пункта активности (psi-activity-icons.diff) * исправлены иконки для прочих активностей (psi-activity-icons.diff) - убран патч psi-chatdlg-copypaste-keypress.diff, т.к. он принят в основную ветвь Psi * исправлен патч psi-configure-hyperlink-color.diff + добавлен новый патч на показ иконок в меню на заголовке аккаунта (psi-more-icons-in-account-menu.diff) + добавлен новый патч на установку оптимального состояния настроек приложения по умолчанию (psi-default-settings.diff) - убран патч psi-optional-confirmation-when-clearing.diff, т.к. он принят в основную ветвь Psi + добавлен новый иконопак активности по умолчанию от tux-den (psiplus-activities.jisp) + добавлены иконки для пустых активностей и настроений (summer-system.jisp), by tux-den * патчи psi-default-account-settings.diff и psi-default-settings.diff объединены в общий патч psi-default-application-settings.diff (умолчальные настройки приложения и аккаунта) + добавлен показ иконок jabber-клиентов во всплывающем окне на контакте ростера (psi-client-icons.diff) + добавлен новый патч для поддержки XEP-0080: User Location в Psi+, http://xmpp.org/extensions/xep-0080.html (psi-geolocation.diff) + в комплект поставки добавлена вторая лицензия для обеспечения наиболее полной совместимости с оригинальным Psi IM (copying-psi.txt) + добавлено определение jabber-бота JuBo, клиентов Jabbroid и Meebo (psi-client-icons.diff) * исправлен детект клиента android (psi-client-icons.diff) * обновлён иконпак с иконками jabber-клиентов (fingerprint.jisp, v0.2.1) * обновлён состав команды Psi+ Dev Team (psiplus-aboutdlg.diff) * обновлены системные иконпаки: добавлены иконки геолокаций, настроений и активностей (blacksys.jisp, crystal_system.jisp, nuvola-system.jisp, summer-system.jisp) ! обновлены библиотеки Qt до версии 4.6.1 -- + added attention plugin (v0.0.3), http://psi-plus.com/wiki/plugins#attention_plugin * updated system iconsets: added icons for attention plugin (blacksys.jisp, summer-system.jisp, nuvola-system.jisp) * fixed accountinfoaccessor (psi-get-account-info-from-plugins.diff) and plugins (attention plugin, image plugin) * restored show caps version of groupchat contacts (psi-temp-show-caps.diff) + added extended options plugin (v0.0.8), http://psi-plus.com/wiki/plugins#extended_options_plugin * fixed system iconset (nuvola-system.jisp) + added new patch psi-activity-icons.diff * fixed activities iconset (gajim-activities.jisp) * fixed Issue 225 (psi-muc-notify-highlight.diff) * fixed jubo detection (psi-client-icons.diff), thx to maj * fixed psi-movable-tabs-qt45-and-custom-style.diff * updated birthday reminder plugin to v0.1.9, http://psi-plus.com/wiki/plugins#birthday_reminder_plugin * updated cleaner plugin to v0.1.0, http://psi-plus.com/wiki/plugins#cleaner_plugin + added specific activities icons (gajim-activities.jisp) * fixed activitydlg (psi-activity-icons.diff) * fixed icons for other activity (psi-activity-icons.diff) - removed psi-chatdlg-copypaste-keypress.diff -- patch already in upstream * fixed psi-configure-hyperlink-color.diff + added new patch psi-more-icons-in-account-menu.diff * changed some settings (psi-receipts.diff, psi-roster-settings.diff, psi-client-icons.diff + added new patch psi-default-settings.diff - removed psi-optional-confirmation-when-clearing.diff -- patch already in upstream + added new default activities icons from tux-den (psiplus-activities.jisp) + added icons for empty activity and mood (summer-system.jisp), by tux-den * merged psi-default-account-settings.diff with psi-default-settings.diff to psi-default-application-settings.diff + added client icons to tooltip (psi-client-icons.diff) + added new patch psi-geolocation.diff + added second license to be fully compatible with psi (copying-psi.txt) + added JuBo jabber bot detection, Jabbroid client detection, Meebo client detection (psi-client-icons.diff) * fixed android client detection (psi-client-icons.diff) * updated jabber clients iconset (fingerprint.jisp, v0.2.1) * updated psiplus-aboutdlg.diff * updated system iconsets: add geolocation, moods & activity icons (blacksys.jisp, crystal_system.jisp, nuvola-system.jisp, summer-system.jisp) ! updated Qt libs to v4.6.1 2010-01-10 zet v0.15.1605 Beta + добавлен новый плагин watcherplugin (v0.0.2), подробнее -- http://psi-plus.com/wiki/plugins#watcher_plugin * подправлен gmailnotifyplugin, добавлен changelog.txt к плагину (v0.4.2) * обновлён birthdayreminderplugin до версии 0.1.8, подробнее -- http://psi-plus.com/wiki/plugins#birthday_reminder_plugin * фикс попытки доступа к отсутствующим опциям (psi-typeahead-find.diff) * фикс для кнопки "Применить" в настройках плагинов (psi-plugins-options.diff) * редизайн настроек плагинов (psi-redesign-plugin-options.diff) + добавлен детект клиента Jabiru (psi-client-icons.diff) * исправлен детект мобильного клиента QIP PDA (psi-client-icons.diff) * обновлён cleanerplugin до версии 0.0.7, подробнее -- http://psi-plus.com/wiki/plugins#cleaner_plugin * теперь не показывается пункт меню vCard при клике ПКМ на адресе e-mail в логе конференции (psi-muc-nickclick-chat.diff) + добавлен детект клиента android [android.com] (psi-client-icons.diff) + добавлен детект клиента Vacuum (psi-client-icons.diff) + попытка детекта клиента Trillian (psi-client-icons.diff) * обновлён иконпак с иконками jabber-клиентов до версии 0.1.9 -- + added watcherplugin (v0.0.2), http://psi-plus.com/wiki/plugins#watcher_plugin * fixed gmailnotifyplugin, added changelog.txt (v0.4.2) * updated birthdayreminderplugin to v0.1.8, http://psi-plus.com/wiki/plugins#birthday_reminder_plugin * fixed accessing missing option (psi-typeahead-find.diff) * fixed apply button in plugins (psi-plugins-options.diff) * redesign plugins options (psi-redesign-plugin-options.diff) * small improvements for plugins options (psi-plugins-options.diff, psi-redesign-plugin-options.diff) + added Jabiru client detection (psi-client-icons.diff) * fixed QIP PDA detection (psi-client-icons.diff) * updated cleanerplugin to v0.0.7, http://psi-plus.com/wiki/plugins#cleaner_plugin * do not show vcard menu item on mailto links (psi-muc-nickclick-chat.diff) + added android (android.com) client detection (psi-client-icons.diff) + added Vacuum client detection (psi-client-icons.diff) + try to Trillian client detection (psi-client-icons.diff) * updated iconset with jabber client icons (fingerprint.jisp) to v0.1.9 2010-01-04 zet v0.15.1575 Beta * поправлен патч psi-movable-tabs-qt45-and-custom-style.diff * поправлены скрипты (trunk/scripts/posix/deploy, trunk/scripts/posix/libpsibuild.sh) * улучшен activetabaccessor (psi-getyourjid-for-activetab-from-plugins.diff) * фиксы для патчей psi-access-to-activetab-from-plugins.diff, psi-access-to-applicationinfo-from-plugins.diff и psi-get-account-info-from-plugins.diff * порция исправлений для патчей psi-user-activity.diff, psi-contact-manager.diff и psi-account-menu-privacy-item.diff + добавлены ростерный иконпак Lamps.jisp от Disabler (trunk/iconsets/roster/Lamps.jisp) + добавлена возможность выводить плагинную кнопку-действие на панель инструментов (psi-add-toolbar-button-from-plugins.diff) + добавлен смайлпак Android.jisp из состава Android SDK (trunk/iconsets/emoticons/Android.jisp) * обновлён системный иконпак blacksys.jisp (trunk/iconsets/system/blacksys.jisp) + добавлен скрипт для сборки debug-версии Psi+ под win32 (trunk/scripts/win32/make-debug-paused.cmd) * обновлены системные иконпаки crystal_system.jisp, nuvola-system.jisp и summer-system.jisp + добавлен патч для вывода всплывающей подсказки при наведении указателя мыши на иконку Psi+ в трее (psi-improve-tray-tooltip.diff) + добавлен новый плагин Image Plugin (текущая версия - 0.0.4), подробнее -- http://psi-plus.com/wiki/plugins#image_plugin * обновлён Conference Logger Plugin до версии 0.1.0, подробнее -- http://psi-plus.com/wiki/plugins#conference_logger_plugin + добавлена возможность выводить всплывающие окна при работе плагинов (psi-add-access-to-popups-from-plugins.diff) * теперь плагины в опциях отсортированы в алфавитном порядке (psi-plugins-options.diff) + добавлен новый плагин ICQ Must Die Plugin (текущая версия - 0.0.3), подробнее -- http://psi-plus.com/wiki/plugins#icq_must_die_plugin * обновлён Screenshot Plugin до версии 0.2.2, подробнее -- http://psi-plus.com/wiki/plugins#screenshot_plugin + добавлен новый плагин Cleaner Plugin (текущая версия - 0.0.6), подробнее -- http://psi-plus.com/wiki/plugins#cleaner_plugin + добавлен патч на расширение функционала формы приёма/передачи файлов (двойной щелчок на принятом/переданном файле вызывает связанное с ним приложение, а также изображение иконок для принятых/переданных файлов) - (psi-improve-filetransfer-dialog.diff) * модифицирован интерфейс настроек плагинов (psi-plugins-options.diff) * обновлён Gmailnotify Plugin до версии 0.4.0 * обновлён Birthday Reminder Plugin до версии 0.1.7, подробнее -- http://psi-plus.com/wiki/plugins#birthday_reminder_plugin * обновлён Stop Spam Plugin до версии 0.1.6, подробнее -- http://psi-plus.com/wiki/plugins#stop_spam_plugin ! обновлены системные библиотеки Qt, версия проименована как 4.6.0M (пропатченная версия Qt 4.6.0 патчем http://qt.gitorious.org/qt/qt/commit/ddf34f3) -- * fixed psi-movable-tabs-qt45-and-custom-style.diff * fixed scripts (trunk/scripts/posix/deploy, trunk/scripts/posix/libpsibuild.sh) * improve activetabaccessor (psi-getyourjid-for-activetab-from-plugins.diff) * fixed psi-access-to-activetab-from-plugins.diff, psi-access-to-applicationinfo-from-plugins.diff, psi-get-account-info-from-plugins.diff * fixed spatch function (deploy, deploy.win, libpsibuild.sh) * fixed patches (psi-user-activity.diff, psi-contact-manager.diff, psi-account-menu-privacy-item.diff) + added Lamps.jisp roster icons by Disabler (trunk/iconsets/roster/Lamps.jisp) + added ability to add toolbar button from plugins (psi-add-toolbar-button-from-plugins.diff) + added Android.jisp smilepack from Android SDK (trunk/iconsets/emoticons/Android.jisp) * updated blacksys.jisp (trunk/iconsets/system/blacksys.jisp) + added make-debug-paused.cmd script (trunk/scripts/win32/make-debug-paused.cmd) * updated system iconsets (crystal_system.jisp, nuvola-system.jisp, summer-system.jisp) + added traytooltip patch (psi-improve-tray-tooltip.diff) + added imageplugin (current version is 0.0.4) (http://psi-plus.com/wiki/plugins#image_plugin) * updated conferenceloggerplugin to v0.1.0 (http://psi-plus.com/wiki/plugins#conference_logger_plugin) * fixed psi-add-toolbar-button-from-plugins.diff + added ability to show popups from plugins (psi-add-access-to-popups-from-plugins.diff) * some fixes for plugins (autoreplyplugin, stopspamplugin) * plugins in alphabetical order (psi-plugins-options.diff) + added icqdieplugin (current version is 0.0.3) (http://psi-plus.com/wiki/plugins#icq_must_die_plugin) * updated screenshotplugin to v0.2.2 (http://psi-plus.com/wiki/plugins#screenshot_plugin) + added cleanerplugin (current version is 0.0.6) (http://psi-plus.com/wiki/plugins#cleaner_plugin) + added improve filetransfer dialog patch (psi-improve-filetransfer-dialog.diff) * psibuild: fix install patch * modify options interface (psi-plugins-options.diff) * updated gmailnotifyplugin to v0.4.0 * updated birthdayreminderplugin to v0.1.7 (http://psi-plus.com/wiki/plugins#birthday_reminder_plugin) * updated stopspamplugin to v0.1.6 (http://psi-plus.com/wiki/plugins#stop_spam_plugin) ! updated Qt libs to v4.6.0M (included patch from http://qt.gitorious.org/qt/qt/commit/ddf34f3) 2009-12-06 zet v0.14.1444 Release * обновлён список участников проекта (psiplus-aboutdlg.diff) * обновлён conferenceloggerplugin до версии 0.0.7, подробнее -- http://psi-dev.googlecode.com/svn/trunk/plugins/generic/conferenceloggerplugin/changelog.txt * фиксы и улучшения для плагинной системы * обновлён birthdayreminderplugin до версии 0.1.1, подробнее -- http://psi-dev.googlecode.com/svn/trunk/plugins/generic/birthdayreminderplugin/changelog.txt * обновлён stopspamplugin до версии 0.1.0, подробнее -- http://psi-dev.googlecode.com/svn/trunk/plugins/generic/stopspamplugin/changelog.txt * обновлена страница о плагинах в Wiki (plugins.wiki) * исправления в activetab accessor для режима "всё-в-одном" * обновлён juickplugin до версии 0.9.9, подробнее -- http://psi-dev.googlecode.com/svn/trunk/plugins/generic/juickplugin/changelog.txt ! обновлены системные библиотеки Qt до версии 4.6.0 ! MS Installer: в комплект поставки теперь входят библиотеки PsiMedia версии 1.0.3, подробнее -- http://delta.affinix.com/psimedia/ ! обновлена русская локализация (psi-ru svn, r155, 2009-12-06) ! Psi+ 0.14 -- релизная версия! :) -- + fix plugins qcm name (added psi-plugins-qcm-fix.diff) * autodownload libpsibuild (trunk/scripts/posix/psibuild.sh) * no seds for plugins anymore (psi-plugins-qcm-fix.diff) * updated win32 scripts (no seds for plugins anymore) - remove sed plugins from nix scripts * updated psiplus-aboutdlg.diff * fixed QCONF path in debian/rules (psi-debianize.diff) * PKGBUILD: some improvements (trunk/scripts/posix/PKGBUILD) * libpsibuild: fix git submodule update (trunk/scripts/posix/libpsibuild.sh) * updated conferenceloggerplugin to v0.0.7 * improved psi-get-account-info-from-plugins.diff * updated birthdayreminderplugin to v0.1.1 * updated stopspamplugin to v0.1.0 * updated plugins.wiki * small fix in libpsibuild for git archive (trunk/scripts/posix/libpsibuild.sh) * fixed activetab accessor in all-in-onee-window mode * updated juickplugin to v0.9.9 ! updated qt libs to v4.6.0 ! MS Installer: added PsiMedia 1.0.3 libs, http://delta.affinix.com/psimedia/ ! updated russian localization (psi-ru svn, r155, 2009-12-06) ! Psi+ 0.14 released! :) 2009-11-22 zet v0.14.1382 Beta + добавлен патч на исправление сломанных оф.разработчиками капсов (psi-temp-show-caps.diff) * обновлён список участников проекта (psiplus-aboutdlg.diff) * возвращены капсы Psi+ (psiplus-application-info.diff) * фикс для вызова контекстного меню по клику на нике в логе конференции (psi-muc-nickclick-chat.diff) + добавлено определение клиента pidgin (psi-client-icons.diff) + добавлено определение клиента telepathy (psi-client-icons.diff) + добавлен новый тулбар для поиска в логе чата/конференции (by machekku, mpk) (psi-typeahead-find.diff) * обновлён autoreplyplugin до версии 0.2.3, подробнее - http://psi-dev.googlecode.com/svn/trunk/plugins/generic/autoreplyplugin/changelog.txt * обновлён birthdayreminderplugin до версии 0.0.8, подробнее - http://psi-dev.googlecode.com/svn/trunk/plugins/generic/birthdayreminderplugin/changelog.txt * обновлён stopspamplugin до версии 0.0.8, подробнее - http://psi-dev.googlecode.com/svn/trunk/plugins/generic/stopspamplugin/changelog.txt * обновлены инструкции по сборке Psi+ под различные ОС, подробнее - http://code.google.com/p/psi-dev/wiki/main?tm=6 ! обновлены Win32 OpenSSL Libs до версии 0.9.8l (L) [0.9.8.12] ! MS Installer: в комплект поставки включен совмещённый русский+английский словарь (два в одном) -- + added psi-temp-show-caps.diff * PKGBUILD: remove debug files from package (trunk/scripts/posix/PKGBUILD) * fix escaping order (psi-muc-nickclick-chat.diff) * updated psiplus-aboutdlg.diff * restored psiplus-application-info.diff (psi+ caps) * try to fix context menu on nicknames in chatlog (psi-muc-nickclick-chat.diff) + make libpsibuild (trunk/scripts/posix/libpsibuild.sh * improved scripts (trunk/scripts/posix/libpsibuild.sh, trunk/scripts/posix/psibuild.sh) + added pidgin detection (psi-client-icons.diff) + added telepathy detection (psi-client-icons.diff) + added typeahead find toolbar by machekku, mpk (psi-typeahead-find.diff) * renamed activeElement to textWidget (psi-typeahead-find.diff) * fixed sed (trunk/scripts/posix/libpsibuild.sh) * updated autoreplyplugin to v0.2.3 * updated stopspamplugin to v0.0.8 * removed debug binary from package (psi-debianize.diff) * updated birthdayreminderplugin to v0.0.8 * updated build instructions for some OS, see at Psi+ Wiki - http://code.google.com/p/psi-dev/wiki/main?tm=6 ! updated Win32 OpenSSL Libs to v0.9.8l (L) [0.9.8.12] ! MS Installer: added aspell data and russian+english (2 in 1) dictionary 2009-11-08 zet v0.14.1318 Beta * psi-all-in-one-window.diff: добавлена опция "ростер слева для режима всё-в-одном" (options.ui.contactlist.roster-at-left-when-all-in-one-window) * исправлено поведение при клике по нелатинским никам в логе конференции (psi-muc-nickclick-chat.diff) + добавлен патч psi-get-account-info-from-plugins.diff * psi-all-in-one-window.diff: хак для показа панели "Контакты" вверху ростера * psi-typed-history.diff: не очищать последний набранный текст при пролистывании истории из ранее введённых сообщений + добавлен патч psi-muc-nicklist-at-left.diff на опциональное переключение отображения ростера конференции с левой стороны - убран ненужный смайлпак QIP_emoticons.jisp + добавлен ростерный иконпак roster/blackroster.jisp + добавлен системный иконпак system/blacksys.jisp * обновлён autoreplyplugin до версии 0.2.1, подробнее - http://psi-dev.googlecode.com/svn/trunk/plugins/generic/autoreplyplugin/changelog.txt * инструменты qconf и configure работают теперь и при сборке Psi+ в MS Windows * обновлён juickplugin до версии 0.9.8, подробнее - http://psi-dev.googlecode.com/svn/trunk/plugins/generic/juickplugin/changelog.txt + добавлен conferenceloggerplugin (текущая версия 0.0.2), подробнее - http://psi-dev.googlecode.com/svn/trunk/plugins/generic/conferenceloggerplugin/changelog.txt - убран ненужный более сериал (trunk/patches/series.txt) + добавлен birthdayreminderplugin (текущая версия 0.0.2), подробнее - http://psi-dev.googlecode.com/svn/trunk/plugins/generic/birthdayreminderplugin/changelog.txt + добавлен stopspamplugin (текущая версия 0.0.7), подробнее - http://psi-dev.googlecode.com/svn/trunk/plugins/generic/stopspamplugin/changelog.txt * поправлен патч psi-entity-time.diff * обновлён патч psi-dirty-check.diff (QDChecker::downloadPageUrl) ! в инсталлер добавлена новая библиотека aspell.dll (v0.60) -- * psi-all-in-one-window.diff: add 'options.ui.contactlist.roster-at-left-when-all-in-one-window' option * fix click on non-latin nicknames (psi-muc-nickclick-chat.diff) + added psiplus-os-hide.diff for app and os versions hide (by Xorg) + added psi-get-account-info-from-plugins.diff * psi-all-in-one-window.diff: quick and dirty hack to show 'Contacts' toolbar at top * PKGBUILD: small improvements (trunk/scripts/posix/PKGBUILD) * psi-typed-history.diff: don't clear last typed text during travelling message history + added psi-img-src-file-and-data-protos.diff + psi-muc-nicklist-at-left.diff: option to have nicklist at left in muc - removed useless QIP_emoticons.jisp * added debug option into deploy script (trunk/scripts/posix/deploy) + added roster/blackroster.jisp + added system/blacksys.jisp * juickplugin: fixed # in juick@conference.jabber.ru * updated autoreplyplugin to v0.2.1, see at http://psi-dev.googlecode.com/svn/trunk/plugins/generic/autoreplyplugin/changelog.txt + added deploy for windows (trunk/scripts/posix/deploy.win) * qconf and configure works now and on windows too * disable conf_windows.pri if conf.pri exists * update juickplugin to v0.9.8, see at http://psi-dev.googlecode.com/svn/trunk/plugins/generic/juickplugin/changelog.txt * win qconf working ssl * updated win32 scripts (qconf) (trunk/scripts/win32/make.cmd, make-paused.cmd) + added conferenceloggerplugin, v0.0.2 see at http://psi-dev.googlecode.com/svn/trunk/plugins/generic/conferenceloggerplugin/changelog.txt * updated win32 scripts: make.cmd make-paused.cmd, make-webkit-paused.cmd (--enable-plugins) * a bit improved psibuild.sh (trunk/scripts/posix/psibuild.sh) - deleted trunk/patches/series.txt + addeed birthdayreminderplugin, v0.0.2 see at http://psi-dev.googlecode.com/svn/trunk/plugins/generic/birthdayreminderplugin/changelog.txt + added stopspamplugin, v0.0.7, see at http://psi-dev.googlecode.com/svn/trunk/plugins/generic/stopspamplugin/changelog.txt * updated win32 scripts (--with-aspell-inc=c:\MinGW\include --with-aspell-lib=c:\MinGW\lib) * updated blacksys.jisp * psibuild-deb.sh: now builds all plugins from psiplus svn (trunk/scripts/posix/psibuild-deb.sh) * fixed psi-entity-time.diff * updated psi-dirty-check.diff (QDChecker::downloadPageUrl) * fixed deploy (trunk/scripts/posix/deploy) ! added new library aspell.dll (v0.60) into installer 2009-10-18 zet v0.14.1178 Beta * psi-send-button-context-menu.diff: исправлены ошибки компиляции для MSVS2005 + добавлен новый скрипт для bsd-систем. спасибо Shura0 (trunk/scripts/posix/psibuild.bsd) * фикс отправки шаблонов в конференциях (psi-send-button-context-menu.diff, psi-muc-bookmark-toolbar-button.diff) * фикс отправки шаблонов в чатах (psi-send-button-context-menu.diff) * исправлено определение mrim-контактов (psi-client-icons.diff) + добавлен патч на подмену версии клиента/платформы/капсов (psi-application-info-and-sysinfo-from-opts.diff), от oxpa * исправление нулевого указателя внутри конструктора PsiToolBar (psi-all-in-one-window.diff) - убран устаревший иконсет mailru.jisp (trunk/iconsets/roster/mailru.jisp) * обновлён патч psi-application-info-and-sysinfo-from-opts.diff (не требуется перезагрузка Psi+, версия ОС после очистки соответствующих полей возвращается к исходному значению), от oxpa * исправлено предупреждение при компиляции для psi-nix-audacious.diff * обновлён патч about-psi-plus.diff (добавлены/убраны некоторые участники проекта) + добавлен патч psi-plugins-fix-string-warnings.diff + добавлен общий FAQ (для пользователей различных ОС), http://code.google.com/p/psi-dev/wiki/FAQ * обновлены скрипты psibuild-deb.sh, psibuild-tar.sh, psibuild.bsd, psibuild.sh + добавлен смайлпак lgs_15x15.jisp от lgs (trunk/iconsets/emoticons/lgs_15x15.jisp) * убрана очистка истории введённых сообщений при очистке окна чата (psi-typed-history.diff) + добавлена "родная" поддержка перемещения табов в Qt 4.5 (скольжение по таббару) (psi-movable-tabs-qt45-and-custom-style.diff) + добавлен отдельный патч на добавление кнопок управления окном для различных форм (psi-customize-dialogs-title-hint.diff) * установка правильного набора флагов окна для некоторых диалогов (psi-customize-dialogs-title-hint.diff) + добавлена кнопка "закрыть" для табов (psi-movable-tabs-qt45-and-custom-style.diff) + добавлен патч на вызов Privacy Lists из контекстного меню в заголовке аккаунта (psi-account-menu-privacy-item.diff) * добавлены кнопки управления окном для формы Privacy (psi-customize-dialogs-title-hint.diff) * закрытие табов по нажатию средней кнопки мыши (psi-movable-tabs-qt45-and-custom-style.diff) + добавлен патч на исправление загрузки локализации при смене профиля psi-fix-lang-detect.diff от mblsha * фикс локализации для screenshotplugin, обновлена версия плагина до 1.3 + добавлена опция в настройках (вкладка Groupchat) для "hash coloring" (psi-muc-nick-hash-color.diff) - патч psi-fix-lang-detect.diff в upstream * добавлена команда сворачивания в ростер headlines-сообщений по нажатию кнопки Esc (psi-muc-minimize-to-roster.diff) * фикс http://code.google.com/p/psi-dev/issues/detail?id=152 (psi-movable-tabs-qt45-and-custom-style.diff) * обновлён смайлпак mra_set03.jisp до версии 0.4 (trunk/iconsets/emoticons/mra_set03.jisp) * фиксы компиляции на Qt < 4.5.0 для psi-customize-dialogs-title-hint.diff * обновлён 050-psi-muc-topic.diff (готов для передачи в upstream) + добавлен плагин-автоответчик (autoreplyplugin), текущая версия 0.0.8, от Dealer_WeARE: + добавлена поддержка списка исключений. если в список добавить jid конференции, то все приватные сообщения от пользователей конференции также окажутся в списке исключений + добавлена возможность задавать количество посылок автоответов ПРИМЕЧАНИЕ: в текущей реализации не поддерживается работа с сервером GMail. это происходит из-за того, что статус аккаунта плагин определяет по приходящим ему от сервера презенсам. А GMail(и, возможно, другие сервера, построенные не на ejabberd2) не шлют этого презенса + добавлена возможность отключения автоответчика для активной закладки * снижена вероятность ложных срабатываний + в настройки плагина по умолчанию добавлены juick и конференция psi-dev@conference.jabber.ru (Примечание: не забудьте добавить в исключения других ботов!) * исправлены некоторые проблемы с опциями http://psi-plus.com/wiki/plugins#autoreply_plugin * патчи, специфичные для Psi+, переименованы с соответствующим префиксом "psiplus" * фикс компиляции на Qt 4.4.3 для psi-movable-tabs-qt45-and-custom-style.diff * обновлён PKGBUILD * исправлена компиляция в ОС Solaris для psi-receipts.diff * фиксы работы табов и кнопки "закрыть" (psi-movable-tabs-qt45-and-custom-style.diff) * смена расположения кнопок управления в форме поиска в конференции (psi-customize-dialogs-title-hint.diff, psi-muc-finddlg-swap-buttons.diff) + добавлен новый патч для парсинга графики в чатах (psi-img-src-data.diff) * обновлён juickplugin до версии 0.9.2, подробности в http://psi-dev.googlecode.com/svn/trunk/plugins/generic/juickplugin/changelog.txt * фикс кодирования данных для psi-img-src-data.diff * исправления для перемещаемых табов (psi-movable-tabs-qt45-and-custom-style.diff) ! обновлена русская локализация (2009-10-10) ! обновлена версия Qt до 4.5.3 -- * fixed typos in psibuild-deb.sh (trunk/scripts/posix/psibuild-deb.sh) * psi-send-button-context-menu.diff: fixed build errors on win32 platform with MSVS2005 + added new bsd script. thanks to Shura0 (trunk/scripts/posix/psibuild.bsd) * psi-send-button-context-menu.diff, psi-muc-bookmark-toolbar-button.diff: fix sending templates * fix sending chat templates (psi-send-button-context-menu.diff) * PKGBUILD: improving and cleaning up build code (trunk/scripts/posix/PKGBUILD) * fixed mrim detection (psi-client-icons.diff) * PKGBUILD: fix patching of version * PKGBUILD: fix program version detection + added ready for use psi-application-info-and-sysinfo-from-opts.diff (by oxpa) * psi-all-in-one-window.diff: fix NULL pointer assertion inside PsiToolBar constructor - removed ugly roster iconset mailru.jisp (trunk/iconsets/roster/mailru.jisp) * updated psi-application-info-and-sysinfo-from-opts.diff (no need to restart the client, OS version after clearing the options back to the original) -- by oxpa * basizm in scripts - not work whis /bin/sh (psibuild-deb.sh, psibuild-tar.sh) * fixed warning for psi-nix-audacious.diff * updated about-psi-plus.diff (added/removed project members) + added fix string warning patch (psi-plugins-fix-string-warnings.diff) * PKGBUILD: add flag for using crash dialog * some fixes for src/libpsi/tools (trunk/scripts/posix/psibuild-deb.sh) + added FAQ.wiki (not for only windows users), http://code.google.com/p/psi-dev/wiki/FAQ * fixed typos in some scripts (psibuild-deb.sh, psibuild-tar.sh, psibuild.bsd, psibuild.sh) + added emoticons/lgs_15x15.jisp by lgs (trunk/iconsets/emoticons/lgs_15x15.jisp) * fixed deploy script to make more consistent diffs (trunk/scripts/posix/deploy) * avoid clearing message history after clearing chat window (psi-typed-history.diff) + supports for native tabs moving in qt 4.5 (sliding along tabbar;) (psi-movable-tabs-qt45-and-custom-style.diff) + customized vcard dialog title hints (psi-customize-dialogs-title-hint.diff) * set correct window flags for some dialogs (psi-customize-dialogs-title-hint.diff) * more translatable for psi-contact-manager.diff + added "close" button for tabs (psi-movable-tabs-qt45-and-custom-style.diff) + adding psi-account-menu-privacy-item.diff * psi-customize-dialogs-title-hint.diff: set title hint for privacy dialog * closing tabs on middle button release (psi-movable-tabs-qt45-and-custom-style.diff) * once again small fix on middle button release (psi-movable-tabs-qt45-and-custom-style.diff) + added psi-fix-lang-detect.diff by mblsha * more translatable for screenshotplugin, updated version to 1.3 + added checkbox for hash coloring (psi-muc-nick-hash-color.diff) * psi-customize-dialogs-title-hint.diff: set window flags for some dialogs - psi-fix-lang-detect.diff in upstream * hide for eventdlg in headlines (psi-muc-minimize-to-roster.diff) * psi-customize-dialogs-title-hint.diff: set window flags for options dialog * psi-account-menu-privacy-item.diff: renamed to Privacy Lists * fixed is#152 (psi-movable-tabs-qt45-and-custom-style.diff) * updated emoticons mra_set03.jisp to v0.4 (trunk/iconsets/emoticons/mra_set03.jisp) * psi-customize-dialogs-title-hint.diff: add preprocessor wrappers for qt < 4.5.0 * updated 050-psi-muc-topic.diff + autoreplyplugin: added exclusion by jid's, v0.0.3, still unstable (by Dealer_WeARE), http://psi-plus.com/wiki/plugins#autoreply_plugin * renamed 'psiplus' marked patches * psi-movable-tabs-qt45-and-custom-style.diff: fixed qt443 compilation + autoreplyplugin: added ability to set number of auto messages sent (GMail is not supported... yet...), started changelog.txt, v0.0.6 (by Dealer_WeARE) * updated faq.wiki: Ответ 9.2: Rhythmbox: http://sites.google.com/site/thesomeprojects/main-1/home * PKGBUILD: handle webkit configuration properly * fixed psi-receipts.diff on solaris * PKGBUILD: fixes and optimization * some fixes for tabs and close buttons (psi-movable-tabs-qt45-and-custom-style.diff) * updated autoreplyplugin, v0.0.7 (by Dealer_WeARE): added exclusion for juick@juick.com and psi-dev@c.j.ru disabled autoreply for active tab reduced the probability of false auto messages * autoreplyplugin: fixed conflicts with juickplugin, v0.0.8 (by Dealer_WeARE) * customize muc find dialog (psi-customize-dialogs-title-hint.diff, psi-muc-finddlg-swap-buttons.diff) + added new patch for parsing embed data in img src (psi-img-src-data.diff) * updated juickplugin to v0.9.2, see changelog * handle percent encoded data as well (psi-img-src-data.diff) * some more fixes for movable tabs (psi-movable-tabs-qt45-and-custom-style.diff) ! updated russian localization (2009-10-10) ! updated Qt to v4.5.3 2009-09-20 zet v0.14.1008 Beta * поправлены скрипты psibuild-deb.sh, deploy, psibuild.sh * MUC: переписан патч для топика конференции (psi-muc-topic.diff, psi-receipts.diff, psi-send-button-context-menu.diff) * MUC: исправлено отображение ника автора топика (psi-muc-topic.diff) * MUC: настройка цвета текста для статусного сообщения. отключен курсив для статусного сообщения (psi-muc-topic.diff, psi-add-options-color-highlighting.diff) + PLUG: добавлена опция ВКЛ/ВЫКЛ для нотификаций о новых письмах 'notification on/off' (gmailnotifyplugin, версия 0.3.8) * MUC: возможность смены цвета шрифта топика (psi-muc-topic.diff) * исправления для src/libpsi в psibuild-tar.sh + добавлен патч на добавление команды удалённого управления для выхода Psi+ из конференции(й) (added psi-adhoc-leave-muc.diff) - отключен нестабильный патч psi-autojoin-dialog-tab-autofoucus-off.diff (перемещён в /dev) * сохранён старый скрипт PKGBUILD и добавлен новый скрипт PKGBUILD (trunk/scripts/posix/PKGBUILD, trunk/scripts/posix/PKGBUILD.old) * исправлена опечатка в psi-adhoc-leave-muc.diff ('Leave all conferenceS') * PKGBUILD: исправлено присвоение имени ОС для пакета в процессе сборки * PKGBUILD: исправление для сборки плагинов (использование qconf) * PKGBUILD: исправлены зависимости в пакете, добавлены некоторые опции для самостоятельных сборок * мелкий фикс для psi-adhoc-leave-muc.diff ('Leave All Conferences') * важное исправление для psi-typed-history.diff: теперь корректно обрабатываются сообщения из истории ввода, содержащие -коды (спасибо RomanU) ! MS Installer: улучшена работа с реестром при установке/удалении/обновлении приложения (HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Psi+ DisplayVersion HelpLink Publisher URLInfoAbout) -- * src/libpsi submodule export fix (psibuild-deb.sh) * MUC: reworked muc topic (psi-muc-topic.diff, psi-receipts.diff, psi-send-button-context-menu.diff) * fixed some posix scripts (deploy, psibuild.sh) * MUC: fix nick in topic (psi-muc-topic.diff) * fixed submodule export src/libpsi/tools/zip and src/libpsi/tools/idle (psibuild-deb.sh) * MUC: disabled body for topic. other font (psi-muc-topic.diff) * MUC: color setting for additional text. removed italic (psi-muc-topic.diff, psi-add-options-color-highlighting.diff) + PLUG: added option 'notification on/off' (gmailnotifyplugin, v0.3.8) * MUC: improved muc topic patch (psi-muc-topic.diff) * MUC: changed default color (psi-muc-topic.diff) * MUC: improved topic (psi-muc-topic.diff) * fix src/libpsi in psibuild-tar.sh + added psi-adhoc-leave-muc.diff - disable buggy psi-autojoin-dialog-tab-autofoucus-off.diff * save old PKGBUILD and upload new (trunk/scripts/posix/PKGBUILD, trunk/scripts/posix/PKGBUILD.old) * typo in psi-adhoc-leave-muc.diff ('Leave all conferenceS') * PKGBUILD: fix os name for package during build process * PKGBUILD: fix plugins build; using qconf * PKGBUILD: fix package dependences; provide some settings for custom building * fixed psi-adhoc-leave-muc.diff * fixed psi-typed-history.diff - correct parsing for messages with codes (thx to RomanU) ! MS Installer: improved add/remove/modify operations with registry (HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Psi+ DisplayVersion HelpLink Publisher URLInfoAbout) 2009-08-24 zet v0.14.956 Beta * фиксы размеров фото в vCard (psi-vcard-photo-addons.diff) * улучшения в форме vCard (psi-vcard-photo-addons.diff, psi-vcard-more-fields-and-buttons.diff) + добавлена обработка css для чатов (psi-chat-css.diff) * обновлены win32-скрипты после тестов на MS Windows 7 RC (make-paused.cmd, make.cmd) + добавлен патч на изменение некоторых иконок (psi-change-some-icons.diff) * обновлён патч psi-avcalls-addons.diff * vCard: фикс цвета фона для поля с датой рождения (psi-vcard-more-fields-and-buttons.diff) * PLUG: обновлён translateplugin до версии 0.3.0 * переписан патч psi-vcard-more-fields-and-buttons.diff * vCard: фиксы для Джастина (psi-vcard-photo-addons.diff, psi-vcard-more-fields-and-buttons.diff) * подправлен патч psi-change-some-icons.diff на предмет вывода иконок для аудио/видео-звонков + новый виджет для некоторых полей (psi-lineedit-with-action-widget.diff) -- не завершён + добавлен новый системный иконпак от maj (summer-system.jisp, v0.1) * обновлён psiplus.jisp до версии 0.3.13 * фикс в патче psi-all-in-one-window.diff. специально для Nirdosh + PLUG: добавлен патч psi-access-to-applicationinfo-from-plugins.diff + форма vCard теперь поддерживает новый виджет (psi-lineedit-with-action-widget.diff, psi-vcard-more-fields-and-buttons.diff) * обновлена avcalls.wiki (на jabber.ru появился собственный stun-сервер: " stun.jabber.ru:5249 ") + добавлен иконпак для activities (спасибо Gajim) -- задел на будущеее. пока не работает. + добавлены билды для Alt Linux на страницу скачивания (спасибо Arc) + добавлен иконпак для ростера dictionary_es.jisp * некоторые обновления для нового виджета (psi-lineedit-with-action-widget.diff, psi-vcard-more-fields-and-buttons.diff, psi-muc-bookmark-toolbar-button.diff) ! MUC: патч psi-muc-user-count.diff принят в основную ветку Psi ! немного перереботанный патч psi-chat-nick-colors.diff принят в основную ветку Psi * MUC: небольшие фиксы патча psi-muc-nick-hash-color.diff * фикс расположения опций в патче psi-add-options-color-highlighting.diff * обновлён патч psi-xmpp-uri-in-chat-mode.diff + правильное вычисление высоты для ActionLineEdit в Qt 4.5. Всё ещё сломано в Qt 4.4 (psi-lineedit-with-action-widget.diff) * фиксы метки имени в патче psi-add-options-color-highlighting.diff * MUC: исправлена работа с кнопкой bookmark в контекстном меню ! патч psi-idle-mcmd-command.diff принят в основную ветку Psi * MUC: фиксы при редактировании "быстрых" закладок (psi-muc-bookmark-toolbar-button.diff) * исправлен краш http://code.google.com/p/psi-dev/issues/detail?id=137 в патче psi-vcard-photo-addons.diff) * исправления в патче psi-vcard-more-fields-and-buttons.diff * фиксы в патчах psi-lineedit-with-action-widget.diff, psi-vcard-more-fields-and-buttons.diff и psi-muc-bookmark-toolbar-button.diff * обновлён fingerprint.jisp до версии 0.1.7 + добавлено определение клиента Isida-bot от Disabler (psi-client-icons.diff) * vCard: небольшие графические улучшения, группировка полей vCard в патче psi-vcard-more-fields-and-buttons.diff ! патчи psi-lineedit-with-action-widget.diff, psi-vcard-photo-addons.diff и psi-vcard-more-fields-and-buttons.diff приняты в основную ветку Psi * автокомпиляция всех плагинов, фикс копирования ресурсов в патче psi-debianize.diff * автокомпиляция qconf, фиксы под обновлённый патч psi-debianize.diff (trunk/scripts/posix/psibuild-deb.sh) * PLUG: обновлён juickplugin до версии 0.7.1, подробнее в juickplugin/changelog.txt -- * fixed vcard photo sizes on dialog init (psi-vcard-photo-addons.diff) * some vcard improvements (psi-vcard-photo-addons.diff, psi-vcard-more-fields-and-buttons.diff) + css for chat view (psi-chat-css.diff) * updated win32 scripts after testing in win7 (make-paused.cmd, make.cmd) + added psi-change-some-icons.diff * updated psi-avcalls-addons.diff * vCard: fix background color for birthday field (psi-vcard-more-fields-and-buttons.diff) * PLUG: updated translateplugin to v0.3.0 * rewritten psi-vcard-more-fields-and-buttons.diff * vcard fixes for justin (psi-vcard-photo-addons.diff, psi-vcard-more-fields-and-buttons.diff) * fixed psi-change-some-icons.diff for chatdlg avcall icon + new widget. not complete (psi-lineedit-with-action-widget.diff) * fixed psi-lineedit-with-action-widget.diff compilation. still incomplete + added summer-system.jisp v0.1 (by maj) * updated psiplus.jisp v0.3.13 * fixed psi-all-in-one-window.diff. special for Nirdosh + PLUG: added psi-access-to-applicationinfo-from-plugins.diff + vcard with new lineedit widget (psi-lineedit-with-action-widget.diff, psi-vcard-more-fields-and-buttons.diff) * updated avcalls.wiki (stun.jabber.ru:5249) + added activities icons (thanx to Gajim) -- not implemented + added Alt Linux builds to download page (thanks to Arc) + added dictionary_es.jisp roster iconset * some updates with new edit widget (psi-lineedit-with-action-widget.diff, psi-vcard-more-fields-and-buttons.diff, psi-muc-bookmark-toolbar-button.diff) * fixed psi-muc-topic.diff after last commits ! psi-muc-user-count.diff in mainstream ! a bit reworked psi-chat-nick-colors.diff in mainstream. * small fix for psi-muc-nick-hash-color.diff * fixed appearanace layout for psi-add-options-color-highlighting.diff * updated psi-xmpp-uri-in-chat-mode.diff + compute ActionLineEdit height with qt-4.5 proper way. still broken with qt-4.4 (psi-lineedit-with-action-widget.diff) * fixed label name for psi-add-options-color-highlighting.diff * fixed empty row for psi-add-options-color-highlighting.diff * fixed code style for psi-add-options-color-highlighting.diff * fixed bookmark in context menu ! psi-idle-mcmd-command.diff in the mainstream * fixed regression for psi-muc-bookmark-toolbar-button.diff * not remove but edit - second state (psi-muc-bookmark-toolbar-button.diff) * fixed http://code.google.com/p/psi-dev/issues/detail?id=137 (psi-vcard-photo-addons.diff) * fixed psi-vcard-more-fields-and-buttons.diff * majish bug fixed. some ui improvements (psi-lineedit-with-action-widget.diff, psi-vcard-more-fields-and-buttons.diff, psi-muc-bookmark-toolbar-button.diff) * updated fingerprint.jisp to v0.1.7 + added detection of Isida-bot by Disabler (psi-client-icons.diff) * vcard: small ui improvement. moving fields (psi-vcard-more-fields-and-buttons.diff) ! some patches in the mainstream (psi-lineedit-with-action-widget.diff, psi-vcard-photo-addons.diff, psi-vcard-more-fields-and-buttons.diff) * autocompile for all plugins, fix resource copying from share/* (psi-debianize.diff) * qconf autocompile, fixes for new debianize patch (trunk/scripts/posix/psibuild-deb.sh) * PLUG: updated juickplugin to v0.7.1, see juickplugin/changelog.txt 2009-08-09 zet v0.14.875 Beta ! начата работа над Psi+ v0.14 Beta * PLUG: исправлен краш в translateplugin + добавлены новые иконсеты: nuvola-roster.jisp и nuvola-system.jisp + MUC: добавлен патч на быстрое добавление/удаление закладок через кнопку на тулбаре + MUC: добавлена возможность задать ник при быстром добавлении конференции в закладки (psi-muc-bookmark-toolbar-button.diff) + теперь используются иконки из набора psiplus icons для кнопки добавления/удаления закладки + добавлен патч на портабельность (win32) -- тестовый режим * поправлен psibuild-deb.sh * переписан патч psi-debianize.diff под новый скрипт psibuild-deb.sh (спасибо ivan1986) + добавлен скрипт psibuild-tar.sh + добавлен патч на выбор цвета для гиперссылок, psi-configure-hyperlink-color.diff (спасибо Shura0) * обновлён psiplus.jisp до версии 0.2.12 + добавлен патч, исправляющий работу с xhtml (psi-bf-xhtml-with-plugins-enabled.diff) - патч psi-bf-xhtml-with-plugins-enabled.diff принят в основную ветку Psi * обновлена иконка application.icns для Mac OS X + PLUG: добавлен патч psi-add-icons-to-iconfactory-from-plugins.diff * PLUG: обновлён translateplugin до версии 0.2.2 + PLUG: добавлен changelog.txt для juickplugin * PLUG: обновлён gmailnorifyplugin до версии 0.3.7 * PLUG: обновлён juickplugin до версии 0.6.2 + добавлен патч на увеличение количества полей в vCard (psi-vcard-addons.diff) -- XEP-0054 * исправлено позиционирование формы календаря для psi-vcard-addons.diff * задан формат даты рождения YYYY-MM-DD для psi-vcard-addons.diff - MUC: патч psi-muc-kickban-reasons.diff принят в основную ветку Psi * рефакторинг патчей для vCard + PLUG: добавлен патч psi-access-to-activetab-from-plugins.diff * слияние/реорганизация патчей для vCard -- ! Psi+ v0.14 Beta started * PLUG: screenshotplugin -- shortcuts rewritten * PLUG: fixed crash in translate plugin + added new iconsets: nuvola-roster.jisp & nuvola-system.jisp + MUC: added easy way to bookmark conference using button on central toolbar * MUC: fully functional bookmark button * MUC: ability to set a nickname for psi-muc-bookmark-toolbar-button.diff + use psiplus icons for add/remove bookmark button + added psi-portable patch (win32) * resource download fix, plugins download from svn and compile feature, code refactoring for psibuild-deb.sh * rewrited psi-debianize.diff for new psibuild-deb.sh, based on ivan1986 specs + added script for build source tar.bz2 from svn & git (psibuild-tar.sh) * fix errors in psibuild-deb.sh * fix psi-debianize patch * add call qconf to psibuild-deb.sh * psibuild-deb.sh patches for plugins + added psi-configure-hyperlink-color.diff (thanks to Shura0) * updated psiplus.jisp to v0.2.12 * fix psibuild-deb.sh - mkdir + fix for xhtml with enabled plugins (psi-bf-xhtml-with-plugins-enabled.diff) - psi-bf-xhtml-with-plugins-enabled.diff in main stream * updated application.icns for Mac OS X + PLUG: added psi-add-icons-to-iconfactory-from-plugins.diff * PLUG: updated translateplugin to v0.2.2 + PLUG: added changelog.txt for juickplugin * PLUG: update gmailnorifyplugin to v0.3.7 * PLUG: update juickplugin to v0.6.2 + new fields in vcard (psi-vcard-addons.diff) -- XEP-0054 * fix calendar positioning (psi-vcard-addons.diff) * changed date format for bday (psi-vcard-addons.diff) -- YYYY-MM-DD * some ui improvements for psi-vcard-addons.diff - MUC: psi-muc-kickban-reasons.diff in main stream * combine some vcard patches * vcard patches refactoring * psi-vcard-photo-addons.diff is ready for infiniti - removed wierd button-pilotka * max size for vcard photo (psi-vcard-photo-addons.diff) + PLUG: added psi-access-to-activetab-from-plugins.diff * reorganize vcard patches 2009-07-28 zet v0.13.795 Release * одинаковое расстояние для аватаров в ростере * обновлён juickplugin до версии 0.4.5 - добавлена опция "использовать номер сообщения как ресурс" (каждый новый тред сообщений открывается в отдельном табе) * полностью работоспособные ярлыки для плагинов * обновлён translateplugin до версии 0.2.1 ! Psi+ 0.13 зарелизилась! :) -- * equal space for avatars in roster. special for maj * typo in About+ dlg * updated juickplugin to v0.4.5 - added option 'use message id as resource' * fully working shortcuts for plugins * updated translateplugin version to 0.2.1 ! Psi+ 0.13 released! :) 2009-07-26 zet v0.13.783 RC4 ! начиная с v0.13.683 сборки Psi+ выпускаются с поддержкой системы проверки орфографии Aspell. в комплекте идёт только aspell-15.dll. словари для проверки орфографии необходимо скачивать отдельно. На данный момент рекомендуется использовать совмещённый словарь English+Russian с поддержкой буквы "Ё" отсюда: http://psi-dev.googlecode.com/files/aspell-ruen.exe (распаковать содержимое архива в каталог Psi+). Для включения функции проверки орфографии необходимо в настройках Psi+ на вкладке Misc. отметить галку [v] Check spelling * контакт-менеджер: некоторые графические улучшения. обновление элементов при групповом переносе * обновлены logo, psiplus.jisp и fingerprint.jisp (спасибо lgs) + добавлена новая иконка приложения Psi+ (app.ico) + опциональная автокалибровка размера иконок в логе сообщений в зависимости от размера шрифта * фикс для says style * обновлён translateplugin до версии 0.1.7 + контакт-менеджер: экспорт контактов * бинарники плагинов (*.dll) перенесены из svn на страницу скачиваний * qtcurve.dll перенесён на страницу скачиваний * скрипт PKGBUILD for ABS перенесён со страницы скачиваний в trunk/scripts/posix * build script for dеb-based Linux (fix #1) перенесён со страницы скачиваний в trunk/scripts/posix * скрипт PKGBUILD for psi-media making перенесён со страницы скачиваний в trunk/scripts/posix + добавлено определение клиента Yabber Instant Messenger * обновлён fingerprint.jisp до версии 0.1.6 + контакт-менеджер: импорт контактов + круглые уголки для аватарок в ростере + добавлен jisp для ростера (qipinfium.jisp) * аватары в ростере: фикс расположения элементов ростера при обновлении аватары + убраны стрелки с кнопок со смайлами и с шаблонами (psi-send-button-context-menu.diff) + добавлена опция на выбор цвета подсвечиваемого сообщения (вкладка настройки шрифтов и цветов) + добавлена инструкция по сборке Psi+ под Mac OS X, http://code.google.com/p/psi-dev/wiki/mac_osx * изменено расположение и порядок иконок на централ-баре. av-вызовы в контекстном меню * поправлено присвоение имени версии в скриптах (psibuild-deb.sh, psibuild.bsd, psibuild.sh) * восстановлена отрисовка иконки на отсоединённом табе (psi-tab-status-icon.diff) * обновлён juickplugin до v0.4.1 * исправлена перерисовка иконки на табе после сворачивания/разворачивания * исправлено присвоение иконки для функции посылки персонального статуса * обновлён psiplus.jisp до версии 0.2.10 ! Beta -> RC4 в названии версии и капсе -- ! aspell support (aspell-15.dll) + english+russian dictionary with "Ё" supporting http://psi-dev.googlecode.com/files/aspell-ruen.exe (unpack archive content to Psi+ dir). for activate this feature goto Psi+ options, Misc. tab and enables checkbox [v] Check spelling * contactmanager: some ui improvements. key events. fix updating on move to group * updated logo, psiplus.jisp, fingerprint.jisp (thanks to lgs) + added app.ico + option to scale messages'es icons * more correct size computation for message icons * fixed says style * updated translateplugin to 0.1.7 + contactmanager: export contacts * moved win32 plugins (*.dll) to downloads page * moved qtcurve.dll to downloads page * moved PKGBUILD for ABS script from downloads page to scripts/posix dir * moved build script for dеb-based Linux (fix #1) from downloads page to scripts/posix dir * moved PKGBUILD for psi-media making from downloads page to scripts/posix dir + added detection Yabber Instant Messenger * updated fingerprint.jisp to v0.1.6 + contactmanager: import contacts * separate icon for send custom status + roster avatars round corners + added roster jisp (qipinfium.jisp) * avatars in roster: invalidate height on update avatar * roster avatars: proper item's height recomputation on any avatar size + shortcut accessor for plugins + kill arrow for psi-send-button-context-menu.diff + added options color highlighting + created mac_osx.wiki * a bit more elegant patch. but needs more work * codestyling a bit improved for psi-send-button-context-menu.diff * move icons on central bar. avcall icon in context menu * fixed scripts after rc (psibuild-deb.sh, psibuild.bsd, psibuild.sh) * keep tab icon on detach for psi-tab-status-icon.diff * update juickplugin to v0.4.1 * muc icons on tabs restored after hide/show * fixed icons in psi-send-custom-status.diff * updated psiplus.jisp to v0.2.10 ! Beta -> RC4 for version name and caps 2009-07-12 zet v0.13.680 Beta * поправлен эскейпинг для системных сообщений (psi-muc-topic.diff) ! поправлен критический баг с html-никами в конференции (psi-muc-topic.diff) * обновлён gmailnotifyplugin до версии 0.3.6 + добавлено определение клиента Sameplace Mozilla-based IM (psi-client-icons.diff) + добавлен juickplugin (текущая версия 0.1.2) + добавлен патч psi-xmpp-uri-in-chat-mode.diff * переписан скрипт psibuild.sh (http://psi-dev.googlecode.com/svn/trunk/scripts/posix/psibuild.sh) + добавлен скрипт psibuild для сборки Psi+ в BSD-системах (http://psi-dev.googlecode.com/svn/trunk/scripts/posix/psibuild.bsd) * обновлён системный иконпак psiplus.jisp + добавлен смайлпак QIP_emoticons.jisp + добавлена команда "paste and send" в контекстное меню на централбаре * изменена позиция кнопки "paste and send" на централбаре + добавлена кнопка вызова шаблонов на централбаре (psi-send-button-context-menu.diff) + добавлен патч psi-fix-ns-correcting.diff + добавлено определение клиента Juick (psi-client-icons.diff) * обновлён иконпак с иконками jabber-клиентов fingerprint.jisp до версии 0.1.4 + добавлены патчи от Thilo (Ephraim), http://flyspray.psi-im.org/task/855 (psi-implements-iq-version-stuff-in-discodlg.diff, psi-add-feature-jabber-iq-version-to-psi-s-feature-list.diff, psi-add-iq-version-feature-stuff.diff) + добавлена команда вызова шаблонов из контекстного меню на централбаре * поправлено обновление списка шаблонов на тулбаре + добавлен патч на вывод версии плагина в опциях (psi-add-plugin-version-to-options-tab.diff) + translateplugin: добавлена команда для отмены конвертации * обновлён translateplugin до версии 0.1.6 * обновлено содержимое информационного окна "About Psi+" (about-psi-plus.diff) * исправлен патч psi-xmpp-uri-in-chat-mode.diff ! обновлена русская локализация до ревизии 108 -- * reverted escaping of sysmessage (psi-muc-topic.diff) ! fixed critical bug with html in nicknames in muc (psi-muc-topic.diff) * updated gmailnotifyplugin to v0.3.6 (src & dll) + added Sameplace Mozilla-based IM detection (psi-client-icons.diff) + added juickplugin v0.1.2 (src & dll) + added psi-xmpp-uri-in-chat-mode.diff * fixed psibuild.sh (http://psi-dev.googlecode.com/svn/trunk/scripts/posix/psibuild.sh) + added psibuild for bsd (http://psi-dev.googlecode.com/svn/trunk/scripts/posix/psibuild.bsd) * mime for psibuild.bsd * updated psiplus.jisp + added QIP_emoticons.jisp + added "paste and send" in central toolbar context menu * change position "paste and send" in central toolbar * updated psi-xmpp-uri-in-chat-mode.diff + added templates in central toolbar (psi-send-button-context-menu.diff) + added psi-fix-ns-correcting.diff for fix default account for uri + added Juick client detection (psi-client-icons.diff) * updated fingerprint.jisp to v0.1.4 + added patches by Thilo (Ephraim) from http://flyspray.psi-im.org/task/855 (psi-implements-iq-version-stuff-in-discodlg.diff, psi-add-feature-jabber-iq-version-to-psi-s-feature-list.diff, psi-add-iq-version-feature-stuff.diff) + added templates in toolbar context menu * fixed update templates in toolbar + added psi-add-plugin-version-to-options-tab.diff + translateplugin: added revert conversion * updated translateplugin to v0.1.6 (src & dll) * updated About Psi+ dlg (about-psi-plus.diff) * fixed psi-xmpp-uri-in-chat-mode.diff ! updated russian localization (r108) 2009-07-05 zet v0.13.643 Beta + начата работа над иконками для всех событий чатлога (http://code.google.com/p/psi-dev/issues/detail?id=2) + добавлен патч для вызова контекстного меню на кнопке Send (psi-send-button-context-menu.diff) * обновлены psi-receipts.diff, psi-roster-icons.diff * обновлён VKontakte.ru.jisp + добавлена кнопка Paste & Send на тулбар + добавлена команда "Only Paste" в меню с шаблонами * обновлён psiplus.jisp для меню с шаблонами, обновлён патч "Paste & Send" * изменена иконка для кнопки Paste & Send + контакт-менеджер: перемещение одного или нескольких контактов в заданную группу + добавлен патч на выход из приложения по нажатию кнопки закрытия ростера (quit-on-close-option.diff) * поправлен фокус после вызова контекстного меню на кнопке Send * изменён внешний вид контекстного меню на кнопке Send * обновлён gmailnotifyplugin до версии 0.3.5 (VampiRUS) -- нестабилен + добавлен translateplugin от VampiRUS (текущая версия 0.1.4) + включена поддержка плагинов в psibuild.sh + добавлена основная инструкция по компиляции Psi+ в Linux (http://code.google.com/p/psi-dev/wiki/linux) + увеличен интервал между опросами нажатия на кнопку "Paste & Send" + добавлен патч на вызов функции выгрузки плагинов перед выходом из приложения (VampiRUS) + добавлен патч на вызов дисконнекта в деструкторе (VampiRUS) + plugins.wiki: добавлено описание для translateplugin + контакт-менеджер: добавлен вызов контекстного меню на контакте + контакт-менеджер: иконки для контекстного меню + добавлена иконка голосового вызова на централбар * обновлён psiplus.jisp до версии 0.2.5 * обновлены скрипты для автоматической компиляции Psi+ в MS Windows * изменено положение меню голосовых вызовов в групчате * контакт-менеджер: некоторые графические улучшения (специально для maj) + добавлен патч psi-fix-ability-to-modify-events-from-plugins.diff (VampiRUS) ! обновлён Qt до версии 4.5.2 ! обновлена русская локализация (ivan101) -- + started work on icons for everything in chatlog (http://code.google.com/p/psi-dev/issues/detail?id=2) * get rid 1px align for psi-receipts.diff + added patch psi-send-button-context-menu.diff * read image resources on log clear * updated psi-receipts.diff, psi-roster-icons.diff * updated VKontakte.ru.jisp + added button "Paste & Send" in toolbar + added options "Only Paste" in template menu * updated psiplus.jisp for Paste and send templates * updated Paste and Send patch * change icon for Paste and Send button + contactmanager: move to group + added new patch quit-on-close-option.diff * fixed quit-on-close-option.diff: enable checkbox on init * fix focus after call send button menu * change aspect send button menu * updated gmailnotifyplugin by VampiRUS to v0.3.5 -- still unstable + added translateplugin by VampiRUS (v0.1.4) * psibuild.sh with plugins support * wiki: general linux build instructions (http://code.google.com/p/psi-dev/wiki/linux) + click limit for paste send button + added unload all plugins on exit function by VampiRUS + added discconnect short to destructor by VampiRUS + plugins.wiki: translateplugin + contactmanager: context menu + contactmanager: context menu icons + avcall icon on central bar * updated psiplus.jisp to v0.2.5 * updated win32 scripts * avcall menu item in gc right under messages * contactmanager: some ui improvements (special for maj) + added psi-fix-ability-to-modify-events-from-plugins.diff by VampiRUS ! updated Qt to v4.5.2 ! updated russian localization by ivan101 (r100) 2009-06-21 zet v0.13.593 Beta + контакт-менеджер: возможность найти и выбрать контакт * muc-nick-hash-color теперь включен по умолчанию + иконки для psi-receipts.diff, psi-roster-settings.diff и psi-roster-icons.diff теперь берутся из специализированного системного иконпака psiplus.jisp * костыль для работы all-in-one-window в Mac OS X * переименован заголовок диалога "About Psi" в "About" * контакт-менеджер: исправлено падение приложения при смене домена * исправлена загрузка распакованных иконсетов при старте приложения + добавлен путь по умолчанию для psi-additional-system-iconset.diff + добавлена иконка для кнопки Send (Отправить) * небольшая оптимизация кода для топика конференции * обновлён psiplus.jisp + контакт-менеджер: добавлено действие "Удалить" + контакт-менеджер: добавлена группа действий "Авторизация" * контакт-менеджер: увеличена щирина диалогового окна - контакт-менеджер: убран неиспользуемый код * контакт-менеджер: небольшие улучшения * обновлён copying.txt (GPL Version 2.1) + скриншот-плагин: добавлена опция для ввода url-адреса сервера и горячей клавиши вызова плагина, подробнее на http://psi-plus.com/wiki/plugins + скриншот-плагин: добавлен ftp-менеджер для загрузки файлов на ftp-сервер. если не указан url-адрес для ftp-сервера, то графический файл будет сохранён на локальный жёсткий диск + скриншот-плагин: добавлено сообщение об ошибке при неудачной загрузке на ftp-сервер * обновлён screenshotplugin.dll для MS Windows + контакт-менеджер: добавлены кнопки управления окном (maximize, minimize) + скриншот-плагин: добавлена опция для назначения имени графического файла * скриншот-плагин: изменён основной класс окна с Widget на Dialog. добавлена реакция на Esc для закрытия окна со скриншотом + скриншот-плагин: добавлена кнопка "Загрузить на FTP". если не указан url-адрес в опциях, то кнопка "Загрузить на FTP" будет скрыта * обновлён screenshotplugin.dll для MS Windows + добавлен некоторый функционал для вызова голосовых звонков (команда в контекстном меню на контакте ростера конференции) + добавлена кнопка "Звонок" в тулбар окна чата * контакт-менеджер: исправлен баг с обновлением контактов после резолвинга ников и при других операциях. возможно исправлено падение приложения * обновлён gmailnotifyplugin (src & dll) ! добавлен инсталлятор для PsiMedia версии 1.0.3 (win32). подробнее на http://code.google.com/p/psi-dev/wiki/avcalls -- + contactmanager: ability to find and select contacts * muc-nick-hash-color now enabled by default + go to use psiplus.jisp in: psi-receipts.diff, psi-roster-settings.diff, psi-roster-icons.diff * ugly fix for mac in all-in-one-window. we should get rid this ugly patch one day... * About Psi -> About * contactmanager: possibly fix crash on changing domain * fix icon search path for unpacked sets + added default icon path for psi-additional-system-iconset.diff + added icon for send button * small optimization topic dialog * updated psiplus.jisp + contactmanager: added remove action + contactmanager: added auth actions * contactmanager: increased dialog's width - contactmanager: removed unnecessary includes * contactmanager: small improvements * updated copying.txt (GPL Version 2.1) + screenshotplugin: added url and shortcut options. see at http://psi-plus.com/wiki/plugins for details * screenshotplugin: fixed reassing shortcut + screenshotplugin: added ftp upload, if url is empty picture will be save to local disk * screenshotplugin: fixed ftp errors + screenshotplugin: added options for message * screenshotplugin: fixed reading default message * screenshotplugin: changed date/time format for uploading files * updated win32/gmailnotifyplugin.dll & win32/screenshotplugin.dll + contactmanager: added maximize, minimize buttons * contactmanager: fix menu item unavailable if bookmarks unavailable + screenshotplugin: added file name format to options * screenshotplugin: changed Widget to Dialog main class. added Esc shortcut for close window + screenshotplugin: added upload button. if no url inputed upload button will be hidden * updated screenshotplugin.dll for win32 + some additional functionality for avcalls. muc menu item atm + add call button into toolbar of chat dialog + conditional adding of avcall menu items/buttons * contactmanager: fixing bug with not updatable contacts after nick resolve. part1 * fixed psi-add-detection-some-os.diff after last git commit * contactmanager: fixed updating contacts on resolve and other actions. possibly fixed possible crash * updated gmailnotifyplugin (src & dll) ! added PsiMedia Installer v1.0.3 (win32). see at http://code.google.com/p/psi-dev/wiki/avcalls for details 2009-06-07 zet v0.13.537 Beta + добавлены команды контекстного меню для формы с фото в vCard + добавлен патч на вызов специализированных системных иконок из иконпака psiplus.jisp + добавлен иконпак со специализированными системными иконками psiplus.jisp * менеджер контактов теперь умеет рассылать групповые сообщения выбранным контактам + добавлено определение клиента "Прогноз погоды с Gismeteo.RU" * обновлён fingerprint.jisp до версии r129.1 * фиксы контакт-менеджера для компиляции под win32 * контакт-менеджер: совместимость с Qt 4.4 + wiki: добавлен скрипт отката для *nix-систем ! исправлена критическая ошибка, приводившая к падению приложения при закрытии окна/таба конференции с открытым редактором топика ! патч на добавление контакт-менеджера перенесён в общий каталог с патчами и входит теперь в поставку Psi+ * исправлено поведение окна с фото в vCard (центрирование при первоначальном вызове) + добавлен патч на добавление опции для подтверждения очистки лога конференции/чата (psi-optional-confirmation-when-clearing.diff) + контакт-менеджер: добавлена функция "преобразовать в ники" + контакт-менеджер: начало работы над функцией "смена домена" * обновлены иконки и превьюшки на главной странице проекта (http://psi-dev.googlecode.com/) + добавлен плагин для уведомлений о новой почте с Gmail (gmailnotifyplugin by VampiRUS) + добавлен патч на поиск директории с плагинами для *nix-систем + добавлено определение клиентов: Мобильный Mail.Ru Агент для Symbian, CenterIM * обновлены иконпаки: psiplus.jisp, fingerprint.jisp - svn: убраны неиспользуемые иконки из директорий с иконпаками * обновлён патч psi-client-icons.diff * svn: устаревшие плагины перемещены в специальный каталог (deprecated) * контакт-менеджер: закончена работа над функцией "смена домена" + добавлен логотип Psi+ в меню "О программе" (About dlg) * gmail notify plugin обновлён до версии 0.2.4 (VampiRUS) * ограничение палитры цветов для патча на hash-раскраску ников (больше контраста для белого фона) * контакт-менеджер теперь готов для локализации + добавлен патч psi-chat-messages-subject-fix.diff для исправления положения темы в чат-сообщениях (спасибо VampiRUS) * gmail notify plugin обновлён до версии 0.3.1 (VampiRUS) ! обновлена русская локализация (ivan101) -- + added context menu in vcard photo dlg + added psi-additional-system-iconset.diff (load icons from psiplus.jisp) + added iconsets/psiplus/psiplus.jisp ver. r11.1 * contact manager is now spam manager. can send messages to selected contacts + added detection "Прогноз погоды с Gismeteo.RU" client. updated fingerprint.jisp to ver. r129.1 * contact manager: compatibility with qt-4.4 + wiki: added revert script for nix ! fixed crash when closing window with while topic editor is opened ! contact manager: added psi-contact-manager.diff to general patches dir (ready for use) * fix vcard photo + added patch psi-optional-confirmation-when-clearing.diff + contact manager: added resolve nicknames action + contact manager: started work on changing domain * updated thumbnails for Psi+ homepage + added gmail notify plugin by VampiRUS + added plugins search path for nix + added detection clients: Mail.ru sis MobileAgent, CenterIM * updated jisp: psiplus.jisp, fingerprint.jisp - remove unwanted icons from svn * updated psi-client-icons.diff * deprecated plugins moved to deprecated dir in svn * contact manager: changing domain is done * some cosmetic changes (psiplus logo in About dlg) * gmailnotifyplugin v0.2.4 by VampiRUS * limiting palettes (contrast for white background) for psi-muc-nick-hash-color.diff * contact manager is now translatable + added patch psi-chat-messages-subject-fix.diff * gmailnotifyplugin v0.3.1 by VampiRUS ! updated russian localization by ivan101 2009-05-24 zet v0.13.491 Beta + добавлено определение клиентов Sapo Messenger Mac и Spark IM Client * обновлён иконпак с ростерными иконками fingerprint.jisp до версии r128.1 + добавлено определение клиента Pandion + автоматический ресайзинг фото при изменении размеров окна с фоткой из vCard + добавлено определение платформы Arch Linux в Psi+ (спасибо Dill) * исправлено положение окна с фото из vCard при повторном вызове ! обновлена русская локализация от ivan101 -- + added clients detection: Sapo Messenger Mac, Spark IM Client * updated fingerprint.jisp to ver r128.1 + added detection of Pandion jabber client + resize clickable photo * adding detection of Arch Linux (thx to Dill) * fix central orientation new photo dlg ! updated russian localization (by ivan101) 2009-05-11 zet v0.13.469 Beta * уменьшен размер иконки о доставке сообщения + добавлен интервал в 1 пиксель между аватарами и иконками клиентов в ростере + добавлен сдвиг на 1 пискель влево от края ростера для аватаров + размер формы редактора топика сопоставим с размером окна самого группчата + теперь окно редактора топика немодальное (не блокирует работу с другими элементами Psi+) * восстановлена работа копирования по Ctrl+C в поле с JID (в окне ростерного чата) + добавлен исходный код для скриншот-плагина (тестовая версия) + добавлен патч на снижение агрессии автофокуса на форме присоединения к конференции (спасибо Nexor) + добавлено определение клиентов OM и freqbot * обновлён fingerprint.jisp до версии r127.2 + добавлен патч на выключение горизонтального скролла для ростера конференции * убрана блокировка копирования по Ctrl+C в случае если окно конференции потеряло связь с сервером - временно выключен патч на возможность перемещения табов (из-за нестабильности в работе) + добавлен скомпилированный скриншот-плагин для MS Windows (x86): http://psi-dev.googlecode.com/svn/trunk/plugins/win32/screenshotplugin.dll. инструкция по самостоятельной компиляции плагина: http://code.google.com/p/psi-dev/wiki/mingw32 * исправлено включение/выключение скриншот-плагина * исправлено поведение кнопки открытия редактора топика. теперь несколько нажатий не приводит к открытию нескольких редакторов * убран множественный вызов окна с фотографией при клике в форме vCard * обновлена вкладка "About Psi+" в Psi->Help->About * добавлен FAQ для пользователей Psi+ в Windows: http://code.google.com/p/psi-dev/wiki/faq_windows + добавлен патч и скрипт для сборки Psi+ под debian и debian-based системами -- * receipts icon size decreased + added 1 pix gap between roster icons + added 1 pix gap between avatar icons and roster edge + size topic dialog relatively groupchat window * topic dialog non modality * fixed Ctrl+C copy jid in chat dialog + added screenshot plugin sources (test version) + added psi-autojoin-dialog-tab-autofoucus-off.diff (thanks to Nexor) + added some clients detection (OM, freqbot). updated fingerprint.jisp to version r127.2 + added patch horizontal scrollbar in muc roster always off * fixed Ctrl+C copy when groupchat is offline - disable patch psi-movable-tabs.diff + added compiled screenshot plugin for win32: http://psi-dev.googlecode.com/svn/trunk/plugins/win32/screenshotplugin.dll. instruction for selfcompiling screenshotplugin: http://code.google.com/p/psi-dev/wiki/mingw32 * fixed enable-disable for screenshot plugin * fixed topic button in psi-muc-topic.diff * fixed clickable photo in psi-vcard-clickable-photo.diff * updated About Psi+ tab * added FAQ for Windows users: http://code.google.com/p/psi-dev/wiki/faq_windows + added debian package info for dpkg-buildpackage 2009-05-01 zet v0.13.433 Beta * исправлена критическая ошибка, приводившая к падению приложения при кике в конференции + добавлен патч на определение операционных систем Exherbo Linux и MS Windows 7 + добавлено распознавание гиперссылок в топике конференции при присоединении к конференции (спасибо AvadoN) + добавлен патч на возможность drag-n-drop-перемещения табов (работает в Qt 4.5.1) - убрано выделение jid-ов в логе конференции + добавлено определение клиента BombusQD * обновлена иконка по умолчанию для mail.ru агента в fingerprint.jisp * исправлено форматирование сообщения во всплывающем окне на топике + добавлено распознавание гиперссылок во всплывающем окне на топике * поправлено определение клиента Psi+ в psi-client-icons.diff (issue 3 #24) * исправлен баг с перетаскиванием табов (issue 81) * изменён алгоритм для отображения иконки о доставке сообщения: теперь вычисляется размер шрифта лога в пикселях - это и будет являться высотой иконки. в дальнейшем иконка каждый раз ресайзится, пропорционально своим размерам и отрисовывается + добавлена опция для отображение многострочного топика конференции в компактном виде options.muc.compact-multiline-topic (по умолчанию - выключено) + добавлено отображение смайлов во всплывающем окне на топике конференции ! обновлена версия Qt до 4.5.1 -- * fixed bug with kick in psi-muc-minimize-to-roster.diff + added patch psi-add-detection-some-os.diff for Exherbo Linux detection + added MS Windows 7 detection in psi-add-detection-some-os.diff + added topics' formatting in psi-muc-topic.diff (by AvadoN) + added patch psi-movable-tabs.diff - removed clickable for jids in muc log (psi-muc-topic.diff) + added detection of BombusQD mod client (psi-client-icons.diff) * changed mailru default client icon (psi-client-icons.diff) * updated fingerprint.jisp * added formatting message in topic tooltip (psi-muc-topic.diff) + links in topic tooltip (psi-muc-topic.diff) * small optimisation for psi-client-icons.diff (issue 3 #24) * fixed some bugs in psi-movable-tabs.diff (issue 81) * sized delivery notification icon (psi-receipts.diff) + added options 'Replace | in chat topic with newline' (psi-muc-topic.diff) * fix compact mode for chat topic (psi-muc-topic.diff) + added advanced option 'options.muc.compact-multiline-topic' (false by default) + added show smiles in tooltip (psi-muc-topic.diff) ! updated Qt version to 4.5.1 2009-04-19 zet v0.13.394 Beta - удалён ненужный патч на запрещение вызова функции голосовых звонков * строки из psi-idle-mcmd-command.diff готовы для локализации * исправлены умолчания для нового аккаунта * исправлена критическая ошибка, приводившая к падению приложения при попытке создания нового аккаунта после первого старта Psi (основная ветка) + добавлена возможность настройки параметров голосовых вызовов через графический интерфейс (основная ветка) ! новая версия библиотек OpenSSL 0.9.8k -- - deleted unused patch psi-disable-av-calls.diff * strings in mcmd is not-translable * fixed options after last git commits * changes to psiaccount belong in setUserAccount not in accountmodifydlg (mainline) + make avcall options normally accessible in the UI (mainline) ! new OpenSSL 0.9.8k version 2009-04-05 zet v0.13.381 Beta + добавлено определение клиента nimbuzz + теперь проверка наличия новой версии дистрибутива Psi+ происходит по состоянию файла version.txt - патч на портабельность ВРЕМЕННО выключен из-за глюков совместного использования Psi+ несколькими пользователями и глюками под не_админом (win32) + добавлен psi-portable.bat для запуска Psi+ с профилем из текущего каталога (Psi+\PsiData) + начата работа над совместимостью плагинной системы Psi для версии 0.13. подробнее на http://psi-plus.com/wiki/plugins * возвращено прежнее поведение фокуса после клика на фото в форме vCard (issue 69) * поправлено название опции "цвет ника для входящего/исходящего сообщения чата" * обновлена русская локализация (спасибо ivan101) -- + added detection of nimbuzz client + added version.txt for monitoring current version * updated psi-dirty-check.diff - temporary disabled patch psi-win32-portable.diff (rewrite needed) + added psi-portable.bat for start Psi+ with profile from current dir (Psi+\PsiData) * fixed Plugin System. Now it can save & restore options. added psi-plugins-options.diff + added test option plugin. It save-load int & string values. * fixed old plugins. Removed const from method options. added virtual methods (psi-fixed-plugins.diff) * reverted changes from r107 (170-psi-vcard-clickable-photo.diff) * updated text in 360-psi-chat-nick-colors.diff * updated russian localization (thanks to ivan101) 2009-03-29 zet v0.13.352 Beta * изменён размер формы About для более удобного отображения вкладки About Psi+ :) + добавлена копия changelog.txt в кодировке cp1251 + добавлен патч на hash-раскраску ников в конференциях (спасибо L29Ah). патч имеет advanced-настройки: • использовать hash-раскраску ников для истории конференции (по умолчанию - отключено) • включение/отключение hash-раскраски ников (по умолчанию - отключено) + добавлен патч, восстанавливающий работу Ctrl+C/Ctrl+V в обычных чатах (спасибо mpk) + добавлен патч "всё в одном" (ростер, чаты и конференции в одном окне) - данный режим экспериментальный и отключен по умолчанию + добавлено опеределение клиента mcabber и вывод его иконки в ростер + добавлено корректное отображение статуса конференции в ростере + добавлен патч, убирающий кнопки в заголовке панели с табами (отключаемо) * исправлена критическая ошибка, приводящая к падению приложения при попытке захода в конференцию по приглашению + добавлено определение клиентов Ya.Online j2me и Adium * обновлён fingerprint.jisp -- * updated about-psi-plus.diff + added changelog_cp1251.txt + added patch psi-muc-nick-hash-color.diff + added patch psi-chatdlg-copypaste-keypress.diff (thanks to mpk) + added patch psi-all-in-one-window.diff (experimental, disabled by default) * updated psi-muc-nick-hash-color.diff (thnx to L29Ah). added two advanced options: • options.ui.muc.colored-history (disabled by default) • options.ui.muc.use-hash-nick-coloring (disabled by default) + added mcabber detection for psi-client-icons.diff + added showing muc's status in roster for psi-muc-minimize-to-roster.diff + added patch psi-tabs-button-killer.diff * fixed some bugs with invite in psi-muc-minimize-to-roster.diff + added detection: Ya.Online j2me client, Adium * updated fingerprint.jisp 2009-03-22 zet v0.13.333 Beta + добавлена иконка ростера для JaPyT-транспорта * обновлены иконки для pyvk-t транспорта * добавлена информация о Psi+ в капсы + добавлен патч для автокорректировки размеров окна vCard (psi-vcard-smartsize.diff) + добавлен патч для отправки прямого статуса выбранному контакту ростера и/или в открытую конференцию psi-send-custom-status.diff (спасибо mpk) + добавлена иконка для меню Send status (psi-send-custom-status.diff) + добавлен патч на запрет вызова меню голосовых звонков (данная функция требует доработки) * переименованы патчи для win32 + добавлена новая вкладка в меню справки Help->About->About Psi+ с информацией об участниках проекта Psi+ -- + added roster icon for JaPyT in iconsets/clients/fingerprint.jisp * Replaced pyvk-t and added pyvk-t2 icon in iconsets/clients/fingerprint.jisp * updated Psi+ caps info (psi-application-info.diff) + added patch for autocorrect vCard fields size (psi-vcard-smartsize.diff) + added patch psi-send-custom-status.diff (thanks to mpk) + added icon send status for psi-send-custom-status.diff + added patch psi-disable-av-calls.diff * renamed psi-info-size.diff to psi-vcard-smartsize.diff * renamed win32-specific patches (win->win32) + added About Psi+ tab in Help->About menu (about-psi-plus.diff) 2009-03-15 zet v0.13.302 Beta + добавлены иконки для ростера в стиле группчатов (muc.jisp) + добавлен патч на проверку новых доступных версий Psi+ при старте приложения (спасибо AlekSi) + добавлен счётчик непрочитанных сообщений для свёрнутых в ростер табах (psi-muc-minimize-to-roster.diff) * поправлено поведение кнопки Delete при нажатии на свёрнутой в ростер конференции + теперь имя контакта для свёрнутой в ростер конференции берётся из имени закладки * поправлено положение индикатора доставки сообщения после очистки чата * добавлено отображение индикатора доставки сообщения для self-контактов и запрещено для невидимых контактов * поправлено отображение индикатора доставки сообщения для первого сообщения в новом чате * поправлена позиция положения индикатора доставки сообщения для чатов в режиме use-chat-says-style * исправлена http://code.google.com/p/psi-dev/issues/detail?id=58 * доработки psi-muc-minimize-to-roster.diff + добавлено определение клиентов Barracuda IM и Jimm, обновлён fingerprint.jisp ! в инсталляторе заменён vcredist_x86.exe на более свежий Microsoft Visual C++ 2008 SP1 Redistributable Package (x86), который не мусорит в корень жёсткого диска :) -- + added roster iconset muc.jisp + added patch psi-dirty-check.diff (thanks to AlekSi) + added count of unread message of conference into roster in psi-muc-minimize-to-roster.diff * fixed key 'del' bug with conferences in roster in psi-muc-minimize-to-roster.diff + added headlined unreaded message counter and getting name of conference from bookmarks into psi-muc-minimize-to-roster.diff * fix broken 1px align icon after chat clear * fixed receipts for self contact and from invisibility * fixed received icon position in use-chat-says-style mode and partially fixed issue 58 * fixed issue 58 * fixed some bugs in 320-psi-muc-minimize-to-roster.diff * fixed first receipt in muc private chat + added detection Barracuda IM, Jimm. Updated fingerprint.jisp ! added new vcredist_x86.exe from Microsoft Visual C++ 2008 SP1 Redistributable Package (x86) in Psi+ installer (no trash in root dir) 2009-03-10 zet v0.13.282 Beta + добавлен смайлпак puzazBox.jisp by puz + добавлена ростерная иконка для GluxiBot + добавлен патч, меняющий местами кнопки Apply и Cancel в окне настроек (win32) + добавлен смайлпак YaEmoMidget + добавлены иконки ростера Yandex Status Icons и Yandex Big Status Icons * поправлен патч psi-muc-notify-highlight.diff. добавлена опция options.ui.notifications.passive-popups.notify-every-muc-message: • true = показывает в попапах все сообщения из конференции • false = показывает попап только при обращении и при упоминании заданных в настройках слов + добавлены ярлыки для psi-typed-history.diff + добавлена опция вывода иконок клиентов в ростере: всех имеющихся ресурсов или только ресурса с наивысшим приоритетом. также убран знак вопроса в ростере у muc-контакта * поправлен патч psi-altN-key-switch-tab.diff + добавлен патч на регулировку времени показа всплывающих окон (спасибо Shura0) + добавлена иконка для клиента swift * Psi+Fingerprint.jisp переименован в fingerprint.jisp * поправлен баг с системными сообщениями в конференции (psi-muc-minimize-to-roster.diff) ! патч на совместимость с qt-4.5 принят в основную ветку (mainline) -- + added iconsets/emoticons/puzazBox.jisp by puz + added client-icon for GluxiBot + added psi-options-change-buttons-places.diff + added YaEmoMidget smiles + added: Yandex Status Icons, Yandex Big Status Icons * fixed psi-muc-notify-highlight.diff + added shortcuts for psi-typed-history.diff + realize some proposals for psi-client-icons.diff * fixed alt+n patch + added patch psi-passive-popups-delays.diff (thnx to Shura0, issue 27 closed) * updated Psi+Fingerprint.jisp (swift icon) * simple name for iconpack fingerprint.jisp * fixed bug with headline from conference in psi-muc-minimize-to-roster.diff 2009-03-05 zet v0.13.256 Beta + начат changelog.txt -- + changelog.txt started ==== Условные обозначения (Legend): + добавлено (added) - убрано (removed) * изменено (modified) ! важное сообщение (important message) ==== ! Если Вы хотите поддержать развитие проекта Psi+, то можете внести небольшое пожертвование на один из нижеприведённых счетов: http://passport.webmoney.ru/asp/certview.asp?wmid=351281428776 WMZ: Z217989559723 WMR: R347278107127 WME: E196199593868 Яндекс.Деньги: 41001348342725 -- ! If you want to support the development of the Psi+ Project, you can make a small donation to one of the following accounts: http://passport.webmoney.ru/asp/certview.asp?wmid=351281428776 WMZ: Z217989559723 WMR: R347278107127 WME: E196199593868 Yandex.Money: 41001348342725 psi-plus-snapshots-1.4.1456/INSTALL000066400000000000000000000014471370065651000166120ustar00rootroot00000000000000Installation ------------ -=[ Linux / Unix ]=- For instructions on how to build and install Psi on Unix systems, see the 'Compiling' section below. -=[ Windows ]=- No installation required for Windows. You can simply just run the Psi executable. You might want to move everything into C:\Program Files\Psi -=[ MacOS X ]=- Drag the Psi application file from the disk image into the Applications dir. Compiling --------- Requirements: - Qt 5.x.x - QCA 2.1.3 or higher. You can obtain Qt at https://www.qt.io/download/ You can obtain QCA at https://userbase.kde.org/QCA For information about building Psi on Unix, see doc/build-unix.txt. For information about building Psi on Mac OS X, see doc/build-mac.txt. For information about building Psi on Windows, see doc/build-win.txt. psi-plus-snapshots-1.4.1456/README000066400000000000000000000065201370065651000164360ustar00rootroot00000000000000This is a special repository with snapshots for Psi+ project: https://psi-plus.com/ Current address: https://github.com/psi-plus/psi-plus-snapshots Description: * Psi is a cross-platform powerful XMPP client designed for experienced users. User interface of program is very flexible in customization. For example, there are "multi windows" and "all in one" modes, support of different iconsets and themes. Psi supports file sharing and audio/video calls. Security is also a major consideration, and Psi provides it for both client-to-server (TLS) and client-to-client (OpenPGP, OTR, OMEMO) via appropriate plugins. * Psi+ is a development branch of Psi with rolling release development model. Users who wants to receive new features and bug fixes very quickly may use Psi+ on daily basis. Users who do not care about new trends and prefer constancy may choose Psi as it uses classical development model and its releases are quite rare. * Psi plugins are developed separately. Plugins API in guaranteed to be compatible only for git tags. In master branches of psi, plugins and psimedia repos they may be not in sync during development. Psi+ solves this problem by providing all-in-one tarballs in this special git repository. Main features of this repository: 1) Source tree is aggregated from independent git repositories: - https://github.com/psi-im/psi (main repo of Psi) - https://github.com/psi-im/iris (XMPP library) - https://github.com/psi-im/libpsi (tools library) - https://github.com/nodejs/http-parser (3rdparty library) - https://github.com/psi-im/qhttp (3rdparty library) This is a fork of dead project - https://github.com/Ri0n/qite (3rdparty library) This Qt widget is detauched from Psi project for wider usage - https://github.com/psi-im/plugins (officially supported plugins) - https://github.com/psi-im/psimedia (multimedia plugin) - https://github.com/psi-im/resources (additional resources) - https://github.com/psi-plus/main (additional patches) 2) Trash is removed. (MS Windows executables, non-free icons, etc.) More details see in file generate-single-repo.sh Advantages of this repository: * Sources are updated regularly (two times per hour) from all git repositories in according to their relationships (git submodules are bind to specific commits) * There is no non-free content in the source tree. * There is a transparent system of version numbering: - you don't need to calculate current version using magic hacks, just look to available git tags - you may download any previous version if you need and all parts of Psi+ sources will be compatible with each other (main Psi sources, plugins, resources, etc.) * Gzipped tarballs are available for each git tag. (Thanks to GitHub service.) This repository was created and is supported by Boris Pek . Please contact me if you have any troubles with it. Please contact me if you find non-free content here! Use Debian criteria of free content in this case: https://www.debian.org/social_contract#guidelines https://en.wikipedia.org/wiki/Debian_Free_Software_Guidelines If you find bugs or problems in Psi+ program you may report about them here: https://github.com/psi-im/psi/issues Even if bugs came from psi-im/plugins or psi-plus/main repositories, developers prefer to get bug reports at above link. psi-plus-snapshots-1.4.1456/README.html000066400000000000000000000303541370065651000174030ustar00rootroot00000000000000

Psi – Qt-based XMPP client

Website: https://psi-im.org/
Sources: https://github.com/psi-im

License

This program is licensed under the GNU General Public License. See the COPYING file for more information.

Description

Psi is a powerful XMPP client designed for experienced users. It is highly portable and runs on GNU/Linux, MS Windows, macOS, FreeBSD and Haiku.

User interface of program is very flexible in customization. For example, there are "multi windows" and "all in one" modes, support of different iconsets and themes. Psi supports file sharing and audio/video calls. Security is also a major consideration, and Psi provides it for both client-to-server (TLS) and client-to-client (OpenPGP, OTR, OMEMO) via appropriate plugins.

WebKit version of Psi has few additional features (in comparing with basic version of Psi): support of animated emoticons, support of (adium) themes in private chats and group chats, support of previewing of images and videos in private chats and group chats, etc. But if you prefer old fashioned plain text chats from era of IRC heyday, then basic version of Psi is your obvious choice.

WebEngine version of Psi is identical to WebKit version of Psi in supporting of extra features, but it has some pros and cons:

  • Better work with embedded video (in chats) in MS Windows.
  • Worse integration to system. (It does not use system theme and fonts in GNU/Linux.)
  • Supports less amount of compilers, systems and CPU architectures.
  • Stability issues in macOS.

Versions history

See CHANGELOG file.

Installation

See INSTALL file.

Development

In 2009 a Psi fork named Psi+ was started. Project purposes were: implementation of new features, writing of patches and plugins for transferring them to upstream. As of 2017 all active Psi+ developers have become official Psi developers and now Psi+ is just a development branch of Psi with rolling release development model.

Users who wants to receive new features and bug fixes very quickly may use Psi+ on daily basis. Users who do not care about new trends and prefer constancy may choose Psi as it uses classical development model and its releases are quite rare.

Currently the development model looks like this:

  • Psi versions should look like: X.Y (where X and Y are not digits but numbers).
  • Psi+ versions should look like: X.Y.Z (where X, Y and Z are not digits but numbers).
  • All development of Psi is done in master branch of psi repository and its submodules. Each commit to it increases Z part of Psi+ version to one.
  • At some point of time new release branch is detouched from master branch (for example, release-1.x, release-2.x, etc.). All new releases (git tags) will be done in it. Necessary changes from master branch to stable branches should be cherry-picked or moved manually. Do not forget to merge release branch into master after adding of new git tags.
  • Psi plugins are developed separately. Plugins API in guaranteed to be compatible only for git tags. In master branches of psi, plugins and psimedia repos they may be not in sync during development. Psi+ solves this problem by providing all-in-one tarballs (Psi core + all officially supported plugins and resources), see special repository psi-plus-snapshots.
  • All translations are prepared for Psi+ project and are semi-automatically adapted for Psi (using special script).

All changes are tested on Continuous Integration services:

Developers

Lead developers

Other contributors

There are a lot of people who were involved into Psi and Psi+ development. Some of them are listed in license headers in source files, some of them might be found only in the history of commits in our git repositories. Also there are translators, makers of graphics and just active users. We are thankful to all them!

How you can help

Bug reports

If you found a bug please report about it in our Bug Tracker. If you have doubts contact with us in XMPP Conference <psi-dev@conference.jabber.ru> (preferable) or in a Mailing List.

Beta testing

As we (intentionally) do not have nor beta versions of Psi, nor daily build builds for it, you are invited to use Psi+ program for suggesting and testing of new features, and for reporting about new bugs (if they happen).

Comments and wishes

We like constructive comments and wishes to functions of program. You may contact with us in XMPP Conference <psi-dev@conference.jabber.ru> for discussing of your ideas. Some of them will be drawn up as feature requests in our Bug Tracker.

Translations

The work of translators is quite routine and boring. People who do it usually lose interests and their translations become incomplete. If you see such situation for translation to your native language, please join to our translations team. It is extremely welcome!

Graphics

There are many ways to contribute to the Psi project, if you think you can do a better job with any of the Psi graphics, then go right ahead!

Programming

Patches are welcome! Contact to Psi+ team if you are working on them.

Packaging

If you want to prepare personal builds of Psi and/or Psi+ for MS Windows and macOS systems, it is very welcome! We may distribute them via our official projects on SouceForge.net: psi, psiplus. Becoming an official maintaner for these systems is more complicated, but also possible.

For GNU/Linux and *BSD systems the situation is quite clear: just update packages (pkgbuilds, ebuild, etc.) in official repositories of your favorite distributions or make a Personal Package Archive (PPA) with them. We will add links to it into our documentation.

Donations

If you want to donate some money for development of Psi and Psi+ project, it is possible. See related info at official websites. Thanks!

Main links

Packages and installers

Extra links

Have fun!

psi-plus-snapshots-1.4.1456/Readme-cmake-ru.txt000066400000000000000000000252371370065651000212240ustar00rootroot00000000000000# Как собрать Psi/Psi+ используя cmake ## Подготовка каталога сборки: > $ mkdir build && cd build > $ cmake FLAGS .. вместо флага FLAGS нужно ставить флаги из секции "Полезные CMAKE флаги" можно вообще не ставить никаких флагов ## Сборка: > $ cmake --build . --target all -- или > $ make ## Установка: > $ cmake --build . --target install -- или > $ make install ## Если в установке нет необходимости, для запуска Psi/Psi+: > $ make prepare-bin > $ cd psi && ./psi && cd .. #для Psi > $ cd psi && ./psi-plus && cd .. #для Psi+ ## Полезные CMAKE флаги: > -DPSI_LIBDIR=${path} Путь к каталогу библиотек Psi/Psi+. Путь по которому Psi/Psi+ будет искать плагины > -DPSI_DATADIR=${path} Путь к каталогу данных программы Psi/Psi+. Путь по которому Psi/Psi+ будет искать данные (иконпаки, темы и.т.д.) > -DCMAKE_INSTALL_PREFIX=prefix задать префикс (каталог установки) > -DBUNDLED_IRIS=ON использовать встроенную библиотеку iris (по-умолчанию - ON) функционал внешней библиотеки пока-что не реализован > -DUSE_ENCHANT=ON использовать механизм проверки орфографии Enchant (по-умолчанию -OFF) > -DUSE_HUNSPELL=ON использовать механизм проверки орфографии Hunspell (по-умолчанию - ON) > -DUSE_ASPELL=ON использовать механизм проверки орфографии Aspell (по-умолчанию - OFF) > -DSEPARATE_QJDNS=ON использовать стороннюю библиотеку qjdns (по-умолчанию - OFF) > -DCHAT_TYPE=BASIC выбрать тип движка чатлогов. Возможные значения: WEBKIT, WEBENGINE, BASIC значение по-умолчанию - BASIC. > -DPSI_VERSION=${version} задать версию Psi/Psi+ вручную ( Пример для Psi+: 1.0.40 (2017-06-05, Psi:a7d2d7b8, Psi+:055e945, webkit) ). Данный флаг ставить не обязательно, т.к. скрипт автоматически определяет версию по содержимому файла "version" > -DCMAKE_BUILD_TYPE=Release задать тип сборки. Возможные значения: DEBUG, RELEASE, RELWITHDEBINFO, MINSIZEREL (по-умолчанию - Release) > -USE_CCACHE=ON (по-умолчанию - ON) использовать утилиту ccache при сборке > -DUSE_MXE=ON (по-умолчанию - OFF) Включает поддержку MXE (M cross environment). Выключает флаг USE_CCACHE и добавляет новые зависимости, если флаг PRODUCTION включен. Данный флаг ставить не обязательно, т.к. скрипт умеет определять MXE автоматически > -DVERBOSE_PROGRAM_NAME=ON использовать развернутое имя для бинарного файла (Экспериментальный флаг) (по-умолчанию -OFF). После компиляции будет создан бинарный файл не с именем psi или psi-plus, а например psi-webkit или psi-plus-webengine-sql > -DPRODUCTION=ON (по-умолчанию для Psi - ON, для Psi+ - OFF) собрать релизную версию Psi/Psi+ > -DUSE_KEYCHAIN=ON собрать с поддержкой Qt5Keychain > -DBUILD_PSIMEDIA=ON собрать вместе с плагином psimedia если подготовлены исходники > -DONLY_BINARY=OFF Если ON - устанавливается только бинарный файл > -DINSTALL_EXTRA_FILES=ON Если OFF, то звуки, сертификаты, иконки, темы и файл cilent_icons.txt установлены не будут > -DINSTALL_PLUGINS_SDK=ON Если флаг включен, то вместе с пси ставятся файлы необходимые для сборки плагинов (возможно будет полезно для сопровождающих пакеты) по-умолчанию - OFF > -DENABLE_PLUGINS=ON включить сборку плагинов (по-умолчанию -OFF) если плагинов в каталоге plugins нет, скрипт упадёт с ошибкой > -DONLY_PLUGINS=ON собирать только плагины не собирая саму Psi/Psi+ (по-умолчанию -OFF). Включив этот флаг, флаг ENABLE_PLUGINS включается автоматически > -DDEV_MODE=ON В OS Windows включает цель сборки prepare-bin-libs. Этот флаг удобен для запуска Psi/Psi+ сразу после сборки при разработке. При включении этого флага скрипт ищет библиотеки зависимостей и по команде: > $ make prepare-bin-libs копирует их в каталог сборки. В OS Linux включает режим разработчика и вместе с флагом ENABLE_PLUGINS при использовании psi-plus-snapshots позовяет отлаживать плагины без установки Psi > -DUSE_XSS=ON В OS Linux добавляет поддержку XScreensaver (по-умолчанию ON). > -DUSE_DBUS=ON В OS Linux включает поддержку DBus для управления клиентом, уведомлений, тюнов (по-умолчанию ON). ## Работа с плагинами: ### Следующие флаги работают только если включены флаги ENABLE_PLUGINS или ONLY_PLUGINS > -DBUILD_PLUGINS=${plugins} задать список плагинов для сборки. Чтобы собрать все плагины можно задать -DBUILD_PLUGINS="ALL" или вообще не ставить этот флаг - возможные значения для ${plugins} (можно определить по содержимому каталога plugins/generic): historykeeperplugin stopspamplugin juickplugin translateplugin gomokugameplugin attentionplugin cleanerplugin autoreplyplugin contentdownloaderplugin qipxstatusesplugin skinsplugin clientswitcherplugin watcherplugin videostatusplugin screenshotplugin jabberdiskplugin storagenotesplugin extendedoptionsplugin imageplugin extendedmenuplugin birthdayreminderplugin pepchangenotifyplugin omemoplugin openpgpplugin otrplugin chessplugin conferenceloggerplugin enummessagesplugin httpuploadplugin imagepreviewplugin Пример: > -DBUILD_PLUGINS="chessplugin;otrplugin" Переменная BUILD_PLUGINS может также быть использована как черный список. В этом случае будут собраны все плагины, кроме указаных. Для этого достаточно указать переменную как > -DBUILD_PLUGINS="-chessplugin;-otrplugin" и плагины chessplugin и otrplugin собраны не будут ВНИМАНИЕ! Смешивание белого и черного списков не допускается. > -DPLUGINS_ROOT_DIR=${path} Путь к каталогу include для сборки плагинов в отрыве от исходников Psi/Psi+ (каталог где лежит файл plugins.cmake) > -DPLUGINS_PATH=${path} установка плагинов в каталог с суфииксом ${path}. Для установки по-умолчанию: -DPLUGINS_PATH=lib/psi-plus/plugins или не задавать этот флаг Например для установки плагинов в ~/.local/share/psi+/plugins: > -DCMAKE_INSTALL_PREFIX=$HOME/.local -DPLUGINS_PATH=share/psi+/plugins Напирмер для установки плагинов в /usr/share/psi-plus/plugins: > -DCMAKE_INSTALL_PREFIX=/usr -DPLUGINS_PATH=share/psi-plus/plugins ## Секция Win32 или MXE: Для сборки под ОС Windows установка программы не требуется!!! > -DENABLE_PORTABLE=ON для сборки портативной версии (сделано для удобства, чтобы не переименовывать бинарник вручную). (по-умолчанию - OFF). На выходе получим "имя-бинарника-portable.exe", а если VERBOSE_PROGRAM_NAME=ON, то "расширенное-имя-бинарника-portable.exe". При включении этого флага автоматически становится доступна цель сборки prepare-bin-libs. > -DQCA_DIR=DIRECTORY задать корневой каталог с библиотекой Qca > -DIDN_ROOT=DIRECTORY задать корневой каталог с библиотекой Idn > -DZLIB_ROOT=DIRECTORY задать корневой каталог с библиотекой Zlib > -DHUNSPELL_ROOT=DIRECTORY задать корневой каталог с библиотекой Hunspell ### Для сборки плагина OTR в OS WINDOWS возможно понадобятся дополнительные флаги: > -DLIBGCRYPT_ROOT=%LIBGCRYPT_ROOT% задать корневой каталог с библиотекой LIBGCRYPT > -DLIBGPGERROR_ROOT=%LIBGPGERROR_ROOT% задать корневой каталог с библиотекой LIBGPG-ERROR > -DLIBOTR_ROOT=%LIBOTR_ROOT% задать корневой каталог с библиотекой LIBOTR > -DLIBTIDY_ROOT=%LIBTIDY_ROOT% задать корневой каталог с библиотекой LIBTIDY Например: > -DLIBGCRYPT_ROOT=C:\libgcrypt -DLIBGPGERROR_ROOT=C:\libgpg-error -DLIBOTR_ROOT=C:\libotr -DLIBTIDY_ROOT=C:\libtidy ### Если при сборке Psi/Psi+ используется SDK, нужно задать путь SDK_PATH: > -DSDK_PATH=path Если задать этот флаг, то флаги к корневым каталогам библиотек зависимостей можно не задавать. psi-plus-snapshots-1.4.1456/Readme-cmake.txt000066400000000000000000000136631370065651000206000ustar00rootroot00000000000000# Howto build Psi/Psi+ using cmake utility ## Prepare sources: > $ mkdir build && cd build > $ cmake FLAGS .. instead of FLAGS can be the flags from "Usefull CMAKE FLAGS" section ## Build sources: > $ cmake --build . --target all -- or > $ make ## Install Psi/Psi+: > $ cmake --build . --target install -- or > $ make install ## To run Psi/Psi+ without installation: > $ cmake --build . --target prepare-bin or > $ make prepare-bin > $ cd psi && ./psi && cd .. #For Psi > $ cd psi && ./psi-plus && cd .. #For Psi+ ## Usefull CMAKE FLAGS: > -DPSI_LIBDIR=${path} Path to Psi/Psi+ libraries directory. Path to the directory where Psi/Psi+ will search plugins. > -DPSI_DATADIR=${path} Path to Psi/Psi+ data directory. Path to the directory where Psi/Psi+ will search datafiles (iconpacks, themes etc) > -DCMAKE_INSTALL_PREFIX=prefix to set installation prefix > -DBUNDLED_IRIS=ON to build iris library bundled (default ON) > -DUSE_ENCHANT=ON to use Enchant spellchecker (default OFF) > -DUSE_HUNSPELL=ON to use Hunspell spellchecker (default ON) > -DUSE_HUNSPELL=ON to use Aspell spellchecker (default OFF) > -DSEPARATE_QJDNS=ON to build qjdns library as separate library (default OFF) > -DCHAT_TYPE = BASIC to set type of chatlog engine. Possible values: WEBKIT, WEBENGINE, BASIC default value - BASIC > -DPSI_VERSION=${version} to set Psi/Psi+ version manually ( Example for Psi+: 1.0.40 (2017-06-05, Psi:a7d2d7b8, Psi+:055e945, webkit) ). Script sets this flag automatically from "version" file if it exists in sources directory > -DCMAKE_BUILD_TYPE=Release (default: Release) to set build type. Possible values: DEBUG, RELEASE, RELWITHDEBINFO, MINSIZEREL > -USE_CCACHE=ON (default: ON) to enable ccache utility support > -DUSE_MXE=ON (default: OFF) Enables MXE (M cross environment) support. Disables USE_CCACHE. Script can automatically detect MXE. > -DVERBOSE_PROGRAM_NAME=ON Verbose output program name. (default OFF) Experimental flag. Exmaple of output name: psi-plus-webkit-sql > -DPRODUCTION=ON to build release version of Psi/Psi+ > -DUSE_KEYCHAIN=ON to enable Qt5Keychain library support > -DBUILD_PSIMEDIA=ON build psimedia plugin if sources found in project folder > -DONLY_BINARY=OFF If ON - only binary file will be installed > -DINSTALL_EXTRA_FILES=ON If OFF - sounds, certificates, iconsets, themes and cilent_icons.txt file will not be installed > -DINSTALL_PLUGINS_SDK=ON If this flag ON than with psi will be installed PluginsAPI that needed to build plugins separately of main program sources (default OFF) > -DENABLE_PLUGINS=ON to build psi plugins (default OFF) > -DONLY_PLUGINS=ON to build only psi plugins (default OFF). On enabling this flag ENABLE_PLUGINS flag turns on automatically > -DDEV_MODE=ON In OS Windows enables prepare-bin-libs target. Allows to copy needed libraries to run Psi/Psi+. In Linux sets PSI_DATA directory to current binary direrctory (Usefull to debug plugins) > -DUSE_XSS=ON In Linux OS adds XScreensaver support (default ON). > -DUSE_DBUS = ON In Linux OS enables DBus support for client management, notifications, tunes (default ON). ## Work with plugins: ### Next flags are working only if ENABLE_PLUGINS or ONLY_PLUGINS are enabled > -DBUILD_PLUGINS=${plugins} set list of plugins to build. To build all plugins: -DBUILD_PLUGINS="ALL" or do not set this flag - possible values for ${plugins}: historykeeperplugin stopspamplugin juickplugin translateplugin gomokugameplugin attentionplugin cleanerplugin autoreplyplugin contentdownloaderplugin qipxstatusesplugin skinsplugin clientswitcherplugin watcherplugin videostatusplugin screenshotplugin jabberdiskplugin storagenotesplugin extendedoptionsplugin imageplugin extendedmenuplugin birthdayreminderplugin pepchangenotifyplugin omemoplugin openpgpplugin otrplugin chessplugin conferenceloggerplugin enummessagesplugin httpuploadplugin imagepreviewplugin Example: > -DBUILD_PLUGINS="chessplugin;otrplugin" The BUILD_PLUGINS variable can also be used as a blacklist. In this case, all plugins will be compiled, except those indicated. To do this, just specify the variable as > -DBUILD_PLUGINS = "-chessplugin;-otrplugin" and plugins chessplugin and otrplugin will not be assembled ATTENTION! Mixing white and black lists is not allowed. > -DPLUGINS_ROOT_DIR=${path} Path to the include directory to build plugins outside of Psi/Psi+ sources (path to the plugins.cmake file) > -DPLUGINS_PATH=${path} to install plugins into ${path}. To install into default suffix: -DPLUGINS_PATH=lib/psi-plus/plugins or do not set this flag For example to install plugins into ~/.local/share/psi+/plugins: > -DCMAKE_INSTALL_PREFIX=$HOME/.local -DPLUGINS_PATH=share/psi+/plugins For example to install plugins into /usr/share/psi-plus/plugins: > -DCMAKE_INSTALL_PREFIX=/usr -DPLUGINS_PATH=share/psi-plus/plugins ## Win32 or MXE Section: > -DQCA_DIR=DIRECTORY to set Qca library root directory > -DIDN_ROOT=DIRECTORY to set Idn library root directory > -DZLIB_ROOT=DIRECTORY to set Zlib library root directory > -DHUNSPELL_ROOT=DIRECTORY to set Hunspell library root directory > -DENABLE_PORTABLE=ON to build portable version (not need to rename binary). Enables prepare-bin-libs target. ### To build OTRPLUGIN in OS WINDOWS you need to set additional variables > -DLIBGCRYPT_ROOT=%LIBGCRYPT_ROOT% path to LIBGCRYPT library root directory > -DLIBGPGERROR_ROOT=%LIBGPGERROR_ROOT% path to LIBGPG-ERROR library root directory > -DLIBOTR_ROOT=%LIBOTR_ROOT% path to LIBOTR library root directory > -DLIBTIDY_ROOT=%LIBTIDY_ROOT% path to LIBTIDY library root directory For example: > -DLIBGCRYPT_ROOT=C:\libgcrypt -DLIBGPGERROR_ROOT=C:\libgpg-error -DLIBOTR_ROOT=C:\libotr -DLIBTIDY_ROOT=C:\libtidy ### If you using Psi+ SDK you need to set SDK_PATH: > -DSDK_PATH=path psi-plus-snapshots-1.4.1456/Readme-dev-cmake-ru.txt000066400000000000000000000312361370065651000217740ustar00rootroot00000000000000Описание cmake файлов (памятка для разработчиков) ./CMakeLists.txt - корневой файл (главный скрипт): содержит список основных опций; определяет тип программы Psi/Psi+ определяет Webkit/Webengine содержит основные определения (definitions) обработка переменных SDK определение версии программы подключает ccache, mxe содержит функцию copy подключает каталоги iris, 3rdparty, src в случае сборки только плагинов - подключает плагины если в корне есть каталог псимедии - собирает псимедию ./3rdparty/CMakeLists.txt - собирает статическую библиотеку qhttp ./3rdparty/qite/libqite/libqite.cmake - содержит список файлов проекта qite ./cmake/modules - каталог модулей для поиска библиотек: ./cmake/modulesCOPYING-CMAKE-SCRIPTS - файл лицензии (для решения проблем с сопровождением) ./cmake/modules/FindLibGpgError.cmake - ищет libgpg-error ./cmake/modules/FindQJDns.cmake - ищет qjdns ./cmake/modules/FindEnchant.cmake - ищет enchant ./cmake/modules/FindLibOtr.cmake - ищет otr ./cmake/modules/FindQJSON.cmake - ищет qjson (устарело и отключено) ./cmake/modules/FindHunspell.cmake - ищет hunspell ./cmake/modules/FindLibTidy.cmake - ищет tidy или html-tidy ./cmake/modules/FindSparkle.cmake - ищет sparkle (не проверялось) ./cmake/modules/FindIDN.cmake - ищет idn ./cmake/modules/FindMINIZIP.cmake - ищет minizip ./cmake/modules/FindXCB.cmake - ищет xcb ./cmake/modules/FindLibGcrypt.cmake - ищет libgctypt или libgcrypt2 ./cmake/modules/FindQca.cmake - ищет qca-qt5 ./cmake/modules/FindZLIB.cmake - ищет zlib ./cmake/modules/FindPsiPluginsApi.cmake - модуль поиска файлов, необходимых для сборки плагинов ./cmake/modules/get-version.cmake - определяет версию клиента по содержимому файла ../version ./cmake/modules/win32-prepare-deps.cmake - генерирует список файлов для установки командой make prepare-bin-libs, которая установит библиотеки зависимостей в выходной каталог сборки. Если доступно использует windeployqt по команде make windeploy ./cmake/modules/generate_desktopfile.cmake - генерирует .desktop файл ./cmake/modules/fix-codestyle.cmake - исправляет стиль кода исходников по make fix-codestyle /*** модули поиска изначально сделаны так, чтобы можно было указать где искать для этого введены переменные ИМЯ_ROOT где можно указать путь поиска перед подключением модуля ***/ ./iris/CMakeLists.txt - собирает библиотеку iris: содержит опции для iris (продублированы в главном скрипте) подключает qjdns (если включена опция) подключает каталоги src/irisnet и src/xmpp /*** ./iris - каталог содержит дополнительные файлы для сборки библиоетки отдельно от проекта Psi. Функционал реализован частично. Требуется доработка. ***/ ./iris/cmake/modules - модули поиска библиотек для iris (копии модулей из корня) ./iris/src/irisnet/CMakeLists.txt - собирает статическую библиотеку irisnet ./iris/src/xmpp/CMakeLists.txt - собирает статическую библиотеку iris ./src/CMakeLists.txt - собирает и устанавливает проект Psi/Psi+: подключает каталог ../translations (если он существует) и собирает файлы переводов ищет библиотеки и подключает их к проекту задает версию программы на основе данных главного скрипта генерирует файл config.h на основе файла config.h.in генерирует файл psi_win.rc на основе файла ../win32/psi_win.rc.in и компилирует psi_win.o подключает файл src.cmake подключает файл ../3rdparty/qite/libqite/libqite.cmake подключает файл irisprotocol/irisprotocol.cmake подключает файл protocol/protocol.cmake подключает файл plugins/plugins.cmake подключает каталоги AutoUpdater, options, tabs, privacy, Certificates, avcall, psimedia, contactmanager, tools, libpsi/dialogs, libpsi/tools, widgets, sxe, whiteboarding подменяет выходное имя программы на расширенное (если включено) если имя расширено, генерирует .desktop файл при помощи модуля generate_desktopfile.cmake создает правила для установки если включена опция DEV_MODE для win32 генерирует правила установки библиотек зависимостей в выходной каталог сборки при помощи модуля win32-prepare-deps.cmake подключает каталог plugins, если включена опция и скопированы плагины ./src/config.h.in - файл-шаблон для создания файла config.h ./src/src.cmake: содержит определения (definitions) определяющие функционал программы по-умолчанию содержит списки основных файлов исходников, необходимых для сборки: список FORMS - содержит ui файлы список HEADERS - содержит заголовки файлов для которых будут генерироваться .moc файлы список SOURCES - содержит исходные файлы для которых будут генерироваться .moc файлы список PLAIN_HEADERS - содержит заголовки файлов для которых не будут генерироваться .moc файлы список PLAIN_SOURCES - содержит исходные файлы для которых не будут генерироваться .moc файлы ./src/AutoUpdater/CMakeLists.txt - собирает статическую библиотеку AutoUpdater ./src/avcall/CMakeLists.txt - собирает статическую библиотеку avcall ./src/Certificates/CMakeLists.txt - собирает статическую библиотеку Certificates ./src/contactmanager/CMakeLists.txt - собирает статическую библиотеку contactmanager ./src/irisprotocol/irisprotocol.cmake - список файлов исходников которые подключаются в основные списки сборки ./src/libpsi/dialogs/CMakeLists.txt - собирает статическую библиотеку libpsi_dialogs ./src/libpsi/tools/CMakeLists.txt - собирает статическую библиотеку libpsi_tools: подключает каталог zip ./src/libpsi/tools/zip/CMakeLists.txt - собирает статическую библиотеку zip ./src/options/CMakeLists.txt - собирает статическую библиотеку options ./src/privacy/CMakeLists.txt - собирает статическую библиотеку privacy ./src/protocol/protocol.cmake - список файлов исходников которые подключаются в основные списки сборки ./src/psimedia/CMakeLists.txt - собирает статическую библиотеку psimedia ./src/sxe/CMakeLists.txt - собирает статическую библиотеку sxe ./src/tabs/CMakeLists.txt - собирает статическую библиотеку tabs ./src/tools/CMakeLists.txt - собирает статическую библиотеку tools ./src/whiteboarding/CMakeLists.txt - собирает статическую библиотеку whiteboarding ./src/widgets/CMakeLists.txt - собирает статическую библиотеку widgets ./plugins/plugins.cmake - список файлов заголовков оторые подключаются в основные списки сборки ./plugins/variables.cmake.in - шаблон файла, который содержит основные переменные для сборки плагинов, общие для всех плагинов ./plugins/pluginsconf.pri.cmake.in - шаблон файла pluginsconf.pri, генерируемого при сборке ./plugins/CMakeLists.txt - основной скрипт управляющий плагинами содержит основные правила сборки для всех плагинов подключает каталоги generic, unix, dev ./plugins/generic/CMakeLists.txt: подключает каталоги плагинов, если задана переменная BUILD_PLUGINS, то подключает только заданные каталоги плагинов ./plugins/dev/CMakeLists.txt: подключает каталоги плагинов, если задана переменная BUILD_PLUGINS, то подключает только заданные каталоги плагинов ./plugins/unix/CMakeLists.txt: подключает каталоги плагинов, если задана переменная BUILD_PLUGINS, то подключает только заданные каталоги плагинов ./plugins/тип/плагин/CMakeLists.txt - собирает и устанавливает плагин типа "тип" с именем "плагин". Структура у всех этих скриптов практически не отличается ./win32/psi_win.rc.in - файл-шаблон для создания файла psi_win.rc /*** Для удобства опакечивания плагинов, чтобы не тянуть все файлы иходников Psi и не заниматься копированием, если включен флаг INSTALL_PLUGINS_SDK при сборке и установке клиента Psi средствами CMake-скриптов, вместе с основными файлами клиента Psi могут быть установлены файлы API плагинов (includes, *.pri файлы, variables.cmake) (путь $prefix/share/имя_клиента/plugins) также в систему устанавливается модуль для поиска API плагинов FindPsiPluginsApi.cmake (путь $prefix/share/cmake/Modules) В данном случае variables.cmake генерируется при сборке Psi и содержит в себе: - путь установки для плагинов - путь установки файлов данных клиента Psi - имя клиента (psi или psi-plus) - defenitions для плагинов Также CMake-скрипт сборки Psi генерирует файлы *.pri, необходимые для сборки плагинов средствами qmake ***/ /*** Если есть необходимость дебажить плагин в линуксе, делаем следующее: - качаем репу psi-plus-snapshots - открываем CMakeLists.txt в корне сырцов при помощи qtcreator - выбрав профиль и тип сборки (иначе при выборе нового типа все флаги слетят) включаем 2 флага ENABLE_PLUGINS и DEV_MODE - обязательно добавляем правило сборки make prepare-bin всё, критор дебажит плагины вместе с пси ***/ psi-plus-snapshots-1.4.1456/TODO000066400000000000000000000174541370065651000162560ustar00rootroot00000000000000Required gcuserview: proper sorting of contacts based on status / alphabetical (like ContactView) server info: show server version in tooltip ssl information dialog (show cert and maybe connection info) local contact settings: checkboxes for accepting various things: messages; files; URLs; chats \ [each has a checkbox option: only from my contact list, or... only from this GROUP on my contact list] be alerted when the contact changes status or comes online (etc) option: ignore headline messages (die MSN) annotations (notes about the user) log history or not activity log window high level logging of connection status, presence changes, messages timestamps "find" feature revamp history system: ability to restore entire chats, show in normal order, not reverse "properties" dialog (could be last entry in cvlist context) for setting groups and subscription, etc. \ it could also show client time / version file downloading nice dialog after file received, there should be the options: "Open" and "Open folder" put each received file into a folder for the contact (optional) if a specific directory is chosen, the dialog should have a history of the most recent download \ folders (10 maybe?) Advanced Alerts (sound, popups, etc) Ignore list Important checkbox to keep eventdlg open (both send and recv). for send, it should reset the content after sending. statussetdlg should automatically click ok in 10 seconds (unless you press a key) ability to flag which accounts are affected by the mass status change button (should be in account context menu) cv: group headings should have a better design than just a rectangle (gradient?) add "Go to Website" and x:oob in presence ability to do various iq requests to arbitrary jids: version, time, info multi-monitor support: windows should pop up on the same display as the main window? merge all contacts into one column, rather than dividing by account consolidated contacts send contacts save/load contacts (hint: use QCheckListItem for dialogs) exporting dialog for selecting contacts to export to file write to file importing import a list from file dialog for selecting contacts to import tell the server about the new contacts check for dups offline animation (door slam) chathistorydlg a nice window to display chat history maybe a menubar with some features like "save to file" ? show/hide times show/hide/rename other person (for privacy) floating contacts (controlled by cvlist?) full message archive dialog (tree/explorer view for choosing contacts on left, display area on the right). Update all (mass user info check) Detecting network connection status. option to auto-connect when network is available. startup argument to select profile ability to execute a shell command on event option to display (in realtime) the cvlist as 'flat', ie without groups voice chat option to launch on startup (platform dependent, good luck) different color choices for different group types deal with presence errors when subscribing command line args? psi.exe --psi-data dir Extra ability to flag a contact or group to always be shown even if they would not be (due to visibility toggles) allow eventdlg and chatdlg inputs to drag / drop URLs (in both directions) translation packs for the Qt library itself?? check to see if app is already running when launched. if it is, then: ask if they really want to open it again if they say yes, then disable auto-open for profiles save presence changes to history? win32: docking (all optional) grapple to edge of screen, like ICQ for windows right-click in chat/eventdlg should have options to paste your current URL or IP address KDE-enhanced mode "previous" button in the eventdlg? Autoresponses? (with general and customized texts) auto close chat windows that are not in focus after so many minutes of inactivity The Main Window's Icon should change when new messages arrive, just like the dock icon does. Ability to filter messages based on words (maybe even shell command filters) Ability to compose messages and send at a later specific date (or when you or the recipient go online) option to autohide main roster window after a set period of time Licq supports replacing some %s with information on the sender of the message and other things. (There's a list below) IT supports these in OnEvent, auto responses (check status), and utilities For example: sound player "~/scripts/say", on message "%a has just sent you an I.C.Q. message" %a - user alias %e - email %f - first name %h - phone number %i - user ip %l - last name %m - # pending messages %n - full name %o - last seen online %O - online since %p - user port %s - full status %S - abbreviated status %u - uin %w - webpage Ability to specify an arbitrary time for chatdlgs to stay active (x minutes/hours/days) "small mode" - put mainwin buttons (and toolbuttons) into the cvlist right-click option to have sounds not play when XA (but still play when Away) option to have mainwin/dock status follow a specific account rather than "best choice" option to show status message in parenthesis to the right of the contact (like yahoo) ability to "auto-reconnect" to a transport? cv: let the user choose how the list is sorted group ideas: option: sort or not choose order of groups and contacts with drag and drop save order on the server support empty groups that get removed on signoff Have a way of marking some people as 'important' contacts, so they will always trigger sound psuedo-chat support like Mirabilis ICQ / Licq (ie, split window, but still used like normal messages) friendlier infodlg. get rid of those lame tabs cvlist sorting options sort by group, online/offline split sort by group, online/offline together sort by group and by status sort by group and alphanumerically sort by status without groups sort alphanumerically without groups sort by online/offline straight alphanumerical sort cvlist select multiple? right-clicking on status button should bring up a list of accounts, each with submenus that would set \ the status of the chosen account. option to have psi remember your password for the running session Remember last status when psi quits (even if improperly shut down). restore this status when psi is run again? if you get a msg from someone in a closed group, it should not expand the whole group "diskless" mode Bugs connection timeouts are reported as "connection refused" (qt) slowdown when receiving a chat message? or a problem with QTextEdit debug messages? Miscellaneous properly sort cvlist accounts if one of them gets renamed class Options: cpp'ify (reset, toXml, fromXml) convert some of the preferences into their own classes (or lists), like fonts, colors, sounds, etc 'find' function in historydlg should show the found result in the center of 50 results, rather than the top Check for libqssl without needing a restart? replies should only close a window that it is associated with (use QGuardedPtr?) don't allow send/recv of blank messages (all content is whitespace or empty) allow blank password entry don't prompt for password until actually connected. reprompt if wrong?? proper dialog button order depending on platform when entering a text string into "Server to browse", any whitespace before or after is not trimmed. lots of \ fields like this should have some sort of auto-trim. when opening a new chat window of a contact with pending events, the presence is logged to the chat window \ before the messages are, which have an earlier timestamp (looks weird) psi-plus-snapshots-1.4.1456/admin/000077500000000000000000000000001370065651000166435ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/admin/apply_version.sh000077500000000000000000000006731370065651000221020ustar00rootroot00000000000000#!/bin/bash version=$1 # this script stamps a version to files that need a version before the build # process has started. everything else gets versionized at configure time or # later. # rewrite version in README line1="Psi $version" line2= len1=${#line1} n=0 while [ $n -lt $len1 ]; do line2="$line2-" n=`expr $n + 1` done echo $line1 > README.new echo $line2 >> README.new tail -n+3 README >> README.new mv README.new README psi-plus-snapshots-1.4.1456/admin/build/000077500000000000000000000000001370065651000177425ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/admin/build/Makefile000066400000000000000000000101331370065651000214000ustar00rootroot00000000000000# usage: make VERSION=1.2.3 UNAME := $(shell uname) include package_info ifeq ($(UNAME), Darwin) PLATFORM = mac all: default-mac deps: deps-mac else ifeq ($(UNAME), MINGW32_NT-6.1) PLATFORM = win all: default-mingw deps: deps-mingw else all: default-unknown deps: deps-unknown endif default-mac: dist-mac default-mingw: dist-mingw clean: rm -rf build out dist distclean: clean rm -rf packages ifeq ($(PLATFORM), mac) rm -f Psi-$(VERSION).dmg else rm -f psi-$(VERSION)-win64.zip psi-$(VERSION)-win32.zip endif dist-mac: apply-version Psi-$(VERSION).dmg dist-mingw: apply-version psi-$(VERSION)-win64.zip psi-$(VERSION)-win32.zip deps-mac: packages/$(qca_mac_file) packages/$(growl_file) packages/$(gstbundle_mac_file) packages/$(psimedia_mac_file) deps-mingw: packages/$(zlib_win_file) packages/$(qca_win_file) packages/$(openssl_win_file) packages/$(aspell_win_file) packages/$(gstbundle_win_file) packages/$(psimedia_win_file) packages/$(zlib_win_file): mkdir -p packages deps ../fetch.sh $(zlib_win_url) packages/$(zlib_win_file) cd deps && unzip ../packages/$(zlib_win_file) && cd .. packages/$(qca_win_file): mkdir -p packages deps ../fetch.sh $(qca_win_url) packages/$(qca_win_file) cd deps && unzip ../packages/$(qca_win_file) && cd .. packages/$(openssl_win_file): mkdir -p packages deps ../fetch.sh $(openssl_win_url) packages/$(openssl_win_file) cd deps && unzip ../packages/$(openssl_win_file) && cd .. packages/$(aspell_win_file): mkdir -p packages deps ../fetch.sh $(aspell_win_url) packages/$(aspell_win_file) cd deps && unzip ../packages/$(aspell_win_file) && cd .. packages/$(gstbundle_win_file): mkdir -p packages deps ../fetch.sh $(gstbundle_win_url) packages/$(gstbundle_win_file) cd deps && unzip ../packages/$(gstbundle_win_file) && cd .. packages/$(psimedia_win_file): mkdir -p packages deps ../fetch.sh $(psimedia_win_url) packages/$(psimedia_win_file) cd deps && unzip ../packages/$(psimedia_win_file) && cd .. packages/$(qca_mac_file): mkdir -p packages deps ../fetch.sh $(qca_mac_url) packages/$(qca_mac_file) cd deps && tar jxvf ../packages/$(qca_mac_file) && cd .. packages/$(growl_file): mkdir -p packages deps ../fetch.sh $(growl_url) packages/$(growl_file) cd deps && unzip ../packages/$(growl_file) && cd .. packages/$(gstbundle_mac_file): mkdir -p packages deps ../fetch.sh $(gstbundle_mac_url) packages/$(gstbundle_mac_file) cd deps && tar jxvf ../packages/$(gstbundle_mac_file) && cd .. packages/$(psimedia_mac_file): mkdir -p packages deps ../fetch.sh $(psimedia_mac_url) packages/$(psimedia_mac_file) cd deps && tar jxvf ../packages/$(psimedia_mac_file) && cd .. apply-version: test "$(VERSION)" != "" && echo "$(VERSION)" > ../../version build/uni/psi/ok: packages/$(qca_mac_file) packages/$(growl_file) packages/$(gstbundle_mac_file) packages/$(psimedia_mac_file) mkdir -p build ./build_package.sh psi "" "" touch build/uni/psi/ok build/i386/psi/ok: packages/$(zlib_win_file) packages/$(qca_win_file) packages/$(openssl_win_file) packages/$(aspell_win_file) packages/$(gstbundle_win_file) packages/$(psimedia_win_file) mkdir -p build ./build_package.sh psi i386 $(PWD)/out touch build/i386/psi/ok build/x86_64/psi/ok: packages/$(zlib_win_file) packages/$(qca_win_file) packages/$(openssl_win_file) packages/$(aspell_win_file) packages/$(gstbundle_win_file) packages/$(psimedia_win_file) mkdir -p build ./build_package.sh psi x86_64 $(PWD)/out touch build/x86_64/psi/ok dist/psi-$(VERSION)-mac: build/uni/psi/ok ./prep_dist.sh "" dist/psi-$(VERSION)-mac dist/psi-$(VERSION)-win64: build/x86_64/psi/ok ./prep_dist.sh $(PWD)/out/x86_64 dist/psi-$(VERSION)-win64 dist/psi-$(VERSION)-win32: build/i386/psi/ok ./prep_dist.sh $(PWD)/out/i386 dist/psi-$(VERSION)-win32 Psi-$(VERSION).dmg: dist/psi-$(VERSION)-mac ./pack_dmg.sh Psi-$(VERSION).dmg Psi dist/psi-$(VERSION)-mac psi-$(VERSION)-win64.zip: dist/psi-$(VERSION)-win64 rm -f psi-$(VERSION)-win64.zip cd dist && zip -r ../psi-$(VERSION)-win64.zip psi-$(VERSION)-win64 psi-$(VERSION)-win32.zip: dist/psi-$(VERSION)-win32 rm -f psi-$(VERSION)-win32.zip cd dist && zip -r ../psi-$(VERSION)-win32.zip psi-$(VERSION)-win32 psi-plus-snapshots-1.4.1456/admin/build/README000066400000000000000000000024031370065651000206210ustar00rootroot00000000000000Psi Build Scripts ----------------- First, prepare your environment according to the prereqs in psideps/README. To create packages for Windows, set QTDIR64 and QTDIR32, and pass VERSION to make. For example: cd admin/build QTDIR64=c:/qt/4.8.2-w64 QTDIR32=c:/qt/4.8.2 make VERSION=0.15-mybuild You must set both Qt paths. The scripts do not support packaging just for one arch. For Mac: cd admin/build QTDIR=/usr/local/Trolltech/Qt-4.8.2 make VERSION=0.15-mybuild It is also possible to prepare a dev environment using similar scripting: Configure for Windows 64-bit: QTDIR64=c:/qt/4.8.2-w64 admin/build/devconfig.sh x86_64 Configure for Windows 32-bit: QTDIR32=c:/qt/4.8.2 admin/build/devconfig.sh i386 Configure for Mac: QTDIR=/usr/local/Trolltech/Qt-4.8.2 admin/build/devconfig.sh "" Executing one of these commands will cause all Psi dependencies to be downloaded, configure run against these dependencies, and a file named devenv generated with all necessary environment variables to build. Then you can proceed as follows: . admin/build/devenv make (or mingw32-make, if on Windows) You can then run the Psi executable from within the shell to test it and everything needed will be found, without having to create a full distribution directory. psi-plus-snapshots-1.4.1456/admin/build/build_package.sh000077500000000000000000000120411370065651000230510ustar00rootroot00000000000000#!/bin/sh set -e if [ $# != 3 ]; then echo "usage: $0 [package] [arch] [prefix]" exit 1 fi platform=`uname -s` if [ "$platform" == "Darwin" ]; then platform=mac elif [ "$platform" == "MINGW32_NT-6.1" ]; then platform=win else echo "error: unsupported platform $platform" exit 1 fi . ./package_info package_name=$1 target_arch=$2 base_prefix=$3 if [ "$platform" == "mac" ]; then target_platform=$target_arch-apple-darwin export MACOSX_DEPLOYMENT_TARGET=10.5 else if [ "$target_arch" == "x86_64" ]; then export PATH=/c/mingw64/bin:$PATH fi fi arch_prefix=$base_prefix/$target_arch pkgdir=$PWD/packages patchdir=$PWD/patches build_base=$PWD psi_base=$PWD/../.. deps_base=$PWD/deps get_msys_path() { if [ `expr index $1 :` -gt 0 ]; then pdrive=`echo $1 | cut -f 1 --delimiter=:` prest=`echo $1 | cut -f 2 --delimiter=:` echo /$pdrive$prest else echo $1 fi } build_package() { if [ ! -d "build/$2/$1" ]; then echo "$1/$2: building..." mkdir -p build/$2/$1 old_pwd=$PWD cd build/$2/$1 build_package_$1 cd $old_pwd else echo "$1/$2: failed on previous run. remove the \"build/$2/$1\" directory to try again" exit 1 fi } build_package_psi() { if [ "$platform" == "win" ]; then if [ "$target_arch" == "x86_64" ]; then qtdir=$QTDIR64 else qtdir=$QTDIR32 fi mqtdir=`get_msys_path $qtdir` cd $psi_base PATH=$mqtdir/bin:$PATH ./configure.exe --qtdir=$qtdir --release --with-qca-inc=$deps_base/$qca_win_dir/$target_arch/include --with-qca-lib=$deps_base/$qca_win_dir/$target_arch/lib --with-zlib-inc=$deps_base/$zlib_win_dir/$target_arch/include --with-zlib-lib=$deps_base/$zlib_win_dir/$target_arch/lib --with-aspell-inc=$deps_base/$aspell_win_dir/$target_arch/include --with-aspell-lib=$deps_base/$aspell_win_dir/$target_arch/lib mingw32-make mkdir -p $arch_prefix cp psi.exe $arch_prefix/Psi.exe cp $mqtdir/bin/QtCore4.dll $arch_prefix cp $mqtdir/bin/QtNetwork4.dll $arch_prefix cp $mqtdir/bin/QtXml4.dll $arch_prefix cp $mqtdir/bin/QtGui4.dll $arch_prefix cp $mqtdir/bin/QtSql4.dll $arch_prefix mkdir -p $arch_prefix/imageformats cp $mqtdir/plugins/imageformats/qgif4.dll $arch_prefix/imageformats cp $mqtdir/plugins/imageformats/qjpeg4.dll $arch_prefix/imageformats cp $mqtdir/plugins/imageformats/qmng4.dll $arch_prefix/imageformats mkdir -p $arch_prefix/sqldrivers cp $mqtdir/plugins/sqldrivers/qsqlite4.dll cp $deps_base/$qca_win_dir/$target_arch/bin/qca2.dll $arch_prefix mkdir -p $arch_prefix/crypto cp $deps_base/$qca_win_dir/$target_arch/plugins/crypto/qca-gnupg2.dll $arch_prefix/crypto cp $deps_base/$qca_win_dir/$target_arch/plugins/crypto/qca-ossl2.dll $arch_prefix/crypto cp $deps_base/$zlib_win_dir/$target_arch/bin/zlib1.dll $arch_prefix cp $deps_base/$aspell_win_dir/$target_arch/bin/libaspell-15.dll $arch_prefix cp -a $deps_base/$aspell_win_dir/$target_arch/lib/aspell-0.60 $arch_prefix/aspell cp $deps_base/$openssl_win_dir/$target_arch/bin/libeay32.dll $arch_prefix cp $deps_base/$openssl_win_dir/$target_arch/bin/ssleay32.dll $arch_prefix for n in `cat $build_base/gstbundle_libs_win`; do cp -a $deps_base/$gstbundle_win_dir/$target_arch/bin/$n $arch_prefix done mkdir -p $arch_prefix/gstreamer-0.10 for n in `cat $build_base/gstbundle_gstplugins_win`; do cp -a $deps_base/$gstbundle_win_dir/$target_arch/lib/gstreamer-0.10/$n $arch_prefix/gstreamer-0.10 done cp $deps_base/$psimedia_win_dir/$target_arch/plugins/gstprovider.dll $arch_prefix if [ "$target_arch" == "x86_64" ]; then cp /c/mingw64/bin/libgcc_s_sjlj-1.dll $arch_prefix cp /c/mingw64/bin/libstdc++-6.dll $arch_prefix cp /c/mingw64/bin/libwinpthread-1.dll $arch_prefix else cp /mingw/bin/libgcc_s_dw2-1.dll $arch_prefix cp /mingw/bin/libstdc++-6.dll $arch_prefix cp /mingw/bin/mingwm10.dll $arch_prefix cp /mingw/bin/pthreadGC2.dll $arch_prefix fi cp -a certs $arch_prefix cp -a iconsets $arch_prefix cp -a sound $arch_prefix cp COPYING $arch_prefix win32/tod README $arch_prefix/ReadMe.txt win32/tod INSTALL $arch_prefix/Install.txt mingw32-make distclean else if [ "$QT_LIB_PATH" == "" ]; then QT_LIB_PATH=$QTDIR/lib fi cd $psi_base export DYLD_FRAMEWORK_PATH=$QT_LIB_PATH:$deps_base/$qca_mac_dir/lib:$deps_base/$growl_dir/Framework ./configure --with-qca-inc=$deps_base/$qca_mac_dir/include --with-qca-lib=$deps_base/$qca_mac_dir/lib --with-growl=$deps_base/$growl_dir/Framework --enable-universal make fi } if [ "$target_arch" != "" ]; then build_package $package_name $target_arch else build_package $package_name uni fi psi-plus-snapshots-1.4.1456/admin/build/devconfig.sh000077500000000000000000000076531370065651000222600ustar00rootroot00000000000000#!/bin/sh set -e if [ $# != 1 ]; then echo "usage: $0 [arch]" exit 1 fi platform=`uname -s` if [ "$platform" == "Darwin" ]; then platform=mac elif [ "$platform" == "MINGW32_NT-6.1" ]; then platform=win else echo "error: unsupported platform $platform" exit 1 fi build_base=$PWD/admin/build . $build_base/package_info target_arch=$1 if [ "$platform" == "mac" ]; then target_platform=$target_arch-apple-darwin export MACOSX_DEPLOYMENT_TARGET=10.5 else if [ "$target_arch" == "x86_64" ]; then export CC=/c/mingw64/bin/gcc export CXX=/c/mingw64/bin/g++ export PATH=/c/mingw64/bin:$PATH fi fi pkgdir=$build_base/packages patchdir=$build_base/patches psi_base=$PWD deps_base=$build_base/deps get_msys_path() { if [ `expr index $1 :` -gt 0 ]; then pdrive=`echo $1 | cut -f 1 --delimiter=:` prest=`echo $1 | cut -f 2 --delimiter=:` echo /$pdrive$prest else echo $1 fi } old_pwd=$PWD cd $build_base && make deps cd $old_pwd if [ "$platform" == "win" ]; then if [ "$target_arch" == "x86_64" ]; then qtdir=$QTDIR64 else qtdir=$QTDIR32 fi mqtdir=`get_msys_path $qtdir` PATH=$mqtdir/bin:$PATH ./configure.exe --qtdir=$qtdir --release --with-qca-inc=$deps_base/$qca_win_dir/$target_arch/include --with-qca-lib=$deps_base/$qca_win_dir/$target_arch/lib --with-zlib-inc=$deps_base/$zlib_win_dir/$target_arch/include --with-zlib-lib=$deps_base/$zlib_win_dir/$target_arch/lib --with-aspell-inc=$deps_base/$aspell_win_dir/$target_arch/include --with-aspell-lib=$deps_base/$aspell_win_dir/$target_arch/lib rm -f $build_base/devenv touch $build_base/devenv if [ "$target_arch" == "x86_64" ]; then echo "export CC=/c/mingw64/bin/gcc" >> $build_base/devenv echo "export CXX=/c/mingw64/bin/g++" >> $build_base/devenv echo "export PATH=/c/mingw64/bin:\$PATH" >> $build_base/devenv fi echo "export PATH=$mqtdir/bin:$deps_base/$qca_win_dir/$target_arch/bin:$deps_base/$zlib_win_dir/$target_arch/bin:$deps_base/$aspell_win_dir/$target_arch/bin:$deps_base/$openssl_win_dir/$target_arch/bin:$deps_base/$gstbundle_win_dir/$target_arch/bin:\$PATH" >> $build_base/devenv echo "export QT_PLUGIN_PATH=$mqtdir/plugins:$deps_base/$qca_win_dir/$target_arch/plugins" >> $build_base/devenv echo "export PSI_MEDIA_PLUGIN=$deps_base/$psimedia_win_dir/$target_arch/plugins/gstprovider.dll" >> $build_base/devenv else if [ "$QT_LIB_PATH" == "" ]; then QT_LIB_PATH=$QTDIR/lib fi if [ "$QT_PLUGIN_PATH" == "" ]; then QT_PLUGIN_PATH=$QTDIR/plugins fi export DYLD_FRAMEWORK_PATH=$QT_LIB_PATH:$deps_base/$qca_mac_dir/lib:$deps_base/$growl_dir/Framework ./configure --with-qca-inc=$deps_base/$qca_mac_dir/include --with-qca-lib=$deps_base/$qca_mac_dir/lib --with-growl=$deps_base/$growl_dir/Framework --enable-universal # remove some gstbundle problem files rm -f $deps_base/$gstbundle_mac_dir/uni/lib/gstreamer-0.10/libgstximagesink.so rm -f $deps_base/$gstbundle_mac_dir/uni/lib/gstreamer-0.10/libgstxvimagesink.so rm -f $deps_base/$gstbundle_mac_dir/uni/lib/gstreamer-0.10/libgstximagesrc.so rm -f $deps_base/$gstbundle_mac_dir/uni/lib/gstreamer-0.10/libgstosxaudio.so rm -f $build_base/devenv touch $build_base/devenv echo "export DYLD_LIBRARY_PATH=$deps_base/$gstbundle_mac_dir/uni/lib:\$DYLD_LIBRARY_PATH" >> $build_base/devenv echo "export DYLD_FRAMEWORK_PATH=$QT_LIB_PATH:$deps_base/$qca_mac_dir/lib:$deps_base/$growl_dir/Framework:\$DYLD_FRAMEWORK_PATH" >> $build_base/devenv echo "export GST_PLUGIN_PATH=$deps_base/$gstbundle_mac_dir/uni/lib/gstreamer-0.10" >> $build_base/devenv echo "export GST_REGISTRY_FORK=no" >> $build_base/devenv echo "export QT_PLUGIN_PATH=$QT_PLUGIN_PATH:$deps_base/$qca_mac_dir/plugins" >> $build_base/devenv echo "export PSI_MEDIA_PLUGIN=$deps_base/$psimedia_mac_dir/plugins/libgstprovider.dylib" >> $build_base/devenv fi psi-plus-snapshots-1.4.1456/admin/build/gstbundle_gstplugins_mac000066400000000000000000000005221370065651000247520ustar00rootroot00000000000000libgstaudioconvert.so libgstaudioresample.so libgstcoreelements.so libgstcoreindexers.so libgstdecodebin.so libgstffmpegcolorspace.so libgstjpeg.so libgstlevel.so libgstogg.so libgstrtp.so libgstrtpmanager.so libgstspeex.so libgsttheora.so libgsttypefindfunctions.so libgstvideorate.so libgstvideoscale.so libgstvolume.so libgstvorbis.so psi-plus-snapshots-1.4.1456/admin/build/gstbundle_gstplugins_win000066400000000000000000000005441370065651000250130ustar00rootroot00000000000000libgstaudioconvert.dll libgstaudioresample.dll libgstcoreelements.dll libgstcoreindexers.dll libgstdecodebin.dll libgstffmpegcolorspace.dll libgstjpeg.dll libgstlevel.dll libgstogg.dll libgstrtp.dll libgstrtpmanager.dll libgstspeex.dll libgsttheora.dll libgsttypefindfunctions.dll libgstvideorate.dll libgstvideoscale.dll libgstvolume.dll libgstvorbis.dll psi-plus-snapshots-1.4.1456/admin/build/gstbundle_libs_mac000066400000000000000000000012361370065651000235070ustar00rootroot00000000000000libasprintf*.dylib libgettextlib*.dylib libgettextpo*.dylib libgettextsrc*.dylib libgio-2*.dylib libglib-2*.dylib libgmodule-2*.dylib libgobject-2*.dylib libgstaudio-0.10*.dylib libgstbase-0.10*.dylib libgstcontroller-0.10*.dylib libgstinterfaces-0.10*.dylib libgstnetbuffer-0.10*.dylib libgstpbutils-0.10*.dylib libgstreamer-0.10*.dylib libgstriff-0.10*.dylib libgstrtp-0.10*.dylib libgsttag-0.10*.dylib libgstvideo-0.10*.dylib libgthread-2*.dylib libintl*.dylib libogg*.dylib liborc*.dylib libspeex*.dylib libspeexdsp*.dylib libtheora*.dylib libtheoradec*.dylib libtheoraenc*.dylib libvorbis*.dylib libvorbisenc*.dylib libvorbisfile*.dylib libffi*.dylib libpcre*.dylibpsi-plus-snapshots-1.4.1456/admin/build/gstbundle_libs_win000066400000000000000000000012761370065651000235500ustar00rootroot00000000000000libasprintf-0.dll libcharset-1.dll libffi-5.dll libgettextlib-0-18-1.dll libgettextpo-0.dll libgettextsrc-0-18-1.dll libgio-2.0-0.dll libglib-2.0-0.dll libgmodule-2.0-0.dll libgobject-2.0-0.dll libgstaudio-0.10-0.dll libgstbase-0.10-0.dll libgstcontroller-0.10-0.dll libgstinterfaces-0.10-0.dll libgstnetbuffer-0.10-0.dll libgstpbutils-0.10-0.dll libgstreamer-0.10-0.dll libgstriff-0.10-0.dll libgstrtp-0.10-0.dll libgsttag-0.10-0.dll libgstvideo-0.10-0.dll libgthread-2.0-0.dll libiconv-2.dll libintl-8.dll libogg-0.dll liborc-0.4-0.dll liborc-test-0.4-0.dll libspeex-1.dll libspeexdsp-1.dll libtheora-0.dll libtheoradec-1.dll libtheoraenc-1.dll libvorbis-0.dll libvorbisenc-2.dll libvorbisfile-3.dll psi-plus-snapshots-1.4.1456/admin/build/pack_dmg.sh000077500000000000000000000017521370065651000220530ustar00rootroot00000000000000#!/bin/sh set -e if [ $# != 3 ]; then echo "usage: $0 [dmg file] [volume name] [content dir]" exit 1 fi VOLUME_NAME=$2 DISK_DIR=$3 WC_DMG=wc.dmg WC_DIR=wc TEMPLATE_DMG=template.dmg MASTER_DMG=$1 # generate empty template if [ ! -f "$TEMPLATE_DMG.bz2" ]; then echo generating empty template mkdir template hdiutil create -size 160m "$TEMPLATE_DMG" -srcfolder template -format UDRW -volname "$VOLUME_NAME" -quiet rmdir template bzip2 "$TEMPLATE_DMG" fi if [ ! -f "$TEMPLATE_DMG" ]; then bunzip2 -k $TEMPLATE_DMG.bz2 fi # fill in with psi echo making psi dmg cp $TEMPLATE_DMG $WC_DMG mkdir -p $WC_DIR hdiutil attach "$WC_DMG" -noautoopen -quiet -mountpoint "$WC_DIR" cp -a $DISK_DIR/* $WC_DIR diskutil eject `diskutil list | grep "$VOLUME_NAME" | grep "Apple_HFS" | awk '{print $6}'` rm -f $MASTER_DMG hdiutil convert "$WC_DMG" -quiet -format UDZO -imagekey zlib-level=9 -o $MASTER_DMG rm -rf $WC_DIR $WC_DMG hdiutil internet-enable -yes -quiet $MASTER_DMG || true psi-plus-snapshots-1.4.1456/admin/build/package_info000066400000000000000000000027531370065651000223020ustar00rootroot00000000000000PACKAGES="zlib_win qca_win qca_mac openssl_win aspell_win growl gstbundle_mac psimedia_mac" zlib_win_file=zlib-1.2.7-win.zip zlib_win_url=https://psi-im.org/files/deps/zlib-1.2.7-win.zip zlib_win_dir=zlib-1.2.7-win qca_win_file=qca-2.0.3-win.zip qca_win_url=https://psi-im.org/files/deps/qca-2.0.3-win.zip qca_win_dir=qca-2.0.3-win qca_mac_file=qca-2.0.3-mac.tar.bz2 qca_mac_url=https://psi-im.org/files/deps/qca-2.0.3-mac.tar.bz2 qca_mac_dir=qca-2.0.3-mac openssl_win_file=openssl-1.0.1c-win.zip openssl_win_url=https://psi-im.org/files/deps/openssl-1.0.1c-win.zip openssl_win_dir=openssl-1.0.1c-win aspell_win_file=aspell-0.60.6.1-win.zip aspell_win_url=https://psi-im.org/files/deps/aspell-0.60.6.1-win.zip aspell_win_dir=aspell-0.60.6.1-win growl_file=Growl-1.3.1-SDK.zip growl_url=https://growl.cachefly.net/Growl-1.3.1-SDK.zip growl_dir=Growl-1.3.1-SDK gstbundle_win_file=gstbundle-0.10.36-win.zip gstbundle_win_url=https://psi-im.org/files/deps/gstbundle-0.10.36-win.zip gstbundle_win_dir=gstbundle-0.10.36-win gstbundle_mac_file=gstbundle-0.10.36-mac.tar.bz2 gstbundle_mac_url=https://psi-im.org/files/deps/gstbundle-0.10.36-mac.tar.bz2 gstbundle_mac_dir=gstbundle-0.10.36-mac psimedia_win_file=psimedia-20120725-win.zip psimedia_win_url=https://psi-im.org/files/deps/psimedia-20120725-win.zip psimedia_win_dir=psimedia-20120725-win psimedia_mac_file=psimedia-20120725-mac.tar.bz2 psimedia_mac_url=https://psi-im.org/files/deps/psimedia-20120725-mac.tar.bz2 psimedia_mac_dir=psimedia-20120725-mac psi-plus-snapshots-1.4.1456/admin/build/prep_dist.sh000077500000000000000000000136271370065651000223030ustar00rootroot00000000000000#!/bin/sh set -e if [ $# != 2 ]; then echo "usage: $0 [prefix] [distdir]" exit 1 fi platform=`uname -s` if [ "$platform" == "Darwin" ]; then platform=mac elif [ "$platform" == "MINGW32_NT-6.1" ]; then platform=win else echo "error: unsupported platform $platform" exit 1 fi . ./package_info destdir= base_prefix=$1 dist_base=$2 build_base=$PWD psi_base=$PWD/../.. deps_base=$PWD/deps mkdir -p $dist_base # args: path to framework, framework name, framework version cleanup_framework() { # remove dev stuff rm -rf $1/Headers rm -f $1/${2}_debug rm -f $1/${2}_debug.prl rm -rf $1/Versions/$3/Headers rm -f $1/Versions/$3/${2}_debug rm -f $1/Versions/$3/${2}_debug.prl } if [ "$platform" == "mac" ]; then target_base=$destdir$base_prefix target_dist_base=$dist_base mkdir -p $target_dist_base QT_FRAMEWORKS="QtCore QtNetwork QtXml QtGui QtSql" QT_PLUGINS="imageformats/libqjpeg.dylib imageformats/libqgif.dylib imageformats/libqmng.dylib sqldrivers/libqsqlite.dylib" QCA_PLUGINS="crypto/libqca-ossl.dylib crypto/libqca-gnupg.dylib" cp -a $psi_base/psi.app $target_dist_base/Psi.app contentsdir=$target_dist_base/Psi.app/Contents for g in $QT_FRAMEWORKS; do install_name_tool -change $QTDIR/lib/$g.framework/Versions/4/$g @executable_path/../Frameworks/$g.framework/Versions/4/$g $contentsdir/MacOS/psi done install_name_tool -change qca.framework/Versions/2/qca @executable_path/../Frameworks/qca.framework/Versions/2/qca $contentsdir/MacOS/psi mkdir -p $contentsdir/Frameworks for f in $QT_FRAMEWORKS; do cp -a $QTDIR/lib/$f.framework $contentsdir/Frameworks cleanup_framework $contentsdir/Frameworks/$f.framework $f 4 install_name_tool -id @executable_path/../Frameworks/$f.framework/Versions/4/$f $contentsdir/Frameworks/$f.framework/$f for g in $QT_FRAMEWORKS; do install_name_tool -change $QTDIR/lib/$g.framework/Versions/4/$g @executable_path/../Frameworks/$g.framework/Versions/4/$g $contentsdir/Frameworks/$f.framework/$f done done for p in $QT_PLUGINS; do mkdir -p $contentsdir/Plugins/$(dirname $p); cp -a $QTDIR/plugins/$p $contentsdir/Plugins/$p for g in $QT_FRAMEWORKS; do install_name_tool -change $QTDIR/lib/$g.framework/Versions/4/$g @executable_path/../Frameworks/$g.framework/Versions/4/$g $contentsdir/Plugins/$p done done cp -a $deps_base/$qca_mac_dir/lib/qca.framework $contentsdir/Frameworks cleanup_framework $contentsdir/Frameworks/qca.framework qca 2 install_name_tool -id @executable_path/../Frameworks/qca.framework/Versions/2/qca $contentsdir/Frameworks/qca.framework/qca for g in $QT_FRAMEWORKS; do install_name_tool -change $g.framework/Versions/4/$g @executable_path/../Frameworks/$g.framework/Versions/4/$g $contentsdir/Frameworks/qca.framework/qca done mkdir -p $contentsdir/Plugins/crypto for p in $QCA_PLUGINS; do cp -a $deps_base/$qca_mac_dir/plugins/$p $contentsdir/Plugins/$p for g in $QT_FRAMEWORKS; do install_name_tool -change $g.framework/Versions/4/$g @executable_path/../Frameworks/$g.framework/Versions/4/$g $contentsdir/Plugins/$p done install_name_tool -change qca.framework/Versions/2/qca @executable_path/../Frameworks/qca.framework/Versions/2/qca $contentsdir/Plugins/$p done cp -a $deps_base/$growl_dir/Framework/Growl.framework $contentsdir/Frameworks cleanup_framework $contentsdir/Frameworks/Growl.framework Growl A GSTBUNDLE_LIB_FILES= for n in `cat $build_base/gstbundle_libs_mac`; do for l in `find $deps_base/$gstbundle_mac_dir/uni/lib -maxdepth 1 -type f -name $n`; do base_l=`basename $l` GSTBUNDLE_LIB_FILES="$GSTBUNDLE_LIB_FILES $base_l" done done GSTBUNDLE_LIB_SUBS= for n in `cat $build_base/gstbundle_libs_mac`; do for l in `find $deps_base/$gstbundle_mac_dir/uni/lib -maxdepth 1 -name $n`; do base_l=`basename $l` GSTBUNDLE_LIB_SUBS="$GSTBUNDLE_LIB_SUBS $base_l" done done GSTBUNDLE_LIB_GST_FILES= for n in `cat $build_base/gstbundle_gstplugins_mac`; do for l in `find $deps_base/$gstbundle_mac_dir/uni/lib/gstreamer-0.10 -type f -name $n`; do base_l=`basename $l` if [ "$base_l" != "libgstosxaudio.so" ]; then GSTBUNDLE_LIB_GST_FILES="$GSTBUNDLE_LIB_GST_FILES $base_l" fi done done # subs are files we need to copy, in addition to being what we attempt to substitute via install_name_tool for l in $GSTBUNDLE_LIB_SUBS; do cp -a $deps_base/$gstbundle_mac_dir/uni/lib/$l $contentsdir/Frameworks done # now to substitutions on the regular files only for l in $GSTBUNDLE_LIB_FILES; do for g in $GSTBUNDLE_LIB_SUBS; do install_name_tool -change $g @executable_path/../Frameworks/$g $contentsdir/Frameworks/$l done done mkdir -p $contentsdir/Frameworks/gstreamer-0.10 for p in $GSTBUNDLE_LIB_GST_FILES; do cp $deps_base/$gstbundle_mac_dir/uni/lib/gstreamer-0.10/$p $contentsdir/Frameworks/gstreamer-0.10 for g in $GSTBUNDLE_LIB_SUBS; do install_name_tool -change $g @executable_path/../Frameworks/$g $contentsdir/Frameworks/gstreamer-0.10/$p done done cp $deps_base/$psimedia_mac_dir/plugins/libgstprovider.dylib $contentsdir/Plugins for g in $GSTBUNDLE_LIB_SUBS; do install_name_tool -change $g @executable_path/../Frameworks/$g $contentsdir/Plugins/libgstprovider.dylib done for g in $QT_FRAMEWORKS; do install_name_tool -change $g.framework/Versions/4/$g @executable_path/../Frameworks/$g.framework/Versions/4/$g $contentsdir/Plugins/libgstprovider.dylib done else target_base=$destdir$base_prefix target_dist_base=$dist_base mkdir -p $target_dist_base cp -a $target_base/* $target_dist_base fi psi-plus-snapshots-1.4.1456/admin/bundle_qca.sh000066400000000000000000000014241370065651000212750ustar00rootroot00000000000000#!/bin/sh TARGET=third-party ################################################################################ # QCA ################################################################################ QCA_SOURCE=../qca QCA_TARGET=third-party/qca rm -rf $QCA_TARGET cp -r $QCA_SOURCE/src $QCA_TARGET cp -r $QCA_SOURCE/certs $QCA_TARGET cp -r $QCA_SOURCE/include $QCA_TARGET rm $QCA_TARGET/qt.tag $QCA_TARGET/src.pro echo 'include(../qca.pri)' > $QCA_TARGET/qca.pro ################################################################################ # QCA-OpenSSL ################################################################################ QCAOPENSSL_SOURCE=../qca-openssl QCAOPENSSL_TARGET=third-party/qca-openssl rm -rf $QCAOPENSSL_TARGET cp -r $QCAOPENSSL_SOURCE $QCA_OPENSSL_TARGET psi-plus-snapshots-1.4.1456/admin/emoji.py000077500000000000000000000065051370065651000203310ustar00rootroot00000000000000#!/usr/bin/env python3 # emoji.py - Emojis registry generator # Copyright (C) 2020 Sergey Ilinykh # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . import re import pprint data = [] ranges = [] template_main="""// This is a generated file. See emoji.py for details // clang-format off static std::vector db = {{{groups} }}; static std::map ranges = {{ {ranges} }}; // clang-format on """ template_group=""" {{ QT_TR_NOOP("{name}"), {{{subgroups} }} }}""" template_subgroup = """ {{ "{name}", {{{emojis} }} }}""" template_emoji = """ {{ "{code}", "{desc}" }}""" def reset(): global group, subgroup group = dict(name="", subgroups=[]) subgroup = dict(name="", emojis=[]) def parse(f): for line in f: if line.startswith("# group:"): reset() group["name"] = line.split(":")[1].strip() data.append(group) elif line.startswith("# subgroup:"): subgroup = dict(name=line.split(":")[1].strip(), emojis=[]) group["subgroups"].append(subgroup) elif not line.strip() or line.startswith("#"): continue else: match = re.match(r'^(.+);.*# .* E\d+\.\d+ (.*)', line) desc = match.group(2).split(':') code = "".join([chr(int(c, 16)) for c in match.group(1).strip().split()]) subgroup["emojis"].append((code, desc[0].strip())) def generate_ranges(): emojis=[] for g in data: for sg in g["subgroups"]: emojis += sg["emojis"] emojis = sorted(emojis) cur_code = range_start = 0 for emoji in emojis: new_code = ord(emoji[0][0]) if new_code != cur_code and new_code != cur_code + 1: if range_start != 0: ranges.append((range_start, cur_code)) range_start = new_code cur_code = new_code ranges.append((range_start, cur_code)) def generate_cpp_db(): print(template_main.format(groups=",".join([ template_group.format(name=group["name"], subgroups=",".join([ template_subgroup.format(name=sub["name"], emojis=",".join([ template_emoji.format(code=emoji[0], desc=emoji[1]) for emoji in sub["emojis"] ])) for sub in group["subgroups"] ])) for group in data ]), ranges=",\n ".join([f"{{{r[0]}, {r[1]}}}" for r in ranges]))) # https://unicode.org/Public/emoji/13.0/emoji-test.txt with open("emoji-test.txt") as f: reset() parse(f) generate_ranges() generate_cpp_db() psi-plus-snapshots-1.4.1456/admin/fetch.sh000077500000000000000000000005461370065651000203000ustar00rootroot00000000000000#!/bin/sh if [ $# != 2 ]; then echo "usage: $0 [url] [file]" exit 1 fi WGET=`which wget` if [ ! -z "$WGET" ]; then echo $WGET -O $2 $1 $WGET -O $2 $1 else CURL=`which curl` if [ ! -z "$CURL" ]; then echo $CURL -o $2 $1 $CURL -o $2 $1 else echo "error: need wget or curl in path" exit 1 fi fi psi-plus-snapshots-1.4.1456/admin/git_revnumber.sh000077500000000000000000000003721370065651000220540ustar00rootroot00000000000000#!/bin/sh cd $(dirname "$0") ref_commit="$(git describe --tags | cut -d - -f1)" if [ ! -z "${1}" ]; then if [ "$(git tag | grep -x "^${1}$" | wc -l)" = "1" ]; then ref_commit="${1}" fi fi git rev-list --count ${ref_commit}..HEAD psi-plus-snapshots-1.4.1456/admin/iccfix.sh000066400000000000000000000005121370065651000204420ustar00rootroot00000000000000#!/bin/sh find ./ -name '*.png' -exec pngout -kiCCP,gAMA '{}' \; for f in ./iconsets/roster/*.jisp; do isn=$(basename $f .jisp); f=$(readlink -f $f); ( mkdir ~/temp/is; cd ~/temp/is; unzip $f; for p in $isn/*.png; do pngout -kiCCP,gAMA $p; done; zip -r9 $f.x * && mv $f.x $f || echo "Failed to zip" ; rm -rf ~/temp/is; ) ; done psi-plus-snapshots-1.4.1456/admin/merge_release_to_master.sh000077500000000000000000000006571370065651000240660ustar00rootroot00000000000000#!/bin/sh set -e if [ -z "${1}" ]; then echo "Error! The name of release branch is not specified!" echo "Example of usage:" echo "${0} release-1.x" exit 1 fi MAIN_DIR="$(realpath -s $(dirname ${0})/..)" cd "${MAIN_DIR}" git checkout master git merge "origin/${1}" --no-ff --no-commit 2>&1 > /dev/null || true git checkout master . 2>&1 > /dev/null git commit -a -m "Merge remote-tracking branch 'origin/${1}'" psi-plus-snapshots-1.4.1456/admin/prune.sh000066400000000000000000000001511370065651000203250ustar00rootroot00000000000000#!/bin/sh rm -rf _darcs .darcs_boring .darcs_binaries rm -rf .git iris/.git src/libpsi/.git rm -rf TODO psi-plus-snapshots-1.4.1456/admin/psi-plus-nightly-version000077500000000000000000000022141370065651000235030ustar00rootroot00000000000000#!/bin/sh # first argument - path to cloned Psi repo # second argument[optional] - --webkit show_help() { echo "Usage: $0 [--webkit|--webengine] [--sql]" } [ -d "${1}/.git" ] || { show_help; exit 1; } psi_path="$(cd $1;pwd)" cd "$(dirname "$0")" # revs psi_rev="$(cd "$psi_path"; git rev-parse --short HEAD)" plus_rev="$(git rev-parse --short HEAD)" plus_tag=$(git describe --tags | cut -d - -f1) # compute version number psi_num=$("${psi_path}/admin/git_revnumber.sh" "${plus_tag}") plus_num=$(git describe --tags | cut -d - -f2) [ "$(echo ${plus_num} | grep \\. | wc -l)" != 0 ] && plus_num=0 sum_commit=$(expr ${psi_num} + ${plus_num}) # compute version date rev_date_list="$(cd "${psi_path}"; git log -n1 --date=short --pretty=format:'%ad') $(git log -n1 --date=short --pretty=format:'%ad')" rev_date=$(echo "${rev_date_list}" | sort -r | head -n1) # features list features=$([ "${2}" = --webkit ] && echo ", webkit")$([ "${2}" = --webengine ] && echo ", webengine")$([ "${2}" = --sql ] && echo ", sql")$([ "${3}" = --sql ] && echo ", sql") echo "${plus_tag}.${sum_commit} (${rev_date}, Psi:${psi_rev}, Psi+:${plus_rev}${features})" psi-plus-snapshots-1.4.1456/admin/psibuild_mac.sh000077500000000000000000000647101370065651000216450ustar00rootroot00000000000000#!/bin/bash #Dependencies: #git #cmake #xcode #Qt 5.x # Qt5 Settings MAC_SDK_VER=10.12 MAC_DEPLOYMENT_TARGET=10.9 QTDIR="${HOME}/Qt/5.9.7/clang_64" QT_FRAMEWORK_VERSION=5 QT_FRAMEWORKS="QtCore QtNetwork QtXml QtGui QtMultimedia QtMultimediaWidgets QtWidgets QtConcurrent QtPrintSupport QtOpenGL QtSvg QtWebEngineWidgets QtWebEngineCore QtQuick QtQml QtWebChannel QtPositioning QtQuickWidgets QtSql QtDBus" #QtWebEngine QT_PLUGINS="audio/libqtaudio_coreaudio.dylib bearer/libqgenericbearer.dylib platforms/libqcocoa.dylib printsupport/libcocoaprintersupport.dylib iconengines/libqsvgicon.dylib" QT_PLUGINS="${QT_PLUGINS} mediaservice/libqtmedia_audioengine.dylib mediaservice/libqavfmediaplayer.dylib styles/libqmacstyle.dylib sqldrivers/libqsqlite.dylib" QT_PLUGINS="${QT_PLUGINS} imageformats/libqgif.dylib imageformats/libqjpeg.dylib imageformats/libqsvg.dylib imageformats/libqwbmp.dylib imageformats/libqtiff.dylib imageformats/libqwebp.dylib imageformats/libqtga.dylib imageformats/libqico.dylib imageformats/libqicns.dylib imageformats/libqmacjp2.dylib" export QMAKESPEC="macx-clang" # OPTIONS / НАСТРОЙКИ # build and store directory / каталог для сорсов и сборки PSI_DIR="${HOME}/psi" QCONFDIR="${PSI_DIR}/qconf" DEPS_PREFIX="${PSI_DIR}/deps" PSI_APP="psi.app" GROWL_URL="https://drive.google.com/uc?export=download&id=0B9THQ10qg_RSNWhuOW1rbV9WYmc" GROWL_FILE="Growl-2.0.1" DEPS_URL="https://drive.google.com/uc?export=download&id=0B9THQ10qg_RSaXplUDlXNEZ3ZDQ" DEPS_FILE="deps_170602" DEPS_DIR="deps" GST_URL="https://drive.google.com/uc?export=download&id=0B9THQ10qg_RSa0hTWWJydDdoN1k" GST_FILE="gstbundle-0.10.36-mac" GST_DIR=$GST_FILE PSIMEDIA_URL="https://drive.google.com/uc?export=download&id=0B9THQ10qg_RSYmtWbk1zN2JJems" PSIMEDIA_FILE="psimedia-1.0.5-mac" PSIMEDIA_DIR=$PSIMEDIA_FILE # official repository GIT_REPO="https://github.com/psi-im/psi.git" GIT_REPO_PLUS=https://github.com/psi-plus/main.git GIT_REPO_PLUGINS=https://github.com/psi-im/plugins.git GIT_REPO_MAINTENANCE=https://github.com/psi-plus/maintenance.git GIT_REPO_RESOURCES=https://github.com/psi-plus/resources.git QCONF_REPO_URI="https://github.com/psi-plus/qconf.git" LANGS_REPO_URI="https://github.com/psi-plus/psi-plus-l10n.git" QCA_REPO_URI="https://github.com/KDE/qca.git" QTKEYCHAIN_REPO_URI="https://github.com/frankosterfeld/qtkeychain.git" LIBSIGNAL_REPO_URI="https://github.com/signalapp/libsignal-protocol-c.git" ALL_TRANS="1" ENABLE_WEBKIT="0" WORK_OFFLINE="0" SSL_PATH="${DEPS_PREFIX}" LIBS_PATH="${DEPS_PREFIX}" QCA_PREFIX="${QTDIR}" #"${DEPS_PREFIX}" QCA_PATH="${PSI_DIR}/qca-build" QCA_VER="2.2.0" QCA_PLUGINS_PATH=${QCA_PREFIX}/plugins/crypto #${QCA_PATH}/lib/qca-qt5/crypto QTKEYCHAIN_PATH="${PSI_DIR}/qtkeychain-build" QTKEYCHAIN_PREFIX="${QTDIR}" #"${DEPS_PREFIX}" LIBSIGNAL_PATH="${PSI_DIR}/libsignal-build" export PATH="$QTDIR/bin:$PATH" ####################### # FUNCTIONS / ФУНКЦИИ # ####################### # Exit with error message die() { echo; echo " !!!ERROR: $@"; exit 1; } warning() { echo; echo " !!!WARNING: $@"; } log() { echo -e "\033[1m*** $@ \033[0m"; } get_qconf() { cd "${PSI_DIR}" if [ ! -f "$QCONFDIR/qconf" ]; then git_fetch "${QCONF_REPO_URI}" qconf "QConf" cd qconf && ./configure && $MAKE $MAKEOPT || die "Can't build qconf!" fi if [ -f "$QCONFDIR/qconf" ]; then QCONF="$QCONFDIR/qconf" else die "Can'find qconf!" fi } check_env() { log "Testing environment..." if [ ! -d "${PSI_DIR}" ] then mkdir "${PSI_DIR}" || die "can't create work directory ${PSI_DIR}" fi MAKEOPT=${MAKEOPT:--j$((`sysctl -n hw.ncpu`+1)) -s } STAT_USER_ID='stat -f %u' STAT_USER_NAME='stat -f %Su' SED_INPLACE_ARG=".bak" v=`cmake --version 2>/dev/null` || \ die "You should install CMake first. / Сначала установите CMake" v=`git --version 2>/dev/null` || \ die "You should install Git first. / Сначала установите Git (http://git-scm.com/download)" # Make if [ ! -f "${MAKE}" ]; then MAKE="" for gn in gmake make; do [ -n "`$gn --version 2>/dev/null`" ] && { MAKE="$gn"; break; } done [ -z "${MAKE}" ] && die "You should install GNU Make first / "\ "Сначала установите GNU Make" fi log "\tFound make tool: ${MAKE}" # QConf QCONF=`which qconf` if [ -z "${QCONF}" ] then for qc in qt-qconf qconf qconf-qt4; do v=`$qc --version 2>/dev/null |grep affinix` && QCONF=$qc done fi if [ -z "${QCONF}" ] then get_qconf fi [ ! -z "${QCONF}" ] && log "\tFound qconf tool: " $QCONF find_qt_util() { local name=$1 result="" for un in $QTDIR/bin/$name $QTDIR/bin/$name-qt4 $QTDIR/bin/qt4-${name} $QTDIR/bin/${name}4; do [ -n "`$un -v 2>&1 |grep Qt`" ] && { result="$un"; break; } done if [ -z "${result}" ]; then [ "$nonfatal" = 1 ] || die "You should install $name util as part of"\ "Qt framework / Сначала установите утилиту $name из Qt framework" log "${name} Qt tool is not found. ignoring.." else log "\tFound ${name} Qt tool: ${result}" fi } local result # qmake find_qt_util qmake; QMAKE="${result}" find_qt_util macdeployqt; MACDEPLOYQT="${result}" nonfatal=1 find_qt_util lrelease; LRELEASE="${result}" log "Environment is OK" } git_fetch() { local remote="$1" local target="$2" local comment="$3" local curd=`pwd` local forcesubmodule=0 [ -d "${target}/.git" ] && [ "$(cd "${target}" && git config --get remote.origin.url)" = "${remote}" ] && { [ $WORK_OFFLINE = 0 ] && { cd "${target}" [ -n "${comment}" ] && log "Update ${comment} .." git pull 2>/dev/null || die "git update failed" cd "${curd}" } || true } || { forcesubmodule=1 log "Checkout ${comment} .." [ -d "${target}" ] && rm -rf "$target" git clone "${remote}" "$target" 2>/dev/null || die "git clone failed" } [ $WORK_OFFLINE = 0 ] && { cd "${target}" git submodule update --init 2>/dev/null || die "git submodule update failed" } cd "${curd}" } cleanup_framework() { # remove dev stuff rm -rf $1/Headers rm -f $1/${2}_debug rm -f $1/${2}_debug.prl rm -rf $1/Versions/$3/Headers rm -f $1/Versions/$3/${2}_debug rm -f $1/Versions/$3/${2}_debug.prl } prepare_workspace() { log "Init directories..." fetch_sources fetch_deps rm -rf "${PSI_DIR}"/build [ -d "${PSI_DIR}"/build ] && die "can't delete old build directory ${PSI_DIR}/build" mkdir "${PSI_DIR}"/build || die "can't create build directory ${PSI_DIR}/build" log "\tCreated base directory structure" cd "${PSI_DIR}" get_framework $GROWL_URL $GROWL_FILE Growl.framework if [ ! -f "${QCA_PREFIX}/lib/qca-qt5.framework/qca-qt5" ] then build_qca fi if [ ! -f "${QTKEYCHAIN_PREFIX}/lib/libqt5keychain.1.dylib" ] then build_qtkeychain fi if [ ! -f "${LIBS_PATH}/lib/libsignal-protocol-c.a" ] then build_libsignal fi } get_framework() { get_url=$1 file_name=$2 if [ ! -f $file_name.tar.bz2 ]; then log "Downloading $file_name" curl -L -o $file_name.tar.bz2 $get_url || die "can't download url $get_url" tar jxvf $file_name.tar.bz2 2>/dev/null || die "can't extract file $file_name" fi } fetch_sources() { log "Fetch sources..." cd "${PSI_DIR}" git_fetch "${GIT_REPO}" psi "Psi" git_fetch "${GIT_REPO_PLUS}" git-plus "Psi+ additionals" git_fetch "${GIT_REPO_MAINTENANCE}" maintenance "Psi+ maintenance" git_fetch "${GIT_REPO_RESOURCES}" resources "Psi+ resources" git_fetch "${GIT_REPO_PLUGINS}" plugins "Psi plugins" git_fetch "${LANGS_REPO_URI}" translations "Psi+ translations" git_fetch "${QCA_REPO_URI}" qca "QCA" git_fetch "${QTKEYCHAIN_REPO_URI}" qtkeychain "QtKeychain" git_fetch "${LIBSIGNAL_REPO_URI}" libsignal "libsignal" } get_deps() { local deps_file=$1 local deps_url=$2 local deps_dir=$3 if [ ! -d ${deps_dir} ] then if [ ! -f ${deps_file}.tar.bz2 ] then log "Downloading ${deps_file}" curl -L -o ${deps_file}.tar.bz2 ${deps_url} || die "can't download url ${deps_url}" fi log "Extracting ${deps_file}" tar jxvf ${deps_file}.tar.bz2 2>/dev/null || die "can't extract file ${deps_file}" fi } fetch_deps() { cd ${PSI_DIR} get_deps $DEPS_FILE $DEPS_URL $DEPS_DIR get_deps $GST_FILE $GST_URL $GST_DIR get_deps $PSIMEDIA_FILE $PSIMEDIA_URL $PSIMEDIA_DIR } build_qca() { mkdir -p "${QCA_PATH}" && cd "${QCA_PATH}" || die "Can't create QCA build folder" #export CC="/usr/bin/clang" #export CXX="/usr/bin/clang++" log "Compiling QCA..." sed -ie "s/target_link_libraries(qca-ossl crypto)/target_link_libraries(qca-ossl)/" "${PSI_DIR}/qca/plugins/qca-ossl/CMakeLists.txt" local opts="-DBUILD_TESTS=OFF -DOPENSSL_ROOT_DIR=${SSL_PATH} -DOPENSSL_LIBRARIES=${SSL_PATH}/lib -DLIBGCRYPT_LIBRARIES=${DEPS_PREFIX}/lib" #opts=$opts -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_CXX_FLAGS="-stdlib=libc++ -std=gnu++11 -arch x86_64" cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="${QCA_PREFIX}" -DQCA_PLUGINS_INSTALL_DIR="${QCA_PLUGINS_PATH}/.." $opts ${PSI_DIR}/qca 2>/dev/null || die "QCA configuring error" make ${MAKEOPT} || die "QCA build error" make install || die "Can't install QCA" install_name_tool -id @rpath/qca-qt5.framework/Versions/${QCA_VER}/qca-qt5 "${QCA_PREFIX}/lib/qca-qt5.framework/qca-qt5" QCA_PLUGINS=`ls ${QCA_PLUGINS_PATH} | grep "dylib"` for p in $QCA_PLUGINS; do install_name_tool -change "${QCA_PREFIX}/lib/qca-qt5.framework/Versions/${QCA_VER}/qca-qt5" "@rpath/qca-qt5.framework/Versions/${QCA_VER}/qca-qt5" "${QCA_PLUGINS_PATH}/$p" done } function build_libsignal() { mkdir -p "${LIBSIGNAL_PATH}" && cd "${LIBSIGNAL_PATH}" || die "Can't create LIBSIGNAL build folder" #export CC="/usr/bin/clang" #export CXX="/usr/bin/clang++" log "Compiling LIBSIGNAL..." cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="${LIBS_PATH}" -DCMAKE_OSX_DEPLOYMENT_TARGET=${MAC_DEPLOYMENT_TARGET} ../libsignal 2>/dev/null || die "LIBSIGNAL configuring error" make ${MAKEOPT} || die "LIBSIGNAL build error" make install || die "Can't install LIBSIGNAL" } function build_qtkeychain() { mkdir -p "${QTKEYCHAIN_PATH}" && cd "${QTKEYCHAIN_PATH}" || die "Can't create QTKEYCHAIN build folder" #export CC="/usr/bin/clang" #export CXX="/usr/bin/clang++" log "Compiling QTKEYCHAIN..." cmake -DCMAKE_INSTALL_PREFIX="${QTKEYCHAIN_PREFIX}" -DCMAKE_OSX_DEPLOYMENT_TARGET=${MAC_DEPLOYMENT_TARGET} -DCMAKE_BUILD_TYPE=Release ${PSI_DIR}/qtkeychain 2>/dev/null || die "QTKEYCHAIN configuring error" make ${MAKEOPT} || die "QTKEYCHAIN build error" make install || die "Can't install QTKEYCHAIN" #install_name_tool -id @rpath/qca-qt5.framework/Versions/${QCA_VER}/qca-qt5 "${QCA_PREFIX}/lib/qca-qt5.framework/qca-qt5" } prepare_sources() { log "Exporting sources..." cd "${PSI_DIR}" cp -a "psi/" "build/" TRANSLATIONS=`ls ${PSI_DIR}/translations/translations | grep -v en | sed s/psi_// | sed s/.ts//` local actual_translations="" LANGS_DIR="${PSI_DIR}/build/langs" [ -n "$TRANSLATIONS" ] && { mkdir -p $LANGS_DIR for l in $TRANSLATIONS; do mkdir -p "$LANGS_DIR/$l" cp -f "translations/translations/psi_$l.ts" "$LANGS_DIR/$l/psi_$l.ts" [ -n "${LRELEASE}" -o -f "$LANGS_DIR/$l/psi_$l.qm" ] && actual_translations="${actual_translations} $l" done actual_translations="$(echo $actual_translations)" [ -z "${actual_translations}" ] && warning "Translations not found" } log "Copying plugins..." cd "${PSI_DIR}/plugins/generic" PLUGINS=`find . -maxdepth 1 -name '*plugin' | cut -d"/" -f2 | grep -v "videostatusplugin"` cp -a "${PSI_DIR}/plugins/generic" "${PSI_DIR}/build/plugins" || \ die "preparing plugins requires prepared psi sources" cd "${PSI_DIR}/psi" local rev=$(./admin/git_revnumber.sh) local rev_date=$(git log -n1 --date=short --pretty=format:'%ad') cd ${PSI_DIR}/build local cur_ver=$(cat ./version | grep -Eoe "^[^\-]+") if [ -z $VERSION ]; then VERSION=$(echo "${cur_ver}.${rev}$([ "$ENABLE_WEBKIT" = 1 ] && echo "-webkit") ($(echo ${rev_date}))") else if [ $ENABLE_WEBKIT != 0 ]; then VERSION="${VERSION}-webkit" fi fi log "Psi version is ${VERSION}" echo "${VERSION}" > version } src_compile() { log "All ready. Now run make..." cd "${PSI_DIR}"/build CONF_OPTS="--disable-qdbus --enable-whiteboarding --disable-xss --release" if [ $ENABLE_WEBKIT != 0 ]; then CONF_OPTS=" --enable-webkit $CONF_OPTS" fi CONF_OPTS=" --with-idn-lib=${LIBS_PATH}/lib --with-idn-inc=${LIBS_PATH}/include --with-qca-lib=${QCA_PREFIX}/lib --with-zlib-lib=${LIBS_PATH}/lib --with-zlib-inc=${LIBS_PATH}/include --with-growl=${PSI_DIR} $CONF_OPTS" ${QCONF} || die "QConf failed" export DYLD_FRAMEWORK_PATH=${QCA_PREFIX}/lib:${PSI_DIR}:$DYLD_FRAMEWORK_PATH ./configure ${CONF_OPTS} || die "configure failed" if [ ! -z "${MAC_SDK_VER}" ]; then echo "QMAKE_MAC_SDK = macosx${MAC_SDK_VER}" >> conf.pri echo "QMAKE_MACOSX_DEPLOYMENT_TARGET = ${MAC_DEPLOYMENT_TARGET}" >> conf.pri fi echo "QMAKE_CXXFLAGS += -Wno-inconsistent-missing-override" >> conf.pri $MAKE $MAKEOPT || die "make failed" plugins_compile } prep_otr_plugin() { sed -ie "s/buffio.h/tidybuffio.h/" src/htmltidy.* sed -ie "s/unix {/unix:!mac {/" otrplugin.pro echo "INCLUDEPATH += ${DEPS_PREFIX}/include/ ${DEPS_PREFIX}/include/libotr" >> otrplugin.pro echo "LIBS += -L${DEPS_PREFIX}/lib" >> otrplugin.pro } prep_omemo_plugin() { sed -ie "s/unix {/unix:!mac {/" omemoplugin.pro echo "INCLUDEPATH += ${DEPS_PREFIX}/include ${DEPS_PREFIX}/include/signal" >> omemoplugin.pro echo "LIBS += -L${DEPS_PREFIX}/lib -lsignal-protocol-c -lcrypto" >> omemoplugin.pro } plugins_compile() { cd "${PSI_DIR}/build/plugins" echo "QMAKE_MAC_SDK = macosx${MAC_SDK_VER}" >> psiplugin.pri echo "QMAKE_MACOSX_DEPLOYMENT_TARGET = ${MAC_DEPLOYMENT_TARGET}" >> psiplugin.pri log "List plugins for compiling..." echo ${PLUGINS} log "Compiling plugins..." for pl in ${PLUGINS}; do cd ${PSI_DIR}/build/plugins/generic/${pl} && log "Compiling ${pl} plugin." if [ $pl = "otrplugin" ]; then prep_otr_plugin fi if [ $pl = "omemoplugin" ]; then prep_omemo_plugin fi $QMAKE && $MAKE $MAKEOPT || log "make ${pl} plugin failed" done } copy_qca() { log "\tCopying qca..." cp -a "${QCA_PREFIX}/lib/qca-qt5.framework" "$CONTENTSDIR/Frameworks" cleanup_framework "$CONTENTSDIR/Frameworks/qca-qt5.framework" qca-qt5 ${QCA_VER} mkdir -p "$BUNDLE_PLUGINS_DIR/crypto/" local QCA_PLUGINS=`ls ${QCA_PLUGINS_PATH} | grep "dylib"` for p in $QCA_PLUGINS; do cp -f "${QCA_PLUGINS_PATH}/$p" "$BUNDLE_PLUGINS_DIR/crypto/$p" install_name_tool -change "${SSL_PATH}/lib/libcrypto.1.0.0.dylib" "@executable_path/../Frameworks/libcrypto.dylib" "$BUNDLE_PLUGINS_DIR/crypto/$p" install_name_tool -change "${SSL_PATH}/lib/libssl.1.0.0.dylib" "@executable_path/../Frameworks/libssl.dylib" "$BUNDLE_PLUGINS_DIR/crypto/$p" install_name_tool -change "${LIBS_PATH}/lib/libgcrypt.20.dylib" "@executable_path/../Frameworks/libgcrypt.dylib" "$BUNDLE_PLUGINS_DIR/crypto/$p" install_name_tool -change "${LIBS_PATH}/lib/libgpg-error.0.dylib" "@executable_path/../Frameworks/libgpg-error.dylib" "$BUNDLE_PLUGINS_DIR/crypto/$p" done } copy_gst_bundle() { log "\tCopying psimedia..." local build_base=${PSI_DIR}/build/admin/build local gst_base=${PSI_DIR}/${GST_FILE}/x86_64 local psimedia_base=${PSI_DIR}/${PSIMEDIA_FILE} GSTBUNDLE_LIB_FILES= for n in `cat $build_base/gstbundle_libs_mac`; do for l in `find $gst_base/lib -maxdepth 1 -type f -name $n`; do base_l=`basename $l` GSTBUNDLE_LIB_FILES="$GSTBUNDLE_LIB_FILES $base_l" done done GSTBUNDLE_LIB_SUBS= for n in `cat $build_base/gstbundle_libs_mac`; do for l in `find $gst_base/lib -maxdepth 1 -name $n`; do base_l=`basename $l` GSTBUNDLE_LIB_SUBS="$GSTBUNDLE_LIB_SUBS $base_l" done done GSTBUNDLE_LIB_GST_FILES= for n in `cat $build_base/gstbundle_gstplugins_mac`; do for l in `find $gst_base/lib/gstreamer-0.10 -type f -name $n`; do base_l=`basename $l` if [ "$base_l" != "libgstosxaudio.so" ]; then GSTBUNDLE_LIB_GST_FILES="$GSTBUNDLE_LIB_GST_FILES $base_l" fi done done # subs are files we need to copy, in addition to being what we attempt to substitute via install_name_tool for l in $GSTBUNDLE_LIB_SUBS; do cp -a $gst_base/lib/$l $CONTENTSDIR/Frameworks done # now to substitutions on the regular files only for l in $GSTBUNDLE_LIB_FILES; do for g in $GSTBUNDLE_LIB_SUBS; do install_name_tool -change $g @executable_path/../Frameworks/$g $CONTENTSDIR/Frameworks/$l done done mkdir -p $CONTENTSDIR/Frameworks/gstreamer-0.10 for p in $GSTBUNDLE_LIB_GST_FILES; do cp $gst_base/lib/gstreamer-0.10/$p $CONTENTSDIR/Frameworks/gstreamer-0.10 for g in $GSTBUNDLE_LIB_SUBS; do install_name_tool -change $g @executable_path/../Frameworks/$g $CONTENTSDIR/Frameworks/gstreamer-0.10/$p done done cp $psimedia_base/plugins/libgstprovider.dylib $BUNDLE_PLUGINS_DIR for g in $GSTBUNDLE_LIB_SUBS; do install_name_tool -change $g @executable_path/../Frameworks/$g $BUNDLE_PLUGINS_DIR/libgstprovider.dylib done } copy_qt() { log "\tCopying Qt libs..." local plugins_dir_name=Plugins BUNDLE_PLUGINS_DIR=$CONTENTSDIR/$plugins_dir_name for f in $QT_FRAMEWORKS; do cp -a ${QTDIR}/lib/$f.framework $CONTENTSDIR/Frameworks cleanup_framework $CONTENTSDIR/Frameworks/$f.framework $f ${QT_FRAMEWORK_VERSION} done for p in $QT_PLUGINS; do mkdir -p $BUNDLE_PLUGINS_DIR/$(dirname $p); cp -a ${QTDIR}/plugins/$p $BUNDLE_PLUGINS_DIR/$p done qt_conf_file="$CONTENTSDIR/Resources/qt.conf" touch ${qt_conf_file} echo "[Paths]" >> ${qt_conf_file} echo "Plugins = ${plugins_dir_name}" >> ${qt_conf_file} install_name_tool -add_rpath "@executable_path/../Frameworks" "$CONTENTSDIR/MacOS/psi" } copy_main_libs() { log "\tCopying libs..." cp -f "${LIBS_PATH}/lib/libz.dylib" "$CONTENTSDIR/Frameworks/" cp -f "${LIBS_PATH}/lib/libidn.dylib" "$CONTENTSDIR/Frameworks/" cp -f "${SSL_PATH}/lib/libssl.dylib" "$CONTENTSDIR/Frameworks/" cp -f "${SSL_PATH}/lib/libcrypto.dylib" "$CONTENTSDIR/Frameworks/" chmod +w "$CONTENTSDIR/Frameworks/libssl.dylib" chmod +w "$CONTENTSDIR/Frameworks/libcrypto.dylib" cp -f "${QTKEYCHAIN_PREFIX}/lib/libqt5keychain.dylib" "$CONTENTSDIR/Frameworks/" install_name_tool -id "@executable_path/../Frameworks/libz.dylib" "$CONTENTSDIR/Frameworks/libz.dylib" install_name_tool -id "@executable_path/../Frameworks/libidn.dylib" "$CONTENTSDIR/Frameworks/libidn.dylib" install_name_tool -id "@executable_path/../Frameworks/libssl.dylib" "$CONTENTSDIR/Frameworks/libssl.dylib" install_name_tool -change "${SSL_PATH}/lib/libcrypto.1.0.0.dylib" "@executable_path/../Frameworks/libcrypto.dylib" "$CONTENTSDIR/Frameworks/libssl.dylib" install_name_tool -id "@executable_path/../Frameworks/libcrypto.dylib" "$CONTENTSDIR/Frameworks/libcrypto.dylib" install_name_tool -id "@executable_path/../Frameworks/libqt5keychain.dylib" "$CONTENTSDIR/Frameworks/libqt5keychain.dylib" install_name_tool -change "${LIBS_PATH}/lib/libz.1.dylib" "@executable_path/../Frameworks/libz.dylib" "$CONTENTSDIR/MacOS/psi" install_name_tool -change "${LIBS_PATH}/lib/libidn.11.dylib" "@executable_path/../Frameworks/libidn.dylib" "$CONTENTSDIR/MacOS/psi" install_name_tool -change "${LIBS_PATH}/lib/libqt5keychain.1.dylib" "@executable_path/../Frameworks/libqt5keychain.dylib" "$CONTENTSDIR/MacOS/psi" } copy_otrplugins_libs() { log "\tCopying otr libs..." cp -f "${LIBS_PATH}/lib/libgpg-error.dylib" "$CONTENTSDIR/Frameworks/" cp -f "${LIBS_PATH}/lib/libgcrypt.dylib" "$CONTENTSDIR/Frameworks/" cp -f "${LIBS_PATH}/lib/libotr.dylib" "$CONTENTSDIR/Frameworks/" cp -f "${LIBS_PATH}/lib/libtidy.dylib" "$CONTENTSDIR/Frameworks/" install_name_tool -id "@executable_path/../Frameworks/libgpg-error.dylib" "$CONTENTSDIR/Frameworks/libgpg-error.dylib" install_name_tool -id "@executable_path/../Frameworks/libgcrypt.dylib" "$CONTENTSDIR/Frameworks/libgcrypt.dylib" install_name_tool -change "${LIBS_PATH}/lib/libgpg-error.0.dylib" "@executable_path/../Frameworks/libgpg-error.dylib" "$CONTENTSDIR/Frameworks/libgcrypt.dylib" install_name_tool -id "@executable_path/../Frameworks/libotr.dylib" "$CONTENTSDIR/Frameworks/libotr.dylib" install_name_tool -change "${LIBS_PATH}/lib/libgcrypt.20.dylib" "@executable_path/../Frameworks/libgcrypt.dylib" "$CONTENTSDIR/Frameworks/libotr.dylib" install_name_tool -change "${LIBS_PATH}/lib/libgpg-error.0.dylib" "@executable_path/../Frameworks/libgpg-error.dylib" "$CONTENTSDIR/Frameworks/libotr.dylib" install_name_tool -id "@executable_path/../Frameworks/libtidy.dylib" "$CONTENTSDIR/Frameworks/libtidy.dylib" install_name_tool -change "${LIBS_PATH}/lib/libotr.5.dylib" "@executable_path/../Frameworks/libotr.dylib" "$CONTENTSDIR/Resources/plugins/libotrplugin.dylib" install_name_tool -change "${LIBS_PATH}/lib/libgcrypt.20.dylib" "@executable_path/../Frameworks/libgcrypt.dylib" "$CONTENTSDIR/Resources/plugins/libotrplugin.dylib" install_name_tool -change "${LIBS_PATH}/lib/libgpg-error.0.dylib" "@executable_path/../Frameworks/libgpg-error.dylib" "$CONTENTSDIR/Resources/plugins/libotrplugin.dylib" install_name_tool -change "${LIBS_PATH}/lib/libtidy.5.dylib" "@executable_path/../Frameworks/libtidy.dylib" "$CONTENTSDIR/Resources/plugins/libotrplugin.dylib" install_name_tool -change "libtidy.5.dylib" "@executable_path/../Frameworks/libtidy.dylib" "$CONTENTSDIR/Resources/plugins/libotrplugin.dylib" #can be this path } copy_plugins() { log "\tCopying plugins..." if [ ! -d $CONTENTSDIR/Resources/plugins ]; then mkdir -p "$CONTENTSDIR/Resources/plugins" fi for pl in ${PLUGINS}; do cd ${PSI_DIR}/build/plugins/generic/${pl} if [ -f "lib${pl}.dylib" ]; then cp "lib${pl}.dylib" "$CONTENTSDIR/Resources/plugins" fi done install_name_tool -change "${LIBS_PATH}/lib/libcrypto.1.0.0.dylib" "@executable_path/../Frameworks/libcrypto.dylib" "$CONTENTSDIR/Resources/plugins/libomemoplugin.dylib" } copy_resources() { log "\tCopying langpack, web, skins..." cd "${PSI_DIR}/build" app_bundl_tr_dir="$CONTENTSDIR/Resources/translations" mkdir "$app_bundl_tr_dir" cd "$app_bundl_tr_dir" for l in $TRANSLATIONS do f="$LANGS_DIR/$l/psi_$l" [ -n "${LRELEASE}" -a -f "${f}.ts" ] && "${LRELEASE}" -silent "${f}.ts" 2>/dev/null [ -f "${f}.qm" ] && cp "${f}.qm" . && { log "Copy translations files for ${l}" qt_tr=`ls ${QTDIR}/translations | grep "^qt.*_${l}"` for qtrl in $qt_tr; do cp -f "${QTDIR}/translations/${qtrl}" . done } done cd "$CONTENTSDIR/Resources/" cp -r ${PSI_DIR}/build/sound . cp -r ${PSI_DIR}/build/themes . cp -r ${PSI_DIR}/build/iconsets . cp -r ${PSI_DIR}/resources/sound . cp -f ${PSI_DIR}/build/client_icons.txt . } copy_growl() { log "\tCopying Growl..." cp -a "${PSI_DIR}/Growl.framework" "$CONTENTSDIR/Frameworks" cleanup_framework "$CONTENTSDIR/Frameworks/Growl.framework" Growl A } prepeare_bundle() { log "Copying dependencies..." cd "${PSI_DIR}/build" CONTENTSDIR=${PSI_DIR}/build/${PSI_APP}/Contents mkdir "$CONTENTSDIR/Frameworks" copy_qt copy_qca copy_plugins copy_growl copy_main_libs copy_otrplugins_libs copy_gst_bundle copy_resources } make_bundle() { log "Making standalone bundle..." VOLUME_NAME="Psi" WC_DMG=wc.dmg WC_DIR=wc TEMPLATE_DMG=template.dmg MASTER_DMG="psi.dmg" cd ${PSI_DIR}/build # generate empty template mkdir template hdiutil create -size 260m "$TEMPLATE_DMG" -srcfolder template -format UDRW -volname "$VOLUME_NAME" -quiet rmdir template cp $TEMPLATE_DMG $WC_DMG mkdir -p $WC_DIR hdiutil attach "$WC_DMG" -noautoopen -quiet -mountpoint "$WC_DIR" cp -a $PSI_APP $WC_DIR diskutil eject `diskutil list | grep "$VOLUME_NAME" | grep "Apple_HFS" | awk '{print $6}'` rm -f $MASTER_DMG hdiutil convert "$WC_DMG" -quiet -format UDZO -imagekey zlib-level=9 -o $MASTER_DMG rm -rf $WC_DIR $WC_DMG hdiutil internet-enable -yes -quiet $MASTER_DMG || true newf="${PSI_DIR}/psi-${VERSION}-mac.dmg" mv -f $MASTER_DMG "${newf}" } make_bundle2() { log "Making standalone bundle..." cd ${PSI_DIR}/build/admin/build cp -f "${PSI_DIR}/maintenance/scripts/macosx/template.dmg.bz2" "template.dmg.bz2" sh pack_dmg.sh "psi-${VERSION}-mac.dmg" "Psi" "dist/psi-${VERSION}-mac" newf="${PSI_DIR}/psi-${VERSION}-mac.dmg" mv -f "psi-${VERSION}-mac.dmg" "${newf}" } ############# # Go Go Go! # ############# while [ "$1" != "" ]; do case $1 in -w | --webkit ) ENABLE_WEBKIT=1 ;; -h | --help ) echo "usage: $0 [-w | --webkit] [-v | --verbose] VERSION" exit ;; * ) VERSION=$1 esac shift done starttime=`date "+Start time: %H:%M:%S"` check_env prepare_workspace prepare_sources src_compile prepeare_bundle make_bundle finishtime=`date "+Finish time: %H:%M:%S"` echo $starttime echo $finishtime psi-plus-snapshots-1.4.1456/admin/update_iconsets.sh000066400000000000000000000023741370065651000223760ustar00rootroot00000000000000#!/bin/sh SOURCE_DIR=../../iconsets TARGET_DIR=../iconsets ROSTER_DEFAULT='crystal-roster' ROSTER_EXTRAS=' crystal-icq crystal-service crystal-yahoo crystal-gadu crystal-sms crystal-roster ' SYSTEM_DEFAULT='crystal-system' SYSTEM_EXTRAS='' ################################################################################ if test ! -d $SOURCE_DIR; then echo "Cannot find source dir $SOURCE_DIR" exit fi if test ! -d $TARGET_DIR; then echo "Cannot find target dir $TARGET_DIR" exit fi ################################################################################ # Roster iconsets echo '*** Updating Roster iconsets ***' #cp -R $SOURCE_DIR/roster/$ROSTER_DEFAULT/* $TARGET_DIR/roster/default rm -f $TARGET_DIR/roster/default/Makefile for i in $ROSTER_EXTRAS; do make -C $SOURCE_DIR/roster $i.jisp cp $SOURCE_DIR/roster/$i.jisp $TARGET_DIR/roster done ################################################################################ # System iconsets echo '*** Updating System iconsets ***' #cp -R $SOURCE_DIR/system/$SYSTEM_DEFAULT/* $TARGET_DIR/system/default rm -f $TARGET_DIR/system/default/Makefile for i in $SYSTEM_EXTRAS; do make -C $SOURCE_DIR/system $i.jisp cp $SOURCE_DIR/system/$i.jisp $TARGET_DIR/system done psi-plus-snapshots-1.4.1456/admin/update_options_ts.py000077500000000000000000000013501370065651000227620ustar00rootroot00000000000000#!/usr/bin/python from xml.dom.minidom import parse, parseString import xml.dom import sys def rec_parse(node, context): # node : xml.dom.Node for i in node.childNodes: if i.nodeType == xml.dom.Node.ELEMENT_NODE: if i.hasAttribute("comment"): print 'QT_TRANSLATE_NOOP("' + context + '","' + i.getAttribute("comment") + '");'; rec_parse(i,context) if len(sys.argv) != 2: print "usage: %s options.xml > output.cpp" % sys.argv[0] sys.exit(1) print "#define QT_TRANSLATE_NOOP(a,b)" dom = parse(sys.argv[1]) # parse an XML file by name toplevel = dom.getElementsByTagName("psi")[0] options = toplevel.getElementsByTagName("options")[0] shortcuts = options.getElementsByTagName("shortcuts")[0] rec_parse(shortcuts,"Shortcuts") psi-plus-snapshots-1.4.1456/certs/000077500000000000000000000000001370065651000166735ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/certs/README000066400000000000000000000012221370065651000175500ustar00rootroot00000000000000This directory contains the SSL certificates shipped with psi/psi+. The files should be in PEM format, and should have the extension '.crt' or '.pem'. Please use PSIDATADIR/certs for local additions. Default value of PSIDATADIR for psi client: GNU/Linux, macOS, FreeBSD and other *nix systems ~/.local/share/psi Windows NT, 2000, XP and Server 2003 %APPDATA%\Psi Windows 7 and later %APPDATA%\Roaming\Psi Default value of PSIDATADIR for psi-plus client: GNU/Linux, macOS, FreeBSD and other *nix systems ~/.local/share/psi+ Windows NT, 2000, XP and Server 2003 %APPDATA%\Psi+ Windows 7 and later %APPDATA%\Roaming\Psi+ psi-plus-snapshots-1.4.1456/certs/startcom_ca.crt000066400000000000000000000034421370065651000217070ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFFjCCBH+gAwIBAgIBADANBgkqhkiG9w0BAQQFADCBsDELMAkGA1UEBhMCSUwx DzANBgNVBAgTBklzcmFlbDEOMAwGA1UEBxMFRWlsYXQxFjAUBgNVBAoTDVN0YXJ0 Q29tIEx0ZC4xGjAYBgNVBAsTEUNBIEF1dGhvcml0eSBEZXAuMSkwJwYDVQQDEyBG cmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJARYS YWRtaW5Ac3RhcnRjb20ub3JnMB4XDTA1MDMxNzE3Mzc0OFoXDTM1MDMxMDE3Mzc0 OFowgbAxCzAJBgNVBAYTAklMMQ8wDQYDVQQIEwZJc3JhZWwxDjAMBgNVBAcTBUVp bGF0MRYwFAYDVQQKEw1TdGFydENvbSBMdGQuMRowGAYDVQQLExFDQSBBdXRob3Jp dHkgRGVwLjEpMCcGA1UEAxMgRnJlZSBTU0wgQ2VydGlmaWNhdGlvbiBBdXRob3Jp dHkxITAfBgkqhkiG9w0BCQEWEmFkbWluQHN0YXJ0Y29tLm9yZzCBnzANBgkqhkiG 9w0BAQEFAAOBjQAwgYkCgYEA7YRgACOeyEpRKSfeOqE5tWmrCbIvNP1h3D3TsM+x 18LEwrHkllbEvqoUDufMOlDIOmKdw6OsWXuO7lUaHEe+o5c5s7XvIywI6Nivcy+5 yYPo7QAPyHWlLzRMGOh2iCNJitu27Wjaw7ViKUylS7eYtAkUEKD4/mJ2IhULpNYI LzUCAwEAAaOCAjwwggI4MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgHmMB0G A1UdDgQWBBQcicOWzL3+MtUNjIExtpidjShkjTCB3QYDVR0jBIHVMIHSgBQcicOW zL3+MtUNjIExtpidjShkjaGBtqSBszCBsDELMAkGA1UEBhMCSUwxDzANBgNVBAgT BklzcmFlbDEOMAwGA1UEBxMFRWlsYXQxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4x GjAYBgNVBAsTEUNBIEF1dGhvcml0eSBEZXAuMSkwJwYDVQQDEyBGcmVlIFNTTCBD ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJARYSYWRtaW5Ac3Rh cnRjb20ub3JnggEAMB0GA1UdEQQWMBSBEmFkbWluQHN0YXJ0Y29tLm9yZzAdBgNV HRIEFjAUgRJhZG1pbkBzdGFydGNvbS5vcmcwEQYJYIZIAYb4QgEBBAQDAgAHMC8G CWCGSAGG+EIBDQQiFiBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAy BglghkgBhvhCAQQEJRYjaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL2NhLWNybC5j cmwwKAYJYIZIAYb4QgECBBsWGWh0dHA6Ly9jZXJ0LnN0YXJ0Y29tLm9yZy8wOQYJ YIZIAYb4QgEIBCwWKmh0dHA6Ly9jZXJ0LnN0YXJ0Y29tLm9yZy9pbmRleC5waHA/ YXBwPTExMTANBgkqhkiG9w0BAQQFAAOBgQBscSXhnjSRIe/bbL0BCFaPiNhBOlP1 ct8nV0t2hPdopP7rPwl+KLhX6h/BquL/lp9JmeaylXOWxkjHXo0Hclb4g4+fd68p 00UOpO6wNnQt8M2YI3s3S9r+UZjEHjQ8iP2ZO1CnwYszx8JSFhKVU2Ui77qLzmLb cCOxgN8aIDjnfg== -----END CERTIFICATE----- psi-plus-snapshots-1.4.1456/certs/startcom_ca_new.crt000066400000000000000000000053101370065651000225540ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM2WhcNMzYwOTE3MTk0NjM2WjB9 MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w +2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+ Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3 Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B 26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID AQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9j ZXJ0LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3Js LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFM BgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUHAgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0 Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRwOi8vY2VydC5zdGFy dGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYgU3Rh cnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlh YmlsaXR5LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2Yg dGhlIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFp bGFibGUgYXQgaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL3BvbGljeS5wZGYwEQYJ YIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNT TCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOCAgEAFmyZ 9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8 jhvh3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUW FjgKXlf2Ysd6AgXmvB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJz ewT4F+irsfMuXGRuczE6Eri8sxHkfY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1 ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3fsNrarnDy0RLrHiQi+fHLB5L EUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZEoalHmdkrQYu L6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuC O3NJo2pXh5Tl1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6V um0ABj6y6koQOdjQK/W/7HW/lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkySh NOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14= -----END CERTIFICATE----- psi-plus-snapshots-1.4.1456/client_icons.txt000066400000000000000000000040271370065651000207700ustar00rootroot00000000000000adium adium aqq aqq asterisk asterisk atalk atalk,android#atalk beem beem bitlbee bitlbee bombus bombus bot aspro,blacksmith,capsula,dictbot,fatal,fin.jabber.ru,freq,gluxi,historian,imformer,j-cool,j-tmb,jabbrik,jabga,jabrvista,jame,jabrss,justa,lytgeygen,magnet2.py,neutrina,osiris,pako#bot,qabber,sleekbot,snapi,sofserver,storm,sulci,talisman,ultimate,utah,witcher,yamaneko candy candy centerim centerim chatsecure chatsecure,gibberbot coccinella coccinella conv6ations conv6ations conversations conversations converse converse,conversejs dino dino ekg2 ekg2 emclient emclient eyecu eyecu freeswitch freeswitch freize freize gajim gajim gloox-api gloox gtalk android.com#gtalk,google.com#client,talk.google.com,talkgadget.google.com habahaba habahaba ichat ichat isida-bot isida jabber.el jabber.el jabbim jabbim jabbin jabbin jajc jajc jappix jappix jimm jimm jitsi jitsi,sip-communicator,sip#communicator jtalk jtalk jsxc jsxc juick juick kadu kadu kaidan kaidan kopete kopete leechcraft-azoth leechcraft libpurple libpurple,purple,finch loudmouth irssi-xmpp mandarin tomclaw.com#mandarin_im mcabber mcabber meegim meegim miranda miranda miranda-ng miranda#ng monal monal movim movim,moxl.movim mozilla mozilla,thunderbird,instantbird omnipresence omnipresence palringo palringo pandion pandion pidgin pidgin,gaim pix-art pix-art poezio poezio,poez.io profanity profanity,www#profanity psi psi,psi-im psiplus psi+,psi-plus,psi-dev pyicq-t pyicq,icq#transport qip qip,2010.qip.ru qutim qutim qxmpp-api qxmpp riddim riddim rnq rnq sat sat,salut-a-toi sawim sawim sleekxmpp-api sleekxmpp slixmpp-api slixmpp smack-api smack,igniterealtime.org#smack spark spark,igniterealtime.org spectrum spectrum,binarytransport stanza-api stanza.io,stanzajs strophe-api strophe swift swift talkonaut talkonaut tkabber tkabber telepathy telepathy,empathy tigase tigase trillian trillian uwpx uwpx vacuum vacuum,github#vacuum verse-api verse vk4xmpp vk4xmpp vkontakte vkontakte,vk.com,pyvk wime wime wodxmpp weonlydo wtw wtw yaxim yaxim yate yate xabber xabber zeus-bot botx.ir zom zom psi-plus-snapshots-1.4.1456/cmake/000077500000000000000000000000001370065651000166335ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/cmake/modules/000077500000000000000000000000001370065651000203035ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/cmake/modules/COPYING-CMAKE-SCRIPTS000066400000000000000000000024571370065651000233110ustar00rootroot00000000000000Redistribution 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 copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. 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 AUTHOR ``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 AUTHOR 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. psi-plus-snapshots-1.4.1456/cmake/modules/FindEnchant.cmake000066400000000000000000000065071370065651000234760ustar00rootroot00000000000000#============================================================================= # Copyright 2016-2020 Psi+ Project, Vitaly Tonkacheyev # 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 copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. 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 AUTHOR ``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 AUTHOR 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. #============================================================================= if (Enchant_INCLUDE_DIR AND Enchant_LIBRARY) # in cache already set(Enchant_FIND_QUIETLY TRUE) endif() if ( UNIX AND NOT( APPLE OR CYGWIN ) ) find_package( PkgConfig QUIET ) if(PkgConfig_FOUND) #Searching for enchant-1.x pkg_check_modules( PC_Enchant QUIET enchant ) if(NOT PC_Enchant_FOUND) #If enchant-1.x not found searching for enchant-2.x pkg_check_modules( PC_Enchant QUIET enchant-2 ) endif() #set package variables if(PC_Enchant_FOUND) set(Enchant_DEFINITIONS ${PC_Enchant_CFLAGS} ${PC_Enchant_CFLAGS_OTHER} ) if(PC_Enchant_VERSION) set(Enchant_VERSION ${PC_Enchant_VERSION}) endif() endif() endif() endif() set ( LIBINCS enchant.h ) find_path( Enchant_INCLUDE_DIR ${LIBINCS} HINTS ${PC_Enchant_INCLUDE_DIRS} ${Enchant_ROOT}/include PATH_SUFFIXES enchant enchant-2 ) find_library( Enchant_LIBRARY NAMES enchant enchant-2 HINTS ${PC_Enchant_LIBRARY_DIRS} ${Enchant_ROOT}/lib ${Enchant_ROOT}/bin ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( Enchant DEFAULT_MSG Enchant_LIBRARY Enchant_INCLUDE_DIR ) if ( Enchant_FOUND ) set ( Enchant_LIBRARIES ${Enchant_LIBRARY} ) set ( Enchant_INCLUDE_DIRS ${Enchant_INCLUDE_DIR} ) endif() if( Enchant_VERSION ) mark_as_advanced( Enchant_INCLUDE_DIR Enchant_LIBRARY Enchant_VERSION ) else () message(WARNING "No enchant version found. For use enchant-2 library you should set HAVE_ENCHANT2 definition manually") mark_as_advanced( Enchant_INCLUDE_DIR Enchant_LIBRARY ) endif() psi-plus-snapshots-1.4.1456/cmake/modules/FindHttpParser.cmake000066400000000000000000000064221370065651000242060ustar00rootroot00000000000000#============================================================================= # Copyright 2020 Psi+ Project, Vitaly Tonkacheyev # 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 copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. 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 AUTHOR ``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 AUTHOR 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. #============================================================================= if (HttpParser_INCLUDE_DIR AND HttpParser_LIBRARY) # in cache already set(HttpParser_FIND_QUIETLY TRUE) endif() find_path( HttpParser_INCLUDE_DIR http_parser.h HINTS ${HTTP_PARSER_ROOT}/include ) find_library( HttpParser_LIBRARY NAMES http_parser HINTS ${HTTP_PARSER_ROOT}/lib ${HTTP_PARSER_ROOT}/bin ) #Obtain library version if(HttpParser_INCLUDE_DIR) set(INC_FILE "${HttpParser_INCLUDE_DIR}/http_parser.h") file(STRINGS "${INC_FILE}" VER_LINES) string(REGEX MATCH "HTTP_PARSER_VERSION_MAJOR ([0-9]+)" VER_LINE_MAJOR ${VER_LINES}) if(CMAKE_MATCH_1) set(HttpParser_VERSION_MAJOR "${CMAKE_MATCH_1}") endif() string(REGEX MATCH "HTTP_PARSER_VERSION_MINOR ([0-9]+)" VER_LINE_MINOR ${VER_LINES}) if(CMAKE_MATCH_1) set(HttpParser_VERSION_MINOR "${CMAKE_MATCH_1}") endif() string(REGEX MATCH "HTTP_PARSER_VERSION_PATCH ([0-9]+)" VER_LINE_PATCH ${VER_LINES}) if(CMAKE_MATCH_1) set(HttpParser_VERSION_PATCH "${CMAKE_MATCH_1}") endif() if(HttpParser_VERSION_MAJOR AND (HttpParser_VERSION_MINOR AND HttpParser_VERSION_PATCH)) set(HttpParser_VERSION "${HttpParser_VERSION_MAJOR}.${HttpParser_VERSION_MINOR}.${HttpParser_VERSION_PATCH}") endif() endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args( HttpParser DEFAULT_MSG HttpParser_LIBRARY HttpParser_INCLUDE_DIR HttpParser_VERSION ) if (HttpParser_FOUND) set ( HttpParser_LIBRARIES ${HttpParser_LIBRARY} ) set ( HttpParser_INCLUDE_DIRS ${HttpParser_INCLUDE_DIR} ) endif() mark_as_advanced( HttpParser_INCLUDE_DIR HttpParser_LIBRARY HttpParser_VERSION ) psi-plus-snapshots-1.4.1456/cmake/modules/FindHunspell.cmake000066400000000000000000000057041370065651000237060ustar00rootroot00000000000000#============================================================================= # Copyright 2016-2017 Psi+ Project, Vitaly Tonkacheyev # 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 copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. 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 AUTHOR ``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 AUTHOR 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. #============================================================================= if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND WIN32) set(D "d") endif() if (HUNSPELL_INCLUDE_DIR AND HUNSPELL_LIBRARY) # in cache already set(HUNSPELL_FIND_QUIETLY TRUE) endif() if ( UNIX AND NOT( APPLE OR CYGWIN ) ) find_package( PkgConfig QUIET ) pkg_check_modules( PC_HUNSPELL QUIET hunspell ) set ( HUNSPELL_DEFINITIONS ${PC_HUNSPELL_CFLAGS} ${PC_HUNSPELL_CFLAGS_OTHER} ) endif() set ( LIBINCS hunspell.hxx ) find_path( HUNSPELL_INCLUDE_DIR ${LIBINCS} HINTS ${HUNSPELL_ROOT}/include ${PC_HUNSPELL_INCLUDEDIR} ${PC_HUNSPELL_INCLUDE_DIRS} PATH_SUFFIXES "" if ( NOT ${WIN32} ) hunspell endif() ) set(HUNSPELL_NAMES hunspell${d} libhunspell${d} hunspell-1.3 hunspell-1.4 hunspell-1.5 hunspell-1.6 hunspell-1.7 ) find_library( HUNSPELL_LIBRARY NAMES ${HUNSPELL_NAMES} HINTS ${PC_HUNSPELL_LIBDIR} ${PC_HUNSPELL_LIBRARY_DIRS} ${HUNSPELL_ROOT}/lib ${HUNSPELL_ROOT}/bin ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( Hunspell DEFAULT_MSG HUNSPELL_LIBRARY HUNSPELL_INCLUDE_DIR ) if ( HUNSPELL_FOUND ) set ( HUNSPELL_LIBRARIES ${HUNSPELL_LIBRARY} ) set ( HUNSPELL_INCLUDE_DIRS ${HUNSPELL_INCLUDE_DIR} ) endif() mark_as_advanced( HUNSPELL_INCLUDE_DIR HUNSPELL_LIBRARY ) psi-plus-snapshots-1.4.1456/cmake/modules/FindIDN.cmake000066400000000000000000000052051370065651000225220ustar00rootroot00000000000000#============================================================================= # Copyright 2016-2017 Psi+ Project, Vitaly Tonkacheyev # 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 copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. 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 AUTHOR ``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 AUTHOR 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. #============================================================================= if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND WIN32) set(D "d") endif() if( IDN_INCLUDE_DIR AND IDN_LIBRARY ) # in cache already set(IDN_FIND_QUIETLY TRUE) endif() if( UNIX AND NOT( APPLE OR CYGWIN ) ) find_package( PkgConfig QUIET ) pkg_check_modules( PC_IDN QUIET libidn ) if( PC_IDN_FOUND ) set( IDN_DEFINITIONS ${PC_IDN_CFLAGS} ${PC_IDN_CFLAGS_OTHER} ) endif() endif() find_path( IDN_INCLUDE_DIR idna.h HINTS ${IDN_ROOT}/include ${PC_IDN_INCLUDEDIR} ${PC_IDN_INCLUDE_DIRS} ) set(IDN_NAMES idn${D} libidn${D} idn-11${D} ) find_library( IDN_LIBRARY NAMES ${IDN_NAMES} HINTS ${PC_IDN_LIBDIR} ${PC_IDN_LIBRARY_DIRS} ${IDN_ROOT}/lib ${IDN_ROOT}/bin ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( IDN DEFAULT_MSG IDN_LIBRARY IDN_INCLUDE_DIR ) if( IDN_FOUND ) set( IDN_LIBRARIES ${IDN_LIBRARY} ) set( IDN_INCLUDE_DIRS ${IDN_INCLUDE_DIR} ) endif() mark_as_advanced( IDN_INCLUDE_DIR IDN_LIBRARY ) psi-plus-snapshots-1.4.1456/cmake/modules/FindLibGcrypt.cmake000066400000000000000000000064601370065651000240130ustar00rootroot00000000000000#============================================================================= # Copyright 2016-2017 Psi+ Project, Vitaly Tonkacheyev # 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 copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. 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 AUTHOR ``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 AUTHOR 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. #============================================================================= if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND WIN32) set(D "d") endif() if( LIBGCRYPT_INCLUDE_DIR AND LIBGCRYPT_LIBRARY ) # in cache already set(libgcrypt_FIND_QUIETLY TRUE) endif() if( UNIX AND NOT( APPLE OR CYGWIN ) ) find_package( PkgConfig QUIET ) pkg_check_modules( PC_LIBGCRYPT QUIET libgcrypt ) if( PC_LIBGCRYPT_FOUND ) set( LIBGCRYPT_DEFINITIONS ${PC_LIBGCRYPT_CFLAGS} ) endif() endif() if( WIN32 ) FIND_PROGRAM(LIBGCRYPTCONFIG_EXECUTABLE NAMES libgcrypt-config PATHS ${LIBGCRYPT_ROOT}/bin) if(NOT "${LIBGCRYPTCONFIG_EXECUTABLE}" STREQUAL "LIBGCRYPTCONFIG_EXECUTABLE-NOTFOUND" ) execute_process(COMMAND sh "${LIBGCRYPTCONFIG_EXECUTABLE}" --prefix OUTPUT_VARIABLE PREFIX) set(LIBGCRYPT_LIB_HINT "${PREFIX}/lib") set(LIBGCRYPT_INCLUDE_HINT "${PREFIX}/include") endif() endif() find_path( LIBGCRYPT_INCLUDE_DIR gcrypt.h HINTS ${LIBGCRYPT_ROOT}/include ${PC_LIBGCRYPT_INCLUDEDIR} ${PC_LIBGCRYPT_INCLUDE_DIRS} ${LIBGCRYPT_INCLUDE_HINT} ) set(LIBGCRYPT_NAMES gcrypt${D} libgcrypt${D} gcrypt-11 libgcrypt-11 gcrypt-20 libgcrypt-20 ) find_library( LIBGCRYPT_LIBRARY NAMES ${LIBGCRYPT_NAMES} HINTS ${PC_LIBGCRYPT_LIBDIR} ${PC_LIBGCRYPT_LIBRARY_DIRS} ${LIBGCRYPT_LIB_HINT} ${LIBGCRYPT_ROOT}/lib ${LIBGCRYPT_ROOT}/bin ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( LibGcrypt DEFAULT_MSG LIBGCRYPT_LIBRARY LIBGCRYPT_INCLUDE_DIR ) if( LIBGCRYPT_FOUND ) set( LIBGCRYPT_LIBRARIES ${LIBGCRYPT_LIBRARY} ) set( LIBGCRYPT_INCLUDE_DIRS ${LIBGCRYPT_INCLUDE_DIR} ) endif() mark_as_advanced( LIBGCRYPT_INCLUDE_DIR LIBGCRYPT_LIBRARY ) psi-plus-snapshots-1.4.1456/cmake/modules/FindLibGpgError.cmake000066400000000000000000000065301370065651000242700ustar00rootroot00000000000000#============================================================================= # Copyright 2016-2017 Psi+ Project, Vitaly Tonkacheyev # 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 copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. 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 AUTHOR ``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 AUTHOR 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. #============================================================================= if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND WIN32) set(D "d") endif() if( LIBGPGERROR_INCLUDE_DIR AND LIBGPGERROR_LIBRARY ) # in cache already set(LIBGPGERROR_FIND_QUIETLY TRUE) endif() if( UNIX AND NOT( APPLE OR CYGWIN ) ) find_package( PkgConfig QUIET ) pkg_check_modules( PC_LIBGPGERROR QUIET libgpg-error ) if( PC_LIBGPGERROR_FOUND ) set( LIBGPGERROR_DEFINITIONS ${PC_LIBGPGERROR_CFLAGS} ) endif() endif() if( WIN32 ) FIND_PROGRAM(LIBGPGERRORCONFIG_EXECUTABLE NAMES libgpg-error-config PATHS ${LIBGPGERROR_ROOT}/bin) if(LIBGPGERRORCONFIG_EXECUTABLE) execute_process(COMMAND sh "${LIBGPGERRORCONFIG_EXECUTABLE}" --prefix OUTPUT_VARIABLE PREFIX) set(LIBGPGERROR_LIB_HINT "${PREFIX}/lib") set(LIBGPGERROR_INCLUDE_HINT "${PREFIX}/include") endif() endif() find_path( LIBGPGERROR_INCLUDE_DIR gpg-error.h HINTS ${LIBGPGERROR_ROOT}/include ${PC_LIBGPGERROR_INCLUDEDIR} ${PC_LIBGPGERROR_INCLUDE_DIRS} ${LIBGPGERROR_INCLUDE_HINT} ) set(LIBGPGERROR_NAMES gpg-error${D} libgpg-error${D} gpg-error-0 libgpg-error-0 gpg-error6-0 libgpg-error6-0 ) find_library( LIBGPGERROR_LIBRARY NAMES ${LIBGPGERROR_NAMES} HINTS ${PC_LIBGPGERROR_LIBDIR} ${PC_LIBGPGERROR_LIBRARY_DIRS} ${LIBGPGERROR_LIB_HINT} ${LIBGPGERROR_ROOT}/lib ${LIBGPGERROR_ROOT}/bin ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( LibGpgError DEFAULT_MSG LIBGPGERROR_LIBRARY LIBGPGERROR_INCLUDE_DIR ) if( LIBGPGERROR_FOUND ) set( LIBGPGERROR_LIBRARIES ${LIBGPGERROR_LIBRARY} ) set( LIBGPGERROR_INCLUDE_DIRS ${LIBGPGERROR_INCLUDE_DIR} ) endif() mark_as_advanced( LIBGPGERROR_INCLUDE_DIR LIBGPGERROR_LIBRARY ) psi-plus-snapshots-1.4.1456/cmake/modules/FindLibOtr.cmake000066400000000000000000000054051370065651000233050ustar00rootroot00000000000000#============================================================================= # Copyright 2016-2017 Psi+ Project, Vitaly Tonkacheyev # 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 copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. 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 AUTHOR ``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 AUTHOR 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. #============================================================================= if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND WIN32) set(D "d") endif() if( LIBOTR_INCLUDE_DIR AND LIBOTR_LIBRARY ) # in cache already set(LIBOTR_FIND_QUIETLY TRUE) endif() if( UNIX AND NOT( APPLE OR CYGWIN ) ) find_package( PkgConfig QUIET ) pkg_check_modules( PC_LIBOTR QUIET libotr ) if( PC_LIBOTR_FOUND ) set( LIBOTR_DEFINITIONS ${PC_LIBOTR_CFLAGS} ${PC_LIBOTR_CFLAGS_OTHER} ) endif() endif() find_path( LIBOTR_INCLUDE_DIR libotr/privkey.h HINTS ${LIBOTR_ROOT}/include ${PC_LIBOTR_INCLUDEDIR} ${PC_LIBOTR_INCLUDE_DIRS} PATH_SUFFIXES "" libotr ) set(LIBOTR_NAMES otr${D} libotr${D} otr-5 ) find_library( LIBOTR_LIBRARY NAMES ${LIBOTR_NAMES} HINTS ${PC_LIBOTR_LIBDIR} ${PC_LIBOTR_LIBRARY_DIRS} ${LIBOTR_ROOT}/lib ${LIBOTR_ROOT}/bin ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( LibOtr DEFAULT_MSG LIBOTR_LIBRARY LIBOTR_INCLUDE_DIR ) if( LIBOTR_FOUND ) set( LIBOTR_LIBRARIES ${LIBOTR_LIBRARY} ) set( LIBOTR_INCLUDE_DIRS ${LIBOTR_INCLUDE_DIR} ) endif() mark_as_advanced( LIBOTR_INCLUDE_DIR LIBOTR_LIBRARY ) psi-plus-snapshots-1.4.1456/cmake/modules/FindLibTidy.cmake000066400000000000000000000061551370065651000234550ustar00rootroot00000000000000#============================================================================= # Copyright 2016-2017 Psi+ Project, Vitaly Tonkacheyev # 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 copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. 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 AUTHOR ``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 AUTHOR 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. #============================================================================= if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND WIN32) set(D "d") endif() if( LIBTIDY_INCLUDE_DIR AND LIBTIDY_LIBRARY ) # in cache already set(LIBTIDY_FIND_QUIETLY TRUE) endif() if( UNIX AND NOT( APPLE OR CYGWIN ) ) find_package( PkgConfig QUIET ) pkg_check_modules( PC_LIBTIDY QUIET libtidy ) if( PC_LIBTIDY_FOUND ) set( LIBTIDY_DEFINITIONS ${PC_LIBTIDY_CFLAGS} ${PC_LIBTIDY_CFLAGS_OTHER} ) endif() endif() find_path( LIBTIDY_INCLUDE_DIR tidy.h HINTS ${PC_LIBTIDY_INCLUDEDIR} ${PC_LIBTIDY_INCLUDE_DIRS} PATHS ${LIBTIDY_ROOT}/include PATH_SUFFIXES "" tidy ) if( EXISTS "${LIBTIDY_INCLUDE_DIR}/tidybuffio.h" OR (EXISTS "${LIBTIDY_INCLUDE_DIR}/tidy/tidybuffio.h") ) message("-- Tidy-html5 detected") else() message("-- Tidy-html legacy detected") set( LIBTIDY_DEFINITIONS "${LIBTIDY_DEFINITIONS} -DLEGACY_TIDY" ) endif() set(LIBTIDY_NAMES tidy${D} tidys${D} libtidy${D} libtidys${D} libtidy-0-99-0 tidy-0-99-0 ) find_library( LIBTIDY_LIBRARY NAMES ${LIBTIDY_NAMES} HINTS ${PC_LIBTIDY_LIBDIR} ${PC_LIBTIDY_LIBRARY_DIRS} ${LIBTIDY_ROOT}/lib ${LIBTIDY_ROOT}/bin ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( LibTidy DEFAULT_MSG LIBTIDY_LIBRARY LIBTIDY_INCLUDE_DIR ) if( LIBTIDY_FOUND ) set( LIBTIDY_LIBRARIES ${LIBTIDY_LIBRARY} ) set( LIBTIDY_INCLUDE_DIRS ${LIBTIDY_INCLUDE_DIR} ) endif() mark_as_advanced( LIBTIDY_INCLUDE_DIR LIBTIDY_LIBRARY ) psi-plus-snapshots-1.4.1456/cmake/modules/FindMINIZIP.cmake000066400000000000000000000053111370065651000232250ustar00rootroot00000000000000#============================================================================= # Copyright 2016-2017 Psi+ Project, Vitaly Tonkacheyev # 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 copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. 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 AUTHOR ``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 AUTHOR 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. #============================================================================= if (MINIZIP_INCLUDE_DIR AND MINIZIP_LIBRARY) # in cache already set(MINIZIP_FIND_QUIETLY TRUE) endif () if ( UNIX AND NOT( APPLE OR CYGWIN ) ) find_package( PkgConfig QUIET ) pkg_check_modules( PC_MINIZIP QUIET minizip ) set ( MINIZIP_DEFINITIONS ${PC_MINIZIP_CFLAGS} ${PC_MINIZIP_CFLAGS_OTHER} ) endif() set ( LIBINCS unzip.h ) find_path( MINIZIP_INCLUDE_DIR ${LIBINCS} HINTS ${MINIZIP_ROOT}/include ${PC_MINIZIP_INCLUDEDIR} ${PC_MINIZIP_INCLUDE_DIRS} PATH_SUFFIXES "" if ( NOT ${WIN32} ) minizip endif() ) find_library( MINIZIP_LIBRARY NAMES minizip HINTS ${PC_MINIZIP_LIBDIR} ${PC_MINIZIP_LIBRARY_DIRS} ${MINIZIP_ROOT}/lib ${MINIZIP_ROOT}/bin ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( MINIZIP DEFAULT_MSG MINIZIP_LIBRARY MINIZIP_INCLUDE_DIR ) if ( MINIZIP_FOUND ) set ( MINIZIP_LIBRARIES ${MINIZIP_LIBRARY} ) set ( MINIZIP_INCLUDE_DIRS ${MINIZIP_INCLUDE_DIR} ) endif() mark_as_advanced( MINIZIP_INCLUDE_DIR MINIZIP_LIBRARY ) psi-plus-snapshots-1.4.1456/cmake/modules/FindPsiPluginsApi.cmake000066400000000000000000000076441370065651000246500ustar00rootroot00000000000000#============================================================================= # Copyright 2018 Psi+ Project # 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 copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. 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 AUTHOR ``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 AUTHOR 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. #============================================================================= #Prevent extra messages on searching if(PsiPluginsApi_INCLUDE_DIR AND PsiPluginsApi_DIR) # in cache already set(PsiPluginsApi_FIND_QUIETLY TRUE) endif() if(PLUGINS_ROOT_DIR) get_filename_component(ABS_PLUGINS_ROOT_DIR "${PLUGINS_ROOT_DIR}" ABSOLUTE) endif() get_filename_component(ABS_CURRENT_DIR "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE) get_filename_component(ABS_PARENT_DIR "${ABS_CURRENT_DIR}/.." ABSOLUTE) if(EXISTS "${ABS_PLUGINS_ROOT_DIR}/cmake/modules/variables.cmake") set(PLUGINS_DIRECTORY "${ABS_PLUGINS_ROOT_DIR}") elseif(EXISTS "${ABS_CURRENT_DIR}/plugins/cmake/modules/variables.cmake") set(PLUGINS_DIRECTORY "${ABS_CURRENT_DIR}/plugins") elseif(EXISTS "${ABS_PARENT_DIR}/plugins/cmake/modules/variables.cmake") set(PLUGINS_DIRECTORY "${ABS_PARENT_DIR}/plugins") endif() if(CMAKE_CROSSCOMPILING OR CMAKE_CROSS_COMPILING OR (EXISTS "${ABS_PLUGINS_ROOT_DIR}/include")) set(SEARCH_FLAG NO_CMAKE_SYSTEM_PATH CMAKE_FIND_ROOT_PATH_BOTH) endif() if(PLUGINS_DIRECTORY) if(EXISTS "${PLUGINS_DIRECTORY}/cmake/modules/variables.cmake") set(PsiPluginsApi_DIR "${PLUGINS_DIRECTORY}/cmake/modules") endif() if(EXISTS "${PLUGINS_DIRECTORY}/include/applicationinfoaccessor.h") set(PsiPluginsApi_INCLUDE_DIR "${PLUGINS_DIRECTORY}/include") endif() endif() #Double check in case while api was found in local repo if(PsiPluginsApi_INCLUDE_DIR AND PsiPluginsApi_DIR) # in cache already set(PsiPluginsApi_FIND_QUIETLY TRUE) endif() if(NOT PsiPluginsApi_DIR) find_path( PsiPluginsApi_DIR NAMES "variables.cmake" PATHS ${ABS_PLUGINS_ROOT_DIR}/cmake/modules ${ABS_CURRENT_DIR} ${ABS_PARENT_DIR}/psi PATH_SUFFIXES plugins/cmake/modules share/psi/plugins share/psi-plus/plugins ${SEARCH_FLAG} ) endif() if(NOT PsiPluginsApi_INCLUDE_DIR) find_path( PsiPluginsApi_INCLUDE_DIR NAMES "applicationinfoaccessor.h" PATHS ${ABS_PLUGINS_ROOT_DIR}/include ${ABS_CURRENT_DIR} ${ABS_PARENT_DIR}/psi PATH_SUFFIXES plugins/include include/psi/plugins include/psi-plus/plugins ${SEARCH_FLAG} ) endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args( PsiPluginsApi DEFAULT_MSG PsiPluginsApi_DIR PsiPluginsApi_INCLUDE_DIR ) mark_as_advanced( PsiPluginsApi_DIR PsiPluginsApi_INCLUDE_DIR ) psi-plus-snapshots-1.4.1456/cmake/modules/FindQJDns.cmake000066400000000000000000000054701370065651000230730ustar00rootroot00000000000000#============================================================================= # Copyright 2016-2017 Psi+ Project # 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 copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. 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 AUTHOR ``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 AUTHOR 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. #============================================================================= if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND WIN32) set(D "d") endif() if (QJDns_INCLUDE_DIR AND QJDns_LIBRARY) # in cache already set(QJDns_FIND_QUIETLY TRUE) endif() if ( UNIX AND NOT( APPLE OR CYGWIN ) ) find_package( PkgConfig QUIET ) pkg_check_modules( PC_QJDns QUIET jdns ) set ( QJDns_DEFINITIONS ${PC_QJDns_CFLAGS} ${PC_QJDns_CFLAGS_OTHER} ) endif() set ( LIBINCS qjdns.h ) if(NOT QJDns_SUFFIX) set( QJDns_SUFFIX "") endif() find_path( QJDns_INCLUDE_DIR ${LIBINCS} HINTS ${QJDNS_DIR}/include ${PC_QJDns_INCLUDEDIR} ${PC_QJDns_INCLUDE_DIRS} PATH_SUFFIXES "" if( NOT WIN32 ) jdns endif() ) set(QJDns_NAMES qjdns${D} qjdns${QJDns_SUFFIX}${D} ) find_library( QJDns_LIBRARY NAMES ${QJDns_NAMES} HINTS ${PC_QJDns_LIBDIR} ${PC_QJDns_LIBRARY_DIRS} ${QJDNS_DIR}/lib ${QJDNS_DIR}/bin ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( QJDns DEFAULT_MSG QJDns_LIBRARY QJDns_INCLUDE_DIR ) if ( QJDns_FOUND ) set ( QJDns_LIBRARIES ${QJDns_LIBRARY} ) set ( QJDns_INCLUDE_DIRS ${QJDns_INCLUDE_DIR} ) endif() mark_as_advanced( QJDns_INCLUDE_DIR QJDns_LIBRARY ) psi-plus-snapshots-1.4.1456/cmake/modules/FindQJSON.cmake000066400000000000000000000052171370065651000230050ustar00rootroot00000000000000#============================================================================= # Copyright 2016-2017 Psi+ Project, Vitaly Tonkacheyev # 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 copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. 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 AUTHOR ``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 AUTHOR 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. #============================================================================= if (QJSON_INCLUDE_DIR AND QJSON_LIBRARY) # in cache already set(QJSON_FIND_QUIETLY TRUE) endif() if ( UNIX AND NOT( APPLE OR CYGWIN ) ) find_package( PkgConfig QUIET ) pkg_check_modules( PC_QJSON QUIET QJson ) set ( QJSON_DEFINITIONS ${PC_QJSON_CFLAGS} ${PC_QJSON_CFLAGS_OTHER} ) endif() set ( LIBINCS parser.h ) find_path( QJSON_INCLUDE_DIR ${LIBINCS} HINTS ${QJSON_ROOT}/include ${PC_QJSON_INCLUDEDIR} ${PC_QJSON_INCLUDE_DIRS} PATH_SUFFIXES "" if ( NOT ${WIN32} ) qjson endif() ) find_library( QJSON_LIBRARY NAMES qjson HINTS ${PC_QJSON_LIBDIR} ${PC_QJSON_LIBRARY_DIRS} ${QJSON_ROOT}/lib ${QJSON_ROOT}/bin ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( QJson DEFAULT_MSG QJSON_LIBRARY QJSON_INCLUDE_DIR ) if ( QJSON_FOUND ) set ( QJSON_LIBRARIES ${QJSON_LIBRARY} ) set ( QJSON_INCLUDE_DIRS ${QJSON_INCLUDE_DIR} ) endif() mark_as_advanced( QJSON_INCLUDE_DIR QJSON_LIBRARY ) psi-plus-snapshots-1.4.1456/cmake/modules/FindQca.cmake000066400000000000000000000047261370065651000226230ustar00rootroot00000000000000#============================================================================= # Copyright 2016-2017 Psi+ Project, Vitaly Tonkacheyev # 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 copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. 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 AUTHOR ``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 AUTHOR 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. #============================================================================= if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND WIN32) set(D "d") endif() if (Qca_INCLUDE_DIR AND Qca_LIBRARY) # in cache already set(Qca_FIND_QUIETLY TRUE) endif() set(EXTRA_PATH_SUFFIXES qt5/Qca-qt5/QtCrypto Qca-qt5/QtCrypto qt5/QtCrypto qt/Qca-qt5/QtCrypto lib/qca-qt5.framework/Versions/2/Headers ) find_path( Qca_INCLUDE_DIR qca.h HINTS ${QCA_DIR}/include PATH_SUFFIXES QtCrypto ${EXTRA_PATH_SUFFIXES} ) find_library( Qca_LIBRARY NAMES qca-qt5${D} HINTS ${QCA_DIR}/lib ${QCA_DIR}/bin ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( Qca DEFAULT_MSG Qca_LIBRARY Qca_INCLUDE_DIR ) if (Qca_FOUND) set ( Qca_LIBRARIES ${Qca_LIBRARY} ) set ( Qca_INCLUDE_DIRS ${Qca_INCLUDE_DIR} ) endif() mark_as_advanced( Qca_INCLUDE_DIR Qca_LIBRARY ) psi-plus-snapshots-1.4.1456/cmake/modules/FindSparkle.cmake000066400000000000000000000015221370065651000235070ustar00rootroot00000000000000# Find Sparkle.framework # # Once done this will define # SPARKLE_FOUND - system has Sparkle # SPARKLE_INCLUDE_DIR - the Sparkle include directory # SPARKLE_LIBRARY - The library needed to use Sparkle # Copyright (c) 2009, Vittorio Giovara # # Distributed under the OSI-approved BSD License (the "License"); # see accompanying file Copyright.txt for details. # # This software is distributed WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the License for more information. include(FindPackageHandleStandardArgs) find_path(SPARKLE_INCLUDE_DIR Sparkle.h) find_library(SPARKLE_LIBRARY NAMES Sparkle) find_package_handle_standard_args(Sparkle DEFAULT_MSG SPARKLE_INCLUDE_DIR SPARKLE_LIBRARY) mark_as_advanced(SPARKLE_INCLUDE_DIR SPARKLE_LIBRARY) psi-plus-snapshots-1.4.1456/cmake/modules/FindXCB.cmake000066400000000000000000000050521370065651000225240ustar00rootroot00000000000000#============================================================================= # Copyright 2016-2017 Psi+ Project, Vitaly Tonkacheyev # 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 copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. 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 AUTHOR ``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 AUTHOR 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. #============================================================================= if (XCB_INCLUDE_DIR AND XCB_LIBRARY) # in cache already set(XCB_FIND_QUIETLY TRUE) endif () if ( UNIX AND NOT( APPLE OR CYGWIN ) ) find_package( PkgConfig QUIET ) pkg_check_modules( PC_XCB QUIET xcb ) set ( XCB_DEFINITIONS ${PC_XCB_CFLAGS} ${PC_XCB_CFLAGS_OTHER} ) endif() set ( LIBINCS xcb.h ) find_path( XCB_INCLUDE_DIR ${LIBINCS} HINTS ${XCB_ROOT}/include ${PC_XCB_INCLUDEDIR} ${PC_XCB_INCLUDE_DIRS} PATH_SUFFIXES "xcb" ) find_library( XCB_LIBRARY NAMES xcb HINTS ${PC_XCB_LIBDIR} ${PC_XCB_LIBRARY_DIRS} ${XCB_ROOT}/lib ${XCB_ROOT}/bin ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( XCB DEFAULT_MSG XCB_LIBRARY XCB_INCLUDE_DIR ) if ( XCB_FOUND ) set ( XCB_LIBRARIES ${XCB_LIBRARY} ) set ( XCB_INCLUDE_DIRS ${XCB_INCLUDE_DIR} ) endif() mark_as_advanced( XCB_INCLUDE_DIR XCB_LIBRARY ) psi-plus-snapshots-1.4.1456/cmake/modules/FindZLIB.cmake000066400000000000000000000053351370065651000226540ustar00rootroot00000000000000#============================================================================= # Copyright 2016-2017 Psi+ Project, Vitaly Tonkacheyev # 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 copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. 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 AUTHOR ``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 AUTHOR 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. #============================================================================= if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND WIN32) set(D "d") endif() if( ZLIB_INCLUDE_DIR AND ZLIB_LIBRARY ) # in cache already set(ZLIB_FIND_QUIETLY TRUE) endif() if( UNIX AND NOT( APPLE OR CYGWIN ) ) find_package( PkgConfig QUIET ) pkg_check_modules( PC_ZLIB QUIET zlib ) if( PC_ZLIB_FOUND ) set( ZLIB_DEFINITIONS ${PC_ZLIB_CFLAGS} ${PC_ZLIB_CFLAGS_OTHER} ) endif() endif() set( ZLIB_ROOT "" CACHE STRING "Path to libidn library" ) find_path( ZLIB_INCLUDE_DIR zlib.h HINTS ${ZLIB_ROOT}/include ${PC_ZLIB_INCLUDEDIR} ${PC_ZLIB_INCLUDE_DIRS} ) set(ZLIB_NAMES z${D} lz${D} zlib${D} zlib1 ) find_library( ZLIB_LIBRARY NAMES ${ZLIB_NAMES} HINTS ${PC_ZLIB_LIBDIR} ${PC_ZLIB_LIBRARY_DIRS} ${ZLIB_ROOT}/lib ${ZLIB_ROOT}/bin ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( ZLIB DEFAULT_MSG ZLIB_LIBRARY ZLIB_INCLUDE_DIR ) if( ZLIB_FOUND ) set( ZLIB_LIBRARIES ${ZLIB_LIBRARY} ) set( ZLIB_INCLUDE_DIRS ${ZLIB_INCLUDE_DIR} ) endif() mark_as_advanced( ZLIB_INCLUDE_DIR ZLIB_LIBRARY ) psi-plus-snapshots-1.4.1456/cmake/modules/fix-codestyle.cmake000066400000000000000000000013711370065651000240660ustar00rootroot00000000000000cmake_minimum_required( VERSION 3.1.0 ) #Find clang-format binary find_program(CLF_BIN clang-format DOC "Path to clang-format binary") if(CLF_BIN) #Obtain list of source files file(GLOB_RECURSE SRC_LIST *.c *.cc *.cpp *.hpp *.h *.mm ../qcm/*.qcm ) foreach(src_file ${SRC_LIST}) #Exclude libpsi if("${src_file}" MATCHES ".*/libpsi/.*") list(REMOVE_ITEM SRC_LIST ${src_file}) endif() endforeach() add_custom_target(fix-codestyle COMMAND ${CLF_BIN} --verbose -style=file -i ${SRC_LIST} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMENT "Fix codestyle with clang-format" VERBATIM ) endif() psi-plus-snapshots-1.4.1456/cmake/modules/generate_desktopfile.cmake000066400000000000000000000034711370065651000254750ustar00rootroot00000000000000cmake_minimum_required( VERSION 3.1.0 ) set(DESKTOP_FILE "${PROJECT_SOURCE_DIR}/psi.desktop") set(DESKTOP_FILE_SEC_PART "${PROJECT_SOURCE_DIR}/psi-extra-action1.desktop") file(READ ${DESKTOP_FILE} DESK_FILE_CONTENTS) file(READ ${DESKTOP_FILE_SEC_PART} PART2_CONTENTS) set(OUT_DESK_FILE "${CMAKE_BINARY_DIR}/${VERBOSED_NAME}.desktop") file(WRITE ${OUT_DESK_FILE} "${DESK_FILE_CONTENTS} ${PART2_CONTENTS}" ) unset(DESK_FILE_CONTENTS) unset(PART2_CONTENTS) set(EXEC_REGEXP "Exec=psi ") set(NAME_REGEXP "Name=Psi") set(ICON_REGEXP "Icon=psi") set(WMCLASS_REGEXP "StartupWMClass=Psi") if(PSI_PLUS) set(WMCLASS_NAME "Psi-plus") else() set(WMCLASS_NAME "Psi") endif() set(TMP_DESK_FILE "${CMAKE_CURRENT_BINARY_DIR}/${VERBOSED_NAME}.desktop.in") file(WRITE ${TMP_DESK_FILE} "") file(READ ${OUT_DESK_FILE} DESK_FILE_CONTENTS) #hack for desktop file generaion string(REGEX REPLACE "${EXEC_REGEXP}" "Exec=${VERBOSED_NAME} " FIX1 "${DESK_FILE_CONTENTS}") string(REGEX REPLACE "${ICON_REGEXP}" "Icon=${VERBOSED_NAME}" FIX2 "${FIX1}") if(IS_WEBENGINE AND VERBOSE_PROGRAM_NAME) string(REGEX REPLACE "${NAME_REGEXP}" "Name=${CLIENT_NAME} Webengine" FIX3 "${FIX2}") elseif(IS_WEBKIT AND VERBOSE_PROGRAM_NAME) string(REGEX REPLACE "${NAME_REGEXP}" "Name=${CLIENT_NAME} Webkit" FIX3 "${FIX2}") else() string(REGEX REPLACE "${NAME_REGEXP}" "Name=${CLIENT_NAME}" FIX3 "${FIX2}") endif() string(REGEX REPLACE "${WMCLASS_REGEXP}" "StartupWMClass=${WMCLASS_NAME}" FIX4 "${FIX3}") if(FIX4) file(APPEND ${TMP_DESK_FILE} "${FIX4}") elseif(FIX3) file(APPEND ${TMP_DESK_FILE} "${FIX3}") elseif(FIX2) file(APPEND ${TMP_DESK_FILE} "${FIX2}") else() file(APPEND ${TMP_DESK_FILE} "${FIX1}") endif() configure_file(${TMP_DESK_FILE} ${OUT_DESK_FILE} COPYONLY) unset(DESK_FILE_CONTENTS) message(STATUS "${OUT_DESK_FILE} file generated") psi-plus-snapshots-1.4.1456/cmake/modules/get-version.cmake000066400000000000000000000127661370065651000235630ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.1.0) set(VER_FILE ${PROJECT_SOURCE_DIR}/version) unset(APP_VERSION) unset(PSI_REVISION) unset(PSI_PLUS_REVISION) set(DEFAULT_VER "1.4") find_program(GIT_BIN git DOC "Path to git utility") if(GIT_BIN) message(STATUS "Git utility found ${GIT_BIN}") else() message("Git utility not found") endif() if(EXISTS "${PROJECT_SOURCE_DIR}/generate-single-repo.sh") set(IS_SNAPSHOT ON) endif() function(read_version_file VF_RESULT) if(EXISTS "${VER_FILE}") message(STATUS "Found Psi version file: ${VER_FILE}") file(STRINGS "${VER_FILE}" VER_LINES) if(VER_LINES) set(${VF_RESULT} ${VER_LINES} PARENT_SCOPE) else() message(FATAL_ERROR "Can't read ${VER_FILE} contents") endif() unset(VER_LINES) else() message(FATAL_ERROR "${VER_FILE} not found") endif() endfunction() function(run_git GIT_ARG1 GIT_ARG2 GIT_ARG3 RESULT) if(GIT_BIN) execute_process( COMMAND ${GIT_BIN} ${GIT_ARG1} ${GIT_ARG2} ${GIT_ARG3} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} OUTPUT_VARIABLE RES OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_VARIABLE ERROR_1 ) if(RES) set(${RESULT} ${RES} PARENT_SCOPE) elseif(ERROR_1) message("Can't execute ${GIT_BIN} ${GIT_ARG1} ${GIT_ARG2} ${GIT_ARG3}: ${ERROR_1}") endif() endif() endfunction() function(obtain_git_version GIT_VERSION GIT_FULL_VERSION) if(EXISTS "${PROJECT_SOURCE_DIR}/\.git" AND (NOT IS_SNAPSHOT)) run_git("describe" "--tags" "--abbrev=0" MAIN_VER) if(MAIN_VER) set(APP_VERSION "${MAIN_VER}" PARENT_SCOPE) set(${GIT_VERSION} ${MAIN_VER} PARENT_SCOPE) run_git("rev-list" "--count" "${MAIN_VER}\.\.HEAD" COMMITS) if(COMMITS) run_git("rev-parse" "--short" "HEAD" VER_HASH) if(COMMITS AND VER_HASH) set(PSI_REVISION ${VER_HASH} PARENT_SCOPE) if(PSI_PLUS) set(APP_VERSION "${MAIN_VER}.${COMMITS}" PARENT_SCOPE) set(${GIT_FULL_VERSION} "${MAIN_VER}.${COMMITS} \(${PSI_COMPILATION_DATE}, ${VER_HASH}${PSI_VER_SUFFIX}\)" PARENT_SCOPE) else() set(${GIT_FULL_VERSION} "${MAIN_VER} \(${PSI_COMPILATION_DATE}, ${VER_HASH}${PSI_VER_SUFFIX}\)" PARENT_SCOPE) endif() endif() endif() endif() elseif(IS_SNAPSHOT) message("${PROJECT_NAME} snapshot detected. Reading version from file...") else() message("${PROJECT_SOURCE_DIR}/\.git directory not found. Reading version from file...") endif() endfunction() function(obtain_psi_file_version VERSION_LINES OUTPUT_VERSION) string(REGEX MATCHALL "^([0-9]+(\\.[0-9]+)+)+(.+Psi:([a-fA-F0-9]+)?.+Psi\\+:([a-fA-F0-9]+)?)?" VER_LINE_A ${VERSION_LINES}) if(${CMAKE_MATCH_COUNT} EQUAL 5) if(CMAKE_MATCH_1) set(_APP_VERSION ${CMAKE_MATCH_1}) endif() if(CMAKE_MATCH_4) set(_PSI_REVISION ${CMAKE_MATCH_4}) endif() if(CMAKE_MATCH_5) set(_PSI_PLUS_REVISION ${CMAKE_MATCH_5}) endif() elseif(${CMAKE_MATCH_COUNT} EQUAL 2) if(CMAKE_MATCH_1) set(_APP_VERSION ${CMAKE_MATCH_1}) endif() endif() if(_APP_VERSION) set(APP_VERSION ${_APP_VERSION} PARENT_SCOPE) message(STATUS "Psi version found: ${_APP_VERSION}") else() message(WARNING "Psi+ version not found! ${DEFAULT_VER} version will be set") set(_APP_VERSION ${DEFAULT_VER}) set(APP_VERSION ${_APP_VERSION} PARENT_SCOPE) endif() if(_PSI_REVISION) set(PSI_REVISION ${_PSI_REVISION} PARENT_SCOPE) message(STATUS "Psi revision found: ${_PSI_REVISION}") endif() if(_PSI_PLUS_REVISION) set(PSI_PLUS_REVISION ${_PSI_PLUS_REVISION} PARENT_SCOPE) message(STATUS "Psi+ revision found: ${_PSI_PLUS_REVISION}") endif() if(_APP_VERSION AND (_PSI_REVISION AND _PSI_PLUS_REVISION )) set(${OUTPUT_VERSION} "${_APP_VERSION} \(${PSI_COMPILATION_DATE}, Psi:${_PSI_REVISION}, Psi+:${_PSI_PLUS_REVISION}${PSI_VER_SUFFIX}\)" PARENT_SCOPE) else() if(PRODUCTION) set(${OUTPUT_VERSION} "${_APP_VERSION}" PARENT_SCOPE) else() set(${OUTPUT_VERSION} "${_APP_VERSION} \(${PSI_COMPILATION_DATE}${PSI_VER_SUFFIX}\)" PARENT_SCOPE) endif() endif() endfunction() if(NOT PSI_VERSION) if(PSI_PLUS) obtain_git_version(PSIVER GITVER) if(GITVER) message(STATUS "Git Version ${GITVER}") set(PSI_VERSION "${GITVER}") else() read_version_file(VER_LINES) obtain_psi_file_version("${VER_LINES}" FILEVER) if(FILEVER) set(PSI_VERSION "${FILEVER}") endif() endif() else() obtain_git_version(PSIVER GITVER) if(PSIVER AND GITVER) message(STATUS "Git Version ${PSIVER}") if(PRODUCTION) set(PSI_VERSION "${PSIVER}") else() set(PSI_VERSION "${GITVER}") endif() else() read_version_file(VER_LINES) obtain_psi_file_version("${VER_LINES}" FILEVER) if(FILEVER) set(PSI_VERSION "${FILEVER}") endif() endif() endif() unset(VER_LINES) endif() message(STATUS "${CLIENT_NAME} version set: ${PSI_VERSION}") psi-plus-snapshots-1.4.1456/cmake/modules/win32-prepare-deps.cmake000066400000000000000000000371521370065651000246440ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.1.0) if(WIN32) set(LIBS_TARGET prepare-bin-libs) if(CMAKE_BUILD_TYPE STREQUAL "Debug") set(D "d") endif() # Get Qt installation path string(REGEX REPLACE "([^ ]+)[/\\].*" "\\1" QT_BIN_DIR_TMP "${QT_MOC_EXECUTABLE}") string(REGEX REPLACE "\\\\" "/" QT_BIN_DIR "${QT_BIN_DIR_TMP}") unset(QT_BIN_DIR_TMP) function(find_psi_lib LIBLIST PATHES OUTPUT_PATH) set(_LIBS ${LIBLIST}) set(_PATHES ${PATHES}) set(_OUTPUT_PATH ${OUTPUT_PATH}) set(FIXED_PATHES "") foreach(_path ${_PATHES}) string(REGEX REPLACE "//bin" "/bin" tmp_path "${_path}") list(APPEND FIXED_PATHES ${tmp_path}) endforeach() if(NOT USE_MXE) set(ADDITTIONAL_FLAG "NO_DEFAULT_PATH") else() set(ADDITTIONAL_FLAG "") endif() foreach(_liba ${_LIBS}) set(_library _library-NOTFOUND) find_file( _library ${_liba} PATHS ${FIXED_PATHES} ${ADDITTIONAL_FLAG}) if( NOT "${_library}" STREQUAL "_library-NOTFOUND" ) message("library found ${_library}") copy("${_library}" "${_OUTPUT_PATH}" "${LIBS_TARGET}") endif() endforeach() unset(_LIBS) unset(_PATHES) unset(_OUTPUT_PATH) endfunction() set(SDK_PREFIX "") get_filename_component(QT_DIR ${QT_BIN_DIR} DIRECTORY) set(QT_PLUGINS_DIR ${QT_DIR}/plugins) set(QT_TRANSLATIONS_DIR ${QT_DIR}/translations) set(PSIMEDIA_FOUND OFF) #Set paths list(APPEND PATHES ${QT_BIN_DIR} ${QCA_DIR}bin ${QCA_DIR}/bin ${QT_PLUGINS_DIR}/crypto ${QCA_DIR}lib/qca-qt5/crypto ${QCA_DIR}lib/Qca-qt5/crypto ) if(USE_MXE) list(APPEND PATHES ${CMAKE_PREFIX_PATH}/bin ${CMAKE_PREFIX_PATH}/lib ) endif() if(EXISTS "${SDK_PATH}") list(APPEND PATHES "${IDN_ROOT}bin" "${HUNSPELL_ROOT}bin" "${LIBGCRYPT_ROOT}bin" "${LIBGPGERROR_ROOT}bin" "${LIBOTR_ROOT}bin" "${LIBTIDY_ROOT}bin" "${QJSON_ROOT}bin" "${ZLIB_ROOT}bin" "${SDK_PATH}openssl/bin" ) if(MSVC) list(APPEND PATHES "${SDK_PATH}bin" "${SDK_PATH}lib/qca-qt5/crypto" "${SDK_PATH}/lib/qca-qt5/crypto" "${SDK_PATH}plugins/crypto" "${SDK_PATH}/plugins/crypto" ) endif() if(SEPARATE_QJDNS) list(APPEND PATHES "${QJDNS_DIR}bin" ) endif() endif() #Find windeployqt prorgam and add windeploy target find_program(WINDEPLOYQTBIN windeployqt ${QT_BIN_DIR}) if(NOT "${WINDEPLOYQTBIN}" STREQUAL "WINDEPLOYQTBIN-NOTFOUND") message(STATUS "WinDeployQt utility - FOUND") if(CMAKE_BUILD_TYPE STREQUAL "Debug") list(APPEND WDARGS --debug) else() list(APPEND WDARGS --release) endif() add_custom_target(windeploy COMMAND ${WINDEPLOYQTBIN} ${WDARGS} $ WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} COMMENT "Preparing Qt runtime dependencies" ) else() #if windeployqt not found search libs manually # required libraries set( ICU_LIBS_PREFIXES icudt5 icudt6 icudt7 icuin5 icuin6 icuin7 icuuc5 icuuc6 icuuc7 ) set( ICU_LIBS "" ) #hack to find icu libs with name template icu\W{2}[1..9]-0.dll foreach( icu_prefix ${ICU_LIBS_PREFIXES} ) foreach( icu_counter RANGE 9 ) list(APPEND ICU_LIBS "${icu_prefix}${icu_counter}.dll" ) endforeach() endforeach() find_psi_lib("${ICU_LIBS}" "${PATHES}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/") unset(ICU_LIBS) # Qt5 libraries set(QT_LIBAS Qt5Concurrent${D}.dll Qt5Core${D}.dll Qt5Gui${D}.dll Qt5Multimedia${D}.dll Qt5MultimediaWidgets${D}.dll Qt5Network${D}.dll Qt5OpenGL${D}.dll Qt5Positioning${D}.dll Qt5PrintSupport${D}.dll Qt5Qml${D}.dll Qt5QmlModels${D}.dll Qt5Quick${D}.dll Qt5Script${D}.dll Qt5Sensors${D}.dll Qt5Sql${D}.dll Qt5Svg${D}.dll Qt5Svg${D}.dll Qt5WebChannel${D}.dll Qt5WebKit${D}.dll Qt5WebKitWidgets${D}.dll Qt5Widgets${D}.dll Qt5WinExtras${D}.dll Qt5Xml${D}.dll Qt5XmlPatterns${D}.dll ) find_psi_lib("${QT_LIBAS}" "${QT_BIN_DIR}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/") # find_psi_lib(qtaudio_windows${D}.dll "${QT_PLUGINS_DIR}/audio/" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/qtplugins/audio/") set(PLATFORMS_PLUGS qminimal${D}.dll qoffscreen${D}.dll qwindows${D}.dll ) find_psi_lib("${PLATFORMS_PLUGS}" "${QT_PLUGINS_DIR}/platforms/" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/qtplugins/platforms/") # set(STYLES_PLUGS qwindowsvistastyle${D}.dll ) find_psi_lib("${STYLES_PLUGS}" "${QT_PLUGINS_DIR}/styles/" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/qtplugins/styles/") # set(BEARER_PLUGS qgenericbearer${D}.dll qnativewifibearer${D}.dll ) find_psi_lib("${BEARER_PLUGS}" "${QT_PLUGINS_DIR}/bearer/" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/qtplugins/bearer/") # set(GENERIC_PLUGS qtuiotouchplugin${D}.dll ) find_psi_lib("${GENERIC_PLUGS}" "${QT_PLUGINS_DIR}/generic/" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/qtplugins/generic/") # set(ICONENGINES_PLUGS qsvgicon${D}.dll ) find_psi_lib("${ICONENGINES_PLUGS}" "${QT_PLUGINS_DIR}/iconengines/" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/qtplugins/iconengines/") # set(IMAGEFORMATS_PLUGS qdds${D}.dll qgif${D}.dll qicns${D}.dll qico${D}.dll qjp2${D}.dll qjpeg${D}.dll qmng${D}.dll qsvg${D}.dll qtga${D}.dll qtiff${D}.dll qwbmp${D}.dll qwebp${D}.dll ) find_psi_lib("${IMAGEFORMATS_PLUGS}" "${QT_PLUGINS_DIR}/imageformats/" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/qtplugins/imageformats/") # set(MEDIASERVICE_PLUGS dsengine${D}.dll qtmedia_audioengine${D}.dll wmfengine${D}.dll ) find_psi_lib("${MEDIASERVICE_PLUGS}" "${QT_PLUGINS_DIR}/mediaservice/" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/qtplugins/mediaservice/") # set(PLAYLISTFORMATS_PLUGS qtmultimedia_m3u${D}.dll ) find_psi_lib("${PLAYLISTFORMATS_PLUGS}" "${QT_PLUGINS_DIR}/playlistformats/" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/qtplugins/playlistformats/") # set(PRINTSUPPORT_PLUGS windowsprintersupport${D}.dll ) find_psi_lib("${PRINTSUPPORT_PLUGS}" "${QT_PLUGINS_DIR}/printsupport/" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/qtplugins/printsupport/") # set(SQLDRIVERS_PLUGS qsqlite${D}.dll ) find_psi_lib("${SQLDRIVERS_PLUGS}" "${QT_PLUGINS_DIR}/sqldrivers/" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/qtplugins/sqldrivers/") # if(KEYCHAIN_LIBS) set(KEYCHAIN_LIBS qt5keychain.dll libqt5keychain.dll ) find_psi_lib("${KEYCHAIN_LIBS}" "${PATHES}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/") endif() # Qt translations # if(EXISTS "${QT_TRANSLATIONS_DIR}") # file(GLOB QT_TRANSLATIONS "${QT_TRANSLATIONS_DIR}/q*.qm") # foreach(FILE ${QT_TRANSLATIONS}) # copy("${FILE}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/translations/" ${LIBS_TARGET}) # endforeach() # endif() endif() # psimedia deps if(BUILD_PSIMEDIA) if(MSVC) set(PSIMEDIA_DEPS ffi-7.dll gio-2.0-0.dll glib-2.0-0.dll gmodule-2.0-0.dll gobject-2.0-0.dll gstapp-1.0-0.dll gstaudio-1.0-0.dll gstbadaudio-1.0-0.dll gstbadbase-1.0-0.dll gstbase-1.0-0.dll gstnet-1.0-0.dll gstpbutils-1.0-0.dll gstreamer-1.0-0.dll gstriff-1.0-0.dll gstrtp-1.0-0.dll gsttag-1.0-0.dll gstvideo-1.0-0.dll gthread-2.0-0.dll libgcc_s_sjlj-1.dll libharfbuzz-0.dll libiconv-2.dll intl-8.dll libjpeg-8.dll libogg-0.dll libopus-0.dll orc-0.4-0.dll libpng16-16.dll libtheora-0.dll libtheoradec-1.dll libtheoraenc-1.dll libvorbis-0.dll libvorbisenc-2.dll libwinpthread-1.dll libxml2-2.dll z-1.dll ) set(GSTREAMER_PLUGINS gstapp.dll gstaudioconvert.dll gstaudiomixer.dll gstaudioresample.dll gstcoreelements.dll gstdirectsound.dll gstdirectsoundsrc.dll gstjpeg.dll gstlevel.dll gstogg.dll gstopus.dll gstopusparse.dll gstplayback.dll gstrtp.dll gstrtpmanager.dll gsttheora.dll gstvideoconvert.dll gstvideorate.dll gstvideoscale.dll gstvolume.dll gstvorbis.dll gstwasapi.dll gstwinks.dll ) set(PSIMEDIA_DEPS_DIR "${GST_SDK}/bin") set(GSTREAMER_PLUGINS_DIR "${GST_SDK}/lib/gstreamer-1.0") set(GST_PLUGINS_OUTPUT "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/gstreamer-1.0/") endif() if(USE_MXE) set(PSIMEDIA_DEPS libffi-6.dll libfontconfig-1.dll libgio-2.0-0.dll libgmodule-2.0-0.dll libgobject-2.0-0.dll libgstapp-1.0-0.dll libgstaudio-1.0-0.dll libgstbadaudio-1.0-0.dll libgstbadbase-1.0-0.dll libgstbase-1.0-0.dll libgstnet-1.0-0.dll libgstpbutils-1.0-0.dll libgstreamer-1.0-0.dll libgstriff-1.0-0.dll libgstrtp-1.0-0.dll libgsttag-1.0-0.dll libgstvideo-1.0-0.dll libgthread-2.0-0.dll libogg-0.dll libopus-0.dll libtheora-0.dll libtheoradec-1.dll libtheoraenc-1.dll libvorbis-0.dll libvorbisenc-2.dll ) set(GSTREAMER_PLUGINS libgstapp.dll libgstaudioconvert.dll libgstaudiomixer.dll libgstaudioresample.dll libgstcoreelements.dll libgstdirectsound.dll libgstdirectsoundsink.dll libgstdirectsoundsrc.dll libgstjpeg.dll libgstlevel.dll libgstogg.dll libgstopus.dll libgstopusparse.dll libgstplayback.dll libgstrtp.dll libgstrtpmanager.dll libgsttheora.dll libgstvideoconvert.dll libgstvideorate.dll libgstvideoscale.dll libgstvolume.dll libgstvorbis.dll libgstwasapi.dll libgstwinks.dll ) set(PSIMEDIA_DEPS_DIR "${CMAKE_PREFIX_PATH}/bin") set(GSTREAMER_PLUGINS_DIR "${CMAKE_PREFIX_PATH}/bin/gstreamer-1.0") set(GST_PLUGINS_OUTPUT "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/gstreamer-1.0/") endif() find_psi_lib("${PSIMEDIA_DEPS}" "${PSIMEDIA_DEPS_DIR}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/") # streamer plugins find_psi_lib("${GSTREAMER_PLUGINS}" "${GSTREAMER_PLUGINS_DIR}/" "${GST_PLUGINS_OUTPUT}") endif() #hack to find hunspell libs with name template libhunspell-1.[1..9]-0.dll foreach( hunsp_counter RANGE 9 ) list(APPEND HUNSPELL_LIBS "libhunspell-1.${hunsp_counter}-0.dll" ) endforeach() find_psi_lib("${HUNSPELL_LIBS}" "${PATHES}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/") unset(HUNSPELL_LIBS) # other libs and executables set( LIBRARIES_LIST gpg.exe libcrypto-1_1-x64.dll libcrypto-1_1.dll libeay32.dll libgcc_s_dw2-1.dll libgcc_s_seh-1.dll libgcc_s_sjlj-1.dll libgcrypt-11.dll libgcrypt-20.dll libgpg-error-0.dll libhunspell.dll libidn-11.dll libidn-12.dll libotr-5.dll libotr.dll libqca-qt5${D}.dll libsignal-protocol-c.dll libssl-1_1-x64.dll libssl-1_1.dll libstdc++-6.dll libtidy.dll libwinpthread-1.dll libxslt-1.dll libzlib.dll libzstd.dll ssleay32.dll zlib1.dll ) if(MSVC) list(APPEND LIBRARIES_LIST libotr${D}.dll tidy${D}.dll zlib1${D}.dll libidn${D}.dll qca-qt5${D}.dll ) endif() if(USE_MXE) list(APPEND LIBRARIES_LIST libbz2.dll libfreetype-6.dll libglib-2.0-0.dll libgpg-error6-0.dll libharfbuzz-0.dll libharfbuzz-icu-0.dll libiconv-2.dll libintl-8.dll libjasper-1.dll libjasper.dll libjpeg-9.dll liblcms2-2.dll liblzma-5.dll liblzo2-2.dll libminizip.dll libmng-2.dll libpcre-1.dll libpcre16-0.dll libpcre2-16-0.dll libpng16-16.dll libsqlite3-0.dll libssp-0.dll libtiff-5.dll libwebp-5.dll libwebp-7.dll libwebpdecoder-1.dll libwebpdecoder-3.dll libwebpdemux-1.dll libwebpdemux-2.dll libwebpmux-3.dll libxml2-2.dll libzstd.dll ) endif() if(SEPARATE_QJDNS) list(APPEND LIBRARIES_LIST libjdns.dll libqjdns.dll ) endif() find_psi_lib("${LIBRARIES_LIST}" "${PATHES}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/") # qca and plugins set(QCA_PLUGINS libqca-gnupg${D}.dll libqca-ossl${D}.dll ) if(MSVC) list(APPEND QCA_PLUGINS qca-gnupg${D}.dll qca-ossl${D}.dll ) endif() find_psi_lib("${QCA_PLUGINS}" "${PATHES}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/qtplugins/crypto/") copy("${PROJECT_SOURCE_DIR}/win32/qt.conf" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/" "${LIBS_TARGET}") endif() psi-plus-snapshots-1.4.1456/com.psi_plus.Psi_plus.json000066400000000000000000000114161370065651000226610ustar00rootroot00000000000000{ "app-id": "con.psi_plus.Psi_plus", "runtime": "org.kde.Platform", "runtime-version": "5.12", "sdk": "org.kde.Sdk", "command": "psi-plus", "rename-desktop-file": "psi-plus.desktop", "rename-appdata-file": "psi-plus.appdata.xml", "rename-icon": "psi-plus", "finish-args": [ "--socket=x11", "--socket=wayland", "--socket=pulseaudio", "--socket=system-bus", "--socket=session-bus", "--share=ipc", "--share=network", "--filesystem=xdg-documents", "--filesystem=xdg-download", "--filesystem=xdg-music", "--filesystem=xdg-pictures", "--filesystem=xdg-videos", "--talk-name=org.kde.StatusNotifierWatcher", "--talk-name=org.freedesktop.Notifications", "--device=dri"], "build-options": { "cflags": "-O2 -g", "cxxflags": "-O2 -g" }, "cleanup": [ "/cache", "/man", "/share/man", "/lib/systemd", "*.la", "*.a" ], "modules": [ { "name": "qca", "buildsystem": "cmake", "config-opts": ["-DBUILD_TOOLS=OFF", "-DBUILD_TESTS=OFF", "-DBUILD_PLUGINS=ossl;gnupg"], "build-options": {"cxxflags": "-O2 -g"}, "sources": [ { "type": "archive", "url": "https://download.kde.org/stable/qca/2.2.1/qca-2.2.1.tar.xz", "sha256": "d716d2d8e3ed8d95bbdb061f03081d7d032206f746a30a4d29d72196f50e7b02" } ] }, { "name": "libidn", "sources": [ { "type": "archive", "url": "https://ftp.gnu.org/gnu/libidn/libidn-1.35.tar.gz", "sha256": "f11af1005b46b7b15d057d7f107315a1ad46935c7fcdf243c16e46ec14f0fe1e" } ] }, { "name": "libgpg-error", "sources": [ { "type": "archive", "url": "https://www.gnupg.org/ftp/gcrypt/libgpg-error/libgpg-error-1.28.tar.bz2", "sha256": "3edb957744905412f30de3e25da18682cbe509541e18cd3b8f9df695a075da49" } ] }, { "name": "libgcrypt", "sources": [ { "type": "archive", "url": "https://www.gnupg.org/ftp/gcrypt/libgcrypt/libgcrypt-1.8.2.tar.bz2", "sha256": "c8064cae7558144b13ef0eb87093412380efa16c4ee30ad12ecb54886a524c07" } ] }, { "name": "libotr", "config-opts": ["--with-pic"], "sources": [ { "type": "archive", "url": "https://otr.cypherpunks.ca/libotr-4.1.1.tar.gz", "sha256": "8b3b182424251067a952fb4e6c7b95a21e644fbb27fbd5f8af2b2ed87ca419f5" } ] }, { "name": "tidy-html", "buildsystem": "simple", "build-commands": [ "cd build/cmake && cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/app ../.. ", "cd build/cmake && make && make install"], "sources": [ { "type": "archive", "url": "https://github.com/htacg/tidy-html5/archive/5.6.0.zip", "sha256": "53e71d63eabdf4d9a29c7b8d242b008b185556ad6761d7f1bceebc8400715a88" } ] }, { "name": "libsignal-protocol-c", "buildsystem": "cmake", "config-opts": ["-DCMAKE_BUILD_TYPE=Release", "-DBUILD_TESTING=0", "-DCMAKE_POSITION_INDEPENDENT_CODE=ON", "-DCMAKE_INSTALL_PREFIX=/app"], "sources": [ { "type": "git", "url": "https://github.com/signalapp/libsignal-protocol-c.git" } ] }, { "name": "psi-plus", "buildsystem": "cmake", "builddir": true, "config-opts": ["-DENABLE_PLUGINS=ON", "-DCHAT_TYPE=basic"], "sources": [ { "type": "git", "url": "https://github.com/psi-plus/psi-plus-snapshots.git" }, { "dest": "psi-plus-l10n", "type": "git", "url": "https://github.com/psi-plus/psi-plus-l10n.git" }, { "type": "shell", "commands": [ "mkdir -p ./translations", "cp -R psi-plus-l10n/translations ./translations"] } ] } ] } psi-plus-snapshots-1.4.1456/configure000077500000000000000000003425241370065651000174740ustar00rootroot00000000000000#!/bin/sh # # Generated by qconf 2.0 ( https://github.com/psi-plus/qconf ) # show_usage() { cat </dev/null` if echo $WHICH | grep 'shell built-in command' >/dev/null 2>&1; then WHICH=which elif [ -z "$WHICH" ]; then if which which >/dev/null 2>&1; then WHICH=which else for a in /usr/ucb /usr/bin /bin /usr/local/bin; do if [ -x $a/which ]; then WHICH=$a/which break fi done fi fi RET_CODE=1 if [ -z "$WHICH" ]; then OLD_IFS=$IFS IFS=: for a in $PATH; do if [ -x $a/$1 ]; then echo "$a/$1" RET_CODE=0 [ -z "$ALL_MATCHES" ] && break fi done IFS=$OLD_IFS export IFS else a=`"$WHICH" "$ALL_MATCHES" "$1" 2>/dev/null` if [ ! -z "$a" -a -x "$a" ]; then echo "$a" RET_CODE=0 fi fi HOME=$OLD_HOME export HOME return $RET_CODE } WHICH=which_command # find a make command if [ -z "$MAKE" ]; then MAKE= for mk in gmake make; do if $WHICH $mk >/dev/null 2>&1; then MAKE=`$WHICH $mk` break fi done if [ -z "$MAKE" ]; then echo "You don't seem to have 'make' or 'gmake' in your PATH." echo "Cannot proceed." exit 1 fi fi show_qt_info() { printf "Be sure you have a proper Qt 4.0+ build environment set up. This means not\n" printf "just Qt, but also a C++ compiler, a make tool, and any other packages\n" printf "necessary for compiling C++ programs.\n" printf "\n" printf "If you are certain everything is installed, then it could be that Qt is not\n" printf "being recognized or that a different version of Qt is being detected by\n" printf "mistake (for example, this could happen if \$QTDIR is pointing to a Qt 3\n" printf "installation). At least one of the following conditions must be satisfied:\n" printf "\n" printf " 1) --qtdir is set to the location of Qt\n" printf " 2) \$QTDIR is set to the location of Qt\n" printf " 3) QtCore is in the pkg-config database\n" printf " 4) qmake is in the \$PATH\n" printf "\n" printf "This script will use the first one it finds to be true, checked in the above\n" printf "order. #3 and #4 are the recommended options. #1 and #2 are mainly for\n" printf "overriding the system configuration.\n" printf "\n" } while [ $# -gt 0 ]; do optarg=`expr "x$1" : 'x[^=]*=\(.*\)'` case "$1" in --prefix=*) PREFIX=$optarg shift ;; --bindir=*) BINDIR=$optarg shift ;; --libdir=*) LIBDIR=$optarg shift ;; --datadir=*) DATADIR=$optarg shift ;; --qtdir=*) EX_QTDIR=$optarg shift ;; --extraconf=*) QC_EXTRACONF=$optarg shift ;; --release) QC_RELEASE="Y" shift ;; --debug) QC_DEBUG="Y" shift ;; --no-separate-debug-info) QC_NO_SEPARATE_DEBUG_INFO="Y" shift ;; --separate-debug-info) QC_SEPARATE_DEBUG_INFO="Y" shift ;; --psiplus) QC_PSIPLUS="Y" shift ;; --with-idn-inc=*) QC_WITH_IDN_INC=$optarg shift ;; --with-idn-lib=*) QC_WITH_IDN_LIB=$optarg shift ;; --with-qca-inc=*) QC_WITH_QCA_INC=$optarg shift ;; --with-qca-lib=*) QC_WITH_QCA_LIB=$optarg shift ;; --with-zlib-inc=*) QC_WITH_ZLIB_INC=$optarg shift ;; --with-zlib-lib=*) QC_WITH_ZLIB_LIB=$optarg shift ;; --with-qjdns-inc=*) QC_WITH_QJDNS_INC=$optarg shift ;; --with-qjdns-lib=*) QC_WITH_QJDNS_LIB=$optarg shift ;; --enable-universal) QC_ENABLE_universal="Y" shift ;; --disable-universal) QC_DEFAULT_DISABLE_universal="Y" shift ;; --disable-qdbus) QC_DISABLE_qdbus="Y" shift ;; --enable-qdbus) QC_DEFAULT_ENABLE_qdbus="Y" shift ;; --disable-keychain) QC_DISABLE_keychain="Y" shift ;; --enable-keychain) QC_DEFAULT_ENABLE_keychain="Y" shift ;; --enable-webkit) QC_ENABLE_webkit="Y" shift ;; --disable-webkit) QC_DEFAULT_DISABLE_webkit="Y" shift ;; --with-webkit=*) QC_WITH_WEBKIT=$optarg shift ;; --with-http-parser-inc=*) QC_WITH_HTTP_PARSER_INC=$optarg shift ;; --with-http-parser-lib=*) QC_WITH_HTTP_PARSER_LIB=$optarg shift ;; --bundled-http-parser) QC_BUNDLED_HTTP_PARSER="Y" shift ;; --with-b2-inc=*) QC_WITH_B2_INC=$optarg shift ;; --with-b2-lib=*) QC_WITH_B2_LIB=$optarg shift ;; --disable-growl) QC_DISABLE_growl="Y" shift ;; --enable-growl) QC_DEFAULT_ENABLE_growl="Y" shift ;; --with-growl=*) QC_WITH_GROWL=$optarg shift ;; --enable-whiteboarding) QC_ENABLE_whiteboarding="Y" shift ;; --disable-whiteboarding) QC_DEFAULT_DISABLE_whiteboarding="Y" shift ;; --disable-xss) QC_DISABLE_xss="Y" shift ;; --enable-xss) QC_DEFAULT_ENABLE_xss="Y" shift ;; --disable-aspell) QC_DISABLE_aspell="Y" shift ;; --enable-aspell) QC_DEFAULT_ENABLE_aspell="Y" shift ;; --with-aspell-inc=*) QC_WITH_ASPELL_INC=$optarg shift ;; --with-aspell-lib=*) QC_WITH_ASPELL_LIB=$optarg shift ;; --disable-enchant) QC_DISABLE_enchant="Y" shift ;; --enable-enchant) QC_DEFAULT_ENABLE_enchant="Y" shift ;; --disable-hunspell) QC_DISABLE_hunspell="Y" shift ;; --enable-hunspell) QC_DEFAULT_ENABLE_hunspell="Y" shift ;; --with-hunspell-inc=*) QC_WITH_HUNSPELL_INC=$optarg shift ;; --with-hunspell-lib=*) QC_WITH_HUNSPELL_LIB=$optarg shift ;; --disable-plugins) QC_DISABLE_plugins="Y" shift ;; --enable-plugins) QC_DEFAULT_ENABLE_plugins="Y" shift ;; --verbose) QC_VERBOSE="Y" shift ;; --qtselect=*) QC_QTSELECT="${optarg}" shift ;; --help) show_usage; exit ;; *) echo "configure: WARNING: unrecognized options: $1" >&2; shift; ;; esac done PREFIX=${PREFIX:-/usr/local} BINDIR=${BINDIR:-$PREFIX/bin} LIBDIR=${LIBDIR:-$PREFIX/lib} DATADIR=${DATADIR:-$PREFIX/share} echo "Configuring Psi ..." if [ "$QC_VERBOSE" = "Y" ]; then echo echo PREFIX=$PREFIX echo BINDIR=$BINDIR echo LIBDIR=$LIBDIR echo DATADIR=$DATADIR echo EX_QTDIR=$EX_QTDIR echo QC_EXTRACONF=$QC_EXTRACONF echo QC_RELEASE=$QC_RELEASE echo QC_DEBUG=$QC_DEBUG echo QC_NO_SEPARATE_DEBUG_INFO=$QC_NO_SEPARATE_DEBUG_INFO echo QC_SEPARATE_DEBUG_INFO=$QC_SEPARATE_DEBUG_INFO echo QC_PSIPLUS=$QC_PSIPLUS echo QC_WITH_IDN_INC=$QC_WITH_IDN_INC echo QC_WITH_IDN_LIB=$QC_WITH_IDN_LIB echo QC_WITH_QCA_INC=$QC_WITH_QCA_INC echo QC_WITH_QCA_LIB=$QC_WITH_QCA_LIB echo QC_WITH_ZLIB_INC=$QC_WITH_ZLIB_INC echo QC_WITH_ZLIB_LIB=$QC_WITH_ZLIB_LIB echo QC_WITH_QJDNS_INC=$QC_WITH_QJDNS_INC echo QC_WITH_QJDNS_LIB=$QC_WITH_QJDNS_LIB echo QC_ENABLE_universal=$QC_ENABLE_universal echo QC_DEFAULT_DISABLE_universal=$QC_DEFAULT_DISABLE_universal echo QC_DISABLE_qdbus=$QC_DISABLE_qdbus echo QC_DEFAULT_ENABLE_qdbus=$QC_DEFAULT_ENABLE_qdbus echo QC_DISABLE_keychain=$QC_DISABLE_keychain echo QC_DEFAULT_ENABLE_keychain=$QC_DEFAULT_ENABLE_keychain echo QC_ENABLE_webkit=$QC_ENABLE_webkit echo QC_DEFAULT_DISABLE_webkit=$QC_DEFAULT_DISABLE_webkit echo QC_WITH_WEBKIT=$QC_WITH_WEBKIT echo QC_WITH_HTTP_PARSER_INC=$QC_WITH_HTTP_PARSER_INC echo QC_WITH_HTTP_PARSER_LIB=$QC_WITH_HTTP_PARSER_LIB echo QC_BUNDLED_HTTP_PARSER=$QC_BUNDLED_HTTP_PARSER echo QC_WITH_B2_INC=$QC_WITH_B2_INC echo QC_WITH_B2_LIB=$QC_WITH_B2_LIB echo QC_DISABLE_growl=$QC_DISABLE_growl echo QC_DEFAULT_ENABLE_growl=$QC_DEFAULT_ENABLE_growl echo QC_WITH_GROWL=$QC_WITH_GROWL echo QC_ENABLE_whiteboarding=$QC_ENABLE_whiteboarding echo QC_DEFAULT_DISABLE_whiteboarding=$QC_DEFAULT_DISABLE_whiteboarding echo QC_DISABLE_xss=$QC_DISABLE_xss echo QC_DEFAULT_ENABLE_xss=$QC_DEFAULT_ENABLE_xss echo QC_DISABLE_aspell=$QC_DISABLE_aspell echo QC_DEFAULT_ENABLE_aspell=$QC_DEFAULT_ENABLE_aspell echo QC_WITH_ASPELL_INC=$QC_WITH_ASPELL_INC echo QC_WITH_ASPELL_LIB=$QC_WITH_ASPELL_LIB echo QC_DISABLE_enchant=$QC_DISABLE_enchant echo QC_DEFAULT_ENABLE_enchant=$QC_DEFAULT_ENABLE_enchant echo QC_DISABLE_hunspell=$QC_DISABLE_hunspell echo QC_DEFAULT_ENABLE_hunspell=$QC_DEFAULT_ENABLE_hunspell echo QC_WITH_HUNSPELL_INC=$QC_WITH_HUNSPELL_INC echo QC_WITH_HUNSPELL_LIB=$QC_WITH_HUNSPELL_LIB echo QC_DISABLE_plugins=$QC_DISABLE_plugins echo QC_DEFAULT_ENABLE_plugins=$QC_DEFAULT_ENABLE_plugins echo fi printf "Verifying Qt build environment ... " if [ -z "$QC_QTSELECT" ]; then QC_QTSELECT="$(echo $QT_SELECT | tr -d "qt")" fi if [ ! -z "$QC_QTSELECT" ]; then QTSEARCHTEXT="$QC_QTSELECT" else QTSEARCHTEXT="4 or 5" fi # run qmake and check version qmake_check() { if [ -x "$1" ]; then cmd="\"$1\" -query QT_VERSION" if [ "$QC_VERBOSE" = "Y" ]; then echo "running: $cmd" fi vout=`/bin/sh -c "$cmd" 2>&1` case "${vout}" in *.*.*) vmaj="${vout%%.*}" if [ ! -z "$QC_QTSELECT" ]; then if [ "$vmaj" = "$QC_QTSELECT" ]; then return 0 fi else if [ "$vmaj" = "4" ] || [ "$vmaj" = "5" ]; then return 0 fi fi ;; esac if [ "$QC_VERBOSE" = "Y" ]; then echo "Warning: $1 not for Qt ${QTSEARCHTEXT}" fi fi return 1 } if [ "$QC_VERBOSE" = "Y" ]; then echo fi qm="" qt4_names="qmake-qt4 qmake4" qt5_names="qmake-qt5 qmake5" names="qmake" if [ -z "$QC_QTSELECT" ]; then names="${qt5_names} ${qt4_names} $names" else if [ "$QC_QTSELECT" = "4" ]; then names="${qt4_names} $names" elif [ "$QC_QTSELECT" -ge "5" ]; then names="${qt5_names} $names" fi fi if [ -z "$qm" ] && [ ! -z "$EX_QTDIR" ]; then # qt4 check: --qtdir for n in $names; do qstr=$EX_QTDIR/bin/$n if qmake_check "$qstr"; then qm=$qstr break fi done if [ -z "$qm" ] && [ "$QC_VERBOSE" = "Y" ]; then echo "Warning: qmake not found via --qtdir" fi elif [ -z "$qm" ] && [ ! -z "$QTDIR" ]; then # qt4 check: QTDIR for n in $names; do qstr=$QTDIR/bin/$n if qmake_check "$qstr"; then qm=$qstr break fi done if [ -z "$qm" ] && [ "$QC_VERBOSE" = "Y" ]; then echo "Warning: qmake not found via \$QTDIR" fi else # Try all other implicit checks # qtchooser if [ -z "$qm" ]; then qtchooser=$($WHICH qtchooser 2>/dev/null) if [ ! -z "$qtchooser" ]; then if [ ! -z "$QC_QTSELECT" ]; then versions="$QC_QTSELECT" else cmd="$qtchooser --list-versions" if [ "$QC_VERBOSE" = "Y" ]; then echo "running: $cmd" fi versions=`$cmd` fi for version in $versions; do cmd="$qtchooser -run-tool=qmake -qt=${version} -query QT_INSTALL_BINS" if [ "$QC_VERBOSE" = "Y" ]; then echo "running: $cmd" fi qtbins=`$cmd 2>/dev/null` if [ ! -z "$qtbins" ] && qmake_check "$qtbins/qmake"; then qm="$qtbins/qmake" break fi done fi fi if [ -z "$qm" ] && [ "$QC_VERBOSE" = "Y" ]; then echo "Warning: qmake not found via qtchooser" fi # qt4: pkg-config if [ -z "$qm" ]; then cmd="pkg-config QtCore --variable=exec_prefix" if [ "$QC_VERBOSE" = "Y" ]; then echo "running: $cmd" fi str=`$cmd 2>/dev/null` if [ ! -z "$str" ]; then for n in $names; do qstr=$str/bin/$n if qmake_check "$qstr"; then qm=$qstr break fi done fi fi if [ -z "$qm" ] && [ "$QC_VERBOSE" = "Y" ]; then echo "Warning: qmake not found via pkg-config" fi # qmake in PATH if [ -z "$qm" ]; then for n in $names; do qstr=`$WHICH -a $n 2>/dev/null` for q in $qstr; do if qmake_check "$q"; then qm="$q" break fi done if [ ! -z "$qm" ]; then break fi done fi if [ -z "$qm" ] && [ "$QC_VERBOSE" = "Y" ]; then echo "Warning: qmake not found via \$PATH" fi # end of implicit checks fi if [ -z "$qm" ]; then if [ "$QC_VERBOSE" = "Y" ]; then echo " -> fail" else echo "fail" fi printf "\n" printf "Reason: Unable to find the 'qmake' tool for Qt ${QTSEARCHTEXT}.\n" printf "\n" show_qt_info exit 1; fi if [ "$QC_VERBOSE" = "Y" ]; then echo qmake found in "$qm" fi # try to determine the active makespec defmakespec=$QMAKESPEC if [ -z "$defmakespec" ]; then if $WHICH readlink >/dev/null 2>&1; then READLINK=`$WHICH readlink` fi if [ ! -z "$READLINK" ]; then qt_mkspecsdir=`"$qm" -query QT_INSTALL_DATA`/mkspecs if [ -d "$qt_mkspecsdir" ] && [ -h "$qt_mkspecsdir/default" ]; then defmakespec=`$READLINK $qt_mkspecsdir/default` fi fi fi if [ "$QC_VERBOSE" = "Y" ]; then echo makespec is $defmakespec fi qm_spec="" # if the makespec is macx-xcode, force macx-g++ if [ "$defmakespec" = "macx-xcode" ]; then qm_spec=macx-g++ QMAKESPEC=$qm_spec export QMAKESPEC if [ "$QC_VERBOSE" = "Y" ]; then echo overriding makespec to $qm_spec fi fi gen_files() { cat >"$1/modules.cpp" <= 5.0.0 -----END QCMOD----- */ class qc_qt5 : public ConfObj { public: qc_qt5(Conf *c) : ConfObj(c) { } QString name() const { return "Qt >= 5.0.0"; } QString shortname() const { return "qt5"; } bool exec() { return (QT_VERSION >= 0x050000); } QString resultString() const { return QT_VERSION_STR; } }; class QtCompFinder : public ConfObj { protected: QString compId; QString compShortName; QString compName; QString extraConfIfFound; QString finderResult; public: /** * id - (QT += ) * shortName - used with QC_ENABLE_ | QC_DISABLE_ env vars * MUST MATCH with qcm module name without extension * name - something visible in the log * extraConfIfFound - extra string added to conf.pri if found */ QtCompFinder(const QString &id, const QString &shortName, const QString &name, const QString &extraConfIfFound, Conf *c) : ConfObj(c), compId(id), compShortName(shortName), compName(name), extraConfIfFound(extraConfIfFound) { } QString name() const { return compName; } QString shortname() const { return compShortName; } QString resultString() const { if (!finderResult.isEmpty()) return finderResult; return ConfObj::resultString(); } bool exec() { if (!conf->getenv("QC_DISABLE_" + compShortName).isEmpty()) { finderResult = "disabled"; return false; } QString proextra = "CONFIG += qt\\n" "QT -= gui\\n" "QT += "; proextra += compId; QString str = "\\n" "int main()\\n" "{\\n" " return 0;\\n" "}\\n"; int ret; if (!conf->doCompileAndLink(str, QStringList(), QString(), proextra, &ret)) return false; if (ret != 0) return false; if (!extraConfIfFound.isEmpty()) { conf->addExtra(extraConfIfFound); } return true; } }; #define QC_AS_STR(s) #s #define QC_SILENT_NOT_FOUND(modname) \\ class qc_##modname : public ConfObj { \\ public: \\ qc_##modname(Conf *c) : ConfObj(c) { } \\ QString name() const { return QC_AS_STR(modname); } \\ QString shortname() const { return QC_AS_STR(modname); } \\ QString checkString() const { return QString(); } \\ bool exec() { return false; } \\ }; #define QC_FIND_QT_COMP_BASE(id, shortname, name, extraConfIfFound, suffix) \\ class qc_##shortname##suffix : public QtCompFinder { \\ public: \\ qc_##shortname##suffix(Conf *c) : \\ QtCompFinder(QC_AS_STR(id), QC_AS_STR(shortname), QC_AS_STR(name), extraConfIfFound, c) \\ { \\ } \\ }; #define QC_FIND_QT_COMP_E(id, shortname, name, extraConfIfFound) \\ QC_FIND_QT_COMP_BASE(id, shortname, name, extraConfIfFound, ) #define QC_FIND_QT_COMP(id, shortname, name) QC_FIND_QT_COMP_E(id, shortname, name, "") #line 1 "buildmodeapp.qcm" /* -----BEGIN QCMOD----- name: buildmodeapp section: project arg: release,Build with debugging turned off (default). arg: debug,Build with debugging turned on. arg: no-separate-debug-info,Do not store debug information in a separate file (default for mac). arg: separate-debug-info,Strip debug information into a separate .debug file (default for non-mac). arg: psiplus,Build Psi+ instead of Psi -----END QCMOD----- arg: debug-and-release,Build two versions, with and without debugging turned on (mac only). */ #include #define QC_BUILDMODE bool qc_buildmode_release = false; bool qc_buildmode_debug = false; bool qc_buildmode_separate_debug_info = false; bool qc_psiplus = false; QString qc_psi_dir_name = "psi"; static QString sourceDir; // this is a utility function required by few other modules static bool psiGenerateFile(const QString &inFile, const QString &outFile, const QHash &vars) { QFile fin(inFile); QFile fout(outFile); if (!fin.open(QIODevice::ReadOnly | QIODevice::Text) || !fout.open(QIODevice::WriteOnly | QIODevice::Truncate)) return false; QRegExp re("@[^@]*@"); QTextStream tin(&fin); QTextStream tout(&fout); while (!tin.atEnd()) { QString line = tin.readLine(); QHashIterator it(vars); while (it.hasNext()) { it.next(); line.replace("@" + it.key() + "@", it.value()); } line.replace(re, ""); line.replace("#cmakedefine", "#define"); tout << line << endl; } return true; } void copyPath(QString src, QString dst) { QDir dir(src); if (!dir.exists()) return; for (const QString &d : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { QString dst_path = dst + QDir::separator() + d; dir.mkpath(dst_path); copyPath(src + QDir::separator() + d, dst_path); } for (const QString &f : dir.entryList(QDir::Files)) { QFile::copy(src + QDir::separator() + f, dst + QDir::separator() + f); } } class qc_buildmodeapp : public ConfObj { public: qc_buildmodeapp(Conf *c) : ConfObj(c) { } QString name() const { return "buildmodeapp"; } QString shortname() const { return "buildmodeapp"; } // no output QString checkString() const { return QString(); } bool exec() { QFileInfo fi(qc_getenv("QC_COMMAND")); sourceDir = fi.absolutePath(); // first, parse out the options bool opt_release = false; bool opt_debug = false; bool opt_debug_and_release = false; bool opt_no_separate_debug_info = false; bool opt_separate_debug_info = false; if (conf->getenv("QC_RELEASE") == "Y") opt_release = true; if (conf->getenv("QC_DEBUG") == "Y") opt_debug = true; if (conf->getenv("QC_DEBUG_AND_RELEASE") == "Y") opt_debug_and_release = true; if (conf->getenv("QC_NO_SEPARATE_DEBUG_INFO") == "Y") opt_no_separate_debug_info = true; if (conf->getenv("QC_SEPARATE_DEBUG_INFO") == "Y") opt_separate_debug_info = true; bool staticmode = false; if (conf->getenv("QC_STATIC") == "Y") staticmode = true; #ifndef Q_OS_MAC if (opt_debug_and_release) { printf("\\nError: The --debug-and-release option is for mac only.\\n"); exit(1); } #endif // sanity check exclusive options int x; // build mode x = 0; if (opt_release) ++x; if (opt_debug) ++x; if (opt_debug_and_release) ++x; if (x > 1) { printf("\\nError: Use only one of --release, --debug, or --debug-and-release.\\n"); exit(1); } // debug info x = 0; if (opt_no_separate_debug_info) ++x; if (opt_separate_debug_info) ++x; if (x > 1) { printf("\\nError: Use only one of --separate-debug-info or --no-separate-debug-info\\n"); exit(1); } // now process the options if (opt_release) qc_buildmode_release = true; else if (opt_debug) qc_buildmode_debug = true; else if (opt_debug_and_release) { qc_buildmode_release = true; qc_buildmode_debug = true; } else // default qc_buildmode_release = true; if (opt_separate_debug_info) qc_buildmode_separate_debug_info = true; else if (opt_no_separate_debug_info) { // nothing to do } else // default { #ifndef Q_OS_MAC qc_buildmode_separate_debug_info = true; #endif } // make the string QStringList opts; QString other; if (qc_buildmode_release && qc_buildmode_debug) { opts += "debug_and_release"; opts += "build_all"; } else if (qc_buildmode_release) opts += "release"; else // qc_buildmode_debug opts += "debug"; if (qc_buildmode_separate_debug_info) { opts += "separate_debug_info"; other += "*-g++*:QMAKE_CFLAGS += -g\\n"; other += "*-g++*:QMAKE_CXXFLAGS += -g\\n"; } QString str = "CONFIG -= debug_and_release debug release\\n"; str += QString("CONFIG += ") + opts.join(" ") + '\\n'; conf->addExtra(str); if (!other.isEmpty()) conf->addExtra(other); // rebranding qc_psiplus = conf->getenv("QC_PSIPLUS") == "Y"; if (qc_psiplus) { qc_psi_dir_name = "psi-plus"; conf->addDefine("PSI_PLUS"); conf->addExtra("CONFIG += psiplus"); } return true; } }; #line 1 "idn.qcm" /* -----BEGIN QCMOD----- name: libidn arg: with-idn-inc=[path],Path to libidn include files arg: with-idn-lib=[path],Path to libidn library or framework files -----END QCMOD----- */ /* Warning: libidn is somewhat deprecated in favor of libidn2, but we can't just drop it since IDNA2008 doesn't include XMPP profiles and has nothing from stringprep. This stuff was moved to RFC8264 and RFC8265 and its usage was described in RFC7622. There are no known C/C++ implementations of these specs at the moment of this writing. A partial and not sufficient implementation can be found at https://gitlab.com/gnutls/gnutls/blob/master/lib/str-unicode.c. It implements OpaqueString Profile but doesn't implement UsernameCaseMapped profile required by RFC7622. Note it may be also desired to implement RFC8266. Some implementations in other languages: Go: https://godoc.org/golang.org/x/text/secure/precis Python: https://pypi.org/project/precis-i18n/ Perl: https://metacpan.org/release/Unicode-Precis PHP: https://github.com/tom--/precis We can try to use https://github.com/lpereira/gomoku to convert from Go. */ //---------------------------------------------------------------------------- // qc_idn //---------------------------------------------------------------------------- class qc_idn : public ConfObj { public: qc_idn(Conf *c) : ConfObj(c) { } QString name() const { return "LibIDN"; } QString shortname() const { return "libidn"; } bool exec() { QString idn_incdir, idn_libdir, idn_prefixdir; idn_incdir = conf->getenv("QC_WITH_IDN_INC"); idn_libdir = conf->getenv("QC_WITH_IDN_LIB"); idn_prefixdir = conf->getenv("PREFIX"); if (!idn_incdir.isEmpty() || !idn_libdir.isEmpty() || !idn_prefixdir.isEmpty()) { // prefer this if given if ((!idn_incdir.isEmpty() && conf->checkHeader(idn_incdir, "stringprep.h")) || (idn_incdir.isEmpty() && conf->findHeader("stringprep.h", QStringList(), &idn_incdir))) { conf->addIncludePath(idn_incdir); } else { printf("Headers not found!\\n"); return false; } #ifdef Q_OS_WIN conf->addDefine("LIBIDN_STATIC"); // it's need only for iris anyway QString staticLibName = qc_buildmode_debug ? "libidnd" : "libidn"; QString dynamicLibName = qc_buildmode_debug ? "idnd" : "idn"; QStringList libNames = QStringList() << staticLibName << dynamicLibName; #else QStringList libNames = QStringList() << "idn"; #endif bool libFound = false; for (const QString &libName : libNames) { if ((!idn_libdir.isEmpty() && conf->checkLibrary(idn_libdir, libName)) || (idn_libdir.isEmpty() && conf->findLibrary(libName, &idn_libdir))) { conf->addLib(idn_libdir.isEmpty() ? QString("-l") + libName : QString("-L%1 -l%2").arg(idn_libdir, libName)); libFound = true; break; } } if (!libFound) { printf("Libraries not found!\\n"); } return libFound; } QStringList incs; QString version, libs, other; if (conf->findPkgConfig("libidn", VersionAny, QString(), &version, &incs, &libs, &other)) { for (int n = 0; n < incs.count(); ++n) conf->addIncludePath(incs[n]); if (!libs.isEmpty()) conf->addLib(libs); return true; } return false; } }; #line 1 "qca.qcm" /* -----BEGIN QCMOD----- name: QCA >= 2.0 arg: with-qca-inc=[path],Path to QCA include files arg: with-qca-lib=[path],Path to QCA library or framework files -----END QCMOD----- */ // adapted from crypto.prf static QString internal_crypto_prf(const QString &incdir, const QString &libdir, const QString &frameworkdir) { QString out = QString("QCA_INCDIR = %1\\n" "QCA_LIBDIR = %2\\n" "QMAKE_RPATHDIR = %2\\n" "QCA_FRAMEWORKDIR = %3\\n" "\\n" "CONFIG *= qt\\n" "\\n" "LINKAGE =\\n" "QCA_NAME = qca-qt5\\n" "\\n" "!isEmpty(QCA_FRAMEWORKDIR): {\\n" " framework_dir = \$\$QCA_FRAMEWORKDIR\\n" " exists(\$\$framework_dir/\$\${QCA_NAME}.framework) {\\n" " #QMAKE_FRAMEWORKPATH *= \$\$framework_dir\\n" " LIBS *= -F\$\$framework_dir\\n" " INCLUDEPATH += \$\$framework_dir/\$\${QCA_NAME}.framework/Headers\\n" " LINKAGE = -framework \$\${QCA_NAME}\\n" " }\\n" "}\\n" "\\n" "# else, link normally\\n" "isEmpty(LINKAGE) {\\n" " !isEmpty(QCA_INCDIR):INCLUDEPATH += \$\$QCA_INCDIR/QtCrypto\\n" " !isEmpty(QCA_LIBDIR):LIBS += -L\$\$QCA_LIBDIR\\n" " LINKAGE = -l\$\${QCA_NAME}\\n" " CONFIG(debug, debug|release) {\\n" " windows:LINKAGE = -l\$\${QCA_NAME}d\\n" " mac:LINKAGE = -l\$\${QCA_NAME}_debug\\n" " }\\n" "}\\n" "\\n" "LIBS += \$\$LINKAGE\\n") .arg(incdir, libdir, frameworkdir); return out; } // set either libdir or frameworkdir, but not both static bool qca_try(Conf *conf, const QString &incdir, const QString &libdir, const QString &frameworkdir, bool release, bool debug, QString *_prf) { QString proextra; QString prf; if (!incdir.isEmpty() || !libdir.isEmpty() || !frameworkdir.isEmpty()) { prf = internal_crypto_prf(conf->escapePath(incdir), conf->escapePath(libdir), frameworkdir); } else { prf = "CONFIG += crypto\\n"; } proextra = "CONFIG += qt\\n" "CONFIG -= debug_and_release debug release\\n" "QT -= gui\\n"; proextra += prf; QString str = "#include \\n" "\\n" "int main()\\n" "{\\n" " unsigned long x = QCA_VERSION;\\n" " if(x >= 0x020000 && x < 0x030000) return 0; else return 1;\\n" "}\\n"; // test desired versions, potentially both release and debug if (release) { int ret; if (!conf->doCompileAndLink(str, QStringList(), QString(), proextra + "CONFIG += release\\n", &ret) || ret != 0) return false; } if (debug) { int ret; if (!conf->doCompileAndLink(str, QStringList(), QString(), proextra + "CONFIG += debug\\n", &ret) || ret != 0) return false; } *_prf = prf; return true; } static bool qca_try_lib(Conf *conf, const QString &incdir, const QString &libdir, bool release, bool debug, QString *prf) { return qca_try(conf, incdir, libdir, QString(), release, debug, prf) || qca_try(conf, incdir + "/Qca-qt5", libdir, QString(), release, debug, prf); } static bool qca_try_framework(Conf *conf, const QString &frameworkdir, bool release, bool debug, QString *prf) { return qca_try(conf, QString(), QString(), frameworkdir, release, debug, prf); } static bool qca_try_ext_prf(Conf *conf, bool release, bool debug, QString *prf) { return qca_try(conf, QString(), QString(), QString(), release, debug, prf); } //---------------------------------------------------------------------------- // qc_qca //---------------------------------------------------------------------------- class qc_qca : public ConfObj { public: qc_qca(Conf *c) : ConfObj(c) { } QString name() const { return "QCA >= 2.0"; } QString shortname() const { return "qca"; } bool exec() { // get the build mode #ifdef QC_BUILDMODE bool release = qc_buildmode_release; bool debug = qc_buildmode_debug; #else // else, default to just release mode bool release = true; bool debug = false; #endif QString qca_incdir, qca_libdir, qca_crypto_prf; qca_incdir = conf->getenv("QC_WITH_QCA_INC"); qca_libdir = conf->getenv("QC_WITH_QCA_LIB"); #if defined(Q_OS_MAC) if (!qca_libdir.isEmpty() && qca_try_framework(conf, qca_libdir, release, debug, &qca_crypto_prf)) { conf->addExtra(qca_crypto_prf); return true; } #endif if (!qca_incdir.isEmpty() && !qca_libdir.isEmpty() && qca_try_lib(conf, qca_incdir, qca_libdir, release, debug, &qca_crypto_prf)) { conf->addExtra(qca_crypto_prf); return true; } if (qca_try_ext_prf(conf, release, debug, &qca_crypto_prf)) { conf->addExtra(qca_crypto_prf); return true; } QStringList incs; QString version, libs, other; if (conf->findPkgConfig("qca2-qt5", VersionMin, "2.0.0", &version, &incs, &libs, &other)) { for (int n = 0; n < incs.count(); ++n) conf->addIncludePath(incs[n]); if (!libs.isEmpty()) conf->addLib(libs); return true; } QStringList prefixes; #ifndef Q_OS_WIN prefixes += "/usr"; prefixes += "/usr/local"; #endif QString prefix = conf->getenv("PREFIX"); if (!prefix.isEmpty()) { prefixes += prefix; } for (int n = 0; n < prefixes.count(); ++n) { const QString &prefix = prefixes[n]; if (qca_try_lib(conf, prefix + "/include", prefix + "/lib", release, debug, &qca_crypto_prf)) { conf->addExtra(qca_crypto_prf); return true; } } return false; } }; #line 1 "zlib.qcm" /* -----BEGIN QCMOD----- name: zlib arg: with-zlib-inc=[path],Path to zlib include files arg: with-zlib-lib=[path],Path to zlib library files -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_zlib //---------------------------------------------------------------------------- class qc_zlib : public ConfObj { public: qc_zlib(Conf *c) : ConfObj(c) { } QString name() const { return "zlib"; } QString shortname() const { return "zlib"; } bool exec() { QStringList incs; QString version, libs, other; QString s; if (!conf->findPkgConfig("zlib", VersionAny, "", &version, &incs, &libs, &other)) { s = conf->getenv("QC_WITH_ZLIB_INC"); if (!s.isEmpty()) { if (!conf->checkHeader(s, "zlib.h")) return false; } else { if (!conf->findHeader("zlib.h", QStringList(), &s)) return false; } QStringList libNames = QStringList() << "z"; QString libName; #ifdef Q_OS_WIN libNames << (qc_buildmode_debug ? "zlibd" : "zlib"); #endif for (;;) { s = conf->getenv("QC_WITH_ZLIB_LIB"); if (!s.isEmpty()) { for (const QString l : libNames) if (conf->checkLibrary(s, l)) { libName = l; break; } } else { for (const QString l : libNames) if (conf->findLibrary(l, &s)) { libName = l; break; } } if (!libName.isEmpty()) break; return false; } if (!s.isEmpty()) { libs = QString("-L%1 -l%2").arg(s, libName); } else { libs = s.isEmpty() ? QString("-l") + libName : QString("-L%1 -l%2").arg(s, libName); } } for (const QString &inc : incs) { conf->addIncludePath(inc); } conf->addLib(libs); #ifdef Q_OS_WIN // HACK: on windows, always use psi's bundled minizip conf->addExtra("CONFIG += psi-minizip"); return true; #else incs.clear(); libs.clear(); if (!conf->findPkgConfig("minizip", VersionAny, "", &version, &incs, &libs, &other)) { s = conf->getenv("QC_WITH_MINIZIP_INC"); if ((!s.isEmpty() && conf->checkHeader(s, "unzip.h")) || (s.isEmpty() && conf->findHeader("unzip.h", QStringList(), &s))) { incs.append(s); } s = conf->getenv("QC_WITH_MINIZIP_LIB"); if ((!s.isEmpty() && conf->checkLibrary(s, "minizip")) || (s.isEmpty() && conf->findLibrary("minizip", &s))) { libs = s.isEmpty() ? "-lminizip" : QString("-L%1 -lminizip").arg(s); } } if (!incs.isEmpty() && !libs.isEmpty()) { for (const QString &inc : incs) { conf->addIncludePath(inc); } conf->addLib(libs); } else { conf->addExtra("CONFIG += psi-minizip"); conf->addExtra("bsd:DEFINES += IOAPI_NO_64"); } return true; #endif } }; #line 1 "qjdns.qcm" /* -----BEGIN QCMOD----- name: jdns arg: with-qjdns-inc=[path],Path to QJDns include files arg: with-qjdns-lib=[path],Path to QJDns library files -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_qjdns //---------------------------------------------------------------------------- class qc_qjdns : public ConfObj { public: qc_qjdns(Conf *c) : ConfObj(c) { } QString name() const { return "QJDns"; } QString shortname() const { return "qjdns"; } QString resultString() const { #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) return "Disabled for Qt5 and above"; #else return ConfObj::resultString(); #endif } bool exec() { #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) return true; // hack. TODO: figure out how to force jdns #endif conf->addExtra("CONFIG += need_jdns"); #if defined Q_OS_WIN || defined Q_OS_MAC // HACK: on Windows and Mac OS X, always use psi's bundled qjdns conf->addExtra("CONFIG += iris-qjdns"); return true; #else QStringList incs; QString version, libs, other; QString s; bool found = conf->findPkgConfig("qjdns-qt5", VersionMin, "2.0.3", &version, &incs, &libs, &other); if (!found && !conf->findPkgConfig("qjdns", VersionMin, "2.0.0", &version, &incs, &libs, &other)) { s = conf->getenv("QC_WITH_QJDNS_INC"); if ((!s.isEmpty() && conf->checkHeader(s, "qjdns.h")) || (s.isEmpty() && conf->findHeader("qjdns.h", QStringList(), &s))) { incs.append(s); } s = conf->getenv("QC_WITH_QJDNS_LIB"); if ((!s.isEmpty() && conf->checkLibrary(s, "qjdns")) || (s.isEmpty() && conf->findLibrary("qjdns", &s))) { libs = s.isEmpty() ? "-lqjdns -ljdns" : QString("-L%1 -lqjdns -ljdns").arg(s); } } if (!incs.isEmpty() && !libs.isEmpty()) { for (const QString &inc : incs) { conf->addIncludePath(inc); } conf->addLib(libs); conf->addExtra("CONFIG += ext-qjdns"); } return true; #endif } }; #line 1 "x11.qcm" /* -----BEGIN QCMOD----- name: Xorg X11 -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_x11 //---------------------------------------------------------------------------- class qc_x11 : public ConfObj { public: qc_x11(Conf *c) : ConfObj(c) { } QString name() const { return "Xorg X11"; } QString shortname() const { return "x11"; } QString checkString() const { return QString(); } bool exec() { QString str = "int main()\\n" "{\\n" " return 0;\\n" "}\\n"; QString proextra = "CONFIG += x11\\n"; #if QT_VERSION >= 0x050000 && defined Q_OS_LINUX proextra += "LIBS += -lxcb\\n"; proextra += "QT += x11extras\\n"; #endif return conf->doCompileAndLink(str, QStringList(), QString(), proextra, nullptr); } }; #line 1 "universal.qcm" /* -----BEGIN QCMOD----- name: Mac OS X universal binary support -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_universal //---------------------------------------------------------------------------- class qc_universal : public ConfObj { public: qc_universal(Conf *c) : ConfObj(c) { } QString name() const { return "universal binary support"; } QString shortname() const { return "universal"; } QString checkString() const { return QString(); } bool exec() { #ifdef Q_OS_MAC conf->addExtra("CONFIG += qc_universal"); #endif return true; } }; #line 1 "qdbus.qcm" /* -----BEGIN QCMOD----- name: QDBUS -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_qdbus //---------------------------------------------------------------------------- #ifdef Q_OS_WIN QC_SILENT_NOT_FOUND(qdbus) #else // id, shortname, name, extraConfIfFound QC_FIND_QT_COMP_E(dbus, qdbus, QtDbus, "CONFIG += dbus") #endif #line 1 "keychain.qcm" /* -----BEGIN QCMOD----- name: Qt Keychain -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_keychain //---------------------------------------------------------------------------- // id, shortname, name, extraConfIfFound QC_FIND_QT_COMP_BASE(Qt5Keychain, keychain, Qt Keychain, "CONFIG += keychain", _base) class qc_keychain : public qc_keychain_base { public: qc_keychain(Conf *c) : qc_keychain_base(c) { } bool exec() { if (qc_keychain_base::exec()) { conf->addExtra("CONFIG += keychain_with_qtmodule"); // some systems with keychain < 0.10.0 have invalid Qt module for Qt5Keychain. let's check. QString str = "int main()\\n" "{\\n" " QKeychain::Job().settings();\\n" " return 0;\\n" "}\\n"; if (conf->doCompileAndLink(str, QStringList(), QString(), "QT += Qt5Keychain")) return true; } // try to find by path QString s; if (!conf->findHeader("qt5keychain/keychain.h", QStringList(), &s)) { qWarning("keychain header qt5keychain/keychain.h is not found"); return false; } QStringList libnames; #ifdef _MSC_VER #ifdef QC_BUILDMODE libnames += (qc_buildmode_debug ? "libqt5keychaind" : "libqt5keychain"); // possibly static libnames += (qc_buildmode_debug ? "qt5keychaind" : "qt5keychain"); // possibly dynamic #else libnames << "libqt5keychain" << "qt5keychain"; #endif #else libnames << "qt5keychain"; #endif QString libName; for (const QString l : libnames) if (conf->findLibrary(l, &s)) { libName = l; break; } if (libName.isEmpty()) { qWarning("keychain library qt5keychain is not found"); return false; } conf->addLib(QString("-L%1 -l%2").arg(s, libName)); // header and library were found by default paths. lets just add extra conf->addExtra(extraConfIfFound); return true; } }; #line 1 "qtmultimedia.qcm" /* -----BEGIN QCMOD----- name: QtMultimedia -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_qtmultimedia //---------------------------------------------------------------------------- // id, shortname, name QC_FIND_QT_COMP(multimedia, qtmultimedia, QtMultimedia) #line 1 "qtconcurrent.qcm" /* -----BEGIN QCMOD----- name: QtConcurrent -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_qtconcurrent //---------------------------------------------------------------------------- // id, shortname, name QC_FIND_QT_COMP(concurrent, qtconcurrent, QtConcurrent) #line 1 "qtsql.qcm" /* -----BEGIN QCMOD----- name: QtSql -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_qtsql //---------------------------------------------------------------------------- // id, shortname, name QC_FIND_QT_COMP(sql, qtsql, QtSql) #line 1 "qtwidgets.qcm" /* -----BEGIN QCMOD----- name: QtWidgets -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_qtwidgets //---------------------------------------------------------------------------- // id, shortname, name QC_FIND_QT_COMP(widgets, qtwidgets, QtWidgets) #line 1 "qtnetwork.qcm" /* -----BEGIN QCMOD----- name: QtNetwork -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_qtnetwork //---------------------------------------------------------------------------- // id, shortname, name QC_FIND_QT_COMP(network, qtnetwork, QtNetwork) #line 1 "qtsvg.qcm" /* -----BEGIN QCMOD----- name: QtSvg -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_qtsvg //---------------------------------------------------------------------------- // id, shortname, name QC_FIND_QT_COMP(network, qtsvg, QtSvg) #line 1 "qtxml.qcm" /* -----BEGIN QCMOD----- name: QtXml -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_qtxml //---------------------------------------------------------------------------- // id, shortname, name QC_FIND_QT_COMP(xml, qtxml, QtXml) #line 1 "webkit.qcm" /* -----BEGIN QCMOD----- name: webkit arg: with-webkit=[type],type of webkit QtWebKit/QtWebEngine -----END QCMOD----- */ QString qc_webkit_type; //---------------------------------------------------------------------------- // qc_webkit //---------------------------------------------------------------------------- class qc_webkit : public ConfObj { QString webkitType; public: qc_webkit(Conf *c) : ConfObj(c) { } QString name() const { return "QtWebKit"; } QString shortname() const { return "webkit"; } bool exec() { if (!conf->getenv("QC_DISABLE_webkit").isEmpty()) { webkitType = "disabled"; return false; } QStringList tryList; QString wt = conf->getenv("QC_WITH_WEBKIT").toLower(); if (wt.isEmpty() || !(wt == "qtwebkit" || wt == "qtwebengine")) { #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) tryList << "qtwebengine" << "qtwebkit"; #else tryList << "qtwebkit"; #endif } else { #if QT_VERSION < QT_VERSION_CHECK(5, 6, 0) if (wt == "qtwebengine") { webkitType = "unsupported"; return false; } #endif tryList << wt; } QString qt; for (const QString &wt : tryList) { if (wt == "qtwebengine") { webkitType = "QtWebEngine"; qt = "webenginewidgets webchannel"; } else { webkitType = "QtWebKit"; qt = "webkit webkitwidgets"; } QString proextra = "CONFIG += qt webkit\\n"; proextra += "QT += " + qt + "\\n"; QString str = "\\n" "int main()\\n" "{\\n" " return 0;\\n" "}\\n"; int ret; if (!conf->doCompileAndLink(str, QStringList(), QString(), proextra, &ret)) continue; if (ret != 0) continue; conf->addExtra("CONFIG += " + wt); qc_webkit_type = wt; return true; } webkitType = "not found"; return false; } QString resultString() const { return webkitType; } }; #line 1 "http-parser.qcm" /* -----BEGIN QCMOD----- name: hunspell arg: with-http-parser-inc=[path],Path to HTTP Parser include files arg: with-http-parser-lib=[path],Path to HTTP Parser library files arg: bundled-http-parser,Build with bundled HTTP Parser -----END QCMOD----- */ #define QC_HTTP_PARSER bool qc_use_http_parser = false; //---------------------------------------------------------------------------- // qc_http_parser //---------------------------------------------------------------------------- class qc_http_parser : public ConfObj { bool use_bundled; public: qc_http_parser(Conf *c) : ConfObj(c), use_bundled(false) { } QString name() const { return "HTTP Parser"; } QString shortname() const { return "httpparser"; } QString resultString() const { if (qc_use_http_parser) { if (use_bundled) return "bundled"; return ConfObj::resultString(); } return "Not needed w/o webengine"; } bool exec() { #ifdef Q_OS_UNIX qc_use_http_parser = true; #else qc_use_http_parser = (qc_webkit_type == "qtwebengine"); #endif if (!qc_use_http_parser) return true; // http parser is not needed. success. QString s; if (conf->getenv("QC_BUNDLED_HTTP_PARSER") == "Y") { goto use_bundled_lbl; } #if !(defined(Q_OS_WIN) || defined(Q_OS_MAC)) s = conf->getenv("QC_WITH_HTTP_PARSER_INC"); if (!s.isEmpty()) { if (!conf->checkHeader(s, "http_parser.h")) { qWarning("HTTP Parser includes not found!"); return false; } conf->addIncludePath(s); } else { QStringList sl; sl += "/usr/include"; sl += "/usr/local/include"; sl += "/sw/include"; if (!conf->findHeader("http_parser.h", sl, &s)) { qWarning("HTTP Parser includes not found. Use bundled"); goto use_bundled_lbl; } conf->addIncludePath(s); } s = conf->getenv("QC_WITH_HTTP_PARSER_LIB"); if (!s.isEmpty()) { if (!conf->checkLibrary(s, "http_parser")) { qWarning("HTTP Parser libraries not found!"); return false; } conf->addLib(QString("-L") + s); } else { if (!conf->findLibrary("http_parser", &s)) { qWarning("HTTP Parser libraries not found. Use bundled"); goto use_bundled_lbl; } if (!s.isEmpty()) conf->addLib(QString("-L") + s); } conf->addLib("-lhttp_parser"); return true; #endif use_bundled_lbl: use_bundled = true; conf->addExtra("CONFIG += bundled_http_parser"); return true; } }; #line 1 "b2.qcm" /* -----BEGIN QCMOD----- name: libb2 arg: with-b2-inc=[path],Path to libb2 include files arg: with-b2-lib=[path],Path to libb2 library or framework files -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_b2 //---------------------------------------------------------------------------- class qc_b2 : public ConfObj { bool use_system = false; public: qc_b2(Conf *c) : ConfObj(c) { } QString name() const { return "LibB2"; } QString shortname() const { return "libb2"; } QString resultString() const { return use_system ? "system" : "bundled"; } bool exec() { QString b2_incdir, b2_libdir; b2_incdir = conf->getenv("QC_WITH_B2_INC"); b2_libdir = conf->getenv("QC_WITH_B2_LIB"); if (b2_incdir.isEmpty() && b2_libdir.isEmpty()) { if (!checkCustomDirs(b2_incdir, b2_libdir)) { printf("b2 search paths provided but library is not found there. use bundled"); return true; } use_system = true; } else { QStringList incs; QString version, libs, other; if (conf->findPkgConfig("libb2", VersionAny, QString::null, &version, &incs, &libs, &other)) { for (int n = 0; n < incs.count(); ++n) conf->addIncludePath(incs[n]); if (!libs.isEmpty()) conf->addLib(libs); use_system = true; } } if (use_system) { conf->addExtra("CONFIG += bundled_blake2"); } return true; } bool checkCustomDirs(QString &b2_incdir, QString &b2_libdir) { if ((b2_incdir.isEmpty() && !conf->findHeader("blake2.h", QStringList(), &b2_incdir)) || (!b2_incdir.isEmpty() && !conf->checkHeader(b2_incdir, "blake2.h"))) { printf("Headers not found!\\n"); return false; } if ((!b2_libdir.isEmpty() && conf->checkLibrary(b2_libdir, "b2")) || (b2_libdir.isEmpty() && conf->findLibrary("b2", &b2_libdir))) { conf->addLib(b2_libdir.isEmpty() ? "-lb2" : QString("-L%1 -lb2").arg(b2_libdir)); conf->addIncludePath(b2_incdir); return true; } printf("Libraries not found!\\n"); return false; } }; #line 1 "growl.qcm" /* -----BEGIN QCMOD----- name: Growl arg: with-growl=[path],Path to the Growl framework -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_growl //---------------------------------------------------------------------------- class qc_growl : public ConfObj { public: qc_growl(Conf *c) : ConfObj(c) { } QString name() const { return "Growl"; } QString shortname() const { return "growl"; } #ifndef Q_OS_MAC QString checkString() const { return QString(); } #endif // TODO: This should go into ConfObj bool checkFramework(const QString &path, const QString &name) { QString str = "int main()\\n" "{\\n" " return 0;\\n" "}\\n"; QString extra; if (!path.isEmpty()) extra += QString("-F") + path + ' '; extra += QString("-framework ") + name; if (!conf->doCompileAndLink(str, QStringList(), extra, "", nullptr)) return false; return true; } // TODO: This should go into ConfObj void addFrameworkPath(const QString &str) { conf->addExtra("QMAKE_CXXFLAGS += -F" + str); conf->addExtra("QMAKE_OBJECTIVE_CFLAGS += -F" + str); conf->addLib("-F" + str); } bool exec() { #ifdef Q_OS_MAC QString growl_path = conf->getenv("QC_WITH_GROWL"); if (!checkFramework(growl_path, "Growl")) return false; if (!growl_path.isEmpty()) addFrameworkPath(growl_path); conf->addLib("-framework Growl"); conf->addDefine("HAVE_GROWL"); return true; #else return false; #endif } }; #line 1 "whiteboarding.qcm" /* -----BEGIN QCMOD----- name: White Board support -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_whiteboarding //---------------------------------------------------------------------------- static bool qc_qtsvg_found = false; class qc_whiteboarding : public QtCompFinder { public: qc_whiteboarding(Conf *c) : QtCompFinder("svg", "whiteboarding", "White Board support", "", c) { } QString resultString() const { if (!qc_qtsvg_found) { return "QtSVG is not found"; } return ConfObj::resultString(); } bool exec() { if (!QtCompFinder::exec()) { return false; } qc_qtsvg_found = true; conf->addDefine("WHITEBOARDING"); // Finish conf->addExtra("CONFIG += whiteboarding"); qWarning(""); qWarning(""); qWarning(" !!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!"); qWarning(" WHITEBOARDING SUPPORT IS STILL UNFINISHED !!!"); qWarning(" USE AT YOUR OWN RISK !!!"); return true; } }; #line 1 "xss.qcm" /* -----BEGIN QCMOD----- name: the XScreenSaver extension -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_xss //---------------------------------------------------------------------------- class qc_xss : public ConfObj { public: qc_xss(Conf *c) : ConfObj(c) { } QString name() const { return "the XScreenSaver extension"; } QString shortname() const { return "xss"; } #ifdef Q_OS_WIN QString checkString() const { return QString(); } #endif bool exec() { #ifdef Q_OS_WIN // skip XSS support on windows return false; #else QString str = "#include\\n" "#include\\n" "#include\\n" "\\n" "int main()\\n" "{\\n" " XScreenSaverQueryExtension(NULL, NULL, NULL);\\n" " return 0;\\n" "}\\n"; QString proextra = "CONFIG += x11\\n"; if (!conf->doCompileAndLink(str, QStringList(), "-lXss", proextra, NULL)) { if (!conf->doCompileAndLink(str, QStringList(), QString(), proextra, NULL)) { return false; } } else { conf->addLib("-lXss"); } conf->addDefine("HAVE_XSS"); return true; #endif } }; #line 1 "aspell.qcm" /* -----BEGIN QCMOD----- name: aspell arg: with-aspell-inc=[path],Path to Aspell include files arg: with-aspell-lib=[path],Path to Aspell library files -----END QCMOD----- */ #define QC_ASPELL bool qc_aspell_have = false; QStringList qc_aspell_defs; QStringList qc_aspell_incs; QStringList qc_aspell_libs; //---------------------------------------------------------------------------- // qc_aspell //---------------------------------------------------------------------------- class qc_aspell : public ConfObj { public: qc_aspell(Conf *c) : ConfObj(c) { } QString name() const { return "aspell"; } QString shortname() const { return "aspell"; } // no output QString checkString() const { return QString(); } bool exec() { // on mac, always use built-in spell check #ifdef Q_OS_MAC return false; #else qc_aspell_have = false; qc_aspell_defs.clear(); qc_aspell_incs.clear(); qc_aspell_libs.clear(); QString s; #ifdef Q_OS_WIN s = conf->getenv("QC_WITH_ASPELL_INC"); if (!s.isEmpty()) { if (!conf->checkHeader(s, "aspell.h")) { conf->debug("Aspell includes not found!"); return false; } qc_aspell_incs += s; } else return false; QString a_lib = conf->getenv("QC_WITH_ASPELL_LIB"); if (a_lib.isEmpty()) return false; QStringList libnames; libnames += "aspell-15"; libnames += "aspell"; bool success; QString libname_success; for (const QString &libname : libnames) { conf->debug(QString("Trying %1").arg(libname)); if (conf->checkLibrary(a_lib, libname)) { success = true; libname_success = libname; break; } } if (!success) return false; qc_aspell_defs += "HAVE_ASPELL"; qc_aspell_libs += QString("-L") + a_lib; qc_aspell_libs += QString("-l") + libname_success; qc_aspell_have = true; #else s = conf->getenv("QC_WITH_ASPELL_INC"); if (!s.isEmpty()) { if (!conf->checkHeader(s, "aspell.h")) { conf->debug("Aspell includes not found!"); return false; } qc_aspell_incs += s; } else { QStringList sl; sl += "/usr/include"; sl += "/usr/local/include"; sl += "/sw/include"; if (!conf->findHeader("aspell.h", sl, &s)) { conf->debug("Aspell includes not found!"); return false; } qc_aspell_incs += s; } s = conf->getenv("QC_WITH_ASPELL_LIB"); if (!s.isEmpty()) { if (!conf->checkLibrary(s, "aspell")) { conf->debug("Aspell libraries not found!"); return false; } qc_aspell_libs += QString("-L") + s; } else { if (!conf->findLibrary("aspell", &s)) { conf->debug("Aspell libraries not found!"); return false; } if (!s.isEmpty()) qc_aspell_libs += QString("-L") + s; } qc_aspell_defs += "HAVE_ASPELL"; qc_aspell_libs += "-laspell"; qc_aspell_have = true; #endif return true; #endif } }; #line 1 "enchant.qcm" /* -----BEGIN QCMOD----- name: enchant -----END QCMOD----- */ #define QC_ENCHANT bool qc_enchant_have = false; QStringList qc_enchant_defs; QStringList qc_enchant_incs; QStringList qc_enchant_libs; //---------------------------------------------------------------------------- // qc_enchant //---------------------------------------------------------------------------- class qc_enchant : public ConfObj { public: qc_enchant(Conf *c) : ConfObj(c) { } QString name() const { return "enchant"; } QString shortname() const { return "enchant"; } // no output QString checkString() const { return QString(); } bool exec() { // on mac, always use built-in spell check #ifdef Q_OS_MAC return false; #endif qc_enchant_have = false; qc_enchant_defs.clear(); qc_enchant_incs.clear(); qc_enchant_libs.clear(); QStringList incs; QString version, libs, other; if (!conf->findPkgConfig("enchant", VersionMin, "1.3.0", &version, &incs, &libs, &other)) if (conf->findPkgConfig("enchant-2", VersionMin, "2.0.0", &version, &incs, &libs, &other)) { qc_enchant_defs += "HAVE_ENCHANT2"; } else { return false; } qc_enchant_defs += "HAVE_ENCHANT"; qc_enchant_incs += incs; qc_enchant_libs += libs; qc_enchant_have = true; return true; } }; #line 1 "hunspell.qcm" /* -----BEGIN QCMOD----- name: hunspell arg: with-hunspell-inc=[path],Path to Hunspell include files arg: with-hunspell-lib=[path],Path to Hunspell library files -----END QCMOD----- */ #define QC_HUNSPELL bool qc_hunspell_have = false; QStringList qc_hunspell_defs; QStringList qc_hunspell_incs; QStringList qc_hunspell_libs; //---------------------------------------------------------------------------- // qc_hunspell //---------------------------------------------------------------------------- class qc_hunspell : public ConfObj { public: qc_hunspell(Conf *c) : ConfObj(c) { } QString name() const { return "hunspell"; } QString shortname() const { return "hunspell"; } // no output QString checkString() const { return QString(); } bool exec() { // on mac, always use built-in spell check #ifdef Q_OS_MAC return false; #else qc_hunspell_have = false; qc_hunspell_defs.clear(); qc_hunspell_incs.clear(); qc_hunspell_libs.clear(); #ifdef Q_OS_WIN QStringList libnames; #ifdef _MSC_VER qc_hunspell_defs += "HUNSPELL_STATIC"; // at least it's static by default when their msvc sln is in use. #ifdef QC_BUILDMODE libnames += (qc_buildmode_debug ? "libhunspelld" : "libhunspell"); // possibly static libnames += (qc_buildmode_debug ? "hunspelld" : "hunspell"); // possibly dynamic #else libnames << "libhunspell" << "hunspell"; #endif #else // mingw? libnames << "hunspell-1.3" << "hunspell"; #endif QString libs, incs; for (const QString libname : libnames) if (conf->findSimpleLibrary("QC_WITH_HUNSPELL_INC", "QC_WITH_HUNSPELL_LIB", "hunspell/hunspell.hxx", libname, &incs, &libs)) { break; } if (libs.isEmpty()) { printf("hunspell library is not found"); return false; } conf->addLib(libs); qc_hunspell_defs += "HAVE_HUNSPELL"; qc_hunspell_libs += libs; qc_hunspell_incs += incs + "/hunspell"; qc_hunspell_have = true; #else // Q_OS_WIN. unix below qc_hunspell_have = false; qc_hunspell_defs.clear(); qc_hunspell_incs.clear(); qc_hunspell_libs.clear(); QStringList incs; QString version, libs, other; if (!conf->findPkgConfig("hunspell", VersionMin, "1.3.0", &version, &incs, &libs, &other)) return false; qc_hunspell_defs += "HAVE_HUNSPELL"; qc_hunspell_incs += incs; qc_hunspell_libs += libs; qc_hunspell_have = true; #endif // Q_OS_WIN return true; #endif // Q_OS_MAC } }; #line 1 "spell.qcm" /* -----BEGIN QCMOD----- name: spellcheck engine -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_spell //---------------------------------------------------------------------------- class qc_spell : public ConfObj { public: QString engine; qc_spell(Conf *c) : ConfObj(c) { } QString name() const { return "spellcheck engine"; } QString shortname() const { return "spell"; } bool exec() { // on mac, always use built-in spell check #ifdef Q_OS_MAC engine = "using Mac built-in"; return true; #endif bool have = false; QStringList defs, incs, libs; #ifdef QC_ENCHANT if (!have && qc_enchant_have) { defs = qc_enchant_defs; incs = qc_enchant_incs; libs = qc_enchant_libs; engine = "enchant"; have = true; } #endif #ifdef QC_ASPELL if (!have && qc_aspell_have) { defs = qc_aspell_defs; incs = qc_aspell_incs; libs = qc_aspell_libs; engine = "aspell"; have = true; } #endif #ifdef QC_HUNSPELL if (!have && qc_hunspell_have) { defs = qc_hunspell_defs; incs = qc_hunspell_incs; libs = qc_hunspell_libs; engine = "hunspell"; have = true; } #endif if (!have) return true; for (int n = 0; n < defs.count(); ++n) conf->addDefine(defs[n]); for (int n = 0; n < incs.count(); ++n) conf->addIncludePath(incs[n]); for (int n = 0; n < libs.count(); ++n) conf->addLib(libs[n]); return true; } QString resultString() const { if (!engine.isEmpty()) return engine; else return "no"; } }; #line 1 "plugins.qcm" /* -----BEGIN QCMOD----- name: Psi Plugin support -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_plugins //---------------------------------------------------------------------------- class qc_plugins : public ConfObj { public: qc_plugins(Conf *c) : ConfObj(c) { } QString name() const { return "Psi Plugin support"; } QString shortname() const { return "plugins"; } bool exec() { QFileInfo fi(qc_getenv("QC_COMMAND")); QString sourceDir = fi.absolutePath(); conf->addDefine("PSI_PLUGINS"); // Finish conf->addExtra("CONFIG += psi_plugins"); QHash vars; #ifdef Q_OS_WIN vars["plugins_dir"] = ""; vars["data_dir"] = ""; vars["include_dir"] = ""; #else vars["plugins_dir"] = conf->getenv("LIBDIR") + "/" + qc_psi_dir_name + "/plugins"; vars["data_dir"] = conf->getenv("DATADIR") + "/" + qc_psi_dir_name; vars["plugins_include_dir"] = conf->getenv("PREFIX") + "/include/" + qc_psi_dir_name + "/plugins"; #endif QStringList psiFeatures; if (!qc_webkit_type.isEmpty()) { psiFeatures << qc_webkit_type; } vars["features"] = psiFeatures.join(" "); psiGenerateFile(sourceDir + "/pluginsconf.pri.in", "pluginsconf-dist.pri", vars); return true; } }; #line 1 "conf.qcm" /* -----BEGIN QCMOD----- name: Psi Configuration -----END QCMOD----- */ #include static QString psiGetVersion() { QString str; { QFile file(sourceDir + "/version"); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream ts(&file); if (!ts.atEnd()) str = ts.readLine(); } if (str.isEmpty()) str = "UnknownVersion"; } QString nowstr = QDateTime::currentDateTime().toString("yyyyMMdd"); str.replace("@@DATE@@", nowstr); return str; } class qc_conf : public ConfObj { public: qc_conf(Conf *c) : ConfObj(c) { } QString name() const { return qc_psiplus ? "Psi+ Configuration" : "Psi Configuration"; } QString shortname() const { return "conf"; } QString checkString() const { return QString(); } bool exec() { QString version = psiGetVersion(); QRegExp verre("^\\\\d+(?:\\\\.\\\\d+)+"); if (verre.indexIn(version) == -1) { conf->debug("Failed to parse version file"); return false; } conf->addExtra(QString("PSI_VERSION = %1").arg(verre.cap(0))); QHash vars; vars["VERSION"] = version; QDir::current().mkpath("mac"); psiGenerateFile(sourceDir + "/mac/Info.plist.in", "mac/Info.plist", vars); vars.clear(); vars["source_dir"] = sourceDir; vars["build_dir"] = QDir::currentPath(); psiGenerateFile(sourceDir + "/.qmake.cache.in", ".qmake.cache", vars); #ifndef Q_OS_WIN conf->addExtra(QString("PSI_LIBDIR=%1/%2").arg(conf->getenv("LIBDIR"), qc_psi_dir_name)); conf->addExtra(QString("PSI_DATADIR=%1/%2").arg(conf->getenv("DATADIR"), qc_psi_dir_name)); #endif QDir(".").mkdir("src"); auto now = QDateTime::currentDateTime(); vars = { { "CLIENT_NAME", qc_psiplus ? "Psi+" : "Psi" }, { "CLIENT_SNAME", qc_psiplus ? "psi+" : "psi" }, { "CLIENT_CAPS_NODE", qc_psiplus ? "https://psi-plus.com" : "https://psi-im.org" }, { "PRODUCTION", "" }, #ifndef Q_OS_WIN { "PSI_LIBDIR", conf->getenv("LIBDIR") + "/" + qc_psi_dir_name }, { "PSI_DATADIR", conf->getenv("DATADIR") + "/" + qc_psi_dir_name }, #endif { "PSI_VERSION", version }, { "PSI_REVISION", "" }, { "PSI_PLUS_REVISION", "" }, { "PSI_COMPILATION_DATE", now.toString("yyyy-MM-dd") }, { "PSI_COMPILATION_TIME", now.toString("HH:mm:ss") }, }; psiGenerateFile(sourceDir + "/src/config.h.in", "src/config.h", vars); vars = { { "PSILOGO_PREFIX", qc_psiplus ? "psiplus/" : "" }, { "MAIN_ICON", qc_psiplus ? "psiplus_icon.png" : "psimain.png" } }; auto buildIconDir = QDir::current().absoluteFilePath("iconsets"); copyPath(sourceDir + "/iconsets", buildIconDir); psiGenerateFile(sourceDir + "/iconsets.qrc.in", "iconsets.qrc", vars); psiGenerateFile(sourceDir + "/icondef.xml.in", buildIconDir + "/system/default/icondef.xml", vars); conf->addDefine("HAVE_CONFIG"); return true; } }; EOT cat >"$1/modules_new.cpp" <required = true; o->disabled = false; o = new qc_buildmodeapp(conf); o->required = true; o->disabled = false; o = new qc_idn(conf); o->required = true; o->disabled = false; o = new qc_qca(conf); o->required = true; o->disabled = false; o = new qc_zlib(conf); o->required = true; o->disabled = false; o = new qc_qjdns(conf); o->required = true; o->disabled = false; o = new qc_x11(conf); o->required = true; o->disabled = false; o = new qc_universal(conf); o->required = false; o->disabled = true; o = new qc_qdbus(conf); o->required = false; o->disabled = false; o = new qc_keychain(conf); o->required = false; o->disabled = false; o = new qc_qtmultimedia(conf); o->required = true; o->disabled = false; o = new qc_qtconcurrent(conf); o->required = true; o->disabled = false; o = new qc_qtsql(conf); o->required = true; o->disabled = false; o = new qc_qtwidgets(conf); o->required = true; o->disabled = false; o = new qc_qtnetwork(conf); o->required = true; o->disabled = false; o = new qc_qtsvg(conf); o->required = true; o->disabled = false; o = new qc_qtxml(conf); o->required = true; o->disabled = false; o = new qc_webkit(conf); o->required = false; o->disabled = true; o = new qc_http_parser(conf); o->required = true; o->disabled = false; o = new qc_b2(conf); o->required = true; o->disabled = false; o = new qc_growl(conf); o->required = false; o->disabled = false; o = new qc_whiteboarding(conf); o->required = false; o->disabled = true; o = new qc_xss(conf); o->required = false; o->disabled = false; o = new qc_aspell(conf); o->required = false; o->disabled = false; o = new qc_enchant(conf); o->required = false; o->disabled = false; o = new qc_hunspell(conf); o->required = false; o->disabled = false; o = new qc_spell(conf); o->required = true; o->disabled = false; o = new qc_plugins(conf); o->required = false; o->disabled = false; o = new qc_conf(conf); o->required = true; o->disabled = false; EOT cat >"$1/conf4.h" < class Conf; enum VersionMode { VersionMin, VersionExact, VersionMax, VersionAny }; // ConfObj // // Subclass ConfObj to create a new configuration module. class ConfObj { public: Conf *conf; bool required; bool disabled; bool success; ConfObj(Conf *c); virtual ~ConfObj(); // long or descriptive name of what is being checked/performed // example: "KDE >= 3.3" virtual QString name() const = 0; // short name // example: "kde" virtual QString shortname() const = 0; // string to display during check // default: "Checking for [name] ..." virtual QString checkString() const; // string to display after check // default: "yes" or "no", based on result of exec() virtual QString resultString() const; // this is where the checking code goes virtual bool exec() = 0; }; // Conf // // Interact with this class from your ConfObj to perform detection // operations and to output configuration parameters. class Conf { public: bool debug_enabled; QString qmake_path; QString qmakespec; QString maketool; QString DEFINES; QStringList INCLUDEPATH; QStringList LIBS; QString extra; QList list; QMap vars; Conf(); ~Conf(); QString getenv(const QString &var); QString qvar(const QString &s); QString normalizePath(const QString &s) const; QString escapeQmakeVar(const QString &s) const; inline QString escapePath(const QString &s) /* prepare fs path for qmake file */ { return escapeQmakeVar(normalizePath(s)); } QString escapedIncludes() const; QString escapedLibs() const; bool exec(); void debug(const QString &s); QString expandIncludes(const QString &inc); QString expandLibs(const QString &lib); int doCommand(const QString &s, QByteArray *out = 0); int doCommand(const QString &prog, const QStringList &args, QByteArray *out = 0); bool doCompileAndLink(const QString &filedata, const QStringList &incs, const QString &libs, const QString &proextra, int *retcode = 0); bool checkHeader(const QString &path, const QString &h); bool findHeader(const QString &h, const QStringList &ext, QString *inc); bool checkLibrary(const QString &path, const QString &name); bool findLibrary(const QString &name, QString *lib); QString findProgram(const QString &prog); bool findSimpleLibrary(const QString &incvar, const QString &libvar, const QString &incname, const QString &libname, QString *incpath, QString *libs); bool findFooConfig(const QString &path, QString *version, QStringList *incs, QString *libs, QString *otherflags); bool findPkgConfig(const QString &name, VersionMode mode, const QString &req_version, QString *version, QStringList *incs, QString *libs, QString *otherflags); void addDefine(const QString &str); void addLib(const QString &str); void addIncludePath(const QString &str); void addExtra(const QString &str); private: bool first_debug; friend class ConfObj; void added(ConfObj *o); }; #endif EOT cat >"$1/conf4.cpp" < #include #include #ifndef PATH_MAX #ifdef Q_OS_WIN #define PATH_MAX 260 #endif #endif class MocTestObject : public QObject { Q_OBJECT public: MocTestObject() {} }; QString qc_getenv(const QString &var) { char *p = ::getenv(var.toLatin1().data()); if (!p) return QString(); return QString(p); } QStringList qc_pathlist() { QStringList list; QString path = qc_getenv("PATH"); if (!path.isEmpty()) { #ifdef Q_OS_WIN list = path.split(';', QString::SkipEmptyParts); #else list = path.split(':', QString::SkipEmptyParts); #endif } #ifdef Q_OS_WIN list.prepend("."); #endif return list; } QString qc_findprogram(const QString &prog) { QString out; QStringList list = qc_pathlist(); for (int n = 0; n < list.count(); ++n) { QFileInfo fi(list[n] + '/' + prog); if (fi.exists() && fi.isExecutable()) { out = fi.filePath(); break; } #ifdef Q_OS_WIN // on windows, be sure to look for .exe if (prog.right(4).toLower() != ".exe") { fi = QFileInfo(list[n] + '/' + prog + ".exe"); if (fi.exists() && fi.isExecutable()) { out = fi.filePath(); break; } } #endif } return out; } QString qc_findself(const QString &argv0) { #ifdef Q_OS_WIN if (argv0.contains('\\\\')) #else if (argv0.contains('/')) #endif return argv0; else return qc_findprogram(argv0); } int qc_run_program_or_command(const QString &prog, const QStringList &args, const QString &command, QByteArray *out, bool showOutput) { if (out) out->clear(); QProcess process; process.setReadChannel(QProcess::StandardOutput); if (!prog.isEmpty()) process.start(prog, args); else if (!command.isEmpty()) process.start(command); else return -1; if (!process.waitForStarted(-1)) return -1; QByteArray buf; while (process.waitForReadyRead(-1)) { buf = process.readAllStandardOutput(); if (out) out->append(buf); if (showOutput) fprintf(stdout, "%s", buf.data()); buf = process.readAllStandardError(); if (showOutput) fprintf(stderr, "%s", buf.data()); } buf = process.readAllStandardError(); if (showOutput) fprintf(stderr, "%s", buf.data()); // calling waitForReadyRead will cause the process to eventually be // marked as finished, so we should not need to separately call // waitForFinished. however, we will do it anyway just to be safe. // we won't check the return value since false could still mean // success (if the process had already been marked as finished). process.waitForFinished(-1); if (process.exitStatus() != QProcess::NormalExit) return -1; return process.exitCode(); } int qc_runcommand(const QString &command, QByteArray *out, bool showOutput) { return qc_run_program_or_command(QString(), QStringList(), command, out, showOutput); } int qc_runprogram(const QString &prog, const QStringList &args, QByteArray *out, bool showOutput) { return qc_run_program_or_command(prog, args, QString(), out, showOutput); } bool qc_removedir(const QString &dirPath) { QDir dir(dirPath); if (!dir.exists()) return false; QStringList list = dir.entryList(); foreach (QString s, list) { if (s == "." || s == "..") continue; QFileInfo fi(dir.filePath(s)); if (fi.isDir()) { if (!qc_removedir(fi.filePath())) return false; } else { if (!dir.remove(s)) return false; } } QString dirName = dir.dirName(); if (!dir.cdUp()) return false; if (!dir.rmdir(dirName)) return false; return true; } // simple command line arguemnts splitter able to understand quoted args. // the splitter removes quotes and unescapes symbols as well. QStringList qc_splitflags(const QString &flags) { QStringList ret; bool searchStart = true; bool inQuotes = false; bool escaped = false; QChar quote, backslash = QLatin1Char('\\\\'); QString buf; #ifdef PATH_MAX buf.reserve(PATH_MAX); #endif for (int i = 0; i < flags.length(); i++) { if (searchStart && flags[i].isSpace()) { continue; } if (searchStart) { searchStart = false; buf.clear(); } if (escaped) { buf += flags[i]; escaped = false; continue; } // buf += flags[i]; if (inQuotes) { if (quote == QLatin1Char('\\'')) { if (flags[i] == quote) { inQuotes = false; continue; } } else { // we are in double quoetes if (flags[i] == backslash && i < flags.length() - 1 && (flags[i + 1] == QLatin1Char('"') || flags[i + 1] == backslash)) { // if next symbol is one of in parentheses ("\\) escaped = true; continue; } } } else { if (flags[i].isSpace()) { ret.append(buf); searchStart = true; buf.clear(); continue; #ifndef Q_OS_WIN /* on windows backslash is just a path separator */ } else if (flags[i] == backslash) { escaped = true; continue; // just add next symbol #endif } else if (flags[i] == QLatin1Char('\\'') || flags[i] == QLatin1Char('"')) { inQuotes = true; quote = flags[i]; continue; } } buf += flags[i]; } if (buf.size()) { ret.append(buf); } return ret; } void qc_splitcflags(const QString &cflags, QStringList *incs, QStringList *otherflags) { incs->clear(); otherflags->clear(); QStringList cflagsList = qc_splitflags(cflags); for (int n = 0; n < cflagsList.count(); ++n) { QString str = cflagsList[n]; if (str.startsWith("-I")) { // we want everything except the leading "-I" incs->append(str.remove(0, 2)); } else { // we want whatever is left otherflags->append(str); } } } QString qc_escapeArg(const QString &str) { QString out; for (int n = 0; n < (int)str.length(); ++n) { if (str[n] == '-') out += '_'; else out += str[n]; } return out; } QString qc_trim_char(const QString &s, const QChar &ch) { if (s.startsWith(ch) && s.endsWith(ch)) { return s.mid(1, s.size() - 2); } return s; } // removes surrounding quotes, removes trailing slashes, converts to native separators. // accepts unescaped but possible quoted path QString qc_normalize_path(const QString &str) { QString path = str.trimmed(); path = qc_trim_char(path, QLatin1Char('"')); path = qc_trim_char(path, QLatin1Char('\\'')); // It's OK to use unix style'/' paths on windows Qt handles this without any problems. // Using Windows-style '\\\\' can leads strange compilation error with MSYS which uses // unix style. QLatin1Char nativeSep('/'); #ifdef Q_OS_WIN path.replace(QLatin1Char('\\\\'), QLatin1Char('/')); #endif // trim trailing slashes while (path.length() && path[path.length() - 1] == nativeSep) { path.resize(path.length() - 1); } return path; } // escape filesystem path to be added to qmake pro/pri file. QString qc_escape_string_var(const QString &str) { QString path = str; path.replace(QLatin1Char('\\\\'), QLatin1String("\\\\\\\\")).replace(QLatin1Char('"'), QLatin1String("\\\\\\"")); if (path.indexOf(QLatin1Char(' ')) != -1) { // has spaces return QLatin1Char('"') + path + QLatin1Char('"'); } return path; } // escapes each path in incs and join into single string suiable for INCLUDEPATH var QString qc_prepare_includepath(const QStringList &incs) { if (incs.empty()) { return QString(); } QStringList ret; foreach (const QString &path, incs) { ret.append(qc_escape_string_var(path)); } return ret.join(QLatin1String(" ")); } // escapes each path in libs and to make it suiable for LIBS var // notice, entries of libs are every single arg for linker. QString qc_prepare_libs(const QStringList &libs) { if (libs.isEmpty()) { return QString(); } QSet pathSet; QStringList paths; QStringList ordered; foreach (const QString &arg, libs) { if (arg.startsWith(QLatin1String("-L"))) { QString path = qc_escape_string_var(arg.mid(2)); if (!pathSet.contains(path)) { pathSet.insert(path); paths.append(path); } } else if (arg.startsWith(QLatin1String("-l"))) { ordered.append(arg); } else { ordered.append(qc_escape_string_var(arg)); } } QString ret; if (paths.size()) { ret += (QLatin1String(" -L") + paths.join(QLatin1String(" -L")) + QLatin1Char(' ')); } return ret + ordered.join(QLatin1String(" ")); } //---------------------------------------------------------------------------- // ConfObj //---------------------------------------------------------------------------- ConfObj::ConfObj(Conf *c) { conf = c; conf->added(this); required = false; disabled = false; success = false; } ConfObj::~ConfObj() {} QString ConfObj::checkString() const { return QString("Checking for %1 ...").arg(name()); } QString ConfObj::resultString() const { if (success) return "yes"; else return "no"; } //---------------------------------------------------------------------------- // qc_internal_pkgconfig //---------------------------------------------------------------------------- class qc_internal_pkgconfig : public ConfObj { public: QString pkgname, desc; VersionMode mode; QString req_ver; qc_internal_pkgconfig(Conf *c, const QString &_name, const QString &_desc, VersionMode _mode, const QString &_req_ver) : ConfObj(c), pkgname(_name), desc(_desc), mode(_mode), req_ver(_req_ver) { } QString name() const { return desc; } QString shortname() const { return pkgname; } bool exec() { QStringList incs; QString version, libs, other; if (!conf->findPkgConfig(pkgname, mode, req_ver, &version, &incs, &libs, &other)) return false; for (int n = 0; n < incs.count(); ++n) conf->addIncludePath(incs[n]); if (!libs.isEmpty()) conf->addLib(libs); // if(!other.isEmpty()) // conf->addExtra(QString("QMAKE_CFLAGS += %1\\n").arg(other)); if (!required) conf->addDefine("HAVE_PKG_" + qc_escapeArg(pkgname).toUpper()); return true; } }; //---------------------------------------------------------------------------- // Conf //---------------------------------------------------------------------------- Conf::Conf() { // TODO: no more vars? // vars.insert("QMAKE_INCDIR_X11", new QString(X11_INC)); // vars.insert("QMAKE_LIBDIR_X11", new QString(X11_LIBDIR)); // vars.insert("QMAKE_LIBS_X11", new QString(X11_LIB)); // vars.insert("QMAKE_CC", CC); debug_enabled = false; first_debug = true; } Conf::~Conf() { qDeleteAll(list); } void Conf::added(ConfObj *o) { list.append(o); } QString Conf::getenv(const QString &var) { return qc_getenv(var); } void Conf::debug(const QString &s) { if (debug_enabled) { if (first_debug) printf("\\n"); first_debug = false; printf(" * %s\\n", qPrintable(s)); } } bool Conf::exec() { for (int n = 0; n < list.count(); ++n) { ConfObj *o = list[n]; // if this was a disabled-by-default option, check if it was enabled if (o->disabled) { QString v = QString("QC_ENABLE_") + qc_escapeArg(o->shortname()); if (getenv(v) != "Y") continue; } // and the opposite? else { QString v = QString("QC_DISABLE_") + qc_escapeArg(o->shortname()); if (getenv(v) == "Y") continue; } bool output = true; QString check = o->checkString(); if (check.isEmpty()) output = false; if (output) { printf("%s", check.toLatin1().data()); fflush(stdout); } first_debug = true; bool ok = o->exec(); o->success = ok; if (output) { QString result = o->resultString(); if (!first_debug) printf(" -> %s\\n", result.toLatin1().data()); else printf(" %s\\n", result.toLatin1().data()); } if (!ok && o->required) { printf("\\nError: need %s!\\n", o->name().toLatin1().data()); return false; } } return true; } QString Conf::qvar(const QString &s) { return vars.value(s); } QString Conf::normalizePath(const QString &s) const { return qc_normalize_path(s); } QString Conf::escapeQmakeVar(const QString &s) const { return qc_escape_string_var(s); } QString Conf::escapedIncludes() const { return qc_prepare_includepath(INCLUDEPATH); } QString Conf::escapedLibs() const { return qc_prepare_libs(LIBS); } QString Conf::expandIncludes(const QString &inc) { return QLatin1String("-I") + inc; } QString Conf::expandLibs(const QString &lib) { return QLatin1String("-L") + lib; } int Conf::doCommand(const QString &s, QByteArray *out) { debug(QString("[%1]").arg(s)); int r = qc_runcommand(s, out, debug_enabled); debug(QString("returned: %1").arg(r)); return r; } int Conf::doCommand(const QString &prog, const QStringList &args, QByteArray *out) { QString fullcmd = prog; QString argstr = args.join(QLatin1String(" ")); if (!argstr.isEmpty()) fullcmd += QString(" ") + argstr; debug(QString("[%1]").arg(fullcmd)); int r = qc_runprogram(prog, args, out, debug_enabled); debug(QString("returned: %1").arg(r)); return r; } bool Conf::doCompileAndLink(const QString &filedata, const QStringList &incs, const QString &libs, const QString &proextra, int *retcode) { #ifdef Q_OS_WIN QDir tmp("qconftemp"); #else QDir tmp(".qconftemp"); #endif QStringList normalizedLibs; foreach (const QString &l, qc_splitflags(libs)) { normalizedLibs.append(qc_normalize_path(l)); } if (!tmp.mkdir("atest")) { debug(QString("unable to create atest dir: %1").arg(tmp.absoluteFilePath("atest"))); return false; } QDir dir(tmp.filePath("atest")); if (!dir.exists()) { debug("atest dir does not exist"); return false; } QString fname = dir.filePath("atest.cpp"); QString out = "atest"; QFile f(fname); if (!f.open(QFile::WriteOnly | QFile::Truncate)) { debug("unable to open atest.cpp for writing"); return false; } if (f.write(filedata.toLatin1()) == -1) { debug("error writing to atest.cpp"); return false; } f.close(); debug(QString("Wrote atest.cpp:\\n%1").arg(filedata)); QString pro = QString("CONFIG += console\\n" "CONFIG -= qt app_bundle\\n" "DESTDIR = \$\$PWD\\n" "SOURCES += atest.cpp\\n"); QString inc = qc_prepare_includepath(incs); if (!inc.isEmpty()) pro += "INCLUDEPATH += " + inc + '\\n'; QString escaped_libs = qc_prepare_libs(normalizedLibs); if (!escaped_libs.isEmpty()) pro += "LIBS += " + escaped_libs + '\\n'; pro += proextra; fname = dir.filePath("atest.pro"); f.setFileName(fname); if (!f.open(QFile::WriteOnly | QFile::Truncate)) { debug("unable to open atest.pro for writing"); return false; } if (f.write(pro.toLatin1()) == -1) { debug("error writing to atest.pro"); return false; } f.close(); debug(QString("Wrote atest.pro:\\n%1").arg(pro)); QString oldpath = QDir::currentPath(); QDir::setCurrent(dir.path()); bool ok = false; int r = doCommand(qmake_path, QStringList() << "atest.pro"); if (r == 0) { r = doCommand(maketool, QStringList()); if (r == 0) { ok = true; if (retcode) { QString runatest = out; #ifdef Q_OS_UNIX runatest.prepend("./"); #endif *retcode = doCommand(runatest, QStringList()); } } r = doCommand(maketool, QStringList() << "distclean"); if (r != 0) debug("error during atest distclean"); } QDir::setCurrent(oldpath); // cleanup // dir.remove("atest.pro"); // dir.remove("atest.cpp"); // tmp.rmdir("atest"); // remove whole dir since distclean doesn't always work qc_removedir(tmp.filePath("atest")); if (!ok) return false; return true; } bool Conf::checkHeader(const QString &path, const QString &h) { return QDir(path).exists(h); } bool Conf::findHeader(const QString &h, const QStringList &ext, QString *inc) { if (checkHeader("/usr/include", h)) { *inc = ""; return true; } QStringList dirs; dirs += "/usr/local/include"; dirs += ext; QString prefix = qc_getenv("PREFIX"); if (!prefix.isEmpty()) { prefix += "/include"; prefix = qc_normalize_path(prefix); if (!dirs.contains(prefix)) dirs << prefix; } for (QStringList::ConstIterator it = dirs.begin(); it != dirs.end(); ++it) { if (checkHeader(*it, h)) { *inc = *it; return true; } } return false; } bool Conf::checkLibrary(const QString &path, const QString &name) { QString str = //"#include \\n" "int main()\\n" "{\\n" //" printf(\\"library checker running\\\\\\\\n\\");\\n" " return 0;\\n" "}\\n"; QString libs; if (!path.isEmpty()) libs += QString("-L") + path + ' '; libs += QString("-l") + name; if (!doCompileAndLink(str, QStringList(), libs, QString())) return false; return true; } bool Conf::findLibrary(const QString &name, QString *lib) { if (checkLibrary("", name)) { *lib = ""; return true; } if (checkLibrary("/usr/local/lib", name)) { *lib = "/usr/local/lib"; return true; } QString prefix = qc_getenv("PREFIX"); if (!prefix.isEmpty()) { prefix += "/lib"; prefix = qc_normalize_path(prefix); if (checkLibrary(prefix, name)) { *lib = prefix; return true; } } return false; } QString Conf::findProgram(const QString &prog) { return qc_findprogram(prog); } bool Conf::findSimpleLibrary(const QString &incvar, const QString &libvar, const QString &incname, const QString &libname, QString *incpath, QString *libs) { QString inc, lib; QString s; s = getenv(incvar).trimmed(); if (!s.isEmpty()) { if (!checkHeader(s, incname)) { if (debug_enabled) printf("%s is not found in \\"%s\\"\\n", qPrintable(incname), qPrintable(s)); return false; } inc = s; } else { if (!findHeader(incname, QStringList(), &s)) { if (debug_enabled) printf("%s is not found anywhere\\n", qPrintable(incname)); return false; } inc = s; } s = getenv(libvar).trimmed(); if (!s.isEmpty()) { if (!checkLibrary(s, libname)) { if (debug_enabled) printf("%s is not found in \\"%s\\"\\n", qPrintable(libname), qPrintable(s)); return false; } lib = s; } else { if (!findLibrary(libname, &s)) { if (debug_enabled) printf("%s is not found anywhere\\n", qPrintable(libname)); return false; } lib = s; } QString lib_out; if (!lib.isEmpty()) lib_out += QString("-L") + s + " "; lib_out += QString("-l") + libname; *incpath = inc; *libs = lib_out; return true; } bool Conf::findFooConfig(const QString &path, QString *version, QStringList *incs, QString *libs, QString *otherflags) { QStringList args; QByteArray out; int ret; args += "--version"; ret = doCommand(path, args, &out); if (ret != 0) return false; QString version_out = QString::fromLatin1(out).trimmed(); args.clear(); args += "--libs"; ret = doCommand(path, args, &out); if (ret != 0) return false; QString libs_out = QString::fromLatin1(out).trimmed(); args.clear(); args += "--cflags"; ret = doCommand(path, args, &out); if (ret != 0) return false; QString cflags = QString::fromLatin1(out).trimmed(); QStringList incs_out, otherflags_out; qc_splitcflags(cflags, &incs_out, &otherflags_out); *version = version_out; *incs = incs_out; *libs = libs_out; *otherflags = otherflags_out.join(QLatin1String(" ")); return true; } bool Conf::findPkgConfig(const QString &name, VersionMode mode, const QString &req_version, QString *version, QStringList *incs, QString *libs, QString *otherflags) { QStringList args; QByteArray out; int ret; args += name; args += "--exists"; ret = doCommand("pkg-config", args, &out); if (ret != 0) return false; if (mode != VersionAny) { args.clear(); args += name; if (mode == VersionMin) args += QString("--atleast-version=%1").arg(req_version); else if (mode == VersionMax) args += QString("--max-version=%1").arg(req_version); else args += QString("--exact-version=%1").arg(req_version); ret = doCommand("pkg-config", args, &out); if (ret != 0) return false; } args.clear(); args += name; args += "--modversion"; ret = doCommand("pkg-config", args, &out); if (ret != 0) return false; QString version_out = QString::fromLatin1(out).trimmed(); args.clear(); args += name; args += "--libs"; ret = doCommand("pkg-config", args, &out); if (ret != 0) return false; QString libs_out = QString::fromLatin1(out).trimmed(); args.clear(); args += name; args += "--cflags"; ret = doCommand("pkg-config", args, &out); if (ret != 0) return false; QString cflags = QString::fromLatin1(out).trimmed(); QStringList incs_out, otherflags_out; qc_splitcflags(cflags, &incs_out, &otherflags_out); *version = version_out; *incs = incs_out; *libs = libs_out; *otherflags = otherflags_out.join(QLatin1String(" ")); return true; } void Conf::addDefine(const QString &str) { if (DEFINES.isEmpty()) DEFINES = str; else DEFINES += QString(" ") + str; debug(QString("DEFINES += %1").arg(str)); } void Conf::addLib(const QString &str) { QStringList libs = qc_splitflags(str); foreach (const QString &lib, libs) { if (lib.startsWith("-l")) { LIBS.append(lib); } else { LIBS.append(qc_normalize_path(lib)); // we don't care about -L prefix since normalier does not touch it. } } debug(QString("LIBS += %1").arg(str)); } void Conf::addIncludePath(const QString &str) { INCLUDEPATH.append(qc_normalize_path(str)); debug(QString("INCLUDEPATH += %1").arg(str)); } void Conf::addExtra(const QString &str) { extra += str + '\\n'; debug(QString("extra += %1").arg(str)); } //---------------------------------------------------------------------------- // main //---------------------------------------------------------------------------- #include "conf4.moc" #ifdef HAVE_MODULES #include "modules.cpp" #endif int main(int argc, char **argv) { QCoreApplication app(argc, argv); Conf * conf = new Conf; ConfObj * o = 0; Q_UNUSED(o); #ifdef HAVE_MODULES #include "modules_new.cpp" #endif conf->debug_enabled = (qc_getenv("QC_VERBOSE") == "Y") ? true : false; if (conf->debug_enabled) printf(" -> ok\\n"); else printf("ok\\n"); QString confCommand = qc_getenv("QC_COMMAND"); QString proName = qc_getenv("QC_PROFILE"); conf->qmake_path = qc_getenv("QC_QMAKE"); conf->qmakespec = qc_getenv("QC_QMAKESPEC"); conf->maketool = qc_getenv("QC_MAKETOOL"); if (conf->debug_enabled) printf("conf command: [%s]\\n", qPrintable(confCommand)); QString confPath = qc_findself(confCommand); if (confPath.isEmpty()) { printf("Error: cannot find myself; rerun with an absolute path\\n"); return 1; } QString srcdir = QFileInfo(confPath).absolutePath(); QString builddir = QDir::current().absolutePath(); QString proPath = QDir(srcdir).filePath(proName); if (conf->debug_enabled) { printf("conf path: [%s]\\n", qPrintable(confPath)); printf("srcdir: [%s]\\n", qPrintable(srcdir)); printf("builddir: [%s]\\n", qPrintable(builddir)); printf("profile: [%s]\\n", qPrintable(proPath)); printf("qmake path: [%s]\\n", qPrintable(conf->qmake_path)); printf("qmakespec: [%s]\\n", qPrintable(conf->qmakespec)); printf("make tool: [%s]\\n", qPrintable(conf->maketool)); printf("\\n"); } bool success = false; if (conf->exec()) { QFile f("conf.pri"); if (!f.open(QFile::WriteOnly | QFile::Truncate)) { printf("Error writing %s\\n", qPrintable(f.fileName())); return 1; } QString str; str += "# qconf\\n\\n"; str += "greaterThan(QT_MAJOR_VERSION, 4):CONFIG += c++11\\n"; QString var; var = qc_normalize_path(qc_getenv("PREFIX")); if (!var.isEmpty()) str += QString("PREFIX = %1\\n").arg(var); var = qc_normalize_path(qc_getenv("BINDIR")); if (!var.isEmpty()) str += QString("BINDIR = %1\\n").arg(var); var = qc_normalize_path(qc_getenv("INCDIR")); if (!var.isEmpty()) str += QString("INCDIR = %1\\n").arg(var); var = qc_normalize_path(qc_getenv("LIBDIR")); if (!var.isEmpty()) str += QString("LIBDIR = %1\\n").arg(var); var = qc_normalize_path(qc_getenv("DATADIR")); if (!var.isEmpty()) str += QString("DATADIR = %1\\n").arg(var); str += '\\n'; if (qc_getenv("QC_STATIC") == "Y") str += "CONFIG += staticlib\\n"; // TODO: don't need this? // str += "QT_PATH_PLUGINS = " + QString(qInstallPathPlugins()) + '\\n'; if (!conf->DEFINES.isEmpty()) str += "DEFINES += " + conf->DEFINES + '\\n'; if (!conf->INCLUDEPATH.isEmpty()) str += "INCLUDEPATH += " + qc_prepare_includepath(conf->INCLUDEPATH) + '\\n'; if (!conf->LIBS.isEmpty()) str += "LIBS += " + qc_prepare_libs(conf->LIBS) + '\\n'; if (!conf->extra.isEmpty()) str += conf->extra; str += '\\n'; var = qc_getenv("QC_EXTRACONF"); if (!var.isEmpty()) str += ("\\n# Extra conf from command line\\n" + var + "\\n"); QByteArray cs = str.toLatin1(); f.write(cs); f.close(); success = true; } QString qmake_path = conf->qmake_path; QString qmakespec = conf->qmakespec; delete conf; if (!success) return 1; // run qmake on the project file QStringList args; if (!qmakespec.isEmpty()) { args += "-spec"; args += qmakespec; } args += proPath; int ret = qc_runprogram(qmake_path, args, 0, true); if (ret != 0) return 1; return 0; } EOT cat >"$1/conf4.pro" </dev/null else "$qm" conf4.pro >/dev/null fi $MAKE clean >/dev/null 2>&1 $MAKE >../conf.log 2>&1 ) if [ "$?" != "0" ]; then rm -rf ".qconftemp" if [ "$QC_VERBOSE" = "Y" ]; then echo " -> fail" else echo "fail" fi printf "\n" printf "Reason: There was an error compiling 'conf'. See conf.log for details.\n" printf "\n" show_qt_info if [ "$QC_VERBOSE" = "Y" ]; then echo "conf.log:" cat conf.log fi exit 1; fi QC_COMMAND=$0 export QC_COMMAND QC_PROFILE=psi.pro export QC_PROFILE QC_QMAKE="$qm" export QC_QMAKE QC_QMAKESPEC=$qm_spec export QC_QMAKESPEC QC_MAKETOOL=$MAKE export QC_MAKETOOL ".qconftemp/conf" ret="$?" if [ "$ret" = "1" ]; then rm -rf ".qconftemp" echo exit 1; else if [ "$ret" != "0" ]; then rm -rf ".qconftemp" if [ "$QC_VERBOSE" = "Y" ]; then echo " -> fail" else echo "fail" fi echo echo "Reason: Unexpected error launching 'conf'" echo exit 1; fi fi rm -rf ".qconftemp" echo echo "Good, your configure finished. Now run $MAKE." echo psi-plus-snapshots-1.4.1456/doc/000077500000000000000000000000001370065651000163205ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/doc/Doxyfile.private000066400000000000000000000315041370065651000215020ustar00rootroot00000000000000# Doxyfile 1.8.4 #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = Psi PROJECT_NUMBER = PROJECT_BRIEF = PROJECT_LOGO = OUTPUT_DIRECTORY = doc/api/private CREATE_SUBDIRS = NO OUTPUT_LANGUAGE = English BRIEF_MEMBER_DESC = YES REPEAT_BRIEF = YES ABBREVIATE_BRIEF = "The $name class" \ "The $name widget" \ "The $name file" \ is \ provides \ specifies \ contains \ represents \ a \ an \ the ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = NO FULL_PATH_NAMES = NO STRIP_FROM_PATH = STRIP_FROM_INC_PATH = SHORT_NAMES = NO JAVADOC_AUTOBRIEF = NO QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO INHERIT_DOCS = YES SEPARATE_MEMBER_PAGES = NO TAB_SIZE = 5 ALIASES = TCL_SUBST = OPTIMIZE_OUTPUT_FOR_C = NO OPTIMIZE_OUTPUT_JAVA = NO OPTIMIZE_FOR_FORTRAN = NO OPTIMIZE_OUTPUT_VHDL = NO EXTENSION_MAPPING = MARKDOWN_SUPPORT = YES AUTOLINK_SUPPORT = YES BUILTIN_STL_SUPPORT = NO CPP_CLI_SUPPORT = NO SIP_SUPPORT = NO IDL_PROPERTY_SUPPORT = YES DISTRIBUTE_GROUP_DOC = NO SUBGROUPING = YES INLINE_GROUPED_CLASSES = NO INLINE_SIMPLE_STRUCTS = NO TYPEDEF_HIDES_STRUCT = NO LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- EXTRACT_ALL = YES EXTRACT_PRIVATE = YES EXTRACT_PACKAGE = NO EXTRACT_STATIC = YES EXTRACT_LOCAL_CLASSES = YES EXTRACT_LOCAL_METHODS = NO EXTRACT_ANON_NSPACES = NO HIDE_UNDOC_MEMBERS = NO HIDE_UNDOC_CLASSES = NO HIDE_FRIEND_COMPOUNDS = NO HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO CASE_SENSE_NAMES = YES HIDE_SCOPE_NAMES = NO SHOW_INCLUDE_FILES = YES FORCE_LOCAL_INCLUDES = NO INLINE_INFO = YES SORT_MEMBER_DOCS = YES SORT_BRIEF_DOCS = NO SORT_MEMBERS_CTORS_1ST = NO SORT_GROUP_NAMES = NO SORT_BY_SCOPE_NAME = NO STRICT_PROTO_MATCHING = NO GENERATE_TODOLIST = YES GENERATE_TESTLIST = YES GENERATE_BUGLIST = YES GENERATE_DEPRECATEDLIST= YES ENABLED_SECTIONS = MAX_INITIALIZER_LINES = 30 SHOW_USED_FILES = YES SHOW_FILES = YES SHOW_NAMESPACES = YES FILE_VERSION_FILTER = LAYOUT_FILE = CITE_BIB_FILES = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- QUIET = YES WARNINGS = YES WARN_IF_UNDOCUMENTED = YES WARN_IF_DOC_ERROR = YES WARN_NO_PARAMDOC = NO WARN_FORMAT = "$file:$line: $text" WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- INPUT = . INPUT_ENCODING = UTF-8 FILE_PATTERNS = *.c \ *.cc \ *.cxx \ *.cpp \ *.c++ \ *.d \ *.java \ *.ii \ *.ixx \ *.ipp \ *.i++ \ *.inl \ *.h \ *.hh \ *.hxx \ *.hpp \ *.h++ \ *.idl \ *.odl \ *.cs \ *.php \ *.php3 \ *.inc \ *.m \ *.mm \ *.dox \ *.py \ *.C \ *.CC \ *.C++ \ *.II \ *.I++ \ *.H \ *.HH \ *.H++ \ *.CS \ *.PHP \ *.PHP3 \ *.M \ *.MM \ *.PY RECURSIVE = YES EXCLUDE = _darcs/ \ third-party/ \ iris/libidn/ \ iris/irisnet/ \ iris/irisnet/jdns \ iris/example \ src/tools/tunecontroller/plugins/winamp/third-party \ src/tools/iconset/unittest \ src/tools/optionstree/optionstreeviewtest \ src/tools/zip/minizip \ src/unittest \ src/widgets/private/ \ src/widgets/unittest/ \ doc/ EXCLUDE_SYMLINKS = NO EXCLUDE_PATTERNS = */.ui/* \ */.moc/* \ moc_* EXCLUDE_SYMBOLS = EXAMPLE_PATH = EXAMPLE_PATTERNS = * EXAMPLE_RECURSIVE = NO IMAGE_PATH = INPUT_FILTER = FILTER_PATTERNS = FILTER_SOURCE_FILES = NO FILTER_SOURCE_PATTERNS = USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- SOURCE_BROWSER = YES INLINE_SOURCES = YES STRIP_CODE_COMMENTS = YES REFERENCED_BY_RELATION = YES REFERENCES_RELATION = NO REFERENCES_LINK_SOURCE = YES USE_HTAGS = NO VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- ALPHABETICAL_INDEX = NO COLS_IN_ALPHA_INDEX = 5 IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- GENERATE_HTML = YES HTML_OUTPUT = . HTML_FILE_EXTENSION = .html HTML_HEADER = doc/doxygen.header.html HTML_FOOTER = doc/doxygen.footer.html HTML_STYLESHEET = doc/doxygen.css HTML_EXTRA_STYLESHEET = HTML_EXTRA_FILES = HTML_COLORSTYLE_HUE = 220 HTML_COLORSTYLE_SAT = 100 HTML_COLORSTYLE_GAMMA = 80 HTML_TIMESTAMP = YES HTML_DYNAMIC_SECTIONS = NO HTML_INDEX_NUM_ENTRIES = 100 GENERATE_DOCSET = NO DOCSET_FEEDNAME = "Doxygen generated docs" DOCSET_BUNDLE_ID = org.doxygen.Project DOCSET_PUBLISHER_ID = org.doxygen.Publisher DOCSET_PUBLISHER_NAME = Publisher GENERATE_HTMLHELP = NO CHM_FILE = HHC_LOCATION = GENERATE_CHI = NO CHM_INDEX_ENCODING = BINARY_TOC = NO TOC_EXPAND = NO GENERATE_QHP = NO QCH_FILE = QHP_NAMESPACE = org.doxygen.Project QHP_VIRTUAL_FOLDER = doc QHP_CUST_FILTER_NAME = QHP_CUST_FILTER_ATTRS = QHP_SECT_FILTER_ATTRS = QHG_LOCATION = GENERATE_ECLIPSEHELP = NO ECLIPSE_DOC_ID = org.doxygen.Project DISABLE_INDEX = NO GENERATE_TREEVIEW = NO ENUM_VALUES_PER_LINE = 4 TREEVIEW_WIDTH = 250 EXT_LINKS_IN_WINDOW = NO FORMULA_FONTSIZE = 10 FORMULA_TRANSPARENT = YES USE_MATHJAX = NO MATHJAX_FORMAT = HTML-CSS MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest MATHJAX_EXTENSIONS = MATHJAX_CODEFILE = SEARCHENGINE = YES SERVER_BASED_SEARCH = NO EXTERNAL_SEARCH = NO SEARCHENGINE_URL = SEARCHDATA_FILE = searchdata.xml EXTERNAL_SEARCH_ID = EXTRA_SEARCH_MAPPINGS = #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- GENERATE_LATEX = NO LATEX_OUTPUT = latex LATEX_CMD_NAME = latex MAKEINDEX_CMD_NAME = makeindex COMPACT_LATEX = NO PAPER_TYPE = a4wide EXTRA_PACKAGES = LATEX_HEADER = LATEX_FOOTER = LATEX_EXTRA_FILES = PDF_HYPERLINKS = NO USE_PDFLATEX = NO LATEX_BATCHMODE = NO LATEX_HIDE_INDICES = NO LATEX_SOURCE_CODE = NO LATEX_BIB_STYLE = plain #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- GENERATE_RTF = NO RTF_OUTPUT = rtf COMPACT_RTF = NO RTF_HYPERLINKS = NO RTF_STYLESHEET_FILE = RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- GENERATE_MAN = NO MAN_OUTPUT = man MAN_EXTENSION = .3 MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- GENERATE_XML = NO XML_OUTPUT = xml XML_SCHEMA = XML_DTD = XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options related to the DOCBOOK output #--------------------------------------------------------------------------- GENERATE_DOCBOOK = NO DOCBOOK_OUTPUT = docbook #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- GENERATE_PERLMOD = NO PERLMOD_LATEX = NO PERLMOD_PRETTY = YES PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- ENABLE_PREPROCESSING = YES MACRO_EXPANSION = NO EXPAND_ONLY_PREDEF = NO SEARCH_INCLUDES = YES INCLUDE_PATH = INCLUDE_FILE_PATTERNS = PREDEFINED = EXPAND_AS_DEFINED = SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- TAGFILES = GENERATE_TAGFILE = ALLEXTERNALS = NO EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- CLASS_DIAGRAMS = YES MSCGEN_PATH = HIDE_UNDOC_RELATIONS = YES HAVE_DOT = YES DOT_NUM_THREADS = 0 DOT_FONTNAME = Helvetica DOT_FONTSIZE = 10 DOT_FONTPATH = CLASS_GRAPH = YES COLLABORATION_GRAPH = YES GROUP_GRAPHS = YES UML_LOOK = NO UML_LIMIT_NUM_FIELDS = 10 TEMPLATE_RELATIONS = NO INCLUDE_GRAPH = YES INCLUDED_BY_GRAPH = YES CALL_GRAPH = NO CALLER_GRAPH = NO GRAPHICAL_HIERARCHY = YES DIRECTORY_GRAPH = YES DOT_IMAGE_FORMAT = png INTERACTIVE_SVG = NO DOT_PATH = DOTFILE_DIRS = MSCFILE_DIRS = DOT_GRAPH_MAX_NODES = 50 MAX_DOT_GRAPH_DEPTH = 1000 DOT_TRANSPARENT = NO DOT_MULTI_TARGETS = NO GENERATE_LEGEND = YES DOT_CLEANUP = YES psi-plus-snapshots-1.4.1456/doc/Doxyfile.public000066400000000000000000000315001370065651000213020ustar00rootroot00000000000000# Doxyfile 1.8.4 #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = Psi PROJECT_NUMBER = PROJECT_BRIEF = PROJECT_LOGO = OUTPUT_DIRECTORY = doc/api/public CREATE_SUBDIRS = no OUTPUT_LANGUAGE = English BRIEF_MEMBER_DESC = YES REPEAT_BRIEF = YES ABBREVIATE_BRIEF = "The $name class" \ "The $name widget" \ "The $name file" \ is \ provides \ specifies \ contains \ represents \ a \ an \ the ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = NO FULL_PATH_NAMES = NO STRIP_FROM_PATH = STRIP_FROM_INC_PATH = SHORT_NAMES = NO JAVADOC_AUTOBRIEF = NO QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO INHERIT_DOCS = YES SEPARATE_MEMBER_PAGES = NO TAB_SIZE = 5 ALIASES = TCL_SUBST = OPTIMIZE_OUTPUT_FOR_C = NO OPTIMIZE_OUTPUT_JAVA = NO OPTIMIZE_FOR_FORTRAN = NO OPTIMIZE_OUTPUT_VHDL = NO EXTENSION_MAPPING = MARKDOWN_SUPPORT = YES AUTOLINK_SUPPORT = YES BUILTIN_STL_SUPPORT = NO CPP_CLI_SUPPORT = NO SIP_SUPPORT = NO IDL_PROPERTY_SUPPORT = YES DISTRIBUTE_GROUP_DOC = NO SUBGROUPING = YES INLINE_GROUPED_CLASSES = NO INLINE_SIMPLE_STRUCTS = NO TYPEDEF_HIDES_STRUCT = NO LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- EXTRACT_ALL = YES EXTRACT_PRIVATE = NO EXTRACT_PACKAGE = NO EXTRACT_STATIC = YES EXTRACT_LOCAL_CLASSES = NO EXTRACT_LOCAL_METHODS = NO EXTRACT_ANON_NSPACES = NO HIDE_UNDOC_MEMBERS = NO HIDE_UNDOC_CLASSES = NO HIDE_FRIEND_COMPOUNDS = NO HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO CASE_SENSE_NAMES = YES HIDE_SCOPE_NAMES = NO SHOW_INCLUDE_FILES = NO FORCE_LOCAL_INCLUDES = NO INLINE_INFO = YES SORT_MEMBER_DOCS = YES SORT_BRIEF_DOCS = NO SORT_MEMBERS_CTORS_1ST = NO SORT_GROUP_NAMES = NO SORT_BY_SCOPE_NAME = NO STRICT_PROTO_MATCHING = NO GENERATE_TODOLIST = NO GENERATE_TESTLIST = NO GENERATE_BUGLIST = NO GENERATE_DEPRECATEDLIST= YES ENABLED_SECTIONS = MAX_INITIALIZER_LINES = 30 SHOW_USED_FILES = YES SHOW_FILES = YES SHOW_NAMESPACES = YES FILE_VERSION_FILTER = LAYOUT_FILE = CITE_BIB_FILES = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- QUIET = YES WARNINGS = YES WARN_IF_UNDOCUMENTED = YES WARN_IF_DOC_ERROR = YES WARN_NO_PARAMDOC = NO WARN_FORMAT = "$file:$line: $text" WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- INPUT = . INPUT_ENCODING = UTF-8 FILE_PATTERNS = *.c \ *.cc \ *.cxx \ *.cpp \ *.c++ \ *.d \ *.java \ *.ii \ *.ixx \ *.ipp \ *.i++ \ *.inl \ *.h \ *.hh \ *.hxx \ *.hpp \ *.h++ \ *.idl \ *.odl \ *.cs \ *.php \ *.php3 \ *.inc \ *.m \ *.mm \ *.dox \ *.py \ *.C \ *.CC \ *.C++ \ *.II \ *.I++ \ *.H \ *.HH \ *.H++ \ *.CS \ *.PHP \ *.PHP3 \ *.M \ *.MM \ *.PY RECURSIVE = YES EXCLUDE = _darcs/ \ third-party/ \ iris/libidn/ \ iris/irisnet/ \ iris/irisnet/jdns \ iris/example \ src/tools/tunecontroller/plugins/winamp/third-party \ src/tools/iconset/unittest \ src/tools/optionstree/optionstreeviewtest \ src/libpsi/tools/zip/minizip \ src/unittest \ src/widgets/private/ \ src/widgets/unittest/ \ doc/ EXCLUDE_SYMLINKS = NO EXCLUDE_PATTERNS = */.ui/* \ */.moc/* \ moc_* EXCLUDE_SYMBOLS = EXAMPLE_PATH = EXAMPLE_PATTERNS = * EXAMPLE_RECURSIVE = NO IMAGE_PATH = INPUT_FILTER = FILTER_PATTERNS = FILTER_SOURCE_FILES = NO FILTER_SOURCE_PATTERNS = USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- SOURCE_BROWSER = NO INLINE_SOURCES = NO STRIP_CODE_COMMENTS = YES REFERENCED_BY_RELATION = NO REFERENCES_RELATION = NO REFERENCES_LINK_SOURCE = YES USE_HTAGS = NO VERBATIM_HEADERS = NO #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- ALPHABETICAL_INDEX = NO COLS_IN_ALPHA_INDEX = 5 IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- GENERATE_HTML = YES HTML_OUTPUT = . HTML_FILE_EXTENSION = .html HTML_HEADER = doc/doxygen.header.html HTML_FOOTER = doc/doxygen.footer.html HTML_STYLESHEET = doc/doxygen.css HTML_EXTRA_STYLESHEET = HTML_EXTRA_FILES = HTML_COLORSTYLE_HUE = 220 HTML_COLORSTYLE_SAT = 100 HTML_COLORSTYLE_GAMMA = 80 HTML_TIMESTAMP = YES HTML_DYNAMIC_SECTIONS = NO HTML_INDEX_NUM_ENTRIES = 100 GENERATE_DOCSET = NO DOCSET_FEEDNAME = "Doxygen generated docs" DOCSET_BUNDLE_ID = org.doxygen.Project DOCSET_PUBLISHER_ID = org.doxygen.Publisher DOCSET_PUBLISHER_NAME = Publisher GENERATE_HTMLHELP = NO CHM_FILE = HHC_LOCATION = GENERATE_CHI = NO CHM_INDEX_ENCODING = BINARY_TOC = NO TOC_EXPAND = NO GENERATE_QHP = NO QCH_FILE = QHP_NAMESPACE = org.doxygen.Project QHP_VIRTUAL_FOLDER = doc QHP_CUST_FILTER_NAME = QHP_CUST_FILTER_ATTRS = QHP_SECT_FILTER_ATTRS = QHG_LOCATION = GENERATE_ECLIPSEHELP = NO ECLIPSE_DOC_ID = org.doxygen.Project DISABLE_INDEX = NO GENERATE_TREEVIEW = NO ENUM_VALUES_PER_LINE = 4 TREEVIEW_WIDTH = 250 EXT_LINKS_IN_WINDOW = NO FORMULA_FONTSIZE = 10 FORMULA_TRANSPARENT = YES USE_MATHJAX = NO MATHJAX_FORMAT = HTML-CSS MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest MATHJAX_EXTENSIONS = MATHJAX_CODEFILE = SEARCHENGINE = YES SERVER_BASED_SEARCH = NO EXTERNAL_SEARCH = NO SEARCHENGINE_URL = SEARCHDATA_FILE = searchdata.xml EXTERNAL_SEARCH_ID = EXTRA_SEARCH_MAPPINGS = #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- GENERATE_LATEX = NO LATEX_OUTPUT = latex LATEX_CMD_NAME = latex MAKEINDEX_CMD_NAME = makeindex COMPACT_LATEX = NO PAPER_TYPE = a4wide EXTRA_PACKAGES = LATEX_HEADER = LATEX_FOOTER = LATEX_EXTRA_FILES = PDF_HYPERLINKS = NO USE_PDFLATEX = NO LATEX_BATCHMODE = NO LATEX_HIDE_INDICES = NO LATEX_SOURCE_CODE = NO LATEX_BIB_STYLE = plain #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- GENERATE_RTF = NO RTF_OUTPUT = rtf COMPACT_RTF = NO RTF_HYPERLINKS = NO RTF_STYLESHEET_FILE = RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- GENERATE_MAN = NO MAN_OUTPUT = man MAN_EXTENSION = .3 MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- GENERATE_XML = NO XML_OUTPUT = xml XML_SCHEMA = XML_DTD = XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options related to the DOCBOOK output #--------------------------------------------------------------------------- GENERATE_DOCBOOK = NO DOCBOOK_OUTPUT = docbook #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- GENERATE_PERLMOD = NO PERLMOD_LATEX = NO PERLMOD_PRETTY = YES PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- ENABLE_PREPROCESSING = YES MACRO_EXPANSION = NO EXPAND_ONLY_PREDEF = NO SEARCH_INCLUDES = YES INCLUDE_PATH = INCLUDE_FILE_PATTERNS = PREDEFINED = EXPAND_AS_DEFINED = SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- TAGFILES = GENERATE_TAGFILE = ALLEXTERNALS = NO EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- CLASS_DIAGRAMS = YES MSCGEN_PATH = HIDE_UNDOC_RELATIONS = YES HAVE_DOT = YES DOT_NUM_THREADS = 0 DOT_FONTNAME = Helvetica DOT_FONTSIZE = 10 DOT_FONTPATH = CLASS_GRAPH = YES COLLABORATION_GRAPH = YES GROUP_GRAPHS = YES UML_LOOK = NO UML_LIMIT_NUM_FIELDS = 10 TEMPLATE_RELATIONS = NO INCLUDE_GRAPH = YES INCLUDED_BY_GRAPH = YES CALL_GRAPH = NO CALLER_GRAPH = NO GRAPHICAL_HIERARCHY = YES DIRECTORY_GRAPH = YES DOT_IMAGE_FORMAT = png INTERACTIVE_SVG = NO DOT_PATH = DOTFILE_DIRS = MSCFILE_DIRS = DOT_GRAPH_MAX_NODES = 50 MAX_DOT_GRAPH_DEPTH = 1000 DOT_TRANSPARENT = NO DOT_MULTI_TARGETS = NO GENERATE_LEGEND = YES DOT_CLEANUP = YES psi-plus-snapshots-1.4.1456/doc/Makefile000066400000000000000000000003201370065651000177530ustar00rootroot00000000000000.PHONY: all all: .PHONY: api_public api_public: mkdir -p api/public && cd .. && doxygen doc/Doxyfile.public .PHONY: api_private api_private: mkdir -p api/private && cd .. && doxygen doc/Doxyfile.private psi-plus-snapshots-1.4.1456/doc/build-mac.txt000066400000000000000000000016731370065651000207250ustar00rootroot00000000000000Building Psi on Mac OS X ------------------------ To simply compile Psi, the same instructions apply as for other *nix platforms: 1. First, configure the build using the 'configure' script, optionally adding parameters to specify where to find certain dependencies: ./configure 2. Build the binary: make After this, the built binary 'psi.app' will be available in src/. For more details, check 'doc/build-unix.txt'. To make a distributable copy of your binary, do the following: 1. As above, use configure to configure the build 2. Alter the first lines of 'mac/Makefile' to reflect your setting 3. Run 'make -C mac/'. This will create a distributable binary 'Psi.app' in 'mac/disk/'. 4. (OPTIONAL) Create a DMG image by running 'make -C mac/ dmg'. This will create a 'Psi-.dmg' file in 'mac/'. You can create your own DMG template to make the resulting DMG look different. For more instructions on this, see 'mac/Makefile'. psi-plus-snapshots-1.4.1456/doc/build-unix.txt000066400000000000000000000037611370065651000211500ustar00rootroot00000000000000Requirements ------------ * You need Qt 4.4 or higher to build Psi. If a packaged version of Qt is not available for your OS (or if you want debug symbols in your Qt libraries), you will need to build and install Qt 4.4 yourself. See the Qt instructions to find out how to do this. * You need QCA 2.0 and the QCA OpenSSL plugin, which you can get at https://userbase.kde.org/QCA Instructions on how to build these packages can be found below. The 'Building QCA' and 'Building QCA the OpenSSL plugin' sections can be skipped by downloading the sources of these packages, and unpackaging them in third-party/qca (see the INSTALL file for the exact location of each package). Building QCA ------------ After unpacking the QCA sources, run the following command: ./configure --prefix=/usr/local/qca2 If configure cannot find your Qt4 library, use the --qtdir option to specify the path to Qt (e.g. /usr/share/qt4), or make sure that the qmake binary for Qt4 occurs first in your PATH. If something else goes wrong, use the --verbose option to get more information on the configuration process. After QCA configured, run make make install To be able to run applications using QCA2, you will need to add /usr/local/qca2/lib to /etc/ld.so.conf and run ldconfig, or add /usr/local/qca2/lib to your LD_LIBRARY_PATH. Building the QCA OpenSSL plugin ------------------------------- After unpacking the QCA OpenSSL plugin's sources, run the following commands ./configure make make install See above on how to troubleshoot configure problems. Building the QCA GnuPG plugin ------------------------------- After unpacking the QCA GnuPG plugin's sources, run the following commands ./configure make make install See above on how to troubleshoot configure problems. Building Psi ------------ From the toplevel Psi source dir, run the following commands: ./configure --prefix=/usr/local/psi make make install This should configure, build, and install Psi. See above on how to troubleshoot configure problems. psi-plus-snapshots-1.4.1456/doc/build-win.txt000066400000000000000000000042511370065651000207550ustar00rootroot00000000000000Requirements ------------ * You need Qt 4.4 or higher to build Psi. Just download and install the MingW32 self-installer. All the instructions below are performed from within the Qt command prompt (found in the Start menu) * You need QCA 2.0 and the QCA OpenSSL plugin, which you can get at https://userbase.kde.org/QCA Instructions on how to build these packages can be found below. * If you want to use the QCA OpenSSL plugin on Windows, you will need to download and install the OpenSSL package from http://www.openssl.org/related/binaries.html The 'Building QCA' and 'Building QCA the OpenSSL plugin' sections can be skipped by downloading the sources of these packages, and unpackaging them in third-party/qca (see the INSTALL file for the exact location of each package). Then, uncomment the 'CONFIG += qca-static' in conf_windows.pri. Building QCA ------------ * Edit crypto_win.prf and change the QCA_PATH to the dir where you unpacked QCA. Then, change -lqca to -lqca2. * Copy crypto_win.prf to the mkspecs/features subdir of your Qt dir, and rename it to crypto.prf. * In the src/ dir, run the following commands: qmake mingw32-make * Copy lib/qca2.dll to your system dir (e.g. \Windows\System32) Building the QCA OpenSSL plugin ------------------------------- * Go to the dir where you unpacked OpenSSL. In the lib\MingW subdir of that dir, copy the files ssleay32.a and libeay32.a to ssleay32.lib and libeay32.lib respectively. * Edit qca-openssl.pro, and change the OPENSSL_PREFIX in the windows section to point to the dir where you installed OpenSSL (e.g. C:/OpenSSL). Change -L$$OPENSSL_PREFIX/lib into -L$$OPENSSL_PREFIX/lib/MingW. * Run the following commands: qmake mingw32-make Building Psi ------------ * Edit src/src.pro in the Psi tree, and remove the line CONFIG += debug. * From the toplevel dir in your tree, run the following commands: qmake mingw32-make * There should now be a binary psi.exe in src/release. Packaging Psi ------------- To package everything up into a self-containing dir, edit win32/Makefile to reflect your local setup, and run mingw32-make -C win32 This should make a dir win32/psi with all the necessary files. psi-plus-snapshots-1.4.1456/doc/doxygen.css000066400000000000000000000206401370065651000205110ustar00rootroot00000000000000BODY,H1,H2,H3,H4,H5,H6,P,CENTER,TD,TH,UL,DL,DIV { font-family: Geneva, Arial, Helvetica, sans-serif; } BODY,TD { font-size: 90%; } H1 { text-align: center; font-size: 160%; } H2 { font-size: 120%; } H3 { font-size: 100%; } CAPTION { font-weight: bold } DIV.qindex { width: 100%; background-color: #e8eef2; border: 1px solid #84b0c7; text-align: center; margin: 2px; padding: 2px; line-height: 140%; } DIV.navpath { width: 100%; background-color: #e8eef2; border: 1px solid #84b0c7; text-align: center; margin: 2px; padding: 2px; line-height: 140%; } DIV.navtab { background-color: #e8eef2; border: 1px solid #84b0c7; text-align: center; margin: 2px; margin-right: 15px; padding: 2px; } TD.navtab { font-size: 70%; } A.qindex { text-decoration: none; font-weight: bold; color: #1A419D; } A.qindex:visited { text-decoration: none; font-weight: bold; color: #1A419D } A.qindex:hover { text-decoration: none; background-color: #ddddff; } A.qindexHL { text-decoration: none; font-weight: bold; background-color: #6666cc; color: #ffffff; border: 1px double #9295C2; } A.qindexHL:hover { text-decoration: none; background-color: #6666cc; color: #ffffff; } A.qindexHL:visited { text-decoration: none; background-color: #6666cc; color: #ffffff } A.el { text-decoration: none; font-weight: bold } A.elRef { font-weight: bold } A.code:link { text-decoration: none; font-weight: normal; color: #0000FF } A.code:visited { text-decoration: none; font-weight: normal; color: #0000FF } A.codeRef:link { font-weight: normal; color: #0000FF } A.codeRef:visited { font-weight: normal; color: #0000FF } A:hover { text-decoration: none; background-color: #f2f2ff } DL.el { margin-left: -1cm } .fragment { font-family: monospace, fixed; font-size: 95%; } PRE.fragment { border: 1px solid #CCCCCC; background-color: #f5f5f5; margin-top: 4px; margin-bottom: 4px; margin-left: 2px; margin-right: 8px; padding-left: 6px; padding-right: 6px; padding-top: 4px; padding-bottom: 4px; } DIV.ah { background-color: black; font-weight: bold; color: #ffffff; margin-bottom: 3px; margin-top: 3px } DIV.groupHeader { margin-left: 16px; margin-top: 12px; margin-bottom: 6px; font-weight: bold; } DIV.groupText { margin-left: 16px; font-style: italic; font-size: 90% } BODY { background: white; color: black; margin-right: 20px; margin-left: 20px; } TD.indexkey { background-color: #e8eef2; font-weight: bold; padding-right : 10px; padding-top : 2px; padding-left : 10px; padding-bottom : 2px; margin-left : 0px; margin-right : 0px; margin-top : 2px; margin-bottom : 2px; border: 1px solid #CCCCCC; } TD.indexvalue { background-color: #e8eef2; font-style: italic; padding-right : 10px; padding-top : 2px; padding-left : 10px; padding-bottom : 2px; margin-left : 0px; margin-right : 0px; margin-top : 2px; margin-bottom : 2px; border: 1px solid #CCCCCC; } TR.memlist { background-color: #f0f0f0; } P.formulaDsp { text-align: center; } IMG.formulaDsp { } IMG.formulaInl { vertical-align: middle; } SPAN.keyword { color: #008000 } SPAN.keywordtype { color: #604020 } SPAN.keywordflow { color: #e08000 } SPAN.comment { color: #800000 } SPAN.preprocessor { color: #806020 } SPAN.stringliteral { color: #002080 } SPAN.charliteral { color: #008080 } SPAN.vhdldigit { color: #ff00ff } SPAN.vhdlchar { color: #000000 } SPAN.vhdlkeyword { color: #700070 } SPAN.vhdllogic { color: #ff0000 } .mdescLeft { padding: 0px 8px 4px 8px; font-size: 80%; font-style: italic; background-color: #FAFAFA; border-top: 1px none #E0E0E0; border-right: 1px none #E0E0E0; border-bottom: 1px none #E0E0E0; border-left: 1px none #E0E0E0; margin: 0px; } .mdescRight { padding: 0px 8px 4px 8px; font-size: 80%; font-style: italic; background-color: #FAFAFA; border-top: 1px none #E0E0E0; border-right: 1px none #E0E0E0; border-bottom: 1px none #E0E0E0; border-left: 1px none #E0E0E0; margin: 0px; } .memItemLeft { padding: 1px 0px 0px 8px; margin: 4px; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-top-color: #E0E0E0; border-right-color: #E0E0E0; border-bottom-color: #E0E0E0; border-left-color: #E0E0E0; border-top-style: solid; border-right-style: none; border-bottom-style: none; border-left-style: none; background-color: #FAFAFA; font-size: 80%; } .memItemRight { padding: 1px 8px 0px 8px; margin: 4px; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-top-color: #E0E0E0; border-right-color: #E0E0E0; border-bottom-color: #E0E0E0; border-left-color: #E0E0E0; border-top-style: solid; border-right-style: none; border-bottom-style: none; border-left-style: none; background-color: #FAFAFA; font-size: 80%; } .memTemplItemLeft { padding: 1px 0px 0px 8px; margin: 4px; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-top-color: #E0E0E0; border-right-color: #E0E0E0; border-bottom-color: #E0E0E0; border-left-color: #E0E0E0; border-top-style: none; border-right-style: none; border-bottom-style: none; border-left-style: none; background-color: #FAFAFA; font-size: 80%; } .memTemplItemRight { padding: 1px 8px 0px 8px; margin: 4px; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-top-color: #E0E0E0; border-right-color: #E0E0E0; border-bottom-color: #E0E0E0; border-left-color: #E0E0E0; border-top-style: none; border-right-style: none; border-bottom-style: none; border-left-style: none; background-color: #FAFAFA; font-size: 80%; } .memTemplParams { padding: 1px 0px 0px 8px; margin: 4px; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-top-color: #E0E0E0; border-right-color: #E0E0E0; border-bottom-color: #E0E0E0; border-left-color: #E0E0E0; border-top-style: solid; border-right-style: none; border-bottom-style: none; border-left-style: none; color: #606060; background-color: #FAFAFA; font-size: 80%; } .search { color: #003399; font-weight: bold; } FORM.search { margin-bottom: 0px; margin-top: 0px; } INPUT.search { font-size: 75%; color: #000080; font-weight: normal; background-color: #e8eef2; } TD.tiny { font-size: 75%; } a { color: #1A41A8; } a:visited { color: #2A3798; } .dirtab { padding: 4px; border-collapse: collapse; border: 1px solid #84b0c7; } TH.dirtab { background: #e8eef2; font-weight: bold; } HR { height: 1px; border: none; border-top: 1px solid black; } /* Style for detailed member documentation */ .memtemplate { font-size: 80%; color: #606060; font-weight: normal; margin-left: 3px; } .memnav { background-color: #e8eef2; border: 1px solid #84b0c7; text-align: center; margin: 2px; margin-right: 15px; padding: 2px; } .memitem { padding: 4px; background-color: #eef3f5; border-width: 1px; border-style: solid; border-color: #dedeee; -moz-border-radius: 8px 8px 8px 8px; } .memname { white-space: nowrap; font-weight: bold; } .memdoc{ padding-left: 10px; } .memproto { background-color: #d5e1e8; width: 100%; border-width: 1px; border-style: solid; border-color: #84b0c7; font-weight: bold; -moz-border-radius: 8px 8px 8px 8px; } .paramkey { text-align: right; } .paramtype { white-space: nowrap; } .paramname { color: #602020; font-style: italic; white-space: nowrap; } /* End Styling for detailed member documentation */ /* for the tree view */ .ftvtree { font-family: sans-serif; margin:0.5em; } .directory { font-size: 9pt; font-weight: bold; } .directory h3 { margin: 0px; margin-top: 1em; font-size: 11pt; } .directory > h3 { margin-top: 0; } .directory p { margin: 0px; white-space: nowrap; } .directory div { display: none; margin: 0px; } .directory img { vertical-align: -30%; } psi-plus-snapshots-1.4.1456/doc/doxygen.footer.html000066400000000000000000000004171370065651000221620ustar00rootroot00000000000000
Generated on $datetime for $projectname by doxygen $doxygenversion
psi-plus-snapshots-1.4.1456/doc/doxygen.header.html000066400000000000000000000004761370065651000221210ustar00rootroot00000000000000 $title psi-plus-snapshots-1.4.1456/generate-single-repo.sh000077500000000000000000000207211370065651000221300ustar00rootroot00000000000000#! /bin/sh # Author: Boris Pek # License: GPLv2 or later # Created: 2012-02-13 # Updated: 2020-05-22 # Version: N/A set -e export SNAPSHOTS_DIR="$(dirname $(realpath -s ${0}))" export MAIN_DIR="$(realpath -s ${SNAPSHOTS_DIR}/..)" PSI_URL=https://github.com/psi-im/psi.git PLUGINS_URL=https://github.com/psi-im/plugins.git PSIMEDIA_URL=https://github.com/psi-im/psimedia.git RESOURCES_URL=https://github.com/psi-im/resources.git PATCHES_URL=https://github.com/psi-plus/main.git # Test Internet connection: host github.com > /dev/null cd "${SNAPSHOTS_DIR}" if [ "${1}" = "push" ]; then git push git push --tags exit 0 fi SNAPSHOTS_URL="$(git remote -v | grep '(fetch)')" if [ "$(echo ${SNAPSHOTS_URL} | grep 'https://' | wc -l)" = "1" ]; then echo "Updating ${SNAPSHOTS_DIR}" git checkout HEAD . git pull --all --prune -f echo; fi MOD=psi if [ -d "${MAIN_DIR}/${MOD}" ]; then echo "Updating ${MAIN_DIR}/${MOD}" cd "${MAIN_DIR}/${MOD}" git pull --all --prune -f git submodule init git submodule update echo; else echo "Creating ${MAIN_DIR}/${MOD}" cd "${MAIN_DIR}" git clone "${PSI_URL}" cd "${MAIN_DIR}/${MOD}" git submodule init git submodule update echo; fi MOD=main if [ -d "${MAIN_DIR}/${MOD}" ]; then echo "Updating ${MAIN_DIR}/${MOD}" cd "${MAIN_DIR}/${MOD}" git pull --all --prune -f echo; else echo "Creating ${MAIN_DIR}/${MOD}" cd "${MAIN_DIR}" git clone "${PATCHES_URL}" echo; fi MOD=plugins if [ -d "${MAIN_DIR}/${MOD}" ]; then echo "Updating ${MAIN_DIR}/${MOD}" cd "${MAIN_DIR}/${MOD}" git pull --all --prune -f echo; else echo "Creating ${MAIN_DIR}/${MOD}" cd "${MAIN_DIR}" git clone "${PLUGINS_URL}" echo; fi MOD=psimedia if [ -d "${MAIN_DIR}/${MOD}" ]; then echo "Updating ${MAIN_DIR}/${MOD}" cd "${MAIN_DIR}/${MOD}" git pull --all --prune -f echo; else echo "Creating ${MAIN_DIR}/${MOD}" cd "${MAIN_DIR}" git clone "${PSIMEDIA_URL}" echo; fi MOD=resources if [ -d "${MAIN_DIR}/${MOD}" ]; then echo "Updating ${MAIN_DIR}/${MOD}" cd "${MAIN_DIR}/${MOD}" git pull --all --prune -f echo; else echo "Creating ${MAIN_DIR}/${MOD}" cd "${MAIN_DIR}" git clone "${RESOURCES_URL}" echo; fi cd "${SNAPSHOTS_DIR}" echo "Checking for updates..." PSI_OLD_HASH=$(cd "${SNAPSHOTS_DIR}" && git show -s --pretty='format:%B' | sed -ne 's/^\* psi: \(.*\)$/\1/p') PATCHES_OLD_HASH=$(cd "${SNAPSHOTS_DIR}" && git show -s --pretty='format:%B' | sed -ne 's/^\* patches: \(.*\)$/\1/p') PLUGINS_OLD_HASH=$(cd "${SNAPSHOTS_DIR}" && git show -s --pretty='format:%B' | sed -ne 's/^\* plugins: \(.*\)$/\1/p') PSIMEDIA_OLD_HASH=$(cd "${SNAPSHOTS_DIR}" && git show -s --pretty='format:%B' | sed -ne 's/^\* psimedia: \(.*\)$/\1/p') RESOURCES_OLD_HASH=$(cd "${SNAPSHOTS_DIR}" && git show -s --pretty='format:%B' | sed -ne 's/^\* resources: \(.*\)$/\1/p') PSI_NEW_HASH=$(cd "${MAIN_DIR}/psi" && git show -s --pretty='format:%h') PATCHES_NEW_HASH=$(cd "${MAIN_DIR}/main" && git show -s --pretty='format:%h') PLUGINS_NEW_HASH=$(cd "${MAIN_DIR}/plugins" && git show -s --pretty='format:%h') PSIMEDIA_NEW_HASH=$(cd "${MAIN_DIR}/psimedia" && git show -s --pretty='format:%h') RESOURCES_NEW_HASH=$(cd "${MAIN_DIR}/resources" && git show -s --pretty='format:%h') if [ "${PSI_OLD_HASH}" = "${PSI_NEW_HASH}" ] && \ [ "${PATCHES_OLD_HASH}" = "${PATCHES_NEW_HASH}" ] && \ [ "${PLUGINS_OLD_HASH}" = "${PLUGINS_NEW_HASH}" ] && \ [ "${PSIMEDIA_OLD_HASH}" = "${PSIMEDIA_NEW_HASH}" ] && \ [ "${RESOURCES_OLD_HASH}" = "${RESOURCES_NEW_HASH}" ]; then echo "Updating is not required!"; git checkout HEAD . echo; exit 0; fi echo "Updating is required!"; echo; cd "${SNAPSHOTS_DIR}" echo "Starting Psi+ update..." find . -type f | \ grep -v "^\./\.git" | \ grep -v "^\./generate-single-repo.sh$" | \ grep -v "^\./README$" | \ while read var; do rm "$var"; done find . -depth -type d -empty -exec rmdir {} \; echo "* Directory is cleaned." # Some paranoid checks: for FILE in generate-single-repo.sh README .gitignore; do if [ ! -e "${SNAPSHOTS_DIR}/${FILE}" ]; then wget -c "https://raw.github.com/psi-plus/psi-plus-snapshots/master/${FILE}" fi done chmod uog+x generate-single-repo.sh cp -f "${SNAPSHOTS_DIR}/README" "${MAIN_DIR}/README" cp -f "${SNAPSHOTS_DIR}/.gitignore" "${MAIN_DIR}/.gitignore" rsync -a --del "${MAIN_DIR}/psi/" "${SNAPSHOTS_DIR}/" \ --exclude=".git*" \ --exclude="/builddir*" \ --exclude="/generate-single-repo.sh" \ --exclude="/README.md" \ --exclude="/README" mv "${MAIN_DIR}/README" "${SNAPSHOTS_DIR}/README" mv "${MAIN_DIR}/.gitignore" "${SNAPSHOTS_DIR}/.gitignore" echo "* Files from Psi project are copied." failed_to_apply_patches() { remove_trash git checkout HEAD . echo "* Failed to apply patches from Psi+ project!" exit 1 } remove_trash() { rm -rf *.exe rm -rf */*.exe rm -rf src/libpsi/tools/idle/win32/ find . -type f -name "*.orig" -delete } # cat "${MAIN_DIR}/main/patches"/*.diff | \ # patch -d "${SNAPSHOTS_DIR}" -p1 2>&1 > \ # "${MAIN_DIR}/applying-patches.log" || failed_to_apply_patches FROM_STR="option(PSI_PLUS .*$" TO_STR="option(PSI_PLUS \"Build Psi+ client instead of Psi\" ON)" sed -i "s|${FROM_STR}|${TO_STR}|g" CMakeLists.txt echo "* Patches from Psi+ project are applied." mkdir -p "${SNAPSHOTS_DIR}/patches" rsync -a "${MAIN_DIR}/main/patches/dev" "${SNAPSHOTS_DIR}/patches/" rsync -a "${MAIN_DIR}/main/patches/mac" "${SNAPSHOTS_DIR}/patches/" echo "* Extra patches from Psi+ project are copied." rsync -a "${MAIN_DIR}/plugins" "${SNAPSHOTS_DIR}/" \ --exclude=".git*" \ --exclude="/builddir*" rsync -a "${MAIN_DIR}/psimedia" "${SNAPSHOTS_DIR}/plugins/generic/" \ --exclude=".git*" \ --exclude="/builddir*" echo "* Plugins from Psi project are copied." rsync -a "${MAIN_DIR}/main/admin/" "${SNAPSHOTS_DIR}/admin/" echo "* Extra scripts from Psi+ project are copied." rsync -a "${MAIN_DIR}/resources/sound/" "${SNAPSHOTS_DIR}/sound/" echo "* Extra sound files from Psi project are copied." cp "${MAIN_DIR}/main/ChangeLog.Psi+.txt" "${SNAPSHOTS_DIR}/ChangeLog.Psi+.txt" echo "* ChangeLog from Psi+ project is copied." remove_trash echo "* Trash is removed." cp -a "${MAIN_DIR}/psi/win32"/*.rc* "${SNAPSHOTS_DIR}/win32/" cp -a "${MAIN_DIR}/psi/win32"/*.cmake "${SNAPSHOTS_DIR}/win32/" cp -a "${MAIN_DIR}/psi/win32"/*.manifest "${SNAPSHOTS_DIR}/win32/" echo "* Some files for MS Windows builds are copied." cp -a "${SNAPSHOTS_DIR}/mac/application-plus.icns" \ "${SNAPSHOTS_DIR}/mac/application.icns" echo "* Some files for macOS builds are copied." echo; # Update repo and make analysis git add -A . TEST_ALL=$(LC_ALL=C git status | grep ": " | grep -v " generate-single-repo.sh" | \ grep -v " README" | \ grep -v " version" | \ wc -l) if [ "${TEST_ALL}" = "0" ]; then echo "Updating is not required!"; git checkout HEAD . echo; exit 0; fi REVISION_DATE_LIST="$(cd ${MAIN_DIR}/psi && git log -n1 --date=short --pretty=format:'%ad') $(cd ${MAIN_DIR}/main && git log -n1 --date=short --pretty=format:'%ad') $(cd ${MAIN_DIR}/plugins && git log -n1 --date=short --pretty=format:'%ad') $(cd ${MAIN_DIR}/resources && git log -n1 --date=short --pretty=format:'%ad')" LAST_REVISION_DATE=$(echo "${REVISION_DATE_LIST}" | sort -r | head -n1) PATCHES_VERSION="$(cd ${MAIN_DIR}/main && git describe --tags | cut -d - -f1)" PATCHES_NUM="$(cd ${MAIN_DIR}/main && git describe --tags | cut -d - -f2)" PSI_NUM="0" if [ "$(cd ${MAIN_DIR}/psi && git tag | grep -x "^${PATCHES_VERSION}$" | wc -l)" = "1" ]; then PSI_NUM="$(cd ${MAIN_DIR}/psi && git rev-list --count ${PATCHES_VERSION}..HEAD)" fi NEW_VER="${PATCHES_VERSION}.$(expr ${PSI_NUM} + ${PATCHES_NUM})" OLD_VER=$(cd "${SNAPSHOTS_DIR}" && git tag -l | sort -V | tail -n1) echo "OLD_VER = ${OLD_VER}" echo "NEW_VER = ${NEW_VER}" echo; echo "${NEW_VER} (${LAST_REVISION_DATE}, Psi:${PSI_NEW_HASH}, Psi+:${PATCHES_NEW_HASH})" > version echo "Version file is created:" cat version echo; COMMENT="Current version of Psi+ is ${NEW_VER} It is based on: * psi: ${PSI_NEW_HASH} * patches: ${PATCHES_NEW_HASH} * plugins: ${PLUGINS_NEW_HASH} * psimedia: ${PSIMEDIA_NEW_HASH} * resources: ${RESOURCES_NEW_HASH} " echo "${COMMENT}" git commit -a -m "${COMMENT}" 2>&1 > /dev/null if [ "${NEW_VER}" != "${OLD_VER}" ]; then git tag "${NEW_VER}" echo "Git tag \"${NEW_VER}\" is created." fi echo; psi-plus-snapshots-1.4.1456/icondef.xml.in000066400000000000000000000555561370065651000203310ustar00rootroot00000000000000 Crystal - System (default) 1.0 Crystal System Iconset 2005-11-23 https://psi-im.org/ Jason Kim (Logo and misc. icons) Everaldo Coelho (Crystal icons) psi/appearance appearance.png psi/account account.png psi/action_direct_presence action_direct_presence.png psi/statusmsg info.png psi/addContact add.png psi/add addx.png psi/arrowUp arrow_up.png psi/arrowDown arrow_down.png psi/arrowLeft arrow_left.png psi/arrowRight arrow_right.png psi/profile changeacc.png psi/clearChat chatclear.png psi/compact ok.png psi/default_avatar default_avatar.png psi/disco disco.png psi/events events.png psi/groupChat groupchat.png psi/help help.png psi/history history.png psi/manageContact manage_contact.png psi/notification_chat_delivery_ok notification_chat_delivery_ok.png psi/notification_chat_info notification_chat_info.png psi/notification_chat_receive notification_chat_receive.png psi/notification_chat_send notification_chat_send.png psi/notification_chat_time notification_chat_time.png psi/notification_chat_delivery_ok_encrypted notification_chat_delivery_ok_encrypted.png psi/notification_chat_receive_encrypted notification_chat_receive_encrypted.png psi/notification_chat_send_encrypted notification_chat_send_encrypted.png psi/action_muc_hide action_muc_hide.png psi/action_muc_leave action_muc_leave.png psi/action_muc_show action_muc_show.png psi/action_templates action_templates.png psi/action_templates_edit action_templates_edit.png psi/shortcuts shortcuts.png psi/vCard vcard.png psi/jabber jabber.png psi/options options.png psi/roster_icon roster_icon.png psi/toolbars configure_toolbars.png psi/configure-room configure-room.png psi/keys64 keys_64.png psi/pgp pgp.png psi/gpg-yes key.png psi/gpg-no key.png psi/keyBad key_bad.png psi/keyUnknown key_unknown.png psi/playSounds play_sounds.png psi/voice play_sounds.png psi/main @PSILOGO_PREFIX@@MAIN_ICON@ psi/quit quit.png psi/register register.png psi/reload reload.png psi/stop stop.png psi/remove remove.png psi/search search.png psi/status status.png psi/sendMessage send.png psi/start-chat start-chat.png psi/cryptoYes ssl_yes.png psi/cryptoNo ssl_no.png psi/time time.png psi/topic topic.svg psi/www url.png psi/xml xml.png psi/logo_16 @PSILOGO_PREFIX@logo_16.png psi/logo_32 @PSILOGO_PREFIX@logo_32.png psi/logo_48 @PSILOGO_PREFIX@logo_48.png psi/logo_64 @PSILOGO_PREFIX@logo_64.png psi/logo_128 @PSILOGO_PREFIX@logo_128.png psi/logo @PSILOGO_PREFIX@psilogo.png psi/gst_logo gstreamer-logo-50.png psi/smile smile.png psi/email send.png psi/ok ok.png psi/done close.png psi/closetab closetab.png psi/cancel cancel.png psi/close close.png psi/apply ok.png psi/info info.png psi/browse browse.png psi/play play.png psi/eye eye_blue.png psi/upload upload.png psi/share_file share_file.png psi/download download.png psi/filemanager filemanager.png psi/command command.png psi/action_button_send action_button_send.png psi/autojid autojid.png psi/ignore_global_actions ignore_global_actions.png psi/roster url.png psi/plugins plugins.png psi/advanced advanced.png psi/advanced-plus advanced-plus.png psi/avcall avcall.png psi/bookmarks bookmarks.png psi/show_self self.png psi/show_offline show_offline.png psi/show_away show_away.png psi/show_hidden show_hidden.png psi/publishTune publish_tune.png psi/save filemanager.png psi/export export.png psi/import import.png psi/throbber animation throbber.png psi/text text.png psi/enable-groups enable-groups.png psi/bookmark_add bookmark_add.png psi/bookmark_remove bookmark_remove.png pep/geolocation geolocation.png pep/activities activity.png pep/mood mood.png pep/tune tune.png psi/whiteboard whiteboarding/whiteboard.png psi/saveBoard whiteboarding/save.png psi/select whiteboarding/select.png psi/translate whiteboarding/translate.png psi/rotate whiteboarding/rotate.png psi/scale whiteboarding/scale.png psi/scroll whiteboarding/scroll.png psi/erase whiteboarding/erase.png psi/drawPaths whiteboarding/draw_paths.png psi/drawLines whiteboarding/draw_lines.png psi/drawEllipses whiteboarding/draw_ellipses.png psi/drawCircles whiteboarding/draw_circles.png psi/drawRectangles whiteboarding/draw_rectangles.png psi/addText whiteboarding/add_text.png psi/addImage whiteboarding/add_image.png psi/bringForwards whiteboarding/bring_forwards.png psi/bringToFront whiteboarding/bring_to_front.png psi/sendBackwards whiteboarding/send_backwards.png psi/sendToBack whiteboarding/send_to_back.png psi/group whiteboarding/group.png psi/ungroup whiteboarding/ungroup.png psi/action_contacts_manager action_contacts_manager.png psi/cm_check cm_check.png psi/cm_invertcheck cm_invertcheck.png psi/cm_uncheck cm_uncheck.png psi/cm_export cm_export.png psi/cm_import cm_import.png psi/xmpp xmpp.svg psi/mic mic.png psi/pin pin.png psi/action_paste_and_send action_paste_and_send.png psi/mic_rec mic_rec.png loggerplugin/openlog history.png psi/action_vcard_restore psiplus/action_vcard_restore.png psi/action_vcard_save_as psiplus/action_vcard_save_as.png psi/psiplus_logo psiplus/psiplus_logo.png psi/doubleNextArrow psiplus/doublenextarrow.png psi/doubleBackArrow psiplus/doublebackarrow.png psi/crop crop.png psi/draw draw.png psi/frame frame.png psi/palette palette.png psi/undo undo.png psi/print print.png psi-plus-snapshots-1.4.1456/iconsets.qrc.in000066400000000000000000000614511370065651000205250ustar00rootroot00000000000000 iconsets/roster/default/ask.png iconsets/roster/default/away.png iconsets/roster/default/call.png iconsets/roster/default/chat.fading.png iconsets/roster/default/online.dimming.png iconsets/roster/default/dnd.png iconsets/roster/default/chatty.png iconsets/roster/default/file.fading.png iconsets/roster/default/groupclose.png iconsets/roster/default/groupopen.png iconsets/roster/default/headline.png iconsets/roster/default/icondef.xml iconsets/roster/default/invisible.png iconsets/roster/default/message.fading.png iconsets/roster/default/noauth.png iconsets/roster/default/offline.png iconsets/roster/default/online.png iconsets/roster/default/perr.png iconsets/roster/default/system.png iconsets/roster/default/xa.png iconsets/roster/default/typing.png iconsets/system/default/account.png iconsets/system/default/add.png iconsets/system/default/addx.png iconsets/system/default/plugins.png iconsets/system/default/advanced.png iconsets/system/default/advanced-plus.png iconsets/system/default/avcall.png iconsets/system/default/bookmarks.png iconsets/system/default/appearance.png iconsets/system/default/arrow_down.png iconsets/system/default/arrow_left.png iconsets/system/default/arrow_right.png iconsets/system/default/arrow_up.png iconsets/system/default/browse.png iconsets/system/default/cancel.png iconsets/system/default/changeacc.png iconsets/system/default/chatclear.png iconsets/system/default/close.png iconsets/system/default/closetab.png iconsets/system/default/configure_toolbars.png iconsets/system/default/configure-room.png iconsets/system/default/disco.png iconsets/system/default/download.png iconsets/system/default/events.png iconsets/system/default/eye_blue.png iconsets/system/default/filemanager.png iconsets/system/default/import.png iconsets/system/default/export.png iconsets/system/default/groupchat.png iconsets/system/default/gstreamer-logo-50.png iconsets/system/default/help.png iconsets/system/default/history.png iconsets/system/default/@PSILOGO_PREFIX@logo_16.png iconsets/system/default/@PSILOGO_PREFIX@logo_32.png iconsets/system/default/@PSILOGO_PREFIX@logo_48.png iconsets/system/default/@PSILOGO_PREFIX@logo_64.png iconsets/system/default/@PSILOGO_PREFIX@logo_128.png iconsets/system/default/icondef.xml iconsets/system/default/info.png iconsets/system/default/jabber.png iconsets/system/default/key.png iconsets/system/default/key_bad.png iconsets/system/default/key_unknown.png iconsets/system/default/manage_contact.png iconsets/system/default/mic.png iconsets/system/default/mic_rec.png iconsets/system/default/ok.png iconsets/system/default/command.png iconsets/system/default/options.png iconsets/system/default/roster_icon.png iconsets/system/default/pin.png iconsets/system/default/pgp.png iconsets/system/default/play.png iconsets/system/default/play_sounds.png iconsets/system/default/@PSILOGO_PREFIX@psilogo.png iconsets/system/default/psimain.png iconsets/system/default/quit.png iconsets/system/default/register.png iconsets/system/default/reload.png iconsets/system/default/remove.png iconsets/system/default/search.png iconsets/system/default/self.png iconsets/system/default/send.png iconsets/system/default/share_file.png iconsets/system/default/show_away.png iconsets/system/default/show_hidden.png iconsets/system/default/show_offline.png iconsets/system/default/shortcuts.png iconsets/system/default/smile.png iconsets/system/default/ssl_no.png iconsets/system/default/ssl_yes.png iconsets/system/default/start-chat.png iconsets/system/default/status.png iconsets/system/default/stop.png iconsets/system/default/time.png iconsets/system/default/topic.svg iconsets/system/default/upload.png iconsets/system/default/url.png iconsets/system/default/vcard.png iconsets/system/default/xml.png iconsets/system/default/publish_tune.png iconsets/system/default/tune.png iconsets/system/default/geolocation.png iconsets/system/default/activity.png iconsets/system/default/mood.png iconsets/system/default/default_avatar.png iconsets/system/default/notification_chat_delivery_ok.png iconsets/system/default/notification_chat_info.png iconsets/system/default/notification_chat_receive.png iconsets/system/default/notification_chat_send.png iconsets/system/default/notification_chat_time.png iconsets/system/default/notification_chat_delivery_ok_encrypted.png iconsets/system/default/notification_chat_receive_encrypted.png iconsets/system/default/notification_chat_send_encrypted.png iconsets/system/default/throbber.png iconsets/system/default/text.png iconsets/system/default/enable-groups.png iconsets/system/default/bookmark_add.png iconsets/system/default/bookmark_remove.png iconsets/system/default/action_muc_hide.png iconsets/system/default/action_muc_leave.png iconsets/system/default/action_muc_show.png iconsets/system/default/action_templates_edit.png iconsets/system/default/action_templates.png iconsets/system/default/action_button_send.png iconsets/system/default/autojid.png iconsets/system/default/ignore_global_actions.png iconsets/system/default/action_direct_presence.png iconsets/system/default/action_contacts_manager.png iconsets/system/default/action_paste_and_send.png iconsets/system/default/cm_check.png iconsets/system/default/cm_invertcheck.png iconsets/system/default/cm_uncheck.png iconsets/system/default/cm_export.png iconsets/system/default/cm_import.png iconsets/system/default/psiplus/psiplus_icon.png iconsets/system/default/psiplus/action_vcard_restore.png iconsets/system/default/psiplus/action_vcard_save_as.png iconsets/system/default/psiplus/psiplus_logo.png iconsets/system/default/psiplus/doublenextarrow.png iconsets/system/default/psiplus/doublebackarrow.png iconsets/system/default/crop.png iconsets/system/default/draw.png iconsets/system/default/frame.png iconsets/system/default/palette.png iconsets/system/default/undo.png iconsets/system/default/print.png iconsets/system/default/keys_64.png iconsets/system/default/whiteboarding/whiteboard.png iconsets/system/default/whiteboarding/save.png iconsets/system/default/whiteboarding/select.png iconsets/system/default/whiteboarding/translate.png iconsets/system/default/whiteboarding/rotate.png iconsets/system/default/whiteboarding/scale.png iconsets/system/default/whiteboarding/scroll.png iconsets/system/default/whiteboarding/erase.png iconsets/system/default/whiteboarding/draw_paths.png iconsets/system/default/whiteboarding/draw_lines.png iconsets/system/default/whiteboarding/draw_ellipses.png iconsets/system/default/whiteboarding/draw_circles.png iconsets/system/default/whiteboarding/draw_rectangles.png iconsets/system/default/whiteboarding/add_text.png iconsets/system/default/whiteboarding/add_image.png iconsets/system/default/whiteboarding/bring_forwards.png iconsets/system/default/whiteboarding/bring_to_front.png iconsets/system/default/whiteboarding/send_backwards.png iconsets/system/default/whiteboarding/send_to_back.png iconsets/system/default/whiteboarding/group.png iconsets/system/default/whiteboarding/ungroup.png iconsets/system/default/xmpp.svg iconsets/emoticons/default/angry.png iconsets/emoticons/default/beer.png iconsets/emoticons/default/broken-heart.png iconsets/emoticons/default/coffee.png iconsets/emoticons/default/country-by.png iconsets/emoticons/default/country-kz.png iconsets/emoticons/default/country-ru.png iconsets/emoticons/default/country-ua.png iconsets/emoticons/default/cry.png iconsets/emoticons/default/fingerdown.png iconsets/emoticons/default/fingerleft.png iconsets/emoticons/default/fingerright.png iconsets/emoticons/default/fingerup.png iconsets/emoticons/default/fire.png iconsets/emoticons/default/flower.png iconsets/emoticons/default/frow.png iconsets/emoticons/default/grinning.png iconsets/emoticons/default/heart.png iconsets/emoticons/default/hi.png iconsets/emoticons/default/icondef.xml iconsets/emoticons/default/joy.png iconsets/emoticons/default/kiss.png iconsets/emoticons/default/music.png iconsets/emoticons/default/neutral.png iconsets/emoticons/default/no.png iconsets/emoticons/default/phone.png iconsets/emoticons/default/police.png iconsets/emoticons/default/signofthehorns.png iconsets/emoticons/default/smile.png iconsets/emoticons/default/star.png iconsets/emoticons/default/sun.png iconsets/emoticons/default/thinking.png iconsets/emoticons/default/tongue.png iconsets/emoticons/default/unhappy.png iconsets/emoticons/default/victory.png iconsets/emoticons/default/wink.png iconsets/emoticons/default/yes.png iconsets/moods/default/Afraid.png iconsets/moods/default/Amazed.png iconsets/moods/default/Angry.png iconsets/moods/default/Amorous.png iconsets/moods/default/Annoyed.png iconsets/moods/default/Anxious.png iconsets/moods/default/Aroused.png iconsets/moods/default/Ashamed.png iconsets/moods/default/Bored.png iconsets/moods/default/Brave.png iconsets/moods/default/Calm.png iconsets/moods/default/Cautious.png iconsets/moods/default/Cold.png iconsets/moods/default/Confident.png iconsets/moods/default/Confused.png iconsets/moods/default/Contemplative.png iconsets/moods/default/Contented.png iconsets/moods/default/Cranky.png iconsets/moods/default/Crazy.png iconsets/moods/default/Creative.png iconsets/moods/default/Curious.png iconsets/moods/default/Dejected.png iconsets/moods/default/Depressed.png iconsets/moods/default/Disappointed.png iconsets/moods/default/Disgusted.png iconsets/moods/default/Dismayed.png iconsets/moods/default/Distracted.png iconsets/moods/default/Embarrassed.png iconsets/moods/default/Envious.png iconsets/moods/default/Excited.png iconsets/moods/default/Flirtatious.png iconsets/moods/default/Frustrated.png iconsets/moods/default/Grumpy.png iconsets/moods/default/Guilty.png iconsets/moods/default/Happy.png iconsets/moods/default/Hopeful.png iconsets/moods/default/Hot.png iconsets/moods/default/Humbled.png iconsets/moods/default/Humiliated.png iconsets/moods/default/Hungry.png iconsets/moods/default/Hurt.png iconsets/moods/default/Impressed.png iconsets/moods/default/In_awe.png iconsets/moods/default/In_love.png iconsets/moods/default/Indignant.png iconsets/moods/default/Interested.png iconsets/moods/default/Intoxicated.png iconsets/moods/default/Invincible.png iconsets/moods/default/Jealous.png iconsets/moods/default/Lonely.png iconsets/moods/default/Lucky.png iconsets/moods/default/Mean.png iconsets/moods/default/Moody.png iconsets/moods/default/Nervous.png iconsets/moods/default/Neutral.png iconsets/moods/default/Offended.png iconsets/moods/default/Outraged.png iconsets/moods/default/Playful.png iconsets/moods/default/Proud.png iconsets/moods/default/Relaxed.png iconsets/moods/default/Relieved.png iconsets/moods/default/Remorseful.png iconsets/moods/default/Restless.png iconsets/moods/default/Sad.png iconsets/moods/default/Sarcastic.png iconsets/moods/default/Serious.png iconsets/moods/default/Shocked.png iconsets/moods/default/Shy.png iconsets/moods/default/Sick.png iconsets/moods/default/Sleepy.png iconsets/moods/default/Spontaneous.png iconsets/moods/default/Stressed.png iconsets/moods/default/Strong.png iconsets/moods/default/Surprised.png iconsets/moods/default/Thankful.png iconsets/moods/default/Thirsty.png iconsets/moods/default/Tired.png iconsets/moods/default/Undefined.png iconsets/moods/default/Weak.png iconsets/moods/default/Worried.png iconsets/moods/default/icondef.xml iconsets/activities/default/doing_chores.png iconsets/activities/default/doing_chores_buying_groceries.png iconsets/activities/default/doing_chores_cleaning.png iconsets/activities/default/doing_chores_cooking.png iconsets/activities/default/doing_chores_doing_maintenance.png iconsets/activities/default/doing_chores_doing_the_dishes.png iconsets/activities/default/doing_chores_doing_the_laundry.png iconsets/activities/default/doing_chores_gardening.png iconsets/activities/default/doing_chores_running_an_errand.png iconsets/activities/default/doing_chores_walking_the_dog.png iconsets/activities/default/drinking.png iconsets/activities/default/drinking_having_a_beer.png iconsets/activities/default/drinking_having_coffee.png iconsets/activities/default/drinking_having_tea.png iconsets/activities/default/eating.png iconsets/activities/default/eating_having_a_snack.png iconsets/activities/default/eating_having_breakfast.png iconsets/activities/default/eating_having_dinner.png iconsets/activities/default/eating_having_lunch.png iconsets/activities/default/exercising.png iconsets/activities/default/exercising_cycling.png iconsets/activities/default/exercising_dancing.png iconsets/activities/default/exercising_hiking.png iconsets/activities/default/exercising_jogging.png iconsets/activities/default/exercising_playing_sports.png iconsets/activities/default/exercising_running.png iconsets/activities/default/exercising_skiing.png iconsets/activities/default/exercising_swimming.png iconsets/activities/default/exercising_working_out.png iconsets/activities/default/grooming.png iconsets/activities/default/grooming_at_the_spa.png iconsets/activities/default/grooming_brushing_teeth.png iconsets/activities/default/grooming_getting_a_haircut.png iconsets/activities/default/grooming_shaving.png iconsets/activities/default/grooming_taking_a_bath.png iconsets/activities/default/grooming_taking_a_shower.png iconsets/activities/default/having_appointment.png iconsets/activities/default/inactive.png iconsets/activities/default/inactive_day_off.png iconsets/activities/default/inactive_hanging_out.png iconsets/activities/default/inactive_hiding.png iconsets/activities/default/inactive_on_vacation.png iconsets/activities/default/inactive_praying.png iconsets/activities/default/inactive_scheduled_holiday.png iconsets/activities/default/inactive_sleeping.png iconsets/activities/default/inactive_thinking.png iconsets/activities/default/relaxing.png iconsets/activities/default/relaxing_fishing.png iconsets/activities/default/relaxing_gaming.png iconsets/activities/default/relaxing_going_out.png iconsets/activities/default/relaxing_partying.png iconsets/activities/default/relaxing_reading.png iconsets/activities/default/relaxing_rehearsing.png iconsets/activities/default/relaxing_shopping.png iconsets/activities/default/relaxing_smoking.png iconsets/activities/default/relaxing_socializing.png iconsets/activities/default/relaxing_sunbathing.png iconsets/activities/default/relaxing_watching_a_movie.png iconsets/activities/default/relaxing_watching_tv.png iconsets/activities/default/talking.png iconsets/activities/default/talking_in_real_life.png iconsets/activities/default/talking_on_the_phone.png iconsets/activities/default/talking_on_video_phone.png iconsets/activities/default/traveling.png iconsets/activities/default/traveling_commuting.png iconsets/activities/default/traveling_cycling.png iconsets/activities/default/traveling_driving.png iconsets/activities/default/traveling_in_a_car.png iconsets/activities/default/traveling_on_a_bus.png iconsets/activities/default/traveling_on_a_plane.png iconsets/activities/default/traveling_on_a_train.png iconsets/activities/default/traveling_on_a_trip.png iconsets/activities/default/traveling_walking.png iconsets/activities/default/unknown.png iconsets/activities/default/working_working.png iconsets/activities/default/working_coding.png iconsets/activities/default/working_in_a_meeting.png iconsets/activities/default/working_studying.png iconsets/activities/default/working_writing.png iconsets/activities/default/icondef.xml iconsets/affiliations/default/owner.png iconsets/affiliations/default/admin.png iconsets/affiliations/default/member.png iconsets/affiliations/default/noaffiliation.png iconsets/affiliations/default/outcast.png iconsets/affiliations/default/icondef.xml iconsets/clients/default/adium.png iconsets/clients/default/bitlbee.png iconsets/clients/default/bot.png iconsets/clients/default/conv6ations.png iconsets/clients/default/conversations.png iconsets/clients/default/dino.png iconsets/clients/default/gajim.png iconsets/clients/default/icondef.xml iconsets/clients/default/isida-bot.png iconsets/clients/default/jtalk.png iconsets/clients/default/kopete.png iconsets/clients/default/leechcraft-azoth.png iconsets/clients/default/mcabber.png iconsets/clients/default/miranda-ng.png iconsets/clients/default/miranda.png iconsets/clients/default/movim.png iconsets/clients/default/pidgin.png iconsets/clients/default/pix-art.png iconsets/clients/default/poezio.png iconsets/clients/default/profanity.png iconsets/clients/default/psiplus.png iconsets/clients/default/psi.png iconsets/clients/default/qip.png iconsets/clients/default/qutim.png iconsets/clients/default/spark.png iconsets/clients/default/swift.png iconsets/clients/default/tkabber.png iconsets/clients/default/vacuum.png iconsets/clients/default/vk4xmpp.png iconsets/clients/default/xabber.png iconsets/clients/default/yaxim.png psi-plus-snapshots-1.4.1456/iconsets/000077500000000000000000000000001370065651000174025ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iconsets/activities/000077500000000000000000000000001370065651000215465ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iconsets/activities/default/000077500000000000000000000000001370065651000231725ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iconsets/activities/default/doing_chores.png000066400000000000000000000014761370065651000263530ustar00rootroot00000000000000PNG  IHDRa vpAg\ƭIDAT8ˍkHQWaCR%2B?k܆ԥfZLf65eZ^0"XRήe%\P&)Ms^xyρͳYHaWG-Ţ7Leee2UʉJX1cj̲ bx{Hǟ4Y_(K:H9IBūCd!MCN}cCh6 */Uc܀`ǫ9= @ i.46Xv(,:"."$͝Zs[}/ #[ InE& so*Sb82S|i0_ AFtK n셸n7QZ%9Q@ȃZ*Xm, d2٭FvW4]y(kqC^ĺ$Oa?+O}_9~Y>Z?Wa"*JQ=x uF<1?}нգA|JX䅭fVV'67NiV&#T _U#" 3; ˸Dm*ԏY2BqH! `mҦ)r+`=Gt#'{S@2#d\/WukS'@Žİ~J)`@Em(:i9hS Y_s&ZDˡT6ȝhy .֜ݦA/mx5hadO.={^c(Kw/&=@2kfv,H@jm2m~?IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/doing_chores_buying_groceries.png000066400000000000000000000014601370065651000317630ustar00rootroot00000000000000PNG  IHDRaIDAT8c?%708o`i8pgyǫܟոVIm1سeOY9gbXstեkT?rb;w\}_@~O7yXrAv}ʘ>/!2Hau+uwߟyݥN>_]=|Cοy_qmǽɯ~ːĠ؉:oz}{ﺺﺲS?rO;zn`4 e"k6xuOמiWUӺk-yUG|v L`ګo_~h{?O~Vmg+ߔ. ~YU6ӿ@z; hݘ|捉+WYv}軚U/ǪQϪ+bt_APf@ Rz.^|[M2e+U, ~65jA3[Fǣ;iљ Ns"ܓV܌_Ҵv0 /qoqg;rOY|v<ە|~cհꎐX qAd`@#< 6b} 6j ;eFдΐ0u%ӐoX}mƆDqqqs@iV%-@< H^@˚h|F\r0 ebMVwIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/doing_chores_cleaning.png000066400000000000000000000015321370065651000302040ustar00rootroot00000000000000PNG  IHDRagAMA7IDAT8OUKh\u߽~53vbcFt!.BD_ Ew](H-.첻R )BQeKAPm!AL6$L&υC1g}η8" "If %T]~x `TTQO3>`rVXvLAAێk\st~DU9]||i콥Z}^N=.ByX:ը-xĿw"7?5U9]u`x)-J*f8w Mc1yttH55jCAAA8`aa///JJJcccQQX>>GJLN9<`TŶ,,-if0445::knn{gf...pkD6]]]MII!!!;;;*]a````97pU222HHHfffod333iZnd ???vvv{zrrr;,BBBooo~}5':%D><.-!fCByyyQ8VEtRNS}Y:$P8"S<*Pu0IDATWc`&)V(ecb`r!a`ca37[W(& ]Q0ITO("+.QYS,"# '8yԈ3fΚ0OUg|ou R͠ظZ:)inY ny^E`{V60ZM:MA\F3^׾ -,A" V6v `-,N` ]Q3mifIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/doing_chores_doing_maintenance.png000066400000000000000000000013721370065651000320700ustar00rootroot00000000000000PNG  IHDRagAMA7IDAT8c`U3}SReDEp]q qI^jeW]pǩSg^半lnn.7uæ-?Μ[Z657:##MGG!0((}<Б{z,;;;]???#iiiYYYfyyyAAA6[[[ʪ3Wyێwd9=Դpzf֬9֮]wk{病';$y)]pd;qc/\dd2W{i7߮lMA%TR0 hlletimラ}ACC]8ʆ!LisO;Z f6;4C@أ :rM\v?~9Jl"fPg g`uPH<73SY J< TeaPd醒?tNB jĘaPaƐmhSE/:vE>(2x)ϸ:++8T-:|B Ā3]E)/t{?)u}qY% pR4hRz6Wg:rc x(k.G #ͯ4IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/doing_chores_doing_the_dishes.png000066400000000000000000000011721370065651000317230ustar00rootroot00000000000000PNG  IHDRaAIDAT8c`)44WZ 1N] @Rʹn.s#)erpp`a { _y9krI ._,>gڶ)~~S_։gG5oܲ%yAQ];Xoϟ=gO-@JKKO757=kW??'O7wi~TU_0w @ŋO{ugϞoOy>mg?+FJbO>so߼,xfr?@{4Xc,_|M͛/9wYV,Y{e{:; kXn ݷnݺW.]y,X҄ #CC`Cc}8~ȗ9ӧln~X^X#FU\\`rɴiKTDhqG/bIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/doing_chores_doing_the_laundry.png000066400000000000000000000014551370065651000321260ustar00rootroot00000000000000PNG  IHDRaIDAT8uR[HTQ>fPPF`?~DEZ$=( 5QD`I(Y),i3gw :}X{^׾wUWƺ?1uW_'V|$3Ou3'bn5mTyx_MyúS/1|AMv/yA(+`E!;;w\ !kKcnȺ`lj:' ɓCnF-%*ː" Ŵ8t@ ,! ~Ebv>¯8O HEOR#(ѯ'&T n D,6#]HB(ntN&,?'_;)ZF]NoN_AP.P'#SdDv#I!]T} 8yD労tW7G:F RU|fsO\D2, @UE9N29e/LqiB繨hmK'E)ёTexas*֏[=3d\/!hYxma8WhCǟ4`Z_ v]Ll5As{AN*(N7*|8?+ , ,n0Wa)"Ԁ.'d*Aߐ( ]SnÃaQ0 h'zЊp{&\^ F)nZܭqk|Xu^A(rduyIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/doing_chores_gardening.png000066400000000000000000000015671370065651000303720ustar00rootroot00000000000000PNG  IHDR(-SPLTE1 x$*dw#&( 8CW/y$w$q"y$&ecYp!'(5&&u#DQt"b+z$Zb'Hx$,_z$s#')j\aim!>, ,*b(Uq") +P s"Y222k & p"+Fhy$v#r"e7m ,*{$&]-)p!y$&,3G/2,e**1c+,11,&'by$&'&&w#_x#>! b22/21321+,-13*/+&2((p!x$.-/u#*,3@$P{tRNSXhB$EVNXes,.L;~#'̇y G|VU$5eXr~xg|z=Խ7 IDAT=W#WV^#ɖdMV!w״WDˑGG!@RP%$ IS&#,r ,"/bJJWˁʪjEVE |<<6]%tvuB?|Cß#06z ^yd_}-ʕr97,zg3nNv0)/2s0÷M*IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/doing_chores_running_an_errand.png000066400000000000000000000015541370065651000321210ustar00rootroot00000000000000PNG  IHDRa3IDAT8uSklu=ՈD !EIHHH QoGx .Qn,= RF p}Z v=X;h2:ݺCܹg`5 Wm?\&X|a˞ڱ11H+a(FUc#=& |"= x>W aFoqp@q_س׫ᦏh_W G9 gG\YҝhFVX^AsLu㞸7UK ْ M@nBMs\h}EUk!c|J,b]tӯPMM-32 ʆ#ϱ@o>q-ؤ(^ l ,ZMf 'v8kC[+'?M@S6R%@3q* /[IdIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/doing_chores_walking_the_dog.png000066400000000000000000000016651370065651000315600ustar00rootroot00000000000000PNG  IHDRa|IDAT8O=SmLSg~%eØlk~.q"huID VkR"BT$lۜ 2nu &֮R(Ƨrړ<'<'AtV~RM;|=b~3~WZڟ.y:oh;>n5<ח^lG{A8X7v1@i3.:u\̸G[pXYjf܄ւWqi/g SXoGI9YQjm 6Xqp,fy?Edz V8nF&Ng ںʬ-uhmXZsk9(r~dz1d܂lͮw`#9?]]M lӁϫ4)_\qf= 2P-#}BKVD8Ϟ9ӗ5PYY[}'o7t|W"گbazOa(CPD,Antm-jurwr "‘T? cI  4{󑓳/izK+S\E\p`۶,ژ3 teJs3d.< z]VeXX~DS"Id)@s~zrip8'R' |fGF?Aee垴k%Cgi87; <€ǃyZ&`B!A.--ݙ`ts3SSc^~txx{nq)|>@oonG<Ͷt]{CM<__혚kנ>N А~ JARi4yr\P(Sl433CDf*x IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/drinking.png000066400000000000000000000016321370065651000255070ustar00rootroot00000000000000PNG  IHDRaaIDAT8]Te̙3ήRt!FP`]W tVBdDAYݘDFihD`l ~sfO*"wlGm)8M4vwFE&TUZ՚R@}yTX<xMTTQ#+V"_F|p"9 WW"ND2I4 %bHwX1Z?ڍW˺M*]QRӨmְ-!:ۥ nwk V&=eWG/қI])a!w.T5 ʂ+"QG_-s,=6ۥfc@/G$At¦tme 51}z>D7_؊]S)5XDn<ғ67V7kJ kۊfW(V-7IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/drinking_having_a_beer.png000066400000000000000000000015111370065651000303340ustar00rootroot00000000000000PNG  IHDRaIDAT8k\e}g-i"zc*/4 EzP EHR jlf~U5&*N|[3 og}RP< 2[J KU ۝[D?83,..^|NEaZ8F5:. $bQ|. z޸Oꠛ'CMOS/H&Ʀ xFy(b*U[?02"FD55/Wn|`vvV af^ -8&n?WyD"*2u֍/2޹{k$MS_o?gftW/?;51`a88ȌIBktϺ'G~@TU v/KXQ0}DѰ`o@# B)4C)&0h41kj,--  \il@1d%n;h%Wx>xy^֨ 3R4FŞ?15nꌪ/:_?gɤnPzÀFc4Xuu 0g\ꁼYT{{e.p&;ѧ9|PT&4Mw**ԝq{.Gn||WnTV:!r|g:R*-lxllx,b`7v\`41!vyQ!x`Alu# %Fpf;Ac[ }ש(M-((z\fna*dIz*2ӸdQQcya7<@3AdtX[@XYj]Z6K/_>[6)%h/~ \LNf2匉 3CFjx[ʉY ?$iEwO'E-0$U(*)}L%zYVm.dD$ 5mM 5*qhf< #Bru.@ZևCoc5~3bnn_G_(\{ܵEt| VWqIᔼ ~=FB}WγW#=6x0w3ٜ#ٜAw,{X,_?5;TtHIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/drinking_having_tea.png000066400000000000000000000014151370065651000276730ustar00rootroot00000000000000PNG  IHDRagAMA7IDAT8ukHQǯ%!(OABE&X ¢kJ6VK.H3itL|C[THGM̜άRrϜ3 #6wV šD+U(:T*23L(%߶*>HWbѧ~TZ{T?hCTe&Th #;zrAGl*06QU ^f*a{/ %"T*Mn dVRuT~&s#aN^ic]9u[ϰ]XoL fsE2Uv *۫[q1}ېgENw.29x `1f,nC5Xio ;q5#6yƻˡaB}/uk P`Zta@@XĎ0Q0 0(dA[-t7c SUtq VdS1,LuÛa֬ xVY1{Ud 1!K 3ku,d{u Y?@Y]CMQX#q;pmWEk1XZtuS`} $Aux ֭"5B&hKq #:#ᴆaE 1IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/eating.png000066400000000000000000000015271370065651000251540ustar00rootroot00000000000000PNG  IHDRa vpAg\ƭ IDAT8˥oTe@;wf:S; H"%SR@J5%itcܸ2AWnXqIbp!& 6u!A[J<;3}.t;,Nr"%=qkx9KP Њ0:Z,4O87ɑlS[AXJ wmJWqP^yT@;Ci~-Dc f[k3k896~zc/ &/VJt# }q/*_|άOpz屃~Lg,i^ 6^đݯ\@*T "nCU}g`u/yT8JBi`7M.WaY 4г n oV;m4͂W<hq~lij(0.%9f0߄B,Ap>EuӐNeaiFuy5Nh'(} ;{3)rNM`|cu 5 AL'mV>GabޞDNJj[孩3vy|w1wu[okbDӡ)Ս*eln'kwH?6o[yorG7\(lBqU^}l)yӴ.]L&3=Vp&iSITn-#ծ)VT5+3YȂ8ed2!àvO֢5,EuGh-IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/eating_having_a_snack.png000066400000000000000000000015521370065651000301650ustar00rootroot00000000000000PNG  IHDR(-SgAMA7PLTEV'ӤGE' ȁ -AR`B̓͏Ċɨjoכ'ߩ:^qܛٵnB-pahKI**Ñ.֤Lw3T]B~'ٛvPp߷CP4r[m>԰Hi4 Ͱ!m)%ؾǀDhI k<զhR+f+z\SzԿ{azR?7}c4oY0$ ۰k;)וTG&۪eznӻl즍˶n`s;10e¦խںܮÊx㸤m͎C̕̕n0 ҕR3˜䪒cWC:mS2Rktjˆq٥Ҝߧޭ“YN]6նuc–uۡлЯA5ͼctRNS&ӵN<& e#8F{bv;M g6dbZ F ٢W?̛h6'E)ܴHIDAT1+'KDܪ@"e02Q2Y.Ae3"TO[\Ǘw<שG3@Mvo9KZx #pJKLz#XY |2n 8aWe pRU[UU1\UUD&ۛIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/eating_having_breakfast.png000066400000000000000000000015431370065651000305300ustar00rootroot00000000000000PNG  IHDRa*IDAT8uk\uLL23 ĀBPKqB "{Mj*$>ښP} 1#ә{ueQٜYCDf||m!yTzwkZwǢ0ze=L\D z^|ejjj?||'`~~~5TVK`0zPJau͹ /=j0==}|a=A|a#5TܣVODaΕ~{'g+JhnB"34ĥR #Z $%1?,̞ohfZE[)$>ODp@wG|>Wdhh8G3\ !Ye]1\^2' \5kBvd8E2}X+? n ^=&v.#ϱ.Yr|)8H kb:1BȠ "@(vѽq s`f C( uBiAC6fx` SX4` Zfk!{T;t gϞVDIQ& ^e!"@waaq^o6m;IJX1VPrHo~7,Iǡ;S.˥;yG_4:jT!$ ݀WݮD O=q۟zr;dm D̬A@:>ICi|qz(a|||0Lac Hfwz5oLNngkl֗qk*^.V;t THyIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/eating_having_dinner.png000066400000000000000000000014761370065651000300520ustar00rootroot00000000000000PNG  IHDRaIDAT8˅[LQIzi^zb˖5׃(9%3)$^@q.H*WŜY[/D_wo?R(-w=-{hAN3%3H I)Nw`0hן!P]eKYš,^҅WfZicNO:ю,]']v Bh0Y>]H]QvohduVtxc#P8u<6eSIRL}M9T*1к^6/Pѷ1X$o!m*5?9W% dX$BVGҌ&Db N a_ ڊaŃwMp4MN_|U m/h*F^r `y ~} Fae s`><}BP\J>PSS܂M+s=0at&Rk*ޫBoc 8*Q]WE2He mCCSh|$29$rD0ɶ3==}W>%GqdsJJc%!2 g^#o222N=bexfsHMb,̼rLɟoerf2ͿG/@&3ogeW>?s|G=RSSst:š#Gwbÿ=c!WIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/eating_having_lunch.png000066400000000000000000000015661370065651000277040ustar00rootroot00000000000000PNG  IHDRa=IDAT8uS}LqRK\]ޓ].)^(9Θ͌M[2eAܰy{ezr[)cr/yg||=CrF> 2LC\.@[chIWRK惹Em7^vۮ|1w\LSJmgn'58Ю>]AO:G*aS(5Xz|Yu~Z7:6w6F_?.7 ^mT(RMtܽ95f>th~.~tuRn탡^jɤZH6<@u0(M'nt1xZ;UcRam) s%8{p>mi֒=(uk00|QrMTxӡܳ18̗-]LszAE%VrO^ Tm#@4fwypaT]B" L*䝋Z)C*aG>v pO :v/?[u +Ivh/nFŹ] Ez("smB.!Vy&QFH.sfo 3awzʎIqi)"̛̃'wAHNi{ǔxX + 8ض|VD!>O~ Q9$=G 0G }MK\HڦP\`P ! `$`@o āT0=$a!JZ}*%1ru\hCįa~"C .ѯehapC$IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/exercising.png000066400000000000000000000013311370065651000260360ustar00rootroot00000000000000PNG  IHDRaIDAT8m_Hq?۴0 IibBD VT/bVUILL"RI(V3iVnMj{9pރD=ZeG4 "*"hfPJB/"x1;%L"Wf_i||禒,j97+S; fnga9"HL@0=Ew NpDWWʟ6q,е HrtKBgHLܴj=ksPC/4g "r2@$0U>? 1 H <`TOCKlThw],$["\Tx0x/`s{=蹪jjpu> 4E A ߿1^p4i8V>=-]:tJ{y-wG>q!#OCVי?R},b2.ϗ;w6,GHmfp70Iu0 tkiX(GS׍FqnEz 7pi7z{cd 鯞&72h>0;\sH^Į<0L/֘g >` ur5m `Lz oj}/]SIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/exercising_cycling.png000066400000000000000000000006171370065651000275540ustar00rootroot00000000000000PNG  IHDRaVIDAT8c` 0B1a LLh eNA!L0Hpt& 驌B]` dM]^d'k ///0E ZY#Љ]] ~X>iM%bڒm-omcg*~pxeFW|tM˗-_[[kbwvJM͙5rr@BxoooLaKcc(=sΜIm]]]n ~t|,TvgҤI >011CX! Ė81Z^LP#:=\R-(wIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/exercising_dancing.png000066400000000000000000000013701370065651000275240ustar00rootroot00000000000000PNG  IHDRaIDAT8˭mHSaǏ0$5сA+-2'K,d[eET4c[6sWؘS  (,)ZaӽKC>xx^xΏ9!qsu4Q==L~i_3EwUT}q:Cr{W+95~q;YcKeIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/exercising_hiking.png000066400000000000000000000016261370065651000273760ustar00rootroot00000000000000PNG  IHDR(-SPLTE %%%&&&XXX###rrr###bbbXXX666'''888\\\,,,(((999&&& ???llleee$$$<<>> ///BBBPPP&&&kkkNNN'''  XXX``` 󆆆???000111999aaa^^^oooVVV===VVVWWWUUUPPP{{{vvv%%%sss ===IIIooobbb֘fffmmmlll%%%LLL,,,""" nnn ---@1jC }o022WqxQ+pwM,r/Y2-f4yV4;z񹌖@Ŏlgz (H @MF=(iӿA̔9 A)@w2Nbg\q/.adR3II=D(IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/exercising_jogging.png000066400000000000000000000007041370065651000275450ustar00rootroot00000000000000PNG  IHDR(-SPLTE<y3YPH'&gb*vQ.m D@JFCN8qgQke*'3pM.gb)G/-JGoi, 'o$Q96 WX H7tn.lh+<}w2?rl-]X%pj,(9tRNS094Z =F+ 6"ED IIDAT=͵asYvݩƓ1'#3Lz\Hm%`,MVz [m ;mm:!r*1pM܌n ͝{_$koc?7&}fIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/exercising_playing_sports.png000066400000000000000000000016471370065651000312050ustar00rootroot00000000000000PNG  IHDRanIDAT8}]Lg_ØvYpP dStVɤtz%  !+#4`̐Ѿ~@)۷6J FX)R -c= I~ɓ<?ϡDQQOPܘe_ν&m#Z՟/77jmm ޽߁eF9|Kx@(b9Nass pWBٵ2Dxg`0d2яrVWWo0YGjj|؇FhiNĴ87[F"H="D"P(G||<233ӠKzҨ;;k0nTGz=JKKqIALKriJGo׉uamm ~Iaii^&v`;PkX( P*hooGuu5F ׋iS/a١fXH=0YPWSh繩i({ֆ*x'eQ=:Ff-\Lt!W Abb" 1!f ^y&~m /VX,6H:Ԥapi$$$Td!"O{xh'O_GIO=j1в: :ѧa~%r\Cvĉ/rҢ$ՌÄztɺ)U@&W94\ qqqHNNF}CC: d7*5zT}pbxM06Ǐ!ǹDrp@ ~/]yB6=)_ziZb# _R?cccc=gee}s..)+q\}FL8B8]clIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/exercising_running.png000066400000000000000000000011201370065651000275720ustar00rootroot00000000000000PNG  IHDR(-SPLTEJGy3'&gb*YPHD@t4fȾ[+SLNvEGqH>WBStv̸CH]VZm@D]JMb@CQJOfsu~BF\GLeGK_dhyÜ6;OCG_KRlfizRTb7:MSXo^`qDzYTbpen}~wxz{Y\kTXeiihighwjkquefZ[\]cddR\;;MN>C:ACDGNSm6;TV,N_b16P5;WCDEHG"qttRNSL\37Ѓ#q!xh_ui3< Ɨ93brUP7ߖE@!,Ӝ%:ŗ pkIDATWcd@4#gutβFFO !9g@V$+ $ x]*H@(P<9̆\wd`<`oyƚaC@k3ap;;<-@ba@PFƕ0F02.c`f\gdHg g`( s).IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/exercising_swimming.png000066400000000000000000000015451370065651000277570ustar00rootroot00000000000000PNG  IHDRa,IDAT8c`rޟ\qpjے[cܑԷDbBߧm޿x՛wo߼st۔I~IX5{+˿[C\ۛߣ+?Ӿ3֍Eu2ώUv&)Mn-]Ϊ?Y:_j#jژu Ν Oo-?Dyʊ05,ؼ)?Qm΢e;'O߾NMOo?åO$zFbqL~X+vxg?}_j+>\(x)6[+g]?WV祭^R+RWX;.xڕ./j[CQnP #!g.#LDƠEAB6IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/exercising_working_out.png000066400000000000000000000015741370065651000304760ustar00rootroot00000000000000PNG  IHDRaCIDAT8Khelv6fcb1D6 B!'*TҋzMC/ J/ZƃH4J6v}̾fgg]G4.~oo~H@בř==sJ]m3ٳofڼ0y{@WI%WdbeK 'k~ [hvﵾr X]ʼSQ?;]'1D2O=Á?0\$;~3Rkf";$>Sc;":zT(a!A!#7{DOaʮ끔' B :C8fC+| e9䢾oX8 u6[Է[u{:#[mObwlK#ڷsSCQM1J6jWʔ*& ؈Qs9h@}1qZgC]C4|leOLM:nv^?sVǿҒv;cJړowwMNNEM`ff$s0??B;}VxA Bˆ+dlkkmllCXljH݁ `$L beeEJ@y" n,I ,p`||9j^]ѝ9Y]]M^҆bn//?g"Zy\cCF< Qp84y %6݂ߤenS䜠lt:4k`#ec@lrI,V05k[o4}?ZqAjc30RS5@.fDj5. i=oACRIcV 5 Mq\d`!e( GvJ?`BhȠTbHmaq~rE<4'kJmf3⣡ ae( OqFY19yMT'&RձGMOOCʢ8@4j%B.a>)Bsl IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/grooming_at_the_spa.png000066400000000000000000000010311370065651000277030ustar00rootroot00000000000000PNG  IHDRaIDAT8˵S]OQݟOW@dVQk"iC‚A)E*BVtfYy&'sg3̕HjǾ'ldٗ^}LH*Vep㥹g\i?q7`Λ`x3Ʃĺu O dQH蹞OFc-ڝrOBid-F՝'5>Z"z\ЏkJtwt_mK>= FuU%r'\8+^C.mft3RhXq!RG-$ $VFw" lH\-Ub {y=CZI _sXUFM}X P(핸iadN̖qP$[g~`#{*d:tYd9Fin8j\9JH@>y&n.Qgr$6 Y֌qo5*zoǛ jIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/grooming_brushing_teeth.png000066400000000000000000000011321370065651000306100ustar00rootroot00000000000000PNG  IHDR(-SPLTE Xq*O W)N¤ѽǴîŐƨֺGprq pqr ])N [< YէέϾɶ6ZWtRNS z:fm;˱˭տÿӬԭq.K{IDATc` C(n f \"  <`9^>~Q5! ;91ZmI)i٨pOjzP&o`hd`b 23`q"]\cps`fage ! @K(# bTIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/grooming_getting_a_haircut.png000066400000000000000000000014221370065651000312600ustar00rootroot00000000000000PNG  IHDR(-S}PLTE ƠxxxdzͿzzΚ̽@@3ɦ~~69{ m                       $     z   K z U + UȲ ܶ ŷ  V1qtRNS5c#(Knb ;. t_vPԐV`>&.S>0KSb/+XS+#϶8 VN56?{6  i0IDATc`Fdadɰ1q1000UBDtOT*#@R`j OLL\tkJH$#Fub ??#/uee巬,b0Z~o(b>g^ZZ:EeĈ㉅)mJ_7@ZSSd2=>Z*jkk'XFF_*vwwO677;zYY$QTWWϡd {5RRoooχ.E{p= pe.zzz<Pk4'TTTA nX nw: \Ȓz ;gŢBzYbn;$))B*_7Z `^%MhJuCsfa rj"Z t䆍O@O9 <:D];. BlbWWJnn.A(`0xT2VNطl+@E^O߈=N&Ca||<AۘqGV62tSw4Z?*AZIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/grooming_taking_a_bath.png000066400000000000000000000013411370065651000303530ustar00rootroot00000000000000PNG  IHDRaIDAT8u]Hyi/ J(׍Ibo I覛 $"h]6b j"1RPJl0rʙŘЇ߆snw}ƛJD"e FduLcERg)Q"vʆ+?Ln/hvoNFrd$%³$5Yoqߏ2]!8R|,Ǟ*|DmGB=z0@ ~d] 5j~WN^Gy8}՝#*tgKH%_h٬B: bw^ͮ6se imn-Y%QW,/uf]tvt@+w>h7_;%7}jIb8HBCh3y }Vd-[jV~W3wJ62w/)L0COYL ?XK֑5BGy 87X>\ qhoܞm ySNOP T*zdaFj>Ӻ߉Bi?q/̆9R`%!~e@.59t>jIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/grooming_taking_a_shower.png000066400000000000000000000017221370065651000307470ustar00rootroot00000000000000PNG  IHDRaIDAT8}Luh'8\B)P3o}::[/~;*7T:뙹kgόe?deu=zg˦M|ΫX,D:}M+*r+>Z眤nřNsSUZkaݵOsWt7 9J0z<0?Nf5ޣ_[OAz*XPX!?B(4(@ ✳DhK;RE}Ƕ)8%ŭ^Gw!M`< _@Ou_U3q8rUJQݡӬmKzRts5}kD<8J߄d(-uw[X@62saR>ϸYCC׾V  F6nBvC(seBv\$37m 飿~X>ml%$8,B*) VQ#Nݷ)>wݓ 0m /ÆkҸa 94VZL5wOMCo.<(eTɸ:q͚ͤzIp-m-9<*. `d%,RIgѓcD3H4ͻY >嗤ϲKضM,W$m2%lSI/aEuuSNM]EMM.e*<Id4q:soI56~-EseTdWE"VִҚmd C/u]uz`p\خ;:X\ IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/having_appointment.png000066400000000000000000000015671370065651000276030ustar00rootroot00000000000000PNG  IHDRagAMA7.IDAT8eSiLA]Ϩ1CMM<HVG4Qb ATD *aF<ЂVR(b4XrR(>4l81n܎s%~tF<_fTer7Y7hؿCNL: `U Дf\5n\GȄr%:q7csWA՞Ҫ/6Y=3]vJ\&>I@0EFX$O9".kФJ^'?A}Tg`*Ec!-, mP`>=Qoi{>#Vsg-nTWaSKvp1?.TiKL牌 V¦}Z)5eQq>&^W]Bkg_UB6M tK2`.$]uu7zwbOA&$Ye&B^EεBeg+QTՋ.Ro"!cp:RC[7@o-@ ʋ6BbS*d $6M4L Pd`P>|1k=#6c[jg5W6m7ir r *(KXjcįl+&E6!NȔ̟}wC@p1VD9WOdSF#3zK ?p ԀKl%ÈGgl$ Psi+ Activities - size 16 0.3 2010-07-18 https://psi-im.org/ tux-den maj Activities iconset for Psi+ IM activities/doing_chores doing_chores.png activities/doing_chores_buying_groceries doing_chores_buying_groceries.png activities/doing_chores_cleaning doing_chores_cleaning.png activities/doing_chores_cooking doing_chores_cooking.png activities/doing_chores_doing_maintenance doing_chores_doing_maintenance.png activities/doing_chores_doing_the_dishes doing_chores_doing_the_dishes.png activities/doing_chores_doing_the_laundry doing_chores_doing_the_laundry.png activities/doing_chores_gardening doing_chores_gardening.png activities/doing_chores_running_an_errand doing_chores_running_an_errand.png activities/doing_chores_walking_the_dog doing_chores_walking_the_dog.png activities/drinking drinking.png activities/drinking_having_a_beer drinking_having_a_beer.png activities/drinking_having_coffee drinking_having_coffee.png activities/drinking_having_tea drinking_having_tea.png activities/eating eating.png activities/eating_having_a_snack eating_having_a_snack.png activities/eating_having_breakfast eating_having_breakfast.png activities/eating_having_dinner eating_having_dinner.png activities/eating_having_lunch eating_having_lunch.png activities/exercising exercising.png activities/exercising_cycling exercising_cycling.png activities/exercising_dancing exercising_dancing.png activities/exercising_hiking exercising_hiking.png activities/exercising_jogging exercising_jogging.png activities/exercising_playing_sports exercising_playing_sports.png activities/exercising_running exercising_running.png activities/exercising_skiing exercising_skiing.png activities/exercising_swimming exercising_swimming.png activities/exercising_working_out exercising_working_out.png activities/grooming grooming.png activities/grooming_at_the_spa grooming_at_the_spa.png activities/grooming_brushing_teeth grooming_brushing_teeth.png activities/grooming_getting_a_haircut grooming_getting_a_haircut.png activities/grooming_shaving grooming_shaving.png activities/grooming_taking_a_bath grooming_taking_a_bath.png activities/grooming_taking_a_shower grooming_taking_a_shower.png activities/having_appointment having_appointment.png activities/inactive inactive.png activities/inactive_day_off inactive_day_off.png activities/inactive_hanging_out inactive_hanging_out.png activities/inactive_hiding inactive_hiding.png activities/inactive_on_vacation inactive_on_vacation.png activities/inactive_praying inactive_praying.png activities/inactive_scheduled_holiday inactive_scheduled_holiday.png activities/inactive_sleeping inactive_sleeping.png activities/inactive_thinking inactive_thinking.png activities/relaxing relaxing.png activities/relaxing_fishing relaxing_fishing.png activities/relaxing_gaming relaxing_gaming.png activities/relaxing_going_out relaxing_going_out.png activities/relaxing_partying relaxing_partying.png activities/relaxing_reading relaxing_reading.png activities/relaxing_rehearsing relaxing_rehearsing.png activities/relaxing_shopping relaxing_shopping.png activities/relaxing_smoking relaxing_smoking.png activities/relaxing_socializing relaxing_socializing.png activities/relaxing_sunbathing relaxing_sunbathing.png activities/relaxing_watching_a_movie relaxing_watching_a_movie.png activities/relaxing_watching_tv relaxing_watching_tv.png activities/talking talking.png activities/talking_in_real_life talking_in_real_life.png activities/talking_on_the_phone talking_on_the_phone.png activities/talking_on_video_phone talking_on_video_phone.png activities/traveling traveling.png activities/traveling_commuting traveling_commuting.png activities/traveling_cycling traveling_cycling.png activities/traveling_driving traveling_driving.png activities/traveling_in_a_car traveling_in_a_car.png activities/traveling_on_a_bus traveling_on_a_bus.png activities/traveling_on_a_plane traveling_on_a_plane.png activities/traveling_on_a_train traveling_on_a_train.png activities/traveling_on_a_trip traveling_on_a_trip.png activities/traveling_walking traveling_walking.png activities/unknown unknown.png activities/working working_working.png activities/working_coding working_coding.png activities/working_in_a_meeting working_in_a_meeting.png activities/working_studying working_studying.png activities/working_writing working_writing.png psi-plus-snapshots-1.4.1456/iconsets/activities/default/inactive.png000066400000000000000000000014561370065651000255100ustar00rootroot00000000000000PNG  IHDR(-SPLTEkL)2.fKha{tC*p hY","?9G@$%DDEnoo|v$eH'#$:=n[CAAl`oC*A$vlۈpR.wz{pj· % `EA*T6>'=9<@BDr`la< |twL1K@:Ν=",`\,"➜gc-&ݘޙ'q\v|Ӿx}R^e&''(3%Q]dhkq¸wndjp8$tRNSv2b2[[ppb˔۹2IDAT-Boʶcl^S˶m۶m>nuy^SߛRR02\ =e,-g6 ZmMbV!@\iiGા`jz "oQ$D -I:]m] Z(lV{|BL3^)byЬfù% ;=|G'(e?..c w?gzL *Z?/_4)^)IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/inactive_day_off.png000066400000000000000000000006251370065651000271740ustar00rootroot00000000000000PNG  IHDR(-SPLTEUUUUUUQQQPPP@@@???@@@AAA===<<<===UUUZZZvB= B& wwwXXXWWWr=uyuVVVYYY{{{~~~ tRNS u-IDATWmW@ Mޖ()g-XV1IkTM)RXU4MS;OV+<-hﯸ'^{q 1}Ľ8,Vo1@򀓐xeKSSF|{C/UDi: IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/inactive_hanging_out.png000066400000000000000000000011431370065651000300630ustar00rootroot00000000000000PNG  IHDRa*IDAT8}KHP?f0ldCiofadY(Y*fAEI/ \h"ע$ZE4dY"ILT ZUv(e 0bvj&8Y ƨ+WdC2GPbqJԧ* {=zmX^O'Fl%?ֱ L :x[ns/]L_ h6qR+'gL0L޶aO4I9I=|pS|WTˇK"2Ddp؆㑨 ;J+ S4$:e=qHs>=8ўIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/inactive_hiding.png000066400000000000000000000015661370065651000270340ustar00rootroot00000000000000PNG  IHDRa=IDAT8ˍk\esr\֙Iɤ.ڦYH- Ŗҕ W "U U-jU7hjb$ C4sL\yE?,>B"Hw ʳNóUFw}-"$)IC~QX9<^ٍJccHƝ;k,cj_P}+>m n0a:GRy]<Ηq/Q29][}Xv- 2ecVG7fuVIV ?~J0IMu63Vu֋HF0fv`cŌgGGh|,.}6k߼h?ΰy,Ν"[o]{ŗX;A`ʏb;RKO+ >{'2}L p3߃?nP1UǨz$3̷BvE R)ahcD<d@\\M@ ?# LnSǔ`l@$ !4ƑSʵ/q#:h {hwؤЏԚ}ml{ЅS֢+?.|u\Al6b0D৐Jv=(+ Ix|bh+I"Ƕ)2b9VwP _( $ܡj9%NaFShH@(x(z3oqV%$ +iihHF/O:~w$EDHR9I@]"+7ZaZ%Iލ6"%UaIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/inactive_on_vacation.png000066400000000000000000000014141370065651000300620ustar00rootroot00000000000000PNG  IHDRaIDAT8}},qw9Pfby(mmLY6ffnlH6QBdD\zw|oÀmJwBɍQ>E_@>nO%ͺU~հXp@S }t\ _ fET{PH[ ?[ɦ$ 3]5FD~%b-$owl"W$r )JtdzSk%=  {9V?ej(T#l;HVUZ1Xrm-$r6 bLl3{6uz(n>Rr NW(N% /'xz"Z (cA(Jr=讯PPR7o?8>x0Yq_Ә2઻i//C@d =v=C;_KK5A~3Ȭ4q.xS=Wve+(#yV5.,waTx8L$pD6À Hb1x1^zeڌGqxBLKo mWgʧjw]fs6 Xn4$\m נv&9BBe6![wZ@iOk*6Vth i\cA4,Ƥ<ұ< )_97Y`]>& L:!{vIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/inactive_praying.png000066400000000000000000000014101370065651000272270ustar00rootroot00000000000000PNG  IHDRaIDAT8ˍ}L?(15^b]YKM-Bt^7nf%:t50]n![2$lH6f4Ν?^yy}|~8@Q?li28{ u#o0cUt2:~u{[}7-B闂Hi+PءzHh^`kDaW ߍ<q-XB3wb RIę2‚["OHK קGM|X"ACg iE*$KVa 3eQeY&hOc'͞7S;!; YqvϩPŠx|R6–+^g 37]5۫w7nkjI*3#c;CVEdKŕKrpKKː.&?vGY̹3< +Bv:F ٶֽ>gkŐG)W꬈*rb90kn<1LM^bDG1ӾbLC 2F/$bBjmx\ `ޗx𼨦~9ޢ\dMZsFlMsH} Ԋ<)EJ H}rI_vXPc*i@g:z*NmO%=QMTt|w<}tfIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/inactive_scheduled_holiday.png000066400000000000000000000016721370065651000312410ustar00rootroot00000000000000PNG  IHDRaIDAT8mSkL=8LONB7 O [-MM ZKK e6 1& a3Ѱl#ԅej܏VB_=s.`I\=ɿiP1^pvZj%,.EPH:ƣ7ri{U/$}!zyG> eKyd |ZJYRJUK`*oiZC`gT\en&ʟ֜RhGChVNoRީ02ϭZ41AM1d4 S+FWR~ÕA˓^6#9̝ v8!~lsWHuJFCkξPD_nqg m!Tdg ep^д .%G#ifz{4YY1)3$h4gLiTF Xu{cjT*ylCI`̭/[Y#9ȫd֊mZ6؛۱/euz:vttYuDv XɌ(i1?mtGty^sZܞ1I# Ci4ctC'wd:[=g޵t$giz_94=`1  qx*WFQJn$Ż9@Twm!DA>*='VFr<\R>-!uOIhۺXrTMLv;CxiQ~a&|)12O*7'A+/ 3\W?^ ͏>Xeu]U@Y"ړˡo&P У/@~rh4~z֢sC ƺkqӁO?vw_?u(lޫG3pcdWIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/inactive_sleeping.png000066400000000000000000000016171370065651000273750ustar00rootroot00000000000000PNG  IHDRaVIDAT8O=KlTe@}NLg: Ci6$6#ZM]ؔEIp!lm$ѝqa+5.4& @Auyt^{.%"*==-w/&4^쾆KJܰ .L\49֦qn񊚙AOOwj/6_g5ؽ]`A)ux+ oDSӹF"zWƲ{[R^|mjK7WOkC3Ob~(MZgtj07c?b>) {cgcjb8(Y,N,8@^y/U>(0޵6 xtk\q87dγ}܉7&u=6x5_^[U`=/?I+эY4^Sc,6xk Th[&VŶPOcJHJK5lO)_?e7%6xP+뷉txPMbebmfP'\NNKQ-pM%jŊHar$'Ӑ_Io@Kxup ڑh׭ɚեLHdOE&Fj5JbdRÆsfd S;ߴ~<1 `x.PIv[qH2/k:n9]\LZꙂTכn?`Y{G<8J?xՑm;#tluhmxjQ`Dշ^ۈxJ)DRRjQNwnzܵ5eE,%\E>Z%"3hݣ 8,/ g!.IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/inactive_thinking.png000066400000000000000000000012131370065651000273720ustar00rootroot00000000000000PNG  IHDRaRIDAT8}S_HSq YQE[IZY *z%FC=DPi=CJlSh)ܲ-Rh̜ նsc˭|DiRĻhX[ Ƴi2BC2! Zh8,Q`l?~ Zv7OfzT?S' ՀG ;,D=='܄ DOݒ ~GtT ដ% Tiﰖ%7]lU ?g3$}lil׶qG? yˉ ;i. ,MCi4Ҍj \&gqelgRϺ[S]IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/relaxing.png000066400000000000000000000016371370065651000255200ustar00rootroot00000000000000PNG  IHDRafIDAT8O=IlTut2SZ& D D֠$ $.U='bM&.(JFH"!F[l>oσ<#d`}N #O\߰hHk/s6:Rmc&O{#&|R>ȧ/R-lYV DғL%vM]{^~ܘ mk'Ցoia>tn T lmӕ=v,NɾuEa?N;ѥ~3DL8&!-q0!p37Ҍo &N;2Feakv8fL,v&3>R&}{w:7!=h%ާmtt`6CWYŠ%ETZ:~ 򌡮lSuAJhd(3P@a *P $a)nֶ1=IHtBKam0Aρ<`j\Ǯoйf#_[,Bq-CnIZR,yt °VTja,Ϗto3z&#Ƒjy4H>6|Yrţ/i5|NI6ཡ Y29<׊5\WS'qj7xh؎,"C x9FoιDUyky*p{'l el,HO:1 hguU!k/zVA@c$YfdN%T ͖IbaǝNyv7\..|:&Wl6O`0,UVuspp1N%|@D;=ssP!0[$`Z 2zom``C& \T"DZJű1; #j+'@=8dR8JUVB###HrGW R$(h4`0iaa^% dYv >PȽOV~9}+˿i6oR:HPaTQ?M/0B%l+;t0ݩ. tPg$y"y֔.kTTF˪sܚsr9@U8,Q#Twg-CiԌ]ON!R_W;;N#kY\RDFUqogŦ:6xX\dCD?hj +q f.޾{Ng|cd)P"dLI~,M}5w`]u=Vd?e>]P$?"J?nRW | ַxXwyZJSC|@zG}L`:m}ED &E8~J Pͬ n";uS}muckk BA"~\x'˞WlhҲBd8c$; T?LjÆ_z{5PVY[꛲ >j2n4ҞYwrl'u8-)y2uc421p @B23Ulq")LI0%I<[*$kHqQnC(IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/relaxing_partying.png000066400000000000000000000014421370065651000274270ustar00rootroot00000000000000PNG  IHDRaIDAT8˕{HPƏ53-keQQD3$$(i`V$b .zIe!ҘfUc,GnMK]n "|s.{Ѵ2bHӹ40/(<9m6ܛ팼6 y{G)lUIJ4##lzYk-]!G;-!RaJf]Kw[-z*we6IYv{VG޿I|?v _-|U7Ͼ !7i+uԉIcu`=`fq6a ݰ|h/acXg!XC`/50t (.V_ә>cZ.mEoY*,cY[ @tVXǐ_rP z:'yv= 4¬XRS(067)lbtjRM6q!KD;7KH8z}-cyw@|}Gc0ꉞIl!NبFf|* Ʀs.âCjcbbY2kcgHp@T8ݚgXlQVC`<ݏ/An;l˿CB8/^=CL=9`<-GНe%*潥~폸K PL<5ÿ1cpS/-BzK`'L49`LR&]4`3[jGu:<> yl09/qj$IIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/relaxing_rehearsing.png000066400000000000000000000012631370065651000277220ustar00rootroot00000000000000PNG  IHDRazIDAT8˥KSwxWW)Ycbs#\״6Ύm&ιMyRs2fDYSc:M&ƺ.H즺iA޻p*pU[tR jm-㤚C؁c|.g|B)*?xPAo ohK+qxc`t7v$J6k*+I]LI۬[^C=j)Ϩ H-$.42c!5!(oN>wqP-KMi&J'*=hPOR4j#vRZhn;11I,*i*u2 F؆z% 5Xj`ղ,A[u' pP>OXhz=aaocf dL + ૱·,=P'=8]8fVьtIœs 2v>iM`B N)'  B_1j߄R@/'I5}AFl %DzId]PTP\@M oUR,KX~^_>&ϻjc"DaԊrKҾrXM,2EF~QX\d'#-yPL IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/relaxing_shopping.png000066400000000000000000000010441370065651000274170ustar00rootroot00000000000000PNG  IHDR(-SPLTEyyyjjjgggxxx4pƦr탄hoŠ|ȱ5͎Ϗ;Ѳv˭؆|7ْދTbFtRNS3.I }~2ꬬ,EIDATeϵ@].wwwC*xt"IVWHz[ꭚj)MD4p:z|KX` JDJQ¡ 0XDŽni/ZVs[́{JdHY:\]VLz$@kyUݔh !bAvxꐆIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/relaxing_smoking.png000066400000000000000000000012601370065651000272370ustar00rootroot00000000000000PNG  IHDRawIDAT8ˍKLQhIԅ. JTcP ΅q… \I !btcO@dEPhis2M4|dr; he4KB˜SǟYYeu?ra! Rw \] q'? $ H a&Bs U׾&;', =?v)vA{ VŋZ#b v`z :.-RbL FϏNcg>?ʠQm?>-jJϥj<,! ]WT kƅQ}^#B&Rd(r$nܯ _#&d軪i]bO2z&ɬ٦3g^ħ$511q9:Lpb Η}Dq6ӎBamCp6=$9y5e]X3(zwZm+rH_z{K㿥j8\<5 /ot{u]>=i=fbmb~r`~v'0iD[BˤDr25]ɥC ўO*vak~wIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/relaxing_socializing.png000066400000000000000000000014111370065651000301010ustar00rootroot00000000000000PNG  IHDRaIDAT8˭SkHQS)*CFA R 5>``FQ$f5 1{ Wn=nE҆e\[t9h]8{sKݠ^5w5(4.}lR&sL&Dk^q*&'F%ڹB2de}>FcAi42 Z'ӰG**K_sn^Xk` (2R-6Jӕ |/pR8IT6y +1?[}a󽀂BЏ=td8<7e6/HUTiVsi0Cn >y`Y*k!kȋuӀo8`` X1z ]JTp0yw^\ / 5utŚ<,vJG iONA(oΕ'P  (6 TiBw=aUDqp&+.M~Dq_DQ^ =P]+2Ss5866$ Pٖi16U?|}(`DPFOm=a}nVJ2jI|GV%^&la|,WΣV#"!I,D8tI+%H! 1/*Bu sz%=X0yNd;?Bvoz{grmAL>1UUAܦ4`U:!jn[j뭽Sonשi(`C,,,FX @y1\gus8PG ]B{%'F2Yk*7#eln儴 YUmע-Dy>E{IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/relaxing_watching_a_movie.png000066400000000000000000000011311370065651000310700ustar00rootroot00000000000000PNG  IHDRa IDAT8ujQ&J/`6iNɞcf29X+FAڂZ!6MZ9T6-kO'ݰa7{&VVV6J˩cԗcFأRG`lbss9Op@ ل kbNal -)ܗJCU{X_܃-.d2fNgk?섿^S!IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/talking.png000066400000000000000000000014751370065651000253400ustar00rootroot00000000000000PNG  IHDRaIDAT8˥k\u7L&'iRLCj!E,B[Z( 7Yҍ ^0--DED,bBfIh.sfLf\WTD !( {uU{utP =Aa'?)nxE{UmI:_t;g=n*&af jMR._Z,V.kunq1F]١{t0=6#iԥdm/rkIR+V ?GDz0H%RN%H#h*a3m2ozȓ/fxĊDlATAЬ׊%mx?P~xFp&7IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/talking_in_real_life.png000066400000000000000000000014151370065651000300220ustar00rootroot00000000000000PNG  IHDRaIDAT8˥kHa_ZI(FE`e -SID`edPeK*6* j5Qin.]YjjYA#|]l ><< pQdB>a4%gc{*i CgHJAE S&!? с*4i:C7z}$ep _:oƌ8`Pp*9%s^b6)xɤ"ä; $o5А=!ٯ6pظ 4&~9#zNvYV"^jp%E͖jzQ?bF` G'){dtFyҚs֧JCjηj4 _-YĻJ)T[pn ƘZ[}JBMb'ٞ:GJdoSUFgOYZѰ"WgRXvGm=}G3xJF.{{*Rj)2hn-06jl]3J&rc钔L|UJ~~mށdUW('N]gڸ|^vSn:U<>&x#$HLx@kҀ14J@^H`pc EAE[HHt2(^s2,R9 @dH a0^ &IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/talking_on_the_phone.png000066400000000000000000000013261370065651000300600ustar00rootroot00000000000000PNG  IHDRaIDAT8mSMOQ=of:-J)`[%~$qaBڕ,‚aܨq[ R( PKK`O7}T,IorΝ{睙3#\ub~4˥LbW+!iZ(KoۼtT7OyqT4DbeCX+k[羖a"J}u }ꤋ~>uS?*r2 txoDs8$RQ6$ᠢgZmQ#rvPJ@8ܯN54Ud|@ǮσUs]hPUB4*)b{^ Qa,G8#3Fqi&&y 16FZNCS[q>ۿ`V4MspE[uF7岲ΥN>HTGEv`ԁ9s( ֠g $UY92pdeZPku1QFTf6-ˍa(I|!G1<WF1u{Fpk@N[tlП ¬\u HЅ E1і08tIrrr7IunW888heeI#]m-qhD,Bi=wwwߗ|dq\16a{ﶶ>V;e??VZ~GWmy>|jϊ<10ʘ82ġ|hoPq'mE9碵 ,P >u2 J'dL:NB |W+ 3)tIiWB@`5D/'eȂwd9˽}UBB1 J}4FA*o.H9kM !DP <n}{Ok6;Ul*IcffM&ibIf$J){xx8޹1 T3 DD8f{N,˄~0TU Uziikm?<7,#Dpnpqvqqspl-&!ssFttuqIC;JE@JwyvyyNHAzjOOM-./-.1}}ڂ i|c%wщډS/(#EhHfuבҒJbU]l9@024VuV1n[f-)igؼutRNS@fIDAT=zwֶk۶ڜcaB.յqz ;70@T\Q{^Lg+:B{X/lpiM & V$'j;lo_5G3}'s>ce=f".і )znWGg|Gڤ}pȡhl9V)+~_:c5-MrjY2hff 4+uh@~.mIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/traveling_commuting.png000066400000000000000000000014701370065651000277570ustar00rootroot00000000000000PNG  IHDRa vpAg\ƭIDAT8˅KhQʤWژՈ(QQP\ "Kw_bADܺQZ65jIt&sz3Xous8^Vzezh-2CKH<bne-$Z=]N&km r̲odK,©*GD2&ok۹6L.xP|҂Co,< 2|⇩ΩJgpbmx+(ERk{_!ͽGAoK X+@gسs+\\_nн΂UUOqߣwөH!7澮k!  D.M](ʷ[]-~+y (lejv$*2cϐF%gh>R~ q?> 'R95٣Ǝ3CKnEUpłL1,bAwZ ԗ@52d04Sr)Kd/:{h6AoWT D@w2D~nBͲ+OY07!Z#;|f֕2Z LSvlW׊ϥew 74[l8(5CALFǦ7bm5_OMi#nDv2)^ZI4RR K)AҌHWLYYedgՂJ^a+9)9Pkcrj5NyAIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/traveling_cycling.png000066400000000000000000000006171370065651000274070ustar00rootroot00000000000000PNG  IHDRaVIDAT8c` 0B1a LLh eNA!L0Hpt& 驌B]` dM]^d'k ///0E ZY#Љ]] ~X>iM%bڒm-omcg*~pxeFW|tM˗-_[[kbwvJM͙5rr@BxoooLaKcc(=sΜIm]]]n ~t|,TvgҤI >011CX! Ė81Z^LP#:=\R-(wIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/traveling_driving.png000066400000000000000000000016201370065651000274140ustar00rootroot00000000000000PNG  IHDRaWIDAT8c`@iuіcǎ-;v#GT۷O(ȀUݢ1sssu}[n=XzիV[xqf}}=VHI,,ʁ]]SN4i͝{r]su&&իQ5QTܷCX&& Rl(/=h""(kwBY٘Bbݥ> ,A7~())icU>(8AxVBZW10 8|V\!][V}ǎf_\\򿰰HHȣԽ Ko㔓ll`^URuXNnqwKslhhcǮǎumͿFF 7.؀>>}j .EeggBCs7"U'ȖΟwy<}9d2Ssה3~1=7ظ(/:Vmd/uGe3z#}ǽ5E4揼g͇2[VjOv 1;n|2y&`>a?O |Bܒb{37k6GV؆=|SqSt:[j?gk[T$?]]Ej.a^0 &$TB_s^L7JqS+hi}+̎e_G!::<1Vvŗz77ޱ ;i&o}39#! ?y3H`RIIWYRbV/|OsHF4CJ` @ $#Y4儕q?L"726]í#Iۛnd eׯ ȟ$upL0v7#)AhCIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/traveling_on_a_plane.png000066400000000000000000000013261370065651000300500ustar00rootroot00000000000000PNG  IHDRaIDAT8˕{HaWݟ $!ee6L A*ę "Q,P&#T9+!U6ft6/yܥ6[Чo&j/x_yimc϶-4e3k1Z-ٗn->PuR '# uD W#4P5b5HX/-VϢ&B4"9pmHmE*;ezFHD4N.esOGIPs!Qit=jts(#؎Z XIxnp0*VZ&qO= wimCm*jbȣa-H,W U,=84!+ƙ)oǕ(G؂ @ۖFO.HҊ 1m,V2"N ܎=߬vWTyw9pUnÙf0;=v*x"xsMfC54O믈A)&* c>&pիjrUi!"Ռ7c>{M &3 )ӹ̸ϸFwo).M]^LFй:(#zb{̩m "ȩx=]vIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/traveling_on_a_train.png000066400000000000000000000014541370065651000300700ustar00rootroot00000000000000PNG  IHDRaIDAT8˅SkHSqF!AA>,M/=A:mJ7-)*Kŋhbz)̈F ACNwOs>0t9 \BA8Z=Xc0'k3E.Ul585iDӴ"хAדdmZ\̻>0jo\4־F=o%"ҭ|7 ,5\Th<({6z>(!j_%ZDbCnH>'$z=#"9CQW(;>Qb8n/3f'O-$~~&7€_ĵHh! H& 3 87 \B+;UAj|w$)*4D.Mh{ zCR-Z|fغ[y%qB8oy9D,*p:ñc:)GWnATobmN 9t |n*pIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/traveling_on_a_trip.png000066400000000000000000000011061370065651000277230ustar00rootroot00000000000000PNG  IHDR(-S)PLTE^_ammoz{{oswptwprsnnoqrtyzz]_a]_`ɉllk}~utvssv`cdpps ijabf`adabestwz{|lkj|}ą`ae_`ctRNS3#"э77rIDATc` €@ 腫%$CE,llD4؁΁A>@+m$ FI ,qb*ʦ*b|, 22Ҟ @C-̤d%$dN@ j*(]%&EȐ X L0óŧ3IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/traveling_walking.png000066400000000000000000000014211370065651000274050ustar00rootroot00000000000000PNG  IHDRaIDAT8u{,ǿGeG(I2eifŊ钻mVjrJF?T$CE!6WM]׽^0߮vu9|ЌUPć.2|FF[.~M)u+$a;5:YJ\la%H::&jhN,`揑=̤䚯8Nmԝ踵E:VbI,jţD퉨UF$GW3T|O ,@yE0!??l(د P䴗y$/@Rq$p`k8ijsiz>& l Z'4(ΒY鷗Bm<&<;JE>H1шX@J6zyS?דrd'z7C~Q $'=r]=b=h0wu'Qu7 l`5TnQ&[S-Eg$74bxlBʡP/\`@ PT ezԗ~"z46@ߡ+$τj ]-0eCЗ_!hM=m-8{6gl`tuu^oi^ j+xiGb@S-<D>~< B*y:ibZn <a#f9΁8@Ͽ )IoU+?Z[H`*e%XO_ѫsNm שQb{$WcnRL+ӫ{Xϛ5k6llGgyi'LoT-d>j#r;(IzLWt#J-ujYSxԊo;\{TE߄6jG|o /M€2 f"򴌐 RrFYww`; go"Dh[陕#D`Iv~ M5M ½H:Q;5sv($,iDA@E q%!?:!*0&3Ϥ3!YYy(ZFHSV*.&i ~uG-rm<`߭[L,3b=eE  }F3 lgm_V=za[IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/working_coding.png000066400000000000000000000007141370065651000267050ustar00rootroot00000000000000PNG  IHDR(-SPLTE777777777777777777iiiXXX777777777777777ddd```:::777AAA```:::999777777777777777777777777777777@@@777777```{ߨዒح՜]]]؀ٶ㍕ܒ}R $tRNS~.3)l+-$%b*Mt}nW 1IIDATe@F_f@xwq}?q#jLs82ynMȍ'{iY}!UN;Ӑ3wɝS*HyE`)ic)P `SQ:FՎhn}s` cx7b"L9IENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/working_in_a_meeting.png000066400000000000000000000015241370065651000300600ustar00rootroot00000000000000PNG  IHDRaIDAT8˕{HQOFDՇ}>M7gr5.n"/B|5(nE"FRE]P5k:sBxxAǚ%X9B(E% ǀY.;47f  hھ-x2rPv;zq{ ʳ&;z@5bhPA:!$x7767t=HoxH!,Ҋ"93&޵he$yr .-[3e80Za͏[ F8Q_O''%J%tb=)3HllMY@Nm[OR "0)D//|a|U/xlg:a(6Liޥypк,|8/jj$4 "gDN(QÕ Lb,AgkvJ$qVNh3,upXzhSBk*B`3kx*L rEL*ets3'd¾cS˓He52:d,ڐH!J0-(WtWj]zq' 3R"q'0^ ;%9 }/8x{4,?79}/%/*)6yMbcఙ[-}]ʡ,}nV9t["&m:Vi+ ,잧%W6 N >[}3[#eLQIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/working_studying.png000066400000000000000000000016751370065651000273170ustar00rootroot00000000000000PNG  IHDRagAMA7tIDAT8O]_h[uws67I4q] 26a/ҍQ7ÐL0ppa{>4K}\V+jM6m?{>t9 #"h]Sf%dT*Ȱ`C˰*86{mmm I}Nˮ\ \ ّc>' Uz>WPN֖y&DRB6Q*6Y=w'\ 8[rxq} nnx3FzS"/np!,[p*xHH.XBbn0UVYWK/Z]O w="O ŷ"F"B}X)dm ʕ׾×'Sspu\nf~8IU-ȮG74t3O xOt/؈q͒BX@~ pq)QI+33xd؈y!3r5 yK@RTݪΝR8y*}]]n&\ p,GH Ge˂Ӎ^|p2xt-euAH 14`0bK29T +[>z71%n3-y)bXS)]K~I6W Y*Y #"Oŏ?(][vWeTf ɬs'93 >ą.z>{mXe)[z.*>~#|Qn[+_~','4v;2 ĒDG>al1=~&QP)ٌ(?+J}`&eIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/working_working.png000066400000000000000000000015121370065651000271170ustar00rootroot00000000000000PNG  IHDRaIDAT8emHSaWVD4@ tZsw/닽`J ńeԴkfiؚ5k4'e6S$R'VCIDvw\V{9?yo..W)_?j?k7mzKq@.JT8˒r3ߴ]Uh}ŔhU:LAu&:Mp΀v{ ><% ;a 8@- nx~ mg9!G"jo?L`ZY8Nw)z);N?6rrpf6.}}Y[{+ nT0ts)^^>qLkPCĎ$(n<.߱1;n@5m.oxA Q"F⏐+{)IKh3__B`4d$I"QAKQya\ NDWP*P(&j4<baxV}  q\.VT@E 9!_ӃF̿܏eRrBTCAhz)HR%'B,#@" %e#+qq(H3QQk+ ?z\fIENDB`psi-plus-snapshots-1.4.1456/iconsets/activities/default/working_writing.png000066400000000000000000000012331370065651000271220ustar00rootroot00000000000000PNG  IHDRabIDAT8˅Aa_:tYM[Et!2MBX1$iu La#Œ[a ńe Dd`>G˶~{hʪ*Y=(cf!1[k1S1mT*|as:3i2Th6vus 8C,Z`vMM@I+Ph4`&*2DCZ>a&Ǥö7eHtqAL^>9;43v9Fgs %? ݃\"N [qx~~vAT!2*v %˲U laީbt"n2zuM1( 6ki i8Wafظ̄8J)lvt׻-Ƕk@;fi BWapNqh0;QA.[z|jPUu]^N'JY3H$r8BqvvvbOpXe&pxEh4Ǘ2LDTե )(~?qv;q c)adymEQ`"L&/ /`vIENDB`psi-plus-snapshots-1.4.1456/iconsets/affiliations/000077500000000000000000000000001370065651000220525ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iconsets/affiliations/default/000077500000000000000000000000001370065651000234765ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iconsets/affiliations/default/admin.png000066400000000000000000000011301370065651000252670ustar00rootroot00000000000000PNG  IHDR(-S&PLTEO[gt~Mz =FMn%vpptLHLJMuZxaKN򄖪ſLɣ<C KvԇZFMMu>UޱňaQJ6xhj,kPBУ޸k,u=3tRNS%U4R0Y;3/IDATW=EPQ>؂؉bww ~[ @A3 :ZwPh"q_lx!C Z1+s#*bh\@pk$0XJTb=0[ . Nr6C dP˕wz} ΡDQ&TVR$w=}IIENDB`psi-plus-snapshots-1.4.1456/iconsets/affiliations/default/icondef.xml000066400000000000000000000015041370065651000256270ustar00rootroot00000000000000 Medals 0.1 Adium Affiliations 2010-06-10 https://psi-im.org/ tux-den affiliation/owner owner.png affiliation/admin admin.png affiliation/member member.png affiliation/noaffiliation noaffiliation.png affiliation/outcast outcast.png psi-plus-snapshots-1.4.1456/iconsets/affiliations/default/member.png000066400000000000000000000011251370065651000254520ustar00rootroot00000000000000PNG  IHDR(-S#PLTELI<:H 5( ْ2oL9Ћ0;q'q(RJ;FۙVȻLH>Fy]GBc48[ib]R^w^gpń"`ffX\]~Cca=?lz=ߋqqaga`OBCw<?ktRNSY%4;0R3/UU19^IDATW=UPEQ"v!vw؝<^wAb1 U9+S qeI?|Duw)Ol& jzth5-Nzv=FMEdpp<:=;ڮ_8֒>s낐N2Ͽ3D#v̸Ψא膖Y)wɈUEzM}׿DӘɅgV[tRNS;U40RY%/3IDATW=P@nFyQJ2e~ǰn0 h`8 a: 5`&JJ40X"O" r%_J!?:l%qͲ6mD[a>jjU $J B*%_0yNͦBSkgWT0LEpiXv{ MKZMM9pPaƣIENDB`psi-plus-snapshots-1.4.1456/iconsets/affiliations/default/outcast.png000066400000000000000000000010321370065651000256620ustar00rootroot00000000000000PNG  IHDR(-SPLTEeeezzzwww???EEE~~~}}}wwwvvvҎyyyӊ򺺺Ɓʮxxxꏏˇkkk߸ԥssslllptRNSUᗰ4Y0%3;/RIDATW=B@@QC^cM"*B{ߤyAD0  [MĀb4ZtMpcƐvoi tNaGc 38M!#`v:VC?rKq&.s\V5u8T,L&ʊ1$7,?Ԥk14v%!JHXPҝBIENDB`psi-plus-snapshots-1.4.1456/iconsets/affiliations/default/owner.png000066400000000000000000000011101370065651000253270ustar00rootroot00000000000000PNG  IHDR(-SPLTEOz+My,s( o%f:g'j${.$|/VߐH݈?{.URNWtڷwKJLLKMMZu$qȃWÐu>߷^D 2FVUWJʼnx)V~7wݱh#qRuʐ=`Ĉa`~0ik/MFNޅ8sSݰ}6j8N݂3sztRNS4RY;03%/UUq<IDAT=CPRvԶA:=dB7y8$$$aXJShA&[/'r6@?g~`^5T3tv\Pr@YUZ+)fM.ۭ;zD^IFÿRj[`ٌ r-D^|DIENDB`psi-plus-snapshots-1.4.1456/iconsets/clients/000077500000000000000000000000001370065651000210435ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iconsets/clients/default/000077500000000000000000000000001370065651000224675ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iconsets/clients/default/.directory000066400000000000000000000001031370065651000244660ustar00rootroot00000000000000[Dolphin] PreviewsShown=true Timestamp=2017,5,1,23,42,48 Version=4 psi-plus-snapshots-1.4.1456/iconsets/clients/default/README.md000066400000000000000000000004571370065651000237540ustar00rootroot00000000000000Next command was used to generate png from svg ``` for f in ~/projects/psi/svg/clients/default/*.svg; do convert -background none -resize 16x16 -define png:compression-level=9 -define png:compression-strategy=1 -depth 24 -define png:compression-filter=2 +antialias $f $(basename $f .svg).png; done ``` psi-plus-snapshots-1.4.1456/iconsets/clients/default/adium.png000066400000000000000000000014171370065651000242770ustar00rootroot00000000000000PNG  IHDRaIDAT8˅S]HTQRwSU AVP ,)E͛ؖ!؃ d؃nڮ?h)dh#ni]M|̙3}g̹kCyI혏cÌ $C|ȝe<ϘZvdĺ Je $3 s6rdԣ~kA DɗDvc&:fm{%a%D.Zf?ޯj,l6iЍ ]2ti*YƏi. YD_.s$-ά/wʋnյkL\PU_+5ǰgh[8}(4E8I77Q|VZ?Srw.WB=^7"-u}Q#bERr5D2-)rDG@7A86,7`¿Rgպ6r㖸ŲZ{&* 7-U[cij{{Jf ~VZ^6EmjT1e/YV={p!d{VO0hc"Lػ XoZo)Jb:7ilj'2 ߘߧ 0Q:fvz ?OoI_"u +JmtC)Rb yi8 QKGe2 ypp5N>0cۢ"R2!O$DMަ\嘟i繯l,}m" XIWtYb}Vj뤻6B yɹQ43p8A~Q nM4x?3'<֑S oiħ*VM50ϖͻW1T H## 44m~>Uɻٻr_,SxJsɠ/%w펗h`Gg*}qt:T"0ҏ<) |uyMgR'o;3U5<&Qo% d\'_zdGN0(t.Z{xXժܖ2#k!o)`LBƼv.v{_ȥqrRum{``hphO=}0' ssr呀Vo%DQwlXaBX$S0%r XEq9{йk+bz?.耛J7[e (hW$JC3I1arexzTS"~H(6w8HL5 Tb]uKysu:/[v˦|uq55̭֓d !#3jQfur%Vs%k>rCx̳&N*utQ߉oڿآBLcW?#I9Q dI"40E+_8u5+Bw}Yj;~sI_ʦWe;L\Æm cKJOY$+Ofrm 6 =8O+9I%tEXtdate:create2020-06-24T10:21:16+00:00%tEXtdate:modify2020-06-11T13:54:22+00:00D[IENDB`psi-plus-snapshots-1.4.1456/iconsets/clients/default/bot.png000066400000000000000000000010471370065651000237630ustar00rootroot00000000000000PNG  IHDRaIDAT8˝S;ZQaS'O?)bl4[P&,h!J (((+ G@X4DBs6ya8o$8v3p8r9Q4 T*y"Ⱥ\.m8[Qb5nb`4ln#Nfn{żj_n, )L&^#NѼ \8y ]WU @)9T`o`X VAP 8߿v?Pst?S" "o;Z,T*h49Ry*ag,-uds. >Z\.Nj>glxBļ( 2M~|>gU>G2Χ{L&&Ze0Պyr2Dd?xl&5vC$czOn}LnX*N-˥Jz 5žA{uŤ;(IENDB`psi-plus-snapshots-1.4.1456/iconsets/clients/default/conv6ations.png000066400000000000000000000036061370065651000254530ustar00rootroot00000000000000PNG  IHDROc#"gAMA a cHRMz&u0`:pQ<bKGDC pHYs``kBtIME $=RuIDATHǍUYP=x%B$Be1HPD%cuhbH1Q * MQr,n5j:NS- pw"pi;NyYBK٨pM:?PeP5}.}ឦ {P)ĞW`΅%g{:Ǝ1zOzގHBv?JXkH.T7H% eZ#`{t S}`&PW{oXK<ғ@x$HQ9^ ]i@ BH4ڸ =/CǪfK~?Ckʒk5 Mc HM=@ 6? JIǁؙ4JZZp#ͲF1@5Fkp@)0HPHDd5ZؘA;]U<)idO59+FRtjNМHYCn5*#)#~*R}cHWlz 'z jح1SrPqlRgCF.mk)Ź$DwI+y1Tr? {grh}-C,Sulo 9S$^\1@?fH͇+)ܐ]Mʎ o:qWzK{> eSyPR(i-iyRsc.i4[lK'n6د%_9n [_5 Ec\;@*3yڥI HIѧ]fs4?x9ir?G*nnHR?EW=C1M͞K+S<|\3~v1}~Ⱦ;mQU>L~4 5q9~uUE:%ip1%tEXtdate:create2020-06-24T10:21:16+00:00%tEXtdate:modify2020-06-11T13:54:22+00:00D[IENDB`psi-plus-snapshots-1.4.1456/iconsets/clients/default/conversations.png000066400000000000000000000036561370065651000261040ustar00rootroot00000000000000PNG  IHDROc#"gAMA a cHRMz&u0`:pQ<bKGDC pHYs``kBtIME $=RIDATHǍyP_D@$pEX,FERRK-Z۔x@ .!l @8X T1u q;Yw0;y;W^z?S{Kƿ\6)R.d'S7BUD7"Ry9yXvQs8 'E$;CvDSrN'ˬ"FFWH ) 8?w> pU*'XլXownw~U1.s~7>y83|J͌Ȍ'r+!bGBֺ)DN"zI-S21zDZ.D jf5  ;'#& ?fkTDk 5I/R'R5u؊"fI35zp9k l`g}NNRvPN%iSWGO%]%z$krcP19Bbd'/D\֓iov~3b(@Ąy&0DId>Kp5<8ޚo՚#j4/sǸDTdFFļGX6޷*-DžkNB"ߩKwFa~#"Q"h3y1dWd`)v#`~ :h =U{{ÆUgD "@οX>Ͼـ[awn9AnϤ7j߸q 6Igt 2`w;&QEzE::h W~JVnv*@= 9D@`lX ˯e؜mZis`qD=X8X80 ]޹ٻ("ó"[W*m(54lcy9q.z[Nci>eDeLI?֔?u:AiWː.@tB_(:h@o~(뚏9!!o\!߸((&wJe.i.:փJ7-o"'."VhWh:AUߖ\Tsr[2 sQ>E[bsvfk!*^j(n$ nS7<[:չZ:|b &NgY]?zrEru"mH/XqŨ)k9z>ǵʈJ"^$W5Td%U~TH_)| >wUe*2wʢ_l^=xiskU^V,,)ԑI]I&UyQ~oQ%tEXtdate:create2020-06-24T10:21:16+00:00%tEXtdate:modify2020-06-11T13:54:22+00:00D[IENDB`psi-plus-snapshots-1.4.1456/iconsets/clients/default/dino.png000066400000000000000000000040311370065651000241240ustar00rootroot00000000000000PNG  IHDROc#"gAMA a cHRMz&u0`:pQ<bKGDC pHYs``kBtIME %JIDATHǍSTgw$Q`A4" @ҀTDRLĦAb{owcb9 AMHI%NAƤf>d9U_ &$$(1{!u.B i O>i[BG26ja@pPja\B'J%H6rv PDoVϜt-I~:#d;,͌H zbV^r\:ԟ3k~0!IEH9F?98*AARU_?-^~k!Jߘd\XӚwr,÷&Nr⩵璴Ѵydg0J[E$/2o +^iڤ?oITSԯM* !>f+xd{sum'.46,nC W/GtR?OdTl>Up{7EI`Q8rmI%"!3P0Ȝki!_mˬX be쯯WMG!3 ,@4q0cUaw'?ڸf֕Zޢn 9\uKф)ث4'-N'GzW@tV mL;G E. -}$}I;gquX0SMo:M>ʺmӒݲln{n~mq7"8l}zLX2Z@AR?fi78U|<x/|_ui@)Kɤ_ O8V;xziY쮿?k @G+7H9 *@5*@f@<+6{n ݮ*y晙GJ)čտm(2Ǭҗp5 dz, ;70 |m$VZ HG>j; b٣:# W??=5/ԢaP0P\tOamZ<!/4 `e63@72 ]^CRNg9CJj ᄺ@Xr: ՀxSwRY֒ 'amq?c39U~K4WmY#?8~H_t3 $3R7q WKD=_uJ@p] F6\'+S=<Ș9j#r〭u|>~틖9C Bs$@ ;Zz8E:n;FMd׊&㳊n@ZW;ڻ(%ZN%إ~AAq΄̧G]cl,zωoW˧}L5 s@t<a2SfCͺ4@`c{E 6^xb^!14۩KB#{׉Y3qZ ~kװwC'@UU!cG-FjA7+ƀee=0ru_z^xIn!0rHFub5jF@k iYXD~it̓!=ALՠ(0ӊf#Ŧmn&4۟Ӵ;Lԣ;C[Q' 8>!*eГin̝&mG-Bi }3?U씀 'U?|SzSnf{фQoOEu-mNwT'IB2s۵w} k"SYUMXi(D-%g<؎_=_1y酇C=Ҝ7+|_UJ;2Fǹ9k$VӅU>CQ'YUMxqOBMhtY?| p}-bآ0wGt)%,p0ڕ$~ VΕ pދ7Za3U DW*V(3vY@>I,H*1  x2wO`UW(eBZ!UU/Txa-eTWvfr*JR(mիwȰ \-) 828ߞ|ָ޲`.k[A2gi)y`Fpniےm, iΓmrB sVU9c8I?NSM{d+ Mz0`jWe g̥cB uug4¤zSZsVa JW@@:DMkusZ{ⶢ/{se%LB4[dG$TԻZfqW"y.723 ص1FSu!Fś ϴ/vNcL`?!өL#Co0Bji !b)'N7=/+ =owS%tEXtdate:create2020-06-24T10:21:16+00:00%tEXtdate:modify2020-06-11T13:54:22+00:00D[IENDB`psi-plus-snapshots-1.4.1456/iconsets/clients/default/icondef.xml000066400000000000000000000115731370065651000246270ustar00rootroot00000000000000 Psi Default Fingerprint - size 16 1.0 2017-05-01 https://psi-im.org/ maj zet rion Default Clients iconpack. Based on the huge on from Psi+ repository. clients/adium adium.png clients/bitlbee bitlbee.png clients/bot bot.png clients/conv6ations conv6ations.png clients/conversations conversations.png clients/dino dino.png clients/isida-bot isida-bot.png clients/gajim gajim.png clients/jtalk jtalk.png clients/kopete kopete.png clients/leechcraft-azoth leechcraft-azoth.png clients/mcabber mcabber.png clients/miranda miranda.png clients/miranda-ng miranda-ng.png clients/movim movim.png clients/pidgin pidgin.png clients/pix-art pix-art.png clients/poezio poezio.png clients/profanity profanity.png clients/psi psi.png clients/psiplus psiplus.png clients/qip qip.png clients/qutim qutim.png clients/spark spark.png clients/swift swift.png clients/tkabber tkabber.png clients/vacuum vacuum.png clients/vk4xmpp vk4xmpp.png clients/xabber xabber.png clients/yaxim yaxim.png psi-plus-snapshots-1.4.1456/iconsets/clients/default/isida-bot.png000066400000000000000000000033571370065651000250600ustar00rootroot00000000000000PNG  IHDRGgAMA a cHRMz&u0`:pQ<bKGDC pHYs``kBtIME %JIDAT8˭kP.,4-Q`e hDAD0E@QDAD#@6<6.D1q 4<61"RQi7~rΜ{s=mY/){@18 D`)X'Y6[׊ƜmEC/ʅelqO`mc,a|zhxd0,(f]@3H=Ӵf=:_P٧Pn_ܓ|(ʿh[C_v? 㻂o=K7/*LK瓿JAx.<9 `aF`H.@L0`2eYeyFjn(^; gV/p3ݎݐ&F#Gi~:jf'3UIwK/fJ=rnb_ n*SEBA]1М5 o&%aҕ,`Y>w#!maX~_Kҹﳁk}tHa«U`C]jȭ|$wjk}cȸpS-\{ @U~PWvN ޣPWRfW^ɄA "=x/ 3i/ EVoBzQK Jf#A.EMW)WP:j7N_ eJfk Ezlug TuB#& eo{wVށ|՛-a[MYy7}P?b:Bn9U">PdK 5+^oy+oD[ˢA-m5(uSυ㳧cbgThw T7CRͯCSG)C#%P@ӷr;> 2($uhvkݹ_.aVXPy)Dߛ>Q;T&3կ#vLF'{|dth g lv}m6)6,ۃ eWQ0:|?fmv?e´= Ѷs=L! C)zFCn5zM!k!x[DbU]ހ|Uݗiw䟺"CnCK5rH/8ȧg.r|b φl/z@pݩRz@>1dU] lm{| :AOb]!!5$fK 9+o2-VRڙp*|;Z'|kؔʒU۬'Wu_ FPT%tEXtdate:create2020-06-24T10:21:16+00:00%tEXtdate:modify2020-06-11T13:54:22+00:00D[IENDB`psi-plus-snapshots-1.4.1456/iconsets/clients/default/jtalk.png000066400000000000000000000037721370065651000243130ustar00rootroot00000000000000PNG  IHDROc#"gAMA a cHRMz&u0`:pQ<bKGDC pHYs``kBtIME %JIDATHǕkTXva5\@[u 6;yծ]IKh- Ib6V@އi @uaߟb$4󆔽@^ N-SR %.D vFerOy<:Uul=٨,NN6h7uR8xC !zxYϱ`<~2.ggA P=Rb| '?h+49a2(WPXdms/Rm&=J`(XZH'kǮӟ,l5T\o8ׁMmU鋪t5_Λ7}쎁]k6%? 7N*l,QFZH' wZvJVKr1dk[5x&{<2 ;Ͼtn|b`}Q Nl@Zs"l5XZH'kZӹO-l_qP2' L ~(p{qm{~ ܏]9u['oӋV-tILm@vr挩U@fZ]!w6^"~%p#T3I2`:!lYt,Vl=iS4]i(ـq4%L%U9Gtާ2q͑=<Vy7_t|w`?ۀP'aK`BaTM.6z?pdOU}_b`NV/-8ۚUaP. ͊m,7+dxOҚ10(7Zn*=jޓ:1pd|ĸ;Qנ%tEXtdate:create2020-06-24T10:21:16+00:00%tEXtdate:modify2020-06-11T13:54:22+00:00D[IENDB`psi-plus-snapshots-1.4.1456/iconsets/clients/default/kopete.png000066400000000000000000000030771370065651000244730ustar00rootroot00000000000000PNG  IHDROc#"gAMA a cHRMz&u0`:pQ<bKGDC pHYs``kBtIME %J.IDATHǭiLwal0(qt(ȍ&:"/<tb1`"9"h2F[ETt:ϩy[LO^O~[SR&mY_OBXi{l+o>hv2spѼD77P!qQ\R;P8&nMlrmʦAcxN¿w*X%`gDAU ѻv4訲/Py,]p?x:h;5a-r`U; b2/dx`] {n`#]a63D_! ܔ0" Y}9ty#6Z!df~tC}G:gyO,!"x5@qPUQ^!n9>c65`TH4?OZaOs IQү4KժێPOQuCais'Tgu%3[r7CU ͮ; v˶+Nu2 C3@ܚnnm|}t*kj~a5AFfI@:=2#jO@ ;uxQJ캰N-tc!,NwmkXު^/ )+vC g* ӣ7ͶDٳ椸uS~~.g O 0݆.cFnݵlպ5K/OYtx\=0QV?̜e X*yo6ӂLw_'Ƌ`k\Mxj>i)*XA!$$F )rxM\e¦iqڡrBef*niAypz9w/% wxzAVMDx+k_ {Xr?at'FM+iƗ4y`!+k'@'>ァC}amS/:w-S%/oByP>F Z*uXVyBT1Ǽ-о_~ߋ^)} tC/Z rR'x3in2馀~z=[,4b%ccޑIɒ W_|%tEXtdate:create2020-06-24T10:21:16+00:00%tEXtdate:modify2020-06-11T13:54:22+00:00D[IENDB`psi-plus-snapshots-1.4.1456/iconsets/clients/default/mcabber.png000066400000000000000000000005521370065651000245720ustar00rootroot00000000000000PNG  IHDRa1IDAT8O=N@FW NMΥ/P•e 7)F(CEN@G|Ѕzط8q@ >e͟'If\$9sam 0y>,y]E$4M#^WGeAJvoNVE+MH]xw?,Kn&hfH2KgI~OjDnuxiq0 欲*O?T@_?Fc))#뼜IIOشkA :s5 .9Ƥ&%3/;}{II>TǚvMSC@'{a*oZ  %!6r0yTUR,\!6́ q0m qo(Jlv[%?4GuYꮆPYiCX\SF4>E0m;\=DZ?ow,'nF >lTOx i7?@/.Go[gv?Odڑ[c(MI  p[q#x1g ;0֝*a W >10l| ~pqa-hokW>3ܠVb[ԧ AjxqcD:Q#މj]dLA杬Q]սOStryG{J1٫KYEY\b%}<{s)w۬&f LwɯjF,H* pbsAE2Ĺ3A`M5uCv%|W»:Wp B8Di$֐(O쒐"guiQtg"JiMh22+!͏fTA Z#ђJۣVHxl=FoYXm 2s,Am.38t%azEiQdPuop6c.Ǘ͹5e{^Ös|@Yz )0Vі7d_<>V@[H=1ΪN7On .7NO(f%tEXtdate:create2020-06-24T10:21:16+00:00%tEXtdate:modify2020-06-11T13:54:22+00:00D[IENDB`psi-plus-snapshots-1.4.1456/iconsets/clients/default/miranda.png000066400000000000000000000024201370065651000246060ustar00rootroot00000000000000PNG  IHDRgAMA a cHRMz&u0`:pQ<bKGDC pHYs``kBtIME %JIDAT8˕3w*iM$RZgEZy⭣-faűR#mG{4ͻv+ABH$DHsM껿aaG U-Q͊|_:Pr [bP' L+4Q*0Dsx˶I0&+S;̓bd%Vv-ZYs҃ᶆ(&V*Nҋ44?}/}R]7+q*7?A]iתKgW^ҭC᎐Wb:os%PתE_CH}uM@BJ{}ͺ_ԧJ%*Q54&O!^І?( lNx"'9F򽝒ƋU py{_!{6ϊG. 9c'ꁓ\0 xT!TGOUz֬ Iz#=0jV)4TT"QsI"'فݭXHК7&|;!@!/" Or{Q ?tRvQlV/~W[J1(ψpx76nf@t7p c~ Yd t|XxE͉#mTNTD 0b/y^X5awLwkxwۛhKa{Z$pu0/Mw_-V;{bAfȍb筫DikCI$sa>:OgZO;u/{8R02pTڣq!V:4NO߸iZnνDțEkeu S{ȠX/5Mg)ձ0q\H1@O Ffҧ2LǸ.@ Y*G)Wi~'T)}RS3uzNysJR{٬ fLOvM,pSLo17]w p5ilw<˴Ϙ@T5 i9G!$'chXc$,,ݹ$h)6[?sX1M2Ȕ!JJ 3 %/c,Ѯ)QMED0HL8ٮ _y*Q3bnhIb2f=&nfw쫳_ys#kxm(h8`@PnJ_[|X.r3MjR  YNtn\80Dyw=>QZLR*OplTNy4g@Tb+=D:]rWB{ٖMiP0a,y$V?ZlOZՙ(❝l@J1 z$gY Iی9T$T;Jne:ɬŷ]؇SbMQD8xAmP*]tu[?]~}S8f$v5Uv>*u}驴%&iCOș (ӥF6Odt1H5t)`#IS)q{F+PT.2nl^_pO/0!@"BLg%]lR$q^dw~2ת0YӜ?gKU۾_}Z,(~ýZ8hi%tEXtdate:create2020-06-24T10:21:16+00:00%tEXtdate:modify2020-06-11T13:54:22+00:00D[IENDB`psi-plus-snapshots-1.4.1456/iconsets/clients/default/pidgin.png000066400000000000000000000040411370065651000244460ustar00rootroot00000000000000PNG  IHDROc#"gAMA a cHRMz&u0`:pQ<bKGDC pHYs``kBtIME &~IDATHu{8ԉq#Z]YR$' :[Qsbb,BkD1unj-Jc0K5|?=Nϳ}'c7|yAd7l6 ٔo:aw>,/~H$lS+1fǬ̟ ;GsFL5leٚ/:'${A$Vl"|n]Wk E %o c7d\Y@+Stlyb ~]89ĵ3]@1\ XswAl"cimdyzo/GyD[r[{ѽ]V/|.|pɠKJRZP*mVu%2_گohnI`VaX/P,tFN@XB>ЛY\0޵JĀ(a~TL)sq8Q̴w_ʝ+xL첦&Pf 3{Ι JW/^,XxEɫ)@ȓ@[xs* Ocg|M|&7A_~O:Pm<ҕ~8[/qh|iҚ +nʮD_*I wv+omԆwЀG{O+ޥ/ZG1+SSVǣ, Djr[ 4f-~@뫲1N0ln1@US>cC]ܠ/r> yD4`D<"+ ~<Q=kp$H.%W3@~rhIHpjwjOOWhԧc"wE,B$\WE)_`t&ˡ_)-AoZ!h^BS,Poy-> .ox_WI`wc#ʂCVr2 |GT>V <[n@N)5ԿgkMprLp'v>~Aq~J6^wIWnnm7sTٮi迃ҹ{{~R w ]CrI &MRÝh_6*mx[]Uf:ǷY)X[]Pim>x3֡8cUkY,%tEXtdate:create2020-06-24T10:21:16+00:00%tEXtdate:modify2020-06-11T13:54:22+00:00D[IENDB`psi-plus-snapshots-1.4.1456/iconsets/clients/default/pix-art.png000066400000000000000000000040241370065651000245610ustar00rootroot00000000000000PNG  IHDROc#"gAMA a cHRMz&u0`:pQ<bKGDC pHYs``kBtIME &~IDATHǍYT4(RE|_BdA!"j% @dU* H!VDMH; v:j;G-n;3gzr}s 7DD+%'$ZZ{ge/}êӷFGĿ+Y:fp`;11˪aw+e ?MU[\ԧB6TawYr\09!!/X8癤 w\2V*MѶ"}vC'ywyv nյf~ݐwH뗪ωJn @H8{Hخ1 bZu"V)+,@}r t\!q°gXqWDt6_DɖeCfF?n{vU@Ĝ"?LrW  o*؋> J$(2o쎌.ޓ`sTi҉QHL1bN Xh.éJe˸AXK.أ 2N_Ur̜$ ?US*1 b 9]D: b9'-x";%~.(7牏)oI.C@Pl&u'{( -dwq>@4q|UcjgsJ1@ĀFTu)P':Bi 04S[F+TϬV,wR)xҧ[CZ/ ye"@6Bnj__no򠇵O/CWlFu-wF˽=ԀzXK00ߌ~le vi)gULXaC@a+jߤXbi r{HI>oĬ-fJX@k Hzw8_i0/+~J)4,[e~qaGDGڥ8T^d$N !-~"J]l<  _)&"HQ1*n XG̼죧V״E5r#/I~/.k|;-Lzrmq=4Hd @tq7^pWHէ '${n`gkHGB#܋ۢVk5~4ɰojO*cgL9pDDNI3\l ls"Bqyasz'0O 8DJa?7ZwPC'`VXM lMt7W{F&nuZ, [<|Mq_#k"sdC:']MkqG] `,<|4{X,bVs$9+jZ- LvmU_ D[LC|o߽΀˟]a #b.|)rwܱ~e_D8UKVهMRAEN"7~|Cnqz[ُ+ӯת߅d%݅*q֭3"{R̚ʱaey/eŭD&x==r- KCV.oPaήV<^9-ȟ,m\n1ug?}<%|qS@XǦ[lgICZS%tEXtdate:create2020-06-24T10:21:16+00:00%tEXtdate:modify2020-06-11T13:54:22+00:00D[IENDB`psi-plus-snapshots-1.4.1456/iconsets/clients/default/poezio.png000066400000000000000000000031041370065651000245000ustar00rootroot00000000000000PNG  IHDRlgAMA a cHRMz&u0`:pQ<bKGDC pHYs``kBtIME &~3IDAT8˥kLTWP[Ab"sݻwvXX7fwxKPA(*Q1(Q`k`ϴjQ+F6mV(4656:_d#&*&3VWYԛ~ \H5ieU+-ʌs 4zW脉 K|oQv.6L-ŬۜW1ˏbό[Wt,Od1ܗ{sØeqXryVuW;ZMQ9oG8oW-ț|kcy\/kɾl!kӜ׏]I'cU]+z3ey[\^>!$YZ@:vfj"؄3YK9V&}ǽUŃYSnzNUy{%9j[}e?睳8/9e=8lUgȏ&OX`I<=˜%Z`P;Fok+':4)!ϒCk)6Ѳ1"ԁ1 j_U;?un_=l޼U7:~jwB8ZQ͸}.%4A~w;(ʉP c  ~v`Kf\X (̘- k3hQkn^?q7=5sfGΫzrQ`-&Bp>%J"E1cD(jT&4|a2yivVMw/3k]n8o)PtDȀ%uXQx?e;ΤG>'B!|5?!t3"ԂTX/OLW" Y}Wq -m12l1q@ާHE6̬e$P93(hȰ@SHϒ<̗io$ӔAϤ8p1~H;ȂFPBj Q1M@J(h.%ꣶzejAlzh0Dhwl/SZ')ΒncL @PdW2 m%#"h\;ɄIAdB65<%(N"A(Ptn&* ;:eb`=Pœ L2"uQ N{!%#%Hh>\HEHP@bh> }Jz@t[k,T,}hrs+QiN%;C"U "HZHDTvpq$u :S@b (_ܿ^2C%tEXtdate:create2020-06-24T10:21:16+00:00%tEXtdate:modify2020-06-11T13:54:22+00:00D[IENDB`psi-plus-snapshots-1.4.1456/iconsets/clients/default/profanity.png000066400000000000000000000032671370065651000252200ustar00rootroot00000000000000PNG  IHDR rBDgAMA a cHRMz&u0`:pQ<bKGDC pHYs``kBtIME &~IDAT8eSeGST&}~veXDC -habL茧 3ax@x(x&<4($`݇C?< >r ÚI=4VS$_VRaҪ,eVhϑEc%_(Acu]g cp82ɩ[kk_5Q]Wpvl3B~'҂і/#A Xsʛ01k<E9Uߚ/PJ~8sҡ˛ss:aI7 R˿%j[7>;--DןAtaioU[] W ao@Vc.F٢繵ިJV)9-DI)]Q>}4,K̫F fjڑmWZj'S͍[j?c=/0=:0qSyq#D>=ee_rM ;MpgP4񱪔n]湎/2^ey=,>cIus6 \jgi&@D OOc+)X2BmOԒAj\ E}|CMDwk b5;jtkv=V Et.>8!6zX| Z<}+?au0'Gu4@u"[j%cLj pҽ sZt9_[[unMo= t♪u&ʷ1 i+M.ϭA>/\l$X>})Qb R&@T8c)QSv`oDҞ:Ô2b}#?Vm'p Y\c`@ƤRqS׍-OZ]l[Dt+XBt$gD~DsfYLu>Q@bL -N;^bWf8Tzˎu&ؙ_^jAg8)9آ.6x ~VXU<&lSϏiz$|t iN^0W@ ~hV@ rD/;qY&U~ĵX|ϨBˤ~yλ=(:i6ׇS솬F9a-Лb_MϜ֬-1Oʘq!Ą+a%m?wB Ղ3JՌ7a1s^qm0Kg*AʺϪFAצϒ:_%``i|+}2Ś.IKIZ-&K0OSUCbfG xҟQjX1`1}+/g?pzVWsݪ+z_c3j "D5ƿ&$~ƚgԈUa!Kr;,Щ:Dso'qsy_Gfx[tgJ( -,/iiՠiizfMnj&ZwsPhXx)(!ثi]{ O:=PQr2u_K+$(_%Hb6};i8o j *O߼Hficeǎv\R}uGAZq[nܡ/fuE!jB×eךtB*3Qd ~2̵.b J=H=u[qz'[qi wle y-P,l+-v|.POH^\tW]fp]tĚLjt]vqIҾd YƖx|BBo~ЛԐ#0"+ٞ\s|j@@s7euy*Gߏf5ޖ͠yγsu*rOz}a:nJ0MbaZflOy]GZ LH)?:/{. |[Kdǵ8y;qfi76orq=|.a'ٖo,ĝG9c1֍#ڌSZ1n)bwzpz5}eoe3 RF6\WwU'i,R?\ʱK)}6lFHrgKuy['τA@J⥹-z;u<tÁ`vjG]k%?H|Nᡁa}tuqr QG1%Ҏl'LWrB@uZ[uzʣMR(0xS7aa EBQˡ|e/%'3u$p moik DKJQ(þYyOWeD~URBJ 4rK 4g 9Y[LWǧ$v@?q3͎ \6{@b:&x 5,꯴$v=7Dؾ+0)/V>XDHHh3v_ImfSzcnYwSS,t64FJMjO'@ʹMjbҵR1Qbn{96Z\G:TB"r2js"CI ^->'k_!8'J%tEXtdate:create2020-06-24T10:21:16+00:00%tEXtdate:modify2020-06-11T13:54:22+00:00D[IENDB`psi-plus-snapshots-1.4.1456/iconsets/clients/default/qip.png000066400000000000000000000031141370065651000237650ustar00rootroot00000000000000PNG  IHDROc#"gAMA a cHRMz&u0`:pQ<bKGDC pHYs``kBtIME ';IDATHǝiPe԰D*H J!%jeHT6hK 7u l#w-KYES)bJ Dazy{Ιyˊ wsLGqW׷(n"#ߙ1=&.]1x?vjԇ,x6}rXS}Y|ͻ/.[^ri:8]ĆE5H^q k2"95JE/J'[.-,#z7u{Fw3J=5 6쯔~$55nJ:&K%.mW?J~jLJ6JϼdY=LI.\]tigg0!OÇ%nIImo,S*Բ|黵M\m+^V:?nUҙZK !'*_0z??<ցO9t#eBqhK~~\S̟iu=JTl W2F#c{N4EapV< ɐ>]A}@W}+ ^ciY_^dYkAV[σdZ ^et , 1O}'yX>¦P wH8SjwByZimi-Hbc?4@꽳V,0u|-.8i2}/pb߰({hH]G٣@ʽ{ $o?o?x} iMY::ӰI̩̋^I}b)yT=z^VW S?;`#H `ogY}0#N˻&RtGqx5zc633lW@JJOMǷw 8KWعIN vX~ ޶iAY  Y@/W֋  =d,m n7\s?5a ^3^=;20}IW']NĴ?E{D{C(hW%%tEXtdate:create2020-06-24T10:21:16+00:00%tEXtdate:modify2020-06-11T13:54:22+00:00D[IENDB`psi-plus-snapshots-1.4.1456/iconsets/clients/default/qutim.png000066400000000000000000000040521370065651000243350ustar00rootroot00000000000000PNG  IHDROc#"gAMA a cHRMz&u0`:pQ<bKGDC pHYs``kBtIME 'IDATHǕy8x[۱-*gZEe#S6I!"\1&1澒4F  ͘ 5:<ϗoXțb++}M{&9Ys:FqZp=Ÿ`HA!ś6+Y:OIG9uQJe][)S|SF1X:B{moBNO 1=*sbBGq0{gCΛθۋ< wKVY?'El;׫tI]wmwג%9N#Ďgo"Gi71)Ke6Fj.{>ʭWvcB'Umk%KU. (6[/IIlG][~%3o$}ZSo̓v)iOgw4irmnk _#p)K{a kiJ[|Ɔ4}fO}49`lCl1t2X~Yhl˨  iTeי4HIW`"P6}>=pX y~tJ.s5R@eq~g@ePlH[8>o Մy\SOEgRZ[εcP_SH\W+%bjVՙNYM X[kzX=ITlEL K+C+iBs QE[-$˟L7V6[4 ;po,zxUgv3M;%7dP)9PϚdU?&V8l#}\wpǵ.Mj6nm"+-.O}^3f)t} 58/8ˀۑ՚\`tl`uE>|(b&X+u Hrd6 J w.#30a&f!mQJ~#g/àź5432k p؏ 9Hưw.׊ZgC@gf4B5nN7^;f>W#C~>D^%Nb.QJ7KϡQKldS؞@3}685/ssP.[ucgV`d]ʅ ӈBj\x.`A!<˴C+"&Oir uԱ<.}WtjWV0UkVúGgnj.;`#MC9! ~508:7Z;\SMԥxyѼ 0{s%tEXtdate:create2020-06-24T10:21:16+00:00%tEXtdate:modify2020-06-11T13:54:22+00:00D[IENDB`psi-plus-snapshots-1.4.1456/iconsets/clients/default/spark.png000066400000000000000000000034111370065651000243140ustar00rootroot00000000000000PNG  IHDRvygAMA a cHRMz&u0`:pQ<bKGDC pHYs``kBtIME 'IDAT8˅kPcx"F1ĐfR5E(X*Ȫ (S1Q1ҨDhQQ\C EQN D1{dw}L3枹z3BI}B|jݲD(irW;WytII6vy>f=?y?{b[/>@α~ER%4]tyܷ2sz\p/ڭ.SLaDҋMg)j~p"eG0;5ZqS1y_3Dd@]7rɍ_z0e~uo:3U\1(v= _3ώ}"1@MۭKJ%kE@Cxv`oq2 )AWqdx"%O@æ`/! yN^Q~r>:Dz)>a V!`Ur@XLR6c ЩJd.@K|3k~,%5eɾ¡v} ɠH`u =.TmBPٛ7<.Ox8sRC<7A+WdU~*چ 1R2< <A,y3AW>WЗdgM˪m-[ 1 ˜7|'˺%d<崁bsn o^0&Ek]]Zi.`*w}=de{bZ$@[z)^ RQ2Niss;j/S _[.`-|29bGawE#)@Rǜ n 8|<" rgg߼4{u1=7? ں#t`JgXr 3E`x{J/^,_Ӛ># {QE֗Ϲ NGvuմ.*GW}Z8ni*-I^`]\70zƭf\p,k]j;+\}7|N^Z\y]R8VկIYݣ2Qh9Ve\7_G5!uGd _Sg]MuyM fujlcsaxl]4B'V5aUZzqU_% jiMIoOSuY.~a%&oQƶϽ7$#G%tEXtdate:create2020-06-24T10:21:16+00:00%tEXtdate:modify2020-06-11T13:54:22+00:00D[IENDB`psi-plus-snapshots-1.4.1456/iconsets/clients/default/swift.png000066400000000000000000000014031370065651000243270ustar00rootroot00000000000000PNG  IHDRjgAMA a cHRMz&u0`:pQ<bKGD#2 pHYs``kBtIME 'IDAT8˕OH?΂F9Mm਄,3֌KZ+t2J+![("QOV !Q0Vb fLۄB{FxD]p[7y-X}GNAKMSa.8j#{j2QaLL̶xE|-,zx. IS8$)V5FunH=~*X g|m"oKJ}~e;T'w> 6lo=_A ("fDD"fENO3Vla=P^^I w K5 /Ǡ!.0[i`!8d?* 3vB$%&9;J(-Е9&xGK:ovZ5cpa·` CГ% PH, mTC%tEXtdate:create2020-06-24T10:21:16+00:00%tEXtdate:modify2020-06-15T23:06:50+00:00HIENDB`psi-plus-snapshots-1.4.1456/iconsets/clients/default/tkabber.png000066400000000000000000000026271370065651000246160ustar00rootroot00000000000000PNG  IHDR rBDgAMA a cHRMz&u0`:pQ<bKGDC pHYs``kBtIME 'IDAT8˭iP]IE]c(R;&5AS-ZDmHe@`PDQuAhW]345 PT]P`Yv>49ͤS~y@yi ρ_m,w:X>*iw)ﺠ1*P;e Vz?eʘ@ߵ>rgbccŐQVn3<Ήj> -z$VDUAGQ9e@S[AuBsW=vt 搼aK!-5=h%8Ft 5 (wU}ç{G=KAek6A^rO8C6=pO:7k8Ar!@r , kHƉ.q]4ABtf,})$; i^Y.Nv=,d 9J 6ϒg:vgűc}%Xќ&f%g"|@!$g7+$&FIMi_ v/ZT8xϐ-ִybp߾梽PXK<{"Q>_x& WҪJ H[]4|0qCk~F}wf9YIVUIm2bz[~F#@^qyr"ǚM8< 9*~1ƜYBƱWW?>yEGBﶟUjfu7I&uDI?HYt78d7:a҆CU I)W7E)ʲc-{ " 7sy-UshH}Enkqoq8䶟{w1ϹXӾUBئ[/K*_ĹM%[;$$/HN08@ Fy6oYzYRe^s8sBst߾X*_9,^鲛Q)$/p0i{s>O"݇cBq[)/'re,X;įNNlMxMmj;)7Gr,04xCJx8\s򭊘t[SUAÒ= /7igd٥_/GׇiQ̋h%i:`p.LT=7N_̚^"Ӊ%tEXtdate:create2020-06-24T10:21:16+00:00%tEXtdate:modify2020-06-11T13:54:22+00:00D[IENDB`psi-plus-snapshots-1.4.1456/iconsets/clients/default/vacuum.png000066400000000000000000000030231370065651000244730ustar00rootroot00000000000000PNG  IHDROc#"gAMA a cHRMz&u0`:pQ<bKGDC pHYs``kBtIME 'IDATHǭ}LU} {uq3 g~VI *^ Vx)Z%`נY)iZͷhRJb9JSy1!S0Y`qQ,7lw;yswuA@B-z^0BL] dJbW⽠Tу1[caxޘB|]qV9.5 7۷ۚy~3Z}=vnL.*A)DwZ=l xRW߻G̿ࠠC  +Ct>`ة[wBɜӠ9y8U0vvg@зC̿QFCRzTW/᥷1z ||x|P7]rc-W+={%)uBU^ YXl7 :?h9<1ebC` [V(*yT*Ђ6.?:/tL8~(~ֶ?<͉gl.-R) -{MWBBh{`F%(]d 0Y}vAmn-X\"/IiBHh=n?[qizI^|2VL{{|S3ƍCwu qJeFOBJ~hyhG.lin2k"8;Cv,Wd< 4$t@KzY|)MI @ٔU q"mӷ֯ \zdE 0}^}c+zΌPW>^pghyEe$It=4,'}mFMW ّ􆤫sG4B[;8-r/\З+W PUàGNy"싪.E[5o§;61 $DlǼP ,1if:K<'耬2"{aۼ+COYncܷg)0kE&G"[@Tzt^ؚU/ñOܼ'{h,,-2zk$D*ADzǏ[ׁ,ɒ(RP-nq)i&i,ɒ(6iÁ2kzw̶6צezަ-nIq.źdO%tEXtdate:create2020-06-24T10:21:16+00:00%tEXtdate:modify2020-06-15T23:00:32+00:00XV}IENDB`psi-plus-snapshots-1.4.1456/iconsets/clients/default/vk4xmpp.png000066400000000000000000000037711370065651000246160ustar00rootroot00000000000000PNG  IHDROc#"gAMA a cHRMz&u0`:pQ<bKGDC pHYs``kBtIME 'IDATH}kT)UꐚT(are0ɷ!1N]vK*QۻaX0 ]39?ZgY}zբxąeQyثZ rAQ6eވKeer4|rCRAn%~OPW <',uxF`jY\"xx fi9ma,HiJ}˟M NGF x&]·u[*RJoNˑk<\RET}j ޑIԽ jn6V[:|`;!|m8X0-/yԮ{@ngH FG}4L7ӶCJˮ2 \O[ɽyR;'JﴌO3G<uUdg/=`D# " ҾǶ T (ƫtߥKDzEY|mO|OW=<\`FW{<kz"͝]ӯTY fb:6O us` {/}qU@9# ܱw;:& 2[XVm]E0 Y%Ufu2ȯ6vY^bd F4]wx0~ߌd7nGab=0aWp)ʾu3ˈ@AcR+cv7p)\u&|dT_Pԡ0_Hb}䯪f"g`o˦%`?w`Nч{wN}U0Y0ed6+qO^rMe픞^H|zDh]_:;н6[7-ΑâO&džw62U{U>0=2 J䟊}d UdZ}k|tcb^Bm`I`Rjy9e) xDC ;~inSs!1r@nRO[叚g-d/ĥ2aU?8MiW^40)yq vI!s_i pV}t#n|nVg#MOΙߙt= 7L9pi5D)YE'`Icci%,JS6J#V 6yoKXX 7SLbqC >gK堶<+ Vb),7RN~qM5Qݓa}ׁϟN쓇}!wQU=^2—os$}vNI(uձVȻR.u-@j 1Ă<\UŃG~X RnB)Hut^gjX!sLȷ55<5#W?;m &[ASoi RM->R13޾/Ms䨭rkXzExCPehSj4[5d DeyCC&Em1؄8(N7[ hoV%tEXtdate:create2020-06-24T10:21:16+00:00%tEXtdate:modify2020-06-11T13:54:22+00:00D[IENDB`psi-plus-snapshots-1.4.1456/iconsets/clients/default/xabber.png000066400000000000000000000007331370065651000244430ustar00rootroot00000000000000PNG  IHDR7gAMA a cHRMz&u0`:pQ<bKGD#2 pHYs``kBtIME 'IDAT(}=Na %t42Ph2Vذ -iF;-@AkqH 7 ^k;&ޟ Ք({/}HD".>Eծk::=O)'bH3iA$vDӢi}oc^Δz_Vmꅡ|HXxD̆̆ɚ4X/uP9N^^dF?͎fr~ C[ccXVBEx}WZGԣa۾ϾofgGG"۲Tj=Ca0%{"$`Z0Dn*.+5r\7.*&(,(,jM0ŗ 07ZZ&Z[_e|Vs|^`??|~8 +Of*^;/YQb턇,G/Y?1QGp3dww3n)Sܞ~T"*ǒo' rmum] dZ%%1g@[Wë9~#ˈ zqC SN#J-ON yeX-{c| /f}M]_dO3a;h1:F[ 1R \"&xo2 KߧYuXuXTϠ!@?D77_"氄? MJe`zs[2ֶ#Yk, c6znW&U66< ooSQP ID@Lܜ@Q~-pEb(6567&y yy&:f٪2[٪il~i;bJZ*Y%tEXtdate:create2020-06-24T10:21:16+00:00%tEXtdate:modify2020-06-11T13:54:22+00:00D[IENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/000077500000000000000000000000001370065651000214025ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/000077500000000000000000000000001370065651000230265ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/angry.png000066400000000000000000000021201370065651000246470ustar00rootroot00000000000000PNG  IHDR(-SgAMA aPLTE%QJ L vLLEEZ\00+ + +, 0#, - $"#) <T`aU:> \|(=HH=|)]>A d:?<2u'tf^hZF_ --b!FXg^UakAx+ ps }. =kaVJYa52 jk52aYK> LXUWbbXTXL> > HQI. . JRH> 2 8 ?5995@9 3 4 8 9 9 7 3 0#'xtRNS -JJ. XZ  \_58QTRU9;gj km?__? vpAgIDATӭ! Q1>p`,)Dy bXDdbL `==6`fʫ= O,@ap{\rFRCGA`NVTRHǰ` {4QB*.[mo/6IENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/beer.png000066400000000000000000000021151370065651000244500ustar00rootroot00000000000000PNG  IHDR(-SgAMA aPLTEȺ~צȔ}ieV>r`Q|o*P!Y*l]1Ͳb:a=kF&U3qK2{mU@RB1uȹ˾˾ʼƸĶ;Ӣ|ػo}_J̮T;&wV<)_@srZ8(L,fNsa|l[]:&L)wYGp_N_=$QV*qZFl[Gc@&c`/mXF7'n^I_?#cd2iSCvdLs_iF*WuA{cY9]Z{kxXAy:`BuwbMnL8}lZJ9kZJyiwyhsƻŹ~R2O0t_êɻe1x6y6`Aqήʧi1::W4`Ɣm]/y5MLc0щX̋YX#@bj}3aQf#{;sa{&"FpΎ,ޓ!E݀mƏ,}Ӎ;p]Ɖ(v~ܕ7cQ̉&FJm*ҘL7O~M3P1tKժv\hL1]tRNS _O|Y~<*zQ(ml,a&Z!VaWd|4[aSf*è^en vpAgIDAT]1aM܁BЪ%h(P F+DDb ޗyo@"5/"NOu1JÃYJDgy` { .EUL]Vb*߅0HU֬0p@ *W{LBIENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/broken-heart.png000066400000000000000000000020561370065651000261200ustar00rootroot00000000000000PNG  IHDR(-SgAMA asPLTE   y y  +05804)- _apo$*%*tu]_ 15??PQ.2 79#$ #(7: *-""*+ #$   !         s          ~|}ywsVTNKGFB?DCKINLSQ]Y;9311/$&,,203232;9')%%'''('(%&#$%')'#$ #  "!     Mi̡tRNSQO O?| b;Qv(j`SfjQ<- gÞEp9as^ k q&U#2t$_ vpAgIDATWM1Jaw})l=@n#jc!#ĐXy,,ډ-, t40c93mxyUUfdVҾ"3Wp{}3ӶԚڬ*P89nB1mwstOefb|KH: _[C kI!I]}_X"<IENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/coffee.png000066400000000000000000000017541370065651000247720ustar00rootroot00000000000000PNG  IHDR(-SgAMA adPLTENNNaaa../ɾzxkԿIIILLLHHH***FFFNNNdddֿε||ޝⳮѶΕñýĿ|ÿy~^QC@2%2%2%4'5(I:,peZz* (0"/!% G;1رzsrh_lbYlaYof_zszzrzvnwȿvrofjg`lgazpï|wrjoibrldv{¼zoyqgxl{pmh^qmdwskyt~vjwpdrk`ph_ztj@xtRNSC6 "D0 )SnZ: fۧAg%H.2ylq/A>۳k,?IC2V vpAgIDATWcd```dc@ fe 39)2'  '{#rHI`bLR& *!,!+(spCDX\u d%'CGu l14DHu l79LPv f$&>CP@P.q9|LLFf1g mpold[Q F>AL L#rH87!A C G E : #  9@   2:  07  4;     36  2Hj^VfDb9X ?Nrf:@ZEbBp;/'#wsZ~> "i_tRNS! , 5YԚ,,-,-,-,-,-,-,.,Ȣ{V<+)\[$G>'F\w$ y$%a0..,''t# Vq#!%#%""!xobY \ eb F V `b_N گɵȳޙpOj8T#?3 66*D-G(D"A>>>8-#|# )/5;@A@9-"x x#x*/$559J(B.B3?595440.*" $!*'/,1.Nf_tRNS! , 5YԚ,,-,-,-,-,-,-,.,Ȣ{V<+)Qeba0txD'/s$}q${n*Կ)Ʋ"%nayqqc hZl_ynzp#3NCcWnc qe l` XN  KtGs@k3^)R'O*R0Y7`#FkLLllAPḧXXxP YQ~#r722!kK/$a|qIENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/fingerdown.png000066400000000000000000000015741370065651000257050ustar00rootroot00000000000000PNG  IHDR(-SgAMA aPLTEv1t(w 3t+v~ `ߕt҃ pߕaƉ|֏ ta ˄f{0r O\B])SAjj +;HLJ1҂7y ۏ%v ڎ0"ّ"ېi q "υhۏ'0s ڋ&ֈ"?al u| ى$zډyP\ aݏԈi ws ۋӊ׌шӈ҈zcrf ILK+3ILJINI34HOI>43BJG?5+,29:;4( #',.-  %''&#:(utRNSϔ%nvicS3-  .Z- .xGntvv}pC?[( vpAgIDATӕAQwSF#/ 6"U($"" o*@!eJ$cO$Y@U4Zbg' K%ɨpdjAV?Hn$կh{vt[3ƏU~:Սy͠'.Vysl՗xd3IENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/fingerleft.png000066400000000000000000000014051370065651000256610ustar00rootroot00000000000000PNG  IHDR(-SgAMA aPLTE؝5w1ݧ8l/0{a#ܚԈ Ј:y Z؞5ܣ9#/,׉%-*(#+161.ߘً*+"&6:86443-؊ߔߓ!#&%#-(!&Xlrsqӈ.;ٕ/?>=;:6/,**""" -gtRNSK9gp6$((%;* o J }/ܰB(NUD!AV vpAgxIDATWcd@X3E032~8@P@ft<$~e`Ԇ 123, |]qD8C!e<:Ps`^GFCI9T0P@, &0!b*IENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/fingerright.png000066400000000000000000000013741370065651000260510ustar00rootroot00000000000000PNG  IHDR(-SgAMA aPLTE͈ީA١8$$8s$m֊ ֈ~ ]ڠ7٠8ޘ)/(!&,--؎%-*ڌܓ./2/%ڔޕً ,3456895)%#&.#%%$"ߔ92ۍosrm\ :2*,҃7q k m y׆܌ލوz MP L 4Y cg l k Z'$6758CJLB<>ADHNQM<>>BHOPI)*,/5:<= ""#}pY ftRNS/Z0j|C$((%)pY՝X ,3Ȯ@ATP- vpAgpIDATc` a4#?ǿpF>FF?\qv@6ff n!.F(֌G`f@lb 8C0O/@C9  [>Up#qOMIENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/fingerup.png000066400000000000000000000015331370065651000253550ustar00rootroot00000000000000PNG  IHDR(-SgAMA aPLTEԖ"Ӈ Ӕ%Ћђ$Ўϑ#ЎΏ!Ύ}\r ubֆڒh q ڍЂ u ua Z dRL TLYN^Ғ .'ZҒ#5.ͅ ё"62ωϏ 53΋͍33͌)#-ҏ/0ݘrd)8#,854-$t (*-;%׏Jݒ,ޔև%Ԉ̀r mҁ[ aq ًg 1D e l>Hc sPGj ݎڌzc KQPL%*LTSSJ(?JNPB'18GI3!,<=#!32&!ߐݎAytRNS p&** )#9O7<ƪaDBwL<> "غ ,W9]W vpAgIDATcd```Tfdd @`e_@p#?g`ή`0C333H M60ي&SXYZ>P \ EiIENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/fire.png000066400000000000000000000020421370065651000244570ustar00rootroot00000000000000PNG  IHDR(-SgAMA a|PLTE]d#-%0,AOL*.6.4/'-T_s}  "*""  #% DD(2)#Y>BX(*$+ /"8$+*G-A**@EGJ& 1< g7/;6CC7"#62:2A&#27.5&,!09)"!+)1   .E%*:8((-I![& !  *  6S  >:78P #")35\ w Gbv(!Z+Jo3;l4e5Z396O%K'z:83Z%`,862[#r,686\(,8<9c2R7=?:{0T#4Ne+\D7tRNSYe[ ^@xs*Ns$**Je_|g&G$O -H{g]<ͅ*2}3 vpAgIDATM; BA E'"f"b\X<;wa*q Ɍ Q:#VA QM;(l2 {'腭k fS B:Z#c!{IA9 ]Z|̃vhU0;—h޵y5y/IENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/flower.png000066400000000000000000000020631370065651000250330ustar00rootroot00000000000000PNG  IHDR(-SgAMA aPLTE$?*{ -9v ١]iT`CRQY[b [cL\Dw(A}+C?nC4Q@g@^W&Bc I(S`{'  +&A &%+w z "4?_!,  2&; `tXIENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/grinning.png000066400000000000000000000021211370065651000253430ustar00rootroot00000000000000PNG  IHDR(-SgAMA aPLTE2g wwm rZ ZYZ|TZ[TMJ{ zu;b~~c<sv "X]"yfQUf02Z ] i jg h c c ^ | |]S n mS` {|_ U e f VYd yyeYV_j tyyu j _WDSY[[YSC]gmrvvrmg^4EG1@QQѻ=å4GC5(.-U 77|]1.("'Ҧ &)*ߺ$Ԫ!'#"%''((''$${;J8˜1Ο.͟.ɛ07Gx3HǾŻƺźøù<͂]h7|P\YvIb0^ֈ~|[ QPPQ^ сۀՂwnoyلwWxtRNS -JJ. XZ  \_58QTRU9;gj!mn@aaAlhF vpAgIDATW! QEy\?8L0 *"hsb3݇IPdbLdOνtn@ȅ '0f8=i\rFlq)AGA`NVTR6c=XIRvԾxܫh> `tXIENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/heart.png000066400000000000000000000020671370065651000246440ustar00rootroot00000000000000PNG  IHDR(-SgAMA aPLTEt          &(,fh|{TU# UV惃qp-0'QR  XY&9<#)(-=A ;<  *,**               }}qllgecb_[Xdalgnirozw89OKFB@>;;990111< SJ+%92C;HBOIY: vpAgIDATM˱Ja ]A] _KK ^AScӧCs7.xCC|{?p[(_M-9o[ ~<7vEW7%= >xFVVR24`nq&`t0?_ QA=SED!AkjIENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/hi.png000066400000000000000000000020721370065651000241350ustar00rootroot00000000000000PNG  IHDR(-SgAMA ajPLTEӂ *ߖ}אwЈ)ц7Ђ*у ۑܠ.цu ke7_ SuAՇf\~ k"9c>-$ْ!%"%,ܓ$-)%,Վ* ,-"%Ά%", %$τ'(/-$&# %4 6+.(~ ݔ(7'664'π ߟ&)ٛ*ڋ'!7>D&Շ +)ِ2σ$2@*'-ޒtGp׋g KKj؏#| Cu Ԇ\Ux ޒЀeUf m n k f Y /.B6C:3.%?GB<65@D-COE>=IR<5PI:>ND48,+2#ޓ tRNS,X7_f/0 5'ݧr T!,ڀ7 X <`Aץ:a+ vpAgIDATWcdFUF[ , ny##_`v-F(8,i8 $m,p Hq';\`?\`e#rFH0ǘ,TX4HlȨ} H1C/123J2jC{1J1Cg0020N3v=9]H@jFmX'7r.IENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/icondef.xml000066400000000000000000000101201370065651000251510ustar00rootroot00000000000000 Telegram icon pack 1.1 Default Psi 1.4 iconset 2019-10-30 https://telegram.org/ Telegram LLC 😊 :-) :) smile.png 😉 ;-) ;) wink.png 😛 :-P :-p :P :p tongue.png 🤔 :thinking: thinking.png ☹️ :-( :( frow.png 😞 :-( :( unhappy.png 😢 :'-( :'( ;-( ;( cry.png 😘 :kiss: kiss.png ✌️ :victory: victory.png 🤘 :signofthehorns: signofthehorns.png 👈 :fingerleft: fingerleft.png 👉 :fingerright: fingerright.png 👆 :fingerup: fingerup.png 👇 :fingerdown: fingerdown.png 🖐️ :hi: hi.png 🔥 :fire: fire.png 😠 :-@ :@ angry.png 👍 (Y) (y) yes.png 👎 (N) (n) no.png 💔 :broken-heart: broken-heart.png ❤️ :heart: heart.png 🌹 flower.png 📞 :phone: phone.png :star: star.png 🎵 :music: music.png 🍺 :beer: beer.png :coffee: coffee.png 👮 :police: police.png ☀️ :sun: sun.png 🇧🇾 :belarus: country-by.png 🇷🇺 :russia: country-ru.png 🇺🇦 :ukraine: country-ua.png 🇰🇿 :kazakhstan: country-kz.png psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/joy.png000066400000000000000000000021061370065651000243340ustar00rootroot00000000000000PNG  IHDR(-SgAMA aPLTE'+0tw^XYjFQx|r9`~g?wu U_#{ ^Li ~.5R a hj g k a ۂވecCf?/܇2&'& u_ysV!&Xdxzf YW^i tyzuk `W8SYZ[ZUL Y\hkpuuqmha3<Ͱ7HKNOMIд8<7ߜ$yѭ,465564ձ-{ٚ%ȃmi()kkÃ̪O0ա#ޱ%ժ%((׬%ݯ&ؤ#.ˬTzXJ9̞1Π-Ο-̝18FxPŽӎ{_ƾźŻŻøĹ|ZAcy'^h6{O\ZwJd2]}$e@}\ QPPQ] ΀|ׁxooxكƝstRNS ,IK0 S_ Si -AG_Ha9LQǘQ&gt>`bC}Y vpAgIDATcd```deX%$aFNyFFO0w3#o0>l`,,L! PAѿ3>7}/[%s`d`kX)V׾{GYIENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/kiss.png000066400000000000000000000021261370065651000245060ustar00rootroot00000000000000PNG  IHDR(-SgAMA aPLTEHvd x m }\]c Wi|2BLm K l !Dk{Z3` ,kLfw"B#&u m r Z q al }^e jJZ t 6( Dd  Yj [i }kvYa l txxqg _[ HTYZ[XQc>V5RsvuqlgW;+=NNOQB3׷:A.+/3$0651ϯ,%۩)% %&T +&{߯$% $&ܰ$''Ҧ$զ$%$&(++)%(*+o'"+--.˒'t(8.218%߂ (+,pr! ˁdLڃz]rtRNS6MF%sܤ@.v p4/g'(l5[)/;^"S Kd\6LX vpAgIDATcd```bYXeaOFFFFg0w'#o.0[2Z0zu0g̌L;XIpfFaF5־pW0>c`*UIENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/music.png000066400000000000000000000017701370065651000246610ustar00rootroot00000000000000PNG  IHDR(-SgAMA a(PLTE*)/QRX/15%&*%%*$%*!"&$&+\^b""'$'+QSW"#(%'+QSW""(%'+QRY#,'(,#%%*PSX&/16:!)*),HOX/267@C*18EKP99:OV[CGM334flr6@I INW]gpV[bPSYLOREFI::=24;OPUNMQHHK@?A8573.1-))++//26-*,+*.'(-   %"+,/'',  %"&+$+3)0:.7A6AL'+/,.1'(-()/5>I=LW[ky(*.-/3'*.,-1()/-/4'*/,-1(*/./4'*/,-1()/./4(*/,-2.05/16'(-+,1$,6+3=/6?:@H9?D%,69=B248.7@W_ebio9>D=HQAIQ7?H3EJ>BE%%&ACE19AV\b136\di).3" "%-05HMRNW^-15"!$@ABKT^?GO@IPNX_6=D(.5+167?A "lpu8=B)+0 !#"F""tRNS Ft[avLvPwQѪV.xT2iwUgwUfwUfwUnxTHx} v0b ZGB\܇ħ5 vpAgMIDATcdFKF8e`ue- (˗/gcbbu*@`, v3|aeaa P" X`S/ cgЬde27ڢ ,,̷8\ >x$q13/)\ t\gg0/}IENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/neutral.png000066400000000000000000000021231370065651000252040ustar00rootroot00000000000000PNG  IHDR(-SgAMA aPLTE9hwwntZ [Z[vT[[V"NM{ zu;b~~c<sv !X]"ygQUf02Z ] i jg h c c ^ { {]S m m R_ z{ ` U e fWYe wxfYW_i r xxs j _X"GSYZ[YSF ]glqvvqlg^4CD*BQQB+EC5(.,N #77#M */("%ݷ$))ݸ$%"#&)(()))&$$&ߦ%ޫ&ޭ(߬(ߪ&ަ$%%knppolĄ(,//-)~$(<ytRNS -JJ. XZ  \_58QTRU9;gj!mn@aaA;! vpAgIDATӭ1AQ󟌛xyB*UHDlN+DPHT Dиd24򪴱@:`y7f=qc*9=&|q)!6`Nܥ,:CX0-XQ>[io0}GIENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/no.png000066400000000000000000000017211370065651000241510ustar00rootroot00000000000000PNG  IHDR(-SgAMA apPLTE߈oEݕ4{<X k g lp n r k i d bځk$13"'d ؊ۍb Tֈm f ֆߒo j }g c j n f @K.ߔ).PMUQ0Շ ,TTUTTSC(41JMPQRI-ڌ{ڐ"!:A000>BE<<<,,,?@C666>CFUZV111BFI000---//.555!!!---98:34679=79=>?C$668DDF79:57:PRS87:246;<>66789:da_222???222222555##$//0KKK111113BAD///555;=?:=??DI///BCC9AAHKVZVSTQ,,,112FFH9<@:=AABF),1-157:=:=@ILP---000DDE9;?./3(*.46936:69;@CD(('../ABD8:<:}=}2p *`3l #X"X*U? Km4kQ&HFHܜϒ͌BВ ߟʊEӓ ѐ<ғ"ݛՓXXӔޛ†"ʼnۘߜϏLjʍϐɈӅEZ|+Ho!=fBHRdP7%?d"An8V|8L_!;b>Ж) 2ڔ<<ϓ'3nˀ ԍٍ7;ѓ(؋66ڒ(ޖ*7ܔӄ 5=#";4d..V#$ޑt!9#K(YC֊)тv Ӄi y܏#{i . g v~yi\ ? <:0921C+9!.+4<# /1356'ӆ$%0E83ٍc V ~:@5A-ܑڑ56bFoJc ^;`(:tdw"$OO2;eZ vpAgIDATm; 1IA]W+mR,R @lV~|BNQ"q=) " VQ/G\ޚpBhgPPEQIx_RU-% 9)&:-1|zvFKMφ9sg'5t*_y|q5IENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/smile.png000066400000000000000000000021231370065651000246430ustar00rootroot00000000000000PNG  IHDR(-SgAMA aPLTE Kh wvmtXW [\YU\[ZQSz zs:cd=tv X]$yfQUf03Y ] h j h ic c ^ z |]Sl m S_ z{` Se e Q Ye xxfZX`i r wxs j `YOUZ[[ZVR ]gmruurmh^4DFʱ9DQPB˱8GC5(.1\66]0.)#(Ӭ ()Ӭ!'##%)(&'(($$%'(***)((%)֙%ܤ'*+(ӗ&(%Ç#zyƒ"'~#"V_ytRNS -JJ. XZ  \_58QTRU9;gj!moAbbB\^F vpAgIDATӭ1AQ󟌛xyB*UHDlN+DPHT Dиd24򪴱@:`y7f=qc*9=&|q)!6`Nܥ,:CX0-XQ>[io0}GIENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/star.png000066400000000000000000000017731370065651000245150ustar00rootroot00000000000000PNG  IHDR(-SgAMA aOPLTEQ|\ptphͮ_l5htx ܰNѤ=ԱG̟3+ڞ-(&1τǑ˜ ͉Ljʫ/Ϊ4ÂMDL[ghmilFс{FaaP]egsۉ܉zqk^GNߟ;ۭSiڏٶ\գCجIۺUyoլH͟:ήH?V[۳=ĕ0)0,#*ӛӟ گ=֋͎ǎʚ͚֖ߝ ΄҅Ј̍Ǖ#̤*͘!͌̆̉Lj͖̏"qǓ$ćzsߗ~ANUTL@'*)**% (^XetRNSYP%o\ @3]~ŜSE@%H<o"7?9^ e(W %O vpAgIDATM?+ 2H2H?/bwR\R罹.P:Ÿ$$ $}<$q$$C qsFl8v՜XUz9[2^asoBF&UuE MZq,mP;z:~:2[b!s[h)HaDIENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/sun.png000066400000000000000000000020741370065651000243440ustar00rootroot00000000000000PNG  IHDR(-SgAMA apPLTELMΰ-J`ҵ-V)*];?@7.,|ڑ TKUoDeiWҋ+ؑ)/.34f(ˇ@z$#ˀA[i24|KM;=NWށ$*+(WK>=3n$35ߘ#\,<>0*"7`ww_7(-x,TT*xC40280""174-"&!"/4&!xw}&_`ל7 p}r!ݘ-0) aryytc")/2.&*i$ހm"O%-3w6ɂC(2c!|`9CL8< vpAgIDATM!HC0,:aMuam`R`[Elm1mֱu`79|~.IDpI53XfVĮ YWW/_AC<ʪ0S28{#X;B Tح8Wp.ճҎ7@_@O햮iP$-.IENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/thinking.png000066400000000000000000000021101370065651000253410ustar00rootroot00000000000000PNG  IHDR(-SgAMA aPLTErq"OM\_z{'9b dY ZYVeOc .. a"#RS& )aq- o !q 37c c j j g f ab [ w y ] K i j M \ ut ] +xwb R*q a Wހf n k c \S J{[SRJKLQltr\T[ʳJQklнO;̶CR1=>])II($B<3&,/Ϩ%.12-*%$%('&()'%$$%&&'('&%%$''ب(ר(ئ''*)$*&+.//,+&H?494+$"Jd\=nل62$rd ttRNS)de*ލ .0vz>ATWNQ/2 MPL%Ϛ?Hv4+ ` vpAgIDATcd```dXaOFFFFW0w;# FFuF7MיXXњ10 *0 836-)c܍*VV&F|>#׌ bތ}Eu/LXc#ٓ~IENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/tongue.png000066400000000000000000000021051370065651000250330ustar00rootroot00000000000000PNG  IHDR(-SgAMA aPLTEsqJKZ _{ z-2b d[ ZYYWW"23c ./ b!QT& *lp- p!p 37db j j f g a c [ w y ] M i i M \ t u ] R bx xa RWar s a WU^S:i:iR^T"uCKaaIyAY]W^chkkgc^X2<@DEHHFC?=3&,)#1///"(+&%Q &%%$Q $%%Ѡ!)''(Ѡ!%%$*,++++,*$~bzɇ ͍"̋!Ƅ!vaݍ݈\ C#6??"6Cg׆Z0m66/l^jJZZJjOOZstRNS)de*ލ .0vz>ATWNQ/2 MP ~=?>>;o vpAgIDATӭͱ Aq| R,,E؜ ˰*)u܉ g !I ,2OoHk Ǒ;y` [$aao^ ܍6}{RBNgi*wa7J3fUΒ+n*,IENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/unhappy.png000066400000000000000000000021211370065651000252140ustar00rootroot00000000000000PNG  IHDR(-SgAMA aPLTE? o w vnsZ[Y[xT[[ULM{ zt:c~d>sv !X]!yfPTf 03Z ] h ig h c d ^ { | ^ Rm m S _ y{ ` Ve f WXe xxf YX_i s xxs j _XCSZ[[YTF]glquuqlg^5DFIMOPNJFB5).02454311.(#&''))&&&$!ƏkǛ((Ölʑ"ŇzН$+**+Κ$yɉ +,-..,,+ 'ܘ%eeݘ&&~q|{qdxtRNS -JJ. XZ  \_58QTRU9;gj!mn@aaAlhF vpAgIDATW! QEy\?8L0 *"hsb3݇IPdbLdOνtn@ȅ '0f8=i\rFlq)AGA`NVTR6c=XIRvԾxܫh> `tXIENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/victory.png000066400000000000000000000017261370065651000252410ustar00rootroot00000000000000PNG  IHDR(-SgAMA a@PLTE9Y;wG08Bڕ(8̓ ז`Ćېs!pU+Wj΄s҂ڕܒ֊Պ{ pf uZ sa `[[ 5A;8A4*C?ʋa9/ג:Aޞ"͂8A$w0B*Ӎ<;Փ0ڔ5{ ۏ(Ԏ!5.o5)->2)&?!ɀܑb҃,֏؎4ەӇ&)τrޔjԉ$ߒu k ҄ֆp\ Um |r` R F>=3>2.@001.DD;цЅ,5;=0ێЂݔ$'>C*s [ ] )D=7#Չ$<5?;3**)5<9+'*&1tRNS YU-J[0(@S7!c%+Ǥ IENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/wink.png000066400000000000000000000021231370065651000245020ustar00rootroot00000000000000PNG  IHDR(-SgAMA aPLTEDj wvltY [Z[vT\[V"NK z ys:cd=tu !X\#xfPUf 02Y ^ h j h i c c ] z |] S l m R_ y{ ` U e fWYe x xfYW`i s xxs i _X"FSY[[YSD ]͵MVrvuqlg^5-*EMOONIEC6'ǚ#344442"ș&*$&qɡ )'++)ҟ"##O ~('cs$%*٦$++ګ'ߪ)ޤ(%)---.-,*#ă ޚ(ܘ'ń!$~uR S {a9ytRNS -JJ. XZ  \_58QTRU9;gj!mn@aaA;! vpAgIDATӭ1AQ󟌛xyB*UHDlN+DPHT Dиd24򪴱@:`y7f=qc*9=&|q)!6`Nܥ,:CX0-XQ>[io0}GIENDB`psi-plus-snapshots-1.4.1456/iconsets/emoticons/default/yes.png000066400000000000000000000017001370065651000243320ustar00rootroot00000000000000PNG  IHDR(-SgAMA adPLTE%ݖ+%҈ّϏ!Ԇ ԇ Wnу$9>);097ܒ<2ޔ A*τ ߍ./Ϗ!ӓ%iߑ'D8!*43+#,,,.E5TJٍTT$QS'ߕHJ׈ >9#ܘ<&zԆՍt [ N]}؋ܐޔу)b L W X a ֆً~NRPOLQURTUK,+CNKJKLUUP6 .71UUUUTG&тޓ#+*TTTTP=#.:6MMKIC8'ێ҄ۑ!82*.13,*53ߒ$'ܐЀޓօݏ!" ttRNSZ+&TO$* >ݒvxMQK9?7TeV;*YL OA vpAgrIDATcdFF xd 0ƲPFYb*< y@01b>=icdtft`Dm@RN}*d]הTu/έ=9/W)IYh&X 3;p-L #?`LkdidWyyDuIENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Amazed.png000066400000000000000000000004241370065651000240560ustar00rootroot00000000000000PNG  IHDR(-SQPLTEXIIIeJx333)eeeRͬOފ-O̊/! 폠E NAA2,63k\'ǹxpZnw7NYs;D{cs]l^U󃰾0OV!IENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Amorous.png000066400000000000000000000014171370065651000243050ustar00rootroot00000000000000PNG  IHDRagAMA7IDAT8˥SMHTQɑŸFJm,PȤb*\DIY dC`X(ڄDPQQ.RG˴҆{޻v""ss9g kZ1gj_}[i|dOKndwt<-]$6h(ʤ('<ޔYj]"a s[,Ph2?ԧLcsa! COL46q]PÎFA`@A>4Ud;E}jnT~XbDd:qbx3"R$BP1IUJ_БL~b (tK 2SɠLB@<&NUPd(ʰ:= K_~ewYZ(D !UHU [vwsI2+NO~A YXVPqx#Y:!Yl[<-'䧦Q,F&`sNQO'G.-\[y#Od* `ZVϴ ^ C~{wrOɓU͖+4XSSAg}l0a['NМV ז#3x= _;}5\'d$;޻j IENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Angry.png000066400000000000000000000004221370065651000237330ustar00rootroot00000000000000PNG  IHDR(-SHPLTErhaP||h[폅X4jZQ蘭LHngL*뮪٭(tRNS@fxIDATWmW!ْrfa(>}S(X$Z?S*(=֮K ◲X::Y,,5R.#bfMU}^S]R1I?޴_FcIENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Ashamed.png000066400000000000000000000005171370065651000242220ustar00rootroot00000000000000PNG  IHDR(-SfPLTEۺəMŔ[רzm,Κm޳yD|ϛ^~UuƾѫC( tRNS@fIDATmUC1 m9y _yqV֦RZCM)RXݶ;CZ`[1+G$j؃x@23O W/d3аwdS &p8oX6UAK"n4tn槺^)C)$EIENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Bored.png000066400000000000000000000004221370065651000237060ustar00rootroot00000000000000PNG  IHDR(-S?PLTE]9~IENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Calm.png000066400000000000000000000005061370065651000235320ustar00rootroot00000000000000PNG  IHDR(-SfPLTEފ۪329xߝtՆACXx〺 ρ8RuAD`;7(7dՁZM/q( %рb]PUgkP*ד0 xmxaSoң &bە, .2(l\V+]$Y}pqˆ%XSp4P=$kǖoԶ?5* ?B"E"hÀ +; `R 2L!YbH3_6oVy O}O}dMƟ]_+}lrr~LIyv$IENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Confused.png000066400000000000000000000003731370065651000244260ustar00rootroot00000000000000PNG  IHDR(-S6PLTE۪3D<֍q8UUYΙ0]0@PD'mtRNS@fsIDATWU0 !8i ƝiYH}Y@UѬ|J)ݞo3\Ai74%V;FA^1@2Lqr9/VIENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Contemplative.png000066400000000000000000000013511370065651000254670ustar00rootroot00000000000000PNG  IHDRagAMA7IDAT8˕KSq}ћHoLL܋"솒!PRtR&$^22p31RiSy7񶔍m^v6n9~.ZZ;MIENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Contented.png000066400000000000000000000003701370065651000246000ustar00rootroot00000000000000PNG  IHDRR-PLTEq8]Ι0UX݃wv'U,PO~6?h}g UKyž?^?PsQԬ :DPƭ>hؒ<9VlZ;yӚ9gIk4yno@͂XP#㦵sy7Ui͌f}~skXca ߀U&%+V'ԼuӚy?6oҚ;t@8Sbe 8oOOA`fi/fȒs&N;k[l s,YmZװi~Ħy|%cƽ?IENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Curious.png000066400000000000000000000004551370065651000243120ustar00rootroot00000000000000PNG  IHDR(-SQPLTEiu]|϶銭ؾ电EsjھөK}40tRNS@fIDATmG0íjNC#1y`n.D{z~$tA괮jM\72 +`@ҌA AZYSd\w)8gWcǢշBo*/I$-gh?t]\yBդIENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Dejected.png000066400000000000000000000007551370065651000243730ustar00rootroot00000000000000PNG  IHDRagAMA7IDAT8c?%ΝO߿|{{"lp Ԙׯz ]qgja j 7apС@ A.`fJ?x/4|rr)^n@'>|[?m&:@`Ag$9 @h+k׮[ݰaC6Z@ AuuuٕwKJJNagcWbeedɒ͓CwAss\mmii)**JplTO4IH755@M`cq(E}{W/w%$$eddAl qG_|I&od%'Ol␓ecЈjy3[ZZ< SLAR: RG3ra6IENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Depressed.png000066400000000000000000000004241370065651000245730ustar00rootroot00000000000000PNG  IHDR(-S9PLTEܦ·xwweeef;tRNS@fIDATWmI! f LN$[!U`!NpB:Ѭu]>E2 :+jϭ67Xۈ^Bdfm( /aw"@.)R)ޅzJFzIENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Disappointed.png000066400000000000000000000004541370065651000253030ustar00rootroot00000000000000PNG  IHDR(-SWPLTE|]ክiĩu6^EsK}oxwwvL{瓦eee@tRNS@fIDATUOE1 3%.כּMҮNf}ˆG}fYP.05Z DrH.lzZ 1&xb!rmjkMi@>}!CDu&jiǠ{&"IENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Disgusted.png000066400000000000000000000004061370065651000246100ustar00rootroot00000000000000PNG  IHDR(-Sx+J rolHl>9YN{rJ7*3h|Uo<>IENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Distracted.png000066400000000000000000000011121370065651000247360ustar00rootroot00000000000000PNG  IHDR(-S PLTEŒÚ]mfw铣jx׿ս־m|͸ڠpѾަnj[j}_m`oapfs10tRNS3 4 [ZV1"x!=IDATMUP@Q-` ݝt8`TyaJ }$&l66/D46 9vB.'8֍L3oB#Q+qk!Jy=nK)m15+/'ta rle`' a(wAIENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Embarrassed.png000066400000000000000000000004051370065651000251040ustar00rootroot00000000000000PNG  IHDR(-S6PLTE]Ι0UPDq8@0n1U۪3mliu1̑ʫבfRUkށ nzWK_-HIENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Envious.png000066400000000000000000000014261370065651000243100ustar00rootroot00000000000000PNG  IHDRagAMA7IDAT8ˍ[HQgA|(ʊK{H%KABp:t7̵fR:Z ^fޚbk*=-"G[$jj۷Af/9T?K޴ze h[Vrg254RiMy Py R#tj>6 kqŒfdC ?+&6@`q\llo$d3 sXQB+AV{? k^)cE܊ڍJD1?-,݃= }e_R9 m%q,y؛x _GBEnO/V]r=EkffSQ'@DY'*'R!&y$4#A('bW қ(Hap5Bd$H4C'|_X~b(-4Eb{zbT0[N230͗0Ϊސ{|/ĵ!-Ь~7 B φ7!.iA/Iм89rf0 w\f0jZw-g"M^BF(TT7N< )J-*:m977^(v pgyT}1ķNDg>5t[EMtQeS9JDŽ0ҧL:  מkK3Gb;cO>{1IENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Excited.png000066400000000000000000000003661370065651000242470ustar00rootroot00000000000000PNG  IHDR(-SBPLTEBDngZQrhh[蘭폅ԍ/1|LH &UtRNS@fbIDATWG0 :*6 Ic/h 1Ę[kk?!BJم  , m$Q튅p-##0k yrr-lҍ1IENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Flirtatious.png000066400000000000000000000005301370065651000251600ustar00rootroot00000000000000PNG  IHDR(-SPLTEfItISnA˟6^m,^yD|(޳~UuoΚmѫרz؟ۺ۽Eseee+exww/]]vtRNS@fIDATWmW0i@CH= yH +rO3y~v}(͌)PPLZ}EK,VDt 6=]*1\);On*̫1oǗ!IENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Frustrated.png000066400000000000000000000003701370065651000250000ustar00rootroot00000000000000PNG  IHDRR-PLTEq8]Ι0} ?iTrP76(&%JLJnUws"Zؾ 91چyM}VӺb䎪EgO\Z,;[1ەWS<àe|q||s1}7 p*xp49 e @;ڧXas$̼Ǵ ܚHT'U*KSdwfj^0&c0VvFE t<^\x'25b \1ZJ>QJC~1>l-L[a">zC1X1"&삽w1LPw) :W1`4Tb2wLοV%` x0WG\D:IrSJwSh%ɴvq2_HvIENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Hot.png000066400000000000000000000004141370065651000234060ustar00rootroot00000000000000PNG  IHDR(-SBPLTEXexߩ2dSd<ފJEt)Ёf}tRNS@fxIDATmG1 IJrے`{э#H ^ NOLFN󾸙{{j7\)*f3%tGsWajIV#+Wܶ9 .1bue~ C}EIENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Humbled.png000066400000000000000000000004001370065651000242270ustar00rootroot00000000000000PNG  IHDR(-S9PLTE⊭ض_Ԕj+eK}I|6^Es15tRNS@fuIDATUI0 Sfha1)vsJU#rFD%;$<Ai?bIхY>:؍D|Ǚ/^ |]6 IBr@y{']5_3  ֛?iIENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/In_awe.png000066400000000000000000000003501370065651000240550ustar00rootroot00000000000000PNG  IHDRR-PLTEf333xwweeetIIIovBD2btRNS@fiIDAT[c`{( D3̜ ,EJJj F Qc  Z(# *2iggg+ja mo$IENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/In_love.png000066400000000000000000000003751370065651000242550ustar00rootroot00000000000000PNG  IHDRR0PLTEaPLHng폅蘭rh|h[0ڊtRNS@f{IDAT[c`AAFAATAcQ 9G22Ho ߖml Z^ll^nll dc#%" ,ˀB$%M5 $UPkXB3]re$IENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Indignant.png000066400000000000000000000004411370065651000245670ustar00rootroot00000000000000PNG  IHDR(-STPLTEIII333eeexwwC 0)@~URފXxӒՆߝI۪3q8yiD~tRNS@f{IDATWuG1 5dgKx(nzޖEoDޗ:uʴr3k!&[[}.TIENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Interested.png000066400000000000000000000005451370065651000247670ustar00rootroot00000000000000PNG  IHDR(-SrPLTEeE<9j~9MS0NIEsh[v|rhX{w@[ZQa,ߝYu]BD]P|.,u7tRNS@fIDATEW Q a\p/Iňofy>N^JFOLk8^Ŕ!׋s ꖱB9v`})J_B:YX1:|ފ9ɥ؅go"ή#M@ιcC)]iu5IENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Invincible.png000066400000000000000000000003741370065651000247430ustar00rootroot00000000000000PNG  IHDRR'PLTEX8Zn1a0S@9CDUk tRNS@fIDAT[cd`RRb``X^$ dh26o2Z 0024."apiA-cccS3] PI CqS``j Lqb``NsIb``P9(RsqSbH&%\QIENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Jealous.png000066400000000000000000000004351370065651000242610ustar00rootroot00000000000000PNG  IHDR(-SWPLTECD<ΐ>ފ(0/AteX7U۪3Ӓߚ$Ŕ[dߝΙ0&tRNS@ftIDATU0 }j.wNнJ9B\뜡hl܄IENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Lonely.png000066400000000000000000000004721370065651000241220ustar00rootroot00000000000000PNG  IHDR(-SiPLTEܸftv|Sʏ˟_MΚmnAu²X8ԊjtRNS@fIDATӕG@ @Q%J?$vVe29Q 3wv7C?u]kzi8WL 0 FH`p*wB#0$BÚRMOEDau: eaDM>+&6IENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Lucky.png000066400000000000000000000015441370065651000237500ustar00rootroot00000000000000PNG  IHDRagAMA7IDAT8UKh\e߹wNg2d&ilm(M-((U4V#n W\DDw"BQ L2ߓ}(;|͑\VPNH9I;r&%y')Oӎ>9={'of2K5HHHb|eGZ|}It幠]xF?d,`0# :IFC&V˯]!T5}r*hu^|DC~sP뽄W/OFޫis矈?̟ I@'M,A褑R4+HSqZ+;~ E&Ih V.8g`Q{ ꊓ+%@5iflv1y.IENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Nervous.png000066400000000000000000000004241370065651000243160ustar00rootroot00000000000000PNG  IHDR(-SEPLTEtmx]Pf[tnNHFz0ۍїףǧȐ%i"la{#9%!tRNS@f}IDATWI DQ $?j@R,{_zZKkuզ4@-GD1|6BL`NBi@kN R cim@7$Rx--?s QHRIENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Neutral.png000066400000000000000000000003701370065651000242670ustar00rootroot00000000000000PNG  IHDRR-PLTEq8]Ι0U<0PUD۪3@DY"tRNS@fyIDAT[c```dF`L@3uݩ8\ `tRQ `wRRyqt1vȐ.{e&Pݦ'78hphb PH j+v(:Ui~IENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Offended.png000066400000000000000000000003731370065651000243720ustar00rootroot00000000000000PNG  IHDRR0PLTEq8Ι0]۪3UD<0DPU@YdtRNS@fyIDAT[c```dF=,>02v;h)P1KAݥd2%@FRP@jiV LA7MˌӒ2 JZVP[CW!K}IENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Outraged.png000066400000000000000000000010451370065651000244270ustar00rootroot00000000000000PNG  IHDRagAMA7IDAT8O[KAV-TG* I1F ecZ Z)=7P<ٸi6ea} ' rc[0DGcɰzJ{#:J::J8\]c ?Ed`D0ܯPQ&E5|$ܙZ}:fpYK[qwP*xv'+bJE-*iJ U*>Ɯo[GIilG(l)ΐX KPw<)TI"ѵʤi3ܤw Q{2U1@Žqs?WiN(p\Fߩ6_m1ˣ ~dWe\ .TE@;IENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Relieved.png000066400000000000000000000003731370065651000244170ustar00rootroot00000000000000PNG  IHDR(-S3PLTEֱҦΖxwweeeybtRNS@fvIDATMI @5mbUBRp#!n c 2 U8jg|? &T4'.ExEDܝڢb[.wCڽs1׺qIENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Remorseful.png000066400000000000000000000003411370065651000247760ustar00rootroot00000000000000PNG  IHDRRPLTEFz0SAb,a$a,E8H,"JtRNS@ftIDAT[c```dFc0K,MT(Td(eT-ԔMA %5eW C (d)G006M5vq$B#Bm @IENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Restless.png000066400000000000000000000003751370065651000244660ustar00rootroot00000000000000PNG  IHDR(-S3PLTE]P9[s$)ib%[wJ#klj";}TM"XR.*  YTβ Zҫ3)8m%IENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Shy.png000066400000000000000000000004421370065651000234200ustar00rootroot00000000000000PNG  IHDR(-SNPLTEeeeIIIxww333_ñjڸiαӠߝ9vqF9tRNS@fIDATӍY AЮreENA(Z3Dx:@):ant۶_LC5`ujZ}jSЀFbb* ڿ@ 斲 ع!I*Cj)H=_?IENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Sick.png000066400000000000000000000004041370065651000235440ustar00rootroot00000000000000PNG  IHDR(-SHPLTEng.,ԍ/1LHBD뮪^tRNS@fjIDATuG E4b( q3ޡ#7ΕaFO5砦hXP,{2TBHGIg lV1vYIENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Sleepy.png000066400000000000000000000007301370065651000241160ustar00rootroot00000000000000PNG  IHDR(-SPLTE|t]iuƏ䊭K}Նӱßo۪36^xww禦Esxf<)t쳓ͬOΙ09t2ЁCdǧvߝJҶx BTtRNS@fIDATWUA Cna 3gNJ{#Uc%Zhb_ֻz& gc7 Qv6~dm_?jFIUbUmK3 b^>lhZ8V~FM-X ,yI"  K+$-a8z-5@9_NM]o 1Zk%e λIT%;4](h1)НLIENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Spontaneous.png000066400000000000000000000006421370065651000251750ustar00rootroot00000000000000PNG  IHDR7gAMA7YIDAT(mJqſUE=B^]Ѳ]2B# $jh[HY`7jLK_ Á#*l>4[*~=;~KMUCĞ #{&",ɓ A"e=%F x xq%w:5 aaqD̗mKG(RãL CV}I]|EsQp(%3mZ4BHO5\RE +1}n{T(Q$A6C OsC &4kx26C3DgVO?(MfiIENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Stressed.png000066400000000000000000000003651370065651000244550ustar00rootroot00000000000000PNG  IHDRR*PLTEX8n1Z9SCa0@DUhZ|atRNS@fyIDAT[c```dF`vc@bcbc#ω R9׍rYjR56/hBnbiy$CH1hjph|?+G!W`wq ttXOmd/ϒpK*M4Ǒ P&K#" YD߶SJsNq+uAg978"{ Wvc-7_#=]UIENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Thankful.png000066400000000000000000000011771370065651000244370ustar00rootroot00000000000000PNG  IHDRagAMA76IDAT8˅SkRQzO%衿`oQA{ F3 !5jAmY ԥp]gC&OܼgWݽz7QCO{7mB>s>^&~HV.7{ ЪÈkѣ?w=#<9&B"418[gs8>SE3ش׷-L~C4P(V㣌I2FWdɢ Zg(C BmQcr90q L+J /eVaf](L@IEM5PAKA^E$)(C^BM7hOi!/e_aR !:hOi!/e _'p}=LwcVC%~Z27=q589aps$8m$iݙ?_tapIENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Thirsty.png000066400000000000000000000004001370065651000243150ustar00rootroot00000000000000PNG  IHDR(-S3PLTEܱø򦦦oQtRNS@f{IDATmI@CQj ?m6D)=)W3"YdЕ wb\3}0'6(g:6`dmj$e@2èE-Ml:Wf:uv}][Dg_|(IENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Tired.png000066400000000000000000000006041370065651000237240ustar00rootroot00000000000000PNG  IHDR7gAMA7;IDAT(}=Ka/-4!hhV!'!h/m ZjDZZshH†@4׏}Eu: /e(E\czPfB\ih +t{ÉKu^VG9FRE#+3g<յXp@fi&L`1!B,mMN`p)(JBU$0v3r,j}B?gAP}aCbz {W!CvW$.Gƙf.mz[uFarU.S]P:e_ku;BIENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Undefined.png000066400000000000000000000005401370065651000245550ustar00rootroot00000000000000PNG  IHDRagAMA7IDAT8c?%a`l :{{z,b0ͯ?x/~~ ${B+ܧ`!^+?HNVZED~p~S/!$'=\߁r(6e _c7@~\3Fwf-c\馗g>t 7#B?.I#I{O}t$4w\0PL"dpX][kң{+E_ 2-o)?9)I GyiԭIENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Weak.png000066400000000000000000000006741370065651000235530ustar00rootroot00000000000000PNG  IHDRagAMA7sIDAT8c?>\XXX@46y" xl뚚6f >B,ds6fcog^jkݯl{4keaLk{khoo^T\cMM`b@z~Դ{w irS3/Lgeq+̻g +kx~ mK=ذ%\~jxC׭KUmxjڰmBT}Uء*FJk\^ ¶0>>3aNj@c@'|߳FYxl[K25훞[c6 FP媧*V= @BR7IENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/Worried.png000066400000000000000000000004011370065651000242630ustar00rootroot00000000000000PNG  IHDR(-S6PLTE]Ι0PDq8<0@۪3UYU9Dn1a0F9tRNS@fyIDATWU[ kvŀZa IP ̼mLHzHrːhLEH - S{7)O^y$kmˌɳ%:ޱ?ہ1DIENDB`psi-plus-snapshots-1.4.1456/iconsets/moods/default/icondef.xml000066400000000000000000000213741370065651000243070ustar00rootroot00000000000000 silk 1.2 Silk iconset 2010-11-16 Famfamfam (Base icons) maj Ky6uk mood/afraid Afraid.png mood/amazed Amazed.png mood/angry Angry.png mood/amorous Amorous.png mood/annoyed Annoyed.png mood/anxious Anxious.png mood/aroused Aroused.png mood/ashamed Ashamed.png mood/bored Bored.png mood/brave Brave.png mood/calm Calm.png mood/cautious Cautious.png mood/cold Cold.png mood/confident Confident.png mood/confused Confused.png mood/contemplative Contemplative.png mood/contented Contented.png mood/cranky Cranky.png mood/crazy Crazy.png mood/creative Creative.png mood/curious Curious.png mood/dejected Dejected.png mood/depressed Depressed.png mood/disappointed Disappointed.png mood/disgusted Disgusted.png mood/dismayed Dismayed.png mood/distracted Distracted.png mood/embarrassed Embarrassed.png mood/envious Envious.png mood/excited Excited.png mood/flirtatious Flirtatious.png mood/frustrated Frustrated.png mood/grumpy Grumpy.png mood/guilty Guilty.png mood/happy Happy.png mood/hopeful Hopeful.png mood/hot Hot.png mood/humbled Humbled.png mood/humiliated Humiliated.png mood/hungry Hungry.png mood/hurt Hurt.png mood/impressed Impressed.png mood/in_awe In_awe.png mood/in_love In_love.png mood/indignant Indignant.png mood/interested Interested.png mood/intoxicated Intoxicated.png mood/invincible Invincible.png mood/jealous Jealous.png mood/lonely Lonely.png mood/lucky Lucky.png mood/mean Mean.png mood/moody Moody.png mood/nervous Nervous.png mood/neutral Neutral.png mood/offended Offended.png mood/outraged Outraged.png mood/playful Playful.png mood/proud Proud.png mood/relaxed Relaxed.png mood/relieved Relieved.png mood/remorseful Remorseful.png mood/restless Restless.png mood/sad Sad.png mood/sarcastic Sarcastic.png mood/serious Serious.png mood/shocked Shocked.png mood/shy Shy.png mood/sick Sick.png mood/sleepy Sleepy.png mood/spontaneous Spontaneous.png mood/stressed Stressed.png mood/strong Strong.png mood/surprised Surprised.png mood/thankful Thankful.png mood/thirsty Thirsty.png mood/tired Tired.png mood/undefined Undefined.png mood/weak Weak.png mood/worried Worried.png psi-plus-snapshots-1.4.1456/iconsets/roster/000077500000000000000000000000001370065651000207205ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iconsets/roster/README000066400000000000000000000024501370065651000216010ustar00rootroot00000000000000Roster iconset README ~~~~~~~~~~~~~~~~~~~~~ For general information about creation of iconsets, see psi/libpsi/iconset/ICONSET-HOWTO. This file contains only required icon names and some details about them. Read a little note about animations in the README for the system iconsets. Note: how to add service iconsets: modify src/psiiconset.cpp and add new service RegExp, add the default iconset name to profiles.cpp, and add your iconset to common.cpp's category2icon() (and don't forget to add entry to lv_isServices in ui_options.ui). status/online - Online status/offline - Offline status/away - Away status/xa - eXtended Away status/dnd - Do Not Disturb status/invisible - Invisible status/chat - Free For Chat // special statuses status/ask - We are asking for authorization status/noauth - No authorization status/error - Error status (contact is unavailable due to some reason) // special roster icons psi/chat - Received a chat message psi/message - Received a message psi/headline - Received a headline message psi/system - Received a system event psi/connect - Connection to server in progress... // these icons make sense, only when used in default roster iconset psi/groupClosed - Closed group in roster psi/groupEmpty - Empty group in roster psi/groupOpen - Open group in roster psi-plus-snapshots-1.4.1456/iconsets/roster/crystal-gadu.jisp000066400000000000000000000262071370065651000242150ustar00rootroot00000000000000PK hz8 crystal-gadu/UT  H) Hux PK :qb3r//crystal-gadu/MakefileUT `hCWhCux include ../Makefile.crystal all: $(ALL_ICONS) PKPHMo}crystal-gadu/online.pngUT FFWXhCux U{lUO7ĥƏHtV nL`TbNXo6&di0@6 k4tDk|0f85tP7׽(`coѭ[֠ߒT) %n;Q*~IZYH 嘰 7w'q2q:OwK?3Woā5=n5 %{3K3{++߂>#t|-`Ee-ɳ5Ck9+HR{M$hUb0q&)}%^abRQp py kE.TwlxC 804:9ɇ1di w8%~KMK7'EdfIc;n+\3@Wt fQZ' @xs4U r:.t+ۥq0˲cccNS$,9xu%X ̌FX,6zUģyߏI s[K]ɽo,\Ŗ?ҧS]XjN.Ηqp!o9p1> >E' `D}hep,42u.(,E]0,S,AYޣ$|^kgZBQO{ںjb碤JkO{T}YgAy*ZwT.TP*{uu㺬Wސ}4Gjcf5hhІg2ӎ4nzbLJw3 G~wIJ?$.2ϵ\H>j{6>73Dz޸t8:qou̙'v.jr睺Td+}<4GMeu(-.ߩ2fot"FC~^gߴ/PK xb3az~~crystal-gadu/dnd.pngUT XhCXhCux PNG  IHDRagAMA|Q cHRMz%u0`:o_FbKGD pHYs  d_IDATxu_h[u?ۛ4i䦣&kYWeUòa) 23Ae/ {T1(8DC[,uKlIݟۥMrso!޽h=Vp<-GE&7KzG[z>-pxanAٗPuyeQ FD|~)?t!\j@u #MmSm{ҽTV5f, yDW (_bS6T`#ƊTʅz.6St>]eZ@T Կ=3}#GvO 7В P ;9*=}dc}]JT0l'xmTYy|>(=GD&&DimD6X>.COG9FFrO#1: p ] "Mm4<^u"QC ~3+/OC ]]kW"3L|U9m0M_وus׽W+oIvUU,/C,2 alPV_&֍DR]EBW.V-H$ .[W7W&bx=+O 00hB( Y~?E9R<\``KsL>'>զ=Ȥz6mL$:-yp=1o=D؈5IENDB`PK PH3crystal-gadu/away.pngUT FFWXhCux PNG  IHDR(-SgAMA|QPLTE: 2 ; 7/5ϭ%@8 PCP= ; ~E/@(d; ?rτP? _@8 D@=P8 ~%ѾɶHP8'D }?_+/X DMۂi-ݐ~!./(#R2Vַ+S xPKEʜ^ɭ󸨸L* %Gֶµٗd~VA&24)A# JT-L3^? _#̬閷îZ/Gbs,lbL\EXAIP7S=f*  ! *5tRNSP@@PP@@@P@PPB@X=|z_^?(@5 t~BG|=KIENDB`PKPH0crystal-gadu/offline.pngUT FFWXhCux  sb``p  $5tK#ƾ߁gQAOOO[[[ss L0gΜiӦLHsϲy~e(cцeWc&`;zr$CS+h聖eF7Rg]ϘT(R Voh)֜E&D}d>-)G`F :ZשtDFK}N) hU>e ?qaZpF K.ۍN۔!R[,?_3sJ Ho XPKdz8K s$crystal-gadu/icondef.xmlUT  H Hux n0Ư)NJMB+uE0T.Mr ^Ab;q(Tp8|߱,`Js)΅qE*3.]1黟T gah[`%j +t[]> u܃FXډSa'?ІV%@?F@c2%mo8h=~|QGxi&xxaÎw/yT! ]v:WŅ۹ ERz:1Ͼk+7E'|YBSKcVdKeѮ[E.i,kT.scV0Lu]0Ls`"X /ش@XR!T9!jMsUu9-x C"y/l픚@ aLMݠs~m._{q~ЍAqIg?#ۮ5$_27ݾ0lm6@ Nd'Q_ eԶ]/;aMy4-0RcG۰56yT$aGg"kR'hy.V\sJ|<*͙irIT,0T;scыjr׏G5QdJI ߮O=yPK xb3ʅcrystal-gadu/xa.pngUT XhCXhCux PNG  IHDRagAMA|Q cHRMz%u0`:o_FbKGD pHYs  d_(IDATxu_h[u?4MzmM뺬QcS )20EŗAA_|QPdঠCt:vMqi5Mw˽>ɝs{8=nb{nqJnV5P'H6'SX6X6S]" j&ʙ}WB~7͖cƐ@5IPMoz)]#6*H'cil JAk: *l]OF m'Q濹}~d7B (+V@^v2} d?n :w;y^׃Xmз7H+0 h:~pH| m OA=[C_PW/anM>щ<[(xDvd]t FcO%'|:t _нw$69>P@?m3z^u/;oM_sFIENDB`PK PH&^crystal-gadu/noauth.pngUT FFWXhCux PNG  IHDR(-SgAMA|Q PLTE &&&GGGMMMhhhuuu }}}xxxyyylll///^^^---+ iiiiiiQ0%m@fffMMMnnn***gbaJ6LLL:"ZZZA3\ s(P|CN ?4,'$MYYY'"/ nJDD$$K ;___]"xxx\"vvvb&}}}```~~~;NAݩ:|||`% GOH||wwwo[d/z;[!?wڨmmm~ExzB؄rȶSEL7ĵJ[!F: ! PMeeeK8[W88dddbbbaLŻzqw?[e1K3Lсk00ׯnaIR7cYY::zw!tDtRNS@P@@Ͽ@PP0rPP߿(kb'k220bpAgIDATx^5S{Qo&j۶]۶m۶c7n.bp`w3E5WTGC 'tkrzfP ]^5}l_ǷLѱ b@ۻ{XW/iаvMgWwOT՚2m1s KAo&T%B@rҚ~~'Y̓"U\R >(bC@k.IENDB`PK PH =crystal-gadu/ask.pngUT FFWXhCux PNG  IHDR(-SgAMA|QPLTE &&&---MMMGGG///yyyiiiuuu}}}xxxfff^^^% iiixZZlllMMMLLL$$nnnhhh ZZZ@@lUUU?''S22YYY?&&GFF...=&&Y }___xxx```ʒ}}}vvv|||\\KKddd++PPRRbbbҥFF))qqDD22www~~~qqyyXXmmm -- 00ll[[!!eee>>ww$$ĵff(( BBff44i22]]11,,<<||g::_\\2G5tRNS@P@PPϿ6@ɿP@ijMjڗϸ IDATx^5vDA30mFl۶m۶_ꛕV?bl୓\$l7iצ {,UY3boXY.8<*1:r z{\sj:J6[''&on_R| J+*U B!SuDB  j,i3s KeJa'g,>OTs ظĤ!5@r IENDB`PK xb3lcrystal-gadu/chatty.pngUT XhCXhCux PNG  IHDRagAMA|Q cHRMz%u0`:o_FbKGD pHYs  d_IDATxu;l[u:4n6+(uT * ^b bd'j(q%igz;:~S;&ğEd{qsrKqEϤ㙖ϽѴi{5k3CRFZ^~nMׄ^,űc.X㢷?P#涨 ~HʰIt,v0A:#T (ycZ6u 8%c)HSf~883RGkr9@ o[&BO_e`?`8每OH"mQ&oӦZȍ ?[65%{*^Ku?#\ GU վH3OĐpЎ-Q6LdSQ 舛ܹ k!GP",=X^aj9;|TG~Fv\yk{M $|,?EIOycxU^DRA$5mR(P>>`6gompGn5&IENDB`PK hz8 crystal-gadu/UT Hux PK :qb3r//Gcrystal-gadu/MakefileUT`hCux PKPHMo}crystal-gadu/online.pngUTFFWux PK xb3az~~crystal-gadu/dnd.pngUTXhCux PK PH3Qcrystal-gadu/away.pngUTFFWux PKPH0M crystal-gadu/offline.pngUTFFWux PK PHoO°sscrystal-gadu/invisible.pngUTFFWux PK6qb3Hd*Ahcrystal-gadu/icondef.xml.inUTWhCux PKdz8K s$crystal-gadu/icondef.xmlUT Hux PK xb3ʅcrystal-gadu/xa.pngUTXhCux PK PH&^crystal-gadu/noauth.pngUTFFWux PK PH =crystal-gadu/ask.pngUTFFWux PK xb3l#crystal-gadu/chatty.pngUTXhCux PK 'psi-plus-snapshots-1.4.1456/iconsets/roster/crystal-icq.jisp000066400000000000000000000262371370065651000240540ustar00rootroot00000000000000PK rz8 crystal-icq/UT  H/ Hux PK pb3r//crystal-icq/MakefileUT ^hCUhCux include ../Makefile.crystal all: $(ALL_ICONS) PK ob3eUcrystal-icq/online.pngUT LhCVhCux PNG  IHDRa pHYs  ~gAMA|Q cHRMz%u0`:o_FcIDATxbd&2J` > +?@1yd?P_ Q Llyp @C&273@&1(ppO4exvag 2 *& 00>p;o]b!AS!|7{ t?~.&k cp;? @3|z1;oyKa@'/FW @$>20]+D#Ë[@b`ЅhO^*pd@1L|#p43 O/30:13\e0`a`81˗~ 0"@1) I5la`Ps``0#`oA{ QW!a=r_E#߯ζ tko2) kv7>oG;@x~vI=e7`}İCAA3ñk< _0ct`o@0fVgcw6~0x p=:0d>l`Fṕ@1M2Qx?>1[2,``d@Ldo L_y>>``PQ0,Di: >  X) wnCq^`e4?@ t90 r 1ra*CC?9(00| -%_`BkvdXp 6@:u0 ?2- *Aa `4IENDB`PK xb3:o_XXcrystal-icq/dnd.pngUT VhCVhCux PNG  IHDRagAMA|Q cHRMz%u0`:o_FbKGD pHYs  ~IDATxuKh>&6)dۤmFj4{1=I#(^*hPѣ zEA5xCz(JڦUؐwyϮ|73Nq̉RڒW!mӞG>N2\n”+"VS[[G9`:qݩ/'4!d RK;`}t- Ru̎}7a zy=Q l^uͩ(˗E,My{: 5 z#jʡ0Z8:wxI 3}cCꝊt~]O~h薄~iԦ!qI!ڑl~ oH&G숅:yYRk刯 _N:ss稫T*榎04˺>1Ա(ď%W~g}z.2.#%XEXnb;޽2Z^q7 [YZN9JN/˽'O HRD4&!C3l%bNmj%\4-bׂ|nigU**+I&5sg?q)ۍbڢ<+o32x6b]MtW '| -2nV:N߶l IENDB`PK xb3icrystal-icq/away.pngUT VhCVhCux PNG  IHDRagAMA|Q cHRMz%u0`:o_FbKGD pHYs  ~IDATxu_LSNʥN3 4Y51F͐̐lOh|1*{ڋ.0,1S?UcVZ \io=O|'Gx]d? jk_a#P]?}zJgK$i^YpQ=}xO7E8 Uk0 9f?̢cgV UPKMz\~\JQ rQ<ҺЉGG2ILD2¿T4Ky8gҺz&*X5@ hf% W% Qx0AKƝ֖8G 9{ |3 ` ,/ń LeM,nneVǼR ytXkia ćᘒDvNQ9z%n4 i\q}YM^T<7dR}',gw8B֤l IqM2rW"B1:ϯQ3I]rHf DZj /h=CƸ PI`҅0t5߮Tn`*MdF}4ts/f5^ I6= V@)Pj;PD5ֶٿՃ)̃rbr9 zqx w4|`B{LHbxox˜^f,`G{& 5}Sb8VM;w,a7sэh/ t-)IENDB`PKPHN~crystal-icq/offline.pngUT FFWVhCux  sb``p  $5tK#ƾ߁|B\7'''wwwOOO HzxxXXX@  lSSSggg{{{;;;GGG fii T dP 񹹹)))EEE@s&$&&EFFWVVfeeU]]㓝 tUBBBjjjxx81aaaQQQUUU͓&M*--6mS@2eJyy9 @O0 t`P444yyy= $`C2::Z`Pq-0uK127o8? J?{`[@<]C*t&>H)Ͼm6i϶fYvNyo|.o㗪ev>]XC\>OX?>0L?vɇ)R>.}k"Afñ5Tp+JTyz|OWJE=^M=o9lWz@ֶ7'TXNy#μzv2W+^|hzfOE7ѧאMh `tsYPK xb3@Icrystal-icq/invisible.pngUT VhCVhCux PNG  IHDRagAMA|Q cHRMz%u0`:o_FbKGD pHYs  ~IDATxuOu?3,vAj b!&])1$4l(Ń'K#4!5B`*삛PCeŘ;};jDeTT1@޴$yx/8F֥g8bƬr#F7eCWX~m}9m/mt]yAdq+NENwsluuXN@9a XhZihs XmۨJTb||`&F-\*+[' nȲcEVWyN^UޑpG<$Խ#؄v vZFH u=cV_PyJ%6Ze&Xsb"Kkķ8,D*k]/A:jp)K`CB%OS Np{M"uy`)oePKoz8Yocrystal-icq/icondef.xmlUT  H Hux Un0}NV*Vj7M*MI&[Cсv!]LVpuY3*ͥ:g^̸wd~tvS)2VPatmFVqzvN_a1i48r]xݏJ>{Hw$p^x@5܍oJh1M]G0O\7lC]m. V*dysឝMZ[\*XV]'7fpbX>IH~J5CLz,0VЗXFO^ӥrQ|[| .\` a^<&2KQK!g 6- R[SXzqFSРӤ꘶`*Ir MTvKQp9K^b%s'QX 5;Dfw԰e؊m4*kDfg"kb'\6gy18ʨ|aE1{'i?tE 4 \xꢳ8o/Ɩi]auDO[ܘЩ ěhBe,p.DpR `H} 4yس@'ꡯ^ Q1M)%"$Iߑr{+AX}TzF]j:LBB  h:V)LO^h(D# NR!M!h6Ah| _XZ䀢(x8Xo/`~ L  k׊mkmoǮ'(JT=gj\opCk XaߙȚu{e;ֽF`ټE~b+)M hpБ"jnss_28N"2_>UT2IENDB`PK PHNcrystal-icq/noauth.pngUT FFWVhCux PNG  IHDR(-SgAMA|QPLTE###,,,333???999%%%&&&$$$a@6iG=,,,)))$$$G=;000 ###--- .#""""P Az'". ` ***$K [#pppmmm___YYYyyyOOOUUUQQQXXXSSS444<<<111ddd|||cccNNNRRRrrr\"]"{{{b&:::WQQN8ka`[!}jixJ9"onnK6v?[^*1J~D:("f[[bbb333D>=;(_+z;mhg}}}80(QD;;;="```EZZZnJH`%G$R@?N;qqqDDDyssI:J[!F: MMM c]]>>>UA;.J6 ''aVVFFF}DxCCC8x?YGG41QKJ~44999wURIIIaONPB[[[888**KK&&;23~~~LLLooo~72///5++JJJPPPB.XNLzzz}4tRNS@Pa 򿿀@ 0k2|t00b0ap> IDATx^-vQ=mF6b۶m۶'YI BoAp#οPٻ[]GZ]|G\"2R,ÞG%Maj3θ8;}~Q=Y81[n=šۻ;{G0;t~yxP< KJ Ua-o}Cc)!&4r&'?0 2) `tl|brJ;3K t,xaѰ]Y]['7B 4Lp(:֜D ܈ S8׷ԴL0SG!IENDB`PK PHWcrystal-icq/ask.pngUT FFWVhCux PNG  IHDR(-SgAMA|Q.PLTE999???,,,$$$333&&& ,,,BBB000JJJ)))%%%$$$~ ### y##"""A))?&&'%%***...=&&Y YYYmmmRRR___OOO111UUUooo444:::SSS<<>>$$''((""DDzaaadddbbb333```++PPRRXX ;;;qq --[[!! KK|66>>wwv\\\FFFgeeIIIZZZff(( E$$VVV999DDD[[[ff44QCCC000JJJMMMLLL|@@rVV~~~Hqqq}}},,llKh0tRNSߟP@Ϗ 0`﯀ ݿrj0Mj`pFIDATx^-VD3ٶm\l۶m۶mwͪ]Bz:3%/͌SYq{`OdJ?0a F,$03Jt%`?05y·ߗJv}TtLl\AaQqIipohlplkB<84<2:6>1I<:3;7Jp?8<=}~qyu}s@`P{BIk*h}IENDB`PK xb31Ycrystal-icq/chatty.pngUT VhCVhCux PNG  IHDRagAMA|Q cHRMz%u0`:o_FbKGD pHYs  ~IDATxu]lSuO{N9ڍvm`@€D8bd@cP Ą;مo5,&$`K6`uX;֭Z=_^T~W#rlXX INI4?"Oq9`k09oZ~O3G駇 Zn>un>#X  -EL\oﯠи,ձe1DlI]ׂ7"U"ԑP`x}0gpfSZ5cS%f@ҿVO5Nwy$AH5&\\i`.zDGM}ʻ_`k+dP\b2I.λa8t\Hem]$|Sr*ɹN{urzLLH8N+lZHm<% vص?P{{p 0cgQȠ aelnG)O1 nr@)jĂHXʙ6&fmFUi7F?@{73%CԷH?U00hT`3 AvG-DJ|{Taޏ/(p2}> Q2\,[*@5Y,yAA:z@!&geFxc#n%EI.yMG)j9ЯIENDB`PK rz8 crystal-icq/UT Hux PK pb3r//Fcrystal-icq/MakefileUT^hCux PK ob3eUcrystal-icq/online.pngUTLhCux PK xb3:o_XXcrystal-icq/dnd.pngUTVhCux PK xb3icrystal-icq/away.pngUTVhCux PKPHN~ crystal-icq/offline.pngUTFFWux PK xb3@Icrystal-icq/invisible.pngUTVhCux PKob3aO?crystal-icq/icondef.xml.inUTLhCux PKoz8Yo4crystal-icq/icondef.xmlUT Hux PK xb3Nzcrystal-icq/xa.pngUTVhCux PK PHNcrystal-icq/noauth.pngUTFFWux PK PHWcrystal-icq/ask.pngUTFFWux PK xb31Y#crystal-icq/chatty.pngUTVhCux PK 'psi-plus-snapshots-1.4.1456/iconsets/roster/crystal-roster.jisp000066400000000000000000001270711370065651000246140ustar00rootroot00000000000000PK z8crystal-roster/UT  HD Hux PK pb3$@FFcrystal-roster/file.in.pngUT hCzhCux PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxb?% X@ĒGqr }? _~g ×/~w Ow0\x @.XO?iˎɞG`>:z̟?oN+{ _a셯_1|:?>6V~c߿b7?116 s QTŘ!߽RĘ=*rǎ5C!Z @C!;++#3#';!^.W@O?  @; 4Xr`G(a= .]@@^ tf`|i ' Y_?>'v?A O80‚ hׯ ?;Û7؁3 LL@W`@~@Ac0?Pd 4ϟpb/^axSEׯO@ Z`F_??#Çׯ} @_7778l (Iׯ3 `2  IENDB`PKpb30\scrystal-roster/MakefileUT nhCxhCux K)MIUMNMIK.,.It ֶKKKW).rSӑ3K<.Ĝ+ Gxq\PK pb3=hAAcrystal-roster/headline.pngUT hC_hCux PNG  IHDRagAMA7IDATxMLuҏut+A6!"s2qaL+fQR"dbyxR$DGGGN`Oڻ‹ IRM캊EU !u IJJ ekM7=.=4ڇfC-'02.EFUTUs5x.\bP8:ՂG Zzl$YBQdTUQB{&n*ҙFUQX[75X4͂[VYQfj6^tQuS. 6R9fl_׮] fGؑC^mg|jO?or0>H`>Bnzg?gf!m,,__]܈ ~H84.$yVhu(&H4!Wm2UX*,#$XxߟһdÍNvp׃n-ͬ=lv`YڊDf~3V\".IENDB`PK pb3kcrystal-roster/online.pngUT hCxhCux PNG  IHDRabKGD pHYs  ~tIME#$釉JIDATxuOQPRZ" JQѨݹs!хl4cB/`E .nܘ+!1Q-?h X"P`0sQ`'9;{ )%daU6\z6GؾПyU/U|C 4!Xоc1{^ _yerPvJuoڷ@j=l:>͕΄8tbJ[q jZXXrs+9#dLRbLD讫QeEa &Cqų &:8EhIa&,J$$=P2}=I*WuzOu=Ơ3 ywu;[ ٽ_;7^&7#h5-=9PvZh4Eeh=vR'/467Իe%,}s5G##ʝ&On*/}ŀBx#Mar wjNRʋGnl^{͊N1sGASyʁ\y̺P=ھ )$>< kVJO4<|3+m-y1JO4W \@T3lPhT$ K/@j'7,hy$bYI[H0ԺZfNy)ϑ.͈9]ByLEw@;op@0soTdkm 6Y5kY]kUQSج ~u\|t/\_]rexp8V?K+9V +x妻򔂻yuRAsG؋Zh}x*w s$48%n~K| PBX.nN'+I8D&~m'BsrpvB|m鮝 oԽ^xrn[n=Sa9NNzC9*;;ney1QthFnMa?>-UYHfG 4J]2q4%d:NZ|7*۞l~&Tt)[eXIB?w8_?zry͔eDֶht_u^d'?4io}MZLn'T3ŠG5 H<>֗zU*_tXͮh(@o<ܷmAߣh *o7I0#w;ݼ Cns"v7&bI EI+,'rgyg"|Ll45'4*_ЛnC'k2E6eҙK 3B.wff?HK+rBgQÉ2S~7AƟT}WԭVD> Eۤ>RtHIV:8}4{|'dQS1wVS=ɡ̴"cz=yiD,n̛ ڏTʹ4G)ۤLJ7 ۑ_&b- O77g.QJ$TiZoU8#ӛe;-4#1Lrb. p7Y߿oڊ˝<=a "û\\Hm\DcѿOg8"Lg3BD{ ) ]d+@(?c)%iFgT P ,:8w*98w#Ʒ|Ĩ8ѧ(l'`13xR%^CE㵷kgЃS`(;H-wSn{@=O{=پ=MJa{X4L݇BV F2N/nɟ HR#tƉSBd?>[ 8 xG!FYߡI훡'JaHLJހdhy\wY! #6^1| Z3C=oP7Æ'^JN~~aa"2EƭF ~e2TG卞#|LYw٤Te1x %ewy n{I{=НE&/:tEQ/II01q57TC&"eX@^/*>0/YJK4ݩK3F_RWwacqDu  tHdbwjܨ,4T\|WxHIGd]Y`Tm(E&z"8r |@ mnW+Ajy4DMwvUQ_)MBI͡"H{t8>t轺"/9M ě3iWҕ甕GWѧbFRTK^yLt FҨk:oԢk/&VӋB劓`mfeuS{i?߿6 (0?G 54lDDWPkΨeφ'- x|(g$e{ߵ,|j#kIZ!$(l ^[~2k[v÷eZr{BoZh$ F 8.u(27{t<.zdwf1E "֬,D C VvbwdiNGF1/={崤: 62WcDiħ )VNЂ 5C+] O0[Gz: m++ˊ^c$fwufz9DGLJZ6Q.&ְؖHX~NRG拓ci|/f[wj=DCjw6-\o=/ _qjSά9^cgA8 k‚=ms1{99PiWooww-DO,ͩ 0Rb(г0a&EƱ9MMEsQ&'xFL+=Wui~z ?rݘ)8-V-FCF `BvL@Qܣ~ƃDL`-O(#*A}^DP {2MEA$sᕧJ26|tk݌kLQPjf`j!ܱwfXixABԤk`37s}Do(#! RgccUfI p~21W6.ؑ(C\_L[ M:g|D:doɄiTRD띂4ÒijZ7mZvg7˓{[7E{}H,3Xgb^ߥ¿B/4 M0$%8lHTS[S`>A'h <̔ffoiVU1(ڎK|~e_(zR[PVx F:Yrn421$Ag/5- ux|ۭ^1K@NL]dr^N"yѺѿW=0ZYapNT~*"W<Ǽ bθn ELΊ1 u*R K6ŭ Py'|+) d91f21TBB!|<]Hp \e|H>yZnE-(nAQ G p \]]h.q|_?µ d0iKe1ݸҨʗe`eja8.`ڟ<==-k6_4BkOj9Ų 9w|QoiZȸL{4LV3@>biQFw XZԚ]\.յ%^szw@RD5{ЀjcsFWġ"U(Z]룋_YܟQT% sS]Ȣ#cܨTNZ1XtXW㋩v)ml,Ό;$wHWnBB#5b9 y0//DdJՊ)ƘFnS8/wl]&=1BÞ>&ܥȷͣT;^+>^GX=Qx95=8زl,.1866[W:;kl,Cj:K^f =!͖lM t,*I\{rۧ3Zu#ٷYŷ TE^Jh+2' &K} FG)6G 7׶SͿ Ya dGGȰBAz#1wir>LGfKMJ{Yf fPam+PG&`7 5;͉3zXa*Kz1 ,8`25~'4PtTV̇.2F'(˩į7/MBCCf x~@]aڤ7S<7ZH SldK){_/4UC[mX1m*4vYb}8=s!#o ߊ lKܜ`e%E yP#}}{"`|nˠO;fT߮yM ۈTx$3$;'Ӧ6xo9O=Ll(;=LϬsknwZ@6nw56y#0_wFY*S"ag6suOqN)6(R>< {PKfbz.$e򣆤5QetKZo|5P,ح-TX>"ʩj%ilnW^7d ?#d~~WzT5{n@ϕWErۣq>p{{JCf;ߏ#G2E;kjk :RwIcfMxFuV 6C{=d W&Ed^Kspr/--aLY|VZ/[v8Zwg3J5WQoC'Z9ꠑeghEaު}MԤOl , ؚ//'aS_ocl_v(z0d[1GtH]Ŭ =Dz㓨@ѴI-λ[4H}Pi^N[}Fu\Z>Ayԟ?eO], ִ>=t>j6=&vc`ha-|=k|Iˍdqx7Ӝ~o~buI/E$8rqg?ᚕSi\p:2~z 9#>y@cc;Sȱ`hw#AMUppUeL\*SJ+>W#/T)aO-.GƿV3d ˊQnuCaⶸ(6)_AFgO1-O[h~倿Nq^>aqgm~P\4v=lR`e.~wuum,SLY~H>F%A.(+C;@#*?xFч.tjYF,rϒ].wGrDbPgZY}4O?ooOfGWh(xmLL+hdWFWF;~SX;UKOԿ x` Jv1W+p 1 ׉d^<"4I4xn_dg@ Ʌ*暪:T_PK Pzb3?}QJJcrystal-roster/away.pngUT xhCxhCux PNG  IHDRabKGDC pHYs  ~IDATxu[hwI51e[XgbȔވʪ"x!UF&lxdl6FuU)C0gIiksr%j}KT!cjWrVC~GQ'_HŬP拝oUP-H Α 2U/`N#kvww 9\ Ufz'7;ܸ'9[DbMlJ~G KC$Di?ŷ^`G;Y7ĦaC,LI4ԤL݋Y+X`?9vOOWrdJͨuhؠwFGx6;zڡP6O'ǡ;|^n0acUc&\-M\́KԬB-5'29yW:WPa;4SXmI0.:W 4w0ykJ?/FGX("D3ÊDŲښz&Et"(:H'*^DED:x<_uO A{8ER֧s굛tnw LC*-P(|`FZX S?r W⚟&mGMY~fkuzhdO{꾯)"{]2!ٚ.=*au NOӵ. FoZw7<!@fBIENDB`PKpb3[:;crystal-roster/system.pngUT hC_hCux  sb``p  $?OR B xn K#ƾ߁|d _*UfKRW V3'uc``Rq+̪ k^kYF|I709?fS7سӠ^nm9\LoycKMOر1`Vc~GFur 3.h3ٻ-lsA*6>&n1MʒthS ) B .4KHLHnD @d;O=P X4 lblԆ -[ 80螅_=#a},m0nRtia˲8ưB v9sN2UV\au34\cf8kq`ތ"_nx.˿z><^eQm=Ǧ ޯ4ޡ@7 'XhOW?uN MPK Pzb32jjcrystal-roster/offline.pngUT xhCxhCux PNG  IHDRabKGDC pHYs  ~ IDATx}ѱKA.p+؈ȩUY$ IT6Imce"I#T) 6&eD"z{;3;)^λ3jy~7Z *@`R<}p7?߄ǿOewaa va|uppnAJVZRewa2==Ԕh4V~uת*aEQ2BǙzmƴ֜9~y6VzV]^^zEQyH@kMfZoqa/pssC(>i2'R(>5oRjX$!M=8tv#oOdWa0ƘeZP"RTk1k-ZR8u !N,{zqqA$dYRtfffp=`nn}߭뜜jg}}=>>{WBxGGGq{{!ePggg{EkdE_4m?C& IENDB`PK PH {{crystal-roster/chat.in.pngUT FFW|hCux PNG  IHDRڭgAMA7DPLTEyowkxvkJ~DFExzGH>pF BSN>ZUHF>aZX@<67M(2 \&aNj| qd)`~Uji1')YTF_Iv\"8_2fT].tRNS`￟ߟpp``pP@Ͽ``  [<IDATx^UCAmkضms5~ w&!p|gfV"Π[鋄`åM;RaB=sAĤ KM#>t3BloeqioT#8R| drol{wL9{-~rptЁ9u|bNMkf qbBV1 Ayw5%iG,dRgNյQ Jz|zkjH59,'?dIENDB`PKpb3F= crystal-roster/icondef.xml.inUT hCxhCux MS0W'q 3 mg {*{Vd*}jN}>ȞEղ8[ T;6{qޜԢժus2%8NY}~gVGYPCpubՏ0UKY;m%! +K_Q. @iU/0jI!aY7P)Zк[W;^'Q,_>lc6Ts'IP/c0nQ Կ ՐMهS6FlXmaHVy`Psqm7J/OC"Mfb2[]Q֢|^;,gzh6cG}P0+m) ZW'/Ꝺ5BtBKW77r[IIdm@{ IS99zPKPH~8=crystal-roster/groupclose.pngUT FFW_hCux  sb``p  $5tGO:tLmm파 [lYlѣG]ݼyG9rϟ?_~ӧO^qƲG@3K@fֿ@fcHE\yOf7"ڳkw6TJkxӂe׹o:iʷ66ӣ5w[Q.לctg"|OW?uN MPKRzb3EF "G crystal-roster/chat.spinning.pngUT |hC|hCux U 4Tgd_ H}_e/2!e( Y.N0*ؒmleL1s}ys}<<7̀9ddgzJ7A Aug5Ӿ:..އ3pglˇ+ϹJ{8؝h[$knjxK4pg!>Jxob͎UHxv́+A.ENM~wt@ BDbhl>`{J)4wu#o})& kc3dR?0dIZ4@l8WBǾ)hBd },2ȵq27[/Bh0@y?a|6~ʭBiE'rtXұ4|#GWWTzsN-_ Ņ׃_gāMZKXy*yi_rR)f'+k틛'~:Azlυ`Hy8x%a8zۀmHXIjDo߉QE\^濈e|<7@Gv4tx,:qx=7[?G 0d1!{Ej'&lz$-זkI6zEo 4Z{[@)РEeW͟3_|1>8h+sGhx?+0ހl[YH,Ugk>xe\0f8<[Z?Jx;Yh9~^ Jg`QnW]yJF /xY_cK?{m`1%)I UAgj@O,EA$23V9ٹDMt$I%({- um`w-MmєǼa6 %dW]]=gdJY^ن})ps߹q_SKH< XC$cB<_,p9\*PdvrE Vc|VH|&vbΦDD:cPw=܇G̴mmo-)Xlʹ0@/*5+ [m4uj57.Eҁ[ +got=?LܜΉ @NΚ^6|Wb0׹R3x#1 sPWW_EG.!mL*,E"{n1*cc}Zu L {0TW<q>pFNtk;|-#>UFNJ% k Ms8tuO#뜸Ha6s`M:Q4L蒻U 8^4vұJgH=i6'vT[;˫W+ Lc;UhI]Ʃ;FNsz>ݱR'B5!vQpnק~DK{3aA @]|U_Ǟ++fyCy5?CN!-dY(Bu,^G"wQZ!!A15[#: =˫+"WTrJctEȣҸ!CCY)O$$}mBǃjܿ 5ΖueAl-n-1ȼy? /ү}H!tY+0>ƃY2~OZ-݆=N6$Zb/BN띋2,do u PBE3o(HQ[\a'76ަ͵k  9>沉e@q'Ey5 UPO<Na0'T:LTg uڭ50T3 Ǵ|k0h(R )Xo}l N@g!"GGS>\^Ka⼘Ij1fspY'5t93QgD*uI~Py>'+_`niBA,`;pjII/^ot vOsUVDtDԂNJqp g=LNJTbwJLU{YrwGd'VU.Bte >}Ym᫩J|VdU{_K-"$MfTD=%a꣐2+M7g|R(Y\ZҐ~YEi-XmdpF4žZ6o*X9 #t?xd/Ş*VVL͡S@}ks9K.h4#p  #U\9d5Mz{^sI'Fb r{#"R Cb(沕8RKA֑Yʑb-"2$'_$ 9Ȉ-Fk'`QWu6Rϔ&ݛindxމrqN2FYZ%G e/S4!'̚J$cIn_ӄsU1;| YЙOjtFEB) Jaoj8?F.Kܘ;IMm;ՉW'p㪒w%[PB}e'܀eV 4CdTl 7tb 塠b!aG3e: 4M1/jGz;]q3B-\#%Q6թ)KEߏN6IX>{wqΎv }BY[.6潨򡹦>#\&dzGO$[Eڏ`_`rmsķ&u{=U#3h7aO٭1#sjӾպ7!mDX֭Lw"|] ֞j}[ke-(Zd~xCf[2SҨ9+'6iV :aZRfR̜hr6C~:)72[vxez'YED#C!5@oMϦ=oDž#~}g í[#9@ٖRѭ/[zɪS2}".ÞYfgj_HtFwk9]^Ե6;:KթV\`xVX#q*Z7؊S(M(f *p: jJ>!BL,ԑ^sƤ9 297E'TԠnOB[Bk?? 4˃J6SƚNT]j[nZi^W/ÍK[cY/_Es4%FABtm>Ts^>܌~I}s4CY&mzPkTiA$n׻0\]6@4CJPɂKè-[bXʥר~Gaqj"E+YݳmF,aM֙UK!26v^TդP:J7ijW6Ai; mILruK\Jg A/dGsuSYVM50t+Qu[AhH҄ckr.D,ZgIA y|x#XvJY(McuCPKQzb3#3'(!crystal-roster/online.dimming.pngUT yhCyhCux UuXa7|4(iD$DzC I隀tw3$4H3Hw>c?qGjk*%f பK]aSPcZ]%Thcw*&^V??2D }l/FBkܱbO}0W30X顫E>G' .u󧺣1h&X1ڧŘgK }@=2Seig^-Gȑ#9:D!~sNyީv}VMүёh7tE99 u STS(jŅP$x>x#tL1w=>T gl,"ޤ\~ĘxjsVS--.U8|]|ܐ}Oh8a>Bg`[Ѻ2s\+^ǯ$AMyI`Br-p\ΡHHp[ϙ_)=fVj*]8+qiX;Qsr*:Pޞŷβ! I dj1ֈbliA5%55l"'BjssB;yd@'DmY~[;4491.q癃%=O1`m?gp$*J.lĜY-L4g 3Mrkx58$N (| wLY,##X Te6L Lb$pI ~Gn!2oތ𭦼S?#X¼X"c 0qa4|+ut]P[|W!GUԌv6kG2UM5{F8f ;˙ſro˳}dHA&¯l,+q{ÔveqS-^ZsDҖOr@?dw 'I{ˠWZ`HUpn.ꤶ6+dDG@AWw\<9IkJtBSBkI{4%{s'ts8>"כakCRWKͪRp) }gᇦ-䞭zA' Plrwu2{U=0=mNX~ֿۚȬ֘h I|4j.axyK˱2yGQ@DS ڿv+Go[*Ev3/7,z?%-?a,Dz{Po'W_%6ˋÿszRZM9. z]q| *<(|sj[Z3@j۽7pfggVk2DE?% k}v%1˩2+man$bOƑP`x5ȥLȘ*E(fw}*.K$+&hQ[k騣 vHdqRff|Ѡ&ugmfmo@LggBRF &^y<Ύq*NEZbZv΁Iwt>ˬ݄17_E*S>NlKx=r >b]3<;͞u$ 5loq=(]OEzՙ\>*׊C>--6syĊF8_RR:-4޵wt g sU,UO~:GGmG,&b]1[fsFM,6<;>{#P% >"|UReWSS#"L`"=2'Ļi2 {ڴw@wU9HKj52ۙ_c2<^UEȗ@r:'?B"qjǚR0G>p77~n/!sɻq~q]b)3M۸-rŏDk{|igr<,2߉fpӧL}IFHSM=Rѝr_GM?ʺCawP"oeC 7^dܸN [wC?DSUUUWWǍWSو<|}fk3)*R@2K͉^hAhm`|ŷkϰ >ӥ:JplHzI{^Uqj0\^6p $&A[" if?R/ЛiyV l7œSKU"ڡBcbb{! ۓrX i \h<#]W zi:2x )A޸p}ucUf`GT&)AB -91$OEh$ax{wC㋞+Se4Tiikݨ`˷W]7YSGih+[TM]KT|/f ()WWG ]t vfi\#aǞ#"x¾KD(sWlS\}i}˗@S@jXͣ6*Ѐ<7}MhNɣ?;o|@nc e˱zEtH\B6ã&!ݰN{GqЧ˱EEG+w)'"8.'JH@aMi׌51JCG ez~.k Y_r{nMI }F;r AP+,®cUէOn ^F,ax?~K1|E6L .|qZ)—R't>Tvhm%Ň!G7w*pD8DxK,>cà R+.V4>n[( <)AƂQB YӞBI§bpqqqeӶw):ؓq)*),Q;xTdBA\N1aɓKjA^uO䟽Wa8.FDtO*/4] 1p$~Og7\hth r[6טpA;bUt6u*ZA!*@,#$O!UI ӵ? ̠5|M#G_+Cτ d>7KLAݎTLzTέ*_M#GA:Ԑ5{!sA/# %x-ȧ#J*ΊN'&'w_eu٤NZȱ׃^4/08+Eu@.۫ŷ ւǏ(Aߊ7e m{# 4}ؘ> \kZ{0 Krq1C tκ=so4Oi@hdz(olMN1 zpy˒ްvtC&rl2AX! 8z|˽oAE~>$}@p`-b3#X@ʓxIHJ*nξM 'Dq'H2ę%077 B3//~PhT!8}b9)K`/_p1 RY ^A,amMrv~%IN˙ ||3-]"SopYiZu巋2^!`cg#$lz{IIAlX}Ɍ۠w)gt/.% lkNN(tZ,hF hZ\c{z.om] Xϡs c]ـvUy 4,׍i鮼((ff.@^on6g,iRq%sIEKS;TF0Ec&c7 :3 .2Ju-<ӧʛf& ]?kh{u V%žTH2;:K”_6{w9H1k5V;HkAd$&~Uv>;LdvB.Z/qr&"MR8o&3t7z̓⊊8{?SsD!:ѓ64VUI+#DAR B*]n{ ]]+HS- \ ʺ,Y:1j?\t YXDؙ{'OZgrGI80q 8{_:Ԙ3L!̗;.Q|0< }ui$:7Yܼ]$(1P&.dlHRrb7a;cikx±l{cXsmJ4C uK OXuqi6/?R7NkZh,/! Wtǎ;/E=h}'5bS4xL_D7#WTLk'U:<mNcT:.70-%-XTCuY@1ǃ~S)oN5'j‘,T0ŎPoD__q2]Vu$y0utؖ|k^ef\ꏾ'PetJAGwpA ^jQٌ(zyz`Gr %]y7כ*X3a:bLwpOi|Z{&wFL10.wN-Z[ +@&O  ?&F H;HN FmK-7BHH5.Jܠ~jڮ FIDuz %*]쎎L@V^_L+nݖmJ)2ѱA 5X}f<,3kPO&d'? :ͷ#Ug_3ryrꃈE C^\hy$kJd}@?|ZHXV߱@߭]pT\7Md6_Ҋ8~bٻM7{T1|z }xYi<\aMj$i[r̷c!G4ح\ƪg{#*/yeb ٷ@t!$Db? r}UgUw17p 7م?eo"﮴Rd̂ݒ+6Sw,/e/qۓ>=#6ofv<ѝb3 5 aX͜Uf6]8Dk }<|-?̂HHMn{CO/a`$+!cFGS wm]-̷-8UF;?J $nMoIVΏl.QVK^W(=U'P[:2RI.FG l6SI,wD[0t80H](8vj-(fsj"4-B\].NBC=cƕVK|lSqKK-"+Jf"^"L( t3B*oаEjm?gh5x߄ ucd.s%M 6!^p,蚳:`S7vRWK#u&t}%L`ְcw4kƁwJ=֔x+$SMooYPU(̎6މhnig~v%~;pxQΓ]9,p2RZ }k9O@:u5̩ݺɝPSJ 髡V^S4m,g|0ǖU)rl;u_毞Q _s~eCF e vǶ[sX 6y?R`=l6E-E9HrK^ RhB/h;4FIg^RZXD=g߰?8--.N}~>s}؟d2#}y`a ]HL{OI-vF^u I1=l5ST]&zfz9w2~Є^hΐ캎FR(工%Q}6+(*#",_ILJZ2tIv@i֪<2q=d2s(Fz68=mhg' ~74ăKCD߆9뜬pP LM;5'p!/gI*]>vYz)қ] T{&PLF+1zv@[\?A.o5݄w\,r[s.=G{%z似~zcda(]rpgr.x=gj*KJW秧.a.l[}جl7sOmE3PiՅ5W&ff>_Sћgp@P&`36,o.:),\R]hz&*e8noJP?Nc$q?v0fCuKZԫ.CY Iz[4q[s/͜]*`cWR\ ]xhNz#ɕP Y%3r%B*r#2Hp!Hm-d/ck䉼Rݏ\7YfQ*x-uƕ검?*q~ )wT=~tq#Gۨ tbBnXj*3!Ѭuͅz/ Dzӧ|N}m*G Iy>G2x_&ϥƻ9!DȺI_Iwҳ%Qx T2Oʖؖʮc0WY~pڅ3 |agou,>u#l}e;mzkyiy+ w SkY,Ev/䞗[sGe8zhS\pߩ^<0Ѳ?je߾*@s/'h4íI<5 N%~"o7=[M|nz{\7{L+Vse83PT//^!(/d"K 0M$ݱ BUE͗PKRzb336=X#crystal-roster/message.spinning.pngUT {hC{hCux my8TU"d+YBBbٲ3vB}d_,QHBu0ֱoAe,Q}}u9s]ǜs~UJMFFF6$#;|%c`?Pkp '*'pHFƌMd%9"EL$܎zqAgaRH+eq"oQ / Wg]_% ֡ܳG EfE)zÕeeܡq%q1Y0Y⧪޵?)sx~r\lіm5)Rgf߬@ѰUHO6m<`&W*D四'7_g>پdd-b`n`mݗo3z 9J0e>T3P~Vb;b)9W}&Vt-hlef v(yI#[IU7.fDOtޓ{H$#~K| PV3s99?/_|o rrke}ѺOtRMQYj A.#cKӳ >ik8%Bo5t^i؀'O"or;rԂcT#"43126K?/pn|nʳIL P%jjd1XΌlOMǚWOFJeUԾ  ]S&Tg!++O3 ?$ٓ.Y2 G}hOXߺ_R LH{)t*Ji aL$f^IH (N0_k.Xs}<+lp<!@;?z>=, &(  v-X:e'>_d4.{|P9 6J0z;Y:\n8zf@(Tj8q̟ԡ6xYt Rh|x# =[ܑf1bMk&~׹&*Jެ,jgULBQ;ATU;_fZ =g =y<4:㾇v$EzH%ԓ]I2#B's?+ pɳref.!i`ǧZY^1Vj+*dsw=*kwGG.,RuiVIŹtCPl]?' 0i8Ori[8!$aK-<)*L1~cRaQL|(~fkX%f$t[?%5)#:ǟhscbσajꠅA,ª2ˆl\,T}Q}JŜ77z] uLn}X}u5 bB"MB!NKt,!̹9ӞƃHSB v.V]d}к5c@n389Z" {rc%/ [ Wm`MLe'hv(*dSFX_g 8./]ttE}) qc#mea3ӀڧՕ(_:Fyn:Sq>8{Ux)S|Ϗunna :;{r@& YF곎~&jMqtyyІ(<;;[2Ȱɰ`42<<,)T-.+&FCy'ɝ&+ݚ t ʗ&r / >ܞA:[[B. "5赲:pZFo+-H C}q*To0Ga`H4+b6Kƶjn-ިSr_@!BL(&ʈsQ7 3g6Qm|mJKV_lc֢ؕtI9q+5?ArWps"k?RG/iPmLݹD2wǃLZE "&/@1s9Cg1si!$1Ft/K8~3t#ܷ% 8nK@ Ag~DBr ('t^8aWUëkcErx?Q(^1G>:v- nMec@…S)X_;>V5$ch>5O4OGDjDjDV+'6㨦0J1/Tj?Ԓ,+|Pc9*^o^ dY;1Gᐡ#g}]Qj<QMokNѦ&mQsZC_*׏q^j]3dTgi}'DB1Iu!a:- f-OzT_ر*LM$?}\0OU54>PyrX.l.%Kwup?hk(L =ММSjn^cg5``~9>WiaiyOUU>{ι&dM{N΋칃 >K_y` o埫4A5jWPք ^@~6sg4e+S7,-UDD8W[$$NH8&ɂua{a`Os:_O#ԓ_c,yIIuv ИʔfoA(-xЌ  =Ћ%MN( iv V'>P;M_- |&wXF9nn苎Ө䲪XniWȖ*.'z1 z=t층GKk)ڶ zƛ< p:|_Ү}۵3ʂ@ Fs jN ^xu/׎i1zolLp˻m" <}8O?8\hT {OP,H.*ϘH$H/? 4 oe xpdAG S%x6xoR^mxtvRGʭ_"^mR|._.bB-$AwO:.wO >W,+ WU^c@Ap~?.̔Q@VLiDRY[5d[ q⫁XXWGG7听1z䦡NRJT >~L\޳uD>/f==xbC0)\ДRm `V}77UJJJtsm;/*%v< ,jttiu/5A L6%|nuY61eۮ]ME^"_.7m_ :O,kz'x*@THYݑe*lۀ9|YO'`0%zK_؉<4#ϔHDQS:U4ep!w#%q+dglR==aH>kw(vÏ'I('CA{ڪUtC9`H:X౟i,m1ʻ~8!YOIjK퉉T{Gb雩ȡVw4q-=V&md40 AR₢ok9;Ù)f cu # pvn W[B;ywRd_WB[=ՓS!e "vB ÌYfכwhmk^ol.qWyNe7KR .Fʟ%Ԓǥ͗6 DUae , 1[ 7#mEV^䇗)lj>qYqs;ت5{} O Q3j3CFX k`ȾSܶFB6mN>̻f g 3^`hYPsMyV2P#J\_-̳Ö~񽾗_IKA7-obH6Wa'BZcgoTc%2gcn="$&S&&eM-JXҿC%.nS f&_9ΫaOSQi:@p -qэ/xCЗRŤH7H==w/@edr}[aVW8:yAr/㡸ɿ֦=~K(kjUUٛ-t'eYXy($%%e"e藎6A[p}w98ul~%*lwB[(m 2<1 V}fvD,p_;Qf؇ЎS;Q <ɷ? [Y躺r %Kҷ~E]8 Fض&I?ЄF]X[U Nzj39Bۍ5B_.&c9h^Bw|_ѣjZ:vޑ"VwOS'J޵ 22ٿ6Ϊh-I"O)TGULzb^cYV{tPH~ b.aAطQ'rGBkJ6k SS%C]Sɾ?KwneҚ~Q6${Ժ{r+Eϣߞv\61A Bݚv: sDYғIRO% <F{K'Vo%g䛸OrHl bE"!jʷIH%1Ưj{_PK Pzb3'~]]crystal-roster/xa.pngUT xhCxhCux PNG  IHDRabKGDC pHYs  ~IDATxeKL\ey]2PcC D[BjJ]h2Ѩ .uIc n j3Z*RH uJ20O{wuA)=-T xw!G vrp׻|7QiZk03wah=gVX'u90:7 [wμ£w8^zi0t6OmRcמ\])W}'6hJu:Eo"G>cj u:f 'o,K:f X9/,Ia1`fkE[FSeϞpk: @uv'?:[-+) LNX`n|z%ϟw9{*JR~&C_ˣ\z$Κ5yS^iL<\))J!E<5tcMdUx!pvtr9(J-ˍYxH,8d24 l6C4phTgӆ]] SKZbe*|@ 0 2(K N;Ej}}t/X VAw32 wP( >h,wzh5O#ቌa3l౱8k6UU!IRdrr24[LVm6ݤe>l\c>QhKQ0>>~9χ:E-J"tݛlT1HKE >$zRx<*`4*@PǯFwڂɊPB*KJ?`{aTӠf J r[#MCTRdY51SG5~YS|.gxo`hۢH~d2 Z1r37'V  ;ûpI92u]f ixk|iiPdj 0Ul޹Ã.{{{}2o~ig^}Gv ydxY{Ӷq lۦT*|y\xdd*b8{&J 666m\]Ey!~H' 80@oveqC!jGl ^}g##hw" ܺ}.j'OHy{Nh"pYbKuV t:v,xZ B ibϣ&!YRVaֶvww-X1:sx \JA<ί0] ,鴤^ņZV)$I$@6(,0$"IU8w.VOu_^ _-j8BIENDB`PK Pzb3;XXXcrystal-roster/chatty.pngUT xhCxhCux PNG  IHDRabKGDC pHYs  ~IDATxeOhuo~3ɮlK?ZJ [Qj؃xP4x"z*E!?DcM5M4566lx I|["v.{;Q͈jյO+|6p3>>zXToP(ܣpK^} rO?[* 'V0tLᣏӑH*#*Tm3q}Z"! H%F>`"6",%p] C"(j?`j/a͹MWr4x' F.g!˦Q7{Sb_SyPxQy"*!έI9m-3}ɗ?R(ByiVKe .k j>#?PPm{(s9ɳ,,YXڅsC>h…rŌLZ>] MquS0BsLZ_F:9 Q@LW#&4fL6a}b_j}lSN`f<xeӘvtݩz8hJ`)Du=Q嗑0oX.m@ÙYKwh$FOhLO+ysXkҵL]c%'V -1} nN@l-b:gw:g9g?uʗM4]7IENDB`PK pb3ޞcrystal-roster/perr.pngUT hC_hCux PNG  IHDRagAMA7IDATxuQHgc¨6Q?Li]/ ] "ل)^x]EB RJ@㲶ikP1&D{v!V||= G(x9>.u&MhL|_˪Ϸu3ܼ| opKzzD2x\2ǎ;C0Io"fcA!$29)27'21!w7D]1EEfgEә; pN$vM$SP~'DQqsϲO Rڱ7jk$À7oؚBS2Ƀw[[p@O!\_ϝJZl6MC3M%!u*?[t}gt^d>ɵnlmOښ4tӴ`IՑ#qbmwN Be%TWCE(j\_Bb/@Fik a;!z<,-,l^v[(A\WtfǼ--!Npa޻mcqq;*+!C{}QU_NSB!mn>ݾcnh e;^ >8/%/}Ve juUkwEm.g[)8pjkIb˗NucyRhdBb@!y*յ7 |5PVSafÙ34-$ϟir_lc/\P3221(P^0v޿pUb0m8}ϟRw߀à"Ơ# ?PXas V ԛ@,Om8p؆05ǏpGOt;;;b7VӖ8q+QS 1 q7=֭GӁeee_>b׌߹|7r--Uea80ќz߾}&,,f%'ONo}4p@ \x 4߿X q. s2:0֤򡜔$IENDB`PK z8crystal-roster/UT Hux PK pb3$@FFIcrystal-roster/file.in.pngUThCux PKpb30\scrystal-roster/MakefileUTnhCux PK pb3=hAAcrystal-roster/headline.pngUThCux PK pb3k&crystal-roster/online.pngUThCux PK Pzb3 H  6 crystal-roster/dnd.pngUTxhCux PKQzb3"l crystal-roster/file.spinning.pngUTzhCux PK Pzb3?}QJJ%crystal-roster/away.pngUTxhCux PKpb3[:;)crystal-roster/system.pngUThCux PK Pzb32jj,crystal-roster/offline.pngUTxhCux PK PH {{.crystal-roster/chat.in.pngUTFFWux PK PH{TII1crystal-roster/invisible.pngUTFFWux PKpb3F= C5crystal-roster/icondef.xml.inUThCux PKPH~8=b7crystal-roster/groupclose.pngUTFFWux PKRzb3EF "G 8crystal-roster/chat.spinning.pngUT|hCux PKz86@ $Kcrystal-roster/icondef.xmlUT Hux PKQzb3#3'(!nNcrystal-roster/online.dimming.pngUTyhCux PKRzb336=X#vcrystal-roster/message.spinning.pngUT{hCux PK Pzb3'~]]=crystal-roster/xa.pngUTxhCux PK Pzb3^*f^^crystal-roster/noauth.pngUTxhCux PKPHacrystal-roster/groupopen.pngUTFFWux PK Pzb3 GGӕcrystal-roster/ask.pngUTxhCux PK Pzb3;XXXjcrystal-roster/chatty.pngUTxhCux PK pb3ޞcrystal-roster/perr.pngUThCux PK pb3<~crystal-roster/message.in.pngUThCux PKe psi-plus-snapshots-1.4.1456/iconsets/roster/crystal-service.jisp000066400000000000000000000250201370065651000247250ustar00rootroot00000000000000PK z8crystal-service/UT  HL Hux PKpb3hPWcrystal-service/MakefileUT ߁hCVhCux K)MIUMNMIK.,.It U 旖(䧥dhe55srT4}|!b \PK PHWcrystal-service/online.pngUT FFWWhCux PNG  IHDR(-SgAMA|QPLTEtfͼhifj͗jihx،ܐgjkia\lhWx@nhxE{gzFGx_2NOo?A@1 ŷsf:A ɼɼuL&k:`3÷Ź;a4+~s}sƺdžǺfr ^U]el uuCvutyO1b#V]sv _P%I،svzhvv`d p_AmW:p4H{8ͱwutpj[w8[EZ\71݈j\{sĹaO#elu- xtVKX\G^Z#|sùlV( [[Uw mw2yf'}snV*v}tu쥏|f*Ļ_@fD ~mP̿2 c9 kssiN7tRNS@@ @Ͽ@߿pP P ߏ1aIDATx^ESo&m{gm۶m:xrXpbVByq)Y^PΫ!\wdt&:-G~ mkGj0BD]=s X|^~ᅑQ?):&%U ()-+N@&S$Tկccs@z!2ONqv~AE='S+E? >)iA+I:anCIENDB`PK xb3NPrrcrystal-service/dnd.pngUT WhCWhCux PNG  IHDRagAMA|Q cHRMz%u0`:o_FbKGD pHYs  IDATxڅ[hTWsΞ3qL(iJC|XO%T/Dj}/SZ|P*X)ELUFLbbN4o21E9Ӗ.X/{_kJƋWn6?tّd9˖_0JV{B(tUy/  7%0(%QlPYj fxt峪4X|4D0Y8 N$=|iRǶ&h9پP۾O,mB"2wR3Ѡg ? +5gp{ϰ&H9=N6Yyz9^`|Qqu{8K4F8r voP@Q@Jp:O掜 RcMշ";2}"VL/c:hmK0 `)9SVS7]+Nhs;B uu%'' |.;GSn|~-Bd`P]Vg,*cxYI[ڔ H7n}kR)(.dQl?6> Au eA.Ǡm rP&}Ϟ q]\"{Vr=(8Ւ9n= @JKy c'֎7/r_S:GQ33IENDB`PK xb3nlϡcrystal-service/away.pngUT VhCVhCux PNG  IHDRagAMA|Q cHRMz%u0`:o_FbKGD pHYs  IDATxڅ]LeA+8EBDra?,3tzQCbL?n2f[\u(T&$ 6v@?XiGi}@\q?vʼn,E`?݁/HoG6ǥ&Af`)$,xٓtoR,5UZx4iDБ%'GX3~Kixq;e@4_ R((Ginmt03֣A^=KlРPdwy2l[9^;G` u]f[|Lh\@l@[U@+E+Rʐ՟Ϣd-T WIh}߾9VΩ=6ƅ,%=bM f#Ggp&~@)#8IPo#+6H&.5>@?Lc+IENDB`PKpb3ȕGcrystal-service/offline.pngUT hCWhCux  sb``p  $?OR B xn K#ƾ߁|d _*UfKRW V3'uc`` tq tgs l*>44u Sdc:ȓB?1 `B~|y)|xyDKk$S ̾NNaF¬oaT(u3#P2s<2\c(++QSaxݟ ?l}}S,CWnF&j3pƛ`$|%)!DWΦ) +%||80L69sV\ۚ?M`,V8|vdu1 쯟 3>T`0۰ysgF&lF:&PK PH:>ƃcrystal-service/invisible.pngUT FFWWhCux PNG  IHDR(-SgAMA|QPLTErrrL[LLO$!MN530mKMOY/9%`n`j/wInq{yBBBHHHoulLLLZZZccc~s[[[̰ +UUU}sǺ`d[w8^^^xtxxxtuʍvuCiNtttt FFF===ͱ惃v}zhAmW:\\\إ###lllPPP<<<U]j\{sel uuĦsvp_#|s񏟐yf'}s㼼el|f*ssEEEiiiwwwfV@uuumPu-4 7ƺŕVV]sv 9aaawughd JLٓB#tRNSW׿@￿PPppOIDATx^=rлImFm۶m۶ͯl3<}KX9[/xttΒ]184{D*ӻzB_2+#Qj[wݼWYbOibEJ*ҘL&+;'ۧ%_\yzm~ Mhnimk';:=GFIffr<[X$y9WV, 3IiDޡp(5->Ҹea|ҚBcbaG7@?< TIENDB`PKpb3t jcrystal-service/icondef.xml.inUT hCVhCux N SpC8恲,SaOSpҷ5 >Hh;v%sX`[#߫4H AӸ-0;vs4 Vi%!5j0} B. {# o((&~j4MGљїąqhFc\0J\N cp+hG)Pqe^SP7-9wrHKL^H0Ab_b~c*wۯ܊!#cㆈqnl?pzB,"=4 zݱ^,fR/\@f`EW]AGZ;|3 I)%RҳkF>/~HXfOXg]omNojqv"OZ4a F̗bѾ[F*xO+.mc&RJzZT"7tHdxm@s{mb圿G:j5Jm8mmz︍ށVH?m[*pTjR)2 BۖzUʆz$*TBQcW$av,%O! VC@YXb:U eY7ƸTW9*ȟ~`jCJfq>;TS6m&eqqD66ߎP\BMiJa9'Rl+ąKܷ}X>7>]c6Z6akF3o=0"Q)K_N=}opO @0ovIENDB`PK xb3**crystal-service/noauth.pngUT WhCWhCux PNG  IHDRagAMA|Q cHRMz%u0`:o_FbKGD pHYs  ~IDATxڍ=LSQ{h X#b򟐘hԘct30f00qT)7"Ft VD%\R=Nw}zb`6%L2??O#@477LR(؊^/mcYP0Zo Bkzu-C8QXVJRJٸush%2g^kRh!u] SCd}R/^)|R{@i_A}`6R |EpS wt.qT]ugU7|]UX]]E)U(c_p.W{eWrZ$Llbы5zBxxçLF%ie\꧅e4Z30S!̜ĞM#29teudZ b|(KK''0v{B< j5LӼVП3$\I N0%:=Pj5r*ڢ16kuG+t!t(ZA8:j^D_\uu \B Bk.'&T(e\n!) ۛB˲"8UaÐH竪warMF"Q2./.䋔l^F*|iʊ4 i66~9>v8j +6_A˾IENDB`PK xb35crystal-service/chatty.pngUT WhCWhCux PNG  IHDRagAMA|Q cHRMz%u0`:o_FbKGD pHYs  IDATxڅKlTe<; 4|$V"ZV&F\14ư1QE5`01j|GL*6RNL̴۹3@nl6s399p'4pdֽw%~ev%/.'_%)e7 ݾc̷nSPf+zM7 -alu2#-3b?ZC!{厣/=鞧/ ~3m Ȇ"QCHK4Q)mM>/ya'jG^,VFi֋&M#?҄yq&=γu&sgO0Bx مÕE|5,3 C O3>JOtv3hmg~م" ^seݍ5?>3gk8tͧ ˍ,MIP!v:69LPUvC=R1248 (:# ]Jו -ƃcrystal-service/invisible.pngUTFFWux PKpb3t jcrystal-service/icondef.xml.inUThCux PKz8{T2crystal-service/icondef.xmlUT Hux PK xb3ǠBqZcrystal-service/xa.pngUTWhCux PK xb3**Zcrystal-service/noauth.pngUTWhCux PK xb3xcHcrystal-service/ask.pngUTWhCux PK xb35'!crystal-service/chatty.pngUTWhCux PK )%psi-plus-snapshots-1.4.1456/iconsets/roster/crystal-sms.jisp000066400000000000000000000253531370065651000241000ustar00rootroot00000000000000PK z8 crystal-sms/UT  HS Hux PK .qb3r//crystal-sms/MakefileUT GhC9 Hux include ../Makefile.crystal all: $(ALL_ICONS) PKPH7 *crystal-sms/online.pngUT FFW9 Hux  sb``p  $5tJIIIHCe $eHVAVEW(b)U:x)j(eUedu@ j@sMՀځdjU䀲@ 4X^E$-( Tfn NNQ(%mzʄ}3S;7U.++WxaumUۆS]Ӵ>˦u}SȆU5΅k&׶r*qi_ey÷{kqQys4-4#LZu8eJI_mXzlEmn*5CUO}m[EVϬ MR=;35ЭeɑjFv69xWv`K-c5էrDLGXNjg׿Inxh)65҉KIӳ;jy=esq%S~W6tq s=~%[r?o3ܶH.zW0Uji|[]y-m'{yA=O-2jyIO?d 3/L}}9,ǣyGs[V1y 彛ҲCs osxS6+>xXлF L75ܘDT 9Vszg=9͟D_}lËftsYPK xb3UYYcrystal-sms/dnd.pngUT XhC9 Hux PNG  IHDRabKGDC pHYs  ~IDATxuMlTu޼lg:LuLI'`BԸae Ƙ& 1nЍ1H4j Q 1.C> a03ň"ړ9\{ip69׻WHH!DRQuRJջXt}/M<71L!|6JuDNJCh﫡:j攸CBο-H˕KXk4n4T-#3Sn]\tvG -l]az! \X_T(Rʮjv]hoLofq4XfudN#.3<Q ~f$Z˭#lxO>GyS@׻{(lܫZ'MI!]Z\tNT0G`:H+H@4B}?~-?o49g3G6zT*ܲ/֊OB{JMzmIENDB`PK xb3V4?crystal-sms/away.pngUT XhC9 Hux PNG  IHDRabKGDC pHYs  ~+IDATxuKhu?g^cSiVJcE F-/EDDhEr>`/Ӂ@U{@)Biؘ{?P}4cYQˁa~ ˶qlRJƢWeIcr#& R߹}q*WQ]Q 0ᖐP kTR7o~[LRY+zeZ_Oa-E 4t5iJY3?=ut eAщRQ)GS]?K-z.vZ7*sz`_o !]$XnbG$|1fij=~5@%[lhD 'x2xC c1q ҍ b~(,% !RJ&Ҷs/YXJ%k,J. S{ⅎ}>| iܒ)xdunpѳ1۶'n=^0hv 45^ƈǛ8qKz^Y'.V7ݑ,]u^;D0`fvby IOOͰ{1 AnĢKVuf|~Wv;}};1SH<2˦yr9Rwݶ~5tnLv yʣ8L w~+C!Dlu5UԤiI ^V=9S?}h[=IENDB`PKPH1crystal-sms/offline.pngUT FFW9 Hux  sb``p  $5tgJIIIKKJ-rrr`r ʔZ@9D(  (***d!*"glyyy2777^f]hѢٳg{yyM:uɒ%ӧO?'g͚=a„sN<9"""%%ȑ#3f ߲eKkkK,--ݻhoo tĉ]yܹ={߿$&&fӦMMMM>s挍YBB¶mΞ=|rW[j̀P˓UeRnns:mR*Ӈ߽Iw;jy=esq%S~W6n1"ε7x~M&?3wi}eA[^-}\\lw;͌jOGgT$֝V?uC {?^\Ȝ% EY /:vݍm9۠*_W;s7E;r]2<>ٶ:-~^|?1 9r-*ʲ_ڿ)=}cyR9N'Pd$lYܟ[ʓ|ugҬuv]a%$~gtsYPK xb3 kkcrystal-sms/invisible.pngUT XhC9 Hux PNG  IHDRabKGDC pHYs  ~ IDATxuAHdu?{ofVf芚!Y( уHٽz3m-ڂnEFP` F.XVy:s޼w-{>g𯘦y1vNBjW:h!D4SZkOHxʵoz+\[u ~K?;gT>UT߉ǰ"zޟ"g\ϣq:2.SR/BQVy ѐx得v.fIbM.4F$BZGZ H};8"$177ǡ:@F4R )Oūhhՙű1p]00Mffggi*?C" R/TTw:=-([[['qRteYb10CQ^@>㰷G*bkk 0eii}۩S(EŸ'߫@V* peRcrystal-sms/icondef.xml.inUT ;hC9 Hux AO0)ꩺdTo9M]ڂP޺_e|ک:itA&L-N15l+#ٞ ]-Wwkc {f! r9 ,{=d X4`uX{1 p$ABݻ:MhW`eDэY3f'J*(Tb [ZFj5g ,f_!lDlF|SN;18u*R&y gp _0ԫ*yjr'78]oOƦ-R['PKz8$^ocrystal-sms/icondef.xmlUT  H9 Hux U]o0}N~T mUڒ)SCсv~~.&i+Ux_s8,`Js)΅qE*3.]1黟T gah][`%j +t2nfSh@LDMN\zxt6/ ķ;G街DwP ]ܧ[I<M)Imv ;U Rt܋ sI aKK,>>Qg O)Y{HXI/F n%t\lƖ$ 09uB؃a&ρRTelx’]AUj34wr _ӂ0)eked>),?05A5K^4(4i::X#J%qņemz:UR\`o,FN풗ux/ kG1B]*5hf& s5oȚ r4=+9%IdԜ ,HuwO>3nrm*{>25݈aTb{;<=ܵ'ȆDWF&NE;oz&~5ќ ݜOT,+,ܸSq4iNd֜cFx"3l?Fa~qR̮R\`w:#G|22bHh}'wMgGme9ܻlS\CN<#/g!B!ŰZO~~sX(~=OL&9}&+k~-5ԝOdg3)TU T?DZ!WgBEGR] К>X)(mavCxg+4A-.V?'Ix$>sGsB㉺kAXQd2P(Hux^LӤjfGIg_~AiF L&jEQDrCR MӰ,znY֟R"SΎ}25g cR)j5urkhFFӴ%$z<&?LoGp_eM0 rh4[4.8Sb ԅ>ǚ\>nPȶ%ug]IE$Kf?]eo{i7obR[4>nϣ(ʳ'8LL P縴:f,j躾iڕ _:4Z2f[az&X(~Oq>)=~IENDB`PK xb3Ghyycrystal-sms/ask.pngUT XhC9 Hux PNG  IHDRabKGDC pHYs  ~IDATxuOheoٝm4BvmA,TL<$ "xRJ)rQDOj-IZ `J)M6+lٙ|R>~9t]d2@wVu@!⚦u) ʑHd"833sqjjj8L !ƁN! lˁfx* !0X[[q6jzm۪WDQJ۟OXlzdd>:Td2)t]4MR^/3xejjWڸW'{z.T 6 ʝ;J%:VKY ċR VVcpH:;?\X3e;jqxx8?='N)˩ T|^{J ;)ؘ+K)'BipxIǽykb'''nk_@Y.Ӿ{\&i4XmHiB$0B!K/Z^'&+ f3gOlD"98H(hmZ xGP(Lu8KRx4vJ(y98aIENDB`PK xb3Q:||crystal-sms/chatty.pngUT XhC9 Hux PNG  IHDRabKGDC pHYs  ~IDATxu]h[u4iMWۤuLzXB/Tc z=As8‹(R ߭Mt[׏#I{rr:٦wޟ䖨Z2'=q]a\ "!DRJ_E6~pg^=cEcX9"l]*!C9j֔ !Ro}|!RSHtPb ;W[DQWyLd2*Ci81>QcvujUkmE Tm?j֤ ŵbjt~!)W^aHXp+< :z O~ܱ7q#ò+dPʈ`7$K#E`[[;>qdgh@.5Ez?( T~^$hnnZ{`zߡbL|Ey gKTk 8/{TskQs&}>$rvLy!S$0֭m4NB)tSp$E*8-wè'oZ(66*^fcJPmh-u+KA,$XZ濛]:x[Nzo( O%'#sssV. RKI]qF,% pesfDK8 g_ ZݝyH튅]SgݗVONB/C^i&a',hQ(~pIENDB`PK z8 crystal-sms/UT Hux PK .qb3r//Fcrystal-sms/MakefileUTGhCux PKPH7 *crystal-sms/online.pngUTFFWux PK xb3UYYcrystal-sms/dnd.pngUTXhCux PK xb3V4?crystal-sms/away.pngUTXhCux PKPH1 crystal-sms/offline.pngUTFFWux PK xb3 kkcrystal-sms/invisible.pngUTXhCux PK(qb3O[>crystal-sms/icondef.xml.inUT;hCux PKz8$^o.crystal-sms/icondef.xmlUT Hux PK xb3Ncrystal-sms/xa.pngUTXhCux PK xb3n3crystal-sms/noauth.pngUTXhCux PK xb3Ghyycrystal-sms/ask.pngUTXhCux PK xb3Q:||l"crystal-sms/chatty.pngUTXhCux PK 8&psi-plus-snapshots-1.4.1456/iconsets/roster/crystal-yahoo.jisp000066400000000000000000000230441370065651000244100ustar00rootroot00000000000000PK z8crystal-yahoo/UT  HY Hux PK qb3r//crystal-yahoo/MakefileUT hCWhCux include ../Makefile.crystal all: $(ALL_ICONS) PK qb3fcrystal-yahoo/online.pngUT hCWhCux PNG  IHDRaIDATxڭOHqǿZ?/jØZ=`ZS;4<G $œAAK Ɉ蠉Dprwn﷋mx>"dYUȼkPӪϚE()bS;@i氅`B{6n띾#8f^rDgSY%aG5H`n2!4Uˎʼۿ":6aIE? '$:!Maζ݈߮!C,Wa6,a>^Fw{?=M]cu[:',KW"|z|[w !$s3 _ /3gz}6S Ct;RB~U-Wi`iZS翎7H]'Zm2-IENDB`PK xb35crystal-yahoo/dnd.pngUT WhCWhCux PNG  IHDRabKGD pHYsHHFk>IDATx}R]Ha~oNs[fN& C "2 *+( EaEHH*"hM 6oa<=o %F%Ȕf雱J땜[9e<$ԇ2CWgq'6:d>?Pv1=aiaK\yy.ݾPGKJ:rkNn<6Y=y>!CL8FW,ʆq}ƈt:">{ M4e38~uE0< D4PUGڙP8?7Rd" AT-bZ7[k@s3P,j 5i7/, :5Uj`6vT|=  (@:eJäZ03H(%MGeL䰸j}PE 8~ݎENN.&`4:!}zK?~më:;唑Z"]rlvOc0Ľ B; K4TIENDB`PK QH crystal-yahoo/away.pngUT FWWhCux PNG  IHDR(-SPLTE~E/X!t 4 3' 3 5(:u(!) %: *38 0&i7?;W |LEQ P#)/) KCU VKcQ/õqV{"'5 * z %_+/n  +0M-ɭ/+5,ֶ¶̬ìbL\EXAIP10*.JîZ/(G.Xv#3ZD#T-L3+[ַ=f*  /,# {DtRNSXrO3.Oď!Z8ξa@߯pm Br0@5*#`0@IDATx^Mcs`лfmֶmvmnd\$ 53ڔW0TV#yMfy~;8꫺ۻ9>o 55C mkK`:~/z=}?Q%$#>X_#w?ɩi03t&\Oq,V,UR(ʹIENDB`PKPHy۹crystal-yahoo/offline.pngUT GFWWhCux  sb``p  $5tԍW RpvCCC\\LNNټy3P288x֬YӦMŋ  77-++jlnnwss8qbKKKWW TS[[ I&Bܓ^ZZޞtP+//TUUUWWtO~+ f(TX l|yz^D_[gLJtR|Q֒{ IM.!qSO r``9pPiݻO4mۃvfw&1&p^c_8ߙ9,Yu _iR0}w5¶KzdnޞUaɱ-s}8[dXMy{u ]K*xi,5 ~.PK QH%]#crystal-yahoo/invisible.pngUT FWWhCux PNG  IHDR(-SPLTEX! }V[ % 0' )))73*8 3 |000in 3 5! )&*%%kkkfIGlOM4   LLLZZZ0䰰UUU/===퐐,aaa/xxx+[[[EEEiiiwww,ĝuuu㼼b'1ʋ3\\\إ###lllPPP<<<^^^ttt*FFF@4tRNSX@`D.Oa83ZO!*#@0䞞rpp^jIDATx^5zAпqն͙Ulm|~͞˃ I-d9 !ӷU3-F(D47 Ԁ%9F |zu$bw`lm쑓B 7 }~>>X.֩=L.QP3׾ʪ:#cLMBe-<2WIENDB`PKqb3+h@crystal-yahoo/icondef.xml.inUT hCWhCux N0)d6'Vt`tkkۑ9Ǩ*S.ɧiR2~{:-\VFװ*# | G{p^4Kl_ kp#Cda),JƠsg6KO毽8s)jPiAv`*Ir/6 n5 tߩC ԗm1rfmľK@ 'lPX ;Dw4e؊m4jkFfg"kc'\IDATx}]HQ33:ΚknPCADEEwDtDF zDE]d?Eal?sBʬp9ҁC#Ȏ?"pܕ^s@#.5>I<86:[<$W<# D)?-(i邧kuAi\=pM㹟w(u?HM#$ğ jIK8GAr R~ e.omo}~-lm E̽qrj*{lorQtB{ח6RB<{/9s8o}̽yeP>h4 Bfj),"TEC40 ԅ* ~?c3fJ!HEHgm\ё[OcӚ B`6L,)+ճɜ/1A( v&m$2ճlbQ)4M|e\gqԫ=ߴ!w rAPu :۷޳b]mbb́} ux6 DQTv8$Og_ٹDa=̽>IENDB`PK QH9$LCCCcrystal-yahoo/noauth.pngUT FWWhCux PNG  IHDR(-SPLTE+++WWWWUUU[&N gggvvvC==sssHHHZZZtttkkk<<fff~73 jt< |||I:4mqqqxxx???iD cH4**#>77/iii '". ` #m=$K I#]"\"b&}}}~~~wwwK[!9H?OB6`%?<x{{KKyyyz;nnebG/"rrsss:[!F: A>[qitRNSXbrbO.OZ30I2|8a!G{BM{@*#2|0vۍ0bb0pZIDATx^5cwAF$ݰU۶mۜجy>sQ  2fՠND0HP4kIHLfuo8=0HJe塊JU5xCSs \[{Ggؗ'y߇G'=#X ?: vLM~}cfv;,-SۻMwvãc-DIENDB`PK xb3_EOOcrystal-yahoo/ask.pngUT WhCWhCux PNG  IHDRabKGDC pHYsHHFk>IDATx}MLiƟwRZ #̔=AӆDcqopa1 kB51nbdوӔCwޙsOloozH$~eYu\ >t]D"(-###Sٶ 0%c4Mz``ǣ8M2 cX,?$-* @Es񴪪_BMlm1:777f&!mmmnw?J)E:NRk5ҡ RܼLz$Ms:; i?՝.ϲ(clC@ Db8r-FS]) E"CLf98Q/.wVW9ssWVx&^壣%EQԣE wp1$IPy;NO)hD1 I?  ( ܁ L Нj5unb]0y4==|S~w~^}A>ķcyX|NLedc³pX/$ T[pJLQēPhOUճ`@DaPԨ.YF5x<|` )AaWCC[[[1v?V[d)a"%iɣGl۶ bjSo5xW,僃;p|>9.ڥ`IENDB`PK xb3z((crystal-yahoo/chatty.pngUT WhCWhCux PNG  IHDRabKGD pHYsHHFk>IDATx}KHTqƿ;祅NΈ6+E(]Q!"ȅ AEDZDHȐBH0IQQGgs>洊t,8Պ(Lpl!6a D PTFX4˱ {+pB9fΎf kɳԕUn k5"ljCՍCPTgۥauZeʪ)+'u;?]v ҆׃>m9]^5)r=EUh|-s#e Gh>м Wwc"4 W6v#at>5aR*O)N;\ H/>w; *2"8]D/&'`'cܶd2xUG<6g*j;Es<*VTJò3x-/bar*vt-%C$j,hg/ԏ_-Mo\of8˶O%S ߺ./:{kIENDB`PK z8crystal-yahoo/UT Hux PK qb3r//Hcrystal-yahoo/MakefileUThCux PK qb3fcrystal-yahoo/online.pngUThCux PK xb35crystal-yahoo/dnd.pngUTWhCux PK QH 0crystal-yahoo/away.pngUTFWux PKPHy۹r crystal-yahoo/offline.pngUTGFWux PK QH%]#~ crystal-yahoo/invisible.pngUTFWux PKqb3+h@crystal-yahoo/icondef.xml.inUThCux PKz8U'pacrystal-yahoo/icondef.xmlUT Hux PK xb3@88$crystal-yahoo/xa.pngUTWhCux PK QH9$LCCCcrystal-yahoo/noauth.pngUTFWux PK xb3_EOO?crystal-yahoo/ask.pngUTWhCux PK xb3z((crystal-yahoo/chatty.pngUTWhCux PK W!psi-plus-snapshots-1.4.1456/iconsets/roster/default/000077500000000000000000000000001370065651000223445ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iconsets/roster/default/ask.png000066400000000000000000000015161370065651000236330ustar00rootroot00000000000000PNG  IHDRagAMA|QIDAT8ˍSILafE KRЃp3x2'ċpbH$1x Q Īi-uvO8ɗ?y{i|\ggcA)~6{lll?(###+\n[݅еt6FFQO E1AtttIjzL&SV媪э7O9 ݮ%IrF {{{x<_E"Inhh(688x %Pd)xPRJ%UP(ޖ&&&N󛛛KG8(.LNqSS(Pl6L٢jfs=iիV %j|r:QliQa䕕T*N;t: ϣx> @y9 8Nk333D@NARXXsqA&r ~?@Ϋ>t2Hd_F{Y2Q?< ~n򅣒 4ha c zf BOJߥdDPbsR۷?U"BEJj=1DnWY 9"%uT;37 stV di-a[)ZU ,/fNY37 n/d~l!VMT_ESaݎ^LV+LAi_?a=ן;FO3Y~algB'x6t~kNǖ!ї+ϑQkКt zC`D3p) -&h B/^,Aa2́e 4MfҥԶv):0ƒ~_ZWX hp:ͨ.Eljj& Yi>*{8٫nbG;FO[ 6S$ޙ7؜_B9a H-@)E%JR@ ᫡!TIENDB`psi-plus-snapshots-1.4.1456/iconsets/roster/default/chat.fading.png000066400000000000000000000212141370065651000252200ustar00rootroot00000000000000PNG  IHDRPWB"SIDATx}TS۶vPlQXQ" 6cPAD {oא -K1z72s$/+ko~kιvpi# S(W{ngr=-&L@os\|.>/GAM-.@{"aϟc1u& .]B7Y^^.>s\|.>q"‰d%S{*+MMN}NyvNlzg*ya6 A?># 8!0&:Ӌ--޽ð+W^ưKyӳ1c0ddg Mm5>CO!*Qn-"=g7o0WW3 ; [Z&nŅp\pPw۶ϿDJtzJ=YY/_zzb3=~ɃvXq_ qC2Kco78QSS_t|J۷oo=0Oo0OuTiRy ^wy> x\.0WW = 0M͚ğN,_db}w9AZIiCBمQQObXRRt͛ca12rR=TU%O%ddۯg ˫1ˆǰdwVsfiD-WV&(*(*>2+HST|`N,QSH԰ҧ;Z.**:ӵdrĆiXpUU` F?Lwjv[鰃I9eedg[_-a#U JJ_+6VXȤeL&R]a4ZyyBتDOÆZ=wP]`i]r9jvv|J(eY;NTp׮hj]v ɤR8 kl,)  *ǭe >7PXJԑ Rܜ&K/((ԲeLO3X㳅7ӽl`LpD--ee@U>m|`b{sr@zͥdwE8/\=|:륬pVV˖-Z1իI^Nd28#/QR lfbiKitl#\~|{:uuy xɷ |JMee0s-ߓ3 C}};uuegڂ-*_&pЦHN6EB@мmx;侇7wӱTm'X[Y|q1Bחaf|dqZ]$)#i#vPȕTUoNi?B&--!Eۙ۶YXDEaXYYM Zn̤R1lӦ#"0l|=hhЅ\_uAA,SP-b1== Ck+dtxØF %ƳX!rc27jmᡡq?oW`|Z!J0 #M::{bGcۋ-+ ,lj*DS^GۓqwAgfJoow"პӖg[Z1}/+ 57#&8?Q`iPP[Ɂxn^i3"۵$ԌN Xs??#Ov54aim qRaAauuyHR58⡡2HICDw>wˌ3qrפ߿7Q]]s]]))L2"( +-}lY oh^a[\ ֽΔs):vQU)(&>iėgP]`%׎(8/_^y9`hm 6?5OOAs9Ew#~ҺZλMK3'ȿ{g&& ]ɇuu!!,. KPaXf)ج,##~MSb.;htu9<߇L>_sʼ_N_??ΣPֆ_ߠ {>6#/Þ+!d ' _$& (,,L lU8^XA|'EIR`eBJJ߿ aBQHxz;=Ld) hJodi4A8yy`ssϟG BHcXF1 KN~=}*5~ssMprsC;z! N8/IK;ylB‘#`BfԺuV_9k _N_?N?@nqut\G?Sa \|.ohhk +*4 V|_O^4zzEC$$ڗz\kh| PJM|H_GGUHHUV"Q^{i)r 8f. cb6%ūtu1 ~VaaPSQQZQT ''E$NrCFF"! frCde e4~IIL t\AL$ҐKLܿlthkh ^lHҥ \\@Z_]/yW?ǟ??)#.<(KY 5>"V^ ;bNJ?"AE Դ|+v􊏽7< 6<|ށּ>t17w4>i2]]$|`&E/` iog@ D 5"觽oߛ2‘ +۩TTMhO#mjBЀFC[W&^pCC@y߼q*_~Fth|a]?a:j@2hP(BA"ÎTVS}zZyVwY *CCGwb_CCS)EAB(8`xx)x`A)&3) M[g::F3E#J5HxTTrGG$@(*B B##$D 483D ߱hי6eP:c*k4Jdr[[z:ZEEB"-IOG+9 8>lۆa>k&HIƿHgq8*=(udX==hb'M8E8buuԴv+r;2κS*pl_gѯ^kuw A?"D0 :bQ#ioT ۲XMM@@TBԣ}}yh7IƟZ8gHuۇW?1uu(܌]{ŶA46"40L Re3guwd1]Y(k~G2Rыlij;BCRH`551R(a(%,YX 991zzǘ/I\ٍƟpMz/o6"P(TUlEÇȢQ ]$ Pm J ٍȑO8ۗ8+u'cdr__z:J#QXByvJ:?j!Vp)9}zD;Դ~JꑐsB.gΌ+j"!ljjo|xZ޸8aE+;qdYY(5<(ô]0읳iMmIE۷|[NH?>_b̅1Ezz7H;{|RiiH`@$,z>)i&"?HCwZY^9_vA6~9oM"sX{H "|D"^m^Ç! )/OZ`i7*-_>M.5k4;D0A!"Hxffnggemnndhc$4qxүN?NNA䒽WבHw_aoFEA-BCCkkokkgg?ܗgTenW\|N+㎉+Tu!FQ „{>"v-DpξVӳJhKksX_+<6/\JEb@11i4TӃVĽ3 ?~D>CCt:?|E_zPI7FKǎU^UuߧO66wÉڠV a\bP=:DRcb055p)HmoMVhQhYȈ$A"!GD`k+ldMM`i) ޲sY^>|1LL+FBBrswtrfAAb"l^,+CFZ5"PM:AxHM*99*>T-/Op⊴Ͽ/A:2d=| ++ ]\vԠM04lJJuH B䫯/) Rgx"5UlDu[/OUSakJMN>pG$_e%F D1kk wXme[d>'@PFܜHD55@NލDǃ99niINa[=qG#G~sz9}szs88QN@` Z-[0+W ifff MN0msv찰]lP+387 wtډq<2sE<.}\_SȻ޽'O]`k zѹv GG9r >ױc66@xyy%%P'k>k'2d;㇇BJ"nџ>߁Pdfv8^N4~!|Y;KJ wp{BE~cpD66Am#Ԣ=z xAjHUT怞 lFVӛEΡT?᧥yz*+Âj?vsÇϞAd69<~ fϞ| %%㸗|*Uh|YwP;-# ׫W==a\޾ϻw:Z\ Bp`7D|wyUnwp!T8=pw  p??!!=55CD D4 7,)^u'}|[=;IGGI?8_))-bKk+3ξ;Mx`3]EWnAJ9=9=9p;_DpuωvXّ֋Ķwui_WY!e%1^l4®;TgXغVXNἼpQ8?$% r߬ވ߽g,֧͊7M1mmhW{z *ۓ-, rꊬ}{a#I$ݍM,\ Ϧ^z10˰ƾr*&YYUEYi]㲅o+z-qw^WU/WPձ$5,,&jVYpO3efFWJIii_ۘ1ΚujhH;8DٔEw y۫cؿ,'f0ݼY8)N;f\WLh}xm8悘$˗MsZ#h *S|??3K]]`osϜStb * -og_O(+|dM33ރgM~: .ond ::cƟ70'~O_]WBp5Zmm|O?_U?5z5WXMW:Fed^ylܟ)++Q<+VxZ-Åa?n6n6n6n6n㶟 ßtCIENDB`psi-plus-snapshots-1.4.1456/iconsets/roster/default/chatty.png000066400000000000000000000015231370065651000243470ustar00rootroot00000000000000PNG  IHDRagAMA|Q IDAT8KlZβ1`1`1<|4 A&$x"d"D#$J"( &:֭-}} ǟY:toxQko?i }h7 FjϦ<@%}ۺuHElB P[{"%~t|߫vHUU[vJzhyP'B6#%XZŢFFr;cgTAлk|ξU.q-S<}|ïOІ:0Z;[-̌Œ<6AIENDB`psi-plus-snapshots-1.4.1456/iconsets/roster/default/dnd.png000066400000000000000000000014531370065651000236220ustar00rootroot00000000000000PNG  IHDRagAMA|QIDAT8O}[hSw?矜fENjA7#826cPQ{/o^DR )(lÎQ\z) ѶK&iN(ߗ=H V\zl{)u?ѕȩ}C-G`+wEyAƽm_|Nl8}P!pW;޺yVUپcf >kh-v48YqUۖMZW4ZѳUE1'@ˁS2x,~c;1I.J}.:!;O!9 j7U u PlƺˬHQD bp:!lFyI0r0hi4A^\;~vϚbშ  *Po++hL/i9ϡM=~bs[<pK'+P׺}@2s| 3$K]߇;4׈E; PLw^NPAJ:A? h8}t.= ӧoNҊT(KC6G4 Qj)?| <=}2n@JJuUV/[UjE/hHW+D>^IENDB`psi-plus-snapshots-1.4.1456/iconsets/roster/default/file.fading.png000066400000000000000000000266431370065651000252330ustar00rootroot00000000000000PNG  IHDRPWB-jIDATxwtUE^!EZIHP@TPA@EޥIGH(!@!v{vcxY{wY9 s3BKTS5aҥ2=rr.$|6FAPo_z~=_ϯ/<Kz,mBIIte&,8=ǹBtHuEȓV 5cyS ~϶ܴ&9λ.;dR؍Pޱ9,N[J&9i0z|aEdGl$(ȫt%o ;َB"#iNa@Pj* Q-2-fNN/i{o{{9BEgVkx^ܹos!ׇ\|\Hz؀Рm7Ӌ^F_EH%8, lhD޲/BCq3?)+\%3eGUvOV&383cZ2on~~@}~19ED1SH>.gwqrtX1wJLJx;9712XRB`l= >g f{sT6/T,ׇa}_5)""2 FmyYEŖK)93M/tL&yіL} ^6o̡C?BɁ%J=o'|M]Jn|&37͹ѻwHHoWZǮi틊~ Oy]'YRC$g{<^E;#/<t]CėxxdMvEO]h*i%kugɒwl;=9?4baQfMmI/^ಬu \TՕ~vm6T[l ALySuus 7#Ifc'b.|]TPYYUNNQ &3ٱ?%uJ)?Wϯo?n?Ӵ{3#pn OѬJ7lkRnpJؙ!χ(o+׊D~vyT*-.\&Yگ toz/&Q(*Yq@P=T /KIGAAF$0%rLS'`Rp˽PJ-P(/K'xlly#L%TVT:‰ CiЀa|\3n7~@(~S؛ #C7j_yG>/ Idgr9.`K QLo>ͧrlɒJeOy/@@_VH[o{ 5pHeVKaE~7>>t 'y?&d,z_t H}Pe21,L:].!cnޜ{YЮD"o(""EZ|?Ud,4 ? /7T(<|H&H T8oҰF%hϷն-'B=^"L?v͉"*mش:+.? d :: >ÓV(d%wn++DGUݺZ|cΝ| F#;pu?;qQŞ=CM?7oP?R[e2]tռaXR*D9 *ʚH4n_߯G =}ltz=BBat{b6mz]U]d  +A: o\}\n߿MvPލ_\{-[n=sя8;5k؃Pׯ_%zٲ/zWoР^7GO&8 {a@ "jKөoeSԮSoJLQ]6-?;Y55{{ʘ/Źczh ) ;wEHH8>𸙨׋x8WAeʊs ^{^}_ښiqc&|wl=Ri@sƼ^,g=zm\|be`1gĭ)KSO83>iE1y"O?lg[;oT'C2B q&cDŽv" B,i .|go7؊N登`zwEON:l2S !YWnv6FD45f48\& JJj%0 bh@1%[nOTm^DbJ^O>#<},^#mDE5?+p`hJRTEDYH(wPXr|TY ) 7JJL&E=G24n6jZ1Bu{4\\%q_6mZvgiҤx&"=,,:!:il<|og[k /ߩkƈj|_C_,KСGAcT#IC\H{Ân$^+5?n7on^z$+ PjAAAށ0~(}a,1 hAh@ ^d4j0T*HG檔c$9ҭ4K:@ˊ ED(R)4a.nXͭBtB\~qMg\B!.!Pj`zHƶ٦+-[)uZZJ&784d^JJC;nlܫ뇐F׃p:rt3 sna20Cn~ 8d-(o___%?iv``!~=Ёå|l6o6LB=EjuPne˂ i={v`ǃRRXFj6Cv>QZMfWÍr~+`ElrrְҏuA=ؒC#w}Ӹqɕg}!LN JB{C̭ 1JpBP}L([/ܵ\-/gVoJ}]nY̴޸܊U6(<WXA x p%& 8,W|>6Šhn+ᤂ9EozSycc/la ??CE)[l ;9Bz[޵VRA4'!%/Z۸t1tb:2@3`\/`|_ A,|XWzE}O@<" M?9W2ev;_.%-c/BC3 Q$,rJ]f[r=kad(>l– ׂeťTR]P^OʻW>xf'7?i&=8XBˁx$/8 \Np+ ʍN]VY0gցdϏw̡WX-@u!㰅 [>r|(߹hkpMMJrQBocwRږ9:LF >XbBlY ,xM$!./X ݩO\7ON,0o7_ 9Z-X,BJAC;! b1L Q}u~LIyuu{'lߚ*1E5-[6jTOl/! ŭ'Oa7yKUg ɑYtIFoX8>8 _<,E>XgyVqTٳ6Oj2k5k8~b:Z5mΎtOo|sk]aEk4מ3tE5Kv|WABN͙3끷Pi}Y(e_"ǹFqyXDEjۆۿJnnT?}ZNA q fF"v֬Y_]?)-CC##Aqǫ0/|K"n>~){RSRt)vgZVsj uZZJe%鮻x)5H\:V %33ĢBXS^0b݉f0sˇ!z~=|ujK#3N7u8}(ߗWT8`(++-NY攕CA**Z31q89llǣp:~&qZ {WEt‚|#*̌Sɠ xT _}uuU3)]]lY:؁7+xPn^7yl!FcvjG(V |V ^ZZn:xj4^{Ug`0;W% ^چ1_oᜡVW=mP1;7\6x 4⃥F#N9`SX j5Tb TLMOݍXx nJ **.++fgd8 x9.ا8KM tPw/Z?j8V86'9\ҢsCL\xݎ-QT}el}kMƒϯ+ eG/^8Rmt,wd;O/rͦB*a|Mx‡>N'bu T qhA㣜}w;O9nߺ՝]s4hߐ'] Cנ> [Th*PA@ Rw}rx̀Aqզ?ų'<-; :>4<0[ LJE0NJ?),n(r#3{^:cի|*ySLɟOT ۍ-0.x\3Nc|>KI !1 siǭӛ %=u3 o' <@8?s!BA(TFM{鴭]-|f0=$'F@J%;l ó N`ȟLݠ}\FW B;dӫȿI`ϧpOCo@(w~ ^XxAxHQR boFI~oFBԇ D6P硡eʁ) a` |=)ByP.0 eBĦ3sY ޜ}}}-|-}9Եk9d3 0BD!bBϱ)uA7m9{{wqb_%/.sqǃ=sO zzN hb[&<^AM$2ǣyyd0{c.vmqa_ŻA bWv2r:E{f1ɷɟɏX;Q@A9'hfOlQ?nVN󩥔WQ`Pdņwzx5+{Ƀ t` UtR(/@}|p3u>]L>?C<&,S\XLEU*%f^͉Zqi~~P>_*,0lzj֪o̚E_geV\dzNZM2 7?D ^:___?__%"+Dj8? |053|ss!tDsY ;`=S'ᬺPCO5kBt}B%4f];picVnc+,f>숵ہgAt'AǕ(wWՃlJƨ3y&]_DQ/篨ZחՅLHabဪUUy?d22H#r~Qh?Zz'QeѨ~# cS$v˸PvQAxɥ¬&%oWJK/7x4ٳ WUjJfa4i69Gu<)^~İEV~1Ta4+} ƒUU^?Z}S~rUBgW/(*ZXh4h-*Q1_2ȿɞw2e'7( Ey媕4iU=íN6/^Teu۷wGC\ٛgJꜜσ՚R8/iV^O,cV]~T1}$5/Q'4]Y*۽mބ=[ۅ#/^+7v@qܟ߰zA|VU4+-OBDd/o|=T[:^J9dhftʛ+{\lq6+kQ;'N*|b]paɲ!]oS¬7s;_jY5ea!{WNuͨ P+k/by#Վ}p^T\q:|zUԌ>ʺwqO'NxDa5y/]ʊ9s2f3?5iٕab2Ar8___?__ _&_qhI4 |1IL=)9Fd@!P!Ta?[Fw11^+m|ڨlǭA}ɗ<dla:NtxypA| ;q"lj:KTwR|| `z0f\NG {? _&foDBtcoq9콍s@q p|>>Yxyt40 @{3k>BdvN'se{c5/)ـ'(-%dG&I{Ѧ*yxƌa.O=e ֲw fǣxz!9v;2l=aΠE"o {T*Q_ԗ(T2{[-5 }/Ԏ< !<շ/N`? CBBCQ4{cA?-?џY,A߳vyf #3où|C[b[~b^Nx8OΓGgٻ/C) te633}l}@זM D(rP'յԛ)uZZV_WU_WU_y|IENDB`psi-plus-snapshots-1.4.1456/iconsets/roster/default/groupclose.png000066400000000000000000000003661370065651000252410ustar00rootroot00000000000000PNG  IHDR(-SHPLTEiii,,,+++hhh+++iiihhh,,,ئtRNSffffYVIDATӍ; P4'i@'囝BQof[ak{2bJ}D!ɭA$@s|Y}TIENDB`psi-plus-snapshots-1.4.1456/iconsets/roster/default/groupopen.png000066400000000000000000000003671370065651000250760ustar00rootroot00000000000000PNG  IHDR(-SgAMA|Q9PLTE+++hhh,,,iiiiiihhh+++ܓ,,,tRNSffffYVIDATW;0 ЄD)b ,KdO##xZH0`"K-u:@9-6gymFnnm]]wJ^^ܳ-(<<ܘnkn fT%R89o r\ˆndGgT E@X\C|M=bB],-T.B$̌ . Whzbjh199TM h Wo< eܮ V/D*vM/7h 6}G,QC<414,,(À6}w3XOA@&_ 6IG 4h"6`e a g%|1d?~5X&jiU8-لJTtB=OavNqQ4 kFCiՄ5XY3TTBhY oW?ɯtDd}sQܜ!&d&I("`xӇ. N#0{InH®U!J%lp! 3K-kjPX"8_B!tv`y;aj|'7 o++Di?-iIENDB`psi-plus-snapshots-1.4.1456/iconsets/roster/default/icondef.xml000066400000000000000000000057751370065651000245130ustar00rootroot00000000000000 Stellar3 Stellar3 Iconset 0.1 2006-08-30 Jason Kim (Base icon) Remko Tronçon Everaldo Coelho (Base icons) The icons in this iconset are copyright by Remko Tronçon, and are made available under the terms of Lesser General Public License. They are based on the "Crystal project icons", copyright 2006-2007 Everaldo Coelho, under the same license. status/online online.png status/offline offline.png status/away away.png status/xa xa.png status/dnd dnd.png status/invisible invisible.png status/chat chatty.png status/ask ask.png status/noauth noauth.png status/error perr.png psi/chat animation chat.fading.png psi/message animation message.fading.png psi/headline headline.png psi/file animation file.fading.png psi/system system.png psi/call call.png psi/connect animation online.dimming.png psi/groupClosed groupclose.png psi/groupEmpty groupopen.png psi/groupOpen groupopen.png psi/typing typing.png psi-plus-snapshots-1.4.1456/iconsets/roster/default/invisible.png000066400000000000000000000014771370065651000250470ustar00rootroot00000000000000PNG  IHDRagAMA|QIDAT8ˍS[HP7LfHA&J%%Q"A0 5tbB){hM뜓=B9#Hd?ܼdN;xa'%wlK=$j X^bNس 3eD[缚_Sws*& o {)9{ bczKKKT$*ugw5G$Ur|R ͰZXZZ$ߏx.`SGRPTVSS,// 籸 Ab>zzgA2?J̝9xaaAWWWш@#P/YGWFNd2A000-'wQ4BIAbll DXXVVVPVVl6?*yoN# >>>+d̑122qTUUKhllk7 \>)Q@:677'J~EaBX!*wwwcxxsssb8qu\M{{; 5RҎ ,F 33hkkg`vv@gg `#a#RT*eLv1++k;s%| juV?U^,`/?;!˝sJJJ III9߾zڢOIENDB`psi-plus-snapshots-1.4.1456/iconsets/roster/default/message.fading.png000066400000000000000000000273061370065651000257350ustar00rootroot00000000000000PNG  IHDRPWB.IDATx}TS %"EX@T (b.RT&^J pg}{]W_|bZJ93{= opP\Mo%i~^|f0@G{WZ0OH03olmm`LO+)3ZW !U7nZ/k=mE~6>AWMP|f2g4K3]\xqG,֭=({Pn*d֬@uquڵ)ZZFF,ekbϘ\UoJO;9ܹ1NhiӇ ۝Rx%KݴI0E0eڝ{1[ *>[`oڪU<5<5E5Ya5x߾>03WVƫոr-ˆE@N?N#l%H.MIIJ@WXb877,,!M~$αކjm|xDuK KO()-ۮl|cU=:D:0Po2w<{pS-|nnBr]Y9_Xi$$={mތ7Νj.OybYVR^?.ǃnu*?{KEЛDrt|<0A"P).FvT &҆J_dR[ ;h;hw[T!M6?߄McwhprAcbJE.-##)^*ӽ$$#h4TN1TLO?ŊbmԊݻO$55!!) A􆆒,ڜ?8~EH$--07n }ά-/|'Hё#|6kji}cB-v/<_NN?N{Q` -Pݶz*zz2P `|78*,B!H)*PL]ZjҭŸx֩Pf.xԅYvW2V$!X}}d'z&hhKGeLc0_y%&"/**?'DUTPydk{K qCkZL{&&{,}#|⚚3$Ud.^4.^{v㓨~1&N\-.ꎎx UX/*^P3:+Ϗ`T@v?N7sM_\Sc\]2/uAxbUi+NX.3,;ۘb}fhh98sQ FFՁk ޽M B\\F?&??22:UUxLL46C!k`s:3[F$j-Y~Ξn)YYqorG]ʸs8 M751_*#w.kG̱1 GyAh >U7Y_moxC>O~צ5I?Om?SW#|alyAn* ]]nq8*{۸˱Lxs#WB]^~:P|62>6SYFo:iZc¦KMMqv} ~sszszsZqZl9#uuނ2 _77Cex GdT**`Յ !e]]|plt~))|Nݑ쐪yNѻ5N U)Yva{{oxxr*pRScu*T.ZP:"DKWn޵UڙP 9{v_v4iW3=Zq16M$RhX,  P8}}UUYW  H IV19W}e2km(mNH93?#[L@݅ MHNcW?{ݝ~:HWWu5w|V]]l+`DA ]9jP]o|1拓QFKIَG] z]#L{ : )OMrPñu\n+SbK <\DtõwfJѪ*OOob"++#HIΝ.ɤ``zѦwpȐ"bNMB~~m\'̌_-tU( ]!2dؗЇlj 5ssӧ$Gx}i`iƷ0~o?????u]KЇ 0O5@CQDE\J[ܼy G3c7@Ii  N_-,$acFG!?11:|__HDu52sYIZwm__.--..-C &|K  (섿R)` *+)'DqryAz@^T-*@U F%;A1YߏG#=))|N~uųb2 ԖhGiiAL|@mmMM or  lY=L@ U>A1'hk`@Wfy!ڲ22MSwUUl[^wƃjlGadrRR@An`GGZ֔`D220s6\.I\ެLrh`P)6@6A@N^k+_ߋ9 `xH$gG䒴V&6HE>7F#~55ٙmp.)Wgg|<<$&(ss)+{fV J ڵN7fdtt/_xܾx@0P 8`W̴ WKϦM`CC7oaGG--#_4e Qn2tVV`Np0#lÇa| `Dhh *AAr P$4T`SȁT1/ޗ/))|NKH׈֝8Q+QRX٥&&Š2$$$b++v$'GG" X^N& P=IV/o?՗n^pqE*CyE3 +сo}=ILBsB0:̌ RuuN<%hoГcXMM8g3/Adyɇ}}cc99`h4PTjQPSRY(,K&a "#PC!=80d\( Zaf^Znaa>s>;ݨw[niuuBkSpʈD`rsCB XV=7 aIIDF?+90A% h.]睓 q)hɛ;meF[HM :0tz~>0`II`@(pŁ0pHJKkoƎJ ,]ST)wil%%[g^cl~PspYґ*iBf :oooj0BC!۳vsn)|C440C1ɇNOJ:wp!II𴷇 L[__h(0t}M>9 Uӟ>a0qCC^0NkZB3%JCC99ПbLp8?]]닍Ɍ߸×/tvw.[10pRx~΋!ݣgʼn[1`9NNNN?NN? &Z$$L#Ms6yT 46wm xQH4*Q '{T wC}PhH8^EDFAa&Lё3UELDFd11hQ@fG|%?@a!:5bUV\?0-G$:;B$} પ`*+LЇk]vwv<-&9yAĕ?r_*s(F -fFJ(I ^AL&(-ƍ*.f633ee溻#fƈhG]\wfj{[Ձrr:/^q|ff>I 0rPΫKmmEEX(P2ꆇi4vT_ B.PZeIIV(ļ<"BWT7@$JYe畎;l~ZUenkn{ۍB2O&yy'hHCC`JN@ FXyWUefJp fJrs)()=4lh8j}JbV1h01qz&yy<=߀Hd0!IC܏ɬ\t`**D0`p9*/ * 2 }QQF~~pXLGI|;\wxW4[0<rC.py9JKXWG$P^ Pkkf&00UU~~zboOB"^!gprt"r/[[cbLMLK\]A3HQ87;RǏsupu+lT;p0Pr*22Q r`0SIF*QVeWUųg/S'!cGz{9/ða0`{ <773rv!dITvXegHQ"zEGܨ 7|kx+MO'v9LQ F _X^BRSSy9b wAiiz:\^Y uI vȚ&QZ @ L67¦ Fca[ (?~++#<{ҧC!̐7>r+f|Bσ 0ᘎ4yr6U}}$OF@u x+8+@3t: aeee~>B B3drF|=<^&`բft:?wN#R' ;deEFw!HaR^ J΁2l*7#4B1۷1h9 -u[QQl楳:`h`KK & rqC+<\DӀ]?w>FGahm L& u[GZFvuA+*nhKWVr >@Ŀ_fi55M$42x.`Bp BMHฐH?څ fǖPy_:>\\_kJsqinf `FGḣ>2n󁁢"Q3Pwv>>kXb0ĕ䞳k7,Ӑk][5XЪmA $p8B`*̊ EE<Vu8)+ qUii1jDPpj0-(Ckkx򄃿#I& 6{@Pcn~ WΜ\sf:"<%%\: А 6y">;?+o[@h@-(\hO54 HZ&i1U0+S}g {{x)0YZ y+uDȘBpQ/Q섉3!w8ax(ܯ XQ &e0Pׯ] 5-X7` szzjk!w6;;tч[?$ٙ($qxBwBŋnr?( ${?!?kҼ>};47޿!1ѣ+ي|BEE.)mc%O> nO2['t` yl{{Ah ,\p<_Fɼqhmon~NAM'?CH~ o.i<*(拓pgy{C? ޡ\`S썌w v7iPbn# p `ֺΟ Vq*ӹ{yhWOx #q[y/)q]]4Tgxq!|EEb.0NN?N? [e3 &`0G8 ncM`pA)//^jοP Ͼv++AVܙ$`[D))_lE# $X |i7 r]ƞ>Gwuum9ٍWmmѵl+*^{/EX1v~?g/M,s?R䳃Jzvu z;a=Ņ>􊋿8 ,ۯ%Xq^"N+Ƣ}ŕ1ܔgű-199z* HG5$r Ío=Xb%{ܲ{>z^dϹK[ngvyqq?V,ʊx|LY,c;zē $mniôa'TU4>&_D '~ƌ ro OOHxKK ej`7ΟnB>{>{ߎ=sSy~HGoL*(_?????gj,?~`epab%SS+'ʒc+n|5k!lݺ? _-Yɳ1j7CjĦMNldw@ Ü;3`ennn400PB7x1w>88Xllla!B 8ZXX@CCblU&t:7\mG{{{eʀm#bh1u,ˇh~kjjfM@BD\2bx6w|\_wW q'~a.JTerZbęfggV}Rmw˼ u1mdՋxތ{~f|8 VPOʛVGeMGS^=B;$+w1T |jj*P@38@'" c޵G3x)߾9](_9Nibtڽ[WD5f$" "3jfVx#H%:tgzRTieiajqPӯ@FVkU[ޥX$''w UhP"*( ĺҐ4ʕbm{s \Hެn3ӏ?{{gjuǟ@a#"U yR z:"XIENDB`psi-plus-snapshots-1.4.1456/iconsets/roster/default/online.dimming.png000066400000000000000000000250421370065651000257640ustar00rootroot00000000000000PNG  IHDR@7z)IDATx XSGsV"Qh*.jkJqZwؾJuj[ZTQDM;!!@V>83sfΙs hCoLZםzwɲt*q;F~v``9a{uLdTq}l-}Nc@ ) K26x 66K.R;b2956aKtc ]Nᛟ c2 ZN7a ~<]?AL`ȱ{B醿 AZ)T/cLo=1bc7:]oG=T}_M|9:, t=^«:]djm g2 /Ʀtwҫ:]Tgo?A|Ld8겒w:6y'ǯxlw,Clfb9J켖{%6LKvt [D7|X~jxc!v9~G*\^Ssrz6z}ZvAGaki@GGt.|ర>RUUUT\h߽uЊz$Ľaד;;yŽ|}Tee\bN:ha9LB?SJHS***,J-jZ_~@'w}o[nTwgeY,-_F>M^U.m d*'Y,[!!?9>ST?o8v?[VR_X.oPa #Jֆ[n.TښX2o{k^u+'e®}SUhT$e{s/>'{>VseZ>oJk4X,yM٤׏r>}0v/,,ky5bb):v\2 )?O? Ǵ<d^pxüྒྷŚ0`h,*]mcWp73<ވ7^kj_zI=tsA]B 96HFg.p@V76sWXc Mhcat>F> FV<^UASO"!X!4 OǰM> [a<^XN6k_n .`X}}A|45GE#?O?ZNO 6ޢvmFٷFF44Ctf0 lº,?$:N0?*1v($Db'32Ddvҗ'~(G^hW ptPEF=;D3Z|$KAADE9f{=-$f+Y9#EB'`oovֹ,}9/l#",1KlkڰX4H4 wGTЙb 3P{~ů %)>2rΕhn`NXG<_{㚘0D =`fh>#D!bnwfp~M@}.##gWF,l4bF&1 HO ս$*Sv9Y~~ZvYB(ނs>,a=  BnA 9*n9nO&oA~z姺)?N 86|oo'Ξ4 `ȱ|9W^ll@66ٌanj}q@L;%2sM}=Ϗ~4fN@(ΚFvpv~Pa1[|v1= `CȚ̈&?pE]\aaP8~rl.8،v镟Z4ޥiF0 PEG}O'x=F|GG77\f H` .X]]Y(F^ ekNj EzY _IM-*jhhoOJts\>ls{DGOR ^bOO OWn&z=)e%kk3'NSH_,-//_WҴ35UV152)c>ÃOvwM`xM:H&WUPbe?uhEnhrs膋74mLJrIv)ß=${0/=Mw] }|xs/_pMnZ}CꖑJ^[BVl"ڿv\Z#*&%9݇BH_ޱ{3$#PדXYVv@&&  rjj]O^~jz/<ܦs?h݀C%o2ww? )jAf >QUUkZPh<WS; 7t LM-`5&%nGW,7]"~X2}@Letu!>| A]]Q+Z+Ej|xkFW A\CYyMzwDFݎ$IJ"t lt?t"}@T,hmkS"[]q}Ejjޢwc 30Ϋ0:zpU$1kwtgX!>|Iy=dFLO(ynhlS1-55/II 5qD P"<Mqt<~, g@~~~̠ Eq|xyԛl0姺?m+3*3w) Z,TDnh4vu3T*Xvw#Cc6ww#`0LR0` p]QQh\gyyɖsIm߷pJ l~ \WWkzD2I-sY], | # |h'$b 7z8,Pظhtp˥~W`T:/.yU`pASP"dc'?ڑ|?_7[$,ƚ +:ҷD#;pVKio=C|hTZs7?(1N,Y,$CՅ + I﯎`wO~NO5=hۓZhJ^6R雡ydjm4BL= 6X#7QZ,?(qf36sFobH \hN427vR_t '\Յ]D/CQIh*ẻˤFK)1$/:aT:ϖ.`x~pA"1b@KQ [ވF㫵GWNH~^tbLTiܪ"bA>XZ[Z,0C joԮ6#>4r.];L/{wi%'0@NF3C ћd3\}N ck0zWTQq.R7 (V!Ƞ <:$ł*S=TU\DUDtNckkOª4ttԀU(J!Ƞ <@jl T'Uϯ(,Ĉ5h |$?$\4@zTXmW|/whQy^[_[[Q{ZZ >lvt7AP; WВeW&TήJχMee T65A .PIG7w$ ߑA 5-*7@/Iܓ`C{'~otZtv@~z䧺)?,浚Eyy?(!AFK>&*[zZ UnY[Ћϫ=U#ok^kLe]G^QZ9*8.`o6 *p{1LNŐl}X[Us&?2;OU&R>h% #<@((2+p~l/%ui7n`͵}EotmX`0FkK VV^)P64!>o_|𵶂0#f077Ȭ$鑟ZxҔ_<s-(@Fh$WD6|J J *3D.?pt 39U+tA??v)'# |$ ?ɍUr)YW^q`cGIÆ~<\>ixPÄ"D]]PyWT'_?{Iwm*w9$9]PV#9HO|ɇv@ AFt:c%̰n+|.~|t,PdhYK'K@#)_]qM.:=+e ~) ).5>sA#/=Y.#?OS=wߕ/L<| ^$H>jF%K2@rQE6j__{[u\~Иmp.)YNKncqЮ dH.4ӎG[T*7~t?@Xt[RP/׀ C|W>dB|A//]5vV~'Ws S+L<:Eh4t^N[.~'ېeoQ tuC"i!#kd t"q\=$7@&xD6Eh#?OS=ilAd9]:mݝ:?g0.W ` 4*1RPfHzeelKNu6;CCg?K?U9y|(>i|8mRtˍYz%,?9r|FohU=?qAĐ$_Uc2 n8-IX$LNUn;_.th+o{c plmpRg{~ta=a{_K<2x4drrrK O~jzS=, g^ 6>~{fkkoߛ0O+h8%J9qPrrUF\|qgng3(;8߻I>zԛP+խD>lHNVa6v%. ]c97=9:2瓼Gr৆D-Ԥ$'.t߶L*`l}p|\[H^_[  ~[d%&'UUM{n_hG/}R |ggn ?т"rp^WWU8?m9;QW(m~=g*?9$HUEOT?Omyf>-+즪 3͑g Oӑg  }66APDhgy.xt4A&ⷬYY}| öi4$(, :NG|ACd- MOGJv7#d"Fq&Y,g$9kӑ{4mwΈ5\$$((%098> `v V/{-=GUu5xVV[1y\෴"/$?yzY,(!R[tRz:r|o_9+\ -OC"{AXLT2xo t=ST?qqJVICڅ *ktDV4V و,0p2:=M7;yZЛ("+ ?zFcOq8Ђ_aGE=_/a:4ۥEVH`=dddD >0pa_iƦ#h|hgLV[Cԇ/ba2ᑚ5b!>f.JbE#不#ڤ.g '̾Y:-tz|[[\S{z姺?N4G,m0FW38xvvhbܘ@tH`E *p>o{.s+YRl9`Xut3H`0D${9 o?h>ֆm08lwV|(LL&R4C=dy<ܶI' ;03l~g|{{\Gb!>C4/탟bG V5 Y,dءudP=X,!., 99/B6l ]OG Y[!## S,?OS=)`)Ov`3 p,dzP%`Hpəɐ3Æ v2R4U,ͧѸ\T;;up|pz&v>S0??>h|>pi4?p ߣJ/ɞvc5R໹NC @|''O{>5A' |oڢzy}ơmNv>ST?E߃ * NH\фF!a!DDc{(j{{8:"`;Ã>Bk2?4/Ý-Z|Q&01#>/cӛqh>}~`EBOFXb2:;u%oO=ϸ;tz_|EԐCA9:>̯!P(w{z姼 ;\Jka2 A mmP#_qZ&Vw+_pY*1BuOMM4h* P8<,0/^5kPL/`Bڟx͍t%`rv[dDޢke{{IH7闻rqGho䵳$̣x'U&Wbɯ`jC,Jod|=92~;9Llz({y-P&k.Js /hjjU*rS0SpwA !?EoïLA #=-֢Kn|:4oiO.⃢By"o#J|[|)d& a>(8G/d "MM<"+!?S?cYLs0f!!凷#P~╄j!kK'xuu>H><<0ǵ;oBfՇMa2 /Rxԍ47yƋSmcKKMMCM fd0G|=~} 0}L;AUZyEzTz@*ݝ~|<d&;cH|VE{rsOY_rTRiIU)+C|n*Bw睠u b&C>vvўkijYǥ_Rh| =ST?UOxҴ|ܱ7HLa㱴+bb=࿖W~}=٬V_li݇"M\Tn9ɛl34GoY~-BnjbSL>{rGJJ`Kࣷ* k |2ZmRi2 Yy䴴dr\܍RB9~rJʃÐ~NraaW~jxS>ii٨ޭoW7nlK4}^ORfe/UKLu11qI;f榤?@Roulk-h۴R柩H4n6\O_P_*wu}>fg<%ož# Ї4w|hB}uknB Jݳ;޴䨁m6?hrܐZ;4&拋Sy_#e+wwəxk,;KO|`Jwi۷mQDZe^2u}iqy15=3x#ȁ|~MEjs`J;bFR'$Lom .r7'hH1;OuQ44j‘3W:I IUhϓn|zQ6۳UMnnj㑑ߢ??=>nWC4?K/NL]"U#"PQ\RMjq05 `BIQI_JIRS)\1mtn)gE&iAOyž P,bChdLG9.ә#Ӻ҉o>DžcfJ^W9 #c ܿf~=Y=u\Km[+ݛ:øO'_.DF "̣<х6&1 I?sMIvEIENDB`psi-plus-snapshots-1.4.1456/iconsets/roster/default/perr.png000066400000000000000000000012471370065651000240260ustar00rootroot00000000000000PNG  IHDRagAMA7^IDAT8}KH[QFDBBE wmPIWJU @ (]օBAFbU-]ԭPhTѨ_Q|p803"*D><y)wVS니Ott8Ft:OkDWUn`k0`vv8-2Ɂ܄e6$7u؀YMpHC23aj ;Q_[DQP | >Q v; cbP-S*+jjJKAU]$p+9 UrѩV*(,u`?MPݸ7>rի wPg4z4ɂꧩ ;[sXX$$ͯ`rki91: zKGő+IbFF`nVWyj&&`||jKGNgUf.{{aff *-izJJ1_Jʨ. b6jHa~{CPWu1]@4=5 vF9AjX=Gۊ{)*v= 8~?^`lb4W*f ʟDr2 ~fY_JmѐTUJm8mQl!N2⧻%~5)mJ0 Rd!ea B5Y/䁚WMӈ㌌H$z8Y.z[npWVoMOOhhu1M )+HS R$i_J~דu[:iv\l;oXTBJ+̻ccl Z\X}mƒl@pU TBH) =60E,Tf; P+o{oVK5IENDB`psi-plus-snapshots-1.4.1456/iconsets/roster/stellar-1.jisp000066400000000000000000000221651370065651000234210ustar00rootroot00000000000000PK (4 stellar-1/UT K=CfGux PKPHtjstellar-1/headline.pngUT GFWfGux  sb``p  $R,鎾 Ovy|B\t'3n(XFRWڽ@֌7M4=1{ҢzG1I[ V##nMLNOW?uN MPKPH2dstellar-1/online.pngUT GFWfGux  sb``p  $}`Iwud`Xœ%?~2d,ƒ `g7ҀOǐ8y 8Z.z1laP(}'ӱOx-据ito<9wՕ *} [[ &`OnzοS7tsYPKPHҗnstellar-1/dnd.pngUT GFWfGux  sb``p  $}`Iwud`Xœ%/ ?w5KHr<]C*|O_m0`|a%  5^,t慥cyB.g_/^Vm)-rx0ˇl >)o9-*btsYPK PH7Gllstellar-1/message.pngUT GFWfGux PNG  IHDRDgAMA aPLTEʰPOtRNS@fIDATx^}m0  v '<7M1e9] }j u<Ȏ=Vo.u ] ;"]E4tKOO.Ǐ1{p,=џ則7 #u}D^N0ȎH2dGʋ`CvkOC #dGcSK!fPmAIENDB`PKPH34pstellar-1/away.pngUT GFWfGux  sb``p  $}`Iwud`Xœ%/b g@5~[|ƒ `g7ҀXOǐ8ߞXs{2\`6bۉ#jO\Aq6^At»]2G͝yFa= 2-btsYPK PHstellar-1/chat.pngUT GFWfGux PNG  IHDR?#gAMA70PLTEŻsss$̌'|TtRNS@f?IDATx^@XYl]qv{8B0B-֫,BҮM/-]:a;Zz)8aX73 9ZǤ_oG 5 2s\~w8JN>$}<ǜݯX~ռřuzF3dY:'ȍ>c;8:A5x9nwhb3hJc.9 ˚0ǜm'9DUo$*vptwd}nsTPu<_p~@:ͷslnF9pMT3O ۡ(e}<8 ꂲVdFSk/m[r&k9$*2cض%U"^e(ݕ {qp/M1j*\=7:41LgMb;W [Xo[v*ff rY/`:ɤ۸1L\.塯]?WΞòb?Ipgcf4V9tyJb8z ^ 5XO')'4^njr~kT^ pzlPVFFXtmٶ'G[M'8WԝttXKx| *mx%ƭ]< Gz!z]8n_._ݻ}7X*2!H 2W^LF(N+;yݙw_x؟ ??IENDB`PKPHWTstellar-1/ffc.pngUT GFWfGux  sb``p  $} q 1 }Oƒ `g7ҀOǐ8y[ xX.N-zCǤƅ ;y,~A򿧫:&PKPHFJstellar-1/offline.pngUT GFWfGux  sb``p  $}`Iwud`Xœ%HefKHr=]C*|O9N4eSBPKPHstellar-1/invisible.pngUT GFWfGux  sb``p  $} qe?~feL 4%A~ n91.!q'.r6^+tn)#7߂ \s<~a$f. ݍOU;.Y៖Z}sp&Cѻw^ҫԠ/nw; K=s<]\9%4PKPHwTstellar-1/groupempty.pngUT GFWfGux  sb``p < $D;^R,鎾 j~N|B\d>}Xt鍥-@>cI_0ói@cHE\ GA׃듙E4[ڲ?74)=XVX#U:^K753xsJhPKPHzkstellar-1/groupclose.pngUT GFWfGux  sb``p < $D;^R,鎾 j~N|B\ddDlXFR{"'{[Kj]ϜVFMI5G[̘)#g6P+:&PK*xV7q>E stellar-1/icondef.xmlUT fGfGux VM0=g{r{ ؕqhJ 0 m66$Qzyx2JzˆUFoYB ( TUF&EUe~J%8Tq oȳ&?4( Pq"IvK簐& a6IE(Ka@Kε㸵xYž -x\ ɱFnQ>tͿq>aĝlȨ\7^aGC()pvIr8}:yB[/&y}6oGWQD~j#}㮳cH\X{)hxXq?`z2Wb~#f0r)Y*#l F,}b^⺙|po]7R2Zf37S:Fb0F)֣p0W;ݾarS;K\k@C 3e*%6W_3p,S{@^?,sC'\j# ]&Cٺ,%+[P^PKPHstellar-1/xa.pngUT GFWfGux  sb``p  $}`Iwud`Xœ%{߿(do; d138<{8Tx`r;g F=[U~ŋo97otM{ɹ+4noU[غth1 }r;s=h4:&PKPHastellar-1/noauth.pngUT GFWfGux  sb``p  $}`Iwud`'ȗ q?Y\\̰!aU˖-;T(XFR{ a[oW7\\u5k_>~ }{ZI^1)+OjvҬac6m܈;lvV/Ql;ܡ9klxguo^Q <|SYޙ6(۰G\ᄡό|z!ㅛ\wpBOW?uN MPK PHstellar-1/connect.pngUT GFWfGux PNG  IHDR(RgAMA aZ6vݢ=v'}vV֠?7"AΗA5ԩ})_O{4>/P,XH ؃7 k%=`;A/:{tfdV!{A:V̓=ynFz=73A @yӨ 5/EkQiIIENDB`PKPH stellar-1/perr.pngUT GFWfGux  sb``p  $}`Iwud`'W qqq1lٲ @ (ṖiP$/emRM.!q]\N6Ho:#c⥷{G,9% sIa-G>Zڵ9QSZyt-r*[3[Oh+T蒽ɎmDeߨbr^^ô;W=%??\) PK (4 stellar-1/UTK=Cux PKPHtjDstellar-1/headline.pngUTGFWux PKPH2d)stellar-1/online.pngUTGFWux PKPHҗn0stellar-1/dnd.pngUTGFWux PK PH7GllGstellar-1/message.pngUTGFWux PKPH34pstellar-1/away.pngUTGFWux PK PHstellar-1/chat.pngUTGFWux PK 3l@: stellar-1/system.pngUT}Cux PKPHWT7 stellar-1/ffc.pngUTGFWux PKPH?E= stellar-1/file.pngUTGFWux PKPHFJstellar-1/offline.pngUTGFWux PKPHstellar-1/invisible.pngUTGFWux PKPHwTstellar-1/groupempty.pngUTGFWux PKPHzkstellar-1/groupclose.pngUTGFWux PK*xV7q>E stellar-1/icondef.xmlUTfGux PKPHLstellar-1/xa.pngUTGFWux PKPHaPstellar-1/noauth.pngUTGFWux PKPHh Fݗstellar-1/groupopen.pngUTGFWux PKPHgstellar-1/ask.pngUTGFWux PK PHstellar-1/connect.pngUTGFWux PKPH stellar-1/perr.pngUTGFWux PKWpsi-plus-snapshots-1.4.1456/iconsets/system/000077500000000000000000000000001370065651000207265ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iconsets/system/README000066400000000000000000000062031370065651000216070ustar00rootroot00000000000000System iconset README ~~~~~~~~~~~~~~~~~~~~~ For general information about creation of iconsets, see psi/libpsi/iconset/ICONSET-HOWTO. This file contains only required icon names and some details about them. System iconset, is a special iconset, that contains icons that are used in labels, buttons and other widgets in Psi. And a little note about animations: Icons could contain animations, and system Psi animations are a bit special. Their first frame is used only for creation of static images (such as in popup menus, or if Psi's alert style is set to 'blink'). This first frame is not used in animation (since it's stripped out of them). PS: If you would use Psi's .png-style animation don't forget to add animation tag to the icon. psi/account - Used in Modify accounts dialog psi/addContact - Add contact psi/manageContact - Manage contact psi/arrowUp - Arrow up psi/arrowDown - Arrow down psi/arrowLeft - Arrow left psi/arrowRight - Arrow right psi/profile - Change account psi/clearChat - Clear chat psi/groupChat - GroupChat psi/help - Help menu psi/history - Chat history psi/vCard - vCard/User info psi/info - Info icon psi/jabber - Jabber icon psi/options - Options psi/toolbars - Configure Toolbars psi/pgp - PGP psi/keys64 - 64px keys icon (for example for pgp dialog) psi/keySingle - Single (public) PGP key psi/keyBad - Bad PGP key psi/keyDouble - Double (public + private) PGP key psi/keyUnknown - Unknown PGP key psi/playSounds - Play sounds psi/main - Psi logo, used in roster and tray context menu psi/quit - Quit psi/register - Register service psi/reload - Reload button psi/stop - Stop button psi/remove - Remove/Delete item psi/search - Search service psi/sendMessage - New plain message/Send message psi/cryptoYes - Encryption enabled psi/cryptoNo - Encryption disabled psi/time - Time psi/www - URL icon psi/email - Email icon psi/xml - XML icon psi/logo_16 - 16x16 Psi icon psi/logo_32 - 32x32 Psi icon psi/logo_48 - 48x48 Psi icon psi/logo_128 - 128x128 Psi icon psi/logo - Psi logo. Used in the first dialog. NOTE: the leftmost and the rightmost vertical lines of that icon are used for stretching psi/smile - Used in the profile creation dialog psi/ok - Check icon psi/cancel - Cross icon psi/done - Done icon -- TODO psi/close - Close icon -- TODO: add to default iconset psi/apply - Apply icon -- TODO: add psi/edit/clear - Clear icon psi/edit/copy - Copy icon psi/edit/cut - Cut icon psi/edit/delete - Delete icon psi/edit/paste - Paste icon psi/edit - Edit icon psi/edit/trash - Trash icon psi/browse - Browse file psi/play - Play sound psi/eye - Used to determine Hidden group visibility psi/upload - Upload icon psi/download - Download icon psi/filemanager - Transfer Manager psi-plus-snapshots-1.4.1456/iconsets/system/default/000077500000000000000000000000001370065651000223525ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iconsets/system/default/account.png000066400000000000000000000015521370065651000245170ustar00rootroot00000000000000PNG  IHDR(-SgAMA|Q PLTEbbb{rrr߂jjjbbb|||\\\```fbbbTrrr=aaaܘ6ڈqqqzzziiipppޙ$=!)  /$QQQUUU___bbb{{{~~~)IY#[+8X;!"%Ch-26ta9(*9G*+Uf{fŜFfr, V-tẴ!\!n{zߢ+3\,Sv;6N)Lr2aH-1p(7*.ϕa.Kh5m$&2I;yel @(jx(.TxVr]ΡJ`%a1k ^ ʊ*1EI Fb-,^>^Mt +,#6%8&:)>,B/H"4O%8V%8V(<[(<[)>^*@a*@a*@a,Be,Be-Cf-Cf0Io2Kq2Ls6Q{8T9V:W:X:X]AbCdEhEhHlHmHmHmJoJoKqLrMsMtMtOvRxTy[bkooqtv{{Ąɇʋ̔ϖќӜӣܸ֯߿ш˙҂ǖњӜӡ֑ΒϔφɕУ֦׹vÅ7VtRNSp zEVR&B6}H}}Y};TPKG83R"!-* !!  /jIDATc`nMffa$ eq }]NHDEGq@J,!"ՊPcbb Qaaᑑaaa@A8KQ4$`"a C%S\!@ lS qF*y<\|*F@ ogsmu +{`&E:IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/action_contacts_manager.png000066400000000000000000000012111370065651000277200ustar00rootroot00000000000000PNG  IHDR(-SYPLTE9& 9))@11J9322S22S44U44UN?;PCCREEZNN\PPJJjdVSeYYl``^^}|||ٛח++M--O::\f=w>뾋ÑėŘϫղ߻-9))=,%?00..O//P//Q11SF8833U66U88WK>>]F/REESFF@@a&&YLLHHj^QQLLnMMnaUU99SSpUUrg[[k_\EElaa[[|``}ecwddxaavigWW|||ijohhTti{{{{ҡKҢLɄwԣQԤSz۸tɣ޽|꿏꿐Ēșɗɪ tRNS-2B?3Diu;IDATc`!a?o~^8twqf ؇Xl #Yipm |IDN^N8C0&ki0d@FIII ( rzz9Ҝ\ rr^1I" P!Qq03d]3ܜd *d-#bd(((1(|G<|!(5{IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/action_direct_presence.png000066400000000000000000000013121370065651000275500ustar00rootroot00000000000000PNG  IHDRaIDAT8˅_Ha-@!"ݸ CӛH$B( 酱HJ(. *CJa$*lJsT8/C+L}\ym"zgˁА!m`>K_l;qlnDt98lt Y&*D#3]Kȭm9,}_l(S8ϛ=N c_P{ 4{V r 1]YmzC. Y ["}1MWv'4[5MJt]ef_5]c0mT5 dXVOUngKcӜ}g5]Gm]qk z|-HR2Ӓ fD{*֥zVxTdo1fcdc6r8U=,?`[iMއ7x+[7__@m!՗3-(VIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/action_muc_hide.png000066400000000000000000000013171370065651000261740ustar00rootroot00000000000000PNG  IHDR(-ShPLTEeee2keeeeeeOaNbHa>b=d:dp>q~FH4rJI;^1tRNS3; dZ~ ?bJ#@a=a܌=  ` TIDATc`ueA^`t\\]Ks"@|Bss77#$yUaIA ?#$Se3H{dAK;_! dZ{0&Bl}ׯR` ĭ[1?$1 *<!lUD[[?>nY0a2RAkh۹3KXqy88tI"عs^Gɽ Vk"@(Zq^o9o&).<\DG g?*s_X={FǏ_nKLhkͥCC/y7n<@NN5U)P^~<d¸o;Ֆd[+*'܉()q.-zNg՚"#wH;uܕÇJr]nץ:44N8`'!@~~-**."rsBGM?DN;*/dz!"b7O? ?yk\IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/action_muc_show.png000066400000000000000000000013111370065651000262350ustar00rootroot00000000000000PNG  IHDR(-S_PLTEeee2keeeeeeOaNbHa>b=d:dp>q~f4rYkvG B7߇BK%E=ࣸ($FFТ`e#Ik).RC* U@s2\dK1>5*IFK7&0Gzo  g<AsАŧa|!D`اWE[}˱GM0JzQMB<r >N[/V1BMz @ OװF Ei̭6:3a8ݎLڙaRTu{ zε)q c4D&б3 n)p>(5 UQf[uM2$)#-b@`.C-f +hE:˃=i*oDU/b䳨{JRּʕ_34U_WƫT m O.n]}=ߌ1WFgnqܚ!>g _=c5|Iݼne+e b/t!9܌ N8N i~Lk>T[s=\Gw[4Zv  s|V_ߋ5_5\`\K[&PUb7>R\HmvU҅(hڄ,Rj"XD +$p2vQW 3>ahntt$s_Dl  WY~TݩwF&񪰹b*Yha{-qVsΥ_:=fW=YOz= Sؑ:*R7no=}]Q`}]L 'n RX7 {eGXG"-IEe@oܹ#=wKelM?Ju/G?7. ӒHPJ5 &GaGߟܶxjezN&Q2z֦$ [ce5D*d32 BWɗ6)slVlV+<_S*,(WvXԲq+&:O7XB EV1y^:j-YK/Vy  h[aU@t0=/*@tu/N.1snM˵@D`&c5 i+!c)nr2;;IE>>39z0?s9u5E5C?=;Bxk^=O(&JNJ&OZsCV'Z땃Y8c׮2k.rrP\nref8sps@읔ͯ:¬Sd°a´lɶdQǪkֺ¼iم˾y +0+-WuN9#D1=%V*P0Qi3\+?n3I-[3hWb1bddEq?vӰ3 8I;NCLPSнdVTV&.X]5VTObbg>`HQ[\fz||@3tRNS Y++B.Roe_jz) tEF-dĭL ;ݺب.)/8;63SL@&bzЈ nIDATWc`5se9uoR߾# Ye 3.c tĮ^wp"PCW_}+6[N.Re`(6%qUF0ݭ:2|je)POu`k'Nc`u=M˺wzdAލw^6!isd.|ݛ/Ȱۺd 7ﭸ Ώw1ؤ }5qîKC2;}ܙx6g) "-uj/ҜL J00qq 2c0d̉IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/advanced.png000066400000000000000000000010011370065651000246150ustar00rootroot00000000000000PNG  IHDRaIDAT8͒OHqƿ݊.] sv!$$ 5fZm5n`9{}ab]dVF&AhS\8ޱO'O[ٵ4$$6r%|U+^I]U bz(Il+(#$Òn `3sͷ؅`M-Ku{$q*!AP1 J&E@ЙDS/)kM_/e(0 L=n7켏uDcf]4Q`X4+^5=Yyz"ԉD"x<|>8(_.Zu d`<g>~os_+>e:PKP?A|OC-V%Gx?]oak üޟޝ׎^ႌd\{Y0*v 3; o)/ψ+vα0tIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/appearance.png000066400000000000000000000014731370065651000251640ustar00rootroot00000000000000PNG  IHDRagAMA|QIDAT8Oohu_9r;U[ AeR?Y3GjRD2& Vf%JjU`dj$lq-xn;v=0#(77c$0=Η"PwjլhyH& ~!޵/?`?6CWuҁ5KH,aH#ǃB'c>8.:\ӵy݅[&T< ]`/^$mju3Ѻ93 B&y< ..ICJdCS/27EաUk[*WU/~øq|,)S(0DggA硥mti\K] f\MG/mb2hNb:dHa;f7fOaeee2i2i2j2i2i2j;f2j7e2h2h2l2i2i2i2j3h2i2i7e4j3h4i2h;e5j5iMd2h>q>pf~e^Hn;JlFI4rb2hNb:deee;fOa2l2i2i2i2h2i2j2h2j2h2h3h7e2i2j2i;f2i2j3h2i2i7e4j4i;e5j5iMd>q>pHF~n;)>JZ헺56e\,s>||9Kx)F7Vߡ*NyiM1rq>jqxvɻy1{O,", SF2ao1"i" $-m<dXP@P| {bD1DʣˠbFg}̡v¹Q(;862_2({v[C4 ئrzc>"yDzYs24v@hyop?BA =訾jNBcj!Ss=j+<I׀/k5# 9h{Q7~5JYZ̑hEе@Z*@q lƶJyLa.= t ~U[^yWKC]a<IɟG149aj:X78_eϊOM?W?7 ,@$4rgp/$k.Zm_$E #ОiU4mݹmf"[9u͎IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/autojid.png000066400000000000000000000007651370065651000245270ustar00rootroot00000000000000PNG  IHDR(-SPLTEd{}dJQJqNZlqlZORg}lRxITdidYXqл.MZMkضXc9Y ^\;!B?$$$IG9}|{?8Ɲh~z|A@q(nwz=k[ri}h5an_>-z1tRNSw0m1efeg==kЬӧt 4014AIDATc` 22#qX XN.nc#aCW#c^>~A!fCCC3{g i," &.!in II@̐us71kP0Q :;:44uFs UIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/avcall.png000066400000000000000000000010101370065651000243120ustar00rootroot00000000000000PNG  IHDRaIDAT8c`raa+mWML%HV \\fg$iM\ H2 s-7?Jx,^')yd=АMXH ?f`@d(,|v۹dHHHnsދJ2!ą뛪lfư$8CYYV4/K115'-RV&8xU2q8ce1?K?7.<<;88?QW(CL.ϟ N@YQQ06vv>v ]S89%i`cccgPPP`faP6 zz bbu n@Ü50ܹSÆ Q@~uֵqZ^jyf͊:SNŀPիW-Y$ p-Y,fŦM潕5IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/bookmark_add.png000066400000000000000000000013241370065651000254750ustar00rootroot00000000000000PNG  IHDRaIDAT8˕ORaE[喵Zc[iǺ,Zs4sYK{YI8'8v’j%R8"1Qg3̵r~uᔚymw.*̭\~W.onTH?I~l$ g~4$9_NƛUUY_+,}m~9llU qO~2eZYP>lu!XK_3' ) &_lzNT4ۑ4@21κKq|,2j 4Mu'K"9I30̈́XC̿oq>c1kGcNl.[f.ghR K"ŢrR8=jn=(N٤($'v]bkSI )k `M PN=epEVz} z P@ %QaB2'ׯ|AR B@ @Xurz֗ۢ%X!,jũg~AF+)sdآ x4=6~N-P AuI2Cnlt䛖>OO Ѓ/m2YbN#ꄩ+wǟ 3sS{e:sJ٧^2IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/bookmarks.png000066400000000000000000000011421370065651000250460ustar00rootroot00000000000000PNG  IHDRa)IDAT8ˍOSQ{{P FbB@P&ERF7Fbt0 ɤ$ q2Fkbp@j|>9E-}~ñd H=`3Jǜj5&|P 5O&L*"^WKksW뻻K̥[`~m 7B`MgL'll9+7{3񶁁;놅,[shmc@g"{(Q _듥gc"5FJfAY_kh)O48Zh[48ݺ@klS_)POƠ|u|™C)yAns5Z wv9? ,5 ,}]]=qܘ_I/L !h p"].rDGFV*E**5C)Y<(ܡ]q#|76?>9r{BQ,/{;jǯrT*O` ;;Ep x<>s RBTT,ZO74xo.zIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/browse.png000066400000000000000000000015321370065651000243620ustar00rootroot00000000000000PNG  IHDRagAMA7IDAT8OKL\eFt mv+k H1FFuՕ$jcѕE}[7.5m&ښ6Tj tZJ h>:Gʑyky$?L)(o[M{ H>k'g8 PUI)PRJDJG_T'/uJS}dɴG}O*%`B)Tkn.$8xb%a ` J[TM"#"dM#(lh^B?*IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/cancel.png000066400000000000000000000011371370065651000243070ustar00rootroot00000000000000PNG  IHDRa&IDAT8}Ka6}Ǵ$TZY]TI EtMmm ZVTMvٮ<9c/w Q~*!2R[t*?D֏۔_$%cऀR;ߝ ʀe gk&ٕݓm%kj ϫk>S!C`C>3W2b+奁)؆2`~DoomnRMn%9qFj̰ erJo=/꘿:oN]~]u2E+]hnfm(9ޞR]ťsiWT=paf؆2`Uɳc!_`%A3`.q]4ä}wv0\Wm]+Jh43af؆2i;` MO$,zk S e|tH'-j%HXÝm36Q p^$6≄36%*lElCP\?/13vT/BiIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/changeacc.png000066400000000000000000000013531370065651000247560ustar00rootroot00000000000000PNG  IHDRagAMAܲIDAT8˽[HbD-9!&E:*+,u#Iʰh(3ECKy L˜fM捦9eڼsnf"`"i% !9 a)V-^̈eakhYrud6!ҦSy; 2ds "ϡ`Zn0DD L"q _⓱ Qt6Ml37|iuR-Kx)0U!E,69Q_N렑-1L cK3./g"V̱]NR[ozr7D):fe٠FX\y6Sec4Oau0^ڲP ,=uk~jcY=m3qdNA a70P)3X 苀h~T>36b`lhUp$ ,qb(bvDTh O Hho`y 4n,ԩVT%tuQSۋ 1ɭ>(ۗ+\TgE O.Eq:ôAHye>OR>^2CWkW1@ ed%YqoVj^38`yo.`܂ynl[0=yi"-rgqXIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/chatclear.png000066400000000000000000000001711370065651000250050ustar00rootroot00000000000000PNG  IHDR7@IDAT(cxǀ2TaeEU9.tӰ؋In oHR#pC"IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/close.png000066400000000000000000000015261370065651000241710ustar00rootroot00000000000000PNG  IHDRagAMA7 IDAT8OmSKQ~/i5;MMYy 8JbhLшzI#R!pJP()B .n.I~--ׯR` ĭ[1?$1 *<!lUD[[?>nY0a2RAkh۹3KXqy88tI"عs^Gɽ Vk"@(Zq^o9o&).<\DG g?*s_X={FǏ_nKLhkͥCC/y7n<@NN5U)P^~<d¸o;Ֆd[+*'܉()q.-zNg՚"#wH;uܕÇJr]nץ:44N8`'!@~~-**."rsBGM?DN;*/dz!"b7O? ?yk\IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/closetab.png000066400000000000000000000007721370065651000246620ustar00rootroot00000000000000PNG  IHDR(-SgAMA7PLTEJxJxJxJxJxJxJxJxJxJxJxJxJxJxJxJx4QJxJx*B"8X2N)AJxJx/JJx2O&?c 4R,E$;]Jx+D?fJxJx,F)@*A2R/J.H@gJxJx)@4U3T0LJx)@,HqJxJx'Af3PJxJxJx0K1MJxJxJx"9Y)A(?,b(DtRNS %Dj O4'խ^ }d= RΙ8N*1$+e(+nHIDATWE0E|;5P6eff|_SؑyjʢdT@*UDF3H`>IRQiWVadbtxw{tRNS@fkIDATWPDQ^4(;) 7&,j L KΗF: K 蒫*J)}xQJ4MH ܣc۳OېVIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/cm_export.png000066400000000000000000000003701370065651000250600ustar00rootroot00000000000000PNG  IHDR(-S?PLTEAAGFHL?AD%%%nUY_nx n?@CJNSY]c"tRNSeIDAT]Y0hqVEq>'$3n["'4mTX+EsY%RjZ # l'nIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/cm_import.png000066400000000000000000000004251370065651000250520ustar00rootroot00000000000000PNG  IHDR(-SNPLTEAAG?ADFHLnUY_nxm?@CJNSUY^Y]c`2ˢtRNSsIDATWeI! Im3`&2%R*hwQ)rtlvы\I{(h6X񴵿ZFC`Woy $^ُjIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/cm_invertcheck.png000066400000000000000000000007601370065651000260470ustar00rootroot00000000000000PNG  IHDR(-SPLTE !""$'(),,<42?>>IRQiWVab`rxw{Șȳﵰ۵þyEtRNSRL \IDATe0 E3p2cfff%=>YGG6B, ym0~]S1 _(_kMN5 aY)| $U+;@Q - '{L5kqfZ@zH-PhVzB?Es=WIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/cm_uncheck.png000066400000000000000000000004031370065651000251540ustar00rootroot00000000000000PNG  IHDR(-SWPLTE+tRNS@fZIDATӝ70DQ{#9ssbKz2tÊD'QJ.EYfUO0^ /sZgk.|VڥsIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/command.png000066400000000000000000000014211370065651000244740ustar00rootroot00000000000000PNG  IHDRagAMA7IDAT8O}ROHa~??ZXM :x(Xb,46$f((&a :lйCX HV6ɘN~e^x<<'۟;~F#.?evvv~ʃV#V{+l6KDV+E8Rlo+TLE|Aqra8\yU߯|zYO"z744ǡb۟666ڙ{5X:8Ngfg篊z4-Sf!;74)!)~[U@,6Դ>~az!Y^ˢXE0>2%IثWE۶Q]]DbaQTAB6 IJ@}lEߐJ%> t` BM0:^/** H{UUaӍ&[!b*Ӄ" QvFW=`YmkkY$g3Hn/2仿2 )uW|Κ*1 UUO HՕB_ӧO?Ų0,TFGg(SZJJ^e.ql~eI$⧷laq@1_DDT IzۨC::NV%Ҳqfl ]̅wW'Qa\p,(e-)*v L\Jݶ]Ρhu?@l9 >` _80 Ic@1>cO4䃟hb7H!t6VxUd@p]73G/Y֛Tooe=IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/configure_toolbars.png000066400000000000000000000017201370065651000267460ustar00rootroot00000000000000PNG  IHDRVΎWgAMA|QIDAT8˭]hU3fS&FBRMM!bb 6/>(՗ R$hWM*}ŇB*iM$띉M {;CL$43-D@=J^*V+$ukLwq|oߓ`R~evX6:TpDNB$v˲e9X\ax;at:(TC5ULjZʕ.+oэ6yq8`.$; zDE::y4t MWܬiTB$gMM){?x,&(3H;+j>^HU0|AZUҡCcoR*lۢL2 UxF0*hctd?(D<7!H4]x^@`aa %5ޱ?gpZ*eGGC˥CCLF@URHe-'(w`l{1Cqf:qK#4N Bz,Q; Wvxfr =t*ճ26Lӂq-n=bqXIR < z]]ES+}t˂RvWFÄetS1 t9<^ghDG7im^o|O t(*_ :>#5e䇷;kƖl=M!2T\ 4&eТh6 4iY6DѴ) keb>Xnsm.闟5jL=ݿjqd#ΤDVopL ?<`IpIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/crop.png000066400000000000000000000002411370065651000240200ustar00rootroot00000000000000PNG  IHDR(-S PLTEP#tRNS@fCIDAT]1J&'b}W +myPVVbx JOLaIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/default_avatar.png000066400000000000000000000050211370065651000260400ustar00rootroot00000000000000PNG  IHDR@@`U IDATh]]Ukߏww<{<hd% ILBBTT$MT$LxOÆCa[ /czn&xZ>޽9;'c0cG$m pK('QDr é ]sy=Rx%*$I`fְՍc, EDvsthrHZ"kwӶ[7!ga}H%麽&5ۥ|ſc!{>2q=leKF5hC{Ml5s_#枲9k;nXR4aDi{[UW^LknzJDZ;kk-{}NSt|bHY`>օ~Hs]g&l„E6S Zhs]vF3g?7ѡq}^w+^A_AA//_*.)(_us9>iF[,lVo-zF v ֬A4r 'PfD.`oIo~ x|y'Tjv˭[7V/]zy * 2$,U`Ƒ<̑. z8ٛ0OyZVļyy0ANKЭ7Cb[[O]#rB7Co_k2j *֯a HB)bc(/tݢs\2G*m FQzVР%[9robdjud4ɦ@W"m<ȨVաakv5k2ӠV1_ϊO:ej5bkC8c:B_lz}ktmrB[NA:\;x5׿Yw>x;g3:,7prpR$b6ʤz\eT5a6ޖr6mČ}y&XA ra;oڈL|ߊ/] <~qˉ/t9LQfˤȂp"mY4];y;ŅopC+מ)ڿ$ P}LбQ5 e+ܤAm;_q Ѯi\nމr̸;Ag4 ]iӰd&5 : +H~c䌱Cwcշ^Xٜo1uJ45Ƙ4FSRJBS>.-*Th2%Nܰ6C=N+mPA|c6k;P_Eޥ8Scj<8+,HB0zM&S:fd_m3/vu؋YC+r˭ "fp8fȊ [N>(V,}3K dJmS"4XR>|ErȕJqɕ)U^+W{irY٩ 8!bPlPts$d (u%iE`t>(=vhbr=+h3PÜ^n=Yf9eҧ4ӴXGfeU!ȪX/ֹ9kBLЮ4nh p ?0!ȔLKdJU57̠BA4im1Q!/g&e Z܋-ωZtҳ!*_"y2ekJ'J(aY8մ²T)2rdZ!ߵo J|< ho#4idY%zv1x*nGuZL0=he)LҤ$S4ӷak,>dD({▏J a[޶J`AWi=6suK 72ˆ.Os.~pKrjE.P % |3 jҝ7ތ.VaOجe[6𡒡?X4w >IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/disco.png000066400000000000000000000016061370065651000241640ustar00rootroot00000000000000PNG  IHDR(-SgAMA|QPLTEtfͼhifj͗jihx،ܐgjkia\lhWx@nhxE{gzFGx_2NOo?A@1 ŷsf:A ɼɼuL&k:`3÷Ź;a4+~s}sƺdžǺfr ^U]el uuCvutyO1b#V]sv _P%I،svzhvv`d p_AmW:p4H{8ͱwutpj[w8[EZ\71݈j\{sĹaO#elu- xtVKX\G^Z#|sùlV( [[Uw mw2yf'}snV*v}tu쥏|f*Ļ_@fD ~mP̿2 c9 kssiN7tRNS@@ @Ͽ@߿pP P ߏ1aIDATWc``ge 6|0)@lp 6> \< *Z?~riAH7^!i/]Đ\ nQ9f^r)Yו ^r q J1 AY9y *E %ejva QM - Zڝ]= :}&NtY s暃hE% ,,MafLLL:@WIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/download.png000066400000000000000000000006151370065651000246710ustar00rootroot00000000000000PNG  IHDR(-SgAMA|QPLTE..hhjj0P x)K*k0ZX[[LZcXktKYZYL=uZ$ Wd*$ <$/$#$SSqq<̽역 tRNSsdIDAT]0E.v3:BoBc,S?yolJUAF^8cqs3[Btl4y&֎1N$Q:!˪pEt"55=ö"LXIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/draw.png000066400000000000000000000006071370065651000240200ustar00rootroot00000000000000PNG  IHDR(-SPLTEc ‚1;ҝ/ɐOʓP˓Sޤ5/۪Oُ;/]D`ôv,m&ϥ۽X@YB^B`i%g}4'ǃ4Ù1HʕBȐNɑOϜJ@НYԜ]٨Pȩgإjݮlaݫsmoh˰ϩϦԧ@  tRNS  EkIDATc` 8TY8umlU|sk qV_BOLL464PC r+E-Q&:("VJpk#\(efgfgOa ED1,IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/enable-groups.png000066400000000000000000000014421370065651000256240ustar00rootroot00000000000000PNG  IHDR(-SPLTEX)x=uD}SٝXW[_fit$uJS ,y,z+|,|-}2}Bx@x'AzBzB{EzC{F{C|E~J~BK?JHKJKMLMR;NHSOPRPQRRWT[PYXZW]Y[`[`bczb}ffcefgjlkknmlnnportw~∵ހ掿䎿x=IDATMVB ݁-&-vv'vwc"vLJq|=C[}Bp@]_@uΦbyeQa_X;+(R+a\Jnp4 Pr=;>ZXR#ZrT%n`oe5a!d 0nY "E,pljqAor#k?=~X E]Tv<_.nn?ކh(NLB# k51&ZIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/events.png000066400000000000000000000007121370065651000243640ustar00rootroot00000000000000PNG  IHDRagAMA|QIDAT8˥]JQ%YBK%m"\A ]t]EЅDA+Ffa88aCDA zjr āgs3nhyE$DMH:ؠ7AԳ=nIyGaAPTf#>Jʨ. b6jHa~{CPWu1]@4=5 vF9AjX=Gۊ{)*v= 8'$3n["'4mTX+EsY%RjZ # l'nIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/eye_blue.png000066400000000000000000000012341370065651000246510ustar00rootroot00000000000000PNG  IHDRagAMA aSIDAT8cf3ĀH֨(`pΐA `iP7HIumU  62xyy؄IKKo} //Mqq񽪪ӌ u `466.zUHV_{Ł(((H988*ii LH=I@²s򗏏%Se)S{ΝywKa\򿧧7Ѐ`@t„ o|2s 3kLw ^`!n#!!3&6ypOȸ|yE&uIL_ŋ7o>P E0Q$ YH?c777:'0%pl'$&OX䙔 ]]o߁_<(-#d,--㕔IJJm@@l'2vIdPXXй nnn Mgɑ@i;qJcIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/filemanager.png000066400000000000000000000012541370065651000253340ustar00rootroot00000000000000PNG  IHDRagAMA7cIDAT8˥S1OA8l8.Ջg.~𽺃م:VOgnOVv\F` "C*!< oCm#yuF0p`?d 8@`\Bn+н }m*!I2FO suE*cF*":b%m9>Ses{`b mIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/frame.png000066400000000000000000000010401370065651000241450ustar00rootroot00000000000000PNG  IHDR'fPLTE)ooo1114;AQQ-NNNYYYdddMCc  /A @-!!! `$$$W_).',2($^ 2,1||"y435<:9;8=-x >9@/| A;DCBDGn: ONM!Tx#Ux,Vw%X|\WS9]v'fdccLf4lToBtAv9yA{|z}FM^FK]olbqslٵ΋֌ձtRNSnWIDATcedg@Ľ\L@ $0  ,{ ƄyհYDG8  t*ӱ5 p٥+i*%#&KZYPIHAOCfX  ` LGIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/geolocation.png000066400000000000000000000017211370065651000253640ustar00rootroot00000000000000PNG  IHDR(-S@PLTEfI 5JrC^eQlEgMdZfVe]boejNmep`}ew^ffe]gVxX{X|X}Y}X}hY}Y}Y}Y[ZZf]]fiiijjilyz{S;pvx{{kU(S=v*`Bg@\@hL4]US;S?S=U;T=T?T>T?T=T>,}@B{T)J(7$un)_lvI$O,lzQ}q5fOK[\\šNc^ȼ'_ʶ6aae5hiӚ2znmmnٍqpql⑘utuutwvxtw⃥{y{z{䅮z|~倣|||~|}}w~ꆨ쇨玬䋫荬菮o줽#&*:tRNS ,$/)9AHѾ5e8E-ҸB!NitjIDATc`S .s3c(PPh1v-d=ۅ-ېL9hPT gIE\Uab\@=6VYYk.HMձ@22kq%,;o;?BS U\'X]!fos=KS6U( [`d{{"H炲XXem R(-mY}c)-tЅntNi}f9L~s{=f)GokxHV@rڪzzf:sF8ΟwzH0sTHZH8Q=z%;jrrT[cbה2|Vd9S9;#ȑҋeEq,j S'N"e0XwjTak3EqXpVmVACWfț\Go65;\̢o6- Gnh=~Vir< c֯RB C"K"|rl}FK# 6:D:͝6cjU*N;khM9>#vӃAF%,Kkkka4/%lPYxV7ޘw[ߚshη)FG״ Mե w/\mi1ݖ=9vuZ,RͷtTWWV_7XgggttMVZLs+h û>䌷1,`0ut]\֛ܺlYK/-ZThk]~gf4vmP?f)',kԨ,gSG~~Gt h7+ƍ5W4~|ȑY..MvIi"!SKKKvve>l=?:L.=}pڵ\ o$g:;LNr{GG vkozzzNr\#SPu'$\Y5mZkF, ^xiimfe'v;1e+fΜ)ӧOMHNI&0+//ѓ 0>bwmk!SYWjj܋ ;Oeۣو:2 Hf4ౄ? nN:LDG͛4Ad-dP&=EEEiDaLlLNnK%%u\Y (/,س>?~~WT===_&ψHŎ;gFDEْ6MmPttHlBM-$\QV^vML(Ob >?'+++65&ΦMHu6^iJJJhY 4⦥u#f2f7+xz꩚Zzx={D7J- N:ewl@|4Pt\ Wȑ-\-!A#Gv-q*ʆa-{j#&a4Hc3BX[8_~_94;)'a̘1C، C(FMv]iQh6hxSc --=vmOVgomڟd*?4G| *^:IvAyĊV QihjjQ?zD)lLTG6{œbƖ Hwy#s4UdxCA{i&,vIkIIx~1#>3vmH3^Hv9jy%!!A\@K'q D-&9M9Z6.ir X(evҩH.ϹJ*IYXd,l @X 9Ѷְ1!{1)SH뮻^}?٪FIԤj6r@* W`MWEed+TMz̨2De9yyy.hNf{%֭&Ԅ FB2ؚe! ;Ck7p D-Vb 9IH9$jfMIWT\E$%%fuT& RB5$gszH{B)x,qMH^{)3&Լ/'̌ ؃ S˳4>2!l?  3c?&M>tww0aOٙZ{͛w0!##믿&u!T+W4hv_bg7P*IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/help.png000066400000000000000000000012001370065651000240010ustar00rootroot00000000000000PNG  IHDRaGIDAT8ˍKQ](*B73i.]!Ii+e"]di)$0C5I 3#b3יF/<̼gyϱٖgI^c[%-vqr|o 鳹mi3Α"ɁWM&f:#:c_<e},9p٠w4)c`Ф% :/|*/ d{ V,jf }<ʷ3f+TV<:͝%'oQ~3 *g(ȹ r<.c*Τ$\ )&LEDŽÖ^#-SO&>đ^)H2u Pςkl0SG#9zC'Pfb/2bt-I}ņ߂uӠ?țewH#V]aL};C AHR%Ra9CxEhx ^Z=~yESR uo}C?fTg{O{ 47rIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/history.png000066400000000000000000000015731370065651000245670ustar00rootroot00000000000000PNG  IHDRagAMA72IDAT8oTU9wa3 Sd4T0 V!"!HH4pe4qʸQƨLLm4Z6-LAus>1nSkUM_Kn/k@Â6<dB#4w86! "d!+Oπ1B>cg>\.Sa_B|- V{u\8;Qy(WLqk\??tmd$>O<ϻVJy& jk_pqnp$Ld1± CN lo?[^flW矛x+("^H ,n&y?P!mQm ~ғC$1|֡׏9 J70aA?fkUDPߏYH3'ljeiN-X]k3X"@)CrY"S%!ܫ(-ߋn0jsKҵջ(m:Z~+ N0V0TG_+҄&T͖YX.PFC Ͷ!ҩ$'ƲTR"$Tv㰲֢1#\aQX\֥YR,w VCjZ:?tH~&h<J(*zƀf䲺XV}mwD b{mJ3P(,.oSk~OYÑ B$RΧՙ{e/_. tub쁒ԽlIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/icondef.xml000066400000000000000000000373751370065651000245220ustar00rootroot00000000000000 Crystal - System (default) 1.0 Crystal System Iconset 2005-11-23 https://psi-im.org/ Jason Kim (Logo and misc. icons) Everaldo Coelho (Crystal icons) psi/appearance appearance.png psi/account account.png psi/action_direct_presence action_direct_presence.png psi/statusmsg info.png psi/addContact add.png psi/add addx.png psi/arrowUp arrow_up.png psi/arrowDown arrow_down.png psi/arrowLeft arrow_left.png psi/arrowRight arrow_right.png psi/profile changeacc.png psi/clearChat chatclear.png psi/compact ok.png psi/default_avatar default_avatar.png psi/disco disco.png psi/events events.png psi/groupChat groupchat.png psi/help help.png psi/history history.png psi/manageContact manage_contact.png psi/notification_chat_delivery_ok notification_chat_delivery_ok.png psi/notification_chat_info notification_chat_info.png psi/notification_chat_receive notification_chat_receive.png psi/notification_chat_send notification_chat_send.png psi/notification_chat_time notification_chat_time.png psi/notification_chat_delivery_ok_encrypted notification_chat_delivery_ok_encrypted.png psi/notification_chat_receive_encrypted notification_chat_receive_encrypted.png psi/notification_chat_send_encrypted notification_chat_send_encrypted.png psi/action_muc_hide action_muc_hide.png psi/action_muc_leave action_muc_leave.png psi/action_muc_show action_muc_show.png psi/action_templates action_templates.png psi/action_templates_edit action_templates_edit.png psi/shortcuts shortcuts.png psi/vCard vcard.png psi/jabber jabber.png psi/options options.png psi/roster_icon roster_icon.png psi/toolbars configure_toolbars.png psi/configure-room configure-room.png psi/keys64 keys_64.png psi/pgp pgp.png psi/gpg-yes key.png psi/gpg-no key.png psi/keyBad key_bad.png psi/keyUnknown key_unknown.png psi/playSounds play_sounds.png psi/voice play_sounds.png psi/main psimain.png psi/quit quit.png psi/register register.png psi/reload reload.png psi/stop stop.png psi/remove remove.png psi/search search.png psi/status status.png psi/sendMessage send.png psi/start-chat start-chat.png psi/cryptoYes ssl_yes.png psi/cryptoNo ssl_no.png psi/time time.png psi/www url.png psi/xml xml.png psi/logo_16 logo_16.png psi/logo_32 logo_32.png psi/logo_48 logo_48.png psi/logo_64 logo_64.png psi/logo_128 logo_128.png psi/logo psilogo.png psi/gst_logo gstreamer-logo-50.png psi/smile smile.png psi/email send.png psi/ok ok.png psi/done close.png psi/closetab closetab.png psi/cancel cancel.png psi/close close.png psi/apply ok.png psi/info info.png psi/browse browse.png psi/play play.png psi/eye eye_blue.png psi/upload upload.png psi/share_file share_file.png psi/download download.png psi/filemanager filemanager.png psi/command command.png psi/action_button_send action_button_send.png psi/autojid autojid.png psi/ignore_global_actions ignore_global_actions.png psi/roster url.png psi/plugins plugins.png psi/advanced advanced.png psi/advanced-plus advanced-plus.png psi/avcall avcall.png psi/bookmarks bookmarks.png psi/show_self self.png psi/show_offline show_offline.png psi/show_away show_away.png psi/show_hidden show_hidden.png psi/publishTune publish_tune.png psi/save filemanager.png psi/export export.png psi/import import.png psi/throbber animation throbber.png psi/text text.png psi/enable-groups enable-groups.png psi/bookmark_add bookmark_add.png psi/bookmark_remove bookmark_remove.png pep/geolocation geolocation.png pep/activities activity.png pep/mood mood.png pep/tune tune.png psi/whiteboard whiteboarding/whiteboard.png psi/saveBoard whiteboarding/save.png psi/select whiteboarding/select.png psi/translate whiteboarding/translate.png psi/rotate whiteboarding/rotate.png psi/scale whiteboarding/scale.png psi/scroll whiteboarding/scroll.png psi/erase whiteboarding/erase.png psi/drawPaths whiteboarding/draw_paths.png psi/drawLines whiteboarding/draw_lines.png psi/drawEllipses whiteboarding/draw_ellipses.png psi/drawCircles whiteboarding/draw_circles.png psi/drawRectangles whiteboarding/draw_rectangles.png psi/addText whiteboarding/add_text.png psi/addImage whiteboarding/add_image.png psi/bringForwards whiteboarding/bring_forwards.png psi/bringToFront whiteboarding/bring_to_front.png psi/sendBackwards whiteboarding/send_backwards.png psi/sendToBack whiteboarding/send_to_back.png psi/group whiteboarding/group.png psi/ungroup whiteboarding/ungroup.png psi/action_contacts_manager action_contacts_manager.png psi/cm_check cm_check.png psi/cm_invertcheck cm_invertcheck.png psi/cm_uncheck cm_uncheck.png psi/cm_export cm_export.png psi/cm_import cm_import.png psi/xmpp xmpp.svg psi/mic mic.png psi/pin pin.png psi/action_paste_and_send action_paste_and_send.png psi/mic_rec mic_rec.png psi-plus-snapshots-1.4.1456/iconsets/system/default/ignore_global_actions.png000066400000000000000000000016071370065651000274070ustar00rootroot00000000000000PNG  IHDRaNIDAT8O]S},a*RwVҋy;RbRcRK^]"T$QM^N/27stΡ+̪i}l}}NAQ2{w.ֺS! S}REYZ;PO=bmd;A4;BL:f4@+FLZ)j]@b (RL]7ԢR4Yw_#N=uRF9e()oe1-bTv .n5dS^'8^I 6){OL0>ep ^n=Rr^ ~%, -gFK9b{}!1\}ӆC˞W]L<\#2Q#Y(AS Y" `,aCV,;./bs<4Fԉ(x Ui F9G &.֜%=}aK\AW;48fNI{îCFlݙ2߼m^HL@i0OyK9,FUR${ lހ*^7AޒXw"q{Xvx ?e! 9‚}!W hw(c4!&qπ @5kO`!-xLqmbHQ4"XUVh0=qmnܷ38kn ˋGϤiwWw%b:-fZ7zAIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/import.png000066400000000000000000000004251370065651000243730ustar00rootroot00000000000000PNG  IHDR(-SNPLTEAAG?ADFHLnUY_nxm?@CJNSUY^Y]c`2ˢtRNSsIDATWeI! Im3`&2%R*hwQ)rtlvы\I{(h6X񴵿ZFC`Woy $^ُjIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/info.png000066400000000000000000000013771370065651000240230ustar00rootroot00000000000000PNG  IHDR(-SgAMA7PLTEr )*2&c&dr 9@$^%ar |?F#Z$]r  "/7%`%ar%-%a$bNWr  aEK[dfK |fR i%j(.fqrrw{!U"Y"`wKPz02o59:=x=@EHIKxLs~W][ƕ/HI6`UREL_!tRNS ```````v:IDATc`^o!n`6Դr5d”ԛ[b嘀2!JJ-.JJJq |fz@A4"r{OJbxZIP$**f@*v)*ڀR=uu PA6 <@t?OE&**ց4)Cee7{e1KY}CMf h'1F-u9@lh08,fv7IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/jabber.png000066400000000000000000000013321370065651000243040ustar00rootroot00000000000000PNG  IHDR(-SPLTE߃H'ߑ ܔ|ݎ:|t |CsgJ=l##X0U*<L(~.B_f_ k_+%բO0εrfzvkfmuZSh/GSLLUf7ؘSy݅ʇ5IՠȽT$ !ӉqEM*0e˟."IKDDq$$A 6 ",T/u(hTT//R+B=ۘ&21@ = +5B'i65""tRNS`@`0 Ͽp``0@@ ߏ ϟeE_IDATc```ddge`ddF܄zT_qlqY3X˭?ŕ ,m SV p+()TVjm ٙ;8:9 @(-$ s#\@<"2*:F! YRZV^!ILJNE0YX2" +tt*B~&v'IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/key.png000066400000000000000000000006151370065651000236520ustar00rootroot00000000000000PNG  IHDR7gAMA7DIDAT(c`@^9`̀ 4mԩcPbQ<CAJ(j/ *0I1n4n%9r\|ωoWACtO# E^wOY(PwMy ?qZq'@_>#c^<v=/Oo9ǣ(v,[j٣wVG3w<iG/q?gL [9Os= cۥXa"Bn(na L&xIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/key_bad.png000066400000000000000000000014171370065651000244610ustar00rootroot00000000000000PNG  IHDRagAMA7IDAT8ˍ[Ha-\knS\P@q"JhAf3--S#RRГ=hC"fIy$Z^ҰNO߿,]ցp|H$VTT4d6F@#\.7bLKһ 'gWTT|D222m s:5<F=jv%@.Z4=蘭v3)udPC6^ct8Յ6 G\(/F +,(myf*n%Ϳ{`ZĞb*Ncf[lv&l5'z}ͷ[u81Џ8s2 W+pLm,& iS,!m2~: } Jc_q!>%2u 8e#YQ_WJͰWv'.|g؍V Bo絋{pQ#@+)MiH7&n_d(.,ȋ\Np5w2.M'˅MMM9NrYv.19'cg!L/,eR6Tjy<&_S\0yj'VK U#l }dNJGѠzh. h4iDfڕ*bqEQD%l0DDDD'j $ IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/key_unknown.png000066400000000000000000000013461370065651000254330ustar00rootroot00000000000000PNG  IHDRagAMA7IDAT8ˍ_HQۜ2a8ö%З0 h)(K( 1Bz!h8dC$[JN0bTɦYC{LP\Zl6OfٗUuvGNN ϓ⛽r8RNR1: ddd\;\d'g aHܥ Z,WlL+/uȤV2VT`}>,..cd._x!}]ko "af[Ph_ T\EY4xCx-M2ֵV U elU}=2ɕ) S0 H덍 P%%wHW Y^1*|cV[#m$L"qefFD?oB9IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/keys_64.png000066400000000000000000000341371370065651000243540ustar00rootroot00000000000000PNG  IHDR@@ vpAg@@`8IDATxuXi(g-uKA$$CP)il챻 ֺ}㸿o}q}:3~^kɬzz571q cbUUp8˜ƨ%<&xNȜ]U3/ÁH&yff2V#?BzM]2/{+@8x1%gtɤF#nga;ȮιxKyYןDlZVKۑV2}:aC C+Bhm}L8|6ӑ00n#s_\1W˜!a gBr*aL %!:W;F1['^ pD(L k(k9FlbRɺXiwp e[㲎0汛00?/3ݜ0&0kAc؎UdAHBx9F3H15$r#bsQdoAlbD|&VSxp2Ò>" SȵH~.|l=a<‘s ܟ``/a.86?q#hƤ{.A-5"֍ ˜Q aTYgg DBf&z_Kٴv E==z@q3?Zȫm~FBR8BۼG2foij͝dhoYqTc.؃?A%fpFftJrRvvv?%m%,"pClՈ@4"0k,JDlb}))1ёEiDuQ"n+1v+ِCQ#oDhm::$08'R>x5yzpSshG65V:8 DEEOp6H!JU"0l,H-$9 ;40ʇkg0W.<!o ^G"r )mI=dAy-fك8t.κ-N9[0nyR*'سr62[UN-em? 'q#m>zpwxΧ>{ r :\}1;i%Ƹ2.Ye[嶮v"g[p'6;nVj͘X턱qr c UG cVM yA+*D+ƄBYT9B _|,WV$O81h߯5SMpǍn\†O9зc;2n7%+{k[i/1]Ve4^d+>O~3f.[lG:[5c-L- `!j!h1=v߯!d}QU2!؎0v0I;>CX†]#PIpc3Z>0rfJ 7FlÉq1]6z0Ϟag a%HhX|H6"OGXRF>2GXMtg H |+6S&FwD}CJ# +'p5'PAnIդdur*%Swc'6a}53/]ϏF"![G0fdZal*h&k /0!>coa*<"YǔcQO8 kl&zT!:<kR4x`лDrwc|sX[\ㆪ%M' )vⷺ.))gk]y[>4Y⋁mHdWXN`|#Gx^P&ޫ_J2N i"Hd:?a Z6W]1$1ɤJ:uP0f"J^vQ;͖GE.܃/@4빬0Grȯon\~S5J TtӼ.~_Q%8I1i2dݬ**ߪe+gke:B3G3MFVh5H"-{ɀ)o+ub /;:ivw~f_?c;S1ع6g34b5Z`k[w^.iH|7J|&WĒpnvp2aKƚ|ȟp&PS x>B0hn=#\E})˜u25 +EEe.L{nZw;)sGR|wWG5y8us=?rD%sS_l t0ąxufZA!7ko6=iztFoOso|7!JY =v?Ef3n^?VG k@[P[V;BUo+P0` 6@ՄBռ#*-E3?pCV d'&j^ xBW=~9aKt9;ݦ"nNeyk s7/EmُC<鹲mg~qvg1#oYs:O~oNu)Sݭ];){R f~(! OWn$neܰXCYo6qqɕF^å4pٿ;Dy܇m5mm=[Zp!8\+]M!]@Iaޗ0 UOc!NZ 3|a=G AvCfO3O`ypйFg}f(d@nos!nlKDO;N ---ؓf `3U)ڼȕidU^`u**-4icF6bc_ VVc~tt/ >g؜qB.%}iϚDc-n}L| [| Z0vȕ0NOϯF?V`#0zwc4.|x՞'+"3qQp@O)ԡB:wZPך֊;aG i @Oy(% C$ȓ J@44tQMBFOƩ]}u6lYq -Z@S<0_" f"X 6 Ngd k @ :[ c]א\Zc2$ g3Vrπ]Q6cC Jl0Zdѝ;<|d]|!5iArƅX?3:n5M{NNrƓX[ @MC0p\)~Hv/cCg.:u;y̲)2OfitN ;UkB Q|Nq!vkuc.\Y` @x)Xu0vF [|]LQ}R7)HOb̫##flRи JJ5М&m.ÒԄ= :_J_L 8'D?OOOSHoع9'O>n uǵuAiӖqyO0.&\T0`@H |f&[ eN.#O&~"|(,]L%XHlO\7@ـQ=%KR^&m1cF oG4ixPU}>8vmx|uXUfvFF6խVP-r=B0JZ0`+P$(# ĉ`}DXmAmm:N3;`GC+~!`Dr 8CIϩ=NJ9apgr |yHn |s)稝 /(IMp] a-W{wۤ?c,]f~{Foa/22 t=^L0\xA .A0` 1K`i&ث .L"ޮDpgq[6_C+w j/XE+'E @Yz`^\Bf-wl\bBegI\0,[oD0s@ظ.8].+ C .Sg/a@yHro%X8 B`'7 TDŽ_WOp&yJg?'q &:>c)J {< xo{?Qˏ/+d*Ot] vі:]g{Nn +*+~ƒqp780.P@m$8w W?Kt -Dp!,qK>#4-`O!BW[¯E@q˂s{ : V;|&)/1@\g*cL3i^DtM]-W*]vaFWKnFlq#۲|8W̭h!رX4.tYAUA.%ž'G߷w`)Lp({OpH%gC !oa;{p>7V|Bxˆ|\K=6ƪwI CqW&!5*pLmb *"2#/5&=~]᪂>̤aE??גWܩ)VRJ0 fc@0sepL0 f=JI2o.Ed>~ Ɖ"xIh2imR c-;\:\Y]6ЌZeF+|MT@0q+^!2#F@ oIP?*̣]Ȱ76l-R=7X+X!pCl*V!K 9`'|<`oCpᇒ )%o)ܦ"Xu3J|YoYv0VpG0>UXc UgfZgūLJ[:e;|(N[LiD߽X0V|p tcَq1V.Hu9~*6+Қʢ,5Y1Ǵ/v~xwR9n7q!# ƒqwGjk6vHƅ3D@fu 3#w pc4I00\#?X! ȘI^s*90VWBw|_f8w3bXb F$ԿISKÌh^̾kEz9@|,+0[<JR;HJ?+۪R]1{$iUhŒho5,Ign+/^2S p`O$j hxk g\]E y0VJ8Q<~C?RYTܹs.s{wF w9{lvGz)c&\W6M[)Î&e Z;i1KnR|]\zbDž؉;,y.3s_sa|/}w@V)N$+ V(n"{/FN=q`{H0!< `F;}| lWtܻ0+q+nۜ id9Po/w<ɘHZ Z0{xtW\;a , /C=OX N=tOh.}xO9]鸛t [B.RSl2g''+<`%MeܾZHA߃dKdDs5myG/b[MF#QQXJOEA* ]e wkwZ VŒVٕ Ka+z׮:b0Wc \disжClC/?(I@fAIW\Kk/pS8]qt;$`8DvA' 4=܂. $$R3R;%tF3vV%`#gʅ 'ya RX:>Gg즷?1d?xo-}&*Vm߉9X0ؽdC܏EMaW8 vx9s{ھu}/_p`|@҅`+$ k}& @'`0'g |h%ߓp%/?A%纾 'On?-ҋ}}s76|4i`J$y? F&8ì"ܾ.o+an<&;Cc'c=9ۀ}#1,3Q;9bJVIAۃ;?ݶnԴ\,on{2qN<[-a/&* a BxsX-w iƽ;7N{/??\?Jٿ^QR^j65-j-'/[[TZ4`8N@ XIc#0!X c]i`:naF0v†#hcdc8zCl=NKt#qa/)䡳8.8$h((.)vi]jEnK/׀-: ~L.CilO ^y|oı+_ 67" pMҾ"-~3\@EW$N|"@m77^D1©#xc| }ު \6LI,OjXLO'V2vsu7j>mJ8T$4yS/>:5lڄy6\ jkLזU޾v#qZW\,y|>-nZ`#ÒNJҭ9L4ü7g12K2,*`$Qo_gHr7}چT @Zp ؊\ \w w߼Xhƚpt+`E:%%+R5<<su_dEgYÉfct,;q}=y8-3LM+ 67oG%@`]f9Zʚ㐙4_xHCT"N}2ǿ()i)h@@ w(ds!Hp4є`wlI@5]+7;Ô0pݒ cUwю^F9[` LK.>*p?c) k#L.Ek#.a5.Bq2$OVp#4^zTGkmuRk{˭wȿi/t`/!BFm(&A0 |q"c@9݈O 8 46L[]FWi; B8ȋ50ЬXjp&&pqz4c W>El/f?f<S2ƭYE@F7@tC#qN4NRY*`1ks{^Jaitn`Ì;C˹Q0@IyraGq}80v,ud 7/,N ~Uq,a > ܾ2k0vp^!슮+}S6$Βi9/a@,7U/cK͑*e`~Q|7e579b̘+bOYRݨԝ;xOL{ "rws@colN=9|f%a |p'];,5%â; 8vZA!t c5k_(?xZإVUzNOz0cmco+O( S s{( b!"VC}jzg\wz]!;+(CHyة}dѦ:XDK`]gC0GA lwΌK=kbu Ѭ/.nF&/P'Wc"-"4T=ޝ_ㅉ r+Wel1zHn},PKS^7X;Ш7#8"Ƴr'I)3Q$"  `+" op`yV`N4  #|R~U6;ِoKP+8~# Ǥ-v X:ޖ'c$xmmg)'bpY};UQ@nUYث{¥D %hD){0[@ȚݸjR/O =`-#}תv8“PL0 D9 L)  8 ^oj7gj¶&K QLةuajE8 ˮzIâ9۞>o얓<f xsV; ~kQ7M=sMWx1r;icSm񪳌ϳU:2Z )ty\MF8&[ 1Y`R #xqGpධBo;38$?bQCImn]yעEfGg i|EŝyIrՕϕM &`׬}E cgMc\jD#&uޫ Pt od Mqbx;[I?тܭs.X-\;Yf,|mXoL5X#NRƪN1t qY/l}pjl~/k%0=Nm++8.pwp1b/3AzѾFJ;S&ʟb,֫^v1.TK cԅϫa.JJ@c^\F87ge`tD_`% %n?)޲P_aCk"| &;ANfby,q}ps1nwromON/mGymS?M3⟎6XPGY=sZmpI{o%ܯ:rL;m[+l~-oM'_W.䊼\=vOSTT e;j{N%?c[xK:B֎ X"&emHO1*rĭCdFCަ ѦZ |/SPޗ;z;wa8Hxc7Mycr#S+ Ά.٦8<+f ^#"RI{ n_Ȗ,ЪGI,Ϲ0֕I;o,%}Iɟ0|mˌV>lddM1f fpHH_jt(Zy?Oh- s$nz;WXNgҢkIT$W$a,1:@TgNq\9EhK{~O Wa˪D(-? G@Ⱥ@ׯzY` I&]DNJވdccW ùkǟpr]nac/TQwO+eoi<mӟ`~_նWOm3{C~R=Kr{q`4_=c\J2V _/JyW}h>u._E4׌r=c]6 gcH~\G^?(<bc54v(N ᙷr L ! p&gKٷI%La[[ cMȟxrKaLYB嗉ثO~Z% 4TÆ%Rc,ugw ˜XF..-!3}UeJjtf%(Fv>xA0pK k@Z_(R>=T^}&J"M>`Ef.q$p(F}EnVV[%_J?^s+v|,ie͘ s8Ss:׵Y)vVF/&8|c # 'h;#En|K{. %O0V@Xra,?A{M3<IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/logo_128.png000066400000000000000000000345231370065651000244210ustar00rootroot00000000000000PNG  IHDR>a9IDATxw|yݫv)RFP]Xn)J8؎Kf;qŎe'8.)"%Q)6ˡn{%k8y>OD~o`sA)ߦ 7pu{߂֓^0X9 )%!zf=|%gN#dܪ5[f!g=k9raze4箽"gee!)i5U=k^K|*7m }OY[MsYZ:K.PAd*믇/|nsjkNkC. / ٲ0{þ lhcc'[[Ekr yS~]j /TeC,jSKK͛up F< |0222eI@5i6y) 4sL@M⍱W45ƭ[Ñ;}I!~%Ž{Yu @pNiL h0B Zz…[V=pm=]X{/˿@`_ oK!~ 0MHoIo߹󚷥RDW9T,ol2id X p0P{".ᚏ|w7ommo]@jh+͇rs/|LBM P__qѪU;BFj9ۊwxb-MCM6sChtħFˮZ>Q.r K|コDu4u>*]OܿfP``kέf͓эY%KnK `Xkoȑ?~#@K aM97y`J<JL3.oHO|0lTU]wSu5VoK~xs󝟚?@*8`؎"gk;ߣN4.~כ* Qt@dBM! G@;mm# Flo<>:S?}4E^]}},2/)>iohW^pfE\s*%@Tqʞ<) |* uu{W^y~~񏣾-Mskj]yXW|0/3Џ949G-h b2|9\ :)` .xx/_0q8ԟ+~gG:yTWOk4kj6mz]U=x?@!FeEAIՊkoE>pq178??Kp6(~@6aTD<{Xy0\ 46 +pmJ 0y_ pEh̯PZgJÿT$SA8ٚєbFEè0s{ d@A:D 7߲lÃق ^=a@8RrY rpfgU0Cc)ع8U_~; 7\VWOY[s42Xz"X{a"p pўLNw)"DjE3h2"@Vn<^׬V-lU,[g&9(8&Qx3Fw-kUU__Sۤߤ$+dcETsׁ݁!Qp=I($-!d#L+, 1)d,l'ˇ@.?lOY'p2k5 PfGJxy] #LKjNwN}XG i#5#;?|Sé|{q& #J ؝^pI-;ufFR㣃T90|k v<՟[7U˖9>iCz@>^qFj'ݤu~VNFDzl{TV.BuʭѨ8hSJ*ZUUͪ| ~_fK a x]O֏+B\R+h4uSqؽXa}@K0$.8 KB/k#HpLu8Ǒ׶_8s(!!hVY5WR h\R^A)/\Xlv?ÄÂ#Qꙮ)"'towr8koJ*E[-rSXDhkќ*|;.q!ih !`1M<wZUf8Zs6WިRny។ g5β4oG~OE)l9_- xb7{YktxRs4+(,i>9Q (oIޗ$g%h}Z W[o҉~ͦiu}vd[^_)MCL Ȫ 7eFUvY:~~+nZ`O; ۔ǻy|yj5H|SO~Oj&@rPZDGk~6?V Gቇ$Zr$. 'BR'~q͖k, g1!»d !)U/ L(DxBWz?I6oV4/_ (&:6RAL._?qG@B͒Z^zS,4/ZXw´@Na ?/;Z A00Lc.7Yd"HTN gK *壆䣭U*'vGM_XvJz9z>.^wxoCøvI^vF)k.Lu8Io0qؿ4AGJ/ɟrbB5LBAlF t52R3tWSw"M-XrpfdC)54(Ь[ndej8@涍ZdAbb5tu 39yKÿbF lY`pj_ B1M,bʵZ'LNޗ%Bg1lk^PX֦m`+i֔ɘbXWi {~W^֯n˂\~C?xDh25:F%AGslR!˝)ay0n^ i."5Դ}/N"-Vͦ :|p9Z?[XZui%q/gճA} "ZTB%x ]Jέk,Z1O A!t@>M}-1)0C!p 5Du9./=od:%OupNE#Qߩ K0FΤ!eVT,YQS[W{`|48K@AT1uڝFK&Ph6ekY rsJuMhB@$6Sd;RضR`CIپ]8y-ĸä́SuijX?? - s"+~W(J Oi1 lZ禆L&Hưa$ U OY'@IKkwP8JL񕾾A*1!xm"e1@!H܃K2p -vSEBhwoz/tH?zpJ7І10jjL պg)߃@&ALmebTψlsAwL]P<*<ǞR/MZܴHQۤg0`[22  vdIpW^Gb 6FJvrlw2@. A\p"w^,@{CvQ#Rs83g_ɖ uTT\~zPʬG//#3V{femhPD+f6uo<*z]vhIIJZqϒm[xqCE]ooG\^U \⺢|/CLLG{J?5xVB#hwB?}Oб _޿PSDUPlrv:OA:v8m=#ٌNx\ZKG~9;Q&Wt`mZQlCCFy"8f&#k7b<>ƴ[LNH4zZ4**\w?KX ,rM~ϱHiBA3HVg<=g/GI2A ^>0zJ3'P?!B薷Z!hJi vvBmt5v3P?8rcDbZ^#ZoP~id')*`pB@EGwfGu :Mxض/pXE.k;+sЄ&N7f9%N P]K`S@GÑ>CvpMgCo&92>ɥwYH .СZ"s2 G;Uw:`;u.3C7k)[L˚² Z2~Ci L~i†bjߨ  `F-ꩨmBc_mcF4R @)M6Hz%]:m{:l %L恃%ԕA% 4-p|4y[{kMDg2MI83&Yi, EN!)<d0LC1s_B=*+Foġa&C)@Jr ЦomC&vrnw/7SB4Ե{wtt?vuItk&FJ&'hV腋d~6/"Q0xPO&ϖ4cppj8ngt#]!>6%<{-D.D@|}Pcg$qfׁLv/w4y5Zؼ#J;&|f u@C*>pdWOk`Y \*4.|[< %,ʦjA$X  d/an3@4"nZlPUfxM&$4x*9Ϸ%9v߲j4/}s~N?8&p"ŋOGX.SW,zE0g2+)FF~tsw e]BJCUYC 3.qmpa%6õcP_3Ԭ`HJ;}36& 'a.͆ϼŊ;dr7S!|u)zeq 9xà*DդCt22 vǔs3.î~,V($òN`t@Lz|#MɼEhny|ư06]k⡽%|gX\N2pܞo%k 2;"H?ϗt^(@UH"x>M1P6I߰Y04fS,o48W ҙ灐S΄ jٴh275KyK4ZL͑ +R ,~i]^1*bϺ!Օ.Y--hg:_-HА_rMw65H y(f2XqYMeLktQ!8,_d5.rIjbEׯFLte* .;# A4,mwAݢŖ /~%9nGX=Sk%!m'##w^<'p,>l@:oP2hX /@c҄ynK.GWHcՖZMM8!ˤ@=&;4~ cYʕm˖YXB-[]cɰOMB-}ή=9+[\M]~1*=;ܥ fY2$ )pD-غZа26/Tf*!Izr>_Wnݲ׮Z\|[nxCπm[ӍFZCuL3H&{W>)96lQF颀˟bmͥ-uJO<׫>4V##O``W$DwR)A_6T6tPDӼPcM'6i?[rmn<+78e=z= r; ՛xŧLFwޱ{'^@k83xF52>!|ͼ.]iT*ゆ)c#v\nr}N~D *M^|ڠebbM8\d+S~ZIB&/=m24\^&>{*ѣi`Q)CW_݉ty׽H_P'T  I<* + QFSp5M&pɪ^J)a^AHJ~W^$Og0XcLi#_W?]&@J)q`4)Ka-IFtLV;1) gH5C.}.=-,yQ,4y7+/Hzz'`&~ɓZt5'3z֠B'dlNahxwmNgy?#bV˷)~Mc٤=DGpO?{g,~ɓKi> ᶶN( v4YUN4 )n8..wVŠ:f̣&EoWr_ZDC%a9'SJPeYMW_g6]8_wLcwOeMH{ZRS%9"ya}d ;?t@i4TޙpVUW^m۽gD{l٣oe"grOuFI ^xdKᡱԞ_yy7$@`G;N+|^o͇@ X7_|?V"y%=Cs}MqI]ij4y9I_yݟ|||$el)i3N`"@7lq_0.rCq}LTt d_]%p ^~ΠL>wxym%B{4kjySYYa_޶q~CkB{\~2# ۙ; t4 TWJbȤ$}YߋN஧~؋%Z_̡Pt V /uM˶y,XPnNb҃!  *c<@+G ;0;:;S%Z?K(c % njڲvӦϿ\8A1Q[b2o0 29]i!Xg baAE_,f>rȠ`R?oz{]"%AB ~v-P +AU?DjX[֮еcqXJy-tGrEMmu oTQg(cmz,hRǁԸ`O%胑a-8tumdŤRS.Q<%UEz, (AT@*p(T]l-{Mp8AMf|ŲE{qoML%Jä 9 J G BOGG{CݒEE/06\T&BO,P8` !.ay65]RXtU : vJüZKO< vt ?;8dw2`>?lLzqITv'8v_)L B+ Ge@e咺n{kk.7BEaA8OsoǎOdЋ4-|Qi&Wn;v)Dt`^,uqlÞgx&SP r \_7&YI|:e+DC|?qs\n4q?xi5[4M͐hQd+$DÂ|ֿ㌧'] B r5KpگvJzjƔ1!Bax=v֬yNJʊȵ+4ciul$ +/L U\!֬yx+5u~SgM5RP[U`WKB>rq?q,.)ظRBs3BiRedT&8 *6JHO p2e< ^77]t8ٶ_~_`rENi:^_+]|rUƴVaP R"t^|`r%(;p@1G@3~8FP Ur.~?s(V L $pSEGzY`v0T bqN spAo ?7N3rln_:g0w al1Jé\YP>qv7IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/logo_16.png000066400000000000000000000015271370065651000243330ustar00rootroot00000000000000PNG  IHDRaIDAT8˅},qww,wH\?V899Bz;ETQMR8Dz@DڲVG w~g\~ԃ b̘8 p in;t;0tWP:xIO\lN<8$cyE޽{JU 0Y߀@-)ԁlMrWK:y@7#z*! ʠs`WbW}oj=\.O x:YNVl곜W E( TKo}iס{v;m!"rmm1"?y܇;kaux z@pǾڗC8.ߦ~W0/AYfPp\'M2SF4%A5A@ywM?~ 1&3D5Qew26WUu9BߜyQpբX]|ΠAnڙfˋs!HXqX I* #u]4c:ObBvn ?cT7'0d@fEyX]֪ǕzxBD<~[Lj6ϪojbLzßss+`20am fix<188A G *qju IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/logo_32.png000066400000000000000000000044431370065651000243310ustar00rootroot00000000000000PNG  IHDR szzIDATXíypUwy%B[Ѩ,S[8v\;S킠ւ2RQ)TE-%l~_HP̜99)%.q"1 A\ q@$py5 i)Q&`;>_5sȗFV4qT5p%P*e,PGv&KsٶºW0HMu`X5*\ua`r _yʮpX>dEyA`x< +>: FKtK! yw|V=4g/C_?#W?] R1yh0"iz &A杵pN=bDtG=?ߜ<KxQUM6p@1h+~7!idDHlǎx-\뢏O/{GKGgOo `@' L0;,Ɓ}~չm=չbnm^W7oy)G[y_8`5+#@Da_im;T.{û-ܸuŲ67?Qٞ Tî~*pbE%2(^ˁ_ޱLݞzKn+RglE@ROy \  *RD9]b+ho힁43n5eiT곛N}Nͺ.6M˨o ܢa>MU}h\bfT6CťֵBGAJvԍ 6t\Y ,;9#4ep#۷o:K2t\N;޸ W}h1*yaZ8t*awP0T7M5e5B6T8"='zԤ8 ]**ݍ~R`$L<&1Ց#p:UښKmwȍuhhzSq:g jL\5JOC{r~76_|z7VSuUe{CuՆ#Skr<> (AOs~מ2쾜g*1-+;A_ [:>8L Xn4BL)1z{¨(LUG-i̚1ofd C!͎Uu_gtnMLCRs \庶 kgiZB]y͉ܫ(\N)PBb_vot:0ؿ/+纡7f& 'ٴ'w뉠;@n*gӈ5pP6З~7ZwJ\l4>oY]ovpt_r@Q[ox虻aAtjD‚HX:ᰍe bbLtgn|l? 4FN{ߛ0/3hE6( \T5t4H oz%_,dg@P,9o9['&{$l 3 fr6Tqc+ܵ~w(T (4Pb'< ,zp|PpgkWo(pk[u Vui rE%3gҝG,. ȝR[!} xJwD 4+k.f}Ӧb❤&""aUT@w-=An=nvIƍ|ܶ L4*++Om򻧄./UU/u(Dֳ:^C{ IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/logo_48.png000066400000000000000000000103041370065651000243310ustar00rootroot00000000000000PNG  IHDR00WIDAThřyxU?Y~/$%aAEfQV3S>3Xǩva tQ;"V\[lmHHB&w=?Aъ%yI{{PJq T$*F{|`0*4I|PQ0L-;,]JRwUq?ٹoI>9aoWO;Pj`PxaU+{Um}f(| ! )E.R*%nuK n*Eʫ<+WS3WJ;psZ3HsYdp/~?訟=訇w9[nC[/$6f]wYR'N(nݞ lY}ywWRQ[uتgV#6|"mPAz1vNĖ/-}R>LE L€EL$d(ݳϷ+10Eayvm0zj_M{zhro_=b{Fvs#!Wdq`o- >q+rlx~I]/|ȑ'a /\PQ VEТ/7aY Q ɔgOG5./ww?o3k~:n<2ӘJC(L`yAEMω}{ &tC^ =~Au0%c+W4? ':hF͌5+c)5wphVE{[+ur`(e%F`t-%U{(Y$t.KdDu帿ضq+<#M \Je`_x/ Fe=/< v-qG6ug`hͮ')?B^]USTuG¢ @j 6u:@ۣC|6P+b %2bQB*楒|4/_kugqSӸUxͣ"/,I R-|N8 iښd d%|" wO `ؤ Q M .|4+ôH0kbf \ N |}nWӏnބ:Ji(u=Wm06J@4v>o|q|* -._l) JEģr/^ Tl[}lK?x yVkƛ KW9.U9~UcӾ מ ,QX4Y"ci|)Gryщ^q7 2rյȺ "\R+䔳!7ߎ R@,$DR14#Pp駼T&C@sMȒN+\W! E_NS.o9SàX,Klft$768) [_=bSSW aqqu!H>D@T X8oQAn8Yp\RzLQt MNBSB~?8w Ω_c@Wwg@Gl(@$*Yp"'@AYh~4ͲpQ؎!=ִ |ʈ?흛u%Yhucpb~㦝uurc =r]Y0f*tF ؘIA8Q)4= Dž Vz, 9)PoϾ[~igZ{Q ;usoQ.ԞFֵE2S3cn@$m(.AG}<0dq ٬r=M%XiH%H;O|'ppLoΪ9_+FGL (j끔]H8dEy\c0_{\Ӑ2D4oHARH$]NeiLj(92G4 3TV&qF.KOX'08uL:oUtlVʕYGPʲ\ Y!XGHAtRұh8֞=MKEOS3GX^N&dcӴvZtir9<| SK>iz\F fi;qhˀ`S4O!_SWIwHf\cA&`1D#3UͬXO! 8H:U!]Hh)g>?KEGddm#:aLC+/\|g|@tF{cj (.&g^/`8Wd ڞn6 +\]6EC 0t0tߧSOZ"lThRk]pOOvQ>mĶ(.F(Ф@i4f =*kU/M\_A]&hTfo0*$>vH/ZS:I#04.FMOlWuyƛ@!e l]M pXˀ}0l۝ ^8iFd_ {MSaߤoJ9-[|a j!@I0M ES7hd5j,4&>MI4)8ٹY1.DV_0{iy K04 LC#/;\"KPVT.8}A O(CR` Y"6:k9Y@lt v:Gȁ(P'a?m;:O/ށR ! >f.sѤ@)0} k8LWFr]ͭ篴D"7j G߶pc%/R$vF BQXqko󻺺{kM!y&xo`;N}/^1T2N\|>#'5Pg:9hiNq-R$Y#2凩k;۾'o5^;[~&il;t`DA8$t ׂ"Z gn;]@ 9D+3$3q2 \pK(Fmw~[g;o 9|gZ҄J |#iCSGSK@  v }ݏx:{(}F:(/W}$?h T#7^U$7#gzw{[|[?/t`Bqe}?o^w͂IS!K*mxZ73х$Xj GW5}b]qO woSiE>V\'TόM22*>]ohރv4}epH8P TCzϮʷ BS}lƧT$z q.mܯB9s3uU}}J%?ҝJ7s޶o( ?+~ \C6ktDe!꼤U&;31{uwwT@2 R4 Gݸ񧛁L1p_=Cį.c(\0 Aoo::@8\NNg:Ns?R!t]5X8?xp:}1Ty<:]璜؄U%VIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/logo_64.png000066400000000000000000000141201370065651000243270ustar00rootroot00000000000000PNG  IHDR@@iqIDATxݛy]U?sϝﭹR2'90$ >hyح>E_Ӎ% J~(4Ha $2O5RJj|8RCB ˾k}׽{a )%#H Bx8~=8yR2S>4mH& :~{ŸEӎi@+广yA^NvM qe9rAK4MxϚA B(TՃ B@}kI7K)aHysW/=n~];JM@Q(J?P{ßRVK8:ܺוXY`瞓2򩧤\r :ࢪӞ9 JH&M_w4`vMY'.ဦæ6ž /55^3e p #ˮX7Qɩ'cmw!Y}ɒO ywy#@Qh=a`֜ٵc&zܴ/=n賬f'?ٍ|ҋýN[`|̃۷tSPRr՚@ DB3OVM'*/\7$P%vLA`"?›o^~w˧l/\w>^4~ͯ>rL`0mnxRꋁ;pM@X,+,WMr0 w{OԷz>!jF_?|Ga`d͚y @wsWZ'maˀ; 48/\$-^#``{3.դ_q:$ZB2sVE}}:\=q%%eU$W['pȟhC`+% 8)$ u$ -_^vk% pŵꚚ6fˑǻRrH*&UOVD6{s`"tf>0~TH}E53Sg a(45h4շf +-+_>޾ٲZ7eudS9EQ-B*ѵ?;04owRyKmXIǀ5@/$n/A654x\ǂ|n?{ DfJ^d2UM+jPTG" Bs\.]&U;1Aa v }4(L} eX1X|pv9)}וH$)}2嚲NG֑BrCy\7Lp`a0rP?G}":T5C~bуǀtuiY.)F;?!ơY~ؗQpX0 ;P7P]<v=Nu`+FMq@J&˅e&Y$K4`!ٮt;hY=dZ)>7)` aJ6+{A_w'Р(IB7Hxs꺙IYw%q6W&M󖸫,w+f/LOVu ;~]ڷBpGe}w@۾w}O~/ d>S( zbx)Hwʒ³j, Ca(ݰٛmth$%mŏr4d ʈд% *'%.ec)SH}yj2M$3Z! >rWnR$"|+o~!ݽUCOUT$} F̡ݗ㴃|ɌyDFhCݭ` OQ*cᨪut<srʳ!p77< _ZgwE2WBk#;J:MM8|ݜ3F+ zp|Ƕwœ*BuqDDH9+=q)@2wm;]f*X\9}eCil>и,~iLJNfi!ɤtrП|v-JIep=pM$dSu} cN|9NuNcP(Mq77i i/4]^RicZ\ ǂttn]ӈ'kg<.G:.r.m.A t 8/ؾ#: l^-.K?:Q EаGCߋ4=0^|oWH$Iu?@ 0HdFuLbؒt֥עZ isȥMە+_cFo)0eU4G-Xt fK&-h/walJI骵YކH,S Q?d؇'_W5 t5vf$}{Cff6}6]}6=6YΖ =;[͗1Phim$pP@UAT*Af|3>ǯ6kQDlZ鄊KJTesu3ٓ@`:uSm4 T +7+v H& ]+[Y^-Ճ TR]P(lmc(JE 8ir-NLc6 ' B7ʉ= skoY2).*YU:pGi\aӁECTыH׋k+jk[c[c H&בLh=בH,Y[9AQbJ_%fUML^{aŔHuBHI' 9HWk^ rTN.[^2{*B!s_inƽH7No"H.W.*~aDӛI(*2@G+ә ԫUMt ZEx ݩF%LXlfM,UQɥ Up B2M29 \&+Pon?da/SR .s#E&8ʈ=!(8ko3uEgLbdq8쭀:\$! [ "V^VY}QITUPFW+S.~̚TNzQhh(A Mzus@y H` 0zOs+kj TK\>&XI)FHW`gtkv"#|%^|: !`5F= 8'qR'!AEq)%%kf6F#.4C )gꅂTBWFWfdrUa~Oz nK!a޵n/{Թ H(I&*UZ̞s㚢˖ .bYJgtuz7]ZlO>ϫj#U Jv`.SwgH.|KK. !DK &X⩇y3[\118/Qb!aǥ*^ؓ,c`G>wwͽy ٜ t2P,hfïyxǿOSFvXVUzݵ7oӗ2Le[l:D  HgFtyK%ȫYꙶhE"h-lRR0?yu;w3)na!YtƾgBIӮbkdyE鵭*A Us83\5r]GaK'!kX~5vxS^ O';v/] 9~KPhoomyw-Q!1zn`'ee]`[`6f?`a<1Ox%2aAw^^KI@ ]y凷<{'7'GhfbÎNJ:;5va_;{vsݽ 9"`"P$wϷ譡`5R ;|߮|g!m۷Zy=, l=e#`/~H54bۆmZ# l^{鎼~#gy#yw\4m&, Tt4{7:\JlPUɓ/eEjű"3@@8eLs`HR#tDbL.r¼O,[ Ҥ=%U&rr u*dޙ-%8I(Yo/ZDY0uM5gga|wOץR3Ǟ2muMd2-m]]{6u1m~aplH,âzR~Vwu-9A1gΔ~a1m~|?i pg;\IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/manage_contact.png000066400000000000000000000013651370065651000260300ustar00rootroot00000000000000PNG  IHDRaIDAT8˕iHqge1D#J1|Q!wRpyyMy9Mtٔ-̜\gZ9o (7HZPdޞYcgOOQ/[shUqV(1;U^".r<-)֐tC5#0|t{ B]?{@Om2]4 {  gZ/@Jq+DWJ Q- H8ؔrPAj?@p@6V,?("oʊpc//?S~GXPr0:WEA1nE\xr(ۄjvss:-ԧLK0*eWh)mm1iYƿyAx$[ч1twNbtHyRHd ,VZ-M#0/*zY._ڑ&ӨuM2&ƥ(!-aN%{}(9MJO|.H񪔂%VI-@A};kwD7#O-QlβAdr 88PvXǗyO~i$=HLp7T" P=_).j [/i~1%gi$FLt ò~̀*]IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/mic.png000066400000000000000000000003731370065651000236330ustar00rootroot00000000000000PNG  IHDR7IDAT(ϭбJBqS*_ (tsp W!ww@B7k;Ásp~C^VV{'Ͼ ʁ)x1.&&{@۲,w@,1+:6TTC'u4ZӍT |@5oj4;:bU' `)MF_؂8 c%8'bz7H]=⸋q IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/mood.png000066400000000000000000000013121370065651000240130ustar00rootroot00000000000000PNG  IHDR(-SePLTE777BBBOOOSSSVVV[[[bbbcccfffjjjmmmuuurDtRNSn` CDc?Axz eH-ǺώQeMRklֶ[IDATc` tMu\m+/OBk-#Dž֥wesItuvd6c`btMqqqwsT`PwssLu h2E%x:k0e;'E{2x9ćx[d* 00(ۗy{8xe--2Tę MF\BX ̕3PdaDū<?( /)IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/notification_chat_delivery_ok.png000066400000000000000000000010371370065651000311420ustar00rootroot00000000000000PNG  IHDR&/IDAT(ϝRkSQ=~{K.TOp6PSjppi):889;@DP*]<׋sQMw8|H~jvj.7w5߭Y&U0E p\LQT7W+)jjAJjQG%~eob4Tzӛ&Z[}{mh8]9xbqd\yKw=c^ l]YGAnwj~eS38>BluX#ID/-ww~\Y]PX` ={^׶mv9$F#܁C%|A!br6>(<*! pH"`j5lg._c1kHhāp8Â>@ L P A:Jh:b'jȱ ^c`0P0T_k'vI{2IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/notification_chat_delivery_ok_encrypted.png000066400000000000000000000012001370065651000332070ustar00rootroot00000000000000PNG  IHDR&/GIDAT(c`$eI}&pD4 6_oK/|Hx*QE2Djrw^?mw:~g9K4j[ojov8C3[#fRқ[n\B/78tc΄,@cNڠ<_^-_rʉ[邚u[^Ώ䀘%1EKgon>}o?|g?;~}yz a5/Kf]mw?quOwHwoS ]fI^X ϑ*?f!>'t7L)ֹ  .']hN9j } G2>1%B `TmPq;ᕬ׋wެqk˧_221^vQ~Eq]ycuGC)P HT=>#I3y3\IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/notification_chat_info.png000066400000000000000000000014031370065651000275560ustar00rootroot00000000000000PNG  IHDRaIDAT8e;U? Iq.FJJ1Z;i6!ZE46("B|neƴ,z?FLoHe/wfi;lׅ@qVOK3ҡ̜he^V$Pw ٙ`ga{s@v7) 6=1)̅c RQsW[Km[y8IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/notification_chat_send.png000066400000000000000000000011531370065651000275560ustar00rootroot00000000000000PNG  IHDRlJPLTE.4=B:#?B(JF*LG+MI*NG,PJ)QI-QO$VL+SR#WM-SL/SS)YV*\Y+_T5]W2^^-cZ3aY5aY6b`.fe0lc7kg=ok@tp@vsAyzE~HV^S]_geu¾|bx}āgfggjiqpqqwvxvyy}}~»½éīʴ̶̵U!6tRNSl4qaaqej[nl[5eqmnnsqnkqjnnqqpqqqqq C#IDATc`~nbʁ25D 2a|^4+TI_L?%6:>CWgQҌ 0uW惨a 6spiijw1P 9{Y)|BTa19P.#3#SeIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/notification_chat_send_encrypted.png000066400000000000000000000012751370065651000316400ustar00rootroot00000000000000PNG  IHDRlPLTEB(JJ)QL+SƺY6bM-SȲI*NI-QuZ3aG,PF*Lk@t.4^g_|:#?;IImG+Ma>q!SuwL/SfKɂ,w}CW2^T5]Y5ag=oۈ3hdO$V=BS)YV*\Y+_^-c`.fe0lR#W.|bx{r~ʴ̵Ӽĺ­wpy|7()ߪ }y~}ī˻†gjnq˚ xq řnmftéɲgiqxʑvɸgh@tRNSa[[jeenkjaplqqJqaqsqql4mnnnnn5IDAT[c`6( ٹEž^\ Os ''/ fVץR!.E2f5r .I5 Je*U5uFt\D@? 0(14 "U䔞 0/0426q-,ml!SqIIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/notification_chat_time.png000066400000000000000000000012341370065651000275630ustar00rootroot00000000000000PNG  IHDR(-S;PLTENNNPPP ,,,000222555:::>>>BBBFFFJJJMMMPPPSSSSSSTTTUUUUUUUUU`bb///000CDDEEEJKKLLL[]]\^^fhhfii˿555888HHHOOOUUU\\\bbbnnnwwwTђJtRNS <^x2.?_mlZ:(( bS7i-+xxkkxx@IDATW]JQG}3׹E#d63+"JBx-U?[cAcR1~N,3 v[7cUG(foA8@ʒ0*a0 OL8)kO](myYlͪ Ls>8< *zl,m2Jx275B?]VE7IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/ok.png000066400000000000000000000007141370065651000234730ustar00rootroot00000000000000PNG  IHDRaIDAT8Ւ+q_&DAp4'%3J٭ Id 'a f®Erom[d bA2â$9~ÛKE0 c{10>M9OEa5B1,8"H6j_;iCVdJOC8԰,X+ {%Lw|qY9gO1NzԆԙMw}Y\-0r2I袌Nr顖k.'p^C7hUQW \`~Xse)Qj.׊AĹB3餕?H ˜O !%3ig[KG#$#>ecU%ftjh+B (50@.,C]A7'w$FIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/options.png000066400000000000000000000015301370065651000245520ustar00rootroot00000000000000PNG  IHDRagAMA7IDAT8˅KQǿ3SFdeA+0 ?( ^"E/EQ/CD/Ba$wD]vv~﬋]/s 󣺻!p}>X:8Ngfg篊z4-Sf!;74)!)~[U@,6Դ>~az!Y^ˢXE0>2%IثWE۶Q]]DbaQTAB6 IJ@}lEߐJ%> t` BM0:^/** H{UUaӍ&[!b*Ӄ" QvFW=`YmkkY$g3Hn/2仿2 )uW|Κ*1 UUO HՕB_ӧO?Ų0,TFGg(SZJJ^e.ql~eI$⧷laq@1_DDT IzۨC::NV%Ҳqfl ]̅wW'Qa\p,(e-)*v L\Jݶ]Ρhu?@l9 >` _80 Ic@1>cO4䃟hb7H!t6VxUd@p]73G/Y֛Tooe=IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/palette.png000066400000000000000000000014251370065651000245200ustar00rootroot00000000000000PNG  IHDRaIDAT8˥kHSasvIE,HMHk^J / fTe֜򆷌ԌtVNbiMxw(Of}Ý;EdSF",gCTg3+Y,%8#K He%01H/0h5w]*<Z^A& -ߣGk0xSrRU2A?V$i FA'MNؚ.gladLسUb~dDw ;[[{IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/pgp.png000066400000000000000000000013151370065651000236460ustar00rootroot00000000000000PNG  IHDRaIDAT8c`l?3Jj['r7 WSEW?)Wpe%O q %Aӗn]| ֯d˱u7O,y )E94oe}'z_|^k@ <}#d,7ŐJ5;>zoVѿ߻߾s{d-xol$ Lj8oթ y;rhĿl_T7ߓ;Z &FcSW. .jӷ_sιu*ԇm2h?7af wЮ>l_ڽ rZNLS~_./|PKu!֬ myCFP$icm뫃~޲|//{YeC L'4gqŭ}^rsX0D͉d3C}"G_?&{n?saYbe/N}-Cq> j9ɯ;Y*31 ޿ګ=ۉOD@2D:;A !IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/pin.png000066400000000000000000000006321370065651000236470ustar00rootroot00000000000000PNG  IHDR;mGaIDAT8c`PrLwأտSgj4:]sV:@7,;7Nun8v9_7iűK АyO_}{^8_ q"T_w g=:@/MD!r.mWe*˻W _5l5UCL\ΰ'c__εg` si5-h{Qα揌]iu]6n)))$$$V( )}A٩t׮]1dt[EEB YhQ4\x;55Հ09 %kIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/play.png000066400000000000000000000013141370065651000240240ustar00rootroot00000000000000PNG  IHDR(-SgAMA|QePLTEyoVL[g`oTc@WG?Wcz4*drMV4*kv41Tc@WLTC?NQWWNQJCTNJCWc1yF5SJF5MV?%LCLTJ12<132<13`o: 4/MKaȏݒQ,G`sc?%K68EH>(sP?JF"D.[gvI3SJӶ鵵Q:ÀyۇLCgofnWWdl_bJ1F=sienF)drZjU?ƀg{vo[N@z_^کI?TNշVtpk INtRNS,Ep!Rdxb:!4P QiW* jϐ IDATWc```ddfef;^(?\W!$T@. ĦWPТ&'sHȃ 3)U,3jL -m]"&C#iyP%D"?, "]jdkUa쒒`e hIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/play_sounds.png000066400000000000000000000010771370065651000254250ustar00rootroot00000000000000PNG  IHDRaIDAT8ˍKq_9#KD RIcӵB]CxU"BEJj=1DnWY 9"%uT;37 stV di-a[)ZU ,/fNY37 n/d~l!VMT_ESaݎ^LV+LAi_?a=ן;FO3Y~algB'x6t~kNǖ!ї+ϑQkКt zC`D3p) -&h B/^,Aa2́e 4MfҥԶv):0ƒ~_ZWX hp:ͨ.Eljj& Yi>*{8٫nbG;FO[ 6S$ޙ7؜_B9a H-@)E%JR@ ᫡!TIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/plugins.png000066400000000000000000000010451370065651000245410ustar00rootroot00000000000000PNG  IHDRaIDAT8ˍKZagcc5" GI+ȃFzBem)EGˁl(bt o',l><~X&R)V&Jӑ.†)H$rPX;88&쫥R E񣪪>\ EQJZ{ >N oolj٦b؍qN^SZ%Y6 '!?xrbpQ8KޜbH^nszhzG+8aU "|sIrxnoN4Lpm' Aw5l6CtC &{6 0c ^ x)<x C闎FE%DžMIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/print.png000066400000000000000000000012461370065651000242170ustar00rootroot00000000000000PNG  IHDRamIDAT8OKSa]w aVi  w1䍁x 6U0tBXnBA` YDt ֬ty׷)y0ZoMS6Xupj:/gfO8r_?Y!_͡PCjO.ە+w__P0T"ֿ4 >Xީha}ov4^(wD`66("sP4Q}FGDW#,J"k֟)|(×,@Vv?$ƮspD6ų vnZxq{2<smo-lUdY%O7F 0>>Z' ȃ\._HYv$l6!AI$Y6c "eqqL:!N,˂X,HRۍt:`0)3==}c2(Nd~#Dv4Gq\ wB1cj4T*(Jzv`FFFZ@j\xKoy f^/d2f XWc?Ud)'R[j$X +~a_PV(R QǒIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/psilogo.png000066400000000000000000000422441370065651000245420ustar00rootroot00000000000000PNG  IHDR@PS;DkIDATx}w%U„&$PWqqPC+L@VaM(*?d].*&\.&VA !0~t9U眾8qyᄒ}v:Uo} ORHYGD9tOݻwp]_<4>Ż _:峠79 7^Mb^|10`ҶCD xe&LHhs) !D4OnXy3O (v vKҏ w#~E}"ןM/D{gH,Fh}YyQCw#@ȷw5&HdNC[fŚ+Qh1ɷ*(!GlQ^y޺{;{݌:ngבwlS^ܥev7=w*6톤pȍa/4\̊e^8#|L6濚k蹋S:^9-(lyE-";2nH-V|1e]v7_]!ۤ{/I1;יi3pl_1s;G!1s'@~ \{*(]{N֍8G?$)d#vt2KƀS< vM e\QgK"zfDLi1 M`i+✵S"fof$,aGx#MH8|%pw`h Cp*?H]Jt}tpwlig.IŻ1?EÂ!{k.Lݮ,tM3ܵ#, %yӸ z9 r:f#|eAq<,GeHHDH#8Xhghy\>:e"F}wmAMc? rYbΎY4TW&>ſ H: zьyZ>y瀖~֥9, 8x'bJcIeZҎkf qC|F?,B2*YKW֣HdY;U̔Yda-Qy 28t ǘ;rw"iIqg^"Lg?v鮕[ߦK$Is`]wk}c:,\ypUM0h :]UN:bz-4O;4;: "@j6Rôqe]cF 6lýw1K1D. `Iɒni@dS ?a#=28еm/l t@V˘^1b( ӽ~"§ GiYa"m;5U!RWݩgQ$cP?ٝV)"e^d&FSl"6pgf 9ސgZ%ąA<76|s̐xia#"j?l UQO0=<Ew lwd2(!/lkxVD:.bO `.z:m?6.:~J18}?"%/jo-;sz2!// ^ҒӝJ9~dz~L-欄/iG? oܡqI6L|'^(~/=Iۃ+MZ "K%{#囘 u#Aiү~A>R$or: [ +XNrudksC;R7 ߯U}:AH `Gza_}C(+aĶ:Z zy2N"2|:`5Իa+εB@Lߪ-R\to*Ej0 uقXo6[+;y>eGMv>`uG{B\Lw5—fH*Exk@,!*V\/$TJ_]o^4udw}s/>smσlfTקmf,L*A(%cybQ'S" |È[=uw i({d-H*e~gB1yMXB J]2w{ x]y p9V1* 7ݨZt_ W}) {q~ڏRJn(Ē&FM@64a5<$;%AgIMF|`䮥]$Na0Tk׾}|"Vy%?Ұ3JQ;z?矱S^nzweYW՟{}߱9oyWv+w[P#vkU)f&wκW\TUR2ֈD?yL-em,HD .erFi4e5K ?P,Pr fd&uƌ.`ɢ{q|։@>#iml[x`~B}|bIWo^`8_—?]hD^ݿT㏪^w'3X=gQn P]Vʌ2˯鎽?ީmѡ fԙNWI)rZNq.E-}= fF#gv hC *+~EKUϬyOo74@lވ=o|ZAž\6rZFp0y^Bǔ=6IS1?@rU |sz \yqgхՌ;c8mbe OIgDq&`j͞V@Qb~PE0lxoƒaCxr>t|aFRΗz0ߘvXQaP(*TRAVB}}Ў+EVJ9ɧܘma)LEuE, h1yRt2,-h^!Swi`D٭݆<C Ve#y46`6~%GGqծ͝]z̞SWCl?fU,QȆ:^+lܹ>_xyEцD 0X~ٿY/[yXt|̋rHP h < EUP)NB8x߳`h4[V[0P|~ovr`(WȇRFI;][lXPqcbwGEx)-P@3CDLtٮH2LF[H[4O]_@w╟#I:+߇Z!9{o'] 39$[N''Ll9k9gqFɝQ "4Col-k"4|=߾`ޡy;J'eԡX?[>_"ܜ^|16YǛ7丐 51k}! XY^KvֳSmNx+G]ل,m%E0\1".! =ůtd+EI9*dlo觊.~$k $DgFڳyg؞ŻvuV y'#,+VETLJvcֽ8ADAԆfBRn{٫/T"“ }ʩoFkP |Kv-Fǂ#_guB%A:p1b;|Nucv>4PzG)m{O?Rz| J7|.Hm^|俿z+)Y6^wB_NFM4"ܧK@H x)H[ctbo(.R9 %Ӥl LkMGʙ\dg_dhVAHSBHdٴLl옍6i!*?Fݓ_nb D8i-$iZ`UjD=9aW l `~/  W?sE|7j?ݵ &T o)n\R8aU Co|pᒰ; !*lw8>x/R a*"$@kKV\ȅ?}m*̣R]U_ͪ~[ϻcA0#.tMix'Gq(Տ4e5IĢhIcN`h~ԼyN OQf2tS?o]K |$F|Qm +zct+S( ~K/-*m]O?Yy W|Ë\>:R/\y dk,.B]pdԋʇŢ s)"v+jU;[/BI%2{l 7`@.jB[=o`y`RyƁ\ m~'۵X0 h`p_b\=J\[<DPN˅7|?mJ졲VެMB9W,'? IKx$=*L.(MY"rY("JB'؈l#6an*r&fMuV#,fJ"EqT BNI)Y״0`'>axIg\"0 7tGg-=۲SsM(7}[K =Uرi} &`$G~res = |vy'^|(rŊ B&A{Z_toƿݛ+T\>u} t"v;jv0)ܜT~=13Y^+JIB:onR]cʨRxibj>ޅHf'K^o C'%BvYCe+A(iq $^~ Fq4R4ـ2%#D)OLއl _zZtH_mN$Fln' sW}gBOs#| +>7IPGb4Z ?>g<aS# "ɭwkβJ/S"`sb>kFX ^tޛoB/,ɇ *6:ӛH(]QRﭷ'$();5~-q-7`67u&*t5lh"3"}/{ Jߝvyb7D63i_M dk$kR'Ko/>¾jmRl~w͘_e 7jC.[orhЂ `s՗6@ GFX?xiT[.&]r(9}O.'7nU h[eKr9j|bW{ۨʹGTȅ&;e &T XQ؊ř=S!2ʿC͟9< h0mSCN7CcѡQԡ$ڑ eHcʠR{k$D]qAݦMSHo=M@rOó/͗;{-@J6>[/r l[/}5TP)]q֙ "Hm</[ c<02[^b@s:z\.c}X֑UW|F/Gљ}V("08jBk'6Hx,^ۄxř [ӌz@HC&.,ΈؕsiHpT1qi#1EĄ:Telz$DL x̘2G@$g ∕H8aMZV.f"ghv nMXU0^sء5h!PSۧah|F, @BjA.80=[X6ܷj/fJbÜ+g Cl+Ur¡KNz`r'n5TX)ϛ TȦRoPRAET,YOkƠb&N,5.L>D,6)@_Պw"F_$1KZ'yZNg}L?2v8f!yȌ %4[uuθωf #"f,|$S- \*IҖfȊl"9cOmsfd`uָ~6 j @t~/[nm*qT*Atyc)̗\nM_4ZT9͚H<5#g޸43h>1\rSֲd\].I-LݶlnXN5Y_3dH7@!"HƗm#R({ؕF.,?\vg\%QEg~Ioټ -AE믽QNl|ߝמ37"('Xo`F4nh[ j-E)7~oǟouio)M|lzչ+j3WJ\y+ȫ+B޲}=læ*W(zzsR\z.3#}?RyD?GƀJA. Xz;tdy3XO*k Y"ajcڌA'd4!Cy!mkR$gwEn!]N~3"s\x;| T*ÆE - %\fچR(H6G ַ6h Œ2\[wzƷN?o|:c襯?-5Zy""sa3. R4KWrV?s1,"V}jY \A. nz&vgbEw=O7OXat9ϨOd|)H"1XKdF Nr=[b)DbIvfn76٬-"G|f?JѣOh7aLœ*y[-WΜU^P vao*/j+SW\E@67A ɁlkF-3~oǾ&nUVV˧]J҈%TXhIG9f@-"g+%?#9E"d7dtMJZLIVħfPCCPzTU8-K._9v9f (Xj"y17Hf]dy = z  " b` f95[sUuCJZ̮^Z5NrRvzWVը5vޓ7;9,nYXr]j`r%׌vWU3%>l+QKl/Jn-d*mj@L&BQZ^r `V2yNE%mfzžhanXxHv6]\G!IfFk)y0ssrE+VJz}DX)ZiǫPB-^wȜ2<jB\Kִ=9U]׾gZWg1!oZcӞ %W:n|~k'+0Gы&"j5jfZ{K.xʉZ㼫>;0, /0Od+5;a271^vV!Ir_BkLΚ`HRdHJIF&o2Jp$@(dQ( 2'@ *0 .dx NDw2.8zuW'*f!êDO^v6am)R!sP??*?_rJy쫄trή^krT*egcNLDv"J'\0Xo==8؀U9QhC!p:u7#Ĩ1'vb7qg9I.@+m%3glcdo(glY"a4lT)@(OAx;Йa˳φWEY6%CJv"E'Tnޯ;o{0& 7 aGMƭУ>uRXQ]PA;^ͩ@rK:v|~&l&ߜiWU}6wfoPVrsVVN_}8s >EEwP-9  <,h-HI\ *em֪LYY$ LOKND| q$ p)-TӌDO}AI $R!k~"fҶXğ)pފ5) Z%3T)2`Pw8(OԷ?W߱yE`8^!oscl&b[BIO}oڴzN8g'ju*60c7<42P&>sapoo^~3W 8INp'#UiN=5i.s#ʼne>=, $zEgn"q%Q3abƒ(B:<$"@RjrD$$%l[n.6eStJeoҊBd2*6Z`Φv@ )#ДXa6oDrv5_i,e%3$b &9tT `M(/*fԪGyEC3̻J=*\Xʗz J/aL(UIP+N`ٽ:;A9ϛ KhKVwS zo=}ٲ;RX/V{U+{+g^?ʹ|Q%<\.]c\i֐`z0P0EJlHn$ـ13Jչ8U"'N}ǟeS[BLvH BяDv6y!O3N18XJvNVv=O0F4||,bb`s>i+9X.[Tk;y ߑ@V\+ raIݬJ$ JzxKpֱybL#Zu>O}!Λ;o@aK?mmJo?tY3HLUCηzR(]mHv>m'o2Ƣ3EEI&’@:zB?ܖLObuiyT&&3y@E| >$iOzaƝl,J Zt&&ؘUY_}P8墛'6kDW g'>Fmv>aY(U\޺ >pzo}d N&@} lRoT7s+"^7b.t^CU~[c (6W&Aa m 2iek`bRj;u6I_T}IJ9< &N=|I+q%9%GHyE!dsAuHf[f $/C}/ʄVBB:S̽IG5D o3Zd,efKd.HbURڎxq V㔥%?>rH:+nϊsxcds1֋꛳<8+ƼTcLRS*$+IDf#lM&'jPʽa.#Fڭf^yhϭzrROX* lNN7ېJn+/ p y/8ynʃjQYpNR"9-rmDB亞j6ل s++O*VΊqC28Nt叁:t9$.zm )׍XWƉpD шvk|[ԬǕˡrb?W8HDUb5 *b~8t!VY(KB)ڲ=BڭfQk7Vn%+!R/a.n:`Vȼ J/*mXL:뛒9E,"+2ysE&5AEVF"bH;psx"!Ei" iIV=LMDH~c+$yBeh9$QpxKGMc}ё>6Leh ; [NvJ*؏ω \b M!KS#JO NWFu)dv4[ڔAO,duN,po^%o%߃|aԒS/ϖM(qrhRpcN)am2=wŸz0-hm@hUKdDBmr\̌ߪږ>Z 2̓YL\߃aS"$eV2?\=:9kWd,ȈoT|l'泋!SJHP e˚ˑ̊-}*kE&Ns5~0cfFq: so-mnGЖb`9f"T[akC2yKd4)RdjѼP<@,•)$\XO/EfV#ɭE Y߰ 7zxo;q XozfFUZ52/ *p(Tʎ1pG/)œXJ ’5B JN@(rtGDk+s8Q/@$-sx+kGpwG B՘!V[Q*=:ڴQt(WKچF I,Eˁɲ&WP&[hǐ:@,1C9Yd Zj퓳tGHAB3\7 R% -2AZ#RcC eAꍓWFBdf,҂d;_fT%lrvImGۆH6'YYx(OrszKagL2 ߂&I~y2uj0Il 3;$8ϘEw"=伤Qu*ItZ%o {NR s+egD0 Ҽ,> 0P9$YqHPGН8']d[P=H F!mJ<#>GnU80m̈́H[؜(tsZ'k1v/gAػ. "zxBD<~[Lj6ϪojbLzßss+`20am fix<188A G *qju IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/psiplus/000077500000000000000000000000001370065651000240515ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iconsets/system/default/psiplus/action_vcard_restore.png000066400000000000000000000014121370065651000307540ustar00rootroot00000000000000PNG  IHDRaIDAT8O,aǿnʪ3q1bW;mYfseKJbQwcBn;-)6uD#.Be=hq4Vr O{jg{}Ϗ2'l!/ 7|gGY؀).fpdE=@gEƵזsOʧ%8m$!EëEV  ^{C]Q4lUHLq?D7bX{v2 +@k=A&uF7U[*xLo¢[jDjg6$2{3Mk@GSebYZybR G dfvj_R!tq^@wloZIl~ hM!%^gs/:1k5b @~xLkgWJG<. 2D:.КKMQ>KuC=::u4Etk @9Rxhfa:SjN`/?av:'*% */Ci5?]N3HZG7枯m7j[Z1қ_+2XnuK e_%t "hK,@{$aw lpv31=/nh~5dS]ߺ Va*"c 8 i88 e4f0\4p>B*ijS4,A&DtH;=z]شl6r5HS [(v; R +Ua aڔe;,xVh0CA@@p)9Ǘ03j؄yZa@PAN:&BM9NZ(0\؏CtRҵ{`.MPAMOF:"XQ2>N .^OۂGa!^Ud^e}jWoh;hm-VFcj($LD*Y٣zDO fS8H3Ƕ:.,$r((JpNe\Mf4e#u<:az]aP'UIJIY J[FZذ K \$. ,JtjιuPqcNFxJ1|Wھ3 d\* f0M8I4{²n<%wpGTzPlƴLaݓ@Oa3/+VMIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/psiplus/doublebackarrow.png000066400000000000000000000012231370065651000277230ustar00rootroot00000000000000PNG  IHDR(-S;PLTEeee2k>beeeOaNbHaeee=d:d;f6ep>qKuLvT}4rJen܀䍬`SHlfI恟^՘蘱鏬ok셡b^だ4q4r8s5r6v7vD|Q;[FFH^_ld~恣䒬窿P1tRNS3; dZ~ ?bJ#@=aa= PGIDATc`u%AP 1c5t @A WJ6q 0@eG_WC ddoh%t$C< l0 ed'#pnH7 LG2zٶ Č.ȶqvڬ ށ@AT1)*=*#U2a k/ b{Y]Fv.n^NVz*32XIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/psiplus/doublenextarrow.png000066400000000000000000000012231370065651000300010ustar00rootroot00000000000000PNG  IHDR(-S;PLTEeee2keeeeeeOaNbHa>b=d:dp>qKuLvT}SHel쀟䍬4rJnIfo^Ձ`☱菬馿慡4q4r8s5r^b⎭k6v7vD|Q;[FFH^_ld~恣䒬窿Dl1tRNS3; dZ ~?bJ#@a=a=܌  ȬIDATc`AE ` T!^[ q55Z ..!^]J0I3pGن"P7$i"%#lC70)icbdz V8CC3G93W%pM659-*%IIR!9 P POɀ[FfVNn^.vF*+ (IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/psiplus/logo_128.png000066400000000000000000000346021370065651000261160ustar00rootroot00000000000000PNG  IHDR>a9IIDATxw\Wy{ooZʪYdɒeɲqo)lC̛fz - `R: E6nk٩qhgWҬ%hF3;O#b{; ZRM,$ 1Mija>O e/#D1s1zYBٟtZ i=:u*uO?Zk|NΛӍ9n8X=㧦yyc(9 |ΗC*sK.>6m }oXכ:ױt\z^CMT Y[7ęgb QA$r?OzP$2 ‡>B/49 >X/0ͅ*մ>[yҺZز|~5 }W ##miy_Oe $!m/^Ү]Wu{A4`v)HP 4 b暚_pgp9]w*A}W-eM=< x!򥍛4Es" |\ ιxu 4=o0TT0" sՍ7 Ǩ)1{I~0*! \ew*>w&,tv _~6XN01^"ަ}/t/rbۻ@0kkw~SW[Ϗ@`L?3ܙzb+oL&poZP,SNN wRYa)`QD9+?kr[s_nlHd0CC_{~7/p]7 jk\w auX / :!57#zʦLZ_?d#0/Xo֬_'dɣ A7~{ۏ﮻_ଶ+\[x` E'R$'%i<57g/O`t~2 &‹D6B5nOmCc#ç>W_]yޢE_Zs.h8Xw=}=|@p8[og_Xͮoիnnx|u2==胿{]E:O+*j5q[cy '}ҋݬ%U>VE!T'~WG[`4 dRPgM۵7qû~~{뭨g9ҫ-oyX?>XWW~i4YGkb2/[LQp0D)lܢԡZq( Xh;81'wmoKгMyj3w >[I&ھ\=ug]xVA{-ٿotH2k2y*+*6)i@dcY9A8s[}} h~)唗_rIC+ŢEK]aUC;GGо=գ{i*]+.89Nrbbb|?c+XE0غ;cbB5Mͮ`o/< *7_t9(XJqի=jkֿyѢm)x~T|dr (ʳV$'vPL 2y A$\]"E4<-Ws"#rNjͿdA8ᑵ5I M07ћ #aw]=&WsN-x||*//f U}) ‘Dl(J RM٬qd Fh, ۟* ն}/~;uu׼=2"hdXLEEŚs+*7n>?8Ϸ' U܀<VDUeHuEA hd;u-$9Ǖk Vj9 m `dQ,]˚Gy9[j tubxB'KWR|uG\,F} zPHZBBGV8dYcO[ؙN6 #ln؞6.0֝%y:piں>z~ GH[?Fi#5#m'5}ɡKH]3AIzn/7 {@29ơ%|8`57h=-&/_c@~ȅKBgqR ZiTitXJal۶{tYٲx(TSyj*6Ru?0\^,/_X:jϳr+VQrdgz{p*} -+ˏWU͂% =7S6\E`ȷ T׈DF:b)J5jH^yjOߊ%L#/|zMGid%`?^K$qL]( 2J.8E\5TN݂;p]j0Zx96%dp LL`@4sc}E+nɀ#.Z#A !ί4\ڍt@ `|^^>h80th &l0"6_nK@ĎK9ڊ8@l A4kZr^6O!J|es&tt LshD; 0v0}@G0ٿ &r8XɅ &?Y \ 0q4tRZ4gz5h~f4H4NpCBb<5z/h=·gY?:g}w}SNO|DIY Ӻwmoz_Xi0޵ZÜX,CSj=آԗ.N7Ĵ C\pq>.fQY6k45>UVA,Les06,t {%C4/y7}5'i%.\z BR§ ۟gfVe݉i!xam.4RbO\XмڲXU]Ë*YZ`F/٩aՀtA_<M|.w㧵;4nYe_xAW|6 5S7+; y۹J=H291%ijD"++TɦfC{$_+.Y?0 &7*H +k97+V5j‘ 1^&dpߝ tJ ֏h\<oo~AQx~>BmyCz  h[E Tss[≇ |5Lƣ1 hF"y_QUVY{\[}A8ƏUK{-)EKꥬRV+* EW|)v--'&Q+d͕ 4R* f'8"r'=ZD#$d:̹H_q]hVYu22=} L ZdAbb5t z;98yOF lY`g8AAh&"13~GLɮ$B@ u5k6*r 4k`LrX%i +y~WׯPA6 Z?ӮTs(DD"VWxKN4RQ剚AnGvlx!K)aj?y0f^i.$ՌlHɟ7N+x:ZBa!Y!0ғM 2|r"W\`i݂w {^$_eU ʷ*DFhmm CTP貕BpVMfъ9&Js݇3%oulI s] )%q_.wy)0m$!h>w4+"*) &+`l+IJ.P+KVzTj ƇA^ &|&_$ʢB`G!]aƷ?w v~ёWol|73w9$5!ff@z l#PURA3Y^WAOa9m4_~D0YU'$5t.VR&-@R ϪO\n BL2_ųmz2_{ZIe&!W^ Q@u BfSb /ReUzNדc`b8/)óSJ5 MzBѬ֣"-ŀrݝģ#J(H\iTj@u䂼 Q?L6>JtP OhHke-[ȳ8M>kDjZvd&qoq݃L\O/|BԐH\||PJGi#-6+=OeoPKhE><&2a]yuڤ$RR}cEc8{5-POQ.@#WÙ纸(] E)!kgOPhE,ZV1qs0?sPQrE(6z8S(tjͱ-O[G>'ȦdP2:̝Ne:+Pu{/׊ek4ʛ#nafhB'3{1c+FX#̪zEBΡԌ @l6Ё"n-3\@5P i!n]m9t>8s!LPyAfB Df(rRաX_g`e4Hg5yhuBLT5-/Ґ8{eg_5=~Mw$3۶wLg NF#g!$=mۑӦ{alö eQQ4Ȳ3; ZMnXD`@ݜStl[aiqhiʳӧ]6=6.tVḚlJpE )ab⎗tPh Y3gjwku :!ypȴMe[aYrNz~>!4Oi†BjfN[ԒH\{W٘)%!P lG*&<G=z]:lZ{:m$tEei7+4 =G)_zO5ߛOj"%:~`4Ņ'W ,Wf2X6"ʊ" >f!2 GL:_:rg >lxmFġn&M)@J!?i ? pHеA6mvr9dʮ>M]M˸@M{MGmc=h٪(3=5K{ٰfUUnY9  [W;ixt({];jCۆ @hĐ흽E}9 CJ K/X* K]60F*&~vAW Libև*VxlzWgXxn7/uwBgc<oĝOvtEڿ_?~W']#A ,8UZktWݘ[b"y vI'& ({8e lסq4M82 ~>ZߴLQ2f?"nzU8 Sk7dULkkHgd2fn#Ag?򗡪ʔ&I4jhLc!pPdEW! 89@{HS̸D3w3xՅWFP9'FkB*띞72LBf6wlYk%Q^ABe)$ 132ߔ5on ~+x%ؽv]{|=>#zZSNİ΁w"L%a/+_Eh=Uhb Y01BJdEfA<[VvɥZc,t,$-L#13`"};90-u5Sg Pi43-Ny%P /Z K Kϸ $4b0my3~m ؆K ]~!4}d. 4RphP=~_8s>*16h(#ݎJ=rhmJ@4! 7] A"tɬX%={,dW;:XYlMYeLT6 d&9n !Q|Eo@GqʵIK J11Qz N?Y$8@Eի 嵊Z핦`U7kR;w.egpC5[PT)3Y FMROu f2Ow1sdchR(7CrK"=eeVT2p@ zMң_d.@|͚Gy˅ٿ0tI+8_Bdu ~qKWf_LL< z ZH<!~9XXҳMLlۣT&&L3XlcUȪ 8` @2m_?c8_h[21lIsh> XlmE8\}FQ*7W2a"$* H$ zE*sl:zu?+0}>&ѓ;?-y9xF,D&QʛO 5l01,p'?TJϻLkdb3f|ớ'hc.bW6k`쟙oǠMAaH0@zR46p1~LjSU@VaE4ZfMXD0a⹐<溃c ӔU3.1MhX'bv{@`N?L>_k'̹Sea)>j=/l Gl?3y G-;E`H;KkWWY@Ssi%%~dbVn4c#VXqv7'C• uZj|?>DԘfQ|%e@ '&y#=86%|R2Q u` D"ΐ*Z钜? ? f|@Tj{״Lʫ.К&p.%+$d7z5P6ǂ 8BdO7ͷ>a*`&Ase@+1ܻ+HI0$䢦zR>p!BJL6I:%ee[6<lODɦ,&?C1ҩg|zqؖȦM/)߲yWgfK˞4D9c=dxzkgRBBdCRږZD"kke{:CCk"_j FG =F+Q3C5HZ;2M+"26↡kMl2𰊤m}BPQ z<fߚAEmPV&mR{ە~vʨnz´Xr՟e1-ڑKy${?MBhar_Z[9PSu!Q6p=zut@W8 z&pp<~faPhq*EN2 vƔ|al Kj) *dLLF_@w`oT*| >_ |~3jQp%8ye_$mr˭j!#P$8tL&`ɥ8C"fT(!TT\|Rи!d }߃;;efڿ au7VWC{;*dPk} 1H~$)`# ¿*s9&w֘RV9\Vӄfz!AD.>;gr_}XR9(io |1X5 ! k I,}ѦG4 ֖Wi*< t~SRX ʓ P,,!;%d!P7+"B[)P9hDLqO`8)Rٺ%~Mx$IC.җvJ4_hh@d2mTjn!`[2f ,T C.vOl.S+/{/lye~9KW"&m[C g!,qZ~١S9LsBʣK.N}M6t9lY\PW%1`lDjM^GH +Y6pB JޚAI*:x[)Ld>X=p~~LAeV@TP+FE99loph 5bX,<T$$2ã$`i 5Dɻ1'Lgy;7њA AA|JQr /qcm YN+?vƍao1.C7ށnɣV'|{.⣦#|4 XD%D%eUW("^VEYEհD-t¿QV=+[ T#hz~Pyg3L,3)OӅ8$RbY!r*mXH}c3uS۰yYl -eْ%dhٹ`c ,eQgAwZn+:PhsL.c& \׆ .8".Pc*;:׵\Σ"dByBP8yA~=;b i27V+s <5t~/0(.OsߝJdx s ZqP(lgR'x`sj<' tRޝ1xcᆵXB`A"??qAS #0>ww޳}## *:LZӶ#JoL$@"qP:CenZlTjW,[ihRzqAaz@0/@2H<ۻֱ]C"Kq\0ƀOƻo >I8|jh.alhϼB0DI@U )VWk\]pZŢ]XCyyՒ5yN =>ɀ&+G>d_@l#Ԝ,05]ϯ`L BL-h,PY%,%5_+o|vIK &i ~s݂O=uozxҝ"[0 OU_n /NpA))Q.)Q` Nu `%K^8^PSU()G=rY'/L ]W^ۃ{ @d@;.0)Ur"8t-GA,pß;g5ܣgP`^~d2ЏկV[/ ,l7Fŝflz-\nc*sآ|D)~ Qp|Gyѷp#+ǣʰo26 `@NjX{ʺ%G `Yz\ .Noݺ"g("1Mc.CKl;9LzQ#``n[-X2LvҷE({Ylv.5 WkRYjDC\ߔqƳhG6;d5{_d.o(hɭ2G 漒p+gݺ4MFH5(J8RhXgt2Eޱ ]LG*(}rLmaO%PV%Kկ)%1;7k8q\;-Xsෝ>V~D䪲2͊;FJe!SN5;*-_ Xhh08!BX7x3UCUeʄOUeT]&./&JIȂl@67;n;E…ׯ}Š4)7|*+a2HH6}RO#A{O_syoq >ٶgo /| bq*89n֥L\.UyrscZ0(Cs@K)B:=Z2rv-NT@}XՁhzr&X=4N 2'PL|(Rhyfuq9bfS޷s` Oa|z9~n@$hCE6z~ӜBxPW`P1n(N%IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/psiplus/logo_16.png000066400000000000000000000014331370065651000260260ustar00rootroot00000000000000PNG  IHDRaIDAT8˅}L厫sյk#-0erՐ1i7/ΰT2/m!͸V+&[yiyy2}ܴw{Ϟ=HB I(A <""XJl$Ɏ `d;x+a]{Rhbx̛osw)o[ȴ}_Hꗭsd}k 6ayi"c/GF~ ׂiܩ`~x߀SMnj5Y7yyϱи󜎫OȊFY6#Hۡ_upWdt\q @`c-)n(DN_1# iqK]-('3!j5)ˤRAR3Q3: |;~;W(wݧCclk+@, 2)]pIYx3O@%9Xl]QPO ߜxRtY\˞2m`K, B%7*wƇI]$70XD h4a0#:]P4HI({IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/psiplus/logo_32.png000066400000000000000000000042441370065651000260270ustar00rootroot00000000000000PNG  IHDR szzkIDATXí 8iǟ"|))[&W +2Qښ4-m5a| !%E(Jla3p/R3`_/y>{qb %0YY0oHeLA\ssM}ƴد?$fhO{cQ,c @ RjII5 29RoN3f+7?kD||mPg0(g5MvPttޮU߃>&((W gl%@8ߋ*v=把KffŭmHʒM1So0RЇO$/T䒡5q:k1K#7_Voy< -μȓOqЛ1I!Fc'LT -}68<06qLΊ+ \ƭ''m)*(L1B3$5EkmîMwg哴Lܩov@g,W'*\zm@^9Med#!i&Y.~_NfH}mz SdIjSi\ɖ_X5/PXՅTTyYY_K_}y.)lIM@eňHY/Yb\}Rŵ]5/;iLJUs7cS\9OY;Oλkcy~#e/ ȧ+q\R12 (6@W>3]\QY)#`nQ 3 = d#K3TGc_De)7DH)Bl{W{.p, zqwN@R.>0 .z#Ԅb5+D:BCz(>Fdnʈ)K:q\[)` w2 Sq.`fVWcG bN48ZcVmlImH1U1;BiGp6`dWJ̅r&>{#+ Vkvж5־-_KsJh¦+ܟ{=EIebv7akP&~%6Тu}id6Eŭ/N/L,`N`j7V>"\tb850EʪxlH _wL›ڪo#/gq7m8҆4TUw?օ%|tt}2im:ڮ JJx u'@ >׎Ĵ&9O_kxζPa44mm@]}(iJ&. .0PV;B۱?-yRHF;{@w 1x|B0DX; 8,Ւ@TaBDeᣢy{b/Nσ_H;Q~(RaӼۅI"DsA< |_ӳD7ft0&,,PJ`]$)* !-zyٴ@_@\dQ'RRI72RkpcL gU,c}ѝFLD߫rni8U؉:N_FTA#'zqy-@`,n/1r?]]чȠHeI##[R)E6י PYA!^fP ?TtL rSp zaiy𲺚q7փZ4Cgjj5c\{QZQ|.UJ#?Fߍ / */)2n[MYz@~= &X1Wxꑦ ukw|S[=\m ()> $ْ(j+xJaYIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/psiplus/logo_48.png000066400000000000000000000101341370065651000260310ustar00rootroot00000000000000PNG  IHDR00W#IDATh͙y՝?VzK~4KC7K KC IP w 1.Qs2&cb4f 1%#:qQ4 Q\@EdVUU4 {{{oBk RwUn^}ޱa݄DkpMm17wOGnn=J::qYؤ܃aBׇ}~tF~;u[*eNxMk%&LijLC#F1 ﷷ/[ c>ZZkmZ/[v Z> @/}5+OS7Z|9Om}Ym[&/P8pNd;xXns~yG?'/[5< iRN XlYx*A-3 @#ą@?`PX\4wwuum]b떤K: ݽॗ^Z}Yȝ -Dzjnp %c]XzV-YaWkuΉQ \ /۞WVjx׀a]>tD=Kq 0Lw^ɳzd'A7K>Ny ̀TZ(4YG;YwuΚ^=75P0 귮M[_V ~QT4c6`A*_ʁ\xGj o-m=A6t9\zӕ׼vW[^β65iؘ5bd}͆݅~r  vJ oƴ+b;(ؽZ0 q/ZaäD" V}ai"`bC oVRȉsJx!ZlB |gnO{̹YRIo\4?\0;) ,8gKznyoM82ЍF| 5ֿ6_ۿs:{wkk@Z4 !@ʣ.@B\H,|ܧ$qP+Z{wN>]F zqK !@;h>(v10!fD((J* e}fPꬉ /w]@{ ߺԅ?ޓr/@]{@,*bvx7%ۭ?@&H^v$d45qY*6`&vA7_ >Hy>+[&=7φ] V>|(O0un]p!"9FROrta֯}Z,ԣs?'s~oc3 __} ͑6@_\#>N5u ~MuU B#Mj!I)1y&L .HCnWldQq e91Ig"BuytZи'Jkp֌ `A_}WylLvNS pzEv߁LFMw 9h\YU~BT7]"ؿKJQ0f3`4x+ޤbs`RrzA9Bj2iI:u(N6%:~ބ!哦_=x\SJ@Gb#M<ə6d joU]*BuN* 0byw$ӚVlO"2.ހq NdJu4PTX1t1<&kQRN~c>3 0C'p)U||Х.CC : VHa\Iiy5Ydw-! L*4tLsե5r8P]lO/ 7]_4ǴKn6V'TKD3A6!S D)#ӉF.9xLi !3T"ā{mw*RL?Ln<٦٦5K- o>$ġy{֌\?ʯ7 dvb瓛{>5Hx؂c)5!0 A(0u|@y;}AbS,S0AVo.I=ů)xӝ́nªrVڪڏ{N"cB{n~S)!d4Uk_UHxvx¼^H-H>nఔlQ.ޖ*JhP&?=5ӀԡthԜ,aLS`jK6e{}###aCz;eX\st8Gc$5 8C>':Af$ő SB@z-k6Ah g‘ZJAYAIsvV2_ʶ[Ǝ`D@U DTD<vx0I8̝å#!ڶG$sr1h} 婙K)...ꦭ=oN=6im OpD)|@cF?nyCp’wHL6ME-gٌ7)%  n|k Ri?e&@NrzTZZX87n'ӦK4W(tړu'Z;o!!ݮ~A ҉ G6 hlOZ[? SP_ISG+< =iGpdJLM~X )X} @I ҫtϮWx;멜鱐F{6\h6eAkBQΐg ĐC ¢q'zCNӟe=2T<@g+(#0i\3teWiUnd&G@?9' h kwm^puO{gۘ4@:Pnr~(`tC\zb@0sQR]j曰jүˆ{3zxҩcD"M#f5%N?eG687qNs絺R⺉a _R 7 "g_u/]}:[._$NBAJI\97Z$*_yw=׽8HxP_ntId죗w_I{YQrCBtkN})hzI)Sh282Rw4{|a+w' Z`]WO w%+hYIр =q!2ipԦwVƪii-mnrRO ׭ ~|,ɓ_,9%4o)3&d!9/ Օm;v5~o)ko}wiP0x'uRuMuaŷ,& XH0f_\B֮h&T>"ķ΁Gn+kPw?D .x\$#]1i?n#~+PyI. x4 lpZ=_Oph u_n|+ GОJe`ځ&7p}Jd?4VFxV7xWau }0BM4:A2mW̜m1WAddX7ud-hoǘ1Wp"i`!rsLII߹Bw/)DKG}fR2?:JQJ 9!)\Jo6 n}#P+hN6!?魙BIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/psiplus/logo_64.png000066400000000000000000000131751370065651000260370ustar00rootroot00000000000000PNG  IHDR@@iqDIDATx՛yx\}?;F.KBƲȻ 1 $HHIÒ@&4i$MIB(mZ~ihlcccc[,eiq}Fsywy!$ BMxF&'#DQdkA*> q!mx>MEl$ߟ.,YrL Py%W4~?sRz{fCQlX5U.e3x&P6ڶ:WU1@U!}{6(sI+AҊ^䓏]vC?CL/i%``Z#ܰcCfO1}:g? /ky^U#_y= Bס.YW\88 sov ky_I5 ~/<R@|TW;+_~]M55=oァ뽃LYr-P9gYnWPZ kWݔ,)I%۷uwV p l΢ù|(, e1WeQAɝ K\<4@[C/޸pK~r Ӵfypx=tW7jQ M#IR?c$WTD[8/NX*~5ٵ!+xvL}*7'--uσc"_]#@|~w/3wtP@K`s .yy=?Y,=푧uVAy4s"y̯.a>-iKC{c1| ,[b鍣|Uy q5?=o ۪ǼO~Tkj-MA,D7$)1,3K"(.Xhڽ/n1 5eW̫Iزr" htc{899ܖǴ$$THJ`?1>{6>·d&O/<[ >[=_^Dg-@QNcootr, ؤ<6~8 ! &v.H`VBŴM^$'z3[y` ;;:(~Tu]Bπ2 @[6RP4摨@JPFL7I*-0k꼂)ɐIK,k  N8~onקU#MKbJtg.|PHGr]?w~H{WC[pLt HٕpP-sr]UTn0kQMOG{PRq"_N.n-O[X؊2˲y9HȄ-k77~m<`Ow?F*r\'wA:y lxV2}(.ibP$'bmutS <\Kbg?q!.$ SW7K{v{.|ܜx0flYCcNqHaSR Ĵz1dn^ ?|b PcfO$KB< ;YK??h]UgL_$g_PEp0ipH]()/YTXesq%4m д)yYQ dF;`85`^ t t9R?^s M,sT, &i={g%;N9z֭4 'RM˞նF7[-y.=;2pԤ7CC[ -:0 qE,r:kTܞWS/l01&L2E& ^rsoZ7m~IJ$osVQ@Wp:&0a^59ڪS߮eDvS,tzr#O Ok󿐹Rxܳ&.*2nU8D}`t@`-Kq }"o7trU m'}PoEQxpF.>Heŝ+K@ pIjenQ8]*S\TOOKg(<ƢrM,_Sru!AU@( }q@PJhn'a`C1FF=-#!z|JQ-/wͺ`p7YP3)wr̈́ B*F{vGSV@f΂d!: R[hZ ygR(ܚ`7D8uU˦ ^ cp 34p۴}d*I| 6ZV$h\H=Ңy5z].Lzn$6d8 4KY:D0l#piv7WJy,u **Ù' *s2 U X ${@3)¢K ˓`1,ǩfd$C`~nb^P]mXB1Ii>Oip |z\.zk|MpɓO|KϽyd2x}ж?_{;'@= eO߹z߾A1AZFg_mPj F~}۷q D$ 'FE U] \(b;(Hgtэ G?*DOD6zhi^s345Ag'd2߅6,< DIjb|c<7ra `2qHRRe[p"-TR|XdeG@&%.tyK NeE"F<.eXTH3s.(HjZoba#+Bs Xz&%eL-"&Ru.← euEełi,klVTpᦣ)5hCqIP#A,::j dp8w=4a5_Mi(r^7W^ 3{+F@89chQ#H$ Pd}iO[~]SǘץP\$Qksse¢'攮Fu{͔igd0蕒Ep.!$Wm?w?%$xqaI%r%HyTwO֍`:}g%ebL0Ak/f\zrr.]KqqƑ ɒ3u͒6m':1ӭ1ggSl<ogmRx93`\x}Od6XrsȰgqNgZf_TA林)TN̞̚VWexp:c9|{rٳw|Yr E% ,X`iūXzDNV8(ܡoP?H&o>jokGN ǩ#UP>8"#O`֏sϝԟ=mi#a@ck8>XΪ$p|f6:>;o ȺUݻw_pMIw__|qӱXg-j.:`R ;g/YIYp])ʆN%҆pI60T8puT^:_?O?h>@ /WP\ & =?Ϭg4Ђ}t9P\\;Fy`p;[ݜ8If2q{2+P n\.FV}6K޹s%##sTWWMM1K4IEǏ]qJ۽KR{7olkkl:UzܴwnX i3K4ipp>m>yeeS:oSNݺuxXvyx;r(L{F\٩G{hMMgif2nQw7^Knj~xرc𢣢WWkUcz)`xӦKkn`_ϝ'h.CG!?JÞY5^m/*֌"_ߖ7nMK۹3̙3KKNjsiW܂; WpoǮ(<JKGʒdM*S}Zt7̡̞WΛ>rСsrqȽgZtt|ccKtaZ=<<=mڭvmٳ.. 9Z8-!ڵk𼈂 kA`Y6y#rdƝemJVKJo|}PSӑ+Pӹ3tPd;rss$Xqq˶m%%۶dig.A<\#tej> .\ ?_ mȴZ5k$xSiZ_ Xx| 8~mFRdMb3N:GI kCσj&aGxd$a wQP?Q<Чsbbb.ʫj͠>ՙC<5_>iJ5. ~ - q9hhGo݋I""ϭ+ʁ-75ܹ-veg @<_(_Rmugs穉*}E}_4EүщmP~yBk..FջS<lfLY&kͪՁSH<.co EED30fz{{ _:zk՜'P6$",\LYWW@S$'&n۬&Xcln:D Z uxxG0r-= Ѳ)%ṟ9s+O_F3qaGD+;1PbJGrϵHH„G ϒVşx8*l\DAGS T $ܼuk֪%\@ Z`Qk$1Rɳa!M aIr`",$TT ]SRT N ` H^`eDGF!9d$zY,>UUU[[ hZ۝7%˂d8 OhvެNSTϐΜPSs-.յgʁO>zᅥphҁ}waEE?Vt?p^)~ljj,-[rr]PPEHy:x2#t% ,fUPsax%eedfgfᘑv\7 bXR2ySFzt65!ƫk@*SLmܔ4)_mu ƭԴ4qO|A @@MM pNŗfQd%h'uҁR6{&T[_ٰ`h@JUvSaWo:zyxxies%`jU**Epe=~c-Xfk{t%>x򗛝z@ijr LSJR2SZJ*CeLTtYI) {r0"LGGǐ3O @N'꫏22(xPvZnN2m#4e3( DRR xCuUSc@d4Qal^zzzq%ȠZoo۷=b%P.{=a4N+UԉSLMMjχ +:ї{??PV~KNŕ?j饖\y}HFS2{kAx6>LW~X\ n=݈F`]R#RNòY$Ca,f^VG<==qFaCBJ S;LPRI⑒L)D2EEEqqqlOw}j m,zwCR }!~ugaͪՖcoxԆ 9{v䩫pfy$ġ)!9Zvsmݫ}yIFFlCP~i ;xi_~Y nV16 QVN#zߧBp*vNQPۿz#ϰ`gkꩤ'~eb*Stį_LQ+)ܹlwK0ThUSwQ0s;LWCCj.$2Ƣ(h;q:Kރ_#!|.@'x:Ge6I¿m?m 7PoJcbmR}o|)7e{ܓ#xs W)XSFF~h~b5Y <Faq&PSRRT/Sa((7HIumTV@G 0 A (4Z$T#,1Yw+HSi~@B5U58LaQX #O2Di[P9JB=;Df9:Z@@tUNvЀ2Qp0THSLh=8YfO-~~ˏ|k`b}oLXJju9)]TT_[SeJ zR\\$NP  )קO .8BЦ5!1r86jTwK|cXB0?XT_G0Qa`qk^O/i:LMA|i@ ]^ F& c ̫=}đsuR`QpS `zh" ;غI=~s"^_}umWpLXʞ7+) S^>"+z)>!G`Pχ.F|CЁ^"87+k8zRGp2`2EFF c $:nKN\ lJGt+`)IӆsKbcQql >1<qFPB7uBCd P Fp %zh0U􄔚򾙃p?b,)Nů{QhFca6u]Av eKBMELe=u{EjF&&K{Jz"jmݴ/̃B; cbH *vks疡$*n" fu:'ܵh_x{GDejWkًǓ˖,SB "9j qѦj(*Twt؊´`?6nZ.`!(2Q)<1±%3^_Z $4 ,%VA,Xej] +,`09\]Yy%Quf@: 1jl&C|d,Ztg;S:;fwtuCm,u멵;ꮃݦTVϴ[%I7Z;l_vΊUVvFoTT4"+0)pܷ@bb<3JDM 1j `ԋsM/0X,%)d @Efy8琌\SflpN#]\窊J:)LІ,qa hV{X Vt不ؘnl:|`hCnp{ҁ۫lB͎( >gΐCH{f4=vJoo 8`]*}rc-[+6z67և-=/%%%M mp@d7"zND/d.26 m[.Yت[9`Vp5jLs6r5Vps Bw0Eϗ3eND!RU)f._=2jQiȴHf%4Lؕ#@͛6l\lթ0i_Ld$mFmLxri$32zWK@2ׁ;nBTWU\k?_Go?.i`xqŞ)qÐ`b2ILIIKK4RFF k`>9`+˦(cbb6T;ZXH@޷o:^"Sh'b$Lvd1*nՅr]%7;L}AGq G؇Hp}y@l&iƻ$G޶!]֘;?g1OOVk(9ںuQPr(VQZ?=}m"o\spC#HÖrZFD q""l6Dl3XZ9bm#_XIcα7ݹ0х(+W]h_?H0GPPQ5EL@}*\mo.i{BۢڪUqP2 9DܵcaJQv?{fDD$*D|Go|CM#z`X|k~/~!3˅fUTԑ] eET-~}qPJ0L$dY#1M){*`ӈ`^1}2!DS&;Ax(.0j3[F6*um " afB|1,clF`xɢR#\igδ^𺵭ڷFarǢ?LswX@cϸy+4c(S߾fټJxDu;sW ݀F#XBs Vm/퉾*C(v H=^`>@ Afggp*LLq * Lqi KH+l Pl9gpl4x՝,-ò .۝x4bŋ3xcڝ0mm97?.\J"J3o>Py |`XO?dyH se:d{rrrVJyU1ApH3#L-R'y'V5P*]hzpD2Z^ZVqΖ!3=Y"0mNvhlHz T fp;T=Џ-YDeDF HsK{91NW[@?rxGtKҎ;;3}wʲ2״-Wn0l_ξp\2m10$k$ե2N6v9.Wd`WF-Rsrs ]VYE[y#TZ6${'yeW@طכLɣX  |Ll-۶f<<33.cʸQ SjFF'5TSyg ,-Yb:#?fXOtio/ZXjcSaoֆ U^^3ɴhtABueU}m77H#`ur&;beVW}p4`ns7LgvM:DKpFUFe-7|ԆWj<o.]oc`gԹk)_ݠ..056\ZxwVk)NXF۹S" ^uN:$vHڑ}{?^/B˿4?SKy47[cQ8O'XE$rAoooc{vO5nXk /1aD 2۸ xtQ 81[FꆤZt`" *Iߊ;PW|ndi2Z`AN4S tpնl -utzvK3%N`-6h'?$Cd m3ws#3cTT@+$ h("~.5#`xnfXe hG\!; ̶[õ=#0qŵ\f\Wdorgɞ@|4f_vqB1{8j>]3L/@ger;}3ww-sb}/?GEE=u?1z ѷV+ jQR:L *7WV&̪);(JuYUPWԟGF7$1ǖғѾA%Ps˪0.[;獤ŨjK xr.h21vڇkQ͞w&eۤŔYZ0hªŋg+e.|zn1;P,.FO]`p91,hWUsq疙}uQ.frqg xWC~ q'WUt gggdd"Oyx\^ f$nTNw&OwyvʛPK( RӁ֬^}18)B $ t$?VhߞOm)r9Ŕc45\B.;N{56e/h[a-! ܨvk'Q V)_W 3aNn o{wЫVws8D;,U΄sXl>{wIou,X.as3"r&r>k/o NԢ˺rsɔAO1=ŃA%W .8^}ot㥗 n?~c7ubJdUTD Esҫ8CmqDUB+̚1Ebdm ,k#ա>ꦺQF'VaZB#}n^(;$6XcI@BNūt d88iuyݡм|Sɨ:x']w(w~o*o|Ѝz?79^}ʓ42'~1O%PBD ,_*lP3XQ*hhrK<V!3v*j'vXTT)sdv-JT[Ƌ?BHx'z2cG*g)z%Iŋ` jIZruOXAODX4&d LHݮǾX>ۂ?_E:\-W_=g08چhQ"S-℟ؑgLf/yWcZKi`IJ, ekèQ<^tUwC>%P`$:'.eϰcժ,&mUO[,bSW}U]3/D٩3_iY00]dLŏ|/lؕdS-A.6>Ծm}K mmՄd233BGQ-1QX~!R-%L.-fcԔHuL ʾZRn-m=Y&&bUM%C32zfT Ef3_,__VoxxlK4Ŗƕ9^=Uπ +^16E]m0UjN7V%W(ZAmi]>m_PPPDDD|||dXګ7\& ` E#<ZQ4;XDcyy#:#' ,Y6ó1\\\`on]{ }}iUzB&/)cyGU7tu8ՅGW^4+/|-UKK|K.ҥQ/OG#˖-}2ۉGhT,#"[v%AV j 4fQ|x@块rEGAHVmfuf2ˎ v\]xm\\ԠTU]Yk0 CgqcVV c W$3d0;cZ_n8<<<j-&(O@ Z*FK4LD_ ,l{@@V}| ?֔kk ;m`!(000,l Juzy?:J׌ta+OjkkNt둁0e gvر|2w#lu'-p=pG_N8H7$5#:q u... 2*)!Tk`\EA@n$Z[~___KxA͡Ndpz~]Py ԏ2pattĚҳ"{ݾ}pjۑJ?^I EEEm;\2auT؆ dggO0ni9 >H>2}0R@Ab0iC $'Qf,Cr\ à!T ΋@16 ~m+**ҕ 2Kp*^^= Έp;6 ٮA*63&vE?v`UW=uzb$?#uwOJ֊ΤjvvG1}\D|H/y :ixAVy@4-%r2¸6< BP@EpMUz *%;q V!.&V/a&՝:H?A^{:K :\hgiXFBw赜~Zdf'?.r:ؽӳdm7k6vb֙IO=h%xg*г$3ZvJ=ާ(bӦθMolYz(A8 gfbKUmӅLLxuJRʳ.p."-; rɯ 5󄟬R,J]* hUZ>Ȝ2J;'So ϒB O G/IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/psiplus/psiplus_icon.png000066400000000000000000000014331370065651000272670ustar00rootroot00000000000000PNG  IHDRaIDAT8˅}L厫sյk#-0erՐ1i7/ΰT2/m!͸V+&[yiyy2}ܴw{Ϟ=HB I(A <""XJl$Ɏ `d;x+a]{Rhbx̛osw)o[ȴ}_Hꗭsd}k 6ayi"c/GF~ ׂiܩ`~x߀SMnj5Y7yyϱи󜎫OȊFY6#Hۡ_upWdt\q @`c-)n(DN_1# iqK]-('3!j5)ˤRAR3Q3: |;~;W(wݧCclk+@, 2)]pIYx3O@%9Xl]QPO ߜxRtY\˞2m`K, B%7*wƇI]$70XD h4a0#:]P4HI({IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/psiplus/psiplus_logo.png000066400000000000000000000101341370065651000272750ustar00rootroot00000000000000PNG  IHDR00W#IDATh͙y՝?VzK~4KC7K KC IP w 1.Qs2&cb4f 1%#:qQ4 Q\@EdVUU4 {{{oBk RwUn^}ޱa݄DkpMm17wOGnn=J::qYؤ܃aBׇ}~tF~;u[*eNxMk%&LijLC#F1 ﷷ/[ c>ZZkmZ/[v Z> @/}5+OS7Z|9Om}Ym[&/P8pNd;xXns~yG?'/[5< iRN XlYx*A-3 @#ą@?`PX\4wwuum]b떤K: ݽॗ^Z}Yȝ -Dzjnp %c]XzV-YaWkuΉQ \ /۞WVjx׀a]>tD=Kq 0Lw^ɳzd'A7K>Ny ̀TZ(4YG;YwuΚ^=75P0 귮M[_V ~QT4c6`A*_ʁ\xGj o-m=A6t9\zӕ׼vW[^β65iؘ5bd}͆݅~r  vJ oƴ+b;(ؽZ0 q/ZaäD" V}ai"`bC oVRȉsJx!ZlB |gnO{̹YRIo\4?\0;) ,8gKznyoM82ЍF| 5ֿ6_ۿs:{wkk@Z4 !@ʣ.@B\H,|ܧ$qP+Z{wN>]F zqK !@;h>(v10!fD((J* e}fPꬉ /w]@{ ߺԅ?ޓr/@]{@,*bvx7%ۭ?@&H^v$d45qY*6`&vA7_ >Hy>+[&=7φ] V>|(O0un]p!"9FROrta֯}Z,ԣs?'s~oc3 __} ͑6@_\#>N5u ~MuU B#Mj!I)1y&L .HCnWldQq e91Ig"BuytZи'Jkp֌ `A_}WylLvNS pzEv߁LFMw 9h\YU~BT7]"ؿKJQ0f3`4x+ޤbs`RrzA9Bj2iI:u(N6%:~ބ!哦_=x\SJ@Gb#M<ə6d joU]*BuN* 0byw$ӚVlO"2.ހq NdJu4PTX1t1<&kQRN~c>3 0C'p)U||Х.CC : VHa\Iiy5Ydw-! L*4tLsե5r8P]lO/ 7]_4ǴKn6V'TKD3A6!S D)#ӉF.9xLi !3T"ā{mw*RL?Ln<٦٦5K- o>$ġy{֌\?ʯ7 dvb瓛{>5Hx؂c)5!0 A(0u|@y;}AbS,S0AVo.I=ů)xӝ́nªrVڪڏ{N"cB{n~S)!d4Uk_UHxvx¼^H-H>nఔlQ.ޖ*JhP&?=5ӀԡthԜ,aLS`jK6e{}###aCz;eX\st8Gc$5 8C>':Af$ő SB@z-k6Ah g‘ZJAYAIsvV2_ʶ[Ǝ`D@U DTD<vx0I8̝å#!ڶG$sr1h} 婙K)...ꦭ=oN=6im OpD)|@cF?nyCp’wHL6ME-gٌ7)%  n|k Ri?e&@NrzTZZX87n'ӦK4W(tړu'Z;o!!ݮ~A ҉ G6 hlOZ[? SP_ISG+< =iGpdJLM~X )X} @I ҫtϮWx;멜鱐F{6\h6eAkBQΐg ĐC ¢q'zCNӟe=2T<@g+(#0i\3teWiUnd&G@?9' h kwm^puO{gۘ4@:Pnr~(`tC\zb@0sQR]j曰jүˆ{3zxҩcD"M#f5%N?eG687qNs絺R⺉a _R 7 "g_u/]}:[._$NBAJI\97Z$*_yw=׽8HxP_ntId죗w_I{YQrCBtkN})hzI)Sh282Rw4{|a+w' Z`]WO w%+hYIр =q!2ipԦwVƪii-mnrRO ׭ ~|,ɓ_,9%4o)3&d!9/ Օm;v5~o)ko}wiP0x'uRuMuaŷ,& XH0f_\B֮h&T>"ķ΁Gn+kPw?D .x\$#]1i?n#~+PyI. x4 lpZ=_Oph u_n|+ GОJe`ځ&7p}Jd?4VFxV7xWau }0BM4:A2mW̜m1WAddX7ud-hoǘ1Wp"i`!rsLII߹Bw/)DKG}fR2?:JQJ 9!)\Jo6 n}#P+hN6!?魙BIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/publish_tune.png000066400000000000000000000014371370065651000255660ustar00rootroot00000000000000PNG  IHDRagAMA7IDAT8}SkHa~v %џVӤL6LaB䘘n:.DLǦRPQB:ae!F5|=Gqp] +ͧ* A{ n?NFkѩS 9KpƈdD1`B@EֱɆ+G\Aje?1%<Éu8V oױ0L/fWCŧp;C 0IR(ݖ;ZXXH"JOكe.T#z}x#X!FbTA"UɤA,(**:r:$\GX[,r"I.` r="eN[ttwwl6Fx<"e7=aUlAٔL<Q R{{;y޵&/8DV7҂YYYD"*#'t^V¦6-4GXH 233+322ġV%FC+A\D~n 9TH Ѹ \nHrhrrhzz,rn}2yyfsj%Osss hvvV$hnpqnyH߲vju3 {+oR?Cz[ BrsIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/quit.png000066400000000000000000000014351370065651000240450ustar00rootroot00000000000000PNG  IHDR(-SgAMA aPLTE  ~W{TѢ  !#$'+. 1 1"4#5%8)5&8)X1\6mFnHqJkIrKtMwPyS|T~XȁZȂ\ɁYɅ`ɆaʁXʇ\ʈb̅Z͈cϔiДhЗmѕkћrҝvӑsәwӞrԡ}Փv՞բןy١пȠvO̊_R,ΏcϋjϒnqLS.wSyTyU|W^7wTԞ|W`8kCեvզwנץץצؙ{؛~4!٨کۢ۰ܢܬܴݵ᱕ᵚ⫓Ⳛ㰗㵛嵞ȮűDzƳͺϼϽ4"?/@0ʁ]ڲ>uNʉcʊ^ʊ_́^tRNSeeee~IDATb=lm۶]۶m;<*\&hLs":t5ڙTnVozfj03n "I"*ƈX9Oҡr7T I#?sI.q1'kfK^n -d ~ci@_- ^S>ɗ:>}Ra]dBD|-Mn./mJ}|a|4Q&bvi{t@>a ;5PJQIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/register.png000066400000000000000000000010161370065651000247020ustar00rootroot00000000000000PNG  IHDRagAMA7IDAT8ՓKq_i1-dEltdRAhRDt!$PDcA`mt2=ϧCXx &UCbboW+GoO&aFZ/v|iBacuH^89 zF7S! 4Z:0>S,×el7fMrCY FP+b+4+И%C]@1s˹rHӦYT1m>X< ئUb~O" 0'w1usgnl??џE@8XCnƻ#Kvp;RfkYJJHytHHO1'U'Mvx@ӷ+^ `?7zҳ0IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/reload.png000066400000000000000000000014071370065651000243300ustar00rootroot00000000000000PNG  IHDRaIDAT8}SKQ7"(AwمN!&Mè϶%A!6Ql 5b!f~"bs:sL5Os)}{`R9QLI(g%2` 嬆)VpΎUN0׭L5G6!+>ƨ,nۄEhsaĥrǓ&U<ƨf-~X,{_ ar΋ViEosyܺn;!\x7d{y%MSZwQ>s%(ٶuyQu5M ]R)[A5%qȚd.Oc9o߹çr~?}P 8Sںu:]u y%IBB4s=0K%(ggߨT,]~L}j QA3_rIMpp4NCnzO\^`08ֲ@naVR6HӮZ8~@LbHYFAfTT.Sr/?1r. '^TvF N뾋ÑėŘϫղ߻-9))=,%?00..O//P//Q11SF8833U66U88WK>>]F/REESFF@@a&&YLLHHj^QQLLnMMnaUU99SSpUUrg[[k_\EElaa[[|``}ecwddxaavigWW|||ijohhTti{{{{ҡKҢLɄwԣQԤSz۸tɣ޽|꿏꿐Ēșɗɪ tRNS-2B?3Diu;IDATc`!a?o~^8twqf ؇Xl #Yipm |IDN^N8C0&ki0d@FIII ( rzz9Ҝ\ rr^1I" P!Qq03d]3ܜd *d-#bd(((1(|G<|!(5{IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/search.png000066400000000000000000000017431370065651000243320ustar00rootroot00000000000000PNG  IHDRagAMA7IDAT8}Luy/J Z5,Eb,ue+Vk1#\lr6_j3sz15@8R{yH]wô.<{[40R1-ዧ6&ٴx_g'|tQanh32Ԃ`ِA vO)r+MF[(^(.˲ ,l`i!m;(I!ƺ e4GUn9Եd iMB@.F!f靶-.jH>g FmDBTb2\ {ܶm/133lȿؼ,-3zRk5&PfmԸCǏtBXLL 099hcfS6@O C\ ̘ށe=gV2㛏6vBv9 7Ѩk=n?m5 ۮ>/,dY݂W?bW8c%~wJ*8[[9ϻ涵Μ9sUQQ񳡱N: x!ý]MtdӴn7~\e2%瀠kMC/rJZ /^(xb-) X{ƞbQ"#Y[-/ Wx<9z+UU.44lC}~n\TvSZnۤ(XLY#I;W-.. //UUe=XuFfݶ3tPOQg qz!u>TcO͠3@ŃFtFhhO-+"qJa׻1>V up6 T|h{klefX"Yv 0(殡 h6Bvl΄Bf#4o\]w$HSP~3|4р mci,4Iy2'O3ƨ'@UKyE?02 _C(LrryXrxtSO]JWB0KJIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/send.png000066400000000000000000000010221370065651000240040ustar00rootroot00000000000000PNG  IHDRagAMA|QIDAT8OœKkSQ{s0k4F v`cARq¡ApЉhHK#>FkDÔ/k9w'sv#OF`v"ڊ k X8Bb Wq4Exw$qbz|!C#j IDAT8unQ1xo7Fc+J9V[4CZrP+MQE,PHM x5!" Sm3p ?kggo{($g|)-5^oPR5yc9_ _*l\.zuyj(]+ x=gHge~)b1Hd@P4DQp.8_9$@k2l.HOCev`3F:oɼ| @nn:=E"8`R@uj`ftz6{f C]2Ns0A{ 3\>,fNW69KOjǝٛ(3`۰Օ7{ 7nMKJ"Jj/nSӓ[K8"ZNZqCw&HjZz~R'8@dN[^L\xaA2$[zD#Π0vՐUFԺQAU *R\:oT\l3WK?IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/shortcuts.png000066400000000000000000000006161370065651000251210ustar00rootroot00000000000000PNG  IHDR7gAMA7EIDAT(JTs;3:הI *EP;D۶mu+Z]IDAm4(HAȉqmFs,|lv;k<ͣ~0> Gj'@v%M"sƊRh09ѳd޸(nx|;URǯbX?ziN8'櫳<ʽhu9Sifmj]Gs͉w:B)Q47QmJ V=pߢE]tۺ[z:\cּN/ K.iݍ+i)nR)=JbIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/show_away.png000066400000000000000000000015011370065651000250560ustar00rootroot00000000000000PNG  IHDRagAMA|QIDAT8khv6 ߯RB"IB@XBЇE-"$}0(5!D E,Fp6ryyvw~ tl'7Gxx杦tqbہ$Y4m5Uqѧj7&K,B PXdK*4+CwxŸuM].85nyM̼D!yb_wmZ"t5`z0:ʫϜ=lN Lρ`~G2:=l<|td>+#0u$mʓϿ~#Q6 F᧫5}~=bzw-JCDD]ϟOgx6y}D`;\v?o)o5ۭVNkM卫, O|[pwoS6m\: {;mA^t*:` [^Tρثwzie˕.:;U0ܿߤ8 mA;`p/SSOL|j~ o A>gei-|]ljoo Dr ҇38LڶM @ED[vM~RloJ@h-`W+_~7D |/IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/show_offline.png000066400000000000000000000007251370065651000255460ustar00rootroot00000000000000PNG  IHDR7gAMA|QIDAT(=kw%TQ2WtЂ8uџ  tNN:Z8(thm~؏@jb撋\t֭ѣ`ʋޓA|ۧCg.KQJFqG\~!5u{ͽıDGWm-y^ߖWG+XU >x#H%:tgzRTieiajqPӯ@FVkU[ޥX$''w UhP"*( ĺҐ4ʕbm{s \Hެn3ӏ?{{gjuǟ@a#"U yR z:"XIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/smile.png000066400000000000000000000013751370065651000241770ustar00rootroot00000000000000PNG  IHDR(-SgAMA|QPLTEߘ ߘߘ  3;V l a`|~H:e`aGO@8` 0PrCEPg - $7  "$A.@ڔj?rY_]AqltsQ7PC3K2J_O d(SfJPRZVt11ag!d/VYԥRU#Ng``y * XIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/ssl_no.png000066400000000000000000000006361370065651000243620ustar00rootroot00000000000000PNG  IHDR7eIDAT(c` !T&#DT?w{A2LPubV-vzU[K3IBd BYmuEE[:k4mX K_xdʅ5{ϿW'n>_/~?'fO/]{oC}ĩ_ 2D =g@  ʡð+@+0|` ~f)s^|D?bx$@|` =[@w@~3|+ !̛`? X$. 3?fzwbLT[IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/ssl_yes.png000066400000000000000000000013171370065651000245430ustar00rootroot00000000000000PNG  IHDRaIDAT8˥SMOQV)R5|$A!3$vMX( !1$,MNŰ0I#~BN֙ wLrrs;yw2g|Pm355>iM|$^J ,>8N!Ñk {{ۜ==Ajtp󖖦GcLNθZ .vwv?h1LÝ˝o JJ!)n| -om=xs3xA?ˢhĮD={ Kkk T?ejh];O}FǂKIjAWy*u.mdpANMSC=tbg yn7M5R1/n,I7ঽFM"![#O{c7\\Nr @k2IO:;yjǽR|V(F(gp"GJ-e $TvH1"Keµ᪁\@-! ZEB`_#% 9WLPU dL@6P$5OZ%OjA ,\F4R"b@AȚ R^/l|`F:.X䳷mB+XiKI 59 `*eIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/start-chat.png000066400000000000000000000007311370065651000251330ustar00rootroot00000000000000PNG  IHDR(-SgAMA7PLTEErErEr,Hr,Hr,HrErErBnErErErErEr2SEr4VErEqErErErBmErEr;bErfDqCoBnDqCndĵR|Lw|͂ЪޮIuhŹErGtWӡrX"tRNS !*/6KTW`kl}IDATӍ7P( 3e!4θjCJ:kp+y,\49!ۣԗ"bZX0J]۱X5tS2:~&{M53jq}ްU[IXIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/status.png000066400000000000000000000010311370065651000243760ustar00rootroot00000000000000PNG  IHDRagAMA|QIDAT8ˍRK@WmEcDw(n.EDtptQP?@$ࠠBjZI.9]MݽBc@p|,+4Jvo7/*15hn?4}_zK2B%k,{ޣR!V.aF:i湶޳uUbGPX%ucJuf[FG4FR7{Fo??3X8ȵoo+y6+_L6 [TYӣ2]:Z?>"an6ev;>GJl['eE$0 ~$V"IQXր)\sW 'ZqqR͋vQGO)mft#$j =b@u}ttzQ\e (qyw#IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/stop.png000066400000000000000000000012561370065651000240510ustar00rootroot00000000000000PNG  IHDRauIDAT8˅SKSa>W!BwuA9ӳtQQLTTŐC "Bl@f qgki桧=sJ酇|{<ǭcd-x:?/V6s3F ]ċф郵9U0 $؈m MDY5dRG.PEb-""AF+ A? zx1Uiw0wOǯXL?LwߋrcdlE5I0pͰ#흈z#V֒7qJ+⒌YP^b1ts-$O!{ѳU qɌ~sE+G" eX`ZSs1yme`ݧkz#Q5+EknY%='-=2 .`gYWj}La*#/ԟFnsN}_Z?}%ccksk(4 z۽7OgF)|):(SYLS_{(_0;5vF.=yvr"i݉Ҥ_}Nٞ{2|3}3' dNr/6t!<%[5 F 0Iz a!}$T S= n7apJR|29] [r|G,Kl o_nH Ӡ.Q<6:Mꚏ"jDyUU}Έ RSØÆ"^2kGVZƒA]rVJ@ `l-z^NX X ,z}R-`3:gvXްicsO%Ku>L~~&].)[]J:iO\ӭwʩ\թIEbZռ]13]zni:P @;( ̆@90nΟ#](˻ 6#^Rs}0oTWsYŕ ރ) ̛5jͅ$ޣkަf.\jIOxz՝9ۜYw:?uo1zYmc!:feG북&BX~&tfxh;BAK{tKx{,oD`!|L;Z/lL{ca&{X$ߞ7S˛UU%! /ӼTWԒqi^c*Ϊ WL&Y,w2b}9*^[Ws764hQyYYMf]fMxIAY,ΜkcFNLBC^3cܢz#S`ĘjT/5*^|TO~<[ZSv-cڸg}YU şapDQWشثҿ30$zP)FJ87)F$zNgfŅsm/$|/z &+.XNzG~n¯lt(gȫvN;aosޚ ǴyѸ#VK|X/ڦXSeF2nH-ƔDD#Yn"Ճ:x'u!)}N.Бw Og3/]B{6qFvbcf퉉&eYdRK >ko61w{3}3|g fRdtXSf*#~R-u5N]/Fʨ]wIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/time.png000066400000000000000000000016401370065651000240170ustar00rootroot00000000000000PNG  IHDRagAMA7WIDAT8[L[e ӖfĈ-/bBUM4{&,f5&D̘E E11BQSЕ1PnNo;~bxx˲T**$8x_z\Gu]\v}kdo Rh~>r&V*+r%E6ph&erؙO/}dQ)DSO57656LLp$ăO]E<֖'?􋡖gOH  z gC=rR-.HS W;{{߻:z4E+:dK`TW_Op V2Z]]'l2+nL%,2ڠAE|WBJIf&{1c FYc7kg(2;ws|xe㯾"ɔ=ANJEtg r fܸzd~;S$,M`Yp|:,j9,4D4uHWoOvzh:ic ĝ{hcÉu Qmx K~6wѝIә^yQAgO:aqO%qΣcֲԢϤE3QTCn`ovanA!9 tzužˏ֋l=CfmQdblQdk%Mr|}{ͭuwåRn mmϾWGkJ #Ga്` /Ͻ G-aZj'?~X4 @@6M[rR)4'n^IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/topic.svg000066400000000000000000000143331370065651000242150ustar00rootroot00000000000000 psi-plus-snapshots-1.4.1456/iconsets/system/default/tune.png000066400000000000000000000007731370065651000240420ustar00rootroot00000000000000PNG  IHDRaIDAT8c`@ERMY^-*rua?eHLE^vF"'6Cj|gh:7+PEZ☒|B1L5J;O3T72W<LQC>s$͇kg?lR')+m?*%Q3=dWS bm󿪤z*"+_EN?А @7HK231ԟ8dJnb+ RT QTQapp`Si`h P{;P Jvr3_a);pR,1qD52Pw/Cѧ@~:CT`4?Fcdɯ2]hL4\Аm GLgFˬQIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/undo.png000066400000000000000000000007771370065651000240400ustar00rootroot00000000000000PNG  IHDRaIDAT8c`Cq@shfgx[\osE5PlP>pifwDS_>I=۽@u12fxT O9=\f y!'+1G65Bż W=PQ9k13.72߿ fWF -K14P^͵l O^c`p3_I+ߵ2g!]1aϦ?t}9NAş B?L e_OP܉yJ;L tK~ܚ}mg'Htc̝Ϥ-贈|aS`f b^!IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/upload.png000066400000000000000000000006271370065651000243510ustar00rootroot00000000000000PNG  IHDR(-SgAMA|QPLTE..jj0P x)hhbnKK[kZZ[LckLYZYLmZ,d,-KSSqqnR tRNSsdIDATU q{ I\AshX:z}}Hts~h4V8z?e9G۶1聦ZD$Fp>Yl:M&zz*UܕT+df,F,`N a?> ݶcIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/url.png000066400000000000000000000016001370065651000236570ustar00rootroot00000000000000PNG  IHDRagAMA77IDAT8oLe<8~q'HKjkrXZ1zAEo7mͬA0&̔h!( r<}>~|J<3;h~%xbeg6'-_8vSZY@ vp-G:{5^zB1BilG6T+KpiV'B %ǯ nF7 KH݅ɿ׳k2SwO:e~(zfq20l=k_[g ׁoqg />^o94sO~ZHR D! Jޏ0;&Z7}uYKe_Pֻ|| MHQZ4x&3z XEfKc7=Ҽ+A(YdRwX"7oPvGgɝmdFC"`V]g$`ԗH/0 G J>6*6+/K擌B<3sa@cAjt\j a O) i$ oCФ3'AP MO1``Z}20IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/vcard.png000066400000000000000000000005521370065651000241610ustar00rootroot00000000000000PNG  IHDRagAMA|Q!IDAT8œJAE݄"XX M'æ,-2 pj-+[ `RLHY 3_~eRL|--1_jKjBF;nk+Jkpގf;X,d \+0ȫ:\*Ğ~fh, xJ䇛\ݳyX k%@F8j`ffp],-xrrۗaEu@&T*`zzĥZ( zppC|,&]N[mj R\.pv``Y ~` h,IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/whiteboarding/add_text.png000066400000000000000000000010221370065651000274750ustar00rootroot00000000000000PNG  IHDRagAMA7IDAT8c?%f8ppwks~[n̲*etW/$#^욯,to➗w=߽%~;]ۦs kɄMxO+~-\]9MewZ\wmAӺ'r$* , /*%Ͼ(nڝCfV944SUY7I;{8LG岇]^R:Ć1s u0^q- 궟FgD&~Uo8յOK.< J`'X[v+as =.KF'-L~auO_~o6&,2έ/ajUv+dGK" 0^Ym t3%Oc|gIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/whiteboarding/bring_forwards.png000066400000000000000000000011401370065651000307120ustar00rootroot00000000000000PNG  IHDRagAMA7IDAT8˥=kAg֏콉^EEVF;_D (AE!EB,"13s{$]8.;93stJIM[oM*qP=SO`%J8pKvP%Y_R<뫋Hgn$ao!1]J}HC 1'3gbC+d; T_Akז//ʛA_|nH4Hth-h=Y釐dRwB2'a^?sT]5 VT!OʐMatv 9EV0sq( bOHbh06q̂) @{ǻUG]Z}ҏ{]w gh? *=jO,9>y'8 l||qt$D5W*Hf=,FAy[g$IHPM4M!B#¢h>]ٖY n<]h礻2?ziԔ_KmIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/whiteboarding/bring_to_front.png000066400000000000000000000011401370065651000307150ustar00rootroot00000000000000PNG  IHDRagAMA7IDAT8˥=kAg֏콉^EEVF;_D (AE!EB,"13s{$]8.;93stJIM[oM*qP=SO`%J8pKvP%Y_R<뫋Hgn$ao!1]J}HC 1'3gbC+d; T_Akז//ʛA_|nH4Hth-h=Y釐dRwB2'a^?sT]5 VT!OʐMatv 9EV0sq( bOHbh06q̂) @{ǻUG]Z}ҏ{]w gh? *=jO,9>y'8 l||qt$D5W*Hf=,FAy[g$IHPM4M!B#¢h>]ٖY n<]h礻2?ziԔ_KmIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/whiteboarding/draw_circles.png000066400000000000000000000006671370065651000303600ustar00rootroot00000000000000PNG  IHDR7gAMA|QnIDAT(ϥKAƿ$h4vQK,bgaa-X,l-A 9Gx.[$30aTvWq!C8m/3dIX!ޮQInU/jgզwF׼CA P|y?q"Jܩ%ۚza062Hծ/Iv`XcͼnYRE< f.#JVF;Ż+ V.5_ xr39KOfd^woӐ:l@Jx9Y7C>,T\<+#9:uVIfҽ^ę7~bE 4iu3ջ n RsrnIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/whiteboarding/draw_ellipses.png000066400000000000000000000006671370065651000305540ustar00rootroot00000000000000PNG  IHDR7gAMA|QnIDAT(ϥKAƿ$h4vQK,bgaa-X,l-A 9Gx.[$30aTvWq!C8m/3dIX!ޮQInU/jgզwF׼CA P|y?q"Jܩ%ۚza062Hծ/Iv`XcͼnYRE< f.#JVF;Ż+ V.5_ xr39KOfd^woӐ:l@Jx9Y7C>,T\<+#9:uVIfҽ^ę7~bE 4iu3ջ n RsrnIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/whiteboarding/draw_lines.png000066400000000000000000000006271370065651000300420ustar00rootroot00000000000000PNG  IHDR(-SgAMA7PLTE/ޤ5c ;‚1ҝ/۪O˓SɐOʓPv,&۽Hgϥ'mݫsݮl˰/]@aϩoYBǃ4ȐNDϦhÙ1}4ԧ`ȩgɑOʕBϜJ^Bmi%`X@НYԜ]إjُ;٨P)P tRNS  kIDATc` ٹؘl͵-X| >>^NBLPW7Ek*I yP>^NBLPW7Ek*I yP\ݍ%F#IH<= 3FN ;r`ߞQIEEKLj!{h=ι!BL=BF,m'uk{{qww苭hg$ XT} [W7E9B9{ V ':IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/whiteboarding/erase.png000066400000000000000000000001711370065651000270040ustar00rootroot00000000000000PNG  IHDR7@IDAT(cxǀ2TaeEU9.tӰ؋In oHR#pC"IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/whiteboarding/group.png000066400000000000000000000005031370065651000270400ustar00rootroot00000000000000PNG  IHDR7gAMA7IDAT(Scπ0I=NtG URziiI3|exp'qf,2~VsPӢ'>I12Vui:P: ,B/n1333y;A׮3Kregf`zTd=Ͽ^ |~_2m+:IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/whiteboarding/rotate.png000066400000000000000000000010561370065651000272060ustar00rootroot00000000000000PNG  IHDR7gAMA|QIDAT(SOHawn4MAi)%ECXѡյDOcԵ !CAv0E(3K {y|:>봒&}lVnZ*F`N;4g(z+:=dlH PR_;_M 6*$Z@L|-`' oD!( f|[A3hV$ΠNHN ]%, (ʘY5\Zj-l2Kj1fe6zHK\"Ǹ 2Y=d ɛ!k NȔȴ}  JNi}ɽ\7), =~ iUnƄj7i#)H}dēSLgYy}  ɤd2%, nbpBg:p#pfp6ppOx{Z1h?׮׵s,Bg HQұȮ 9$+Kg ܟFre]Ȼ& k?; A-jݯ{ù_O(iAT^j?P6XCmwA=:Jjx2ϰ~57 *'\k >{**XE܌ ҇T=YC5ED( $*˥3vB: Wx!6̾XбK3: I+;4. E9e}ta.a4_CN^"'?i9xy|'rf[XVG(8M[A3hV$3b6t0088Nn- l6v;>[Vv>66Ʋ8~hynAaقTc>ARRRRR Tz&E3m4Z><͸u \bdddIs'mOXчn~Aˈ@.,,Dqq1#//L-#.tLOh7qq=EEElxII $yrMaGzac' Y,ü(,.Ϸ>. ,>FS/,-ap 71#Y&m$.L/C0F>=#^T,ԀM—Jur|0 /#^fUR|h_P csz-^}g@p|щQD:Щ@KLQf%kz/न-stYldq&l/U/y'Ͳ ߽}UMsb r-l@XWa=麛o]/=q ϟJ)e kR'ɳ8Ik[/k wECIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/whiteboarding/select.png000066400000000000000000000004751370065651000271730ustar00rootroot00000000000000PNG  IHDR7gAMA7IDAT(}1Kq$uzBAib)\(98 `QpPQ"ڥ#A hw'{O ){m=ʳy'8 l||qt$D5W*Hf=,FAy[g$IHPM4M!B#¢h>]ٖY n<]h礻2?ziԔ_KmIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/whiteboarding/send_to_back.png000066400000000000000000000011401370065651000303150ustar00rootroot00000000000000PNG  IHDRagAMA7IDAT8˥=kAg֏콉^EEVF;_D (AE!EB,"13s{$]8.;93stJIM[oM*qP=SO`%J8pKvP%Y_R<뫋Hgn$ao!1]J}HC 1'3gbC+d; T_Akז//ʛA_|nH4Hth-h=Y釐dRwB2'a^?sT]5 VT!OʐMatv 9EV0sq( bOHbh06q̂) @{ǻUG]Z}ҏ{]w gh? *=jO,9>y'8 l||qt$D5W*Hf=,FAy[g$IHPM4M!B#¢h>]ٖY n<]h礻2?ziԔ_KmIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/whiteboarding/translate.png000066400000000000000000000007701370065651000277070ustar00rootroot00000000000000PNG  IHDRagAMA|QIDAT8˥SR@=+ =?PQhl34Zi3- >n€aܙ3ns{72Z^+9\Pd ( yq2lZM^Q*")jj\υrgj<pmATA>F#eSn%kG@!"K&\׃%&`P,0D#@Оc̒ Ɂ337&RS8;=U/dj2}?} 2 B:+J%xՕ o'bΩ)rd F`2`:,]fKIENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/whiteboarding/ungroup.png000066400000000000000000000010631370065651000274050ustar00rootroot00000000000000PNG  IHDRagAMA7IDAT8RAha6~ bBz]4S0R;t 4DN sy#;Yx7)Ҡ52wY/<(`0x<'xD:>,ehG/ZD*Bۅ,˨jFpn5 t@ jNb\.iB!L B9 Lm٦FjfzRhGҒgj~6}p8M&)g8ڑ`Gd_,er9~x^ӧ/L6&coCI^DŻ ~.0$+Yx}vzQP[ocfFZV/s ( YPo8fAKǣ{3 cOވWI}^×dzTYx5n<Ԉ$rE5IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/whiteboarding/whiteboard.png000066400000000000000000000012401370065651000300330ustar00rootroot00000000000000PNG  IHDRagAMA7WIDAT8˥KSa/t]uTA DH!b&˕C&CeU%0rhM4ʽknsQl휳o9QBA>Ϗ9? ?d7,ͽ\H$_\B3?ڞ2{jP>\ȉu 85YAēHvx{Y2Z>p|ycydR4D-4" ,=qg~PUO& P,JSAP"fW#߰8%޶E#K Z@uNp}ާM,%>Jq$ye6HnA`` $!8$(Ǽ^vR +Nm$#^  :6RL@Ƞ -ʗ| @Q+`/!@tl<2bY;`A= dְd]s1y#i{lcuJ9 \ҝ@vJsqخtw5ԬM}'7 09IENDB`psi-plus-snapshots-1.4.1456/iconsets/system/default/xml.png000066400000000000000000000014211370065651000236560ustar00rootroot00000000000000PNG  IHDRagAMA7IDAT8O}ROHa~??ZXM :x(Xb,46$f((&a :lйCX HV6ɘN~e^x<<'۟;~F#.?evvv~ʃV#V{+l6KDV+E8Rlo+TLE|Aqra8\yU߯|zYO"z744ǡb۟666ڙ{5 psi-plus-snapshots-1.4.1456/iris/000077500000000000000000000000001370065651000165215ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/.pre-commit-config.yaml000066400000000000000000000015501370065651000230030ustar00rootroot00000000000000# See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks exclude: '^3rdparty' repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.5.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-added-large-files - id: check-merge-conflict - repo: https://github.com/doublify/pre-commit-clang-format # for clang-tidy we can take github.com/pocc/pre-commit-hooks rev: f4c4ac5948aff384af2b439bfabb2bdd65d2b3ac hooks: - id: clang-format - repo: https://github.com/Lucas-C/pre-commit-hooks rev: v1.1.7 hooks: - id: forbid-crlf - id: remove-crlf - id: forbid-tabs - id: remove-tabs - repo: https://github.com/openstack-dev/bashate rev: 2.0.0 hooks: - id: bashate args: ['--ignore', 'E006'] psi-plus-snapshots-1.4.1456/iris/CMakeLists.txt000066400000000000000000000075161370065651000212720ustar00rootroot00000000000000if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Release") endif() project(iris) cmake_minimum_required(VERSION 3.1.0) #Set automoc and autouic policy if(POLICY CMP0071) if(${CMAKE_VERSION} VERSION_LESS "3.10.0") cmake_policy(SET CMP0071 OLD) message(STATUS "CMP0071 policy set to OLD") else() cmake_policy(SET CMP0071 NEW) message(STATUS "CMP0071 policy set to NEW") endif() endif() if(POLICY CMP0074) cmake_policy(SET CMP0074 NEW) message(STATUS "CMP0074 policy set to NEW") endif() set( IRIS_LIB_VERSION_MAJOR 1 ) set( IRIS_LIB_VERSION_MINOR 0 ) set( IRIS_LIB_VERSION_PATCH 0 ) set( IRIS_LIB_VERSION_STRING ${IRIS_LIB_VERSION_MAJOR}.${IRIS_LIB_VERSION_MINOR}.${IRIS_LIB_VERSION_PATCH} ) get_filename_component(ABS_PARENT_DIR "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE) set( CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules" "${ABS_PARENT_DIR}/cmake/modules" "${PROJECT_SOURCE_DIR}/cmake/modules" ) option( USE_QJDNS "Use qjdns/jdns library. Disabled by default for Qt5" OFF ) option( SEPARATE_QJDNS "Build qjdns with iris library" OFF ) set(CMAKE_CXX_STANDARD 14) include_directories(${PROJECT_SOURCE_DIR} ${PROJECT_BINARY_DIR}) include_directories(include/iris) set(CMAKE_AUTOMOC ON) find_package(Qt5 COMPONENTS Core Gui Xml Network REQUIRED) if(USE_QJDNS) message(WARNING "USE_QJDNS flag is enabled with Qt5. If you have problems with connection please disable this flag") endif() if(USE_QJDNS) add_definitions(-DNEED_JDNS) endif() find_package(Qca REQUIRED) set(QCA_INCLUDES ${Qca_INCLUDE_DIR}) set(qca_LIB ${Qca_LIBRARY}) include_directories( ${QCA_INCLUDES} ) if(NOT USE_QJDNS AND SEPARATE_QJDNS) message(FATAL_ERROR "SEPARATE_QJDNS flag enabled, but USE_QJDNS flag disabled.\nPlease enable flag USE_QJDNS or disable both flags") endif() if(USE_QJDNS AND (NOT SEPARATE_QJDNS)) add_definitions(-DJDNS_STATIC) set(QJDns_LIBRARY qjdns) include_directories( src/jdns/include/jdns ) set(jdns_SRCS src/jdns/src/jdns/jdns.c src/jdns/src/jdns/jdns_mdnsd.c src/jdns/src/jdns/jdns_packet.c src/jdns/src/jdns/jdns_sys.c src/jdns/src/jdns/jdns_util.c ) set(jdns_PUBLIC_HEADERS src/jdns/include/jdns/jdns.h src/jdns/include/jdns/jdns_export.h ) set(jdns_HEADERS src/jdns/src/jdns/jdns_packet.h src/jdns/src/jdns/jdns_mdnsd.h src/jdns/src/jdns/jdns_p.h ) add_library(jdns STATIC ${jdns_SRCS} ${jdns_HEADERS} ${jdns_PUBLIC_HEADERS}) if(WIN32) target_link_libraries(jdns ws2_32 advapi32) endif() set(qjdns_MOC_HDRS src/jdns/include/jdns/qjdns.h src/jdns/include/jdns/qjdnsshared.h src/jdns/src/qjdns/qjdns_p.h src/jdns/src/qjdns/qjdnsshared_p.h ) qt_wrap_cpp(qjdns_MOC_SRCS ${qjdns_MOC_HDRS}) set(qjdns_SRCS src/jdns/src/qjdns/qjdns.cpp src/jdns/src/qjdns/qjdns_sock.cpp src/jdns/src/qjdns/qjdnsshared.cpp ) set(qjdns_PUBLIC_HEADERS src/jdns/include/jdns/qjdns.h src/jdns/include/jdns/qjdnsshared.h ) set(qjdns_HEADERS src/jdns/src/qjdns/qjdns_sock.h ) add_library(${QJDns_LIBRARY} STATIC ${qjdns_SRCS} ${qjdns_MOC_SRCS} ${qjdns_MOC_HDRS} ${qjdns_PUBLIC_HEADERS}) target_link_libraries(${QJDns_LIBRARY} Qt5::Core Qt5::Network) target_link_libraries(${QJDns_LIBRARY} jdns) elseif(USE_QJDNS) set(QJDns_SUFFIX -qt5) find_package(QJDns REQUIRED) set(QJDns_LIBRARY ${QJDns_LIBRARY} PARENT_SCOPE) include_directories( ${QJDns_INCLUDE_DIR} ) endif() find_package(IDN REQUIRED) find_package(B2 QUIET) if(B2_FOUND) message(STATUS "Found B2: ${B2_LIBRARY}") endif() add_definitions(-DIRISNET_STATIC) add_subdirectory(src/irisnet) add_subdirectory(src/xmpp) psi-plus-snapshots-1.4.1456/iris/COPYING000066400000000000000000000635021370065651000175620ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! psi-plus-snapshots-1.4.1456/iris/IrisConfig.cmake.in000066400000000000000000000012521370065651000221640ustar00rootroot00000000000000# known at buildtime set(Iris_VERSION "@IRIS_LIB_VERSION_STRING@") set(Iris_VERSION_MAJOR @IRIS_LIB_VERSION_MAJOR@) set(Iris_VERSION_MINOR @IRIS_LIB_VERSION_MINOR@) set(Iris_VERSION_PATCH @IRIS_LIB_VERSION_PATCH@) get_filename_component(currentDir ${CMAKE_CURRENT_LIST_FILE} PATH) # get the directory where I myself am get_filename_component(rootDir ${currentDir}/@relInstallDir@ ABSOLUTE) # get the chosen install prefix # install locations set(Iris_INCLUDE_DIR "${rootDir}/@IRIS_INCLUDEDIR_REL@") set(Iris_LIBRARY_DIR "${rootDir}/@LIB_INSTALL_DIR_REL@") set(Iris_LIB_SONAME iris) if(NOT TARGET iris) include(${currentDir}/IrisTargets.cmake) endif() set(Iris_LIBRARY iris) psi-plus-snapshots-1.4.1456/iris/IrisConfigVersion.cmake.in000066400000000000000000000007241370065651000235350ustar00rootroot00000000000000set(PACKAGE_VERSION "@IRIS_LIB_VERSION_STRING@") if(PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION) set(PACKAGE_VERSION_EXACT TRUE) endif(PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION) if(NOT PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) set(PACKAGE_VERSION_COMPATIBLE TRUE) else(NOT PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) set(PACKAGE_VERSION_UNSUITABLE TRUE) endif(NOT PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) psi-plus-snapshots-1.4.1456/iris/README.md000066400000000000000000000043151370065651000200030ustar00rootroot00000000000000# Iris XMPP Library ## What is it? Iris is a comprehensive Qt/C++ library for working with the XMPP protocol. It complies with the XMPP RFCs and many XEPs. It should be useful for creating clients, servers, and other components. In addition to XMPP, Iris also offers access to many other protocols and concepts, including DNS, DNS-SD, connection and transport abstraction, HTTP, SOCKS, XML streaming, network interfaces, compression, encryption, authentication, flow control, proxies, and NAT traversal. ## License This library is licensed under the Lesser GNU General Public License. See the COPYING file for more information. ## What do I need to be able to use it? Iris depends on Qt and QCA. ## What features are supported? * Full support for draft-ietf-xmpp-core-21, including: * SRV DNS lookups * SSL/TLS security (both old way and ‘STARTTLS’ variations) * SASL authentication/encryption * Older jabber:iq:auth login * Resource binding * HTTP Connect, SOCKS5, and HTTP Polling proxy support * High-level objects for dealing with Stanzas and Streams * Parts of xmpp-im: * Message exchange * Presence broadcast / reciept * Roster management * Subscriptions * JEP extensions: * Version/Time requests * Service Discovery (disco, browse, and older ‘agents’ modes) * Account registration * Password changing * Agent/Transport registration * VCards * Basic Groupchat * OpenPGP capable * S5B Direct Connections * File Transfer * And probably more things I forgot to mention… ## Install First, build Iris: Unix: ```sh ./configure make ``` Windows: ``` copy conf_win.pri.example conf_win.pri copy confapp_win.pri.example confapp_win.pri qmake make (or nmake) ``` There is no installation. Just include iris.pri in your qmake project. Iris requires Qt 4.2 or greater and QCA 2.0 or greater. ## How does it work? (TODO) ## What is the development plan? 1. Finish basic server support for xmpp-core 2. Ensure xmpp-core fully matches the draft 3. Write API docs for xmpp-core 4. Write full xmpp-im API 5. Additional important specs: x:data, MUC, etc 6. ... ## What's up with the name 'Iris'? Iris is the Greek goddess of messaging and the rainbow. She works part-time delivering XML. psi-plus-snapshots-1.4.1456/iris/TODO000066400000000000000000000035221370065651000172130ustar00rootroot00000000000000netinterface use qca syncthread, match keystore in thread safety concerns "Interface ids obtained through NetInterfaceManager are guaranteed to be valid until the event loop resumes, or until the next call to interfaces() or interfaceForAddress()." ... the code seems to be lying about interfaceForAddress. netnames support faking srv (or perhaps any record) somehow, through config or code support multithreading put the netnames backend into an alternate thread (this thread should probably be some generic irisnet thing that other modules can use too) netnames front-end api communicates with the backend, so that one backend is shared by all threads NameResolver/ServiceBrowser/ServiceResolver should have isActive? report ServiceBrowser error codes report ServiceResolver error codes ServiceInstance attribs arg of constructor should be optional? ServiceInstance should cache the name() answer ServiceProvider should support error codes and passing many ip addresses ServiceResolver should give the hostnames and the ip addresses. hostnames may be needed for SASL consider reverse dns (for both internet and multicast) dns/bonjour/idn/dns-sd tcp/udp/rtp, bytestream abstraction protocol http/socks/ice/ocsp/crl lineproto, httpproto, xmlproto, binaryproto?, socksishproto? auth/cert/pgp parameter abstraction network interface detection simplified keystore handling? stringprep built-in simplified SASL digest-md5, plain?, anonymous? layer tracking & flow control both sync and async api threading capable proxies http/httpsconnect/httppoll/socks4,5/fakessl? dns/net/proxy/auth engine plugins? upnp/bonjour nat dodging / port opening? connector questions: 1) where does it end? 2) how would a pgp-auth for xmpp-core work? goal: support xmpp-core for tcp/httpbind xmpp-core should be "just another protocol" psi-plus-snapshots-1.4.1456/iris/cmake/000077500000000000000000000000001370065651000176015ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/cmake/modules/000077500000000000000000000000001370065651000212515ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/cmake/modules/COPYING-CMAKE-SCRIPTS000066400000000000000000000024571370065651000242570ustar00rootroot00000000000000Redistribution 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 copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. 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 AUTHOR ``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 AUTHOR 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. psi-plus-snapshots-1.4.1456/iris/cmake/modules/FindB2.cmake000066400000000000000000000051171370065651000233230ustar00rootroot00000000000000#============================================================================= # Copyright (C) 2016-2020 Psi+ Project, Vitaly Tonkacheyev # 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 copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. 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 AUTHOR ``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 AUTHOR 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. #============================================================================= if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND WIN32) set(D "d") endif() if( B2_INCLUDE_DIR AND B2_LIBRARY ) # in cache already set(B2_FIND_QUIETLY TRUE) endif() if( UNIX AND NOT( APPLE OR CYGWIN ) ) find_package( PkgConfig QUIET ) pkg_check_modules( PC_B2 QUIET libb2 ) if( PC_B2_FOUND ) set( B2_DEFINITIONS ${PC_B2_CFLAGS} ${PC_B2_CFLAGS_OTHER} ) endif() endif() find_path( B2_INCLUDE_DIR blake2.h HINTS ${B2_ROOT}/include ${PC_B2_INCLUDEDIR} ${PC_B2_INCLUDE_DIRS} ) set(B2_NAMES b2${D} ) find_library( B2_LIBRARY NAMES ${B2_NAMES} HINTS ${PC_B2_LIBDIR} ${PC_B2_LIBRARY_DIRS} ${B2_ROOT}/lib ${B2_ROOT}/bin ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( B2 DEFAULT_MSG B2_LIBRARY B2_INCLUDE_DIR ) if( B2_FOUND ) set( B2_LIBRARIES ${B2_LIBRARY} ) set( B2_INCLUDE_DIRS ${B2_INCLUDE_DIR} ) endif() mark_as_advanced( B2_INCLUDE_DIR B2_LIBRARY ) psi-plus-snapshots-1.4.1456/iris/cmake/modules/FindIDN.cmake000066400000000000000000000052121370065651000234660ustar00rootroot00000000000000#============================================================================= # Copyright (C) 2016-2017 Psi+ Project, Vitaly Tonkacheyev # 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 copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. 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 AUTHOR ``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 AUTHOR 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. #============================================================================= if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND WIN32) set(D "d") endif() if( IDN_INCLUDE_DIR AND IDN_LIBRARY ) # in cache already set(IDN_FIND_QUIETLY TRUE) endif() if( UNIX AND NOT( APPLE OR CYGWIN ) ) find_package( PkgConfig QUIET ) pkg_check_modules( PC_IDN QUIET libidn ) if( PC_IDN_FOUND ) set( IDN_DEFINITIONS ${PC_IDN_CFLAGS} ${PC_IDN_CFLAGS_OTHER} ) endif() endif() find_path( IDN_INCLUDE_DIR idna.h HINTS ${IDN_ROOT}/include ${PC_IDN_INCLUDEDIR} ${PC_IDN_INCLUDE_DIRS} ) set(IDN_NAMES idn${D} libidn${D} idn-11${D} ) find_library( IDN_LIBRARY NAMES ${IDN_NAMES} HINTS ${PC_IDN_LIBDIR} ${PC_IDN_LIBRARY_DIRS} ${IDN_ROOT}/lib ${IDN_ROOT}/bin ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( IDN DEFAULT_MSG IDN_LIBRARY IDN_INCLUDE_DIR ) if( IDN_FOUND ) set( IDN_LIBRARIES ${IDN_LIBRARY} ) set( IDN_INCLUDE_DIRS ${IDN_INCLUDE_DIR} ) endif() mark_as_advanced( IDN_INCLUDE_DIR IDN_LIBRARY ) psi-plus-snapshots-1.4.1456/iris/cmake/modules/FindQJDns.cmake000066400000000000000000000055711370065651000240430ustar00rootroot00000000000000#============================================================================= # Copyright (C) 2016-2017 Psi+ Project # 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 copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. 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 AUTHOR ``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 AUTHOR 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. #============================================================================= if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND WIN32) set(D "d") endif() if (QJDns_INCLUDE_DIR AND QJDns_LIBRARY) # in cache already set(QJDns_FIND_QUIETLY TRUE) endif () if ( UNIX AND NOT( APPLE OR CYGWIN ) ) find_package( PkgConfig QUIET ) pkg_check_modules( PC_QJDns QUIET jdns ) set ( QJDns_DEFINITIONS ${PC_QJDns_CFLAGS} ${PC_QJDns_CFLAGS_OTHER} ) endif ( UNIX AND NOT( APPLE OR CYGWIN ) ) set ( LIBINCS qjdns.h ) if(NOT QJDns_SUFFIX) set( QJDns_SUFFIX "") endif() find_path( QJDns_INCLUDE_DIR ${LIBINCS} HINTS ${QJDNS_DIR}/include ${PC_QJDns_INCLUDEDIR} ${PC_QJDns_INCLUDE_DIRS} PATH_SUFFIXES "" if( NOT WIN32 ) jdns endif( NOT WIN32 ) ) set(QJDns_NAMES qjdns${D} qjdns${QJDns_SUFFIX}${D} ) find_library( QJDns_LIBRARY NAMES ${QJDns_NAMES} HINTS ${PC_QJDns_LIBDIR} ${PC_QJDns_LIBRARY_DIRS} ${QJDNS_DIR}/lib ${QJDNS_DIR}/bin ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( QJDns DEFAULT_MSG QJDns_LIBRARY QJDns_INCLUDE_DIR ) if ( QJDns_FOUND ) set ( QJDns_LIBRARIES ${QJDns_LIBRARY} ) set ( QJDns_INCLUDE_DIRS ${QJDns_INCLUDE_DIR} ) endif ( QJDns_FOUND ) mark_as_advanced( QJDns_INCLUDE_DIR QJDns_LIBRARY ) psi-plus-snapshots-1.4.1456/iris/cmake/modules/FindQca.cmake000066400000000000000000000047241370065651000235670ustar00rootroot00000000000000#============================================================================= # Copyright 2016-2020 Psi+ Project, Vitaly Tonkacheyev # 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 copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. 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 AUTHOR ``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 AUTHOR 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. #============================================================================= if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND WIN32) set(D "d") endif() if (Qca_INCLUDE_DIR AND Qca_LIBRARY) # in cache already set(Qca_FIND_QUIETLY TRUE) endif() set(EXTRA_PATH_SUFFIXES qt5/Qca-qt5/QtCrypto Qca-qt5/QtCrypto qt5/QtCrypto qt/Qca-qt5/QtCrypto lib/qca-qt5.framework/Versions/2/Headers ) find_path( Qca_INCLUDE_DIR qca.h HINTS ${QCA_DIR}/include PATH_SUFFIXES QtCrypto ${EXTRA_PATH_SUFFIXES} ) find_library( Qca_LIBRARY NAMES qca-qt5${D} HINTS ${QCA_DIR}/lib ${QCA_DIR}/bin ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( Qca DEFAULT_MSG Qca_LIBRARY Qca_INCLUDE_DIR ) if (Qca_FOUND) set ( Qca_LIBRARIES ${Qca_LIBRARY} ) set ( Qca_INCLUDE_DIRS ${Qca_INCLUDE_DIR} ) endif() mark_as_advanced( Qca_INCLUDE_DIR Qca_LIBRARY ) psi-plus-snapshots-1.4.1456/iris/cmake_uninstall.cmake.in000066400000000000000000000014011370065651000232750ustar00rootroot00000000000000if(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") message(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") endif(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) string(REGEX REPLACE "\n" ";" files "${files}") foreach(file ${files}) message(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") exec_program( "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" OUTPUT_VARIABLE rm_out RETURN_VALUE rm_retval ) if(NOT "${rm_retval}" STREQUAL 0) message(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") endif(NOT "${rm_retval}" STREQUAL 0) endforeach(file) psi-plus-snapshots-1.4.1456/iris/common.pri000066400000000000000000000006421370065651000205270ustar00rootroot00000000000000# common stuff for iris.pro and iris.pri CONFIG -= c++11 CONFIG += c++14 # default build configuration !iris_build_pri { # build appledns on mac mac:CONFIG += appledns # bundle appledns inside of irisnetcore on mac mac:CONFIG += appledns_bundle # bundle irisnetcore inside of iris CONFIG += irisnetcore_bundle # don't build iris, app will include iris.pri #CONFIG += iris_bundle } psi-plus-snapshots-1.4.1456/iris/conf_win.pri.example000066400000000000000000000004321370065651000224700ustar00rootroot00000000000000load(winlocal.prf) # qca CONFIG += crypto # zlib INCLUDEPATH += $$WINLOCAL_PREFIX/include LIBS += -L$$WINLOCAL_PREFIX/lib # zlib may have a different lib name depending on how it was compiled win32-g++ { LIBS += -lz } else { LIBS += -lzlib # static #LIBS += -lzdll # dll } psi-plus-snapshots-1.4.1456/iris/confapp.pri000066400000000000000000000002611370065651000206620ustar00rootroot00000000000000unix:exists(confapp_unix.pri):include(confapp_unix.pri) windows:exists(confapp_win.pri):include(confapp_win.pri) include(common.pri) mac:QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.9 psi-plus-snapshots-1.4.1456/iris/confapp_win.pri.example000066400000000000000000000004321370065651000231710ustar00rootroot00000000000000load(winlocal.prf) # qca CONFIG += crypto # zlib INCLUDEPATH += $$WINLOCAL_PREFIX/include LIBS += -L$$WINLOCAL_PREFIX/lib # zlib may have a different lib name depending on how it was compiled win32-g++ { LIBS += -lz } else { LIBS += -lzlib # static #LIBS += -lzdll # dll } psi-plus-snapshots-1.4.1456/iris/configure000077500000000000000000002150761370065651000204430ustar00rootroot00000000000000#!/bin/sh # # Generated by qconf 2.0 ( https://github.com/psi-plus/qconf ) # show_usage() { cat </dev/null` if echo $WHICH | grep 'shell built-in command' >/dev/null 2>&1; then WHICH=which elif [ -z "$WHICH" ]; then if which which >/dev/null 2>&1; then WHICH=which else for a in /usr/ucb /usr/bin /bin /usr/local/bin; do if [ -x $a/which ]; then WHICH=$a/which break fi done fi fi RET_CODE=1 if [ -z "$WHICH" ]; then OLD_IFS=$IFS IFS=: for a in $PATH; do if [ -x $a/$1 ]; then echo "$a/$1" RET_CODE=0 [ -z "$ALL_MATCHES" ] && break fi done IFS=$OLD_IFS export IFS else a=`"$WHICH" "$ALL_MATCHES" "$1" 2>/dev/null` if [ ! -z "$a" -a -x "$a" ]; then echo "$a" RET_CODE=0 fi fi HOME=$OLD_HOME export HOME return $RET_CODE } WHICH=which_command # find a make command if [ -z "$MAKE" ]; then MAKE= for mk in gmake make; do if $WHICH $mk >/dev/null 2>&1; then MAKE=`$WHICH $mk` break fi done if [ -z "$MAKE" ]; then echo "You don't seem to have 'make' or 'gmake' in your PATH." echo "Cannot proceed." exit 1 fi fi show_qt_info() { printf "Be sure you have a proper Qt 4.0+ build environment set up. This means not\n" printf "just Qt, but also a C++ compiler, a make tool, and any other packages\n" printf "necessary for compiling C++ programs.\n" printf "\n" printf "If you are certain everything is installed, then it could be that Qt is not\n" printf "being recognized or that a different version of Qt is being detected by\n" printf "mistake (for example, this could happen if \$QTDIR is pointing to a Qt 3\n" printf "installation). At least one of the following conditions must be satisfied:\n" printf "\n" printf " 1) --qtdir is set to the location of Qt\n" printf " 2) \$QTDIR is set to the location of Qt\n" printf " 3) QtCore is in the pkg-config database\n" printf " 4) qmake is in the \$PATH\n" printf "\n" printf "This script will use the first one it finds to be true, checked in the above\n" printf "order. #3 and #4 are the recommended options. #1 and #2 are mainly for\n" printf "overriding the system configuration.\n" printf "\n" } while [ $# -gt 0 ]; do optarg=`expr "x$1" : 'x[^=]*=\(.*\)'` case "$1" in --qtdir=*) EX_QTDIR=$optarg shift ;; --static) QC_STATIC="Y" shift ;; --extraconf=*) QC_EXTRACONF=$optarg shift ;; --release) QC_RELEASE="Y" shift ;; --debug) QC_DEBUG="Y" shift ;; --debug-and-release) QC_DEBUG_AND_RELEASE="Y" shift ;; --no-separate-debug-info) QC_NO_SEPARATE_DEBUG_INFO="Y" shift ;; --separate-debug-info) QC_SEPARATE_DEBUG_INFO="Y" shift ;; --no-framework) QC_NO_FRAMEWORK="Y" shift ;; --framework) QC_FRAMEWORK="Y" shift ;; --universal) QC_UNIVERSAL="Y" shift ;; --mac-sdk=*) QC_MAC_SDK=$optarg shift ;; --enable-tests) QC_ENABLE_TESTS="Y" shift ;; --with-idn-inc=*) QC_WITH_IDN_INC=$optarg shift ;; --with-idn-lib=*) QC_WITH_IDN_LIB=$optarg shift ;; --with-qca-inc=*) QC_WITH_QCA_INC=$optarg shift ;; --with-qca-lib=*) QC_WITH_QCA_LIB=$optarg shift ;; --with-zlib-inc=*) QC_WITH_ZLIB_INC=$optarg shift ;; --with-zlib-lib=*) QC_WITH_ZLIB_LIB=$optarg shift ;; --with-qjdns-inc=*) QC_WITH_QJDNS_INC=$optarg shift ;; --with-qjdns-lib=*) QC_WITH_QJDNS_LIB=$optarg shift ;; --with-b2-inc=*) QC_WITH_B2_INC=$optarg shift ;; --with-b2-lib=*) QC_WITH_B2_LIB=$optarg shift ;; --verbose) QC_VERBOSE="Y" shift ;; --qtselect=*) QC_QTSELECT="${optarg}" shift ;; --help) show_usage; exit ;; *) echo "configure: WARNING: unrecognized options: $1" >&2; shift; ;; esac done echo "Configuring Iris ..." if [ "$QC_VERBOSE" = "Y" ]; then echo echo EX_QTDIR=$EX_QTDIR echo QC_STATIC=$QC_STATIC echo QC_EXTRACONF=$QC_EXTRACONF echo QC_RELEASE=$QC_RELEASE echo QC_DEBUG=$QC_DEBUG echo QC_DEBUG_AND_RELEASE=$QC_DEBUG_AND_RELEASE echo QC_NO_SEPARATE_DEBUG_INFO=$QC_NO_SEPARATE_DEBUG_INFO echo QC_SEPARATE_DEBUG_INFO=$QC_SEPARATE_DEBUG_INFO echo QC_NO_FRAMEWORK=$QC_NO_FRAMEWORK echo QC_FRAMEWORK=$QC_FRAMEWORK echo QC_UNIVERSAL=$QC_UNIVERSAL echo QC_MAC_SDK=$QC_MAC_SDK echo QC_ENABLE_TESTS=$QC_ENABLE_TESTS echo QC_WITH_IDN_INC=$QC_WITH_IDN_INC echo QC_WITH_IDN_LIB=$QC_WITH_IDN_LIB echo QC_WITH_QCA_INC=$QC_WITH_QCA_INC echo QC_WITH_QCA_LIB=$QC_WITH_QCA_LIB echo QC_WITH_ZLIB_INC=$QC_WITH_ZLIB_INC echo QC_WITH_ZLIB_LIB=$QC_WITH_ZLIB_LIB echo QC_WITH_QJDNS_INC=$QC_WITH_QJDNS_INC echo QC_WITH_QJDNS_LIB=$QC_WITH_QJDNS_LIB echo QC_WITH_B2_INC=$QC_WITH_B2_INC echo QC_WITH_B2_LIB=$QC_WITH_B2_LIB echo fi printf "Verifying Qt build environment ... " if [ -z "$QC_QTSELECT" ]; then QC_QTSELECT="$(echo $QT_SELECT | tr -d "qt")" fi if [ ! -z "$QC_QTSELECT" ]; then QTSEARCHTEXT="$QC_QTSELECT" else QTSEARCHTEXT="4 or 5" fi # run qmake and check version qmake_check() { if [ -x "$1" ]; then cmd="\"$1\" -query QT_VERSION" if [ "$QC_VERBOSE" = "Y" ]; then echo "running: $cmd" fi vout=`/bin/sh -c "$cmd" 2>&1` case "${vout}" in *.*.*) vmaj="${vout%%.*}" if [ ! -z "$QC_QTSELECT" ]; then if [ "$vmaj" = "$QC_QTSELECT" ]; then return 0 fi else if [ "$vmaj" = "4" ] || [ "$vmaj" = "5" ]; then return 0 fi fi ;; esac if [ "$QC_VERBOSE" = "Y" ]; then echo "Warning: $1 not for Qt ${QTSEARCHTEXT}" fi fi return 1 } if [ "$QC_VERBOSE" = "Y" ]; then echo fi qm="" qt4_names="qmake-qt4 qmake4" qt5_names="qmake-qt5 qmake5" names="qmake" if [ -z "$QC_QTSELECT" ]; then names="${qt5_names} ${qt4_names} $names" else if [ "$QC_QTSELECT" = "4" ]; then names="${qt4_names} $names" elif [ "$QC_QTSELECT" -ge "5" ]; then names="${qt5_names} $names" fi fi if [ -z "$qm" ] && [ ! -z "$EX_QTDIR" ]; then # qt4 check: --qtdir for n in $names; do qstr=$EX_QTDIR/bin/$n if qmake_check "$qstr"; then qm=$qstr break fi done if [ -z "$qm" ] && [ "$QC_VERBOSE" = "Y" ]; then echo "Warning: qmake not found via --qtdir" fi elif [ -z "$qm" ] && [ ! -z "$QTDIR" ]; then # qt4 check: QTDIR for n in $names; do qstr=$QTDIR/bin/$n if qmake_check "$qstr"; then qm=$qstr break fi done if [ -z "$qm" ] && [ "$QC_VERBOSE" = "Y" ]; then echo "Warning: qmake not found via \$QTDIR" fi else # Try all other implicit checks # qtchooser if [ -z "$qm" ]; then qtchooser=$($WHICH qtchooser 2>/dev/null) if [ ! -z "$qtchooser" ]; then if [ ! -z "$QC_QTSELECT" ]; then versions="$QC_QTSELECT" else cmd="$qtchooser --list-versions" if [ "$QC_VERBOSE" = "Y" ]; then echo "running: $cmd" fi versions=`$cmd` fi for version in $versions; do cmd="$qtchooser -run-tool=qmake -qt=${version} -query QT_INSTALL_BINS" if [ "$QC_VERBOSE" = "Y" ]; then echo "running: $cmd" fi qtbins=`$cmd 2>/dev/null` if [ ! -z "$qtbins" ] && qmake_check "$qtbins/qmake"; then qm="$qtbins/qmake" break fi done fi fi if [ -z "$qm" ] && [ "$QC_VERBOSE" = "Y" ]; then echo "Warning: qmake not found via qtchooser" fi # qt4: pkg-config if [ -z "$qm" ]; then cmd="pkg-config QtCore --variable=exec_prefix" if [ "$QC_VERBOSE" = "Y" ]; then echo "running: $cmd" fi str=`$cmd 2>/dev/null` if [ ! -z "$str" ]; then for n in $names; do qstr=$str/bin/$n if qmake_check "$qstr"; then qm=$qstr break fi done fi fi if [ -z "$qm" ] && [ "$QC_VERBOSE" = "Y" ]; then echo "Warning: qmake not found via pkg-config" fi # qmake in PATH if [ -z "$qm" ]; then for n in $names; do qstr=`$WHICH -a $n 2>/dev/null` for q in $qstr; do if qmake_check "$q"; then qm="$q" break fi done if [ ! -z "$qm" ]; then break fi done fi if [ -z "$qm" ] && [ "$QC_VERBOSE" = "Y" ]; then echo "Warning: qmake not found via \$PATH" fi # end of implicit checks fi if [ -z "$qm" ]; then if [ "$QC_VERBOSE" = "Y" ]; then echo " -> fail" else echo "fail" fi printf "\n" printf "Reason: Unable to find the 'qmake' tool for Qt ${QTSEARCHTEXT}.\n" printf "\n" show_qt_info exit 1; fi if [ "$QC_VERBOSE" = "Y" ]; then echo qmake found in "$qm" fi # try to determine the active makespec defmakespec=$QMAKESPEC if [ -z "$defmakespec" ]; then if $WHICH readlink >/dev/null 2>&1; then READLINK=`$WHICH readlink` fi if [ ! -z "$READLINK" ]; then qt_mkspecsdir=`"$qm" -query QT_INSTALL_DATA`/mkspecs if [ -d "$qt_mkspecsdir" ] && [ -h "$qt_mkspecsdir/default" ]; then defmakespec=`$READLINK $qt_mkspecsdir/default` fi fi fi if [ "$QC_VERBOSE" = "Y" ]; then echo makespec is $defmakespec fi qm_spec="" # if the makespec is macx-xcode, force macx-g++ if [ "$defmakespec" = "macx-xcode" ]; then qm_spec=macx-g++ QMAKESPEC=$qm_spec export QMAKESPEC if [ "$QC_VERBOSE" = "Y" ]; then echo overriding makespec to $qm_spec fi fi gen_files() { cat >"$1/modules.cpp" <= 4.2 -----END QCMOD----- */ class qc_qt42 : public ConfObj { public: qc_qt42(Conf *c) : ConfObj(c) {} QString name() const { return "Qt >= 4.2"; } QString shortname() const { return "qt42"; } bool exec() { conf->debug(QString("QT_VERSION = 0x%1").arg(QString::number(QT_VERSION, 16))); if(QT_VERSION >= 0x040200) return true; else return false; } }; #line 1 "buildmode.qcm" /* -----BEGIN QCMOD----- name: buildmode section: project arg: release,Build with debugging turned off (default). arg: debug,Build with debugging turned on. arg: debug-and-release,Build two versions, with and without debugging turned on (mac only). arg: no-separate-debug-info,Do not store debug information in a separate file (default for mac). arg: separate-debug-info,Strip debug information into a separate .debug file (default for non-mac). arg: no-framework,Do not build as a Mac framework. arg: framework,Build as a Mac framework (default). -----END QCMOD----- */ #define QC_BUILDMODE bool qc_buildmode_release = false; bool qc_buildmode_debug = false; bool qc_buildmode_framework = false; bool qc_buildmode_separate_debug_info = false; class qc_buildmode : public ConfObj { public: qc_buildmode(Conf *c) : ConfObj(c) {} QString name() const { return "buildmode"; } QString shortname() const { return "buildmode"; } // no output QString checkString() const { return QString(); } bool exec() { // first, parse out the options bool opt_release = false; bool opt_debug = false; bool opt_debug_and_release = false; bool opt_no_framework = false; bool opt_framework = false; bool opt_no_separate_debug_info = false; bool opt_separate_debug_info = false; if(conf->getenv("QC_RELEASE") == "Y") opt_release = true; if(conf->getenv("QC_DEBUG") == "Y") opt_debug = true; if(conf->getenv("QC_DEBUG_AND_RELEASE") == "Y") opt_debug_and_release = true; if(conf->getenv("QC_NO_FRAMEWORK") == "Y") opt_no_framework = true; if(conf->getenv("QC_FRAMEWORK") == "Y") opt_framework = true; if(conf->getenv("QC_NO_SEPARATE_DEBUG_INFO") == "Y") opt_no_separate_debug_info = true; if(conf->getenv("QC_SEPARATE_DEBUG_INFO") == "Y") opt_separate_debug_info = true; bool staticmode = false; if(conf->getenv("QC_STATIC") == "Y") staticmode = true; #ifndef Q_OS_MAC if(opt_debug_and_release) { printf("\\nError: The --debug-and-release option is for mac only.\\n"); exit(1); } if(opt_framework) { printf("\\nError: The --framework option is for mac only.\\n"); exit(1); } #endif if(opt_framework && opt_debug) { printf("\\nError: Cannot use both --framework and --debug.\\n"); exit(1); } // sanity check exclusive options int x; // build mode x = 0; if(opt_release) ++x; if(opt_debug) ++x; if(opt_debug_and_release) ++x; if(x > 1) { printf("\\nError: Use only one of --release, --debug, or --debug-and-release.\\n"); exit(1); } // framework if(opt_framework && staticmode) { printf("\\nError: Cannot use both --framework and --static.\\n"); exit(1); } x = 0; if(opt_no_framework) ++x; if(opt_framework) ++x; if(x > 1) { printf("\\nError: Use only one of --framework or --no-framework.\\n"); exit(1); } // debug info x = 0; if(opt_no_separate_debug_info) ++x; if(opt_separate_debug_info) ++x; if(x > 1) { printf("\\nError: Use only one of --separate-debug-info or --no-separate-debug-info\\n"); exit(1); } // now process the options if(opt_release) qc_buildmode_release = true; else if(opt_debug) qc_buildmode_debug = true; else if(opt_debug_and_release) { qc_buildmode_release = true; qc_buildmode_debug = true; } else // default qc_buildmode_release = true; if(opt_framework) qc_buildmode_framework = true; else if(opt_no_framework) { // nothing to do } else // default { if(!staticmode && !opt_debug) qc_buildmode_framework = true; } if(opt_separate_debug_info) qc_buildmode_separate_debug_info = true; else if(opt_no_separate_debug_info) { // nothing to do } else // default { #ifndef Q_OS_MAC qc_buildmode_separate_debug_info = true; #endif } // make the string QStringList opts; QString other; if(qc_buildmode_release && qc_buildmode_debug) { opts += "debug_and_release"; opts += "build_all"; } else if(qc_buildmode_release) opts += "release"; else // qc_buildmode_debug opts += "debug"; #ifdef Q_OS_MAC if(qc_buildmode_framework) opts += "lib_bundle"; #endif if(qc_buildmode_separate_debug_info) { opts += "separate_debug_info"; other += "QMAKE_CFLAGS += -g\\n"; other += "QMAKE_CXXFLAGS += -g\\n"; } QString str = QString("CONFIG += ") + opts.join(" ") + '\\n'; conf->addExtra(str); if(!other.isEmpty()) conf->addExtra(other); return true; } }; #line 1 "universal.qcm" /* -----BEGIN QCMOD----- name: Mac universal binary support section: project arg: universal,Build with Mac universal binary support. arg: mac-sdk=[path],Path to Mac universal SDK (PPC host only). -----END QCMOD----- */ #define QC_UNIVERSAL bool qc_universal_enabled = false; QString qc_universal_sdk; //---------------------------------------------------------------------------- // qc_universal //---------------------------------------------------------------------------- class qc_universal : public ConfObj { public: qc_universal(Conf *c) : ConfObj(c) {} QString name() const { return "Mac universal binary support"; } QString shortname() const { return "universal"; } QString checkString() const { return QString(); } bool exec() { #ifdef Q_OS_MAC if(qc_getenv("QC_UNIVERSAL") == "Y") { qc_universal_enabled = true; QString str = "contains(QT_CONFIG,x86):contains(QT_CONFIG,ppc) {\\n" " CONFIG += x86 ppc\\n" "}\\n"; QString sdk = qc_getenv("QC_MAC_SDK"); if(!sdk.isEmpty()) { str += QString("QMAKE_MAC_SDK = %1\\n").arg(sdk); qc_universal_sdk = sdk; } conf->addExtra(str); } #endif return true; } }; #line 1 "idn.qcm" /* -----BEGIN QCMOD----- name: libidn arg: with-idn-inc=[path],Path to libidn include files arg: with-idn-lib=[path],Path to libidn library or framework files -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_idn //---------------------------------------------------------------------------- class qc_idn : public ConfObj { public: qc_idn(Conf *c) : ConfObj(c) {} QString name() const { return "LibIDN"; } QString shortname() const { return "libidn"; } bool exec() { QString idn_incdir, idn_libdir; idn_incdir = conf->getenv("QC_WITH_IDN_INC"); idn_libdir = conf->getenv("QC_WITH_IDN_LIB"); if (!idn_incdir.isEmpty() || !idn_libdir.isEmpty()) { // prefer this if given if ((!idn_incdir.isEmpty() && conf->checkHeader(idn_incdir, "stringprep.h")) || (idn_incdir.isEmpty() && conf->findHeader("stringprep.h", QStringList(), &idn_incdir))) { conf->addIncludePath(idn_incdir); } else { printf("Headers not found!\\n"); return false; } if((!idn_libdir.isEmpty() && conf->checkLibrary(idn_libdir, "idn")) || (idn_libdir.isEmpty() && conf->findLibrary("idn", &idn_libdir))) { conf->addLib(idn_libdir.isEmpty()? "-lidn" : QString("-L%1 -lidn").arg(idn_libdir)); } else { printf("Libraries not found!\\n"); return false; } return true; } QStringList incs; QString version, libs, other; if(conf->findPkgConfig("libidn", VersionAny, QString::null, &version, &incs, &libs, &other)) { for(int n = 0; n < incs.count(); ++n) conf->addIncludePath(incs[n]); if(!libs.isEmpty()) conf->addLib(libs); return true; } return false; } }; #line 1 "qca.qcm" /* -----BEGIN QCMOD----- name: QCA >= 2.0 arg: with-qca-inc=[path],Path to QCA include files arg: with-qca-lib=[path],Path to QCA library or framework files -----END QCMOD----- */ // adapted from crypto.prf static QString internal_crypto_prf(const QString &incdir, const QString &libdir, const QString &frameworkdir) { QString out = QString( "QCA_INCDIR = %1\\n" "QCA_LIBDIR = %2\\n" "QMAKE_RPATHDIR = %2\\n" "QCA_FRAMEWORKDIR = %3\\n" "\\n" "CONFIG *= qt\\n" "\\n" "LINKAGE =\\n" "QCA_NAME = qca-qt5\\n" "\\n" "!isEmpty(QCA_FRAMEWORKDIR): {\\n" " framework_dir = \$\$QCA_FRAMEWORKDIR\\n" " exists(\$\$framework_dir/\$\${QCA_NAME}.framework) {\\n" " #QMAKE_FRAMEWORKPATH *= \$\$framework_dir\\n" " LIBS *= -F\$\$framework_dir\\n" " INCLUDEPATH += \$\$framework_dir/\$\${QCA_NAME}.framework/Headers\\n" " LINKAGE = -framework \$\${QCA_NAME}\\n" " }\\n" "}\\n" "\\n" "# else, link normally\\n" "isEmpty(LINKAGE) {\\n" " !isEmpty(QCA_INCDIR):INCLUDEPATH += \$\$QCA_INCDIR/QtCrypto\\n" " !isEmpty(QCA_LIBDIR):LIBS += -L\$\$QCA_LIBDIR\\n" " LINKAGE = -l\$\${QCA_NAME}\\n" " CONFIG(debug, debug|release) {\\n" " windows:LINKAGE = -l\$\${QCA_NAME}d\\n" " mac:LINKAGE = -l\$\${QCA_NAME}_debug\\n" " }\\n" "}\\n" "\\n" "LIBS += \$\$LINKAGE\\n" ).arg(incdir, libdir, frameworkdir); return out; } // set either libdir or frameworkdir, but not both static bool qca_try(Conf *conf, const QString &incdir, const QString &libdir, const QString &frameworkdir, bool release, bool debug, QString *_prf) { QString proextra; QString prf; if (!incdir.isEmpty() || !libdir.isEmpty() || !frameworkdir.isEmpty()) { prf = internal_crypto_prf(conf->escapePath(incdir), conf->escapePath(libdir), frameworkdir); } else { prf = "CONFIG += crypto\\n"; } proextra = "CONFIG += qt\\n" "CONFIG -= debug_and_release debug release\\n" "QT -= gui\\n"; proextra += prf; QString str = "#include \\n" "\\n" "int main()\\n" "{\\n" " unsigned long x = QCA_VERSION;\\n" " if(x >= 0x020000 && x < 0x030000) return 0; else return 1;\\n" "}\\n"; // test desired versions, potentially both release and debug if(release) { int ret; if(!conf->doCompileAndLink(str, QStringList(), QString(), proextra + "CONFIG += release\\n", &ret) || ret != 0) return false; } if(debug) { int ret; if(!conf->doCompileAndLink(str, QStringList(), QString(), proextra + "CONFIG += debug\\n", &ret) || ret != 0) return false; } *_prf = prf; return true; } static bool qca_try_lib(Conf *conf, const QString &incdir, const QString &libdir, bool release, bool debug, QString *prf) { return qca_try(conf, incdir, libdir, QString(), release, debug, prf) || qca_try(conf, incdir + "/Qca-qt5", libdir, QString(), release, debug, prf); } static bool qca_try_framework(Conf *conf, const QString &frameworkdir, bool release, bool debug, QString *prf) { return qca_try(conf, QString(), QString(), frameworkdir, release, debug, prf); } static bool qca_try_ext_prf(Conf *conf, bool release, bool debug, QString *prf) { return qca_try(conf, QString(), QString(), QString(), release, debug, prf); } //---------------------------------------------------------------------------- // qc_qca //---------------------------------------------------------------------------- class qc_qca : public ConfObj { public: qc_qca(Conf *c) : ConfObj(c) {} QString name() const { return "QCA >= 2.0"; } QString shortname() const { return "qca"; } bool exec() { // get the build mode #ifdef QC_BUILDMODE bool release = qc_buildmode_release; bool debug = qc_buildmode_debug; #else // else, default to just release mode bool release = true; bool debug = false; #endif QString qca_incdir, qca_libdir, qca_crypto_prf; qca_incdir = conf->getenv("QC_WITH_QCA_INC"); qca_libdir = conf->getenv("QC_WITH_QCA_LIB"); #if defined(Q_OS_MAC) if(!qca_libdir.isEmpty() && qca_try_framework(conf, qca_libdir, release, debug, &qca_crypto_prf)) { conf->addExtra(qca_crypto_prf); return true; } #endif if(!qca_incdir.isEmpty() && !qca_libdir.isEmpty() && qca_try_lib(conf, qca_incdir, qca_libdir, release, debug, &qca_crypto_prf)) { conf->addExtra(qca_crypto_prf); return true; } if (qca_try_ext_prf(conf, release, debug, &qca_crypto_prf)) { conf->addExtra(qca_crypto_prf); return true; } QStringList incs; QString version, libs, other; if(conf->findPkgConfig("qca2-qt5", VersionMin, "2.0.0", &version, &incs, &libs, &other)) { for(int n = 0; n < incs.count(); ++n) conf->addIncludePath(incs[n]); if(!libs.isEmpty()) conf->addLib(libs); return true; } QStringList prefixes; #ifndef Q_OS_WIN prefixes += "/usr"; prefixes += "/usr/local"; #endif QString prefix = conf->getenv("PREFIX"); if (!prefix.isEmpty()) { prefixes += prefix; } for(int n = 0; n < prefixes.count(); ++n) { const QString &prefix = prefixes[n]; if(qca_try_lib(conf, prefix + "/include", prefix + "/lib", release, debug, &qca_crypto_prf)) { conf->addExtra(qca_crypto_prf); return true; } } return false; } }; #line 1 "zlib.qcm" /* -----BEGIN QCMOD----- name: zlib arg: with-zlib-inc=[path],Path to zlib include files arg: with-zlib-lib=[path],Path to zlib library files -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_zlib //---------------------------------------------------------------------------- class qc_zlib : public ConfObj { public: qc_zlib(Conf *c) : ConfObj(c) {} QString name() const { return "zlib"; } QString shortname() const { return "zlib"; } bool exec() { QString inc, lib; QString s; s = conf->getenv("QC_WITH_ZLIB_INC"); if(!s.isEmpty()) { if(!conf->checkHeader(s, "zlib.h")) return false; inc = s; } else { if(!conf->findHeader("zlib.h", QStringList(), &s)) return false; inc = s; } s = conf->getenv("QC_WITH_ZLIB_LIB"); if(!s.isEmpty()) { if(!conf->checkLibrary(s, "z")) return false; lib = s; } else { if(!conf->findLibrary("z", &s)) return false; lib = s; } if(!inc.isEmpty()) conf->addIncludePath(inc); if(!lib.isEmpty()) conf->addLib(QString("-L") + s); conf->addLib("-lz"); return true; } }; #line 1 "extra.qcm" /* -----BEGIN QCMOD----- name: extra section: project arg: enable-tests,Build examples and unittests. -----END QCMOD----- */ class qc_extra : public ConfObj { public: qc_extra(Conf *c) : ConfObj(c) {} QString name() const { return "extra"; } QString shortname() const { return "extra"; } // no output QString checkString() const { return QString(); } bool exec() { QString str; QFile f; if(conf->getenv("QC_ENABLE_TESTS") == "Y") str += "CONFIG += iris_tests\\n"; conf->addExtra(str); bool release = true; bool debug = false; bool debug_info = false; bool universal = false; QString sdk; #ifdef QC_BUILDMODE release = qc_buildmode_release; debug = qc_buildmode_debug; debug_info = qc_buildmode_separate_debug_info; #endif #ifdef QC_UNIVERSAL universal = qc_universal_enabled; sdk = qc_universal_sdk; #endif // write confapp_unix.pri str = QString(); QString var = conf->getenv("BINDIR"); if(!var.isEmpty()) str += QString("BINDIR = %1\\n").arg(var); if(debug) // debug or debug-and-release str += QString("CONFIG += debug\\n"); else // release str += QString("CONFIG += release\\n"); if(debug_info) { str += QString("CONFIG += separate_debug_info\\n"); str += "QMAKE_CFLAGS += -g\\n"; str += "QMAKE_CXXFLAGS += -g\\n"; } if(universal) { str += "contains(QT_CONFIG,x86):contains(QT_CONFIG,ppc) {\\n" " CONFIG += x86 ppc\\n" "}\\n"; if(!sdk.isEmpty()) str += QString("QMAKE_MAC_SDK = %1\\n").arg(sdk); } #ifdef QC_QCA if(!qc_qca_procode.isEmpty()) str += qc_qca_procode; #endif f.setFileName("confapp_unix.pri"); if(f.open(QFile::WriteOnly | QFile::Truncate)) f.write(str.toLatin1()); f.close(); return true; } }; #line 1 "qjdns.qcm" /* -----BEGIN QCMOD----- name: jdns arg: with-qjdns-inc=[path],Path to QJDns include files arg: with-qjdns-lib=[path],Path to QJDns library files -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_qjdns //---------------------------------------------------------------------------- class qc_qjdns : public ConfObj { public: qc_qjdns(Conf *c) : ConfObj(c) {} QString name() const { return "QJDns"; } QString shortname() const { return "qjdns"; } QString resultString() const { #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) return "Disabled for Qt5 and above"; #else return ConfObj::resultString(); #endif } bool exec() { #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) return true; // hack. TODO: figure out how to force jdns #endif conf->addExtra("CONFIG += need_jdns"); #if defined Q_OS_WIN || defined Q_OS_MAC // HACK: on Windows and Mac OS X, always use psi's bundled qjdns conf->addExtra("CONFIG += iris-qjdns"); return true; #else QStringList incs; QString version, libs, other; QString s; #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) bool found = conf->findPkgConfig("qjdns-qt5", VersionMin, "2.0.3", &version, &incs, &libs, &other); #else bool found = conf->findPkgConfig("qjdns-qt4", VersionMin, "2.0.3", &version, &incs, &libs, &other); #endif if(!found && !conf->findPkgConfig("qjdns", VersionMin, "2.0.0", &version, &incs, &libs, &other)) { s = conf->getenv("QC_WITH_QJDNS_INC"); if ((!s.isEmpty() && conf->checkHeader(s, "qjdns.h")) || (s.isEmpty() && conf->findHeader("qjdns.h", QStringList(), &s))) { incs.append(s); } s = conf->getenv("QC_WITH_QJDNS_LIB"); if((!s.isEmpty() && conf->checkLibrary(s, "qjdns")) || (s.isEmpty() && conf->findLibrary("qjdns", &s))) { libs = s.isEmpty()? "-lqjdns -ljdns" : QString("-L%1 -lqjdns -ljdns").arg(s); } } if (!incs.isEmpty() && !libs.isEmpty()) { foreach(const QString &inc, incs) { conf->addIncludePath(inc); } conf->addLib(libs); conf->addExtra("CONFIG += ext-qjdns"); } return true; #endif } }; #line 1 "b2.qcm" /* -----BEGIN QCMOD----- name: libb2 arg: with-b2-inc=[path],Path to libb2 include files arg: with-b2-lib=[path],Path to libb2 library or framework files -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_b2 //---------------------------------------------------------------------------- class qc_b2 : public ConfObj { bool use_system = false; public: qc_b2(Conf *c) : ConfObj(c) {} QString name() const { return "LibB2"; } QString shortname() const { return "libb2"; } QString resultString() const { return use_system? "system":"bundled"; } bool exec() { QString b2_incdir, b2_libdir; b2_incdir = conf->getenv("QC_WITH_B2_INC"); b2_libdir = conf->getenv("QC_WITH_B2_LIB"); if (b2_incdir.isEmpty() && b2_libdir.isEmpty()) { if (!checkCustomDirs(b2_incdir, b2_libdir)) { printf("b2 search paths provided but library is not found there. use bundled"); return true; } use_system = true; } else { QStringList incs; QString version, libs, other; if(conf->findPkgConfig("libb2", VersionAny, QString::null, &version, &incs, &libs, &other)) { for(int n = 0; n < incs.count(); ++n) conf->addIncludePath(incs[n]); if(!libs.isEmpty()) conf->addLib(libs); use_system = true; } } if (use_system) { conf->addExtra("CONFIG += bundled_blake2"); } return true; } bool checkCustomDirs(QString &b2_incdir, QString &b2_libdir) { if ((b2_incdir.isEmpty() && !conf->findHeader("blake2.h", QStringList(), &b2_incdir)) || (!b2_incdir.isEmpty() && !conf->checkHeader(b2_incdir, "blake2.h"))) { printf("Headers not found!\\n"); return false; } if((!b2_libdir.isEmpty() && conf->checkLibrary(b2_libdir, "b2")) || (b2_libdir.isEmpty() && conf->findLibrary("b2", &b2_libdir))) { conf->addLib(b2_libdir.isEmpty()? "-lb2" : QString("-L%1 -lb2").arg(b2_libdir)); conf->addIncludePath(b2_incdir); return true; } printf("Libraries not found!\\n"); return false; } }; EOT cat >"$1/modules_new.cpp" <required = true; o->disabled = false; o = new qc_buildmode(conf); o->required = true; o->disabled = false; o = new qc_universal(conf); o->required = true; o->disabled = false; o = new qc_idn(conf); o->required = true; o->disabled = false; o = new qc_qca(conf); o->required = true; o->disabled = false; o = new qc_zlib(conf); o->required = true; o->disabled = false; o = new qc_extra(conf); o->required = true; o->disabled = false; o = new qc_qjdns(conf); o->required = true; o->disabled = false; o = new qc_b2(conf); o->required = true; o->disabled = false; EOT cat >"$1/conf4.h" < class Conf; enum VersionMode { VersionMin, VersionExact, VersionMax, VersionAny }; // ConfObj // // Subclass ConfObj to create a new configuration module. class ConfObj { public: Conf *conf; bool required; bool disabled; bool success; ConfObj(Conf *c); virtual ~ConfObj(); // long or descriptive name of what is being checked/performed // example: "KDE >= 3.3" virtual QString name() const = 0; // short name // example: "kde" virtual QString shortname() const = 0; // string to display during check // default: "Checking for [name] ..." virtual QString checkString() const; // string to display after check // default: "yes" or "no", based on result of exec() virtual QString resultString() const; // this is where the checking code goes virtual bool exec() = 0; }; // Conf // // Interact with this class from your ConfObj to perform detection // operations and to output configuration parameters. class Conf { public: bool debug_enabled; QString qmake_path; QString qmakespec; QString maketool; QString DEFINES; QStringList INCLUDEPATH; QStringList LIBS; QString extra; QList list; QMap vars; Conf(); ~Conf(); QString getenv(const QString &var); QString qvar(const QString &s); QString normalizePath(const QString &s) const; QString escapeQmakeVar(const QString &s) const; inline QString escapePath(const QString &s) /* prepare fs path for qmake file */ { return escapeQmakeVar(normalizePath(s)); } QString escapedIncludes() const; QString escapedLibs() const; bool exec(); void debug(const QString &s); QString expandIncludes(const QString &inc); QString expandLibs(const QString &lib); int doCommand(const QString &s, QByteArray *out = 0); int doCommand(const QString &prog, const QStringList &args, QByteArray *out = 0); bool doCompileAndLink(const QString &filedata, const QStringList &incs, const QString &libs, const QString &proextra, int *retcode = 0); bool checkHeader(const QString &path, const QString &h); bool findHeader(const QString &h, const QStringList &ext, QString *inc); bool checkLibrary(const QString &path, const QString &name); bool findLibrary(const QString &name, QString *lib); QString findProgram(const QString &prog); bool findSimpleLibrary(const QString &incvar, const QString &libvar, const QString &incname, const QString &libname, QString *incpath, QString *libs); bool findFooConfig(const QString &path, QString *version, QStringList *incs, QString *libs, QString *otherflags); bool findPkgConfig(const QString &name, VersionMode mode, const QString &req_version, QString *version, QStringList *incs, QString *libs, QString *otherflags); void addDefine(const QString &str); void addLib(const QString &str); void addIncludePath(const QString &str); void addExtra(const QString &str); private: bool first_debug; friend class ConfObj; void added(ConfObj *o); }; #endif EOT cat >"$1/conf4.cpp" < #include #include #ifndef PATH_MAX #ifdef Q_OS_WIN #define PATH_MAX 260 #endif #endif class MocTestObject : public QObject { Q_OBJECT public: MocTestObject() {} }; QString qc_getenv(const QString &var) { char *p = ::getenv(var.toLatin1().data()); if (!p) return QString(); return QString(p); } QStringList qc_pathlist() { QStringList list; QString path = qc_getenv("PATH"); if (!path.isEmpty()) { #ifdef Q_OS_WIN list = path.split(';', QString::SkipEmptyParts); #else list = path.split(':', QString::SkipEmptyParts); #endif } #ifdef Q_OS_WIN list.prepend("."); #endif return list; } QString qc_findprogram(const QString &prog) { QString out; QStringList list = qc_pathlist(); for (int n = 0; n < list.count(); ++n) { QFileInfo fi(list[n] + '/' + prog); if (fi.exists() && fi.isExecutable()) { out = fi.filePath(); break; } #ifdef Q_OS_WIN // on windows, be sure to look for .exe if (prog.right(4).toLower() != ".exe") { fi = QFileInfo(list[n] + '/' + prog + ".exe"); if (fi.exists() && fi.isExecutable()) { out = fi.filePath(); break; } } #endif } return out; } QString qc_findself(const QString &argv0) { #ifdef Q_OS_WIN if (argv0.contains('\\\\')) #else if (argv0.contains('/')) #endif return argv0; else return qc_findprogram(argv0); } int qc_run_program_or_command(const QString &prog, const QStringList &args, const QString &command, QByteArray *out, bool showOutput) { if (out) out->clear(); QProcess process; process.setReadChannel(QProcess::StandardOutput); if (!prog.isEmpty()) process.start(prog, args); else if (!command.isEmpty()) process.start(command); else return -1; if (!process.waitForStarted(-1)) return -1; QByteArray buf; while (process.waitForReadyRead(-1)) { buf = process.readAllStandardOutput(); if (out) out->append(buf); if (showOutput) fprintf(stdout, "%s", buf.data()); buf = process.readAllStandardError(); if (showOutput) fprintf(stderr, "%s", buf.data()); } buf = process.readAllStandardError(); if (showOutput) fprintf(stderr, "%s", buf.data()); // calling waitForReadyRead will cause the process to eventually be // marked as finished, so we should not need to separately call // waitForFinished. however, we will do it anyway just to be safe. // we won't check the return value since false could still mean // success (if the process had already been marked as finished). process.waitForFinished(-1); if (process.exitStatus() != QProcess::NormalExit) return -1; return process.exitCode(); } int qc_runcommand(const QString &command, QByteArray *out, bool showOutput) { return qc_run_program_or_command(QString(), QStringList(), command, out, showOutput); } int qc_runprogram(const QString &prog, const QStringList &args, QByteArray *out, bool showOutput) { return qc_run_program_or_command(prog, args, QString(), out, showOutput); } bool qc_removedir(const QString &dirPath) { QDir dir(dirPath); if (!dir.exists()) return false; QStringList list = dir.entryList(); foreach (QString s, list) { if (s == "." || s == "..") continue; QFileInfo fi(dir.filePath(s)); if (fi.isDir()) { if (!qc_removedir(fi.filePath())) return false; } else { if (!dir.remove(s)) return false; } } QString dirName = dir.dirName(); if (!dir.cdUp()) return false; if (!dir.rmdir(dirName)) return false; return true; } // simple command line arguemnts splitter able to understand quoted args. // the splitter removes quotes and unescapes symbols as well. QStringList qc_splitflags(const QString &flags) { QStringList ret; bool searchStart = true; bool inQuotes = false; bool escaped = false; QChar quote, backslash = QLatin1Char('\\\\'); QString buf; #ifdef PATH_MAX buf.reserve(PATH_MAX); #endif for (int i = 0; i < flags.length(); i++) { if (searchStart && flags[i].isSpace()) { continue; } if (searchStart) { searchStart = false; buf.clear(); } if (escaped) { buf += flags[i]; escaped = false; continue; } // buf += flags[i]; if (inQuotes) { if (quote == QLatin1Char('\\'')) { if (flags[i] == quote) { inQuotes = false; continue; } } else { // we are in double quoetes if (flags[i] == backslash && i < flags.length() - 1 && (flags[i + 1] == QLatin1Char('"') || flags[i + 1] == backslash)) { // if next symbol is one of in parentheses ("\\) escaped = true; continue; } } } else { if (flags[i].isSpace()) { ret.append(buf); searchStart = true; buf.clear(); continue; #ifndef Q_OS_WIN /* on windows backslash is just a path separator */ } else if (flags[i] == backslash) { escaped = true; continue; // just add next symbol #endif } else if (flags[i] == QLatin1Char('\\'') || flags[i] == QLatin1Char('"')) { inQuotes = true; quote = flags[i]; continue; } } buf += flags[i]; } if (buf.size()) { ret.append(buf); } return ret; } void qc_splitcflags(const QString &cflags, QStringList *incs, QStringList *otherflags) { incs->clear(); otherflags->clear(); QStringList cflagsList = qc_splitflags(cflags); for (int n = 0; n < cflagsList.count(); ++n) { QString str = cflagsList[n]; if (str.startsWith("-I")) { // we want everything except the leading "-I" incs->append(str.remove(0, 2)); } else { // we want whatever is left otherflags->append(str); } } } QString qc_escapeArg(const QString &str) { QString out; for (int n = 0; n < (int)str.length(); ++n) { if (str[n] == '-') out += '_'; else out += str[n]; } return out; } QString qc_trim_char(const QString &s, const QChar &ch) { if (s.startsWith(ch) && s.endsWith(ch)) { return s.mid(1, s.size() - 2); } return s; } // removes surrounding quotes, removes trailing slashes, converts to native separators. // accepts unescaped but possible quoted path QString qc_normalize_path(const QString &str) { QString path = str.trimmed(); path = qc_trim_char(path, QLatin1Char('"')); path = qc_trim_char(path, QLatin1Char('\\'')); // It's OK to use unix style'/' paths on windows Qt handles this without any problems. // Using Windows-style '\\\\' can leads strange compilation error with MSYS which uses // unix style. QLatin1Char nativeSep('/'); #ifdef Q_OS_WIN path.replace(QLatin1Char('\\\\'), QLatin1Char('/')); #endif // trim trailing slashes while (path.length() && path[path.length() - 1] == nativeSep) { path.resize(path.length() - 1); } return path; } // escape filesystem path to be added to qmake pro/pri file. QString qc_escape_string_var(const QString &str) { QString path = str; path.replace(QLatin1Char('\\\\'), QLatin1String("\\\\\\\\")).replace(QLatin1Char('"'), QLatin1String("\\\\\\"")); if (path.indexOf(QLatin1Char(' ')) != -1) { // has spaces return QLatin1Char('"') + path + QLatin1Char('"'); } return path; } // escapes each path in incs and join into single string suiable for INCLUDEPATH var QString qc_prepare_includepath(const QStringList &incs) { if (incs.empty()) { return QString(); } QStringList ret; foreach (const QString &path, incs) { ret.append(qc_escape_string_var(path)); } return ret.join(QLatin1String(" ")); } // escapes each path in libs and to make it suiable for LIBS var // notice, entries of libs are every single arg for linker. QString qc_prepare_libs(const QStringList &libs) { if (libs.isEmpty()) { return QString(); } QSet pathSet; QStringList paths; QStringList ordered; foreach (const QString &arg, libs) { if (arg.startsWith(QLatin1String("-L"))) { QString path = qc_escape_string_var(arg.mid(2)); if (!pathSet.contains(path)) { pathSet.insert(path); paths.append(path); } } else if (arg.startsWith(QLatin1String("-l"))) { ordered.append(arg); } else { ordered.append(qc_escape_string_var(arg)); } } QString ret; if (paths.size()) { ret += (QLatin1String(" -L") + paths.join(QLatin1String(" -L")) + QLatin1Char(' ')); } return ret + ordered.join(QLatin1String(" ")); } //---------------------------------------------------------------------------- // ConfObj //---------------------------------------------------------------------------- ConfObj::ConfObj(Conf *c) { conf = c; conf->added(this); required = false; disabled = false; success = false; } ConfObj::~ConfObj() {} QString ConfObj::checkString() const { return QString("Checking for %1 ...").arg(name()); } QString ConfObj::resultString() const { if (success) return "yes"; else return "no"; } //---------------------------------------------------------------------------- // qc_internal_pkgconfig //---------------------------------------------------------------------------- class qc_internal_pkgconfig : public ConfObj { public: QString pkgname, desc; VersionMode mode; QString req_ver; qc_internal_pkgconfig(Conf *c, const QString &_name, const QString &_desc, VersionMode _mode, const QString &_req_ver) : ConfObj(c), pkgname(_name), desc(_desc), mode(_mode), req_ver(_req_ver) { } QString name() const { return desc; } QString shortname() const { return pkgname; } bool exec() { QStringList incs; QString version, libs, other; if (!conf->findPkgConfig(pkgname, mode, req_ver, &version, &incs, &libs, &other)) return false; for (int n = 0; n < incs.count(); ++n) conf->addIncludePath(incs[n]); if (!libs.isEmpty()) conf->addLib(libs); // if(!other.isEmpty()) // conf->addExtra(QString("QMAKE_CFLAGS += %1\\n").arg(other)); if (!required) conf->addDefine("HAVE_PKG_" + qc_escapeArg(pkgname).toUpper()); return true; } }; //---------------------------------------------------------------------------- // Conf //---------------------------------------------------------------------------- Conf::Conf() { // TODO: no more vars? // vars.insert("QMAKE_INCDIR_X11", new QString(X11_INC)); // vars.insert("QMAKE_LIBDIR_X11", new QString(X11_LIBDIR)); // vars.insert("QMAKE_LIBS_X11", new QString(X11_LIB)); // vars.insert("QMAKE_CC", CC); debug_enabled = false; first_debug = true; } Conf::~Conf() { qDeleteAll(list); } void Conf::added(ConfObj *o) { list.append(o); } QString Conf::getenv(const QString &var) { return qc_getenv(var); } void Conf::debug(const QString &s) { if (debug_enabled) { if (first_debug) printf("\\n"); first_debug = false; printf(" * %s\\n", qPrintable(s)); } } bool Conf::exec() { for (int n = 0; n < list.count(); ++n) { ConfObj *o = list[n]; // if this was a disabled-by-default option, check if it was enabled if (o->disabled) { QString v = QString("QC_ENABLE_") + qc_escapeArg(o->shortname()); if (getenv(v) != "Y") continue; } // and the opposite? else { QString v = QString("QC_DISABLE_") + qc_escapeArg(o->shortname()); if (getenv(v) == "Y") continue; } bool output = true; QString check = o->checkString(); if (check.isEmpty()) output = false; if (output) { printf("%s", check.toLatin1().data()); fflush(stdout); } first_debug = true; bool ok = o->exec(); o->success = ok; if (output) { QString result = o->resultString(); if (!first_debug) printf(" -> %s\\n", result.toLatin1().data()); else printf(" %s\\n", result.toLatin1().data()); } if (!ok && o->required) { printf("\\nError: need %s!\\n", o->name().toLatin1().data()); return false; } } return true; } QString Conf::qvar(const QString &s) { return vars.value(s); } QString Conf::normalizePath(const QString &s) const { return qc_normalize_path(s); } QString Conf::escapeQmakeVar(const QString &s) const { return qc_escape_string_var(s); } QString Conf::escapedIncludes() const { return qc_prepare_includepath(INCLUDEPATH); } QString Conf::escapedLibs() const { return qc_prepare_libs(LIBS); } QString Conf::expandIncludes(const QString &inc) { return QLatin1String("-I") + inc; } QString Conf::expandLibs(const QString &lib) { return QLatin1String("-L") + lib; } int Conf::doCommand(const QString &s, QByteArray *out) { debug(QString("[%1]").arg(s)); int r = qc_runcommand(s, out, debug_enabled); debug(QString("returned: %1").arg(r)); return r; } int Conf::doCommand(const QString &prog, const QStringList &args, QByteArray *out) { QString fullcmd = prog; QString argstr = args.join(QLatin1String(" ")); if (!argstr.isEmpty()) fullcmd += QString(" ") + argstr; debug(QString("[%1]").arg(fullcmd)); int r = qc_runprogram(prog, args, out, debug_enabled); debug(QString("returned: %1").arg(r)); return r; } bool Conf::doCompileAndLink(const QString &filedata, const QStringList &incs, const QString &libs, const QString &proextra, int *retcode) { #ifdef Q_OS_WIN QDir tmp("qconftemp"); #else QDir tmp(".qconftemp"); #endif QStringList normalizedLibs; foreach (const QString &l, qc_splitflags(libs)) { normalizedLibs.append(qc_normalize_path(l)); } if (!tmp.mkdir("atest")) { debug(QString("unable to create atest dir: %1").arg(tmp.absoluteFilePath("atest"))); return false; } QDir dir(tmp.filePath("atest")); if (!dir.exists()) { debug("atest dir does not exist"); return false; } QString fname = dir.filePath("atest.cpp"); QString out = "atest"; QFile f(fname); if (!f.open(QFile::WriteOnly | QFile::Truncate)) { debug("unable to open atest.cpp for writing"); return false; } if (f.write(filedata.toLatin1()) == -1) { debug("error writing to atest.cpp"); return false; } f.close(); debug(QString("Wrote atest.cpp:\\n%1").arg(filedata)); QString pro = QString("CONFIG += console\\n" "CONFIG -= qt app_bundle\\n" "DESTDIR = \$\$PWD\\n" "SOURCES += atest.cpp\\n"); QString inc = qc_prepare_includepath(incs); if (!inc.isEmpty()) pro += "INCLUDEPATH += " + inc + '\\n'; QString escaped_libs = qc_prepare_libs(normalizedLibs); if (!escaped_libs.isEmpty()) pro += "LIBS += " + escaped_libs + '\\n'; pro += proextra; fname = dir.filePath("atest.pro"); f.setFileName(fname); if (!f.open(QFile::WriteOnly | QFile::Truncate)) { debug("unable to open atest.pro for writing"); return false; } if (f.write(pro.toLatin1()) == -1) { debug("error writing to atest.pro"); return false; } f.close(); debug(QString("Wrote atest.pro:\\n%1").arg(pro)); QString oldpath = QDir::currentPath(); QDir::setCurrent(dir.path()); bool ok = false; int r = doCommand(qmake_path, QStringList() << "atest.pro"); if (r == 0) { r = doCommand(maketool, QStringList()); if (r == 0) { ok = true; if (retcode) { QString runatest = out; #ifdef Q_OS_UNIX runatest.prepend("./"); #endif *retcode = doCommand(runatest, QStringList()); } } r = doCommand(maketool, QStringList() << "distclean"); if (r != 0) debug("error during atest distclean"); } QDir::setCurrent(oldpath); // cleanup // dir.remove("atest.pro"); // dir.remove("atest.cpp"); // tmp.rmdir("atest"); // remove whole dir since distclean doesn't always work qc_removedir(tmp.filePath("atest")); if (!ok) return false; return true; } bool Conf::checkHeader(const QString &path, const QString &h) { return QDir(path).exists(h); } bool Conf::findHeader(const QString &h, const QStringList &ext, QString *inc) { if (checkHeader("/usr/include", h)) { *inc = ""; return true; } QStringList dirs; dirs += "/usr/local/include"; dirs += ext; QString prefix = qc_getenv("PREFIX"); if (!prefix.isEmpty()) { prefix += "/include"; prefix = qc_normalize_path(prefix); if (!dirs.contains(prefix)) dirs << prefix; } for (QStringList::ConstIterator it = dirs.begin(); it != dirs.end(); ++it) { if (checkHeader(*it, h)) { *inc = *it; return true; } } return false; } bool Conf::checkLibrary(const QString &path, const QString &name) { QString str = //"#include \\n" "int main()\\n" "{\\n" //" printf(\\"library checker running\\\\\\\\n\\");\\n" " return 0;\\n" "}\\n"; QString libs; if (!path.isEmpty()) libs += QString("-L") + path + ' '; libs += QString("-l") + name; if (!doCompileAndLink(str, QStringList(), libs, QString())) return false; return true; } bool Conf::findLibrary(const QString &name, QString *lib) { if (checkLibrary("", name)) { *lib = ""; return true; } if (checkLibrary("/usr/local/lib", name)) { *lib = "/usr/local/lib"; return true; } QString prefix = qc_getenv("PREFIX"); if (!prefix.isEmpty()) { prefix += "/lib"; prefix = qc_normalize_path(prefix); if (checkLibrary(prefix, name)) { *lib = prefix; return true; } } return false; } QString Conf::findProgram(const QString &prog) { return qc_findprogram(prog); } bool Conf::findSimpleLibrary(const QString &incvar, const QString &libvar, const QString &incname, const QString &libname, QString *incpath, QString *libs) { QString inc, lib; QString s; s = getenv(incvar).trimmed(); if (!s.isEmpty()) { if (!checkHeader(s, incname)) { if (debug_enabled) printf("%s is not found in \\"%s\\"\\n", qPrintable(incname), qPrintable(s)); return false; } inc = s; } else { if (!findHeader(incname, QStringList(), &s)) { if (debug_enabled) printf("%s is not found anywhere\\n", qPrintable(incname)); return false; } inc = s; } s = getenv(libvar).trimmed(); if (!s.isEmpty()) { if (!checkLibrary(s, libname)) { if (debug_enabled) printf("%s is not found in \\"%s\\"\\n", qPrintable(libname), qPrintable(s)); return false; } lib = s; } else { if (!findLibrary(libname, &s)) { if (debug_enabled) printf("%s is not found anywhere\\n", qPrintable(libname)); return false; } lib = s; } QString lib_out; if (!lib.isEmpty()) lib_out += QString("-L") + s + " "; lib_out += QString("-l") + libname; *incpath = inc; *libs = lib_out; return true; } bool Conf::findFooConfig(const QString &path, QString *version, QStringList *incs, QString *libs, QString *otherflags) { QStringList args; QByteArray out; int ret; args += "--version"; ret = doCommand(path, args, &out); if (ret != 0) return false; QString version_out = QString::fromLatin1(out).trimmed(); args.clear(); args += "--libs"; ret = doCommand(path, args, &out); if (ret != 0) return false; QString libs_out = QString::fromLatin1(out).trimmed(); args.clear(); args += "--cflags"; ret = doCommand(path, args, &out); if (ret != 0) return false; QString cflags = QString::fromLatin1(out).trimmed(); QStringList incs_out, otherflags_out; qc_splitcflags(cflags, &incs_out, &otherflags_out); *version = version_out; *incs = incs_out; *libs = libs_out; *otherflags = otherflags_out.join(QLatin1String(" ")); return true; } bool Conf::findPkgConfig(const QString &name, VersionMode mode, const QString &req_version, QString *version, QStringList *incs, QString *libs, QString *otherflags) { QStringList args; QByteArray out; int ret; args += name; args += "--exists"; ret = doCommand("pkg-config", args, &out); if (ret != 0) return false; if (mode != VersionAny) { args.clear(); args += name; if (mode == VersionMin) args += QString("--atleast-version=%1").arg(req_version); else if (mode == VersionMax) args += QString("--max-version=%1").arg(req_version); else args += QString("--exact-version=%1").arg(req_version); ret = doCommand("pkg-config", args, &out); if (ret != 0) return false; } args.clear(); args += name; args += "--modversion"; ret = doCommand("pkg-config", args, &out); if (ret != 0) return false; QString version_out = QString::fromLatin1(out).trimmed(); args.clear(); args += name; args += "--libs"; ret = doCommand("pkg-config", args, &out); if (ret != 0) return false; QString libs_out = QString::fromLatin1(out).trimmed(); args.clear(); args += name; args += "--cflags"; ret = doCommand("pkg-config", args, &out); if (ret != 0) return false; QString cflags = QString::fromLatin1(out).trimmed(); QStringList incs_out, otherflags_out; qc_splitcflags(cflags, &incs_out, &otherflags_out); *version = version_out; *incs = incs_out; *libs = libs_out; *otherflags = otherflags_out.join(QLatin1String(" ")); return true; } void Conf::addDefine(const QString &str) { if (DEFINES.isEmpty()) DEFINES = str; else DEFINES += QString(" ") + str; debug(QString("DEFINES += %1").arg(str)); } void Conf::addLib(const QString &str) { QStringList libs = qc_splitflags(str); foreach (const QString &lib, libs) { if (lib.startsWith("-l")) { LIBS.append(lib); } else { LIBS.append(qc_normalize_path(lib)); // we don't care about -L prefix since normalier does not touch it. } } debug(QString("LIBS += %1").arg(str)); } void Conf::addIncludePath(const QString &str) { INCLUDEPATH.append(qc_normalize_path(str)); debug(QString("INCLUDEPATH += %1").arg(str)); } void Conf::addExtra(const QString &str) { extra += str + '\\n'; debug(QString("extra += %1").arg(str)); } //---------------------------------------------------------------------------- // main //---------------------------------------------------------------------------- #include "conf4.moc" #ifdef HAVE_MODULES #include "modules.cpp" #endif int main(int argc, char **argv) { QCoreApplication app(argc, argv); Conf * conf = new Conf; ConfObj * o = 0; Q_UNUSED(o); #ifdef HAVE_MODULES #include "modules_new.cpp" #endif conf->debug_enabled = (qc_getenv("QC_VERBOSE") == "Y") ? true : false; if (conf->debug_enabled) printf(" -> ok\\n"); else printf("ok\\n"); QString confCommand = qc_getenv("QC_COMMAND"); QString proName = qc_getenv("QC_PROFILE"); conf->qmake_path = qc_getenv("QC_QMAKE"); conf->qmakespec = qc_getenv("QC_QMAKESPEC"); conf->maketool = qc_getenv("QC_MAKETOOL"); if (conf->debug_enabled) printf("conf command: [%s]\\n", qPrintable(confCommand)); QString confPath = qc_findself(confCommand); if (confPath.isEmpty()) { printf("Error: cannot find myself; rerun with an absolute path\\n"); return 1; } QString srcdir = QFileInfo(confPath).absolutePath(); QString builddir = QDir::current().absolutePath(); QString proPath = QDir(srcdir).filePath(proName); if (conf->debug_enabled) { printf("conf path: [%s]\\n", qPrintable(confPath)); printf("srcdir: [%s]\\n", qPrintable(srcdir)); printf("builddir: [%s]\\n", qPrintable(builddir)); printf("profile: [%s]\\n", qPrintable(proPath)); printf("qmake path: [%s]\\n", qPrintable(conf->qmake_path)); printf("qmakespec: [%s]\\n", qPrintable(conf->qmakespec)); printf("make tool: [%s]\\n", qPrintable(conf->maketool)); printf("\\n"); } bool success = false; if (conf->exec()) { QFile f("conf.pri"); if (!f.open(QFile::WriteOnly | QFile::Truncate)) { printf("Error writing %s\\n", qPrintable(f.fileName())); return 1; } QString str; str += "# qconf\\n\\n"; str += "greaterThan(QT_MAJOR_VERSION, 4):CONFIG += c++11\\n"; QString var; var = qc_normalize_path(qc_getenv("PREFIX")); if (!var.isEmpty()) str += QString("PREFIX = %1\\n").arg(var); var = qc_normalize_path(qc_getenv("BINDIR")); if (!var.isEmpty()) str += QString("BINDIR = %1\\n").arg(var); var = qc_normalize_path(qc_getenv("INCDIR")); if (!var.isEmpty()) str += QString("INCDIR = %1\\n").arg(var); var = qc_normalize_path(qc_getenv("LIBDIR")); if (!var.isEmpty()) str += QString("LIBDIR = %1\\n").arg(var); var = qc_normalize_path(qc_getenv("DATADIR")); if (!var.isEmpty()) str += QString("DATADIR = %1\\n").arg(var); str += '\\n'; if (qc_getenv("QC_STATIC") == "Y") str += "CONFIG += staticlib\\n"; // TODO: don't need this? // str += "QT_PATH_PLUGINS = " + QString(qInstallPathPlugins()) + '\\n'; if (!conf->DEFINES.isEmpty()) str += "DEFINES += " + conf->DEFINES + '\\n'; if (!conf->INCLUDEPATH.isEmpty()) str += "INCLUDEPATH += " + qc_prepare_includepath(conf->INCLUDEPATH) + '\\n'; if (!conf->LIBS.isEmpty()) str += "LIBS += " + qc_prepare_libs(conf->LIBS) + '\\n'; if (!conf->extra.isEmpty()) str += conf->extra; str += '\\n'; var = qc_getenv("QC_EXTRACONF"); if (!var.isEmpty()) str += ("\\n# Extra conf from command line\\n" + var + "\\n"); QByteArray cs = str.toLatin1(); f.write(cs); f.close(); success = true; } QString qmake_path = conf->qmake_path; QString qmakespec = conf->qmakespec; delete conf; if (!success) return 1; // run qmake on the project file QStringList args; if (!qmakespec.isEmpty()) { args += "-spec"; args += qmakespec; } args += proPath; int ret = qc_runprogram(qmake_path, args, 0, true); if (ret != 0) return 1; return 0; } EOT cat >"$1/conf4.pro" </dev/null else "$qm" conf4.pro >/dev/null fi $MAKE clean >/dev/null 2>&1 $MAKE >../conf.log 2>&1 ) if [ "$?" != "0" ]; then rm -rf ".qconftemp" if [ "$QC_VERBOSE" = "Y" ]; then echo " -> fail" else echo "fail" fi printf "\n" printf "Reason: There was an error compiling 'conf'. See conf.log for details.\n" printf "\n" show_qt_info if [ "$QC_VERBOSE" = "Y" ]; then echo "conf.log:" cat conf.log fi exit 1; fi QC_COMMAND=$0 export QC_COMMAND QC_PROFILE=iris.pro export QC_PROFILE QC_QMAKE="$qm" export QC_QMAKE QC_QMAKESPEC=$qm_spec export QC_QMAKESPEC QC_MAKETOOL=$MAKE export QC_MAKETOOL ".qconftemp/conf" ret="$?" if [ "$ret" = "1" ]; then rm -rf ".qconftemp" echo exit 1; else if [ "$ret" != "0" ]; then rm -rf ".qconftemp" if [ "$QC_VERBOSE" = "Y" ]; then echo " -> fail" else echo "fail" fi echo echo "Reason: Unexpected error launching 'conf'" echo exit 1; fi fi rm -rf ".qconftemp" echo echo "Good, your configure finished. Now run $MAKE." echo psi-plus-snapshots-1.4.1456/iris/include/000077500000000000000000000000001370065651000201445ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/include/iris/000077500000000000000000000000001370065651000211125ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/include/iris/addressresolver.h000066400000000000000000000000671370065651000244750ustar00rootroot00000000000000#include "../../src/irisnet/corelib/addressresolver.h" psi-plus-snapshots-1.4.1456/iris/include/iris/bsocket.h000066400000000000000000000000711370065651000227130ustar00rootroot00000000000000#include "../../src/irisnet/noncore/cutestuff/bsocket.h" psi-plus-snapshots-1.4.1456/iris/include/iris/bytestream.h000066400000000000000000000000741370065651000234430ustar00rootroot00000000000000#include "../../src/irisnet/noncore/cutestuff/bytestream.h" psi-plus-snapshots-1.4.1456/iris/include/iris/filetransfer.h000066400000000000000000000000611370065651000237440ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/filetransfer.h" psi-plus-snapshots-1.4.1456/iris/include/iris/httpconnect.h000066400000000000000000000000751370065651000236160ustar00rootroot00000000000000#include "../../src/irisnet/noncore/cutestuff/httpconnect.h" psi-plus-snapshots-1.4.1456/iris/include/iris/httpfileupload.h000066400000000000000000000000631370065651000243060ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/httpfileupload.h" psi-plus-snapshots-1.4.1456/iris/include/iris/httppoll.h000066400000000000000000000000721370065651000231300ustar00rootroot00000000000000#include "../../src/irisnet/noncore/cutestuff/httppoll.h" psi-plus-snapshots-1.4.1456/iris/include/iris/ice176.h000066400000000000000000000000561370065651000222620ustar00rootroot00000000000000#include "../../src/irisnet/noncore/ice176.h" psi-plus-snapshots-1.4.1456/iris/include/iris/iceagent.h000066400000000000000000000000601370065651000230360ustar00rootroot00000000000000#include "../../src/irisnet/noncore/iceagent.h" psi-plus-snapshots-1.4.1456/iris/include/iris/im.h000066400000000000000000000000471370065651000216710ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/im.h" psi-plus-snapshots-1.4.1456/iris/include/iris/irisnetexport.h000066400000000000000000000000651370065651000242030ustar00rootroot00000000000000#include "../../src/irisnet/corelib/irisnetexport.h" psi-plus-snapshots-1.4.1456/iris/include/iris/irisnetglobal.h000066400000000000000000000000651370065651000241220ustar00rootroot00000000000000#include "../../src/irisnet/corelib/irisnetglobal.h" psi-plus-snapshots-1.4.1456/iris/include/iris/irisnetplugin.h000066400000000000000000000000651370065651000241600ustar00rootroot00000000000000#include "../../src/irisnet/corelib/irisnetplugin.h" psi-plus-snapshots-1.4.1456/iris/include/iris/jingle-application.h000066400000000000000000000000671370065651000250370ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/jingle-application.h" psi-plus-snapshots-1.4.1456/iris/include/iris/jingle-ft.h000066400000000000000000000000561370065651000231430ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/jingle-ft.h" psi-plus-snapshots-1.4.1456/iris/include/iris/jingle-ice.h000066400000000000000000000000571370065651000232730ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/jingle-ice.h" psi-plus-snapshots-1.4.1456/iris/include/iris/jingle-nstransportslist.h000066400000000000000000000000741370065651000262060ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/jingle-nstransportslist.h" psi-plus-snapshots-1.4.1456/iris/include/iris/jingle-s5b.h000066400000000000000000000000571370065651000232240ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/jingle-s5b.h" psi-plus-snapshots-1.4.1456/iris/include/iris/jingle-session.h000066400000000000000000000000631370065651000242130ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/jingle-session.h" psi-plus-snapshots-1.4.1456/iris/include/iris/jingle-transport.h000066400000000000000000000000651370065651000245660ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/jingle-transport.h" psi-plus-snapshots-1.4.1456/iris/include/iris/jingle.h000066400000000000000000000000531370065651000225310ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/jingle.h" psi-plus-snapshots-1.4.1456/iris/include/iris/ndns.h000066400000000000000000000000631370065651000222240ustar00rootroot00000000000000#include "../../src/irisnet/noncore/legacy/ndns.h" psi-plus-snapshots-1.4.1456/iris/include/iris/netavailability.h000066400000000000000000000000671370065651000244470ustar00rootroot00000000000000#include "../../src/irisnet/corelib/netavailability.h" psi-plus-snapshots-1.4.1456/iris/include/iris/netinterface.h000066400000000000000000000000641370065651000237320ustar00rootroot00000000000000#include "../../src/irisnet/corelib/netinterface.h" psi-plus-snapshots-1.4.1456/iris/include/iris/netnames.h000066400000000000000000000000601370065651000230710ustar00rootroot00000000000000#include "../../src/irisnet/corelib/netnames.h" psi-plus-snapshots-1.4.1456/iris/include/iris/objectsession.h000066400000000000000000000000651370065651000241360ustar00rootroot00000000000000#include "../../src/irisnet/corelib/objectsession.h" psi-plus-snapshots-1.4.1456/iris/include/iris/processquit.h000066400000000000000000000000631370065651000236430ustar00rootroot00000000000000#include "../../src/irisnet/noncore/processquit.h" psi-plus-snapshots-1.4.1456/iris/include/iris/s5b.h000066400000000000000000000000501370065651000217470ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/s5b.h" psi-plus-snapshots-1.4.1456/iris/include/iris/socks.h000066400000000000000000000000671370065651000224100ustar00rootroot00000000000000#include "../../src/irisnet/noncore/cutestuff/socks.h" psi-plus-snapshots-1.4.1456/iris/include/iris/srvresolver.h000066400000000000000000000000721370065651000236560ustar00rootroot00000000000000#include "../../src/irisnet/noncore/legacy/srvresolver.h" psi-plus-snapshots-1.4.1456/iris/include/iris/stunallocate.h000066400000000000000000000000641370065651000237610ustar00rootroot00000000000000#include "../../src/irisnet/noncore/stunallocate.h" psi-plus-snapshots-1.4.1456/iris/include/iris/stunbinding.h000066400000000000000000000000631370065651000236060ustar00rootroot00000000000000#include "../../src/irisnet/noncore/stunbinding.h" psi-plus-snapshots-1.4.1456/iris/include/iris/stunmessage.h000066400000000000000000000000631370065651000236200ustar00rootroot00000000000000#include "../../src/irisnet/noncore/stunmessage.h" psi-plus-snapshots-1.4.1456/iris/include/iris/stuntransaction.h000066400000000000000000000000671370065651000245250ustar00rootroot00000000000000#include "../../src/irisnet/noncore/stuntransaction.h" psi-plus-snapshots-1.4.1456/iris/include/iris/tcpportreserver.h000066400000000000000000000000671370065651000245370ustar00rootroot00000000000000#include "../../src/irisnet/noncore/tcpportreserver.h" psi-plus-snapshots-1.4.1456/iris/include/iris/turnclient.h000066400000000000000000000000621370065651000234500ustar00rootroot00000000000000#include "../../src/irisnet/noncore/turnclient.h" psi-plus-snapshots-1.4.1456/iris/include/iris/udpportreserver.h000066400000000000000000000000671370065651000245410ustar00rootroot00000000000000#include "../../src/irisnet/noncore/udpportreserver.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp.h000066400000000000000000000000531370065651000222450ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-core/xmpp.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_address.h000066400000000000000000000000611370065651000237510ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_address.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_agentitem.h000066400000000000000000000000631370065651000243030ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_agentitem.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_bitsofbinary.h000066400000000000000000000000661370065651000250240ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_bitsofbinary.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_caps.h000066400000000000000000000000561370065651000232560ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_caps.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_captcha.h000066400000000000000000000000611370065651000237270ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_captcha.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_chatstate.h000066400000000000000000000000631370065651000243060ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_chatstate.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_client.h000066400000000000000000000000601370065651000236010ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_client.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_clientstream.h000066400000000000000000000000701370065651000250160ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-core/xmpp_clientstream.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_discoinfotask.h000066400000000000000000000000671370065651000251720ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_discoinfotask.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_discoitem.h000066400000000000000000000000631370065651000243060ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_discoitem.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_encryptionhandler.h000066400000000000000000000000731370065651000260570ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_encryptionhandler.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_features.h000066400000000000000000000000621370065651000241430ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_features.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_hash.h000066400000000000000000000000561370065651000232530ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_hash.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_htmlelement.h000066400000000000000000000000651370065651000246460ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_htmlelement.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_httpauthrequest.h000066400000000000000000000000711370065651000255770ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_httpauthrequest.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_jid.h000066400000000000000000000000441370065651000230730ustar00rootroot00000000000000#include "../../src/xmpp/jid/jid.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_liveroster.h000066400000000000000000000000641370065651000245250ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_liveroster.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_liverosteritem.h000066400000000000000000000000701370065651000254010ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_liverosteritem.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_message.h000066400000000000000000000000611370065651000237500ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_message.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_muc.h000066400000000000000000000000551370065651000231130ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_muc.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_pubsubitem.h000066400000000000000000000000641370065651000245060ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_pubsubitem.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_pubsubretraction.h000066400000000000000000000000721370065651000257210ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_pubsubretraction.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_reference.h000066400000000000000000000000631370065651000242640ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_reference.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_resource.h000066400000000000000000000000621370065651000241540ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_resource.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_resourcelist.h000066400000000000000000000000661370065651000250540ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_resourcelist.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_roster.h000066400000000000000000000000601370065651000236410ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_roster.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_rosteritem.h000066400000000000000000000000641370065651000245240ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_rosteritem.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_rosterx.h000066400000000000000000000000611370065651000240320ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_rosterx.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_serverinfomanager.h000066400000000000000000000000731370065651000260440ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_serverinfomanager.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_stanza.h000066400000000000000000000000621370065651000236250ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-core/xmpp_stanza.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_status.h000066400000000000000000000000601370065651000236460ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_status.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_stream.h000066400000000000000000000000621370065651000236200ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-core/xmpp_stream.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_subsets.h000066400000000000000000000000611370065651000240140ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_subsets.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_task.h000066400000000000000000000000561370065651000232720ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_task.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_tasks.h000066400000000000000000000000571370065651000234560ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_tasks.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_thumbs.h000066400000000000000000000000601370065651000236250ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_thumbs.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_url.h000066400000000000000000000000551370065651000231310ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_url.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_vcard.h000066400000000000000000000000571370065651000234300ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_vcard.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_xdata.h000066400000000000000000000000571370065651000234320ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_xdata.h" psi-plus-snapshots-1.4.1456/iris/include/iris/xmpp_xmlcommon.h000066400000000000000000000000631370065651000243370ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_xmlcommon.h" psi-plus-snapshots-1.4.1456/iris/iris.pc.in000066400000000000000000000005241370065651000204210ustar00rootroot00000000000000prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=${prefix} libdir=@LIB_INSTALL_DIR@ includedir=@INCLUDE_INSTALL_DIR@/iris Name: iris Description: Iris is a comprehensive library for working with the XMPP protocol Version: @IRIS_LIB_MAJOR_VERSION@.@IRIS_LIB_MINOR_VERSION@.@IRIS_LIB_PATCH_VERSION@ Libs: -L${libdir} -liris Cflags: -I${includedir} psi-plus-snapshots-1.4.1456/iris/iris.pri000066400000000000000000000021761370065651000202110ustar00rootroot00000000000000IRIS_BASE = $$PWD include(common.pri) CONFIG *= link_prl # doesn't seems to work but at least it's documented unlike dependp_prl unix { # most of devs are on Linux anyway PRE_TARGETDEPS += $$top_iris_builddir/lib/libiris.a PRE_TARGETDEPS += $$top_iris_builddir/lib/libirisnet.a } INCLUDEPATH += $$IRIS_BASE/include $$IRIS_BASE/include/iris $$IRIS_BASE/src iris_bundle:{ include(src/xmpp/xmpp.pri) } else { isEmpty(top_iris_builddir):top_iris_builddir = $$PWD LIBS += -L$$top_iris_builddir/lib -liris } # force on all windows, plus qca ordering workaround windows { DEFINES += IRISNET_STATIC # from irisnet LIBS += -L$$top_iris_builddir/lib -lirisnet # from iris LIBS += -lWs2_32 -lAdvapi32 # from jdns contains(LIBS, -lqca) { LIBS -= -lqca LIBS += -lqca } contains(LIBS, -lqcad) { LIBS -= -lqcad LIBS += -lqcad } contains(LIBS, -lidn) { LIBS -= -lidn LIBS += -lidn } contains(LIBS, -lz) { LIBS -= -lz LIBS += -lz } contains(LIBS, -lzlib) { LIBS -= -lzlib LIBS += -lzlib } } psi-plus-snapshots-1.4.1456/iris/iris.pro000066400000000000000000000007001370065651000202060ustar00rootroot00000000000000TEMPLATE = subdirs IRIS_BASE = $$PWD isEmpty(top_iris_builddir):top_iris_builddir = . include($$top_iris_builddir/conf.pri) include(common.pri) # do we have a reason to enter the src dir? appledns:!appledns_bundle:CONFIG *= build_src !irisnetcore_bundle:CONFIG *= build_src !iris_bundle:CONFIG *= build_src sub_src.subdir = src sub_tools.subdir = tools sub_tools.depends = sub_src build_src:SUBDIRS += sub_src iris_tests:SUBDIRS += sub_tools psi-plus-snapshots-1.4.1456/iris/iris.qc000066400000000000000000000007451370065651000200220ustar00rootroot00000000000000 Iris iris.pro qcm psi-plus-snapshots-1.4.1456/iris/qcm/000077500000000000000000000000001370065651000173015ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/qcm/README000066400000000000000000000002761370065651000201660ustar00rootroot00000000000000qt42, buildmode, and universal modules are all copied from qca. do not modify them here. the qca module is copied from qca, but modified here. we should consider pushing the changes back. psi-plus-snapshots-1.4.1456/iris/qcm/b2.qcm000066400000000000000000000045351370065651000203150ustar00rootroot00000000000000/* -----BEGIN QCMOD----- name: libb2 arg: with-b2-inc=[path],Path to libb2 include files arg: with-b2-lib=[path],Path to libb2 library or framework files -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_b2 //---------------------------------------------------------------------------- class qc_b2 : public ConfObj { bool use_system = false; public: qc_b2(Conf *c) : ConfObj(c) {} QString name() const { return "LibB2"; } QString shortname() const { return "libb2"; } QString resultString() const { return use_system? "system":"bundled"; } bool exec() { QString b2_incdir, b2_libdir; b2_incdir = conf->getenv("QC_WITH_B2_INC"); b2_libdir = conf->getenv("QC_WITH_B2_LIB"); if (b2_incdir.isEmpty() && b2_libdir.isEmpty()) { if (!checkCustomDirs(b2_incdir, b2_libdir)) { printf("b2 search paths provided but library is not found there. use bundled"); return true; } use_system = true; } else { QStringList incs; QString version, libs, other; if(conf->findPkgConfig("libb2", VersionAny, QString::null, &version, &incs, &libs, &other)) { for(int n = 0; n < incs.count(); ++n) conf->addIncludePath(incs[n]); if(!libs.isEmpty()) conf->addLib(libs); use_system = true; } } if (use_system) { conf->addExtra("CONFIG += bundled_blake2"); } return true; } bool checkCustomDirs(QString &b2_incdir, QString &b2_libdir) { if ((b2_incdir.isEmpty() && !conf->findHeader("blake2.h", QStringList(), &b2_incdir)) || (!b2_incdir.isEmpty() && !conf->checkHeader(b2_incdir, "blake2.h"))) { printf("Headers not found!\n"); return false; } if((!b2_libdir.isEmpty() && conf->checkLibrary(b2_libdir, "b2")) || (b2_libdir.isEmpty() && conf->findLibrary("b2", &b2_libdir))) { conf->addLib(b2_libdir.isEmpty()? "-lb2" : QString("-L%1 -lb2").arg(b2_libdir)); conf->addIncludePath(b2_incdir); return true; } printf("Libraries not found!\n"); return false; } }; psi-plus-snapshots-1.4.1456/iris/qcm/buildmode.qcm000066400000000000000000000105751370065651000217570ustar00rootroot00000000000000/* -----BEGIN QCMOD----- name: buildmode section: project arg: release,Build with debugging turned off (default). arg: debug,Build with debugging turned on. arg: debug-and-release,Build two versions, with and without debugging turned on (mac only). arg: no-separate-debug-info,Do not store debug information in a separate file (default for mac). arg: separate-debug-info,Strip debug information into a separate .debug file (default for non-mac). arg: no-framework,Do not build as a Mac framework. arg: framework,Build as a Mac framework (default). -----END QCMOD----- */ #define QC_BUILDMODE bool qc_buildmode_release = false; bool qc_buildmode_debug = false; bool qc_buildmode_framework = false; bool qc_buildmode_separate_debug_info = false; class qc_buildmode : public ConfObj { public: qc_buildmode(Conf *c) : ConfObj(c) {} QString name() const { return "buildmode"; } QString shortname() const { return "buildmode"; } // no output QString checkString() const { return QString(); } bool exec() { // first, parse out the options bool opt_release = false; bool opt_debug = false; bool opt_debug_and_release = false; bool opt_no_framework = false; bool opt_framework = false; bool opt_no_separate_debug_info = false; bool opt_separate_debug_info = false; if(conf->getenv("QC_RELEASE") == "Y") opt_release = true; if(conf->getenv("QC_DEBUG") == "Y") opt_debug = true; if(conf->getenv("QC_DEBUG_AND_RELEASE") == "Y") opt_debug_and_release = true; if(conf->getenv("QC_NO_FRAMEWORK") == "Y") opt_no_framework = true; if(conf->getenv("QC_FRAMEWORK") == "Y") opt_framework = true; if(conf->getenv("QC_NO_SEPARATE_DEBUG_INFO") == "Y") opt_no_separate_debug_info = true; if(conf->getenv("QC_SEPARATE_DEBUG_INFO") == "Y") opt_separate_debug_info = true; bool staticmode = false; if(conf->getenv("QC_STATIC") == "Y") staticmode = true; #ifndef Q_OS_MAC if(opt_debug_and_release) { printf("\nError: The --debug-and-release option is for mac only.\n"); exit(1); } if(opt_framework) { printf("\nError: The --framework option is for mac only.\n"); exit(1); } #endif if(opt_framework && opt_debug) { printf("\nError: Cannot use both --framework and --debug.\n"); exit(1); } // sanity check exclusive options int x; // build mode x = 0; if(opt_release) ++x; if(opt_debug) ++x; if(opt_debug_and_release) ++x; if(x > 1) { printf("\nError: Use only one of --release, --debug, or --debug-and-release.\n"); exit(1); } // framework if(opt_framework && staticmode) { printf("\nError: Cannot use both --framework and --static.\n"); exit(1); } x = 0; if(opt_no_framework) ++x; if(opt_framework) ++x; if(x > 1) { printf("\nError: Use only one of --framework or --no-framework.\n"); exit(1); } // debug info x = 0; if(opt_no_separate_debug_info) ++x; if(opt_separate_debug_info) ++x; if(x > 1) { printf("\nError: Use only one of --separate-debug-info or --no-separate-debug-info\n"); exit(1); } // now process the options if(opt_release) qc_buildmode_release = true; else if(opt_debug) qc_buildmode_debug = true; else if(opt_debug_and_release) { qc_buildmode_release = true; qc_buildmode_debug = true; } else // default qc_buildmode_release = true; if(opt_framework) qc_buildmode_framework = true; else if(opt_no_framework) { // nothing to do } else // default { if(!staticmode && !opt_debug) qc_buildmode_framework = true; } if(opt_separate_debug_info) qc_buildmode_separate_debug_info = true; else if(opt_no_separate_debug_info) { // nothing to do } else // default { #ifndef Q_OS_MAC qc_buildmode_separate_debug_info = true; #endif } // make the string QStringList opts; QString other; if(qc_buildmode_release && qc_buildmode_debug) { opts += "debug_and_release"; opts += "build_all"; } else if(qc_buildmode_release) opts += "release"; else // qc_buildmode_debug opts += "debug"; #ifdef Q_OS_MAC if(qc_buildmode_framework) opts += "lib_bundle"; #endif if(qc_buildmode_separate_debug_info) { opts += "separate_debug_info"; other += "QMAKE_CFLAGS += -g\n"; other += "QMAKE_CXXFLAGS += -g\n"; } QString str = QString("CONFIG += ") + opts.join(" ") + '\n'; conf->addExtra(str); if(!other.isEmpty()) conf->addExtra(other); return true; } }; psi-plus-snapshots-1.4.1456/iris/qcm/extra.qcm000066400000000000000000000033341370065651000211310ustar00rootroot00000000000000/* -----BEGIN QCMOD----- name: extra section: project arg: enable-tests,Build examples and unittests. -----END QCMOD----- */ class qc_extra : public ConfObj { public: qc_extra(Conf *c) : ConfObj(c) {} QString name() const { return "extra"; } QString shortname() const { return "extra"; } // no output QString checkString() const { return QString(); } bool exec() { QString str; QFile f; if(conf->getenv("QC_ENABLE_TESTS") == "Y") str += "CONFIG += iris_tests\n"; conf->addExtra(str); bool release = true; bool debug = false; bool debug_info = false; bool universal = false; QString sdk; #ifdef QC_BUILDMODE release = qc_buildmode_release; debug = qc_buildmode_debug; debug_info = qc_buildmode_separate_debug_info; #endif #ifdef QC_UNIVERSAL universal = qc_universal_enabled; sdk = qc_universal_sdk; #endif // write confapp_unix.pri str = QString(); QString var = conf->getenv("BINDIR"); if(!var.isEmpty()) str += QString("BINDIR = %1\n").arg(var); if(debug) // debug or debug-and-release str += QString("CONFIG += debug\n"); else // release str += QString("CONFIG += release\n"); if(debug_info) { str += QString("CONFIG += separate_debug_info\n"); str += "QMAKE_CFLAGS += -g\n"; str += "QMAKE_CXXFLAGS += -g\n"; } if(universal) { str += "contains(QT_CONFIG,x86):contains(QT_CONFIG,ppc) {\n" " CONFIG += x86 ppc\n" "}\n"; if(!sdk.isEmpty()) str += QString("QMAKE_MAC_SDK = %1\n").arg(sdk); } #ifdef QC_QCA if(!qc_qca_procode.isEmpty()) str += qc_qca_procode; #endif f.setFileName("confapp_unix.pri"); if(f.open(QFile::WriteOnly | QFile::Truncate)) f.write(str.toLatin1()); f.close(); return true; } }; psi-plus-snapshots-1.4.1456/iris/qcm/idn.qcm000066400000000000000000000032171370065651000205600ustar00rootroot00000000000000/* -----BEGIN QCMOD----- name: libidn arg: with-idn-inc=[path],Path to libidn include files arg: with-idn-lib=[path],Path to libidn library or framework files -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_idn //---------------------------------------------------------------------------- class qc_idn : public ConfObj { public: qc_idn(Conf *c) : ConfObj(c) {} QString name() const { return "LibIDN"; } QString shortname() const { return "libidn"; } bool exec() { QString idn_incdir, idn_libdir; idn_incdir = conf->getenv("QC_WITH_IDN_INC"); idn_libdir = conf->getenv("QC_WITH_IDN_LIB"); if (!idn_incdir.isEmpty() || !idn_libdir.isEmpty()) { // prefer this if given if ((!idn_incdir.isEmpty() && conf->checkHeader(idn_incdir, "stringprep.h")) || (idn_incdir.isEmpty() && conf->findHeader("stringprep.h", QStringList(), &idn_incdir))) { conf->addIncludePath(idn_incdir); } else { printf("Headers not found!\n"); return false; } if((!idn_libdir.isEmpty() && conf->checkLibrary(idn_libdir, "idn")) || (idn_libdir.isEmpty() && conf->findLibrary("idn", &idn_libdir))) { conf->addLib(idn_libdir.isEmpty()? "-lidn" : QString("-L%1 -lidn").arg(idn_libdir)); } else { printf("Libraries not found!\n"); return false; } return true; } QStringList incs; QString version, libs, other; if(conf->findPkgConfig("libidn", VersionAny, QString::null, &version, &incs, &libs, &other)) { for(int n = 0; n < incs.count(); ++n) conf->addIncludePath(incs[n]); if(!libs.isEmpty()) conf->addLib(libs); return true; } return false; } }; psi-plus-snapshots-1.4.1456/iris/qcm/qca.qcm000066400000000000000000000124461370065651000205560ustar00rootroot00000000000000/* -----BEGIN QCMOD----- name: QCA >= 2.0 arg: with-qca-inc=[path],Path to QCA include files arg: with-qca-lib=[path],Path to QCA library or framework files -----END QCMOD----- */ // adapted from crypto.prf static QString internal_crypto_prf(const QString &incdir, const QString &libdir, const QString &frameworkdir) { QString out = QString( "QCA_INCDIR = %1\n" "QCA_LIBDIR = %2\n" "QMAKE_RPATHDIR = %2\n" "QCA_FRAMEWORKDIR = %3\n" "\n" "CONFIG *= qt\n" "\n" "LINKAGE =\n" "QCA_NAME = qca-qt5\n" "\n" "!isEmpty(QCA_FRAMEWORKDIR): {\n" " framework_dir = $$QCA_FRAMEWORKDIR\n" " exists($$framework_dir/$${QCA_NAME}.framework) {\n" " #QMAKE_FRAMEWORKPATH *= $$framework_dir\n" " LIBS *= -F$$framework_dir\n" " INCLUDEPATH += $$framework_dir/$${QCA_NAME}.framework/Headers\n" " LINKAGE = -framework $${QCA_NAME}\n" " }\n" "}\n" "\n" "# else, link normally\n" "isEmpty(LINKAGE) {\n" " !isEmpty(QCA_INCDIR):INCLUDEPATH += $$QCA_INCDIR/QtCrypto\n" " !isEmpty(QCA_LIBDIR):LIBS += -L$$QCA_LIBDIR\n" " LINKAGE = -l$${QCA_NAME}\n" " CONFIG(debug, debug|release) {\n" " windows:LINKAGE = -l$${QCA_NAME}d\n" " mac:LINKAGE = -l$${QCA_NAME}_debug\n" " }\n" "}\n" "\n" "LIBS += $$LINKAGE\n" ).arg(incdir, libdir, frameworkdir); return out; } // set either libdir or frameworkdir, but not both static bool qca_try(Conf *conf, const QString &incdir, const QString &libdir, const QString &frameworkdir, bool release, bool debug, QString *_prf) { QString proextra; QString prf; if (!incdir.isEmpty() || !libdir.isEmpty() || !frameworkdir.isEmpty()) { prf = internal_crypto_prf(conf->escapePath(incdir), conf->escapePath(libdir), frameworkdir); } else { prf = "CONFIG += crypto\n"; } proextra = "CONFIG += qt\n" "CONFIG -= debug_and_release debug release\n" "QT -= gui\n"; proextra += prf; QString str = "#include \n" "\n" "int main()\n" "{\n" " unsigned long x = QCA_VERSION;\n" " if(x >= 0x020000 && x < 0x030000) return 0; else return 1;\n" "}\n"; // test desired versions, potentially both release and debug if(release) { int ret; if(!conf->doCompileAndLink(str, QStringList(), QString(), proextra + "CONFIG += release\n", &ret) || ret != 0) return false; } if(debug) { int ret; if(!conf->doCompileAndLink(str, QStringList(), QString(), proextra + "CONFIG += debug\n", &ret) || ret != 0) return false; } *_prf = prf; return true; } static bool qca_try_lib(Conf *conf, const QString &incdir, const QString &libdir, bool release, bool debug, QString *prf) { return qca_try(conf, incdir, libdir, QString(), release, debug, prf) || qca_try(conf, incdir + "/Qca-qt5", libdir, QString(), release, debug, prf); } static bool qca_try_framework(Conf *conf, const QString &frameworkdir, bool release, bool debug, QString *prf) { return qca_try(conf, QString(), QString(), frameworkdir, release, debug, prf); } static bool qca_try_ext_prf(Conf *conf, bool release, bool debug, QString *prf) { return qca_try(conf, QString(), QString(), QString(), release, debug, prf); } //---------------------------------------------------------------------------- // qc_qca //---------------------------------------------------------------------------- class qc_qca : public ConfObj { public: qc_qca(Conf *c) : ConfObj(c) {} QString name() const { return "QCA >= 2.0"; } QString shortname() const { return "qca"; } bool exec() { // get the build mode #ifdef QC_BUILDMODE bool release = qc_buildmode_release; bool debug = qc_buildmode_debug; #else // else, default to just release mode bool release = true; bool debug = false; #endif QString qca_incdir, qca_libdir, qca_crypto_prf; qca_incdir = conf->getenv("QC_WITH_QCA_INC"); qca_libdir = conf->getenv("QC_WITH_QCA_LIB"); #if defined(Q_OS_MAC) if(!qca_libdir.isEmpty() && qca_try_framework(conf, qca_libdir, release, debug, &qca_crypto_prf)) { conf->addExtra(qca_crypto_prf); return true; } #endif if(!qca_incdir.isEmpty() && !qca_libdir.isEmpty() && qca_try_lib(conf, qca_incdir, qca_libdir, release, debug, &qca_crypto_prf)) { conf->addExtra(qca_crypto_prf); return true; } if (qca_try_ext_prf(conf, release, debug, &qca_crypto_prf)) { conf->addExtra(qca_crypto_prf); return true; } QStringList incs; QString version, libs, other; if(conf->findPkgConfig("qca2-qt5", VersionMin, "2.0.0", &version, &incs, &libs, &other)) { for(int n = 0; n < incs.count(); ++n) conf->addIncludePath(incs[n]); if(!libs.isEmpty()) conf->addLib(libs); return true; } QStringList prefixes; #ifndef Q_OS_WIN prefixes += "/usr"; prefixes += "/usr/local"; #endif QString prefix = conf->getenv("PREFIX"); if (!prefix.isEmpty()) { prefixes += prefix; } for(int n = 0; n < prefixes.count(); ++n) { const QString &prefix = prefixes[n]; if(qca_try_lib(conf, prefix + "/include", prefix + "/lib", release, debug, &qca_crypto_prf)) { conf->addExtra(qca_crypto_prf); return true; } } return false; } }; psi-plus-snapshots-1.4.1456/iris/qcm/qjdns.qcm000066400000000000000000000040671370065651000211310ustar00rootroot00000000000000/* -----BEGIN QCMOD----- name: jdns arg: with-qjdns-inc=[path],Path to QJDns include files arg: with-qjdns-lib=[path],Path to QJDns library files -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_qjdns //---------------------------------------------------------------------------- class qc_qjdns : public ConfObj { public: qc_qjdns(Conf *c) : ConfObj(c) {} QString name() const { return "QJDns"; } QString shortname() const { return "qjdns"; } QString resultString() const { #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) return "Disabled for Qt5 and above"; #else return ConfObj::resultString(); #endif } bool exec() { #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) return true; // hack. TODO: figure out how to force jdns #endif conf->addExtra("CONFIG += need_jdns"); #if defined Q_OS_WIN || defined Q_OS_MAC // HACK: on Windows and Mac OS X, always use psi's bundled qjdns conf->addExtra("CONFIG += iris-qjdns"); return true; #else QStringList incs; QString version, libs, other; QString s; #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) bool found = conf->findPkgConfig("qjdns-qt5", VersionMin, "2.0.3", &version, &incs, &libs, &other); #else bool found = conf->findPkgConfig("qjdns-qt4", VersionMin, "2.0.3", &version, &incs, &libs, &other); #endif if(!found && !conf->findPkgConfig("qjdns", VersionMin, "2.0.0", &version, &incs, &libs, &other)) { s = conf->getenv("QC_WITH_QJDNS_INC"); if ((!s.isEmpty() && conf->checkHeader(s, "qjdns.h")) || (s.isEmpty() && conf->findHeader("qjdns.h", QStringList(), &s))) { incs.append(s); } s = conf->getenv("QC_WITH_QJDNS_LIB"); if((!s.isEmpty() && conf->checkLibrary(s, "qjdns")) || (s.isEmpty() && conf->findLibrary("qjdns", &s))) { libs = s.isEmpty()? "-lqjdns -ljdns" : QString("-L%1 -lqjdns -ljdns").arg(s); } } if (!incs.isEmpty() && !libs.isEmpty()) { foreach(const QString &inc, incs) { conf->addIncludePath(inc); } conf->addLib(libs); conf->addExtra("CONFIG += ext-qjdns"); } return true; #endif } }; psi-plus-snapshots-1.4.1456/iris/qcm/qt42.qcm000066400000000000000000000006241370065651000205770ustar00rootroot00000000000000/* -----BEGIN QCMOD----- name: Qt >= 4.2 -----END QCMOD----- */ class qc_qt42 : public ConfObj { public: qc_qt42(Conf *c) : ConfObj(c) {} QString name() const { return "Qt >= 4.2"; } QString shortname() const { return "qt42"; } bool exec() { conf->debug(QString("QT_VERSION = 0x%1").arg(QString::number(QT_VERSION, 16))); if(QT_VERSION >= 0x040200) return true; else return false; } }; psi-plus-snapshots-1.4.1456/iris/qcm/universal.qcm000066400000000000000000000022111370065651000220070ustar00rootroot00000000000000/* -----BEGIN QCMOD----- name: Mac universal binary support section: project arg: universal,Build with Mac universal binary support. arg: mac-sdk=[path],Path to Mac universal SDK (PPC host only). -----END QCMOD----- */ #define QC_UNIVERSAL bool qc_universal_enabled = false; QString qc_universal_sdk; //---------------------------------------------------------------------------- // qc_universal //---------------------------------------------------------------------------- class qc_universal : public ConfObj { public: qc_universal(Conf *c) : ConfObj(c) {} QString name() const { return "Mac universal binary support"; } QString shortname() const { return "universal"; } QString checkString() const { return QString(); } bool exec() { #ifdef Q_OS_MAC if(qc_getenv("QC_UNIVERSAL") == "Y") { qc_universal_enabled = true; QString str = "contains(QT_CONFIG,x86):contains(QT_CONFIG,ppc) {\n" " CONFIG += x86 ppc\n" "}\n"; QString sdk = qc_getenv("QC_MAC_SDK"); if(!sdk.isEmpty()) { str += QString("QMAKE_MAC_SDK = %1\n").arg(sdk); qc_universal_sdk = sdk; } conf->addExtra(str); } #endif return true; } }; psi-plus-snapshots-1.4.1456/iris/qcm/zlib.qcm000066400000000000000000000021651370065651000207470ustar00rootroot00000000000000/* -----BEGIN QCMOD----- name: zlib arg: with-zlib-inc=[path],Path to zlib include files arg: with-zlib-lib=[path],Path to zlib library files -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_zlib //---------------------------------------------------------------------------- class qc_zlib : public ConfObj { public: qc_zlib(Conf *c) : ConfObj(c) {} QString name() const { return "zlib"; } QString shortname() const { return "zlib"; } bool exec() { QString inc, lib; QString s; s = conf->getenv("QC_WITH_ZLIB_INC"); if(!s.isEmpty()) { if(!conf->checkHeader(s, "zlib.h")) return false; inc = s; } else { if(!conf->findHeader("zlib.h", QStringList(), &s)) return false; inc = s; } s = conf->getenv("QC_WITH_ZLIB_LIB"); if(!s.isEmpty()) { if(!conf->checkLibrary(s, "z")) return false; lib = s; } else { if(!conf->findLibrary("z", &s)) return false; lib = s; } if(!inc.isEmpty()) conf->addIncludePath(inc); if(!lib.isEmpty()) conf->addLib(QString("-L") + s); conf->addLib("-lz"); return true; } }; psi-plus-snapshots-1.4.1456/iris/src/000077500000000000000000000000001370065651000173105ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/irisnet/000077500000000000000000000000001370065651000207655ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/irisnet/CMakeLists.txt000066400000000000000000000057121370065651000235320ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.1.0) get_filename_component(ABS_PARENT_DIR "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE) include_directories( ${CMAKE_CURRENT_BINARY_DIR} corelib noncore noncore/cutestuff noncore/legacy ${QCA_INCLUDES} ) set( CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules" "${ABS_PARENT_DIR}/cmake/modules" ) set(SOURCES corelib/irisnetglobal.cpp corelib/irisnetplugin.cpp noncore/icetransport.cpp noncore/stunmessage.cpp noncore/stuntypes.cpp noncore/stunutil.cpp noncore/cutestuff/bytestream.cpp noncore/cutestuff/httpconnect.cpp noncore/cutestuff/httppoll.cpp noncore/cutestuff/socks.cpp noncore/legacy/ndns.cpp noncore/legacy/srvresolver.cpp corelib/addressresolver.cpp corelib/netavailability.cpp corelib/netinterface.cpp corelib/netnames.cpp corelib/objectsession.cpp noncore/iceagent.cpp noncore/ice176.cpp noncore/icecomponent.cpp noncore/icelocaltransport.cpp noncore/iceturntransport.cpp noncore/processquit.cpp noncore/stunallocate.cpp noncore/stunbinding.cpp noncore/stuntransaction.cpp noncore/turnclient.cpp noncore/udpportreserver.cpp noncore/tcpportreserver.cpp noncore/cutestuff/bsocket.cpp ) if(UNIX) list(APPEND SOURCES corelib/netinterface_unix.cpp) endif() if(NOT USE_QJDNS) list(APPEND SOURCES corelib/netinterface_qtname.cpp) else() list(APPEND SOURCES corelib/netnames_jdns.cpp) endif() if (Qt5Network_VERSION VERSION_LESS 5.15.0) list(APPEND SOURCES corelib/netinterface_qtnet.cpp) endif() set(HEADERS corelib/irisnetexport.h corelib/irisnetglobal.h corelib/irisnetglobal_p.h noncore/stunmessage.h noncore/stuntypes.h noncore/stunutil.h corelib/addressresolver.h corelib/irisnetplugin.h corelib/netavailability.h corelib/netinterface.h corelib/netnames.h corelib/objectsession.h noncore/iceagent.h noncore/ice176.h noncore/icecomponent.h noncore/icelocaltransport.h noncore/icetransport.h noncore/iceturntransport.h noncore/processquit.h noncore/stunallocate.h noncore/stunbinding.h noncore/stuntransaction.h noncore/turnclient.h noncore/udpportreserver.h noncore/tcpportreserver.h noncore/cutestuff/bsocket.h noncore/cutestuff/bytestream.h noncore/cutestuff/httpconnect.h noncore/cutestuff/httppoll.h noncore/cutestuff/socks.h noncore/legacy/ndns.h noncore/legacy/srvresolver.h ) add_library(irisnet STATIC ${SOURCES} ${HEADERS} ) if(WIN32 AND (SEPARATE_QJDNS OR (NOT USE_QJDNS))) set(EXTRA_LDFLAGS ws2_32) endif() if(NOT USE_QJDNS) set(QJDns_LIBRARY "") endif() if (Qt5Network_VERSION VERSION_LESS 5.15.0) target_compile_definitions(irisnet PRIVATE HAVE_QTNET) endif() target_link_libraries(irisnet ${QJDns_LIBRARY} ${EXTRA_LDFLAGS}) target_link_libraries(irisnet Qt5::Core Qt5::Network Qt5::Xml ${qca_LIB}) psi-plus-snapshots-1.4.1456/iris/src/irisnet/appledns/000077500000000000000000000000001370065651000225735ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/irisnet/appledns/appledns.cpp000066400000000000000000000632771370065651000251240ustar00rootroot00000000000000/* * Copyright (C) 2007 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "irisnetplugin.h" #include "qdnssd.h" #include #include #ifdef Q_OS_WIN // for ntohl #include #else #include #endif static QByteArray nameToDottedString(const QByteArray &in) { QByteArray out; int at = 0; while (at < in.size()) { int len = in[at++]; if (len > 0) out += in.mid(at, len); out += '.'; at += len; } return out; } static QMap textsToAttribs(const QList &texts) { QMap out; for (const QByteArray &a : texts) { QString key; QByteArray value; int x = a.indexOf('='); if (x != -1) { key = QString::fromLatin1(a.mid(0, x)); value = a.mid(x + 1); } else { key = QString::fromLatin1(a); } out.insert(key, value); } return out; } static QByteArray attribsToTxtRecord(const QMap &attribs) { QList texts; QMapIterator it(attribs); while (it.hasNext()) { it.next(); QByteArray line = it.key().toLatin1() + '=' + it.value(); texts += line; } return QDnsSd::createTxtRecord(texts); } // returns a list of 3 items, or an empty list on error static QList nameToInstanceParts(const QByteArray &name) { // FIXME: improve this parsing... (what about escaping??) int at = name.indexOf('.'); QByteArray sname = name.mid(0, at); ++at; int next = name.indexOf('.', at); ++next; next = name.indexOf('.', next); QByteArray stype = name.mid(at, next - at); at = next + 1; QByteArray sdomain = name.mid(at); QList out; out += sname; out += stype; out += sdomain; return out; } static XMPP::NameRecord importQDnsSdRecord(const QDnsSd::Record &in) { XMPP::NameRecord out; switch (in.rrtype) { case 1: // A { quint32 *p = (quint32 *)in.rdata.data(); out.setAddress(QHostAddress(ntohl(*p))); } break; case 28: // AAAA { out.setAddress(QHostAddress((quint8 *)in.rdata.data())); } break; case 12: // PTR { out.setPtr(nameToDottedString(in.rdata)); } break; case 10: // NULL { out.setNull(in.rdata); } break; case 16: // TXT { QList txtEntries = QDnsSd::parseTxtRecord(in.rdata); if (txtEntries.isEmpty()) return out; out.setTxt(txtEntries); } break; default: // unsupported { return out; } } out.setOwner(in.name); out.setTtl(in.ttl); return out; } namespace { class QDnsSdDelegate { public: virtual ~QDnsSdDelegate() { } virtual void dns_queryResult(int id, const QDnsSd::QueryResult &result) { Q_UNUSED(id); Q_UNUSED(result); } virtual void dns_browseResult(int id, const QDnsSd::BrowseResult &result) { Q_UNUSED(id); Q_UNUSED(result); } virtual void dns_resolveResult(int id, const QDnsSd::ResolveResult &result) { Q_UNUSED(id); Q_UNUSED(result); } virtual void dns_regResult(int id, const QDnsSd::RegResult &result) { Q_UNUSED(id); Q_UNUSED(result); } }; class IdManager { private: QSet set; int at; inline void bump_at() { if (at == 0x7fffffff) at = 0; else ++at; } public: IdManager() : at(0) { } int reserveId() { while (1) { if (!set.contains(at)) { int id = at; set.insert(id); bump_at(); return id; } bump_at(); } } void releaseId(int id) { set.remove(id); } }; } // namespace //---------------------------------------------------------------------------- // AppleProvider //---------------------------------------------------------------------------- class AppleProvider : public XMPP::IrisNetProvider { Q_OBJECT Q_INTERFACES(XMPP::IrisNetProvider); public: QDnsSd dns; QHash delegateById; AppleProvider() : dns(this) { connect(&dns, SIGNAL(queryResult(int, QDnsSd::QueryResult)), SLOT(dns_queryResult(int, QDnsSd::QueryResult))); connect(&dns, SIGNAL(browseResult(int, QDnsSd::BrowseResult)), SLOT(dns_browseResult(int, QDnsSd::BrowseResult))); connect(&dns, SIGNAL(resolveResult(int, QDnsSd::ResolveResult)), SLOT(dns_resolveResult(int, QDnsSd::ResolveResult))); connect(&dns, SIGNAL(regResult(int, QDnsSd::RegResult)), SLOT(dns_regResult(int, QDnsSd::RegResult))); } virtual XMPP::NameProvider * createNameProviderInternet(); virtual XMPP::NameProvider * createNameProviderLocal(); virtual XMPP::ServiceProvider *createServiceProvider(); int query(QDnsSdDelegate *p, const QByteArray &name, int qType) { int id = dns.query(name, qType); delegateById[id] = p; return id; } int browse(QDnsSdDelegate *p, const QByteArray &serviceType, const QByteArray &domain) { int id = dns.browse(serviceType, domain); delegateById[id] = p; return id; } int resolve(QDnsSdDelegate *p, const QByteArray &serviceName, const QByteArray &serviceType, const QByteArray &domain) { int id = dns.resolve(serviceName, serviceType, domain); delegateById[id] = p; return id; } int reg(QDnsSdDelegate *p, const QByteArray &serviceName, const QByteArray &serviceType, const QByteArray &domain, int port, const QByteArray &txtRecord) { int id = dns.reg(serviceName, serviceType, domain, port, txtRecord); delegateById[id] = p; return id; } void stop(int id) { delegateById.remove(id); dns.stop(id); } void stop_all(QDnsSdDelegate *p) { QList ids; QHashIterator it(delegateById); while (it.hasNext()) { it.next(); if (it.value() == p) ids += it.key(); } for (int id : ids) stop(id); } private slots: void dns_queryResult(int id, const QDnsSd::QueryResult &result) { delegateById[id]->dns_queryResult(id, result); } void dns_browseResult(int id, const QDnsSd::BrowseResult &result) { delegateById[id]->dns_browseResult(id, result); } void dns_resolveResult(int id, const QDnsSd::ResolveResult &result) { delegateById[id]->dns_resolveResult(id, result); } void dns_regResult(int id, const QDnsSd::RegResult &result) { delegateById[id]->dns_regResult(id, result); } }; //---------------------------------------------------------------------------- // AppleBrowseSession //---------------------------------------------------------------------------- // only use this class for a single browse. if you want to browse again, // create a new object. class AppleBrowse : public QObject, public QDnsSdDelegate { Q_OBJECT public: AppleProvider * global; int browse_id; QList instances; QHash pendingByQueryId; // waiting for TXT AppleBrowse(AppleProvider *_global, QObject *parent = 0) : QObject(parent), global(_global), browse_id(-1) { connect(this, SIGNAL(unavailable_p(XMPP::ServiceInstance)), SIGNAL(unavailable(XMPP::ServiceInstance))); } ~AppleBrowse() { global->stop_all(this); } void browse(const QString &type, const QString &domain) { browse_id = global->browse(this, type.toUtf8(), domain.toUtf8()); } signals: void available(const XMPP::ServiceInstance &instance); void unavailable(const XMPP::ServiceInstance &instance); void error(); // emit delayed void unavailable_p(const XMPP::ServiceInstance &instance); protected: virtual void dns_browseResult(int id, const QDnsSd::BrowseResult &result) { Q_UNUSED(id); if (!result.success) { emit error(); return; } for (const QDnsSd::BrowseEntry &e : result.entries) { XMPP::ServiceInstance si(e.serviceName, e.serviceType, e.replyDomain, QMap()); if (e.added) { int query_id = global->query(this, si.name(), 16); // 16 == TXT pendingByQueryId[query_id] = si.name(); } else // removed { // emit these queued for SS. no worry of SR since // the browse operation is not cancellable. for (int n = 0; n < instances.count(); ++n) { const XMPP::ServiceInstance &i = instances[n]; if (i.name() == si.name()) { emit unavailable_p(i); instances.removeAt(n); --n; // adjust position } } } } } virtual void dns_queryResult(int id, const QDnsSd::QueryResult &result) { if (!result.success) { // if we get here, then it means we received a browse // entry, but could not fetch its TXT record. if // that happens, cancel the query and drop the // browse entry. global->stop(id); pendingByQueryId.remove(id); return; } // qdnssd guarantees at least one answer Q_ASSERT(!result.records.isEmpty()); // only the first entry matters, and it must be an added TXT if (!result.records[0].added || result.records[0].rrtype != 16) return; // we only care about one answer QByteArray name = pendingByQueryId[id]; QList parts = nameToInstanceParts(name); if (parts.isEmpty()) { // TODO: error Q_ASSERT(0); } global->stop(id); pendingByQueryId.remove(id); XMPP::NameRecord rec = importQDnsSdRecord(result.records[0]); // bad answer? if (rec.isNull()) return; QMap attribs = textsToAttribs(rec.texts()); // FIXME: conversion/escaping? XMPP::ServiceInstance si(QString::fromUtf8(parts[0]), QString::fromUtf8(parts[1]), QString::fromUtf8(parts[2]), attribs); // does qdnssd guarantee we won't receive dups? bool found = false; for (const XMPP::ServiceInstance &i : instances) { if (i.name() == si.name()) { found = true; break; } } Q_ASSERT(!found); instances += si; emit available(si); } }; //---------------------------------------------------------------------------- // AppleBrowseLookup //---------------------------------------------------------------------------- // only use this class for a single lookup. if you want to lookup again, // create a new object. class AppleBrowseLookup : public QObject, public QDnsSdDelegate { Q_OBJECT public: AppleProvider * global; int resolve_id; XMPP::NameResolver nameResolverAaaa; XMPP::NameResolver nameResolverA; bool activeAaaa; bool activeA; QTimer waitTimer; QByteArray host; QHostAddress addr4; QHostAddress addr6; int port; AppleBrowseLookup(AppleProvider *_global, QObject *parent = 0) : QObject(parent), global(_global), resolve_id(-1), nameResolverAaaa(this), nameResolverA(this), activeAaaa(false), activeA(false), waitTimer(this) { connect(&nameResolverAaaa, SIGNAL(resultsReady(QList)), SLOT(nameAaaa_resultsReady(QList))); connect(&nameResolverAaaa, SIGNAL(error(XMPP::NameResolver::Error)), SLOT(nameAaaa_error(XMPP::NameResolver::Error))); connect(&nameResolverA, SIGNAL(resultsReady(QList)), SLOT(nameA_resultsReady(QList))); connect(&nameResolverA, SIGNAL(error(XMPP::NameResolver::Error)), SLOT(nameA_error(XMPP::NameResolver::Error))); connect(&waitTimer, SIGNAL(timeout()), SLOT(waitTimer_timeout())); waitTimer.setSingleShot(true); } ~AppleBrowseLookup() { global->stop_all(this); } void resolve(const QByteArray &instance, const QByteArray &type, const QByteArray &domain) { resolve_id = global->resolve(this, instance, type, domain); } signals: // emits at least 1 and at most 2 void finished(const QList &addrs, int port); void error(); protected: void dns_resolveResult(int id, const QDnsSd::ResolveResult &result) { // there is only one response, so deregister global->stop(id); if (!result.success) { emit error(); return; } host = result.hostTarget; port = result.port; activeAaaa = true; activeA = true; nameResolverAaaa.start(host, XMPP::NameRecord::Aaaa); nameResolverA.start(host, XMPP::NameRecord::A); waitTimer.start(500); // 500ms cut-off time, take what we have and run } private slots: void nameAaaa_resultsReady(const QList &results) { // nameresolver guarantees at least one result, and we only // care about the first addr6 = results[0].address(); activeAaaa = false; tryDone(); } void nameAaaa_error(XMPP::NameResolver::Error e) { Q_UNUSED(e); activeAaaa = false; tryDone(); } void nameA_resultsReady(const QList &results) { // nameresolver guarantees at least one result, and we only // care about the first addr4 = results[0].address(); activeA = false; tryDone(); } void nameA_error(XMPP::NameResolver::Error e) { Q_UNUSED(e); activeA = false; tryDone(); } void waitTimer_timeout() { tryDone(); } private: void tryDone() { // we're done if both resolves are inactive and we have no // results, or if the wait timer ends and we have at least // one result if (!activeAaaa && !activeA && addr6.isNull() && addr4.isNull()) { nameResolverAaaa.stop(); nameResolverA.stop(); waitTimer.stop(); emit error(); return; } if (!waitTimer.isActive() && (!addr6.isNull() || !addr4.isNull())) { nameResolverAaaa.stop(); nameResolverA.stop(); QList out; if (!addr4.isNull()) out += addr4; if (!addr6.isNull()) out += addr6; emit finished(out, port); } } }; //---------------------------------------------------------------------------- // AppleNameProvider //---------------------------------------------------------------------------- class AppleNameProvider : public XMPP::NameProvider, public QDnsSdDelegate { Q_OBJECT public: AppleProvider *global; AppleNameProvider(AppleProvider *parent) : NameProvider(parent), global(parent) { } ~AppleNameProvider() { global->stop_all(this); } virtual bool supportsLongLived() const { return true; } virtual bool supportsRecordType(int type) const { // all record types supported Q_UNUSED(type); return true; } virtual int resolve_start(const QByteArray &name, int qType, bool longLived) { Q_UNUSED(longLived); // query is always long lived return global->query(this, name, qType); } virtual void resolve_stop(int id) { global->stop(id); } protected: virtual void dns_queryResult(int id, const QDnsSd::QueryResult &result) { if (!result.success) { emit resolve_error(id, XMPP::NameResolver::ErrorGeneric); return; } QList results; for (const QDnsSd::Record &rec : result.records) { XMPP::NameRecord nr = importQDnsSdRecord(rec); // unsupported type if (nr.isNull()) continue; // if removed, ensure ttl is 0 if (!rec.added) nr.setTtl(0); results += nr; } emit resolve_resultsReady(id, results); } }; //---------------------------------------------------------------------------- // AppleServiceProvider //---------------------------------------------------------------------------- class AppleServiceProvider : public XMPP::ServiceProvider, public QDnsSdDelegate { Q_OBJECT public: class Browse { public: AppleServiceProvider *parent; int id; AppleBrowse * browse; Browse(AppleServiceProvider *_parent) : parent(_parent), id(-1), browse(0) { } ~Browse() { delete browse; parent->idManager.releaseId(id); } }; class Resolve { public: AppleServiceProvider *parent; int id; AppleBrowseLookup * resolve; Resolve(AppleServiceProvider *_parent) : parent(_parent), id(-1), resolve(0) { } ~Resolve() { delete resolve; parent->idManager.releaseId(id); } }; AppleProvider * global; QList browseList; QList resolveList; IdManager idManager; AppleServiceProvider(AppleProvider *parent) : ServiceProvider(parent), global(parent) { } ~AppleServiceProvider() { qDeleteAll(resolveList); qDeleteAll(browseList); global->stop_all(this); } int indexOfBrowseByBrowse(AppleBrowse *browse) const { for (int n = 0; n < browseList.count(); ++n) { if (browseList[n]->browse == browse) return n; } return -1; } int indexOfBrowseById(int id) const { for (int n = 0; n < browseList.count(); ++n) { if (browseList[n]->id == id) return n; } return -1; } int indexOfResolveByResolve(AppleBrowseLookup *resolve) const { for (int n = 0; n < resolveList.count(); ++n) { if (resolveList[n]->resolve == resolve) return n; } return -1; } int indexOfResolveById(int id) const { for (int n = 0; n < resolveList.count(); ++n) { if (resolveList[n]->id == id) return n; } return -1; } virtual int browse_start(const QString &type, const QString &domain) { Browse *b = new Browse(this); b->id = idManager.reserveId(); b->browse = new AppleBrowse(global, this); connect(b->browse, SIGNAL(available(XMPP::ServiceInstance)), SLOT(browse_available(XMPP::ServiceInstance))); connect(b->browse, SIGNAL(unavailable(XMPP::ServiceInstance)), SLOT(browse_unavailable(XMPP::ServiceInstance))); connect(b->browse, SIGNAL(error()), SLOT(browse_error())); browseList += b; b->browse->browse(type, domain); return b->id; } virtual void browse_stop(int id) { int at = indexOfBrowseById(id); if (at == -1) return; Browse *b = browseList[at]; browseList.removeAt(at); delete b; } virtual int resolve_start(const QByteArray &name) { QList parts = nameToInstanceParts(name); if (parts.isEmpty()) { // TODO: signal error rather than die Q_ASSERT(0); } Resolve *r = new Resolve(this); r->id = idManager.reserveId(); r->resolve = new AppleBrowseLookup(global, this); connect(r->resolve, SIGNAL(finished(QList)), SLOT(resolve_finished(QList))); connect(r->resolve, SIGNAL(error()), SLOT(resolve_error())); resolveList += r; r->resolve->resolve(parts[0], parts[1], parts[2]); return r->id; } virtual void resolve_stop(int id) { int at = indexOfResolveById(id); if (at == -1) return; Resolve *r = resolveList[at]; resolveList.removeAt(at); delete r; } virtual int publish_start(const QString &instance, const QString &type, int port, const QMap &attributes) { QByteArray txtRecord = attribsToTxtRecord(attributes); if (txtRecord.isEmpty()) { // TODO: signal error rather than die Q_ASSERT(0); } QString domain = "local"; // FIXME: conversion/escaping is probably wrong? return global->reg(this, instance.toUtf8(), type.toUtf8(), domain.toUtf8(), port, txtRecord); } virtual void publish_update(int id, const QMap &attributes) { // TODO: verify 'id' is valid. if not valid, then assert/return (don't do anything or signal error) QByteArray txtRecord = attribsToTxtRecord(attributes); if (txtRecord.isEmpty()) { // TODO: signal error rather than die Q_ASSERT(0); } if (global->dns.recordUpdateTxt(id, txtRecord, 4500)) { // FIXME: SR QMetaObject::invokeMethod(this, "publish_published", Qt::QueuedConnection, Q_ARG(int, id)); } else { // TODO: unpublish // FIXME: register meta type, SR QMetaObject::invokeMethod( this, "publish_error", Qt::QueuedConnection, Q_ARG(int, id), Q_ARG(XMPP::ServiceLocalPublisher::Error, XMPP::ServiceLocalPublisher::ErrorGeneric)); } } virtual void publish_stop(int id) { global->stop(id); } virtual int publish_extra_start(int pub_id, const XMPP::NameRecord &name) { // TODO Q_UNUSED(pub_id); Q_UNUSED(name); return 0; } virtual void publish_extra_update(int id, const XMPP::NameRecord &name) { // TODO Q_UNUSED(id); Q_UNUSED(name); } virtual void publish_extra_stop(int id) { // TODO Q_UNUSED(id); } // called by AppleProvider void dns_regResult(int id, const QDnsSd::RegResult &result) { // TODO Q_UNUSED(id); Q_UNUSED(result); } private slots: void browse_available(const XMPP::ServiceInstance &instance) { int at = indexOfBrowseByBrowse(static_cast(sender())); Q_ASSERT(at != -1); emit browse_instanceAvailable(browseList[at]->id, instance); } void browse_unavailable(const XMPP::ServiceInstance &instance) { int at = indexOfBrowseByBrowse(static_cast(sender())); Q_ASSERT(at != -1); emit browse_instanceUnavailable(browseList[at]->id, instance); } void browse_error() { int at = indexOfBrowseByBrowse(static_cast(sender())); Q_ASSERT(at != -1); Browse *b = browseList[at]; browseList.removeAt(at); int id = b->id; delete b; // FIXME: this looks weird, we should probably rename our // local function emit ServiceProvider::browse_error(id, XMPP::ServiceBrowser::ErrorGeneric); } void resolve_finished(const QList &addrs, int port) { int at = indexOfResolveByResolve(static_cast(sender())); Q_ASSERT(at != -1); Resolve *r = resolveList[at]; resolveList.removeAt(at); int id = r->id; delete r; QList results; for (const QHostAddress &addr : addrs) { ResolveResult r; r.address = addr; r.port = port; results += r; } emit resolve_resultsReady(id, results); } void resolve_error() { int at = indexOfResolveByResolve(static_cast(sender())); Q_ASSERT(at != -1); Resolve *r = resolveList[at]; resolveList.removeAt(at); int id = r->id; delete r; // FIXME: this looks weird, we should probably rename our // local function emit ServiceProvider::resolve_error(id, XMPP::ServiceResolver::ErrorGeneric); } }; // AppleProvider XMPP::NameProvider *AppleProvider::createNameProviderInternet() { return new AppleNameProvider(this); } XMPP::NameProvider *AppleProvider::createNameProviderLocal() { return new AppleNameProvider(this); } XMPP::ServiceProvider *AppleProvider::createServiceProvider() { return new AppleServiceProvider(this); } #ifdef APPLEDNS_STATIC XMPP::IrisNetProvider *irisnet_createAppleProvider() { return new AppleProvider; } #else Q_EXPORT_PLUGIN2(appledns, AppleProvider) #endif #include "appledns.moc" psi-plus-snapshots-1.4.1456/iris/src/irisnet/appledns/appledns.pri000066400000000000000000000001571370065651000251200ustar00rootroot00000000000000QT *= network HEADERS += $$PWD/qdnssd.h SOURCES += $$PWD/qdnssd.cpp $$PWD/appledns.cpp !mac:LIBS += -ldns_sd psi-plus-snapshots-1.4.1456/iris/src/irisnet/appledns/appledns.pro000066400000000000000000000003521370065651000251230ustar00rootroot00000000000000IRIS_BASE = ../../.. include(../../libbase.pri) TEMPLATE = lib CONFIG += plugin QT -= gui DESTDIR = $$IRIS_BASE/plugins VERSION = 1.0.0 INCLUDEPATH *= $$PWD/../corelib LIBS += -L$$IRIS_BASE/lib -lirisnetcore include(appledns.pri) psi-plus-snapshots-1.4.1456/iris/src/irisnet/appledns/qdnssd.cpp000066400000000000000000000710561370065651000246040ustar00rootroot00000000000000/* * Copyright (C) 2007-2008 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "qdnssd.h" #include "dns_sd.h" #include #include #ifdef Q_OS_WIN // for ntohs #include #else #include #endif namespace { // safeobj stuff, from qca void releaseAndDeleteLater(QObject *owner, QObject *obj) { obj->disconnect(owner); obj->setParent(0); obj->deleteLater(); } class SafeTimer : public QObject { Q_OBJECT public: SafeTimer(QObject *parent = 0) : QObject(parent) { t = new QTimer(this); connect(t, SIGNAL(timeout()), SIGNAL(timeout())); } ~SafeTimer() { releaseAndDeleteLater(this, t); } int interval() const { return t->interval(); } bool isActive() const { return t->isActive(); } bool isSingleShot() const { return t->isSingleShot(); } void setInterval(int msec) { t->setInterval(msec); } void setSingleShot(bool singleShot) { t->setSingleShot(singleShot); } int timerId() const { return t->timerId(); } public slots: void start(int msec) { t->start(msec); } void start() { t->start(); } void stop() { t->stop(); } signals: void timeout(); private: QTimer *t; }; class SafeSocketNotifier : public QObject { Q_OBJECT public: SafeSocketNotifier(int socket, QSocketNotifier::Type type, QObject *parent = 0) : QObject(parent) { sn = new QSocketNotifier(socket, type, this); connect(sn, SIGNAL(activated(int)), SIGNAL(activated(int))); } ~SafeSocketNotifier() { sn->setEnabled(false); releaseAndDeleteLater(this, sn); } bool isEnabled() const { return sn->isEnabled(); } int socket() const { return sn->socket(); } QSocketNotifier::Type type() const { return sn->type(); } public slots: void setEnabled(bool enable) { sn->setEnabled(enable); } signals: void activated(int socket); private: QSocketNotifier *sn; }; // DNSServiceRef must be allocated by the user and initialized by the // API. Additionally, it is unclear from the API whether or not // DNSServiceRef can be copied (it is an opaque data structure). // What we'll do is allocate DNSServiceRef on the heap, allowing us // to maintain a pointer which /can/ be copied. Also, we'll keep // a flag to indicate whether the allocated DNSServiceRef has been // initialized yet. class ServiceRef { private: DNSServiceRef *_p; bool _initialized; public: ServiceRef() : _initialized(false) { _p = (DNSServiceRef *)malloc(sizeof(DNSServiceRef)); } ~ServiceRef() { if (_initialized) DNSServiceRefDeallocate(*_p); free(_p); } DNSServiceRef *data() { return _p; } void setInitialized() { _initialized = true; } }; class RecordRef { private: DNSRecordRef *_p; public: RecordRef() { _p = (DNSRecordRef *)malloc(sizeof(DNSRecordRef)); } ~RecordRef() { free(_p); } DNSRecordRef *data() { return _p; } }; class IdManager { private: QSet set; int at; inline void bump_at() { if (at == 0x7fffffff) at = 0; else ++at; } public: IdManager() : at(0) { } int reserveId() { while (1) { if (!set.contains(at)) { int id = at; set.insert(id); bump_at(); return id; } bump_at(); } } void releaseId(int id) { set.remove(id); } }; } // namespace //---------------------------------------------------------------------------- // QDnsSd //---------------------------------------------------------------------------- class QDnsSd::Private : public QObject { Q_OBJECT public: QDnsSd * q; IdManager idManager; class SubRecord { public: Private * _self; int _id; RecordRef *_sdref; SubRecord(Private *self) : _self(self), _id(-1), _sdref(0) { } ~SubRecord() { delete _sdref; _self->idManager.releaseId(_id); } }; class Request { public: enum Type { Query, Browse, Resolve, Reg }; Private * _self; int _type; int _id; ServiceRef * _sdref; int _sockfd; SafeSocketNotifier *_sn_read; SafeTimer * _errorTrigger; bool _doSignal; LowLevelError _lowLevelError; QList _queryRecords; QList _browseEntries; QByteArray _resolveFullName; QByteArray _resolveHost; int _resolvePort; QByteArray _resolveTxtRecord; QByteArray _regDomain; bool _regConflict; QList _subRecords; Request(Private *self) : _self(self), _id(-1), _sdref(0), _sockfd(-1), _sn_read(0), _errorTrigger(0), _doSignal(false) { } ~Request() { qDeleteAll(_subRecords); delete _errorTrigger; delete _sn_read; delete _sdref; _self->idManager.releaseId(_id); } int subRecordIndexById(int rec_id) const { for (int n = 0; n < _subRecords.count(); ++n) { if (_subRecords[n]->_id == rec_id) return n; } return -1; } }; QHash _requestsById; QHash _requestsBySocket; QHash _requestsByTimer; QHash _requestsByRecId; Private(QDnsSd *_q) : QObject(_q), q(_q) { } ~Private() { qDeleteAll(_requestsById); } void setDelayedError(Request *req, const LowLevelError &lowLevelError) { delete req->_sdref; req->_sdref = 0; req->_lowLevelError = lowLevelError; req->_errorTrigger = new SafeTimer(this); connect(req->_errorTrigger, SIGNAL(timeout()), SLOT(doError())); req->_errorTrigger->setSingleShot(true); _requestsByTimer.insert(req->_errorTrigger, req); req->_errorTrigger->start(); } void removeRequest(Request *req) { for (const SubRecord *srec : req->_subRecords) _requestsByRecId.remove(srec->_id); if (req->_errorTrigger) _requestsByTimer.remove(req->_errorTrigger); if (req->_sn_read) _requestsBySocket.remove(req->_sn_read); _requestsById.remove(req->_id); delete req; } int regIdForRecId(int rec_id) const { Request *req = _requestsByRecId.value(rec_id); if (req) return req->_id; return -1; } int query(const QByteArray &name, int qType) { int id = idManager.reserveId(); Request *req = new Request(this); req->_type = Request::Query; req->_id = id; req->_sdref = new ServiceRef; DNSServiceErrorType err = DNSServiceQueryRecord(req->_sdref->data(), kDNSServiceFlagsLongLivedQuery, 0, name.constData(), qType, kDNSServiceClass_IN, cb_queryRecordReply, req); if (err != kDNSServiceErr_NoError) { setDelayedError(req, LowLevelError("DNSServiceQueryRecord", err)); return id; } req->_sdref->setInitialized(); int sockfd = DNSServiceRefSockFD(*(req->_sdref->data())); if (sockfd == -1) { setDelayedError(req, LowLevelError("DNSServiceRefSockFD", -1)); return id; } req->_sockfd = sockfd; req->_sn_read = new SafeSocketNotifier(sockfd, QSocketNotifier::Read, this); connect(req->_sn_read, SIGNAL(activated(int)), SLOT(sn_activated())); _requestsById.insert(id, req); _requestsBySocket.insert(req->_sn_read, req); return id; } int browse(const QByteArray &serviceType, const QByteArray &domain) { int id = idManager.reserveId(); Request *req = new Request(this); req->_type = Request::Browse; req->_id = id; req->_sdref = new ServiceRef; DNSServiceErrorType err = DNSServiceBrowse(req->_sdref->data(), 0, 0, serviceType.constData(), !domain.isEmpty() ? domain.constData() : NULL, cb_browseReply, req); if (err != kDNSServiceErr_NoError) { setDelayedError(req, LowLevelError("DNSServiceBrowse", err)); return id; } req->_sdref->setInitialized(); int sockfd = DNSServiceRefSockFD(*(req->_sdref->data())); if (sockfd == -1) { setDelayedError(req, LowLevelError("DNSServiceRefSockFD", -1)); return id; } req->_sockfd = sockfd; req->_sn_read = new SafeSocketNotifier(sockfd, QSocketNotifier::Read, this); connect(req->_sn_read, SIGNAL(activated(int)), SLOT(sn_activated())); _requestsById.insert(id, req); _requestsBySocket.insert(req->_sn_read, req); return id; } int resolve(const QByteArray &serviceName, const QByteArray &serviceType, const QByteArray &domain) { int id = idManager.reserveId(); Request *req = new Request(this); req->_type = Request::Resolve; req->_id = id; req->_sdref = new ServiceRef; DNSServiceErrorType err = DNSServiceResolve(req->_sdref->data(), 0, 0, serviceName.constData(), serviceType.constData(), domain.constData(), (DNSServiceResolveReply)cb_resolveReply, req); if (err != kDNSServiceErr_NoError) { setDelayedError(req, LowLevelError("DNSServiceResolve", err)); return id; } req->_sdref->setInitialized(); int sockfd = DNSServiceRefSockFD(*(req->_sdref->data())); if (sockfd == -1) { setDelayedError(req, LowLevelError("DNSServiceRefSockFD", -1)); return id; } req->_sockfd = sockfd; req->_sn_read = new SafeSocketNotifier(sockfd, QSocketNotifier::Read, this); connect(req->_sn_read, SIGNAL(activated(int)), SLOT(sn_activated())); _requestsById.insert(id, req); _requestsBySocket.insert(req->_sn_read, req); return id; } int reg(const QByteArray &serviceName, const QByteArray &serviceType, const QByteArray &domain, int port, const QByteArray &txtRecord) { int id = idManager.reserveId(); Request *req = new Request(this); req->_type = Request::Reg; req->_id = id; if (port < 1 || port > 0xffff) { setDelayedError(req, LowLevelError()); return id; } uint16_t sport = port; sport = htons(sport); req->_sdref = new ServiceRef; DNSServiceErrorType err = DNSServiceRegister( req->_sdref->data(), kDNSServiceFlagsNoAutoRename, 0, serviceName.constData(), serviceType.constData(), domain.constData(), NULL, sport, txtRecord.size(), txtRecord.data(), cb_regReply, req); if (err != kDNSServiceErr_NoError) { setDelayedError(req, LowLevelError("DNSServiceRegister", err)); return id; } req->_sdref->setInitialized(); int sockfd = DNSServiceRefSockFD(*(req->_sdref->data())); if (sockfd == -1) { setDelayedError(req, LowLevelError("DNSServiceRefSockFD", -1)); return id; } req->_sockfd = sockfd; req->_sn_read = new SafeSocketNotifier(sockfd, QSocketNotifier::Read, this); connect(req->_sn_read, SIGNAL(activated(int)), SLOT(sn_activated())); _requestsById.insert(id, req); _requestsBySocket.insert(req->_sn_read, req); return id; } int recordAdd(int reg_id, const Record &rec, LowLevelError *lowLevelError) { Request *req = _requestsById.value(reg_id); if (!req) { if (lowLevelError) *lowLevelError = LowLevelError(); return -1; } RecordRef *recordRef = new RecordRef; DNSServiceErrorType err = DNSServiceAddRecord(*(req->_sdref->data()), recordRef->data(), 0, rec.rrtype, rec.rdata.size(), rec.rdata.data(), rec.ttl); if (err != kDNSServiceErr_NoError) { if (lowLevelError) *lowLevelError = LowLevelError("DNSServiceAddRecord", err); delete recordRef; return -1; } int id = idManager.reserveId(); SubRecord *srec = new SubRecord(this); srec->_id = id; srec->_sdref = recordRef; req->_subRecords += srec; _requestsByRecId.insert(id, req); return id; } bool recordUpdate(int reg_id, int rec_id, const Record &rec, LowLevelError *lowLevelError) { Request *req = _requestsById.value(reg_id); if (!req) { if (lowLevelError) *lowLevelError = LowLevelError(); return false; } SubRecord *srec = 0; if (rec_id != -1) { int at = req->subRecordIndexById(rec_id); if (at == -1) { if (lowLevelError) *lowLevelError = LowLevelError(); return false; } srec = req->_subRecords[at]; } DNSServiceErrorType err = DNSServiceUpdateRecord(*(req->_sdref->data()), srec ? *(srec->_sdref->data()) : NULL, 0, rec.rdata.size(), rec.rdata.data(), rec.ttl); if (err != kDNSServiceErr_NoError) { if (lowLevelError) *lowLevelError = LowLevelError("DNSServiceUpdateRecord", err); return false; } return true; } void recordRemove(int rec_id) { Request *req = _requestsByRecId.value(rec_id); if (!req) return; // this can't fail int at = req->subRecordIndexById(rec_id); SubRecord *srec = req->_subRecords[at]; DNSServiceRemoveRecord(*(req->_sdref->data()), *(srec->_sdref->data()), 0); _requestsByRecId.remove(srec->_id); req->_subRecords.removeAt(at); delete srec; } void stop(int id) { Request *req = _requestsById.value(id); if (req) removeRequest(req); } private slots: void sn_activated() { SafeSocketNotifier *sn_read = static_cast(sender()); Request * req = _requestsBySocket.value(sn_read); if (!req) return; int id = req->_id; DNSServiceErrorType err = DNSServiceProcessResult(*(req->_sdref->data())); // do error if the above function returns an error, or if we // collected an error during a callback if (err != kDNSServiceErr_NoError || !req->_lowLevelError.func.isEmpty()) { LowLevelError lowLevelError; if (err != kDNSServiceErr_NoError) lowLevelError = LowLevelError("DNSServiceProcessResult", err); else lowLevelError = req->_lowLevelError; // reg conflict indicated via callback bool regConflict = false; if (req->_type == Request::Reg && !req->_lowLevelError.func.isEmpty()) regConflict = req->_regConflict; removeRequest(req); if (req->_type == Request::Query) { QDnsSd::QueryResult r; r.success = false; r.lowLevelError = lowLevelError; emit q->queryResult(id, r); } else if (req->_type == Request::Browse) { QDnsSd::BrowseResult r; r.success = false; r.lowLevelError = lowLevelError; emit q->browseResult(id, r); } else if (req->_type == Request::Resolve) { QDnsSd::ResolveResult r; r.success = false; r.lowLevelError = lowLevelError; emit q->resolveResult(id, r); } else // Reg { QDnsSd::RegResult r; r.success = false; if (regConflict) r.errorCode = QDnsSd::RegResult::ErrorConflict; else r.errorCode = QDnsSd::RegResult::ErrorGeneric; r.lowLevelError = lowLevelError; emit q->regResult(id, r); } return; } // handle success if (req->_type == Request::Query) { if (req->_doSignal) { QDnsSd::QueryResult r; r.success = true; r.records = req->_queryRecords; req->_queryRecords.clear(); req->_doSignal = false; emit q->queryResult(id, r); } } else if (req->_type == Request::Browse) { if (req->_doSignal) { QDnsSd::BrowseResult r; r.success = true; r.entries = req->_browseEntries; req->_browseEntries.clear(); req->_doSignal = false; emit q->browseResult(id, r); } } else if (req->_type == Request::Resolve) { if (req->_doSignal) { QDnsSd::ResolveResult r; r.success = true; r.fullName = req->_resolveFullName; r.hostTarget = req->_resolveHost; r.port = req->_resolvePort; r.txtRecord = req->_resolveTxtRecord; req->_doSignal = false; // there is only one response removeRequest(req); emit q->resolveResult(id, r); } } else // Reg { if (req->_doSignal) { QDnsSd::RegResult r; r.success = true; r.domain = req->_regDomain; req->_doSignal = false; emit q->regResult(id, r); } } } void doError() { SafeTimer *t = static_cast(sender()); Request * req = _requestsByTimer.value(t); if (!req) return; int id = req->_id; int type = req->_type; removeRequest(req); if (type == Request::Query) { QDnsSd::QueryResult r; r.success = false; r.lowLevelError = req->_lowLevelError; emit q->queryResult(id, r); } else if (type == Request::Browse) { QDnsSd::BrowseResult r; r.success = false; r.lowLevelError = req->_lowLevelError; emit q->browseResult(id, r); } else if (type == Request::Resolve) { QDnsSd::ResolveResult r; r.success = false; r.lowLevelError = req->_lowLevelError; emit q->resolveResult(id, r); } else // Reg { QDnsSd::RegResult r; r.success = false; r.errorCode = QDnsSd::RegResult::ErrorGeneric; r.lowLevelError = req->_lowLevelError; emit q->regResult(id, r); } } private: static void cb_queryRecordReply(DNSServiceRef ref, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *fullname, uint16_t rrtype, uint16_t rrclass, uint16_t rdlen, const void *rdata, uint32_t ttl, void *context) { Q_UNUSED(ref); Q_UNUSED(interfaceIndex); Q_UNUSED(rrclass); Request *req = static_cast(context); req->_self->handle_queryRecordReply(req, flags, errorCode, fullname, rrtype, rdlen, (const char *)rdata, ttl); } static void cb_browseReply(DNSServiceRef ref, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context) { Q_UNUSED(ref); Q_UNUSED(interfaceIndex); Request *req = static_cast(context); req->_self->handle_browseReply(req, flags, errorCode, serviceName, regtype, replyDomain); } static void cb_resolveReply(DNSServiceRef ref, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *fullname, const char *hosttarget, uint16_t port, uint16_t txtLen, const unsigned char *txtRecord, void *context) { Q_UNUSED(ref); Q_UNUSED(flags); Q_UNUSED(interfaceIndex); Request *req = static_cast(context); req->_self->handle_resolveReply(req, errorCode, fullname, hosttarget, port, txtLen, txtRecord); } static void cb_regReply(DNSServiceRef ref, DNSServiceFlags flags, DNSServiceErrorType errorCode, const char *name, const char *regtype, const char *domain, void *context) { Q_UNUSED(ref); Q_UNUSED(flags); Request *req = static_cast(context); req->_self->handle_regReply(req, errorCode, name, regtype, domain); } void handle_queryRecordReply(Request *req, DNSServiceFlags flags, DNSServiceErrorType errorCode, const char *fullname, uint16_t rrtype, uint16_t rdlen, const char *rdata, uint16_t ttl) { if (errorCode != kDNSServiceErr_NoError) { req->_doSignal = true; req->_lowLevelError = LowLevelError("DNSServiceQueryRecordReply", errorCode); return; } QDnsSd::Record rec; rec.added = (flags & kDNSServiceFlagsAdd) ? true : false; rec.name = QByteArray(fullname); rec.rrtype = rrtype; rec.rdata = QByteArray(rdata, rdlen); rec.ttl = ttl; req->_queryRecords += rec; if (!(flags & kDNSServiceFlagsMoreComing)) req->_doSignal = true; } void handle_browseReply(Request *req, DNSServiceFlags flags, DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain) { if (errorCode != kDNSServiceErr_NoError) { req->_doSignal = true; req->_lowLevelError = LowLevelError("DNSServiceBrowseReply", errorCode); return; } QDnsSd::BrowseEntry e; e.added = (flags & kDNSServiceFlagsAdd) ? true : false; e.serviceName = QByteArray(serviceName); e.serviceType = QByteArray(regtype); e.replyDomain = QByteArray(replyDomain); req->_browseEntries += e; if (!(flags & kDNSServiceFlagsMoreComing)) req->_doSignal = true; } void handle_resolveReply(Request *req, DNSServiceErrorType errorCode, const char *fullname, const char *hosttarget, uint16_t port, uint16_t txtLen, const unsigned char *txtRecord) { if (errorCode != kDNSServiceErr_NoError) { req->_doSignal = true; req->_lowLevelError = LowLevelError("DNSServiceResolveReply", errorCode); return; } req->_resolveFullName = QByteArray(fullname); req->_resolveHost = QByteArray(hosttarget); req->_resolvePort = ntohs(port); req->_resolveTxtRecord = QByteArray((const char *)txtRecord, txtLen); req->_doSignal = true; } void handle_regReply(Request *req, DNSServiceErrorType errorCode, const char *name, const char *regtype, const char *domain) { Q_UNUSED(name); Q_UNUSED(regtype); if (errorCode != kDNSServiceErr_NoError) { req->_doSignal = true; req->_lowLevelError = LowLevelError("DNSServiceRegisterReply", errorCode); if (errorCode == kDNSServiceErr_NameConflict) req->_regConflict = true; else req->_regConflict = false; return; } req->_regDomain = QByteArray(domain); req->_doSignal = true; } }; QDnsSd::QDnsSd(QObject *parent) : QObject(parent) { d = new Private(this); } QDnsSd::~QDnsSd() { delete d; } int QDnsSd::query(const QByteArray &name, int qType) { return d->query(name, qType); } int QDnsSd::browse(const QByteArray &serviceType, const QByteArray &domain) { return d->browse(serviceType, domain); } int QDnsSd::resolve(const QByteArray &serviceName, const QByteArray &serviceType, const QByteArray &domain) { return d->resolve(serviceName, serviceType, domain); } int QDnsSd::reg(const QByteArray &serviceName, const QByteArray &serviceType, const QByteArray &domain, int port, const QByteArray &txtRecord) { return d->reg(serviceName, serviceType, domain, port, txtRecord); } int QDnsSd::recordAdd(int reg_id, const Record &rec, LowLevelError *lowLevelError) { return d->recordAdd(reg_id, rec, lowLevelError); } bool QDnsSd::recordUpdate(int rec_id, const Record &rec, LowLevelError *lowLevelError) { int reg_id = d->regIdForRecId(rec_id); if (reg_id == -1) return false; return d->recordUpdate(reg_id, rec_id, rec, lowLevelError); } bool QDnsSd::recordUpdateTxt(int reg_id, const QByteArray &txtRecord, quint32 ttl, LowLevelError *lowLevelError) { Record rec; rec.rrtype = kDNSServiceType_TXT; rec.rdata = txtRecord; rec.ttl = ttl; return d->recordUpdate(reg_id, -1, rec, lowLevelError); } void QDnsSd::recordRemove(int rec_id) { d->recordRemove(rec_id); } void QDnsSd::stop(int id) { d->stop(id); } QByteArray QDnsSd::createTxtRecord(const QList &strings) { // split into var/val and validate QList vars; QList vals; // null = no value, empty = empty value for (const QByteArray &i : strings) { QByteArray var; QByteArray val; int n = i.indexOf('='); if (n != -1) { var = i.mid(0, n); val = i.mid(n + 1); } else var = i; for (int n = 0; n < var.size(); ++n) { unsigned char c = var[n]; if (c < 0x20 || c > 0x7e) return QByteArray(); } vars += var; vals += val; } TXTRecordRef ref; QByteArray buf(256, 0); TXTRecordCreate(&ref, buf.size(), buf.data()); for (int n = 0; n < vars.count(); ++n) { int valueSize = vals[n].size(); char *value; if (!vals[n].isNull()) value = vals[n].data(); else value = 0; DNSServiceErrorType err = TXTRecordSetValue(&ref, vars[n].data(), valueSize, value); if (err != kDNSServiceErr_NoError) { TXTRecordDeallocate(&ref); return QByteArray(); } } QByteArray out((const char *)TXTRecordGetBytesPtr(&ref), TXTRecordGetLength(&ref)); TXTRecordDeallocate(&ref); return out; } QList QDnsSd::parseTxtRecord(const QByteArray &txtRecord) { QList out; int count = TXTRecordGetCount(txtRecord.size(), txtRecord.data()); for (int n = 0; n < count; ++n) { QByteArray keyBuf(256, 0); uint8_t valueLen; const void * value; DNSServiceErrorType err = TXTRecordGetItemAtIndex(txtRecord.size(), txtRecord.data(), n, keyBuf.size(), keyBuf.data(), &valueLen, &value); if (err != kDNSServiceErr_NoError) return QList(); keyBuf.resize(qstrlen(keyBuf.data())); QByteArray entry = keyBuf; if (value) { entry += '='; entry += QByteArray((const char *)value, valueLen); } out += entry; } return out; } #include "qdnssd.moc" psi-plus-snapshots-1.4.1456/iris/src/irisnet/appledns/qdnssd.h000066400000000000000000000075141370065651000242470ustar00rootroot00000000000000/* * Copyright (C) 2007 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef QDNSSD_H #define QDNSSD_H #include #include #include // DOR-compliant class QDnsSd : public QObject { Q_OBJECT public: class LowLevelError { public: QString func; int code; LowLevelError() : code(0) { } LowLevelError(const QString &_func, int _code) : func(_func), code(_code) { } }; class Record { public: bool added; // only used by QueryResult QByteArray name; int rrtype; QByteArray rdata; quint32 ttl; }; class BrowseEntry { public: bool added; QByteArray serviceName; // these may be different from request, see dns_sd docs QByteArray serviceType; QByteArray replyDomain; }; class QueryResult { public: bool success; LowLevelError lowLevelError; QList records; }; class BrowseResult { public: bool success; LowLevelError lowLevelError; QList entries; }; class ResolveResult { public: bool success; LowLevelError lowLevelError; QByteArray fullName; QByteArray hostTarget; int port; // host byte-order QByteArray txtRecord; }; class RegResult { public: enum Error { ErrorGeneric, ErrorConflict }; bool success; Error errorCode; LowLevelError lowLevelError; QByteArray domain; }; QDnsSd(QObject *parent = 0); ~QDnsSd(); int query(const QByteArray &name, int qType); // domain may be empty int browse(const QByteArray &serviceType, const QByteArray &domain); int resolve(const QByteArray &serviceName, const QByteArray &serviceType, const QByteArray &domain); // domain may be empty int reg(const QByteArray &serviceName, const QByteArray &serviceType, const QByteArray &domain, int port, const QByteArray &txtRecord); // return -1 on error, else a record id int recordAdd(int reg_id, const Record &rec, LowLevelError *lowLevelError = 0); bool recordUpdate(int rec_id, const Record &rec, LowLevelError *lowLevelError = 0); bool recordUpdateTxt(int reg_id, const QByteArray &txtRecord, quint32 ttl, LowLevelError *lowLevelError = 0); void recordRemove(int rec_id); void stop(int id); // return empty array on error static QByteArray createTxtRecord(const QList &strings); // return empty list on error (note that it is possible to have a // txt record with no entries, but in that case txtRecord will be // empty and so you shouldn't call this function) static QList parseTxtRecord(const QByteArray &txtRecord); signals: void queryResult(int id, const QDnsSd::QueryResult &result); void browseResult(int id, const QDnsSd::BrowseResult &result); void resolveResult(int id, const QDnsSd::ResolveResult &result); void regResult(int id, const QDnsSd::RegResult &result); private: class Private; friend class Private; Private *d; }; #endif // QDNSSD_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/appledns/sdtest.cpp000066400000000000000000000315421370065651000246120ustar00rootroot00000000000000/* * Copyright (C) 2007 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "qdnssd.h" #include #include #ifdef Q_OS_WIN // for ntohl #include #else #include #endif class Command { public: enum Type { Query, Browse, Resolve, Reg }; Type type; QString name; // query, resolve, reg int rtype; // query QString stype; // browse, resolve, reg QString domain; // browse, resolve, reg int port; // reg QByteArray txtRecord; // reg int id; int dnsId; bool error; bool done; // for resolve Command() : error(false), done(false) { } }; static QString nameToString(const QByteArray &in) { QStringList parts; int at = 0; while (at < in.size()) { int len = in[at++]; parts += QString::fromUtf8(in.mid(at, len)); at += len; } return parts.join("."); } static QString recordToDesc(const QDnsSd::Record &rec) { QString desc; if (rec.rrtype == 1) // A { quint32 *p = (quint32 *)rec.rdata.data(); desc = QHostAddress(ntohl(*p)).toString(); } else if (rec.rrtype == 28) // AAAA { desc = QHostAddress((quint8 *)rec.rdata.data()).toString(); } else if (rec.rrtype == 12) // PTR { desc = QString("[%1]").arg(nameToString(rec.rdata)); } else desc = QString("%1 bytes").arg(rec.rdata.size()); return desc; } static QStringList txtRecordToStringList(const QByteArray &rdata) { QList txtEntries = QDnsSd::parseTxtRecord(rdata); if (txtEntries.isEmpty()) return QStringList(); QStringList out; foreach (const QByteArray &entry, txtEntries) out += QString::fromUtf8(entry); return out; } static void printIndentedTxt(const QByteArray &txtRecord) { QStringList list = txtRecordToStringList(txtRecord); if (!list.isEmpty()) { foreach (const QString &s, list) printf(" %s\n", qPrintable(s)); } else printf(" (TXT parsing error)\n"); } class App : public QObject { Q_OBJECT public: QList commands; QDnsSd * dns; App() { dns = new QDnsSd(this); connect(dns, SIGNAL(queryResult(int, QDnsSd::QueryResult)), SLOT(dns_queryResult(int, QDnsSd::QueryResult))); connect(dns, SIGNAL(browseResult(int, QDnsSd::BrowseResult)), SLOT(dns_browseResult(int, QDnsSd::BrowseResult))); connect(dns, SIGNAL(resolveResult(int, QDnsSd::ResolveResult)), SLOT(dns_resolveResult(int, QDnsSd::ResolveResult))); connect(dns, SIGNAL(regResult(int, QDnsSd::RegResult)), SLOT(dns_regResult(int, QDnsSd::RegResult))); } public slots: void start() { for (int n = 0; n < commands.count(); ++n) { Command &c = commands[n]; c.id = n; if (c.type == Command::Query) { printf("%2d: Query name=[%s], type=%d ...\n", c.id, qPrintable(c.name), c.rtype); c.dnsId = dns->query(c.name.toUtf8(), c.rtype); } else if (c.type == Command::Browse) { printf("%2d: Browse type=[%s]", c.id, qPrintable(c.stype)); if (!c.domain.isEmpty()) printf(", domain=[%s]", qPrintable(c.domain)); printf(" ...\n"); c.dnsId = dns->browse(c.stype.toUtf8(), c.domain.toUtf8()); } else if (c.type == Command::Resolve) { printf("%2d: Resolve name=[%s], type=[%s], domain=[%s] ...\n", c.id, qPrintable(c.name), qPrintable(c.stype), qPrintable(c.domain)); c.dnsId = dns->resolve(c.name.toUtf8(), c.stype.toUtf8(), c.domain.toUtf8()); } else if (c.type == Command::Reg) { printf("%2d: Register name=[%s], type=[%s]", c.id, qPrintable(c.name), qPrintable(c.stype)); if (!c.domain.isEmpty()) printf(", domain=[%s]", qPrintable(c.domain)); printf(", port=%d ...\n", c.port); if (!c.txtRecord.isEmpty()) printIndentedTxt(c.txtRecord); c.dnsId = dns->reg(c.name.toUtf8(), c.stype.toUtf8(), c.domain.toUtf8(), c.port, c.txtRecord); } } } signals: void quit(); private: int cmdIdToCmdIndex(int cmdId) { for (int n = 0; n < commands.count(); ++n) { const Command &c = commands[n]; if (c.id == cmdId) return n; } return -1; } int dnsIdToCmdIndex(int dnsId) { for (int n = 0; n < commands.count(); ++n) { const Command &c = commands[n]; if (c.dnsId == dnsId) return n; } return -1; } void tryQuit() { // quit if there are nothing but errors or completed resolves bool doQuit = true; foreach (const Command &c, commands) { if (c.error || (c.type == Command::Resolve && c.done)) continue; doQuit = false; break; } if (doQuit) emit quit(); } private slots: void dns_queryResult(int id, const QDnsSd::QueryResult &result) { int at = dnsIdToCmdIndex(id); Command &c = commands[at]; if (!result.success) { printf("%2d: Error.", c.id); if (!result.lowLevelError.func.isEmpty()) printf(" (%s, %d)", qPrintable(result.lowLevelError.func), result.lowLevelError.code); printf("\n"); c.error = true; tryQuit(); return; } foreach (const QDnsSd::Record &rec, result.records) { if (rec.added) { printf("%2d: Added: %s, ttl=%u\n", c.id, qPrintable(recordToDesc(rec)), rec.ttl); if (rec.rrtype == 16) printIndentedTxt(rec.rdata); } else printf("%2d: Removed: %s, ttl=%u\n", c.id, qPrintable(recordToDesc(rec)), rec.ttl); } } void dns_browseResult(int id, const QDnsSd::BrowseResult &result) { int at = dnsIdToCmdIndex(id); Command &c = commands[at]; if (!result.success) { printf("%2d: Error.", c.id); if (!result.lowLevelError.func.isEmpty()) printf(" (%s, %d)", qPrintable(result.lowLevelError.func), result.lowLevelError.code); printf("\n"); c.error = true; tryQuit(); return; } foreach (const QDnsSd::BrowseEntry &e, result.entries) { if (e.added) printf("%2d: Added: [%s] [%s] [%s]\n", c.id, qPrintable(QString::fromUtf8(e.serviceName)), qPrintable(QString::fromUtf8(e.serviceType)), qPrintable(QString::fromUtf8(e.replyDomain))); else printf("%2d: Removed: [%s]\n", c.id, qPrintable(QString::fromUtf8(e.serviceName))); } } void dns_resolveResult(int id, const QDnsSd::ResolveResult &result) { int at = dnsIdToCmdIndex(id); Command &c = commands[at]; if (!result.success) { printf("%2d: Error.", c.id); if (!result.lowLevelError.func.isEmpty()) printf(" (%s, %d)", qPrintable(result.lowLevelError.func), result.lowLevelError.code); printf("\n"); c.error = true; tryQuit(); return; } printf("%2d: Result: host=[%s] port=%d\n", c.id, qPrintable(QString::fromUtf8(result.hostTarget)), result.port); if (!result.txtRecord.isEmpty()) printIndentedTxt(result.txtRecord); c.done = true; tryQuit(); } void dns_regResult(int id, const QDnsSd::RegResult &result) { int at = dnsIdToCmdIndex(id); Command &c = commands[at]; if (!result.success) { QString errstr; if (result.errorCode == QDnsSd::RegResult::ErrorConflict) errstr = "Conflict"; else errstr = "Generic"; printf("%2d: Error (%s).", c.id, qPrintable(errstr)); if (!result.lowLevelError.func.isEmpty()) printf(" (%s, %d)", qPrintable(result.lowLevelError.func), result.lowLevelError.code); printf("\n"); c.error = true; tryQuit(); return; } printf("%2d: Registered: domain=[%s]\n", c.id, qPrintable(QString::fromUtf8(result.domain))); } }; #include "sdtest.moc" void usage() { printf("usage: sdtest [[command] (command) ...]\n"); printf(" options: --txt=str0,...,strn\n"); printf("\n"); printf(" q=name,type# query for a record\n"); printf(" b=type(,domain) browse for services\n"); printf(" r=name,type,domain resolve a service\n"); printf(" e=name,type,port(,domain) register a service\n"); printf("\n"); } int main(int argc, char **argv) { QCoreApplication qapp(argc, argv); QStringList args = qapp.arguments(); args.removeFirst(); if (args.count() < 1) { usage(); return 1; } // options QStringList txt; for (int n = 0; n < args.count(); ++n) { QString s = args[n]; if (!s.startsWith("--")) continue; QString var; QString val; int x = s.indexOf('='); if (x != -1) { var = s.mid(2, x - 2); val = s.mid(x + 1); } else { var = s.mid(2); } bool known = true; if (var == "txt") { txt = val.split(','); } else known = false; if (known) { args.removeAt(n); --n; // adjust position } } // commands QList commands; for (int n = 0; n < args.count(); ++n) { QString str = args[n]; int n = str.indexOf('='); if (n == -1) { printf("Error: bad format of command.\n"); return 1; } QString type = str.mid(0, n); QString rest = str.mid(n + 1); QStringList parts = rest.split(','); if (type == "q") { if (parts.count() < 2) { usage(); return 1; } Command c; c.type = Command::Query; c.name = parts[0]; c.rtype = parts[1].toInt(); commands += c; } else if (type == "b") { if (parts.count() < 1) { usage(); return 1; } Command c; c.type = Command::Browse; c.stype = parts[0]; if (parts.count() >= 2) c.domain = parts[1]; commands += c; } else if (type == "r") { if (parts.count() < 3) { usage(); return 1; } Command c; c.type = Command::Resolve; c.name = parts[0]; c.stype = parts[1]; c.domain = parts[2]; commands += c; } else if (type == "e") { if (parts.count() < 3) { usage(); return 1; } Command c; c.type = Command::Reg; c.name = parts[0]; c.stype = parts[1]; c.port = parts[2].toInt(); if (parts.count() >= 4) c.domain = parts[3]; if (!txt.isEmpty()) { QList strings; foreach (const QString &str, txt) strings += str.toUtf8(); QByteArray txtRecord = QDnsSd::createTxtRecord(strings); if (txtRecord.isEmpty()) { printf("Error: failed to create TXT record, input too large or invalid\n"); return 1; } c.txtRecord = txtRecord; } commands += c; } else { printf("Error: unknown command type '%s'.\n", qPrintable(type)); return 1; } } App app; app.commands = commands; QObject::connect(&app, SIGNAL(quit()), &qapp, SLOT(quit())); QTimer::singleShot(0, &app, SLOT(start())); qapp.exec(); return 0; } psi-plus-snapshots-1.4.1456/iris/src/irisnet/appledns/sdtest.pro000066400000000000000000000002141370065651000246200ustar00rootroot00000000000000CONFIG += console CONFIG -= app_bundle QT -= gui QT += network HEADERS += qdnssd.h SOURCES += qdnssd.cpp sdtest.cpp !mac:LIBS += -ldns_sd psi-plus-snapshots-1.4.1456/iris/src/irisnet/corelib/000077500000000000000000000000001370065651000224045ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/irisnet/corelib/addressresolver.cpp000066400000000000000000000112061370065651000263170ustar00rootroot00000000000000/* * Copyright (C) 2010 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "addressresolver.h" #include "netnames.h" #include "objectsession.h" namespace XMPP { class AddressResolver::Private : public QObject { Q_OBJECT public: enum State { AddressWait, AddressFirstCome }; AddressResolver * q; ObjectSession sess; State state; NameResolver req6; NameResolver req4; bool done6; bool done4; QList addrs6; QList addrs4; QTimer * opTimer; Private(AddressResolver *_q) : QObject(_q), q(_q), sess(this), req6(this), req4(this) { connect(&req6, SIGNAL(resultsReady(QList)), SLOT(req6_resultsReady(QList))); connect(&req6, SIGNAL(error(XMPP::NameResolver::Error)), SLOT(req6_error(XMPP::NameResolver::Error))); connect(&req4, SIGNAL(resultsReady(QList)), SLOT(req4_resultsReady(QList))); connect(&req4, SIGNAL(error(XMPP::NameResolver::Error)), SLOT(req4_error(XMPP::NameResolver::Error))); opTimer = new QTimer(this); connect(opTimer, SIGNAL(timeout()), SLOT(op_timeout())); opTimer->setSingleShot(true); } ~Private() { opTimer->disconnect(this); opTimer->setParent(nullptr); opTimer->deleteLater(); } void start(const QByteArray &hostName) { state = AddressWait; // was an IP address used as input? QHostAddress addr; if (addr.setAddress(QString::fromLatin1(hostName))) { // use this as the result, no need to perform dns query done6 = true; done4 = true; if (addr.protocol() == QAbstractSocket::IPv6Protocol) addrs6 += addr; else addrs4 += addr; sess.defer(this, "ipAddress_input"); return; } done6 = false; done4 = false; // wait at least 5 seconds for one of AAAA or A, to be // consistent with netnames_jdns' dns-sd resolves opTimer->start(5000); req6.start(hostName, NameRecord::Aaaa); req4.start(hostName, NameRecord::A); } void stop() { cleanup(); } private: void cleanup() { sess.reset(); req6.stop(); req4.stop(); opTimer->stop(); addrs6.clear(); addrs4.clear(); } bool tryDone() { if ((done6 && done4) || (state == AddressFirstCome && (done6 || done4))) { QList results = addrs6 + addrs4; cleanup(); if (!results.isEmpty()) emit q->resultsReady(results); else emit q->error(ErrorGeneric); return true; } return false; } private slots: void req6_resultsReady(const QList &results) { for (const NameRecord &rec : results) addrs6 += rec.address(); done6 = true; tryDone(); } void req6_error(XMPP::NameResolver::Error e) { Q_UNUSED(e); done6 = true; tryDone(); } void req4_resultsReady(const QList &results) { for (const NameRecord &rec : results) addrs4 += rec.address(); done4 = true; tryDone(); } void req4_error(XMPP::NameResolver::Error e) { Q_UNUSED(e); done4 = true; tryDone(); } void op_timeout() { state = AddressFirstCome; if (done6 || done4) tryDone(); } void ipAddress_input() { tryDone(); } }; AddressResolver::AddressResolver(QObject *parent) : QObject(parent) { d = new Private(this); } AddressResolver::~AddressResolver() { delete d; } void AddressResolver::start(const QByteArray &hostName) { d->start(hostName); } void AddressResolver::stop() { d->stop(); } } // namespace XMPP #include "addressresolver.moc" psi-plus-snapshots-1.4.1456/iris/src/irisnet/corelib/addressresolver.h000066400000000000000000000025171370065651000257710ustar00rootroot00000000000000/* * Copyright (C) 2010 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef ADDRESSRESOLVER_H #define ADDRESSRESOLVER_H #include #include namespace XMPP { // resolve both AAAA and A for a hostname class AddressResolver : public QObject { Q_OBJECT public: enum Error { ErrorGeneric }; AddressResolver(QObject *parent = nullptr); ~AddressResolver(); void start(const QByteArray &hostName); void stop(); signals: void resultsReady(const QList &results); void error(XMPP::AddressResolver::Error e); private: class Private; friend class Private; Private *d; }; } // namespace XMPP #endif // ADDRESSRESOLVER_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/corelib/corelib.pri000066400000000000000000000021141370065651000245350ustar00rootroot00000000000000QT *= network HEADERS += \ $$PWD/objectsession.h \ $$PWD/irisnetexport.h \ $$PWD/irisnetplugin.h \ $$PWD/irisnetglobal.h \ $$PWD/irisnetglobal_p.h \ $$PWD/netinterface.h \ $$PWD/netavailability.h \ $$PWD/netnames.h \ $$PWD/addressresolver.h SOURCES += \ $$PWD/objectsession.cpp \ $$PWD/irisnetplugin.cpp \ $$PWD/irisnetglobal.cpp \ $$PWD/netinterface.cpp \ $$PWD/netavailability.cpp \ $$PWD/netnames.cpp \ $$PWD/addressresolver.cpp unix { SOURCES += \ $$PWD/netinterface_unix.cpp } need_jdns|lessThan(QT_MAJOR_VERSION, 5) { !ext-qjdns { include(../../jdns/jdns.pri) INCLUDEPATH += $$PWD/../../jdns } SOURCES += \ $$PWD/netnames_jdns.cpp DEFINES += NEED_JDNS } else { SOURCES += $$PWD/netinterface_qtname.cpp \ } equals(QT_MAJOR_VERSION, 5):lessThan(QT_MINOR_VERSION, 15) { SOURCES += $$PWD/netinterface_qtnet.cpp DEFINES += HAVE_QTNET } #include(legacy/legacy.pri) appledns:appledns_bundle { DEFINES += APPLEDNS_STATIC include(../appledns/appledns.pri) } psi-plus-snapshots-1.4.1456/iris/src/irisnet/corelib/corelib.pro000066400000000000000000000005751370065651000245540ustar00rootroot00000000000000IRIS_BASE = ../../.. TEMPLATE = lib QT -= gui TARGET = irisnetcore DESTDIR = $$IRIS_BASE/lib windows:DLLDESTDIR = $$IRIS_BASE/bin VERSION = 1.0.0 include(../../libbase.pri) include(corelib.pri) # fixme: irisnetcore builds as dll or bundled, never static? CONFIG += create_prl windows:!staticlib:DEFINES += IRISNET_MAKEDLL staticlib:PRL_EXPORT_DEFINES += IRISNET_STATIC psi-plus-snapshots-1.4.1456/iris/src/irisnet/corelib/irisnetexport.h000066400000000000000000000017341370065651000255010ustar00rootroot00000000000000/* * Copyright (C) 2006 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef IRISNETEXPORT_H #define IRISNETEXPORT_H #include #ifdef IRISNET_STATIC #define IRISNET_EXPORT #else #ifdef IRISNET_MAKEDLL #define IRISNET_EXPORT Q_DECL_EXPORT #else #define IRISNET_EXPORT Q_DECL_IMPORT #endif #endif #endif // IRISNETEXPORT_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/corelib/irisnetglobal.cpp000066400000000000000000000162741370065651000257600ustar00rootroot00000000000000/* * Copyright (C) 2006 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "irisnetglobal_p.h" #include "irisnetplugin.h" namespace XMPP { // built-in providers #ifdef HAVE_QTNET extern IrisNetProvider *irisnet_createQtNetProvider(); #endif #ifdef Q_OS_UNIX extern IrisNetProvider *irisnet_createUnixNetProvider(); #endif #ifdef NEED_JDNS extern IrisNetProvider *irisnet_createJDnsProvider(); #else extern IrisNetProvider *irisnet_createQtNameProvider(); #endif #ifdef APPLEDNS_STATIC extern IrisNetProvider *irisnet_createAppleProvider(); #endif //---------------------------------------------------------------------------- // internal //---------------------------------------------------------------------------- class PluginInstance { private: QPluginLoader *_loader = nullptr; QObject * _instance = nullptr; bool _ownInstance = false; PluginInstance() { } public: static PluginInstance *fromFile(const QString &fname) { QPluginLoader *loader = new QPluginLoader(fname); if (!loader->load()) { delete loader; return nullptr; } QObject *obj = loader->instance(); if (!obj) { loader->unload(); delete loader; return nullptr; } PluginInstance *i = new PluginInstance; i->_loader = loader; i->_instance = obj; i->_ownInstance = true; return i; } static PluginInstance *fromStatic(QObject *obj) { PluginInstance *i = new PluginInstance; i->_loader = nullptr; i->_instance = obj; i->_ownInstance = false; return i; } static PluginInstance *fromInstance(QObject *obj) { PluginInstance *i = new PluginInstance; i->_loader = nullptr; i->_instance = obj; i->_ownInstance = true; return i; } ~PluginInstance() { if (_ownInstance) delete _instance; if (_loader) { _loader->unload(); delete _loader; } } void claim() { if (_loader) _loader->moveToThread(0); if (_ownInstance) _instance->moveToThread(nullptr); } QObject *instance() { return _instance; } bool sameType(const PluginInstance *other) { if (!_instance || !other->_instance) return false; if (qstrcmp(_instance->metaObject()->className(), other->_instance->metaObject()->className()) != 0) return false; return true; } }; class PluginManager { public: bool builtin_done; QStringList paths; QList plugins; QList providers; PluginManager() { builtin_done = false; } ~PluginManager() { unload(); } bool tryAdd(PluginInstance *i, bool lowPriority = false) { // is it the right kind of plugin? IrisNetProvider *p = qobject_cast(i->instance()); if (!p) return false; // make sure we don't have it already for (int n = 0; n < plugins.count(); ++n) { if (i->sameType(plugins[n])) return false; } i->claim(); plugins += i; if (lowPriority) providers.append(p); else providers.prepend(p); return true; } void addBuiltIn(IrisNetProvider *p) { PluginInstance *i = PluginInstance::fromInstance(p); if (!tryAdd(i, true)) delete i; } void scan() { if (!builtin_done) { #ifdef HAVE_QTNET addBuiltIn(irisnet_createQtNetProvider()); // interfaces. crossplatform. no need to reimplement #endif #ifdef Q_OS_UNIX addBuiltIn(irisnet_createUnixNetProvider()); // gateways #endif #ifdef NEED_JDNS addBuiltIn(irisnet_createJDnsProvider()); #else addBuiltIn(irisnet_createQtNameProvider()); // works with Qt5+ only #endif builtin_done = true; } QObjectList list = QPluginLoader::staticInstances(); for (int n = 0; n < list.count(); ++n) { PluginInstance *i = PluginInstance::fromStatic(list[n]); if (!tryAdd(i)) delete i; } for (int n = 0; n < paths.count(); ++n) { QDir dir(paths[n]); if (!dir.exists()) continue; QStringList entries = dir.entryList(); for (int k = 0; k < entries.count(); ++k) { QFileInfo fi(dir.filePath(entries[k])); if (!fi.exists()) continue; QString fname = fi.filePath(); PluginInstance *i = PluginInstance::fromFile(fname); if (!i) continue; if (!tryAdd(i)) delete i; } } } void unload() { // unload in reverse order QList revlist; for (int n = 0; n < plugins.count(); ++n) revlist.prepend(plugins[n]); qDeleteAll(revlist); plugins.clear(); providers.clear(); } }; class IrisNetGlobal { public: QMutex m; PluginManager pluginManager; QList cleanupList; }; Q_GLOBAL_STATIC(QMutex, global_mutex) static IrisNetGlobal *global = nullptr; static void deinit(); static void init() { QMutexLocker locker(global_mutex()); if (global) return; qRegisterMetaType("QHostAddress"); global = new IrisNetGlobal; qAddPostRoutine(deinit); } void deinit() { if (!global) return; while (!global->cleanupList.isEmpty()) (global->cleanupList.takeFirst())(); delete global; global = nullptr; } //---------------------------------------------------------------------------- // Global //---------------------------------------------------------------------------- void irisNetSetPluginPaths(const QStringList &paths) { init(); QMutexLocker locker(&global->m); global->pluginManager.paths = paths; } void irisNetCleanup() { deinit(); qRemovePostRoutine(deinit); } void irisNetAddPostRoutine(IrisNetCleanUpFunction func) { init(); QMutexLocker locker(&global->m); global->cleanupList.prepend(func); } QList irisNetProviders() { init(); QMutexLocker locker(&global->m); global->pluginManager.scan(); return global->pluginManager.providers; } } // namespace XMPP psi-plus-snapshots-1.4.1456/iris/src/irisnet/corelib/irisnetglobal.h000066400000000000000000000022321370065651000254120ustar00rootroot00000000000000/* * Copyright (C) 2006 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef IRISNETGLOBAL_H #define IRISNETGLOBAL_H #include "irisnetexport.h" #include #include namespace XMPP { // set the directories for plugins. call before doing anything else. IRISNET_EXPORT void irisNetSetPluginPaths(const QStringList &paths); // free any shared data and plugins. // note: this is automatically called when qapp shuts down. IRISNET_EXPORT void irisNetCleanup(); } // namespace XMPP #endif // IRISNETGLOBAL_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/corelib/irisnetglobal_p.h000066400000000000000000000020601370065651000257300ustar00rootroot00000000000000/* * Copyright (C) 2006 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef IRISNETGLOBAL_P_H #define IRISNETGLOBAL_P_H #include "irisnetglobal.h" #include "irisnetplugin.h" namespace XMPP { typedef void (*IrisNetCleanUpFunction)(); IRISNET_EXPORT void irisNetAddPostRoutine(IrisNetCleanUpFunction func); IRISNET_EXPORT QList irisNetProviders(); } // namespace XMPP #endif // IRISNETGLOBAL_P_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/corelib/irisnetplugin.cpp000066400000000000000000000041121370065651000260020ustar00rootroot00000000000000/* * Copyright (C) 2006 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "irisnetplugin.h" namespace XMPP { //---------------------------------------------------------------------------- // IrisNetProvider //---------------------------------------------------------------------------- NetInterfaceProvider *IrisNetProvider::createNetInterfaceProvider() { return nullptr; } NetGatewayProvider *IrisNetProvider::createNetGatewayProvider() { return nullptr; } NetAvailabilityProvider *IrisNetProvider::createNetAvailabilityProvider() { return nullptr; } NameProvider *IrisNetProvider::createNameProviderInternet() { return nullptr; } NameProvider *IrisNetProvider::createNameProviderLocal() { return nullptr; } ServiceProvider *IrisNetProvider::createServiceProvider() { return nullptr; } //---------------------------------------------------------------------------- // NameProvider //---------------------------------------------------------------------------- bool NameProvider::supportsSingle() const { return false; } bool NameProvider::supportsLongLived() const { return false; } bool NameProvider::supportsRecordType(int type) const { Q_UNUSED(type); return false; } void NameProvider::resolve_localResultsReady(int id, const QList &results) { Q_UNUSED(id); Q_UNUSED(results); } void NameProvider::resolve_localError(int id, XMPP::NameResolver::Error e) { Q_UNUSED(id); Q_UNUSED(e); } } // namespace XMPP psi-plus-snapshots-1.4.1456/iris/src/irisnet/corelib/irisnetplugin.h000066400000000000000000000145451370065651000254620ustar00rootroot00000000000000/* * Copyright (C) 2006-2008 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef IRISNETPLUGIN_H #define IRISNETPLUGIN_H #include "irisnetglobal.h" #include "netavailability.h" #include "netinterface.h" #include "netnames.h" namespace XMPP { class NameProvider; class NetAvailabilityProvider; class NetGatewayProvider; class NetInterfaceProvider; class ServiceProvider; class IRISNET_EXPORT IrisNetProvider : public QObject { Q_OBJECT public: virtual NetInterfaceProvider * createNetInterfaceProvider(); virtual NetGatewayProvider * createNetGatewayProvider(); virtual NetAvailabilityProvider *createNetAvailabilityProvider(); virtual NameProvider * createNameProviderInternet(); virtual NameProvider * createNameProviderLocal(); virtual ServiceProvider * createServiceProvider(); }; class IRISNET_EXPORT NetInterfaceProvider : public QObject { Q_OBJECT public: class Info { public: QString id, name; bool isLoopback; QList addresses; }; NetInterfaceProvider(QObject *parent = nullptr) : QObject(parent) { } // calling start should populate an initial list that can be // immediately fetched. do not signal updated() for this. virtual void start() = 0; virtual QList interfaces() const = 0; signals: void updated(); }; class IRISNET_EXPORT NetGatewayProvider : public QObject { Q_OBJECT public: class Info { public: QString ifaceId; QHostAddress gateway; }; NetGatewayProvider(QObject *parent = nullptr) : QObject(parent) { } // calling start should populate an initial list that can be // immediately fetched. do not signal updated() for this. virtual void start() = 0; virtual QList gateways() const = 0; signals: void updated(); }; class IRISNET_EXPORT NetAvailabilityProvider : public QObject { Q_OBJECT public: NetAvailabilityProvider(QObject *parent = nullptr) : QObject(parent) { } // calling start should populate an initial value that can be // immediately fetched. do not signal updated() for this. virtual void start() = 0; virtual bool isAvailable() const = 0; signals: void updated(); }; class IRISNET_EXPORT NameProvider : public QObject { Q_OBJECT public: NameProvider(QObject *parent = nullptr) : QObject(parent) { } virtual bool supportsSingle() const; virtual bool supportsLongLived() const; virtual bool supportsRecordType(int type) const; virtual int resolve_start(const QByteArray &name, int qType, bool longLived) = 0; virtual void resolve_stop(int id) = 0; // transfer from local back to internet virtual void resolve_localResultsReady(int id, const QList &results); virtual void resolve_localError(int id, XMPP::NameResolver::Error e); signals: void resolve_resultsReady(int id, const QList &results); void resolve_error(int id, XMPP::NameResolver::Error e); // transfer from internet to local provider void resolve_useLocal(int id, const QByteArray &name); }; class IRISNET_EXPORT ServiceProvider : public QObject { Q_OBJECT public: class ResolveResult { public: QMap attributes; QHostAddress address; int port; QByteArray hostName; // optional }; ServiceProvider(QObject *parent = nullptr) : QObject(parent) { } virtual int browse_start(const QString &type, const QString &domain) = 0; virtual void browse_stop(int id) = 0; virtual int resolve_start(const QByteArray &name) = 0; virtual void resolve_stop(int id) = 0; virtual int publish_start(const QString &instance, const QString &type, int port, const QMap &attributes) = 0; virtual void publish_update(int id, const QMap &attributes) = 0; virtual void publish_stop(int id) = 0; virtual int publish_extra_start(int pub_id, const NameRecord &name) = 0; virtual void publish_extra_update(int id, const NameRecord &name) = 0; virtual void publish_extra_stop(int id) = 0; signals: void browse_instanceAvailable(int id, const XMPP::ServiceInstance &instance); void browse_instanceUnavailable(int id, const XMPP::ServiceInstance &instance); void browse_error(int id, XMPP::ServiceBrowser::Error e); void resolve_resultsReady(int id, const QList &results); void resolve_error(int id, XMPP::ServiceResolver::Error e); // update does not cause published() signal to be emitted again void publish_published(int id); void publish_error(int id, XMPP::ServiceLocalPublisher::Error e); // update does not cause published() signal to be emitted again void publish_extra_published(int id); void publish_extra_error(int id, XMPP::ServiceLocalPublisher::Error e); }; } // namespace XMPP Q_DECLARE_INTERFACE(XMPP::NetGatewayProvider, "com.affinix.irisnet.IrisGatewayProvider/1.0") Q_DECLARE_INTERFACE(XMPP::IrisNetProvider, "com.affinix.irisnet.IrisNetProvider/1.0") Q_DECLARE_INTERFACE(XMPP::NetInterfaceProvider, "com.affinix.irisnet.NetInterfaceProvider/2.0") Q_DECLARE_INTERFACE(XMPP::NameProvider, "com.affinix.irisnet.NameProvider/1.0") Q_DECLARE_INTERFACE(XMPP::ServiceProvider, "com.affinix.irisnet.ServiceProvider/1.0") #endif // IRISNETPLUGIN_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/corelib/netavailability.cpp000066400000000000000000000022641370065651000262750ustar00rootroot00000000000000/* * Copyright (C) 2008 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "netavailability.h" namespace XMPP { class NetAvailability::Private : public QObject { Q_OBJECT public: NetAvailability *q; Private(NetAvailability *_q) : QObject(_q), q(_q) { } }; NetAvailability::NetAvailability(QObject *parent) : QObject(parent) { d = new Private(this); } NetAvailability::~NetAvailability() { delete d; } bool NetAvailability::isAvailable() const { // TODO return true; } } // namespace XMPP #include "netavailability.moc" psi-plus-snapshots-1.4.1456/iris/src/irisnet/corelib/netavailability.h000066400000000000000000000022021370065651000257320ustar00rootroot00000000000000/* * Copyright (C) 2008 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef NETAVAILABILITY_H #define NETAVAILABILITY_H #include "irisnetglobal.h" namespace XMPP { class NetAvailability : public QObject { Q_OBJECT public: NetAvailability(QObject *parent = nullptr); ~NetAvailability(); bool isAvailable() const; signals: void changed(bool available); private: class Private; friend class Private; Private *d; }; } // namespace XMPP #endif // NETAVAILABILITY_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/corelib/netinterface.cpp000066400000000000000000000243641370065651000255700ustar00rootroot00000000000000/* * Copyright (C) 2006 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "netinterface.h" #include "irisnetglobal_p.h" #include "irisnetplugin.h" #include #include #include namespace XMPP { //---------------------------------------------------------------------------- // NetTracker //---------------------------------------------------------------------------- class NetTracker : public QObject { Q_OBJECT public: QList getInterfaces() { QMutexLocker locker(&m); return info; } NetTracker() { QList list = irisNetProviders(); c = nullptr; for (IrisNetProvider *p : list) { c = p->createNetInterfaceProvider(); if (c) break; } Q_ASSERT(c); // we have built-in support, so this should never fail connect(c, SIGNAL(updated()), SLOT(c_updated())); c->start(); info = filterList(c->interfaces()); } ~NetTracker() { QMutexLocker locker(&m); delete c; } signals: void updated(); private: static QList filterList(const QList &in) { QList out; for (int n = 0; n < in.count(); ++n) { if (!in[n].isLoopback) out += in[n]; } return out; } private slots: void c_updated() { { QMutexLocker locker(&m); info = filterList(c->interfaces()); } emit updated(); } private: // this are all protected by m NetInterfaceProvider * c; QMutex m; QList info; }; // Global because static getRef needs this too. Q_GLOBAL_STATIC(QMutex, nettracker_mutex) class NetTrackerThread : public QThread { Q_OBJECT public: /** Get a reference to the NetTracker singleton. Calls to getInterfaces will immediately give valid results */ static NetTrackerThread *getRef() { QMutexLocker locker(nettracker_mutex()); if (!self) { self = new NetTrackerThread(); } self->refs++; return self; } /** Release reference. */ void releaseRef() { QMutexLocker locker(nettracker_mutex()); Q_ASSERT(refs > 0); refs--; if (refs <= 0) { exit(0); wait(); delete this; self = nullptr; } } QList getInterfaces() { return nettracker->getInterfaces(); } ~NetTrackerThread() { // locked from caller } signals: void updated(); private: NetTrackerThread() { // locked from caller refs = 0; moveToThread(QCoreApplication::instance()->thread()); startMutex = new QMutex(); { QMutexLocker startLocker(startMutex); start(); startCond.wait(startMutex); // wait for thread startup finished } delete startMutex; startMutex = nullptr; } void run() { { QMutexLocker locker(startMutex); nettracker = new NetTracker(); connect(nettracker, SIGNAL(updated()), SIGNAL(updated()), Qt::DirectConnection); startCond.wakeOne(); // we're ready to serve. } exec(); delete nettracker; nettracker = nullptr; } private: QWaitCondition startCond; QMutex * startMutex = nullptr; // these are all protected by global nettracker_mutex. int refs = 0; static NetTrackerThread *self; NetTracker * nettracker = nullptr; }; NetTrackerThread *NetTrackerThread::self = nullptr; //---------------------------------------------------------------------------- // NetInterface //---------------------------------------------------------------------------- class NetInterfacePrivate : public QObject { Q_OBJECT public: friend class NetInterfaceManagerPrivate; NetInterface *q; QPointer man; bool valid; QString id, name; QList addrs; NetInterfacePrivate(NetInterface *_q) : QObject(_q), q(_q) { valid = false; } void doUnavailable() { if (!valid) return; valid = false; if (man.isNull()) return; man->unreg(q); emit q->unavailable(); } }; NetInterface::NetInterface(const QString &id, NetInterfaceManager *manager) : QObject(manager) { d = new NetInterfacePrivate(this); d->man = manager; NetInterfaceProvider::Info *info = (NetInterfaceProvider::Info *)d->man->reg(id, this); if (info) { d->valid = true; d->id = info->id; d->name = info->name; d->addrs = info->addresses; delete info; } } NetInterface::~NetInterface() { if (d->valid && !d->man.isNull()) d->man->unreg(this); delete d; } bool NetInterface::isValid() const { return d->valid && !d->man.isNull(); } QString NetInterface::id() const { return d->id; } QString NetInterface::name() const { return d->name; } QList NetInterface::addresses() const { return d->addrs; } //---------------------------------------------------------------------------- // NetInterfaceManager //---------------------------------------------------------------------------- class NetInterfaceManagerPrivate : public QObject { Q_OBJECT public: NetInterfaceManager *q; QList info; QList listeners; NetTrackerThread * tracker; bool pending; NetInterfaceManagerPrivate(NetInterfaceManager *_q) : QObject(_q), q(_q) { tracker = NetTrackerThread::getRef(); pending = false; connect(tracker, SIGNAL(updated()), SLOT(tracker_updated())); } ~NetInterfaceManagerPrivate() { tracker->releaseRef(); tracker = nullptr; } static int lookup(const QList &list, const QString &id) { for (int n = 0; n < list.count(); ++n) { if (list[n].id == id) return n; } return -1; } static bool sameContent(const NetInterfaceProvider::Info &a, const NetInterfaceProvider::Info &b) { // assume ids are the same already return (a.name == b.name && a.isLoopback == b.isLoopback && a.addresses == b.addresses); } void do_update() { // grab the latest info QList newinfo = tracker->getInterfaces(); QStringList here_ids, gone_ids; // removed / changed for (int n = 0; n < info.count(); ++n) { int i = lookup(newinfo, info[n].id); // id is still here if (i != -1) { // content changed? if (!sameContent(info[n], newinfo[i])) { gone_ids += info[n].id; here_ids += info[n].id; } } else { // id is gone gone_ids += info[n].id; } } // added for (int n = 0; n < newinfo.count(); ++n) { int i = lookup(info, newinfo[n].id); if (i == -1) here_ids += newinfo[n].id; } info = newinfo; // announce gone for (int n = 0; n < gone_ids.count(); ++n) { // work on a copy, just in case the list changes. // it is important to make the copy here, and not // outside the outer loop, in case the items // get deleted QList list = listeners; for (int i = 0; i < list.count(); ++i) { if (list[i]->d->id == gone_ids[n]) { list[i]->d->doUnavailable(); } } } // announce here for (int n = 0; n < here_ids.count(); ++n) emit q->interfaceAvailable(here_ids[n]); } public slots: void tracker_updated() { // collapse multiple updates by queuing up an update if there isn't any queued yet. if (!pending) { QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); pending = true; } } void update() { pending = false; do_update(); } }; NetInterfaceManager::NetInterfaceManager(QObject *parent) : QObject(parent) { d = new NetInterfaceManagerPrivate(this); } NetInterfaceManager::~NetInterfaceManager() { delete d; } QStringList NetInterfaceManager::interfaces() const { d->info = d->tracker->getInterfaces(); QStringList out; for (int n = 0; n < d->info.count(); ++n) { out += d->info[n].id; } return out; } QString NetInterfaceManager::interfaceForAddress(const QHostAddress &a) { NetInterfaceManager netman; QStringList list = netman.interfaces(); for (int n = 0; n < list.count(); ++n) { NetInterface iface(list[n], &netman); if (iface.addresses().contains(a)) return list[n]; } return QString(); } void *NetInterfaceManager::reg(const QString &id, NetInterface *i) { for (int n = 0; n < d->info.count(); ++n) { if (d->info[n].id == id) { d->listeners += i; return new NetInterfaceProvider::Info(d->info[n]); } } return nullptr; } void NetInterfaceManager::unreg(NetInterface *i) { d->listeners.removeAll(i); } } // namespace XMPP #include "netinterface.moc" psi-plus-snapshots-1.4.1456/iris/src/irisnet/corelib/netinterface.h000066400000000000000000000144741370065651000252360ustar00rootroot00000000000000/* * Copyright (C) 2006 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef NETINTERFACE_H #define NETINTERFACE_H #include "irisnetglobal.h" namespace XMPP { class NetInterfaceManager; class NetInterfaceManagerPrivate; class NetInterfacePrivate; /** \brief Provides information about a network interface NetInterface provides information about a particular network interface. Construct it by passing the interface id of interest (e.g. "eth0") and a NetInterfaceManager parent object. Interface ids can be obtained from NetInterfaceManager. To test if a NetInterface is valid, call isValid(). Use name() to return a display-friendly name of the interface. The addresses() function returns a list of IP addresses for this interface. Here's an example of how to print the IP addresses of eth0: \code NetInterface iface("eth0"); if(iface.isValid()) { QList addrs = iface.addresses(); for(int n = 0; n < addrs.count(); ++n) printf("%s\n", qPrintable(addrs[n].toString())); } \endcode If the interface goes away, the unavailable() signal is emitted and the NetInterface becomes invalid. \sa NetInterfaceManager */ class IRISNET_EXPORT NetInterface : public QObject { Q_OBJECT public: /** \brief Constructs a new interface object with the given \a id and \a manager If \a id is not a valid interface id, then the object will not be valid (isValid() will return false). Normally it is not necessary to check for validity, since interface ids obtained from NetInterfaceManager are guaranteed to be valid until the event loop resumes. \sa isValid */ NetInterface(const QString &id, NetInterfaceManager *manager); /** \brief Destroys the interface object */ ~NetInterface(); /** \brief Returns true if the interface is valid, otherwise returns false \sa unavailable */ bool isValid() const; /** \brief Returns the id of this interface This is the id that was passed in the constructor. */ QString id() const; /** \brief Returns a display-friendly name of this interface The name may be the same as the id. \sa id */ QString name() const; /** \brief Returns the addresses of this interface There will always be at least one address. In some cases there might be multiple, such as on Unix where it is possible for the same interface to have both an IPv4 and an IPv6 address. */ QList addresses() const; signals: /** \brief Notifies when the interface becomes unavailable Once this signal is emitted, the NetInterface object becomes invalid and is no longer very useful. A new NetInterface object must be created if a valid object with current information is desired. \note If the interface information changes, the interface is considered to have become unavailable. \sa isValid */ void unavailable(); private: friend class NetInterfacePrivate; NetInterfacePrivate *d; friend class NetInterfaceManagerPrivate; }; /** \brief Manages network interface information NetInterfaceManager keeps track of all available network interfaces. An interface is considered available if it exists, is "Up", has at least one IP address, and is non-Loopback. The interfaces() function returns a list of available interface ids. These ids can be used with NetInterface to get information about the interfaces. For example, here is how you could print the names of the available interfaces: \code NetInterfaceManager netman; QStringList id_list = netman.interfaces(); for(int n = 0; n < id_list.count(); ++n) { NetInterface iface(id_list[n], &netman); printf("name: [%s]\n", qPrintable(iface.name())); } \endcode When a new network interface is available, the interfaceAvailable() signal will be emitted. Note that interface unavailability is not notified by NetInterfaceManager. Instead, use NetInterface to monitor a specific network interface for unavailability. Interface ids obtained through NetInterfaceManager are guaranteed to be valid until the event loop resumes, or until the next call to interfaces() or interfaceForAddress(). \sa NetInterface */ class IRISNET_EXPORT NetInterfaceManager : public QObject { Q_OBJECT public: /** \brief Constructs a new manager object with the given \a parent */ NetInterfaceManager(QObject *parent = nullptr); /** \brief Destroys the manager object */ ~NetInterfaceManager(); /** \brief Returns the list of available interface ids \sa interfaceAvailable \sa interfaceForAddress */ QStringList interfaces() const; /** \brief Looks up an interface id by IP address This function looks for an interface that has the address \a a. If there is no such interface, a null string is returned. This is useful for determing the network interface associated with an outgoing QTcpSocket: \code QString iface = NetInterfaceManager::interfaceForAddress(tcpSocket->localAddress()); \endcode \sa interfaces */ static QString interfaceForAddress(const QHostAddress &a); signals: /** \brief Notifies when an interface becomes available The \a id parameter is the interface id, ready to use with NetInterface. */ void interfaceAvailable(const QString &id); private: friend class NetInterfaceManagerPrivate; NetInterfaceManagerPrivate *d; friend class NetInterface; friend class NetInterfacePrivate; void *reg(const QString &id, NetInterface *i); void unreg(NetInterface *i); }; } // namespace XMPP #endif // NETINTERFACE_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/corelib/netinterface_qtname.cpp000066400000000000000000000176631370065651000271410ustar00rootroot00000000000000/* * Copyright (C) 2017 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "irisnetplugin.h" #include namespace XMPP { class IrisQtName : public NameProvider { Q_OBJECT Q_INTERFACES(XMPP::NameProvider) struct Query { bool isHostInfo; quintptr handle; }; int currentId; QHash lookups; QHash hostInfo; // we need all these tricks with double mapping because we still support Qt 5.5.1. (5.12 // is way better) public: IrisQtName(QObject *parent = nullptr) : NameProvider(parent), currentId(0) { } ~IrisQtName() { for (auto const &l : lookups) { if (!l.isHostInfo) { delete reinterpret_cast(l.handle); } } } bool supportsSingle() const { return true; } bool supportsRecordType(int type) const { // yes the types matched to ones from jdns, so it's fine. static QVector types = { QDnsLookup::A, QDnsLookup::AAAA, QDnsLookup::ANY, QDnsLookup::CNAME, QDnsLookup::MX, QDnsLookup::NS, QDnsLookup::PTR, QDnsLookup::SRV, QDnsLookup::TXT }; return types.contains(type); } int resolve_start(const QByteArray &name, int qType, bool longLived) { Q_UNUSED(longLived); // FIXME handle local like in jdns name provider int id = currentId++; // check if it's A/AAAA. QDnsLookup fails to handle this in some cases. QHostAddress addr(QString::fromLatin1(name)); if (!addr.isNull()) { QList results; XMPP::NameRecord r; r.setAddress(addr); results.append(r); QMetaObject::invokeMethod(this, "resolve_resultsReady", Qt::QueuedConnection, Q_ARG(int, id), Q_ARG(QList, results)); } else { if (qType == QDnsLookup::A || qType == QDnsLookup::AAAA) { // QDnsLookup doesn't support A and AAAA according to docs (see corresponding note) int hiid = QHostInfo::lookupHost(QString::fromLatin1(name), this, SLOT(hostInfoFinished(QHostInfo))); hostInfo.insert(hiid, id); lookups.insert(id, Query { true, quintptr(hiid) }); } else { QDnsLookup *lookup = new QDnsLookup((QDnsLookup::Type)qType, QString::fromLatin1(name), this); connect(lookup, SIGNAL(finished()), this, SLOT(handleLookup())); lookup->setProperty("iid", id); lookups.insert(id, Query { false, quintptr(lookup) }); QMetaObject::invokeMethod(lookup, "lookup", Qt::QueuedConnection); } } return id; } void resolve_stop(int id) { auto it = lookups.find(id); if (it != lookups.end()) { Query q = *it; if (q.isHostInfo) { auto hiid = int(q.handle); QHostInfo::abortHostLookup(hiid); hostInfo.remove(hiid); lookups.erase(it); } else { auto lookup = reinterpret_cast(q.handle); lookup->abort(); // handleLookup will catch it and delete } } } private slots: void hostInfoFinished(const QHostInfo &info) { auto hiid = info.lookupId(); auto idIt = hostInfo.find(hiid); if (idIt == hostInfo.end()) { // removed already? return; } auto id = *idIt; hostInfo.erase(idIt); lookups.remove(id); if (info.error() != QHostInfo::NoError) { if (info.error() == QHostInfo::HostNotFound) { emit resolve_error(id, XMPP::NameResolver::ErrorNoName); } else { emit resolve_error(id, XMPP::NameResolver::ErrorGeneric); } return; } QList results; for (const auto &a : info.addresses()) { XMPP::NameRecord ir(info.hostName().toLatin1(), 5 * 60); // ttl = 5 mins ir.setAddress(a); results += ir; } emit resolve_resultsReady(id, results); } void handleLookup() { QDnsLookup *lookup = static_cast(sender()); int id = lookup->property("iid").toInt(); lookups.remove(id); if (lookup->error() != QDnsLookup::NoError) { XMPP::NameResolver::Error e; switch (lookup->error()) { case QDnsLookup::InvalidReplyError: e = XMPP::NameResolver::ErrorTimeout; break; case QDnsLookup::NotFoundError: e = XMPP::NameResolver::ErrorNoName; break; case QDnsLookup::ResolverError: case QDnsLookup::OperationCancelledError: case QDnsLookup::InvalidRequestError: case QDnsLookup::ServerFailureError: case QDnsLookup::ServerRefusedError: default: e = XMPP::NameResolver::ErrorGeneric; break; } if (lookup->error() != QDnsLookup::OperationCancelledError) { // don't report after resolve_stop() emit resolve_error(id, e); } lookup->deleteLater(); return; } QList results; for (auto &qtr : lookup->hostAddressRecords()) { XMPP::NameRecord ir(qtr.name().toLatin1(), qtr.timeToLive()); ir.setAddress(qtr.value()); results += ir; } for (auto &qtr : lookup->mailExchangeRecords()) { XMPP::NameRecord ir(qtr.name().toLatin1(), qtr.timeToLive()); ir.setMx(qtr.exchange().toLatin1(), qtr.preference()); results += ir; } for (auto &qtr : lookup->nameServerRecords()) { XMPP::NameRecord ir(qtr.name().toLatin1(), qtr.timeToLive()); ir.setNs(qtr.value().toLatin1()); results += ir; } for (auto &qtr : lookup->pointerRecords()) { XMPP::NameRecord ir(qtr.name().toLatin1(), qtr.timeToLive()); ir.setPtr(qtr.value().toLatin1()); results += ir; } for (auto &qtr : lookup->canonicalNameRecords()) { XMPP::NameRecord ir(qtr.name().toLatin1(), qtr.timeToLive()); ir.setCname(qtr.value().toLatin1()); results += ir; } for (auto &qtr : lookup->serviceRecords()) { XMPP::NameRecord ir(qtr.name().toLatin1(), qtr.timeToLive()); ir.setSrv(qtr.target().toLatin1(), qtr.port(), qtr.priority(), qtr.weight()); results += ir; } for (auto &qtr : lookup->textRecords()) { XMPP::NameRecord ir(qtr.name().toLatin1(), qtr.timeToLive()); ir.setTxt(qtr.values()); results += ir; } lookup->deleteLater(); emit resolve_resultsReady(id, results); } }; class IrisQtNameProvider : public IrisNetProvider { Q_OBJECT Q_INTERFACES(XMPP::IrisNetProvider) public: NameProvider *createNameProviderInternet() { return new IrisQtName; } }; IrisNetProvider *irisnet_createQtNameProvider() { return new IrisQtNameProvider; } } // namespace XMPP #include "netinterface_qtname.moc" psi-plus-snapshots-1.4.1456/iris/src/irisnet/corelib/netinterface_qtnet.cpp000066400000000000000000000044221370065651000267740ustar00rootroot00000000000000/* * Copyright (C) 2017 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "irisnetplugin.h" #include namespace XMPP { class IrisQtNet : public NetInterfaceProvider { Q_OBJECT Q_INTERFACES(XMPP::NetInterfaceProvider) public: QList info; QNetworkConfigurationManager ncm; IrisQtNet() { connect(&ncm, SIGNAL(configurationAdded(QNetworkConfiguration)), SLOT(check())); connect(&ncm, SIGNAL(configurationChanged(QNetworkConfiguration)), SLOT(check())); connect(&ncm, SIGNAL(configurationRemoved(QNetworkConfiguration)), SLOT(check())); } void start() { poll(); } QList interfaces() const { return info; } void poll() { QList ifaces; auto const interfaces = QNetworkInterface::allInterfaces(); for (auto &iface : interfaces) { Info i; i.id = iface.name(); i.name = iface.humanReadableName(); i.isLoopback = (iface.flags() & QNetworkInterface::IsLoopBack); for (auto &ae : iface.addressEntries()) { i.addresses.append(ae.ip()); } ifaces << i; } info = ifaces; } public slots: void check() { poll(); emit updated(); } }; class IrisQtNetProvider : public IrisNetProvider { Q_OBJECT Q_INTERFACES(XMPP::IrisNetProvider) public: NetInterfaceProvider *createNetInterfaceProvider() { return new IrisQtNet; } }; IrisNetProvider *irisnet_createQtNetProvider() { return new IrisQtNetProvider; } } // namespace XMPP #include "netinterface_qtnet.moc" psi-plus-snapshots-1.4.1456/iris/src/irisnet/corelib/netinterface_unix.cpp000066400000000000000000000130351370065651000266240ustar00rootroot00000000000000/* * Copyright (C) 2006 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ // this code assumes the following ioctls work: // SIOCGIFCONF - get list of devices // SIOCGIFFLAGS - get flags about a device // gateway detection currently only works on linux #include "irisnetplugin.h" #include #include #include #include #include #include #ifndef SIOCGIFCONF // for solaris #include #endif #include #include #ifdef Q_OS_LINUX static QStringList read_proc_as_lines(const char *procfile) { QStringList out; FILE *f = fopen(procfile, "r"); if (!f) return out; QByteArray buf; while (!feof(f)) { // max read on a proc is 4K QByteArray block(4096, 0); int ret = fread(block.data(), 1, block.size(), f); if (ret <= 0) break; block.resize(ret); buf += block; } fclose(f); QString str = QString::fromLocal8Bit(buf); #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) out = str.split('\n', Qt::SkipEmptyParts); #else out = str.split('\n', QString::SkipEmptyParts); #endif return out; } static QHostAddress linux_ipv6_to_qaddr(const QString &in) { QHostAddress out; if (in.length() != 32) return out; quint8 raw[16]; for (int n = 0; n < 16; ++n) { bool ok; int x = in.mid(n * 2, 2).toInt(&ok, 16); if (!ok) return out; raw[n] = (quint8)x; } out.setAddress(raw); return out; } static QHostAddress linux_ipv4_to_qaddr(const QString &in) { QHostAddress out; if (in.length() != 8) return out; quint32 raw; unsigned char *rawp = (unsigned char *)&raw; for (int n = 0; n < 4; ++n) { bool ok; int x = in.mid(n * 2, 2).toInt(&ok, 16); if (!ok) return out; rawp[n] = (unsigned char)x; } out.setAddress(raw); return out; } static QList get_linux_gateways() { QList out; QStringList lines = read_proc_as_lines("/proc/net/route"); // skip the first line, so we start at 1 for (int n = 1; n < lines.count(); ++n) { const QString &line = lines[n]; #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) QStringList parts = line.simplified().split(' ', Qt::SkipEmptyParts); #else QStringList parts = line.simplified().split(' ', QString::SkipEmptyParts); #endif if (parts.count() < 10) // net-tools does 10, but why not 11? continue; QHostAddress addr = linux_ipv4_to_qaddr(parts[2]); if (addr.isNull()) continue; int iflags = parts[3].toInt(nullptr, 16); if (!(iflags & RTF_UP)) continue; if (!(iflags & RTF_GATEWAY)) continue; XMPP::NetGatewayProvider::Info g; g.ifaceId = parts[0]; g.gateway = addr; out += g; } lines = read_proc_as_lines("/proc/net/ipv6_route"); for (int n = 0; n < lines.count(); ++n) { const QString &line = lines[n]; #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) QStringList parts = line.simplified().split(' ', Qt::SkipEmptyParts); #else QStringList parts = line.simplified().split(' ', QString::SkipEmptyParts); #endif if (parts.count() < 10) continue; QHostAddress addr = linux_ipv6_to_qaddr(parts[4]); if (addr.isNull()) continue; int iflags = parts[8].toInt(nullptr, 16); if (!(iflags & RTF_UP)) continue; if (!(iflags & RTF_GATEWAY)) continue; XMPP::NetGatewayProvider::Info g; g.ifaceId = parts[9]; g.gateway = addr; out += g; } return out; } #endif static QList get_unix_gateways() { // support other platforms here QList out; #ifdef Q_OS_LINUX out = get_linux_gateways(); #endif return out; } namespace XMPP { class UnixGateway : public NetGatewayProvider { Q_OBJECT Q_INTERFACES(XMPP::NetGatewayProvider) public: QList info; // QTimer t; UnixGateway() //: t(this) { // connect(&t, SIGNAL(timeout()), SLOT(check())); // TODO track changes without timers } void start() { // t.start(5000); poll(); } QList gateways() const { return info; } void poll() { info = get_unix_gateways(); } public slots: void check() { poll(); emit updated(); } }; class UnixNetProvider : public IrisNetProvider { Q_OBJECT Q_INTERFACES(XMPP::IrisNetProvider) public: virtual NetGatewayProvider *createNetGatewayProvider() { return new UnixGateway; } }; IrisNetProvider *irisnet_createUnixNetProvider() { return new UnixNetProvider; } } // namespace XMPP #include "netinterface_unix.moc" psi-plus-snapshots-1.4.1456/iris/src/irisnet/corelib/netnames.cpp000066400000000000000000001260001370065651000247210ustar00rootroot00000000000000/* * Copyright (C) 2006 Justin Karneges * Copyright (C) 2009-2010 Dennis Schridde * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "netnames.h" #include "addressresolver.h" #include "irisnetglobal_p.h" #include "irisnetplugin.h" #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) #include #endif //#include #include //#define NETNAMES_DEBUG #ifdef NETNAMES_DEBUG #define NNDEBUG (qDebug() << this << "#" << __FUNCTION__ << ":") #endif namespace XMPP { //---------------------------------------------------------------------------- // NameRecord //---------------------------------------------------------------------------- class NameRecord::Private : public QSharedData { public: QByteArray owner; NameRecord::Type type; int ttl; QHostAddress address; QByteArray name; int priority, weight, port; QList texts; QByteArray cpu, os; QByteArray rawData; }; #define ENSURE_D \ { \ if (!d) \ d = new Private; \ } NameRecord::NameRecord() : d(nullptr) { } NameRecord::NameRecord(const QByteArray &owner, int ttl) : d(nullptr) { setOwner(owner); setTtl(ttl); } NameRecord::NameRecord(const NameRecord &from) : d(nullptr) { *this = from; } NameRecord::~NameRecord() { } NameRecord &NameRecord::operator=(const NameRecord &from) { d = from.d; return *this; } bool NameRecord::operator==(const NameRecord &o) { if (isNull() != o.isNull() || owner() != o.owner() || ttl() != o.ttl() || type() != o.type()) { return false; } switch (type()) { case XMPP::NameRecord::A: case XMPP::NameRecord::Aaaa: return address() == o.address(); case XMPP::NameRecord::Mx: return name() == o.name() && priority() == o.priority(); case XMPP::NameRecord::Srv: return name() == o.name() && port() == o.port() && priority() == o.priority() && weight() == o.weight(); case XMPP::NameRecord::Cname: case XMPP::NameRecord::Ptr: case XMPP::NameRecord::Ns: return name() == o.name(); case XMPP::NameRecord::Txt: return texts() == o.texts(); case XMPP::NameRecord::Hinfo: return cpu() == o.cpu() && os() == o.os(); case XMPP::NameRecord::Null: return rawData() == o.rawData(); case XMPP::NameRecord::Any: return false; } return false; } bool NameRecord::isNull() const { return (d ? false : true); } QByteArray NameRecord::owner() const { Q_ASSERT(d); return d->owner; } int NameRecord::ttl() const { Q_ASSERT(d); return d->ttl; } NameRecord::Type NameRecord::type() const { Q_ASSERT(d); return d->type; } QHostAddress NameRecord::address() const { Q_ASSERT(d); return d->address; } QByteArray NameRecord::name() const { Q_ASSERT(d); return d->name; } int NameRecord::priority() const { Q_ASSERT(d); return d->priority; } int NameRecord::weight() const { Q_ASSERT(d); return d->weight; } int NameRecord::port() const { Q_ASSERT(d); return d->port; } QList NameRecord::texts() const { Q_ASSERT(d); return d->texts; } QByteArray NameRecord::cpu() const { Q_ASSERT(d); return d->cpu; } QByteArray NameRecord::os() const { Q_ASSERT(d); return d->os; } QByteArray NameRecord::rawData() const { Q_ASSERT(d); return d->rawData; } void NameRecord::setOwner(const QByteArray &name) { ENSURE_D d->owner = name; } void NameRecord::setTtl(int seconds) { ENSURE_D d->ttl = seconds; } void NameRecord::setAddress(const QHostAddress &a) { ENSURE_D if (a.protocol() == QAbstractSocket::IPv6Protocol) d->type = NameRecord::Aaaa; else d->type = NameRecord::A; d->address = a; } void NameRecord::setMx(const QByteArray &name, int priority) { ENSURE_D d->type = NameRecord::Mx; d->name = name; d->priority = priority; } void NameRecord::setSrv(const QByteArray &name, int port, int priority, int weight) { ENSURE_D d->type = NameRecord::Srv; d->name = name; d->port = port; d->priority = priority; d->weight = weight; } void NameRecord::setCname(const QByteArray &name) { ENSURE_D d->type = NameRecord::Cname; d->name = name; } void NameRecord::setPtr(const QByteArray &name) { ENSURE_D d->type = NameRecord::Ptr; d->name = name; } void NameRecord::setTxt(const QList &texts) { ENSURE_D d->type = NameRecord::Txt; d->texts = texts; } void NameRecord::setHinfo(const QByteArray &cpu, const QByteArray &os) { ENSURE_D d->type = NameRecord::Hinfo; d->cpu = cpu; d->os = os; } void NameRecord::setNs(const QByteArray &name) { ENSURE_D d->type = NameRecord::Ns; d->name = name; } void NameRecord::setNull(const QByteArray &rawData) { ENSURE_D d->type = NameRecord::Null; d->rawData = rawData; } QDebug operator<<(QDebug dbg, XMPP::NameRecord::Type type) { dbg.nospace() << "XMPP::NameRecord::"; switch (type) { case XMPP::NameRecord::A: dbg.nospace() << "A"; break; case XMPP::NameRecord::Aaaa: dbg.nospace() << "Aaaa"; break; case XMPP::NameRecord::Mx: dbg.nospace() << "Mx"; break; case XMPP::NameRecord::Srv: dbg.nospace() << "Srv"; break; case XMPP::NameRecord::Cname: dbg.nospace() << "Cname"; break; case XMPP::NameRecord::Ptr: dbg.nospace() << "Ptr"; break; case XMPP::NameRecord::Txt: dbg.nospace() << "Txt"; break; case XMPP::NameRecord::Hinfo: dbg.nospace() << "Hinfo"; break; case XMPP::NameRecord::Ns: dbg.nospace() << "Ns"; break; case XMPP::NameRecord::Null: dbg.nospace() << "Null"; break; case XMPP::NameRecord::Any: dbg.nospace() << "Any"; break; } return dbg; } QDebug operator<<(QDebug dbg, const XMPP::NameRecord &record) { dbg.nospace() << "XMPP::NameRecord(" << "owner=" << record.owner() << ", ttl=" << record.ttl() << ", type=" << record.type(); switch (record.type()) { case XMPP::NameRecord::A: case XMPP::NameRecord::Aaaa: dbg.nospace() << ", address=" << record.address(); break; case XMPP::NameRecord::Mx: dbg.nospace() << ", name=" << record.name() << ", priority=" << record.priority(); break; case XMPP::NameRecord::Srv: dbg.nospace() << ", name=" << record.name() << ", port=" << record.port() << ", priority=" << record.priority() << ", weight=" << record.weight(); break; case XMPP::NameRecord::Cname: case XMPP::NameRecord::Ptr: case XMPP::NameRecord::Ns: dbg.nospace() << ", name=" << record.name(); break; case XMPP::NameRecord::Txt: dbg.nospace() << ", texts={" << record.texts() << "}"; break; case XMPP::NameRecord::Hinfo: dbg.nospace() << ", cpu=" << record.cpu() << ", os=" << record.os(); break; case XMPP::NameRecord::Null: dbg.nospace() << ", size=" << record.rawData().size(); break; case XMPP::NameRecord::Any: dbg.nospace() << ", "; // should not happen Q_ASSERT(false); break; } dbg.nospace() << ")"; return dbg; } //---------------------------------------------------------------------------- // ServiceInstance //---------------------------------------------------------------------------- class ServiceInstance::Private : public QSharedData { public: QString instance, type, domain; QMap attribs; QByteArray name; }; ServiceInstance::ServiceInstance() : d(new Private) { } ServiceInstance::ServiceInstance(const QString &instance, const QString &type, const QString &domain, const QMap &attribs) : d(new Private) { d->instance = instance; d->type = type; d->domain = domain; d->attribs = attribs; // FIXME: escape the items d->name = instance.toLatin1() + '.' + type.toLatin1() + '.' + domain.toLatin1(); } ServiceInstance::ServiceInstance(const ServiceInstance &from) : d(nullptr) { *this = from; } ServiceInstance::~ServiceInstance() { } ServiceInstance &ServiceInstance::operator=(const ServiceInstance &from) { d = from.d; return *this; } QString ServiceInstance::instance() const { return d->instance; } QString ServiceInstance::type() const { return d->type; } QString ServiceInstance::domain() const { return d->domain; } QMap ServiceInstance::attributes() const { return d->attribs; } QByteArray ServiceInstance::name() const { return d->name; } //---------------------------------------------------------------------------- // NameManager //---------------------------------------------------------------------------- class NameManager; Q_GLOBAL_STATIC(QMutex, nman_mutex) static NameManager *g_nman = nullptr; class NameResolver::Private { public: NameResolver *q; int type; bool longLived; int id; Private(NameResolver *_q) : q(_q) { } }; class ServiceBrowser::Private { public: ServiceBrowser *q; int id; Private(ServiceBrowser *_q) : q(_q) { } }; class ServiceResolver::Private : public QObject { Q_OBJECT public: Private(ServiceResolver *parent) : q(parent), dns_sd_resolve_id(0), requestedProtocol(IPv6_IPv4), port(0), protocol(QAbstractSocket::IPv6Protocol) { } /* DNS-SD interaction with NameManager */ ServiceResolver *q; //!< Pointing upwards, so NameManager can call its signals int dns_sd_resolve_id; //!< DNS-SD lookup id, set by NameManager /* configuration */ Protocol requestedProtocol; //!< IP protocol requested by user /* state trackers */ QString domain; //!< Domain we are currently looking up QString host; //!< Hostname we are currently looking up QHostAddress address; //!< IP address we are currently looking up quint16 port; //!< Port we are currently looking up QAbstractSocket::NetworkLayerProtocol protocol; //!< IP protocol we are currently looking up XMPP::WeightedNameRecordList srvList; //!< List of resolved SRV names QList hostList; //!< List or resolved hostnames for current SRV name QList resolverList; //!< NameResolvers currently in use, needed for cleanup }; WeightedNameRecordList::WeightedNameRecordList() : currentPriorityGroup(priorityGroups.end()) /* void current state */ { } WeightedNameRecordList::WeightedNameRecordList(const QList &list) { append(list); } WeightedNameRecordList::WeightedNameRecordList(const WeightedNameRecordList &other) { *this = other; } WeightedNameRecordList &WeightedNameRecordList::operator=(const WeightedNameRecordList &other) { priorityGroups = other.priorityGroups; if (other.currentPriorityGroup != other.priorityGroups.end()) { currentPriorityGroup = priorityGroups.find(other.currentPriorityGroup.key()); } else { currentPriorityGroup = priorityGroups.end(); } return *this; } WeightedNameRecordList::~WeightedNameRecordList() { } bool WeightedNameRecordList::isEmpty() const { return currentPriorityGroup == const_cast(this)->priorityGroups.end(); } XMPP::NameRecord WeightedNameRecordList::takeNext() { /* Find the next useful priority group */ while (currentPriorityGroup != priorityGroups.end() && currentPriorityGroup->empty()) { ++currentPriorityGroup; } /* There are no priority groups left, return failure */ if (currentPriorityGroup == priorityGroups.end()) { #ifdef NETNAMES_DEBUG NNDEBUG << "No more SRV records left"; #endif return XMPP::NameRecord(); } /* Find the new total weight of this priority group */ int totalWeight = 0; for (const XMPP::NameRecord &record : *currentPriorityGroup) { totalWeight += record.weight(); } #ifdef NETNAMES_DEBUG NNDEBUG << "Total weight:" << totalWeight; #endif /* Pick a random entry */ #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) int randomWeight = totalWeight ? QRandomGenerator::global()->bounded(totalWeight) : 0; #else int randomWeight = qrand() / static_cast(RAND_MAX) * totalWeight; #endif #ifdef NETNAMES_DEBUG NNDEBUG << "Picked weight:" << randomWeight; #endif /* Iterate through the priority group until we found the randomly selected entry */ WeightedNameRecordPriorityGroup::iterator it(currentPriorityGroup->begin()); for (int currentWeight = it->weight(); currentWeight < randomWeight; currentWeight += (++it)->weight()) { } Q_ASSERT(it != currentPriorityGroup->end()); /* We are going to delete the entry in the list, so save it */ XMPP::NameRecord result(*it); #ifdef NETNAMES_DEBUG NNDEBUG << "Picked record:" << result; #endif /* Delete the entry from list, to prevent it from being tried multiple times */ currentPriorityGroup->remove(it->weight(), *it); if (currentPriorityGroup->isEmpty()) { priorityGroups.erase(currentPriorityGroup++); } return result; } void WeightedNameRecordList::clear() { priorityGroups.clear(); /* void current state */ currentPriorityGroup = priorityGroups.end(); } void WeightedNameRecordList::append(const XMPP::WeightedNameRecordList &list) { /* Copy over all records from all groups */ for (const WeightedNameRecordPriorityGroup &group : list.priorityGroups) { for (const NameRecord &record : group) { append(record); } } /* Reset to beginning */ currentPriorityGroup = priorityGroups.begin(); } void WeightedNameRecordList::append(const QList &list) { for (const XMPP::NameRecord &record : list) { if (record.type() != XMPP::NameRecord::Srv) { continue; } WeightedNameRecordPriorityGroup group(priorityGroups.value(record.priority())); group.insert(record.weight(), record); if (!priorityGroups.contains(record.priority())) { priorityGroups.insert(record.priority(), group); } } /* Reset to beginning */ currentPriorityGroup = priorityGroups.begin(); } void WeightedNameRecordList::append(const XMPP::NameRecord &record) { WeightedNameRecordPriorityGroup group(priorityGroups.value(record.priority())); Q_ASSERT(record.type() == XMPP::NameRecord::Srv); group.insert(record.weight(), record); if (!priorityGroups.contains(record.priority())) { priorityGroups.insert(record.priority(), group); } /* Reset to beginning */ currentPriorityGroup = priorityGroups.begin(); } void WeightedNameRecordList::append(const QString &hostname, quint16 port) { NameRecord record(hostname.toLocal8Bit(), std::numeric_limits::max()); record.setSrv(hostname.toLocal8Bit(), port, std::numeric_limits::max(), 0); append(record); /* Reset to beginning */ currentPriorityGroup = priorityGroups.begin(); } XMPP::WeightedNameRecordList &WeightedNameRecordList::operator<<(const XMPP::WeightedNameRecordList &list) { append(list); return *this; } WeightedNameRecordList &WeightedNameRecordList::operator<<(const QList &list) { append(list); return *this; } XMPP::WeightedNameRecordList &WeightedNameRecordList::operator<<(const XMPP::NameRecord &record) { append(record); return *this; } QDebug operator<<(QDebug dbg, const XMPP::WeightedNameRecordList &list) { dbg.nospace() << "XMPP::WeightedNameRecordList(\n"; /* operator(QDebug, QMap const&) has a bug which makes it crash when trying to print the dereferenced end() iterator */ if (!list.isEmpty()) { #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) dbg.nospace() << "current=" << *list.currentPriorityGroup << Qt::endl; #else dbg.nospace() << "current=" << *list.currentPriorityGroup << endl; #endif } dbg.nospace() << "{"; for (int priority : list.priorityGroups.keys()) { #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) dbg.nospace() << "\t" << priority << "->" << list.priorityGroups.value(priority) << Qt::endl; #else dbg.nospace() << "\t" << priority << "->" << list.priorityGroups.value(priority) << endl; #endif } dbg.nospace() << "})"; return dbg; } class ServiceLocalPublisher::Private { public: ServiceLocalPublisher *q; int id; Private(ServiceLocalPublisher *_q) : q(_q) { } }; class NameManager : public QObject { Q_OBJECT public: NameProvider * p_net, *p_local; ServiceProvider * p_serv; QHash res_instances; QHash res_sub_instances; QHash br_instances; QHash sres_instances; QHash slp_instances; NameManager(QObject *parent = nullptr) : QObject(parent) { p_net = nullptr; p_local = nullptr; p_serv = 0; } ~NameManager() { delete p_net; delete p_local; delete p_serv; } static NameManager *instance() { QMutexLocker locker(nman_mutex()); if (!g_nman) { g_nman = new NameManager; irisNetAddPostRoutine(NetNames::cleanup); } return g_nman; } static void cleanup() { delete g_nman; g_nman = nullptr; } void resolve_start(NameResolver::Private *np, const QByteArray &name, int qType, bool longLived) { QMutexLocker locker(nman_mutex()); np->type = qType; np->longLived = longLived; if (!p_net) { NameProvider * c = 0; QList list = irisNetProviders(); for (int n = 0; n < list.count(); ++n) { IrisNetProvider *p = list[n]; c = p->createNameProviderInternet(); if (c) break; } Q_ASSERT(c); // we have built-in support, so this should never fail p_net = c; // use queued connections qRegisterMetaType>("QList"); qRegisterMetaType("XMPP::NameResolver::Error"); connect(p_net, SIGNAL(resolve_resultsReady(int, QList)), SLOT(provider_resolve_resultsReady(int, QList))); connect(p_net, SIGNAL(resolve_error(int, XMPP::NameResolver::Error)), SLOT(provider_resolve_error(int, XMPP::NameResolver::Error))); connect(p_net, SIGNAL(resolve_useLocal(int, QByteArray)), SLOT(provider_resolve_useLocal(int, QByteArray))); } np->id = p_net->resolve_start(name, qType, longLived); // printf("assigning %d to %p\n", req_id, np); res_instances.insert(np->id, np); } void resolve_stop(NameResolver::Private *np) { // FIXME: stop sub instances? p_net->resolve_stop(np->id); resolve_cleanup(np); } void resolve_cleanup(NameResolver::Private *np) { // clean up any sub instances QList sub_instances_to_remove; QHashIterator it(res_sub_instances); while (it.hasNext()) { it.next(); if (it.value() == np->id) sub_instances_to_remove += it.key(); } for (int res_sub_id : sub_instances_to_remove) { res_sub_instances.remove(res_sub_id); p_local->resolve_stop(res_sub_id); } // clean up primary instance res_instances.remove(np->id); NameResolver *q = np->q; delete q->d; q->d = nullptr; } void browse_start(ServiceBrowser::Private *np, const QString &type, const QString &domain) { QMutexLocker locker(nman_mutex()); if (!p_serv) { ServiceProvider * c = nullptr; QList list = irisNetProviders(); for (int n = 0; n < list.count(); ++n) { IrisNetProvider *p = list[n]; c = p->createServiceProvider(); if (c) break; } Q_ASSERT(c); // we have built-in support, so this should never fail p_serv = c; // use queued connections qRegisterMetaType("XMPP::ServiceInstance"); qRegisterMetaType("XMPP::ServiceBrowser::Error"); connect(p_serv, SIGNAL(browse_instanceAvailable(int, XMPP::ServiceInstance)), SLOT(provider_browse_instanceAvailable(int, XMPP::ServiceInstance)), Qt::QueuedConnection); connect(p_serv, SIGNAL(browse_instanceUnavailable(int, XMPP::ServiceInstance)), SLOT(provider_browse_instanceUnavailable(int, XMPP::ServiceInstance)), Qt::QueuedConnection); connect(p_serv, SIGNAL(browse_error(int, XMPP::ServiceBrowser::Error)), SLOT(provider_browse_error(int, XMPP::ServiceBrowser::Error)), Qt::QueuedConnection); } /*np->id = */ np->id = p_serv->browse_start(type, domain); br_instances.insert(np->id, np); } void resolve_instance_start(ServiceResolver::Private *np, const QByteArray &name) { QMutexLocker locker(nman_mutex()); if (!p_serv) { ServiceProvider * c = nullptr; QList list = irisNetProviders(); for (int n = 0; n < list.count(); ++n) { IrisNetProvider *p = list[n]; c = p->createServiceProvider(); if (c) break; } Q_ASSERT(c); // we have built-in support, so this should never fail p_serv = c; // use queued connections qRegisterMetaType>( "QList"); connect(p_serv, SIGNAL(resolve_resultsReady(int, QList)), SLOT(provider_resolve_resultsReady(int, QList)), Qt::QueuedConnection); } /* store the id so we can stop it later */ np->dns_sd_resolve_id = p_serv->resolve_start(name); sres_instances.insert(np->dns_sd_resolve_id, np); } void publish_start(ServiceLocalPublisher::Private *np, const QString &instance, const QString &type, int port, const QMap &attribs) { QMutexLocker locker(nman_mutex()); if (!p_serv) { ServiceProvider * c = nullptr; QList list = irisNetProviders(); for (int n = 0; n < list.count(); ++n) { IrisNetProvider *p = list[n]; c = p->createServiceProvider(); if (c) break; } Q_ASSERT(c); // we have built-in support, so this should never fail p_serv = c; // use queued connections qRegisterMetaType("XMPP::ServiceLocalPublisher::Error"); connect(p_serv, SIGNAL(publish_published(int)), SLOT(provider_publish_published(int)), Qt::QueuedConnection); connect(p_serv, SIGNAL(publish_extra_published(int)), SLOT(provider_publish_extra_published(int)), Qt::QueuedConnection); } /*np->id = */ np->id = p_serv->publish_start(instance, type, port, attribs); slp_instances.insert(np->id, np); } void publish_extra_start(ServiceLocalPublisher::Private *np, const NameRecord &rec) { np->id = p_serv->publish_extra_start(np->id, rec); } private slots: void provider_resolve_resultsReady(int id, const QList &results) { NameResolver::Private *np = res_instances.value(id); NameResolver * q = np->q; // resolve_cleanup deletes np if (!np->longLived) resolve_cleanup(np); emit q->resultsReady(results); } void provider_resolve_error(int id, XMPP::NameResolver::Error e) { NameResolver::Private *np = res_instances.value(id); NameResolver * q = np->q; // resolve_cleanup deletes np resolve_cleanup(np); emit q->error(e); } void provider_local_resolve_resultsReady(int id, const QList &results) { int par_id = res_sub_instances.value(id); NameResolver::Private *np = res_instances.value(par_id); if (!np->longLived) res_sub_instances.remove(id); p_net->resolve_localResultsReady(par_id, results); } void provider_local_resolve_error(int id, XMPP::NameResolver::Error e) { int par_id = res_sub_instances.value(id); res_sub_instances.remove(id); p_net->resolve_localError(par_id, e); } void provider_resolve_useLocal(int id, const QByteArray &name) { // transfer to local if (!p_local) { NameProvider * c = nullptr; QList list = irisNetProviders(); for (int n = 0; n < list.count(); ++n) { IrisNetProvider *p = list[n]; c = p->createNameProviderLocal(); if (c) break; } Q_ASSERT(c); // we have built-in support, so this should never fail // FIXME: not true, binding can fail p_local = c; // use queued connections qRegisterMetaType>("QList"); qRegisterMetaType("XMPP::NameResolver::Error"); connect(p_local, SIGNAL(resolve_resultsReady(int, QList)), SLOT(provider_local_resolve_resultsReady(int, QList)), Qt::QueuedConnection); connect(p_local, SIGNAL(resolve_error(int, XMPP::NameResolver::Error)), SLOT(provider_local_resolve_error(int, XMPP::NameResolver::Error)), Qt::QueuedConnection); } NameResolver::Private *np = res_instances.value(id); /*// transfer to local only if(np->longLived) { res_instances.remove(np->id); np->id = p_local->resolve_start(name, np->type, true); res_instances.insert(np->id, np); } // sub request else { int req_id = p_local->resolve_start(name, np->type, false); res_sub_instances.insert(req_id, np->id); }*/ int req_id = p_local->resolve_start(name, np->type, np->longLived); res_sub_instances.insert(req_id, np->id); } void provider_browse_instanceAvailable(int id, const XMPP::ServiceInstance &i) { ServiceBrowser::Private *np = br_instances.value(id); emit np->q->instanceAvailable(i); } void provider_browse_instanceUnavailable(int id, const XMPP::ServiceInstance &i) { ServiceBrowser::Private *np = br_instances.value(id); emit np->q->instanceUnavailable(i); } void provider_browse_error(int id, XMPP::ServiceBrowser::Error e) { Q_UNUSED(e); ServiceBrowser::Private *np = br_instances.value(id); // TODO emit np->q->error(); } void provider_resolve_resultsReady(int id, const QList &results) { ServiceResolver::Private *np = sres_instances.value(id); emit np->q->resultReady(results[0].address, results[0].port); } void provider_publish_published(int id) { ServiceLocalPublisher::Private *np = slp_instances.value(id); emit np->q->published(); } void provider_publish_extra_published(int id) { Q_UNUSED(id); // ServiceLocalPublisher::Private *np = slp_instances.value(id); // emit np->q->published(); } }; //---------------------------------------------------------------------------- // NameResolver //---------------------------------------------------------------------------- // copied from JDNS #define JDNS_RTYPE_A 1 #define JDNS_RTYPE_AAAA 28 #define JDNS_RTYPE_MX 15 #define JDNS_RTYPE_SRV 33 #define JDNS_RTYPE_CNAME 5 #define JDNS_RTYPE_PTR 12 #define JDNS_RTYPE_TXT 16 #define JDNS_RTYPE_HINFO 13 #define JDNS_RTYPE_NS 2 #define JDNS_RTYPE_ANY 255 static int recordType2Rtype(NameRecord::Type type) { switch (type) { case NameRecord::A: return JDNS_RTYPE_A; case NameRecord::Aaaa: return JDNS_RTYPE_AAAA; case NameRecord::Mx: return JDNS_RTYPE_MX; case NameRecord::Srv: return JDNS_RTYPE_SRV; case NameRecord::Cname: return JDNS_RTYPE_CNAME; case NameRecord::Ptr: return JDNS_RTYPE_PTR; case NameRecord::Txt: return JDNS_RTYPE_TXT; case NameRecord::Hinfo: return JDNS_RTYPE_HINFO; case NameRecord::Ns: return JDNS_RTYPE_NS; case NameRecord::Null: return 10; case NameRecord::Any: return JDNS_RTYPE_ANY; } return -1; } NameResolver::NameResolver(QObject *parent) : QObject(parent) { d = nullptr; } NameResolver::~NameResolver() { stop(); } void NameResolver::start(const QByteArray &name, NameRecord::Type type, Mode mode) { stop(); d = new Private(this); int qType = recordType2Rtype(type); if (qType == -1) qType = JDNS_RTYPE_A; NameManager::instance()->resolve_start(d, name, qType, mode == NameResolver::LongLived ? true : false); } void NameResolver::stop() { if (d) { NameManager::instance()->resolve_stop(d); delete d; d = nullptr; } } QDebug operator<<(QDebug dbg, XMPP::NameResolver::Error e) { dbg.nospace() << "XMPP::NameResolver::"; switch (e) { case XMPP::NameResolver::ErrorGeneric: dbg.nospace() << "ErrorGeneric"; break; case XMPP::NameResolver::ErrorNoName: dbg.nospace() << "ErrorNoName"; break; case XMPP::NameResolver::ErrorTimeout: dbg.nospace() << "ErrorTimeout"; break; case XMPP::NameResolver::ErrorNoLocal: dbg.nospace() << "ErrorNoLocal"; break; case XMPP::NameResolver::ErrorNoLongLived: dbg.nospace() << "ErrorNoLongLived"; break; } return dbg; } //---------------------------------------------------------------------------- // ServiceBrowser //---------------------------------------------------------------------------- ServiceBrowser::ServiceBrowser(QObject *parent) : QObject(parent) { d = new Private(this); } ServiceBrowser::~ServiceBrowser() { delete d; } void ServiceBrowser::start(const QString &type, const QString &domain) { NameManager::instance()->browse_start(d, type, domain); } void ServiceBrowser::stop() { } //---------------------------------------------------------------------------- // ServiceResolver //---------------------------------------------------------------------------- ServiceResolver::ServiceResolver(QObject *parent) : QObject(parent) { #ifdef NETNAMES_DEBUG NNDEBUG; #endif d = new Private(this); } ServiceResolver::~ServiceResolver() { delete d; } void ServiceResolver::clear_resolvers() { #ifdef NETNAMES_DEBUG NNDEBUG; #endif /* cleanup all resolvers */ for (XMPP::NameResolver *resolver : d->resolverList) { cleanup_resolver(resolver); } } void ServiceResolver::cleanup_resolver(XMPP::NameResolver *resolver) { #ifdef NETNAMES_DEBUG NNDEBUG << "r:" << resolver; #endif if (resolver) { /* do not just "delete", because we might have been called from a slot that was invoked by the resolver, and we do not want to create a mess there. */ disconnect(resolver); resolver->stop(); resolver->deleteLater(); d->resolverList.removeAll(resolver); } } ServiceResolver::Protocol ServiceResolver::protocol() const { return d->requestedProtocol; } void ServiceResolver::setProtocol(ServiceResolver::Protocol p) { d->requestedProtocol = p; } /* DNS-SD lookup */ void ServiceResolver::start(const QByteArray &name) { NameManager::instance()->resolve_instance_start(d, name); } /* normal host lookup */ void ServiceResolver::start(const QString &host, quint16 port) { #ifdef NETNAMES_DEBUG NNDEBUG << "h:" << host << "p:" << port; #endif /* clear host list */ d->hostList.clear(); d->protocol = (d->requestedProtocol == IPv6_IPv4 || d->requestedProtocol == IPv6 ? QAbstractSocket::IPv6Protocol : QAbstractSocket::IPv4Protocol); d->host = host; d->port = port; #ifdef NETNAMES_DEBUG NNDEBUG << "d->p:" << d->protocol; #endif /* initiate the host lookup */ XMPP::NameRecord::Type querytype = (d->protocol == QAbstractSocket::IPv6Protocol ? XMPP::NameRecord::Aaaa : XMPP::NameRecord::A); XMPP::NameResolver *resolver = new XMPP::NameResolver; connect(resolver, SIGNAL(resultsReady(QList)), this, SLOT(handle_host_ready(QList))); connect(resolver, SIGNAL(error(XMPP::NameResolver::Error)), this, SLOT(handle_host_error(XMPP::NameResolver::Error))); resolver->start(host.toLocal8Bit(), querytype); d->resolverList << resolver; } /* SRV lookup */ void ServiceResolver::start(const QString &service, const QString &transport, const QString &domain, int port) { #ifdef NETNAMES_DEBUG NNDEBUG << "s:" << service << "t:" << transport << "d:" << domain << "p:" << port; #endif QString srv_request("_" + service + "._" + transport + "." + domain + "."); /* clear SRV list */ d->srvList.clear(); d->domain = domain; /* after we tried all SRV hosts, we shall connect directly (if requested) */ if (port < std::numeric_limits::max()) { d->srvList.append(domain.toLocal8Bit(), port); } else { /* The only "valid" port above the valid port range is our specification of an invalid port */ Q_ASSERT(port == std::numeric_limits::max()); } /* initiate the SRV lookup */ XMPP::NameResolver *resolver = new XMPP::NameResolver; connect(resolver, SIGNAL(resultsReady(QList)), this, SLOT(handle_srv_ready(QList))); connect(resolver, SIGNAL(error(XMPP::NameResolver::Error)), this, SLOT(handle_srv_error(XMPP::NameResolver::Error))); resolver->start(srv_request.toLocal8Bit(), XMPP::NameRecord::Srv); d->resolverList << resolver; } /* SRV request resolved, now try to connect to the hosts */ void ServiceResolver::handle_srv_ready(const QList &r) { #ifdef NETNAMES_DEBUG NNDEBUG << "sl:" << r; #endif /* cleanup resolver */ cleanup_resolver(static_cast(sender())); /* lookup srv pointers */ d->srvList << r; emit srvReady(); if (d->requestedProtocol != HappyEyeballs) { try_next_srv(); } } /* failed the srv lookup, but we might have a fallback host in the srvList */ void ServiceResolver::handle_srv_error(XMPP::NameResolver::Error e) { #ifdef NETNAMES_DEBUG NNDEBUG << "e:" << e; #else Q_UNUSED(e) #endif /* cleanup resolver */ cleanup_resolver(static_cast(sender())); /* srvList already contains a failsafe host, try that */ emit srvFailed(); if (d->requestedProtocol != HappyEyeballs) { try_next_srv(); } } /* hosts resolved, now try to connect to them */ void ServiceResolver::handle_host_ready(const QList &r) { #ifdef NETNAMES_DEBUG NNDEBUG << "hl:" << r; #endif /* cleanup resolver */ cleanup_resolver(static_cast(sender())); /* connect to host */ d->hostList << r; try_next_host(); } /* failed to lookup the primary record (A or AAAA, depending on user choice) */ void ServiceResolver::handle_host_error(XMPP::NameResolver::Error e) { #ifdef NETNAMES_DEBUG NNDEBUG << "e:" << e; #endif /* cleanup resolver */ cleanup_resolver(static_cast(sender())); /* try a fallback lookup if requested*/ if (!lookup_host_fallback()) { /* no-fallback should behave the same as a failed fallback */ handle_host_fallback_error(e); } } /* failed to lookup the fallback record (A or AAAA, depending on user choice) */ void ServiceResolver::handle_host_fallback_error(XMPP::NameResolver::Error e) { #ifdef NETNAMES_DEBUG NNDEBUG << "e:" << e; #else Q_UNUSED(e) #endif /* cleanup resolver */ cleanup_resolver(static_cast(sender())); /* lookup next SRV */ try_next_srv(); } /* check whether a fallback is needed in the current situation */ bool ServiceResolver::check_protocol_fallback() { return (d->requestedProtocol == IPv6_IPv4 && d->protocol == QAbstractSocket::IPv6Protocol) || (d->requestedProtocol == IPv4_IPv6 && d->protocol == QAbstractSocket::IPv4Protocol); } /* lookup the fallback host */ bool ServiceResolver::lookup_host_fallback() { #ifdef NETNAMES_DEBUG NNDEBUG; #endif /* if a fallback is desired, otherwise we must fail immediately */ if (!check_protocol_fallback()) { return false; } d->protocol = (d->protocol == QAbstractSocket::IPv6Protocol ? QAbstractSocket::IPv4Protocol : QAbstractSocket::IPv6Protocol); #ifdef NETNAMES_DEBUG NNDEBUG << "d->p:" << d->protocol; #endif /* initiate the fallback host lookup */ XMPP::NameRecord::Type querytype = (d->protocol == QAbstractSocket::IPv6Protocol ? XMPP::NameRecord::Aaaa : XMPP::NameRecord::A); XMPP::NameResolver *resolver = new XMPP::NameResolver; connect(resolver, SIGNAL(resultsReady(QList)), this, SLOT(handle_host_ready(QList))); connect(resolver, SIGNAL(error(XMPP::NameResolver::Error)), this, SLOT(handle_host_fallback_error(XMPP::NameResolver::Error))); resolver->start(d->host.toLocal8Bit(), querytype); d->resolverList << resolver; return true; } /* notify user about next host */ bool ServiceResolver::try_next_host() { #ifdef NETNAMES_DEBUG NNDEBUG << "hl:" << d->hostList; #endif /* if there is a host left for current protocol (AAAA or A) */ if (!d->hostList.empty()) { XMPP::NameRecord record(d->hostList.takeFirst()); /* emit found address and the port specified earlier */ emit resultReady(record.address(), d->port); return true; } /* otherwise try the fallback protocol */ return lookup_host_fallback(); } /* lookup the next SRV record in line */ void ServiceResolver::try_next_srv() { #ifdef NETNAMES_DEBUG NNDEBUG << "sl:" << d->srvList; #endif /* if there are still hosts we did not try */ if (!d->srvList.isEmpty()) { XMPP::NameRecord record(d->srvList.takeNext()); /* lookup host by name and specify port for later use */ start(record.name(), record.port()); } else { #ifdef NETNAMES_DEBUG NNDEBUG << "SRV list empty, failing"; #endif /* no more SRV hosts to try, fail */ emit error(NoHostLeft); } } void ServiceResolver::tryNext() { /* if the host list cannot help, try the SRV list */ if (!try_next_host()) { try_next_srv(); } } void ServiceResolver::stop() { clear_resolvers(); } bool ServiceResolver::hasPendingSrv() const { return !d->srvList.isEmpty(); } ServiceResolver::ProtoSplit ServiceResolver::happySplit() { Q_ASSERT(d->requestedProtocol == HappyEyeballs); ProtoSplit s; s.ipv4 = new ServiceResolver(this); s.ipv4->setProtocol(IPv4); s.ipv4->d->srvList = d->srvList; s.ipv4->d->hostList = d->hostList; s.ipv4->d->domain = d->domain; s.ipv6 = new ServiceResolver(this); s.ipv6->setProtocol(IPv6); s.ipv6->d->srvList = d->srvList; s.ipv6->d->hostList = d->hostList; s.ipv6->d->domain = d->domain; return s; } //---------------------------------------------------------------------------- // ServiceLocalPublisher //---------------------------------------------------------------------------- ServiceLocalPublisher::ServiceLocalPublisher(QObject *parent) : QObject(parent) { d = new Private(this); } ServiceLocalPublisher::~ServiceLocalPublisher() { delete d; } void ServiceLocalPublisher::publish(const QString &instance, const QString &type, int port, const QMap &attributes) { NameManager::instance()->publish_start(d, instance, type, port, attributes); } void ServiceLocalPublisher::updateAttributes(const QMap &attributes) { Q_UNUSED(attributes); } void ServiceLocalPublisher::addRecord(const NameRecord &rec) { NameManager::instance()->publish_extra_start(d, rec); } void ServiceLocalPublisher::cancel() { } //---------------------------------------------------------------------------- // NetNames //---------------------------------------------------------------------------- void NetNames::cleanup() { NameManager::cleanup(); } QString NetNames::diagnosticText() { // TODO return QString(); } QByteArray NetNames::idnaFromString(const QString &in) { // TODO Q_UNUSED(in); return QByteArray(); } QString NetNames::idnaToString(const QByteArray &in) { // TODO Q_UNUSED(in); return QString(); } QByteArray NetNames::escapeDomain(const QByteArray &in) { // TODO Q_UNUSED(in); return QByteArray(); } QByteArray NetNames::unescapeDomain(const QByteArray &in) { // TODO Q_UNUSED(in); return QByteArray(); } } // namespace XMPP #include "netnames.moc" psi-plus-snapshots-1.4.1456/iris/src/irisnet/corelib/netnames.h000066400000000000000000000536161370065651000244020ustar00rootroot00000000000000/* * Copyright (C) 2006-2008 Justin Karneges * Copyright (C) 2009-2010 Dennis Schridde * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef NETNAMES_H #define NETNAMES_H #include "irisnetglobal.h" #include #include #include // it seems visual studio defines it somewhere #ifdef max #undef max #endif namespace XMPP { class NameManager; class IRISNET_EXPORT NetNames { public: // free any shared data, shutdown internal dns sessions if necessary. static void cleanup(); // return current diagnostic text, clear the buffer. static QString diagnosticText(); // convert idn names static QByteArray idnaFromString(const QString &in); static QString idnaToString(const QByteArray &in); // dns escaping static QByteArray escapeDomain(const QByteArray &in); static QByteArray unescapeDomain(const QByteArray &in); private: NetNames(); }; /** \brief Provides a DNS record NameRecord provides a DNS (Domain Name System) record, which is information assicated with a domain name. For most purposes, the information is an IP address. However, DNS records are capable of holding a variety of data types, such as named pointers to other domain names and even arbitrary text strings. The results of a NameResolver operation are a list of NameRecords. The most common type is the address record, "A", which contains an IPv4 address. Here is an example of how to get the IP address out of an address record: \code NameRecord record = ... // obtain a record from somewhere if(record.type() == NameRecord::A) { QHostAddress ip = record.address(); // get the IP ... } \endcode Getting the data out of a NameRecord involves calling the right retrieval functions, depending on the type. Many types share retrieval functions. For example, the "AAAA" type holds an IPv6 address, which is accessed the same way as the "A" type, by calling address(). See the NameRecord::Type enum for further information about which retrieval functions should be called for each type. To create a NameRecord, use setOwner() and setTtl() as necessary, and then call one of the setX functions (where X is the desired type). For example, to set an A or AAAA record, use setAddress() like this: \code // make example.com the owner, with 1 hour TTL NameRecord record("example.com", 3600); record.setAddress(QHostAddress("1.2.3.4")); \endcode Note that in the case of setAddress(), the record type need not be specified. NameRecord will determine the type to use based on the given QHostAddress. \sa NameResolver */ class IRISNET_EXPORT NameRecord { public: /** \brief The type of DNS record The retrieval functions are shown for each type. */ enum Type { A, ///< IPv4 address. Use address(). Aaaa, ///< IPv6 address. Use address(). Mx, ///< Mail server. Use name() and priority(). Srv, ///< Generic server. Use name(), port(), priority(), and weight(). Cname, ///< Canonical name. Use name(). Ptr, ///< Pointer. Use name(). Txt, ///< List of text strings. Use texts(). Hinfo, ///< Host information. Use cpu() and os(). Ns, ///< Name server. Use name(). Null, ///< Null type. Use rawData(). Any ///< "Any record", for use with NameResolver::start() only. A NameRecord object will never be of this type. }; /** \brief Constructs a null record object \sa isNull */ NameRecord(); /** \brief Constructs a partially initialized record object, with the given \a owner and \a ttl For the record to be usable, call an appropriate setX function (where X is the desired type) afterwards. */ NameRecord(const QByteArray &owner, int ttl); /** \brief Constructs a copy of \a from */ NameRecord(const NameRecord &from); /** \brief Destroys the record object */ ~NameRecord(); /** \brief Assigns \a from to this object and returns a reference to this object */ NameRecord &operator=(const NameRecord &from); /** \brief Compares \a other with this object */ bool operator==(const NameRecord &other); /** \brief Returns true if this record object is null, otherwise returns false Be sure not to confuse a null object with the NULL type (NameRecord::Null). Don't ask why DNS has a type called NULL that contains valid data. */ bool isNull() const; // don't confuse with Null type /** \brief Returns the owner of this record The owner is usually not a useful attribute, since it will be the same as the name searched for with NameResolver. For example, if the A record of "example.com" is looked up, then the resulting records will all have "example.com" as the owner. \sa setOwner */ QByteArray owner() const; /** \brief Returns the TTL (time-to-live) of this record This is the number of seconds the record should be considered valid, which is useful information when performing caching. As a special exception, a TTL of 0 when performing a long-lived lookup indicates that a record is no longer available. \sa setTtl */ int ttl() const; /** \brief Returns the type of this record */ Type type() const; /** \brief Returns the IP address For NameRecord::A and NameRecord::Aaaa types. */ QHostAddress address() const; /** \brief Returns the domain name For NameRecord::Mx, NameRecord::Srv, NameRecord::Cname, NameRecord::Ptr, and NameRecord::Ns types. */ QByteArray name() const; /** \brief Returns the priority For NameRecord::Mx and NameRecord::Srv types. */ int priority() const; /** \brief Returns the weight For the NameRecord::Srv type. */ int weight() const; /** \brief Returns the port For the NameRecord::Srv type. */ int port() const; /** \brief Returns the list of text strings For the NameRecord::Txt type. */ QList texts() const; /** \brief Returns the architecture identifier string For the NameRecord::Hinfo type. */ QByteArray cpu() const; /** \brief Returns the operating system identifier string For the NameRecord::Hinfo type. */ QByteArray os() const; /** \brief Returns the raw data For the NameRecord::Null type. */ QByteArray rawData() const; /** \brief Sets the owner of this record to \a name \sa owner */ void setOwner(const QByteArray &name); /** \brief Sets the TTL (time-to-live) of this record to \a ttl seconds \sa ttl */ void setTtl(int seconds); /** \brief Set as A or AAAA record, with data \a a The protocol of \a a determines whether the type will be NameRecord::A or NameRecord::Aaaa. */ void setAddress(const QHostAddress &a); /** \brief Set as MX record, with data \a name and \a priority */ void setMx(const QByteArray &name, int priority); /** \brief Set as SRV record, with data \a name, \a port, \a priority, and \a weight */ void setSrv(const QByteArray &name, int port, int priority, int weight); /** \brief Set as CNAME record, with data \a name */ void setCname(const QByteArray &name); /** \brief Set as PTR record, with data \a name */ void setPtr(const QByteArray &name); /** \brief Set as TXT record, with data \a texts */ void setTxt(const QList &texts); /** \brief Set as HINFO record, with data \a cpu and \a os */ void setHinfo(const QByteArray &cpu, const QByteArray &os); /** \brief Set as NS record, with data \a name */ void setNs(const QByteArray &name); /** \brief Set as NULL record, with data \a rawData */ void setNull(const QByteArray &rawData); private: class Private; QSharedDataPointer d; }; IRISNET_EXPORT QDebug operator<<(QDebug, XMPP::NameRecord::Type); IRISNET_EXPORT QDebug operator<<(QDebug, const XMPP::NameRecord &); class IRISNET_EXPORT ServiceInstance { public: ServiceInstance(); ServiceInstance(const QString &instance, const QString &type, const QString &domain, const QMap &attributes); ServiceInstance(const ServiceInstance &from); ~ServiceInstance(); ServiceInstance &operator=(const ServiceInstance &from); QString instance() const; QString type() const; QString domain() const; QMap attributes() const; QByteArray name() const; // full dns label private: class Private; QSharedDataPointer d; friend class NameManager; }; /** \brief Represents a DNS query/lookup NameResolver performs an asynchronous DNS lookup for a given domain name and record type. Call start() to begin. The resultsReady() signal is emitted on success, otherwise error() is emitted. To cancel a lookup, call stop(). Each NameResolver object can only perform one DNS lookup at a time. If start() is called while a lookup is already in progress, then the existing lookup is stopped before starting the new lookup. Each NameResolver object should be used for just one DNS query and then be deleted. Otherwise ambiguity might arise when receiving multiple answers to future queries. For example, here is how to obtain the IPv4 addresses of a domain name: \code NameResolver *resolver; void do_lookup() { resolver = new NameResolver; connect(resolver, SIGNAL(resultsReady(QList)), SLOT(dns_resultsReady(QList))); connect(resolver, SIGNAL(error(XMPP::NameResolver::Error)), SLOT(dns_error(XMPP::NameResolver::Error))); // look up affinix.com resolver->start("affinix.com"); } void dns_resultsReady(const QList &results) { // print IP addresses foreach(NameRecord i, results) printf("%s\n", qPrintable(i.address().toString())); } void dns_error(XMPP::NameResolver::Error error) { // handle error ... } \endcode Yes, a domain name can have multiple IP addresses. Many applications ignore this fact, and use only one of the answers. A proper network application should try connecting to each IP address until one succeeds. To lookup other types, pass the desired type to start(). For example, suppose you want to look up the MX record of a domain name: \code // look up the MX record for affinix.com resolver->start("affinix.com", NameRecord::Mx); \endcode It is also possible to perform long-lived queries. This is generally useful for DNS Service Discovery. Long-lived queries are continuous, and resultsReady() may be emitted multiple times. Unlike a normal lookup, which stops once the results are returned, a long-lived query will keep going until stop() is called. For example, suppose you want to scan the local network for SSH services. According to the DNS-SD protocol, this is done by querying for the name "_ssh._tcp.local." of type PTR. \code // monitor for SSH services on the local network resolver->start("_ssh._tcp.local.", NameRecord::Ptr, NameResolver::LongLived); \endcode Don't be alarmed by the trailing dot (".") character in this last example. It is not well known, but all valid DNS domain names end with a dot. However, NameResolver, like most DNS programming interfaces, allows the dot to be left out. What this means is that if a trailing dot is missing in the input to start(), NameResolver will internally append one before performing the query. \sa NameRecord */ class IRISNET_EXPORT NameResolver : public QObject { Q_OBJECT public: /** \brief Resolve mode */ enum Mode { Single, ///< A normal DNS query with a single result set. LongLived ///< An endless query, with multiple result sets allowed. }; /** \brief Resolve error */ enum Error { ErrorGeneric, ///< General failure during lookup, no further details. ErrorNoName, ///< Name does not exist. ErrorTimeout, ///< The operation timed out. ErrorNoLocal, ///< The query is to the local network, but no mechanism for Multicast DNS is available. ErrorNoLongLived ///< The query requires long-lived capability, but no mechanism for doing so is available. }; /** \brief Constructs a new resolver object with the given \a parent */ NameResolver(QObject *parent = nullptr); /** \brief Destroys the resolver object The lookup is, of course, stopped. */ ~NameResolver(); /** \brief Starts a lookup A lookup for \a name of \a type is started. For normal queries, \a mode should be NameResolver::Single (this is the default). For long-lived queries, use NameResolver::LongLived. If a lookup is already in progress, it is stopped before starting the new lookup. \sa stop */ void start(const QByteArray &name, NameRecord::Type type = NameRecord::A, Mode mode = Single); /** \brief Stops a lookup Use this function if you want to stop the current lookup, such that the resolver object may be reused again later. If you don't plan to reuse the object, then destroying the object is enough. \sa start */ void stop(); signals: /** \brief Notification of result records This signal is emitted when results of the lookup operation have arrived. The \a results parameter is a list of NameRecords. All records will be of the type queried for with start(), unless the NameRecord::Any type was specified, in which case the records may be of any type When using the NameResolver::Single mode, the lookup is stopped once results are ready. However, with the NameResolver::LongLived mode, the lookup stays active, and in that case this signal may be emitted multiple times. */ void resultsReady(const QList &results); /** \brief Notification of error This signal is emitted if an error has occurred while performing a lookup. The reason for error can be found in \a e. Regardless of the mode used, the lookup is stopped when an error occurs. */ void error(XMPP::NameResolver::Error e); private: class Private; friend class Private; Private *d; friend class NameManager; }; IRISNET_EXPORT QDebug operator<<(QDebug, XMPP::NameResolver::Error); class IRISNET_EXPORT WeightedNameRecordList { friend QDebug operator<<(QDebug, const WeightedNameRecordList &); public: WeightedNameRecordList(); WeightedNameRecordList(const QList &list); WeightedNameRecordList(const WeightedNameRecordList &other); WeightedNameRecordList &operator=(const WeightedNameRecordList &other); ~WeightedNameRecordList(); bool isEmpty() const; //!< Returns true if the list contains no items; otherwise returns false. NameRecord takeNext(); //!< Removes the next host to try from the list and returns it. void clear(); //!< Removes all items from the list. void append(const WeightedNameRecordList &); void append(const QList &); void append(const NameRecord &); void append(const QString &hostname, quint16 port); WeightedNameRecordList &operator<<(const WeightedNameRecordList &); WeightedNameRecordList &operator<<(const QList &); WeightedNameRecordList &operator<<(const NameRecord &); private: typedef QMultiMap WeightedNameRecordPriorityGroup; typedef QMap WNRL; WNRL priorityGroups; WNRL::iterator currentPriorityGroup; }; QDebug operator<<(QDebug, const XMPP::WeightedNameRecordList &); class IRISNET_EXPORT ServiceBrowser : public QObject { Q_OBJECT public: enum Error { ErrorGeneric, ErrorNoLocal, ErrorNoWide }; ServiceBrowser(QObject *parent = nullptr); ~ServiceBrowser(); void start(const QString &type, const QString &domain = "local"); void stop(); signals: void instanceAvailable(const XMPP::ServiceInstance &instance); void instanceUnavailable(const XMPP::ServiceInstance &instance); void error(); private: class Private; friend class Private; Private *d; friend class NameManager; }; /*! DNS resolver with DNS-SD/mDNS and recursive lookup support */ /* Flow: 1) SRV query for server : answer = host[] : failure -> (9) 2) Primary query for host[i] (usually AAAA) : answer = address[] : failure -> (5) 3) Connect to address[j] : connect -> FINISHED : failure -> j++, (3) 4) address[] empty -> (5) 5) Fallback query for host[i] (usually A) : answer = address[] : failure -> i++, (2) 6) Connect to address[j] : connect -> FINISHED : failure -> j++, (6) 7) address[] empty -> i++, (2) 8) host[] empty -> (9) 9) Try servername directly */ class IRISNET_EXPORT ServiceResolver : public QObject { Q_OBJECT public: struct ProtoSplit { ServiceResolver *ipv4; ServiceResolver *ipv6; }; /*! Error codes for (SRV) lookups */ enum Error { ServiceNotFound, //!< There is no service with the specified parameters NoHostLeft, //!< we did all we could, none of the found host seemed to suffice the users needs ErrorGeneric, ErrorTimeout, ErrorNoLocal // Stuff that netnames_jdns.cpp needs ... }; /*! Order of lookup / IP protocols to try */ enum Protocol { IPv6_IPv4, IPv4_IPv6, HappyEyeballs, IPv6, IPv4 }; /*! * Create a new ServiceResolver. * This resolver can be used for multiple lookups in a row, but not concurrently! */ ServiceResolver(QObject *parent = nullptr); ~ServiceResolver(); Protocol protocol() const; //!< IP protocol to use, defaults to IPv6_IPv4 void setProtocol(Protocol); //!< Set IP protocol to use, \sa protocol /*! * Start a DNS-SD lookup * \param name Instance to lookup */ void start(const QByteArray &name); /*! * Start a lookup for host directly * Behaves like a NameResolver with IP protocol fallback * \param host Hostname to lookup * \param port Port to signal via resultReady (for convenience) */ void start(const QString &host, quint16 port); /*! * Start an indirect (SRV) lookup for the service * \param service Service type, like "ssh" or "ftp" * \param transport IP transport, like "tcp" or "udp" * \param domain Domainname to lookup * \param port Specify a valid port number to make ServiceResolver fallback to domain:port */ void start(const QString &service, const QString &transport, const QString &domain, int port = std::numeric_limits::max()); /*! Announce the next resolved host, \sa resultReady */ void tryNext(); /*! Stop the current lookup */ void stop(); /*! Check if we have more unreslved domain:port records */ bool hasPendingSrv() const; /*! * Split resolver to IPv4 and IPv6 to use with HappyEyeballs connector. * The most appropriate to call this method is after srvReady() was emitted. * Returned resolvers are owned by current resolver */ ProtoSplit happySplit(); signals: /*! * The lookup succeeded * \param address Resolved IP address * \param port Port the service resides on */ void resultReady(const QHostAddress &address, quint16 port); /*! The lookup failed */ void error(XMPP::ServiceResolver::Error); /*! SRV domain:port records received. No IP yet. */ void srvReady(); void srvFailed(); private slots: void handle_srv_ready(const QList &); void handle_srv_error(XMPP::NameResolver::Error); void handle_host_ready(const QList &); void handle_host_error(XMPP::NameResolver::Error); void handle_host_fallback_error(XMPP::NameResolver::Error); private: void clear_resolvers(); void cleanup_resolver(XMPP::NameResolver *); bool check_protocol_fallback(); bool lookup_host_fallback(); bool try_next_host(); void try_next_srv(); class Private; friend class Private; Private *d; friend class NameManager; }; class IRISNET_EXPORT ServiceLocalPublisher : public QObject { Q_OBJECT public: enum Error { ErrorGeneric, // generic error ErrorConflict, // name in use ErrorNoLocal // unable to setup multicast dns }; ServiceLocalPublisher(QObject *parent = nullptr); ~ServiceLocalPublisher(); void publish(const QString &instance, const QString &type, int port, const QMap &attributes); void updateAttributes(const QMap &attributes); void addRecord(const NameRecord &rec); void cancel(); signals: void published(); void error(XMPP::ServiceLocalPublisher::Error e); private: class Private; friend class Private; Private *d; friend class NameManager; }; } // namespace XMPP Q_DECLARE_METATYPE(XMPP::NameResolver::Error) #endif // NETNAMES_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/corelib/netnames_jdns.cpp000066400000000000000000002051751370065651000257520ustar00rootroot00000000000000/* * Copyright (C) 2005-2008 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "irisnetplugin.h" #include "netinterface.h" #include "objectsession.h" #include "qjdnsshared.h" //#define JDNS_DEBUG Q_DECLARE_METATYPE(XMPP::NameRecord) // Q_DECLARE_METATYPE(XMPP::NameResolver::Error) Q_DECLARE_METATYPE(XMPP::ServiceBrowser::Error) Q_DECLARE_METATYPE(XMPP::ServiceResolver::Error) Q_DECLARE_METATYPE(XMPP::ServiceLocalPublisher::Error) namespace XMPP { static NameRecord importJDNSRecord(const QJDns::Record &in) { NameRecord out; switch (in.type) { case QJDns::A: out.setAddress(in.address); break; case QJDns::Aaaa: out.setAddress(in.address); break; case QJDns::Mx: out.setMx(in.name, in.priority); break; case QJDns::Srv: out.setSrv(in.name, in.port, in.priority, in.weight); break; case QJDns::Cname: out.setCname(in.name); break; case QJDns::Ptr: out.setPtr(in.name); break; case QJDns::Txt: out.setTxt(in.texts); break; case QJDns::Hinfo: out.setHinfo(in.cpu, in.os); break; case QJDns::Ns: out.setNs(in.name); break; case 10: out.setNull(in.rdata); break; default: return out; } out.setOwner(in.owner); out.setTtl(in.ttl); return out; } static QJDns::Record exportJDNSRecord(const NameRecord &in) { QJDns::Record out; switch (in.type()) { case NameRecord::A: out.type = QJDns::A; out.haveKnown = true; out.address = in.address(); break; case NameRecord::Aaaa: out.type = QJDns::Aaaa; out.haveKnown = true; out.address = in.address(); break; case NameRecord::Mx: out.type = QJDns::Mx; out.haveKnown = true; out.name = in.name(); out.priority = in.priority(); break; case NameRecord::Srv: out.type = QJDns::Srv; out.haveKnown = true; out.name = in.name(); out.port = in.port(); out.priority = in.priority(); out.weight = in.weight(); break; case NameRecord::Cname: out.type = QJDns::Cname; out.haveKnown = true; out.name = in.name(); break; case NameRecord::Ptr: out.type = QJDns::Ptr; out.haveKnown = true; out.name = in.name(); break; case NameRecord::Txt: out.type = QJDns::Txt; out.haveKnown = true; out.texts = in.texts(); break; case NameRecord::Hinfo: out.type = QJDns::Hinfo; out.haveKnown = true; out.cpu = in.cpu(); out.os = in.os(); break; case NameRecord::Ns: out.type = QJDns::Ns; out.haveKnown = true; out.name = in.name(); break; case NameRecord::Null: out.type = 10; out.rdata = in.rawData(); break; default: return out; } out.owner = in.owner(); out.ttl = in.ttl(); return out; } static bool validServiceType(const QByteArray &in) { // can't be empty, or start/end with a dot if (in.isEmpty() || in[0] == '.' || in[in.length() - 1] == '.') return false; // must contain exactly one dot int dotcount = 0; for (int n = 0; n < in.length(); ++n) { if (in[n] == '.') { ++dotcount; // no need to count more than 2 if (dotcount >= 2) break; } } if (dotcount != 1) return false; return true; } static QByteArray escapeDomainPart(const QByteArray &in) { QByteArray out; for (int n = 0; n < in.length(); ++n) { if (in[n] == '\\') out += "\\\\"; else if (in[n] == '.') out += "\\."; else out += in[n]; } return out; } static QByteArray unescapeDomainPart(const QByteArray &in) { QByteArray out; for (int n = 0; n < in.length(); ++n) { if (in[n] == '\\') { if (n + 1 >= in.length()) return QByteArray(); out += in[n + 1]; } else out += in[n]; } return out; } class IdManager { private: QSet set; int at; inline void bump_at() { if (at == 0x7fffffff) at = 0; else ++at; } public: IdManager() : at(0) {} int reserveId() { while (1) { if (!set.contains(at)) { int id = at; set.insert(id); bump_at(); return id; } bump_at(); } } void releaseId(int id) { set.remove(id); } void clear() { set.clear(); at = 0; } }; //---------------------------------------------------------------------------- // JDnsGlobal //---------------------------------------------------------------------------- class JDnsGlobal : public QObject { Q_OBJECT public: QJDnsSharedDebug db; QJDnsShared * uni_net, *uni_local, *mul; QHostAddress mul_addr4, mul_addr6; NetInterfaceManager netman; QList ifaces; QTimer * updateTimer; JDnsGlobal() { uni_net = 0; uni_local = 0; mul = 0; qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); connect(&db, SIGNAL(readyRead()), SLOT(jdns_debugReady())); updateTimer = new QTimer(this); connect(updateTimer, SIGNAL(timeout()), SLOT(doUpdateMulticastInterfaces())); updateTimer->setSingleShot(true); } ~JDnsGlobal() { updateTimer->disconnect(this); updateTimer->setParent(0); updateTimer->deleteLater(); qDeleteAll(ifaces); QList list; if (uni_net) list += uni_net; if (uni_local) list += uni_local; if (mul) list += mul; // calls shutdown on the list, waits for shutdownFinished, deletes QJDnsShared::waitForShutdown(list); // get final debug jdns_debugReady(); } QJDnsShared *ensure_uni_net() { if (!uni_net) { uni_net = new QJDnsShared(QJDnsShared::UnicastInternet, this); uni_net->setDebug(&db, "U"); bool ok4 = uni_net->addInterface(QHostAddress::Any); bool ok6 = uni_net->addInterface(QHostAddress::AnyIPv6); if (!ok4 && !ok6) { delete uni_net; uni_net = 0; } } return uni_net; } QJDnsShared *ensure_uni_local() { if (!uni_local) { uni_local = new QJDnsShared(QJDnsShared::UnicastLocal, this); uni_local->setDebug(&db, "L"); bool ok4 = uni_local->addInterface(QHostAddress::Any); bool ok6 = uni_local->addInterface(QHostAddress::AnyIPv6); if (!ok4 && !ok6) { delete uni_local; uni_local = 0; } } return uni_local; } QJDnsShared *ensure_mul() { if (!mul) { mul = new QJDnsShared(QJDnsShared::Multicast, this); mul->setDebug(&db, "M"); connect(&netman, SIGNAL(interfaceAvailable(QString)), SLOT(iface_available(QString))); // get the current network interfaces. this initial // fetching should not trigger any calls to // updateMulticastInterfaces(). only future // activity should do that. for (const QString &id: netman.interfaces()) { NetInterface *iface = new NetInterface(id, &netman); connect(iface, SIGNAL(unavailable()), SLOT(iface_unavailable())); ifaces += iface; } updateMulticastInterfaces(false); } return mul; } bool haveMulticast4() const { return !mul_addr4.isNull(); } bool haveMulticast6() const { return !mul_addr6.isNull(); } signals: void interfacesChanged(); private slots: void jdns_debugReady() { QStringList lines = db.readDebugLines(); #ifdef JDNS_DEBUG for (int n = 0; n < lines.count(); ++n) qDebug("jdns: %s\n", qPrintable(lines[n])); #else Q_UNUSED(lines); #endif } void iface_available(const QString &id) { NetInterface *iface = new NetInterface(id, &netman); connect(iface, SIGNAL(unavailable()), SLOT(iface_unavailable())); ifaces += iface; updateTimer->start(100); } void iface_unavailable() { NetInterface *iface = static_cast(sender()); ifaces.removeAll(iface); delete iface; updateTimer->start(100); } void doUpdateMulticastInterfaces() { updateMulticastInterfaces(true); } private: void updateMulticastInterfaces(bool useSignals) { QHostAddress addr4 = QJDns::detectPrimaryMulticast(QHostAddress::Any); QHostAddress addr6 = QJDns::detectPrimaryMulticast(QHostAddress::AnyIPv6); bool had4 = !mul_addr4.isNull(); bool had6 = !mul_addr6.isNull(); updateMulticastInterface(&mul_addr4, addr4); updateMulticastInterface(&mul_addr6, addr6); bool have4 = !mul_addr4.isNull(); bool have6 = !mul_addr6.isNull(); // did we gain/lose something? if (had4 != have4 || had6 != have6) { if (useSignals) emit interfacesChanged(); } } void updateMulticastInterface(QHostAddress *curaddr, const QHostAddress &newaddr) { if (!(newaddr == *curaddr)) // QHostAddress doesn't have operator!= { if (!curaddr->isNull()) mul->removeInterface(*curaddr); *curaddr = newaddr; if (!curaddr->isNull()) { if (!mul->addInterface(*curaddr)) *curaddr = QHostAddress(); } } } }; //---------------------------------------------------------------------------- // JDnsNameProvider //---------------------------------------------------------------------------- class JDnsNameProvider : public NameProvider { Q_OBJECT Q_INTERFACES(XMPP::NameProvider) public: enum Mode { Internet, Local }; JDnsGlobal * global; Mode mode; IdManager idman; ObjectSession sess; class Item { public: int id; QJDnsSharedRequest *req; int type; bool longLived; ObjectSession sess; bool useLocal; bool localResult; NameResolver::Error error; NameResolver::Error localError; Item(QObject *parent = 0) : id(-1), req(0), sess(parent), useLocal(false), localResult(false) {} ~Item() { delete req; } }; QList items; static JDnsNameProvider *create(JDnsGlobal *global, Mode mode, QObject *parent = 0) { if (mode == Internet) { if (!global->ensure_uni_net()) return 0; } else { if (!global->ensure_uni_local()) return 0; } return new JDnsNameProvider(global, mode, parent); } JDnsNameProvider(JDnsGlobal *_global, Mode _mode, QObject *parent = 0) : NameProvider(parent) { global = _global; mode = _mode; } ~JDnsNameProvider() { qDeleteAll(items); } Item *getItemById(int id) { for (int n = 0; n < items.count(); ++n) { if (items[n]->id == id) return items[n]; } return 0; } Item *getItemByReq(QJDnsSharedRequest *req) { for (int n = 0; n < items.count(); ++n) { if (items[n]->req == req) return items[n]; } return 0; } void releaseItem(Item *i) { idman.releaseId(i->id); items.removeAll(i); delete i; } void tryError(Item *i) { // if we are doing dual resolves, make sure both are done if (!i->longLived && (i->req || (i->useLocal && !i->localResult))) return; int id = i->id; NameResolver::Error error = i->error; releaseItem(i); emit resolve_error(id, error); } virtual bool supportsSingle() const { return true; } virtual bool supportsLongLived() const { if (mode == Local) return true; // we support long-lived local queries else return false; // we do NOT support long-lived internet queries } virtual bool supportsRecordType(int type) const { // all record types supported Q_UNUSED(type); return true; } virtual int resolve_start(const QByteArray &name, int qType, bool longLived) { if (mode == Internet) { bool isLocalName = false; if (name.right(6) == ".local" || name.right(7) == ".local.") isLocalName = true; // if query ends in .local, switch to local resolver /*if(isLocalName) { Item *i = new Item(this); i->id = idman.reserveId(); i->longLived = longLived; items += i; i->sess.defer(this, "do_local", Q_ARG(int, i->id), Q_ARG(QByteArray, name)); return i->id; }*/ // we don't support long-lived internet queries if (longLived) { // but we do support long-lived local queries if (isLocalName) { Item *i = new Item(this); i->id = idman.reserveId(); i->longLived = longLived; i->useLocal = true; items += i; i->sess.defer(this, "do_local", Q_ARG(int, i->id), Q_ARG(QByteArray, name)); return i->id; } Item *i = new Item(this); i->id = idman.reserveId(); items += i; i->sess.defer(this, "do_error", Q_ARG(int, i->id), Q_ARG(XMPP::NameResolver::Error, NameResolver::ErrorNoLongLived)); return i->id; } // perform the query Item *i = new Item(this); i->id = idman.reserveId(); i->req = new QJDnsSharedRequest(global->uni_net); connect(i->req, SIGNAL(resultsReady()), SLOT(req_resultsReady())); i->type = qType; i->longLived = false; if (isLocalName) i->useLocal = true; items += i; i->req->query(name, qType); // if query ends in .local, simultaneously do local resolve if (isLocalName) i->sess.defer(this, "do_local", Q_ARG(int, i->id), Q_ARG(QByteArray, name)); return i->id; } else { Item *i = new Item(this); i->id = idman.reserveId(); i->type = qType; if (longLived) { if (!global->ensure_mul()) { items += i; i->sess.defer(this, "do_error", Q_ARG(int, i->id), Q_ARG(XMPP::NameResolver::Error, NameResolver::ErrorNoLocal)); return i->id; } i->req = new QJDnsSharedRequest(global->mul); i->longLived = true; } else { i->req = new QJDnsSharedRequest(global->uni_local); i->longLived = false; } connect(i->req, SIGNAL(resultsReady()), SLOT(req_resultsReady())); items += i; i->req->query(name, qType); return i->id; } } virtual void resolve_stop(int id) { Item *i = getItemById(id); Q_ASSERT(i); if (i->req) i->req->cancel(); releaseItem(i); } virtual void resolve_localResultsReady(int id, const QList &results) { Item *i = getItemById(id); Q_ASSERT(i); Q_ASSERT(!i->localResult); i->localResult = true; i->sess.defer(this, "do_local_ready", Q_ARG(int, id), Q_ARG(QList, results)); } virtual void resolve_localError(int id, XMPP::NameResolver::Error e) { Item *i = getItemById(id); Q_ASSERT(i); Q_ASSERT(!i->localResult); i->localResult = true; i->sess.defer(this, "do_local_error", Q_ARG(int, id), Q_ARG(XMPP::NameResolver::Error, e)); } private slots: void req_resultsReady() { QJDnsSharedRequest *req = static_cast(sender()); Item * i = getItemByReq(req); Q_ASSERT(i); int id = i->id; NameResolver::Error error; if (req->success()) { QList out; for (const QJDns::Record &r: req->results()) { // unless we are asking for all types, only // accept the type we asked for if (i->type == QJDns::Any || r.type == i->type) { NameRecord rec = importJDNSRecord(r); if (!rec.isNull()) out += rec; } } // don't report anything if long-lived gives no results if (i->longLived && out.isEmpty()) return; // only emit success if we have at least 1 result if (!out.isEmpty()) { // FIXME: need a way to cancel related local // query if still active if (!i->longLived) releaseItem(i); emit resolve_resultsReady(id, out); return; } else { error = NameResolver::ErrorGeneric; } } else { QJDnsSharedRequest::Error e = req->error(); error = NameResolver::ErrorGeneric; if (e == QJDnsSharedRequest::ErrorNXDomain) error = NameResolver::ErrorNoName; else if (e == QJDnsSharedRequest::ErrorTimeout) error = NameResolver::ErrorTimeout; else // ErrorGeneric or ErrorNoNet error = NameResolver::ErrorGeneric; } delete i->req; i->req = 0; i->error = error; tryError(i); } void do_error(int id, XMPP::NameResolver::Error e) { Item *i = getItemById(id); Q_ASSERT(i); // note: for an internet resolve, this slot is not called in // any case where a local subquery is invoked as well (yet?) releaseItem(i); emit resolve_error(id, e); } void do_local(int id, const QByteArray &name) { // Item *i = getItemById(id); // Q_ASSERT(i); /*// resolve_useLocal has two behaviors: // - if longlived, then it indicates a hand-off // - if non-longlived, then it indicates we want a subquery if(i->longLived) releaseItem(i);*/ emit resolve_useLocal(id, name); } void do_local_ready(int id, const QList &results) { Item *i = getItemById(id); Q_ASSERT(i); if (!i->longLived) { // stop any simultaneous internet resolve if (i->req) i->req->cancel(); // for non-longlived, we're done releaseItem(i); } emit resolve_resultsReady(id, results); } void do_local_error(int id, XMPP::NameResolver::Error e) { Item *i = getItemById(id); Q_ASSERT(i); i->localError = e; tryError(i); } }; //---------------------------------------------------------------------------- // JDnsBrowse //---------------------------------------------------------------------------- class JDnsBrowse : public QObject { Q_OBJECT public: QByteArray type, typeAndDomain; QJDnsSharedRequest req; JDnsBrowse(QJDnsShared *_jdns, QObject *parent = 0) : QObject(parent), req(_jdns, this) { connect(&req, SIGNAL(resultsReady()), SLOT(jdns_resultsReady())); } void start(const QByteArray &_type) { type = _type; Q_ASSERT(validServiceType(type)); typeAndDomain = type + ".local."; req.query(typeAndDomain, QJDns::Ptr); } signals: void available(const QByteArray &instance); void unavailable(const QByteArray &instance); private: QByteArray parseInstanceName(const QByteArray &name) { // needs to be at least X + '.' + typeAndDomain if (name.length() < typeAndDomain.length() + 2) return QByteArray(); // index of the '.' character int at = name.length() - typeAndDomain.length() - 1; if (name[at] != '.') return QByteArray(); if (name.mid(at + 1) != typeAndDomain) return QByteArray(); QByteArray friendlyName = unescapeDomainPart(name.mid(0, at)); if (friendlyName.isEmpty()) return QByteArray(); return friendlyName; } private slots: void jdns_resultsReady() { // ignore errors if (!req.success()) return; QJDns::Record rec = req.results().first(); Q_ASSERT(rec.type == QJDns::Ptr); QByteArray name = rec.name; QByteArray instance = parseInstanceName(name); if (instance.isEmpty()) return; if (rec.ttl == 0) { emit unavailable(instance); return; } emit available(instance); } }; //---------------------------------------------------------------------------- // JDnsServiceResolve //---------------------------------------------------------------------------- // 5 second timeout waiting for both A and AAAA // 8 second timeout waiting for at least one record class JDnsServiceResolve : public QObject { Q_OBJECT public: enum SrvState { Srv = 0, AddressWait = 1, AddressFirstCome = 2 }; QJDnsSharedRequest reqtxt; // for TXT QJDnsSharedRequest req; // for SRV/A QJDnsSharedRequest req6; // for AAAA bool haveTxt; SrvState srvState; QTimer * opTimer; // out QList attribs; QByteArray host; int port; bool have4, have6; QHostAddress addr4, addr6; JDnsServiceResolve(QJDnsShared *_jdns, QObject *parent = 0) : QObject(parent), reqtxt(_jdns, this), req(_jdns, this), req6(_jdns, this) { connect(&reqtxt, SIGNAL(resultsReady()), SLOT(reqtxt_ready())); connect(&req, SIGNAL(resultsReady()), SLOT(req_ready())); connect(&req6, SIGNAL(resultsReady()), SLOT(req6_ready())); opTimer = new QTimer(this); connect(opTimer, SIGNAL(timeout()), SLOT(op_timeout())); opTimer->setSingleShot(true); } ~JDnsServiceResolve() { opTimer->disconnect(this); opTimer->setParent(0); opTimer->deleteLater(); } void start(const QByteArray name) { haveTxt = false; srvState = Srv; have4 = false; have6 = false; opTimer->start(8000); reqtxt.query(name, QJDns::Txt); req.query(name, QJDns::Srv); } signals: void finished(); void error(QJDnsSharedRequest::Error e); private: void cleanup() { if (opTimer->isActive()) opTimer->stop(); if (!haveTxt) reqtxt.cancel(); if (srvState == Srv || !have4) req.cancel(); if (srvState >= AddressWait && !have6) req6.cancel(); } bool tryDone() { // we're done when we have txt and addresses if (haveTxt && ((have4 && have6) || (srvState == AddressFirstCome && (have4 || have6)))) { cleanup(); emit finished(); return true; } return false; } private slots: void reqtxt_ready() { if (!reqtxt.success()) { cleanup(); emit error(reqtxt.error()); return; } QJDns::Record rec = reqtxt.results().first(); reqtxt.cancel(); Q_ASSERT(rec.type == QJDns::Txt); attribs.clear(); if (!rec.texts.isEmpty()) { // if there is only 1 text, it needs to be // non-empty for us to care if (rec.texts.count() != 1 || !rec.texts[0].isEmpty()) attribs = rec.texts; } haveTxt = true; tryDone(); } void req_ready() { if (!req.success()) { cleanup(); emit error(req.error()); return; } QJDns::Record rec = req.results().first(); req.cancel(); if (srvState == Srv) { // in Srv state, req is used for SRV records Q_ASSERT(rec.type == QJDns::Srv); host = rec.name; port = rec.port; srvState = AddressWait; opTimer->start(5000); req.query(host, QJDns::A); req6.query(host, QJDns::Aaaa); } else { // in the other states, req is used for A records Q_ASSERT(rec.type == QJDns::A); addr4 = rec.address; have4 = true; tryDone(); } } void req6_ready() { if (!req6.success()) { cleanup(); emit error(req6.error()); return; } QJDns::Record rec = req6.results().first(); req6.cancel(); Q_ASSERT(rec.type == QJDns::Aaaa); addr6 = rec.address; have6 = true; tryDone(); } void op_timeout() { if (srvState == Srv) { // timeout getting SRV. it is possible that we could // have obtained the TXT record, but if SRV times // out then we consider the whole job to have // failed. cleanup(); emit error(QJDnsSharedRequest::ErrorTimeout); } else if (srvState == AddressWait) { // timeout while waiting for both A and AAAA. we now // switch to the AddressFirstCome state, where an // answer for either will do srvState = AddressFirstCome; // if we have at least one of these, we're done if (have4 || have6) { // well, almost. we might still be waiting // for the TXT record if (tryDone()) return; } // if we are here, then it means we are missing TXT // still, or we have neither A nor AAAA. // wait 3 more seconds opTimer->start(3000); } else // AddressFirstCome { // last chance! if (!tryDone()) { cleanup(); emit error(QJDnsSharedRequest::ErrorTimeout); } } } }; //---------------------------------------------------------------------------- // JDnsPublishAddresses //---------------------------------------------------------------------------- // helper class for JDnsPublishAddresses. publishes A+PTR or AAAA+PTR pair. class JDnsPublishAddress : public QObject { Q_OBJECT public: enum Type { IPv4, IPv6 }; Type type; QByteArray host; QJDnsSharedRequest pub_addr; QJDnsSharedRequest pub_ptr; bool success_; JDnsPublishAddress(QJDnsShared *_jdns, QObject *parent = 0) : QObject(parent), pub_addr(_jdns, this), pub_ptr(_jdns, this) { connect(&pub_addr, SIGNAL(resultsReady()), SLOT(pub_addr_ready())); connect(&pub_ptr, SIGNAL(resultsReady()), SLOT(pub_ptr_ready())); } void start(Type _type, const QByteArray &_host) { type = _type; host = _host; success_ = false; QJDns::Record rec; if (type == IPv6) rec.type = QJDns::Aaaa; else rec.type = QJDns::A; rec.owner = host; rec.ttl = 120; rec.haveKnown = true; rec.address = QHostAddress(); // null address, will be filled in pub_addr.publish(QJDns::Unique, rec); } void cancel() { pub_addr.cancel(); pub_ptr.cancel(); } bool success() const { return success_; } signals: void resultsReady(); private slots: void pub_addr_ready() { if (pub_addr.success()) { QJDns::Record rec; rec.type = QJDns::Ptr; if (type == IPv6) rec.owner = ".ip6.arpa."; else rec.owner = ".in-addr.arpa."; rec.ttl = 120; rec.haveKnown = true; rec.name = host; pub_ptr.publish(QJDns::Shared, rec); } else { pub_ptr.cancel(); // needed if addr fails during or after ptr success_ = false; emit resultsReady(); } } void pub_ptr_ready() { if (pub_ptr.success()) { success_ = true; } else { pub_addr.cancel(); success_ = false; } emit resultsReady(); } }; // This class publishes A/AAAA records for the machine, using a derived // hostname (it will use QHostInfo::localHostName(), but append a unique // suffix if necessary). If there is ever a record conflict, it will // republish under a unique name. // // The hostName() signal is emitted when a hostname is successfully // published as. When there is a conflict, hostName() is emitted with // an empty value, and will again be emitted with a non-empty value // once the conflict is resolved. A missing hostname is considered a // temporary problem, and so other publish operations that depend on a // hostname (SRV, etc) should block until a hostname is available. class JDnsPublishAddresses : public QObject { Q_OBJECT public: bool started; bool use6, use4; JDnsPublishAddress pub6; JDnsPublishAddress pub4; int counter; QByteArray host; bool success; bool have6, have4; ObjectSession sess; JDnsPublishAddresses(QJDnsShared *_jdns, QObject *parent = 0) : QObject(parent), started(false), use6(false), use4(false), pub6(_jdns, this), pub4(_jdns, this), sess(this) { connect(&pub6, SIGNAL(resultsReady()), SLOT(pub6_ready())); connect(&pub4, SIGNAL(resultsReady()), SLOT(pub4_ready())); } void start() { counter = 1; success = false; have6 = false; have4 = false; started = true; tryPublish(); } bool isStarted() const { return started; } // comments in this method apply to setUseIPv4 as well. void setUseIPv6(bool b) { if (b == use6) return; use6 = b; if (!started) return; // a "deferred call to doDisable" and "publish operations" // are mutually exclusive. thus, a deferred call is only // invoked when both publishes are canceled, and the // deferred call is canceled if any of the publishes are // reinstantiated. if (use6) { if (use4) { // if the other is already active, then // just activate this one without // recomputing the hostname tryPublish6(); } else { sess.reset(); // otherwise, recompute the hostname tryPublish(); } } else { pub6.cancel(); have6 = false; if (!use4) sess.defer(this, "doDisable"); } } void setUseIPv4(bool b) { if (b == use4) return; use4 = b; if (!started) return; if (use4) { if (use6) { tryPublish4(); } else { sess.reset(); tryPublish(); } } else { pub4.cancel(); have4 = false; if (!use6) sess.defer(this, "doDisable"); } } signals: void hostName(const QByteArray &str); private: void tryPublish() { QString me = QHostInfo::localHostName(); // some hosts may already have ".local" in their name if (me.endsWith(".local")) me.truncate(me.length() - 6); // prefix our hostname so we don't conflict with a system // mdns daemon me.prepend("jdns-"); if (counter > 1) me += QString("-%1").arg(counter); host = escapeDomainPart(me.toUtf8()) + ".local."; if (use6) tryPublish6(); if (use4) tryPublish4(); } void tryPublish6() { pub6.start(JDnsPublishAddress::IPv6, host); } void tryPublish4() { pub4.start(JDnsPublishAddress::IPv4, host); } void tryDone() { bool done = true; if (use6 && !have6) done = false; if (use4 && !have4) done = false; if (done) { success = true; emit hostName(host); } } void handleFail() { // we get here if we fail to publish at all, or if we // successfully publish but then fail later on. in the // latter case it means we "lost" our host records. bool lostHost = success; // as in earlier publish success success = false; // if we lost a hostname with a suffix, or counter is // at 99, then start counter over at 1 (no suffix). if ((lostHost && counter > 1) || counter >= 99) counter = 1; else ++counter; tryPublish(); // only emit lost host signal once if (lostHost) emit hostName(QByteArray()); } private slots: void doDisable() { bool lostHost = success; success = false; if (lostHost) emit hostName(QByteArray()); } void pub6_ready() { if (pub6.success()) { have6 = true; tryDone(); } else { have6 = false; have4 = false; pub4.cancel(); handleFail(); } } void pub4_ready() { if (pub4.success()) { have4 = true; tryDone(); } else { have4 = false; have6 = false; pub6.cancel(); handleFail(); } } }; //---------------------------------------------------------------------------- // JDnsPublish //---------------------------------------------------------------------------- class JDnsPublish; class JDnsPublishExtra : public QObject { Q_OBJECT public: JDnsPublishExtra(JDnsPublish *_jdnsPub); ~JDnsPublishExtra(); void start(const QJDns::Record &_rec); void update(const QJDns::Record &_rec); signals: void published(); void error(QJDnsSharedRequest::Error e); private: friend class JDnsPublish; JDnsPublish * jdnsPub; bool started; QJDnsSharedRequest pub; QJDns::Record rec; bool have; bool need_update; }; // This class publishes SRV/TXT/PTR for a service. if a hostName is not // is not available (see JDnsPublishAddresses) then the publish action // will be deferred until one is available. SRV and TXT are published // as unique records, and once they both succeed then the PTR record // is published. once the PTR succeeds, then published() is emitted. // if a conflict occurs with any action, then the whole thing fails and // error() is emitted. if, at any time, the hostName is lost, then // then the SRV operation is canceled, but no error is emitted. when the // hostName is regained, then the SRV record is republished. // // It's important to note that published() is only emitted once ever, even // if a hostName change causes a republishing. this way, hostName changes // are completely transparent. class JDnsPublish : public QObject { Q_OBJECT public: QJDnsShared * jdns; QJDnsSharedRequest pub_srv; QJDnsSharedRequest pub_txt; QJDnsSharedRequest pub_ptr; bool have_srv, have_txt, have_ptr; bool need_update_txt; QByteArray fullname; QByteArray instance; QByteArray type; QByteArray host; int port; QList attribs; QSet extraList; JDnsPublish(QJDnsShared *_jdns, QObject *parent = 0) : QObject(parent), jdns(_jdns), pub_srv(_jdns, this), pub_txt(_jdns, this), pub_ptr(_jdns, this) { connect(&pub_srv, SIGNAL(resultsReady()), SLOT(pub_srv_ready())); connect(&pub_txt, SIGNAL(resultsReady()), SLOT(pub_txt_ready())); connect(&pub_ptr, SIGNAL(resultsReady()), SLOT(pub_ptr_ready())); } ~JDnsPublish() { qDeleteAll(extraList); } void start(const QString &_instance, const QByteArray &_type, const QByteArray &localHost, int _port, const QMap &attributes) { type = _type; Q_ASSERT(validServiceType(type)); instance = escapeDomainPart(_instance.toUtf8()); fullname = instance + '.' + type + ".local."; host = localHost; port = _port; attribs = makeTxtList(attributes); have_srv = false; have_txt = false; have_ptr = false; need_update_txt = false; // no host? defer publishing till we have one if (host.isEmpty()) return; doPublish(); } void update(const QMap &attributes) { attribs = makeTxtList(attributes); // still publishing the initial txt? if (!have_txt) { // flag that we want to update once the publish // succeeds. need_update_txt = true; return; } // no SRV, but have TXT? this means we lost SRV due to // a hostname change. if (!have_srv) { // in that case, revoke the TXT. it'll get // republished after SRV then. have_txt = false; pub_txt.cancel(); return; } doPublishTxt(); } public slots: // pass empty host if host lost void hostChanged(const QByteArray &_host) { bool changed = (host != _host); if (changed) { host = _host; if (host.isEmpty()) { // cancel srv record momentarily have_srv = false; pub_srv.cancel(); } else { // we now have a host, publish doPublish(); } } } signals: void published(); void error(QJDnsSharedRequest::Error e); private: friend class JDnsPublishExtra; static QList makeTxtList(const QMap &attributes) { QList out; QMapIterator it(attributes); while (it.hasNext()) { it.next(); out += it.key().toLatin1() + '=' + it.value(); } if (out.isEmpty()) out += QByteArray(); return out; } void doPublish() { // SRV QJDns::Record rec; rec.type = QJDns::Srv; rec.owner = fullname; rec.ttl = 120; rec.haveKnown = true; rec.name = host; rec.port = port; rec.priority = 0; rec.weight = 0; pub_srv.publish(QJDns::Unique, rec); // if we're just republishing SRV after losing/regaining // our hostname, then TXT is already published if (!have_txt) doPublishTxt(); // publish extra records as needed for (JDnsPublishExtra *extra: extraList) { if (!extra->have) doPublishExtra(extra); } } void doPublishTxt() { // TXT QJDns::Record rec; rec.type = QJDns::Txt; rec.owner = fullname; rec.ttl = 4500; rec.haveKnown = true; rec.texts = attribs; if (!have_txt) pub_txt.publish(QJDns::Unique, rec); else pub_txt.publishUpdate(rec); } void tryDone() { if (have_srv && have_txt) { // PTR QJDns::Record rec; rec.type = QJDns::Ptr; rec.owner = type + ".local."; rec.ttl = 4500; rec.haveKnown = true; rec.name = fullname; pub_ptr.publish(QJDns::Shared, rec); } } void cleanup() { for (JDnsPublishExtra *extra : extraList) cleanupExtra(extra); qDeleteAll(extraList); extraList.clear(); have_srv = false; have_txt = false; have_ptr = false; pub_srv.cancel(); pub_txt.cancel(); pub_ptr.cancel(); } void publishExtra(JDnsPublishExtra *extra) { Q_ASSERT(!extraList.contains(extra)); connect(&extra->pub, SIGNAL(resultsReady()), SLOT(pub_extra_ready())); extraList += extra; // defer publishing until SRV is ready if (!have_srv) return; doPublishExtra(extra); } void publishExtraUpdate(JDnsPublishExtra *extra) { if (!extra->have) { extra->need_update = true; return; } if (!have_srv) { extra->have = false; extra->pub.cancel(); return; } doPublishExtra(extra); } void unpublishExtra(JDnsPublishExtra *extra) { extraList.remove(extra); } void doPublishExtra(JDnsPublishExtra *extra) { if (!extra->have) extra->pub.publish(QJDns::Unique, extra->rec); else extra->pub.publishUpdate(extra->rec); } void cleanupExtra(JDnsPublishExtra *extra) { extra->pub.cancel(); extra->disconnect(this); extra->started = false; extra->have = false; } private slots: void pub_srv_ready() { if (pub_srv.success()) { have_srv = true; tryDone(); } else { QJDnsSharedRequest::Error e = pub_srv.error(); cleanup(); emit error(e); } } void pub_txt_ready() { if (pub_txt.success()) { have_txt = true; if (need_update_txt) { need_update_txt = false; doPublishTxt(); } tryDone(); } else { QJDnsSharedRequest::Error e = pub_txt.error(); cleanup(); emit error(e); } } void pub_ptr_ready() { if (pub_ptr.success()) { have_ptr = true; emit published(); } else { QJDnsSharedRequest::Error e = pub_ptr.error(); cleanup(); emit error(e); } } void pub_extra_ready() { QJDnsSharedRequest *req = static_cast(sender()); JDnsPublishExtra * extra = 0; for (JDnsPublishExtra *e: extraList) { if (&e->pub == req) { extra = e; break; } } Q_ASSERT(extra); if (extra->pub.success()) { extra->have = true; if (extra->need_update) { extra->need_update = false; doPublishExtra(extra); } emit extra->published(); } else { QJDnsSharedRequest::Error e = extra->pub.error(); cleanupExtra(extra); emit extra->error(e); } } }; JDnsPublishExtra::JDnsPublishExtra(JDnsPublish *_jdnsPub) : QObject(_jdnsPub), jdnsPub(_jdnsPub), started(false), pub(_jdnsPub->jdns, this) { } JDnsPublishExtra::~JDnsPublishExtra() { if (started) jdnsPub->unpublishExtra(this); } void JDnsPublishExtra::start(const QJDns::Record &_rec) { rec = _rec; started = true; have = false; need_update = false; jdnsPub->publishExtra(this); } void JDnsPublishExtra::update(const QJDns::Record &_rec) { rec = _rec; jdnsPub->publishExtraUpdate(this); } //---------------------------------------------------------------------------- // JDnsServiceProvider //---------------------------------------------------------------------------- class BrowseItem { public: const int id; JDnsBrowse *const browse; ObjectSession * sess; BrowseItem(int _id, JDnsBrowse *_browse) : id(_id), browse(_browse), sess(0) {} ~BrowseItem() { delete browse; delete sess; } }; class BrowseItemList { private: QSet items; QHash indexById; QHash indexByBrowse; IdManager idman; public: ~BrowseItemList() { qDeleteAll(items); } int reserveId() { return idman.reserveId(); } void insert(BrowseItem *item) { items.insert(item); indexById.insert(item->id, item); indexByBrowse.insert(item->browse, item); } void remove(BrowseItem *item) { indexById.remove(item->id); indexByBrowse.remove(item->browse); items.remove(item); if (item->id != -1) idman.releaseId(item->id); delete item; } BrowseItem *itemById(int id) const { return indexById.value(id); } BrowseItem *itemByBrowse(JDnsBrowse *browse) const { return indexByBrowse.value(browse); } }; class ResolveItem { public: const int id; JDnsServiceResolve *const resolve; ObjectSession * sess; ResolveItem(int _id, JDnsServiceResolve *_resolve) : id(_id), resolve(_resolve), sess(0) {} ~ResolveItem() { delete resolve; delete sess; } }; class ResolveItemList { private: QSet items; QHash indexById; QHash indexByResolve; IdManager idman; public: ~ResolveItemList() { qDeleteAll(items); } int reserveId() { return idman.reserveId(); } void insert(ResolveItem *item) { items.insert(item); indexById.insert(item->id, item); indexByResolve.insert(item->resolve, item); } void remove(ResolveItem *item) { indexById.remove(item->id); indexByResolve.remove(item->resolve); items.remove(item); if (item->id != -1) idman.releaseId(item->id); delete item; } ResolveItem *itemById(int id) const { return indexById.value(id); } ResolveItem *itemByResolve(JDnsServiceResolve *resolve) const { return indexByResolve.value(resolve); } }; class PublishItem { public: const int id; JDnsPublish *const publish; ObjectSession * sess; PublishItem(int _id, JDnsPublish *_publish) : id(_id), publish(_publish), sess(0) {} ~PublishItem() { delete publish; delete sess; } }; class PublishItemList { public: QSet items; private: QHash indexById; QHash indexByPublish; IdManager idman; public: ~PublishItemList() { qDeleteAll(items); } int reserveId() { return idman.reserveId(); } void insert(PublishItem *item) { items.insert(item); indexById.insert(item->id, item); indexByPublish.insert(item->publish, item); } void remove(PublishItem *item) { indexById.remove(item->id); indexByPublish.remove(item->publish); items.remove(item); if (item->id != -1) idman.releaseId(item->id); delete item; } PublishItem *itemById(int id) const { return indexById.value(id); } PublishItem *itemByPublish(JDnsPublish *publish) const { return indexByPublish.value(publish); } }; class PublishExtraItem { public: const int id; JDnsPublishExtra *const publish; ObjectSession * sess; PublishExtraItem(int _id, JDnsPublishExtra *_publish) : id(_id), publish(_publish), sess(0) {} ~PublishExtraItem() { delete publish; delete sess; } }; class PublishExtraItemList { public: QSet items; private: QHash indexById; QHash indexByPublish; IdManager idman; public: ~PublishExtraItemList() { qDeleteAll(items); } void clear() { qDeleteAll(items); items.clear(); indexById.clear(); indexByPublish.clear(); idman.clear(); } int reserveId() { return idman.reserveId(); } void insert(PublishExtraItem *item) { items.insert(item); indexById.insert(item->id, item); indexByPublish.insert(item->publish, item); } void remove(PublishExtraItem *item) { indexById.remove(item->id); indexByPublish.remove(item->publish); items.remove(item); if (item->id != -1) idman.releaseId(item->id); delete item; } PublishExtraItem *itemById(int id) const { return indexById.value(id); } PublishExtraItem *itemByPublish(JDnsPublishExtra *publish) const { return indexByPublish.value(publish); } }; class JDnsServiceProvider : public ServiceProvider { Q_OBJECT public: JDnsGlobal *global; // browse BrowseItemList browseItemList; QHash items; // resolve ResolveItemList resolveItemList; // publish JDnsPublishAddresses *pub_addresses; QByteArray localHost; PublishItemList publishItemList; PublishExtraItemList publishExtraItemList; static JDnsServiceProvider *create(JDnsGlobal *global, QObject *parent = 0) { return new JDnsServiceProvider(global, parent); } JDnsServiceProvider(JDnsGlobal *_global, QObject *parent = 0) : ServiceProvider(parent), pub_addresses(0) { global = _global; connect(global, SIGNAL(interfacesChanged()), SLOT(interfacesChanged())); } ~JDnsServiceProvider() { // make sure extra items are deleted before normal ones publishExtraItemList.clear(); } virtual int browse_start(const QString &_type, const QString &_domain) { QString domain; if (_domain.isEmpty() || _domain == ".") domain = "local."; else domain = _domain; if (domain[domain.length() - 1] != '.') domain += '.'; Q_ASSERT(domain.length() >= 2 && domain[domain.length() - 1] == '.'); int id = browseItemList.reserveId(); // no support for non-local domains if (domain != "local.") { BrowseItem *i = new BrowseItem(id, 0); i->sess = new ObjectSession(this); browseItemList.insert(i); i->sess->defer(this, "do_browse_error", Q_ARG(int, i->id), Q_ARG(XMPP::ServiceBrowser::Error, ServiceBrowser::ErrorNoWide)); return i->id; } if (!global->ensure_mul()) { BrowseItem *i = new BrowseItem(id, 0); i->sess = new ObjectSession(this); browseItemList.insert(i); i->sess->defer(this, "do_browse_error", Q_ARG(int, i->id), Q_ARG(XMPP::ServiceBrowser::Error, ServiceBrowser::ErrorNoLocal)); return i->id; } QByteArray type = _type.toUtf8(); if (!validServiceType(type)) { BrowseItem *i = new BrowseItem(id, 0); i->sess = new ObjectSession(this); browseItemList.insert(i); i->sess->defer(this, "do_browse_error", Q_ARG(int, i->id), Q_ARG(XMPP::ServiceBrowser::Error, ServiceBrowser::ErrorGeneric)); return i->id; } BrowseItem *i = new BrowseItem(id, new JDnsBrowse(global->mul, this)); connect(i->browse, SIGNAL(available(QByteArray)), SLOT(jb_available(QByteArray))); connect(i->browse, SIGNAL(unavailable(QByteArray)), SLOT(jb_unavailable(QByteArray))); browseItemList.insert(i); i->browse->start(type); return i->id; } virtual void browse_stop(int id) { BrowseItem *i = browseItemList.itemById(id); Q_ASSERT(i); browseItemList.remove(i); } virtual int resolve_start(const QByteArray &name) { int id = resolveItemList.reserveId(); if (!global->ensure_mul()) { ResolveItem *i = new ResolveItem(id, 0); i->sess = new ObjectSession(this); resolveItemList.insert(i); i->sess->defer(this, "do_resolve_error", Q_ARG(int, i->id), Q_ARG(XMPP::ServiceResolver::Error, ServiceResolver::ErrorNoLocal)); return i->id; } ResolveItem *i = new ResolveItem(id, new JDnsServiceResolve(global->mul, this)); connect(i->resolve, SIGNAL(finished()), SLOT(jr_finished())); connect(i->resolve, SIGNAL(error(QJDnsSharedRequest::Error)), SLOT(jr_error(QJDnsSharedRequest::Error))); resolveItemList.insert(i); i->resolve->start(name); return i->id; } virtual void resolve_stop(int id) { ResolveItem *i = resolveItemList.itemById(id); Q_ASSERT(i); resolveItemList.remove(i); } virtual int publish_start(const QString &instance, const QString &_type, int port, const QMap &attributes) { int id = publishItemList.reserveId(); if (!global->ensure_mul()) { PublishItem *i = new PublishItem(id, 0); i->sess = new ObjectSession(this); publishItemList.insert(i); i->sess->defer(this, "do_publish_error", Q_ARG(int, i->id), Q_ARG(XMPP::ServiceLocalPublisher::Error, ServiceLocalPublisher::ErrorNoLocal)); return i->id; } QByteArray type = _type.toUtf8(); if (!validServiceType(type)) { PublishItem *i = new PublishItem(id, 0); i->sess = new ObjectSession(this); publishItemList.insert(i); i->sess->defer(this, "do_publish_error", Q_ARG(int, i->id), Q_ARG(XMPP::ServiceLocalPublisher::Error, ServiceLocalPublisher::ErrorGeneric)); return i->id; } // make sure A/AAAA records are published if (!pub_addresses) { pub_addresses = new JDnsPublishAddresses(global->mul, this); connect(pub_addresses, SIGNAL(hostName(QByteArray)), SLOT(pub_addresses_hostName(QByteArray))); pub_addresses->setUseIPv6(global->haveMulticast6()); pub_addresses->setUseIPv4(global->haveMulticast4()); pub_addresses->start(); } // it's okay to attempt to publish even if pub_addresses // hasn't succeeded yet. JDnsPublish is smart enough to // defer the operation until a host is acquired. PublishItem *i = new PublishItem(id, new JDnsPublish(global->mul, this)); connect(i->publish, SIGNAL(published()), SLOT(jp_published())); connect(i->publish, SIGNAL(error(QJDnsSharedRequest::Error)), SLOT(jp_error(QJDnsSharedRequest::Error))); publishItemList.insert(i); i->publish->start(instance, type, localHost, port, attributes); return i->id; } virtual void publish_update(int id, const QMap &attributes) { PublishItem *i = publishItemList.itemById(id); Q_ASSERT(i); // if we already have an error queued, do nothing if (i->sess->isDeferred(this, "do_publish_error")) return; i->publish->update(attributes); } virtual void publish_stop(int id) { PublishItem *i = publishItemList.itemById(id); Q_ASSERT(i); cleanupExtra(i); publishItemList.remove(i); } virtual int publish_extra_start(int pub_id, const NameRecord &name) { PublishItem *pi = publishItemList.itemById(pub_id); Q_ASSERT(pi); int id = publishItemList.reserveId(); QJDns::Record rec = exportJDNSRecord(name); if (rec.type == -1) { PublishExtraItem *i = new PublishExtraItem(id, 0); i->sess = new ObjectSession(this); publishExtraItemList.insert(i); i->sess->defer(this, "do_publish_extra_error", Q_ARG(int, i->id), Q_ARG(XMPP::ServiceLocalPublisher::Error, ServiceLocalPublisher::ErrorGeneric)); return i->id; } // fill in owner if necessary if (rec.owner.isEmpty()) rec.owner = pi->publish->fullname; // fill in the ttl if necessary if (rec.ttl == 0) rec.ttl = 4500; PublishExtraItem *i = new PublishExtraItem(id, new JDnsPublishExtra(pi->publish)); connect(i->publish, SIGNAL(published()), SLOT(jpe_published())); connect(i->publish, SIGNAL(error(QJDnsSharedRequest::Error)), SLOT(jpe_error(QJDnsSharedRequest::Error))); publishExtraItemList.insert(i); i->publish->start(rec); return i->id; } virtual void publish_extra_update(int id, const NameRecord &name) { PublishExtraItem *i = publishExtraItemList.itemById(id); Q_ASSERT(i); // if we already have an error queued, do nothing if (i->sess->isDeferred(this, "do_publish_extra_error")) return; QJDns::Record rec = exportJDNSRecord(name); if (rec.type == -1) { i->sess = new ObjectSession(this); i->sess->defer(this, "do_publish_extra_error", Q_ARG(int, i->id), Q_ARG(XMPP::ServiceLocalPublisher::Error, ServiceLocalPublisher::ErrorGeneric)); return; } // fill in owner if necessary if (rec.owner.isEmpty()) rec.owner = static_cast(i->publish->parent())->fullname; // fill in the ttl if necessary if (rec.ttl == 0) rec.ttl = 4500; i->publish->update(rec); } virtual void publish_extra_stop(int id) { PublishExtraItem *i = publishExtraItemList.itemById(id); Q_ASSERT(i); publishExtraItemList.remove(i); } private: void cleanupExtra(PublishItem *pi) { // remove all extra publishes associated with this publish. // the association can be checked via QObject parenting. QSet remove; for (PublishExtraItem *i: publishExtraItemList.items) { if (static_cast(i->publish->parent()) == pi->publish) remove += i; } for (PublishExtraItem *i : remove) publishExtraItemList.remove(i); } private slots: void interfacesChanged() { if (pub_addresses) { pub_addresses->setUseIPv6(global->haveMulticast6()); pub_addresses->setUseIPv4(global->haveMulticast4()); } } void jb_available(const QByteArray &instance) { JDnsBrowse *jb = static_cast(sender()); BrowseItem *i = browseItemList.itemByBrowse(jb); Q_ASSERT(i); QByteArray name = instance + '.' + jb->typeAndDomain; ServiceInstance si(QString::fromLatin1(instance), QString::fromLatin1(jb->type), "local.", QMap()); items.insert(name, si); emit browse_instanceAvailable(i->id, si); } void jb_unavailable(const QByteArray &instance) { JDnsBrowse *jb = static_cast(sender()); BrowseItem *i = browseItemList.itemByBrowse(jb); Q_ASSERT(i); QByteArray name = instance + '.' + jb->typeAndDomain; Q_ASSERT(items.contains(name)); ServiceInstance si = items.value(name); items.remove(name); emit browse_instanceUnavailable(i->id, si); } void do_browse_error(int id, XMPP::ServiceBrowser::Error e) { BrowseItem *i = browseItemList.itemById(id); Q_ASSERT(i); browseItemList.remove(i); emit browse_error(id, e); } void jr_finished() { JDnsServiceResolve *jr = static_cast(sender()); ResolveItem * i = resolveItemList.itemByResolve(jr); Q_ASSERT(i); // parse TXT list into attribute map QMap attribs; for (int n = 0; n < jr->attribs.count(); ++n) { const QByteArray &a = jr->attribs[n]; QString key; QByteArray value; int x = a.indexOf('='); if (x != -1) { key = QString::fromLatin1(a.mid(0, x)); value = a.mid(x + 1); } else { key = QString::fromLatin1(a); } attribs.insert(key, value); } // one of these must be true Q_ASSERT(jr->have4 || jr->have6); QList results; if (jr->have6) { ResolveResult r; r.attributes = attribs; r.address = jr->addr6; r.port = jr->port; r.hostName = jr->host; results += r; } if (jr->have4) { ResolveResult r; r.attributes = attribs; r.address = jr->addr4; r.port = jr->port; r.hostName = jr->host; results += r; } int id = i->id; resolveItemList.remove(i); emit resolve_resultsReady(id, results); } void jr_error(QJDnsSharedRequest::Error e) { JDnsServiceResolve *jr = static_cast(sender()); ResolveItem * i = resolveItemList.itemByResolve(jr); Q_ASSERT(i); ServiceResolver::Error err; if (e == QJDnsSharedRequest::ErrorTimeout) err = ServiceResolver::ErrorTimeout; else err = ServiceResolver::ErrorGeneric; int id = i->id; resolveItemList.remove(i); emit resolve_error(id, err); } void do_resolve_error(int id, XMPP::ServiceResolver::Error e) { ResolveItem *i = resolveItemList.itemById(id); Q_ASSERT(i); resolveItemList.remove(i); emit resolve_error(id, e); } void pub_addresses_hostName(const QByteArray &name) { // tell all active publishes about the change for (PublishItem *item : publishItemList.items) item->publish->hostChanged(name); } void jp_published() { JDnsPublish *jp = static_cast(sender()); PublishItem *i = publishItemList.itemByPublish(jp); Q_ASSERT(i); emit publish_published(i->id); } void jp_error(QJDnsSharedRequest::Error e) { JDnsPublish *jp = static_cast(sender()); PublishItem *i = publishItemList.itemByPublish(jp); Q_ASSERT(i); ServiceLocalPublisher::Error err; if (e == QJDnsSharedRequest::ErrorConflict) err = ServiceLocalPublisher::ErrorConflict; else err = ServiceLocalPublisher::ErrorGeneric; int id = i->id; cleanupExtra(i); publishItemList.remove(i); emit publish_error(id, err); } void do_publish_error(int id, XMPP::ServiceLocalPublisher::Error e) { PublishItem *i = publishItemList.itemById(id); Q_ASSERT(i); cleanupExtra(i); publishItemList.remove(i); emit publish_error(id, e); } void jpe_published() { JDnsPublishExtra *jp = static_cast(sender()); PublishExtraItem *i = publishExtraItemList.itemByPublish(jp); Q_ASSERT(i); emit publish_extra_published(i->id); } void jpe_error(QJDnsSharedRequest::Error e) { JDnsPublishExtra *jp = static_cast(sender()); PublishExtraItem *i = publishExtraItemList.itemByPublish(jp); Q_ASSERT(i); ServiceLocalPublisher::Error err; if (e == QJDnsSharedRequest::ErrorConflict) err = ServiceLocalPublisher::ErrorConflict; else err = ServiceLocalPublisher::ErrorGeneric; int id = i->id; publishExtraItemList.remove(i); emit publish_extra_error(id, err); } void do_publish_extra_error(int id, XMPP::ServiceLocalPublisher::Error e) { PublishExtraItem *i = publishExtraItemList.itemById(id); Q_ASSERT(i); publishExtraItemList.remove(i); emit publish_extra_error(id, e); } }; //---------------------------------------------------------------------------- // JDnsProvider //---------------------------------------------------------------------------- class JDnsProvider : public IrisNetProvider { Q_OBJECT Q_INTERFACES(XMPP::IrisNetProvider) public: JDnsGlobal *global; JDnsProvider() { global = 0; } ~JDnsProvider() { delete global; } void ensure_global() { if (!global) global = new JDnsGlobal; } virtual NameProvider *createNameProviderInternet() { ensure_global(); return JDnsNameProvider::create(global, JDnsNameProvider::Internet); } virtual NameProvider *createNameProviderLocal() { ensure_global(); return JDnsNameProvider::create(global, JDnsNameProvider::Local); } virtual ServiceProvider *createServiceProvider() { ensure_global(); return JDnsServiceProvider::create(global); } }; IrisNetProvider *irisnet_createJDnsProvider() { return new JDnsProvider; } } // namespace XMPP #include "netnames_jdns.moc" psi-plus-snapshots-1.4.1456/iris/src/irisnet/corelib/objectsession.cpp000066400000000000000000000165561370065651000257770ustar00rootroot00000000000000/* * Copyright (C) 2008 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "objectsession.h" #include #include #include #include #include #include namespace XMPP { class ObjectSessionWatcherPrivate { public: ObjectSession *sess; }; class ObjectSessionPrivate : public QObject { Q_OBJECT public: ObjectSession *q; class MethodCall { public: QObject * obj; QByteArray method; class Argument { public: int type; void *data; }; QList args; MethodCall(QObject *_obj, const char *_method) : obj(_obj), method(_method) { } ~MethodCall() { clearArgs(); } void clearArgs() { for (int n = 0; n < args.count(); ++n) QMetaType::destroy(args[n].type, args[n].data); args.clear(); } bool setArgs(QGenericArgument val0 = QGenericArgument(), QGenericArgument val1 = QGenericArgument(), QGenericArgument val2 = QGenericArgument(), QGenericArgument val3 = QGenericArgument(), QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(), QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(), QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument()) { const char *arg_name[] = { val0.name(), val1.name(), val2.name(), val3.name(), val4.name(), val5.name(), val6.name(), val7.name(), val8.name(), val9.name() }; void *arg_data[] = { val0.data(), val1.data(), val2.data(), val3.data(), val4.data(), val5.data(), val6.data(), val7.data(), val8.data(), val9.data() }; clearArgs(); for (int n = 0; n < 10; ++n) { if (arg_name[n] == nullptr) break; Argument arg; arg.type = QMetaType::type(arg_name[n]); if (!arg.type) { clearArgs(); return false; } arg.data = QMetaType::create(arg.type, arg_data[n]); args += arg; } return true; } }; QList pendingCalls; QTimer * callTrigger; bool paused; QList watchers; ObjectSessionPrivate(ObjectSession *_q) : QObject(_q), q(_q), paused(false) { callTrigger = new QTimer(this); connect(callTrigger, SIGNAL(timeout()), SLOT(doCall())); callTrigger->setSingleShot(true); } ~ObjectSessionPrivate() { invalidateWatchers(); callTrigger->disconnect(this); callTrigger->setParent(nullptr); callTrigger->deleteLater(); qDeleteAll(pendingCalls); pendingCalls.clear(); } void addPendingCall(MethodCall *call) { pendingCalls += call; if (!paused && !callTrigger->isActive()) callTrigger->start(); } bool havePendingCall(QObject *obj, const char *method) const { for (const MethodCall *call : pendingCalls) { if (call->obj == obj && qstrcmp(call->method.data(), method) == 0) return true; } return false; } void invalidateWatchers() { for (int n = 0; n < watchers.count(); ++n) watchers[n]->sess = nullptr; watchers.clear(); } private slots: void doCall() { MethodCall *call = pendingCalls.takeFirst(); if (!pendingCalls.isEmpty()) callTrigger->start(); Q_ASSERT(call->args.count() <= 10); QGenericArgument arg[10]; for (int n = 0; n < call->args.count(); ++n) arg[n] = QGenericArgument(QMetaType::typeName(call->args[n].type), call->args[n].data); bool ok; ok = QMetaObject::invokeMethod(call->obj, call->method.data(), Qt::DirectConnection, arg[0], arg[1], arg[2], arg[3], arg[4], arg[5], arg[6], arg[7], arg[8], arg[9]); Q_ASSERT(ok); if (!ok) abort(); delete call; } }; ObjectSessionWatcher::ObjectSessionWatcher(ObjectSession *sess) { d = new ObjectSessionWatcherPrivate; d->sess = sess; if (d->sess) d->sess->d->watchers += d; } ObjectSessionWatcher::~ObjectSessionWatcher() { if (d->sess) d->sess->d->watchers.removeAll(d); delete d; } bool ObjectSessionWatcher::isValid() const { if (d->sess) return true; else return false; } ObjectSession::ObjectSession(QObject *parent) : QObject(parent) { d = new ObjectSessionPrivate(this); } ObjectSession::~ObjectSession() { delete d; } void ObjectSession::reset() { d->invalidateWatchers(); if (d->callTrigger->isActive()) d->callTrigger->stop(); qDeleteAll(d->pendingCalls); d->pendingCalls.clear(); } bool ObjectSession::isDeferred(QObject *obj, const char *method) { return d->havePendingCall(obj, method); } void ObjectSession::defer(QObject *obj, const char *method, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9) { ObjectSessionPrivate::MethodCall *call = new ObjectSessionPrivate::MethodCall(obj, method); call->setArgs(val0, val1, val2, val3, val4, val5, val6, val7, val8, val9); d->addPendingCall(call); } void ObjectSession::deferExclusive(QObject *obj, const char *method, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9) { if (d->havePendingCall(obj, method)) return; ObjectSessionPrivate::MethodCall *call = new ObjectSessionPrivate::MethodCall(obj, method); call->setArgs(val0, val1, val2, val3, val4, val5, val6, val7, val8, val9); d->addPendingCall(call); } void ObjectSession::pause() { Q_ASSERT(!d->paused); if (d->callTrigger->isActive()) d->callTrigger->stop(); d->paused = true; } void ObjectSession::resume() { Q_ASSERT(d->paused); d->paused = false; if (!d->pendingCalls.isEmpty()) d->callTrigger->start(); } } // namespace XMPP #include "objectsession.moc" psi-plus-snapshots-1.4.1456/iris/src/irisnet/corelib/objectsession.h000066400000000000000000000052301370065651000254270ustar00rootroot00000000000000/* * Copyright (C) 2008 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef OBJECTSESSION_H #define OBJECTSESSION_H #include namespace XMPP { class ObjectSessionPrivate; class ObjectSessionWatcherPrivate; class ObjectSession : public QObject { Q_OBJECT public: ObjectSession(QObject *parent = nullptr); ~ObjectSession(); // clear all deferred requests, invalidate watchers void reset(); bool isDeferred(QObject *obj, const char *method); void defer(QObject *obj, const char *method, QGenericArgument val0 = QGenericArgument(), QGenericArgument val1 = QGenericArgument(), QGenericArgument val2 = QGenericArgument(), QGenericArgument val3 = QGenericArgument(), QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(), QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(), QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument()); void deferExclusive(QObject *obj, const char *method, QGenericArgument val0 = QGenericArgument(), QGenericArgument val1 = QGenericArgument(), QGenericArgument val2 = QGenericArgument(), QGenericArgument val3 = QGenericArgument(), QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(), QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(), QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument()); void pause(); void resume(); private: friend class ObjectSessionWatcher; ObjectSessionPrivate *d; }; class ObjectSessionWatcher { public: ObjectSessionWatcher(ObjectSession *sess); ~ObjectSessionWatcher(); bool isValid() const; private: friend class ObjectSessionPrivate; ObjectSessionWatcherPrivate *d; }; } // namespace XMPP #endif // OBJECTSESSION_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/irisnet.pro000066400000000000000000000006001370065651000231600ustar00rootroot00000000000000TEMPLATE = subdirs include(../libbase.pri) sub_corelib.subdir = corelib sub_appledns.subdir = appledns sub_appledns.depends = sub_corelib sub_noncore.subdir = noncore !irisnetcore_bundle:sub_noncore.depends = sub_corelib !irisnetcore_bundle:SUBDIRS += sub_corelib appledns:!appledns_bundle:SUBDIRS += sub_appledns !iris_bundle:SUBDIRS += sub_noncore OTHER_FILES += CMakeLists.txt psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/000077500000000000000000000000001370065651000224305ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/cutestuff/000077500000000000000000000000001370065651000244405ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/cutestuff/bsocket.cpp000066400000000000000000000476641370065651000266170ustar00rootroot00000000000000/* * bsocket.cpp - QSocket wrapper based on Bytestream with SRV DNS support * Copyright (C) 2003 Justin Karneges * Copyright (C) 2009-2010 Dennis Schridde * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "bsocket.h" #include #include #include #include //#define BS_DEBUG #ifdef BS_DEBUG #define BSDEBUG (qDebug() << this << "#" << __FUNCTION__ << ":") #endif #define READBUFSIZE 65536 // CS_NAMESPACE_BEGIN class QTcpSocketSignalRelay : public QObject { Q_OBJECT public: QTcpSocketSignalRelay(QTcpSocket *sock, QObject *parent = nullptr) : QObject(parent) { qRegisterMetaType("QAbstractSocket::SocketError"); connect(sock, SIGNAL(hostFound()), SLOT(sock_hostFound()), Qt::QueuedConnection); connect(sock, SIGNAL(connected()), SLOT(sock_connected()), Qt::QueuedConnection); connect(sock, SIGNAL(disconnected()), SLOT(sock_disconnected()), Qt::QueuedConnection); connect(sock, SIGNAL(readyRead()), SLOT(sock_readyRead()), Qt::QueuedConnection); connect(sock, SIGNAL(bytesWritten(qint64)), SLOT(sock_bytesWritten(qint64)), Qt::QueuedConnection); connect(sock, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(sock_error(QAbstractSocket::SocketError)), Qt::QueuedConnection); } signals: void hostFound(); void connected(); void disconnected(); void readyRead(); void bytesWritten(qint64); void error(QAbstractSocket::SocketError); public slots: void sock_hostFound() { emit hostFound(); } void sock_connected() { emit connected(); } void sock_disconnected() { emit disconnected(); } void sock_readyRead() { emit readyRead(); } void sock_bytesWritten(qint64 x) { emit bytesWritten(x); } void sock_error(QAbstractSocket::SocketError x) { emit error(x); } }; class HappyEyeballsConnector : public QObject { Q_OBJECT public: enum State { Failure, Created, Resolve, Connecting, Connected }; struct SockData { QTcpSocket * sock; QTcpSocketSignalRelay *relay; State state; XMPP::ServiceResolver *resolver; }; /*! source data */ QString service; QString transport; QString domain; quint16 port = 0; QHostAddress address; QAbstractSocket::NetworkLayerProtocol fallbackProtocol = QAbstractSocket::IPv4Protocol; /*! runtime data */ QString lastError; int lastIndex; QList sockets; QTimer fallbackTimer; HappyEyeballsConnector(QObject *parent) : QObject(parent) { fallbackTimer.setSingleShot(true); fallbackTimer.setInterval(250); /* rfc recommends 150-250ms */ connect(&fallbackTimer, SIGNAL(timeout()), SLOT(startFallback())); } SockData &addSocket() { SockData sd; sd.state = Created; sd.sock = new QTcpSocket(this); sd.sock->setProxy(QNetworkProxy::NoProxy); sd.sock->setReadBufferSize(READBUFSIZE); sd.relay = new QTcpSocketSignalRelay(sd.sock, this); sd.resolver = nullptr; connect(sd.relay, &QTcpSocketSignalRelay::connected, this, &HappyEyeballsConnector::qs_connected); connect(sd.relay, &QTcpSocketSignalRelay::error, this, &HappyEyeballsConnector::qs_error); sockets.append(sd); return sockets[sockets.count() - 1]; } void cleanup() { for (int i = 0; i < sockets.count(); i++) { abortSocket(sockets[i]); } fallbackTimer.stop(); } void connectToHost(const QHostAddress &address, quint16 port) { #ifdef BS_DEBUG BSDEBUG << "a:" << address << "p:" << port; #endif this->address = address; SockData &sd = addSocket(); sd.state = Connecting; sd.sock->connectToHost(address, port); } /* Connect to a host via the specified protocol, or the default protocols if not specified */ void connectToHost(const QString &host, quint16 port, QAbstractSocket::NetworkLayerProtocol protocol) { #ifdef BS_DEBUG BSDEBUG << "h:" << host << "p:" << port << "pr:" << protocol; #endif this->domain = host; this->port = port; SockData &sd = addSocket(); QHostAddress addr(host); if (addr.isNull()) { sd.resolver = new XMPP::ServiceResolver; initResolver(sd.resolver); sd.resolver->setProtocol(protocol == QAbstractSocket::UnknownNetworkLayerProtocol ? (fallbackProtocol == QAbstractSocket::IPv4Protocol ? XMPP::ServiceResolver::IPv6 : XMPP::ServiceResolver::IPv4) : (protocol == QAbstractSocket::IPv4Protocol ? XMPP::ServiceResolver::IPv4 : XMPP::ServiceResolver::IPv6)); if (protocol == QAbstractSocket::UnknownNetworkLayerProtocol) { addSocket(); fallbackTimer.start(); } sd.state = Resolve; sd.resolver->start(domain, port); } else { // connecting by IP. lastIndex = sockets.count() - 1; sd.state = Connecting; sd.sock->connectToHost(addr, port); } } void connectToHost(const QString &service, const QString &transport, const QString &domain, quint16 port) { #ifdef BS_DEBUG BSDEBUG << "s:" << service << "t:" << transport << "d:" << domain; #endif this->service = service; this->transport = transport; this->domain = domain; this->port = port; SockData &sd = addSocket(); sd.resolver = new XMPP::ServiceResolver(this); sd.resolver->setProtocol(XMPP::ServiceResolver::HappyEyeballs); connect(sd.resolver, SIGNAL(srvReady()), SLOT(splitSrvResolvers())); // we don't care about special handling of fail. we have fallback host there anyway connect(sd.resolver, SIGNAL(srvFailed()), SLOT(splitSrvResolvers())); sd.state = Resolve; sd.resolver->start(service, transport, domain, port); } SockData takeCurrent(QObject *parent) { SockData csd = sockets.takeAt(lastIndex); lastIndex = -1; disconnect(csd.relay); csd.relay->setParent(parent); csd.sock->setParent(parent); delete csd .resolver; // FIME ensure it's accessible only from connected signal. we don't delete resolver from its slot csd.resolver = nullptr; return csd; } private: void abortSocket(SockData &sd) { sd.relay->disconnect(this); if (sd.state >= Connecting) { sd.sock->abort(); } if (sd.resolver) { sd.resolver->stop(); disconnect(sd.resolver); sd.resolver->deleteLater(); // or just delete ? } delete sd.relay; delete sd.sock; } void initResolver(XMPP::ServiceResolver *resolver) { resolver->setParent(this); connect(resolver, SIGNAL(resultReady(QHostAddress, quint16)), this, SLOT(handleDnsReady(QHostAddress, quint16))); connect(resolver, SIGNAL(error(XMPP::ServiceResolver::Error)), this, SLOT(handleDnsError(XMPP::ServiceResolver::Error))); } void setCurrentByResolver(XMPP::ServiceResolver *resolver) { for (int i = 0; i < sockets.count(); i++) { if (sockets.at(i).resolver == resolver) { lastIndex = i; return; } } lastIndex = -1; } void setCurrentByRelay(QTcpSocketSignalRelay *relay) { for (int i = 0; i < sockets.count(); i++) { if (sockets.at(i).relay == relay) { lastIndex = i; return; } } lastIndex = -1; } private slots: /* Notice: most probably recipient should reparent socket and relay */ void qs_connected() { #ifdef BS_DEBUG BSDEBUG; #endif QPointer valid(this); setCurrentByRelay(static_cast(sender())); for (int i = 0; i < sockets.count(); i++) { if (i != lastIndex) { abortSocket(sockets[i]); } else { disconnect(sockets[i].relay); sockets[i].state = Connected; } emit connected(); if (!valid) return; } } void qs_error(QAbstractSocket::SocketError errorCode) { setCurrentByRelay(static_cast(sender())); // TODO remember error code lastError = sockets[lastIndex].sock->errorString(); #ifdef BS_DEBUG BSDEBUG << "error:" << lastError; #endif if (sockets[lastIndex].resolver) { sockets[lastIndex].sock->abort(); sockets[lastIndex].state = Resolve; sockets[lastIndex].resolver->tryNext(); } else { // it seems we connect by hostaddress. just one socket w/o resolver emit error(errorCode); } } void splitSrvResolvers() { #ifdef BS_DEBUG BSDEBUG << "splitting resolvers"; #endif setCurrentByResolver(static_cast(sender())); SockData & sdv4 = sockets[lastIndex]; SockData & sdv6 = addSocket(); XMPP::ServiceResolver::ProtoSplit ps = sdv4.resolver->happySplit(); initResolver(ps.ipv4); initResolver(ps.ipv6); disconnect(sdv4.resolver); sdv4.resolver->deleteLater(); sdv4.resolver = ps.ipv4; sdv4.state = Created; sdv6.resolver = ps.ipv6; if (fallbackProtocol == QAbstractSocket::IPv4Protocol) { sdv6.state = Resolve; sdv6.resolver->tryNext(); } else { sdv4.state = Resolve; sdv4.resolver->tryNext(); } fallbackTimer.start(); } /* host resolved, now try to connect to it */ void handleDnsReady(const QHostAddress &address, quint16 port) { #ifdef BS_DEBUG BSDEBUG << "a:" << address << "p:" << port; #endif setCurrentByResolver(static_cast(sender())); sockets[lastIndex].state = Connecting; sockets[lastIndex].sock->connectToHost(address, port); } /* resolver failed the dns lookup */ void handleDnsError(XMPP::ServiceResolver::Error e) { #ifdef BS_DEBUG BSDEBUG << "e:" << e; #else Q_UNUSED(e) #endif if (!fallbackTimer.isActive()) { emit error(QAbstractSocket::HostNotFoundError); } } void startFallback() { #ifdef BS_DEBUG BSDEBUG; #endif for (int i = 0; i < sockets.count(); i++) { SockData &sd = sockets[i]; if (sd.state == Created) { sd.state = Resolve; if (sd.resolver) { sd.resolver->tryNext(); } else { sd.resolver = new XMPP::ServiceResolver; initResolver(sd.resolver); sd.resolver->setProtocol(fallbackProtocol == QAbstractSocket::IPv4Protocol ? XMPP::ServiceResolver::IPv4 : XMPP::ServiceResolver::IPv6); sd.resolver->start(domain, port); } } } } signals: void connected(); void error(QAbstractSocket::SocketError); }; class BSocket::Private { public: Private() { qsock = nullptr; qsock_relay = nullptr; } QTcpSocket * qsock; QTcpSocketSignalRelay *qsock_relay; int state; QString domain; //!< Domain we are currently connected to QString host; //!< Hostname we are currently connected to QHostAddress address; //!< IP address we are currently connected to quint16 port; //!< Port we are currently connected to QPointer connector; }; BSocket::BSocket(QObject *parent) : ByteStream(parent) { d = new Private; resetConnection(); } BSocket::~BSocket() { resetConnection(true); delete d; } void BSocket::resetConnection(bool clear) { #ifdef BS_DEBUG BSDEBUG << clear; #endif if (d->connector) { d->connector->deleteLater(); disconnect(d->connector); d->connector = nullptr; } if (d->qsock) { delete d->qsock_relay; d->qsock_relay = nullptr; // move remaining into the local queue if (d->qsock->isOpen()) { QByteArray block(int(d->qsock->bytesAvailable()), 0); // memory won't never be cheap enough to have gigabytes for socket buffer if (block.size()) { d->qsock->read(block.data(), block.size()); appendRead(block); } d->qsock->close(); } // d->sd.deleteLater(d->qsock); d->qsock->deleteLater(); d->qsock = nullptr; } else { if (clear) clearReadBuffer(); } d->state = Idle; d->domain = ""; d->host = ""; d->address = QHostAddress(); d->port = 0; setOpenMode(QIODevice::NotOpen); } void BSocket::ensureConnector() { if (!d->connector) { d->connector = new HappyEyeballsConnector(this); connect(d->connector, &HappyEyeballsConnector::connected, this, &BSocket::qs_connected); connect(d->connector, &HappyEyeballsConnector::error, this, &BSocket::qs_error); } } /* Connect to an already resolved host */ void BSocket::connectToHost(const QHostAddress &address, quint16 port) { resetConnection(true); d->address = address; d->port = port; d->state = Connecting; ensureConnector(); d->connector->connectToHost(address, port); } /* Connect to a host via the specified protocol, or the default protocols if not specified */ void BSocket::connectToHost(const QString &host, quint16 port, QAbstractSocket::NetworkLayerProtocol protocol) { resetConnection(true); d->host = host; d->port = port; d->state = Connecting; ensureConnector(); d->connector->connectToHost(host, port, protocol); } /* Connect to the hosts for the specified service */ void BSocket::connectToHost(const QString &service, const QString &transport, const QString &domain, quint16 port) { resetConnection(true); d->domain = domain; d->state = Connecting; ensureConnector(); d->connector->connectToHost(service, transport, domain, port); } QAbstractSocket *BSocket::abstractSocket() const { return d->qsock; } qintptr BSocket::socket() const { if (d->qsock) return d->qsock->socketDescriptor(); else return -1; } void BSocket::setSocket(QTcpSocket *s) { resetConnection(true); s->setParent(this); d->qsock = s; d->qsock_relay = new QTcpSocketSignalRelay(d->qsock, this); qs_connected_step2(false); // we have desriptor already. so it's already known to be connected } int BSocket::state() const { return d->state; } bool BSocket::isOpen() const { if (d->state == Connected) return true; else return false; } void BSocket::close() { if (d->state == Idle) return; if (d->qsock) { d->state = Closing; d->qsock->close(); if (d->qsock->state() == QAbstractSocket::ClosingState) { return; // wait for disconnected signal } else { resetConnection(); } } else { resetConnection(); } } qint64 BSocket::writeData(const char *data, qint64 maxSize) { if (d->state != Connected) return 0; #ifdef BS_DEBUG_EXTRA BSDEBUG << "- [" << maxSize << "]: {" << QByteArray::fromRawData(data, maxSize) << "}"; #endif return d->qsock->write(data, maxSize); } qint64 BSocket::readData(char *data, qint64 maxSize) { if (!maxSize) { return 0; } qint64 readSize; if (d->qsock) { qint64 max = bytesAvailable(); if (maxSize <= 0 || maxSize > max) { maxSize = max; } readSize = d->qsock->read(data, maxSize); } else { readSize = ByteStream::readData(data, maxSize); } #ifdef BS_DEBUG_EXTRA BSDEBUG << "- [" << readSize << "]: {" << QByteArray::fromRawData(data, readSize) << "}"; #endif return readSize; } qint64 BSocket::bytesAvailable() const { if (d->qsock) return d->qsock->bytesAvailable(); else return ByteStream::bytesAvailable(); } qint64 BSocket::bytesToWrite() const { if (!d->qsock) return 0; return d->qsock->bytesToWrite(); } QHostAddress BSocket::address() const { if (d->qsock) return d->qsock->localAddress(); else return QHostAddress(); } quint16 BSocket::port() const { if (d->qsock) return d->qsock->localPort(); else return 0; } QHostAddress BSocket::peerAddress() const { if (d->qsock) return d->qsock->peerAddress(); else return QHostAddress(); } quint16 BSocket::peerPort() const { if (d->qsock) return d->qsock->peerPort(); else return 0; } void BSocket::qs_connected() { HappyEyeballsConnector::SockData sd = d->connector->takeCurrent(this); d->qsock = sd.sock; d->qsock_relay = sd.relay; d->connector->deleteLater(); qs_connected_step2(true); } void BSocket::qs_connected_step2(bool signalConnected) { connect(d->qsock_relay, SIGNAL(disconnected()), SLOT(qs_closed())); connect(d->qsock_relay, SIGNAL(readyRead()), SLOT(qs_readyRead())); connect(d->qsock_relay, SIGNAL(bytesWritten(qint64)), SLOT(qs_bytesWritten(qint64))); connect(d->qsock_relay, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(qs_error(QAbstractSocket::SocketError))); setOpenMode(QIODevice::ReadWrite); d->state = Connected; #ifdef BS_DEBUG BSDEBUG << "Connected"; #endif QPointer valid(this); if (signalConnected) { emit connected(); } if (valid && d->qsock->bytesAvailable()) { qs_readyRead(); } } void BSocket::qs_closed() { if (d->state == Closing) { #ifdef BS_DEBUG BSDEBUG << "Delayed Close Finished"; #endif resetConnection(); emit delayedCloseFinished(); } } void BSocket::qs_readyRead() { emit readyRead(); } void BSocket::qs_bytesWritten(qint64 x64) { int x = x64; #ifdef BS_DEBUG_EXTRA BSDEBUG << "BytesWritten [" << x << "]"; #endif emit bytesWritten(x); } void BSocket::qs_error(QAbstractSocket::SocketError x) { if (x == QTcpSocket::RemoteHostClosedError) { #ifdef BS_DEBUG BSDEBUG << "Connection Closed"; #endif resetConnection(); emit connectionClosed(); return; } #ifdef BS_DEBUG BSDEBUG << "Error"; #endif resetConnection(); if (x == QTcpSocket::ConnectionRefusedError) emit error(ErrConnectionRefused); else if (x == QTcpSocket::HostNotFoundError) emit error(ErrHostNotFound); else emit error(ErrRead); } #include "bsocket.moc" // CS_NAMESPACE_END psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/cutestuff/bsocket.h000066400000000000000000000062501370065651000262460ustar00rootroot00000000000000/* * bsocket.h - QSocket wrapper based on Bytestream with SRV DNS support * Copyright (C) 2003 Justin Karneges * Copyright (C) 2009-2010 Dennis Schridde * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef CS_BSOCKET_H #define CS_BSOCKET_H #include "bytestream.h" #include "netnames.h" #include #include class QByteArray; class QObject; class QString; // CS_NAMESPACE_BEGIN /*! Socket with automatic hostname lookups, using SRV, AAAA and A DNS queries. */ class BSocket : public ByteStream { Q_OBJECT public: enum Error { ErrConnectionRefused = ErrCustom, ErrHostNotFound }; enum State { Idle, HostLookup, Connecting, Connected, Closing }; BSocket(QObject *parent = nullptr); ~BSocket(); /*! Connect to an already resolved host */ void connectToHost(const QHostAddress &address, quint16 port); /*! Connect to a host via the specified protocol, or the default protocols if not specified */ void connectToHost(const QString &host, quint16 port, QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::UnknownNetworkLayerProtocol); /*! Connect to the hosts for the specified service */ void connectToHost(const QString &service, const QString &transport, const QString &domain, quint16 port = std::numeric_limits::max()); virtual QAbstractSocket *abstractSocket() const; qintptr socket() const; void setSocket(QTcpSocket *); int state() const; // from ByteStream bool isOpen() const; void close(); qint64 bytesAvailable() const; qint64 bytesToWrite() const; // local QHostAddress address() const; quint16 port() const; // remote QHostAddress peerAddress() const; quint16 peerPort() const; protected: qint64 writeData(const char *data, qint64 maxSize); qint64 readData(char *data, qint64 maxSize); signals: void hostFound(); void connected(); private slots: void qs_connected(); void qs_closed(); void qs_readyRead(); void qs_bytesWritten(qint64); void qs_error(QAbstractSocket::SocketError); private: class Private; Private *d; void resetConnection(bool clear = false); void ensureConnector(); void recreate_resolver(); bool check_protocol_fallback(); void dns_srv_try_next(); bool connect_host_try_next(); void qs_connected_step2(bool signalConnected = true); }; // CS_NAMESPACE_END #endif // CS_BSOCKET_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/cutestuff/bytestream.cpp000066400000000000000000000156761370065651000273420ustar00rootroot00000000000000/* * bytestream.cpp - base class for bytestreams * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "bytestream.h" #include // CS_NAMESPACE_BEGIN //! \class ByteStream bytestream.h //! \brief Base class for "bytestreams" //! //! This class provides a basic framework for a "bytestream", here defined //! as a bi-directional, asynchronous pipe of data. It can be used to create //! several different kinds of bytestream-applications, such as a console or //! TCP connection, or something more abstract like a security layer or tunnel, //! all with the same interface. The provided functions make creating such //! classes simpler. ByteStream is a pure-virtual class, so you do not use it //! on its own, but instead through a subclass such as \a BSocket. //! //! The signals connectionClosed(), delayedCloseFinished(), readyRead(), //! bytesWritten(), and error() serve the exact same function as those from //! QSocket. //! //! The simplest way to create a ByteStream is to reimplement isOpen(), close(), //! and tryWrite(). Call appendRead() whenever you want to make data available for //! reading. ByteStream will take care of the buffers with regards to the caller, //! and will call tryWrite() when the write buffer gains data. It will be your //! job to call tryWrite() whenever it is acceptable to write more data to //! the underlying system. //! //! If you need more advanced control, reimplement read(), write(), bytesAvailable(), //! and/or bytesToWrite() as necessary. //! //! Use appendRead(), appendWrite(), takeRead(), and takeWrite() to modify the //! buffers. If you have more advanced requirements, the buffers can be accessed //! directly with readBuf() and writeBuf(). //! //! Also available are the static convenience functions ByteStream::appendArray() //! and ByteStream::takeArray(), which make dealing with byte queues very easy. class ByteStream::Private { public: Private() { } QByteArray readBuf, writeBuf; int errorCode; QString errorText; }; //! //! Constructs a ByteStream object with parent \a parent. ByteStream::ByteStream(QObject *parent) : QIODevice(parent) { d = new Private; } //! //! Destroys the object and frees allocated resources. ByteStream::~ByteStream() { delete d; } //! //! Writes array \a a to the stream. qint64 ByteStream::writeData(const char *data, qint64 maxSize) { if (!isOpen()) return -1; bool doWrite = bytesToWrite() == 0 ? true : false; d->writeBuf.append(data, maxSize); if (doWrite) tryWrite(); return maxSize; } //! //! Reads bytes \a bytes of data from the stream and returns them as an array. If \a bytes is 0, then //! \a read will return all available data. qint64 ByteStream::readData(char *data, qint64 maxSize) { maxSize = maxSize > d->readBuf.size() ? d->readBuf.size() : maxSize; memcpy(data, d->readBuf.constData(), maxSize); d->readBuf.remove(0, maxSize); return maxSize; } //! //! Returns the number of bytes available for reading. qint64 ByteStream::bytesAvailable() const { return QIODevice::bytesAvailable() + d->readBuf.size(); } //! //! Returns the number of bytes that are waiting to be written. qint64 ByteStream::bytesToWrite() const { return d->writeBuf.size(); } //! //! Clears the read buffer. void ByteStream::clearReadBuffer() { d->readBuf.resize(0); } //! //! Clears the write buffer. void ByteStream::clearWriteBuffer() { d->writeBuf.resize(0); } //! //! Appends \a block to the end of the read buffer. void ByteStream::appendRead(const QByteArray &block) { d->readBuf += block; } //! //! Appends \a block to the end of the write buffer. void ByteStream::appendWrite(const QByteArray &block) { d->writeBuf += block; } //! //! Returns \a size bytes from the start of the read buffer. //! If \a size is 0, then all available data will be returned. //! If \a del is TRUE, then the bytes are also removed. QByteArray ByteStream::takeRead(int size, bool del) { return takeArray(d->readBuf, size, del); } //! //! Returns \a size bytes from the start of the write buffer. //! If \a size is 0, then all available data will be returned. //! If \a del is TRUE, then the bytes are also removed. QByteArray ByteStream::takeWrite(int size, bool del) { return takeArray(d->writeBuf, size, del); } //! //! Returns a reference to the read buffer. QByteArray &ByteStream::readBuf() { return d->readBuf; } //! //! Returns a reference to the write buffer. QByteArray &ByteStream::writeBuf() { return d->writeBuf; } //! //! Attempts to try and write some bytes from the write buffer, and returns the number //! successfully written or -1 on error. The default implementation returns -1. int ByteStream::tryWrite() { return -1; } //! //! Returns \a size bytes from the start of the array pointed to by \a from. //! If \a size is 0, then all available data will be returned. //! If \a del is TRUE, then the bytes are also removed. QByteArray ByteStream::takeArray(QByteArray &from, int size, bool del) { QByteArray result; if (size == 0) { result = from; if (del) from.resize(0); } else { result = from.left(size); if (del) { from.remove(0, size); } } return result; } //! //! Returns last error code. int ByteStream::errorCode() const { return d->errorCode; } //! //! Returns last error string corresponding to last error code. QString &ByteStream::errorText() const { return d->errorText; } //! //! Sets last error with \a code and \a text and emit it void ByteStream::setError(int code, const QString &text) { d->errorCode = code; d->errorText = text; if (code != ErrOk) { emit error(code); } } void connectionClosed(); void delayedCloseFinished(); void readyRead(); void bytesWritten(qint64); void error(int); //! \fn void ByteStream::connectionClosed() //! This signal is emitted when the remote end of the stream closes. //! \fn void ByteStream::delayedCloseFinished() //! This signal is emitted when all pending data has been written to the stream //! after an attempt to close. //! \fn void ByteStream::readyRead() //! This signal is emitted when data is available to be read. //! \fn void ByteStream::error(int code) //! This signal is emitted when an error occurs in the stream. The reason for //! error is indicated by \a code. // CS_NAMESPACE_END psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/cutestuff/bytestream.h000066400000000000000000000043421370065651000267730ustar00rootroot00000000000000/* * bytestream.h - base class for bytestreams * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef CS_BYTESTREAM_H #define CS_BYTESTREAM_H #include #include #include class QAbstractSocket; // CS_NAMESPACE_BEGIN // CS_EXPORT_BEGIN class ByteStream : public QIODevice { Q_OBJECT public: enum Error { ErrOk, ErrRead, ErrWrite, ErrCustom = 10 }; ByteStream(QObject *parent = nullptr); ~ByteStream() = 0; bool isSequential() const { return true; } qint64 bytesAvailable() const; qint64 bytesToWrite() const; static QByteArray takeArray(QByteArray &from, int size = 0, bool del = true); int errorCode() const; QString &errorText() const; virtual QAbstractSocket *abstractSocket() const { return nullptr; } signals: void connectionClosed(); void delayedCloseFinished(); void error(int); protected: qint64 writeData(const char *data, qint64 maxSize); qint64 readData(char *data, qint64 maxSize); void setError(int code = ErrOk, const QString &text = QString()); void clearReadBuffer(); void clearWriteBuffer(); void appendRead(const QByteArray &); void appendWrite(const QByteArray &); QByteArray takeRead(int size = 0, bool del = true); QByteArray takeWrite(int size = 0, bool del = true); QByteArray &readBuf(); QByteArray &writeBuf(); virtual int tryWrite(); private: //! \if _hide_doc_ class Private; Private *d; //! \endif }; // CS_EXPORT_END // CS_NAMESPACE_END #endif // CS_BYTESTREAM_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/cutestuff/cutestuff.pri000066400000000000000000000004371370065651000271700ustar00rootroot00000000000000INCLUDEPATH += $$PWD HEADERS += \ $$PWD/bytestream.h \ $$PWD/bsocket.h \ $$PWD/httpconnect.h \ $$PWD/httppoll.h \ $$PWD/socks.h SOURCES += \ $$PWD/bytestream.cpp \ $$PWD/bsocket.cpp \ $$PWD/httpconnect.cpp \ $$PWD/httppoll.cpp \ $$PWD/socks.cpp psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/cutestuff/httpconnect.cpp000066400000000000000000000226641370065651000275070ustar00rootroot00000000000000/* * httpconnect.cpp - HTTP "CONNECT" proxy * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "httpconnect.h" #include "bsocket.h" #include #include #include //#define PROX_DEBUG #ifdef PROX_DEBUG #include #endif // CS_NAMESPACE_BEGIN #ifdef PROX_DEBUG QString escapeOutput(const QByteArray &in) { QString out; for (int n = 0; n < in.size(); ++n) { if (in[n] == '\\') { out += QString("\\\\"); } else if (in[n] >= 32 && in[n] < 127) { out += QChar::fromLatin1(in[n]); } else { out += QString::asprintf("\\x%02x", (unsigned char)in[n]); } } return out; } #endif static QString extractLine(QByteArray *buf, bool *found) { // Scan for newline int index = buf->indexOf("\r\n"); if (index == -1) { // Newline not found if (found) *found = false; return ""; } else { // Found newline QString s = QString::fromLatin1(buf->left(index)); buf->remove(0, index + 2); if (found) *found = true; return s; } } static bool extractMainHeader(const QString &line, QString *proto, int *code, QString *msg) { int n = line.indexOf(' '); if (n == -1) return false; if (proto) *proto = line.mid(0, n); ++n; int n2 = line.indexOf(' ', n); if (n2 == -1) return false; if (code) *code = line.mid(n, n2 - n).toInt(); n = n2 + 1; if (msg) *msg = line.mid(n); return true; } class HttpConnect::Private { public: Private(HttpConnect *_q) : sock(_q) { } BSocket sock; QString host; int port; QString user, pass; QString real_host; int real_port; QByteArray recvBuf; bool inHeader; QStringList headerLines; int toWrite; bool active; }; HttpConnect::HttpConnect(QObject *parent) : ByteStream(parent) { d = new Private(this); connect(&d->sock, SIGNAL(connected()), SLOT(sock_connected())); connect(&d->sock, SIGNAL(connectionClosed()), SLOT(sock_connectionClosed())); connect(&d->sock, SIGNAL(delayedCloseFinished()), SLOT(sock_delayedCloseFinished())); connect(&d->sock, SIGNAL(readyRead()), SLOT(sock_readyRead())); connect(&d->sock, SIGNAL(bytesWritten(qint64)), SLOT(sock_bytesWritten(qint64))); connect(&d->sock, SIGNAL(error(int)), SLOT(sock_error(int))); resetConnection(true); } HttpConnect::~HttpConnect() { resetConnection(true); delete d; } void HttpConnect::resetConnection(bool clear) { if (d->sock.state() != BSocket::Idle) d->sock.close(); if (clear) { clearReadBuffer(); d->recvBuf.resize(0); } d->active = false; setOpenMode(QIODevice::NotOpen); } void HttpConnect::setAuth(const QString &user, const QString &pass) { d->user = user; d->pass = pass; } void HttpConnect::connectToHost(const QString &proxyHost, int proxyPort, const QString &host, int port) { resetConnection(true); d->host = proxyHost; d->port = proxyPort; d->real_host = host; d->real_port = port; #ifdef PROX_DEBUG fprintf(stderr, "HttpConnect: Connecting to %s:%d", qPrintable(proxyHost), proxyPort); if (d->user.isEmpty()) fprintf(stderr, "\n"); else fprintf(stderr, ", auth {%s,%s}\n", qPrintable(d->user), qPrintable(d->pass)); #endif d->sock.connectToHost(d->host, d->port); } void HttpConnect::close() { d->sock.close(); if (d->sock.bytesToWrite() == 0) resetConnection(); } qint64 HttpConnect::writeData(const char *data, qint64 maxSize) { if (d->active) return d->sock.write(data, maxSize); return 0; } qint64 HttpConnect::bytesToWrite() const { if (d->active) return d->sock.bytesToWrite(); else return 0; } void HttpConnect::sock_connected() { #ifdef PROX_DEBUG fprintf(stderr, "HttpConnect: Connected\n"); #endif d->inHeader = true; d->headerLines.clear(); // connected, now send the request QString s; s += QString("CONNECT ") + d->real_host + ':' + QString::number(d->real_port) + " HTTP/1.0\r\n"; if (!d->user.isEmpty()) { QString str = d->user + ':' + d->pass; s += QString("Proxy-Authorization: Basic ") + QCA::Base64().encodeString(str) + "\r\n"; } s += "Pragma: no-cache\r\n"; s += "\r\n"; QByteArray block = s.toUtf8(); #ifdef PROX_DEBUG fprintf(stderr, "HttpConnect: writing: {%s}\n", qPrintable(escapeOutput(block))); #endif d->toWrite = block.size(); d->sock.write(block); } void HttpConnect::sock_connectionClosed() { if (d->active) { resetConnection(); connectionClosed(); } else { setError(ErrProxyNeg); } } void HttpConnect::sock_delayedCloseFinished() { if (d->active) { resetConnection(); delayedCloseFinished(); } } void HttpConnect::sock_readyRead() { QByteArray block = d->sock.readAll(); if (!d->active) { d->recvBuf += block; if (d->inHeader) { // grab available lines while (1) { bool found; QString line = extractLine(&d->recvBuf, &found); if (!found) break; if (line.isEmpty()) { d->inHeader = false; break; } d->headerLines += line; } // done with grabbing the header? if (!d->inHeader) { QString str = d->headerLines.first(); d->headerLines.takeFirst(); QString proto; int code; QString msg; if (!extractMainHeader(str, &proto, &code, &msg)) { #ifdef PROX_DEBUG fprintf(stderr, "HttpConnect: invalid header!\n"); #endif resetConnection(true); setError(ErrProxyNeg); return; } else { #ifdef PROX_DEBUG fprintf(stderr, "HttpConnect: header proto=[%s] code=[%d] msg=[%s]\n", qPrintable(proto), code, qPrintable(msg)); for (QStringList::ConstIterator it = d->headerLines.begin(); it != d->headerLines.end(); ++it) fprintf(stderr, "HttpConnect: * [%s]\n", qPrintable(*it)); #endif } if (code == 200) { // OK #ifdef PROX_DEBUG fprintf(stderr, "HttpConnect: << Success >>\n"); #endif d->active = true; setOpenMode(QIODevice::ReadWrite); connected(); if (!d->recvBuf.isEmpty()) { appendRead(d->recvBuf); d->recvBuf.resize(0); readyRead(); return; } } else { int err; QString errStr; if (code == 407) { // Authentication failed err = ErrProxyAuth; errStr = tr("Authentication failed"); } else if (code == 404) { // Host not found err = ErrHostNotFound; errStr = tr("Host not found"); } else if (code == 403) { // Access denied err = ErrProxyNeg; errStr = tr("Access denied"); } else if (code == 503) { // Connection refused err = ErrConnectionRefused; errStr = tr("Connection refused"); } else { // invalid reply err = ErrProxyNeg; errStr = tr("Invalid reply"); } #ifdef PROX_DEBUG fprintf(stderr, "HttpConnect: << Error >> [%s]\n", qPrintable(errStr)); #endif resetConnection(true); setError(err); return; } } } } else { appendRead(block); readyRead(); return; } } void HttpConnect::sock_bytesWritten(qint64 x) { if (d->toWrite > 0) { int size = x; if (d->toWrite < x) size = d->toWrite; d->toWrite -= size; x -= size; } if (d->active && x > 0) bytesWritten(x); } void HttpConnect::sock_error(int x) { if (d->active) { resetConnection(); setError(ErrRead); } else { resetConnection(true); if (x == BSocket::ErrHostNotFound) setError(ErrProxyConnect); else if (x == BSocket::ErrConnectionRefused) setError(ErrProxyConnect); else if (x == BSocket::ErrRead) setError(ErrProxyNeg); } } // CS_NAMESPACE_END psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/cutestuff/httpconnect.h000066400000000000000000000033731370065651000271500ustar00rootroot00000000000000/* * httpconnect.h - HTTP "CONNECT" proxy * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef CS_HTTPCONNECT_H #define CS_HTTPCONNECT_H #include "bytestream.h" // CS_NAMESPACE_BEGIN class HttpConnect : public ByteStream { Q_OBJECT public: enum Error { ErrConnectionRefused = ErrCustom, ErrHostNotFound, ErrProxyConnect, ErrProxyNeg, ErrProxyAuth }; HttpConnect(QObject *parent = nullptr); ~HttpConnect(); void setAuth(const QString &user, const QString &pass = ""); void connectToHost(const QString &proxyHost, int proxyPort, const QString &host, int port); // from ByteStream void close(); qint64 bytesToWrite() const; protected: qint64 writeData(const char *data, qint64 maxSize); signals: void connected(); private slots: void sock_connected(); void sock_connectionClosed(); void sock_delayedCloseFinished(); void sock_readyRead(); void sock_bytesWritten(qint64); void sock_error(int); private: class Private; Private *d; void resetConnection(bool clear = false); }; // CS_NAMESPACE_END #endif // CS_HTTPCONNECT_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/cutestuff/httppoll.cpp000066400000000000000000000625101370065651000270160ustar00rootroot00000000000000/* * httppoll.cpp - HTTP polling proxy * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "httppoll.h" #include "bsocket.h" #include #include #include #include #include #include #ifdef PROX_DEBUG #include #endif #include #define POLL_KEYS 64 // CS_NAMESPACE_BEGIN static QByteArray randomArray(int size) { QByteArray a; a.resize(size); for (int n = 0; n < size; ++n) a[n] = (char)(256.0 * rand() / (RAND_MAX + 1.0)); return a; } //---------------------------------------------------------------------------- // HttpPoll //---------------------------------------------------------------------------- static QString hpk(int n, const QString &s) { if (n == 0) return s; else return QCA::Base64().arrayToString(QCA::Hash("sha1").hash(hpk(n - 1, s).toLatin1()).toByteArray()); } class HttpPoll::Private { public: Private(HttpPoll *_q) : http(_q) { } HttpProxyPost http; QString host; int port; QString user, pass; QUrl url; bool use_proxy; QByteArray out; int state; bool closing; QString ident; QTimer *t; QString key[POLL_KEYS]; int key_n; int polltime; }; HttpPoll::HttpPoll(QObject *parent) : ByteStream(parent) { d = new Private(this); d->polltime = 30; d->t = new QTimer(this); d->t->setSingleShot(true); connect(d->t, SIGNAL(timeout()), SLOT(do_sync())); connect(&d->http, SIGNAL(result()), SLOT(http_result())); connect(&d->http, SIGNAL(error(int)), SLOT(http_error(int))); resetConnection(true); } HttpPoll::~HttpPoll() { resetConnection(true); delete d->t; delete d; } QAbstractSocket *HttpPoll::abstractSocket() const { return d->http.abstractSocket(); } void HttpPoll::resetConnection(bool clear) { if (d->http.isActive()) d->http.stop(); if (clear) clearReadBuffer(); clearWriteBuffer(); d->out.resize(0); d->state = 0; d->closing = false; d->t->stop(); } void HttpPoll::setAuth(const QString &user, const QString &pass) { d->user = user; d->pass = pass; } void HttpPoll::connectToUrl(const QUrl &url) { connectToHost("", 0, url); } void HttpPoll::connectToHost(const QString &proxyHost, int proxyPort, const QUrl &url) { resetConnection(true); bool useSsl = false; d->port = 80; // using proxy? if (!proxyHost.isEmpty()) { d->host = proxyHost; d->port = proxyPort; d->url = url; d->use_proxy = true; } else { d->host = url.host(); if (url.port() != -1) d->port = url.port(); else if (url.scheme() == "https") { d->port = 443; useSsl = true; } d->url.setUrl(url.path() + "?" + url.query(QUrl::FullyEncoded), QUrl::StrictMode); d->use_proxy = false; } resetKey(); bool last; QString key = getKey(&last); #ifdef PROX_DEBUG fprintf(stderr, "HttpPoll: Connecting to %s:%d [%s]", d->host.latin1(), d->port, d->url.latin1()); if (d->user.isEmpty()) fprintf(stderr, "\n"); else fprintf(stderr, ", auth {%s,%s}\n", d->user.latin1(), d->pass.latin1()); #endif QPointer self = this; syncStarted(); if (!self) return; d->state = 1; d->http.setUseSsl(useSsl); d->http.setAuth(d->user, d->pass); d->http.post(d->host, d->port, d->url, makePacket("0", key, "", QByteArray()), d->use_proxy); } QByteArray HttpPoll::makePacket(const QString &ident, const QString &key, const QString &newkey, const QByteArray &block) { QString str = ident; if (!key.isEmpty()) { str += ';'; str += key; } if (!newkey.isEmpty()) { str += ';'; str += newkey; } str += ','; QByteArray cs = str.toLatin1(); int len = cs.length(); QByteArray a; a.resize(len + block.size()); memcpy(a.data(), cs.data(), len); memcpy(a.data() + len, block.data(), block.size()); return a; } int HttpPoll::pollInterval() const { return d->polltime; } void HttpPoll::setPollInterval(int seconds) { d->polltime = seconds; } bool HttpPoll::isOpen() const { return (d->state == 2 ? true : false); } void HttpPoll::close() { if (d->state == 0 || d->closing) return; if (bytesToWrite() == 0) resetConnection(); else d->closing = true; } void HttpPoll::http_result() { // check for death :) QPointer self = this; syncFinished(); if (!self) return; // get id and packet QString id; QString cookie = d->http.getHeader("Set-Cookie"); int n = cookie.indexOf("ID="); if (n == -1) { resetConnection(); setError(ErrRead); return; } n += 3; int n2 = cookie.indexOf(';', n); if (n2 != -1) id = cookie.mid(n, n2 - n); else id = cookie.mid(n); QByteArray block = d->http.body(); // session error? if (id.right(2) == ":0") { if (id == "0:0" && d->state == 2) { resetConnection(); connectionClosed(); return; } else { resetConnection(); setError(ErrRead); return; } } d->ident = id; bool justNowConnected = false; if (d->state == 1) { d->state = 2; justNowConnected = true; } // sync up again soon if (bytesToWrite() > 0 || !d->closing) { d->t->start(d->polltime * 1000); } // connecting if (justNowConnected) { connected(); } else { if (!d->out.isEmpty()) { int x = d->out.size(); d->out.resize(0); takeWrite(x); bytesWritten(x); } } if (!self) return; if (!block.isEmpty()) { appendRead(block); readyRead(); } if (!self) return; if (bytesToWrite() > 0) { do_sync(); } else { if (d->closing) { resetConnection(); delayedCloseFinished(); return; } } } void HttpPoll::http_error(int x) { resetConnection(); if (x == HttpProxyPost::ErrConnectionRefused) setError(ErrConnectionRefused); else if (x == HttpProxyPost::ErrHostNotFound) setError(ErrHostNotFound); else if (x == HttpProxyPost::ErrSocket) setError(ErrRead); else if (x == HttpProxyPost::ErrProxyConnect) setError(ErrProxyConnect); else if (x == HttpProxyPost::ErrProxyNeg) setError(ErrProxyNeg); else if (x == HttpProxyPost::ErrProxyAuth) setError(ErrProxyAuth); } int HttpPoll::tryWrite() { if (!d->http.isActive()) do_sync(); return 0; } void HttpPoll::do_sync() { if (d->http.isActive()) return; d->t->stop(); d->out = takeWrite(0, false); bool last; QString key = getKey(&last); QString newkey; if (last) { resetKey(); newkey = getKey(&last); } QPointer self = this; syncStarted(); if (!self) return; d->http.post(d->host, d->port, d->url, makePacket(d->ident, key, newkey, d->out), d->use_proxy); } void HttpPoll::resetKey() { #ifdef PROX_DEBUG fprintf(stderr, "HttpPoll: reset key!\n"); #endif QByteArray a = randomArray(64); QString str = QString::fromLatin1(a.data(), a.size()); d->key_n = POLL_KEYS; for (int n = 0; n < POLL_KEYS; ++n) d->key[n] = hpk(n + 1, str); } const QString &HttpPoll::getKey(bool *last) { *last = false; --(d->key_n); if (d->key_n == 0) *last = true; return d->key[d->key_n]; } //---------------------------------------------------------------------------- // HttpProxyPost //---------------------------------------------------------------------------- static QString extractLine(QByteArray *buf, bool *found) { // scan for newline int n; for (n = 0; n < (int)buf->size() - 1; ++n) { if (buf->at(n) == '\r' && buf->at(n + 1) == '\n') { QByteArray cstr; cstr.resize(n); memcpy(cstr.data(), buf->data(), n); n += 2; // hack off CR/LF memmove(buf->data(), buf->data() + n, buf->size() - n); buf->resize(buf->size() - n); QString s = QString::fromUtf8(cstr); if (found) *found = true; return s; } } if (found) *found = false; return ""; } static bool extractMainHeader(const QString &line, QString *proto, int *code, QString *msg) { int n = line.indexOf(' '); if (n == -1) return false; if (proto) *proto = line.mid(0, n); ++n; int n2 = line.indexOf(' ', n); if (n2 == -1) return false; if (code) *code = line.mid(n, n2 - n).toInt(); n = n2 + 1; if (msg) *msg = line.mid(n); return true; } class HttpProxyPost::Private { public: Private(HttpProxyPost *_q) : sock(_q), tls(nullptr) { } ~Private() { delete tls; } BSocket sock; QHostAddress lastAddress; QByteArray postdata, recvBuf, body; QUrl url; QString user, pass; bool inHeader; QStringList headerLines; bool asProxy; bool useSsl; QString host; QCA::TLS * tls; }; HttpProxyPost::HttpProxyPost(QObject *parent) : QObject(parent) { d = new Private(this); connect(&d->sock, SIGNAL(connected()), SLOT(sock_connected())); connect(&d->sock, SIGNAL(connectionClosed()), SLOT(sock_connectionClosed())); connect(&d->sock, SIGNAL(readyRead()), SLOT(sock_readyRead())); connect(&d->sock, SIGNAL(error(int)), SLOT(sock_error(int))); resetConnection(true); } HttpProxyPost::~HttpProxyPost() { resetConnection(true); delete d; } void HttpProxyPost::setUseSsl(bool state) { d->useSsl = state; } QAbstractSocket *HttpProxyPost::abstractSocket() const { return d->sock.abstractSocket(); } void HttpProxyPost::resetConnection(bool clear) { if (d->sock.state() != BSocket::Idle) d->sock.close(); d->recvBuf.resize(0); if (clear) d->body.resize(0); } void HttpProxyPost::setAuth(const QString &user, const QString &pass) { d->user = user; d->pass = pass; } bool HttpProxyPost::isActive() const { return (d->sock.state() == BSocket::Idle ? false : true); } void HttpProxyPost::post(const QString &proxyHost, int proxyPort, const QUrl &url, const QByteArray &data, bool asProxy) { resetConnection(true); d->host = proxyHost; d->url = url; d->postdata = data; d->asProxy = asProxy; #ifdef PROX_DEBUG fprintf(stderr, "HttpProxyPost: Connecting to %s:%d", proxyHost.latin1(), proxyPort); if (d->user.isEmpty()) fprintf(stderr, "\n"); else fprintf(stderr, ", auth {%s,%s}\n", d->user.latin1(), d->pass.latin1()); #endif if (d->sock.state() != QAbstractSocket::ConnectingState) { // in case of http/1.1 it may be connected if (d->lastAddress.isNull()) { d->sock.connectToHost(proxyHost, proxyPort); } else { d->sock.connectToHost(d->lastAddress, proxyPort); } } } void HttpProxyPost::stop() { resetConnection(); } QByteArray HttpProxyPost::body() const { return d->body; } QString HttpProxyPost::getHeader(const QString &var) const { for (const QString &s : d->headerLines) { int n = s.indexOf(": "); if (n == -1) continue; QString v = s.mid(0, n); if (v.toLower() == var.toLower()) return s.mid(n + 2); } return ""; } void HttpProxyPost::sock_connected() { #ifdef PROX_DEBUG fprintf(stderr, "HttpProxyPost: Connected\n"); #endif if (d->useSsl) { d->tls = new QCA::TLS(this); connect(d->tls, SIGNAL(readyRead()), SLOT(tls_readyRead())); connect(d->tls, SIGNAL(readyReadOutgoing()), SLOT(tls_readyReadOutgoing())); connect(d->tls, SIGNAL(error()), SLOT(tls_error())); d->tls->startClient(); } d->lastAddress = d->sock.peerAddress(); d->inHeader = true; d->headerLines.clear(); QUrl u = d->url; // connected, now send the request QByteArray s; s += QByteArray("POST ") + d->url.toEncoded() + " HTTP/1.1\r\n"; if (d->asProxy) { if (!d->user.isEmpty()) { QByteArray str = d->user.toUtf8() + ':' + d->pass.toUtf8(); s += QByteArray("Proxy-Authorization: Basic ") + str.toBase64() + "\r\n"; } s += "Pragma: no-cache\r\n"; s += QByteArray("Host: ") + u.host().toUtf8() + "\r\n"; } else { s += QByteArray("Host: ") + d->host.toUtf8() + "\r\n"; } s += "Content-Type: application/x-www-form-urlencoded\r\n"; s += QByteArray("Content-Length: ") + QByteArray::number(d->postdata.size()) + "\r\n"; s += "\r\n"; if (d->useSsl) { // write request d->tls->write(s); // write postdata d->tls->write(d->postdata); } else { // write request d->sock.write(s); // write postdata d->sock.write(d->postdata); } } void HttpProxyPost::sock_connectionClosed() { d->body = d->recvBuf; resetConnection(); result(); } void HttpProxyPost::tls_readyRead() { // printf("tls_readyRead\n"); processData(d->tls->read()); } void HttpProxyPost::tls_readyReadOutgoing() { // printf("tls_readyReadOutgoing\n"); d->sock.write(d->tls->readOutgoing()); } void HttpProxyPost::tls_error() { #ifdef PROX_DEBUG fprintf(stderr, "HttpProxyGetStream: ssl error: %d\n", d->tls->errorCode()); #endif resetConnection(true); error(ErrConnectionRefused); // FIXME: bogus error } void HttpProxyPost::sock_readyRead() { QByteArray block = d->sock.readAll(); if (d->useSsl) d->tls->writeIncoming(block); else processData(block); } void HttpProxyPost::processData(const QByteArray &block) { d->recvBuf += block; if (d->inHeader) { // grab available lines while (1) { bool found; QString line = extractLine(&d->recvBuf, &found); if (!found) break; if (line.isEmpty()) { d->inHeader = false; break; } d->headerLines += line; } // done with grabbing the header? if (!d->inHeader) { QString str = d->headerLines.first(); d->headerLines.takeFirst(); QString proto; int code; QString msg; if (!extractMainHeader(str, &proto, &code, &msg)) { #ifdef PROX_DEBUG fprintf(stderr, "HttpProxyPost: invalid header!\n"); #endif resetConnection(true); error(ErrProxyNeg); return; } else { #ifdef PROX_DEBUG fprintf(stderr, "HttpProxyPost: header proto=[%s] code=[%d] msg=[%s]\n", proto.latin1(), code, msg.latin1()); for (const QString &s : d->headerLines) fprintf(stderr, "HttpProxyPost: * [%s]\n", qPrintable(s)); #endif } if (code == 200) { // OK #ifdef PROX_DEBUG fprintf(stderr, "HttpProxyPost: << Success >>\n"); #endif } else { int err; QString errStr; if (code == 407) { // Authentication failed err = ErrProxyAuth; errStr = tr("Authentication failed"); } else if (code == 404) { // Host not found err = ErrHostNotFound; errStr = tr("Host not found"); } else if (code == 403) { // Access denied err = ErrProxyNeg; errStr = tr("Access denied"); } else if (code == 503) { // Connection refused err = ErrConnectionRefused; errStr = tr("Connection refused"); } else { // invalid reply err = ErrProxyNeg; errStr = tr("Invalid reply"); } #ifdef PROX_DEBUG fprintf(stderr, "HttpProxyPost: << Error >> [%s]\n", errStr.latin1()); #endif resetConnection(true); error(err); return; } } } } void HttpProxyPost::sock_error(int x) { #ifdef PROX_DEBUG fprintf(stderr, "HttpProxyPost: socket error: %d\n", x); #endif resetConnection(true); if (x == BSocket::ErrHostNotFound) error(ErrProxyConnect); else if (x == BSocket::ErrConnectionRefused) error(ErrProxyConnect); else if (x == BSocket::ErrRead) error(ErrProxyNeg); } //---------------------------------------------------------------------------- // HttpProxyGetStream //---------------------------------------------------------------------------- class HttpProxyGetStream::Private { public: Private(HttpProxyGetStream *_q) : sock(_q) { } BSocket sock; QByteArray recvBuf; QString url; QString user, pass; bool inHeader; QStringList headerLines; bool use_ssl; bool asProxy; QString host; int length; QCA::TLS *tls; }; HttpProxyGetStream::HttpProxyGetStream(QObject *parent) : QObject(parent) { d = new Private(this); d->tls = nullptr; connect(&d->sock, SIGNAL(connected()), SLOT(sock_connected())); connect(&d->sock, SIGNAL(connectionClosed()), SLOT(sock_connectionClosed())); connect(&d->sock, SIGNAL(readyRead()), SLOT(sock_readyRead())); connect(&d->sock, SIGNAL(error(int)), SLOT(sock_error(int))); resetConnection(true); } HttpProxyGetStream::~HttpProxyGetStream() { resetConnection(true); delete d; } void HttpProxyGetStream::resetConnection(bool /*clear*/) { if (d->tls) { delete d->tls; d->tls = nullptr; } if (d->sock.state() != BSocket::Idle) d->sock.close(); d->recvBuf.resize(0); // if(clear) // d->body.resize(0); d->length = -1; } void HttpProxyGetStream::setAuth(const QString &user, const QString &pass) { d->user = user; d->pass = pass; } bool HttpProxyGetStream::isActive() const { return (d->sock.state() == BSocket::Idle ? false : true); } void HttpProxyGetStream::get(const QString &proxyHost, int proxyPort, const QString &url, bool ssl, bool asProxy) { resetConnection(true); d->host = proxyHost; d->url = url; d->use_ssl = ssl; d->asProxy = asProxy; #ifdef PROX_DEBUG fprintf(stderr, "HttpProxyGetStream: Connecting to %s:%d", proxyHost.latin1(), proxyPort); if (d->user.isEmpty()) fprintf(stderr, "\n"); else fprintf(stderr, ", auth {%s,%s}\n", d->user.latin1(), d->pass.latin1()); #endif d->sock.connectToHost(proxyHost, proxyPort); } void HttpProxyGetStream::stop() { resetConnection(); } QString HttpProxyGetStream::getHeader(const QString &var) const { for (const QString &s : d->headerLines) { int n = s.indexOf(": "); if (n == -1) continue; QString v = s.mid(0, n); if (v.toLower() == var.toLower()) return s.mid(n + 2); } return ""; } int HttpProxyGetStream::length() const { return d->length; } void HttpProxyGetStream::sock_connected() { #ifdef PROX_DEBUG fprintf(stderr, "HttpProxyGetStream: Connected\n"); #endif if (d->use_ssl) { d->tls = new QCA::TLS(this); connect(d->tls, SIGNAL(readyRead()), SLOT(tls_readyRead())); connect(d->tls, SIGNAL(readyReadOutgoing()), SLOT(tls_readyReadOutgoing())); connect(d->tls, SIGNAL(error()), SLOT(tls_error())); d->tls->startClient(); } d->inHeader = true; d->headerLines.clear(); QUrl u = d->url; // connected, now send the request QString s; s += QString("GET ") + d->url + " HTTP/1.0\r\n"; if (d->asProxy) { if (!d->user.isEmpty()) { QString str = d->user + ':' + d->pass; s += QString("Proxy-Authorization: Basic ") + QCA::Base64().encodeString(str) + "\r\n"; } s += "Pragma: no-cache\r\n"; s += QString("Host: ") + u.host() + "\r\n"; } else { s += QString("Host: ") + d->host + "\r\n"; } s += "\r\n"; // write request if (d->use_ssl) d->tls->write(s.toUtf8()); else d->sock.write(s.toUtf8()); } void HttpProxyGetStream::sock_connectionClosed() { // d->body = d->recvBuf; resetConnection(); emit finished(); } void HttpProxyGetStream::sock_readyRead() { QByteArray block = d->sock.readAll(); if (d->use_ssl) d->tls->writeIncoming(block); else processData(block); } void HttpProxyGetStream::processData(const QByteArray &block) { printf("processData: %d bytes\n", block.size()); if (!d->inHeader) { emit dataReady(block); return; } d->recvBuf += block; if (d->inHeader) { // grab available lines while (1) { bool found; QString line = extractLine(&d->recvBuf, &found); if (!found) break; if (line.isEmpty()) { printf("empty line\n"); d->inHeader = false; break; } d->headerLines += line; printf("headerLine: [%s]\n", qPrintable(line)); } // done with grabbing the header? if (!d->inHeader) { QString str = d->headerLines.first(); d->headerLines.takeFirst(); QString proto; int code; QString msg; if (!extractMainHeader(str, &proto, &code, &msg)) { #ifdef PROX_DEBUG fprintf(stderr, "HttpProxyGetStream: invalid header!\n"); #endif resetConnection(true); error(ErrProxyNeg); return; } else { #ifdef PROX_DEBUG fprintf(stderr, "HttpProxyGetStream: header proto=[%s] code=[%d] msg=[%s]\n", proto.latin1(), code, msg.latin1()); for (const QString &s : d->headerLines) fprintf(stderr, "HttpProxyGetStream: * [%s]\n", qPrintable(s)); #endif } if (code == 200) { // OK #ifdef PROX_DEBUG fprintf(stderr, "HttpProxyGetStream: << Success >>\n"); #endif bool ok; int x = getHeader("Content-Length").toInt(&ok); if (ok) d->length = x; QPointer self = this; emit handshaken(); if (!self) return; } else { int err; QString errStr; if (code == 407) { // Authentication failed err = ErrProxyAuth; errStr = tr("Authentication failed"); } else if (code == 404) { // Host not found err = ErrHostNotFound; errStr = tr("Host not found"); } else if (code == 403) { // Access denied err = ErrProxyNeg; errStr = tr("Access denied"); } else if (code == 503) { // Connection refused err = ErrConnectionRefused; errStr = tr("Connection refused"); } else { // invalid reply err = ErrProxyNeg; errStr = tr("Invalid reply"); } #ifdef PROX_DEBUG fprintf(stderr, "HttpProxyGetStream: << Error >> [%s]\n", errStr.latin1()); #endif resetConnection(true); error(err); return; } if (!d->recvBuf.isEmpty()) { QByteArray a = d->recvBuf; d->recvBuf.clear(); emit dataReady(a); } } } } void HttpProxyGetStream::sock_error(int x) { #ifdef PROX_DEBUG fprintf(stderr, "HttpProxyGetStream: socket error: %d\n", x); #endif resetConnection(true); if (x == BSocket::ErrHostNotFound) error(ErrProxyConnect); else if (x == BSocket::ErrConnectionRefused) error(ErrProxyConnect); else if (x == BSocket::ErrRead) error(ErrProxyNeg); } void HttpProxyGetStream::tls_readyRead() { // printf("tls_readyRead\n"); processData(d->tls->read()); } void HttpProxyGetStream::tls_readyReadOutgoing() { // printf("tls_readyReadOutgoing\n"); d->sock.write(d->tls->readOutgoing()); } void HttpProxyGetStream::tls_error() { #ifdef PROX_DEBUG fprintf(stderr, "HttpProxyGetStream: ssl error: %d\n", d->tls->errorCode()); #endif resetConnection(true); error(ErrConnectionRefused); // FIXME: bogus error } // CS_NAMESPACE_END psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/cutestuff/httppoll.h000066400000000000000000000100031370065651000264510ustar00rootroot00000000000000/* * httppoll.h - HTTP polling proxy * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef CS_HTTPPOLL_H #define CS_HTTPPOLL_H #include "bytestream.h" class QUrl; // CS_NAMESPACE_BEGIN class HttpPoll : public ByteStream { Q_OBJECT public: enum Error { ErrConnectionRefused = ErrCustom, ErrHostNotFound, ErrProxyConnect, ErrProxyNeg, ErrProxyAuth }; HttpPoll(QObject *parent = nullptr); ~HttpPoll(); virtual QAbstractSocket *abstractSocket() const; void setAuth(const QString &user, const QString &pass = ""); void connectToUrl(const QUrl &url); void connectToHost(const QString &proxyHost, int proxyPort, const QUrl &url); int pollInterval() const; void setPollInterval(int seconds); // from ByteStream bool isOpen() const; void close(); signals: void connected(); void syncStarted(); void syncFinished(); protected: int tryWrite(); private slots: void http_result(); void http_error(int); void do_sync(); private: class Private; Private *d; void resetConnection(bool clear = false); QByteArray makePacket(const QString &ident, const QString &key, const QString &newkey, const QByteArray &block); void resetKey(); const QString &getKey(bool *); }; class HttpProxyPost : public QObject { Q_OBJECT public: enum Error { ErrConnectionRefused, ErrHostNotFound, ErrSocket, ErrProxyConnect, ErrProxyNeg, ErrProxyAuth }; HttpProxyPost(QObject *parent = nullptr); ~HttpProxyPost(); QAbstractSocket *abstractSocket() const; void setUseSsl(bool state); void setAuth(const QString &user, const QString &pass = ""); bool isActive() const; void post(const QString &proxyHost, int proxyPort, const QUrl &url, const QByteArray &data, bool asProxy = true); void stop(); QByteArray body() const; QString getHeader(const QString &) const; signals: void result(); void error(int); private slots: void sock_connected(); void sock_connectionClosed(); void sock_readyRead(); void sock_error(int); void tls_readyRead(); void tls_readyReadOutgoing(); void tls_error(); private: class Private; Private *d; void resetConnection(bool clear = false); void processData(const QByteArray &block); }; class HttpProxyGetStream : public QObject { Q_OBJECT public: enum Error { ErrConnectionRefused, ErrHostNotFound, ErrSocket, ErrProxyConnect, ErrProxyNeg, ErrProxyAuth }; HttpProxyGetStream(QObject *parent = nullptr); ~HttpProxyGetStream(); void setAuth(const QString &user, const QString &pass = ""); bool isActive() const; void get(const QString &proxyHost, int proxyPort, const QString &url, bool ssl = false, bool asProxy = false); void stop(); QString getHeader(const QString &) const; int length() const; // -1 for unknown signals: void handshaken(); void dataReady(const QByteArray &buf); void finished(); void error(int); private slots: void sock_connected(); void sock_connectionClosed(); void sock_readyRead(); void sock_error(int); void tls_readyRead(); void tls_readyReadOutgoing(); void tls_error(); private: class Private; Private *d; void resetConnection(bool clear = false); void processData(const QByteArray &block); }; // CS_NAMESPACE_END #endif // CS_HTTPPOLL_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/cutestuff/socks.cpp000066400000000000000000000656211370065651000263000ustar00rootroot00000000000000/* * socks.cpp - SOCKS5 TCP proxy client/server * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "socks.h" #include "bsocket.h" #include #include #include #include #include #include //#define PROX_DEBUG #ifdef PROX_DEBUG #include #endif #ifdef Q_OS_UNIX #include #include #include #include #endif #ifdef Q_OS_WIN32 #include #endif // CS_NAMESPACE_BEGIN //---------------------------------------------------------------------------- // SocksUDP //---------------------------------------------------------------------------- class SocksUDP::Private { public: QUdpSocket * sd; SocksClient *sc; QHostAddress routeAddr; int routePort; QString host; int port; }; SocksUDP::SocksUDP(SocksClient *sc, const QString &host, int port, const QHostAddress &routeAddr, int routePort) : QObject(sc) { d = new Private; d->sc = sc; d->sd = new QUdpSocket(this); connect(d->sd, SIGNAL(readyRead()), SLOT(sd_readyRead())); d->host = host; d->port = port; d->routeAddr = routeAddr; d->routePort = routePort; } SocksUDP::~SocksUDP() { delete d->sd; delete d; } void SocksUDP::change(const QString &host, int port) { d->host = host; d->port = port; } void SocksUDP::write(const QByteArray &data) { d->sd->writeDatagram(data, d->routeAddr, d->routePort); } void SocksUDP::sd_activated() { while (d->sd->hasPendingDatagrams()) { QByteArray datagram; datagram.resize(d->sd->pendingDatagramSize()); d->sd->readDatagram(datagram.data(), datagram.size()); packetReady(datagram); } } //---------------------------------------------------------------------------- // SocksClient //---------------------------------------------------------------------------- #define REQ_CONNECT 0x01 #define REQ_BIND 0x02 #define REQ_UDPASSOCIATE 0x03 #define RET_SUCCESS 0x00 #define RET_UNREACHABLE 0x04 #define RET_CONNREFUSED 0x05 // spc = socks packet client // sps = socks packet server // SPCS = socks packet client struct // SPSS = socks packet server struct // Version static QByteArray spc_set_version(bool hasCreds) { QByteArray ver; ver.resize(hasCreds ? 4 : 3); ver[0] = 0x05; // socks version 5 ver[2] = 0x00; // no-auth if (hasCreds) { ver[1] = 0x02; // number of methods ver[3] = 0x02; // username } else { ver[1] = 0x01; // number of methods } return ver; } static QByteArray sps_set_version(int method) { QByteArray ver; ver.resize(2); ver[0] = 0x05; ver[1] = method; return ver; } struct SPCS_VERSION { unsigned char version; QByteArray methodList; }; static int spc_get_version(QByteArray &from, SPCS_VERSION *s) { if (from.size() < 1) return 0; if (from.at(0) != 0x05) // only SOCKS5 supported return -1; if (from.size() < 2) return 0; unsigned char mlen = from.at(1); int num = mlen; if (num > 16) // who the heck has over 16 auth methods?? return -1; if (from.size() < 2 + num) return 0; QByteArray a = ByteStream::takeArray(from, 2 + num); s->version = a[0]; s->methodList.resize(num); memcpy(s->methodList.data(), a.data() + 2, num); return 1; } struct SPSS_VERSION { unsigned char version; unsigned char method; }; static int sps_get_version(QByteArray &from, SPSS_VERSION *s) { if (from.size() < 2) return 0; QByteArray a = ByteStream::takeArray(from, 2); s->version = a[0]; s->method = a[1]; return 1; } // authUsername static QByteArray spc_set_authUsername(const QByteArray &user, const QByteArray &pass) { int len1 = user.length(); int len2 = pass.length(); if (len1 > 255) len1 = 255; if (len2 > 255) len2 = 255; QByteArray a; a.resize(1 + 1 + len1 + 1 + len2); a[0] = 0x01; // username auth version 1 a[1] = len1; memcpy(a.data() + 2, user.data(), len1); a[2 + len1] = len2; memcpy(a.data() + 3 + len1, pass.data(), len2); return a; } static QByteArray sps_set_authUsername(bool success) { QByteArray a; a.resize(2); a[0] = 0x01; a[1] = success ? 0x00 : 0xff; return a; } struct SPCS_AUTHUSERNAME { QString user, pass; }; static int spc_get_authUsername(QByteArray &from, SPCS_AUTHUSERNAME *s) { if (from.size() < 1) return 0; unsigned char ver = from.at(0); if (ver != 0x01) return -1; if (from.size() < 2) return 0; unsigned char ulen = from.at(1); if ((int)from.size() < ulen + 3) return 0; unsigned char plen = from.at(ulen + 2); if ((int)from.size() < ulen + plen + 3) return 0; QByteArray a = ByteStream::takeArray(from, ulen + plen + 3); QByteArray user, pass; user.resize(ulen); pass.resize(plen); memcpy(user.data(), a.data() + 2, ulen); memcpy(pass.data(), a.data() + ulen + 3, plen); s->user = QString::fromUtf8(user); s->pass = QString::fromUtf8(pass); return 1; } struct SPSS_AUTHUSERNAME { unsigned char version; bool success; }; static int sps_get_authUsername(QByteArray &from, SPSS_AUTHUSERNAME *s) { if (from.size() < 2) return 0; QByteArray a = ByteStream::takeArray(from, 2); s->version = a[0]; s->success = ((char)a[1] == 0 ? true : false); return 1; } // connectRequest static QByteArray sp_set_request(const QHostAddress &addr, unsigned short port, unsigned char cmd1) { int at = 0; QByteArray a; a.resize(4); a[at++] = 0x05; // socks version 5 a[at++] = cmd1; a[at++] = 0x00; // reserved if (addr.protocol() == QAbstractSocket::IPv4Protocol || addr.protocol() == QAbstractSocket::UnknownNetworkLayerProtocol) { a[at++] = 0x01; // address type = ipv4 quint32 ip4 = htonl(addr.toIPv4Address()); a.resize(at + 4); memcpy(a.data() + at, &ip4, 4); at += 4; } else { a[at++] = 0x04; Q_IPV6ADDR ip6 = addr.toIPv6Address(); a.resize(at + 16); for (int i = 0; i < 16; ++i) a[at++] = ip6[i]; } // port a.resize(at + 2); quint16 p = htons(port); memcpy(a.data() + at, &p, 2); return a; } static QByteArray sp_set_request(const QString &host, quint16 port, unsigned char cmd1) { // detect for IP addresses QHostAddress addr; if (addr.setAddress(host)) return sp_set_request(addr, port, cmd1); QByteArray h = host.toUtf8(); h.truncate(255); h = QString::fromUtf8(h).toUtf8(); // delete any partial characters? int hlen = h.length(); int at = 0; QByteArray a; a.resize(4); a[at++] = 0x05; // socks version 5 a[at++] = cmd1; a[at++] = 0x00; // reserved a[at++] = 0x03; // address type = domain // host a.resize(at + hlen + 1); a[at++] = hlen; memcpy(a.data() + at, h.data(), hlen); at += hlen; // port a.resize(at + 2); unsigned short p = htons(port); memcpy(a.data() + at, &p, 2); return a; } struct SPS_CONNREQ { unsigned char version; unsigned char cmd; int address_type; QString host; QHostAddress addr; quint16 port; }; static int sp_get_request(QByteArray &from, SPS_CONNREQ *s) { int full_len = 4; if ((int)from.size() < full_len) return 0; QString host; QHostAddress addr; unsigned char atype = from.at(3); if (atype == 0x01) { full_len += 4; if ((int)from.size() < full_len) return 0; quint32 ip4; memcpy(&ip4, from.data() + 4, 4); addr.setAddress(ntohl(ip4)); } else if (atype == 0x03) { ++full_len; if ((int)from.size() < full_len) return 0; unsigned char host_len = from.at(4); full_len += host_len; if ((int)from.size() < full_len) return 0; QByteArray cs; cs.resize(host_len); memcpy(cs.data(), from.data() + 5, host_len); host = QString::fromLatin1(cs); } else if (atype == 0x04) { full_len += 16; if ((int)from.size() < full_len) return 0; quint8 a6[16]; memcpy(a6, from.data() + 4, 16); addr.setAddress(a6); } full_len += 2; if ((int)from.size() < full_len) return 0; QByteArray a = ByteStream::takeArray(from, full_len); quint16 p; memcpy(&p, a.data() + full_len - 2, 2); s->version = a[0]; s->cmd = a[1]; s->address_type = atype; s->host = host; s->addr = addr; s->port = ntohs(p); return 1; } enum { StepVersion, StepAuth, StepRequest }; class SocksClient::Private { public: Private(SocksClient *_q) : sock(_q) { } BSocket sock; QString host; int port; QString user, pass; QString real_host; int real_port; QByteArray recvBuf; int step; int authMethod; bool incoming, waiting; QString rhost; int rport; int pending; bool udp; QString udpAddr; int udpPort; }; SocksClient::SocksClient(QObject *parent) : ByteStream(parent) { init(); d->incoming = false; } SocksClient::SocksClient(QTcpSocket *s, QObject *parent) : ByteStream(parent) { init(); d->incoming = true; d->waiting = true; d->sock.setSocket(s); } void SocksClient::init() { d = new Private(this); connect(&d->sock, SIGNAL(connected()), SLOT(sock_connected())); connect(&d->sock, SIGNAL(connectionClosed()), SLOT(sock_connectionClosed())); connect(&d->sock, SIGNAL(delayedCloseFinished()), SLOT(sock_delayedCloseFinished())); connect(&d->sock, SIGNAL(readyRead()), SLOT(sock_readyRead())); connect(&d->sock, SIGNAL(bytesWritten(qint64)), SLOT(sock_bytesWritten(qint64))); connect(&d->sock, SIGNAL(error(int)), SLOT(sock_error(int))); resetConnection(true); } SocksClient::~SocksClient() { resetConnection(true); delete d; } QAbstractSocket *SocksClient::abstractSocket() const { return d->sock.abstractSocket(); } void SocksClient::resetConnection(bool clear) { if (d->sock.state() != BSocket::Idle) d->sock.close(); if (clear) clearReadBuffer(); d->recvBuf.resize(0); d->waiting = false; d->udp = false; d->pending = 0; if (bytesAvailable()) { setOpenMode(QIODevice::ReadOnly); } else { setOpenMode(QIODevice::NotOpen); } } bool SocksClient::isIncoming() const { return d->incoming; } void SocksClient::setAuth(const QString &user, const QString &pass) { d->user = user; d->pass = pass; } void SocksClient::connectToHost(const QString &proxyHost, int proxyPort, const QString &host, int port, bool udpMode) { resetConnection(true); d->host = proxyHost; d->port = proxyPort; d->real_host = host; d->real_port = port; d->udp = udpMode; #ifdef PROX_DEBUG fprintf(stderr, "SocksClient: Connecting to %s:%d", qPrintable(proxyHost), proxyPort); if (d->user.isEmpty()) fprintf(stderr, "\n"); else fprintf(stderr, ", auth {%s,%s}\n", qPrintable(d->user), qPrintable(d->pass)); #endif d->sock.connectToHost(d->host, d->port); } void SocksClient::close() { d->sock.close(); if (d->sock.bytesToWrite() == 0) resetConnection(); } void SocksClient::writeData(const QByteArray &buf) { #ifdef PROX_DEBUG // show hex fprintf(stderr, "SocksClient: client write { "); for (int n = 0; n < (int)buf.size(); ++n) fprintf(stderr, "%02X ", (unsigned char)buf[n]); fprintf(stderr, " } \n"); #endif d->pending += buf.size(); d->sock.write(buf); } qint64 SocksClient::writeData(const char *data, qint64 maxSize) { if (isOpen() && !d->udp) return d->sock.write(data, maxSize); return 0; } qint64 SocksClient::readData(char *data, qint64 maxSize) { qint64 ret = ByteStream::readData(data, maxSize); if (d->sock.state() != BSocket::Connected && !bytesAvailable()) { setOpenMode(QIODevice::NotOpen); } return ret; } qint64 SocksClient::bytesAvailable() const { return ByteStream::bytesAvailable(); } qint64 SocksClient::bytesToWrite() const { if (isOpen()) return d->sock.bytesToWrite(); else return 0; } void SocksClient::sock_connected() { #ifdef PROX_DEBUG fprintf(stderr, "SocksClient: Connected\n"); #endif d->step = StepVersion; writeData(spc_set_version(!d->user.isEmpty())); // fixme requirement for auth should set outside } void SocksClient::sock_connectionClosed() { if (isOpen()) { resetConnection(); emit connectionClosed(); } else { setError(ErrProxyNeg); } } void SocksClient::sock_delayedCloseFinished() { if (isOpen()) { resetConnection(); delayedCloseFinished(); } } void SocksClient::sock_readyRead() { QByteArray block = d->sock.readAll(); // qDebug() << this << "::sock_readyRead " << block.size() << " bytes." << // "udp=" << d->udp << openMode(); if (!isOpen()) { if (d->incoming) processIncoming(block); else processOutgoing(block); } else { if (!d->udp) { appendRead(block); emit readyRead(); } } } void SocksClient::processOutgoing(const QByteArray &block) { #ifdef PROX_DEBUG // show hex fprintf(stderr, "SocksClient: client recv { "); for (int n = 0; n < (int)block.size(); ++n) fprintf(stderr, "%02X ", (unsigned char)block[n]); fprintf(stderr, " } \n"); #endif d->recvBuf += block; if (d->step == StepVersion) { SPSS_VERSION s; int r = sps_get_version(d->recvBuf, &s); if (r == -1) { resetConnection(true); setError(ErrProxyNeg); return; } else if (r == 1) { if (s.version != 0x05 || s.method == 0xff) { #ifdef PROX_DEBUG fprintf(stderr, "SocksClient: Method selection failed\n"); #endif resetConnection(true); setError(ErrProxyNeg); return; } QString str; if (s.method == 0x00) { str = "None"; d->authMethod = AuthNone; } else if (s.method == 0x02) { str = "Username/Password"; d->authMethod = AuthUsername; } else { #ifdef PROX_DEBUG fprintf(stderr, "SocksClient: Server wants to use unknown method '%02x'\n", s.method); #endif resetConnection(true); setError(ErrProxyNeg); return; } if (d->authMethod == AuthNone) { // no auth, go straight to the request do_request(); } else if (d->authMethod == AuthUsername) { d->step = StepAuth; #ifdef PROX_DEBUG fprintf(stderr, "SocksClient: Authenticating [Username] ...\n"); #endif writeData(spc_set_authUsername(d->user.toLatin1(), d->pass.toLatin1())); } } } if (d->step == StepAuth) { if (d->authMethod == AuthUsername) { SPSS_AUTHUSERNAME s; int r = sps_get_authUsername(d->recvBuf, &s); if (r == -1) { resetConnection(true); setError(ErrProxyNeg); return; } else if (r == 1) { if (s.version != 0x01) { resetConnection(true); setError(ErrProxyNeg); return; } if (!s.success) { resetConnection(true); setError(ErrProxyAuth); return; } do_request(); } } } else if (d->step == StepRequest) { SPS_CONNREQ s; int r = sp_get_request(d->recvBuf, &s); if (r == -1) { resetConnection(true); setError(ErrProxyNeg); return; } else if (r == 1) { if (s.cmd != RET_SUCCESS) { #ifdef PROX_DEBUG fprintf(stderr, "SocksClient: client << Error >> [%02x]\n", s.cmd); #endif resetConnection(true); if (s.cmd == RET_UNREACHABLE) setError(ErrHostNotFound); else if (s.cmd == RET_CONNREFUSED) setError(ErrConnectionRefused); else setError(ErrProxyNeg); return; } #ifdef PROX_DEBUG fprintf(stderr, "SocksClient: client << Success >>\n"); #endif if (d->udp) { if (s.address_type == 0x03) d->udpAddr = s.host; else d->udpAddr = s.addr.toString(); d->udpPort = s.port; } setOpenMode(QIODevice::ReadWrite); QPointer self = this; setOpenMode(QIODevice::ReadWrite); emit connected(); if (!self) return; if (!d->recvBuf.isEmpty()) { appendRead(d->recvBuf); d->recvBuf.resize(0); readyRead(); } } } } void SocksClient::do_request() { #ifdef PROX_DEBUG fprintf(stderr, "SocksClient: Requesting ...\n"); #endif d->step = StepRequest; int cmd = d->udp ? REQ_UDPASSOCIATE : REQ_CONNECT; QByteArray buf; if (!d->real_host.isEmpty()) buf = sp_set_request(d->real_host, d->real_port, cmd); else buf = sp_set_request(QHostAddress(), 0, cmd); writeData(buf); } void SocksClient::sock_bytesWritten(qint64 x) { int bytes = x; if (d->pending >= bytes) { d->pending -= bytes; bytes = 0; } else { bytes -= d->pending; d->pending = 0; } if (bytes > 0) bytesWritten(bytes); } void SocksClient::sock_error(int x) { if (isOpen()) { resetConnection(); setError(ErrRead); } else { resetConnection(true); if (x == BSocket::ErrHostNotFound) setError(ErrProxyConnect); else if (x == BSocket::ErrConnectionRefused) setError(ErrProxyConnect); else if (x == BSocket::ErrRead) setError(ErrProxyNeg); } } void SocksClient::serve() { d->waiting = false; d->step = StepVersion; continueIncoming(); } void SocksClient::processIncoming(const QByteArray &block) { #ifdef PROX_DEBUG // show hex fprintf(stderr, "SocksClient: server recv { "); for (int n = 0; n < (int)block.size(); ++n) fprintf(stderr, "%02X ", (unsigned char)block[n]); fprintf(stderr, " } \n"); #endif d->recvBuf += block; if (!d->waiting) continueIncoming(); } void SocksClient::continueIncoming() { if (d->recvBuf.isEmpty()) return; if (d->step == StepVersion) { SPCS_VERSION s; int r = spc_get_version(d->recvBuf, &s); if (r == -1) { resetConnection(true); setError(ErrProxyNeg); return; } else if (r == 1) { if (s.version != 0x05) { resetConnection(true); setError(ErrProxyNeg); return; } int methods = 0; for (int n = 0; n < (int)s.methodList.size(); ++n) { unsigned char c = s.methodList[n]; if (c == 0x00) methods |= AuthNone; else if (c == 0x02) methods |= AuthUsername; } d->waiting = true; emit incomingMethods(methods); } } else if (d->step == StepAuth) { SPCS_AUTHUSERNAME s; int r = spc_get_authUsername(d->recvBuf, &s); if (r == -1) { resetConnection(true); setError(ErrProxyNeg); return; } else if (r == 1) { d->waiting = true; incomingAuth(s.user, s.pass); } } else if (d->step == StepRequest) { SPS_CONNREQ s; int r = sp_get_request(d->recvBuf, &s); if (r == -1) { resetConnection(true); setError(ErrProxyNeg); return; } else if (r == 1) { d->waiting = true; if (s.cmd == REQ_CONNECT) { if (!s.host.isEmpty()) d->rhost = s.host; else d->rhost = s.addr.toString(); d->rport = s.port; QIODevice::open(QIODevice::ReadWrite); incomingConnectRequest(d->rhost, d->rport); } else if (s.cmd == REQ_UDPASSOCIATE) { incomingUDPAssociateRequest(); } else { requestDeny(); return; } } } } void SocksClient::chooseMethod(int method) { if (d->step != StepVersion || !d->waiting) return; unsigned char c; if (method == AuthNone) { d->step = StepRequest; c = 0x00; } else { d->step = StepAuth; c = 0x02; } // version response d->waiting = false; writeData(sps_set_version(c)); continueIncoming(); } void SocksClient::authGrant(bool b) { if (d->step != StepAuth || !d->waiting) return; if (b) d->step = StepRequest; // auth response d->waiting = false; writeData(sps_set_authUsername(b)); if (!b) { resetConnection(true); return; } continueIncoming(); } void SocksClient::requestDeny() { if (d->step != StepRequest || !d->waiting) return; // response d->waiting = false; writeData(sp_set_request(d->rhost, d->rport, RET_UNREACHABLE)); resetConnection(true); } void SocksClient::grantConnect() { if (d->step != StepRequest || !d->waiting) return; // response d->waiting = false; writeData(sp_set_request(d->rhost, d->rport, RET_SUCCESS)); setOpenMode(QIODevice::ReadWrite); #ifdef PROX_DEBUG fprintf(stderr, "SocksClient: server << Success >>\n"); #endif if (!d->recvBuf.isEmpty()) { appendRead(d->recvBuf); d->recvBuf.resize(0); readyRead(); } } void SocksClient::grantUDPAssociate(const QString &relayHost, int relayPort) { if (d->step != StepRequest || !d->waiting) return; // response d->waiting = false; writeData(sp_set_request(relayHost, relayPort, RET_SUCCESS)); d->udp = true; setOpenMode(QIODevice::ReadWrite); #ifdef PROX_DEBUG fprintf(stderr, "SocksClient: server << Success >>\n"); #endif if (!d->recvBuf.isEmpty()) d->recvBuf.resize(0); } QHostAddress SocksClient::peerAddress() const { return d->sock.peerAddress(); } quint16 SocksClient::peerPort() const { return d->sock.peerPort(); } QString SocksClient::udpAddress() const { return d->udpAddr; } quint16 SocksClient::udpPort() const { return d->udpPort; } SocksUDP *SocksClient::createUDP(const QString &host, int port, const QHostAddress &routeAddr, int routePort) { return new SocksUDP(this, host, port, routeAddr, routePort); } //---------------------------------------------------------------------------- // SocksServer //---------------------------------------------------------------------------- class SocksServer::Private { public: QTcpServer * serv = nullptr; QList incomingConns; QUdpSocket * sd = nullptr; }; SocksServer::SocksServer(QObject *parent) : QObject(parent) { d = new Private; } SocksServer::~SocksServer() { stop(); while (d->incomingConns.count()) { delete d->incomingConns.takeFirst(); } delete d; } void SocksServer::setServerSocket(QTcpServer *server) { d->serv = server; connect(d->serv, SIGNAL(newConnection()), SLOT(newConnection())); } bool SocksServer::isActive() const { return d->serv->isListening(); } bool SocksServer::listen(quint16 port, bool udp) { stop(); if (!d->serv) { setServerSocket(new QTcpServer(this)); } if (!d->serv->listen(QHostAddress::Any, port)) return false; if (udp) { d->sd = new QUdpSocket(this); if (!d->sd->bind(QHostAddress::LocalHost, port)) { delete d->sd; d->sd = nullptr; delete d->serv; d->serv = nullptr; return false; } connect(d->sd, SIGNAL(readyRead()), SLOT(sd_activated())); } return true; } void SocksServer::stop() { delete d->sd; d->sd = nullptr; delete d->serv; d->serv = nullptr; } int SocksServer::port() const { return d->serv ? d->serv->serverPort() : 0; } QHostAddress SocksServer::address() const { return d->serv ? d->serv->serverAddress() : QHostAddress(); } SocksClient *SocksServer::takeIncoming() { if (d->incomingConns.isEmpty()) return nullptr; SocksClient *c = d->incomingConns.takeFirst(); // we don't care about errors anymore disconnect(c, SIGNAL(error(int)), this, SLOT(connectionError())); // don't serve the connection until the event loop, to give the caller a chance to map signals QTimer::singleShot(0, c, SLOT(serve())); return c; } void SocksServer::writeUDP(const QHostAddress &addr, int port, const QByteArray &data) { if (d->sd) { d->sd->writeDatagram(data.data(), data.size(), addr, port); } } void SocksServer::newConnection() { SocksClient *c = new SocksClient(d->serv->nextPendingConnection(), this); connect(c, SIGNAL(error(int)), this, SLOT(connectionError())); d->incomingConns.append(c); incomingReady(); } void SocksServer::connectionError() { SocksClient *c = static_cast(sender()); d->incomingConns.removeAll(c); c->deleteLater(); } void SocksServer::sd_activated() { while (d->sd->hasPendingDatagrams()) { QByteArray datagram(d->sd->pendingDatagramSize(), Qt::Uninitialized); QHostAddress sender; quint16 senderPort; auto sz = d->sd->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort); if (sz >= 0) { datagram.truncate(sz); incomingUDP(sender.toString(), senderPort, d->sd->peerAddress(), d->sd->peerPort(), datagram); } } } // CS_NAMESPACE_END psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/cutestuff/socks.h000066400000000000000000000104321370065651000257330ustar00rootroot00000000000000/* * socks.h - SOCKS5 TCP proxy client/server * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef CS_SOCKS_H #define CS_SOCKS_H #include "bytestream.h" // CS_NAMESPACE_BEGIN class QHostAddress; class QTcpServer; class QTcpSocket; class SocksClient; class SocksServer; class SocksUDP : public QObject { Q_OBJECT public: ~SocksUDP(); void change(const QString &host, int port); void write(const QByteArray &data); signals: void packetReady(const QByteArray &data); private slots: void sd_activated(); private: class Private; Private *d; friend class SocksClient; SocksUDP(SocksClient *sc, const QString &host, int port, const QHostAddress &routeAddr, int routePort); }; class SocksClient : public ByteStream { Q_OBJECT public: enum Error { ErrConnectionRefused = ErrCustom, ErrHostNotFound, ErrProxyConnect, ErrProxyNeg, ErrProxyAuth }; enum Method { AuthNone = 0x0001, AuthUsername = 0x0002 }; enum Request { ReqConnect, ReqUDPAssociate }; SocksClient(QObject *parent = nullptr); SocksClient(QTcpSocket *, QObject *parent = nullptr); ~SocksClient(); virtual QAbstractSocket *abstractSocket() const; bool isIncoming() const; // outgoing void setAuth(const QString &user, const QString &pass = ""); void connectToHost(const QString &proxyHost, int proxyPort, const QString &host, int port, bool udpMode = false); // incoming void chooseMethod(int); void authGrant(bool); void requestDeny(); void grantConnect(); void grantUDPAssociate(const QString &relayHost, int relayPort); // from ByteStream void close(); qint64 bytesAvailable() const; qint64 bytesToWrite() const; // remote address QHostAddress peerAddress() const; quint16 peerPort() const; // udp QString udpAddress() const; quint16 udpPort() const; SocksUDP *createUDP(const QString &host, int port, const QHostAddress &routeAddr, int routePort); protected: qint64 writeData(const char *data, qint64 maxSize); qint64 readData(char *data, qint64 maxSize); signals: // outgoing void connected(); // incoming void incomingMethods(int); void incomingAuth(const QString &user, const QString &pass); void incomingConnectRequest(const QString &host, int port); void incomingUDPAssociateRequest(); private slots: void sock_connected(); void sock_connectionClosed(); void sock_delayedCloseFinished(); void sock_readyRead(); void sock_bytesWritten(qint64); void sock_error(int); void serve(); private: class Private; Private *d; void init(); void resetConnection(bool clear = false); void do_request(); void processOutgoing(const QByteArray &); void processIncoming(const QByteArray &); void continueIncoming(); void writeData(const QByteArray &a); }; class SocksServer : public QObject { Q_OBJECT public: SocksServer(QObject *parent = nullptr); ~SocksServer(); void setServerSocket(QTcpServer *server); bool isActive() const; bool listen(quint16 port, bool udp = false); void stop(); int port() const; QHostAddress address() const; SocksClient *takeIncoming(); void writeUDP(const QHostAddress &addr, int port, const QByteArray &data); signals: void incomingReady(); void incomingUDP(const QString &host, int port, const QHostAddress &addr, int sourcePort, const QByteArray &data); private slots: void newConnection(); void connectionError(); void sd_activated(); private: class Private; Private *d; }; // CS_NAMESPACE_END #endif // CS_SOCKS_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/ice176.cpp000066400000000000000000002016571370065651000241450ustar00rootroot00000000000000/* * Copyright (C) 2009-2010 Barracuda Networks, Inc. * Copyright (C) 2020 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "ice176.h" #include "iceagent.h" #include "icecomponent.h" #include "icelocaltransport.h" #include "iceturntransport.h" #include "stunbinding.h" #include "stunmessage.h" #include "stuntransaction.h" #include "stuntypes.h" #include "udpportreserver.h" #include #include #include #include #include #include #define ICE_DEBUG #ifdef ICE_DEBUG #define iceDebug qDebug #else #define iceDebug(...) #endif namespace XMPP { enum { Direct, Relayed }; static qint64 calc_pair_priority(int a, int b) { qint64 priority = ((qint64)1 << 32) * qMin(a, b); priority += (qint64)2 * qMax(a, b); if (a > b) ++priority; return priority; } // scope values: 0 = local, 1 = link-local, 2 = private, 3 = public // FIXME: dry (this is in psi avcall also) static int getAddressScope(const QHostAddress &a) { if (a.protocol() == QAbstractSocket::IPv6Protocol) { if (a == QHostAddress(QHostAddress::LocalHostIPv6)) return 0; else if (XMPP::Ice176::isIPv6LinkLocalAddress(a)) return 1; } else if (a.protocol() == QAbstractSocket::IPv4Protocol) { quint32 v4 = a.toIPv4Address(); quint8 a0 = v4 >> 24; quint8 a1 = (v4 >> 16) & 0xff; if (a0 == 127) return 0; else if (a0 == 169 && a1 == 254) return 1; else if (a0 == 10) return 2; else if (a0 == 172 && a1 >= 16 && a1 <= 31) return 2; else if (a0 == 192 && a1 == 168) return 2; } return 3; } class Ice176::Private : public QObject { Q_OBJECT public: // note, Nominating state is skipped when aggressive nomination is enabled. enum State { Stopped, Starting, // preparing local candidates right after start() call Started, // local candidates ready. ready for pairing with remote Active, // all components have a nominated pair and media transferred over them Stopping // when received a command from the user to stop }; enum CandidatePairState { PWaiting, PInProgress, PSucceeded, PFailed, PFrozen }; enum CheckListState { LRunning, LCompleted, LFailed }; class CandidatePair { public: using Ptr = QSharedPointer; IceComponent::CandidateInfo::Ptr local, remote; bool isDefault = false; // not used in xmpp bool isValid = false; // a pair which is also in valid list bool isNominated = false; // states for last or comming checks bool isTriggered = false; // last scheduled check was a triggered check bool isTriggeredForNominated = false; bool finalNomination = false; #ifdef ICE_DEBUG bool logNew = false; #endif CandidatePairState state = CandidatePairState::PFrozen; qint64 priority = 0; QString foundation; // rfc8445 6.1.2.6 (combination of foundations) StunBinding *binding = nullptr; // FIXME: this is wrong i think, it should be in LocalTransport // or such, to multiplex ids StunTransactionPool::Ptr pool; inline bool isNull() const { return local->addr.addr.isNull() || remote->addr.addr.isNull(); } inline operator QString() const { if (isNull()) return QLatin1String("null pair"); return QString(QLatin1String("L:%1 %2 - R:%3 %4 (prio:%5)")) .arg(candidateType_to_string(local->type), QString(local->addr), candidateType_to_string(remote->type), QString(remote->addr), QString::number(priority)); } }; class CheckList { public: QList> pairs; QQueue> triggeredPairs; QList> validPairs; // highest priority and nominated come first CheckListState state; }; class Component { public: int id = 0; IceComponent * ic = nullptr; std::unique_ptr nominationTimer = std::unique_ptr(); CandidatePair::Ptr selectedPair; // final selected pair. won't be changed CandidatePair::Ptr highestPair; // current highest priority pair to send data bool localFinished = false; bool hasValidPairs = false; bool hasNominatedPairs = false; bool stopped = false; bool lowOverhead = false; // initiator is nominating the final pair (will be set as `selectePair` when ready) bool nominating = false; // with aggressive nomination it's always false }; Ice176 * q; Ice176::Mode mode; State state = Stopped; QTimer checkTimer; TurnClient::Proxy proxy; UdpPortReserver * portReserver = nullptr; std::unique_ptr pacTimer; int nominationTimeout = 3000; // 3s int pacTimeout = 30000; // 30s todo: compute from rto. see draft-ietf-ice-pac-06 int componentCount = 0; QList localAddrs; QList extAddrs; QHostAddress stunBindAddr; int stunBindPort; QHostAddress stunRelayUdpAddr; int stunRelayUdpPort; QString stunRelayUdpUser; QCA::SecureArray stunRelayUdpPass; QHostAddress stunRelayTcpAddr; int stunRelayTcpPort; QString stunRelayTcpUser; QCA::SecureArray stunRelayTcpPass; QString localUser, localPass; QString peerUser, peerPass; std::vector components; QList localCandidates; QList remoteCandidates; QSet> iceTransports; CheckList checkList; QList> in; Features remoteFeatures; Features localFeatures; bool allowIpExposure = true; bool useLocal = true; bool useStunBind = true; bool useStunRelayUdp = true; bool useStunRelayTcp = true; bool localHostGatheringFinished = false; bool localGatheringComplete = false; bool remoteGatheringComplete = false; bool readyToSendMedia = false; bool canStartChecks = false; Private(Ice176 *_q) : QObject(_q), q(_q) { connect(&checkTimer, &QTimer::timeout, this, [this]() { auto pair = selectNextPairToCheck(); if (pair) checkPair(pair); else checkTimer.stop(); }); checkTimer.setInterval(20); checkTimer.setSingleShot(false); } ~Private() { for (const Component &c : components) delete c.ic; } void reset() { checkTimer.stop(); /*TODO*/ } int findLocalAddress(const QHostAddress &addr) { for (int n = 0; n < localAddrs.count(); ++n) { if (localAddrs[n].addr == addr) return n; } return -1; } void updateLocalAddresses(const QList &addrs) { // for now, ignore address changes during operation if (state != Stopped) return; localAddrs.clear(); for (const auto &la : addrs) { int at = findLocalAddress(la.addr); if (at == -1) localAddrs += la; } } void updateExternalAddresses(const QList &addrs) { // for now, ignore address changes during operation if (state != Stopped) return; extAddrs.clear(); for (const ExternalAddress &ea : addrs) { int at = findLocalAddress(ea.base.addr); if (at != -1) extAddrs += ea; } } void start() { Q_ASSERT(state == Stopped); state = Starting; localUser = IceAgent::randomCredential(4); localPass = IceAgent::randomCredential(22); if (!useLocal) useStunBind = false; QList socketList; if (portReserver) // list size = componentCount * number of interfaces socketList = portReserver->borrowSockets(componentCount, this); components.reserve(componentCount); for (int n = 0; n < componentCount; ++n) { components.emplace_back(); Component &c = components.back(); c.id = n + 1; c.ic = new IceComponent(c.id, this); c.ic->setDebugLevel(IceComponent::DL_Info); connect(c.ic, SIGNAL(candidateAdded(XMPP::IceComponent::Candidate)), SLOT(ic_candidateAdded(XMPP::IceComponent::Candidate))); connect(c.ic, SIGNAL(candidateRemoved(XMPP::IceComponent::Candidate)), SLOT(ic_candidateRemoved(XMPP::IceComponent::Candidate))); connect(c.ic, SIGNAL(localFinished()), SLOT(ic_localFinished())); connect(c.ic, &IceComponent::gatheringComplete, this, &Private::ic_gatheringComplete); connect(c.ic, SIGNAL(stopped()), SLOT(ic_stopped())); connect(c.ic, SIGNAL(debugLine(QString)), SLOT(ic_debugLine(QString))); c.ic->setClientSoftwareNameAndVersion("Iris"); c.ic->setProxy(proxy); if (portReserver) c.ic->setPortReserver(portReserver); c.ic->setLocalAddresses(localAddrs); c.ic->setExternalAddresses(extAddrs); if (!stunBindAddr.isNull()) c.ic->setStunBindService(stunBindAddr, stunBindPort); if (!stunRelayUdpAddr.isNull()) c.ic->setStunRelayUdpService(stunRelayUdpAddr, stunRelayUdpPort, stunRelayUdpUser, stunRelayUdpPass); if (!stunRelayTcpAddr.isNull()) c.ic->setStunRelayTcpService(stunRelayTcpAddr, stunRelayTcpPort, stunRelayTcpUser, stunRelayTcpPass); c.ic->setUseLocal(useLocal && allowIpExposure); c.ic->setUseStunBind(useStunBind && allowIpExposure); c.ic->setUseStunRelayUdp(useStunRelayUdp); c.ic->setUseStunRelayTcp(useStunRelayTcp); // create an inbound queue for this component in += QList(); c.ic->update(&socketList); } // socketList should always empty here, but might not be if // the app provided a different address list to // UdpPortReserver and Ice176. and that would really be // a dumb thing to do but I'm not going to Q_ASSERT it if (!socketList.isEmpty()) portReserver->returnSockets(socketList); } void startChecks() { pacTimer.reset(new QTimer(this)); pacTimer->setSingleShot(true); pacTimer->setInterval(pacTimeout); connect(pacTimer.get(), &QTimer::timeout, this, &Ice176::Private::onPacTimeout); iceDebug("Start Patiently Awaiting Connectivity timer"); canStartChecks = true; pacTimer->start(); checkTimer.start(); } void stop() { if (state == Stopped || state == Stopping) return; // stopped as a result of previous error? state = Stopping; pacTimer.reset(); checkTimer.stop(); // will trigger candidateRemoved events and result pairs cleanup. if (!components.empty()) { for (auto &c : components) { c.nominationTimer.reset(); c.ic->stop(); } } else { // TODO: hmm, is it possible to have no components? QMetaObject::invokeMethod(this, "postStop", Qt::QueuedConnection); } } void addRemoteCandidates(const QList &list) { Q_ASSERT(state == Started || state == Starting); QList remoteCandidates; for (const Candidate &c : list) { auto ci = IceComponent::CandidateInfo::Ptr::create(); ci->addr.addr = c.ip; ci->addr.addr.setScopeId(QString()); ci->addr.port = c.port; ci->type = (IceComponent::CandidateType)string_to_candidateType(c.type); // TODO: handle error ci->componentId = c.component; ci->priority = c.priority; ci->foundation = c.foundation; if (!c.rel_addr.isNull()) { ci->base.addr = c.rel_addr; ci->base.addr.setScopeId(QString()); ci->base.port = c.rel_port; } ci->network = c.network; ci->id = c.id; // find remote prflx with same addr. we have to update them instead adding new one. RFC8445 7.3.1.3 auto it = std::find_if(this->remoteCandidates.begin(), this->remoteCandidates.end(), [&](IceComponent::CandidateInfo::Ptr rc) { return ci->addr == rc->addr && ci->componentId == rc->componentId && rc->type == IceComponent::PeerReflexiveType; }); if (it != this->remoteCandidates.end()) { (*it)->type = ci->type; // RFC8445 5.1.2.1. Recommended Formula (peer-reflexive are preferred) // B.7. Why Prefer Peer-Reflexive Candidates? // if srflx == prflx -> set srflx because not secure anyway (*it)->foundation = ci->foundation; (*it)->base = ci->base; (*it)->network = ci->network; (*it)->id = ci->id; iceDebug("Previously known remote prflx was updated from signalling: %s", qPrintable((*it)->addr)); } else { remoteCandidates += ci; } } this->remoteCandidates += remoteCandidates; iceDebug("adding %d remote candidates. total=%d", remoteCandidates.count(), this->remoteCandidates.count()); doPairing(localCandidates, remoteCandidates); } void setRemoteGatheringComplete() { remoteGatheringComplete = true; if (!localGatheringComplete || state != Started) return; for (auto &c : components) tryNominateSelectedPair(c.id); } // returns a pair is pairable or null QSharedPointer makeCandidatesPair(IceComponent::CandidateInfo::Ptr lc, IceComponent::CandidateInfo::Ptr rc) { if (lc->componentId != rc->componentId) return {}; // don't pair ipv4 with ipv6. FIXME: is this right? if (lc->addr.addr.protocol() != rc->addr.addr.protocol()) { iceDebug("Skip building pair: %s - %s (protocol mismatch)", qPrintable(lc->addr), qPrintable(rc->addr)); return {}; } // don't relay to localhost. turnserver // doesn't like it. i don't know if this // should qualify as a HACK or not. // trying to relay to localhost is pretty // stupid anyway if (lc->type == IceComponent::RelayedType && getAddressScope(rc->addr.addr) == 0) { qDebug("Skip building pair: %s - %s (relay to localhost)", qPrintable(lc->addr), qPrintable(rc->addr)); return {}; } auto pair = QSharedPointer::create(); pair->local = lc; pair->remote = rc; if (pair->local->addr.addr.protocol() == QAbstractSocket::IPv6Protocol && isIPv6LinkLocalAddress(pair->local->addr.addr)) pair->remote->addr.addr.setScopeId(pair->local->addr.addr.scopeId()); if (mode == Ice176::Initiator) pair->priority = calc_pair_priority(lc->priority, rc->priority); else pair->priority = calc_pair_priority(rc->priority, lc->priority); return pair; } // adds new pairs, sorts, prunes void addChecklistPairs(const QList> &pairs) { #ifdef ICE_DEBUG iceDebug("%d new pairs", pairs.count()); for (auto &p : pairs) p->logNew = true; #endif if (!pairs.count()) return; // combine pairs with existing, and sort checkList.pairs += pairs; std::sort(checkList.pairs.begin(), checkList.pairs.end(), [&](const QSharedPointer &a, const QSharedPointer &b) { return a->priority == b->priority ? a->local->componentId < b->local->componentId : a->priority > b->priority; }); // pruning for (int n = 0; n < checkList.pairs.count(); ++n) { auto &pair = checkList.pairs[n]; #ifdef ICE_DEBUG if (pair->logNew) iceDebug("C%d, %s", pair->local->componentId, qPrintable(*pair)); #endif for (int i = n - 1; i >= 0; --i) { // RFC8445 says to use base only for reflexive. but base is set properly for host and relayed too. if (pair->local->componentId == checkList.pairs[i]->local->componentId && pair->local->base == checkList.pairs[i]->local->base && pair->remote->addr == checkList.pairs[i]->remote->addr) { checkList.pairs.removeAt(n); --n; // adjust position break; } } } // max pairs is 100 * number of components int max_pairs = 100 * int(components.size()); while (checkList.pairs.count() > max_pairs) checkList.pairs.removeLast(); #ifdef ICE_DEBUG iceDebug("%d after pruning (just new below):", checkList.pairs.count()); for (auto &p : checkList.pairs) { if (p->logNew) iceDebug("C%d, %s", p->local->componentId, qPrintable(*p)); p->logNew = false; } #endif } QSharedPointer selectNextPairToCheck() { // rfc8445 6.1.4.2. Performing Connectivity Checks QSharedPointer pair; while (!checkList.triggeredPairs.empty() && !(pair = checkList.triggeredPairs.dequeue().lock())) ; if (pair) { pair->isTriggered = true; // according to rfc - check just this one iceDebug("next check from triggered list: %s", qPrintable(*pair)); return pair; } auto it = std::find_if(checkList.pairs.begin(), checkList.pairs.end(), [&](const auto &p) mutable { if (p->state == PFrozen && !pair) pair = p; return p->state == PWaiting; }); if (it != checkList.pairs.end()) { // found waiting // the list was sorted already by priority and componentId. So first one is Ok iceDebug("next check for already waiting: %s", qPrintable(**it)); (*it)->isTriggered = false; return *it; } if (pair) { // now it's frozen highest-priority pair pair->isTriggered = false; iceDebug("next check for a frozen pair: %s", qPrintable(*pair)); } // FIXME real algo should be (but is requires significant refactoring) // 1) go over all knows pair foundations over all checklists // 2) if for the foundation there is a frozen pair but no (in-progress or waiting) // 3) - do checks on this pair return pair; } void checkPair(QSharedPointer pair) { pair->foundation = pair->local->foundation + pair->remote->foundation; pair->state = PInProgress; int at = findLocalCandidate(pair->local->addr.addr, pair->local->addr.port); Q_ASSERT(at != -1); auto &lc = localCandidates[at]; Component &c = *findComponent(lc.info->componentId); // read comment to the pool member how wrong it is pair->pool = StunTransactionPool::Ptr::create(StunTransaction::Udp); connect(pair->pool.data(), &StunTransactionPool::outgoingMessage, this, [this, weakPair = pair.toWeakRef()](const QByteArray &packet, const QHostAddress &, int) { auto pair = weakPair.toStrongRef(); if (!pair) return; int at = findLocalCandidate(pair->local->addr.addr, pair->local->addr.port); if (at == -1) { // FIXME: assert? qDebug("Failed to find local candidate %s:%d", qPrintable(pair->local->addr.addr.toString()), pair->local->addr.port); return; } IceComponent::Candidate &lc = localCandidates[at]; int path = lc.path; iceDebug("send connectivity check for pair %s%s", qPrintable(*pair), (mode == Initiator ? (pair->binding->useCandidate() ? " (nominating)" : "") : (pair->isTriggeredForNominated ? " (triggered check for nominated)" : ""))); lc.iceTransport->writeDatagram(path, packet, pair->remote->addr.addr, pair->remote->addr.port); }); // pair->pool->setUsername(peerUser + ':' + localUser); // pair->pool->setPassword(peerPass.toUtf8()); pair->binding = new StunBinding(pair->pool.data()); connect(pair->binding, &StunBinding::success, this, [this, wpair = pair.toWeakRef()]() { auto pair = wpair.lock(); if (pair) handlePairBindingSuccess(pair); }); connect(pair->binding, &StunBinding::error, this, [this, wpair = pair.toWeakRef()](XMPP::StunBinding::Error e) { auto pair = wpair.lock(); if (pair) handlePairBindingError(pair, e); }); int prflx_priority = c.ic->peerReflexivePriority(lc.iceTransport, lc.path); pair->binding->setPriority(prflx_priority); if (mode == Ice176::Initiator) { pair->binding->setIceControlling(0); if (localFeatures & AggressiveNomination || pair->finalNomination) pair->binding->setUseCandidate(true); } else pair->binding->setIceControlled(0); pair->binding->setShortTermUsername(peerUser + ':' + localUser); pair->binding->setShortTermPassword(peerPass); pair->binding->start(); } void doPairing(const QList & localCandidates, const QList &remoteCandidates) { QList> pairs; for (const IceComponent::Candidate &cc : localCandidates) { auto lc = cc.info; if (lc->type == IceComponent::PeerReflexiveType) { iceDebug("not pairing local prflx. %s", qPrintable(lc->addr)); // see RFC8445 7.2.5.3.1. Discovering Peer-Reflexive Candidates continue; } for (IceComponent::CandidateInfo::Ptr rc : remoteCandidates) { auto pair = makeCandidatesPair(lc, rc); if (!pair.isNull()) pairs += pair; } } if (!pairs.count()) return; addChecklistPairs(pairs); if (canStartChecks && !checkTimer.isActive()) checkTimer.start(); } void write(int componentIndex, const QByteArray &datagram) { auto cIt = findComponent(componentIndex + 1); Q_ASSERT(cIt != components.end()); auto pair = cIt->selectedPair; if (!pair) { pair = cIt->highestPair; if (!pair) { iceDebug("An attempt to write to an ICE component w/o valid sockets"); return; } } int at = findLocalCandidate(pair->local->addr.addr, pair->local->addr.port); if (at == -1) { // FIXME: assert? iceDebug("FIXME! Failed to find local candidate for componentId=%d, addr=%s", componentIndex + 1, qPrintable(pair->local->addr)); return; } IceComponent::Candidate &lc = localCandidates[at]; int path = lc.path; lc.iceTransport->writeDatagram(path, datagram, pair->remote->addr.addr, pair->remote->addr.port); // DOR-SR? QMetaObject::invokeMethod(q, "datagramsWritten", Qt::QueuedConnection, Q_ARG(int, componentIndex), Q_ARG(int, 1)); } void flagComponentAsLowOverhead(int componentIndex) { // FIXME: ok to assume in order? Component &c = components[componentIndex]; c.lowOverhead = true; // FIXME: actually do something } void cleanupButSelectedPair(int componentId) { CandidatePair::Ptr selected = findComponent(componentId)->selectedPair; Q_ASSERT(selected); decltype(checkList.validPairs) newValid; newValid.push_back(selected); for (auto &p : checkList.validPairs) if (p->local->componentId != componentId) newValid.push_back(p); checkList.validPairs = newValid; auto &sa = selected->local->base; auto &t = localCandidates[findLocalCandidate(sa.addr, sa.port)].iceTransport; Q_ASSERT(t.data() != nullptr); // cancel planned/active transactions QMutableListIterator> it(checkList.triggeredPairs); while (it.hasNext()) { auto p = it.next().toStrongRef(); if (!p || p->local->componentId == componentId) it.remove(); } for (auto &p : checkList.pairs) { if (p->local->componentId == componentId && p->state == PInProgress) { p->binding->cancel(); p->state = PFailed; iceDebug("Cancel %s setting it to failed state", qPrintable(*p)); } } // stop not used transports for (auto &c : localCandidates) { if (c.info->componentId == componentId && c.iceTransport != t) { c.iceTransport->stop(); } } } void setSelectedPair(int componentId) { auto &component = *findComponent(componentId); auto &pair = component.selectedPair; if (pair) return; #ifdef ICE_DEBUG iceDebug("Current valid list state:"); for (auto &p : checkList.validPairs) { iceDebug(" C%d: %s", p->local->componentId, qPrintable(*p)); } #endif component.nominationTimer.reset(); pair = component.highestPair; if (!pair) { qWarning("C%d: failed to find selected pair for previously nominated component. Candidates removed " "without ICE restart?", componentId); stop(); emit q->error(ErrorGeneric); return; } iceDebug("C%d: selected pair: %s (base: %s)", componentId, qPrintable(*pair), qPrintable(pair->local->base)); cleanupButSelectedPair(componentId); emit q->componentReady(componentId - 1); tryIceFinished(); } void optimizeCheckList(int componentId) { auto it = findComponent(componentId); Q_ASSERT(it != components.end() && it->highestPair); auto minPriority = it->highestPair->priority; for (auto &p : checkList.pairs) { bool toStop = p->local->componentId == componentId && (p->state == PFrozen || p->state == PWaiting) && p->priority < minPriority; if (toStop) { iceDebug("Disable check for %s since we already have better valid pairs", qPrintable(*p)); p->state = PFailed; } } for (auto &pWeak : checkList.triggeredPairs) { auto p = pWeak.toStrongRef(); if (p->local->componentId == componentId && p->priority < minPriority) { iceDebug("Disable triggered check for %s since we already have better valid pairs", qPrintable(*p)); p->state = PFailed; } } } bool doesItWorthNominateNow(int componentId) { auto &c = *findComponent(componentId); if (mode != Initiator || (localFeatures & AggressiveNomination) || state != Started || !c.highestPair || c.selectedPair || c.nominating) return false; auto pair = c.highestPair; Q_ASSERT(!pair->isNominated); if (pair->local->type == IceComponent::RelayedType) { if (!(localGatheringComplete && remoteGatheringComplete)) { iceDebug("Waiting for gathering complete on both sides before nomination of relayed pair"); return false; // maybe we gonna have a non-relayed pair. RFC8445 anyway allows to send data on any // valid. } // if there is any non-relayed pending pair if (std::any_of(checkList.pairs.begin(), checkList.pairs.end(), [](auto &p) { return p->state != PSucceeded && p->state != PFailed && p->local->type != IceComponent::RelayedType; })) { iceDebug("There are some non-relayed pairs to check before relayed nomination"); return false; // either till checked or remote gathering timeout } } return true; } void nominateSelectedPair(int componentId) { auto &c = *findComponent(componentId); Q_ASSERT(mode == Initiator && c.highestPair && !c.selectedPair && !c.nominating); c.nominationTimer.reset(); c.nominating = true; c.highestPair->finalNomination = true; iceDebug("Nominating valid pair: %s", qPrintable(*c.highestPair)); checkList.triggeredPairs.prepend(c.highestPair); if (!checkTimer.isActive()) checkTimer.start(); } void tryNominateSelectedPair(int componentId) { if (doesItWorthNominateNow(componentId)) nominateSelectedPair(componentId); } void tryIceFinished() { if (!std::all_of(components.begin(), components.end(), [](auto &c) { return c.selectedPair != nullptr; })) return; tryReadyToSendMedia(); #ifdef ICE_DEBUG iceDebug("ICE selected final pairs!"); for (auto &c : components) { iceDebug(" C%d: %s", c.id, qPrintable(*c.selectedPair)); } iceDebug("Signalling iceFinished now"); #endif pacTimer.reset(); state = Active; emit q->iceFinished(); } void setupNominationTimer(int componentId) { Component &c = *findComponent(componentId); if (c.nominationTimer) return; bool agrNom = (mode == Initiator ? localFeatures : remoteFeatures) & AggressiveNomination; if (!agrNom && mode == Responder) return; // responder will wait for nominated pairs till very end auto timer = new QTimer(); c.nominationTimer.reset(timer); timer->setSingleShot(true); timer->setInterval(nominationTimeout); connect(timer, &QTimer::timeout, this, [this, componentId, agrNom]() { Q_ASSERT(state == Started); Component &c = *findComponent(componentId); c.nominationTimer.release()->deleteLater(); if (c.stopped) return; // already queue signal likely if (agrNom) setSelectedPair(componentId); else if (!c.nominating && !c.selectedPair) nominateSelectedPair(componentId); }); timer->start(); } // nominated - out side=responder. and remote request had USE_CANDIDATE void doTriggeredCheck(const IceComponent::Candidate &locCand, IceComponent::CandidateInfo::Ptr remCand, bool nominated) { // let's figure out if this pair already in the check list auto it = std::find_if(checkList.pairs.begin(), checkList.pairs.end(), [&](auto const &p) { return *(p->local) == locCand.info && *(p->remote) == remCand; }); CandidatePair::Ptr pair = (it == checkList.pairs.end()) ? CandidatePair::Ptr() : *it; Component & component = *findComponent(locCand.info->componentId); int minPriority = component.highestPair ? component.highestPair->priority : 0; if (pair) { if (pair->priority < minPriority) { iceDebug( "Don't do triggered check for known pair since the pair has lower priority than highest valid"); return; } if (pair->state == CandidatePairState::PSucceeded) { // Check nominated here? iceDebug("Don't do triggered check since pair is already in success state"); if (mode == Responder && !pair->isNominated && nominated) { pair->isNominated = true; onNewValidPair(pair); } return; // nothing todo. rfc 8445 7.3.1.4 } pair->isNominated = false; if (pair->state == CandidatePairState::PInProgress) { if (pair->isTriggered) { iceDebug( "Current in-progress check is already triggered. Don't cancel it while have to according to " "RFC8445\n"); return; } pair->binding->cancel(); } if (pair->state == PFailed) { // if (state == Stopped) { // TODO Stopped? maybe Failed? and we have to notify the outer world //} } } else { // RFC8445 7.3.1.4. Triggered Checks / "If the pair is not already on the checklist" pair = makeCandidatesPair(locCand.info, remCand); if (pair.isNull()) { return; } if (pair->priority < minPriority) { iceDebug( "Don't do triggered check for a new pair since the pair has lower priority than highest valid"); return; } addChecklistPairs(QList() << pair); } pair->state = PWaiting; pair->isTriggeredForNominated = nominated; checkList.triggeredPairs.enqueue(pair); if (canStartChecks && !checkTimer.isActive()) checkTimer.start(); } void onPacTimeout() { Q_ASSERT(state == Starting || state == Started); pacTimer.release()->deleteLater(); iceDebug("Patiently Awaiting Connectivity timeout"); stop(); emit q->error(ErrorGeneric); } private: inline decltype(components)::iterator findComponent(const IceComponent *ic) { return std::find_if(components.begin(), components.end(), [&](auto &c) { return c.ic == ic; }); } inline decltype(components)::iterator findComponent(int id) { return std::find_if(components.begin(), components.end(), [&](auto &c) { return c.id == id; }); } int findLocalCandidate(const IceTransport *iceTransport, int path, bool hostAndRelayOnly = false) const { for (int n = 0; n < localCandidates.count(); ++n) { const IceComponent::Candidate &cc = localCandidates[n]; if (cc.iceTransport == iceTransport && cc.path == path && (!hostAndRelayOnly || cc.info->type == IceComponent::RelayedType || cc.info->type == IceComponent::HostType)) return n; } return -1; } int findLocalCandidate(const QHostAddress &fromAddr, int fromPort) { for (int n = 0; n < localCandidates.count(); ++n) { const IceComponent::Candidate &cc = localCandidates[n]; if (cc.info->addr.addr == fromAddr && cc.info->addr.port == fromPort) return n; } return -1; } static QString candidateType_to_string(IceComponent::CandidateType type) { QString out; switch (type) { case IceComponent::HostType: out = "host"; break; case IceComponent::PeerReflexiveType: out = "prflx"; break; case IceComponent::ServerReflexiveType: out = "srflx"; break; case IceComponent::RelayedType: out = "relay"; break; default: Q_ASSERT(0); } return out; } static int string_to_candidateType(const QString &in) { if (in == "host") return IceComponent::HostType; else if (in == "prflx") return IceComponent::PeerReflexiveType; else if (in == "srflx") return IceComponent::ServerReflexiveType; else if (in == "relay") return IceComponent::RelayedType; else return -1; } static void toOutCandidate(const IceComponent::Candidate &cc, Ice176::Candidate &out) { out.component = cc.info->componentId; out.foundation = cc.info->foundation; out.generation = 0; // TODO out.id = cc.info->id; out.ip = cc.info->addr.addr; out.ip.setScopeId(QString()); out.network = cc.info->network; out.port = cc.info->addr.port; out.priority = cc.info->priority; out.protocol = "udp"; if (cc.info->type != IceComponent::HostType) { out.rel_addr = cc.info->base.addr; out.rel_addr.setScopeId(QString()); out.rel_port = cc.info->base.port; } else { out.rel_addr = QHostAddress(); out.rel_port = -1; } out.rem_addr = QHostAddress(); out.rem_port = -1; out.type = candidateType_to_string(cc.info->type); } void dumpCandidatesAndStart() { QList list; for (auto const &cc : localCandidates) { Ice176::Candidate c; toOutCandidate(cc, c); list += c; } if (list.size()) emit q->localCandidatesReady(list); state = Started; emit q->started(); if (mode == Responder) doPairing(localCandidates, remoteCandidates); } QString generateIdForCandidate() { QString id; do { id = IceAgent::randomCredential(10); } while (std::find_if(localCandidates.begin(), localCandidates.end(), [&id](auto const &c) { return c.info->id == id; }) != localCandidates.end()); return id; } void tryReadyToSendMedia() { if (readyToSendMedia) { return; } bool allowNotNominatedData = (localFeatures & NotNominatedData) && (remoteFeatures & NotNominatedData); // if both follow RFC8445 and allow to send data on any valid pair if (!std::all_of(components.begin(), components.end(), [&](auto &c) { return (allowNotNominatedData && c.hasValidPairs) || c.hasNominatedPairs; })) { return; } #ifdef ICE_DEBUG iceDebug("Ready to send media!"); for (auto &c : components) { if (c.selectedPair) iceDebug(" C%d: selected pair: %s (base: %s)", c.id, qPrintable(*c.selectedPair), qPrintable(c.selectedPair->local->base)); else { iceDebug(" C%d: any pair from valid list", c.id); iceDebug(" highest: %s", qPrintable(*c.highestPair)); } } #endif readyToSendMedia = true; emit q->readyToSendMedia(); } void insertIntoValidList(int componentId, CandidatePair::Ptr pair) { auto &component = *findComponent(componentId); if (!component.selectedPair) { // no final pair yet // find position to insert in sorted list of valid pairs auto insIt = std::upper_bound( checkList.validPairs.begin(), checkList.validPairs.end(), pair, [](auto &item, auto &toins) { return item->priority == toins->priority ? item->local->componentId < toins->local->componentId : item->priority >= toins->priority; // inverted since we need high priority first }); bool highest = false; if (!component.highestPair || component.highestPair->priority < pair->priority) { component.highestPair = pair; highest = true; } checkList.validPairs.insert(insIt, pair); // nominated and highest priority first iceDebug("C%d: insert to valid list %s%s", component.id, qPrintable(*pair), highest ? " (as highest priority)" : ""); } } void onNewValidPair(CandidatePair::Ptr pair) { auto &component = *findComponent(pair->local->componentId); bool alreadyInValidList = pair->isValid; pair->isValid = true; pair->state = PSucceeded; // what if it was in progress? component.hasValidPairs = true; // mark all with same foundation as Waiting to prioritize them (see RFC8445 7.2.5.3.3) for (auto &p : checkList.pairs) if (p->state == PFrozen && p->foundation == pair->foundation) p->state = PWaiting; if (!alreadyInValidList) insertIntoValidList(component.id, pair); optimizeCheckList(component.id); // if (c.lowOverhead) { // commented out since we need turn permissions for all components iceDebug("component is flagged for low overhead. setting up for %s", qPrintable(*pair)); auto &cc = localCandidates[findLocalCandidate(pair->local->addr.addr, pair->local->addr.port)]; component.ic->flagPathAsLowOverhead(cc.id, pair->remote->addr.addr, pair->remote->addr.port); //} if (pair->isNominated) { component.hasNominatedPairs = true; bool agrNom = (mode == Initiator ? localFeatures : remoteFeatures) & AggressiveNomination; if (!agrNom) { setSelectedPair(component.id); } else setupNominationTimer(component.id); } else setupNominationTimer(component.id); tryReadyToSendMedia(); } void handlePairBindingSuccess(CandidatePair::Ptr pair) { /* RFC8445 7.2.5.2.1. Non-Symmetric Transport Addresses tells us addr:port of source->dest of request MUST match with dest<-source of the response, and we should mark the pair as failed if doesn't match. But StunTransaction already does this for us in its checkActiveAndFrom. So it will fail with timeout instead if response comes from a wrong address. */ StunBinding *binding = pair->binding; // pair->isValid = true; pair->state = CandidatePairState::PSucceeded; bool isTriggeredForNominated = pair->isTriggeredForNominated; bool isNominatedByInitiator = mode == Initiator && binding->useCandidate(); bool finalNomination = pair->finalNomination; auto &component = *findComponent(pair->local->componentId); iceDebug("check success for %s", qPrintable(QString(*pair))); // RFC8445 7.2.5.3.1. Discovering Peer-Reflexive Candidates auto mappedAddr = IceComponent::TransportAddress(binding->reflexiveAddress(), binding->reflexivePort()); if (pair->local->addr != mappedAddr) { // skip "If the valid pair equals the pair that generated the check" // so mapped address doesn't match with local candidate sending binding request. // gotta find/create one auto locIt = std::find_if(localCandidates.begin(), localCandidates.end(), [&](const auto &c) { return (c.info->base == mappedAddr || c.info->addr == mappedAddr) && c.info->componentId == component.id; }); if (locIt == localCandidates.end()) { // RFC8445 7.2.5.3.1. Discovering Peer-Reflexive Candidates // new peer-reflexive local candidate discovered component.ic->addLocalPeerReflexiveCandidate(mappedAddr, pair->local, binding->priority()); // find just inserted prflx candidate locIt = std::find_if(localCandidates.begin(), localCandidates.end(), [&](const auto &c) { return c.info->addr == mappedAddr; }); Q_ASSERT(locIt != localCandidates.end()); // local candidate wasn't found, so it wasn't on the checklist RFC8445 7.2.5.3.1.3 // allow v4/v6 proto mismatch in case NAT does magic pair = makeCandidatesPair(locIt->info, pair->remote); } else { // local candidate found. If it's a part of a pair on checklist, we have to add this pair to valid list, // otherwise we have to create a new pair and add it to valid list auto it = std::find_if(checkList.pairs.begin(), checkList.pairs.end(), [&](auto const &p) { return p->local->base == locIt->info->base && p->remote->addr == pair->remote->addr && p->local->componentId == locIt->info->componentId; }); if (it == checkList.pairs.constEnd()) { // allow v4/v6 proto mismatch in case NAT does magic pair = makeCandidatesPair(locIt->info, pair->remote); } else { pair = *it; iceDebug("mapped address belongs to another pair on checklist %s", qPrintable(QString(*pair))); } } } if (!pair) { qWarning("binding success but failed to build a pair with mapped address %s!", qPrintable(mappedAddr)); return; } pair->isTriggeredForNominated = isTriggeredForNominated; pair->finalNomination = finalNomination; pair->isNominated = isTriggeredForNominated || isNominatedByInitiator; onNewValidPair(pair); } void handlePairBindingError(CandidatePair::Ptr pair, XMPP::StunBinding::Error) { Q_ASSERT(state != Stopped); if (state == Stopping) return; // we don't care about late errors if (state == Active) { iceDebug("todo! binding error ignored in Active state"); return; // TODO hadle keep-alive binding properly } iceDebug("check failed for %s", qPrintable(*pair)); auto &c = *findComponent(pair->local->componentId); pair->state = CandidatePairState::PFailed; if (pair->isValid) { // RFC8445 7.2.5.3.4. Updating the Nominated Flag / about failure checkList.validPairs.removeOne(pair); pair->isValid = false; if (c.highestPair == pair) { // the failed binding is nomination or triggered after receiving success on canceled binding c.highestPair.reset(); } } if ((c.nominating && pair->finalNomination) || (!(remoteFeatures & AggressiveNomination) && pair->isTriggeredForNominated)) { if (pair->isTriggeredForNominated) qInfo("Failed to do triggered check for nominated selectedPair. set ICE status to failed"); else qInfo("Failed to nominate selected pair. set ICE status to failed"); stop(); emit q->error(ErrorDisconnected); return; } // if not nominating but use-candidate then I'm initiator with aggressive nomination. It's Ok to fail. // if nominating but not use-candidate then I'm initiator and something not important failed } private slots: void postStop() { state = Stopped; emit q->stopped(); } void ic_candidateAdded(const XMPP::IceComponent::Candidate &_cc) { IceComponent::Candidate cc = _cc; cc.info->id = generateIdForCandidate(); localCandidates += cc; iceDebug("C%d: candidate added: %s %s;%d", cc.info->componentId, qPrintable(candidateType_to_string(cc.info->type)), qPrintable(cc.info->addr.addr.toString()), cc.info->addr.port); if (!iceTransports.contains(cc.iceTransport)) { connect(cc.iceTransport.data(), SIGNAL(readyRead(int)), SLOT(it_readyRead(int))); connect(cc.iceTransport.data(), SIGNAL(datagramsWritten(int, int, QHostAddress, int)), SLOT(it_datagramsWritten(int, int, QHostAddress, int))); iceTransports += cc.iceTransport; } if (!localHostGatheringFinished) return; // all local IPs will be reported at once if (localFeatures & Trickle) { QList list; Ice176::Candidate c; toOutCandidate(cc, c); list += c; emit q->localCandidatesReady(list); } if (state == Started) { doPairing(QList() << cc, remoteCandidates); } } void ic_candidateRemoved(const XMPP::IceComponent::Candidate &cc) { // TODO iceDebug("C%d: candidate removed: %s;%d", cc.info->componentId, qPrintable(cc.info->addr.addr.toString()), cc.info->addr.port); QStringList idList; for (int n = 0; n < localCandidates.count(); ++n) { if (localCandidates[n].id == cc.id && localCandidates[n].info->componentId == cc.info->componentId) { // FIXME: this is rather ridiculous I think idList += localCandidates[n].info->id; localCandidates.removeAt(n); --n; // adjust position } } bool iceTransportInUse = false; for (const IceComponent::Candidate &lc : localCandidates) { if (lc.iceTransport == cc.iceTransport) { iceTransportInUse = true; break; } } if (!iceTransportInUse) { cc.iceTransport->disconnect(this); iceTransports.remove(cc.iceTransport); } for (int n = 0; n < checkList.pairs.count(); ++n) { if (idList.contains(checkList.pairs[n]->local->id)) { StunBinding *binding = checkList.pairs[n]->binding; auto pool = checkList.pairs[n]->pool; delete binding; if (pool) { pool->disconnect(this); } checkList.pairs[n]->pool.reset(); checkList.pairs.removeAt(n); --n; // adjust position } } } void ic_localFinished() { IceComponent *ic = static_cast(sender()); auto it = findComponent(ic); Q_ASSERT(it != components.end()); Q_ASSERT(!it->localFinished); it->localFinished = true; for (const Component &c : components) { if (!c.localFinished) { return; } } localHostGatheringFinished = true; if (localFeatures & Trickle) dumpCandidatesAndStart(); } void ic_gatheringComplete() { if (localGatheringComplete) return; // wtf? Why are we here then for (auto const &c : components) { if (!c.ic->isGatheringComplete()) { return; } } localGatheringComplete = true; if (localFeatures & Trickle) { // It was already started emit q->localGatheringComplete(); return; } dumpCandidatesAndStart(); } void ic_stopped() { IceComponent *ic = static_cast(sender()); auto it = findComponent(ic); Q_ASSERT(it != components.end()); it->stopped = true; it->nominationTimer.reset(); bool allStopped = true; for (const Component &c : components) { if (!c.stopped) { allStopped = false; break; } } if (allStopped) postStop(); } void ic_debugLine(const QString &line) { #ifdef ICE_DEBUG IceComponent *ic = static_cast(sender()); auto it = findComponent(ic); Q_ASSERT(it != components.end()); // FIXME: components are always sorted? iceDebug("C%d: %s", it->id, qPrintable(line)); #else Q_UNUSED(line) #endif } // path is either direct or relayed void it_readyRead(int path) { IceTransport *it = static_cast(sender()); int at = findLocalCandidate(it, path, true); // just host or relay Q_ASSERT(at != -1); IceComponent::Candidate &locCand = localCandidates[at]; IceTransport *sock = it; while (sock->hasPendingDatagrams(path)) { QHostAddress fromAddr; int fromPort; QByteArray buf = sock->readDatagram(path, &fromAddr, &fromPort); // iceDebug("port %d: received packet (%d bytes)", lt->sock->localPort(), buf.size()); QString requser = localUser + ':' + peerUser; QByteArray reqkey = localPass.toUtf8(); StunMessage::ConvertResult result; StunMessage msg = StunMessage::fromBinary(buf, &result, StunMessage::MessageIntegrity | StunMessage::Fingerprint, reqkey); if (!msg.isNull() && (msg.mclass() == StunMessage::Request || msg.mclass() == StunMessage::Indication)) { iceDebug("received validated request or indication from %s:%d", qPrintable(fromAddr.toString()), fromPort); QString user = QString::fromUtf8(msg.attribute(StunTypes::USERNAME)); if (requser != user) { iceDebug("user [%s] is wrong. it should be [%s]. skipping", qPrintable(user), qPrintable(requser)); continue; } if (msg.method() != StunTypes::Binding) { iceDebug("not a binding request. skipping"); continue; } StunMessage response; response.setClass(StunMessage::SuccessResponse); response.setMethod(StunTypes::Binding); response.setId(msg.id()); QList list; StunMessage::Attribute attr; attr.type = StunTypes::XOR_MAPPED_ADDRESS; attr.value = StunTypes::createXorPeerAddress(fromAddr, quint16(fromPort), response.magic(), response.id()); list += attr; response.setAttributes(list); QByteArray packet = response.toBinary(StunMessage::MessageIntegrity | StunMessage::Fingerprint, reqkey); sock->writeDatagram(path, packet, fromAddr, fromPort); if (state != Started) // only in started state we do triggered checks return; auto it = std::find_if(remoteCandidates.begin(), remoteCandidates.end(), [&](IceComponent::CandidateInfo::Ptr remCand) { return remCand->componentId == locCand.info->componentId && remCand->addr.addr == fromAddr && remCand->addr.port == fromPort; }); bool nominated = false; if (mode == Responder) nominated = msg.hasAttribute(StunTypes::USE_CANDIDATE); if (it == remoteCandidates.end()) { // RFC8445 7.3.1.3. Learning Peer-Reflexive Candidates iceDebug("found NEW remote prflx! %s:%d", qPrintable(fromAddr.toString()), fromPort); quint32 priority; StunTypes::parsePriority(msg.attribute(StunTypes::PRIORITY), &priority); auto remCand = IceComponent::CandidateInfo::makeRemotePrflx(locCand.info->componentId, fromAddr, fromPort, priority); remoteCandidates += remCand; doTriggeredCheck(locCand, remCand, nominated); } else { doTriggeredCheck(locCand, *it, nominated); } } else { QByteArray reskey = peerPass.toUtf8(); StunMessage msg = StunMessage::fromBinary( buf, &result, StunMessage::MessageIntegrity | StunMessage::Fingerprint, reskey); if (!msg.isNull() && (msg.mclass() == StunMessage::SuccessResponse || msg.mclass() == StunMessage::ErrorResponse)) { iceDebug("received validated response from %s:%d to %s", qPrintable(fromAddr.toString()), fromPort, qPrintable(locCand.info->addr)); // FIXME: this is so gross and completely defeats the point of having pools for (int n = 0; n < checkList.pairs.count(); ++n) { CandidatePair &pair = *checkList.pairs[n]; if (pair.state == PInProgress && pair.local->addr.addr == locCand.info->addr.addr && pair.local->addr.port == locCand.info->addr.port) pair.pool->writeIncomingMessage(msg); } } else { // iceDebug("received some non-stun or invalid stun packet"); // FIXME: i don't know if this is good enough if (StunMessage::isProbablyStun(buf)) { iceDebug("unexpected stun packet (loopback?), skipping."); continue; } int at = -1; for (int n = 0; n < checkList.pairs.count(); ++n) { CandidatePair &pair = *checkList.pairs[n]; if (pair.local->addr.addr == locCand.info->addr.addr && pair.local->addr.port == locCand.info->addr.port) { at = n; break; } } if (at == -1) { iceDebug("the local transport does not seem to be associated with a candidate?!"); continue; } int componentIndex = checkList.pairs[at]->local->componentId - 1; // iceDebug("packet is considered to be application data for component index %d", componentIndex); // FIXME: this assumes components are ordered by id in our local arrays in[componentIndex] += buf; emit q->readyRead(componentIndex); } } } } void it_datagramsWritten(int path, int count, const QHostAddress &addr, int port) { // TODO Q_UNUSED(path); Q_UNUSED(count); Q_UNUSED(addr); Q_UNUSED(port); } }; Ice176::Ice176(QObject *parent) : QObject(parent) { d = new Private(this); } Ice176::~Ice176() { delete d; } void Ice176::reset() { d->reset(); } void Ice176::setProxy(const TurnClient::Proxy &proxy) { d->proxy = proxy; } void Ice176::setPortReserver(UdpPortReserver *portReserver) { Q_ASSERT(d->state == Private::Stopped); d->portReserver = portReserver; } void Ice176::setLocalAddresses(const QList &addrs) { d->updateLocalAddresses(addrs); } void Ice176::setExternalAddresses(const QList &addrs) { d->updateExternalAddresses(addrs); } void Ice176::setStunBindService(const QHostAddress &addr, int port) { d->stunBindAddr = addr; d->stunBindPort = port; } void Ice176::setStunRelayUdpService(const QHostAddress &addr, int port, const QString &user, const QCA::SecureArray &pass) { d->stunRelayUdpAddr = addr; d->stunRelayUdpPort = port; d->stunRelayUdpUser = user; d->stunRelayUdpPass = pass; } void Ice176::setStunRelayTcpService(const QHostAddress &addr, int port, const QString &user, const QCA::SecureArray &pass) { d->stunRelayTcpAddr = addr; d->stunRelayTcpPort = port; d->stunRelayTcpUser = user; d->stunRelayTcpPass = pass; } void Ice176::setAllowIpExposure(bool enabled) { d->allowIpExposure = enabled; } void Ice176::setUseLocal(bool enabled) { d->useLocal = enabled; } void Ice176::setUseStunBind(bool enabled) { d->useStunBind = enabled; } void Ice176::setUseStunRelayUdp(bool enabled) { d->useStunRelayUdp = enabled; } void Ice176::setUseStunRelayTcp(bool enabled) { d->useStunRelayTcp = enabled; } void Ice176::setComponentCount(int count) { Q_ASSERT(d->state == Private::Stopped); d->componentCount = count; } void Ice176::setLocalFeatures(const Features &features) { d->localFeatures = features; } void Ice176::setRemoteFeatures(const Features &features) { d->remoteFeatures = features; } void Ice176::start(Mode mode) { d->mode = mode; d->start(); } void Ice176::stop() { d->stop(); } bool Ice176::isStopped() const { return d->state == Private::Stopped; } void Ice176::startChecks() { d->startChecks(); } QString Ice176::localUfrag() const { return d->localUser; } QString Ice176::localPassword() const { return d->localPass; } void Ice176::setPeerUfrag(const QString &ufrag) { d->peerUser = ufrag; } void Ice176::setPeerPassword(const QString &pass) { d->peerPass = pass; } void Ice176::addRemoteCandidates(const QList &list) { d->addRemoteCandidates(list); } void Ice176::setRemoteGatheringComplete() { iceDebug("Got remote gathering complete signal"); d->setRemoteGatheringComplete(); } bool Ice176::canSendMedia() const { return d->readyToSendMedia; } bool Ice176::hasPendingDatagrams(int componentIndex) const { return !d->in[componentIndex].isEmpty(); } QByteArray Ice176::readDatagram(int componentIndex) { return d->in[componentIndex].takeFirst(); } void Ice176::writeDatagram(int componentIndex, const QByteArray &datagram) { d->write(componentIndex, datagram); } void Ice176::flagComponentAsLowOverhead(int componentIndex) { d->flagComponentAsLowOverhead(componentIndex); } bool Ice176::isIPv6LinkLocalAddress(const QHostAddress &addr) { Q_ASSERT(addr.protocol() == QAbstractSocket::IPv6Protocol); Q_IPV6ADDR addr6 = addr.toIPv6Address(); quint16 hi = addr6[0]; hi <<= 8; hi += addr6[1]; if ((hi & 0xffc0) == 0xfe80) return true; else return false; } void Ice176::changeThread(QThread *thread) { for (auto &c : d->localCandidates) { if (c.iceTransport) c.iceTransport->changeThread(thread); } for (auto &p : d->checkList.pairs) { if (p->pool) p->pool->moveToThread(thread); } moveToThread(thread); } } // namespace XMPP #include "ice176.moc" psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/ice176.h000066400000000000000000000134721370065651000236060ustar00rootroot00000000000000/* * Copyright (C) 2009-2010 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef ICE176_H #define ICE176_H #include "turnclient.h" #include #include #include namespace QCA { class SecureArray; } namespace XMPP { class UdpPortReserver; class Ice176 : public QObject { Q_OBJECT public: enum Error { ErrorGeneric, ErrorDisconnected }; enum Mode { Initiator, Responder }; class LocalAddress { public: QHostAddress addr; int network = -1; // -1 = unknown bool isVpn = false; }; class ExternalAddress { public: LocalAddress base; QHostAddress addr; int portBase; // -1 = same as base ExternalAddress() : portBase(-1) { } }; class Candidate { public: int component = -1; QString foundation; int generation = -1; QString id; QHostAddress ip; int network = -1; // -1 = unknown int port = -1; int priority = -1; QString protocol; QHostAddress rel_addr; int rel_port = -1; QHostAddress rem_addr; int rem_port = -1; QString type; }; Ice176(QObject *parent = nullptr); ~Ice176(); void reset(); void setProxy(const TurnClient::Proxy &proxy); // if set, ports will be drawn from the reserver if possible, before // binding to random ports // note: ownership is not passed void setPortReserver(UdpPortReserver *portReserver); void setLocalAddresses(const QList &addrs); // one per local address. you must set local addresses first. void setExternalAddresses(const QList &addrs); void setStunBindService(const QHostAddress &addr, int port); void setStunRelayUdpService(const QHostAddress &addr, int port, const QString &user, const QCA::SecureArray &pass); void setStunRelayTcpService(const QHostAddress &addr, int port, const QString &user, const QCA::SecureArray &pass); // these all start out enabled, but can be disabled for diagnostic // purposes void setUseLocal(bool enabled); void setUseStunBind(bool enabled); void setUseStunRelayUdp(bool enabled); void setUseStunRelayTcp(bool enabled); void setAllowIpExposure(bool enabled); void setComponentCount(int count); enum Feature { Trickle = 0x1, // additional candidates will be sent later when discovered AggressiveNomination = 0x2, // all the candidates are nominated. so select by priority NotNominatedData = 0x4, // Data on valid but not nominated candidates is allowed RTPOptimization = 0x8, // Different formula for RTO, not used in RFC8445 GatheringComplete = 0x10 // Looks MUST in XEP-0371 but missed in XEP-0176 }; Q_DECLARE_FLAGS(Features, Feature) void setLocalFeatures(const Features &features); void setRemoteFeatures(const Features &features); void start(Mode mode); // init everything and prepare candidates void stop(); bool isStopped() const; void startChecks(); // actually start doing checks when connection is accepted QString localUfrag() const; QString localPassword() const; void setPeerUfrag(const QString &ufrag); void setPeerPassword(const QString &pass); void addRemoteCandidates(const QList &list); void setRemoteGatheringComplete(); bool canSendMedia() const; bool hasPendingDatagrams(int componentIndex) const; QByteArray readDatagram(int componentIndex); void writeDatagram(int componentIndex, const QByteArray &datagram); // this call will ensure that TURN headers are minimized on this // component, with the drawback that packets might not be able to // be set as non-fragmentable. use this on components that expect // to send lots of very small packets, where header overhead is the // most costly but also where fragmentation is impossible anyway. // in short, use this on audio, but not on video. void flagComponentAsLowOverhead(int componentIndex); // FIXME: this should probably be in netinterface.h or such static bool isIPv6LinkLocalAddress(const QHostAddress &addr); void changeThread(QThread *thread); signals: // indicates that the ice engine is started and is ready to receive // peer creds and remote candidates void started(); void stopped(); void error(XMPP::Ice176::Error e); void localCandidatesReady(const QList &list); void localGatheringComplete(); void readyToSendMedia(); // Has at least one valid candidate for each component void componentReady(int index); // has valid nominated candidate for component with index void iceFinished(); // Final nominated candidates are selected for all components void readyRead(int componentIndex); void datagramsWritten(int componentIndex, int count); private: class Private; friend class Private; Private *d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(Ice176::Features) } // namespace XMPP #endif // ICE176_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/iceagent.cpp000066400000000000000000000040331370065651000247130ustar00rootroot00000000000000#include "iceagent.h" #include #include namespace XMPP { struct Foundation { IceComponent::CandidateType type; const QHostAddress baseAddr; const QHostAddress stunServAddr; QAbstractSocket::SocketType stunRequestProto; bool operator==(const Foundation &f) const { return type == f.type && baseAddr == f.baseAddr && stunServAddr == f.stunServAddr && stunRequestProto == f.stunRequestProto; }; }; inline uint qHash(const Foundation &f, uint seed = 0) { auto tmp = uint(f.stunRequestProto) & (uint(f.type) << 8); return qHash(f.baseAddr, seed) ^ qHash(f.stunServAddr, seed) ^ tmp; } static QChar randomPrintableChar() { // 0-25 = a-z // 26-51 = A-Z // 52-61 = 0-9 uchar c = QCA::Random::randomChar() % 62; if (c <= 25) return 'a' + c; else if (c <= 51) return 'A' + (c - 26); else return '0' + (c - 52); } struct IceAgent::Private { QHash foundations; }; IceAgent *IceAgent::instance() { static auto i = new IceAgent(QCoreApplication::instance()); return i; } IceAgent::~IceAgent() { } QString IceAgent::foundation(IceComponent::CandidateType type, const QHostAddress baseAddr, const QHostAddress &stunServAddr, QAbstractSocket::SocketType stunRequestProto) { Foundation f { type, baseAddr, stunServAddr, stunRequestProto }; QString ret = d->foundations.value(f); if (ret.isEmpty()) { do { ret = randomCredential(8); } while (std::find_if(d->foundations.begin(), d->foundations.end(), [&](auto const &fp) { return fp == ret; }) != d->foundations.end()); d->foundations.insert(f, ret); } return ret; } QString IceAgent::randomCredential(int len) { QString out; out.reserve(len); for (int n = 0; n < len; ++n) out += randomPrintableChar(); return out; } IceAgent::IceAgent(QObject *parent) : QObject(parent), d(new Private) { } } // namespace XMPP psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/iceagent.h000066400000000000000000000013531370065651000243620ustar00rootroot00000000000000#ifndef XMPP_ICEAGENT_H #define XMPP_ICEAGENT_H #include "icecomponent.h" #include #include namespace XMPP { class IceAgent : public QObject { Q_OBJECT public: static IceAgent *instance(); ~IceAgent(); QString foundation(IceComponent::CandidateType type, const QHostAddress baseAddr, const QHostAddress & stunServAddr = QHostAddress(), QAbstractSocket::SocketType stunRequestProto = QAbstractSocket::UnknownSocketType); static QString randomCredential(int len); private: explicit IceAgent(QObject *parent = nullptr); signals: private: struct Private; std::unique_ptr d; }; } // namespace XMPP #endif // XMPP_ICEAGENT_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/icecomponent.cpp000066400000000000000000000750271370065651000256320ustar00rootroot00000000000000/* * Copyright (C) 2010 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "icecomponent.h" #include "iceagent.h" #include "icelocaltransport.h" #include "iceturntransport.h" #include "objectsession.h" #include "udpportreserver.h" #include #include #include #include #include namespace XMPP { static int calc_priority(int typePref, int localPref, int componentId) { Q_ASSERT(typePref >= 0 && typePref <= 126); Q_ASSERT(localPref >= 0 && localPref <= 65535); Q_ASSERT(componentId >= 1 && componentId <= 256); int priority = (1 << 24) * typePref; priority += (1 << 8) * localPref; priority += (256 - componentId); return priority; } class IceComponent::Private : public QObject { Q_OBJECT public: class Config { public: QList localAddrs; // for example manually provided external address mapped to every local QList extAddrs; QHostAddress stunBindAddr; int stunBindPort; QHostAddress stunRelayUdpAddr; int stunRelayUdpPort; QString stunRelayUdpUser; QCA::SecureArray stunRelayUdpPass; QHostAddress stunRelayTcpAddr; int stunRelayTcpPort; QString stunRelayTcpUser; QCA::SecureArray stunRelayTcpPass; }; class LocalTransport { public: QUdpSocket * qsock; QHostAddress addr; QSharedPointer sock; int network; bool isVpn; bool started; bool stun_started; bool stun_finished, turn_finished; // candidates emitted QHostAddress extAddr; bool ext_finished; bool borrowed = false; LocalTransport() : network(-1), isVpn(false), started(false), stun_started(false), stun_finished(false), turn_finished(false), ext_finished(false) { } }; IceComponent * q; ObjectSession sess; int id; QString clientSoftware; TurnClient::Proxy proxy; UdpPortReserver * portReserver = nullptr; Config pending; Config config; bool stopping = false; QList udpTransports; // transport for local host-only candidates QSharedPointer tcpTurn; // tcp relay candidate QList localCandidates; QHash> channelPeers; bool useLocal = true; // use local host candidates bool useStunBind = true; bool useStunRelayUdp = true; bool useStunRelayTcp = true; bool localFinished = false; // bool stunFinished = false; bool gatheringComplete = false; int debugLevel = DL_None; Private(IceComponent *_q) : QObject(_q), q(_q), sess(this) { } ~Private() { qDeleteAll(udpTransports); } LocalTransport *createLocalTransport(QUdpSocket *socket, const Ice176::LocalAddress &la) { auto lt = new LocalTransport; lt->qsock = socket; lt->addr = la.addr; lt->sock = QSharedPointer::create(); lt->sock->setDebugLevel(IceTransport::DebugLevel(debugLevel)); lt->network = la.network; lt->isVpn = la.isVpn; connect(lt->sock.data(), SIGNAL(started()), SLOT(lt_started())); connect(lt->sock.data(), &IceLocalTransport::stopped, this, [this, lt]() { if (eraseLocalTransport(lt)) tryStopped(); }); connect(lt->sock.data(), SIGNAL(addressesChanged()), SLOT(lt_addressesChanged())); connect(lt->sock.data(), &IceLocalTransport::error, this, [this, lt](int) { if (eraseLocalTransport(lt)) tryGatheringComplete(); }); connect(lt->sock.data(), SIGNAL(debugLine(QString)), SLOT(lt_debugLine(QString))); return lt; } void update(QList *socketList) { Q_ASSERT(!stopping); // only allow setting stun stuff once if ((!pending.stunBindAddr.isNull() && config.stunBindAddr.isNull()) || (!pending.stunRelayUdpAddr.isNull() && config.stunRelayUdpAddr.isNull()) || (!pending.stunRelayTcpAddr.isNull() && config.stunRelayTcpAddr.isNull())) { config.stunBindAddr = pending.stunBindAddr; config.stunBindPort = pending.stunBindPort; config.stunRelayUdpAddr = pending.stunRelayUdpAddr; config.stunRelayUdpPort = pending.stunRelayUdpPort; config.stunRelayUdpUser = pending.stunRelayUdpUser; config.stunRelayUdpPass = pending.stunRelayUdpPass; config.stunRelayTcpAddr = pending.stunRelayTcpAddr; config.stunRelayTcpPort = pending.stunRelayTcpPort; config.stunRelayTcpUser = pending.stunRelayTcpUser; config.stunRelayTcpPass = pending.stunRelayTcpPass; } // for now, only allow setting localAddrs once if (!pending.localAddrs.isEmpty() && config.localAddrs.isEmpty()) { for (const Ice176::LocalAddress &la : pending.localAddrs) { // skip duplicate addrs if (findLocalAddr(la.addr) != -1) continue; QUdpSocket *qsock = nullptr; if (useLocal && socketList) { qsock = takeFromSocketList(socketList, la.addr, this); } bool borrowedSocket = qsock != nullptr; if (!qsock) { // otherwise, bind to random qsock = new QUdpSocket(this); if (!qsock->bind(la.addr, 0)) { delete qsock; emit q->debugLine("Warning: unable to bind to random port."); continue; } } config.localAddrs += la; auto lt = createLocalTransport(qsock, la); lt->borrowed = borrowedSocket; udpTransports += lt; if (lt->addr.protocol() != QAbstractSocket::IPv6Protocol) { lt->sock->setClientSoftwareNameAndVersion(clientSoftware); if (useStunBind && !config.stunBindAddr.isNull()) { lt->sock->setStunBindService(config.stunBindAddr, config.stunBindPort); } if (useStunRelayUdp && !config.stunRelayUdpAddr.isNull() && !config.stunRelayUdpUser.isEmpty()) { lt->sock->setStunRelayService(config.stunRelayUdpAddr, config.stunRelayUdpPort, config.stunRelayUdpUser, config.stunRelayUdpPass); } } int port = qsock->localPort(); lt->sock->start(qsock); emit q->debugLine(QString("starting transport ") + la.addr.toString() + ';' + QString::number(port) + " for component " + QString::number(id)); } } // extAddrs created on demand if present, but only once if (!pending.extAddrs.isEmpty() && config.extAddrs.isEmpty()) { config.extAddrs = pending.extAddrs; bool need_doExt = false; for (auto lt : udpTransports) { // already assigned an ext address? skip if (!lt->extAddr.isNull()) continue; QHostAddress laddr = lt->sock->localAddress(); int lport = lt->sock->localPort(); if (laddr.protocol() == QAbstractSocket::IPv6Protocol) continue; // find external address by address of local socket (external has to be configured that way) auto eaIt = std::find_if(config.extAddrs.constBegin(), config.extAddrs.constEnd(), [&](auto const &ea) { return ea.base.addr == laddr && (ea.portBase == -1 || ea.portBase == lport); }); if (eaIt != config.extAddrs.constEnd()) { lt->extAddr = eaIt->addr; if (lt->started) need_doExt = true; } } if (need_doExt) QTimer::singleShot(0, this, [this]() { if (stopping) return; ObjectSessionWatcher watch(&sess); for (auto lt : udpTransports) { if (lt->started) { int addrAt = findLocalAddr(lt->addr); Q_ASSERT(addrAt != -1); ensureExt(lt, addrAt); // will emit candidateAdded if everything goes well if (!watch.isValid()) return; } } }); } if (useStunRelayTcp && !config.stunRelayTcpAddr.isNull() && !config.stunRelayTcpUser.isEmpty() && !tcpTurn) { tcpTurn = QSharedPointer::create(); tcpTurn->setDebugLevel(IceTransport::DebugLevel(debugLevel)); connect(tcpTurn.data(), SIGNAL(started()), SLOT(tt_started())); connect(tcpTurn.data(), SIGNAL(stopped()), SLOT(tt_stopped())); connect(tcpTurn.data(), SIGNAL(error(int)), SLOT(tt_error(int))); connect(tcpTurn.data(), SIGNAL(debugLine(QString)), SLOT(tt_debugLine(QString))); tcpTurn->setClientSoftwareNameAndVersion(clientSoftware); tcpTurn->setProxy(proxy); tcpTurn->setUsername(config.stunRelayTcpUser); tcpTurn->setPassword(config.stunRelayTcpPass); tcpTurn->start(config.stunRelayTcpAddr, config.stunRelayTcpPort); emit q->debugLine(QString("starting TURN transport with server ") + config.stunRelayTcpAddr.toString() + ';' + QString::number(config.stunRelayTcpPort) + " for component " + QString::number(id)); } if (udpTransports.isEmpty() && !localFinished) { localFinished = true; sess.defer(q, "localFinished"); } sess.defer(this, "tryGatheringComplete"); } void stop() { Q_ASSERT(!stopping); stopping = true; // nothing to stop? if (allStopped()) { sess.defer(this, "postStop"); return; } for (LocalTransport *lt : udpTransports) lt->sock->stop(); if (tcpTurn) tcpTurn->stop(); } int peerReflexivePriority(QSharedPointer iceTransport, int path) const { int addrAt = -1; const IceLocalTransport *lt = qobject_cast(iceTransport.data()); if (lt) { auto it = std::find_if(udpTransports.begin(), udpTransports.end(), [&](auto const &a) { return a->sock == lt; }); Q_ASSERT(it != udpTransports.end()); addrAt = std::distance(udpTransports.begin(), it); if (path == 1) { // lower priority, but not as far as IceTurnTransport addrAt += 512; } } else if (qobject_cast(iceTransport) == tcpTurn) { // lower priority by making it seem like the last nic addrAt = 1024; } return choose_default_priority(PeerReflexiveType, 65535 - addrAt, false, id); } void flagPathAsLowOverhead(int id, const QHostAddress &addr, int port) { int at = -1; for (int n = 0; n < localCandidates.count(); ++n) { if (localCandidates[n].id == id) { at = n; break; } } Q_ASSERT(at != -1); if (at == -1) return; Candidate &c = localCandidates[at]; TransportAddress ta(addr, port); QSet &addrs = channelPeers[c.id]; if (!addrs.contains(ta)) { addrs += ta; c.iceTransport->addChannelPeer(ta.addr, ta.port); } } void addLocalPeerReflexiveCandidate(const IceComponent::TransportAddress &addr, IceComponent::CandidateInfo::Ptr base, quint32 priority) { auto ci = IceComponent::CandidateInfo::Ptr::create(); ci->addr = addr; ci->addr.addr.setScopeId(QString()); ci->base = base->addr; ci->type = IceComponent::PeerReflexiveType; ci->priority = priority; ci->foundation = IceAgent::instance()->foundation(IceComponent::PeerReflexiveType, ci->base.addr); ci->componentId = base->componentId; ci->network = base->network; auto baseCand = std::find_if(localCandidates.begin(), localCandidates.end(), [&](auto const &c) { return c.info->base == base->base && c.info->type == HostType; }); Q_ASSERT(baseCand != localCandidates.end()); Candidate c; c.id = getId(); c.info = ci; c.iceTransport = baseCand->iceTransport; c.path = 0; localCandidates += c; emit q->candidateAdded(c); } private: // localPref is the priority of the network interface being used for // this candidate. the value must be between 0-65535 and different // interfaces must have different values. if there is only one // interface, the value should be 65535. static int choose_default_priority(CandidateType type, int localPref, bool isVpn, int componentId) { int typePref; if (type == HostType) { if (isVpn) typePref = 0; else typePref = 126; } else if (type == PeerReflexiveType) typePref = 110; else if (type == ServerReflexiveType) typePref = 100; else // RelayedType typePref = 0; return calc_priority(typePref, localPref, componentId); } static QUdpSocket *takeFromSocketList(QList *socketList, const QHostAddress &addr, QObject *parent = nullptr) { for (int n = 0; n < socketList->count(); ++n) { if ((*socketList)[n]->localAddress() == addr) { QUdpSocket *sock = socketList->takeAt(n); sock->setParent(parent); return sock; } } return nullptr; } int getId() const { for (int n = 0;; ++n) { bool found = false; for (const Candidate &c : localCandidates) { if (c.id == n) { found = true; break; } } if (!found) return n; } } int findLocalAddr(const QHostAddress &addr) { for (int n = 0; n < config.localAddrs.count(); ++n) { if (config.localAddrs[n].addr == addr) return n; } return -1; } void ensureExt(LocalTransport *lt, int addrAt) { if (!lt->extAddr.isNull() && !lt->ext_finished) { auto ci = CandidateInfo::Ptr::create(); ci->addr.addr = lt->extAddr; ci->addr.port = lt->sock->localPort(); ci->type = ServerReflexiveType; ci->componentId = id; ci->priority = choose_default_priority(ci->type, 65535 - addrAt, lt->isVpn, ci->componentId); ci->base.addr = lt->sock->localAddress(); ci->base.port = lt->sock->localPort(); ci->network = lt->network; ci->foundation = IceAgent::instance()->foundation(ServerReflexiveType, ci->base.addr); Candidate c; c.id = getId(); c.info = ci; c.iceTransport = lt->sock; c.path = 0; lt->ext_finished = true; storeLocalNotReduntantCandidate(c); } } void removeLocalCandidates(const QSharedPointer sock) { ObjectSessionWatcher watch(&sess); for (int n = 0; n < localCandidates.count(); ++n) { Candidate &c = localCandidates[n]; if (c.iceTransport == sock) { Candidate tmp = localCandidates.takeAt(n); --n; // adjust position channelPeers.remove(tmp.id); emit q->candidateRemoved(tmp); if (!watch.isValid()) return; } } } void storeLocalNotReduntantCandidate(const Candidate &c) { ObjectSessionWatcher watch(&sess); // RFC8445 5.1.3. Eliminating Redundant Candidates auto it = std::find_if(localCandidates.begin(), localCandidates.end(), [&](const Candidate &cc) { return cc.info->addr == c.info->addr && cc.info->base == c.info->base && cc.info->priority >= c.info->priority; }); if (it == localCandidates.end()) { // not reduntant localCandidates += c; emit q->candidateAdded(c); } } bool allStopped() const { return udpTransports.isEmpty() && !tcpTurn; } void tryStopped() { if (allStopped()) postStop(); } // return true if component is still alive after transport removal bool eraseLocalTransport(LocalTransport *lt) { ObjectSessionWatcher watch(&sess); removeLocalCandidates(lt->sock); if (!watch.isValid()) return false; lt->sock->disconnect(this); if (lt->borrowed) { lt->qsock->disconnect(this); portReserver->returnSockets({ lt->qsock }); } delete lt; udpTransports.removeOne(lt); return true; } private slots: void tryGatheringComplete() { if (gatheringComplete || (tcpTurn && !tcpTurn->isStarted())) return; auto checkFinished = [&](const LocalTransport *lt) { return lt->started && (lt->sock->stunBindServiceAddress().isNull() || lt->stun_finished) && (lt->sock->stunRelayServiceAddress().isNull() || lt->turn_finished); }; bool allFinished = true; for (const LocalTransport *lt : udpTransports) { if (!checkFinished(lt)) { allFinished = false; break; } } if (allFinished) { gatheringComplete = true; emit q->gatheringComplete(); } } void postStop() { stopping = false; emit q->stopped(); } void lt_started() { IceLocalTransport *sock = static_cast(sender()); auto it = std::find_if(udpTransports.begin(), udpTransports.end(), [&](auto const &a) { return a->sock == sock; }); Q_ASSERT(it != udpTransports.end()); LocalTransport *lt = *it; lt->started = true; int addrAt = findLocalAddr(lt->addr); Q_ASSERT(addrAt != -1); ObjectSessionWatcher watch(&sess); if (useLocal) { auto ci = CandidateInfo::Ptr::create(); ci->addr.addr = lt->sock->localAddress(); ci->addr.port = lt->sock->localPort(); ci->type = HostType; ci->componentId = id; ci->priority = choose_default_priority(ci->type, 65535 - addrAt, lt->isVpn, ci->componentId); ci->base = ci->addr; ci->network = lt->network; ci->foundation = IceAgent::instance()->foundation(HostType, ci->base.addr); Candidate c; c.id = getId(); c.info = ci; c.iceTransport = sock->sharedFromThis(); c.path = 0; localCandidates += c; emit q->candidateAdded(c); if (!watch.isValid()) return; ensureExt(lt, addrAt); if (!watch.isValid()) return; } if (!lt->stun_started && (!lt->sock->stunBindServiceAddress().isNull() || !lt->sock->stunRelayServiceAddress().isNull())) { lt->stun_started = true; lt->sock->stunStart(); if (!watch.isValid()) return; } // check completeness of various stuff if (!localFinished) { bool allStarted = true; for (const LocalTransport *lt : udpTransports) { if (!lt->started) { allStarted = false; break; } } if (allStarted) { localFinished = true; emit q->localFinished(); if (!watch.isValid()) return; } } tryGatheringComplete(); } void lt_addressesChanged() { IceLocalTransport *sock = static_cast(sender()); auto it = std::find_if(udpTransports.begin(), udpTransports.end(), [&](auto const &a) { return a->sock == sock; }); Q_ASSERT(it != udpTransports.end()); LocalTransport *lt = *it; int addrAt = findLocalAddr(lt->addr); Q_ASSERT(addrAt != -1); ObjectSessionWatcher watch(&sess); if (useStunBind && !lt->sock->serverReflexiveAddress().isNull() && !lt->stun_finished) { // automatically assign ext to related leaps, if possible for (LocalTransport *i : udpTransports) { if (i->extAddr.isNull() && i->sock->localAddress() == lt->sock->localAddress()) { i->extAddr = lt->sock->serverReflexiveAddress(); if (i->started) { ensureExt(i, addrAt); if (!watch.isValid()) return; } } } auto ci = CandidateInfo::Ptr::create(); ci->addr.addr = lt->sock->serverReflexiveAddress(); ci->addr.port = lt->sock->serverReflexivePort(); ci->base.addr = lt->sock->localAddress(); ci->base.port = lt->sock->localPort(); ci->type = ServerReflexiveType; ci->componentId = id; ci->priority = choose_default_priority(ci->type, 65535 - addrAt, lt->isVpn, ci->componentId); ci->network = lt->network; ci->foundation = IceAgent::instance()->foundation( ServerReflexiveType, ci->base.addr, lt->sock->reflexiveAddressSource(), QAbstractSocket::UdpSocket); Candidate c; c.id = getId(); c.info = ci; c.iceTransport = sock->sharedFromThis(); c.path = 0; lt->stun_finished = true; storeLocalNotReduntantCandidate(c); } if (!lt->sock->relayedAddress().isNull() && !lt->turn_finished) { auto ci = CandidateInfo::Ptr::create(); ci->addr.addr = lt->sock->relayedAddress(); ci->addr.port = lt->sock->relayedPort(); ci->base.addr = lt->sock->relayedAddress(); ci->base.port = lt->sock->relayedPort(); ci->type = RelayedType; ci->componentId = id; ci->priority = choose_default_priority(ci->type, 65535 - addrAt, lt->isVpn, ci->componentId); ci->network = lt->network; ci->foundation = IceAgent::instance()->foundation( RelayedType, ci->base.addr, lt->sock->stunRelayServiceAddress(), QAbstractSocket::UdpSocket); Candidate c; c.id = getId(); c.info = ci; c.iceTransport = sock->sharedFromThis(); c.path = 1; lt->turn_finished = true; storeLocalNotReduntantCandidate(c); } if (!watch.isValid()) return; tryGatheringComplete(); } void lt_debugLine(const QString &line) { emit q->debugLine(line); } void tt_started() { // lower priority by making it seem like the last nic int addrAt = 1024; auto ci = CandidateInfo::Ptr::create(); ci->addr.addr = tcpTurn->relayedAddress(); ci->addr.port = tcpTurn->relayedPort(); ci->type = RelayedType; ci->componentId = id; ci->priority = choose_default_priority(ci->type, 65535 - addrAt, false, ci->componentId); ci->base = ci->addr; ci->network = 0; // not relevant ci->foundation = IceAgent::instance()->foundation(RelayedType, ci->base.addr, config.stunRelayTcpAddr, QAbstractSocket::TcpSocket); Candidate c; c.id = getId(); c.info = ci; c.iceTransport = tcpTurn->sharedFromThis(); c.path = 0; localCandidates += c; emit q->candidateAdded(c); tryGatheringComplete(); } void tt_stopped() { ObjectSessionWatcher watch(&sess); removeLocalCandidates(tcpTurn->sharedFromThis()); if (!watch.isValid()) return; tcpTurn->disconnect(this); tcpTurn.reset(); tryStopped(); } void tt_error(int e) { Q_UNUSED(e) ObjectSessionWatcher watch(&sess); removeLocalCandidates(tcpTurn); if (!watch.isValid()) return; tcpTurn->disconnect(this); tcpTurn.reset(); tryGatheringComplete(); } void tt_debugLine(const QString &line) { emit q->debugLine(line); } }; IceComponent::IceComponent(int id, QObject *parent) : QObject(parent) { d = new Private(this); d->id = id; } IceComponent::~IceComponent() { delete d; } int IceComponent::id() const { return d->id; } bool IceComponent::isGatheringComplete() const { return d->gatheringComplete; } void IceComponent::setClientSoftwareNameAndVersion(const QString &str) { d->clientSoftware = str; } void IceComponent::setProxy(const TurnClient::Proxy &proxy) { d->proxy = proxy; } void IceComponent::setPortReserver(UdpPortReserver *portReserver) { d->portReserver = portReserver; } UdpPortReserver *IceComponent::portReserver() const { return d->portReserver; } void IceComponent::setLocalAddresses(const QList &addrs) { d->pending.localAddrs = addrs; } void IceComponent::setExternalAddresses(const QList &addrs) { d->pending.extAddrs = addrs; } void IceComponent::setStunBindService(const QHostAddress &addr, int port) { d->pending.stunBindAddr = addr; d->pending.stunBindPort = port; } void IceComponent::setStunRelayUdpService(const QHostAddress &addr, int port, const QString &user, const QCA::SecureArray &pass) { d->pending.stunRelayUdpAddr = addr; d->pending.stunRelayUdpPort = port; d->pending.stunRelayUdpUser = user; d->pending.stunRelayUdpPass = pass; } void IceComponent::setStunRelayTcpService(const QHostAddress &addr, int port, const QString &user, const QCA::SecureArray &pass) { d->pending.stunRelayTcpAddr = addr; d->pending.stunRelayTcpPort = port; d->pending.stunRelayTcpUser = user; d->pending.stunRelayTcpPass = pass; } void IceComponent::setUseLocal(bool enabled) { d->useLocal = enabled; } void IceComponent::setUseStunBind(bool enabled) { d->useStunBind = enabled; } void IceComponent::setUseStunRelayUdp(bool enabled) { d->useStunRelayUdp = enabled; } void IceComponent::setUseStunRelayTcp(bool enabled) { d->useStunRelayTcp = enabled; } void IceComponent::update(QList *socketList) { d->update(socketList); } void IceComponent::stop() { d->stop(); } int IceComponent::peerReflexivePriority(QSharedPointer iceTransport, int path) const { return d->peerReflexivePriority(iceTransport, path); } void IceComponent::addLocalPeerReflexiveCandidate(const IceComponent::TransportAddress &addr, IceComponent::CandidateInfo::Ptr base, quint32 priority) { d->addLocalPeerReflexiveCandidate(addr, base, priority); } void IceComponent::flagPathAsLowOverhead(int id, const QHostAddress &addr, int port) { return d->flagPathAsLowOverhead(id, addr, port); } void IceComponent::setDebugLevel(DebugLevel level) { d->debugLevel = level; for (const Private::LocalTransport *lt : d->udpTransports) lt->sock->setDebugLevel(IceTransport::DebugLevel(level)); if (d->tcpTurn) d->tcpTurn->setDebugLevel((IceTransport::DebugLevel)level); } IceComponent::CandidateInfo::Ptr IceComponent::CandidateInfo::makeRemotePrflx(int componentId, const QHostAddress &fromAddr, quint16 fromPort, quint32 priority) { auto c = IceComponent::CandidateInfo::Ptr::create(); c->addr = TransportAddress(fromAddr, fromPort); c->addr.addr.setScopeId(QString()); c->type = PeerReflexiveType; c->priority = priority; c->foundation = QUuid::createUuid().toString(); c->componentId = componentId; c->network = -1; return c; } } // namespace XMPP #include "icecomponent.moc" psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/icecomponent.h000066400000000000000000000142551370065651000252730ustar00rootroot00000000000000/* * Copyright (C) 2010 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef ICECOMPONENT_H #define ICECOMPONENT_H #include "ice176.h" #include "icetransport.h" #include "turnclient.h" #include class QUdpSocket; namespace XMPP { class UdpPortReserver; class IceComponent : public QObject { Q_OBJECT public: enum CandidateType { HostType, PeerReflexiveType, ServerReflexiveType, RelayedType }; class TransportAddress { public: QHostAddress addr; int port; TransportAddress() : port(-1) { } TransportAddress(const QHostAddress &_addr, int _port) : addr(_addr), port(_port) { } bool operator==(const TransportAddress &other) const { if (addr == other.addr && port == other.port) return true; else return false; } inline bool operator!=(const TransportAddress &other) const { return !operator==(other); } inline operator QString() const { return QString("%1:%2").arg(addr.toString(), QString::number(port)); } }; class CandidateInfo { public: using Ptr = QSharedPointer; CandidateType type; int priority; int componentId; int network; TransportAddress addr; // address according to candidate type TransportAddress base; // network interface address TransportAddress related; // not used in agent but usefule for diagnostics QString foundation; QString id; static Ptr makeRemotePrflx(int componentId, const QHostAddress &fromAddr, quint16 fromPort, quint32 priority); inline bool operator==(const CandidateInfo &o) const { return addr == o.addr && componentId == o.componentId; } inline bool operator==(CandidateInfo::Ptr o) const { return *this == *o; } }; class Candidate { public: // unique across all candidates within this component int id; // info.id is unset, since it must be unique across all // components and this class is only aware of itself. it // is up to the user to create the candidate id. // info.foundation is also unset, since awareness of all // components and candidates is needed to calculate it. CandidateInfo::Ptr info; // note that these may be the same for multiple candidates QSharedPointer iceTransport; int path; }; enum DebugLevel { DL_None, DL_Info, DL_Packet }; IceComponent(int id, QObject *parent = nullptr); ~IceComponent(); int id() const; bool isGatheringComplete() const; void setClientSoftwareNameAndVersion(const QString &str); void setProxy(const TurnClient::Proxy &proxy); void setPortReserver(UdpPortReserver *portReserver); UdpPortReserver *portReserver() const; // can be set once, but later changes are ignored void setLocalAddresses(const QList &addrs); // can be set once, but later changes are ignored. local addresses // must have been set for this to work void setExternalAddresses(const QList &addrs); // can be set at any time, but only once. later changes are ignored void setStunBindService(const QHostAddress &addr, int port); void setStunRelayUdpService(const QHostAddress &addr, int port, const QString &user, const QCA::SecureArray &pass); void setStunRelayTcpService(const QHostAddress &addr, int port, const QString &user, const QCA::SecureArray &pass); // these all start out enabled, but can be disabled for diagnostic // purposes void setUseLocal(bool enabled); // where to make local host candidates void setUseStunBind(bool enabled); void setUseStunRelayUdp(bool enabled); void setUseStunRelayTcp(bool enabled); /** * @brief update component with local listening sockets * @param socketList * If socketList is not null then port reserver must be set. * If the pool doesn't have enough sockets, the component will allocate its own. */ void update(QList *socketList = nullptr); void stop(); // prflx priority to use when replying from this transport/path int peerReflexivePriority(QSharedPointer iceTransport, int path) const; void addLocalPeerReflexiveCandidate(const TransportAddress &addr, CandidateInfo::Ptr base, quint32 priority); void flagPathAsLowOverhead(int id, const QHostAddress &addr, int port); void setDebugLevel(DebugLevel level); signals: // this is emitted in the same pass of the eventloop that a // transport/path becomes ready void candidateAdded(const XMPP::IceComponent::Candidate &c); // this is emitted just before a transport/path will be deleted void candidateRemoved(const XMPP::IceComponent::Candidate &c); // indicates all the initial HostType candidates have been pushed. // note that it is possible there are no HostType candidates. void localFinished(); // no more candidates will be emitted unless network candidition changes void gatheringComplete(); void stopped(); // reports debug of iceTransports as well. not DOR-SS/DS safe void debugLine(const QString &line); private: class Private; friend class Private; Private *d; }; inline uint qHash(const XMPP::IceComponent::TransportAddress &key, uint seed = 0) { return ::qHash(key.addr, seed) ^ ::qHash(key.port, seed); } } // namespace XMPP #endif // ICECOMPONENT_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/icelocaltransport.cpp000066400000000000000000000547151370065651000267000ustar00rootroot00000000000000/* * Copyright (C) 2009-2010 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "icelocaltransport.h" #include "objectsession.h" #include "stunallocate.h" #include "stunbinding.h" #include "stunmessage.h" #include "stuntransaction.h" #include "turnclient.h" #include #include #include // don't queue more incoming packets than this per transmit path #define MAX_PACKET_QUEUE 64 namespace XMPP { enum { Direct, Relayed }; //---------------------------------------------------------------------------- // SafeUdpSocket //---------------------------------------------------------------------------- // DOR-safe wrapper for QUdpSocket class SafeUdpSocket : public QObject { Q_OBJECT private: ObjectSession sess; QUdpSocket * sock; int writtenCount; public: SafeUdpSocket(QUdpSocket *_sock, QObject *parent = nullptr) : QObject(parent), sess(this), sock(_sock) { sock->setParent(this); connect(sock, SIGNAL(readyRead()), SLOT(sock_readyRead())); connect(sock, SIGNAL(bytesWritten(qint64)), SLOT(sock_bytesWritten(qint64))); writtenCount = 0; } ~SafeUdpSocket() { if (sock) { QUdpSocket *out = release(); out->deleteLater(); } } QUdpSocket *release() { sock->disconnect(this); sock->setParent(nullptr); QUdpSocket *out = sock; sock = nullptr; return out; } QHostAddress localAddress() const { return sock->localAddress(); } quint16 localPort() const { return sock->localPort(); } bool hasPendingDatagrams() const { return sock->hasPendingDatagrams(); } QByteArray readDatagram(QHostAddress *address = nullptr, quint16 *port = nullptr) { if (!sock->hasPendingDatagrams()) return QByteArray(); QByteArray buf; buf.resize(sock->pendingDatagramSize()); sock->readDatagram(buf.data(), buf.size(), address, port); return buf; } void writeDatagram(const QByteArray &buf, const QHostAddress &address, quint16 port) { sock->writeDatagram(buf, address, port); } signals: void readyRead(); void datagramsWritten(int count); private slots: void sock_readyRead() { emit readyRead(); } void sock_bytesWritten(qint64 bytes) { Q_UNUSED(bytes); ++writtenCount; sess.deferExclusive(this, "processWritten"); } void processWritten() { int count = writtenCount; writtenCount = 0; emit datagramsWritten(count); } }; //---------------------------------------------------------------------------- // IceLocalTransport //---------------------------------------------------------------------------- class IceLocalTransport::Private : public QObject { Q_OBJECT public: class WriteItem { public: enum Type { Direct, Pool, Turn }; Type type; QHostAddress addr; int port; }; class Written { public: QHostAddress addr; int port; int count; }; class Datagram { public: QHostAddress addr; int port; QByteArray buf; }; IceLocalTransport * q; ObjectSession sess; QUdpSocket * extSock; SafeUdpSocket * sock; StunTransactionPool::Ptr pool; StunBinding * stunBinding; TurnClient * turn; bool turnActivated; QHostAddress addr; int port; QHostAddress refAddr; int refPort; QHostAddress refAddrSource; QHostAddress relAddr; int relPort; QHostAddress stunBindAddr; int stunBindPort; QHostAddress stunRelayAddr; int stunRelayPort; QString stunUser; QCA::SecureArray stunPass; QString clientSoftware; QList in; QList inRelayed; QList pendingWrites; int retryCount; bool stopping; int debugLevel; Private(IceLocalTransport *_q) : QObject(_q), q(_q), sess(this), extSock(nullptr), sock(nullptr), pool(nullptr), stunBinding(nullptr), turn(nullptr), turnActivated(false), port(-1), refPort(-1), relPort(-1), retryCount(0), stopping(false), debugLevel(IceTransport::DL_None) { } ~Private() { reset(); } void reset() { sess.reset(); delete stunBinding; stunBinding = nullptr; delete turn; turn = nullptr; turnActivated = false; if (sock) { // if started if (extSock) { sock->release(); // detaches the socket but doesn't destroy extSock = nullptr; } delete sock; sock = nullptr; } addr = QHostAddress(); port = -1; refAddr = QHostAddress(); refPort = -1; refAddrSource = QHostAddress(); relAddr = QHostAddress(); relPort = -1; in.clear(); inRelayed.clear(); pendingWrites.clear(); retryCount = 0; stopping = false; } void start() { Q_ASSERT(!sock); sess.defer(this, "postStart"); } void stop() { Q_ASSERT(sock); if (stopping) { auto as = QString("%1:%2").arg(addr.toString(), QString::number(port)); emit q->debugLine(QString("local transport %1 is already stopping. just wait...").arg(as)); return; } else { auto as = QString("%1:%2").arg(addr.toString(), QString::number(port)); emit q->debugLine(QString("stopping local transport %1.").arg(as)); } stopping = true; if (turn) turn->close(); // will emit stopped() eventually calling postStop() else sess.defer(this, "postStop"); } void stunStart() { Q_ASSERT(!pool); pool = StunTransactionPool::Ptr::create(StunTransaction::Udp); pool->setDebugLevel((StunTransactionPool::DebugLevel)debugLevel); connect(pool.data(), SIGNAL(outgoingMessage(QByteArray, QHostAddress, int)), SLOT(pool_outgoingMessage(QByteArray, QHostAddress, int))); connect(pool.data(), SIGNAL(needAuthParams()), SLOT(pool_needAuthParams())); connect(pool.data(), SIGNAL(debugLine(QString)), SLOT(pool_debugLine(QString))); pool->setLongTermAuthEnabled(true); if (!stunUser.isEmpty()) { pool->setUsername(stunUser); pool->setPassword(stunPass); } do_stun(); do_turn(); } void do_stun() { if (stunBindAddr.isNull()) { return; } stunBinding = new StunBinding(pool.data()); connect(stunBinding, &StunBinding::success, this, [&]() { refAddr = stunBinding->reflexiveAddress(); refPort = stunBinding->reflexivePort(); refAddrSource = stunBindAddr; delete stunBinding; stunBinding = nullptr; emit q->addressesChanged(); }); connect(stunBinding, &StunBinding::error, this, [&](XMPP::StunBinding::Error) { delete stunBinding; stunBinding = nullptr; }); stunBinding->start(stunBindAddr, stunBindPort); } void do_turn() { if (stunRelayAddr.isNull()) { return; } turn = new TurnClient(this); turn->setDebugLevel((TurnClient::DebugLevel)debugLevel); connect(turn, SIGNAL(connected()), SLOT(turn_connected())); connect(turn, SIGNAL(tlsHandshaken()), SLOT(turn_tlsHandshaken())); connect(turn, SIGNAL(closed()), SLOT(turn_closed())); connect(turn, SIGNAL(activated()), SLOT(turn_activated())); connect(turn, SIGNAL(packetsWritten(int, QHostAddress, int)), SLOT(turn_packetsWritten(int, QHostAddress, int))); connect(turn, SIGNAL(error(XMPP::TurnClient::Error)), SLOT(turn_error(XMPP::TurnClient::Error))); connect(turn, SIGNAL(outgoingDatagram(QByteArray)), SLOT(turn_outgoingDatagram(QByteArray))); connect(turn, SIGNAL(debugLine(QString)), SLOT(turn_debugLine(QString))); turn->setClientSoftwareNameAndVersion(clientSoftware); turn->connectToHost(pool.data(), stunRelayAddr, stunRelayPort); } private: // note: emits signal on error QUdpSocket *createSocket() { QUdpSocket *qsock = new QUdpSocket(this); if (!qsock->bind(addr, 0)) { delete qsock; emit q->error(IceLocalTransport::ErrorBind); return nullptr; } return qsock; } void prepareSocket() { addr = sock->localAddress(); port = sock->localPort(); connect(sock, SIGNAL(readyRead()), SLOT(sock_readyRead())); connect(sock, SIGNAL(datagramsWritten(int)), SLOT(sock_datagramsWritten(int))); } // return true if we are retrying, false if we should error out bool handleRetry() { // don't allow retrying if activated or stopping) if (turnActivated || stopping) return false; ++retryCount; if (retryCount < 3) { if (debugLevel >= IceTransport::DL_Info) emit q->debugLine("retrying..."); delete sock; sock = nullptr; // to receive this error, it is a Relay, so change // the mode // stunType = IceLocalTransport::Relay; QUdpSocket *qsock = createSocket(); if (!qsock) { // signal emitted in this case. bail. // (return true so caller takes no action) return true; } sock = new SafeUdpSocket(qsock, this); prepareSocket(); refAddr = QHostAddress(); refPort = -1; refAddrSource = QHostAddress(); relAddr = QHostAddress(); relPort = -1; do_turn(); // tell the world that our local address probably // changed, and that we lost our reflexive address emit q->addressesChanged(); return true; } return false; } // return true if data packet, false if pool or nothing bool processIncomingStun(const QByteArray &buf, const QHostAddress &fromAddr, int fromPort, Datagram *dg) { QByteArray data; QHostAddress dataAddr; int dataPort; bool notStun; if (!pool->writeIncomingMessage(buf, ¬Stun, fromAddr, fromPort) && turn) { data = turn->processIncomingDatagram(buf, notStun, &dataAddr, &dataPort); if (!data.isNull()) { dg->addr = dataAddr; dg->port = dataPort; dg->buf = data; return true; } else { if (debugLevel >= IceTransport::DL_Packet) emit q->debugLine( "Warning: server responded with what doesn't seem to be a STUN or data packet, skipping."); } } return false; } private slots: void postStart() { if (stopping) return; if (extSock) { sock = new SafeUdpSocket(extSock, this); } else { QUdpSocket *qsock = createSocket(); if (!qsock) { // signal emitted in this case. bail return; } sock = new SafeUdpSocket(qsock, this); } prepareSocket(); emit q->started(); } void postStop() { reset(); emit q->stopped(); } void sock_readyRead() { ObjectSessionWatcher watch(&sess); QList dreads; // direct QList rreads; // relayed while (sock->hasPendingDatagrams()) { QHostAddress from; quint16 fromPort; Datagram dg; QByteArray buf = sock->readDatagram(&from, &fromPort); if ((from == stunBindAddr && fromPort == stunBindPort) || (from == stunRelayAddr && fromPort == stunRelayPort)) { bool haveData = processIncomingStun(buf, from, fromPort, &dg); // processIncomingStun could cause signals to // emit. for example, stopped() if (!watch.isValid()) return; if (haveData) rreads += dg; } else { dg.addr = from; dg.port = fromPort; dg.buf = buf; dreads += dg; } } if (dreads.count() > 0) { in += dreads; emit q->readyRead(Direct); if (!watch.isValid()) return; } if (rreads.count() > 0) { inRelayed += rreads; emit q->readyRead(Relayed); } } void sock_datagramsWritten(int count) { QList dwrites; int twrites = 0; while (count > 0) { Q_ASSERT(!pendingWrites.isEmpty()); WriteItem wi = pendingWrites.takeFirst(); --count; if (wi.type == WriteItem::Direct) { int at = -1; for (int n = 0; n < dwrites.count(); ++n) { if (dwrites[n].addr == wi.addr && dwrites[n].port == wi.port) { at = n; break; } } if (at != -1) { ++dwrites[at].count; } else { Written wr; wr.addr = wi.addr; wr.port = wi.port; wr.count = 1; dwrites += wr; } } else if (wi.type == WriteItem::Turn) ++twrites; } if (dwrites.isEmpty() && twrites == 0) return; ObjectSessionWatcher watch(&sess); if (!dwrites.isEmpty()) { for (const Written &wr : dwrites) { emit q->datagramsWritten(Direct, wr.count, wr.addr, wr.port); if (!watch.isValid()) return; } } if (twrites > 0) { // note: this will invoke turn_packetsWritten() turn->outgoingDatagramsWritten(twrites); } } void pool_outgoingMessage(const QByteArray &packet, const QHostAddress &toAddress, int toPort) { // warning: read StunTransactionPool docs before modifying // this function WriteItem wi; wi.type = WriteItem::Pool; pendingWrites += wi; // emit q->debugLine(QString("Sending udp packet from: %1:%2 to: %3:%4") // .arg(sock->localAddress().toString()) // .arg(sock->localPort()) // .arg(toAddress.toString()) // .arg(toPort)); sock->writeDatagram(packet, toAddress, toPort); } void pool_needAuthParams() { // we can get this signal if the user did not provide // creds to us. however, since this class doesn't support // prompting just continue on as if we had a blank // user/pass pool->continueAfterParams(); } void pool_debugLine(const QString &line) { emit q->debugLine(line); } void turn_connected() { if (debugLevel >= IceTransport::DL_Info) emit q->debugLine("turn_connected"); } void turn_tlsHandshaken() { if (debugLevel >= IceTransport::DL_Info) emit q->debugLine("turn_tlsHandshaken"); } void turn_closed() { if (debugLevel >= IceTransport::DL_Info) emit q->debugLine("turn_closed"); delete turn; turn = nullptr; turnActivated = false; postStop(); } void turn_activated() { StunAllocate *allocate = turn->stunAllocate(); // take reflexive address from TURN only if we are not using a // separate STUN server if (stunBindAddr.isNull() || stunBindAddr == stunRelayAddr) { refAddr = allocate->reflexiveAddress(); refPort = allocate->reflexivePort(); refAddrSource = stunRelayAddr; } if (debugLevel >= IceTransport::DL_Info) emit q->debugLine(QString("Server says we are ") + allocate->reflexiveAddress().toString() + ';' + QString::number(allocate->reflexivePort())); relAddr = allocate->relayedAddress(); relPort = allocate->relayedPort(); if (debugLevel >= IceTransport::DL_Info) emit q->debugLine(QString("Server relays via ") + relAddr.toString() + ';' + QString::number(relPort)); turnActivated = true; emit q->addressesChanged(); } void turn_packetsWritten(int count, const QHostAddress &addr, int port) { emit q->datagramsWritten(Relayed, count, addr, port); } void turn_error(XMPP::TurnClient::Error e) { if (debugLevel >= IceTransport::DL_Info) emit q->debugLine(QString("turn_error: ") + turn->errorString()); delete turn; turn = nullptr; bool wasActivated = turnActivated; turnActivated = false; if (e == TurnClient::ErrorMismatch) { if (!extSock && handleRetry()) return; } // this means our relay died on us. in the future we might // consider reporting this if (wasActivated) return; // don't report any error // if(stunType == IceLocalTransport::Relay || (stunType == IceLocalTransport::Auto && !stunBinding)) // emit q->addressesChanged(); } void turn_outgoingDatagram(const QByteArray &buf) { WriteItem wi; wi.type = WriteItem::Turn; pendingWrites += wi; sock->writeDatagram(buf, stunRelayAddr, stunRelayPort); } void turn_debugLine(const QString &line) { emit q->debugLine(line); } }; IceLocalTransport::IceLocalTransport(QObject *parent) : IceTransport(parent) { d = new Private(this); } IceLocalTransport::~IceLocalTransport() { delete d; } void IceLocalTransport::setClientSoftwareNameAndVersion(const QString &str) { d->clientSoftware = str; } void IceLocalTransport::start(QUdpSocket *sock) { d->extSock = sock; d->start(); } void IceLocalTransport::start(const QHostAddress &addr) { d->addr = addr; d->start(); } void IceLocalTransport::stop() { d->stop(); } void IceLocalTransport::setStunBindService(const QHostAddress &addr, int port) { d->stunBindAddr = addr; d->stunBindPort = port; } void IceLocalTransport::setStunRelayService(const QHostAddress &addr, int port, const QString &user, const QCA::SecureArray &pass) { d->stunRelayAddr = addr; d->stunRelayPort = port; d->stunUser = user; d->stunPass = pass; } QHostAddress IceLocalTransport::stunBindServiceAddress() const { return d->stunBindAddr; } QHostAddress IceLocalTransport::stunRelayServiceAddress() const { return d->stunRelayAddr; } void IceLocalTransport::stunStart() { d->stunStart(); } QHostAddress IceLocalTransport::localAddress() const { return d->addr; } int IceLocalTransport::localPort() const { return d->port; } QHostAddress IceLocalTransport::serverReflexiveAddress() const { return d->refAddr; } int IceLocalTransport::serverReflexivePort() const { return d->refPort; } QHostAddress IceLocalTransport::reflexiveAddressSource() const { return d->refAddrSource; } QHostAddress IceLocalTransport::relayedAddress() const { return d->relAddr; } int IceLocalTransport::relayedPort() const { return d->relPort; } void IceLocalTransport::addChannelPeer(const QHostAddress &addr, int port) { if (d->turn) d->turn->addChannelPeer(addr, port); } bool IceLocalTransport::hasPendingDatagrams(int path) const { if (path == Direct) return !d->in.isEmpty(); else if (path == Relayed) return !d->inRelayed.isEmpty(); else { Q_ASSERT(0); return false; } } QByteArray IceLocalTransport::readDatagram(int path, QHostAddress *addr, int *port) { QList *in = nullptr; if (path == Direct) in = &d->in; else if (path == Relayed) in = &d->inRelayed; else Q_ASSERT(0); if (!in->isEmpty()) { Private::Datagram datagram = in->takeFirst(); *addr = datagram.addr; *port = datagram.port; return datagram.buf; } else return QByteArray(); } void IceLocalTransport::writeDatagram(int path, const QByteArray &buf, const QHostAddress &addr, int port) { if (path == Direct) { Private::WriteItem wi; wi.type = Private::WriteItem::Direct; wi.addr = addr; wi.port = port; d->pendingWrites += wi; d->sock->writeDatagram(buf, addr, port); } else if (path == Relayed) { if (d->turn && d->turnActivated) d->turn->write(buf, addr, port); } else Q_ASSERT(0); } void IceLocalTransport::setDebugLevel(DebugLevel level) { d->debugLevel = level; if (d->pool) d->pool->setDebugLevel((StunTransactionPool::DebugLevel)level); if (d->turn) d->turn->setDebugLevel((TurnClient::DebugLevel)level); } void IceLocalTransport::changeThread(QThread *thread) { if (d->pool) d->pool->moveToThread(thread); moveToThread(thread); } } // namespace XMPP #include "icelocaltransport.moc" psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/icelocaltransport.h000066400000000000000000000065671370065651000263470ustar00rootroot00000000000000/* * Copyright (C) 2009-2010 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef ICELOCALTRANSPORT_H #define ICELOCALTRANSPORT_H #include "icetransport.h" #include #include #include class QHostAddress; class QUdpSocket; namespace QCA { class SecureArray; } namespace XMPP { // this class manages a single port on a single interface, including the // relationship with an associated STUN/TURN server. if TURN is used, this // class offers two paths (0=direct and 1=relayed), otherwise it offers // just one path (0=direct) class IceLocalTransport : public IceTransport, public QEnableSharedFromThis { Q_OBJECT public: enum Error { ErrorBind = ErrorCustom }; IceLocalTransport(QObject *parent = nullptr); ~IceLocalTransport(); void setClientSoftwareNameAndVersion(const QString &str); // passed socket must already be bind()'ed, don't support // ErrorMismatch retries void start(QUdpSocket *sock); // bind to this address on a random port, do support ErrorMismatch // retries void start(const QHostAddress &addr); void setStunBindService(const QHostAddress &addr, int port); void setStunRelayService(const QHostAddress &addr, int port, const QString &user, const QCA::SecureArray &pass); QHostAddress stunBindServiceAddress() const; QHostAddress stunRelayServiceAddress() const; // obtain relay / reflexive void stunStart(); QHostAddress localAddress() const; int localPort() const; QHostAddress serverReflexiveAddress() const; int serverReflexivePort() const; QHostAddress reflexiveAddressSource() const; // address of stun/turn server provided the srflx QHostAddress relayedAddress() const; int relayedPort() const; // reimplemented void stop() override; bool hasPendingDatagrams(int path) const override; QByteArray readDatagram(int path, QHostAddress *addr, int *port) override; void writeDatagram(int path, const QByteArray &buf, const QHostAddress &addr, int port) override; void addChannelPeer(const QHostAddress &addr, int port) override; void setDebugLevel(DebugLevel level) override; void changeThread(QThread *thread) override; signals: // may be emitted multiple times. // if handling internal ErrorMismatch, then local address may change // and server reflexive address may disappear. // if start(QUdpSocket*) was used, then ErrorMismatch is not handled, // and this signal will only be emitted to add addresses void addressesChanged(); private: class Private; friend class Private; Private *d; }; } // namespace XMPP #endif // ICELOCALTRANSPORT_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/icetransport.cpp000066400000000000000000000015761370065651000256620ustar00rootroot00000000000000/* * Copyright (C) 2010 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "icetransport.h" namespace XMPP { IceTransport::IceTransport(QObject *parent) : QObject(parent) { } IceTransport::~IceTransport() { } } // namespace XMPP psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/icetransport.h000066400000000000000000000041311370065651000253150ustar00rootroot00000000000000/* * Copyright (C) 2010 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef ICETRANSPORT_H #define ICETRANSPORT_H #include #include #include class QHostAddress; namespace XMPP { class IceTransport : public QObject { Q_OBJECT public: enum Error { ErrorGeneric, ErrorCustom }; enum DebugLevel { DL_None, DL_Info, DL_Packet }; IceTransport(QObject *parent = nullptr); ~IceTransport(); virtual void stop() = 0; virtual bool hasPendingDatagrams(int path) const = 0; virtual QByteArray readDatagram(int path, QHostAddress *addr, int *port) = 0; virtual void writeDatagram(int path, const QByteArray &buf, const QHostAddress &addr, int port) = 0; virtual void addChannelPeer(const QHostAddress &addr, int port) = 0; virtual void setDebugLevel(DebugLevel level) = 0; virtual void changeThread(QThread *thread) = 0; signals: void started(); void stopped(); // emitted when stop() finished cleaning up void error(int e); void readyRead(int path); void datagramsWritten(int path, int count, const QHostAddress &addr, int port); // not DOR-SS/DS safe void debugLine(const QString &str); }; inline uint qHash(const QWeakPointer &p) { return qHash(p.toStrongRef().data()); } } // namespace XMPP #endif // ICETRANSPORT_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/iceturntransport.cpp000066400000000000000000000150101370065651000265570ustar00rootroot00000000000000/* * Copyright (C) 2010 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "iceturntransport.h" #include "stunallocate.h" #include namespace XMPP { class IceTurnTransport::Private : public QObject { Q_OBJECT public: IceTurnTransport *q; int mode; QHostAddress serverAddr; int serverPort; QString relayUser; QCA::SecureArray relayPass; QHostAddress relayAddr; int relayPort; TurnClient turn; int turnErrorCode = 0; int debugLevel; bool started = false; Private(IceTurnTransport *_q) : QObject(_q), q(_q), turn(this), debugLevel(IceTransport::DL_None) { connect(&turn, SIGNAL(connected()), SLOT(turn_connected())); connect(&turn, SIGNAL(tlsHandshaken()), SLOT(turn_tlsHandshaken())); connect(&turn, SIGNAL(closed()), SLOT(turn_closed())); connect(&turn, SIGNAL(needAuthParams()), SLOT(turn_needAuthParams())); connect(&turn, SIGNAL(retrying()), SLOT(turn_retrying())); connect(&turn, SIGNAL(activated()), SLOT(turn_activated())); connect(&turn, SIGNAL(readyRead()), SLOT(turn_readyRead())); connect(&turn, SIGNAL(packetsWritten(int, QHostAddress, int)), SLOT(turn_packetsWritten(int, QHostAddress, int))); connect(&turn, SIGNAL(error(XMPP::TurnClient::Error)), SLOT(turn_error(XMPP::TurnClient::Error))); connect(&turn, SIGNAL(debugLine(QString)), SLOT(turn_debugLine(QString))); } void start() { turn.setUsername(relayUser); turn.setPassword(relayPass); turn.connectToHost(serverAddr, serverPort, (TurnClient::Mode)mode); } void stop() { turn.close(); } private slots: void turn_connected() { if (debugLevel >= IceTransport::DL_Info) emit q->debugLine("turn_connected"); } void turn_tlsHandshaken() { if (debugLevel >= IceTransport::DL_Info) emit q->debugLine("turn_tlsHandshaken"); } void turn_closed() { if (debugLevel >= IceTransport::DL_Info) emit q->debugLine("turn_closed"); emit q->stopped(); } void turn_needAuthParams() { // we can get this signal if the user did not provide // creds to us. however, since this class doesn't support // prompting just continue on as if we had a blank // user/pass turn.continueAfterParams(); } void turn_retrying() { if (debugLevel >= IceTransport::DL_Info) emit q->debugLine("turn_retrying"); } void turn_activated() { StunAllocate *allocate = turn.stunAllocate(); QHostAddress saddr = allocate->reflexiveAddress(); quint16 sport = allocate->reflexivePort(); if (debugLevel >= IceTransport::DL_Info) emit q->debugLine(QString("Server says we are ") + saddr.toString() + ';' + QString::number(sport)); saddr = allocate->relayedAddress(); sport = allocate->relayedPort(); if (debugLevel >= IceTransport::DL_Info) emit q->debugLine(QString("Server relays via ") + saddr.toString() + ';' + QString::number(sport)); relayAddr = saddr; relayPort = sport; started = true; emit q->started(); } void turn_readyRead() { emit q->readyRead(0); } void turn_packetsWritten(int count, const QHostAddress &addr, int port) { emit q->datagramsWritten(0, count, addr, port); } void turn_error(XMPP::TurnClient::Error e) { if (debugLevel >= IceTransport::DL_Info) emit q->debugLine(QString("turn_error: ") + turn.errorString()); turnErrorCode = e; emit q->error(IceTurnTransport::ErrorTurn); } void turn_debugLine(const QString &line) { emit q->debugLine(line); } }; IceTurnTransport::IceTurnTransport(QObject *parent) : IceTransport(parent) { d = new Private(this); } IceTurnTransport::~IceTurnTransport() { delete d; } void IceTurnTransport::setClientSoftwareNameAndVersion(const QString &str) { d->turn.setClientSoftwareNameAndVersion(str); } void IceTurnTransport::setUsername(const QString &user) { d->relayUser = user; } void IceTurnTransport::setPassword(const QCA::SecureArray &pass) { d->relayPass = pass; } void IceTurnTransport::setProxy(const TurnClient::Proxy &proxy) { d->turn.setProxy(proxy); } void IceTurnTransport::start(const QHostAddress &addr, int port, TurnClient::Mode mode) { d->serverAddr = addr; d->serverPort = port; d->mode = mode; d->start(); } QHostAddress IceTurnTransport::relayedAddress() const { return d->relayAddr; } int IceTurnTransport::relayedPort() const { return d->relayPort; } bool IceTurnTransport::isStarted() const { return d->started; } void IceTurnTransport::addChannelPeer(const QHostAddress &addr, int port) { d->turn.addChannelPeer(addr, port); } TurnClient::Error IceTurnTransport::turnErrorCode() const { return (TurnClient::Error)d->turnErrorCode; } void IceTurnTransport::stop() { d->stop(); } bool IceTurnTransport::hasPendingDatagrams(int path) const { Q_ASSERT(path == 0); Q_UNUSED(path) return (d->turn.packetsToRead() > 0 ? true : false); } QByteArray IceTurnTransport::readDatagram(int path, QHostAddress *addr, int *port) { Q_ASSERT(path == 0); Q_UNUSED(path) return d->turn.read(addr, port); } void IceTurnTransport::writeDatagram(int path, const QByteArray &buf, const QHostAddress &addr, int port) { Q_ASSERT(path == 0); Q_UNUSED(path) d->turn.write(buf, addr, port); } void IceTurnTransport::setDebugLevel(DebugLevel level) { d->debugLevel = level; d->turn.setDebugLevel((TurnClient::DebugLevel)level); } void IceTurnTransport::changeThread(QThread *thread) { d->turn.changeThread(thread); moveToThread(thread); } } // namespace XMPP #include "iceturntransport.moc" psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/iceturntransport.h000066400000000000000000000044721370065651000262360ustar00rootroot00000000000000/* * Copyright (C) 2010 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef ICETURNTRANSPORT_H #define ICETURNTRANSPORT_H #include "icetransport.h" #include "turnclient.h" #include #include #include #include namespace XMPP { // for the turn transport, only path 0 is used class IceTurnTransport : public IceTransport, public QEnableSharedFromThis { Q_OBJECT public: enum Error { ErrorTurn = ErrorCustom }; IceTurnTransport(QObject *parent = nullptr); ~IceTurnTransport(); void setClientSoftwareNameAndVersion(const QString &str); // set these before calling start() void setUsername(const QString &user); void setPassword(const QCA::SecureArray &pass); void setProxy(const TurnClient::Proxy &proxy); void start(const QHostAddress &addr, int port, TurnClient::Mode mode = TurnClient::PlainMode); QHostAddress relayedAddress() const; int relayedPort() const; bool isStarted() const; TurnClient::Error turnErrorCode() const; // reimplemented virtual void stop(); virtual bool hasPendingDatagrams(int path) const; virtual QByteArray readDatagram(int path, QHostAddress *addr, int *port); virtual void writeDatagram(int path, const QByteArray &buf, const QHostAddress &addr, int port); virtual void addChannelPeer(const QHostAddress &addr, int port); virtual void setDebugLevel(DebugLevel level); virtual void changeThread(QThread *thread) override; private: class Private; friend class Private; Private *d; }; } // namespace XMPP #endif // ICETURNTRANSPORT_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/legacy/000077500000000000000000000000001370065651000236745ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/legacy/legacy.pri000066400000000000000000000001651370065651000256560ustar00rootroot00000000000000HEADERS += \ $$PWD/ndns.h \ $$PWD/srvresolver.h SOURCES += \ $$PWD/ndns.cpp \ $$PWD/srvresolver.cpp psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/legacy/ndns.cpp000066400000000000000000000067771370065651000253630ustar00rootroot00000000000000/* * ndns.cpp - native DNS resolution * Copyright (C) 2001-2002 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ //! \class NDns ndns.h //! \brief Simple DNS resolution using native system calls //! //! This class is to be used when Qt's QDns is not good enough. Because QDns //! does not use threads, it cannot make a system call asyncronously. Thus, //! QDns tries to imitate the behavior of each platform's native behavior, and //! generally falls short. //! //! NDns uses a thread to make the system call happen in the background. This //! gives your program native DNS behavior, at the cost of requiring threads //! to build. //! //! \code //! #include "ndns.h" //! //! ... //! //! NDns dns; //! dns.resolve("psi.affinix.com"); //! //! // The class will emit the resultsReady() signal when the resolution //! // is finished. You may then retrieve the results: //! //! uint ip_address = dns.result(); //! //! // or if you want to get the IP address as a string: //! //! QString ip_address = dns.resultString(); //! \endcode #include "ndns.h" #include "netnames.h" // CS_NAMESPACE_BEGIN //---------------------------------------------------------------------------- // NDns //---------------------------------------------------------------------------- //! \fn void NDns::resultsReady() //! This signal is emitted when the DNS resolution succeeds or fails. //! //! Constructs an NDns object with parent \a parent. NDns::NDns(QObject *parent) : QObject(parent), dns(this) { busy = false; connect(&dns, SIGNAL(resultsReady(QList)), SLOT(dns_resultsReady(QList))); connect(&dns, SIGNAL(error(XMPP::NameResolver::Error)), SLOT(dns_error(XMPP::NameResolver::Error))); } //! //! Destroys the object and frees allocated resources. NDns::~NDns() { stop(); } //! //! Resolves hostname \a host (eg. psi.affinix.com) void NDns::resolve(const QString &host) { stop(); busy = true; dns.start(host.toLatin1()); } //! //! Cancels the lookup action. //! \note This will not stop the underlying system call, which must finish before the next lookup will proceed. void NDns::stop() { dns.stop(); busy = false; } //! //! Returns the IP address as a 32-bit integer in host-byte-order. This will be 0 if the lookup failed. //! \sa resultsReady() QHostAddress NDns::result() const { return addr; } //! //! Returns the IP address as a string. This will be an empty string if the lookup failed. //! \sa resultsReady() QString NDns::resultString() const { return addr.toString(); } //! //! Returns TRUE if busy resolving a hostname. bool NDns::isBusy() const { return busy; } void NDns::dns_resultsReady(const QList &results) { addr = results[0].address(); busy = false; emit resultsReady(); } void NDns::dns_error(XMPP::NameResolver::Error) { addr = QHostAddress(); busy = false; emit resultsReady(); } // CS_NAMESPACE_END psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/legacy/ndns.h000066400000000000000000000026331370065651000250130ustar00rootroot00000000000000/* * ndns.h - native DNS resolution * Copyright (C) 2001-2002 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef CS_NDNS_H #define CS_NDNS_H #include "netnames.h" #include #include // CS_NAMESPACE_BEGIN class NDns : public QObject { Q_OBJECT public: NDns(QObject *parent = nullptr); ~NDns(); void resolve(const QString &); void stop(); bool isBusy() const; QHostAddress result() const; QString resultString() const; signals: void resultsReady(); private slots: void dns_resultsReady(const QList &); void dns_error(XMPP::NameResolver::Error); private: XMPP::NameResolver dns; bool busy; QHostAddress addr; }; // CS_NAMESPACE_END #endif // CS_NDNS_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/legacy/srvresolver.cpp000066400000000000000000000161641370065651000270040ustar00rootroot00000000000000/* * srvresolver.cpp - class to simplify SRV lookups * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "srvresolver.h" #ifndef NO_NDNS #include "ndns.h" #endif // CS_NAMESPACE_BEGIN static void sortSRVList(QList &list) { QList tmp = list; list.clear(); while (!tmp.isEmpty()) { QList::Iterator p = tmp.end(); for (QList::Iterator it = tmp.begin(); it != tmp.end(); ++it) { if (p == tmp.end()) p = it; else { int a = (*it).priority; int b = (*p).priority; int j = (*it).weight; int k = (*p).weight; if (a < b || (a == b && j < k)) p = it; } } list.append(*p); tmp.erase(p); } } class SrvResolver::Private { public: Private(SrvResolver *_q) : nndns(_q), #ifndef NO_NDNS ndns(_q), #endif t(_q) { } XMPP::NameResolver nndns; XMPP::NameRecord::Type nntype; bool nndns_busy; #ifndef NO_NDNS NDns ndns; #endif bool failed; QHostAddress resultAddress; quint16 resultPort; bool srvonly; QString srv; QList servers; bool aaaa; QTimer t; }; SrvResolver::SrvResolver(QObject *parent) : QObject(parent) { d = new Private(this); d->nndns_busy = false; connect(&d->nndns, SIGNAL(resultsReady(QList)), SLOT(nndns_resultsReady(QList))); connect(&d->nndns, SIGNAL(error(XMPP::NameResolver::Error)), SLOT(nndns_error(XMPP::NameResolver::Error))); #ifndef NO_NDNS connect(&d->ndns, SIGNAL(resultsReady()), SLOT(ndns_done())); #endif connect(&d->t, SIGNAL(timeout()), SLOT(t_timeout())); stop(); } SrvResolver::~SrvResolver() { stop(); delete d; } void SrvResolver::resolve(const QString &server, const QString &type, const QString &proto) { stop(); d->failed = false; d->srvonly = false; d->srv = QString("_") + type + "._" + proto + '.' + server; d->t.setSingleShot(true); d->t.start(15000); d->nndns_busy = true; d->nntype = XMPP::NameRecord::Srv; d->nndns.start(d->srv.toLatin1(), d->nntype); } void SrvResolver::resolveSrvOnly(const QString &server, const QString &type, const QString &proto) { stop(); d->failed = false; d->srvonly = true; d->srv = QString("_") + type + "._" + proto + '.' + server; d->t.setSingleShot(true); d->t.start(15000); d->nndns_busy = true; d->nntype = XMPP::NameRecord::Srv; d->nndns.start(d->srv.toLatin1(), d->nntype); } void SrvResolver::next() { if (d->servers.isEmpty()) return; tryNext(); } void SrvResolver::stop() { if (d->t.isActive()) d->t.stop(); if (d->nndns_busy) { d->nndns.stop(); d->nndns_busy = false; } #ifndef NO_NDNS if (d->ndns.isBusy()) d->ndns.stop(); #endif d->resultAddress = QHostAddress(); d->resultPort = 0; d->servers.clear(); d->srv = ""; d->failed = true; } bool SrvResolver::isBusy() const { #ifndef NO_NDNS if (d->nndns_busy || d->ndns.isBusy()) #else if (d->nndns_busy) #endif return true; else return false; } QList SrvResolver::servers() const { return d->servers; } bool SrvResolver::failed() const { return d->failed; } QHostAddress SrvResolver::resultAddress() const { return d->resultAddress; } quint16 SrvResolver::resultPort() const { return d->resultPort; } void SrvResolver::tryNext() { #ifndef NO_NDNS d->ndns.resolve(d->servers.first().name); #else d->nndns_busy = true; d->nntype = d->aaaa ? XMPP::NameRecord::Aaaa : XMPP::NameRecord::A; d->nndns.start(d->servers.first().name.toLatin1(), d->nntype); #endif } void SrvResolver::nndns_resultsReady(const QList &results) { if (!d->nndns_busy) return; d->t.stop(); if (d->nntype == XMPP::NameRecord::Srv) { // grab the server list and destroy the qdns object QList list; for (int n = 0; n < results.count(); ++n) { list += Q3Dns::Server(QString::fromLatin1(results[n].name()), results[n].priority(), results[n].weight(), results[n].port()); } d->nndns_busy = false; d->nndns.stop(); if (list.isEmpty()) { stop(); resultsReady(); return; } sortSRVList(list); d->servers = list; if (d->srvonly) resultsReady(); else { // kick it off d->aaaa = true; tryNext(); } } else { // grab the address list and destroy the qdns object QList list; if (d->nntype == XMPP::NameRecord::A || d->nntype == XMPP::NameRecord::Aaaa) { for (int n = 0; n < results.count(); ++n) { list += results[n].address(); } } d->nndns_busy = false; d->nndns.stop(); if (!list.isEmpty()) { int port = d->servers.first().port; d->servers.removeFirst(); d->aaaa = true; d->resultAddress = list.first(); d->resultPort = port; resultsReady(); } else { if (!d->aaaa) d->servers.removeFirst(); d->aaaa = !d->aaaa; // failed? bail if last one if (d->servers.isEmpty()) { stop(); resultsReady(); return; } // otherwise try the next tryNext(); } } } void SrvResolver::nndns_error(XMPP::NameResolver::Error) { nndns_resultsReady(QList()); } void SrvResolver::ndns_done() { #ifndef NO_NDNS QHostAddress r = d->ndns.result(); int port = d->servers.first().port; d->servers.removeFirst(); if (!r.isNull()) { d->resultAddress = d->ndns.result(); d->resultPort = port; resultsReady(); } else { // failed? bail if last one if (d->servers.isEmpty()) { stop(); resultsReady(); return; } // otherwise try the next tryNext(); } #endif } void SrvResolver::t_timeout() { stop(); resultsReady(); } // CS_NAMESPACE_END psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/legacy/srvresolver.h000066400000000000000000000040251370065651000264420ustar00rootroot00000000000000/* * srvresolver.h - class to simplify SRV lookups * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef CS_SRVRESOLVER_H #define CS_SRVRESOLVER_H #include "netnames.h" #include #include // CS_NAMESPACE_BEGIN class Q3Dns { public: class Server { public: Server(const QString &n = QString(), quint16 p = 0, quint16 w = 0, quint16 po = 0) : name(n), priority(p), weight(w), port(po) { } QString name; quint16 priority; quint16 weight; quint16 port; }; }; class SrvResolver : public QObject { Q_OBJECT public: SrvResolver(QObject *parent = nullptr); ~SrvResolver(); void resolve(const QString &server, const QString &type, const QString &proto); void resolveSrvOnly(const QString &server, const QString &type, const QString &proto); void next(); void stop(); bool isBusy() const; QList servers() const; bool failed() const; QHostAddress resultAddress() const; quint16 resultPort() const; signals: void resultsReady(); private slots: void nndns_resultsReady(const QList &); void nndns_error(XMPP::NameResolver::Error); void ndns_done(); void t_timeout(); private: class Private; Private *d; void tryNext(); }; // CS_NAMESPACE_END #endif // CS_SRVRESOLVER_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/noncore.pri000066400000000000000000000022541370065651000246120ustar00rootroot00000000000000IRIS_BASE = $$PWD/../../.. QT *= network irisnetcore_bundle:{ include(../corelib/corelib.pri) } else { LIBS += -L$$IRIS_BASE/lib -lirisnetcore } INCLUDEPATH += $$PWD/../corelib include($$PWD/cutestuff/cutestuff.pri) HEADERS += \ $$PWD/iceagent.h \ $$PWD/processquit.h \ $$PWD/stunutil.h \ $$PWD/stunmessage.h \ $$PWD/stuntypes.h \ $$PWD/stuntransaction.h \ $$PWD/stunbinding.h \ $$PWD/stunallocate.h \ $$PWD/turnclient.h \ $$PWD/udpportreserver.h \ $$PWD/icetransport.h \ $$PWD/icelocaltransport.h \ $$PWD/iceturntransport.h \ $$PWD/icecomponent.h \ $$PWD/ice176.h \ $$PWD/tcpportreserver.h SOURCES += \ $$PWD/iceagent.cpp \ $$PWD/processquit.cpp \ $$PWD/stunutil.cpp \ $$PWD/stunmessage.cpp \ $$PWD/stuntypes.cpp \ $$PWD/stuntransaction.cpp \ $$PWD/stunbinding.cpp \ $$PWD/stunallocate.cpp \ $$PWD/turnclient.cpp \ $$PWD/udpportreserver.cpp \ $$PWD/icetransport.cpp \ $$PWD/icelocaltransport.cpp \ $$PWD/iceturntransport.cpp \ $$PWD/icecomponent.cpp \ $$PWD/ice176.cpp \ $$PWD/tcpportreserver.cpp INCLUDEPATH += $$PWD/legacy include(legacy/legacy.pri) psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/noncore.pro000066400000000000000000000004421370065651000246150ustar00rootroot00000000000000IRIS_BASE = ../../.. TEMPLATE = lib QT -= gui TARGET = irisnet DESTDIR = $$IRIS_BASE/lib CONFIG += staticlib create_prl VERSION = 1.0.0 include(../../libbase.pri) include(noncore.pri) windows:!staticlib:DEFINES += IRISNET_MAKEDLL staticlib:PRL_EXPORT_DEFINES += IRISNET_STATIC psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/processquit.cpp000066400000000000000000000133261370065651000255220ustar00rootroot00000000000000/* * Copyright (C) 2006 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "processquit.h" #ifndef NO_IRISNET #include "irisnetglobal_p.h" #endif #ifdef QT_GUI_LIB #include #endif #ifdef Q_OS_UNIX #include #include #endif #ifdef Q_OS_WIN #include #endif namespace { // safeobj stuff, from qca void releaseAndDeleteLater(QObject *owner, QObject *obj) { obj->disconnect(owner); obj->setParent(nullptr); obj->deleteLater(); } class SafeSocketNotifier : public QObject { Q_OBJECT public: SafeSocketNotifier(int socket, QSocketNotifier::Type type, QObject *parent = nullptr) : QObject(parent) { sn = new QSocketNotifier(socket, type, this); connect(sn, SIGNAL(activated(int)), SIGNAL(activated(int))); } ~SafeSocketNotifier() { sn->setEnabled(false); releaseAndDeleteLater(this, sn); } bool isEnabled() const { return sn->isEnabled(); } int socket() const { return sn->socket(); } QSocketNotifier::Type type() const { return sn->type(); } public slots: void setEnabled(bool enable) { sn->setEnabled(enable); } signals: void activated(int socket); private: QSocketNotifier *sn; }; } // namespace #ifndef NO_IRISNET namespace XMPP { #endif Q_GLOBAL_STATIC(QMutex, pq_mutex) static ProcessQuit *g_pq = nullptr; inline bool is_gui_app() { #ifdef QT_GUI_LIB return qobject_cast(QCoreApplication::instance()); #else return false; #endif } class ProcessQuit::Private : public QObject { Q_OBJECT public: ProcessQuit *q; bool done; #ifdef Q_OS_WIN bool use_handler; #endif #ifdef Q_OS_UNIX int sig_pipe[2]; SafeSocketNotifier *sig_notifier; #endif Private(ProcessQuit *_q) : QObject(_q), q(_q) { done = false; #ifdef Q_OS_WIN use_handler = !is_gui_app(); if (use_handler) SetConsoleCtrlHandler((PHANDLER_ROUTINE)winHandler, TRUE); #endif #ifdef Q_OS_UNIX if (pipe(sig_pipe) == -1) { // no support then return; } sig_notifier = new SafeSocketNotifier(sig_pipe[0], QSocketNotifier::Read, this); connect(sig_notifier, SIGNAL(activated(int)), SLOT(sig_activated(int))); unixWatchAdd(SIGINT); unixWatchAdd(SIGHUP); unixWatchAdd(SIGTERM); #endif } ~Private() { #ifdef Q_OS_WIN if (use_handler) SetConsoleCtrlHandler((PHANDLER_ROUTINE)winHandler, FALSE); #endif #ifdef Q_OS_UNIX unixWatchRemove(SIGINT); unixWatchRemove(SIGHUP); unixWatchRemove(SIGTERM); delete sig_notifier; close(sig_pipe[0]); close(sig_pipe[1]); #endif } #ifdef Q_OS_WIN static BOOL winHandler(DWORD ctrlType) { Q_UNUSED(ctrlType); QMetaObject::invokeMethod(g_pq->d, "ctrl_ready", Qt::QueuedConnection); return TRUE; } #endif #ifdef Q_OS_UNIX static void unixHandler(int sig) { Q_UNUSED(sig); unsigned char c = 0; if (::write(g_pq->d->sig_pipe[1], &c, 1) == -1) { // TODO: error handling? return; } } void unixWatchAdd(int sig) { struct sigaction sa; sigaction(sig, nullptr, &sa); // if the signal is ignored, don't take it over. this is // recommended by the glibc manual if (sa.sa_handler == SIG_IGN) return; sigemptyset(&(sa.sa_mask)); sa.sa_flags = 0; sa.sa_handler = unixHandler; sigaction(sig, &sa, nullptr); } void unixWatchRemove(int sig) { struct sigaction sa; sigaction(sig, nullptr, &sa); // ignored means we skipped it earlier, so we should // skip it again if (sa.sa_handler == SIG_IGN) return; sigemptyset(&(sa.sa_mask)); sa.sa_flags = 0; sa.sa_handler = SIG_DFL; sigaction(sig, &sa, nullptr); } #endif public slots: void ctrl_ready() { #ifdef Q_OS_WIN do_emit(); #endif } void sig_activated(int) { #ifdef Q_OS_UNIX unsigned char c; if (::read(sig_pipe[0], &c, 1) == -1) { // TODO: error handling? return; } do_emit(); #endif } private: void do_emit() { // only signal once if (!done) { done = true; emit q->quit(); } } }; ProcessQuit::ProcessQuit(QObject *parent) : QObject(parent) { d = new Private(this); } ProcessQuit::~ProcessQuit() { delete d; } ProcessQuit *ProcessQuit::instance() { QMutexLocker locker(pq_mutex()); if (!g_pq) { g_pq = new ProcessQuit; g_pq->moveToThread(QCoreApplication::instance()->thread()); #ifndef NO_IRISNET irisNetAddPostRoutine(cleanup); #endif } return g_pq; } void ProcessQuit::reset() { QMutexLocker locker(pq_mutex()); if (g_pq) g_pq->d->done = false; } void ProcessQuit::cleanup() { delete g_pq; g_pq = nullptr; } #ifndef NO_IRISNET } #endif // NO_IRISNET #include "processquit.moc" psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/processquit.h000066400000000000000000000105461370065651000251700ustar00rootroot00000000000000/* * Copyright (C) 2006 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef PROCESSQUIT_H #define PROCESSQUIT_H #ifdef NO_IRISNET #include #define IRISNET_EXPORT #else #include "irisnetglobal.h" #endif #ifndef NO_IRISNET namespace XMPP { #endif /** \brief Listens for termination requests ProcessQuit listens for requests to terminate the application process. On Unix platforms, these are the signals SIGINT, SIGHUP, and SIGTERM. On Windows, these are the console control events for Ctrl+C, console window close, and system shutdown. For Windows GUI programs, ProcessQuit has no effect. For GUI programs, ProcessQuit is not a substitute for QSessionManager. The only safe way to handle termination of a GUI program in the usual way is to use QSessionManager. However, ProcessQuit does give additional benefit to Unix GUI programs that might be terminated unconventionally, so it can't hurt to support both. When a termination request is received, the application should exit gracefully, and generally without user interaction. Otherwise, it is at risk of being terminated outside of its control. For example, if a Windows console application does not exit after just a few seconds of attempting to close the console window, Windows will display a prompt to the user asking if the process should be ended immediately. Using ProcessQuit is easy, and it usually amounts to a single line: \code myapp.connect(ProcessQuit::instance(), SIGNAL(quit()), SLOT(do_quit())); \endcode Calling instance() returns a pointer to the global ProcessQuit instance, which will be created if necessary. The quit() signal is emitted when a request to terminate is received. The quit() signal is only emitted once, future termination requests are ignored. Call reset() to allow the quit() signal to be emitted again. */ class IRISNET_EXPORT ProcessQuit : public QObject { Q_OBJECT public: /** \brief Returns the global ProcessQuit instance If the global instance does not exist yet, it will be created, and the termination handlers will be installed. \sa cleanup */ static ProcessQuit *instance(); /** \brief Allows the quit() signal to be emitted again ProcessQuit only emits the quit() signal once, so that if a user repeatedly presses Ctrl-C or sends SIGTERM, your shutdown slot will not be called multiple times. This is normally the desired behavior, but if you are ignoring the termination request then you may want to allow future notifications. Calling this function will allow the quit() signal to be emitted again, if a new termination request arrives. \sa quit */ static void reset(); /** \brief Frees all resources used by ProcessQuit This function will free any resources used by ProcessQuit, including the global instance, and the termination handlers will be uninstalled (reverted to default). Future termination requests will cause the application to exit abruptly. \note You normally do not need to call this function directly. When IrisNet cleans up, it will be called. \sa instance */ static void cleanup(); signals: /** \brief Notification of termination request This signal is emitted when a termination request is received. It is only emitted once, unless reset() is called. Upon receiving this signal, the application should proceed to exit gracefully, and generally without user interaction. \sa reset */ void quit(); private: class Private; friend class Private; Private *d; ProcessQuit(QObject *parent = 0); ~ProcessQuit(); }; #ifndef NO_IRISNET } #endif #endif // PROCESSQUIT_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/stunallocate.cpp000066400000000000000000001125071370065651000256400ustar00rootroot00000000000000/* * Copyright (C) 2009 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "stunallocate.h" #include "objectsession.h" #include "stunmessage.h" #include "stuntransaction.h" #include "stuntypes.h" #include "stunutil.h" #include #include #include #include // permissions last 5 minutes, update them every 4 minutes #define PERM_INTERVAL (4 * 60 * 1000) // channels last 10 minutes, update them every 9 minutes #define CHAN_INTERVAL (9 * 60 * 1000) namespace XMPP { void releaseAndDeleteLater(QObject *owner, QObject *obj) { obj->disconnect(owner); obj->setParent(nullptr); obj->deleteLater(); } // return size of channelData packet, or -1 static int check_channelData(const quint8 *data, int size) { // top two bits are never zero for ChannelData if ((data[0] & 0xc0) == 0) return -1; if (size < 4) return -1; quint16 len = StunUtil::read16(data + 2); if (size - 4 < (int)len) return -1; // data from a stream must be 4 byte aligned int plen = len; int remainder = plen % 4; if (remainder != 0) plen += (4 - remainder); int need = plen + 4; if (size < need) return -1; return need; } class StunAllocatePermission : public QObject { Q_OBJECT public: QTimer * timer; StunTransactionPool::Ptr pool; StunTransaction * trans; QHostAddress stunAddr; int stunPort; QHostAddress addr; bool active; enum Error { ErrorGeneric, ErrorProtocol, ErrorCapacity, ErrorForbidden, ErrorRejected, ErrorTimeout }; StunAllocatePermission(StunTransactionPool::Ptr _pool, const QHostAddress &_addr) : QObject(_pool.data()), pool(_pool), trans(nullptr), addr(_addr), active(false) { timer = new QTimer(this); connect(timer, SIGNAL(timeout()), SLOT(timer_timeout())); timer->setSingleShot(true); timer->setInterval(PERM_INTERVAL); } ~StunAllocatePermission() { cleanup(); releaseAndDeleteLater(this, timer); } void start(const QHostAddress &_addr, int _port) { Q_ASSERT(!active); stunAddr = _addr; stunPort = _port; doTransaction(); } static StunAllocate::Error errorToStunAllocateError(Error e) { switch (e) { case ErrorProtocol: return StunAllocate::ErrorProtocol; case ErrorCapacity: return StunAllocate::ErrorCapacity; case ErrorForbidden: case ErrorRejected: return StunAllocate::ErrorRejected; case ErrorTimeout: return StunAllocate::ErrorTimeout; default: return StunAllocate::ErrorGeneric; } } signals: void ready(); void error(XMPP::StunAllocatePermission::Error e, const QString &reason); private: void cleanup() { delete trans; trans = nullptr; timer->stop(); active = false; } void doTransaction() { Q_ASSERT(!trans); trans = new StunTransaction(this); connect(trans, SIGNAL(createMessage(QByteArray)), SLOT(trans_createMessage(QByteArray))); connect(trans, SIGNAL(finished(XMPP::StunMessage)), SLOT(trans_finished(XMPP::StunMessage))); connect(trans, SIGNAL(error(XMPP::StunTransaction::Error)), SLOT(trans_error(XMPP::StunTransaction::Error))); trans->start(pool.data(), stunAddr, stunPort); } void restartTimer() { timer->start(); } private slots: void trans_createMessage(const QByteArray &transactionId) { // CreatePermission StunMessage message; message.setMethod(StunTypes::CreatePermission); message.setId((const quint8 *)transactionId.data()); QList list; // we only do one address per permission request, because // otherwise if we receive an error it would be ambiguous // as to which address the error applies to { StunMessage::Attribute a; a.type = StunTypes::XOR_PEER_ADDRESS; a.value = StunTypes::createXorPeerAddress(addr, 0, message.magic(), message.id()); list += a; } message.setAttributes(list); trans->setMessage(message); } void trans_finished(const XMPP::StunMessage &response) { delete trans; trans = nullptr; bool err = false; int code; QString reason; if (response.mclass() == StunMessage::ErrorResponse) { if (!StunTypes::parseErrorCode(response.attribute(StunTypes::ERROR_CODE), &code, &reason)) { cleanup(); emit error(ErrorProtocol, "Unable to parse ERROR-CODE in error response."); return; } err = true; } if (err) { cleanup(); if (code == StunTypes::InsufficientCapacity) emit error(ErrorCapacity, reason); else if (code == StunTypes::Forbidden) emit error(ErrorForbidden, reason); else emit error(ErrorRejected, reason); return; } restartTimer(); if (!active) { active = true; emit ready(); } } void trans_error(XMPP::StunTransaction::Error e) { cleanup(); if (e == XMPP::StunTransaction::ErrorTimeout) emit error(ErrorTimeout, "Request timed out."); else emit error(ErrorGeneric, "Generic transaction error."); } void timer_timeout() { doTransaction(); } }; class StunAllocateChannel : public QObject { Q_OBJECT public: QTimer * timer; StunTransactionPool::Ptr pool; StunTransaction * trans; QHostAddress stunAddr; int stunPort; int channelId; QHostAddress addr; int port; bool active; enum Error { ErrorGeneric, ErrorProtocol, ErrorCapacity, ErrorForbidden, ErrorRejected, ErrorTimeout }; StunAllocateChannel(StunTransactionPool::Ptr _pool, int _channelId, const QHostAddress &_addr, int _port) : QObject(_pool.data()), pool(_pool), trans(nullptr), channelId(_channelId), addr(_addr), port(_port), active(false) { timer = new QTimer(this); connect(timer, SIGNAL(timeout()), SLOT(timer_timeout())); timer->setSingleShot(true); timer->setInterval(CHAN_INTERVAL); } ~StunAllocateChannel() { cleanup(); releaseAndDeleteLater(this, timer); } void start(const QHostAddress &_addr, int _port) { Q_ASSERT(!active); stunAddr = _addr; stunPort = _port; doTransaction(); } static StunAllocate::Error errorToStunAllocateError(Error e) { switch (e) { case ErrorProtocol: return StunAllocate::ErrorProtocol; case ErrorCapacity: return StunAllocate::ErrorCapacity; case ErrorForbidden: case ErrorRejected: return StunAllocate::ErrorRejected; case ErrorTimeout: return StunAllocate::ErrorTimeout; default: return StunAllocate::ErrorGeneric; } } signals: void ready(); void error(XMPP::StunAllocateChannel::Error e, const QString &reason); private: void cleanup() { delete trans; trans = nullptr; timer->stop(); channelId = -1; active = false; } void doTransaction() { Q_ASSERT(!trans); trans = new StunTransaction(this); connect(trans, SIGNAL(createMessage(QByteArray)), SLOT(trans_createMessage(QByteArray))); connect(trans, SIGNAL(finished(XMPP::StunMessage)), SLOT(trans_finished(XMPP::StunMessage))); connect(trans, SIGNAL(error(XMPP::StunTransaction::Error)), SLOT(trans_error(XMPP::StunTransaction::Error))); trans->start(pool.data(), stunAddr, stunPort); } void restartTimer() { timer->start(); } private slots: void trans_createMessage(const QByteArray &transactionId) { // ChannelBind StunMessage message; message.setMethod(StunTypes::ChannelBind); message.setId((const quint8 *)transactionId.data()); QList list; { StunMessage::Attribute a; a.type = StunTypes::CHANNEL_NUMBER; a.value = StunTypes::createChannelNumber(channelId); list += a; } { StunMessage::Attribute a; a.type = StunTypes::XOR_PEER_ADDRESS; a.value = StunTypes::createXorPeerAddress(addr, port, message.magic(), message.id()); list += a; } message.setAttributes(list); trans->setMessage(message); } void trans_finished(const XMPP::StunMessage &response) { delete trans; trans = nullptr; bool err = false; int code; QString reason; if (response.mclass() == StunMessage::ErrorResponse) { if (!StunTypes::parseErrorCode(response.attribute(StunTypes::ERROR_CODE), &code, &reason)) { cleanup(); emit error(ErrorProtocol, "Unable to parse ERROR-CODE in error response."); return; } err = true; } if (err) { cleanup(); if (code == StunTypes::InsufficientCapacity) emit error(ErrorCapacity, reason); else if (code == StunTypes::Forbidden) emit error(ErrorForbidden, reason); else emit error(ErrorRejected, reason); return; } restartTimer(); if (!active) { active = true; emit ready(); } } void trans_error(XMPP::StunTransaction::Error e) { cleanup(); if (e == XMPP::StunTransaction::ErrorTimeout) emit error(ErrorTimeout, "Request timed out."); else emit error(ErrorGeneric, "Generic transaction error."); } void timer_timeout() { doTransaction(); } }; class StunAllocate::Private : public QObject { Q_OBJECT public: enum DontFragmentState { DF_Unknown, DF_Supported, DF_Unsupported }; enum State { Stopped, Starting, Started, Refreshing, Stopping, Erroring // like stopping, but emits error when finished }; StunAllocate * q; ObjectSession sess; StunTransactionPool::Ptr pool; StunTransaction * trans; QHostAddress stunAddr; int stunPort; State state; QString errorString; DontFragmentState dfState; QString clientSoftware, serverSoftware; QHostAddress reflexiveAddress, relayedAddress; int reflexivePort, relayedPort; StunMessage msg; int allocateLifetime; QTimer * allocateRefreshTimer; QList perms; QList channels; QList permQueue; QList permsOut; QList channelsOut; int erroringCode; QString erroringString; Private(StunAllocate *_q) : QObject(_q), q(_q), sess(this), pool(nullptr), trans(nullptr), state(Stopped), dfState(DF_Unknown), erroringCode(-1) { allocateRefreshTimer = new QTimer(this); connect(allocateRefreshTimer, SIGNAL(timeout()), SLOT(refresh())); allocateRefreshTimer->setSingleShot(true); } ~Private() { cleanup(); releaseAndDeleteLater(this, allocateRefreshTimer); } void start(const QHostAddress &_addr = QHostAddress(), int _port = -1) { Q_ASSERT(state == Stopped); stunAddr = _addr; stunPort = _port; state = Starting; doTransaction(); } void stop() { // erroring already? no need to do anything if (state == Erroring) return; Q_ASSERT(state == Started); cleanupTasks(); state = Stopping; doTransaction(); } void stopWithError(int code, const QString &str) { Q_ASSERT(state == Started); cleanupTasks(); erroringCode = code; erroringString = str; state = Erroring; doTransaction(); } void setPermissions(const QList &newPerms) { // if currently erroring out, skip if (state == Erroring) return; if (state == Starting) { permQueue += newPerms; return; } Q_ASSERT(state == Started); int freeCount = 0; // removed? for (int n = 0; n < perms.count(); ++n) { bool found = false; for (int k = 0; k < newPerms.count(); ++k) { if (newPerms[k] == perms[n]->addr) { found = true; break; } } if (!found) { // delete related channels for (int j = 0; j < channels.count(); ++j) { if (channels[j]->addr == perms[n]->addr) { delete channels[j]; channels.removeAt(j); --j; // adjust position } } ++freeCount; delete perms[n]; perms.removeAt(n); --n; // adjust position } } if (freeCount > 0) { // removals count as a change, so emit the signal sess.deferExclusive(q, "permissionsChanged"); // wake up inactive perms now that we've freed space for (int n = 0; n < perms.count(); ++n) { if (!perms[n]->active) perms[n]->start(stunAddr, stunPort); } } // added? for (int n = 0; n < newPerms.count(); ++n) { bool found = false; for (int k = 0; k < perms.count(); ++k) { if (perms[k]->addr == newPerms[n]) { found = true; break; } } if (!found) { StunAllocatePermission *perm = new StunAllocatePermission(pool, newPerms[n]); connect(perm, SIGNAL(ready()), SLOT(perm_ready())); connect(perm, SIGNAL(error(XMPP::StunAllocatePermission::Error, QString)), SLOT(perm_error(XMPP::StunAllocatePermission::Error, QString))); perms += perm; perm->start(stunAddr, stunPort); } } } void setChannels(const QList &newChannels) { // if currently erroring out, skip if (state == Erroring) return; Q_ASSERT(state == Started); int freeCount = 0; // removed? for (int n = 0; n < channels.count(); ++n) { bool found = false; for (int k = 0; k < newChannels.count(); ++k) { if (newChannels[k].address == channels[n]->addr && newChannels[k].port == channels[n]->port) { found = true; break; } } if (!found) { ++freeCount; delete channels[n]; channels.removeAt(n); --n; // adjust position } } if (freeCount > 0) { // removals count as a change, so emit the signal sess.deferExclusive(q, "channelsChanged"); // wake up inactive channels now that we've freed space for (int n = 0; n < channels.count(); ++n) { if (!channels[n]->active) { int channelId = getFreeChannelNumber(); // out of channels? give up if (channelId == -1) break; channels[n]->channelId = channelId; channels[n]->start(stunAddr, stunPort); } } } // added? for (int n = 0; n < newChannels.count(); ++n) { bool found = false; for (int k = 0; k < channels.count(); ++k) { if (channels[k]->addr == newChannels[n].address && channels[k]->port == newChannels[n].port) { found = true; break; } } if (!found) { // look up the permission for this channel int at = -1; for (int k = 0; k < perms.count(); ++k) { if (perms[k]->addr == newChannels[n].address) { at = k; break; } } // only install a channel if we have a permission if (at != -1) { int channelId = getFreeChannelNumber(); StunAllocateChannel *channel = new StunAllocateChannel(pool, channelId, newChannels[n].address, newChannels[n].port); connect(channel, SIGNAL(ready()), SLOT(channel_ready())); connect(channel, SIGNAL(error(XMPP::StunAllocateChannel::Error, QString)), SLOT(channel_error(XMPP::StunAllocateChannel::Error, QString))); channels += channel; if (channelId != -1) channel->start(stunAddr, stunPort); } } } } int getFreeChannelNumber() { for (int tryId = 0x4000; tryId <= 0x7fff; ++tryId) { bool found = false; for (int n = 0; n < channels.count(); ++n) { if (channels[n]->channelId == tryId) { found = true; break; } } if (!found) return tryId; } return -1; } int getChannel(const QHostAddress &addr, int port) { for (int n = 0; n < channels.count(); ++n) { if (channels[n]->active && channels[n]->addr == addr && channels[n]->port == port) return channels[n]->channelId; } return -1; } // note that this function works even for inactive channels, so that // incoming traffic that is received out-of-order with a // ChannelBind success response is still processable bool getAddressPort(int channelId, QHostAddress *addr, int *port) { for (int n = 0; n < channels.count(); ++n) { if (channels[n]->channelId == channelId) { *addr = channels[n]->addr; *port = channels[n]->port; return true; } } return false; } private: void cleanup() { sess.reset(); cleanupTasks(); erroringCode = -1; erroringString.clear(); state = Stopped; } // stop refreshing, permissions, and channelbinds void cleanupTasks() { delete trans; trans = nullptr; allocateRefreshTimer->stop(); qDeleteAll(channels); channels.clear(); channelsOut.clear(); qDeleteAll(perms); perms.clear(); permsOut.clear(); } void doTransaction() { Q_ASSERT(!trans); trans = new StunTransaction(this); connect(trans, SIGNAL(createMessage(QByteArray)), SLOT(trans_createMessage(QByteArray))); connect(trans, SIGNAL(finished(XMPP::StunMessage)), SLOT(trans_finished(XMPP::StunMessage))); connect(trans, SIGNAL(error(XMPP::StunTransaction::Error)), SLOT(trans_error(XMPP::StunTransaction::Error))); trans->start(pool.data(), stunAddr, stunPort); } void restartRefreshTimer() { // refresh 1 minute shy of the lifetime allocateRefreshTimer->start((allocateLifetime - 60) * 1000); } bool updatePermsOut() { QList newList; for (int n = 0; n < perms.count(); ++n) { if (perms[n]->active) newList += perms[n]->addr; } if (newList == permsOut) return false; permsOut = newList; return true; } bool updateChannelsOut() { QList newList; for (int n = 0; n < channels.count(); ++n) { if (channels[n]->active) newList += StunAllocate::Channel(channels[n]->addr, channels[n]->port); } if (newList == channelsOut) return false; channelsOut = newList; return true; } private slots: void refresh() { Q_ASSERT(state == Started); state = Refreshing; doTransaction(); } void trans_createMessage(const QByteArray &transactionId) { if (state == Starting) { // send Allocate request StunMessage message; message.setMethod(StunTypes::Allocate); message.setId((const quint8 *)transactionId.data()); QList list; if (!clientSoftware.isEmpty()) { StunMessage::Attribute a; a.type = StunTypes::SOFTWARE; a.value = StunTypes::createSoftware(clientSoftware); list += a; } { StunMessage::Attribute a; a.type = StunTypes::LIFETIME; a.value = StunTypes::createLifetime(3600); list += a; } { StunMessage::Attribute a; a.type = StunTypes::REQUESTED_TRANSPORT; a.value = StunTypes::createRequestedTransport(17); // 17=UDP list += a; } if (dfState == DF_Unknown) { StunMessage::Attribute a; a.type = StunTypes::DONT_FRAGMENT; list += a; } message.setAttributes(list); trans->setMessage(message); } else if (state == Stopping || state == Erroring) { StunMessage message; message.setMethod(StunTypes::Refresh); message.setId((const quint8 *)transactionId.data()); QList list; { StunMessage::Attribute a; a.type = StunTypes::LIFETIME; a.value = StunTypes::createLifetime(0); list += a; } message.setAttributes(list); trans->setMessage(message); } else if (state == Refreshing) { StunMessage message; message.setMethod(StunTypes::Refresh); message.setId((const quint8 *)transactionId.data()); QList list; { StunMessage::Attribute a; a.type = StunTypes::LIFETIME; a.value = StunTypes::createLifetime(3600); list += a; } message.setAttributes(list); trans->setMessage(message); } } void trans_finished(const XMPP::StunMessage &response) { delete trans; trans = nullptr; bool error = false; int code; QString reason; if (response.mclass() == StunMessage::ErrorResponse) { if (!StunTypes::parseErrorCode(response.attribute(StunTypes::ERROR_CODE), &code, &reason)) { cleanup(); errorString = "Unable to parse ERROR-CODE in error response."; emit q->error(StunAllocate::ErrorProtocol); return; } error = true; } if (state == Starting) { if (error) { if (code == StunTypes::UnknownAttribute) { QList typeList; if (!StunTypes::parseUnknownAttributes(response.attribute(StunTypes::UNKNOWN_ATTRIBUTES), &typeList)) { cleanup(); errorString = "Unable to parse UNKNOWN-ATTRIBUTES in 420 (Unknown Attribute) error response."; emit q->error(StunAllocate::ErrorProtocol); return; } if (typeList.contains(StunTypes::DONT_FRAGMENT)) { dfState = DF_Unsupported; // stay in same state, try again doTransaction(); } else { cleanup(); errorString = reason; emit q->error(StunAllocate::ErrorGeneric); } return; } else if (code == StunTypes::AllocationMismatch) { cleanup(); errorString = "437 (Allocation Mismatch)."; emit q->error(StunAllocate::ErrorMismatch); return; } else if (code == StunTypes::InsufficientCapacity) { cleanup(); errorString = reason; emit q->error(StunAllocate::ErrorCapacity); return; } else if (code == StunTypes::Unauthorized) { cleanup(); errorString = "Unauthorized"; emit q->error(StunAllocate::ErrorAuth); return; } else { cleanup(); errorString = reason; emit q->error(StunAllocate::ErrorGeneric); return; } } quint32 lifetime; if (!StunTypes::parseLifetime(response.attribute(StunTypes::LIFETIME), &lifetime)) { cleanup(); errorString = "Unable to parse LIFETIME."; emit q->error(StunAllocate::ErrorProtocol); return; } QHostAddress raddr; quint16 rport; if (!StunTypes::parseXorRelayedAddress(response.attribute(StunTypes::XOR_RELAYED_ADDRESS), response.magic(), response.id(), &raddr, &rport)) { cleanup(); errorString = "Unable to parse XOR-RELAYED-ADDRESS."; emit q->error(StunAllocate::ErrorProtocol); return; } QHostAddress saddr; quint16 sport; if (!StunTypes::parseXorMappedAddress(response.attribute(StunTypes::XOR_MAPPED_ADDRESS), response.magic(), response.id(), &saddr, &sport)) { cleanup(); errorString = "Unable to parse XOR-MAPPED-ADDRESS."; emit q->error(StunAllocate::ErrorProtocol); return; } if (lifetime < 120) { state = Started; // stopWithError requires this stopWithError(StunAllocate::ErrorProtocol, "LIFETIME is less than two minutes. That is ridiculous."); return; } QString str; if (StunTypes::parseSoftware(response.attribute(StunTypes::SOFTWARE), &str)) { serverSoftware = str; } allocateLifetime = lifetime; relayedAddress = raddr; relayedPort = rport; reflexiveAddress = saddr; reflexivePort = sport; if (dfState == DF_Unknown) dfState = DF_Supported; state = Started; restartRefreshTimer(); setPermissions(permQueue); permQueue.clear(); emit q->started(); } else if (state == Stopping || state == Erroring) { if (error) { // AllocationMismatch on session cancel doesn't count as an error if (code != StunTypes::AllocationMismatch) { cleanup(); errorString = reason; emit q->error(StunAllocate::ErrorGeneric); return; } } if (state == Stopping) { // cleanup will set the state to Stopped cleanup(); emit q->stopped(); } else // Erroring { int code = erroringCode; QString str = erroringString; // cleanup will set the state to Stopped cleanup(); errorString = str; emit q->error((StunAllocate::Error)code); } } else if (state == Refreshing) { if (error) { cleanup(); errorString = reason; emit q->error(StunAllocate::ErrorRejected); return; } quint32 lifetime; if (!StunTypes::parseLifetime(response.attribute(StunTypes::LIFETIME), &lifetime)) { cleanup(); errorString = "Unable to parse LIFETIME."; emit q->error(StunAllocate::ErrorProtocol); return; } allocateLifetime = lifetime; state = Started; restartRefreshTimer(); } } void perm_ready() { if (updatePermsOut()) emit q->permissionsChanged(); } void perm_error(XMPP::StunAllocatePermission::Error e, const QString &reason) { if (e == StunAllocatePermission::ErrorCapacity) { // if we aren't allowed to make anymore permissions, // don't consider this an error. the perm stays // in the list inactive. we'll try it again if // any perms get removed. return; } else if (e == StunAllocatePermission::ErrorForbidden) { // silently discard the permission request StunAllocatePermission *perm = static_cast(sender()); QHostAddress addr = perm->addr; delete perm; perms.removeAll(perm); emit q->debugLine(QString("Warning: permission forbidden to %1").arg(addr.toString())); return; } cleanup(); errorString = reason; emit q->error(StunAllocatePermission::errorToStunAllocateError(e)); } void channel_ready() { if (updateChannelsOut()) emit q->channelsChanged(); } void channel_error(XMPP::StunAllocateChannel::Error e, const QString &reason) { if (e == StunAllocateChannel::ErrorCapacity) { // if we aren't allowed to make anymore channels, // don't consider this an error. the channel stays // in the list inactive. we'll try it again if // any channels get removed. return; } cleanup(); errorString = reason; emit q->error(StunAllocateChannel::errorToStunAllocateError(e)); } void trans_error(XMPP::StunTransaction::Error e) { delete trans; trans = nullptr; cleanup(); if (e == StunTransaction::ErrorTimeout) { errorString = "Request timed out."; emit q->error(StunAllocate::ErrorTimeout); } else { errorString = "Generic transaction error."; emit q->error(StunAllocate::ErrorGeneric); } } }; StunAllocate::StunAllocate(StunTransactionPool *pool) : QObject(pool) { d = new Private(this); d->pool = pool->sharedFromThis(); } StunAllocate::~StunAllocate() { delete d; } void StunAllocate::setClientSoftwareNameAndVersion(const QString &str) { d->clientSoftware = str; } void StunAllocate::start() { d->start(); } void StunAllocate::start(const QHostAddress &addr, int port) { d->start(addr, port); } void StunAllocate::stop() { d->stop(); } QString StunAllocate::serverSoftwareNameAndVersion() const { return d->serverSoftware; } QHostAddress StunAllocate::reflexiveAddress() const { return d->reflexiveAddress; } int StunAllocate::reflexivePort() const { return d->reflexivePort; } QHostAddress StunAllocate::relayedAddress() const { return d->relayedAddress; } int StunAllocate::relayedPort() const { return d->relayedPort; } QList StunAllocate::permissions() const { return d->permsOut; } void StunAllocate::setPermissions(const QList &perms) { d->setPermissions(perms); } QList StunAllocate::channels() const { return d->channelsOut; } void StunAllocate::setChannels(const QList &channels) { d->setChannels(channels); } int StunAllocate::packetHeaderOverhead(const QHostAddress &addr, int port) const { int channelId = d->getChannel(addr, port); if (channelId != -1) { // overhead of ChannelData if (d->pool->mode() == StunTransaction::Udp) return 4; else // Tcp return 4 + 3; // add 3 for potential padding } else { // we add 3 for potential padding if (d->dfState == StunAllocate::Private::DF_Supported) { // overhead of STUN-based data, with DONT_FRAGMENT return 40 + 3; } else { // overhead of STUN-based data, without DONT-FRAGMENT return 36 + 3; } } return -1; } QByteArray StunAllocate::encode(const QByteArray &datagram, const QHostAddress &addr, int port) { int channelId = d->getChannel(addr, port); if (channelId != -1) { if (datagram.size() > 65535) return QByteArray(); quint16 num = channelId; quint16 len = datagram.size(); int plen = len; // in tcp mode, round to up to nearest 4 bytes if (d->pool->mode() == StunTransaction::Tcp) { int remainder = plen % 4; if (remainder != 0) plen += (4 - remainder); } QByteArray out(4 + plen, 0); StunUtil::write16((quint8 *)out.data(), num); StunUtil::write16((quint8 *)out.data() + 2, len); memcpy(out.data() + 4, datagram.data(), datagram.size()); return out; } else { StunMessage message; message.setClass(StunMessage::Indication); message.setMethod(StunTypes::Send); QByteArray id = d->pool->generateId(); message.setId((const quint8 *)id.data()); QList list; { StunMessage::Attribute a; a.type = StunTypes::XOR_PEER_ADDRESS; a.value = StunTypes::createXorPeerAddress(addr, port, message.magic(), message.id()); list += a; } if (d->dfState == StunAllocate::Private::DF_Supported) { StunMessage::Attribute a; a.type = StunTypes::DONT_FRAGMENT; list += a; } { StunMessage::Attribute a; a.type = StunTypes::DATA; a.value = datagram; list += a; } message.setAttributes(list); return message.toBinary(); } } QByteArray StunAllocate::decode(const QByteArray &encoded, QHostAddress *addr, int *port) { if (encoded.size() < 4) return QByteArray(); quint16 num = StunUtil::read16((const quint8 *)encoded.data()); quint16 len = StunUtil::read16((const quint8 *)encoded.data() + 2); if (encoded.size() - 4 < (int)len) return QByteArray(); if (!d->getAddressPort(num, addr, port)) return QByteArray(); return encoded.mid(4, len); } QByteArray StunAllocate::decode(const StunMessage &encoded, QHostAddress *addr, int *port) { QHostAddress paddr; quint16 pport; if (!StunTypes::parseXorPeerAddress(encoded.attribute(StunTypes::XOR_PEER_ADDRESS), encoded.magic(), encoded.id(), &paddr, &pport)) return QByteArray(); QByteArray data = encoded.attribute(StunTypes::DATA); if (data.isNull()) return QByteArray(); *addr = paddr; *port = pport; return data; } QString StunAllocate::errorString() const { return d->errorString; } bool StunAllocate::containsChannelData(const quint8 *data, int size) { return (check_channelData(data, size) != -1 ? true : false); } QByteArray StunAllocate::readChannelData(const quint8 *data, int size) { int len = check_channelData(data, size); if (len != -1) return QByteArray((const char *)data, len); else return QByteArray(); } } // namespace XMPP #include "stunallocate.moc" psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/stunallocate.h000066400000000000000000000063211370065651000253010ustar00rootroot00000000000000/* * Copyright (C) 2009 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef STUNALLOCATE_H #define STUNALLOCATE_H #include #include #include class QByteArray; namespace XMPP { class StunMessage; class StunTransactionPool; class StunAllocate : public QObject { Q_OBJECT public: enum Error { ErrorGeneric, ErrorTimeout, ErrorAuth, ErrorRejected, ErrorProtocol, ErrorCapacity, ErrorMismatch }; class Channel { public: QHostAddress address; int port; Channel(const QHostAddress &_address, int _port) : address(_address), port(_port) { } inline bool operator==(const Channel &other) { if (address == other.address && port == other.port) return true; else return false; } inline bool operator!=(const Channel &other) { return !operator==(other); } }; StunAllocate(XMPP::StunTransactionPool *pool); ~StunAllocate(); void setClientSoftwareNameAndVersion(const QString &str); void start(); void start(const QHostAddress &addr, int port); // use addr association void stop(); QString serverSoftwareNameAndVersion() const; QHostAddress reflexiveAddress() const; int reflexivePort() const; QHostAddress relayedAddress() const; int relayedPort() const; QList permissions() const; void setPermissions(const QList &perms); QList channels() const; void setChannels(const QList &channels); int packetHeaderOverhead(const QHostAddress &addr, int port) const; QByteArray encode(const QByteArray &datagram, const QHostAddress &addr, int port); QByteArray decode(const QByteArray &encoded, QHostAddress *addr = nullptr, int *port = nullptr); QByteArray decode(const StunMessage &encoded, QHostAddress *addr = nullptr, int *port = nullptr); QString errorString() const; static bool containsChannelData(const quint8 *data, int size); static QByteArray readChannelData(const quint8 *data, int size); signals: void started(); void stopped(); void error(XMPP::StunAllocate::Error e); // emitted after calling setPermissions() void permissionsChanged(); // emitted after calling setChannels() void channelsChanged(); // not DOR-SS/DS safe void debugLine(const QString &line); private: Q_DISABLE_COPY(StunAllocate) class Private; friend class Private; Private *d; }; } // namespace XMPP #endif // STUNALLOCATE_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/stunbinding.cpp000066400000000000000000000171771370065651000254750ustar00rootroot00000000000000/* * Copyright (C) 2009 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "stunbinding.h" #include "stunmessage.h" #include "stuntransaction.h" #include "stuntypes.h" #include namespace XMPP { class StunBinding::Private : public QObject { Q_OBJECT public: StunBinding * q; StunTransactionPool::Ptr pool; QScopedPointer trans; QHostAddress stunAddr; int stunPort = 0; QHostAddress addr; int port = 0; QString errorString; bool use_extPriority = false, use_extIceControlling = false, use_extIceControlled = false; quint32 extPriority = 0; bool extUseCandidate = false; quint64 extIceControlling = 0, extIceControlled = 0; QString stuser, stpass; bool fpRequired = false; Private(StunBinding *_q) : QObject(_q), q(_q) { } ~Private() { } void start(const QHostAddress &_addr = QHostAddress(), int _port = -1) { Q_ASSERT(!trans); stunAddr = _addr; stunPort = _port; trans.reset(new StunTransaction()); connect(trans.data(), SIGNAL(createMessage(QByteArray)), SLOT(trans_createMessage(QByteArray))); connect(trans.data(), SIGNAL(finished(XMPP::StunMessage)), SLOT(trans_finished(XMPP::StunMessage))); connect(trans.data(), SIGNAL(error(XMPP::StunTransaction::Error)), SLOT(trans_error(XMPP::StunTransaction::Error))); if (!stuser.isEmpty()) { trans->setShortTermUsername(stuser); trans->setShortTermPassword(stpass); } trans->setFingerprintRequired(fpRequired); trans->start(pool.data(), stunAddr, stunPort); } void cancel() { if (!trans) return; auto t = trans.take(); t->disconnect(this); t->cancel(); // will self-delete the transaction either on incoming or timeout // just in case those too addr = QHostAddress(); port = 0; errorString.clear(); // now the binding can be reused } private slots: void trans_createMessage(const QByteArray &transactionId) { StunMessage message; message.setMethod(StunTypes::Binding); message.setId((const quint8 *)transactionId.data()); QList list; if (use_extPriority) { StunMessage::Attribute a; a.type = StunTypes::PRIORITY; a.value = StunTypes::createPriority(extPriority); list += a; } if (extUseCandidate) { StunMessage::Attribute a; a.type = StunTypes::USE_CANDIDATE; list += a; } if (use_extIceControlling) { StunMessage::Attribute a; a.type = StunTypes::ICE_CONTROLLING; a.value = StunTypes::createIceControlling(extIceControlling); list += a; } if (use_extIceControlled) { StunMessage::Attribute a; a.type = StunTypes::ICE_CONTROLLED; a.value = StunTypes::createIceControlled(extIceControlled); list += a; } message.setAttributes(list); trans->setMessage(message); } void trans_finished(const XMPP::StunMessage &response) { trans.reset(); bool error = false; int code; QString reason; if (response.mclass() == StunMessage::ErrorResponse) { if (!StunTypes::parseErrorCode(response.attribute(StunTypes::ERROR_CODE), &code, &reason)) { errorString = "Unable to parse ERROR-CODE in error response."; emit q->error(StunBinding::ErrorProtocol); return; } error = true; } if (error) { errorString = reason; if (code == StunTypes::RoleConflict) emit q->error(StunBinding::ErrorConflict); else emit q->error(StunBinding::ErrorRejected); return; } QHostAddress saddr; quint16 sport = 0; QByteArray val; val = response.attribute(StunTypes::XOR_MAPPED_ADDRESS); if (!val.isNull()) { if (!StunTypes::parseXorMappedAddress(val, response.magic(), response.id(), &saddr, &sport)) { errorString = "Unable to parse XOR-MAPPED-ADDRESS response."; emit q->error(StunBinding::ErrorProtocol); return; } } else { val = response.attribute(StunTypes::MAPPED_ADDRESS); if (!val.isNull()) { if (!StunTypes::parseMappedAddress(val, &saddr, &sport)) { errorString = "Unable to parse MAPPED-ADDRESS response."; emit q->error(StunBinding::ErrorProtocol); return; } } else { errorString = "Response does not contain XOR-MAPPED-ADDRESS or MAPPED-ADDRESS."; emit q->error(StunBinding::ErrorProtocol); return; } } addr = saddr; port = sport; emit q->success(); } void trans_error(XMPP::StunTransaction::Error e) { trans.reset(); if (e == StunTransaction::ErrorTimeout) { errorString = "Request timed out."; emit q->error(StunBinding::ErrorTimeout); } else { errorString = "Generic transaction error."; emit q->error(StunBinding::ErrorGeneric); } } }; StunBinding::StunBinding(StunTransactionPool *pool) : QObject(pool) { d = new Private(this); d->pool = pool->sharedFromThis(); } StunBinding::~StunBinding() { delete d; } void StunBinding::setPriority(quint32 i) { d->use_extPriority = true; d->extPriority = i; } quint32 StunBinding::priority() const { return d->extPriority; } void StunBinding::setUseCandidate(bool enabled) { d->extUseCandidate = enabled; } bool StunBinding::useCandidate() const { return d->extUseCandidate; } void StunBinding::setIceControlling(quint64 i) { d->use_extIceControlling = true; d->extIceControlling = i; } void StunBinding::setIceControlled(quint64 i) { d->use_extIceControlled = true; d->extIceControlled = i; } void StunBinding::setShortTermUsername(const QString &username) { d->stuser = username; } void StunBinding::setShortTermPassword(const QString &password) { d->stpass = password; } void StunBinding::setFingerprintRequired(bool enabled) { d->fpRequired = enabled; } void StunBinding::start() { d->start(); } void StunBinding::start(const QHostAddress &addr, int port) { d->start(addr, port); } void StunBinding::cancel() { d->cancel(); } QHostAddress StunBinding::reflexiveAddress() const { return d->addr; } int StunBinding::reflexivePort() const { return d->port; } QString StunBinding::errorString() const { return d->errorString; } } // namespace XMPP #include "stunbinding.moc" psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/stunbinding.h000066400000000000000000000037231370065651000251320ustar00rootroot00000000000000/* * Copyright (C) 2009 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef STUNBINDING_H #define STUNBINDING_H #include class QHostAddress; namespace XMPP { class StunTransactionPool; class StunBinding : public QObject { Q_OBJECT public: enum Error { ErrorGeneric, ErrorTimeout, ErrorRejected, ErrorProtocol, ErrorConflict }; StunBinding(StunTransactionPool *pool); ~StunBinding(); // for ICE-use only void setPriority(quint32 i); quint32 priority() const; void setUseCandidate(bool enabled); bool useCandidate() const; void setIceControlling(quint64 i); void setIceControlled(quint64 i); void setShortTermUsername(const QString &username); void setShortTermPassword(const QString &password); void setFingerprintRequired(bool enabled); void start(); void start(const QHostAddress &addr, int port); // use addr association void cancel(); QHostAddress reflexiveAddress() const; int reflexivePort() const; // non-translatable diagnostic string for convenience QString errorString() const; signals: void success(); void error(XMPP::StunBinding::Error e); private: Q_DISABLE_COPY(StunBinding) class Private; friend class Private; Private *d; }; } // namespace XMPP #endif // STUNBINDING_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/stunmessage.cpp000066400000000000000000000503031370065651000254730ustar00rootroot00000000000000/* * Copyright (C) 2009 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "stunmessage.h" #include "stunutil.h" #include #include #define ENSURE_D \ { \ if (!d) \ d = new Private; \ } namespace XMPP { using namespace StunUtil; // some attribute types we need to explicitly support enum { AttribMessageIntegrity = 0x0008, AttribFingerprint = 0x8028 }; // adapted from public domain source by Ross Williams and Eric Durbin unsigned long crctable[256] = { 0x00000000L, 0x77073096L, 0xEE0E612CL, 0x990951BAL, 0x076DC419L, 0x706AF48FL, 0xE963A535L, 0x9E6495A3L, 0x0EDB8832L, 0x79DCB8A4L, 0xE0D5E91EL, 0x97D2D988L, 0x09B64C2BL, 0x7EB17CBDL, 0xE7B82D07L, 0x90BF1D91L, 0x1DB71064L, 0x6AB020F2L, 0xF3B97148L, 0x84BE41DEL, 0x1ADAD47DL, 0x6DDDE4EBL, 0xF4D4B551L, 0x83D385C7L, 0x136C9856L, 0x646BA8C0L, 0xFD62F97AL, 0x8A65C9ECL, 0x14015C4FL, 0x63066CD9L, 0xFA0F3D63L, 0x8D080DF5L, 0x3B6E20C8L, 0x4C69105EL, 0xD56041E4L, 0xA2677172L, 0x3C03E4D1L, 0x4B04D447L, 0xD20D85FDL, 0xA50AB56BL, 0x35B5A8FAL, 0x42B2986CL, 0xDBBBC9D6L, 0xACBCF940L, 0x32D86CE3L, 0x45DF5C75L, 0xDCD60DCFL, 0xABD13D59L, 0x26D930ACL, 0x51DE003AL, 0xC8D75180L, 0xBFD06116L, 0x21B4F4B5L, 0x56B3C423L, 0xCFBA9599L, 0xB8BDA50FL, 0x2802B89EL, 0x5F058808L, 0xC60CD9B2L, 0xB10BE924L, 0x2F6F7C87L, 0x58684C11L, 0xC1611DABL, 0xB6662D3DL, 0x76DC4190L, 0x01DB7106L, 0x98D220BCL, 0xEFD5102AL, 0x71B18589L, 0x06B6B51FL, 0x9FBFE4A5L, 0xE8B8D433L, 0x7807C9A2L, 0x0F00F934L, 0x9609A88EL, 0xE10E9818L, 0x7F6A0DBBL, 0x086D3D2DL, 0x91646C97L, 0xE6635C01L, 0x6B6B51F4L, 0x1C6C6162L, 0x856530D8L, 0xF262004EL, 0x6C0695EDL, 0x1B01A57BL, 0x8208F4C1L, 0xF50FC457L, 0x65B0D9C6L, 0x12B7E950L, 0x8BBEB8EAL, 0xFCB9887CL, 0x62DD1DDFL, 0x15DA2D49L, 0x8CD37CF3L, 0xFBD44C65L, 0x4DB26158L, 0x3AB551CEL, 0xA3BC0074L, 0xD4BB30E2L, 0x4ADFA541L, 0x3DD895D7L, 0xA4D1C46DL, 0xD3D6F4FBL, 0x4369E96AL, 0x346ED9FCL, 0xAD678846L, 0xDA60B8D0L, 0x44042D73L, 0x33031DE5L, 0xAA0A4C5FL, 0xDD0D7CC9L, 0x5005713CL, 0x270241AAL, 0xBE0B1010L, 0xC90C2086L, 0x5768B525L, 0x206F85B3L, 0xB966D409L, 0xCE61E49FL, 0x5EDEF90EL, 0x29D9C998L, 0xB0D09822L, 0xC7D7A8B4L, 0x59B33D17L, 0x2EB40D81L, 0xB7BD5C3BL, 0xC0BA6CADL, 0xEDB88320L, 0x9ABFB3B6L, 0x03B6E20CL, 0x74B1D29AL, 0xEAD54739L, 0x9DD277AFL, 0x04DB2615L, 0x73DC1683L, 0xE3630B12L, 0x94643B84L, 0x0D6D6A3EL, 0x7A6A5AA8L, 0xE40ECF0BL, 0x9309FF9DL, 0x0A00AE27L, 0x7D079EB1L, 0xF00F9344L, 0x8708A3D2L, 0x1E01F268L, 0x6906C2FEL, 0xF762575DL, 0x806567CBL, 0x196C3671L, 0x6E6B06E7L, 0xFED41B76L, 0x89D32BE0L, 0x10DA7A5AL, 0x67DD4ACCL, 0xF9B9DF6FL, 0x8EBEEFF9L, 0x17B7BE43L, 0x60B08ED5L, 0xD6D6A3E8L, 0xA1D1937EL, 0x38D8C2C4L, 0x4FDFF252L, 0xD1BB67F1L, 0xA6BC5767L, 0x3FB506DDL, 0x48B2364BL, 0xD80D2BDAL, 0xAF0A1B4CL, 0x36034AF6L, 0x41047A60L, 0xDF60EFC3L, 0xA867DF55L, 0x316E8EEFL, 0x4669BE79L, 0xCB61B38CL, 0xBC66831AL, 0x256FD2A0L, 0x5268E236L, 0xCC0C7795L, 0xBB0B4703L, 0x220216B9L, 0x5505262FL, 0xC5BA3BBEL, 0xB2BD0B28L, 0x2BB45A92L, 0x5CB36A04L, 0xC2D7FFA7L, 0xB5D0CF31L, 0x2CD99E8BL, 0x5BDEAE1DL, 0x9B64C2B0L, 0xEC63F226L, 0x756AA39CL, 0x026D930AL, 0x9C0906A9L, 0xEB0E363FL, 0x72076785L, 0x05005713L, 0x95BF4A82L, 0xE2B87A14L, 0x7BB12BAEL, 0x0CB61B38L, 0x92D28E9BL, 0xE5D5BE0DL, 0x7CDCEFB7L, 0x0BDBDF21L, 0x86D3D2D4L, 0xF1D4E242L, 0x68DDB3F8L, 0x1FDA836EL, 0x81BE16CDL, 0xF6B9265BL, 0x6FB077E1L, 0x18B74777L, 0x88085AE6L, 0xFF0F6A70L, 0x66063BCAL, 0x11010B5CL, 0x8F659EFFL, 0xF862AE69L, 0x616BFFD3L, 0x166CCF45L, 0xA00AE278L, 0xD70DD2EEL, 0x4E048354L, 0x3903B3C2L, 0xA7672661L, 0xD06016F7L, 0x4969474DL, 0x3E6E77DBL, 0xAED16A4AL, 0xD9D65ADCL, 0x40DF0B66L, 0x37D83BF0L, 0xA9BCAE53L, 0xDEBB9EC5L, 0x47B2CF7FL, 0x30B5FFE9L, 0xBDBDF21CL, 0xCABAC28AL, 0x53B39330L, 0x24B4A3A6L, 0xBAD03605L, 0xCDD70693L, 0x54DE5729L, 0x23D967BFL, 0xB3667A2EL, 0xC4614AB8L, 0x5D681B02L, 0x2A6F2B94L, 0xB40BBE37L, 0xC30C8EA1L, 0x5A05DF1BL, 0x2D02EF8DL }; class Crc32 { private: quint32 result; public: Crc32() { clear(); } void clear() { result = 0xffffffff; } void update(const QByteArray &in) { for (int n = 0; n < in.size(); ++n) result = (result >> 8) ^ (crctable[(result & 0xff) ^ (quint8)in[n]]); } quint32 final() { return result ^= 0xffffffff; } static quint32 process(const QByteArray &in) { Crc32 c; c.update(in); return c.final(); } }; static quint8 magic_cookie[4] = { 0x21, 0x12, 0xA4, 0x42 }; // do 3-field check of stun packet // returns length of packet not counting the header, or -1 on error static int check_and_get_length(const QByteArray &buf) { // stun packets are at least 20 bytes if (buf.size() < 20) return -1; // minimal 3-field check // top 2 bits of packet must be 0 if (buf[0] & 0xC0) return -1; const quint8 *p = (const quint8 *)buf.data(); quint16 mlen = read16(p + 2); // bottom 2 bits of message length field must be 0 if (mlen & 0x03) return -1; // (also, the message length should be a reasonable size) if (mlen + 20 > buf.size()) return -1; // magic cookie must be set if (memcmp(p + 4, magic_cookie, 4) != 0) return -1; return mlen; } #define ATTRIBUTE_AREA_START 20 #define ATTRIBUTE_AREA_MAX 65535 #define ATTRIBUTE_VALUE_MAX 65531 // note: because the attribute area of the packet has a maximum size of // 2^16-1, and each attribute itself has a 4 byte header, it follows that // the maximum size of an attribute's value is 2^16-5. this means that, // even if padded with up to 3 bytes, the physical size of an attribute's // value will not overflow a 16-bit unsigned integer. static quint16 round_up_length(quint16 in) { Q_ASSERT(in <= ATTRIBUTE_VALUE_MAX); quint16 out = in; quint16 remainder = out % 4; if (remainder != 0) out += (4 - remainder); return out; } // buf = entire stun packet // offset = byte index of current attribute (first is offset=20) // type = take attribute type // len = take attribute value length (value is at offset + 4) // returns offset of next attribute, -1 if no more static int get_attribute_props(const QByteArray &buf, int offset, quint16 *type, int *len) { Q_ASSERT(offset >= ATTRIBUTE_AREA_START); const quint8 *p = (const quint8 *)buf.data(); // need at least 4 bytes for an attribute if (offset + 4 > buf.size()) return -1; quint16 _type = read16(p + offset); offset += 2; quint16 _alen = read16(p + offset); offset += 2; // get physical length. stun attributes are 4-byte aligned, and may // contain 0-3 bytes of padding. quint16 plen = round_up_length(_alen); if (offset + plen > buf.size()) return -1; *type = _type; *len = _alen; return offset + plen; } // buf = entire stun packet // type = attribute type to find // len = take attribute value length (value is at offset + 4) // next = take offset of next attribute // returns offset of found attribute, -1 if not found static int find_attribute(const QByteArray &buf, quint16 type, int *len, int *next = nullptr) { int at = ATTRIBUTE_AREA_START; quint16 _type; int _len; int _next; while (1) { _next = get_attribute_props(buf, at, &_type, &_len); if (_next == -1) break; if (_type == type) { *len = _len; if (next) *next = _next; return at; } at = _next; } return -1; } // buf = stun packet to append attribute to // type = type of attribute // len = length of value // returns offset of new attribute, or -1 if it can't fit // note: attribute value is located at offset + 4 and is uninitialized // note: padding following attribute is zeroed out static int append_attribute_uninitialized(QByteArray *buf, quint16 type, int len) { if (len > ATTRIBUTE_VALUE_MAX) return -1; quint16 alen = (quint16)len; quint16 plen = round_up_length(alen); if ((buf->size() - ATTRIBUTE_AREA_START) + 4 + plen > ATTRIBUTE_AREA_MAX) return -1; int at = buf->size(); buf->resize(buf->size() + 4 + plen); quint8 *p = (quint8 *)buf->data(); write16(p + at, type); write16(p + at + 2, alen); // padding for (int n = 0; n < plen - alen; ++n) p[at + alen + n] = 0; return at; } static quint32 fingerprint_calc(const quint8 *buf, int size) { QByteArray region = QByteArray::fromRawData((const char *)buf, size); return Crc32::process(region) ^ 0x5354554e; } static QByteArray message_integrity_calc(const quint8 *buf, int size, const QByteArray &key) { QCA::MessageAuthenticationCode hmac("hmac(sha1)", key); QByteArray region = QByteArray::fromRawData((const char *)buf, size); QByteArray result = hmac.process(region).toByteArray(); Q_ASSERT(result.size() == 20); return result; } // look for fingerprint attribute and confirm it // buf = entire stun packet // returns true if fingerprint attribute exists and is correct static bool fingerprint_check(const QByteArray &buf) { int at, len; at = find_attribute(buf, AttribFingerprint, &len); if (at == -1 || len != 4) // value must be 4 bytes return false; const quint8 *p = (const quint8 *)buf.data(); quint32 fpval = read32(p + at + 4); quint32 fpcalc = fingerprint_calc(p, at); if (fpval == fpcalc) return true; else return false; } // copy the input buffer and prepare for message integrity checking. the // packet is truncated after the message-integrity attribute (since nothing // after it is protected), and the packet length is adjusted in the header // accordingly. // buf = input stun packet // out = take output stun packet // offset = take offset of message-integrity attribute // returns true if message-integrity attribute exists and packet is prepared // note: message-integrity value is at offset + 4 and is exactly 20 bytes static bool message_integrity_prep(const QByteArray &buf, QByteArray *out, int *offset) { int at, len, next; at = find_attribute(buf, AttribMessageIntegrity, &len, &next); if (at == -1 || len != 20) // value must be 20 bytes return false; // prepare new attribute area size int i = next - ATTRIBUTE_AREA_START; // new value must be divisible by 4 if (i % 4 != 0) return false; // copy truncated packet *out = buf.mid(0, next); // set new length in header quint16 newlen = (quint16)i; write16((quint8 *)out->data() + 2, newlen); *offset = at; return true; } // confirm message integrity // buf = prepared stun packet (from message_integrity_prep()) // offset = offset of message-integrity attribute // key = the HMAC key // returns true if correct static bool message_integrity_check(const QByteArray &buf, int offset, const QByteArray &key) { QByteArray mival = QByteArray::fromRawData(buf.data() + offset + 4, 20); QByteArray micalc = message_integrity_calc((const quint8 *)buf.data(), offset, key); if (mival == micalc) return true; else return false; } class StunMessage::Private : public QSharedData { public: StunMessage::Class mclass; quint16 method; quint8 magic[4]; quint8 id[12]; QList attribs; Private() { mclass = (StunMessage::Class)-1; method = 0; memcpy(magic, magic_cookie, 4); memset(id, 0, 12); } }; StunMessage::StunMessage() : d(nullptr) { } StunMessage::StunMessage(const StunMessage &from) : d(from.d) { } StunMessage::~StunMessage() { } StunMessage &StunMessage::operator=(const StunMessage &from) { d = from.d; return *this; } bool StunMessage::isNull() const { return (d ? false : true); } StunMessage::Class StunMessage::mclass() const { Q_ASSERT(d); return d->mclass; } quint16 StunMessage::method() const { Q_ASSERT(d); return d->method; } const quint8 *StunMessage::magic() const { Q_ASSERT(d); return d->magic; } const quint8 *StunMessage::id() const { Q_ASSERT(d); return d->id; } QList StunMessage::attributes() const { Q_ASSERT(d); return d->attribs; } QByteArray StunMessage::attribute(quint16 type) const { Q_ASSERT(d); for (const Attribute &i : d->attribs) { if (i.type == type) return i.value; } return QByteArray(); } bool StunMessage::hasAttribute(quint16 type) const { Q_ASSERT(d); for (const Attribute &i : d->attribs) { if (i.type == type) return true; } return false; } void StunMessage::setClass(Class mclass) { ENSURE_D d->mclass = mclass; } void StunMessage::setMethod(quint16 method) { ENSURE_D d->method = method; } void StunMessage::setMagic(const quint8 *magic) { ENSURE_D memcpy(d->magic, magic, 4); } void StunMessage::setId(const quint8 *id) { ENSURE_D memcpy(d->id, id, 12); } void StunMessage::setAttributes(const QList &attribs) { ENSURE_D d->attribs = attribs; } QByteArray StunMessage::toBinary(int validationFlags, const QByteArray &key) const { Q_ASSERT(d); // header QByteArray buf(20, 0); quint8 * p = (quint8 *)buf.data(); quint8 classbits = 0; if (d->mclass == Request) classbits = 0; // 00 else if (d->mclass == Indication) classbits = 1; // 01 else if (d->mclass == SuccessResponse) classbits = 2; // 10 else if (d->mclass == ErrorResponse) classbits = 3; // 11 else Q_ASSERT(0); // method bits are split into 3 sections quint16 m1, m2, m3; m1 = d->method & 0x0f80; // M7-11 m1 <<= 2; m2 = d->method & 0x0070; // M4-6 m2 <<= 1; m3 = d->method & 0x000f; // M0-3 // class bits are split into 2 sections quint16 c1, c2; c1 = classbits & 0x02; // C1 c1 <<= 7; c2 = classbits & 0x01; // C0 c2 <<= 4; quint16 type = m1 | m2 | m3 | c1 | c2; write16(p, type); write16(p + 2, 0); memcpy(p + 4, d->magic, 4); memcpy(p + 8, d->id, 12); for (const Attribute &i : d->attribs) { int at = append_attribute_uninitialized(&buf, i.type, i.value.size()); if (at == -1) return QByteArray(); p = (quint8 *)buf.data(); // follow the resize memcpy(buf.data() + at + 4, i.value.data(), i.value.size()); } // set attribute area size write16(p + 2, buf.size() - ATTRIBUTE_AREA_START); if (validationFlags & MessageIntegrity) { quint16 alen = 20; // size of hmac(sha1) int at = append_attribute_uninitialized(&buf, AttribMessageIntegrity, alen); if (at == -1) return QByteArray(); p = (quint8 *)buf.data(); // follow the resize // set attribute area size to include the new attribute write16(p + 2, buf.size() - ATTRIBUTE_AREA_START); // now calculate the hash and fill in the value QByteArray result = message_integrity_calc(p, at, key); Q_ASSERT(result.size() == alen); memcpy(p + at + 4, result.data(), alen); } if (validationFlags & Fingerprint) { quint16 alen = 4; // size of crc32 int at = append_attribute_uninitialized(&buf, AttribFingerprint, alen); if (at == -1) return QByteArray(); p = (quint8 *)buf.data(); // follow the resize // set attribute area size to include the new attribute write16(p + 2, buf.size() - ATTRIBUTE_AREA_START); // now calculate the fingerprint and fill in the value quint32 fpcalc = fingerprint_calc(p, at); write32(p + at + 4, fpcalc); } return buf; } StunMessage StunMessage::fromBinary(const QByteArray &a, ConvertResult *result, int validationFlags, const QByteArray &key) { int mlen = check_and_get_length(a); if (mlen == -1) { if (result) *result = ErrorFormat; return StunMessage(); } if (validationFlags & Fingerprint) { if (!fingerprint_check(a)) { if (result) *result = ErrorFingerprint; return StunMessage(); } } QByteArray in; if (validationFlags & MessageIntegrity) { int offset; if (!message_integrity_prep(a, &in, &offset)) { if (result) *result = ErrorMessageIntegrity; return StunMessage(); } if (!message_integrity_check(in, offset, key)) { if (result) *result = ErrorMessageIntegrity; return StunMessage(); } } else in = a; // all validating complete, now just parse the packet const quint8 *p = (const quint8 *)in.data(); // method bits are split into 3 sections quint16 m1, m2, m3; m1 = p[0] & 0x3e; // M7-11 m1 <<= 6; m2 = p[1] & 0xe0; // M4-6 m2 >>= 1; m3 = p[1] & 0x0f; // M0-3 // class bits are split into 2 sections quint8 c1, c2; c1 = p[0] & 0x01; // C1 c1 <<= 1; c2 = p[1] & 0x10; // C0 c2 >>= 4; quint16 method = m1 | m2 | m3; quint8 classbits = c1 | c2; Class mclass; if (classbits == 0) // 00 mclass = Request; else if (classbits == 1) // 01 mclass = Indication; else if (classbits == 2) // 10 mclass = SuccessResponse; else // 11 mclass = ErrorResponse; StunMessage out; out.setClass(mclass); out.setMethod(method); out.setMagic(p + 4); out.setId(p + 8); QList list; int at = ATTRIBUTE_AREA_START; while (1) { quint16 type; int len; int next; next = get_attribute_props(in, at, &type, &len); if (next == -1) break; Attribute attrib; attrib.type = type; attrib.value = in.mid(at + 4, len); list += attrib; at = next; } out.setAttributes(list); if (result) *result = ConvertGood; return out; } bool StunMessage::isProbablyStun(const QByteArray &a) { return (check_and_get_length(a) != -1 ? true : false); } StunMessage::Class StunMessage::extractClass(const QByteArray &in) { const quint8 *p = (const quint8 *)in.data(); // class bits are split into 2 sections quint8 c1, c2; c1 = p[0] & 0x01; // C1 c1 <<= 1; c2 = p[1] & 0x10; // C0 c2 >>= 4; quint8 classbits = c1 | c2; Class mclass; if (classbits == 0) // 00 mclass = Request; else if (classbits == 1) // 01 mclass = Indication; else if (classbits == 2) // 10 mclass = SuccessResponse; else // 11 mclass = ErrorResponse; return mclass; } bool StunMessage::containsStun(const quint8 *data, int size) { // check_and_get_length does a full packet check so it works even on a stream return (check_and_get_length(QByteArray::fromRawData((const char *)data, size)) != -1 ? true : false); } QByteArray StunMessage::readStun(const quint8 *data, int size) { QByteArray in = QByteArray::fromRawData((const char *)data, size); int mlen = check_and_get_length(in); if (mlen != -1) return QByteArray((const char *)data, mlen + 20); else return QByteArray(); } } // namespace XMPP psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/stunmessage.h000066400000000000000000000060151370065651000251410ustar00rootroot00000000000000/* * Copyright (C) 2009 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef STUNMESSAGE_H #define STUNMESSAGE_H #include #include #include namespace XMPP { class StunMessage { public: enum Class { Request, SuccessResponse, ErrorResponse, Indication }; enum ValidationFlags { Fingerprint = 0x01, // you must have the hmac(sha1) algorithm in QCA to use MessageIntegrity = 0x02 }; enum ConvertResult { ConvertGood, ErrorFormat, ErrorFingerprint, ErrorMessageIntegrity, ErrorConvertUnknown = 64 }; class Attribute { public: quint16 type; QByteArray value; }; StunMessage(); StunMessage(const StunMessage &from); ~StunMessage(); StunMessage &operator=(const StunMessage &from); bool isNull() const; Class mclass() const; quint16 method() const; const quint8 * magic() const; // 4 bytes const quint8 * id() const; // 12 bytes QList attributes() const; // returns the first instance or null QByteArray attribute(quint16 type) const; bool hasAttribute(quint16 type) const; void setClass(Class mclass); void setMethod(quint16 method); void setMagic(const quint8 *magic); // 4 bytes void setId(const quint8 *id); // 12 bytes void setAttributes(const QList &attribs); QByteArray toBinary(int validationFlags = 0, const QByteArray &key = QByteArray()) const; static StunMessage fromBinary(const QByteArray &a, ConvertResult *result = nullptr, int validationFlags = 0, const QByteArray &key = QByteArray()); // minimal 3-field check static bool isProbablyStun(const QByteArray &a); // extract out the class value from a raw packet. assumes that 'a' has // already passed isProbablyStun() static Class extractClass(const QByteArray &a); // examine raw data, such as from a stream, to see if it contains a // stun packet static bool containsStun(const quint8 *data, int size); // try to read a stun packet from the raw data, else return null. // a successful result can be passed to fromBinary() static QByteArray readStun(const quint8 *data, int size); private: class Private; QSharedDataPointer d; }; } // namespace XMPP #endif // STUNMESSAGE_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/stuntransaction.cpp000066400000000000000000000534701370065651000264040ustar00rootroot00000000000000/* * Copyright (C) 2009 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "stuntransaction.h" #include "stunbinding.h" #include "stunmessage.h" #include "stuntypes.h" #include "stunutil.h" #include #include #include #include #include #include Q_DECLARE_METATYPE(XMPP::StunTransaction::Error) namespace XMPP { // parse a stun message, optionally performing validity checks. the // StunMessage class itself provides parsing with validity or parsing // without validity, but it does not provide a way to do both together, // so we attempt to do that here. // TODO: consider moving this code into StunMessage static StunMessage parse_stun_message(const QByteArray &packet, int *validationFlags, const QByteArray &key) { // ideally we shouldn't fully parse the packet more than once. the // integrity checks performed by fromBinary do not require fully // parsing the packet, so we should be able to avoid most redundant // processing. fromBinary checks the fingerprint first, and we // can use that knowledge to avoid duplicating integrity checks. int flags = 0; StunMessage::ConvertResult result; StunMessage msg = StunMessage::fromBinary(packet, &result, StunMessage::MessageIntegrity | StunMessage::Fingerprint, key); if (result == StunMessage::ErrorFingerprint) { // if fingerprint fails, then it is the only thing that was // performed and we can skip it now. msg = StunMessage::fromBinary(packet, &result, StunMessage::MessageIntegrity, key); if (result == StunMessage::ErrorMessageIntegrity) { // if message-integrity fails, then it is the only // thing that was performed and we can skip it now msg = StunMessage::fromBinary(packet, &result); if (result == StunMessage::ConvertGood) flags = 0; else return msg; // null } else if (result == StunMessage::ConvertGood) flags = StunMessage::MessageIntegrity; else return msg; // null } else if (result == StunMessage::ErrorMessageIntegrity) { // fingerprint succeeded, but message-integrity failed. parse // without validation now (to skip redundant // fingerprint/message-integrity checks), and assume correct // fingerprint msg = StunMessage::fromBinary(packet, &result); if (result == StunMessage::ConvertGood) flags = StunMessage::Fingerprint; else return msg; // null } else if (result == StunMessage::ConvertGood) flags = StunMessage::MessageIntegrity | StunMessage::Fingerprint; else return msg; // null *validationFlags = flags; return msg; } class StunTransactionPoolPrivate : public QObject { Q_OBJECT public: StunTransactionPool * q; StunTransaction::Mode mode; QSet transactions; QHash transToId; QHash idToTrans; bool useLongTermAuth; bool needLongTermAuth; bool triedLongTermAuth; QString user; QCA::SecureArray pass; QString realm; QString nonce; int debugLevel; StunTransactionPoolPrivate(StunTransactionPool *_q) : QObject(_q), q(_q), useLongTermAuth(false), needLongTermAuth(false), triedLongTermAuth(false), debugLevel(StunTransactionPool::DL_None) { } QByteArray generateId() const; void insert(StunTransaction *trans); void remove(StunTransaction *trans); void transmit(StunTransaction *trans); }; //---------------------------------------------------------------------------- // StunTransaction //---------------------------------------------------------------------------- class StunTransactionPrivate : public QObject { Q_OBJECT public: StunTransaction *q; StunTransactionPool::Ptr pool; bool active; bool cancelling = false; StunTransaction::Mode mode; StunMessage origMessage; QByteArray id; QByteArray packet; QHostAddress to_addr; int to_port; int rto, rc, rm, ti; int tries; int last_interval; QTimer *t; QString stuser; QString stpass; bool fpRequired; QByteArray key; QElapsedTimer time; StunTransactionPrivate(StunTransaction *_q) : QObject(_q), q(_q), pool(nullptr), fpRequired(false) { qRegisterMetaType(); active = false; t = new QTimer(this); connect(t, SIGNAL(timeout()), SLOT(t_timeout())); t->setSingleShot(true); // defaults from RFC 5389 rto = 500; rc = 7; rm = 16; ti = 39500; } ~StunTransactionPrivate() { if (pool) pool->d->remove(q); t->disconnect(this); t->setParent(nullptr); t->deleteLater(); } void start(StunTransactionPool::Ptr _pool, const QHostAddress &toAddress, int toPort) { pool = _pool; mode = pool->d->mode; to_addr = toAddress; to_port = toPort; tryRequest(); } void setMessage(const StunMessage &request) { origMessage = request; } void retry() { Q_ASSERT(!active); pool->d->remove(q); tryRequest(); } void tryRequest() { emit q->createMessage(pool->d->generateId()); if (origMessage.isNull()) { // since a transaction is not cancelable nor reusable, // there's no DOR-SR issue here QMetaObject::invokeMethod(q, "error", Qt::QueuedConnection, Q_ARG(XMPP::StunTransaction::Error, StunTransaction::ErrorGeneric)); return; } StunMessage out = origMessage; out.setClass(StunMessage::Request); id = QByteArray((const char *)out.id(), 12); if (!stuser.isEmpty()) { QList list = out.attributes(); StunMessage::Attribute attr; attr.type = StunTypes::USERNAME; attr.value = StunTypes::createUsername(QString::fromUtf8(StunUtil::saslPrep(stuser.toUtf8()).toByteArray())); list += attr; out.setAttributes(list); key = StunUtil::saslPrep(stpass.toUtf8()).toByteArray(); } else if (!pool->d->nonce.isEmpty()) { QList list = out.attributes(); { StunMessage::Attribute attr; attr.type = StunTypes::USERNAME; attr.value = StunTypes::createUsername( QString::fromUtf8(StunUtil::saslPrep(pool->d->user.toUtf8()).toByteArray())); list += attr; } { StunMessage::Attribute attr; attr.type = StunTypes::REALM; attr.value = StunTypes::createRealm(pool->d->realm); list += attr; } { StunMessage::Attribute attr; attr.type = StunTypes::NONCE; attr.value = StunTypes::createNonce(pool->d->nonce); list += attr; } out.setAttributes(list); QCA::SecureArray buf; buf += StunUtil::saslPrep(pool->d->user.toUtf8()); buf += QByteArray(1, ':'); buf += StunUtil::saslPrep(pool->d->realm.toUtf8()); buf += QByteArray(1, ':'); buf += StunUtil::saslPrep(pool->d->pass); key = QCA::Hash("md5").process(buf).toByteArray(); } if (!key.isEmpty()) packet = out.toBinary(StunMessage::MessageIntegrity | StunMessage::Fingerprint, key); else packet = out.toBinary(StunMessage::Fingerprint); if (packet.isEmpty()) { // since a transaction is not cancelable nor reusable, // there's no DOR-SR issue here QMetaObject::invokeMethod(q, "error", Qt::QueuedConnection, Q_ARG(XMPP::StunTransaction::Error, StunTransaction::ErrorGeneric)); return; } active = true; tries = 1; // we transmit immediately here, so count it if (mode == StunTransaction::Udp) { last_interval = rm * rto; t->start(rto); rto *= 2; } else if (mode == StunTransaction::Tcp) { t->start(ti); } else Q_ASSERT(0); time.start(); pool->d->insert(q); transmit(); } private slots: void t_timeout() { if (cancelling) { q->deleteLater(); return; } if (mode == StunTransaction::Tcp || tries == rc) { pool->d->remove(q); emit q->error(StunTransaction::ErrorTimeout); return; } ++tries; if (tries == rc) { t->start(last_interval); } else { t->start(rto); rto *= 2; } QString dbg; if (!to_addr.isNull()) dbg += QString("to=(") + to_addr.toString() + ';' + QString::number(to_port) + ')'; emit pool->debugLine(QString("stun transaction %1 timeout. retransmitting..").arg(dbg)); transmit(); } private: void transmit() { if (pool->d->debugLevel >= StunTransactionPool::DL_Packet) { QString str = QString("STUN SEND: elapsed=") + QString::number(time.elapsed()); if (!to_addr.isNull()) str += QString(" to=(") + to_addr.toString() + ';' + QString::number(to_port) + ')'; emit pool->debugLine(str); StunMessage msg = StunMessage::fromBinary(packet); emit pool->debugLine(StunTypes::print_packet_str(msg)); } pool->d->transmit(q); } bool checkActiveAndFrom(const QHostAddress &from_addr, int from_port) { if (!active) return false; if (!to_addr.isNull() && (to_addr != from_addr || to_port != from_port)) return false; return true; } void processIncoming(const StunMessage &msg, bool authed) { active = false; t->stop(); if (cancelling) { q->deleteLater(); return; } if (pool->d->debugLevel >= StunTransactionPool::DL_Packet) emit pool->debugLine(QString("matched incoming response to existing request. elapsed=") + QString::number(time.elapsed())); // will be set to true when receiving an Unauthorized error bool unauthError = false; if (msg.mclass() == StunMessage::ErrorResponse && pool->d->useLongTermAuth) { // we'll handle certain error codes at this layer int code; QString reason; if (StunTypes::parseErrorCode(msg.attribute(StunTypes::ERROR_CODE), &code, &reason)) { if (code == StunTypes::Unauthorized) unauthError = true; if (unauthError && !pool->d->triedLongTermAuth) { QString realm; QString nonce; if (StunTypes::parseRealm(msg.attribute(StunTypes::REALM), &realm) && StunTypes::parseRealm(msg.attribute(StunTypes::NONCE), &nonce)) { // always set these to the latest received values, // which will be used for all transactions // once creds are provided. if (pool->d->realm.isEmpty()) pool->d->realm = realm; pool->d->nonce = nonce; if (!pool->d->needLongTermAuth) { if (!pool->d->user.isEmpty()) { // creds already set? use them pool->d->triedLongTermAuth = true; retry(); } else { // else ask the user pool->d->needLongTermAuth = true; emit pool->needAuthParams(); } } return; } } else if (code == StunTypes::StaleNonce && pool->d->triedLongTermAuth) { QString nonce; if (StunTypes::parseNonce(msg.attribute(StunTypes::NONCE), &nonce) && nonce != pool->d->nonce) { pool->d->nonce = nonce; retry(); return; } } } } // require message integrity when auth is used if (!unauthError && (!stuser.isEmpty() || pool->d->triedLongTermAuth) && !authed) return; pool->d->remove(q); emit q->finished(msg); } public: bool writeIncomingMessage(const StunMessage &msg, const QHostAddress &from_addr, int from_port) { if (!checkActiveAndFrom(from_addr, from_port)) return false; // if a StunMessage is passed directly to us then we assume // the user has authenticated the message as necessary processIncoming(msg, true); return true; } bool writeIncomingMessage(const QByteArray &packet, bool *notStun, const QHostAddress &from_addr, int from_port) { if (!checkActiveAndFrom(from_addr, from_port)) { // could be STUN, don't really know for sure *notStun = false; return false; } int validationFlags = 0; StunMessage msg = parse_stun_message(packet, &validationFlags, key); if (msg.isNull()) { // packet doesn't parse at all, surely not STUN *notStun = true; return false; } if (fpRequired && !(validationFlags & StunMessage::Fingerprint)) { // fingerprint failed when required. consider the // packet to be surely not STUN *notStun = true; return false; } processIncoming(msg, (validationFlags & StunMessage::MessageIntegrity) ? true : false); return true; } public slots: void continueAfterParams() { if (cancelling) return; retry(); } }; StunTransaction::StunTransaction(QObject *parent) : QObject(parent) { d = new StunTransactionPrivate(this); } StunTransaction::~StunTransaction() { delete d; } void StunTransaction::start(StunTransactionPool *pool, const QHostAddress &toAddress, int toPort) { Q_ASSERT(!d->active); d->start(pool->sharedFromThis(), toAddress, toPort); } void StunTransaction::cancel() { d->cancelling = true; } void StunTransaction::setMessage(const StunMessage &request) { d->setMessage(request); } void StunTransaction::setRTO(int i) { Q_ASSERT(!d->active); d->rto = i; } void StunTransaction::setRc(int i) { Q_ASSERT(!d->active); d->rc = i; } void StunTransaction::setRm(int i) { Q_ASSERT(!d->active); d->rm = i; } void StunTransaction::setTi(int i) { Q_ASSERT(!d->active); d->ti = i; } void StunTransaction::setShortTermUsername(const QString &username) { d->stuser = username; } void StunTransaction::setShortTermPassword(const QString &password) { d->stpass = password; } void StunTransaction::setFingerprintRequired(bool enabled) { d->fpRequired = enabled; } //---------------------------------------------------------------------------- // StunTransactionPool //---------------------------------------------------------------------------- QByteArray StunTransactionPoolPrivate::generateId() const { QByteArray id; do { id = QCA::Random::randomArray(12).toByteArray(); } while (idToTrans.contains(id)); return id; } void StunTransactionPoolPrivate::insert(StunTransaction *trans) { Q_ASSERT(!trans->d->id.isEmpty()); transactions.insert(trans); QByteArray id = trans->d->id; transToId.insert(trans, id); idToTrans.insert(id, trans); } void StunTransactionPoolPrivate::remove(StunTransaction *trans) { if (transactions.contains(trans)) { transactions.remove(trans); QByteArray id = transToId.value(trans); transToId.remove(trans); idToTrans.remove(id); } } void StunTransactionPoolPrivate::transmit(StunTransaction *trans) { emit q->outgoingMessage(trans->d->packet, trans->d->to_addr, trans->d->to_port); } StunTransactionPool::StunTransactionPool(StunTransaction::Mode mode) { d = new StunTransactionPoolPrivate(this); d->mode = mode; } StunTransactionPool::~StunTransactionPool() { qDeleteAll( findChildren()); // early remove of binding since they require alive pool (should fix one crash) delete d; } StunTransaction::Mode StunTransactionPool::mode() const { return d->mode; } bool StunTransactionPool::writeIncomingMessage(const StunMessage &msg, const QHostAddress &addr, int port) { if (d->debugLevel >= DL_Packet) { QString str = "STUN RECV"; if (!addr.isNull()) str += QString(" from=(") + addr.toString() + ';' + QString::number(port) + ')'; emit debugLine(str); emit debugLine(StunTypes::print_packet_str(msg)); } QByteArray id = QByteArray::fromRawData((const char *)msg.id(), 12); StunMessage::Class mclass = msg.mclass(); if (mclass != StunMessage::SuccessResponse && mclass != StunMessage::ErrorResponse) return false; StunTransaction *trans = d->idToTrans.value(id); if (!trans) return false; return trans->d->writeIncomingMessage(msg, addr, port); } bool StunTransactionPool::writeIncomingMessage(const QByteArray &packet, bool *notStun, const QHostAddress &addr, int port) { if (!StunMessage::isProbablyStun(packet)) { // basic stun check failed? surely not STUN if (notStun) *notStun = true; return false; } if (d->debugLevel >= DL_Packet) { StunMessage msg = StunMessage::fromBinary(packet); QString str = "STUN RECV"; if (!addr.isNull()) str += QString(" from=(") + addr.toString() + ';' + QString::number(port) + ')'; emit debugLine(str); emit debugLine(StunTypes::print_packet_str(msg)); } // isProbablyStun ensures the packet is 20 bytes long, so we can safely // safely extract out the transaction id from the raw packet QByteArray id = QByteArray((const char *)packet.data() + 8, 12); StunMessage::Class mclass = StunMessage::extractClass(packet); if (mclass != StunMessage::SuccessResponse && mclass != StunMessage::ErrorResponse) { // could be STUN, don't really know for sure if (notStun) *notStun = false; return false; } StunTransaction *trans = d->idToTrans.value(id); if (!trans) { // could be STUN, don't really know for sure if (notStun) *notStun = false; return false; } bool _notStun = false; bool ret = trans->d->writeIncomingMessage(packet, &_notStun, addr, port); if (!ret && notStun) *notStun = _notStun; return ret; } void StunTransactionPool::setLongTermAuthEnabled(bool enabled) { d->useLongTermAuth = enabled; } QString StunTransactionPool::realm() const { return d->realm; } void StunTransactionPool::setUsername(const QString &username) { d->user = username; } void StunTransactionPool::setPassword(const QCA::SecureArray &password) { d->pass = password; } void StunTransactionPool::setRealm(const QString &realm) { d->realm = realm; } void StunTransactionPool::continueAfterParams() { if (d->debugLevel >= DL_Info) { emit debugLine("continue after params:"); emit debugLine(QString(" U=[%1]").arg(d->user)); emit debugLine(QString(" P=[%1]").arg(d->pass.data())); emit debugLine(QString(" R=[%1]").arg(d->realm)); emit debugLine(QString(" N=[%1]").arg(d->nonce)); } Q_ASSERT(d->useLongTermAuth); Q_ASSERT(d->needLongTermAuth); Q_ASSERT(!d->triedLongTermAuth); d->needLongTermAuth = false; d->triedLongTermAuth = true; for (StunTransaction *trans : d->transactions) { // the only reason an inactive transaction would be in the // list is if it is waiting for an auth retry if (!trans->d->active && !trans->d->cancelling) { // use queued call to prevent all sorts of DOR-SS // nastiness QMetaObject::invokeMethod(trans->d, "continueAfterParams", Qt::QueuedConnection); } } } QByteArray StunTransactionPool::generateId() const { return d->generateId(); } void StunTransactionPool::setDebugLevel(DebugLevel level) { d->debugLevel = level; } } // namespace XMPP #include "stuntransaction.moc" psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/stuntransaction.h000066400000000000000000000164271370065651000260520ustar00rootroot00000000000000/* * Copyright (C) 2009 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef STUNTRANSACTION_H #define STUNTRANSACTION_H #include #include #include #include namespace QCA { class SecureArray; } namespace XMPP { class StunMessage; class StunTransactionPool; class StunTransactionPoolPrivate; class StunTransactionPrivate; // Notes: // // - we allow multiple simultaneous requests. no serializing or waiting, at // least not at the transaction layer. // - requests may require authentication. the protocol flow for STUN is that // you first try a request without providing credentials, and if // authentication is needed then an error is returned. the request must be // tried again with credentials provided for it to succeed. note that the // error response contains a nonce value that must be passed back in the // second request, and so the first request cannot be skipped. // - it is possible to provide credentials in advance, so that the user is not // asked for them dynamically. however, the protocol flow remains the same // either way (i.e. request w/o creds, error, request with creds). // - the user is only asked for credentials once ever. if two requests require // authentication, the user is asked only once and both requests will be // retried once the creds are provided. if an authentication error is // received after providing creds, then the transaction will fail. this // means the user only has one chance to get the creds right, and creds // cannot change during a session. in the event of failure due to wrong or // changed creds, the pool will need to be recreated in order to try new // creds. // - if short term or long term auth is used, then the request is authenticated // and the response is required to be authenticated. class StunTransaction : public QObject { Q_OBJECT public: enum Mode { Udp, // handle retransmissions Tcp // send once }; enum Error { ErrorGeneric, ErrorTimeout }; StunTransaction(QObject *parent = nullptr); ~StunTransaction(); // toAddress/toPort are optional, to associate this request to a // specific endpoint // note: not DOR-DS safe. this function will cause the pool's // outgoingMessage() signal to be emitted. void start(StunTransactionPool *pool, const QHostAddress &toAddress = QHostAddress(), int toPort = -1); void cancel(); // pass message with class unset. use transaction id from the // createMessage signal. void setMessage(const StunMessage &request); // transmission/timeout parameters, from RFC 5389. by default, // they are set to the recommended values from the RFC. void setRTO(int i); void setRc(int i); void setRm(int i); void setTi(int i); void setShortTermUsername(const QString &username); void setShortTermPassword(const QString &password); // fingerprint is always provided in outbound requests, but ignored // on responses. if this flag is set, then responses will be // required to provide a fingerprint. void setFingerprintRequired(bool enabled); signals: // you must use a direct connection with this signal and call // setMessage() in the slot. this signal may occur many times // before the StunTransaction completes, and you must recreate the // message every time using the new transactionId. void createMessage(const QByteArray &transactionId); void finished(const XMPP::StunMessage &response); void error(XMPP::StunTransaction::Error error); private: Q_DISABLE_COPY(StunTransaction) friend class StunTransactionPool; friend class StunTransactionPoolPrivate; friend class StunTransactionPrivate; StunTransactionPrivate *d; }; // keep track of many open transactions. note that retransmit() may be // emitted as a direct result of calling certain member functions of this // class as well as any other class that might use it (such as StunBinding). // so, be careful with what you do in your retransmit slot. class StunTransactionPool : public QObject, public QEnableSharedFromThis { Q_OBJECT public: using Ptr = QSharedPointer; enum DebugLevel { DL_None, DL_Info, DL_Packet }; StunTransactionPool(StunTransaction::Mode mode); ~StunTransactionPool(); StunTransaction::Mode mode() const; // note: the writeIncomingMessage functions are not DOR-DS safe. they // may cause a transaction to emit finished() or error() signals. // returns true if the message is owned by the pool, else false. bool writeIncomingMessage(const StunMessage &msg, const QHostAddress &addr = QHostAddress(), int port = -1); // returns true if the packet is surely a STUN message and owned by the // pool, else false. a packet must be owned by the pool to be // considered surely a STUN message. if false, the packet may or may // not be a STUN message. *notStun will be set to true if the packet // is surely not STUN, or set to false if it is unclear whether the // packet is STUN or not. bool writeIncomingMessage(const QByteArray &packet, bool *notStun = nullptr, const QHostAddress &addr = QHostAddress(), int port = -1); void setLongTermAuthEnabled(bool enabled); QString realm() const; void setUsername(const QString &username); void setPassword(const QCA::SecureArray &password); void setRealm(const QString &realm); void continueAfterParams(); // for use with stun indications QByteArray generateId() const; void setDebugLevel(DebugLevel level); // default DL_None signals: // note: not DOR-SS safe. writeIncomingMessage() must not be called // during this signal. // // why do we need this restriction? long explanation: since // outgoingMessage() can be emitted as a result of calling a // transaction's start(), and calling writeIncomingMessage() could // result in a transaction completing, then calling // writeIncomingMessage() during outgoingMessage() could cause // a transaction's finished() or error() signals to emit during // start(), which would violate DOR-DS. void outgoingMessage(const QByteArray &packet, const QHostAddress &addr, int port); void needAuthParams(); // not DOR-SS/DS safe void debugLine(const QString &line); private: Q_DISABLE_COPY(StunTransactionPool) friend class StunTransaction; friend class StunTransactionPrivate; friend class StunTransactionPoolPrivate; StunTransactionPoolPrivate *d; }; } // namespace XMPP #endif // STUNTRANSACTION_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/stuntypes.cpp000066400000000000000000000501511370065651000252140ustar00rootroot00000000000000/* * Copyright (C) 2009 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "stuntypes.h" #include "stunutil.h" #include #include #define STRING_MAX_CHARS 127 #define STRING_MAX_BYTES 763 namespace XMPP { using namespace StunUtil; namespace StunTypes { static void xorIPv4(QByteArray *in, const quint8 *magic) { quint8 *p = (quint8 *)in->data(); p[2] ^= magic[0]; p[3] ^= magic[1]; for (int n = 0; n < 4; ++n) p[n + 4] ^= magic[n]; } static void xorIPv6(QByteArray *in, const quint8 *magic, const quint8 *id) { quint8 *p = (quint8 *)in->data(); p[2] ^= magic[0]; p[3] ^= magic[1]; for (int n = 0; n < 4; ++n) p[n + 4] ^= magic[n]; for (int n = 0; n < 12; ++n) p[n + 8] ^= id[n]; } static bool validateString(const QByteArray &in, QString *out) { if (in.size() <= STRING_MAX_BYTES) { QString s = QString::fromUtf8(in); if (s.length() <= STRING_MAX_CHARS) { *out = s; return true; } } return false; } QByteArray createMappedAddress(const QHostAddress &addr, quint16 port) { QByteArray out; if (addr.protocol() == QAbstractSocket::IPv6Protocol) { out = QByteArray(20, 0); out[1] = 0x02; // IPv6 Q_IPV6ADDR addr6 = addr.toIPv6Address(); memcpy(out.data() + 4, addr6.c, 16); } else if (addr.protocol() == QAbstractSocket::IPv4Protocol) { out = QByteArray(8, 0); out[1] = 0x01; // IPv4 write32((quint8 *)out.data() + 4, addr.toIPv4Address()); } else Q_ASSERT(0); write16((quint8 *)out.data() + 2, port); return out; } QByteArray createUsername(const QString &username) { return username.left(STRING_MAX_CHARS).toUtf8(); } QByteArray createErrorCode(int code, const QString &reason) { QByteArray out(4, 0); int ih = code / 100; int il = code % 100; ih &= 0x07; // keep only lower 3 bits unsigned char ch = (unsigned char)ih; unsigned char cl = (unsigned char)il; out[2] = ch; out[3] = cl; out += reason.left(STRING_MAX_CHARS).toUtf8(); return out; } QByteArray createUnknownAttributes(const QList &typeList) { if (typeList.isEmpty()) return QByteArray(); QByteArray out(typeList.count() * 2, 0); for (int n = 0; n < typeList.count(); ++n) write16((quint8 *)out.data() + (n * 2), typeList[n]); return out; } QByteArray createRealm(const QString &realm) { return realm.left(STRING_MAX_CHARS).toUtf8(); } QByteArray createNonce(const QString &nonce) { return nonce.left(STRING_MAX_CHARS).toUtf8(); } QByteArray createXorMappedAddress(const QHostAddress &addr, quint16 port, const quint8 *magic, const quint8 *id) { QByteArray out = createMappedAddress(addr, port); if (addr.protocol() == QAbstractSocket::IPv6Protocol) xorIPv6(&out, magic, id); else // IPv4 xorIPv4(&out, magic); return out; } QByteArray createChannelNumber(quint16 i) { QByteArray val(4, 0); write16((quint8 *)val.data(), i); // bytes 2-3 are zeroed out return val; } QByteArray createLifetime(quint32 i) { QByteArray val(4, 0); write32((quint8 *)val.data(), i); return val; } QByteArray createXorPeerAddress(const QHostAddress &addr, quint16 port, const quint8 *magic, const quint8 *id) { return createXorMappedAddress(addr, port, magic, id); } QByteArray createXorRelayedAddress(const QHostAddress &addr, quint16 port, const quint8 *magic, const quint8 *id) { return createXorMappedAddress(addr, port, magic, id); } QByteArray createEvenPort(bool reserve) { QByteArray val(1, 0); unsigned char c = 0; if (reserve) c |= 0x80; // set high bit val[0] = c; return val; } QByteArray createRequestedTransport(quint8 proto) { QByteArray val(4, 0); val[0] = proto; // bytes 1-3 are zeroed out return val; } QByteArray createReservationToken(const QByteArray &token) { Q_ASSERT(token.size() == 8); return token; } QByteArray createPriority(quint32 i) { QByteArray val(4, 0); write32((quint8 *)val.data(), i); return val; } QByteArray createSoftware(const QString &str) { return str.left(STRING_MAX_CHARS).toUtf8(); } QByteArray createAlternateServer(const QHostAddress &addr, quint16 port) { return createMappedAddress(addr, port); } QByteArray createIceControlled(quint64 i) { QByteArray val(8, 0); write64((quint8 *)val.data(), i); return val; } QByteArray createIceControlling(quint64 i) { QByteArray val(8, 0); write64((quint8 *)val.data(), i); return val; } bool parseMappedAddress(const QByteArray &val, QHostAddress *addr, quint16 *port) { if (val[1] == 0x02 && val.size() == 20) // IPv6 { *port = read16((const quint8 *)val.data() + 2); QByteArray buf = val.mid(4); *addr = QHostAddress((quint8 *)buf.data()); return true; } else if (val[1] == 0x01 && val.size() == 8) // IPv4 { *port = read16((const quint8 *)val.data() + 2); *addr = QHostAddress(read32((const quint8 *)val.data() + 4)); return true; } else return false; } bool parseUsername(const QByteArray &val, QString *username) { return validateString(val, username); } bool parseErrorCode(const QByteArray &val, int *code, QString *reason) { if (val.size() < 4) return false; unsigned char ch = (unsigned char)val[2]; unsigned char cl = (unsigned char)val[3]; int ih = ch & 0x07; // lower 3 bits int x = ih * 100 + (int)cl; QString str; if (validateString(val.mid(4), &str)) { *code = x; *reason = str; return true; } return false; } bool parseUnknownAttributes(const QByteArray &val, QList *typeList) { if (val.size() % 2 != 0) return false; typeList->clear(); int count = val.size() / 2; for (int n = 0; n < count; ++n) typeList->append(read16((const quint8 *)val.data() + (n * 2))); return true; } bool parseRealm(const QByteArray &val, QString *realm) { return validateString(val, realm); } bool parseNonce(const QByteArray &val, QString *nonce) { return validateString(val, nonce); } bool parseXorMappedAddress(const QByteArray &val, const quint8 *magic, const quint8 *id, QHostAddress *addr, quint16 *port) { if (val.size() < 4) return false; QByteArray buf; if (val[1] == 0x02 && val.size() == 20) // IPv6 { buf = val; xorIPv6(&buf, magic, id); } else if (val[1] == 0x01 && val.size() == 8) // IPv4 { buf = val; xorIPv4(&buf, magic); } else return false; return parseMappedAddress(buf, addr, port); } bool parseChannelNumber(const QByteArray &val, quint16 *i) { if (val.size() != 4) return false; const quint8 *p = (const quint8 *)val.data(); *i = read16(p); return true; } bool parseLifetime(const QByteArray &val, quint32 *i) { if (val.size() != 4) return false; const quint8 *p = (const quint8 *)val.data(); *i = read32(p); return true; } bool parseXorPeerAddress(const QByteArray &val, const quint8 *magic, const quint8 *id, QHostAddress *addr, quint16 *port) { return parseXorMappedAddress(val, magic, id, addr, port); } bool parseXorRelayedAddress(const QByteArray &val, const quint8 *magic, const quint8 *id, QHostAddress *addr, quint16 *port) { return parseXorMappedAddress(val, magic, id, addr, port); } bool parseEvenPort(const QByteArray &val, bool *reserve) { if (val.size() != 1) return false; unsigned char c = val[0]; if (c & 0x80) *reserve = true; else *reserve = false; return true; } bool parseRequestedTransport(const QByteArray &val, quint8 *proto) { if (val.size() != 4) return false; *proto = val[0]; return true; } bool parseReservationToken(const QByteArray &val, QByteArray *token) { if (val.size() != 8) return false; *token = val; return true; } bool parsePriority(const QByteArray &val, quint32 *i) { if (val.size() != 4) return false; const quint8 *p = (const quint8 *)val.data(); *i = read32(p); return true; } bool parseSoftware(const QByteArray &val, QString *str) { *str = QString::fromUtf8(val); return true; } bool parseAlternateServer(const QByteArray &val, QHostAddress *addr, quint16 *port) { return parseMappedAddress(val, addr, port); } bool parseIceControlled(const QByteArray &val, quint64 *i) { if (val.size() != 8) return false; const quint8 *p = (const quint8 *)val.data(); *i = read64(p); return true; } bool parseIceControlling(const QByteArray &val, quint64 *i) { if (val.size() != 8) return false; const quint8 *p = (const quint8 *)val.data(); *i = read64(p); return true; } #define METHOD_ENTRY(x) \ { \ x, #x \ } struct MethodEntry { Method method; const char *str; } method_table[] = { METHOD_ENTRY(Binding), METHOD_ENTRY(Allocate), METHOD_ENTRY(Refresh), METHOD_ENTRY(Send), METHOD_ENTRY(Data), METHOD_ENTRY(CreatePermission), METHOD_ENTRY(ChannelBind), { (Method)-1, nullptr } }; QString methodToString(int method) { for (int n = 0; method_table[n].str; ++n) { if (method_table[n].method == (Method)method) return QString::fromLatin1(method_table[n].str); } return QString(); } #define ATTRIB_ENTRY(x) \ { \ x, #x \ } struct AttribEntry { Attribute type; const char *str; } attrib_table[] = { ATTRIB_ENTRY(MAPPED_ADDRESS), ATTRIB_ENTRY(USERNAME), ATTRIB_ENTRY(MESSAGE_INTEGRITY), ATTRIB_ENTRY(ERROR_CODE), ATTRIB_ENTRY(UNKNOWN_ATTRIBUTES), ATTRIB_ENTRY(REALM), ATTRIB_ENTRY(NONCE), ATTRIB_ENTRY(XOR_MAPPED_ADDRESS), ATTRIB_ENTRY(CHANNEL_NUMBER), ATTRIB_ENTRY(LIFETIME), ATTRIB_ENTRY(XOR_PEER_ADDRESS), ATTRIB_ENTRY(DATA), ATTRIB_ENTRY(XOR_RELAYED_ADDRESS), ATTRIB_ENTRY(EVEN_PORT), ATTRIB_ENTRY(REQUESTED_TRANSPORT), ATTRIB_ENTRY(DONT_FRAGMENT), ATTRIB_ENTRY(RESERVATION_TOKEN), ATTRIB_ENTRY(PRIORITY), ATTRIB_ENTRY(USE_CANDIDATE), ATTRIB_ENTRY(SOFTWARE), ATTRIB_ENTRY(ALTERNATE_SERVER), ATTRIB_ENTRY(FINGERPRINT), ATTRIB_ENTRY(ICE_CONTROLLED), ATTRIB_ENTRY(ICE_CONTROLLING), { (Attribute)-1, nullptr } }; QString attributeTypeToString(int type) { for (int n = 0; attrib_table[n].str; ++n) { if (attrib_table[n].type == (Attribute)type) { QString name = QString::fromLatin1(attrib_table[n].str); name.replace('_', '-'); return name; } } return QString(); } static QString quoted(const QString &in) { return QString("\"") + in + '\"'; } QString attributeValueToString(int type, const QByteArray &val, const quint8 *magic, const quint8 *id) { switch ((Attribute)type) { case MAPPED_ADDRESS: { QHostAddress addr; quint16 port; if (parseMappedAddress(val, &addr, &port)) return addr.toString() + ';' + QString::number(port); break; } case USERNAME: { QString str; if (parseUsername(val, &str)) return quoted(str); break; } case MESSAGE_INTEGRITY: { return QCA::arrayToHex(val); } case ERROR_CODE: { int code; QString reason; if (parseErrorCode(val, &code, &reason)) { QString out = QString::number(code); if (!reason.isEmpty()) out += QString(", ") + quoted(reason); return out; } break; } case UNKNOWN_ATTRIBUTES: { QList typeList; if (parseUnknownAttributes(val, &typeList)) { if (!typeList.isEmpty()) { QStringList strList; for (quint16 i : typeList) strList += QString::asprintf("0x%04x", i); return strList.join(", "); } else return "(None)"; } break; } case REALM: { QString str; if (parseRealm(val, &str)) return quoted(str); break; } case NONCE: { QString str; if (parseNonce(val, &str)) return quoted(str); break; } case XOR_MAPPED_ADDRESS: { QHostAddress addr; quint16 port; if (parseXorMappedAddress(val, magic, id, &addr, &port)) return addr.toString() + ';' + QString::number(port); break; } case CHANNEL_NUMBER: { quint16 i; if (parseChannelNumber(val, &i)) return QString::asprintf("0x%04x", (int)i); break; } case LIFETIME: { quint32 i; if (parseLifetime(val, &i)) return QString::number(i); break; } case XOR_PEER_ADDRESS: { return attributeValueToString(XOR_MAPPED_ADDRESS, val, magic, id); } case DATA: { return QString("len=%1, ").arg(val.size()) + QCA::arrayToHex(val); } case XOR_RELAYED_ADDRESS: { return attributeValueToString(XOR_MAPPED_ADDRESS, val, magic, id); } case EVEN_PORT: { bool reserve; if (parseEvenPort(val, &reserve)) return QString("reserve=") + (reserve ? "true" : "false"); break; } case REQUESTED_TRANSPORT: { quint8 proto; if (parseRequestedTransport(val, &proto)) { QString str = QString::number((int)proto); if (proto == 17) // UDP str += " (UDP)"; else str += " (Unknown)"; return str; } break; } case DONT_FRAGMENT: { return QString(""); } case RESERVATION_TOKEN: { QByteArray token; if (parseReservationToken(val, &token)) return QCA::arrayToHex(token); break; } case PRIORITY: { quint32 i; if (parsePriority(val, &i)) return QString::number(i); break; } case USE_CANDIDATE: { return QString(""); } case SOFTWARE: { QString out; if (parseSoftware(val, &out)) return quoted(out); break; } case ALTERNATE_SERVER: { return attributeValueToString(MAPPED_ADDRESS, val, magic, id); } case FINGERPRINT: { return QCA::arrayToHex(val); } case ICE_CONTROLLED: { quint64 i; if (parseIceControlled(val, &i)) return QString::number(i); break; } case ICE_CONTROLLING: { quint64 i; if (parseIceControlling(val, &i)) return QString::number(i); break; } } return QString(); } QString print_packet_str(const StunMessage &message) { QString out; QString mclass; if (message.mclass() == StunMessage::Request) mclass = "Request"; else if (message.mclass() == StunMessage::SuccessResponse) mclass = "Response (Success)"; else if (message.mclass() == StunMessage::ErrorResponse) mclass = "Response (Error)"; else if (message.mclass() == StunMessage::Indication) mclass = "Indication"; else Q_ASSERT(0); out += QString("Class: %1\n").arg(mclass); out += QString("Method: %1\n").arg(methodToString(message.method())); out += QString("Transaction id: %1\n").arg(QCA::arrayToHex(QByteArray((const char *)message.id(), 12))); out += "Attributes:"; QList attribs = message.attributes(); if (!attribs.isEmpty()) { for (const StunMessage::Attribute &a : attribs) { out += '\n'; QString name = attributeTypeToString(a.type); if (!name.isNull()) { QString val = attributeValueToString(a.type, a.value, message.magic(), message.id()); if (val.isNull()) val = QString("Unable to parse %1 bytes").arg(a.value.size()); out += QString(" %1").arg(name); if (!val.isEmpty()) out += QString(" = %1").arg(val); } else out += QString::asprintf(" Unknown attribute (0x%04x) of %d bytes", a.type, a.value.size()); } } else out += "\n (None)"; return out; } void print_packet(const StunMessage &message) { printf("%s\n", qPrintable(print_packet_str(message))); } } // namespace StunTypes } // namespace XMPP psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/stuntypes.h000066400000000000000000000132501370065651000246600ustar00rootroot00000000000000/* * Copyright (C) 2009 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef STUNTYPES_H #define STUNTYPES_H #include "stunmessage.h" #include #include #include #include namespace XMPP { namespace StunTypes { enum Method { Binding = 0x001, Allocate = 0x003, Refresh = 0x004, Send = 0x006, Data = 0x007, CreatePermission = 0x008, ChannelBind = 0x009 }; enum Attribute { MAPPED_ADDRESS = 0x0001, USERNAME = 0x0006, MESSAGE_INTEGRITY = 0x0008, ERROR_CODE = 0x0009, UNKNOWN_ATTRIBUTES = 0x000a, REALM = 0x0014, NONCE = 0x0015, XOR_MAPPED_ADDRESS = 0x0020, CHANNEL_NUMBER = 0x000c, LIFETIME = 0x000d, XOR_PEER_ADDRESS = 0x0012, DATA = 0x0013, XOR_RELAYED_ADDRESS = 0x0016, EVEN_PORT = 0x0018, REQUESTED_TRANSPORT = 0x0019, DONT_FRAGMENT = 0x001a, RESERVATION_TOKEN = 0x0022, PRIORITY = 0x0024, USE_CANDIDATE = 0x0025, SOFTWARE = 0x8022, ALTERNATE_SERVER = 0x8023, FINGERPRINT = 0x8028, ICE_CONTROLLED = 0x8029, ICE_CONTROLLING = 0x802a }; enum Error { TryAlternate = 300, BadRequest = 400, Unauthorized = 401, UnknownAttribute = 420, StaleNonce = 438, ServerError = 500, Forbidden = 403, AllocationMismatch = 437, WrongCredentials = 441, UnsupportedTransportProtocol = 442, AllocationQuotaReached = 486, InsufficientCapacity = 508, RoleConflict = 487 }; QByteArray createMappedAddress(const QHostAddress &addr, quint16 port); QByteArray createUsername(const QString &username); QByteArray createErrorCode(int code, const QString &reason); QByteArray createUnknownAttributes(const QList &typeList); QByteArray createRealm(const QString &realm); QByteArray createNonce(const QString &nonce); QByteArray createXorMappedAddress(const QHostAddress &addr, quint16 port, const quint8 *magic, const quint8 *id); QByteArray createChannelNumber(quint16 i); QByteArray createLifetime(quint32 i); QByteArray createXorPeerAddress(const QHostAddress &addr, quint16 port, const quint8 *magic, const quint8 *id); QByteArray createXorRelayedAddress(const QHostAddress &addr, quint16 port, const quint8 *magic, const quint8 *id); QByteArray createEvenPort(bool reserve); QByteArray createRequestedTransport(quint8 proto); QByteArray createReservationToken(const QByteArray &token); QByteArray createPriority(quint32 i); QByteArray createSoftware(const QString &str); QByteArray createAlternateServer(const QHostAddress &addr, quint16 port); QByteArray createIceControlled(quint64 i); QByteArray createIceControlling(quint64 i); bool parseMappedAddress(const QByteArray &val, QHostAddress *addr, quint16 *port); bool parseUsername(const QByteArray &val, QString *username); bool parseErrorCode(const QByteArray &val, int *code, QString *reason); bool parseUnknownAttributes(const QByteArray &val, QList *typeList); bool parseRealm(const QByteArray &val, QString *realm); bool parseNonce(const QByteArray &val, QString *nonce); bool parseXorMappedAddress(const QByteArray &val, const quint8 *magic, const quint8 *id, QHostAddress *addr, quint16 *port); bool parseChannelNumber(const QByteArray &val, quint16 *i); bool parseLifetime(const QByteArray &val, quint32 *i); bool parseXorPeerAddress(const QByteArray &val, const quint8 *magic, const quint8 *id, QHostAddress *addr, quint16 *port); bool parseXorRelayedAddress(const QByteArray &val, const quint8 *magic, const quint8 *id, QHostAddress *addr, quint16 *port); bool parseEvenPort(const QByteArray &val, bool *reserve); bool parseRequestedTransport(const QByteArray &val, quint8 *proto); bool parseReservationToken(const QByteArray &val, QByteArray *token); bool parsePriority(const QByteArray &val, quint32 *i); bool parseSoftware(const QByteArray &val, QString *str); bool parseAlternateServer(const QByteArray &val, QHostAddress *addr, quint16 *port); bool parseIceControlled(const QByteArray &val, quint64 *i); bool parseIceControlling(const QByteArray &val, quint64 *i); QString methodToString(int method); QString attributeTypeToString(int type); QString attributeValueToString(int type, const QByteArray &val, const quint8 *magic, const quint8 *id); QString print_packet_str(const StunMessage &message); void print_packet(const StunMessage &message); } // namespace StunTypes } // namespace XMPP #endif // STUNTYPES_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/stunutil.cpp000066400000000000000000000044071370065651000250300ustar00rootroot00000000000000/* * Copyright (C) 2009 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "stunutil.h" namespace XMPP { namespace StunUtil { quint16 read16(const quint8 *in) { quint16 out = in[0]; out <<= 8; out += in[1]; return out; } quint32 read32(const quint8 *in) { quint32 out = in[0]; out <<= 8; out += in[1]; out <<= 8; out += in[2]; out <<= 8; out += in[3]; return out; } quint64 read64(const quint8 *in) { quint64 out = in[0]; out <<= 8; out += in[1]; out <<= 8; out += in[2]; out <<= 8; out += in[3]; out <<= 8; out += in[4]; out <<= 8; out += in[5]; out <<= 8; out += in[6]; out <<= 8; out += in[7]; return out; } void write16(quint8 *out, quint16 i) { out[0] = (i >> 8) & 0xff; out[1] = i & 0xff; } void write32(quint8 *out, quint32 i) { out[0] = (i >> 24) & 0xff; out[1] = (i >> 16) & 0xff; out[2] = (i >> 8) & 0xff; out[3] = i & 0xff; } void write64(quint8 *out, quint64 i) { out[0] = (i >> 56) & 0xff; out[1] = (i >> 48) & 0xff; out[2] = (i >> 40) & 0xff; out[3] = (i >> 32) & 0xff; out[4] = (i >> 24) & 0xff; out[5] = (i >> 16) & 0xff; out[6] = (i >> 8) & 0xff; out[7] = i & 0xff; } QCA::SecureArray saslPrep(const QCA::SecureArray &in) { // TODO return in; } } // namespace StunUtil } // namespace XMPP psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/stunutil.h000066400000000000000000000022521370065651000244710ustar00rootroot00000000000000/* * Copyright (C) 2009 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef STUNUTIL_H #define STUNUTIL_H #include namespace XMPP { namespace StunUtil { quint16 read16(const quint8 *in); quint32 read32(const quint8 *in); quint64 read64(const quint8 *in); void write16(quint8 *out, quint16 i); void write32(quint8 *out, quint32 i); void write64(quint8 *out, quint64 i); QCA::SecureArray saslPrep(const QCA::SecureArray &in); } // namespace StunUtil } // namespace XMPP #endif // STUNUTIL_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/tcpportreserver.cpp000066400000000000000000000156651370065651000264220ustar00rootroot00000000000000/* * tcpportreserver.cpp - a utility to bind local tcp server sockets * Copyright (C) 2019 Sergey Ilinykh * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "tcpportreserver.h" #include "ice176.h" #include #include #include namespace XMPP { TcpPortDiscoverer::TcpPortDiscoverer(TcpPortScope *scope) : QObject(scope), scope(scope) { } bool TcpPortDiscoverer::setExternalHost(const QString &extHost, quint16 extPort, const QHostAddress &localAddr, quint16 localPort) { if (!(typeMask & TcpPortServer::NatAssited)) { return false; // seems like we don't need nat-assited } auto server = scope->bind(localAddr, localPort); if (!server) { return false; } TcpPortServer::Port p; p.portType = TcpPortServer::NatAssited; p.publishHost = extHost; p.publishPort = extPort; server->setPortInfo(p); servers.append(server); emit portAvailable(); return true; } TcpPortServer::PortTypes TcpPortDiscoverer::inProgressPortTypes() const { return {}; // same as for stop() } bool TcpPortDiscoverer::isDepleted() const { return servers.size() == 0; // TODO and no active subdiscoveries } TcpPortServer::PortTypes TcpPortDiscoverer::setTypeMask(TcpPortServer::PortTypes mask) { this->typeMask = mask; // drop ready ports if any auto it = std::remove_if(servers.begin(), servers.end(), [mask](auto &s) { return !(s->portType() & mask); }); servers.erase(it, servers.end()); TcpPortServer::PortTypes pendingTypes; for (auto &s : servers) pendingTypes |= s->portType(); // TODO drop pending subdiscoveries too and update pendingType when implemented return pendingTypes; } void TcpPortDiscoverer::start() { QList listenAddrs; auto const interfaces = QNetworkInterface::allInterfaces(); for (const QNetworkInterface &ni : interfaces) { if (!(ni.flags() & (QNetworkInterface::IsUp | QNetworkInterface::IsRunning))) { continue; } if (ni.flags() & QNetworkInterface::IsLoopBack) { continue; } QList entries = ni.addressEntries(); for (const QNetworkAddressEntry &na : entries) { QHostAddress h = na.ip(); if (h.isLoopback()) { continue; } // don't put the same address in twice. // this also means that if there are // two link-local ipv6 interfaces // with the exact same address, we // only use the first one if (listenAddrs.contains(h)) continue; #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) if (h.protocol() == QAbstractSocket::IPv6Protocol && h.isLinkLocal()) #else if (h.protocol() == QAbstractSocket::IPv6Protocol && XMPP::Ice176::isIPv6LinkLocalAddress(h)) #endif h.setScopeId(ni.name()); listenAddrs += h; } } for (auto &h : listenAddrs) { auto server = scope->bind(h, 0); if (!server) { continue; } TcpPortServer::Port p; p.portType = TcpPortServer::Direct; QHostAddress addr = server->serverAddress(); addr.setScopeId(QString()); p.publishHost = addr.toString(); p.publishPort = server->serverPort(); server->setPortInfo(p); servers.append(server); } if (listenAddrs.size()) { emit portAvailable(); } } void TcpPortDiscoverer::stop() { // nothing really to do here. but if we invent extension interface it can call stop on subdisco } QList TcpPortDiscoverer::takeServers() { auto ret = servers; servers.clear(); for (auto &p : ret) { p->disconnect(this); } return ret; } // -------------------------------------------------------------------------- // TcpPortScope // -------------------------------------------------------------------------- struct TcpPortScope::Private { QHash, QWeakPointer> servers; }; TcpPortScope::TcpPortScope() : d(new Private) { } TcpPortScope::~TcpPortScope() { } TcpPortDiscoverer *TcpPortScope::disco() { auto discoverer = new TcpPortDiscoverer(this); QMetaObject::invokeMethod(parent(), "newDiscoverer", Q_ARG(TcpPortDiscoverer *, discoverer)); QMetaObject::invokeMethod(discoverer, "start", Qt::QueuedConnection); return discoverer; } QList TcpPortScope::allServers() const { QList ret; for (auto &s : d->servers) { auto sl = s.lock(); if (sl) { ret.append(sl); } } return ret; } void TcpPortScope::destroyServer(TcpPortServer *server) { delete server; } TcpPortServer::Ptr TcpPortScope::bind(const QHostAddress &addr, quint16 port) { if (port) { auto srv = d->servers.value(qMakePair(addr, port)).toStrongRef(); if (srv) { return srv; } } auto socket = new QTcpServer(this); if (!socket->listen(addr, port)) { delete socket; return TcpPortServer::Ptr(); } auto server = makeServer(socket); TcpPortServer::Ptr shared(server, [](TcpPortServer *s) { auto scope = qobject_cast(s->parent()); if (scope) { scope->d->servers.remove(qMakePair(s->serverAddress(), s->serverPort())); scope->destroyServer(s); } else { delete s; } }); d->servers.insert(qMakePair(socket->serverAddress(), socket->serverPort()), shared.toWeakRef()); return shared; } // -------------------------------------------------------------------------- // TcpPortScope // -------------------------------------------------------------------------- TcpPortReserver::TcpPortReserver(QObject *parent) : QObject(parent) { } TcpPortReserver::~TcpPortReserver() { } TcpPortScope *TcpPortReserver::scope(const QString &id) { return findChild(id, Qt::FindDirectChildrenOnly); } void TcpPortReserver::registerScope(const QString &id, TcpPortScope *scope) { scope->setObjectName(id); scope->setParent(this); } TcpPortScope *TcpPortReserver::unregisterScope(const QString &id) { auto s = scope(id); if (s) { s->setParent(nullptr); } return s; } } // namespace XMPP psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/tcpportreserver.h000066400000000000000000000111621370065651000260530ustar00rootroot00000000000000/* * tcpportreserver.cpp - a utility to bind local tcp server sockets * Copyright (C) 2019 Sergey Ilinykh * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef TCPPORTRESERVER_H #define TCPPORTRESERVER_H #include #include #include #include namespace XMPP { class TcpPortServer : public QObject { Q_OBJECT public: using Ptr = QSharedPointer; enum PortType { NoType = 0x0, Direct = 0x1, NatAssited = 0x2, Tunneled = 0x4 }; Q_DECLARE_FLAGS(PortTypes, PortType) struct Port { PortType portType = NoType; QString publishHost; quint16 publishPort = 0; QVariant meta; }; inline TcpPortServer(QTcpServer *serverSocket) : serverSocket(serverSocket) { } inline void setPortInfo(const Port &port) { this->port = port; } inline QHostAddress serverAddress() const { return serverSocket->serverAddress(); } inline quint16 serverPort() const { return serverSocket->serverPort(); } inline const QString & publishHost() const { return port.publishHost; } inline quint16 publishPort() const { return port.publishPort; } inline PortType portType() const { return port.portType; } inline const QVariant &meta() const { return port.meta; } protected: QTcpServer *serverSocket = nullptr; Port port; }; class TcpPortScope; /** * @brief The TcpPortDiscoverer class * * Discovers / starts listening on a set of unique tcp ports. */ class TcpPortDiscoverer : public QObject { Q_OBJECT public: TcpPortDiscoverer(TcpPortScope *scope); bool setExternalHost(const QString &extHost, quint16 extPort, const QHostAddress &localIp, quint16 localPort); TcpPortServer::PortTypes inProgressPortTypes() const; bool isDepleted() const; /** * @brief setTypeMask sets expected port types mask and frees unnecessary resources * @param mask * @return remaining port types */ XMPP::TcpPortServer::PortTypes setTypeMask(TcpPortServer::PortTypes mask); /** * @brief takeServers takes all discovered servers * @return */ QList takeServers(); public slots: void start(); // it's autocalled after outside world is notified about this new discoverer void stop(); signals: void portAvailable(); private: TcpPortServer::PortTypes typeMask = TcpPortServer::PortTypes(TcpPortServer::Direct | TcpPortServer::NatAssited | TcpPortServer::Tunneled); TcpPortScope * scope = nullptr; QList servers; }; class TcpPortReserver; /** * @brief The TcpPortScope class * * Handles scopes of ports. For example just S5B dedicated ports. * There only on scope instance per scope id */ class TcpPortScope : public QObject { Q_OBJECT public: TcpPortScope(); ~TcpPortScope(); TcpPortDiscoverer * disco(); QList allServers() const; protected: virtual TcpPortServer *makeServer(QTcpServer *socket) = 0; virtual void destroyServer(TcpPortServer *server); private: friend class TcpPortDiscoverer; TcpPortServer::Ptr bind(const QHostAddress &addr, quint16 port); private: class Private; QScopedPointer d; }; /** * @brief The TcpPortReserver class * This class should have the only instance per application */ class TcpPortReserver : public QObject { Q_OBJECT public: explicit TcpPortReserver(QObject *parent = nullptr); ~TcpPortReserver(); /** * @brief scope returns a registered scope corresponding to scope id * @param id * @return scope * @note Do not reparent the object */ TcpPortScope *scope(const QString &id); void registerScope(const QString &id, TcpPortScope *scope); TcpPortScope *unregisterScope(const QString &id); signals: void newDiscoverer(TcpPortDiscoverer *discoverer); public slots: }; } // namespace XMPP Q_DECLARE_OPERATORS_FOR_FLAGS(XMPP::TcpPortServer::PortTypes) #endif // TCPPORTRESERVER_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/turnclient.cpp000066400000000000000000000712551370065651000253350ustar00rootroot00000000000000/* * Copyright (C) 2010 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "turnclient.h" #include "bsocket.h" #include "bytestream.h" #include "httpconnect.h" #include "objectsession.h" #include "socks.h" #include "stunallocate.h" #include "stunmessage.h" #include "stuntransaction.h" #include "stuntypes.h" #include namespace XMPP { //---------------------------------------------------------------------------- // TurnClient::Proxy //---------------------------------------------------------------------------- TurnClient::Proxy::Proxy() : t(None) { } TurnClient::Proxy::~Proxy() { } int TurnClient::Proxy::type() const { return t; } QString TurnClient::Proxy::host() const { return v_host; } quint16 TurnClient::Proxy::port() const { return v_port; } QString TurnClient::Proxy::user() const { return v_user; } QString TurnClient::Proxy::pass() const { return v_pass; } void TurnClient::Proxy::setHttpConnect(const QString &host, quint16 port) { t = HttpConnect; v_host = host; v_port = port; } void TurnClient::Proxy::setSocks(const QString &host, quint16 port) { t = Socks; v_host = host; v_port = port; } void TurnClient::Proxy::setUserPass(const QString &user, const QString &pass) { v_user = user; v_pass = pass; } //---------------------------------------------------------------------------- // TurnClient //---------------------------------------------------------------------------- class TurnClient::Private : public QObject { Q_OBJECT public: TurnClient * q; Proxy proxy; QString clientSoftware; TurnClient::Mode mode = PlainMode; QHostAddress serverAddr; int serverPort = 0; ObjectSession sess; ByteStream * bs = nullptr; QCA::TLS * tls = nullptr; bool tlsHandshaken = false; QByteArray inStream; bool udp = false; StunTransactionPool::Ptr pool; StunAllocate * allocate = nullptr; bool allocateStarted = false; QString user; QCA::SecureArray pass; QString realm; int retryCount = 0; QString errorString; int debugLevel = 0; class WriteItem { public: enum Type { Data, Other }; Type type; int size; QHostAddress addr; int port; WriteItem(int _size) : type(Other), size(_size), port(-1) { } WriteItem(int _size, const QHostAddress &_addr, int _port) : type(Data), size(_size), addr(_addr), port(_port) { } }; QList writeItems; int writtenBytes; bool stopping; class Packet { public: QHostAddress addr; int port; QByteArray data; // for outbound bool requireChannel; Packet() : port(-1), requireChannel(false) { } }; QList in; QList outPending; int outPendingWrite; QList desiredPerms; QList pendingChannels, desiredChannels; class Written { public: QHostAddress addr; int port; int count; }; Private(TurnClient *_q) : QObject(_q), q(_q), sess(this), bs(nullptr), tls(0), udp(false), pool(nullptr), allocate(nullptr), retryCount(0), debugLevel(TurnClient::DL_None), writtenBytes(0), stopping(false), outPendingWrite(0) { } ~Private() { cleanup(); } void unsetPool() { // in udp mode, we don't own the pool if (!udp && pool) { pool->disconnect(this); pool.reset(); } } void cleanup() { delete allocate; allocate = nullptr; unsetPool(); delete tls; tls = nullptr; delete bs; bs = 0; udp = false; sess.reset(); inStream.clear(); retryCount = 0; writeItems.clear(); writtenBytes = 0; stopping = false; outPending.clear(); outPendingWrite = 0; desiredPerms.clear(); pendingChannels.clear(); desiredChannels.clear(); } void do_connect() { if (udp) { after_connected(); return; } if (proxy.type() == Proxy::HttpConnect) { HttpConnect *s = new HttpConnect(this); bs = s; connect(s, SIGNAL(connected()), SLOT(bs_connected())); connect(s, SIGNAL(error(int)), SLOT(bs_error(int))); if (!proxy.user().isEmpty()) s->setAuth(proxy.user(), proxy.pass()); s->connectToHost(proxy.host(), proxy.port(), serverAddr.toString(), serverPort); } else if (proxy.type() == Proxy::Socks) { SocksClient *s = new SocksClient(this); bs = s; connect(s, SIGNAL(connected()), SLOT(bs_connected())); connect(s, SIGNAL(error(int)), SLOT(bs_error(int))); if (!proxy.user().isEmpty()) s->setAuth(proxy.user(), proxy.pass()); s->connectToHost(proxy.host(), proxy.port(), serverAddr.toString(), serverPort); } else { BSocket *s = new BSocket(this); bs = s; connect(s, SIGNAL(connected()), SLOT(bs_connected())); connect(s, SIGNAL(error(int)), SLOT(bs_error(int))); s->connectToHost(serverAddr.toString(), serverPort); } connect(bs, SIGNAL(connectionClosed()), SLOT(bs_connectionClosed())); connect(bs, SIGNAL(delayedCloseFinished()), SLOT(bs_delayedCloseFinished())); connect(bs, SIGNAL(readyRead()), SLOT(bs_readyRead())); connect(bs, SIGNAL(bytesWritten(qint64)), SLOT(bs_bytesWritten(qint64))); } void do_close() { stopping = true; if (allocate && allocateStarted) { if (debugLevel >= TurnClient::DL_Info) emit q->debugLine("Deallocating..."); allocate->stop(); } else { delete allocate; allocate = nullptr; unsetPool(); if (udp) sess.defer(q, "closed"); else do_transport_close(); } } void do_transport_close() { if (tls && tlsHandshaken) { tls->close(); } else { delete tls; tls = nullptr; do_sock_close(); } } void do_sock_close() { bool waitForSignal = false; if (bs->bytesToWrite() > 0) waitForSignal = true; bs->close(); if (!waitForSignal) { cleanup(); sess.defer(q, "closed"); } } void after_connected() { // when retrying, pool will be non-null because we reuse it if (!udp && !pool) { pool = StunTransactionPool::Ptr::create(StunTransaction::Tcp); pool->setDebugLevel((StunTransactionPool::DebugLevel)debugLevel); connect(pool.data(), SIGNAL(outgoingMessage(QByteArray, QHostAddress, int)), SLOT(pool_outgoingMessage(QByteArray, QHostAddress, int))); connect(pool.data(), SIGNAL(needAuthParams()), SLOT(pool_needAuthParams())); connect(pool.data(), SIGNAL(debugLine(QString)), SLOT(pool_debugLine(QString))); pool->setLongTermAuthEnabled(true); if (!user.isEmpty()) { pool->setUsername(user); pool->setPassword(pass); if (!realm.isEmpty()) pool->setRealm(realm); } } allocate = new StunAllocate(pool.data()); connect(allocate, SIGNAL(started()), SLOT(allocate_started())); connect(allocate, SIGNAL(stopped()), SLOT(allocate_stopped())); connect(allocate, SIGNAL(error(XMPP::StunAllocate::Error)), SLOT(allocate_error(XMPP::StunAllocate::Error))); connect(allocate, SIGNAL(permissionsChanged()), SLOT(allocate_permissionsChanged())); connect(allocate, SIGNAL(channelsChanged()), SLOT(allocate_channelsChanged())); connect(allocate, SIGNAL(debugLine(QString)), SLOT(allocate_debugLine(QString))); allocate->setClientSoftwareNameAndVersion(clientSoftware); allocateStarted = false; if (debugLevel >= TurnClient::DL_Info) emit q->debugLine("Allocating..."); // only use addr association in udp mode if (udp) allocate->start(serverAddr, serverPort); else allocate->start(); } void processStream(const QByteArray &in) { inStream += in; ObjectSessionWatcher watch(&sess); while (1) { QByteArray packet; // try to extract ChannelData or a STUN message from // the stream packet = StunAllocate::readChannelData((const quint8 *)inStream.data(), inStream.size()); if (packet.isNull()) { packet = StunMessage::readStun((const quint8 *)inStream.data(), inStream.size()); if (packet.isNull()) break; } inStream = inStream.mid(packet.size()); // processDatagram may cause the session to be reset // or the object to be deleted processDatagram(packet); if (!watch.isValid()) break; } } void processDatagram(const QByteArray &buf) { bool notStun; if (!pool->writeIncomingMessage(buf, ¬Stun)) { QByteArray data; QHostAddress fromAddr; int fromPort; data = processNonPoolPacket(buf, notStun, &fromAddr, &fromPort); if (!data.isNull()) processDataPacket(data, fromAddr, fromPort); } } QByteArray processNonPoolPacket(const QByteArray &buf, bool notStun, QHostAddress *addr, int *port) { if (notStun) { // not stun? maybe it is a data packet QByteArray data = allocate->decode(buf, addr, port); if (!data.isNull()) { if (debugLevel >= TurnClient::DL_Packet) emit q->debugLine("Received ChannelData-based data packet"); return data; } } else { // packet might be stun not owned by pool. // let's see StunMessage message = StunMessage::fromBinary(buf); if (!message.isNull()) { QByteArray data = allocate->decode(message, addr, port); if (!data.isNull()) { if (debugLevel >= TurnClient::DL_Packet) emit q->debugLine("Received STUN-based data packet"); return data; } else { if (debugLevel >= TurnClient::DL_Packet) emit q->debugLine("Warning: server responded with an unexpected STUN packet, skipping."); } return QByteArray(); } } if (debugLevel >= TurnClient::DL_Packet) emit q->debugLine( "Warning: server responded with what doesn't seem to be a STUN or data packet, skipping."); return QByteArray(); } void processDataPacket(const QByteArray &buf, const QHostAddress &addr, int port) { Packet p; p.addr = addr; p.port = port; p.data = buf; in += p; emit q->readyRead(); } void writeOrQueue(const QByteArray &buf, const QHostAddress &addr, int port) { Q_ASSERT(allocateStarted); StunAllocate::Channel c(addr, port); bool writeImmediately = false; bool requireChannel = pendingChannels.contains(c) || desiredChannels.contains(c); QList actualPerms = allocate->permissions(); if (actualPerms.contains(addr)) { if (requireChannel) { QList actualChannels = allocate->channels(); if (actualChannels.contains(c)) writeImmediately = true; } else writeImmediately = true; } if (writeImmediately) { write(buf, addr, port); } else { Packet p; p.addr = addr; p.port = port; p.data = buf; p.requireChannel = requireChannel; outPending += p; ensurePermission(addr); } } void tryWriteQueued() { QList actualPerms = allocate->permissions(); QList actualChannels = allocate->channels(); for (int n = 0; n < outPending.count(); ++n) { const Packet &p = outPending[n]; if (actualPerms.contains(p.addr)) { StunAllocate::Channel c(p.addr, p.port); if (!p.requireChannel || actualChannels.contains(c)) { Packet po = outPending[n]; outPending.removeAt(n); --n; // adjust position write(po.data, po.addr, po.port); } } } } void tryChannelQueued() { if (!pendingChannels.isEmpty()) { QList actualPerms = allocate->permissions(); QList list; for (int n = 0; n < pendingChannels.count(); ++n) { if (actualPerms.contains(pendingChannels[n].address)) { list += pendingChannels[n]; pendingChannels.removeAt(n); --n; // adjust position } } if (!list.isEmpty()) ensureChannels(list); } } void write(const QByteArray &buf, const QHostAddress &addr, int port) { QByteArray packet = allocate->encode(buf, addr, port); if (debugLevel >= TurnClient::DL_Packet) { StunMessage msg = StunMessage::fromBinary(packet); if (!msg.isNull()) { emit q->debugLine("STUN SEND"); emit q->debugLine(StunTypes::print_packet_str(msg)); } else emit q->debugLine("Sending ChannelData-based data packet"); } writeItems += WriteItem(packet.size(), addr, port); ++outPendingWrite; if (udp) { emit q->outgoingDatagram(packet); } else { if (tls) tls->write(packet); else bs->write(packet); } } void ensurePermission(const QHostAddress &addr) { if (!desiredPerms.contains(addr)) { if (debugLevel >= TurnClient::DL_Info) emit q->debugLine(QString("Setting permission for peer address %1").arg(addr.toString())); desiredPerms += addr; allocate->setPermissions(desiredPerms); } } // assumes we have perms for all input already void ensureChannels(const QList &channels) { bool changed = false; for (const StunAllocate::Channel &c : channels) { if (!desiredChannels.contains(c)) { if (debugLevel >= TurnClient::DL_Info) emit q->debugLine( QString("Setting channel for peer address/port %1;%2").arg(c.address.toString()).arg(c.port)); desiredChannels += c; changed = true; } } if (changed) allocate->setChannels(desiredChannels); } void addChannelPeer(const QHostAddress &addr, int port) { ensurePermission(addr); StunAllocate::Channel c(addr, port); if (!pendingChannels.contains(c) && !desiredChannels.contains(c)) { pendingChannels += c; tryChannelQueued(); } } void udp_datagramsWritten(int count) { QList writtenDests; while (count > 0) { Q_ASSERT(!writeItems.isEmpty()); WriteItem wi = writeItems.takeFirst(); --count; if (wi.type == WriteItem::Data) { int at = -1; for (int n = 0; n < writtenDests.count(); ++n) { if (writtenDests[n].addr == wi.addr && writtenDests[n].port == wi.port) { at = n; break; } } if (at != -1) { ++writtenDests[at].count; } else { Written wr; wr.addr = wi.addr; wr.port = wi.port; wr.count = 1; writtenDests += wr; } } } emitPacketsWritten(writtenDests); } void emitPacketsWritten(const QList &writtenDests) { ObjectSessionWatcher watch(&sess); for (const Written &wr : writtenDests) { emit q->packetsWritten(wr.count, wr.addr, wr.port); if (!watch.isValid()) return; } } // return true if we are retrying, false if we should error out bool handleRetry() { ++retryCount; if (retryCount < 3 && !stopping) { if (debugLevel >= TurnClient::DL_Info) emit q->debugLine("retrying..."); // start completely over, but retain the same pool // so the user isn't asked to auth again int tmp_retryCount = retryCount; StunTransactionPool::Ptr tmp_pool = pool; pool.reset(); cleanup(); retryCount = tmp_retryCount; pool = tmp_pool; do_connect(); return true; } return false; } private slots: void bs_connected() { ObjectSessionWatcher watch(&sess); emit q->connected(); if (!watch.isValid()) return; if (mode == TurnClient::TlsMode) { tls = new QCA::TLS(this); connect(tls, SIGNAL(handshaken()), SLOT(tls_handshaken())); connect(tls, SIGNAL(readyRead()), SLOT(tls_readyRead())); connect(tls, SIGNAL(readyReadOutgoing()), SLOT(tls_readyReadOutgoing())); connect(tls, SIGNAL(error()), SLOT(tls_error())); tlsHandshaken = false; if (debugLevel >= TurnClient::DL_Info) emit q->debugLine("TLS handshaking..."); tls->startClient(); } else after_connected(); } void bs_connectionClosed() { cleanup(); errorString = "Server unexpectedly disconnected."; emit q->error(TurnClient::ErrorStream); } void bs_delayedCloseFinished() { cleanup(); emit q->closed(); } void bs_readyRead() { QByteArray buf = bs->readAll(); if (tls) tls->writeIncoming(buf); else processStream(buf); } void bs_bytesWritten(qint64 written) { if (tls) { // convertBytesWritten() is unsafe to call unless // the TLS handshake is completed if (!tlsHandshaken) return; written = tls->convertBytesWritten(written); } writtenBytes += written; QList writtenDests; while (writtenBytes > 0) { Q_ASSERT(!writeItems.isEmpty()); if (writtenBytes < writeItems.first().size) break; WriteItem wi = writeItems.takeFirst(); writtenBytes -= wi.size; if (wi.type == WriteItem::Data) { int at = -1; for (int n = 0; n < writtenDests.count(); ++n) { if (writtenDests[n].addr == wi.addr && writtenDests[n].port == wi.port) { at = n; break; } } if (at != -1) { ++writtenDests[at].count; } else { Written wr; wr.addr = wi.addr; wr.port = wi.port; wr.count = 1; writtenDests += wr; } } } emitPacketsWritten(writtenDests); } void bs_error(int e) { TurnClient::Error te; if (qobject_cast(bs)) { if (e == HttpConnect::ErrConnectionRefused) te = TurnClient::ErrorConnect; else if (e == HttpConnect::ErrHostNotFound) te = TurnClient::ErrorHostNotFound; else if (e == HttpConnect::ErrProxyConnect) te = TurnClient::ErrorProxyConnect; else if (e == HttpConnect::ErrProxyNeg) te = TurnClient::ErrorProxyNeg; else if (e == HttpConnect::ErrProxyAuth) te = TurnClient::ErrorProxyAuth; else te = TurnClient::ErrorStream; } else if (qobject_cast(bs)) { if (e == SocksClient::ErrConnectionRefused) te = TurnClient::ErrorConnect; else if (e == SocksClient::ErrHostNotFound) te = TurnClient::ErrorHostNotFound; else if (e == SocksClient::ErrProxyConnect) te = TurnClient::ErrorProxyConnect; else if (e == SocksClient::ErrProxyNeg) te = TurnClient::ErrorProxyNeg; else if (e == SocksClient::ErrProxyAuth) te = TurnClient::ErrorProxyAuth; else te = TurnClient::ErrorStream; } else { if (e == BSocket::ErrConnectionRefused) te = TurnClient::ErrorConnect; else if (e == BSocket::ErrHostNotFound) te = TurnClient::ErrorHostNotFound; else te = TurnClient::ErrorStream; } cleanup(); errorString = "Transport error."; emit q->error(te); } void tls_handshaken() { tlsHandshaken = true; ObjectSessionWatcher watch(&sess); emit q->tlsHandshaken(); if (!watch.isValid()) return; tls->continueAfterStep(); after_connected(); } void tls_readyRead() { processStream(tls->read()); } void tls_readyReadOutgoing() { bs->write(tls->readOutgoing()); } void tls_closed() { delete tls; tls = nullptr; do_sock_close(); } void tls_error() { cleanup(); errorString = "TLS error."; emit q->error(TurnClient::ErrorTls); } void pool_outgoingMessage(const QByteArray &packet, const QHostAddress &toAddress, int toPort) { // we aren't using IP-associated transactions Q_UNUSED(toAddress); Q_UNUSED(toPort); writeItems += WriteItem(packet.size()); // emit q->debugLine(QString("Sending turn packet to: %1:%2") // .arg(bs->abstractSocket()->peerAddress().toString()) // .arg(bs->abstractSocket()->peerPort())); if (tls) tls->write(packet); else bs->write(packet); } void pool_needAuthParams() { emit q->needAuthParams(); } void pool_debugLine(const QString &line) { emit q->debugLine(line); } void allocate_started() { allocateStarted = true; if (debugLevel >= TurnClient::DL_Info) emit q->debugLine("Allocate started"); emit q->activated(); } void allocate_stopped() { delete allocate; allocate = nullptr; unsetPool(); if (udp) emit q->closed(); else do_transport_close(); } void allocate_error(XMPP::StunAllocate::Error e) { QString str = allocate->errorString(); TurnClient::Error te; if (e == StunAllocate::ErrorAuth) te = TurnClient::ErrorAuth; else if (e == StunAllocate::ErrorRejected) te = TurnClient::ErrorRejected; else if (e == StunAllocate::ErrorProtocol) te = TurnClient::ErrorProtocol; else if (e == StunAllocate::ErrorCapacity) te = TurnClient::ErrorCapacity; else if (e == StunAllocate::ErrorMismatch) { if (!udp && handleRetry()) return; te = TurnClient::ErrorMismatch; } else te = TurnClient::ErrorGeneric; cleanup(); errorString = str; emit q->error(te); } void allocate_permissionsChanged() { if (debugLevel >= TurnClient::DL_Info) emit q->debugLine("PermissionsChanged"); tryChannelQueued(); tryWriteQueued(); } void allocate_channelsChanged() { if (debugLevel >= TurnClient::DL_Info) emit q->debugLine("ChannelsChanged"); tryWriteQueued(); } void allocate_debugLine(const QString &line) { emit q->debugLine(line); } }; TurnClient::TurnClient(QObject *parent) : QObject(parent) { d = new Private(this); } TurnClient::~TurnClient() { delete d; } void TurnClient::setProxy(const Proxy &proxy) { d->proxy = proxy; } void TurnClient::setClientSoftwareNameAndVersion(const QString &str) { d->clientSoftware = str; } void TurnClient::connectToHost(StunTransactionPool *pool, const QHostAddress &addr, int port) { d->serverAddr = addr; d->serverPort = port; d->udp = true; d->pool = pool->sharedFromThis(); d->in.clear(); d->do_connect(); } void TurnClient::connectToHost(const QHostAddress &addr, int port, Mode mode) { d->serverAddr = addr; d->serverPort = port; d->udp = false; d->mode = mode; d->in.clear(); d->do_connect(); } QHostAddress TurnClient::serverAddress() const { return d->serverAddr; } QByteArray TurnClient::processIncomingDatagram(const QByteArray &buf, bool notStun, QHostAddress *addr, int *port) { return d->processNonPoolPacket(buf, notStun, addr, port); } void TurnClient::outgoingDatagramsWritten(int count) { d->udp_datagramsWritten(count); } QString TurnClient::realm() const { if (d->pool) return d->pool->realm(); else return d->realm; } void TurnClient::setUsername(const QString &username) { d->user = username; if (d->pool) d->pool->setUsername(d->user); } void TurnClient::setPassword(const QCA::SecureArray &password) { d->pass = password; if (d->pool) d->pool->setPassword(d->pass); } void TurnClient::setRealm(const QString &realm) { d->realm = realm; if (d->pool) d->pool->setRealm(d->realm); } void TurnClient::continueAfterParams() { Q_ASSERT(d->pool); d->pool->continueAfterParams(); } void TurnClient::close() { d->do_close(); } StunAllocate *TurnClient::stunAllocate() { return d->allocate; } void TurnClient::addChannelPeer(const QHostAddress &addr, int port) { d->addChannelPeer(addr, port); } int TurnClient::packetsToRead() const { return d->in.count(); } int TurnClient::packetsToWrite() const { return d->outPending.count() + d->outPendingWrite; } QByteArray TurnClient::read(QHostAddress *addr, int *port) { if (!d->in.isEmpty()) { Private::Packet p = d->in.takeFirst(); *addr = p.addr; *port = p.port; return p.data; } else return QByteArray(); } void TurnClient::write(const QByteArray &buf, const QHostAddress &addr, int port) { d->writeOrQueue(buf, addr, port); } QString TurnClient::errorString() const { return d->errorString; } void TurnClient::setDebugLevel(DebugLevel level) { d->debugLevel = level; if (d->pool) d->pool->setDebugLevel((StunTransactionPool::DebugLevel)level); } void TurnClient::changeThread(QThread *thread) { if (d->pool) d->pool->moveToThread(thread); moveToThread(thread); } } // namespace XMPP #include "turnclient.moc" psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/turnclient.h000066400000000000000000000122341370065651000247720ustar00rootroot00000000000000/* * Copyright (C) 2010 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef TURNCLIENT_H #define TURNCLIENT_H #include #include #include #include namespace QCA { class SecureArray; } namespace XMPP { class StunAllocate; class StunTransactionPool; class TurnClient : public QObject { Q_OBJECT public: enum Error { ErrorGeneric, ErrorHostNotFound, ErrorConnect, // stream error or stream unexpectedly disconnected by peer ErrorStream, ErrorProxyConnect, ErrorProxyNeg, ErrorProxyAuth, ErrorTls, ErrorAuth, ErrorRejected, ErrorProtocol, ErrorCapacity, // according to the TURN spec, a client should try three times // to correct a mismatch error before giving up. this class // will perform the retries internally, and ErrorMismatch is // only emitted when it has given up. note that if this // happens, the TURN spec says you should not connect to the // TURN server again for at least 2 minutes. // note: in UDP mode, this class does not perform retries and // will emit this error immediately. ErrorMismatch }; enum Mode { PlainMode, TlsMode }; enum DebugLevel { DL_None, DL_Info, DL_Packet }; // adapted from XMPP::AdvancedConnector class Proxy { public: enum { None, HttpConnect, Socks }; Proxy(); ~Proxy(); int type() const; QString host() const; quint16 port() const; QString user() const; QString pass() const; void setHttpConnect(const QString &host, quint16 port); void setSocks(const QString &host, quint16 port); void setUserPass(const QString &user, const QString &pass); private: int t; QString v_host; quint16 v_port = 0; QString v_user, v_pass; }; TurnClient(QObject *parent = nullptr); ~TurnClient(); void setProxy(const Proxy &proxy); void setClientSoftwareNameAndVersion(const QString &str); // for UDP. does not take ownership of the pool. stun transaction // I/O occurs through the pool. transfer of data packets occurs // via processIncomingDatagram(), outgoingDatagram(), and // outgoingDatagramsWritten(). authentication happens through the // pool and not through this class. the turn addr/port is optional, // and used only for addr association with the pool void connectToHost(StunTransactionPool *pool, const QHostAddress &addr = QHostAddress(), int port = -1); // for TCP and TCP-TLS void connectToHost(const QHostAddress &addr, int port, Mode mode = PlainMode); QHostAddress serverAddress() const; // for UDP, use this function to process incoming packets instead of // read(). QByteArray processIncomingDatagram(const QByteArray &buf, bool notStun, QHostAddress *addr, int *port); // call after writing datagrams from outgoingDatagram. not DOR-DS safe void outgoingDatagramsWritten(int count); QString realm() const; void setUsername(const QString &username); void setPassword(const QCA::SecureArray &password); void setRealm(const QString &realm); void continueAfterParams(); void close(); StunAllocate *stunAllocate(); void addChannelPeer(const QHostAddress &addr, int port); int packetsToRead() const; int packetsToWrite() const; // TCP mode only QByteArray read(QHostAddress *addr, int *port); // for UDP, this call may emit outgoingDatagram() immediately (not // DOR-DS safe) void write(const QByteArray &buf, const QHostAddress &addr, int port); QString errorString() const; void setDebugLevel(DebugLevel level); // default DL_None void changeThread(QThread *thread); signals: void connected(); // tcp connected void tlsHandshaken(); void closed(); void needAuthParams(); void retrying(); // mismatch error received, starting all over void activated(); // ready for read/write // TCP mode only void readyRead(); void packetsWritten(int count, const QHostAddress &addr, int port); void error(XMPP::TurnClient::Error e); // data packets to be sent to the TURN server, UDP mode only void outgoingDatagram(const QByteArray &buf); // not DOR-SS/DS safe void debugLine(const QString &line); private: class Private; friend class Private; Private *d; }; } // namespace XMPP #endif // TURNCLIENT_H psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/udpportreserver.cpp000066400000000000000000000236051370065651000264150ustar00rootroot00000000000000/* * Copyright (C) 2010 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "udpportreserver.h" #include #include namespace XMPP { class UdpPortReserver::Private : public QObject { Q_OBJECT public: class Item { public: int port; // port to reserve bool lent; // list of sockets for this port, one socket per address. // note that we may have sockets bound for addresses // we no longer care about, if we are currently lending // them out QList sockList; // keep track of which addresses we lent out QList lentAddrs; Item() : port(-1), lent(false) { } bool haveAddress(const QHostAddress &addr) const { for (const QUdpSocket *sock : sockList) { if (sock->localAddress() == addr) return true; } return false; } }; UdpPortReserver * q; QList addrs; QList ports; // sorted. // addrs * ports = all available sockets /** * @brief items kind of ports slice over all provided addresses. * When we request binding on one port it means it will be bound on one item (all its addresses). * The items are sorted by port. */ QList items; Private(UdpPortReserver *_q) : QObject(_q), q(_q) { } ~Private() { bool lendingAny = false; for (const Item &i : items) { if (i.lent) { lendingAny = true; break; } } Q_ASSERT(!lendingAny); if (lendingAny) abort(); for (const Item &i : items) { for (QUdpSocket *sock : i.sockList) sock->deleteLater(); } } void updateAddresses(const QList &newAddrs) { addrs = newAddrs; tryBind(); tryCleanup(); } void updatePorts(const QList &newPorts) { QList added; for (int x : newPorts) { bool found = false; for (const Item &i : items) { if (i.port == x) { found = true; break; } } if (!found) added += x; } ports = newPorts; // keep ports in sorted order std::sort(ports.begin(), ports.end()); for (int x : added) { int insert_before = items.count(); for (int n = 0; n < items.count(); ++n) { if (x < items[n].port) { insert_before = n; break; } } Item i; i.port = x; items.insert(insert_before, i); } tryBind(); tryCleanup(); } bool reservedAll() const { bool ok = true; for (const Item &i : items) { // skip ports we don't care about if (!ports.contains(i.port)) continue; if (!isReserved(i)) { ok = false; break; } } return ok; } QList borrowSockets(int portCount, QObject *parent) { Q_ASSERT(portCount > 0); QList out; if (portCount > 1) { // first try to see if we can find something all in a // row, starting with best alignment to worst for (int align = portCount; align >= 2; align /= 2) { int at = findConsecutive(portCount, align); if (at != -1) { for (int n = 0; n < portCount; ++n) out += lendItem(&items[at + n], parent); break; } } if (out.isEmpty()) { // otherwise, try splitting them up into // smaller consecutive chunks int chunks[2]; chunks[0] = portCount / 2 + (portCount % 2); chunks[1] = portCount / 2; for (int n = 0; n < 2; ++n) out += borrowSockets(chunks[n], parent); } } else { // take the next available port int at = findConsecutive(1, 1); if (at != -1) out += lendItem(&items[at], parent); } return out; } void returnSockets(const QList &sockList) { for (QUdpSocket *sock : sockList) { int at = -1; for (int n = 0; n < items.count(); ++n) { if (items[n].sockList.contains(sock)) { at = n; break; } } Q_ASSERT(at != -1); if (at == -1) continue; Item &i = items[at]; QHostAddress a = sock->localAddress(); Q_ASSERT(i.lent); Q_ASSERT(i.lentAddrs.contains(a)); sock->setParent(q); connect(sock, SIGNAL(readyRead()), SLOT(sock_readyRead())); i.lentAddrs.removeAll(a); if (i.lentAddrs.isEmpty()) i.lent = false; } tryCleanup(); } private slots: void sock_readyRead() { QUdpSocket *sock = static_cast(sender()); // eat all packets while (sock->hasPendingDatagrams()) sock->readDatagram(nullptr, 0); } private: void tryBind() { for (int n = 0; n < items.count(); ++n) { Item &i = items[n]; // skip ports we don't care about if (!ports.contains(i.port)) continue; QList neededAddrs; for (const QHostAddress &a : addrs) { if (!i.haveAddress(a)) neededAddrs += a; } for (const QHostAddress &a : neededAddrs) { QUdpSocket *sock = new QUdpSocket(q); if (!sock->bind(a, i.port)) { delete sock; continue; } connect(sock, SIGNAL(readyRead()), SLOT(sock_readyRead())); i.sockList += sock; } } } void tryCleanup() { for (int n = 0; n < items.count(); ++n) { Item &i = items[n]; // don't care about this port anymore? if (!i.lent && !ports.contains(i.port)) { for (QUdpSocket *sock : i.sockList) sock->deleteLater(); items.removeAt(n); --n; // adjust position continue; } // any addresses we don't care about? for (int k = 0; k < i.sockList.count(); ++k) { QUdpSocket *sock = i.sockList[k]; QHostAddress a = sock->localAddress(); if (!addrs.contains(a) && !i.lentAddrs.contains(a)) { sock->deleteLater(); i.sockList.removeAt(k); --k; // adjust position continue; } } } } bool isReserved(const Item &i) const { // must have desired addrs to consider a port reserved if (addrs.isEmpty()) return false; for (const QHostAddress &a : addrs) { if (!i.haveAddress(a)) return false; } return true; } bool isConsecutive(int at, int count) const { if (at + count > items.count()) return false; for (int n = 0; n < count; ++n) { const Item &i = items[at + n]; if (i.lent || !isReserved(i)) return false; if (n > 0 && (i.port != items[at + n - 1].port + 1)) return false; } return true; } int findConsecutive(int count, int align) const { for (int n = 0; n < items.count(); n += align) { if (isConsecutive(n, count)) return n; } return -1; } QList lendItem(Item *i, QObject *parent) { QList out; i->lent = true; for (QUdpSocket *sock : i->sockList) { i->lentAddrs += sock->localAddress(); sock->disconnect(this); sock->setParent(parent); out += sock; } return out; } }; UdpPortReserver::UdpPortReserver(QObject *parent) : QObject(parent) { d = new Private(this); } UdpPortReserver::~UdpPortReserver() { delete d; } void UdpPortReserver::setAddresses(const QList &addrs) { d->updateAddresses(addrs); } void UdpPortReserver::setPorts(int start, int len) { QList ports; for (int n = 0; n < len; ++n) ports += start + n; setPorts(ports); } void UdpPortReserver::setPorts(const QList &ports) { d->updatePorts(ports); } bool UdpPortReserver::reservedAll() const { return d->reservedAll(); } QList UdpPortReserver::borrowSockets(int portCount, QObject *parent) { return d->borrowSockets(portCount, parent); } void UdpPortReserver::returnSockets(const QList &sockList) { d->returnSockets(sockList); } } // namespace XMPP #include "udpportreserver.moc" psi-plus-snapshots-1.4.1456/iris/src/irisnet/noncore/udpportreserver.h000066400000000000000000000047741370065651000260700ustar00rootroot00000000000000/* * Copyright (C) 2010 Barracuda Networks, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef UDPPORTRESERVER_H #define UDPPORTRESERVER_H #include #include class QHostAddress; class QUdpSocket; namespace XMPP { // call both setAddresses() and setPorts() at least once for socket // reservations to occur. at any time you can update the list of addresses // (interfaces) and ports to reserve. note that the port must be available // on all addresses in order for it to get reserved. // note: you must return all sockets back to this class before destructing class UdpPortReserver : public QObject { Q_OBJECT public: UdpPortReserver(QObject *parent = nullptr); ~UdpPortReserver(); void setAddresses(const QList &addrs); void setPorts(int start, int len); void setPorts(const QList &ports); // return true if all ports got reserved, false if only some // or none got reserved bool reservedAll() const; // may return less than asked for, if we had less reserved ports // left. some attempt is made to return aligned or consecutive port // values, but this is just a best effort and not a guarantee. if // not all ports were able to be reserved earlier, then this call // may attempt to reserve those ports again. the sockets in the // returned list are ordered by port (in ascending order) and then // by address (in the order provided). since all addresses must be // able to bind to a port for it to be considered reserved, this // function always returns a list with a size that is a multiple of // the number of addresses. QList borrowSockets(int portCount, QObject *parent = nullptr); void returnSockets(const QList &sockList); private: class Private; Private *d; }; } // namespace XMPP #endif // UDPPORTRESERVER_H psi-plus-snapshots-1.4.1456/iris/src/jdns/000077500000000000000000000000001370065651000202465ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/jdns/CMakeLists.txt000066400000000000000000000141741370065651000230150ustar00rootroot00000000000000project(jdns) # Force cmake 2.8.8 in order to have a decent support of Qt5 cmake_minimum_required(VERSION 2.8.8) cmake_policy(SET CMP0003 NEW) # Do not link against qtmain on Windows cmake_policy(SET CMP0020 OLD) # On Windows debug library should have 'd' postfix. if(WIN32) set(CMAKE_DEBUG_POSTFIX "d") elseif(APPLE) set(CMAKE_DEBUG_POSTFIX "_debug") endif(WIN32) # OPTION(OSX_FRAMEWORK "Build a Mac OS X Framework") # SET(FRAMEWORK_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/Library/Frameworks" # CACHE PATH "Where to place jdns.framework if OSX_FRAMEWORK is selected") option(BUILD_SHARED_LIBS "Build shared library" ON) option(BUILD_QJDNS "Buid JDNS Qt-wrapper" ON) option(BUILD_JDNS_TOOL "Build jdns test tool" ON) # jdns tool requires qjdns if(NOT BUILD_QJDNS) set(BUILD_JDNS_TOOL OFF) endif(NOT BUILD_QJDNS) if(NOT BUILD_SHARED_LIBS) add_definitions(-DJDNS_STATIC) endif(NOT BUILD_SHARED_LIBS) if(BUILD_QJDNS) option(QT4_BUILD "Force building with Qt4 even if Qt5 is found") if(NOT QT4_BUILD) find_package(Qt5Core QUIET) find_package(Qt5Network QUIET) endif(NOT QT4_BUILD) if(Qt5Core_FOUND) message("Qt5 found") include_directories(${Qt5Core_INCLUDE_DIRS}) include_directories(${Qt5Network_INCLUDE_DIRS}) add_definitions(${Qt5Core_DEFINITIONS}) add_definitions(${Qt5Network_DEFINITIONS}) # Tell CMake to run moc when necessary: set(CMAKE_AUTOMOC ON) # As moc files are generated in the binary dir, tell CMake # to always look for includes there: set(CMAKE_INCLUDE_CURRENT_DIR ON) set(QJDns_QT_PC_VERSION "Qt5Core Qt5Network") else(Qt5Core_FOUND) message("Qt5 not found, searching for Qt4") # Find Qt4 find_package(Qt4 REQUIRED QtCore QtNetwork) # Include the cmake file needed to use qt4 include(${QT_USE_FILE}) set(QJDns_QT_PC_VERSION "QtCore QtNetwork") endif(Qt5Core_FOUND) if(NOT WIN32) set(QT_DONT_USE_QTGUI TRUE) endif(NOT WIN32) endif(BUILD_QJDNS) # put the include dirs which are in the source or build tree # before all other include dirs, so the headers in the sources # are prefered over the already installed ones set(CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE ON) set(JDNS_INCLUDEDIR "${CMAKE_CURRENT_SOURCE_DIR}/include/jdns" ) #add extra search paths for libraries and includes set(LIB_SUFFIX "" CACHE STRING "Define suffix of directory name (32/64)" ) set(BIN_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE STRING "Directory where binary will install") set(LIB_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}" CACHE STRING "Directory where library will install") set(INCLUDE_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/include" CACHE PATH "The directory the headers are installed in") if(NOT MSVC) set(JDNS_CONFIG_INSTALL_DIR "${LIB_INSTALL_DIR}/cmake/jdns") set(QJDNS_CONFIG_INSTALL_DIR "${LIB_INSTALL_DIR}/cmake/qjdns") else(NOT MSVC) set(JDNS_CONFIG_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/cmake/") set(QJDNS_CONFIG_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/cmake/") endif(NOT MSVC) if(APPLE) set(CMAKE_INSTALL_NAME_DIR ${LIB_INSTALL_DIR}) endif(APPLE) set(JDNS_LIB_MAJOR_VERSION "2") set(JDNS_LIB_MINOR_VERSION "0") set(JDNS_LIB_PATCH_VERSION "0") set(JDNS_LIB_VERSION_STRING "${JDNS_LIB_MAJOR_VERSION}.${JDNS_LIB_MINOR_VERSION}.${JDNS_LIB_PATCH_VERSION}") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" ) # Use the same path for shared and static library set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" ) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" ) # pkg-config if(NOT WIN32) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/jdns.pc.in ${CMAKE_CURRENT_BINARY_DIR}/jdns.pc @ONLY) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/jdns.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/qjdns.pc.in ${CMAKE_CURRENT_BINARY_DIR}/qjdns.pc @ONLY) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/qjdns.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig) endif(NOT WIN32) include_directories("include/jdns/") # Subdirs add_subdirectory(src) if(BUILD_JDNS_TOOL) add_subdirectory(tools/jdns) endif(BUILD_JDNS_TOOL) configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY) # figure out the relative path from the installed Config.cmake file to the install prefix (which may be at # runtime different from the chosen CMAKE_INSTALL_PREFIX if under Windows the package was installed anywhere) # This relative path will be configured into the QJDNSConfig.cmake file(RELATIVE_PATH relInstallDir ${JDNS_CONFIG_INSTALL_DIR} ${CMAKE_INSTALL_PREFIX}) file(RELATIVE_PATH JDNS_INCLUDEDIR_REL ${CMAKE_INSTALL_PREFIX} "${INCLUDE_INSTALL_DIR}/jdns") file(RELATIVE_PATH LIB_INSTALL_DIR_REL ${CMAKE_INSTALL_PREFIX} ${LIB_INSTALL_DIR}) # cmake-modules configure_file(${CMAKE_CURRENT_SOURCE_DIR}/JDnsConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/JDnsConfig.cmake @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/JDnsConfigVersion.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/JDnsConfigVersion.cmake @ONLY) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/JDnsConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/JDnsConfigVersion.cmake DESTINATION ${JDNS_CONFIG_INSTALL_DIR}) install(EXPORT jdns-export DESTINATION ${JDNS_CONFIG_INSTALL_DIR} FILE JDnsTargets.cmake) if(BUILD_QJDNS) install(EXPORT qjdns-export DESTINATION ${QJDNS_CONFIG_INSTALL_DIR} FILE QJDnsTargets.cmake) # cmake-modules configure_file(${CMAKE_CURRENT_SOURCE_DIR}/QJDnsConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/QJDnsConfig.cmake @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/QJDnsConfigVersion.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/QJDnsConfigVersion.cmake @ONLY) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/QJDnsConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/QJDnsConfigVersion.cmake DESTINATION ${QJDNS_CONFIG_INSTALL_DIR}) endif(BUILD_QJDNS) add_custom_target(uninstall "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") psi-plus-snapshots-1.4.1456/iris/src/jdns/COPYING000066400000000000000000000020701370065651000213000ustar00rootroot00000000000000Copyright (c) 2005-2008 Justin Karneges, Jeremie Miller Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. psi-plus-snapshots-1.4.1456/iris/src/jdns/JDnsConfig.cmake.in000066400000000000000000000012501370065651000236370ustar00rootroot00000000000000# known at buildtime set(JDns_VERSION "@JDNS_LIB_VERSION_STRING@") set(JDns_VERSION_MAJOR @JDNS_LIB_MAJOR_VERSION@) set(JDns_VERSION_MINOR @JDNS_LIB_MINOR_VERSION@) set(JDns_VERSION_PATCH @JDNS_LIB_PATCH_VERSION@) get_filename_component(currentDir ${CMAKE_CURRENT_LIST_FILE} PATH) # get the directory where I myself am get_filename_component(rootDir ${currentDir}/@relInstallDir@ ABSOLUTE) # get the chosen install prefix # install locations set(JDns_INCLUDE_DIR "${rootDir}/@JDNS_INCLUDEDIR_REL@") set(JDns_LIBRARY_DIR "${rootDir}/@LIB_INSTALL_DIR_REL@") set(JDns_LIB_SONAME jdns) if(NOT TARGET jdns) include(${currentDir}/JDnsTargets.cmake) endif() set(JDns_LIBRARY jdns) psi-plus-snapshots-1.4.1456/iris/src/jdns/JDnsConfigVersion.cmake.in000066400000000000000000000007161370065651000252130ustar00rootroot00000000000000set(PACKAGE_VERSION "@JDNS_LIB_VERSION_STRING@") if(PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION) set(PACKAGE_VERSION_EXACT TRUE) endif(PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION) if(NOT PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) set(PACKAGE_VERSION_COMPATIBLE TRUE) else(NOT PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) set(PACKAGE_VERSION_UNSUITABLE TRUE) endif(NOT PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) psi-plus-snapshots-1.4.1456/iris/src/jdns/QJDnsConfig.cmake.in000066400000000000000000000014301370065651000237600ustar00rootroot00000000000000if(NOT JDns_FOUND) find_package(JDns "@JDNS_LIB_VERSION_STRING@" REQUIRED) endif(NOT JDns_FOUND) # known at buildtime set(QJDns_VERSION "@JDNS_LIB_VERSION_STRING@") set(QJDns_VERSION_MAJOR @JDNS_LIB_MAJOR_VERSION@) set(QJDns_VERSION_MINOR @JDNS_LIB_MINOR_VERSION@) set(QJDns_VERSION_PATCH @JDNS_LIB_PATCH_VERSION@) get_filename_component(currentDir ${CMAKE_CURRENT_LIST_FILE} PATH) # get the directory where I myself am get_filename_component(rootDir ${currentDir}/@relInstallDir@ ABSOLUTE) # get the chosen install prefix # install locations set(QJDns_INCLUDE_DIR "${rootDir}/@JDNS_INCLUDEDIR_REL@") set(QJDns_LIBRARY_DIR "${rootDir}/@LIB_INSTALL_DIR_REL@") set(QJDns_LIB_SONAME qjdns) if(NOT TARGET qjdns) include(${currentDir}/QJDnsTargets.cmake) endif() set(QJDns_LIBRARY qjdns) psi-plus-snapshots-1.4.1456/iris/src/jdns/QJDnsConfigVersion.cmake.in000066400000000000000000000007161370065651000253340ustar00rootroot00000000000000set(PACKAGE_VERSION "@JDNS_LIB_VERSION_STRING@") if(PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION) set(PACKAGE_VERSION_EXACT TRUE) endif(PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION) if(NOT PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) set(PACKAGE_VERSION_COMPATIBLE TRUE) else(NOT PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) set(PACKAGE_VERSION_UNSUITABLE TRUE) endif(NOT PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) psi-plus-snapshots-1.4.1456/iris/src/jdns/README.md000066400000000000000000000066541370065651000215400ustar00rootroot00000000000000### JDNS Date: October 1st, 2005 Author: Justin Karneges JDNS is a simple DNS implementation that can perform normal DNS queries of any record type (notably SRV), as well as Multicast DNS queries and advertising. Multicast support is based on Jeremie Miller's "mdnsd" implementation. For maximum flexibility, JDNS is written in C with no direct dependencies, and is licensed under the MIT license. Your application must supply functionality to JDNS, such as UDP sending/receiving, via callbacks. For Qt users there is a wrapper available called QJDns. jdns.pri can be used to include everything into a qmake project. jdns.pro will build the sample Qt-based commandline tool 'jdns'. #### Features: * DNS client "stub" resolver * Can fetch any record type, but provides handy decoding for many known types: A, AAAA, SRV, MX, TXT, etc. * Performs retries, caching/expiration, and CNAME following * Algorithm logic adapted from Q3Dns * Multicast queries * Multicast advertising #### Why? * Trolltech is phasing out the Qt DNS implementation, which in Qt 4 has been relegated to the Qt3Support module. A replacement was desired. * While there are many DNS libraries available, at the time of this writing it was (and still may be) hard to find one that satisfies three essential conditions: cross-platform friendliness (and this includes Windows 9x!), the ability to integrate into existing eventloops, sensible licensing (ie, not GPL). #### How to use: * Prepare callbacks and call jdns_session_new() * Call jdns_init_unicast() or jdns_init_multicast(), depending on if you want regular or multicast DNS. If you want both kinds, you can always make two sessions. * Make queries and have fun * Call jdns_step() at the right times to advance JDNS processing #### What is left to you: * The callback functions, obviously. * Querying for several "qualified" names. Here is what Q3Dns does: Query for name as provided Query for name + '.domain' (for every domain the computer is in) * Detecting for '.local' in a name to be queried, and using that to decide whether to query via Multicast or normal DNS. * Recognition of IP addresses. If you want an IP address to resolve to itself, then do that yourself. Passing an IP address as a DNS name to JDNS won't work (especially since it wouldn't make any sense in some contexts, like SRV). * Recognition of known hosts. If you want this, compare inputs against jdns_system_dnsparams(). * For zeroconf/Bonjour, keep in mind that JDNS only provides Multicast DNS capability. DNS-SD and any higher layers would be your job. Using a custom DNS implementation has the drawback that it is difficult to take advantage of platform-specific features (for example, an OS-wide DNS cache or LDAP integration). An application strategy for normal DNS should probably be: * If an A or AAAA record is desired, use a native lookup. * Else, if the platform has advanced DNS features already (ie, res_query), use those. * Else, use JDNS. However, it may not be a bad idea at first to use JDNS for all occasions, so that it can be debugged. For Multicast DNS, awareness of the platform is doubly important. There should only be one Multicast DNS "Responder" per computer, and using JDNS at the same time could result in a conflict. An application strategy for Multicast DNS should be: * If the platform has a Multicast DNS daemon installed already, use it somehow. * Else, use JDNS. Have fun! psi-plus-snapshots-1.4.1456/iris/src/jdns/TODO000066400000000000000000000017171370065651000207440ustar00rootroot00000000000000(nothing) but, this stuff couldn't hurt: fields that need to be an explicit size should use int16_t, etc support for other DNS record types (SOA, NSPTR) detect CNAME loops, rather than looping max times in order to fail don't follow CNAME for SRV (or so I'm told?) if it is not possible to implement DNSSEC outside of jdns, then add the minimal number of hooks to jdns so that it becomes possible use hash tables to speed up the list lookups unit tests qjdns debug lines reworking: init should emit debugLinesReady, and debug should be available immediately after call (the emit will break SS/DS, put a note about this in a comment about init()) anywhere else, debugLines should conform to SS, but be emitted at the proper time, not deferred. currently, the debug arrives out of sequence with the other signals, resulting in strange output in the commandline tool. consideration for these changes in jdnsshared psi-plus-snapshots-1.4.1456/iris/src/jdns/cmake_uninstall.cmake.in000066400000000000000000000013351370065651000250300ustar00rootroot00000000000000if(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") message(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") endif(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) string(REGEX REPLACE "\n" ";" files "${files}") foreach(file ${files}) message(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") exec_program( "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" OUTPUT_VARIABLE rm_out RETURN_VALUE rm_retval ) if(NOT "${rm_retval}" STREQUAL 0) message(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") endif(NOT "${rm_retval}" STREQUAL 0) endforeach(file) psi-plus-snapshots-1.4.1456/iris/src/jdns/include/000077500000000000000000000000001370065651000216715ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/jdns/include/jdns/000077500000000000000000000000001370065651000226275ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/jdns/include/jdns/jdns.h000066400000000000000000000416251370065651000237460ustar00rootroot00000000000000/* * Copyright (C) 2005-2006 Justin Karneges * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef JDNS_H #define JDNS_H #include "jdns_export.h" #ifdef __cplusplus extern "C" { #endif typedef void (*jdns_object_dtor_func)(void *); typedef void *(*jdns_object_cctor_func)(const void *); #define JDNS_OBJECT \ jdns_object_dtor_func dtor; \ jdns_object_cctor_func cctor; #define JDNS_OBJECT_NEW(name) \ (name##_t *)jdns_object_new(sizeof(name##_t), \ (jdns_object_dtor_func)name##_delete, \ (jdns_object_cctor_func)name##_copy); typedef struct jdns_object { JDNS_OBJECT } jdns_object_t; JDNS_EXPORT void *jdns_object_new(int size, void (*dtor)(void *), void *(*cctor)(const void *)); JDNS_EXPORT void *jdns_object_copy(const void *a); JDNS_EXPORT void jdns_object_delete(void *a); JDNS_EXPORT void jdns_object_free(void *a); #define JDNS_LIST_DECLARE(name) \ JDNS_OBJECT \ int count; \ name##_t **item; typedef struct jdns_list { JDNS_OBJECT int count; void **item; int valueList; int autoDelete; } jdns_list_t; JDNS_EXPORT jdns_list_t *jdns_list_new(); JDNS_EXPORT jdns_list_t *jdns_list_copy(const jdns_list_t *a); JDNS_EXPORT void jdns_list_delete(jdns_list_t *a); JDNS_EXPORT void jdns_list_clear(jdns_list_t *a); JDNS_EXPORT void jdns_list_insert(jdns_list_t *a, void *item, int pos); JDNS_EXPORT void jdns_list_insert_value(jdns_list_t *a, const void *item, int pos); JDNS_EXPORT void jdns_list_remove(jdns_list_t *a, void *item); JDNS_EXPORT void jdns_list_remove_at(jdns_list_t *a, int pos); typedef struct jdns_string { JDNS_OBJECT unsigned char *data; int size; } jdns_string_t; JDNS_EXPORT jdns_string_t *jdns_string_new(); JDNS_EXPORT jdns_string_t *jdns_string_copy(const jdns_string_t *s); JDNS_EXPORT void jdns_string_delete(jdns_string_t *s); JDNS_EXPORT void jdns_string_set(jdns_string_t *s, const unsigned char *str, int str_len); JDNS_EXPORT void jdns_string_set_cstr(jdns_string_t *s, const char *str); // overlays jdns_list typedef struct jdns_stringlist { JDNS_OBJECT int count; jdns_string_t **item; } jdns_stringlist_t; JDNS_EXPORT jdns_stringlist_t *jdns_stringlist_new(); JDNS_EXPORT jdns_stringlist_t *jdns_stringlist_copy(const jdns_stringlist_t *a); JDNS_EXPORT void jdns_stringlist_delete(jdns_stringlist_t *a); JDNS_EXPORT void jdns_stringlist_append(jdns_stringlist_t *a, const jdns_string_t *str); typedef struct jdns_address { int isIpv6; union { unsigned long int v4; unsigned char *v6; // 16 bytes } addr; char *c_str; } jdns_address_t; JDNS_EXPORT jdns_address_t *jdns_address_new(); JDNS_EXPORT jdns_address_t *jdns_address_copy(const jdns_address_t *a); JDNS_EXPORT void jdns_address_delete(jdns_address_t *a); JDNS_EXPORT void jdns_address_set_ipv4(jdns_address_t *a, unsigned long int ipv4); JDNS_EXPORT void jdns_address_set_ipv6(jdns_address_t *a, const unsigned char *ipv6); // return 1 if string was ok, else 0. Note: IPv4 addresses only! JDNS_EXPORT int jdns_address_set_cstr(jdns_address_t *a, const char *str); // return 1 if the same, else 0 JDNS_EXPORT int jdns_address_cmp(const jdns_address_t *a, const jdns_address_t *b); // convenient predefined addresses/ports #define JDNS_UNICAST_PORT 53 #define JDNS_MULTICAST_PORT 5353 JDNS_EXPORT jdns_address_t *jdns_address_multicast4_new(); // 224.0.0.251 JDNS_EXPORT jdns_address_t *jdns_address_multicast6_new(); // FF02::FB typedef struct jdns_server { unsigned char *name; int port; // SRV only int priority; int weight; // SRV only } jdns_server_t; JDNS_EXPORT jdns_server_t *jdns_server_new(); JDNS_EXPORT jdns_server_t *jdns_server_copy(const jdns_server_t *s); JDNS_EXPORT void jdns_server_delete(jdns_server_t *s); JDNS_EXPORT void jdns_server_set_name(jdns_server_t *s, const unsigned char *name); typedef struct jdns_nameserver { jdns_address_t *address; int port; } jdns_nameserver_t; JDNS_EXPORT jdns_nameserver_t *jdns_nameserver_new(); JDNS_EXPORT jdns_nameserver_t *jdns_nameserver_copy(const jdns_nameserver_t *a); JDNS_EXPORT void jdns_nameserver_delete(jdns_nameserver_t *a); JDNS_EXPORT void jdns_nameserver_set(jdns_nameserver_t *a, const jdns_address_t *addr, int port); typedef struct jdns_nameserverlist { int count; jdns_nameserver_t **item; } jdns_nameserverlist_t; JDNS_EXPORT jdns_nameserverlist_t *jdns_nameserverlist_new(); JDNS_EXPORT jdns_nameserverlist_t *jdns_nameserverlist_copy(const jdns_nameserverlist_t *a); JDNS_EXPORT void jdns_nameserverlist_delete(jdns_nameserverlist_t *a); JDNS_EXPORT void jdns_nameserverlist_append(jdns_nameserverlist_t *a, const jdns_address_t *addr, int port); typedef struct jdns_dnshost { jdns_string_t *name; jdns_address_t *address; } jdns_dnshost_t; typedef struct jdns_dnshostlist { int count; jdns_dnshost_t **item; } jdns_dnshostlist_t; typedef struct jdns_dnsparams { jdns_nameserverlist_t *nameservers; jdns_stringlist_t *domains; jdns_dnshostlist_t *hosts; } jdns_dnsparams_t; JDNS_EXPORT jdns_dnsparams_t *jdns_dnsparams_new(); JDNS_EXPORT jdns_dnsparams_t *jdns_dnsparams_copy(jdns_dnsparams_t *a); JDNS_EXPORT void jdns_dnsparams_delete(jdns_dnsparams_t *a); JDNS_EXPORT void jdns_dnsparams_append_nameserver(jdns_dnsparams_t *a, const jdns_address_t *addr, int port); JDNS_EXPORT void jdns_dnsparams_append_domain(jdns_dnsparams_t *a, const jdns_string_t *domain); JDNS_EXPORT void jdns_dnsparams_append_host(jdns_dnsparams_t *a, const jdns_string_t *name, const jdns_address_t *address); #define JDNS_RTYPE_A 1 #define JDNS_RTYPE_AAAA 28 #define JDNS_RTYPE_MX 15 #define JDNS_RTYPE_SRV 33 #define JDNS_RTYPE_CNAME 5 #define JDNS_RTYPE_PTR 12 #define JDNS_RTYPE_TXT 16 #define JDNS_RTYPE_HINFO 13 #define JDNS_RTYPE_NS 2 #define JDNS_RTYPE_ANY 255 typedef struct jdns_rr { unsigned char *owner; int ttl; int type; int qclass; int rdlength; unsigned char *rdata; int haveKnown; union { jdns_address_t *address; // for A, AAAA jdns_server_t *server; // for MX, SRV unsigned char *name; // for CNAME, PTR, NS jdns_stringlist_t *texts; // for TXT struct { jdns_string_t *cpu; jdns_string_t *os; } hinfo; // for HINFO } data; } jdns_rr_t; JDNS_EXPORT jdns_rr_t *jdns_rr_new(); JDNS_EXPORT jdns_rr_t *jdns_rr_copy(const jdns_rr_t *r); JDNS_EXPORT void jdns_rr_delete(jdns_rr_t *r); JDNS_EXPORT void jdns_rr_set_owner(jdns_rr_t *r, const unsigned char *name); JDNS_EXPORT void jdns_rr_set_record(jdns_rr_t *r, int type, const unsigned char *rdata, int rdlength); JDNS_EXPORT void jdns_rr_set_A(jdns_rr_t *r, const jdns_address_t *address); JDNS_EXPORT void jdns_rr_set_AAAA(jdns_rr_t *r, const jdns_address_t *address); JDNS_EXPORT void jdns_rr_set_MX(jdns_rr_t *r, const unsigned char *name, int priority); JDNS_EXPORT void jdns_rr_set_SRV(jdns_rr_t *r, const unsigned char *name, int port, int priority, int weight); JDNS_EXPORT void jdns_rr_set_CNAME(jdns_rr_t *r, const unsigned char *name); JDNS_EXPORT void jdns_rr_set_PTR(jdns_rr_t *r, const unsigned char *name); JDNS_EXPORT void jdns_rr_set_TXT(jdns_rr_t *r, const jdns_stringlist_t *texts); JDNS_EXPORT void jdns_rr_set_HINFO(jdns_rr_t *r, const jdns_string_t *cpu, const jdns_string_t *os); JDNS_EXPORT void jdns_rr_set_NS(jdns_rr_t *r, const unsigned char *name); // note: only works on known types JDNS_EXPORT int jdns_rr_verify(const jdns_rr_t *r); typedef struct jdns_response { int answerCount; jdns_rr_t **answerRecords; int authorityCount; jdns_rr_t **authorityRecords; int additionalCount; jdns_rr_t **additionalRecords; } jdns_response_t; JDNS_EXPORT jdns_response_t *jdns_response_new(); JDNS_EXPORT jdns_response_t *jdns_response_copy(const jdns_response_t *r); JDNS_EXPORT void jdns_response_delete(jdns_response_t *r); JDNS_EXPORT void jdns_response_append_answer(jdns_response_t *r, const jdns_rr_t *rr); JDNS_EXPORT void jdns_response_append_authority(jdns_response_t *r, const jdns_rr_t *rr); JDNS_EXPORT void jdns_response_append_additional(jdns_response_t *r, const jdns_rr_t *rr); #define JDNS_PUBLISH_SHARED 0x0001 #define JDNS_PUBLISH_UNIQUE 0x0002 #define JDNS_STEP_TIMER 0x0001 #define JDNS_STEP_HANDLE 0x0002 #define JDNS_EVENT_RESPONSE 0x0001 #define JDNS_EVENT_PUBLISH 0x0002 #define JDNS_EVENT_SHUTDOWN 0x0003 #define JDNS_STATUS_SUCCESS 0x0001 #define JDNS_STATUS_NXDOMAIN 0x0002 #define JDNS_STATUS_ERROR 0x0003 #define JDNS_STATUS_TIMEOUT 0x0004 #define JDNS_STATUS_CONFLICT 0x0005 typedef struct jdns_session jdns_session_t; typedef struct jdns_callbacks { void *app; // user-supplied context // time_now: // s: session // app: user-supplied context // return: milliseconds since session started int (*time_now)(jdns_session_t *s, void *app); // rand_int: // s: session // app: user-supplied context // return: random integer between 0-65535 int (*rand_int)(jdns_session_t *s, void *app); // debug_line: // s: session // app: user-supplied context // str: a line of debug text // return: nothing void (*debug_line)(jdns_session_t *s, void *app, const char *str); // udp_bind: // s: session // app: user-supplied context // addr: ip address of interface to bind to. 0 for all // port: port of interface to bind to. 0 for any // maddr: multicast address. 0 if not using multicast // return: handle (>0) of bound socket, or 0 on error // note: for multicast, the following must be done: // use SO_REUSEPORT to share with other mdns programs // use IP_ADD_MEMBERSHIP to associate addr and maddr // set IP_MULTICAST_TTL to 255 int (*udp_bind)(jdns_session_t *s, void *app, const jdns_address_t *addr, int port, const jdns_address_t *maddr); // udp_unbind: // s: session // app: user-supplied context // handle: handle of socket obtained with udp_bind // return: nothing void (*udp_unbind)(jdns_session_t *s, void *app, int handle); // udp_read: // s: session // app: user-supplied context // handle: handle of socket obtained with udp_bind // addr: store ip address of sender // port: store port of sender // buf: store packet content // bufsize: value contains max size, to be changed to real size // return: 1 if packet read, 0 if none available int (*udp_read)(jdns_session_t *s, void *app, int handle, jdns_address_t *addr, int *port, unsigned char *buf, int *bufsize); // udp_write: // s: session // app: user-supplied context // handle: handle of socket obtained with udp_bind // addr: ip address of recipient // port: port of recipient // buf: packet content // bufsize: size of packet // return: 1 if packet taken for writing, 0 if this is a bad time int (*udp_write)(jdns_session_t *s, void *app, int handle, const jdns_address_t *addr, int port, unsigned char *buf, int bufsize); } jdns_callbacks_t; typedef struct jdns_event { int type; // JDNS_EVENT int id; // query id or publish id // for query, this can be SUCCESS, NXDOMAIN, ERROR, or TIMEOUT // for publish, this can be SUCCESS, ERROR, or CONFLICT int status; // for query jdns_response_t *response; } jdns_event_t; JDNS_EXPORT void jdns_event_delete(jdns_event_t *e); // jdns_session_new: // callbacks: the struct of callbacks // return: newly allocated session JDNS_EXPORT jdns_session_t *jdns_session_new(jdns_callbacks_t *callbacks); // jdns_session_delete: // s: session to free // return: nothing JDNS_EXPORT void jdns_session_delete(jdns_session_t *s); // jdns_init_unicast: // s: session // addr: ip address of interface to bind to. NULL for all // port: port of interface to bind to. 0 for any // return: 1 on success, 0 on failure JDNS_EXPORT int jdns_init_unicast(jdns_session_t *s, const jdns_address_t *addr, int port); // jdns_init_multicast: // s: session // addr: ip address of interface to bind to. NULL for all // port: port of interface to bind to. 0 for any // addr: multicast address to associate with. cannot be NULL // return: 1 on success, 0 on failure JDNS_EXPORT int jdns_init_multicast(jdns_session_t *s, const jdns_address_t *addr, int port, const jdns_address_t *maddr); // jdns_shutdown: // s: session // return: nothing JDNS_EXPORT void jdns_shutdown(jdns_session_t *s); // jdns_set_nameservers: // s: session // nslist: list of nameservers // return nothing JDNS_EXPORT void jdns_set_nameservers(jdns_session_t *s, const jdns_nameserverlist_t *nslist); // jdns_probe: // s: session // return: nothing JDNS_EXPORT void jdns_probe(jdns_session_t *s); // jdns_query: // s: session // name: the name to look up // rtype: the record type // return: id of this operation JDNS_EXPORT int jdns_query(jdns_session_t *s, const unsigned char *name, int rtype); // jdns_cancel_query: // s: session // id: the operation id to cancel // return: nothing JDNS_EXPORT void jdns_cancel_query(jdns_session_t *s, int id); // jdns_publish: // s: session // mode: JDNS_PUBLISH shared or unique // rec: the record data // return: id of this operation // note: supported record types: A, AAAA, SRV, CNAME, PTR, TXT, and HINFO. // if the published type is not one of these, raw rdata must be set. JDNS_EXPORT int jdns_publish(jdns_session_t *s, int mode, const jdns_rr_t *rec); // jdns_update_publish: // s: session // id: the operation id to update // rec: the record data // return: nothing // note: update only works on successfully published records, and no event // is generated for a successful update. JDNS_EXPORT void jdns_update_publish(jdns_session_t *s, int id, const jdns_rr_t *rec); // jdns_cancel_publish: // s: session // id: the operation id to cancel // return: nothing JDNS_EXPORT void jdns_cancel_publish(jdns_session_t *s, int id); // jdns_step: // s: session // return: JDNS_STEP flags OR'd together JDNS_EXPORT int jdns_step(jdns_session_t *s); // jdns_next_timer: // s: session // return: milliseconds until timeout JDNS_EXPORT int jdns_next_timer(jdns_session_t *s); // jdns_set_handle_readable: // s: session // handle: handle that is now readable // return: nothing JDNS_EXPORT void jdns_set_handle_readable(jdns_session_t *s, int handle); // jdns_set_handle_writable: // s: session // handle: handle that is now writable // return: nothing JDNS_EXPORT void jdns_set_handle_writable(jdns_session_t *s, int handle); // jdns_next_event: // s: session // return: newly allocated event, or zero if none are ready JDNS_EXPORT jdns_event_t *jdns_next_event(jdns_session_t *s); // jdns_system_dnsparams: // return: newly allocated dnsparams from the system JDNS_EXPORT jdns_dnsparams_t *jdns_system_dnsparams(); // jdns_set_hold_ids_enabled // s: session // enabled: whether to enable id holding. default is 0 (disabled) // return: nothing // normally, when a unicast query completes or any kind of query or publish // operation results in an error, the operation is automatically "canceled". // when id holding is enabled, the operation still stops internally, but the // id value used by that operation is "held" until the application // explicitly calls jdns_cancel_query() or jdns_cancel_publish() to release // it. this allows the application to ensure there is no ambiguity when // determining which operation a particular event belongs to. it is disabled // be default so as to not introduce memory leaks in existing applications, // however new applications really should use it. JDNS_EXPORT void jdns_set_hold_ids_enabled(jdns_session_t *s, int enabled); #ifdef __cplusplus } #endif #endif // JDNS_H psi-plus-snapshots-1.4.1456/iris/src/jdns/include/jdns/jdns_export.h000066400000000000000000000032241370065651000253400ustar00rootroot00000000000000/* * Copyright (C) 2014 Ivan Romanov * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** \file qjdns_export.h Preprocessor magic to allow export of library symbols. This is strictly internal. \note You should not include this header directly from an application. You should just use \#include \ instead. */ #ifndef JDNS_EXPORT_H #define JDNS_EXPORT_H #ifdef _WIN32 # ifndef JDNS_STATIC # ifdef JDNS_MAKEDLL # define JDNS_EXPORT __declspec(dllexport) # else # define JDNS_EXPORT __declspec(dllimport) # endif # endif #endif #ifndef JDNS_EXPORT # define JDNS_EXPORT #endif #endif // JDNS_EXPORT_H psi-plus-snapshots-1.4.1456/iris/src/jdns/include/jdns/qjdns.h000066400000000000000000000075701370065651000241300ustar00rootroot00000000000000/* * Copyright (C) 2005-2006 Justin Karneges * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ // this is the Qt wrapper to jdns. it requires Qt 4.1+ #ifndef QJDNS_H #define QJDNS_H #include "jdns_export.h" #include #include class JDNS_EXPORT QJDns : public QObject { Q_OBJECT public: enum Mode { Unicast, Multicast }; enum PublishMode { Unique, Shared }; enum Type { A = 1, Aaaa = 28, Mx = 15, Srv = 33, Cname = 5, Ptr = 12, Txt = 16, Hinfo = 13, Ns = 2, Any = 255 }; enum Error { ErrorGeneric, ErrorNXDomain, // query only ErrorTimeout, // query only ErrorConflict // publish only }; class JDNS_EXPORT NameServer { public: QHostAddress address; int port; NameServer(); }; class JDNS_EXPORT DnsHost { public: QByteArray name; QHostAddress address; }; class JDNS_EXPORT SystemInfo { public: QList nameServers; QList domains; QList hosts; }; class JDNS_EXPORT Record { public: QByteArray owner; int ttl; int type; QByteArray rdata; bool haveKnown; // known QHostAddress address; // for A, Aaaa QByteArray name; // for Mx, Srv, Cname, Ptr, Ns int priority; // for Mx, Srv int weight; // for Srv int port; // for Srv QList texts; // for Txt QByteArray cpu; // for Hinfo QByteArray os; // for Hinfo Record(); bool verify() const; }; class JDNS_EXPORT Response { public: QList answerRecords; QList authorityRecords; QList additionalRecords; }; QJDns(QObject *parent = 0); ~QJDns(); bool init(Mode mode, const QHostAddress &address); void shutdown(); QStringList debugLines(); static SystemInfo systemInfo(); static QHostAddress detectPrimaryMulticast(const QHostAddress &address); void setNameServers(const QList &list); int queryStart(const QByteArray &name, int type); void queryCancel(int id); // for multicast mode only int publishStart(PublishMode m, const Record &record); void publishUpdate(int id, const Record &record); void publishCancel(int id); signals: void resultsReady(int id, const QJDns::Response &results); void published(int id); void error(int id, QJDns::Error e); void shutdownFinished(); void debugLinesReady(); private: class Private; friend class Private; Private *d; }; #endif // QJDNS_H psi-plus-snapshots-1.4.1456/iris/src/jdns/include/jdns/qjdnsshared.h000066400000000000000000000523671370065651000253230ustar00rootroot00000000000000/* * Copyright (C) 2006-2007 Justin Karneges * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef QJDNSSHARED_H #define QJDNSSHARED_H #include "qjdns.h" class QJDnsShared; class QJDnsSharedDebugPrivate; class QJDnsSharedPrivate; class QJDnsSharedRequestPrivate; /** \brief Collects debugging information from QJDnsShared \note Iris users should utilize NetNames for DNS capabilities, not QJDnsSharedDebug. See the QJDnsShared documentation for more information. QJDnsSharedDebug is used to collect debugging information from one or many QJDnsShared objects. To use it, simply create it and pass it to QJDnsShared::setDebug(). Example use: \code QJDnsSharedDebug *db = new QJDnsSharedDebug; connect(db, SIGNAL(debugLinesReady(QStringList)), SLOT(db_debugLinesReady(QStringList))); QJDnsShared *jdnsShared1 = new QJDnsShared(QJDnsShared::UnicastInternet); jdnsShared1->setDebug(db, "U"); QJDnsShared *jdnsShared2 = new QJDnsShared(QJDnsShared::UnicastLocal); jdnsShared2->setDebug(db, "L"); ... void db_debugLinesReady(const QStringList &lines) { foreach(QString line, lines) printf("%s\n", qPrintable(line)); } \endcode QJDnsShared reports debug lines with the name and interface number prepended to each line. For example, if there is debug information to report about the second interface added to \a jdnsShared2 in the above example, the lines would be prepended with "L1: ". Do not destroy QJDnsSharedDebug until all of the QJDnsShared objects associated with it have been destroyed. \sa QJDnsShared QJDnsSharedRequest */ class JDNS_EXPORT QJDnsSharedDebug : public QObject { Q_OBJECT public: /** \brief Constructs a new object with the given \a parent */ QJDnsSharedDebug(QObject *parent = 0); /** \brief Destroys the object */ ~QJDnsSharedDebug(); /** \brief Read the available debug information Debug information is reported as a series of lines. The lines are of reasonable length, and so if you're storing a backlog of the most recent debug information, it should be safe to make the cut-off point based on lines. \sa readyRead */ QStringList readDebugLines(); signals: /** \brief Emitted when there is debug information to report \sa readDebugLines */ void readyRead(); private: friend class QJDnsShared; friend class QJDnsSharedPrivate; friend class QJDnsSharedDebugPrivate; QJDnsSharedDebugPrivate *d; }; /** \brief Performs a DNS operation using QJDnsShared \note Iris users should utilize NetNames for DNS capabilities, not QJDnsSharedRequest. See the QJDnsShared documentation for more information. QJDnsSharedRequest is used to perform DNS operations on a QJDnsShared object. Many requests may be performed simultaneously, such that a single QJDnsShared object can be "shared" across the application. Please see the QJDnsShared documentation for more complete information about how the overall system works. Call query() to perform a query. Call publish() (or publishUpdate()) to make DNS records available on the local network (QJDnsShared::Multicast mode only). When the operation has something to report, the resultsReady() signal is emitted. Call success() to determine the status of the operation. If success() returns false, then the operation has failed and the reason for the failure can be determined with error(). If success() returns true, then the meaning differs depending on the type of operation being performed:
  • For QJDnsShared::UnicastInternet and QJDnsShared::UnicastLocal modes, call results() to obtain the records obtained by the query. In these modes, resultsReady() is only emitted once, at which point the operation is no longer active.
  • For QJDnsShared::Multicast, operations are long-lived. Query operations never timeout, and resultsReady() may be emitted multiple times. In order to stop the query, either call cancel() or destroy the QJDnsSharedRequest object. Similarly, publishing is long-lived. The record stays published as long as the QJDnsSharedRequest has not been cancelled or destroyed.
Here is how you might look up an A record: \code QJDnsSharedRequest *req = new QJDnsSharedRequest(jdnsShared); connect(req, SIGNAL(resultsReady()), SLOT(req_resultsReady())); req->query("psi-im.org", QJDns::A); ... void req_resultsReady() { if(req->success()) { // print all of the IP addresses obtained QList results = req->results(); foreach(QJDns::Record r, results) { if(r.type == QJDns::A) printf("%s\n", qPrintable(r.address.toString()); } } else printf("Error resolving!\n"); } \endcode Here is an example of publishing a record: \code QJDnsSharedRequest *pub = new QJDnsSharedRequest(jdnsShared); connect(pub, SIGNAL(resultsReady()), SLOT(pub_resultsReady())); // let's publish an A record QJDns::Record rec; rec.owner = "SomeComputer.local."; rec.type = QJDns::A; rec.ttl = 120; rec.haveKnown = true; rec.address = QHostAddress("192.168.0.32"); pub->publish(QJDns::Unique, rec); ... void pub_resultsReady() { if(pub->success()) printf("Record published\n"); else printf("Error publishing!\n"); } \endcode To update an existing record, use publishUpdate(): \code // the IP address of the host changed, so make a new record QJDns::Record rec; rec.owner = "SomeComputer.local."; rec.type = QJDns::A; rec.ttl = 120; rec.haveKnown = true; rec.address = QHostAddress("192.168.0.64"); // update it pub->publishUpdate(rec); \endcode As a special exception, the address value can be left unspecified for A and Aaaa record types, which tells QJDnsShared to substitute the address value with the address of whatever interfaces the record gets published on. This is the preferred way to publish the IP address of your own machine, and in fact it is the only way to do so if you have multiple interfaces, because there will likely be a different IP address value for each interface (the record resolves to a different answer depending on which interface a query comes from). \code // let's publish our own A record QJDns::Record rec; rec.owner = "MyComputer.local."; rec.type = QJDns::A; rec.ttl = 120; rec.haveKnown = true; rec.address = QHostAddress(); pub->publish(QJDns::Unique, rec); \endcode When you want to unpublish, call cancel() or destroy the QJDnsSharedRequest. \sa QJDnsShared */ class JDNS_EXPORT QJDnsSharedRequest : public QObject { Q_OBJECT public: /** \brief Operation type */ enum Type { Query, ///< Query operation, initiated by query() Publish ///< Publish operation, initiated by publish() or publishUpdate() }; /** \brief Request error */ enum Error { ErrorNoNet, ///< There are no available network interfaces to operate on. This happens if QJDnsShared::addInterface() was not called. ErrorGeneric, ///< Generic error during the operation. ErrorNXDomain, ///< The name looked up does not exist. ErrorTimeout, ///< The operation timed out. ErrorConflict ///< Attempt to publish an already published unique record. }; /** \brief Constructs a new object with the given \a jdnsShared and \a parent */ QJDnsSharedRequest(QJDnsShared *jdnsShared, QObject *parent = 0); /** \brief Destroys the object If there is an active operation, it is cancelled. */ ~QJDnsSharedRequest(); /** \brief The type of operation being performed */ Type type(); /** \brief Perform a query operation */ void query(const QByteArray &name, int type); /** \brief Perform a publish operation */ void publish(QJDns::PublishMode m, const QJDns::Record &record); /** \brief Update a record that is currently published */ void publishUpdate(const QJDns::Record &record); /** \brief Cancels the current operation */ void cancel(); /** \brief Indicates whether or not the operation was successful */ bool success() const; /** \brief Returns the reason for error */ Error error() const; /** \brief Returns the results of the operation */ QList results() const; signals: /** \brief Indicates that the operation has something to report After receiving this signal, call success() to check on the status of the operation, followed by results() or error() as appropriate. */ void resultsReady(); private: friend class QJDnsShared; friend class QJDnsSharedPrivate; friend class QJDnsSharedRequestPrivate; QJDnsSharedRequestPrivate *d; }; /** \brief Abstraction layer on top of QJDns \note Iris users should utilize NetNames for DNS capabilities, not QJDnsShared. QJDnsShared is provided for non-Iris users (and it is also used internally by NetNames). To use QJDnsShared by itself, simply drop the jdnsshared.h and jdnsshared.cpp files, along with JDNS, into your project. It is not a full replacement for Qt's Q3Dns, as some tasks are left to you, but it covers most of it. QJDns supports everything a typical application should ever need in DNS. However, it is expected that modern applications will need to maintain multiple QJDns instances at the same time, and this is where things can get complicated. For example, most applications will want at least two QJDns instances: one for IPv4 unicast and one for IPv6 unicast. A single QJDnsShared object encapsulates multiple instances of QJDns that are related. For example, an IPv4 unicast instance and an IPv6 unicast instance could be coupled within QJDnsShared. Then, when a unicast operation is performed on the QJDnsShared object, both underlying instances will be queried as appropriate. The application would not need to perform two resolutions itself, nor deal with any related complexity. Further, individual operations are performed using a separate class called QJDnsSharedRequest, eliminating the need for the application to directly interface with a central QJDns object or track integer handles. This makes it easier for individual parts of the application to "share" the same instance (or set of instances) of QJDns, hence the name. QJDnsShared is a thin abstraction. QJDns subtypes (e.g. QJDns::Type, QJDns::Record, etc) are still used with QJDnsShared. Because of the duplication of documentation effort between NetNames and QJDns, there is no formal documentation for QJDns. Users of QJDnsShared will need to read qjdns.h, although a basic explanation of the elements can be found below. Types:
QJDns::TypeThis is a convenience enumeration for common DNS record types. For example: A, Aaaa, Srv, etc. The values directly map to the integer values of the DNS protocol (e.g. Srv = 33). See qjdns.h for all of the types and values.
QJDns::RecordThis class holds a DNS record. The main fields are type (integer type, probably something listed in QJDns::Type), rdata (QByteArray of the record value), and haveKnown (boolean to indicate if a decoded form of the record value is also available). See qjdns.h for the possible known fields. You will most-likely always work with known types. Received records that have a type listed in QJDns::Type are guaranteed to be known and will provide a decoded value. If you are creating a record for publishing, you will need to set owner, ttl, and type. If the type to be published is listed in QJDns::Type, then you will need to set haveKnown to true and set the known fields as appropriate, otherwise you need to set rdata. You do not need to supply an encoded form in rdata for known types, it can be left empty in that case.
QJDns::PublishModeThis is for Multicast DNS, and can either be Unique or Shared. A shared record can be published by multiple owners (for example, a "_ssh._tcp.local." PTR record might resolve to many different SSH services owned by different machines). A unique record can only have one owner (for example, a "mycomputer.local." A record would resolve to the IP address of the machine that published it). Attempting to publish a record on a network where a unique record is already present will result in a conflict error.
Functions:
QJDns::detectPrimaryMulticast()Detects a multicast interface. Pass QHostAddress::Any or QHostAddress::AnyIPv6, depending on which type of interface is desired.
To use QJDnsShared, first create an instance of it, set it up by calling addInterface() as necessary, and then use QJDnsSharedRequest to perform operations on it. Here is an example of how to create and set up a QJDnsShared object for typical DNS resolution: \code // construct QJDnsShared *dns = new QJDnsShared(QJDnsShared::UnicastInternet); // add IPv4 and IPv6 interfaces dns->addInterface(QHostAddress::Any); dns->addInterface(QHostAddress::AnyIPv6); // at this point, the object is ready for operation \endcode Perform a resolution like this: \code QJDnsSharedRequest *req = new QJDnsSharedRequest(dns); connect(req, SIGNAL(resultsReady()), SLOT(req_resultsReady())); req->query("psi-im.org", QJDns::A); ... void req_resultsReady() { if(req->success()) { // print all of the IP addresses obtained QList results = req->results(); foreach(QJDns::Record r, results) { if(r.type == QJDns::A) printf("%s\n", qPrintable(r.address.toString()); } } else printf("Error resolving!\n"); } \endcode It is important to filter the results as shown in the above example. QJDns guarantees at least one record in the results will be of the type queried for, but there may also be CNAME records present (of course, if the query was for a CNAME type, then the results will only be CNAME records). The recommended approach is to simply filter for the record types desired, as shown, rather than single out CNAME specifically. When you are finished with a QJDnsShared object, it should be shut down before deleting: \code connect(dns, SIGNAL(shutdownFinished()), SLOT(dns_shutdownFinished())); dns->shutdown(); ... void dns_shutdownFinished() { delete dns; } \endcode Setting up QJDnsShared for UnicastLocal and Multicast mode is done the same way as with UnicastInternet. For example, here is how Multicast mode could be set up: \code // construct QJDnsShared *dns = new QJDnsShared(QJDnsShared::Multicast); // add IPv4 interface QHostAddress addr = QJDns::detectPrimaryMulticast(QHostAddress::Any); dns->addInterface(addr); // at this point, the object is ready for operation \endcode QJDnsShared provides a lot of functionality, but certain aspects of DNS are deemed out of its scope. Below are the responsibilities of the user of QJDnsShared, if a more complete DNS behavior is desired:
  • Querying for several "qualified" names. You should first query for the name as provided, and if that fails then query for name + ".domain" (for every domain the computer is in). See domains().
  • Detecting for ".local" in the name to be queried, and using that to decide whether to query via Multicast/UnicastLocal or UnicastInternet.
  • For zeroconf/Bonjour, keep in mind that QJDnsShared only provides low-level record queries. DNS-SD and any higher layers would be your job.
Using a custom DNS implementation, such as QJDnsShared, has the drawback that it is difficult to take advantage of platform-specific features (for example, an OS-wide DNS cache or LDAP integration). An application strategy for normal DNS should probably be:
  • If an A or AAAA record is desired, use a native lookup.
  • Else, if the platform has advanced DNS features already (ie, res_query), use those.
  • Else, use QJDnsShared.
For Multicast DNS, awareness of the platform is doubly important. There should only be one Multicast DNS "Responder" per computer, and using QJDnsShared in Multicast mode at the same time could result in a conflict. An application strategy for Multicast DNS should be:
  • If the platform has a Multicast DNS daemon installed already, use it somehow.
  • Else, use QJDnsShared.
\sa QJDnsSharedRequest */ class JDNS_EXPORT QJDnsShared : public QObject { Q_OBJECT public: /** \brief The mode to operate in */ enum Mode { /** For regular DNS resolution. In this mode, lookups are performed on all interfaces, and the first returned result is used. */ UnicastInternet, /** Perform regular DNS resolution using the Multicast DNS address. This is used to resolve large and/or known Multicast DNS names without actually multicasting anything. */ UnicastLocal, /** Multicast DNS querying and publishing. \note For Multicast mode, QJDnsShared supports up to one interface for each IP version (e.g. one IPv4 interface and one IPv6 interface), and expects the default/primary multicast interface for that IP version to be used. */ Multicast }; /** \brief Constructs a new object with the given \a mode and \a parent */ QJDnsShared(Mode mode, QObject *parent = 0); /** \brief Destroys the object */ ~QJDnsShared(); /** \brief Sets the debug object to report to If a debug object is set using this function, then QJDnsShared will send output text to it, prefixing each line with \a name. */ void setDebug(QJDnsSharedDebug *db, const QString &name); /** \brief Adds an interface to operate on For UnicastInternet and UnicastLocal, these will almost always be QHostAddress::Any or QHostAddress::AnyIPv6 (operate on the default interface for IPv4 or IPv6, respectively). For Multicast, it is expected that the default/primary multicast interface will be used here. Do not pass QHostAddress::Any (or AnyIPv6) with Multicast mode. Returns true if the interface was successfully added, otherwise returns false. */ bool addInterface(const QHostAddress &addr); /** \brief Removes a previously-added interface */ void removeInterface(const QHostAddress &addr); /** \brief Shuts down the object This operation primarily exists for Multicast mode, so that any published records have a chance to be unpublished. If the QJDnsShared object is simply deleted without performing a shutdown, then published records will linger on the network until their TTLs expire. When shutdown is complete, the shutdownFinished() signal will be emitted. */ void shutdown(); /** \brief The domains to search in You should perform a separate resolution for every domain configured on this machine. */ static QList domains(); /** \brief Performs a blocking shutdown of many QJDnsShared instances This function is a convenient way to shutdown multiple QJDnsShared instances synchronously. The internal shutdown procedure uses no more than a few cycles of the eventloop, so it should be safe to call without worry of the application being overly stalled. This function takes ownership of the instances passed to it, and will delete them upon completion. It is worth noting that this function is implemented without the use of a nested eventloop. All of the QJDnsShared instances are moved into a temporary thread to perform the shutdown procedure, which should not cause any unexpected behavior in the current thread. \code QList list; list += jdnsShared_unicast; list += jdnsShared_multicast; QJDnsShared::waitForShutdown(list); // collect remaining debug information QStringList finalDebugLines = jdnsSharedDebug.readDebugLines(); \endcode */ static void waitForShutdown(const QList &instances); signals: /** \brief Indicates the object has been shut down */ void shutdownFinished(); private: friend class QJDnsSharedRequest; friend class QJDnsSharedPrivate; QJDnsSharedPrivate *d; }; #endif // QJDNSSHARED_H psi-plus-snapshots-1.4.1456/iris/src/jdns/jdns.pc.in000066400000000000000000000005121370065651000221330ustar00rootroot00000000000000prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=${prefix} libdir=@LIB_INSTALL_DIR@ includedir=@INCLUDE_INSTALL_DIR@/jdns Name: jdns Description: Simple DNS implementation can perform normal DNS queries Version: @JDNS_LIB_MAJOR_VERSION@.@JDNS_LIB_MINOR_VERSION@.@JDNS_LIB_PATCH_VERSION@ Libs: -L${libdir} -ljdns Cflags: -I${includedir} psi-plus-snapshots-1.4.1456/iris/src/jdns/jdns.pri000066400000000000000000000014731370065651000217250ustar00rootroot00000000000000# qmake project include file QT *= network DEFINES += JDNS_STATIC INCLUDEPATH += $$PWD/include/jdns windows:{ LIBS += -lWs2_32 -lAdvapi32 } unix:{ #QMAKE_CFLAGS += -pedantic } HEADERS += \ $$PWD/src/jdns/jdns_packet.h \ $$PWD/src/jdns/jdns_mdnsd.h \ $$PWD/src/jdns/jdns_p.h \ $$PWD/include/jdns/jdns.h \ $$PWD/src/qjdns/qjdns_sock.h \ $$PWD/src/qjdns/qjdns_p.h \ $$PWD/src/qjdns/qjdnsshared_p.h \ $$PWD/include/jdns/qjdns.h \ $$PWD/include/jdns/qjdnsshared.h \ $$PWD/include/jdns/jdns_export.h SOURCES += \ $$PWD/src/jdns/jdns_util.c \ $$PWD/src/jdns/jdns_packet.c \ $$PWD/src/jdns/jdns_mdnsd.c \ $$PWD/src/jdns/jdns_sys.c \ $$PWD/src/jdns/jdns.c \ $$PWD/src/qjdns/qjdns_sock.cpp \ $$PWD/src/qjdns/qjdns.cpp \ $$PWD/src/qjdns/qjdnsshared.cpp psi-plus-snapshots-1.4.1456/iris/src/jdns/package.sh000077500000000000000000000005011370065651000221740ustar00rootroot00000000000000#!/bin/sh set -e if [ $# -lt 1 ]; then echo "usage: $0 [version]" exit 1 fi VERSION=$1 mkdir -p build/jdns-$VERSION cp -a .gitignore COPYING README.md TODO CMakeLists.txt cmake_uninstall.cmake.in jdns.* qjdns.* JDns* QJDns* include src tools build/jdns-$VERSION cd build tar jcvf jdns-$VERSION.tar.bz2 jdns-$VERSION psi-plus-snapshots-1.4.1456/iris/src/jdns/qjdns.pc.in000066400000000000000000000005151370065651000223170ustar00rootroot00000000000000prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=${prefix} libdir=@LIB_INSTALL_DIR@ includedir=@INCLUDE_INSTALL_DIR@/jdns Name: qjdns Description: Qt bindings for JDNS Version: @JDNS_LIB_MAJOR_VERSION@.@JDNS_LIB_MINOR_VERSION@.@JDNS_LIB_PATCH_VERSION@ Requires: jdns @QJDns_QT_PC_VERSION@ Libs: -L${libdir} -lqjdns Cflags: -I${includedir} psi-plus-snapshots-1.4.1456/iris/src/jdns/src/000077500000000000000000000000001370065651000210355ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/jdns/src/CMakeLists.txt000066400000000000000000000001241370065651000235720ustar00rootroot00000000000000add_subdirectory(jdns) if(BUILD_QJDNS) add_subdirectory(qjdns) endif(BUILD_QJDNS) psi-plus-snapshots-1.4.1456/iris/src/jdns/src/jdns/000077500000000000000000000000001370065651000217735ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/jdns/src/jdns/CMakeLists.txt000066400000000000000000000031561370065651000245400ustar00rootroot00000000000000set(jdns_SRCS jdns.c jdns_mdnsd.c jdns_packet.c jdns_sys.c jdns_util.c ) set(jdns_PUBLIC_HEADERS "${JDNS_INCLUDEDIR}/jdns.h" "${JDNS_INCLUDEDIR}/jdns_export.h" ) set(jdns_HEADERS jdns_packet.h jdns_mdnsd.h jdns_p.h ) add_library(jdns ${jdns_SRCS} ${jdns_HEADERS} ${jdns_PUBLIC_HEADERS}) if(WIN32) target_link_libraries(jdns ws2_32 advapi32) endif(WIN32) if(HAIKU) target_link_libraries(jdns network) endif(HAIKU) if(NOT android) set_target_properties(jdns PROPERTIES VERSION ${JDNS_LIB_MAJOR_VERSION}.${JDNS_LIB_MINOR_VERSION}.${JDNS_LIB_PATCH_VERSION} SOVERSION ${JDNS_LIB_MAJOR_VERSION} ) endif() set_target_properties(jdns PROPERTIES DEFINE_SYMBOL JDNS_MAKEDLL PUBLIC_HEADER "${jdns_PUBLIC_HEADERS}" # FRAMEWORK ${OSX_FRAMEWORK} ) install(TARGETS jdns EXPORT jdns-export LIBRARY DESTINATION ${LIB_INSTALL_DIR} RUNTIME DESTINATION ${BIN_INSTALL_DIR} ARCHIVE DESTINATION ${LIB_INSTALL_DIR} # FRAMEWORK DESTINATION ${FRAMEWORK_INSTALL_DIR} PUBLIC_HEADER DESTINATION "${INCLUDE_INSTALL_DIR}/jdns" ) if(MSVC) get_target_property(LOCATION jdns LOCATION_DEBUG) string(REGEX REPLACE "\\.[^.]*$" ".pdb" LOCATION "${LOCATION}") install(FILES ${LOCATION} DESTINATION ${LIB_INSTALL_DIR} CONFIGURATIONS Debug) get_target_property(LOCATION jdns LOCATION_RELWITHDEBINFO) string(REGEX REPLACE "\\.[^.]*$" ".pdb" LOCATION "${LOCATION}") install(FILES ${LOCATION} DESTINATION ${LIB_INSTALL_DIR} CONFIGURATIONS RelWithDebInfo) endif(MSVC) psi-plus-snapshots-1.4.1456/iris/src/jdns/src/jdns/jdns.c000066400000000000000000002677361370065651000231220ustar00rootroot00000000000000/* * Copyright (C) 2005-2008 Justin Karneges * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "jdns_mdnsd.h" #include "jdns_p.h" #include "jdns_packet.h" #include #define JDNS_UDP_UNI_OUT_MAX 512 #define JDNS_UDP_UNI_IN_MAX 16384 #define JDNS_UDP_MUL_OUT_MAX 9000 #define JDNS_UDP_MUL_IN_MAX 16384 // cache no more than 7 days #define JDNS_TTL_MAX (86400 * 7) #define JDNS_CACHE_MAX 16384 #define JDNS_CNAME_MAX 16 #define JDNS_QUERY_MAX 4096 //---------------------------------------------------------------------------- // util //---------------------------------------------------------------------------- // declare this here, but implement it later after we define jdns_session_t static void _debug_line(jdns_session_t *s, const char *format, ...); static unsigned char _hex_nibble(unsigned char c) { if(c <= 9) return '0' + c; else if(c <= 15) return 'a' + (c - 10); else return '?'; } static void _hex_byte(unsigned char c, unsigned char *dest) { dest[0] = _hex_nibble((unsigned char)(c >> 4)); dest[1] = _hex_nibble((unsigned char)(c & 0x0f)); } static jdns_string_t *_make_printable(const unsigned char *str, int size) { unsigned char *buf; int n, i; jdns_string_t *out; if(size == 0) { out = jdns_string_new(); jdns_string_set_cstr(out, ""); return out; } // make room for the largest possible result buf = (unsigned char *)malloc(size * 4); i = 0; for(n = 0; n < size; ++n) { unsigned char c = str[n]; if(c == '\\') { buf[i++] = '\\'; buf[i++] = '\\'; } else if(c >= 0x20 && c < 0x7f) { buf[i++] = c; } else { buf[i++] = '\\'; buf[i++] = 'x'; _hex_byte(c, buf + i); i += 2; } } out = jdns_string_new(); jdns_string_set(out, buf, i); free(buf); return out; } static jdns_string_t *_make_printable_str(const jdns_string_t *str) { return _make_printable(str->data, str->size); } static jdns_string_t *_make_printable_cstr(const char *str) { return _make_printable((const unsigned char *)str, strlen(str)); } static unsigned char *_fix_input(const unsigned char *in) { unsigned char *out; int len; // truncate len = _ustrlen(in); if(len > 254) len = 254; // add a dot to the end if needed if(in[len - 1] != '.' && len < 254) { out = (unsigned char *)malloc(len + 2); // a dot and a zero memcpy(out, in, len); out[len] = '.'; out[len+1] = 0; ++len; } else { out = (unsigned char *)malloc(len + 1); // a zero memcpy(out, in, len); out[len] = 0; } return out; } static const char *_qtype2str(int qtype) { const char *str; switch(qtype) { case JDNS_RTYPE_A: str = "A"; break; case JDNS_RTYPE_AAAA: str = "AAAA"; break; case JDNS_RTYPE_MX: str = "MX"; break; case JDNS_RTYPE_SRV: str = "SRV"; break; case JDNS_RTYPE_CNAME: str = "CNAME"; break; case JDNS_RTYPE_PTR: str = "PTR"; break; case JDNS_RTYPE_TXT: str = "TXT"; break; case JDNS_RTYPE_HINFO: str = "HINFO"; break; case JDNS_RTYPE_NS: str = "NS"; break; case JDNS_RTYPE_ANY: str = "ANY"; break; default: str = ""; break; } return str; } static int _cmp_rdata(const jdns_rr_t *a, const jdns_rr_t *b) { if(a->rdlength != b->rdlength) return 0; if(memcmp(a->rdata, b->rdata, a->rdlength) != 0) return 0; return 1; } static int _cmp_rr(const jdns_rr_t *a, const jdns_rr_t *b) { if(a->type != b->type) return 0; if(!jdns_domain_cmp(a->owner, b->owner)) return 0; switch(a->type) { case JDNS_RTYPE_A: if(!jdns_address_cmp(a->data.address, b->data.address)) return 0; break; case JDNS_RTYPE_AAAA: if(!_cmp_rdata(a, b)) return 0; break; case JDNS_RTYPE_MX: // unsupported return 0; case JDNS_RTYPE_SRV: if(a->data.server->port != b->data.server->port || a->data.server->priority != b->data.server->priority || a->data.server->weight != b->data.server->weight || !jdns_domain_cmp(a->data.server->name, b->data.server->name) ) return 0; break; case JDNS_RTYPE_CNAME: if(!jdns_domain_cmp(a->data.name, b->data.name)) return 0; break; case JDNS_RTYPE_PTR: if(!jdns_domain_cmp(a->data.name, b->data.name)) return 0; break; case JDNS_RTYPE_TXT: if(!_cmp_rdata(a, b)) return 0; break; case JDNS_RTYPE_HINFO: if(!_cmp_rdata(a, b)) return 0; break; case JDNS_RTYPE_NS: // unsupported return 0; default: if(!_cmp_rdata(a, b)) return 0; break; } return 1; } static jdns_response_t *_packet2response(const jdns_packet_t *packet, const unsigned char *qname, int qtype, int classmask) { int n; jdns_response_t *r; r = jdns_response_new(); for(n = 0; n < packet->answerRecords->count; ++n) { jdns_packet_resource_t *res = (jdns_packet_resource_t *)packet->answerRecords->item[n]; jdns_rr_t *rr; int put_in_answer; if((res->qclass & classmask) != 0x0001) continue; rr = jdns_rr_from_resource(res, packet); if(!rr) continue; // if qname is set, restrict answers to those that match // the question put_in_answer = 1; if(qname) { // name must match. type must either match or be CNAME, // unless the query was for any type if((qtype != JDNS_RTYPE_ANY && res->qtype != qtype && res->qtype != JDNS_RTYPE_CNAME) || !jdns_domain_cmp(res->qname->data, qname)) { // put unusable records in additional section put_in_answer = 0; } } if(put_in_answer) jdns_response_append_answer(r, rr); else jdns_response_append_additional(r, rr); jdns_rr_delete(rr); } for(n = 0; n < packet->authorityRecords->count; ++n) { jdns_packet_resource_t *res = (jdns_packet_resource_t *)packet->authorityRecords->item[n]; jdns_rr_t *rr; if((res->qclass & classmask) != 0x0001) continue; rr = jdns_rr_from_resource(res, packet); if(!rr) continue; jdns_response_append_authority(r, rr); jdns_rr_delete(rr); } for(n = 0; n < packet->additionalRecords->count; ++n) { jdns_packet_resource_t *res = (jdns_packet_resource_t *)packet->additionalRecords->item[n]; jdns_rr_t *rr; if((res->qclass & classmask) != 0x0001) continue; rr = jdns_rr_from_resource(res, packet); if(!rr) continue; jdns_response_append_additional(r, rr); jdns_rr_delete(rr); } return r; } // size must be 1 to 16 static void _print_hexdump_line(jdns_session_t *s, const unsigned char *buf, int size) { char line[67]; // 3 * 16 + 2 + 16 + zero byte int n; memset(line, ' ', 66); line[66] = 0; if(size > 16) size = 16; for(n = 0; n < size; ++n) { unsigned char c = buf[n]; _hex_byte(c, ((unsigned char *)line) + n * 3); line[n * 3 + 2] = ' '; if(c >= 0x20 && c < 0x7f) line[50 + n] = c; else line[50 + n] = '.'; } _debug_line(s, " %s", line); } static void _print_hexdump(jdns_session_t *s, const unsigned char *buf, int size) { int n; int lines; int at, len; lines = size / 16; if(size % 16 != 0) ++lines; for(n = 0; n < lines; ++n) { at = n * 16; if(at + 16 <= size) len = 16; else len = size - at; _print_hexdump_line(s, buf + at, len); } } static void _print_packet_resources(jdns_session_t *s, const jdns_list_t *reslist) { int n; for(n = 0; n < reslist->count; ++n) { jdns_packet_resource_t *r; jdns_string_t *str; r = (jdns_packet_resource_t *)reslist->item[n]; str = _make_printable_str(r->qname); _debug_line(s, " %04x/%04x [%s] ttl=%ld size=%d", r->qclass, r->qtype, str->data, r->ttl, r->rdlength); jdns_string_delete(str); } } static void _print_packet(jdns_session_t *s, const jdns_packet_t *packet) { int n; _debug_line(s, "Packet:"); _debug_line(s, " id: %d", packet->id); _debug_line(s, " opts: qr:%d, opcode:%d, aa:%d, tc:%d, rd:%d, ra:%d, z:%d, rcode:%d", packet->opts.qr, packet->opts.opcode, packet->opts.aa, packet->opts.tc, packet->opts.rd, packet->opts.ra, packet->opts.z, packet->opts.rcode); _debug_line(s, " qdcount=%d, ancount=%d, nscount=%d, arcount=%d", packet->qdcount, packet->ancount, packet->nscount, packet->arcount); if(packet->questions->count > 0) { _debug_line(s, " questions: (class/type name)"); for(n = 0; n < packet->questions->count; ++n) { jdns_packet_question_t *q; jdns_string_t *str; q = (jdns_packet_question_t *)packet->questions->item[n]; str = _make_printable_str(q->qname); _debug_line(s, " %04x/%04x [%s]", q->qclass, q->qtype, str->data); jdns_string_delete(str); } } if(packet->answerRecords->count > 0) { _debug_line(s, " answerRecords: (class/type owner ttl size)"); _print_packet_resources(s, packet->answerRecords); } if(packet->authorityRecords->count > 0) { _debug_line(s, " authorityRecords: (class/type owner ttl size)"); _print_packet_resources(s, packet->authorityRecords); } if(packet->additionalRecords->count > 0) { _debug_line(s, " additionalRecords: (class/type owner ttl size)"); _print_packet_resources(s, packet->additionalRecords); } } static void _print_rr(jdns_session_t *s, const jdns_rr_t *rr, const unsigned char *owner) { int n; jdns_string_t *ownerstr; ownerstr = jdns_string_new(); // not the expected owner? if(!owner || !jdns_domain_cmp(owner, rr->owner)) { unsigned char *buf; jdns_string_t *str = _make_printable_cstr((const char *)rr->owner); buf = (unsigned char *)malloc(str->size + 3); // " [%s]" buf[0] = ' '; buf[1] = '['; memcpy(buf + 2, str->data, str->size); buf[str->size + 2] = ']'; jdns_string_set(ownerstr, buf, str->size + 3); jdns_string_delete(str); free(buf); } else jdns_string_set_cstr(ownerstr, ""); switch(rr->type) { case JDNS_RTYPE_A: { _debug_line(s, " A: [%s] (ttl=%d)%s", rr->data.address->c_str, rr->ttl, ownerstr->data); break; } case JDNS_RTYPE_AAAA: { _debug_line(s, " AAAA: [%s] (ttl=%d)%s", rr->data.address->c_str, rr->ttl, ownerstr->data); break; } case JDNS_RTYPE_MX: { jdns_string_t *str = _make_printable_cstr((const char *)rr->data.server->name); _debug_line(s, " MX: [%s] priority=%d (ttl=%d)%s", str->data, rr->data.server->priority, rr->ttl, ownerstr->data); jdns_string_delete(str); break; } case JDNS_RTYPE_SRV: { jdns_string_t *str = _make_printable_cstr((const char *)rr->data.server->name); _debug_line(s, " SRV: [%s] port=%d priority=%d weight=%d (ttl=%d)%s", str->data, rr->data.server->port, rr->data.server->priority, rr->data.server->weight, rr->ttl, ownerstr->data); jdns_string_delete(str); break; } case JDNS_RTYPE_CNAME: { jdns_string_t *str = _make_printable_cstr((const char *)rr->data.name); _debug_line(s, " CNAME: [%s] (ttl=%d)%s", str->data, rr->ttl, ownerstr->data); jdns_string_delete(str); break; } case JDNS_RTYPE_PTR: { jdns_string_t *str = _make_printable_cstr((const char *)rr->data.name); _debug_line(s, " PTR: [%s] (ttl=%d)%s", str->data, rr->ttl, ownerstr->data); jdns_string_delete(str); break; } case JDNS_RTYPE_TXT: { _debug_line(s, " TXT: count=%d (ttl=%d)%s", rr->data.texts->count, rr->ttl, ownerstr->data); for(n = 0; n < rr->data.texts->count; ++n) { jdns_string_t *str, *pstr; str = rr->data.texts->item[n]; pstr = _make_printable_str(str); _debug_line(s, " len=%d [%s]", str->size, pstr->data); jdns_string_delete(pstr); } break; } case JDNS_RTYPE_HINFO: { jdns_string_t *cpu, *os; cpu = _make_printable_str(rr->data.hinfo.cpu); os = _make_printable_str(rr->data.hinfo.os); _debug_line(s, " HINFO: [%s] [%s] (ttl=%d)%s", cpu->data, os->data, rr->ttl, ownerstr->data); jdns_string_delete(cpu); jdns_string_delete(os); break; } case JDNS_RTYPE_NS: { jdns_string_t *str = _make_printable_cstr((const char *)rr->data.name); _debug_line(s, " NS: [%s] (ttl=%d)%s", str->data, rr->ttl, ownerstr->data); jdns_string_delete(str); break; } default: { _debug_line(s, " Unknown (%d): %d bytes (ttl=%d)%s", rr->type, rr->rdlength, rr->ttl, ownerstr->data); break; } } jdns_string_delete(ownerstr); } static void _print_records(jdns_session_t *s, const jdns_response_t *r, const unsigned char *owner) { int n; _debug_line(s, "Records:"); _debug_line(s, " Answer Records: %d", r->answerCount); for(n = 0; n < r->answerCount; ++n) _print_rr(s, r->answerRecords[n], owner); _debug_line(s, " Authority Records: %d", r->authorityCount); for(n = 0; n < r->authorityCount; ++n) _print_rr(s, r->authorityRecords[n], owner); _debug_line(s, " Additional Records: %d", r->additionalCount); for(n = 0; n < r->additionalCount; ++n) _print_rr(s, r->additionalRecords[n], owner); } static int _min(int a, int b) { return (a < b) ? a : b; } //---------------------------------------------------------------------------- // jdns_event //---------------------------------------------------------------------------- jdns_event_t *jdns_event_new() { jdns_event_t *e = alloc_type(jdns_event_t); e->response = 0; return e; } void jdns_event_delete(jdns_event_t *e) { if(!e) return; jdns_response_delete(e->response); jdns_free(e); } //---------------------------------------------------------------------------- // jdns - internal types //---------------------------------------------------------------------------- typedef struct list_item { void (*dtor)(void *); } list_item_t; typedef struct list { int count; list_item_t **item; } list_t; static list_t *list_new() { list_t *l = alloc_type(list_t); l->count = 0; l->item = 0; return l; } static void list_delete(list_t *l) { int n; if(!l) return; for(n = 0; n < l->count; ++n) l->item[n]->dtor(l->item[n]); if(l->item) free(l->item); jdns_free(l); } static void list_insert(list_t *l, void *item, int pos) { list_item_t *i = (list_item_t *)item; if(!l->item) l->item = (list_item_t **)malloc(sizeof(list_item_t *)); else l->item = (list_item_t **)realloc(l->item, sizeof(list_item_t *) * (l->count + 1)); if(pos != -1) memmove(l->item + pos + 1, l->item + pos, (l->count - pos) * sizeof(list_item_t *)); else pos = l->count; l->item[pos] = i; ++l->count; } static void list_remove(list_t *l, void *item) { int n; list_item_t *i = (list_item_t *)item; int pos = -1; for(n = 0; n < l->count; ++n) { if(l->item[n] == i) { pos = n; break; } } if(pos == -1) return; i->dtor(i); if(l->count > 1) { memmove(l->item + pos, l->item + pos + 1, (l->count - pos - 1) * sizeof(list_item_t *)); --l->count; } else { free(l->item); l->item = 0; l->count = 0; } } typedef struct name_server { void (*dtor)(struct name_server *); int id; jdns_address_t *address; int port; } name_server_t; static void name_server_delete(name_server_t *ns); static name_server_t *name_server_new() { name_server_t *ns = alloc_type(name_server_t); ns->dtor = name_server_delete; ns->address = 0; return ns; } void name_server_delete(name_server_t *ns) { if(!ns) return; jdns_address_delete(ns->address); jdns_free(ns); } int _intarray_indexOf(int *array, int count, int val) { int n; for(n = 0; n < count; ++n) { if(array[n] == val) return n; } return -1; } int _intarray_add(int **array, int *count, int val) { int *p; if(!*array) p = (int *)malloc(sizeof(int)); else p = (int *)realloc(*array, sizeof(int) * (*count + 1)); if(!p) return 0; *array = p; (*array)[*count] = val; ++(*count); return 1; } void _intarray_remove(int **array, int *count, int pos) { int *p; if(*count > 1) { memmove(*array + pos, *array + pos + 1, (*count - pos - 1) * sizeof(int)); --(*count); p = (int *)realloc(*array, sizeof(int) * (*count)); if(p) *array = p; } else { free(*array); *array = 0; *count = 0; } } typedef struct query { void (*dtor)(struct query *); int id; // user request ids int req_ids_count; int *req_ids; // packet id int dns_id; // what we are looking up unsigned char *qname; int qtype; // how many transmission attempts we have done. note this // is not actually how many packets have been sent, since // it is possible for the first transmission to send many // at once. this variable lets us decide when to give up. // (idea taken from qdns). // set to -1 to deactivate (stop sending packets) int step; // which nameservers we've tried (stored as a list of ids) int servers_tried_count; int *servers_tried; // which servers we shouldn't try again int servers_failed_count; int *servers_failed; // flag to indicate whether or not we've tried all available // nameservers already. this means that all future // transmissions are likely repeats, and should be slowed // down. int retrying; // flag to indicate if we've received nxdomain as an error so far int nxdomain; // holds a timeout for the next step (time_start == -1 means no timer) int time_start; int time_next; // whether or not to look in the cache for this query int trycache; // cname subquerying. only cname_parent or cname_child may be set, // never both. int cname_chain_count; struct query *cname_parent; struct query *cname_child; // accumulates known multicast records to prevent duplicates jdns_response_t *mul_known; } query_t; void query_delete(query_t *q); query_t *query_new() { query_t *q = alloc_type(query_t); q->dtor = query_delete; q->req_ids_count = 0; q->req_ids = 0; q->qname = 0; q->servers_tried_count = 0; q->servers_tried = 0; q->servers_failed_count = 0; q->servers_failed = 0; q->nxdomain = 0; q->cname_chain_count = 0; q->cname_parent = 0; q->cname_child = 0; q->mul_known = 0; return q; } void query_delete(query_t *q) { if(!q) return; if(q->req_ids) free(q->req_ids); if(q->qname) free(q->qname); if(q->servers_tried) free(q->servers_tried); if(q->servers_failed) free(q->servers_failed); jdns_response_delete(q->mul_known); jdns_free(q); } int query_have_req_id(const query_t *q, int req_id) { if(_intarray_indexOf(q->req_ids, q->req_ids_count, req_id) != -1) return 1; return 0; } void query_add_req_id(query_t *q, int req_id) { _intarray_add(&q->req_ids, &q->req_ids_count, req_id); } void query_remove_req_id(query_t *q, int req_id) { int pos; pos = _intarray_indexOf(q->req_ids, q->req_ids_count, req_id); if(pos != -1) _intarray_remove(&q->req_ids, &q->req_ids_count, pos); } int query_server_tried(const query_t *q, int ns_id) { if(_intarray_indexOf(q->servers_tried, q->servers_tried_count, ns_id) != -1) return 1; return 0; } void query_add_server_tried(query_t *q, int ns_id) { _intarray_add(&q->servers_tried, &q->servers_tried_count, ns_id); } int query_server_failed(const query_t *q, int ns_id); void query_clear_servers_tried(query_t *q) { int n; // all failed servers must continue to be considered tried servers, so // only clear tried servers that haven't failed for(n = 0; n < q->servers_tried_count; ++n) { if(!query_server_failed(q, q->servers_tried[n])) { _intarray_remove(&q->servers_tried, &q->servers_tried_count, n); --n; // adjust position } } } int query_server_failed(const query_t *q, int ns_id) { if(_intarray_indexOf(q->servers_failed, q->servers_failed_count, ns_id) != -1) return 1; return 0; } void query_add_server_failed(query_t *q, int ns_id) { _intarray_add(&q->servers_failed, &q->servers_failed_count, ns_id); } void query_name_server_gone(query_t *q, int ns_id) { int pos; pos = _intarray_indexOf(q->servers_tried, q->servers_tried_count, ns_id); if(pos != -1) _intarray_remove(&q->servers_tried, &q->servers_tried_count, pos); pos = _intarray_indexOf(q->servers_failed, q->servers_failed_count, ns_id); if(pos != -1) _intarray_remove(&q->servers_failed, &q->servers_failed_count, pos); } typedef struct datagram { void (*dtor)(struct datagram *); int handle; jdns_address_t *dest_address; int dest_port; unsigned char *data; int size; // query association query_t *query; int query_send_type; // 0 == normal, 1 == first step send-all // name server association int ns_id; } datagram_t; void datagram_delete(datagram_t *a); datagram_t *datagram_new() { datagram_t *a = alloc_type(datagram_t); a->dtor = datagram_delete; a->dest_address = 0; a->data = 0; a->size = 0; a->query = 0; return a; } void datagram_delete(datagram_t *a) { if(!a) return; jdns_address_delete(a->dest_address); if(a->data) free(a->data); jdns_free(a); } typedef struct cache_item { void (*dtor)(struct cache_item *); unsigned char *qname; int qtype; int time_start; int ttl; jdns_rr_t *record; // if zero, nxdomain is assumed } cache_item_t; void cache_item_delete(cache_item_t *e); cache_item_t *cache_item_new() { cache_item_t *a = alloc_type(cache_item_t); a->dtor = cache_item_delete; a->qname = 0; a->record = 0; return a; } void cache_item_delete(cache_item_t *a) { if(!a) return; if(a->qname) free(a->qname); jdns_rr_delete(a->record); jdns_free(a); } typedef struct event { void (*dtor)(struct event *); jdns_event_t *event; } event_t; void event_delete(event_t *e); event_t *event_new() { event_t *e = alloc_type(event_t); e->dtor = event_delete; e->event = 0; return e; } void event_delete(event_t *e) { if(!e) return; jdns_event_delete(e->event); jdns_free(e); } typedef struct published_item { void (*dtor)(struct published_item *); int id; int mode; unsigned char *qname; int qtype; mdnsdr rec; jdns_rr_t *rr; } published_item_t; void published_item_delete(published_item_t *a); published_item_t *published_item_new() { published_item_t *a = alloc_type(published_item_t); a->dtor = published_item_delete; a->qname = 0; a->rec = 0; a->rr = 0; return a; } void published_item_delete(published_item_t *a) { if(!a) return; if(a->qname) free(a->qname); jdns_rr_delete(a->rr); jdns_free(a); } //---------------------------------------------------------------------------- // jdns //---------------------------------------------------------------------------- struct jdns_session { jdns_callbacks_t cb; int mode; int shutdown; int next_qid; int next_req_id; int last_time; int next_timer; int next_name_server_id; int handle; int handle_readable, handle_writable; int port; list_t *name_servers; list_t *queries; list_t *outgoing; list_t *events; list_t *cache; // for blocking req_ids from reuse until user explicitly releases int do_hold_req_ids; int held_req_ids_count; int *held_req_ids; // mdns mdnsd mdns; list_t *published; jdns_address_t *maddr; }; jdns_session_t *jdns_session_new(jdns_callbacks_t *callbacks) { jdns_session_t *s = alloc_type(jdns_session_t); memcpy(&s->cb, callbacks, sizeof(jdns_callbacks_t)); s->shutdown = 0; s->next_qid = 0; s->next_req_id = 1; s->last_time = 0; s->next_timer = 0; s->next_name_server_id = 0; s->handle = 0; s->handle_readable = 0; s->handle_writable = 1; s->port = 0; s->name_servers = list_new(); s->queries = list_new(); s->outgoing = list_new(); s->events = list_new(); s->cache = list_new(); s->do_hold_req_ids = 0; s->held_req_ids_count = 0; s->held_req_ids = 0; s->mdns = 0; s->published = list_new(); s->maddr = 0; return s; } void jdns_session_delete(jdns_session_t *s) { if(!s) return; if(s->handle) s->cb.udp_unbind(s, s->cb.app, s->handle); list_delete(s->name_servers); list_delete(s->queries); list_delete(s->outgoing); list_delete(s->events); list_delete(s->cache); if(s->held_req_ids) free(s->held_req_ids); if(s->mdns) mdnsd_free(s->mdns); list_delete(s->published); jdns_address_delete(s->maddr); free(s); } // declare some internal functions static int _callback_time_now(mdnsd d, void *arg); static int _callback_rand_int(mdnsd d, void *arg); static void _append_event(jdns_session_t *s, jdns_event_t *event); static void _append_event_and_hold_id(jdns_session_t *s, jdns_event_t *event); static void _remove_name_server_datagrams(jdns_session_t *s, int ns_id); static void _remove_query_datagrams(jdns_session_t *s, const query_t *q); static int _unicast_query(jdns_session_t *s, const unsigned char *name, int qtype); static void _unicast_cancel(jdns_session_t *s, query_t *q); static int _multicast_query(jdns_session_t *s, const unsigned char *name, int qtype); static void _multicast_cancel(jdns_session_t *s, int req_id); static int _multicast_publish(jdns_session_t *s, int mode, const jdns_rr_t *rr); static void _multicast_update_publish(jdns_session_t *s, int id, const jdns_rr_t *rr); static void _multicast_cancel_publish(jdns_session_t *s, int id); static void _multicast_flush(jdns_session_t *s); static int jdns_step_unicast(jdns_session_t *s, int now); static int jdns_step_multicast(jdns_session_t *s, int now); static void _hold_req_id(jdns_session_t *s, int req_id) { int pos; // make sure we don't hold an id twice pos = _intarray_indexOf(s->held_req_ids, s->held_req_ids_count, req_id); if(pos != -1) return; _intarray_add(&s->held_req_ids, &s->held_req_ids_count, req_id); } static void _unhold_req_id(jdns_session_t *s, int req_id) { int pos; pos = _intarray_indexOf(s->held_req_ids, s->held_req_ids_count, req_id); if(pos != -1) _intarray_remove(&s->held_req_ids, &s->held_req_ids_count, pos); } static void _set_hold_ids_enabled(jdns_session_t *s, int enabled) { if(enabled && !s->do_hold_req_ids) { s->do_hold_req_ids = 1; } else if(!enabled && s->do_hold_req_ids) { s->do_hold_req_ids = 0; if(s->held_req_ids) free(s->held_req_ids); s->held_req_ids = 0; s->held_req_ids_count = 0; } } static int _int_wrap(int *src, int start) { int x; x = (*src)++; if(*src < start) *src = start; return x; } // starts at 0 static int get_next_qid(jdns_session_t *s) { int n, id; id = -1; while(id == -1) { id = _int_wrap(&s->next_qid, 0); for(n = 0; n < s->queries->count; ++n) { if(((query_t *)s->queries->item[n])->id == id) { id = -1; break; } } } return id; } // starts at 1 static int get_next_req_id(jdns_session_t *s) { int n, k, id; id = -1; while(id == -1) { id = _int_wrap(&s->next_req_id, 1); // no query using this? for(n = 0; n < s->queries->count; ++n) { query_t *q = (query_t *)s->queries->item[n]; for(k = 0; k < q->req_ids_count; ++k) { if(q->req_ids[k] == id) { id = -1; break; } } if(id == -1) break; } // no publish using this? for(n = 0; n < s->published->count; ++n) { if(((published_item_t *)s->published->item[n])->id == id) { id = -1; break; } } // successful unicast queries or any kind of error result in // events for actions that are no longer active. we need // to make sure ids for these actions are not reassigned // until the user explicitly releases them for(n = 0; n < s->held_req_ids_count; ++n) { if(s->held_req_ids[n] == id) { id = -1; break; } } } return id; } // random number fitting in 16 bits static int get_next_dns_id(jdns_session_t *s) { int n, id, active_ids; active_ids = 0; id = -1; while(id == -1) { // use random number for dns id id = s->cb.rand_int(s, s->cb.app) & 0xffff; for(n = 0; n < s->queries->count; ++n) { query_t *q = (query_t *)s->queries->item[n]; if(q->dns_id != -1) { ++active_ids; if(active_ids >= JDNS_QUERY_MAX) return -1; if(q->dns_id == id) { id = -1; break; } } } } return id; } // starts at 0 static int get_next_name_server_id(jdns_session_t *s) { int n, id; id = -1; while(id == -1) { id = _int_wrap(&s->next_name_server_id, 0); for(n = 0; n < s->name_servers->count; ++n) { if(((name_server_t *)s->name_servers->item[n])->id == id) { id = -1; break; } } } return id; } int jdns_init_unicast(jdns_session_t *s, const jdns_address_t *addr, int port) { int ret; s->mode = 0; ret = s->cb.udp_bind(s, s->cb.app, addr, port, 0); if(ret <= 0) return 0; s->handle = ret; s->port = port; return 1; } int jdns_init_multicast(jdns_session_t *s, const jdns_address_t *addr, int port, const jdns_address_t *maddr) { int ret; s->mode = 1; ret = s->cb.udp_bind(s, s->cb.app, addr, port, maddr); if(ret <= 0) return 0; s->handle = ret; s->port = port; s->maddr = jdns_address_copy(maddr); // class 1. note: frame size is ignored by the jdns version of mdnsd s->mdns = mdnsd_new(0x0001, 1000, s->port, _callback_time_now, _callback_rand_int, s); return 1; } void jdns_shutdown(jdns_session_t *s) { if(s->shutdown == 0) s->shutdown = 1; // request shutdown } void jdns_set_nameservers(jdns_session_t *s, const jdns_nameserverlist_t *nslist) { int n, k; // removed? for(k = 0; k < s->name_servers->count; ++k) { name_server_t *ns = (name_server_t *)(s->name_servers->item[k]); int found = 0; for(n = 0; n < nslist->count; ++n) { jdns_nameserver_t *i = (jdns_nameserver_t *)nslist->item[n]; if(jdns_address_cmp(ns->address, i->address) && ns->port == i->port) { found = 1; break; } } if(!found) { int i; int ns_id; // remove any pending packets to this nameserver _remove_name_server_datagrams(s, ns->id); _debug_line(s, "ns [%s:%d] (id=%d) removed", ns->address->c_str, ns->port, ns->id); ns_id = ns->id; list_remove(s->name_servers, ns); --k; // adjust position for(i = 0; i < s->queries->count; ++i) query_name_server_gone((query_t *)s->queries->item[i], ns_id); } } // added? for(n = 0; n < nslist->count; ++n) { name_server_t *ns; jdns_nameserver_t *i; int found; i = (jdns_nameserver_t *)nslist->item[n]; found = 0; for(k = 0; k < s->name_servers->count; ++k) { ns = (name_server_t *)(s->name_servers->item[k]); if(jdns_address_cmp(ns->address, i->address) && ns->port == i->port) { found = 1; break; } } if(found) { _debug_line(s, "ns [%s:%d] (id=%d) still present", ns->address->c_str, ns->port, ns->id); } else { ns = name_server_new(); ns->id = get_next_name_server_id(s); ns->address = jdns_address_copy(i->address); ns->port = i->port; list_insert(s->name_servers, ns, -1); _debug_line(s, "ns [%s:%d] (id=%d) added", ns->address->c_str, ns->port, ns->id); } } // no nameservers? if(nslist->count == 0) { _debug_line(s, "nameserver count is zero, invalidating any queries"); // invalidate all of the queries! for(n = 0; n < s->queries->count; ++n) { query_t *q = (query_t *)s->queries->item[n]; // report event to any requests listening for(k = 0; k < q->req_ids_count; ++k) { jdns_event_t *event = jdns_event_new(); event->type = JDNS_EVENT_RESPONSE; event->id = q->req_ids[k]; event->status = JDNS_STATUS_TIMEOUT; _append_event_and_hold_id(s, event); } // this line is probably redundant, but just for // consistency we'll do it... _remove_query_datagrams(s, q); list_remove(s->queries, q); --n; // adjust position } } } void jdns_probe(jdns_session_t *s) { if(s->mode != 1) return; _multicast_flush(s); } int jdns_query(jdns_session_t *s, const unsigned char *name, int rtype) { if(s->mode == 0) return _unicast_query(s, name, rtype); else return _multicast_query(s, name, rtype); } static void _remove_events(jdns_session_t *s, int event_type, int id) { int n; for(n = 0; n < s->events->count; ++n) { event_t *e = (event_t *)s->events->item[n]; if(e->event->type == event_type && e->event->id == id) { list_remove(s->events, e); --n; // adjust position } } } void jdns_cancel_query(jdns_session_t *s, int id) { int n; _unhold_req_id(s, id); // remove any events associated with the query. this avoids any // possibility that stale events from one query are mistaken to be // events resulting from a later query that happened to reuse the // id. it also means we don't deliver events for cancelled queries, // which can simplify application logic. _remove_events(s, JDNS_EVENT_RESPONSE, id); // multicast if(s->mode == 1) { _multicast_cancel(s, id); return; } // unicast for(n = 0; n < s->queries->count; ++n) { query_t *q = (query_t *)s->queries->item[n]; if(query_have_req_id(q, id)) { query_remove_req_id(q, id); // note: calling _unicast_cancel might remove an item // from s->queries, thereby screwing up our iterator // position, but that's ok because we just break // anyway. // if no one else is depending on this request, then take action if(q->req_ids_count == 0 && !q->cname_parent) { // remove a possible cname child if(q->cname_child && q->cname_child->req_ids_count == 0) { q->cname_child->cname_parent = 0; _unicast_cancel(s, q->cname_child); q->cname_child = 0; } _unicast_cancel(s, q); } break; } } } int jdns_publish(jdns_session_t *s, int mode, const jdns_rr_t *rr) { return _multicast_publish(s, mode, rr); } void jdns_update_publish(jdns_session_t *s, int id, const jdns_rr_t *rr) { _multicast_update_publish(s, id, rr); } void jdns_cancel_publish(jdns_session_t *s, int id) { _unhold_req_id(s, id); _remove_events(s, JDNS_EVENT_PUBLISH, id); _multicast_cancel_publish(s, id); } int jdns_step(jdns_session_t *s) { int now, passed; int ret; // session is shut down if(s->shutdown == 2) return 0; now = s->cb.time_now(s, s->cb.app); passed = now - s->last_time; _debug_line(s, "passed: %d", passed); if(s->mode == 0) ret = jdns_step_unicast(s, now); else ret = jdns_step_multicast(s, now); s->last_time = now; return ret; } int jdns_next_timer(jdns_session_t *s) { return s->next_timer; } void jdns_set_handle_readable(jdns_session_t *s, int handle) { (void)handle; s->handle_readable = 1; } void jdns_set_handle_writable(jdns_session_t *s, int handle) { (void)handle; s->handle_writable = 1; } jdns_event_t *jdns_next_event(jdns_session_t *s) { jdns_event_t *event = 0; if(s->events->count > 0) { event_t *e = (event_t *)s->events->item[0]; event = e->event; e->event = 0; list_remove(s->events, e); } return event; } void jdns_set_hold_ids_enabled(jdns_session_t *s, int enabled) { _set_hold_ids_enabled(s, enabled); } //---------------------------------------------------------------------------- // jdns - internal functions //---------------------------------------------------------------------------- // we don't have vsnprintf on windows, so don't pass anything enormous to // this function. the plan is that no line should exceed 1000 bytes, // although _print_rr() might get close. a 2048 byte buffer should be // plenty then. void _debug_line(jdns_session_t *s, const char *format, ...) { char *buf = (char *)malloc(2048); va_list ap; va_start(ap, format); jdns_vsprintf_s(buf, 2048, format, ap); va_end(ap); s->cb.debug_line(s, s->cb.app, buf); free(buf); } int _callback_time_now(mdnsd d, void *arg) { jdns_session_t *s = (jdns_session_t *)arg; (void)d; // offset the time, mdnsd doesn't like starting at 0 return s->cb.time_now(s, s->cb.app) + 120 * 1000; } int _callback_rand_int(mdnsd d, void *arg) { jdns_session_t *s = (jdns_session_t *)arg; (void)d; return s->cb.rand_int(s, s->cb.app); } void _append_event(jdns_session_t *s, jdns_event_t *event) { event_t *e = event_new(); e->event = event; list_insert(s->events, e, -1); } void _append_event_and_hold_id(jdns_session_t *s, jdns_event_t *event) { if(s->do_hold_req_ids) _hold_req_id(s, event->id); _append_event(s, event); } void _remove_name_server_datagrams(jdns_session_t *s, int ns_id) { int n; for(n = 0; n < s->outgoing->count; ++n) { datagram_t *a = (datagram_t *)s->outgoing->item[n]; if(a->ns_id == ns_id) { list_remove(s->outgoing, a); --n; // adjust position } } } void _remove_query_datagrams(jdns_session_t *s, const query_t *q) { int n; for(n = 0; n < s->outgoing->count; ++n) { datagram_t *a = (datagram_t *)s->outgoing->item[n]; if(a->query == q) { list_remove(s->outgoing, a); --n; // adjust position } } } void _process_message(jdns_session_t *s, jdns_packet_t *p, int now, query_t *q, name_server_t *ns); // return 1 if 'q' should be deleted, 0 if not int _process_response(jdns_session_t *s, jdns_response_t *r, int nxdomain, int now, query_t *q); jdns_response_t *_cache_get_response(jdns_session_t *s, const unsigned char *qname, int qtype, int *_lowest_timeleft) { int n; int lowest_timeleft = -1; int now = s->cb.time_now(s, s->cb.app); jdns_response_t *r = 0; for(n = 0; n < s->cache->count; ++n) { cache_item_t *i = (cache_item_t *)s->cache->item[n]; if(jdns_domain_cmp(i->qname, qname) && i->qtype == qtype) { int passed, timeleft; if(!r) r = jdns_response_new(); if(i->record) jdns_response_append_answer(r, i->record); passed = now - i->time_start; timeleft = (i->ttl * 1000) - passed; if(lowest_timeleft == -1 || timeleft < lowest_timeleft) lowest_timeleft = timeleft; } } if(_lowest_timeleft) *_lowest_timeleft = lowest_timeleft; return r; } query_t *_find_first_active_query(jdns_session_t *s, const unsigned char *qname, int qtype) { int n; query_t *q; for(n = 0; n < s->queries->count; ++n) { q = (query_t *)s->queries->item[n]; if(jdns_domain_cmp(q->qname, qname) && q->qtype == qtype && q->step != -1) return q; } return 0; } query_t *_get_query(jdns_session_t *s, const unsigned char *qname, int qtype, int unique) { query_t *q; jdns_string_t *str; if(!unique) { q = _find_first_active_query(s, qname, qtype); if(q) { str = _make_printable_cstr((const char *)q->qname); _debug_line(s, "[%d] reusing query for: [%s] [%s]", q->id, _qtype2str(qtype), str->data); jdns_string_delete(str); return q; } } q = query_new(); q->id = get_next_qid(s); q->qname = _ustrdup(qname); q->qtype = qtype; q->step = 0; q->dns_id = -1; q->time_start = 0; q->time_next = 0; q->trycache = 1; q->retrying = 0; list_insert(s->queries, q, -1); str = _make_printable_cstr((const char *)q->qname); _debug_line(s, "[%d] querying: [%s] [%s]", q->id, _qtype2str(qtype), str->data); jdns_string_delete(str); return q; } int _unicast_query(jdns_session_t *s, const unsigned char *name, int qtype) { unsigned char *qname; query_t *q; int req_id; jdns_string_t *str; str = _make_printable_cstr((const char *)name); _debug_line(s, "query input: [%s]", str->data); jdns_string_delete(str); qname = _fix_input(name); q = _get_query(s, qname, qtype, 0); req_id = get_next_req_id(s); query_add_req_id(q, req_id); free(qname); return req_id; } void _unicast_cancel(jdns_session_t *s, query_t *q) { // didn't even do a step yet? just remove it if(q->step == 0) { _remove_query_datagrams(s, q); list_remove(s->queries, q); } // otherwise, just deactivate else { // deactivate and remain in the background for // 1 minute. this will allow us to cache a // reply, even if the user is not currently // interested. q->step = -1; q->time_start = s->cb.time_now(s, s->cb.app); q->time_next = 60000; } } void _queue_packet(jdns_session_t *s, query_t *q, const name_server_t *ns, int recurse, int query_send_type) { jdns_packet_t *packet; datagram_t *a; packet = jdns_packet_new(); packet->id = q->dns_id; packet->opts.rd = recurse; // recursion desired { jdns_packet_question_t *question = jdns_packet_question_new(); question->qname = jdns_string_new(); jdns_string_set_cstr(question->qname, (const char *)q->qname); question->qtype = q->qtype; question->qclass = 0x0001; jdns_list_insert(packet->questions, question, -1); jdns_packet_question_delete(question); } if(!jdns_packet_export(packet, JDNS_UDP_UNI_OUT_MAX)) { _debug_line(s, "outgoing packet export error, not sending"); jdns_packet_delete(packet); return; } a = datagram_new(); a->handle = s->handle; a->dest_address = jdns_address_copy(ns->address); a->dest_port = ns->port; a->data = jdns_copy_array(packet->raw_data, packet->raw_size); a->size = packet->raw_size; a->query = q; a->query_send_type = query_send_type; a->ns_id = ns->id; jdns_packet_delete(packet); list_insert(s->outgoing, a, -1); } // return 1 if packets still need to be written int _unicast_do_writes(jdns_session_t *s, int now); // return 1 if packets still need to be read int _unicast_do_reads(jdns_session_t *s, int now); int jdns_step_unicast(jdns_session_t *s, int now) { int n; int need_read = 0; int need_write = 0; int smallest_time = -1; int flags; if(s->shutdown == 1) { jdns_event_t *event = jdns_event_new(); event->type = JDNS_EVENT_SHUTDOWN; _append_event(s, event); s->shutdown = 2; return 0; } // expire cached items for(n = 0; n < s->cache->count; ++n) { cache_item_t *i = (cache_item_t *)s->cache->item[n]; if(now >= i->time_start + (i->ttl * 1000)) { jdns_string_t *str = _make_printable_cstr((const char *)i->qname); _debug_line(s, "cache exp [%s]", str->data); jdns_string_delete(str); list_remove(s->cache, i); --n; // adjust position } } need_write = _unicast_do_writes(s, now); need_read = _unicast_do_reads(s, now); // calculate next timer (based on queries and cache) for(n = 0; n < s->queries->count; ++n) { query_t *q = (query_t *)(s->queries->item[n]); if(q->time_start != -1) { int qpassed = now - q->time_start; int timeleft = q->time_next - qpassed; if(timeleft < 0) timeleft = 0; if(smallest_time == -1 || timeleft < smallest_time) smallest_time = timeleft; } } for(n = 0; n < s->cache->count; ++n) { cache_item_t *i = (cache_item_t *)(s->cache->item[n]); int passed = now - i->time_start; int timeleft = (i->ttl * 1000) - passed; if(timeleft < 0) timeleft = 0; if(smallest_time == -1 || timeleft < smallest_time) smallest_time = timeleft; } flags = 0; if(smallest_time != -1) { flags |= JDNS_STEP_TIMER; s->next_timer = smallest_time; // offset it a little bit, so that the user doesn't call // us too early, resulting in a no-op and another timer // of 1 millisecond. s->next_timer += 2; } if(need_read || need_write) flags |= JDNS_STEP_HANDLE; return flags; } int _unicast_do_writes(jdns_session_t *s, int now) { int need_write = 0; int n, k; for(n = 0; n < s->queries->count; ++n) { query_t *q; int qpassed, timeleft; int giveup; name_server_t *ns; int already_sending; q = (query_t *)s->queries->item[n]; // nothing to do if(q->time_start == -1) continue; qpassed = now - q->time_start; timeleft = q->time_next - qpassed; if(timeleft < 0) timeleft = 0; _debug_line(s, "[%d] time_start/next=%d/%d (left=%d)", q->id, q->time_start, q->time_next, timeleft); if(timeleft > 0) continue; if(q->trycache) { // is it cached? int lowest_timeleft; int qtype = q->qtype; jdns_response_t *r; r = _cache_get_response(s, q->qname, qtype, &lowest_timeleft); // not found? try cname if(!r) { qtype = JDNS_RTYPE_CNAME; r = _cache_get_response(s, q->qname, qtype, &lowest_timeleft); } if(r) { int nxdomain; _debug_line(s, "[%d] using cached answer", q->id); // are any of the records about to expire in 3 minutes? // assume the client is interested in this record and // query it again "in the background" (but only // if we are not already doing so) if(lowest_timeleft < (3 * 60 * 1000) && !_find_first_active_query(s, q->qname, q->qtype)) { query_t *new_q; _debug_line(s, "requerying for cached item about to expire"); new_q = _get_query(s, q->qname, q->qtype, 1); new_q->retrying = 1; // slow it down new_q->trycache = 0; // don't use the cache for this } nxdomain = r->answerCount == 0 ? 1 : 0; if(_process_response(s, r, nxdomain, -1, q)) { _remove_query_datagrams(s, q); list_remove(s->queries, q); --n; // adjust position } jdns_response_delete(r); continue; } } // inactive if(q->step == -1) { // time up on an inactive query? remove it _debug_line(s, "removing inactive query"); _remove_query_datagrams(s, q); list_remove(s->queries, q); --n; // adjust position continue; } giveup = 0; // too many tries, give up if(q->step == 8) giveup = 1; // no nameservers, give up // (this would happen if someone removed all nameservers // during a query) if(s->name_servers->count == 0) giveup = 1; if(giveup) { // report event to any requests listening for(k = 0; k < q->req_ids_count; ++k) { jdns_event_t *event = jdns_event_new(); event->type = JDNS_EVENT_RESPONSE; event->id = q->req_ids[k]; event->status = JDNS_STATUS_TIMEOUT; _append_event_and_hold_id(s, event); } // report error to parent if(q->cname_parent) { // report event to any requests listening query_t *cq = q->cname_parent; for(k = 0; k < cq->req_ids_count; ++k) { jdns_event_t *event = jdns_event_new(); event->type = JDNS_EVENT_RESPONSE; event->id = cq->req_ids[k]; event->status = JDNS_STATUS_TIMEOUT; _append_event_and_hold_id(s, event); } list_remove(s->queries, cq); } _remove_query_datagrams(s, q); list_remove(s->queries, q); --n; // adjust position continue; } // assign a packet id if we don't have one yet if(q->dns_id == -1) { q->dns_id = get_next_dns_id(s); // couldn't get an id? if(q->dns_id == -1) { _debug_line(s, "unable to reserve packet id"); // report event to any requests listening for(k = 0; k < q->req_ids_count; ++k) { jdns_event_t *event = jdns_event_new(); event->type = JDNS_EVENT_RESPONSE; event->id = q->req_ids[k]; event->status = JDNS_STATUS_ERROR; _append_event_and_hold_id(s, event); } // report error to parent if(q->cname_parent) { // report event to any requests listening query_t *cq = q->cname_parent; for(k = 0; k < cq->req_ids_count; ++k) { jdns_event_t *event = jdns_event_new(); event->type = JDNS_EVENT_RESPONSE; event->id = cq->req_ids[k]; event->status = JDNS_STATUS_ERROR; _append_event_and_hold_id(s, event); } list_remove(s->queries, cq); } _remove_query_datagrams(s, q); list_remove(s->queries, q); --n; // adjust position continue; } } // out of name servers? if(q->servers_tried_count == s->name_servers->count) { // clear the 'tried' list, and start over in retry mode query_clear_servers_tried(q); q->retrying = 1; } // find a nameserver that has not been tried ns = 0; for(k = 0; k < s->name_servers->count; ++k) { name_server_t *i = (name_server_t *)s->name_servers->item[k]; if(!query_server_tried(q, i->id)) { ns = i; break; } } // in theory, it is not possible for 'ns' to be null here // don't send the packet if there is already one in the queue already_sending = 0; for(k = 0; k < s->outgoing->count; ++k) { datagram_t *a = (datagram_t *)s->outgoing->item[k]; if(a->query == q && a->query_send_type == 0) { already_sending = 1; break; } } // send the query, with recursion desired, normal query_send_type if(!already_sending) _queue_packet(s, q, ns, 1, 0); query_add_server_tried(q, ns->id); // if there is one query, then do a trick on the first step /*if(s->queries->count == 1 && q->step == 0 && !q->retrying) { // query all other servers non-recursively // note: if sending fails, there is no retry for(k = 0; k < s->name_servers->count; ++k) { name_server_t *i = (name_server_t *)s->name_servers->item[k]; if(!query_server_tried(q, i->id)) { // last arg sets first-step query_send_type _queue_packet(s, q, i, 0, 1); } } }*/ // out of name servers? if(q->servers_tried_count == s->name_servers->count) { // clear the 'tried' list, and start over in retry mode query_clear_servers_tried(q); q->retrying = 1; } q->time_start = now; q->time_next = q->retrying ? 1500 : 800; ++q->step; } // try to send queued outgoing packets for(n = 0; n < s->outgoing->count; ++n) { datagram_t *a = (datagram_t *)s->outgoing->item[n]; int ret; if(!s->handle_writable) { need_write = 1; break; } _debug_line(s, "SEND %s:%d (size=%d)", a->dest_address->c_str, a->dest_port, a->size); _print_hexdump(s, a->data, a->size); ret = s->cb.udp_write(s, s->cb.app, a->handle, a->dest_address, a->dest_port, a->data, a->size); if(ret == 0) { s->handle_writable = 0; need_write = 1; break; } list_remove(s->outgoing, a); --n; // adjust position } return need_write; } void _cache_add(jdns_session_t *s, const unsigned char *qname, int qtype, int time_start, int ttl, const jdns_rr_t *record) { cache_item_t *i; jdns_string_t *str; if(ttl == 0) return; if(s->cache->count >= JDNS_CACHE_MAX) return; i = cache_item_new(); i->qname = _ustrdup(qname); i->qtype = qtype; i->time_start = time_start; i->ttl = ttl; if(record) i->record = jdns_rr_copy(record); list_insert(s->cache, i, -1); str = _make_printable_cstr((const char *)i->qname); _debug_line(s, "cache add [%s] for %d seconds", str->data, i->ttl); jdns_string_delete(str); } void _cache_remove_all_of_kind(jdns_session_t *s, const unsigned char *qname, int qtype) { int n; for(n = 0; n < s->cache->count; ++n) { cache_item_t *i = (cache_item_t *)s->cache->item[n]; if(jdns_domain_cmp(i->qname, qname) && i->qtype == qtype) { jdns_string_t *str = _make_printable_cstr((const char *)i->qname); _debug_line(s, "cache del [%s]", str->data); jdns_string_delete(str); list_remove(s->cache, i); --n; // adjust position } } } void _cache_remove_all_of_record(jdns_session_t *s, const jdns_rr_t *record) { int n; for(n = 0; n < s->cache->count; ++n) { cache_item_t *i = (cache_item_t *)s->cache->item[n]; if(i->record && _cmp_rr(i->record, record)) { jdns_string_t *str = _make_printable_cstr((const char *)i->qname); _debug_line(s, "cache del [%s]", str->data); jdns_string_delete(str); list_remove(s->cache, i); --n; // adjust position } } } // same as _cache_add, but make sure the exact same record (name AND value) // isn't stored twice, and make sure no more than one cname record per name // is stored. void _cache_add_no_dups(jdns_session_t *s, const unsigned char *qname, int qtype, int time_start, int ttl, const jdns_rr_t *record) { if(qtype == JDNS_RTYPE_CNAME) _cache_remove_all_of_kind(s, qname, qtype); else _cache_remove_all_of_record(s, record); _cache_add(s, qname, qtype, time_start, ttl, record); } int _unicast_do_reads(jdns_session_t *s, int now) { int need_read; int n, k; // let's always ask for reads, just so the user doesn't have to // worry about what should happen to incoming packets otherwise need_read = 1; if(!s->handle_readable) return need_read; while(1) { unsigned char buf[JDNS_UDP_UNI_IN_MAX]; int bufsize = JDNS_UDP_UNI_IN_MAX; int ret; jdns_packet_t *packet; jdns_address_t *addr; int port; query_t *q; name_server_t *ns; addr = jdns_address_new(); ret = s->cb.udp_read(s, s->cb.app, s->handle, addr, &port, buf, &bufsize); // no packet? if(ret == 0) { s->handle_readable = 0; jdns_address_delete(addr); break; } _debug_line(s, "RECV %s:%d (size=%d)", addr->c_str, port, bufsize); _print_hexdump(s, buf, bufsize); if(!jdns_packet_import(&packet, buf, bufsize)) { _debug_line(s, "error parsing packet / too large"); jdns_address_delete(addr); continue; } _print_packet(s, packet); if(s->queries->count == 0) { _debug_line(s, "we have no queries"); jdns_address_delete(addr); jdns_packet_delete(packet); continue; } // who does it belong to? q = 0; ns = 0; for(n = 0; n < s->queries->count; ++n) { query_t *i = (query_t *)s->queries->item[n]; if(i->dns_id == -1) continue; if(i->dns_id == packet->id) { q = i; break; } } if(q) { // what name server did it come from? for(k = 0; k < s->name_servers->count; ++k) { name_server_t *i = (name_server_t *)s->name_servers->item[k]; if(jdns_address_cmp(i->address, addr) && i->port == port) { ns = i; break; } } // none? maybe that's because we're using unicast // over multicast, where responses always come // from an unexpected address if(!ns && s->name_servers->count > 0) { name_server_t *i; jdns_address_t *m4, *m6; i = (name_server_t *)s->name_servers->item[0]; m4 = jdns_address_multicast4_new(); m6 = jdns_address_multicast6_new(); if(jdns_address_cmp(i->address, m4) || jdns_address_cmp(i->address, m6)) ns = i; jdns_address_delete(m4); jdns_address_delete(m6); } // no suitable name server if(!ns) { // setting q = 0 causes the response to be // ignored. earlier versions of jdns would // do this, but now we comment it out because // the behavior is too strict. //q = 0; // instead we'll just print a warning _debug_line(s, "warning: response from unexpected nameserver"); } } jdns_address_delete(addr); // no queries? eat the packet if(!q) { _debug_line(s, "no such query for packet"); jdns_packet_delete(packet); continue; } _process_message(s, packet, now, q, ns); jdns_packet_delete(packet); } return need_read; } void _process_message(jdns_session_t *s, jdns_packet_t *packet, int now, query_t *q, name_server_t *ns) { int n; int authoritative; int truncated; int recursion_desired; int answer_section_ok; jdns_response_t *r; if(packet->opts.opcode != 0) { _debug_line(s, "opcode != 0, discarding"); return; } // we don't test RA (recursion available) // we don't test the extra Z fields authoritative = packet->opts.aa; truncated = packet->opts.tc; recursion_desired = packet->opts.rd; answer_section_ok = 0; if(packet->qdcount == packet->questions->count && packet->ancount == packet->answerRecords->count) answer_section_ok = 1; r = 0; // nxdomain if(packet->opts.rcode == 3) { // treat nxdomain as a generic error, but at the same time flag // the fact that it was received. this ensures that // resolving keeps going, in case the user has multiple dns // servers and one of them reports nxdomain when a later one // would succeed. if all of the servers fail then this flag // can be used at the end to report nxdomain instead of a // generic error. q->nxdomain = 1; } // normal else if(packet->opts.rcode == 0) { int at_least_something; int success; r = _packet2response(packet, q->qname, q->qtype, 0xffff); at_least_something = 0; if(r->answerCount > 0) at_least_something = 1; _print_records(s, r, q->qname); success = 0; if(at_least_something) { success = 1; } else { // note: why does qdns care about recursion_desired here? if(authoritative && recursion_desired) success = 1; } if(!success) { jdns_response_delete(r); r = 0; } } // caching if(r) { int cache_answers; int cache_additional; // clear past items _cache_remove_all_of_kind(s, q->qname, q->qtype); cache_answers = 1; cache_additional = 1; // if truncated, we may not want to cache if(truncated) { cache_additional = 0; if(!answer_section_ok) cache_answers = 0; } if(cache_answers) { for(n = 0; n < r->answerCount; ++n) { jdns_rr_t *record = r->answerRecords[n]; _cache_add_no_dups(s, q->qname, record->type, now, _min(record->ttl, JDNS_TTL_MAX), record); } } if(cache_additional) { for(n = 0; n < r->additionalCount; ++n) { jdns_rr_t *record = r->additionalRecords[n]; _cache_add_no_dups(s, record->owner, record->type, now, _min(record->ttl, JDNS_TTL_MAX), record); } } } // don't pass authority/additional records upwards if(r) jdns_response_remove_extra(r); // this server returned an error? if(!r && ns) { // all failed servers must also be considered tried servers, // so mark as tried if necessary. this can happen if the // tried list is cleared (to perform retrying) and then an // error is received if(!query_server_tried(q, ns->id)) query_add_server_tried(q, ns->id); query_add_server_failed(q, ns->id); } if(_process_response(s, r, 0, now, q)) { _remove_query_datagrams(s, q); list_remove(s->queries, q); } jdns_response_delete(r); } // 'r' can be null, for processing an error // 'now' can be -1, if processing a cached response ('r' always non-null) int _process_response(jdns_session_t *s, jdns_response_t *r, int nxdomain, int now, query_t *q) { int k; int do_error = 0; int do_nxdomain = 0; // error if(!r) { int all_errored; // if not all servers have errored, ignore error all_errored = 1; for(k = 0; k < s->name_servers->count; ++k) { name_server_t *ns = (name_server_t *)s->name_servers->item[k]; if(!query_server_failed(q, ns->id)) { all_errored = 0; break; } } if(!all_errored) return 0; do_error = 1; // if we picked up an nxdomain along the way, act on it now if(q->nxdomain) { do_nxdomain = 1; // cache nxdomain for 1 minute if(q->qtype != JDNS_RTYPE_ANY && now != -1) { _cache_remove_all_of_kind(s, q->qname, q->qtype); _cache_add(s, q->qname, q->qtype, now, 60, 0); } } } else if(nxdomain) { do_error = 1; do_nxdomain = 1; } if(do_error) { // report event to any requests listening for(k = 0; k < q->req_ids_count; ++k) { jdns_event_t *event = jdns_event_new(); event->type = JDNS_EVENT_RESPONSE; event->id = q->req_ids[k]; if(do_nxdomain) event->status = JDNS_STATUS_NXDOMAIN; else event->status = JDNS_STATUS_ERROR; _append_event_and_hold_id(s, event); } // report error to parent if(q->cname_parent) { // report event to any requests listening query_t *cq = q->cname_parent; for(k = 0; k < cq->req_ids_count; ++k) { jdns_event_t *event = jdns_event_new(); event->type = JDNS_EVENT_RESPONSE; event->id = cq->req_ids[k]; event->status = JDNS_STATUS_ERROR; _append_event_and_hold_id(s, event); } list_remove(s->queries, cq); } return 1; } if(!r) return 1; // all we got was a cname that we didn't ask for? if(r->answerCount == 1 && r->answerRecords[0]->type == JDNS_RTYPE_CNAME && q->qtype != JDNS_RTYPE_CNAME) { query_t *new_q; _debug_line(s, "all we got was a cname, following the chain ..."); // max chain count, bail if(q->cname_chain_count >= JDNS_CNAME_MAX) { // report event to any requests listening for(k = 0; k < q->req_ids_count; ++k) { jdns_event_t *event = jdns_event_new(); event->type = JDNS_EVENT_RESPONSE; event->id = q->req_ids[k]; event->status = JDNS_STATUS_ERROR; _append_event_and_hold_id(s, event); } // report error to parent if(q->cname_parent) { // report event to any requests listening query_t *cq = q->cname_parent; for(k = 0; k < cq->req_ids_count; ++k) { jdns_event_t *event = jdns_event_new(); event->type = JDNS_EVENT_RESPONSE; event->id = cq->req_ids[k]; event->status = JDNS_STATUS_ERROR; _append_event_and_hold_id(s, event); } list_remove(s->queries, cq); } return 1; } new_q = _get_query(s, r->answerRecords[0]->data.name, q->qtype, 1); // is the current query a child query? (has a parent) if(q->cname_parent) { // if so, then set new_q as the new child new_q->cname_chain_count = q->cname_chain_count + 1; new_q->cname_parent = q->cname_parent; new_q->cname_parent->cname_child = new_q; // and delete the current query return 1; } else { // otherwise, the current query becomes a parent, with // new_q set as the child new_q->cname_chain_count = q->cname_chain_count + 1; new_q->cname_parent = q; q->cname_child = new_q; q->time_start = -1; q->dns_id = -1; // don't handle responses } } // if this query now has a child, then don't report events or delete if(q->cname_child) return 0; // report event to any requests listening for(k = 0; k < q->req_ids_count; ++k) { jdns_event_t *event = jdns_event_new(); event->type = JDNS_EVENT_RESPONSE; event->id = q->req_ids[k]; event->status = JDNS_STATUS_SUCCESS; event->response = jdns_response_copy(r); _append_event_and_hold_id(s, event); } // report to parent if(q->cname_parent) { // report event to any requests listening query_t *cq = q->cname_parent; for(k = 0; k < cq->req_ids_count; ++k) { jdns_event_t *event = jdns_event_new(); event->type = JDNS_EVENT_RESPONSE; event->id = cq->req_ids[k]; event->status = JDNS_STATUS_SUCCESS; event->response = jdns_response_copy(r); _append_event_and_hold_id(s, event); } list_remove(s->queries, cq); } return 1; } //---------------------------------------------------------------------------- // jdns - multicast //---------------------------------------------------------------------------- static jdns_rr_t *_mdnsda2rr(mdnsda a) { jdns_rr_t *rr; if(a->type == JDNS_RTYPE_ANY) return 0; // for AAAA, TXT and HINFO, run the raw rdata through jdns_rr's parser if(a->type == JDNS_RTYPE_AAAA || a->type == JDNS_RTYPE_TXT || a->type == JDNS_RTYPE_HINFO) { jdns_packet_resource_t *pr = jdns_packet_resource_new(); pr->qname = jdns_string_new(); jdns_string_set_cstr(pr->qname, (const char *)a->name); pr->qtype = a->type; pr->qclass = 0x0001; // class is always 1 for us if(a->ttl == 0) pr->ttl = 0; else pr->ttl = a->real_ttl; pr->rdata = jdns_copy_array(a->rdata, a->rdlen); pr->rdlength = a->rdlen; // we don't need a reference for these types rr = jdns_rr_from_resource(pr, 0); } // else, pull the values out of 'a' directly else { rr = jdns_rr_new(); rr->owner = _ustrdup(a->name); rr->qclass = 0x0001; // class is always 1 for us if(a->ttl == 0) rr->ttl = 0; else rr->ttl = a->real_ttl; switch(a->type) { case JDNS_RTYPE_A: { jdns_address_t *addr = jdns_address_new(); jdns_address_set_ipv4(addr, a->ip); jdns_rr_set_A(rr, addr); jdns_address_delete(addr); break; } case JDNS_RTYPE_AAAA: { // covered earlier break; } case JDNS_RTYPE_MX: { // don't care about MX jdns_rr_delete(rr); rr = 0; break; } case JDNS_RTYPE_SRV: { jdns_rr_set_SRV(rr, a->rdname, a->srv.port, a->srv.priority, a->srv.weight); break; } case JDNS_RTYPE_CNAME: { jdns_rr_set_CNAME(rr, a->rdname); break; } case JDNS_RTYPE_PTR: { jdns_rr_set_PTR(rr, a->rdname); break; } case JDNS_RTYPE_TXT: { // covered earlier break; } case JDNS_RTYPE_HINFO: { // covered earlier break; } case JDNS_RTYPE_NS: { // don't care about NS jdns_rr_delete(rr); rr = 0; break; } default: { jdns_rr_set_record(rr, a->type, a->rdata, a->rdlen); break; } } } return rr; } int _multicast_query_ans(mdnsda a, void *arg) { int n; jdns_session_t *s; query_t *q; jdns_response_t *r; jdns_rr_t *rr; jdns_event_t *event; s = (jdns_session_t *)arg; // what query is this for? q = 0; for(n = 0; n < s->queries->count; ++n) { query_t *i = (query_t *)s->queries->item[n]; if((i->qtype == JDNS_RTYPE_ANY || i->qtype == a->type) && jdns_domain_cmp(i->qname, a->name)) { q = i; break; } } // note: this can't happen, but we'll check anyway if(!q) { _debug_line(s, "no such multicast query"); return 0; } rr = _mdnsda2rr(a); if(!rr) return 0; // add/remove as a known if(rr->ttl == 0) { for(n = 0; n < q->mul_known->answerCount; ++n) { jdns_rr_t *k = q->mul_known->answerRecords[n]; if(_cmp_rr(k, rr)) { jdns_response_remove_answer(q->mul_known, n); break; } } } else jdns_response_append_answer(q->mul_known, rr); r = jdns_response_new(); jdns_response_append_answer(r, rr); jdns_rr_delete(rr); // report event to any requests listening for(n = 0; n < q->req_ids_count; ++n) { event = jdns_event_new(); event->type = JDNS_EVENT_RESPONSE; event->id = q->req_ids[n]; event->status = JDNS_STATUS_SUCCESS; event->response = jdns_response_copy(r); _append_event(s, event); } jdns_response_delete(r); return 0; } query_t *_get_multicast_query(jdns_session_t *s, const unsigned char *qname, int qtype) { int n; query_t *q; jdns_string_t *str; // check for existing queries for(n = 0; n < s->queries->count; ++n) { q = (query_t *)s->queries->item[n]; if(jdns_domain_cmp(q->qname, qname) && q->qtype == qtype) { str = _make_printable_cstr((const char *)q->qname); _debug_line(s, "[%d] reusing query for: [%s] [%s]", q->id, _qtype2str(qtype), str->data); jdns_string_delete(str); return q; } } q = query_new(); q->id = get_next_qid(s); q->qname = _ustrdup(qname); q->qtype = qtype; q->step = 0; q->mul_known = jdns_response_new(); list_insert(s->queries, q, -1); str = _make_printable_cstr((const char *)q->qname); _debug_line(s, "[%d] querying: [%s] [%s]", q->id, _qtype2str(qtype), str->data); jdns_string_delete(str); return q; } int _multicast_query(jdns_session_t *s, const unsigned char *name, int qtype) { unsigned char *qname; query_t *q; int req_id; jdns_string_t *str; str = _make_printable_cstr((const char *)name); _debug_line(s, "query input: [%s]", str->data); jdns_string_delete(str); // add a dot to the end if needed qname = _fix_input(name); q = _get_multicast_query(s, qname, qtype); req_id = get_next_req_id(s); query_add_req_id(q, req_id); free(qname); // start the mdnsd_query if necessary if(q->step == 0) { q->step = 1; mdnsd_query(s->mdns, (char *)q->qname, q->qtype, _multicast_query_ans, s); } else { int n; // report the knowns for(n = 0; n < q->mul_known->answerCount; ++n) { const jdns_rr_t *rr; jdns_response_t *r; jdns_event_t *event; rr = q->mul_known->answerRecords[n]; r = jdns_response_new(); jdns_response_append_answer(r, rr); event = jdns_event_new(); event->type = JDNS_EVENT_RESPONSE; event->id = req_id; event->status = JDNS_STATUS_SUCCESS; event->response = r; _append_event(s, event); } } return req_id; } void _multicast_cancel(jdns_session_t *s, int req_id) { int n; for(n = 0; n < s->queries->count; ++n) { query_t *q = (query_t *)s->queries->item[n]; if(query_have_req_id(q, req_id)) { query_remove_req_id(q, req_id); // if no one else is depending on this request, then take action if(q->req_ids_count == 0) { mdnsd_query(s->mdns, (char *)q->qname, q->qtype, NULL, 0); list_remove(s->queries, q); } break; } } } void _multicast_pubresult(int result, char *name, int type, void *arg) { jdns_session_t *s; published_item_t *pub; jdns_event_t *event; int n; s = (jdns_session_t *)arg; // find the associated pub item pub = 0; for(n = 0; n < s->published->count; ++n) { published_item_t *i = (published_item_t *)s->published->item[n]; if(strcmp((char *)i->qname, name) == 0 && i->qtype == type) { pub = i; break; } } // note: this can't happen, but we'll check anyway if(!pub) { _debug_line(s, "no such multicast published item"); return; } if(result == 1) { jdns_string_t *str = _make_printable_cstr(name); _debug_line(s, "published name %s for type %d", str->data, type); jdns_string_delete(str); event = jdns_event_new(); event->type = JDNS_EVENT_PUBLISH; event->id = pub->id; event->status = JDNS_STATUS_SUCCESS; _append_event(s, event); } else { jdns_string_t *str = _make_printable_cstr(name); _debug_line(s, "conflicting name detected %s for type %d", str->data, type); jdns_string_delete(str); event = jdns_event_new(); event->type = JDNS_EVENT_PUBLISH; event->id = pub->id; event->status = JDNS_STATUS_CONFLICT; _append_event_and_hold_id(s, event); // remove the item list_remove(s->published, pub); } } static jdns_string_t *_create_text(const jdns_stringlist_t *texts) { jdns_string_t *out; int n; int total; unsigned char *buf; buf = 0; total = 0; for(n = 0; n < texts->count; ++n) total += texts->item[n]->size + 1; if(total > 0) { int at = 0; buf = (unsigned char *)malloc(total); for(n = 0; n < texts->count; ++n) { unsigned int len = texts->item[n]->size; buf[at++] = len; memcpy(buf + at, texts->item[n]->data, len); at += len; } } out = jdns_string_new(); if(buf) { out->data = buf; out->size = total; } else jdns_string_set_cstr(out, ""); return out; } static void _publish_applyrr_unknown(jdns_session_t *s, mdnsdr r, const jdns_rr_t *rr) { // for unsupported/unknown, just take the rdata // note: for this to work, the app must explicitly set the rdata. // if the record is MX or some other known but unsupported record // type, setting the known fields is not enough mdnsd_set_raw(s->mdns, r, (char *)rr->rdata, rr->rdlength); } static int _publish_applyrr(jdns_session_t *s, mdnsdr r, const jdns_rr_t *rr) { if(!rr->haveKnown) { _publish_applyrr_unknown(s, r, rr); return 1; } // jdns_mdnsd supports: A, AAAA, SRV, CNAME, PTR, TXT, and HINFO switch(rr->type) { case JDNS_RTYPE_A: { unsigned long int ip_net = htonl(rr->data.address->addr.v4); mdnsd_set_raw(s->mdns, r, (char *)&ip_net, 4); break; } case JDNS_RTYPE_AAAA: { mdnsd_set_raw(s->mdns, r, (char *)rr->data.address->addr.v6, 16); break; } case JDNS_RTYPE_SRV: { mdnsd_set_srv(s->mdns, r, rr->data.server->priority, rr->data.server->weight, rr->data.server->port, (char *)rr->data.server->name); break; } case JDNS_RTYPE_CNAME: { mdnsd_set_host(s->mdns, r, (char *)rr->data.name); break; } case JDNS_RTYPE_PTR: { mdnsd_set_host(s->mdns, r, (char *)rr->data.name); break; } case JDNS_RTYPE_TXT: { jdns_string_t *out = _create_text(rr->data.texts); mdnsd_set_raw(s->mdns, r, (char *)out->data, out->size); jdns_string_delete(out); break; } case JDNS_RTYPE_HINFO: { jdns_string_t *out; jdns_stringlist_t *list; list = jdns_stringlist_new(); jdns_stringlist_append(list, rr->data.hinfo.cpu); jdns_stringlist_append(list, rr->data.hinfo.os); out = _create_text(list); jdns_stringlist_delete(list); mdnsd_set_raw(s->mdns, r, (char *)out->data, out->size); jdns_string_delete(out); break; } default: { _publish_applyrr_unknown(s, r, rr); break; } } return 1; } static void report_published(jdns_session_t *s, published_item_t *pub) { jdns_event_t *event; jdns_string_t *str; str = _make_printable_cstr((char *)pub->qname); _debug_line(s, "published name %s for type %d", str->data, pub->qtype); jdns_string_delete(str); event = jdns_event_new(); event->type = JDNS_EVENT_PUBLISH; event->id = pub->id; event->status = JDNS_STATUS_SUCCESS; _append_event(s, event); } int _multicast_publish(jdns_session_t *s, int mode, const jdns_rr_t *rr) { mdnsdr r; published_item_t *pub; int next_id; jdns_event_t *event; int n; r = 0; next_id = get_next_req_id(s); // see if we have an item with this name+type combination already pub = 0; for(n = 0; n < s->published->count; ++n) { published_item_t *i = (published_item_t *)s->published->item[n]; if(i->qtype == rr->type && jdns_domain_cmp(i->qname, rr->owner)) { pub = i; break; } } if(pub) goto error; if(!jdns_rr_verify(rr)) goto error; if(mode == JDNS_PUBLISH_UNIQUE) r = mdnsd_unique(s->mdns, (char *)rr->owner, rr->type, rr->ttl, _multicast_pubresult, s); else r = mdnsd_shared(s->mdns, (char *)rr->owner, rr->type, rr->ttl); if(!_publish_applyrr(s, r, rr)) goto error; pub = published_item_new(); pub->id = next_id; pub->mode = mode; pub->qname = _ustrdup(rr->owner); pub->qtype = rr->type; pub->rec = r; pub->rr = jdns_rr_copy(rr); list_insert(s->published, pub, -1); // mdnsd doesn't report publish events for shared, so do that here if(mode == JDNS_PUBLISH_SHARED) report_published(s, pub); return pub->id; error: _debug_line(s, "attempt to publish record, malformed, unsupported, or duplicate type"); if(r) { // don't publish mdnsd_done(s->mdns, r); } // send an error to the app event = jdns_event_new(); event->type = JDNS_EVENT_PUBLISH; event->id = next_id; event->status = JDNS_STATUS_ERROR; _append_event_and_hold_id(s, event); return next_id; } void _multicast_update_publish(jdns_session_t *s, int id, const jdns_rr_t *rr) { mdnsdr r; published_item_t *pub; int n; pub = 0; for(n = 0; n < s->published->count; ++n) { published_item_t *i = (published_item_t *)s->published->item[n]; if(i->id == id) { pub = i; break; } } if(!pub) return; r = pub->rec; // expire existing record. this is mostly needed for shared records // since unique records already have the cache flush bit and that // should achieve the same result. however, since Apple expires // unique records before updates, so will we. mdnsd_done(s->mdns, r); if(pub->mode == JDNS_PUBLISH_UNIQUE) r = mdnsd_unique(s->mdns, (char *)pub->rr->owner, pub->rr->type, rr->ttl, _multicast_pubresult, s); else r = mdnsd_shared(s->mdns, (char *)pub->rr->owner, pub->rr->type, rr->ttl); pub->rec = r; if(!_publish_applyrr(s, r, rr)) { _debug_line(s, "attempt to update_publish an unsupported type"); return; } } void _multicast_cancel_publish(jdns_session_t *s, int id) { int n; for(n = 0; n < s->published->count; ++n) { published_item_t *i = (published_item_t *)s->published->item[n]; if(i->id == id) { mdnsd_done(s->mdns, i->rec); list_remove(s->published, i); break; } } } void _multicast_flush(jdns_session_t *s) { int n; // to flush, we make like our queries and published items are all new. // we'll do this by destroying/creating the mdnsd object again (so it // is fresh) and then reapply all queries and published items to it. // start over with mdnsd mdnsd_free(s->mdns); s->mdns = mdnsd_new(0x0001, 1000, s->port, _callback_time_now, _callback_rand_int, s); // attempt to publish again for(n = 0; n < s->published->count; ++n) { published_item_t *i; mdnsdr r; i = (published_item_t *)s->published->item[n]; if(i->mode == JDNS_PUBLISH_UNIQUE) r = mdnsd_unique(s->mdns, (char *)i->rr->owner, i->rr->type, i->rr->ttl, _multicast_pubresult, s); else r = mdnsd_shared(s->mdns, (char *)i->rr->owner, i->rr->type, i->rr->ttl); _publish_applyrr(s, r, i->rr); i->rec = r; } // restore the queries for(n = 0; n < s->queries->count; ++n) { query_t *q = (query_t *)s->queries->item[n]; // issue the query mdnsd_query(s->mdns, (char *)q->qname, q->qtype, _multicast_query_ans, s); } } int jdns_step_multicast(jdns_session_t *s, int now) { int need_read, need_write, smallest_time; struct mytimeval *tv; jdns_packet_t *packet; int flags; // not used (void)now; need_read = 0; need_write = 0; if(s->shutdown == 1) mdnsd_shutdown(s->mdns); while(1) { jdns_address_t *addr; unsigned short int port; int ret; unsigned char *buf; int buf_len; if(!mdnsd_out(s->mdns, &packet, &addr, &port)) break; if(!s->handle_writable) { need_write = 1; jdns_address_delete(addr); break; } if(!jdns_packet_export(packet, JDNS_UDP_MUL_OUT_MAX)) { _debug_line(s, "outgoing packet export error, not sending"); jdns_packet_delete(packet); continue; } buf = packet->raw_data; buf_len = packet->raw_size; // multicast if(!addr) { addr = jdns_address_copy(s->maddr); port = s->port; } _debug_line(s, "SEND %s:%d (size=%d)", addr->c_str, port, buf_len); _print_hexdump(s, buf, buf_len); ret = s->cb.udp_write(s, s->cb.app, s->handle, addr, port, buf, buf_len); jdns_address_delete(addr); jdns_packet_delete(packet); // if we can't write the packet, oh well if(ret == 0) { s->handle_writable = 0; need_write = 1; break; } } if(s->shutdown == 1) { jdns_event_t *event = jdns_event_new(); event->type = JDNS_EVENT_SHUTDOWN; _append_event(s, event); s->shutdown = 2; return 0; } // let's always ask for reads, just so the user doesn't have to // worry about what should happen to incoming packets otherwise need_read = 1; if(s->handle_readable) { while(1) { unsigned char buf[JDNS_UDP_MUL_IN_MAX]; int bufsize = JDNS_UDP_MUL_IN_MAX; int ret; jdns_address_t *addr; int port; jdns_response_t *r; addr = jdns_address_new(); ret = s->cb.udp_read(s, s->cb.app, s->handle, addr, &port, buf, &bufsize); // no packet? if(ret == 0) { s->handle_readable = 0; jdns_address_delete(addr); break; } _debug_line(s, "RECV %s:%d (size=%d)", addr->c_str, port, bufsize); _print_hexdump(s, buf, bufsize); if(!jdns_packet_import(&packet, buf, bufsize)) { _debug_line(s, "error parsing packet / too large"); jdns_address_delete(addr); continue; } _print_packet(s, packet); r = _packet2response(packet, 0, 0, 0x7fff); _print_records(s, r, 0); mdnsd_in(s->mdns, packet, r, addr, (unsigned short)port); jdns_address_delete(addr); jdns_packet_delete(packet); jdns_response_delete(r); } } tv = mdnsd_sleep(s->mdns); smallest_time = tv->tv_sec * 1000 + tv->tv_usec / 1000; flags = 0; if(smallest_time != -1) { flags |= JDNS_STEP_TIMER; s->next_timer = smallest_time; // offset it a little bit, so that the user doesn't call // us too early, resulting in a no-op and another timer // of 1 millisecond. s->next_timer += 2; } if(need_read || need_write) flags |= JDNS_STEP_HANDLE; return flags; } psi-plus-snapshots-1.4.1456/iris/src/jdns/src/jdns/jdns_mdnsd.c000066400000000000000000001013741370065651000242700ustar00rootroot00000000000000/* * Copyright (C) 2005 Jeremie Miller * Copyright (C) 2005-2006 Justin Karneges * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "jdns_mdnsd.h" #include #include #define QTYPE_A JDNS_RTYPE_A #define QTYPE_AAAA JDNS_RTYPE_AAAA #define QTYPE_MX JDNS_RTYPE_MX #define QTYPE_SRV JDNS_RTYPE_SRV #define QTYPE_CNAME JDNS_RTYPE_CNAME #define QTYPE_PTR JDNS_RTYPE_PTR #define QTYPE_TXT JDNS_RTYPE_TXT #define QTYPE_HINFO JDNS_RTYPE_HINFO #define QTYPE_NS JDNS_RTYPE_NS #define QTYPE_ANY JDNS_RTYPE_ANY // size of query/publish hashes #define SPRIME 108 // size of cache hash #define LPRIME 1009 // brute force garbage cleanup frequency, rarely needed (daily default) #define GC 86400 // maximum number of items to cache. if an attacker on the LAN advertises // a million services, we don't want to crash our program trying to collect // them all. any dns records received beyond this max are ignored. this // means the attacker would succeed in DoS'ing the multicast dns network, // but he wouldn't succeed in running our program out of memory. #define MAX_CACHE 16384 #define bzero(p, size) memset(p, 0, size) /* messy, but it's the best/simplest balance I can find at the moment Some internal data types, and a few hashes: querys, answers, cached, and records (published, unique and shared) Each type has different semantics for processing, both for timeouts, incoming, and outgoing I/O They inter-relate too, like records affect the querys they are relevant to Nice things about MDNS: we only publish once (and then ask asked), and only query once, then just expire records we've got cached */ struct query { char *name; int type; unsigned long int nexttry; int tries; int (*answer)(mdnsda, void *); void *arg; struct query *next, *list; }; struct unicast { int id; char ipv6; unsigned long int to; unsigned char to6[16]; unsigned short int port; mdnsdr r; struct unicast *next; }; struct cached { struct mdnsda_struct rr; struct query *q; struct cached *next; }; struct mdnsdr_struct { struct mdnsda_struct rr; char unique; // # of checks performed to ensure int tries; void (*pubresult)(int, char *, int, void *); void *arg; struct mdnsdr_struct *next, *list; }; struct mdnsd_struct { char shutdown; unsigned long int expireall, checkqlist; struct mytimeval now, sleep, pause, probe, publish; int class, frame; struct cached *cache[LPRIME]; int cache_count; struct mdnsdr_struct *published[SPRIME], *probing, *a_now, *a_pause, *a_publish; struct unicast *uanswers; struct query *queries[SPRIME], *qlist; int (*cb_time_now)(struct mdnsd_struct *dp, void *arg); int (*cb_rand_int)(struct mdnsd_struct *dp, void *arg); void *cb_arg; int port; }; void mygettimeofday(mdnsd d, struct mytimeval *tv) { //struct timeval t; //gettimeofday(&t, 0); //tv->tv_sec = t.tv_sec; //tv->tv_usec = t.tv_usec; int msec = d->cb_time_now(d, d->cb_arg); tv->tv_sec = msec / 1000; tv->tv_usec = (msec % 1000) * 1000; } void query_free(struct query *q) { jdns_free(q->name); jdns_free(q); } void mdnsda_content_free(struct mdnsda_struct *rr) { if(rr->name) jdns_free(rr->name); if(rr->rdata) jdns_free(rr->rdata); if(rr->rdname) jdns_free(rr->rdname); } int _namehash(const char *s) { const unsigned char *name = (const unsigned char *)s; unsigned long h = 0, g; while (*name) { /* do some fancy bitwanking on the string */ h = (h << 4) + (unsigned long)(*name++); if ((g = (h & 0xF0000000UL))!=0) h ^= (g >> 24); h &= ~g; } return (int)h; } // case-insensitive hash int _namehash_nocase(const char *s) { int n, len; char *low = jdns_strdup(s); len = strlen(low); for(n = 0; n < len; ++n) low[n] = tolower(low[n]); n = _namehash(low); jdns_free(low); return n; } // basic linked list and hash primitives struct query *_q_next(mdnsd d, struct query *q, char *host, int type) { if(q == 0) q = d->queries[_namehash_nocase(host) % SPRIME]; else q = q->next; for(;q != 0; q = q->next) if(q->type == type && jdns_domain_cmp((unsigned char *)q->name, (unsigned char *)host)) return q; return 0; } struct cached *_c_next(mdnsd d, struct cached *c, char *host, int type) { if(c == 0) c = d->cache[_namehash_nocase(host) % LPRIME]; else c = c->next; for(;c != 0; c = c->next) if((type == c->rr.type || type == 255) && jdns_domain_cmp(c->rr.name, (unsigned char *)host)) return c; return 0; } mdnsdr _r_next(mdnsd d, mdnsdr r, char *host, int type) { if(r == 0) r = d->published[_namehash_nocase(host) % SPRIME]; else r = r->next; for(;r != 0; r = r->next) if(type == r->rr.type && jdns_domain_cmp(r->rr.name, (unsigned char *)host)) return r; return 0; } /* int _rr_len(mdnsda rr) { int len = 12; // name is always compressed (dup of earlier), plus normal stuff if(rr->rdata) len += rr->rdlen; if(rr->rdname) len += strlen((char *)rr->rdname); // worst case if(rr->ip) len += 4; if(rr->type == QTYPE_PTR) len += 6; // srv record stuff return len; } */ /* int _a_match(struct resource *r, mdnsda a) { // compares new rdata with known a, painfully if(strcmp((char *)r->name,(char *)a->name) || r->type != a->type) return 0; if(r->type == QTYPE_SRV && !strcmp((char *)r->known.srv.name,(char *)a->rdname) && a->srv.port == r->known.srv.port && a->srv.weight == r->known.srv.weight && a->srv.priority == r->known.srv.priority) return 1; if((r->type == QTYPE_PTR || r->type == QTYPE_NS || r->type == QTYPE_CNAME) && !strcmp((char *)a->rdname,(char *)r->known.ns.name)) return 1; if(r->rdlength == a->rdlen && !memcmp(r->rdata,a->rdata,r->rdlength)) return 1; return 0; } */ int _a_match(const jdns_rr_t *r, mdnsda a) { if(r->type != a->type || !jdns_domain_cmp(r->owner, a->name)) return 0; if(r->type == JDNS_RTYPE_SRV) { if(jdns_domain_cmp(r->data.server->name, a->rdname) && r->data.server->port == a->srv.port && r->data.server->priority == a->srv.priority && r->data.server->weight == a->srv.weight ) return 1; } else if(r->type == JDNS_RTYPE_PTR || r->type == JDNS_RTYPE_NS || r->type == JDNS_RTYPE_CNAME) { if(jdns_domain_cmp(r->data.name, a->rdname)) return 1; } else if(r->rdlength == a->rdlen && !memcmp(r->rdata, a->rdata, r->rdlength)) return 1; return 0; } // compare time values easily int _tvdiff(struct mytimeval old, struct mytimeval new) { int udiff = 0; if(old.tv_sec != new.tv_sec) udiff = (new.tv_sec - old.tv_sec) * 1000000; return (new.tv_usec - old.tv_usec) + udiff; } // make sure not already on the list, then insert void _r_push(mdnsdr *list, mdnsdr r) { mdnsdr cur; for(cur = *list; cur != 0; cur = cur->list) if(cur == r) return; r->list = *list; *list = r; } // set this r to probing, set next probe time void _r_probe(mdnsd d, mdnsdr r) { (void)d; (void)r; } // force any r out right away, if valid void _r_publish(mdnsd d, mdnsdr r) { if(r->unique && r->unique < 5) return; // probing already r->tries = 0; d->publish.tv_sec = d->now.tv_sec; d->publish.tv_usec = d->now.tv_usec; _r_push(&d->a_publish,r); } // send r out asap void _r_send(mdnsd d, mdnsdr r) { // removing record if(r->rr.ttl == 0) { if(d->a_publish == r) d->a_publish = r->list; _r_push(&d->a_now, r); return; } if(r->tries < 4) { // being published, make sure that happens soon d->publish.tv_sec = d->now.tv_sec; d->publish.tv_usec = d->now.tv_usec; return; } if(r->unique) { // known unique ones can be sent asap _r_push(&d->a_now,r); return; } // set d->pause.tv_usec to random 20-120 msec d->pause.tv_sec = d->now.tv_sec; //d->pause.tv_usec = d->now.tv_usec + ((d->now.tv_usec % 100) + 20) * 1000; d->pause.tv_usec = d->now.tv_usec; d->pause.tv_usec += ((d->cb_rand_int(d, d->cb_arg) % 100) + 20) * 1000; _r_push(&d->a_pause,r); } // create generic unicast response struct void _u_push(mdnsd d, mdnsdr r, int id, const jdns_address_t *addr, unsigned short int port) { struct unicast *u; u = (struct unicast *)jdns_alloc(sizeof(struct unicast)); bzero(u,sizeof(struct unicast)); u->r = r; u->id = id; if(addr->isIpv6) { u->ipv6 = 1; memcpy(u->to6, addr->addr.v6, 16); } else { u->ipv6 = 0; u->to = addr->addr.v4; } u->port = port; u->next = d->uanswers; d->uanswers = u; } void _q_reset(mdnsd d, struct query *q) { struct cached *cur = 0; q->nexttry = 0; q->tries = 0; while((cur = _c_next(d,cur,q->name,q->type))) if(q->nexttry == 0 || cur->rr.ttl - 7 < q->nexttry) q->nexttry = cur->rr.ttl - 7; if(q->nexttry != 0 && q->nexttry < d->checkqlist) d->checkqlist = q->nexttry; } void _q_done(mdnsd d, struct query *q) { // no more query, update all it's cached entries, remove from lists struct cached *c = 0; struct query *cur; int i = _namehash_nocase(q->name) % SPRIME; while((c = _c_next(d,c,q->name,q->type))) c->q = 0; if(d->qlist == q) d->qlist = q->list; else { for(cur=d->qlist;cur->list != q;cur = cur->list); cur->list = q->list; } if(d->queries[i] == q) d->queries[i] = q->next; else { for(cur=d->queries[i];cur->next != q;cur = cur->next); cur->next = q->next; } query_free(q); } void _r_done(mdnsd d, mdnsdr r) { // buh-bye, remove from hash and free mdnsdr cur = 0; int i = _namehash_nocase((char *)r->rr.name) % SPRIME; if(d->a_now == r) d->a_now = r->list; if(d->a_pause == r) d->a_pause = r->list; if(d->a_publish == r) d->a_publish = r->list; if(d->published[i] == r) d->published[i] = r->next; else { for(cur=d->published[i];cur && cur->next != r;cur = cur->next); if(cur) cur->next = r->next; } mdnsda_content_free(&r->rr); jdns_free(r); } void _q_answer(mdnsd d, struct cached *c) { // call the answer function with this cached entry if(c->rr.ttl <= d->now.tv_sec) c->rr.ttl = 0; if(c->q->answer(&c->rr,c->q->arg) == -1) _q_done(d, c->q); } void _conflict(mdnsd d, mdnsdr r) { r->pubresult(0, (char *)r->rr.name,r->rr.type,r->arg); mdnsd_done(d,r); } void _published(mdnsd d, mdnsdr r) { (void)d; r->pubresult(1, (char *)r->rr.name,r->rr.type,r->arg); } void _c_expire(mdnsd d, struct cached **list) { // expire any old entries in this list struct cached *next, *cur = *list, *last = 0; while(cur != 0) { next = cur->next; if(d->now.tv_sec >= cur->rr.ttl) { if(last) last->next = next; if(*list == cur) *list = next; // update list pointer if the first one expired --(d->cache_count); if(cur->q) _q_answer(d,cur); mdnsda_content_free(&cur->rr); jdns_free(cur); }else{ last = cur; } cur = next; } } // brute force expire any old cached records void _gc(mdnsd d) { int i; for(i=0;icache[i]) _c_expire(d,&d->cache[i]); d->expireall = d->now.tv_sec + GC; } struct cached *_find_exact(mdnsd d, const jdns_rr_t *r) { struct cached *c = 0; while(1) { c = _c_next(d, c, (char *)r->owner, r->type); if(!c) break; if(_a_match(r, &c->rr)) return c; } return 0; } void _cache(mdnsd d, const jdns_rr_t *r) { struct cached *c; int i = _namehash_nocase((char *)r->owner) % LPRIME; struct cached *same_value; // do we already have it? //printf("cache: checking for entry: [%s] [%d]\n", r->owner, r->type); same_value = _find_exact(d, r); if(same_value) { //printf("already have entry of same value\n"); } if(r->qclass == 32768 + d->class) { // cache flush // simulate removal of all records for this question, // except if the value hasn't changed c = 0; while((c = _c_next(d,c,(char *)r->owner,r->type))) { if(c != same_value) c->rr.ttl = 0; } _c_expire(d,&d->cache[i]); // we may have expired same_value here, so check for it again same_value = _find_exact(d, r); } if(r->ttl == 0) { // process deletes if(same_value) same_value->rr.ttl = 0; _c_expire(d,&d->cache[i]); return; } if(same_value) { //printf("updating ttl only\n"); // only update ttl (this code directly copied from below) same_value->rr.ttl = d->now.tv_sec + (r->ttl / 2) + 8; same_value->rr.real_ttl = r->ttl; return; } //printf("cache: inserting entry: [%s] [%d]\n", r->owner, r->type); if(d->cache_count >= MAX_CACHE) return; c = (struct cached *)jdns_alloc(sizeof(struct cached)); bzero(c,sizeof(struct cached)); c->rr.name = (unsigned char *)jdns_strdup((char *)r->owner); c->rr.type = r->type; c->rr.ttl = d->now.tv_sec + (r->ttl / 2) + 8; // XXX hack for now, BAD SPEC, start retrying just after half-waypoint, then expire c->rr.real_ttl = r->ttl; c->rr.rdlen = r->rdlength; c->rr.rdata = jdns_copy_array(r->rdata, r->rdlength); switch(r->type) { case QTYPE_A: c->rr.ip = r->data.address->addr.v4; break; case QTYPE_NS: case QTYPE_CNAME: case QTYPE_PTR: c->rr.rdname = (unsigned char *)jdns_strdup((const char *)r->data.name); break; case QTYPE_SRV: c->rr.rdname = (unsigned char *)jdns_strdup((const char *)r->data.server->name); c->rr.srv.port = r->data.server->port; c->rr.srv.weight = r->data.server->weight; c->rr.srv.priority = r->data.server->priority; break; } c->next = d->cache[i]; d->cache[i] = c; if((c->q = _q_next(d, 0, (char *)r->owner, r->type))) _q_answer(d,c); if(c->q && c->q->nexttry == 0) { //printf("cache insert, but nexttry == 0\n"); _q_reset(d,c->q); if(d->checkqlist == 0) d->checkqlist = c->q->nexttry; //printf("after reset: q->nexttry=%d d->checkqlist=%d\n", c->q->nexttry, d->checkqlist); } } /* void _a_copy(struct message *m, mdnsda a) { // copy the data bits only if(a->rdata) { message_rdata_raw(m, a->rdata, a->rdlen); return; } if(a->ip) message_rdata_long(m, a->ip); if(a->type == QTYPE_SRV) message_rdata_srv(m, a->srv.priority, a->srv.weight, a->srv.port, a->rdname); else if(a->rdname) message_rdata_name(m, a->rdname); } */ void _a_copyq(jdns_list_t *dest, unsigned char *name, unsigned short type, unsigned short class) { jdns_packet_question_t *q = jdns_packet_question_new(); q->qname = jdns_string_new(); jdns_string_set_cstr(q->qname, (char *)name); q->qtype = type; q->qclass = class; jdns_list_insert(dest, q, -1); jdns_packet_question_delete(q); } void _a_copy(jdns_list_t *dest, unsigned char *name, unsigned short type, unsigned short class, unsigned long int ttl, mdnsda a) { jdns_packet_resource_t *r = jdns_packet_resource_new(); r->qname = jdns_string_new(); jdns_string_set_cstr(r->qname, (char *)name); r->qtype = type; r->qclass = class; r->ttl = ttl; if(a->rdata) jdns_packet_resource_add_bytes(r, a->rdata, a->rdlen); else if(a->ip) { unsigned long int ip; ip = htonl(a->ip); jdns_packet_resource_add_bytes(r, (unsigned char *)&ip, 4); } else if(a->type == QTYPE_SRV) { unsigned short priority, weight, port; jdns_string_t *name; priority = htons(a->srv.priority); weight = htons(a->srv.weight); port = htons(a->srv.port); name = jdns_string_new(); jdns_string_set_cstr(name, (const char *)a->rdname); jdns_packet_resource_add_bytes(r, (unsigned char *)&priority, 2); jdns_packet_resource_add_bytes(r, (unsigned char *)&weight, 2); jdns_packet_resource_add_bytes(r, (unsigned char *)&port, 2); jdns_packet_resource_add_name(r, name); jdns_string_delete(name); } else if(a->rdname) { jdns_string_t *name; name = jdns_string_new(); jdns_string_set_cstr(name, (const char *)a->rdname); jdns_packet_resource_add_name(r, name); jdns_string_delete(name); } jdns_list_insert(dest, r, -1); jdns_packet_resource_delete(r); } /* int _r_out(mdnsd d, struct message *m, mdnsdr *list) { // copy a published record into an outgoing message mdnsdr r; //, next; int ret = 0; while((r = *list) != 0 && message_packet_len(m) + _rr_len(&r->rr) < d->frame) { *list = r->list; ret++; if(r->unique) message_an(m, r->rr.name, r->rr.type, (unsigned short)(d->class + 32768), r->rr.ttl); else message_an(m, r->rr.name, r->rr.type, (unsigned short)d->class, r->rr.ttl); _a_copy(m, &r->rr); if(r->rr.ttl == 0) _r_done(d,r); } return ret; } */ int _r_out(mdnsd d, jdns_packet_t *m, mdnsdr *list) { // copy a published record into an outgoing message mdnsdr r; //, next; unsigned short class; int ret = 0; while((r = *list) != 0) { *list = r->list; ret++; class = r->unique ? d->class | 0x8000 : d->class; _a_copy(m->answerRecords, r->rr.name, r->rr.type, class, r->rr.ttl, &r->rr); if(r->rr.ttl == 0) _r_done(d,r); } return ret; } mdnsd mdnsd_new(int class, int frame, int port, int (*time_now)(mdnsd d, void *arg), int (*rand_int)(mdnsd d, void *arg), void *arg) { //int i; mdnsd d; d = (mdnsd)jdns_alloc(sizeof(struct mdnsd_struct)); bzero(d,sizeof(struct mdnsd_struct)); d->cb_time_now = time_now; d->cb_rand_int = rand_int; d->cb_arg = arg; mygettimeofday(d, &d->now); d->expireall = d->now.tv_sec + GC; d->class = class; d->frame = frame; d->cache_count = 0; d->port = port; return d; } void mdnsd_shutdown(mdnsd d) { // shutting down, zero out ttl and push out all records int i; mdnsdr cur,next; d->a_now = 0; for(i=0;ipublished[i]; cur != 0;) { next = cur->next; cur->rr.ttl = 0; cur->list = d->a_now; d->a_now = cur; cur = next; } d->shutdown = 1; } void mdnsd_flush(mdnsd d) { // set all querys to 0 tries // free whole cache // set all mdnsdr to probing // reset all answer lists (void)d; } void mdnsd_free(mdnsd d) { int i; // loop through all hashes, free everything // free answers if any for(i = 0; i < LPRIME; ++i) { while(d->cache[i]) { struct cached *cur = d->cache[i]; d->cache[i] = cur->next; mdnsda_content_free(&cur->rr); jdns_free(cur); } } for(i = 0; i < SPRIME; ++i) { while(d->published[i]) { struct mdnsdr_struct *cur = d->published[i]; d->published[i] = cur->next; mdnsda_content_free(&cur->rr); jdns_free(cur); } } while(d->uanswers) { struct unicast *u = d->uanswers; d->uanswers = u->next; jdns_free(u); } for(i = 0; i < SPRIME; ++i) { while(d->queries[i]) { struct query *cur = d->queries[i]; d->queries[i] = cur->next; query_free(cur); } } jdns_free(d); } void mdnsd_in(mdnsd d, const jdns_packet_t *m, const jdns_response_t *resp, const jdns_address_t *addr, unsigned short int port) { int i, j; mdnsdr r = 0; if(d->shutdown) return; mygettimeofday(d, &d->now); if(m->opts.qr == 0) { for(i=0;iquestions->count;i++) { // process each query jdns_packet_question_t *pq = (jdns_packet_question_t *)m->questions->item[i]; if(pq->qclass != d->class || (r = _r_next(d,0,(char *)pq->qname->data,pq->qtype)) == 0) continue; // send the matching unicast reply if(port != d->port) _u_push(d,r,m->id,addr,port); for(;r != 0; r = _r_next(d,r,(char *)pq->qname->data,pq->qtype)) { // check all of our potential answers if(r->unique && r->unique < 5) { // probing state, check for conflicts for(j=0;jauthorityCount;j++) { // check all to-be answers against our own jdns_rr_t *ns = resp->authorityRecords[j]; if(pq->qtype != ns->type || !jdns_domain_cmp(pq->qname->data, ns->owner)) continue; if(!_a_match(ns,&r->rr)) { _conflict(d,r); // answer isn't ours, conflict! // r is invalid after conflict, start all over r = 0; break; } } continue; } for(j=0;janswerCount;j++) { // check the known answers for this question jdns_rr_t *an = resp->answerRecords[j]; if(pq->qtype != an->type || !jdns_domain_cmp(pq->qname->data, an->owner)) continue; if(_a_match(an,&r->rr)) break; // they already have this answer } if(j == resp->answerCount) _r_send(d,r); } } return; } for(i=0;ianswerCount;i++) { // process each answer, check for a conflict, and cache jdns_rr_t *an = resp->answerRecords[i]; if((r = _r_next(d,0,(char *)an->owner,an->type)) != 0 && r->unique && _a_match(an,&r->rr) == 0) _conflict(d,r); _cache(d,an); } // cache additional records for(i=0;iadditionalCount;i++) { jdns_rr_t *an = resp->additionalRecords[i]; _cache(d,an); } } int mdnsd_out(mdnsd d, jdns_packet_t **_m, jdns_address_t **addr, unsigned short int *port) { mdnsdr r; int ret = 0; jdns_packet_t *m; mygettimeofday(d, &d->now); //bzero(m,sizeof(struct message)); m = jdns_packet_new(); // defaults, multicast *port = 0; //htons(5353); *addr = 0; // *ip = 0; //inet_addr("224.0.0.251"); m->opts.qr = 1; m->opts.aa = 1; if(d->uanswers) { // send out individual unicast answers struct unicast *u = d->uanswers; d->uanswers = u->next; *port = u->port; // *ip = u->to; *addr = jdns_address_new(); if(u->ipv6) jdns_address_set_ipv6(*addr, u->to6); else jdns_address_set_ipv4(*addr, u->to); m->id = u->id; _a_copyq(m->questions, u->r->rr.name, u->r->rr.type, (unsigned short)d->class); _a_copy(m->answerRecords, u->r->rr.name, u->r->rr.type, (unsigned short)d->class, u->r->rr.ttl, &u->r->rr); jdns_free(u); ret = 1; goto end; } //printf("OUT: probing %X now %X pause %X publish %X\n",d->probing,d->a_now,d->a_pause,d->a_publish); // accumulate any immediate responses if(d->a_now) { ret += _r_out(d, m, &d->a_now); } if(d->a_publish && _tvdiff(d->now,d->publish) <= 0) { // check to see if it's time to send the publish retries (and unlink if done) mdnsdr next, cur = d->a_publish, last = 0; unsigned short class; while(cur /*&& message_packet_len(m) + _rr_len(&cur->rr) < d->frame*/ ) { next = cur->list; ret++; cur->tries++; class = cur->unique ? d->class | 0x8000 : d->class; _a_copy(m->answerRecords, cur->rr.name, cur->rr.type, class, cur->rr.ttl, &cur->rr); if(cur->rr.ttl != 0 && cur->tries < 4) { last = cur; cur = next; continue; } if(d->a_publish == cur) d->a_publish = next; if(last) last->list = next; if(cur->rr.ttl == 0) _r_done(d,cur); cur = next; } if(d->a_publish) { d->publish.tv_sec = d->now.tv_sec + 2; d->publish.tv_usec = d->now.tv_usec; } } // if we're in shutdown, we're done if(d->shutdown) goto end; // check if a_pause is ready if(d->a_pause && _tvdiff(d->now, d->pause) <= 0) ret += _r_out(d, m, &d->a_pause); // now process questions if(ret) goto end; m->opts.qr = 0; m->opts.aa = 0; if(d->probing && _tvdiff(d->now,d->probe) <= 0) { mdnsdr last = 0; for(r = d->probing; r != 0;) { // scan probe list to ask questions and process published if(r->unique == 4) { // done probing, publish mdnsdr next = r->list; if(d->probing == r) d->probing = r->list; else last->list = r->list; r->list = 0; r->unique = 5; _r_publish(d,r); _published(d,r); r = next; continue; } //message_qd(m, r->rr.name, r->rr.type, (unsigned short)d->class); _a_copyq(m->questions, r->rr.name, r->rr.type, (unsigned short)d->class); last = r; r = r->list; } for(r = d->probing; r != 0; last = r, r = r->list) { // scan probe list again to append our to-be answers r->unique++; _a_copy(m->authorityRecords, r->rr.name, r->rr.type, (unsigned short)d->class, r->rr.ttl, &r->rr); ret++; } if(ret) { // process probes again in the future d->probe.tv_sec = d->now.tv_sec; d->probe.tv_usec = d->now.tv_usec + 250000; goto end; } } if(d->checkqlist && d->now.tv_sec >= d->checkqlist) { // process qlist for retries or expirations struct query *q; struct cached *c; unsigned long int nextbest = 0; // ask questions first, track nextbest time for(q = d->qlist; q != 0; q = q->list) if(q->nexttry > 0 && q->nexttry <= d->now.tv_sec && q->tries < 3) _a_copyq(m->questions, (unsigned char *)q->name, (unsigned short)q->type, (unsigned short)d->class); else if(q->nexttry > 0 && (nextbest == 0 || q->nexttry < nextbest)) nextbest = q->nexttry; // include known answers, update questions for(q = d->qlist; q != 0; q = q->list) { if(q->nexttry == 0 || q->nexttry > d->now.tv_sec) continue; if(q->tries == 3) { // done retrying, expire and reset _c_expire(d,&d->cache[_namehash_nocase(q->name) % LPRIME]); _q_reset(d,q); continue; } ret++; q->nexttry = d->now.tv_sec + ++q->tries; if(nextbest == 0 || q->nexttry < nextbest) nextbest = q->nexttry; // if room, add all known good entries c = 0; while((c = _c_next(d,c,q->name,q->type)) != 0 && c->rr.ttl > d->now.tv_sec + 8 /* && message_packet_len(m) + _rr_len(&c->rr) < d->frame */) { _a_copy(m->answerRecords, (unsigned char *)q->name, (unsigned short)q->type, (unsigned short)d->class, (unsigned long int)(c->rr.ttl - d->now.tv_sec), &c->rr); } } d->checkqlist = nextbest; } if(d->now.tv_sec > d->expireall) _gc(d); end: if(ret) *_m = m; else jdns_packet_delete(m); return ret; } struct mytimeval *mdnsd_sleep(mdnsd d) { int sec, usec; //mdnsdr r; d->sleep.tv_sec = d->sleep.tv_usec = 0; #define RET while(d->sleep.tv_usec > 1000000) {d->sleep.tv_sec++;d->sleep.tv_usec -= 1000000;} return &d->sleep; // first check for any immediate items to handle if(d->uanswers || d->a_now) return &d->sleep; mygettimeofday(d, &d->now); if(d->a_pause) { // then check for paused answers if((usec = _tvdiff(d->now,d->pause)) > 0) d->sleep.tv_usec = usec; RET; } if(d->probing) { // now check for probe retries if((usec = _tvdiff(d->now,d->probe)) > 0) d->sleep.tv_usec = usec; RET; } if(d->a_publish) { // now check for publish retries if((usec = _tvdiff(d->now,d->publish)) > 0) d->sleep.tv_usec = usec; RET; } if(d->checkqlist) { // also check for queries with known answer expiration/retry if((sec = d->checkqlist - d->now.tv_sec) > 0) d->sleep.tv_sec = sec; RET; } // last resort, next gc expiration if((sec = d->expireall - d->now.tv_sec) > 0) d->sleep.tv_sec = sec; RET; } void mdnsd_query(mdnsd d, char *host, int type, int (*answer)(mdnsda a, void *arg), void *arg) { struct query *q; struct cached *cur = 0; int i = _namehash_nocase(host) % SPRIME; if(!(q = _q_next(d,0,host,type))) { if(!answer) return; q = (struct query *)jdns_alloc(sizeof(struct query)); bzero(q,sizeof(struct query)); q->name = jdns_strdup(host); q->type = type; q->next = d->queries[i]; q->list = d->qlist; d->qlist = d->queries[i] = q; q->answer = answer; q->arg = arg; while((cur = _c_next(d,cur,q->name,q->type))) { cur->q = q; // any cached entries should be associated _q_answer(d,cur); // and reported! } _q_reset(d,q); q->nexttry = d->checkqlist = d->now.tv_sec; // new questin, immediately send out return; } if(!answer) { // no answer means we don't care anymore _q_done(d,q); return; } q->answer = answer; q->arg = arg; } mdnsda mdnsd_list(mdnsd d, char *host, int type, mdnsda last) { return (mdnsda)_c_next(d,(struct cached *)last,host,type); } mdnsdr mdnsd_shared(mdnsd d, char *host, int type, long int ttl) { int i = _namehash_nocase(host) % SPRIME; mdnsdr r; r = (mdnsdr)jdns_alloc(sizeof(struct mdnsdr_struct)); bzero(r,sizeof(struct mdnsdr_struct)); r->rr.name = (unsigned char *)jdns_strdup(host); r->rr.type = type; r->rr.ttl = ttl; r->next = d->published[i]; d->published[i] = r; return r; } mdnsdr mdnsd_unique(mdnsd d, char *host, int type, long int ttl, void (*pubresult)(int result, char *host, int type, void *arg), void *arg) { mdnsdr r; r = mdnsd_shared(d,host,type,ttl); r->pubresult = pubresult; r->arg = arg; r->unique = 1; _r_push(&d->probing,r); d->probe.tv_sec = d->now.tv_sec; d->probe.tv_usec = d->now.tv_usec; return r; } void mdnsd_done(mdnsd d, mdnsdr r) { mdnsdr cur; if(r->unique && r->unique < 5) { // probing yet, zap from that list first! if(d->probing == r) d->probing = r->list; else { for(cur=d->probing;cur->list != r;cur = cur->list); cur->list = r->list; } _r_done(d,r); return; } r->rr.ttl = 0; _r_send(d,r); } void mdnsd_set_raw(mdnsd d, mdnsdr r, char *data, int len) { if(r->rr.rdata) jdns_free(r->rr.rdata); r->rr.rdata = jdns_copy_array((unsigned char*)data, len); r->rr.rdlen = len; _r_publish(d,r); } void mdnsd_set_host(mdnsd d, mdnsdr r, char *name) { jdns_free(r->rr.rdname); r->rr.rdname = (unsigned char *)jdns_strdup(name); _r_publish(d,r); } void mdnsd_set_ip(mdnsd d, mdnsdr r, unsigned long int ip) { r->rr.ip = ip; _r_publish(d,r); } void mdnsd_set_srv(mdnsd d, mdnsdr r, int priority, int weight, int port, char *name) { r->rr.srv.priority = priority; r->rr.srv.weight = weight; r->rr.srv.port = port; mdnsd_set_host(d,r,name); } psi-plus-snapshots-1.4.1456/iris/src/jdns/src/jdns/jdns_mdnsd.h000066400000000000000000000107531370065651000242750ustar00rootroot00000000000000/* * Copyright (C) 2005 Jeremie Miller * Copyright (C) 2005-2006 Justin Karneges * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef JDNS_MDNSD_H #define JDNS_MDNSD_H #include "jdns_p.h" struct mytimeval { unsigned long int tv_sec; /* seconds */ unsigned long int tv_usec; /* microseconds */ }; typedef struct mdnsd_struct *mdnsd; // main daemon data typedef struct mdnsdr_struct *mdnsdr; // record entry // answer data typedef struct mdnsda_struct { unsigned char *name; unsigned short int type; unsigned long int ttl; unsigned long int real_ttl; unsigned short int rdlen; unsigned char *rdata; unsigned long int ip; // A unsigned char *rdname; // NS/CNAME/PTR/SRV struct { unsigned short int priority, weight, port; } srv; // SRV } *mdnsda; /////////// // Global functions // // create a new mdns daemon for the given class of names (usually 1) and maximum frame size mdnsd mdnsd_new(int class, int frame, int port, int (*time_now)(mdnsd d, void *arg), int (*rand_int)(mdnsd d, void *arg), void *arg); // // gracefully shutdown the daemon, use mdnsd_out() to get the last packets void mdnsd_shutdown(mdnsd d); // // flush all cached records (network/interface changed) void mdnsd_flush(mdnsd d); // // free given mdnsd (should have used mdnsd_shutdown() first!) void mdnsd_free(mdnsd d); // /////////// /////////// // I/O functions // // incoming message from host (to be cached/processed) void mdnsd_in(mdnsd d, const jdns_packet_t *m, const jdns_response_t *resp, const jdns_address_t *addr, unsigned short int port); // // outgoing messge to be delivered to host, returns >0 if one was returned and m/ip/port set int mdnsd_out(mdnsd d, jdns_packet_t **m, jdns_address_t **addr, unsigned short int *port); // // returns the max wait-time until mdnsd_out() needs to be called again struct mytimeval *mdnsd_sleep(mdnsd d); // //////////// /////////// // Q/A functions // // register a new query // answer(record, arg) is called whenever one is found/changes/expires (immediate or anytime after, mdnsda valid until ->ttl==0) // either answer returns -1, or another mdnsd_query with a NULL answer will remove/unregister this query void mdnsd_query(mdnsd d, char *host, int type, int (*answer)(mdnsda a, void *arg), void *arg); // // returns the first (if last == NULL) or next answer after last from the cache // mdnsda only valid until an I/O function is called mdnsda mdnsd_list(mdnsd d, char *host, int type, mdnsda last); // /////////// /////////// // Publishing functions // // create a new unique record (try mdnsda_list first to make sure it's not used) // conflict(arg) called at any point when one is detected and unable to recover // after the first data is set_*(), any future changes effectively expire the old one and attempt to create a new unique record mdnsdr mdnsd_unique(mdnsd d, char *host, int type, long int ttl, void (*pubresult)(int result, char *host, int type, void *arg), void *arg); // // create a new shared record mdnsdr mdnsd_shared(mdnsd d, char *host, int type, long int ttl); // // de-list the given record void mdnsd_done(mdnsd d, mdnsdr r); // // these all set/update the data for the given record, nothing is published until they are called void mdnsd_set_raw(mdnsd d, mdnsdr r, char *data, int len); void mdnsd_set_host(mdnsd d, mdnsdr r, char *name); void mdnsd_set_ip(mdnsd d, mdnsdr r, unsigned long int ip); void mdnsd_set_srv(mdnsd d, mdnsdr r, int priority, int weight, int port, char *name); // /////////// #endif // JDNS_MDNSD_H psi-plus-snapshots-1.4.1456/iris/src/jdns/src/jdns/jdns_p.h000066400000000000000000000067411370065651000234310ustar00rootroot00000000000000/* * Copyright (C) 2005-2008 Justin Karneges * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef JDNS_P_H #define JDNS_P_H #include "jdns.h" #include "jdns_packet.h" #include #include #include #include #include #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) # define JDNS_OS_WIN #else # define JDNS_OS_UNIX #endif #if defined(__FreeBSD__) || defined(__DragonFly__) # define JDNS_OS_FREEBSD #elif defined(__NetBSD__) # define JDNS_OS_NETBSD #elif defined(__OpenBSD__) # define JDNS_OS_OPENBSD #elif defined(__HAIKU__) # define JDNS_OS_HAIKU #elif defined(sun) || defined(__sun) # define JDNS_OS_SOLARIS #elif defined(__APPLE__) && (defined(__GNUC__) || defined(__xlC__) || defined(__xlc__)) # define JDNS_OS_MAC #endif #ifdef JDNS_OS_UNIX # include # include #endif #ifdef JDNS_OS_WIN # include #endif // jdns_util.c void *jdns_alloc(int size); void *jdns_realloc(void *p, int size); void jdns_free(void *p); char *jdns_strdup(const char *s); unsigned char *jdns_copy_array(const unsigned char *src, int size); int jdns_domain_cmp(const unsigned char *a, const unsigned char *b); int jdns_sprintf_s(char *str, int n, const char *format, ...); int jdns_vsprintf_s(char *str, int n, const char *format, va_list ap); FILE *jdns_fopen(const char *path, const char *mode); jdns_string_t *jdns_getenv(const char *name); char *jdns_strcpy(char *dst, const char *src); int jdns_string_indexOf(const jdns_string_t *s, unsigned char c, int pos); jdns_stringlist_t *jdns_string_split(const jdns_string_t *s, unsigned char sep); jdns_dnshost_t *jdns_dnshost_new(); jdns_dnshost_t *jdns_dnshost_copy(const jdns_dnshost_t *a); void jdns_dnshost_delete(jdns_dnshost_t *a); jdns_dnshostlist_t *jdns_dnshostlist_new(); jdns_dnshostlist_t *jdns_dnshostlist_copy(const jdns_dnshostlist_t *a); void jdns_dnshostlist_delete(jdns_dnshostlist_t *a); void jdns_dnshostlist_append(jdns_dnshostlist_t *a, const jdns_dnshost_t *host); jdns_rr_t *jdns_rr_from_resource(const jdns_packet_resource_t *pr, const jdns_packet_t *ref); void jdns_response_remove_extra(jdns_response_t *r); void jdns_response_remove_answer(jdns_response_t *r, int pos); #define alloc_type(type) (type *)jdns_alloc(sizeof(type)) #define _ustrdup(str) (unsigned char *)jdns_strdup((const char *)str) #define _ustrlen(str) strlen((const char *)str) #define _ustrcmp(a, b) strcmp((const char *)a, (const char *)b) #endif // JDNS_P_H psi-plus-snapshots-1.4.1456/iris/src/jdns/src/jdns/jdns_packet.c000066400000000000000000000634721370065651000244400ustar00rootroot00000000000000/* * Copyright (C) 2006 Justin Karneges * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "jdns_packet.h" #include "jdns_p.h" // maximum length of a sublabel #define MAX_SUBLABEL_LENGTH 63 // maximum length of a label, including final terminating zero (root sublabel) // according to RFC 2181, the maximum length is 255, not counting the root // sublabel. so, with the root sublabel, that means a max length of 256. #define MAX_LABEL_LENGTH 256 // jer's endian functions static unsigned short int net2short(const unsigned char **bufp) { unsigned short int i; i = **bufp; i <<= 8; i |= *(*bufp + 1); *bufp += 2; return i; } static unsigned long int net2long(const unsigned char **bufp) { unsigned long int l; l = **bufp; l <<= 8; l |= *(*bufp + 1); l <<= 8; l |= *(*bufp + 2); l <<= 8; l |= *(*bufp + 3); *bufp += 4; return l; } static void short2net(unsigned short int i, unsigned char **bufp) { *(*bufp + 1) = (unsigned char)i; i >>= 8; **bufp = (unsigned char)i; *bufp += 2; } static void long2net(unsigned long int l, unsigned char **bufp) { *(*bufp + 3) = (unsigned char)l; l >>= 8; *(*bufp + 2) = (unsigned char)l; l >>= 8; *(*bufp + 1) = (unsigned char)l; l >>= 8; **bufp = (unsigned char)l; *bufp += 4; } // label stuff typedef struct jdns_packet_label { JDNS_OBJECT int offset; jdns_string_t *value; } jdns_packet_label_t; static void jdns_packet_label_delete(jdns_packet_label_t *a); static jdns_packet_label_t *jdns_packet_label_copy(const jdns_packet_label_t *a); static jdns_packet_label_t *jdns_packet_label_new() { jdns_packet_label_t *a = JDNS_OBJECT_NEW(jdns_packet_label); a->offset = 0; a->value = 0; return a; } jdns_packet_label_t *jdns_packet_label_copy(const jdns_packet_label_t *a) { jdns_packet_label_t *c = jdns_packet_label_new(); c->offset = a->offset; if(a->value) c->value = jdns_string_copy(a->value); return c; } void jdns_packet_label_delete(jdns_packet_label_t *a) { if(!a) return; jdns_string_delete(a->value); jdns_object_free(a); } // gets an offset for decompression. does range and hop count checking also static int getoffset(const unsigned char *str, int refsize, int *hopsleft) { unsigned short int x; if(*hopsleft <= 0) return -1; --(*hopsleft); x = str[0] & 0x3f; x <<= 8; x |= str[1]; // stay in bounds if(x >= refsize) return -1; return x; } static int readlabel(const unsigned char *in, int insize, const unsigned char *ref, int refsize, int *_at, jdns_string_t **name) { int at; // string format is one character smaller than dns format. e.g.: // dns: [7] affinix [3] com [0] = 13 bytes // string: "affinix.com." = 12 bytes // only exception is '.' itself, but that won't influence the max. unsigned char out[MAX_LABEL_LENGTH - 1]; int out_size; const unsigned char *label, *last; int hopped_yet; int hopsleft; int label_size; at = *_at; // stay in range if(at < 0 || at >= insize) return 0; out_size = 0; label = in + at; hopped_yet = 0; last = in + insize; while(1) { // need a byte if(label + 1 > last) goto error; // we make this a while loop instead of an 'if', in case // there's a pointer to a pointer. as a precaution, // we will hop no more than 8 times hopsleft = 8; while(*label & 0xc0) { int offset; // need the next byte, too if(label + 2 > last) goto error; offset = getoffset(label, refsize, &hopsleft); if(offset == -1) goto error; label = ref + offset; if(!hopped_yet) { at += 2; hopped_yet = 1; last = ref + refsize; } // need a byte if(label + 1 > last) goto error; } label_size = *label & 0x3f; // null label? then we're done if(label_size == 0) { if(!hopped_yet) ++at; break; } // enough source bytes? (length byte + length) if(label + label_size + 1 > last) goto error; // enough dest bytes? (length + dot) if(out_size + label_size + 1 > MAX_LABEL_LENGTH - 1) goto error; memcpy(out + out_size, label + 1, label_size); out_size += label_size; out[out_size] = '.'; ++out_size; if(!hopped_yet) at += label_size + 1; label += label_size + 1; } *_at = at; *name = jdns_string_new(); jdns_string_set(*name, out, out_size); return 1; error: return 0; } // this function compares labels in label format: // [length] [value ...] [length] [value ...] [0] static int matchlabel(const unsigned char *a, int asize, const unsigned char *b, int bsize, const unsigned char *ref, int refsize, int ahopsleft, int bhopsleft) { int n, alen, blen, offset; // same pointer? if(a == b) return 1; if(asize < 1 || bsize < 1) return 0; // always ensure we get called without a pointer if(*a & 0xc0) { if(asize < 2) return 0; offset = getoffset(a, refsize, &ahopsleft); if(offset == -1) return 0; return matchlabel(ref + offset, refsize - offset, b, bsize, ref, refsize, ahopsleft, bhopsleft); } if(*b & 0xc0) { if(bsize < 2) return 0; offset = getoffset(b, refsize, &bhopsleft); if(offset == -1) return 0; return matchlabel(a, asize, ref + offset, refsize - offset, ref, refsize, ahopsleft, bhopsleft); } alen = *a & 0x3f; blen = *b & 0x3f; // must be same length if(alen != blen) return 0; // done? if(alen == 0) return 1; // length byte + length + first byte of next label if(asize < alen + 2) return 0; if(bsize < blen + 2) return 0; // compare the value for(n = 1; n < alen + 1; ++n) { if(a[n] != b[n]) return 0; } // try next labels n = alen + 1; return matchlabel(a + n, asize - n, b + n, bsize - n, ref, refsize, ahopsleft, bhopsleft); } int jdns_packet_name_isvalid(const unsigned char *name, int size) { int n, at, len; // at least one byte, no larger than MAX_LABEL_LENGTH - 1 (one byte is // gained when converting to a label) if(size < 1 || size > (MAX_LABEL_LENGTH - 1)) return 0; // last byte must be a dot if(name[size - 1] != '.') return 0; // first byte can't be a dot if there are characters after if(size > 1 && name[0] == '.') return 0; // each sublabel must be between 1 and MAX_SUBLABEL_LENGTH in length at = 0; while(1) { // search for dot or end for(n = at; n < size; ++n) { if(name[n] == '.') break; } // length of last one is always zero if(n >= size) break; len = n - at; if(len < 1 || len > MAX_SUBLABEL_LENGTH) return 0; at = n + 1; // skip over the dot } return 1; } // this function assumes label is pointing to a MAX_LABEL_LENGTH byte buffer static int name_to_label(const jdns_string_t *name, unsigned char *label) { int n, i, at, len; if(!jdns_packet_name_isvalid(name->data, name->size)) return -1; if(name->size == 1) { label[0] = 0; return 1; } at = 0; i = 0; while(1) { // search for dot or end for(n = at; n < name->size; ++n) { if(name->data[n] == '.') break; } len = n - at; if(i + (len + 1) > MAX_LABEL_LENGTH) // length byte + length return 0; label[i++] = len; memcpy(label + i, name->data + at, len); i += len; if(n >= name->size) // end? break; at = n + 1; // skip over the dot } return i; } // lookup list is made of jdns_packet_labels static int writelabel(const jdns_string_t *name, int at, int left, unsigned char **bufp, jdns_list_t *lookup) { unsigned char label[MAX_LABEL_LENGTH]; int n, i, len; unsigned char *l; unsigned char *ref; int refsize; len = name_to_label(name, label); if(len == -1) return 0; ref = *bufp - at; refsize = at + left; for(n = 0; label[n]; n += label[n] + 1) { for(i = 0; i < lookup->count; ++i) { jdns_packet_label_t *pl = (jdns_packet_label_t *)lookup->item[i]; if(matchlabel(label + n, len - n, pl->value->data, pl->value->size, ref, refsize, 8, 8)) { // set up a pointer right here, overwriting // the length byte and the first content // byte of this section within 'label'. // this is safe, because the length value // will always be greater than zero, // ensuring we have two bytes available to // use. l = label + n; short2net((unsigned short int)pl->offset, &l); label[n] |= 0xc0; len = n + 2; // cut things short break; } } if(label[n] & 0xc0) // double loop, so break again break; } if(left < len) return 0; // copy into buffer, point there now memcpy(*bufp, label, len); l = *bufp; *bufp += len; // for each new label, store its location for future compression for(n = 0; l[n]; n += l[n] + 1) { jdns_string_t *str; jdns_packet_label_t *pl; if(l[n] & 0xc0) break; pl = jdns_packet_label_new(); str = jdns_string_new(); jdns_string_set(str, l + n, len - n); pl->offset = l + n - ref; pl->value = str; jdns_list_insert(lookup, pl, -1); } return 1; } //---------------------------------------------------------------------------- // jdns_packet_write //---------------------------------------------------------------------------- #define JDNS_PACKET_WRITE_RAW 0 #define JDNS_PACKET_WRITE_NAME 1 struct jdns_packet_write { JDNS_OBJECT int type; jdns_string_t *value; }; void jdns_packet_write_delete(jdns_packet_write_t *a); jdns_packet_write_t *jdns_packet_write_copy(const jdns_packet_write_t *a); jdns_packet_write_t *jdns_packet_write_new() { jdns_packet_write_t *a = JDNS_OBJECT_NEW(jdns_packet_write); a->type = 0; a->value = 0; return a; } jdns_packet_write_t *jdns_packet_write_copy(const jdns_packet_write_t *a) { jdns_packet_write_t *c = jdns_packet_write_new(); c->type = a->type; if(a->value) c->value = jdns_string_copy(a->value); return c; } void jdns_packet_write_delete(jdns_packet_write_t *a) { if(!a) return; jdns_string_delete(a->value); jdns_object_free(a); } //---------------------------------------------------------------------------- // jdns_packet_question //---------------------------------------------------------------------------- jdns_packet_question_t *jdns_packet_question_new() { jdns_packet_question_t *a = JDNS_OBJECT_NEW(jdns_packet_question); a->qname = 0; a->qtype = 0; a->qclass = 0; return a; } jdns_packet_question_t *jdns_packet_question_copy(const jdns_packet_question_t *a) { jdns_packet_question_t *c = jdns_packet_question_new(); if(a->qname) c->qname = jdns_string_copy(a->qname); c->qtype = a->qtype; c->qclass = a->qclass; return c; } void jdns_packet_question_delete(jdns_packet_question_t *a) { if(!a) return; jdns_string_delete(a->qname); jdns_object_free(a); } //---------------------------------------------------------------------------- // jdns_packet_resource //---------------------------------------------------------------------------- jdns_packet_resource_t *jdns_packet_resource_new() { jdns_packet_resource_t *a = JDNS_OBJECT_NEW(jdns_packet_resource); a->qname = 0; a->qtype = 0; a->qclass = 0; a->ttl = 0; a->rdlength = 0; a->rdata = 0; a->writelog = jdns_list_new(); a->writelog->valueList = 1; return a; } jdns_packet_resource_t *jdns_packet_resource_copy(const jdns_packet_resource_t *a) { jdns_packet_resource_t *c = jdns_packet_resource_new(); if(a->qname) c->qname = jdns_string_copy(a->qname); c->qtype = a->qtype; c->qclass = a->qclass; c->ttl = a->ttl; c->rdlength = a->rdlength; c->rdata = jdns_copy_array(a->rdata, a->rdlength); jdns_list_delete(c->writelog); c->writelog = jdns_list_copy(a->writelog); return c; } void jdns_packet_resource_delete(jdns_packet_resource_t *a) { if(!a) return; jdns_string_delete(a->qname); if(a->rdata) jdns_free(a->rdata); jdns_list_delete(a->writelog); jdns_object_free(a); } void jdns_packet_resource_add_bytes(jdns_packet_resource_t *a, const unsigned char *data, int size) { jdns_packet_write_t *write = jdns_packet_write_new(); write->type = JDNS_PACKET_WRITE_RAW; write->value = jdns_string_new(); jdns_string_set(write->value, data, size); jdns_list_insert_value(a->writelog, write, -1); jdns_packet_write_delete(write); } void jdns_packet_resource_add_name(jdns_packet_resource_t *a, const jdns_string_t *name) { jdns_packet_write_t *write = jdns_packet_write_new(); write->type = JDNS_PACKET_WRITE_NAME; write->value = jdns_string_copy(name); jdns_list_insert_value(a->writelog, write, -1); jdns_packet_write_delete(write); } int jdns_packet_resource_read_name(const jdns_packet_resource_t *a, const jdns_packet_t *p, int *at, jdns_string_t **name) { return readlabel(a->rdata, a->rdlength, p->raw_data, p->raw_size, at, name); } //---------------------------------------------------------------------------- // jdns_packet //---------------------------------------------------------------------------- // note: both process_qsection and process_rrsection modify the 'dest' list, // even if later items cause an error. this turns out to be convenient // for handling truncated dns packets static int process_qsection(jdns_list_t *dest, int count, const unsigned char *data, int size, const unsigned char **bufp) { int n; int offset, at; jdns_string_t *name = 0; const unsigned char *buf; buf = *bufp; for(n = 0; n < count; ++n) { jdns_packet_question_t *q; offset = buf - data; at = 0; if(!readlabel(data + offset, size - offset, data, size, &at, &name)) goto error; offset += at; // need 4 more bytes if(size - offset < 4) goto error; buf = data + offset; q = jdns_packet_question_new(); q->qname = name; name = 0; q->qtype = net2short(&buf); q->qclass = net2short(&buf); jdns_list_insert_value(dest, q, -1); jdns_packet_question_delete(q); } *bufp = buf; return 1; error: jdns_string_delete(name); return 0; } static int process_rrsection(jdns_list_t *dest, int count, const unsigned char *data, int size, const unsigned char **bufp) { int n; int offset, at; jdns_string_t *name = 0; const unsigned char *buf; buf = *bufp; for(n = 0; n < count; ++n) { jdns_packet_resource_t *r; offset = buf - data; at = 0; if(!readlabel(data + offset, size - offset, data, size, &at, &name)) goto error; offset += at; // need 10 more bytes if(offset + 10 > size) goto error; buf = data + offset; r = jdns_packet_resource_new(); r->qname = name; name = 0; r->qtype = net2short(&buf); r->qclass = net2short(&buf); r->ttl = net2long(&buf); // per RFC 2181, ttl is supposed to be a 31 bit number. if // the top bit of the 32 bit field is 1, then entire ttl is // to be considered 0. if(r->ttl & 0x80000000) r->ttl = 0; r->rdlength = net2short(&buf); offset = buf - data; // make sure we have enough for the rdata if(size - offset < r->rdlength) { jdns_packet_resource_delete(r); goto error; } r->rdata = jdns_copy_array(buf, r->rdlength); buf += r->rdlength; jdns_list_insert_value(dest, r, -1); jdns_packet_resource_delete(r); } *bufp = buf; return 1; error: jdns_string_delete(name); return 0; } static int append_qsection(const jdns_list_t *src, int at, int left, unsigned char **bufp, jdns_list_t *lookup) { unsigned char *buf, *start, *last; int n; buf = *bufp; start = buf - at; last = buf + left; for(n = 0; n < src->count; ++n) { jdns_packet_question_t *q = (jdns_packet_question_t *)src->item[n]; if(!writelabel(q->qname, buf - start, last - buf, &buf, lookup)) goto error; if(buf + 4 > last) goto error; short2net(q->qtype, &buf); short2net(q->qclass, &buf); } *bufp = buf; return 1; error: return 0; } static int append_rrsection(const jdns_list_t *src, int at, int left, unsigned char **bufp, jdns_list_t *lookup) { unsigned char *buf, *start, *last, *rdlengthp; int n, i; buf = *bufp; start = buf - at; last = buf + left; for(n = 0; n < src->count; ++n) { jdns_packet_resource_t *r = (jdns_packet_resource_t *)src->item[n]; if(!writelabel(r->qname, buf - start, last - buf, &buf, lookup)) goto error; if(buf + 10 > last) goto error; short2net(r->qtype, &buf); short2net(r->qclass, &buf); long2net(r->ttl, &buf); // skip over rdlength rdlengthp = buf; buf += 2; // play write log for(i = 0; i < r->writelog->count; ++i) { jdns_packet_write_t *write = (jdns_packet_write_t *)r->writelog->item[i]; if(write->type == JDNS_PACKET_WRITE_RAW) { if(buf + write->value->size > last) goto error; memcpy(buf, write->value->data, write->value->size); buf += write->value->size; } else // JDNS_PACKET_WRITE_NAME { if(!writelabel(write->value, buf - start, last - buf, &buf, lookup)) goto error; } } i = buf - rdlengthp; // should be rdata size + 2 short2net((unsigned short int)(i - 2), &rdlengthp); } *bufp = buf; return 1; error: return 0; } jdns_packet_t *jdns_packet_new() { jdns_packet_t *a = JDNS_OBJECT_NEW(jdns_packet); a->id = 0; a->opts.qr = 0; a->opts.opcode = 0; a->opts.aa = 0; a->opts.tc = 0; a->opts.rd = 0; a->opts.ra = 0; a->opts.z = 0; a->opts.rcode = 0; a->questions = jdns_list_new(); a->answerRecords = jdns_list_new(); a->authorityRecords = jdns_list_new(); a->additionalRecords = jdns_list_new(); a->questions->valueList = 1; a->answerRecords->valueList = 1; a->authorityRecords->valueList = 1; a->additionalRecords->valueList = 1; a->fully_parsed = 0; a->raw_size = 0; a->raw_data = 0; return a; } jdns_packet_t *jdns_packet_copy(const jdns_packet_t *a) { jdns_packet_t *c = jdns_packet_new(); c->id = a->id; c->opts.qr = a->opts.qr; c->opts.opcode = a->opts.opcode; c->opts.aa = a->opts.aa; c->opts.tc = a->opts.tc; c->opts.rd = a->opts.rd; c->opts.ra = a->opts.ra; c->opts.z = a->opts.z; c->opts.rcode = a->opts.rcode; jdns_list_delete(c->questions); jdns_list_delete(c->answerRecords); jdns_list_delete(c->authorityRecords); jdns_list_delete(c->additionalRecords); c->questions = jdns_list_copy(a->questions); c->answerRecords = jdns_list_copy(a->answerRecords); c->authorityRecords = jdns_list_copy(a->authorityRecords); c->additionalRecords = jdns_list_copy(a->additionalRecords); c->fully_parsed = a->fully_parsed; c->raw_size = a->raw_size; c->raw_data = jdns_copy_array(a->raw_data, a->raw_size); return c; } void jdns_packet_delete(jdns_packet_t *a) { if(!a) return; jdns_list_delete(a->questions); jdns_list_delete(a->answerRecords); jdns_list_delete(a->authorityRecords); jdns_list_delete(a->additionalRecords); if(a->raw_data) jdns_free(a->raw_data); jdns_object_free(a); } int jdns_packet_import(jdns_packet_t **a, const unsigned char *data, int size) { jdns_packet_t *tmp = 0; const unsigned char *buf; // need at least some data if(!data || size == 0) return 0; // header (id + options + item counts) is 12 bytes if(size < 12) goto error; tmp = jdns_packet_new(); buf = data; // id tmp->id = net2short(&buf); // options if(buf[0] & 0x80) // qr is bit 7 tmp->opts.qr = 1; tmp->opts.opcode = (buf[0] & 0x78) >> 3; // opcode is bits 6,5,4,3 if(buf[0] & 0x04) // aa is bit 2 tmp->opts.aa = 1; if(buf[0] & 0x02) // tc is bit 1 tmp->opts.tc = 1; if(buf[0] & 0x01) // rd is bit 0 tmp->opts.rd = 1; if(buf[1] & 0x80) // ra is bit 7 (second byte) tmp->opts.ra = 1; tmp->opts.z = (buf[1] & 0x70) >> 4; // z is bits 6,5,4 tmp->opts.rcode = buf[1] & 0x0f; // rcode is bits 3,2,1,0 buf += 2; // item counts tmp->qdcount = net2short(&buf); tmp->ancount = net2short(&buf); tmp->nscount = net2short(&buf); tmp->arcount = net2short(&buf); // if these fail, we don't count them as errors, since the packet // might have been truncated if(!process_qsection(tmp->questions, tmp->qdcount, data, size, &buf)) goto skip; if(!process_rrsection(tmp->answerRecords, tmp->ancount, data, size, &buf)) goto skip; if(!process_rrsection(tmp->authorityRecords, tmp->nscount, data, size, &buf)) goto skip; if(!process_rrsection(tmp->additionalRecords, tmp->arcount, data, size, &buf)) goto skip; tmp->fully_parsed = 1; skip: // keep the raw data for reference during rdata parsing tmp->raw_size = size; tmp->raw_data = jdns_copy_array(data, size); *a = tmp; return 1; error: jdns_packet_delete(tmp); return 0; } int jdns_packet_export(jdns_packet_t *a, int maxsize) { unsigned char *block = 0; unsigned char *buf, *last; unsigned char c; int size; jdns_list_t *lookup = 0; // to hold jdns_packet_label_t // clear out any existing raw data before we begin if(a->raw_data) { jdns_free(a->raw_data); a->raw_data = 0; a->raw_size = 0; } // preallocate size = maxsize; block = (unsigned char *)jdns_alloc(size); memset(block, 0, size); buf = block; last = block + size; if(size < 12) goto error; short2net(a->id, &buf); if(a->opts.qr) buf[0] |= 0x80; c = (unsigned char)a->opts.opcode; buf[0] |= c << 3; if(a->opts.aa) buf[0] |= 0x04; if(a->opts.tc) buf[0] |= 0x02; if(a->opts.rd) buf[0] |= 0x01; if(a->opts.ra) buf[1] |= 0x80; c = (unsigned char)a->opts.z; buf[1] |= c << 4; c = (unsigned char)a->opts.rcode; buf[1] |= c; buf += 2; short2net((unsigned short int)a->questions->count, &buf); short2net((unsigned short int)a->answerRecords->count, &buf); short2net((unsigned short int)a->authorityRecords->count, &buf); short2net((unsigned short int)a->additionalRecords->count, &buf); // append sections lookup = jdns_list_new(); lookup->autoDelete = 1; if(!append_qsection(a->questions, buf - block, last - buf, &buf, lookup)) goto error; if(!append_rrsection(a->answerRecords, buf - block, last - buf, &buf, lookup)) goto error; if(!append_rrsection(a->authorityRecords, buf - block, last - buf, &buf, lookup)) goto error; if(!append_rrsection(a->additionalRecords, buf - block, last - buf, &buf, lookup)) goto error; // done with all sections jdns_list_delete(lookup); // condense size = buf - block; block = (unsigned char *)jdns_realloc(block, size); // finalize a->qdcount = a->questions->count; a->ancount = a->answerRecords->count; a->nscount = a->authorityRecords->count; a->arcount = a->additionalRecords->count; a->raw_data = block; a->raw_size = size; return 1; error: jdns_list_delete(lookup); if(block) jdns_free(block); return 0; } psi-plus-snapshots-1.4.1456/iris/src/jdns/src/jdns/jdns_packet.h000066400000000000000000000102771370065651000244400ustar00rootroot00000000000000/* * Copyright (C) 2006 Justin Karneges * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef JDNS_PACKET_H #define JDNS_PACKET_H #include "jdns.h" // -- howto -- // // writing packets: // 1) call jdns_packet_new() // 2) populate the jdns_packet_t structure, using the functions // as necessary // 3) call jdns_packet_export() to populate the raw data of the packet // // reading packets: // 1) call jdns_packet_new() // 2) call jdns_packet_import() with the raw data // 3) the jdns_packet_t structure is now populated // // IMPORTANT: all names must be valid. that is, ending in a dot character int jdns_packet_name_isvalid(const unsigned char *name, int size); // 0 if not valid typedef struct jdns_packet_question { JDNS_OBJECT jdns_string_t *qname; unsigned short int qtype, qclass; } jdns_packet_question_t; jdns_packet_question_t *jdns_packet_question_new(); jdns_packet_question_t *jdns_packet_question_copy(const jdns_packet_question_t *a); void jdns_packet_question_delete(jdns_packet_question_t *a); typedef struct jdns_packet_write jdns_packet_write_t; typedef struct jdns_packet jdns_packet_t; typedef struct jdns_packet_resource { JDNS_OBJECT jdns_string_t *qname; unsigned short int qtype, qclass; unsigned long int ttl; // 31-bit number, top bit always 0 unsigned short int rdlength; unsigned char *rdata; // private jdns_list_t *writelog; // jdns_packet_write_t } jdns_packet_resource_t; jdns_packet_resource_t *jdns_packet_resource_new(); jdns_packet_resource_t *jdns_packet_resource_copy(const jdns_packet_resource_t *a); void jdns_packet_resource_delete(jdns_packet_resource_t *a); void jdns_packet_resource_add_bytes(jdns_packet_resource_t *a, const unsigned char *data, int size); void jdns_packet_resource_add_name(jdns_packet_resource_t *a, const jdns_string_t *name); int jdns_packet_resource_read_name(const jdns_packet_resource_t *a, const jdns_packet_t *p, int *at, jdns_string_t **name); struct jdns_packet { JDNS_OBJECT unsigned short int id; struct { unsigned short qr, opcode, aa, tc, rd, ra, z, rcode; } opts; // item counts as specified by the packet. do not use these // for iteration over the item lists, since they can be wrong // if the packet is truncated. int qdcount, ancount, nscount, arcount; // value lists jdns_list_t *questions; // jdns_packet_question_t jdns_list_t *answerRecords; // jdns_packet_resource_t jdns_list_t *authorityRecords; // jdns_packet_resource_t jdns_list_t *additionalRecords; // jdns_packet_resource_t // since dns packets are allowed to be truncated, it is possible // for a packet to not get fully parsed yet still be considered // successfully parsed. this flag means the packet was fully // parsed also. int fully_parsed; int raw_size; unsigned char *raw_data; }; jdns_packet_t *jdns_packet_new(); jdns_packet_t *jdns_packet_copy(const jdns_packet_t *a); void jdns_packet_delete(jdns_packet_t *a); int jdns_packet_import(jdns_packet_t **a, const unsigned char *data, int size); // 0 on fail int jdns_packet_export(jdns_packet_t *a, int maxsize); // 0 on fail #endif // JDNS_PACKET_H psi-plus-snapshots-1.4.1456/iris/src/jdns/src/jdns/jdns_sys.c000066400000000000000000000567611370065651000240120ustar00rootroot00000000000000/* * Copyright (C) 2005-2008 Justin Karneges * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* this code probes the system for dns settings. blah. q3dns strategies ---------------- windows: domain name, name server, "search list" found in windows registry here: HKEY_LOCAL_MACHINE System\CurrentControlSet\Services\Tcpip\Parameters <-- win nt+ System\CurrentControlSet\Services\VxD\MSTCP <-- win 98 for domain, try DhcpDomain else Domain for name servers, try DhcpNameServer, else NameServer for search list, try SearchList iphlpapi.dll : GetNetworkParams(PFIXED_INFO, PULONG); info->DomainName info->DnsServerList (if not null, use it, and loop through ->Next until null) no search list first try getnetworkparams. if that fails, try the nt regkey then the 98 regkey. it seems that search list can only come from the registry, so maybe we need to grab that entry even if getnetworkparams works. in the case of the registry, the nameserver and searchlist entries are delimited by spaces on win nt and commas on win 98. probably a good idea to simplify white space first (chop away space at start and end, reduce all sections of spaces to one char). also, lowercase the search list. qt doesn't read the hosts file on windows. this might be a good idea, but probably not worth it. unix: read /etc/resolv.conf manually: for each line, split at spaces if the first item is "nameserver", then there should be an IP address following it. note: may contain mixed ipv4 and ipv6 addresses if the first item is "search", all other items are added to the domain list if the first item is "domain", then the next item should be added to the domain list. do case-insensitive matching for the item types for search/domain, the items are in the 8-bit system locale info can also be fetched using system calls. we use the res_* stuff here. first we should detect for a "modern res api". this is available from glibc 2.3 and onward. use the following scheme to check for it: #if defined(__GLIBC__) && ((__GLIBC__ > 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 3))) // modern res api #endif on mac we should look up res_init in the system library. see: qt_mac_resolve_sys(RTLD_NEXT, "res_init"); for a hint. otherwise we can just use res_init() straight. under a modern res api, we do: struct __res_state res; res_ninit(&res); otherwise, we simply call res_init(). for the modern api, we use the "res" struct that we made. otherwise, we use the global "_res" struct. read the res struct to obtain the name servers, search list, and domain. lowercase the search list and domain. qt tries the file, and if that fails it tries the syscalls. we may want to do the syscalls first, or even just do both all the time. read /etc/hosts manually: for each line if there is a '#' character in the line, remove it and everything to the right simplify white space convert to lowercase split the line at spaces first item is the ip address all remaining items are hostnames note: these hosts could also be used for reverse-dns too note2: Windows has a hosts file as well (like C:\WINDOWS\hosts) */ #include "jdns_p.h" #ifdef JDNS_OS_UNIX # include # include # include # include #endif #ifdef JDNS_OS_WIN # include #endif #define string_indexOf jdns_string_indexOf #define string_split jdns_string_split static int char_isspace(unsigned char c) { if(c == ' ' || c == '\t' || c == '\n' || c == '\r') return 1; return 0; } static unsigned char *string_getnextword(unsigned char *in, int size, int pos, int *newpos) { int n; int at; int len; unsigned char *out; at = pos; // skip any space at the start while(at < size && char_isspace(in[at])) ++at; // all space? no word then if(at >= size) return 0; // skip until a space or end n = at; while(n < size && !char_isspace(in[n])) ++n; len = n - at; // allocate length + zero byte out = (unsigned char *)jdns_alloc(len + 1); if(!out) return 0; memcpy(out, in + at, len); out[len] = 0; *newpos = at + len; return out; } static jdns_string_t *string_simplify(const jdns_string_t *in) { int n; int pos; int total; unsigned char *out; int outlen; jdns_string_t *outstr; jdns_stringlist_t *wordlist; // gather words and total of lengths pos = 0; total = 0; wordlist = jdns_stringlist_new(); while(1) { jdns_string_t *word; unsigned char *str = string_getnextword(in->data, in->size, pos, &pos); if(!str) break; word = jdns_string_new(); jdns_string_set_cstr(word, (char *)str); jdns_free(str); jdns_stringlist_append(wordlist, word); total += word->size; jdns_string_delete(word); } if(total == 0) { jdns_stringlist_delete(wordlist); outstr = jdns_string_new(); jdns_string_set_cstr(outstr, ""); return outstr; } // we need to allocate space for total lengths and wordcount-1 spaces outlen = total + (wordlist->count - 1); out = (unsigned char *)jdns_alloc(outlen); // lay out the words pos = 0; for(n = 0; n < wordlist->count; ++n) { unsigned char *data = wordlist->item[n]->data; int size = wordlist->item[n]->size; memcpy(out + pos, data, size); pos += size; // if this is not the last word, append a space if(n + 1 < wordlist->count) out[pos++] = ' '; } jdns_stringlist_delete(wordlist); outstr = jdns_string_new(); jdns_string_set(outstr, out, outlen); jdns_free(out); return outstr; } static jdns_string_t *string_tolower(const jdns_string_t *in) { int n; jdns_string_t *out = jdns_string_copy(in); for(n = 0; n < out->size; ++n) out->data[n] = tolower(out->data[n]); return out; } static jdns_string_t *file_nextline(FILE *f) { int at, size; unsigned char *buf; jdns_string_t *str; size = 1023; buf = (unsigned char *)jdns_alloc(size); at = 0; while(1) { unsigned char c = fgetc(f); if(feof(f)) { if(at > 0) { // if we read at least one char, take it as a // line break; } else { jdns_free(buf); return 0; } } if(c == '\n') break; if(c == '\r') continue; if(at < 1023) buf[at++] = c; } str = jdns_string_new(); jdns_string_set(str, buf, at); jdns_free(buf); return str; } static jdns_dnshostlist_t *read_hosts_file(const char *path) { jdns_dnshostlist_t *out; FILE *f; jdns_string_t *line, *simp; jdns_stringlist_t *parts; jdns_address_t *addr; int n; out = jdns_dnshostlist_new(); f = jdns_fopen(path, "r"); if(!f) return out; while(1) { line = file_nextline(f); if(!line) break; // truncate at comment n = string_indexOf(line, '#', 0); if(n != -1) { line->size = n; line->data[n] = 0; } simp = string_simplify(line); jdns_string_delete(line); parts = string_split(simp, ' '); jdns_string_delete(simp); if(parts->count < 2) { jdns_stringlist_delete(parts); continue; } addr = jdns_address_new(); if(!jdns_address_set_cstr(addr, (const char *)parts->item[0]->data)) { jdns_address_delete(addr); jdns_stringlist_delete(parts); continue; } for(n = 1; n < parts->count; ++n) { jdns_dnshost_t *h = jdns_dnshost_new(); h->name = jdns_string_copy(parts->item[n]); h->address = jdns_address_copy(addr); jdns_dnshostlist_append(out, h); jdns_dnshost_delete(h); } jdns_address_delete(addr); jdns_stringlist_delete(parts); } fclose(f); return out; } static void apply_hosts_file(jdns_dnsparams_t *a, const char *path) { int n; jdns_dnshostlist_t *list; list = read_hosts_file(path); for(n = 0; n < list->count; ++n) jdns_dnshostlist_append(a->hosts, list->item[n]); jdns_dnshostlist_delete(list); } static int dnsparams_have_domain(const jdns_dnsparams_t *a, const jdns_string_t *domain) { int n; for(n = 0; n < a->domains->count; ++n) { jdns_string_t *str = a->domains->item[n]; if(strcmp((const char *)str->data, (const char *)domain->data) == 0) return 1; } return 0; } #ifdef JDNS_OS_WIN // from Microsoft IPTypes.h #ifndef IP_TYPES_INCLUDED #define MAX_HOSTNAME_LEN 128 #define MAX_DOMAIN_NAME_LEN 128 #define MAX_SCOPE_ID_LEN 256 typedef struct { char String[4 * 4]; } IP_ADDRESS_STRING, *PIP_ADDRESS_STRING, IP_MASK_STRING, *PIP_MASK_STRING; typedef struct _IP_ADDR_STRING { struct _IP_ADDR_STRING* Next; IP_ADDRESS_STRING IpAddress; IP_MASK_STRING IpMask; DWORD Context; } IP_ADDR_STRING, *PIP_ADDR_STRING; typedef struct { char HostName[MAX_HOSTNAME_LEN + 4] ; char DomainName[MAX_DOMAIN_NAME_LEN + 4]; PIP_ADDR_STRING CurrentDnsServer; IP_ADDR_STRING DnsServerList; UINT NodeType; char ScopeId[MAX_SCOPE_ID_LEN + 4]; UINT EnableRouting; UINT EnableProxy; UINT EnableDns; } FIXED_INFO, *PFIXED_INFO; #endif typedef DWORD (WINAPI *GetNetworkParamsFunc)(PFIXED_INFO, PULONG); static jdns_string_t *reg_readString(HKEY hk, const char *subkey) { char *buf; DWORD bufsize; int ret; jdns_string_t *str = 0; bufsize = 1024; buf = (char *)jdns_alloc((int)bufsize); if(!buf) return 0; ret = RegQueryValueExA(hk, subkey, 0, 0, (LPBYTE)buf, &bufsize); if(ret == ERROR_MORE_DATA) { buf = (char *)jdns_realloc(buf, bufsize); if(!buf) { jdns_free(buf); return 0; } ret = RegQueryValueExA(hk, subkey, 0, 0, (LPBYTE)buf, &bufsize); } if(ret == ERROR_SUCCESS) { str = jdns_string_new(); jdns_string_set_cstr(str, (char *)buf); } jdns_free(buf); return str; } static jdns_dnsparams_t *dnsparams_get_winreg() { int n; jdns_dnsparams_t *params; HKEY key; int ret; char sep; jdns_string_t *str_domain, *str_nameserver, *str_searchlist; jdns_stringlist_t *list_nameserver, *list_searchlist; sep = ' '; ret = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "System\\CurrentControlSet\\Services\\Tcpip\\Parameters", 0, KEY_READ, &key); if(ret != ERROR_SUCCESS) { sep = ','; ret = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "System\\CurrentControlSet\\Services\\VxD\\MSTCP", 0, KEY_READ, &key); if(ret != ERROR_SUCCESS) return 0; } str_domain = reg_readString(key, "DhcpDomain"); if(!str_domain) str_domain = reg_readString(key, "Domain"); str_nameserver = reg_readString(key, "DhcpNameServer"); if(!str_nameserver) str_nameserver = reg_readString(key, "NameServer"); str_searchlist = reg_readString(key, "SearchList"); RegCloseKey(key); list_nameserver = 0; if(str_nameserver) { list_nameserver = string_split(str_nameserver, sep); jdns_string_delete(str_nameserver); } list_searchlist = 0; if(str_searchlist) { // lowercase the string jdns_string_t *p = string_tolower(str_searchlist); jdns_string_delete(str_searchlist); str_searchlist = p; list_searchlist = string_split(str_searchlist, sep); jdns_string_delete(str_searchlist); } params = jdns_dnsparams_new(); if(list_nameserver) { // qt seems to do a strange thing here by running each name // server address through the q3dns setLabel function, and // then pulls the result as a list of addresses. i have // no idea why they do this, or how one IP address would // turn into anything else, let alone several addresses. // so, uh, we're not going to do that. for(n = 0; n < list_nameserver->count; ++n) { jdns_address_t *addr = jdns_address_new(); if(jdns_address_set_cstr(addr, (char *)list_nameserver->item[n]->data)) jdns_dnsparams_append_nameserver(params, addr, JDNS_UNICAST_PORT); jdns_address_delete(addr); } jdns_stringlist_delete(list_nameserver); } if(str_domain) { if(str_domain->size > 0) jdns_dnsparams_append_domain(params, str_domain); jdns_string_delete(str_domain); } if(list_searchlist) { for(n = 0; n < list_searchlist->count; ++n) { if(list_searchlist->item[n]->size > 0) jdns_dnsparams_append_domain(params, list_searchlist->item[n]); } jdns_stringlist_delete(list_searchlist); } return params; } static jdns_dnsparams_t *dnsparams_get_winsys() { jdns_dnsparams_t *params; GetNetworkParamsFunc myGetNetworkParams; DWORD ret; HINSTANCE lib; jdns_address_t *addr; jdns_string_t *str; IP_ADDR_STRING *ipstr; lib = LoadLibraryA("iphlpapi"); if(!lib) return 0; params = 0; myGetNetworkParams = (GetNetworkParamsFunc)GetProcAddress(lib, "GetNetworkParams"); if(myGetNetworkParams) { ULONG bufsize = 0; ret = myGetNetworkParams(0, &bufsize); if(ret == ERROR_BUFFER_OVERFLOW) { FIXED_INFO *info = (FIXED_INFO *)jdns_alloc((int)bufsize); ret = myGetNetworkParams(info, &bufsize); if(ret == ERROR_SUCCESS) { params = jdns_dnsparams_new(); ipstr = &info->DnsServerList; while(ipstr) { addr = jdns_address_new(); if(jdns_address_set_cstr(addr, (char *)ipstr->IpAddress.String)) jdns_dnsparams_append_nameserver(params, addr, JDNS_UNICAST_PORT); jdns_address_delete(addr); ipstr = ipstr->Next; } str = jdns_string_new(); jdns_string_set_cstr(str, info->DomainName); if(str->size > 0) jdns_dnsparams_append_domain(params, str); jdns_string_delete(str); } jdns_free(info); } } FreeLibrary(lib); return params; } static void apply_hosts_var_filepath(jdns_dnsparams_t *a, const char *envvar, const char *path) { jdns_string_t *e; char *str; int elen, plen; e = jdns_getenv(envvar); if(!e) return; elen = strlen((char *)e->data); plen = strlen(path); str = (char *)jdns_alloc(elen + plen + 1); memcpy(str, e->data, elen); jdns_string_delete(e); jdns_strcpy(str + elen, path); apply_hosts_file(a, str); jdns_free(str); } static void apply_win_hosts_file(jdns_dnsparams_t *a) { // windows 64-bit apply_hosts_var_filepath(a, "SystemRoot", "\\SysWOW64\\drivers\\etc\\hosts"); // winnt+ apply_hosts_var_filepath(a, "SystemRoot", "\\system32\\drivers\\etc\\hosts"); // win9x apply_hosts_var_filepath(a, "WINDIR", "\\hosts"); } static jdns_dnsparams_t *dnsparams_get_win() { int n; jdns_dnsparams_t *sys_params, *reg_params; reg_params = dnsparams_get_winreg(); sys_params = dnsparams_get_winsys(); // no sys params? take the reg params then if(!sys_params) { apply_win_hosts_file(reg_params); return reg_params; } // sys params don't have a search list, so merge the domains from // the registry if possible if(reg_params) { for(n = 0; n < reg_params->domains->count; ++n) { jdns_string_t *reg_str = reg_params->domains->item[n]; // don't add dups if(!dnsparams_have_domain(sys_params, reg_str)) jdns_dnsparams_append_domain(sys_params, reg_str); } jdns_dnsparams_delete(reg_params); } apply_win_hosts_file(sys_params); return sys_params; } #endif #ifdef JDNS_OS_UNIX static jdns_dnsparams_t *dnsparams_get_unixfiles() { FILE *f; int n; jdns_dnsparams_t *params; jdns_string_t *line, *simp; jdns_stringlist_t *parts; params = jdns_dnsparams_new(); f = jdns_fopen("/etc/resolv.conf", "r"); if(!f) return params; while(1) { line = file_nextline(f); if(!line) break; // truncate at comment n = string_indexOf(line, '#', 0); if(n != -1) { line->size = n; line->data[n] = 0; } simp = string_simplify(line); jdns_string_delete(line); parts = string_split(simp, ' '); jdns_string_delete(simp); if(parts->count < 2) { jdns_stringlist_delete(parts); continue; } simp = string_tolower(parts->item[0]); if(strcmp((char *)simp->data, "nameserver") == 0) { jdns_address_t *addr = jdns_address_new(); jdns_address_set_cstr(addr, (const char *)parts->item[1]->data); jdns_dnsparams_append_nameserver(params, addr, JDNS_UNICAST_PORT); jdns_address_delete(addr); } else if(strcmp((char *)simp->data, "search") == 0) { for(n = 1; n < parts->count; ++n) { jdns_dnsparams_append_domain(params, parts->item[n]); } } else if(strcmp((char *)simp->data, "domain") == 0) { jdns_dnsparams_append_domain(params, parts->item[1]); } jdns_string_delete(simp); jdns_stringlist_delete(parts); } fclose(f); return params; } #if defined(__GLIBC__) && ((__GLIBC__ > 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 3))) # define JDNS_MODERN_RES_API #endif #ifndef JDNS_MODERN_RES_API typedef int (*res_init_func)(); static int my_res_init() { #ifdef JDNS_OS_MAC res_init_func mac_res_init; // look up res_init in the system library (qt does this, not sure why) mac_res_init = (res_init_func)dlsym(RTLD_NEXT, "res_init"); if(!mac_res_init) return -1; return mac_res_init(); #else return res_init(); #endif } #endif // on some platforms, __res_state_ext exists as a struct but it is not // a define, so the #ifdef doesn't work. as a workaround, we'll explicitly // specify the platforms that have __res_state_ext //#ifdef __res_state_ext #if defined(JDNS_OS_MAC) || defined(JDNS_OS_FREEBSD) || \ defined(JDNS_OS_NETBSD) || defined (JDNS_OS_SOLARIS) || defined (JDNS_OS_HAIKU) # define USE_EXTEXT #endif #if defined(JDNS_OS_OPENBSD) # define USE_INDEP_EXT #endif static jdns_dnsparams_t *dnsparams_get_unixsys() { int n; jdns_dnsparams_t *params; #ifdef JDNS_MODERN_RES_API struct __res_state res; memset(&res, 0, sizeof(struct __res_state)); n = res_ninit(&res); #define RESVAR res #else n = my_res_init(); #define RESVAR _res #endif params = jdns_dnsparams_new(); // error initializing? if(n == -1) return params; #ifdef USE_INDEP_EXT for(n = 0; n < MAXNS && n < RESVAR.nscount; ++n) { struct sockaddr_in *sa = (struct sockaddr_in*)&(_res_ext.nsaddr_list[n]); jdns_address_t *addr = jdns_address_new(); if (sa->sin_family = AF_INET6) { struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)sa; jdns_address_set_ipv6(addr, sa6->sin6_addr.s6_addr); } else { jdns_address_set_ipv4(addr, ntohl(sa->sin_addr.s_addr)); } jdns_dnsparams_append_nameserver(params, addr, JDNS_UNICAST_PORT); jdns_address_delete(addr); } #else // nameservers - ipv6 #ifdef __GLIBC__ for(n = 0; n < MAXNS; ++n) #else for(n = 0; n < MAXNS && n < RESVAR._u._ext.nscount; ++n) #endif { jdns_address_t *addr; struct sockaddr_in6 *sa6; #ifdef USE_EXTEXT // seems _ext.ext can be null in some cases... if(RESVAR._u._ext.ext == NULL) break; sa6 = ((struct sockaddr_in6 *)RESVAR._u._ext.ext) + n; #else sa6 = RESVAR._u._ext.nsaddrs[n]; #endif if(sa6 == NULL) continue; addr = jdns_address_new(); jdns_address_set_ipv6(addr, sa6->sin6_addr.s6_addr); jdns_dnsparams_append_nameserver(params, addr, JDNS_UNICAST_PORT); jdns_address_delete(addr); } // nameservers - ipv4 #ifdef __GLIBC__ int ns4count = RESVAR.nscount - RESVAR._u._ext.nscount6; for(n = 0; n < MAXNS && n < ns4count; ++n) #else for(n = 0; n < MAXNS && n < RESVAR.nscount; ++n) #endif { jdns_address_t *addr = jdns_address_new(); jdns_address_set_ipv4(addr, ntohl(RESVAR.nsaddr_list[n].sin_addr.s_addr)); jdns_dnsparams_append_nameserver(params, addr, JDNS_UNICAST_PORT); jdns_address_delete(addr); } #endif // domain name if(strlen(RESVAR.defdname) > 0) { jdns_string_t *str; jdns_string_t *p; str = jdns_string_new(); jdns_string_set_cstr(str, RESVAR.defdname); p = string_tolower(str); jdns_string_delete(str); str = p; jdns_dnsparams_append_domain(params, str); jdns_string_delete(str); } // search list #ifdef MAXDFLSRCH for(n = 0; n < MAXDFLSRCH && RESVAR.dnsrch[n]; ++n) { if(strlen(RESVAR.dnsrch[n]) > 0) { jdns_string_t *str; jdns_string_t *p; str = jdns_string_new(); jdns_string_set_cstr(str, RESVAR.dnsrch[n]); p = string_tolower(str); jdns_string_delete(str); str = p; // don't add dups if(!dnsparams_have_domain(params, str)) jdns_dnsparams_append_domain(params, str); jdns_string_delete(str); } } #endif return params; } static jdns_dnsparams_t *dnsparams_get_unix() { jdns_dnsparams_t *params; // prefer system calls over files params = dnsparams_get_unixsys(); if(params->nameservers->count == 0) { jdns_dnsparams_delete(params); params = dnsparams_get_unixfiles(); } apply_hosts_file(params, "/etc/hosts"); return params; } #endif jdns_dnsparams_t *jdns_system_dnsparams() { #ifdef JDNS_OS_WIN return dnsparams_get_win(); #else return dnsparams_get_unix(); #endif } psi-plus-snapshots-1.4.1456/iris/src/jdns/src/jdns/jdns_util.c000066400000000000000000001165341370065651000241440ustar00rootroot00000000000000/* * Copyright (C) 2005-2008 Justin Karneges * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "jdns_p.h" #include "jdns_packet.h" //---------------------------------------------------------------------------- // misc //---------------------------------------------------------------------------- void *jdns_alloc(int size) { return malloc(size); } void *jdns_realloc(void *p, int size) { return realloc(p, size); } void jdns_free(void *p) { free(p); } char *jdns_strdup(const char *s) { char *p; int len; len = strlen(s) + 1; // the zero p = (char *)jdns_alloc(len); memcpy(p, s, len); return p; } unsigned char *jdns_copy_array(const unsigned char *src, int size) { unsigned char *out; if(size <= 0) return 0; out = (unsigned char *)jdns_alloc(size); memcpy(out, src, size); return out; } int jdns_domain_cmp(const unsigned char *a, const unsigned char *b) { int n; int len_a; // case-insensitive compare len_a = _ustrlen(a); if(len_a != (int)_ustrlen(b)) return 0; for(n = 0; n < len_a; ++n) { if(tolower(a[n]) != tolower(b[n])) return 0; } return 1; } int jdns_sprintf_s(char *str, int n, const char *format, ...) { int ret; va_list ap; va_start(ap, format); ret = jdns_vsprintf_s(str, n, format, ap); va_end(ap); return ret; } int jdns_vsprintf_s(char *str, int n, const char *format, va_list ap) { #if defined(_MSC_VER) && _MSC_VER >= 1400 return vsprintf_s(str, n, format, ap); #else (void)n; return vsprintf(str, format, ap); #endif } FILE *jdns_fopen(const char *path, const char *mode) { #if defined(_MSC_VER) && _MSC_VER >= 1400 FILE *fp; if(fopen_s(&fp, path, mode) != 0) return 0; return fp; #else return fopen(path, mode); #endif } jdns_string_t *jdns_getenv(const char *name) { #if defined(_MSC_VER) && _MSC_VER >= 1400 jdns_string_t *out; char *dest; size_t size; int sizei; errno_t ret; ret = getenv_s(&size, 0, 0, name); if(ret != 0 || size == 0) return 0; sizei = (int)size; dest = (char *)jdns_alloc(sizei); ret = getenv_s(&size, dest, size, name); if(ret != 0) { free(dest); return 0; } out = jdns_string_new(); out->size = sizei - 1; out->data = dest; // must be zero-terminated, which it is return out; #else jdns_string_t *out; char *val; val = getenv(name); if(!val) return 0; out = jdns_string_new(); jdns_string_set_cstr(out, val); return out; #endif } char *jdns_strcpy(char *dst, const char *src) { #if defined(_MSC_VER) && _MSC_VER >= 1400 int len; // deliberately unsafe len = strlen(src); if(strcpy_s(dst, len + 1, src) != 0) return 0; return dst; #else return strcpy(dst, src); #endif } //---------------------------------------------------------------------------- // jdns_object //---------------------------------------------------------------------------- void *jdns_object_new(int size, void (*dtor)(void *), void *(*cctor)(const void *)) { jdns_object_t *a = (jdns_object_t *)jdns_alloc(size); memset(a, 0, size); a->dtor = dtor; a->cctor = cctor; return a; } void *jdns_object_copy(const void *a) { return ((const jdns_object_t *)a)->cctor(a); } void jdns_object_delete(void *a) { ((jdns_object_t *)a)->dtor(a); } void jdns_object_free(void *a) { jdns_free(a); } //---------------------------------------------------------------------------- // jdns_list //---------------------------------------------------------------------------- jdns_list_t *jdns_list_new() { jdns_list_t *a = JDNS_OBJECT_NEW(jdns_list); a->count = 0; a->item = 0; a->valueList = 0; a->autoDelete = 0; return a; } jdns_list_t *jdns_list_copy(const jdns_list_t *a) { jdns_list_t *c = jdns_list_new(); // note: copying a list with autoDelete should not ever be done. // heck, let's not even allow it. return an empty list. if(a->autoDelete) return c; c->valueList = a->valueList; // copy the items if(a->item) { int n; c->count = a->count; c->item = (void **)jdns_alloc(sizeof(void *) * c->count); if(a->valueList) { // deep copy for(n = 0; n < c->count; ++n) c->item[n] = jdns_object_copy(a->item[n]); } else { // just the pointer for(n = 0; n < c->count; ++n) c->item[n] = a->item[n]; } } return c; } void jdns_list_delete(jdns_list_t *a) { if(!a) return; jdns_list_clear(a); jdns_object_free(a); } void jdns_list_clear(jdns_list_t *a) { if(a->item) { // delete the items if necessary if(a->valueList || a->autoDelete) { int n; for(n = 0; n < a->count; ++n) jdns_object_delete(a->item[n]); } jdns_free(a->item); a->item = 0; a->count = 0; } } void jdns_list_insert(jdns_list_t *a, void *item, int pos) { // make memory if(!a->item) a->item = (void **)jdns_alloc(sizeof(void *)); else a->item = (void **)jdns_realloc(a->item, sizeof(void *) * (a->count + 1)); // prepare position if(pos != -1) memmove(a->item + pos + 1, a->item + pos, (a->count - pos) * sizeof(void *)); else pos = a->count; // insert it if(a->valueList) a->item[pos] = jdns_object_copy(item); else a->item[pos] = item; ++a->count; } void jdns_list_insert_value(jdns_list_t *a, const void *item, int pos) { jdns_list_insert(a, (void *)item, pos); } void jdns_list_remove(jdns_list_t *a, void *item) { int n; int pos = -1; for(n = 0; n < a->count; ++n) { if(a->item[n] == item) { pos = n; break; } } if(pos == -1) return; jdns_list_remove_at(a, pos); } void jdns_list_remove_at(jdns_list_t *a, int pos) { if(pos < 0 || pos >= a->count) return; // delete the item if necessary if(a->valueList || a->autoDelete) jdns_object_delete(a->item[pos]); // free the position if(a->count > 1) { memmove(a->item + pos, a->item + pos + 1, (a->count - pos - 1) * sizeof(void *)); --a->count; } else { jdns_free(a->item); a->item = 0; a->count = 0; } } //---------------------------------------------------------------------------- // jdns_string //---------------------------------------------------------------------------- jdns_string_t *jdns_string_new() { jdns_string_t *s = JDNS_OBJECT_NEW(jdns_string); s->data = 0; s->size = 0; return s; } jdns_string_t *jdns_string_copy(const jdns_string_t *s) { jdns_string_t *c = jdns_string_new(); if(s->data) jdns_string_set(c, s->data, s->size); return c; } void jdns_string_delete(jdns_string_t *s) { if(!s) return; if(s->data) jdns_free(s->data); jdns_object_free(s); } void jdns_string_set(jdns_string_t *s, const unsigned char *str, int str_len) { if(s->data) jdns_free(s->data); s->data = (unsigned char *)jdns_alloc(str_len + 1); memcpy(s->data, str, str_len); s->data[str_len] = 0; s->size = str_len; } void jdns_string_set_cstr(jdns_string_t *s, const char *str) { jdns_string_set(s, (const unsigned char *)str, strlen(str)); } int jdns_string_indexOf(const jdns_string_t *s, unsigned char c, int pos) { int n; for(n = pos; n < s->size; ++n) { if(s->data[n] == c) return n; } return -1; } jdns_stringlist_t *jdns_string_split(const jdns_string_t *s, unsigned char sep) { int at, n, len; jdns_string_t *str; jdns_stringlist_t *out; at = 0; out = jdns_stringlist_new(); while(at < s->size) { n = jdns_string_indexOf(s, sep, at); if(n == -1) n = s->size; len = n - at; // FIXME: should we allow empty items? //if(len == 0) // break; str = jdns_string_new(); jdns_string_set(str, s->data + at, len); jdns_stringlist_append(out, str); jdns_string_delete(str); at = n + 1; // skip over separator } return out; } //---------------------------------------------------------------------------- // jdns_stringlist //---------------------------------------------------------------------------- jdns_stringlist_t *jdns_stringlist_new() { jdns_list_t *a = jdns_list_new(); a->valueList = 1; return (jdns_stringlist_t *)a; } jdns_stringlist_t *jdns_stringlist_copy(const jdns_stringlist_t *a) { return (jdns_stringlist_t *)jdns_list_copy((const jdns_list_t *)a); } void jdns_stringlist_delete(jdns_stringlist_t *a) { jdns_list_delete((jdns_list_t *)a); // note: no need to call jdns_object_free() here } void jdns_stringlist_append(jdns_stringlist_t *a, const jdns_string_t *str) { jdns_list_insert_value((jdns_list_t *)a, str, -1); } //---------------------------------------------------------------------------- // jdns_address //---------------------------------------------------------------------------- jdns_address_t *jdns_address_new() { jdns_address_t *a = alloc_type(jdns_address_t); a->isIpv6 = 0; a->addr.v4 = 0; a->c_str = jdns_strdup(""); return a; } jdns_address_t *jdns_address_copy(const jdns_address_t *a) { jdns_address_t *c = jdns_address_new(); if(a->isIpv6) jdns_address_set_ipv6(c, a->addr.v6); else jdns_address_set_ipv4(c, a->addr.v4); return c; } void jdns_address_delete(jdns_address_t *a) { if(!a) return; if(a->isIpv6) jdns_free(a->addr.v6); jdns_free(a->c_str); jdns_free(a); } void jdns_address_set_ipv4(jdns_address_t *a, unsigned long int ipv4) { if(a->isIpv6) jdns_free(a->addr.v6); jdns_free(a->c_str); a->isIpv6 = 0; a->addr.v4 = ipv4; a->c_str = (char *)jdns_alloc(16); // max size (3 * 4 + 3 + 1) jdns_sprintf_s(a->c_str, 16, "%d.%d.%d.%d", (unsigned char)((ipv4 >> 24) & 0xff), (unsigned char)((ipv4 >> 16) & 0xff), (unsigned char)((ipv4 >> 8) & 0xff), (unsigned char)((ipv4) & 0xff)); } void jdns_address_set_ipv6(jdns_address_t *a, const unsigned char *ipv6) { int n; unsigned char *p; unsigned short word[8]; if(a->isIpv6) jdns_free(a->addr.v6); jdns_free(a->c_str); a->isIpv6 = 1; a->addr.v6 = (unsigned char *)jdns_alloc(16); memcpy(a->addr.v6, ipv6, 16); p = (unsigned char *)a->addr.v6; a->c_str = (char *)jdns_alloc(40); // max size (8 * 4 + 7 + 1) // each word in a 16-byte ipv6 address is network byte order for(n = 0; n < 8; ++n) word[n] = ((unsigned short)(p[n * 2]) << 8) + (unsigned short)(p[n * 2 + 1]); jdns_sprintf_s(a->c_str, 40, "%04X:%04X:%04X:%04X:%04X:%04X:%04X:%04X", word[0], word[1], word[2], word[3], word[4], word[5], word[6], word[7]); } int jdns_address_set_cstr(jdns_address_t *a, const char *str) { int slen = strlen(str); // ipv6 if(strchr(str, ':')) { jdns_string_t *in; jdns_stringlist_t *list; unsigned char ipv6[16]; int n, at, count, fill; in = jdns_string_new(); jdns_string_set_cstr(in, str); list = jdns_string_split(in, ':'); jdns_string_delete(in); // a confusing outputting-backwards parser adapted from qt count = list->count; if(count < 3 || count > 8) goto error; at = 16; fill = 9 - count; for(n = count - 1; n >= 0; --n) { if(at <= 0) goto error; if(list->item[n]->size == 0) { if(n == count - 1) { if(list->item[n - 1]->size != 0) goto error; ipv6[--at] = 0; ipv6[--at] = 0; } else if(n == 0) { if(list->item[n + 1]->size != 0) goto error; ipv6[--at] = 0; ipv6[--at] = 0; } else { int i; for(i = 0; i < fill; ++i) { if(at <= 0) goto error; ipv6[--at] = 0; ipv6[--at] = 0; } } } else { if(jdns_string_indexOf(list->item[n], '.', 0) == -1) { int x; x = strtol((const char *)list->item[n]->data, NULL, 16); if(x < 0 || x > 0xffff) goto error; ipv6[--at] = x & 0xff; ipv6[--at] = (x >> 8) & 0xff; } else { jdns_address_t *v4; if(n != count - 1) goto error; v4 = jdns_address_new(); if(!jdns_address_set_cstr(v4, (char *)list->item[n]->data)) { jdns_address_delete(v4); goto error; } ipv6[--at] = (unsigned char)(v4->addr.v4 & 0xff); ipv6[--at] = (unsigned char)((v4->addr.v4 >> 8) & 0xff); ipv6[--at] = (unsigned char)((v4->addr.v4 >> 16) & 0xff); ipv6[--at] = (unsigned char)((v4->addr.v4 >> 24) & 0xff); jdns_address_delete(v4); --fill; } } } jdns_stringlist_delete(list); jdns_address_set_ipv6(a, ipv6); return 1; error: jdns_stringlist_delete(list); return 0; } else if(strchr(str, '.')) { unsigned char b[4]; int x; unsigned long int ipv4; int at; char *part; int len; const char *p, *p2; p = str; at = 0; while(1) { p2 = strchr(p, '.'); if(!p2) p2 = str + slen; len = p2 - p; // convert the section into a byte part = (char *)jdns_alloc(len + 1); memcpy(part, p, len); part[len] = 0; x = strtol(part, NULL, 10); jdns_free(part); if(x < 0 || x > 0xff) break; b[at++] = (unsigned char)x; // done? if(p2 >= str + slen) break; // skip over the separator p = p2 + 1; } if(at != 4) return 0; ipv4 = 0; ipv4 += b[0]; ipv4 <<= 8; ipv4 += b[1]; ipv4 <<= 8; ipv4 += b[2]; ipv4 <<= 8; ipv4 += b[3]; jdns_address_set_ipv4(a, ipv4); return 1; } else return 0; } int jdns_address_cmp(const jdns_address_t *a, const jdns_address_t *b) { // same protocol? if(a->isIpv6 != b->isIpv6) return 0; if(a->isIpv6) { int n; for(n = 0; n < 16; ++n) { if(a->addr.v6[n] != b->addr.v6[n]) break; } if(n == 16) return 1; } else { if(a->addr.v4 == b->addr.v4) return 1; } return 0; } // FF02::FB unsigned char jdns_multicast_addr6_value_v6[] = { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb }; jdns_address_t *jdns_address_multicast4_new() { jdns_address_t *a = jdns_address_new(); jdns_address_set_ipv4(a, 0xe00000fb); return a; } jdns_address_t *jdns_address_multicast6_new() { jdns_address_t *a = jdns_address_new(); jdns_address_set_ipv6(a, jdns_multicast_addr6_value_v6); return a; } //---------------------------------------------------------------------------- // jdns_server //---------------------------------------------------------------------------- jdns_server_t *jdns_server_new() { jdns_server_t *s = alloc_type(jdns_server_t); s->name = 0; s->port = 0; s->priority = 0; s->weight = 0; return s; } jdns_server_t *jdns_server_copy(const jdns_server_t *s) { jdns_server_t *c = jdns_server_new(); if(s->name) c->name = _ustrdup(s->name); c->port = s->port; c->priority = s->priority; c->weight = s->weight; return c; } void jdns_server_delete(jdns_server_t *s) { if(!s) return; if(s->name) jdns_free(s->name); jdns_object_free(s); } void jdns_server_set_name(jdns_server_t *s, const unsigned char *name) { if(s->name) jdns_free(s->name); s->name = _ustrdup(name); } //---------------------------------------------------------------------------- // jdns_nameserver //---------------------------------------------------------------------------- jdns_nameserver_t *jdns_nameserver_new() { jdns_nameserver_t *a = alloc_type(jdns_nameserver_t); a->address = 0; a->port = -1; return a; } jdns_nameserver_t *jdns_nameserver_copy(const jdns_nameserver_t *a) { jdns_nameserver_t *c = jdns_nameserver_new(); if(a->address) c->address = jdns_address_copy(a->address); c->port = a->port; return c; } void jdns_nameserver_delete(jdns_nameserver_t *a) { if(!a) return; jdns_address_delete(a->address); jdns_free(a); } void jdns_nameserver_set(jdns_nameserver_t *a, const jdns_address_t *addr, int port) { if(a->address) jdns_address_delete(a->address); a->address = jdns_address_copy(addr); a->port = port; } //---------------------------------------------------------------------------- // jdns_nameserverlist //---------------------------------------------------------------------------- jdns_nameserverlist_t *jdns_nameserverlist_new() { jdns_nameserverlist_t *a = alloc_type(jdns_nameserverlist_t); a->count = 0; a->item = 0; return a; } jdns_nameserverlist_t *jdns_nameserverlist_copy(const jdns_nameserverlist_t *a) { int n; jdns_nameserverlist_t *c = jdns_nameserverlist_new(); if(a->item) { c->item = (jdns_nameserver_t **)jdns_alloc(sizeof(jdns_nameserver_t *) * a->count); c->count = a->count; for(n = 0; n < c->count; ++n) c->item[n] = jdns_nameserver_copy(a->item[n]); } return c; } void jdns_nameserverlist_delete(jdns_nameserverlist_t *a) { int n; if(!a) return; if(a->item) { for(n = 0; n < a->count; ++n) jdns_nameserver_delete(a->item[n]); jdns_free(a->item); } jdns_free(a); } void jdns_nameserverlist_append(jdns_nameserverlist_t *a, const jdns_address_t *addr, int port) { if(!a->item) a->item = (jdns_nameserver_t **)jdns_alloc(sizeof(jdns_nameserver_t *)); else a->item = (jdns_nameserver_t **)jdns_realloc(a->item, sizeof(jdns_nameserver_t *) * (a->count + 1)); a->item[a->count] = jdns_nameserver_new(); jdns_nameserver_set(a->item[a->count], addr, port); ++a->count; } //---------------------------------------------------------------------------- // jdns_dnshost //---------------------------------------------------------------------------- jdns_dnshost_t *jdns_dnshost_new() { jdns_dnshost_t *a = alloc_type(jdns_dnshost_t); a->name = 0; a->address = 0; return a; } jdns_dnshost_t *jdns_dnshost_copy(const jdns_dnshost_t *a) { jdns_dnshost_t *c = jdns_dnshost_new(); if(a->name) c->name = jdns_string_copy(a->name); if(a->address) c->address = jdns_address_copy(a->address); return c; } void jdns_dnshost_delete(jdns_dnshost_t *a) { if(!a) return; jdns_string_delete(a->name); jdns_address_delete(a->address); jdns_free(a); } //---------------------------------------------------------------------------- // jdns_dnshostlist //---------------------------------------------------------------------------- jdns_dnshostlist_t *jdns_dnshostlist_new() { jdns_dnshostlist_t *a = alloc_type(jdns_dnshostlist_t); a->count = 0; a->item = 0; return a; } jdns_dnshostlist_t *jdns_dnshostlist_copy(const jdns_dnshostlist_t *a) { int n; jdns_dnshostlist_t *c = jdns_dnshostlist_new(); if(a->item) { c->item = (jdns_dnshost_t **)jdns_alloc(sizeof(jdns_dnshost_t *) * a->count); c->count = a->count; for(n = 0; n < c->count; ++n) c->item[n] = jdns_dnshost_copy(a->item[n]); } return c; } void jdns_dnshostlist_delete(jdns_dnshostlist_t *a) { int n; if(!a) return; if(a->item) { for(n = 0; n < a->count; ++n) jdns_dnshost_delete(a->item[n]); jdns_free(a->item); } jdns_free(a); } void jdns_dnshostlist_append(jdns_dnshostlist_t *a, const jdns_dnshost_t *host) { if(!a->item) a->item = (jdns_dnshost_t **)jdns_alloc(sizeof(jdns_dnshost_t *)); else a->item = (jdns_dnshost_t **)jdns_realloc(a->item, sizeof(jdns_dnshost_t *) * (a->count + 1)); a->item[a->count] = jdns_dnshost_copy(host); ++a->count; } //---------------------------------------------------------------------------- // jdns_dnsparams //---------------------------------------------------------------------------- jdns_dnsparams_t *jdns_dnsparams_new() { jdns_dnsparams_t *a = alloc_type(jdns_dnsparams_t); a->nameservers = jdns_nameserverlist_new(); a->domains = jdns_stringlist_new(); a->hosts = jdns_dnshostlist_new(); return a; } jdns_dnsparams_t *jdns_dnsparams_copy(jdns_dnsparams_t *a) { jdns_dnsparams_t *c = jdns_dnsparams_new(); c->nameservers = jdns_nameserverlist_copy(a->nameservers); c->domains = jdns_stringlist_copy(a->domains); c->hosts = jdns_dnshostlist_copy(a->hosts); return c; } void jdns_dnsparams_delete(jdns_dnsparams_t *a) { if(!a) return; jdns_nameserverlist_delete(a->nameservers); jdns_stringlist_delete(a->domains); jdns_dnshostlist_delete(a->hosts); jdns_free(a); } void jdns_dnsparams_append_nameserver(jdns_dnsparams_t *a, const jdns_address_t *addr, int port) { jdns_nameserverlist_append(a->nameservers, addr, port); } void jdns_dnsparams_append_domain(jdns_dnsparams_t *a, const jdns_string_t *domain) { jdns_stringlist_append(a->domains, domain); } void jdns_dnsparams_append_host(jdns_dnsparams_t *a, const jdns_string_t *name, const jdns_address_t *address) { jdns_dnshost_t *h = jdns_dnshost_new(); h->name = jdns_string_copy(name); h->address = jdns_address_copy(address); jdns_dnshostlist_append(a->hosts, h); jdns_dnshost_delete(h); } //---------------------------------------------------------------------------- // jdns_rr //---------------------------------------------------------------------------- void _jdns_rr_data_reset(jdns_rr_t *r) { if(r->rdata) { jdns_free(r->rdata); r->rdata = 0; } r->rdlength = 0; if(r->haveKnown) { switch(r->type) { case JDNS_RTYPE_A: case JDNS_RTYPE_AAAA: jdns_address_delete(r->data.address); break; case JDNS_RTYPE_MX: case JDNS_RTYPE_SRV: jdns_server_delete(r->data.server); break; case JDNS_RTYPE_CNAME: case JDNS_RTYPE_PTR: case JDNS_RTYPE_NS: jdns_free(r->data.name); break; case JDNS_RTYPE_TXT: jdns_stringlist_delete(r->data.texts); break; case JDNS_RTYPE_HINFO: jdns_string_delete(r->data.hinfo.cpu); jdns_string_delete(r->data.hinfo.os); break; default: break; }; r->haveKnown = 0; } r->type = -1; } void _jdns_rr_data_copy(const jdns_rr_t *r, jdns_rr_t *c) { c->type = r->type; c->qclass = r->qclass; c->rdlength = r->rdlength; c->rdata = jdns_copy_array(r->rdata, r->rdlength); if(r->haveKnown) { switch(r->type) { case JDNS_RTYPE_A: case JDNS_RTYPE_AAAA: c->data.address = jdns_address_copy(r->data.address); break; case JDNS_RTYPE_MX: case JDNS_RTYPE_SRV: c->data.server = jdns_server_copy(r->data.server); break; case JDNS_RTYPE_CNAME: case JDNS_RTYPE_PTR: case JDNS_RTYPE_NS: c->data.name = _ustrdup(r->data.name); break; case JDNS_RTYPE_TXT: c->data.texts = jdns_stringlist_copy(r->data.texts); break; case JDNS_RTYPE_HINFO: c->data.hinfo.cpu = jdns_string_copy(r->data.hinfo.cpu); c->data.hinfo.os = jdns_string_copy(r->data.hinfo.os); break; default: break; }; c->haveKnown = 1; } } jdns_rr_t *jdns_rr_new() { jdns_rr_t *r = alloc_type(jdns_rr_t); r->owner = 0; r->ttl = 0; r->type = -1; r->qclass = 0; r->rdata = 0; r->rdlength = 0; r->haveKnown = 0; return r; } jdns_rr_t *jdns_rr_copy(const jdns_rr_t *r) { jdns_rr_t *c = jdns_rr_new(); if(r->owner) c->owner = _ustrdup(r->owner); c->ttl = r->ttl; _jdns_rr_data_copy(r, c); return c; } void jdns_rr_delete(jdns_rr_t *r) { if(!r) return; if(r->owner) jdns_free(r->owner); _jdns_rr_data_reset(r); jdns_free(r); } void jdns_rr_set_owner(jdns_rr_t *r, const unsigned char *name) { if(r->owner) jdns_free(r->owner); r->owner = _ustrdup(name); } void jdns_rr_set_record(jdns_rr_t *r, int type, const unsigned char *rdata, int rdlength) { _jdns_rr_data_reset(r); r->type = type; r->rdlength = rdlength; r->rdata = jdns_copy_array(rdata, rdlength); } void jdns_rr_set_A(jdns_rr_t *r, const jdns_address_t *address) { _jdns_rr_data_reset(r); r->type = JDNS_RTYPE_A; r->haveKnown = 1; r->data.address = jdns_address_copy(address); } void jdns_rr_set_AAAA(jdns_rr_t *r, const jdns_address_t *address) { _jdns_rr_data_reset(r); r->type = JDNS_RTYPE_AAAA; r->haveKnown = 1; r->data.address = jdns_address_copy(address); } void jdns_rr_set_MX(jdns_rr_t *r, const unsigned char *name, int priority) { jdns_server_t *s = jdns_server_new(); jdns_server_set_name(s, name); s->priority = priority; _jdns_rr_data_reset(r); r->type = JDNS_RTYPE_MX; r->haveKnown = 1; r->data.server = s; } void jdns_rr_set_SRV(jdns_rr_t *r, const unsigned char *name, int port, int priority, int weight) { jdns_server_t *s = jdns_server_new(); jdns_server_set_name(s, name); s->port = port; s->priority = priority; s->weight = weight; _jdns_rr_data_reset(r); r->type = JDNS_RTYPE_SRV; r->haveKnown = 1; r->data.server = s; } void jdns_rr_set_CNAME(jdns_rr_t *r, const unsigned char *name) { _jdns_rr_data_reset(r); r->type = JDNS_RTYPE_CNAME; r->haveKnown = 1; r->data.name = _ustrdup(name); } void jdns_rr_set_PTR(jdns_rr_t *r, const unsigned char *name) { _jdns_rr_data_reset(r); r->type = JDNS_RTYPE_PTR; r->haveKnown = 1; r->data.name = _ustrdup(name); } void jdns_rr_set_TXT(jdns_rr_t *r, const jdns_stringlist_t *texts) { _jdns_rr_data_reset(r); r->type = JDNS_RTYPE_TXT; r->haveKnown = 1; r->data.texts = jdns_stringlist_copy(texts); } void jdns_rr_set_HINFO(jdns_rr_t *r, const jdns_string_t *cpu, const jdns_string_t *os) { _jdns_rr_data_reset(r); r->type = JDNS_RTYPE_HINFO; r->haveKnown = 1; r->data.hinfo.cpu = jdns_string_copy(cpu); r->data.hinfo.os = jdns_string_copy(os); } void jdns_rr_set_NS(jdns_rr_t *r, const unsigned char *name) { _jdns_rr_data_reset(r); r->type = JDNS_RTYPE_NS; r->haveKnown = 1; r->data.name = _ustrdup(name); } int jdns_rr_verify(const jdns_rr_t *r) { if(r->type == -1) return 0; if(!jdns_packet_name_isvalid(r->owner, _ustrlen(r->owner))) return 0; switch(r->type) { case JDNS_RTYPE_MX: case JDNS_RTYPE_SRV: { // consider it valid if we don't have a known to check if(!r->haveKnown) return 1; if(!jdns_packet_name_isvalid(r->data.server->name, _ustrlen(r->data.server->name))) return 0; break; } case JDNS_RTYPE_CNAME: case JDNS_RTYPE_PTR: case JDNS_RTYPE_NS: { if(!r->haveKnown) return 1; if(!jdns_packet_name_isvalid(r->data.name, _ustrlen(r->data.name))) return 0; break; } case JDNS_RTYPE_TXT: { int n; if(!r->haveKnown) return 1; for(n = 0; n < r->data.texts->count; ++n) { if(r->data.texts->item[n]->size > 255) return 0; } break; } case JDNS_RTYPE_HINFO: { if(!r->haveKnown) return 1; if(r->data.hinfo.cpu->size > 255) return 0; if(r->data.hinfo.os->size > 255) return 0; break; } } return 1; } static jdns_string_t *read_name_at_end(const jdns_packet_resource_t *pr, const jdns_packet_t *ref, int _at) { jdns_string_t *name; int at; at = _at; if(!jdns_packet_resource_read_name(pr, ref, &at, &name)) return 0; if(at != pr->rdlength) { jdns_string_delete(name); return 0; } return name; } static jdns_string_t *read_text_string(const jdns_packet_resource_t *pr, int *_at) { jdns_string_t *out; int at, len; at = *_at; if(at + 1 > pr->rdlength) return 0; len = pr->rdata[at++]; if(at + len > pr->rdlength) return 0; out = jdns_string_new(); jdns_string_set(out, pr->rdata + at, len); at += len; *_at = at; return out; } // if the type is known, then it must be parsed properly // if the type is unknown, then that's ok // rdata is always copied, known or not jdns_rr_t *jdns_rr_from_resource(const jdns_packet_resource_t *pr, const jdns_packet_t *ref) { jdns_rr_t *rr = 0; if(pr->qtype == JDNS_RTYPE_ANY) return 0; switch(pr->qtype) { case JDNS_RTYPE_A: { jdns_address_t *addr; unsigned long int ip; if(pr->rdlength != 4) break; memcpy(&ip, pr->rdata, 4); ip = ntohl(ip); addr = jdns_address_new(); jdns_address_set_ipv4(addr, ip); rr = jdns_rr_new(); jdns_rr_set_A(rr, addr); jdns_address_delete(addr); break; } case JDNS_RTYPE_AAAA: { jdns_address_t *addr; if(pr->rdlength != 16) break; addr = jdns_address_new(); jdns_address_set_ipv6(addr, pr->rdata); rr = jdns_rr_new(); jdns_rr_set_AAAA(rr, addr); jdns_address_delete(addr); break; } case JDNS_RTYPE_MX: { unsigned short priority; jdns_string_t *name; if(pr->rdlength < 2) break; memcpy(&priority, pr->rdata, 2); priority = ntohs(priority); name = read_name_at_end(pr, ref, 2); if(!name) break; rr = jdns_rr_new(); jdns_rr_set_MX(rr, name->data, priority); jdns_string_delete(name); break; } case JDNS_RTYPE_SRV: { unsigned short priority, weight, port; jdns_string_t *name; if(pr->rdlength < 6) break; memcpy(&priority, pr->rdata, 2); priority = ntohs(priority); memcpy(&weight, pr->rdata + 2, 2); weight = ntohs(weight); memcpy(&port, pr->rdata + 4, 2); port = ntohs(port); name = read_name_at_end(pr, ref, 6); if(!name) break; rr = jdns_rr_new(); jdns_rr_set_SRV(rr, name->data, port, priority, weight); jdns_string_delete(name); break; } case JDNS_RTYPE_CNAME: { jdns_string_t *name; name = read_name_at_end(pr, ref, 0); if(!name) break; rr = jdns_rr_new(); jdns_rr_set_CNAME(rr, name->data); jdns_string_delete(name); break; } case JDNS_RTYPE_PTR: { jdns_string_t *name; name = read_name_at_end(pr, ref, 0); if(!name) break; rr = jdns_rr_new(); jdns_rr_set_PTR(rr, name->data); jdns_string_delete(name); break; } case JDNS_RTYPE_TXT: { jdns_stringlist_t *texts; jdns_string_t *str; int at, error; texts = jdns_stringlist_new(); at = 0; error = 0; while(at < pr->rdlength) { str = read_text_string(pr, &at); if(!str) { error = 1; break; } jdns_stringlist_append(texts, str); jdns_string_delete(str); } if(error) { jdns_stringlist_delete(texts); break; } rr = jdns_rr_new(); jdns_rr_set_TXT(rr, texts); jdns_stringlist_delete(texts); break; } case JDNS_RTYPE_HINFO: { jdns_string_t *cpu, *os; int at; at = 0; cpu = read_text_string(pr, &at); if(!cpu) break; os = read_text_string(pr, &at); if(!os) { jdns_string_delete(cpu); break; } if(at != pr->rdlength) { jdns_string_delete(cpu); jdns_string_delete(os); break; } rr = jdns_rr_new(); jdns_rr_set_HINFO(rr, cpu, os); jdns_string_delete(cpu); jdns_string_delete(os); break; } case JDNS_RTYPE_NS: { jdns_string_t *name; name = read_name_at_end(pr, ref, 0); if(!name) break; rr = jdns_rr_new(); jdns_rr_set_NS(rr, name->data); jdns_string_delete(name); break; } default: { rr = jdns_rr_new(); rr->type = pr->qtype; break; } } if(rr) { rr->qclass = pr->qclass; rr->owner = _ustrdup(pr->qname->data); rr->ttl = (int)pr->ttl; // pr->ttl is 31-bits, cast is safe rr->rdlength = pr->rdlength; rr->rdata = jdns_copy_array(pr->rdata, pr->rdlength); } return rr; } //---------------------------------------------------------------------------- // jdns_response //---------------------------------------------------------------------------- #define ARRAY_DELETE(array, count, dtor) \ { \ if(count > 0) \ { \ int n; \ for(n = 0; n < count; ++n) \ dtor(array[n]); \ } \ jdns_free(array); \ array = 0; \ count = 0; \ } #define ARRAY_COPY(type, array_src, count_src, array_dest, count_dest, cctor) \ { \ if(count_src > 0) \ { \ int n; \ count_dest = count_src; \ array_dest = (type **)jdns_alloc(sizeof(type *) * count_dest); \ for(n = 0; n < count_dest; ++n) \ array_dest[n] = cctor(array_src[n]); \ } \ } #define ARRAY_APPEND(type, array, count, item) \ { \ if(!array) \ array = (type **)jdns_alloc(sizeof(type *)); \ else \ array = (type **)jdns_realloc(array, sizeof(type *) * (count + 1)); \ array[count] = item; \ ++count; \ } jdns_response_t *jdns_response_new() { jdns_response_t *r = alloc_type(jdns_response_t); r->answerCount = 0; r->answerRecords = 0; r->authorityCount = 0; r->authorityRecords = 0; r->additionalCount = 0; r->additionalRecords = 0; return r; } jdns_response_t *jdns_response_copy(const jdns_response_t *r) { jdns_response_t *c = jdns_response_new(); ARRAY_COPY(jdns_rr_t, r->answerRecords, r->answerCount, c->answerRecords, c->answerCount, jdns_rr_copy); ARRAY_COPY(jdns_rr_t, r->authorityRecords, r->authorityCount, c->authorityRecords, c->authorityCount, jdns_rr_copy); ARRAY_COPY(jdns_rr_t, r->additionalRecords, r->additionalCount, c->additionalRecords, c->additionalCount, jdns_rr_copy); return c; } void jdns_response_delete(jdns_response_t *r) { if(!r) return; ARRAY_DELETE(r->answerRecords, r->answerCount, jdns_rr_delete); ARRAY_DELETE(r->authorityRecords, r->authorityCount, jdns_rr_delete); ARRAY_DELETE(r->additionalRecords, r->additionalCount, jdns_rr_delete); jdns_free(r); } void jdns_response_append_answer(jdns_response_t *r, const jdns_rr_t *rr) { ARRAY_APPEND(jdns_rr_t, r->answerRecords, r->answerCount, jdns_rr_copy(rr)); } void jdns_response_append_authority(jdns_response_t *r, const jdns_rr_t *rr) { ARRAY_APPEND(jdns_rr_t, r->authorityRecords, r->authorityCount, jdns_rr_copy(rr)); } void jdns_response_append_additional(jdns_response_t *r, const jdns_rr_t *rr) { ARRAY_APPEND(jdns_rr_t, r->additionalRecords, r->additionalCount, jdns_rr_copy(rr)); } void jdns_response_remove_extra(jdns_response_t *r) { ARRAY_DELETE(r->authorityRecords, r->authorityCount, jdns_rr_delete); ARRAY_DELETE(r->additionalRecords, r->additionalCount, jdns_rr_delete); } void jdns_response_remove_answer(jdns_response_t *r, int pos) { jdns_rr_t *rr = r->answerRecords[pos]; jdns_rr_delete(rr); // free the position if(r->answerCount > 1) { memmove(r->answerRecords + pos, r->answerRecords + pos + 1, (r->answerCount - pos - 1) * sizeof(void *)); --r->answerCount; } else { jdns_free(r->answerRecords); r->answerRecords = 0; r->answerCount = 0; } } psi-plus-snapshots-1.4.1456/iris/src/jdns/src/qjdns/000077500000000000000000000000001370065651000221545ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/jdns/src/qjdns/CMakeLists.txt000066400000000000000000000036131370065651000247170ustar00rootroot00000000000000set(qjdns_MOC_HDRS "${JDNS_INCLUDEDIR}/qjdns.h" "${JDNS_INCLUDEDIR}/qjdnsshared.h" qjdns_p.h qjdnsshared_p.h ) if(NOT Qt5Core_FOUND) qt4_wrap_cpp(qjdns_MOC_SRCS ${qjdns_MOC_HDRS}) endif() set(qjdns_SRCS qjdns.cpp qjdns_sock.cpp qjdnsshared.cpp ) set(qjdns_PUBLIC_HEADERS "${JDNS_INCLUDEDIR}/qjdns.h" "${JDNS_INCLUDEDIR}/qjdnsshared.h" ) set(qjdns_HEADERS qjdns_sock.h ) add_library(qjdns ${qjdns_SRCS} ${qjdns_MOC_SRCS} ${qjdns_MOC_HDRS} ${qjdns_PUBLIC_HEADERS}) if(Qt5Core_FOUND) target_link_libraries(qjdns ${Qt5Core_LIBRARIES} ${Qt5Network_LIBRARIES}) else(Qt5Core_FOUND) target_link_libraries(qjdns ${QT_LIBRARIES}) endif(Qt5Core_FOUND) target_link_libraries(qjdns jdns) if(NOT android) set_target_properties(qjdns PROPERTIES VERSION ${JDNS_LIB_MAJOR_VERSION}.${JDNS_LIB_MINOR_VERSION}.${JDNS_LIB_PATCH_VERSION} SOVERSION ${JDNS_LIB_MAJOR_VERSION} ) endif() set_target_properties(qjdns PROPERTIES DEFINE_SYMBOL JDNS_MAKEDLL PUBLIC_HEADER "${qjdns_PUBLIC_HEADERS}" # FRAMEWORK ${OSX_FRAMEWORK} ) install(TARGETS qjdns EXPORT qjdns-export LIBRARY DESTINATION ${LIB_INSTALL_DIR} RUNTIME DESTINATION ${BIN_INSTALL_DIR} ARCHIVE DESTINATION ${LIB_INSTALL_DIR} # FRAMEWORK DESTINATION ${FRAMEWORK_INSTALL_DIR} PUBLIC_HEADER DESTINATION "${INCLUDE_INSTALL_DIR}/jdns" ) if(MSVC) get_target_property(LOCATION qjdns LOCATION_DEBUG) string(REGEX REPLACE "\\.[^.]*$" ".pdb" LOCATION "${LOCATION}") install(FILES ${LOCATION} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin CONFIGURATIONS Debug) get_target_property(LOCATION qjdns LOCATION_RELWITHDEBINFO) string(REGEX REPLACE "\\.[^.]*$" ".pdb" LOCATION "${LOCATION}") install(FILES ${LOCATION} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin CONFIGURATIONS RelWithDebInfo) endif(MSVC) psi-plus-snapshots-1.4.1456/iris/src/jdns/src/qjdns/qjdns.cpp000066400000000000000000000613371370065651000240110ustar00rootroot00000000000000/* * Copyright (C) 2005-2008 Justin Karneges * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "qjdns_p.h" #include "qjdns_sock.h" #include // for fprintf #include // safeobj stuff, from qca static void releaseAndDeleteLater(QObject *owner, QObject *obj) { obj->disconnect(owner); obj->setParent(0); obj->deleteLater(); } SafeTimer::SafeTimer(QObject *parent) : QObject(parent) { t = new QTimer(this); connect(t, SIGNAL(timeout()), SIGNAL(timeout())); } SafeTimer::~SafeTimer() { releaseAndDeleteLater(this, t); } int SafeTimer::interval() const { return t->interval(); } bool SafeTimer::isActive() const { return t->isActive(); } bool SafeTimer::isSingleShot() const { return t->isSingleShot(); } void SafeTimer::setInterval(int msec) { t->setInterval(msec); } void SafeTimer::setSingleShot(bool singleShot) { t->setSingleShot(singleShot); } int SafeTimer::timerId() const { return t->timerId(); } void SafeTimer::start(int msec) { t->start(msec); } void SafeTimer::start() { t->start(); } void SafeTimer::stop() { t->stop(); } static jdns_string_t *qt2str(const QByteArray &in) { jdns_string_t *out = jdns_string_new(); jdns_string_set(out, (const unsigned char *)in.data(), in.size()); return out; } static QByteArray str2qt(const jdns_string_t *in) { return QByteArray((const char *)in->data, in->size); } static void qt2addr_set(jdns_address_t *addr, const QHostAddress &host) { if(host.protocol() == QAbstractSocket::IPv6Protocol) jdns_address_set_ipv6(addr, host.toIPv6Address().c); else jdns_address_set_ipv4(addr, host.toIPv4Address()); } static jdns_address_t *qt2addr(const QHostAddress &host) { jdns_address_t *addr = jdns_address_new(); qt2addr_set(addr, host); return addr; } static QHostAddress addr2qt(const jdns_address_t *addr) { if(addr->isIpv6) return QHostAddress(addr->addr.v6); else return QHostAddress(addr->addr.v4); } static QJDns::Record import_record(const jdns_rr_t *in) { QJDns::Record out; out.owner = QByteArray((const char *)in->owner); out.ttl = in->ttl; out.type = in->type; out.rdata = QByteArray((const char *)in->rdata, in->rdlength); // known if(in->haveKnown) { int type = in->type; if(type == QJDns::A || type == QJDns::Aaaa) { out.haveKnown = true; out.address = addr2qt(in->data.address); } else if(type == QJDns::Mx) { out.haveKnown = true; out.name = QByteArray((const char *)in->data.server->name); out.priority = in->data.server->priority; } else if(type == QJDns::Srv) { out.haveKnown = true; out.name = QByteArray((const char *)in->data.server->name); out.priority = in->data.server->priority; out.weight = in->data.server->weight; out.port = in->data.server->port; } else if(type == QJDns::Cname || type == QJDns::Ptr || type == QJDns::Ns) { out.haveKnown = true; out.name = QByteArray((const char *)in->data.name); } else if(type == QJDns::Txt) { out.haveKnown = true; out.texts.clear(); for(int n = 0; n < in->data.texts->count; ++n) out.texts += str2qt(in->data.texts->item[n]); } else if(type == QJDns::Hinfo) { out.haveKnown = true; out.cpu = str2qt(in->data.hinfo.cpu); out.os = str2qt(in->data.hinfo.os); } } return out; } static jdns_rr_t *export_record(const QJDns::Record &in) { jdns_rr_t *out = jdns_rr_new(); jdns_rr_set_owner(out, (const unsigned char *)in.owner.data()); out->ttl = in.ttl; // if we have known, use that if(in.haveKnown) { int type = in.type; if(type == QJDns::A) { jdns_address_t *addr = qt2addr(in.address); jdns_rr_set_A(out, addr); jdns_address_delete(addr); } else if(type == QJDns::Aaaa) { jdns_address_t *addr = qt2addr(in.address); jdns_rr_set_AAAA(out, addr); jdns_address_delete(addr); } else if(type == QJDns::Mx) { jdns_rr_set_MX(out, (const unsigned char *)in.name.data(), in.priority); } else if(type == QJDns::Srv) { jdns_rr_set_SRV(out, (const unsigned char *)in.name.data(), in.port, in.priority, in.weight); } else if(type == QJDns::Cname) { jdns_rr_set_CNAME(out, (const unsigned char *)in.name.data()); } else if(type == QJDns::Ptr) { jdns_rr_set_PTR(out, (const unsigned char *)in.name.data()); } else if(type == QJDns::Txt) { jdns_stringlist_t *list = jdns_stringlist_new(); for(int n = 0; n < in.texts.count(); ++n) { jdns_string_t *str = qt2str(in.texts[n]); jdns_stringlist_append(list, str); jdns_string_delete(str); } jdns_rr_set_TXT(out, list); jdns_stringlist_delete(list); } else if(type == QJDns::Hinfo) { jdns_string_t *cpu = qt2str(in.cpu); jdns_string_t *os = qt2str(in.os); jdns_rr_set_HINFO(out, cpu, os); jdns_string_delete(cpu); jdns_string_delete(os); } else if(type == QJDns::Ns) { jdns_rr_set_NS(out, (const unsigned char *)in.name.data()); } } else jdns_rr_set_record(out, in.type, (const unsigned char *)in.rdata.data(), in.rdata.size()); return out; } //---------------------------------------------------------------------------- // QJDns::NameServer //---------------------------------------------------------------------------- QJDns::NameServer::NameServer() { port = JDNS_UNICAST_PORT; } //---------------------------------------------------------------------------- // QJDns::Record //---------------------------------------------------------------------------- QJDns::Record::Record() { ttl = 0; type = -1; haveKnown = false; } bool QJDns::Record::verify() const { jdns_rr_t *rr = export_record(*this); int ok = jdns_rr_verify(rr); jdns_rr_delete(rr); return (ok ? true : false); } //---------------------------------------------------------------------------- // QJDns //---------------------------------------------------------------------------- static int my_srand_done = 0; static void my_srand() { if(my_srand_done) return; // lame attempt at randomizing without srand int count = ::time(NULL) % 128; for(int n = 0; n < count; ++n) rand(); my_srand_done = 1; } QJDns::Private::Private(QJDns *_q) : QObject(_q) , q(_q) , stepTrigger(this) , debugTrigger(this) , stepTimeout(this) , pErrors(0) , pPublished(0) , pResponses(0) { sess = 0; shutting_down = false; new_debug_strings = false; pending = 0; connect(&stepTrigger, SIGNAL(timeout()), SLOT(doNextStepSlot())); stepTrigger.setSingleShot(true); connect(&debugTrigger, SIGNAL(timeout()), SLOT(doDebug())); debugTrigger.setSingleShot(true); connect(&stepTimeout, SIGNAL(timeout()), SLOT(st_timeout())); stepTimeout.setSingleShot(true); my_srand(); clock.start(); } QJDns::Private::~Private() { cleanup(); } void QJDns::Private::cleanup() { if(sess) { jdns_session_delete(sess); sess = 0; } shutting_down = false; pending = 0; // it is safe to delete the QUdpSocket objects here without // deleteLater, since this code path never occurs when // a signal from those objects is on the stack qDeleteAll(socketForHandle); socketForHandle.clear(); handleForSocket.clear(); stepTrigger.stop(); stepTimeout.stop(); need_handle = 0; } bool QJDns::Private::init(QJDns::Mode _mode, const QHostAddress &address) { mode = _mode; jdns_callbacks_t callbacks; callbacks.app = this; callbacks.time_now = cb_time_now; callbacks.rand_int = cb_rand_int; callbacks.debug_line = cb_debug_line; callbacks.udp_bind = cb_udp_bind; callbacks.udp_unbind = cb_udp_unbind; callbacks.udp_read = cb_udp_read; callbacks.udp_write = cb_udp_write; sess = jdns_session_new(&callbacks); jdns_set_hold_ids_enabled(sess, 1); next_handle = 1; need_handle = false; int ret; jdns_address_t *baddr = qt2addr(address); if(mode == Unicast) { ret = jdns_init_unicast(sess, baddr, 0); } else { jdns_address_t *maddr; if(address.protocol() == QAbstractSocket::IPv6Protocol) maddr = jdns_address_multicast6_new(); else maddr = jdns_address_multicast4_new(); ret = jdns_init_multicast(sess, baddr, JDNS_MULTICAST_PORT, maddr); jdns_address_delete(maddr); } jdns_address_delete(baddr); if(!ret) { jdns_session_delete(sess); sess = 0; return false; } return true; } void QJDns::Private::setNameServers(const QList &nslist) { jdns_nameserverlist_t *addrs = jdns_nameserverlist_new(); for(int n = 0; n < nslist.count(); ++n) { jdns_address_t *addr = qt2addr(nslist[n].address); jdns_nameserverlist_append(addrs, addr, nslist[n].port); jdns_address_delete(addr); } jdns_set_nameservers(sess, addrs); jdns_nameserverlist_delete(addrs); } void QJDns::Private::process() { if(!stepTrigger.isActive()) { stepTimeout.stop(); stepTrigger.start(); } } void QJDns::Private::processDebug() { new_debug_strings = true; if(!debugTrigger.isActive()) debugTrigger.start(); } void QJDns::Private::doNextStep() { if(shutting_down && complete_shutdown) { cleanup(); emit q->shutdownFinished(); return; } QPointer self = this; int ret = jdns_step(sess); QList errors; QList published; QList responses; bool finish_shutdown = false; pErrors = &errors; pPublished = &published; pResponses = &responses; while(1) { jdns_event_t *e = jdns_next_event(sess); if(!e) break; if(e->type == JDNS_EVENT_SHUTDOWN) { finish_shutdown = true; } else if(e->type == JDNS_EVENT_PUBLISH) { if(e->status != JDNS_STATUS_SUCCESS) { QJDns::Error error; if(e->status == JDNS_STATUS_CONFLICT) error = QJDns::ErrorConflict; else error = QJDns::ErrorGeneric; LateError le; le.source_type = 1; le.id = e->id; le.error = error; errors += le; } else { published += e->id; } } else if(e->type == JDNS_EVENT_RESPONSE) { if(e->status != JDNS_STATUS_SUCCESS) { QJDns::Error error; if(e->status == JDNS_STATUS_NXDOMAIN) error = QJDns::ErrorNXDomain; else if(e->status == JDNS_STATUS_TIMEOUT) error = QJDns::ErrorTimeout; else error = QJDns::ErrorGeneric; LateError le; le.source_type = 0; le.id = e->id; le.error = error; errors += le; } else { QJDns::Response out_response; for(int n = 0; n < e->response->answerCount; ++n) out_response.answerRecords += import_record(e->response->answerRecords[n]); LateResponse lr; lr.id = e->id; lr.response = out_response; if(mode == Unicast) lr.do_cancel = true; else lr.do_cancel = false; responses += lr; } } jdns_event_delete(e); } if(ret & JDNS_STEP_TIMER) stepTimeout.start(jdns_next_timer(sess)); else stepTimeout.stop(); need_handle = (ret & JDNS_STEP_HANDLE); // read the lists safely enough so that items can be deleted // behind our back while(!errors.isEmpty()) { LateError i = errors.takeFirst(); if(i.source_type == 0) jdns_cancel_query(sess, i.id); else jdns_cancel_publish(sess, i.id); emit q->error(i.id, i.error); if(!self) return; } while(!published.isEmpty()) { int i = published.takeFirst(); emit q->published(i); if(!self) return; } while(!responses.isEmpty()) { LateResponse i = responses.takeFirst(); if(i.do_cancel) jdns_cancel_query(sess, i.id); emit q->resultsReady(i.id, i.response); if(!self) return; } if(finish_shutdown) { // if we have pending udp packets to write, stick around if(pending > 0) { pending_wait = true; } else { complete_shutdown = true; process(); } } pErrors = 0; pPublished = 0; pResponses = 0; } void QJDns::Private::removeCancelled(int id) { if(pErrors) { for(int n = 0; n < pErrors->count(); ++n) { if(pErrors->at(n).id == id) { pErrors->removeAt(n); --n; // adjust position } } } if(pPublished) { for(int n = 0; n < pPublished->count(); ++n) { if(pPublished->at(n) == id) { pPublished->removeAt(n); --n; // adjust position } } } if(pResponses) { for(int n = 0; n < pResponses->count(); ++n) { if(pResponses->at(n).id == id) { pResponses->removeAt(n); --n; // adjust position } } } } void QJDns::Private::udp_readyRead() { QUdpSocket *sock = static_cast(sender()); int handle = handleForSocket.value(sock); if(need_handle) { jdns_set_handle_readable(sess, handle); process(); } else { // eat packet QByteArray buf(4096, 0); QHostAddress from_addr; quint16 from_port; sock->readDatagram(buf.data(), buf.size(), &from_addr, &from_port); } } void QJDns::Private::udp_bytesWritten(qint64) { if(pending > 0) { --pending; if(shutting_down && pending_wait && pending == 0) { pending_wait = false; complete_shutdown = true; process(); } } } void QJDns::Private::st_timeout() { doNextStep(); } void QJDns::Private::doNextStepSlot() { doNextStep(); } void QJDns::Private::doDebug() { if(new_debug_strings) { new_debug_strings = false; if(!debug_strings.isEmpty()) emit q->debugLinesReady(); } } // jdns callbacks int QJDns::Private::cb_time_now(jdns_session_t *, void *app) { QJDns::Private *self = (QJDns::Private *)app; return self->clock.elapsed(); } int QJDns::Private::cb_rand_int(jdns_session_t *, void *) { return rand() % 65536; } void QJDns::Private::cb_debug_line(jdns_session_t *, void *app, const char *str) { QJDns::Private *self = (QJDns::Private *)app; self->debug_strings += QString::fromLatin1(str); self->processDebug(); } int QJDns::Private::cb_udp_bind(jdns_session_t *, void *app, const jdns_address_t *addr, int port, const jdns_address_t *maddr) { QJDns::Private *self = (QJDns::Private *)app; // we always pass non-null to jdns_init, so this should be a valid address QHostAddress host = addr2qt(addr); QUdpSocket *sock = new QUdpSocket(self); self->connect(sock, SIGNAL(readyRead()), SLOT(udp_readyRead())); // use queued for bytesWritten, since qt is evil and emits before writeDatagram returns qRegisterMetaType("qint64"); self->connect(sock, SIGNAL(bytesWritten(qint64)), SLOT(udp_bytesWritten(qint64)), Qt::QueuedConnection); QUdpSocket::BindMode mode; mode |= QUdpSocket::ShareAddress; mode |= QUdpSocket::ReuseAddressHint; if(!sock->bind(host, port, mode)) { delete sock; return 0; } if(maddr) { int sd = sock->socketDescriptor(); bool ok; int errorCode; if(maddr->isIpv6) ok = qjdns_sock_setMulticast6(sd, maddr->addr.v6, &errorCode); else ok = qjdns_sock_setMulticast4(sd, maddr->addr.v4, &errorCode); if(!ok) { delete sock; self->debug_strings += QString("failed to setup multicast on the socket (errorCode=%1)").arg(errorCode); self->processDebug(); return 0; } if(maddr->isIpv6) { qjdns_sock_setTTL6(sd, 255); qjdns_sock_setIPv6Only(sd); } else qjdns_sock_setTTL4(sd, 255); } int handle = self->next_handle++; self->socketForHandle.insert(handle, sock); self->handleForSocket.insert(sock, handle); return handle; } void QJDns::Private::cb_udp_unbind(jdns_session_t *, void *app, int handle) { QJDns::Private *self = (QJDns::Private *)app; QUdpSocket *sock = self->socketForHandle.value(handle); if(!sock) return; self->socketForHandle.remove(handle); self->handleForSocket.remove(sock); delete sock; } int QJDns::Private::cb_udp_read(jdns_session_t *, void *app, int handle, jdns_address_t *addr, int *port, unsigned char *buf, int *bufsize) { QJDns::Private *self = (QJDns::Private *)app; QUdpSocket *sock = self->socketForHandle.value(handle); if(!sock) return 0; // nothing to read? if(!sock->hasPendingDatagrams()) return 0; QHostAddress from_addr; quint16 from_port; int ret = sock->readDatagram((char *)buf, *bufsize, &from_addr, &from_port); if(ret == -1) return 0; qt2addr_set(addr, from_addr); *port = (int)from_port; *bufsize = ret; return 1; } int QJDns::Private::cb_udp_write(jdns_session_t *, void *app, int handle, const jdns_address_t *addr, int port, unsigned char *buf, int bufsize) { QJDns::Private *self = (QJDns::Private *)app; QUdpSocket *sock = self->socketForHandle.value(handle); if(!sock) return 0; QHostAddress host = addr2qt(addr); int ret = sock->writeDatagram((const char *)buf, bufsize, host, port); if(ret == -1) { // this can happen if the datagram to send is too big. i'm not sure what else // may cause this. if we return 0, then jdns may try to resend the packet, // which might not work if it is too large (causing the same error over and // over). we'll return success to jdns, so the result is as if the packet // was dropped. return 1; } ++self->pending; return 1; } QJDns::QJDns(QObject *parent) :QObject(parent) { d = new Private(this); } QJDns::~QJDns() { delete d; } bool QJDns::init(Mode mode, const QHostAddress &address) { return d->init(mode, address); } void QJDns::shutdown() { d->shutting_down = true; d->pending_wait = false; d->complete_shutdown = false; jdns_shutdown(d->sess); d->process(); } QStringList QJDns::debugLines() { QStringList tmp = d->debug_strings; d->debug_strings.clear(); return tmp; } QJDns::SystemInfo QJDns::systemInfo() { SystemInfo out; jdns_dnsparams_t *params = jdns_system_dnsparams(); for(int n = 0; n < params->nameservers->count; ++n) { NameServer ns; ns.address = addr2qt(params->nameservers->item[n]->address); out.nameServers += ns; } for(int n = 0; n < params->domains->count; ++n) out.domains += str2qt(params->domains->item[n]); for(int n = 0; n < params->hosts->count; ++n) { DnsHost h; h.name = str2qt(params->hosts->item[n]->name); h.address = addr2qt(params->hosts->item[n]->address); out.hosts += h; } jdns_dnsparams_delete(params); return out; } #define PROBE_BASE 20000 #define PROBE_RANGE 100 QHostAddress QJDns::detectPrimaryMulticast(const QHostAddress &address) { my_srand(); QUdpSocket *sock = new QUdpSocket; QUdpSocket::BindMode mode; mode |= QUdpSocket::ShareAddress; mode |= QUdpSocket::ReuseAddressHint; int port = -1; for(int n = 0; n < PROBE_RANGE; ++n) { if(sock->bind(address, PROBE_BASE + n, mode)) { port = PROBE_BASE + n; break; } } if(port == -1) { delete sock; return QHostAddress(); } jdns_address_t *a; if(address.protocol() == QAbstractSocket::IPv6Protocol) a = jdns_address_multicast6_new(); else a = jdns_address_multicast4_new(); QHostAddress maddr = addr2qt(a); jdns_address_delete(a); if(address.protocol() == QAbstractSocket::IPv6Protocol) { int x; if(!qjdns_sock_setMulticast6(sock->socketDescriptor(), maddr.toIPv6Address().c, &x)) { delete sock; return QHostAddress(); } qjdns_sock_setTTL6(sock->socketDescriptor(), 0); } else { int x; if(!qjdns_sock_setMulticast4(sock->socketDescriptor(), maddr.toIPv4Address(), &x)) { delete sock; return QHostAddress(); } qjdns_sock_setTTL4(sock->socketDescriptor(), 0); } QHostAddress result; QByteArray out(128, 0); for(int n = 0; n < out.size(); ++n) out[n] = rand(); if(sock->writeDatagram(out.data(), out.size(), maddr, port) == -1) { delete sock; return QHostAddress(); } while(1) { if(!sock->waitForReadyRead(1000)) { fprintf(stderr, "QJDns::detectPrimaryMulticast: timeout while checking %s\n", qPrintable(address.toString())); delete sock; return QHostAddress(); } QByteArray in(128, 0); QHostAddress from_addr; quint16 from_port; int ret = sock->readDatagram(in.data(), in.size(), &from_addr, &from_port); if(ret == -1) { delete sock; return QHostAddress(); } if(from_port != port) continue; in.resize(ret); if(in != out) continue; result = from_addr; break; } delete sock; return result; } void QJDns::setNameServers(const QList &list) { d->setNameServers(list); } int QJDns::queryStart(const QByteArray &name, int type) { int id = jdns_query(d->sess, (const unsigned char *)name.data(), type); d->process(); return id; } void QJDns::queryCancel(int id) { jdns_cancel_query(d->sess, id); d->removeCancelled(id); d->process(); } int QJDns::publishStart(PublishMode m, const Record &record) { jdns_rr_t *rr = export_record(record); int pubmode; if(m == QJDns::Unique) pubmode = JDNS_PUBLISH_UNIQUE; else pubmode = JDNS_PUBLISH_SHARED; int id = jdns_publish(d->sess, pubmode, rr); jdns_rr_delete(rr); d->process(); return id; } void QJDns::publishUpdate(int id, const Record &record) { jdns_rr_t *rr = export_record(record); jdns_update_publish(d->sess, id, rr); jdns_rr_delete(rr); d->process(); } void QJDns::publishCancel(int id) { jdns_cancel_publish(d->sess, id); d->removeCancelled(id); d->process(); } psi-plus-snapshots-1.4.1456/iris/src/jdns/src/qjdns/qjdns_p.h000066400000000000000000000075261370065651000237750ustar00rootroot00000000000000/* * Copyright (C) 2005-2008 Justin Karneges * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef QJDNS_P_H #define QJDNS_P_H #include "jdns.h" #include "qjdns.h" #include #include #include #include class QTimer; class QUdpSocket; class SafeTimer : public QObject { Q_OBJECT public: SafeTimer(QObject *parent = 0); ~SafeTimer(); int interval() const; bool isActive() const; bool isSingleShot() const; void setInterval(int msec); void setSingleShot(bool singleShot); int timerId() const; public slots: void start(int msec); void start(); void stop(); signals: void timeout(); private: QTimer *t; }; class QJDns::Private : public QObject { Q_OBJECT public: class LateError { public: int source_type; // 0 for query, 1 for publish int id; Error error; }; class LateResponse { public: int id; QJDns::Response response; bool do_cancel; }; QJDns *q; QJDns::Mode mode; jdns_session_t *sess; bool shutting_down; SafeTimer stepTrigger, debugTrigger; SafeTimer stepTimeout; QTime clock; QStringList debug_strings; bool new_debug_strings; int next_handle; bool need_handle; QHash socketForHandle; QHash handleForSocket; int pending; bool pending_wait; bool complete_shutdown; // pointers that will point to things we are currently signalling // about. when a query or publish is cancelled, we can use these // pointers to extract anything we shouldn't signal. QList *pErrors; QList *pPublished; QList *pResponses; Private(QJDns *_q); ~Private(); void cleanup(); bool init(QJDns::Mode _mode, const QHostAddress &address); void setNameServers(const QList &nslist); void process(); void processDebug(); void doNextStep(); void removeCancelled(int id); private slots: void udp_readyRead(); void udp_bytesWritten(qint64); void st_timeout(); void doNextStepSlot(); void doDebug(); private: static int cb_time_now(jdns_session_t *, void *app); static int cb_rand_int(jdns_session_t *, void *); static void cb_debug_line(jdns_session_t *, void *app, const char *str); static int cb_udp_bind(jdns_session_t *, void *app, const jdns_address_t *addr, int port, const jdns_address_t *maddr); static void cb_udp_unbind(jdns_session_t *, void *app, int handle); static int cb_udp_read(jdns_session_t *, void *app, int handle, jdns_address_t *addr, int *port, unsigned char *buf, int *bufsize); static int cb_udp_write(jdns_session_t *, void *app, int handle, const jdns_address_t *addr, int port, unsigned char *buf, int bufsize); }; #endif // QJDNS_P_H psi-plus-snapshots-1.4.1456/iris/src/jdns/src/qjdns/qjdns_sock.cpp000066400000000000000000000113271370065651000250220ustar00rootroot00000000000000/* * Copyright (C) 2005-2006 Justin Karneges * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "qjdns_sock.h" #include #include #include #include #ifdef Q_OS_UNIX # include # include # include # include # include # include # include # include #endif #ifdef Q_OS_WIN # include # include #endif #ifdef Q_OS_HAIKU #define QT_NO_IPV6 1 #endif #ifndef QT_NO_IPV6 # define HAVE_IPV6 # ifndef s6_addr # define IPPROTO_IPV6 41 struct in6_addr { union { unsigned char _S6_u8[16]; unsigned short _S6_u16[8]; unsigned long _S6_u32[4]; } _S6_un; }; # define s6_addr _S6_un._S6_u8 # endif // s6_addr # ifndef IPV6_JOIN_GROUP # define IPV6_JOIN_GROUP 12 # define IPV6_MULTICAST_HOPS 10 struct ipv6_mreq { struct in6_addr ipv6mr_multiaddr; unsigned int ipv6mr_interface; }; # endif // IPV6_JOIN_GROUP #endif // QT_NO_IPV6 static int get_last_error() { int x; #ifdef Q_OS_WIN x = WSAGetLastError(); #else x = errno; #endif return x; } bool qjdns_sock_setMulticast4(int s, unsigned long int addr, int *errorCode) { #ifndef Q_OS_HAIKU int ret; struct ip_mreq mc; memset(&mc, 0, sizeof(mc)); mc.imr_multiaddr.s_addr = htonl(addr); mc.imr_interface.s_addr = INADDR_ANY; ret = setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char *)&mc, sizeof(mc)); if(ret != 0) { if(errorCode) *errorCode = get_last_error(); return false; } return true; #else // Q_OS_HAIKU Q_UNUSED(s); Q_UNUSED(addr); Q_UNUSED(errorCode); return false; #endif // Q_OS_HAIKU } bool qjdns_sock_setMulticast6(int s, unsigned char *addr, int *errorCode) { #ifdef HAVE_IPV6 int ret; struct ipv6_mreq mc; memset(&mc, 0, sizeof(mc)); memcpy(mc.ipv6mr_multiaddr.s6_addr, addr, 16); mc.ipv6mr_interface = 0; ret = setsockopt(s, IPPROTO_IPV6, IPV6_JOIN_GROUP, (const char *)&mc, sizeof(mc)); if(ret != 0) { if(errorCode) *errorCode = get_last_error(); return false; } return true; #else // HAVE_IPV6 Q_UNUSED(s); Q_UNUSED(addr); Q_UNUSED(errorCode); return false; #endif // HAVE_IPV6 } bool qjdns_sock_setTTL4(int s, int ttl) { #ifndef Q_OS_HAIKU unsigned char cttl; int ret, ittl; cttl = ttl; ittl = ttl; // IP_MULTICAST_TTL might take 1 byte or 4, try both ret = setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, (const char *)&cttl, sizeof(cttl)); if(ret != 0) { ret = setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, (const char *)&ittl, sizeof(ittl)); if(ret != 0) return false; } return true; #else // Q_OS_HAIKU Q_UNUSED(s); Q_UNUSED(ttl); return false; #endif // Q_OS_HAIKU } bool qjdns_sock_setTTL6(int s, int ttl) { #ifdef HAVE_IPV6 unsigned char cttl; int ret, ittl; cttl = ttl; ittl = ttl; // IPV6_MULTICAST_HOPS might take 1 byte or 4, try both ret = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (const char *)&cttl, sizeof(cttl)); if(ret != 0) { ret = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (const char *)&ittl, sizeof(ittl)); if(ret != 0) return false; } return true; #else // HAVE_IPV6 Q_UNUSED(s); Q_UNUSED(ttl); return false; #endif // HAVE_IPV6 } bool qjdns_sock_setIPv6Only(int s) { #if defined(HAVE_IPV6) && defined(IPV6_V6ONLY) int x = 1; if(setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&x, sizeof(x)) != 0) return false; return true; #else Q_UNUSED(s); return false; #endif } psi-plus-snapshots-1.4.1456/iris/src/jdns/src/qjdns/qjdns_sock.h000066400000000000000000000026741370065651000244740ustar00rootroot00000000000000/* * Copyright (C) 2005-2006 Justin Karneges * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef QJDNS_SOCK_H #define QJDNS_SOCK_H bool qjdns_sock_setMulticast4(int s, unsigned long int addr, int *errorCode); bool qjdns_sock_setMulticast6(int s, unsigned char *addr, int *errorCode); bool qjdns_sock_setTTL4(int s, int ttl); bool qjdns_sock_setTTL6(int s, int ttl); bool qjdns_sock_setIPv6Only(int s); #endif // QJDNS_SOCK_H psi-plus-snapshots-1.4.1456/iris/src/jdns/src/qjdns/qjdnsshared.cpp000066400000000000000000001006561370065651000251760ustar00rootroot00000000000000/* * Copyright (C) 2006-2008 Justin Karneges * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ // Note: QJDnsShared supports multiple interfaces for multicast, but only one // for IPv4 and one for IPv6. Sharing multiple interfaces of the same IP // version for multicast is unfortunately not possible without reworking // the jdns subsystem. // // The reason for this limitation is that in order to do multi-interface // multicast, you have to do a single bind to Any, and then use special // functions to determine which interface a packet came from and to // specify which interface a packet should go out on. Again this is just // not possible with the current system and the assumptions made by jdns. // Note: When quering against multiple interfaces with multicast, it is // possible that different answers for a unique record may be reported // on each interface. We don't do anything about this. #include "qjdnsshared_p.h" // for caching system info class SystemInfoCache { public: QJDns::SystemInfo info; QTime time; }; Q_GLOBAL_STATIC(QMutex, jdnsshared_mutex) Q_GLOBAL_STATIC(SystemInfoCache, jdnsshared_infocache) static QJDns::SystemInfo get_sys_info() { QMutexLocker locker(jdnsshared_mutex()); SystemInfoCache *c = jdnsshared_infocache(); // cache info for 1/2 second, enough to prevent re-reading of sys // info 20 times because of all the different resolves if(c->time.isNull() || c->time.elapsed() >= 500) { c->info = QJDns::systemInfo(); c->time.start(); } return c->info; } static bool domainCompare(const QByteArray &a, const QByteArray &b) { return (qstricmp(a.data(), b.data()) == 0) ? true: false; } // adapted from jdns_mdnsd.c, _a_match() static bool matchRecordExceptTtl(const QJDns::Record &a, const QJDns::Record &b) { if(a.type != b.type || !domainCompare(a.owner, b.owner)) return false; if(a.type == QJDns::Srv) { if(domainCompare(a.name, b.name) && a.port == b.port && a.priority == b.priority && a.weight == b.weight) { return true; } } else if(a.type == QJDns::Ptr || a.type == QJDns::Ns || a.type == QJDns::Cname) { if(domainCompare(a.name, b.name)) return true; } else if(a.rdata == b.rdata) return true; return false; } static void getHex(unsigned char in, char *hi, char *lo) { auto str = QString::asprintf("%02x", in); if (!str.isEmpty()) { *hi = str[0].toLatin1(); *lo = str[1].toLatin1(); } } static QByteArray getDec(int in) { return QString::number(in).toLatin1(); } static QByteArray makeReverseName(const QHostAddress &addr) { QByteArray out; if(addr.protocol() == QAbstractSocket::IPv6Protocol) { Q_IPV6ADDR raw = addr.toIPv6Address(); for(int n = 0; n < 32; ++n) { char hi = '0', lo = '0'; getHex(raw.c[31 - n], &hi, &lo); out += lo; out += '.'; out += hi; out += '.'; } out += "ip6.arpa."; } else { quint32 rawi = addr.toIPv4Address(); int raw[4]; raw[0] = (rawi >> 24) & 0xff; raw[1] = (rawi >> 16) & 0xff; raw[2] = (rawi >> 8) & 0xff; raw[3] = rawi & 0xff; for(int n = 0; n < 4; ++n) { out += getDec(raw[3 - n]); out += '.'; } out += "in-addr.arpa."; } return out; } // adapted from qHash static inline uint qHash(const Handle &key) { uint h1 = ::qHash(key.jdns); uint h2 = ::qHash(key.id); return ((h1 << 16) | (h1 >> 16)) ^ h2; } //---------------------------------------------------------------------------- // JDnsShutdown //---------------------------------------------------------------------------- void JDnsShutdownAgent::start() { QMetaObject::invokeMethod(this, "started", Qt::QueuedConnection); } JDnsShutdownWorker::JDnsShutdownWorker(const QList &_list) : QObject(0), list(_list) { foreach(QJDnsShared *i, list) { connect(i, SIGNAL(shutdownFinished()), SLOT(jdns_shutdownFinished())); i->shutdown(); // MUST support DOR-DS, and it does } } void JDnsShutdownWorker::jdns_shutdownFinished() { QJDnsShared *i = static_cast(sender()); list.removeAll(i); delete i; if(list.isEmpty()) emit finished(); } void JDnsShutdown::waitForShutdown(const QList &_list) { list = _list; phase = 0; m.lock(); start(); w.wait(&m); foreach(QJDnsShared *i, list) { i->setParent(0); i->moveToThread(this); } phase = 1; agent->start(); wait(); } void JDnsShutdown::run() { m.lock(); agent = new JDnsShutdownAgent; connect(agent, SIGNAL(started()), SLOT(agent_started()), Qt::DirectConnection); agent->start(); exec(); delete agent; } void JDnsShutdown::agent_started() { if(phase == 0) { w.wakeOne(); m.unlock(); } else { worker = new JDnsShutdownWorker(list); connect(worker, SIGNAL(finished()), SLOT(worker_finished()), Qt::DirectConnection); } } void JDnsShutdown::worker_finished() { delete worker; worker = 0; quit(); } //---------------------------------------------------------------------------- // QJDnsSharedDebug //---------------------------------------------------------------------------- QJDnsSharedDebugPrivate::QJDnsSharedDebugPrivate(QJDnsSharedDebug *_q) : QObject(_q) , q(_q) { dirty = false; } void QJDnsSharedDebugPrivate::addDebug(const QString &name, const QStringList &_lines) { if(!_lines.isEmpty()) { QMutexLocker locker(&m); for(int n = 0; n < _lines.count(); ++n) lines += name + ": " + _lines[n]; if(!dirty) { dirty = true; QMetaObject::invokeMethod(this, "doUpdate", Qt::QueuedConnection); } } } void QJDnsSharedDebugPrivate::doUpdate() { { QMutexLocker locker(&m); if(!dirty) return; } emit q->readyRead(); } QJDnsSharedDebug::QJDnsSharedDebug(QObject *parent) :QObject(parent) { d = new QJDnsSharedDebugPrivate(this); } QJDnsSharedDebug::~QJDnsSharedDebug() { delete d; } QStringList QJDnsSharedDebug::readDebugLines() { QMutexLocker locker(&d->m); QStringList tmplines = d->lines; d->lines.clear(); d->dirty = false; return tmplines; } //---------------------------------------------------------------------------- // QJDnsSharedRequest //---------------------------------------------------------------------------- QJDnsSharedPrivate::QJDnsSharedPrivate(QJDnsShared *_q) : QObject(_q) , q(_q) { } QJDnsSharedRequest *QJDnsSharedPrivate::findRequest(QJDns *jdns, int id) const { Handle h(jdns, id); return requestForHandle.value(h); } void QJDnsSharedPrivate::jdns_link(QJDns *jdns) { connect(jdns, SIGNAL(resultsReady(int,QJDns::Response)), SLOT(jdns_resultsReady(int,QJDns::Response))); connect(jdns, SIGNAL(published(int)), SLOT(jdns_published(int))); connect(jdns, SIGNAL(error(int,QJDns::Error)), SLOT(jdns_error(int,QJDns::Error))); connect(jdns, SIGNAL(shutdownFinished()), SLOT(jdns_shutdownFinished())); connect(jdns, SIGNAL(debugLinesReady()), SLOT(jdns_debugLinesReady())); } int QJDnsSharedPrivate::getNewIndex() const { // find lowest unused value for(int n = 0;; ++n) { bool found = false; foreach(Instance *i, instances) { if(i->index == n) { found = true; break; } } if(!found) return n; } } void QJDnsSharedPrivate::addDebug(int index, const QString &line) { if(db) db->d->addDebug(dbname + QString::number(index), QStringList() << line); } void QJDnsSharedPrivate::doDebug(QJDns *jdns, int index) { QStringList lines = jdns->debugLines(); if(db) db->d->addDebug(dbname + QString::number(index), lines); } QJDnsSharedPrivate::PreprocessMode QJDnsSharedPrivate::determinePpMode(const QJDns::Record &in) { // Note: since our implementation only allows 1 ipv4 and 1 ipv6 // interface to exist, it is safe to publish both kinds of // records on both interfaces, with the same values. For // example, an A record can be published on both interfaces, // with the value set to the ipv4 interface. If we supported // multiple ipv4 interfaces, then this wouldn't work, because // we wouldn't know which value to use for the A record when // publishing on the ipv6 interface. // publishing our own IP address? null address means the user // wants us to fill in the blank with our address. if((in.type == QJDns::Aaaa || in.type == QJDns::A) && in.address.isNull()) { return FillInAddress; } // publishing our own reverse lookup? partial owner means // user wants us to fill in the rest. else if(in.type == QJDns::Ptr && in.owner == ".ip6.arpa.") { return FillInPtrOwner6; } else if(in.type == QJDns::Ptr && in.owner == ".in-addr.arpa.") { return FillInPtrOwner4; } return None; } QJDns::Record QJDnsSharedPrivate::manipulateRecord(const QJDns::Record &in, PreprocessMode ppmode, bool *modified) { if(ppmode == FillInAddress) { QJDns::Record out = in; if(in.type == QJDns::Aaaa) { // are we operating on ipv6? foreach(Instance *i, instances) { if(i->addr.protocol() == QAbstractSocket::IPv6Protocol) { if(modified && !(out.address == i->addr)) *modified = true; out.address = i->addr; break; } } } else // A { // are we operating on ipv4? foreach(Instance *i, instances) { if(i->addr.protocol() == QAbstractSocket::IPv4Protocol) { if(modified && !(out.address == i->addr)) *modified = true; out.address = i->addr; break; } } } return out; } else if(ppmode == FillInPtrOwner6) { QJDns::Record out = in; // are we operating on ipv6? foreach(Instance *i, instances) { if(i->addr.protocol() == QAbstractSocket::IPv6Protocol) { QByteArray newOwner = makeReverseName(i->addr); if(modified && !(out.owner == newOwner)) *modified = true; out.owner = newOwner; break; } } return out; } else if(ppmode == FillInPtrOwner4) { QJDns::Record out = in; // are we operating on ipv4? foreach(Instance *i, instances) { if(i->addr.protocol() == QAbstractSocket::IPv4Protocol) { QByteArray newOwner = makeReverseName(i->addr); if(modified && !(out.owner == newOwner)) *modified = true; out.owner = newOwner; break; } } return out; } if(modified) *modified = false; return in; } void QJDnsSharedPrivate::late_shutdown() { shutting_down = false; emit q->shutdownFinished(); } QJDnsSharedRequestPrivate::QJDnsSharedRequestPrivate(QJDnsSharedRequest *_q) : QObject(_q), q(_q), lateTimer(this) { connect(&lateTimer, SIGNAL(timeout()), SLOT(lateTimer_timeout())); } void QJDnsSharedRequestPrivate::resetSession() { name = QByteArray(); pubrecord = QJDns::Record(); handles.clear(); published.clear(); queryCache.clear(); } void QJDnsSharedRequestPrivate::lateTimer_timeout() { emit q->resultsReady(); } QJDnsSharedRequest::QJDnsSharedRequest(QJDnsShared *jdnsShared, QObject *parent) :QObject(parent) { d = new QJDnsSharedRequestPrivate(this); d->jsp = jdnsShared->d; } QJDnsSharedRequest::~QJDnsSharedRequest() { cancel(); delete d; } QJDnsSharedRequest::Type QJDnsSharedRequest::type() { return d->type; } void QJDnsSharedRequest::query(const QByteArray &name, int type) { cancel(); d->jsp->queryStart(this, name, type); } void QJDnsSharedRequest::publish(QJDns::PublishMode m, const QJDns::Record &record) { cancel(); d->jsp->publishStart(this, m, record); } void QJDnsSharedRequest::publishUpdate(const QJDns::Record &record) { // only allowed to update if we have an active publish if(!d->handles.isEmpty() && d->type == Publish) d->jsp->publishUpdate(this, record); } void QJDnsSharedRequest::cancel() { d->lateTimer.stop(); if(!d->handles.isEmpty()) { if(d->type == Query) d->jsp->queryCancel(this); else d->jsp->publishCancel(this); } d->resetSession(); } bool QJDnsSharedRequest::success() const { return d->success; } QJDnsSharedRequest::Error QJDnsSharedRequest::error() const { return d->error; } QList QJDnsSharedRequest::results() const { return d->results; } //---------------------------------------------------------------------------- // QJDnsShared //---------------------------------------------------------------------------- QJDnsShared::QJDnsShared(Mode mode, QObject *parent) :QObject(parent) { d = new QJDnsSharedPrivate(this); d->mode = mode; d->shutting_down = false; d->db = 0; } QJDnsShared::~QJDnsShared() { foreach(QJDnsSharedPrivate::Instance *i, d->instances) { delete i->jdns; delete i; } delete d; } void QJDnsShared::setDebug(QJDnsSharedDebug *db, const QString &name) { d->db = db; d->dbname = name; } bool QJDnsShared::addInterface(const QHostAddress &addr) { return d->addInterface(addr); } void QJDnsShared::removeInterface(const QHostAddress &addr) { d->removeInterface(addr); } void QJDnsShared::shutdown() { d->shutting_down = true; if(!d->instances.isEmpty()) { foreach(QJDnsSharedPrivate::Instance *i, d->instances) i->jdns->shutdown(); } else QMetaObject::invokeMethod(d, "late_shutdown", Qt::QueuedConnection); } QList QJDnsShared::domains() { return get_sys_info().domains; } void QJDnsShared::waitForShutdown(const QList &instances) { JDnsShutdown s; s.waitForShutdown(instances); } bool QJDnsSharedPrivate::addInterface(const QHostAddress &addr) { if(shutting_down) return false; // make sure we don't have this one already foreach(Instance *i, instances) { if(i->addr == addr) return false; } int index = getNewIndex(); addDebug(index, QString("attempting to use interface %1").arg(addr.toString())); QJDns *jdns; if(mode == QJDnsShared::UnicastInternet || mode == QJDnsShared::UnicastLocal) { jdns = new QJDns(this); jdns_link(jdns); if(!jdns->init(QJDns::Unicast, addr)) { doDebug(jdns, index); delete jdns; return false; } if(mode == QJDnsShared::UnicastLocal) { QJDns::NameServer host; if(addr.protocol() == QAbstractSocket::IPv6Protocol) host.address = QHostAddress("FF02::FB"); else host.address = QHostAddress("224.0.0.251"); host.port = 5353; jdns->setNameServers(QList() << host); } } else // Multicast { // only one multicast interface allowed per IP protocol version. // this is because we bind to INADDR_ANY. bool have_v6 = false; bool have_v4 = false; foreach(Instance *i, instances) { if(i->addr.protocol() == QAbstractSocket::IPv6Protocol) have_v6 = true; else have_v4 = true; } bool is_v6 = (addr.protocol() == QAbstractSocket::IPv6Protocol) ? true : false; if(is_v6 && have_v6) { addDebug(index, "already have an ipv6 interface"); return false; } if(!is_v6 && have_v4) { addDebug(index, "already have an ipv4 interface"); return false; } QHostAddress actualBindAddress; if(is_v6) actualBindAddress = QHostAddress::AnyIPv6; else actualBindAddress = QHostAddress::Any; jdns = new QJDns(this); jdns_link(jdns); if(!jdns->init(QJDns::Multicast, actualBindAddress)) { doDebug(jdns, index); delete jdns; return false; } } Instance *i = new Instance; i->jdns = jdns; i->addr = addr; i->index = index; instances += i; instanceForQJDns.insert(i->jdns, i); addDebug(index, "interface ready"); if(mode == QJDnsShared::Multicast) { // extend active requests to this interface foreach(QJDnsSharedRequest *obj, requests) { if(obj->d->type == QJDnsSharedRequest::Query) { Handle h(i->jdns, i->jdns->queryStart(obj->d->name, obj->d->qType)); obj->d->handles += h; requestForHandle.insert(h, obj); } else // Publish { bool modified; obj->d->pubrecord = manipulateRecord(obj->d->pubrecord, obj->d->ppmode, &modified); // if the record changed, update on the other (existing) interfaces if(modified) { foreach(Handle h, obj->d->handles) h.jdns->publishUpdate(h.id, obj->d->pubrecord); } // publish the record on the new interface Handle h(i->jdns, i->jdns->publishStart(obj->d->pubmode, obj->d->pubrecord)); obj->d->handles += h; requestForHandle.insert(h, obj); } } } return true; } void QJDnsSharedPrivate::removeInterface(const QHostAddress &addr) { Instance *i = 0; for(int n = 0; n < instances.count(); ++n) { if(instances[n]->addr == addr) { i = instances[n]; break; } } if(!i) return; int index = i->index; // we don't cancel operations or shutdown jdns, we simply // delete our references. this is because if the interface // is gone, then we have nothing to send on anyway. foreach(QJDnsSharedRequest *obj, requests) { for(int n = 0; n < obj->d->handles.count(); ++n) { Handle h = obj->d->handles[n]; if(h.jdns == i->jdns) { // see above, no need to cancel the operation obj->d->handles.removeAt(n); requestForHandle.remove(h); break; } } // remove published reference if(obj->d->type == QJDnsSharedRequest::Publish) { for(int n = 0; n < obj->d->published.count(); ++n) { Handle h = obj->d->published[n]; if(h.jdns == i->jdns) { obj->d->published.removeAt(n); break; } } } } // see above, no need to shutdown jdns instanceForQJDns.remove(i->jdns); instances.removeAll(i); delete i->jdns; delete i; // if that was the last interface to be removed, then there should // be no more handles left. let's take action with these // handleless requests. foreach(QJDnsSharedRequest *obj, requests) { if(obj->d->handles.isEmpty()) { if(mode == QJDnsShared::UnicastInternet || mode == QJDnsShared::UnicastLocal) { // for unicast, we'll invalidate with ErrorNoNet obj->d->success = false; obj->d->error = QJDnsSharedRequest::ErrorNoNet; obj->d->lateTimer.start(); } else // Multicast { // for multicast, we'll keep all requests alive. // activity will resume when an interface is // added. } } } addDebug(index, QString("removing from %1").arg(addr.toString())); } void QJDnsSharedPrivate::queryStart(QJDnsSharedRequest *obj, const QByteArray &name, int qType) { obj->d->type = QJDnsSharedRequest::Query; obj->d->success = false; obj->d->results.clear(); obj->d->name = name; obj->d->qType = qType; // is the input an IP address and the qType is an address record? if(qType == QJDns::Aaaa || qType == QJDns::A) { QHostAddress addr; if(addr.setAddress(QString::fromLocal8Bit(name))) { if(qType == QJDns::Aaaa && addr.protocol() == QAbstractSocket::IPv6Protocol) { QJDns::Record rec; rec.owner = name; rec.type = QJDns::Aaaa; rec.ttl = 120; rec.haveKnown = true; rec.address = addr; obj->d->success = true; obj->d->results = QList() << rec; obj->d->lateTimer.start(); return; } else if(qType == QJDns::A && addr.protocol() == QAbstractSocket::IPv4Protocol) { QJDns::Record rec; rec.owner = name; rec.type = QJDns::A; rec.ttl = 120; rec.haveKnown = true; rec.address = addr; obj->d->success = true; obj->d->results = QList() << rec; obj->d->lateTimer.start(); return; } } } QJDns::SystemInfo sysInfo = get_sys_info(); // is the input name a known host and the qType is an address record? if(qType == QJDns::Aaaa || qType == QJDns::A) { QByteArray lname = name.toLower(); QList known = sysInfo.hosts; foreach(QJDns::DnsHost host, known) { if(((qType == QJDns::Aaaa && host.address.protocol() == QAbstractSocket::IPv6Protocol) || (qType == QJDns::A && host.address.protocol() == QAbstractSocket::IPv4Protocol)) && host.name.toLower() == lname) { QJDns::Record rec; rec.owner = name; rec.type = qType; rec.ttl = 120; rec.haveKnown = true; rec.address = host.address; obj->d->success = true; obj->d->results = QList() << rec; obj->d->lateTimer.start(); return; } } } // if we have no QJDns instances to operate on, then error if(instances.isEmpty()) { obj->d->error = QJDnsSharedRequest::ErrorNoNet; obj->d->lateTimer.start(); return; } if(mode == QJDnsShared::UnicastInternet) { // get latest nameservers, split into ipv6/v4, apply to jdns instances QList ns_v6; QList ns_v4; { QList nameServers = sysInfo.nameServers; foreach(QJDns::NameServer ns, nameServers) { if(ns.address.protocol() == QAbstractSocket::IPv6Protocol) ns_v6 += ns; else ns_v4 += ns; } } foreach(Instance *i, instances) { if(i->addr.protocol() == QAbstractSocket::IPv6Protocol) i->jdns->setNameServers(ns_v6); else i->jdns->setNameServers(ns_v4); } } // keep track of this request requests += obj; // query on all jdns instances foreach(Instance *i, instances) { Handle h(i->jdns, i->jdns->queryStart(name, qType)); obj->d->handles += h; // keep track of this handle for this request requestForHandle.insert(h, obj); } } void QJDnsSharedPrivate::queryCancel(QJDnsSharedRequest *obj) { if(!requests.contains(obj)) return; foreach(Handle h, obj->d->handles) { h.jdns->queryCancel(h.id); requestForHandle.remove(h); } obj->d->handles.clear(); requests.remove(obj); } void QJDnsSharedPrivate::publishStart(QJDnsSharedRequest *obj, QJDns::PublishMode m, const QJDns::Record &record) { obj->d->type = QJDnsSharedRequest::Publish; obj->d->success = false; obj->d->results.clear(); obj->d->pubmode = m; obj->d->ppmode = determinePpMode(record); obj->d->pubrecord = manipulateRecord(record, obj->d->ppmode); // if we have no QJDns instances to operate on, then error if(instances.isEmpty()) { obj->d->error = QJDnsSharedRequest::ErrorNoNet; obj->d->lateTimer.start(); return; } // keep track of this request requests += obj; // attempt to publish on all jdns instances foreach(QJDnsSharedPrivate::Instance *i, instances) { Handle h(i->jdns, i->jdns->publishStart(m, obj->d->pubrecord)); obj->d->handles += h; // keep track of this handle for this request requestForHandle.insert(h, obj); } } void QJDnsSharedPrivate::publishUpdate(QJDnsSharedRequest *obj, const QJDns::Record &record) { if(!requests.contains(obj)) return; obj->d->ppmode = determinePpMode(record); obj->d->pubrecord = manipulateRecord(record, obj->d->ppmode); // publish update on all handles for this request foreach(Handle h, obj->d->handles) h.jdns->publishUpdate(h.id, obj->d->pubrecord); } void QJDnsSharedPrivate::publishCancel(QJDnsSharedRequest *obj) { if(!requests.contains(obj)) return; foreach(Handle h, obj->d->handles) { h.jdns->publishCancel(h.id); requestForHandle.remove(h); } obj->d->handles.clear(); obj->d->published.clear(); requests.remove(obj); } void QJDnsSharedPrivate::jdns_resultsReady(int id, const QJDns::Response &results) { QJDns *jdns = static_cast(sender()); QJDnsSharedRequest *obj = findRequest(jdns, id); Q_ASSERT(obj); obj->d->success = true; obj->d->results = results.answerRecords; if(mode == QJDnsShared::UnicastInternet || mode == QJDnsShared::UnicastLocal) { // only one response, so "cancel" it for(int n = 0; n < obj->d->handles.count(); ++n) { Handle h = obj->d->handles[n]; if(h.jdns == jdns && h.id == id) { obj->d->handles.removeAt(n); requestForHandle.remove(h); break; } } // cancel related handles foreach(Handle h, obj->d->handles) { h.jdns->queryCancel(h.id); requestForHandle.remove(h); } obj->d->handles.clear(); requests.remove(obj); } else // Multicast { // check our cache to see how we should report these results for(int n = 0; n < obj->d->results.count(); ++n) { QJDns::Record &r = obj->d->results[n]; // do we have this answer already in our cache? QJDns::Record *c = 0; int c_at = -1; for(int k = 0; k < obj->d->queryCache.count(); ++k) { QJDns::Record &tmp = obj->d->queryCache[k]; if(matchRecordExceptTtl(r, tmp)) { c = &tmp; c_at = k; break; } } // don't report duplicates or unknown removals if((c && r.ttl != 0) || (!c && r.ttl == 0)) { obj->d->results.removeAt(n); --n; // adjust position continue; } // if we have it, and it is removed, remove from cache if(c && r.ttl == 0) { obj->d->queryCache.removeAt(c_at); } // otherwise, if we don't have it, add it to the cache else if(!c) { obj->d->queryCache += r; } } if(obj->d->results.isEmpty()) return; } emit obj->resultsReady(); } void QJDnsSharedPrivate::jdns_published(int id) { QJDns *jdns = static_cast(sender()); QJDnsSharedRequest *obj = findRequest(jdns, id); Q_ASSERT(obj); // find handle Handle handle; for(int n = 0; n < obj->d->handles.count(); ++n) { Handle h = obj->d->handles[n]; if(h.jdns == jdns && h.id == id) { handle = h; break; } } obj->d->published += handle; // if this publish has already been considered successful, then // a publish has succeeded on a new interface and there's no // need to report success for this request again if(obj->d->success) return; // all handles published? if(obj->d->published.count() == obj->d->handles.count()) { obj->d->success = true; emit obj->resultsReady(); } } void QJDnsSharedPrivate::jdns_error(int id, QJDns::Error e) { QJDns *jdns = static_cast(sender()); QJDnsSharedRequest *obj = findRequest(jdns, id); Q_ASSERT(obj); // "cancel" it for(int n = 0; n < obj->d->handles.count(); ++n) { Handle h = obj->d->handles[n]; if(h.jdns == jdns && h.id == id) { obj->d->handles.removeAt(n); requestForHandle.remove(h); break; } } if(obj->d->type == QJDnsSharedRequest::Query) { // ignore the error if it is not the last error if(!obj->d->handles.isEmpty()) return; requests.remove(obj); obj->d->success = false; QJDnsSharedRequest::Error x = QJDnsSharedRequest::ErrorGeneric; if(e == QJDns::ErrorNXDomain) x = QJDnsSharedRequest::ErrorNXDomain; else if(e == QJDns::ErrorTimeout) x = QJDnsSharedRequest::ErrorTimeout; else // ErrorGeneric x = QJDnsSharedRequest::ErrorGeneric; obj->d->error = x; emit obj->resultsReady(); } else // Publish { // cancel related handles foreach(Handle h, obj->d->handles) { h.jdns->publishCancel(h.id); requestForHandle.remove(h); } obj->d->handles.clear(); obj->d->published.clear(); requests.remove(obj); obj->d->success = false; QJDnsSharedRequest::Error x = QJDnsSharedRequest::ErrorGeneric; if(e == QJDns::ErrorConflict) x = QJDnsSharedRequest::ErrorConflict; else // ErrorGeneric x = QJDnsSharedRequest::ErrorGeneric; obj->d->error = x; emit obj->resultsReady(); } } void QJDnsSharedPrivate::jdns_shutdownFinished() { QJDns *jdns = static_cast(sender()); addDebug(instanceForQJDns.value(jdns)->index, "jdns_shutdownFinished, removing interface"); Instance *instance = instanceForQJDns.value(jdns); delete instance->jdns; delete instance; instanceForQJDns.remove(jdns); instances.removeAll(instance); if(instances.isEmpty()) late_shutdown(); } void QJDnsSharedPrivate::jdns_debugLinesReady() { QJDns *jdns = static_cast(sender()); doDebug(jdns, instanceForQJDns.value(jdns)->index); } psi-plus-snapshots-1.4.1456/iris/src/jdns/src/qjdns/qjdnsshared_p.h000066400000000000000000000134721370065651000251610ustar00rootroot00000000000000/* * Copyright (C) 2006-2008 Justin Karneges * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef QJDNSSHARED_P_H #define QJDNSSHARED_P_H // SafeTimer #include "qjdns_p.h" #include "qjdnsshared.h" #include #include #include #include #include #include #include class JDnsShutdownAgent : public QObject { Q_OBJECT public: void start(); signals: void started(); }; class JDnsShutdownWorker : public QObject { Q_OBJECT public: QList list; JDnsShutdownWorker(const QList &_list); signals: void finished(); private slots: void jdns_shutdownFinished(); }; class JDnsShutdown : public QThread { Q_OBJECT public: QMutex m; QWaitCondition w; QList list; JDnsShutdownAgent *agent; JDnsShutdownWorker *worker; int phase; void waitForShutdown(const QList &_list); protected: virtual void run(); private slots: void agent_started(); void worker_finished(); }; class QJDnsSharedDebugPrivate : public QObject { Q_OBJECT public: QJDnsSharedDebug *q; QMutex m; QStringList lines; bool dirty; QJDnsSharedDebugPrivate(QJDnsSharedDebug *_q); void addDebug(const QString &name, const QStringList &_lines); private slots: void doUpdate(); }; //---------------------------------------------------------------------------- // Handle //---------------------------------------------------------------------------- // QJDns uses integer handle ids, but they are only unique within // the relevant QJDns instance. Since we want our handles to be // unique across all instances, we'll make an instance/id pair. class Handle { public: QJDns *jdns; int id; Handle() : jdns(0), id(-1) { } Handle(QJDns *_jdns, int _id) : jdns(_jdns), id(_id) { } bool operator==(const Handle &a) const { if(a.jdns == jdns && a.id == id) return true; return false; } bool operator!=(const Handle &a) const { return !(operator==(a)); } }; class QJDnsSharedPrivate : public QObject { Q_OBJECT public: class Instance { public: QJDns *jdns; QHostAddress addr; int index; Instance() : jdns(0) { } }; enum PreprocessMode { None, // don't muck with anything FillInAddress, // for A/AAAA FillInPtrOwner6, // for PTR, IPv6 FillInPtrOwner4, // for PTR, IPv4 }; QJDnsShared *q; QJDnsShared::Mode mode; bool shutting_down; QJDnsSharedDebug *db; QString dbname; QList instances; QHash instanceForQJDns; QSet requests; QHash requestForHandle; QJDnsSharedPrivate(QJDnsShared *_q); QJDnsSharedRequest *findRequest(QJDns *jdns, int id) const; void jdns_link(QJDns *jdns); int getNewIndex() const; void addDebug(int index, const QString &line); void doDebug(QJDns *jdns, int index); PreprocessMode determinePpMode(const QJDns::Record &in); QJDns::Record manipulateRecord(const QJDns::Record &in, PreprocessMode ppmode, bool *modified = 0); bool addInterface(const QHostAddress &addr); void removeInterface(const QHostAddress &addr); void queryStart(QJDnsSharedRequest *obj, const QByteArray &name, int qType); void queryCancel(QJDnsSharedRequest *obj); void publishStart(QJDnsSharedRequest *obj, QJDns::PublishMode m, const QJDns::Record &record); void publishUpdate(QJDnsSharedRequest *obj, const QJDns::Record &record); void publishCancel(QJDnsSharedRequest *obj); public slots: void late_shutdown(); private slots: void jdns_resultsReady(int id, const QJDns::Response &results); void jdns_published(int id); void jdns_error(int id, QJDns::Error e); void jdns_shutdownFinished(); void jdns_debugLinesReady(); }; class QJDnsSharedRequestPrivate : public QObject { Q_OBJECT public: QJDnsSharedRequest *q; QJDnsSharedPrivate *jsp; // current action QJDnsSharedRequest::Type type; QByteArray name; int qType; QJDns::PublishMode pubmode; QJDnsSharedPrivate::PreprocessMode ppmode; QJDns::Record pubrecord; // a single request might have to perform multiple QJDns operations QList handles; // keep a list of handles that successfully publish QList published; // use to weed out dups for multicast QList queryCache; bool success; QJDnsSharedRequest::Error error; QList results; SafeTimer lateTimer; QJDnsSharedRequestPrivate(QJDnsSharedRequest *_q); void resetSession(); private slots: void lateTimer_timeout(); }; #endif // QJDNSSHARED_P_H psi-plus-snapshots-1.4.1456/iris/src/jdns/tools/000077500000000000000000000000001370065651000214065ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/jdns/tools/jdns/000077500000000000000000000000001370065651000223445ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/jdns/tools/jdns/CMakeLists.txt000066400000000000000000000020341370065651000251030ustar00rootroot00000000000000set(jdns_tool_MOC_HDRS main.h ) if(NOT Qt5Core_FOUND) qt4_wrap_cpp(jdns_tool_MOC_SRCS ${jdns_tool_MOC_HDRS}) endif() set(jdns_tool_SRCS main.cpp ) add_executable(jdns-tool ${jdns_tool_SRCS} ${jdns_tool_MOC_SRCS} ${jdns_tool_MOC_SRCS}) target_link_libraries(jdns-tool jdns qjdns) set_target_properties(jdns-tool PROPERTIES OUTPUT_NAME jdns ) install(TARGETS jdns-tool LIBRARY DESTINATION ${LIB_INSTALL_DIR} RUNTIME DESTINATION ${BIN_INSTALL_DIR} ARCHIVE DESTINATION ${LIB_INSTALL_DIR} # FRAMEWORK DESTINATION ${FRAMEWORK_INSTALL_DIR} ) if(MSVC) get_target_property(LOCATION jdns-tool LOCATION_DEBUG) string(REGEX REPLACE "\\.[^.]*$" ".pdb" LOCATION "${LOCATION}") install(FILES ${LOCATION} DESTINATION ${BIN_INSTALL_DIR} CONFIGURATIONS Debug) get_target_property(LOCATION jdns-tool LOCATION_RELWITHDEBINFO) string(REGEX REPLACE "\\.[^.]*$" ".pdb" LOCATION "${LOCATION}") install(FILES ${LOCATION} DESTINATION ${BIN_INSTALL_DIR} CONFIGURATIONS RelWithDebInfo) endif(MSVC) psi-plus-snapshots-1.4.1456/iris/src/jdns/tools/jdns/main.cpp000066400000000000000000000412041370065651000237750ustar00rootroot00000000000000/* * Copyright (C) 2005 Justin Karneges * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "main.h" #include "qjdns.h" #include #include QString dataToString(const QByteArray &buf) { QString out; for(int n = 0; n < buf.size(); ++n) { unsigned char c = (unsigned char)buf[n]; if(c == '\\') out += "\\\\"; else if(c >= 0x20 && c < 0x7f) out += c; else out += QString().sprintf("\\x%02x", (unsigned int)c); } return out; } void print_record(const QJDns::Record &r) { switch(r.type) { case QJDns::A: printf(" A: [%s] (ttl=%d)\n", qPrintable(r.address.toString()), r.ttl); break; case QJDns::Aaaa: printf(" AAAA: [%s] (ttl=%d)\n", qPrintable(r.address.toString()), r.ttl); break; case QJDns::Mx: printf(" MX: [%s] priority=%d (ttl=%d)\n", r.name.data(), r.priority, r.ttl); break; case QJDns::Srv: printf(" SRV: [%s] port=%d priority=%d weight=%d (ttl=%d)\n", r.name.data(), r.port, r.priority, r.weight, r.ttl); break; case QJDns::Cname: printf(" CNAME: [%s] (ttl=%d)\n", r.name.data(), r.ttl); break; case QJDns::Ptr: printf(" PTR: [%s] (ttl=%d)\n", r.name.data(), r.ttl); break; case QJDns::Txt: { printf(" TXT: count=%d (ttl=%d)\n", r.texts.count(), r.ttl); for(int n = 0; n < r.texts.count(); ++n) printf(" len=%d [%s]\n", r.texts[n].size(), qPrintable(dataToString(r.texts[n]))); break; } case QJDns::Hinfo: printf(" HINFO: [%s] [%s] (ttl=%d)\n", r.cpu.data(), r.os.data(), r.ttl); break; case QJDns::Ns: printf(" NS: [%s] (ttl=%d)\n", r.name.data(), r.ttl); break; default: printf(" (Unknown): type=%d, size=%d (ttl=%d)\n", r.type, r.rdata.size(), r.ttl); break; } } App::App() { connect(&jdns, SIGNAL(resultsReady(int,QJDns::Response)), SLOT(jdns_resultsReady(int,QJDns::Response))); connect(&jdns, SIGNAL(published(int)), SLOT(jdns_published(int))); connect(&jdns, SIGNAL(error(int,QJDns::Error)), SLOT(jdns_error(int,QJDns::Error))); connect(&jdns, SIGNAL(shutdownFinished()), SLOT(jdns_shutdownFinished())); connect(&jdns, SIGNAL(debugLinesReady()), SLOT(jdns_debugLinesReady())); } App::~App() { } void App::start() { if(mode == "uni") { if(!jdns.init(QJDns::Unicast, opt_ipv6 ? QHostAddress::AnyIPv6 : QHostAddress::Any)) { jdns_debugLinesReady(); printf("unable to bind\n"); emit quit(); return; } QList addrs; for(int n = 0; n < nslist.count(); ++n) { QJDns::NameServer host; QString str = nslist[n]; if(str == "mul") { if(opt_ipv6) host.address = QHostAddress("FF02::FB"); else host.address = QHostAddress("224.0.0.251"); host.port = 5353; } else { int at = str.indexOf(';'); if(at != -1) { host.address = QHostAddress(str.mid(0, at)); host.port = str.mid(at + 1).toInt(); } else { host.address = QHostAddress(str); } } if(host.address.isNull() || host.port <= 0) { printf("bad nameserver: [%s]\n", qPrintable(nslist[n])); emit quit(); return; } addrs += host; } if(addrs.isEmpty()) addrs = QJDns::systemInfo().nameServers; if(addrs.isEmpty()) { printf("no nameservers were detected or specified\n"); emit quit(); return; } jdns.setNameServers(addrs); } else { if(!jdns.init(QJDns::Multicast, opt_ipv6 ? QHostAddress::AnyIPv6 : QHostAddress::Any)) { jdns_debugLinesReady(); printf("unable to bind\n"); emit quit(); return; } } if(mode == "uni" || mode == "mul") { int x = QJDns::A; if(type == "ptr") x = QJDns::Ptr; else if(type == "srv") x = QJDns::Srv; else if(type == "a") x = QJDns::A; else if(type == "aaaa") x = QJDns::Aaaa; else if(type == "mx") x = QJDns::Mx; else if(type == "txt") x = QJDns::Txt; else if(type == "hinfo") x = QJDns::Hinfo; else if(type == "cname") x = QJDns::Cname; else if(type == "any") x = QJDns::Any; else { bool ok; int y = type.toInt(&ok); if(ok) x = y; } req_id = jdns.queryStart(name.toLatin1(), x); printf("[%d] Querying for [%s] type=%d ...\n", req_id, qPrintable(name), x); } else // publish { for(int n = 0; n < pubitems.count(); ++n) { const QJDns::Record &rr = pubitems[n]; QJDns::PublishMode m = QJDns::Unique; if(rr.type == QJDns::Ptr) m = QJDns::Shared; int id = jdns.publishStart(m, rr); printf("[%d] Publishing [%s] type=%d ...\n", id, rr.owner.data(), rr.type); } } if(opt_quit) QTimer::singleShot(quit_time * 1000, this, SLOT(doShutdown())); } void App::jdns_resultsReady(int id, const QJDns::Response &results) { printf("[%d] Results\n", id); for(int n = 0; n < results.answerRecords.count(); ++n) print_record(results.answerRecords[n]); if(mode == "uni") jdns.shutdown(); } void App::jdns_published(int id) { printf("[%d] Published\n", id); } void App::jdns_error(int id, QJDns::Error e) { QString str; if(e == QJDns::ErrorGeneric) str = "Generic"; else if(e == QJDns::ErrorNXDomain) str = "NXDomain"; else if(e == QJDns::ErrorTimeout) str = "Timeout"; else if(e == QJDns::ErrorConflict) str = "Conflict"; printf("[%d] Error: %s\n", id, qPrintable(str)); jdns.shutdown(); } void App::jdns_shutdownFinished() { emit quit(); } void App::jdns_debugLinesReady() { QStringList lines = jdns.debugLines(); if(opt_debug) { for(int n = 0; n < lines.count(); ++n) printf("jdns: %s\n", qPrintable(lines[n])); } } void App::doShutdown() { jdns.shutdown(); } void usage() { printf("usage: jdns (options) uni [type] [name] (nameserver(;port)|mul ...)\n"); printf(" jdns (options) mul [type] [name]\n"); printf(" jdns (options) pub [items ...]\n"); printf(" jdns sys\n"); printf("\n"); printf("options:\n"); printf(" -d show debug output\n"); printf(" -6 use ipv6\n"); printf(" -q x quit x seconds after starting\n"); printf("\n"); printf("uni/mul types: a aaaa ptr srv mx txt hinfo cname any\n"); printf("pub items: ptr:name,answer srv:name,answer,port a:name,ipaddr\n"); printf(" txt:name,str0,...,strn aaaa:name,ipaddr\n"); printf("\n"); printf("examples:\n"); printf(" jdns uni a jabber.org 192.168.0.1\n"); printf(" jdns uni srv _xmpp-client._tcp.jabber.org 192.168.0.1;53\n"); printf(" jdns uni 10 user@host._presence._tcp.local mul\n"); printf(" jdns mul a foobar.local\n"); printf(" jdns mul ptr _services._dns-sd._udp.local\n"); printf(" jdns pub a:mybox.local.,192.168.0.55\n"); printf("\n"); } int main(int argc, char **argv) { QCoreApplication app(argc, argv); if(argc < 2) { usage(); return 1; } // get args QStringList args; for(int n = 1; n < argc; ++n) args += QString(argv[n]); bool opt_debug = false; bool opt_ipv6 = false; bool opt_quit = false; int quit_time = 0; QString mode, type, name, ipaddr; QStringList nslist; QList pubitems; // options for(int n = 0; n < args.count(); ++n) { if(args[n].left(1) == "-") { if(args[n] == "-d") opt_debug = true; else if(args[n] == "-6") opt_ipv6 = true; else if(args[n] == "-q") { if(n + 1 >= args.count()) { printf("need to specify number of seconds\n"); usage(); return 1; } int x = args[n + 1].toInt(); if(x < 1) x = 30; opt_quit = true; quit_time = x; args.removeAt(n + 1); } else { printf("bad option\n"); usage(); return 1; } args.removeAt(n); --n; // adjust position } } mode = args[0]; if(mode == "uni" || mode == "mul") { if(args.count() < 3) { printf("not enough args\n"); usage(); return 1; } type = args[1]; name = args[2]; if(mode == "uni") { for(int n = 3; n < args.count(); ++n) nslist += QString(args[n]); } } else if(mode == "pub") { if(args.count() < 2) { printf("not enough args\n"); usage(); return 1; } for(int n = 1; n < args.count(); ++n) { QString arg = args[n]; int at = arg.indexOf(':'); if(at == -1) { printf("missing colon\n"); usage(); return 1; } QString type = arg.mid(0, at).toLower(); QString val = arg.mid(at + 1); if(type == "a") { QStringList list = val.split(','); if(list.count() != 2) { printf("bad format for A type\n"); usage(); return 1; } QHostAddress host(list[1]); if(host.isNull() || host.protocol() != QAbstractSocket::IPv4Protocol) { printf("bad format for A type IP address\n"); usage(); return 1; } QJDns::Record rec; rec.owner = list[0].toLatin1(); rec.type = QJDns::A; rec.ttl = 120; rec.haveKnown = true; rec.address = host; pubitems += rec; } else if(type == "aaaa") { QStringList list = val.split(','); if(list.count() != 2) { printf("bad format for AAAA type\n"); usage(); return 1; } QHostAddress host(list[1]); if(host.isNull() || host.protocol() != QAbstractSocket::IPv6Protocol) { printf("bad format for AAAA type IP address\n"); usage(); return 1; } QJDns::Record rec; rec.owner = list[0].toLatin1(); rec.type = QJDns::Aaaa; rec.ttl = 120; rec.haveKnown = true; rec.address = host; pubitems += rec; } else if(type == "srv") { QStringList list = val.split(','); if(list.count() != 3) { printf("bad format for SRV type\n"); usage(); return 1; } QJDns::Record rec; rec.owner = list[0].toLatin1(); rec.type = QJDns::Srv; rec.ttl = 120; rec.haveKnown = true; rec.name = list[1].toLatin1(); rec.priority = 0; rec.weight = 0; rec.port = list[2].toInt(); pubitems += rec; } else if(type == "ptr") { QStringList list = val.split(','); if(list.count() != 2) { printf("bad format for PTR type\n"); usage(); return 1; } QJDns::Record rec; rec.owner = list[0].toLatin1(); rec.type = QJDns::Ptr; rec.ttl = 120; rec.haveKnown = true; rec.name = list[1].toLatin1(); pubitems += rec; } else if(type == "txt") { QStringList list = val.split(','); QList texts; for(int n = 1; n < list.count(); ++n) texts += list[n].toLatin1(); QJDns::Record rec; rec.owner = list[0].toLatin1(); rec.type = QJDns::Txt; rec.ttl = 120; rec.haveKnown = true; rec.texts = texts; pubitems += rec; } else { printf("bad record type [%s]\n", qPrintable(type)); usage(); return 1; } } } else if(mode == "sys") { QJDns::SystemInfo info = QJDns::systemInfo(); printf("DNS System Information\n"); printf(" Name Servers:\n"); if(!info.nameServers.isEmpty()) { for(int n = 0; n < info.nameServers.count(); ++n) printf(" %s\n", qPrintable(info.nameServers[n].address.toString())); } else printf(" (None)\n"); printf(" Domains:\n"); if(!info.domains.isEmpty()) { for(int n = 0; n < info.domains.count(); ++n) printf(" [%s]\n", info.domains[n].data()); } else printf(" (None)\n"); printf(" Hosts:\n"); if(!info.hosts.isEmpty()) { for(int n = 0; n < info.hosts.count(); ++n) { const QJDns::DnsHost &h = info.hosts[n]; printf(" [%s] -> %s\n", h.name.data(), qPrintable(h.address.toString())); } } else printf(" (None)\n"); QHostAddress addr; printf("Primary IPv4 Multicast Address: "); addr = QJDns::detectPrimaryMulticast(QHostAddress::Any); if(!addr.isNull()) printf("%s\n", qPrintable(addr.toString())); else printf("(None)\n"); printf("Primary IPv6 Multicast Address: "); addr = QJDns::detectPrimaryMulticast(QHostAddress::AnyIPv6); if(!addr.isNull()) printf("%s\n", qPrintable(addr.toString())); else printf("(None)\n"); return 0; } else { usage(); return 1; } App a; a.opt_debug = opt_debug; a.opt_ipv6 = opt_ipv6; a.opt_quit = opt_quit; a.quit_time = quit_time; a.mode = mode; a.type = type.toLower(); a.name = name; a.ipaddr = ipaddr; a.nslist = nslist; a.pubitems = pubitems; QObject::connect(&a, SIGNAL(quit()), &app, SLOT(quit())); QTimer::singleShot(0, &a, SLOT(start())); app.exec(); return 0; } psi-plus-snapshots-1.4.1456/iris/src/jdns/tools/jdns/main.h000066400000000000000000000035261370065651000234470ustar00rootroot00000000000000/* * Copyright (C) 2005 Justin Karneges * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef MAIN_H #define MAIN_H #include "qjdns.h" #include #include class App : public QObject { Q_OBJECT public: bool opt_debug = false; bool opt_ipv6 = false; bool opt_quit = false; int quit_time = 500; QString mode, type, name, ipaddr; QStringList nslist; QList pubitems; QJDns jdns; int req_id = 0; App(); ~App(); public slots: void start(); signals: void quit(); private slots: void jdns_resultsReady(int id, const QJDns::Response &results); void jdns_published(int id); void jdns_error(int id, QJDns::Error e); void jdns_shutdownFinished(); void jdns_debugLinesReady(); void doShutdown(); }; #endif // MAIN_H psi-plus-snapshots-1.4.1456/iris/src/libbase.pri000066400000000000000000000002571370065651000214310ustar00rootroot00000000000000IRIS_BASE = $$PWD/.. isEmpty(top_iris_builddir):top_iris_builddir = . include($$top_iris_builddir/../conf.pri) include(../common.pri) QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.9 psi-plus-snapshots-1.4.1456/iris/src/src.pro000066400000000000000000000002661370065651000206250ustar00rootroot00000000000000TEMPLATE = subdirs include(libbase.pri) sub_irisnet.subdir = irisnet sub_xmpp.subdir = xmpp sub_xmpp.depends = sub_irisnet SUBDIRS += sub_irisnet !iris_bundle:SUBDIRS += sub_xmpp psi-plus-snapshots-1.4.1456/iris/src/xmpp/000077500000000000000000000000001370065651000202745ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/xmpp/CMakeLists.txt000066400000000000000000000122721370065651000230400ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.1.0) if(POLICY CMP0074) cmake_policy(SET CMP0074 NEW) message(STATUS "CMP0074 policy set to NEW") endif() add_definitions(-DXMPP_TEST) find_package(ZLIB REQUIRED) get_filename_component(ABS_PARENT_DIR "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE) get_filename_component(ABS_ROOT_DIR "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE) include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_LIST_DIR} ${ABS_PARENT_DIR} ${ABS_PARENT_DIR}/irisnet/corelib xmpp-core xmpp-im ${IDN_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS} ${QCA_INCLUDES} ) set(HEADERS xmpp-core/parser.h xmpp-core/protocol.h xmpp-core/sm.h xmpp-core/td.h xmpp-core/xmlprotocol.h xmpp-core/xmpp_stanza.h xmpp-im/xmpp_address.h xmpp-im/xmpp_hash.h xmpp-im/xmpp_thumbs.h xmpp-im/xmpp_agentitem.h xmpp-im/xmpp_captcha.h xmpp-im/xmpp_chatstate.h xmpp-im/xmpp_discoitem.h xmpp-im/xmpp_features.h xmpp-im/xmpp_form.h xmpp-im/xmpp_htmlelement.h xmpp-im/xmpp_httpauthrequest.h xmpp-im/xmpp_liveroster.h xmpp-im/xmpp_liverosteritem.h xmpp-im/xmpp_message.h xmpp-im/xmpp_muc.h xmpp-im/xmpp_reference.h xmpp-im/xmpp_pubsubitem.h xmpp-im/xmpp_pubsubretraction.h xmpp-im/xmpp_receipts.h xmpp-im/xmpp_resource.h xmpp-im/xmpp_resourcelist.h xmpp-im/xmpp_roster.h xmpp-im/xmpp_rosteritem.h xmpp-im/xmpp_rosterx.h xmpp-im/xmpp_status.h xmpp-im/xmpp_subsets.h xmpp-im/xmpp_url.h xmpp-im/xmpp_vcard.h xmpp-im/xmpp_xdata.h xmpp-im/xmpp_xmlcommon.h base/randomnumbergenerator.h base/randrandomnumbergenerator.h base/timezone.h xmpp-im/im.h jid/jid.h sasl/digestmd5proplist.h sasl/digestmd5response.h sasl/plainmessage.h sasl/scramsha1message.h sasl/scramsha1response.h sasl/scramsha1signature.h blake2/blake2qt.h xmpp-core/compressionhandler.h xmpp-core/securestream.h xmpp-core/xmpp.h xmpp-core/xmpp_clientstream.h xmpp-core/xmpp_stream.h xmpp-im/xmpp_caps.h xmpp-im/filetransfer.h xmpp-im/httpfileupload.h xmpp-im/s5b.h xmpp-im/xmpp_bitsofbinary.h xmpp-im/xmpp_bytestream.h xmpp-im/xmpp_client.h xmpp-im/xmpp_discoinfotask.h xmpp-im/xmpp_ibb.h xmpp-im/xmpp_serverinfomanager.h xmpp-im/xmpp_task.h xmpp-im/xmpp_tasks.h xmpp-im/jingle.h xmpp-im/jingle-transport.h xmpp-im/jingle-nstransportslist.h xmpp-im/jingle-application.h xmpp-im/jingle-session.h xmpp-im/jingle-ft.h xmpp-im/jingle-ice.h xmpp-im/jingle-s5b.h xmpp-im/jingle-ibb.h zlib/zlibcompressor.h zlib/zlibdecompressor.h ) set(SOURCES xmpp-core/compressionhandler.cpp xmpp-core/connector.cpp xmpp-core/parser.cpp xmpp-core/protocol.cpp xmpp-core/sm.cpp xmpp-core/stream.cpp xmpp-core/tlshandler.cpp xmpp-core/xmlprotocol.cpp xmpp-core/xmpp_stanza.cpp xmpp-im/client.cpp xmpp-im/filetransfer.cpp xmpp-im/httpfileupload.cpp xmpp-im/types.cpp xmpp-im/xmpp_bitsofbinary.cpp xmpp-im/xmpp_bytestream.cpp xmpp-im/xmpp_caps.cpp xmpp-im/xmpp_discoinfotask.cpp xmpp-im/xmpp_discoitem.cpp xmpp-im/xmpp_ibb.cpp xmpp-im/xmpp_reference.cpp xmpp-im/xmpp_serverinfomanager.cpp xmpp-im/xmpp_subsets.cpp xmpp-im/xmpp_task.cpp xmpp-im/xmpp_tasks.cpp xmpp-im/xmpp_vcard.cpp xmpp-im/xmpp_xdata.cpp xmpp-im/xmpp_xmlcommon.cpp xmpp-im/jingle.cpp xmpp-im/jingle-session.cpp xmpp-im/jingle-application.cpp xmpp-im/jingle-transport.cpp xmpp-im/jingle-nstransportslist.cpp xmpp-im/jingle-ft.cpp xmpp-im/jingle-ice.cpp xmpp-im/jingle-s5b.cpp xmpp-im/jingle-ibb.cpp base/randomnumbergenerator.cpp base/timezone.cpp zlib/zlibcompressor.cpp zlib/zlibdecompressor.cpp blake2/blake2qt.cpp jid/jid.cpp sasl/digestmd5proplist.cpp sasl/digestmd5response.cpp sasl/plainmessage.cpp sasl/scramsha1message.cpp sasl/scramsha1response.cpp sasl/scramsha1signature.cpp xmpp-core/securestream.cpp xmpp-core/simplesasl.cpp xmpp-im/s5b.cpp xmpp-im/xmpp_features.cpp ) if(NOT MSVC) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11") endif() add_library(iris STATIC ${HEADERS} ${SOURCES} ) set_property(TARGET iris PROPERTY C_STANDARD 11) if(B2_FOUND) message(STATUS "Building with system blake2 library") target_compile_definitions(iris PUBLIC IRIS_SYSTEM_BLAKE2) target_link_libraries(iris ${B2_LIBRARY}) else() message(STATUS "Building with slow blake2 reference implementation") target_sources(iris PRIVATE blake2/blake2b-ref.c blake2/blake2s-ref.c blake2/blake2.h blake2/blake2-impl.h) target_include_directories(iris PRIVATE blake2) endif() if( SEPARATE_QJDNS AND WIN32) set(EXTRA_LDFLAGS ws2_32) endif() target_link_libraries(iris ${IDN_LIBRARY} ${ZLIB_LIBRARY} ${EXTRA_LDFLAGS}) target_link_libraries(iris irisnet Qt5::Core Qt5::Gui Qt5::Network Qt5::Xml ${qca_LIB}) target_compile_definitions(iris INTERFACE IRISNET_STATIC) target_include_directories(iris INTERFACE ${ABS_ROOT_DIR}/include ${ABS_ROOT_DIR}/include/iris ${ABS_PARENT_DIR}) psi-plus-snapshots-1.4.1456/iris/src/xmpp/base/000077500000000000000000000000001370065651000212065ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/xmpp/base/base.pri000066400000000000000000000003571370065651000226410ustar00rootroot00000000000000INCLUDEPATH += $$PWD/../.. DEPENDPATH += $$PWD/../.. HEADERS += \ $$PWD/randomnumbergenerator.h \ $$PWD/randrandomnumbergenerator.h \ $$PWD/timezone.h SOURCES += \ $$PWD/randomnumbergenerator.cpp \ $$PWD/timezone.cpp psi-plus-snapshots-1.4.1456/iris/src/xmpp/base/randomnumbergenerator.cpp000066400000000000000000000020351370065651000263120ustar00rootroot00000000000000/* * Copyright (C) 2008 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp/base/randomnumbergenerator.h" #include namespace XMPP { RandomNumberGenerator::~RandomNumberGenerator() { } double RandomNumberGenerator::generateNumberBetween(double a, double b) const { assert(b > a); return a + (generateNumber() / getMaximumGeneratedNumber()) * (b - a); } } // namespace XMPP psi-plus-snapshots-1.4.1456/iris/src/xmpp/base/randomnumbergenerator.h000066400000000000000000000021461370065651000257620ustar00rootroot00000000000000/* * Copyright (C) 2008 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef RANDOMNUMBERGENERATOR_H #define RANDOMNUMBERGENERATOR_H namespace XMPP { class RandomNumberGenerator { public: virtual ~RandomNumberGenerator(); double generateNumberBetween(double a, double b) const; protected: virtual double generateNumber() const = 0; virtual double getMaximumGeneratedNumber() const = 0; }; } // namespace XMPP #endif // RANDOMNUMBERGENERATOR_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/base/randrandomnumbergenerator.h000066400000000000000000000022411370065651000266230ustar00rootroot00000000000000/* * Copyright (C) 2008 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef RANDRANDOMNUMBERGENERATOR_H #define RANDRANDOMNUMBERGENERATOR_H #include "xmpp/base/randomnumbergenerator.h" #include namespace XMPP { class RandRandomNumberGenerator : public RandomNumberGenerator { public: RandRandomNumberGenerator() { } virtual double generateNumber() const { return rand(); } virtual double getMaximumGeneratedNumber() const { return RAND_MAX; } }; } // namespace XMPP #endif // RANDRANDOMNUMBERGENERATOR_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/base/timezone.cpp000066400000000000000000000066531370065651000235560ustar00rootroot00000000000000/* * Copyright (C) Psi Development Team * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "timezone.h" #if QT_VERSION < QT_VERSION_CHECK(5, 2, 0) #include #include #ifdef Q_OS_UNIX #include #endif #ifdef Q_OS_WIN #include #endif #else #include #endif #include #if QT_VERSION < QT_VERSION_CHECK(5, 2, 0) static bool inited = false; static int timezone_offset_; static QString timezone_str_; static void init() { #if defined(Q_OS_UNIX) time_t x; time(&x); char str[256]; char fmt[32]; int size; strcpy(fmt, "%z"); size = strftime(str, 256, fmt, localtime(&x)); if (size && strncmp(fmt, str, size)) { timezone_offset_ = QByteArray::fromRawData(str + 1, 2).toInt() * 60 + QByteArray::fromRawData(str + 3, 2).toInt(); if (str[0] == '-') timezone_offset_ = -timezone_offset_; } strcpy(fmt, "%Z"); strftime(str, 256, fmt, localtime(&x)); if (strcmp(fmt, str)) timezone_str_ = str; #elif defined(Q_OS_WIN) TIME_ZONE_INFORMATION i; memset(&i, 0, sizeof(i)); bool inDST = (GetTimeZoneInformation(&i) == TIME_ZONE_ID_DAYLIGHT); int bias = i.Bias; if (inDST) bias += i.DaylightBias; timezone_offset_ = -bias; timezone_str_ = ""; for (int n = 0; n < 32; ++n) { int w = inDST ? i.DaylightName[n] : i.StandardName[n]; if (w == 0) break; timezone_str_ += QChar(w); } #else qWarning("Failed to properly init timezone data. Use UTC offset instead"); inited = true; timezone_offset_ = 0; timezone_str_ = QLatin1String("N/A"); #endif } #endif int TimeZone::offsetFromUtc() { #if QT_VERSION < QT_VERSION_CHECK(5, 2, 0) if (!inited) { init(); } return timezone_offset_; #else return QTimeZone::systemTimeZone().offsetFromUtc(QDateTime::currentDateTime()) / 60; #endif } QString TimeZone::abbreviation() { #if QT_VERSION < QT_VERSION_CHECK(5, 2, 0) return timezone_str_; #else return QTimeZone::systemTimeZone().abbreviation(QDateTime::currentDateTime()); #endif } int TimeZone::tzdToInt(const QString &tzd) { int tzoSign = 1; if (tzd.startsWith('Z')) { return 0; } else if (tzd.startsWith('+') || tzd.startsWith('-')) { QTime time = QTime::fromString(tzd.mid(1), "hh:mm"); if (time.isValid()) { if (tzd[0] == '-') { tzoSign = -1; } return tzoSign * (time.hour() * 60 + time.second()); } } return -1; /* we don't have -1 sec offset. and usually the value is common for errors */ } /** * \fn int TimeZone::timezoneOffset() * \brief Local timezone offset in minutes. */ /** * \fn QString TimeZone::timezoneString() * \brief Local timezone name. */ psi-plus-snapshots-1.4.1456/iris/src/xmpp/base/timezone.h000066400000000000000000000017241370065651000232150ustar00rootroot00000000000000/* * Copyright (C) Psi Development Team * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef IRIS_TIMEZONE_H #define IRIS_TIMEZONE_H #include class TimeZone { public: static int offsetFromUtc(); // in minutes static QString abbreviation(); static int tzdToInt(const QString &tzd); }; #endif // IRIS_TIMEZONE_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/base/unittest/000077500000000000000000000000001370065651000230655ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/xmpp/base/unittest/incrementingrandomnumbergenerator.h000066400000000000000000000027351370065651000322500ustar00rootroot00000000000000/* * Copyright (C) 2008 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef INCREMENTINGRANDOMNUMBERGENERATOR_H #define INCREMENTINGRANDOMNUMBERGENERATOR_H #include "xmpp/base/randomnumbergenerator.h" #include namespace XMPP { class IncrementingRandomNumberGenerator : public RandomNumberGenerator { public: IncrementingRandomNumberGenerator(int maximumNumber = 10) : maximumNumber_(maximumNumber), currentNumber_(maximumNumber_) { } virtual double generateNumber() const { currentNumber_ = (currentNumber_ + 1) % (maximumNumber_ + 1); return currentNumber_; } virtual double getMaximumGeneratedNumber() const { return maximumNumber_; } private: int maximumNumber_; mutable int currentNumber_; }; } // namespace XMPP #endif // INCREMENTINGRANDOMNUMBERGENERATOR_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/base/unittest/randomnumbergeneratortest.cpp000066400000000000000000000036601370065651000310760ustar00rootroot00000000000000/* * Copyright (C) 2008 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp/base/randomnumbergenerator.h" #include "qttestutil/qttestutil.h" #include #include using namespace XMPP; class RandomNumberGeneratorTest : public QObject { Q_OBJECT private: class DummyRandomNumberGenerator : public RandomNumberGenerator { public: DummyRandomNumberGenerator(double value, double maximum) : value_(value), maximum_(maximum) { } double generateNumber() const { return value_; } double getMaximumGeneratedNumber() const { return maximum_; } private: double value_; double maximum_; }; private slots: void testGenerateNumberBetween() { DummyRandomNumberGenerator testling(5, 10); QCOMPARE(75.0, testling.generateNumberBetween(50.0, 100.0)); } void testGenerateNumberBetween_Minimum() { DummyRandomNumberGenerator testling(0, 10); QCOMPARE(0.0, testling.generateNumberBetween(0.0, 100.0)); } void testGenerateNumberBetween_Maximum() { DummyRandomNumberGenerator testling(10, 10); QCOMPARE(100.0, testling.generateNumberBetween(0.0, 100.0)); } }; QTTESTUTIL_REGISTER_TEST(RandomNumberGeneratorTest); #include "randomnumbergeneratortest.moc" psi-plus-snapshots-1.4.1456/iris/src/xmpp/base/unittest/randrandomnumbergeneratortest.cpp000066400000000000000000000024271370065651000317430ustar00rootroot00000000000000/* * Copyright (C) 2008 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp/base/randrandomnumbergenerator.h" #include "qttestutil/qttestutil.h" #include #include using namespace XMPP; class RandRandomNumberGeneratorTest : public QObject { Q_OBJECT private slots: void testGenerateNumber() { RandRandomNumberGenerator testling; double a = testling.generateNumberBetween(0.0, 100.0); double b = testling.generateNumberBetween(0.0, 100.0); QVERIFY(a != b); } }; QTTESTUTIL_REGISTER_TEST(RandRandomNumberGeneratorTest); #include "randrandomnumbergeneratortest.moc" psi-plus-snapshots-1.4.1456/iris/src/xmpp/base/unittest/unittest.pri000066400000000000000000000001431370065651000254560ustar00rootroot00000000000000SOURCES += \ $$PWD/randrandomnumbergeneratortest.cpp \ $$PWD/randomnumbergeneratortest.cpp psi-plus-snapshots-1.4.1456/iris/src/xmpp/base/unittest/unittest.pro000066400000000000000000000001721370065651000254660ustar00rootroot00000000000000include(../../modules.pri) include($$IRIS_XMPP_QA_UNITTEST_MODULE) include($$IRIS_XMPP_BASE_MODULE) include(unittest.pri) psi-plus-snapshots-1.4.1456/iris/src/xmpp/blake2/000077500000000000000000000000001370065651000214345ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/xmpp/blake2/README.md000066400000000000000000000013351370065651000227150ustar00rootroot00000000000000This directories includes copies of source files taken from https://github.com/BLAKE2/BLAKE2 at rev 320c325 The copy was required because we neither current Qt no QCA support BLAKE2 algorithm at the moment of this writing. It's supported by OpenSSL though which is now under Apache2 but we don't link it directly. So it's proposed to keep the copies here until either Qt or QCA get support for the algo. Note it has to be done eventually if we need optimized versions. Copied files: blake2b-ref.c blake2s-ref.c blake2.h blake2-impl.h The copied files is matter of CC0 1.0 Universal license https://raw.githubusercontent.com/BLAKE2/BLAKE2/master/COPYING Any other files in this directory just wrap the copies to have Qt interface. psi-plus-snapshots-1.4.1456/iris/src/xmpp/blake2/blake2-impl.h000066400000000000000000000100221370065651000236770ustar00rootroot00000000000000/* BLAKE2 reference source code package - reference C implementations Copyright 2012, Samuel Neves . You may use this under the terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at your option. The terms of these licenses can be found at: - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 - OpenSSL license : https://www.openssl.org/source/license.html - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 More information about the BLAKE2 hash function can be found at https://blake2.net. */ #ifndef BLAKE2_IMPL_H #define BLAKE2_IMPL_H #include #include #if !defined(__cplusplus) && (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L) #if defined(_MSC_VER) #define BLAKE2_INLINE __inline #elif defined(__GNUC__) #define BLAKE2_INLINE __inline__ #else #define BLAKE2_INLINE #endif #else #define BLAKE2_INLINE inline #endif static BLAKE2_INLINE uint32_t load32(const void *src) { #if defined(NATIVE_LITTLE_ENDIAN) uint32_t w; memcpy(&w, src, sizeof w); return w; #else const uint8_t *p = (const uint8_t *)src; return ((uint32_t)(p[0]) << 0) | ((uint32_t)(p[1]) << 8) | ((uint32_t)(p[2]) << 16) | ((uint32_t)(p[3]) << 24); #endif } static BLAKE2_INLINE uint64_t load64(const void *src) { #if defined(NATIVE_LITTLE_ENDIAN) uint64_t w; memcpy(&w, src, sizeof w); return w; #else const uint8_t *p = (const uint8_t *)src; return ((uint64_t)(p[0]) << 0) | ((uint64_t)(p[1]) << 8) | ((uint64_t)(p[2]) << 16) | ((uint64_t)(p[3]) << 24) | ((uint64_t)(p[4]) << 32) | ((uint64_t)(p[5]) << 40) | ((uint64_t)(p[6]) << 48) | ((uint64_t)(p[7]) << 56); #endif } static BLAKE2_INLINE uint16_t load16(const void *src) { #if defined(NATIVE_LITTLE_ENDIAN) uint16_t w; memcpy(&w, src, sizeof w); return w; #else const uint8_t *p = (const uint8_t *)src; return (uint16_t)(((uint32_t)(p[0]) << 0) | ((uint32_t)(p[1]) << 8)); #endif } static BLAKE2_INLINE void store16(void *dst, uint16_t w) { #if defined(NATIVE_LITTLE_ENDIAN) memcpy(dst, &w, sizeof w); #else uint8_t *p = (uint8_t *)dst; *p++ = (uint8_t)w; w >>= 8; *p++ = (uint8_t)w; #endif } static BLAKE2_INLINE void store32(void *dst, uint32_t w) { #if defined(NATIVE_LITTLE_ENDIAN) memcpy(dst, &w, sizeof w); #else uint8_t *p = (uint8_t *)dst; p[0] = (uint8_t)(w >> 0); p[1] = (uint8_t)(w >> 8); p[2] = (uint8_t)(w >> 16); p[3] = (uint8_t)(w >> 24); #endif } static BLAKE2_INLINE void store64(void *dst, uint64_t w) { #if defined(NATIVE_LITTLE_ENDIAN) memcpy(dst, &w, sizeof w); #else uint8_t *p = (uint8_t *)dst; p[0] = (uint8_t)(w >> 0); p[1] = (uint8_t)(w >> 8); p[2] = (uint8_t)(w >> 16); p[3] = (uint8_t)(w >> 24); p[4] = (uint8_t)(w >> 32); p[5] = (uint8_t)(w >> 40); p[6] = (uint8_t)(w >> 48); p[7] = (uint8_t)(w >> 56); #endif } static BLAKE2_INLINE uint64_t load48(const void *src) { const uint8_t *p = (const uint8_t *)src; return ((uint64_t)(p[0]) << 0) | ((uint64_t)(p[1]) << 8) | ((uint64_t)(p[2]) << 16) | ((uint64_t)(p[3]) << 24) | ((uint64_t)(p[4]) << 32) | ((uint64_t)(p[5]) << 40); } static BLAKE2_INLINE void store48(void *dst, uint64_t w) { uint8_t *p = (uint8_t *)dst; p[0] = (uint8_t)(w >> 0); p[1] = (uint8_t)(w >> 8); p[2] = (uint8_t)(w >> 16); p[3] = (uint8_t)(w >> 24); p[4] = (uint8_t)(w >> 32); p[5] = (uint8_t)(w >> 40); } static BLAKE2_INLINE uint32_t rotr32(const uint32_t w, const unsigned c) { return (w >> c) | (w << (32 - c)); } static BLAKE2_INLINE uint64_t rotr64(const uint64_t w, const unsigned c) { return (w >> c) | (w << (64 - c)); } /* prevents compiler optimizing out memset() */ static BLAKE2_INLINE void secure_zero_memory(void *v, size_t n) { static void *(*const volatile memset_v)(void *, int, size_t) = &memset; memset_v(v, 0, n); } #endif // BLAKE2_IMPL_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/blake2/blake2.h000066400000000000000000000142071370065651000227510ustar00rootroot00000000000000/* BLAKE2 reference source code package - reference C implementations Copyright 2012, Samuel Neves . You may use this under the terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at your option. The terms of these licenses can be found at: - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 - OpenSSL license : https://www.openssl.org/source/license.html - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 More information about the BLAKE2 hash function can be found at https://blake2.net. */ #ifndef BLAKE2_H #define BLAKE2_H #include #include #if defined(_MSC_VER) #define BLAKE2_PACKED(x) __pragma(pack(push, 1)) x __pragma(pack(pop)) #else #define BLAKE2_PACKED(x) x __attribute__((packed)) #endif #if defined(__cplusplus) extern "C" { #endif enum blake2s_constant { BLAKE2S_BLOCKBYTES = 64, BLAKE2S_OUTBYTES = 32, BLAKE2S_KEYBYTES = 32, BLAKE2S_SALTBYTES = 8, BLAKE2S_PERSONALBYTES = 8 }; enum blake2b_constant { BLAKE2B_BLOCKBYTES = 128, BLAKE2B_OUTBYTES = 64, BLAKE2B_KEYBYTES = 64, BLAKE2B_SALTBYTES = 16, BLAKE2B_PERSONALBYTES = 16 }; typedef struct blake2s_state__ { uint32_t h[8]; uint32_t t[2]; uint32_t f[2]; uint8_t buf[BLAKE2S_BLOCKBYTES]; size_t buflen; size_t outlen; uint8_t last_node; } blake2s_state; typedef struct blake2b_state__ { uint64_t h[8]; uint64_t t[2]; uint64_t f[2]; uint8_t buf[BLAKE2B_BLOCKBYTES]; size_t buflen; size_t outlen; uint8_t last_node; } blake2b_state; typedef struct blake2sp_state__ { blake2s_state S[8][1]; blake2s_state R[1]; uint8_t buf[8 * BLAKE2S_BLOCKBYTES]; size_t buflen; size_t outlen; } blake2sp_state; typedef struct blake2bp_state__ { blake2b_state S[4][1]; blake2b_state R[1]; uint8_t buf[4 * BLAKE2B_BLOCKBYTES]; size_t buflen; size_t outlen; } blake2bp_state; BLAKE2_PACKED(struct blake2s_param__ { uint8_t digest_length; /* 1 */ uint8_t key_length; /* 2 */ uint8_t fanout; /* 3 */ uint8_t depth; /* 4 */ uint32_t leaf_length; /* 8 */ uint32_t node_offset; /* 12 */ uint16_t xof_length; /* 14 */ uint8_t node_depth; /* 15 */ uint8_t inner_length; /* 16 */ /* uint8_t reserved[0]; */ uint8_t salt[BLAKE2S_SALTBYTES]; /* 24 */ uint8_t personal[BLAKE2S_PERSONALBYTES]; /* 32 */ }); typedef struct blake2s_param__ blake2s_param; BLAKE2_PACKED(struct blake2b_param__ { uint8_t digest_length; /* 1 */ uint8_t key_length; /* 2 */ uint8_t fanout; /* 3 */ uint8_t depth; /* 4 */ uint32_t leaf_length; /* 8 */ uint32_t node_offset; /* 12 */ uint32_t xof_length; /* 16 */ uint8_t node_depth; /* 17 */ uint8_t inner_length; /* 18 */ uint8_t reserved[14]; /* 32 */ uint8_t salt[BLAKE2B_SALTBYTES]; /* 48 */ uint8_t personal[BLAKE2B_PERSONALBYTES]; /* 64 */ }); typedef struct blake2b_param__ blake2b_param; typedef struct blake2xs_state__ { blake2s_state S[1]; blake2s_param P[1]; } blake2xs_state; typedef struct blake2xb_state__ { blake2b_state S[1]; blake2b_param P[1]; } blake2xb_state; /* Streaming API */ int blake2s_init(blake2s_state *S, size_t outlen); int blake2s_init_key(blake2s_state *S, size_t outlen, const void *key, size_t keylen); int blake2s_init_param(blake2s_state *S, const blake2s_param *P); int blake2s_update(blake2s_state *S, const void *in, size_t inlen); int blake2s_final(blake2s_state *S, void *out, size_t outlen); int blake2b_init(blake2b_state *S, size_t outlen); int blake2b_init_key(blake2b_state *S, size_t outlen, const void *key, size_t keylen); int blake2b_init_param(blake2b_state *S, const blake2b_param *P); int blake2b_update(blake2b_state *S, const void *in, size_t inlen); int blake2b_final(blake2b_state *S, void *out, size_t outlen); int blake2sp_init(blake2sp_state *S, size_t outlen); int blake2sp_init_key(blake2sp_state *S, size_t outlen, const void *key, size_t keylen); int blake2sp_update(blake2sp_state *S, const void *in, size_t inlen); int blake2sp_final(blake2sp_state *S, void *out, size_t outlen); int blake2bp_init(blake2bp_state *S, size_t outlen); int blake2bp_init_key(blake2bp_state *S, size_t outlen, const void *key, size_t keylen); int blake2bp_update(blake2bp_state *S, const void *in, size_t inlen); int blake2bp_final(blake2bp_state *S, void *out, size_t outlen); /* Variable output length API */ int blake2xs_init(blake2xs_state *S, const size_t outlen); int blake2xs_init_key(blake2xs_state *S, const size_t outlen, const void *key, size_t keylen); int blake2xs_update(blake2xs_state *S, const void *in, size_t inlen); int blake2xs_final(blake2xs_state *S, void *out, size_t outlen); int blake2xb_init(blake2xb_state *S, const size_t outlen); int blake2xb_init_key(blake2xb_state *S, const size_t outlen, const void *key, size_t keylen); int blake2xb_update(blake2xb_state *S, const void *in, size_t inlen); int blake2xb_final(blake2xb_state *S, void *out, size_t outlen); /* Simple API */ int blake2s(void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen); int blake2b(void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen); int blake2sp(void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen); int blake2bp(void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen); int blake2xs(void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen); int blake2xb(void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen); /* This is simply an alias for blake2b */ int blake2(void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen); #if defined(__cplusplus) } #endif #endif // BLAKE2_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/blake2/blake2.pri000066400000000000000000000005211370065651000233060ustar00rootroot00000000000000INCLUDEPATH *= $$PWD/../.. DEPENDPATH *= $$PWD/../.. SOURCES += $$PWD/blake2qt.cpp bundled_blake2 { SOURCES += \ $$PWD/blake2s-ref.c \ $$PWD/blake2b-ref.c HEADERS += $$PWD/blake2.h INCLUDEPATH += $PWD } else { DEFINES += IRIS_SYSTEM_BLAKE2 } HEADERS += $$PWD/blake2qt.h OTHER_FILES += $$PWD/README.md psi-plus-snapshots-1.4.1456/iris/src/xmpp/blake2/blake2b-ref.c000066400000000000000000000235051370065651000236610ustar00rootroot00000000000000/* BLAKE2 reference source code package - reference C implementations Copyright 2012, Samuel Neves . You may use this under the terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at your option. The terms of these licenses can be found at: - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 - OpenSSL license : https://www.openssl.org/source/license.html - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 More information about the BLAKE2 hash function can be found at https://blake2.net. */ #include "blake2-impl.h" #include "blake2.h" #include #include #include static const uint64_t blake2b_IV[8] = { 0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL, 0x3c6ef372fe94f82bULL, 0xa54ff53a5f1d36f1ULL, 0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL, 0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL }; static const uint8_t blake2b_sigma[12][16] = { { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } , { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } , { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 } , { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 } , { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 } , { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 } , { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 } , { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 } , { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 } , { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13 , 0 } , { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } , { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } }; static void blake2b_set_lastnode( blake2b_state *S ) { S->f[1] = (uint64_t)-1; } /* Some helper functions, not necessarily useful */ static int blake2b_is_lastblock( const blake2b_state *S ) { return S->f[0] != 0; } static void blake2b_set_lastblock( blake2b_state *S ) { if( S->last_node ) blake2b_set_lastnode( S ); S->f[0] = (uint64_t)-1; } static void blake2b_increment_counter( blake2b_state *S, const uint64_t inc ) { S->t[0] += inc; S->t[1] += ( S->t[0] < inc ); } static void blake2b_init0( blake2b_state *S ) { size_t i; memset( S, 0, sizeof( blake2b_state ) ); for( i = 0; i < 8; ++i ) S->h[i] = blake2b_IV[i]; } /* init xors IV with input parameter block */ int blake2b_init_param( blake2b_state *S, const blake2b_param *P ) { const uint8_t *p = ( const uint8_t * )( P ); size_t i; blake2b_init0( S ); /* IV XOR ParamBlock */ for( i = 0; i < 8; ++i ) S->h[i] ^= load64( p + sizeof( S->h[i] ) * i ); S->outlen = P->digest_length; return 0; } int blake2b_init( blake2b_state *S, size_t outlen ) { blake2b_param P[1]; if ( ( !outlen ) || ( outlen > BLAKE2B_OUTBYTES ) ) return -1; P->digest_length = (uint8_t)outlen; P->key_length = 0; P->fanout = 1; P->depth = 1; store32( &P->leaf_length, 0 ); store32( &P->node_offset, 0 ); store32( &P->xof_length, 0 ); P->node_depth = 0; P->inner_length = 0; memset( P->reserved, 0, sizeof( P->reserved ) ); memset( P->salt, 0, sizeof( P->salt ) ); memset( P->personal, 0, sizeof( P->personal ) ); return blake2b_init_param( S, P ); } int blake2b_init_key( blake2b_state *S, size_t outlen, const void *key, size_t keylen ) { blake2b_param P[1]; if ( ( !outlen ) || ( outlen > BLAKE2B_OUTBYTES ) ) return -1; if ( !key || !keylen || keylen > BLAKE2B_KEYBYTES ) return -1; P->digest_length = (uint8_t)outlen; P->key_length = (uint8_t)keylen; P->fanout = 1; P->depth = 1; store32( &P->leaf_length, 0 ); store32( &P->node_offset, 0 ); store32( &P->xof_length, 0 ); P->node_depth = 0; P->inner_length = 0; memset( P->reserved, 0, sizeof( P->reserved ) ); memset( P->salt, 0, sizeof( P->salt ) ); memset( P->personal, 0, sizeof( P->personal ) ); if( blake2b_init_param( S, P ) < 0 ) return -1; { uint8_t block[BLAKE2B_BLOCKBYTES]; memset( block, 0, BLAKE2B_BLOCKBYTES ); memcpy( block, key, keylen ); blake2b_update( S, block, BLAKE2B_BLOCKBYTES ); secure_zero_memory( block, BLAKE2B_BLOCKBYTES ); /* Burn the key from stack */ } return 0; } #define G(r,i,a,b,c,d) \ do { \ a = a + b + m[blake2b_sigma[r][2*i+0]]; \ d = rotr64(d ^ a, 32); \ c = c + d; \ b = rotr64(b ^ c, 24); \ a = a + b + m[blake2b_sigma[r][2*i+1]]; \ d = rotr64(d ^ a, 16); \ c = c + d; \ b = rotr64(b ^ c, 63); \ } while(0) #define ROUND(r) \ do { \ G(r,0,v[ 0],v[ 4],v[ 8],v[12]); \ G(r,1,v[ 1],v[ 5],v[ 9],v[13]); \ G(r,2,v[ 2],v[ 6],v[10],v[14]); \ G(r,3,v[ 3],v[ 7],v[11],v[15]); \ G(r,4,v[ 0],v[ 5],v[10],v[15]); \ G(r,5,v[ 1],v[ 6],v[11],v[12]); \ G(r,6,v[ 2],v[ 7],v[ 8],v[13]); \ G(r,7,v[ 3],v[ 4],v[ 9],v[14]); \ } while(0) static void blake2b_compress( blake2b_state *S, const uint8_t block[BLAKE2B_BLOCKBYTES] ) { uint64_t m[16]; uint64_t v[16]; size_t i; for( i = 0; i < 16; ++i ) { m[i] = load64( block + i * sizeof( m[i] ) ); } for( i = 0; i < 8; ++i ) { v[i] = S->h[i]; } v[ 8] = blake2b_IV[0]; v[ 9] = blake2b_IV[1]; v[10] = blake2b_IV[2]; v[11] = blake2b_IV[3]; v[12] = blake2b_IV[4] ^ S->t[0]; v[13] = blake2b_IV[5] ^ S->t[1]; v[14] = blake2b_IV[6] ^ S->f[0]; v[15] = blake2b_IV[7] ^ S->f[1]; ROUND( 0 ); ROUND( 1 ); ROUND( 2 ); ROUND( 3 ); ROUND( 4 ); ROUND( 5 ); ROUND( 6 ); ROUND( 7 ); ROUND( 8 ); ROUND( 9 ); ROUND( 10 ); ROUND( 11 ); for( i = 0; i < 8; ++i ) { S->h[i] = S->h[i] ^ v[i] ^ v[i + 8]; } } #undef G #undef ROUND int blake2b_update( blake2b_state *S, const void *pin, size_t inlen ) { const unsigned char * in = (const unsigned char *)pin; if( inlen > 0 ) { size_t left = S->buflen; size_t fill = BLAKE2B_BLOCKBYTES - left; if( inlen > fill ) { S->buflen = 0; memcpy( S->buf + left, in, fill ); /* Fill buffer */ blake2b_increment_counter( S, BLAKE2B_BLOCKBYTES ); blake2b_compress( S, S->buf ); /* Compress */ in += fill; inlen -= fill; while(inlen > BLAKE2B_BLOCKBYTES) { blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES); blake2b_compress( S, in ); in += BLAKE2B_BLOCKBYTES; inlen -= BLAKE2B_BLOCKBYTES; } } memcpy( S->buf + S->buflen, in, inlen ); S->buflen += inlen; } return 0; } int blake2b_final( blake2b_state *S, void *out, size_t outlen ) { uint8_t buffer[BLAKE2B_OUTBYTES] = {0}; size_t i; if( out == NULL || outlen < S->outlen ) return -1; if( blake2b_is_lastblock( S ) ) return -1; blake2b_increment_counter( S, S->buflen ); blake2b_set_lastblock( S ); memset( S->buf + S->buflen, 0, BLAKE2B_BLOCKBYTES - S->buflen ); /* Padding */ blake2b_compress( S, S->buf ); for( i = 0; i < 8; ++i ) /* Output full hash to temp buffer */ store64( buffer + sizeof( S->h[i] ) * i, S->h[i] ); memcpy( out, buffer, S->outlen ); secure_zero_memory(buffer, sizeof(buffer)); return 0; } /* inlen, at least, should be uint64_t. Others can be size_t. */ int blake2b( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ) { blake2b_state S[1]; /* Verify parameters */ if ( NULL == in && inlen > 0 ) return -1; if ( NULL == out ) return -1; if( NULL == key && keylen > 0 ) return -1; if( !outlen || outlen > BLAKE2B_OUTBYTES ) return -1; if( keylen > BLAKE2B_KEYBYTES ) return -1; if( keylen > 0 ) { if( blake2b_init_key( S, outlen, key, keylen ) < 0 ) return -1; } else { if( blake2b_init( S, outlen ) < 0 ) return -1; } blake2b_update( S, ( const uint8_t * )in, inlen ); blake2b_final( S, out, outlen ); return 0; } int blake2( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ) { return blake2b(out, outlen, in, inlen, key, keylen); } #if defined(SUPERCOP) int crypto_hash( unsigned char *out, unsigned char *in, unsigned long long inlen ) { return blake2b( out, BLAKE2B_OUTBYTES, in, inlen, NULL, 0 ); } #endif #if defined(BLAKE2B_SELFTEST) #include "blake2-kat.h" #include int main( void ) { uint8_t key[BLAKE2B_KEYBYTES]; uint8_t buf[BLAKE2_KAT_LENGTH]; size_t i, step; for( i = 0; i < BLAKE2B_KEYBYTES; ++i ) key[i] = ( uint8_t )i; for( i = 0; i < BLAKE2_KAT_LENGTH; ++i ) buf[i] = ( uint8_t )i; /* Test simple API */ for( i = 0; i < BLAKE2_KAT_LENGTH; ++i ) { uint8_t hash[BLAKE2B_OUTBYTES]; blake2b( hash, BLAKE2B_OUTBYTES, buf, i, key, BLAKE2B_KEYBYTES ); if( 0 != memcmp( hash, blake2b_keyed_kat[i], BLAKE2B_OUTBYTES ) ) { goto fail; } } /* Test streaming API */ for(step = 1; step < BLAKE2B_BLOCKBYTES; ++step) { for (i = 0; i < BLAKE2_KAT_LENGTH; ++i) { uint8_t hash[BLAKE2B_OUTBYTES]; blake2b_state S; uint8_t * p = buf; size_t mlen = i; int err = 0; if( (err = blake2b_init_key(&S, BLAKE2B_OUTBYTES, key, BLAKE2B_KEYBYTES)) < 0 ) { goto fail; } while (mlen >= step) { if ( (err = blake2b_update(&S, p, step)) < 0 ) { goto fail; } mlen -= step; p += step; } if ( (err = blake2b_update(&S, p, mlen)) < 0) { goto fail; } if ( (err = blake2b_final(&S, hash, BLAKE2B_OUTBYTES)) < 0) { goto fail; } if (0 != memcmp(hash, blake2b_keyed_kat[i], BLAKE2B_OUTBYTES)) { goto fail; } } } puts( "ok" ); return 0; fail: puts("error"); return -1; } #endif psi-plus-snapshots-1.4.1456/iris/src/xmpp/blake2/blake2qt.cpp000066400000000000000000000034261370065651000236520ustar00rootroot00000000000000#include "blake2qt.h" #include "blake2.h" #include namespace XMPP { /* Padded structs result in a compile-time error */ static_assert(sizeof(blake2s_param) == BLAKE2S_OUTBYTES, "sizeof(blake2s_param) != BLAKE2S_OUTBYTES"); static_assert(sizeof(blake2b_param) == BLAKE2B_OUTBYTES, "sizeof(blake2b_param) != BLAKE2B_OUTBYTES"); QByteArray computeBlake2Hash(const QByteArray &ba, Blake2DigestSize digestSize) { // otherwise try to libb2 or bundled reference implementation depending on which is available size_t digestSizeBytes = digestSize == Blake2Digest256 ? 32 : 64; QByteArray ret(digestSizeBytes, Qt::Uninitialized); if (blake2b(ret.data(), digestSizeBytes, ba.data(), ba.size(), nullptr, 0) != 0) { ret.clear(); } return ret; } QByteArray computeBlake2Hash(QIODevice *dev, Blake2DigestSize digestSize) { if (!dev->isOpen()) { dev->open(QIODevice::ReadOnly); } if (!dev->isOpen()) { return QByteArray(); } // otherwise try to libb2 or bundled reference implementation depending on which is available size_t digestSizeBytes = digestSize == Blake2Digest256 ? 32 : 64; blake2b_state state; int retCode = blake2b_init(&state, digestSizeBytes); if (retCode != 0) { return QByteArray(); } QByteArray buf; // reading by 1Mb should work well with disk caches while ((buf = dev->read(1024 * 1024)).size() > 0) { retCode = blake2b_update(&state, buf.data(), buf.size()); if (retCode != 0) { return QByteArray(); } } QByteArray ret; ret.resize(digestSizeBytes); retCode = blake2b_final(&state, ret.data(), ret.size()); if (retCode != 0) { return QByteArray(); } return ret; } } // namespace XMPP psi-plus-snapshots-1.4.1456/iris/src/xmpp/blake2/blake2qt.h000066400000000000000000000005441370065651000233150ustar00rootroot00000000000000#ifndef BLAKE2QT_H #define BLAKE2QT_H #include class QIODevice; namespace XMPP { enum Blake2DigestSize { Blake2Digest256, Blake2Digest512 }; QByteArray computeBlake2Hash(const QByteArray &ba, Blake2DigestSize digestSize); QByteArray computeBlake2Hash(QIODevice *dev, Blake2DigestSize digestSize); } // namespace XMPP #endif // BLAKE2QT_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/blake2/blake2s-ref.c000066400000000000000000000227041370065651000237020ustar00rootroot00000000000000/* BLAKE2 reference source code package - reference C implementations Copyright 2012, Samuel Neves . You may use this under the terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at your option. The terms of these licenses can be found at: - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 - OpenSSL license : https://www.openssl.org/source/license.html - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 More information about the BLAKE2 hash function can be found at https://blake2.net. */ #include "blake2-impl.h" #include "blake2.h" #include #include #include static const uint32_t blake2s_IV[8] = { 0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL, 0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL }; static const uint8_t blake2s_sigma[10][16] = { { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } , { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } , { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 } , { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 } , { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 } , { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 } , { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 } , { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 } , { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 } , { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13 , 0 } , }; static void blake2s_set_lastnode( blake2s_state *S ) { S->f[1] = (uint32_t)-1; } /* Some helper functions, not necessarily useful */ static int blake2s_is_lastblock( const blake2s_state *S ) { return S->f[0] != 0; } static void blake2s_set_lastblock( blake2s_state *S ) { if( S->last_node ) blake2s_set_lastnode( S ); S->f[0] = (uint32_t)-1; } static void blake2s_increment_counter( blake2s_state *S, const uint32_t inc ) { S->t[0] += inc; S->t[1] += ( S->t[0] < inc ); } static void blake2s_init0( blake2s_state *S ) { size_t i; memset( S, 0, sizeof( blake2s_state ) ); for( i = 0; i < 8; ++i ) S->h[i] = blake2s_IV[i]; } /* init2 xors IV with input parameter block */ int blake2s_init_param( blake2s_state *S, const blake2s_param *P ) { const unsigned char *p = ( const unsigned char * )( P ); size_t i; blake2s_init0( S ); /* IV XOR ParamBlock */ for( i = 0; i < 8; ++i ) S->h[i] ^= load32( &p[i * 4] ); S->outlen = P->digest_length; return 0; } /* Sequential blake2s initialization */ int blake2s_init( blake2s_state *S, size_t outlen ) { blake2s_param P[1]; /* Move interval verification here? */ if ( ( !outlen ) || ( outlen > BLAKE2S_OUTBYTES ) ) return -1; P->digest_length = (uint8_t)outlen; P->key_length = 0; P->fanout = 1; P->depth = 1; store32( &P->leaf_length, 0 ); store32( &P->node_offset, 0 ); store16( &P->xof_length, 0 ); P->node_depth = 0; P->inner_length = 0; /* memset(P->reserved, 0, sizeof(P->reserved) ); */ memset( P->salt, 0, sizeof( P->salt ) ); memset( P->personal, 0, sizeof( P->personal ) ); return blake2s_init_param( S, P ); } int blake2s_init_key( blake2s_state *S, size_t outlen, const void *key, size_t keylen ) { blake2s_param P[1]; if ( ( !outlen ) || ( outlen > BLAKE2S_OUTBYTES ) ) return -1; if ( !key || !keylen || keylen > BLAKE2S_KEYBYTES ) return -1; P->digest_length = (uint8_t)outlen; P->key_length = (uint8_t)keylen; P->fanout = 1; P->depth = 1; store32( &P->leaf_length, 0 ); store32( &P->node_offset, 0 ); store16( &P->xof_length, 0 ); P->node_depth = 0; P->inner_length = 0; /* memset(P->reserved, 0, sizeof(P->reserved) ); */ memset( P->salt, 0, sizeof( P->salt ) ); memset( P->personal, 0, sizeof( P->personal ) ); if( blake2s_init_param( S, P ) < 0 ) return -1; { uint8_t block[BLAKE2S_BLOCKBYTES]; memset( block, 0, BLAKE2S_BLOCKBYTES ); memcpy( block, key, keylen ); blake2s_update( S, block, BLAKE2S_BLOCKBYTES ); secure_zero_memory( block, BLAKE2S_BLOCKBYTES ); /* Burn the key from stack */ } return 0; } #define G(r,i,a,b,c,d) \ do { \ a = a + b + m[blake2s_sigma[r][2*i+0]]; \ d = rotr32(d ^ a, 16); \ c = c + d; \ b = rotr32(b ^ c, 12); \ a = a + b + m[blake2s_sigma[r][2*i+1]]; \ d = rotr32(d ^ a, 8); \ c = c + d; \ b = rotr32(b ^ c, 7); \ } while(0) #define ROUND(r) \ do { \ G(r,0,v[ 0],v[ 4],v[ 8],v[12]); \ G(r,1,v[ 1],v[ 5],v[ 9],v[13]); \ G(r,2,v[ 2],v[ 6],v[10],v[14]); \ G(r,3,v[ 3],v[ 7],v[11],v[15]); \ G(r,4,v[ 0],v[ 5],v[10],v[15]); \ G(r,5,v[ 1],v[ 6],v[11],v[12]); \ G(r,6,v[ 2],v[ 7],v[ 8],v[13]); \ G(r,7,v[ 3],v[ 4],v[ 9],v[14]); \ } while(0) static void blake2s_compress( blake2s_state *S, const uint8_t in[BLAKE2S_BLOCKBYTES] ) { uint32_t m[16]; uint32_t v[16]; size_t i; for( i = 0; i < 16; ++i ) { m[i] = load32( in + i * sizeof( m[i] ) ); } for( i = 0; i < 8; ++i ) { v[i] = S->h[i]; } v[ 8] = blake2s_IV[0]; v[ 9] = blake2s_IV[1]; v[10] = blake2s_IV[2]; v[11] = blake2s_IV[3]; v[12] = S->t[0] ^ blake2s_IV[4]; v[13] = S->t[1] ^ blake2s_IV[5]; v[14] = S->f[0] ^ blake2s_IV[6]; v[15] = S->f[1] ^ blake2s_IV[7]; ROUND( 0 ); ROUND( 1 ); ROUND( 2 ); ROUND( 3 ); ROUND( 4 ); ROUND( 5 ); ROUND( 6 ); ROUND( 7 ); ROUND( 8 ); ROUND( 9 ); for( i = 0; i < 8; ++i ) { S->h[i] = S->h[i] ^ v[i] ^ v[i + 8]; } } #undef G #undef ROUND int blake2s_update( blake2s_state *S, const void *pin, size_t inlen ) { const unsigned char * in = (const unsigned char *)pin; if( inlen > 0 ) { size_t left = S->buflen; size_t fill = BLAKE2S_BLOCKBYTES - left; if( inlen > fill ) { S->buflen = 0; memcpy( S->buf + left, in, fill ); /* Fill buffer */ blake2s_increment_counter( S, BLAKE2S_BLOCKBYTES ); blake2s_compress( S, S->buf ); /* Compress */ in += fill; inlen -= fill; while(inlen > BLAKE2S_BLOCKBYTES) { blake2s_increment_counter(S, BLAKE2S_BLOCKBYTES); blake2s_compress( S, in ); in += BLAKE2S_BLOCKBYTES; inlen -= BLAKE2S_BLOCKBYTES; } } memcpy( S->buf + S->buflen, in, inlen ); S->buflen += inlen; } return 0; } int blake2s_final( blake2s_state *S, void *out, size_t outlen ) { uint8_t buffer[BLAKE2S_OUTBYTES] = {0}; size_t i; if( out == NULL || outlen < S->outlen ) return -1; if( blake2s_is_lastblock( S ) ) return -1; blake2s_increment_counter( S, ( uint32_t )S->buflen ); blake2s_set_lastblock( S ); memset( S->buf + S->buflen, 0, BLAKE2S_BLOCKBYTES - S->buflen ); /* Padding */ blake2s_compress( S, S->buf ); for( i = 0; i < 8; ++i ) /* Output full hash to temp buffer */ store32( buffer + sizeof( S->h[i] ) * i, S->h[i] ); memcpy( out, buffer, outlen ); secure_zero_memory(buffer, sizeof(buffer)); return 0; } int blake2s( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ) { blake2s_state S[1]; /* Verify parameters */ if ( NULL == in && inlen > 0 ) return -1; if ( NULL == out ) return -1; if ( NULL == key && keylen > 0) return -1; if( !outlen || outlen > BLAKE2S_OUTBYTES ) return -1; if( keylen > BLAKE2S_KEYBYTES ) return -1; if( keylen > 0 ) { if( blake2s_init_key( S, outlen, key, keylen ) < 0 ) return -1; } else { if( blake2s_init( S, outlen ) < 0 ) return -1; } blake2s_update( S, ( const uint8_t * )in, inlen ); blake2s_final( S, out, outlen ); return 0; } #if defined(SUPERCOP) int crypto_hash( unsigned char *out, unsigned char *in, unsigned long long inlen ) { return blake2s( out, BLAKE2S_OUTBYTES, in, inlen, NULL, 0 ); } #endif #if defined(BLAKE2S_SELFTEST) #include "blake2-kat.h" #include int main( void ) { uint8_t key[BLAKE2S_KEYBYTES]; uint8_t buf[BLAKE2_KAT_LENGTH]; size_t i, step; for( i = 0; i < BLAKE2S_KEYBYTES; ++i ) key[i] = ( uint8_t )i; for( i = 0; i < BLAKE2_KAT_LENGTH; ++i ) buf[i] = ( uint8_t )i; /* Test simple API */ for( i = 0; i < BLAKE2_KAT_LENGTH; ++i ) { uint8_t hash[BLAKE2S_OUTBYTES]; blake2s( hash, BLAKE2S_OUTBYTES, buf, i, key, BLAKE2S_KEYBYTES ); if( 0 != memcmp( hash, blake2s_keyed_kat[i], BLAKE2S_OUTBYTES ) ) { goto fail; } } /* Test streaming API */ for(step = 1; step < BLAKE2S_BLOCKBYTES; ++step) { for (i = 0; i < BLAKE2_KAT_LENGTH; ++i) { uint8_t hash[BLAKE2S_OUTBYTES]; blake2s_state S; uint8_t * p = buf; size_t mlen = i; int err = 0; if( (err = blake2s_init_key(&S, BLAKE2S_OUTBYTES, key, BLAKE2S_KEYBYTES)) < 0 ) { goto fail; } while (mlen >= step) { if ( (err = blake2s_update(&S, p, step)) < 0 ) { goto fail; } mlen -= step; p += step; } if ( (err = blake2s_update(&S, p, mlen)) < 0) { goto fail; } if ( (err = blake2s_final(&S, hash, BLAKE2S_OUTBYTES)) < 0) { goto fail; } if (0 != memcmp(hash, blake2s_keyed_kat[i], BLAKE2S_OUTBYTES)) { goto fail; } } } puts( "ok" ); return 0; fail: puts("error"); return -1; } #endif psi-plus-snapshots-1.4.1456/iris/src/xmpp/common.pri000066400000000000000000000000571370065651000223020ustar00rootroot00000000000000OBJECTS_DIR = .obj MOC_DIR = .moc UI_DIR = .ui psi-plus-snapshots-1.4.1456/iris/src/xmpp/jid/000077500000000000000000000000001370065651000210425ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/xmpp/jid/jid.cpp000066400000000000000000000210361370065651000223160ustar00rootroot00000000000000/* * jid.cpp - class for verifying and manipulating XMPP addresses * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp/jid/jid.h" #ifndef NO_IRISNET #include "irisnetglobal_p.h" #endif #include #include using namespace XMPP; //---------------------------------------------------------------------------- // StringPrepCache //---------------------------------------------------------------------------- QScopedPointer StringPrepCache::_instance; bool StringPrepCache::nameprep(const QString &in, int maxbytes, QString &out) { if (in.trimmed().isEmpty()) { out = QString(); return false; // empty names or just spaces are disallowed (rfc5892+rfc6122) } StringPrepCache *that = instance(); auto it = that->nameprep_table.constFind(in); if (it != that->nameprep_table.constEnd()) { if (it.value().isNull()) { return false; } out = it.value(); return true; } QByteArray cs = in.toUtf8(); cs.resize(maxbytes); if (stringprep(cs.data(), maxbytes, (Stringprep_profile_flags)0, stringprep_nameprep) != 0) { that->nameprep_table.insert(in, QString()); return false; } QString norm = QString::fromUtf8(cs); that->nameprep_table.insert(in, norm); out = norm; return true; } bool StringPrepCache::nodeprep(const QString &in, int maxbytes, QString &out) { if (in.isEmpty()) { out = QString(); return true; } StringPrepCache *that = instance(); auto it = that->nodeprep_table.constFind(in); if (it != that->nodeprep_table.constEnd()) { if (it.value().isNull()) { return false; } out = it.value(); return true; } QByteArray cs = in.toUtf8(); cs.resize(maxbytes); if (stringprep(cs.data(), maxbytes, (Stringprep_profile_flags)0, stringprep_xmpp_nodeprep) != 0) { that->nodeprep_table.insert(in, QString()); return false; } QString norm = QString::fromUtf8(cs); that->nodeprep_table.insert(in, norm); out = norm; return true; } bool StringPrepCache::resourceprep(const QString &in, int maxbytes, QString &out) { if (in.isEmpty()) { out = QString(); return true; } StringPrepCache *that = instance(); auto it = that->resourceprep_table.constFind(in); if (it != that->resourceprep_table.constEnd()) { if (it.value().isNull()) { return false; } out = it.value(); return true; } QByteArray cs = in.toUtf8(); cs.resize(maxbytes); if (stringprep(cs.data(), maxbytes, (Stringprep_profile_flags)0, stringprep_xmpp_resourceprep) != 0) { that->resourceprep_table.insert(in, QString()); return false; } QString norm = QString::fromUtf8(cs); that->resourceprep_table.insert(in, norm); out = norm; return true; } bool StringPrepCache::saslprep(const QString &in, int maxbytes, QString &out) { if (in.isEmpty()) { out = QString(); return true; } StringPrepCache *that = instance(); auto it = that->saslprep_table.constFind(in); if (it != that->saslprep_table.constEnd()) { if (it.value().isNull()) { return false; } out = it.value(); return true; } QByteArray cs = in.toUtf8(); cs.resize(maxbytes); if (stringprep(cs.data(), maxbytes, (Stringprep_profile_flags)0, stringprep_saslprep) != 0) { that->saslprep_table.insert(in, QString()); return false; } QString norm = QString::fromUtf8(cs); that->saslprep_table.insert(in, norm); out = norm; return true; } void StringPrepCache::cleanup() { _instance.reset(nullptr); } StringPrepCache *StringPrepCache::instance() { if (!_instance) { _instance.reset(new StringPrepCache); #ifndef NO_IRISNET irisNetAddPostRoutine(cleanup); // REVIEW probably not necessary since heap will be deallocated with destructors #endif } return _instance.data(); } StringPrepCache::StringPrepCache() { } //---------------------------------------------------------------------------- // Jid //---------------------------------------------------------------------------- // static inline bool validDomain(const QString &s, QString &norm) { return StringPrepCache::nameprep(s, 1024, norm); } static inline bool validNode(const QString &s, QString &norm) { return StringPrepCache::nodeprep(s, 1024, norm); } static inline bool validResource(const QString &s, QString &norm) { return StringPrepCache::resourceprep(s, 1024, norm); } Jid::Jid() { valid = false; null = true; } Jid::~Jid() { } Jid::Jid(const QString &s) { set(s); } Jid::Jid(const QString &node, const QString &domain, const QString &resource) { set(domain, node, resource); } Jid::Jid(const char *s) { set(QString(s)); } Jid &Jid::operator=(const QString &s) { set(s); return *this; } Jid &Jid::operator=(const char *s) { set(QString(s)); return *this; } void Jid::reset() { f = QString(); b = QString(); d = QString(); n = QString(); r = QString(); valid = false; null = true; } void Jid::update() { // build 'bare' and 'full' jids if (n.isEmpty()) b = d; else b = n + '@' + d; if (r.isEmpty()) f = b; else f = b + '/' + r; if (f.isEmpty()) valid = false; null = f.isEmpty() && r.isEmpty(); } void Jid::set(const QString &s) { QString rest, domain, node, resource; QString norm_domain, norm_node, norm_resource; int x = s.indexOf('/'); if (x != -1) { rest = s.mid(0, x); resource = s.mid(x + 1); } else { rest = s; resource = QString(); } if (!validResource(resource, norm_resource)) { reset(); return; } x = rest.indexOf('@'); if (x != -1) { node = rest.mid(0, x); domain = rest.mid(x + 1); } else { node = QString(); domain = rest; } if (!validDomain(domain, norm_domain) || !validNode(node, norm_node)) { reset(); return; } valid = true; null = false; d = norm_domain; n = norm_node; r = norm_resource; update(); } void Jid::set(const QString &domain, const QString &node, const QString &resource) { QString norm_domain, norm_node, norm_resource; if (!validDomain(domain, norm_domain) || !validNode(node, norm_node) || !validResource(resource, norm_resource)) { reset(); return; } valid = true; null = false; d = norm_domain; n = norm_node; r = norm_resource; update(); } void Jid::setDomain(const QString &s) { if (!valid) return; QString norm; if (!validDomain(s, norm)) { reset(); return; } d = norm; update(); } void Jid::setNode(const QString &s) { if (!valid) return; QString norm; if (!validNode(s, norm)) { reset(); return; } n = norm; update(); } void Jid::setResource(const QString &s) { if (!valid) return; QString norm; if (!validResource(s, norm)) { reset(); return; } r = norm; update(); } Jid Jid::withNode(const QString &s) const { Jid j = *this; j.setNode(s); return j; } Jid Jid::withDomain(const QString &s) const { Jid j = *this; j.setDomain(s); return j; } Jid Jid::withResource(const QString &s) const { Jid j = *this; j.setResource(s); return j; } bool Jid::isValid() const { return valid; } bool Jid::isEmpty() const { return f.isEmpty(); } bool Jid::compare(const Jid &a, bool compareRes) const { if (null && a.null) return true; // only compare valid jids if (!valid || !a.valid) return false; if (compareRes ? (f != a.f) : (b != a.b)) return false; return true; } psi-plus-snapshots-1.4.1456/iris/src/xmpp/jid/jid.h000066400000000000000000000057541370065651000217740ustar00rootroot00000000000000/* * jid.h * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_JID_H #define XMPP_JID_H #include #include #include #include namespace XMPP { class StringPrepCache { public: static bool nameprep(const QString &in, int maxbytes, QString &out); static bool nodeprep(const QString &in, int maxbytes, QString &out); static bool resourceprep(const QString &in, int maxbytes, QString &out); static bool saslprep(const QString &in, int maxbytes, QString &out); static void cleanup(); private: QHash nameprep_table; QHash nodeprep_table; QHash resourceprep_table; QHash saslprep_table; static QScopedPointer _instance; static StringPrepCache * instance(); StringPrepCache(); }; class Jid { public: Jid(); ~Jid(); Jid(const QString &s); Jid(const QString &node, const QString &domain, const QString &resource = ""); Jid(const char *s); Jid &operator=(const QString &s); Jid &operator=(const char *s); bool isNull() const { return null; } const QString &domain() const { return d; } const QString &node() const { return n; } const QString &resource() const { return r; } const QString &bare() const { return b; } const QString &full() const { return f; } Jid withNode(const QString &s) const; Jid withDomain(const QString &s) const; Jid withResource(const QString &s) const; bool isValid() const; bool isEmpty() const; bool compare(const Jid &a, bool compareRes = true) const; inline bool operator==(const Jid &other) const { return compare(other, true); } inline bool operator!=(const Jid &other) const { return !(*this == other); } private: void set(const QString &s); void set(const QString &domain, const QString &node, const QString &resource = ""); void setDomain(const QString &s); void setNode(const QString &s); void setResource(const QString &s); private: void reset(); void update(); QString f, b, d, n, r; bool valid, null; }; Q_DECL_PURE_FUNCTION inline uint qHash(const XMPP::Jid &key, uint seed = 0) Q_DECL_NOTHROW { return qHash(key.full(), seed); } } // namespace XMPP #endif // XMPP_JID_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/jid/jid.pri000066400000000000000000000001631370065651000223240ustar00rootroot00000000000000INCLUDEPATH *= $$PWD/../.. DEPENDPATH *= $$PWD/../.. HEADERS += \ $$PWD/jid.h SOURCES += \ $$PWD/jid.cpp psi-plus-snapshots-1.4.1456/iris/src/xmpp/jid/unittest/000077500000000000000000000000001370065651000227215ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/xmpp/jid/unittest/jidtest.cpp000066400000000000000000000023361370065651000250770ustar00rootroot00000000000000/* * Copyright (C) 2008 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ // FIXME: Complete this #include "xmpp/jid/jid.h" #include "qttestutil/qttestutil.h" #include #include using namespace XMPP; class JidTest : public QObject { Q_OBJECT private slots: void testConstructorWithString() { Jid testling("foo@bar/baz"); QCOMPARE(testling.node(), QString("foo")); QCOMPARE(testling.domain(), QString("bar")); QCOMPARE(testling.resource(), QString("baz")); } }; QTTESTUTIL_REGISTER_TEST(JidTest); #include "jidtest.moc" psi-plus-snapshots-1.4.1456/iris/src/xmpp/jid/unittest/unittest.pri000066400000000000000000000000431370065651000253110ustar00rootroot00000000000000SOURCES += \ $$PWD/jidtest.cpp psi-plus-snapshots-1.4.1456/iris/src/xmpp/jid/unittest/unittest.pro000066400000000000000000000001711370065651000253210ustar00rootroot00000000000000include(../../modules.pri) include($$IRIS_XMPP_QA_UNITTEST_MODULE) include($$IRIS_XMPP_JID_MODULE) include(unittest.pri) psi-plus-snapshots-1.4.1456/iris/src/xmpp/modules.pri000066400000000000000000000004241370065651000224600ustar00rootroot00000000000000IRIS_XMPP_QA_UNITTEST_MODULE = $$PWD/qa/unittest.pri IRIS_XMPP_JID_MODULE = $$PWD/jid/jid.pri IRIS_XMPP_SASL_MODULE = $$PWD/sasl/sasl.pri IRIS_XMPP_BASE_MODULE = $$PWD/base/base.pri IRIS_XMPP_ZLIB_MODULE = $$PWD/zlib/zlib.pri IRIS_XMPP_BLAKE2_MODULE = $$PWD/blake2/blake2.pri psi-plus-snapshots-1.4.1456/iris/src/xmpp/qa/000077500000000000000000000000001370065651000206755ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/xmpp/qa/README000066400000000000000000000022701370065651000215560ustar00rootroot00000000000000How to add unit tests to a module --------------------------------- - Copy the qa/unittest.template module to the module dir, and rename it to 'unittest'. Be careful not to copy '.svn'. - Create a file test.cpp for every class, and implement the tests for each method.. See 'myclasstest.cpp' for a template. - Add every test.cpp file to 'unittest.pri' - In 'unittest.pro', replace '$$MYMODULE' with the name of the module that you are unit testing. Also include all the modules that are required to compile a standalone checker for the module under test. - Add an 'include' line to the list of unit tests in qa/unittests.pri. This is used to compile the full unit test suite of all modules. - Make sure 'qa/unittests.pro' contains the module under test, and make sure that it compiles and runs fine. How to run the unit tests of a specific module ---------------------------------------------- In the 'unittest' subdir of a module, run 'qmake', and then run 'make check' to build and run the standalone checker for the module. How to run all unit tests ------------------------- First, make sure Iris has been built. Go to qa/unittests, run 'qmake', and run 'make check'. psi-plus-snapshots-1.4.1456/iris/src/xmpp/qa/qttestutil/000077500000000000000000000000001370065651000231175ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/xmpp/qa/qttestutil/example/000077500000000000000000000000001370065651000245525ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/xmpp/qa/qttestutil/example/example.pro000066400000000000000000000006501370065651000267300ustar00rootroot00000000000000# # Example unit test module with 2 unit test suites. # include(../qttestutil.pri) QT += testlib QT -= gui CONFIG -= app_bundle TARGET = checker SOURCES += \ myfirstclasstest.cpp \ mysecondclasstest.cpp \ ../simplechecker.cpp # Add an extra 'make check' target. QMAKE_EXTRA_TARGETS = check check.commands = \$(MAKE) && ./$(QMAKE_TARGET) # Cleanup the checker on 'make clean' QMAKE_CLEAN += $(QMAKE_TARGET) psi-plus-snapshots-1.4.1456/iris/src/xmpp/qa/qttestutil/example/myfirstclasstest.cpp000066400000000000000000000005571370065651000307100ustar00rootroot00000000000000#include "qttestutil/qttestutil.h" #include #include class MyFirstClassTest : public QObject { Q_OBJECT private slots: void initTestCase() { } void cleanupTestCase() { } void testMyMethod() { QCOMPARE(1, 1); // Dummy test } }; QTTESTUTIL_REGISTER_TEST(MyFirstClassTest); #include "myfirstclasstest.moc" psi-plus-snapshots-1.4.1456/iris/src/xmpp/qa/qttestutil/example/mysecondclasstest.cpp000066400000000000000000000005621370065651000310300ustar00rootroot00000000000000#include "qttestutil/qttestutil.h" #include #include class MySecondClassTest : public QObject { Q_OBJECT private slots: void initTestCase() { } void cleanupTestCase() { } void testMyMethod() { QCOMPARE(1, 0); // Dummy test } }; QTTESTUTIL_REGISTER_TEST(MySecondClassTest); #include "mysecondclasstest.moc" psi-plus-snapshots-1.4.1456/iris/src/xmpp/qa/qttestutil/qttestutil.h000066400000000000000000000024361370065651000255170ustar00rootroot00000000000000/* * Copyright (C) 2008 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef QTTESTUTIL_H #define QTTESTUTIL_H #include "qttestutil/testregistration.h" /** * A macro to register a test class. * * This macro will create a static variable which registers the * testclass with the TestRegistry, and creates an instance of the * test class. * * Execute this macro in the body of your unit test's .cpp file, e.g. * class MyTest { * ... * }; * * QTTESTUTIL_REGISTER_TEST(MyTest) */ #define QTTESTUTIL_REGISTER_TEST(TestClass) static QtTestUtil::TestRegistration TestClass##Registration #endif // QTTESTUTIL_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/qa/qttestutil/qttestutil.pri000066400000000000000000000001301370065651000260470ustar00rootroot00000000000000INCLUDEPATH *= $$PWD/.. DEPENDPATH *= $$PWD/.. SOURCES += \ $$PWD/testregistry.cpp psi-plus-snapshots-1.4.1456/iris/src/xmpp/qa/qttestutil/simplechecker.cpp000066400000000000000000000017641370065651000264510ustar00rootroot00000000000000/* * Copyright (C) 2008 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "qttestutil/testregistry.h" #include /** * Runs all tests registered with the QtTestUtil registry. */ int main(int argc, char *argv[]) { QCoreApplication application(argc, argv); return QtTestUtil::TestRegistry::getInstance()->runTests(argc, argv); } psi-plus-snapshots-1.4.1456/iris/src/xmpp/qa/qttestutil/testregistration.h000066400000000000000000000026061370065651000267060ustar00rootroot00000000000000/* * Copyright (C) 2008 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef QTTESTUTIL_TESTREGISTRATION_H #define QTTESTUTIL_TESTREGISTRATION_H #include "qttestutil/testregistry.h" namespace QtTestUtil { /** * A wrapper class around a test to manage registration and static * creation of an instance of the test class. * This class is used by QTTESTUTIL_REGISTER_TEST(), and you should not * use this class directly. */ template class TestRegistration { public: TestRegistration() { test_ = new TestClass(); TestRegistry::getInstance()->registerTest(test_); } ~TestRegistration() { delete test_; } private: TestClass *test_; }; } // namespace QtTestUtil #endif // QTTESTUTIL_TESTREGISTRATION_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/qa/qttestutil/testregistry.cpp000066400000000000000000000022511370065651000263730ustar00rootroot00000000000000/* * Copyright (C) 2008 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "qttestutil/testregistry.h" #include namespace QtTestUtil { TestRegistry *TestRegistry::getInstance() { static TestRegistry registry; return ®istry; } void TestRegistry::registerTest(QObject *test) { tests_ += test; } int TestRegistry::runTests(int argc, char *argv[]) { int result = 0; foreach (QObject *test, tests_) { result |= QTest::qExec(test, argc, argv); } return result; } } // namespace QtTestUtil psi-plus-snapshots-1.4.1456/iris/src/xmpp/qa/qttestutil/testregistry.h000066400000000000000000000031451370065651000260430ustar00rootroot00000000000000/* * Copyright (C) 2008 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef QTTESTUTIL_TESTREGISTRY_H #define QTTESTUTIL_TESTREGISTRY_H #include class QObject; namespace QtTestUtil { /** * A registry of QtTest test classes. * All test classes registered with QTTESTUTIL_REGISTER_TEST add * themselves to this registry. All registered tests can then be run at * once using runTests(). */ class TestRegistry { public: /** * Retrieve the single instance of the registry. */ static TestRegistry *getInstance(); /** * Register a QtTest test. * This method is called by QTTESTUTIL_REGISTER_TEST, and you should * not use this method directly. */ void registerTest(QObject *); /** * Run all registered tests using QTest::qExec() */ int runTests(int argc, char *argv[]); private: TestRegistry() { } private: QList tests_; }; } // namespace QtTestUtil #endif // QTTESTUTIL_TESTREGISTRY_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/qa/unittest.pri000066400000000000000000000010171370065651000232670ustar00rootroot00000000000000# # Declares all common settings/includes for a unittest module. # # Include this file from your module's unittest project file to create a # standalone checker for the module. # CONFIG += crypto include($$PWD/qttestutil/qttestutil.pri) include($$PWD/../common.pri) QT += testlib QT -= gui CONFIG -= app_bundle INCLUDEPATH *= $$PWD DEPENDPATH *= $$PWD TARGET = checker SOURCES += \ $$PWD/qttestutil/simplechecker.cpp QMAKE_EXTRA_TARGETS = check check.commands = \$(MAKE) && ./checker QMAKE_CLEAN += $(QMAKE_TARGET) psi-plus-snapshots-1.4.1456/iris/src/xmpp/qa/unittest.template/000077500000000000000000000000001370065651000243665ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/xmpp/qa/unittest.template/myclasstest.cpp000066400000000000000000000021011370065651000274370ustar00rootroot00000000000000/* * Copyright (C) 2008 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "qttestutil/qttestutil.h" #include #include class MyClassTest : public QObject { Q_OBJECT private slots: void initTestCase() { } void cleanupTestCase() { } void testMyMethod() { // QCOMPARE(foo, bar); // QVERIFY(baz); } }; QTTESTUTIL_REGISTER_TEST(MyClassTest); #include "myclasstest.moc" psi-plus-snapshots-1.4.1456/iris/src/xmpp/qa/unittest.template/unittest.pri000066400000000000000000000000471370065651000267620ustar00rootroot00000000000000SOURCES += \ $$PWD/myclasstest.cpp psi-plus-snapshots-1.4.1456/iris/src/xmpp/qa/unittest.template/unittest.pro000066400000000000000000000001551370065651000267700ustar00rootroot00000000000000include(../../modules.pri) include($$IRIS_XMPP_QA_UNITTEST_MODULE) include($$MYMODULE) include(unittest.pri) psi-plus-snapshots-1.4.1456/iris/src/xmpp/qa/unittests.pri000066400000000000000000000001661370065651000234560ustar00rootroot00000000000000# All available unit tests include($$PWD/../base/unittest/unittest.pri) include($$PWD/../sasl/unittest/unittest.pri) psi-plus-snapshots-1.4.1456/iris/src/xmpp/qa/unittests/000077500000000000000000000000001370065651000227375ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/xmpp/qa/unittests/unittests.pro000066400000000000000000000002121370065651000255160ustar00rootroot00000000000000include(../../../../iris.pri) include(../unittests.pri) include(../unittest.pri) # FIXME include(../../../../../third-party/qca/qca.pri) psi-plus-snapshots-1.4.1456/iris/src/xmpp/sasl/000077500000000000000000000000001370065651000212365ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/xmpp/sasl/digestmd5proplist.cpp000066400000000000000000000073421370065651000254320ustar00rootroot00000000000000/* * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp/sasl/digestmd5proplist.h" namespace XMPP { DIGESTMD5PropList::DIGESTMD5PropList() : QList() { } void DIGESTMD5PropList::set(const QByteArray &var, const QByteArray &val) { DIGESTMD5Prop p; p.var = var; p.val = val; append(p); } QByteArray DIGESTMD5PropList::get(const QByteArray &var) const { for (ConstIterator it = begin(); it != end(); ++it) { if ((*it).var == var) return (*it).val; } return QByteArray(); } QByteArray DIGESTMD5PropList::toString() const { QByteArray str; bool first = true; for (ConstIterator it = begin(); it != end(); ++it) { if (!first) str += ','; if ((*it).var == "realm" || (*it).var == "nonce" || (*it).var == "username" || (*it).var == "cnonce" || (*it).var == "digest-uri" || (*it).var == "authzid") str += (*it).var + "=\"" + (*it).val + '\"'; else str += (*it).var + "=" + (*it).val; first = false; } return str; } bool DIGESTMD5PropList::fromString(const QByteArray &str) { DIGESTMD5PropList list; int at = 0; while (1) { while (at < str.length() && (str[at] == ',' || str[at] == ' ' || str[at] == '\t')) ++at; int n = str.indexOf('=', at); if (n == -1) break; QByteArray var, val; var = str.mid(at, n - at); at = n + 1; if (str[at] == '\"') { ++at; n = str.indexOf('\"', at); if (n == -1) break; val = str.mid(at, n - at); at = n + 1; } else { n = at; while (n < str.length() && str[n] != ',' && str[n] != ' ' && str[n] != '\t') ++n; val = str.mid(at, n - at); at = n; } DIGESTMD5Prop prop; prop.var = var; if (var == "qop" || var == "cipher") { int a = 0; while (a < val.length()) { while (a < val.length() && (val[a] == ',' || val[a] == ' ' || val[a] == '\t')) ++a; if (a == val.length()) break; n = a + 1; while (n < val.length() && val[n] != ',' && val[n] != ' ' && val[n] != '\t') ++n; prop.val = val.mid(a, n - a); list.append(prop); a = n + 1; } } else { prop.val = val; list.append(prop); } if (at >= str.size() - 1 || (str[at] != ',' && str[at] != ' ' && str[at] != '\t')) break; } // integrity check if (list.varCount("nonce") != 1) return false; if (list.varCount("algorithm") != 1) return false; *this = list; return true; } int DIGESTMD5PropList::varCount(const QByteArray &var) const { int n = 0; for (ConstIterator it = begin(); it != end(); ++it) { if ((*it).var == var) ++n; } return n; } } // namespace XMPP psi-plus-snapshots-1.4.1456/iris/src/xmpp/sasl/digestmd5proplist.h000066400000000000000000000024101370065651000250660ustar00rootroot00000000000000/* * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef DIGESTMD5PROPLIST_H #define DIGESTMD5PROPLIST_H #include #include namespace XMPP { struct DIGESTMD5Prop { QByteArray var, val; }; class DIGESTMD5PropList : public QList { public: DIGESTMD5PropList(); void set(const QByteArray &var, const QByteArray &val); QByteArray get(const QByteArray &var) const; QByteArray toString() const; bool fromString(const QByteArray &str); private: int varCount(const QByteArray &var) const; }; } // namespace XMPP #endif // DIGESTMD5PROPLIST_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/sasl/digestmd5response.cpp000066400000000000000000000066051370065651000254150ustar00rootroot00000000000000/* * Copyright (C) 2008 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp/sasl/digestmd5response.h" #include "xmpp/base/randomnumbergenerator.h" #include "xmpp/sasl/digestmd5proplist.h" #include #include #include namespace XMPP { DIGESTMD5Response::DIGESTMD5Response(const QByteArray &challenge, const QString &service, const QString &host, const QString &arealm, const QString &user, const QString &authz, const QByteArray &password, const RandomNumberGenerator &rand) : isValid_(true) { QString realm = arealm; // get props DIGESTMD5PropList in; if (!in.fromString(challenge)) { isValid_ = false; return; } // qDebug() << (QString("simplesasl.cpp: IN: %1").arg(QString(in.toString()))); // make a cnonce QByteArray a; a.resize(32); for (int n = 0; n < (int)a.size(); ++n) { a[n] = (char)rand.generateNumberBetween(0, 255); } QByteArray cnonce = a.toBase64(); // make other variables if (realm.isEmpty()) { realm = QString::fromUtf8(in.get("realm")); } QByteArray nonce = in.get("nonce"); QByteArray nc = "00000001"; QByteArray uri = service.toUtf8() + '/' + host.toUtf8(); QByteArray qop = "auth"; // build 'response' QByteArray X = user.toUtf8() + ':' + realm.toUtf8() + ':' + password; QByteArray Y = QCA::Hash("md5").hash(X).toByteArray(); QByteArray tmp = ':' + nonce + ':' + cnonce; if (!authz.isEmpty()) tmp += ':' + authz.toUtf8(); // qDebug() << (QString(tmp)); QByteArray A1(Y + tmp); QByteArray A2 = QByteArray("AUTHENTICATE:") + uri; QByteArray HA1 = QCA::Hash("md5").hashToString(A1).toLatin1(); QByteArray HA2 = QCA::Hash("md5").hashToString(A2).toLatin1(); QByteArray KD = HA1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + HA2; QByteArray Z = QCA::Hash("md5").hashToString(KD).toLatin1(); // qDebug() << QString("simplesasl.cpp: A1 = %1").arg(QString(A1)); // qDebug() << QString("simplesasl.cpp: A2 = %1").arg(QString(A2)); // qDebug() << QString("simplesasl.cpp: KD = %1").arg(QString(KD)); // build output DIGESTMD5PropList out; out.set("username", user.toUtf8()); if (!realm.isEmpty()) out.set("realm", realm.toUtf8()); out.set("nonce", nonce); out.set("cnonce", cnonce); out.set("nc", nc); // out.set("serv-type", service.toUtf8()); // out.set("host", host.toUtf8()); out.set("digest-uri", uri); out.set("qop", qop); out.set("response", Z); out.set("charset", "utf-8"); if (!authz.isEmpty()) out.set("authzid", authz.toUtf8()); value_ = out.toString(); } } // namespace XMPP psi-plus-snapshots-1.4.1456/iris/src/xmpp/sasl/digestmd5response.h000066400000000000000000000025541370065651000250610ustar00rootroot00000000000000/* * Copyright (C) 2008 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef DIGESTMD5RESPONSE_H #define DIGESTMD5RESPONSE_H #include #include namespace XMPP { class RandomNumberGenerator; class DIGESTMD5Response { public: DIGESTMD5Response(const QByteArray &challenge, const QString &service, const QString &host, const QString &realm, const QString &user, const QString &authz, const QByteArray &password, const RandomNumberGenerator &rand); const QByteArray &getValue() const { return value_; } bool isValid() const { return isValid_; } private: bool isValid_; QByteArray value_; }; } // namespace XMPP #endif // DIGESTMD5RESPONSE_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/sasl/plainmessage.cpp000066400000000000000000000017221370065651000244140ustar00rootroot00000000000000/* * Copyright (C) 2008 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp/sasl/plainmessage.h" namespace XMPP { PLAINMessage::PLAINMessage(const QString &authzid, const QString &authcid, const QByteArray &password) { value_ = authzid.toUtf8() + '\0' + authcid.toUtf8() + '\0' + password; } } // namespace XMPP psi-plus-snapshots-1.4.1456/iris/src/xmpp/sasl/plainmessage.h000066400000000000000000000020751370065651000240630ustar00rootroot00000000000000/* * Copyright (C) 2008 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef PLAINMESSAGE_H #define PLAINMESSAGE_H #include #include namespace XMPP { class PLAINMessage { public: PLAINMessage(const QString &authzid, const QString &authcid, const QByteArray &password); const QByteArray &getValue() { return value_; } private: QByteArray value_; }; } // namespace XMPP #endif // PLAINMESSAGE_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/sasl/sasl.pri000066400000000000000000000007211370065651000227140ustar00rootroot00000000000000INCLUDEPATH *= $$PWD/../.. DEPENDPATH *= $$PWD/../.. HEADERS += \ $$PWD/plainmessage.h \ $$PWD/digestmd5proplist.h \ $$PWD/digestmd5response.h \ $$PWD/scramsha1message.h \ $$PWD/scramsha1response.h \ $$PWD/scramsha1signature.cpp SOURCES += \ $$PWD/plainmessage.cpp \ $$PWD/digestmd5proplist.cpp \ $$PWD/digestmd5response.cpp \ $$PWD/scramsha1message.cpp \ $$PWD/scramsha1response.cpp \ $$PWD/scramsha1signature.cpp psi-plus-snapshots-1.4.1456/iris/src/xmpp/sasl/scramsha1message.cpp000066400000000000000000000042201370065651000251670ustar00rootroot00000000000000/* * Copyright (C) 2010 Tobias Markmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp/sasl/scramsha1message.h" #include "xmpp/base/randomnumbergenerator.h" #include "xmpp/jid/jid.h" #include #include #include #include namespace XMPP { bool Normalize(const QString &username_in, QString &username_out) { // SASLprep if (StringPrepCache::saslprep(username_in, 1024, username_out)) { // '=' -> '=3D' and ',' -> '=2C' username_out.replace("=", "=3D"); username_out.replace(",", "=2C"); return true; } else { return false; } } SCRAMSHA1Message::SCRAMSHA1Message(const QString &authzid, const QString &authcid, const QByteArray &cnonce, const RandomNumberGenerator &rand) : isValid_(true) { QString result; QByteArray clientnonce; QString username; if (!Normalize(authcid, username)) { isValid_ = false; return; } if (cnonce.size() == 0) { // make a cnonce QByteArray a; a.resize(32); for (int n = 0; n < (int)a.size(); ++n) { a[n] = (char)rand.generateNumberBetween(0, 255); } clientnonce = a.toBase64(); } else clientnonce = cnonce; QTextStream(&result) << "n,"; if (authzid.size() > 0) { QTextStream(&result) << authzid.toUtf8(); } QTextStream(&result) << ",n=" << username << ",r=" << clientnonce; value_ = result.toUtf8(); } } // namespace XMPP psi-plus-snapshots-1.4.1456/iris/src/xmpp/sasl/scramsha1message.h000066400000000000000000000023771370065651000246470ustar00rootroot00000000000000/* * Copyright (C) 2010 Tobias Markmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef SCRAMSHA1MESSAGE_H #define SCRAMSHA1MESSAGE_H #include "xmpp/base/randomnumbergenerator.h" #include #include namespace XMPP { class SCRAMSHA1Message { public: SCRAMSHA1Message(const QString &authzid, const QString &authcid, const QByteArray &cnonce, const RandomNumberGenerator &rand); const QByteArray &getValue() { return value_; } bool isValid() const { return isValid_; } private: QByteArray value_; bool isValid_; }; } // namespace XMPP #endif // SCRAMSHA1MESSAGE_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/sasl/scramsha1response.cpp000066400000000000000000000123471370065651000254120ustar00rootroot00000000000000/* * Copyright (C) 2010 Tobias Markmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp/sasl/scramsha1response.h" #include "xmpp/base/randomnumbergenerator.h" #include "xmpp/jid/jid.h" #include #include #include #include #include #include namespace XMPP { QCA::SecureArray HMAC_SHA_1(const QCA::SecureArray &key, const QCA::SecureArray &str) { QCA::SecureArray result = QCA::MessageAuthenticationCode("hmac(sha1)", key).process(str); return result; } SCRAMSHA1Response::SCRAMSHA1Response(const QByteArray &server_first_message, const QByteArray &password_in, const QByteArray &client_first_message, const QString &salted_password_base64, const RandomNumberGenerator &rand) { Q_UNUSED(rand); QString pass_in = QString::fromUtf8(password_in); QString pass_out; QRegExp pattern("r=(.*),s=(.+),i=(\\d+)"); int pos = pattern.indexIn(QString(server_first_message)); isValid_ = true; if (pos > -1) { QString clientservernonce = pattern.cap(1); QString salt = pattern.cap(2); QString icount = pattern.cap(3); unsigned int dkLen; QCA::Hash shaHash("sha1"); shaHash.update("", 0); dkLen = shaHash.final().size(); QCA::PBKDF2 hi("sha1"); QByteArray password; // SaltedPassword := Hi(Normalize(password), salt, i) if (salted_password_base64.size() > 0) salted_password_ = QCA::SymmetricKey(QCA::SecureArray(QCA::Base64().stringToArray(salted_password_base64.toUtf8()))); if (salted_password_.size() == 0) { if (!StringPrepCache::saslprep(pass_in, 1023, pass_out)) { isValid_ = false; return; } password = pass_out.toUtf8(); salted_password_ = hi.makeKey(QCA::SecureArray(password), QCA::InitializationVector(QCA::Base64().stringToArray(salt)), dkLen, icount.toULong()); } // ClientKey := HMAC(SaltedPassword, "Client Key") QCA::SecureArray client_key(HMAC_SHA_1(salted_password_.toByteArray(), QByteArray("Client Key"))); // StoredKey := H(ClientKey) QCA::SecureArray stored_key = QCA::Hash("sha1").process(client_key); // assemble client-final-message-without-proof QString gs2_header; { QRegExp pattern("(.+)n=.+"); pattern.indexIn(QString(client_first_message)); gs2_header = pattern.cap(1); } QString client_final_message; QTextStream final_message_stream(&client_final_message); final_message_stream << "c=" << QCA::Base64().arrayToString((gs2_header.toUtf8())); final_message_stream << ",r=" << clientservernonce; // AuthMessage := client-first-message-bare + "," + server-first-message + "," + // client-final-message-without-proof QRegExp extract_cfmb_pattern("(n=.+)"); if (extract_cfmb_pattern.indexIn(QString(client_first_message)) < 0) { isValid_ = false; return; } QString client_first_message_bare = extract_cfmb_pattern.cap(1); QCA::SecureArray auth_message = QCA::SecureArray(client_first_message_bare.toUtf8()); auth_message += QCA::SecureArray(",") + QCA::SecureArray(server_first_message); auth_message += QCA::SecureArray(",") + QCA::SecureArray(client_final_message.toUtf8()); // ClientSignature := HMAC(StoredKey, AuthMessage) QCA::SecureArray client_signature = HMAC_SHA_1(stored_key, auth_message); // ClientProof := ClientKey XOR ClientSignature QCA::SecureArray client_proof(client_key.size()); for (int i = 0; i < client_proof.size(); ++i) { client_proof[i] = client_key[i] ^ client_signature[i]; } // ServerKey := HMAC(SaltedPassword, "Server Key") QCA::SecureArray server_key = HMAC_SHA_1(salted_password_, QByteArray("Server Key")); // ServerSignature := HMAC(ServerKey, AuthMessage) server_signature_ = HMAC_SHA_1(server_key, auth_message); final_message_stream << ",p=" << QCA::Base64().arrayToString(client_proof); value_ = client_final_message.toUtf8(); } else { qWarning("SASL/SCRAM-SHA-1: Failed to match pattern for server-final-message."); isValid_ = false; } } const QString SCRAMSHA1Response::getSaltedPassword() { return QCA::Base64().arrayToString(salted_password_); } } // namespace XMPP psi-plus-snapshots-1.4.1456/iris/src/xmpp/sasl/scramsha1response.h000066400000000000000000000031121370065651000250450ustar00rootroot00000000000000/* * Copyright (C) 2010 Tobias Markmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef SCRAMSHA1RESPONSE_H #define SCRAMSHA1RESPONSE_H #include #include #include namespace XMPP { class RandomNumberGenerator; class SCRAMSHA1Response { public: SCRAMSHA1Response(const QByteArray &server_first_message, const QByteArray &password, const QByteArray &client_first_message, const QString &salted_password_base64, const RandomNumberGenerator &rand); const QByteArray &getValue() const { return value_; } const QCA::SecureArray getServerSignature() const { return server_signature_; } const QString getSaltedPassword(); bool isValid() const { return isValid_; } private: bool isValid_; QByteArray value_; QCA::SecureArray server_signature_; QCA::SymmetricKey salted_password_; }; } // namespace XMPP #endif // SCRAMSHA1RESPONSE_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/sasl/scramsha1signature.cpp000066400000000000000000000031641370065651000255520ustar00rootroot00000000000000/* * Copyright (C) 2010 Tobias Markmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp/sasl/scramsha1signature.h" #include "xmpp/base/randomnumbergenerator.h" #include #include #include #include #include #include namespace XMPP { SCRAMSHA1Signature::SCRAMSHA1Signature(const QByteArray & server_final_message, const QCA::SecureArray &server_signature_should) { QRegExp pattern("v=([^,]*)"); int pos = pattern.indexIn(QString(server_final_message)); isValid_ = true; if (pos > -1) { QString server_signature = pattern.cap(1); QCA::SecureArray server_sig(QCA::Base64().stringToArray(server_signature)); if (server_sig != server_signature_should) isValid_ = false; } else { qWarning("SASL/SCRAM-SHA-1: Failed to match pattern for server-final-message."); isValid_ = false; } } } // namespace XMPP psi-plus-snapshots-1.4.1456/iris/src/xmpp/sasl/scramsha1signature.h000066400000000000000000000021641370065651000252160ustar00rootroot00000000000000/* * Copyright (C) 2010 Tobias Markmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef SCRAMSHA1SIGNATURE_H #define SCRAMSHA1SIGNATURE_H #include #include #include namespace XMPP { class SCRAMSHA1Signature { public: SCRAMSHA1Signature(const QByteArray &server_final_message, const QCA::SecureArray &server_signature_should); bool isValid() const { return isValid_; } private: bool isValid_; }; } // namespace XMPP #endif // SCRAMSHA1SIGNATURE_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/sasl/unittest/000077500000000000000000000000001370065651000231155ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/xmpp/sasl/unittest/digestmd5responsetest.cpp000066400000000000000000000064211370065651000301700ustar00rootroot00000000000000/* * Copyright (C) 2008 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp/sasl/digestmd5response.h" #include "qttestutil/qttestutil.h" #include "xmpp/base/unittest/incrementingrandomnumbergenerator.h" #include #include #include using namespace XMPP; class DIGESTMD5ResponseTest : public QObject { Q_OBJECT private slots: void testConstructor_WithAuthzid() { DIGESTMD5Response response("realm=\"example.com\"," "nonce=\"O6skKPuaCZEny3hteI19qXMBXSadoWs840MchORo\"," "qop=\"auth\",charset=\"utf-8\",algorithm=\"md5-sess\"", "xmpp", "jabber.example.com", "example.com", "myuser", "myuser_authz", "mypass", IncrementingRandomNumberGenerator(255)); QByteArray expectedValue("username=\"myuser\",realm=\"example.com\"," "nonce=\"O6skKPuaCZEny3hteI19qXMBXSadoWs840MchORo\"," "cnonce=\"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=\"," "nc=00000001," "digest-uri=\"xmpp/jabber.example.com\"," "qop=auth,response=8fe15bc2aa31956b62d9de831b21a5d4," "charset=utf-8,authzid=\"myuser_authz\""); QVERIFY(response.isValid()); QCOMPARE(response.getValue(), expectedValue); } void testConstructor_WithoutAuthzid() { DIGESTMD5Response response("realm=\"example.com\"," "nonce=\"O6skKPuaCZEny3hteI19qXMBXSadoWs840MchORo\"," "qop=\"auth\",charset=\"utf-8\",algorithm=\"md5-sess\"", "xmpp", "jabber.example.com", "example.com", "myuser", "", "mypass", IncrementingRandomNumberGenerator(255)); QByteArray expectedValue("username=\"myuser\",realm=\"example.com\"," "nonce=\"O6skKPuaCZEny3hteI19qXMBXSadoWs840MchORo\"," "cnonce=\"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=\"," "nc=00000001," "digest-uri=\"xmpp/jabber.example.com\"," "qop=auth,response=564b1c1cc16d97b019f18b14c979964b,charset=utf-8"); QVERIFY(response.isValid()); QCOMPARE(response.getValue(), expectedValue); } private: QCA::Initializer initializer; }; QTTESTUTIL_REGISTER_TEST(DIGESTMD5ResponseTest); #include "digestmd5responsetest.moc" psi-plus-snapshots-1.4.1456/iris/src/xmpp/sasl/unittest/plainmessagetest.cpp000066400000000000000000000032471370065651000271770ustar00rootroot00000000000000/* * Copyright (C) 2008 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp/sasl/plainmessage.h" #include "qttestutil/qttestutil.h" #include #include using namespace XMPP; class PlainMessageTest : public QObject { Q_OBJECT private slots: void testConstructor_WithoutAuthzID() { PLAINMessage message("", QString("user"), "pass"); QCOMPARE(message.getValue(), QByteArray("\0user\0pass", 10)); } void testConstructor_WithAuthzID() { PLAINMessage message(QString("authz"), QString("user"), "pass"); QCOMPARE(message.getValue(), QByteArray("authz\0user\0pass", 15)); } void testConstructor_WithNonASCIICharacters() { PLAINMessage message(QString("authz") + QChar(0x03A8) /* psi */, QString("user") + QChar(0x03A8) /* psi */, "pass"); QCOMPARE(message.getValue(), QByteArray("authz\xCE\xA8\0user\xCE\xA8\0pass", 19)); } }; QTTESTUTIL_REGISTER_TEST(PlainMessageTest); #include "plainmessagetest.moc" psi-plus-snapshots-1.4.1456/iris/src/xmpp/sasl/unittest/scramsha1messagetest.cpp000066400000000000000000000034751370065651000277610ustar00rootroot00000000000000/* * Copyright (C) 2010 Tobias Markmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp/sasl/scramsha1message.h" #include "qttestutil/qttestutil.h" #include "xmpp/base/unittest/incrementingrandomnumbergenerator.h" #include #include #include using namespace XMPP; class SCRAMSHA1MessageTest : public QObject { Q_OBJECT private slots: void testConstructor_WithAuthzid() { } void testConstructor_WithoutAuthzid() { SCRAMSHA1Message msg1("", "testuser", QByteArray(0, ' '), IncrementingRandomNumberGenerator(255)); QByteArray msg1_good("n,,n=testuser,r=AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="); QVERIFY(msg1.isValid()); QCOMPARE(msg1.getValue(), msg1_good); SCRAMSHA1Message msg2("", "username=test,man", QByteArray(0, ' '), IncrementingRandomNumberGenerator(255)); QByteArray msg2_good("n,,n=username=3Dtest=2Cman,r=AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="); QVERIFY(msg2.isValid()); QCOMPARE(msg2.getValue(), msg2_good); } private: QCA::Initializer initializer; }; QTTESTUTIL_REGISTER_TEST(SCRAMSHA1MessageTest); #include "scramsha1messagetest.moc" psi-plus-snapshots-1.4.1456/iris/src/xmpp/sasl/unittest/scramsha1responsetest.cpp000066400000000000000000000040411370065651000301610ustar00rootroot00000000000000/* * Copyright (C) 2008 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp/sasl/scramsha1response.h" #include "qttestutil/qttestutil.h" #include "xmpp/base/unittest/incrementingrandomnumbergenerator.h" #include "xmpp/sasl/scramsha1signature.h" #include #include #include using namespace XMPP; class SCRAMSHA1ResponseTest : public QObject { Q_OBJECT private slots: void testConstructor_WithAuthzid() { } void testConstructor_WithoutAuthzid() { if (QCA::isSupported("hmac(sha1)")) { SCRAMSHA1Response resp1("r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096", "pencil", "n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL", "", IncrementingRandomNumberGenerator(255)); const QCA::SecureArray sig = resp1.getServerSignature(); QByteArray resp_sig("v=rmF9pqV8S7suAoZWja4dJRkFsKQ="); SCRAMSHA1Signature sig1(resp_sig, sig); QByteArray resp1_ok("c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts="); QCOMPARE(resp1.getValue(), resp1_ok); QVERIFY(sig1.isValid()); } else { QFAIL("hmac(sha1) not supported in QCA."); } } private: QCA::Initializer initializer; }; QTTESTUTIL_REGISTER_TEST(SCRAMSHA1ResponseTest); #include "scramsha1responsetest.moc" psi-plus-snapshots-1.4.1456/iris/src/xmpp/sasl/unittest/unittest.pri000066400000000000000000000002351370065651000255100ustar00rootroot00000000000000SOURCES += \ $$PWD/plainmessagetest.cpp \ $$PWD/digestmd5responsetest.cpp \ $$PWD/scramsha1messagetest.cpp \ $$PWD/scramsha1responsetest.cpp psi-plus-snapshots-1.4.1456/iris/src/xmpp/sasl/unittest/unittest.pro000066400000000000000000000006071370065651000255210ustar00rootroot00000000000000include(../../modules.pri) include($$IRIS_XMPP_QA_UNITTEST_MODULE) include($$IRIS_XMPP_SASL_MODULE) include($$IRIS_XMPP_BASE_MODULE) include($$IRIS_XMPP_BASE64_MODULE) include($$IRIS_XMPP_JID_MODULE) include(unittest.pri) # QCA stuff. # TODO: Remove the dependency on QCA from this module include(../../../../../third-party/qca/qca.pri) include(../../../../../third-party/qca/qca-ossl.pri) psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-core/000077500000000000000000000000001370065651000222065ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-core/compressionhandler.cpp000066400000000000000000000034321370065651000266130ustar00rootroot00000000000000#include "compressionhandler.h" #include "xmpp/zlib/zlibcompressor.h" #include "xmpp/zlib/zlibdecompressor.h" #include #include CompressionHandler::CompressionHandler() : errorCode_(0) { outgoing_buffer_.open(QIODevice::ReadWrite); compressor_ = new ZLibCompressor(&outgoing_buffer_); incoming_buffer_.open(QIODevice::ReadWrite); decompressor_ = new ZLibDecompressor(&incoming_buffer_); } CompressionHandler::~CompressionHandler() { delete compressor_; delete decompressor_; } void CompressionHandler::writeIncoming(const QByteArray &a) { // qDebug("CompressionHandler::writeIncoming"); // qDebug() << QString("Incoming %1 bytes").arg(a.size()); errorCode_ = decompressor_->write(a); if (!errorCode_) QTimer::singleShot(0, this, SIGNAL(readyRead())); else QTimer::singleShot(0, this, SIGNAL(error())); } void CompressionHandler::write(const QByteArray &a) { // qDebug() << QString("CompressionHandler::write(%1)").arg(a.size()); errorCode_ = compressor_->write(a); if (!errorCode_) QTimer::singleShot(0, this, SIGNAL(readyReadOutgoing())); else QTimer::singleShot(0, this, SIGNAL(error())); } QByteArray CompressionHandler::read() { // qDebug("CompressionHandler::read"); QByteArray b = incoming_buffer_.buffer(); incoming_buffer_.buffer().clear(); incoming_buffer_.reset(); return b; } QByteArray CompressionHandler::readOutgoing(int *i) { // qDebug("CompressionHandler::readOutgoing"); // qDebug() << QString("Outgoing %1 bytes").arg(outgoing_buffer_.size()); QByteArray b = outgoing_buffer_.buffer(); outgoing_buffer_.buffer().clear(); outgoing_buffer_.reset(); *i = b.size(); return b; } int CompressionHandler::errorCode() { return errorCode_; } psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-core/compressionhandler.h000066400000000000000000000013361370065651000262610ustar00rootroot00000000000000#ifndef COMPRESSIONHANDLER_H #define COMPRESSIONHANDLER_H #include #include class ZLibCompressor; class ZLibDecompressor; class CompressionHandler : public QObject { Q_OBJECT public: CompressionHandler(); ~CompressionHandler(); void writeIncoming(const QByteArray &a); void write(const QByteArray &a); QByteArray read(); QByteArray readOutgoing(int *); int errorCode(); signals: void readyRead(); void readyReadOutgoing(); void error(); private: ZLibCompressor * compressor_; ZLibDecompressor *decompressor_; QBuffer outgoing_buffer_, incoming_buffer_; int errorCode_; }; #endif // COMPRESSIONHANDLER_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-core/connector.cpp000066400000000000000000000331111370065651000247030ustar00rootroot00000000000000/* * connector.cpp - establish a connection to an XMPP server * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ /* TODO: - Test and analyze all possible branches XMPP::AdvancedConnector is "good for now." The only real issue is that most of what it provides is just to work around the old Jabber/XMPP 0.9 connection behavior. When XMPP 1.0 has taken over the world, we can greatly simplify this class. - Sep 3rd, 2003. */ #include "bsocket.h" #include "httpconnect.h" #include "httppoll.h" #include "socks.h" #include "xmpp.h" #include #include #include #include #include //#define XMPP_DEBUG #ifdef XMPP_DEBUG #define XDEBUG (qDebug() << this << "#" << __FUNCTION__ << ":") #endif using namespace XMPP; static const int XMPP_DEFAULT_PORT = 5222; static const int XMPP_LEGACY_PORT = 5223; static const char *XMPP_CLIENT_SRV = "xmpp-client"; static const char *XMPP_CLIENT_TRANSPORT = "tcp"; //---------------------------------------------------------------------------- // Connector //---------------------------------------------------------------------------- Connector::Connector(QObject *parent) : QObject(parent) { setUseSSL(false); setPeerAddressNone(); } Connector::~Connector() { } bool Connector::useSSL() const { return ssl; } bool Connector::havePeerAddress() const { return haveaddr; } QHostAddress Connector::peerAddress() const { return addr; } quint16 Connector::peerPort() const { return port; } void Connector::setUseSSL(bool b) { ssl = b; } void Connector::setPeerAddressNone() { haveaddr = false; addr = QHostAddress(); port = 0; } void Connector::setPeerAddress(const QHostAddress &_addr, quint16 _port) { haveaddr = true; addr = _addr; port = _port; } QString Connector::host() const { return QString(); } //---------------------------------------------------------------------------- // AdvancedConnector::Proxy //---------------------------------------------------------------------------- int AdvancedConnector::Proxy::type() const { return t; } QString AdvancedConnector::Proxy::host() const { return v_host; } quint16 AdvancedConnector::Proxy::port() const { return v_port; } QUrl AdvancedConnector::Proxy::url() const { return v_url; } QString AdvancedConnector::Proxy::user() const { return v_user; } QString AdvancedConnector::Proxy::pass() const { return v_pass; } int AdvancedConnector::Proxy::pollInterval() const { return v_poll; } void AdvancedConnector::Proxy::setHttpConnect(const QString &host, quint16 port) { t = HttpConnect; v_host = host; v_port = port; } void AdvancedConnector::Proxy::setHttpPoll(const QString &host, quint16 port, const QUrl &url) { t = HttpPoll; v_host = host; v_port = port; v_url = url; } void AdvancedConnector::Proxy::setSocks(const QString &host, quint16 port) { t = Socks; v_host = host; v_port = port; } void AdvancedConnector::Proxy::setUserPass(const QString &user, const QString &pass) { v_user = user; v_pass = pass; } void AdvancedConnector::Proxy::setPollInterval(int secs) { v_poll = secs; } //---------------------------------------------------------------------------- // AdvancedConnector //---------------------------------------------------------------------------- typedef enum { Idle, Connecting, Connected } Mode; typedef enum { Force, Probe, Never } LegacySSL; class AdvancedConnector::Private { public: ByteStream *bs; //!< Socket to use /* configuration values / "options" */ QString opt_host; //!< explicit host from config quint16 opt_port; //!< explicit port from config LegacySSL opt_ssl; //!< Whether to use legacy SSL support Proxy proxy; //!< Proxy configuration /* State tracking values */ Mode mode; //!< Idle, Connecting, Connected QString host; //!< Host we currently try to connect to, set from connectToServer() int port; //!< Port we currently try to connect to, set from connectToServer() and bs_error() int errorCode; //!< Current error, if any }; AdvancedConnector::AdvancedConnector(QObject *parent) : Connector(parent) { d = new Private; d->bs = nullptr; d->opt_ssl = Never; cleanup(); d->errorCode = 0; } AdvancedConnector::~AdvancedConnector() { cleanup(); delete d; } void AdvancedConnector::cleanup() { d->mode = Idle; // destroy the bytestream, if there is one delete d->bs; d->bs = nullptr; setUseSSL(false); setPeerAddressNone(); } void AdvancedConnector::setProxy(const Proxy &proxy) { if (d->mode != Idle) return; d->proxy = proxy; } void AdvancedConnector::setOptHostPort(const QString &_host, quint16 _port) { #ifdef XMPP_DEBUG XDEBUG << "h:" << _host << "p:" << _port; #endif if (d->mode != Idle) return; // empty host means disable explicit host support if (_host.isEmpty()) { d->opt_host.clear(); return; } d->opt_host = _host; d->opt_port = _port; } void AdvancedConnector::setOptProbe(bool b) { #ifdef XMPP_DEBUG XDEBUG << "b:" << b; #endif if (d->mode != Idle) return; d->opt_ssl = (b ? Probe : Never); } void AdvancedConnector::setOptSSL(bool b) { #ifdef XMPP_DEBUG XDEBUG << "b:" << b; #endif if (d->mode != Idle) return; d->opt_ssl = (b ? Force : Never); } void AdvancedConnector::connectToServer(const QString &server) { #ifdef XMPP_DEBUG XDEBUG << "s:" << server; #endif if (d->mode != Idle) return; if (server.isEmpty()) return; d->errorCode = 0; d->mode = Connecting; // Encode the servername d->host = QUrl::toAce(server); if (d->host == QByteArray()) { /* server contains invalid characters for DNS name, but maybe valid characters for connecting, like "::1" */ d->host = server; } d->port = XMPP_DEFAULT_PORT; if (d->opt_ssl == Probe && (d->proxy.type() != Proxy::None || !d->opt_host.isEmpty())) { #ifdef XMPP_DEBUG XDEBUG << "Don't probe ssl port because of incompatible params"; #endif d->opt_ssl = Never; // probe is possible only with direct connect } if (d->proxy.type() == Proxy::HttpPoll) { HttpPoll *s = new HttpPoll; d->bs = s; connect(s, SIGNAL(connected()), SLOT(bs_connected())); connect(s, SIGNAL(syncStarted()), SLOT(http_syncStarted())); connect(s, SIGNAL(syncFinished()), SLOT(http_syncFinished())); connect(s, SIGNAL(error(int)), SLOT(bs_error(int))); if (!d->proxy.user().isEmpty()) s->setAuth(d->proxy.user(), d->proxy.pass()); s->setPollInterval(d->proxy.pollInterval()); if (d->proxy.host().isEmpty()) s->connectToUrl(d->proxy.url()); else s->connectToHost(d->proxy.host(), d->proxy.port(), d->proxy.url()); } else if (d->proxy.type() == Proxy::HttpConnect) { HttpConnect *s = new HttpConnect; d->bs = s; connect(s, SIGNAL(connected()), SLOT(bs_connected())); connect(s, SIGNAL(error(int)), SLOT(bs_error(int))); if (!d->opt_host.isEmpty()) { d->host = d->opt_host; d->port = d->opt_port; } if (!d->proxy.user().isEmpty()) s->setAuth(d->proxy.user(), d->proxy.pass()); s->connectToHost(d->proxy.host(), d->proxy.port(), d->host, d->port); } else if (d->proxy.type() == Proxy::Socks) { SocksClient *s = new SocksClient; d->bs = s; connect(s, SIGNAL(connected()), SLOT(bs_connected())); connect(s, SIGNAL(error(int)), SLOT(bs_error(int))); if (!d->opt_host.isEmpty()) { d->host = d->opt_host; d->port = d->opt_port; } if (!d->proxy.user().isEmpty()) s->setAuth(d->proxy.user(), d->proxy.pass()); s->connectToHost(d->proxy.host(), d->proxy.port(), d->host, d->port); } else { BSocket *s = new BSocket; d->bs = s; #ifdef XMPP_DEBUG XDEBUG << "Adding socket:" << s; #endif connect(s, SIGNAL(connected()), SLOT(bs_connected())); connect(s, SIGNAL(error(int)), SLOT(bs_error(int))); if (!d->opt_host.isEmpty()) { /* if custom host:port */ d->host = d->opt_host; d->port = d->opt_port; s->connectToHost(d->host, d->port); return; } else if (d->opt_ssl != Never) { /* if ssl forced or should be probed */ d->port = XMPP_LEGACY_PORT; } s->connectToHost(XMPP_CLIENT_SRV, XMPP_CLIENT_TRANSPORT, d->host, d->port); } } void AdvancedConnector::changePollInterval(int secs) { if (d->bs && (d->bs->inherits("XMPP::HttpPoll") || d->bs->inherits("HttpPoll"))) { HttpPoll *s = static_cast(d->bs); s->setPollInterval(secs); } } ByteStream *AdvancedConnector::stream() const { if (d->mode == Connected) return d->bs; else return nullptr; } void AdvancedConnector::done() { cleanup(); } int AdvancedConnector::errorCode() const { return d->errorCode; } void AdvancedConnector::bs_connected() { #ifdef XMPP_DEBUG XDEBUG; #endif if (d->proxy.type() == Proxy::None) { QHostAddress h = (static_cast(d->bs))->peerAddress(); int p = (static_cast(d->bs))->peerPort(); setPeerAddress(h, p); } // We won't use ssl with HttpPoll since it has ow tls handler enabled for https. // The only variant for ssl is legacy port in probing or forced mde. if (d->proxy.type() != Proxy::HttpPoll && (d->opt_ssl == Force || (d->opt_ssl == Probe && peerPort() == XMPP_LEGACY_PORT))) { // in case of Probe it's ok to check actual peer "port" since we are sure Proxy=None setUseSSL(true); } d->mode = Connected; emit connected(); } void AdvancedConnector::bs_error(int x) { #ifdef XMPP_DEBUG XDEBUG << "e:" << x; #endif if (d->mode == Connected) { d->errorCode = ErrStream; emit error(); return; } bool proxyError = false; int err = ErrConnectionRefused; int t = d->proxy.type(); #ifdef XMPP_DEBUG qDebug("bse1"); #endif // figure out the error if (t == Proxy::None) { if (x == BSocket::ErrHostNotFound) err = ErrHostNotFound; else err = ErrConnectionRefused; } else if (t == Proxy::HttpConnect) { if (x == HttpConnect::ErrConnectionRefused) err = ErrConnectionRefused; else if (x == HttpConnect::ErrHostNotFound) err = ErrHostNotFound; else { proxyError = true; if (x == HttpConnect::ErrProxyAuth) err = ErrProxyAuth; else if (x == HttpConnect::ErrProxyNeg) err = ErrProxyNeg; else err = ErrProxyConnect; } } else if (t == Proxy::HttpPoll) { if (x == HttpPoll::ErrConnectionRefused) err = ErrConnectionRefused; else if (x == HttpPoll::ErrHostNotFound) err = ErrHostNotFound; else { proxyError = true; if (x == HttpPoll::ErrProxyAuth) err = ErrProxyAuth; else if (x == HttpPoll::ErrProxyNeg) err = ErrProxyNeg; else err = ErrProxyConnect; } } else if (t == Proxy::Socks) { if (x == SocksClient::ErrConnectionRefused) err = ErrConnectionRefused; else if (x == SocksClient::ErrHostNotFound) err = ErrHostNotFound; else { proxyError = true; if (x == SocksClient::ErrProxyAuth) err = ErrProxyAuth; else if (x == SocksClient::ErrProxyNeg) err = ErrProxyNeg; else err = ErrProxyConnect; } } // no-multi or proxy error means we quit if (proxyError) { cleanup(); d->errorCode = err; emit error(); return; } /* if we shall probe the ssl legacy port, and we just did that (port=legacy), then try to connect to the normal port instead */ if (d->opt_ssl == Probe && d->port == XMPP_LEGACY_PORT) { #ifdef XMPP_DEBUG qDebug("bse1.2"); #endif BSocket *s = static_cast(d->bs); d->port = XMPP_DEFAULT_PORT; // at this moment we already tried everything from srv. so just try the host itself s->connectToHost(d->host, d->port); } /* otherwise we have no fallbacks and must have failed to connect */ else { #ifdef XMPP_DEBUG qDebug("bse1.3"); #endif cleanup(); d->errorCode = ErrConnectionRefused; emit error(); } } void AdvancedConnector::http_syncStarted() { httpSyncStarted(); } void AdvancedConnector::http_syncFinished() { httpSyncFinished(); } void AdvancedConnector::t_timeout() { // bs_error(-1); } QString AdvancedConnector::host() const { return d->host; } psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-core/parser.cpp000066400000000000000000000250321370065651000242100ustar00rootroot00000000000000/* * parser.cpp - parse an XMPP "document" * Copyright (C) 2020 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "parser.h" #include namespace XMPP { //---------------------------------------------------------------------------- // Event //---------------------------------------------------------------------------- class Parser::Event::Private : public QSharedData { public: int type; QString ns, ln, qn; QXmlStreamAttributes a; QDomElement e; QString str; QXmlStreamNamespaceDeclarations nsPrefixes; }; Parser::Event::Event() { } Parser::Event::Event(const Event &from) : d(from.d) { } Parser::Event &Parser::Event::operator=(const Event &from) { d = from.d; return *this; } Parser::Event::~Event() { } bool Parser::Event::isNull() const { return d == nullptr; } void Parser::Event::ensureD() { if (!d) d = new Private(); } int Parser::Event::type() const { if (isNull()) return -1; return d->type; } QString Parser::Event::nsprefix(const QString &s) const { Q_ASSERT(d != nullptr); auto it = std::find_if(d->nsPrefixes.cbegin(), d->nsPrefixes.cend(), [&](auto const &v) { return v.prefix() == s; }); if (it == d->nsPrefixes.cend()) return QString(); return it->namespaceUri().toString(); } QString Parser::Event::namespaceURI() const { Q_ASSERT(d != nullptr); return d->ns; } QString Parser::Event::localName() const { Q_ASSERT(d != nullptr); return d->ln; } QString Parser::Event::qName() const { Q_ASSERT(d != nullptr); return d->qn; } QXmlStreamAttributes Parser::Event::atts() const { Q_ASSERT(d != nullptr); return d->a; } QString Parser::Event::actualString() const { Q_ASSERT(d != nullptr); return d->str; } QDomElement Parser::Event::element() const { Q_ASSERT(d != nullptr); return d->e; } void Parser::Event::setDocumentOpen(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlStreamAttributes &atts, const QXmlStreamNamespaceDeclarations &nsPrefixes) { ensureD(); d->type = DocumentOpen; d->ns = namespaceURI; d->ln = localName; d->qn = qName; d->a = atts; d->nsPrefixes = nsPrefixes; } void Parser::Event::setDocumentClose(const QString &namespaceURI, const QString &localName, const QString &qName) { ensureD(); d->type = DocumentClose; d->ns = namespaceURI; d->ln = localName; d->qn = qName; } void Parser::Event::setElement(const QDomElement &elem) { ensureD(); d->type = Element; d->e = elem; } void Parser::Event::setError() { ensureD(); d->type = Error; } void Parser::Event::setActualString(const QString &str) { ensureD(); d->str = str; } //---------------------------------------------------------------------------- // Parser //---------------------------------------------------------------------------- class Parser::Private { public: QDomDocument doc; QDomElement curElement; QDomElement element; // root part std::list in; QXmlStreamReader reader; const char * completeTag = nullptr; // this is basically a workaround for bugs like QTBUG-14661 int completeOffset = 0; bool streamOpened = false; bool readerStarted = false; std::queue events; QString streamQName; void pushDataToReader() { if (completeTag) { readerStarted = true; while (!in.empty()) { if (in.front().constData() != completeTag) { reader.addData(in.front()); in.erase(in.begin()); } else { // Qt has some bugs, so ensure we push data only ending with '>' if (completeOffset == in.front().size() - 1) { reader.addData(in.front()); in.erase(in.begin()); } else { QByteArray part = in.front().left(completeOffset + 1); reader.addData(part); in.front().remove(0, completeOffset + 1); } completeTag = nullptr; break; } } } } void handleStartElement() { auto ns = reader.namespaceUri().toString(); QString name = reader.name().toString(); if (streamOpened) { QDomElement newEl; if (ns.isEmpty()) newEl = doc.createElement(name); else newEl = doc.createElementNS(ns, name); if (curElement.isNull()) { curElement = newEl; element = newEl; } else { curElement = curElement.appendChild(newEl).toElement(); } for (auto const &a : reader.attributes()) { QDomAttr da; if (a.namespaceUri().isEmpty()) da = doc.createAttribute(a.name().toString()); else da = doc.createAttributeNS(a.namespaceUri().toString(), a.name().toString()); da.setPrefix(a.prefix().toString()); da.setValue(a.value().toString()); if (a.namespaceUri().isEmpty()) curElement.setAttributeNode(da); else curElement.setAttributeNodeNS(da); } } else { Event e; streamQName = reader.qualifiedName().toString(); e.setDocumentOpen(ns, name, streamQName, reader.attributes(), reader.namespaceDeclarations()); events.push(e); streamOpened = true; } } void handleEndElement() { if (curElement.isNull() && reader.qualifiedName() == streamQName) { Event e; e.setDocumentClose(reader.namespaceUri().toString(), reader.name().toString(), streamQName); events.push(e); return; } Q_ASSERT_X(!curElement.isNull(), "xml parser", "XML reader hasn't reported error for invalid element close"); Q_ASSERT_X( curElement.namespaceURI() == reader.namespaceUri() && curElement.tagName() == reader.name(), "xml parser", qPrintable( QString("XML reader hasn't reported open/close tags mismatch. expected close for <%1 xmlns=\"%2\"> " "but got close for <%3 xmlns=\"%4\">") .arg(curElement.tagName(), curElement.namespaceURI(), reader.name().toString(), reader.namespaceUri().toString()))); #if 0 if (element.isNull()) { Event e; qWarning("xml parser: closing not existing element: %s", qPrintable(reader.qualifiedName().toString())); e.setError(); return; } if (!(element.namespaceURI() == reader.namespaceUri() && element.tagName() == reader.name())) { Event e; qWarning("XML reader hasn't reported open/close tags mismatch: %s vs %s", qPrintable(element.tagName()), qPrintable(reader.qualifiedName().toString())); e.setError(); return; } #endif if (curElement.parentNode().isNull()) { Event e; e.setElement(curElement); events.push(e); } curElement = curElement.parentNode().toElement(); } void handleText() { if (curElement.isNull()) { if (!reader.isWhitespace()) qWarning("Text node out of element (ignored): %s", qPrintable(reader.text().toString())); return; } auto node = doc.createTextNode(reader.text().toString()); curElement.appendChild(node); } void collectEvents() { auto tt = reader.readNext(); while (tt != QXmlStreamReader::NoToken && tt != QXmlStreamReader::Invalid) { if (tt == QXmlStreamReader::StartElement) { handleStartElement(); } else if (tt == QXmlStreamReader::EndElement) { handleEndElement(); } else if (tt == QXmlStreamReader::Characters) { handleText(); } else { Q_ASSERT_X(tt != QXmlStreamReader::EntityReference, "xml parser", qPrintable(QString("unexpected xml entity: %1").arg(reader.text()))); } tt = reader.readNext(); } if (tt == QXmlStreamReader::Invalid) { if (reader.error() == QXmlStreamReader::PrematureEndOfDocumentError) return; qDebug("xml parser error: %s", qPrintable(reader.errorString())); Event e; e.setError(); events.push(e); } } Parser::Event readNext() { Event e; pushDataToReader(); if (!readerStarted) return e; collectEvents(); if (!events.empty()) { e = events.front(); events.pop(); } return e; } }; Parser::Parser() { reset(); } Parser::~Parser() { } void Parser::reset() { d.reset(new Private); } void Parser::appendData(const QByteArray &a) { if (a.isEmpty()) return; d->in.push_back(a); for (int i = a.size() - 1; i >= 0; --i) { if (a.at(i) == '>') { // this may happend in CDATA too, but let's hope Qt handles it properly d->completeTag = a.constData(); d->completeOffset = i; break; } } } Parser::Event Parser::readNext() { return d->readNext(); } QByteArray Parser::unprocessed() const { QByteArray ret; for (auto const &a : d->in) { ret += a; } return ret; } QStringRef Parser::encoding() const { return d->reader.documentEncoding(); } } psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-core/parser.h000066400000000000000000000051121370065651000236520ustar00rootroot00000000000000/* * parser.h - parse an XMPP "document" * Copyright (C) 2003-2020 Justin Karneges, Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef PARSER_H #define PARSER_H #include #include #include #include namespace XMPP { class Parser { public: struct NSPrefix { QString name; QString value; }; class Event { public: enum Type { DocumentOpen, DocumentClose, Element, Error }; Event(); Event(const Event &); Event &operator=(const Event &); ~Event(); bool isNull() const; int type() const; // for document open QString nsprefix(const QString &s = QString()) const; // for document open / close QString namespaceURI() const; QString localName() const; QString qName() const; QXmlStreamAttributes atts() const; // for element QDomElement element() const; // for any QString actualString() const; // setup void setDocumentOpen(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlStreamAttributes &atts, const QXmlStreamNamespaceDeclarations &nsPrefixes); void setDocumentClose(const QString &namespaceURI, const QString &localName, const QString &qName); void setElement(const QDomElement &elem); void setError(); void setActualString(const QString &); private: void ensureD(); class Private; QExplicitlySharedDataPointer d; }; Parser(); ~Parser(); void reset(); void appendData(const QByteArray &a); Event readNext(); QByteArray unprocessed() const; QStringRef encoding() const; private: class Private; std::unique_ptr d; }; } // namespace XMPP #endif // PARSER_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-core/protocol.cpp000066400000000000000000001556031370065651000245650ustar00rootroot00000000000000/* * protocol.cpp - XMPP-Core protocol state machine * Copyright (C) 2004 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ // TODO: let the app know if tls is required // require mutual auth for server out/in // report ErrProtocol if server uses wrong NS #include "protocol.h" #ifdef XMPP_TEST #include "td.h" #endif #include #include #include #include using namespace XMPP; // printArray // // This function prints out an array of bytes as latin characters, converting // non-printable bytes into hex values as necessary. Useful for displaying // QByteArrays for debugging purposes. static QString printArray(const QByteArray &a) { QString s; for (int n = 0; n < a.size(); ++n) { unsigned char c = (unsigned char)a[(int)n]; if (c < 32 || c >= 127) { QString str = QString::asprintf("[%02x]", c); s += str; } else s += c; } return s; } // firstChildElement // // Get an element's first child element static QDomElement firstChildElement(const QDomElement &e) { for (QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { if (n.isElement()) return n.toElement(); } return QDomElement(); } //---------------------------------------------------------------------------- // Version //---------------------------------------------------------------------------- Version::Version(int maj, int min) { major = maj; minor = min; } //---------------------------------------------------------------------------- // StreamFeatures //---------------------------------------------------------------------------- StreamFeatures::StreamFeatures() { tls_supported = false; sasl_supported = false; bind_supported = false; tls_required = false; compress_supported = false; sm_supported = false; session_supported = false; session_required = false; } //---------------------------------------------------------------------------- // BasicProtocol //---------------------------------------------------------------------------- BasicProtocol::SASLCondEntry BasicProtocol::saslCondTable[] = { { "aborted", Aborted }, { "account-disabled", AccountDisabled }, { "credentials-expired", CredentialsExpired }, { "encryption-required", EncryptionRequired }, { "incorrect-encoding", IncorrectEncoding }, { "invalid-authzid", InvalidAuthzid }, { "invalid-mechanism", InvalidMech }, { "malformed-request", MalformedRequest }, { "mechanism-too-weak", MechTooWeak }, { "not-authorized", NotAuthorized }, { "temporary-auth-failure", TemporaryAuthFailure }, { nullptr, 0 }, }; BasicProtocol::StreamCondEntry BasicProtocol::streamCondTable[] = { { "bad-format", BadFormat }, { "bad-namespace-prefix", BadNamespacePrefix }, { "conflict", Conflict }, { "connection-timeout", ConnectionTimeout }, { "host-gone", HostGone }, { "host-unknown", HostUnknown }, { "improper-addressing", ImproperAddressing }, { "internal-server-error", InternalServerError }, { "invalid-from", InvalidFrom }, { "invalid-namespace", InvalidNamespace }, { "invalid-xml", InvalidXml }, { "not-authorized", StreamNotAuthorized }, { "not-well-formed", NotWellFormed }, { "policy-violation", PolicyViolation }, { "remote-connection-failed", RemoteConnectionFailed }, { "reset", StreamReset }, { "resource-constraint", ResourceConstraint }, { "restricted-xml", RestrictedXml }, { "see-other-host", SeeOtherHost }, { "system-shutdown", SystemShutdown }, { "undefined-condition", UndefinedCondition }, { "unsupported-encoding", UnsupportedEncoding }, { "unsupported-stanza-type", UnsupportedStanzaType }, { "unsupported-version", UnsupportedVersion }, { nullptr, 0 }, }; BasicProtocol::BasicProtocol() : XmlProtocol() { init(); } BasicProtocol::~BasicProtocol() { } void BasicProtocol::init() { errCond = -1; sasl_authed = false; doShutdown = false; delayedError = false; closeError = false; ready = false; stanzasPending = 0; stanzasWritten = 0; } void BasicProtocol::reset() { XmlProtocol::reset(); init(); to = QString(); from = QString(); id = QString(); lang = QString(); version = Version(1, 0); errText = QString(); errAppSpec = QDomElement(); otherHost = QString(); spare.resize(0); sasl_mech = QString(); sasl_mechlist.clear(); sasl_step.resize(0); stanzaToRecv = QDomElement(); sendList.clear(); } void BasicProtocol::sendStanza(const QDomElement &e) { SendItem i; i.stanzaToSend = e; sendList += i; } void BasicProtocol::sendDirect(const QString &s) { SendItem i; i.stringToSend = s; sendList += i; } void BasicProtocol::sendWhitespace() { SendItem i; i.doWhitespace = true; sendList += i; } void BasicProtocol::clearSendQueue() { sendList.clear(); XmlProtocol::clearSendQueue(); } QDomElement BasicProtocol::recvStanza() { QDomElement e = stanzaToRecv; stanzaToRecv = QDomElement(); return e; } void BasicProtocol::shutdown() { doShutdown = true; } void BasicProtocol::shutdownWithError(int cond, const QString &str) { otherHost = str; delayErrorAndClose(cond); } bool BasicProtocol::isReady() const { return ready; } void BasicProtocol::setReady(bool b) { ready = b; } QString BasicProtocol::saslMech() const { return sasl_mech; } QByteArray BasicProtocol::saslStep() const { return sasl_step; } void BasicProtocol::setSASLMechList(const QStringList &list) { sasl_mechlist = list; } void BasicProtocol::setSASLFirst(const QString &mech, const QByteArray &step) { sasl_mech = mech; sasl_step = step; } void BasicProtocol::setSASLNext(const QByteArray &step) { sasl_step = step; } void BasicProtocol::setSASLAuthed() { sasl_authed = true; } int BasicProtocol::stringToSASLCond(const QString &s) { for (int n = 0; saslCondTable[n].str; ++n) { if (s == saslCondTable[n].str) return saslCondTable[n].cond; } return -1; } int BasicProtocol::stringToStreamCond(const QString &s) { for (int n = 0; streamCondTable[n].str; ++n) { if (s == streamCondTable[n].str) return streamCondTable[n].cond; } return -1; } QString BasicProtocol::saslCondToString(int x) { for (int n = 0; saslCondTable[n].str; ++n) { if (x == saslCondTable[n].cond) return saslCondTable[n].str; } return QString(); } QString BasicProtocol::streamCondToString(int x) { for (int n = 0; streamCondTable[n].str; ++n) { if (x == streamCondTable[n].cond) return streamCondTable[n].str; } return QString(); } void BasicProtocol::extractStreamError(const QDomElement &e) { QString text; QHash langText; QDomElement appSpec; QDomElement t = firstChildElement(e); if (t.isNull() || t.namespaceURI() != NS_STREAMS) { // probably old-style error errCond = -1; errText = e.text(); } else errCond = stringToStreamCond(t.tagName()); if (errCond != -1) { if (errCond == SeeOtherHost) otherHost = t.text(); auto nodes = e.elementsByTagNameNS(NS_STREAMS, "text"); if (nodes.count()) { for (int i = 0; i < nodes.count(); i++) { auto e = nodes.item(i).toElement(); QString lang = e.attributeNS(NS_STREAMS, "lang", ""); langText.insert(lang, e.text()); } } else text = t.text(); // find first non-standard namespaced element QDomNodeList nl = e.childNodes(); for (int n = 0; n < nl.count(); ++n) { QDomNode i = nl.item(n); if (i.isElement() && i.namespaceURI() != NS_STREAMS) { appSpec = i.toElement(); break; } } errText = text; errLangText = langText; errAppSpec = appSpec; } } void BasicProtocol::send(const QDomElement &e, bool clip) { writeElement(e, TypeElement, false, clip, false); } void BasicProtocol::sendUrgent(const QDomElement &e, bool clip) { writeElement(e, TypeElement, false, clip, true); } void BasicProtocol::sendStreamError(int cond, const QString &text, const QDomElement &appSpec) { QDomElement se = doc.createElementNS(NS_ETHERX, "stream:error"); QDomElement err = doc.createElementNS(NS_STREAMS, streamCondToString(cond)); if (!otherHost.isEmpty()) err.appendChild(doc.createTextNode(otherHost)); se.appendChild(err); if (!text.isEmpty()) { QDomElement te = doc.createElementNS(NS_STREAMS, "text"); te.setAttributeNS(NS_XML, "xml:lang", "en"); te.appendChild(doc.createTextNode(text)); se.appendChild(te); } se.appendChild(appSpec); writeElement(se, 100, false); } void BasicProtocol::sendStreamError(const QString &text) { QDomElement se = doc.createElementNS(NS_ETHERX, "stream:error"); se.appendChild(doc.createTextNode(text)); writeElement(se, 100, false); } bool BasicProtocol::errorAndClose(int cond, const QString &text, const QDomElement &appSpec) { closeError = true; errCond = cond; errText = text; errAppSpec = appSpec; sendStreamError(cond, text, appSpec); return close(); } bool BasicProtocol::error(int code) { event = EError; errorCode = code; return true; } void BasicProtocol::delayErrorAndClose(int cond, const QString &text, const QDomElement &appSpec) { errorCode = ErrStream; errCond = cond; errText = text; errAppSpec = appSpec; delayedError = true; } void BasicProtocol::delayError(int code) { errorCode = code; delayedError = true; } QDomElement BasicProtocol::docElement() { // create the root element QDomElement e = doc.createElementNS(NS_ETHERX, "stream:stream"); QString defns = defaultNamespace(); const QStringList list = extraNamespaces(); // HACK: using attributes seems to be the only way to get additional namespaces in here if (!defns.isEmpty()) e.setAttribute(QString::fromLatin1("xmlns"), defns); for (QStringList::ConstIterator it = list.begin(); it != list.end();) { QString prefix = *(it++); QString uri = *(it++); e.setAttribute(QString::fromLatin1("xmlns:") + prefix, uri); } // additional attributes if (!isIncoming() && !to.isEmpty()) e.setAttribute(QString::fromLatin1("to"), to); if (isIncoming() && !from.isEmpty()) e.setAttribute(QString::fromLatin1("from"), from); if (!id.isEmpty()) e.setAttribute(QString::fromLatin1("id"), id); if (!lang.isEmpty()) e.setAttributeNS(QString::fromLatin1(NS_XML), QString::fromLatin1("xml:lang"), lang); if (version.major > 0 || version.minor > 0) e.setAttribute(QString::fromLatin1("version"), QString::number(version.major) + '.' + QString::number(version.minor)); return e; } void BasicProtocol::handleDocOpen(const Parser::Event &pe) { if (isIncoming()) { if (xmlEncoding() != "UTF-8") { delayErrorAndClose(UnsupportedEncoding); return; } } if (pe.namespaceURI() == NS_ETHERX && pe.localName() == "stream") { auto atts = pe.atts(); // grab the version int major = 0; int minor = 0; auto verstr = atts.value("version"); if (!verstr.isEmpty()) { int n = verstr.indexOf('.'); if (n != -1) { major = verstr.mid(0, n).toInt(); minor = verstr.mid(n + 1).toInt(); } else { major = verstr.toInt(); minor = 0; } } version = Version(major, minor); if (isIncoming()) { to = atts.value("to").toString(); auto peerLang = atts.value(NS_XML, "lang"); if (!peerLang.isEmpty()) lang = peerLang.toString(); } // outgoing else { from = atts.value("from").toString(); lang = atts.value(NS_XML, "lang").toString(); id = atts.value("id").toString(); } handleStreamOpen(pe); } else { if (isIncoming()) delayErrorAndClose(BadFormat); else delayError(ErrProtocol); } } bool BasicProtocol::handleError() { if (isIncoming()) return errorAndClose(NotWellFormed); else return error(ErrParse); } bool BasicProtocol::handleCloseFinished() { if (closeError) { event = EError; errorCode = ErrStream; // note: errCond and friends are already set at this point } else event = EClosed; return true; } bool BasicProtocol::doStep(const QDomElement &e) { // handle pending error if (delayedError) { if (isIncoming()) return errorAndClose(errCond, errText, errAppSpec); else return error(errorCode); } // shutdown? if (doShutdown) { doShutdown = false; return close(); } if (!e.isNull()) { // check for error if (e.namespaceURI() == NS_ETHERX && e.tagName() == "error") { extractStreamError(e); return error(ErrStream); } } if (ready) { // stanzas written? if (stanzasWritten > 0) { --stanzasWritten; event = EStanzaSent; return true; } // send items? if (!sendList.isEmpty()) { SendItem i; { QList::Iterator it = sendList.begin(); i = (*it); sendList.erase(it); } // outgoing stanza? if (!i.stanzaToSend.isNull()) { ++stanzasPending; writeElement(i.stanzaToSend, TypeStanza, true); event = ESend; } // direct send? else if (!i.stringToSend.isEmpty()) { writeString(i.stringToSend, TypeDirect, true); event = ESend; } // whitespace keepalive? else if (i.doWhitespace) { writeString("\n", TypePing, false); event = ESend; } return true; } else { // if we have pending outgoing stanzas, ask for write notification if (stanzasPending) notify |= NSend; } } return doStep2(e); } void BasicProtocol::itemWritten(int id, int) { if (id == TypeStanza) { --stanzasPending; ++stanzasWritten; } } QString BasicProtocol::defaultNamespace() { // default none return QString(); } QStringList BasicProtocol::extraNamespaces() { // default none return QStringList(); } void BasicProtocol::handleStreamOpen(const Parser::Event &) { // default does nothing } //---------------------------------------------------------------------------- // CoreProtocol //---------------------------------------------------------------------------- CoreProtocol::CoreProtocol() : BasicProtocol() { init(); } CoreProtocol::~CoreProtocol() { // fprintf(stderr, "\tCoreProtocol::~CoreProtocol()\n"); } void CoreProtocol::init() { step = Start; // ?? server = false; dialback = false; dialback_verify = false; // settings jid_ = Jid(); password = QString(); oldOnly = false; allowPlain = false; doTLS = true; doAuth = true; doCompress = true; doBinding = true; // input user = QString(); host = QString(); // status old = false; digest = false; tls_started = false; sasl_started = false; compress_started = false; sm.reset(); } void CoreProtocol::reset() { BasicProtocol::reset(); init(); } void CoreProtocol::needTimer(int seconds) { notify |= NTimeout; need = NNotify; timeout_sec = seconds; } void CoreProtocol::sendStanza(const QDomElement &e) { if (sm.isActive()) { int len = sm.addUnacknowledgedStanza(e); if (len > 5 && len % 4 == 0) if (needSMRequest()) event = ESend; } BasicProtocol::sendStanza(e); } void CoreProtocol::startClientOut(const Jid &_jid, bool _oldOnly, bool tlsActive, bool _doAuth, bool _doCompress) { jid_ = _jid; to = _jid.domain(); oldOnly = _oldOnly; doAuth = _doAuth; doCompress = _doCompress; tls_started = tlsActive; if (oldOnly) version = Version(0, 0); startConnect(); } void CoreProtocol::startServerOut(const QString &_to) { server = true; to = _to; startConnect(); } void CoreProtocol::startDialbackOut(const QString &_to, const QString &_from) { server = true; dialback = true; to = _to; self_from = _from; startConnect(); } void CoreProtocol::startDialbackVerifyOut(const QString &_to, const QString &_from, const QString &id, const QString &key) { server = true; dialback = true; dialback_verify = true; to = _to; self_from = _from; dialback_id = id; dialback_key = key; startConnect(); } void CoreProtocol::startClientIn(const QString &_id) { id = _id; startAccept(); } void CoreProtocol::startServerIn(const QString &_id) { server = true; id = _id; startAccept(); } void CoreProtocol::setLang(const QString &s) { lang = s; } void CoreProtocol::setAllowTLS(bool b) { doTLS = b; } void CoreProtocol::setAllowBind(bool b) { doBinding = b; } void CoreProtocol::setAllowPlain(bool b) { allowPlain = b; } const Jid &CoreProtocol::jid() const { return jid_; } void CoreProtocol::setPassword(const QString &s) { password = s; } void CoreProtocol::setFrom(const QString &s) { from = s; } void CoreProtocol::setDialbackKey(const QString &s) { dialback_key = s; } bool CoreProtocol::loginComplete() { setReady(true); // deal with stream management if (features.sm_supported && sm.state().isEnabled() && !sm.isActive()) { if (sm.state().isResumption()) { QDomElement e = doc.createElementNS(NS_STREAM_MANAGEMENT, "resume"); e.setAttribute("previd", sm.state().resumption_id); e.setAttribute("h", sm.state().received_count); send(e); } else { QDomElement e = doc.createElementNS(NS_STREAM_MANAGEMENT, "enable"); e.setAttribute("resume", "true"); send(e); } event = ESend; step = GetSMResponse; } else { event = EReady; step = Done; } return true; } int CoreProtocol::getOldErrorCode(const QDomElement &e) { QDomElement err = e.elementsByTagNameNS(NS_CLIENT, "error").item(0).toElement(); if (err.isNull() || !err.hasAttribute("code")) return -1; return err.attribute("code").toInt(); } /*QString CoreProtocol::xmlToString(const QDomElement &e, bool clip) { // determine an appropriate 'fakeNS' to use QString ns; if(e.prefix() == "stream") ns = NS_ETHERX; else if(e.prefix() == "db") ns = NS_DIALBACK; else ns = NS_CLIENT; return ::xmlToString(e, ns, "stream:stream", clip); }*/ bool CoreProtocol::stepAdvancesParser() const { if (stepRequiresElement()) return true; else if (isReady()) return true; return false; } // all element-needing steps need to be registered here bool CoreProtocol::stepRequiresElement() const { switch (step) { case GetFeatures: case GetTLSProceed: case GetCompressProceed: case GetSASLChallenge: case GetBindResponse: case GetAuthGetResponse: case GetAuthSetResponse: case GetRequest: case GetSASLResponse: case GetSMResponse: return true; } return false; } void CoreProtocol::stringSend(const QString &s) { #ifdef XMPP_TEST TD::outgoingTag(s); #endif } void CoreProtocol::stringRecv(const QString &s) { #ifdef XMPP_TEST TD::incomingTag(s); #endif } QString CoreProtocol::defaultNamespace() { if (server) return NS_SERVER; else return NS_CLIENT; } QStringList CoreProtocol::extraNamespaces() { QStringList list; if (dialback) { list += "db"; list += NS_DIALBACK; } return list; } void CoreProtocol::handleStreamOpen(const Parser::Event &pe) { if (isIncoming()) { QString ns = pe.nsprefix(); QString db; if (server) { db = pe.nsprefix("db"); if (!db.isEmpty()) dialback = true; } // verify namespace if ((!server && ns != NS_CLIENT) || (server && ns != NS_SERVER) || (dialback && db != NS_DIALBACK)) { delayErrorAndClose(InvalidNamespace); return; } // verify version if (version.major < 1 && !dialback) { delayErrorAndClose(UnsupportedVersion); return; } } else { if (!dialback) { if (version.major >= 1 && !oldOnly) old = false; else old = true; } } } void CoreProtocol::elementSend(const QDomElement &e) { #ifdef XMPP_TEST TD::outgoingXml(e); #endif } void CoreProtocol::elementRecv(const QDomElement &e) { #ifdef XMPP_TEST TD::incomingXml(e); #endif } bool CoreProtocol::doStep2(const QDomElement &e) { if (dialback) return dialbackStep(e); else return normalStep(e); } bool CoreProtocol::isValidStanza(const QDomElement &e) const { QString s = e.tagName(); Stanza::Kind kind = Stanza::kind(s); if (e.namespaceURI() == (server ? NS_SERVER : NS_CLIENT) && (kind == Stanza::Message || kind == Stanza::Presence || kind == Stanza::IQ)) return true; else return false; } bool CoreProtocol::streamManagementHandleStanza(const QDomElement &e) { QString s = e.tagName(); if (s == "r") { #ifdef IRIS_SM_DEBUG qDebug() << "Stream Management: [<-?] Received request from server"; #endif sendUrgent(sm.makeResponseStanza(doc)); event = ESend; return true; } else if (s == "a") { quint32 last_id = e.attribute("h").toULong(); #ifdef IRIS_SM_DEBUG qDebug() << "Stream Management: [<--] Received ack response from server with h =" << last_id; #endif sm.processAcknowledgement(last_id); needTimer(SM_TIMER_INTERVAL_SECS); event = EAck; return true; } else { need = NNotify; notify |= NRecv; return false; } } bool CoreProtocol::needSMRequest() { QDomElement e = sm.generateRequestStanza(doc); if (!e.isNull()) { send(e); needTimer(SM_TIMER_INTERVAL_SECS); return true; } return false; } bool CoreProtocol::grabPendingItem(const Jid &to, const Jid &from, int type, DBItem *item) { for (QList::Iterator it = dbpending.begin(); it != dbpending.end(); ++it) { const DBItem &i = *it; if (i.type == type && i.to.compare(to) && i.from.compare(from)) { const DBItem &i = (*it); *item = i; dbpending.erase(it); return true; } } return false; } bool CoreProtocol::dialbackStep(const QDomElement &e) { if (step == Start) { setReady(true); step = Done; event = EReady; return true; } if (!dbrequests.isEmpty()) { // process a request DBItem i; { QList::Iterator it = dbrequests.begin(); i = (*it); dbrequests.erase(it); } QDomElement r; if (i.type == DBItem::ResultRequest) { r = doc.createElementNS(NS_DIALBACK, "db:result"); r.setAttribute("to", i.to.full()); r.setAttribute("from", i.from.full()); r.appendChild(doc.createTextNode(i.key)); dbpending += i; } else if (i.type == DBItem::ResultGrant) { r = doc.createElementNS(NS_DIALBACK, "db:result"); r.setAttribute("to", i.to.full()); r.setAttribute("from", i.from.full()); r.setAttribute("type", i.ok ? "valid" : "invalid"); if (i.ok) { i.type = DBItem::Validated; dbvalidated += i; } else { // TODO: disconnect after writing element } } else if (i.type == DBItem::VerifyRequest) { r = doc.createElementNS(NS_DIALBACK, "db:verify"); r.setAttribute("to", i.to.full()); r.setAttribute("from", i.from.full()); r.setAttribute("id", i.id); r.appendChild(doc.createTextNode(i.key)); dbpending += i; } // VerifyGrant else { r = doc.createElementNS(NS_DIALBACK, "db:verify"); r.setAttribute("to", i.to.full()); r.setAttribute("from", i.from.full()); r.setAttribute("id", i.id); r.setAttribute("type", i.ok ? "valid" : "invalid"); } send(r); event = ESend; return true; } if (!e.isNull()) { if (e.namespaceURI() == NS_DIALBACK) { if (e.tagName() == "result") { Jid to(Jid(e.attribute("to")).domain()); Jid from(Jid(e.attribute("from")).domain()); if (isIncoming()) { QString key = e.text(); // TODO: report event } else { bool ok = (e.attribute("type") == "valid") ? true : false; DBItem i; if (grabPendingItem(from, to, DBItem::ResultRequest, &i)) { if (ok) { i.type = DBItem::Validated; i.ok = true; dbvalidated += i; // TODO: report event } else { // TODO: report event } } } } else if (e.tagName() == "verify") { Jid to(Jid(e.attribute("to")).domain()); Jid from(Jid(e.attribute("from")).domain()); QString id = e.attribute("id"); if (isIncoming()) { QString key = e.text(); // TODO: report event } else { bool ok = (e.attribute("type") == "valid") ? true : false; DBItem i; if (grabPendingItem(from, to, DBItem::VerifyRequest, &i)) { if (ok) { // TODO: report event } else { // TODO: report event } } } } } else { if (isReady()) { if (isValidStanza(e)) { // TODO: disconnect if stanza is from unverified sender // TODO: ignore packets from receiving servers stanzaToRecv = e; event = EStanzaReady; return true; } } } } need = NNotify; notify |= NRecv; return false; } bool CoreProtocol::normalStep(const QDomElement &e) { if (step == Start) { if (isIncoming()) { need = NSASLMechs; step = SendFeatures; return false; } else { if (old) { if (doAuth) step = HandleAuthGet; else return loginComplete(); } else step = GetFeatures; return processStep(); } } else if (step == HandleFeatures) { // deal with TLS? if (doTLS && !tls_started && !sasl_authed && features.tls_supported) { QDomElement e = doc.createElementNS(NS_TLS, "starttls"); send(e, true); event = ESend; step = GetTLSProceed; return true; } // Should we go further ? if (!doAuth) return loginComplete(); // Deal with compression if (doCompress && !compress_started && features.compress_supported && features.compression_mechs.contains("zlib")) { QDomElement e = doc.createElementNS(NS_COMPRESS_PROTOCOL, "compress"); QDomElement m = doc.createElementNS(NS_COMPRESS_PROTOCOL, "method"); m.appendChild(doc.createTextNode("zlib")); e.appendChild(m); send(e, true); event = ESend; step = GetCompressProceed; return true; } // deal with SASL? if (!sasl_authed) { if (!features.sasl_supported) { // SASL MUST be supported // event = EError; // errorCode = ErrProtocol; // return true; // Fall back on auth for non-compliant servers step = HandleAuthGet; old = true; return true; } #ifdef XMPP_TEST TD::msg("starting SASL authentication..."); #endif need = NSASLFirst; step = GetSASLFirst; return false; } if (server) { return loginComplete(); } else { if (!doBinding) return loginComplete(); } // deal with bind if (!features.bind_supported) { // bind MUST be supported event = EError; errorCode = ErrProtocol; return true; } if (sm.state().isResumption()) { // try to resume; return loginComplete(); } else { QDomElement e = doc.createElement("iq"); e.setAttribute("type", "set"); e.setAttribute("id", "bind_1"); QDomElement b = doc.createElementNS(NS_BIND, "bind"); // request specific resource? QString resource = jid_.resource(); if (!resource.isEmpty()) { QDomElement r = doc.createElement("resource"); r.appendChild(doc.createTextNode(jid_.resource())); b.appendChild(r); } e.appendChild(b); send(e); event = ESend; step = GetBindResponse; return true; } } else if (step == GetSASLFirst) { QDomElement e = doc.createElementNS(NS_SASL, "auth"); e.setAttribute("mechanism", sasl_mech); if (!sasl_step.isEmpty()) { #ifdef XMPP_TEST TD::msg(QString("SASL OUT: [%1]").arg(printArray(sasl_step))); #endif e.appendChild(doc.createTextNode(QCA::Base64().arrayToString(sasl_step))); } send(e, true); event = ESend; step = GetSASLChallenge; return true; } else if (step == GetSASLNext) { if (isIncoming()) { if (sasl_authed) { QDomElement e = doc.createElementNS(NS_SASL, "success"); send(e, true); event = ESend; step = IncHandleSASLSuccess; return true; } else { QByteArray stepData = sasl_step; QDomElement e = doc.createElementNS(NS_SASL, "challenge"); if (!stepData.isEmpty()) e.appendChild(doc.createTextNode(QCA::Base64().arrayToString(stepData))); send(e, true); event = ESend; step = GetSASLResponse; return true; } } else { // already authed? then ignore last client step // (this happens if "additional data with success" // is used) if (sasl_authed) { event = ESASLSuccess; step = HandleSASLSuccess; return true; } QByteArray stepData = sasl_step; #ifdef XMPP_TEST TD::msg(QString("SASL OUT: [%1]").arg(printArray(sasl_step))); #endif QDomElement e = doc.createElementNS(NS_SASL, "response"); if (!stepData.isEmpty()) e.appendChild(doc.createTextNode(QCA::Base64().arrayToString(stepData))); send(e, true); event = ESend; step = GetSASLChallenge; return true; } } else if (step == HandleSASLSuccess) { need = NSASLLayer; spare = resetStream(); step = Start; return false; } else if (step == HandleAuthGet) { QDomElement e = doc.createElement("iq"); e.setAttribute("to", to); e.setAttribute("type", "get"); e.setAttribute("id", "auth_1"); QDomElement q = doc.createElementNS("jabber:iq:auth", "query"); QDomElement u = doc.createElement("username"); u.appendChild(doc.createTextNode(jid_.node())); q.appendChild(u); e.appendChild(q); send(e); event = ESend; step = GetAuthGetResponse; return true; } else if (step == HandleAuthSet) { QDomElement e = doc.createElement("iq"); e.setAttribute("to", to); e.setAttribute("type", "set"); e.setAttribute("id", "auth_2"); QDomElement q = doc.createElementNS("jabber:iq:auth", "query"); QDomElement u = doc.createElement("username"); u.appendChild(doc.createTextNode(jid_.node())); q.appendChild(u); QDomElement p; if (digest) { // need SHA1 here // if(!QCA::isSupported(QCA::CAP_SHA1)) // QCA::insertProvider(createProviderHash()); p = doc.createElement("digest"); QByteArray cs = id.toUtf8() + password.toUtf8(); p.appendChild(doc.createTextNode(QCA::Hash("sha1").hashToString(cs))); } else { p = doc.createElement("password"); p.appendChild(doc.createTextNode(password)); } q.appendChild(p); QDomElement r = doc.createElement("resource"); r.appendChild(doc.createTextNode(jid_.resource())); q.appendChild(r); e.appendChild(q); send(e, true); event = ESend; step = GetAuthSetResponse; return true; } // server else if (step == SendFeatures) { QDomElement f = doc.createElementNS(NS_ETHERX, "stream:features"); if (!tls_started && !sasl_authed) { // don't offer tls if we are already sasl'd QDomElement tls = doc.createElementNS(NS_TLS, "starttls"); f.appendChild(tls); } if (sasl_authed) { if (!server) { QDomElement bind = doc.createElementNS(NS_BIND, "bind"); f.appendChild(bind); } } else { QDomElement mechs = doc.createElementNS(NS_SASL, "mechanisms"); for (const QString &it : sasl_mechlist) { QDomElement m = doc.createElement("mechanism"); m.appendChild(doc.createTextNode(it)); mechs.appendChild(m); } f.appendChild(mechs); } send(f); event = ESend; step = GetRequest; return true; } // server else if (step == HandleTLS) { tls_started = true; need = NStartTLS; spare = resetStream(); step = Start; return false; } // server else if (step == IncHandleSASLSuccess) { event = ESASLSuccess; spare = resetStream(); step = Start; printf("sasl success\n"); return true; } else if (step == GetFeatures) { // we are waiting for stream features if (e.namespaceURI() == NS_ETHERX && e.tagName() == QLatin1String("features")) { // extract features StreamFeatures f; QDomNodeList nl = e.childNodes(); QList unhandled; for (int i = 0; i < nl.size(); i++) { QDomElement c = nl.item(i).toElement(); if (c.isNull()) { continue; } if (c.localName() == QLatin1String("starttls") && c.namespaceURI() == NS_TLS) { f.tls_supported = true; f.tls_required = c.elementsByTagNameNS(NS_TLS, QLatin1String("required")).count() > 0; } else if (c.localName() == QLatin1String("mechanisms") && c.namespaceURI() == NS_SASL) { f.sasl_supported = true; QDomNodeList l = c.elementsByTagNameNS(NS_SASL, QLatin1String("mechanism")); for (int n = 0; n < l.count(); ++n) f.sasl_mechs += l.item(n).toElement().text(); } else if (c.localName() == QLatin1String("compression") && c.namespaceURI() == NS_COMPRESS_FEATURE) { f.compress_supported = true; QDomNodeList l = c.elementsByTagNameNS(NS_COMPRESS_FEATURE, QLatin1String("method")); for (int n = 0; n < l.count(); ++n) f.compression_mechs += l.item(n).toElement().text(); } else if (c.localName() == QLatin1String("bind") && c.namespaceURI() == NS_BIND) { f.bind_supported = true; } else if (c.localName() == QLatin1String("hosts") && c.namespaceURI() == NS_HOSTS) { QDomNodeList l = c.elementsByTagNameNS(NS_HOSTS, QLatin1String("host")); for (int n = 0; n < l.count(); ++n) f.hosts += l.item(n).toElement().text(); hosts += f.hosts; } else if (c.localName() == QLatin1String("sm") && c.namespaceURI() == NS_STREAM_MANAGEMENT) { f.sm_supported = true; // REVIEW: previously we checked for sasl_authed as well. why? } else if (c.localName() == QLatin1String("session") && c.namespaceURI() == NS_SESSION) { f.session_supported = true; f.session_required = c.elementsByTagName(QLatin1String("optional")).count() == 0; // more details https://tools.ietf.org/html/draft-cridland-xmpp-session-01 } else { unhandled.append(c); } } if (f.tls_supported) { #ifdef XMPP_TEST QString s = "STARTTLS is available"; if (f.tls_required) s += " (required)"; TD::msg(s); #endif } if (f.sasl_supported) { #ifdef XMPP_TEST QString s = "SASL mechs:"; for (QStringList::ConstIterator it = f.sasl_mechs.begin(); it != f.sasl_mechs.end(); ++it) s += QString(" [%1]").arg((*it)); TD::msg(s); #endif } if (f.compress_supported) { #ifdef XMPP_TEST QString s = "Compression mechs:"; for (QStringList::ConstIterator it = f.compression_mechs.begin(); it != f.compression_mechs.end(); ++it) s += QString(" [%1]").arg((*it)); TD::msg(s); #endif } event = EFeatures; features = f; unhandledFeatures = unhandled; step = HandleFeatures; return true; } else { // ignore } } else if (step == GetTLSProceed) { // waiting for proceed to starttls if (e.namespaceURI() == NS_TLS) { if (e.tagName() == "proceed") { #ifdef XMPP_TEST TD::msg("Server wants us to proceed with ssl handshake"); #endif tls_started = true; need = NStartTLS; spare = resetStream(); step = Start; return false; } else if (e.tagName() == "failure") { event = EError; errorCode = ErrStartTLS; return true; } else { event = EError; errorCode = ErrProtocol; return true; } } else { // ignore } } else if (step == GetCompressProceed) { // waiting for proceed to compression if (e.namespaceURI() == NS_COMPRESS_PROTOCOL) { if (e.tagName() == "compressed") { #ifdef XMPP_TEST TD::msg("Server wants us to proceed with compression"); #endif compress_started = true; need = NCompress; spare = resetStream(); step = Start; return false; } else if (e.tagName() == "failure") { event = EError; errorCode = ErrCompress; return true; } else { event = EError; errorCode = ErrProtocol; return true; } } else { // ignore } } else if (step == GetSASLChallenge) { // waiting for sasl challenge/success/fail if (e.namespaceURI() == NS_SASL) { if (e.tagName() == "challenge") { QByteArray a = QCA::Base64().stringToArray(e.text()).toByteArray(); #ifdef XMPP_TEST TD::msg(QString("SASL IN: [%1]").arg(printArray(a))); #endif sasl_step = a; need = NSASLNext; step = GetSASLNext; return false; } else if (e.tagName() == "success") { QString str = e.text(); // "additional data with success" ? if (!str.isEmpty()) { QByteArray a = QCA::Base64().stringToArray(str).toByteArray(); sasl_step = a; sasl_authed = true; need = NSASLNext; step = GetSASLNext; return false; } sasl_authed = true; event = ESASLSuccess; step = HandleSASLSuccess; return true; } else if (e.tagName() == "failure") { QDomElement t = firstChildElement(e); if (t.isNull() || t.namespaceURI() != NS_SASL) errCond = -1; else errCond = stringToSASLCond(t.tagName()); // handle text elements auto nodes = e.elementsByTagNameNS(NS_SASL, QLatin1String("text")); decltype(errLangText) lt; for (int i = 0; i < nodes.count(); i++) { auto e = nodes.item(i).toElement(); QString lang = e.attributeNS(NS_SASL, "lang", ""); lt.insert(lang, e.text()); } errLangText = lt; event = EError; errorCode = ErrAuth; return true; } else { event = EError; errorCode = ErrProtocol; return true; } } } else if (step == GetBindResponse) { if (e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") { QString type(e.attribute("type")); QString id(e.attribute("id")); if (id == "bind_1" && (type == "result" || type == "error")) { if (type == "result") { QDomElement b = e.elementsByTagNameNS(NS_BIND, "bind").item(0).toElement(); Jid j; if (!b.isNull()) { QDomElement je = e.elementsByTagName("jid").item(0).toElement(); j = je.text(); } if (!j.isValid()) { event = EError; errorCode = ErrProtocol; return true; } jid_ = j; return loginComplete(); } else { errCond = -1; QDomElement err = e.elementsByTagNameNS(NS_CLIENT, "error").item(0).toElement(); if (!err.isNull()) { // get error condition QDomNodeList nl = err.childNodes(); QDomElement t; for (int n = 0; n < nl.count(); ++n) { QDomNode i = nl.item(n); if (i.isElement()) { t = i.toElement(); break; } } if (!t.isNull() && t.namespaceURI() == NS_STANZAS) { QString cond = t.tagName(); if (cond == "not-allowed") errCond = BindNotAllowed; else if (cond == "conflict") errCond = BindConflict; } } event = EError; errorCode = ErrBind; return true; } } else { // ignore } } else { // ignore } } else if (step == GetAuthGetResponse) { // waiting for an iq if (e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") { Jid from(e.attribute("from")); QString type(e.attribute("type")); QString id(e.attribute("id")); bool okfrom = (from.isEmpty() || from.compare(Jid(to))); if (okfrom && id == "auth_1" && (type == "result" || type == "error")) { if (type == "result") { QDomElement q = e.elementsByTagNameNS("jabber:iq:auth", "query").item(0).toElement(); if (q.isNull() || q.elementsByTagName("username").item(0).isNull() || q.elementsByTagName("resource").item(0).isNull()) { event = EError; errorCode = ErrProtocol; return true; } bool plain_supported = !q.elementsByTagName("password").item(0).isNull(); bool digest_supported = !q.elementsByTagName("digest").item(0).isNull(); if (!digest_supported && !plain_supported) { event = EError; errorCode = ErrProtocol; return true; } // plain text not allowed? if (!digest_supported && !allowPlain) { event = EError; errorCode = ErrPlain; return true; } digest = digest_supported; need = NPassword; step = HandleAuthSet; return false; } else { errCond = getOldErrorCode(e); event = EError; errorCode = ErrAuth; return true; } } else { // ignore } } else { // ignore } } else if (step == GetAuthSetResponse) { // waiting for an iq if (e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") { Jid from(e.attribute("from")); QString type(e.attribute("type")); QString id(e.attribute("id")); bool okfrom = (from.isEmpty() || from.compare(Jid(to))); if (okfrom && id == "auth_2" && (type == "result" || type == "error")) { if (type == "result") { return loginComplete(); } else { errCond = getOldErrorCode(e); event = EError; errorCode = ErrAuth; return true; } } else { // ignore } } else { // ignore } } // server else if (step == GetRequest) { printf("get request: [%s], %s\n", e.namespaceURI().toLatin1().data(), e.tagName().toLatin1().data()); if (e.namespaceURI() == NS_TLS && e.localName() == "starttls") { // TODO: don't let this be done twice QDomElement e = doc.createElementNS(NS_TLS, "proceed"); send(e, true); event = ESend; step = HandleTLS; return true; } if (e.namespaceURI() == NS_SASL) { if (e.localName() == "auth") { if (sasl_started) { // TODO printf("error\n"); return false; } sasl_started = true; sasl_mech = e.attribute("mechanism"); // TODO: if child text missing, don't pass it sasl_step = QCA::Base64().stringToArray(e.text()).toByteArray(); need = NSASLFirst; step = GetSASLNext; return false; } else { // TODO printf("unknown sasl tag\n"); return false; } } if (e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") { QDomElement b = e.elementsByTagNameNS(NS_BIND, "bind").item(0).toElement(); if (!b.isNull()) { QDomElement res = b.elementsByTagName("resource").item(0).toElement(); QString resource = res.text(); QDomElement r = doc.createElement("iq"); r.setAttribute("type", "result"); r.setAttribute("id", e.attribute("id")); QDomElement bind = doc.createElementNS(NS_BIND, "bind"); QDomElement jid = doc.createElement("jid"); Jid j = QString(user + '@' + host + '/' + resource); jid.appendChild(doc.createTextNode(j.full())); bind.appendChild(jid); r.appendChild(bind); send(r); event = ESend; // TODO return true; } else { // TODO } } } else if (step == GetSASLResponse) { if (e.namespaceURI() == NS_SASL && e.localName() == "response") { sasl_step = QCA::Base64().stringToArray(e.text()).toByteArray(); need = NSASLNext; step = GetSASLNext; return false; } } else if (step == GetSMResponse) { #ifdef IRIS_SM_DEBUG qWarning() << "HandleSM: step"; #endif if (e.namespaceURI() == NS_STREAM_MANAGEMENT) { if (e.localName() == "enabled") { #ifdef IRIS_SM_DEBUG qDebug() << "Stream Management: [INF] Enabled"; #endif QString rs = e.attribute("resume"); QString id = (rs == "true" || rs == "1") ? e.attribute("id") : QString(); sm.start(id); if (!id.isEmpty()) { #ifdef IRIS_SM_DEBUG qDebug() << "Stream Management: [INF] Resumption Supported"; #endif QString location = e.attribute("location").trimmed(); if (!location.isEmpty()) { int port_off = 0; QStringRef sm_host; int sm_port = 0; if (location.startsWith('[')) { // ipv6 port_off = location.indexOf(']'); if (port_off != -1) { // looks valid sm_host = location.midRef(1, port_off - 1); if (location.length() > port_off + 2 && location.at(port_off + 1) == ':') sm_port = location.mid(port_off + 2).toUInt(); } } if (port_off == 0) { port_off = location.indexOf(':'); if (port_off != -1) { sm_host = location.leftRef(port_off); sm_port = location.mid(port_off + 1).toUInt(); } else { sm_host = location.midRef(0); } } sm.setLocation(sm_host.toString(), sm_port); } } // else resumption is not supported on this server needTimer(SM_TIMER_INTERVAL_SECS); event = EReady; step = Done; return true; } else if (e.localName() == "resumed") { sm.resume(e.attribute("h").toULong()); while (true) { QDomElement st = sm.getUnacknowledgedStanza(); if (st.isNull()) break; send(st); } needTimer(SM_TIMER_INTERVAL_SECS); event = EReady; step = Done; return true; } else if (e.localName() == "failed") { if (sm.state().isResumption()) { // tried to resume? ok, then try to just enable sm.state().resumption_id.clear(); // step = HandleFeatures; event = ESMResumeFailed; return true; } } } } if (isReady()) { if (!e.isNull()) { if (isValidStanza(e)) { stanzaToRecv = e; event = EStanzaReady; setIncomingAsExternal(); return true; } else if (sm.isActive()) { return streamManagementHandleStanza(e); } } if (sm.isActive()) { if (sm.lastAckElapsed() >= SM_TIMER_INTERVAL_SECS) { if (needSMRequest()) event = ESend; else event = ESMConnTimeout; return true; } } } need = NNotify; notify |= NRecv; return false; } psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-core/protocol.h000066400000000000000000000305731370065651000242300ustar00rootroot00000000000000/* * protocol.h - XMPP-Core protocol state machine * Copyright (C) 2004 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef PROTOCOL_H #define PROTOCOL_H #include "sm.h" #include "xmlprotocol.h" #include "xmpp.h" #include #include #include #include #define NS_ETHERX "http://etherx.jabber.org/streams" #define NS_CLIENT "jabber:client" #define NS_SERVER "jabber:server" #define NS_DIALBACK "jabber:server:dialback" #define NS_STREAMS "urn:ietf:params:xml:ns:xmpp-streams" #define NS_TLS "urn:ietf:params:xml:ns:xmpp-tls" #define NS_SASL "urn:ietf:params:xml:ns:xmpp-sasl" #define NS_SESSION "urn:ietf:params:xml:ns:xmpp-session" #define NS_STANZAS "urn:ietf:params:xml:ns:xmpp-stanzas" #define NS_BIND "urn:ietf:params:xml:ns:xmpp-bind" #define NS_CAPS "http://jabber.org/protocol/caps" #define NS_CAPS_OPTIMIZE "http://jabber.org/protocol/caps#optimize" #define NS_COMPRESS_FEATURE "http://jabber.org/features/compress" #define NS_COMPRESS_PROTOCOL "http://jabber.org/protocol/compress" #define NS_HOSTS "http://barracuda.com/xmppextensions/hosts" namespace XMPP { class Version { public: Version(int maj = 0, int min = 0); int major, minor; }; class StreamFeatures { public: StreamFeatures(); bool tls_supported, sasl_supported, bind_supported, compress_supported; bool tls_required; bool sm_supported; bool session_supported; bool session_required; QStringList sasl_mechs; QStringList compression_mechs; QStringList hosts; }; class BasicProtocol : public XmlProtocol { public: // xmpp 1.0 error conditions // rfc6120 enum SASLCond { Aborted, // server confirms auth abort AccountDisabled, // account temporrily disabled CredentialsExpired, // credential expired EncryptionRequired, // can't use mech without TLS IncorrectEncoding, // Incorrect encoding. should not happen InvalidAuthzid, // bad input JID InvalidMech, // bad mechanism MalformedRequest, // malformded request MechTooWeak, // can't use mech with this authzid NotAuthorized, // bad user, bad password, bad creditials TemporaryAuthFailure, // please try again later! }; enum StreamCond { BadFormat, BadNamespacePrefix, Conflict, ConnectionTimeout, HostGone, HostUnknown, ImproperAddressing, InternalServerError, InvalidFrom, InvalidNamespace, InvalidXml, StreamNotAuthorized, PolicyViolation, RemoteConnectionFailed, StreamReset, ResourceConstraint, RestrictedXml, SeeOtherHost, SystemShutdown, UndefinedCondition, UnsupportedEncoding, UnsupportedStanzaType, UnsupportedVersion, NotWellFormed }; enum BindCond { BindBadRequest, BindNotAllowed, BindConflict }; // extend the XmlProtocol enums enum Need { NSASLMechs = XmlProtocol::NCustom, // need SASL mechlist NStartTLS, // need to switch on TLS layer NCompress, // need to switch on compression layer NSASLFirst, // need SASL first step NSASLNext, // need SASL next step NSASLLayer, // need to switch on SASL layer NCustom = XmlProtocol::NCustom + 10 }; enum Event { EFeatures = XmlProtocol::ECustom, // breakpoint after features packet is received ESASLSuccess, // breakpoint after successful sasl auth EStanzaReady, // a stanza was received EStanzaSent, // a stanza was sent EReady, // stream is ready for stanza use EAck, // received SM ack response from server ECustom = XmlProtocol::ECustom + 10 }; enum Error { ErrProtocol = XmlProtocol::ErrCustom, // there was an error in the xmpp-core protocol exchange ErrStream, // , see errCond, errText, and errAppSpec for details ErrStartTLS, // server refused starttls ErrCompress, // server refused compression ErrAuth, // authorization error. errCond holds sasl condition (or numeric code for old-protocol) ErrBind, // server refused resource bind ErrCustom = XmlProtocol::ErrCustom + 10 }; BasicProtocol(); ~BasicProtocol(); void reset(); // for outgoing xml QDomDocument doc; // sasl-related QString saslMech() const; QByteArray saslStep() const; void setSASLMechList(const QStringList &list); void setSASLFirst(const QString &mech, const QByteArray &step); void setSASLNext(const QByteArray &step); void setSASLAuthed(); // send / recv void sendStanza(const QDomElement &e); void sendDirect(const QString &s); void sendWhitespace(); void clearSendQueue(); QDomElement recvStanza(); // shutdown void shutdown(); void shutdownWithError(int cond, const QString &otherHost = ""); // information QString to, from, id, lang; Version version; // error output int errCond; QString errText; QHash errLangText; QDomElement errAppSpec; QString otherHost; QByteArray spare; // filled with unprocessed data on NStartTLS and NSASLLayer bool isReady() const; enum { TypeElement, TypeStanza, TypeDirect, TypePing }; protected: static int stringToSASLCond(const QString &s); static int stringToStreamCond(const QString &s); static QString saslCondToString(int); static QString streamCondToString(int); void send(const QDomElement &e, bool clip = false); void sendUrgent(const QDomElement &e, bool clip = false); void sendStreamError(int cond, const QString &text = "", const QDomElement &appSpec = QDomElement()); void sendStreamError(const QString &text); // old-style bool errorAndClose(int cond, const QString &text = "", const QDomElement &appSpec = QDomElement()); bool error(int code); void delayErrorAndClose(int cond, const QString &text = "", const QDomElement &appSpec = QDomElement()); void delayError(int code); // reimplemented QDomElement docElement(); void handleDocOpen(const Parser::Event &pe); bool handleError(); bool handleCloseFinished(); bool doStep(const QDomElement &e); void itemWritten(int id, int size); virtual QString defaultNamespace(); virtual QStringList extraNamespaces(); // stringlist: prefix,uri,prefix,uri, [...] virtual void handleStreamOpen(const Parser::Event &pe); virtual bool doStep2(const QDomElement &e) = 0; void setReady(bool b); QString sasl_mech; QStringList sasl_mechlist; QByteArray sasl_step; bool sasl_authed; QDomElement stanzaToRecv; private: struct SASLCondEntry { const char *str; int cond; }; static SASLCondEntry saslCondTable[]; struct StreamCondEntry { const char *str; int cond; }; static StreamCondEntry streamCondTable[]; struct SendItem { QDomElement stanzaToSend; QString stringToSend; bool doWhitespace; }; QList sendList; bool doShutdown, delayedError, closeError, ready; int stanzasPending, stanzasWritten; void init(); void extractStreamError(const QDomElement &e); }; class CoreProtocol : public BasicProtocol { public: enum { NPassword = NCustom, // need password for old-mode EDBVerify = ECustom, // breakpoint after db:verify request ErrPlain = ErrCustom // server only supports plain, but allowPlain is false locally }; CoreProtocol(); ~CoreProtocol(); void reset(); void needTimer(int seconds); // reimplemented to do SM void sendStanza(const QDomElement &e); void startClientOut(const Jid &jid, bool oldOnly, bool tlsActive, bool doAuth, bool doCompression); void startServerOut(const QString &to); void startDialbackOut(const QString &to, const QString &from); void startDialbackVerifyOut(const QString &to, const QString &from, const QString &id, const QString &key); void startClientIn(const QString &id); void startServerIn(const QString &id); void setLang(const QString &s); void setAllowTLS(bool b); void setAllowBind(bool b); void setAllowPlain(bool b); // old-mode const Jid &jid() const; void setPassword(const QString &s); void setFrom(const QString &s); void setDialbackKey(const QString &s); // input QString user, host; // status bool old; StreamFeatures features; QList unhandledFeatures; QStringList hosts; // static QString xmlToString(const QDomElement &e, bool clip=false); StreamManagement sm; class DBItem { public: enum { ResultRequest, ResultGrant, VerifyRequest, VerifyGrant, Validated }; int type; Jid to, from; QString key, id; bool ok; }; private: enum Step { Start, Done, SendFeatures, GetRequest, HandleTLS, GetSASLResponse, IncHandleSASLSuccess, GetFeatures, // read features packet HandleFeatures, // act on features, by initiating tls, sasl, or bind GetTLSProceed, // read tls response GetCompressProceed, // read compression response GetSASLFirst, // perform sasl first step using provided data GetSASLChallenge, // read server sasl challenge GetSASLNext, // perform sasl next step using provided data HandleSASLSuccess, // handle what must be done after reporting sasl success GetBindResponse, // read bind response HandleAuthGet, // send old-protocol auth-get GetAuthGetResponse, // read auth-get response HandleAuthSet, // send old-protocol auth-set GetAuthSetResponse, // read auth-set response GetSMResponse // read SM init response }; QList dbrequests, dbpending, dbvalidated; bool server, dialback, dialback_verify; int step; bool digest; bool tls_started, sasl_started, compress_started; Jid jid_; bool oldOnly; bool allowPlain; bool doTLS, doAuth, doBinding, doCompress; QString password; QString dialback_id, dialback_key; QString self_from; void init(); static int getOldErrorCode(const QDomElement &e); bool loginComplete(); bool isValidStanza(const QDomElement &e) const; bool streamManagementHandleStanza(const QDomElement &e); bool grabPendingItem(const Jid &to, const Jid &from, int type, DBItem *item); bool normalStep(const QDomElement &e); bool dialbackStep(const QDomElement &e); bool needSMRequest(); // reimplemented bool stepAdvancesParser() const; bool stepRequiresElement() const; void stringSend(const QString &s); void stringRecv(const QString &s); QString defaultNamespace(); QStringList extraNamespaces(); void handleStreamOpen(const Parser::Event &pe); bool doStep2(const QDomElement &e); void elementSend(const QDomElement &e); void elementRecv(const QDomElement &e); }; } // namespace XMPP #endif // PROTOCOL_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-core/securestream.cpp000066400000000000000000000370221370065651000254200ustar00rootroot00000000000000/* * securestream.cpp - combines a ByteStream with TLS and SASL * Copyright (C) 2004 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ /* Note: SecureStream depends on the underlying security layers to signal plain-to-encrypted results immediately (as opposed to waiting for the event loop) so that the user cannot add/remove security layers during this conversion moment. QCA::TLS and QCA::SASL behave as expected, but future layers might not. */ #include "securestream.h" #include "compressionhandler.h" #ifdef USE_TLSHANDLER #include "xmpp.h" #endif #include #include #include //---------------------------------------------------------------------------- // LayerTracker //---------------------------------------------------------------------------- class LayerTracker { public: struct Item { int plain; int encoded; }; LayerTracker(); void reset(); void addPlain(int plain); void specifyEncoded(int encoded, int plain); int finished(int encoded); int p; QList list; }; LayerTracker::LayerTracker() { p = 0; } void LayerTracker::reset() { p = 0; list.clear(); } void LayerTracker::addPlain(int plain) { p += plain; } void LayerTracker::specifyEncoded(int encoded, int plain) { // can't specify more bytes than we have if (plain > p) plain = p; p -= plain; Item i; i.plain = plain; i.encoded = encoded; list += i; } int LayerTracker::finished(int encoded) { int plain = 0; for (QList::Iterator it = list.begin(); it != list.end();) { Item &i = *it; // not enough? if (encoded < i.encoded) { i.encoded -= encoded; break; } encoded -= i.encoded; plain += i.plain; it = list.erase(it); } return plain; } //---------------------------------------------------------------------------- // SecureStream //---------------------------------------------------------------------------- class SecureLayer : public QObject { Q_OBJECT public: enum { TLS, SASL, TLSH, Compression }; int type; union { QCA::TLS * tls; QCA::SASL *sasl; #ifdef USE_TLSHANDLER XMPP::TLSHandler *tlsHandler; #endif CompressionHandler *compressionHandler; } p; LayerTracker layer; bool tls_done; int prebytes; SecureLayer(QCA::TLS *t) { type = TLS; p.tls = t; init(); connect(p.tls, SIGNAL(handshaken()), SLOT(tls_handshaken())); connect(p.tls, SIGNAL(readyRead()), SLOT(tls_readyRead())); connect(p.tls, SIGNAL(readyReadOutgoing(int)), SLOT(tls_readyReadOutgoing(int))); connect(p.tls, SIGNAL(closed()), SLOT(tls_closed())); connect(p.tls, SIGNAL(error(int)), SLOT(tls_error(int))); } SecureLayer(QCA::SASL *s) { type = SASL; p.sasl = s; init(); connect(p.sasl, SIGNAL(readyRead()), SLOT(sasl_readyRead())); connect(p.sasl, SIGNAL(readyReadOutgoing()), SLOT(sasl_readyReadOutgoing())); connect(p.sasl, SIGNAL(error()), SLOT(sasl_error())); } SecureLayer(CompressionHandler *t) { t->setParent(this); // automatically clean up CompressionHandler when SecureLayer is destroyed type = Compression; p.compressionHandler = t; init(); connect(p.compressionHandler, SIGNAL(readyRead()), SLOT(compressionHandler_readyRead())); connect(p.compressionHandler, SIGNAL(readyReadOutgoing()), SLOT(compressionHandler_readyReadOutgoing())); connect(p.compressionHandler, SIGNAL(error()), SLOT(compressionHandler_error())); } #ifdef USE_TLSHANDLER SecureLayer(XMPP::TLSHandler *t) { type = TLSH; p.tlsHandler = t; init(); connect(p.tlsHandler, SIGNAL(success()), SLOT(tlsHandler_success())); connect(p.tlsHandler, SIGNAL(fail()), SLOT(tlsHandler_fail())); connect(p.tlsHandler, SIGNAL(closed()), SLOT(tlsHandler_closed())); connect(p.tlsHandler, SIGNAL(readyRead(QByteArray)), SLOT(tlsHandler_readyRead(QByteArray))); connect(p.tlsHandler, SIGNAL(readyReadOutgoing(QByteArray, int)), SLOT(tlsHandler_readyReadOutgoing(QByteArray, int))); } #endif void init() { tls_done = false; prebytes = 0; } void write(const QByteArray &a) { layer.addPlain(a.size()); switch (type) { case TLS: { p.tls->write(a); break; } case SASL: { p.sasl->write(a); break; } #ifdef USE_TLSHANDLER case TLSH: { p.tlsHandler->write(a); break; } #endif case Compression: { p.compressionHandler->write(a); break; } } } void writeIncoming(const QByteArray &a) { switch (type) { case TLS: { p.tls->writeIncoming(a); break; } case SASL: { p.sasl->writeIncoming(a); break; } #ifdef USE_TLSHANDLER case TLSH: { p.tlsHandler->writeIncoming(a); break; } #endif case Compression: { p.compressionHandler->writeIncoming(a); break; } } } int finished(int plain) { int written = 0; // deal with prebytes (bytes sent prior to this security layer) if (prebytes > 0) { if (prebytes >= plain) { written += plain; prebytes -= plain; plain = 0; } else { written += prebytes; plain -= prebytes; prebytes = 0; } } // put remainder into the layer tracker if (type == SASL || tls_done) written += layer.finished(plain); return written; } signals: void tlsHandshaken(); void tlsClosed(const QByteArray &); void readyRead(const QByteArray &); void needWrite(const QByteArray &); void error(int); private slots: void tls_handshaken() { tls_done = true; tlsHandshaken(); } void tls_readyRead() { QByteArray a = p.tls->read(); readyRead(a); } void tls_readyReadOutgoing(int plainBytes) { QByteArray a = p.tls->readOutgoing(); if (tls_done) layer.specifyEncoded(a.size(), plainBytes); needWrite(a); } void tls_closed() { QByteArray a = p.tls->readUnprocessed(); tlsClosed(a); } void tls_error(int x) { error(x); } void sasl_readyRead() { QByteArray a = p.sasl->read(); readyRead(a); } void sasl_readyReadOutgoing() { int plainBytes; QByteArray a = p.sasl->readOutgoing(&plainBytes); layer.specifyEncoded(a.size(), plainBytes); needWrite(a); } void sasl_error() { error(p.sasl->errorCode()); } void compressionHandler_readyRead() { QByteArray a = p.compressionHandler->read(); readyRead(a); } void compressionHandler_readyReadOutgoing() { int plainBytes; QByteArray a = p.compressionHandler->readOutgoing(&plainBytes); layer.specifyEncoded(a.size(), plainBytes); needWrite(a); } void compressionHandler_error() { error(p.compressionHandler->errorCode()); } #ifdef USE_TLSHANDLER void tlsHandler_success() { tls_done = true; tlsHandshaken(); } void tlsHandler_fail() { error(0); } void tlsHandler_closed() { tlsClosed(QByteArray()); } void tlsHandler_readyRead(const QByteArray &a) { readyRead(a); } void tlsHandler_readyReadOutgoing(const QByteArray &a, int plainBytes) { if (tls_done) layer.specifyEncoded(a.size(), plainBytes); needWrite(a); } #endif }; #include "securestream.moc" class SecureStream::Private { public: ByteStream * bs; QList layers; int pending; int errorCode; bool active; bool topInProgress; bool haveTLS() const { for (SecureLayer *s : layers) { if (s->type == SecureLayer::TLS #ifdef USE_TLSHANDLER || s->type == SecureLayer::TLSH #endif ) { return true; } } return false; } bool haveSASL() const { for (SecureLayer *s : layers) { if (s->type == SecureLayer::SASL) return true; } return false; } bool haveCompress() const { for (SecureLayer *s : layers) { if (s->type == SecureLayer::Compression) return true; } return false; } void deleteLayers() { qDeleteAll(layers); layers.clear(); } }; SecureStream::SecureStream(ByteStream *s) : ByteStream(nullptr) { d = new Private; d->bs = s; connect(d->bs, SIGNAL(readyRead()), SLOT(bs_readyRead())); connect(d->bs, SIGNAL(bytesWritten(qint64)), SLOT(bs_bytesWritten(qint64))); d->pending = 0; d->active = true; d->topInProgress = false; setOpenMode(QIODevice::ReadWrite); } SecureStream::~SecureStream() { d->deleteLayers(); delete d; } void SecureStream::linkLayer(QObject *s) { connect(s, SIGNAL(tlsHandshaken()), SLOT(layer_tlsHandshaken())); connect(s, SIGNAL(tlsClosed(QByteArray)), SLOT(layer_tlsClosed(QByteArray))); connect(s, SIGNAL(readyRead(QByteArray)), SLOT(layer_readyRead(QByteArray))); connect(s, SIGNAL(needWrite(QByteArray)), SLOT(layer_needWrite(QByteArray))); connect(s, SIGNAL(error(int)), SLOT(layer_error(int))); } int SecureStream::calcPrebytes() const { int x = 0; for (SecureLayer *s : d->layers) { x += s->prebytes; } return (d->pending - x); } void SecureStream::startTLSClient(QCA::TLS *t, const QByteArray &spare) { if (!d->active || d->topInProgress || d->haveTLS()) return; SecureLayer *s = new SecureLayer(t); s->prebytes = calcPrebytes(); linkLayer(s); d->layers.append(s); d->topInProgress = true; insertData(spare); } void SecureStream::startTLSServer(QCA::TLS *t, const QByteArray &spare) { if (!d->active || d->topInProgress || d->haveTLS()) return; SecureLayer *s = new SecureLayer(t); s->prebytes = calcPrebytes(); linkLayer(s); d->layers.append(s); d->topInProgress = true; insertData(spare); } void SecureStream::setLayerCompress(const QByteArray &spare) { if (!d->active || d->topInProgress || d->haveCompress()) return; SecureLayer *s = new SecureLayer(new CompressionHandler()); s->prebytes = calcPrebytes(); linkLayer(s); d->layers.append(s); insertData(spare); } void SecureStream::setLayerSASL(QCA::SASL *sasl, const QByteArray &spare) { if (!d->active || d->topInProgress || d->haveSASL()) return; SecureLayer *s = new SecureLayer(sasl); s->prebytes = calcPrebytes(); linkLayer(s); d->layers.append(s); insertData(spare); } #ifdef USE_TLSHANDLER void SecureStream::startTLSClient(XMPP::TLSHandler *t, const QString &server, const QByteArray &spare) { if (!d->active || d->topInProgress || d->haveTLS()) return; SecureLayer *s = new SecureLayer(t); s->prebytes = calcPrebytes(); linkLayer(s); d->layers.append(s); d->topInProgress = true; // unlike QCA::TLS, XMPP::TLSHandler has no return value s->p.tlsHandler->startClient(server); insertData(spare); } #endif void SecureStream::closeTLS() { if (!d->layers.isEmpty()) { SecureLayer *s = d->layers.last(); if (s->type == SecureLayer::TLS) { s->p.tls->close(); } } } int SecureStream::errorCode() const { return d->errorCode; } bool SecureStream::isOpen() const { return d->active; } void SecureStream::write(const QByteArray &a) { if (!isOpen()) { qDebug("Writing to closed stream!"); return; } d->pending += a.size(); // send to the last layer if (!d->layers.isEmpty()) { SecureLayer *s = d->layers.last(); s->write(a); } else { writeRawData(a); } } qint64 SecureStream::bytesToWrite() const { return d->pending; } void SecureStream::bs_readyRead() { QByteArray a = d->bs->readAll(); // send to the first layer if (!d->layers.isEmpty()) { SecureLayer *s = d->layers.first(); s->writeIncoming(a); } else { incomingData(a); } } void SecureStream::bs_bytesWritten(qint64 bytes) { for (SecureLayer *s : d->layers) { bytes = s->finished(bytes); } if (bytes > 0) { d->pending -= bytes; bytesWritten(bytes); } } void SecureStream::layer_tlsHandshaken() { d->topInProgress = false; tlsHandshaken(); } void SecureStream::layer_tlsClosed(const QByteArray &) { setOpenMode(QIODevice::NotOpen); d->active = false; d->deleteLayers(); tlsClosed(); } void SecureStream::layer_readyRead(const QByteArray &a) { SecureLayer * s = static_cast(sender()); QList::Iterator it(d->layers.begin()); while ((*it) != s) { Q_ASSERT(it != d->layers.end()); ++it; } Q_ASSERT(it != d->layers.end()); // pass upwards ++it; if (it != d->layers.end()) { s = (*it); s->writeIncoming(a); } else { incomingData(a); } } void SecureStream::layer_needWrite(const QByteArray &a) { SecureLayer * s = static_cast(sender()); QList::Iterator it(d->layers.begin()); while ((*it) != s) { Q_ASSERT(it != d->layers.end()); ++it; } Q_ASSERT(it != d->layers.end()); // pass downwards if (it != d->layers.begin()) { --it; s = (*it); s->write(a); } else { writeRawData(a); } } void SecureStream::layer_error(int x) { SecureLayer *s = static_cast(sender()); int type = s->type; d->errorCode = x; setOpenMode(QIODevice::NotOpen); d->active = false; d->deleteLayers(); if (type == SecureLayer::TLS) setError(ErrTLS); else if (type == SecureLayer::SASL) setError(ErrSASL); #ifdef USE_TLSHANDLER else if (type == SecureLayer::TLSH) setError(ErrTLS); #endif } void SecureStream::insertData(const QByteArray &a) { if (!a.isEmpty()) { if (!d->layers.isEmpty()) { SecureLayer *s = d->layers.last(); s->writeIncoming(a); } else { incomingData(a); } } } void SecureStream::writeRawData(const QByteArray &a) { d->bs->write(a); } void SecureStream::incomingData(const QByteArray &a) { appendRead(a); if (bytesAvailable()) emit readyRead(); } psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-core/securestream.h000066400000000000000000000044711370065651000250670ustar00rootroot00000000000000/* * securestream.h - combines a ByteStream with TLS and SASL * Copyright (C) 2004 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef SECURESTREAM_H #define SECURESTREAM_H #include "bytestream.h" #include #define USE_TLSHANDLER #ifdef USE_TLSHANDLER namespace XMPP { class TLSHandler; } #endif class CompressionHandler; class SecureStream : public ByteStream { Q_OBJECT public: enum Error { ErrTLS = ErrCustom, ErrSASL }; SecureStream(ByteStream *s); ~SecureStream(); void startTLSClient(QCA::TLS *t, const QByteArray &spare = QByteArray()); void startTLSServer(QCA::TLS *t, const QByteArray &spare = QByteArray()); void setLayerCompress(const QByteArray &spare = QByteArray()); void setLayerSASL(QCA::SASL *s, const QByteArray &spare = QByteArray()); #ifdef USE_TLSHANDLER void startTLSClient(XMPP::TLSHandler *t, const QString &server, const QByteArray &spare = QByteArray()); #endif void closeTLS(); int errorCode() const; // reimplemented bool isOpen() const; void write(const QByteArray &); qint64 bytesToWrite() const; signals: void tlsHandshaken(); void tlsClosed(); private slots: void bs_readyRead(); void bs_bytesWritten(qint64); void layer_tlsHandshaken(); void layer_tlsClosed(const QByteArray &); void layer_readyRead(const QByteArray &); void layer_needWrite(const QByteArray &); void layer_error(int); private: void linkLayer(QObject *); int calcPrebytes() const; void insertData(const QByteArray &a); void writeRawData(const QByteArray &a); void incomingData(const QByteArray &a); class Private; Private *d; }; #endif // SECURESTREAM_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-core/simplesasl.cpp000066400000000000000000000331211370065651000250660ustar00rootroot00000000000000/* * simplesasl.cpp - Simple SASL implementation * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "simplesasl.h" #include "xmpp/base/randrandomnumbergenerator.h" #include "xmpp/sasl/digestmd5response.h" #include "xmpp/sasl/plainmessage.h" #include "xmpp/sasl/scramsha1message.h" #include "xmpp/sasl/scramsha1response.h" #include "xmpp/sasl/scramsha1signature.h" #include #include #include #include #include #include #include #include #include namespace XMPP { class SimpleSASLContext : public QCA::SASLContext { Q_OBJECT public: class ParamsMutable { public: /** User is held */ bool user; /** Authorization ID is held */ bool authzid; /** Password is held */ bool pass; /** Realm is held */ bool realm; }; // core props QString service, host; // state int step; bool capable; bool allow_plain; QByteArray out_buf, in_buf; QString mechanism_; QString out_mech; ParamsMutable need; ParamsMutable have; QString user, authz, realm; QCA::SecureArray pass; Result result_; QCA::SASL::AuthCondition authCondition_; QByteArray result_to_net_, result_to_app_; int encoded_; // scram specific stuff QByteArray client_first_message; QCA::SecureArray server_signature; SimpleSASLContext(QCA::Provider *p) : QCA::SASLContext(p) { reset(); } ~SimpleSASLContext() { reset(); } void reset() { resetState(); capable = true; allow_plain = false; need.user = false; need.authzid = false; need.pass = false; need.realm = false; have.user = false; have.authzid = false; have.pass = false; have.realm = false; user = QString(); authz = QString(); pass = QCA::SecureArray(); realm = QString(); } void resetState() { out_mech = QString(); out_buf.resize(0); authCondition_ = QCA::SASL::AuthFail; } virtual void setConstraints(QCA::SASL::AuthFlags flags, int ssfMin, int) { if (flags & (QCA::SASL::RequireForwardSecrecy | QCA::SASL::RequirePassCredentials | QCA::SASL::RequireMutualAuth) || ssfMin > 0) capable = false; else capable = true; allow_plain = flags & QCA::SASL::AllowPlain; } virtual void setup(const QString &_service, const QString &_host, const QCA::SASLContext::HostPort *, const QCA::SASLContext::HostPort *, const QString &, int) { service = _service; host = _host; } virtual void startClient(const QStringList &mechlist, bool allowClientSendFirst) { Q_UNUSED(allowClientSendFirst); mechanism_ = QString(); for (const QString &mech : mechlist) { if (mech == "SCRAM-SHA-1") { mechanism_ = "SCRAM-SHA-1"; break; } if (mech == "DIGEST-MD5") { mechanism_ = "DIGEST-MD5"; break; } if (mech == "PLAIN" && allow_plain) mechanism_ = "PLAIN"; } if (!capable || mechanism_.isEmpty()) { result_ = Error; authCondition_ = QCA::SASL::NoMechanism; if (!capable) qWarning("simplesasl.cpp: Not enough capabilities"); if (mechanism_.isEmpty()) qWarning("simplesasl.cpp: No mechanism available"); QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection); return; } resetState(); result_ = Continue; step = 0; tryAgain(); } virtual void nextStep(const QByteArray &from_net) { in_buf = from_net; tryAgain(); } virtual void tryAgain() { // All exits of the method must emit the ready signal // so all exits go through a goto ready; if (step == 0) { out_mech = mechanism_; // PLAIN if (out_mech == "PLAIN" || out_mech == "SCRAM-SHA-1") { // First, check if we have everything if (need.user || need.pass) { qWarning("simplesasl.cpp: Did not receive necessary auth parameters"); result_ = Error; goto ready; } if (!have.user) need.user = true; if (!have.pass) need.pass = true; if (need.user || need.pass) { result_ = Params; goto ready; } } if (out_mech == "PLAIN") { out_buf = PLAINMessage(authz, user, pass.toByteArray()).getValue(); } else if (out_mech == "SCRAM-SHA-1") { // send client-first-message SCRAMSHA1Message msg(authz, user, QByteArray(0, ' '), RandRandomNumberGenerator()); if (msg.isValid()) { out_buf = msg.getValue(); client_first_message = out_buf; } else { qWarning("simplesasl.cpp: SASLprep failed."); result_ = Error; goto ready; } } ++step; if (out_mech == "PLAIN") result_ = Success; else result_ = Continue; } else if (step == 1) { Q_ASSERT(out_mech != "PLAIN"); if (out_mech == "DIGEST-MD5") { // if we still need params, then the app has failed us! if (need.user || need.authzid || need.pass || need.realm) { qWarning("simplesasl.cpp: Did not receive necessary auth parameters"); result_ = Error; goto ready; } // see if some params are needed if (!have.user) need.user = true; // if(!have.authzid) // need.authzid = true; if (!have.pass) need.pass = true; if (need.user || need.authzid || need.pass) { result_ = Params; goto ready; } DIGESTMD5Response response(in_buf, service, host, realm, user, authz, pass.toByteArray(), RandRandomNumberGenerator()); if (!response.isValid()) { authCondition_ = QCA::SASL::BadProtocol; result_ = Error; goto ready; } out_buf = response.getValue(); ++step; result_ = Continue; } else if (out_mech == "SCRAM-SHA-1") { // if we still need params, then the app has failed us! if (need.user || need.pass) { qWarning("simplesasl.cpp: Did not receive necessary auth parameters"); result_ = Error; goto ready; } // see if some params are needed if (!have.user) need.user = true; // if(!have.authzid) // need.authzid = true; if (!have.pass) need.pass = true; if (need.user || need.pass) { result_ = Params; goto ready; } // parse server-first-message, send client-final-message QVariant prop = property("scram-salted-password-base64"); QString salted_password_base64; if (prop.isValid()) { salted_password_base64 = prop.toString(); } SCRAMSHA1Response response(in_buf, pass.toByteArray(), client_first_message, salted_password_base64, RandRandomNumberGenerator()); if (!response.isValid()) { authCondition_ = QCA::SASL::BadProtocol; result_ = Error; goto ready; } setProperty("scram-salted-password-base64", QVariant(response.getSaltedPassword())); server_signature = response.getServerSignature(); out_buf = response.getValue(); ++step; result_ = Continue; } } else if (step == 2 && out_mech == "SCRAM-SHA-1") { // verify the server's response on success, for SCRAM-SHA-1 SCRAMSHA1Signature sig(in_buf, server_signature); if (sig.isValid()) { result_ = Success; } else { qWarning() << "ServerSignature doesn't match the one we've calculated."; authCondition_ = QCA::SASL::AuthFail; result_ = Error; goto ready; } } /*else if (step == 2) { //Commenting this out is Justin's fix for updated QCA. out_buf.resize(0); result_ = Continue; ++step; }*/ else { out_buf.resize(0); result_ = Success; } ready: QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection); } virtual void update(const QByteArray &from_net, const QByteArray &from_app) { result_to_app_ = from_net; result_to_net_ = from_app; encoded_ = from_app.size(); result_ = Success; QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection); } virtual bool waitForResultsReady(int msecs) { // TODO: for now, all operations block anyway Q_UNUSED(msecs); return true; } virtual Result result() const { return result_; } virtual QStringList mechlist() const { return QStringList(); } virtual QString mech() const { return out_mech; } virtual bool haveClientInit() const { return out_mech == "PLAIN"; } virtual QByteArray stepData() const { return out_buf; } virtual QByteArray to_net() { return result_to_net_; } virtual int encoded() const { return encoded_; } virtual QByteArray to_app() { return result_to_app_; } virtual int ssf() const { return 0; } virtual QCA::SASL::AuthCondition authCondition() const { return authCondition_; } virtual QCA::SASL::Params clientParams() const { return QCA::SASL::Params(need.user, need.authzid, need.pass, need.realm); } virtual void setClientParams(const QString *_user, const QString *_authzid, const QCA::SecureArray *_pass, const QString *_realm) { if (_user) { user = *_user; need.user = false; have.user = true; } if (_authzid) { authz = *_authzid; need.authzid = false; have.authzid = true; } if (_pass) { pass = *_pass; need.pass = false; have.pass = true; } if (_realm) { realm = *_realm; need.realm = false; have.realm = true; } } virtual QStringList realmlist() const { // TODO return QStringList(); } virtual QString username() const { return QString(); } virtual QString authzid() const { return QString(); } virtual QCA::Provider::Context *clone() const { SimpleSASLContext *s = new SimpleSASLContext(provider()); // TODO: Copy all the members return s; } virtual void startServer(const QString &, bool) { result_ = QCA::SASLContext::Error; QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection); } virtual void serverFirstStep(const QString &, const QByteArray *) { result_ = QCA::SASLContext::Error; QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection); } }; class QCASimpleSASL : public QCA::Provider { public: QCASimpleSASL() { } ~QCASimpleSASL() { } void init() { } QString name() const { return "simplesasl"; } QStringList features() const { return QStringList("sasl"); } QCA::Provider::Context *createContext(const QString &cap) { if (cap == "sasl") return new SimpleSASLContext(this); return nullptr; } int qcaVersion() const { return QCA_VERSION; } }; QCA::Provider *createProviderSimpleSASL() { return (new QCASimpleSASL); } } // namespace XMPP #include "simplesasl.moc" psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-core/simplesasl.h000066400000000000000000000016441370065651000245400ustar00rootroot00000000000000/* * simplesasl.h - Simple SASL implementation * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef SIMPLESASL_H #define SIMPLESASL_H namespace QCA { class Provider; } namespace XMPP { QCA::Provider *createProviderSimpleSASL(); } #endif // SIMPLESASL_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-core/sm.cpp000066400000000000000000000117551370065651000233420ustar00rootroot00000000000000/* * sm.cpp - XMPP Stream Management protocol * Copyright (C) 2016 Aleksey Andreev * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "sm.h" #ifdef IRIS_SM_DEBUG #include #endif using namespace XMPP; SMState::SMState() { enabled = false; resumption_id.clear(); resumption_location.host.clear(); resumption_location.port = 0; resetCounters(); } void SMState::resetCounters() { received_count = 0; server_last_handled = 0; send_queue.clear(); } StreamManagement::StreamManagement(QObject *parent) : QObject(parent), sm_started(false), sm_resumed(false), sm_stanzas_notify(0), sm_resend_pos(0) { } void StreamManagement::reset() { sm_started = false; sm_resumed = false; sm_stanzas_notify = 0; sm_resend_pos = 0; sm_timeout_data.elapsed_timer = QElapsedTimer(); sm_timeout_data.waiting_answer = false; } void StreamManagement::start(const QString &resumption_id) { reset(); state_.resetCounters(); state_.resumption_id = resumption_id; sm_started = true; sm_timeout_data.elapsed_timer.start(); } void StreamManagement::resume(quint32 last_handled) { sm_resumed = true; sm_resend_pos = 0; processAcknowledgement(last_handled); sm_timeout_data.waiting_answer = false; sm_timeout_data.elapsed_timer.start(); } void StreamManagement::setLocation(const QString &host, int port) { state_.resumption_location.host = host; state_.resumption_location.port = port; } int StreamManagement::lastAckElapsed() const { if (!sm_timeout_data.elapsed_timer.isValid()) return 0; int msecs = sm_timeout_data.elapsed_timer.elapsed(); int secs = msecs / 1000; if (msecs % 1000 != 0) ++secs; return secs; } int StreamManagement::takeAckedCount() { int cnt = sm_stanzas_notify; sm_stanzas_notify = 0; return cnt; } void StreamManagement::countInputRawData(int bytes) { if (sm_timeout_data.waiting_answer) { if (bytes > 2) // '\r' and '\n' sm_timeout_data.elapsed_timer.start(); } } QDomElement StreamManagement::getUnacknowledgedStanza() { if (sm_resend_pos < state_.send_queue.size()) return state_.send_queue.at(sm_resend_pos++); return QDomElement(); } int StreamManagement::addUnacknowledgedStanza(const QDomElement &e) { state_.send_queue.enqueue(e); int len = state_.send_queue.length(); #ifdef IRIS_SM_DEBUG qDebug() << "Stream Management: [INF] Send queue length is changed: " << len; #endif return len; } void StreamManagement::processAcknowledgement(quint32 last_handled) { sm_timeout_data.waiting_answer = false; sm_timeout_data.elapsed_timer.start(); #ifdef IRIS_SM_DEBUG bool f = false; #endif while (!state_.send_queue.isEmpty() && state_.server_last_handled != last_handled) { state_.send_queue.dequeue(); ++state_.server_last_handled; ++sm_stanzas_notify; #ifdef IRIS_SM_DEBUG f = true; #endif } #ifdef IRIS_SM_DEBUG if (f) { qDebug() << "Stream Management: [INF] Send queue length is changed: " << state_.send_queue.length(); if (state_.send_queue.isEmpty() && last_handled != state_.server_last_handled) qDebug() << "Stream Management: [ERR] Send queue is empty but last_handled != server_last_handled " << last_handled << state_.server_last_handled; } #endif } void StreamManagement::markStanzaHandled() { ++state_.received_count; #ifdef IRIS_SM_DEBUG qDebug() << "Stream Management: [INF] current received id: " << state_.received_count; #endif } QDomElement StreamManagement::generateRequestStanza(QDomDocument &doc) { if (!sm_timeout_data.waiting_answer) { #ifdef IRIS_SM_DEBUG qDebug() << "Stream Management: [?->] Sending request of acknowledgment to server"; #endif sm_timeout_data.waiting_answer = true; sm_timeout_data.elapsed_timer.start(); return doc.createElementNS(NS_STREAM_MANAGEMENT, "r"); } return QDomElement(); } QDomElement StreamManagement::makeResponseStanza(QDomDocument &doc) { #ifdef IRIS_SM_DEBUG qDebug() << "Stream Management: [-->] Sending acknowledgment with h =" << state_.received_count; #endif QDomElement e = doc.createElementNS(NS_STREAM_MANAGEMENT, "a"); e.setAttribute("h", state_.received_count); return e; } psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-core/sm.h000066400000000000000000000057111370065651000230020ustar00rootroot00000000000000/* * sm.h - XMPP Stream Management protocol * Copyright (C) 2016 Aleksey Andreev * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_SM_H #define XMPP_SM_H #include #include #include #include #define NS_STREAM_MANAGEMENT "urn:xmpp:sm:3" #define SM_TIMER_INTERVAL_SECS 40 //#define IRIS_SM_DEBUG namespace XMPP { class SMState { public: SMState(); void resetCounters(); bool isResumption() const { return !resumption_id.isEmpty(); } bool isEnabled() const { return enabled; } bool isLocationValid() { return !resumption_location.host.isEmpty() && resumption_location.port != 0; } void setEnabled(bool e) { enabled = e; } public: bool enabled; quint32 received_count; quint32 server_last_handled; QQueue send_queue; QString resumption_id; struct { QString host; quint16 port; } resumption_location; }; class StreamManagement : QObject { public: StreamManagement(QObject *parent = nullptr); XMPP::SMState & state() { return state_; } const XMPP::SMState &state() const { return state_; } bool isActive() const { return sm_started || sm_resumed; } bool isResumed() const { return sm_resumed; } void reset(); void start(const QString &resumption_id); void resume(quint32 last_handled); void setLocation(const QString &host, int port); int lastAckElapsed() const; int takeAckedCount(); void countInputRawData(int bytes); QDomElement getUnacknowledgedStanza(); int addUnacknowledgedStanza(const QDomElement &e); void processAcknowledgement(quint32 last_handled); void markStanzaHandled(); QDomElement generateRequestStanza(QDomDocument &doc); QDomElement makeResponseStanza(QDomDocument &doc); private: SMState state_; bool sm_started; bool sm_resumed; int sm_stanzas_notify; int sm_resend_pos; struct { QElapsedTimer elapsed_timer; bool waiting_answer = false; } sm_timeout_data; }; } // namespace XMPP #endif // XMPP_SM_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-core/stream.cpp000066400000000000000000001277341370065651000242230ustar00rootroot00000000000000/* * stream.cpp - handles a client stream * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ /* Notes: - For Non-SASL auth (XEP-0078), username and resource fields are required. TODO: - sasl needParams is totally jacked? PLAIN requires authzid, etc - server error handling - reply with protocol errors if the client send something wrong - don't necessarily disconnect on protocol error. prepare for more. - server function - deal with stream 'to' attribute dynamically - flag tls/sasl/binding support dynamically (have the ability to specify extra stream:features) - inform the caller about the user authentication information - sasl security settings - resource-binding interaction - timeouts - allow exchanges of non-standard stanzas - send even if we close prematurely? - ensure ClientStream and child classes are fully deletable after signals - xml:lang in root () element - sasl external - sasl anonymous */ #include "bytestream.h" #ifndef NO_IRISNET #include "irisnetglobal_p.h" #endif #include "protocol.h" #include "securestream.h" #include "simplesasl.h" #ifdef XMPP_TEST #include "td.h" #endif #include #include #include #include #include #include #include //#include #include //#define XMPP_DEBUG using namespace XMPP; static Debug *debug_ptr = nullptr; void XMPP::setDebug(Debug *p) { debug_ptr = p; } static QByteArray randomArray(int size) { QByteArray a; a.resize(size); for (int n = 0; n < size; ++n) a[n] = (char)(256.0 * rand() / (RAND_MAX + 1.0)); return a; } static QString genId() { // need SHA1 here // if(!QCA::isSupported(QCA::CAP_SHA1)) // QCA::insertProvider(createProviderHash()); return QCA::Hash("sha1").hashToString(randomArray(128)); } //---------------------------------------------------------------------------- // Stream //---------------------------------------------------------------------------- static XmlProtocol *foo = nullptr; Stream::Stream(QObject *parent) : QObject(parent) { } Stream::~Stream() { } Stanza Stream::createStanza(Stanza::Kind k, const Jid &to, const QString &type, const QString &id) { return Stanza(this, k, to, type, id); } Stanza Stream::createStanza(const QDomElement &e) { return Stanza(this, e); } QString Stream::xmlToString(const QDomElement &e, bool clip) { if (!foo) { foo = new CoreProtocol; #ifndef NO_IRISNET irisNetAddPostRoutine(cleanup); #endif } return foo->elementToString(e, clip); } void Stream::cleanup() { delete foo; foo = nullptr; } //---------------------------------------------------------------------------- // ClientStream //---------------------------------------------------------------------------- enum { Idle, Connecting, WaitVersion, WaitTLS, NeedParams, AuthAbort, Active, Closing }; enum { Client, Server }; class ClientStream::Private { public: Private() = default; void reset() { state = Idle; notify = 0; newStanzas = false; sasl_ssf = 0; tls_warned = false; using_tls = false; } Jid jid; QString server; bool oldOnly = false; bool mutualAuth = false; AllowPlainType allowPlain = NoAllowPlain; bool haveLocalAddr = false; QHostAddress localAddr; quint16 localPort; QString connectHost; int minimumSSF = 0; int maximumSSF = 0; QString sasl_mech; QMap mechProviders; // mech to provider map bool doBinding = true; bool in_rrsig = false; Connector * conn = nullptr; ByteStream * bs = nullptr; TLSHandler * tlsHandler = nullptr; QCA::TLS * tls = nullptr; QCA::SASL * sasl = nullptr; SecureStream *ss = nullptr; CoreProtocol client; CoreProtocol srv; QString lang; QString defRealm; int mode; int state = Idle; int notify = 0; bool newStanzas = false; int sasl_ssf = 0; bool tls_warned = false; bool using_tls; bool doAuth; bool doCompress = false; QStringList sasl_mechlist; int errCond; QString errText; QHash errLangText; // xml:lang => error text QDomElement errAppSpec; QList in; QTimer timeout_timer; QTimer noopTimer; int noop_time; bool quiet_reconnection = false; }; ClientStream::ClientStream(Connector *conn, TLSHandler *tlsHandler, QObject *parent) : Stream(parent) { d = new Private; d->mode = Client; d->conn = conn; connect(d->conn, SIGNAL(connected()), SLOT(cr_connected())); connect(d->conn, SIGNAL(error()), SLOT(cr_error())); d->noop_time = 0; connect(&d->noopTimer, SIGNAL(timeout()), SLOT(doNoop())); d->tlsHandler = tlsHandler; } ClientStream::ClientStream(const QString &host, const QString &defRealm, ByteStream *bs, QCA::TLS *tls, QObject *parent) : Stream(parent) { d = new Private; d->mode = Server; d->bs = bs; connect(d->bs, SIGNAL(connectionClosed()), SLOT(bs_connectionClosed())); connect(d->bs, SIGNAL(delayedCloseFinished()), SLOT(bs_delayedCloseFinished())); connect(d->bs, SIGNAL(error(int)), SLOT(bs_error(int))); QByteArray spare = d->bs->readAll(); d->ss = new SecureStream(d->bs); connect(d->ss, SIGNAL(readyRead()), SLOT(ss_readyRead())); connect(d->ss, SIGNAL(bytesWritten(qint64)), SLOT(ss_bytesWritten(qint64))); connect(d->ss, SIGNAL(tlsHandshaken()), SLOT(ss_tlsHandshaken())); connect(d->ss, SIGNAL(tlsClosed()), SLOT(ss_tlsClosed())); connect(d->ss, SIGNAL(error(int)), SLOT(ss_error(int))); d->server = host; d->defRealm = defRealm; d->tls = tls; d->srv.startClientIn(genId()); // d->srv.startServerIn(genId()); // d->state = Connecting; // d->jid = Jid(); // d->server = QString(); connect(&(d->timeout_timer), SIGNAL(timeout()), SLOT(sm_timeout())); } ClientStream::~ClientStream() { // fprintf(stderr, "\tClientStream::~ClientStream\n"); // fflush(stderr); reset(); delete d; } void ClientStream::reset(bool all) { // fprintf(stderr, "\tClientStream::reset\n"); // fflush(stderr); d->reset(); d->noopTimer.stop(); // delete securestream delete d->ss; d->ss = nullptr; // reset sasl delete d->sasl; d->sasl = nullptr; if (all) { while (!d->in.isEmpty()) { delete d->in.takeFirst(); } } else { QSharedPointer sd; for (Stanza *s : d->in) { sd = s->unboundDocument(sd); } } // client if (d->mode == Client) { // reset tls // FIXME: Temporarily disabled // if(d->tlsHandler) // d->tlsHandler->reset(); // reset connector if (d->bs) { d->bs->close(); d->bs = nullptr; } d->conn->done(); // reset state machine d->client.reset(); } // server else { if (d->tls) d->tls->reset(); if (d->bs) { d->bs->close(); d->bs = nullptr; } d->srv.reset(); } } Jid ClientStream::jid() const { return d->jid; } void ClientStream::connectToServer(const Jid &jid, bool auth) { reset(true); d->state = Connecting; d->jid = jid; d->doAuth = auth; d->server = d->jid.domain(); d->conn->connectToServer(d->server); } void ClientStream::continueAfterWarning() { if (d->state == WaitVersion) { // if we don't have TLS yet, then we're never going to get it if (!d->tls_warned && !d->using_tls) { d->tls_warned = true; d->state = WaitTLS; warning(WarnNoTLS); return; } d->state = Connecting; processNext(); } else if (d->state == WaitTLS) { d->state = Connecting; processNext(); } } void ClientStream::accept() { d->srv.host = d->server; processNext(); } bool ClientStream::isActive() const { return (d->state != Idle) ? true : false; } bool ClientStream::isAuthenticated() const { return (d->state == Active) ? true : false; } void ClientStream::setUsername(const QString &s) { if (d->sasl) d->sasl->setUsername(s); } void ClientStream::setPassword(const QString &s) { if (d->client.old) { d->client.setPassword(s); } else { if (d->sasl) d->sasl->setPassword(QCA::SecureArray(s.toUtf8())); } } void ClientStream::setRealm(const QString &s) { if (d->sasl) d->sasl->setRealm(s); } void ClientStream::setAuthzid(const QString &s) { if (d->sasl) d->sasl->setAuthzid(s); } void ClientStream::continueAfterParams() { if (d->state == NeedParams) { d->state = Connecting; if (d->client.old) { processNext(); } else { if (d->sasl) d->sasl->continueAfterParams(); } } else if (d->state == AuthAbort) { auto e = doc().createElementNS(NS_SASL, "abort"); d->client.sendStanza(e); processNext(); } } void ClientStream::abortAuth() { if (d->state != NeedParams) { return; // nothing to abort } d->state = AuthAbort; } void ClientStream::setSaslMechanismProvider(const QString &m, const QString &p) { d->mechProviders.insert(m, p); } QString ClientStream::saslMechanismProvider(const QString &m) const { return d->mechProviders.value(m); } QCA::Provider::Context *ClientStream::currentSASLContext() const { if (d->sasl) { return d->sasl->context(); } return nullptr; } void ClientStream::setSCRAMStoredSaltedHash(const QString &s) { QCA::SASLContext *context = (QCA::SASLContext *)(d->sasl->context()); if (context) { context->setProperty("scram-salted-password-base64", s); } } const QString ClientStream::getSCRAMStoredSaltedHash() { QCA::SASLContext *context = (QCA::SASLContext *)(d->sasl->context()); if (context) { return context->property("scram-salted-password-base64").toString(); } return QString(); } void ClientStream::setResourceBinding(bool b) { d->doBinding = b; } void ClientStream::setLang(const QString &lang) { d->lang = lang; } void ClientStream::setNoopTime(int mills) { d->noop_time = mills; if (d->state != Active) return; if (d->noop_time == 0) { d->noopTimer.stop(); return; } d->noopTimer.start(d->noop_time); } QString ClientStream::saslMechanism() const { return d->client.saslMech(); } int ClientStream::saslSSF() const { return d->sasl_ssf; } void ClientStream::setSASLMechanism(const QString &s) { d->sasl_mech = s; } void ClientStream::setLocalAddr(const QHostAddress &addr, quint16 port) { d->haveLocalAddr = true; d->localAddr = addr; d->localPort = port; } void ClientStream::setCompress(bool compress) { d->doCompress = compress; } int ClientStream::errorCondition() const { return d->errCond; } QString ClientStream::errorText() const { return d->errText; } QHash ClientStream::errorLangText() const { return d->errLangText; } QDomElement ClientStream::errorAppSpec() const { return d->errAppSpec; } bool ClientStream::old() const { return d->client.old; } void ClientStream::close() { if (d->state == Active) { d->state = Closing; d->client.shutdown(); processNext(); } else if (d->state != Idle && d->state != Closing) { reset(); } } QDomDocument &ClientStream::doc() const { return d->client.doc; } QString ClientStream::baseNS() const { return NS_CLIENT; } void ClientStream::setAllowPlain(AllowPlainType a) { d->allowPlain = a; } void ClientStream::setRequireMutualAuth(bool b) { d->mutualAuth = b; } void ClientStream::setSSFRange(int low, int high) { d->minimumSSF = low; d->maximumSSF = high; } void ClientStream::setOldOnly(bool b) { d->oldOnly = b; } bool ClientStream::stanzaAvailable() const { return (!d->in.isEmpty()); } Stanza ClientStream::read() { if (d->in.isEmpty()) return Stanza(); else { Stanza *sp = d->in.takeFirst(); Stanza s = *sp; delete sp; return s; } } void ClientStream::write(const Stanza &s) { if (d->state == Active) { d->client.sendStanza(s.element()); processNext(); } } void ClientStream::clearSendQueue() { d->client.clearSendQueue(); } void ClientStream::cr_connected() { d->connectHost = d->conn->host(); d->bs = d->conn->stream(); connect(d->bs, SIGNAL(connectionClosed()), SLOT(bs_connectionClosed())); connect(d->bs, SIGNAL(delayedCloseFinished()), SLOT(bs_delayedCloseFinished())); QByteArray spare = d->bs->readAll(); d->ss = new SecureStream(d->bs); connect(d->ss, SIGNAL(readyRead()), SLOT(ss_readyRead())); connect(d->ss, SIGNAL(bytesWritten(qint64)), SLOT(ss_bytesWritten(qint64))); connect(d->ss, SIGNAL(tlsHandshaken()), SLOT(ss_tlsHandshaken())); connect(d->ss, SIGNAL(tlsClosed()), SLOT(ss_tlsClosed())); connect(d->ss, SIGNAL(error(int)), SLOT(ss_error(int))); // d->client.startDialbackOut("andbit.net", "im.pyxa.org"); // d->client.startServerOut(d->server); d->client.startClientOut(d->jid, d->oldOnly, d->conn->useSSL(), d->doAuth, d->doCompress); d->client.setAllowTLS(d->tlsHandler ? true : false); d->client.setAllowBind(d->doBinding); d->client.setAllowPlain(d->allowPlain == AllowPlain || (d->allowPlain == AllowPlainOverTLS && d->conn->useSSL())); d->client.setLang(d->lang); /*d->client.jid = d->jid; d->client.server = d->server; d->client.allowPlain = d->allowPlain; d->client.oldOnly = d->oldOnly; d->client.sasl_mech = d->sasl_mech; d->client.doTLS = d->tlsHandler ? true: false; d->client.doBinding = d->doBinding;*/ QPointer self = this; if (!d->quiet_reconnection) emit connected(); if (!self) return; // immediate SSL? if (d->conn->useSSL()) { d->using_tls = true; d->ss->startTLSClient(d->tlsHandler, d->server, spare); } else { d->client.addIncomingData(spare); processNext(); } } void ClientStream::cr_error() { reset(); error(ErrConnection); } void ClientStream::bs_connectionClosed() { reset(); connectionClosed(); } void ClientStream::bs_delayedCloseFinished() { // we don't care about this (we track all important data ourself) } void ClientStream::bs_error(int) { // TODO } void ClientStream::ss_readyRead() { QByteArray a = d->ss->readAll(); #ifdef XMPP_DEBUG qDebug("ClientStream: recv: %d [%s]\n", a.size(), a.data()); #endif if (d->mode == Client) { d->client.addIncomingData(a); d->client.sm.countInputRawData(a.size()); } else { d->srv.addIncomingData(a); d->srv.sm.countInputRawData(a.size()); } if (d->notify & CoreProtocol::NRecv) { #ifdef XMPP_DEBUG qDebug("We needed data, so let's process it\n"); #endif processNext(); } } void ClientStream::ss_bytesWritten(qint64 bytes) { if (d->mode == Client) d->client.outgoingDataWritten(bytes); else d->srv.outgoingDataWritten(bytes); if (d->notify & CoreProtocol::NSend) { #ifdef XMPP_DEBUG qDebug("We were waiting for data to be written, so let's process\n"); #endif processNext(); } } void ClientStream::ss_tlsHandshaken() { QPointer self = this; if (!d->quiet_reconnection) securityLayerActivated(LayerTLS); if (!self) return; d->client.setAllowPlain(d->allowPlain == AllowPlain || d->allowPlain == AllowPlainOverTLS); processNext(); } void ClientStream::ss_tlsClosed() { reset(); connectionClosed(); } void ClientStream::ss_error(int x) { if (x == SecureStream::ErrTLS) { reset(); d->errCond = TLSFail; error(ErrTLS); } else { reset(); error(ErrSecurityLayer); } } void ClientStream::sasl_clientFirstStep(bool, const QByteArray &ba) { d->client.setSASLFirst(d->sasl->mechanism(), ba); // d->client.sasl_mech = mech; // d->client.sasl_firstStep = stepData ? true : false; // d->client.sasl_step = stepData ? *stepData : QByteArray(); processNext(); } void ClientStream::sasl_nextStep(const QByteArray &stepData) { if (d->mode == Client) d->client.setSASLNext(stepData); // d->client.sasl_step = stepData; else d->srv.setSASLNext(stepData); // d->srv.sasl_step = stepData; processNext(); } void ClientStream::sasl_needParams(const QCA::SASL::Params &p) { #ifdef XMPP_DEBUG qDebug("need params: needUsername: %d, canSendAuthzid: %d, needPassword: %d, canSendRealm: %d\n", p.needUsername() ? 1 : 0, p.canSendAuthzid() ? 1 : 0, p.needPassword() ? 1 : 0, p.canSendRealm() ? 1 : 0); #endif /*if(p.authzid && !p.user) { d->sasl->setAuthzid(d->jid.bare()); //d->sasl->setAuthzid("infiniti.homelesshackers.org"); }*/ if (p.needUsername() || p.needPassword() || p.canSendRealm()) { d->state = NeedParams; needAuthParams(p.needUsername(), p.needPassword(), p.canSendRealm()); } else d->sasl->continueAfterParams(); } void ClientStream::sasl_authCheck(const QString &user, const QString &) { //#ifdef XMPP_DEBUG // qDebug("authcheck: [%s], [%s]\n", user.latin1(), authzid.latin1()); //#endif QString u = user; int n = u.indexOf('@'); if (n != -1) u.truncate(n); d->srv.user = u; d->sasl->continueAfterAuthCheck(); } void ClientStream::sasl_authenticated() { #ifdef XMPP_DEBUG qDebug("sasl authed!!\n"); #endif d->sasl_ssf = d->sasl->ssf(); if (d->mode == Server) { d->srv.setSASLAuthed(); processNext(); } } void ClientStream::sasl_error() { #ifdef XMPP_DEBUG qDebug("sasl error: %d\n", d->sasl->authCondition()); #endif // has to be auth error int x = convertedSASLCond(); d->errText = tr("Offered mechanisms: ") + d->client.features.sasl_mechs.join(", "); reset(); d->errCond = x; error(ErrAuth); } void ClientStream::srvProcessNext() { while (1) { qDebug("Processing step...\n"); if (!d->srv.processStep()) { int need = d->srv.need; if (need == CoreProtocol::NNotify) { d->notify = d->srv.notify; if (d->notify & CoreProtocol::NSend) qDebug("More data needs to be written to process next step\n"); if (d->notify & CoreProtocol::NRecv) qDebug("More data is needed to process next step\n"); } else if (need == CoreProtocol::NSASLMechs) { if (!d->sasl) { d->sasl = new QCA::SASL; connect(d->sasl, SIGNAL(authCheck(QString, QString)), SLOT(sasl_authCheck(QString, QString))); connect(d->sasl, SIGNAL(nextStep(QByteArray)), SLOT(sasl_nextStep(QByteArray))); connect(d->sasl, SIGNAL(authenticated()), SLOT(sasl_authenticated())); connect(d->sasl, SIGNAL(error()), SLOT(sasl_error())); // d->sasl->setAllowAnonymous(false); // d->sasl->setRequirePassCredentials(true); // d->sasl->setExternalAuthID("localhost"); QCA::SASL::AuthFlags auth_flags = (QCA::SASL::AuthFlags)0; d->sasl->setConstraints(auth_flags, 0, 256); QStringList list; // TODO: d->server is probably wrong here d->sasl->startServer("xmpp", d->server, d->defRealm, QCA::SASL::AllowServerSendLast); d->sasl_mechlist = list; } d->srv.setSASLMechList(d->sasl_mechlist); continue; } else if (need == CoreProtocol::NStartTLS) { qDebug("Need StartTLS\n"); // if(!d->tls->startServer()) { d->tls->startServer(); QByteArray a = d->srv.spare; d->ss->startTLSServer(d->tls, a); } else if (need == CoreProtocol::NSASLFirst) { qDebug("Need SASL First Step\n"); QByteArray a = d->srv.saslStep(); d->sasl->putServerFirstStep(d->srv.saslMech(), a); } else if (need == CoreProtocol::NSASLNext) { qDebug("Need SASL Next Step\n"); QByteArray a = d->srv.saslStep(); qDebug("[%s]\n", a.data()); d->sasl->putStep(a); } else if (need == CoreProtocol::NSASLLayer) { } // now we can announce stanzas // if(!d->in.isEmpty()) // readyRead(); return; } d->notify = 0; int event = d->srv.event; qDebug("event: %d\n", event); switch (event) { case CoreProtocol::EError: { qDebug("Error! Code=%d\n", d->srv.errorCode); reset(); error(ErrProtocol); // handleError(); return; } case CoreProtocol::ESend: { while (true) { QByteArray a = d->srv.takeOutgoingData(); if (a.isEmpty()) break; #ifdef XMPP_DEBUG qDebug("Need Send: {%s}\n", a.data()); #endif d->ss->write(a); } break; } case CoreProtocol::ERecvOpen: { qDebug("Break (RecvOpen)\n"); // calculate key QByteArray str = QCA::Hash("sha1").hashToString("secret").toUtf8(); str = QCA::Hash("sha1").hashToString(QByteArray(str + "im.pyxa.org")).toUtf8(); str = QCA::Hash("sha1").hashToString(QByteArray(str + d->srv.id.toUtf8())).toUtf8(); d->srv.setDialbackKey(str); // d->srv.setDialbackKey("3c5d721ea2fcc45b163a11420e4e358f87e3142a"); if (d->srv.to != d->server) { // host-gone, host-unknown, see-other-host d->srv.shutdownWithError(CoreProtocol::HostUnknown); } else d->srv.setFrom(d->server); break; } case CoreProtocol::ESASLSuccess: { qDebug("Break SASL Success\n"); disconnect(d->sasl, SIGNAL(error()), this, SLOT(sasl_error())); QByteArray a = d->srv.spare; d->ss->setLayerSASL(d->sasl, a); break; } case CoreProtocol::EPeerClosed: { // TODO: this isn' an error qDebug("peer closed\n"); reset(); error(ErrProtocol); return; } } } } void ClientStream::doReadyRead() { // QGuardedPtr self = this; if (isActive()) emit readyRead(); // if(!self) // return; // d->in_rrsig = false; } void ClientStream::processNext() { if (d->mode == Server) { srvProcessNext(); return; } QPointer self = this; while (1) { #ifdef XMPP_DEBUG qDebug("Processing step...\n"); #endif bool ok = d->client.processStep(); // deal with send/received items for (const XmlProtocol::TransferItem &i : d->client.transferItemList) { if (i.isExternal) continue; QString str; if (i.isString) { // skip whitespace pings if (i.str.trimmed().isEmpty()) continue; str = i.str; } else str = d->client.elementToString(i.elem); if (i.isSent) emit outgoingXml(str); else emit incomingXml(str); } #ifdef XMPP_DEBUG qDebug("\tNOTIFY: %d\n", d->client.notify); #endif if (d->client.notify & CoreProtocol::NTimeout) { #ifdef XMPP_DEBUG qDebug() << "Time = " << d->client.timeout_sec; #endif setTimer(d->client.timeout_sec); #ifdef XMPP_DEBUG qDebug() << "\tNTimeout received | Start timer"; #endif } if (!ok) { bool cont = handleNeed(); // now we can announce stanzas // if(!d->in_rrsig && !d->in.isEmpty()) { if (!d->in.isEmpty()) { // d->in_rrsig = true; // fprintf(stderr, "\tClientStream::processNext() QTimer::singleShot\n"); // fflush(stderr); QTimer::singleShot(0, this, SLOT(doReadyRead())); } if (cont) continue; return; } int event = d->client.event; d->notify = 0; switch (event) { case CoreProtocol::EError: { #ifdef XMPP_DEBUG qDebug("Error! Code=%d\n", d->client.errorCode); #endif handleError(); return; } case CoreProtocol::ESend: { while (true) { QByteArray a = d->client.takeOutgoingData(); if (a.isEmpty()) break; #ifdef XMPP_DEBUG qDebug("Need Send: {%s}\n", a.data()); #endif d->ss->write(a); } break; } case CoreProtocol::ERecvOpen: { #ifdef XMPP_DEBUG qDebug("Break (RecvOpen)\n"); #endif #ifdef XMPP_TEST QString s = QString("handshake success (lang=[%1]").arg(d->client.lang); if (!d->client.from.isEmpty()) s += QString(", from=[%1]").arg(d->client.from); s += ')'; TD::msg(s); #endif if (d->client.old) { d->state = WaitVersion; warning(WarnOldVersion); return; } break; } case CoreProtocol::EFeatures: { #ifdef XMPP_DEBUG qDebug("Break (Features)\n"); #endif if (d->client.unhandledFeatures.count()) { emit haveUnhandledFeatures(); } if (!d->tls_warned && !d->using_tls && !d->client.features.tls_supported) { d->tls_warned = true; d->state = WaitTLS; warning(WarnNoTLS); return; } break; } case CoreProtocol::ESASLSuccess: { #ifdef XMPP_DEBUG qDebug("Break SASL Success\n"); #endif break; } case CoreProtocol::EReady: { #ifdef XMPP_DEBUG qDebug("Done!\n"); #endif // grab the JID, in case it changed d->jid = d->client.jid(); d->state = Active; setNoopTime(d->noop_time); if (!d->quiet_reconnection) authenticated(); if (!self) return; break; } case CoreProtocol::EPeerClosed: { #ifdef XMPP_DEBUG qDebug("DocumentClosed\n"); #endif reset(); connectionClosed(); return; } case CoreProtocol::EStanzaReady: { #ifdef XMPP_DEBUG qDebug("StanzaReady\n"); #endif // store the stanza for now, announce after processing all events // TODO: add a method to the stanza to mark them handled. Stanza s = createStanza(d->client.recvStanza()); if (s.isNull()) break; if (d->client.sm.isActive()) d->client.sm.markStanzaHandled(); d->in.append(new Stanza(s)); break; } case CoreProtocol::EStanzaSent: { #ifdef XMPP_DEBUG qDebug("StanzasSent\n"); #endif stanzaWritten(); if (!self) return; break; } case CoreProtocol::EClosed: { #ifdef XMPP_DEBUG qDebug("Closed\n"); #endif reset(); delayedCloseFinished(); return; } case CoreProtocol::EAck: { int ack_cnt = d->client.sm.takeAckedCount(); #ifdef XMPP_DEBUG qDebug() << "Stream Management: [INF] Received ack amount: " << ack_cnt; #endif emit stanzasAcked(ack_cnt); break; } case CoreProtocol::ESMConnTimeout: { #ifdef XMPP_DEBUG qDebug() << "Stream Management: [INF] Connection timeout"; #endif reset(); if (d->client.sm.state().isResumption()) { d->state = Connecting; emit warning(WarnSMReconnection); d->quiet_reconnection = true; if (d->client.sm.state().isLocationValid()) d->conn->setOptHostPort(d->client.sm.state().resumption_location.host, d->client.sm.state().resumption_location.port); d->conn->connectToServer(d->server); } else { d->quiet_reconnection = false; emit connectionClosed(); } return; } case CoreProtocol::ESMResumeFailed: { #ifdef XMPP_DEBUG qDebug() << "Stream Management: [INF] Resuming session failed"; #endif reset(); d->quiet_reconnection = false; emit error(ErrSmResume); return; } } } } bool ClientStream::handleNeed() { int need = d->client.need; if (need == CoreProtocol::NNotify) { d->notify = d->client.notify; #ifdef XMPP_DEBUG if (d->notify & CoreProtocol::NSend) qDebug("More data needs to be written to process next step\n"); if (d->notify & CoreProtocol::NRecv) qDebug("More data is needed to process next step\n"); #endif return false; } d->notify = 0; switch (need) { case CoreProtocol::NStartTLS: { #ifdef XMPP_DEBUG qDebug("Need StartTLS\n"); #endif d->using_tls = true; d->ss->startTLSClient(d->tlsHandler, d->server, d->client.spare); return false; } case CoreProtocol::NCompress: { #ifdef XMPP_DEBUG qDebug("Need compress\n"); #endif d->ss->setLayerCompress(d->client.spare); return true; } case CoreProtocol::NSASLFirst: { #ifdef XMPP_DEBUG qDebug("Need SASL First Step\n"); #endif // ensure simplesasl provider is installed bool found = false; for (QCA::Provider *p : QCA::providers()) { if (p->name() == "simplesasl") { found = true; break; } } if (!found) { // install with low-priority QCA::insertProvider(createProviderSimpleSASL()); QCA::setProviderPriority("simplesasl", 10); } static QStringList preference { "GSSAPI", "SCRAM-SHA-512-PLUS", "SCRAM-SHA-512", "SCRAM-SHA-384-PLUS", "SCRAM-SHA-384", "SCRAM-SHA-256-PLUS", "SCRAM-SHA-256", "SCRAM-SHA-1-PLUS", "SCRAM-SHA-1", "DIGEST-MD5", "PLAIN" }; // TODO qca should maintain the list of preferred QStringList ml; if (!d->sasl_mech.isEmpty()) ml += d->sasl_mech; else { QMap prefOrdered; QStringList unpreferred; for (auto const &m : d->client.features.sasl_mechs) { int i = preference.indexOf(m); if (i != -1) { prefOrdered.insert(i, m); } else { unpreferred.append(m); } } ml = prefOrdered.values() + unpreferred; } QString saslProvider; for (const QString &mech : d->mechProviders.keys()) { if (ml.contains(mech)) { saslProvider = d->mechProviders[mech]; break; } } d->sasl = new QCA::SASL(nullptr, saslProvider); connect(d->sasl, SIGNAL(clientStarted(bool, QByteArray)), SLOT(sasl_clientFirstStep(bool, QByteArray))); connect(d->sasl, SIGNAL(nextStep(QByteArray)), SLOT(sasl_nextStep(QByteArray))); connect(d->sasl, SIGNAL(needParams(QCA::SASL::Params)), SLOT(sasl_needParams(QCA::SASL::Params))); connect(d->sasl, SIGNAL(authenticated()), SLOT(sasl_authenticated())); connect(d->sasl, SIGNAL(error()), SLOT(sasl_error())); if (d->haveLocalAddr) d->sasl->setLocalAddress(d->localAddr.toString(), d->localPort); if (d->conn->havePeerAddress()) d->sasl->setRemoteAddress(d->conn->peerAddress().toString(), d->conn->peerPort()); // d->sasl_mech = "ANONYMOUS"; // d->sasl->setRequirePassCredentials(true); // d->sasl->setExternalAuthID("localhost"); // d->sasl->setExternalSSF(64); // d->sasl_mech = "EXTERNAL"; QCA::SASL::AuthFlags auth_flags = (QCA::SASL::AuthFlags)0; if (d->allowPlain == AllowPlain || (d->allowPlain == AllowPlainOverTLS && d->using_tls)) auth_flags = (QCA::SASL::AuthFlags)(auth_flags | QCA::SASL::AllowPlain); if (d->mutualAuth) auth_flags = (QCA::SASL::AuthFlags)(auth_flags | QCA::SASL::RequireMutualAuth); d->sasl->setConstraints(auth_flags, d->minimumSSF, d->maximumSSF); #ifdef IRIS_SASLCONNECTHOST d->sasl->startClient("xmpp", QUrl::toAce(d->connectHost), ml, QCA::SASL::AllowClientSendFirst); #else d->sasl->startClient("xmpp", QUrl::toAce(d->server), ml, QCA::SASL::AllowClientSendFirst); #endif return false; } case CoreProtocol::NSASLNext: { #ifdef XMPP_DEBUG qDebug("Need SASL Next Step\n"); #endif QByteArray a = d->client.saslStep(); d->sasl->putStep(a); return false; } case CoreProtocol::NSASLLayer: { // SecureStream will handle the errors from this point disconnect(d->sasl, SIGNAL(error()), this, SLOT(sasl_error())); d->ss->setLayerSASL(d->sasl, d->client.spare); if (d->sasl_ssf > 0) { QPointer self = this; if (!d->quiet_reconnection) securityLayerActivated(LayerSASL); if (!self) return false; } break; } case CoreProtocol::NPassword: { #ifdef XMPP_DEBUG qDebug("Need Password\n"); #endif d->state = NeedParams; needAuthParams(false, true, false); return false; } } return true; } int ClientStream::convertedSASLCond() const { int x = d->sasl->authCondition(); if (x == QCA::SASL::NoMechanism) return NoMech; else if (x == QCA::SASL::BadProtocol) return MalformedRequest; else if (x == QCA::SASL::BadServer) return BadServ; else if (x == QCA::SASL::TooWeak) return MechTooWeak; else return GenericAuthError; return 0; } void ClientStream::sm_timeout() { #ifdef XMPP_DEBUG printf("ClientStream::sm_timeout()\n"); #endif int elapsed = d->client.sm.lastAckElapsed(); if (elapsed < d->client.timeout_sec) { setTimer(d->client.timeout_sec - elapsed); } else { d->client.timeout_sec = 0; processNext(); } } void ClientStream::doNoop() { if (d->state == Active) { #ifdef XMPP_DEBUG qDebug("doPing\n"); #endif d->client.sendWhitespace(); processNext(); } } void ClientStream::writeDirect(const QString &s) { if (d->state == Active) { #ifdef XMPP_DEBUG qDebug("writeDirect\n"); #endif d->client.sendDirect(s); processNext(); } } void ClientStream::handleError() { int c = d->client.errorCode; if (c == CoreProtocol::ErrParse) { reset(); error(ErrParse); } else if (c == CoreProtocol::ErrProtocol) { reset(); error(ErrProtocol); } else if (c == CoreProtocol::ErrStream) { int x = d->client.errCond; QString text = d->client.errText; auto langText = d->client.errLangText; QDomElement appSpec = d->client.errAppSpec; int connErr = -1; int strErr = -1; switch (x) { case CoreProtocol::BadFormat: { break; } // should NOT happen (we send the right format) case CoreProtocol::BadNamespacePrefix: { break; } // should NOT happen (we send prefixes) case CoreProtocol::Conflict: { strErr = Conflict; break; } case CoreProtocol::ConnectionTimeout: { strErr = ConnectionTimeout; break; } case CoreProtocol::HostGone: { connErr = HostGone; break; } case CoreProtocol::HostUnknown: { connErr = HostUnknown; break; } case CoreProtocol::ImproperAddressing: { break; } // should NOT happen (we aren't a server) case CoreProtocol::InternalServerError: { strErr = InternalServerError; break; } case CoreProtocol::InvalidFrom: { strErr = InvalidFrom; break; } case CoreProtocol::InvalidNamespace: { break; } // should NOT happen (we set the right ns) case CoreProtocol::InvalidXml: { strErr = InvalidXml; break; } // shouldn't happen either, but just in case ... case CoreProtocol::StreamNotAuthorized: { break; } // should NOT happen (we're not stupid) case CoreProtocol::PolicyViolation: { strErr = PolicyViolation; break; } case CoreProtocol::RemoteConnectionFailed: { connErr = RemoteConnectionFailed; break; } case CoreProtocol::StreamReset: { strErr = StreamReset; break; } case CoreProtocol::ResourceConstraint: { strErr = ResourceConstraint; break; } case CoreProtocol::RestrictedXml: { strErr = InvalidXml; break; } // group with this one case CoreProtocol::SeeOtherHost: { connErr = SeeOtherHost; break; } case CoreProtocol::SystemShutdown: { strErr = SystemShutdown; break; } case CoreProtocol::UndefinedCondition: { break; } // leave as null error case CoreProtocol::UnsupportedEncoding: { break; } // should NOT happen (we send good encoding) case CoreProtocol::UnsupportedStanzaType: { break; } // should NOT happen (we're not stupid) case CoreProtocol::UnsupportedVersion: { connErr = UnsupportedVersion; break; } case CoreProtocol::NotWellFormed: { strErr = InvalidXml; break; } // group with this one default: { break; } } reset(); d->errText = text; d->errLangText = langText; d->errAppSpec = appSpec; if (connErr != -1) { d->errCond = connErr; error(ErrNeg); } else { if (strErr != -1) d->errCond = strErr; else d->errCond = GenericStreamError; error(ErrStream); } } else if (c == CoreProtocol::ErrStartTLS) { reset(); d->errCond = TLSStart; error(ErrTLS); } else if (c == CoreProtocol::ErrAuth) { int x = d->client.errCond; int r = GenericAuthError; if (d->client.old) { if (x == 401) // not authorized r = NotAuthorized; else if (x == 409) // conflict r = GenericAuthError; else if (x == 406) // not acceptable (this should NOT happen) r = GenericAuthError; } else { switch (x) { case CoreProtocol::Aborted: { r = GenericAuthError; break; } // should NOT happen (we never send ) case CoreProtocol::AccountDisabled: { r = AccountDisabled; break; } // account temporrily disabled case CoreProtocol::CredentialsExpired: { r = CredentialsExpired; break; } // credential expired case CoreProtocol::EncryptionRequired: { r = EncryptionRequired; break; } // can't use mech without TLS case CoreProtocol::IncorrectEncoding: { r = GenericAuthError; break; } // should NOT happen case CoreProtocol::InvalidAuthzid: { r = InvalidAuthzid; break; } case CoreProtocol::InvalidMech: { r = InvalidMech; break; } case CoreProtocol::MalformedRequest: { r = MalformedRequest; break; } case CoreProtocol::MechTooWeak: { r = MechTooWeak; break; } case CoreProtocol::NotAuthorized: { r = NotAuthorized; break; } case CoreProtocol::TemporaryAuthFailure: { r = TemporaryAuthFailure; break; } } } reset(); d->errCond = r; d->errLangText = d->client.errLangText; error(ErrAuth); } else if (c == CoreProtocol::ErrPlain) { reset(); d->errCond = NoMech; error(ErrAuth); } else if (c == CoreProtocol::ErrBind) { int r = -1; if (d->client.errCond == CoreProtocol::BindBadRequest) { // should NOT happen } else if (d->client.errCond == CoreProtocol::BindNotAllowed) { r = BindNotAllowed; } else if (d->client.errCond == CoreProtocol::BindConflict) { r = BindConflict; } if (r != -1) { reset(); d->errCond = r; error(ErrBind); } else { reset(); error(ErrProtocol); } } } bool ClientStream::isResumed() const { return d->client.sm.isResumed(); } void ClientStream::setSMEnabled(bool e) { d->client.sm.state().setEnabled(e); } void ClientStream::setTimer(int secs) { d->timeout_timer.setSingleShot(true); d->timeout_timer.start(secs * 1000); d->client.notify &= ~CoreProtocol::NTimeout; } QStringList ClientStream::hosts() const { return d->client.hosts; } const StreamFeatures &ClientStream::streamFeatures() const { return d->client.features; } QList ClientStream::unhandledFeatures() const { return d->client.unhandledFeatures; } //---------------------------------------------------------------------------- // Debug //---------------------------------------------------------------------------- Debug::~Debug() { } #ifdef XMPP_TEST TD::TD() { } TD::~TD() { } void TD::msg(const QString &s) { if (debug_ptr) debug_ptr->msg(s); } void TD::outgoingTag(const QString &s) { if (debug_ptr) debug_ptr->outgoingTag(s); } void TD::incomingTag(const QString &s) { if (debug_ptr) debug_ptr->incomingTag(s); } void TD::outgoingXml(const QDomElement &e) { if (debug_ptr) debug_ptr->outgoingXml(e); } void TD::incomingXml(const QDomElement &e) { if (debug_ptr) debug_ptr->incomingXml(e); } #endif psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-core/td.h000066400000000000000000000005451370065651000227720ustar00rootroot00000000000000#ifndef TESTDEBUG_H #define TESTDEBUG_H #include class TD { public: TD(); ~TD(); static void msg(const QString &); static void outgoingTag(const QString &); static void incomingTag(const QString &); static void outgoingXml(const QDomElement &); static void incomingXml(const QDomElement &); }; #endif // TESTDEBUG_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-core/tlshandler.cpp000066400000000000000000000307311370065651000250560ustar00rootroot00000000000000/* * tlshandler.cpp - abstract wrapper for TLS * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "qca.h" #include "xmpp.h" #include #include using namespace XMPP; // FIXME: remove this code once qca cert host checking works ... using namespace QCA; // ip address string to binary (msb), adapted from jdns (adapted from qt) // return: size 4 = ipv4, size 16 = ipv6, size 0 = error static QByteArray ipaddr_str2bin(const QString &str) { // ipv6 if (str.contains(':')) { #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) QStringList parts = str.split(':', Qt::KeepEmptyParts); #else QStringList parts = str.split(':', QString::KeepEmptyParts); #endif if (parts.count() < 3 || parts.count() > 8) return QByteArray(); QByteArray ipv6(16, 0); int at = 16; int fill = 9 - parts.count(); for (int n = parts.count() - 1; n >= 0; --n) { if (at <= 0) return QByteArray(); if (parts[n].isEmpty()) { if (n == parts.count() - 1) { if (!parts[n - 1].isEmpty()) return QByteArray(); ipv6[--at] = 0; ipv6[--at] = 0; } else if (n == 0) { if (!parts[n + 1].isEmpty()) return QByteArray(); ipv6[--at] = 0; ipv6[--at] = 0; } else { for (int i = 0; i < fill; ++i) { if (at <= 0) return QByteArray(); ipv6[--at] = 0; ipv6[--at] = 0; } } } else { if (parts[n].indexOf('.') == -1) { bool ok; int x = parts[n].toInt(&ok, 16); if (!ok || x < 0 || x > 0xffff) return QByteArray(); ipv6[--at] = x & 0xff; ipv6[--at] = (x >> 8) & 0xff; } else { if (n != parts.count() - 1) return QByteArray(); QByteArray buf = ipaddr_str2bin(parts[n]); if (buf.isEmpty()) return QByteArray(); ipv6[--at] = buf[3]; ipv6[--at] = buf[2]; ipv6[--at] = buf[1]; ipv6[--at] = buf[0]; --fill; } } } return ipv6; } else if (str.contains('.')) { #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) QStringList parts = str.split('.', Qt::KeepEmptyParts); #else QStringList parts = str.split('.', QString::KeepEmptyParts); #endif if (parts.count() != 4) return QByteArray(); QByteArray out(4, 0); for (int n = 0; n < 4; ++n) { bool ok; int x = parts[n].toInt(&ok); if (!ok || x < 0 || x > 0xff) return QByteArray(); out[n] = (unsigned char)x; } return out; } else return QByteArray(); } // acedomain must be all lowercase, with no trailing dot or wildcards static bool cert_match_domain(const QString &certname, const QString &acedomain) { // KSSL strips start/end whitespace, even though such whitespace is // probably not legal anyway. (compat) QString name = certname.trimmed(); // KSSL strips trailing dot, even though the dot is probably not // legal anyway. (compat) if (name.length() > 0 && name[name.length() - 1] == '.') name.truncate(name.length() - 1); // after our compatibility modifications, make sure the name isn't // empty. if (name.isEmpty()) return false; // lowercase, for later performing case insensitive matching name = name.toLower(); // ensure the cert field contains valid characters only if (QRegExp("[^a-z0-9\\.\\*\\-]").indexIn(name) >= 0) return false; // hack into parts, and require at least 1 part #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) QStringList parts_name = name.split('.', Qt::KeepEmptyParts); #else QStringList parts_name = name.split('.', QString::KeepEmptyParts); #endif if (parts_name.isEmpty()) return false; // KSSL checks to make sure the last two parts don't contain // wildcards. I don't know where it is written that this // should be done, but for compat sake we'll do it. if (parts_name[parts_name.count() - 1].contains('*')) return false; if (parts_name.count() >= 2 && parts_name[parts_name.count() - 2].contains('*')) return false; #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) QStringList parts_compare = acedomain.split('.', Qt::KeepEmptyParts); #else QStringList parts_compare = acedomain.split('.', QString::KeepEmptyParts); #endif if (parts_compare.isEmpty()) return false; // don't allow empty parts for (const QString &s : parts_name) { if (s.isEmpty()) return false; } for (const QString &s : parts_compare) { if (s.isEmpty()) return false; } // RFC2818: "Names may contain the wildcard character * which is // considered to match any single domain name component or // component fragment. E.g., *.a.com matches foo.a.com but not // bar.foo.a.com. f*.com matches foo.com but not bar.com." // // This means that for the domain to match it must have the // same number of components, wildcards or not. If there are // wildcards, their scope must only be within the component // they reside in. // // First, make sure the number of parts is equal. if (parts_name.count() != parts_compare.count()) return false; // Now compare each part for (int n = 0; n < parts_name.count(); ++n) { const QString &p1 = parts_name[n]; const QString &p2 = parts_compare[n]; if (!QRegExp(p1, Qt::CaseSensitive, QRegExp::Wildcard).exactMatch(p2)) return false; } return true; } // ipaddress must be an ipv4 or ipv6 address in binary format static bool cert_match_ipaddress(const QString &certname, const QByteArray &ipaddress) { // KSSL strips start/end whitespace, even though such whitespace is // probably not legal anyway. (compat) QString name = certname.trimmed(); // KSSL accepts IPv6 in brackets, which is usually done for URIs, but // IMO sounds very strange for a certificate. We'll follow this // behavior anyway. (compat) if (name.length() >= 2 && name[0] == '[' && name[name.length() - 1] == ']') name = name.mid(1, name.length() - 2); // chop off brackets // after our compatibility modifications, make sure the name isn't // empty. if (name.isEmpty()) return false; // convert to binary form QByteArray addr = ipaddr_str2bin(name); if (addr.isEmpty()) return false; // not the same? if (addr != ipaddress) return false; return true; } static bool matchesHostName(const QCA::Certificate &cert, const QString &host) { QByteArray ipaddr = ipaddr_str2bin(host); if (!ipaddr.isEmpty()) // ip address { // check iPAddress const auto ipAddresses = cert.subjectInfo().values(IPAddress); for (const QString &s : ipAddresses) { if (cert_match_ipaddress(s, ipaddr)) return true; } // check dNSName const auto dnsNames = cert.subjectInfo().values(DNS); for (const QString &s : dnsNames) { if (cert_match_ipaddress(s, ipaddr)) return true; } // check commonName const auto commonNames = cert.subjectInfo().values(CommonName); for (const QString &s : commonNames) { if (cert_match_ipaddress(s, ipaddr)) return true; } } else // domain { // lowercase QString name = host.toLower(); // ACE name = QString::fromLatin1(QUrl::toAce(name)); // don't allow wildcards in the comparison host if (name.contains('*')) return false; // strip out trailing dot if (name.length() > 0 && name[name.length() - 1] == '.') name.truncate(name.length() - 1); // make sure the name is not empty after our modifications if (name.isEmpty()) return false; // check dNSName const auto dnsNames = cert.subjectInfo().values(DNS); for (const QString &s : dnsNames) { if (cert_match_domain(s, name)) return true; } // check commonName const auto commonNames = cert.subjectInfo().values(CommonName); for (const QString &s : commonNames) { if (cert_match_domain(s, name)) return true; } } return false; } //---------------------------------------------------------------------------- // TLSHandler //---------------------------------------------------------------------------- TLSHandler::TLSHandler(QObject *parent) : QObject(parent) { } TLSHandler::~TLSHandler() { } //---------------------------------------------------------------------------- // QCATLSHandler //---------------------------------------------------------------------------- class QCATLSHandler::Private { public: QCA::TLS *tls; int state, err; QString host; bool internalHostMatch; }; QCATLSHandler::QCATLSHandler(QCA::TLS *parent) : TLSHandler(parent) { d = new Private; d->tls = parent; connect(d->tls, SIGNAL(handshaken()), SLOT(tls_handshaken())); connect(d->tls, SIGNAL(readyRead()), SLOT(tls_readyRead())); connect(d->tls, SIGNAL(readyReadOutgoing()), SLOT(tls_readyReadOutgoing())); connect(d->tls, SIGNAL(closed()), SLOT(tls_closed())); connect(d->tls, SIGNAL(error()), SLOT(tls_error())); d->state = 0; d->err = -1; d->internalHostMatch = false; } QCATLSHandler::~QCATLSHandler() { delete d; } void QCATLSHandler::setXMPPCertCheck(bool enable) { d->internalHostMatch = enable; } bool QCATLSHandler::XMPPCertCheck() { return d->internalHostMatch; } bool QCATLSHandler::certMatchesHostname() { if (!d->internalHostMatch) return false; QCA::CertificateChain peerCert = d->tls->peerCertificateChain(); if (matchesHostName(peerCert.primary(), d->host)) return true; Jid host(d->host); const auto hosts = peerCert.primary().subjectInfo().values(QCA::XMPP); for (const QString &idOnXmppAddr : hosts) { if (host.compare(Jid(idOnXmppAddr))) return true; } return false; } QCA::TLS *QCATLSHandler::tls() const { return d->tls; } int QCATLSHandler::tlsError() const { return d->err; } void QCATLSHandler::reset() { d->tls->reset(); d->state = 0; } void QCATLSHandler::startClient(const QString &host) { d->state = 0; d->err = -1; if (d->internalHostMatch) d->host = host; d->tls->startClient(d->internalHostMatch ? QString() : host); } void QCATLSHandler::write(const QByteArray &a) { d->tls->write(a); } void QCATLSHandler::writeIncoming(const QByteArray &a) { d->tls->writeIncoming(a); } void QCATLSHandler::continueAfterHandshake() { if (d->state == 2) { d->tls->continueAfterStep(); success(); d->state = 3; } } void QCATLSHandler::tls_handshaken() { d->state = 2; tlsHandshaken(); } void QCATLSHandler::tls_readyRead() { readyRead(d->tls->read()); } void QCATLSHandler::tls_readyReadOutgoing() { int plainBytes; QByteArray buf = d->tls->readOutgoing(&plainBytes); readyReadOutgoing(buf, plainBytes); } void QCATLSHandler::tls_closed() { closed(); } void QCATLSHandler::tls_error() { d->err = d->tls->errorCode(); d->state = 0; fail(); } psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-core/xmlprotocol.cpp000066400000000000000000000432171370065651000253030ustar00rootroot00000000000000/* * xmlprotocol.cpp - state machine for 'xmpp-like' protocols * Copyright (C) 2004 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmlprotocol.h" #include "bytestream.h" #include #include #include using namespace XMPP; // stripExtraNS // // This function removes namespace information from various nodes for // display purposes only (the element is pretty much useless for processing // after this). We do this because QXml is a bit overzealous about outputting // redundant namespaces. static QDomElement stripExtraNS(const QDomElement &e) { // find closest parent with a namespace QDomNode par = e.parentNode(); while (!par.isNull() && par.namespaceURI().isNull()) par = par.parentNode(); bool noShowNS = false; if (!par.isNull() && par.namespaceURI() == e.namespaceURI()) noShowNS = true; // build qName (prefix:localName) QString qName; if (!e.prefix().isEmpty()) qName = e.prefix() + ':' + e.localName(); else qName = e.tagName(); QDomElement i; int x; if (noShowNS) i = e.ownerDocument().createElement(qName); else i = e.ownerDocument().createElementNS(e.namespaceURI(), qName); // copy attributes QDomNamedNodeMap al = e.attributes(); for (x = 0; x < al.count(); ++x) { QDomAttr a = al.item(x).cloneNode().toAttr(); // don't show xml namespace if (a.namespaceURI() == NS_XML) i.setAttribute(QString("xml:") + a.name(), a.value()); else i.setAttributeNodeNS(a); } // copy children QDomNodeList nl = e.childNodes(); for (x = 0; x < nl.count(); ++x) { QDomNode n = nl.item(x); if (n.isElement()) i.appendChild(stripExtraNS(n.toElement())); else i.appendChild(n.cloneNode()); } return i; } // xmlToString // // This function converts a QDomElement into a QString, using stripExtraNS // to make it pretty. static QString xmlToString(const QDomElement &e, const QString &fakeNS, const QString &fakeQName, bool clip) { QDomElement i = e.cloneNode().toElement(); // It seems QDom can only have one namespace attribute at a time (see docElement 'HACK'). // Fortunately we only need one kind depending on the input, so it is specified here. QDomElement fake = e.ownerDocument().createElementNS(fakeNS, fakeQName); fake.appendChild(i); fake = stripExtraNS(fake); QString out; { QTextStream ts(&out, QIODevice::WriteOnly); // NOTE: Workaround for bug in QtXML https://bugreports.qt.io/browse/QTBUG-25291 (Qt4 only): // Qt by default convert low surrogate to XML notation &#x....; and let high in binary! // // Qt is calling encode function per UTF-16 codepoint, which means that high and low // surrogate pairs are encoded separately. So all encoding except UTF-16 will leads // to damaged Unicode characters above 0xFFFF. Internal QString encoding is UTF-16 // so this should be safe as QString still contains valid Unicode characters. ts.setCodec("UTF-16"); fake.firstChild().save(ts, 0); } // 'clip' means to remove any unwanted (and unneeded) characters, such as a trailing newline if (clip) { int n = out.lastIndexOf('>'); out.truncate(n + 1); } return out; } // createRootXmlTags // // This function creates three QStrings, one being an processing // instruction, and the others being the opening and closing tags of an // element, and . This basically allows us to get the raw XML // text needed to open/close an XML stream, without resorting to generating // the XML ourselves. This function uses QDom to do the generation, which // ensures proper encoding and entity output. static void createRootXmlTags(const QDomElement &root, QString *xmlHeader, QString *tagOpen, QString *tagClose) { QDomElement e = root.cloneNode(false).toElement(); // insert a dummy element to ensure open and closing tags are generated QDomElement dummy = e.ownerDocument().createElement("dummy"); e.appendChild(dummy); // convert to xml->text QString str; { QTextStream ts(&str, QIODevice::WriteOnly); e.save(ts, 0); } // parse the tags out int n = str.indexOf('<'); int n2 = str.indexOf('>', n); ++n2; *tagOpen = str.mid(n, n2 - n); n2 = str.lastIndexOf('>'); n = str.lastIndexOf('<'); ++n2; *tagClose = str.mid(n, n2 - n); // generate a nice xml processing header *xmlHeader = ""; } // w3c xml spec: // [2] Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] static inline bool validChar(const quint32 ch) { return ch == 0x9 || ch == 0xA || ch == 0xD || (ch >= 0x20 && ch <= 0xD7FF) || (ch >= 0xE000 && ch <= 0xFFFD) || (ch >= 0x10000 && ch <= 0x10FFFF); } static inline bool lowSurrogate(const quint32 ch) { return ch >= 0xDC00 && ch <= 0xDFFF; } static inline bool highSurrogate(const quint32 ch) { return ch >= 0xD800 && ch <= 0xDBFF; } // force encoding of '>'. this function is needed for XMPP-Core, which // requires the '>' character to be encoded as ">" even though this is // not required by the XML spec. // Also remove chars that are ouside the allowed range for XML (see validChar) // and invalid surrogate pairs static QString sanitizeForStream(const QString &in) { QString out; bool intag = false; bool inquote = false; QChar quotechar; int inlength = in.length(); for (int n = 0; n < inlength; ++n) { QChar c = in[n]; bool escape = false; if (c == '<') { intag = true; } else if (c == '>') { if (inquote) { escape = true; } else if (!intag) { escape = true; } else { intag = false; } } else if (c == '\'' || c == '\"') { if (intag) { if (!inquote) { inquote = true; quotechar = c; } else { if (quotechar == c) { inquote = false; } } } } if (escape) { out += ">"; } else { // don't silently drop invalid chars in element or attribute names, // because that's something that should not happen. if (intag && (!inquote)) { out += c; } else if (validChar(c.unicode())) { out += c; } else if (highSurrogate(c.unicode()) && (n + 1 < inlength) && lowSurrogate(in[n + 1].unicode())) { // uint unicode = (c.unicode() & 0x3FF) << 10 | in[n+1].unicode() & 0x3FF + 0x10000; // we don't need to recheck this, because 0x10000 <= unicode <= 0x100000 is always true out += c; out += in[n + 1]; ++n; } else { qDebug("Dropping invalid XML char U+%04x", c.unicode()); } } } return out; } //---------------------------------------------------------------------------- // Protocol //---------------------------------------------------------------------------- XmlProtocol::TransferItem::TransferItem() { } XmlProtocol::TransferItem::TransferItem(const QString &_str, bool sent, bool external) : isSent(sent), isString(true), isExternal(external), str(_str) { } XmlProtocol::TransferItem::TransferItem(const QDomElement &_elem, bool sent, bool external) : isSent(sent), isString(false), isExternal(external), elem(_elem) { } XmlProtocol::XmlProtocol() { init(); } XmlProtocol::~XmlProtocol() { } void XmlProtocol::init() { incoming = false; peerClosed = false; closeWritten = false; } void XmlProtocol::reset() { init(); elem = QDomElement(); elemDoc = QDomDocument(); tagOpen = QString(); tagClose = QString(); xml.reset(); outDataNormal.resize(0); outDataUrgent.resize(0); trackQueueNormal.clear(); trackQueueUrgent.clear(); transferItemList.clear(); } void XmlProtocol::addIncomingData(const QByteArray &a) { xml.appendData(a); } QByteArray XmlProtocol::takeOutgoingData() { if (!outDataUrgent.isEmpty()) { QByteArray a = outDataUrgent; outDataUrgent.resize(0); return a; } QByteArray a = outDataNormal; outDataNormal.resize(0); return a; } void XmlProtocol::outgoingDataWritten(int bytes) { int b = processTrackQueue(trackQueueUrgent, bytes); if (b > 0) processTrackQueue(trackQueueNormal, b); } void XmlProtocol::clearSendQueue() { outDataUrgent.clear(); outDataNormal.clear(); } bool XmlProtocol::processStep() { Parser::Event pe; notify = 0; transferItemList.clear(); if (state != Closing && (state == RecvOpen || stepAdvancesParser())) { // if we get here, then it's because we're in some step that advances the parser pe = xml.readNext(); if (!pe.isNull()) { // note: error/close events should be handled for ALL steps, so do them here switch (pe.type()) { case Parser::Event::DocumentOpen: { transferItemList += TransferItem(pe.actualString(), false); // stringRecv(pe.actualString()); break; } case Parser::Event::DocumentClose: { transferItemList += TransferItem(pe.actualString(), false); // stringRecv(pe.actualString()); if (incoming) { sendTagClose(); event = ESend; peerClosed = true; state = Closing; } else { event = EPeerClosed; } return true; } case Parser::Event::Element: { QDomElement e = elemDoc.importNode(pe.element(), true).toElement(); transferItemList += TransferItem(e, false); // elementRecv(pe.element()); break; } case Parser::Event::Error: { if (incoming) { // If we get a parse error during the initial element exchange, // flip immediately into 'open' mode so that we can report an error. if (state == RecvOpen) { sendTagOpen(); state = Open; } return handleError(); } else { event = EError; errorCode = ErrParse; return true; } } } } else { if (state == RecvOpen || stepRequiresElement()) { need = NNotify; notify |= NRecv; return false; } } } return baseStep(pe); } QString XmlProtocol::xmlEncoding() const { return xml.encoding().toString(); } QString XmlProtocol::elementToString(const QDomElement &e, bool clip) { if (elem.isNull()) elem = elemDoc.importNode(docElement(), true).toElement(); // Determine the appropriate 'fakeNS' to use QString ns; // first, check root namespace QString pre = e.prefix(); if (pre.isNull()) pre = ""; if (pre == elem.prefix()) { ns = elem.namespaceURI(); } else { // scan the root attributes for 'xmlns' (oh joyous hacks) QDomNamedNodeMap al = elem.attributes(); int n; for (n = 0; n < al.count(); ++n) { QDomAttr a = al.item(n).toAttr(); QString s = a.name(); int x = s.indexOf(':'); if (x != -1) s = s.mid(x + 1); else s = ""; if (pre == s) { ns = a.value(); break; } } if (n >= al.count()) { // if we get here, then no appropriate ns was found. use root then.. ns = elem.namespaceURI(); } } // build qName QString qn; if (!elem.prefix().isEmpty()) qn = elem.prefix() + ':'; qn += elem.localName(); // make the string return sanitizeForStream(xmlToString(e, ns, qn, clip)); } bool XmlProtocol::stepRequiresElement() const { // default returns false return false; } void XmlProtocol::itemWritten(int, int) { // default does nothing } void XmlProtocol::stringSend(const QString &) { // default does nothing } void XmlProtocol::stringRecv(const QString &) { // default does nothing } void XmlProtocol::elementSend(const QDomElement &) { // default does nothing } void XmlProtocol::elementRecv(const QDomElement &) { // default does nothing } void XmlProtocol::startConnect() { incoming = false; state = SendOpen; } void XmlProtocol::startAccept() { incoming = true; state = RecvOpen; } bool XmlProtocol::close() { sendTagClose(); event = ESend; state = Closing; return true; } int XmlProtocol::writeString(const QString &s, int id, bool external) { transferItemList += TransferItem(s, true, external); return internalWriteString(s, TrackItem::Custom, id); } int XmlProtocol::writeElement(const QDomElement &e, int id, bool external, bool clip, bool urgent) { if (e.isNull()) return 0; transferItemList += TransferItem(e, true, external); // elementSend(e); QString out = sanitizeForStream(elementToString(e, clip)); return internalWriteString(out, TrackItem::Custom, id, urgent); } QByteArray XmlProtocol::resetStream() { // reset the state if (incoming) state = RecvOpen; else state = SendOpen; // grab unprocessed data before resetting QByteArray spare = xml.unprocessed(); xml.reset(); return spare; } int XmlProtocol::internalWriteData(const QByteArray &a, TrackItem::Type t, int id, bool urgent) { TrackItem i; i.type = t; i.id = id; i.size = a.size(); if (urgent) { trackQueueUrgent += i; outDataUrgent += a; } else { trackQueueNormal += i; outDataNormal += a; } return a.size(); } int XmlProtocol::internalWriteString(const QString &s, TrackItem::Type t, int id, bool urgent) { return internalWriteData(s.toUtf8(), t, id, urgent); } int XmlProtocol::processTrackQueue(QList &queue, int bytes) { for (QList::Iterator it = queue.begin(); it != queue.end();) { TrackItem &i = *it; // enough bytes? if (bytes < i.size) { i.size -= bytes; bytes = 0; break; } int type = i.type; int id = i.id; int size = i.size; bytes -= i.size; it = queue.erase(it); if (type == TrackItem::Raw) { // do nothing } else if (type == TrackItem::Close) { closeWritten = true; } else if (type == TrackItem::Custom) { itemWritten(id, size); } if (bytes == 0) break; } return bytes; } void XmlProtocol::sendTagOpen() { if (elem.isNull()) elem = elemDoc.importNode(docElement(), true).toElement(); QString xmlHeader; createRootXmlTags(elem, &xmlHeader, &tagOpen, &tagClose); QString s; s += xmlHeader + '\n'; s += sanitizeForStream(tagOpen) + '\n'; transferItemList += TransferItem(xmlHeader, true); transferItemList += TransferItem(tagOpen, true); // stringSend(xmlHeader); // stringSend(tagOpen); internalWriteString(s, TrackItem::Raw); } void XmlProtocol::sendTagClose() { transferItemList += TransferItem(tagClose, true); // stringSend(tagClose); internalWriteString(tagClose, TrackItem::Close); } bool XmlProtocol::baseStep(const Parser::Event &pe) { // Basic if (state == SendOpen) { sendTagOpen(); event = ESend; if (incoming) state = Open; else state = RecvOpen; return true; } else if (state == RecvOpen) { if (incoming) state = SendOpen; else state = Open; // note: event will always be DocumentOpen here handleDocOpen(pe); event = ERecvOpen; return true; } else if (state == Open) { QDomElement e; if (pe.type() == Parser::Event::Element) e = pe.element(); return doStep(e); } // Closing else { if (closeWritten) { if (peerClosed) { event = EPeerClosed; return true; } else return handleCloseFinished(); } need = NNotify; notify = NSend; return false; } } void XmlProtocol::setIncomingAsExternal() { for (QList::Iterator it = transferItemList.begin(); it != transferItemList.end(); ++it) { TransferItem &i = *it; // look for elements received if (!i.isString && !i.isSent) i.isExternal = true; } } psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-core/xmlprotocol.h000066400000000000000000000117271370065651000247510ustar00rootroot00000000000000/* * xmlprotocol.h - state machine for 'xmpp-like' protocols * Copyright (C) 2004 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMLPROTOCOL_H #define XMLPROTOCOL_H #include "parser.h" #include #include #include #define NS_XML "http://www.w3.org/XML/1998/namespace" namespace XMPP { class XmlProtocol : public QObject { public: enum Need { NNotify, // need a data send and/or recv update NCustom = 10 }; enum Event { EError, // unrecoverable error, see errorCode for details ESend, // data needs to be sent, use takeOutgoingData() ERecvOpen, // breakpoint after root element open tag is received EPeerClosed, // root element close tag received EClosed, // finished closing ESMConnTimeout, // absence of responses to query ESMResumeFailed, // failed to resume sm session ECustom = 10 }; enum Error { ErrParse, // there was an error parsing the xml ErrCustom = 10 }; enum Notify { NSend = 0x01, // need to know if data has been written NRecv = 0x02, // need incoming data NTimeout = 0x04 // need to know when time passed }; XmlProtocol(); virtual ~XmlProtocol(); virtual void reset(); // byte I/O for the stream void addIncomingData(const QByteArray &); QByteArray takeOutgoingData(); void outgoingDataWritten(int); void clearSendQueue(); // advance the state machine bool processStep(); // set these before returning from a step int need = 0, event = 0, errorCode = 0, notify = 0, timeout_sec = 0; inline bool isIncoming() const { return incoming; } QString xmlEncoding() const; QString elementToString(const QDomElement &e, bool clip = false); class TransferItem { public: TransferItem(); TransferItem(const QString &str, bool sent, bool external = false); TransferItem(const QDomElement &elem, bool sent, bool external = false); bool isSent; // else, received bool isString; // else, is element bool isExternal; // not owned by protocol QString str; QDomElement elem; }; QList transferItemList; void setIncomingAsExternal(); protected: virtual QDomElement docElement() = 0; virtual void handleDocOpen(const Parser::Event &pe) = 0; virtual bool handleError() = 0; virtual bool handleCloseFinished() = 0; virtual bool stepAdvancesParser() const = 0; virtual bool stepRequiresElement() const; virtual bool doStep(const QDomElement &e) = 0; virtual void itemWritten(int id, int size); // 'debug' virtual void stringSend(const QString &s); virtual void stringRecv(const QString &s); virtual void elementSend(const QDomElement &e); virtual void elementRecv(const QDomElement &e); void startConnect(); void startAccept(); bool close(); int writeString(const QString &s, int id, bool external); int writeElement(const QDomElement &e, int id, bool external, bool clip = false, bool urgent = false); QByteArray resetStream(); private: enum { SendOpen, RecvOpen, Open, Closing }; class TrackItem { public: enum Type { Raw, Close, Custom }; int type, id, size; }; bool incoming; QDomDocument elemDoc; QDomElement elem; QString tagOpen; QString tagClose; int state = 0; bool peerClosed; bool closeWritten; Parser xml; QByteArray outDataNormal; QByteArray outDataUrgent; QList trackQueueNormal; QList trackQueueUrgent; void init(); int internalWriteData(const QByteArray &a, TrackItem::Type t, int id = -1, bool urgent = false); int internalWriteString(const QString &s, TrackItem::Type t, int id = -1, bool urgent = false); int processTrackQueue(QList &queue, int bytes); void sendTagOpen(); void sendTagClose(); bool baseStep(const Parser::Event &pe); }; } // namespace XMPP #endif // XMLPROTOCOL_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-core/xmpp.h000066400000000000000000000131441370065651000233460ustar00rootroot00000000000000/* * xmpp.h - XMPP "core" library API * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_H #define XMPP_H #include "addressresolver.h" #include "xmpp/jid/jid.h" #include "xmpp_clientstream.h" #include "xmpp_stanza.h" #include "xmpp_stream.h" #include #include #include #include #include #include #include // For QCA::SASL::Params #ifndef CS_XMPP class ByteStream; #endif namespace QCA { class TLS; }; namespace XMPP { // CS_IMPORT_BEGIN cutestuff/bytestream.h #ifdef CS_XMPP class ByteStream; #endif // CS_IMPORT_END class Debug { public: virtual ~Debug(); virtual void msg(const QString &) = 0; virtual void outgoingTag(const QString &) = 0; virtual void incomingTag(const QString &) = 0; virtual void outgoingXml(const QDomElement &) = 0; virtual void incomingXml(const QDomElement &) = 0; }; void setDebug(Debug *); class Connector : public QObject { Q_OBJECT public: Connector(QObject *parent = nullptr); virtual ~Connector(); virtual void setOptHostPort(const QString &host, quint16 port) = 0; virtual void connectToServer(const QString &server) = 0; virtual ByteStream *stream() const = 0; virtual void done() = 0; bool useSSL() const; bool havePeerAddress() const; QHostAddress peerAddress() const; quint16 peerPort() const; virtual QString host() const; signals: void connected(); void error(); protected: void setUseSSL(bool b); void setPeerAddressNone(); void setPeerAddress(const QHostAddress &addr, quint16 port); private: bool ssl; // a flag to start ssl handshake immediately bool haveaddr; QHostAddress addr; quint16 port; }; class AdvancedConnector : public Connector { Q_OBJECT public: enum Error { ErrConnectionRefused, ErrHostNotFound, ErrProxyConnect, ErrProxyNeg, ErrProxyAuth, ErrStream }; AdvancedConnector(QObject *parent = nullptr); virtual ~AdvancedConnector(); class Proxy { public: enum { None, HttpConnect, HttpPoll, Socks }; Proxy() = default; ~Proxy() { } int type() const; QString host() const; quint16 port() const; QUrl url() const; QString user() const; QString pass() const; int pollInterval() const; void setHttpConnect(const QString &host, quint16 port); void setHttpPoll(const QString &host, quint16 port, const QUrl &url); void setSocks(const QString &host, quint16 port); void setUserPass(const QString &user, const QString &pass); void setPollInterval(int secs); private: int t = None; QUrl v_url; QString v_host; quint16 v_port = 0; QString v_user; QString v_pass; int v_poll = 30; }; void setProxy(const Proxy &proxy); void setOptProbe(bool); void setOptSSL(bool); void changePollInterval(int secs); void setOptHostPort(const QString &host, quint16 port); void connectToServer(const QString &server); ByteStream *stream() const; void done(); int errorCode() const; virtual QString host() const; signals: void srvLookup(const QString &server); void srvResult(bool success); void httpSyncStarted(); void httpSyncFinished(); private slots: void bs_connected(); void bs_error(int); void http_syncStarted(); void http_syncFinished(); void t_timeout(); private: class Private; Private *d; void cleanup(); }; class TLSHandler : public QObject { Q_OBJECT public: TLSHandler(QObject *parent = nullptr); virtual ~TLSHandler(); virtual void reset() = 0; virtual void startClient(const QString &host) = 0; virtual void write(const QByteArray &a) = 0; virtual void writeIncoming(const QByteArray &a) = 0; signals: void success(); void fail(); void closed(); void readyRead(const QByteArray &a); void readyReadOutgoing(const QByteArray &a, int plainBytes); }; class QCATLSHandler : public TLSHandler { Q_OBJECT public: QCATLSHandler(QCA::TLS *parent); ~QCATLSHandler(); QCA::TLS *tls() const; int tlsError() const; void setXMPPCertCheck(bool enable); bool XMPPCertCheck(); bool certMatchesHostname(); void reset(); void startClient(const QString &host); void write(const QByteArray &a); void writeIncoming(const QByteArray &a); signals: void tlsHandshaken(); public slots: void continueAfterHandshake(); private slots: void tls_handshaken(); void tls_readyRead(); void tls_readyReadOutgoing(); void tls_closed(); void tls_error(); private: class Private; Private *d; }; }; // namespace XMPP #endif // XMPP_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-core/xmpp_clientstream.h000066400000000000000000000161531370065651000261230ustar00rootroot00000000000000/* * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_CLIENTSTREAM_H #define XMPP_CLIENTSTREAM_H #include "xmpp_stream.h" #include class ByteStream; class QByteArray; class QDomDocument; class QDomElement; class QHostAddress; class QObject; class QString; namespace XMPP { class Connector; class StreamFeatures; class TLSHandler; class ClientStream : public Stream { Q_OBJECT public: enum Error { ErrConnection = ErrCustom, // Connection error, ask Connector-subclass what's up ErrNeg, // Negotiation error, see condition ErrTLS, // TLS error, see condition ErrAuth, // Auth error, see condition ErrSecurityLayer, // broken SASL security layer ErrSmResume, // SM resume error ErrBind // Resource binding error }; enum Warning { WarnOldVersion, // server uses older XMPP/Jabber "0.9" protocol WarnNoTLS, // there is no chance for TLS at this point WarnSMReconnection // SM started a quiet stream reconnection }; enum NegCond { HostGone, // host no longer hosted HostUnknown, // unknown host RemoteConnectionFailed, // unable to connect to a required remote resource SeeOtherHost, // a 'redirect', see errorText() for other host UnsupportedVersion // unsupported XMPP version }; enum TLSCond { TLSStart, // server rejected STARTTLS TLSFail // TLS failed, ask TLSHandler-subclass what's up }; enum SecurityLayer { LayerTLS, LayerSASL }; enum AuthCond { GenericAuthError, // all-purpose "can't login" error (includes: IncorrectEncoding, ) // standard xmpp auth errors (not all. some of the converted to GenericAuthError): Aborted, // server confirms auth abort AccountDisabled, // account temporrily disabled CredentialsExpired, // credential expired EncryptionRequired, // can't use mech without TLS InvalidAuthzid, // bad input JID InvalidMech, // bad mechanism MalformedRequest, // malformded request MechTooWeak, // can't use mech with this authzid NotAuthorized, // bad user, bad password, bad creditials TemporaryAuthFailure, // please try again later! // non-xmpp NoMech, // No appropriate auth mech available BadServ, // Server failed mutual auth }; enum BindCond { BindNotAllowed, // not allowed to bind a resource BindConflict // resource in-use }; enum AllowPlainType { NoAllowPlain, AllowPlain, AllowPlainOverTLS }; ClientStream(Connector *conn, TLSHandler *tlsHandler = nullptr, QObject *parent = nullptr); ClientStream(const QString &host, const QString &defRealm, ByteStream *bs, QCA::TLS *tls = nullptr, QObject *parent = nullptr); // server ~ClientStream(); Jid jid() const; void connectToServer(const Jid &jid, bool auth = true); void accept(); // server bool isActive() const; bool isAuthenticated() const; // login params void setUsername(const QString &s); void setPassword(const QString &s); void setRealm(const QString &s); void setAuthzid(const QString &s); void continueAfterParams(); void abortAuth(); void setSaslMechanismProvider(const QString &m, const QString &p); QString saslMechanismProvider(const QString &m) const; QCA::Provider::Context *currentSASLContext() const; void setSCRAMStoredSaltedHash(const QString &s); const QString getSCRAMStoredSaltedHash(); // SASL information QString saslMechanism() const; int saslSSF() const; // binding void setResourceBinding(bool); // Language void setLang(const QString &); // security options (old protocol only uses the first !) void setAllowPlain(AllowPlainType); void setRequireMutualAuth(bool); void setSSFRange(int low, int high); void setOldOnly(bool); void setSASLMechanism(const QString &s); void setLocalAddr(const QHostAddress &addr, quint16 port); // Compression void setCompress(bool); // reimplemented QDomDocument &doc() const; QString baseNS() const; bool old() const; void close(); bool stanzaAvailable() const; Stanza read(); void write(const Stanza &s); void clearSendQueue(); int errorCondition() const; QString errorText() const; QHash errorLangText() const; QDomElement errorAppSpec() const; // extra void writeDirect(const QString &s); void setNoopTime(int mills); // Stream management bool isResumed() const; void setSMEnabled(bool enable); // barracuda extension QStringList hosts() const; const StreamFeatures &streamFeatures() const; QList unhandledFeatures() const; signals: void connected(); void securityLayerActivated(int); void needAuthParams(bool user, bool pass, bool realm); void authenticated(); void warning(int); void haveUnhandledFeatures(); void incomingXml(const QString &s); void outgoingXml(const QString &s); void stanzasAcked(int); public slots: void continueAfterWarning(); private slots: void cr_connected(); void cr_error(); void bs_connectionClosed(); void bs_delayedCloseFinished(); void bs_error(int); // server only void ss_readyRead(); void ss_bytesWritten(qint64); void ss_tlsHandshaken(); void ss_tlsClosed(); void ss_error(int); void sasl_clientFirstStep(bool, const QByteArray &); void sasl_nextStep(const QByteArray &stepData); void sasl_needParams(const QCA::SASL::Params &); void sasl_authCheck(const QString &user, const QString &authzid); void sasl_authenticated(); void sasl_error(); void sm_timeout(); void doNoop(); void doReadyRead(); private: class Private; Private *d; void reset(bool all = false); void processNext(); int convertedSASLCond() const; bool handleNeed(); void handleError(); void srvProcessNext(); void setTimer(int secs); }; } // namespace XMPP #endif // XMPP_CLIENTSTREAM_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-core/xmpp_stanza.cpp000066400000000000000000000505771370065651000252740ustar00rootroot00000000000000/* * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp_stanza.h" #include "xmpp/jid/jid.h" #include "xmpp_clientstream.h" #include "xmpp_stream.h" #include #include using namespace XMPP; #define NS_STANZAS "urn:ietf:params:xml:ns:xmpp-stanzas" #define NS_XML "http://www.w3.org/XML/1998/namespace" //---------------------------------------------------------------------------- // Stanza::Error //---------------------------------------------------------------------------- /** \class Stanza::Error \brief Represents stanza error Stanza error consists of error type and condition. In addition, it may contain a human readable description, and application specific element. One of the usages of this class is to easily generate error XML: \code QDomElement e = createIQ(client()->doc(), "error", jid, id); Error error(Stanza::Error::Auth, Stanza::Error::NotAuthorized); e.appendChild(error.toXml(*client()->doc(), client()->stream().baseNS())); \endcode This class implements XEP-0086, which means that it can read both old and new style error elements. Also, generated XML will contain both type/condition and code. Error text in output XML is always presented in XMPP-style only. All functions will always try to guess missing information based on mappings defined in the JEP. */ /** \enum Stanza::Error::ErrorType \brief Represents error type */ /** \enum Stanza::Error::ErrorCond \brief Represents error condition */ /** \brief Constructs new error */ Stanza::Error::Error(int _type, int _condition, const QString &_text, const QDomElement &_appSpec) { type = _type; condition = _condition; text = _text; appSpec = _appSpec; originalCode = 0; } void Stanza::Error::reset() { type = 0; condition = UndefinedCondition; text.clear(); by.clear(); appSpec = QDomElement(); originalCode = 0; } class Stanza::Error::Private { public: struct ErrorTypeEntry { const char *str; int type; }; static ErrorTypeEntry errorTypeTable[]; struct ErrorCondEntry { const char *str; int cond; }; static ErrorCondEntry errorCondTable[]; struct ErrorCodeEntry { int cond; int type; int code; }; static ErrorCodeEntry errorCodeTable[]; struct ErrorDescEntry { int cond; const char *name; const char *str; }; static ErrorDescEntry errorDescriptions[]; static int stringToErrorType(const QString &s) { for (int n = 0; errorTypeTable[n].str; ++n) { if (s == errorTypeTable[n].str) return errorTypeTable[n].type; } return -1; } static QString errorTypeToString(int x) { for (int n = 0; errorTypeTable[n].str; ++n) { if (x == errorTypeTable[n].type) return errorTypeTable[n].str; } return QString(); } static int stringToErrorCond(const QString &s) { for (int n = 0; errorCondTable[n].str; ++n) { if (s == errorCondTable[n].str) return errorCondTable[n].cond; } return -1; } static QString errorCondToString(int x) { for (int n = 0; errorCondTable[n].str; ++n) { if (x == errorCondTable[n].cond) return errorCondTable[n].str; } return QString(); } static int errorTypeCondToCode(int t, int c) { Q_UNUSED(t); for (int n = 0; errorCodeTable[n].cond; ++n) { if (c == errorCodeTable[n].cond) return errorCodeTable[n].code; } return 0; } static QPair errorCodeToTypeCond(int x) { for (int n = 0; errorCodeTable[n].cond; ++n) { if (x == errorCodeTable[n].code) return QPair(errorCodeTable[n].type, errorCodeTable[n].cond); } return QPair(-1, -1); } static QPair errorCondToDesc(int x) { for (int n = 0; errorDescriptions[n].str; ++n) { if (x == errorDescriptions[n].cond) return QPair( QCoreApplication::translate("Stanza::Error::Private", errorDescriptions[n].name), QCoreApplication::translate("Stanza::Error::Private", errorDescriptions[n].str)); } return QPair(); } }; Stanza::Error::Private::ErrorTypeEntry Stanza::Error::Private::errorTypeTable[] = { { "cancel", Cancel }, { "continue", Continue }, { "modify", Modify }, { "auth", Auth }, { "wait", Wait }, { nullptr, 0 }, }; Stanza::Error::Private::ErrorCondEntry Stanza::Error::Private::errorCondTable[] = { { "bad-request", BadRequest }, { "conflict", Conflict }, { "feature-not-implemented", FeatureNotImplemented }, { "forbidden", Forbidden }, { "gone", Gone }, { "internal-server-error", InternalServerError }, { "item-not-found", ItemNotFound }, { "jid-malformed", JidMalformed }, { "not-acceptable", NotAcceptable }, { "not-allowed", NotAllowed }, { "not-authorized", NotAuthorized }, { "recipient-unavailable", RecipientUnavailable }, { "redirect", Redirect }, { "registration-required", RegistrationRequired }, { "remote-server-not-found", RemoteServerNotFound }, { "remote-server-timeout", RemoteServerTimeout }, { "resource-constraint", ResourceConstraint }, { "service-unavailable", ServiceUnavailable }, { "subscription-required", SubscriptionRequired }, { "undefined-condition", UndefinedCondition }, { "unexpected-request", UnexpectedRequest }, { nullptr, 0 }, }; Stanza::Error::Private::ErrorCodeEntry Stanza::Error::Private::errorCodeTable[] = { { BadRequest, Modify, 400 }, { Conflict, Cancel, 409 }, { FeatureNotImplemented, Cancel, 501 }, { Forbidden, Auth, 403 }, { Gone, Modify, 302 }, // permanent { InternalServerError, Wait, 500 }, { ItemNotFound, Cancel, 404 }, { JidMalformed, Modify, 400 }, { NotAcceptable, Modify, 406 }, { NotAllowed, Cancel, 405 }, { NotAuthorized, Auth, 401 }, { RecipientUnavailable, Wait, 404 }, { Redirect, Modify, 302 }, // temporary { RegistrationRequired, Auth, 407 }, { RemoteServerNotFound, Cancel, 404 }, { RemoteServerTimeout, Wait, 504 }, { ResourceConstraint, Wait, 500 }, { ServiceUnavailable, Cancel, 503 }, { SubscriptionRequired, Auth, 407 }, { UndefinedCondition, Wait, 500 }, // Note: any type matches really { UnexpectedRequest, Wait, 400 }, { 0, 0, 0 }, }; Stanza::Error::Private::ErrorDescEntry Stanza::Error::Private::errorDescriptions[] = { { BadRequest, QT_TR_NOOP("Bad request"), QT_TR_NOOP("The sender has sent XML that is malformed or that cannot be processed.") }, { Conflict, QT_TR_NOOP("Conflict"), QT_TR_NOOP( "Access cannot be granted because an existing resource or session exists with the same name or address.") }, { FeatureNotImplemented, QT_TR_NOOP("Feature not implemented"), QT_TR_NOOP( "The feature requested is not implemented by the recipient or server and therefore cannot be processed.") }, { Forbidden, QT_TR_NOOP("Forbidden"), QT_TR_NOOP("The requesting entity does not possess the required permissions to perform the action.") }, { Gone, QT_TR_NOOP("Gone"), QT_TR_NOOP("The recipient or server can no longer be contacted at this address.") }, { InternalServerError, QT_TR_NOOP("Internal server error"), QT_TR_NOOP("The server could not process the stanza because of a misconfiguration or an otherwise-undefined " "internal server error.") }, { ItemNotFound, QT_TR_NOOP("Item not found"), QT_TR_NOOP("The addressed JID or item requested cannot be found.") }, { JidMalformed, QT_TR_NOOP("JID malformed"), QT_TR_NOOP("The sending entity has provided or communicated an XMPP address (e.g., a value of the 'to' " "attribute) or aspect thereof (e.g., a resource identifier) that does not adhere to the syntax " "defined in Addressing Scheme.") }, { NotAcceptable, QT_TR_NOOP("Not acceptable"), QT_TR_NOOP("The recipient or server understands the request but is refusing to process it because it does not " "meet criteria defined by the recipient or server (e.g., a local policy regarding acceptable words in " "messages).") }, { NotAllowed, QT_TR_NOOP("Not allowed"), QT_TR_NOOP("The recipient or server does not allow any entity to perform the action.") }, { NotAuthorized, QT_TR_NOOP("Not authorized"), QT_TR_NOOP("The sender must provide proper credentials before being allowed to perform the action, or has " "provided improper credentials.") }, { RecipientUnavailable, QT_TR_NOOP("Recipient unavailable"), QT_TR_NOOP("The intended recipient is temporarily unavailable.") }, { Redirect, QT_TR_NOOP("Redirect"), QT_TR_NOOP("The recipient or server is redirecting requests for this information to another entity, usually " "temporarily.") }, { RegistrationRequired, QT_TR_NOOP("Registration required"), QT_TR_NOOP("The requesting entity is not authorized to access the requested service because registration is " "required.") }, { RemoteServerNotFound, QT_TR_NOOP("Remote server not found"), QT_TR_NOOP( "A remote server or service specified as part or all of the JID of the intended recipient does not exist.") }, { RemoteServerTimeout, QT_TR_NOOP("Remote server timeout"), QT_TR_NOOP("A remote server or service specified as part or all of the JID of the intended recipient (or " "required to fulfill a request) could not be contacted within a reasonable amount of time.") }, { ResourceConstraint, QT_TR_NOOP("Resource constraint"), QT_TR_NOOP("The server or recipient lacks the system resources necessary to service the request.") }, { ServiceUnavailable, QT_TR_NOOP("Service unavailable"), QT_TR_NOOP("The server or recipient does not currently provide the requested service.") }, { SubscriptionRequired, QT_TR_NOOP("Subscription required"), QT_TR_NOOP("The requesting entity is not authorized to access the requested service because a subscription is " "required.") }, { UndefinedCondition, QT_TR_NOOP("Undefined condition"), QT_TR_NOOP("The error condition is not one of those defined by the other conditions in this list.") }, { UnexpectedRequest, QT_TR_NOOP("Unexpected request"), QT_TR_NOOP("The recipient or server understood the request but was not expecting it at this time (e.g., the " "request was out of order).") }, }; /** \brief Returns the error code If the error object was constructed with a code, this code will be returned. Otherwise, the code will be guessed. 0 means unknown code. */ int Stanza::Error::code() const { return originalCode ? originalCode : Private::errorTypeCondToCode(type, condition); } /** \brief Creates a StanzaError from \a code. The error's type and condition are guessed from the give \a code. The application-specific error element is preserved. */ bool Stanza::Error::fromCode(int code) { QPair guess = Private::errorCodeToTypeCond(code); if (guess.first == -1 || guess.second == -1) return false; type = guess.first; condition = guess.second; originalCode = code; return true; } /** \brief Reads the error from XML This function finds and reads the error element \a e. You need to provide the base namespace of the stream which this stanza belongs to (probably by using stream.baseNS() function). */ bool Stanza::Error::fromXml(const QDomElement &e, const QString &baseNS) { if (e.tagName() != "error" && e.namespaceURI() != baseNS) return false; // type type = Private::stringToErrorType(e.attribute("type")); by = e.attribute(QLatin1String("by")); condition = -1; QString textTag(QString::fromLatin1("text")); for (auto t = e.firstChildElement(); !t.isNull(); t = t.nextSiblingElement()) { if (t.namespaceURI() == NS_STANZAS) { if (t.tagName() == textTag) { text = t.text().trimmed(); } else { condition = Private::stringToErrorCond(t.tagName()); } } else { appSpec = t; } if (condition != -1 && !appSpec.isNull() && !text.isEmpty()) break; } // code originalCode = e.attribute("code").toInt(); // deprecated. rfc6120 has just a little note about it. also see XEP-0086 // try to guess type/condition if (type == -1 || condition == -1) { QPair guess(-1, -1); if (originalCode) guess = Private::errorCodeToTypeCond(originalCode); if (type == -1) type = guess.first != -1 ? guess.first : Cancel; if (condition == -1) condition = guess.second != -1 ? guess.second : UndefinedCondition; } return true; } /** \brief Writes the error to XML This function creates an error element representing the error object. You need to provide the base namespace of the stream to which this stanza belongs to (probably by using stream.baseNS() function). */ QDomElement Stanza::Error::toXml(QDomDocument &doc, const QString &baseNS) const { QDomElement errElem = doc.createElementNS(baseNS, "error"); QDomElement t; // XMPP error QString stype = Private::errorTypeToString(type); if (stype.isEmpty()) return errElem; QString scond = Private::errorCondToString(condition); if (scond.isEmpty()) return errElem; errElem.setAttribute("type", stype); if (!by.isEmpty()) { errElem.setAttribute("by", by); } errElem.appendChild(t = doc.createElementNS(NS_STANZAS, scond)); // t.setAttribute("xmlns", NS_STANZAS); // FIX-ME: this shouldn't be needed // old code int scode = code(); if (scode) errElem.setAttribute("code", scode); // text if (!text.isEmpty()) { t = doc.createElementNS(NS_STANZAS, "text"); // t.setAttribute("xmlns", NS_STANZAS); // FIX-ME: this shouldn't be needed t.appendChild(doc.createTextNode(text)); errElem.appendChild(t); } // application specific errElem.appendChild(appSpec); return errElem; } /** \brief Returns the error name and description Returns the error name (e.g. "Not Allowed") and generic description. */ QPair Stanza::Error::description() const { return Private::errorCondToDesc(condition); } /** * \brief Returns string human-reabable representation of the error */ QString Stanza::Error::toString() const { QPair desc = description(); if (text.isEmpty()) return desc.first + ".\n" + desc.second; else return desc.first + ".\n" + desc.second + "\n" + text; } //---------------------------------------------------------------------------- // Stanza //---------------------------------------------------------------------------- class Stanza::Private { public: static int stringToKind(const QString &s) { if (s == QLatin1String("message")) return Message; else if (s == QLatin1String("presence")) return Presence; else if (s == QLatin1String("iq")) return IQ; else return -1; } static QString kindToString(Kind k) { if (k == Message) return QLatin1String("message"); else if (k == Presence) return QLatin1String("presence"); else return QLatin1String("iq"); } Stream * s; QDomElement e; QSharedPointer sharedDoc; }; Stanza::Stanza() { d = nullptr; } Stanza::Stanza(Stream *s, Kind k, const Jid &to, const QString &type, const QString &id) { Q_ASSERT(s); d = new Private; Kind kind; if (k == Message || k == Presence || k == IQ) kind = k; else kind = Message; d->s = s; if (d->s) d->e = d->s->doc().createElementNS(s->baseNS(), Private::kindToString(kind)); if (to.isValid()) setTo(to); if (!type.isEmpty()) setType(type); if (!id.isEmpty()) setId(id); } Stanza::Stanza(Stream *s, const QDomElement &e) { Q_ASSERT(s); d = nullptr; if (e.namespaceURI() != s->baseNS()) return; int x = Private::stringToKind(e.tagName()); if (x == -1) return; d = new Private; d->s = s; d->e = e; } Stanza::Stanza(const Stanza &from) { d = nullptr; *this = from; } Stanza &Stanza::operator=(const Stanza &from) { if (&from == this) return *this; delete d; d = nullptr; if (from.d) d = new Private(*from.d); return *this; } Stanza::~Stanza() { delete d; } bool Stanza::isNull() const { return (d ? false : true); } QDomElement Stanza::element() const { return d->e; } QString Stanza::toString() const { return Stream::xmlToString(d->e); } QDomDocument &Stanza::doc() const { return d->s->doc(); } QString Stanza::baseNS() const { return d->s->baseNS(); } QDomElement Stanza::createElement(const QString &ns, const QString &tagName) { return d->s->doc().createElementNS(ns, tagName); } QDomElement Stanza::createTextElement(const QString &ns, const QString &tagName, const QString &text) { QDomElement e = d->s->doc().createElementNS(ns, tagName); e.appendChild(d->s->doc().createTextNode(text)); return e; } void Stanza::appendChild(const QDomElement &e) { d->e.appendChild(e); } Stanza::Kind Stanza::kind() const { return (Kind)Private::stringToKind(d->e.tagName()); } Stanza::Kind Stanza::kind(const QString &tagName) { return (Kind)Private::stringToKind(tagName); } void Stanza::setKind(Kind k) { d->e.setTagName(Private::kindToString(k)); } Jid Stanza::to() const { return Jid(d->e.attribute("to")); } Jid Stanza::from() const { return Jid(d->e.attribute("from")); } QString Stanza::id() const { return d->e.attribute("id"); } QString Stanza::type() const { return d->e.attribute("type"); } QString Stanza::lang() const { return d->e.attributeNS(NS_XML, "lang", QString()); } void Stanza::setTo(const Jid &j) { d->e.setAttribute("to", j.full()); } void Stanza::setFrom(const Jid &j) { d->e.setAttribute("from", j.full()); } void Stanza::setId(const QString &id) { d->e.setAttribute("id", id); } void Stanza::setType(const QString &type) { d->e.setAttribute("type", type); } void Stanza::setLang(const QString &lang) { d->e.setAttribute("xml:lang", lang); } Stanza::Error Stanza::error() const { Error err; QDomElement e = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement(); if (!e.isNull()) err.fromXml(e, d->s->baseNS()); return err; } void Stanza::setError(const Error &err) { QDomDocument doc = d->e.ownerDocument(); QDomElement errElem = err.toXml(doc, d->s->baseNS()); QDomElement oldElem = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement(); if (oldElem.isNull()) { d->e.appendChild(errElem); } else { d->e.replaceChild(errElem, oldElem); } } void Stanza::clearError() { QDomElement errElem = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement(); if (!errElem.isNull()) d->e.removeChild(errElem); } QSharedPointer Stanza::unboundDocument(QSharedPointer sd) { if (!sd) { sd = QSharedPointer(new QDomDocument); } d->e = sd->importNode(d->e, true).toElement(); d->sharedDoc = sd; return d->sharedDoc; } psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-core/xmpp_stanza.h000066400000000000000000000074201370065651000247260ustar00rootroot00000000000000/* * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_STANZA_H #define XMPP_STANZA_H #include #include #include #include class QDomDocument; namespace XMPP { class Jid; class Stream; class Stanza { public: enum Kind { Message, Presence, IQ }; Stanza(); Stanza(const Stanza &from); Stanza &operator=(const Stanza &from); virtual ~Stanza(); class Error { public: enum ErrorType { Cancel = 1, Continue, Modify, Auth, Wait }; enum ErrorCond { BadRequest = 1, Conflict, FeatureNotImplemented, Forbidden, Gone, InternalServerError, ItemNotFound, JidMalformed, NotAcceptable, NotAllowed, NotAuthorized, PaymentRequired, RecipientUnavailable, Redirect, RegistrationRequired, RemoteServerNotFound, RemoteServerTimeout, ResourceConstraint, ServiceUnavailable, SubscriptionRequired, UndefinedCondition, UnexpectedRequest }; Error(int type = Cancel, int condition = UndefinedCondition, const QString &text = QString(), const QDomElement &appSpec = QDomElement()); int type; int condition; QString text; QString by; QDomElement appSpec; void reset(); int code() const; bool fromCode(int code); QPair description() const; QString toString() const; QDomElement toXml(QDomDocument &doc, const QString &baseNS) const; bool fromXml(const QDomElement &e, const QString &baseNS); private: class Private; int originalCode; }; bool isNull() const; QDomElement element() const; QString toString() const; QDomDocument &doc() const; QString baseNS() const; QDomElement createElement(const QString &ns, const QString &tagName); QDomElement createTextElement(const QString &ns, const QString &tagName, const QString &text); void appendChild(const QDomElement &e); Kind kind() const; static Kind kind(const QString &tagName); void setKind(Kind k); Jid to() const; Jid from() const; QString id() const; QString type() const; QString lang() const; void setTo(const Jid &j); void setFrom(const Jid &j); void setId(const QString &id); void setType(const QString &type); void setLang(const QString &lang); Error error() const; void setError(const Error &err); void clearError(); void markHandled(); void setSMId(unsigned long id); QSharedPointer unboundDocument(QSharedPointer); private: friend class Stream; Stanza(Stream *s, Kind k, const Jid &to, const QString &type, const QString &id); Stanza(Stream *s, const QDomElement &e); class Private; Private *d; }; } // namespace XMPP #endif // XMPP_STANZA_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-core/xmpp_stream.h000066400000000000000000000046361370065651000247270ustar00rootroot00000000000000/* * xmpp.h - XMPP "core" library API * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_STREAM_H #define XMPP_STREAM_H #include "xmpp/jid/jid.h" #include "xmpp_stanza.h" #include #include class QDomDocument; namespace XMPP { class Stream : public QObject { Q_OBJECT public: enum Error { ErrParse, ErrProtocol, ErrStream, ErrCustom = 10 }; enum StreamCond { GenericStreamError, Conflict, ConnectionTimeout, InternalServerError, InvalidFrom, InvalidXml, PolicyViolation, ResourceConstraint, SystemShutdown, StreamReset }; Stream(QObject *parent = nullptr); virtual ~Stream(); virtual QDomDocument &doc() const = 0; virtual QString baseNS() const = 0; virtual bool old() const = 0; virtual void close() = 0; virtual bool stanzaAvailable() const = 0; virtual Stanza read() = 0; virtual void write(const Stanza &s) = 0; virtual int errorCondition() const = 0; virtual QString errorText() const = 0; virtual QHash errorLangText() const = 0; // localized error descriptions virtual QDomElement errorAppSpec() const = 0; Stanza createStanza(Stanza::Kind k, const Jid &to = "", const QString &type = "", const QString &id = ""); Stanza createStanza(const QDomElement &e); static QString xmlToString(const QDomElement &e, bool clip = false); static void cleanup(); signals: void connectionClosed(); void delayedCloseFinished(); void readyRead(); void stanzaWritten(); void error(int); }; } // namespace XMPP #endif // XMPP_STREAM_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/000077500000000000000000000000001370065651000216635ustar00rootroot00000000000000psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/client.cpp000066400000000000000000001154671370065651000236630ustar00rootroot00000000000000/* * client.cpp - IM Client * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ //! \class XMPP::Client client.h //! \brief Communicates with the XMPP network. Start here. //! //! Client controls an active XMPP connection. It allows you to connect, //! authenticate, manipulate the roster, and send / receive messages and //! presence. It is the centerpiece of this library, and all Tasks must pass //! through it. //! //! For convenience, many Tasks are handled internally to Client (such as //! JT_Auth). However, for accessing features beyond the basics provided by //! Client, you will need to manually invoke Tasks. Fortunately, the //! process is very simple. //! //! The entire Task system is heavily founded on Qt. All Tasks have a parent, //! except for the root Task, and are considered QObjects. By using Qt's RTTI //! facilities (QObject::sender(), QObject::isA(), etc), you can use a //! "fire and forget" approach with Tasks. //! //! \code //! #include "client.h" //! using namespace XMPP; //! //! ... //! //! Client *client; //! //! Session::Session() //! { //! client = new Client; //! connect(client, SIGNAL(handshaken()), SLOT(clientHandshaken())); //! connect(client, SIGNAL(authFinished(bool,int,QString)), SLOT(authFinished(bool,int,QString))); //! client->connectToHost("jabber.org"); //! } //! //! void Session::clientHandshaken() //! { //! client->authDigest("jabtest", "12345", "Psi"); //! } //! //! void Session::authFinished(bool success, int, const QString &err) //! { //! if(success) //! printf("Login success!"); //! else //! printf("Login failed. Here's why: %s\n", err.toLatin1()); //! } //! \endcode #include "filetransfer.h" #include "httpfileupload.h" #include "im.h" #include "jingle-ft.h" #include "jingle-ibb.h" #include "jingle-ice.h" #include "jingle-s5b.h" #include "jingle.h" #include "protocol.h" #include "s5b.h" #include "tcpportreserver.h" #include "xmpp_bitsofbinary.h" #include "xmpp_caps.h" #include "xmpp_hash.h" #include "xmpp_ibb.h" #include "xmpp_serverinfomanager.h" #include "xmpp_tasks.h" #include "xmpp_xmlcommon.h" #include #include #include #include #include #ifdef Q_OS_WIN #define vsnprintf _vsnprintf #endif #define GROUPS_DELIMITER_TIMEOUT 10 namespace XMPP { //---------------------------------------------------------------------------- // Client //---------------------------------------------------------------------------- class Client::GroupChat { public: enum { Connecting, Connected, Closing }; GroupChat() = default; Jid j; int status = 0; QString password; }; class Client::ClientPrivate { public: ClientPrivate() { } QPointer stream; QDomDocument doc; int id_seed = 0xaaaa; Task * root = nullptr; QNetworkAccessManager * qnam = nullptr; QString host, user, pass, resource; QString osName, osVersion, tzname, clientName, clientVersion; CapsSpec caps, serverCaps; DiscoItem::Identity identity; Features features; QMap extension_features; int tzoffset = 0; bool useTzoffset = false; // manual tzoffset is old way of doing utc<->local translations bool active = false; bool capsOptimization = false; // don't send caps every time LiveRoster roster; ResourceList resourceList; CapsManager * capsman = nullptr; TcpPortReserver * tcpPortReserver = nullptr; S5BManager * s5bman = nullptr; Jingle::S5B::Manager * jingleS5BManager = nullptr; Jingle::IBB::Manager * jingleIBBManager = nullptr; Jingle::ICE::Manager * jingleICEManager = nullptr; IBBManager * ibbman = nullptr; BoBManager * bobman = nullptr; FileTransferManager * ftman = nullptr; ServerInfoManager * serverInfoManager = nullptr; HttpFileUploadManager *httpFileUploadManager = nullptr; Jingle::Manager * jingleManager = nullptr; QList groupChatList; EncryptionHandler * encryptionHandler = nullptr; }; Client::Client(QObject *par) : QObject(par) { d = new ClientPrivate; d->active = false; d->osName = "N/A"; d->clientName = "N/A"; d->clientVersion = "0.0"; d->root = new Task(this, true); d->s5bman = new S5BManager(this); connect(d->s5bman, SIGNAL(incomingReady()), SLOT(s5b_incomingReady())); d->ibbman = new IBBManager(this); connect(d->ibbman, SIGNAL(incomingReady()), SLOT(ibb_incomingReady())); d->bobman = new BoBManager(this); d->ftman = nullptr; d->capsman = new CapsManager(this); d->serverInfoManager = new ServerInfoManager(this); d->httpFileUploadManager = new HttpFileUploadManager(this); d->jingleManager = new Jingle::Manager(this); auto ft = new Jingle::FileTransfer::Manager(this); d->jingleManager->registerApp(Jingle::FileTransfer::NS, ft); d->jingleS5BManager = new Jingle::S5B::Manager(d->jingleManager); d->jingleIBBManager = new Jingle::IBB::Manager(d->jingleManager); d->jingleICEManager = new Jingle::ICE::Manager(d->jingleManager); d->jingleManager->registerTransport(Jingle::S5B::NS, d->jingleS5BManager); d->jingleManager->registerTransport(Jingle::IBB::NS, d->jingleIBBManager); d->jingleManager->registerTransport(Jingle::ICE::NS, d->jingleICEManager); } Client::~Client() { // fprintf(stderr, "\tClient::~Client\n"); // fflush(stderr); close(true); delete d->ftman; delete d->ibbman; delete d->s5bman; delete d->jingleManager; delete d->root; delete d; // fprintf(stderr, "\tClient::~Client\n"); } void Client::connectToServer(ClientStream *s, const Jid &j, bool auth) { d->stream = s; // connect(d->stream, SIGNAL(connected()), SLOT(streamConnected())); // connect(d->stream, SIGNAL(handshaken()), SLOT(streamHandshaken())); connect(d->stream, SIGNAL(error(int)), SLOT(streamError(int))); // connect(d->stream, SIGNAL(sslCertificateReady(QSSLCert)), SLOT(streamSSLCertificateReady(QSSLCert))); connect(d->stream, SIGNAL(readyRead()), SLOT(streamReadyRead())); // connect(d->stream, SIGNAL(closeFinished()), SLOT(streamCloseFinished())); connect(d->stream, SIGNAL(incomingXml(QString)), SLOT(streamIncomingXml(QString))); connect(d->stream, SIGNAL(outgoingXml(QString)), SLOT(streamOutgoingXml(QString))); connect(d->stream, SIGNAL(haveUnhandledFeatures()), SLOT(parseUnhandledStreamFeatures())); d->stream->connectToServer(j, auth); } void Client::start(const QString &host, const QString &user, const QString &pass, const QString &_resource) { // TODO d->host = host; d->user = user; d->pass = pass; d->resource = _resource; Status stat; stat.setIsAvailable(false); d->resourceList += Resource(resource(), stat); JT_PushPresence *pp = new JT_PushPresence(rootTask()); connect(pp, SIGNAL(subscription(Jid, QString, QString)), SLOT(ppSubscription(Jid, QString, QString))); connect(pp, SIGNAL(presence(Jid, Status)), SLOT(ppPresence(Jid, Status))); JT_PushMessage *pm = new JT_PushMessage(rootTask(), d->encryptionHandler); connect(pm, SIGNAL(message(Message)), SLOT(pmMessage(Message))); JT_PushRoster *pr = new JT_PushRoster(rootTask()); connect(pr, SIGNAL(roster(Roster)), SLOT(prRoster(Roster))); new JT_ServInfo(rootTask()); new JT_PongServer(rootTask()); d->active = true; } void Client::setTcpPortReserver(TcpPortReserver *portReserver) { d->tcpPortReserver = portReserver; } TcpPortReserver *Client::tcpPortReserver() const { return d->tcpPortReserver; } void Client::setFileTransferEnabled(bool b) { if (b) { if (!d->ftman) d->ftman = new FileTransferManager(this); } else { if (d->ftman) { delete d->ftman; d->ftman = nullptr; } } } FileTransferManager *Client::fileTransferManager() const { return d->ftman; } S5BManager *Client::s5bManager() const { return d->s5bman; } Jingle::S5B::Manager *Client::jingleS5BManager() const { return d->jingleS5BManager; } Jingle::IBB::Manager *Client::jingleIBBManager() const { return d->jingleIBBManager; } Jingle::ICE::Manager *Client::jingleICEManager() const { return d->jingleICEManager; } IBBManager *Client::ibbManager() const { return d->ibbman; } BoBManager *Client::bobManager() const { return d->bobman; } CapsManager *Client::capsManager() const { return d->capsman; } void Client::setCapsOptimizationAllowed(bool allowed) { d->capsOptimization = allowed; } bool Client::capsOptimizationAllowed() const { if (d->capsOptimization && d->active && d->serverInfoManager->features().hasCapsOptimize()) { auto it = d->resourceList.find(d->resource); return it != d->resourceList.end() && it->status().isAvailable(); } return false; } ServerInfoManager *Client::serverInfoManager() const { return d->serverInfoManager; } HttpFileUploadManager *Client::httpFileUploadManager() const { return d->httpFileUploadManager; } Jingle::Manager *Client::jingleManager() const { return d->jingleManager; } bool Client::isActive() const { return d->active; } QString Client::groupChatPassword(const QString &host, const QString &room) const { Jid jid(room + "@" + host); for (const GroupChat &i : d->groupChatList) { if (i.j.compare(jid, false)) { return i.password; } } return QString(); } void Client::groupChatChangeNick(const QString &host, const QString &room, const QString &nick, const Status &_s) { Jid jid(room + "@" + host + "/" + nick); for (QList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { GroupChat &i = *it; if (i.j.compare(jid, false)) { i.j = jid; Status s = _s; s.setIsAvailable(true); JT_Presence *j = new JT_Presence(rootTask()); j->pres(jid, s); j->go(true); break; } } } bool Client::groupChatJoin(const QString &host, const QString &room, const QString &nick, const QString &password, int maxchars, int maxstanzas, int seconds, const QDateTime &since, const Status &_s) { Jid jid(room + "@" + host + "/" + nick); for (QList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end();) { GroupChat &i = *it; if (i.j.compare(jid, false)) { // if this room is shutting down, then free it up if (i.status == GroupChat::Closing) it = d->groupChatList.erase(it); else return false; } else ++it; } debug(QString("Client: Joined: [%1]\n").arg(jid.full())); GroupChat i; i.j = jid; i.status = GroupChat::Connecting; i.password = password; d->groupChatList += i; JT_Presence *j = new JT_Presence(rootTask()); Status s = _s; s.setMUC(); s.setMUCHistory(maxchars, maxstanzas, seconds, since); if (!password.isEmpty()) { s.setMUCPassword(password); } j->pres(jid, s); j->go(true); return true; } void Client::groupChatSetStatus(const QString &host, const QString &room, const Status &_s) { Jid jid(room + "@" + host); bool found = false; for (const GroupChat &i : d->groupChatList) { if (i.j.compare(jid, false)) { found = true; jid = i.j; break; } } if (!found) return; Status s = _s; s.setIsAvailable(true); JT_Presence *j = new JT_Presence(rootTask()); j->pres(jid, s); j->go(true); } void Client::groupChatLeave(const QString &host, const QString &room, const QString &statusStr) { Jid jid(room + "@" + host); for (QList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { GroupChat &i = *it; if (!i.j.compare(jid, false)) continue; i.status = GroupChat::Closing; debug(QString("Client: Leaving: [%1]\n").arg(i.j.full())); JT_Presence *j = new JT_Presence(rootTask()); Status s; s.setIsAvailable(false); s.setStatus(statusStr); j->pres(i.j, s); j->go(true); } } void Client::groupChatLeaveAll(const QString &statusStr) { if (d->stream && d->active) { for (QList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { GroupChat &i = *it; i.status = GroupChat::Closing; JT_Presence *j = new JT_Presence(rootTask()); Status s; s.setIsAvailable(false); s.setStatus(statusStr); j->pres(i.j, s); j->go(true); } } } QString Client::groupChatNick(const QString &host, const QString &room) const { Jid jid(room + "@" + host); for (const GroupChat &gc : d->groupChatList) { if (gc.j.compare(jid, false)) { return gc.j.resource(); } } return QString(); } /*void Client::start() { if(d->stream->old()) { // old has no activation step d->active = true; activated(); } else { // TODO: IM session } }*/ // TODO: fast close void Client::close(bool) { // fprintf(stderr, "\tClient::close\n"); // fflush(stderr); if (d->stream) { d->stream->disconnect(this); d->stream->close(); d->stream = nullptr; } disconnected(); cleanup(); // TODO wait till stream writes all data to the socket } void Client::cleanup() { d->active = false; // d->authed = false; d->groupChatList.clear(); } /*void Client::continueAfterCert() { d->stream->continueAfterCert(); } void Client::streamConnected() { connected(); } void Client::streamHandshaken() { handshaken(); }*/ void Client::streamError(int) { // StreamError e = err; // error(e); // if(!e.isWarning()) { disconnected(); cleanup(); //} } // namespace XMPP /*void Client::streamSSLCertificateReady(const QSSLCert &cert) { sslCertReady(cert); } void Client::streamCloseFinished() { closeFinished(); }*/ void Client::streamReadyRead() { // fprintf(stderr, "\tClientStream::streamReadyRead\n"); // fflush(stderr); while (d->stream && d->stream->stanzaAvailable()) { Stanza s = d->stream->read(); QString out = s.toString(); debug(QString("Client: incoming: [\n%1]\n").arg(out)); emit xmlIncoming(out); QDomElement x = s.element(); // oldStyleNS(s.element()); distribute(x); } } void Client::streamIncomingXml(const QString &s) { QString str = s; if (str.at(str.length() - 1) != '\n') str += '\n'; emit xmlIncoming(str); } void Client::streamOutgoingXml(const QString &s) { QString str = s; if (str.at(str.length() - 1) != '\n') str += '\n'; emit xmlOutgoing(str); } void Client::parseUnhandledStreamFeatures() { QList nl = d->stream->unhandledFeatures(); for (const QDomElement &e : nl) { if (e.localName() == "c" && e.namespaceURI() == NS_CAPS) { d->serverCaps = CapsSpec::fromXml(e); if (d->capsman->isEnabled()) { d->capsman->updateCaps(Jid(d->stream->jid().domain()), d->serverCaps); } } } } void Client::debug(const QString &str) { emit debugText(str); } QString Client::genUniqueId() { QString s = QString::asprintf("a%x", d->id_seed); d->id_seed += 0x10; return s; } Task *Client::rootTask() { return d->root; } QDomDocument *Client::doc() const { return &d->doc; } void Client::distribute(const QDomElement &x) { static QString fromAttr(QStringLiteral("from")); if (x.hasAttribute(fromAttr)) { Jid j(x.attribute(fromAttr)); if (!j.isValid()) { debug("Client: bad 'from' JID\n"); return; } } if (!rootTask()->take(x) && (x.attribute("type") == "get" || x.attribute("type") == "set")) { debug("Client: Unrecognized IQ.\n"); // Create reply element QDomElement reply = createIQ(doc(), "error", x.attribute("from"), x.attribute("id")); // Copy children for (QDomNode n = x.firstChild(); !n.isNull(); n = n.nextSibling()) { reply.appendChild(n.cloneNode()); } // Add error QDomElement error = doc()->createElement("error"); error.setAttribute("type", "cancel"); reply.appendChild(error); QDomElement error_type = doc()->createElementNS(QLatin1String("urn:ietf:params:xml:ns:xmpp-stanzas"), QLatin1String("feature-not-implemented")); error.appendChild(error_type); send(reply); } } void Client::send(const QDomElement &x) { if (!d->stream) return; // QString out; // QTextStream ts(&out, IO_WriteOnly); // x.save(ts, 0); // QString out = Stream::xmlToString(x); // debug(QString("Client: outgoing: [\n%1]\n").arg(out)); // xmlOutgoing(out); QDomElement e = addCorrectNS(x); Stanza s = d->stream->createStanza(e); if (s.isNull()) { // e's namespace is not "jabber:client" or e.tagName is not in (message,presence,iq) // printf("bad stanza??\n"); return; } emit stanzaElementOutgoing(e); // signal handler may change the node (TODO weird design?) if (e.isNull()) { // so it was changed by signal above return; } QString out = s.toString(); // qWarning() << "Out: " << out; debug(QString("Client: outgoing: [\n%1]\n").arg(out)); emit xmlOutgoing(out); // printf("x[%s] x2[%s] s[%s]\n", Stream::xmlToString(x).toLatin1(), Stream::xmlToString(e).toLatin1(), // s.toString().toLatin1()); d->stream->write(s); } void Client::send(const QString &str) { if (!d->stream) return; debug(QString("Client: outgoing: [\n%1]\n").arg(str)); emit xmlOutgoing(str); static_cast(d->stream)->writeDirect(str); } /* drops any pending outgoing xml elements */ void Client::clearSendQueue() { if (d->stream) d->stream->clearSendQueue(); } bool Client::hasStream() const { return !!d->stream; } Stream &Client::stream() { return *(d->stream.data()); } QString Client::streamBaseNS() const { return d->stream->baseNS(); } const LiveRoster &Client::roster() const { return d->roster; } const ResourceList &Client::resourceList() const { return d->resourceList; } bool Client::isSessionRequired() const { return d->stream && !d->stream->old() && d->stream->streamFeatures().session_required; } QString Client::host() const { return d->host; } QString Client::user() const { return d->user; } QString Client::pass() const { return d->pass; } QString Client::resource() const { return d->resource; } Jid Client::jid() const { QString s; if (!d->user.isEmpty()) s += d->user + '@'; s += d->host; if (!d->resource.isEmpty()) { s += '/'; s += d->resource; } return Jid(s); } void Client::setNetworkAccessManager(QNetworkAccessManager *qnam) { d->qnam = qnam; } QNetworkAccessManager *Client::networkAccessManager() const { return d->qnam; } void Client::ppSubscription(const Jid &j, const QString &s, const QString &n) { emit subscription(j, s, n); } void Client::ppPresence(const Jid &j, const Status &s) { if (s.isAvailable()) debug(QString("Client: %1 is available.\n").arg(j.full())); else debug(QString("Client: %1 is unavailable.\n").arg(j.full())); for (QList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { GroupChat &i = *it; if (i.j.compare(j, false)) { bool us = (i.j.resource() == j.resource() || j.resource().isEmpty()) ? true : false; debug(QString("for groupchat i=[%1] pres=[%2], [us=%3].\n").arg(i.j.full()).arg(j.full()).arg(us)); switch (i.status) { case GroupChat::Connecting: if (us && s.hasError()) { Jid j = i.j; d->groupChatList.erase(it); emit groupChatError(j, s.errorCode(), s.errorString()); } else { // don't signal success unless it is a non-error presence if (!s.hasError()) { i.status = GroupChat::Connected; emit groupChatJoined(i.j); } emit groupChatPresence(j, s); } break; case GroupChat::Connected: emit groupChatPresence(j, s); break; case GroupChat::Closing: if (us && !s.isAvailable()) { Jid j = i.j; d->groupChatList.erase(it); emit groupChatLeft(j); } break; default: break; } return; } } if (s.hasError()) { emit presenceError(j, s.errorCode(), s.errorString()); return; } // is it me? if (j.compare(jid(), false)) { updateSelfPresence(j, s); } else { // update all relavent roster entries for (LiveRoster::Iterator it = d->roster.begin(); it != d->roster.end(); ++it) { LiveRosterItem &i = *it; if (!i.jid().compare(j, false)) continue; // roster item has its own resource? if (!i.jid().resource().isEmpty()) { if (i.jid().resource() != j.resource()) continue; } updatePresence(&i, j, s); } } } void Client::updateSelfPresence(const Jid &j, const Status &s) { ResourceList::Iterator rit = d->resourceList.find(j.resource()); bool found = (rit == d->resourceList.end()) ? false : true; // unavailable? remove the resource if (!s.isAvailable()) { if (found) { debug(QString("Client: Removing self resource: name=[%1]\n").arg(j.resource())); (*rit).setStatus(s); emit resourceUnavailable(j, *rit); d->resourceList.erase(rit); } } // available? add/update the resource else { Resource r; if (!found) { r = Resource(j.resource(), s); d->resourceList += r; debug(QString("Client: Adding self resource: name=[%1]\n").arg(j.resource())); } else { (*rit).setStatus(s); r = *rit; debug(QString("Client: Updating self resource: name=[%1]\n").arg(j.resource())); } emit resourceAvailable(j, r); } } void Client::updatePresence(LiveRosterItem *i, const Jid &j, const Status &s) { ResourceList::Iterator rit = i->resourceList().find(j.resource()); bool found = (rit == i->resourceList().end()) ? false : true; // unavailable? remove the resource if (!s.isAvailable()) { if (found) { (*rit).setStatus(s); debug(QString("Client: Removing resource from [%1]: name=[%2]\n").arg(i->jid().full()).arg(j.resource())); emit resourceUnavailable(j, *rit); i->resourceList().erase(rit); i->setLastUnavailableStatus(s); } else { // create the resource just for the purpose of emit Resource r = Resource(j.resource(), s); i->resourceList() += r; rit = i->resourceList().find(j.resource()); emit resourceUnavailable(j, *rit); i->resourceList().erase(rit); i->setLastUnavailableStatus(s); } } // available? add/update the resource else { Resource r; if (!found) { r = Resource(j.resource(), s); i->resourceList() += r; debug(QString("Client: Adding resource to [%1]: name=[%2]\n").arg(i->jid().full()).arg(j.resource())); } else { (*rit).setStatus(s); r = *rit; debug(QString("Client: Updating resource to [%1]: name=[%2]\n").arg(i->jid().full()).arg(j.resource())); } emit resourceAvailable(j, r); } } void Client::pmMessage(const Message &m) { debug(QString("Client: Message from %1\n").arg(m.from().full())); // bits of binary. we can't do this in Message, since it knows nothing about Client for (const BoBData &b : m.bobDataList()) { d->bobman->append(b); } if (!m.ibbData().data.isEmpty()) { d->ibbman->takeIncomingData(m.from(), m.id(), m.ibbData(), Stanza::Message); } if (m.type() == "groupchat") { for (QList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { const GroupChat &i = *it; if (!i.j.compare(m.from(), false)) continue; if (i.status == GroupChat::Connected) messageReceived(m); } } else messageReceived(m); } void Client::prRoster(const Roster &r) { importRoster(r); } void Client::rosterRequest(bool withGroupsDelimiter) { if (!d->active) return; JT_Roster *r = new JT_Roster(rootTask()); if (withGroupsDelimiter) { connect(r, &JT_Roster::finished, this, [this, r]() mutable { if (r->success()) { d->roster.setGroupsDelimiter(r->groupsDelimiter()); emit rosterGroupsDelimiterRequestFinished(r->groupsDelimiter()); } r = new JT_Roster(rootTask()); connect(r, SIGNAL(finished()), SLOT(slotRosterRequestFinished())); r->get(); d->roster.flagAllForDelete(); // mod_groups patch r->go(true); }); r->getGroupsDelimiter(); // WORKAROUND: Some bad servers (Facebook for example) don't respond // on groups delimiter request. Wait timeout and go ahead. r->setTimeout(GROUPS_DELIMITER_TIMEOUT); } else { connect(r, SIGNAL(finished()), SLOT(slotRosterRequestFinished())); r->get(); d->roster.flagAllForDelete(); // mod_groups patch } r->go(true); } void Client::slotRosterRequestFinished() { JT_Roster *r = static_cast(sender()); // on success, let's take it if (r->success()) { // d->roster.flagAllForDelete(); // mod_groups patch importRoster(r->roster()); for (LiveRoster::Iterator it = d->roster.begin(); it != d->roster.end();) { LiveRosterItem &i = *it; if (i.flagForDelete()) { emit rosterItemRemoved(i); it = d->roster.erase(it); } else ++it; } } else { // don't report a disconnect. Client::error() will do that. if (r->statusCode() == Task::ErrDisc) return; } // report success / fail emit rosterRequestFinished(r->success(), r->statusCode(), r->statusString()); } void Client::importRoster(const Roster &r) { emit beginImportRoster(); for (Roster::ConstIterator it = r.begin(); it != r.end(); ++it) { importRosterItem(*it); } emit endImportRoster(); } void Client::importRosterItem(const RosterItem &item) { QString substr; switch (item.subscription().type()) { case Subscription::Both: substr = "<-->"; break; case Subscription::From: substr = " ->"; break; case Subscription::To: substr = "<- "; break; case Subscription::Remove: substr = "xxxx"; break; case Subscription::None: default: substr = "----"; break; } QString dstr, str = QString::asprintf(" %s %-32s", qPrintable(substr), qPrintable(item.jid().full())); if (!item.name().isEmpty()) str += QString(" [") + item.name() + "]"; str += '\n'; // Remove if (item.subscription().type() == Subscription::Remove) { LiveRoster::Iterator it = d->roster.find(item.jid()); if (it != d->roster.end()) { emit rosterItemRemoved(*it); d->roster.erase(it); } dstr = "Client: (Removed) "; } // Add/Update else { LiveRoster::Iterator it = d->roster.find(item.jid()); if (it != d->roster.end()) { LiveRosterItem &i = *it; i.setFlagForDelete(false); i.setRosterItem(item); emit rosterItemUpdated(i); dstr = "Client: (Updated) "; } else { LiveRosterItem i(item); d->roster += i; // signal it emit rosterItemAdded(i); dstr = "Client: (Added) "; } } debug(dstr + str); } void Client::sendMessage(Message &m) { JT_Message *j = new JT_Message(rootTask(), m); j->go(true); } void Client::sendSubscription(const Jid &jid, const QString &type, const QString &nick) { JT_Presence *j = new JT_Presence(rootTask()); j->sub(jid, type, nick); j->go(true); } void Client::setPresence(const Status &s) { if (d->capsman->isEnabled()) { if (d->caps.version().isEmpty() && !d->caps.node().isEmpty()) { d->caps = CapsSpec(makeDiscoResult(d->caps.node())); /* recompute caps hash */ } } JT_Presence *j = new JT_Presence(rootTask()); j->pres(s); j->go(true); // update our resourceList ppPresence(jid(), s); // ResourceList::Iterator rit = d->resourceList.find(resource()); // Resource &r = *rit; // r.setStatus(s); } QString Client::OSName() const { return d->osName; } QString Client::OSVersion() const { return d->osVersion; } QString Client::timeZone() const { return d->tzname; } int Client::timeZoneOffset() const { return d->tzoffset; } /** \brief Returns true if Client is using old, manual time zone conversions. By default, conversions between UTC and local time are done automatically by Qt. In this mode, manualTimeZoneOffset() returns true, and timeZoneOffset() always retuns 0 (so you shouldn't use that function). However, if you call setTimeZone(), Client instance switches to old mode and uses given time zone offset for all calculations. */ bool Client::manualTimeZoneOffset() const { return d->useTzoffset; } QString Client::clientName() const { return d->clientName; } QString Client::clientVersion() const { return d->clientVersion; } CapsSpec Client::caps() const { return d->caps; } CapsSpec Client::serverCaps() const { return d->serverCaps; } void Client::setOSName(const QString &name) { if (d->osName != name) d->caps.resetVersion(); d->osName = name; } void Client::setOSVersion(const QString &version) { if (d->osVersion != version) d->caps.resetVersion(); d->osVersion = version; } void Client::setTimeZone(const QString &name, int offset) { d->tzname = name; d->tzoffset = offset; d->useTzoffset = true; } void Client::setClientName(const QString &s) { if (d->clientName != s) d->caps.resetVersion(); d->clientName = s; } void Client::setClientVersion(const QString &s) { if (d->clientVersion != s) d->caps.resetVersion(); d->clientVersion = s; } void Client::setCaps(const CapsSpec &s) { d->caps = s; } void Client::setEncryptionHandler(EncryptionHandler *encryptionHandler) { d->encryptionHandler = encryptionHandler; } EncryptionHandler *Client::encryptionHandler() const { return d->encryptionHandler; } DiscoItem::Identity Client::identity() const { return d->identity; } void Client::setIdentity(const DiscoItem::Identity &identity) { if (!(d->identity == identity)) { d->caps.resetVersion(); } d->identity = identity; } void Client::setFeatures(const Features &f) { if (!(d->features == f)) { d->caps.resetVersion(); } d->features = f; } const Features &Client::features() const { return d->features; } DiscoItem Client::makeDiscoResult(const QString &node) const { DiscoItem item; item.setNode(node); DiscoItem::Identity id = identity(); if (id.category.isEmpty() || id.type.isEmpty()) { id.category = "client"; id.type = "pc"; } item.setIdentities(id); Features features; if (d->ftman) { features.addFeature("http://jabber.org/protocol/bytestreams"); features.addFeature("http://jabber.org/protocol/ibb"); features.addFeature("http://jabber.org/protocol/si"); features.addFeature("http://jabber.org/protocol/si/profile/file-transfer"); } features.addFeature("http://jabber.org/protocol/disco#info"); features.addFeature("jabber:x:data"); features.addFeature("urn:xmpp:bob"); features.addFeature("urn:xmpp:ping"); features.addFeature("urn:xmpp:time"); features.addFeature("urn:xmpp:message-correct:0"); features.addFeature("urn:xmpp:jingle:1"); // TODO rather do foreach for all registered jingle apps and transports features.addFeature("urn:xmpp:jingle:transports:s5b:1"); features.addFeature("urn:xmpp:jingle:transports:ibb:1"); // TODO: since it depends on UI it needs a way to be disabled features.addFeature("urn:xmpp:jingle:apps:file-transfer:5"); Hash::populateFeatures(features); features.addFeature(NS_CAPS); // Client-specific features for (const QString &i : d->features.list()) { features.addFeature(i); } item.setFeatures(features); // xep-0232 Software Information XData si; XData::FieldList si_fields; XData::Field si_type_field; si_type_field.setType(XData::Field::Field_Hidden); si_type_field.setVar("FORM_TYPE"); si_type_field.setValue(QStringList(QLatin1String("urn:xmpp:dataforms:softwareinfo"))); si_fields.append(si_type_field); XData::Field software_field; software_field.setType(XData::Field::Field_TextSingle); software_field.setVar("software"); software_field.setValue(QStringList(d->clientName)); si_fields.append(software_field); XData::Field software_v_field; software_v_field.setType(XData::Field::Field_TextSingle); software_v_field.setVar("software_version"); software_v_field.setValue(QStringList(d->clientVersion)); si_fields.append(software_v_field); XData::Field os_field; os_field.setType(XData::Field::Field_TextSingle); os_field.setVar("os"); os_field.setValue(QStringList(d->osName)); si_fields.append(os_field); XData::Field os_v_field; os_v_field.setType(XData::Field::Field_TextSingle); os_v_field.setVar("os_version"); os_v_field.setValue(QStringList(d->osVersion)); si_fields.append(os_v_field); si.setType(XData::Data_Result); si.setFields(si_fields); item.setExtensions(QList() << si); return item; } void Client::s5b_incomingReady() { handleIncoming(d->s5bman->takeIncoming()); } void Client::ibb_incomingReady() { auto c = d->ibbman->takeIncoming(); if (!c) return; if (d->jingleIBBManager && d->jingleIBBManager->handleIncoming(c)) return; handleIncoming(c); } void Client::handleIncoming(BSConnection *c) { if (!c) return; if (!d->ftman) { c->close(); c->deleteLater(); return; } d->ftman->stream_incomingReady(c); } void Client::handleSMAckResponse(int h) { qDebug() << "handleSMAckResponse: h = " << h; } //--------------------------------------------------------------------------- // LiveRosterItem //--------------------------------------------------------------------------- LiveRosterItem::LiveRosterItem(const Jid &jid) : RosterItem(jid) { setFlagForDelete(false); } LiveRosterItem::LiveRosterItem(const RosterItem &i) { setRosterItem(i); setFlagForDelete(false); } LiveRosterItem::~LiveRosterItem() { } void LiveRosterItem::setRosterItem(const RosterItem &i) { setJid(i.jid()); setName(i.name()); setGroups(i.groups()); setSubscription(i.subscription()); setAsk(i.ask()); setIsPush(i.isPush()); } ResourceList &LiveRosterItem::resourceList() { return v_resourceList; } ResourceList::Iterator LiveRosterItem::priority() { return v_resourceList.priority(); } const ResourceList &LiveRosterItem::resourceList() const { return v_resourceList; } ResourceList::ConstIterator LiveRosterItem::priority() const { return v_resourceList.priority(); } bool LiveRosterItem::isAvailable() const { if (v_resourceList.count() > 0) return true; return false; } const Status &LiveRosterItem::lastUnavailableStatus() const { return v_lastUnavailableStatus; } bool LiveRosterItem::flagForDelete() const { return v_flagForDelete; } void LiveRosterItem::setLastUnavailableStatus(const Status &s) { v_lastUnavailableStatus = s; } void LiveRosterItem::setFlagForDelete(bool b) { v_flagForDelete = b; } //--------------------------------------------------------------------------- // LiveRoster //--------------------------------------------------------------------------- class LiveRoster::Private { public: QString groupsDelimiter; }; LiveRoster::LiveRoster() : QList(), d(new LiveRoster::Private) { } LiveRoster::LiveRoster(const LiveRoster &other) : QList(other), d(new LiveRoster::Private) { d->groupsDelimiter = other.d->groupsDelimiter; } LiveRoster::~LiveRoster() { delete d; } LiveRoster &LiveRoster::operator=(const LiveRoster &other) { QList::operator=(other); d->groupsDelimiter = other.d->groupsDelimiter; return *this; } void LiveRoster::flagAllForDelete() { for (Iterator it = begin(); it != end(); ++it) (*it).setFlagForDelete(true); } LiveRoster::Iterator LiveRoster::find(const Jid &j, bool compareRes) { Iterator it; for (it = begin(); it != end(); ++it) { if ((*it).jid().compare(j, compareRes)) break; } return it; } LiveRoster::ConstIterator LiveRoster::find(const Jid &j, bool compareRes) const { ConstIterator it; for (it = begin(); it != end(); ++it) { if ((*it).jid().compare(j, compareRes)) break; } return it; } void LiveRoster::setGroupsDelimiter(const QString &groupsDelimiter) { d->groupsDelimiter = groupsDelimiter; } QString LiveRoster::groupsDelimiter() const { return d->groupsDelimiter; } } psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/filetransfer.cpp000066400000000000000000000565001370065651000250610ustar00rootroot00000000000000/* * filetransfer.cpp - File Transfer * Copyright (C) 2004 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "filetransfer.h" #include "s5b.h" #include "xmpp_client.h" #include "xmpp_ibb.h" #include "xmpp_stream.h" #include "xmpp_xmlcommon.h" #include #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) #include #endif #define SENDBUFSIZE 65536 using namespace XMPP; // firstChildElement // // Get an element's first child element static QDomElement firstChildElement(const QDomElement &e) { for (QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { if (n.isElement()) return n.toElement(); } return QDomElement(); } //---------------------------------------------------------------------------- // FileTransfer //---------------------------------------------------------------------------- class FileTransfer::Private { public: FileTransferManager *m; JT_FT * ft; Jid peer; QString fname; qlonglong size; qlonglong sent; QString desc; bool rangeSupported; qlonglong rangeOffset, rangeLength, length; QString streamType; Thumbnail thumbnail; bool needStream; QString id, iq_id; BSConnection * c; Jid proxy; int state; bool sender; }; FileTransfer::FileTransfer(FileTransferManager *m, QObject *parent) : QObject(parent) { d = new Private; d->m = m; d->ft = nullptr; d->c = nullptr; reset(); } FileTransfer::FileTransfer(const FileTransfer &other) : QObject(other.parent()) { d = new Private; *d = *other.d; d->m = other.d->m; d->ft = nullptr; d->c = 0; reset(); if (d->m->isActive(&other)) d->m->link(this); } FileTransfer::~FileTransfer() { reset(); delete d; } FileTransfer *FileTransfer::copy() const { return new FileTransfer(*this); } void FileTransfer::reset() { d->m->unlink(this); delete d->ft; d->ft = nullptr; if (d->c) { d->c->disconnect(this); d->c->manager()->deleteConnection(d->c, d->state == Active && !d->sender ? 3000 : 0); d->c = nullptr; } d->state = Idle; d->needStream = false; d->sent = 0; d->sender = false; } void FileTransfer::setProxy(const Jid &proxy) { d->proxy = proxy; } void FileTransfer::sendFile(const Jid &to, const QString &fname, qlonglong size, const QString &desc, Thumbnail &thumb) { d->state = Requesting; d->peer = to; d->fname = fname; d->size = size; d->desc = desc; d->sender = true; d->id = d->m->link(this); d->ft = new JT_FT(d->m->client()->rootTask()); connect(d->ft, SIGNAL(finished()), SLOT(ft_finished())); d->ft->request(to, d->id, fname, size, desc, d->m->streamPriority(), thumb); d->ft->go(true); } int FileTransfer::dataSizeNeeded() const { int pending = d->c->bytesToWrite(); if (pending >= SENDBUFSIZE) return 0; qlonglong left = d->length - (d->sent + pending); int size = SENDBUFSIZE - pending; if ((qlonglong)size > left) size = (int)left; return size; } void FileTransfer::writeFileData(const QByteArray &a) { int pending = d->c->bytesToWrite(); qlonglong left = d->length - (d->sent + pending); if (left == 0) return; QByteArray block; if ((qlonglong)a.size() > left) { block = a; block.resize((uint)left); } else block = a; d->c->write(block); } const Thumbnail &FileTransfer::thumbnail() const { return d->thumbnail; } Jid FileTransfer::peer() const { return d->peer; } QString FileTransfer::fileName() const { return d->fname; } qlonglong FileTransfer::fileSize() const { return d->size; } QString FileTransfer::description() const { return d->desc; } bool FileTransfer::rangeSupported() const { return d->rangeSupported; } qlonglong FileTransfer::offset() const { return d->rangeOffset; } qlonglong FileTransfer::length() const { return d->length; } void FileTransfer::accept(qlonglong offset, qlonglong length) { d->state = Connecting; d->rangeOffset = offset; d->rangeLength = length; if (length > 0) d->length = length; else d->length = d->size; d->m->con_accept(this); } void FileTransfer::close() { if (d->state == Idle) return; if (d->state == WaitingForAccept) d->m->con_reject(this); else if (d->state == Active) d->c->close(); reset(); } BSConnection *FileTransfer::bsConnection() const { return d->c; } // file transfer request accepted or error happened void FileTransfer::ft_finished() { JT_FT *ft = d->ft; d->ft = nullptr; if (ft->success()) { d->state = Connecting; d->rangeOffset = ft->rangeOffset(); d->length = ft->rangeLength(); if (d->length == 0) d->length = d->size - d->rangeOffset; d->streamType = ft->streamType(); BytestreamManager *streamManager = d->m->streamManager(d->streamType); if (streamManager) { d->c = streamManager->createConnection(); if (dynamic_cast(streamManager) && d->proxy.isValid()) { ((S5BConnection *)(d->c))->setProxy(d->proxy); } connect(d->c, SIGNAL(connected()), SLOT(stream_connected())); connect(d->c, SIGNAL(connectionClosed()), SLOT(stream_connectionClosed())); connect(d->c, SIGNAL(bytesWritten(qint64)), SLOT(stream_bytesWritten(qint64))); connect(d->c, SIGNAL(error(int)), SLOT(stream_error(int))); d->c->connectToJid(d->peer, d->id); accepted(); } else { emit error(Err400); reset(); } } else { if (ft->statusCode() == 403) emit error(ErrReject); else if (ft->statusCode() == 400) emit error(Err400); else emit error(ErrNeg); reset(); } } void FileTransfer::takeConnection(BSConnection *c) { d->c = c; connect(d->c, SIGNAL(connected()), SLOT(stream_connected())); connect(d->c, SIGNAL(connectionClosed()), SLOT(stream_connectionClosed())); connect(d->c, SIGNAL(readyRead()), SLOT(stream_readyRead())); connect(d->c, SIGNAL(error(int)), SLOT(stream_error(int))); S5BConnection *s5b = dynamic_cast(c); if (s5b && d->proxy.isValid()) s5b->setProxy(d->proxy); accepted(); QTimer::singleShot(0, this, SLOT(doAccept())); } void FileTransfer::stream_connected() { d->state = Active; emit connected(); } void FileTransfer::stream_connectionClosed() { bool err = (d->sent != d->length); reset(); if (err) emit error(ErrStream); } void FileTransfer::stream_readyRead() { QByteArray a = d->c->readAll(); qlonglong need = d->length - d->sent; if ((qlonglong)a.size() > need) a.resize((uint)need); d->sent += a.size(); // if(d->sent == d->length) // we close it in stream_connectionClosed. at least for ibb // reset(); // in other words we wait for another party to close the connection readyRead(a); } void FileTransfer::stream_bytesWritten(qint64 x) { d->sent += x; if (d->sent == d->length) reset(); emit bytesWritten(x); } void FileTransfer::stream_error(int x) { reset(); if (x == BSConnection::ErrRefused || x == BSConnection::ErrConnect) error(ErrConnect); else if (x == BSConnection::ErrProxy) error(ErrProxy); else error(ErrStream); } void FileTransfer::man_waitForAccept(const FTRequest &req, const QString &streamType) { d->state = WaitingForAccept; d->peer = req.from; d->id = req.id; d->iq_id = req.iq_id; d->fname = req.fname; d->size = req.size; d->desc = req.desc; d->rangeSupported = req.rangeSupported; d->streamType = streamType; d->thumbnail = req.thumbnail; } void FileTransfer::doAccept() { d->c->accept(); } //---------------------------------------------------------------------------- // FileTransferManager //---------------------------------------------------------------------------- class FileTransferManager::Private { public: Client * client; QList list, incoming; QStringList streamPriority; QHash streamMap; QSet disabledStreamTypes; JT_PushFT * pft; }; FileTransferManager::FileTransferManager(Client *client) : QObject(client) { d = new Private; d->client = client; if (client->s5bManager()) { d->streamPriority.append(S5BManager::ns()); d->streamMap[S5BManager::ns()] = client->s5bManager(); } if (client->ibbManager()) { d->streamPriority.append(IBBManager::ns()); d->streamMap[IBBManager::ns()] = client->ibbManager(); } d->pft = new JT_PushFT(d->client->rootTask()); connect(d->pft, SIGNAL(incoming(FTRequest)), SLOT(pft_incoming(FTRequest))); } FileTransferManager::~FileTransferManager() { while (!d->incoming.isEmpty()) { delete d->incoming.takeFirst(); } delete d->pft; delete d; } Client *FileTransferManager::client() const { return d->client; } FileTransfer *FileTransferManager::createTransfer() { FileTransfer *ft = new FileTransfer(this); return ft; } FileTransfer *FileTransferManager::takeIncoming() { if (d->incoming.isEmpty()) return nullptr; FileTransfer *ft = d->incoming.takeFirst(); // move to active list d->list.append(ft); return ft; } bool FileTransferManager::isActive(const FileTransfer *ft) const { return d->list.contains(const_cast(ft)); } void FileTransferManager::setDisabled(const QString &ns, bool state) { if (state) { d->disabledStreamTypes.insert(ns); } else { d->disabledStreamTypes.remove(ns); } } void FileTransferManager::pft_incoming(const FTRequest &req) { QString streamType; for (const QString &ns : d->streamPriority) { if (req.streamTypes.contains(ns)) { BytestreamManager *manager = streamManager(ns); if (manager && manager->isAcceptableSID(req.from, req.id)) { streamType = ns; break; } } } if (streamType.isEmpty()) { d->pft->respondError(req.from, req.iq_id, Stanza::Error::NotAcceptable, "No valid stream types"); return; } FileTransfer *ft = new FileTransfer(this); ft->man_waitForAccept(req, streamType); d->incoming.append(ft); incomingReady(); } BytestreamManager *FileTransferManager::streamManager(const QString &ns) const { if (d->disabledStreamTypes.contains(ns)) { return nullptr; } return d->streamMap.value(ns); } QStringList FileTransferManager::streamPriority() const { QStringList ret; for (const QString &ns : d->streamPriority) { if (!d->disabledStreamTypes.contains(ns)) { ret.append(ns); } } return ret; } void FileTransferManager::stream_incomingReady(BSConnection *c) { for (FileTransfer *ft : d->list) { if (ft->d->needStream && ft->d->peer.compare(c->peer()) && ft->d->id == c->sid()) { ft->takeConnection(c); return; } } c->close(); delete c; } QString FileTransferManager::link(FileTransfer *ft) { QString id; bool found; do { found = false; #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) id = QString("ft_%1").arg(QRandomGenerator::global()->generate() & 0xffff, 4, 16, QChar('0')); #else id = QString("ft_%1").arg(qrand() & 0xffff, 4, 16, QChar('0')); #endif for (FileTransfer *ft : d->list) { if (ft->d->peer.compare(ft->d->peer) && ft->d->id == id) { found = true; break; } } } while (found); d->list.append(ft); return id; } void FileTransferManager::con_accept(FileTransfer *ft) { ft->d->needStream = true; d->pft->respondSuccess(ft->d->peer, ft->d->iq_id, ft->d->rangeOffset, ft->d->rangeLength, ft->d->streamType); } void FileTransferManager::con_reject(FileTransfer *ft) { d->pft->respondError(ft->d->peer, ft->d->iq_id, Stanza::Error::Forbidden, "Declined"); } void FileTransferManager::unlink(FileTransfer *ft) { d->list.removeAll(ft); } //---------------------------------------------------------------------------- // JT_FT //---------------------------------------------------------------------------- class JT_FT::Private { public: QDomElement iq; Jid to; qlonglong size, rangeOffset, rangeLength; QString streamType; QStringList streamTypes; }; JT_FT::JT_FT(Task *parent) : Task(parent) { d = new Private; } JT_FT::~JT_FT() { delete d; } void JT_FT::request(const Jid &to, const QString &_id, const QString &fname, qlonglong size, const QString &desc, const QStringList &streamTypes, Thumbnail &thumb) { QDomElement iq; d->to = to; iq = createIQ(doc(), "set", to.full(), id()); QDomElement si = doc()->createElementNS("http://jabber.org/protocol/si", "si"); si.setAttribute("id", _id); si.setAttribute("profile", "http://jabber.org/protocol/si/profile/file-transfer"); QDomElement file = doc()->createElementNS("http://jabber.org/protocol/si/profile/file-transfer", "file"); file.setAttribute("name", fname); file.setAttribute("size", QString::number(size)); if (!desc.isEmpty()) { QDomElement de = doc()->createElement("desc"); de.appendChild(doc()->createTextNode(desc)); file.appendChild(de); } QDomElement range = doc()->createElement("range"); file.appendChild(range); if (!thumb.data.isEmpty()) { BoBData data = client()->bobManager()->append(thumb.data, thumb.mimeType); thumb.uri = QLatin1String("cid:") + data.cid(); file.appendChild(thumb.toXml(doc())); } si.appendChild(file); QDomElement feature = doc()->createElementNS("http://jabber.org/protocol/feature-neg", "feature"); QDomElement x = doc()->createElementNS("jabber:x:data", "x"); x.setAttribute("type", "form"); QDomElement field = doc()->createElement("field"); field.setAttribute("var", "stream-method"); field.setAttribute("type", "list-single"); for (QStringList::ConstIterator it = streamTypes.begin(); it != streamTypes.end(); ++it) { QDomElement option = doc()->createElement("option"); QDomElement value = doc()->createElement("value"); value.appendChild(doc()->createTextNode(*it)); option.appendChild(value); field.appendChild(option); } x.appendChild(field); feature.appendChild(x); si.appendChild(feature); iq.appendChild(si); d->streamTypes = streamTypes; d->size = size; d->iq = iq; } qlonglong JT_FT::rangeOffset() const { return d->rangeOffset; } qlonglong JT_FT::rangeLength() const { return d->rangeLength; } QString JT_FT::streamType() const { return d->streamType; } void JT_FT::onGo() { send(d->iq); } bool JT_FT::take(const QDomElement &x) { if (!iqVerify(x, d->to, id())) return false; if (x.attribute("type") == "result") { QDomElement si = firstChildElement(x); if (si.namespaceURI() != "http://jabber.org/protocol/si" || si.tagName() != "si") { setError(900, ""); return true; } QString id = si.attribute("id"); qlonglong range_offset = 0; qlonglong range_length = 0; QDomElement file = si.elementsByTagName("file").item(0).toElement(); if (!file.isNull()) { QDomElement range = file.elementsByTagName("range").item(0).toElement(); if (!range.isNull()) { qlonglong x; bool ok; if (range.hasAttribute("offset")) { x = range.attribute("offset").toLongLong(&ok); if (!ok || x < 0) { setError(900, ""); return true; } range_offset = x; } if (range.hasAttribute("length")) { x = range.attribute("length").toLongLong(&ok); if (!ok || x < 0) { setError(900, ""); return true; } range_length = x; } } } if (range_offset > d->size || (range_length > (d->size - range_offset))) { setError(900, ""); return true; } QString streamtype; QDomElement feature = si.elementsByTagName("feature").item(0).toElement(); if (!feature.isNull() && feature.namespaceURI() == "http://jabber.org/protocol/feature-neg") { QDomElement x = feature.elementsByTagName("x").item(0).toElement(); if (!x.isNull() && x.attribute("type") == "submit") { QDomElement field = x.elementsByTagName("field").item(0).toElement(); if (!field.isNull() && field.attribute("var") == "stream-method") { QDomElement value = field.elementsByTagName("value").item(0).toElement(); if (!value.isNull()) streamtype = value.text(); } } } // must be one of the offered streamtypes if (!d->streamTypes.contains(streamtype)) { return true; } d->rangeOffset = range_offset; d->rangeLength = range_length; d->streamType = streamtype; setSuccess(); } else { setError(x); } return true; } //---------------------------------------------------------------------------- // JT_PushFT //---------------------------------------------------------------------------- JT_PushFT::JT_PushFT(Task *parent) : Task(parent) { } JT_PushFT::~JT_PushFT() { } void JT_PushFT::respondSuccess(const Jid &to, const QString &id, qlonglong rangeOffset, qlonglong rangeLength, const QString &streamType) { QDomElement iq = createIQ(doc(), "result", to.full(), id); QDomElement si = doc()->createElementNS("http://jabber.org/protocol/si", "si"); if (rangeOffset != 0 || rangeLength != 0) { QDomElement file = doc()->createElementNS("http://jabber.org/protocol/si/profile/file-transfer", "file"); QDomElement range = doc()->createElement("range"); if (rangeOffset > 0) range.setAttribute("offset", QString::number(rangeOffset)); if (rangeLength > 0) range.setAttribute("length", QString::number(rangeLength)); file.appendChild(range); si.appendChild(file); } QDomElement feature = doc()->createElementNS("http://jabber.org/protocol/feature-neg", "feature"); QDomElement x = doc()->createElementNS("jabber:x:data", "x"); x.setAttribute("type", "submit"); QDomElement field = doc()->createElement("field"); field.setAttribute("var", "stream-method"); QDomElement value = doc()->createElement("value"); value.appendChild(doc()->createTextNode(streamType)); field.appendChild(value); x.appendChild(field); feature.appendChild(x); si.appendChild(feature); iq.appendChild(si); send(iq); } void JT_PushFT::respondError(const Jid &to, const QString &id, Stanza::Error::ErrorCond cond, const QString &str) { QDomElement iq = createIQ(doc(), "error", to.full(), id); Stanza::Error error(Stanza::Error::Cancel, cond, str); iq.appendChild(error.toXml(*client()->doc(), client()->stream().baseNS())); send(iq); } bool JT_PushFT::take(const QDomElement &e) { // must be an iq-set tag if (e.tagName() != "iq") return false; if (e.attribute("type") != "set") return false; QDomElement si = firstChildElement(e); if (si.namespaceURI() != "http://jabber.org/protocol/si" || si.tagName() != "si") return false; if (si.attribute("profile") != "http://jabber.org/protocol/si/profile/file-transfer") return false; Jid from(e.attribute("from")); QString id = si.attribute("id"); QDomElement file = si.elementsByTagName("file").item(0).toElement(); if (file.isNull()) return true; QString fname = file.attribute("name"); if (fname.isEmpty()) { respondError(from, id, Stanza::Error::BadRequest, "Bad file name"); return true; } // ensure kosher { QFileInfo fi(fname); fname = fi.fileName(); } bool ok; qlonglong size = file.attribute("size").toLongLong(&ok); if (!ok || size < 0) { respondError(from, id, Stanza::Error::BadRequest, "Bad file size"); return true; } QString desc; QDomElement de = file.elementsByTagName("desc").item(0).toElement(); if (!de.isNull()) desc = de.text(); bool rangeSupported = false; QDomElement range = file.elementsByTagName("range").item(0).toElement(); if (!range.isNull()) rangeSupported = true; QStringList streamTypes; QDomElement feature = si.elementsByTagName("feature").item(0).toElement(); if (!feature.isNull() && feature.namespaceURI() == "http://jabber.org/protocol/feature-neg") { QDomElement x = feature.elementsByTagName("x").item(0).toElement(); if (!x.isNull() /*&& x.attribute("type") == "form"*/) { QDomElement field = x.elementsByTagName("field").item(0).toElement(); if (!field.isNull() && field.attribute("var") == "stream-method" && field.attribute("type") == "list-single") { QDomNodeList nl = field.elementsByTagName("option"); for (int n = 0; n < nl.count(); ++n) { QDomElement e = nl.item(n).toElement(); QDomElement value = e.elementsByTagName("value").item(0).toElement(); if (!value.isNull()) streamTypes += value.text(); } } } } FTRequest r; r.from = from; r.iq_id = e.attribute("id"); r.id = id; r.fname = fname; r.size = size; r.desc = desc; r.rangeSupported = rangeSupported; r.streamTypes = streamTypes; r.thumbnail = Thumbnail(file.firstChildElement(QLatin1String("thumbnail"))); emit incoming(r); return true; } psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/filetransfer.h000066400000000000000000000123061370065651000245220ustar00rootroot00000000000000/* * filetransfer.h - File Transfer * Copyright (C) 2004 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_FILETRANSFER_H #define XMPP_FILETRANSFER_H #include "xmpp/jid/jid.h" #include "xmpp_task.h" #include "xmpp_thumbs.h" namespace XMPP { class BSConnection; class BytestreamManager; class Client; class FileTransferManager; struct FTRequest; class Thumbnail; /*class AbstractFileTransfer { public: // Receive virtual Jid peer() const = 0; virtual QString fileName() const = 0; virtual qlonglong fileSize() const = 0; virtual QString description() const { return ""; } virtual bool rangeSupported() const { return false; } virtual void accept(qlonglong offset=0, qlonglong length=0) = 0; };*/ class FileTransfer : public QObject /*, public AbstractFileTransfer */ { Q_OBJECT public: enum { ErrReject, ErrNeg, ErrConnect, ErrProxy, ErrStream, Err400 }; enum { Idle, Requesting, Connecting, WaitingForAccept, Active }; ~FileTransfer(); FileTransfer *copy() const; void setProxy(const Jid &proxy); // send void sendFile(const Jid &to, const QString &fname, qlonglong size, const QString &desc, Thumbnail &thumb); qlonglong offset() const; qlonglong length() const; int dataSizeNeeded() const; void writeFileData(const QByteArray &a); const Thumbnail &thumbnail() const; // receive Jid peer() const; QString fileName() const; qlonglong fileSize() const; QString description() const; bool rangeSupported() const; void accept(qlonglong offset = 0, qlonglong length = 0); // both void close(); // reject, or stop sending/receiving BSConnection *bsConnection() const; // active link signals: void accepted(); // indicates BSConnection has started void connected(); void readyRead(const QByteArray &a); void bytesWritten(qint64); void error(int); private slots: void ft_finished(); void stream_connected(); void stream_connectionClosed(); void stream_readyRead(); void stream_bytesWritten(qint64); void stream_error(int); void doAccept(); void reset(); private: class Private; Private *d; friend class FileTransferManager; FileTransfer(FileTransferManager *, QObject *parent = nullptr); FileTransfer(const FileTransfer &other); void man_waitForAccept(const FTRequest &req, const QString &streamType); void takeConnection(BSConnection *c); }; class FileTransferManager : public QObject { Q_OBJECT public: FileTransferManager(Client *); ~FileTransferManager(); bool isActive(const FileTransfer *ft) const; void setDisabled(const QString &ns, bool state = true); Client * client() const; FileTransfer *createTransfer(); FileTransfer *takeIncoming(); signals: void incomingReady(); private slots: void pft_incoming(const FTRequest &req); private: class Private; Private *d; friend class Client; void stream_incomingReady(BSConnection *); friend class FileTransfer; BytestreamManager *streamManager(const QString &ns) const; QStringList streamPriority() const; QString link(FileTransfer *); void con_accept(FileTransfer *); void con_reject(FileTransfer *); void unlink(FileTransfer *); }; class JT_FT : public Task { Q_OBJECT public: JT_FT(Task *parent); ~JT_FT(); void request(const Jid &to, const QString &id, const QString &fname, qlonglong size, const QString &desc, const QStringList &streamTypes, Thumbnail &thumb); qlonglong rangeOffset() const; qlonglong rangeLength() const; QString streamType() const; void onGo(); bool take(const QDomElement &); private: class Private; Private *d; }; struct FTRequest { Jid from; QString iq_id, id; QString fname; qlonglong size; QString desc; bool rangeSupported; QStringList streamTypes; Thumbnail thumbnail; }; class JT_PushFT : public Task { Q_OBJECT public: JT_PushFT(Task *parent); ~JT_PushFT(); void respondSuccess(const Jid &to, const QString &id, qlonglong rangeOffset, qlonglong rangeLength, const QString &streamType); void respondError(const Jid &to, const QString &id, Stanza::Error::ErrorCond cond, const QString &str); bool take(const QDomElement &); signals: void incoming(const FTRequest &req); }; } // namespace XMPP #endif // XMPP_FILETRANSFER_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/httpfileupload.cpp000066400000000000000000000423651370065651000254250ustar00rootroot00000000000000/* * httpfileupload.cpp - HTTP File upload * Copyright (C) 2017-2019 Aleksey Andreev, Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "httpfileupload.h" #include "xmpp_client.h" #include "xmpp_serverinfomanager.h" #include "xmpp_tasks.h" #include "xmpp_xmlcommon.h" #include #include #include #include #include using namespace XMPP; static QLatin1String xmlns_v0_2_5("urn:xmpp:http:upload"); static QLatin1String xmlns_v0_3_1("urn:xmpp:http:upload:0"); //---------------------------------------------------------------------------- // HttpFileUpload //---------------------------------------------------------------------------- class HttpFileUpload::Private { public: HttpFileUpload::State state = State::None; XMPP::Client * client = nullptr; QIODevice * sourceDevice = nullptr; QPointer qnam = nullptr; quint64 fileSize = 0; QString fileName; QString mediaType; QList httpHosts; struct { HttpFileUpload::ErrorCode statusCode = HttpFileUpload::ErrorCode::NoError; QString statusString; QString getUrl; QString putUrl; XEP0363::HttpHeaders putHeaders; quint64 sizeLimit = 0; } result; }; HttpFileUpload::HttpFileUpload(XMPP::Client *client, QIODevice *source, size_t fsize, const QString &dstFilename, const QString &mType) : QObject(client), d(new Private) { d->client = client; d->sourceDevice = source; d->fileName = dstFilename; d->fileSize = fsize; d->mediaType = mType; } HttpFileUpload::~HttpFileUpload() { qDebug("destroying"); } void HttpFileUpload::setNetworkAccessManager(QNetworkAccessManager *qnam) { d->qnam = qnam; } void HttpFileUpload::start() { if (d->state != State::None) // Attempt to start twice? return; setState(State::GettingSlot); d->result.statusCode = HttpFileUpload::ErrorCode::NoError; auto mgr = d->client->httpFileUploadManager(); if (mgr->discoveryStatus() == HttpFileUploadManager::DiscoNotFound) { d->result.statusCode = HttpFileUpload::ErrorCode::NoUploadService; d->result.statusString = "No suitable http upload services were found"; done(State::Error); return; } if (mgr->discoveryStatus() == HttpFileUploadManager::DiscoFound) { d->httpHosts = mgr->discoHosts(); tryNextServer(); return; } static QList> featureOptions; if (featureOptions.isEmpty()) { featureOptions << (QSet() << xmlns_v0_2_5) << (QSet() << xmlns_v0_3_1); } d->client->serverInfoManager()->queryServiceInfo( QLatin1String("store"), QLatin1String("file"), featureOptions, QRegExp("^(upload|http|stor|file|dis|drive).*"), ServerInfoManager::SQ_CheckAllOnNoMatch, [this](const QList &items) { d->httpHosts.clear(); for (const auto &item : items) { const QStringList &l = item.features().list(); XEP0363::version ver = XEP0363::vUnknown; QString xmlns; quint64 sizeLimit = 0; if (l.contains(xmlns_v0_3_1)) { ver = XEP0363::v0_3_1; xmlns = xmlns_v0_3_1; } else if (l.contains(xmlns_v0_2_5)) { ver = XEP0363::v0_2_5; xmlns = xmlns_v0_2_5; } if (ver != XEP0363::vUnknown) { QList> hosts; const XData::Field field = item.registeredExtension(xmlns).getField(QLatin1String("max-file-size")); if (field.isValid() && field.type() == XData::Field::Field_TextSingle) sizeLimit = field.value().at(0).toULongLong(); HttpHost host; host.ver = ver; host.jid = item.jid(); host.sizeLimit = sizeLimit; QVariant metaProps(d->client->serverInfoManager()->serviceMeta(host.jid, "httpprops")); if (metaProps.isValid()) { host.props = HostProps(metaProps.value()); } else { host.props = SecureGet | SecurePut; if (ver == XEP0363::v0_3_1) host.props |= NewestVer; } int value = 0; if (host.props & SecureGet) value += 5; if (host.props & SecurePut) value += 5; if (host.props & NewestVer) value += 3; if (host.props & Failure) value -= 15; if (!sizeLimit || d->fileSize < sizeLimit) hosts.append({ host, value }); // no sorting in preference order. most preferred go first std::sort(hosts.begin(), hosts.end(), [](const auto &a, const auto &b) { return a.second > b.second; }); for (auto &hp : hosts) { d->httpHosts.append(hp.first); } } } // d->currentHost = d->httpHosts.begin(); d->client->httpFileUploadManager()->setDiscoHosts(d->httpHosts); if (d->httpHosts.isEmpty()) { // if empty as the last resort check all services d->result.statusCode = HttpFileUpload::ErrorCode::NoUploadService; d->result.statusString = "No suitable http upload services were found"; done(State::Error); } else { tryNextServer(); } }); } void HttpFileUpload::tryNextServer() { if (d->httpHosts.isEmpty()) { // if empty as the last resort check all services if (d->result.statusCode == HttpFileUpload::ErrorCode::NoError) { d->result.statusCode = HttpFileUpload::ErrorCode::NoUploadService; d->result.statusString = "All http services are either non compliant or returned errors"; } done(State::Error); return; } HttpHost host = d->httpHosts.takeFirst(); d->result.sizeLimit = host.sizeLimit; auto jt = new JT_HTTPFileUpload(d->client->rootTask()); connect( jt, &JT_HTTPFileUpload::finished, this, [this, jt, host]() mutable { if (!jt->success()) { host.props |= Failure; int code = jt->statusCode(); if (code < 300) { code++; // ErrDisc and ErrTimeout. but 0 code is already occupated } d->result.statusCode = static_cast(jt->statusCode()); d->result.statusString = jt->statusString(); d->client->serverInfoManager()->setServiceMeta(host.jid, QLatin1String("httpprops"), int(host.props)); if (d->httpHosts.isEmpty()) done(State::Error); else tryNextServer(); return; } d->result.getUrl = jt->url(JT_HTTPFileUpload::GetUrl); d->result.putUrl = jt->url(JT_HTTPFileUpload::PutUrl); d->result.putHeaders = jt->headers(); if (d->result.getUrl.startsWith("https://")) host.props |= SecureGet; else host.props &= ~SecureGet; if (d->result.putUrl.startsWith("https://")) host.props |= SecurePut; else host.props &= ~SecurePut; host.props &= ~Failure; d->client->serverInfoManager()->setServiceMeta(host.jid, QLatin1String("httpprops"), int(host.props)); if (!d->qnam) { // w/o network access manager, it's not more than getting slots done(State::Success); return; } setState(State::HttpRequest); // time for a http request QNetworkRequest req(d->result.putUrl); for (auto &h : d->result.putHeaders) { req.setRawHeader(h.name.toLatin1(), h.value.toLatin1()); } auto reply = d->qnam->put(req, d->sourceDevice); connect(reply, &QNetworkReply::uploadProgress, this, &HttpFileUpload::progress); connect(reply, &QNetworkReply::finished, this, [this, reply]() { if (reply->error() == QNetworkReply::NoError) { done(State::Success); } else { d->result.statusCode = ErrorCode::HttpFailed; d->result.statusString = reply->errorString(); qDebug("http upload failed: %s", qPrintable(d->result.statusString)); if (d->httpHosts.isEmpty()) done(State::Error); else tryNextServer(); } reply->deleteLater(); }); }, Qt::QueuedConnection); jt->request(host.jid, d->fileName, d->fileSize, d->mediaType, host.ver); jt->go(true); } bool HttpFileUpload::success() const { return d->state == State::Success; } HttpFileUpload::ErrorCode HttpFileUpload::statusCode() const { return d->result.statusCode; } const QString &HttpFileUpload::statusString() const { return d->result.statusString; } HttpFileUpload::HttpSlot HttpFileUpload::getHttpSlot() { HttpSlot slot {}; if (d->state == State::Success) { slot.get.url = d->result.getUrl; slot.put.url = d->result.putUrl; slot.put.headers = d->result.putHeaders; slot.limits.fileSize = d->result.sizeLimit; } return slot; } void HttpFileUpload::setState(State state) { d->state = state; if (state == Success) { d->result.statusCode = ErrorCode::NoError; d->result.statusString.clear(); } emit stateChanged(); } void HttpFileUpload::done(State state) { setState(state); emit finished(); } //---------------------------------------------------------------------------- // JT_HTTPFileUpload //---------------------------------------------------------------------------- class JT_HTTPFileUpload::Private { public: Jid to; QDomElement iq; QStringList urls; XEP0363::version ver; XEP0363::HttpHeaders headers; }; JT_HTTPFileUpload::JT_HTTPFileUpload(Task *parent) : Task(parent) { d = new Private; d->ver = XEP0363::vUnknown; d->urls << QString() << QString(); } JT_HTTPFileUpload::~JT_HTTPFileUpload() { delete d; } void JT_HTTPFileUpload::request(const Jid &to, const QString &fname, quint64 fsize, const QString &ftype, XEP0363::version ver) { QString ns; switch (ver) { case XEP0363::v0_2_5: ns = xmlns_v0_2_5; break; case XEP0363::v0_3_1: ns = xmlns_v0_3_1; break; default: return; } d->to = to; d->ver = ver; d->iq = createIQ(doc(), "get", to.full(), id()); QDomElement req = doc()->createElementNS(ns, "request"); switch (ver) { case XEP0363::v0_2_5: ns = xmlns_v0_2_5; req.appendChild(textTag(doc(), "filename", fname)); req.appendChild(textTag(doc(), "size", QString::number(fsize))); if (!ftype.isEmpty()) { req.appendChild(textTag(doc(), "content-type", ftype)); } break; case XEP0363::v0_3_1: ns = xmlns_v0_3_1; req.setAttribute("filename", fname); req.setAttribute("size", fsize); if (!ftype.isEmpty()) req.setAttribute("content-type", ftype); break; default: d->ver = XEP0363::vUnknown; break; } d->iq.appendChild(req); } QString JT_HTTPFileUpload::url(UrlType t) const { return d->urls.value(t); } XEP0363::HttpHeaders JT_HTTPFileUpload::headers() const { return d->headers; } void JT_HTTPFileUpload::onGo() { if (d->ver != XEP0363::vUnknown) send(d->iq); } bool JT_HTTPFileUpload::take(const QDomElement &e) { if (!iqVerify(e, d->to, id())) return false; if (e.attribute("type") != "result") { setError(e); return true; } bool correct_xmlns = false; QString getUrl, putUrl; XEP0363::HttpHeaders headers; const QDomElement & slot = e.firstChildElement("slot"); if (!slot.isNull()) { const QDomElement &get = slot.firstChildElement("get"); const QDomElement &put = slot.firstChildElement("put"); switch (d->ver) { case XEP0363::v0_2_5: correct_xmlns = slot.namespaceURI() == xmlns_v0_2_5; getUrl = tagContent(get); putUrl = tagContent(put); break; case XEP0363::v0_3_1: correct_xmlns = slot.namespaceURI() == xmlns_v0_3_1; getUrl = get.attribute("url"); if (!put.isNull()) { putUrl = put.attribute("url"); QDomElement he = put.firstChildElement("header"); while (!he.isNull()) { // only next are allowed: Authorization, Cookie, Expires QString header = he.attribute("name").trimmed().remove(QLatin1Char('\n')); QString value = he.text().trimmed().remove(QLatin1Char('\n')); if (!value.isEmpty() && (header.compare(QLatin1String("Authorization"), Qt::CaseInsensitive) == 0 || header.compare(QLatin1String("Cookie"), Qt::CaseInsensitive) == 0 || header.compare(QLatin1String("Expires"), Qt::CaseInsensitive) == 0)) { headers.append(XEP0363::HttpHeader { header, value }); } he = he.nextSiblingElement("header"); } } break; default: break; } } if (!correct_xmlns) { setError(ErrInvalidResponse); return true; } if (!getUrl.isEmpty() && !putUrl.isEmpty()) { d->urls[GetUrl] = getUrl; d->urls[PutUrl] = putUrl; d->headers = headers; setSuccess(); } else setError(ErrInvalidResponse, "Either `put` or `get` URL is missing in the server's reply."); return true; } class HttpFileUploadManager::Private { public: Client * client = nullptr; QPointer qnam; int discoStatus = 0; QList discoHosts; bool externalQnam = false; std::list hosts; }; HttpFileUploadManager::HttpFileUploadManager(Client *parent) : QObject(parent), d(new Private) { d->client = parent; } HttpFileUploadManager::~HttpFileUploadManager() { delete d; } int HttpFileUploadManager::discoveryStatus() const { return d->discoStatus; } void HttpFileUploadManager::setNetworkAccessManager(QNetworkAccessManager *qnam) { d->externalQnam = true; d->qnam = qnam; } HttpFileUpload *HttpFileUploadManager::upload(const QString &srcFilename, const QString &dstFilename, const QString &mType) { auto f = new QFile(srcFilename); f->open(QIODevice::ReadOnly); auto hfu = upload(f, f->size(), dstFilename, mType); connect(hfu, &HttpFileUpload::finished, this, [f]() { f->close(); }); f->setParent(hfu); return hfu; } HttpFileUpload *HttpFileUploadManager::upload(QIODevice *source, size_t fsize, const QString &dstFilename, const QString &mType) { auto hfu = new HttpFileUpload(d->client, source, fsize, dstFilename, mType); QNetworkAccessManager *qnam = d->externalQnam ? d->qnam.data() : d->client->networkAccessManager(); hfu->setNetworkAccessManager(qnam); QMetaObject::invokeMethod(hfu, "start", Qt::QueuedConnection); return hfu; } const QList &HttpFileUploadManager::discoHosts() const { return d->discoHosts; } void HttpFileUploadManager::setDiscoHosts(const QList &hosts) { d->discoStatus = hosts.size() ? DiscoFound : DiscoNotFound; d->discoHosts = hosts; } psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/httpfileupload.h000066400000000000000000000145101370065651000250610ustar00rootroot00000000000000/* * httpfileupload.h - HTTP File upload * Copyright (C) 2017-2019 Aleksey Andreev, Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_HTTPFILEUPLOAD_H #define XMPP_HTTPFILEUPLOAD_H #include "xmpp/jid/jid.h" #include "xmpp_task.h" #include #include class QIODevice; class QNetworkAccessManager; namespace XMPP { class Client; namespace XEP0363 { enum version { vUnknown, v0_2_5, v0_3_1 }; struct HttpHeader { QString name; QString value; }; typedef QList HttpHeaders; } class HttpFileUpload : public QObject { Q_OBJECT public: enum HostPropFlag { SecureGet = 1, // 0.2.5 of the xep didn't require that SecurePut = 2, // 0.2.5 of the xep didn't require that NewestVer = 4, Failure = 8 // had some failure (no/unexpected response to slot request, early http errors) }; Q_DECLARE_FLAGS(HostProps, HostPropFlag) enum class ErrorCode : int { NoError = 0, XmppConnectionFailure, Timeout, SlotReceiveFailed, NoUploadService = 5, // previous could be mapped to Task errors HttpFailed }; struct HttpSlot { struct { QString url; } get; struct { QString url; QList headers; } put; struct { quint64 fileSize; } limits; }; struct HttpHost { XEP0363::version ver; Jid jid; quint64 sizeLimit; HostProps props; }; HttpFileUpload(Client *client, QIODevice *source, size_t fsize, const QString &dstFilename, const QString &mType = QString()); HttpFileUpload(const HttpFileUpload &) = delete; ~HttpFileUpload(); /** * @brief setNetworkAccessManager sets network access manager to do http requests. * @param qnam network access manager instance * * HttpFileUpload by default stops after receiving an http slot from the xmpp server. * setting qnam allows doing automatic http requests after getting slot, * so finished signal will be emitted when http finished. */ void setNetworkAccessManager(QNetworkAccessManager *qnam); bool success() const; ErrorCode statusCode() const; const QString &statusString() const; HttpSlot getHttpSlot(); public slots: void start(); signals: void stateChanged(); void finished(); void progress(qint64 bytesReceived, qint64 bytesTotal); private: enum State { None, GettingSlot, HttpRequest, Success, Error }; friend class HttpFileUploadManager; void init(); void done(State state); void tryNextServer(); void setState(State state); private: class Private; std::unique_ptr d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(HttpFileUpload::HostProps) class JT_HTTPFileUpload : public Task { Q_OBJECT public: enum UrlType { GetUrl = 0, PutUrl = 1 }; enum { ErrInvalidResponse = int(HttpFileUpload::ErrorCode::SlotReceiveFailed) - 1 }; // -1 to be mapped to ErrDisc, ErrTimeout, ... JT_HTTPFileUpload(Task *parent); ~JT_HTTPFileUpload(); void request(const Jid &to, const QString &fname, quint64 fsize, const QString &ftype, XEP0363::version ver); QString url(UrlType t) const; XEP0363::HttpHeaders headers() const; void onGo(); bool take(const QDomElement &); private: class Private; Private *d; }; class HttpFileUploadManager : public QObject { Q_OBJECT public: enum { DiscoNone = 0x0, DiscoNotFound = 0x1, DiscoFound = 0x2 }; typedef std::function Callback; // params: success, detail. where detail could be a "get" url HttpFileUploadManager(Client *parent); ~HttpFileUploadManager(); int discoveryStatus() const; /** * @brief setNetworkAccessManager sets network access manager to do http requests. * @param qnam network access manager instance * * HttpFileUpload by default stops after receiving an http slot from the xmpp server. * setting qnam allows doing automatic http requests after getting slot, * so finished signal will be emitted when http finished. * * NOTE: by default QNAM from Client will be in use until something set with this method. * So it's possible to disable HTTP part by setting NULL here. */ void setNetworkAccessManager(QNetworkAccessManager *qnam); /** * @brief uploads given file to http server * @param srcFilename name of the real file on the filesystem * @param dstFilename name of remote/target file * @param mType meta type. image/png for example * @return returns a handler object which will signal "finished" when ready */ HttpFileUpload *upload(const QString &srcFilename, const QString &dstFilename = QString(), const QString &mType = QString()); /** * @brief uploads data of given size from the given to remote server * @param source - source device * @param fsize - size of data to upload * @param dstFilename - name of file on the remote server * @param mType - meta type * @return returns a handler object which will signal "finished" when ready */ HttpFileUpload *upload(QIODevice *source, size_t fsize, const QString &dstFilename, const QString &mType = QString()); private: friend class HttpFileUpload; const QList &discoHosts() const; void setDiscoHosts(const QList &hosts); class Private; Private *d; }; } // namespace XMPP #endif // XMPP_HTTPFILEUPLOAD_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/im.h000066400000000000000000000030301370065651000224350ustar00rootroot00000000000000/* * im.h - XMPP "IM" library API * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_IM_H #define XMPP_IM_H #include "xmpp.h" #include "xmpp/jid/jid.h" #include "xmpp_address.h" #include "xmpp_agentitem.h" #include "xmpp_chatstate.h" #include "xmpp_client.h" #include "xmpp_discoitem.h" #include "xmpp_features.h" #include "xmpp_form.h" #include "xmpp_hash.h" #include "xmpp_htmlelement.h" #include "xmpp_httpauthrequest.h" #include "xmpp_liveroster.h" #include "xmpp_liverosteritem.h" #include "xmpp_message.h" #include "xmpp_muc.h" #include "xmpp_pubsubitem.h" #include "xmpp_pubsubretraction.h" #include "xmpp_resource.h" #include "xmpp_resourcelist.h" #include "xmpp_roster.h" #include "xmpp_rosteritem.h" #include "xmpp_rosterx.h" #include "xmpp_status.h" #include "xmpp_task.h" #include "xmpp_thumbs.h" #include "xmpp_url.h" #include "xmpp_xdata.h" #endif // XMPP_IM_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/jingle-application.cpp000066400000000000000000000303201370065651000261360ustar00rootroot00000000000000/* * jignle-application.cpp - Base Jingle application classes * Copyright (C) 2019 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "jingle-application.h" #include "jingle-session.h" #include "xmpp_client.h" #include "xmpp_task.h" namespace XMPP { namespace Jingle { //---------------------------------------------------------------------------- // Application //---------------------------------------------------------------------------- ApplicationManager::ApplicationManager(QObject *parent) : QObject(parent) { } //---------------------------------------------------------------------------- // Application //---------------------------------------------------------------------------- Application::Update Application::evaluateOutgoingUpdate() { _update = { Action::NoAction, Reason() }; if (_state == State::Finished || _state == State::Created || _pendingTransportReplace == PendingTransportReplace::NeedAck) return _update; if (_terminationReason.isValid()) { _update = { Action::ContentRemove, _terminationReason }; return _update; } // missing transport means it's an incoming application with invalid transport, // but basically it shouldn't happen if ((_creator != _pad->session()->role() && _state == State::Pending) || !_transport) { return _update; } bool inTrReplace = _pendingTransportReplace == PendingTransportReplace::InProgress; if (_transport->state() == State::Finished) { if (inTrReplace && _transport->creator() != _pad->session()->role()) _update = { Action::TransportReject, _transport->lastReason() }; else _update = { _transportSelector->hasMoreTransports() ? Action::TransportReplace : Action::ContentRemove, _transport->lastReason() }; return _update; } switch (_state) { case State::ApprovedToSend: if (_transport->hasUpdates() && _transport->state() == State::ApprovedToSend) { if (inTrReplace) { // either we are waiting for incoming transport-accept or local confirmation of remote // transport-replace bool localTranport = _pad->session()->role() == _transport->creator(); _update = { localTranport ? Action::TransportInfo : Action::TransportAccept, Reason() }; } else _update = { _pad->session()->role() == _creator ? Action::ContentAdd : Action::ContentAccept, Reason() }; } break; case State::Pending: if (_creator != _pad->session()->role() && !inTrReplace && _transport->hasUpdates() && _transport->state() == State::ApprovedToSend) { // if remote transport has initial updates and it's not transport-replace then it's time to accept the // content _update = { Action::ContentAccept, Reason() }; break; } // fallthrough case State::Connecting: if (inTrReplace) { // for transport replace we handle just replace until it's finished if (_transport->creator() == _pad->session()->role()) { if (_transport->state() == State::Finished) // replace over unconfirmed replace (2nd transport failed shortly) _update = { _transportSelector->hasMoreTransports() ? Action::TransportReplace : Action::ContentRemove, _transport->lastReason() }; break; } if (_transport->hasUpdates() && _transport->state() == State::ApprovedToSend) { _update = { Action::TransportAccept, Reason() }; break; } if (_transport->state() == State::Finished) { _update = { Action::TransportReject, _transport->lastReason() }; } break; } if (_transport->hasUpdates()) { if (_transport->state() == State::Connecting) _update = { Action::TransportInfo, Reason() }; } else if (_transport->state() == State::Finished) { _update = { _transportSelector->hasMoreTransports() ? Action::TransportReplace : Action::ContentRemove, _transport->lastReason() }; } break; case State::Active: if (_transport->hasUpdates()) _update = { Action::TransportInfo, Reason() }; break; default: break; } return _update; } OutgoingUpdate Application::takeOutgoingUpdate() { QDomElement transportEl; OutgoingUpdateCB transportCB; auto client = _pad->session()->manager()->client(); auto doc = client->doc(); ContentBase cb(_creator, _contentName); // we need to send senders for initial offer/answer if (_state == State::ApprovedToSend) cb.senders = _senders; QList updates; auto contentEl = cb.toXml(doc, "content"); updates << contentEl; switch (_update.action) { case Action::ContentReject: case Action::ContentRemove: if (_update.reason.isValid()) updates << _update.reason.toXml(doc); return OutgoingUpdate { updates, [this](bool) { setState(State::Finished); } }; case Action::ContentAdd: contentEl.appendChild(makeLocalOffer()); std::tie(transportEl, transportCB) = wrapOutgoingTransportUpdate(); contentEl.appendChild(transportEl); setState(State::Unacked); return OutgoingUpdate { updates, [this, transportCB](Task *task) { transportCB(task); if (task->success()) setState(State::Pending); } }; case Action::ContentAccept: contentEl.appendChild(makeLocalAnswer()); std::tie(transportEl, transportCB) = wrapOutgoingTransportUpdate(); contentEl.appendChild(transportEl); setState(State::Unacked); return OutgoingUpdate { updates, [this, transportCB](Task *task) { transportCB(task); if (task->success()) setState(State::Connecting); } }; case Action::TransportInfo: Q_ASSERT(_transport->hasUpdates()); std::tie(transportEl, transportCB) = wrapOutgoingTransportUpdate(); contentEl.appendChild(transportEl); return OutgoingUpdate { updates, transportCB }; case Action::TransportReplace: case Action::TransportAccept: { Q_ASSERT(_transport->hasUpdates()); std::tie(transportEl, transportCB) = wrapOutgoingTransportUpdate(); contentEl.appendChild(transportEl); return OutgoingUpdate { updates, transportCB }; } default: break; } return OutgoingUpdate(); // TODO } OutgoingTransportInfoUpdate Application::wrapOutgoingTransportUpdate() { QDomElement transportEl; OutgoingUpdateCB transportCB; std::tie(transportEl, transportCB) = _transport->takeOutgoingUpdate(); auto wrapCB = [this, tr = _transport.toWeakRef(), cb = std::move(transportCB)](Task *task) { auto transport = tr.lock(); if (transport && cb) cb(task); if (_pendingTransportReplace == PendingTransportReplace::NeedAck) { _pendingTransportReplace = task->success() ? PendingTransportReplace::InProgress : PendingTransportReplace::None; emit updated(); } }; return OutgoingTransportInfoUpdate { transportEl, wrapCB }; } bool Application::selectNextTransport(const QSharedPointer alikeTransport) { if (!_transportSelector->hasMoreTransports()) { emit updated(); // will be evaluated to content-remove return false; } if (alikeTransport) { auto tr = _transportSelector->getAlikeTransport(alikeTransport); if (tr && setTransport(tr)) return true; } QSharedPointer t; while ((t = _transportSelector->getNextTransport())) if (setTransport(t)) return true; emit updated(); // will be evaluated to content-remove return false; } bool Application::wantBetterTransport(const QSharedPointer &t) const { if (!_transportSelector->hasTransport(t)) return false; return !_transport || _transportSelector->compare(t, _transport) > 0; } bool Application::isTransportReplaceEnabled() const { return true; } bool Application::setTransport(const QSharedPointer &transport, const Reason &reason) { if (!isTransportReplaceEnabled() || !_transportSelector->replace(_transport, transport)) return false; // in case we automatically select a new transport on our own we definitely will come up to this point if (_transport) { if (_transport->state() < State::Unacked && _transport->creator() == _pad->session()->role() && _transport->pad()->ns() != transport->pad()->ns()) { // the transport will be reused later since the remote doesn't know about it yet _transportSelector->backupTransport(_transport); } if (transport->creator() == _pad->session()->role()) { auto ts = _transport->state() == State::Finished ? _transport->prevState() : _transport->state(); if (_transport->creator() != _pad->session()->role() || ts > State::Unacked) { // if remote knows of the current transport _pendingTransportReplace = PendingTransportReplace::InProgress; } else if (_transport->creator() == _pad->session()->role() && ts == State::Unacked) { // if remote may know but we don't know yet about it _pendingTransportReplace = PendingTransportReplace::NeedAck; } } else { _pendingTransportReplace = PendingTransportReplace::InProgress; } if (_pendingTransportReplace != PendingTransportReplace::None) { if (_transport->state() == State::Finished) { // initiate replace? _transportReplaceReason = reason.isValid() ? reason : _transport->lastReason(); } else { _transportReplaceReason = reason; } } _transport->disconnect(this); _transport.reset(); } _transport = transport; connect(transport.data(), &Transport::updated, this, &Application::updated); connect(transport.data(), &Transport::failed, this, [this]() { selectNextTransport(); }); initTransport(); if (_state >= State::Unacked) { _transport->prepare(); } return true; } }} psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/jingle-application.h000066400000000000000000000174501370065651000256140ustar00rootroot00000000000000/* * jignle-application.h - Base Jingle application classes * Copyright (C) 2019 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef JINGLE_APPLICATION_H #define JINGLE_APPLICATION_H #include "jingle-transport.h" namespace XMPP { namespace Jingle { class ApplicationManager; class ApplicationManagerPad : public SessionManagerPad { Q_OBJECT public: typedef QSharedPointer Ptr; using SessionManagerPad::SessionManagerPad; virtual ApplicationManager *manager() const = 0; /* * for example we transfer a file * then first file may generate name "file1", next "file2" etc * As result it will be sent as */ virtual QString generateContentName(Origin senders) = 0; }; // Represents a session for single application. for example a single file in a file transfer session. // There maybe multiple application instances in a session. // It's designed as QObject to exposed to JavaScript (qml/webkit) class Application : public QObject { Q_OBJECT public: struct Update { Action action; Reason reason; }; enum SetDescError { Ok, Unparsed, IncompatibleParameters // this one is for }; virtual void setState(State state) = 0; // likely just remember the state and not generate any signals virtual XMPP::Stanza::Error lastError() const = 0; virtual Reason lastReason() const = 0; inline ApplicationManagerPad::Ptr pad() const { return _pad; } inline State state() const { return _state; } inline Origin creator() const { return _creator; } inline Origin senders() const { return _senders; } inline QString contentName() const { return _contentName; } inline QSharedPointer transport() const { return _transport; } inline TransportSelector * transportSelector() const { return _transportSelector.data(); } virtual SetDescError setRemoteOffer(const QDomElement &description) = 0; virtual SetDescError setRemoteAnswer(const QDomElement &description) = 0; virtual QDomElement makeLocalOffer() = 0; virtual QDomElement makeLocalAnswer() = 0; /** * @brief evaluateOutgoingUpdate computes and prepares next update which will be taken with takeOutgoingUpdate * The updated will be taked immediately if considered to be most preferred among other updates types of * other applications. * @return update type */ virtual Update evaluateOutgoingUpdate(); // this may return something only when evaluateOutgoingUpdate() != NoAction virtual OutgoingUpdate takeOutgoingUpdate(); /** * @brief setTransport checks if transport is compatible and stores it * @param transport * @return false if not compatible */ bool setTransport(const QSharedPointer &transport, const Reason &reason = Reason()); /** * @brief selectNextTransport selects next transport from compatible transports list. * The list is usually stored in the application * @return */ bool selectNextTransport(const QSharedPointer alikeTransport = QSharedPointer()); /** * @brief Checks where transport-replace is possible atm * @return */ virtual bool isTransportReplaceEnabled() const; /** * @brief wantBetterTransport checks if the transport is a better match for the application * Used in content is provided twice with two different transports * @return */ virtual bool wantBetterTransport(const QSharedPointer &) const; /** * @brief prepare to send content-add/session-initiate * When ready, the application first set update type to ContentAdd and then emit updated() */ virtual void prepare() = 0; virtual void start() = 0; virtual void remove(Reason::Condition cond = Reason::Success, const QString &comment = QString()) = 0; virtual void incomingRemove(const Reason &r) = 0; protected: /** * @brief wraps transport update so transport can be safely-deleted before callback is triggered */ OutgoingTransportInfoUpdate wrapOutgoingTransportUpdate(); /** * @brief initTransport in general connects any necessary for the application transport signals */ virtual void initTransport() = 0; signals: void updated(); // signal for session it has to send updates to remote. so it will follow with // takeOutgoingUpdate() eventually void stateChanged(State); protected: State _state = State::Created; enum class PendingTransportReplace { None, NeedAck, InProgress }; // has to be set when whatever way remote knows about the current transport // bool _remoteKnowsOfTheTransport = false; // per session object responsible for all applications of this type QSharedPointer _pad; // content properties as come from the request QString _contentName; Origin _creator; Origin _senders; // current transport. either local or remote. has info about origin and state QSharedPointer _transport; QScopedPointer _transportSelector; // if transport-replace is in progress. will be set to true when accepted by both sides. PendingTransportReplace _pendingTransportReplace = PendingTransportReplace::None; // while it's valid - we are in unaccepted yet transport-replace Reason _transportReplaceReason; // when set the content will be removed with this reason Reason _terminationReason; // evaluated update to be sent Update _update; }; inline bool operator<(const Application::Update &a, const Application::Update &b) { return a.action < b.action || (a.action == b.action && a.reason.condition() < b.reason.condition()); } class ApplicationManager : public QObject { Q_OBJECT public: ApplicationManager(QObject *parent = nullptr); virtual void setJingleManager(Manager *jm) = 0; virtual Application *startApplication(const ApplicationManagerPad::Ptr &pad, const QString &contentName, Origin creator, Origin senders) = 0; virtual ApplicationManagerPad *pad(Session *session) = 0; // this method is supposed to gracefully close all related sessions as a preparation for plugin unload for // example virtual void closeAll() = 0; }; }} #endif psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/jingle-ft.cpp000066400000000000000000000710141370065651000242510ustar00rootroot00000000000000/* * jignle-ft.h - Jingle file transfer * Copyright (C) 2019 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "jingle-ft.h" #include "jingle-nstransportslist.h" #include "jingle-session.h" #include "xmpp_client.h" #include "xmpp_hash.h" #include "xmpp_thumbs.h" #include "xmpp_xmlcommon.h" #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) #include #endif #include #include namespace XMPP { namespace Jingle { namespace FileTransfer { const QString NS = QStringLiteral("urn:xmpp:jingle:apps:file-transfer:5"); const QString AMPLITUDES_NS = QStringLiteral("urn:audio:amplitudes"); // tags static const QString AMPLITUDES_TAG = QStringLiteral("amplitudes"); static const QString FILETAG = QStringLiteral("file"); static const QString DATE_TAG = QStringLiteral("date"); static const QString DESC_TAG = QStringLiteral("desc"); static const QString MEDIA_TYPE_TAG = QStringLiteral("media-type"); static const QString NAME_TAG = QStringLiteral("name"); static const QString SIZE_TAG = QStringLiteral("size"); static const QString RANGE_TAG = QStringLiteral("range"); static const QString THUMBNAIL_TAG = QStringLiteral("thumbnail"); QDomElement Range::toXml(QDomDocument *doc) const { auto r = doc->createElement(RANGE_TAG); if (length) { r.setAttribute(QStringLiteral("length"), QString::number(length)); } if (offset) { r.setAttribute(QStringLiteral("offset"), QString::number(offset)); } for (auto const &h : hashes) { auto hel = h.toXml(doc); if (!hel.isNull()) { r.appendChild(hel); } } return r; } //---------------------------------------------------------------------------- // File //---------------------------------------------------------------------------- class File::Private : public QSharedData { public: bool rangeSupported = false; bool hasSize = false; QDateTime date; QString mediaType; QString name; QString desc; qint64 size = 0; Range range; QList hashes; Thumbnail thumbnail; QByteArray amplitudes; }; File::File() { } File::~File() { } File &File::operator=(const File &other) { d = other.d; return *this; } File::File(const File &other) : d(other.d) { } File::File(const QDomElement &file) { QDateTime date; QString mediaType; QString name; QString desc; qint64 size = 0; bool rangeSupported = false; bool hasSize = false; Range range; QList hashes; Thumbnail thumbnail; QByteArray amplitudes; bool ok; for (QDomElement ce = file.firstChildElement(); !ce.isNull(); ce = ce.nextSiblingElement()) { if (ce.tagName() == DATE_TAG) { date = QDateTime::fromString(ce.text().left(19), Qt::ISODate); if (!date.isValid()) { return; } } else if (ce.tagName() == MEDIA_TYPE_TAG) { mediaType = ce.text(); } else if (ce.tagName() == NAME_TAG) { name = ce.text(); } else if (ce.tagName() == SIZE_TAG) { size = ce.text().toLongLong(&ok); if (!ok || size < 0) { return; } hasSize = true; } else if (ce.tagName() == RANGE_TAG) { if (ce.hasAttribute(QLatin1String("offset"))) { range.offset = ce.attribute(QLatin1String("offset")).toLongLong(&ok); if (!ok || range.offset < 0) { return; } } if (ce.hasAttribute(QLatin1String("length"))) { range.length = ce.attribute(QLatin1String("length")).toLongLong(&ok); if (!ok || range.length <= 0) { // length should absent if we need to read till end of file. // 0-length is nonsense return; } } QDomElement hashEl = ce.firstChildElement(QLatin1String("hash")); for (; !hashEl.isNull(); hashEl = hashEl.nextSiblingElement(QLatin1String("hash"))) { if (hashEl.namespaceURI() == HASH_NS) { auto hash = Hash(hashEl); if (hash.type() == Hash::Type::Unknown) { continue; } range.hashes.append(hash); } } rangeSupported = true; } else if (ce.tagName() == DESC_TAG) { desc = ce.text(); } else if (ce.tagName() == QLatin1String("hash")) { if (ce.namespaceURI() == HASH_NS) { Hash h(ce); if (h.type() == Hash::Type::Unknown) { return; } hashes.append(h); } } else if (ce.tagName() == QLatin1String("hash-used")) { if (ce.namespaceURI() == HASH_NS) { Hash h(ce); if (h.type() == Hash::Type::Unknown) { return; } hashes.append(h); } } else if (ce.tagName() == THUMBNAIL_TAG) { thumbnail = Thumbnail(ce); } else if (ce.tagName() == AMPLITUDES_TAG && ce.namespaceURI() == AMPLITUDES_NS) { amplitudes = QByteArray::fromBase64(ce.text().toLatin1()); } } auto p = new Private; p->date = date; p->mediaType = mediaType; p->name = name; p->desc = desc; p->size = size; p->rangeSupported = rangeSupported; p->hasSize = hasSize; p->range = range; p->hashes = hashes; p->thumbnail = thumbnail; p->amplitudes = amplitudes; d = p; } QDomElement File::toXml(QDomDocument *doc) const { if (!isValid() || d->hashes.isEmpty()) { return QDomElement(); } QDomElement el = doc->createElementNS(NS, QStringLiteral("file")); if (d->date.isValid()) { el.appendChild(XMLHelper::textTag(*doc, DATE_TAG, d->date.toString(Qt::ISODate))); } if (d->desc.size()) { el.appendChild(XMLHelper::textTag(*doc, DESC_TAG, d->desc)); } for (const auto &h : d->hashes) { el.appendChild(h.toXml(doc)); } if (d->mediaType.size()) { el.appendChild(XMLHelper::textTag(*doc, MEDIA_TYPE_TAG, d->mediaType)); } if (d->name.size()) { el.appendChild(XMLHelper::textTag(*doc, NAME_TAG, d->name)); } if (d->hasSize) { el.appendChild(XMLHelper::textTag(*doc, SIZE_TAG, d->size)); } if (d->rangeSupported || d->range.isValid()) { el.appendChild(d->range.toXml(doc)); } if (d->thumbnail.isValid()) { el.appendChild(d->thumbnail.toXml(doc)); } if (d->amplitudes.size()) { el.appendChild(XMLHelper::textTagNS(doc, AMPLITUDES_NS, AMPLITUDES_TAG, d->amplitudes)); } return el; } bool File::merge(const File &other) { if (!d->thumbnail.isValid()) { d->thumbnail = other.thumbnail(); } for (auto const &h : other.d->hashes) { auto it = std::find_if(d->hashes.constBegin(), d->hashes.constEnd(), [&h](auto const &v) { return h.type() == v.type(); }); if (it == d->hashes.constEnd()) { d->hashes.append(h); } else if (h.data() != it->data()) { return false; // hashes are different } } return true; } bool File::hasComputedHashes() const { if (!d) return false; for (auto const &h : d->hashes) { if (h.data().size()) return true; } return false; } bool File::hasSize() const { return d->hasSize; } QDateTime File::date() const { return d ? d->date : QDateTime(); } QString File::description() const { return d ? d->desc : QString(); } QList File::hashes() const { return d ? d->hashes : QList(); } QList File::computedHashes() const { QList ret; if (!d) return ret; for (auto const &h : d->hashes) { if (h.data().size()) ret.append(h); } return ret; } Hash File::hash(Hash::Type t) const { if (d && d->hashes.count()) { if (t == Hash::Unknown) return d->hashes.at(0); for (auto const &h : d->hashes) { if (h.type() == t) { return h; } } } return Hash(); } QString File::mediaType() const { return d ? d->mediaType : QString(); } QString File::name() const { return d ? d->name : QString(); } qint64 File::size() const { return d ? d->size : 0; } Range File::range() const { return d ? d->range : Range(); } Thumbnail File::thumbnail() const { return d ? d->thumbnail : Thumbnail(); } QByteArray File::amplitudes() const { return d ? d->amplitudes : QByteArray(); } void File::setDate(const QDateTime &date) { ensureD()->date = date; } void File::setDescription(const QString &desc) { ensureD()->desc = desc; } void File::addHash(const Hash &hash) { ensureD()->hashes.append(hash); } void File::setHashes(const QList &hashes) { ensureD()->hashes = hashes; } void File::setMediaType(const QString &mediaType) { ensureD()->mediaType = mediaType; } void File::setName(const QString &name) { ensureD()->name = name; } void File::setSize(qint64 size) { ensureD()->size = size; d->hasSize = true; } void File::setRange(const Range &range) { ensureD()->range = range; d->rangeSupported = true; } void File::setThumbnail(const Thumbnail &thumb) { ensureD()->thumbnail = thumb; } void File::setAmplitudes(const QByteArray &litudes) { d->amplitudes = amplitudes; } File::Private *File::ensureD() { if (!d) { d = new Private; } return d.data(); } //---------------------------------------------------------------------------- // Checksum //---------------------------------------------------------------------------- Checksum::Checksum(const QDomElement &cs) : ContentBase(cs) { file = File(cs.firstChildElement(QLatin1String("file"))); } bool Checksum::isValid() const { return ContentBase::isValid() && file.isValid(); } QDomElement Checksum::toXml(QDomDocument *doc) const { auto el = ContentBase::toXml(doc, "checksum"); if (!el.isNull()) { el.appendChild(file.toXml(doc)); } return el; } //---------------------------------------------------------------------------- // Received //---------------------------------------------------------------------------- QDomElement Received::toXml(QDomDocument *doc) const { return ContentBase::toXml(doc, "received"); } //---------------------------------------------------------------------------- // ApplicationManager //---------------------------------------------------------------------------- Manager::Manager(QObject *parent) : XMPP::Jingle::ApplicationManager(parent) { } Manager::~Manager() { if (jingleManager) jingleManager->unregisterApp(NS); } void Manager::setJingleManager(XMPP::Jingle::Manager *jm) { jingleManager = jm; } Application *Manager::startApplication(const ApplicationManagerPad::Ptr &pad, const QString &contentName, Origin creator, Origin senders) { if (!(contentName.size() > 0 && (senders == Origin::Initiator || senders == Origin::Responder))) { qDebug("Invalid Jignle FT App start parameters"); return nullptr; } return new Application(pad.staticCast(), contentName, creator, senders); // ContentOrigin::Remote } ApplicationManagerPad *Manager::pad(Session *session) { return new Pad(this, session); } void Manager::closeAll() { } Client *Manager::client() { if (jingleManager) { return jingleManager->client(); } return nullptr; } QStringList Manager::availableTransports() const { return jingleManager->availableTransports(TransportFeature::Reliable); } //---------------------------------------------------------------------------- // Application //---------------------------------------------------------------------------- class Application::Private { public: struct TransportDesc { Origin creator = Origin::None; State state = State::Created; QSharedPointer transport; }; Application *q = nullptr; Reason updateReason; // Action updateToSend = Action::NoAction; bool closeDeviceOnFinish = true; bool streamingMode = false; bool endlessRange = false; // where range in accepted file doesn't have end bool outgoingReceived = false; File file; File acceptFile; // as it comes with "accept" response XMPP::Stanza::Error lastError; Reason lastReason; Connection::Ptr connection; QIODevice * device = nullptr; qint64 bytesLeft = 0; QList outgoingChecksum; qint64 outgoingChecksumRangeOffset = 0, outgoingChecksumRangeLength = 0; void setState(State s) { q->_state = s; if (s == State::Finished) { if (device && closeDeviceOnFinish) { device->close(); } if (connection) { connection->close(); } q->disconnect(q->transport().data(), &Transport::updated, q, nullptr); } if (s >= State::Finishing) { q->disconnect(q->transport().data(), &Transport::failed, q, nullptr); q->disconnect(q->transport().data(), &Transport::connected, q, nullptr); // we can still try to send transport updates } emit q->stateChanged(s); } void handleStreamFail() { lastReason = Reason(Reason::Condition::FailedApplication, QString::fromLatin1("stream failed")); setState(State::Finished); } void writeNextBlockToTransport() { if (!(endlessRange || bytesLeft)) { lastReason = Reason(Reason::Condition::Success); setState(State::Finished); return; // everything is written } auto sz = qint64(connection->blockSize()); sz = sz ? sz : 8192; if (!endlessRange && sz > bytesLeft) { sz = bytesLeft; } QByteArray data; if (device->isSequential()) { if (!device->bytesAvailable()) return; // we will come back on readyRead data = device->read(qMin(qint64(sz), device->bytesAvailable())); } else { data = device->read(sz); } if (data.isEmpty()) { if (endlessRange) { lastReason = Reason(Reason::Condition::Success); setState(State::Finished); } else { handleStreamFail(); } return; } // qDebug("JINGLE-FT write %d bytes to connection", data.size()); if (connection->write(data) == -1) { handleStreamFail(); return; } emit q->progress(device->pos()); bytesLeft -= data.size(); } void readNextBlockFromTransport() { qint64 bytesAvail; while (bytesLeft && (bytesAvail = connection->bytesAvailable())) { qint64 sz = 65536; // shall we respect transport->blockSize() ? if (sz > bytesLeft) { sz = bytesLeft; } if (sz > bytesAvail) { sz = bytesAvail; } QByteArray data = connection->read(sz); // qDebug("JINGLE-FT read %d bytes from connection", data.size()); if (data.isEmpty()) { handleStreamFail(); return; } if (device->write(data) == -1) { handleStreamFail(); return; } emit q->progress(device->pos()); bytesLeft -= sz; } if (!bytesLeft) { // TODO send lastReason = Reason(Reason::Condition::Success); setState(State::Finished); } } }; Application::Application(const QSharedPointer &pad, const QString &contentName, Origin creator, Origin senders) : d(new Private) { d->q = this; _pad = pad; _contentName = contentName; _creator = creator; _senders = senders; _transportSelector.reset( new NSTransportsList(pad->session(), static_cast(pad->manager())->availableTransports())); } Application::~Application() { } void Application::setState(State state) { d->setState(state); } Stanza::Error Application::lastError() const { return d->lastError; } Reason Application::lastReason() const { return d->lastReason; } static Application::SetDescError parseDescription(const QDomElement &description, File &file) { auto el = description.firstChildElement("file"); if (el.isNull()) return Application::Unparsed; auto f = File(el); if (!f.isValid()) return Application::IncompatibleParameters; file = f; return Application::Ok; } Application::SetDescError Application::setRemoteOffer(const QDomElement &description) { File f; auto ret = parseDescription(description, f); if (ret == Application::Ok) d->file = f; return ret; } Application::SetDescError Application::setRemoteAnswer(const QDomElement &description) { File f; auto ret = parseDescription(description, f); if (ret == Application::Ok) { d->acceptFile = f; setState(State::Accepted); } return ret; } void Application::prepareThumbnail(File &file) { if (file.thumbnail().data.size()) { auto client = _pad->session()->manager()->client(); auto thumb = file.thumbnail(); auto bm = client->bobManager(); BoBData data = bm->append(thumb.data, thumb.mimeType); thumb.uri = QLatin1String("cid:") + data.cid(); d->file.setThumbnail(thumb); } } QDomElement Application::makeLocalOffer() { if (!d->file.isValid()) { return QDomElement(); } auto doc = _pad->doc(); auto el = doc->createElementNS(NS, "description"); prepareThumbnail(d->file); el.appendChild(d->file.toXml(doc)); return el; } QDomElement Application::makeLocalAnswer() { if (!d->file.isValid()) { return QDomElement(); } if (!d->acceptFile.isValid()) { d->acceptFile = d->file; } auto doc = _pad->doc(); auto el = doc->createElementNS(NS, "description"); el.appendChild(d->acceptFile.toXml(doc)); return el; } void Application::setFile(const File &file) { d->file = file; } File Application::file() const { return d->file; } File Application::acceptFile() const { return d->acceptFile; } bool Application::isTransportReplaceEnabled() const { return _state < State::Active; } void Application::initTransport() { connect(_transport.data(), &Transport::connected, this, [this]() { d->lastReason = Reason(); d->lastError.reset(); d->connection = _transport->addChannel(); if (!d->streamingMode) { connect(d->connection.data(), &Connection::readyRead, this, [this]() { if (!d->device) { return; } if (_pad->session()->role() != _senders) { d->readNextBlockFromTransport(); } }); connect(d->connection.data(), &Connection::bytesWritten, this, [this](qint64 bytes) { Q_UNUSED(bytes) if (_pad->session()->role() == _senders && !d->connection->bytesToWrite()) { d->writeNextBlockToTransport(); } }); } d->setState(State::Active); if (!d->streamingMode) { if (d->acceptFile.range().isValid()) { d->bytesLeft = d->acceptFile.range().length; if (!d->bytesLeft) d->endlessRange = true; emit deviceRequested(d->acceptFile.range().offset, d->bytesLeft); } else { d->bytesLeft = d->acceptFile.size(); emit deviceRequested(0, d->bytesLeft); } } else { emit connectionReady(); } }); } void Application::setStreamingMode(bool mode) { if (_state <= State::Connecting) { d->streamingMode = mode; } } XMPP::Jingle::Application::Update Application::evaluateOutgoingUpdate() { if (!isValid()) { _update = { Action::NoAction, Reason() }; return _update; } if (_state == State::Active && (d->outgoingChecksum.size() > 0 || d->outgoingReceived)) _update = { Action::SessionInfo, Reason() }; else return XMPP::Jingle::Application::evaluateOutgoingUpdate(); return _update; } OutgoingUpdate Application::takeOutgoingUpdate() { if (_update.action == Action::NoAction) { return OutgoingUpdate(); } auto client = _pad->session()->manager()->client(); auto doc = client->doc(); if (_update.action == Action::SessionInfo && (d->outgoingChecksum.size() > 0 || d->outgoingReceived)) { if (d->outgoingReceived) { d->outgoingReceived = false; ContentBase cb(_pad->session()->role(), _contentName); return OutgoingUpdate { QList() << cb.toXml(doc, "received", NS), [this](bool) { d->setState(State::Finished); } }; } if (!d->outgoingChecksum.isEmpty()) { ContentBase cb(_pad->session()->role(), _contentName); File f; if (d->outgoingChecksumRangeOffset || d->outgoingChecksumRangeLength) { Range r; r.hashes = d->outgoingChecksum; r.offset = d->outgoingChecksumRangeOffset; r.length = d->outgoingChecksumRangeLength; f.setRange(r); } else { f.setHashes(d->outgoingChecksum); } auto el = cb.toXml(doc, "checksum", NS); el.appendChild(f.toXml(doc)); d->outgoingChecksum.clear(); return OutgoingUpdate { QList() << el, [this](bool) { d->setState(State::Finished); } }; } } if (_update.action == Action::ContentAdd && _creator == _pad->session()->role()) { // we are doing outgoing file transfer request. so need thumbnail } return XMPP::Jingle::Application::takeOutgoingUpdate(); } void Application::prepare() { if (!_transport) { selectNextTransport(); } if (_transport) { d->setState(State::ApprovedToSend); _transport->prepare(); } } void Application::start() { if (_transport) { d->setState(State::Connecting); _transport->start(); } // TODO we need QIODevice somewhere here } void Application::remove(Reason::Condition cond, const QString &comment) { if (_state >= State::Finishing) return; _terminationReason = Reason(cond, comment); _transport->disconnect(this); _transport.reset(); if (_creator == _pad->session()->role() && _state <= State::ApprovedToSend) { // local content, not yet sent to remote setState(State::Finished); return; } emit updated(); } void Application::incomingRemove(const Reason &r) { d->lastReason = r; d->setState(State::Finished); } bool Application::isValid() const { return d->file.isValid() && _contentName.size() > 0 && (_senders == Origin::Initiator || _senders == Origin::Responder); } void Application::setDevice(QIODevice *dev, bool closeOnFinish) { if (!dev) { // failed to provide proper device _terminationReason = Reason(Reason::Condition::FailedApplication, QString::fromLatin1("No destination device")); emit updated(); return; } d->device = dev; d->closeDeviceOnFinish = closeOnFinish; if (_senders == _pad->session()->role()) { d->writeNextBlockToTransport(); } else { d->readNextBlockFromTransport(); } } Connection::Ptr Application::connection() const { return d->connection.staticCast(); } Pad::Pad(Manager *manager, Session *session) : _manager(manager), _session(session) { } QDomElement Pad::takeOutgoingSessionInfoUpdate() { return QDomElement(); // TODO } QString Pad::ns() const { return NS; } Session *Pad::session() const { return _session; } ApplicationManager *Pad::manager() const { return _manager; } QString Pad::generateContentName(Origin senders) { QString prefix = senders == _session->role() ? "fileoffer" : "filereq"; QString name; do { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) name = prefix + QString("_%1").arg(QRandomGenerator::global()->generate() & 0xffff, 4, 16, QChar('0')); #else name = prefix + QString("_%1").arg(qrand() & 0xffff, 4, 16, QChar('0')); #endif } while (_session->content(name, _session->role())); return name; } void Pad::addOutgoingOffer(const File &file) { auto selfp = _session->applicationPad(NS); auto app = _manager->startApplication(selfp, "ft", _session->role(), _session->role()); app->setFile(file); } } // namespace FileTransfer } // namespace Jingle } // namespace XMPP psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/jingle-ft.h000066400000000000000000000161051370065651000237160ustar00rootroot00000000000000/* * jignle-ft.h - Jingle file transfer * Copyright (C) 2019 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef JINGLEFT_H #define JINGLEFT_H #include "jingle-application.h" #include "jingle.h" #include "xmpp_hash.h" namespace XMPP { class Client; class Thumbnail; } namespace XMPP { namespace Jingle { namespace FileTransfer { extern const QString NS; class Manager; struct Range { qint64 offset = 0; qint64 length = 0; // 0 - from offset to the end of the file QList hashes; inline Range() { } inline Range(qint64 offset, qint64 length) : offset(offset), length(length) { } inline bool isValid() const { return hashes.size() || offset || length; } inline operator bool() const { return isValid(); } QDomElement toXml(QDomDocument *doc) const; }; class File { public: File(); File(const File &other); File(const QDomElement &file); ~File(); File & operator=(const File &other); inline bool isValid() const { return d != nullptr; } QDomElement toXml(QDomDocument *doc) const; bool merge(const File &other); bool hasComputedHashes() const; bool hasSize() const; QDateTime date() const; QString description() const; QList hashes() const; QList computedHashes() const; Hash hash(Hash::Type t = Hash::Unknown) const; QString mediaType() const; QString name() const; qint64 size() const; Range range() const; Thumbnail thumbnail() const; QByteArray amplitudes() const; void setDate(const QDateTime &date); void setDescription(const QString &desc); void addHash(const Hash &hash); void setHashes(const QList &hashes); void setMediaType(const QString &mediaType); void setName(const QString &name); void setSize(qint64 size); void setRange(const Range &range = Range()); // default empty just to indicate it's supported void setThumbnail(const Thumbnail &thumb); void setAmplitudes(const QByteArray &litudes); private: class Private; Private * ensureD(); QSharedDataPointer d; }; class Checksum : public ContentBase { inline Checksum() { } Checksum(const QDomElement &file); bool isValid() const; QDomElement toXml(QDomDocument *doc) const; private: File file; }; class Received : public ContentBase { using ContentBase::ContentBase; QDomElement toXml(QDomDocument *doc) const; }; class Pad : public ApplicationManagerPad { Q_OBJECT // TODO public: Pad(Manager *manager, Session *session); QDomElement takeOutgoingSessionInfoUpdate() override; QString ns() const override; Session * session() const override; ApplicationManager *manager() const override; QString generateContentName(Origin senders) override; void addOutgoingOffer(const File &file); private: Manager *_manager; Session *_session; }; class Application : public XMPP::Jingle::Application { Q_OBJECT public: Application(const QSharedPointer &pad, const QString &contentName, Origin creator, Origin senders); ~Application() override; void setState(State state) override; XMPP::Stanza::Error lastError() const override; Reason lastReason() const override; SetDescError setRemoteOffer(const QDomElement &description) override; SetDescError setRemoteAnswer(const QDomElement &description) override; QDomElement makeLocalOffer() override; QDomElement makeLocalAnswer() override; bool isTransportReplaceEnabled() const override; void remove(Reason::Condition cond = Reason::Success, const QString &comment = QString()) override; XMPP::Jingle::Application::Update evaluateOutgoingUpdate() override; OutgoingUpdate takeOutgoingUpdate() override; void prepare() override; void start() override; void setFile(const File &file); File file() const; File acceptFile() const; /** * @brief setStreamingMode enables external download control. * So Jingle-FT won't request output device but instead underlying established * connection will be emitted (see connectionReady() signal). * The connection is an XMPP::Jingle::Connection::Ptr instance. * When the connection is not needed anymore, one can just destroy jingle * session or remove the Application from the session. * Make sure to set the mode before connection is established. * @param mode */ void setStreamingMode(bool mode = true); bool isValid() const; void setDevice(QIODevice *dev, bool closeOnFinish = true); Connection::Ptr connection() const; protected: void incomingRemove(const Reason &r) override; void initTransport() override; private: void prepareThumbnail(File &file); signals: void connectionReady(); // streaming mode only // if size = 0 then it's reamaining part of the file (non-streaming mode only) void deviceRequested(qint64 offset, qint64 size); void progress(qint64 offset); private: class Private; QScopedPointer d; }; class Manager : public XMPP::Jingle::ApplicationManager { Q_OBJECT public: Manager(QObject *parent = nullptr); ~Manager(); void setJingleManager(XMPP::Jingle::Manager *jm); Application *startApplication(const ApplicationManagerPad::Ptr &pad, const QString &contentName, Origin creator, Origin senders); ApplicationManagerPad *pad(Session *session); // pad factory void closeAll(); Client * client(); QStringList availableTransports() const; private: XMPP::Jingle::Manager *jingleManager = nullptr; }; } // namespace FileTransfer } // namespace Jingle } // namespace XMPP #endif // JINGLEFT_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/jingle-ibb.cpp000066400000000000000000000372771370065651000244110ustar00rootroot00000000000000/* * jignle-ibb.cpp - Jingle In-Band Bytestream transport * Copyright (C) 2019 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "jingle-ibb.h" #include "jingle-session.h" #include "xmpp/jid/jid.h" #include "xmpp_client.h" #include "xmpp_ibb.h" #include #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) #include #endif namespace XMPP { namespace Jingle { namespace IBB { const QString NS(QStringLiteral("urn:xmpp:jingle:transports:ibb:1")); class Connection : public XMPP::Jingle::Connection { Q_OBJECT public: Client * client; Jid peer; QString sid; size_t _blockSize; IBBConnection *connection = nullptr; State state = State::Created; Origin creator = Origin::None; // bool offerSent = false; // bool offerReceived = false; // bool closing = false; // bool finished = false; Connection(Client *client, const Jid &jid, const QString &sid, size_t blockSize) : client(client), peer(jid), sid(sid), _blockSize(blockSize) { } void setConnection(IBBConnection *c) { c->setParent(this); connection = c; connect(c, &IBBConnection::readyRead, this, &Connection::readyRead); connect(c, &IBBConnection::bytesWritten, this, &Connection::bytesWritten); connect(c, &IBBConnection::connectionClosed, this, &Connection::handleIBBClosed); connect(c, &IBBConnection::delayedCloseFinished, this, &Connection::handleIBBClosed); connect(c, &IBBConnection::aboutToClose, this, &Connection::aboutToClose); connect(c, &IBBConnection::connected, this, &Connection::handleConnnected); } void handleConnnected() { state = State::Active; setOpenMode(connection->openMode()); emit connected(); } size_t blockSize() const { return _blockSize; } qint64 bytesAvailable() const { return XMPP::Jingle::Connection::bytesAvailable() + (connection ? connection->bytesAvailable() : 0); } qint64 bytesToWrite() const { return XMPP::Jingle::Connection::bytesToWrite() + (connection ? connection->bytesToWrite() : 0); } void close() { if (connection) { connection->close(); setOpenMode(connection->openMode()); } else { XMPP::Jingle::Connection::close(); emit connectionClosed(); } state = State::Finished; } signals: void connected(); protected: qint64 writeData(const char *data, qint64 maxSize) { return connection->write(data, maxSize); } qint64 readData(char *data, qint64 maxSize) { qint64 ret = connection->read(data, maxSize); if (state == State::Finishing && !bytesAvailable()) { postCloseAllDataRead(); } return ret; } private: void handleIBBClosed() { state = State::Finishing; if (bytesAvailable()) setOpenMode(QIODevice::ReadOnly); else postCloseAllDataRead(); } void postCloseAllDataRead() { state = State::Finished; connection->deleteLater(); connection = nullptr; setOpenMode(QIODevice::NotOpen); emit connectionClosed(); } }; struct Transport::Private { Transport * q = nullptr; QMap> connections; QList> readyConnections; size_t defaultBlockSize = 4096; bool started = false; void checkAndStartConnection(const QSharedPointer &c) { if (c->connection || c->state != State::Accepted) return; c->state = State::Connecting; if (q->_pad->session()->role() == Origin::Initiator) { auto con = q->_pad->session()->manager()->client()->ibbManager()->createConnection(); auto ibbcon = static_cast(con); ibbcon->setPacketSize(int(c->blockSize())); c->setConnection(ibbcon); ibbcon->connectToJid(q->_pad->session()->peer(), c->sid); } // else we are waiting for incoming open } QSharedPointer newStream(const QString &sid, std::size_t blockSize, Origin creator) { auto conn = q->_pad.staticCast()->makeConnection(sid, blockSize); auto ibbConn = conn.staticCast(); if (!ibbConn) return ibbConn; ibbConn->creator = creator; QObject::connect(ibbConn.data(), &Connection::connected, q, [this]() { if (q->_state == State::Connecting) { q->setState(State::Active); emit q->connected(); } }); connections.insert(ibbConn->sid, ibbConn); QObject::connect(ibbConn.data(), &Connection::connectionClosed, q, [this]() { Connection *c = static_cast(q->sender()); connections.remove(c->sid); QMutableListIterator> it(readyConnections); while (it.hasNext()) { auto &p = it.next(); if (p.data() == c) { it.remove(); break; } } }); return ibbConn; } }; Transport::Transport(const TransportManagerPad::Ptr &pad, Origin creator) : XMPP::Jingle::Transport(pad, creator), d(new Private) { d->q = this; connect(pad->manager(), &TransportManager::abortAllRequested, this, [this]() { for (auto &c : d->connections) { c->close(); } // d->aborted = true; emit failed(); // TODO review if necessary. likely it's not }); } Transport::~Transport() { // we have to mark all of them as finished just in case they are captured somewhere else if (d) { for (auto &c : d->connections) { c->close(); } } } void Transport::prepare() { setState(State::ApprovedToSend); if (_creator == _pad->session()->role()) { // outgoing auto c = d->newStream(QString(), d->defaultBlockSize, _pad->session()->role()); c->state = State::ApprovedToSend; } else { for (auto &c : d->connections) { c->state = State::ApprovedToSend; } } emit updated(); } void Transport::start() { setState(State::Connecting); for (auto &c : d->connections) { d->checkAndStartConnection(c); } } bool Transport::update(const QDomElement &transportEl) { if (_state == State::Finished) { qWarning("The IBB transport has finished already"); return false; } QString sid = transportEl.attribute(QString::fromLatin1("sid")); if (sid.isEmpty()) { qWarning("empty SID"); return false; } size_t bs_final = d->defaultBlockSize; auto bs = transportEl.attribute(QString::fromLatin1("block-size")); if (!bs.isEmpty()) { size_t bsn = bs.toULongLong(); if (bsn && bsn <= bs_final) { bs_final = bsn; } } auto it = d->connections.find(sid); if (it == d->connections.end()) { // new sid = new stream according to xep auto c = d->newStream(sid, bs_final, _pad->session()->peerRole()); if (!c) { qWarning("failed to create IBB connection"); return false; } c->state = State::Pending; if (_state == State::Created && _creator != _pad->session()->role()) { // seems like we are just initing remote transport setState(State::Pending); } } else { if ((*it)->creator != _pad->session()->role() || (*it)->state != State::Pending) { qWarning("Unexpected IBB answer"); return false; // out of order or something like this } if (bs_final < (*it)->_blockSize) { (*it)->_blockSize = bs_final; } if (_creator == _pad->session()->role()) { setState(State::Accepted); } (*it)->state = State::Accepted; } if (_state >= State::Connecting) { auto c = it.value(); QTimer::singleShot(0, this, [this, c]() mutable { d->checkAndStartConnection(c); }); } return true; } bool Transport::hasUpdates() const { for (auto &c : d->connections) { if (c->state == State::ApprovedToSend) { return true; } } return false; } OutgoingTransportInfoUpdate Transport::takeOutgoingUpdate() { OutgoingTransportInfoUpdate upd; if (!isValid()) { return upd; } QSharedPointer connection; for (auto &c : d->connections) { if (c->state == State::ApprovedToSend) { connection = c; break; } } if (!connection) return upd; connection->state = State::Unacked; auto doc = _pad->session()->manager()->client()->doc(); QDomElement tel = doc->createElementNS(NS, "transport"); tel.setAttribute(QStringLiteral("sid"), connection->sid); tel.setAttribute(QString::fromLatin1("block-size"), qulonglong(connection->_blockSize)); if (_state == State::ApprovedToSend) { setState(State::Unacked); } upd = OutgoingTransportInfoUpdate { tel, [this, connection](bool success) mutable { if (!success || connection->state != State::Unacked) return; if (connection->creator == _pad->session()->role()) { connection->state = State::Pending; } else { connection->state = State::Accepted; } if (_state == State::Unacked) { setState(_creator == _pad->session()->role() ? State::Pending : State::Accepted); } if (_state >= State::Connecting) d->checkAndStartConnection(connection); } }; return upd; } bool Transport::isValid() const { return d; } TransportFeatures Transport::features() const { return TransportFeatures(TransportFeature::AlwaysConnect) | TransportFeature::Reliable | TransportFeature::Slow; } int Transport::maxSupportedChannels() const { return -1; } Connection::Ptr Transport::addChannel() const { return d->readyConnections.isEmpty() ? Connection::Ptr() : d->readyConnections.takeFirst().staticCast(); } Pad::Pad(Manager *manager, Session *session) { _manager = manager; _session = session; } QString Pad::ns() const { return NS; } Session *Pad::session() const { return _session; } TransportManager *Pad::manager() const { return _manager; } Connection::Ptr Pad::makeConnection(const QString &sid, size_t blockSize) { return _manager->makeConnection(_session->peer(), sid, blockSize); } struct Manager::Private { QHash, QSharedPointer> connections; XMPP::Jingle::Manager * jingleManager = nullptr; }; Manager::Manager(QObject *parent) : TransportManager(parent), d(new Private) { } Manager::~Manager() { if (d->jingleManager) d->jingleManager->unregisterTransport(NS); } TransportFeatures Manager::features() const { return TransportFeatures(TransportFeature::AlwaysConnect) | TransportFeature::Reliable | TransportFeature::Slow; } void Manager::setJingleManager(XMPP::Jingle::Manager *jm) { d->jingleManager = jm; } QSharedPointer Manager::newTransport(const TransportManagerPad::Ptr &pad, Origin creator) { return QSharedPointer::create(pad, creator).staticCast(); } TransportManagerPad *Manager::pad(Session *session) { return new Pad(this, session); } void Manager::closeAll() { emit abortAllRequested(); } XMPP::Jingle::Connection::Ptr Manager::makeConnection(const Jid &peer, const QString &sid, size_t blockSize) { if (!sid.isEmpty() && d->connections.contains(qMakePair(peer, sid))) { qWarning("sid %s was already registered for %s", qPrintable(sid), qPrintable(peer.full())); return Connection::Ptr(); } QString s(sid); if (s.isEmpty()) { QPair key; do { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) s = QString("ibb_%1").arg(QRandomGenerator::global()->generate() & 0xffff, 4, 16, QChar('0')); #else s = QString("ibb_%1").arg(qrand() & 0xffff, 4, 16, QChar('0')); #endif key = qMakePair(peer, s); } while (d->connections.contains(key)); } auto conn = QSharedPointer::create(d->jingleManager->client(), peer, s, blockSize); d->connections.insert(qMakePair(peer, s), conn); connect(conn.data(), &Connection::connectionClosed, this, [this]() { Connection *c = static_cast(sender()); d->connections.remove(qMakePair(c->peer, c->sid)); }); return conn.staticCast(); } bool Manager::handleIncoming(IBBConnection *c) { auto conn = d->connections.value(qMakePair(c->peer(), c->sid())); if (conn) { conn->setConnection(c); QTimer::singleShot(0, c, &IBBConnection::accept); return true; } return false; } } // namespace IBB } // namespace Jingle } // namespace XMPP #include "jingle-ibb.moc" psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/jingle-ibb.h000066400000000000000000000063761370065651000240520ustar00rootroot00000000000000/* * jignle-ibb.h - Jingle In-Band Bytestream transport * Copyright (C) 2019 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef JINGLEIBB_H #define JINGLEIBB_H #include "jingle-transport.h" namespace XMPP { class IBBConnection; namespace Jingle { namespace IBB { extern const QString NS; class Transport : public XMPP::Jingle::Transport { Q_OBJECT public: Transport(const TransportManagerPad::Ptr &pad, Origin creator); ~Transport() override; void prepare() override; void start() override; bool update(const QDomElement &transportEl) override; bool hasUpdates() const override; OutgoingTransportInfoUpdate takeOutgoingUpdate() override; bool isValid() const override; TransportFeatures features() const override; int maxSupportedChannels() const override; Connection::Ptr addChannel() const override; private: friend class Manager; class Private; QScopedPointer d; }; class Manager; class Pad : public TransportManagerPad { Q_OBJECT // TODO public: typedef QSharedPointer Ptr; Pad(Manager *manager, Session *session); QString ns() const override; Session * session() const override; TransportManager *manager() const override; Connection::Ptr makeConnection(const QString &sid, size_t blockSize); private: Manager *_manager = nullptr; Session *_session = nullptr; }; class Manager : public TransportManager { Q_OBJECT public: Manager(QObject *parent = nullptr); ~Manager() override; XMPP::Jingle::TransportFeatures features() const override; void setJingleManager(XMPP::Jingle::Manager *jm) override; QSharedPointer newTransport(const TransportManagerPad::Ptr &pad, Origin creator) override; TransportManagerPad * pad(Session *session) override; void closeAll() override; Connection::Ptr makeConnection(const Jid &peer, const QString &sid, size_t blockSize); bool handleIncoming(IBBConnection *c); private: class Private; QScopedPointer d; }; } // namespace IBB } // namespace Jingle } // namespace XMPP #endif // JINGLEIBB_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/jingle-ice.cpp000066400000000000000000001224751370065651000244100ustar00rootroot00000000000000/* * jignle-s5b.cpp - Jingle SOCKS5 transport * Copyright (C) 2019 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "jingle-ice.h" #include "ice176.h" #include "jingle-session.h" #include "netnames.h" #include "udpportreserver.h" #include "xmpp/jid/jid.h" #include "xmpp_client.h" #include "xmpp_serverinfomanager.h" #include #include #include namespace XMPP { namespace Jingle { namespace ICE { const QString NS(QStringLiteral("urn:xmpp:jingle:transports:ice:0")); // TODO: reject offers that don't contain at least one of audio or video // TODO: support candidate negotiations over the JingleRtpChannel thread // boundary, so we can change candidates after the stream is active // scope values: 0 = local, 1 = link-local, 2 = private, 3 = public static int getAddressScope(const QHostAddress &a) { if (a.protocol() == QAbstractSocket::IPv6Protocol) { if (a == QHostAddress(QHostAddress::LocalHostIPv6)) return 0; else if (XMPP::Ice176::isIPv6LinkLocalAddress(a)) return 1; } else if (a.protocol() == QAbstractSocket::IPv4Protocol) { quint32 v4 = a.toIPv4Address(); quint8 a0 = v4 >> 24; quint8 a1 = (v4 >> 16) & 0xff; if (a0 == 127) return 0; else if (a0 == 169 && a1 == 254) return 1; else if (a0 == 10) return 2; else if (a0 == 172 && a1 >= 16 && a1 <= 31) return 2; else if (a0 == 192 && a1 == 168) return 2; } return 3; } // -1 = a is higher priority, 1 = b is higher priority, 0 = equal static int comparePriority(const QHostAddress &a, const QHostAddress &b) { // prefer closer scope int a_scope = getAddressScope(a); int b_scope = getAddressScope(b); if (a_scope < b_scope) return -1; else if (a_scope > b_scope) return 1; // prefer ipv6 if (a.protocol() == QAbstractSocket::IPv6Protocol && b.protocol() != QAbstractSocket::IPv6Protocol) return -1; else if (b.protocol() == QAbstractSocket::IPv6Protocol && a.protocol() != QAbstractSocket::IPv6Protocol) return 1; return 0; } static QList sortAddrs(const QList &in) { QList out; for (const QHostAddress &a : in) { int at; for (at = 0; at < out.count(); ++at) { if (comparePriority(a, out[at]) < 0) break; } out.insert(at, a); } return out; } // resolve external address and stun server // TODO: resolve hosts and start ice engine simultaneously // FIXME: when/if our ICE engine supports adding these dynamically, we should // not have the lookups block on each other class Resolver : public QObject { Q_OBJECT private: XMPP::NameResolver dnsA; XMPP::NameResolver dnsB; XMPP::NameResolver dnsC; XMPP::NameResolver dnsD; QString extHost; QString stunBindHost, stunRelayUdpHost, stunRelayTcpHost; bool extDone; bool stunBindDone; bool stunRelayUdpDone; bool stunRelayTcpDone; public: QHostAddress extAddr; QHostAddress stunBindAddr, stunRelayUdpAddr, stunRelayTcpAddr; Resolver(QObject *parent = nullptr) : QObject(parent), dnsA(parent), dnsB(parent), dnsC(parent), dnsD(parent) { connect(&dnsA, SIGNAL(resultsReady(const QList &)), SLOT(dns_resultsReady(const QList &))); connect(&dnsA, SIGNAL(error(XMPP::NameResolver::Error)), SLOT(dns_error(XMPP::NameResolver::Error))); connect(&dnsB, SIGNAL(resultsReady(const QList &)), SLOT(dns_resultsReady(const QList &))); connect(&dnsB, SIGNAL(error(XMPP::NameResolver::Error)), SLOT(dns_error(XMPP::NameResolver::Error))); connect(&dnsC, SIGNAL(resultsReady(const QList &)), SLOT(dns_resultsReady(const QList &))); connect(&dnsC, SIGNAL(error(XMPP::NameResolver::Error)), SLOT(dns_error(XMPP::NameResolver::Error))); connect(&dnsD, SIGNAL(resultsReady(const QList &)), SLOT(dns_resultsReady(const QList &))); connect(&dnsD, SIGNAL(error(XMPP::NameResolver::Error)), SLOT(dns_error(XMPP::NameResolver::Error))); } void start(const QString &_extHost, const QString &_stunBindHost, const QString &_stunRelayUdpHost, const QString &_stunRelayTcpHost) { extHost = _extHost; stunBindHost = _stunBindHost; stunRelayUdpHost = _stunRelayUdpHost; stunRelayTcpHost = _stunRelayTcpHost; if (!extHost.isEmpty()) { extDone = false; dnsA.start(extHost.toLatin1()); } else extDone = true; if (!stunBindHost.isEmpty()) { stunBindDone = false; dnsB.start(stunBindHost.toLatin1()); } else stunBindDone = true; if (!stunRelayUdpHost.isEmpty()) { stunRelayUdpDone = false; dnsC.start(stunRelayUdpHost.toLatin1()); } else stunRelayUdpDone = true; if (!stunRelayTcpHost.isEmpty()) { stunRelayTcpDone = false; dnsD.start(stunRelayTcpHost.toLatin1()); } else stunRelayTcpDone = true; if (extDone && stunBindDone && stunRelayUdpDone && stunRelayTcpDone) QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); } signals: void finished(); private slots: void dns_resultsReady(const QList &results) { XMPP::NameResolver *dns = static_cast(sender()); // FIXME: support more than one address? QHostAddress addr = results.first().address(); if (dns == &dnsA) { extAddr = addr; extDone = true; tryFinish(); } else if (dns == &dnsB) { stunBindAddr = addr; stunBindDone = true; tryFinish(); } else if (dns == &dnsC) { stunRelayUdpAddr = addr; stunRelayUdpDone = true; tryFinish(); } else // dnsD { stunRelayTcpAddr = addr; stunRelayTcpDone = true; tryFinish(); } } void dns_error(XMPP::NameResolver::Error e) { Q_UNUSED(e) XMPP::NameResolver *dns = static_cast(sender()); if (dns == &dnsA) { extDone = true; tryFinish(); } else if (dns == &dnsB) { stunBindDone = true; tryFinish(); } else if (dns == &dnsC) { stunRelayUdpDone = true; tryFinish(); } else // dnsD { stunRelayTcpDone = true; tryFinish(); } } private: void tryFinish() { if (extDone && stunBindDone && stunRelayUdpDone && stunRelayTcpDone) emit finished(); } }; class IceStopper : public QObject { Q_OBJECT public: QTimer t; XMPP::UdpPortReserver *portReserver; QList left; IceStopper(QObject *parent = nullptr) : QObject(parent), t(this), portReserver(nullptr) { connect(&t, SIGNAL(timeout()), SLOT(t_timeout())); t.setSingleShot(true); } ~IceStopper() { qDeleteAll(left); delete portReserver; printf("IceStopper done\n"); } void start(XMPP::UdpPortReserver *_portReserver, const QList iceList) { if (_portReserver) { portReserver = _portReserver; portReserver->setParent(this); } left = iceList; for (XMPP::Ice176 *ice : left) { ice->setParent(this); // TODO: error() also? connect(ice, SIGNAL(stopped()), SLOT(ice_stopped())); connect(ice, SIGNAL(error(XMPP::Ice176::Error)), SLOT(ice_error(XMPP::Ice176::Error))); ice->stop(); } t.start(3000); } private slots: void ice_stopped() { XMPP::Ice176 *ice = static_cast(sender()); ice->disconnect(this); ice->setParent(nullptr); ice->deleteLater(); left.removeAll(ice); if (left.isEmpty()) deleteLater(); } void ice_error(XMPP::Ice176::Error e) { Q_UNUSED(e) ice_stopped(); } void t_timeout() { deleteLater(); } }; class Manager::Private { public: XMPP::Jingle::Manager *jingleManager = nullptr; int basePort = -1; QString extHost; QHostAddress selfAddr; QString stunBindHost; int stunBindPort; QString stunRelayUdpHost; int stunRelayUdpPort; QString stunRelayUdpUser; QString stunRelayUdpPass; QString stunRelayTcpHost; int stunRelayTcpPort; QString stunRelayTcpUser; QString stunRelayTcpPass; XMPP::TurnClient::Proxy stunProxy; // FIMME it's reuiqred to split transports by direction otherwise we gonna hit conflicts. // jid,transport-sid -> transport mapping // QSet> sids; // QHash key2transport; // Jid proxy; }; class JingleRtpRemoteCandidate { public: int component; QHostAddress addr; int port; JingleRtpRemoteCandidate() : component(-1), port(-1) { } }; class JingleRtpTrans { public: QString user; QString pass; QList candidates; QList remoteCandidates; }; static XMPP::Ice176::Candidate elementToCandidate(const QDomElement &e) { if (e.tagName() != "candidate") return XMPP::Ice176::Candidate(); XMPP::Ice176::Candidate c; c.component = e.attribute("component").toInt(); c.foundation = e.attribute("foundation"); c.generation = e.attribute("generation").toInt(); c.id = e.attribute("id"); c.ip = QHostAddress(e.attribute("ip")); c.network = e.attribute("network").toInt(); c.port = e.attribute("port").toInt(); c.priority = e.attribute("priority").toInt(); c.protocol = e.attribute("protocol"); c.rel_addr = QHostAddress(e.attribute("rel-addr")); c.rel_port = e.attribute("rel-port").toInt(); // TODO: remove this? // c.rem_addr = QHostAddress(e.attribute("rem-addr")); // c.rem_port = e.attribute("rem-port").toInt(); c.type = e.attribute("type"); return c; } class Connection : public XMPP::Jingle::Connection { Q_OBJECT public: QList datagrams; void * client; int channelIndex; Connection(int channelIndex) : channelIndex(channelIndex) { /*connect(client, &SocksClient::readyRead, this, &Connection::readyRead); connect(client, &SocksClient::bytesWritten, this, &Connection::bytesWritten); connect(client, &SocksClient::aboutToClose, this, &Connection::aboutToClose); if (client->isOpen()) { setOpenMode(client->openMode()); } else { qWarning("Creating S5B Transport connection on closed SockClient connection %p", client); }*/ } bool hasPendingDatagrams() const { return datagrams.size() > 0; } NetworkDatagram receiveDatagram(qint64 maxSize = -1) { Q_UNUSED(maxSize) // TODO or not? return datagrams.size() ? datagrams.takeFirst() : NetworkDatagram(); } qint64 bytesAvailable() const { return 0; } qint64 bytesToWrite() const { return 0; /*client->bytesToWrite();*/ } void close() { XMPP::Jingle::Connection::close(); } protected: qint64 writeData(const char *data, qint64 maxSize) { return 0; // client->write(data, maxSize); } qint64 readData(char *data, qint64 maxSize) { return 0; } private: friend class Transport; void onConnected(Ice176 *ice) { emit connected(); } void enqueueIncomingUDP(const QByteArray &data) { datagrams.append(NetworkDatagram { data }); emit readyRead(); } }; class Transport::Private { public: enum PendingActions { NewCandidate = 1, RemoteCandidate = 2, GatheringComplete = 4 }; Transport * q = nullptr; bool offerSent = false; bool waitingAck = true; bool aborted = false; bool proxyDiscoveryInProgress = false; // if we have valid proxy requests bool remoteReportedGatheringComplete = false; bool iceStarted = false; quint16 pendingActions = 0; int proxiesInDiscoCount = 0; QList localCandidates; // cid to candidate mapping QList remoteCandidates; QString remoteUfrag; QString remotePassword; // QString sid; // Transport::Mode mode = Transport::Tcp; // QTimer probingTimer; // QTimer negotiationFinishTimer; // QElapsedTimer lastConnectionStart; // size_t blockSize = 8192; TcpPortDiscoverer *disco = nullptr; UdpPortReserver * portReserver = nullptr; Resolver resolver; XMPP::Ice176 * ice = nullptr; QHostAddress extAddr; QHostAddress stunBindAddr, stunRelayUdpAddr, stunRelayTcpAddr; int stunBindPort; int stunRelayUdpPort; int stunRelayTcpPort; QMap> channels; // udp stuff bool udpInitialized; quint16 udpPort; QHostAddress udpAddress; inline Jid remoteJid() const { return q->_pad->session()->peer(); } void flushRemoteCandidates() { if (q->_state < State::ApprovedToSend || q->_state == State::Finished) return; ice->setPeerUfrag(remoteUfrag); ice->setPeerPassword(remotePassword); if (!remoteCandidates.isEmpty()) { ice->addRemoteCandidates(remoteCandidates); remoteCandidates.clear(); } } bool handleIncomingCandidate(const QDomElement &transportEl) { QString candidateTag(QStringLiteral("candidate")); int candidatesAdded = 0; for (QDomElement ce = transportEl.firstChildElement(candidateTag); !ce.isNull(); ce = ce.nextSiblingElement(candidateTag)) { XMPP::Ice176::Candidate ic = elementToCandidate(ce); if (ic.type.isEmpty()) { throw std::runtime_error("failed to parse incoming candidate"); } if (!candidatesAdded) { remoteUfrag = transportEl.attribute("ufrag"); remotePassword = transportEl.attribute("pwd"); if (remoteUfrag.isEmpty() || remotePassword.isEmpty()) throw std::runtime_error("user fragment or password can't be empty"); } // qDebug("new remote candidate: %s", qPrintable(c.toString())); remoteCandidates.append(ic); // TODO check for collisions! candidatesAdded++; } if (candidatesAdded) { QTimer::singleShot(0, q, [this]() { flushRemoteCandidates(); }); return true; } return false; } bool handleIncomingRemoteCandidate(const QDomElement &transportEl) { QDomElement el = transportEl.firstChildElement(QStringLiteral("remote-candidate")); if (!el.isNull()) { bool ok, ok2; auto component = el.attribute(QLatin1String("component")).toUInt(&ok); auto ip = QHostAddress(el.attribute(QLatin1String("ip"))); uint16_t port = el.attribute(QLatin1String("port")).toUShort(&ok2); if (!(ok && ok2 && !ip.isNull())) throw std::runtime_error("failed to parse remote-candidate"); /* auto cUsed = localCandidates.value(el.attribute(QStringLiteral("cid"))); if (!cUsed) { throw std::runtime_error("failed to find incoming candidate-used candidate"); } if (cUsed.state() == Candidate::Pending) { cUsed.setState(Candidate::Accepted); localUsedCandidate = cUsed; updateMinimalPriorityOnConnected(); QTimer::singleShot(0, q, [this]() { checkAndFinishNegotiation(); }); } else { // we already rejected the candidate and either remote side already knows about it or will soon // it's possible for example if we were able to connect to higher priority candidate, so // we have o pretend like remote couldn't select anything better but finished already, in other // words like if it sent candidate-error. localUsedCandidate = Candidate(); } */ return true; } return false; } bool handleIncomingGatheringComplete(const QDomElement &transportEl) { auto el = transportEl.firstChildElement(QStringLiteral("gathering-complete")); if (!el.isNull()) { remoteReportedGatheringComplete = true; /* for (auto &c : localCandidates) { if (c.state() == Candidate::Pending) { c.setState(Candidate::Discarded); } } */ qDebug("recv gathering-complete: all local pending candidates were discarded"); // QTimer::singleShot(0, q, [this]() { checkAndFinishNegotiation(); }); return true; } return false; } void startIce() { auto manager = dynamic_cast(q->_pad->manager())->d.data(); stunBindPort = manager->stunBindPort; stunRelayUdpPort = manager->stunRelayUdpPort; stunRelayTcpPort = manager->stunRelayTcpPort; if (!stunBindAddr.isNull() && stunBindPort > 0) printf("STUN service: %s;%d\n", qPrintable(stunBindAddr.toString()), stunBindPort); if (!stunRelayUdpAddr.isNull() && stunRelayUdpPort > 0 && !manager->stunRelayUdpUser.isEmpty()) printf("TURN w/ UDP service: %s;%d\n", qPrintable(stunRelayUdpAddr.toString()), stunRelayUdpPort); if (!stunRelayTcpAddr.isNull() && stunRelayTcpPort > 0 && !manager->stunRelayTcpUser.isEmpty()) printf("TURN w/ TCP service: %s;%d\n", qPrintable(stunRelayTcpAddr.toString()), stunRelayTcpPort); QList listenAddrs; auto const interfaces = QNetworkInterface::allInterfaces(); for (const QNetworkInterface &ni : interfaces) { QList entries = ni.addressEntries(); for (const QNetworkAddressEntry &na : entries) { QHostAddress h = na.ip(); // skip localhost if (getAddressScope(h) == 0) continue; // don't put the same address in twice. // this also means that if there are // two link-local ipv6 interfaces // with the exact same address, we // only use the first one if (listenAddrs.contains(h)) continue; if (h.protocol() == QAbstractSocket::IPv6Protocol && XMPP::Ice176::isIPv6LinkLocalAddress(h)) h.setScopeId(ni.name()); listenAddrs += h; } } listenAddrs = sortAddrs(listenAddrs); QList localAddrs; QStringList strList; for (const QHostAddress &h : listenAddrs) { XMPP::Ice176::LocalAddress addr; addr.addr = h; localAddrs += addr; strList += h.toString(); } if (manager->basePort != -1) { portReserver = new XMPP::UdpPortReserver(q); portReserver->setAddresses(listenAddrs); portReserver->setPorts(manager->basePort, 4); } if (!strList.isEmpty()) { printf("Host addresses:\n"); for (const QString &s : strList) printf(" %s\n", qPrintable(s)); } ice = new XMPP::Ice176(q); iceStarted = false; // iceA_status.channelsReady.resize(2); // iceA_status.channelsReady[0] = false; // iceA_status.channelsReady[1] = false; q->connect(ice, &XMPP::Ice176::started, [this]() { for (auto &c : channels) { ice->flagComponentAsLowOverhead((c->hints() & Connection::AvoidRelays) ? 0 : 1); } iceStarted = true; }); q->connect(ice, &XMPP::Ice176::error, [this](XMPP::Ice176::Error err) { q->_lastReason = Reason(Reason::Condition::FailedTransport, "ICE failed"); q->setState(State::Finished); emit q->failed(); }); q->connect(ice, &XMPP::Ice176::localCandidatesReady, [this](const QList &candidates) { localCandidates = candidates; if (q->_state >= State::ApprovedToSend) emit q->updated(); }); QObject::connect( ice, &XMPP::Ice176::componentReady, q, [this](int componentIdx) { for (auto &c : channels) { if (c->channelIndex == componentIdx) { c->onConnected(ice); } } }, Qt::QueuedConnection); // signal is not DOR-SS ice->setProxy(manager->stunProxy); if (portReserver) ice->setPortReserver(portReserver); // QList localAddrs; // XMPP::Ice176::LocalAddress addr; // FIXME: the following is not true, a local address is not // required, for example if you use TURN with TCP only // a local address is required to use ice. however, if // we don't have a local address, we won't handle it as // an error here. instead, we'll start Ice176 anyway, // which should immediately error back at us. /*if(manager->selfAddr.isNull()) { printf("no self address to use. this will fail.\n"); return; } addr.addr = manager->selfAddr; localAddrs += addr;*/ ice->setLocalAddresses(localAddrs); // if an external address is manually provided, then apply // it only to the selfAddr. FIXME: maybe we should apply // it to all local addresses? if (!extAddr.isNull()) { QList extAddrs; /*XMPP::Ice176::ExternalAddress eaddr; eaddr.base = addr; eaddr.addr = extAddr; extAddrs += eaddr;*/ for (const XMPP::Ice176::LocalAddress &la : localAddrs) { XMPP::Ice176::ExternalAddress ea; ea.base = la; ea.addr = extAddr; extAddrs += ea; } ice->setExternalAddresses(extAddrs); } if (!stunBindAddr.isNull() && stunBindPort > 0) ice->setStunBindService(stunBindAddr, stunBindPort); if (!stunRelayUdpAddr.isNull() && !manager->stunRelayUdpUser.isEmpty()) ice->setStunRelayUdpService(stunRelayUdpAddr, stunRelayUdpPort, manager->stunRelayUdpUser, manager->stunRelayUdpPass.toUtf8()); if (!stunRelayTcpAddr.isNull() && !manager->stunRelayTcpUser.isEmpty()) ice->setStunRelayTcpService(stunRelayTcpAddr, stunRelayTcpPort, manager->stunRelayTcpUser, manager->stunRelayTcpPass.toUtf8()); // RTP+RTCP ice->setComponentCount(channels.count()); ice->setLocalFeatures(Ice176::Trickle); auto mode = q->creator() == q->pad()->session()->role() ? XMPP::Ice176::Initiator : XMPP::Ice176::Responder; ice->start(mode); } }; Transport::Transport(const TransportManagerPad::Ptr &pad, Origin creator) : XMPP::Jingle::Transport(pad, creator), d(new Private) { d->q = this; connect(_pad->manager(), &TransportManager::abortAllRequested, this, [this]() { d->aborted = true; _state = State::Finished; emit failed(); }); } Transport::~Transport() { if (d) { } } void Transport::prepare() { qDebug("Prepare local offer"); setState(State::ApprovedToSend); // auto md = static_cast(_pad.staticCast()->manager())->d.data(); /* if (_creator == _pad->session()->role()) { // I'm creator d->sid = _pad.staticCast()->generateSid(); } _pad.staticCast()->registerSid(d->sid); d->directAddr = makeKey(d->sid, _pad.staticCast()->session()->initiator(), _pad.staticCast()->session()->responder()); m->addKeyMapping(d->directAddr, this); */ // auto scope = _pad.staticCast()->discoScope(); // d->disco = scope->disco(); // FIXME store and handle signale. delete when not needed auto manager = dynamic_cast(_pad->manager())->d.data(); auto resolver = new Resolver(); connect(resolver, &Resolver::finished, this, [this, resolver]() { d->extAddr = resolver->extAddr; d->stunBindAddr = resolver->stunBindAddr; d->stunRelayUdpAddr = resolver->stunRelayUdpAddr; d->stunRelayTcpAddr = resolver->stunRelayTcpAddr; printf("resolver finished\n"); resolver->deleteLater(); d->startIce(); }); d->resolver.start(manager->extHost, manager->stunBindHost, manager->stunRelayUdpHost, manager->stunRelayTcpHost); // connect(d->disco, &TcpPortDiscoverer::portAvailable, this, [this]() { d->onLocalServerDiscovered(); }); // d->onLocalServerDiscovered(); emit updated(); } // we got content acceptance from any side and now can connect void Transport::start() { qDebug("Starting connecting"); setState(State::Connecting); } bool Transport::update(const QDomElement &transportEl) { try { if (d->handleIncomingCandidate(transportEl) || d->handleIncomingRemoteCandidate(transportEl) || d->handleIncomingGatheringComplete(transportEl)) { if (_state == State::Created && _creator != _pad->session()->role()) { // initial incoming transport setState(State::Pending); } if (_state == State::Pending && _creator == _pad->session()->role()) { // initial acceptance by remote of the local transport setState(State::Accepted); } return true; } } catch (std::runtime_error &e) { qWarning("Transport updated failed: %s", e.what()); return false; } return true; } bool Transport::hasUpdates() const { return isValid() && d->pendingActions; } OutgoingTransportInfoUpdate Transport::takeOutgoingUpdate() { qDebug("taking outgoing update"); OutgoingTransportInfoUpdate upd; if (!isValid()) { return upd; } auto makeUpdate = [&](QDomElement tel, std::function cb = std::function()) { d->waitingAck = true; return OutgoingTransportInfoUpdate { tel, [this, cb, trptr = QPointer(d->q)](bool success) { if (!success || !trptr) return; d->waitingAck = false; if (cb) cb(); } }; }; auto doc = _pad.staticCast()->session()->manager()->client()->doc(); QDomElement tel = doc->createElementNS(NS, "transport"); // tel.setAttribute(QStringLiteral("sid"), d->sid); // check where we make initial offer bool noPending = (d->localCandidates.isEmpty() && !d->proxyDiscoveryInProgress && !(d->disco && d->disco->inProgressPortTypes())); bool initial = _state == State::ApprovedToSend && !d->offerSent && ((!d->pendingActions && noPending) || d->pendingActions & Private::NewCandidate); if (initial) { d->offerSent = true; } if (d->pendingActions & Private::NewCandidate) { d->pendingActions &= ~Private::NewCandidate; QList candidatesToSend; for (auto &c : d->localCandidates) { // if (c.state() != Candidate::New) { // continue; // } // if (c.type() == Candidate::Proxy) { // useProxy = true; // } // qDebug("sending local candidate: cid=%s", qPrintable(c.cid())); // tel.appendChild(c.toXml(doc)); // candidatesToSend.append(c); // c.setState(Candidate::Unacked); } if (!candidatesToSend.isEmpty()) { upd = makeUpdate(tel, [this, candidatesToSend, initial]() mutable { if (initial) { _state = _creator == _pad->session()->role() ? State::Pending : State::Accepted; } // d->checkAndFinishNegotiation(); }); } else { qWarning("Got NewCandidate pending action but no candidate to send"); } } else if (d->pendingActions & Private::RemoteCandidate) { d->pendingActions &= ~Private::RemoteCandidate; // we should have the only remote candidate in Pending state. // all other has to be discarded by priority check for (auto &c : d->remoteCandidates) { // if (c.state() != Candidate::Pending) { // continue; // } // qDebug("sending candidate-used: cid=%s", qPrintable(c.cid())); auto el = tel.appendChild(doc->createElement(QStringLiteral("remote-candidate"))).toElement(); // el.setAttribute(QStringLiteral("cid"), c.cid()); // c.setState(Candidate::Unacked); upd = makeUpdate(tel, [this, c]() mutable { // if (c.state() == Candidate::Unacked) { // c.setState(Candidate::Accepted); // qDebug("ack: sending candidate-used: cid=%s", qPrintable(c.cid())); // d->remoteUsedCandidate = c; // } // d->checkAndFinishNegotiation(); }); break; } if (std::get<0>(upd).isNull()) { qWarning("Got CandidateUsed pending action but no pending candidates"); } } else if (d->pendingActions & Private::GatheringComplete) { d->pendingActions &= ~Private::GatheringComplete; qDebug("sending gathering-complete"); // we are here because all remote are already in Discardd state tel.appendChild(doc->createElement(QStringLiteral("gathering-complete"))); upd = makeUpdate(tel, [this]() mutable { // d->localReportedGatheringComplete = true; // d->checkAndFinishNegotiation(); }); } else { qDebug("sending empty transport-info"); upd = makeUpdate(tel, [this, initial]() mutable { if (initial) { _state = _creator == _pad->session()->role() ? State::Pending : State::Accepted; } }); } return upd; // TODO } bool Transport::isValid() const { return d != nullptr; } TransportFeatures Transport::features() const { return TransportFeatures(TransportFeature::HardToConnect) | TransportFeature::Reliable | TransportFeature::Fast; } int Transport::maxSupportedChannels() const { return -1; }; Connection::Ptr Transport::addChannel() const { if (_state >= State::ApprovedToSend) { qWarning("Adding channel after negotiation start is not yet supported"); return Connection::Ptr(); } int channelIdx = 0; auto it = d->channels.constBegin(); while (it != d->channels.constEnd()) { if (it.key() != channelIdx) break; channelIdx++; ++it; } auto conn = QSharedPointer::create(channelIdx); d->channels.insert(it, channelIdx, conn); return conn.staticCast(); } //---------------------------------------------------------------- // Manager //---------------------------------------------------------------- Manager::Manager(QObject *parent) : TransportManager(parent), d(new Private) { } Manager::~Manager() { if (d->jingleManager) d->jingleManager->unregisterTransport(NS); } TransportFeatures Manager::features() const { return TransportFeatures(TransportFeature::Reliable) | TransportFeature::NotReliable | TransportFeature::RealTime; } void Manager::setJingleManager(XMPP::Jingle::Manager *jm) { d->jingleManager = jm; } QSharedPointer Manager::newTransport(const TransportManagerPad::Ptr &pad, Origin creator) { return QSharedPointer::create(pad, creator).staticCast(); } TransportManagerPad *Manager::pad(Session *session) { return new Pad(this, session); } void Manager::closeAll() { emit abortAllRequested(); } void Manager::setBasePort(int port) { d->basePort = port; } void Manager::setExternalAddress(const QString &host) { d->extHost = host; } void Manager::setSelfAddress(const QHostAddress &addr) { d->selfAddr = addr; } void Manager::setStunBindService(const QString &host, int port) { d->stunBindHost = host; d->stunBindPort = port; } void Manager::setStunRelayUdpService(const QString &host, int port, const QString &user, const QString &pass) { d->stunRelayUdpHost = host; d->stunRelayUdpPort = port; d->stunRelayUdpUser = user; d->stunRelayUdpPass = pass; } void Manager::setStunRelayTcpService(const QString &host, int port, const XMPP::AdvancedConnector::Proxy &proxy, const QString &user, const QString &pass) { d->stunRelayTcpHost = host; d->stunRelayTcpPort = port; d->stunRelayTcpUser = user; d->stunRelayTcpPass = pass; XMPP::TurnClient::Proxy tproxy; if (proxy.type() == XMPP::AdvancedConnector::Proxy::HttpConnect) { tproxy.setHttpConnect(proxy.host(), proxy.port()); tproxy.setUserPass(proxy.user(), proxy.pass()); } else if (proxy.type() == XMPP::AdvancedConnector::Proxy::Socks) { tproxy.setSocks(proxy.host(), proxy.port()); tproxy.setUserPass(proxy.user(), proxy.pass()); } d->stunProxy = tproxy; } // void Manager::addKeyMapping(const QString &key, Transport *transport) { d->key2transport.insert(key, transport); // } // void Manager::removeKeyMapping(const QString &key) { d->key2transport.remove(key); } // QString Manager::generateSid(const Jid &remote) // { // auto servers = // d->jingleManager->client()->tcpPortReserver()->scope(QString::fromLatin1("s5b"))->allServers(); QString // sid; QPair key; QString key1; QString key2; auto servChecker = // [&](const TcpPortServer::Ptr &s) { // return s.staticCast()->hasKey(key1) || s.staticCast()->hasKey(key2); // }; // do { // sid = QString("s5b_%1").arg(qrand() & 0xffff, 4, 16, QChar('0')); // key = qMakePair(remote, sid); // key1 = makeKey(sid, remote, d->jingleManager->client()->jid()); // key2 = makeKey(sid, d->jingleManager->client()->jid(), remote); // } while (d->sids.contains(key) || std::find_if(servers.begin(), servers.end(), servChecker) != // servers.end()); return sid; // } // void Manager::registerSid(const Jid &remote, const QString &sid) { d->sids.insert(qMakePair(remote, sid)); } // Jid Manager::userProxy() const { return d->proxy; } // void Manager::setUserProxy(const Jid &jid) { d->proxy = jid; } //---------------------------------------------------------------- // Pad //---------------------------------------------------------------- Pad::Pad(Manager *manager, Session *session) : _manager(manager), _session(session) { auto reserver = _session->manager()->client()->tcpPortReserver(); _discoScope = reserver->scope(QString::fromLatin1("s5b")); } QString Pad::ns() const { return NS; } Session *Pad::session() const { return _session; } TransportManager *Pad::manager() const { return _manager; } } // namespace Ice } // namespace Jingle } // namespace XMPP #include "jingle-ice.moc" psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/jingle-ice.h000066400000000000000000000201501370065651000240400ustar00rootroot00000000000000/* * jignle-ice.h - Jingle SOCKS5 transport * Copyright (C) 2019 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef JINGLE_ICE_H #define JINGLE_ICE_H #include "jingle-transport.h" #include "tcpportreserver.h" #include "xmpp.h" class QHostAddress; namespace XMPP { class Client; namespace Jingle { namespace ICE { extern const QString NS; class Transport; #if 0 class Candidate { public: enum Type { None, // non standard, just a default Proxy, Tunnel, Assisted, Direct }; enum { ProxyPreference = 10, TunnelPreference = 110, AssistedPreference = 120, DirectPreference = 126 }; /** * Local candidates states: * Probing - potential candidate but no ip:port yet. upnp for example * New - candidate is ready to be sent to remote * Unacked - candidate is sent to remote but no iq ack yet * Pending - canidate sent to remote. we have iq ack but no "used" or "error" * Accepted - we got "candidate-used" for this candidate * Activating - only for proxy: we activate the proxy * Active - use this candidate for actual data transfer * Discarded - we got "candidate-error" so all pending were marked Discarded * * Remote candidates states: * New - the candidate waits its turn to start connection probing * Probing - connection probing * Pending - connection was successful, but we didn't send candidate-used to remote * Unacked - connection was successful and we sent candidate-used to remote but no iq ack yet * Accepted - we sent candidate-used and got iq ack * Activating - [not used] * Active - use this candidate for actual data transfer * Discarded - failed to connect to all remote candidates */ enum State { New, Probing, Pending, Unacked, Accepted, Activating, Active, Discarded, }; Candidate(); Candidate(Transport *transport, const QDomElement &el); Candidate(const Candidate &other); Candidate(Transport *transport, const Jid &proxy, const QString &cid, quint16 localPreference = 0); Candidate(Transport *transport, const TcpPortServer::Ptr &server, const QString &cid, quint16 localPreference = 0); ~Candidate(); Candidate & operator=(const Candidate &other) = default; inline bool isValid() const { return d != nullptr; } inline operator bool() const { return isValid(); } Type type() const; static const char *typeText(Type t); QString cid() const; Jid jid() const; QString host() const; void setHost(const QString &host); quint16 port() const; void setPort(quint16 port); quint16 localPort() const; QHostAddress localAddress() const; State state() const; void setState(State s); static const char *stateText(State s); quint32 priority() const; QDomElement toXml(QDomDocument *doc) const; QString toString() const; void connectToHost(const QString &key, State successState, QObject *callbackContext, std::function callback, bool isUdp = false); // bool incomingConnection(SocksClient *sc); // SocksClient * takeSocksClient(); // void deleteSocksClient(); TcpPortServer::Ptr server() const; bool operator==(const Candidate &other) const; inline bool operator!=(const Candidate &other) const { return !(*this == other); } private: class Private; friend class Transport; QExplicitlySharedDataPointer d; }; #endif class Manager; class Transport : public XMPP::Jingle::Transport { Q_OBJECT public: enum Mode { Tcp, Udp }; Transport(const TransportManagerPad::Ptr &pad, Origin creator); ~Transport() override; void prepare() override; void start() override; bool update(const QDomElement &transportEl) override; bool hasUpdates() const override; OutgoingTransportInfoUpdate takeOutgoingUpdate() override; bool isValid() const override; TransportFeatures features() const override; int maxSupportedChannels() const override; Connection::Ptr addChannel() const override; private: friend class Manager; class Private; QScopedPointer d; }; class Pad : public TransportManagerPad { Q_OBJECT // TODO public: typedef QSharedPointer Ptr; Pad(Manager *manager, Session *session); QString ns() const override; Session * session() const override; TransportManager *manager() const override; inline TcpPortScope *discoScope() const { return _discoScope; } private: Manager * _manager; Session * _session; TcpPortScope *_discoScope; }; class Manager : public TransportManager { Q_OBJECT public: Manager(QObject *parent = nullptr); ~Manager() override; XMPP::Jingle::TransportFeatures features() const override; void setJingleManager(XMPP::Jingle::Manager *jm) override; QSharedPointer newTransport(const TransportManagerPad::Ptr &pad, Origin creator) override; TransportManagerPad * pad(Session *session) override; void closeAll() override; /** * @brief userProxy returns custom (set by user) SOCKS proxy JID * @return */ // Jid userProxy() const; // void setUserProxy(const Jid &jid); /** * @brief addKeyMapping sets mapping between key/socks hostname used for direct connection and transport. * The key is sha1(sid, initiator full jid, responder full jid) * @param key * @param transport */ void addKeyMapping(const QString &key, Transport *transport); void removeKeyMapping(const QString &key); void setBasePort(int port); void setExternalAddress(const QString &host); void setSelfAddress(const QHostAddress &addr); void setStunBindService(const QString &host, int port); void setStunRelayUdpService(const QString &host, int port, const QString &user, const QString &pass); void setStunRelayTcpService(const QString &host, int port, const XMPP::AdvancedConnector::Proxy &proxy, const QString &user, const QString &pass); // stunProxy() const; private: friend class Transport; class Private; QScopedPointer d; }; } // namespace Ice } // namespace Jingle } // namespace XMPP #endif // JINGLE_ICE_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/jingle-nstransportslist.cpp000066400000000000000000000053221370065651000273130ustar00rootroot00000000000000/* * jignle-nstransportslist.cpp - Simple transport selector based on sorted namespaces list * Copyright (C) 2020 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "jingle-nstransportslist.h" #include "jingle-session.h" namespace XMPP { namespace Jingle { QSharedPointer NSTransportsList::getNextTransport() { return getNextNSTransport(); } QSharedPointer NSTransportsList::getAlikeTransport(QSharedPointer alike) { return getNextNSTransport(alike->pad()->ns()); } QSharedPointer NSTransportsList::getNextNSTransport(const QString &preferredNS) { if (_transports.isEmpty()) { return QSharedPointer(); } int idx = _transports.indexOf(preferredNS); if (idx == -1) idx = _transports.size() - 1; do { auto t = _session->newOutgoingTransport(_transports[idx]); if (t) { return t; } _transports.removeAt(idx); idx = _transports.size() - 1; } while (idx != -1); return QSharedPointer(); } void NSTransportsList::backupTransport(QSharedPointer tr) { _transports += tr->pad()->ns(); } bool NSTransportsList::hasMoreTransports() const { return !_transports.isEmpty(); } bool NSTransportsList::hasTransport(QSharedPointer t) const { return t && _transports.indexOf(t->pad()->ns()) != -1; } int NSTransportsList::compare(QSharedPointer a, QSharedPointer b) const { int idxA = -1; if (a) idxA = _transports.indexOf(a->pad()->ns()); int idxB = -1; if (b) idxB = _transports.indexOf(b->pad()->ns()); return idxA < idxB ? -1 : idxA > idxB ? 1 : 0; } bool NSTransportsList::replace(QSharedPointer old, QSharedPointer newer) { if (!newer || !canReplace(old, newer)) return false; _transports.removeAll(newer->pad()->ns()); return true; } }} psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/jingle-nstransportslist.h000066400000000000000000000036051370065651000267620ustar00rootroot00000000000000/* * jignle-nstransportslist.h - Simple transport selector based on sorted namespaces list * Copyright (C) 2020 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef JINGLENSTRANSPORTSLIST_H #define JINGLENSTRANSPORTSLIST_H #include "jingle-transport.h" namespace XMPP { namespace Jingle { class Session; class NSTransportsList : public TransportSelector { public: inline NSTransportsList(Session *session, const QStringList &transports) : _session(session), _transports(transports) { } QSharedPointer getNextTransport() override; QSharedPointer getAlikeTransport(QSharedPointer alike) override; QSharedPointer getNextNSTransport(const QString &preferredNS = QString()); void backupTransport(QSharedPointer) override; bool hasMoreTransports() const override; bool hasTransport(QSharedPointer) const override; int compare(QSharedPointer a, QSharedPointer b) const override; bool replace(QSharedPointer old, QSharedPointer newer) override; private: Session * _session = nullptr; QStringList _transports; }; }} #endif psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/jingle-s5b.cpp000066400000000000000000002247631370065651000243440ustar00rootroot00000000000000/* * jignle-s5b.cpp - Jingle SOCKS5 transport * Copyright (C) 2019 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "jingle-s5b.h" #include "ice176.h" #include "jingle-session.h" #include "jingle.h" #include "s5b.h" #include "socks.h" #include "xmpp/jid/jid.h" #include "xmpp_client.h" #include "xmpp_serverinfomanager.h" #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) #include #endif namespace XMPP { namespace Jingle { namespace S5B { const QString NS(QStringLiteral("urn:xmpp:jingle:transports:s5b:1")); static QString makeKey(const QString &sid, const Jid &j1, const Jid &j2) { auto data = QString::fromLatin1( QCryptographicHash::hash((sid + j1.full() + j2.full()).toUtf8(), QCryptographicHash::Sha1).toHex()); qDebug() << "Generated key from:" << sid << j1.full() << j2.full() << " = " << data; return data; } class Connection : public XMPP::Jingle::Connection { Q_OBJECT QList datagrams; SocksClient * client; Transport::Mode mode; public: Connection(SocksClient *client, Transport::Mode mode) : client(client), mode(mode) { connect(client, &SocksClient::readyRead, this, &Connection::readyRead); connect(client, &SocksClient::bytesWritten, this, &Connection::bytesWritten); connect(client, &SocksClient::aboutToClose, this, &Connection::aboutToClose); if (client->isOpen()) { setOpenMode(client->openMode()); } else { qWarning("Creating S5B Transport connection on closed SockClient connection %p", client); } } bool hasPendingDatagrams() const { return datagrams.size() > 0; } NetworkDatagram receiveDatagram(qint64 maxSize = -1) { Q_UNUSED(maxSize) // TODO or not? return datagrams.size() ? datagrams.takeFirst() : NetworkDatagram(); } qint64 bytesAvailable() const { if (client) return client->bytesAvailable(); else return 0; } qint64 bytesToWrite() const { return client->bytesToWrite(); } void close() { if (client) { client->disconnect(this); } XMPP::Jingle::Connection::close(); client->deleteLater(); client = nullptr; } protected: qint64 writeData(const char *data, qint64 maxSize) { if (mode == Transport::Tcp) return client->write(data, maxSize); return 0; } qint64 readData(char *data, qint64 maxSize) { if (client) return client->read(data, maxSize); else return 0; } private: friend class Transport; void enqueueIncomingUDP(const QByteArray &data) { datagrams.append(NetworkDatagram { data }); emit readyRead(); } }; class V6LinkLocalSocksConnector : public QObject { Q_OBJECT QMap clients; SocksClient * client = nullptr; public: using QObject::QObject; void connectToHost(const QHostAddress &proxyHost, int proxyPort, const QString &host, bool udpMode) { auto const interfaces = QNetworkInterface::allInterfaces(); for (const QNetworkInterface &ni : interfaces) { if (!(ni.flags() & (QNetworkInterface::IsUp | QNetworkInterface::IsRunning))) { continue; } if (ni.flags() & QNetworkInterface::IsLoopBack) { continue; } QList entries = ni.addressEntries(); for (const QNetworkAddressEntry &na : entries) { QHostAddress ha = na.ip(); if (ha.protocol() == QAbstractSocket::IPv6Protocol && #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) ha.isLinkLocal() #else XMPP::Ice176::isIPv6LinkLocalAddress(ha) #endif ) // && proxyHost.isInSubnet(ha, na.prefixLength()) { auto client = new SocksClient(this); clients.insert(ni.name(), client); break; } } } if (!clients.size()) { emit ready(); return; } QHostAddress ph(proxyHost); for (auto it = clients.begin(); it != clients.end(); ++it) { QString name = it.key(); SocksClient *client = it.value(); connect(client, &SocksClient::connected, this, [this, name, client]() { this->client = client; clients.remove(name); qDeleteAll(clients); emit ready(); }); connect( client, &SocksClient::error, this, [this, name, client](int error) { Q_UNUSED(error) clients.remove(name); delete client; if (clients.isEmpty()) { emit ready(); } }, Qt::QueuedConnection); ph.setScopeId(name); client->connectToHost(ph.toString(), proxyPort, host, 0, udpMode); } } SocksClient *takeClient() { auto c = client; if (c) { c->setParent(nullptr); client = nullptr; } return c; } signals: void ready(); }; class Candidate::Private : public QObject, public QSharedData { Q_OBJECT public: ~Private() { if (server && transport) { server->unregisterKey(transport->directAddr()); } delete socksClient; } QPointer transport; QString cid; QString host; Jid jid; quint16 port = 0; quint32 priority = 0; Candidate::Type type = Candidate::Direct; Candidate::State state = Candidate::New; QSharedPointer server; SocksClient * socksClient = nullptr; QString toString() const { QString extra; if (type == Tunnel || type == Assisted || type == Direct) { extra = QString("host=%1:%2").arg(host, QString::number(port)); } else if (type == Proxy) { extra = QString("proxy=%1 host=%2:%3").arg(jid.full(), host, QString::number(port)); } return QString("Cadidate(type=%1 cid=%2 state=%3 %4)") .arg(typeText(type), cid, Candidate::stateText(state), extra); } void connectToHost(const QString &key, State successState, QObject *callbackContext, std::function callback, bool isUdp) { QHostAddress ha(host); if (!ha.isNull() && ha.protocol() == QAbstractSocket::IPv6Protocol && ha.scopeId().isEmpty() && #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) ha.isLinkLocal() #else XMPP::Ice176::isIPv6LinkLocalAddress(ha) #endif ) { qDebug() << "connect to " << toString() << "with key=" << key << "using V6LinkLocalSocksConnector"; // we have link local address without scope. We have to enumerate all possible scopes. auto v6llConnector = new V6LinkLocalSocksConnector(this); connect(v6llConnector, &V6LinkLocalSocksConnector::ready, callbackContext, [this, v6llConnector, callback, successState]() { socksClient = v6llConnector->takeClient(); delete v6llConnector; if (state == Candidate::Discarded) { return; } if (socksClient) { state = successState; qDebug() << "connected: " << toString() << "socks client (ipv6)" << socksClient; callback(true); return; } state = Candidate::Discarded; qDebug() << "failed to connect: " << toString() << "no socks client (ipv6)"; callback(false); }); v6llConnector->connectToHost(ha, port, key, isUdp); } else { socksClient = new SocksClient; qDebug() << "connect to " << toString() << "with key=" << key << " and socks client" << socksClient; connect(socksClient, &SocksClient::connected, callbackContext, [this, callback, successState]() { if (state == Candidate::Discarded) { return; } state = successState; qDebug() << "connected: " << toString() << "socks client" << socksClient; callback(true); }); connect(socksClient, &SocksClient::error, callbackContext, [this, callback](int error) { Q_UNUSED(error) if (state == Candidate::Discarded) { return; } state = Candidate::Discarded; qDebug() << "failed to connect: " << toString() << "socks client" << socksClient; callback(false); }); // connect(&t, SIGNAL(timeout()), SLOT(trySendUDP())); socksClient->connectToHost(host, port, key, 0, isUdp); } } void setupIncomingSocksClient() { connect(socksClient, &SocksClient::error, [this](int error) { Q_UNUSED(error) state = Candidate::Discarded; }); } }; Candidate::Candidate() { } Candidate::Candidate(Transport *transport, const QDomElement &el) { bool ok; QString host(el.attribute(QStringLiteral("host"))); Jid jid(el.attribute(QStringLiteral("jid"))); auto portStr = el.attribute(QStringLiteral("port")); quint16 port = 0; if (!portStr.isEmpty()) { port = portStr.toUShort(&ok); if (!ok) { return; // make the whole candidate invalid } } auto priorityStr = el.attribute(QStringLiteral("priority")); if (priorityStr.isEmpty()) { return; } quint32 priority = priorityStr.toUInt(&ok); if (!ok) { return; // make the whole candidate invalid } QString cid = el.attribute(QStringLiteral("cid")); if (cid.isEmpty()) { return; } QString ct = el.attribute(QStringLiteral("type")); if (ct.isEmpty()) { ct = QStringLiteral("direct"); } static QMap types { { QStringLiteral("assisted"), Assisted }, { QStringLiteral("direct"), Direct }, { QStringLiteral("proxy"), Proxy }, { QStringLiteral("tunnel"), Tunnel } }; auto candidateType = types.value(ct); if (ct.isEmpty() || candidateType == None) { return; } if ((candidateType == Proxy && !jid.isValid()) || (candidateType != Proxy && (host.isEmpty() || !port))) { return; } auto d = new Private; d->transport = transport; d->cid = cid; d->host = host; d->jid = jid; d->port = port; d->priority = priority; d->type = candidateType; d->state = New; this->d = d; } Candidate::Candidate(const Candidate &other) : d(other.d) { } Candidate::Candidate(Transport *transport, const Jid &proxy, const QString &cid, quint16 localPreference) : d(new Private) { d->transport = transport; d->cid = cid; d->jid = proxy; d->priority = (ProxyPreference << 16) + localPreference; d->type = Proxy; d->state = Probing; // it's probing because it's a local side proxy and host and port are unknown } Candidate::Candidate(Transport *transport, const TcpPortServer::Ptr &server, const QString &cid, quint16 localPreference) : d(new Private) { Type type = None; switch (server->portType()) { case TcpPortServer::Direct: type = Candidate::Direct; break; case TcpPortServer::NatAssited: type = Candidate::Assisted; break; case TcpPortServer::Tunneled: type = Candidate::Tunnel; break; case TcpPortServer::NoType: break; } if (type == None) { d.reset(); return; } d->transport = transport; d->server = server.staticCast(); d->cid = cid; d->host = server->publishHost(); d->port = server->publishPort(); d->type = type; static const quint32 priorities[] = { 0, ProxyPreference, TunnelPreference, AssistedPreference, DirectPreference }; if (type >= Type(0) && type <= Direct) { d->priority = (priorities[type] << 16) + localPreference; } else { d->priority = 0; } d->state = New; } Candidate::~Candidate() { } Candidate::Type Candidate::type() const { return d->type; } const char *Candidate::typeText(Candidate::Type t) { switch (t) { case None: return "Unibitialized"; case Proxy: return "Proxy"; case Tunnel: return "Tunnel"; case Assisted: return "Assisted"; case Direct: return "Direct"; } return "Unknown"; } QString Candidate::cid() const { return d->cid; } Jid Candidate::jid() const { return d->jid; } QString Candidate::host() const { return d->host; } void Candidate::setHost(const QString &host) { d->host = host; } quint16 Candidate::port() const { return d->port; } void Candidate::setPort(quint16 port) { d->port = port; } quint16 Candidate::localPort() const { return d->server ? d->server->serverPort() : 0; } QHostAddress Candidate::localAddress() const { return d->server ? d->server->serverAddress() : QHostAddress(); } Candidate::State Candidate::state() const { return d->state; } void Candidate::setState(Candidate::State s) { // don't close sockets here since pending events may change state machine or remote side and closed socket // may break it d->state = s; } const char *Candidate::stateText(Candidate::State s) { switch (s) { case New: return "New"; case Probing: return "Probing"; case Pending: return "Pending"; case Unacked: return "Unacked"; case Accepted: return "Accepted"; case Activating: return "Activating"; case Active: return "Active"; case Discarded: return "Discarded"; } return nullptr; } quint32 Candidate::priority() const { return d->priority; } QDomElement Candidate::toXml(QDomDocument *doc) const { auto e = doc->createElement(QStringLiteral("candidate")); e.setAttribute(QStringLiteral("cid"), d->cid); if (d->type == Proxy) { e.setAttribute(QStringLiteral("jid"), d->jid.full()); } if (!d->host.isEmpty() && d->port) { e.setAttribute(QStringLiteral("host"), d->host); e.setAttribute(QStringLiteral("port"), d->port); } e.setAttribute(QStringLiteral("priority"), d->priority); static const char *types[] = { "proxy", "tunnel", "assisted" }; // same order as in enum if (d->type && d->type < Direct) { e.setAttribute(QStringLiteral("type"), QLatin1String(types[d->type - 1])); } return e; } QString Candidate::toString() const { if (d) { return d->toString(); } else return QString("Candidate(null)"); } // connect to the host and sets successState on success or discards the cadidate. // If the candidate was discarded before the connection is finished, then the passed callback won't be called. void Candidate::connectToHost(const QString &key, State successState, QObject *callbackContext, std::function callback, bool isUdp) { d->connectToHost(key, successState, callbackContext, callback, isUdp); } bool Candidate::incomingConnection(SocksClient *sc) { qDebug() << "incoming connection on" << d->cid << "candidate with socks client" << sc; if (d->socksClient) { return false; } d->socksClient = sc; d->setupIncomingSocksClient(); return true; } SocksClient *Candidate::takeSocksClient() { qDebug() << "taking socks client" << d->socksClient << "from " << d->cid << "candidate"; if (!d->socksClient) { return nullptr; } auto c = d->socksClient; d->socksClient = nullptr; d->disconnect(c); return c; } void Candidate::deleteSocksClient() { if (d->socksClient) { qDebug("deleting socks client %p", d->socksClient); d->socksClient->disconnect(); delete d->socksClient; d->socksClient = nullptr; } } TcpPortServer::Ptr Candidate::server() const { return d->server.staticCast(); } bool Candidate::isConnected() const { return d->socksClient != nullptr; } bool Candidate::operator==(const Candidate &other) const { return d.data() == other.d.data(); } // ------------------------------------------------------------------ // Transport::Private // ------------------------------------------------------------------ class Transport::Private { public: enum PendingActions { NewCandidate = 1, CandidateUsed = 2, CandidateError = 4, Activated = 8, ProxyError = 16 }; Transport * q = nullptr; bool p2pAllowed = true; bool offerSent = false; bool waitingAck = true; bool aborted = false; bool remoteReportedCandidateError = false; bool localReportedCandidateError = false; bool proxyDiscoveryInProgress = false; // if we have valid proxy requests quint16 pendingActions = 0; int proxiesInDiscoCount = 0; QMap localCandidates; // cid to candidate mapping QMap remoteCandidates; Candidate localUsedCandidate; // we received "candidate-used" for this candidate from localCandidates list Candidate remoteUsedCandidate; // we sent "candidate-used" for this candidate from remoteCandidates list QString dstaddr; // an address for xmpp proxy as it comes from remote. each side calculates it like sha1(sid // + local jid + remote jid) QString directAddr; // like dstaddr but for direct connection. Basically it's sha1(sid + initiator jid + // responder jid) QString sid; Transport::Mode mode = Transport::Tcp; QTimer probingTimer; QTimer negotiationFinishTimer; QElapsedTimer lastConnectionStart; size_t blockSize = 8192; TcpPortDiscoverer *disco = nullptr; QSharedPointer connection; // udp stuff bool udpInitialized; quint16 udpPort; QHostAddress udpAddress; inline Jid remoteJid() const { return q->_pad->session()->peer(); } QString generateCid() const { QString cid; do { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) cid = QString("%1").arg(QRandomGenerator::global()->generate() & 0xffff, 4, 16, QChar('0')); #else cid = QString("%1").arg(qrand() & 0xffff, 4, 16, QChar('0')); #endif } while (localCandidates.contains(cid) || remoteCandidates.contains(cid)); return cid; } bool isDup(const Candidate &c) const { for (auto const &rc : remoteCandidates) { if (c.host() == rc.host() && c.port() == rc.port()) { return true; } } return false; } // queries proxy's host/port and sends the candidate to remote void queryS5BProxy(const Jid &j, const QString &cid) { proxiesInDiscoCount++; auto query = new JT_S5B(q->_pad->session()->manager()->client()->rootTask()); connect(query, &JT_S5B::finished, q, [this, query, cid]() { if (!proxyDiscoveryInProgress) { return; } bool candidateUpdated = false; auto c = localCandidates.value(cid); if (c && c.state() == Candidate::Probing) { auto sh = query->proxyInfo(); if (query->success() && !sh.host().isEmpty() && sh.port()) { // it can be discarded by this moment (e.g. got success on a higher priority // candidate). so we have to check. c.setHost(sh.host()); c.setPort(quint16(sh.port())); c.setState(Candidate::New); candidateUpdated = true; pendingActions |= Private::NewCandidate; } else { c.setState(Candidate::Discarded); } } proxiesInDiscoCount--; if (!proxiesInDiscoCount) { proxyDiscoveryInProgress = false; } if (candidateUpdated) { emit q->updated(); } else if (!proxiesInDiscoCount) { // it's possible it was our last hope and probaby we have to send candidate-error now. checkAndFinishNegotiation(); } }); query->requestProxyInfo(j); query->go(true); } void discoS5BProxy() { auto m = static_cast(q->_pad->manager()); Jid proxy = m->userProxy(); if (proxy.isValid()) { Candidate c(q, proxy, generateCid()); if (!isDup(c)) { qDebug("new local candidate: %s", qPrintable(c.toString())); localCandidates.insert(c.cid(), c); queryS5BProxy(c.jid(), c.cid()); } } proxyDiscoveryInProgress = true; QList> featureOptions = { { "http://jabber.org/protocol/bytestreams" } }; q->_pad->session()->manager()->client()->serverInfoManager()->queryServiceInfo( QStringLiteral("proxy"), QStringLiteral("bytestreams"), featureOptions, QRegExp("proxy.*|socks.*|stream.*|s5b.*"), ServerInfoManager::SQ_CheckAllOnNoMatch, [this](const QList &items) { if (!proxyDiscoveryInProgress) { // check if new results are ever/still expected // seems like we have successful connection via higher priority channel. so nobody cares // about proxy return; } auto m = static_cast(q->_pad->manager()); Jid userProxy = m->userProxy(); bool userProxyFound = !userProxy.isValid(); for (const auto &i : items) { quint16 localPref = 0; if (!userProxyFound && i.jid() == userProxy) { localPref = 1; userProxyFound = true; continue; } Candidate c(q, i.jid(), generateCid(), localPref); localCandidates.insert(c.cid(), c); qDebug("new local candidate: %s", qPrintable(c.toString())); queryS5BProxy(i.jid(), c.cid()); } if (!userProxyFound) { Candidate c(q, userProxy, generateCid(), 1); localCandidates.insert(c.cid(), c); qDebug("new local candidate: %s", qPrintable(c.toString())); queryS5BProxy(userProxy, c.cid()); } else if (items.count() == 0) { // seems like we don't have any proxy proxyDiscoveryInProgress = false; checkAndFinishNegotiation(); } }); } void tryConnectToRemoteCandidate() { if (q->_state < State::Accepted) { return; // will come back later } quint64 maxProbingPrio = 0; quint64 maxNewPrio = 0; Candidate maxProbing; QList maxNew; // keeps highest (same) priority New candidates /* We have to find highest-priority already connecting candidate and highest-priority new candidate. If already-connecting is not found then start connecting to new if it's found. If both already-connecting and new are found then if new candidate has higher priority or the same priority then start connecting else ensure the new candidate starts connecting in 200ms after previous connection attempt (if it's in future then reschedule this call for future) In all the other cases just return and wait for events. */ qDebug("tryConnectToRemoteCandidate()"); for (auto &c : remoteCandidates) { if (c.state() == Candidate::New) { if (c.priority() > maxNewPrio) { maxNew = QList(); maxNew.append(c); maxNewPrio = c.priority(); } else if (c.priority() == maxNewPrio) { maxNew.append(c); } } if (c.state() == Candidate::Probing && c.priority() > maxProbingPrio) { maxProbing = c; maxProbingPrio = c.priority(); } } if (maxNew.isEmpty()) { qDebug(" tryConnectToRemoteCandidate() no maxNew candidates"); return; // nowhere to connect } // check if we have to hang on for a little if a higher priority candidate is Probing if (maxProbing) { if (maxNewPrio < maxProbing.priority()) { if (probingTimer.isActive()) { qDebug(" tryConnectToRemoteCandidate() timer is already active. let's wait"); return; // we will come back here soon } qint64 msToFuture = 200 - lastConnectionStart.elapsed(); if (msToFuture > 0) { // seems like we have to rescheduler for future probingTimer.start(int(msToFuture)); qDebug(" tryConnectToRemoteCandidate() too early. timer started. let's wait"); return; } } } probingTimer.start(200); // for the next candidate if any // now we have to connect to maxNew candidates for (auto &mnc : maxNew) { lastConnectionStart.start(); QString key = mnc.type() == Candidate::Proxy ? dstaddr : directAddr; mnc.setState(Candidate::Probing); mnc.connectToHost( key, Candidate::Pending, q, [this, mnc](bool success) { // candidate's status had to be changed by connectToHost, so we don't set it again if (success) { // let's reject candidates which are meaningless to try bool hasUnckeckedNew = false; for (auto &c : remoteCandidates) { if (c.state() == Candidate::New) { if (c.priority() <= mnc.priority()) { c.setState(Candidate::Discarded); } else { hasUnckeckedNew = true; } } } if (!hasUnckeckedNew) { pendingActions &= ~Private::NewCandidate; // just if we had it for example after // proxy discovery } setLocalProbingMinimalPreference(mnc.priority() >> 16); updateMinimalPriorityOnConnected(); } checkAndFinishNegotiation(); }, mode == Transport::Udp); } } /** * @brief limitTcpDiscoByMinimalPreference take upper part of candidate preference (type preference) * and drops lower priority pending local servers disco * @param preference */ void setLocalProbingMinimalPreference(quint32 preference) { if (proxyDiscoveryInProgress && preference > Candidate::ProxyPreference) { proxyDiscoveryInProgress = false; // doesn't make sense anymore } // and now local ports discoverer.. if (!disco) { return; } TcpPortServer::PortTypes types = TcpPortServer::NoType; if (p2pAllowed) { types |= TcpPortServer::Direct; if (preference >= Candidate::AssistedPreference) { types |= TcpPortServer::NatAssited; } if (preference >= Candidate::TunnelPreference) { types |= TcpPortServer::Tunneled; } } if (!disco->setTypeMask(types)) { delete disco; disco = nullptr; } } bool hasUnaknowledgedLocalCandidates() const { // now ensure all local were sent to remote and no hope left if (proxyDiscoveryInProgress || (disco && !disco->isDepleted())) { qDebug("still has: either s5b proxy or host candidates disco in progress"); return true; } // now local candidates for (const auto &c : localCandidates) { auto s = c.state(); if (s == Candidate::Probing || s == Candidate::New || s == Candidate::Unacked) { qDebug("still has: a local candidte cid=%s in %s state", qPrintable(c.cid()), qPrintable(c.stateText(s))); return true; } } return false; } Candidate preferredUsedCandidate() const { if (localUsedCandidate) { if (remoteUsedCandidate) { if (localUsedCandidate.priority() == remoteUsedCandidate.priority()) { if (q->_pad->session()->role() == Origin::Initiator) { qDebug("Both sides have condidate-used with same priority. Our(inititator) selection is " "preferred"); return remoteUsedCandidate; } qDebug("Both sides have condidate-used with same priority. Remote(initiator) selection is " "preferred"); return localUsedCandidate; } return localUsedCandidate.priority() > remoteUsedCandidate.priority() ? localUsedCandidate : remoteUsedCandidate; } return localUsedCandidate; } return remoteUsedCandidate; } // We come here when both sides reported either candidate-used or candidate-error void onBothSidesFinished() { // so remote seems to be finished too. // tell application about it and it has to change its state immediatelly auto c = preferredUsedCandidate(); bool bothErrors = localReportedCandidateError && remoteReportedCandidateError; if (!bothErrors && c) { if (c.state() != Candidate::Active) { if (c.type() == Candidate::Proxy) { // local proxy // If it's proxy, first it has to be activated if (c == localUsedCandidate) { if (c.state() == Candidate::Activating) { qDebug("The proxy cid=%s is still activating", qPrintable(c.cid())); return; } // it's our side who offered proxy. so we have to connect to it and activate auto key = makeKey(sid, q->_pad->session()->manager()->client()->jid(), q->_pad->session()->peer()); qDebug("Connect to proxy offered by local side (cid=%s) and activate it", qPrintable(c.cid())); c.setState(Candidate::Activating); c.connectToHost( key, Candidate::Activating, q, [this, c](bool success) { if (!success) { pendingActions |= Private::ProxyError; emit q->updated(); return; } auto query = new JT_S5B(q->_pad->session()->manager()->client()->rootTask()); connect(query, &JT_S5B::finished, q, [this, c, query]() { if (c.state() != Candidate::Activating) { qDebug("Proxy candidate cid=%s was changed state while we were " "trying " "to activate(activate) it. Ignore the result", qPrintable(c.cid())); return; } if (!query->success()) { pendingActions |= Private::ProxyError; emit q->updated(); return; } pendingActions |= Private::Activated; localUsedCandidate.setState(Candidate::Active); emit q->updated(); handleConnected(localUsedCandidate); }); query->requestActivation(localUsedCandidate.jid(), sid, q->_pad->session()->peer()); query->go(true); }, mode == Transport::Udp); } // else so it's remote proxy. let's just wait for from remote } else { c.setState(Candidate::Active); } } if (c.state() == Candidate::Active) { handleConnected(c); } else qDebug("checkAndFinishNegotiation not finished: preferred is not Active"); } else { // both sides reported candidate error q->setState(State::Finished); emit q->failed(); } } void sendCandidateUsedOrError() { qDebug("checkAndFinishNegotiation not finished: trying to send condidate-used/error if any"); // if we are here then neither candidate-used nor candidate-error was sent to remote, // but we can send it now. // first let's check if we can send candidate-used bool allRemoteDiscarded = true; bool hasConnectedRemoteCandidate = false; QString states; for (const auto &c : remoteCandidates) { auto s = c.state(); states += QString("\n%1").arg(c.toString()); if (s != Candidate::Discarded) { allRemoteDiscarded = false; } if (s == Candidate::Pending) { // connected but not yet sent hasConnectedRemoteCandidate = true; } } qDebug().noquote() << "Candidates dump:" << states; // if we have connection to remote candidate it's time to send it if (hasConnectedRemoteCandidate) { pendingActions |= Private::CandidateUsed; qDebug("sendCandidateUsedOrError: sending used"); emit q->updated(); return; } if (allRemoteDiscarded) { pendingActions |= Private::CandidateError; qDebug("sendCandidateUsedOrError: sending error"); emit q->updated(); return; } qDebug("checkAndFinishNegotiation not finished: there are more remote candidates to try"); // apparently we haven't connected anywhere but there are more remote candidates to try } void checkAndFinishNegotiation() { // Why we can't send candidate-used/error right when this happens: // so the situation: we discarded all remote candidates (failed to connect) // but we have some local candidates which are still in Probing state (upnp for example) // if we send candidate-error while we have unsent candidates this may trigger transport failure. // So for candidate-error two conditions have to be met 1) all remote failed 2) all local were sent no // more local candidates are expected to be discovered if (q->_state != State::Connecting) { // if not started or already finished qDebug("checkAndFinishNegotiation not finished: !connectionStarted || connection"); return; } // sort out already handled states or states which will bring us here a little later if (waitingAck || pendingActions || hasUnaknowledgedLocalCandidates()) { // waitingAck some query waits for ack and in the callback this func will be called again // pendingActions means we reported to app we have data to send but the app didn't take this data // yet, but as soon as it's taken it will switch to waitingAck. And with unacknowledged local // candidates we can't send used/error as well as report connected()/failure() until tried them all qDebug("checkAndFinishNegotiation not finished: waitingAck=%d || pendingActions=%x || " "hasUnaknowledgedLocalCandidates()=%d", int(waitingAck), int(pendingActions), int(hasUnaknowledgedLocalCandidates())); return; } bool localFinished = localReportedCandidateError || remoteUsedCandidate; bool remoteFinished = remoteReportedCandidateError || localUsedCandidate; if (!localFinished || !remoteFinished) { qDebug("checkAndFinishNegotiation: local=%s remote=%s", localFinished ? "finished" : "in-progress", remoteFinished ? "finished" : "in-progress"); if (!localFinished) sendCandidateUsedOrError(); return; } onBothSidesFinished(); } // take used-candidate with highest priority and discard all with lower. also update used candidates // themselves void updateMinimalPriorityOnConnected() { quint32 prio = 0; if (localUsedCandidate && localUsedCandidate.state() != Candidate::Discarded) { prio = localUsedCandidate.priority(); } // find highest priority withing connected remote candidates for (const auto &c : remoteCandidates) { if (c.state() != Candidate::Discarded && c.state() >= Candidate::Pending && c.priority() > prio) { prio = c.priority(); } } for (auto &c : localCandidates) { if (c.priority() < prio && c.state() != Candidate::Discarded) { c.setState(Candidate::Discarded); } } for (auto &c : remoteCandidates) { if (c.priority() < prio && c.state() != Candidate::Discarded) { c.setState(Candidate::Discarded); } } prio >>= 16; setLocalProbingMinimalPreference(prio); // if we discarded "used" candidates then reset them to invalid if (localUsedCandidate && localUsedCandidate.state() == Candidate::Discarded) { localUsedCandidate = Candidate(); } if (remoteUsedCandidate && remoteUsedCandidate.state() == Candidate::Discarded) { remoteUsedCandidate = Candidate(); } if (localUsedCandidate && remoteUsedCandidate) { if (q->_pad->session()->role() == Origin::Initiator) { // i'm initiator. see 2.4.4 localUsedCandidate.setState(Candidate::Discarded); localUsedCandidate = Candidate(); remoteReportedCandidateError = true; // as a sign of completeness even if not true } else { remoteUsedCandidate.setState(Candidate::Discarded); remoteUsedCandidate = Candidate(); localReportedCandidateError = true; // as a sign of completeness even if not true } } // now check and reset NewCandidate pending action bool haveNewCandidates = false; for (auto &c : remoteCandidates) { if (c.state() == Candidate::New) { haveNewCandidates = true; break; } } if (!haveNewCandidates) { pendingActions &= ~NewCandidate; } negotiationFinishTimer.start(); } void onLocalServerDiscovered() { bool hasNewCandidates = false; for (auto serv : disco->takeServers()) { auto s5bserv = serv.staticCast(); s5bserv->registerKey(directAddr); Candidate c(q, serv, generateCid()); if (c.isValid() && !isDup(c) && c.priority()) { QObject::connect(s5bserv.data(), &S5BServer::incomingConnection, q, [this, c](SocksClient *sc, const QString &key) mutable { if (!connection && key == directAddr && (c.state() == Candidate::Pending || c.state() == Candidate::Unacked)) { c.incomingConnection(sc); c.server().data()->disconnect(q); // drop this connection. if (mode == Transport::Udp) sc->grantUDPAssociate("", 0); else sc->grantConnect(); return; } qDebug("Reject incoming socks5 connection with key %s (%s)", qPrintable(key), connection ? "already has connection" : "key mismatch"); sc->requestDeny(); sc->deleteLater(); }); QObject::connect( s5bserv.data(), &S5BServer::incomingUdp, q, [this, c](bool isInit, const QHostAddress &addr, int sourcePort, const QString &key, const QByteArray &data) { if (mode != Transport::Mode::Udp || !connection) { return false; } if (isInit) { // TODO probably we could create a Connection here and put all the params inside if (udpInitialized) return false; // only init once // lock on to this sender udpAddress = addr; udpPort = quint16(sourcePort); udpInitialized = true; // reply that initialization was successful q->_pad->session()->manager()->client()->s5bManager()->jtPush()->sendUDPSuccess( q->_pad->session()->peer(), key); // TODO fix ->->-> return true; } // not initialized yet? something went wrong if (!udpInitialized) return false; // must come from same source as when initialized if (addr != udpAddress || sourcePort != udpPort) return false; connection->enqueueIncomingUDP(data); // man_udpReady return true; }); localCandidates.insert(c.cid(), c); qDebug("new local candidate: %s", qPrintable(c.toString())); pendingActions |= NewCandidate; hasNewCandidates = true; } } if (hasNewCandidates) { emit q->updated(); } } void handleConnected(Candidate &connCand) { connection.reset(new Connection(connCand.takeSocksClient(), mode)); probingTimer.stop(); negotiationFinishTimer.stop(); proxyDiscoveryInProgress = false; for (auto &rc : remoteCandidates) { if (rc != connCand && rc.state() == Candidate::Probing) { rc.deleteSocksClient(); } } QTimer::singleShot(0, q, [this]() { localCandidates.clear(); remoteCandidates.clear(); q->setState(State::Active); emit q->connected(); }); } void handleNegotiationTimeout() { // probingTimer.stop(); proxyDiscoveryInProgress = false; for (auto &rc : remoteCandidates) { if (rc.state() <= Candidate::Probing) { rc.setState(Candidate::Discarded); } } for (auto &rc : localCandidates) { if (rc.state() <= Candidate::Probing) { rc.setState(Candidate::Discarded); } } checkAndFinishNegotiation(); } bool handleIncomingCandidate(const QDomElement &transportEl) { QString candidateTag(QStringLiteral("candidate")); bool handled = false; bool reallyAdded = false; for (QDomElement ce = transportEl.firstChildElement(candidateTag); !ce.isNull(); ce = ce.nextSiblingElement(candidateTag)) { Candidate c(q, ce); if (!c) { throw Stanza::Error(Stanza::Error::Cancel, Stanza::Error::BadRequest); } if (!p2pAllowed && c.type() != Candidate::Proxy) { qDebug("new remote candidate discarded with forbidden p2p: %s", qPrintable(c)); } else { qDebug("new remote candidate: %s", qPrintable(c.toString())); remoteCandidates.insert(c.cid(), c); // TODO check for collisions! reallyAdded = true; } handled = true; } if (reallyAdded) { pendingActions &= ~CandidateError; localReportedCandidateError = false; QTimer::singleShot(0, q, [this]() { tryConnectToRemoteCandidate(); }); } return handled; } bool handleIncomingCandidateUsed(const QDomElement &transportEl) { QDomElement el = transportEl.firstChildElement(QStringLiteral("candidate-used")); if (!el.isNull()) { auto cid = QStringLiteral("cid"); auto cUsed = localCandidates.value(el.attribute(cid)); if (!cUsed) { throw Stanza::Error(Stanza::Error::Cancel, Stanza::Error::ItemNotFound, QString("failed to find incoming candidate-used candidate %1").arg(cid)); } if (cUsed.state() == Candidate::Pending) { if (cUsed.type() != Candidate::Proxy && !cUsed.isConnected()) { throw Stanza::Error( Stanza::Error::Cancel, Stanza::Error::NotAcceptable, QString("incoming candidate-used refers a candidate w/o active socks connection: %1") .arg(QString(cUsed))); } cUsed.setState(Candidate::Accepted); localUsedCandidate = cUsed; updateMinimalPriorityOnConnected(); QTimer::singleShot(0, q, [this]() { checkAndFinishNegotiation(); }); } else { // we already rejected the candidate and either remote side already knows about it or will soon // it's possible for example if we were able to connect to higher priority candidate, so // we have o pretend like remote couldn't select anything better but finished already, in other // words like if it sent candidate-error. localUsedCandidate = Candidate(); remoteReportedCandidateError = true; } return true; } return false; } bool handleIncomingCandidateError(const QDomElement &transportEl) { auto el = transportEl.firstChildElement(QStringLiteral("candidate-error")); if (!el.isNull()) { remoteReportedCandidateError = true; for (auto &c : localCandidates) { if (c.state() == Candidate::Pending) { c.setState(Candidate::Discarded); } } qDebug("recv candidate-error: all local pending candidates were discarded"); QTimer::singleShot(0, q, [this]() { checkAndFinishNegotiation(); }); return true; } return false; } bool handleIncomingActivated(const QDomElement &transportEl) { auto el = transportEl.firstChildElement(QStringLiteral("activated")); if (!el.isNull()) { QString cid = el.attribute(QStringLiteral("cid")); if (cid.isEmpty()) { throw Stanza::Error(Stanza::Error::Cancel, Stanza::Error::ItemNotFound, "failed to find incoming activated candidate"); } auto c = remoteUsedCandidate; if (!(c.cid() == cid && c.type() == Candidate::Proxy && c.state() == Candidate::Accepted)) { qDebug("Received on a candidate in an inappropriate state. Ignored."); return true; } c.setState(Candidate::Active); handleConnected(c); return true; } return false; } bool handleIncomingProxyError(const QDomElement &transportEl) { auto el = transportEl.firstChildElement(QStringLiteral("proxy-error")); if (!el.isNull()) { auto c = localCandidates.value(el.attribute(QStringLiteral("cid"))); if (!c) { throw Stanza::Error(Stanza::Error::Cancel, Stanza::Error::ItemNotFound, "failed to find incoming proxy-error candidate"); } if (c != localUsedCandidate || c.state() != Candidate::Accepted) { qDebug("Received on a candidate in an inappropriate state. Ignored."); return true; } // if we got proxy-error then the transport has to be considered failed according to spec // so never send proxy-error while we have unaknowledged local non-proxy candidates, // but we have to follow the standard. // Discard everything for (auto &c : localCandidates) { c.setState(Candidate::Discarded); } for (auto &c : remoteCandidates) { c.setState(Candidate::Discarded); } proxyDiscoveryInProgress = false; delete disco; QTimer::singleShot(0, q, [this]() { q->setState(State::Finished); emit q->failed(); }); return true; } return false; } }; Transport::Transport(const TransportManagerPad::Ptr &pad, Origin creator) : XMPP::Jingle::Transport(pad, creator), d(new Private) { d->q = this; d->probingTimer.setSingleShot(true); d->negotiationFinishTimer.setSingleShot(true); d->negotiationFinishTimer.setInterval(5000); // TODO select the value smart way connect(&d->probingTimer, &QTimer::timeout, [this]() { d->tryConnectToRemoteCandidate(); }); connect(&d->negotiationFinishTimer, &QTimer::timeout, this, [this]() { d->handleNegotiationTimeout(); }); connect(_pad->manager(), &TransportManager::abortAllRequested, this, [this]() { d->aborted = true; _state = State::Finished; emit failed(); }); } Transport::~Transport() { if (d) { // TODO unregister sid too static_cast(_pad.staticCast()->manager())->removeKeyMapping(d->directAddr); for (auto &c : d->remoteCandidates) { c.deleteSocksClient(); } for (auto &c : d->remoteCandidates) { auto srv = c.server(); if (srv) { srv.staticCast()->unregisterKey(d->directAddr); } } } } void Transport::prepare() { qDebug("Prepare local offer"); setState(State::ApprovedToSend); auto m = static_cast(_pad.staticCast()->manager()); if (_creator == _pad->session()->role()) { // I'm creator d->sid = _pad.staticCast()->generateSid(); } _pad.staticCast()->registerSid(d->sid); d->directAddr = makeKey(d->sid, _pad.staticCast()->session()->initiator(), _pad.staticCast()->session()->responder()); m->addKeyMapping(d->directAddr, this); auto scope = _pad.staticCast()->discoScope(); d->disco = scope->disco(); // FIXME store and handle signal. delete when not needed connect(d->disco, &TcpPortDiscoverer::portAvailable, this, [this]() { d->onLocalServerDiscovered(); }); d->setLocalProbingMinimalPreference(0); // allow all on start d->discoS5BProxy(); emit updated(); } // we got content acceptance from any side and now can connect void Transport::start() { qDebug("Starting connecting"); setState(State::Connecting); d->tryConnectToRemoteCandidate(); // if there is no higher priority candidates than ours but they are already connected then d->checkAndFinishNegotiation(); } bool Transport::update(const QDomElement &transportEl) { // we can just on type of elements in transport-info // so return as soon as any type handled. Though it leaves a room for remote to send invalid transport-info auto bs = transportEl.attribute(QString::fromLatin1("block-size")); if (!bs.isEmpty()) { size_t bsn = bs.toULongLong(); if (bsn && bsn <= d->blockSize) { d->blockSize = bsn; } } if (_state == State::Created && isRemote() && d->sid.isEmpty()) { d->sid = transportEl.attribute(QStringLiteral("sid")); } auto dstaddr = transportEl.attribute(QStringLiteral("dstaddr")); if (!dstaddr.isEmpty()) { d->dstaddr = dstaddr; } try { if (d->handleIncomingCandidate(transportEl) || d->handleIncomingCandidateUsed(transportEl) || d->handleIncomingCandidateError(transportEl) || d->handleIncomingActivated(transportEl) || d->handleIncomingProxyError(transportEl)) { if (_state == State::Created && _creator != _pad->session()->role()) { // initial incoming transport setState(State::Pending); } if (_state == State::Pending && _creator == _pad->session()->role()) { // initial acceptance by remote of the local transport setState(State::Accepted); } return true; } } catch (XMPP::Stanza::Error &e) { qWarning("Transport updated failed: %s", qPrintable(e.toString())); _lastError = e; return false; } // Seems like we got an empty transport. It's still valid though. QTimer::singleShot(0, this, [this]() { d->checkAndFinishNegotiation(); }); return true; } bool Transport::hasUpdates() const { return isValid() && d->pendingActions; } OutgoingTransportInfoUpdate Transport::takeOutgoingUpdate() { qDebug("taking outgoing update"); OutgoingTransportInfoUpdate upd; if (!isValid()) { return upd; } auto makeUpdate = [&](QDomElement tel, bool expectedSuccess = false, std::function cb = std::function()) { d->waitingAck = true; return OutgoingTransportInfoUpdate { tel, [this, cb, expectedSuccess, trptr = QPointer(d->q)](Task *task) { if (!trptr) return; d->waitingAck = false; if (expectedSuccess && !task->success()) { _state = State::Finished; d->localCandidates.clear(); d->remoteCandidates.clear(); emit failed(); } else if (cb) cb(task); } }; }; auto doc = _pad.staticCast()->session()->manager()->client()->doc(); QDomElement tel = doc->createElementNS(NS, "transport"); tel.setAttribute(QStringLiteral("sid"), d->sid); // check where we make initial offer bool noPending = (d->localCandidates.isEmpty() && !d->proxyDiscoveryInProgress && !(d->disco && d->disco->inProgressPortTypes())); bool initial = _state == State::ApprovedToSend && !d->offerSent && ((!d->pendingActions && noPending) || d->pendingActions & Private::NewCandidate); if (initial) { if (_creator == _pad->session()->role() && d->mode != Tcp) { tel.setAttribute(QStringLiteral("mode"), "udp"); } tel.setAttribute(QString::fromLatin1("block-size"), qulonglong(d->blockSize)); d->offerSent = true; } if (d->pendingActions & Private::NewCandidate) { d->pendingActions &= ~Private::NewCandidate; bool useProxy = false; QList candidatesToSend; for (auto &c : d->localCandidates) { if (c.state() != Candidate::New) { continue; } if (c.type() == Candidate::Proxy) { useProxy = true; } qDebug("sending local candidate: cid=%s", qPrintable(c.cid())); tel.appendChild(c.toXml(doc)); candidatesToSend.append(c); c.setState(Candidate::Unacked); } if (useProxy) { QString dstaddr = makeKey(d->sid, _pad.staticCast()->session()->manager()->client()->jid(), _pad.staticCast()->session()->peer()); tel.setAttribute(QStringLiteral("dstaddr"), dstaddr); } if (!candidatesToSend.isEmpty()) { upd = makeUpdate(tel, false, [this, candidatesToSend, initial](Task *jt) mutable { if (jt->success()) { if (initial) { _state = _creator == _pad->session()->role() ? State::Pending : State::Accepted; } for (auto &c : candidatesToSend) { if (c.state() == Candidate::Unacked) { c.setState(Candidate::Pending); qDebug("ack: remote side accepted local candidate: cid=%s", qPrintable(c.cid())); } } } else { for (auto &c : candidatesToSend) { if (c.state() == Candidate::Unacked) { c.setState(Candidate::Discarded); qDebug("ack: remote side discarded local candidate: cid=%s", qPrintable(c.cid())); } } d->updateMinimalPriorityOnConnected(); } d->checkAndFinishNegotiation(); }); } else { qWarning("Got NewCandidate pending action but no candidate to send"); } } else if (d->pendingActions & Private::CandidateUsed) { d->pendingActions &= ~Private::CandidateUsed; // we should have the only remote candidate in Pending state. // all other has to be discarded by priority check for (auto &c : d->remoteCandidates) { if (c.state() != Candidate::Pending) { continue; } qDebug("sending candidate-used: cid=%s", qPrintable(c.cid())); auto el = tel.appendChild(doc->createElement(QStringLiteral("candidate-used"))).toElement(); el.setAttribute(QStringLiteral("cid"), c.cid()); c.setState(Candidate::Unacked); upd = makeUpdate(tel, true, [this, c](Task *) mutable { if (c.state() == Candidate::Unacked) { c.setState(Candidate::Accepted); qDebug("ack: sending candidate-used: cid=%s", qPrintable(c.cid())); d->remoteUsedCandidate = c; } d->checkAndFinishNegotiation(); }); break; } if (std::get<0>(upd).isNull()) { qWarning("Got CandidateUsed pending action but no pending candidates"); } } else if (d->pendingActions & Private::CandidateError) { d->pendingActions &= ~Private::CandidateError; qDebug("sending candidate-error"); // we are here because all remote are already in Discardd state tel.appendChild(doc->createElement(QStringLiteral("candidate-error"))); upd = makeUpdate(tel, true, [this](Task *) mutable { d->localReportedCandidateError = true; d->checkAndFinishNegotiation(); }); } else if (d->pendingActions & Private::Activated) { d->pendingActions &= ~Private::Activated; if (d->localUsedCandidate) { auto cand = d->localUsedCandidate; qDebug("sending activated: cid=%s", qPrintable(cand.cid())); auto el = tel.appendChild(doc->createElement(QStringLiteral("activated"))).toElement(); el.setAttribute(QStringLiteral("cid"), cand.cid()); upd = makeUpdate(tel, true); } } else if (d->pendingActions & Private::ProxyError) { // we send proxy error only for local proxy d->pendingActions &= ~Private::ProxyError; if (d->localUsedCandidate) { auto cand = d->localUsedCandidate; tel.appendChild(doc->createElement(QStringLiteral("proxy-error"))); qDebug("sending proxy error: cid=%s", qPrintable(cand.cid())); upd = makeUpdate(tel, true, [this, cand](Task *task) mutable { qDebug("ack: sending proxy error: cid=%s", qPrintable(cand.cid())); if ((cand.state() != Candidate::Accepted || d->localUsedCandidate != cand) && task->success()) { return; // seems like state was changed while we were waiting for an ack } cand.setState(Candidate::Discarded); d->localUsedCandidate = Candidate(); _state = State::Finished; emit failed(); }); } else { qWarning("Got ProxyError pending action but no local used candidate is not set"); } } else { qDebug("sending empty transport-info"); upd = makeUpdate(tel, false, [this, initial](Task *jt) mutable { if (!jt->success()) { if (initial) { _state = State::Finished; emit failed(); } else qWarning("Ignored failed IQ response"); } if (initial) { _state = _creator == _pad->session()->role() ? State::Pending : State::Accepted; } }); } return upd; // TODO } bool Transport::isValid() const { return d != nullptr; } TransportFeatures Transport::features() const { return TransportFeatures(TransportFeature::HardToConnect) | TransportFeature::Reliable | TransportFeature::Fast; } QString Transport::sid() const { return d->sid; } QString Transport::directAddr() const { return d->directAddr; } Connection::Ptr Transport::addChannel() const { return d->connection.staticCast(); } //---------------------------------------------------------------- // Manager //---------------------------------------------------------------- class Manager::Private { public: XMPP::Jingle::Manager *jingleManager = nullptr; // FIMME it's reuiqred to split transports by direction otherwise we gonna hit conflicts. // jid,transport-sid -> transport mapping QSet> sids; QHash key2transport; Jid proxy; }; Manager::Manager(QObject *parent) : TransportManager(parent), d(new Private) { } Manager::~Manager() { if (d->jingleManager) d->jingleManager->unregisterTransport(NS); } TransportFeatures Manager::features() const { return TransportFeatures(TransportFeature::Reliable) | TransportFeature::Fast; } void Manager::setJingleManager(XMPP::Jingle::Manager *jm) { d->jingleManager = jm; if (!jm) return; // ensure S5BManager is initialized QTimer::singleShot(0, this, [this]() { if (!d->jingleManager) // unregistered that early? return; auto jt = d->jingleManager->client()->s5bManager()->jtPush(); connect(jt, &JT_PushS5B::incomingUDPSuccess, this, [this](const Jid &from, const QString &dstaddr) { Q_UNUSED(from) auto t = d->key2transport.value(dstaddr); if (t) { // TODO return t->incomingUDPSuccess(from); } }); }); } QSharedPointer Manager::newTransport(const TransportManagerPad::Ptr &pad, Origin creator) { return QSharedPointer::create(pad, creator).staticCast(); } TransportManagerPad *Manager::pad(Session *session) { return new Pad(this, session); } void Manager::closeAll() { emit abortAllRequested(); } void Manager::addKeyMapping(const QString &key, Transport *transport) { d->key2transport.insert(key, transport); } void Manager::removeKeyMapping(const QString &key) { d->key2transport.remove(key); } QString Manager::generateSid(const Jid &remote) { auto servers = d->jingleManager->client()->tcpPortReserver()->scope(QString::fromLatin1("s5b"))->allServers(); QString sid; QPair key; QString key1; QString key2; auto servChecker = [&](const TcpPortServer::Ptr &s) { return s.staticCast()->hasKey(key1) || s.staticCast()->hasKey(key2); }; do { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) sid = QString("s5b_%1").arg(QRandomGenerator::global()->bounded(0x10000), 4, 16, QChar('0')); #else sid = QString("s5b_%1").arg(qrand() & 0xffff, 4, 16, QChar('0')); #endif key = qMakePair(remote, sid); key1 = makeKey(sid, remote, d->jingleManager->client()->jid()); key2 = makeKey(sid, d->jingleManager->client()->jid(), remote); } while (d->sids.contains(key) || std::any_of(servers.begin(), servers.end(), servChecker)); return sid; } void Manager::registerSid(const Jid &remote, const QString &sid) { d->sids.insert(qMakePair(remote, sid)); } Jid Manager::userProxy() const { return d->proxy; } void Manager::setUserProxy(const Jid &jid) { d->proxy = jid; } //---------------------------------------------------------------- // Pad //---------------------------------------------------------------- Pad::Pad(Manager *manager, Session *session) : _manager(manager), _session(session) { auto reserver = _session->manager()->client()->tcpPortReserver(); _discoScope = reserver->scope(QString::fromLatin1("s5b")); } QString Pad::ns() const { return NS; } Session *Pad::session() const { return _session; } TransportManager *Pad::manager() const { return _manager; } QString Pad::generateSid() const { return _manager->generateSid(_session->peer()); } void Pad::registerSid(const QString &sid) { return _manager->registerSid(_session->peer(), sid); } } // namespace S5B } // namespace Jingle } // namespace XMPP #include "jingle-s5b.moc" psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/jingle-s5b.h000066400000000000000000000175641370065651000240100ustar00rootroot00000000000000/* * jignle-s5b.h - Jingle SOCKS5 transport * Copyright (C) 2019 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef JINGLE_S5B_H #define JINGLE_S5B_H #include "jingle-transport.h" #include "tcpportreserver.h" class QHostAddress; class SocksClient; namespace XMPP { class Client; namespace Jingle { namespace S5B { extern const QString NS; class Transport; class Candidate { public: enum Type { None, // non standard, just a default Proxy, Tunnel, Assisted, Direct }; enum { ProxyPreference = 10, TunnelPreference = 110, AssistedPreference = 120, DirectPreference = 126 }; /** * Local candidates states: * Probing - potential candidate but no ip:port yet. upnp for example * New - candidate is ready to be sent to remote * Unacked - candidate is sent to remote but no iq ack yet * Pending - canidate sent to remote. we have iq ack but no "used" or "error" * Accepted - we got "candidate-used" for this candidate * Activating - only for proxy: we activate the proxy * Active - use this candidate for actual data transfer * Discarded - we got "candidate-error" so all pending were marked Discarded * * Remote candidates states: * New - the candidate waits its turn to start connection probing * Probing - connection probing * Pending - connection was successful, but we didn't send candidate-used to remote * Unacked - connection was successful and we sent candidate-used to remote but no iq ack yet * Accepted - we sent candidate-used and got iq ack * Activating - [not used] * Active - use this candidate for actual data transfer * Discarded - failed to connect to all remote candidates */ enum State { New, Probing, Pending, Unacked, Accepted, Activating, Active, Discarded, }; Candidate(); Candidate(Transport *transport, const QDomElement &el); Candidate(const Candidate &other); Candidate(Transport *transport, const Jid &proxy, const QString &cid, quint16 localPreference = 0); Candidate(Transport *transport, const TcpPortServer::Ptr &server, const QString &cid, quint16 localPreference = 0); ~Candidate(); Candidate & operator=(const Candidate &other) = default; inline bool isValid() const { return d != nullptr; } inline operator bool() const { return isValid(); } Type type() const; static const char *typeText(Type t); QString cid() const; Jid jid() const; QString host() const; void setHost(const QString &host); quint16 port() const; void setPort(quint16 port); quint16 localPort() const; QHostAddress localAddress() const; State state() const; void setState(State s); static const char *stateText(State s); quint32 priority() const; QDomElement toXml(QDomDocument *doc) const; QString toString() const; inline operator QString() const { return toString(); } void connectToHost(const QString &key, State successState, QObject *callbackContext, std::function callback, bool isUdp = false); bool incomingConnection(SocksClient *sc); SocksClient * takeSocksClient(); void deleteSocksClient(); TcpPortServer::Ptr server() const; bool isConnected() const; bool operator==(const Candidate &other) const; inline bool operator!=(const Candidate &other) const { return !(*this == other); } private: class Private; friend class Transport; QExplicitlySharedDataPointer d; }; class Manager; class Transport : public XMPP::Jingle::Transport { Q_OBJECT public: enum Mode { Tcp, Udp }; Transport(const TransportManagerPad::Ptr &pad, Origin creator); ~Transport() override; void prepare() override; void start() override; bool update(const QDomElement &transportEl) override; bool hasUpdates() const override; OutgoingTransportInfoUpdate takeOutgoingUpdate() override; bool isValid() const override; TransportFeatures features() const override; QString sid() const; QString directAddr() const; Connection::Ptr addChannel() const override; private: friend class Manager; class Private; QScopedPointer d; }; class Pad : public TransportManagerPad { Q_OBJECT // TODO public: typedef QSharedPointer Ptr; Pad(Manager *manager, Session *session); QString ns() const override; Session * session() const override; TransportManager *manager() const override; QString generateSid() const; void registerSid(const QString &sid); inline TcpPortScope *discoScope() const { return _discoScope; } private: Manager * _manager; Session * _session; TcpPortScope *_discoScope; }; class Manager : public TransportManager { Q_OBJECT public: Manager(QObject *parent = nullptr); ~Manager() override; XMPP::Jingle::TransportFeatures features() const override; void setJingleManager(XMPP::Jingle::Manager *jm) override; QSharedPointer newTransport(const TransportManagerPad::Ptr &pad, Origin creator) override; TransportManagerPad * pad(Session *session) override; void closeAll() override; QString generateSid(const Jid &remote); void registerSid(const Jid &remote, const QString &sid); /** * @brief userProxy returns custom (set by user) SOCKS proxy JID * @return */ Jid userProxy() const; void setUserProxy(const Jid &jid); /** * @brief addKeyMapping sets mapping between key/socks hostname used for direct connection and transport. * The key is sha1(sid, initiator full jid, responder full jid) * @param key * @param transport */ void addKeyMapping(const QString &key, Transport *transport); void removeKeyMapping(const QString &key); private: class Private; QScopedPointer d; }; } // namespace S5B } // namespace Jingle } // namespace XMPP #endif // JINGLE_S5B_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/jingle-session.cpp000066400000000000000000001343041370065651000253250ustar00rootroot00000000000000/* * jignle-session.cpp - Jingle Session * Copyright (C) 2019 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "jingle-session.h" #include "jingle-application.h" #include "xmpp/jid/jid.h" #include "xmpp_client.h" #include "xmpp_task.h" #include "xmpp_xmlcommon.h" #include #include namespace XMPP { namespace Jingle { //---------------------------------------------------------------------------- // JT - Jingle Task //---------------------------------------------------------------------------- class JT : public Task { Q_OBJECT QDomElement iq_; Jid to_; public: JT(Task *parent) : Task(parent) { } ~JT() { } void request(const Jid &to, const QDomElement &jingleEl) { to_ = to; iq_ = createIQ(doc(), "set", to.full(), id()); iq_.appendChild(jingleEl); } void onGo() { send(iq_); } bool take(const QDomElement &x) { if (!iqVerify(x, to_, id())) return false; if (x.attribute("type") == "error") { setError(x); } else { setSuccess(); } return true; } }; //---------------------------------------------------------------------------- // Session //---------------------------------------------------------------------------- class Session::Private { public: Session *q; Manager *manager; QTimer stepTimer; State state = State::Created; // state of session on our side. if it's incoming we start from Created anyaway // but Pending state is skipped Origin role = Origin::Initiator; // my role in the session XMPP::Stanza::Error lastError; Reason terminateReason; QMap> applicationPads; QMap> transportPads; QMap contentList; QSet signalingContent; QList initialIncomingUnacceptedContent; // not yet acccepted applications from initial incoming request // session level updates. session-info for example or some rejected apps QHash outgoingUpdates; QString sid; Jid origFrom; // "from" attr of IQ. Jid otherParty; // either "from" or initiator/responder. it's where to send all requests. Jid localParty; // that one will be set as initiator/responder if provided bool waitingAck = false; void setSessionFinished() { state = State::Finished; emit q->terminated(); signalingContent.clear(); for (auto &c : contentList) { if (c->state() != State::Finished) { c->setState(State::Finished); } } auto vals = contentList.values(); contentList.clear(); while (vals.size()) { vals.takeLast()->deleteLater(); } q->deleteLater(); } void sendJingle(Action action, QList update, std::function callback = std::function()) { QDomDocument &doc = *manager->client()->doc(); Jingle jingle(action, sid); if (action == Action::SessionInitiate) { jingle.setInitiator(manager->client()->jid()); } if (action == Action::SessionAccept) { jingle.setResponder(manager->client()->jid()); } auto xml = jingle.toXml(&doc); for (const QDomElement &e : update) { xml.appendChild(e); } auto jt = new JT(manager->client()->rootTask()); jt->request(otherParty, xml); QObject::connect(jt, &JT::finished, q, [jt, jingle, callback, this]() { waitingAck = false; if (callback) { callback(jt); } if (!jt->success()) { lastError = jt->error(); } planStep(); }); waitingAck = true; jt->go(true); } void planStep() { if (waitingAck) { return; } lastError = Stanza::Error(0, 0); if (!stepTimer.isActive()) { stepTimer.start(); } } void doStep() { if (waitingAck) { // we will return here when ack is received. Session::Unacked is possible also only with // waitingAck return; } if (terminateReason.condition() && state != State::Finished) { if (state != State::Created || role == Origin::Responder) { sendJingle(Action::SessionTerminate, QList() << terminateReason.toXml(manager->client()->doc())); } setSessionFinished(); return; } if (state == State::Created || state == State::Finished) { return; // we will start doing something when initiate() is called } if (outgoingUpdates.size()) { auto it = outgoingUpdates.begin(); auto action = it.key(); auto updates = it.value(); auto elements = std::get<0>(updates); auto cb = std::get<1>(updates); outgoingUpdates.erase(it); sendJingle(action, elements, cb); return; } typedef std::tuple, OutgoingUpdateCB> AckHndl; // will be used from callback on iq ack if (state == State::ApprovedToSend) { // we are going to send session-initiate/accept (already accepted // by the user but not sent yet) /* * For session-initiate everything is prety much straightforward, just any content with * Action::ContentAdd update type has to be added. But with session-accept things are more complicated * 1. Local client could add its content. So we have to check content origin too. * 2. Remote client could add more content before local session-accept. Then we have two options * a) send content-accept and skip this content in session-accept later * b) don't send content-accept and accept everything with session-accept * We prefer option (b) in our implementation. */ if (role == Origin::Responder) { for (const auto &c : initialIncomingUnacceptedContent) { auto out = c->evaluateOutgoingUpdate(); if (out.action == Action::ContentReject) { lastError = XMPP::Stanza::Error(XMPP::Stanza::Error::Cancel, XMPP::Stanza::Error::BadRequest); setSessionFinished(); return; } if (out.action != Action::ContentAccept) { return; // keep waiting. } } } else { for (const auto &c : contentList) { auto out = c->evaluateOutgoingUpdate(); if (out.action == Action::ContentRemove) { lastError = XMPP::Stanza::Error(XMPP::Stanza::Error::Cancel, XMPP::Stanza::Error::BadRequest); setSessionFinished(); return; } if (out.action != Action::ContentAdd) { return; // keep waiting. } } } Action actionToSend = Action::SessionAccept; State finalState = State::Active; // so all contents is ready for session-initiate. let's do it if (role == Origin::Initiator) { sid = manager->registerSession(q); actionToSend = Action::SessionInitiate; finalState = State::Pending; } QList contents; QList acceptApps; for (const auto &app : contentList) { QList xml; OutgoingUpdateCB callback; std::tie(xml, callback) = app->takeOutgoingUpdate(); contents += xml; // p->setState(State::Unacked); if (callback) { acceptApps.append(AckHndl { app, callback }); } } state = State::Unacked; sendJingle(actionToSend, contents, [this, acceptApps, finalState](JT *jt) { if (!jt->success()) return; state = finalState; for (const auto &h : acceptApps) { auto app = std::get<0>(h); auto callback = std::get<1>(h); if (app) { callback(jt); if (role == Origin::Responder) { app->start(); } } } if (finalState == State::Active) { emit q->activated(); } planStep(); }); return; } // So session is either in State::Pending or State::Active here. // State::Connecting status is skipped for session. QList updateXml; for (auto mp : applicationPads) { auto p = mp.toStrongRef(); QDomElement el = p->takeOutgoingSessionInfoUpdate(); if (!el.isNull()) { updateXml.append(el); // we can send session-info for just one application. so stop processing sendJingle(Action::SessionInfo, updateXml, [](JT *jt) { if (!jt->success()) qWarning("failure for session-info is ignored"); }); return; } } QMultiMap updates; for (auto app : signalingContent) { auto updateType = app->evaluateOutgoingUpdate(); if (updateType.action != Action::NoAction) { updates.insert(updateType, app); } } QList acceptApps; if (updates.size()) { auto upd = updates.begin().key(); // NOTE maybe some actions have more priority than others auto apps = updates.values(upd); for (auto app : apps) { QList xml; OutgoingUpdateCB callback; std::tie(xml, callback) = app->takeOutgoingUpdate(); updateXml += xml; if (callback) { acceptApps.append(AckHndl { app, callback }); } } sendJingle(upd.action, updateXml, [this, acceptApps](JT *jt) { for (const auto &h : acceptApps) { auto app = std::get<0>(h); auto callback = std::get<1>(h); if (app) { callback(jt); } } planStep(); }); } } Reason reason(const QDomElement &jingleEl) { QDomElement re = jingleEl.firstChildElement(QLatin1String("reason")); Reason reason; if (!re.isNull()) { reason = Reason(re); if (!reason.isValid()) { qDebug("invalid reason"); } } return reason; } using TransportResult = std::tuple>; TransportResult parseIncomingTransport(const QDomElement &contentEl) { auto tel = contentEl.firstChildElement(QLatin1String("transport")); QString transportNS; if (tel.isNull() || (transportNS = tel.namespaceURI()).isEmpty()) { TransportResult { false, Reason::NoReason, QSharedPointer() }; } auto trPad = q->transportPadFactory(transportNS); if (!trPad) { return TransportResult { true, Reason::UnsupportedTransports, QSharedPointer() }; } auto transport = trPad->manager()->newTransport(trPad, negateOrigin(role)); if (transport && transport->update(tel)) { return TransportResult { true, Reason::NoReason, transport }; } return TransportResult { false, Reason::NoReason, QSharedPointer() }; } void addAndInitContent(Origin creator, Application *content) { contentList.insert(ContentKey { content->contentName(), creator }, content); if (state != State::Created && content->evaluateOutgoingUpdate().action != Action::NoAction) { signalingContent.insert(content); } QObject::connect(content, &Application::updated, q, [this, content]() { signalingContent.insert(content); planStep(); }); QObject::connect(content, &Application::destroyed, q, [this, content]() { signalingContent.remove(content); initialIncomingUnacceptedContent.removeOne(content); for (auto it = contentList.begin(); it != contentList.end(); ++it) { // optimize for large lists? if (it.value() == content) { contentList.erase(it); break; } } }); } enum AddContentError { Ok, Unparsed, Unexpected, Unsupported }; std::tuple parseContentAdd(const QDomElement &ce) { QDomElement descriptionEl = ce.firstChildElement(QLatin1String("description")); QString descriptionNS = descriptionEl.namespaceURI(); typedef std::tuple result; ContentBase c(ce); auto trpr = parseIncomingTransport(ce); if (!c.isValid() || descriptionEl.isNull() || descriptionNS.isEmpty() || !std::get<0>(trpr)) { return result { Unparsed, Reason::Success, nullptr }; } auto appPad = q->applicationPadFactory(descriptionNS); auto trReason = std::get<1>(trpr); if (!appPad || trReason != Reason::NoReason) { return result { Unsupported, trReason == Reason::NoReason ? Reason::UnsupportedApplications : trReason, nullptr }; } QScopedPointer app(appPad->manager()->startApplication(appPad, c.name, c.creator, c.senders)); if (!app) return result { Unparsed, Reason::Success, nullptr }; auto descErr = app->setRemoteOffer(descriptionEl); if (descErr == Application::IncompatibleParameters) { return result { Unsupported, Reason::IncompatibleParameters, nullptr }; } else if (descErr == Application::Unparsed) { return result { Unparsed, Reason::Success, nullptr }; } if (app->setTransport(std::get<2>(trpr))) { return result { Ok, Reason::Success, app.take() }; } // TODO We can do transport-replace in all cases where std::get<1>(trpr) != NoReason return result { Unsupported, Reason::IncompatibleParameters, app.take() }; } typedef std::tuple, QList> ParseContentListResult; ParseContentListResult parseContentAddList(const QDomElement &jingleEl) { QMap addSet; QMap> rejectSet; QString contentTag(QStringLiteral("content")); for (QDomElement ce = jingleEl.firstChildElement(contentTag); !ce.isNull(); ce = ce.nextSiblingElement(contentTag)) { Private::AddContentError err; Reason::Condition cond; Application * app; std::tie(err, cond, app) = parseContentAdd(ce); if (err == Private::AddContentError::Unparsed) { lastError = XMPP::Stanza::Error(XMPP::Stanza::Error::Cancel, XMPP::Stanza::Error::BadRequest); qDeleteAll(addSet); return ParseContentListResult(Unparsed, cond, QList(), QList()); } auto contentName = app->contentName(); auto it = addSet.find(contentName); if (err != Private::AddContentError::Ok) { // can't continue as well if (app) { // we are going to reject it completely so delete delete app; } if (it == addSet.end()) { rejectSet.insert(contentName, std::make_pair(ce, cond)); } continue; } rejectSet.remove(contentName); // REVIEW probably not wantBetterTransport but wantBetterApplication if (it == addSet.end() || (*it)->wantBetterTransport(app->transport())) { if (it == addSet.end()) { addSet.insert(contentName, app); } else { delete *it; // unpreferred app *it = app; } } } if (rejectSet.size()) { QList rejectList; for (auto const &i : rejectSet) { rejectList.append(i.first); } return ParseContentListResult(Unsupported, rejectSet.first().second, addSet.values(), rejectList); } return ParseContentListResult(Ok, Reason::Success, addSet.values(), QList()); } std::tuple parseContentAccept(const QDomElement &ce) { QDomElement descriptionEl = ce.firstChildElement(QLatin1String("description")); QDomElement transportEl = ce.firstChildElement(QLatin1String("transport")); QString descriptionNS = descriptionEl.namespaceURI(); QString transportNS = transportEl.namespaceURI(); typedef std::tuple result; ContentBase c(ce); if (!c.isValid() || role != c.creator || descriptionEl.isNull() || transportEl.isNull() || descriptionNS.isEmpty() || transportNS.isEmpty()) { return result { Unparsed, Reason::NoReason, nullptr }; } auto app = q->content(c.name, role); if (!(app && app->state() == State::Pending)) { // reaccept is possible return result { AddContentError::Unexpected, Reason::NoReason, app }; } if (app->pad()->ns() != descriptionNS || app->transport()->pad()->ns() != transportNS) { // well it's more than unexpected. let's send unparsed return result { AddContentError::Unparsed, Reason::NoReason, app }; } if (!app->transport()->update(transportEl)) { // clearly unparsed. otherwise the app will generate failure event with a Reason. return result { AddContentError::Unparsed, Reason::NoReason, app }; } auto ansret = app->setRemoteAnswer(descriptionEl); if (ansret == Application::Unparsed) return result { AddContentError::Unparsed, Reason::NoReason, app }; if (ansret == Application::IncompatibleParameters || app->state() != State::Accepted) { // parsed but was not accepted. so it's somehow incompatible return result { AddContentError::Unsupported, Reason::IncompatibleParameters, app }; } return result { AddContentError::Ok, Reason::Success, app }; } std::tuple> parseContentAcceptList(const QDomElement &jingleEl) { QMap acceptSet; QMap> rejectSet; QString contentTag(QStringLiteral("content")); for (QDomElement ce = jingleEl.firstChildElement(contentTag); !ce.isNull(); ce = ce.nextSiblingElement(contentTag)) { Private::AddContentError err; Reason::Condition cond; Application * app; std::tie(err, cond, app) = parseContentAccept(ce); if (err == Private::AddContentError::Unparsed || err == Private::AddContentError::Unexpected) { for (auto &a : acceptSet) { a->setState(State::Pending); // reset state to pending for already passed validation before // passing error back } lastError = XMPP::Stanza::Error(XMPP::Stanza::Error::Cancel, err == Private::AddContentError::Unexpected ? XMPP::Stanza::Error::UnexpectedRequest : XMPP::Stanza::Error::BadRequest); if (err == Private::AddContentError::Unexpected) { ErrorUtil::fill(jingleEl.ownerDocument(), lastError, ErrorUtil::OutOfOrder); } return std::tuple>(false, QList()); } auto contentName = app->contentName(); auto it = acceptSet.find(contentName); auto rit = rejectSet.find(contentName); if (it != acceptSet.end() || rit != rejectSet.end()) { // duplicates are not allowed in accept request for (auto &a : acceptSet) { a->setState(State::Pending); // reset state to pending for already passed validation before // passing error back } lastError = XMPP::Stanza::Error(XMPP::Stanza::Error::Cancel, XMPP::Stanza::Error::BadRequest); return std::tuple>(false, QList()); } if (err != Private::AddContentError::Ok) { app->setState(State::Finished); // we can't keep working with this content for whatever reason. if // "accept" failed there is no fallback rejectSet.insert( contentName, std::make_pair(ce, cond)); // NOTE, probably instead of ce we have to generate original description continue; } acceptSet.insert(contentName, app); } if (rejectSet.size()) { QTimer::singleShot(0, q, [this, rejectSet]() mutable { auto cond = rejectSet.first().second; QList rejects; for (auto const &i : rejectSet) { rejects.append(i.first); } rejects += Reason(cond).toXml(manager->client()->doc()); outgoingUpdates.insert(Action::ContentRemove, OutgoingUpdate { rejects, [this, rejects](bool) { for (auto &r : rejects) { ContentBase c(r); delete contentList.take(ContentKey { c.name, role }); } if (contentList.isEmpty()) { // the other party has to generate session-terminate // but we do not care already setSessionFinished(); } } }); }); } return std::tuple>(true, acceptSet.values()); } bool handleIncomingContentAdd(const QDomElement &jingleEl) { Private::AddContentError err; Reason::Condition cond; QList apps; QList rejects; std::tie(err, cond, apps, rejects) = parseContentAddList(jingleEl); switch (err) { case Private::AddContentError::Unparsed: case Private::AddContentError::Unexpected: lastError = XMPP::Stanza::Error(XMPP::Stanza::Error::Cancel, XMPP::Stanza::Error::BadRequest); if (err == Private::AddContentError::Unexpected) { ErrorUtil::fill(jingleEl.ownerDocument(), lastError, ErrorUtil::OutOfOrder); } return false; case Private::AddContentError::Unsupported: rejects += Reason(cond).toXml(manager->client()->doc()); outgoingUpdates.insert(Action::ContentReject, OutgoingUpdate { rejects, OutgoingUpdateCB() }); break; case Private::AddContentError::Ok: break; } if (apps.size()) { Origin remoteRole = negateOrigin(role); for (auto app : apps) { addAndInitContent(remoteRole, app); // TODO check conflicts } QTimer::singleShot(0, q, [this]() { emit q->newContentReceived(); }); } planStep(); return true; } bool handleIncomingContentRemove(const QDomElement &jingleEl) { QSet toRemove; QString contentTag(QStringLiteral("content")); for (QDomElement ce = jingleEl.firstChildElement(contentTag); !ce.isNull(); ce = ce.nextSiblingElement(contentTag)) { ContentBase cb(ce); if (!cb.isValid()) { lastError = XMPP::Stanza::Error(XMPP::Stanza::Error::Cancel, XMPP::Stanza::Error::BadRequest); return false; } Application *app = contentList.value(ContentKey { cb.name, cb.creator }); if (app) { toRemove.insert(app); } } auto reasonEl = jingleEl.firstChildElement(QString::fromLatin1("reason")); Reason reason = reasonEl.isNull() ? Reason(Reason::Success) : Reason(reasonEl); for (auto app : toRemove) { app->incomingRemove(reason); contentList.remove(ContentKey { app->contentName(), app->creator() }); delete app; } if (contentList.isEmpty()) { terminateReason = reason; } planStep(); return true; } bool handleIncomingSessionTerminate(const QDomElement &jingleEl) { terminateReason = Reason(jingleEl.firstChildElement(QString::fromLatin1("reason"))); setSessionFinished(); return true; } bool handleIncomingSessionAccept(const QDomElement &jingleEl) { bool parsed; QList apps; std::tie(parsed, apps) = parseContentAcceptList(jingleEl); if (!parsed) { lastError = XMPP::Stanza::Error(XMPP::Stanza::Error::Cancel, XMPP::Stanza::Error::BadRequest); return false; } state = State::Connecting; if (apps.size()) { for (auto app : apps) { app->start(); } } QTimer::singleShot(0, q, [this]() { emit q->activated(); }); planStep(); return true; } bool handleIncomingContentAccept(const QDomElement &jingleEl) { bool parsed; QList apps; std::tie(parsed, apps) = parseContentAcceptList(jingleEl); // marks valid apps as accepted if (!parsed) { lastError = XMPP::Stanza::Error(XMPP::Stanza::Error::Cancel, XMPP::Stanza::Error::BadRequest); return false; } if (apps.size() && state >= State::Active) { for (auto app : apps) { app->start(); // start accepted app. connection establishing and data transfer are inside } } planStep(); return true; } bool handleIncomingTransportReplace(const QDomElement &jingleEl) { QList, QDomElement>> passed; QList toReject; QString contentTag(QStringLiteral("content")); bool doTieBreak = false; for (QDomElement ce = jingleEl.firstChildElement(contentTag); !ce.isNull(); ce = ce.nextSiblingElement(contentTag)) { ContentBase cb(ce); bool transportParsed; Reason::Condition errReason; QSharedPointer transport; std::tie(transportParsed, errReason, transport) = parseIncomingTransport(ce); if (!cb.isValid() || !transportParsed) { lastError = XMPP::Stanza::Error(XMPP::Stanza::Error::Cancel, XMPP::Stanza::Error::BadRequest); return false; } Application *app = contentList.value(ContentKey { cb.name, cb.creator }); if (!app || (app->creator() == role && app->state() <= State::Unacked)) { qDebug("not existing app or inaporpriate app state"); lastError = XMPP::Stanza::Error(XMPP::Stanza::Error::Cancel, XMPP::Stanza::Error::ItemNotFound); return false; } if (errReason) { qDebug("failed to construct transport"); toReject.append(ce); continue; } auto old = app->transport(); Q_ASSERT(old != nullptr); // if it's my transport and it's sent but unacknowledged but has to be accepted if (old->isLocal() && old->state() == State::Unacked && role == Origin::Initiator) { doTieBreak = true; continue; } if (!app->transportSelector()->canReplace(app->transport(), transport)) { qDebug("incoming unsupported or already used transport"); toReject.append(ce); continue; } if (!app->isTransportReplaceEnabled()) { qDebug("transport replace is disabled for %s", qPrintable(app->contentName())); toReject.append(ce); continue; } passed.append(std::make_tuple(app, transport, ce)); } for (auto &v : passed) { Application * app; QSharedPointer transport; QDomElement ce; std::tie(app, transport, ce) = v; if (doTieBreak) { if (app->transport()->creator() == role && app->transport()->state() < State::Unacked) continue; // it will send transport soon app->selectNextTransport(transport); } else if (!app->setTransport(transport)) { // app should generate transport accept eventually. content-accept will // work too if the content wasn't accepted yet toReject.append(ce); } } if (doTieBreak) { lastError = ErrorUtil::makeTieBreak(*manager->client()->doc()); return false; } else if (toReject.size()) { outgoingUpdates.insert(Action::TransportReject, OutgoingUpdate { toReject, OutgoingUpdateCB() }); } planStep(); return true; } bool handleIncomingTransportAccept(const QDomElement &jingleEl) { QString contentTag(QStringLiteral("content")); QList> updates; for (QDomElement ce = jingleEl.firstChildElement(contentTag); !ce.isNull(); ce = ce.nextSiblingElement(contentTag)) { ContentBase cb(ce); auto transportEl = ce.firstChildElement(QString::fromLatin1("transport")); QString transportNS = transportEl.namespaceURI(); if (!cb.isValid() || transportEl.isNull() || transportNS.isEmpty()) { lastError = XMPP::Stanza::Error(XMPP::Stanza::Error::Cancel, XMPP::Stanza::Error::BadRequest); return false; } Application *app = contentList.value(ContentKey { cb.name, cb.creator }); if (!app || !app->transport() || app->transport()->creator() != role || app->transport()->state() != State::Pending) { // ignore out of order continue; } updates.append(qMakePair(app, transportEl)); } for (auto &u : updates) { if (u.first->transport()->update(u.second) && u.first->state() >= State::Connecting) { u.first->transport()->start(); } // if update fails transport should trigger replace procedure } planStep(); return true; } bool handleIncomingTransportInfo(const QDomElement &jingleEl) { QString contentTag(QStringLiteral("content")); QList, QDomElement>> updates; for (QDomElement ce = jingleEl.firstChildElement(contentTag); !ce.isNull(); ce = ce.nextSiblingElement(contentTag)) { Application *app = nullptr; ContentBase cb(ce); if (!cb.isValid() || !(app = q->content(cb.name, cb.creator)) || app->state() >= State::Finishing || !app->transport()) { lastError = XMPP::Stanza::Error(XMPP::Stanza::Error::Cancel, XMPP::Stanza::Error::BadRequest); return false; } auto tel = ce.firstChildElement(QStringLiteral("transport")); if (tel.isNull() || tel.namespaceURI() != app->transport()->pad()->ns()) { lastError = XMPP::Stanza::Error(XMPP::Stanza::Error::Cancel, XMPP::Stanza::Error::BadRequest); return false; } updates.append(qMakePair(app->transport(), tel)); } for (auto &u : updates) { if (!u.first->update(u.second)) { lastError = u.first->lastError(); return false; // failure should trigger transport replace } } return true; } }; Session::Session(Manager *manager, const Jid &peer, Origin role) : d(new Private) { d->q = this; d->role = role; d->manager = manager; d->otherParty = peer; d->stepTimer.setSingleShot(true); d->stepTimer.setInterval(0); connect(&d->stepTimer, &QTimer::timeout, this, [this]() { d->doStep(); }); } Session::~Session() { qDeleteAll(d->contentList); qDebug("session %s destroyed", qPrintable(d->sid)); } Manager *Session::manager() const { return d->manager; } State Session::state() const { return d->state; } Jid Session::me() const { return d->localParty; } Jid Session::peer() const { return d->otherParty; } Jid Session::initiator() const { return d->role == Origin::Initiator ? d->manager->client()->jid() : d->otherParty; } Jid Session::responder() const { return d->role == Origin::Responder ? d->manager->client()->jid() : d->otherParty; } QString Session::sid() const { return d->sid; } Origin Session::role() const { return d->role; } Origin Session::peerRole() const { return negateOrigin(d->role); } XMPP::Stanza::Error Session::lastError() const { return d->lastError; } Application *Session::newContent(const QString &ns, Origin senders) { auto pad = applicationPadFactory(ns); if (pad) { return pad->manager()->startApplication(pad, pad->generateContentName(senders), d->role, senders); } return nullptr; } Application *Session::content(const QString &contentName, Origin creator) { return d->contentList.value(ContentKey { contentName, creator }); } void Session::addContent(Application *content) { Q_ASSERT(d->state < State::Finishing); d->addAndInitContent(d->role, content); if (d->state >= State::ApprovedToSend) { // If we add content to already initiated session then we are gonna // send it immediatelly. So start prepare content->prepare(); } } const QMap &Session::contentList() const { return d->contentList; } ApplicationManagerPad::Ptr Session::applicationPad(const QString &ns) { return d->applicationPads.value(ns).toStrongRef(); } TransportManagerPad::Ptr Session::transportPad(const QString &ns) { return d->transportPads.value(ns).toStrongRef(); } QSharedPointer Session::newOutgoingTransport(const QString &ns) { auto pad = transportPadFactory(ns); if (pad) { return pad->manager()->newTransport(pad, d->role); // pad on both side becaue we need shared pointer } return QSharedPointer(); } QString Session::preferredApplication() const { // TODO some heuristics to detect preferred application if (d->applicationPads.size()) { return d->applicationPads.constBegin().key(); } return QString(); } QStringList Session::allApplicationTypes() const { return d->applicationPads.keys(); } void Session::setLocalJid(const Jid &jid) { d->localParty = jid; } void Session::accept() { Q_ASSERT(d->role == Origin::Responder && d->state == State::Created); // So we presented a user incoming session in UI, the user modified it somehow and finally accepted. d->state = State::ApprovedToSend; for (auto &c : d->contentList) { c->prepare(); } d->planStep(); } void Session::initiate() { emit initiated(); if (d->role == Origin::Initiator && d->state == State::Created) { d->state = State::ApprovedToSend; for (auto &c : d->contentList) { c->prepare(); } d->planStep(); } } void Session::terminate(Reason::Condition cond, const QString &comment) { if (d->role == Origin::Initiator && d->state == State::ApprovedToSend) { d->setSessionFinished(); return; } d->state = State::Finishing; d->terminateReason = Reason(cond, comment); d->planStep(); } TransportManagerPad::Ptr Session::transportPadFactory(const QString &ns) { auto pad = d->transportPads.value(ns).toStrongRef(); if (!pad) { auto deleter = [ns, this](TransportManagerPad *pad) { d->transportPads.remove(ns); delete pad; }; pad = TransportManagerPad::Ptr(d->manager->transportPad(this, ns), deleter); if (pad) { d->transportPads.insert(ns, pad); } } return pad; } ApplicationManagerPad::Ptr Session::applicationPadFactory(const QString &ns) { auto pad = d->applicationPads.value(ns).toStrongRef(); if (!pad) { auto deleter = [ns, this](ApplicationManagerPad *pad) { d->applicationPads.remove(ns); delete pad; }; pad = ApplicationManagerPad::Ptr(d->manager->applicationPad(this, ns), deleter); if (pad) { d->applicationPads.insert(ns, pad); } } return pad; } bool Session::incomingInitiate(const Jingle &jingle, const QDomElement &jingleEl) { d->sid = jingle.sid(); d->origFrom = d->otherParty; if (jingle.initiator().isValid() && !jingle.initiator().compare(d->origFrom)) { d->otherParty = jingle.initiator(); } Private::AddContentError err; Reason::Condition cond; QList apps; QList rejects; std::tie(err, cond, apps, rejects) = d->parseContentAddList(jingleEl); switch (err) { case Private::AddContentError::Unparsed: case Private::AddContentError::Unexpected: return false; case Private::AddContentError::Unsupported: d->terminateReason = Reason(cond); d->planStep(); return true; case Private::AddContentError::Ok: if (!apps.size()) return false; d->initialIncomingUnacceptedContent = apps; for (auto app : apps) { d->addAndInitContent(Origin::Initiator, app); } d->planStep(); return true; } return false; } bool Session::updateFromXml(Action action, const QDomElement &jingleEl) { if (d->state == State::Finished) { d->lastError = XMPP::Stanza::Error(XMPP::Stanza::Error::Cancel, XMPP::Stanza::Error::UnexpectedRequest); ErrorUtil::fill(jingleEl.ownerDocument(), d->lastError, ErrorUtil::OutOfOrder); return false; } switch (action) { case Action::ContentAccept: return d->handleIncomingContentAccept(jingleEl); case Action::ContentAdd: return d->handleIncomingContentAdd(jingleEl); case Action::ContentModify: break; case Action::ContentReject: break; case Action::ContentRemove: return d->handleIncomingContentRemove(jingleEl); case Action::DescriptionInfo: break; case Action::SecurityInfo: break; case Action::SessionAccept: return d->handleIncomingSessionAccept(jingleEl); case Action::SessionInfo: break; case Action::SessionInitiate: // impossible case. but let compiler be happy break; case Action::SessionTerminate: return d->handleIncomingSessionTerminate(jingleEl); case Action::TransportAccept: return d->handleIncomingTransportAccept(jingleEl); case Action::TransportInfo: return d->handleIncomingTransportInfo(jingleEl); case Action::TransportReject: break; case Action::TransportReplace: return d->handleIncomingTransportReplace(jingleEl); case Action::NoAction: break; } d->lastError = XMPP::Stanza::Error(XMPP::Stanza::Error::Cancel, XMPP::Stanza::Error::FeatureNotImplemented); return false; } }} #include "jingle-session.moc" psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/jingle-session.h000066400000000000000000000064271370065651000247760ustar00rootroot00000000000000/* * jignle-session.h - Jingle Session * Copyright (C) 2019 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef JINGLE_SESSION_H #define JINGLE_SESSION_H #include "jingle-application.h" #include "jingle-transport.h" namespace XMPP { namespace Jingle { // class Manager; class Application; class Session : public QObject { Q_OBJECT public: // Note incoming session are not registered in Jingle Manager until validated. // and then either rejected or registered in Pending state. Session(Manager *manager, const Jid &peer, Origin role = Origin::Initiator); ~Session(); Manager *manager() const; State state() const; Jid me() const; Jid peer() const; Jid initiator() const; Jid responder() const; QString sid() const; Origin role() const; // my role in session: initiator or responder Origin peerRole() const; XMPP::Stanza::Error lastError() const; // make new local content but do not add it to session yet Application *newContent(const QString &ns, Origin senders = Origin::Both); // get registered content if any Application * content(const QString &contentName, Origin creator); void addContent(Application *content); const QMap &contentList() const; ApplicationManagerPad::Ptr applicationPad(const QString &ns); TransportManagerPad::Ptr transportPad(const QString &ns); QSharedPointer newOutgoingTransport(const QString &ns); QString preferredApplication() const; QStringList allApplicationTypes() const; void setLocalJid(const Jid &jid); // w/o real use case the implementation is rather stub void accept(); void initiate(); void terminate(Reason::Condition cond, const QString &comment = QString()); // allocates or returns existing pads ApplicationManagerPad::Ptr applicationPadFactory(const QString &ns); TransportManagerPad::Ptr transportPadFactory(const QString &ns); signals: void managerPadAdded(const QString &ns); void initiated(); void activated(); void terminated(); void newContentReceived(); private: friend class Manager; friend class JTPush; bool incomingInitiate(const Jingle &jingle, const QDomElement &jingleEl); bool updateFromXml(Action action, const QDomElement &jingleEl); class Private; QScopedPointer d; }; }} #endif // JINGLE_SESSION_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/jingle-transport.cpp000066400000000000000000000051751370065651000257010ustar00rootroot00000000000000/* * jignle-transport.cpp - Base Jingle transport classes * Copyright (C) 2019 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "jingle-transport.h" #include "jingle-session.h" namespace XMPP { namespace Jingle { //---------------------------------------------------------------------------- // TransportManager //---------------------------------------------------------------------------- TransportManager::TransportManager(QObject *parent) : QObject(parent) { } //---------------------------------------------------------------------------- // Transport //---------------------------------------------------------------------------- Transport::Transport(TransportManagerPad::Ptr pad, Origin creator) : _creator(creator), _pad(pad) { } bool Transport::isRemote() const { return _pad->session()->role() != _creator; } int Transport::maxSupportedChannels() const { return 1; } void Transport::setState(State newState) { _prevState = _state; _state = newState; emit stateChanged(); } //---------------------------------------------------------------------------- // Connection //---------------------------------------------------------------------------- bool Connection::hasPendingDatagrams() const { return false; } NetworkDatagram Connection::receiveDatagram(qint64 maxSize) { Q_UNUSED(maxSize) return NetworkDatagram(); } size_t Connection::blockSize() const { return 0; // means "block" is not applicable for this kind of connection } //---------------------------------------------------------------------------- // TransportSelector //---------------------------------------------------------------------------- TransportSelector::~TransportSelector() { } bool TransportSelector::canReplace(QSharedPointer old, QSharedPointer newer) { return newer && (hasTransport(newer) || compare(old, newer) == 0); } }} psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/jingle-transport.h000066400000000000000000000213011370065651000253330ustar00rootroot00000000000000/* * jignle-transport.h - Base Jingle transport classes * Copyright (C) 2019 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef JINGLE_TRANSPORT_H #define JINGLE_TRANSPORT_H #include "bytestream.h" #include "jingle.h" #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0) #include #else #include #endif namespace XMPP { namespace Jingle { #if QT_VERSION < QT_VERSION_CHECK(5, 8, 0) // stub implementation class NetworkDatagram { public: bool _valid = false; QByteArray _data; inline NetworkDatagram(const QByteArray &data, const QHostAddress &destinationAddress = QHostAddress(), quint16 port = 0) : _valid(true), _data(data) { Q_UNUSED(destinationAddress); Q_UNUSED(port) } inline NetworkDatagram() { } inline bool isValid() const { return _valid; } inline QByteArray data() const { return _data; } }; #else typedef QNetworkDatagram NetworkDatagram; #endif class Connection : public ByteStream { Q_OBJECT public: enum Hint { AvoidRelays = 1 }; Q_DECLARE_FLAGS(Hints, Hint) using Ptr = QSharedPointer; // will be shared between transport and application virtual bool hasPendingDatagrams() const; virtual NetworkDatagram receiveDatagram(qint64 maxSize = -1); virtual size_t blockSize() const; inline void setHints(Hints hints) { _hints = hints; } inline Hints hints() const { return _hints; } signals: void connected(); protected: Hints _hints; }; Q_DECLARE_OPERATORS_FOR_FLAGS(Connection::Hints) class TransportManager; class TransportManagerPad : public SessionManagerPad { Q_OBJECT public: typedef QSharedPointer Ptr; virtual TransportManager *manager() const = 0; }; class Transport : public QObject { Q_OBJECT public: Transport(TransportManagerPad::Ptr pad, Origin creator); /*enum Direction { // incoming or outgoing file/data transfer. Outgoing, Incoming };*/ inline Origin creator() const { return _creator; } inline State state() const { return _state; } inline State prevState() const { return _prevState; } inline Reason lastReason() const { return _lastReason; } inline XMPP::Stanza::Error lastError() const { return _lastError; } inline TransportManagerPad::Ptr pad() const { return _pad; } bool isRemote() const; inline bool isLocal() const { return !isRemote(); } /** * @brief prepare to send content-add/session-initiate * When ready, the application first set update type to ContentAdd and then emit updated() */ virtual void prepare() = 0; /** * @brief start really transfer data. starting with connection to remote candidates for example */ virtual void start() = 0; // for local transport start searching for candidates (including probing proxy,stun // etc) for remote transport try to connect to all proposed hosts in order their // priority. in-band transport may just emit updated() here virtual bool update(const QDomElement &el) = 0; // accepts transport element on incoming transport-info virtual bool hasUpdates() const = 0; virtual OutgoingTransportInfoUpdate takeOutgoingUpdate() = 0; virtual bool isValid() const = 0; virtual TransportFeatures features() const = 0; virtual int maxSupportedChannels() const; virtual Connection::Ptr addChannel() const = 0; // returns established QIODevice-based connection signals: void updated(); // found some candidates and they have to be sent. takeUpdate has to be called from this signal // handler. if it's just always ready then signal has to be sent at least once otherwise // session-initiate won't be sent. void connected(); // this signal is for app logic. maybe to finally start drawing some progress bar void failed(); // transport ailed for whatever reason. aborted for example. _state will be State::Finished void stateChanged(); protected: // just updates state and signals about the change. No any loggic attached to the new state void setState(State newState); State _state = State::Created; State _prevState = State::Created; Origin _creator = Origin::None; QSharedPointer _pad; Reason _lastReason; XMPP::Stanza::Error _lastError; int _channelCount = 1; }; // It's an available transports collection per application struct TransportSelector { virtual ~TransportSelector(); // Allocate the most preferred transport from the set // Returned transport is removed from the list of available. virtual QSharedPointer getNextTransport() = 0; // Allocate alike transport (e.g. we have remote transport but instead want to use our // own of the same type and similar parameters) // Returned transport is removed from the list of available. virtual QSharedPointer getAlikeTransport(QSharedPointer alike) = 0; // Checks if replacement old with newer is possible (e.g. calls canReplace) and removes // the newer transport from the list of available. // Returns false if impossible. virtual bool replace(QSharedPointer old, QSharedPointer newer) = 0; // Put transport back to the set for future use virtual void backupTransport(QSharedPointer) = 0; // Where we can allocate another transport for a replacement virtual bool hasMoreTransports() const = 0; // Check where we can (still) use this transport for the application virtual bool hasTransport(QSharedPointer) const = 0; /* >0: transport `a` is more preferred than `b` <0: transport `a` is less preferred =0: it's essentially the same transport, so hardly a replacement. */ virtual int compare(QSharedPointer a, QSharedPointer b) const = 0; // Returns false if it's impossible to replace old with newer for example if the newer is // not supported or already proven to be useless. // Default implementation checks is thew newer transport is among remaining or same as old virtual bool canReplace(QSharedPointer old, QSharedPointer newer); }; class TransportManager : public QObject { Q_OBJECT public: TransportManager(QObject *parent = nullptr); // may show more features than Transport instance. For example some transports may work in both reliable and not // reliable modes virtual TransportFeatures features() const = 0; virtual void setJingleManager(Manager *jm) = 0; // FIXME rename methods virtual QSharedPointer newTransport(const TransportManagerPad::Ptr &pad, Origin creator) = 0; virtual TransportManagerPad * pad(Session *session) = 0; // this method is supposed to gracefully close all related sessions as a preparation for plugin unload for // example virtual void closeAll() = 0; signals: void abortAllRequested(); // mostly used by transport instances to abort immediately }; }} #endif psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/jingle.cpp000066400000000000000000000666051370065651000236540ustar00rootroot00000000000000/* * jignle.cpp - General purpose Jingle * Copyright (C) 2019 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "jingle.h" #include "jingle-application.h" #include "jingle-session.h" #include "xmpp-im/xmpp_hash.h" #include "xmpp/jid/jid.h" #include "xmpp_client.h" #include "xmpp_stream.h" #include "xmpp_task.h" #include "xmpp_xmlcommon.h" #include #include #include #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) #include #endif namespace XMPP { namespace Jingle { const QString NS(QStringLiteral("urn:xmpp:jingle:1")); const QString ERROR_NS(QStringLiteral("urn:xmpp:jingle:errors:1")); //---------------------------------------------------------------------------- // Jingle //---------------------------------------------------------------------------- static const struct { const char *text; Action action; } jingleActions[] = { { "content-accept", Action::ContentAccept }, { "content-add", Action::ContentAdd }, { "content-modify", Action::ContentModify }, { "content-reject", Action::ContentReject }, { "content-remove", Action::ContentRemove }, { "description-info", Action::DescriptionInfo }, { "security-info", Action::SecurityInfo }, { "session-accept", Action::SessionAccept }, { "session-info", Action::SessionInfo }, { "session-initiate", Action::SessionInitiate }, { "session-terminate", Action::SessionTerminate }, { "transport-accept", Action::TransportAccept }, { "transport-info", Action::TransportInfo }, { "transport-reject", Action::TransportReject }, { "transport-replace", Action::TransportReplace } }; Origin negateOrigin(Origin o) { switch (o) { case Origin::None: return Origin::Both; case Origin::Both: return Origin::None; case Origin::Initiator: return Origin::Responder; case Origin::Responder: return Origin::Initiator; } return Origin::None; } class Jingle::Private : public QSharedData { public: Action action; QString sid; Jid initiator; Jid responder; }; Jingle::Jingle() { } Jingle::Jingle(Action action, const QString &sid) : d(new Private) { d->action = action; d->sid = sid; } Jingle::Jingle(const QDomElement &e) { QString actionStr = e.attribute(QLatin1String("action")); Action action = Action::NoAction; QString sid = e.attribute(QLatin1String("sid")); Jid initiator; Jid responder; for (unsigned int i = 0; i < sizeof(jingleActions) / sizeof(jingleActions[0]); i++) { if (actionStr == jingleActions[i].text) { action = jingleActions[i].action; break; } } if (action == Action::NoAction || sid.isEmpty()) { return; } if (!e.attribute(QLatin1String("initiator")).isEmpty()) { initiator = Jid(e.attribute(QLatin1String("initiator"))); if (initiator.isNull()) { qDebug("malformed initiator jid"); return; } } if (!e.attribute(QLatin1String("responder")).isEmpty()) { responder = Jid(e.attribute(QLatin1String("responder"))); if (responder.isNull()) { qDebug("malformed responder jid"); return; } } d = new Private; d->action = action; d->sid = sid; d->responder = responder; } Jingle::Jingle(const Jingle &other) : d(other.d) { } Jingle::~Jingle() { } Jingle::Private *Jingle::ensureD() { if (!d) { d = new Private; } return d.data(); } QDomElement Jingle::toXml(QDomDocument *doc) const { if (!d || d->sid.isEmpty() || d->action == Action::NoAction) { return QDomElement(); } QDomElement query = doc->createElementNS(NS, QLatin1String("jingle")); for (unsigned int i = 0; i < sizeof(jingleActions) / sizeof(jingleActions[0]); i++) { if (jingleActions[i].action == d->action) { query.setAttribute(QLatin1String("action"), QLatin1String(jingleActions[i].text)); break; } } if (!d->initiator.isNull()) query.setAttribute(QLatin1String("initiator"), d->initiator.full()); if (!d->responder.isNull()) query.setAttribute(QLatin1String("responder"), d->responder.full()); query.setAttribute(QLatin1String("sid"), d->sid); return query; } Action Jingle::action() const { return d->action; } const QString &Jingle::sid() const { return d->sid; } const Jid &Jingle::initiator() const { return d->initiator; } void Jingle::setInitiator(const Jid &jid) { d->initiator = jid; } const Jid &Jingle::responder() const { return d->responder; } void Jingle::setResponder(const Jid &jid) { d->responder = jid; } //---------------------------------------------------------------------------- // Reason //---------------------------------------------------------------------------- static const QMap reasonConditions = { { QStringLiteral("alternative-session"), Reason::AlternativeSession }, { QStringLiteral("busy"), Reason::Busy }, { QStringLiteral("cancel"), Reason::Cancel }, { QStringLiteral("connectivity-error"), Reason::ConnectivityError }, { QStringLiteral("decline"), Reason::Decline }, { QStringLiteral("expired"), Reason::Expired }, { QStringLiteral("failed-application"), Reason::FailedApplication }, { QStringLiteral("failed-transport"), Reason::FailedTransport }, { QStringLiteral("general-error"), Reason::GeneralError }, { QStringLiteral("gone"), Reason::Gone }, { QStringLiteral("incompatible-parameters"), Reason::IncompatibleParameters }, { QStringLiteral("media-error"), Reason::MediaError }, { QStringLiteral("security-error"), Reason::SecurityError }, { QStringLiteral("success"), Reason::Success }, { QStringLiteral("timeout"), Reason::Timeout }, { QStringLiteral("unsupported-applications"), Reason::UnsupportedApplications }, { QStringLiteral("unsupported-transports"), Reason::UnsupportedTransports }, }; class Reason::Private : public QSharedData { public: Reason::Condition cond; QString text; }; Reason::Reason() { } Reason::~Reason() { } Reason::Reason(Reason::Condition cond, const QString &text) : d(new Private) { d->cond = cond; d->text = text; } Reason::Reason(const QDomElement &e) { if (e.tagName() != QLatin1String("reason")) return; Condition condition = NoReason; QString text; QString rns = e.namespaceURI(); for (QDomElement c = e.firstChildElement(); !c.isNull(); c = c.nextSiblingElement()) { if (c.tagName() == QLatin1String("text")) { text = c.text(); } else if (c.namespaceURI() != rns) { // TODO add here all the extensions to reason. } else { condition = reasonConditions.value(c.tagName()); } } if (condition != NoReason) { d = new Private; d->cond = condition; d->text = text; } } Reason::Reason(const Reason &other) : d(other.d) { } Reason &Reason::operator=(const Reason &other) { d = other.d; return *this; } Reason::Condition Reason::condition() const { if (d) return d->cond; return NoReason; } void Reason::setCondition(Condition cond) { ensureD()->cond = cond; } QString Reason::text() const { if (d) return d->text; return QString(); } void Reason::setText(const QString &text) { ensureD()->text = text; } QDomElement Reason::toXml(QDomDocument *doc) const { if (d && d->cond != NoReason) { for (auto r = reasonConditions.cbegin(); r != reasonConditions.cend(); ++r) { if (r.value() == d->cond) { QDomElement e = doc->createElement(QLatin1String("reason")); e.appendChild(doc->createElement(r.key())); if (!d->text.isEmpty()) { e.appendChild(textTag(doc, QLatin1String("text"), d->text)); } return e; } } } return QDomElement(); } Reason::Private *Reason::ensureD() { if (!d) { d = new Private; } return d.data(); } //---------------------------------------------------------------------------- // ContentBase //---------------------------------------------------------------------------- ContentBase::ContentBase(Origin creator, const QString &name) : creator(creator), name(name) { } ContentBase::ContentBase(const QDomElement &el) { static QMap sendersMap({ { QStringLiteral("initiator"), Origin::Initiator }, { QStringLiteral("none"), Origin::Both }, { QStringLiteral("responder"), Origin::Responder } }); creator = creatorAttr(el); name = el.attribute(QLatin1String("name")); senders = sendersMap.value(el.attribute(QLatin1String("senders"))); disposition = el.attribute(QLatin1String("disposition")); // if empty, it's "session" } QDomElement ContentBase::toXml(QDomDocument *doc, const char *tagName, const QString &ns) const { if (!isValid()) { return QDomElement(); } auto el = ns.isEmpty() ? doc->createElement(QLatin1String(tagName)) : doc->createElementNS(ns, QLatin1String(tagName)); setCreatorAttr(el, creator); el.setAttribute(QLatin1String("name"), name); QString sendersStr; switch (senders) { case Origin::None: sendersStr = QLatin1String("none"); break; case Origin::Initiator: sendersStr = QLatin1String("initiator"); break; case Origin::Responder: sendersStr = QLatin1String("responder"); break; case Origin::Both: break; } if (!disposition.isEmpty() && disposition != QLatin1String("session")) { el.setAttribute(QLatin1String("disposition"), disposition); // NOTE review how we can parse it some generic way } if (!sendersStr.isEmpty()) { el.setAttribute(QLatin1String("senders"), sendersStr); } return el; } Origin ContentBase::creatorAttr(const QDomElement &el) { auto creatorStr = el.attribute(QLatin1String("creator")); if (creatorStr == QLatin1String("initiator")) { return Origin::Initiator; } if (creatorStr == QLatin1String("responder")) { return Origin::Responder; } return Origin::None; } bool ContentBase::setCreatorAttr(QDomElement &el, Origin creator) { if (creator == Origin::Initiator) { el.setAttribute(QLatin1String("creator"), QLatin1String("initiator")); } else if (creator == Origin::Responder) { el.setAttribute(QLatin1String("creator"), QLatin1String("responder")); } else { return false; } return true; } //---------------------------------------------------------------------------- // JTPush - Jingle Task //---------------------------------------------------------------------------- class JTPush : public Task { Q_OBJECT QList externalManagers; QList externalSessions; public: JTPush(Task *parent) : Task(parent) { } ~JTPush() { } inline void addExternalManager(const QString &ns) { externalManagers.append(ns); } inline void forgetExternalSession(const QString &sid) { externalSessions.removeOne(sid); } inline void registerExternalSession(const QString &sid) { externalSessions.append(sid); } bool take(const QDomElement &iq) { if (iq.tagName() != QLatin1String("iq") || iq.attribute(QLatin1String("type")) != QLatin1String("set")) { return false; } auto jingleEl = iq.firstChildElement(QStringLiteral("jingle")); if (jingleEl.isNull() || jingleEl.namespaceURI() != ::XMPP::Jingle::NS) { return false; } Jingle jingle(jingleEl); if (!jingle.isValid()) { respondError(iq, Stanza::Error::Cancel, Stanza::Error::BadRequest); return true; } if (externalManagers.size()) { if (jingle.action() == Action::SessionInitiate) { auto cname = QString::fromLatin1("content"); auto dname = QString::fromLatin1("description"); for (auto n = jingleEl.firstChildElement(cname); !n.isNull(); n = n.nextSiblingElement(cname)) { auto del = n.firstChildElement(dname); if (!del.isNull() && externalManagers.contains(del.namespaceURI())) { externalSessions.append(jingle.sid()); return false; } } } else if (externalSessions.contains(jingle.sid())) { if (jingle.action() == Action::SessionTerminate) { externalSessions.removeOne(jingle.sid()); } return false; } } QString fromStr(iq.attribute(QStringLiteral("from"))); Jid from(fromStr); if (jingle.action() == Action::SessionInitiate) { if (!client()->jingleManager()->isAllowedParty(from) || (!jingle.initiator().isEmpty() && !client()->jingleManager()->isAllowedParty(jingle.initiator()))) { respondError(iq, Stanza::Error::Cancel, Stanza::Error::ServiceUnavailable); return true; } Jid redirection(client()->jingleManager()->redirectionJid()); if (redirection.isValid()) { respondError(iq, Stanza::Error::Modify, Stanza::Error::Redirect, QStringLiteral("xmpp:") + redirection.full()); return true; } auto session = client()->jingleManager()->session(from, jingle.sid()); if (session) { if (session->role() == Origin::Initiator) { // respondTieBreak(iq); } else { // second session from this peer with the same sid. respondError(iq, Stanza::Error::Cancel, Stanza::Error::BadRequest); } return true; } session = client()->jingleManager()->incomingSessionInitiate(from, jingle, jingleEl); if (!session) { respondError(iq, client()->jingleManager()->lastError()); return true; } } else { auto session = client()->jingleManager()->session(from, jingle.sid()); if (!session) { if (jingle.action() == Action::SessionTerminate) { auto resp = createIQ(client()->doc(), "result", fromStr, iq.attribute(QStringLiteral("id"))); client()->send(resp); } else { auto el = client()->doc()->createElementNS(ERROR_NS, QStringLiteral("unknown-session")); respondError(iq, Stanza::Error::Cancel, Stanza::Error::ItemNotFound, QString(), el); } return true; } if (!session->updateFromXml(jingle.action(), jingleEl)) { respondError(iq, session->lastError()); return true; } } auto resp = createIQ(client()->doc(), "result", fromStr, iq.attribute(QStringLiteral("id"))); client()->send(resp); return true; } void respondError(const QDomElement &iq, Stanza::Error::ErrorType errType, Stanza::Error::ErrorCond errCond, const QString &text = QString(), const QDomElement &jingleErr = QDomElement()) { auto resp = createIQ(client()->doc(), "error", iq.attribute(QStringLiteral("from")), iq.attribute(QStringLiteral("id"))); Stanza::Error error(errType, errCond, text); auto errEl = error.toXml(*client()->doc(), client()->stream().baseNS()); if (!jingleErr.isNull()) { errEl.appendChild(jingleErr); } resp.appendChild(errEl); client()->send(resp); } void respondTieBreak(const QDomElement &iq) { Stanza::Error error(Stanza::Error::Cancel, Stanza::Error::Conflict); ErrorUtil::fill(*client()->doc(), error, ErrorUtil::TieBreak); respondError(iq, error); } void respondError(const QDomElement &iq, const Stanza::Error &error) { auto resp = createIQ(client()->doc(), "error", iq.attribute(QStringLiteral("from")), iq.attribute(QStringLiteral("id"))); resp.appendChild(error.toXml(*client()->doc(), client()->stream().baseNS())); client()->send(resp); } }; //---------------------------------------------------------------------------- // SessionManagerPad - handle event related to a type of app/transport but not specific instance //---------------------------------------------------------------------------- QDomElement SessionManagerPad::takeOutgoingSessionInfoUpdate() { return QDomElement(); } QDomDocument *SessionManagerPad::doc() const { return session()->manager()->client()->doc(); } //---------------------------------------------------------------------------- // Manager //---------------------------------------------------------------------------- class Manager::Private { public: XMPP::Client * client; Manager * manager; QScopedPointer pushTask; // ns -> application QMap> applicationManagers; // ns -> parser function QMap> transportManagers; std::function remoteJidCecker; // when set/valid any incoming session initiate will be replied with redirection error Jid redirectionJid; XMPP::Stanza::Error lastError; QHash, Session *> sessions; int maxSessions = -1; // no limit void setupSession(Session *s) { QObject::connect(s, &Session::terminated, manager, [this, s]() { sessions.remove(qMakePair(s->peer(), s->sid())); }); } }; Manager::Manager(Client *client) : QObject(client), d(new Private()) { d->client = client; d->manager = this; d->pushTask.reset(new JTPush(client->rootTask())); /* static bool mtReg = false; if (!mtReg) { qRegisterMetaType(); } */ } Manager::~Manager() { for (auto &m : d->transportManagers) { m->setJingleManager(nullptr); } for (auto &m : d->applicationManagers) { m->setJingleManager(nullptr); } } Client *Manager::client() const { return d->client; } void Manager::addExternalManager(const QString &ns) { d->pushTask->addExternalManager(ns); } void Manager::registerExternalSession(const QString &sid) { d->pushTask->registerExternalSession(sid); } void Manager::forgetExternalSession(const QString &sid) { d->pushTask->forgetExternalSession(sid); } void Manager::setRedirection(const Jid &to) { d->redirectionJid = to; } const Jid &Manager::redirectionJid() const { return d->redirectionJid; } void Manager::registerApp(const QString &ns, ApplicationManager *app) { d->applicationManagers.insert(ns, app); app->setJingleManager(this); } void Manager::unregisterApp(const QString &ns) { auto appManager = d->applicationManagers.value(ns); if (appManager) { appManager->closeAll(); d->applicationManagers.remove(ns); } } bool Manager::isRegisteredApplication(const QString &ns) { return d->applicationManagers.contains(ns); } ApplicationManagerPad *Manager::applicationPad(Session *session, const QString &ns) { auto am = d->applicationManagers.value(ns); if (!am) { return nullptr; } return am->pad(session); } void Manager::registerTransport(const QString &ns, TransportManager *transport) { d->transportManagers.insert(ns, transport); transport->setJingleManager(this); } void Manager::unregisterTransport(const QString &ns) { auto trManager = d->transportManagers.value(ns); if (trManager) { trManager->closeAll(); d->transportManagers.remove(ns); } } bool Manager::isRegisteredTransport(const QString &ns) { return d->transportManagers.contains(ns); } bool Manager::isAllowedParty(const Jid &jid) const { if (d->remoteJidCecker) { return d->remoteJidCecker(jid); } // REVIEW probably we can check Client's internal roster when checker is not set. return true; } Session *Manager::session(const Jid &remoteJid, const QString &sid) { return d->sessions.value(qMakePair(remoteJid, sid)); } void Manager::detachSession(Session *s) { s->disconnect(this); d->sessions.remove(qMakePair(s->peer(), s->sid())); } void Manager::setRemoteJidChecker(std::function checker) { d->remoteJidCecker = checker; } TransportManagerPad *Manager::transportPad(Session *session, const QString &ns) { auto transportManager = d->transportManagers.value(ns); if (!transportManager) { return nullptr; } return transportManager->pad(session); } QStringList Manager::availableTransports(const TransportFeatures &features) const { QStringList ret; for (auto it = d->transportManagers.cbegin(); it != d->transportManagers.cend(); ++it) { if (((*it)->features() & features) == features) { ret.append(it.key()); } } return ret; } Session *Manager::incomingSessionInitiate(const Jid &from, const Jingle &jingle, const QDomElement &jingleEl) { if (d->maxSessions > 0 && d->sessions.size() == d->maxSessions) { d->lastError = XMPP::Stanza::Error(XMPP::Stanza::Error::Wait, XMPP::Stanza::Error::ResourceConstraint); return nullptr; } auto key = qMakePair(from, jingle.sid()); auto s = new Session(this, from, Origin::Responder); if (s->incomingInitiate(jingle, jingleEl)) { // if parsed well d->sessions.insert(key, s); d->setupSession(s); // emit incomingSession makes sense when there are no unsolved conflicts in content descriptions / // transports // QTimer::singleShot(0,[s, this](){ emit incomingSession(s); }); // QMetaObject::invokeMethod(this, "incomingSession", Qt::QueuedConnection, Q_ARG(Session *, s)); QTimer::singleShot(0, this, [s, this]() { emit incomingSession(s); }); return s; } d->lastError = s->lastError(); delete s; return nullptr; } XMPP::Stanza::Error Manager::lastError() const { return d->lastError; } Session *Manager::newSession(const Jid &j) { auto s = new Session(this, j); d->setupSession(s); return s; } QString Manager::registerSession(Session *session) { QString id; auto peer = session->peer(); do { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) id = QString("%1").arg(QRandomGenerator::global()->generate(), 6, 32, QChar('0')); #else id = QString("%1").arg(quint32(qrand()), 6, 32, QChar('0')); #endif } while (d->sessions.contains(qMakePair(peer, id))); d->sessions.insert(qMakePair(peer, id), session); return id; } //---------------------------------------------------------------------------- // ErrorUtil //---------------------------------------------------------------------------- const char *ErrorUtil::names[ErrorUtil::Last] = { "out-of-order", "tie-break", "unknown-session", "unsupported-info" }; Stanza::Error ErrorUtil::make(QDomDocument &doc, int jingleCond, int type, int condition, const QString &text) { auto el = doc.createElementNS(ERROR_NS, QString::fromLatin1(names[jingleCond - 1])); return Stanza::Error(type, condition, text, el); } void ErrorUtil::fill(QDomDocument doc, Stanza::Error &error, int jingleCond) { error.appSpec = doc.createElementNS(ERROR_NS, QString::fromLatin1(names[jingleCond - 1])); } int ErrorUtil::jingleCondition(const Stanza::Error &error) { if (error.appSpec.namespaceURI() != ERROR_NS) { return UnknownError; } QString tagName = error.appSpec.tagName(); for (int i = 0; i < int(sizeof(names) / sizeof(names[0])); ++i) { if (tagName == names[i]) { return i + 1; } } return UnknownError; } Stanza::Error ErrorUtil::makeTieBreak(QDomDocument &doc) { return make(doc, TieBreak, XMPP::Stanza::Error::Cancel, XMPP::Stanza::Error::Conflict); } Stanza::Error ErrorUtil::makeOutOfOrder(QDomDocument &doc) { return make(doc, OutOfOrder, XMPP::Stanza::Error::Cancel, XMPP::Stanza::Error::UnexpectedRequest); } } // namespace Jingle } // namespace XMPP #include "jingle.moc" psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/jingle.h000066400000000000000000000345521370065651000233150ustar00rootroot00000000000000/* * jignle.h - General purpose Jingle * Copyright (C) 2019 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef JINGLE_H #define JINGLE_H #include "xmpp_stanza.h" #include #include #include class QDomDocument; class QDomElement; namespace XMPP { class Client; class Task; namespace Jingle { extern const QString NS; class Manager; class Session; enum class Origin { None, Both, Initiator, Responder }; inline uint qHash(const XMPP::Jingle::Origin &o, uint seed = 0) { return ::qHash(int(o), seed); } /* Session states: * Created - new session * ApprovedToSend - user accepted session but it's not yet ready for session-initiate/accept message * Unacked - session-initiate/accept was sent. wait for IQ ack * Pending - local only: session-initiate was acknowledged. awaits session-accept. * Active - session was accepted and now active. * Finihed - session-terminate was sent/received Local app states: * Created - after constructor till local user initiates the sending * ApprovedToSend - user already clicked "send" but our offer is not ready yet (collecting candidates) * Unacked - initial offer is sent but no iq result yet * Pending - got iq result but no accept (answer) request yet * Accepted - remote accepted the app. waiting for start() (for example when all session is accepted) * Connecting - session was accepted (or content-accepted for early-session). negotiating connection * Active - connection was established. now real data passes. * Finishing - need to send some final statuses over signalling * Finished - app was removed from session Remote app states: * Created - after constructor till local user accepts the app * ApprovedToSend - user already accepted but our answer is not ready yet (collecting candidates) * Unacked - the answer is sent but no iq result yet * Accepted - waiting for start() (for example when all session is accepted) * Connecting - session was accepted (or content-accepted for early-session). negotiating connection * Active - connection was established. now real data passes. * Finishing - need to send some final statuses over signalling * Finished - app was removed from session Local transport states (our initial offer or our outgoing transport-replace): * Created - initial: just after constructor but before "send" button was pushed. * replace: if previous was > Created then replace will start right from ApprovedToSend * ApprovedToSend - initial: we are preparing initial offer ("send" was pushed already) * replace: we are going to replace previously sent trasport offer. preparing new one * Unacked - no iq "result" yet * Pending - got iq result but no any kind of transport accept * Accepted - session/content/transport-replace accepted. app should start negotiation explicitly * Connecting - connetion negotiation * Active - traferring data * Finished - In failure case: Needs to report transport failure / replace / reject Remote transport states (remote initial offer or remote transport-replace): * Created - initial: local user hasn't accepted yet the offer * replace: remote changes its own offer before local accepted anything * ApprovedToSend - initial/replace: user accepted the offer. we are preparing our response * Unacked - no iq "result" yet * Accepted - session/content/transport-replace accepted. app should start negotiation explicitly * Connecting - connetion negotiation * Active - traferring data * Finished - In failure case: Needs to report transport failure / replace / reject Locally initiated session passes all the above and remotely initiated skips Pending. */ enum class State { Created, ApprovedToSend, Unacked, Pending, Accepted, Connecting, Active, Finishing, Finished }; enum class Action { NoAction, // non-standard, just a default ContentAccept, ContentAdd, ContentModify, ContentReject, ContentRemove, DescriptionInfo, SecurityInfo, SessionAccept, SessionInfo, SessionInitiate, SessionTerminate, TransportAccept, TransportInfo, TransportReject, TransportReplace }; inline uint qHash(const XMPP::Jingle::Action &o, uint seed = 0) { return ::qHash(int(o), seed); } /* Categorization by speed, reliability and connectivity - speed: realtim, fast, slow - reliability: reliable, not reliable (some transport can both modes) - connectivity: always connect, hard to connect Some transports may change their qualities, so we have to consider worst case. ICE-UDP: RealTime, Not Reliable, Hard To Connect S5B: Fast, Reliable, Hard To Connect IBB: Slow, Reliable, Always Connect Also most of transports may add extra features but it's matter of configuration. For example all of them can enable p2p crypto mode ( should work here) */ enum class TransportFeature { // connection establishment HardToConnect = 0x01, // anything else but ibb AlwaysConnect = 0x02, // ibb. basically it's always connected // reliability NotReliable = 0x10, // datagram-oriented Reliable = 0x20, // connection-orinted // speed. Slow = 0x100, // only ibb is here probably Fast = 0x200, // basically all tcp-based and reliable part of sctp RealTime = 0x400 // it's rather about synchronization of frames with time which implies fast }; Q_DECLARE_FLAGS(TransportFeatures, TransportFeature) typedef QPair ContentKey; typedef std::function OutgoingUpdateCB; typedef std::tuple, OutgoingUpdateCB> OutgoingUpdate; // list of elements to b inserted to and success callback typedef std::tuple OutgoingTransportInfoUpdate; // transport element and success callback class ErrorUtil { public: enum { UnknownError, // unparsed/unknown error OutOfOrder, TieBreak, UnknownSession, UnsupportedInfo, Last }; static const char *names[Last]; static XMPP::Stanza::Error make(QDomDocument &doc, int jingleCond, int type = XMPP::Stanza::Error::Cancel, int condition = XMPP::Stanza::Error::UndefinedCondition, const QString &text = QString()); static XMPP::Stanza::Error makeTieBreak(QDomDocument &doc); static XMPP::Stanza::Error makeOutOfOrder(QDomDocument &doc); static void fill(QDomDocument doc, XMPP::Stanza::Error &error, int jingleCond); static int jingleCondition(const XMPP::Stanza::Error &error); }; class Jingle { public: Jingle(); // make invalid jingle element Jingle(Action action, const QString &sid); // start making outgoing jingle Jingle(const QDomElement &e); // likely incoming Jingle(const Jingle &); ~Jingle(); QDomElement toXml(QDomDocument *doc) const; inline bool isValid() const { return d != nullptr; } Action action() const; const QString &sid() const; const Jid & initiator() const; void setInitiator(const Jid &jid); const Jid & responder() const; void setResponder(const Jid &jid); private: class Private; QSharedDataPointer d; Jingle::Private * ensureD(); }; class Reason { class Private; public: enum Condition { NoReason = 0, // non-standard, just a default AlternativeSession, Busy, Cancel, ConnectivityError, Decline, Expired, FailedApplication, FailedTransport, GeneralError, Gone, IncompatibleParameters, MediaError, SecurityError, Success, Timeout, UnsupportedApplications, UnsupportedTransports }; Reason(); ~Reason(); Reason(Condition cond, const QString &text = QString()); Reason(const QDomElement &el); Reason(const Reason &other); Reason & operator=(const Reason &); inline bool isValid() const { return d != nullptr; } Condition condition() const; void setCondition(Condition cond); QString text() const; void setText(const QString &text); QDomElement toXml(QDomDocument *doc) const; private: Private *ensureD(); QSharedDataPointer d; }; class ContentBase { public: inline ContentBase() { } ContentBase(Origin creator, const QString &name); ContentBase(const QDomElement &el); inline bool isValid() const { return creator != Origin::None && !name.isEmpty(); } QDomElement toXml(QDomDocument *doc, const char *tagName, const QString &ns = QString()) const; static Origin creatorAttr(const QDomElement &el); static bool setCreatorAttr(QDomElement &el, Origin creator); Origin creator = Origin::None; QString name; Origin senders = Origin::Both; QString disposition; // default "session" }; class Security { }; /** * @brief The SessionManagerPad class - TransportManager/AppManager PAD * * The class is intended to be used to monitor global session events * as well as send them in context of specific application type. * * For example a session has 3 content elements (voice, video and whiteboard). * voice and video are related to RTP application while whiteaboard (Jingle SXE) * is a different application. Therefore the session will have 2 pads: * rtp pad and whitebaord pad. * The pads are connected to both session and transport/application manager * and their main task to handle Jingle session-info events. * * SessionManagerPad is a base class for all kinds of pads. * UI can connect to its signals. */ class SessionManagerPad : public QObject { Q_OBJECT public: virtual QDomElement takeOutgoingSessionInfoUpdate(); virtual QString ns() const = 0; virtual Session * session() const = 0; QDomDocument * doc() const; }; class ApplicationManager; class ApplicationManagerPad; class TransportManager; class TransportManagerPad; class Manager : public QObject { Q_OBJECT public: explicit Manager(XMPP::Client *client = nullptr); ~Manager(); XMPP::Client *client() const; // if we have another jingle manager we can add its contents' namespaces here. void addExternalManager(const QString &ns); // on outgoing session destroy an external manager should call this function. void registerExternalSession(const QString &sid); void forgetExternalSession(const QString &sid); void setRedirection(const Jid &to); const Jid &redirectionJid() const; void registerApp(const QString &ns, ApplicationManager *app); void unregisterApp(const QString &ns); bool isRegisteredApplication(const QString &ns); ApplicationManagerPad *applicationPad(Session * session, const QString &ns); // allocates new pad on application manager void registerTransport(const QString &ns, TransportManager *transport); void unregisterTransport(const QString &ns); bool isRegisteredTransport(const QString &ns); TransportManagerPad *transportPad(Session * session, const QString &ns); // allocates new pad on transport manager QStringList availableTransports(const TransportFeatures &features = TransportFeatures()) const; /** * @brief isAllowedParty checks if the remote jid allowed to initiate a session * @param jid - remote jid * @return true if allowed */ bool isAllowedParty(const Jid &jid) const; void setRemoteJidChecker(std::function checker); Session * session(const Jid &remoteJid, const QString &sid); Session * newSession(const Jid &j); QString registerSession(Session *session); XMPP::Stanza::Error lastError() const; void detachSession(Session *s); // disconnect the session from manager signals: void incomingSession(Session *); private: friend class JTPush; Session *incomingSessionInitiate(const Jid &from, const Jingle &jingle, const QDomElement &jingleEl); class Private; QScopedPointer d; }; Origin negateOrigin(Origin o); } // namespace Jingle } // namespace XMPP Q_DECLARE_OPERATORS_FOR_FLAGS(XMPP::Jingle::TransportFeatures) #endif // JINGLE_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/s5b.cpp000066400000000000000000001723041370065651000230670ustar00rootroot00000000000000/* * s5b.cpp - direct connection protocol via tcp * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "s5b.h" #include "im.h" #include "jingle-s5b.h" #include "socks.h" #include "tcpportreserver.h" #include "xmpp_xmlcommon.h" #include #include #include #include #include #ifdef Q_OS_WIN #include #else #include #endif #define MAXSTREAMHOSTS 5 //#define S5B_DEBUG static const char *S5B_NS = "http://jabber.org/protocol/bytestreams"; namespace XMPP { static QString makeKey(const QString &sid, const Jid &requester, const Jid &target) { #ifdef S5B_DEBUG qDebug("makeKey: sid=%s requester=%s target=%s %s", qPrintable(sid), qPrintable(requester.full()), qPrintable(target.full()), qPrintable(QCA::Hash("sha1").hashToString(QString(sid + requester.full() + target.full()).toUtf8()))); #endif QString str = sid + requester.full() + target.full(); return QCA::Hash("sha1").hashToString(str.toUtf8()); } static bool haveHost(const StreamHostList &list, const Jid &j) { for (StreamHostList::ConstIterator it = list.begin(); it != list.end(); ++it) { if ((*it).jid().compare(j)) return true; } return false; } class S5BManager::Item : public QObject { Q_OBJECT public: enum { Idle, Requester, Target, Active }; enum { ErrRefused, ErrConnect, ErrWrongHost, ErrProxy }; enum { Unknown, Fast, NotFast }; S5BManager * m = nullptr; int state = 0; QString sid, key, out_key, out_id, in_id; Jid self, peer; StreamHostList in_hosts; JT_S5B * task = nullptr; JT_S5B * proxy_task = nullptr; QList> relatedServers; SocksClient * client = nullptr; SocksClient * client_out = nullptr; SocksUDP * client_udp = nullptr; SocksUDP * client_out_udp = nullptr; S5BConnector * conn = nullptr; S5BConnector * proxy_conn = nullptr; // S5BServersManager::S5BLocalServers *localServ = nullptr; bool wantFast = false; StreamHost proxy; int targetMode = 0; // requester sets this once it figures it out bool fast; // target sets this bool activated; bool lateProxy; bool connSuccess; bool localFailed, remoteFailed; bool allowIncoming; bool udp; int statusCode = 0; Jid activatedStream; Item(S5BManager *manager); ~Item(); void resetConnection(); void startRequester(const QString &_sid, const Jid &_self, const Jid &_peer, bool fast, bool udp); void startTarget(const QString &_sid, const Jid &_self, const Jid &_peer, const QString &_dstaddr, const StreamHostList &hosts, const QString &iq_id, bool fast, bool udp); void handleFast(const StreamHostList &hosts, const QString &iq_id); void doOutgoing(); void doIncoming(); void setIncomingClient(SocksClient *sc); void incomingActivate(const Jid &streamHost); signals: void accepted(); void tryingHosts(const StreamHostList &list); void proxyConnect(); void waitingForActivation(); void connected(); void error(int); private slots: void jt_finished(); void conn_result(bool b); void proxy_result(bool b); void proxy_finished(); void sc_readyRead(); void sc_bytesWritten(qint64); void sc_error(int); private: void doConnectError(); void tryActivation(); void checkForActivation(); void checkFailure(); void finished(); }; //---------------------------------------------------------------------------- // S5BDatagram //---------------------------------------------------------------------------- S5BDatagram::S5BDatagram() { } S5BDatagram::S5BDatagram(int source, int dest, const QByteArray &data) : _source(source), _dest(dest), _buf(data) { } int S5BDatagram::sourcePort() const { return _source; } int S5BDatagram::destPort() const { return _dest; } QByteArray S5BDatagram::data() const { return _buf; } //---------------------------------------------------------------------------- // S5BConnection //---------------------------------------------------------------------------- class S5BConnection::Private { public: S5BManager * m; SocksClient * sc; SocksUDP * su; int state; Jid peer; QString sid; bool remote; bool switched; bool notifyRead, notifyClose; int id; S5BRequest req; Jid proxy; Mode mode; QList dglist; }; static int id_conn = 0; static int num_conn = 0; S5BConnection::S5BConnection(S5BManager *m, QObject *parent) : BSConnection(parent) { d = new Private; d->m = m; d->sc = nullptr; d->su = nullptr; ++num_conn; d->id = id_conn++; #ifdef S5B_DEBUG qDebug("S5BConnection[%d]: constructing, count=%d, %p\n", d->id, num_conn, this); #endif resetConnection(); } S5BConnection::~S5BConnection() { resetConnection(true); --num_conn; #ifdef S5B_DEBUG qDebug("S5BConnection[%d]: destructing, count=%d\n", d->id, num_conn); #endif delete d; } void S5BConnection::resetConnection(bool clear) { d->m->con_unlink(this); if (clear && d->sc) { delete d->sc; d->sc = nullptr; } delete d->su; d->su = nullptr; if (clear) { while (!d->dglist.isEmpty()) { delete d->dglist.takeFirst(); } } d->state = Idle; setOpenMode(QIODevice::NotOpen); d->peer = Jid(); d->sid = QString(); d->remote = false; d->switched = false; d->notifyRead = false; d->notifyClose = false; } Jid S5BConnection::proxy() const { return d->proxy; } void S5BConnection::setProxy(const Jid &proxy) { d->proxy = proxy; } void S5BConnection::connectToJid(const Jid &peer, const QString &sid, Mode m) { resetConnection(true); if (!d->m->isAcceptableSID(peer, sid)) return; d->peer = peer; d->sid = sid; d->state = Requesting; d->mode = m; #ifdef S5B_DEBUG qDebug("S5BConnection[%d]: connecting %s [%s]\n", d->id, qPrintable(d->peer.full()), qPrintable(d->sid)); #endif d->m->con_connect(this); } void S5BConnection::accept() { if (d->state != WaitingForAccept) return; d->state = Connecting; #ifdef S5B_DEBUG qDebug("S5BConnection[%d]: accepting %s [%s]\n", d->id, qPrintable(d->peer.full()), qPrintable(d->sid)); #endif d->m->con_accept(this); } void S5BConnection::close() { if (d->state == Idle) return; if (d->state == WaitingForAccept) d->m->con_reject(this); else if (d->state == Active) d->sc->close(); #ifdef S5B_DEBUG qDebug("S5BConnection[%d]: closing %s [%s]\n", d->id, qPrintable(d->peer.full()), qPrintable(d->sid)); #endif resetConnection(); } Jid S5BConnection::peer() const { return d->peer; } QString S5BConnection::sid() const { return d->sid; } BytestreamManager *S5BConnection::manager() const { return d->m; } bool S5BConnection::isRemote() const { return d->remote; } S5BConnection::Mode S5BConnection::mode() const { return d->mode; } int S5BConnection::state() const { return d->state; } qint64 S5BConnection::writeData(const char *data, qint64 maxSize) { if (d->state == Active && d->mode == Stream) return d->sc->write(data, maxSize); return 0; } qint64 S5BConnection::readData(char *data, qint64 maxSize) { if (d->sc) return d->sc->read(data, maxSize); else return 0; } qint64 S5BConnection::bytesAvailable() const { if (d->sc) return d->sc->bytesAvailable(); else return 0; } qint64 S5BConnection::bytesToWrite() const { if (d->state == Active) return d->sc->bytesToWrite(); else return 0; } void S5BConnection::writeDatagram(const S5BDatagram &i) { QByteArray buf; buf.resize(i.data().size() + 4); ushort ssp = htons(i.sourcePort()); ushort sdp = htons(i.destPort()); QByteArray data = i.data(); memcpy(buf.data(), &ssp, 2); memcpy(buf.data() + 2, &sdp, 2); memcpy(buf.data() + 4, data.data(), data.size()); sendUDP(buf); } S5BDatagram S5BConnection::readDatagram() { if (d->dglist.isEmpty()) return S5BDatagram(); S5BDatagram *i = d->dglist.takeFirst(); S5BDatagram val = *i; delete i; return val; } int S5BConnection::datagramsAvailable() const { return d->dglist.count(); } void S5BConnection::man_waitForAccept(const S5BRequest &r) { d->state = WaitingForAccept; d->remote = true; d->req = r; d->peer = r.from; d->sid = r.sid; d->mode = r.udp ? Datagram : Stream; } void S5BConnection::man_clientReady(SocksClient *sc, SocksUDP *sc_udp) { d->sc = sc; connect(d->sc, SIGNAL(connectionClosed()), SLOT(sc_connectionClosed())); connect(d->sc, SIGNAL(delayedCloseFinished()), SLOT(sc_delayedCloseFinished())); connect(d->sc, SIGNAL(readyRead()), SLOT(sc_readyRead())); connect(d->sc, SIGNAL(bytesWritten(qint64)), SLOT(sc_bytesWritten(qint64))); connect(d->sc, SIGNAL(error(int)), SLOT(sc_error(int))); if (sc_udp) { d->su = sc_udp; connect(d->su, SIGNAL(packetReady(QByteArray)), SLOT(su_packetReady(QByteArray))); } d->state = Active; setOpenMode(QIODevice::ReadWrite); #ifdef S5B_DEBUG qDebug("S5BConnection[%d]: %s [%s] <<< success >>>\n", d->id, qPrintable(d->peer.full()), qPrintable(d->sid)); #endif // bytes already in the stream? if (d->sc->bytesAvailable()) { #ifdef S5B_DEBUG qDebug("Stream has %d bytes in it.\n", (int)d->sc->bytesAvailable()); #endif d->notifyRead = true; } // closed before it got here? if (!d->sc->isOpen()) { #ifdef S5B_DEBUG qDebug("Stream was closed before S5B request finished?\n"); #endif d->notifyClose = true; } if (d->notifyRead || d->notifyClose) QTimer::singleShot(0, this, SLOT(doPending())); emit connected(); } void S5BConnection::doPending() { if (d->notifyRead) { if (d->notifyClose) QTimer::singleShot(0, this, SLOT(doPending())); sc_readyRead(); } else if (d->notifyClose) sc_connectionClosed(); } void S5BConnection::man_udpReady(const QByteArray &buf) { handleUDP(buf); } void S5BConnection::man_failed(int x) { resetConnection(true); if (x == S5BManager::Item::ErrRefused) setError(ErrRefused); if (x == S5BManager::Item::ErrConnect) setError(ErrConnect); if (x == S5BManager::Item::ErrWrongHost) setError(ErrConnect); if (x == S5BManager::Item::ErrProxy) setError(ErrProxy); } void S5BConnection::sc_connectionClosed() { // if we have a pending read notification, postpone close if (d->notifyRead) { #ifdef S5B_DEBUG qDebug("closed while pending read\n"); #endif d->notifyClose = true; return; } d->notifyClose = false; resetConnection(); connectionClosed(); } void S5BConnection::sc_delayedCloseFinished() { // echo emit delayedCloseFinished(); } void S5BConnection::sc_readyRead() { if (d->mode == Datagram) { // throw the data away d->sc->readAll(); return; } d->notifyRead = false; // echo emit readyRead(); } void S5BConnection::sc_bytesWritten(qint64 x) { // echo bytesWritten(x); } void S5BConnection::sc_error(int) { resetConnection(); setError(ErrSocket); } void S5BConnection::su_packetReady(const QByteArray &buf) { handleUDP(buf); } void S5BConnection::handleUDP(const QByteArray &buf) { // must be at least 4 bytes, to accomodate virtual ports if (buf.size() < 4) return; // drop ushort ssp, sdp; memcpy(&ssp, buf.data(), 2); memcpy(&sdp, buf.data() + 2, 2); int source = ntohs(ssp); int dest = ntohs(sdp); QByteArray data; data.resize(buf.size() - 4); memcpy(data.data(), buf.data() + 4, data.size()); d->dglist.append(new S5BDatagram(source, dest, data)); datagramReady(); } void S5BConnection::sendUDP(const QByteArray &buf) { if (d->su) d->su->write(buf); else d->m->con_sendUDP(this, buf); } //---------------------------------------------------------------------------- // S5BManager //---------------------------------------------------------------------------- class S5BManager::Entry { public: Entry() = default; ~Entry() { delete query; } S5BConnection *c = nullptr; Item * i = nullptr; QString sid; JT_S5B * query = nullptr; StreamHost proxyInfo; bool udp_init = false; QHostAddress udp_addr; int udp_port = 0; }; class S5BManager::Private { public: Client * client; QList activeList; S5BConnectionList incomingConns; JT_PushS5B * ps; }; S5BManager::S5BManager(Client *parent) : BytestreamManager(parent) { // S5B needs SHA1 // if(!QCA::isSupported(QCA::CAP_SHA1)) // QCA::insertProvider(createProviderHash()); d = new Private; d->client = parent; d->ps = new JT_PushS5B(d->client->rootTask()); connect(d->ps, SIGNAL(incoming(S5BRequest)), SLOT(ps_incoming(S5BRequest))); connect(d->ps, SIGNAL(incomingUDPSuccess(Jid, QString)), SLOT(ps_incomingUDPSuccess(Jid, QString))); connect(d->ps, SIGNAL(incomingActivate(Jid, QString, Jid)), SLOT(ps_incomingActivate(Jid, QString, Jid))); } S5BManager::~S5BManager() { while (!d->incomingConns.isEmpty()) { delete d->incomingConns.takeFirst(); } delete d->ps; delete d; } const char *S5BManager::ns() { return S5B_NS; } Client *S5BManager::client() const { return d->client; } JT_PushS5B *S5BManager::jtPush() const { return d->ps; } BSConnection *S5BManager::createConnection() { return new S5BConnection(this); } S5BConnection *S5BManager::takeIncoming() { if (d->incomingConns.isEmpty()) return nullptr; S5BConnection *c = d->incomingConns.takeFirst(); // move to activeList Entry *e = new Entry; e->c = c; e->sid = c->d->sid; d->activeList.append(e); return c; } void S5BManager::ps_incoming(const S5BRequest &req) { #ifdef S5B_DEBUG qDebug("S5BManager: incoming from %s\n", qPrintable(req.from.full())); #endif bool ok = false; // ensure we don't already have an incoming connection from this peer+sid S5BConnection *c = findIncoming(req.from, req.sid); if (!c) { // do we have an active entry with this sid already? Entry *e = findEntryBySID(req.from, req.sid); if (e) { if (e->i) { // loopback if (req.from.compare(d->client->jid()) && (req.id == e->i->out_id)) { #ifdef S5B_DEBUG qDebug("ALLOWED: loopback\n"); #endif ok = true; } // allowed by 'fast mode' else if (e->i->state == Item::Requester && e->i->targetMode == Item::Unknown) { #ifdef S5B_DEBUG qDebug("ALLOWED: fast-mode\n"); #endif e->i->handleFast(req.hosts, req.id); return; } } } else { #ifdef S5B_DEBUG qDebug("ALLOWED: we don't have it\n"); #endif ok = true; } } if (!ok) { d->ps->respondError(req.from, req.id, Stanza::Error::NotAcceptable, "SID in use"); return; } // create an incoming connection c = new S5BConnection(this); c->man_waitForAccept(req); d->incomingConns.append(c); emit incomingReady(); } void S5BManager::ps_incomingUDPSuccess(const Jid &from, const QString &key) { Entry *e = findEntryByHash(key); if (e && e->i) { if (e->i->conn) e->i->conn->man_udpSuccess(from); else if (e->i->proxy_conn) e->i->proxy_conn->man_udpSuccess(from); } } void S5BManager::ps_incomingActivate(const Jid &from, const QString &sid, const Jid &streamHost) { Entry *e = findEntryBySID(from, sid); if (e && e->i) e->i->incomingActivate(streamHost); } void S5BManager::doSuccess(const Jid &peer, const QString &id, const Jid &streamHost) { d->ps->respondSuccess(peer, id, streamHost); } void S5BManager::doError(const Jid &peer, const QString &id, Stanza::Error::ErrorCond cond, const QString &str) { d->ps->respondError(peer, id, cond, str); } void S5BManager::doActivate(const Jid &peer, const QString &sid, const Jid &streamHost) { d->ps->sendActivate(peer, sid, streamHost); } bool S5BManager::isAcceptableSID(const Jid &peer, const QString &sid) const { QString key = makeKey(sid, d->client->jid(), peer); QString key_out = makeKey(sid, peer, d->client->jid()); // not valid in muc via proxy for (Entry *e : d->activeList) { if (e->i) { if (e->i->key == key || e->i->key == key_out) return false; else { for (auto &s : e->i->relatedServers) { if (s->hasKey(key) || s->hasKey(key_out)) { return false; } } } } } return true; } const char *S5BManager::sidPrefix() const { return "s5b_"; } S5BConnection *S5BManager::findIncoming(const Jid &from, const QString &sid) const { for (S5BConnection *c : d->incomingConns) { if (c->d->peer.compare(from) && c->d->sid == sid) return c; } return nullptr; } S5BManager::Entry *S5BManager::findEntry(S5BConnection *c) const { for (Entry *e : d->activeList) { if (e->c == c) return e; } return nullptr; } S5BManager::Entry *S5BManager::findEntry(Item *i) const { for (Entry *e : d->activeList) { if (e->i == i) return e; } return nullptr; } S5BManager::Entry *S5BManager::findEntryByHash(const QString &key) const { for (Entry *e : d->activeList) { if (e->i && e->i->key == key) return e; } return nullptr; } S5BManager::Entry *S5BManager::findEntryBySID(const Jid &peer, const QString &sid) const { for (Entry *e : d->activeList) { if (e->i && e->i->peer.compare(peer) && e->sid == sid) return e; } return nullptr; } bool S5BManager::srv_ownsHash(const QString &key) const { if (findEntryByHash(key)) return true; return false; } void S5BManager::srv_incomingReady(SocksClient *sc, const QString &key) { Entry *e = findEntryByHash(key); if (!e->i->allowIncoming) { sc->requestDeny(); return; } if (e->c->d->mode == S5BConnection::Datagram) sc->grantUDPAssociate("", 0); else sc->grantConnect(); e->i->setIncomingClient(sc); } void S5BManager::srv_incomingUDP(bool init, const QHostAddress &addr, int port, const QString &key, const QByteArray &data) { Entry *e = findEntryByHash(key); if (e->c->d->mode != S5BConnection::Datagram) return; // this key isn't in udp mode? drop! if (init) { if (e->udp_init) return; // only init once // lock on to this sender e->udp_addr = addr; e->udp_port = port; e->udp_init = true; // reply that initialization was successful d->ps->sendUDPSuccess(e->c->d->peer, key); return; } // not initialized yet? something went wrong if (!e->udp_init) return; // must come from same source as when initialized if (addr.toString() != e->udp_addr.toString() || port != e->udp_port) return; e->c->man_udpReady(data); } void S5BManager::con_connect(S5BConnection *c) { if (findEntry(c)) return; Entry *e = new Entry; e->c = c; e->sid = c->d->sid; d->activeList.append(e); if (c->d->proxy.isValid()) { queryProxy(e); return; } entryContinue(e); } void S5BManager::con_accept(S5BConnection *c) { Entry *e = findEntry(c); if (!e) return; if (e->c->d->req.fast) { if (targetShouldOfferProxy(e)) { queryProxy(e); return; } } entryContinue(e); } void S5BManager::con_reject(S5BConnection *c) { d->ps->respondError(c->d->peer, c->d->req.id, Stanza::Error::NotAcceptable, "Not acceptable"); } void S5BManager::con_unlink(S5BConnection *c) { Entry *e = findEntry(c); if (!e) return; // active incoming request? cancel it if (e->i && e->i->conn) d->ps->respondError(e->i->peer, e->i->out_id, Stanza::Error::NotAcceptable, "Not acceptable"); delete e->i; d->activeList.removeAll(e); delete e; } void S5BManager::con_sendUDP(S5BConnection *c, const QByteArray &buf) { Entry *e = findEntry(c); if (!e) return; if (!e->udp_init) return; if (e->i->relatedServers.size()) // FIXME in fact we shoule keep eact related server in Connection e->i->relatedServers[0]->writeUDP(e->udp_addr, e->udp_port, buf); } void S5BManager::item_accepted() { Item * i = static_cast(sender()); Entry *e = findEntry(i); emit e->c->accepted(); // signal } void S5BManager::item_tryingHosts(const StreamHostList &list) { Item * i = static_cast(sender()); Entry *e = findEntry(i); e->c->tryingHosts(list); // signal } void S5BManager::item_proxyConnect() { Item * i = static_cast(sender()); Entry *e = findEntry(i); e->c->proxyConnect(); // signal } void S5BManager::item_waitingForActivation() { Item * i = static_cast(sender()); Entry *e = findEntry(i); e->c->waitingForActivation(); // signal } void S5BManager::item_connected() { Item * i = static_cast(sender()); Entry *e = findEntry(i); // grab the client SocksClient *client = i->client; i->client = nullptr; SocksUDP *client_udp = i->client_udp; i->client_udp = nullptr; // give it to the connection e->c->man_clientReady(client, client_udp); } void S5BManager::item_error(int x) { Item * i = static_cast(sender()); Entry *e = findEntry(i); e->c->man_failed(x); } void S5BManager::entryContinue(Entry *e) { e->i = new Item(this); e->i->proxy = e->proxyInfo; connect(e->i, SIGNAL(accepted()), SLOT(item_accepted())); connect(e->i, SIGNAL(tryingHosts(StreamHostList)), SLOT(item_tryingHosts(StreamHostList))); connect(e->i, SIGNAL(proxyConnect()), SLOT(item_proxyConnect())); connect(e->i, SIGNAL(waitingForActivation()), SLOT(item_waitingForActivation())); connect(e->i, SIGNAL(connected()), SLOT(item_connected())); connect(e->i, SIGNAL(error(int)), SLOT(item_error(int))); if (e->c->isRemote()) { const S5BRequest &req = e->c->d->req; e->i->startTarget(e->sid, d->client->jid(), e->c->d->peer, req.dstaddr, req.hosts, req.id, req.fast, req.udp); } else { e->i->startRequester(e->sid, d->client->jid(), e->c->d->peer, true, e->c->d->mode == S5BConnection::Datagram ? true : false); e->c->requesting(); // signal } } void S5BManager::queryProxy(Entry *e) { QPointer self = this; e->c->proxyQuery(); // signal if (!self) return; #ifdef S5B_DEBUG qDebug("querying proxy: [%s]\n", qPrintable(e->c->d->proxy.full())); #endif e->query = new JT_S5B(d->client->rootTask()); connect(e->query, SIGNAL(finished()), SLOT(query_finished())); e->query->requestProxyInfo(e->c->d->proxy); e->query->go(true); } void S5BManager::query_finished() { JT_S5B *query = static_cast(sender()); Entry * e = nullptr; for (Entry *i : d->activeList) { if (i->query == query) { e = i; break; } } if (!e) return; e->query = nullptr; #ifdef S5B_DEBUG qDebug("query finished: "); #endif if (query->success()) { e->proxyInfo = query->proxyInfo(); #ifdef S5B_DEBUG qDebug("host/ip=[%s] port=[%d]\n", qPrintable(e->proxyInfo.host()), e->proxyInfo.port()); #endif } else { #ifdef S5B_DEBUG qDebug("fail\n"); #endif } QPointer self = this; e->c->proxyResult(query->success()); // signal if (!self) return; entryContinue(e); } bool S5BManager::targetShouldOfferProxy(Entry *e) { if (!e->c->d->proxy.isValid()) return false; // if target, don't offer any proxy if the requester already did const StreamHostList &hosts = e->c->d->req.hosts; for (StreamHostList::ConstIterator it = hosts.begin(); it != hosts.end(); ++it) { if ((*it).isProxy()) return false; } // ensure we don't offer the same proxy as the requester if (haveHost(hosts, e->c->d->proxy)) return false; return true; } //---------------------------------------------------------------------------- // S5BManager::Item //---------------------------------------------------------------------------- S5BManager::Item::Item(S5BManager *manager) : QObject(nullptr), m(manager) { resetConnection(); } S5BManager::Item::~Item() { resetConnection(); } void S5BManager::Item::resetConnection() { for (auto &s : relatedServers) { s->unregisterKey(key); s.reset(); } relatedServers.clear(); delete task; task = nullptr; delete proxy_task; proxy_task = nullptr; delete conn; conn = nullptr; delete proxy_conn; proxy_conn = nullptr; delete client_udp; client_udp = nullptr; delete client; client = nullptr; delete client_out_udp; client_out_udp = nullptr; delete client_out; client_out = nullptr; state = Idle; wantFast = false; targetMode = Unknown; fast = false; activated = false; lateProxy = false; connSuccess = false; localFailed = false; remoteFailed = false; allowIncoming = false; udp = false; } void S5BManager::Item::startRequester(const QString &_sid, const Jid &_self, const Jid &_peer, bool fast, bool _udp) { sid = _sid; self = _self; peer = _peer; key = makeKey(sid, self, peer); out_key = makeKey(sid, peer, self); wantFast = fast; udp = _udp; #ifdef S5B_DEBUG qDebug("S5BManager::Item initiating request %s [%s] (inhash=%s)\n", qPrintable(peer.full()), qPrintable(sid), qPrintable(key)); #endif state = Requester; doOutgoing(); } void S5BManager::Item::startTarget(const QString &_sid, const Jid &_self, const Jid &_peer, const QString &_dstaddr, const StreamHostList &hosts, const QString &iq_id, bool _fast, bool _udp) { sid = _sid; peer = _peer; self = _self; in_hosts = hosts; in_id = iq_id; fast = _fast; key = makeKey(sid, self, peer); out_key = _dstaddr.isEmpty() ? makeKey(sid, peer, self) : _dstaddr; udp = _udp; #ifdef S5B_DEBUG qDebug("S5BManager::Item incoming request %s [%s] (inhash=%s)\n", qPrintable(peer.full()), qPrintable(sid), qPrintable(key)); #endif state = Target; if (fast) doOutgoing(); doIncoming(); } void S5BManager::Item::handleFast(const StreamHostList &hosts, const QString &iq_id) { targetMode = Fast; QPointer self = this; emit accepted(); if (!self) return; // if we already have a stream, then bounce this request if (client) { m->doError(peer, iq_id, Stanza::Error::NotAcceptable, "Not acceptable"); } else { in_hosts = hosts; in_id = iq_id; doIncoming(); } } void S5BManager::Item::doOutgoing() { StreamHostList hosts; auto disco = m->client()->tcpPortReserver()->scope(QString::fromLatin1("s5b"))->disco(); if (!haveHost(in_hosts, self)) { for (auto &c : disco->takeServers()) { auto server = c.staticCast(); server->registerKey(key); relatedServers.append(server); connect(server.data(), &S5BServer::incomingConnection, this, [this](SocksClient *c, const QString &key) { if (key == this->key) { m->srv_incomingReady(c, key); } }); connect(server.data(), &S5BServer::incomingUdp, this, [this](bool isInit, const QHostAddress &addr, int sourcePort, const QString &key, const QByteArray &data) { if (key == this->key) { m->srv_incomingUDP(isInit, addr, sourcePort, key, data); } }); StreamHost h; h.setJid(self); h.setHost(c->publishHost()); h.setPort(c->publishPort()); hosts += h; } } delete disco; // FIXME we could start listening for signals instead. it may send us upnp candidate // if the proxy is valid, then it's ok to add (the manager already ensured that it doesn't conflict) if (proxy.jid().isValid()) hosts += proxy; // if we're the target and we have no streamhosts of our own, then don't even bother with fast-mode if (state == Target && hosts.isEmpty()) { fast = false; return; } allowIncoming = true; task = new JT_S5B(m->client()->rootTask()); connect(task, SIGNAL(finished()), SLOT(jt_finished())); task->request(peer, sid, key, hosts, state == Requester ? wantFast : false, udp); out_id = task->id(); task->go(true); } void S5BManager::Item::doIncoming() { if (in_hosts.isEmpty()) { doConnectError(); return; } StreamHostList list; if (lateProxy) { // take just the proxy streamhosts for (const StreamHost &it : in_hosts) { if (it.isProxy()) list += it; } lateProxy = false; } else { // only try doing the late proxy trick if using fast mode AND we did not offer a proxy if ((state == Requester || (state == Target && fast)) && !proxy.jid().isValid()) { // take just the non-proxy streamhosts bool hasProxies = false; for (const StreamHost &it : in_hosts) { if (it.isProxy()) hasProxies = true; else list += it; } if (hasProxies) { lateProxy = true; // no regular streamhosts? wait for remote error if (list.isEmpty()) return; } } else list = in_hosts; } conn = new S5BConnector; connect(conn, SIGNAL(result(bool)), SLOT(conn_result(bool))); QPointer self = this; tryingHosts(list); if (!self) return; conn->start(this->self, list, out_key, udp, lateProxy ? 10 : 30); } void S5BManager::Item::setIncomingClient(SocksClient *sc) { #ifdef S5B_DEBUG qDebug("S5BManager::Item: %s [%s] successful incoming connection\n", qPrintable(peer.full()), qPrintable(sid)); #endif connect(sc, SIGNAL(readyRead()), SLOT(sc_readyRead())); connect(sc, SIGNAL(bytesWritten(qint64)), SLOT(sc_bytesWritten(qint64))); connect(sc, SIGNAL(error(int)), SLOT(sc_error(int))); sc->setParent(nullptr); // avoid deleting it by SocksServer destructor client = sc; allowIncoming = false; } void S5BManager::Item::incomingActivate(const Jid &streamHost) { if (!activated) { activatedStream = streamHost; checkForActivation(); } } void S5BManager::Item::jt_finished() { JT_S5B *j = task; task = nullptr; #ifdef S5B_DEBUG qDebug("jt_finished: state=%s, %s\n", state == Requester ? "requester" : "target", j->success() ? "ok" : "fail"); #endif if (state == Requester) { if (targetMode == Unknown) { targetMode = NotFast; QPointer self = this; emit accepted(); if (!self) return; } } // if we've already reported successfully connecting to them, then this response doesn't matter if (state == Requester && connSuccess) { tryActivation(); return; } if (j->success()) { // stop connecting out if (conn || lateProxy) { delete conn; conn = nullptr; doConnectError(); } Jid streamHost = j->streamHostUsed(); // they connected to us? if (streamHost.compare(self)) { if (client) { if (state == Requester) { activatedStream = streamHost; tryActivation(); } else checkForActivation(); } else { #ifdef S5B_DEBUG qDebug("S5BManager::Item %s claims to have connected to us, but we don't see this\n", qPrintable(peer.full())); #endif resetConnection(); error(ErrWrongHost); } } else if (streamHost.compare(proxy.jid())) { // toss out any direct incoming, since it won't be used delete client; client = nullptr; allowIncoming = false; #ifdef S5B_DEBUG qDebug("attempting to connect to proxy\n"); #endif // connect to the proxy proxy_conn = new S5BConnector; connect(proxy_conn, SIGNAL(result(bool)), SLOT(proxy_result(bool))); StreamHostList list; list += proxy; QPointer self = this; proxyConnect(); if (!self) return; proxy_conn->start(this->self, list, key, udp, 30); } else { #ifdef S5B_DEBUG qDebug("S5BManager::Item %s claims to have connected to a streamhost we never offered\n", qPrintable(peer.full())); #endif resetConnection(); error(ErrWrongHost); } } else { #ifdef S5B_DEBUG qDebug("S5BManager::Item %s [%s] error\n", qPrintable(peer.full()), qPrintable(sid)); #endif remoteFailed = true; statusCode = j->statusCode(); if (lateProxy) { if (!conn) doIncoming(); } else { // if connSuccess is true at this point, then we're a Target if (connSuccess) checkForActivation(); else checkFailure(); } } } void S5BManager::Item::conn_result(bool b) { if (b) { SocksClient *sc = conn->takeClient(); SocksUDP * sc_udp = conn->takeUDP(); StreamHost h = conn->streamHostUsed(); delete conn; conn = nullptr; connSuccess = true; #ifdef S5B_DEBUG qDebug("S5BManager::Item: %s [%s] successful outgoing connection\n", qPrintable(peer.full()), qPrintable(sid)); #endif connect(sc, SIGNAL(readyRead()), SLOT(sc_readyRead())); connect(sc, SIGNAL(bytesWritten(qint64)), SLOT(sc_bytesWritten(qint64))); connect(sc, SIGNAL(error(int)), SLOT(sc_error(int))); m->doSuccess(peer, in_id, h.jid()); // if the first batch works, don't try proxy lateProxy = false; // if requester, run with this one if (state == Requester) { // if we had an incoming one, toss it delete client_udp; client_udp = sc_udp; delete client; client = sc; allowIncoming = false; activatedStream = peer; tryActivation(); } else { client_out_udp = sc_udp; client_out = sc; checkForActivation(); } } else { delete conn; conn = nullptr; // if we delayed the proxies for later, try now if (lateProxy) { if (remoteFailed) doIncoming(); } else doConnectError(); } } void S5BManager::Item::proxy_result(bool b) { #ifdef S5B_DEBUG qDebug("proxy_result: %s\n", b ? "ok" : "fail"); #endif if (b) { SocksClient *sc = proxy_conn->takeClient(); SocksUDP * sc_udp = proxy_conn->takeUDP(); delete proxy_conn; proxy_conn = nullptr; connect(sc, SIGNAL(readyRead()), SLOT(sc_readyRead())); connect(sc, SIGNAL(bytesWritten(qint64)), SLOT(sc_bytesWritten(qint64))); connect(sc, SIGNAL(error(int)), SLOT(sc_error(int))); client = sc; client_udp = sc_udp; // activate #ifdef S5B_DEBUG qDebug("activating proxy stream\n"); #endif proxy_task = new JT_S5B(m->client()->rootTask()); connect(proxy_task, SIGNAL(finished()), SLOT(proxy_finished())); proxy_task->requestActivation(proxy.jid(), sid, peer); proxy_task->go(true); } else { delete proxy_conn; proxy_conn = nullptr; resetConnection(); error(ErrProxy); } } void S5BManager::Item::proxy_finished() { JT_S5B *j = proxy_task; proxy_task = nullptr; if (j->success()) { #ifdef S5B_DEBUG qDebug("proxy stream activated\n"); #endif if (state == Requester) { activatedStream = proxy.jid(); tryActivation(); } else checkForActivation(); } else { resetConnection(); error(ErrProxy); } } void S5BManager::Item::sc_readyRead() { #ifdef S5B_DEBUG qDebug("sc_readyRead\n"); #endif // only targets check for activation, and only should do it if there is no pending outgoing iq-set if (state == Target && !task && !proxy_task) checkForActivation(); } void S5BManager::Item::sc_bytesWritten(qint64) { #ifdef S5B_DEBUG qDebug("sc_bytesWritten\n"); #endif // this should only happen to the requester, and should always be 1 byte (the '\r' sent earlier) finished(); } void S5BManager::Item::sc_error(int) { #ifdef S5B_DEBUG qDebug("sc_error\n"); #endif resetConnection(); error(ErrConnect); } void S5BManager::Item::doConnectError() { localFailed = true; m->doError(peer, in_id, Stanza::Error::RemoteServerNotFound, "Could not connect to given hosts"); checkFailure(); } void S5BManager::Item::tryActivation() { #ifdef S5B_DEBUG qDebug("tryActivation\n"); #endif if (activated) { #ifdef S5B_DEBUG qDebug("already activated !?\n"); #endif return; } if (targetMode == NotFast) { #ifdef S5B_DEBUG qDebug("tryActivation: NotFast\n"); #endif // nothing to activate, we're done finished(); } else if (targetMode == Fast) { // with fast mode, we don't wait for the iq reply, so delete the task (if any) delete task; task = nullptr; activated = true; // if udp, activate using special stanza if (udp) { m->doActivate(peer, sid, activatedStream); } else { #ifdef S5B_DEBUG qDebug("sending extra CR\n"); #endif // must send [CR] to activate target streamhost client->write("\r", 1); } } } void S5BManager::Item::checkForActivation() { QList clientList; if (client) clientList.append(client); if (client_out) clientList.append(client_out); for (SocksClient *sc : clientList) { #ifdef S5B_DEBUG qDebug("checking for activation\n"); #endif if (fast) { bool ok = false; if (udp) { if ((sc == client_out && activatedStream.compare(self)) || (sc == client && !activatedStream.compare(self))) { clientList.removeAll(sc); ok = true; } } else { #ifdef S5B_DEBUG qDebug("need CR\n"); #endif if (sc->bytesAvailable() >= 1) { clientList.removeAll(sc); char c; if (!sc->getChar(&c) || c != '\r') { delete sc; // FIXME breaks S5BManager::Item destructor? return; } ok = true; } } if (ok) { SocksUDP *sc_udp = nullptr; if (sc == client) { delete client_out_udp; client_out_udp = nullptr; sc_udp = client_udp; } else if (sc == client_out) { delete client_udp; client_udp = nullptr; sc_udp = client_out_udp; } sc->disconnect(this); while (!clientList.isEmpty()) { delete clientList.takeFirst(); } client = sc; client_out = nullptr; client_udp = sc_udp; activated = true; #ifdef S5B_DEBUG qDebug("activation success\n"); #endif break; } } else { #ifdef S5B_DEBUG qDebug("not fast mode, no need to wait for anything\n"); #endif clientList.removeAll(sc); sc->disconnect(this); while (!clientList.isEmpty()) { delete clientList.takeFirst(); } client = sc; client_out = nullptr; activated = true; break; } } if (activated) { finished(); } else { // only emit waitingForActivation if there is nothing left to do if ((connSuccess || localFailed) && !proxy_task && !proxy_conn) waitingForActivation(); } } void S5BManager::Item::checkFailure() { bool failed = false; if (state == Requester) { if (remoteFailed) { if ((localFailed && targetMode == Fast) || targetMode == NotFast) failed = true; } } else { if (localFailed) { if ((remoteFailed && fast) || !fast) failed = true; } } if (failed) { if (state == Requester) { resetConnection(); if (statusCode == 404) error(ErrConnect); else error(ErrRefused); } else { resetConnection(); error(ErrConnect); } } } void S5BManager::Item::finished() { client->disconnect(this); state = Active; #ifdef S5B_DEBUG qDebug("S5BManager::Item %s [%s] linked successfully\n", qPrintable(peer.full()), qPrintable(sid)); #endif emit connected(); } //---------------------------------------------------------------------------- // S5BConnector //---------------------------------------------------------------------------- class S5BConnector::Item : public QObject { Q_OBJECT public: SocksClient *client; SocksUDP * client_udp; StreamHost host; QString key; bool udp; int udp_tries; QTimer t; Jid jid; Item(const Jid &self, const StreamHost &_host, const QString &_key, bool _udp) : QObject(nullptr), client(new SocksClient), client_udp(nullptr), host(_host), key(_key), udp(_udp), udp_tries(0), jid(self) { connect(client, SIGNAL(connected()), SLOT(sc_connected())); connect(client, SIGNAL(error(int)), SLOT(sc_error(int))); connect(&t, SIGNAL(timeout()), SLOT(trySendUDP())); } ~Item() { cleanup(); } void start() { client->connectToHost(host.host(), host.port(), key, 0, udp); } void udpSuccess() { t.stop(); client_udp->change(key, 0); // flip over to the data port success(); } signals: void result(bool); private slots: void sc_connected() { // if udp, need to send init packet before we are good if (udp) { // port 1 is init client_udp = client->createUDP(key, 1, client->peerAddress(), client->peerPort()); udp_tries = 0; t.start(5000); trySendUDP(); return; } success(); } void sc_error(int) { #ifdef S5B_DEBUG qDebug("S5BConnector[%s]: error\n", qPrintable(host.host())); #endif cleanup(); result(false); } void trySendUDP() { if (udp_tries == 5) { t.stop(); cleanup(); result(false); return; } // send initialization with our JID QByteArray a(jid.full().toUtf8()); client_udp->write(a); ++udp_tries; } private: void cleanup() { delete client_udp; client_udp = nullptr; delete client; client = nullptr; } void success() { #ifdef S5B_DEBUG qDebug("S5BConnector[%s]: success\n", qPrintable(host.host())); #endif client->disconnect(this); result(true); } }; class S5BConnector::Private { public: SocksClient * active; SocksUDP * active_udp; QList itemList; QString key; StreamHost activeHost; QTimer t; }; S5BConnector::S5BConnector(QObject *parent) : QObject(parent) { d = new Private; d->active = nullptr; d->active_udp = nullptr; connect(&d->t, SIGNAL(timeout()), SLOT(t_timeout())); } S5BConnector::~S5BConnector() { resetConnection(); delete d; } void S5BConnector::resetConnection() { d->t.stop(); delete d->active_udp; d->active_udp = nullptr; delete d->active; d->active = nullptr; while (!d->itemList.empty()) { delete d->itemList.takeFirst(); } } void S5BConnector::start(const Jid &self, const StreamHostList &hosts, const QString &key, bool udp, int timeout) { resetConnection(); #ifdef S5B_DEBUG qDebug("S5BConnector: starting [%p]!\n", this); #endif for (StreamHostList::ConstIterator it = hosts.begin(); it != hosts.end(); ++it) { Item *i = new Item(self, *it, key, udp); connect(i, SIGNAL(result(bool)), SLOT(item_result(bool))); d->itemList.append(i); i->start(); } d->t.start(timeout * 1000); } SocksClient *S5BConnector::takeClient() { SocksClient *c = d->active; d->active = nullptr; return c; } SocksUDP *S5BConnector::takeUDP() { SocksUDP *c = d->active_udp; d->active_udp = nullptr; return c; } StreamHost S5BConnector::streamHostUsed() const { return d->activeHost; } void S5BConnector::item_result(bool b) { Item *i = static_cast(sender()); if (b) { d->active = i->client; i->client = nullptr; d->active_udp = i->client_udp; i->client_udp = nullptr; d->activeHost = i->host; while (!d->itemList.isEmpty()) { delete d->itemList.takeFirst(); } d->t.stop(); #ifdef S5B_DEBUG qDebug("S5BConnector: complete! [%p]\n", this); #endif emit result(true); } else { d->itemList.removeAll(i); delete i; if (d->itemList.isEmpty()) { d->t.stop(); #ifdef S5B_DEBUG qDebug("S5BConnector: failed! [%p]\n", this); #endif emit result(false); } } } void S5BConnector::t_timeout() { resetConnection(); #ifdef S5B_DEBUG qDebug("S5BConnector: failed! (timeout)\n"); #endif result(false); } void S5BConnector::man_udpSuccess(const Jid &streamHost) { // was anyone sending to this streamhost? for (Item *i : d->itemList) { if (i->host.jid().compare(streamHost) && i->client_udp) { i->udpSuccess(); return; } } } //---------------------------------------------------------------------------- // JT_S5B //---------------------------------------------------------------------------- class JT_S5B::Private { public: QDomElement iq; Jid to; Jid streamHost; StreamHost proxyInfo; int mode; QTimer t; }; JT_S5B::JT_S5B(Task *parent) : Task(parent) { d = new Private; d->mode = -1; connect(&d->t, SIGNAL(timeout()), SLOT(t_timeout())); } JT_S5B::~JT_S5B() { delete d; } void JT_S5B::request(const Jid &to, const QString &sid, const QString &dstaddr, const StreamHostList &hosts, bool fast, bool udp) { d->mode = 0; QDomElement iq; d->to = to; iq = createIQ(doc(), "set", to.full(), id()); QDomElement query = doc()->createElementNS(S5B_NS, "query"); query.setAttribute("sid", sid); if (!client()->groupChatNick(to.domain(), to.node()).isEmpty()) { query.setAttribute("dstaddr", dstaddr); // special case for muc as in xep-0065rc3 } query.setAttribute("mode", udp ? "udp" : "tcp"); iq.appendChild(query); for (StreamHostList::ConstIterator it = hosts.begin(); it != hosts.end(); ++it) { QDomElement shost = doc()->createElement("streamhost"); shost.setAttribute("jid", (*it).jid().full()); shost.setAttribute("host", (*it).host()); shost.setAttribute("port", QString::number((*it).port())); if ((*it).isProxy()) { QDomElement p = doc()->createElementNS("http://affinix.com/jabber/stream", "proxy"); shost.appendChild(p); } query.appendChild(shost); } if (fast) { QDomElement e = doc()->createElementNS("http://affinix.com/jabber/stream", "fast"); query.appendChild(e); } d->iq = iq; } void JT_S5B::requestProxyInfo(const Jid &to) { d->mode = 1; QDomElement iq; d->to = to; iq = createIQ(doc(), "get", to.full(), id()); QDomElement query = doc()->createElementNS(S5B_NS, "query"); iq.appendChild(query); d->iq = iq; } void JT_S5B::requestActivation(const Jid &to, const QString &sid, const Jid &target) { d->mode = 2; QDomElement iq; d->to = to; iq = createIQ(doc(), "set", to.full(), id()); QDomElement query = doc()->createElementNS(S5B_NS, "query"); query.setAttribute("sid", sid); iq.appendChild(query); QDomElement act = doc()->createElement("activate"); act.appendChild(doc()->createTextNode(target.full())); query.appendChild(act); d->iq = iq; } void JT_S5B::onGo() { if (d->mode == 1) { d->t.setSingleShot(true); d->t.start(15000); } send(d->iq); } void JT_S5B::onDisconnect() { d->t.stop(); } bool JT_S5B::take(const QDomElement &x) { if (d->mode == -1) return false; if (!iqVerify(x, d->to, id())) return false; d->t.stop(); if (x.attribute("type") == "result") { QDomElement q = queryTag(x); if (d->mode == 0) { d->streamHost = ""; if (!q.isNull()) { QDomElement shost = q.elementsByTagName("streamhost-used").item(0).toElement(); if (!shost.isNull()) d->streamHost = shost.attribute("jid"); } setSuccess(); } else if (d->mode == 1) { if (!q.isNull()) { QDomElement shost = q.elementsByTagName("streamhost").item(0).toElement(); if (!shost.isNull()) { Jid j = shost.attribute("jid"); if (j.isValid()) { QString host = shost.attribute("host"); if (!host.isEmpty()) { int port = shost.attribute("port").toInt(); StreamHost h; h.setJid(j); h.setHost(host); h.setPort(port); h.setIsProxy(true); d->proxyInfo = h; } } } } setSuccess(); } else { setSuccess(); } } else { setError(x); } return true; } void JT_S5B::t_timeout() { d->mode = -1; setError(500, "Timed out"); } Jid JT_S5B::streamHostUsed() const { return d->streamHost; } StreamHost JT_S5B::proxyInfo() const { return d->proxyInfo; } //---------------------------------------------------------------------------- // JT_PushS5B //---------------------------------------------------------------------------- JT_PushS5B::JT_PushS5B(Task *parent) : Task(parent) { } JT_PushS5B::~JT_PushS5B() { } int JT_PushS5B::priority() const { return 1; } bool JT_PushS5B::take(const QDomElement &e) { // look for udpsuccess if (e.tagName() == "message") { QDomElement x = e.elementsByTagName("udpsuccess").item(0).toElement(); if (!x.isNull() && x.namespaceURI() == S5B_NS) { incomingUDPSuccess(Jid(x.attribute("from")), x.attribute("dstaddr")); return true; } x = e.elementsByTagName("activate").item(0).toElement(); if (!x.isNull() && x.namespaceURI() == "http://affinix.com/jabber/stream") { incomingActivate(Jid(x.attribute("from")), x.attribute("sid"), Jid(x.attribute("jid"))); return true; } return false; } // must be an iq-set tag if (e.tagName() != "iq") return false; if (e.attribute("type") != "set") return false; if (queryNS(e) != S5B_NS) return false; Jid from(e.attribute("from")); QDomElement q = queryTag(e); QString sid = q.attribute("sid"); StreamHostList hosts; QDomNodeList nl = q.elementsByTagName("streamhost"); for (int n = 0; n < nl.count(); ++n) { QDomElement shost = nl.item(n).toElement(); if (hosts.count() < MAXSTREAMHOSTS) { Jid j = shost.attribute("jid"); if (!j.isValid()) continue; QString host = shost.attribute("host"); if (host.isEmpty()) continue; int port = shost.attribute("port").toInt(); QDomElement p = shost.elementsByTagName("proxy").item(0).toElement(); bool isProxy = false; if (!p.isNull() && p.namespaceURI() == "http://affinix.com/jabber/stream") isProxy = true; StreamHost h; h.setJid(j); h.setHost(host); h.setPort(port); h.setIsProxy(isProxy); hosts += h; } } bool fast = false; QDomElement t; t = q.elementsByTagName("fast").item(0).toElement(); if (!t.isNull() && t.namespaceURI() == "http://affinix.com/jabber/stream") fast = true; S5BRequest r; r.from = from; r.id = e.attribute("id"); r.sid = sid; r.dstaddr = q.attribute("dstaddr"); // special case for muc as in xep-0065rc3 r.hosts = hosts; r.fast = fast; r.udp = q.attribute("mode") == "udp" ? true : false; emit incoming(r); return true; } void JT_PushS5B::respondSuccess(const Jid &to, const QString &id, const Jid &streamHost) { QDomElement iq = createIQ(doc(), "result", to.full(), id); QDomElement query = doc()->createElementNS(S5B_NS, "query"); iq.appendChild(query); QDomElement shost = doc()->createElement("streamhost-used"); shost.setAttribute("jid", streamHost.full()); query.appendChild(shost); send(iq); } void JT_PushS5B::respondError(const Jid &to, const QString &id, Stanza::Error::ErrorCond cond, const QString &str) { QDomElement iq = createIQ(doc(), "error", to.full(), id); Stanza::Error error(Stanza::Error::Cancel, cond, str); iq.appendChild(error.toXml(*client()->doc(), client()->stream().baseNS())); send(iq); } void JT_PushS5B::sendUDPSuccess(const Jid &to, const QString &dstaddr) { QDomElement m = doc()->createElement("message"); m.setAttribute("to", to.full()); QDomElement u = doc()->createElementNS(S5B_NS, "udpsuccess"); u.setAttribute("dstaddr", dstaddr); m.appendChild(u); send(m); } void JT_PushS5B::sendActivate(const Jid &to, const QString &sid, const Jid &streamHost) { QDomElement m = doc()->createElement("message"); m.setAttribute("to", to.full()); QDomElement act = doc()->createElementNS("http://affinix.com/jabber/stream", "activate"); act.setAttribute("sid", sid); act.setAttribute("jid", streamHost.full()); m.appendChild(act); send(m); } //---------------------------------------------------------------------------- // StreamHost //---------------------------------------------------------------------------- StreamHost::StreamHost() { v_port = -1; proxy = false; } const Jid &StreamHost::jid() const { return j; } const QString &StreamHost::host() const { return v_host; } int StreamHost::port() const { return v_port; } bool StreamHost::isProxy() const { return proxy; } void StreamHost::setJid(const Jid &_j) { j = _j; } void StreamHost::setHost(const QString &host) { v_host = host; } void StreamHost::setPort(int port) { v_port = port; } void StreamHost::setIsProxy(bool b) { proxy = b; } //---------------------------------------------------------------------------- // S5BServersProducer //---------------------------------------------------------------------------- TcpPortServer *S5BServersProducer::makeServer(QTcpServer *socket) { return new S5BServer(socket); } //---------------------------------------------------------------------------- // S5BIncomingConnection //---------------------------------------------------------------------------- class S5BIncomingConnection : public QObject { Q_OBJECT public: SocksClient *client; QString host; QTimer expire; S5BIncomingConnection(SocksClient *c) : QObject(nullptr) { client = c; connect(client, &SocksClient::incomingMethods, [this](int methods) { if (methods & SocksClient::AuthNone) client->chooseMethod(SocksClient::AuthNone); else doError(); }); connect(client, &SocksClient::incomingConnectRequest, [this](const QString &_host, int port) { if (port == 0) { host = _host; client->disconnect(this); expire.stop(); emit result(true); } else doError(); }); connect(client, &SocksClient::error, [this]() { doError(); }); connect(&expire, SIGNAL(timeout()), SLOT(doError())); resetExpiration(); } ~S5BIncomingConnection() { delete client; } void resetExpiration() { expire.start(30000); } signals: void result(bool); private slots: void doError() { expire.stop(); delete client; client = nullptr; result(false); } }; //---------------------------------------------------------------------------- // S5BServer //---------------------------------------------------------------------------- struct S5BServer::Private { SocksServer serv; QSet keys; }; S5BServer::S5BServer(QTcpServer *serverSocket) : TcpPortServer(serverSocket), d(new Private) { d->serv.setServerSocket(serverSocket); connect(&d->serv, &SocksServer::incomingReady, this, [this]() { S5BIncomingConnection *inConn = new S5BIncomingConnection(d->serv.takeIncoming()); #ifdef S5B_DEBUG qDebug("S5BServer: incoming connection from %s:%d\n", qPrintable(inConn->client->peerAddress().toString()), inConn->client->peerPort()); #endif connect(inConn, &S5BIncomingConnection::result, this, [this, inConn](bool success) { #ifdef S5B_DEBUG qDebug("S5BServer item result: %d\n", success); #endif if (!success) { delete inConn; return; } SocksClient *c = inConn->client; inConn->client = nullptr; QString key = inConn->host; delete inConn; emit incomingConnection(c, key); if (!c->isOpen()) { delete c; } }); }); connect(&d->serv, &SocksServer::incomingUDP, this, [this](const QString &host, int port, const QHostAddress &addr, int sourcePort, const QByteArray &data) { if (port != 0 && port != 1) return; bool isInit = port == 1; emit incomingUdp(isInit, addr, sourcePort, host, data); }); } S5BServer::~S5BServer() { // basically to make QScopedPointer happy } void S5BServer::writeUDP(const QHostAddress &addr, int port, const QByteArray &data) { d->serv.writeUDP(addr, port, data); } bool S5BServer::isActive() const { return d->serv.isActive(); } bool S5BServer::hasKey(const QString &key) { return d->keys.contains(key); } void S5BServer::registerKey(const QString &key) { d->keys.insert(key); } void S5BServer::unregisterKey(const QString &key) { d->keys.remove(key); } } // namespace XMPP #include "s5b.moc" psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/s5b.h000066400000000000000000000224041370065651000225270ustar00rootroot00000000000000/* * s5b.h - direct connection protocol via tcp * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_S5B_H #define XMPP_S5B_H #include "bytestream.h" #include "xmpp/jid/jid.h" #include "xmpp_bytestream.h" #include "xmpp_stanza.h" #include "xmpp_task.h" #include #include #include #include class SocksClient; class SocksUDP; namespace XMPP { namespace Jingle { namespace S5B { class Manager; }} class Client; class JT_PushS5B; class S5BConnection; class S5BManager; class StreamHost; class TcpPortReserver; struct S5BRequest; typedef QList StreamHostList; typedef QList S5BConnectionList; class S5BDatagram { public: S5BDatagram(); S5BDatagram(int source, int dest, const QByteArray &data); int sourcePort() const; int destPort() const; QByteArray data() const; private: int _source = 0; int _dest = 0; QByteArray _buf; }; class S5BConnection : public BSConnection { Q_OBJECT public: enum Mode { Stream, Datagram }; ~S5BConnection(); Jid proxy() const; void setProxy(const Jid &proxy); void connectToJid(const Jid &peer, const QString &sid, Mode m = Stream); void connectToJid(const Jid &peer, const QString &sid) { connectToJid(peer, sid, Stream); } void accept(); void close(); Jid peer() const; QString sid() const; BytestreamManager *manager() const; bool isRemote() const; Mode mode() const; int state() const; qint64 bytesAvailable() const; qint64 bytesToWrite() const; void writeDatagram(const S5BDatagram &); S5BDatagram readDatagram(); int datagramsAvailable() const; protected: qint64 writeData(const char *data, qint64 maxSize); qint64 readData(char *data, qint64 maxSize); signals: void proxyQuery(); // querying proxy for streamhost information void proxyResult(bool b); // query success / fail void requesting(); // sent actual S5B request (requester only) void accepted(); // target accepted (requester only void tryingHosts(const StreamHostList &hosts); // currently connecting to these hosts void proxyConnect(); // connecting to proxy void waitingForActivation(); // waiting for activation (target only) void connected(); // connection active void datagramReady(); private slots: void doPending(); void sc_connectionClosed(); void sc_delayedCloseFinished(); void sc_readyRead(); void sc_bytesWritten(qint64); void sc_error(int); void su_packetReady(const QByteArray &buf); private: class Private; Private *d; void resetConnection(bool clear = false); void handleUDP(const QByteArray &buf); void sendUDP(const QByteArray &buf); friend class S5BManager; void man_waitForAccept(const S5BRequest &r); void man_clientReady(SocksClient *, SocksUDP *); void man_udpReady(const QByteArray &buf); void man_failed(int); S5BConnection(S5BManager *, QObject *parent = nullptr); }; class S5BManager : public BytestreamManager { Q_OBJECT public: S5BManager(Client *); ~S5BManager(); static const char *ns(); Client * client() const; JT_PushS5B * jtPush() const; bool isAcceptableSID(const Jid &peer, const QString &sid) const; BSConnection * createConnection(); S5BConnection *takeIncoming(); class Item; class Entry; protected: const char *sidPrefix() const; private slots: void ps_incoming(const S5BRequest &req); void ps_incomingUDPSuccess(const Jid &from, const QString &dstaddr); void ps_incomingActivate(const Jid &from, const QString &sid, const Jid &streamHost); void item_accepted(); void item_tryingHosts(const StreamHostList &list); void item_proxyConnect(); void item_waitingForActivation(); void item_connected(); void item_error(int); void query_finished(); private: class Private; Private *d; S5BConnection *findIncoming(const Jid &from, const QString &sid) const; Entry * findEntry(S5BConnection *) const; Entry * findEntry(Item *) const; Entry * findEntryByHash(const QString &key) const; Entry * findEntryBySID(const Jid &peer, const QString &sid) const; void entryContinue(Entry *e); void queryProxy(Entry *e); bool targetShouldOfferProxy(Entry *e); friend class S5BConnection; void con_connect(S5BConnection *); void con_accept(S5BConnection *); void con_reject(S5BConnection *); void con_unlink(S5BConnection *); void con_sendUDP(S5BConnection *, const QByteArray &buf); friend class S5BServer; bool srv_ownsHash(const QString &key) const; void srv_incomingReady(SocksClient *sc, const QString &key); void srv_incomingUDP(bool init, const QHostAddress &addr, int port, const QString &key, const QByteArray &data); friend class Item; void doSuccess(const Jid &peer, const QString &id, const Jid &streamHost); void doError(const Jid &peer, const QString &id, Stanza::Error::ErrorCond, const QString &); void doActivate(const Jid &peer, const QString &sid, const Jid &streamHost); }; class S5BConnector : public QObject { Q_OBJECT public: S5BConnector(QObject *parent = nullptr); ~S5BConnector(); void resetConnection(); void start(const Jid &self, const StreamHostList &hosts, const QString &key, bool udp, int timeout); SocksClient *takeClient(); SocksUDP * takeUDP(); StreamHost streamHostUsed() const; class Item; signals: void result(bool); private slots: void item_result(bool); void t_timeout(); private: class Private; Private *d; friend class S5BManager; void man_udpSuccess(const Jid &streamHost); }; class S5BServersProducer : public TcpPortScope { protected: TcpPortServer *makeServer(QTcpServer *socket); // in fact returns S5BServer }; class S5BServer : public TcpPortServer { Q_OBJECT public: S5BServer(QTcpServer *serverSocket); ~S5BServer(); void writeUDP(const QHostAddress &addr, int port, const QByteArray &data); bool isActive() const; bool hasKey(const QString &key); void registerKey(const QString &key); void unregisterKey(const QString &key); signals: void incomingConnection(SocksClient *c, const QString &key); void incomingUdp(bool isInit, const QHostAddress &addr, int sourcePort, const QString &key, const QByteArray &data); private: class Private; QScopedPointer d; }; class JT_S5B : public Task { Q_OBJECT public: JT_S5B(Task *); ~JT_S5B(); void request(const Jid &to, const QString &sid, const QString &dstaddr, const StreamHostList &hosts, bool fast, bool udp = false); void requestProxyInfo(const Jid &to); void requestActivation(const Jid &to, const QString &sid, const Jid &target); void onGo(); void onDisconnect(); bool take(const QDomElement &); Jid streamHostUsed() const; StreamHost proxyInfo() const; private slots: void t_timeout(); private: class Private; Private *d; }; struct S5BRequest { Jid from; QString id, sid, dstaddr; StreamHostList hosts; bool fast; bool udp; }; class JT_PushS5B : public Task { Q_OBJECT public: JT_PushS5B(Task *); ~JT_PushS5B(); int priority() const; void respondSuccess(const Jid &to, const QString &id, const Jid &streamHost); void respondError(const Jid &to, const QString &id, Stanza::Error::ErrorCond cond, const QString &str); void sendUDPSuccess(const Jid &to, const QString &dstaddr); void sendActivate(const Jid &to, const QString &sid, const Jid &streamHost); bool take(const QDomElement &); signals: void incoming(const S5BRequest &req); void incomingUDPSuccess(const Jid &from, const QString &dstaddr); void incomingActivate(const Jid &from, const QString &sid, const Jid &streamHost); }; class StreamHost { public: StreamHost(); const Jid & jid() const; const QString &host() const; int port() const; bool isProxy() const; void setJid(const Jid &); void setHost(const QString &); void setPort(int); void setIsProxy(bool); private: Jid j; QString v_host; int v_port; bool proxy; }; } // namespace XMPP #endif // XMPP_S5B_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/types.cpp000066400000000000000000002704741370065651000235510ustar00rootroot00000000000000/* * types.cpp - IM data types * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "im.h" #include "protocol.h" #include "xmpp/blake2/blake2qt.h" #include "xmpp_bitsofbinary.h" #include "xmpp_captcha.h" #include "xmpp_features.h" #include "xmpp_ibb.h" #include "xmpp_reference.h" #include "xmpp_xmlcommon.h" #include #include #include #define NS_XML "http://www.w3.org/XML/1998/namespace" namespace XMPP { QString HASH_NS = QStringLiteral("urn:xmpp:hashes:2"); //---------------------------------------------------------------------------- // Url //---------------------------------------------------------------------------- class Url::Private { public: QString url; QString desc; }; //! \brief Construct Url object with a given URL and Description. //! //! This function will construct a Url object. //! \param QString - url (default: empty string) //! \param QString - description of url (default: empty string) //! \sa setUrl() setDesc() Url::Url(const QString &url, const QString &desc) { d = new Private; d->url = url; d->desc = desc; } //! \brief Construct Url object. //! //! Overloaded constructor which will constructs a exact copy of the Url object that was passed to the constructor. //! \param Url - Url Object Url::Url(const Url &from) { d = new Private; *this = from; } //! \brief operator overloader needed for d pointer (Internel). Url &Url::operator=(const Url &from) { *d = *from.d; return *this; } //! \brief destroy Url object. Url::~Url() { delete d; } //! \brief Get url information. //! //! Returns url information. QString Url::url() const { return d->url; } //! \brief Get Description information. //! //! Returns desction of the URL. QString Url::desc() const { return d->desc; } //! \brief Set Url information. //! //! Set url information. //! \param url - url string (eg: http://psi.affinix.com/) void Url::setUrl(const QString &url) { d->url = url; } //! \brief Set Description information. //! //! Set description of the url. //! \param desc - description of url void Url::setDesc(const QString &desc) { d->desc = desc; } //---------------------------------------------------------------------------- // Address //---------------------------------------------------------------------------- //! \brief Construct Address object with a given Type and Jid. //! //! This function will construct a Address object. //! \param Type - type (default: Unknown) //! \param Jid - specify address (default: empty string) //! \sa setType() setJid() Address::Address(Type type, const Jid &jid) : v_jid(jid), v_delivered(false), v_type(type) { } Address::Address(const QDomElement &e) : v_delivered(false) { fromXml(e); } void Address::fromXml(const QDomElement &t) { setJid(t.attribute("jid")); setUri(t.attribute("uri")); setNode(t.attribute("node")); setDesc(t.attribute("desc")); setDelivered(t.attribute("delivered") == "true"); QString type = t.attribute("type"); if (type == "to") setType(To); else if (type == "cc") setType(Cc); else if (type == "bcc") setType(Bcc); else if (type == "replyto") setType(ReplyTo); else if (type == "replyroom") setType(ReplyRoom); else if (type == "noreply") setType(NoReply); else if (type == "ofrom") setType(OriginalFrom); else if (type == "oto") setType(OriginalTo); } QDomElement Address::toXml(Stanza &s) const { QDomElement e = s.createElement("http://jabber.org/protocol/address", "address"); if (!jid().isEmpty()) e.setAttribute("jid", jid().full()); if (!uri().isEmpty()) e.setAttribute("uri", uri()); if (!node().isEmpty()) e.setAttribute("node", node()); if (!desc().isEmpty()) e.setAttribute("desc", desc()); if (delivered()) e.setAttribute("delivered", "true"); switch (type()) { case To: e.setAttribute("type", "to"); break; case Cc: e.setAttribute("type", "cc"); break; case Bcc: e.setAttribute("type", "bcc"); break; case ReplyTo: e.setAttribute("type", "replyto"); break; case ReplyRoom: e.setAttribute("type", "replyroom"); break; case NoReply: e.setAttribute("type", "noreply"); break; case OriginalFrom: e.setAttribute("type", "ofrom"); break; case OriginalTo: e.setAttribute("type", "oto"); break; case Unknown: // Add nothing break; } return e; } //! \brief Get Jid information. //! //! Returns jid information. const Jid &Address::jid() const { return v_jid; } //! \brief Get Uri information. //! //! Returns desction of the Address. const QString &Address::uri() const { return v_uri; } //! \brief Get Node information. //! //! Returns node of the Address. const QString &Address::node() const { return v_node; } //! \brief Get Description information. //! //! Returns desction of the Address. const QString &Address::desc() const { return v_desc; } //! \brief Get Delivered information. //! //! Returns delivered of the Address. bool Address::delivered() const { return v_delivered; } //! \brief Get Type information. //! //! Returns type of the Address. Address::Type Address::type() const { return v_type; } //! \brief Set Address information. //! //! Set jid information. //! \param jid - jid void Address::setJid(const Jid &jid) { v_jid = jid; } //! \brief Set Address information. //! //! Set uri information. //! \param uri - url string (eg: http://psi.affinix.com/) void Address::setUri(const QString &uri) { v_uri = uri; } //! \brief Set Node information. //! //! Set node information. //! \param node - node string void Address::setNode(const QString &node) { v_node = node; } //! \brief Set Description information. //! //! Set description of the url. //! \param desc - description of url void Address::setDesc(const QString &desc) { v_desc = desc; } //! \brief Set delivered information. //! //! Set delivered information. //! \param delivered - delivered flag void Address::setDelivered(bool delivered) { v_delivered = delivered; } //! \brief Set Type information. //! //! Set type information. //! \param type - type void Address::setType(Type type) { v_type = type; } //---------------------------------------------------------------------------- // Hash //---------------------------------------------------------------------------- static const char *const sha1_synonims[] = { "sha1", nullptr }; // NOTE: keep this in sync with enum. same order! static const struct { const char * text; Hash::Type hashType; const char *const *synonims = nullptr; } hashTypes[] = { { "sha-1", Hash::Type::Sha1, sha1_synonims }, { "sha-256", Hash::Type::Sha256 }, { "sha-512", Hash::Type::Sha512 }, { "sha3-256", Hash::Type::Sha3_256 }, { "sha3-512", Hash::Type::Sha3_512 }, { "blake2b-256", Hash::Type::Blake2b256 }, { "blake2b-512", Hash::Type::Blake2b512 } }; Hash::Hash(const QDomElement &el) { QString algo = el.attribute(QLatin1String("algo")); v_type = parseType(QStringRef(&algo)); if (v_type != Unknown && el.tagName() == QLatin1String("hash")) { v_data = QByteArray::fromBase64(el.text().toLatin1()); if (v_data.isEmpty()) { v_type = Type::Unknown; } } } QString Hash::stringType() const { if (!v_type) return QString(); static_assert(LastType == (sizeof(hashTypes) / sizeof(hashTypes[0])), "hashType and enum are not in sync"); return QLatin1String(hashTypes[int(v_type) - 1].text); } Hash::Type Hash::parseType(const QStringRef &algo) { if (!algo.isEmpty()) { for (size_t n = 0; n < sizeof(hashTypes) / sizeof(hashTypes[0]); ++n) { if (algo == QLatin1String(hashTypes[n].text)) { return hashTypes[n].hashType; } if (hashTypes[n].synonims) { auto cur = hashTypes[n].synonims; while (*cur) { if (algo == QLatin1String(*cur)) { return hashTypes[n].hashType; } cur++; } } } } return Unknown; } bool Hash::compute(const QByteArray &ba) { v_data.clear(); switch (v_type) { case Type::Sha1: v_data = QCryptographicHash::hash(ba, QCryptographicHash::Sha1); break; case Type::Sha256: v_data = QCryptographicHash::hash(ba, QCryptographicHash::Sha256); break; case Type::Sha512: v_data = QCryptographicHash::hash(ba, QCryptographicHash::Sha512); break; case Type::Sha3_256: v_data = QCryptographicHash::hash(ba, QCryptographicHash::Sha3_256); break; case Type::Sha3_512: v_data = QCryptographicHash::hash(ba, QCryptographicHash::Sha3_512); break; case Type::Blake2b256: v_data = computeBlake2Hash(ba, Blake2Digest256); break; case Type::Blake2b512: v_data = computeBlake2Hash(ba, Blake2Digest512); break; case Type::Unknown: default: qDebug("invalid hash type"); return false; } return !v_data.isEmpty(); } bool Hash::compute(QIODevice *dev) { QString qcaType; QCryptographicHash::Algorithm qtType = QCryptographicHash::Algorithm(-1); Blake2DigestSize blakeDS = Blake2DigestSize(-1); v_data.clear(); switch (v_type) { case Type::Sha1: qtType = QCryptographicHash::Sha1; qcaType = "sha1"; break; case Type::Sha256: qtType = QCryptographicHash::Sha256; qcaType = "sha256"; break; case Type::Sha512: qtType = QCryptographicHash::Sha512; qcaType = "sha512"; break; case Type::Sha3_256: qtType = QCryptographicHash::Sha3_256; qcaType = "sha3_256"; break; case Type::Sha3_512: qtType = QCryptographicHash::Sha3_512; qcaType = "sha3_512"; break; case Type::Blake2b256: qcaType = "blake2b_256"; blakeDS = Blake2Digest256; break; case Type::Blake2b512: qcaType = "blake2b_512"; blakeDS = Blake2Digest512; break; case Type::Unknown: default: qDebug("invalid hash type"); return false; } if (!qcaType.isEmpty()) { QCA::Hash hashObj(qcaType); if (hashObj.context()) { hashObj.update(dev); v_data = hashObj.final().toByteArray(); if (!v_data.isEmpty()) return true; } } if (qtType != QCryptographicHash::Algorithm(-1)) { QCryptographicHash h(QCryptographicHash::Sha1); h.addData(dev); v_data = h.result(); } else if (blakeDS != Blake2DigestSize(-1)) { v_data = computeBlake2Hash(dev, blakeDS); } return !v_data.isEmpty(); } QDomElement Hash::toXml(QDomDocument *doc) const { if (v_type != Type::Unknown) { for (size_t n = 0; n < sizeof(hashTypes) / sizeof(hashTypes[0]); ++n) { if (v_type == hashTypes[n].hashType) { auto el = doc->createElementNS(HASH_NS, QLatin1String(v_data.isEmpty() ? "hash-used" : "hash")); el.setAttribute(QLatin1String("algo"), QLatin1String(hashTypes[n].text)); if (!v_data.isEmpty()) { XMLHelper::setTagText(el, v_data.toBase64()); } return el; } } } return QDomElement(); } void Hash::populateFeatures(Features &features) { features.addFeature("urn:xmpp:hashes:2"); for (size_t n = 0; n < sizeof(hashTypes) / sizeof(hashTypes[0]); ++n) { features.addFeature(QLatin1String("urn:xmpp:hash-function-text-names:") + QLatin1String(hashTypes[n].text)); } } Hash Hash::from(XMPP::Hash::Type t, const QByteArray &fileData) { Hash h(t); if (!h.compute(fileData)) h.setType(Unknown); return h; } Hash Hash::from(XMPP::Hash::Type t, QIODevice *dev) { Hash h(t); if (!h.compute(dev)) h.setType(Unknown); return h; } Hash Hash::from(Hash::Type t, const QFileInfo &file) { if (file.isReadable()) { QFile f(file.filePath()); f.open(QIODevice::ReadOnly); return from(t, &f); } return Hash(); } Hash Hash::from(const QStringRef &str) { auto ind = str.indexOf('+'); if (ind <= 0) return Hash(); Hash hash(str.left(ind)); if (hash.isValid()) { auto data = QByteArray::fromHex(str.mid(ind + 1).toLatin1()); if (data.size()) hash.setData(data); else hash = Hash(); } return hash; } //---------------------------------------------------------------------------- // RosterExchangeItem //---------------------------------------------------------------------------- RosterExchangeItem::RosterExchangeItem(const Jid &jid, const QString &name, const QStringList &groups, Action action) : jid_(jid), name_(name), groups_(groups), action_(action) { } RosterExchangeItem::RosterExchangeItem(const QDomElement &el) : action_(Add) { fromXml(el); } const Jid &RosterExchangeItem::jid() const { return jid_; } RosterExchangeItem::Action RosterExchangeItem::action() const { return action_; } const QString &RosterExchangeItem::name() const { return name_; } const QStringList &RosterExchangeItem::groups() const { return groups_; } bool RosterExchangeItem::isNull() const { return jid_.isEmpty(); } void RosterExchangeItem::setJid(const Jid &jid) { jid_ = jid; } void RosterExchangeItem::setAction(Action action) { action_ = action; } void RosterExchangeItem::setName(const QString &name) { name_ = name; } void RosterExchangeItem::setGroups(const QStringList &groups) { groups_ = groups; } QDomElement RosterExchangeItem::toXml(Stanza &s) const { QDomElement e = s.createElement("http://jabber.org/protocol/rosterx", "item"); e.setAttribute("jid", jid().full()); if (!name().isEmpty()) e.setAttribute("name", name()); switch (action()) { case Add: e.setAttribute("action", "add"); break; case Delete: e.setAttribute("action", "delete"); break; case Modify: e.setAttribute("action", "modify"); break; } for (const QString &group : groups_) { e.appendChild(s.createTextElement("http://jabber.org/protocol/rosterx", "group", group)); } return e; } void RosterExchangeItem::fromXml(const QDomElement &e) { setJid(e.attribute("jid")); setName(e.attribute("name")); if (e.attribute("action") == "delete") { setAction(Delete); } else if (e.attribute("action") == "modify") { setAction(Modify); } else { setAction(Add); } QDomNodeList nl = e.childNodes(); for (int n = 0; n < nl.count(); ++n) { QDomElement g = nl.item(n).toElement(); if (!g.isNull() && g.tagName() == "group") { groups_ += g.text(); } } } //---------------------------------------------------------------------------- // MUCItem //---------------------------------------------------------------------------- MUCItem::MUCItem(Role r, Affiliation a) : affiliation_(a), role_(r) { } MUCItem::MUCItem(const QDomElement &el) : affiliation_(UnknownAffiliation), role_(UnknownRole) { fromXml(el); } void MUCItem::setNick(const QString &n) { nick_ = n; } void MUCItem::setJid(const Jid &j) { jid_ = j; } void MUCItem::setAffiliation(Affiliation a) { affiliation_ = a; } void MUCItem::setRole(Role r) { role_ = r; } void MUCItem::setActor(const Jid &a) { actor_ = a; } void MUCItem::setReason(const QString &r) { reason_ = r; } const QString &MUCItem::nick() const { return nick_; } const Jid &MUCItem::jid() const { return jid_; } MUCItem::Affiliation MUCItem::affiliation() const { return affiliation_; } MUCItem::Role MUCItem::role() const { return role_; } const Jid &MUCItem::actor() const { return actor_; } const QString &MUCItem::reason() const { return reason_; } void MUCItem::fromXml(const QDomElement &e) { if (e.tagName() != QLatin1String("item")) return; jid_ = Jid(e.attribute("jid")); nick_ = e.attribute(QLatin1String("nick")); // Affiliation if (e.attribute(QLatin1String("affiliation")) == QLatin1String("owner")) { affiliation_ = Owner; } else if (e.attribute(QLatin1String("affiliation")) == QLatin1String("admin")) { affiliation_ = Admin; } else if (e.attribute(QLatin1String("affiliation")) == QLatin1String("member")) { affiliation_ = Member; } else if (e.attribute(QLatin1String("affiliation")) == QLatin1String("outcast")) { affiliation_ = Outcast; } else if (e.attribute(QLatin1String("affiliation")) == QLatin1String("none")) { affiliation_ = NoAffiliation; } // Role if (e.attribute(QLatin1String("role")) == QLatin1String("moderator")) { role_ = Moderator; } else if (e.attribute(QLatin1String("role")) == QLatin1String("participant")) { role_ = Participant; } else if (e.attribute(QLatin1String("role")) == QLatin1String("visitor")) { role_ = Visitor; } else if (e.attribute(QLatin1String("role")) == QLatin1String("none")) { role_ = NoRole; } for (QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement i = n.toElement(); if (i.isNull()) continue; if (i.tagName() == QLatin1String("actor")) actor_ = Jid(i.attribute(QLatin1String("jid"))); else if (i.tagName() == QLatin1String("reason")) reason_ = i.text(); } } QDomElement MUCItem::toXml(QDomDocument &d) { QDomElement e = d.createElement("item"); if (!nick_.isEmpty()) e.setAttribute("nick", nick_); if (!jid_.isEmpty()) e.setAttribute("jid", jid_.full()); if (!reason_.isEmpty()) e.appendChild(textTag(&d, "reason", reason_)); switch (affiliation_) { case NoAffiliation: e.setAttribute("affiliation", "none"); break; case Owner: e.setAttribute("affiliation", "owner"); break; case Admin: e.setAttribute("affiliation", "admin"); break; case Member: e.setAttribute("affiliation", "member"); break; case Outcast: e.setAttribute("affiliation", "outcast"); break; default: break; } switch (role_) { case NoRole: e.setAttribute("role", "none"); break; case Moderator: e.setAttribute("role", "moderator"); break; case Participant: e.setAttribute("role", "participant"); break; case Visitor: e.setAttribute("role", "visitor"); break; default: break; } return e; } bool MUCItem::operator==(const MUCItem &o) { return !nick_.compare(o.nick_) && ((!jid_.isValid() && !o.jid_.isValid()) || jid_.compare(o.jid_, true)) && ((!actor_.isValid() && !o.actor_.isValid()) || actor_.compare(o.actor_, true)) && affiliation_ == o.affiliation_ && role_ == o.role_ && !reason_.compare(o.reason_); } //---------------------------------------------------------------------------- // MUCInvite //---------------------------------------------------------------------------- MUCInvite::MUCInvite() : cont_(false) { } MUCInvite::MUCInvite(const Jid &to, const QString &reason) : to_(to), reason_(reason), cont_(false) { } MUCInvite::MUCInvite(const QDomElement &e) : cont_(false) { fromXml(e); } const Jid &MUCInvite::from() const { return from_; } void MUCInvite::setFrom(const Jid &j) { from_ = j; } const Jid &MUCInvite::to() const { return to_; } void MUCInvite::setTo(const Jid &j) { to_ = j; } const QString &MUCInvite::reason() const { return reason_; } void MUCInvite::setReason(const QString &r) { reason_ = r; } bool MUCInvite::cont() const { return cont_; } void MUCInvite::setCont(bool b) { cont_ = b; } void MUCInvite::fromXml(const QDomElement &e) { if (e.tagName() != "invite") return; from_ = e.attribute("from"); to_ = e.attribute("to"); for (QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement i = n.toElement(); if (i.isNull()) continue; if (i.tagName() == "continue") cont_ = true; else if (i.tagName() == "reason") reason_ = i.text(); } } QDomElement MUCInvite::toXml(QDomDocument &d) const { QDomElement invite = d.createElement("invite"); if (!to_.isEmpty()) { invite.setAttribute("to", to_.full()); } if (!from_.isEmpty()) { invite.setAttribute("from", from_.full()); } if (!reason_.isEmpty()) { invite.appendChild(textTag(&d, "reason", reason_)); } if (cont_) { invite.appendChild(d.createElement("continue")); } return invite; } bool MUCInvite::isNull() const { return to_.isEmpty() && from_.isEmpty(); } //---------------------------------------------------------------------------- // MUCDecline //---------------------------------------------------------------------------- MUCDecline::MUCDecline() { } MUCDecline::MUCDecline(const QDomElement &e) { fromXml(e); } const Jid &MUCDecline::from() const { return from_; } void MUCDecline::setFrom(const Jid &j) { from_ = j; } const Jid &MUCDecline::to() const { return to_; } void MUCDecline::setTo(const Jid &j) { to_ = j; } const QString &MUCDecline::reason() const { return reason_; } void MUCDecline::setReason(const QString &r) { reason_ = r; } void MUCDecline::fromXml(const QDomElement &e) { if (e.tagName() != "decline") return; from_ = e.attribute("from"); to_ = e.attribute("to"); for (QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement i = n.toElement(); if (i.isNull()) continue; if (i.tagName() == "reason") reason_ = i.text(); } } QDomElement MUCDecline::toXml(QDomDocument &d) const { QDomElement decline = d.createElement("decline"); if (!to_.isEmpty()) { decline.setAttribute("to", to_.full()); } if (!from_.isEmpty()) { decline.setAttribute("from", from_.full()); } if (!reason_.isEmpty()) { decline.appendChild(textTag(&d, "reason", reason_)); } return decline; } bool MUCDecline::isNull() const { return to_.isEmpty() && from_.isEmpty(); } //---------------------------------------------------------------------------- // MUCDestroy //---------------------------------------------------------------------------- MUCDestroy::MUCDestroy() { } MUCDestroy::MUCDestroy(const QDomElement &e) { fromXml(e); } const Jid &MUCDestroy::jid() const { return jid_; } void MUCDestroy::setJid(const Jid &j) { jid_ = j; } const QString &MUCDestroy::reason() const { return reason_; } void MUCDestroy::setReason(const QString &r) { reason_ = r; } void MUCDestroy::fromXml(const QDomElement &e) { if (e.tagName() != "destroy") return; jid_ = e.attribute("jid"); for (QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement i = n.toElement(); if (i.isNull()) continue; if (i.tagName() == "reason") reason_ = i.text(); } } QDomElement MUCDestroy::toXml(QDomDocument &d) const { QDomElement destroy = d.createElement("destroy"); if (!jid_.isEmpty()) { destroy.setAttribute("jid", jid_.full()); } if (!reason_.isEmpty()) { destroy.appendChild(textTag(&d, "reason", reason_)); } return destroy; } //---------------------------------------------------------------------------- // HTMLElement //---------------------------------------------------------------------------- HTMLElement::HTMLElement() { } HTMLElement::HTMLElement(const QDomElement &body) { setBody(body); } void HTMLElement::setBody(const QDomElement &body) { body_ = doc_.importNode(body, true).toElement(); } const QDomElement &HTMLElement::body() const { return body_; } /** * Returns the string reperesentation of the HTML element. * By default, this is of the form ..., but the * root tag can be modified using a parameter. * * \param rootTagName the tagname of the root element to use. */ QString HTMLElement::toString(const QString &rootTagName) const { // create a copy of the body_ node, // get rid of the xmlns attribute and // change the root node name QDomElement e = body_.cloneNode().toElement(); e.setTagName(rootTagName); // instead of using: // QDomDocument msg; // msg.appendChild(e); // return msg.toString(); // call Stream::xmlToString, to remove unwanted namespace attributes return (Stream::xmlToString(e)); } QString HTMLElement::text() const { return body_.text(); } void HTMLElement::filterOutUnwanted(bool strict) { Q_UNUSED(strict) // TODO filter out not xhtml-im elements filterOutUnwantedRecursive(body_, strict); } void HTMLElement::filterOutUnwantedRecursive(QDomElement &el, bool strict) { Q_UNUSED(strict) // TODO filter out not xhtml-im elements static QSet unwanted = QSet() << "script" << "iframe"; QDomNode child = el.firstChild(); while (!child.isNull()) { QDomNode sibling = child.nextSibling(); if (child.isElement()) { QDomElement childEl = child.toElement(); if (unwanted.contains(childEl.tagName())) { child.parentNode().removeChild(child); } else { QDomNamedNodeMap domAttrs = childEl.attributes(); int acnt = domAttrs.count(); QStringList attrs; // attributes for removing for (int i = 0; i < acnt; i++) { QString name = domAttrs.item(i).toAttr().name(); if (name.startsWith("on")) { attrs.append(name); } } for (const QString &name : attrs) { domAttrs.removeNamedItem(name); } filterOutUnwantedRecursive(childEl, strict); } } child = sibling; } } //---------------------------------------------------------------------------- // Message //---------------------------------------------------------------------------- class Message::Private : public QSharedData { public: Jid to, from; QString id, type, lang; StringMap subject, body; QString thread; bool threadSend = false; Stanza::Error error; // extensions QDateTime timeStamp; // local time bool timeStampSend = false; UrlList urlList; AddressList addressList; RosterExchangeItems rosterExchangeItems; QString messageReceiptId; QString nick; QString eventId; QString xsigned, xencrypted, invite; QString pubsubNode; QList pubsubItems; QList pubsubRetractions; QList eventList; ChatState chatState = StateNone; MessageReceipt messageReceipt = ReceiptNone; HttpAuthRequest httpAuthRequest; XData xdata; IBBData ibbData; QMap htmlElements; QDomElement sxe; QList bobDataList; Jid forwardedFrom; QList mucStatuses; QList mucInvites; MUCDecline mucDecline; QString mucPassword; bool hasMUCUser = false; bool spooled = false, wasEncrypted = false; // XEP-0280 Message Carbons bool isDisabledCarbons = false; Message::CarbonDir carbonDir = Message::NoCarbon; // it's a forwarded message Message::ProcessingHints processingHints; QString replaceId; QString originId; // XEP-0359 QString encryptionProtocol; // XEP-0380 Message::StanzaId stanzaId; // XEP-0359 QList references; // XEP-0385 and XEP-0372 }; #define MessageD() (d ? d : (d = new Private)) //! \brief Constructs Message with given Jid information. //! //! This function will construct a Message container. //! \param to - specify receiver (default: empty string) Message::Message() { } Message::Message(const Jid &to) : d(new Private) { d->to = to; } //! \brief Constructs a copy of Message object //! //! Overloaded constructor which will constructs a exact copy of the Message //! object that was passed to the constructor. //! \param from - Message object you want to copy Message::Message(const Message &from) : d(from.d) { } //! \brief Required for internel use. Message &Message::operator=(const Message &from) { d = from.d; return *this; } //! \brief Destroy Message object. Message::~Message() { } //! \brief Check if it's exactly the same instance. bool Message::operator==(const Message &from) const { return d == from.d; } //! \brief Return receiver's Jid information. Jid Message::to() const { return d ? d->to : Jid(); } //! \brief Return sender's Jid information. Jid Message::from() const { return d ? d->from : Jid(); } QString Message::id() const { return d ? d->id : QString(); } //! \brief Return type information QString Message::type() const { return d ? d->type : QString(); } QString Message::lang() const { return d ? d->lang : QString(); } //! \brief Return subject information. QString Message::subject(const QString &lang) const { return d ? d->subject.value(lang) : QString(); } //! \brief Return subject information. QString Message::subject(const QLocale &lang) const { return d ? d->subject.value(lang.bcp47Name()) : QString(); } StringMap Message::subjectMap() const { return d ? d->subject : StringMap(); } //! \brief Return body information. //! //! This function will return a plain text body //! for speficified BCP4 language if it it exists. //! //! \param lang - Language identified by BCP47 standard //! \note Returns first body if not found by language. QString Message::body(const QString &lang) const { if (!d || d->body.empty()) return QString(); auto it = d->body.constFind(lang); if (it != d->body.constEnd()) return *it; return d->body.begin().value(); } //! \brief Return body information. //! //! This is a convenience function for getting body by locale //! //! \param lang - requested body's locale //! \note Returns first body if not found by locale. QString Message::body(const QLocale &lang) const { return body(lang.bcp47Name()); } //! \brief Return xhtml body. //! //! This function will return the richtext version of the body, if //! available. //! \param lang - body language //! \note The return string is in xhtml HTMLElement Message::html(const QString &lang) const { if (containsHTML()) { if (d->htmlElements.contains(lang)) return d->htmlElements[lang]; else return d->htmlElements.begin().value(); } else return HTMLElement(); } //! \brief Tells if message has xhtml-im items. //! //! Returns true if there is at least one xhtml-im body //! in the message. bool Message::containsHTML() const { return d && !(d->htmlElements.isEmpty()); } QString Message::thread() const { return d ? d->thread : QString(); } Stanza::Error Message::error() const { return d ? d->error : Stanza::Error(); } //! \brief Set receivers information //! //! \param to - Receivers Jabber id void Message::setTo(const Jid &j) { MessageD()->to = j; // d->flag = false; } void Message::setFrom(const Jid &j) { MessageD()->from = j; // d->flag = false; } void Message::setId(const QString &s) { MessageD()->id = s; } //! \brief Set Type of message //! //! \param type - type of message your going to send void Message::setType(const QString &s) { MessageD()->type = s; // d->flag = false; } void Message::setLang(const QString &s) { MessageD()->lang = s; } //! \brief Set subject //! //! \param subject - Subject information void Message::setSubject(const QString &s, const QString &lang) { MessageD()->subject[lang] = s; // d->flag = false; } //! \brief Set body //! //! \param body - body information //! \param rich - set richtext if true and set plaintext if false. //! \note Richtext support will be implemented in the future... Sorry. void Message::setBody(const QString &s, const QString &lang) { MessageD()->body[lang] = s; // d->flag = false; } //! \brief Set xhtml body //! //! \param s - body node //! \param lang - body language //! \note The body should be in xhtml. void Message::setHTML(const HTMLElement &e, const QString &lang) { MessageD()->htmlElements[lang] = e; } void Message::setThread(const QString &s, bool send) { MessageD()->threadSend = send; d->thread = s; } void Message::setError(const Stanza::Error &err) { MessageD()->error = err; } QString Message::pubsubNode() const { return d ? d->pubsubNode : QString(); } QList Message::pubsubItems() const { return d ? d->pubsubItems : QList(); } QList Message::pubsubRetractions() const { return d ? d->pubsubRetractions : QList(); } QDateTime Message::timeStamp() const { return d ? d->timeStamp : QDateTime(); } void Message::setTimeStamp(const QDateTime &ts, bool send) { MessageD()->timeStampSend = send; d->timeStamp = ts; } //! \brief Return list of urls attached to message. UrlList Message::urlList() const { return d ? d->urlList : UrlList(); } //! \brief Add Url to the url list. //! //! \param url - url to append void Message::urlAdd(const Url &u) { MessageD()->urlList += u; } //! \brief clear out the url list. void Message::urlsClear() { if (d) { d->urlList.clear(); } } //! \brief Set urls to send //! //! \param urlList - list of urls to send void Message::setUrlList(const UrlList &list) { MessageD()->urlList = list; } //! \brief Return list of addresses attached to message. AddressList Message::addresses() const { return d ? d->addressList : AddressList(); } //! \brief Add Address to the address list. //! //! \param address - address to append void Message::addAddress(const Address &a) { MessageD()->addressList += a; } //! \brief clear out the address list. void Message::clearAddresses() { if (d) { d->addressList.clear(); } } AddressList Message::findAddresses(Address::Type t) const { if (!d) { return AddressList(); } AddressList matches; for (Address a : d->addressList) { if (a.type() == t) matches.append(a); } return matches; } //! \brief Set addresses to send //! //! \param list - list of addresses to send void Message::setAddresses(const AddressList &list) { MessageD()->addressList = list; } RosterExchangeItems Message::rosterExchangeItems() const { return d ? d->rosterExchangeItems : RosterExchangeItems(); } void Message::setRosterExchangeItems(const RosterExchangeItems &items) { MessageD()->rosterExchangeItems = items; } QString Message::eventId() const { return d ? d->eventId : QString(); } void Message::setEventId(const QString &id) { MessageD()->eventId = id; } bool Message::containsEvents() const { return d && !d->eventList.isEmpty(); } bool Message::containsEvent(MsgEvent e) const { return d && d->eventList.contains(e); } void Message::addEvent(MsgEvent e) { if (!MessageD()->eventList.contains(e)) { if (e == CancelEvent || containsEvent(CancelEvent)) d->eventList.clear(); // Reset list d->eventList += e; } } ChatState Message::chatState() const { return d ? d->chatState : StateNone; } void Message::setChatState(ChatState state) { MessageD()->chatState = state; } MessageReceipt Message::messageReceipt() const { return d ? d->messageReceipt : ReceiptNone; } void Message::setMessageReceipt(MessageReceipt messageReceipt) { MessageD()->messageReceipt = messageReceipt; } QString Message::messageReceiptId() const { return d ? d->messageReceiptId : QString(); } void Message::setMessageReceiptId(const QString &s) { MessageD()->messageReceiptId = s; } QString Message::xsigned() const { return d ? d->xsigned : QString(); } void Message::setXSigned(const QString &s) { MessageD()->xsigned = s; } QString Message::xencrypted() const { return d ? d->xencrypted : QString(); } void Message::setXEncrypted(const QString &s) { MessageD()->xencrypted = s; } QList Message::getMUCStatuses() const { return d ? d->mucStatuses : QList(); } void Message::addMUCStatus(int i) { MessageD()->mucStatuses += i; } void Message::addMUCInvite(const MUCInvite &i) { MessageD()->mucInvites += i; } QList Message::mucInvites() const { return d ? d->mucInvites : QList(); } void Message::setMUCDecline(const MUCDecline &de) { MessageD()->mucDecline = de; } MUCDecline Message::mucDecline() const { return d ? d->mucDecline : MUCDecline(); } QString Message::mucPassword() const { return d ? d->mucPassword : QString(); } void Message::setMUCPassword(const QString &p) { MessageD()->mucPassword = p; } bool Message::hasMUCUser() const { return d & d->hasMUCUser; } Message::StanzaId Message::stanzaId() const { return d ? d->stanzaId : StanzaId(); } void Message::setStanzaId(const Message::StanzaId &id) { MessageD()->stanzaId = id; } QString Message::originId() const { return d ? d->originId : QString(); } void Message::setOriginId(const QString &id) { MessageD()->originId = id; } QString Message::encryptionProtocol() const { return d ? d->encryptionProtocol : QString(); } void Message::setEncryptionProtocol(const QString &protocol) { MessageD()->encryptionProtocol = protocol; } QList Message::references() const { return d ? d->references : QList(); } void Message::addReference(const Reference &r) { MessageD()->references.append(r); } void Message::setReferences(const QList &r) { MessageD()->references = r; } QString Message::invite() const { return d ? d->invite : QString(); } void Message::setInvite(const QString &s) { MessageD()->invite = s; } QString Message::nick() const { return d ? d->nick : QString(); } void Message::setNick(const QString &n) { MessageD()->nick = n; } void Message::setHttpAuthRequest(const HttpAuthRequest &req) { MessageD()->httpAuthRequest = req; } HttpAuthRequest Message::httpAuthRequest() const { return d ? d->httpAuthRequest : HttpAuthRequest(); } void Message::setForm(const XData &form) { MessageD()->xdata = form; } XData Message::getForm() const { return d ? d->xdata : XData(); } QDomElement Message::sxe() const { return d ? d->sxe : QDomElement(); } void Message::setSxe(const QDomElement &e) { MessageD()->sxe = e; } void Message::addBoBData(const BoBData &bob) { MessageD()->bobDataList.append(bob); } QList Message::bobDataList() const { return d ? d->bobDataList : QList(); } IBBData Message::ibbData() const { return d ? d->ibbData : IBBData(); } void Message::setDisabledCarbons(bool disabled) { MessageD()->isDisabledCarbons = disabled; } bool Message::isDisabledCarbons() const { return d && d->isDisabledCarbons; } void Message::setCarbonDirection(Message::CarbonDir cd) { MessageD()->carbonDir = cd; } Message::CarbonDir Message::carbonDirection() const { return d ? d->carbonDir : NoCarbon; } void Message::setForwardedFrom(const Jid &jid) { MessageD()->forwardedFrom = jid; } Jid Message::forwardedFrom() const { return d ? d->forwardedFrom : Jid(); } bool Message::spooled() const { return d && d->spooled; } void Message::setSpooled(bool b) { MessageD()->spooled = b; } bool Message::wasEncrypted() const { return d && d->wasEncrypted; } void Message::setWasEncrypted(bool b) { MessageD()->wasEncrypted = b; } QString Message::replaceId() const { return d ? d->replaceId : QString(); } void Message::setReplaceId(const QString &id) { MessageD()->replaceId = id; } void Message::setProcessingHints(const ProcessingHints &hints) { MessageD()->processingHints = hints; } Message::ProcessingHints Message::processingHints() const { return d ? d->processingHints : ProcessingHints(); } Stanza Message::toStanza(Stream *stream) const { if (!d) { return Stanza(); } Stanza s = stream->createStanza(Stanza::Message, d->to, d->type); if (!d->from.isEmpty()) s.setFrom(d->from); if (!d->id.isEmpty()) s.setId(d->id); if (!d->lang.isEmpty()) s.setLang(d->lang); StringMap::ConstIterator it; for (it = d->subject.constBegin(); it != d->subject.constEnd(); ++it) { const QString &str = (*it); if (!str.isNull()) { QDomElement e = s.createTextElement(s.baseNS(), "subject", str); if (!it.key().isEmpty()) e.setAttributeNS(NS_XML, "xml:lang", it.key()); s.appendChild(e); } } for (it = d->body.constBegin(); it != d->body.constEnd(); ++it) { const QString &str = (*it); if (!str.isEmpty()) { QDomElement e = s.createTextElement(s.baseNS(), "body", str); if (!it.key().isEmpty()) e.setAttributeNS(NS_XML, "xml:lang", it.key()); s.appendChild(e); } } if (containsHTML()) { QDomElement html = s.createElement("http://jabber.org/protocol/xhtml-im", "html"); s.appendChild(html); for (HTMLElement el : d->htmlElements) { html.appendChild(s.doc().importNode(el.body(), true).toElement()); } } if (d->type == "error") s.setError(d->error); // thread if (d->threadSend && !d->thread.isEmpty()) { QDomElement e = s.createTextElement(s.baseNS(), "thread", d->thread); s.appendChild(e); } // timestamp if (d->timeStampSend && !d->timeStamp.isNull()) { QDomElement e = s.createElement("urn:xmpp:delay", "delay"); e.setAttribute("stamp", d->timeStamp.toUTC().toString(Qt::ISODate) + "Z"); s.appendChild(e); e = s.createElement("jabber:x:delay", "x"); e.setAttribute("stamp", TS2stamp(d->timeStamp.toUTC())); s.appendChild(e); } // urls for (const Url &uit : d->urlList) { QDomElement x = s.createElement("jabber:x:oob", "x"); x.appendChild(s.createTextElement("jabber:x:oob", "url", uit.url())); if (!uit.desc().isEmpty()) x.appendChild(s.createTextElement("jabber:x:oob", "desc", uit.desc())); s.appendChild(x); } // events if (!d->eventList.isEmpty()) { QDomElement x = s.createElement("jabber:x:event", "x"); if (d->body.isEmpty()) { if (d->eventId.isEmpty()) x.appendChild(s.createElement("jabber:x:event", "id")); else x.appendChild(s.createTextElement("jabber:x:event", "id", d->eventId)); } for (const MsgEvent &ev : d->eventList) { switch (ev) { case OfflineEvent: x.appendChild(s.createElement("jabber:x:event", "offline")); break; case DeliveredEvent: x.appendChild(s.createElement("jabber:x:event", "delivered")); break; case DisplayedEvent: x.appendChild(s.createElement("jabber:x:event", "displayed")); break; case ComposingEvent: x.appendChild(s.createElement("jabber:x:event", "composing")); break; case CancelEvent: // Add nothing break; } } s.appendChild(x); } // chat state QString chatStateNS = "http://jabber.org/protocol/chatstates"; if (d->chatState != StateNone) { switch (d->chatState) { case StateActive: s.appendChild(s.createElement(chatStateNS, "active")); break; case StateComposing: s.appendChild(s.createElement(chatStateNS, "composing")); break; case StatePaused: s.appendChild(s.createElement(chatStateNS, "paused")); break; case StateInactive: s.appendChild(s.createElement(chatStateNS, "inactive")); break; case StateGone: s.appendChild(s.createElement(chatStateNS, "gone")); break; default: break; } } // message receipt QString messageReceiptNS = "urn:xmpp:receipts"; if (d->messageReceipt != ReceiptNone) { switch (d->messageReceipt) { case ReceiptRequest: s.appendChild(s.createElement(messageReceiptNS, "request")); break; case ReceiptReceived: { QDomElement elem = s.createElement(messageReceiptNS, "received"); if (!d->messageReceiptId.isEmpty()) { elem.setAttribute("id", d->messageReceiptId); } s.appendChild(elem); } break; default: break; } } // xsigned if (!d->xsigned.isEmpty()) s.appendChild(s.createTextElement("jabber:x:signed", "x", d->xsigned)); // OpenPGP encrypted message if (!d->xencrypted.isEmpty()) { // See: XEP-0027: Current Jabber OpenPGP Usage s.appendChild(s.createTextElement("jabber:x:encrypted", "x", d->xencrypted)); // See: XEP-0280: Message Carbons QDomElement nc = s.createElement("urn:xmpp:hints", "no-copy"); QDomElement pr = s.createElement("urn:xmpp:carbons:2", "private"); s.appendChild(nc); s.appendChild(pr); // See: XEP-0380: Explicit Message Encryption QDomElement en = s.createElement("urn:xmpp:eme:0", "encryption"); en.setAttribute("namespace", "jabber:x:encrypted"); s.appendChild(en); } // addresses if (!d->addressList.isEmpty()) { QDomElement as = s.createElement("http://jabber.org/protocol/address", "addresses"); for (Address a : d->addressList) { as.appendChild(a.toXml(s)); } s.appendChild(as); } // roster item exchange if (!d->rosterExchangeItems.isEmpty()) { QDomElement rx = s.createElement("http://jabber.org/protocol/rosterx", "x"); for (RosterExchangeItem r : d->rosterExchangeItems) { rx.appendChild(r.toXml(s)); } s.appendChild(rx); } // invite if (!d->invite.isEmpty()) { QDomElement e = s.createElement("jabber:x:conference", "x"); e.setAttribute("jid", d->invite); s.appendChild(e); } // nick if (!d->nick.isEmpty()) { s.appendChild(s.createTextElement("http://jabber.org/protocol/nick", "nick", d->nick)); } // sxe if (!d->sxe.isNull()) { s.appendChild(d->sxe); } // muc if (!d->mucInvites.isEmpty()) { QDomElement e = s.createElement("http://jabber.org/protocol/muc#user", "x"); for (MUCInvite i : d->mucInvites) { e.appendChild(i.toXml(s.doc())); } if (!d->mucPassword.isEmpty()) { e.appendChild(s.createTextElement("http://jabber.org/protocol/muc#user", "password", d->mucPassword)); } s.appendChild(e); } else if (!d->mucDecline.isNull()) { QDomElement e = s.createElement("http://jabber.org/protocol/muc#user", "x"); e.appendChild(d->mucDecline.toXml(s.doc())); s.appendChild(e); } // http auth if (!d->httpAuthRequest.isEmpty()) { s.appendChild(d->httpAuthRequest.toXml(s.doc())); } // data form if (!d->xdata.fields().empty() || (d->xdata.type() == XData::Data_Cancel)) { bool submit = (d->xdata.type() == XData::Data_Submit) || (d->xdata.type() == XData::Data_Cancel); QDomElement dr = s.element(); if (d->xdata.registrarType() == "urn:xmpp:captcha") { dr = dr.appendChild(s.createElement("urn:xmpp:captcha", "captcha")).toElement(); } dr.appendChild(d->xdata.toXml(&s.doc(), submit)); } // bits of binary for (const BoBData &bd : d->bobDataList) { s.appendChild(bd.toXml(&s.doc())); } // Avoiding Carbons if (isDisabledCarbons()) { QDomElement e = s.createElement("urn:xmpp:carbons:2", "private"); s.appendChild(e); } if (!d->replaceId.isEmpty()) { QDomElement e = s.createElement("urn:xmpp:message-correct:0", "replace"); e.setAttribute("id", d->replaceId); s.appendChild(e); } // Message processing hints. XEP-0334 if (d->processingHints) { QString ns = QStringLiteral(u"urn:xmpp:hints"); if (d->processingHints & NoPermanentStore) { s.appendChild(s.createElement(ns, QStringLiteral("no-permanent-store"))); } if (d->processingHints & NoStore) { s.appendChild(s.createElement(ns, QStringLiteral("no-store"))); } if (d->processingHints & NoCopy) { s.appendChild(s.createElement(ns, QStringLiteral("no-copy"))); } if (d->processingHints & Store) { s.appendChild(s.createElement(ns, QStringLiteral("store"))); } } // XEP-0359: Unique and Stable Stanza IDs if (!d->originId.isEmpty()) { auto e = s.createElement(QStringLiteral("urn:xmpp:sid:0"), QStringLiteral("origin-id")); e.setAttribute(QStringLiteral("id"), d->originId); s.appendChild(e); } if (!d->stanzaId.id.isEmpty() && d->stanzaId.by.isValid()) { // only for servers using iris auto e = s.createElement(QStringLiteral("urn:xmpp:sid:0"), QStringLiteral("stanza-id")); e.setAttribute(QStringLiteral("id"), d->stanzaId.id); e.setAttribute(QStringLiteral("by"), d->stanzaId.by.full()); s.appendChild(e); } // XEP-0372 and XEP-0385 for (auto const &r : d->references) { s.appendChild(r.toXml(&s.doc())); } return s; } /** \brief Create Message from Stanza \a s, using given \a timeZoneOffset (old style) */ bool Message::fromStanza(const Stanza &s, int timeZoneOffset) { return fromStanza(s, true, timeZoneOffset); } /** \brief Create Message from Stanza \a s */ bool Message::fromStanza(const Stanza &s) { return fromStanza(s, false, 0); } /** \brief Create Message from Stanza \a s If \a useTimeZoneOffset is true, \a timeZoneOffset is used when converting between UTC and local time (old style). Else, \a timeZoneOffset is ignored and Qt is used to do the conversion (new style). This function exists to make transition between old and new style easier. */ bool Message::fromStanza(const Stanza &s, bool useTimeZoneOffset, int timeZoneOffset) { if (s.kind() != Stanza::Message) return false; d = new Private; setTo(s.to()); setFrom(s.from()); setId(s.id()); setType(s.type()); setLang(s.lang()); d->subject.clear(); d->body.clear(); d->htmlElements.clear(); d->thread = QString(); QDomElement root = s.element(); XDomNodeList nl = root.childNodes(); int n; for (n = 0; n < nl.count(); ++n) { QDomNode i = nl.item(n); if (i.isElement()) { QDomElement e = i.toElement(); if (e.namespaceURI() == s.baseNS()) { if (e.tagName() == QLatin1String("subject")) { QString lang = e.attributeNS(NS_XML, "lang", ""); if (lang.isEmpty() || !(lang = XMLHelper::sanitizedLang(lang)).isEmpty()) { d->subject[lang] = e.text(); } } else if (e.tagName() == QLatin1String("body")) { QString lang = e.attributeNS(NS_XML, "lang", ""); if (lang.isEmpty() || !(lang = XMLHelper::sanitizedLang(lang)).isEmpty()) { d->body[lang] = e.text(); } } else if (e.tagName() == QLatin1String("thread")) d->thread = e.text(); } else if (e.tagName() == QLatin1String("event") && e.namespaceURI() == QLatin1String("http://jabber.org/protocol/pubsub#event")) { for (QDomNode enode = e.firstChild(); !enode.isNull(); enode = enode.nextSibling()) { QDomElement eel = enode.toElement(); if (eel.tagName() == QLatin1String("items")) { d->pubsubNode = eel.attribute("node"); for (QDomNode inode = eel.firstChild(); !inode.isNull(); inode = inode.nextSibling()) { QDomElement o = inode.toElement(); if (o.tagName() == QLatin1String("item")) { for (QDomNode j = o.firstChild(); !j.isNull(); j = j.nextSibling()) { QDomElement item = j.toElement(); if (!item.isNull()) { d->pubsubItems += PubSubItem(o.attribute("id"), item); } } } if (o.tagName() == "retract") { d->pubsubRetractions += PubSubRetraction(o.attribute("id")); } } } } } else if (e.tagName() == QLatin1String("no-permanent-store") && e.namespaceURI() == QLatin1String("urn:xmpp:hints")) { d->processingHints |= NoPermanentStore; } else if (e.tagName() == QLatin1String("no-store") && e.namespaceURI() == QLatin1String("urn:xmpp:hints")) { d->processingHints |= NoStore; } else if (e.tagName() == QLatin1String("no-copy") && e.namespaceURI() == QLatin1String("urn:xmpp:hints")) { d->processingHints |= NoCopy; } else if (e.tagName() == QLatin1String("store") && e.namespaceURI() == QLatin1String("urn:xmpp:hints")) { d->processingHints |= Store; } else if (e.tagName() == QLatin1String("origin-id") && e.namespaceURI() == QLatin1String("urn:xmpp:sid:0")) { d->originId = e.attribute(QStringLiteral("id")); } else if (e.tagName() == QLatin1String("stanza-id") && e.namespaceURI() == QLatin1String("urn:xmpp:sid:0")) { d->stanzaId.id = e.attribute(QStringLiteral("id")); d->stanzaId.by = Jid(e.attribute(QStringLiteral("by"))); } else { // printf("extension element: [%s]\n", e.tagName().latin1()); } } } if (s.type() == "error") d->error = s.error(); // Bits of Binary XEP-0231 nl = childElementsByTagNameNS(root, "urn:xmpp:bob", "data"); for (n = 0; n < nl.count(); ++n) { addBoBData(BoBData(nl.item(n).toElement())); } // xhtml-im nl = childElementsByTagNameNS(root, "http://jabber.org/protocol/xhtml-im", "html"); if (nl.count()) { nl = nl.item(0).childNodes(); for (n = 0; n < nl.count(); ++n) { QDomElement e = nl.item(n).toElement(); if (e.tagName() == "body" && e.namespaceURI() == "http://www.w3.org/1999/xhtml") { QString lang = e.attributeNS(NS_XML, "lang", ""); if (lang.isEmpty() || !(lang = XMLHelper::sanitizedLang(lang)).isEmpty()) { d->htmlElements[lang] = e; d->htmlElements[lang].filterOutUnwanted(false); // just clear iframes and javascript event handlers } } } } // timestamp QDomElement t = childElementsByTagNameNS(root, "urn:xmpp:delay", "delay").item(0).toElement(); QDateTime stamp; if (!t.isNull()) { stamp = QDateTime::fromString(t.attribute("stamp").left(19), Qt::ISODate); } else { t = childElementsByTagNameNS(root, "jabber:x:delay", "x").item(0).toElement(); if (!t.isNull()) { stamp = stamp2TS(t.attribute("stamp")); } } if (!stamp.isNull()) { if (useTimeZoneOffset) { d->timeStamp = stamp.addSecs(timeZoneOffset * 3600); } else { stamp.setTimeSpec(Qt::UTC); d->timeStamp = stamp.toLocalTime(); } d->timeStampSend = true; d->spooled = true; } else { d->timeStamp = QDateTime::currentDateTime(); d->timeStampSend = false; d->spooled = false; } // urls d->urlList.clear(); nl = childElementsByTagNameNS(root, "jabber:x:oob", "x"); for (n = 0; n < nl.count(); ++n) { QDomElement t = nl.item(n).toElement(); Url u; u.setUrl(t.elementsByTagName("url").item(0).toElement().text()); u.setDesc(t.elementsByTagName("desc").item(0).toElement().text()); d->urlList += u; } // events d->eventList.clear(); nl = childElementsByTagNameNS(root, "jabber:x:event", "x"); if (nl.count()) { nl = nl.item(0).childNodes(); for (n = 0; n < nl.count(); ++n) { QString evtag = nl.item(n).toElement().tagName(); if (evtag == "id") { d->eventId = nl.item(n).toElement().text(); } else if (evtag == "displayed") d->eventList += DisplayedEvent; else if (evtag == "composing") d->eventList += ComposingEvent; else if (evtag == "delivered") d->eventList += DeliveredEvent; } if (d->eventList.isEmpty()) d->eventList += CancelEvent; } // Chat states QString chatStateNS = "http://jabber.org/protocol/chatstates"; t = childElementsByTagNameNS(root, chatStateNS, "active").item(0).toElement(); if (!t.isNull()) d->chatState = StateActive; t = childElementsByTagNameNS(root, chatStateNS, "composing").item(0).toElement(); if (!t.isNull()) d->chatState = StateComposing; t = childElementsByTagNameNS(root, chatStateNS, "paused").item(0).toElement(); if (!t.isNull()) d->chatState = StatePaused; t = childElementsByTagNameNS(root, chatStateNS, "inactive").item(0).toElement(); if (!t.isNull()) d->chatState = StateInactive; t = childElementsByTagNameNS(root, chatStateNS, "gone").item(0).toElement(); if (!t.isNull()) d->chatState = StateGone; // message receipts QString messageReceiptNS = "urn:xmpp:receipts"; t = childElementsByTagNameNS(root, messageReceiptNS, "request").item(0).toElement(); if (!t.isNull()) { d->messageReceipt = ReceiptRequest; d->messageReceiptId.clear(); } t = childElementsByTagNameNS(root, messageReceiptNS, "received").item(0).toElement(); if (!t.isNull()) { d->messageReceipt = ReceiptReceived; d->messageReceiptId = t.attribute("id"); if (d->messageReceiptId.isEmpty()) d->messageReceiptId = id(); } // xsigned t = childElementsByTagNameNS(root, "jabber:x:signed", "x").item(0).toElement(); if (!t.isNull()) d->xsigned = t.text(); else d->xsigned = QString(); // xencrypted t = childElementsByTagNameNS(root, "jabber:x:encrypted", "x").item(0).toElement(); if (!t.isNull()) d->xencrypted = t.text(); else d->xencrypted = QString(); // addresses d->addressList.clear(); nl = childElementsByTagNameNS(root, "http://jabber.org/protocol/address", "addresses"); if (nl.count()) { QDomElement t = nl.item(0).toElement(); nl = t.elementsByTagName("address"); for (n = 0; n < nl.count(); ++n) { d->addressList += Address(nl.item(n).toElement()); } } // roster item exchange d->rosterExchangeItems.clear(); nl = childElementsByTagNameNS(root, "http://jabber.org/protocol/rosterx", "x"); if (nl.count()) { QDomElement t = nl.item(0).toElement(); nl = t.elementsByTagName("item"); for (n = 0; n < nl.count(); ++n) { RosterExchangeItem it = RosterExchangeItem(nl.item(n).toElement()); if (!it.isNull()) d->rosterExchangeItems += it; } } // invite t = childElementsByTagNameNS(root, "jabber:x:conference", "x").item(0).toElement(); if (!t.isNull()) d->invite = t.attribute("jid"); else d->invite = QString(); // nick t = childElementsByTagNameNS(root, "http://jabber.org/protocol/nick", "nick").item(0).toElement(); if (!t.isNull()) d->nick = t.text(); else d->nick = QString(); // sxe t = childElementsByTagNameNS(root, "http://jabber.org/protocol/sxe", "sxe").item(0).toElement(); if (!t.isNull()) d->sxe = t; else d->sxe = QDomElement(); t = childElementsByTagNameNS(root, "http://jabber.org/protocol/muc#user", "x").item(0).toElement(); if (!t.isNull()) { d->hasMUCUser = true; for (QDomNode muc_n = t.firstChild(); !muc_n.isNull(); muc_n = muc_n.nextSibling()) { QDomElement muc_e = muc_n.toElement(); if (muc_e.isNull()) continue; if (muc_e.tagName() == "status") { addMUCStatus(muc_e.attribute("code").toInt()); } else if (muc_e.tagName() == "invite") { MUCInvite inv(muc_e); if (!inv.isNull()) addMUCInvite(inv); } else if (muc_e.tagName() == "decline") { setMUCDecline(MUCDecline(muc_e)); } else if (muc_e.tagName() == "password") { setMUCPassword(muc_e.text()); } } } // http auth t = childElementsByTagNameNS(root, "http://jabber.org/protocol/http-auth", "confirm").item(0).toElement(); if (!t.isNull()) { d->httpAuthRequest = HttpAuthRequest(t); } else { d->httpAuthRequest = HttpAuthRequest(); } QDomElement captcha = childElementsByTagNameNS(root, "urn:xmpp:captcha", "captcha").item(0).toElement(); QDomElement xdataRoot = root; if (!captcha.isNull()) { xdataRoot = captcha; } // data form t = childElementsByTagNameNS(xdataRoot, "jabber:x:data", "x").item(0).toElement(); if (!t.isNull()) { d->xdata.fromXml(t); } t = childElementsByTagNameNS(root, IBBManager::ns(), "data").item(0).toElement(); if (!t.isNull()) { d->ibbData.fromXml(t); } t = childElementsByTagNameNS(root, "urn:xmpp:message-correct:0", "replace").item(0).toElement(); if (!t.isNull()) { d->replaceId = t.attribute("id"); } // XEP-0385 SIMS and XEP-0372 Reference auto references = childElementsByTagNameNS(root, REFERENCE_NS, QString::fromLatin1("reference")); for (int i = 0; i < references.size(); i++) { Reference r; if (r.fromXml(references.at(i).toElement())) { d->references.append(r); } } return true; } /*! Error object used to deny a request. */ Stanza::Error HttpAuthRequest::denyError(Stanza::Error::Auth, Stanza::Error::NotAuthorized); /*! Constructs request of resource URL \a u, made by method \a m, with transaction id \a i. */ HttpAuthRequest::HttpAuthRequest(const QString &m, const QString &u, const QString &i) : method_(m), url_(u), id_(i), hasId_(true) { } /*! Constructs request of resource URL \a u, made by method \a m, without transaction id. */ HttpAuthRequest::HttpAuthRequest(const QString &m, const QString &u) : method_(m), url_(u), hasId_(false) { } /*! Constructs request object by reading XML element \a e. */ HttpAuthRequest::HttpAuthRequest(const QDomElement &e) { fromXml(e); } /*! Returns true is object is empty (not valid). */ bool HttpAuthRequest::isEmpty() const { return method_.isEmpty() && url_.isEmpty(); } /*! Sets request method. */ void HttpAuthRequest::setMethod(const QString &m) { method_ = m; } /*! Sets requested URL. */ void HttpAuthRequest::setUrl(const QString &u) { url_ = u; } /*! Sets transaction identifier. */ void HttpAuthRequest::setId(const QString &i) { id_ = i; hasId_ = true; } /*! Returns request method. */ QString HttpAuthRequest::method() const { return method_; } /*! Returns requested URL. */ QString HttpAuthRequest::url() const { return url_; } /*! Returns transaction identifier. Empty QString may mean both empty id or no id. Use hasId() to tell the difference. */ QString HttpAuthRequest::id() const { return id_; } /*! Returns true if the request contains transaction id. */ bool HttpAuthRequest::hasId() const { return hasId_; } /*! Returns XML element representing the request. If object is empty, this function returns empty element. */ QDomElement HttpAuthRequest::toXml(QDomDocument &doc) const { QDomElement e; if (isEmpty()) return e; e = doc.createElementNS("http://jabber.org/protocol/http-auth", "confirm"); if (hasId_) e.setAttribute("id", id_); e.setAttribute("method", method_); e.setAttribute("url", url_); return e; } /*! Reads request data from XML element \a e. */ bool HttpAuthRequest::fromXml(const QDomElement &e) { if (e.tagName() != "confirm") return false; hasId_ = e.hasAttribute("id"); if (hasId_) id_ = e.attribute("id"); method_ = e.attribute("method"); url_ = e.attribute("url"); return true; } //--------------------------------------------------------------------------- // Subscription //--------------------------------------------------------------------------- Subscription::Subscription(SubType type) { value = type; } int Subscription::type() const { return value; } QString Subscription::toString() const { switch (value) { case Remove: return "remove"; case Both: return "both"; case From: return "from"; case To: return "to"; case None: default: return "none"; } } bool Subscription::fromString(const QString &s) { if (s == QLatin1String("remove")) value = Remove; else if (s == QLatin1String("both")) value = Both; else if (s == QLatin1String("from")) value = From; else if (s == QLatin1String("to")) value = To; else if (s.isEmpty() || s == QLatin1String("none")) value = None; else return false; return true; } //--------------------------------------------------------------------------- // Status //--------------------------------------------------------------------------- /** * Default constructor. */ CapsSpec::CapsSpec() : hashAlgo_(CapsSpec::invalidAlgo) { } /** * \brief Basic constructor. * @param node the node * @param ven the version * @param ext the list of extensions (separated by spaces) */ CapsSpec::CapsSpec(const QString &node, QCryptographicHash::Algorithm hashAlgo, const QString &ver) : node_(node), ver_(ver), hashAlgo_(hashAlgo) { } CapsSpec::CapsSpec(const DiscoItem &disco, QCryptographicHash::Algorithm hashAlgo) : node_(disco.node().section('#', 0, 0)), ver_(disco.capsHash(hashAlgo)), hashAlgo_(hashAlgo) { } /** * @brief Checks for validity * @return true on valid */ bool CapsSpec::isValid() const { return !node_.isEmpty() && !ver_.isEmpty() && (hashAlgo_ != CapsSpec::invalidAlgo); } /** * \brief Returns the node of the capabilities specification. */ const QString &CapsSpec::node() const { return node_; } /** * \brief Returns the version of the capabilities specification. */ const QString &CapsSpec::version() const { return ver_; } QCryptographicHash::Algorithm CapsSpec::hashAlgorithm() const { return hashAlgo_; } QDomElement CapsSpec::toXml(QDomDocument *doc) const { QDomElement c = doc->createElementNS(NS_CAPS, "c"); QString algo = cryptoMap().key(hashAlgo_); c.setAttribute("hash", algo); c.setAttribute("node", node_); c.setAttribute("ver", ver_); return c; } CapsSpec CapsSpec::fromXml(const QDomElement &e) { QString node = e.attribute("node"); QString ver = e.attribute("ver"); QString hashAlgo = e.attribute("hash"); QString ext = e.attribute("ext"); // deprecated. let it be here till 2018 CryptoMap &cm = cryptoMap(); CapsSpec cs; if (!node.isEmpty() && !ver.isEmpty()) { QCryptographicHash::Algorithm algo = CapsSpec::invalidAlgo; CryptoMap::ConstIterator it; if (!hashAlgo.isEmpty() && (it = cm.constFind(hashAlgo)) != cm.constEnd()) { algo = it.value(); } cs = CapsSpec(node, algo, ver); if (!ext.isEmpty()) { #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) cs.ext_ = ext.split(" ", Qt::SkipEmptyParts); #else cs.ext_ = ext.split(" ", QString::SkipEmptyParts); #endif } } return cs; } CapsSpec::CryptoMap &CapsSpec::cryptoMap() { static CryptoMap cm; if (cm.isEmpty()) { cm.insert("md5", QCryptographicHash::Md5); cm.insert("sha-1", QCryptographicHash::Sha1); cm.insert("sha-224", QCryptographicHash::Sha224); cm.insert("sha-256", QCryptographicHash::Sha256); cm.insert("sha-384", QCryptographicHash::Sha384); cm.insert("sha-512", QCryptographicHash::Sha512); } return cm; } /** * \brief Flattens the caps specification into the set of 'simple' * specifications. * A 'simple' specification is a specification with exactly one extension, * or with the version number as the extension. * * Example: A caps specification with node=https://psi-im.org, version=0.10, * and ext='achat vchat' would be expanded into the following list of specs: * node=https://psi-im.org, ver=0.10, ext=0.10 * node=https://psi-im.org, ver=0.10, ext=achat * node=https://psi-im.org, ver=0.10, ext=vchat */ QString CapsSpec::flatten() const { if (isValid()) return node_ + QLatin1String("#") + ver_; return QString(); } void CapsSpec::resetVersion() { ver_.clear(); } bool CapsSpec::operator==(const CapsSpec &s) const { return (node() == s.node() && version() == s.version() && hashAlgorithm() == s.hashAlgorithm()); } bool CapsSpec::operator!=(const CapsSpec &s) const { return !((*this) == s); } bool CapsSpec::operator<(const CapsSpec &s) const { return (node() != s.node() ? node() < s.node() : (version() != s.version() ? version() < s.version() : hashAlgorithm() < s.hashAlgorithm())); } class StatusPrivate : public QSharedData { public: StatusPrivate() = default; int priority = 0; QString show, status, key; QDateTime timeStamp; bool isAvailable = false; bool isInvisible = false; QByteArray photoHash; bool hasPhotoHash = false; QString xsigned; // gabber song extension QString songTitle; CapsSpec caps; QList bobDataList; // MUC bool isMUC = false; bool hasMUCItem = false; bool hasMUCDestroy = false; MUCItem mucItem; MUCDestroy mucDestroy; QList mucStatuses; QString mucPassword; int mucHistoryMaxChars = -1; int mucHistoryMaxStanzas = -1; int mucHistorySeconds = -1; QDateTime mucHistorySince; int ecode = -1; QString estr; }; Status::Status(const QString &show, const QString &status, int priority, bool available) : d(new StatusPrivate) { d->isAvailable = available; d->show = show; d->status = status; d->priority = priority; d->timeStamp = QDateTime::currentDateTime(); d->isInvisible = false; } Status::Status(Type type, const QString &status, int priority) : d(new StatusPrivate) { d->status = status; d->priority = priority; d->timeStamp = QDateTime::currentDateTime(); setType(type); } Status::Status(const Status &other) : d(other.d) { } Status &Status::operator=(const Status &other) { d = other.d; return *this; } Status::~Status() { } bool Status::hasError() const { return (d->ecode != -1); } void Status::setError(int code, const QString &str) { d->ecode = code; d->estr = str; } void Status::setIsAvailable(bool available) { d->isAvailable = available; } void Status::setIsInvisible(bool invisible) { d->isInvisible = invisible; } void Status::setPriority(int x) { d->priority = x; } void Status::setType(Status::Type _type) { bool available = true; bool invisible = false; QString show; switch (_type) { case Away: show = "away"; break; case FFC: show = "chat"; break; case XA: show = "xa"; break; case DND: show = "dnd"; break; case Offline: available = false; break; case Invisible: invisible = true; break; default: break; } setShow(show); setIsAvailable(available); setIsInvisible(invisible); } Status::Type Status::txt2type(const QString &stat) { if (stat == "offline") return XMPP::Status::Offline; else if (stat == "online") return XMPP::Status::Online; else if (stat == "away") return XMPP::Status::Away; else if (stat == "xa") return XMPP::Status::XA; else if (stat == "dnd") return XMPP::Status::DND; else if (stat == "invisible") return XMPP::Status::Invisible; else if (stat == "chat") return XMPP::Status::FFC; else return XMPP::Status::Away; } void Status::setType(const QString &stat) { setType(txt2type(stat)); } void Status::setShow(const QString &_show) { d->show = _show; } void Status::setStatus(const QString &_status) { d->status = _status; } void Status::setTimeStamp(const QDateTime &_timestamp) { d->timeStamp = _timestamp; } void Status::setKeyID(const QString &key) { d->key = key; } void Status::setXSigned(const QString &s) { d->xsigned = s; } void Status::setSongTitle(const QString &_songtitle) { d->songTitle = _songtitle; } void Status::setCaps(const CapsSpec &caps) { d->caps = caps; } void Status::setMUC() { d->isMUC = true; } void Status::setMUCItem(const MUCItem &i) { d->hasMUCItem = true; d->mucItem = i; } void Status::setMUCDestroy(const MUCDestroy &i) { d->hasMUCDestroy = true; d->mucDestroy = i; } void Status::setMUCHistory(int maxchars, int maxstanzas, int seconds, const QDateTime &since) { d->mucHistoryMaxChars = maxchars; d->mucHistoryMaxStanzas = maxstanzas; d->mucHistorySeconds = seconds; d->mucHistorySince = since; } const QByteArray &Status::photoHash() const { return d->photoHash; } void Status::setPhotoHash(const QByteArray &h) { d->photoHash = h; d->hasPhotoHash = true; } bool Status::hasPhotoHash() const { return d->hasPhotoHash; } void Status::addBoBData(const BoBData &bob) { d->bobDataList.append(bob); } QList Status::bobDataList() const { return d->bobDataList; } bool Status::isAvailable() const { return d->isAvailable; } bool Status::isAway() const { return (d->show == "away" || d->show == "xa" || d->show == "dnd"); } bool Status::isInvisible() const { return d->isInvisible; } int Status::priority() const { return d->priority; } Status::Type Status::type() const { Status::Type type = Status::Online; if (!isAvailable()) { type = Status::Offline; } else if (isInvisible()) { type = Status::Invisible; } else { QString s = show(); if (s == "away") type = Status::Away; else if (s == "xa") type = Status::XA; else if (s == "dnd") type = Status::DND; else if (s == "chat") type = Status::FFC; } return type; } QString Status::typeString() const { QString stat; switch (type()) { case XMPP::Status::Offline: stat = "offline"; break; case XMPP::Status::Online: stat = "online"; break; case XMPP::Status::Away: stat = "away"; break; case XMPP::Status::XA: stat = "xa"; break; case XMPP::Status::DND: stat = "dnd"; break; case XMPP::Status::Invisible: stat = "invisible"; break; case XMPP::Status::FFC: stat = "chat"; break; default: stat = "away"; } return stat; } const QString &Status::show() const { return d->show; } const QString &Status::status() const { return d->status; } QDateTime Status::timeStamp() const { return d->timeStamp; } const QString &Status::keyID() const { return d->key; } const QString &Status::xsigned() const { return d->xsigned; } const QString &Status::songTitle() const { return d->songTitle; } const CapsSpec &Status::caps() const { return d->caps; } bool Status::isMUC() const { return d->isMUC || !d->mucPassword.isEmpty() || hasMUCHistory(); } bool Status::hasMUCItem() const { return d->hasMUCItem; } const MUCItem &Status::mucItem() const { return d->mucItem; } bool Status::hasMUCDestroy() const { return d->hasMUCDestroy; } const MUCDestroy &Status::mucDestroy() const { return d->mucDestroy; } const QList &Status::getMUCStatuses() const { return d->mucStatuses; } void Status::addMUCStatus(int i) { d->mucStatuses += i; } const QString &Status::mucPassword() const { return d->mucPassword; } bool Status::hasMUCHistory() const { return d->mucHistoryMaxChars >= 0 || d->mucHistoryMaxStanzas >= 0 || d->mucHistorySeconds >= 0 || !d->mucHistorySince.isNull(); } int Status::mucHistoryMaxChars() const { return d->mucHistoryMaxChars; } int Status::mucHistoryMaxStanzas() const { return d->mucHistoryMaxStanzas; } int Status::mucHistorySeconds() const { return d->mucHistorySeconds; } const QDateTime &Status::mucHistorySince() const { return d->mucHistorySince; } void Status::setMUCPassword(const QString &i) { d->mucPassword = i; } int Status::errorCode() const { return d->ecode; } const QString &Status::errorString() const { return d->estr; } //--------------------------------------------------------------------------- // Resource //--------------------------------------------------------------------------- Resource::Resource(const QString &name, const Status &stat) : v_name(name), v_status(stat) { } const QString &Resource::name() const { return v_name; } int Resource::priority() const { return v_status.priority(); } const Status &Resource::status() const { return v_status; } void Resource::setName(const QString &_name) { v_name = _name; } void Resource::setStatus(const Status &_status) { v_status = _status; } //--------------------------------------------------------------------------- // ResourceList //--------------------------------------------------------------------------- ResourceList::ResourceList() : QList() { } ResourceList::~ResourceList() { } ResourceList::Iterator ResourceList::find(const QString &_find) { for (ResourceList::Iterator it = begin(); it != end(); ++it) { if ((*it).name() == _find) return it; } return end(); } ResourceList::Iterator ResourceList::priority() { ResourceList::Iterator highest = end(); for (ResourceList::Iterator it = begin(); it != end(); ++it) { if (highest == end() || (*it).priority() > (*highest).priority()) highest = it; } return highest; } ResourceList::ConstIterator ResourceList::find(const QString &_find) const { for (ResourceList::ConstIterator it = begin(); it != end(); ++it) { if ((*it).name() == _find) return it; } return end(); } ResourceList::ConstIterator ResourceList::priority() const { ResourceList::ConstIterator highest = end(); for (ResourceList::ConstIterator it = begin(); it != end(); ++it) { if (highest == end() || (*it).priority() > (*highest).priority()) highest = it; } return highest; } //--------------------------------------------------------------------------- // RosterItem //--------------------------------------------------------------------------- RosterItem::RosterItem(const Jid &_jid) : v_jid(_jid), v_push(false) { } RosterItem::RosterItem(const RosterItem &item) : v_jid(item.v_jid), v_name(item.v_name), v_groups(item.v_groups), v_subscription(item.v_subscription), v_ask(item.v_ask), v_push(item.v_push) { } RosterItem::~RosterItem() { } const Jid &RosterItem::jid() const { return v_jid; } const QString &RosterItem::name() const { return v_name; } const QStringList &RosterItem::groups() const { return v_groups; } const Subscription &RosterItem::subscription() const { return v_subscription; } const QString &RosterItem::ask() const { return v_ask; } bool RosterItem::isPush() const { return v_push; } bool RosterItem::inGroup(const QString &g) const { for (QStringList::ConstIterator it = v_groups.begin(); it != v_groups.end(); ++it) { if (*it == g) return true; } return false; } void RosterItem::setJid(const Jid &_jid) { v_jid = _jid; } void RosterItem::setName(const QString &_name) { v_name = _name; } void RosterItem::setGroups(const QStringList &_groups) { v_groups = _groups; } void RosterItem::setSubscription(const Subscription &type) { v_subscription = type; } void RosterItem::setAsk(const QString &_ask) { v_ask = _ask; } void RosterItem::setIsPush(bool b) { v_push = b; } bool RosterItem::addGroup(const QString &g) { if (inGroup(g)) return false; v_groups += g; return true; } bool RosterItem::removeGroup(const QString &g) { for (QStringList::Iterator it = v_groups.begin(); it != v_groups.end(); ++it) { if (*it == g) { v_groups.erase(it); return true; } } return false; } QDomElement RosterItem::toXml(QDomDocument *doc) const { QDomElement item = doc->createElement("item"); item.setAttribute("jid", v_jid.full()); item.setAttribute("name", v_name); item.setAttribute("subscription", v_subscription.toString()); if (!v_ask.isEmpty()) item.setAttribute("ask", v_ask); for (QStringList::ConstIterator it = v_groups.begin(); it != v_groups.end(); ++it) item.appendChild(textTag(doc, "group", *it)); return item; } bool RosterItem::fromXml(const QDomElement &item) { if (item.tagName() != "item") return false; Jid j(item.attribute("jid")); if (!j.isValid()) return false; QString na = item.attribute("name"); Subscription s; if (!s.fromString(item.attribute("subscription"))) return false; QStringList g; for (QDomNode n = item.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement i = n.toElement(); if (i.isNull()) continue; if (i.tagName() == "group") g += tagContent(i); } QString a = item.attribute("ask"); v_jid = j; v_name = na; v_subscription = s; v_groups = g; v_ask = a; return true; } //--------------------------------------------------------------------------- // Roster //--------------------------------------------------------------------------- class Roster::Private { public: QString groupsDelimiter; }; Roster::Roster() : QList(), d(new Roster::Private) { } Roster::~Roster() { delete d; } Roster::Roster(const Roster &other) : QList(other), d(new Roster::Private) { d->groupsDelimiter = other.d->groupsDelimiter; } Roster &Roster::operator=(const Roster &other) { QList::operator=(other); d->groupsDelimiter = other.d->groupsDelimiter; return *this; } Roster::Iterator Roster::find(const Jid &j) { for (Roster::Iterator it = begin(); it != end(); ++it) { if ((*it).jid().compare(j)) return it; } return end(); } Roster::ConstIterator Roster::find(const Jid &j) const { for (Roster::ConstIterator it = begin(); it != end(); ++it) { if ((*it).jid().compare(j)) return it; } return end(); } void Roster::setGroupsDelimiter(const QString &groupsDelimiter) { d->groupsDelimiter = groupsDelimiter; } QString Roster::groupsDelimiter() const { return d->groupsDelimiter; } //--------------------------------------------------------------------------- // FormField //--------------------------------------------------------------------------- FormField::FormField(const QString &type, const QString &value) { v_type = misc; if (!type.isEmpty()) { int x = tagNameToType(type); if (x != -1) v_type = x; } v_value = value; } FormField::~FormField() { } int FormField::type() const { return v_type; } QString FormField::realName() const { return typeToTagName(v_type); } QString FormField::fieldName() const { switch (v_type) { case username: return QObject::tr("Username"); case nick: return QObject::tr("Nickname"); case password: return QObject::tr("Password"); case name: return QObject::tr("Name"); case first: return QObject::tr("First Name"); case last: return QObject::tr("Last Name"); case email: return QObject::tr("E-mail"); case address: return QObject::tr("Address"); case city: return QObject::tr("City"); case state: return QObject::tr("State"); case zip: return QObject::tr("Zipcode"); case phone: return QObject::tr("Phone"); case url: return QObject::tr("URL"); case date: return QObject::tr("Date"); case misc: return QObject::tr("Misc"); default: return ""; }; } bool FormField::isSecret() const { return (type() == password); } const QString &FormField::value() const { return v_value; } void FormField::setType(int x) { v_type = x; } bool FormField::setType(const QString &in) { int x = tagNameToType(in); if (x == -1) return false; v_type = x; return true; } void FormField::setValue(const QString &in) { v_value = in; } int FormField::tagNameToType(const QString &in) const { if (!in.compare("username")) return username; if (!in.compare("nick")) return nick; if (!in.compare("password")) return password; if (!in.compare("name")) return name; if (!in.compare("first")) return first; if (!in.compare("last")) return last; if (!in.compare("email")) return email; if (!in.compare("address")) return address; if (!in.compare("city")) return city; if (!in.compare("state")) return state; if (!in.compare("zip")) return zip; if (!in.compare("phone")) return phone; if (!in.compare("url")) return url; if (!in.compare("date")) return date; if (!in.compare("misc")) return misc; return -1; } QString FormField::typeToTagName(int type) const { switch (type) { case username: return "username"; case nick: return "nick"; case password: return "password"; case name: return "name"; case first: return "first"; case last: return "last"; case email: return "email"; case address: return "address"; case city: return "city"; case state: return "state"; case zip: return "zipcode"; case phone: return "phone"; case url: return "url"; case date: return "date"; case misc: return "misc"; default: return ""; }; } //--------------------------------------------------------------------------- // Form //--------------------------------------------------------------------------- Form::Form(const Jid &j) : QList() { setJid(j); } Form::~Form() { } Jid Form::jid() const { return v_jid; } QString Form::instructions() const { return v_instructions; } QString Form::key() const { return v_key; } void Form::setJid(const Jid &j) { v_jid = j; } void Form::setInstructions(const QString &s) { v_instructions = s; } void Form::setKey(const QString &s) { v_key = s; } //--------------------------------------------------------------------------- // SearchResult //--------------------------------------------------------------------------- SearchResult::SearchResult(const Jid &jid) { setJid(jid); } SearchResult::~SearchResult() { } const Jid &SearchResult::jid() const { return v_jid; } const QString &SearchResult::nick() const { return v_nick; } const QString &SearchResult::first() const { return v_first; } const QString &SearchResult::last() const { return v_last; } const QString &SearchResult::email() const { return v_email; } void SearchResult::setJid(const Jid &jid) { v_jid = jid; } void SearchResult::setNick(const QString &nick) { v_nick = nick; } void SearchResult::setFirst(const QString &first) { v_first = first; } void SearchResult::setLast(const QString &last) { v_last = last; } void SearchResult::setEmail(const QString &email) { v_email = email; } PubSubItem::PubSubItem() { } PubSubItem::PubSubItem(const QString &id, const QDomElement &payload) : id_(id), payload_(payload) { } const QString &PubSubItem::id() const { return id_; } const QDomElement &PubSubItem::payload() const { return payload_; } PubSubRetraction::PubSubRetraction() { } PubSubRetraction::PubSubRetraction(const QString &id) : id_(id) { } const QString &PubSubRetraction::id() const { return id_; } // ========================================= // CaptchaChallenge // ========================================= class CaptchaChallengePrivate : public QSharedData { public: CaptchaChallengePrivate() : state(CaptchaChallenge::New) { } CaptchaChallenge::State state; Jid arbiter; Jid offendedJid; XData form; QDateTime dt; QString explanation; UrlList urls; }; CaptchaChallenge::CaptchaChallenge() : d(new CaptchaChallengePrivate) { } CaptchaChallenge::CaptchaChallenge(const CaptchaChallenge &other) : d(other.d) { } CaptchaChallenge::CaptchaChallenge(const Message &m) : d(new CaptchaChallengePrivate) { if (m.spooled()) { if (m.timeStamp().secsTo(QDateTime::currentDateTime()) < Timeout) { return; } d->dt = m.timeStamp(); } else { d->dt = QDateTime::currentDateTime(); } if (m.getForm().registrarType() != "urn:xmpp:captcha" || m.getForm().type() != XData::Data_Form) return; if (m.id().isEmpty() || m.getForm().getField("challenge").value().value(0) != m.id()) return; if (m.getForm().getField("from").value().value(0).isEmpty()) return; d->form = m.getForm(); d->explanation = m.body(); d->urls = m.urlList(); d->arbiter = m.from(); d->offendedJid = Jid(m.getForm().getField("from").value().value(0)); } CaptchaChallenge::~CaptchaChallenge() { } CaptchaChallenge &CaptchaChallenge::operator=(const CaptchaChallenge &from) { d = from.d; return *this; } const XData &CaptchaChallenge::form() const { return d->form; } QString CaptchaChallenge::explanation() const { return d->explanation; } const UrlList &CaptchaChallenge::urls() const { return d->urls; } CaptchaChallenge::State CaptchaChallenge::state() const { return d->state; } CaptchaChallenge::Result CaptchaChallenge::validateResponse(const XData &xd) { Q_UNUSED(xd) d->state = Fail; return Unavailable; // TODO implement response validation } bool CaptchaChallenge::isValid() const { return d->dt.isValid() && d->dt.secsTo(QDateTime::currentDateTime()) < Timeout && d->form.fields().count() > 0; } const Jid &CaptchaChallenge::offendedJid() const { return d->offendedJid; } const Jid &CaptchaChallenge::arbiter() const { return d->arbiter; } Thumbnail::Thumbnail(const QDomElement &el) { QString ns(QLatin1String(XMPP_THUMBS_NS)); if (el.namespaceURI() == ns) { uri = QUrl(el.attribute("uri"), QUrl::StrictMode); mimeType = el.attribute("mime-type"); width = el.attribute("width").toUInt(); height = el.attribute("height").toUInt(); } } QDomElement Thumbnail::toXml(QDomDocument *doc) const { auto el = doc->createElementNS(XMPP_THUMBS_NS, QStringLiteral("thumbnail")); el.setAttribute("uri", uri.toString(QUrl::FullyEncoded)); el.setAttribute("mime-type", mimeType); if (width && height) { el.setAttribute("width", width); el.setAttribute("height", height); } return el; } } // namespace XMPP psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_address.h000066400000000000000000000033511370065651000245270ustar00rootroot00000000000000/* * Copyright (C) 2006 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_ADDRESS_H #define XMPP_ADDRESS_H #include "xmpp/jid/jid.h" #include "xmpp_stanza.h" #include class QDomElement; namespace XMPP { class Address { public: typedef enum { Unknown, To, Cc, Bcc, ReplyTo, ReplyRoom, NoReply, OriginalFrom, OriginalTo } Type; Address(Type type = Unknown, const Jid &jid = Jid()); Address(const QDomElement &); const Jid & jid() const; const QString &uri() const; const QString &node() const; const QString &desc() const; bool delivered() const; Type type() const; QDomElement toXml(Stanza &) const; void fromXml(const QDomElement &t); void setJid(const Jid &); void setUri(const QString &); void setNode(const QString &); void setDesc(const QString &); void setDelivered(bool); void setType(Type); private: Jid v_jid; QString v_uri, v_node, v_desc; bool v_delivered; Type v_type; }; typedef QList
AddressList; } // namespace XMPP #endif // XMPP_ADDRESS_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_agentitem.h000066400000000000000000000031161370065651000250560ustar00rootroot00000000000000/* * xmpp_agentitem.h * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_AGENTITEM #define XMPP_AGENTITEM #include "xmpp/jid/jid.h" #include "xmpp_features.h" #include namespace XMPP { class AgentItem { public: AgentItem() { } const Jid & jid() const { return v_jid; } const QString & name() const { return v_name; } const QString & category() const { return v_category; } const QString & type() const { return v_type; } const Features &features() const { return v_features; } void setJid(const Jid &j) { v_jid = j; } void setName(const QString &n) { v_name = n; } void setCategory(const QString &c) { v_category = c; } void setType(const QString &t) { v_type = t; } void setFeatures(const Features &f) { v_features = f; } private: Jid v_jid; QString v_name, v_category, v_type; Features v_features; }; } // namespace XMPP #endif // XMPP_AGENTITEM psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_bitsofbinary.cpp000066400000000000000000000120721370065651000261300ustar00rootroot00000000000000/* * Copyright (C) 2010 Sergey Ilinykh * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp_bitsofbinary.h" #include "xmpp_client.h" #include "xmpp_hash.h" #include "xmpp_tasks.h" #include "xmpp_xmlcommon.h" #include #include using namespace XMPP; class BoBData::Private : public QSharedData { public: QByteArray data; // file data itself QString type; // mime type. e.g. image/png // QString cid; // content identifier without "cid:" Hash hash; unsigned int maxAge; // seconds to live }; BoBData::BoBData() : d(new Private) { } BoBData::BoBData(const BoBData &other) : d(other.d) { } BoBData::BoBData(const QDomElement &e) : d(new Private) { fromXml(e); } BoBData::~BoBData() { } BoBData &BoBData::operator=(const BoBData &other) { if (this == &other) return *this; // Protect against self-assignment d = other.d; return *this; } bool BoBData::isNull() const { return !d->hash.isValid() || d->data.isNull(); } Hash BoBData::cidToHash(const QString &cid) { if (!cid.endsWith(QLatin1String("@bob.xmpp.org"))) return Hash(); return Hash::from(cid.leftRef(cid.size() - sizeof("@bob.xmpp.org") + 1)); } QString BoBData::cid() const { if (isNull()) return QString(); return QString("%1+%2@bob.xmpp.org").arg(d->hash.stringType(), QString::fromLatin1(d->hash.data().toHex())); } void BoBData::setCid(const QString &cid) { d->hash = cidToHash(cid); } const Hash &BoBData::hash() const { return d->hash; } void BoBData::setHash(const Hash &hash) { d->hash = hash; } QByteArray BoBData::data() const { return d->data; } void BoBData::setData(const QByteArray &data) { d->data = data; } QString BoBData::type() const { return d->type; } void BoBData::setType(const QString &type) { d->type = type; } unsigned int BoBData::maxAge() const { return d->maxAge; } void BoBData::setMaxAge(unsigned int maxAge) { d->maxAge = maxAge; } void BoBData::fromXml(const QDomElement &data) { setCid(data.attribute("cid")); d->maxAge = data.attribute("max-age").toInt(); d->type = data.attribute("type"); d->data = QByteArray::fromBase64(data.text().replace("\n", "").toLatin1()); } QDomElement BoBData::toXml(QDomDocument *doc) const { QDomElement data = doc->createElementNS("urn:xmpp:bob", "data"); data.setAttribute("cid", cid()); data.setAttribute("max-age", d->maxAge); data.setAttribute("type", d->type); data.appendChild(doc->createTextNode(QString::fromLatin1(d->data.toBase64()))); return data; } // --------------------------------------------------------- // BoBCache // --------------------------------------------------------- BoBCache::BoBCache(QObject *parent) : QObject(parent) { } //------------------------------------------------------------------------------ // BoBManager //------------------------------------------------------------------------------ BoBManager::BoBManager(Client *client) : QObject(client), _cache(nullptr) { new JT_BoBServer(client->rootTask()); } void BoBManager::setCache(BoBCache *cache) { _cache = cache; } BoBData BoBManager::bobData(const QString &cid) { BoBData bd; Hash h = BoBData::cidToHash(cid); if (_cache) { bd = _cache->get(h); } if (!bd.isNull()) return bd; auto it = _localFiles.find(h); if (it != _localFiles.end()) { QPair fileData = it.value(); QFile file(fileData.first); if (file.open(QIODevice::ReadOnly)) { bd.setHash(h); bd.setData(file.readAll()); bd.setMaxAge(0); bd.setType(fileData.second); } } return bd; } BoBData BoBManager::append(const QByteArray &data, const QString &type, unsigned int maxAge) { BoBData b; b.setHash(Hash::from(Hash::Sha1, data)); b.setData(data); b.setMaxAge(maxAge); b.setType(type); if (_cache) { _cache->put(b); } return b; } XMPP::Hash BoBManager::append(QFile &file, const QString &type) { bool isOpen = file.isOpen(); if (isOpen || file.open(QIODevice::ReadOnly)) { Hash h = Hash::from(Hash::Sha1, &file); if (h.isValid()) { _localFiles[h] = QPair(file.fileName(), type); } if (!isOpen) { file.close(); } return h; } return XMPP::Hash(); } void BoBManager::append(const BoBData &data) { if (!data.isNull() && _cache) { _cache->put(data); } } psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_bitsofbinary.h000066400000000000000000000050641370065651000256000ustar00rootroot00000000000000/* * Copyright (C) 2010 Sergey Ilinykh * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_BITSOFBINARY_H #define XMPP_BITSOFBINARY_H #include "xmpp/jid/jid.h" #include "xmpp_hash.h" #include #include #include #include #include namespace XMPP { class Client; class JT_BitsOfBinary; class BoBData { class Private; public: BoBData(); BoBData(const BoBData &other); BoBData(const QDomElement &); ~BoBData(); BoBData &operator=(const BoBData &other); bool isNull() const; static Hash cidToHash(const QString &cid); QString cid() const; void setCid(const QString &); const Hash &hash() const; void setHash(const Hash &hash); QByteArray data() const; void setData(const QByteArray &); QString type() const; void setType(const QString &); unsigned int maxAge() const; void setMaxAge(unsigned int); void fromXml(const QDomElement &); QDomElement toXml(QDomDocument *doc) const; private: QSharedDataPointer d; }; class BoBCache : public QObject { Q_OBJECT public: BoBCache(QObject *parent); virtual void put(const BoBData &) = 0; virtual BoBData get(const Hash &) = 0; }; class BoBManager : public QObject { Q_OBJECT public: BoBManager(Client *); void setCache(BoBCache *); BoBData bobData(const QString &); // file data, mime type, max age in seconds BoBData append(const QByteArray &data, const QString &type, unsigned int maxAge = 0); Hash append(QFile & file, const QString &type = "application/octet-stream"); // this method adds just to runtime cache void append(const BoBData &); private: BoBCache * _cache; QHash> _localFiles; // cid => (filename, mime) }; } // namespace XMPP #endif // XMPP_BITSOFBINARY_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_bytestream.cpp000066400000000000000000000034011370065651000256100ustar00rootroot00000000000000/* * bytestream_manager.cpp - base class for bytestreams over xmpp * Copyright (C) 2003 Justin Karneges, Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp_bytestream.h" #include "xmpp_client.h" #include #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) #include #endif namespace XMPP { BytestreamManager::BytestreamManager(Client *parent) : QObject(parent) { } BytestreamManager::~BytestreamManager() { } QString BytestreamManager::genUniqueSID(const Jid &peer) const { // get unused key QString sid; do { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) sid = QString("%1%2").arg(sidPrefix()).arg(QRandomGenerator::global()->generate() & 0xffff, 4, 16, QChar('0')); #else sid = QString("%1%2").arg(sidPrefix()).arg(qrand() & 0xffff, 4, 16, QChar('0')); #endif } while (!isAcceptableSID(peer, sid)); return sid; } /** * Deletes conection in specified interval */ void BytestreamManager::deleteConnection(BSConnection *c, int msec) { if (msec) { QTimer::singleShot(msec, c, SLOT(deleteLater())); } else { delete c; } } } // namespace XMPP psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_bytestream.h000066400000000000000000000041161370065651000252610ustar00rootroot00000000000000/* * bytestream_manager.h - base class for bytestreams over xmpp * Copyright (C) 2003 Justin Karneges, Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef BYTESTREAM_MANAGER_H #define BYTESTREAM_MANAGER_H #include "bytestream.h" #include "xmpp/jid/jid.h" #include namespace XMPP { class BytestreamManager; class Client; class BSConnection : public ByteStream { public: enum Error { ErrRefused = ErrCustom, ErrConnect, ErrProxy, ErrSocket }; enum State { Idle, Requesting, Connecting, WaitingForAccept, Active }; BSConnection(QObject *parent = nullptr) : ByteStream(parent) { } virtual void connectToJid(const Jid &peer, const QString &sid) = 0; virtual void accept() = 0; virtual Jid peer() const = 0; virtual QString sid() const = 0; virtual BytestreamManager *manager() const = 0; }; class BytestreamManager : public QObject { Q_OBJECT public: BytestreamManager(Client *); virtual ~BytestreamManager(); virtual bool isAcceptableSID(const Jid &peer, const QString &sid) const = 0; QString genUniqueSID(const Jid &peer) const; virtual BSConnection *createConnection() = 0; virtual void deleteConnection(BSConnection *c, int msec = 0); protected: virtual const char *sidPrefix() const = 0; signals: void incomingReady(); }; } // namespace XMPP #endif // BYTESTREAM_MANAGER_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_caps.cpp000066400000000000000000000336031370065651000243660ustar00rootroot00000000000000/* * capsregistry.cpp * Copyright (C) 2006-2016 Remko Troncon, Sergey Ilinykh * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ /* * We have one global CapsRegistry to cache disco results from all application. * Each Client has its own CapsManager to control caps just for an Account. * Caps result is stored in CapsInfo class and node from presence * goes to CapsSpec. */ #include "xmpp_caps.h" #include "xmpp_client.h" #include "xmpp_discoinfotask.h" #include "xmpp_features.h" #include "xmpp_xmlcommon.h" #include #include #include #include #include namespace XMPP { QDomElement CapsInfo::toXml(QDomDocument *doc) const { QDomElement caps = doc->createElement("info"); caps.appendChild(textTag(doc, "atime", _lastSeen.toString(Qt::ISODate))); caps.appendChild(_disco.toDiscoInfoResult(doc)); return caps; } CapsInfo CapsInfo::fromXml(const QDomElement &caps) { QDateTime lastSeen = QDateTime::fromString(caps.firstChildElement("atime").nodeValue(), Qt::ISODate); DiscoItem item = DiscoItem::fromDiscoInfoResult(caps.firstChildElement("query")); if (item.features().isEmpty()) { // it's hardly possible if client does not support anything. return CapsInfo(); } return CapsInfo(item, lastSeen); } // ----------------------------------------------------------------------------- /** * \class CapsRegistry * \brief A singleton class managing the capabilities of clients. */ CapsRegistry *CapsRegistry::instance_ = nullptr; /** * \brief Default constructor. */ CapsRegistry::CapsRegistry(QObject *parent) : QObject(parent) { } CapsRegistry *CapsRegistry::instance() { if (!instance_) { instance_ = new CapsRegistry(qApp); } return instance_; } void CapsRegistry::setInstance(CapsRegistry *instance) { instance_ = instance; } /** * \brief Convert all capabilities info to XML. */ void CapsRegistry::save() { // Generate XML QDomDocument doc; QDomElement capabilities = doc.createElement("capabilities"); doc.appendChild(capabilities); QHash::ConstIterator i = capsInfo_.constBegin(); for (; i != capsInfo_.end(); i++) { QDomElement info = i.value().toXml(&doc); info.setAttribute("node", i.key()); capabilities.appendChild(info); } saveData(doc.toString().toUtf8()); } void CapsRegistry::saveData(const QByteArray &data) { Q_UNUSED(data) return; } QByteArray CapsRegistry::loadData() { return QByteArray(); } /** * \brief Sets the file to save the capabilities info to */ void CapsRegistry::load() { QByteArray data = loadData(); if (data.isEmpty()) { return; } // Load settings QDomDocument doc; if (!doc.setContent(QString::fromUtf8(data))) { qWarning() << "CapsRegistry: Cannnot parse input"; return; } QDomElement caps = doc.documentElement(); if (caps.tagName() != "capabilities") { qWarning("caps.cpp: Invalid capabilities element"); return; } // keep unseen info for last 3 month. adjust if required QDateTime validTime = QDateTime::currentDateTime().addMonths(-3); for (QDomNode n = caps.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement i = n.toElement(); if (i.isNull()) { qWarning("capsregistry.cpp: Null element"); continue; } if (i.tagName() == "info") { QString node = i.attribute("node"); int sep = node.indexOf('#'); if (sep > 0 && sep + 1 < node.length()) { CapsInfo info = CapsInfo::fromXml(i); if (info.isValid() && info.lastSeen() > validTime) { capsInfo_[node] = CapsInfo::fromXml(i); } // qDebug() << QString("Read %1 %2").arg(node).arg(ver); } else { qWarning() << "capsregistry.cpp: Node" << node << "invalid"; } } else { qWarning("capsregistry.cpp: Unknown element"); } } } /** * \brief Registers capabilities of a client. */ void CapsRegistry::registerCaps(const CapsSpec &spec, const DiscoItem &item) { QString dnode = spec.flatten(); if (!isRegistered(dnode)) { CapsInfo info(item); capsInfo_[dnode] = info; emit registered(spec); } } /** * \brief Checks if capabilities have been registered. */ bool CapsRegistry::isRegistered(const QString &spec) const { return capsInfo_.contains(spec); } DiscoItem CapsRegistry::disco(const QString &spec) const { CapsInfo ci = capsInfo_.value(spec); return ci.disco(); } /*-------------------------------------------------------------- _____ __ __ / ____| | \/ | | | __ _ _ __ ___| \ / | __ _ _ __ __ _ __ _ ___ _ __ | | / _` | '_ \/ __| |\/| |/ _` | '_ \ / _` |/ _` |/ _ \ '__| | |___| (_| | |_) \__ \ | | | (_| | | | | (_| | (_| | __/ | \_____\__,_| .__/|___/_| |_|\__,_|_| |_|\__,_|\__, |\___|_| | | __/ | |_| |___/ --------------------------------------------------------------*/ /** * \class CapsManager * \brief A class managing all the capabilities of JIDs and their * clients. */ /** * \brief Default constructor. */ CapsManager::CapsManager(Client *client) : QObject(client), client_(client), isEnabled_(true) { } CapsManager::~CapsManager() { } /** * \brief Checks whether the caps manager is enabled (and does lookups). */ bool CapsManager::isEnabled() { return isEnabled_; } /** * \brief Enables or disables the caps manager. */ void CapsManager::setEnabled(bool b) { isEnabled_ = b; } /** * \brief Registers new incoming capabilities information of a JID. * If the features of the entity are unknown, discovery requests are sent to * retrieve the information. * * @param jid The entity's JID * @param node The entity's caps node * @param ver The entity's caps version * @param ext The entity's caps extensions */ void CapsManager::updateCaps(const Jid &jid, const CapsSpec &c) { if (jid.compare(client_->jid(), false)) return; QString fullNode = c.flatten(); if (capsSpecs_[jid.full()] != c) { // qDebug() << QString("caps.cpp: Updating caps for %1 // (node=%2,ver=%3,ext=%4)").arg(QString(jid.full()).replace('%',"%%")).arg(node).arg(ver).arg(ext); // Unregister from all old caps node capsJids_[capsSpecs_[jid.full()].flatten()].removeAll(jid.full()); if (c.isValid()) { // Register with all new caps nodes capsSpecs_[jid.full()] = c; if (!capsJids_[fullNode].contains(jid.full())) { capsJids_[fullNode].push_back(jid.full()); } emit capsChanged(jid); // Register new caps and check if we need to discover features if (isEnabled()) { if (!CapsRegistry::instance()->isRegistered(fullNode) && capsJids_[fullNode].count() == 1) { // qDebug() << QString("caps.cpp: Sending disco request to %1, // node=%2").arg(QString(jid.full()).replace('%',"%%")).arg(node + "#" + s.extensions()); JT_DiscoInfo *disco = new JT_DiscoInfo(client_->rootTask()); disco->setAllowCache(false); connect(disco, SIGNAL(finished()), SLOT(discoFinished())); disco->get(jid, fullNode); disco->go(true); } } } else { // Remove all caps specifications qWarning() << QString("caps.cpp: Illegal caps info from %1: node=%2, ver=%3") .arg(QString(jid.full()).replace('%', "%%")) .arg(fullNode) .arg(c.version()); capsSpecs_.remove(jid.full()); } } else { // Add to the list of jids capsJids_[fullNode].push_back(jid.full()); } } /** * \brief Removes all feature information for a given JID. * * @param jid The entity's JID */ void CapsManager::disableCaps(const Jid &jid) { // qDebug() << QString("caps.cpp: Disabling caps for %1.").arg(QString(jid.full()).replace('%',"%%")); if (capsEnabled(jid)) { QString node = capsSpecs_[jid.full()].flatten(); if (!node.isEmpty()) { capsJids_[node].removeAll(jid.full()); } capsSpecs_.remove(jid.full()); emit capsChanged(jid); } } /** * \brief Called when a reply to disco#info request was received. * If the result was succesful, the resulting features are recorded in the * features database for the requested node, and all the affected jids are * put in the queue for update notification. */ void CapsManager::discoFinished() { JT_DiscoInfo *task = static_cast(sender()); updateDisco(task->jid(), task->item()); } void CapsManager::updateDisco(const Jid &jid, const DiscoItem &item) { CapsSpec cs = capsSpecs_.value(jid.full()); if (!cs.isValid()) { return; } if (item.capsHash(cs.hashAlgorithm()) == cs.version()) { CapsRegistry::instance()->registerCaps(cs, item); } } /** * \brief This slot is called whenever capabilities of a client were discovered. * All jids with the corresponding client are updated. */ void CapsManager::capsRegistered(const CapsSpec &cs) { // Notify affected jids. for (const QString &s : capsJids_[cs.flatten()]) { // qDebug() << QString("caps.cpp: Notifying %1.").arg(s.replace('%',"%%")); emit capsChanged(s); } } /** * \brief Checks whether a given JID is broadcastingn its entity capabilities. */ bool CapsManager::capsEnabled(const Jid &jid) const { return capsSpecs_.contains(jid.full()); } /** * \brief Requests the list of features of a given JID. */ XMPP::DiscoItem CapsManager::disco(const Jid &jid) const { // qDebug() << "caps.cpp: Retrieving features of " << jid.full(); QStringList f; if (!capsEnabled(jid)) { return DiscoItem(); } QString node = capsSpecs_[jid.full()].flatten(); // qDebug() << QString(" %1").arg(CapsRegistry::instance()->features(s).list().join("\n")); return CapsRegistry::instance()->disco(node); } /** * \brief Requests the list of features of a given JID. */ XMPP::Features CapsManager::features(const Jid &jid) const { return disco(jid).features(); } /** * \brief Returns the client name of a given jid. * \param jid the jid to retrieve the client name of */ QString CapsManager::clientName(const Jid &jid) const { if (capsEnabled(jid)) { CapsSpec cs = capsSpecs_[jid.full()]; QString name; QString cs_str = cs.flatten(); if (CapsRegistry::instance()->isRegistered(cs_str)) { DiscoItem disco = CapsRegistry::instance()->disco(cs_str); XData si = disco.registeredExtension(QLatin1String("urn:xmpp:dataforms:softwareinfo")); if (si.isValid()) { name = si.getField("software").value().value(0); } if (name.isEmpty()) { const DiscoItem::Identities &i = disco.identities(); if (i.count() > 0) { name = i.first().name; } } } // Try to be intelligent about the name if (name.isEmpty()) { const QString &node = cs.node(); int startPos = 0, ds = 0; if (node.startsWith(QStringLiteral("http://"))) startPos = 7; else if (node.startsWith(QStringLiteral("https://"))) startPos = 8; if (node.startsWith(QStringLiteral("www."))) startPos += 4; if (node.endsWith(QStringLiteral("/caps"))) { ds = 5; } name = QStringRef(&node, startPos, node.size() - startPos - ds).toString(); } return name; } else { return QString(); } } /** * \brief Returns the client version of a given jid. */ QString CapsManager::clientVersion(const Jid &jid) const { if (!capsEnabled(jid)) return QString(); QString version; const CapsSpec &cs = capsSpecs_[jid.full()]; QString cs_str = cs.flatten(); if (CapsRegistry::instance()->isRegistered(cs_str)) { XData form = CapsRegistry::instance()->disco(cs_str).registeredExtension("urn:xmpp:dataforms:softwareinfo"); version = form.getField("software_version").value().value(0); } return version; } /** * \brief Returns the OS version of a given jid. */ QString CapsManager::osVersion(const Jid &jid) const { QString os_str; if (capsEnabled(jid)) { QString cs_str = capsSpecs_[jid.full()].flatten(); if (CapsRegistry::instance()->isRegistered(cs_str)) { XData form = CapsRegistry::instance()->disco(cs_str).registeredExtension("urn:xmpp:dataforms:softwareinfo"); os_str = form.getField("os").value().value(0).trimmed(); if (!os_str.isEmpty()) { QString os_ver = form.getField("os_version").value().value(0).trimmed(); if (!os_ver.isEmpty()) os_str.append(" " + os_ver); } } } return os_str; } CapsSpec CapsManager::capsSpec(const Jid &jid) const { return capsSpecs_.value(jid.full()); } } // namespace XMPP psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_caps.h000066400000000000000000000066541370065651000240410ustar00rootroot00000000000000/* * Copyright (C) 2016 Remko Troncon, Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_CAPS_H #define XMPP_CAPS_H #include "xmpp_discoitem.h" #include "xmpp_features.h" #include "xmpp_status.h" #include namespace XMPP { class CapsInfo { public: inline CapsInfo() { } inline CapsInfo(const XMPP::DiscoItem &disco, const QDateTime &lastSeen = QDateTime()) : _lastSeen(lastSeen.isNull() ? QDateTime::currentDateTime() : lastSeen), _disco(disco) { } inline bool isValid() const { return _lastSeen.isValid(); } inline const QDateTime & lastSeen() const { return _lastSeen; } inline const XMPP::DiscoItem &disco() const { return _disco; } QDomElement toXml(QDomDocument *doc) const; static CapsInfo fromXml(const QDomElement &ci); private: QDateTime _lastSeen; XMPP::DiscoItem _disco; }; class CapsRegistry : public QObject { Q_OBJECT public: CapsRegistry(QObject *parent = nullptr); static CapsRegistry *instance(); static void setInstance(CapsRegistry *instance); void registerCaps(const CapsSpec &, const XMPP::DiscoItem &item); bool isRegistered(const QString &) const; DiscoItem disco(const QString &) const; signals: void registered(const XMPP::CapsSpec &); public slots: void load(); void save(); protected: virtual void saveData(const QByteArray &data); // reimplmenet these two functions virtual QByteArray loadData(); // to have permanent cache private: static CapsRegistry * instance_; QHash capsInfo_; }; class CapsManager : public QObject { Q_OBJECT public: CapsManager(Client *client); ~CapsManager(); bool isEnabled(); void setEnabled(bool); void updateCaps(const Jid &jid, const CapsSpec &caps); void disableCaps(const Jid &jid); bool capsEnabled(const Jid &jid) const; XMPP::DiscoItem disco(const Jid &jid) const; void updateDisco(const Jid &jid, const XMPP::DiscoItem &item); XMPP::Features features(const Jid &jid) const; QString clientName(const Jid &jid) const; QString clientVersion(const Jid &jid) const; QString osVersion(const Jid &jid) const; CapsSpec capsSpec(const Jid &jid) const; signals: /** * This signal is emitted when the feature list of a given JID have changed. */ void capsChanged(const Jid &jid); protected slots: void discoFinished(); void capsRegistered(const CapsSpec &); private: Client * client_; bool isEnabled_; QMap capsSpecs_; QMap> capsJids_; }; } // namespace XMPP #endif // XMPP_CAPS_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_captcha.h000066400000000000000000000033121370065651000245020ustar00rootroot00000000000000/* * Copyright (C) 2016 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_CAPTCHA_H #define XMPP_CAPTCHA_H #include "xmpp/jid/jid.h" #include "xmpp_url.h" #include namespace XMPP { class CaptchaChallengePrivate; class Message; class XData; class CaptchaChallenge { public: enum Result { Passed, Unavailable, NotAcceptable }; enum State { New, Success, Fail }; static const int Timeout = 120; // secs CaptchaChallenge(); CaptchaChallenge(const Message &); CaptchaChallenge(const CaptchaChallenge &); ~CaptchaChallenge(); CaptchaChallenge &operator=(const CaptchaChallenge &); bool isValid() const; const Jid & offendedJid() const; const Jid & arbiter() const; const XData & form() const; QString explanation() const; const UrlList &urls() const; State state() const; Result validateResponse(const XData &); private: friend class CaptchaChallengePrivate; QSharedDataPointer d; }; } // namespace XMPP #endif // XMPP_CAPTCHA_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_chatstate.h000066400000000000000000000016511370065651000250630ustar00rootroot00000000000000/* * Copyright (C) 2006 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_CHATSTATE #define XMPP_CHATSTATE namespace XMPP { typedef enum { StateNone, StateActive, StateComposing, StatePaused, StateInactive, StateGone } ChatState; } // namespace XMPP #endif // XMPP_CHATSTATE psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_client.h000066400000000000000000000176701370065651000243710ustar00rootroot00000000000000/* * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_CLIENT_H #define XMPP_CLIENT_H #include "xmpp/jid/jid.h" #include "xmpp_discoitem.h" #include "xmpp_status.h" #include #include #include class ByteStream; class QDomDocument; class QDomElement; class QNetworkAccessManager; class QString; namespace XMPP { class BSConnection; class CapsManager; class ClientStream; class EncryptionHandler; class Features; class FileTransferManager; class HttpFileUploadManager; class IBBManager; class JidLinkManager; class LiveRoster; class LiveRosterItem; class Message; class Resource; class ResourceList; class Roster; class RosterItem; class S5BManager; class ServerInfoManager; class Stream; class Task; class TcpPortReserver; namespace Jingle { class Manager; namespace S5B { class Manager; } namespace IBB { class Manager; } namespace ICE { class Manager; } } } namespace XMPP { class Client : public QObject { Q_OBJECT public: Client(QObject *parent = nullptr); ~Client(); bool isActive() const; void connectToServer(ClientStream *s, const Jid &j, bool auth = true); void start(const QString &host, const QString &user, const QString &pass, const QString &resource); void close(bool fast = false); bool hasStream() const; Stream & stream(); QString streamBaseNS() const; const LiveRoster & roster() const; const ResourceList &resourceList() const; bool isSessionRequired() const; void send(const QDomElement &); void send(const QString &); void clearSendQueue(); QString host() const; QString user() const; QString pass() const; QString resource() const; Jid jid() const; void setNetworkAccessManager(QNetworkAccessManager *qnam); QNetworkAccessManager *networkAccessManager() const; void rosterRequest(bool withGroupsDelimiter = true); void sendMessage(Message &); void sendSubscription(const Jid &, const QString &, const QString &nick = QString()); void setPresence(const Status &); void debug(const QString &); QString genUniqueId(); Task * rootTask(); QDomDocument *doc() const; QString OSName() const; QString OSVersion() const; QString timeZone() const; int timeZoneOffset() const; bool manualTimeZoneOffset() const; QString clientName() const; QString clientVersion() const; CapsSpec caps() const; CapsSpec serverCaps() const; void setOSName(const QString &); void setOSVersion(const QString &); void setTimeZone(const QString &, int); void setClientName(const QString &); void setClientVersion(const QString &); void setCaps(const CapsSpec &); void setEncryptionHandler(EncryptionHandler *); EncryptionHandler *encryptionHandler() const; void setIdentity(const DiscoItem::Identity &); DiscoItem::Identity identity() const; void setFeatures(const Features &f); const Features &features() const; DiscoItem makeDiscoResult(const QString &node = QString()) const; void setCapsOptimizationAllowed(bool allowed); bool capsOptimizationAllowed() const; void setTcpPortReserver(TcpPortReserver *portReserver); TcpPortReserver * tcpPortReserver() const; S5BManager * s5bManager() const; IBBManager * ibbManager() const; BoBManager * bobManager() const; JidLinkManager * jidLinkManager() const; CapsManager * capsManager() const; ServerInfoManager * serverInfoManager() const; HttpFileUploadManager *httpFileUploadManager() const; Jingle::Manager * jingleManager() const; Jingle::S5B::Manager * jingleS5BManager() const; Jingle::IBB::Manager * jingleIBBManager() const; Jingle::ICE::Manager * jingleICEManager() const; void setFileTransferEnabled(bool b); FileTransferManager *fileTransferManager() const; QString groupChatPassword(const QString &host, const QString &room) const; bool groupChatJoin(const QString &host, const QString &room, const QString &nick, const QString &password = QString(), int maxchars = -1, int maxstanzas = -1, int seconds = -1, const QDateTime &since = QDateTime(), const Status & = Status()); void groupChatSetStatus(const QString &host, const QString &room, const Status &); void groupChatChangeNick(const QString &host, const QString &room, const QString &nick, const Status &); void groupChatLeave(const QString &host, const QString &room, const QString &statusStr = QString()); void groupChatLeaveAll(const QString &statusStr = QString()); QString groupChatNick(const QString &host, const QString &room) const; signals: void activated(); void disconnected(); // void authFinished(bool, int, const QString &); void rosterGroupsDelimiterRequestFinished(const QString &); void rosterRequestFinished(bool, int, const QString &); void rosterItemAdded(const RosterItem &); void rosterItemUpdated(const RosterItem &); void rosterItemRemoved(const RosterItem &); void resourceAvailable(const Jid &, const Resource &); void resourceUnavailable(const Jid &, const Resource &); void presenceError(const Jid &, int, const QString &); void subscription(const Jid &, const QString &, const QString &); void messageReceived(const Message &); void debugText(const QString &); void xmlIncoming(const QString &); void xmlOutgoing(const QString &); void stanzaElementOutgoing(QDomElement &); void groupChatJoined(const Jid &); void groupChatLeft(const Jid &); void groupChatPresence(const Jid &, const Status &); void groupChatError(const Jid &, int, const QString &); void incomingJidLink(); void beginImportRoster(); void endImportRoster(); private slots: // void streamConnected(); // void streamHandshaken(); // void streamError(const StreamError &); // void streamSSLCertificateReady(const QSSLCert &); // void streamCloseFinished(); void streamError(int); void streamReadyRead(); void streamIncomingXml(const QString &); void streamOutgoingXml(const QString &); void slotRosterRequestFinished(); // basic daemons void ppSubscription(const Jid &, const QString &, const QString &); void ppPresence(const Jid &, const Status &); void pmMessage(const Message &); void prRoster(const Roster &); void s5b_incomingReady(); void ibb_incomingReady(); void handleSMAckResponse(int); void parseUnhandledStreamFeatures(); public: class GroupChat; private: void cleanup(); void distribute(const QDomElement &); void importRoster(const Roster &); void importRosterItem(const RosterItem &); void updateSelfPresence(const Jid &, const Status &); void updatePresence(LiveRosterItem *, const Jid &, const Status &); void handleIncoming(BSConnection *); void sendAckRequest(); class ClientPrivate; ClientPrivate *d; }; } // namespace XMPP #endif // XMPP_CLIENT_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_discoinfotask.cpp000066400000000000000000000075331370065651000263030ustar00rootroot00000000000000/* * Copyright (C) 2001-2002 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp_discoinfotask.h" #include "xmpp/jid/jid.h" #include "xmpp_caps.h" #include "xmpp_client.h" #include "xmpp_discoitem.h" #include "xmpp_task.h" #include "xmpp_xmlcommon.h" #include #include #include using namespace XMPP; class DiscoInfoTask::Private { public: bool allowCache = true; Jid jid; QString node; DiscoItem::Identity ident; DiscoItem item; }; DiscoInfoTask::DiscoInfoTask(Task *parent) : Task(parent) { d = new Private; } DiscoInfoTask::~DiscoInfoTask() { delete d; } void DiscoInfoTask::setAllowCache(bool allow) { d->allowCache = allow; } void DiscoInfoTask::get(const DiscoItem &item) { DiscoItem::Identity id; if (item.identities().count() == 1) id = item.identities().first(); get(item.jid(), item.node(), id); } void DiscoInfoTask::get(const Jid &j, const QString &node, DiscoItem::Identity ident) { d->item = DiscoItem(); // clear item d->jid = j; d->node = node; d->ident = ident; } /** * Original requested jid. * Is here because sometimes the responder does not include this information * in the reply. */ const Jid &DiscoInfoTask::jid() const { return d->jid; } /** * Original requested node. * Is here because sometimes the responder does not include this information * in the reply. */ const QString &DiscoInfoTask::node() const { return d->node; } const DiscoItem &DiscoInfoTask::item() const { return d->item; } void DiscoInfoTask::onGo() { if (d->allowCache && client()->capsManager()->isEnabled()) { d->item = client()->capsManager()->disco(d->jid); if (!d->item.features().isEmpty() || d->item.identities().count()) { QTimer::singleShot(0, this, SLOT(cachedReady())); // to be consistent with network requests return; } } QDomElement iq = createIQ(doc(), "get", d->jid.full(), id()); QDomElement query = doc()->createElementNS("http://jabber.org/protocol/disco#info", "query"); if (!d->node.isEmpty()) query.setAttribute("node", d->node); #if 0 // seems like disco#info get request was misinterpreted. xep-0030 says it has to be an EMPTY query. if ( !d->ident.category.isEmpty() && !d->ident.type.isEmpty() ) { QDomElement i = doc()->createElement("item"); i.setAttribute("category", d->ident.category); i.setAttribute("type", d->ident.type); if ( !d->ident.name.isEmpty() ) i.setAttribute("name", d->ident.name); query.appendChild( i ); } #endif iq.appendChild(query); send(iq); } void DiscoInfoTask::cachedReady() { d->item.setJid(d->jid); setSuccess(); } bool DiscoInfoTask::take(const QDomElement &x) { if (!iqVerify(x, d->jid, id())) return false; if (x.attribute("type") == "result") { d->item = DiscoItem::fromDiscoInfoResult(queryTag(x)); d->item.setJid(d->jid); if (d->allowCache && client()->capsManager()->isEnabled()) { client()->capsManager()->updateDisco(d->jid, d->item); } setSuccess(); } else { setError(x); } return true; } psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_discoinfotask.h000066400000000000000000000031411370065651000257370ustar00rootroot00000000000000/* * Copyright (C) 2001-2002 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_DISCOINFOTASK_H #define XMPP_DISCOINFOTASK_H #include "xmpp_discoitem.h" #include "xmpp_task.h" class QDomElement; class QString; namespace XMPP { class Jid; class DiscoInfoTask : public Task { Q_OBJECT public: DiscoInfoTask(Task *); ~DiscoInfoTask(); // Allow retreive result from cache and update cache on finish with new data void setAllowCache(bool allow = true); void get(const Jid &, const QString &node = QString(), const DiscoItem::Identity = DiscoItem::Identity()); void get(const DiscoItem &); const DiscoItem &item() const; const Jid & jid() const; const QString & node() const; void onGo(); bool take(const QDomElement &); private slots: void cachedReady(); private: class Private; Private *d; }; // Deprecated name typedef DiscoInfoTask JT_DiscoInfo; } // namespace XMPP #endif // XMPP_DISCOINFOTASK_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_discoitem.cpp000066400000000000000000000211311370065651000254110ustar00rootroot00000000000000/* * xmpp_discoitem.cpp * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp_discoitem.h" #include using namespace XMPP; class XMPP::DiscoItemPrivate : public QSharedData { public: DiscoItemPrivate() { action = DiscoItem::None; } Jid jid; QString name; QString node; DiscoItem::Action action; Features features; DiscoItem::Identities identities; QList exts; }; DiscoItem::DiscoItem() : d(new DiscoItemPrivate) { } DiscoItem::DiscoItem(const DiscoItem &from) : d(new DiscoItemPrivate) { *this = from; } DiscoItem &DiscoItem::operator=(const DiscoItem &from) { d->jid = from.d->jid; d->name = from.d->name; d->node = from.d->node; d->action = from.d->action; d->features = from.d->features; d->identities = from.d->identities; d->exts = from.d->exts; return *this; } DiscoItem::~DiscoItem() { } AgentItem DiscoItem::toAgentItem() const { AgentItem ai; ai.setJid(jid()); ai.setName(name()); Identity id; if (!identities().isEmpty()) id = identities().first(); ai.setCategory(id.category); ai.setType(id.type); ai.setFeatures(d->features); return ai; } void DiscoItem::fromAgentItem(const AgentItem &ai) { setJid(ai.jid()); setName(ai.name()); Identity id; id.category = ai.category(); id.type = ai.type(); id.name = ai.name(); Identities idList; idList << id; setIdentities(idList); setFeatures(ai.features()); } QString DiscoItem::capsHash(QCryptographicHash::Algorithm algo) const { QStringList prep; DiscoItem::Identities idents = d->identities; std::sort(idents.begin(), idents.end()); for (const DiscoItem::Identity &id : idents) { prep << QString("%1/%2/%3/%4").arg(id.category, id.type, id.lang, id.name); } QStringList fl = d->features.list(); std::sort(fl.begin(), fl.end()); prep += fl; QMap forms; for (const XData &xd : d->exts) { if (xd.registrarType().isEmpty()) { continue; } if (forms.contains(xd.registrarType())) { return QString(); // ill-formed } forms.insert(xd.registrarType(), xd); } for (const XData &xd : forms.values()) { prep << xd.registrarType(); QMap values; for (const XData::Field &f : xd.fields()) { if (f.var() == QLatin1String("FORM_TYPE")) { continue; } if (values.contains(f.var())) { return QString(); // ill-formed } QStringList v = f.value(); if (v.isEmpty()) { continue; // maybe it's media-element but xep-115 (1.5) and xep-232 (0.3) are not clear about that. } std::sort(v.begin(), v.end()); values[f.var()] = v; } QMap::ConstIterator it = values.constBegin(); for (; it != values.constEnd(); ++it) { prep += it.key(); prep += it.value(); } } QByteArray ba = QString(prep.join(QLatin1String("<")) + QLatin1Char('<')).toUtf8(); // qDebug() << "Server caps ver: " << (prep.join(QLatin1String("<")) + QLatin1Char('<')) // << "Hash:" << QString::fromLatin1(QCryptographicHash::hash(ba, algo).toBase64()); return QString::fromLatin1(QCryptographicHash::hash(ba, algo).toBase64()); } DiscoItem DiscoItem::fromDiscoInfoResult(const QDomElement &q) { DiscoItem item; item.setNode(q.attribute("node")); QStringList features; DiscoItem::Identities identities; QList extList; for (QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement e = n.toElement(); if (e.isNull()) continue; if (e.tagName() == "feature") { features << e.attribute("var"); } else if (e.tagName() == "identity") { DiscoItem::Identity id; id.category = e.attribute("category"); id.type = e.attribute("type"); id.lang = e.attribute("lang"); id.name = e.attribute("name"); identities.append(id); } else if (e.tagName() == QLatin1String("x") && e.namespaceURI() == QLatin1String("jabber:x:data")) { XData form; form.fromXml(e); extList.append(form); } } item.setFeatures(features); item.setIdentities(identities); item.setExtensions(extList); return item; } QDomElement DiscoItem::toDiscoInfoResult(QDomDocument *doc) const { QDomElement q = doc->createElementNS(QLatin1String("http://jabber.org/protocol/disco#info"), QLatin1String("query")); q.setAttribute("node", d->node); for (const Identity &id : d->identities) { QDomElement idel = q.appendChild(doc->createElement(QLatin1String("identity"))).toElement(); idel.setAttribute("category", id.category); idel.setAttribute("type", id.type); if (!id.lang.isEmpty()) { idel.setAttribute("lang", id.lang); } if (!id.name.isEmpty()) { idel.setAttribute("name", id.name); } } for (const QString &f : d->features.list()) { QDomElement fel = q.appendChild(doc->createElement(QLatin1String("feature"))).toElement(); fel.setAttribute("var", f); } for (const XData &f : d->exts) { q.appendChild(f.toXml(doc)); } return q; } const Jid &DiscoItem::jid() const { return d->jid; } void DiscoItem::setJid(const Jid &j) { d->jid = j; } const QString &DiscoItem::name() const { return d->name; } void DiscoItem::setName(const QString &n) { d->name = n; } const QString &DiscoItem::node() const { return d->node; } void DiscoItem::setNode(const QString &n) { d->node = n; } DiscoItem::Action DiscoItem::action() const { return d->action; } void DiscoItem::setAction(Action a) { d->action = a; } const Features &DiscoItem::features() const { return d->features; } void DiscoItem::setFeatures(const Features &f) { d->features = f; } const DiscoItem::Identities &DiscoItem::identities() const { return d->identities; } void DiscoItem::setIdentities(const Identities &i) { d->identities = i; if (name().isEmpty() && i.count()) setName(i.first().name); } const QList &DiscoItem::extensions() const { return d->exts; } XData DiscoItem::findExtension(XData::Type xdataType, const QString &formType) const { for (const auto &x : d->exts) { if (x.type() == xdataType && (formType.isEmpty() || x.registrarType() == formType)) { return x; } } return XData(XData::Data_Invalid); } void DiscoItem::setExtensions(const QList &extlist) { d->exts = extlist; } XData DiscoItem::registeredExtension(const QString &ns) const { for (const XData &xd : d->exts) { if (xd.registrarType() == ns) { return xd; } } return XData(); } DiscoItem::Action DiscoItem::string2action(const QString &s) { Action a; if (s == "update") a = Update; else if (s == "remove") a = Remove; else a = None; return a; } QString DiscoItem::action2string(const Action a) { QString s; if (a == Update) s = "update"; else if (a == Remove) s = "remove"; else s = QString(); return s; } bool XMPP::operator<(const DiscoItem::Identity &a, const DiscoItem::Identity &b) { int r = a.category.compare(b.category); if (!r) { r = a.type.compare(b.type); if (!r) { r = a.lang.compare(b.lang); if (!r) { r = a.name.compare(b.name); } } } return r < 0; } bool DiscoItem::Identity::operator==(const DiscoItem::Identity &other) const { return category == other.category && type == other.type && lang == other.lang && name == other.name; } psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_discoitem.h000066400000000000000000000061551370065651000250670ustar00rootroot00000000000000/* * xmpp_discoitem.h * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_DISCOITEM #define XMPP_DISCOITEM #include "xmpp/jid/jid.h" #include "xmpp_agentitem.h" #include "xmpp_features.h" #include "xmpp_xdata.h" #include #include namespace XMPP { class DiscoItemPrivate; class DiscoItem { public: DiscoItem(); ~DiscoItem(); const Jid & jid() const; const QString &node() const; const QString &name() const; void setJid(const Jid &); void setName(const QString &); void setNode(const QString &); enum Action { None = 0, Remove, Update }; Action action() const; void setAction(Action); const Features &features() const; void setFeatures(const Features &); struct Identity { QString category; QString type; QString lang; QString name; inline Identity() { } inline Identity(const QString &categoty, const QString &type, const QString &lang = QString(), const QString &name = QString()) : category(categoty), type(type), lang(lang), name(name) { } bool operator==(const Identity &other) const; }; typedef QList Identities; const Identities &identities() const; void setIdentities(const Identities &); inline void setIdentities(const Identity &id) { setIdentities(Identities() << id); } const QList &extensions() const; XData findExtension(XData::Type xdataType, const QString &formType = QString()) const; void setExtensions(const QList &extlist); XData registeredExtension(const QString &ns) const; // some useful helper functions static Action string2action(const QString &s); static QString action2string(const Action a); DiscoItem &operator=(const DiscoItem &); DiscoItem(const DiscoItem &); operator AgentItem() const { return toAgentItem(); } AgentItem toAgentItem() const; void fromAgentItem(const AgentItem &); QString capsHash(QCryptographicHash::Algorithm algo) const; static DiscoItem fromDiscoInfoResult(const QDomElement &x); QDomElement toDiscoInfoResult(QDomDocument *doc) const; private: QSharedDataPointer d; }; bool operator<(const DiscoItem::Identity &a, const DiscoItem::Identity &b); } // namespace XMPP #endif // XMPP_DISCOITEM psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_encryptionhandler.h000066400000000000000000000020731370065651000266320ustar00rootroot00000000000000/* * xmpp_encryptionhandler.h * Copyright (C) 2018 Vyacheslav Karpukhin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef PSI_XMPP_ENCRYPTIONHANDLER_H #define PSI_XMPP_ENCRYPTIONHANDLER_H class QDomElement; namespace XMPP { class EncryptionHandler { public: virtual bool decryptMessageElement(QDomElement &) = 0; virtual bool encryptMessageElement(QDomElement &) = 0; }; } // namespace XMPP #endif // PSI_XMPP_ENCRYPTIONHANDLER_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_features.cpp000066400000000000000000000173771370065651000252700ustar00rootroot00000000000000/* * xmpp_features.cpp - XMPP entity features * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp_features.h" #include "jingle-ft.h" #include "jingle.h" #include #include #include #include using namespace XMPP; Features::Features() { } Features::Features(const QStringList &l) { setList(l); } Features::Features(const QSet &s) { setList(s); } Features::Features(const QString &str) { QSet l; l << str; setList(l); } Features::~Features() { } QStringList Features::list() const { #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) return QStringList(_list.begin(), _list.end()); #else return _list.toList(); #endif } void Features::setList(const QStringList &l) { #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) _list = QSet(l.begin(), l.end()); #else _list = QSet::fromList(l); #endif } void Features::setList(const QSet &l) { _list = l; } void Features::addFeature(const QString &s) { _list += s; } bool Features::test(const QStringList &ns) const { #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) auto ss = QSet(ns.begin(), ns.end()); #else auto ss = QSet::fromList(ns); #endif return _list.contains(ss); } bool Features::test(const QSet &ns) const { return _list.contains(ns); } #define FID_MULTICAST "http://jabber.org/protocol/address" bool Features::hasMulticast() const { QSet ns; ns << FID_MULTICAST; return test(ns); } #define FID_AHCOMMAND "http://jabber.org/protocol/commands" bool Features::hasCommand() const { QSet ns; ns << FID_AHCOMMAND; return test(ns); } #define FID_REGISTER "jabber:iq:register" bool Features::hasRegister() const { QSet ns; ns << FID_REGISTER; return test(ns); } #define FID_SEARCH "jabber:iq:search" bool Features::hasSearch() const { QSet ns; ns << FID_SEARCH; return test(ns); } #define FID_GROUPCHAT "http://jabber.org/protocol/muc" bool Features::hasGroupchat() const { QSet ns; ns << FID_GROUPCHAT; return test(ns); } #define FID_VOICE "http://www.google.com/xmpp/protocol/voice/v1" bool Features::hasVoice() const { QSet ns; ns << FID_VOICE; return test(ns); } #define FID_GATEWAY "jabber:iq:gateway" bool Features::hasGateway() const { QSet ns; ns << FID_GATEWAY; return test(ns); } #define FID_QUERYVERSION "jabber:iq:version" bool Features::hasVersion() const { QSet ns; ns << FID_QUERYVERSION; return test(ns); } #define FID_DISCO "http://jabber.org/protocol/disco" bool Features::hasDisco() const { QSet ns; ns << FID_DISCO; ns << "http://jabber.org/protocol/disco#info"; ns << "http://jabber.org/protocol/disco#items"; return test(ns); } #define FID_CHATSTATE "http://jabber.org/protocol/chatstates" bool Features::hasChatState() const { QSet ns; ns << FID_CHATSTATE; return test(ns); } #define FID_VCARD "vcard-temp" bool Features::hasVCard() const { QSet ns; ns << FID_VCARD; return test(ns); } #define FID_MESSAGECARBONS "urn:xmpp:carbons:2" bool Features::hasMessageCarbons() const { QStringList ns; ns << FID_MESSAGECARBONS; return test(ns); } bool Features::hasJingleFT() const { QStringList ns; ns << Jingle::FileTransfer::NS; return test(ns); } #define FID_JINGLEICEUDP "urn:xmpp:jingle:transports:ice-udp:1" bool Features::hasJingleIceUdp() const { return test(QStringList() << QLatin1String(FID_JINGLEICEUDP)); } #define FID_JINGLEICE "urn:xmpp:jingle:transports:ice:0" bool Features::hasJingleIce() const { return test(QStringList() << QLatin1String(FID_JINGLEICE)); } #define NS_CAPS "http://jabber.org/protocol/caps" bool Features::hasCaps() const { return test(QStringList() << QLatin1String(NS_CAPS)); } #define NS_CAPS_OPTIMIZE "http://jabber.org/protocol/caps#optimize" bool Features::hasCapsOptimize() const { return test(QStringList() << QLatin1String(NS_CAPS_OPTIMIZE)); } #define NS_DIRECT_MUC_INVITE "jabber:x:conference" bool Features::hasDirectMucInvite() const { return test(QStringList() << QLatin1String(NS_DIRECT_MUC_INVITE)); } // custom Psi actions #define FID_ADD "psi:add" class Features::FeatureName : public QObject { Q_OBJECT public: FeatureName() : QObject(QCoreApplication::instance()) { id2s[FID_Invalid] = tr("ERROR: Incorrect usage of Features class"); id2s[FID_None] = tr("None"); id2s[FID_Register] = tr("Register"); id2s[FID_Search] = tr("Search"); id2s[FID_Groupchat] = tr("Groupchat"); id2s[FID_Gateway] = tr("Gateway"); id2s[FID_Disco] = tr("Service Discovery"); id2s[FID_VCard] = tr("VCard"); id2s[FID_AHCommand] = tr("Execute command"); id2s[FID_QueryVersion] = tr("Query version"); id2s[FID_MessageCarbons] = tr("Message Carbons"); // custom Psi actions id2s[FID_Add] = tr("Add to roster"); // compute reverse map // QMap::Iterator it = id2s.begin(); // for ( ; it != id2s.end(); ++it) // s2id[it.data()] = it.key(); id2f[FID_Register] = FID_REGISTER; id2f[FID_Search] = FID_SEARCH; id2f[FID_Groupchat] = FID_GROUPCHAT; id2f[FID_Gateway] = FID_GATEWAY; id2f[FID_Disco] = FID_DISCO; id2f[FID_VCard] = FID_VCARD; id2f[FID_AHCommand] = FID_AHCOMMAND; id2f[FID_QueryVersion] = FID_QUERYVERSION; id2f[FID_MessageCarbons] = FID_MESSAGECARBONS; // custom Psi actions id2f[FID_Add] = FID_ADD; } // QMap s2id; QMap id2s; QMap id2f; }; static Features::FeatureName *featureName = nullptr; long Features::id() const { if (_list.count() > 1) return FID_Invalid; else if (hasRegister()) return FID_Register; else if (hasSearch()) return FID_Search; else if (hasGroupchat()) return FID_Groupchat; else if (hasGateway()) return FID_Gateway; else if (hasDisco()) return FID_Disco; else if (hasVCard()) return FID_VCard; else if (hasCommand()) return FID_AHCommand; else if (test(QStringList(FID_ADD))) return FID_Add; else if (hasVersion()) return FID_QueryVersion; return FID_None; } long Features::id(const QString &feature) { Features f(feature); return f.id(); } QString Features::feature(long id) { if (!featureName) featureName = new FeatureName(); return featureName->id2f[id]; } Features &Features::operator<<(const QString &feature) { _list << feature; return *this; } QString Features::name(long id) { if (!featureName) featureName = new FeatureName(); return featureName->id2s[id]; } QString Features::name() const { return name(id()); } QString Features::name(const QString &feature) { Features f(feature); return f.name(f.id()); } #include "xmpp_features.moc" psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_features.h000066400000000000000000000073661370065651000247320ustar00rootroot00000000000000/* * xmpp_features.h * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_FEATURES_H #define XMPP_FEATURES_H #include #include class QString; namespace XMPP { class Features { public: Features(); Features(const QStringList &); Features(const QSet &); Features(const QString &); ~Features(); QStringList list() const; // actual featurelist void setList(const QStringList &); void setList(const QSet &); void addFeature(const QString &); // features inline bool isEmpty() const { return _list.isEmpty(); } bool hasRegister() const; bool hasSearch() const; bool hasMulticast() const; bool hasGroupchat() const; bool hasVoice() const; bool hasDisco() const; bool hasChatState() const; bool hasCommand() const; bool hasGateway() const; bool hasVersion() const; bool hasVCard() const; bool hasMessageCarbons() const; bool hasJingleFT() const; bool hasJingleIceUdp() const; bool hasJingleIce() const; bool hasCaps() const; bool hasCapsOptimize() const; bool hasDirectMucInvite() const; [[deprecated]] inline bool canRegister() const { return hasRegister(); } [[deprecated]] inline bool canSearch() const { return hasSearch(); } [[deprecated]] inline bool canMulticast() const { return hasMulticast(); } [[deprecated]] inline bool canGroupchat() const { return hasGroupchat(); } [[deprecated]] inline bool canVoice() const { return hasVoice(); } [[deprecated]] inline bool canDisco() const { return hasDisco(); } [[deprecated]] inline bool canChatState() const { return hasChatState(); } [[deprecated]] inline bool canCommand() const { return hasCommand(); } [[deprecated]] inline bool isGateway() const { return hasGateway(); } [[deprecated]] inline bool haveVCard() const { return hasVCard(); } [[deprecated]] inline bool canMessageCarbons() const { return hasMessageCarbons(); } enum FeatureID { FID_Invalid = -1, FID_None, FID_Register, FID_Search, FID_Groupchat, FID_Disco, FID_Gateway, FID_VCard, FID_AHCommand, FID_QueryVersion, FID_MessageCarbons, // private Psi actions FID_Add }; // useful functions inline bool test(const char *ns) const { return test(QSet() << QLatin1String(ns)); } bool test(const QStringList &) const; bool test(const QSet &) const; QString name() const; static QString name(long id); static QString name(const QString &feature); long id() const; static long id(const QString &feature); static QString feature(long id); Features & operator<<(const QString &feature); inline bool operator==(const Features &other) { return _list == other._list; } class FeatureName; private: QSet _list; }; } // namespace XMPP #endif // XMPP_FEATURES_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_form.h000066400000000000000000000047641370065651000240560ustar00rootroot00000000000000/* * xmpp_form.h - XMPP form * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #pragma once #include #include namespace XMPP { typedef QList AgentList; typedef QList DiscoList; class FormField { public: enum { username, nick, password, name, first, last, email, address, city, state, zip, phone, url, date, misc }; FormField(const QString &type = "", const QString &value = ""); ~FormField(); int type() const; QString fieldName() const; QString realName() const; bool isSecret() const; const QString &value() const; void setType(int); bool setType(const QString &); void setValue(const QString &); private: int tagNameToType(const QString &) const; QString typeToTagName(int) const; int v_type; QString v_value; class Private; Private *d = nullptr; }; class Form : public QList { public: Form(const Jid &j = ""); ~Form(); Jid jid() const; QString instructions() const; QString key() const; void setJid(const Jid &); void setInstructions(const QString &); void setKey(const QString &); private: Jid v_jid; QString v_instructions, v_key; class Private; Private *d = nullptr; }; class SearchResult { public: SearchResult(const Jid &jid = ""); ~SearchResult(); const Jid & jid() const; const QString &nick() const; const QString &first() const; const QString &last() const; const QString &email() const; void setJid(const Jid &); void setNick(const QString &); void setFirst(const QString &); void setLast(const QString &); void setEmail(const QString &); private: Jid v_jid; QString v_nick, v_first, v_last, v_email; }; } // namespace XMPP psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_hash.h000066400000000000000000000060721370065651000240300ustar00rootroot00000000000000/* * Copyright (C) 2019 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_HASH_H #define XMPP_HASH_H #include "xmpp_stanza.h" #include #include class QDomElement; namespace XMPP { extern QString HASH_NS; class Features; class Hash { public: enum Type { // XEP-0300 Version 0.5.3 (2018-02-14) Unknown, // not standard, just a default Sha1, // SHOULD NOT Sha256, // MUST Sha512, // SHOULD Sha3_256, // MUST Sha3_512, // SHOULD Blake2b256, // MUST Blake2b512, // SHOULD LastType = Blake2b512 }; inline Hash(Type type = Type::Unknown) : v_type(type) { } inline Hash(Type type, const QByteArray &data) : v_type(type), v_data(data) { } inline Hash(const QStringRef &algo) : v_type(parseType(algo)) { } Hash(const QDomElement &); inline bool operator==(const Hash &other) const { return v_type == other.v_type && v_data == other.v_data; } inline bool isValid() const { return v_type > Unknown && v_type <= LastType; } inline operator bool() const { return isValid(); } inline Type type() const { return v_type; } inline void setType(Type t) { v_type = t; } QString stringType() const; static Type parseType(const QStringRef &algo); inline QByteArray data() const { return v_data; } inline void setData(const QByteArray &d) { v_data = d; } // sets already computed hash inline QByteArray toHex() const { return v_data.toHex(); } inline QByteArray toBase64() const { return v_data.toBase64(); } inline QString toString() const { return QString("%1+%2").arg(stringType(), QString::fromLatin1(v_data.toHex())); } bool compute(const QByteArray &); // computes hash from passed data bool compute(QIODevice *dev); QDomElement toXml(QDomDocument *doc) const; static void populateFeatures(XMPP::Features &); static Hash from(Type t, const QByteArray &fileData); static Hash from(XMPP::Hash::Type t, QIODevice *dev); static Hash from(XMPP::Hash::Type t, const QFileInfo &file); static Hash from(const QStringRef &str); // e.g. sha1+aabccddeeffaabbcc232387539465923645 private: Type v_type = Type::Unknown; QByteArray v_data; }; Q_DECL_PURE_FUNCTION inline uint qHash(const Hash &hash, uint seed = 0) Q_DECL_NOTHROW { return qHash(hash.data(), seed); } } // namespace XMPP #endif // XMPP_HASH_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_htmlelement.h000066400000000000000000000025331370065651000254210ustar00rootroot00000000000000/* * Copyright (C) 2006 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_HTMLELEMENT_H #define XMPP_HTMLELEMENT_H #include class QString; namespace XMPP { class HTMLElement { public: HTMLElement(); HTMLElement(const QDomElement &body); void setBody(const QDomElement &body); const QDomElement &body() const; QString toString(const QString &rootTagName = "body") const; QString text() const; void filterOutUnwanted(bool strict = false); private: void filterOutUnwantedRecursive(QDomElement &el, bool strict); QDomDocument doc_; QDomElement body_; }; } // namespace XMPP #endif // XMPP_HTMLELEMENT_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_httpauthrequest.h000066400000000000000000000031031370065651000263470ustar00rootroot00000000000000/* * Copyright (C) 2006 Maciek Niedzielski * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_AUTHREQUEST_H #define XMPP_AUTHREQUEST_H #include #include "xmpp_stanza.h" class QDomDocument; class QDomElement; namespace XMPP { class HttpAuthRequest { public: HttpAuthRequest(const QString &m, const QString &u, const QString &i); HttpAuthRequest(const QString &m = QString(), const QString &u = QString()); HttpAuthRequest(const QDomElement &); bool isEmpty() const; void setMethod(const QString &); void setUrl(const QString &); void setId(const QString &); QString method() const; QString url() const; QString id() const; bool hasId() const; QDomElement toXml(QDomDocument &) const; bool fromXml(const QDomElement &); static Stanza::Error denyError; private: QString method_, url_, id_; bool hasId_; }; } // namespace XMPP #endif // XMPP_AUTHREQUEST_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_ibb.cpp000066400000000000000000000364741370065651000242050ustar00rootroot00000000000000/* * ibb.cpp - Inband bytestream * Copyright (C) 2001-2002 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp_ibb.h" #include "xmpp_client.h" #include "xmpp_stream.h" #include "xmpp_xmlcommon.h" #include #include #include #define IBB_PACKET_DELAY 0 using namespace XMPP; static int num_conn = 0; static int id_conn = 0; static const char *IBB_NS = "http://jabber.org/protocol/ibb"; //---------------------------------------------------------------------------- // IBBConnection //---------------------------------------------------------------------------- class IBBConnection::Private { public: Private() = default; int state = 0; quint16 seq = 0; Jid peer; QString sid; IBBManager *m = nullptr; JT_IBB * j = nullptr; QString iq_id; QString stanza; int blockSize = IBBConnection::PacketSize; // QByteArray recvBuf, sendBuf; bool closePending, closing; int id; // connection id }; IBBConnection::IBBConnection(IBBManager *m) : BSConnection(m) { d = new Private; d->m = m; d->j = nullptr; d->blockSize = PacketSize; resetConnection(); ++num_conn; d->id = id_conn++; #ifdef IBB_DEBUG qDebug("IBBConnection[%d]: constructing, count=%d", d->id, num_conn); #endif } void IBBConnection::resetConnection(bool clear) { d->m->unlink(this); d->state = Idle; d->closePending = false; d->closing = false; d->seq = 0; delete d->j; d->j = nullptr; clearWriteBuffer(); if (clear) clearReadBuffer(); setOpenMode(clear || !bytesAvailable() ? QIODevice::NotOpen : QIODevice::ReadOnly); } IBBConnection::~IBBConnection() { clearWriteBuffer(); // drop buffer to make closing procedure fast close(); --num_conn; #ifdef IBB_DEBUG qDebug("IBBConnection[%d]: destructing, count=%d", d->id, num_conn); #endif delete d; } void IBBConnection::setPacketSize(int blockSize) { d->blockSize = blockSize; } void IBBConnection::connectToJid(const Jid &peer, const QString &sid) { close(); resetConnection(true); d->state = Requesting; d->peer = peer; d->sid = sid; #ifdef IBB_DEBUG qDebug("IBBConnection[%d]: initiating request to %s", d->id, qPrintable(peer.full())); #endif d->j = new JT_IBB(d->m->client()->rootTask()); connect(d->j, SIGNAL(finished()), SLOT(ibb_finished())); d->j->request(d->peer, d->sid, d->blockSize); d->j->go(true); } void IBBConnection::accept() { if (d->state != WaitingForAccept) return; #ifdef IBB_DEBUG qDebug("IBBConnection[%d]: accepting %s [%s]", d->id, qPrintable(d->peer.full()), qPrintable(d->sid)); #endif d->m->doAccept(this, d->iq_id); d->state = Active; setOpenMode(QIODevice::ReadWrite); d->m->link(this); emit connected(); // to be compatible with S5B } void IBBConnection::close() { if (d->state == Idle) return; if (d->state == WaitingForAccept) { d->m->doReject(this, d->iq_id, Stanza::Error::Forbidden, "Rejected"); resetConnection(); return; } #ifdef IBB_DEBUG qDebug("IBBConnection[%d]: closing", d->id); #endif if (d->state == Active) { d->closePending = true; trySend(); // if there is data pending to be written, then pend the closing if (bytesToWrite() > 0 || d->closing) { return; } } resetConnection(); } int IBBConnection::state() const { return d->state; } Jid IBBConnection::peer() const { return d->peer; } QString IBBConnection::sid() const { return d->sid; } BytestreamManager *IBBConnection::manager() const { return d->m; } bool IBBConnection::isOpen() const { if (d->state == Active) return true; else return false; } qint64 IBBConnection::writeData(const char *data, qint64 maxSize) { if (d->state != Active || d->closePending || d->closing) { setErrorString("read only"); return 0; } ByteStream::appendWrite(QByteArray::fromRawData(data, maxSize)); trySend(); return maxSize; } void IBBConnection::waitForAccept(const Jid &peer, const QString &iq_id, const QString &sid, int blockSize, const QString &stanza) { close(); resetConnection(true); d->state = WaitingForAccept; d->peer = peer; d->iq_id = iq_id; d->sid = sid; d->blockSize = blockSize; d->stanza = stanza; } void IBBConnection::takeIncomingData(const IBBData &ibbData) { if (ibbData.seq != d->seq) { d->m->doReject(this, d->iq_id, Stanza::Error::UnexpectedRequest, "Invalid sequence"); return; } if (ibbData.data.size() > d->blockSize) { d->m->doReject(this, d->iq_id, Stanza::Error::BadRequest, "Too much data"); return; } d->seq++; appendRead(ibbData.data); emit readyRead(); } void IBBConnection::setRemoteClosed() { resetConnection(); emit connectionClosed(); } void IBBConnection::ibb_finished() { JT_IBB *j = d->j; d->j = nullptr; if (j->success()) { if (j->mode() == JT_IBB::ModeRequest) { #ifdef IBB_DEBUG qDebug("IBBConnection[%d]: %s [%s] accepted.", d->id, qPrintable(d->peer.full()), qPrintable(d->sid)); #endif d->state = Active; setOpenMode(QIODevice::ReadWrite); d->m->link(this); emit connected(); } else { if (d->closing) { resetConnection(); emit delayedCloseFinished(); } if (bytesToWrite() || d->closePending) QTimer::singleShot(IBB_PACKET_DELAY, this, SLOT(trySend())); emit bytesWritten(j->bytesWritten()); // will delete this connection if no bytes left. } } else { if (j->mode() == JT_IBB::ModeRequest) { #ifdef IBB_DEBUG qDebug("IBBConnection[%d]: %s refused.", d->id, qPrintable(d->peer.full())); #endif resetConnection(true); setError(ErrRequest); } else { resetConnection(true); setError(ErrData); } } } void IBBConnection::trySend() { // if we already have an active task, then don't do anything if (d->j) return; QByteArray a = takeWrite(d->blockSize); if (a.isEmpty()) { if (!d->closePending) return; // null operation? d->closePending = false; d->closing = true; #ifdef IBB_DEBUG qDebug("IBBConnection[%d]: closing", d->id); #endif } else { #ifdef IBB_DEBUG qDebug("IBBConnection[%d]: sending [%d] bytes (%d bytes left)", d->id, a.size(), bytesToWrite()); #endif } d->j = new JT_IBB(d->m->client()->rootTask()); connect(d->j, SIGNAL(finished()), SLOT(ibb_finished())); if (d->closing) { d->j->close(d->peer, d->sid); } else { d->j->sendData(d->peer, IBBData(d->sid, d->seq++, a)); } d->j->go(true); } //---------------------------------------------------------------------------- // IBBData //---------------------------------------------------------------------------- IBBData &IBBData::fromXml(const QDomElement &e) { sid = e.attribute("sid"); seq = e.attribute("seq").toInt(); data = QByteArray::fromBase64(e.text().toUtf8()); return *this; } QDomElement IBBData::toXml(QDomDocument *doc) const { QDomElement query = textTagNS(doc, IBB_NS, "data", QString::fromLatin1(data.toBase64())).toElement(); query.setAttribute("seq", QString::number(seq)); query.setAttribute("sid", sid); return query; } //---------------------------------------------------------------------------- // IBBManager //---------------------------------------------------------------------------- class IBBManager::Private { public: Private() = default; Client * client = nullptr; IBBConnectionList activeConns; IBBConnectionList incomingConns; JT_IBB * ibb = nullptr; }; IBBManager::IBBManager(Client *parent) : BytestreamManager(parent) { d = new Private; d->client = parent; d->ibb = new JT_IBB(d->client->rootTask(), true); connect(d->ibb, SIGNAL(incomingRequest(Jid, QString, QString, int, QString)), SLOT(ibb_incomingRequest(Jid, QString, QString, int, QString))); connect(d->ibb, SIGNAL(incomingData(Jid, QString, IBBData, Stanza::Kind)), SLOT(takeIncomingData(Jid, QString, IBBData, Stanza::Kind))); connect(d->ibb, SIGNAL(closeRequest(Jid, QString, QString)), SLOT(ibb_closeRequest(Jid, QString, QString))); } IBBManager::~IBBManager() { qDeleteAll(d->incomingConns); d->incomingConns.clear(); delete d->ibb; delete d; } const char *IBBManager::ns() { return IBB_NS; } Client *IBBManager::client() const { return d->client; } BSConnection *IBBManager::createConnection() { return new IBBConnection(this); } IBBConnection *IBBManager::takeIncoming() { return d->incomingConns.isEmpty() ? nullptr : d->incomingConns.takeFirst(); } void IBBManager::ibb_incomingRequest(const Jid &from, const QString &id, const QString &sid, int blockSize, const QString &stanza) { // create a "waiting" connection IBBConnection *c = new IBBConnection(this); c->waitForAccept(from, id, sid, blockSize, stanza); d->incomingConns.append(c); emit incomingReady(); } void IBBManager::takeIncomingData(const Jid &from, const QString &id, const IBBData &data, Stanza::Kind sKind) { IBBConnection *c = findConnection(data.sid, from); if (!c) { if (sKind == Stanza::IQ) { d->ibb->respondError(from, id, Stanza::Error::ItemNotFound, "No such stream"); } // TODO imeplement xep-0079 error processing in case of Stanza::Message } else { if (sKind == Stanza::IQ) { d->ibb->respondAck(from, id); } c->takeIncomingData(data); } } void IBBManager::ibb_closeRequest(const Jid &from, const QString &id, const QString &sid) { IBBConnection *c = findConnection(sid, from); if (!c) { d->ibb->respondError(from, id, Stanza::Error::ItemNotFound, "No such stream"); } else { d->ibb->respondAck(from, id); c->setRemoteClosed(); } } bool IBBManager::isAcceptableSID(const XMPP::Jid &jid, const QString &sid) const { return findConnection(sid, jid) == nullptr; } const char *IBBManager::sidPrefix() const { return "ibb_"; } void IBBManager::link(IBBConnection *c) { d->activeConns.append(c); } void IBBManager::unlink(IBBConnection *c) { d->activeConns.removeAll(c); } IBBConnection *IBBManager::findConnection(const QString &sid, const Jid &peer) const { for (IBBConnection *c : d->activeConns) { if (c->sid() == sid && (peer.isEmpty() || c->peer().compare(peer))) return c; } return nullptr; } void IBBManager::doAccept(IBBConnection *c, const QString &id) { d->ibb->respondAck(c->peer(), id); } void IBBManager::doReject(IBBConnection *c, const QString &id, Stanza::Error::ErrorCond cond, const QString &str) { d->ibb->respondError(c->peer(), id, cond, str); } //---------------------------------------------------------------------------- // JT_IBB //---------------------------------------------------------------------------- class JT_IBB::Private { public: Private() = default; QDomElement iq; int mode = 0; bool serve = false; Jid to; QString sid; int bytesWritten = 0; }; JT_IBB::JT_IBB(Task *parent, bool serve) : Task(parent) { d = new Private; d->serve = serve; } JT_IBB::~JT_IBB() { delete d; } void JT_IBB::request(const Jid &to, const QString &sid, int blockSize) { d->mode = ModeRequest; QDomElement iq; d->to = to; iq = createIQ(doc(), "set", to.full(), id()); QDomElement query = doc()->createElementNS(IBB_NS, "open"); // genUniqueKey query.setAttribute("sid", sid); query.setAttribute("block-size", blockSize); query.setAttribute("stanza", "iq"); iq.appendChild(query); d->iq = iq; } void JT_IBB::sendData(const Jid &to, const IBBData &ibbData) { d->mode = ModeSendData; QDomElement iq; d->to = to; d->bytesWritten = ibbData.data.size(); iq = createIQ(doc(), "set", to.full(), id()); iq.appendChild(ibbData.toXml(doc())); d->iq = iq; } void JT_IBB::close(const Jid &to, const QString &sid) { d->mode = ModeSendData; QDomElement iq; d->to = to; iq = createIQ(doc(), "set", to.full(), id()); QDomElement query = iq.appendChild(doc()->createElementNS(IBB_NS, "close")).toElement(); query.setAttribute("sid", sid); d->iq = iq; } void JT_IBB::respondError(const Jid &to, const QString &id, Stanza::Error::ErrorCond cond, const QString &text) { QDomElement iq = createIQ(doc(), "error", to.full(), id); Stanza::Error error(Stanza::Error::Cancel, cond, text); iq.appendChild(error.toXml(*client()->doc(), client()->stream().baseNS())); send(iq); } void JT_IBB::respondAck(const Jid &to, const QString &id) { send(createIQ(doc(), "result", to.full(), id)); } void JT_IBB::onGo() { send(d->iq); } bool JT_IBB::take(const QDomElement &e) { if (d->serve) { // must be an iq-set tag if (e.tagName() != "iq" || e.attribute("type") != "set") return false; QString id = e.attribute("id"); QString from = e.attribute("from"); QDomElement openEl = e.firstChildElement("open"); if (!openEl.isNull() && openEl.namespaceURI() == IBB_NS) { emit incomingRequest(Jid(from), id, openEl.attribute("sid"), openEl.attribute("block-size").toInt(), openEl.attribute("stanza")); return true; } QDomElement dataEl = e.firstChildElement("data"); if (!dataEl.isNull() && dataEl.namespaceURI() == IBB_NS) { IBBData data; emit incomingData(Jid(from), id, data.fromXml(dataEl), Stanza::IQ); return true; } QDomElement closeEl = e.firstChildElement("close"); if (!closeEl.isNull() && closeEl.namespaceURI() == IBB_NS) { emit closeRequest(Jid(from), id, closeEl.attribute("sid")); return true; } return false; } else { Jid from(e.attribute("from")); if (e.attribute("id") != id() || !d->to.compare(from)) return false; if (e.attribute("type") == "result") { setSuccess(); } else { setError(e); } return true; } } Jid JT_IBB::jid() const { return d->to; } int JT_IBB::mode() const { return d->mode; } int JT_IBB::bytesWritten() const { return d->bytesWritten; } psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_ibb.h000066400000000000000000000111341370065651000236340ustar00rootroot00000000000000/* * ibb.h - Inband bytestream * Copyright (C) 2001-2002 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_IBB_H #define XMPP_IBB_H #include "bytestream.h" #include "xmpp_bytestream.h" #include "xmpp_task.h" #include #include #include namespace XMPP { class Client; class IBBManager; class IBBData { public: IBBData() : seq(0) { } IBBData(const QString &sid, quint16 seq, const QByteArray &data) : sid(sid), seq(seq), data(data) { } IBBData & fromXml(const QDomElement &e); QDomElement toXml(QDomDocument *) const; QString sid; quint16 seq; QByteArray data; }; // this is an IBB connection. use it much like a qsocket class IBBConnection : public BSConnection { Q_OBJECT public: static const int PacketSize = 4096; enum { ErrRequest, ErrData }; enum { Idle, Requesting, WaitingForAccept, Active }; IBBConnection(IBBManager *); ~IBBConnection(); void setPacketSize(int blockSize = IBBConnection::PacketSize); void connectToJid(const Jid &peer, const QString &sid); void accept(); void close(); int state() const; Jid peer() const; QString sid() const; BytestreamManager *manager() const; bool isOpen() const; protected: qint64 writeData(const char *data, qint64 maxSize); signals: void connected(); private slots: void ibb_finished(); void trySend(); private: class Private; Private *d; void resetConnection(bool clear = false); friend class IBBManager; void waitForAccept(const Jid &peer, const QString &iq_id, const QString &sid, int blockSize, const QString &stanza); void takeIncomingData(const IBBData &ibbData); void setRemoteClosed(); }; typedef QList IBBConnectionList; class IBBManager : public BytestreamManager { Q_OBJECT public: IBBManager(Client *); ~IBBManager(); static const char *ns(); Client * client() const; bool isAcceptableSID(const Jid &peer, const QString &sid) const; BSConnection * createConnection(); IBBConnection *takeIncoming(); public slots: void takeIncomingData(const Jid &from, const QString &id, const IBBData &data, Stanza::Kind); protected: const char *sidPrefix() const; private slots: void ibb_incomingRequest(const Jid &from, const QString &id, const QString &sid, int blockSize, const QString &stanza); void ibb_closeRequest(const Jid &from, const QString &id, const QString &sid); private: class Private; Private *d; friend class IBBConnection; IBBConnection *findConnection(const QString &sid, const Jid &peer = "") const; void link(IBBConnection *); void unlink(IBBConnection *); void doAccept(IBBConnection *c, const QString &id); void doReject(IBBConnection *c, const QString &id, Stanza::Error::ErrorCond cond, const QString &); }; class JT_IBB : public Task { Q_OBJECT public: enum { ModeRequest, ModeSendData }; JT_IBB(Task *, bool serve = false); ~JT_IBB(); void request(const Jid &, const QString &sid, int blockSize = IBBConnection::PacketSize); void sendData(const Jid &, const IBBData &ibbData); void close(const Jid &, const QString &sid); void respondError(const Jid &, const QString &id, Stanza::Error::ErrorCond cond, const QString &text = ""); void respondAck(const Jid &to, const QString &id); void onGo(); bool take(const QDomElement &); Jid jid() const; int mode() const; int bytesWritten() const; signals: void incomingRequest(const Jid &from, const QString &id, const QString &sid, int blockSize, const QString &stanza); void incomingData(const Jid &from, const QString &id, const IBBData &data, Stanza::Kind); void closeRequest(const Jid &from, const QString &id, const QString &sid); private: class Private; Private *d; }; } // namespace XMPP #endif // XMPP_IBB_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_liveroster.h000066400000000000000000000026501370065651000253010ustar00rootroot00000000000000/* * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_LIVEROSTER_H #define XMPP_LIVEROSTER_H #include "xmpp_liverosteritem.h" #include namespace XMPP { class Jid; class LiveRoster : public QList { public: LiveRoster(); LiveRoster(const LiveRoster &other); ~LiveRoster(); LiveRoster &operator=(const LiveRoster &other); void flagAllForDelete(); LiveRoster::Iterator find(const Jid &, bool compareRes = true); LiveRoster::ConstIterator find(const Jid &, bool compareRes = true) const; void setGroupsDelimiter(const QString &groupsDelimiter); QString groupsDelimiter() const; private: class Private; Private *d; }; } // namespace XMPP #endif // XMPP_LIVEROSTER_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_liverosteritem.h000066400000000000000000000033051370065651000261560ustar00rootroot00000000000000/* * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_LIVEROSTERITEM_H #define XMPP_LIVEROSTERITEM_H #include "xmpp_resourcelist.h" #include "xmpp_rosteritem.h" #include "xmpp_status.h" namespace XMPP { class LiveRosterItem : public RosterItem { public: LiveRosterItem(const Jid &j = ""); LiveRosterItem(const RosterItem &); ~LiveRosterItem(); LiveRosterItem &operator=(const LiveRosterItem &other) = default; void setRosterItem(const RosterItem &); ResourceList & resourceList(); ResourceList::Iterator priority(); const ResourceList & resourceList() const; ResourceList::ConstIterator priority() const; bool isAvailable() const; const Status &lastUnavailableStatus() const; bool flagForDelete() const; void setLastUnavailableStatus(const Status &); void setFlagForDelete(bool); private: ResourceList v_resourceList; Status v_lastUnavailableStatus; bool v_flagForDelete; }; } // namespace XMPP #endif // XMPP_LIVEROSTERITEM_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_message.h000066400000000000000000000161231370065651000245270ustar00rootroot00000000000000/* * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_MESSAGE_H #define XMPP_MESSAGE_H #include "xmpp_address.h" #include "xmpp_chatstate.h" #include "xmpp_muc.h" #include "xmpp_receipts.h" #include "xmpp_reference.h" #include "xmpp_rosterx.h" #include "xmpp_stanza.h" #include "xmpp_url.h" #include class QDateTime; class QString; namespace XMPP { class BoBData; class HTMLElement; class HttpAuthRequest; class IBBData; class Jid; class PubSubItem; class PubSubRetraction; class XData; typedef QMap StringMap; typedef enum { OfflineEvent, DeliveredEvent, DisplayedEvent, ComposingEvent, CancelEvent } MsgEvent; class Message { public: enum CarbonDir : quint8 { NoCarbon, Received, // other party messages are sent to another own client Sent // own messages are sent from other clients }; // XEP-0334 enum ProcessingHint { NoPermanentStore = 1, NoStore = 2, NoCopy = 4, Store = 8 }; Q_DECLARE_FLAGS(ProcessingHints, ProcessingHint) struct StanzaId { Jid by; QString id; }; Message(); Message(const Jid &to); Message(const Message &from); Message &operator=(const Message &from); ~Message(); bool operator==(const Message &from) const; inline bool isNull() const { return d == nullptr; } Jid to() const; Jid from() const; QString id() const; QString type() const; QString lang() const; QString subject(const QString &lang = QString()) const; QString subject(const QLocale &lang) const; StringMap subjectMap() const; QString body(const QString &lang = "") const; QString body(const QLocale &lang) const; QString thread() const; Stanza::Error error() const; void setTo(const Jid &j); void setFrom(const Jid &j); void setId(const QString &s); void setType(const QString &s); void setLang(const QString &s); void setSubject(const QString &s, const QString &lang = ""); void setBody(const QString &s, const QString &lang = ""); void setThread(const QString &s, bool send = false); void setError(const Stanza::Error &err); // XEP-0060 QString pubsubNode() const; QList pubsubItems() const; QList pubsubRetractions() const; // XEP-0091 QDateTime timeStamp() const; void setTimeStamp(const QDateTime &ts, bool send = false); // XEP-0071 HTMLElement html(const QString &lang = "") const; void setHTML(const HTMLElement &s, const QString &lang = ""); bool containsHTML() const; // XEP-0066 UrlList urlList() const; void urlAdd(const Url &u); void urlsClear(); void setUrlList(const UrlList &list); // XEP-0022 QString eventId() const; void setEventId(const QString &id); bool containsEvents() const; bool containsEvent(MsgEvent e) const; void addEvent(MsgEvent e); // XEP-0085 ChatState chatState() const; void setChatState(ChatState); // XEP-0184 MessageReceipt messageReceipt() const; void setMessageReceipt(MessageReceipt); QString messageReceiptId() const; void setMessageReceiptId(const QString &s); // XEP-0027 QString xsigned() const; void setXSigned(const QString &s); QString xencrypted() const; void setXEncrypted(const QString &s); // XEP-0033 AddressList addresses() const; AddressList findAddresses(Address::Type t) const; void addAddress(const Address &a); void clearAddresses(); void setAddresses(const AddressList &list); // XEP-144 RosterExchangeItems rosterExchangeItems() const; void setRosterExchangeItems(const RosterExchangeItems &); // XEP-172 void setNick(const QString &); QString nick() const; // XEP-0070 void setHttpAuthRequest(const HttpAuthRequest &); HttpAuthRequest httpAuthRequest() const; // XEP-0004 void setForm(const XData &); XData getForm() const; // XEP-xxxx SXE void setSxe(const QDomElement &); QDomElement sxe() const; // XEP-0231 bits of binary void addBoBData(const BoBData &); QList bobDataList() const; // XEP-0047 ibb IBBData ibbData() const; // XEP-0280 Message Carbons void setDisabledCarbons(bool disabled); bool isDisabledCarbons() const; void setCarbonDirection(CarbonDir); CarbonDir carbonDirection() const; // XEP-0297 void setForwardedFrom(const Jid &jid); Jid forwardedFrom() const; // XEP-0308 QString replaceId() const; void setReplaceId(const QString &id); // XEP-0334 void setProcessingHints(const ProcessingHints &hints); ProcessingHints processingHints() const; // MUC void addMUCStatus(int); QList getMUCStatuses() const; void addMUCInvite(const MUCInvite &); QList mucInvites() const; void setMUCDecline(const MUCDecline &); MUCDecline mucDecline() const; QString mucPassword() const; void setMUCPassword(const QString &); bool hasMUCUser() const; // XEP-0359 StanzaId stanzaId() const; void setStanzaId(const StanzaId &id); QString originId() const; void setOriginId(const QString &id); // XEP-0380 QString encryptionProtocol() const; void setEncryptionProtocol(const QString &protocol); // XEP-0385 and XEP-0372 QList references() const; void addReference(const Reference &r); void setReferences(const QList &r); // Obsolete invitation QString invite() const; void setInvite(const QString &s); // for compatibility. delete me later bool spooled() const; void setSpooled(bool); bool wasEncrypted() const; void setWasEncrypted(bool); Stanza toStanza(Stream *stream) const; bool fromStanza(const Stanza &s); bool fromStanza(const Stanza &s, int tzoffset); bool fromStanza(const Stanza &s, bool useTimeZoneOffset, int timeZoneOffset); private: class Private; QExplicitlySharedDataPointer d; }; } // namespace XMPP Q_DECLARE_OPERATORS_FOR_FLAGS(XMPP::Message::ProcessingHints) #endif // XMPP_MESSAGE_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_muc.h000066400000000000000000000066251370065651000236750ustar00rootroot00000000000000/* * xmpp_muc.h * Copyright (C) 2006 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_MUC_H #define XMPP_MUC_H #include "xmpp/jid/jid.h" #include #include namespace XMPP { class MUCItem { public: enum Affiliation { UnknownAffiliation, Outcast, NoAffiliation, Member, Admin, Owner }; enum Role { UnknownRole, NoRole, Visitor, Participant, Moderator }; MUCItem(Role = UnknownRole, Affiliation = UnknownAffiliation); MUCItem(const QDomElement &); void setNick(const QString &); void setJid(const Jid &); void setAffiliation(Affiliation); void setRole(Role); void setActor(const Jid &); void setReason(const QString &); const QString &nick() const; const Jid & jid() const; // real jid of muc participant Affiliation affiliation() const; Role role() const; const Jid & actor() const; const QString &reason() const; void fromXml(const QDomElement &); QDomElement toXml(QDomDocument &); bool operator==(const MUCItem &o); private: QString nick_; Jid jid_, actor_; Affiliation affiliation_; Role role_; QString reason_; }; class MUCInvite { public: MUCInvite(); MUCInvite(const QDomElement &); MUCInvite(const Jid &to, const QString &reason = QString()); const Jid & to() const; void setTo(const Jid &); const Jid & from() const; void setFrom(const Jid &); const QString &reason() const; void setReason(const QString &); bool cont() const; void setCont(bool); void fromXml(const QDomElement &); QDomElement toXml(QDomDocument &) const; bool isNull() const; private: Jid to_, from_; QString reason_, password_; bool cont_; }; class MUCDecline { public: MUCDecline(); MUCDecline(const Jid &to, const QString &reason); MUCDecline(const QDomElement &); const Jid & to() const; void setTo(const Jid &); const Jid & from() const; void setFrom(const Jid &); const QString &reason() const; void setReason(const QString &); void fromXml(const QDomElement &); QDomElement toXml(QDomDocument &) const; bool isNull() const; private: Jid to_, from_; QString reason_; }; class MUCDestroy { public: MUCDestroy(); MUCDestroy(const QDomElement &); const Jid & jid() const; void setJid(const Jid &); const QString &reason() const; void setReason(const QString &); void fromXml(const QDomElement &); QDomElement toXml(QDomDocument &) const; private: Jid jid_; QString reason_; }; } // namespace XMPP #endif // XMPP_MUC_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_pubsubitem.h000066400000000000000000000021461370065651000252620ustar00rootroot00000000000000/* * Copyright (C) 2006 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_PUBSUBITEM_H #define XMPP_PUBSUBITEM_H #include #include namespace XMPP { class PubSubItem { public: PubSubItem(); PubSubItem(const QString &id, const QDomElement &payload); const QString & id() const; const QDomElement &payload() const; private: QString id_; QDomElement payload_; }; } // namespace XMPP #endif // XMPP_PUBSUBITEM_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_pubsubretraction.h000066400000000000000000000020441370065651000264730ustar00rootroot00000000000000/* * Copyright (C) 2006 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_PUBSUBRETRACTION_H #define XMPP_PUBSUBRETRACTION_H #include #include namespace XMPP { class PubSubRetraction { public: PubSubRetraction(); PubSubRetraction(const QString &id); const QString &id() const; private: QString id_; }; } // namespace XMPP #endif // XMPP_PUBSUBRETRACTION_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_receipts.h000066400000000000000000000017031370065651000247170ustar00rootroot00000000000000/* * xmpp_receipts.h - XEP-0184 support helper file * Copyright (C) 2007 Michail Pishchagin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_RECEIPTS_H #define XMPP_RECEIPTS_H namespace XMPP { typedef enum { ReceiptNone, ReceiptRequest, ReceiptReceived } MessageReceipt; } // namespace XMPP #endif // XMPP_RECEIPTS_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_reference.cpp000066400000000000000000000132621370065651000253750ustar00rootroot00000000000000/* * xmpp_reference.h - XMPP References / XEP-0372 * Copyright (C) 2019 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp_reference.h" using namespace XMPP; #define D() (d ? d : (d = new Private)) const QString XMPP::MEDIASHARING_NS(QStringLiteral("urn:xmpp:sims:1")); const QString XMPP::REFERENCE_NS(QStringLiteral("urn:xmpp:reference:0")); class Reference::Private : public QSharedData { public: Reference::Type type; QString uri; QString anchor; int begin = -1; int end = -1; MediaSharing mediaSharing; }; Reference::Reference() { } Reference::Reference(Type type, const QString &uri) : d(new Private) { d->type = type; d->uri = uri; } Reference::~Reference() { } Reference::Reference(const Reference &other) : d(other.d) { } Reference &Reference::operator=(const Reference &other) { d = other.d; return *this; } Reference::Type Reference::type() const { return d->type; } const QString &Reference::uri() const { return d->uri; } void Reference::setRange(int begin, int end) { D()->begin = begin; d->end = end; } int Reference::begin() const { return d->begin; } int Reference::end() const { return d->end; } const QString &Reference::anchor() const { return d->anchor; } void Reference::setAnchor(const QString &anchor) { D()->anchor = anchor; } void Reference::setMediaSharing(const MediaSharing &ms) { D()->mediaSharing = ms; } const MediaSharing &Reference::mediaSharing() const { return d->mediaSharing; } bool Reference::fromXml(const QDomElement &e) { QString type = e.attribute(QString::fromLatin1("type")); QString uri = e.attribute(QString::fromLatin1("uri")); QString begin = e.attribute(QString::fromLatin1("begin")); QString end = e.attribute(QString::fromLatin1("end")); QString anchor = e.attribute(QString::fromLatin1("anchor")); if (type.isEmpty() || uri.isEmpty()) { return false; } Type t; if (type == QString::fromLatin1("data")) t = Data; else if (type == QString::fromLatin1("mention")) t = Mention; else return false; int beginN = -1, endN = -1; bool ok; if (!begin.isEmpty() && !(beginN = begin.toInt(&ok), ok)) return false; if (!end.isEmpty() && !(endN = end.toInt(&ok), ok)) return false; if (beginN >= 0 && endN >= 0 && endN < beginN) return false; if ((endN >= 0 && beginN == -1) || (endN == -1 && beginN >= 0)) return false; auto msEl = e.firstChildElement("media-sharing"); MediaSharing ms; if (msEl.namespaceURI() == MEDIASHARING_NS) { auto fileEl = msEl.firstChildElement("file"); auto sourcesEl = msEl.firstChildElement("sources"); if (sourcesEl.isNull() || fileEl.isNull() || fileEl.namespaceURI() != XMPP::Jingle::FileTransfer::NS) return false; ms.file = XMPP::Jingle::FileTransfer::File(fileEl); if (!ms.file.isValid() || !ms.file.hasComputedHashes()) return false; auto srcName = QString::fromLatin1("reference"); for (auto el = sourcesEl.firstChildElement(srcName); !el.isNull(); el = el.nextSiblingElement(srcName)) { if (el.namespaceURI() == REFERENCE_NS) { Reference ref; if (!ref.fromXml(el)) { return false; } ms.sources.append(ref.uri()); } } if (ms.sources.isEmpty()) return false; } D()->type = t; d->uri = uri; d->begin = beginN; d->end = endN; d->anchor = anchor; d->mediaSharing = ms; return true; } QDomElement Reference::toXml(QDomDocument *doc) const { if (!d) { return QDomElement(); } auto root = doc->createElementNS(REFERENCE_NS, QString::fromLatin1("reference")); root.setAttribute(QString::fromLatin1("uri"), d->uri); root.setAttribute(QString::fromLatin1("type"), QString(d->type == Reference::Mention ? "mention" : "data")); if (d->mediaSharing.file.isValid() && d->mediaSharing.sources.count()) { auto msEl = doc->createElementNS(MEDIASHARING_NS, QString::fromLatin1("media-sharing")); root.appendChild(msEl); msEl.appendChild(d->mediaSharing.file.toXml(doc)); auto sourcesEl = msEl.appendChild(doc->createElement(QString::fromLatin1("sources"))).toElement(); for (auto const &s : d->mediaSharing.sources) { auto sEl = sourcesEl.appendChild(doc->createElementNS(REFERENCE_NS, QString::fromLatin1("reference"))) .toElement(); sEl.setAttribute(QString::fromLatin1("uri"), s); sEl.setAttribute(QString::fromLatin1("type"), QString::fromLatin1("data")); } } if (d->begin != -1) root.setAttribute(QString::fromLatin1("begin"), d->begin); if (d->end != -1) root.setAttribute(QString::fromLatin1("end"), d->end); if (!d->anchor.isEmpty()) root.setAttribute(QString::fromLatin1("anchor"), d->anchor); return root; } psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_reference.h000066400000000000000000000037571370065651000250520ustar00rootroot00000000000000/* * xmpp_reference.h - XMPP References / XEP-0372 * Copyright (C) 2019 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPPREFERENCE_H #define XMPPREFERENCE_H #include "jingle-ft.h" #include #include namespace XMPP { extern const QString MEDIASHARING_NS; extern const QString REFERENCE_NS; class MediaSharing { public: Jingle::FileTransfer::File file; QStringList sources; inline bool isValid() const { return file.isValid(); } }; class Reference { public: enum Type : char { Mention, Data }; Reference(); Reference(Type type, const QString &uri); ~Reference(); Reference(const Reference &other); Reference &operator=(const Reference &other); bool isValid() const { return d != nullptr; } Type type() const; const QString &uri() const; void setRange(int begin, int end); int begin() const; int end() const; const QString &anchor() const; void setAnchor(const QString &anchor); void setMediaSharing(const MediaSharing &); const MediaSharing &mediaSharing() const; bool fromXml(const QDomElement &e); QDomElement toXml(QDomDocument *) const; private: class Private; QSharedDataPointer d; }; } // namespace XMPP #endif // XMPPREFERENCE_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_resource.h000066400000000000000000000022701370065651000247300ustar00rootroot00000000000000/* * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_RESOURCE_H #define XMPP_RESOURCE_H #include "xmpp_status.h" #include namespace XMPP { class Resource { public: Resource(const QString &name = "", const Status &s = Status()); const QString &name() const; int priority() const; const Status & status() const; void setName(const QString &); void setStatus(const Status &); private: QString v_name; Status v_status; }; } // namespace XMPP #endif // XMPP_RESOURCE_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_resourcelist.h000066400000000000000000000022721370065651000256260ustar00rootroot00000000000000/* * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_RESOURCELIST_H #define XMPP_RESOURCELIST_H #include "xmpp_resource.h" #include class QString; namespace XMPP { class ResourceList : public QList { public: ResourceList(); ~ResourceList(); ResourceList::Iterator find(const QString &); ResourceList::Iterator priority(); ResourceList::ConstIterator find(const QString &) const; ResourceList::ConstIterator priority() const; }; } // namespace XMPP #endif // XMPP_RESOURCELIST_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_roster.h000066400000000000000000000025001370065651000244130ustar00rootroot00000000000000/* * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_ROSTER_H #define XMPP_ROSTER_H #include "xmpp_rosteritem.h" #include class QDomDocument; class QDomElement; namespace XMPP { class Jid; class Roster : public QList { public: Roster(); Roster(const Roster &other); ~Roster(); Roster &operator=(const Roster &other); Roster::Iterator find(const Jid &); Roster::ConstIterator find(const Jid &) const; void setGroupsDelimiter(const QString &groupsDelimiter); QString groupsDelimiter() const; private: class Private; Private *d = nullptr; }; } // namespace XMPP #endif // XMPP_ROSTER_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_rosteritem.h000066400000000000000000000043311370065651000252760ustar00rootroot00000000000000/* * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_ROSTERITEM_H #define XMPP_ROSTERITEM_H #include "xmpp/jid/jid.h" #include #include namespace XMPP { class Subscription { public: enum SubType { None, To, From, Both, Remove }; Subscription(SubType type = None); int type() const; QString toString() const; bool fromString(const QString &); private: SubType value; }; class RosterItem { public: RosterItem(const Jid &jid = ""); RosterItem(const RosterItem &item); virtual ~RosterItem(); RosterItem &operator=(const RosterItem &other) = default; const Jid & jid() const; const QString & name() const; const QStringList & groups() const; const Subscription &subscription() const; const QString & ask() const; bool isPush() const; bool inGroup(const QString &) const; virtual void setJid(const Jid &); void setName(const QString &); void setGroups(const QStringList &); void setSubscription(const Subscription &); void setAsk(const QString &); void setIsPush(bool); bool addGroup(const QString &); bool removeGroup(const QString &); QDomElement toXml(QDomDocument *) const; bool fromXml(const QDomElement &); private: Jid v_jid; QString v_name; QStringList v_groups; Subscription v_subscription; QString v_ask; bool v_push; }; } // namespace XMPP #endif // XMPP_ROSTERITEM_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_rosterx.h000066400000000000000000000034131370065651000246070ustar00rootroot00000000000000/* * rosterexchangeitem.h * Copyright (C) 2003 Remko Troncon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_ROSTERX_H #define XMPP_ROSTERX_H #include "xmpp/jid/jid.h" #include #include class QDomElement; namespace XMPP { class Stanza; class RosterExchangeItem { public: enum Action { Add, Delete, Modify }; RosterExchangeItem(const Jid &jid, const QString &name = "", const QStringList &groups = QStringList(), Action = Add); RosterExchangeItem(const QDomElement &); const Jid & jid() const; Action action() const; const QString & name() const; const QStringList &groups() const; bool isNull() const; void setJid(const Jid &); void setAction(Action); void setName(const QString &); void setGroups(const QStringList &); QDomElement toXml(Stanza &) const; void fromXml(const QDomElement &); private: Jid jid_; QString name_; QStringList groups_; Action action_; }; typedef QList RosterExchangeItems; } // namespace XMPP #endif // XMPP_ROSTERX_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_serverinfomanager.cpp000066400000000000000000000257221370065651000271600ustar00rootroot00000000000000/* * xmpp_serverinfomanager.cpp * Copyright (C) 2006-2019 Remko Troncon, Sergey Ilinykh * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp_serverinfomanager.h" #include "xmpp_caps.h" #include "xmpp_client.h" #include "xmpp_tasks.h" namespace XMPP { ServerInfoManager::ServerInfoManager(Client *client) : QObject(client), _client(client), _canMessageCarbons(false) { deinitialize(); // NOTE we can use this class for any server, but for this we shouldn't use roster signal here connect(_client, SIGNAL(rosterRequestFinished(bool, int, const QString &)), SLOT(initialize()), Qt::QueuedConnection); } void ServerInfoManager::reset() { _hasPEP = false; _multicastService.clear(); _extraServerInfo.clear(); disconnect(CapsRegistry::instance()); disconnect(_client, SIGNAL(disconnected()), this, SLOT(deinitialize())); } void ServerInfoManager::initialize() { connect(_client, SIGNAL(disconnected()), SLOT(deinitialize())); JT_DiscoInfo *jt = new JT_DiscoInfo(_client->rootTask()); connect(jt, SIGNAL(finished()), SLOT(disco_finished())); jt->get(_client->jid().domain()); jt->go(true); queryServicesList(); } void ServerInfoManager::deinitialize() { reset(); emit featuresChanged(); } const QString &ServerInfoManager::multicastService() const { return _multicastService; } bool ServerInfoManager::hasPEP() const { return _hasPEP; } bool ServerInfoManager::canMessageCarbons() const { return _canMessageCarbons; } void ServerInfoManager::queryServicesList() { _servicesListState = ST_InProgress; auto jtitems = new JT_DiscoItems(_client->rootTask()); connect( jtitems, &JT_DiscoItems::finished, this, [=]() { _servicesInfo.clear(); // if (jtitems->success()) { _servicesListState = ST_Ready; for (const auto &item : jtitems->items()) { _servicesInfo.insert(item.jid().full(), { ST_NotQueried, item, QMap() }); } } else { _servicesListState = ST_Failed; } checkPendingServiceQueries(); }, Qt::QueuedConnection); jtitems->get(_client->jid().domain()); jtitems->go(true); } void ServerInfoManager::checkPendingServiceQueries() { // if services list is not ready yet we have to exit. if it's failed we have to finish all pending queries if (_servicesListState != ST_Ready) { if (_servicesListState == ST_Failed) { const auto sqs = _serviceQueries; _serviceQueries.clear(); for (const auto &q : sqs) { q.callback(QList()); } } return; } // services list is ready here and we can start checking it and sending disco#info to not cached entries auto sqIt = _serviceQueries.begin(); while (sqIt != _serviceQueries.end()) { // populate services to query for this service request if (!sqIt->servicesToQueryDefined) { sqIt->spareServicesToQuery.clear(); // grep all suitble service jids. moving forward preferred ones QMapIterator si(_servicesInfo); while (si.hasNext()) { si.next(); if (!sqIt->nameHint.isEmpty()) { if (sqIt->nameHint.isEmpty() || sqIt->nameHint.exactMatch(si.key())) { sqIt->servicesToQuery.push_back(si.key()); } else if (sqIt->options & SQ_CheckAllOnNoMatch) { sqIt->spareServicesToQuery.push_back(si.key()); } } else { sqIt->servicesToQuery.push_back(si.key()); } } if (sqIt->servicesToQuery.empty()) { sqIt->servicesToQuery = sqIt->spareServicesToQuery; sqIt->spareServicesToQuery.clear(); } if (sqIt->servicesToQuery.empty()) { sqIt->callback(QList()); _serviceQueries.erase(sqIt++); continue; } sqIt->servicesToQueryDefined = true; } // now `sqIt->servicesToQuery` definitely has something to check. maybe some info is already in cache. bool hasInProgress = false; auto jidIt = sqIt->servicesToQuery.begin(); // bool foundMatch = false; while (jidIt != sqIt->servicesToQuery.end()) { auto si = _servicesInfo.find(*jidIt); // find cached service corresponding to one of matched jids if (si == _servicesInfo.end() || si->state == ST_Failed) { // the map was updated after the first service list request, or info request failed. sqIt->servicesToQuery.erase(jidIt++); continue; } else if (si->state == ST_Ready) { // disco info finished successfully for current jid from `servicesToQuery` bool foundIdentity = sqIt->category.isEmpty() && sqIt->type.isEmpty(); if (!foundIdentity) { for (auto &i : si->item.identities()) { if ((sqIt->category.isEmpty() || sqIt->category == i.category) && (sqIt->type.isEmpty() || sqIt->type == i.type)) { foundIdentity = true; break; } } } if (foundIdentity && (sqIt->features.isEmpty() || std::accumulate( sqIt->features.constBegin(), sqIt->features.constEnd(), false, [&si](bool a, const QSet &b) { return a || si->item.features().test(b); }))) { sqIt->result.append(si->item); if (sqIt->options & SQ_FinishOnFirstMatch) { break; } } sqIt->servicesToQuery.erase(jidIt++); continue; } // if we a here then service info state is either not-queried or in-progress Q_ASSERT(si->state == ST_NotQueried || si->state == ST_InProgress); hasInProgress = true; if (si->state == ST_NotQueried) { // if not queried then let's query si->state = ST_InProgress; auto jtinfo = new JT_DiscoInfo(_client->rootTask()); connect(jtinfo, &DiscoInfoTask::finished, this, [this, jtinfo]() { auto si = _servicesInfo.find(jtinfo->jid().full()); if (si != _servicesInfo.end()) { if (jtinfo->success()) { si.value().state = ST_Ready; si.value().item = jtinfo->item(); } else { si.value().state = ST_Failed; } } checkPendingServiceQueries(); }); jtinfo->get(Jid(*jidIt), si.value().item.node()); jtinfo->go(true); } ++jidIt; } if (sqIt->result.isEmpty() && !hasInProgress && !sqIt->spareServicesToQuery.empty()) { // we don't check sqIt->servicesToQuery.isEmpty() since it comes from other conditions // (sqIt->result.isEmpty() && !hasInProgress) sqIt->servicesToQuery = sqIt->spareServicesToQuery; sqIt->spareServicesToQuery.clear(); continue; // continue with the same ServiceQuery but with different jids list } // if has at least one sufficient result auto forceFinish = (!sqIt->result.isEmpty() && (sqIt->options & SQ_FinishOnFirstMatch)); // stop on first found // if nothing in progress then we have full result set or nothing found even in spare list if (forceFinish || !hasInProgress) { // self explanatory auto callback = std::move(sqIt->callback); auto result = sqIt->result; _serviceQueries.erase(sqIt++); callback(result); } else { ++sqIt; } } } void ServerInfoManager::appendQuery(const ServiceQuery &q) { _serviceQueries.push_back(q); if (_servicesListState == ST_InProgress) { return; } if (_servicesListState == ST_NotQueried || _servicesListState == ST_Failed) { queryServicesList(); } else { // ready checkPendingServiceQueries(); } } void ServerInfoManager::queryServiceInfo(const QString &category, const QString &type, const QList> &features, const QRegExp &nameHint, SQOptions options, std::function &items)> callback) { appendQuery(ServiceQuery(type, category, features, nameHint, options, std::move(callback))); } void ServerInfoManager::setServiceMeta(const Jid &service, const QString &key, const QVariant &value) { auto it = _servicesInfo.find(service.full()); if (it != _servicesInfo.end()) { it.value().meta.insert(key, value); } } QVariant ServerInfoManager::serviceMeta(const Jid &service, const QString &key) { auto it = _servicesInfo.find(service.full()); if (it != _servicesInfo.end()) { return it.value().meta.value(key); } return QVariant(); } void ServerInfoManager::disco_finished() { JT_DiscoInfo *jt = static_cast(sender()); if (jt->success()) { _features = jt->item().features(); if (_features.hasMulticast()) _multicastService = _client->jid().domain(); _canMessageCarbons = _features.hasMessageCarbons(); // Identities DiscoItem::Identities is = jt->item().identities(); for (DiscoItem::Identity i : is) { if (i.category == "pubsub" && i.type == "pep") _hasPEP = true; } auto servInfo = jt->item().findExtension(XData::Data_Result, QLatin1String("http://jabber.org/network/serverinfo")); if (servInfo.isValid()) { for (const auto &f : servInfo.fields()) { if (f.type() == XData::Field::Field_ListMulti) { _extraServerInfo.insert(f.var(), f.value()); // covers XEP-0157 } } } emit featuresChanged(); } } } // namespace XMPP psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_serverinfomanager.h000066400000000000000000000127301370065651000266200ustar00rootroot00000000000000/* * xmpp_serverinfomanager.h * Copyright (C) 2006 Remko Troncon * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef SERVERINFOMANAGER_H #define SERVERINFOMANAGER_H #include "xmpp_caps.h" #include "xmpp_discoitem.h" #include #include #include #include #include namespace XMPP { class Client; class Features; class Jid; class ServerInfoManager : public QObject { Q_OBJECT public: enum SQOption { SQ_CheckAllOnNoMatch = 1, // check all if matched by name services do not match or no matched by name SQ_FinishOnFirstMatch = 2, // first callback is final SQ_CallbackOnAnyMatches = 4 // TODO don't wait while all services will be discovered. empty result list = final }; Q_DECLARE_FLAGS(SQOptions, SQOption) private: struct ServiceQuery { const QString type; const QString category; const QList> features; const QRegExp nameHint; const SQOptions options; const std::function &item)> callback; std::list servicesToQuery; std::list spareServicesToQuery; // usually a fallback when the above is not matched bool servicesToQueryDefined = false; QList result; ServiceQuery(const QString &type, const QString &category, const QList> &features, const QRegExp &nameHint, const SQOptions &options, const std::function &item)> &&callback) : type(type), category(category), features(features), nameHint(nameHint), options(options), callback(callback) { } }; enum ServicesState { ST_NotQueried, ST_InProgress, ST_Ready, ST_Failed }; struct ServiceInfo { ServicesState state; DiscoItem item; QMap meta; }; public: ServerInfoManager(XMPP::Client *client); const QString & multicastService() const; bool hasPEP() const; inline const Features & features() const { return _features; } bool canMessageCarbons() const; inline const QMap &extraServerInfo() const { return _extraServerInfo; } /* empty type/category/features/nameHint means it won't be checked. nameHint is a regular expression for service jid. empty regexp = ".*". if regexp is not empty but matches with empty string then first matched not empty name will be preferred, and if nothing nonempty matched then all services will be checked by other params. If regexp doesn't match with empty string then only exact matches will be checked. It means nameHint may work like a hint but not a requirement. features is a list of options groups. all options of any group must match Example: type = file category = store features = [("urn:xmpp:http:upload"),("urn:xmpp:http:upload:0")] nameHint = (http\..*|) // search for service name like http.jabber.ru Result: disco info for upload.jabber.ru will be returned. */ void queryServiceInfo(const QString &category, const QString &type, const QList> &features, const QRegExp &nameHint, SQOptions options, std::function &items)> callback); void setServiceMeta(const Jid &service, const QString &key, const QVariant &value); QVariant serviceMeta(const Jid &service, const QString &key); signals: void featuresChanged(); void servicesChanged(); private slots: void disco_finished(); void initialize(); void deinitialize(); void reset(); private: void queryServicesList(); void checkPendingServiceQueries(); void appendQuery(const ServiceQuery &q); private: XMPP::Client * _client = nullptr; CapsSpec _caps; Features _features; QString _multicastService; QMap _extraServerInfo; // XEP-0128, XEP-0157 std::list _serviceQueries; // a storage of pending requests as result of `queryService` call ServicesState _servicesListState = ST_NotQueried; QMap _servicesInfo; // all the diso#info requests for services of this server jid=>(state,info) bool _featuresRequested; bool _hasPEP; bool _canMessageCarbons; }; } // namespace XMPP #endif // SERVERINFOMANAGER_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_status.h000066400000000000000000000114431370065651000244260ustar00rootroot00000000000000/* * xmpp_status.h * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_STATUS_H #define XMPP_STATUS_H #include "xmpp_bitsofbinary.h" #include "xmpp_muc.h" #include #include #include #include #include namespace XMPP { class DiscoItem; class StatusPrivate; class CapsSpec { public: typedef QMap CryptoMap; static const QCryptographicHash::Algorithm invalidAlgo = (QCryptographicHash::Algorithm)255; CapsSpec(); CapsSpec(const QString &node, QCryptographicHash::Algorithm hashAlgo, const QString &ver = QString()); CapsSpec(const DiscoItem &disco, QCryptographicHash::Algorithm hashAlgo = QCryptographicHash::Sha1); bool isValid() const; const QString & node() const; const QString & version() const; QCryptographicHash::Algorithm hashAlgorithm() const; inline const QStringList & ext() const { return ext_; } QString flatten() const; void resetVersion(); bool operator==(const CapsSpec &) const; bool operator!=(const CapsSpec &) const; bool operator<(const CapsSpec &) const; QDomElement toXml(QDomDocument *doc) const; static CapsSpec fromXml(const QDomElement &e); static CryptoMap &cryptoMap(); private: QString node_, ver_; QCryptographicHash::Algorithm hashAlgo_; QStringList ext_; }; class Status { public: enum Type { Offline, Online, Away, XA, DND, Invisible, FFC }; Status(const QString &show = QString(), const QString &status = QString(), int priority = 0, bool available = true); Status(Type type, const QString &status = QString(), int priority = 0); Status(const Status &); Status &operator=(const Status &); ~Status(); int priority() const; Type type() const; QString typeString() const; const QString &show() const; const QString &status() const; QDateTime timeStamp() const; const QString &keyID() const; bool isAvailable() const; bool isAway() const; bool isInvisible() const; bool hasError() const; int errorCode() const; const QString &errorString() const; const QString & xsigned() const; const QString & songTitle() const; const CapsSpec &caps() const; bool isMUC() const; bool hasMUCItem() const; const MUCItem & mucItem() const; bool hasMUCDestroy() const; const MUCDestroy &mucDestroy() const; const QList &getMUCStatuses() const; const QString & mucPassword() const; bool hasMUCHistory() const; int mucHistoryMaxChars() const; int mucHistoryMaxStanzas() const; int mucHistorySeconds() const; const QDateTime & mucHistorySince() const; static Type txt2type(const QString &stat); void setPriority(int); void setType(Type); void setType(const QString &); void setShow(const QString &); void setStatus(const QString &); void setTimeStamp(const QDateTime &); void setKeyID(const QString &); void setIsAvailable(bool); void setIsInvisible(bool); void setError(int, const QString &); void setCaps(const CapsSpec &); void setMUC(); void setMUCItem(const MUCItem &); void setMUCDestroy(const MUCDestroy &); void addMUCStatus(int); void setMUCPassword(const QString &); void setMUCHistory(int maxchars, int maxstanzas, int seconds, const QDateTime &since); void setXSigned(const QString &); void setSongTitle(const QString &); // XEP-153: VCard-based Avatars const QByteArray &photoHash() const; void setPhotoHash(const QByteArray &); bool hasPhotoHash() const; // XEP-0231 bits of binary void addBoBData(const BoBData &); QList bobDataList() const; private: QSharedDataPointer d; }; } // namespace XMPP Q_DECLARE_METATYPE(XMPP::Status) #endif // XMPP_STATUS_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_subsets.cpp000066400000000000000000000163041370065651000251270ustar00rootroot00000000000000/* * subsets.cpp - Implementation of Result Set Management (XEP-0059) * Copyright (C) 2018 Aleksey Andreev * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp_subsets.h" #include "xmpp_xmlcommon.h" using namespace XMPP; static QLatin1String xmlns_ns_rsm("http://jabber.org/protocol/rsm"); class SubsetsClientManager::Private { public: enum QueryType { None, Count, First, Last, Next, Previous, Index }; struct { QueryType type; int max; int index; } query; struct { int count; int index; bool first; bool last; int itemsCount; QString firstId; QString lastId; } result; bool valid; void resetResult() { result.count = -1; result.index = -1; result.first = false; result.last = false; result.itemsCount = 0; valid = false; } QDomElement mainElement(QDomDocument *doc) { QDomElement e = doc->createElementNS(xmlns_ns_rsm, QStringLiteral("set")); return e; } void insertMaxElement(QDomDocument *doc, QDomElement *el, int i) { el->appendChild(textTag(doc, QStringLiteral("max"), QString::number(i))); } void insertBeforeElement(QDomDocument *doc, QDomElement *el, const QString &s) { if (s.isEmpty()) el->appendChild(doc->createElement(QStringLiteral("before"))); else el->appendChild(textTag(doc, QStringLiteral("before"), s)); } void insertAfterElement(QDomDocument *doc, QDomElement *el, const QString &s) { el->appendChild(textTag(doc, QStringLiteral("after"), s)); } void insertIndexElement(QDomDocument *doc, QDomElement *el, int i) { el->appendChild(textTag(doc, QStringLiteral("index"), QString::number(i))); } bool updateFromElement(const QDomElement &el) { valid = true; bool ok = false; QDomElement e = el.firstChildElement(QLatin1String("count")); if (!e.isNull()) result.count = tagContent(e).toInt(&ok); if (!ok || result.count < 0) result.count = -1; result.index = -1; e = el.firstChildElement(QLatin1String("first")); if (!e.isNull()) { result.firstId = tagContent(e); if (result.firstId.isEmpty()) valid = false; int i = e.attribute(QLatin1String("index")).toInt(&ok); if (ok && i >= 0) result.index = i; } else result.firstId = ""; e = el.firstChildElement(QLatin1String("last")); if (!e.isNull()) { result.lastId = tagContent(e); if (result.lastId.isEmpty()) valid = false; } else result.lastId = ""; if (result.firstId.isEmpty() != result.lastId.isEmpty()) valid = false; result.first = query.type == First || result.index == 0 || (result.itemsCount == 0 && result.index == -1 && (query.type == Last || query.type == Previous)); result.last = query.type == Last || (result.index != -1 && result.count != -1 && result.count - result.itemsCount <= result.index) || (result.itemsCount == 0 && result.index == -1 && (query.type == First || query.type == Next)); if (result.firstId.isEmpty() && result.lastId.isEmpty()) { switch (query.type) { case Previous: result.first = true; break; case Next: case Index: result.last = true; break; default: break; } } return valid; } }; SubsetsClientManager::SubsetsClientManager() { d = new Private; reset(); } SubsetsClientManager::~SubsetsClientManager() { delete d; } void SubsetsClientManager::reset() { d->query.type = Private::None; d->query.max = 50; d->query.index = -1; d->result.firstId = QString(); d->result.lastId = QString(); d->resetResult(); } bool SubsetsClientManager::isValid() const { return d->valid; } bool SubsetsClientManager::isFirst() const { return d->result.first; } bool SubsetsClientManager::isLast() const { return d->result.last; } int SubsetsClientManager::count() const { return d->result.count; } void SubsetsClientManager::setMax(int max) { d->query.max = max; } QDomElement SubsetsClientManager::findElement(const QDomElement &el, bool child) { if (el.tagName() == QLatin1String("set") && el.namespaceURI() == xmlns_ns_rsm) return el; if (child) { QDomElement e = el.firstChildElement(QLatin1String("set")); while (!e.isNull()) { if (e.namespaceURI() == xmlns_ns_rsm) { return e; } e = e.nextSiblingElement(QLatin1String("set")); } } return QDomElement(); } bool SubsetsClientManager::updateFromElement(const QDomElement &el, int itemsCount) { if (findElement(el, false).isNull()) return false; d->result.itemsCount = itemsCount; return d->updateFromElement(el); } void SubsetsClientManager::getCount() { d->query.type = Private::Count; d->resetResult(); } void SubsetsClientManager::getFirst() { d->query.type = Private::First; d->resetResult(); } void SubsetsClientManager::getNext() { d->query.type = Private::Next; d->resetResult(); } void SubsetsClientManager::getLast() { d->query.type = Private::Last; d->resetResult(); } void SubsetsClientManager::getPrevious() { d->query.type = Private::Previous; d->resetResult(); } void SubsetsClientManager::getByIndex() { d->query.type = Private::Index; d->resetResult(); } QDomElement SubsetsClientManager::makeQueryElement(QDomDocument *doc) const { if (d->query.type == Private::None) return QDomElement(); QDomElement e = d->mainElement(doc); switch (d->query.type) { case Private::Count: d->insertMaxElement(doc, &e, 0); break; case Private::Last: d->insertBeforeElement(doc, &e, QString()); break; case Private::Next: d->insertAfterElement(doc, &e, d->result.lastId); break; case Private::Previous: d->insertBeforeElement(doc, &e, d->result.firstId); break; case Private::Index: d->insertIndexElement(doc, &e, d->query.index); case Private::First: default: break; } if (d->query.type != Private::Count) d->insertMaxElement(doc, &e, d->query.max); return e; } psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_subsets.h000066400000000000000000000030311370065651000245650ustar00rootroot00000000000000/* * subsets.h - Implementation of Result Set Management (XEP-0059) * Copyright (C) 2018 Aleksey Andreev * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_SUBSETS_H #define XMPP_SUBSETS_H #include namespace XMPP { class SubsetsClientManager { public: SubsetsClientManager(); ~SubsetsClientManager(); void reset(); bool isValid() const; bool isFirst() const; bool isLast() const; int count() const; void setMax(int max); void getCount(); void getFirst(); void getNext(); void getLast(); void getPrevious(); void getByIndex(); static QDomElement findElement(const QDomElement &el, bool child); bool updateFromElement(const QDomElement &el, int itemsCount); QDomElement makeQueryElement(QDomDocument *doc) const; private: class Private; Private *d; }; } // namespace XMPP #endif // XMPP_SUBSETS_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_task.cpp000066400000000000000000000201121370065651000243710ustar00rootroot00000000000000/* * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp_task.h" #include "xmpp_client.h" #include "xmpp_stanza.h" #include "xmpp_xmlcommon.h" #include #define DEFAULT_TIMEOUT 120 using namespace XMPP; class Task::TaskPrivate { public: TaskPrivate() = default; QString id; bool success = false; int statusCode = 0; QString statusString; XMPP::Stanza::Error error; Client * client = nullptr; bool insig = false; bool deleteme = false; bool autoDelete = false; bool done = false; int timeout = 0; }; Task::Task(Task *parent) : QObject(parent) { init(); d->client = parent->client(); d->id = client()->genUniqueId(); connect(d->client, SIGNAL(disconnected()), SLOT(clientDisconnected())); } Task::Task(Client *parent, bool) : QObject(nullptr) { init(); d->client = parent; connect(d->client, SIGNAL(disconnected()), SLOT(clientDisconnected())); } Task::~Task() { delete d; } void Task::init() { d = new TaskPrivate; d->success = false; d->insig = false; d->deleteme = false; d->autoDelete = false; d->done = false; d->timeout = DEFAULT_TIMEOUT; } Task *Task::parent() const { return (Task *)QObject::parent(); } Client *Task::client() const { return d->client; } QDomDocument *Task::doc() const { return client()->doc(); } QString Task::id() const { return d->id; } bool Task::success() const { return d->success; } int Task::statusCode() const { return d->statusCode; } const QString &Task::statusString() const { return d->statusString; } const Stanza::Error &Task::error() const { return d->error; } void Task::setTimeout(int seconds) const { d->timeout = seconds; } int Task::timeout() { return d->timeout; } void Task::go(bool autoDelete) { d->autoDelete = autoDelete; if (!client() || !client()->hasStream()) { qWarning("Task::go(): attempted to send a task over the broken connection."); if (autoDelete) { deleteLater(); } } else { onGo(); if (d->timeout) { QTimer::singleShot(d->timeout * 1000, this, SLOT(timeoutFinished())); } } } bool Task::take(const QDomElement &x) { const QObjectList p = children(); // pass along the xml Task *t; for (QObjectList::ConstIterator it = p.begin(); it != p.end(); ++it) { QObject *obj = *it; if (!obj->inherits("XMPP::Task")) continue; t = static_cast(obj); if (t->take(x)) // don't check for done here. it will hurt server tasks return true; } return false; } void Task::safeDelete() { if (d->deleteme) return; d->deleteme = true; if (!d->insig) deleteLater(); } void Task::onGo() { } void Task::onDisconnect() { if (!d->done) { d->success = false; d->statusCode = ErrDisc; d->statusString = tr("Disconnected"); // delay this so that tasks that react don't block the shutdown QTimer::singleShot(0, this, SLOT(done())); // Even server tasks will be marked as done, // but we don't check the attribute for them. } } void Task::onTimeout() { if (!d->done) { d->success = false; d->statusCode = ErrTimeout; d->statusString = tr("Request timed out"); done(); } } void Task::send(const QDomElement &x) { client()->send(x); } void Task::setSuccess(int code, const QString &str) { if (!d->done) { d->success = true; d->statusCode = code; d->statusString = str; done(); } } void Task::setError(const QDomElement &e) { if (!d->done) { d->success = false; QDomElement tag = e.firstChildElement("error"); if (tag.isNull()) return; XMPP::Stanza::Error err; err.fromXml(tag, d->client->streamBaseNS()); d->error = err; d->statusCode = err.code(); d->statusString = err.toString(); done(); } } void Task::setError(int code, const QString &str) { if (!d->done) { d->success = false; d->statusCode = code; d->statusString = str; done(); } } void Task::done() { if (d->done || d->insig) return; d->done = true; if (d->autoDelete) d->deleteme = true; d->insig = true; emit finished(); d->insig = false; if (d->deleteme) deleteLater(); } void Task::clientDisconnected() { onDisconnect(); } void Task::timeoutFinished() { if (!d->done) onTimeout(); } void Task::debug(const char *fmt, ...) { va_list ap; va_start(ap, fmt); QString str = QString::vasprintf(fmt, ap); va_end(ap); debug(str); } void Task::debug(const QString &str) { client()->debug(QString("%1: ").arg(metaObject()->className()) + str); } /** * \brief verifiys a stanza is a IQ reply for this task * * it checks that the stanze is form the jid the request was send to and the id and the namespace (if given) match. * * it further checks that the sender jid is not empty (except if \a to is our server), it's not from * our bare jid (except if send to one of our resources or our server) * \param x the stanza to test * \param to the Jid this task send a IQ to * \param id the id of the send IQ * \param xmlns the expected namespace if the reply (if non empty) * \return true if it's a valid reply */ bool Task::iqVerify(const QDomElement &x, const Jid &to, const QString &id, const QString &xmlns) { if (x.tagName() != QStringLiteral("iq")) return false; Jid from(x.attribute(QStringLiteral("from"))); Jid local = client()->jid(); Jid server = client()->host(); // empty 'from' ? if (from.isEmpty()) { // allowed if we are querying the server if (!to.isEmpty() && !to.compare(server)) return false; } // from ourself? else if (from.compare(local, false) || from.compare(local.domain(), false)) { // allowed if we are querying ourself or the server if (!to.isEmpty() && !to.compare(local, false) && !to.compare(server)) return false; } // from anywhere else? else { if (!from.compare(to)) return false; } if (!id.isEmpty()) { if (x.attribute(QStringLiteral("id")) != id) return false; } if (!xmlns.isEmpty()) { if (queryNS(x) != xmlns) return false; } return true; } QString Task::encryptionProtocol(const QDomElement &e) const { if (e.elementsByTagNameNS("urn:xmpp:eme:0", "encryption").isEmpty()) return QString(); QDomElement encryption = e.elementsByTagNameNS("urn:xmpp:eme:0", "encryption").at(0).toElement(); const QString &&ns = encryption.attribute("namespace"); // https://xmpp.org/extensions/xep-0380.html#protocols QString protocol; if (ns == "urn:xmpp:otr:0") { protocol = "OTR"; } else if (ns == "jabber:x:encrypted") { protocol = "Legacy OpenPGP"; } else if (ns == "urn:xmpp:openpgp:0") { protocol = "OpenPGP for XMPP"; } else if (ns == "eu.siacs.conversations.axolotl") { protocol = "OMEMO"; } else if (encryption.attribute("name").isEmpty()) { protocol = encryption.attribute("name"); } return protocol; } psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_task.h000066400000000000000000000045421370065651000240470ustar00rootroot00000000000000/* * xmpp_task.h * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_TASK_H #define XMPP_TASK_H #include "xmpp_stanza.h" #include #include class QDomDocument; class QDomElement; namespace XMPP { class Client; class Jid; class Task : public QObject { Q_OBJECT public: enum { ErrDisc, ErrTimeout }; Task(Task *parent); Task(Client *, bool isRoot); virtual ~Task(); Task * parent() const; Client * client() const; QDomDocument *doc() const; QString id() const; bool success() const; int statusCode() const; const QString & statusString() const; const Stanza::Error &error() const; void setTimeout(int seconds) const; int timeout(); void go(bool autoDelete = false); virtual bool take(const QDomElement &); void safeDelete(); signals: void finished(); protected: virtual void onGo(); virtual void onDisconnect(); virtual void onTimeout(); void send(const QDomElement &); void setSuccess(int code = 0, const QString &str = ""); void setError(const QDomElement &); void setError(int code = 0, const QString &str = ""); void debug(const char *, ...); void debug(const QString &); bool iqVerify(const QDomElement &x, const Jid &to, const QString &id, const QString &xmlns = ""); QString encryptionProtocol(const QDomElement &) const; private slots: void clientDisconnected(); void timeoutFinished(); void done(); private: void init(); class TaskPrivate; TaskPrivate *d; }; } // namespace XMPP #endif // XMPP_TASK_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_tasks.cpp000066400000000000000000001560411370065651000245670ustar00rootroot00000000000000/* * tasks.cpp - basic tasks * Copyright (C) 2001-2002 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp_tasks.h" #include "xmpp/base/timezone.h" #include "xmpp_bitsofbinary.h" #include "xmpp_caps.h" #include "xmpp_captcha.h" #include "xmpp_client.h" #include "xmpp_roster.h" #include "xmpp_vcard.h" #include "xmpp_xmlcommon.h" #include #include #include using namespace XMPP; static QString lineEncode(QString str) { str.replace(QRegExp("\\\\"), "\\\\"); // backslash to double-backslash str.replace(QRegExp("\\|"), "\\p"); // pipe to \p str.replace(QRegExp("\n"), "\\n"); // newline to \n return str; } static QString lineDecode(const QString &str) { QString ret; for (int n = 0; n < str.length(); ++n) { if (str.at(n) == '\\') { ++n; if (n >= str.length()) break; if (str.at(n) == 'n') ret.append('\n'); if (str.at(n) == 'p') ret.append('|'); if (str.at(n) == '\\') ret.append('\\'); } else { ret.append(str.at(n)); } } return ret; } static Roster xmlReadRoster(const QDomElement &q, bool push) { Roster r; for (QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement i = n.toElement(); if (i.isNull()) continue; if (i.tagName() == "item") { RosterItem item; item.fromXml(i); if (push) item.setIsPush(true); r += item; } } return r; } //---------------------------------------------------------------------------- // JT_Session //---------------------------------------------------------------------------- // #include "protocol.h" JT_Session::JT_Session(Task *parent) : Task(parent) { } void JT_Session::onGo() { QDomElement iq = createIQ(doc(), "set", "", id()); QDomElement session = doc()->createElementNS(NS_SESSION, "session"); iq.appendChild(session); send(iq); } bool JT_Session::take(const QDomElement &x) { QString from = x.attribute("from"); if (!from.endsWith("chat.facebook.com")) { // remove this code when chat.facebook.com is disabled completely from.clear(); } if (!iqVerify(x, from, id())) return false; if (x.attribute("type") == "result") { setSuccess(); } else { setError(x); } return true; } //---------------------------------------------------------------------------- // JT_Register //---------------------------------------------------------------------------- class JT_Register::Private { public: Private() = default; Form form; XData xdata; bool hasXData; bool registered; Jid jid; int type; }; JT_Register::JT_Register(Task *parent) : Task(parent) { d = new Private; d->type = -1; d->hasXData = false; d->registered = false; } JT_Register::~JT_Register() { delete d; } void JT_Register::reg(const QString &user, const QString &pass) { d->type = 0; to = client()->host(); iq = createIQ(doc(), "set", to.full(), id()); QDomElement query = doc()->createElementNS("jabber:iq:register", "query"); iq.appendChild(query); query.appendChild(textTag(doc(), "username", user)); query.appendChild(textTag(doc(), "password", pass)); } void JT_Register::changepw(const QString &pass) { d->type = 1; to = client()->host(); iq = createIQ(doc(), "set", to.full(), id()); QDomElement query = doc()->createElementNS("jabber:iq:register", "query"); iq.appendChild(query); query.appendChild(textTag(doc(), "username", client()->user())); query.appendChild(textTag(doc(), "password", pass)); } void JT_Register::unreg(const Jid &j) { d->type = 2; to = j.isEmpty() ? client()->host() : j.full(); iq = createIQ(doc(), "set", to.full(), id()); QDomElement query = doc()->createElementNS("jabber:iq:register", "query"); iq.appendChild(query); // this may be useful if (!d->form.key().isEmpty()) query.appendChild(textTag(doc(), "key", d->form.key())); query.appendChild(doc()->createElement("remove")); } void JT_Register::getForm(const Jid &j) { d->type = 3; to = j; iq = createIQ(doc(), "get", to.full(), id()); QDomElement query = doc()->createElementNS("jabber:iq:register", "query"); iq.appendChild(query); } void JT_Register::setForm(const Form &form) { d->type = 4; to = form.jid(); iq = createIQ(doc(), "set", to.full(), id()); QDomElement query = doc()->createElementNS("jabber:iq:register", "query"); iq.appendChild(query); // key? if (!form.key().isEmpty()) query.appendChild(textTag(doc(), "key", form.key())); // fields for (Form::ConstIterator it = form.begin(); it != form.end(); ++it) { const FormField &f = *it; query.appendChild(textTag(doc(), f.realName(), f.value())); } } void JT_Register::setForm(const Jid &to, const XData &xdata) { d->type = 4; iq = createIQ(doc(), "set", to.full(), id()); QDomElement query = doc()->createElementNS("jabber:iq:register", "query"); iq.appendChild(query); query.appendChild(xdata.toXml(doc(), true)); } const Form &JT_Register::form() const { return d->form; } bool JT_Register::hasXData() const { return d->hasXData; } const XData &JT_Register::xdata() const { return d->xdata; } bool JT_Register::isRegistered() const { return d->registered; } void JT_Register::onGo() { send(iq); } bool JT_Register::take(const QDomElement &x) { if (!iqVerify(x, to, id())) return false; Jid from(x.attribute("from")); if (x.attribute("type") == "result") { if (d->type == 3) { d->form.clear(); d->form.setJid(from); QDomElement q = queryTag(x); for (QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement i = n.toElement(); if (i.isNull()) continue; if (i.tagName() == "instructions") d->form.setInstructions(tagContent(i)); else if (i.tagName() == "key") d->form.setKey(tagContent(i)); else if (i.tagName() == QLatin1String("registered")) d->registered = true; else if (i.tagName() == "x" && i.namespaceURI() == "jabber:x:data") { d->xdata.fromXml(i); d->hasXData = true; } else if (i.tagName() == "data" && i.namespaceURI() == "urn:xmpp:bob") { client()->bobManager()->append(BoBData(i)); // xep-0231 } else { FormField f; if (f.setType(i.tagName())) { f.setValue(tagContent(i)); d->form += f; } } } } setSuccess(); } else setError(x); return true; } //---------------------------------------------------------------------------- // JT_UnRegister //---------------------------------------------------------------------------- class JT_UnRegister::Private { public: Private() = default; Jid j; JT_Register *jt_reg = nullptr; }; JT_UnRegister::JT_UnRegister(Task *parent) : Task(parent) { d = new Private; d->jt_reg = nullptr; } JT_UnRegister::~JT_UnRegister() { delete d->jt_reg; delete d; } void JT_UnRegister::unreg(const Jid &j) { d->j = j; } void JT_UnRegister::onGo() { delete d->jt_reg; d->jt_reg = new JT_Register(this); d->jt_reg->getForm(d->j); connect(d->jt_reg, SIGNAL(finished()), SLOT(getFormFinished())); d->jt_reg->go(false); } void JT_UnRegister::getFormFinished() { disconnect(d->jt_reg, nullptr, this, nullptr); if (d->jt_reg->isRegistered()) { d->jt_reg->unreg(d->j); connect(d->jt_reg, SIGNAL(finished()), SLOT(unregFinished())); d->jt_reg->go(false); } else { setSuccess(); // no need to unregister } } void JT_UnRegister::unregFinished() { if (d->jt_reg->success()) setSuccess(); else setError(d->jt_reg->statusCode(), d->jt_reg->statusString()); delete d->jt_reg; d->jt_reg = nullptr; } //---------------------------------------------------------------------------- // JT_Roster //---------------------------------------------------------------------------- class JT_Roster::Private { public: Private() = default; Roster roster; QString groupsDelimiter; QList itemList; }; JT_Roster::JT_Roster(Task *parent) : Task(parent) { type = Unknown; d = new Private; } JT_Roster::~JT_Roster() { delete d; } void JT_Roster::get() { type = Get; // to = client()->host(); iq = createIQ(doc(), "get", to.full(), id()); QDomElement query = doc()->createElementNS("jabber:iq:roster", "query"); iq.appendChild(query); } void JT_Roster::set(const Jid &jid, const QString &name, const QStringList &groups) { type = Set; // to = client()->host(); QDomElement item = doc()->createElement("item"); item.setAttribute("jid", jid.full()); if (!name.isEmpty()) item.setAttribute("name", name); for (QStringList::ConstIterator it = groups.begin(); it != groups.end(); ++it) item.appendChild(textTag(doc(), "group", *it)); d->itemList += item; } void JT_Roster::remove(const Jid &jid) { type = Remove; // to = client()->host(); QDomElement item = doc()->createElement("item"); item.setAttribute("jid", jid.full()); item.setAttribute("subscription", "remove"); d->itemList += item; } void JT_Roster::getGroupsDelimiter() { type = GetDelimiter; // to = client()->host(); iq = createIQ(doc(), "get", to.full(), id()); QDomElement roster = doc()->createElement("roster"); roster.setAttribute("xmlns", "roster:delimiter"); QDomElement query = doc()->createElement("query"); query.setAttribute("xmlns", "jabber:iq:private"); query.appendChild(roster); iq.appendChild(query); } void JT_Roster::setGroupsDelimiter(const QString &groupsDelimiter) { type = SetDelimiter; // to = client()->host(); iq = createIQ(doc(), "set", to.full(), id()); QDomText text = doc()->createTextNode(groupsDelimiter); QDomElement roster = doc()->createElement("roster"); roster.setAttribute("xmlns", "roster:delimiter"); roster.appendChild(text); QDomElement query = doc()->createElement("query"); query.setAttribute("xmlns", "jabber:iq:private"); query.appendChild(roster); iq.appendChild(query); } void JT_Roster::onGo() { if (type == Get) send(iq); else if (type == Set || type == Remove) { // to = client()->host(); iq = createIQ(doc(), "set", to.full(), id()); QDomElement query = doc()->createElementNS("jabber:iq:roster", "query"); iq.appendChild(query); for (const QDomElement &it : d->itemList) query.appendChild(it); send(iq); } else if (type == GetDelimiter) { send(iq); } else if (type == SetDelimiter) { send(iq); } } const Roster &JT_Roster::roster() const { return d->roster; } QString JT_Roster::groupsDelimiter() const { return d->groupsDelimiter; } QString JT_Roster::toString() const { if (type != Set) return ""; QDomElement i = doc()->createElement("request"); i.setAttribute("type", "JT_Roster"); for (const QDomElement &it : d->itemList) i.appendChild(it); return lineEncode(Stream::xmlToString(i)); } bool JT_Roster::fromString(const QString &str) { QDomDocument *dd = new QDomDocument; if (!dd->setContent(lineDecode(str).toUtf8())) return false; QDomElement e = doc()->importNode(dd->documentElement(), true).toElement(); delete dd; if (e.tagName() != "request" || e.attribute("type") != "JT_Roster") return false; type = Set; d->itemList.clear(); for (QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement i = n.toElement(); if (i.isNull()) continue; d->itemList += i; } return true; } bool JT_Roster::take(const QDomElement &x) { if (!iqVerify(x, client()->host(), id())) return false; if (type == Get) { if (x.attribute("type") == "result") { QDomElement q = queryTag(x); d->roster = xmlReadRoster(q, false); setSuccess(); } else { setError(x); } return true; } else if (type == Set) { if (x.attribute("type") == "result") setSuccess(); else setError(x); return true; } else if (type == Remove) { setSuccess(); return true; } else if (type == GetDelimiter) { if (x.attribute("type") == "result") { QDomElement q = queryTag(x); QDomElement delimiter = q.firstChild().toElement(); d->groupsDelimiter = delimiter.firstChild().toText().data(); setSuccess(); } else { setError(x); } return true; } else if (type == SetDelimiter) { setSuccess(); return true; } return false; } //---------------------------------------------------------------------------- // JT_PushRoster //---------------------------------------------------------------------------- JT_PushRoster::JT_PushRoster(Task *parent) : Task(parent) { } JT_PushRoster::~JT_PushRoster() { } bool JT_PushRoster::take(const QDomElement &e) { // must be an iq-set tag if (e.tagName() != "iq" || e.attribute("type") != "set") return false; if (!iqVerify(e, client()->host(), "", "jabber:iq:roster")) return false; roster(xmlReadRoster(queryTag(e), true)); send(createIQ(doc(), "result", e.attribute("from"), e.attribute("id"))); return true; } //---------------------------------------------------------------------------- // JT_Presence //---------------------------------------------------------------------------- JT_Presence::JT_Presence(Task *parent) : Task(parent) { } JT_Presence::~JT_Presence() { } void JT_Presence::pres(const Status &s) { type = 0; tag = doc()->createElement("presence"); if (!s.isAvailable()) { tag.setAttribute("type", "unavailable"); if (!s.status().isEmpty()) tag.appendChild(textTag(doc(), "status", s.status())); } else { if (s.isInvisible()) tag.setAttribute("type", "invisible"); if (!s.show().isEmpty()) tag.appendChild(textTag(doc(), "show", s.show())); if (!s.status().isEmpty()) tag.appendChild(textTag(doc(), "status", s.status())); tag.appendChild(textTag(doc(), "priority", QString("%1").arg(s.priority()))); if (!s.keyID().isEmpty()) { QDomElement x = textTagNS(doc(), "http://jabber.org/protocol/e2e", "x", s.keyID()); tag.appendChild(x); } if (!s.xsigned().isEmpty()) { QDomElement x = textTagNS(doc(), "jabber:x:signed", "x", s.xsigned()); tag.appendChild(x); } if (client()->capsManager()->isEnabled() && !client()->capsOptimizationAllowed()) { CapsSpec cs = client()->caps(); if (cs.isValid()) { tag.appendChild(cs.toXml(doc())); } } if (s.isMUC()) { QDomElement m = doc()->createElementNS("http://jabber.org/protocol/muc", "x"); if (!s.mucPassword().isEmpty()) { m.appendChild(textTag(doc(), "password", s.mucPassword())); } if (s.hasMUCHistory()) { QDomElement h = doc()->createElement("history"); if (s.mucHistoryMaxChars() >= 0) h.setAttribute("maxchars", s.mucHistoryMaxChars()); if (s.mucHistoryMaxStanzas() >= 0) h.setAttribute("maxstanzas", s.mucHistoryMaxStanzas()); if (s.mucHistorySeconds() >= 0) h.setAttribute("seconds", s.mucHistorySeconds()); if (!s.mucHistorySince().isNull()) h.setAttribute("since", s.mucHistorySince().toUTC().addSecs(1).toString(Qt::ISODate)); m.appendChild(h); } tag.appendChild(m); } if (s.hasPhotoHash()) { QDomElement m = doc()->createElementNS("vcard-temp:x:update", "x"); m.appendChild(textTag(doc(), "photo", QString::fromLatin1(s.photoHash().toHex()))); tag.appendChild(m); } // bits of binary for (const BoBData &bd : s.bobDataList()) { tag.appendChild(bd.toXml(doc())); } } } void JT_Presence::pres(const Jid &to, const Status &s) { pres(s); tag.setAttribute("to", to.full()); } void JT_Presence::sub(const Jid &to, const QString &subType, const QString &nick) { type = 1; tag = doc()->createElement("presence"); tag.setAttribute("to", to.full()); tag.setAttribute("type", subType); if (!nick.isEmpty() && (subType == QLatin1String("subscribe") || subType == QLatin1String("subscribed") || subType == QLatin1String("unsubscribe") || subType == QLatin1String("unsubscribed"))) { QDomElement nick_tag = textTagNS(doc(), "http://jabber.org/protocol/nick", "nick", nick); tag.appendChild(nick_tag); } } void JT_Presence::probe(const Jid &to) { type = 2; tag = doc()->createElement("presence"); tag.setAttribute("to", to.full()); tag.setAttribute("type", "probe"); } void JT_Presence::onGo() { send(tag); setSuccess(); } //---------------------------------------------------------------------------- // JT_PushPresence //---------------------------------------------------------------------------- JT_PushPresence::JT_PushPresence(Task *parent) : Task(parent) { } JT_PushPresence::~JT_PushPresence() { } bool JT_PushPresence::take(const QDomElement &e) { if (e.tagName() != "presence") return false; Jid j(e.attribute("from")); Status p; if (e.hasAttribute("type")) { QString type = e.attribute("type"); if (type == "unavailable") { p.setIsAvailable(false); } else if (type == "error") { QString str = ""; int code = 0; getErrorFromElement(e, client()->stream().baseNS(), &code, &str); p.setError(code, str); } else if (type == QLatin1String("subscribe") || type == QLatin1String("subscribed") || type == QLatin1String("unsubscribe") || type == QLatin1String("unsubscribed")) { QString nick; QDomElement tag = e.firstChildElement("nick"); if (!tag.isNull() && tag.namespaceURI() == "http://jabber.org/protocol/nick") { nick = tagContent(tag); } subscription(j, type, nick); return true; } } QDomElement tag; tag = e.firstChildElement("status"); if (!tag.isNull()) p.setStatus(tagContent(tag)); tag = e.firstChildElement("show"); if (!tag.isNull()) p.setShow(tagContent(tag)); tag = e.firstChildElement("priority"); if (!tag.isNull()) p.setPriority(tagContent(tag).toInt()); QDateTime stamp; for (QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement i = n.toElement(); if (i.isNull()) continue; if (i.tagName() == "x" && i.namespaceURI() == "jabber:x:delay") { if (i.hasAttribute("stamp") && !stamp.isValid()) { stamp = stamp2TS(i.attribute("stamp")); } } else if (i.tagName() == "delay" && i.namespaceURI() == "urn:xmpp:delay") { if (i.hasAttribute("stamp") && !stamp.isValid()) { stamp = QDateTime::fromString(i.attribute("stamp").left(19), Qt::ISODate); } } else if (i.tagName() == "x" && i.namespaceURI() == "gabber:x:music:info") { QDomElement t; QString title, state; t = i.firstChildElement("title"); if (!t.isNull()) title = tagContent(t); t = i.firstChildElement("state"); if (!t.isNull()) state = tagContent(t); if (!title.isEmpty() && state == "playing") p.setSongTitle(title); } else if (i.tagName() == "x" && i.namespaceURI() == "jabber:x:signed") { p.setXSigned(tagContent(i)); } else if (i.tagName() == "x" && i.namespaceURI() == "http://jabber.org/protocol/e2e") { p.setKeyID(tagContent(i)); } else if (i.tagName() == "c" && i.namespaceURI() == NS_CAPS) { p.setCaps(CapsSpec::fromXml(i)); if (!e.hasAttribute("type") && p.caps().isValid()) { client()->capsManager()->updateCaps(j, p.caps()); } } else if (i.tagName() == "x" && i.namespaceURI() == "vcard-temp:x:update") { QDomElement t; t = i.firstChildElement("photo"); if (!t.isNull()) p.setPhotoHash( QByteArray::fromHex(tagContent(t).toLatin1())); // if hash is empty this may mean photo removal // else vcard.hasPhotoHash() returns false and that's mean user is not yet ready to advertise his image } else if (i.tagName() == "x" && i.namespaceURI() == "http://jabber.org/protocol/muc#user") { for (QDomNode muc_n = i.firstChild(); !muc_n.isNull(); muc_n = muc_n.nextSibling()) { QDomElement muc_e = muc_n.toElement(); if (muc_e.isNull()) continue; if (muc_e.tagName() == "item") p.setMUCItem(MUCItem(muc_e)); else if (muc_e.tagName() == "status") p.addMUCStatus(muc_e.attribute("code").toInt()); else if (muc_e.tagName() == "destroy") p.setMUCDestroy(MUCDestroy(muc_e)); } } else if (i.tagName() == "data" && i.namespaceURI() == "urn:xmpp:bob") { BoBData bd(i); client()->bobManager()->append(bd); p.addBoBData(bd); } } if (stamp.isValid()) { if (client()->manualTimeZoneOffset()) { stamp = stamp.addSecs(client()->timeZoneOffset() * 3600); } else { stamp.setTimeSpec(Qt::UTC); stamp = stamp.toLocalTime(); } p.setTimeStamp(stamp); } presence(j, p); return true; } //---------------------------------------------------------------------------- // JT_Message //---------------------------------------------------------------------------- static QDomElement oldStyleNS(const QDomElement &e) { // find closest parent with a namespace QDomNode par = e.parentNode(); while (!par.isNull() && par.namespaceURI().isNull()) par = par.parentNode(); bool noShowNS = false; if (!par.isNull() && par.namespaceURI() == e.namespaceURI()) noShowNS = true; QDomElement i; int x; // if(noShowNS) i = e.ownerDocument().createElement(e.tagName()); // else // i = e.ownerDocument().createElementNS(e.namespaceURI(), e.tagName()); // copy attributes QDomNamedNodeMap al = e.attributes(); for (x = 0; x < al.count(); ++x) i.setAttributeNode(al.item(x).cloneNode().toAttr()); if (!noShowNS) i.setAttribute("xmlns", e.namespaceURI()); // copy children QDomNodeList nl = e.childNodes(); for (x = 0; x < nl.count(); ++x) { QDomNode n = nl.item(x); if (n.isElement()) i.appendChild(oldStyleNS(n.toElement())); else i.appendChild(n.cloneNode()); } return i; } JT_Message::JT_Message(Task *parent, Message &msg) : Task(parent), m(msg) { if (msg.id().isEmpty()) msg.setId(id()); } JT_Message::~JT_Message() { } void JT_Message::onGo() { Stanza s = m.toStanza(&(client()->stream())); QDomElement e = s.element(); // oldStyleNS(s.element()); if (auto encryptionHandler = client()->encryptionHandler()) { Q_UNUSED(encryptionHandler->encryptMessageElement(e)); } // See: XEP-0380: Explicit Message Encryption const bool wasEncrypted = !e.firstChildElement("encryption").isNull(); m.setWasEncrypted(wasEncrypted); m.setEncryptionProtocol(encryptionProtocol(e)); // if the element is null, then the encryption is happening asynchronously if (!e.isNull()) { send(e); } setSuccess(); } //---------------------------------------------------------------------------- // JT_PushMessage //---------------------------------------------------------------------------- class JT_PushMessage::Private { public: EncryptionHandler *m_encryptionHandler; }; JT_PushMessage::JT_PushMessage(Task *parent, EncryptionHandler *encryptionHandler) : Task(parent) { d = new Private; d->m_encryptionHandler = encryptionHandler; } JT_PushMessage::~JT_PushMessage() { delete d; } bool JT_PushMessage::take(const QDomElement &e) { if (e.tagName() != "message") return false; QDomElement e1 = e; if (d->m_encryptionHandler) { if (d->m_encryptionHandler->decryptMessageElement(e1)) { if (e1.isNull()) { // The message was processed, but has to be discarded for some reason return true; } } } QDomElement forward; Message::CarbonDir cd = Message::NoCarbon; Jid fromJid = Jid(e1.attribute(QLatin1String("from"))); // Check for Carbon QDomNodeList list = e1.childNodes(); for (int i = 0; i < list.size(); ++i) { QDomElement el = list.at(i).toElement(); if (el.namespaceURI() == QLatin1String("urn:xmpp:carbons:2") && (el.tagName() == QLatin1String("received") || el.tagName() == QLatin1String("sent")) && fromJid.compare(Jid(e1.attribute(QLatin1String("to"))), false)) { QDomElement el1 = el.firstChildElement(); if (el1.tagName() == QLatin1String("forwarded") && el1.namespaceURI() == QLatin1String("urn:xmpp:forward:0")) { QDomElement el2 = el1.firstChildElement(QLatin1String("message")); if (!el2.isNull()) { forward = el2; cd = el.tagName() == QLatin1String("received") ? Message::Received : Message::Sent; break; } } } else if (el.tagName() == QLatin1String("forwarded") && el.namespaceURI() == QLatin1String("urn:xmpp:forward:0")) { forward = el.firstChildElement(QLatin1String("message")); // currently only messages are supportted // TODO element support if (!forward.isNull()) { break; } } } Stanza s = client()->stream().createStanza(addCorrectNS(forward.isNull() ? e1 : forward)); if (s.isNull()) { // printf("take: bad stanza??\n"); return false; } Message m; if (!m.fromStanza(s, client()->manualTimeZoneOffset(), client()->timeZoneOffset())) { // printf("bad message\n"); return false; } if (!forward.isNull()) { m.setForwardedFrom(fromJid); m.setCarbonDirection(cd); } // See: XEP-0380: Explicit Message Encryption const bool wasEncrypted = !e1.firstChildElement("encryption").isNull(); m.setWasEncrypted(wasEncrypted); m.setEncryptionProtocol(encryptionProtocol(e)); emit message(m); return true; } //---------------------------------------------------------------------------- // JT_VCard //---------------------------------------------------------------------------- class JT_VCard::Private { public: Private() = default; QDomElement iq; Jid jid; VCard vcard; }; JT_VCard::JT_VCard(Task *parent) : Task(parent) { type = -1; d = new Private; } JT_VCard::~JT_VCard() { delete d; } void JT_VCard::get(const Jid &_jid) { type = 0; d->jid = _jid; d->iq = createIQ(doc(), "get", type == 1 ? Jid().full() : d->jid.full(), id()); QDomElement v = doc()->createElementNS("vcard-temp", "vCard"); d->iq.appendChild(v); } const Jid &JT_VCard::jid() const { return d->jid; } const VCard &JT_VCard::vcard() const { return d->vcard; } void JT_VCard::set(const VCard &card) { type = 1; d->vcard = card; d->jid = ""; d->iq = createIQ(doc(), "set", d->jid.full(), id()); d->iq.appendChild(card.toXml(doc())); } // isTarget is when we setting target's vcard. for example in case of muc own vcard void JT_VCard::set(const Jid &j, const VCard &card, bool isTarget) { type = 1; d->vcard = card; d->jid = j; d->iq = createIQ(doc(), "set", isTarget ? j.full() : "", id()); d->iq.appendChild(card.toXml(doc())); } void JT_VCard::onGo() { send(d->iq); } bool JT_VCard::take(const QDomElement &x) { Jid to = d->jid; if (to.bare() == client()->jid().bare()) to = client()->host(); if (!iqVerify(x, to, id())) return false; if (x.attribute("type") == "result") { if (type == 0) { for (QDomNode n = x.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement q = n.toElement(); if (q.isNull()) continue; if (q.tagName().toUpper() == "VCARD") { d->vcard = VCard::fromXml(q); if (d->vcard) { setSuccess(); return true; } } } setError(ErrDisc + 1, tr("No VCard available")); return true; } else { setSuccess(); return true; } } else { setError(x); } return true; } //---------------------------------------------------------------------------- // JT_Search //---------------------------------------------------------------------------- class JT_Search::Private { public: Private() = default; Jid jid; Form form; bool hasXData = false; XData xdata; QList resultList; }; JT_Search::JT_Search(Task *parent) : Task(parent) { d = new Private; type = -1; } JT_Search::~JT_Search() { delete d; } void JT_Search::get(const Jid &jid) { type = 0; d->jid = jid; d->hasXData = false; d->xdata = XData(); iq = createIQ(doc(), "get", d->jid.full(), id()); QDomElement query = doc()->createElementNS("jabber:iq:search", "query"); iq.appendChild(query); } void JT_Search::set(const Form &form) { type = 1; d->jid = form.jid(); d->hasXData = false; d->xdata = XData(); iq = createIQ(doc(), "set", d->jid.full(), id()); QDomElement query = doc()->createElementNS("jabber:iq:search", "query"); iq.appendChild(query); // key? if (!form.key().isEmpty()) query.appendChild(textTag(doc(), "key", form.key())); // fields for (Form::ConstIterator it = form.begin(); it != form.end(); ++it) { const FormField &f = *it; query.appendChild(textTag(doc(), f.realName(), f.value())); } } void JT_Search::set(const Jid &jid, const XData &form) { type = 1; d->jid = jid; d->hasXData = false; d->xdata = XData(); iq = createIQ(doc(), "set", d->jid.full(), id()); QDomElement query = doc()->createElementNS("jabber:iq:search", "query"); iq.appendChild(query); query.appendChild(form.toXml(doc(), true)); } const Form &JT_Search::form() const { return d->form; } const QList &JT_Search::results() const { return d->resultList; } bool JT_Search::hasXData() const { return d->hasXData; } const XData &JT_Search::xdata() const { return d->xdata; } void JT_Search::onGo() { send(iq); } bool JT_Search::take(const QDomElement &x) { if (!iqVerify(x, d->jid, id())) return false; Jid from(x.attribute("from")); if (x.attribute("type") == "result") { if (type == 0) { d->form.clear(); d->form.setJid(from); QDomElement q = queryTag(x); for (QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement i = n.toElement(); if (i.isNull()) continue; if (i.tagName() == "instructions") d->form.setInstructions(tagContent(i)); else if (i.tagName() == "key") d->form.setKey(tagContent(i)); else if (i.tagName() == "x" && i.namespaceURI() == "jabber:x:data") { d->xdata.fromXml(i); d->hasXData = true; } else { FormField f; if (f.setType(i.tagName())) { f.setValue(tagContent(i)); d->form += f; } } } } else { d->resultList.clear(); QDomElement q = queryTag(x); for (QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement i = n.toElement(); if (i.isNull()) continue; if (i.tagName() == "item") { SearchResult r(Jid(i.attribute("jid"))); QDomElement tag; tag = i.firstChildElement("nick"); if (!tag.isNull()) r.setNick(tagContent(tag)); tag = i.firstChildElement("first"); if (!tag.isNull()) r.setFirst(tagContent(tag)); tag = i.firstChildElement("last"); if (!tag.isNull()) r.setLast(tagContent(tag)); tag = i.firstChildElement("email"); if (!tag.isNull()) r.setEmail(tagContent(tag)); d->resultList += r; } else if (i.tagName() == "x" && i.namespaceURI() == "jabber:x:data") { d->xdata.fromXml(i); d->hasXData = true; } } } setSuccess(); } else { setError(x); } return true; } //---------------------------------------------------------------------------- // JT_ClientVersion //---------------------------------------------------------------------------- JT_ClientVersion::JT_ClientVersion(Task *parent) : Task(parent) { } void JT_ClientVersion::get(const Jid &jid) { j = jid; iq = createIQ(doc(), "get", j.full(), id()); QDomElement query = doc()->createElementNS("jabber:iq:version", "query"); iq.appendChild(query); } void JT_ClientVersion::onGo() { send(iq); } bool JT_ClientVersion::take(const QDomElement &x) { if (!iqVerify(x, j, id())) return false; if (x.attribute("type") == "result") { QDomElement q = queryTag(x); QDomElement tag; tag = q.firstChildElement("name"); if (!tag.isNull()) v_name = tagContent(tag); tag = q.firstChildElement("version"); if (!tag.isNull()) v_ver = tagContent(tag); tag = q.firstChildElement("os"); if (!tag.isNull()) v_os = tagContent(tag); setSuccess(); } else { setError(x); } return true; } const Jid &JT_ClientVersion::jid() const { return j; } const QString &JT_ClientVersion::name() const { return v_name; } const QString &JT_ClientVersion::version() const { return v_ver; } const QString &JT_ClientVersion::os() const { return v_os; } //---------------------------------------------------------------------------- // JT_EntityTime //---------------------------------------------------------------------------- JT_EntityTime::JT_EntityTime(Task *parent) : Task(parent) { } /** * \brief Queried entity's JID. */ const Jid &JT_EntityTime::jid() const { return j; } /** * \brief Prepares the task to get information from JID. */ void JT_EntityTime::get(const Jid &jid) { j = jid; iq = createIQ(doc(), "get", jid.full(), id()); QDomElement time = doc()->createElementNS("urn:xmpp:time", "time"); iq.appendChild(time); } void JT_EntityTime::onGo() { send(iq); } bool JT_EntityTime::take(const QDomElement &x) { if (!iqVerify(x, j, id())) return false; if (x.attribute("type") == "result") { QDomElement q = x.firstChildElement("time"); QDomElement tag; tag = q.firstChildElement("utc"); do { if (tag.isNull()) { break; } utc = QDateTime::fromString(tagContent(tag), Qt::ISODate); tag = q.firstChildElement("tzo"); if (!utc.isValid() || tag.isNull()) { break; } tzo = TimeZone::tzdToInt(tagContent(tag)); if (tzo == -1) { break; } setSuccess(); return true; } while (false); setError(406); } else { setError(x); } return true; } const QDateTime &JT_EntityTime::dateTime() const { return utc; } int JT_EntityTime::timezoneOffset() const { return tzo; } //---------------------------------------------------------------------------- // JT_ServInfo //---------------------------------------------------------------------------- JT_ServInfo::JT_ServInfo(Task *parent) : Task(parent) { } JT_ServInfo::~JT_ServInfo() { } bool JT_ServInfo::take(const QDomElement &e) { if (e.tagName() != "iq" || e.attribute("type") != "get") return false; QString ns = queryNS(e); if (ns == "jabber:iq:version") { QDomElement iq = createIQ(doc(), "result", e.attribute("from"), e.attribute("id")); QDomElement query = doc()->createElementNS("jabber:iq:version", "query"); iq.appendChild(query); query.appendChild(textTag(doc(), "name", client()->clientName())); query.appendChild(textTag(doc(), "version", client()->clientVersion())); query.appendChild(textTag(doc(), "os", client()->OSName() + ' ' + client()->OSVersion())); send(iq); return true; } else if (ns == "http://jabber.org/protocol/disco#info") { // Find out the node QString node; QDomElement q = e.firstChildElement("query"); if (!q.isNull()) // NOTE: Should always be true, since a NS was found above node = q.attribute("node"); if (node.isEmpty() || node == client()->caps().flatten()) { QDomElement iq = createIQ(doc(), "result", e.attribute("from"), e.attribute("id")); DiscoItem item = client()->makeDiscoResult(node); iq.appendChild(item.toDiscoInfoResult(doc())); send(iq); } else { // Create error reply QDomElement error_reply = createIQ(doc(), "result", e.attribute("from"), e.attribute("id")); // Copy children for (QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { error_reply.appendChild(n.cloneNode()); } // Add error QDomElement error = doc()->createElement("error"); error.setAttribute("type", "cancel"); error_reply.appendChild(error); QDomElement error_type = doc()->createElementNS("urn:ietf:params:xml:ns:xmpp-stanzas", "item-not-found"); error.appendChild(error_type); send(error_reply); } return true; } if (!ns.isEmpty()) { return false; } ns = e.firstChildElement("time").namespaceURI(); if (ns == "urn:xmpp:time") { QDomElement iq = createIQ(doc(), "result", e.attribute("from"), e.attribute("id")); QDomElement time = doc()->createElementNS(ns, "time"); iq.appendChild(time); QDateTime local = QDateTime::currentDateTime(); int off = TimeZone::offsetFromUtc(); QTime t = QTime(0, 0).addSecs(qAbs(off) * 60); QString tzo = (off < 0 ? "-" : "+") + t.toString("HH:mm"); time.appendChild(textTag(doc(), "tzo", tzo)); QString localTimeStr = local.toUTC().toString(Qt::ISODate); if (!localTimeStr.endsWith("Z")) localTimeStr.append("Z"); time.appendChild(textTag(doc(), "utc", localTimeStr)); send(iq); return true; } return false; } //---------------------------------------------------------------------------- // JT_Gateway //---------------------------------------------------------------------------- JT_Gateway::JT_Gateway(Task *parent) : Task(parent) { type = -1; } void JT_Gateway::get(const Jid &jid) { type = 0; v_jid = jid; iq = createIQ(doc(), "get", v_jid.full(), id()); QDomElement query = doc()->createElementNS("jabber:iq:gateway", "query"); iq.appendChild(query); } void JT_Gateway::set(const Jid &jid, const QString &prompt) { type = 1; v_jid = jid; v_prompt = prompt; iq = createIQ(doc(), "set", v_jid.full(), id()); QDomElement query = doc()->createElementNS("jabber:iq:gateway", "query"); iq.appendChild(query); query.appendChild(textTag(doc(), "prompt", v_prompt)); } void JT_Gateway::onGo() { send(iq); } Jid JT_Gateway::jid() const { return v_jid; } QString JT_Gateway::desc() const { return v_desc; } QString JT_Gateway::prompt() const { return v_prompt; } Jid JT_Gateway::translatedJid() const { return v_translatedJid; } bool JT_Gateway::take(const QDomElement &x) { if (!iqVerify(x, v_jid, id())) return false; if (x.attribute("type") == "result") { if (type == 0) { QDomElement query = queryTag(x); QDomElement tag; tag = query.firstChildElement("desc"); if (!tag.isNull()) { v_desc = tagContent(tag); } tag = query.firstChildElement("prompt"); if (!tag.isNull()) { v_prompt = tagContent(tag); } } else { QDomElement query = queryTag(x); QDomElement tag; tag = query.firstChildElement("jid"); if (!tag.isNull()) { v_translatedJid = tagContent(tag); } // we used to read 'prompt' in the past // and some gateways still send it tag = query.firstChildElement("prompt"); if (!tag.isNull()) { v_prompt = tagContent(tag); } } setSuccess(); } else { setError(x); } return true; } //---------------------------------------------------------------------------- // JT_DiscoItems //---------------------------------------------------------------------------- class JT_DiscoItems::Private { public: Private() { } QDomElement iq; Jid jid; DiscoList items; QDomElement subsetsEl; }; JT_DiscoItems::JT_DiscoItems(Task *parent) : Task(parent) { d = new Private; } JT_DiscoItems::~JT_DiscoItems() { delete d; } void JT_DiscoItems::get(const DiscoItem &item) { get(item.jid(), item.node()); } void JT_DiscoItems::get(const Jid &j, const QString &node) { d->items.clear(); d->jid = j; d->iq = createIQ(doc(), "get", d->jid.full(), id()); QDomElement query = doc()->createElementNS("http://jabber.org/protocol/disco#items", "query"); if (!node.isEmpty()) query.setAttribute("node", node); if (!d->subsetsEl.isNull()) { query.appendChild(d->subsetsEl); d->subsetsEl = QDomElement(); } d->iq.appendChild(query); } const DiscoList &JT_DiscoItems::items() const { return d->items; } void JT_DiscoItems::includeSubsetQuery(const SubsetsClientManager &subsets) { d->subsetsEl = subsets.makeQueryElement(doc()); } bool JT_DiscoItems::extractSubsetInfo(SubsetsClientManager &subsets) { return d->subsetsEl.isNull() ? false : subsets.updateFromElement(d->subsetsEl, d->items.count()); } void JT_DiscoItems::onGo() { send(d->iq); } bool JT_DiscoItems::take(const QDomElement &x) { if (!iqVerify(x, d->jid, id())) return false; if (x.attribute("type") == "result") { QDomElement q = queryTag(x); for (QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement e = n.toElement(); if (e.isNull()) continue; if (e.tagName() == "item") { DiscoItem item; item.setJid(e.attribute("jid")); item.setName(e.attribute("name")); item.setNode(e.attribute("node")); item.setAction(DiscoItem::string2action(e.attribute("action"))); d->items.append(item); } else if (d->subsetsEl.isNull()) { d->subsetsEl = SubsetsClientManager::findElement(e, false); } } setSuccess(); } else { setError(x); } return true; } //---------------------------------------------------------------------------- // JT_DiscoPublish //---------------------------------------------------------------------------- class JT_DiscoPublish::Private { public: Private() { } QDomElement iq; Jid jid; DiscoList list; }; JT_DiscoPublish::JT_DiscoPublish(Task *parent) : Task(parent) { d = new Private; } JT_DiscoPublish::~JT_DiscoPublish() { delete d; } void JT_DiscoPublish::set(const Jid &j, const DiscoList &list) { d->list = list; d->jid = j; d->iq = createIQ(doc(), "set", d->jid.full(), id()); QDomElement query = doc()->createElementNS("http://jabber.org/protocol/disco#items", "query"); // FIXME: unsure about this // if ( !node.isEmpty() ) // query.setAttribute("node", node); DiscoList::ConstIterator it = list.begin(); for (; it != list.end(); ++it) { QDomElement w = doc()->createElement("item"); w.setAttribute("jid", (*it).jid().full()); if (!(*it).name().isEmpty()) w.setAttribute("name", (*it).name()); if (!(*it).node().isEmpty()) w.setAttribute("node", (*it).node()); w.setAttribute("action", DiscoItem::action2string((*it).action())); query.appendChild(w); } d->iq.appendChild(query); } void JT_DiscoPublish::onGo() { send(d->iq); } bool JT_DiscoPublish::take(const QDomElement &x) { if (!iqVerify(x, d->jid, id())) return false; if (x.attribute("type") == "result") { setSuccess(); } else { setError(x); } return true; } // --------------------------------------------------------- // JT_BoBServer // --------------------------------------------------------- JT_BoBServer::JT_BoBServer(Task *parent) : Task(parent) { } bool JT_BoBServer::take(const QDomElement &e) { if (e.tagName() != "iq" || e.attribute("type") != "get") return false; QDomElement data = e.firstChildElement("data"); if (data.namespaceURI() == "urn:xmpp:bob") { QDomElement iq; BoBData bd = client()->bobManager()->bobData(data.attribute("cid")); if (bd.isNull()) { iq = createIQ(client()->doc(), "error", e.attribute("from"), e.attribute("id")); Stanza::Error error(Stanza::Error::Cancel, Stanza::Error::ItemNotFound); iq.appendChild(error.toXml(*doc(), client()->stream().baseNS())); } else { iq = createIQ(doc(), "result", e.attribute("from"), e.attribute("id")); iq.appendChild(bd.toXml(doc())); } send(iq); return true; } return false; } //---------------------------------------------------------------------------- // JT_BitsOfBinary //---------------------------------------------------------------------------- class JT_BitsOfBinary::Private { public: Private() { } QDomElement iq; Jid jid; QString cid; BoBData data; }; JT_BitsOfBinary::JT_BitsOfBinary(Task *parent) : Task(parent) { d = new Private; } JT_BitsOfBinary::~JT_BitsOfBinary() { delete d; } void JT_BitsOfBinary::get(const Jid &j, const QString &cid) { d->jid = j; d->cid = cid; d->data = client()->bobManager()->bobData(cid); if (d->data.isNull()) { d->iq = createIQ(doc(), "get", d->jid.full(), id()); QDomElement data = doc()->createElementNS("urn:xmpp:bob", "data"); data.setAttribute("cid", cid); d->iq.appendChild(data); } } void JT_BitsOfBinary::onGo() { if (d->data.isNull()) { send(d->iq); } else { setSuccess(); } } bool JT_BitsOfBinary::take(const QDomElement &x) { if (!iqVerify(x, d->jid, id())) { return false; } if (x.attribute("type") == "result") { QDomElement data = x.firstChildElement("data"); if (!data.isNull() && data.attribute("cid") == d->cid) { // check xmlns? d->data.fromXml(data); client()->bobManager()->append(d->data); } setSuccess(); } else { setError(x); } return true; } BoBData &JT_BitsOfBinary::data() { return d->data; } //---------------------------------------------------------------------------- // JT_PongServer //---------------------------------------------------------------------------- /** * \class JT_PongServer * \brief Answers XMPP Pings */ JT_PongServer::JT_PongServer(Task *parent) : Task(parent) { } bool JT_PongServer::take(const QDomElement &e) { if (e.tagName() != "iq" || e.attribute("type") != "get") return false; QDomElement ping = e.firstChildElement("ping"); if (!e.isNull() && ping.namespaceURI() == "urn:xmpp:ping") { QDomElement iq = createIQ(doc(), "result", e.attribute("from"), e.attribute("id")); send(iq); return true; } return false; } //--------------------------------------------------------------------------- // JT_CaptchaChallenger //--------------------------------------------------------------------------- class JT_CaptchaChallenger::Private { public: Jid j; CaptchaChallenge challenge; }; JT_CaptchaChallenger::JT_CaptchaChallenger(Task *parent) : Task(parent), d(new Private) { } JT_CaptchaChallenger::~JT_CaptchaChallenger() { delete d; } void JT_CaptchaChallenger::set(const Jid &j, const CaptchaChallenge &c) { d->j = j; d->challenge = c; } void JT_CaptchaChallenger::onGo() { setTimeout(CaptchaValidTimeout); Message m; m.setId(id()); m.setBody(d->challenge.explanation()); m.setUrlList(d->challenge.urls()); XData form = d->challenge.form(); XData::FieldList fl = form.fields(); XData::FieldList::Iterator it; for (it = fl.begin(); it < fl.end(); ++it) { if (it->var() == "challenge" && it->type() == XData::Field::Field_Hidden) { it->setValue(QStringList() << id()); } } if (it == fl.end()) { XData::Field f; f.setType(XData::Field::Field_Hidden); f.setVar("challenge"); f.setValue(QStringList() << id()); fl.append(f); } form.setFields(fl); m.setForm(form); m.setTo(d->j); client()->sendMessage(m); } bool JT_CaptchaChallenger::take(const QDomElement &x) { if (x.tagName() == "message" && x.attribute("id") == id() && Jid(x.attribute("from")) == d->j && !x.firstChildElement("error").isNull()) { setError(x); return true; } XDomNodeList nl; XData xd; QString rid = x.attribute("id"); if (rid.isEmpty() || x.tagName() != "iq" || Jid(x.attribute("from")) != d->j || x.attribute("type") != "set" || (nl = childElementsByTagNameNS(x, "urn:xmpp:captcha", "captcha")).isEmpty() || (nl = childElementsByTagNameNS(nl.item(0).toElement(), "jabber:x:data", "x")).isEmpty() || (xd.fromXml(nl.item(0).toElement()), xd.getField("challenge").value().value(0) != id())) { return false; } CaptchaChallenge::Result r = d->challenge.validateResponse(xd); QDomElement iq; if (r == CaptchaChallenge::Passed) { iq = createIQ(doc(), "result", d->j.full(), rid); } else { Stanza::Error::ErrorCond ec; if (r == CaptchaChallenge::Unavailable) { ec = Stanza::Error::ServiceUnavailable; } else { ec = Stanza::Error::NotAcceptable; } iq = createIQ(doc(), "error", d->j.full(), rid); Stanza::Error error(Stanza::Error::Cancel, ec); iq.appendChild(error.toXml(*doc(), client()->stream().baseNS())); } send(iq); setSuccess(); return true; } //--------------------------------------------------------------------------- // JT_CaptchaSender //--------------------------------------------------------------------------- JT_CaptchaSender::JT_CaptchaSender(Task *parent) : Task(parent) { } void JT_CaptchaSender::set(const Jid &j, const XData &xd) { to = j; iq = createIQ(doc(), "set", to.full(), id()); iq.appendChild(doc()->createElementNS("urn:xmpp:captcha", "captcha")).appendChild(xd.toXml(doc(), true)); } void JT_CaptchaSender::onGo() { send(iq); } bool JT_CaptchaSender::take(const QDomElement &x) { if (!iqVerify(x, to, id())) { return false; } if (x.attribute("type") == "result") { setSuccess(); } else { setError(x); } return true; } //---------------------------------------------------------------------------- // JT_MessageCarbons //---------------------------------------------------------------------------- JT_MessageCarbons::JT_MessageCarbons(Task *parent) : Task(parent) { } void JT_MessageCarbons::enable() { _iq = createIQ(doc(), "set", "", id()); QDomElement enable = doc()->createElementNS("urn:xmpp:carbons:2", "enable"); _iq.appendChild(enable); } void JT_MessageCarbons::disable() { _iq = createIQ(doc(), "set", "", id()); QDomElement disable = doc()->createElementNS("urn:xmpp:carbons:2", "disable"); _iq.appendChild(disable); } void JT_MessageCarbons::onGo() { send(_iq); setSuccess(); } bool JT_MessageCarbons::take(const QDomElement &e) { if (e.tagName() != "iq" || e.attribute("type") != "result") return false; bool res = iqVerify(e, Jid(), id()); return res; } psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_tasks.h000066400000000000000000000213541370065651000242320ustar00rootroot00000000000000/* * tasks.h - basic tasks * Copyright (C) 2001-2002 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_TASKS_H #define XMPP_TASKS_H #include "xmpp_discoinfotask.h" #include "xmpp_encryptionhandler.h" #include "xmpp_form.h" #include "xmpp_message.h" #include "xmpp_subsets.h" #include "xmpp_vcard.h" #include #include #include namespace XMPP { class BoBData; class CaptchaChallenge; class Roster; class Status; class JT_Register : public Task { Q_OBJECT public: JT_Register(Task *parent); ~JT_Register(); // OLd style registration void reg(const QString &user, const QString &pass); void changepw(const QString &pass); void unreg(const Jid &j = ""); const Form & form() const; bool hasXData() const; const XData &xdata() const; bool isRegistered() const; void getForm(const Jid &); void setForm(const Form &); void setForm(const Jid &, const XData &); void onGo(); bool take(const QDomElement &); private: QDomElement iq; Jid to; class Private; Private *d = nullptr; }; class JT_UnRegister : public Task { Q_OBJECT public: JT_UnRegister(Task *parent); ~JT_UnRegister(); void unreg(const Jid &); void onGo(); private slots: void getFormFinished(); void unregFinished(); private: class Private; Private *d = nullptr; }; class JT_Roster : public Task { Q_OBJECT public: JT_Roster(Task *parent); ~JT_Roster(); void get(); void set(const Jid &, const QString &name, const QStringList &groups); void remove(const Jid &); // XEP-0083 void getGroupsDelimiter(); void setGroupsDelimiter(const QString &groupsDelimiter); const Roster &roster() const; QString groupsDelimiter() const; QString toString() const; bool fromString(const QString &); void onGo(); bool take(const QDomElement &x); private: enum Type { Get, Set, Remove, GetDelimiter, SetDelimiter, Unknown = -1 }; int type; QDomElement iq; Jid to; class Private; Private *d = nullptr; }; class JT_PushRoster : public Task { Q_OBJECT public: JT_PushRoster(Task *parent); ~JT_PushRoster(); bool take(const QDomElement &); signals: void roster(const Roster &); private: class Private; Private *d = nullptr; }; class JT_Presence : public Task { Q_OBJECT public: JT_Presence(Task *parent); ~JT_Presence(); void pres(const Status &); void pres(const Jid &, const Status &); void sub(const Jid &, const QString &subType, const QString &nick = QString()); void probe(const Jid &to); void onGo(); private: QDomElement tag; int type = -1; class Private; Private *d = nullptr; }; class JT_PushPresence : public Task { Q_OBJECT public: JT_PushPresence(Task *parent); ~JT_PushPresence(); bool take(const QDomElement &); signals: void presence(const Jid &, const Status &); void subscription(const Jid &, const QString &, const QString &); private: class Private; Private *d = nullptr; }; class JT_Session : public Task { public: JT_Session(Task *parent); void onGo(); bool take(const QDomElement &); }; class JT_Message : public Task { Q_OBJECT public: JT_Message(Task *parent, Message &); ~JT_Message(); void onGo(); private: Message m; class Private; Private *d = nullptr; }; class JT_PushMessage : public Task { Q_OBJECT public: JT_PushMessage(Task *parent, EncryptionHandler *encryptionHandler = nullptr); ~JT_PushMessage(); bool take(const QDomElement &); signals: void message(const Message &); private: class Private; Private *d = nullptr; }; class JT_VCard : public Task { Q_OBJECT public: JT_VCard(Task *parent); ~JT_VCard(); void get(const Jid &); void set(const VCard &); void set(const Jid &, const VCard &, bool isTarget = false); const Jid & jid() const; const VCard &vcard() const; void onGo(); bool take(const QDomElement &x); private: int type; class Private; Private *d = nullptr; }; class JT_Search : public Task { Q_OBJECT public: JT_Search(Task *parent); ~JT_Search(); const Form & form() const; const QList &results() const; bool hasXData() const; const XData &xdata() const; void get(const Jid &); void set(const Form &); void set(const Jid &, const XData &); void onGo(); bool take(const QDomElement &x); private: QDomElement iq; int type; class Private; Private *d = nullptr; }; class JT_ClientVersion : public Task { Q_OBJECT public: JT_ClientVersion(Task *); void get(const Jid &); void onGo(); bool take(const QDomElement &); const Jid & jid() const; const QString &name() const; const QString &version() const; const QString &os() const; private: QDomElement iq; Jid j; QString v_name, v_ver, v_os; }; class JT_EntityTime : public Task { public: JT_EntityTime(Task *); void onGo(); bool take(const QDomElement &); void get(const XMPP::Jid &j); const XMPP::Jid &jid() const; const QDateTime &dateTime() const; int timezoneOffset() const; private: QDomElement iq; XMPP::Jid j; QDateTime utc; int tzo = 0; }; class JT_ServInfo : public Task { Q_OBJECT public: JT_ServInfo(Task *); ~JT_ServInfo(); bool take(const QDomElement &); }; class JT_Gateway : public Task { Q_OBJECT public: JT_Gateway(Task *); void get(const Jid &); void set(const Jid &, const QString &prompt); void onGo(); bool take(const QDomElement &); Jid jid() const; QString desc() const; QString prompt() const; Jid translatedJid() const; private: QDomElement iq; int type; Jid v_jid; Jid v_translatedJid; QString v_prompt, v_desc; }; class JT_DiscoItems : public Task { Q_OBJECT public: JT_DiscoItems(Task *); ~JT_DiscoItems(); void get(const Jid &, const QString &node = QString()); void get(const DiscoItem &); const DiscoList &items() const; void includeSubsetQuery(const SubsetsClientManager &); bool extractSubsetInfo(SubsetsClientManager &); void onGo(); bool take(const QDomElement &); private: class Private; Private *d = nullptr; }; class JT_DiscoPublish : public Task { Q_OBJECT public: JT_DiscoPublish(Task *); ~JT_DiscoPublish(); void set(const Jid &, const DiscoList &); void onGo(); bool take(const QDomElement &); private: class Private; Private *d = nullptr; }; class JT_BoBServer : public Task { Q_OBJECT public: JT_BoBServer(Task *parent); bool take(const QDomElement &); }; class JT_BitsOfBinary : public Task { Q_OBJECT public: JT_BitsOfBinary(Task *); ~JT_BitsOfBinary(); void get(const Jid &, const QString &); void onGo(); bool take(const QDomElement &); BoBData &data(); private: class Private; Private *d = nullptr; }; class JT_PongServer : public Task { Q_OBJECT public: JT_PongServer(Task *); bool take(const QDomElement &); }; class JT_MessageCarbons : public Task { Q_OBJECT public: JT_MessageCarbons(Task *parent); void enable(); void disable(); void onGo(); bool take(const QDomElement &e); private: QDomElement _iq; }; class JT_CaptchaChallenger : public Task { Q_OBJECT public: const static int CaptchaValidTimeout = 120; JT_CaptchaChallenger(Task *); ~JT_CaptchaChallenger(); void set(const Jid &, const CaptchaChallenge &); void onGo(); bool take(const QDomElement &); private: class Private; Private *d = nullptr; }; class JT_CaptchaSender : public Task { Q_OBJECT public: JT_CaptchaSender(Task *); void set(const Jid &, const XData &); void onGo(); bool take(const QDomElement &); private: Jid to; QDomElement iq; }; } // namespace XMPP #endif // XMPP_TASKS_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_thumbs.h000066400000000000000000000031351370065651000244040ustar00rootroot00000000000000/* * Copyright (C) 2019 Sergey Ilinykh * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_THUMBS_H #define XMPP_THUMBS_H #include #include #include #define XMPP_THUMBS_NS "urn:xmpp:thumbs:1" // TODO make nsdb.cpp/h with static declarations of all ns namespace XMPP { class Thumbnail { public: inline Thumbnail() : width(0), height(0) { } // data - for outgoing it's actual image data. for incoming - cid inline Thumbnail(const QByteArray &data, const QString &mimeType = QString(), quint32 width = 0, quint32 height = 0) : data(data), mimeType(mimeType), width(width), height(height) { } Thumbnail(const QDomElement &el); inline bool isValid() const { return uri.isValid(); } QDomElement toXml(QDomDocument *doc) const; QUrl uri; QByteArray data; QString mimeType; quint32 width; quint32 height; }; } // namespace XMPP #endif // XMPP_THUMBS_H psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_url.h000066400000000000000000000022311370065651000237000ustar00rootroot00000000000000/* * Copyright (C) 2003 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_URL #define XMPP_URL class QString; namespace XMPP { class Url { public: Url(const QString &url = "", const QString &desc = ""); Url(const Url &); Url &operator=(const Url &); ~Url(); QString url() const; QString desc() const; void setUrl(const QString &); void setDesc(const QString &); private: class Private; Private *d; }; typedef QList UrlList; } // namespace XMPP #endif // XMPP_URL psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_vcard.cpp000066400000000000000000000756701370065651000245510ustar00rootroot00000000000000/* * xmpp_vcard.cpp - classes for handling vCards * Copyright (C) 2003 Michail Pishchagin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "xmpp_vcard.h" #include "xmpp_xmlcommon.h" #include #include #include // needed for image format recognition #include #include #include #include #include using namespace XMLHelper; //---------------------------------------------------------------------------- // VCard //---------------------------------------------------------------------------- QString openedImage2type(QIODevice *dev) { QString format = QImageReader::imageFormat(dev).toUpper(); // TODO: add more formats: PBM PGM PPM XBM XPM if (format == QLatin1String("PNG") || format == QLatin1String("PSIPNG")) // PsiPNG in normal case return QLatin1String("image/png"); if (format == QLatin1String("MNG")) return QLatin1String("video/x-mng"); if (format == QLatin1String("GIF")) return QLatin1String("image/gif"); if (format == QLatin1String("JPEG")) return QLatin1String("image/jpeg"); if (format == QLatin1String("BMP")) return QLatin1String("image/bmp"); if (format == QLatin1String("WEBP")) return QLatin1String("image/webp"); if (format == QLatin1String("XPM")) return QLatin1String("image/x-xpm"); if (format == QLatin1String("SVG")) return QLatin1String("image/svg+xml"); return QString(); } QString image2type(const QByteArray &ba) { if (ba.isEmpty()) return QString(); QBuffer buf; buf.setData(ba); buf.open(QIODevice::ReadOnly); return openedImage2type(&buf); } namespace XMPP { // Long lines of encoded binary data SHOULD BE folded to 75 characters using the folding method defined in [MIME-DIR]. static QString foldString(const QString &s) { QString ret; for (int i = 0; i < (int)s.length(); i++) { if (!(i % 75)) ret += '\n'; ret += s[i]; } return ret; } class VCardPrivate : public QSharedData { public: VCardPrivate(); ~VCardPrivate(); // do we need copy constructor? // VCardPrivate(const VCardPrivate &other) : // QSharedData(other), version(other.version), fullName(other.fullName) { qDebug("Copy VCardPrivate"); } QString version; QString fullName; QString familyName, givenName, middleName, prefixName, suffixName; QString nickName; QByteArray photo; QString photoURI; QString bday; VCard::AddressList addressList; VCard::LabelList labelList; VCard::PhoneList phoneList; VCard::EmailList emailList; QString jid; QString mailer; QString timezone; VCard::Geo geo; QString title; QString role; QByteArray logo; QString logoURI; QSharedPointer agent; QString agentURI; VCard::Org org; QStringList categories; QString note; QString prodId; QString rev; QString sortString; QByteArray sound; QString soundURI, soundPhonetic; QString uid; QString url; QString desc; VCard::PrivacyClass privacyClass; QByteArray key; bool isEmpty() const; }; VCardPrivate::VCardPrivate() { privacyClass = VCard::pcNone; } VCardPrivate::~VCardPrivate() { } bool VCardPrivate::isEmpty() const { if (!version.isEmpty() || !fullName.isEmpty() || !familyName.isEmpty() || !givenName.isEmpty() || !middleName.isEmpty() || !prefixName.isEmpty() || !suffixName.isEmpty() || !nickName.isEmpty() || !photo.isEmpty() || !photoURI.isEmpty() || !bday.isEmpty() || !addressList.isEmpty() || !labelList.isEmpty() || !phoneList.isEmpty() || !emailList.isEmpty() || !jid.isEmpty() || !mailer.isEmpty() || !timezone.isEmpty() || !geo.lat.isEmpty() || !geo.lon.isEmpty() || !title.isEmpty() || !role.isEmpty() || !logo.isEmpty() || !logoURI.isEmpty() || (agent && !agent->isEmpty()) || !agentURI.isEmpty() || !org.name.isEmpty() || !org.unit.isEmpty() || !categories.isEmpty() || !note.isEmpty() || !prodId.isEmpty() || !rev.isEmpty() || !sortString.isEmpty() || !sound.isEmpty() || !soundURI.isEmpty() || !soundPhonetic.isEmpty() || !uid.isEmpty() || !url.isEmpty() || !desc.isEmpty() || (privacyClass != VCard::pcNone) || !key.isEmpty()) { return false; } return true; } VCard::VCard() { } VCard::VCard(const VCard &from) : d(from.d) { } VCard &VCard::operator=(const VCard &from) { d = from.d; return *this; } VCard::~VCard() { } QDomElement VCard::toXml(QDomDocument *doc) const { QDomElement v = doc->createElementNS("vcard-temp", "vCard"); if (!d->version.isEmpty()) v.appendChild(textTag(doc, "VERSION", d->version)); if (!d->fullName.isEmpty()) v.appendChild(textTag(doc, "FN", d->fullName)); if (!d->familyName.isEmpty() || !d->givenName.isEmpty() || !d->middleName.isEmpty() || !d->prefixName.isEmpty() || !d->suffixName.isEmpty()) { QDomElement w = doc->createElement("N"); if (!d->familyName.isEmpty()) w.appendChild(textTag(doc, "FAMILY", d->familyName)); if (!d->givenName.isEmpty()) w.appendChild(textTag(doc, "GIVEN", d->givenName)); if (!d->middleName.isEmpty()) w.appendChild(textTag(doc, "MIDDLE", d->middleName)); if (!d->prefixName.isEmpty()) w.appendChild(textTag(doc, "PREFIX", d->prefixName)); if (!d->suffixName.isEmpty()) w.appendChild(textTag(doc, "SUFFIX", d->suffixName)); v.appendChild(w); } if (!d->nickName.isEmpty()) v.appendChild(textTag(doc, "NICKNAME", d->nickName)); if (!d->photo.isEmpty() || !d->photoURI.isEmpty()) { QDomElement w = doc->createElement("PHOTO"); if (!d->photo.isEmpty()) { w.appendChild(textTag(doc, "TYPE", image2type(d->photo))); w.appendChild(textTag(doc, "BINVAL", foldString(QCA::Base64().arrayToString(d->photo)))); } else if (!d->photoURI.isEmpty()) w.appendChild(textTag(doc, "EXTVAL", d->photoURI)); v.appendChild(w); } if (!d->bday.isEmpty()) v.appendChild(textTag(doc, "BDAY", d->bday)); if (!d->addressList.isEmpty()) { AddressList::ConstIterator it = d->addressList.constBegin(); for (; it != d->addressList.end(); ++it) { QDomElement w = doc->createElement("ADR"); const Address &a = *it; if (a.home) w.appendChild(emptyTag(doc, "HOME")); if (a.work) w.appendChild(emptyTag(doc, "WORK")); if (a.postal) w.appendChild(emptyTag(doc, "POSTAL")); if (a.parcel) w.appendChild(emptyTag(doc, "PARCEL")); if (a.dom) w.appendChild(emptyTag(doc, "DOM")); if (a.intl) w.appendChild(emptyTag(doc, "INTL")); if (a.pref) w.appendChild(emptyTag(doc, "PREF")); if (!a.pobox.isEmpty()) w.appendChild(textTag(doc, "POBOX", a.pobox)); if (!a.extaddr.isEmpty()) w.appendChild(textTag(doc, "EXTADR", a.extaddr)); if (!a.street.isEmpty()) w.appendChild(textTag(doc, "STREET", a.street)); if (!a.locality.isEmpty()) w.appendChild(textTag(doc, "LOCALITY", a.locality)); if (!a.region.isEmpty()) w.appendChild(textTag(doc, "REGION", a.region)); if (!a.pcode.isEmpty()) w.appendChild(textTag(doc, "PCODE", a.pcode)); if (!a.country.isEmpty()) w.appendChild(textTag(doc, "CTRY", a.country)); v.appendChild(w); } } if (!d->labelList.isEmpty()) { LabelList::ConstIterator it = d->labelList.constBegin(); for (; it != d->labelList.end(); ++it) { QDomElement w = doc->createElement("LABEL"); const Label &l = *it; if (l.home) w.appendChild(emptyTag(doc, "HOME")); if (l.work) w.appendChild(emptyTag(doc, "WORK")); if (l.postal) w.appendChild(emptyTag(doc, "POSTAL")); if (l.parcel) w.appendChild(emptyTag(doc, "PARCEL")); if (l.dom) w.appendChild(emptyTag(doc, "DOM")); if (l.intl) w.appendChild(emptyTag(doc, "INTL")); if (l.pref) w.appendChild(emptyTag(doc, "PREF")); if (!l.lines.isEmpty()) { QStringList::ConstIterator it = l.lines.constBegin(); for (; it != l.lines.end(); ++it) w.appendChild(textTag(doc, "LINE", *it)); } v.appendChild(w); } } if (!d->phoneList.isEmpty()) { PhoneList::ConstIterator it = d->phoneList.constBegin(); for (; it != d->phoneList.end(); ++it) { QDomElement w = doc->createElement("TEL"); const Phone &p = *it; if (p.home) w.appendChild(emptyTag(doc, "HOME")); if (p.work) w.appendChild(emptyTag(doc, "WORK")); if (p.voice) w.appendChild(emptyTag(doc, "VOICE")); if (p.fax) w.appendChild(emptyTag(doc, "FAX")); if (p.pager) w.appendChild(emptyTag(doc, "PAGER")); if (p.msg) w.appendChild(emptyTag(doc, "MSG")); if (p.cell) w.appendChild(emptyTag(doc, "CELL")); if (p.video) w.appendChild(emptyTag(doc, "VIDEO")); if (p.bbs) w.appendChild(emptyTag(doc, "BBS")); if (p.modem) w.appendChild(emptyTag(doc, "MODEM")); if (p.isdn) w.appendChild(emptyTag(doc, "ISDN")); if (p.pcs) w.appendChild(emptyTag(doc, "PCS")); if (p.pref) w.appendChild(emptyTag(doc, "PREF")); if (!p.number.isEmpty()) w.appendChild(textTag(doc, "NUMBER", p.number)); v.appendChild(w); } } if (!d->emailList.isEmpty()) { EmailList::ConstIterator it = d->emailList.constBegin(); for (; it != d->emailList.end(); ++it) { QDomElement w = doc->createElement("EMAIL"); const Email &e = *it; if (e.pref) w.appendChild(emptyTag(doc, "PREF")); if (e.home) w.appendChild(emptyTag(doc, "HOME")); if (e.work) w.appendChild(emptyTag(doc, "WORK")); if (e.internet) w.appendChild(emptyTag(doc, "INTERNET")); if (e.x400) w.appendChild(emptyTag(doc, "X400")); if (!e.userid.isEmpty()) w.appendChild(textTag(doc, "USERID", e.userid)); v.appendChild(w); } } if (!d->jid.isEmpty()) v.appendChild(textTag(doc, "JABBERID", d->jid)); if (!d->mailer.isEmpty()) v.appendChild(textTag(doc, "MAILER", d->mailer)); if (!d->timezone.isEmpty()) v.appendChild(textTag(doc, "TZ", d->timezone)); if (!d->geo.lat.isEmpty() || !d->geo.lon.isEmpty()) { QDomElement w = doc->createElement("GEO"); if (!d->geo.lat.isEmpty()) w.appendChild(textTag(doc, "LAT", d->geo.lat)); if (!d->geo.lon.isEmpty()) w.appendChild(textTag(doc, "LON", d->geo.lon)); v.appendChild(w); } if (!d->title.isEmpty()) v.appendChild(textTag(doc, "TITLE", d->title)); if (!d->role.isEmpty()) v.appendChild(textTag(doc, "ROLE", d->role)); if (!d->logo.isEmpty() || !d->logoURI.isEmpty()) { QDomElement w = doc->createElement("LOGO"); if (!d->logo.isEmpty()) { w.appendChild(textTag(doc, "TYPE", image2type(d->logo))); w.appendChild(textTag(doc, "BINVAL", foldString(QCA::Base64().arrayToString(d->logo)))); } else if (!d->logoURI.isEmpty()) w.appendChild(textTag(doc, "EXTVAL", d->logoURI)); v.appendChild(w); } if (!d->agentURI.isEmpty() || (d->agent && d->agent->isEmpty())) { QDomElement w = doc->createElement("AGENT"); if (d->agent && !d->agent->isEmpty()) w.appendChild(d->agent->toXml(doc)); else if (!d->agentURI.isEmpty()) w.appendChild(textTag(doc, "EXTVAL", d->agentURI)); v.appendChild(w); } if (!d->org.name.isEmpty() || !d->org.unit.isEmpty()) { QDomElement w = doc->createElement("ORG"); if (!d->org.name.isEmpty()) w.appendChild(textTag(doc, "ORGNAME", d->org.name)); if (!d->org.unit.isEmpty()) { QStringList::ConstIterator it = d->org.unit.constBegin(); for (; it != d->org.unit.end(); ++it) w.appendChild(textTag(doc, "ORGUNIT", *it)); } v.appendChild(w); } if (!d->categories.isEmpty()) { QDomElement w = doc->createElement("CATEGORIES"); QStringList::ConstIterator it = d->categories.constBegin(); for (; it != d->categories.end(); ++it) w.appendChild(textTag(doc, "KEYWORD", *it)); v.appendChild(w); } if (!d->note.isEmpty()) v.appendChild(textTag(doc, "NOTE", d->note)); if (!d->prodId.isEmpty()) v.appendChild(textTag(doc, "PRODID", d->prodId)); if (!d->rev.isEmpty()) v.appendChild(textTag(doc, "REV", d->rev)); if (!d->sortString.isEmpty()) v.appendChild(textTag(doc, "SORT-STRING", d->sortString)); if (!d->sound.isEmpty() || !d->soundURI.isEmpty() || !d->soundPhonetic.isEmpty()) { QDomElement w = doc->createElement("SOUND"); if (!d->sound.isEmpty()) w.appendChild(textTag(doc, "BINVAL", foldString(QCA::Base64().arrayToString(d->sound)))); else if (!d->soundURI.isEmpty()) w.appendChild(textTag(doc, "EXTVAL", d->soundURI)); else if (!d->soundPhonetic.isEmpty()) w.appendChild(textTag(doc, "PHONETIC", d->soundPhonetic)); v.appendChild(w); } if (!d->uid.isEmpty()) v.appendChild(textTag(doc, "UID", d->uid)); if (!d->url.isEmpty()) v.appendChild(textTag(doc, "URL", d->url)); if (!d->desc.isEmpty()) v.appendChild(textTag(doc, "DESC", d->desc)); if (d->privacyClass != pcNone) { QDomElement w = doc->createElement("CLASS"); if (d->privacyClass == pcPublic) w.appendChild(emptyTag(doc, "PUBLIC")); else if (d->privacyClass == pcPrivate) w.appendChild(emptyTag(doc, "PRIVATE")); else if (d->privacyClass == pcConfidential) w.appendChild(emptyTag(doc, "CONFIDENTIAL")); v.appendChild(w); } if (!d->key.isEmpty()) { QDomElement w = doc->createElement("KEY"); // TODO: Justin, please check out this code w.appendChild(textTag(doc, "TYPE", "text/plain")); // FIXME w.appendChild(textTag(doc, "CRED", QString::fromUtf8(d->key))); // FIXME v.appendChild(w); } return v; } VCard VCard::fromXml(const QDomElement &q) { if (q.tagName().toUpper() != "VCARD") return VCard(); VCard v; v.d = new VCardPrivate; QDomNode n = q.firstChild(); for (; !n.isNull(); n = n.nextSibling()) { QDomElement i = n.toElement(); if (i.isNull()) continue; QString tag = i.tagName().toUpper(); QDomElement e; if (tag == "VERSION") v.d->version = i.text().trimmed(); else if (tag == "FN") v.d->fullName = i.text().trimmed(); else if (tag == "N") { v.d->familyName = subTagText(i, "FAMILY"); v.d->givenName = subTagText(i, "GIVEN"); v.d->middleName = subTagText(i, "MIDDLE"); v.d->prefixName = subTagText(i, "PREFIX"); v.d->suffixName = subTagText(i, "SUFFIX"); } else if (tag == "NICKNAME") v.d->nickName = i.text().trimmed(); else if (tag == "PHOTO") { v.d->photo = QCA::Base64().stringToArray(subTagText(i, "BINVAL").replace(QRegExp("[\r\n]+"), "")).toByteArray(); v.d->photoURI = subTagText(i, "EXTVAL"); } else if (tag == "BDAY") v.d->bday = i.text().trimmed(); else if (tag == "ADR") { Address a; a.home = hasSubTag(i, "HOME"); a.work = hasSubTag(i, "WORK"); a.postal = hasSubTag(i, "POSTAL"); a.parcel = hasSubTag(i, "PARCEL"); a.dom = hasSubTag(i, "DOM"); a.intl = hasSubTag(i, "INTL"); a.pref = hasSubTag(i, "PREF"); a.pobox = subTagText(i, "POBOX"); a.extaddr = subTagText(i, "EXTADR"); a.street = subTagText(i, "STREET"); a.locality = subTagText(i, "LOCALITY"); a.region = subTagText(i, "REGION"); a.pcode = subTagText(i, "PCODE"); a.country = subTagText(i, "CTRY"); if (a.country.isEmpty()) // FIXME: Workaround for Psi prior to 0.9 if (hasSubTag(i, "COUNTRY")) a.country = subTagText(i, "COUNTRY"); if (a.extaddr.isEmpty()) // FIXME: Workaround for Psi prior to 0.9 if (hasSubTag(i, "EXTADD")) a.extaddr = subTagText(i, "EXTADD"); v.d->addressList.append(a); } else if (tag == "LABEL") { Label l; l.home = hasSubTag(i, "HOME"); l.work = hasSubTag(i, "WORK"); l.postal = hasSubTag(i, "POSTAL"); l.parcel = hasSubTag(i, "PARCEL"); l.dom = hasSubTag(i, "DOM"); l.intl = hasSubTag(i, "INTL"); l.pref = hasSubTag(i, "PREF"); QDomNode nn = i.firstChild(); for (; !nn.isNull(); nn = nn.nextSibling()) { QDomElement ii = nn.toElement(); if (ii.isNull()) continue; if (ii.tagName().toUpper() == "LINE") l.lines.append(ii.text().trimmed()); } v.d->labelList.append(l); } else if (tag == "TEL") { Phone p; p.home = hasSubTag(i, "HOME"); p.work = hasSubTag(i, "WORK"); p.voice = hasSubTag(i, "VOICE"); p.fax = hasSubTag(i, "FAX"); p.pager = hasSubTag(i, "PAGER"); p.msg = hasSubTag(i, "MSG"); p.cell = hasSubTag(i, "CELL"); p.video = hasSubTag(i, "VIDEO"); p.bbs = hasSubTag(i, "BBS"); p.modem = hasSubTag(i, "MODEM"); p.isdn = hasSubTag(i, "ISDN"); p.pcs = hasSubTag(i, "PCS"); p.pref = hasSubTag(i, "PREF"); p.number = subTagText(i, "NUMBER"); if (!p.number.isEmpty()) { v.d->phoneList.append(p); auto it = std::find_if(v.d->phoneList.begin(), v.d->phoneList.end(), [number = p.number](const Phone &p) { return p.number == number; }); if (it == v.d->phoneList.end()) { v.d->phoneList.append(p); } else { it->home = (it->home || p.home); it->work = (it->work || p.work); it->voice = (it->voice || p.voice); it->fax = (it->fax || p.fax); it->pager = (it->pager || p.pager); it->msg = (it->msg || p.msg); it->cell = (it->cell || p.cell); it->video = (it->video || p.video); it->bbs = (it->bbs || p.bbs); it->modem = (it->modem || p.modem); it->isdn = (it->isdn || p.isdn); it->pcs = (it->pcs || p.pcs); it->pref = (it->pref || p.pref); } } } else if (tag == "EMAIL") { Email m; m.home = hasSubTag(i, "HOME"); m.work = hasSubTag(i, "WORK"); m.internet = hasSubTag(i, "INTERNET"); m.x400 = hasSubTag(i, "X400"); m.pref = hasSubTag(i, "PREF"); m.userid = subTagText(i, "USERID").trimmed(); if (!m.userid.isEmpty()) { auto it = std::find_if(v.d->emailList.begin(), v.d->emailList.end(), [user_id = m.userid](const Email &e) { return e.userid == user_id; }); if (it == v.d->emailList.end()) { v.d->emailList.append(m); } else { it->home = (it->home || m.home); it->work = (it->work || m.work); it->internet = (it->internet || m.internet); it->x400 = (it->x400 || m.x400); it->pref = (it->pref || m.pref); } } } else if (tag == "JABBERID") v.d->jid = i.text().trimmed(); else if (tag == "MAILER") v.d->mailer = i.text().trimmed(); else if (tag == "TZ") v.d->timezone = i.text().trimmed(); else if (tag == "GEO") { v.d->geo.lat = subTagText(i, "LAT"); v.d->geo.lon = subTagText(i, "LON"); } else if (tag == "TITLE") v.d->title = i.text().trimmed(); else if (tag == "ROLE") v.d->role = i.text().trimmed(); else if (tag == "LOGO") { v.d->logo = QCA::Base64().stringToArray(subTagText(i, "BINVAL").replace("\n", "")).toByteArray(); v.d->logoURI = subTagText(i, "EXTVAL"); } else if (tag == "AGENT") { e = i.firstChildElement("VCARD"); if (!e.isNull()) { VCard a; if (a.fromXml(e)) { if (!v.d->agent) v.d->agent = QSharedPointer(new VCard); *(v.d->agent) = a; } } v.d->agentURI = subTagText(i, "EXTVAL"); } else if (tag == "ORG") { v.d->org.name = subTagText(i, "ORGNAME"); QDomNode nn = i.firstChild(); for (; !nn.isNull(); nn = nn.nextSibling()) { QDomElement ii = nn.toElement(); if (ii.isNull()) continue; if (ii.tagName().toUpper() == "ORGUNIT") v.d->org.unit.append(ii.text().trimmed()); } } else if (tag == "CATEGORIES") { QDomNode nn = i.firstChild(); for (; !nn.isNull(); nn = nn.nextSibling()) { QDomElement ee = nn.toElement(); if (ee.isNull()) continue; if (ee.tagName().toUpper() == "KEYWORD") v.d->categories << ee.text().trimmed(); } } else if (tag == "NOTE") v.d->note = i.text().trimmed(); else if (tag == "PRODID") v.d->prodId = i.text().trimmed(); else if (tag == "REV") v.d->rev = i.text().trimmed(); else if (tag == "SORT-STRING") v.d->sortString = i.text().trimmed(); else if (tag == "SOUND") { v.d->sound = QCA::Base64().stringToArray(subTagText(i, "BINVAL").replace("\n", "")).toByteArray(); v.d->soundURI = subTagText(i, "EXTVAL"); v.d->soundPhonetic = subTagText(i, "PHONETIC"); } else if (tag == "UID") v.d->uid = i.text().trimmed(); else if (tag == "URL") v.d->url = i.text().trimmed(); else if (tag == "DESC") v.d->desc = i.text().trimmed(); else if (tag == "CLASS") { if (hasSubTag(i, "PUBLIC")) v.d->privacyClass = pcPublic; else if (hasSubTag(i, "PRIVATE")) v.d->privacyClass = pcPrivate; else if (hasSubTag(i, "CONFIDENTIAL")) v.d->privacyClass = pcConfidential; } else if (tag == "KEY") { // TODO: Justin, please check out this code e = i.firstChildElement("TYPE"); QString type = "text/plain"; if (!e.isNull()) type = e.text().trimmed(); e = i.firstChildElement("CRED"); if (e.isNull()) e = i.firstChildElement("BINVAL"); // case for very clever clients ;-) if (!e.isNull()) v.d->key = e.text().toUtf8(); // FIXME } } return v; } bool VCard::isEmpty() const { return !d || d->isEmpty(); } VCard VCard::makeEmpty() { VCard vcard; vcard.d = new VCardPrivate; return vcard; } // Some constructors VCard::Address::Address() { home = work = postal = parcel = dom = intl = pref = false; } VCard::Label::Label() { home = work = postal = parcel = dom = intl = pref = false; } VCard::Phone::Phone() { home = work = voice = fax = pager = msg = cell = video = bbs = modem = isdn = pcs = pref = false; } VCard::Email::Email() { home = work = internet = x400 = pref = false; } VCard::Geo::Geo() { } VCard::Org::Org() { } // vCard properties... const QString &VCard::version() const { return d->version; } void VCard::setVersion(const QString &v) { d->version = v; } const QString &VCard::fullName() const { return d->fullName; } void VCard::setFullName(const QString &n) { d->fullName = n; } const QString &VCard::familyName() const { return d->familyName; } void VCard::setFamilyName(const QString &n) { d->familyName = n; } const QString &VCard::givenName() const { return d->givenName; } void VCard::setGivenName(const QString &n) { d->givenName = n; } const QString &VCard::middleName() const { return d->middleName; } void VCard::setMiddleName(const QString &n) { d->middleName = n; } const QString &VCard::prefixName() const { return d->prefixName; } void VCard::setPrefixName(const QString &p) { d->prefixName = p; } const QString &VCard::suffixName() const { return d->suffixName; } void VCard::setSuffixName(const QString &s) { d->suffixName = s; } const QString &VCard::nickName() const { return d->nickName; } void VCard::setNickName(const QString &n) { d->nickName = n; } const QByteArray &VCard::photo() const { return d->photo; } void VCard::setPhoto(const QByteArray &i) { d->photo = i; } const QString &VCard::photoURI() const { return d->photoURI; } void VCard::setPhotoURI(const QString &p) { d->photoURI = p; } const QDate VCard::bday() const { return QDate::fromString(d->bday); } void VCard::setBday(const QDate &date) { d->bday = date.toString(); } const QString &VCard::bdayStr() const { return d->bday; } void VCard::setBdayStr(const QString &date) { d->bday = date; } const VCard::AddressList &VCard::addressList() const { return d->addressList; } void VCard::setAddressList(const VCard::AddressList &a) { d->addressList = a; } const VCard::LabelList &VCard::labelList() const { return d->labelList; } void VCard::setLabelList(const VCard::LabelList &l) { d->labelList = l; } const VCard::PhoneList &VCard::phoneList() const { return d->phoneList; } void VCard::setPhoneList(const VCard::PhoneList &p) { d->phoneList = p; } const VCard::EmailList &VCard::emailList() const { return d->emailList; } void VCard::setEmailList(const VCard::EmailList &e) { d->emailList = e; } const QString &VCard::jid() const { return d->jid; } void VCard::setJid(const QString &j) { d->jid = j; } const QString &VCard::mailer() const { return d->mailer; } void VCard::setMailer(const QString &m) { d->mailer = m; } const QString &VCard::timezone() const { return d->timezone; } void VCard::setTimezone(const QString &t) { d->timezone = t; } const VCard::Geo &VCard::geo() const { return d->geo; } void VCard::setGeo(const VCard::Geo &g) { d->geo = g; } const QString &VCard::title() const { return d->title; } void VCard::setTitle(const QString &t) { d->title = t; } const QString &VCard::role() const { return d->role; } void VCard::setRole(const QString &r) { d->role = r; } const QByteArray &VCard::logo() const { return d->logo; } void VCard::setLogo(const QByteArray &i) { d->logo = i; } const QString &VCard::logoURI() const { return d->logoURI; } void VCard::setLogoURI(const QString &l) { d->logoURI = l; } VCard VCard::agent() const { if (d->agent) { return *(d->agent); // implicit copy } return VCard(); } void VCard::setAgent(const VCard &v) { if (!d->agent) d->agent = QSharedPointer(new VCard); *(d->agent) = v; } const QString VCard::agentURI() const { return d->agentURI; } void VCard::setAgentURI(const QString &a) { d->agentURI = a; } const VCard::Org &VCard::org() const { return d->org; } void VCard::setOrg(const VCard::Org &o) { d->org = o; } const QStringList &VCard::categories() const { return d->categories; } void VCard::setCategories(const QStringList &c) { d->categories = c; } const QString &VCard::note() const { return d->note; } void VCard::setNote(const QString &n) { d->note = n; } const QString &VCard::prodId() const { return d->prodId; } void VCard::setProdId(const QString &p) { d->prodId = p; } const QString &VCard::rev() const { return d->rev; } void VCard::setRev(const QString &r) { d->rev = r; } const QString &VCard::sortString() const { return d->sortString; } void VCard::setSortString(const QString &s) { d->sortString = s; } const QByteArray &VCard::sound() const { return d->sound; } void VCard::setSound(const QByteArray &s) { d->sound = s; } const QString &VCard::soundURI() const { return d->soundURI; } void VCard::setSoundURI(const QString &s) { d->soundURI = s; } const QString &VCard::soundPhonetic() const { return d->soundPhonetic; } void VCard::setSoundPhonetic(const QString &s) { d->soundPhonetic = s; } const QString &VCard::uid() const { return d->uid; } void VCard::setUid(const QString &u) { d->uid = u; } const QString &VCard::url() const { return d->url; } void VCard::setUrl(const QString &u) { d->url = u; } const QString &VCard::desc() const { return d->desc; } void VCard::setDesc(const QString &desc) { d->desc = desc; } const VCard::PrivacyClass &VCard::privacyClass() const { return d->privacyClass; } void VCard::setPrivacyClass(const VCard::PrivacyClass &c) { d->privacyClass = c; } const QByteArray &VCard::key() const { return d->key; } void VCard::setKey(const QByteArray &k) { d->key = k; } } // namespace XMPP psi-plus-snapshots-1.4.1456/iris/src/xmpp/xmpp-im/xmpp_vcard.h000066400000000000000000000146731370065651000242120ustar00rootroot00000000000000/* * xmpp_vcard.h - classes for handling vCards * Copyright (C) 2003 Michail Pishchagin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifndef XMPP_VCARD_H #define XMPP_VCARD_H #include #include #include #include #include class QDate; namespace XMPP { class VCardPrivate; class VCard { public: VCard(); VCard(const VCard &); VCard &operator=(const VCard &); ~VCard(); QDomElement toXml(QDomDocument *) const; static VCard fromXml(const QDomElement &); bool isEmpty() const; inline bool isNull() const { return !d; } inline bool operator!() const { return !d; } inline operator bool() const { return !!d; } static VCard makeEmpty(); const QString &version() const; void setVersion(const QString &); const QString &fullName() const; void setFullName(const QString &); const QString &familyName() const; void setFamilyName(const QString &); const QString &givenName() const; void setGivenName(const QString &); const QString &middleName() const; void setMiddleName(const QString &); const QString &prefixName() const; void setPrefixName(const QString &); const QString &suffixName() const; void setSuffixName(const QString &); const QString &nickName() const; void setNickName(const QString &); const QByteArray &photo() const; void setPhoto(const QByteArray &); const QString &photoURI() const; void setPhotoURI(const QString &); const QDate bday() const; void setBday(const QDate &); const QString &bdayStr() const; void setBdayStr(const QString &); class Address { public: Address(); bool home; bool work; bool postal; bool parcel; bool dom; bool intl; bool pref; QString pobox; QString extaddr; QString street; QString locality; QString region; QString pcode; QString country; }; typedef QList
AddressList; const AddressList & addressList() const; void setAddressList(const AddressList &); class Label { public: Label(); bool home; bool work; bool postal; bool parcel; bool dom; bool intl; bool pref; QStringList lines; }; typedef QList