pax_global_header00006660000000000000000000000064134266351640014524gustar00rootroot0000000000000052 comment=3e10ee11845415c27937c7a5612f3fa2e9fac8cf psi-plus-snapshots-1.4.554/000077500000000000000000000000001342663516400155005ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/.qmake.cache.in000066400000000000000000000001401342663516400202410ustar00rootroot00000000000000top_srcdir="@@source_dir@@" top_builddir="@@build_dir@@" top_iris_builddir="@@build_dir@@/iris" psi-plus-snapshots-1.4.554/.qmake.conf000066400000000000000000000001311342663516400175160ustar00rootroot00000000000000top_srcdir=$$PWD top_builddir=$$shadowed($$PWD) top_iris_builddir=$$shadowed($$PWD)/iris psi-plus-snapshots-1.4.554/3rdparty/000077500000000000000000000000001342663516400172505ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/3rdparty/CMakeLists.txt000066400000000000000000000003201342663516400220030ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.1.0) if( USE_WEBENGINE ) include(qhttp.cmake) endif() include(qite/libqite/libqite.cmake) list(APPEND HEADERS ${qite_HEADERS}) list(APPEND PLAIN_SOURCES ${qite_SOURCES}) psi-plus-snapshots-1.4.554/3rdparty/http-parser/000077500000000000000000000000001342663516400215215ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/3rdparty/http-parser/.mailmap000066400000000000000000000007401342663516400231430ustar00rootroot00000000000000# 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.554/3rdparty/http-parser/.travis.yml000066400000000000000000000002041342663516400236260ustar00rootroot00000000000000language: c compiler: - clang - gcc script: - "make" notifications: email: false irc: - "irc.freenode.net#node-ci" psi-plus-snapshots-1.4.554/3rdparty/http-parser/AUTHORS000066400000000000000000000047061342663516400226000ustar00rootroot00000000000000# 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.554/3rdparty/http-parser/LICENSE-MIT000066400000000000000000000020651342663516400231600ustar00rootroot00000000000000Copyright 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.554/3rdparty/http-parser/Makefile000066400000000000000000000122441342663516400231640ustar00rootroot00000000000000# 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 = 8 SOREV = 1 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 -s $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(SONAME) ln -s $(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 -s $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(SONAME) ln -s $(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.554/3rdparty/http-parser/README.md000066400000000000000000000221771342663516400230110ustar00rootroot00000000000000HTTP 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.554/3rdparty/http-parser/bench.c000066400000000000000000000073121342663516400227470ustar00rootroot00000000000000/* 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.554/3rdparty/http-parser/contrib/000077500000000000000000000000001342663516400231615ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/3rdparty/http-parser/contrib/parsertrace.c000066400000000000000000000101341342663516400256370ustar00rootroot00000000000000/* 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.554/3rdparty/http-parser/contrib/url_parser.c000066400000000000000000000021771342663516400255120ustar00rootroot00000000000000#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.554/3rdparty/http-parser/http_parser.c000066400000000000000000002116511342663516400242260ustar00rootroot00000000000000/* 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 #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->http_errno = (e); \ } while(0) #define CURRENT_STATE() p_state #define UPDATE_STATE(V) p_state = (enum state) (V); #define RETURN(V) \ do { \ 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 HTTP_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. HTTP_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 { \ parser->nread += (V); \ if (UNLIKELY(parser->nread > (HTTP_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, '!', 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_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_chunked , 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) (tokens[(unsigned char)c]) #if HTTP_PARSER_STRICT #define TOKEN(c) (tokens[(unsigned char)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) ((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; } /* FALLTHROUGH */ 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; /* 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->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: { parser->flags = 0; parser->content_length = ULLONG_MAX; switch (ch) { case 'H': UPDATE_STATE(s_res_H); break; case CR: case LF: break; default: 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->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 'H': UPDATE_STATE(s_req_http_H); break; case ' ': break; 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_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: 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; } 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; } } COUNT_HEADER_SIZE(p - start); if (p == data + len) { --p; break; } 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; } /* FALLTHROUGH */ 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_general; } 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; 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: { const char* p_cr; const char* p_lf; size_t limit = data + len - p; limit = MIN(limit, HTTP_MAX_HEADER_SIZE); p_cr = (const char*) memchr(p, CR, limit); p_lf = (const char*) memchr(p, LF, limit); if (p_cr != NULL) { if (p_lf != NULL && p_cr >= p_lf) p = p_lf; else p = p_cr; } else if (UNLIKELY(p_lf != NULL)) { p = p_lf; } else { 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; /* FALLTHROUGH */ 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_chunked: parser->index++; if (parser->index > sizeof(CHUNKED)-1 || c != CHUNKED[parser->index]) { h_state = h_general; } else if (parser->index == sizeof(CHUNKED)-2) { h_state = h_transfer_encoding_chunked; } 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_general; 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; COUNT_HEADER_SIZE(p - start); if (p == data + len) --p; 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') { 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; 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 use chunked encoding and a content-length header together per the HTTP specification. */ if ((parser->flags & F_CHUNKED) && (parser->flags & F_CONTENTLENGTH)) { 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; /* FALLTHROUGH */ 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; 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 */ UPDATE_STATE(s_chunk_size_start); } 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(parser->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; 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; 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 our 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; } 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, ""); } 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; } /* FALLTHROUGH */ 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; } /* FALLTHROUGH */ 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; } /* FALLTHROUGH */ 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 = 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 = 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 = 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 = 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; 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; /* FALLTHROUGH */ 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 = 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) { 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; } psi-plus-snapshots-1.4.554/3rdparty/http-parser/http_parser.gyp000066400000000000000000000054471342663516400246070ustar00rootroot00000000000000# 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.554/3rdparty/http-parser/http_parser.h000066400000000000000000000445061342663516400242360ustar00rootroot00000000000000/* 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 8 #define HTTP_PARSER_VERSION_PATCH 1 #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 }; /* 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") /* 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 : 7; /* index into current matcher */ 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); /* 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); #ifdef __cplusplus } #endif #endif psi-plus-snapshots-1.4.554/3rdparty/http-parser/test.c000066400000000000000000003503331342663516400226530ustar00rootroot00000000000000/* 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 DUMBFUCK 2 , {.name= "dumbfuck" ,.type= HTTP_REQUEST ,.raw= "GET /dumbfuck 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= "/dumbfuck" ,.request_url= "/dumbfuck" ,.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" "Transfer-Encoding: identity\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= 3 ,.headers= { { "Accept", "*/*" } , { "Transfer-Encoding", "identity" } , { "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_BULLSHIT_AFTER_LENGTH 11 , {.name= "with bullshit after the length" ,.type= HTTP_REQUEST ,.raw= "POST /chunked_w_bullshit_after_length HTTP/1.1\r\n" "Transfer-Encoding: chunked\r\n" "\r\n" "5; ihatew3;whatthefuck=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_bullshit_after_length" ,.request_url= "/chunked_w_bullshit_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= "" } }; /* * 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 } } }; /* 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); 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; assert(parser == NULL); parser = malloc(sizeof(http_parser)); http_parser_init(parser, type); memset(&messages, 0, sizeof messages); } void parser_free () { assert(parser); free(parser); parser = NULL; } 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, strlen(test->url), 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_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) { read = parse(msg1, msg1len); 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(); parser_free(); } } 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(); parser_free(); } 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); parser_free(); /* 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: chunked\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(); parser_free(); } /* 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; read = parse(buf1, buf1_len); 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; } parser_free(); } } } 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) { parser_free(); 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(); parser_free(); } /* 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(); parser_free(); } int main (void) { parser = NULL; 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)); //// API test_preserve_data(); test_parse_url(); test_method_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: 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); //// 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); 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 / 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); // 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); 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 *dumbfuck2 = "GET / HTTP/1.1\r\n" "X-SSL-Bullshit: -----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(dumbfuck2, 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_BULLSHIT_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.554/3rdparty/qhttp.cmake000066400000000000000000000040251342663516400214130ustar00rootroot00000000000000cmake_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 ./http-parser ) find_package(Qt5 REQUIRED Core Network) set(http_parser_srcs http-parser/http_parser.c ) set(http_parser_hdrs http-parser/http_parser.h ) 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) target_link_libraries(qhttp Qt5::Core Qt5::Network) target_include_directories(qhttp PUBLIC ./qhttp/src ./qhttp/src/private ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ) psi-plus-snapshots-1.4.554/3rdparty/qhttp.pri000066400000000000000000000023361342663516400211300ustar00rootroot00000000000000PRJDIR = $$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.554/3rdparty/qhttp/000077500000000000000000000000001342663516400204105ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/3rdparty/qhttp/LICENSE000066400000000000000000000020701342663516400214140ustar00rootroot00000000000000The 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.554/3rdparty/qhttp/README.md000066400000000000000000000211131342663516400216650ustar00rootroot00000000000000# 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.554/3rdparty/qhttp/commondir.pri000066400000000000000000000012661342663516400231200ustar00rootroot00000000000000# 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.554/3rdparty/qhttp/example/000077500000000000000000000000001342663516400220435ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/3rdparty/qhttp/example/basic-server/000077500000000000000000000000001342663516400244305ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/3rdparty/qhttp/example/basic-server/README.md000066400000000000000000000017551342663516400257170ustar00rootroot00000000000000# 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.554/3rdparty/qhttp/example/basic-server/basic-server.pro000066400000000000000000000005331342663516400275400ustar00rootroot00000000000000QT += 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.554/3rdparty/qhttp/example/basic-server/main.cpp000066400000000000000000000072541342663516400260700ustar00rootroot00000000000000#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.554/3rdparty/qhttp/example/example.pro000066400000000000000000000002371342663516400242220ustar00rootroot00000000000000TEMPLATE = subdirs SUBDIRS += helloworld SUBDIRS += postcollector SUBDIRS += basic-server contains(DEFINES, QHTTP_HAS_CLIENT) { SUBDIRS += keep-alive } psi-plus-snapshots-1.4.554/3rdparty/qhttp/example/helloworld/000077500000000000000000000000001342663516400242165ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/3rdparty/qhttp/example/helloworld/README.md000066400000000000000000000025721342663516400255030ustar00rootroot00000000000000# 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.554/3rdparty/qhttp/example/helloworld/helloworld.pro000066400000000000000000000005241342663516400271140ustar00rootroot00000000000000QT += 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.554/3rdparty/qhttp/example/helloworld/main.cpp000066400000000000000000000155461342663516400256610ustar00rootroot00000000000000#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 "Hello World!\n"; char buffer[65] = {0}; qsnprintf(buffer, 64, "Hello!\nyou've sent me %d bytes!\n", size); return 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.554/3rdparty/qhttp/example/include/000077500000000000000000000000001342663516400234665ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/3rdparty/qhttp/example/include/ticktock.hxx000066400000000000000000000023241342663516400260330ustar00rootroot00000000000000#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.554/3rdparty/qhttp/example/include/unixcatcher.hpp000066400000000000000000000014241342663516400265150ustar00rootroot00000000000000#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.554/3rdparty/qhttp/example/keep-alive/000077500000000000000000000000001342663516400240655ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/3rdparty/qhttp/example/keep-alive/README.md000066400000000000000000000006141342663516400253450ustar00rootroot00000000000000# 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.554/3rdparty/qhttp/example/keep-alive/keep-alive.pro000066400000000000000000000004751342663516400266370ustar00rootroot00000000000000QT += 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.554/3rdparty/qhttp/example/keep-alive/main.cpp000066400000000000000000000152311342663516400255170ustar00rootroot00000000000000#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.554/3rdparty/qhttp/example/postcollector/000077500000000000000000000000001342663516400247375ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/3rdparty/qhttp/example/postcollector/README.md000066400000000000000000000027601342663516400262230ustar00rootroot00000000000000# 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.554/3rdparty/qhttp/example/postcollector/main.cpp000066400000000000000000000034041342663516400263700ustar00rootroot00000000000000#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 "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()); } }); }); if ( !server.isListening() ) { qDebug("failed: listening at %s!\n", qPrintable(port)); return -1; } return app.exec(); } psi-plus-snapshots-1.4.554/3rdparty/qhttp/example/postcollector/postcollector.pro000066400000000000000000000005261342663516400303600ustar00rootroot00000000000000QT += 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.554/3rdparty/qhttp/qhttp.pro000066400000000000000000000005111342663516400222670ustar00rootroot00000000000000TEMPLATE = 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.554/3rdparty/qhttp/qompoter.json000066400000000000000000000010301342663516400231430ustar00rootroot00000000000000{ "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.554/3rdparty/qhttp/qompoter.pri000066400000000000000000000036541342663516400230020ustar00rootroot00000000000000qhttp|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.554/3rdparty/qhttp/src/000077500000000000000000000000001342663516400211775ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/3rdparty/qhttp/src/private/000077500000000000000000000000001342663516400226515ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/3rdparty/qhttp/src/private/httpparser.hxx000066400000000000000000000071021342663516400255760ustar00rootroot00000000000000/** @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.554/3rdparty/qhttp/src/private/httpreader.hxx000066400000000000000000000037541342663516400255550ustar00rootroot00000000000000/** @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.554/3rdparty/qhttp/src/private/httpwriter.hxx000066400000000000000000000055511342663516400256240ustar00rootroot00000000000000/** @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.554/3rdparty/qhttp/src/private/qhttpbase.hpp000066400000000000000000000023731342663516400253620ustar00rootroot00000000000000/** 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.554/3rdparty/qhttp/src/private/qhttpclient_private.hpp000066400000000000000000000132131342663516400274530ustar00rootroot00000000000000/** 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.554/3rdparty/qhttp/src/private/qhttpclientrequest_private.hpp000066400000000000000000000030711342663516400310650ustar00rootroot00000000000000/** 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.554/3rdparty/qhttp/src/private/qhttpclientresponse_private.hpp000066400000000000000000000025241342663516400312350ustar00rootroot00000000000000/** 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.554/3rdparty/qhttp/src/private/qhttpserver_private.hpp000066400000000000000000000046131342663516400275070ustar00rootroot00000000000000/** 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.554/3rdparty/qhttp/src/private/qhttpserverconnection_private.hpp000066400000000000000000000122261342663516400315660ustar00rootroot00000000000000/** 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.554/3rdparty/qhttp/src/private/qhttpserverrequest_private.hpp000066400000000000000000000025641342663516400311230ustar00rootroot00000000000000/** 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.554/3rdparty/qhttp/src/private/qhttpserverresponse_private.hpp000066400000000000000000000034451342663516400312700ustar00rootroot00000000000000/** 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.554/3rdparty/qhttp/src/private/qsocket.hpp000066400000000000000000000061171342663516400250400ustar00rootroot00000000000000/** @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.554/3rdparty/qhttp/src/qhttpabstracts.cpp000066400000000000000000000107431342663516400247570ustar00rootroot00000000000000#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.554/3rdparty/qhttp/src/qhttpabstracts.hpp000066400000000000000000000152771342663516400247730ustar00rootroot00000000000000/** 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.554/3rdparty/qhttp/src/qhttpclient.cpp000066400000000000000000000161501342663516400242450ustar00rootroot00000000000000#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.554/3rdparty/qhttp/src/qhttpclient.hpp000066400000000000000000000155401342663516400242540ustar00rootroot00000000000000/** 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.554/3rdparty/qhttp/src/qhttpclientrequest.cpp000066400000000000000000000045551342663516400256640ustar00rootroot00000000000000#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.554/3rdparty/qhttp/src/qhttpclientrequest.hpp000066400000000000000000000040501342663516400256570ustar00rootroot00000000000000/** 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.554/3rdparty/qhttp/src/qhttpclientresponse.cpp000066400000000000000000000030271342663516400260230ustar00rootroot00000000000000#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.554/3rdparty/qhttp/src/qhttpclientresponse.hpp000066400000000000000000000046021342663516400260300ustar00rootroot00000000000000/** 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.554/3rdparty/qhttp/src/qhttpfwd.hpp000077500000000000000000000177241342663516400235670ustar00rootroot00000000000000/** 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.554/3rdparty/qhttp/src/qhttpserver.cpp000066400000000000000000000056321342663516400243000ustar00rootroot00000000000000#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.554/3rdparty/qhttp/src/qhttpserver.hpp000066400000000000000000000113311342663516400242760ustar00rootroot00000000000000/** 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.554/3rdparty/qhttp/src/qhttpserverconnection.cpp000066400000000000000000000136121342663516400263550ustar00rootroot00000000000000#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.554/3rdparty/qhttp/src/qhttpserverconnection.hpp000066400000000000000000000055751342663516400263730ustar00rootroot00000000000000/** 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.554/3rdparty/qhttp/src/qhttpserverrequest.cpp000066400000000000000000000034161342663516400257070ustar00rootroot00000000000000#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.554/3rdparty/qhttp/src/qhttpserverrequest.hpp000066400000000000000000000051241342663516400257120ustar00rootroot00000000000000/** 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.554/3rdparty/qhttp/src/qhttpserverresponse.cpp000066400000000000000000000044611342663516400260560ustar00rootroot00000000000000#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.554/3rdparty/qhttp/src/qhttpserverresponse.hpp000066400000000000000000000042221342663516400260560ustar00rootroot00000000000000/** 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.554/3rdparty/qhttp/src/src.pro000066400000000000000000000036741342663516400225220ustar00rootroot00000000000000DEPENDPATH += $$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.554/3rdparty/qhttp/utils.sh000077500000000000000000000023501342663516400221070ustar00rootroot00000000000000#!/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.554/3rdparty/qite/000077500000000000000000000000001342663516400202125ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/3rdparty/qite/LICENSE000066400000000000000000000261201342663516400212200ustar00rootroot00000000000000 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.554/3rdparty/qite/NOTICE000066400000000000000000000003041342663516400211130ustar00rootroot00000000000000Apache 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.554/3rdparty/qite/README.md000066400000000000000000000001231342663516400214650ustar00rootroot00000000000000# Qt Interactive Text Element Allows to manage interactive elements on QTextEdit. psi-plus-snapshots-1.4.554/3rdparty/qite/libqite/000077500000000000000000000000001342663516400216435ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/3rdparty/qite/libqite/CMakeLists.txt000066400000000000000000000011601342663516400244010ustar00rootroot00000000000000cmake_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.554/3rdparty/qite/libqite/libqite.cmake000066400000000000000000000006161342663516400243010ustar00rootroot00000000000000# Qt5 or above is required. set(qite_SOURCES ${CMAKE_CURRENT_LIST_DIR}/qite.cpp ${CMAKE_CURRENT_LIST_DIR}/qiteaudio.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}/qiteaudiorecorder.h ) include_directories( ${CMAKE_CURRENT_LIST_DIR} ) psi-plus-snapshots-1.4.554/3rdparty/qite/libqite/libqite.pri000066400000000000000000000004141342663516400240070ustar00rootroot00000000000000!greaterThan(QT_MAJOR_VERSION, 4):error(Qt5 or above is required) SOURCES += \ $$PWD/qite.cpp \ $$PWD/qiteaudio.cpp \ $$PWD/qiteaudiorecorder.cpp HEADERS += \ $$PWD/qite.h \ $$PWD/qiteaudio.h \ $$PWD/qiteaudiorecorder.h INCLUDEPATH += $$PWD psi-plus-snapshots-1.4.554/3rdparty/qite/libqite/libqite.pro000066400000000000000000000001671342663516400240220ustar00rootroot00000000000000TEMPLATE = lib QT += core gui multimedia widgets CONFIG += c++14 static TARGET = qite include($$PWD/libqite.pri) psi-plus-snapshots-1.4.554/3rdparty/qite/libqite/qite.cpp000066400000000000000000000270761342663516400233250ustar00rootroot00000000000000/* 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) : 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); } quint32 InteractiveText::insert(InteractiveTextFormat &fmt) { auto id = _uniqueElementId++; fmt.setProperty(InteractiveTextFormat::Id, id); _textEdit->textCursor().insertText(QString(QChar::ObjectReplacementCharacter), fmt); // TODO check if mouse is already on the element return id; } 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.554/3rdparty/qite/libqite/qite.h000066400000000000000000000073331342663516400227640ustar00rootroot00000000000000/* 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; inline InteractiveTextFormat(int objectType) { setObjectType(objectType); } inline ElementId id() const { return id(*this); } static inline ElementId id(const QTextFormat &format) { return 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); 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); quint32 insert(InteractiveTextFormat &fmt); QTextCursor findElement(quint32 elementId, int cursorPositionHint = 0); void markVisible(const InteractiveTextFormat::ElementId &id); 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; }; #endif // QITE_H psi-plus-snapshots-1.4.554/3rdparty/qite/libqite/qiteaudio.cpp000066400000000000000000000530631342663516400243420ustar00rootroot00000000000000/* 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, PlayPosition, /* in pixels */ State, MetadataState, Metadata }; enum MDState { NotRequested, RequestInProgress, Finished }; enum Flag { Playing = 0x1, MouseOnButton = 0x2, }; Q_DECLARE_FLAGS(Flags, Flag) using InteractiveTextFormat::InteractiveTextFormat; AudioMessageFormat(int objectType, const QUrl &url, 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; QVariant metaData() const; void setMetaData(const QVariant &v); MDState metaDataState() const; void setMetaDataState(MDState state); static AudioMessageFormat fromCharFormat(const QTextCharFormat &fmt) { return AudioMessageFormat(fmt); } }; Q_DECLARE_OPERATORS_FOR_FLAGS(AudioMessageFormat::Flags) AudioMessageFormat::AudioMessageFormat(int objectType, const QUrl &url, quint32 position, const Flags &state) : InteractiveTextFormat(objectType) { setProperty(Url, url); 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).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 histogramColumnWidth = qRound(baseSize); if (!histogramColumnWidth) { histogramColumnWidth = 1; } auto rightPadding = int(baseSize * 5); // elementHeight already includes 2 paddings: to the lest and to the right of button elementSize = QSize(elementHeight + histogramColumnWidth * 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) { Q_UNUSED(posInDocument); 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) { if (!autoFetchMetadata || mdState == AudioMessageFormat::RequestInProgress) { return; } // we need t query histogram. Let's check if it makes sense first. if (audioFormat.url().path().endsWith(".mka")) { // we use mka for audio messages. so it may have histogram 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 histogram file if (!nam) { nam = new QNetworkAccessManager(this); } QUrl metaUrl(audioFormat.url()); metaUrl.setPath(metaUrl.path() + ".histogram"); 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); for (auto v : QString::fromLatin1(reply->readAll()).split(',')) { 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>()) { // histogram 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 } void ITEAudioController::insert(const QUrl &audioSrc) { AudioMessageFormat fmt(objectType, audioSrc); fmt.setFontPointSize(itc->textEdit()->currentFont().pointSize()); itc->insert(fmt); } bool ITEAudioController::mouseEvent(const Event &event, const QRect &rect, QTextCursor &selected) { Q_UNUSED(rect); quint32 onButton = false; if (event.type != EventType::Leave) { onButton = isOnButton(event.pos, bgRect)? AudioMessageFormat::MouseOnButton : 0; } if (onButton) { _cursor = QCursor(Qt::PointingHandCursor); } else { _cursor = QCursor(Qt::ArrowCursor); } AudioMessageFormat format = AudioMessageFormat::fromCharFormat(selected.charFormat()); AudioMessageFormat::Flags state = format.state(); bool onButtonChanged = (state & AudioMessageFormat::MouseOnButton) != onButton; bool playStateChanged = false; bool positionSet = false; if (onButtonChanged) { state ^= AudioMessageFormat::MouseOnButton; } 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); player->setMedia(format.url()); 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 histogram already } format.setMetaData(title); cursor.setCharFormat(format); } }); // try to extract from metadata and store histogram connect(player, QOverload::of(&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 histogram. We don't expect anything else } auto sl = comment.mid(int(sizeof("AMPLDIAGSTART")), index - int(sizeof("AMPLDIAGSTART")) - 1).split(","); QList histogram; histogram.reserve(sl.size()); std::transform(sl.constBegin(), sl.constEnd(), std::back_inserter(histogram), [](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(histogram)); cursor.setCharFormat(format); }); connect(player, SIGNAL(stateChanged(QMediaPlayer::State)), this, SLOT(playerStateChanged(QMediaPlayer::State))); } //player->setVolume(0); player->play(); } else { if (player) { player->pause(); //player->disconnect(this);activePlayers.take(playerId)->deleteLater(); } } } else if (scaleRect.contains(event.pos)) { // 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) { 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 newPixelPos = decltype (lastPixelPos)(scaleFillRect.width() * (double(newPos) / double(player->duration()))); if (newPixelPos != lastPixelPos) { 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().setFlag(AudioMessageFormat::Playing, false)); audioFormat.setPlayPosition(0); cursor.setCharFormat(audioFormat); } activePlayers.take(playerId)->deleteLater(); } } ITEAudioController::ITEAudioController(InteractiveText *itc) : InteractiveTextElementController(itc) { } QCursor ITEAudioController::cursor() { return _cursor; } psi-plus-snapshots-1.4.554/3rdparty/qite/libqite/qiteaudio.h000066400000000000000000000046601342663516400240060ustar00rootroot00000000000000/* 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 typedef QList Histogram; 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: static const int HistogramCompressedSize = 100; // amount of drawn columns ITEAudioController(InteractiveText *itc); QSizeF intrinsicSize(QTextDocument *doc, int posInDocument, const QTextFormat &format); void drawITE(QPainter *painter, const QRectF &rect, int posInDocument, const QTextFormat &format); void insert(const QUrl &audioSrc); // add new media to textedit 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.554/3rdparty/qite/libqite/qiteaudiorecorder.cpp000066400000000000000000000211321342663516400260600ustar00rootroot00000000000000/* 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 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 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"); connect(_recorder, &QAudioRecorder::stateChanged, this, [this](){ if (_recorder->state() == QAudioRecorder::StoppedState && _recorder->duration() && _maxVolume) { // compress histogram.. auto volumeK = 255.0 / double(_maxVolume); // amplificator if (volumeK > 2) { volumeK = 2; // don't be mad on showing silence } auto step = histogram.size() / double(ITEAudioController::HistogramCompressedSize); QStringList columns; for (int i = 0; i < ITEAudioController::HistogramCompressedSize; i++) { int prev = int(step * i); int curr = int(step * (i + 1)); if (curr == histogram.size()) { curr = histogram.size() - 1; } int sum = 0; for (int j = prev; j <= curr; j++) { sum += quint8(histogram[j]); } columns.append(QString::number(int(sum / double(curr - prev + 1) * volumeK))); } //qDebug() << columns.join(","); histogram.clear(); histogram.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 QFile metaFile(_recorder->outputLocation().toLocalFile()+".histogram"); if (metaFile.open(QIODevice::WriteOnly)) { metaFile.write(columns.join(",").toLatin1()); metaFile.close(); } #endif } 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, histogram, _maxVolume); else handle>(buffer, quantum, histogram, _maxVolume); break; case 16: if(format.channelCount() == 2) handle(buffer, quantum, histogram, _maxVolume); else handle>(buffer, quantum, histogram, _maxVolume); break; } } else if (format.sampleType() == QAudioFormat::UnSignedInt) { switch (format.sampleSize()) { case 8: if(format.channelCount() == 2) handle(buffer, quantum, histogram, _maxVolume); else handle>(buffer, quantum, histogram, _maxVolume); break; case 16: if(format.channelCount() == 2) handle(buffer, quantum, histogram, _maxVolume); else handle>(buffer, quantum, histogram, _maxVolume); break; } } else if(format.sampleType() == QAudioFormat::Float) { if(format.channelCount() == 2) handle(buffer, quantum, histogram, _maxVolume); else handle>(buffer, quantum, histogram, _maxVolume); } else { qWarning("unsupported audio sample type: %d", int(format.sampleType())); } }); } void AudioRecorder::record(const QString &fileName) { quantum = Quantum(); histogram.clear(); histogram.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 _recorder->record(); } void AudioRecorder::stop() { _recorder->stop(); } psi-plus-snapshots-1.4.554/3rdparty/qite/libqite/qiteaudiorecorder.h000066400000000000000000000034131342663516400255270ustar00rootroot00000000000000/* 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 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 histogram qreal sum = 0.0; int count = 0; }; explicit AudioRecorder(QObject *parent = nullptr); void record(const QString &fileName); void stop(); inline auto recorder() const { return _recorder; } inline auto maxVolume() const { return _maxVolume; } // peak value of vlume over all the recording. signals: void stateChanged(); public slots: private: QAudioRecorder *_recorder = nullptr; QAudioProbe *probe; Quantum quantum; QByteArray histogram; quint8 _maxVolume; }; #endif // QITEAUDIORECORDER_H psi-plus-snapshots-1.4.554/3rdparty/qite/main.cpp000066400000000000000000000003071342663516400216420ustar00rootroot00000000000000#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.554/3rdparty/qite/main.qrc000066400000000000000000000001521342663516400216430ustar00rootroot00000000000000 recorder-microphone.png psi-plus-snapshots-1.4.554/3rdparty/qite/mainwindow.cpp000066400000000000000000000075241342663516400231020ustar00rootroot00000000000000/* 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 "ui_mainwindow.h" #include "qite.h" #include "qiteaudio.h" #include "qiteaudiorecorder.h" #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->maxVolume() / double(std::numeric_limitsmaxVolume())>::max()) > 0.1) { atc->insert(QUrl::fromLocalFile(QFileInfo(recorder->recorder()->outputLocation().toLocalFile()).absoluteFilePath())); } else { ui->textEdit->append("Prefer silence?"); } } if (recorder->recorder()->state() == QAudioRecorder::RecordingState) { recordAction->setIcon(style()->standardIcon(QStyle::SP_MediaStop)); } }); } if (recorder->recorder()->state() == QAudioRecorder::StoppedState) { recorder->record(QString("test-%1.ogg").arg(QDateTime::currentSecsSinceEpoch())); } else { recorder->stop(); } } psi-plus-snapshots-1.4.554/3rdparty/qite/mainwindow.h000066400000000000000000000023301342663516400225350ustar00rootroot00000000000000/* 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.554/3rdparty/qite/mainwindow.ui000066400000000000000000000021261342663516400227260ustar00rootroot00000000000000 MainWindow 0 0 400 300 Interactive QTextEdit 0 0 400 23 TopToolBarArea false psi-plus-snapshots-1.4.554/3rdparty/qite/qite.pro000066400000000000000000000020751342663516400217020ustar00rootroot00000000000000#------------------------------------------------- # # 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.554/3rdparty/qite/recorder-microphone.png000066400000000000000000000011551342663516400246700ustar00rootroot00000000000000PNG  IHDR sgAMA a cHRMz&u0`:pQ<bKGD#2 pHYs B(xtIME  2R;IDATH;KAWF%KeƈBAV "d k ,$Kj&U]' Ip= 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.554/CMakeLists.txt000066400000000000000000000225161342663516400202460ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.1.0) #Set automoc and autouic policy if(POLICY CMP0071) cmake_policy(SET CMP0071 OLD) message(STATUS "CMP0071 policy set to OLD") 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 set(APP_INFO_FILE "${CMAKE_CURRENT_SOURCE_DIR}/src/applicationinfo.cpp") if(EXISTS "${APP_INFO_FILE}") message(STATUS "${APP_INFO_FILE} - Found") file(STRINGS "${APP_INFO_FILE}" AINFO_LINES) string(REGEX MATCH "#define PROG_SNAME \"psi\\+\"" AINFO_LINE ${AINFO_LINES}) if(AINFO_LINE) message(STATUS "Psi+ features - FOUND") set(IS_PSIPLUS ON) project(psi-plus) else() message(STATUS "Psi+ features - NOT FOUND") set(IS_PSIPLUS OFF) project(psi) endif() endif() #Common options option( BUNDLED_IRIS "Build iris library bundled" ON ) option( ONLY_PLUGINS "Build psi-plus plugins only" OFF ) option( USE_HUNSPELL "Build psi-plus with hunspell spellcheck" ON ) option( USE_ENCHANT "Build psi-plus with enchant spellcheck" OFF ) option( ENABLE_PLUGINS "Enable plugins" OFF ) option( ENABLE_WEBKIT "Enable webkit/webengine support" ON ) option( USE_WEBKIT "Use Webkit support instead of WebEngine" OFF ) option( USE_WEBENGINE "Use WebEngine support instead of Webkit" OFF ) 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 sources found" ON ) 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 ) #Iris options option( USE_QJDNS "Use qjdns/jdns library. Disabled by default for Qt5" OFF ) option( SEPARATE_QJDNS "Build qjdns as separate lib" OFF ) if( IS_PSIPLUS ) option( PRODUCTION "Build production version" OFF ) else() option( PRODUCTION "Build production version" ON ) endif() #Windows or MXE only option( USE_MXE "Use MXE cross-compilation" OFF ) option( ENABLE_PORTABLE "Create portable version of Psi+ in win32" OFF ) #Apple only if(APPLE) option( USE_SPARKLE "Use Sparkle for APPLE builds" ON ) endif() if( USE_HUNSPELL AND USE_ENCHANT ) message(FATAL_ERROR "Both flags USE_HUNSPELL and USE_ENCHANT cannot be enabled at the same time. Please disable 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. Please disable one of them") endif() set( GLOBAL_DEPENDS_DEBUG_MODE ON ) set( CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules" ) #Detect QtWebEngine if enabled if(ENABLE_WEBKIT) add_definitions( -DWEBKIT ) find_package( Qt5Core REQUIRED ) find_package( Qt5 COMPONENTS WebEngine QUIET ) if(${Qt5Core_VERSION} VERSION_GREATER 5.6.0) if( Qt5WebEngine_FOUND AND (NOT USE_WEBKIT) ) set( USE_WEBENGINE ON ) add_definitions( -DWEBENGINE=1 ) message(STATUS "QtWebEngine - enabled") else() set( USE_WEBENGINE OFF ) endif() endif() message(STATUS "Webkit - enabled") endif() message(STATUS "SQL history - enabled since 1.4.444 version") if(USE_WEBENGINE AND USE_WEBKIT) message(FATAL_ERROR "Both flags USE_WEBENGINE and USE_WEBKIT cannot be enabled at the same time. Please disable one of them") endif() if(USE_WEBENGINE AND (NOT ENABLE_WEBKIT)) message(FATAL_ERROR "USE_WEBENGINE flag is enabled but ENABLE_WEBKIT flag is disabled. Please disable USE_WEBENGINE or enable ENABLE_WEBKIT") endif() if(USE_WEBKIT AND (NOT ENABLE_WEBKIT)) message(FATAL_ERROR "USE_WEBKIT flag is enabled but ENABLE_WEBKIT flag is disabled. Please disable USE_WEBKIT or enable ENABLE_WEBKIT") endif() 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(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}") # TODO: Check why this breaks build with GCC >= 6.0: #set(Qt5Keychain_DIR "${CMAKE_PREFIX_PATH}/lib/cmake/Qt5Keychain" CACHE STRING "Path to Qt5Keychain cmake files") set(USE_CCACHE OFF) endif() # Define LINUX on Linux like as WIN32 on Windows and APPLE on Mac OS X if(UNIX AND NOT APPLE) set(LINUX ON) add_definitions( -DHAVE_X11 -DHAVE_FREEDESKTOP -DAPP_PREFIX=${CMAKE_INSTALL_PREFIX} -DAPP_BIN_NAME=${PROJECT_NAME} ) 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(ISDEBUG) message(STATUS "CXX debug flags: ${CMAKE_CXX_FLAGS_DEBUG}") message(STATUS "C debug flags: ${CMAKE_C_FLAGS_DEBUG}") 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( ${BUNDLED_IRIS} ) find_package( Iris REQUIRED ) include_directories(${Iris_INCLUDE_DIR}) endif( ${BUNDLED_IRIS} ) set( iris_LIB iris ) add_subdirectory(src) if(EXISTS "${PROJECT_SOURCE_DIR}/psimedia" AND BUILD_PSIMEDIA) if(IS_PSIPLUS) option(USE_PSI "use psi" OFF) else() option(USE_PSI "use psi" ON) endif() option(BUILD_DEMO "build demo" OFF) add_subdirectory(psimedia) endif() else() add_subdirectory(src/plugins) endif() psi-plus-snapshots-1.4.554/COPYING000066400000000000000000000367661342663516400165550ustar00rootroot00000000000000 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.554/ChangeLog.Psi+.txt000066400000000000000000020122641342663516400207040ustar00rootroot0000000000000020180306 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.554/INSTALL000066400000000000000000000014471342663516400165370ustar00rootroot00000000000000Installation ------------ -=[ 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.554/README000066400000000000000000000035741342663516400163710ustar00rootroot00000000000000This is a special repository with snapshots for Psi+ project: https://psi-plus.com/ Current address: https://github.com/psi-plus/psi-plus-snapshots Main features of this repository: 1) Source tree is aggregated from six independent git repositories: - psi (original Psi project) - iris (from original Psi project) - libpsi (from original Psi project) - plugins (from original Psi project) - main (patches and icons from Psi+ project) - resources (sounds and icons from Psi+ project) 2) All patches from Psi+ project are applied. (Except patches from dev/ and mac/ subdirectories.) 3) Trash is removed. (MS Windows executables and non-free icons.) More details see in file generate-single-repo.sh Advantages of this repository: * Sources has been updated regularly from Psi and Psi+ projects. (Script is launched one time per hour.) * 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 could download any previous version if you need and all parts of Psi+ sources will be compatible with each other (Psi sources, Psi+ patches, plugins, 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.554/README.html000066400000000000000000000242201342663516400173230ustar00rootroot00000000000000

Psi IM – 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 IM is a capable XMPP client aimed at experienced users. Its design goals are simplicity and stability. Psi IM is highly portable and runs on MS Windows, GNU/Linux and macOS.

Psi IM is minimal but powerful. There are keybindings for just about everything, Unicode is supported throughout, and contacts are cached offline. Security is also a major consideration, and Psi IM provides it for both client-to-server (TLS) and client-to-client (GnuPG, OTR, OMEMO).

WebKit version of Psi IM 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 IM is your obvious choice.

WebEngine version of Psi IM is identical to WebKit version of Psi IM 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 in GNU/Linux.)
  • Support 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 IM fork named Psi+ was started. Project purposes are: 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 IM developers, but Psi+ still has a number of unique features. From developers point of view Psi+ is just a development branch of Psi IM which is hosted at separate git repositories and for which rolling release development model is used.

Currently the development model looks like this:

  • Psi IM 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 IM (bug fixes, new features from Psi+ project) are 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.
  • All new features are come into master branch of main Psi+ repo. Each commit increases Z part of Psi+ version to one. Sometime later they will be moved to Psi. It may require different amount of time.
  • All patches in main Psi+ repo should be prepared against master branch of psi repo.
  • After each new release of Psi IM new git tag with the same version should be created in main Psi+ repo.
  • Psi IM plugins are developed separately. Plugin API in master branches of psi and plugin repos should be in sync.
  • All translations are prepared for master branch of Psi+ and are (semi-automatically) adapted for Psi.

Developers

Lead developers

Other contributors

There are a lot of people who were involved into Psi IM 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 IM project, if you think you can do a better job with any of the Psi IM 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 IM 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 IM and Psi+ project, it is possible. See related info at official websites. Thanks!

Extra links

Have fun!

psi-plus-snapshots-1.4.554/Readme-cmake-ru.txt000066400000000000000000000241701342663516400211440ustar00rootroot00000000000000# Как собрать 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) > -DSEPARATE_QJDNS=ON использовать стороннюю библиотеку qjdns (по-умолчанию - OFF) > -DENABLE_WEBKIT=ON включить поддержку QtWebKit или QtWebengine. Если флаг включен и в системе установлены обе библиотеки и qtwebkit, и qtwebengine скрипт атоматически выберет qtwebengine. (по-умолчанию - ON) > -DUSE_WEBKIT=OFF использовать QtWebKit вместо QtWebengine при включенном флаге ENABLE_WEBKIT (по-умолчанию - OFF) > -DUSE_WEBENGINE=OFF использовать QtWebengine вместо QtWebKit. Этот флаг устанавливается автоматически, если не включен флаг USE_WEBKIT и в системе установлена библиотека Qt5Webengine>=5.6.0. (по-умолчанию - OFF) > -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) если плагинов в каталоге src/plugins нет, скрипт упадёт с ошибкой > -DONLY_PLUGINS=ON собирать только плагины не собирая саму Psi/Psi+ (по-умолчанию -OFF). Включив этот флаг, флаг ENABLE_PLUGINS включается автоматически ## Работа с плагинами: ### Следующие флаги работают только если включены флаги ENABLE_PLUGINS или ONLY_PLUGINS > -DBUILD_PLUGINS=${plugins} задать список плагинов для сборки. Чтобы собрать все плагины можно задать -DBUILD_PLUGINS="ALL" или вообще не ставить этот флаг - возможные значения для ${plugins} (можно определить по содержимому каталога plugins/generic): historykeeperplugin stopspamplugin juickplugin translateplugin gomokugameplugin attentionplugin cleanerplugin autoreplyplugin contentdownloaderplugin qipxstatusesplugin skinsplugin icqdieplugin clientswitcherplugin captchaformsplugin watcherplugin videostatusplugin screenshotplugin jabberdiskplugin storagenotesplugin extendedoptionsplugin imageplugin extendedmenuplugin birthdayreminderplugin gmailserviceplugin gnupgplugin pepchangenotifyplugin otrplugin chessplugin conferenceloggerplugin gnome3supportplugin enummessagesplugin httpuploadplugin imagepreviewplugin Пример: > -DBUILD_PLUGINS="chessplugin;otrplugin;gnome3supportplugin" > -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 > -DDEV_MODE=ON Включает цель сборки prepare-bin-libs. Этот флаг удобен для запуска Psi/Psi+ сразу после сборки при разработке. При включении этого флага скрипт ищет библиотеки зависимостей и по команде: > $ make prepare-bin-libs копирует их в каталог сборки. ### Для сборки плагина 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.554/Readme-cmake.txt000066400000000000000000000134411342663516400205170ustar00rootroot00000000000000# 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) > -DSEPARATE_QJDNS=ON to build qjdns library as separate library (default OFF) > -DENABLE_WEBKIT = ON enable support of QtWebKit or QtWebengine. If the flag is enabled and both libraries qtwebkit and qtwebengine are installed the script automatically selects qtwebengine. (default is ON) > -DUSE_WEBKIT = OFF used to force build with QtWebKit instead of QtWebengine (ENABLE_WEBKIT flag should be set ON) (default is OFF) > -DUSE_WEBENGINE = OFF use QtWebengine instead of QtWebKit. This flag is set automatically if the USE_WEBKIT flag is disabled and the Qt5Webengine>=5.6.0 library is installed. (ENABLE_WEBKIT flag should be set ON) (default is OFF) > -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 ## 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 icqdieplugin clientswitcherplugin captchaformsplugin watcherplugin videostatusplugin screenshotplugin jabberdiskplugin storagenotesplugin extendedoptionsplugin imageplugin extendedmenuplugin birthdayreminderplugin gmailserviceplugin gnupgplugin pepchangenotifyplugin otrplugin chessplugin conferenceloggerplugin gnome3supportplugin enummessagesplugin httpuploadplugin imagepreviewplugin Example: > -DBUILD_PLUGINS="chessplugin;otrplugin;gnome3supportplugin" > -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 > -DDEV_MODE=ON to enable prepare-bin-libs target. Allows to copy needed libraries to run Psi/Psi+. > -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.554/Readme-dev-cmake-ru.txt000066400000000000000000000342231342663516400217200ustar00rootroot00000000000000Описание 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 ./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 ./src/plugins/plugins.cmake - список файлов заголовков оторые подключаются в основные списки сборки ./src/plugins/variables.cmake.in - шаблон файла, который содержит основные переменные для сборки плагинов, общие для всех плагинов ./src/plugins/pluginsconf.pri.cmake.in - шаблон файла pluginsconf.pri, генерируемого при сборке ./src/plugins/CMakeLists.txt - основной скрипт управляющий плагинами содержит основные правила сборки для всех плагинов подключает каталоги generic, unix, dev ./src/plugins/generic/CMakeLists.txt: подключает каталоги плагинов, если задана переменная BUILD_PLUGINS, то подключает только заданные каталоги плагинов ./src/plugins/dev/CMakeLists.txt: подключает каталоги плагинов, если задана переменная BUILD_PLUGINS, то подключает только заданные каталоги плагинов ./src/plugins/unix/CMakeLists.txt: подключает каталоги плагинов, если задана переменная BUILD_PLUGINS, то подключает только заданные каталоги плагинов ./src/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 всё, критор дебажит плагины вместе с пси ***/ /*** в CMake скриптах проекта предусмотрено 4 типа исходников: - PLAIN_HEADERS - PLAIN_SOURCES - HEADERS - SOURCES Исходники типа PLAIN - это исходники для которых скрипты не генерируют moc-файлы. Эти файлы не обрабатываются программой moс, а передаются сразу компилятору коммандами add_executable или add_library. Исходники без префикса PLAIN обрабатываются программой moc при помощи специальных макросов qt_wrap_cpp, которые добавлены в скрипты проекта. Для файлов типа *.cpp генерируется файл типа filename.moc Для файлов типа *.h генерируется файл типа moc_filename.cpp Если класс в модуле наследован от класса QObject или содержит макрос Q_OBJECT, то он должен быть в исходниках без префикса PLAIN, а все остальные должны быть в исходниках типа PLAIN. Если вы при сборке получаете предупреждение примерно следующего содержания: file.(h|cpp) No relevant classes found, это означает, что данный файл должен быть в исходниках типа PLAIN. ***/ psi-plus-snapshots-1.4.554/TODO000066400000000000000000000174541342663516400162030ustar00rootroot00000000000000Required 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.554/admin/000077500000000000000000000000001342663516400165705ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/admin/apply_version.sh000077500000000000000000000006731342663516400220270ustar00rootroot00000000000000#!/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.554/admin/build/000077500000000000000000000000001342663516400176675ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/admin/build/Makefile000066400000000000000000000101331342663516400213250ustar00rootroot00000000000000# 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.554/admin/build/README000066400000000000000000000024031342663516400205460ustar00rootroot00000000000000Psi 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.554/admin/build/build_package.sh000077500000000000000000000120411342663516400227760ustar00rootroot00000000000000#!/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.554/admin/build/devconfig.sh000077500000000000000000000076531342663516400222050ustar00rootroot00000000000000#!/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.554/admin/build/gstbundle_gstplugins_mac000066400000000000000000000005221342663516400246770ustar00rootroot00000000000000libgstaudioconvert.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.554/admin/build/gstbundle_gstplugins_win000066400000000000000000000005441342663516400247400ustar00rootroot00000000000000libgstaudioconvert.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.554/admin/build/gstbundle_libs_mac000066400000000000000000000012361342663516400234340ustar00rootroot00000000000000libasprintf*.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.554/admin/build/gstbundle_libs_win000066400000000000000000000012761342663516400234750ustar00rootroot00000000000000libasprintf-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.554/admin/build/pack_dmg.sh000077500000000000000000000017521342663516400220000ustar00rootroot00000000000000#!/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.554/admin/build/package_info000066400000000000000000000027411342663516400222240ustar00rootroot00000000000000PACKAGES="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=http://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=http://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=http://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=http://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=http://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=http://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=http://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=http://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=http://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=http://psi-im.org/files/deps/psimedia-20120725-mac.tar.bz2 psimedia_mac_dir=psimedia-20120725-mac psi-plus-snapshots-1.4.554/admin/build/prep_dist.sh000077500000000000000000000136271342663516400222300ustar00rootroot00000000000000#!/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.554/admin/bundle_qca.sh000066400000000000000000000014241342663516400212220ustar00rootroot00000000000000#!/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.554/admin/fetch.sh000077500000000000000000000005461342663516400202250ustar00rootroot00000000000000#!/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.554/admin/git_revnumber.sh000077500000000000000000000003721342663516400220010ustar00rootroot00000000000000#!/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.554/admin/iccfix.sh000066400000000000000000000005121342663516400203670ustar00rootroot00000000000000#!/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.554/admin/merge_release_to_master.sh000077500000000000000000000006571342663516400240130ustar00rootroot00000000000000#!/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.554/admin/prune.sh000066400000000000000000000001511342663516400202520ustar00rootroot00000000000000#!/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.554/admin/psi-plus-nightly-version000077500000000000000000000022141342663516400234300ustar00rootroot00000000000000#!/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.554/admin/psibuild_mac.sh000077500000000000000000000647261342663516400216010ustar00rootroot00000000000000#!/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="git://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/src/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/src/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/src/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/src/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.554/admin/update_iconsets.sh000066400000000000000000000023741342663516400223230ustar00rootroot00000000000000#!/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.554/admin/update_options_ts.py000077500000000000000000000013501342663516400227070ustar00rootroot00000000000000#!/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.554/certs/000077500000000000000000000000001342663516400166205ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/certs/README000066400000000000000000000007701342663516400175040ustar00rootroot00000000000000This directory contains the SSL certificates shipped with 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: Linux, MacOS X and other Unices ~/.local/share/psi+/ Windows NT, 2000, XP and Server 2003 %UserProfile%\PsiData\ (usually C:\Documents and Settings\username\PsiData ) Windows 95, 98 and Me %ProgramFiles%\Psi\PsiData\ (usually C:\Program Files\Psi\PsiData ) psi-plus-snapshots-1.4.554/certs/startcom_ca.crt000066400000000000000000000034421342663516400216340ustar00rootroot00000000000000-----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.554/certs/startcom_ca_new.crt000066400000000000000000000053101342663516400225010ustar00rootroot00000000000000-----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.554/client_icons.txt000066400000000000000000000101371342663516400207140ustar00rootroot00000000000000adium adium android google#talk#user,accountandroid.com aqq aqq asterisk asterisk bayanicq bayanicq,barobin.com barracuda-im barracuda beem beem-project bitlbee bitlbee vk4xmpp simpleapps.ru,vk4xmpp bluejabb bluejabb bombus-avalon bombus,avalon bombus-avalon-old java.util.random bombus-klub klub54.wen.ru,bombusklub,jabber.pdg.pl bombus-old bombus-im.org#java bombusqd bombusmod-qd.wen.ru,bombusqd bombusqd-ng bombusng-qd.googlecode.com bombuslime bombus-im.org bombusmod bombusmod bombusmod-old bombusmod.net.ru,ex-im.name bombusng-md bombusng-md bombusng bombus-ng bombuspl bombus.pl bombusplus bombus+,voffk.org.ru bombus bombus-im.org,bombus bot jame,jabrss,pako#bot,fatal-bot,storm,sulci,sleekbot,sofserver,neutrina,yamaneko,talisman,fromhexefbbbfd0b3d0bed0b2d0bdd0bed0b1d0bed182,fromhexefbbbfcf84ceb1cebbceb9cf82cebcceb1ceb7 buddydroid buddydroid capsula-bot fin.jabber.ru centerim centerim chatopus chatopus.com chatsecure chatsecure,gibberbot coccinella coccinella conversations conversations dictbot dictbot digsby digsby dino dino.im ekg2 ekg2 emess emess emess-old emess.eqx.su erlim erlim.a7x-im.com exodus exodus freize hat.freize.org freo freomessenger.com freqbot freqbot fring google.com#1.0.0.66 gaim gaim gajim gajim gamebot j-cool.ru gismeteo weather2jabber,gismeteo.ru gizmo gizmo gloox camaya.net,gloox glu glu.net gluxibot gluxibot gtalk google.com#client gtalk-android android.com#gtalk habahaba habahaba.im hipchat hipchat.com historian-bot aspro.users.ru,historian-bot ichat ichat,apple.com icq-mobile icq#mobile imadering imadering imov imov imformer-bot imformer.ru isida-bot isida jabber.el jabber.el jabber-popov memegenerator.net,bolgenos-popov jabbim ifjabbim jabbin jabbin jabbroid jabbroid jabiru jabiru jajc jajc,just,another jamebot qabber.ru,jame-bot jappix jappix japyt ifjapyt jasmine jasmineicq.ru,jasmine#im jimm-android jimm.net.ru#android jimm-aspro jimm jitsi jitsi.org jbother jbot jubo pjc jtalk jtalk juick juick kadu kadu lampiro bluendo,lampiro leechcraft-azoth leechcraft libpurple libpurple libpurple-old pidgin.im loudmouth irssi-xmpp kopete kopete magnet2-bot magnet2.py mail.google.com mail.google.com mailruagent mrim,svn.xmpp.ru,mail.ru,list.ru,bk.ru,inbox.ru mailruagent.sis mobileagent mailruagent.mob mobile#mail#agent mandarin tomclaw.com#mandarin_im mcabber mcabber mchat mchat meebo meebo meegim code.google.com#qxmpp megafon megafonvolga.ru miranda miranda miranda-ng nightly.miranda.im,miranda-ng.org mirandahotcoffee hotcoffee monal monal.im movamessenger.sis movamessenge movim movim nekbot sleekxmpp.com#1.1.10 nimbuzz nimbuzz omnipresence omnipresence,home.gna.org om.beeonline.ru om oneteamiphone process-one.net oneteam oneteam osiris osiris ovi-chat chat.ovi.com,chat.nokia.com,nokiachat,ovi#contacts pandion pandion palringo palringo poezio slixmpp.com,poezio psiplus psi+,psi-dev psi psi,psi-im pidgin pidgin,d0bfd0b8d0b4d0b6d0b8d0bd pyicq-t pyicqt.googlecode.com,icq#transport qip qip.ru qippda pda.qip.ru,qip pda qipmobile mobileqip.ru &&qip qipinfium qip#infium,qip#2010,qip#2012,2010.qip.ru qutim qutim radio-t apps.radio-t.com riddim code.matthewwild.co.uk#riddim sameplace xmpp4moz,hyperstruct.net sapo sapo,messenger.sapo.pt sawim sawim.ru,sawim#ne siejc siemens#native#jabber#client sim sim sip-communicator sip-communicator,sip#communicator,jitsi smack-api igniterealtime.org#smack snapi-snup-bot snapi-bot.googlecode.comgithub.com sonic-revolution sonicrevolution spark spark,igniterealtime.org spectrum spectrum,binarytransport swift swift talisman-bot jabber-net.ru#talisman-bot,j-tmb.ru talkonaut talkonaut,google.com#1.0.0.84 talkgadget.google.com talkgadget.google.com talk.google.com talk.google.com,google.com#1.0.0.104 google.com google.com tkabber tkabber telepathy.freedesktop.org telepathy tigase tigase.org trillian trillian ultimate-bot ultimate-bot.googlecode.com utah-bot jabbrik.ru,jabrvista.net.ru utalk palringo.com weather.com jabber weather.com#transport webclient chat.jabbercity.ru,web-am31.dyndns-ip.com weonlydo weonlydo wod-xmpp weonlydo.com wtw wtw vacuum vacuum vkontakte vk.com,pyvk-t,vkontakte yabber yabber yaxim yaxim xabber xabber xu6-bot xu-6.jabbrik.ru zeus-bot botx.ir psi-plus-snapshots-1.4.554/cmake/000077500000000000000000000000001342663516400165605ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/cmake/modules/000077500000000000000000000000001342663516400202305ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/cmake/modules/COPYING-CMAKE-SCRIPTS000066400000000000000000000024571342663516400232360ustar00rootroot00000000000000Redistribution 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.554/cmake/modules/FindEnchant.cmake000066400000000000000000000057351342663516400234250ustar00rootroot00000000000000#============================================================================= # 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 (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 ) pkg_check_modules( PC_Enchant QUIET enchant ) set ( Enchant_DEFINITIONS ${PC_Enchant_CFLAGS} ${PC_Enchant_CFLAGS_OTHER} ) endif ( UNIX AND NOT( APPLE OR CYGWIN ) ) set ( LIBINCS enchant.h ) find_path( Enchant_INCLUDE_DIR ${LIBINCS} HINTS ${Enchant_ROOT}/include ${PC_Enchant_INCLUDEDIR} ${PC_Enchant_INCLUDE_DIRS} PATH_SUFFIXES "" if ( NOT ${WIN32} ) enchant endif ( NOT ${WIN32} ) ) find_library( Enchant_LIBRARY NAMES enchant HINTS ${PC_Enchant_LIBDIR} ${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} ) if(PC_Enchant_FOUND) set ( Enchant_VERSION ${PC_Enchant_VERSION} ) endif() endif ( Enchant_FOUND ) if( Enchant_VERSION ) mark_as_advanced( Enchant_INCLUDE_DIR Enchant_LIBRARY Enchant_VERSION ) else () mark_as_advanced( Enchant_INCLUDE_DIR Enchant_LIBRARY ) endif() psi-plus-snapshots-1.4.554/cmake/modules/FindHunspell.cmake000066400000000000000000000060131342663516400236250ustar00rootroot00000000000000#============================================================================= # 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 ( UNIX AND NOT( APPLE OR CYGWIN ) ) 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 ( NOT ${WIN32} ) ) set(HUNSPELL_NAMES hunspell${d} hunspell-1.3 hunspell-1.4 hunspell-1.5 hunspell-1.6 hunspell-1.7 libhunspell${d} ) 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 ( HUNSPELL_FOUND ) mark_as_advanced( HUNSPELL_INCLUDE_DIR HUNSPELL_LIBRARY ) psi-plus-snapshots-1.4.554/cmake/modules/FindIDN.cmake000066400000000000000000000054541342663516400224550ustar00rootroot00000000000000#============================================================================= # 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( IDN_INCLUDE_DIR AND IDN_LIBRARY ) 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( PC_IDN_FOUND ) endif( UNIX AND NOT( APPLE OR CYGWIN ) ) set( IDN_ROOT "" CACHE STRING "Path to libidn library" ) 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} libidn-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( IDN_FOUND ) mark_as_advanced( IDN_INCLUDE_DIR IDN_LIBRARY ) psi-plus-snapshots-1.4.554/cmake/modules/FindLibGcrypt.cmake000066400000000000000000000067561342663516400237500ustar00rootroot00000000000000#============================================================================= # 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( LIBGCRYPT_INCLUDE_DIR AND LIBGCRYPT_LIBRARY ) 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( PC_LIBGCRYPT_FOUND ) endif( UNIX AND NOT( APPLE OR CYGWIN ) ) 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( WIN32 ) set( LIBGCRYPT_ROOT "" CACHE STRING "Path to libgcrypt library" ) 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( LIBGCRYPT_FOUND ) mark_as_advanced( LIBGCRYPT_INCLUDE_DIR LIBGCRYPT_LIBRARY ) psi-plus-snapshots-1.4.554/cmake/modules/FindLibGpgError.cmake000066400000000000000000000070771342663516400242240ustar00rootroot00000000000000#============================================================================= # 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( LIBGPGERROR_INCLUDE_DIR AND LIBGPGERROR_LIBRARY ) 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( PC_LIBGPGERROR_FOUND ) endif( UNIX AND NOT( APPLE OR CYGWIN ) ) 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(LIBGPGERRORCONFIG_EXECUTABLE) endif( WIN32 ) set( LIBGPGERROR_ROOT "" CACHE STRING "Path to libgpg-error library" ) 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( LIBGPGERROR_FOUND ) mark_as_advanced( LIBGPGERROR_INCLUDE_DIR LIBGPGERROR_LIBRARY ) psi-plus-snapshots-1.4.554/cmake/modules/FindLibOtr.cmake000066400000000000000000000056521342663516400232360ustar00rootroot00000000000000#============================================================================= # 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( LIBOTR_INCLUDE_DIR AND LIBOTR_LIBRARY ) 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( PC_LIBOTR_FOUND ) endif( UNIX AND NOT( APPLE OR CYGWIN ) ) set( LIBOTR_ROOT "" CACHE STRING "Path to libotr library" ) 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( LIBOTR_FOUND ) mark_as_advanced( LIBOTR_INCLUDE_DIR LIBOTR_LIBRARY ) psi-plus-snapshots-1.4.554/cmake/modules/FindLibTidy.cmake000066400000000000000000000063701342663516400234010ustar00rootroot00000000000000#============================================================================= # 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( LIBTIDY_INCLUDE_DIR AND LIBTIDY_LIBRARY ) 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( PC_LIBTIDY_FOUND ) endif( UNIX AND NOT( APPLE OR CYGWIN ) ) set( LIBTIDY_ROOT "" CACHE STRING "Path to libtidy library" ) 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} libtidy${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( LIBTIDY_FOUND ) mark_as_advanced( LIBTIDY_INCLUDE_DIR LIBTIDY_LIBRARY ) psi-plus-snapshots-1.4.554/cmake/modules/FindMINIZIP.cmake000066400000000000000000000054121342663516400231540ustar00rootroot00000000000000#============================================================================= # 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 ( UNIX AND NOT( APPLE OR CYGWIN ) ) 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 ( NOT ${WIN32} ) ) 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 ( MINIZIP_FOUND ) mark_as_advanced( MINIZIP_INCLUDE_DIR MINIZIP_LIBRARY ) psi-plus-snapshots-1.4.554/cmake/modules/FindPsiPluginsApi.cmake000066400000000000000000000053071342663516400245670ustar00rootroot00000000000000#============================================================================= # 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. #============================================================================= if(PsiPluginsApi_INCLUDE_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 ) find_path( PsiPluginsApi_DIR NAMES "variables.cmake" PATHS ${ABS_CURRENT_DIR} ${ABS_PLUGINS_ROOT_DIR}/cmake/modules PATH_SUFFIXES src/plugins/cmake/modules share/psi/plugins share/psi-plus/plugins CMAKE_FIND_ROOT_PATH_BOTH ) find_path( PsiPluginsApi_INCLUDE_DIR NAMES "applicationinfoaccessor.h" PATHS ${ABS_CURRENT_DIR} ${ABS_PLUGINS_ROOT_DIR}/include PATH_SUFFIXES src/plugins/include share/psi/plugins/include share/psi-plus/plugins/include CMAKE_FIND_ROOT_PATH_BOTH ) 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.554/cmake/modules/FindQJDns.cmake000066400000000000000000000055641342663516400230240ustar00rootroot00000000000000#============================================================================= # 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 ( 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.554/cmake/modules/FindQJSON.cmake000066400000000000000000000053171342663516400227330ustar00rootroot00000000000000#============================================================================= # 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 ( UNIX AND NOT( APPLE OR CYGWIN ) ) 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 ( NOT ${WIN32} ) ) 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 ( QJSON_FOUND ) mark_as_advanced( QJSON_INCLUDE_DIR QJSON_LIBRARY ) psi-plus-snapshots-1.4.554/cmake/modules/FindQca.cmake000066400000000000000000000047401342663516400225440ustar00rootroot00000000000000#============================================================================= # 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(Qca_FOUND) mark_as_advanced( Qca_INCLUDE_DIR Qca_LIBRARY ) psi-plus-snapshots-1.4.554/cmake/modules/FindSparkle.cmake000066400000000000000000000015221342663516400234340ustar00rootroot00000000000000# 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.554/cmake/modules/FindXCB.cmake000066400000000000000000000051301342663516400224460ustar00rootroot00000000000000#============================================================================= # 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 ( UNIX AND NOT( APPLE OR CYGWIN ) ) 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 ( XCB_FOUND ) mark_as_advanced( XCB_INCLUDE_DIR XCB_LIBRARY ) psi-plus-snapshots-1.4.554/cmake/modules/FindZLIB.cmake000066400000000000000000000054741342663516400226050ustar00rootroot00000000000000#============================================================================= # 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( ZLIB_INCLUDE_DIR AND ZLIB_LIBRARY ) 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( PC_ZLIB_FOUND ) endif( UNIX AND NOT( APPLE OR CYGWIN ) ) 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( ZLIB_FOUND ) mark_as_advanced( ZLIB_INCLUDE_DIR ZLIB_LIBRARY ) psi-plus-snapshots-1.4.554/cmake/modules/fix-codestyle.cmake000066400000000000000000000007211342663516400240110ustar00rootroot00000000000000cmake_minimum_required( VERSION 3.1.0 ) find_program(ASTYLE_BIN astyle DOC "Path to astyle binary") if(ASTYLE_BIN) add_custom_target(fix-codestyle COMMAND ${ASTYLE_BIN} --options=${PROJECT_SOURCE_DIR}/psi.astylerc --exclude=${PROJECT_SOURCE_DIR}/3rdparty -R -Q *.cpp *.h *.c WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMENT "Run astyle application to fix project codestyle" VERBATIM ) endif() psi-plus-snapshots-1.4.554/cmake/modules/generate_desktopfile.cmake000066400000000000000000000023631342663516400254210ustar00rootroot00000000000000cmake_minimum_required( VERSION 3.1.0 ) if(IS_PSIPLUS) set(NAME_PREFIX "Psi+") set(EXEC_REGEXP "Exec=psi-plus %U") set(NAME_REGEXP "Name=Psi\\+") set(ICON_REGEXP "Icon=psi-plus") else() set(NAME_PREFIX "Psi") set(EXEC_REGEXP "Exec=psi %U") set(NAME_REGEXP "Name=Psi") set(ICON_REGEXP "Icon=psi") endif() set(TMP_DESK_FILE "${CMAKE_CURRENT_BINARY_DIR}/${VERBOSED_NAME}.desktop.in") set(OUT_DESK_FILE "${CMAKE_BINARY_DIR}/${VERBOSED_NAME}.desktop") file(WRITE ${TMP_DESK_FILE} "") file(READ ${DESKTOP_FILE} DESK_FILE_CONTENTS) #hack for desktop file generaion string(REGEX REPLACE "${EXEC_REGEXP}" "Exec=${VERBOSED_NAME} %U" FIX1 "${DESK_FILE_CONTENTS}") string(REGEX REPLACE "${ICON_REGEXP}" "Icon=${VERBOSED_NAME}" FIX2 "${FIX1}") if(USE_WEBENGINE) string(REGEX REPLACE "${NAME_REGEXP}" "Name=${NAME_PREFIX} Webengine" FIX3 "${FIX2}") elseif(ENABLE_WEBKIT) string(REGEX REPLACE "${NAME_REGEXP}" "Name=${NAME_PREFIX} Webkit" FIX3 "${FIX2}") endif() if(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) message(STATUS "${OUT_DESK_FILE} file generated") psi-plus-snapshots-1.4.554/cmake/modules/get-version.cmake000066400000000000000000000054461342663516400235050ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.1.0) set(VER_FILE ${PROJECT_SOURCE_DIR}/version) set(DEFAULT_VER "1.4") #updated manually unset(APP_VERSION) unset(PSI_REVISION) unset(PSI_PLUS_REVISION) set(VERSION_OBTAINED OFF) function(obtain_psi_version VERSION_LINES) set(_VLINES ${VERSION_LINES}) string(REGEX MATCH "^[0-9]+\\.[0-9]+" _VER_LINE ${_VLINES}) if(_VER_LINE) set(_APP_VERSION ${_VER_LINE}) message(STATUS "Psi version found: ${_VER_LINE}") else() message(WARNING "Psi version not found! ${DEFAULT_VER} version will be set") set(_APP_VERSION ${DEFAULT_VER}) endif() if(_APP_VERSION) set(APP_VERSION ${_APP_VERSION} PARENT_SCOPE) set(VERSION_OBTAINED ON PARENT_SCOPE) endif() endfunction() function(obtain_psi_plus_version VERSION_LINES) set(_VLINES ${VERSION_LINES}) string(REGEX MATCHALL "^([0-9]+\\.[0-9]+\\.[0-9]+)+.+Psi:([a-fA-F0-9]+)+.+Psi\\+:([a-fA-F0-9]+)+" VER_LINE_A ${VER_LINES}) if(${CMAKE_MATCH_COUNT} EQUAL 3) if(CMAKE_MATCH_1) set(_APP_VERSION ${CMAKE_MATCH_1}) endif() if(CMAKE_MATCH_2) set(_PSI_REVISION ${CMAKE_MATCH_2}) endif() if(CMAKE_MATCH_3) set(_PSI_PLUS_REVISION ${CMAKE_MATCH_3}) 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} 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(VERSION_OBTAINED ON PARENT_SCOPE) endif() endfunction() if(NOT PSI_VERSION AND (EXISTS "${VER_FILE}")) message(STATUS "Found Psi version file: ${VER_FILE}") file(STRINGS "${VER_FILE}" VER_LINES) if(IS_PSIPLUS) obtain_psi_plus_version("${VER_LINES}") if(VERSION_OBTAINED) set(PSI_VERSION "${APP_VERSION} \(${PSI_COMPILATION_DATE}, Psi:${PSI_REVISION}, Psi+:${PSI_PLUS_REVISION}${PSI_VER_SUFFIX}\)") endif() else() obtain_psi_version("${VER_LINES}") if(VERSION_OBTAINED) if(PRODUCTION) set(PSI_VERSION "${APP_VERSION}") else() set(PSI_VERSION "${APP_VERSION} \(${PSI_COMPILATION_DATE}${PSI_VER_SUFFIX}\)") endif() endif() endif() endif() message(STATUS "${CLIENT_NAME} version set: ${PSI_VERSION}") psi-plus-snapshots-1.4.554/cmake/modules/win32-prepare-deps.cmake000066400000000000000000000427311342663516400245700ustar00rootroot00000000000000cmake_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() set(_LIBS "") set(_PATHES "") set(_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" ) endif() if(SEPARATE_QJDNS) list(APPEND PATHES "${QJDNS_DIR}bin" ) endif() endif() #Find windeployqt prorgam 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_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${WINDEPLOYQTBIN} ARGS ${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 icuin5 icuuc5 ) set( ICU_LIBS "" ) 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}/") # Qt5 libraries set(QT_LIBAS Qt5Core${D}.dll Qt5Gui${D}.dll Qt5Widgets${D}.dll Qt5Svg${D}.dll Qt5Network${D}.dll Qt5Svg${D}.dll Qt5Script${D}.dll Qt5Xml${D}.dll Qt5XmlPatterns${D}.dll Qt5Sql${D}.dll Qt5WebKit${D}.dll Qt5WebKitWidgets${D}.dll Qt5Qml${D}.dll Qt5Quick${D}.dll Qt5Positioning${D}.dll Qt5WebChannel${D}.dll Qt5Multimedia${D}.dll Qt5MultimediaWidgets${D}.dll Qt5Concurrent${D}.dll Qt5Sensors${D}.dll Qt5OpenGL${D}.dll Qt5PrintSupport${D}.dll Qt5WinExtras${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}/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}/platforms/") # set(STYLES_PLUGS qwindowsvistastyle${D}.dll ) find_psi_lib("${STYLES_PLUGS}" "${QT_PLUGINS_DIR}/styles/" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/styles/") # set(BEARER_PLUGS qgenericbearer${D}.dll qnativewifibearer${D}.dll ) find_psi_lib("${BEARER_PLUGS}" "${QT_PLUGINS_DIR}/bearer/" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/bearer/") # set(GENERIC_PLUGS qtuiotouchplugin${D}.dll ) find_psi_lib("${GENERIC_PLUGS}" "${QT_PLUGINS_DIR}/generic/" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/generic/") # set(ICONENGINES_PLUGS qsvgicon${D}.dll ) find_psi_lib("${ICONENGINES_PLUGS}" "${QT_PLUGINS_DIR}/iconengines/" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/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}/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}/mediaservice/") # set(PLAYLISTFORMATS_PLUGS qtmultimedia_m3u${D}.dll ) find_psi_lib("${PLAYLISTFORMATS_PLUGS}" "${QT_PLUGINS_DIR}/playlistformats/" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/playlistformats/") # set(PRINTSUPPORT_PLUGS windowsprintersupport${D}.dll ) find_psi_lib("${PRINTSUPPORT_PLUGS}" "${QT_PLUGINS_DIR}/printsupport/" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/printsupport/") # set(SQLDRIVERS_PLUGS qsqlite${D}.dll ) find_psi_lib("${SQLDRIVERS_PLUGS}" "${QT_PLUGINS_DIR}/sqldrivers/" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/sqldrivers/") # if(KEYCHAIN_LIBS) set(KEYCHAIN_LIBS qt5keychain.dll libqt5keychain.dll ) find_psi_lib("${KEYCHAIN_LIBS}" "${PATHES}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/") endif() # Qt translations file(GLOB QT_TRANSLATIONS "${QT_TRANSLATIONS_DIR}/qt_*.qm") foreach(FILE ${QT_TRANSLATIONS}) if(NOT FILE MATCHES "_help_") copy(${FILE} "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/translations/" ${LIBS_TARGET}) endif() endforeach() file(GLOB QT_TRANSLATIONS "${QT_TRANSLATIONS_DIR}/qtbase_*.qm") foreach(FILE ${QT_TRANSLATIONS}) copy(${FILE} "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/translations/" ${LIBS_TARGET}) endforeach() endif() # psimedia if(EXISTS "${PSIMEDIA_DIR}") set(PSIMEDIA_LIB_NAME "libgstprovider${D}.dll") if(MSVC) set(PSIMEDIA_LIB_NAME "gstprovider${D}.dll") endif() find_program(PSIMEDIA_PLUGIN ${PSIMEDIA_LIB_NAME} PATHS "${PSIMEDIA_DIR}") if(NOT "${PSIMEDIA_PLUGIN}" STREQUAL "PSIMEDIA_PLUGIN-NOTFOUND") get_filename_component(PSIMEDIA_PATH "${PSIMEDIA_PLUGIN}" DIRECTORY) message("library found ${PSIMEDIA_PLUGIN}") copy(${PSIMEDIA_PLUGIN} "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/" ${LIBS_TARGET}) set(PSIMEDIA_FOUND ON) endif() endif() # psimedia deps if(PSIMEDIA_FOUND) if(NOT MSVC) find_program(PSIMEDIA_DEPS_PATH "libgstvideo-0.10-0.dll" PATHS ${GST_SDK}/bin ) if(NOT "${PSIMEDIA_DEPS_PATH}" STREQUAL "PSIMEDIA_DEPS_PATH-NOTFOUND") get_filename_component(PSIMEDIA_DEPS_DIR ${PSIMEDIA_DEPS_PATH} DIRECTORY) endif() endif() set(PSIMEDIA_DEPS libjpeg-9.dll libgettextlib-0-19-6.dll libogg-0.dll libtheoradec-1.dll libgettextpo-0.dll liborc-0.4-0.dll libtheoraenc-1.dll libasprintf-0.dll libgettextsrc-0-19-6.dll liborc-test-0.4-0.dll libvorbis-0.dll libcharset-1.dll libgio-2.0-0.dll libspeex-1.dll libvorbisenc-2.dll libglib-2.0-0.dll libgthread-2.0-0.dll libspeexdsp-1.dll libvorbisfile-3.dll libffi-6.dll libgmodule-2.0-0.dll libgobject-2.0-0.dll libintl-8.dll libtheora-0.dll libgstapp-0.10-0.dll libgstaudio-0.10-0.dll libgstbase-0.10-0.dll libgstcdda-0.10-0.dll libgstcontroller-0.10-0.dll libgstdataprotocol-0.10-0.dll libgstfft-0.10-0.dll libgstinterfaces-0.10-0.dll libgstnet-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 libgstrtsp-0.10-0.dll libgstsdp-0.10-0.dll libgsttag-0.10-0.dll libgstvideo-0.10-0.dll ) if(MSVC) set(PSIMEDIA_DEPS libffi-7.dll libgio-2.0-0.dll libglib-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 libharfbuzz-0.dll libiconv-2.dll libintl-8.dll libjpeg-8.dll libogg-0.dll libopus-0.dll liborc-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 libz.dll ) set(GSTREAMER_PLUGINS libgstapp.dll libgstaudioconvert.dll libgstaudiomixer.dll libgstaudioresample.dll libgstcoreelements.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 "${GST_SDK}/bin") set(GSTREAMER_PLUGINS_DIR "${GST_SDK}/lib/gstreamer-1.0") set(GST_PLUGINS_OUTPUT "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/lib/gstreamer-1.0/") else() set(GSTREAMER_PLUGINS_DIR "${PSIMEDIA_DIR}/gstreamer-0.10") file(GLOB GSTREAMER_PLUGINS "${GSTREAMER_PLUGINS_DIR}/*.dll") set(GST_PLUGINS_OUTPUT "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/gstreamer-0.10/") 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 libgstdirectsoundsrc.dll libgstplayback.dll libgstvideoscale.dll libgstaudioconvert.dll libgstjpeg.dll libgstrtp.dll libgstvolume.dll libgstaudiomixer.dll libgstlevel.dll libgstrtpmanager.dll libgstvorbis.dll libgstaudioresample.dll libgstogg.dll libgsttheora.dll libgstwasapi.dll libgstcoreelements.dll libgstopus.dll libgstvideoconvert.dll libgstwinks.dll libgstdirectsoundsink.dll libgstopusparse.dll libgstvideorate.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}/lib/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() # other libs and executables set( LIBRARIES_LIST libgcc_s_sjlj-1.dll libgcc_s_dw2-1.dll libgcc_s_seh-1.dll libstdc++-6.dll libwinpthread-1.dll gpg.exe libgcrypt-11.dll libgcrypt-20.dll libcrypto-1_1.dll libcrypto-1_1-x64.dll libssl-1_1.dll libssl-1_1-x64.dll libotr.dll libotr-5.dll libsignal-protocol-c.dll libtidy.dll libzlib.dll zlib1.dll libgpg-error-0.dll libidn-11.dll libidn-12.dll libhunspell.dll libhunspell-1.3-0.dll libhunspell-1.4-0.dll libhunspell-1.5-0.dll libhunspell-1.6-0.dll libeay32.dll libqca-qt5${D}.dll ssleay32.dll libxslt-1.dll ) if(MSVC) list(APPEND LIBRARIES_LIST otr${D}.dll tidy${D}.dll zlib${D}.dll idn${D}.dll qca-qt5${D}.dll ) endif() if(USE_MXE) list(APPEND LIBRARIES_LIST libgpg-error6-0.dll libbz2.dll libfreetype-6.dll libglib-2.0-0.dll libharfbuzz-0.dll libharfbuzz-icu-0.dll libiconv-2.dll libintl-8.dll libjasper.dll libjasper-1.dll libjpeg-9.dll liblcms2-2.dll liblzma-5.dll liblzo2-2.dll libmng-2.dll libpcre16-0.dll libpcre2-16-0.dll libpcre-1.dll libpng16-16.dll libssp-0.dll libsqlite3-0.dll libtiff-5.dll libwebp-5.dll libwebp-7.dll libwebpdecoder-1.dll libwebpdemux-1.dll libwebpdecoder-3.dll libwebpdemux-2.dll libxml2-2.dll ) endif() if(SEPARATE_QJDNS) list(APPEND LIBRARIES_LIST libqjdns.dll libjdns.dll ) endif() find_psi_lib("${LIBRARIES_LIST}" "${PATHES}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/") # qca and plugins set(QCA_PLUGINS libqca-ossl${D}.dll libqca-gnupg${D}.dll ) if(MSVC) list(APPEND QCA_PLUGINS qca-ossl${D}.dll qca-gnupg${D}.dll ) endif() find_psi_lib("${QCA_PLUGINS}" "${PATHES}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/crypto/") endif() psi-plus-snapshots-1.4.554/configure000077500000000000000000002744441342663516400174260ustar00rootroot00000000000000#!/bin/sh # # Generated by qconf 2.0 ( http://delta.affinix.com/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 ;; --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-qdbus) QC_DISABLE_qdbus="Y" shift ;; --disable-keychain) QC_DISABLE_keychain="Y" shift ;; --enable-webkit) QC_ENABLE_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 ;; --disable-growl) QC_DISABLE_growl="Y" shift ;; --with-growl=*) QC_WITH_GROWL=$optarg shift ;; --enable-whiteboarding) QC_ENABLE_whiteboarding="Y" shift ;; --disable-xss) QC_DISABLE_xss="Y" shift ;; --disable-aspell) QC_DISABLE_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 ;; --disable-hunspell) QC_DISABLE_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 ;; --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_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_DISABLE_qdbus=$QC_DISABLE_qdbus echo QC_DISABLE_keychain=$QC_DISABLE_keychain echo QC_ENABLE_webkit=$QC_ENABLE_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_DISABLE_growl=$QC_DISABLE_growl echo QC_WITH_GROWL=$QC_WITH_GROWL echo QC_ENABLE_whiteboarding=$QC_ENABLE_whiteboarding echo QC_DISABLE_xss=$QC_DISABLE_xss echo QC_DISABLE_aspell=$QC_DISABLE_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_DISABLE_hunspell=$QC_DISABLE_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 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). -----END QCMOD----- arg: debug-and-release,Build two versions, with and without debugging turned on (mac only). */ #define QC_BUILDMODE bool qc_buildmode_release = false; bool qc_buildmode_debug = false; bool qc_buildmode_separate_debug_info = false; #define PSI_DIR_NAME "psi-plus" 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; 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()); } tout << line << endl; } return true; } 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); 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; foreach (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::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() { 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()) { foreach (const QString l, libNames) if(conf->checkLibrary(s, l)) { libName = l; break; } } else { foreach (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); } } foreach(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()) { foreach(const QString &inc, incs) { conf->addIncludePath(inc); } conf->addLib(libs); } else { conf->addExtra("CONFIG += psi-minizip"); } 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()) { foreach(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, NULL); } }; #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"); 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; foreach (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 "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; foreach (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() { qc_use_http_parser = (qc_webkit_type == "qtwebengine"); 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 "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, "", NULL)) 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; foreach(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)) return false; if(conf->findPkgConfig("enchant", VersionMin, "2.0.0", &version, &incs, &libs, &other)) qc_enchant_defs += "HAVE_ENCHANT2"; 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; foreach (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"] = ""; #else vars["plugins_dir"] = conf->getenv("LIBDIR") + "/" PSI_DIR_NAME "/plugins"; vars["data_dir"] = conf->getenv("DATADIR") + "/" PSI_DIR_NAME; #endif QStringList psiFeatures; if (!qc_webkit_type.isEmpty()) { psiFeatures << qc_webkit_type; } vars["features"] = psiFeatures.join(" "); psiGenerateFile(sourceDir + "/pluginsconf.pri.in", "pluginsconf.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 "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/" PSI_DIR_NAME).arg(conf->getenv("LIBDIR"))); conf->addExtra(QString("PSI_DATADIR=%1/" PSI_DIR_NAME).arg(conf->getenv("DATADIR"))); #endif QDir(".").mkdir("src"); QFile file("src/config.h"); if ( file.open(QIODevice::WriteOnly | QIODevice::Text) ) { QTextStream stream( &file ); #ifndef Q_OS_WIN stream << "#define PSI_LIBDIR \\"" << conf->getenv("LIBDIR") << "/" PSI_DIR_NAME "\\"" << endl; stream << "#define PSI_DATADIR \\"" << conf->getenv("DATADIR") << "/" PSI_DIR_NAME "\\"" << endl; #endif stream << "#define PSI_VERSION \\"" << version << "\\"" << endl; } conf->addDefine("HAVE_CONFIG"); return true; } }; #line 1 "recursiveprl.qcm" /* -----BEGIN QCMOD----- name: Generate .prl files -----END QCMOD----- */ //---------------------------------------------------------------------------- // qc_recursiveprl //---------------------------------------------------------------------------- class qc_recursiveprl : public ConfObj { public: bool success; qc_recursiveprl(Conf *c) : ConfObj(c) {} QString name() const { return "Generate .prl files"; } QString shortname() const { return "recursiveprl"; } QString checkString() const { return "Generating .prl files ..."; } QString resultString() const { if(success) return "ok"; else return "fail"; } bool exec() { success = true; if(!writeConf()) { success = false; return false; } QFileInfo fi(qc_getenv("QC_COMMAND")); QStringList args; args += "-prl"; args += "-r"; args += fi.dir().filePath(qc_getenv("QC_PROFILE")); if(conf->doCommand(conf->qmake_path, args) != 0) { success = false; return false; } return true; } bool writeConf() { // adapted from conf4.cpp. probably a future version of qconf // should just have a function to do this so we don't have // to copy code QFile f("conf.pri"); if(!f.open(QFile::WriteOnly | QFile::Truncate)) { conf->debug(QString("Error writing %1").arg(f.fileName())); return false; } QString str; str += "# qconf\\n\\n"; QString var; var = qc_getenv("PREFIX"); if(!var.isEmpty()) str += QString("PREFIX = %1\\n").arg(var); var = qc_getenv("BINDIR"); if(!var.isEmpty()) str += QString("BINDIR = %1\\n").arg(var); var = qc_getenv("INCDIR"); if(!var.isEmpty()) str += QString("INCDIR = %1\\n").arg(var); var = qc_getenv("LIBDIR"); if(!var.isEmpty()) str += QString("LIBDIR = %1\\n").arg(var); var = 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"; if(!conf->DEFINES.isEmpty()) str += "DEFINES += " + conf->DEFINES + '\\n'; if(!conf->INCLUDEPATH.isEmpty()) str += "INCLUDEPATH += " + conf->escapedIncludes() + '\\n'; if(!conf->LIBS.isEmpty()) str += "LIBS += " + conf->escapedLibs() + '\\n'; if(!conf->extra.isEmpty()) str += conf->extra; str += '\\n'; QByteArray cs = str.toLatin1(); f.write(cs); f.close(); 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_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_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; o = new qc_recursiveprl(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'/' pathes 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; } 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; 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; } 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); if(!s.isEmpty()) { if(!checkHeader(s, incname)) return false; inc = s; } else { if(!findHeader(incname, QStringList(), &s)) return false; inc = s; } s = getenv(libvar); if(!s.isEmpty()) { if(!checkLibrary(s, libname)) return false; lib = s; } else { if(!findLibrary(libname, &s)) 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.554/doc/000077500000000000000000000000001342663516400162455ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/doc/Doxyfile.private000066400000000000000000000315041342663516400214270ustar00rootroot00000000000000# 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.554/doc/Doxyfile.public000066400000000000000000000315001342663516400212270ustar00rootroot00000000000000# 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.554/doc/Makefile000066400000000000000000000003201342663516400177000ustar00rootroot00000000000000.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.554/doc/build-mac.txt000066400000000000000000000016731342663516400206520ustar00rootroot00000000000000Building 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.554/doc/build-unix.txt000066400000000000000000000037621342663516400210760ustar00rootroot00000000000000Requirements ------------ * 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 http://delta.affinix.com/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.554/doc/build-win.txt000066400000000000000000000042521342663516400207030ustar00rootroot00000000000000Requirements ------------ * 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 http://delta.affinix.com/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.554/doc/doxygen.css000066400000000000000000000206401342663516400204360ustar00rootroot00000000000000BODY,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.554/doc/doxygen.footer.html000066400000000000000000000004171342663516400221070ustar00rootroot00000000000000
Generated on $datetime for $projectname by doxygen $doxygenversion
psi-plus-snapshots-1.4.554/doc/doxygen.header.html000066400000000000000000000004761342663516400220460ustar00rootroot00000000000000 $title psi-plus-snapshots-1.4.554/generate-single-repo.sh000077500000000000000000000177141342663516400220650ustar00rootroot00000000000000#! /bin/sh # Author: Boris Pek # License: GPLv2 or later # Created: 2012-02-13 # Updated: 2018-11-24 # 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 PATCHES_URL=https://github.com/psi-plus/main.git RESOURCES_URL=https://github.com/psi-plus/resources.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 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=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') 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') 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}" ] && \ [ "${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 "^\./configure$" | \ 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 configure README; 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 configure cp -f "${SNAPSHOTS_DIR}/configure" "${MAIN_DIR}/configure" cp -f "${SNAPSHOTS_DIR}/README" "${MAIN_DIR}/README" rsync -a "${MAIN_DIR}/psi/" "${SNAPSHOTS_DIR}/" \ --exclude=".git*" \ --exclude="/configure" \ --exclude="/README.md" \ --exclude="/README" mv "${MAIN_DIR}/configure" "${SNAPSHOTS_DIR}/configure" mv "${MAIN_DIR}/README" "${SNAPSHOTS_DIR}/README" 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 configure.exe rm iris/configure.exe rm -r win32/* rm -r 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 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/" rsync -a "${MAIN_DIR}/main/patches/haiku" "${SNAPSHOTS_DIR}/patches/" echo "* Extra patches from Psi+ project are copied." rsync -a "${MAIN_DIR}/plugins" "${SNAPSHOTS_DIR}/src/" \ --exclude=".git*" 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}/main/iconsets/system/" "${SNAPSHOTS_DIR}/iconsets/system/" echo "* Iconsets from Psi+ project are copied." rsync -a "${MAIN_DIR}/resources/sound/" "${SNAPSHOTS_DIR}/sound/" echo "* Sound files from Psi+ project are copied." mkdir -p "${SNAPSHOTS_DIR}/skins/" rsync -a "${MAIN_DIR}/resources/skins/" "${SNAPSHOTS_DIR}/skins/" echo "* Skins from Psi+ project are copied." rsync -a "${MAIN_DIR}/resources/themes/" "${SNAPSHOTS_DIR}/themes/" echo "* Themes 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/" cp -a "${MAIN_DIR}/main/app.ico" "${SNAPSHOTS_DIR}/win32/" echo "* Some files for MS Windows build 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 " configure" | \ 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} * 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.554/iconsets.qrc000066400000000000000000000605571342663516400200530ustar00rootroot00000000000000 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/logo_16.png iconsets/system/default/logo_32.png iconsets/system/default/logo_48.png iconsets/system/default/logo_64.png iconsets/system/default/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/ok.png iconsets/system/default/command.png iconsets/system/default/options.png iconsets/system/default/roster_icon.png iconsets/system/default/pgp.png iconsets/system/default/play.png iconsets/system/default/play_sounds.png iconsets/system/default/psiplus/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/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/tip.png 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_pgp.png iconsets/system/default/notification_chat_receive_pgp.png iconsets/system/default/notification_chat_send_pgp.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/cm_check.png iconsets/system/default/cm_invertcheck.png iconsets/system/default/cm_uncheck.png iconsets/system/default/psiplus/psiplus_icon.png iconsets/system/default/psiplus/action_paste_and_send.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/psiplus/crop.png iconsets/system/default/psiplus/draw.png iconsets/system/default/psiplus/frame.png iconsets/system/default/psiplus/palette.png iconsets/system/default/psiplus/pin.png iconsets/system/default/psiplus/undo.png iconsets/system/default/psiplus/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/bat.png iconsets/emoticons/default/beer.png iconsets/emoticons/default/biggrin.png iconsets/emoticons/default/blush.png iconsets/emoticons/default/boy.png iconsets/emoticons/default/brflower.png iconsets/emoticons/default/brheart.png iconsets/emoticons/default/coffee.png iconsets/emoticons/default/coolglasses.png iconsets/emoticons/default/cry.png iconsets/emoticons/default/cuffs.png iconsets/emoticons/default/devil.png iconsets/emoticons/default/drink.png iconsets/emoticons/default/flower.png iconsets/emoticons/default/frowning.png iconsets/emoticons/default/girl.png iconsets/emoticons/default/heart.png iconsets/emoticons/default/hugleft.png iconsets/emoticons/default/hugright.png iconsets/emoticons/default/icondef.xml iconsets/emoticons/default/kiss.png iconsets/emoticons/default/lamp.png iconsets/emoticons/default/mail.png iconsets/emoticons/default/music.png iconsets/emoticons/default/no.png iconsets/emoticons/default/oh.png iconsets/emoticons/default/phone.png iconsets/emoticons/default/photo.png iconsets/emoticons/default/pussy.png iconsets/emoticons/default/rainbow.png iconsets/emoticons/default/smile.png iconsets/emoticons/default/star.png iconsets/emoticons/default/stare.png iconsets/emoticons/default/tongue.png iconsets/emoticons/default/unhappy.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/conversations.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/kadu.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/poezio.png iconsets/clients/default/psiplus.png iconsets/clients/default/psi.png iconsets/clients/default/qip.png iconsets/clients/default/qutim.png iconsets/clients/default/sawim.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.554/iconsets/000077500000000000000000000000001342663516400173275ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iconsets/activities/000077500000000000000000000000001342663516400214735ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iconsets/activities/default/000077500000000000000000000000001342663516400231175ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iconsets/activities/default/doing_chores.png000066400000000000000000000015771342663516400263020ustar00rootroot00000000000000PNG  IHDRabKGDԂ pHYsHHFk> vpAg\ƭ IDATxڍ]h[uݦib&3 ݲTV"7ST/:JqXAoe*S`эR[mڦm^O99 R?wy.~144PcO^.7\Y[97LM㿩{RݡDD(:(t|>d23'4 @@RmUwVx}`ppinD}G(Rfzs-;]/uvҭ֖}zhv0vܼYRٞ7k=se|pA d0 jۯ \ߪ^zmN\0ޠߍjP5:(cFҶF6?wyoط=F)5MDQ47n\NфX0 _1IOo?5=ѧinR1m\RG 1&Fn?W5ta"9q%zjC;+;NXެcҴ7<`JSq~w":F )jX=acnNr-r 5Vj #,g__-R**U`KL>;K8mk%lr(erre WT&m=W(@/IENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/doing_chores_buying_groceries.png000066400000000000000000000066541342663516400317220ustar00rootroot00000000000000PNG  IHDRa DiCCPICC ProfilexwTl/]"e齷.H& KYe7D"V$(bh(+X "J F;'Nw>}w(!a@P"f'0D6p(h@_63u_ -Z[3C+K;?r!YLD)c#c1 ʪ2N|bO h{yIHD.VV>RV:|{ [RF ”"MF1L1[Te'Jx%C%_%RJ#4GcӸu:(G73%Ie%e{SC add1T4UT*TTTUzUUUoScemUkS{Q7UPWߣ~A}b}9Հ5L5"5iјi<9Ъ:5MvhWh~Tfz1U.椎NTgNΌ|ݵͺHz,T NI}mPw ,tӆF -5j4oL50^l\k|g24mr6u0M713fͱBZA EEŰ%2res+}VV(٬Ԗk[c{Îjgʮ=~mCNNb&q'}d]N,:+Uʺuv^|o]5˟[7wM׍mȝ}CǃQSϓY9eu빷ػ{^>*}7l6 8`k`f 7!p2)hEPW0%8*:Qi8# z<ἶ0-AQ#p5#m"GvGѢG.7xt~g|LbLCtOlyPU܊|BLB}&:$%Zh`EꋲJO$O&&N~ rRSvLrgIsKۖ6^>!` /22fLge̜͊j&d'g* 3]9Z99"3Qhh'\(wanLHyy5yoc( z.ٴdloaqu.Yf WB+SVv[UjtCkHk2zmWbuj.Y￾HH\4uލ6W|ĺ})76T}39usocٞ---zl=TX|d[ fEqūI/WWA!1TRվS疝ӫox4صin={j-n`[k k+x\S-ۆzEjpjh8qn6Ik:8w7ޜw[nn?uݼ3V/~ڟM~nr:53(ѽȳ_ry?ZrL{퓓~מ.x:LlfW_w=7~oLM˃_uNO=|zfڛCoYož_CgggI) pHYs  IDAT8SMhQ6ئZ ZТR^"?`֛ yQPPDP/VBբEM&iҟnwI}|EEoy|3R #OoU"";Q6 >/< ]/DԾ0Js٩?k^2gȡiZWo|vFM6<떑F.P FU]"Hlx3p)NO撅[ ]g>Eu"8dlj0-C/ ΎM \QZ!(2QɼDeռ:V%02}s@mŘ6 oTH+){ᴉ'] V'+%bV5v apdْXm0m+5H.|ޗ^aw9e"aADlW%R g^:>r؈m8 QNp9qP]kGb%T0-#d=H!y޳x%8O".gժ{ @e@,Z-h~?n]5}{ZJ.b02>pn,iOZlÖ?BWG"Ç[KOOqz`m]GQl~1| p8'BAF+@M b* -q1b񨿿~SgbӐYąܿo*?{E IENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/doing_chores_cleaning.png000066400000000000000000000032141342663516400301300ustar00rootroot00000000000000PNG  IHDRagAMA7iCCPICC ProfilexTkA6n"Zkx"IYhE6bk Ed3In6&*Ezd/JZE(ޫ(b-nL~7}ov r4 Ril|Bj A4%UN$As{z[V{wwҶ@G*q Y<ߡ)t9Nyx+=Y"|@5-MS%@H8qR>׋infObN~N>! ?F?aĆ=5`5_M'Tq. VJp8dasZHOLn}&wVQygE0  HPEaP@<14r?#{2u$jtbDA{6=Q<("qCA*Oy\V;噹sM^|vWGyz?W15s-_̗)UKuZ17ߟl;=..s7VgjHUO^gc)1&v!.K `m)m$``/]?[xF QT*d4o(/lșmSqens}nk~8X<R5 vz)Ӗ9R,bRPCRR%eKUbvؙn9BħJeRR~NցoEx pHYs  4IDAT8MkTWƿeΙK&K2qX*1"K+)}P` *>C"}E|-AaĊFdL s̜ޮsLy8lo}{m&bZ1%,'X֢C2v:5{-f />Ysۊ,ߗ_CPTL د.]IGJ/𽱂aW6l!k03PMV-~PR}̌YzZ”N}PS\Z]mYp>3W"tA:X;'qZ$c&.N,r#wlg;ǰ-{+;V-]L4ruQ@7G!# uj::nյ6:c(l.T\ŤRN4~Ĉx^5ECxrj6bt0;zm2>RYEȧ4ua\I`y &Cz#a*@jIGdD=Hi.v[$=n0m^LjTr=!z BdD/~0/yVKW؁^oK<^ҔI5l1sEữǦfj&#7=4cc[ŰFf*IENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/doing_chores_cooking.png000066400000000000000000000015271342663516400300060ustar00rootroot00000000000000PNG  IHDR(-SPLTE222qqq~~cccVWWOOOZ[[TYY(% {{{@1yttH55jCAAA8`aa///JJJcccQQX>>GJLN9<`TŶ,,-if0445::knn{gf...pkD6]]]MII!!!;;;*]a````97pU222HHHfffod333iZnd ???vvv{zrrr;,BBBooo~}5':%D><.-!fCByyyQ8VEtRNS}Y:$P8"S<*Pu0IDATx^ESoQڶmkֶm۶Wlm:/~8'N_n!^7>GgsY@)(x}lZ k 2*=3.ĥյ˫_HJf9OR,1 2`Z=3_iu-B=8KjۻbJāҲʪjqjQQ3m}IENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/doing_chores_doing_maintenance.png000066400000000000000000000016161342663516400320160ustar00rootroot00000000000000PNG  IHDRagAMA7bKGDC pHYsHHFk>IDAT8ˍ[HSo;ghIt5[`9fFRCA$Y=t{"4"I,ʬuӆN3:ks7szF}_$7W7`0,Tq /,ϝ ###?$1kj(55p4xb6_x{ִyvl2a:2l?is1^yALMTB)TY";::%=HP*mp[YyShPo[.'I&q!==]R  <{]lM$yA((OOg4==ƦCZuZaygeE)6ɲ%H84~-"HIRP3+EF6<A_oή[5),5ʊ gtğ ?~bAۀK~sesdB|"6CSL':m]Y9 HL&S`(F!rA-%ƾ`9{8:Ü)E] 3}E˅[7khis#[򋑅f1f#Z=P]fR}]H:FH}fNv,r%Wn>ԯ[& ujnW.%3,R N.im{9ؼn4*DOJQ_$}oN :m]aB:'N?s<zRb5H3o9G"zTXtSoftwarexsLOJUMLO JML/Ԯ MIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/doing_chores_doing_the_dishes.png000066400000000000000000000011721342663516400316500ustar00rootroot00000000000000PNG  IHDRaAIDATxc`)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/>IENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/doing_chores_doing_the_laundry.png000066400000000000000000000016171342663516400320530ustar00rootroot00000000000000PNG  IHDRasRGBbKGD pHYsaa?itIME8p"tEXtCommentCreated with GIMP on a MacwCIDAT8u]hTgٍ V,)n,A4H  +E)RHDD!FED6 b45&ٟ#jfxfxww[|*ZϷb5w7ԋ­H>uOOUKMub9sm͊F/%H]7}ӝ ' U_)tG1U+80Hw|ogY);^zkX]rmpi MFu0'Sl @"Yʄm|{P@bjē bs}Д1lD:qJ#~9X|6bׄ"sf"@y^M{Jhvf^5Ռ` CcdSd6~AGáɼQ˯gז^5 iqoSЇWƉ_zh9",NY]n k׍mGPatxƫks."i.Ǎ3Ci45|3SqJx&Ö$ 4cr~eM O|q\ 2Br/q9&^ +`;PI<}CP**zjU;--QR>8+WH,?CQP OAb/oԂ] ;I*lX58bQ>Q(Fͼ`HIQlIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/doing_chores_gardening.png000066400000000000000000000015541342663516400303130ustar00rootroot00000000000000PNG  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 IDATx^=v@7Uڶm۶m$6~]^TJpB H35Z@ހL^2.B""`H$)y.XH#"(޿A9OU_ںdžFh懖֗׶.޾~rn`xFq8%N ̞ >߼wKB@kWZ>~s0é IENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/doing_chores_running_an_errand.png000066400000000000000000000015631342663516400320460ustar00rootroot00000000000000PNG  IHDRa:IDATxu}LW[Ƣf4J}EfbBB2X[7؀f%*:`TB^8+Xv@-tUSl)!-(@qR}Rͅq;yyCDq Ѳ٣O"=ky3eJ]ţGڳ̪7?p2 -[hQqhI\k}'=8|ɷ{}:V-q,tf2W5]rPS+넏{LcRgβݶ[bxmHK4Gp830^#h_S!t찚J|pf"$e6 P=JPp_0螀6tP6fuf3;~Ʀ@A _?G4̙{ϣsIZX>-]D@źoWo311(ւ#zwNཚm}7Aؽ.x]E8 &zg(c\8p4% ZaC(RaCD=֚8L ]YD!3qi☁O_~Lg.~Fzz}ؾP/$ۺxhnI5(*tn]uA<ϻ\!'z'BِB˺>BAaUFJWW͕=֐Vl"t 2 aRwqך31X8ܥ[3[GEۉJ_llTv)T짢O'zM-Q{Yt%f>82/.GIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/doing_chores_walking_the_dog.png000066400000000000000000000016651342663516400315050ustar00rootroot00000000000000PNG  IHDRa|IDATx]SkLW>[2,0&ۚb\Zh5hj-RJd)͹(]`^hV7apAU e*ހ~gkY&O7y缗B/;vjky<;_|f5~v9grڎC9S{mIvZ&09%i;p^6bvR>BӅJRF4罊{ 8U=qw囅j2b)x`6̻h־]PQxKp<`%N8hoiĠi)x֤[Fz~.D<>c!"'H4p$(M,OXTŅA\demSdʯ_q~?3Lv:غ5&f^g' ,miQٻʱ,ss|0ߏh8J$,O4/%GDBJ$GFVLqahh5JhJS}8ܙ˽ss9bAD0<tXoB$]Pv2k."rK&֜ (O hM*&Ҋ.j&?z׶<Xӕj!%LI$ءO$lEY*DbYJze%́K}]7i.#ev)О1Tw0Ѷ)L[y}.D*Ζm):^ZE5lsnnbgy u5%icvr+&ea,/Chy孁Z3-D ʧT2gFnc|K7+:Q *oxA|yBe*q3> :h+B.p; vr_03Ql6z;GѶh4fD(QxA邊IENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/drinking_having_a_beer.png000066400000000000000000000031601342663516400302630ustar00rootroot00000000000000PNG  IHDRaiCCPICC ProfilexTkA6n"Zkx"IYhE6bk Ed3In6&*Ezd/JZE(ޫ(b-nL~7}ov r4 Ril|Bj A4%UN$As{z[V{wwҶ@G*q Y<ߡ)t9Nyx+=Y"|@5-MS%@H8qR>׋infObN~N>! ?F?aĆ=5`5_M'Tq. VJp8dasZHOLn}&wVQygE0  HPEaP@<14r?#{2u$jtbDA{6=Q<("qCA*Oy\V;噹sM^|vWGyz?W15s-_̗)UKuZ17ߟl;=..s7VgjHUO^gc)1&v!.K `m)m$``/]?[xF QT*d4o(/lșmSqens}nk~8X<R5 vz)Ӗ9R,bRPCRR%eKUbvؙn9BħJeRR~NցoEx pHYs B(x(IDAT8MۋIOU5ȨeVV݅AAA_'fEۀİȮ7e޼ #8%F\ttwUYU^FsnYSSSj-Rcf5;GoZĮ MOOLډT:Ͷxϳ(DJЊm7\Gcc5P(ˇ~sR2r;,sDQ gfɔaMu>3<#Pg0(B1&$0l-v|Bw=5NQGF܄T)IG% \ڪHR `zzD|υ( CH?㺫V:!&@b:炙. do;2R(j|r2_u,Ƞ T%Z׋infObN~N>! ?F?aĆ=5`5_M'Tq. VJp8dasZHOLn}&wVQygE0  HPEaP@<14r?#{2u$jtbDA{6=Q<("qCA*Oy\V;噹sM^|vWGyz?W15s-_̗)UKuZ17ߟl;=..s7VgjHUO^gc)1&v!.K `m)m$``/]?[xF QT*d4o(/lșmSqens}nk~8X<R5 vz)Ӗ9R,bRPCRR%eKUbvؙn9BħJeRR~NցoEx pHYs  [IDAT8MRmH[g>{׍I헋ZMj?0j":d1Z )B:_-Jc([[e*&퇵K-6Fs5Ms}K#9|E{eY\duvpLFڔ`[S}#C# ~)&ٺs?ojl.%$ !6yy,( jҋɩ@  BW+p[q*Z$`StSsTV$q@3LNjpA`I,ҟ)%kmS?okyj[GO|UH@:ucAHAn1`|xan~1[,u7f@#jm*"eRo6AEAHBu/,_o _rlDKoJ I"$I_C+xN >ϕ}NjÝ;4(cFf8jPJIe2p+/8NAQhTcyV*RRBX#UTFguU2E5NWq-׋infObN~N>! ?F?aĆ=5`5_M'Tq. VJp8dasZHOLn}&wVQygE0  HPEaP@<14r?#{2u$jtbDA{6=Q<("qCA*Oy\V;噹sM^|vWGyz?W15s-_̗)UKuZ17ߟl;=..s7VgjHUO^gc)1&v!.K `m)m$``/]?[xF QT*d4o(/lșmSqens}nk~8X<R5 vz)Ӗ9R,bRPCRR%eKUbvؙn9BħJeRR~NցoEx pHYs  IDAT8mR]HTQs{w]GM|h{)%)@Pz !\Bh~ʈ@!|L)1롵 K%Un{wu9g曙B ;0 r"p8<6 *,6#@許SD~5ĮZmltiiah{$J =Mӎ-hP(nP! 3Cy'*ݝ¼YʛGRI52r/\mig+΄ZWUm˟|>'L@o祎φ_Bv SB, \ zIݚ]˫'6 vpAg\ƭIDAT8˥KoU@7s=vlqJDJRT-i ",/ٰc `Q$ VR5-Xy;O;3׽,vYDK}jv %h McuͿʶ훋o ܜ2Hi䬇f7@n>rs!s#3TneokW'p*SA/O AMZ A[e8s\>f/[^+\?Htkʅ<)hZ/ >+ľ(g6yuzʅ c[ep jB―#CH^Fwa\:'1j!4,r?;&ӣmVA"@d6tY?4:$J7>p'b3aR:dmXPei@)\&l`V ɗzjo~}tH~ 4UdowՊ%x )BBy #J%xs9BJ.{흥SQ;cQ& MHޤ B+Ժ+ v* c5/+bn̑ 5z:{̽f1|V;вW})KWy',o.v( luӺnHHu:pODV]m( Ԇj+R߶"8P:lp s*bIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/eating_having_a_snack.png000066400000000000000000000016301342663516400301070ustar00rootroot00000000000000PNG  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)ܴHIDATx^mϳvṶmm۶m۶&_7R6y1|PHer*}IC'No00-WӇGv4v }}_݇GDF!HSRGF'&32вWVrr+`gwoҲʪj:ohlj&CZZک PEe#65IENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/eating_having_breakfast.png000066400000000000000000000016051342663516400304540ustar00rootroot00000000000000PNG  IHDRaLIDATxڅSIl[U}E X Ă,a !6ݰ!"EEU$D'Piڄ18N\v4CbQxg;!+T JWwsw/cc3sݨnF}vxxiaY6/#ůCQҬAY5Th_?v{Eyjm}\kUdNi8Kww: G| BL [n6l_y ?`iya;X]e(gQ.dЬਸ਼ jp4lV@׺f4*LYqV!hStPJYRCTICA!TeD^(-ԋ HIH~UgܝV1V9lFN8V |( 8xD[HR3SǑ|BbjB]0mńb>KuY$QP"<:jFGJʏ ᛏ#D\Bl|IӬv"RS~o$§Y>Am $b(ak4U8W>W?BQSk\;u1j)|ןBu_& Ѓ8u󗇾:g>2~ U!AqsHXϐS@-}}_xW,(aԅ5\T}M3FZΏnruͼ7pPOmY򮛺,FPₘus_kΠ f|彐 \b6Q9tBߺ~G w\>r.кmKWd+r>'"ϓAIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/eating_having_dinner.png000066400000000000000000000014761342663516400277770ustar00rootroot00000000000000PNG  IHDRaIDATxڅ[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!xިIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/eating_having_lunch.png000066400000000000000000000015661342663516400276310ustar00rootroot00000000000000PNG  IHDRa=IDATxuS}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QCIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/exercising.png000066400000000000000000000071161342663516400257720ustar00rootroot00000000000000PNG  IHDRa iCCPICC Profilexy8ό}߲eCBeX2cZDltMBI\DrQ\s<9}}`H8H!ۘ ]L`l@`A$}++ H!M >aҭ[; F9! }V>8B@:4`p '٠!΅{Khu4zl`%|0M@lja`]tbIdHz>ꡆ[@1$O[j@rٿe6{ RQsc7a|wwUZO:;i;DCtf}ׂ -[rz ,c &f6NmR>W N sMFpJ*!JEI?<*3|ч UU԰4ҏ?l^: =w0 c 3ךt-ZpZ?mejmo wHuإlk.Aa ̬2vb;g@w"bK䤠 JNpA5$ixŅȧQ%Ԙ{Y/ƻ_>}x*Օđk IWRΤ*໱6^qr:S13kv睢LNhFR?8\X8oE K3ϗt?)/UQ}zBQ^lzyCmŋyu7.7F75kyoþ{tuuvuqus|go;`5hlްGߑBcO&;>*ΛI 1¢Ʒˍ<^:ryVvί?v%vwAFhDd.ey˺ˎ8IJ~w Y(I`Ȍ薸,X"(]xZ5qx]UNR^騱1bR}gjg565P?ֱG\G6,24n1nܒy`D4%*[=z0=?ap o/ya?$ሿx.8ВlDq @DGGSbȱ pn$X^5IԿp]&*t'%rsdǬwsJr n܍~T{Rx!7"y*с̏L>,}SV[^4FŪgj굨/^M65>nlk!ri5~&ZxGGgeW~w۠^>w,kW|H{wQ1q_&F&;??Ng\ 7 ȷDm?jVk?q[:*P;2ӕG0H2131m3ײx`c`W`_4*(=$b!-+vSYB\bQ&DKqH}P+\[o:9Ƞ8D9VQUAQmBFqɓ:UR/ ]̍5MP<;fݧ*-XƞƝ1R޴m{blqVU꜄;Ci⇽}+;"/m`I m\eԕ_^zկoxɐk,Kv܀ >+MȘ{8ImimYyç+B+Tq<ܮYeta={sMȦʫ |hOvv >Tω^;,K^]a|g<ukތlܻyF W{~'/h>!zqs{|g|G ti@*Vi8X{>a{p)0 4 ,6WgwHt D-F9>hfUUlxv%Ni. n)E^#>܁dM!SFb҈HT!CÑrSV_է/h3T 0T4.2;z'RkA^x<'&"p&aMߜ%'Dͼ|ߨfeȊՊ=1yF&f4cW?[ = L¿yѓ yKL1,(u^rlW[G MTI!tFNJdQ#rp:XE e)U:oӚ3+:zX|)3S,lzO;~p;^Kz9_{D䫩32_dk5޷.\薭U> ~2Ic(m8s~di=i~ف Wcr |>,lT1N ."[^u(b!u6+IivC(#@vU~YL[MB㈦6 EH(&H˾36vN|΃gyz&|g P {`y?%!+7;1`]WVQKr-_ǵtzPCG& C@ɾ>>TQfDfQ(+HsQ9ﻝ9%w|{~9~@\ 3 u]D;wnD}0@Om aւq5`2ԕ|P cd=@@i ?w.QF(xs $ dEŰeA3FI펾gKke݁,,.p@d266u<㰋3 -xu;Y2(X:1IFM XtȻ6tLU) @mj10ԋcʳMa;BDɄ]cZ F}^Nh6}Dh^~i\mo \_YkG]~XgGU2zX8gi]YW\Nm}2*FxpJ0l;Br HV} WiM%bڒm-omcg*~pxeFW|tM˗-_[[kbwvJM͙5rr@BxoooLaKcc(=sΜIm]]]n ~t|,TvgҤI >011CX! Ė81Z^LP#:=\R-(~B}@IENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/exercising_dancing.png000066400000000000000000000013701342663516400274510ustar00rootroot00000000000000PNG  IHDRaIDATxڭmHSaǏ0$5сA+-2'K,d[eET4c[6sWؘS  (,)ZaӽKC>xx^xΏ9!qsu4Q==L~i_3EwUT}q:Cr{W+95~q;Yc\CIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/exercising_hiking.png000066400000000000000000000016261342663516400273230ustar00rootroot00000000000000PNG  IHDR(-SPLTE %%%&&&XXX###rrr###bbbXXX666'''888\\\,,,(((999&&& ???llleee$$$<<>> ///BBBPPP&&&kkkNNN'''  XXX``` 󆆆???000111999aaa^^^oooVVV===VVVWWWUUUPPP{{{vvv%%%sss ===IIIooobbb֘fffmmmlll%%%LLL,,,""" nnn ---cjSR$dEx. n Qdyd)^,* U| ]'~xr7qGwvu}_а>2:6>LNM>^9z~aq ![ .7'г?!9FұIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/exercising_jogging.png000066400000000000000000000007001342663516400274660ustar00rootroot00000000000000PNG  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 IIDATx^=5v0afff"IozIW0c Ĵ,$%ҒZclZ`v[='9tGt8嬮˫v& $n;'_zX28;; 7n97oϳ?{k==Q[[ƣxFCzK:L+1?v\j?'3!RwT&|u< )q0w`fP^w )_:dCPaDb%&ك$:1a} 4s5U,YѾy0:<}UURD?FHfȾ[+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@!,Ӝ%:ŗ pkIDATx^]c`]۶϶m۶mͦηȠ3SOFT_fNCH;O$L6)SQ @UyMm]=Р\LW++;zze-  SL!/,.Jpxt|"~z&]\^]?HH?t|wIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/exercising_swimming.png000066400000000000000000000015451342663516400277040ustar00rootroot00000000000000PNG  IHDRa,IDATxc`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ƠEmIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/exercising_working_out.png000066400000000000000000000016131342663516400304150ustar00rootroot00000000000000PNG  IHDRaRIDATxuS{Pk:IqK*:s?$MlM"5;t$ָT~. V֭ss9ʥr736PTgӰSwy{g%GP[WG42'vvW*qNJ| lϒ.Z9Ug5mlk[q,E.d x}\^)~0Ez %%y>yx26]gO6_^m֩b 'l*%fʱ( ?駧oB{&N7er-OłA8o-Aq\Qgnm:;倰^/_p7sT==6lLЭ#G싌izr-n\ηs8jQaCN"P(cE jң3 / p!3VVkDJE≢IW\;"ėwOoF5vc0z>tw0MUIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/grooming.png000066400000000000000000000030111342663516400254410ustar00rootroot00000000000000PNG  IHDRaiCCPICC ProfilexTkA6n"Zkx"IYhE6bk Ed3In6&*Ezd/JZE(ޫ(b-nL~7}ov r4 Ril|Bj A4%UN$As{z[V{wwҶ@G*q Y<ߡ)t9Nyx+=Y"|@5-MS%@H8qR>׋infObN~N>! ?F?aĆ=5`5_M'Tq. VJp8dasZHOLn}&wVQygE0  HPEaP@<14r?#{2u$jtbDA{6=Q<("qCA*Oy\V;噹sM^|vWGyz?W15s-_̗)UKuZ17ߟl;=..s7VgjHUO^gc)1&v!.K `m)m$``/]?[xF QT*d4o(/lșmSqens}nk~8X<R5 vz)Ӗ9R,bRPCRR%eKUbvؙn9BħJeRR~NցoEx pHYs  IDAT8S=OP=$$Qˆ bgAe&s9ub.]P 1HDj;pl뽏Jg{;k@}SL,{{{(˴/qy nhX8>Yr'紹c&ckkkX]]E縹A:ʕJLE LЦӠg9fɬJ'맧pvz 8:: qei.3QQQIMV Vm9KjO&q,QJ DKTNQFlmmaii r((7Au+AL&RlllaKI r - ,MIm4 Jvq;05P0 ! %^ϡ=:EQ P=;mcKEldz*#l)cfg=hFQv4!no$`9b BTLH%Z&5bR!mY= e#.EX%kϡ~(=Fi4]sTv̥ Äk$&2db>}" @B ; 6̖f}ri3jjQp9Z'?ZWƣ󕘠@ II@̓$rkըŮR#ɰ!gөBTg \"Sjnd{E_9F mEQ@Uɼg 5(i=k&T"?s!񂢨1T!r8G ؽf:{[-T3%.X,`H 3NͯT{t֖ S*bhQQUqj~Z 0dT&ȏKy>1Z6R&7.3GIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/grooming_brushing_teeth.png000066400000000000000000000011271342663516400305410ustar00rootroot00000000000000PNG  IHDR(-SPLTE Xq*O W)N¤ѽǴîŐƨֺGprq pqr ])N [< YէέϾɶ6ZWtRNS z:fm;˱˭տÿӬԭq.K{IDATx^aЯAm۶moQE_@Up(!G&W`ݮ1^Mfv> \n׷ 7!pGcD2 #/KrzD O4VnxxBM|VO2;@b IENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/grooming_getting_a_haircut.png000066400000000000000000000030071342663516400312060ustar00rootroot00000000000000PNG  IHDR(-SiCCPICC ProfilexTkA6n"Zkx"IYhE6bk Ed3In6&*Ezd/JZE(ޫ(b-nL~7}ov r4 Ril|Bj A4%UN$As{z[V{wwҶ@G*q Y<ߡ)t9Nyx+=Y"|@5-MS%@H8qR>׋infObN~N>! ?F?aĆ=5`5_M'Tq. VJp8dasZHOLn}&wVQygE0  HPEaP@<14r?#{2u$jtbDA{6=Q<("qCA*Oy\V;噹sM^|vWGyz?W15s-_̗)UKuZ17ߟl;=..s7VgjHUO^gc)1&v!.K `m)m$``/]?[xF QT*d4o(/lșmSqens}nk~8X<R5 vz)Ӗ9R,bRPCRR%eKUbvؙn9BħJeRR~NցoEx}PLTE ƠxxxdzͿzzΚ̽@@3ɦ~~69{ m                       $     z   K z U + UȲ ܶ ŷ  V1qtRNS5c#(Knb ;. t_vPԐV`>&.S>0KSb/+XS+#϶8 VN56?{6  i0IDATx^MSQkfֶmۼm6~=gvW]x1#!#W(EIpCꠇ!4I }77ĩau"c< "t\KPQ\&%8AbLMC@AON/.ݽx2IIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/grooming_shaving.png000066400000000000000000000015541342663516400271720ustar00rootroot00000000000000PNG  IHDRa3IDATx]SmHQ)FAHh A#fRs*n2tƜ+L$l?4"Pt̘pTI%b`tK {{{<,cH$r:6삂 fRtrqXv:g Z[[#\.o4}NNN&zͦ br"d2wMMoXӷ$ ohhpG}xxXJ @[[^7`=2722"@ mh4 {F!|\>OT*#@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?*ADIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/grooming_taking_a_bath.png000066400000000000000000000013451342663516400303040ustar00rootroot00000000000000PNG  IHDRaIDATxuS_HQ>NSCqM[>-ID/AK/%DG%(($- aE+[7Lq;Ks6}V7kݹBKQ1r`΋/Y6d\rwr^" (R`)M>9O|X6Aʓ^ VTzWb>#A~&\q.O#.T )O'X`$a0I X4I7j6ivwE.88IOAE aL?I3yɜB݂6-dgA#Kҋnݰp_V '|K@c ЮhM#?!gn9]Ǡ m$d/0= A<$$e\|Qf+>0DJZѐZriCd0a|CmM6;GQ98atFۢl ^Ng_9)eC2uhfN_vE _s\~BdxWLEQ)~r儅pD5;a<čKbO.ʤ[8X }+_Fӟn·*ZEG}mEVA]1Y{CTFϙ4֗t CIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/grooming_taking_a_shower.png000066400000000000000000000020001342663516400306620ustar00rootroot00000000000000PNG  IHDRaIDATxU LG1fLHJR6$allsDŽM&1+o&m'+jo&[GLm6|Z^[wSdd|͊k8;W3`bh]=(sm7e vY^b0e֛5Uߠ((/ya][ Y1 S z*aóLINʋ,Q{ƽ^1aYZZoWo3ÐޡKSa^)lc iUͶ.Aig>I'PFl@-@m=@KUͅ@NV5ajuPjlͤ\|W ISR8df\I#_d0GX g`0{!^K֏$1.?q;r׋infObN~N>! ?F?aĆ=5`5_M'Tq. VJp8dasZHOLn}&wVQygE0  HPEaP@<14r?#{2u$jtbDA{6=Q<("qCA*Oy\V;噹sM^|vWGyz?W15s-_̗)UKuZ17ߟl;=..s7VgjHUO^gc)1&v!.K `m)m$``/]?[xF QT*d4o(/lșmSqens}nk~8X<R5 vz)Ӗ9R,bRPCRR%eKUbvؙn9BħJeRR~NցoEx pHYs  gIDAT8=SmhU~I,iԭk>M*8LW2H¦!Da9u6-2pquMҥ]Ҥxznhwp9><{ \Y=^qnZ[OF{"|^⛂Lv7w(u-)Ed̄ߎv]ǼmV/v2݈-YVvöxhIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/icondef.xml000066400000000000000000000356201342663516400252560ustar00rootroot00000000000000 Psi+ Activities - size 16 0.3 2010-07-18 http://psi-dev.googlecode.com/ 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.554/iconsets/activities/default/inactive.png000066400000000000000000000014601342663516400254300ustar00rootroot00000000000000PNG  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˔۹2IDATx^-SwAޫضf6B۶m6~nzK@\|_4`1Xĺ彞Ȑax0r`1wqiv.-/m %[O{j( hS&鉓~ )_|M% !qXS-56N&rT3ϋβlY A6&K욌n_=<:szS  V\R=럭W}?|557_4)cy#IENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/inactive_day_off.png000066400000000000000000000006241342663516400271200ustar00rootroot00000000000000PNG  IHDR(-SPLTEUUUUUUQQQPPP@@@???@@@AAA===<<<===UUUZZZvB= B& wwwXXXWWWr=uyuVVVYYY{{{~~~ tRNS u-IDATx^m0 Q3N؛?[*R]Nn `ܴ+I,e^uxJ2ތ/8 h/ V@ex# pƆni}}!P QɠNEY%fG5 ;~t=oUD%WqIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/inactive_hanging_out.png000066400000000000000000000027161342663516400300170ustar00rootroot00000000000000PNG  IHDRaiCCPICC ProfilexTkA6n"Zkx"IYhE6bk Ed3In6&*Ezd/JZE(ޫ(b-nL~7}ov r4 Ril|Bj A4%UN$As{z[V{wwҶ@G*q Y<ߡ)t9Nyx+=Y"|@5-MS%@H8qR>׋infObN~N>! ?F?aĆ=5`5_M'Tq. VJp8dasZHOLn}&wVQygE0  HPEaP@<14r?#{2u$jtbDA{6=Q<("qCA*Oy\V;噹sM^|vWGyz?W15s-_̗)UKuZ17ߟl;=..s7VgjHUO^gc)1&v!.K `m)m$``/]?[xF QT*d4o(/lșmSqens}nk~8X<R5 vz)Ӗ9R,bRPCRR%eKUbvؙn9BħJeRR~NցoEx pHYs  IDAT8mS_HSa}۝ٺ2A(2EhP%hDDP=He=9L׶nݻks~@DXIBukA2re󟱥ݵ)#:60Ojmov.ba.y*S#\adlb΂_Sct4o|/[pԨty'ԇ!Dv"R,A6eD??١xpX:`b(}7{7Bذ1o[g0L%p$OL\Zp˕&I%:AΚD,Wy+٘H3TNa>Wdz|X:a*L,0%\}A ~L`jсLVܬ&O%PrDQD1eӋѥBbj.ѵ~xnBTµ)$: tK8=`*o'4B=B'A7B14ۧh{ q@6ȜIy 7>@ E-ߛ0+ g9Q8jy+K&%f"<}@e).>]aEgTzIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/inactive_hiding.png000066400000000000000000000032141342663516400267510ustar00rootroot00000000000000PNG  IHDRaiCCPICC ProfilexTkA6n"Zkx"IYhE6bk Ed3In6&*Ezd/JZE(ޫ(b-nL~7}ov r4 Ril|Bj A4%UN$As{z[V{wwҶ@G*q Y<ߡ)t9Nyx+=Y"|@5-MS%@H8qR>׋infObN~N>! ?F?aĆ=5`5_M'Tq. VJp8dasZHOLn}&wVQygE0  HPEaP@<14r?#{2u$jtbDA{6=Q<("qCA*Oy\V;噹sM^|vWGyz?W15s-_̗)UKuZ17ߟl;=..s7VgjHUO^gc)1&v!.K `m)m$``/]?[xF QT*d4o(/lșmSqens}nk~8X<R5 vz)Ӗ9R,bRPCRR%eKUbvؙn9BħJeRR~NցoEx pHYs B(xDIDAT8SKUt #$j2QLV A,\ibq% !  & QQ#1&$Гq=]UVK!;9H qee^1 pgE;zuOcTjdG^qh1fj!%67cc.o:R( ~q́{;1sY'Q*&}x$Jәm#O͞K!476d;vg4nw0&(8ф #Rrs 牴ll+0h m'I/{,.*y7;ϼmR-/r4x~~?]u,Yܼ#KR{w@5p >pÀbqz {,(L#Q!fih"h Q.#n@>oq8Nm }iI `]u?<1@@*TO@Y E_)djQ*%ATdb9 wO>1kbX[oi H=Bq$`sטPkrE'N Uz'Dш30㊏_Zs +Vn򗻁ҙBMA2!H sE(K3T@Î>[Tɚ IAXnjr/޴ 0bD|AeJp||? @rDCDoVAb>N魼^.sc Q5}󗺫]MΒV^2IENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/inactive_on_vacation.png000066400000000000000000000014141342663516400300070ustar00rootroot00000000000000PNG  IHDRaIDATx}R}HQS!57]laHR * !& &JP LǦiLL2̥.#e9Zmtu{޻Alw".GX |= im h]h6Ͽpm~$HDG]"3-?6IX`,ktnB{7 @$VWIj Cr1(Ϛ!BgLlsɤփO̍;3{wm+U=@2w̳rS] TA\`}CZ9c`@ Y5@A[U1J5),5yagHp zUg$$}!& * {nS5X=KZ5@L2u꧌R|E@5qZ|Jp_ז3m{Pat6W7ncE/(瓘弃vX<$͞ew4 VگjlVq-id!}{iA}?%д2Q74RI%u \|FDZ]9ɧʧʩ*ۘ 'Q_直IM{-HHH$f!97a~ow<}+KIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/inactive_scheduled_holiday.png000066400000000000000000000016731342663516400311670ustar00rootroot00000000000000PNG  IHDRaIDATxmS{LWt.QO*{XViiA VD+! BĂ ΰhX4_3 >2n`%dٗܛ9=JQ0+ ڕ\y1W=Kޒq'$/Hjbt\2بjfbUTG0ynѹ4T3#|Lt #ʯ7< 2N30QpYndkؿe61/no+V돚SWGƗ1:꜄rvl~tLcf0a~O˴۹= T e\g}[ZG1͔3Xxfw>g2M'fjGvHb%ky50I'xW$_? ta>*@u֫jUX9U Z$ ʲECUQL #*%sM}JPPa]cVY£OXXDZve$oKEpDi0>T8LsT(`ȡ2pmOoQPw9ap)ܛ p"[.p5dhf 9 ?n[zn{՛%31EI9-관m~m}H6ߵeɹڌަ"k_043p^ O߀۶09$|3pc~HIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/inactive_sleeping.png000066400000000000000000000032771342663516400273260ustar00rootroot00000000000000PNG  IHDRaiCCPICC ProfilexTkA6n"Zkx"IYhE6bk Ed3In6&*Ezd/JZE(ޫ(b-nL~7}ov r4 Ril|Bj A4%UN$As{z[V{wwҶ@G*q Y<ߡ)t9Nyx+=Y"|@5-MS%@H8qR>׋infObN~N>! ?F?aĆ=5`5_M'Tq. VJp8dasZHOLn}&wVQygE0  HPEaP@<14r?#{2u$jtbDA{6=Q<("qCA*Oy\V;噹sM^|vWGyz?W15s-_̗)UKuZ17ߟl;=..s7VgjHUO^gc)1&v!.K `m)m$``/]?[xF QT*d4o(/lșmSqens}nk~8X<R5 vz)Ӗ9R,bRPCRR%eKUbvؙn9BħJeRR~NցoEx pHYs B(xwIDAT85S]UNgZVf7VY0M&f|3>B؍(&>Ã?4Ĉ !qeY@Yw)Zvf:ēܹowsB`a' ^W>42kCSzP;p״|V~:ю.VS>s;KClq_]߷sd|۶qGSG5@f@ -d8ԴG 诹pVYY/:>4 \X*=SϾ:C( |Cy{kveJ=<Ͽas8! #46,h1Wĝ{LMq&ǣM z𓞼KҁMa{#_\;#Gd3)=T,I& -#O FHEqfebYf 0uHl_kSF"4)EP21*c.}}8(lQq^X'fh0д.R $Np@f9٬#Ͷ& q@:,qGl(V ڪ zg ^}23],QD_T6L }BUkaoO1|7 =I԰.+!⃐%PO! "EB?g̾yY\-!Kkxk*:PX6-vzC\jUaօa;}='ݭ?śo{` ylҙT]cYU8\\ߋbk2ϼY'@Вˌ7]lU ?g3$}lil׶qG? yˉ ;i. ,MCi4Ҍj \&gqelgRϺ[IENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/relaxing.png000066400000000000000000000033171342663516400254420ustar00rootroot00000000000000PNG  IHDRaiCCPICC ProfilexTkA6n"Zkx"IYhE6bk Ed3In6&*Ezd/JZE(ޫ(b-nL~7}ov r4 Ril|Bj A4%UN$As{z[V{wwҶ@G*q Y<ߡ)t9Nyx+=Y"|@5-MS%@H8qR>׋infObN~N>! ?F?aĆ=5`5_M'Tq. VJp8dasZHOLn}&wVQygE0  HPEaP@<14r?#{2u$jtbDA{6=Q<("qCA*Oy\V;噹sM^|vWGyz?W15s-_̗)UKuZ17ߟl;=..s7VgjHUO^gc)1&v!.K `m)m$``/]?[xF QT*d4o(/lșmSqens}nk~8X<R5 vz)Ӗ9R,bRPCRR%eKUbvؙn9BħJeRR~NցoEx pHYs B(xIDAT8SKL\Usܙ0O(Pp *,15Q]B ӅGB16n%*iNug̝;w=3.Nw}2ʾ7{8pB[W~ ?9ϹW'7vFON({% P2HEmڜ:6n^h=*; c$Z{Jd c "8[L=Iٖfo95{8-# FI@ `~< rqwg᥁CG09%Ie:RÅ'PVE]·yKa!yCR ڏB2,'S&k&nK 5DԡZp`,3,: Rha"PF'_ES蘓 -2"au`d:!I{q̂r,Pw B bj!zh?0* C,³@̩*`+TvF S5`M*v9(ZtHSkT-8IWQOIE%h T*De.pɪUaM [Dꡐo-8/g 4 +ufƷB쑌%v,VR2=W U-Jt񺔝[,Q=Ii_2]\!Dj<.~ڶ929RJ5 Wሒ?(f74w/t_W8)|~$pՃ^oC.HѲ !KꫧpwA',ʼvxmg6?=ox/%mr*Aܔ#-yV&9Ţ.Q#!nk;l^.q1[w!M7J.lv{w T &zX|LX؀;nj"Xp#ԅ}-Th;!C_X8x;)L?+%T Z5Sۅxz-RQɼ'5b)Td*gW[6?'ˢTR|tiȝtuT!U_XZ23p,3yASͩ'=`շ bLSK+5"x {k6@ٶ>4 $&a G ;66,NPU$4%]{tP2TӈBW|a3eބp4(%ʨU@b ZC*m%4ac4 4pDžaCX*/q_otФx.{pQ8@ө)$s\n08JeR9ǖH Xk MOفn%PiǑJA%[](~N&ٕ!:FW|f*V=͇XrɧOWvY"?|X .Pҍ"| *Y̢#WjNs!Q*xẐW킡.ǤɎ>RA._xͦbsLYIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/relaxing_gaming.png000066400000000000000000000014361342663516400267640ustar00rootroot00000000000000PNG  IHDRagAMA7bKGDC pHYsHHFk>IDAT8ˍAhihN7MZ -Zl]/^=VO{<쮰zwϋQEz"`"vdf2Ez(A|//~)F lhex}CWA?8\pS+`2¯vpݺ4&p47" > ^oV6h0!Hc?+B!b]p-~ `anz {ׯd3^5 X퍁$e#O# Ckbbb:NO>|h76#J$0##"8)J="&fff8(az=T1-˺Y,l6a.rRT.\ݞZ[[;+"/EewMc CE40 }[k,j4M0p뺃;tz8D$=C8|Z^&z8%Z]Rfaaj߿2>>~()QD$: oLjJ~xA`v6̙L=h}ވֶ& 87 $"Y"_a&xwxzAVv<7R s;`Qb&(fUf&46dRZ'/W(_4=2$ØO~Щţy'Fz?:^qG;W`CA!/B[9 a4=6B@mnmtm`/bpa}oX*B|& ~CKK= rU!i-Xz/Y޺)OI54X4 T ZʕxK,| D؄D=<.GtR}/!◴aՇdרPWE)XƋ2Kɗ{\b$ZLQnIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/relaxing_partying.png000066400000000000000000000014421342663516400273540ustar00rootroot00000000000000PNG  IHDRaIDATxڕ{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= ׋infObN~N>! ?F?aĆ=5`5_M'Tq. VJp8dasZHOLn}&wVQygE0  HPEaP@<14r?#{2u$jtbDA{6=Q<("qCA*Oy\V;噹sM^|vWGyz?W15s-_̗)UKuZ17ߟl;=..s7VgjHUO^gc)1&v!.K `m)m$``/]?[xF QT*d4o(/lșmSqens}nk~8X<R5 vz)Ӗ9R,bRPCRR%eKUbvؙn9BħJeRR~NցoEx pHYs  "IDAT8uS[lLQ]{[cFgFkthiPD#фx>|PD"DBH!H$ʇxQgԻGKfL;\a'sY{}BH'c5UPD 59&N*ڑ1HSwfNsճ (o_\~  `UѺOh[Vs-s2c=Ci@O<XCE"t/_38^'}[UOդ{-A F5'3Uʔǐ>|8_~聚9z̴DGWHyWʄ\VW蝛RXJa ~(F\oK \|`$Thv3kJ+|Vbg}}"Acc=kV@sXP3?߃^D.~3 UiZ2bp(2zCN: 3|1Fw+K5R)WX]C$:;t53+kQaI-X2F?lYR tu5wj" &F8=@*}+Xjn>iiVi TnoCv_s W٩ #b,tׯ[1Z֞]2ih6ۓ:qʼ¹ 湤ퟔx[C?|( x)@2xvm4H=xK56%hbg v-׻3zӍ ҝGڃ8͆1,Qh-y?'jc~g@TÅlAAiXCIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/relaxing_rehearsing.png000066400000000000000000000013201342663516400276410ustar00rootroot00000000000000PNG  IHDRaIDAT8[HSƿK.w;f7ZzY+'GA^Rz E=hd%D4cmJݷӓuʾ?>.4q?DZOMnYWT( _ۃVs02aTRR8jĐIjWwlY=.L,>oM|It@E[>7mjW6߀F+C 佮ѿ^jLA*50~U.WTv4pke f1*\6X40fC]HfLb8~ _ehFT$5%%iRlzX"8>matMoz#vXf--l1O*2X_7W,/ ܟחqc{ƙG޵3&3WNnwz.Znu;M0Poc@5Cz3=`KC"j\_z"1&gk_3 ~1;&+ cRQ3ʽ l/5k|C9'[m@WS@museXOdybXNuyBG\)Zǀ`z z2mNk9cFl2BwLKf,~^ j;ʡDuFS+"Z#MJkx ߯=nOVkNᏖFKDuL婅=;ē/ l0oR:zrbjUNY,ҳvbl`0Z~X˕y8#Q'kj#o6`y0£~޾ƞY% C(ЖHp}qN?0@d0Y귖J%.Y1Qf-M?vΆv.[* q6HEt -TmhN.E TqB[n/Kx *um[UݙG|FD/?+Azq5c'?{TIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/relaxing_socializing.png000066400000000000000000000014111342663516400300260ustar00rootroot00000000000000PNG  IHDRaIDATxڭ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 iONvA|)3z)3ڕ Ag*U#ň68"E5kO !Px [hXAHIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/relaxing_watching_a_movie.png000066400000000000000000000012531342663516400310220ustar00rootroot00000000000000PNG  IHDRatEXtSoftwareAdobe ImageReadyqe<MIDATxlSkAfv7GmԫwxOڛJJS""]V+1MAoУUAO ^Jmdgw&IJv^ 4 83$՗B]ss(<|k8GQIy6-hWV#=2]+UVAU2@"eʏ2L"/=n* PkC2֎*dguނA%BH>`Ia fcOlKw^cz&su'zV9-Iۢ}G:J;@ͼo͎rzIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/relaxing_watching_tv.png000066400000000000000000000011121342663516400300260ustar00rootroot00000000000000PNG  IHDRatEXtSoftwareAdobe ImageReadyqe<IDATxڤS=oA};wa ґHLO@F*tH44HH8llxVyo޾>:ϯ]㈾m6}u6NEU#4`X viQ!]kV׬v?t .b%[V$g4S Yh`'#E"a7BAЯ8<PFwg8a񱱳[B3IdN1l)477L?\QU1Dz)iЊdaN[LF9jP$n[`wᯁur(SoTpδ2w,k}9%iB\ OE/p=y\^zgߨNҶm\.oʷgw5rs+]_VIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/talking.png000066400000000000000000000031501342663516400252550ustar00rootroot00000000000000PNG  IHDRaiCCPICC ProfilexTkA6n"Zkx"IYhE6bk Ed3In6&*Ezd/JZE(ޫ(b-nL~7}ov r4 Ril|Bj A4%UN$As{z[V{wwҶ@G*q Y<ߡ)t9Nyx+=Y"|@5-MS%@H8qR>׋infObN~N>! ?F?aĆ=5`5_M'Tq. VJp8dasZHOLn}&wVQygE0  HPEaP@<14r?#{2u$jtbDA{6=Q<("qCA*Oy\V;噹sM^|vWGyz?W15s-_̗)UKuZ17ߟl;=..s7VgjHUO^gc)1&v!.K `m)m$``/]?[xF QT*d4o(/lșmSqens}nk~8X<R5 vz)Ӗ9R,bRPCRR%eKUbvؙn9BħJeRR~NցoEx pHYs   IDAT8OlE]Yv'jF*-OAZJ\^zA8!qj+U@H8 EPB@4RqVcڻzg:c*fy BRp I/-zǧ46zZo~@{Y@fłu&=m æPmE/pku;N[jW.W@$5p1T>v:z4VNGfmĭ{= P@c2:5E:k}g6eC}!5Y0 0)I2Ð,/aab> DlmtD$7$(IC\,=h>ulV$ϼ M Q~ e_i_7U_Kb*]~ʬwp{#||y[MiF$ #Qpۑ߮b|~2Ʊ*7_HgY.]EE拓8Zn;_^E 2Qp&$Eo"*qP}=^t &=&uo{]Pe0ge }܊epK# 'Ude,>hvb=j=>Ye*AІB21Wmsb!'˰_+f12QI0àA6ܯb}xW=~aǰe_IENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/talking_in_real_life.png000066400000000000000000000014161342663516400277500ustar00rootroot00000000000000PNG  IHDRaIDATxڥRkHQ>J 4%Ј?di*HL" VHEm#/͉ZNt6Mќ~xf ˾}  ^y@Ȏz=hsI,r@A6_;f,n5uad˦*Xx<Rdg(%ZNN U/uZ,c9(/ +$-2ș% phL縮Uy@U$`yS`,iu7\}S#?P hۜd².>pEZZw & і s>~&0uuԹ\aD(TvI&S\V"*__@ /T Yd2Uq ]L>V0g];2 7s@z3T{,ggH;i' /M{̥b_a0^E*IENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/talking_on_the_phone.png000066400000000000000000000013661342663516400300110ustar00rootroot00000000000000PNG  IHDRaIDATx}OSQv?v&+cR41k:ЁRƴM5v@ oh G;c"}b*`_;s *ߪjf3 tfSl&%L&g+tj%ə,v ,k"/5^dC@`T%^hءiv~C,or\9|*%u 0iʛXf3y1>,{a+,v@>GYRbBt{ ܝj9 Ň Z撸+W0W2YU(ʷLlp;S G]B $}-wla[ads C \jHqa*;,6c@ag'$t:]0y۴Ö)=sN̻YZƤeENSV;r0րNBG~`2)R!ShEWb}=b/hCӣ6NR m2 Z;P"t@;0 g-I@΋uO%2f8E4՗HX,f^;kF^gN.(L2c$xO``gTOBt>bbU l66K6_q K6x J>/]عn7&[!24}KV 큐3C.-I)eض\ě!/j,,,M UVUh sne+EPpOOm#%km?<7,#Dpnpqvqqspl-&!ssFttuqIC;JE@JwyvyyNHAzjOOM-./-.1}}ڂ i|c%wщډS/(#EhHfuבҒJbU]l9@024VuV1n[f-)igؼutRNS@fIDATx^=SQsض3m۶mSzewIF%ќhz>z"=@vR3O,y U4[>F WhL@Vň.6n8٦VΘ=oCWGn}|vp ^Ш%g` z %Ç5-M%1KTRfvZB*FH^.[4 IENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/traveling_commuting.png000066400000000000000000000014671342663516400277120ustar00rootroot00000000000000PNG  IHDRabKGDԂ pHYsHHFk> vpAg\ƭIDATx}kg?gwfwbLd "AEZ#ҖěPA Bś7A[Q!EM]u쏙2fE"f3SǾLIq^J3bzw:9 B//..h/:sb#saItdOH}[T7n?y /8ib>%ҹ ٺ0Un%+KO9(𩙩FN5} `) E$WW…jE]ʹ_@  .: RMI赚mgN1% `V#0}5= 'a{s >S5ݷ_->']_*e l+G.151/J𷞠6Y$/=0~(44H@S&I jP$`ܥ2? )BP=Ndh(@<< r2# MM@D T>"D&?+G=+Y=ՇxV?y+MَI r]0$2#`ڒ 벻 #lYA/7 .+5{7CaViM%bڒm-omcg*~pxeFW|tM˗-_[[kbwvJM͙5rr@BxoooLaKcc(=sΜIm]]]n ~t|,TvgҤI >011CX! Ė81Z^LP#:=\R-(~B}@IENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/traveling_driving.png000066400000000000000000000016201342663516400273410ustar00rootroot00000000000000PNG  IHDRaWIDATxc`@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' [pꭁ(}r:+nnQo<ކ<OVjRa <=^%_$>x:7  esˆx HbN$ JIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/traveling_on_a_bus.png000066400000000000000000000015511342663516400274670ustar00rootroot00000000000000PNG  IHDRa0IDATxuSkPa~%12d 0DI?-R֚IfME)AFNkWݦEǪRkVeZ~>ȖΟ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#)}uIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/traveling_on_a_plane.png000066400000000000000000000014531342663516400277760ustar00rootroot00000000000000PNG  IHDRaIDATxڕ[Ha7?m9sLJI%!eE70((#@iotE`w5_jӹUq v}6h e(҈1[{~-4;14 E5[t80wfSQIߍ 3UM<8ж]z:h i|ijp°0 xH+Pf kGޓϰ dEVN$gGtT*m=/X mޝKLM!ln<=2+[K)7H+%&9SQmK늆joae;K8PZ{gxb*8xYj'[Kͻ,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 |nCIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/traveling_on_a_trip.png000066400000000000000000000011071342663516400276510ustar00rootroot00000000000000PNG  IHDR(-S)PLTE^_ammoz{{oswptwprsnnoqrtyzz]_a]_`ɉllk}~utvssv`cdpps ijabf`adabestwz{|lkj|}ą`ae_`ctRNS3#"э77rIDATx^n1 d` Ì efxΥaSK8n]7ēL{LAϧ;yuX=e+bgQWαY*F!ύ(ژM8jH]KJ-E?^,gg+Ge{1 /D_IENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/traveling_walking.png000066400000000000000000000014331342663516400273350ustar00rootroot00000000000000PNG  IHDRaIDATxuR},qCG(I)Ke5R׬XERH #u[/**7ͩ霗νx˄vwr]}>||Cr6ұ`\rkzhTY%qip '^ZCכ+WĭWY 8/b}c񾬾 bJx-rNm(͆skx'IKЫMY!X"x4#;Dl;u%1 c#f[v=72DQv^rF?8=C`|Op{N.VCC^am„'S91y,m.tN:L ˚Ss^Rșy{)a .p^دtLrՌs{npPEFR/OQ*C %Ro3x?A, gIy/jI?T4s&P)". Λm :cm(UPJ.T>s@Y>PO b[2Lt_yˈUc} JQ 8\ jM2fMf 4s_jC @Ix`E3紝z'ĩfaN _qt#U(rҗ`uzb Nw0MFq^.EtT5""j|alEi+h Ec&UB / 23-~e.QIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/unknown.png000066400000000000000000000031271342663516400253270ustar00rootroot00000000000000PNG  IHDRagAMA7iCCPICC ProfilexTkA6n"Zkx"IYhE6bk Ed3In6&*Ezd/JZE(ޫ(b-nL~7}ov r4 Ril|Bj A4%UN$As{z[V{wwҶ@G*q Y<ߡ)t9Nyx+=Y"|@5-MS%@H8qR>׋infObN~N>! ?F?aĆ=5`5_M'Tq. VJp8dasZHOLn}&wVQygE0  HPEaP@<14r?#{2u$jtbDA{6=Q<("qCA*Oy\V;噹sM^|vWGyz?W15s-_̗)UKuZ17ߟl;=..s7VgjHUO^gc)1&v!.K `m)m$``/]?[xF QT*d4o(/lșmSqens}nk~8X<R5 vz)Ӗ9R,bRPCRR%eKUbvؙn9BħJeRR~NցoEx pHYs  IDAT8uSKHTQιכfvDzI^JY-QaBhD(6-i"HZD"E2eM3:3xgMЁyw Y/)1 ei h)[ro~|HCfĂX$n0dߴsZ+!lh c^\$L4C"g&w.milWv|ĦfaXyg_({8C½^P^jvjh/Yc_?|E݅B>U/#*^l9ga(UM[;**3%eSCyi>ĜeãY""hs-Bcb!d@cb_P+׸>~XWmHJ'Y X -mQ0NuHvu_`D7QYđcKՉDBGgG ERـn`?#$ 91./?x&gRstΡ8=4̿1,SS#w W󢰛z`ތѣeVD XQ:fQ:́1) b"L&IENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/working_in_a_meeting.png000066400000000000000000000015241342663516400300050ustar00rootroot00000000000000PNG  IHDRaIDATxڕ{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[#e'IENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/working_studying.png000066400000000000000000000033131342663516400272330ustar00rootroot00000000000000PNG  IHDRagAMA7iCCPICC ProfilexTkA6n"Zkx"IYhE6bk Ed3In6&*Ezd/JZE(ޫ(b-nL~7}ov r4 Ril|Bj A4%UN$As{z[V{wwҶ@G*q Y<ߡ)t9Nyx+=Y"|@5-MS%@H8qR>׋infObN~N>! ?F?aĆ=5`5_M'Tq. VJp8dasZHOLn}&wVQygE0  HPEaP@<14r?#{2u$jtbDA{6=Q<("qCA*Oy\V;噹sM^|vWGyz?W15s-_̗)UKuZ17ߟl;=..s7VgjHUO^gc)1&v!.K `m)m$``/]?[xF QT*d4o(/lșmSqens}nk~8X<R5 vz)Ӗ9R,bRPCRR%eKUbvؙn9BħJeRR~NցoEx pHYs  sIDAT8]SMlEzm퍝IҤ!4 " P RT?(RVBTEP9i/ B IFNڮݝa=vfͼo  .xc`{9T V(n0:pXZBpO9AIg+CYhqswz2 2e\>hb\M*kA2|ˍ<[SxS\.a.;mj;VJ9 "Y ZMG S |6#~l#bcc|4$_Y|ٳ%mի W0Uw9E=j@?q֜vv80z߸HKHs].ݗ{vzAW3~wZ/^Ͼzd TCDyķ@O"@JK2>~l8O~;iXkC`*‘\Ce!TD"g.zuukd6|d1 "sHkV!ƈRoU].׼Zq2gsw&% ̗T{\2gyTXR_%\&uؓm:uv8/ܼT;PBC9o"hjG"Y,! &s]=I)g;S@OF,vL과jd{rӷ3q񻃱7|ٽyi'ɽfÑk@@‰QR2_2fT^\FDfKږl?ZIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/working_working.png000066400000000000000000000032501342663516400270450ustar00rootroot00000000000000PNG  IHDRaiCCPICC ProfilexTkA6n"Zkx"IYhE6bk Ed3In6&*Ezd/JZE(ޫ(b-nL~7}ov r4 Ril|Bj A4%UN$As{z[V{wwҶ@G*q Y<ߡ)t9Nyx+=Y"|@5-MS%@H8qR>׋infObN~N>! ?F?aĆ=5`5_M'Tq. VJp8dasZHOLn}&wVQygE0  HPEaP@<14r?#{2u$jtbDA{6=Q<("qCA*Oy\V;噹sM^|vWGyz?W15s-_̗)UKuZ17ߟl;=..s7VgjHUO^gc)1&v!.K `m)m$``/]?[xF QT*d4o(/lșmSqens}nk~8X<R5 vz)Ӗ9R,bRPCRR%eKUbvؙn9BħJeRR~NցoEx pHYs  `IDAT85S_hE]HnM\lb$IbA>ŧT > BAXdO% mDZAK$m¥%K/vf&7o1@`4m7 q3^v\1.7nϖΎ8Od˲Abl \Tsz yc'yrĂ'&wB+F-RR0Ul1?H/4j|e!\,ՕLr55N"?"@:Uh|'PFd}Y~p'W=m)樀CK!8Nn֠yAecUnmg!yw^|m<@= X)^ i'54{+$2}T[^n!Ry]CylB³9O9#iL$;׿:Du c@&lً8ڵ3OLgK%Viu[Ybq fW7w Ac###Źsc:s?52 ? 'mKx<.n==ɴ~_x >~_Wm_}zj "r=ۛD"[o#v$|}X2d.X/.ydNhWQpj.ѓ$\mmm 6u|~g$HtDP§ ]]Fajj t])= %dCCC~Jp1>MnQU@({†!`dK/?נ [U *P -5$N xY(g lIENDB`psi-plus-snapshots-1.4.554/iconsets/activities/default/working_writing.png000066400000000000000000000013751342663516400270560ustar00rootroot00000000000000PNG  IHDRaIDAT8mmHq?.uLjf34YNA4M2W"EEUc ڋ"" d:",dM$`> 0?s^Ԥ긻玻Ml,}C1@vlVe^A^iNw^Bf۰ 84O)"?(ʳ`0@u:j y@h2SSS3[=k6^CV#i8S`"LH(z`F[ۜ@ *0=cf5Qm\kkauˠ*'f&xD,Rk҃ r':h4b *j ŀ!ȶƼMCs>2V<(+Zmt>ZQ'!39 J.rö?ϰhmm=`aXxUޱňaQJ6xhj,kPBУ޸k,u=3tRNS%U4R0Y;3/IDATx^=bA@ѝ%bk3mv'$"0h-C% x$C }M/sL{{}|Mp})l=~~[XhT%!Lևt, RY˒XeI7t횪V8b/@7-}(LX$cT, Md3=$w036IENDB`psi-plus-snapshots-1.4.554/iconsets/affiliations/default/icondef.xml000066400000000000000000000015211342663516400255530ustar00rootroot00000000000000 Medals 0.1 Adium Affiliations 2010-06-10 http://code.google.com/p/psi-dev 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.554/iconsets/affiliations/default/member.png000066400000000000000000000011221342663516400253740ustar00rootroot00000000000000PNG  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^IDATx^=@twCcl^{|L*ӀOg2n2.d> fA۝ѴzicwoeP\~}k8zv) A,)Ţ"|j;~ߥA VfBzRJL\߉R˿@`pQ.c* Α&IENDB`psi-plus-snapshots-1.4.554/iconsets/affiliations/default/noaffiliation.png000066400000000000000000000011131342663516400267470ustar00rootroot00000000000000PNG  IHDR(-SPLTE?[gt>zv=FMEdpp<:=;ڮ_8֒>s낐N2Ͽ3D#v̸Ψא膖Y)wɈUEzM}׿DӘɅgV[tRNS;U40RY%/3IDATx^=ӂ@@L݉6ֶ3Iy> 0Z !YM0uEF&ߣZͮ7C'?TyHҽ"C\ {H$m[$Grl"jZ){NOi g2#3?1R| aU(IENDB`psi-plus-snapshots-1.4.554/iconsets/affiliations/default/outcast.png000066400000000000000000000010261342663516400256120ustar00rootroot00000000000000PNG  IHDR(-SPLTEeeezzzwww???EEE~~~}}}wwwvvvҎyyyӊ򺺺Ɓʮxxxꏏˇkkk߸ԥssslllptRNSUᗰ4Y0%3;/RIDATx^=Eb0QA~afN-a$8"b\s+P #g70M` ٜ8.+ϋLU\jDa3PyӲk2y5þWHXMW٣ԉ lq?t8pz CWo.nZZk~urҝB &IENDB`psi-plus-snapshots-1.4.554/iconsets/affiliations/default/owner.png000066400000000000000000000011061342663516400252610ustar00rootroot00000000000000PNG  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<IDATx^=Ev@Qg%3.#hR˿(Af Z@eHaA{.U BXoz8\ֳ@@[_@C@tQTTʥ1vfl5ҁ~DG, U mL~):nOCx~>3doP< ><@$DԆ?^IIENDB`psi-plus-snapshots-1.4.554/iconsets/clients/000077500000000000000000000000001342663516400207705ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iconsets/clients/default/000077500000000000000000000000001342663516400224145ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iconsets/clients/default/.directory000066400000000000000000000001031342663516400244130ustar00rootroot00000000000000[Dolphin] PreviewsShown=true Timestamp=2017,5,1,23,42,48 Version=4 psi-plus-snapshots-1.4.554/iconsets/clients/default/adium.png000066400000000000000000000014171342663516400242240ustar00rootroot00000000000000PNG  IHDRaIDATxڅ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.Z 9 Ia"ʕǏ]?/uw7 )HԐJyyf9X_t+r?_ 4*("`d]dw4H@]$u#{zJ88׎`H^$448(ׯHX+''Wu=\ &JkBqqlǨ2+._^XQWL[_ߖ_~nUfeti1*y'8qŽ @v旝y֠z8<7hWJe%` ~iի-*MM>-TzӱUd`S3$;w=$,VVZ dD4ɂ,67}.&)w6E"vVW9Gf)*)>ɓ{JF/:H+-G JI6חv+$FFZXvR@i;IYh=:zǏ4BK-۳O_-E" m+hh؅Ī⃕]~Dg62X ޳`i:kkCh4:p$8;}q pύIENDB`psi-plus-snapshots-1.4.554/iconsets/clients/default/bot.png000066400000000000000000000013041342663516400237040ustar00rootroot00000000000000PNG  IHDRabKGD pHYscqdIDATxS]HSa~1;$E6T&d` /DNn EW^aanYc" ؙ1<w.OK{(id8%(C _Zں9k1L!Dd2_ du}{<e]dYȲ L&7BN$U($J%H$ dqqERTU!~5 C#'#GXb},3ffAQp+c!Ku[],(鵵Iޠ>onà4 f,QQPf;qM F3N"/#$Izr`EU Qq7y}$._`b؝B[WWwy`/?VWW_8wO@ *N|>O&&&͠az'S,˘YWc/_*>iJcccqkkm{{IVϡ)6ÇIENDB`psi-plus-snapshots-1.4.554/iconsets/clients/default/conversations.png000066400000000000000000000013161342663516400260200ustar00rootroot00000000000000PNG  IHDRaIDAT8˕=l\U}o&qǸ0HJa.EрEJ% ET4H(EtҤ0Р )E D^}wf(VVhs3:G##[{+Q7g?KB(7Cd92k<3q6hQD-9;pcw V9}#480l` 6eh=L!tamS~} 1ܧK8*c|ˑ;!obH fp w#JDV2fAvfߣ[IsLxkCʸ@sW4T@;N=3K95=u t^?.P7[$ivwO}̟{׸&|;hUeDJ6`.yAx T( [.E޽CB p x@A"3ZCqw+ ɘ"pƠ6 f&+3"97˟du4;[w/mDBl:[\*aB=qfTPJVb~v<}83lz C1nc A2DMO~?/J_,IENDB`psi-plus-snapshots-1.4.554/iconsets/clients/default/gajim.png000066400000000000000000000014021342663516400242060ustar00rootroot00000000000000PNG  IHDRaIDATxڍkHQOE%!1і^S3f:/4u^K%j_B j"&Ry, "2JEfoLq(RwBV8eȡVl>^#sr2ԡÏ2%K1:^TYI֯ E5Q_: .ќdM^ST0”sTCgl%6mC Mf"h/̎]Bbk 3w{Ҩ " x^ɭz9xRs;>lb-qS]P_ "QL ū4-jHATE/,ښt

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/conversations conversations.png clients/isida-bot isida-bot.png clients/gajim gajim.png clients/jtalk jtalk.png clients/kadu kadu.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/poezio poezio.png clients/psi psi.png clients/psiplus psiplus.png clients/qip qip.png clients/qutim qutim.png clients/sawim sawim.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.554/iconsets/clients/default/isida-bot.png000066400000000000000000000011731342663516400247770ustar00rootroot00000000000000PNG  IHDRaBIDATxڕnA4mtc;*İЅqBcbFx *P]TRB!@i@~V!O|CId2͜NC6%l61X[TJԕQvjFF0`l XXPJ ù7,JD"ɤv гfqeXހe )^AsװjG湲hx<ё] nX_[4<.|JAѠjކ{)y? 8n'k*r^k#dՋm(\q9G< VLLLH$0ZS3g+b^zNS Noٯ||Tb*]Y~̚N2Jp8b?@U[>2+]zJn!tK#~\- BbWReVre] >b"텮v J7}0~-9 `Ow=eUfIB#Z s&lЃ5#1=6Y7YZl'g\kUs ZZ("wqUx~#՘`9#N0W'823~p1DbLJ >|ulfdY}ɷ ';NbZQY'<"n}>0}*g[IENDB`psi-plus-snapshots-1.4.554/iconsets/clients/default/kadu.png000066400000000000000000000013651342663516400240530ustar00rootroot00000000000000PNG  IHDRabKGD pHYscqIDATxuKHQ>gttTDajImhS(ZJhE ۄFA\b4|U>rt683~^=EQIM&,%1.jxOS ̝M:@`k^ Xi#,kmXC""Q^̕ J^3SP ΥgƝyV(+[H\4l5:yHi!Y۾HlH7"hosKF)/; 'FIENDB`psi-plus-snapshots-1.4.554/iconsets/clients/default/kopete.png000066400000000000000000000015501342663516400244120ustar00rootroot00000000000000PNG  IHDRa/IDATx] LMqݸͭQ$RҸUՄGaZZ膛7]"eIET1fW҈2q>?|+zq0 =S+.,:Kk\I[ej14}QJDXm }5Ϝ}J^QE9Jen,edЫڢ(v7J$ wPf^N~b&qfc/)z!5BRy>*w[VWֱ' : 7~[;[TOyj|L:`RA|texH8<\! Ctմ~x BQK/v|JXcbIAKChfZSra+*VS@ti>"\"jxGC~$dig֞6mљWy-<b}X? rKc8J4)m-:}|&P@(,ƄQF~s (jウЀ1b_'ިxȺ&@L M;W޴1Wwl`͇n.l]; 2F[.rMF: 5;@w.{thZ17U{ 8^^٤%{JP5Mj b Њ۰q}q{`>rtbw)=t_y&c+5x[Wv%d;^+ŬZƤ1Sđ5HK7pVrIENDB`psi-plus-snapshots-1.4.554/iconsets/clients/default/leechcraft-azoth.png000066400000000000000000000016611342663516400263510ustar00rootroot00000000000000PNG  IHDRa pHYs.#.#x?vcIDATxm]Hg ֲ1I|'4q.A[FanZm\?bb͂i PkFAPo y}ѕ"Ң* TBz]89}{< Q2qx(SUUQ$ՊÙD&IJZccksY__'Opp9]p:\.\iI)zzz! DyY9#E1:?OQII)ۉFFFh{{;E\ t:Z )`s:ۛ ֖},WZ==tnbI22u0]!\G{VbƆVSf?7paXDXZE ^r!Zlmm%z>oAJb\ laSka@?tG|t s8[ZZ?e_[M#0<EO'.==~xDN`r%}ʿpWoW@Y{#yVʊF)J ~-,(6n*ME\[u!D|յHNRWW'A#X ٿe )g| LҸYNK}v#Čhgv,\VV٩gjklaaAhll$D"b0>:~ !0",\(/B峳z5/R KKKb:,\;66V@ $$Jܕk~IbY9^=\/e_$<[NXIENDB`psi-plus-snapshots-1.4.554/iconsets/clients/default/mcabber.png000066400000000000000000000010731342663516400245160ustar00rootroot00000000000000PNG  IHDRabKGD pHYscqIDATxMkQNo0-d(fB#t!ٸ"uT.]U, tQ%KÐ'P"Qgu8}]4MbXi >}E$QjPyqبhBP8?sC=?sxRʟL&Ð |4 |χMHt8 r9ƣ1MnKgd~qRҿ@v0!c&[wbl^S1i \ץhrpP 0PŬ KōFS1˻X"wRJݥRvAEM{MN2?,0 J U[,GS-_/l'?>@JeYH)(sw:~YEL'x'$>-z˲, x@/` Xf*`V)ęa眃e7PfMi\nlTpzcA{>RZ=b_wlEMis{8 QHžo!p֍i5q~1L3.[_r&ծgѹ Vr,A I/2|y*WrV?z¤&#sݼK[EFgW>jyź r x)ؖ\PU5t:.00ÑeIPpqϦ`ɲxVM٦X&>5;y7A3<IENDB`psi-plus-snapshots-1.4.554/iconsets/clients/default/movim.png000066400000000000000000000005421342663516400242520ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  tIME4IDAT8c @!xX`M ?~ah_|![AL 37\fPgHbag= >~j+CY17'#7'+CZCs0111,u' : 9! Kv\gy!Cz0w  G/=< ?e`````m4aGOBH]IENDB`psi-plus-snapshots-1.4.554/iconsets/clients/default/pidgin.png000066400000000000000000000015271342663516400244010ustar00rootroot00000000000000PNG  IHDRaIDATxڅ{HSaoE8".Cܚ&$#̦,,Mm19Gi@C 1"a=,2BRс}wGZGQN8s'_#Wض|/͆Lew)?P_2+`#' P@ IXD y!Ӧ$3.;셛35Pl$R]$/9~pZu'J: 5`RlhGg-h’D?[7J^Ӆ,aќ-Zt6BS$O3IJywB7>E#ԳN8θpj?&M{8J(Zf). g~7/sl3=>ThiȀ_'Vr HPCJTONCg r[9ހ^AҢ܈vfĈ⼐/.5]@ nitq3: Otr[@yaj#80IZǤAU{|۫݊ ܸtd\g}jQJkw_vd1l{t=s.j3Tw{Q=vԝTi$Çn=h) :cV $PR. GfTUR=H>? 1…#^N߬\cI0-DžNP"?-hu:A$B{ۃfW;4;◍g]o7 b,jXi[0 '/nJi16<(UMwg?Czy;x'~>UՑٻPbB_A+RmHGIRʄp ?ҕE3PIENDB`psi-plus-snapshots-1.4.554/iconsets/clients/default/psi.png000066400000000000000000000013111342663516400237110ustar00rootroot00000000000000PNG  IHDRaIDATxc` pOV,.H4њ-:k|`FOhˮ=6 F?SN͹O8eYznkwWZx`nJVԿ%ցQeM]%ՌlD-Ϫ{ ,|c`I3aJv\b`pɜ+XWס[ABݥ}gGkσ T;q '<ҳylf1ݰuvw1HDUe˿B*I<%ba>}]￘nt:n>B_$@;xY|~e`Q X_s`ֽzۿY~2 k5ȿ3HpE? G _ .zwB%]t [&5RZƸM#-Z˦k?7>{+RPdҾٿ3ֳ ].S쬃_w\5"r"DeIz9=ʉ $Ic.n1UW6@6mBH/em=IENDB`psi-plus-snapshots-1.4.554/iconsets/clients/default/psiplus.png000066400000000000000000000013551342663516400246250ustar00rootroot00000000000000PNG  IHDRasRGBbKGD pHYs  tIME l_mIDAT8˕mHa-R_̖iZ~aefF"i5т[? I(Ќ~(Qo -DHȯkfHͤ}c:2сs7+T5ԚvrOM']yvdcY1E&llzO\z\(ᷯ"2R#G>Ґ}.GL}?/6_)WnuH_dpP`f\,׿ToФ~c3L0N~C',PsTPeYM6٭ UTbEDOmVPFF*7Fֿ'`qT @554Ums _3Ʊ%@h<>tqFYd>u AHQ] sNv-F~ڃRi&IENDB`psi-plus-snapshots-1.4.554/iconsets/clients/default/qutim.png000066400000000000000000000013161342663516400242620ustar00rootroot00000000000000PNG  IHDRabKGD pHYscqnIDATxUkUWkߓwo&XAbiVX:+8q? cBTZ%~Ucg1䝳B=pYkٽS1ɩ#$RL<;߯L.0=WnufOi9#a D9oP")"~)2ePnn#wENCXQ5Xv_loF:x' -*V ptošc\go?osV(0fS*,%o5 dwpk{w;|8f V3 Wɑ35O357X&?:5gpOlj|Fll8>0Lhbv\S!M˝.'qř?H{Q^žÄ?; #ȹ/!9Npiͧ_l|6ߌ)p{Es Y ya,#z(B}hWFHݷYOyPyZs"x30:A YebDEV6` _gR6rP r?ө,ja%C+Zl+9>'- P(-7YwFIENDB`psi-plus-snapshots-1.4.554/iconsets/clients/default/sawim.png000066400000000000000000000015241342663516400242440ustar00rootroot00000000000000PNG  IHDRabKGD pHYscqIDATxu_h[ew49Mj)flntA7:Qv3 7:AQڅ^y!u^(48i`2źKƚsHQH)%H`'px(X 9tsV5ZVm_Ea 0 RF{Wob2!ߚܻJee2@|^o 叏MU-52hfw~?È8O?w' >vhT*E;D^$vgvƞ|c[tIW=G.ZmE @wֈBDZ=n=])5ڗO9dKB.kG.gTvIm9Z}EnӉN%3B1SM\>(Ng#ښ`Cvz$怇|nKtC6%nYWg|>6!Z)!+<ϋ+1W6OjA2ie0qK;q[^knUHMbXgZahW`fmsodOO_i{gcbPXgr15qhZS^~Uom݈kYNFwZix#u|#S|\KCvvnsoaPnMBeg noone %tQcg w}|ub)uO#~.,eZj,)$SIDATc`F&f`ea@p9ly|^>{~_PYXDT pqus HȂB#"cb@ I)iJ ܼ¢jiYyEEeUuMmH@CC$?a$m:L6}Yz͞3w)fV631ȇ%tEXtdate:create2018-08-18T12:03:30+03:00pwu%tEXtdate:modify2018-08-18T12:03:30+03:00-IENDB`psi-plus-snapshots-1.4.554/iconsets/clients/default/swift.png000066400000000000000000000005631342663516400242620ustar00rootroot00000000000000PNG  IHDRabKGD pHYscqIDATxӻJQo bZhcBkAE;QP[  X&6QHBg93AaT" F=PRW(e3LKhvX5\! s̻:%K*"%r 8! bl#5޺Ul y,c :muEn^5= '64O_8}ɛoel2KIO.cH~O<IENDB`psi-plus-snapshots-1.4.554/iconsets/clients/default/tkabber.png000066400000000000000000000010451342663516400245340ustar00rootroot00000000000000PNG  IHDRabKGD pHYscqIDATx;LQ(VADI ͐ltrP&'c`PSńE/}@ G3{8str{2`Wx3ԙ7LL vJ|d]J Azy /*7\["K*\Rّ>Q4ny&裨 ='S52#5i;X7Y3Ƚx̩2d`mFh@dm[XDd5 `L6s#Trǒm"08r[`v/PN@(JĠ'9Q #v@A{H/_u#u->vu7}RPAun/ai/|Jv406(Mw>_S#}(ǣ|z~x* `VЙbIENDB`psi-plus-snapshots-1.4.554/iconsets/clients/default/vacuum.png000066400000000000000000000014111342663516400244170ustar00rootroot00000000000000PNG  IHDRabKGD pHYscqIDATxMoU;6 r y+6DY mEYD@ZXB*PLHLcc/\ьscV ڡwԘ+ |tnxgFXܦވ@Tޝᓏ?$YYP=h`=b(TpN8}:GY NNh4#(z@QT9<<ׇ˔HL/`p۫`ݽډw7DjԚpey@`9ջqWn~JߙSVKBirε&wn :Γ5'w!'1x?Ađv48 ܴ3J2ɛ[fXAѬmV%Ya8ffinv~2.95bĘmj2@3}iI%|^ er(3 *DԈ $חVmkPQI%M$YEEf, IQD,$8f"~4~*GIENDB`psi-plus-snapshots-1.4.554/iconsets/clients/default/xabber.png000066400000000000000000000015021342663516400243630ustar00rootroot00000000000000PNG  IHDRabKGD pHYscqIDATxmMhTgͽwf20 BPBHBCqB̲E 7qUADԢTnk*Dl̏ct2;w(Y{c2{'QDon?Ig>ۺz8PR3d±ܜLLy]zϟc[-ln|x(HFDTA?8kys^IAZ2rTi/-梨` 0FD۷^w}2vYc6wFm"e꾏$'OtD"ӧ6m36ZDUuƈq@t0=M}btRTZ?~rN_YY?9~R o~>J4 B~s;6 U4\^?z/UOo!-6n529MUDAak^2Ҫx&E^ryC1n|-VV8FFکѿ".\뭭%?6Fŋk;cz*?g7b\-ܩ_F\( IENDB`psi-plus-snapshots-1.4.554/iconsets/clients/default/yaxim.png000066400000000000000000000016571342663516400242620ustar00rootroot00000000000000PNG  IHDRavIDAT}Lu3-eJ.j FND{Y[5IXi03HBd%2%BCt)q;^7{{;θ[9C +a@`'# HTL_v~vKk׫╅D&đr?NF}V+!|u3m OGav;u:%YNƋ,k|!33ݰ47$(٫kt~|9oYeˠl }A#@%^BcC$wbsW56YEql6wm:RQɶ`U7E͂?jj蹱T˝A絘nfӊ%>cmێcJ[f+ke`>ЂUuWYH}yݽKXy6qXtI:RI0v*hwcLRV~"T~*/"/gJ^SoCx&K̇a+Ѳz] dwuBorQcp&ξK X h>%h9zZX{` =`d:ڛhTe>Ɯe' PgW4mOE7c*S?}P*pZJ@[k1:_mi8WMD+Eb ko2ozUQ_ _Rߎ :R%m<uiHQ=]Wah^˷rOg {Ġ9 ~5@//!sHP~ `+b6m5IH&K՘+Ҏ+[3ˤX;H"sN yIENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/000077500000000000000000000000001342663516400213275ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iconsets/emoticons/default/000077500000000000000000000000001342663516400227535ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iconsets/emoticons/default/angry.png000066400000000000000000000012141342663516400245770ustar00rootroot00000000000000PNG  IHDR(-S-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLAiPLTERPtRNSv8IDATx^e DQR{wd"6H0D@6>G,'G-GlGS'C0cqmD]U v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLAKPLTEU?UU_U**U*?U*?_U?UUUU???U?fetRNS@foIDATx^7ABLRi{˯@߲  B$@Wjei.\fqEMJ vIN 4iTOk 0`m&?yIENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/beer.png000066400000000000000000000011611342663516400243750ustar00rootroot00000000000000PNG  IHDR(-S-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLAQPLTE{ޡʹރ{ޝޝޥުՕޙޙՍޮޕgtRNS@fIDATx^= 0 WvuqoRb:㇠M5xu$1ydh[ڨPkV'!4[,^Hlwt7rR۽00أ1&uuIENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/biggrin.png000066400000000000000000000011641342663516400251040ustar00rootroot00000000000000PNG  IHDR(-S-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLA`PLTEع޾kmŦƧбuhqtlqͮ|ѲqLtRNS@fyIDATx^m1E\\{b%^ w\ v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLA*PLTE55qq^^¸tRNS@fIDATx^M!0 vU1!1UWZ(&GHO5Ioð5aw-~%>~̔T@ZRp3%n}yUj|N1Al=J;G/'$?_UIENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/boy.png000066400000000000000000000011461342663516400242540ustar00rootroot00000000000000PNG  IHDR(-S-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLAWPLTEU?UUUUUUU_UUU*UԿU*_UU*U_ԿUUU*?UtRNS@ftIDATx^UW@ C*wĈЄ~D Z '7 &r+_N 8%kD\:#FdTIV'0|HQ>m@3W' ym5*OIENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/brflower.png000066400000000000000000000011611342663516400253020ustar00rootroot00000000000000PNG  IHDR(-S-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLAWPLTE_U?UUU?*_UU*UU?U?Կ_U__UUUԿ_U_UU_Un?tRNS@fIDATx^}GCA B]_ؓ(UXxB<&M,L}eZ$c}RJFgkj^.@+i1vWpX){. b|@. CX:yn:ohd6$IENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/brheart.png000066400000000000000000000012661342663516400251150ustar00rootroot00000000000000PNG  IHDR(-S-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLAPLTEKK[[!!44 nn^^ ttBBQQ77))< v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLA cHRMz%RX:oZIDAT8K@'YpI')$8Ap.%t(puҡpnpC:d}|Cc?T!!s#Օ{x/L-A3u(&Yw7A4 HC5Dr/ѹyT h@\ Qd;h;7I:ڒ8pru ~8XY0H Թ8A5)qDc->e %% ^rj*OyEw-,QٗCZuc= 9@MlEg ȸ|#9Pqp41C4kV>&5R5'n/ S^%T77ON[}ɭogH$aϾ ~<<6L zV2Q$ ]OlIENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/coolglasses.png000066400000000000000000000010611342663516400257750ustar00rootroot00000000000000PNG  IHDRR-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLA!PLTE @@RؿtRNS@fuIDATx^M 0  'eؽCgy 602]Zoj*i>N.DV}[wn<xJYi5@3NZSK} l%S,OZ8IENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/cry.png000066400000000000000000000012601342663516400242550ustar00rootroot00000000000000PNG  IHDR(-S-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLA{PLTEƧع޾mtŦuͮбѲw|hqӴlkq_6tRNS@fIDATx^mW 1 Dw0 !̗HFk:> λٴUss/RDt+ۀ f&OaQz*YSrBobXurykhS,̄TༀH VqݕE4=[]- ̂/_IENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/cuffs.png000066400000000000000000000011241342663516400245650ustar00rootroot00000000000000PNG  IHDR(-S-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLA6PLTE9t-1+ (R++s%ǵ\bAz: _oU IENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/devil.png000066400000000000000000000011201342663516400245560ustar00rootroot00000000000000PNG  IHDR(-S-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLA?PLTE|eUk:E¼(|YmtRNS@fvIDATx^EG1iP|IBch N!\J 6)֒a]OP.<`ԨC=Mᚰ$V k0A\w #z/?zIKIENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/drink.png000066400000000000000000000011361342663516400245710ustar00rootroot00000000000000PNG  IHDR(-S-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLA]PLTE{sŽդRbURUZs̓惺 tRNS@ffIDATx^O bvYSUb;@Հ]} ] KltdYhezBPC5p7C1jQIENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/flower.png000066400000000000000000000011401342663516400247530ustar00rootroot00000000000000PNG  IHDR(-S-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLAQPLTE*UUU_UU_U??U_U?U*_UUԿU?_U__U_Կ#tRNS@ftIDATx^UW @ C]m?貁8?zHFS8Wn x?d,"m,#[g Îcl v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLAPLTEhbRQ ͽum}{WǷM JPN]grIF$ev+|yX<HAG=. 2& =@53+ U % v4ôAB=TJtRNS@fIDATx^mn`f3s}RHsUOcp/\GK\۹Yb3vmvՋ0qLD!P6L@Ɗ6[@h E)x2]UǛ[}?"#|48Epդúx|z~y}{1K) )YڮܰTm 5ZIENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/girl.png000066400000000000000000000011711342663516400244160ustar00rootroot00000000000000PNG  IHDR(-S-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLAfPLTE*_U_UUU_*?U?U?_U?U_UԿ?**?UUԿUU?UKptRNS@fxIDATx^MW A C=H6~ v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLAEPLTEKKtt!!ww [[44D EBtRNS@f[IDATx^GB1њǟp-2;۴vݝ @9<|ˤȗCbz1ӰTtbdZ 9gZNIENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/hugleft.png000066400000000000000000000013101342663516400251120ustar00rootroot00000000000000PNG  IHDR(-S-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLAPLTE*_U?U_UU_UU?U*?*?UU_U*?U?*U_UU_U_UU*?UU_U_ԟ?U__҇ItRNS@fIDATx^MEv@ [Lf Njz_%Ncf$wZ7vk߼~Qͣ?W][Rs..}]۵c^Iahc7obVI ty- b|E ̙> X\kD";WL]JfZ.T?\ FTIENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/hugright.png000066400000000000000000000012071342663516400253020ustar00rootroot00000000000000PNG  IHDR(-S-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLA]PLTEUUUUU*UԟUUԿU*_UUUU_U_ԿUUԟߪU?UߪU_UUZJtRNS@fIDATx^MU0 ?榫Jϲ4 qq!nz "ȫ"F`Vf8qf(KW9SY !0W@Ŭ䷐Pm ^Y.C^%=HKӿcg4#IENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/icondef.xml000066400000000000000000000105121342663516400251030ustar00rootroot00000000000000 Stellar (default) 1.0 Default Psi 0.9.3 iconset 2003-07-08 http://psi.affinix.com Jason Kim Michail Pishchagin (icondef.xml) :-) :) smile.png ;-) ;) wink.png :-P :-p :P :p tongue.png :-D :-d :D :d :-> :> biggrin.png :-( :( unhappy.png :'-( :'( ;-( ;( cry.png :-O :-o :O :o oh.png :-@ :@ angry.png :-$ :$ blush.png :-| :| stare.png :-S :-s :S :s frowning.png B-) B) (H) (h) coolglasses.png :-[ :[ bat.png (L) (l) heart.png (U) (u) brheart.png (Y) (y) yes.png (N) (n) no.png (Z) (z) boy.png (X) (x) girl.png (@) pussy.png (}) hugleft.png ({) hugright.png (6) devil.png (R) (r) rainbow.png (W) (w) brflower.png (F) (f) flower.png (P) (p) photo.png (T) (t) phone.png (*) star.png (8) music.png (I) (i) lamp.png (B) (b) beer.png (D) (d) drink.png (C) (c) coffee.png (%) cuffs.png (E) (e) mail.png (K) (k) kiss.png psi-plus-snapshots-1.4.554/iconsets/emoticons/default/kiss.png000066400000000000000000000011451342663516400244330ustar00rootroot00000000000000PNG  IHDR(-S-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLA`PLTE40H]yR <981D a9$uR]9,)srtRNS@fjIDATx^G0 RkzeHs&pXasX"짻<[M]Uxg,D4l_M G yhʼnZ _ Pg>I)%O|)]IENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/lamp.png000066400000000000000000000013671342663516400244210ustar00rootroot00000000000000PNG  IHDR(-S-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLAPLTEAs)Z 9JAb1)199{RjJjR9R `tRNS@fIDATx^MUrA^ef39?Wf6v$Tg6ܤqE?Exsߕd^Mfi3_60z5v"w;c4\^rz㑀K?@^}To Z@|1rZ-(c/ >캱IENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/mail.png000066400000000000000000000016031342663516400244030ustar00rootroot00000000000000PNG  IHDRa pHYs  ~-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLA cHRMz%RX:oZIDAT8k@LέnX`bq T5Bsp5#T01ARQH剉'*N|{tmǘ=x8wXQbH ؘ!vc$t>a5T^8\z!ռB> $ބ=} 0{au:&YZit{]X !5E@)uxkaRs{~ҁՈ|w|/]yShQ~ v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLAPLTEADA  $ 989A@AZYZjij{y{ AmJHJRURZ]ZZaZbabbeb ( jmjjsus)(){}{{9<9Ŵ9e{ Hc2tRNS@fIDATx^u5C1 EQ=3Sqe{R%jG9Sgȼm"Ƶ9ŐC4De{ sT Ol ]iJS Fm H7]M85\ɒg6IENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/no.png000066400000000000000000000011301342663516400240700ustar00rootroot00000000000000PNG  IHDRR-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLA0PLTE>0cQ-Mu)1Xe̯vγ~ҮhԹ{ҜtRNS@fIDATx^E̱ PxB8 ^YDۛ"6\B D m-uoz1!(K{YRQ+r4&`A5O1uP]baJ>g wIp៞h:~_ Z {/c zIENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/oh.png000066400000000000000000000012621342663516400240700ustar00rootroot00000000000000PNG  IHDR(-S-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLAPLTERRrrxxZZVVGG\\ccddkknnJJMMzz{{PPۏEETTIISSqqIIDD@@MM||]]``ↆNNAAff攔ggiiVStRNS@fIDATx^mG1|\ϗDQK v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLA$PLTEZ]bjmssy{ybjhs_7tRNS@fVIDATx^ʱ C6`ⰰI iR nTqjBEdB4qj wk /UqH]O>Ɇxt %?2IENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/photo.png000066400000000000000000000013071342663516400246130ustar00rootroot00000000000000PNG  IHDR(-S-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLAPLTE{sys{{{ރ{yլ{ŬjisZ]b149s{Ŭͬ tRNS@fIDATx^5CAa~`f޽ܲ8俢>Sу^MR.N_#a9hJ8aj|]D%S:sp[NHQp){ʓ# í=_TڻP4dLJA E9' -IENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/pussy.png000066400000000000000000000011721342663516400246450ustar00rootroot00000000000000PNG  IHDR(-S-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLAEPLTEU?UU_U**U*?U*?Կ_UߪUUUUtRNS@fIDATx^- 1 г%Be:WU0(@{h+Rݿ"~ѮG?O(")֊XKl|jG`i77\AIP,? ueE$o]#q[ IENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/rainbow.png000066400000000000000000000013571342663516400251300ustar00rootroot00000000000000PNG  IHDR(-S-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLAPLTEҡ(iu U@]} }uƥH0,$ i8,U@<((@@L¶…qmmyemi@L tRNS@fIDATx^EQwwݹ`BjՕI~ҏups+E& al)VzxűVVצÌJ =P9kԎ Al&p7A2ٸ;ܺM8}ro]Z'.!wDq(dWӄHEeIENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/smile.png000066400000000000000000000012541342663516400245740ustar00rootroot00000000000000PNG  IHDR(-S-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLA~PLTEtƧع޾|kmqŦqͮбѲӴhuwflՇtRNS@fIDATx^mG@CC Ced#=ɺH}\~V2c$X6`; r +p\ H5- u۵I@^@O?S YY#ޯeԥ5W~o@6IENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/star.png000066400000000000000000000013011342663516400244250ustar00rootroot00000000000000PNG  IHDR(-S-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLAPLTEƋڴs11 !tRNS@fIDATx^]U0C3a(2,3?#?{+'07;Ur.ז1܋t v fb_P *xۿ”V0NR&S<߫0:y{,|A.y&dIENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/stare.png000066400000000000000000000012531342663516400246000ustar00rootroot00000000000000PNG  IHDR(-S-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLA~PLTEƧع޾qtmͮŦkuбѲӴw|hqlfgR%2tRNS@fIDATx^m0Ќ{ v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLA'PLTEBBي:tRNS@fuIDATx^M1 P(ԩV. :ޥ+\CVѱ! ӷ,.~^<& o+bPg?B?"6 ؠ "}?Bj+IENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/unhappy.png000066400000000000000000000012651342663516400251510ustar00rootroot00000000000000PNG  IHDR(-S-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLAPLTEL,Y8cJsW_ft$R*W@-Z2^7bA;f?iAkEoERzU}IY <>hk)VƃF8=gF ;7IrMw~!OŒ^ntRNS@fIDATx^m0ЎPNUh>.dICO%Jcyj%Q/qD l VP ~:TJAkщI@RI^B_U v4!p%wҮig/rv(,lIENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/wink.png000066400000000000000000000012551342663516400244340ustar00rootroot00000000000000PNG  IHDR(-S-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLAPLTE½w@PtRNS@fIDATx^mE@C]j?`Jy]]"r O=QE˺h%ʌѝ`ƺ5J/6961꽷2ע5&B,yh=5͐q8ZE0"Զ1~0*˷"dEIENDB`psi-plus-snapshots-1.4.554/iconsets/emoticons/default/yes.png000066400000000000000000000011241342663516400242570ustar00rootroot00000000000000PNG  IHDRR-iCCPPhotoshop ICC profilexc``2ptqre``+) rwRR`?> v^~^**`d`v D20\@J.(*(%8h]^Rgd$e@좐 g ͗a_ ' v@t0l-bVep/,L(Q0TpLOJU,.I-VK/*/J,IM ! A!h@es 8|  LƄ3H00/e``3e`X?!f o³OgAMAaLA0PLTE>0cQ-Mu)1Xe̯vγ~ҮhԹ{ҜtRNS@fIDATx^E! @w~fMbY:a0XĶ>e}bd,e0'w⯼Ozo8-ilkk[L{]B7HǙ6d# 9.K v~~숳'(9_@}] ב6IENDB`psi-plus-snapshots-1.4.554/iconsets/moods/000077500000000000000000000000001342663516400204505ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iconsets/moods/default/000077500000000000000000000000001342663516400220745ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iconsets/moods/default/Afraid.png000066400000000000000000000004471342663516400237750ustar00rootroot00000000000000PNG  IHDR(-SQPLTE▖.,Ͷ/1BDK],EtRNS@fIDATx^M 0 EAm?*!y`> :]s:s^) vkfiI1ƤPeiWUCϞ~p_ @aB7x"CvOsH#'3\IENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Amazed.png000066400000000000000000000004241342663516400240030ustar00rootroot00000000000000PNG  IHDR(-STPLTEXIIIeJx333)eeeRͬOފ~ʁHJ'qsxQ^;(kX>,@eIENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Amorous.png000066400000000000000000000014641342663516400242340ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˥]HQwEvuEuS $]؅H QEK,E Զ馛-ʅ=uهi~)~S1*yy{#dWs>gccj]]rUURy\aivR99/.%0llkeRǂ=WPw`3fV j|&:C02f3z}aGAH13LJ٪lu? /AS#PS#ֽzqv Roܺ7oBQp>}phl{dP'j>hQ  ۀIodX_܃P/P~L&c:)OrrQQlر9٢4rjZzۓ17  =T*:/n(^=]iQJ%Br\$XƤJAok^ޞKP$AqMSֈVU$sOs!$TGцgZL'SS{3qIWU# p )gIb7i7~'IENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Anxious.png000066400000000000000000000003751342663516400242350ustar00rootroot00000000000000PNG  IHDR(-S6PLTEΙ0۪3 IENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Ashamed.png000066400000000000000000000005161342663516400241460ustar00rootroot00000000000000PNG  IHDR(-SfPLTEۺəMŔ[רzm,Κm޳yD|ϛ^~UuƾѫC( tRNS@fIDATx^mGv0c겝vK,2; (})c-tZ~Z5o)Pޚ9k?:Ih:aQ(ib+Im% Aq6\] ]m1Mgm9/~_CͷbIENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Bored.png000066400000000000000000000004201342663516400236310ustar00rootroot00000000000000PNG  IHDR(-S?PLTE]چH'X9T6IENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Brave.png000066400000000000000000000005431342663516400236430ustar00rootroot00000000000000PNG  IHDR(-SPLTEފxS2ߝߩelaߚ$ףtyfd։ї쳮N*۪3f[tm<εV\h1ۋ Z[ V6:pRqHD"4C :?!0oQa)،t2o# '/5UC{ ګ<_]_>^NG1oIENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Cautious.png000066400000000000000000000007641342663516400244050ustar00rootroot00000000000000PNG  IHDR(-SPLTE̅;̄:˄:˂9ʃ9ʂ8~3~3ɀ5˃9ʀ5~3ɀ6ɀ8ʂ8UVP'LUiÄȞo꾆4S]l}8ɂ?@|9zRKMMK?Ig͆;˃:ɂx〺 ρ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.554/iconsets/moods/default/Confused.png000066400000000000000000000003701342663516400243500ustar00rootroot00000000000000PNG  IHDR(-S6PLTE۪3D<֍q8UUYΙ0]0@PD'mtRNS@fpIDATx^mI0 2ZekَtHb&k(R&?*"*B~R]L|ꇙuhOx2YFKAݚ)΃,/̟IENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Contemplative.png000066400000000000000000000014161342663516400254160ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˕KSq}ћHoLL܋"솒!PRtR&$^22p31RiSy7񶔍m^v6n9~.ZZ;MIENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Contented.png000066400000000000000000000004221342663516400245230ustar00rootroot00000000000000PNG  IHDRR-PLTEq8]Ι0U4{)+&4~/?UޝIENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Cranky.png000066400000000000000000000004201342663516400240250ustar00rootroot00000000000000PNG  IHDRR-PLTEq8]Ι0KyG" oPldAd1\?V"'IENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Crazy.png000066400000000000000000000012101342663516400236640ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8c?.5SˮzX݃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.554/iconsets/moods/default/Curious.png000066400000000000000000000004531342663516400242350ustar00rootroot00000000000000PNG  IHDR(-SQPLTEiu]|϶銭ؾ电EsjھөK}40tRNS@fIDATx^mG0 DQ SXrd Qt#=JI)Jt75} YuZI569\7:U R`A(Z̬Y"1`_.Y9tTYdGp]\yn IENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Dejected.png000066400000000000000000000010131342663516400243040ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˥S1k@] ?Cҭ[QN N (v\[ڡd)8  vSPD0KI~W }ݽﻗw/Զmz^4ʹa.IENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Depressed.png000066400000000000000000000004211342663516400245150ustar00rootroot00000000000000PNG  IHDR(-S9PLTEܦ·xwweeef;tRNS@fIDATx^uI0 c! `'K=_d* ÈRS>CSzJ?NIENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Disappointed.png000066400000000000000000000004471342663516400252320ustar00rootroot00000000000000PNG  IHDR(-SWPLTE|]ክiĩu6^EsK}oxwwvL{瓦eee@tRNS@f~IDATx^UG0v3 '0oa0t@_nUD={*qU`yHJa~YrakYr*q+OSiJ@?/q0.ܝ 1n:1с8&kiIENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Disgusted.png000066400000000000000000000004011342663516400245300ustar00rootroot00000000000000PNG  IHDR(-Sx+J rolHl>9YN{rJ7*3h|Uo<>IENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Distracted.png000066400000000000000000000011061342663516400246660ustar00rootroot00000000000000PNG  IHDR(-S PLTEŒÚ]mfw铣jx׿ս־m|͸ڠpѾަnj[j}_m`oapfs10tRNS3 4 [ZV1"x!=IDATx^MrF"m_< HJ<"_,qlKJtNz=,Ǣ늗<`(l6΋cX*WZR. 1Q^7L#!ȴfmu0P/ F2u>T}:Ҁv7_Lonbq|g.0hFIENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Embarrassed.png000066400000000000000000000004031342663516400250270ustar00rootroot00000000000000PNG  IHDR(-S6PLTE]Ι0UPDq8@0n1U۪3tZ 7[x3C aƢx J\PVY= !]횔B2W{"꡷(1A#6{k5mf0^)-IENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Envious.png000066400000000000000000000014731342663516400242370ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8ˍ[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.554/iconsets/moods/default/Excited.png000066400000000000000000000003601342663516400241660ustar00rootroot00000000000000PNG  IHDR(-SBPLTEBDngZQrhh[蘭폅ԍ/1|LH &UtRNS@f\IDATx^7 DэdUeua#"%R'"X\Ȏ{"@$$8 ?ۖҲ #vHqv,z!^QG}IENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Flirtatious.png000066400000000000000000000005201342663516400251040ustar00rootroot00000000000000PNG  IHDR(-SPLTEfItISnA˟6^m,^yD|(޳~UuoΚmѫרz؟ۺ۽Eseee+exww/]]vtRNS@fzIDATx^uA M2.qg)֝T) Y/fT|uDZYWx WS!9KyG" oPldAd1\?V"'IENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Grumpy.png000066400000000000000000000004041342663516400240630ustar00rootroot00000000000000PNG  IHDRR'PLTEX8n1Za0S9@CDU^tRNS@fIDATx^E1 @E'7"kTvn&+[X,sT!i [ll-;R~3a^Djg:jV֍7ޜ1-vo=AyU"f 9SNA41\?>TB,IENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Guilty.png000066400000000000000000000004241342663516400240570ustar00rootroot00000000000000PNG  IHDRR0PLTEq8Ι0۪3<]DU@0PDUYDltRNS@fIDATx^E 0/ou )A d E.unv#8u !7}?QDgKl*yK*5PM;F!Zo eXu[¸C-$Nkmz͕uf>JP;~}Q IBuIENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Happy.png000066400000000000000000000003661342663516400236700ustar00rootroot00000000000000PNG  IHDR(-S3PLTEq8Ι0]۪3DU} ?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.554/iconsets/moods/default/Hot.png000066400000000000000000000004121342663516400233310ustar00rootroot00000000000000PNG  IHDR(-SBPLTEXexߩ2dSd<ފJEt)Ёf}tRNS@fvIDATx^mI 0 C-yJ:WtS-, {Ļ${k}#p7S5TIcD2RJKU~Оs9 CIgOvXW\~uKC}JYIENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Humbled.png000066400000000000000000000003751342663516400241670ustar00rootroot00000000000000PNG  IHDR(-S9PLTE⊭ض_Ԕj+eK}I|6^Es15tRNS@frIDATx^U[@DQyILSZU*sFk2x Lx~I*kΧHnp09=0Qo R`EDڂk}> [1<1IENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Humiliated.png000066400000000000000000000004171342663516400246710ustar00rootroot00000000000000PNG  IHDR(-SEPLTE<Ι0@0q8YD۪3UPCUX8SDZn1@DYtRNS@fxIDATx^m 2ٳvO'@O7섗_r]/c~8{'m:B"Uܥ-24T{{ # 6AxA/"RgA/ :2wIENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Hungry.png000066400000000000000000000004071342663516400240570ustar00rootroot00000000000000PNG  IHDR(-SEPLTE7n1݊5L*/1Fa/g#X44L?~9|(ABDGДtRNS@fpIDATx^90W[Qv(xuB1í94MxN'f6ݘm!U4%/D |Iw/[6RWTE??ҝhIENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Hurt.png000066400000000000000000000003771342663516400235330ustar00rootroot00000000000000PNG  IHDR(-S9PLTE۪3P0w!SuO?&[[=IENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Interested.png000066400000000000000000000005351342663516400247130ustar00rootroot00000000000000PNG  IHDR(-SrPLTEeE<9j~9MS0NIEsh[v|rhX{w@[ZQa,ߝYu]BD]P|.,u7tRNS@fIDATx^EW bNje&$/yފ(0/AteX7U۪3Ӓߚ$Ŕ[dߝΙ0&tRNS@fqIDATx^UG0 M5l/"l604G㕜Ѻ{10aoުf6~ÐT lL @f(o tBG+IENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Lucky.png000066400000000000000000000015741342663516400237000ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8UMheoٝov+/4 ` PE^O) x"DЋz(XEi)H 5g&ٝ8-=g69! :}gV{L@1;_ο0|q$.6C-ş+eIQx״gcsGm"JDZ#Ģ\G-DE+,(#"Wp!G 6!!6.2z.io/e42iB$B"4`z0rxѪ^5il>6bE`t~w]Wmrwf34H&\_3fu!|Dp@Tܞ4PS#*Pw&B;6Y$ ZŽ=DUemӁfZ/Rm Cz/ tB ̛NJ a;rd N,XX'>Kry88 x__Y7ͫʹZȋ ==tajD_RA#(Uxf84ݹ9n2$b'C{M9Hj9,=ݫ R,2Z˾u0(fԘycRݵt#]畸ݿIENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Playful.png000066400000000000000000000004631342663516400242210ustar00rootroot00000000000000PNG  IHDR(-SBPLTEҦøxww⇇eeeIII333atRNS@fIDATx^5W@ar|H^_կxAuu !tV-a},,Dٕ'JqJM 7˵22ލW R0BOcsOsя7 _xvu:(D*j ?rkfZwIENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Proud.png000066400000000000000000000003741342663516400236770ustar00rootroot00000000000000PNG  IHDR(-Si0r IENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Readme.txt000066400000000000000000000002731342663516400240340ustar00rootroot00000000000000Name = silk Author = http://www.famfamfam.com/lab/icons/silk/ Made for Psi = maksim.maj@gmail.com, ky6uk@jabber.ufanet.ru Date = Thu, 25 Dec 2008 01:42:32 +0500 Version = 1.1psi-plus-snapshots-1.4.554/iconsets/moods/default/Relaxed.png000066400000000000000000000007701342663516400241720ustar00rootroot00000000000000PNG  IHDR7gAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT(=QMKQ}R?$t_"UDBRnmZ[ˊ"?MPFT RiWeEELws=>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.554/iconsets/moods/default/Relieved.png000066400000000000000000000003711342663516400243420ustar00rootroot00000000000000PNG  IHDR(-S3PLTEֱҦΖxwweeeybtRNS@ftIDATx^MW! EQdŒ-q 3DS2 @j0CBD~ڙw :L0!a8J`.Q!ܠ.2w9޿q,wIENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Remorseful.png000066400000000000000000000003451342663516400247270ustar00rootroot00000000000000PNG  IHDRRPLTEFz0SAb,a$a,E8H,"JtRNS@fxIDATx^E1 0E$aMUR.Sx%~q )qu'~N " O-Xپpla~AP%7oPTv z~ {;IENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Restless.png000066400000000000000000000003731342663516400244110ustar00rootroot00000000000000PNG  IHDR(-S3PLTE]PUjLbnG^s7D^F1Kt&^.]"_ KAf^$72{|Fpʈ6-pgG1[!IENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Sad.png000066400000000000000000000004141342663516400233100ustar00rootroot00000000000000PNG  IHDRR-PLTEq8]Ι0s~|&eCQ$4]6IENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Serious.png000066400000000000000000000004451342663516400242360ustar00rootroot00000000000000PNG  IHDR(-SWPLTE]6^L{Esv|iĦ 8eK}_oPtRNS@f|IDATx^I 1 -ɻg߳i 9RuaK)؁`%Zuaya@Υ%ޅ&P1ݴ| N 1iqb(cLZ4c-2e1nDBqDIENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Shocked.png000066400000000000000000000004451342663516400241650ustar00rootroot00000000000000PNG  IHDR(-SZPLTEܖҸxww汱]eee|Ϧ锵vjioEtRNS@fyIDATx^uU0L""ITu`Cf8̐iaΩ-{6Ǥ[^:qHl*-z$t[bHl73)hWIENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Shy.png000066400000000000000000000004401342663516400233430ustar00rootroot00000000000000PNG  IHDR(-SNPLTEeeeIIIxww333_ñjڸiαӠߝ9vqF9tRNS@fIDATx^W AЮ2tyUA(X.Ď}Xq&9RЭo~3 ؃BkMAFxs$h*H[ʢ'~p Vg.\gUIENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Sick.png000066400000000000000000000003721342663516400234750ustar00rootroot00000000000000PNG  IHDR(-SHPLTEng.,ԍ/1LHBD뮪^tRNS@f`IDATx^uE0 Q)3iT"{MwjuyܪW22*1s{ao, '"ִ-mDsf;L_YOIENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Sleepy.png000066400000000000000000000007261342663516400240500ustar00rootroot00000000000000PNG  IHDR(-SPLTE|t]iuƏ䊭K}Նӱßo۪36^xww禦Esxf<)t쳓ͬOΙ09t2ЁCdǧvߝJҶx BTtRNS@fIDATx^U0 -39?3U!w"|oߢpw98yzcr 7eA (vdYw^cݶKE2ĈmНmjIENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Spontaneous.png000066400000000000000000000007041342663516400251210ustar00rootroot00000000000000PNG  IHDR7gAMA7tEXtSoftwareAdobe ImageReadyqe<VIDAT?HTݝ^ٟ ju ɥpOhi/8ԩ!p*0Ў;}E,tnM2|13 "byK$i^NFDЙ;4eZ|Uj|jf^$ig)kYbIuKWPcJ'U>4MKB݆eSJtO9iԄ ʆn#2u` *_ R4+*BuϘMYU׷oAaLG۽kE|"2w9m[yGwSB3{er~㫹<{ @% @IENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Stressed.png000066400000000000000000000004041342663516400243740ustar00rootroot00000000000000PNG  IHDRR*PLTEX8n1Z9SCa0@DUhZ|atRNS@fIDATx^E1 @77MvD+&XEXH-*V)r֩Vv q"HW<h?%/h4\ݞnGU3z13"v UË!v )bew6@^}༣JIENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Strong.png000066400000000000000000000010351342663516400240550ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8œJQ}(&((@g*NPTH!1Ff,4;P ]YRaCM:3HG]hЌtn6{}{k$P\ ;wg[_HD+A൛ T)3x.'(}es˜yfe\z1j]~`pFм"zg|.h kIF@B/vpbT%)Cw@ݯQOTinZLD?d>R56/hBnbiy$CH1hjph|?+G!W`wq ttXOmd/ϒpK*M4Ǒ P&K#" YD߶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.554/iconsets/moods/default/Thirsty.png000066400000000000000000000003771342663516400242570ustar00rootroot00000000000000PNG  IHDR(-S3PLTEܱø򦦦oQtRNS@fzIDATx^eY![`?mTKQ(0Qۮ`-e."9v@m]V l!OnY׈X\H=qx4q߃h?O?IENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Tired.png000066400000000000000000000006621342663516400236550ustar00rootroot00000000000000PNG  IHDR7gAMA7tEXtSoftwareAdobe ImageReadyqe<DIDATKqC#nhhUhp@hm{i({C!3}@˪\^m<<DڛaSS3A^_G9˿L;'9yy+؜|wc*y`?6zwf*]cԕ*Ek|7/+Ҭ(FŬD(jd *MvfP_Ѩ&ҦrY14PETݡM}:QE~ ^,_qMgNL}O+`}-sfƎy5VY2o;@+QIENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/Undefined.png000066400000000000000000000006051342663516400245040ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8c?%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.554/iconsets/moods/default/Weak.png000066400000000000000000000007411342663516400234730ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<sIDAT8c?>\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.554/iconsets/moods/default/Worried.png000066400000000000000000000003771342663516400242240ustar00rootroot00000000000000PNG  IHDR(-S6PLTE]Ι0PDq8<0@۪3UYU9Dn1a0F9tRNS@fwIDATx^mI! ؙXKZe׉E-]QF01gU̥Z\ę{ oINXk4>toIH 1şsJɿm FIENDB`psi-plus-snapshots-1.4.554/iconsets/moods/default/icondef.xml000066400000000000000000000213741342663516400242340ustar00rootroot00000000000000 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.554/iconsets/roster/000077500000000000000000000000001342663516400206455ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iconsets/roster/README000066400000000000000000000024501342663516400215260ustar00rootroot00000000000000Roster 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.554/iconsets/roster/crystal-gadu.jisp000066400000000000000000000262071342663516400241420ustar00rootroot00000000000000PK 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.554/iconsets/roster/crystal-icq.jisp000066400000000000000000000262371342663516400240010ustar00rootroot00000000000000PK 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.554/iconsets/roster/crystal-roster.jisp000066400000000000000000001270711342663516400245410ustar00rootroot00000000000000PK 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.554/iconsets/roster/crystal-service.jisp000066400000000000000000000250201342663516400246520ustar00rootroot00000000000000PK 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.554/iconsets/roster/crystal-sms.jisp000066400000000000000000000253531342663516400240250ustar00rootroot00000000000000PK 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.554/iconsets/roster/crystal-yahoo.jisp000066400000000000000000000230441342663516400243350ustar00rootroot00000000000000PK 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.554/iconsets/roster/default/000077500000000000000000000000001342663516400222715ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iconsets/roster/default/ask.png000066400000000000000000000016721342663516400235630ustar00rootroot00000000000000PNG  IHDRagAMA|Q cHRMz%u0`:o_FbKGDC pHYs  IDATxm=hg;N:lZeǥ J{1t!СpSȐ-RP8XmP$l’*O}']Py]^_ͭ(3@MQ^T;-Ph,JU*W w7cMx|vllmooX,4  Xu) uO8Q.4 ˲ly^Yu0fEggx

] iA!D}6i<۷ UBu,ˢWJ<4nA2~>|K8|_B#  $~ 7NmͶi눡!hk#aT*Ugkkutz˗]*u~v/ a|Y}[&J&@<Vz)$$D˺RP 4//r3^A *IENDB`psi-plus-snapshots-1.4.554/iconsets/roster/default/away.png000066400000000000000000000016401342663516400237410ustar00rootroot00000000000000PNG  IHDRagAMA|Q cHRMz%u0`:o_FbKGD pHYs  IDATxڍ[hSwsIIIjh+[TV+&D}7acna8|pLXhKzMc19g}O9U1_1*5mteTwNm{FPnž-X~,GЖeDġ}N-7}qx[[zJ| aI/M&i*lT&m_|yk4Ɏ,͛d->W>=0Qڻ$:G򢴯'IѰ06EK`29[d^P 0_\[ ͳUflmiol,ǦQ0#"H1 Dɥ:c+VGC0un03`eA&3y3u%佡dߺ+PRs;<9hgqp@\E*nQzh2 TE_.*0H-mXͯs] =.'4\! M40$EBٹu`#l+VPe׉;6(E5`vSSKs2fytx" ai ompbrRVN]rqn3٧!ǿz1-ɸQ7cOLA` <#r  ߀Ћ\'E1 xSIENDB`psi-plus-snapshots-1.4.554/iconsets/roster/default/call.png000066400000000000000000000012621342663516400237130ustar00rootroot00000000000000PNG  IHDRasRGBbKGD pHYs  tIME3){tEXtCommentCreated with GIMPW IDAT8ˍkajMR*mn"RpFp#]HEW.tF(@,uSDlUBS+Mm3Wd&3E6|-9sUDE':ҫV}cQ l%ILn7Up i6p3P8tNi5(^IUdL_,eoym;:%,-]l!H0m ybi ۫Whh_Yԭpn [eZ Ƙ3bۖEqD}<|\B[:tgd aD8 tUojق)(?AO"6.U`A?@߮VR[6XJ-B _\͗ tH5`0t[K?%Hb*h'=!Wl(wO@4/-M{ou|@*˂C+E! չ@:w$QIENDB`psi-plus-snapshots-1.4.554/iconsets/roster/default/chat.fading.png000066400000000000000000000212721342663516400251510ustar00rootroot00000000000000PNG  IHDRPWB pHYsHHFk>bKGDC"ZIDATx}XSYv(9X@ذ"* (6DD,H(T@Q(z B pnp|7;zNΛs޽;8-㯿r%([?OwpP5lrnp&7ws\|.>ɫ[h;Nǰ'O0wgCCS p.wO,s\|.>?k8QddI YV6js7߽ðSn İ'^°`jh։sx0Nw GCR[[7ׯ1E7/0…7>Ű= ,tY9Bk6|)-.uMWz E%ktu59N ܾ%ݼ w>~any[ۤ3zx nw Ո֌Q۲;'3__ swswǰ>{)M;n!U.>_!A~ZzbEgbbddP'ED6ac[v]&xU ~[o>/[QV^HnQULLrrDģG{X[Akj0MLxly/ *)=Ó˓;PJRl$bcÊRS99AAfi,_Q!$&$&wt&3gm| \7-99kvBCkkSR~˻xê޿?yy+)JvLpQ<+edi4-kHJJ$Jo.VVvv@@q1EFfd44`XqqmmG /-}~~ͨ^|FgɻwmY\|.ψ//5MTਚrK J`GLJ j%sz #I$2,\Wa5AAsRwߘNF67hԳgx+PƷEcEG =ھvqIɩU'!>YdVW֖),[[[T(a4_吋 ׹Sf]}>=~}uo KI3\(3>^pMy~~t\L0*2) lMMr2gFb잸Lj/μ!5UZ^v\ ''~eA|ǭ &sմ+ummL]i+l[`zHx>e"lAu|2^[UUMc#R5yFF%ʇ lyur:1rNcՕ$zPoo}=YZ x99`iLd& ⾾J~ىg G{i{uwk쩶*nn(ir;e{-t(#p)8 8Hk+kicJ|29$<^^U;4:)SGGpw]D[!!ĉ/G>y֨3=MM/'R(0!°B$T xdRR)\m`iVk6O}UV~S̛7&5ihv}"p5- 0emO}l*YQ^ysZUU()ĽΆp )(D0,;lN |?IIG]奭a6.jZ==. vx/О6?%??NC3x0eI{SG``h(y_[}SPaOWma0s/BCÓa @Bxؖ0YC>YAAQQ<0XKM "*d+*vv&H:aB||1Egď^v6*A3fd /XL@L">Y(Tp>> `h%~RRZZP f)H1b Ho!I&#G9 }|^Ű2 9Dmm>lM 8^XAb/ݦrcƅ--s !HAup@ |"ݳ=Lp/*odTA8`33ϞE BHcXV1 KMn})%~kk]ݛ7pH>>B;z! N8/8qlR`v BFڵ`눹pN_N?N?H^eDŽQQzz/mlD߷SƝt:\|.πol3: 4 f|_~4EG$%ۛ~}, DX(zD==h&n4B3с J`fFw}n͝h7nH9cJ "Z䨨X>~D9zpYYп3v|2 F#inFkj5H)HLX C$"PUdE&QPr&Y>Y5i,;:DiG%%PćHDRp~A fB,0!TX޽H_WWM HUW#QY{y9r쥥 8f!cf6-ūY[<(*׏OOZ*ԏrԏ\ lPď@@8yy#pRS<6: jϞ_aoJ&/+JԟKC8yy(♝^Fxݾt--aaV _ 䂇PKkli4s7xEqe62g0~HO/)a]y9Vq?2P5-_K5;cjvwMJ9[dd/Wl+YG$BflA3^jLUe#3 *ԏ!Hdf"ᕚWBD`y wneE~VNmkH6ij:=5EKKN~>On.)hD#ᙔgl,QQWXFҥwҒIdnn?[9}9}szs8%L7Y MIAdeR?}bPes9t`&d`_Z4,{JJ=+a`-2[/ϻTj:/tKz{GF"::;tvBm̖ s1_w-} T)Wk<n"23Q  bEϳS҅P{1HHP\ܩS,4[K˺j,%=N/1OLRЯkT++P1? cǐEwVD@xf޽0 vİ9רԥH^o<)*}X|Ѫqƕ\%蛯VRJgd DQ" 5k? 0 ({@Ǿ}?[9}9}szs8&Dåug :8A-F ;z&~`0>?劗|!dQi #^% 3%Ed_XY餙&JZh㿶L&4{{g8ȿkkp~&=g|!/'21=\Y[R]jtu-"L&&!ќ);UUBbzzc;H DKA!2yS ~Au;g &ܹdunq\\ssz:Q44XɤP}tzVD@]ٳ*hhGFK޲e,>kBe +'})&EH$`kjPHDw;:J$BbGG\8`!rrO]'6haoW]@~Y"~H`Wy9.ٖD:IIPi/zX|弿Yϟ?}!Wo76#WTV~>Hx`GG ?<<)*J2j 9-O+cq<<3fM8"e|2 L|H`fdgv6ٛ6ff9967:-836(HxxqI-SS\2LN_N?N?g͸8;){60m wr%.^Z;AؗGTy2 I7SR\|.>'qpG%$V ʂyos3DPanf(;:ayZ{Ho?$$$Wb=6-(^Rirٳ@QQqq0ѨTTׇf ߿G>##4̄?ȾMY@JhYWYe+.ڢ7$&&$@mP{;Rap]bP=:FRbc0uup+p}jOQlSl[x;JL$N$"GD`{;Zl{zH$MOpy9 ]wr]Y9jqL\ _X9aay x{A (9/twWT GKB&C N $&"ٯO' leE ޞrh(/E@wBX៶9=9N?,'(b-ǚ߻y3;v"ֵ--8YKѭ[6gvkkXŖ]^}}ðQ+NL'zv/s|_۷>Gv#CW@jGOe s\]a;;\G:8ANOOGsp{G7ۘ )EGG|_D"&Gbmp%%ee65!fKLrNWy⒐߇.[[WWE߇ԜÇz*+}}uu@FLfZ 32|}UUaB 5xyA ޽Ǐ!2 ~ڷ!D(wrrsCGRS{uzv~ !/r*߄DXu57D.=,"~'Rt$22l&qvG? *+wJ`1ۍ Kjj~',|3e]1Bz™`@J 8DRaa^^PgRsܚs$/ ?~vQ4fsu`D5IIϞ`9S!0{Zl~UBN?0~VaNVSbcS\ @۶ ,ޭ93E{H_cX-ZUh|_N@ahXdISӗUTN_N?N8>۝2Ecp8CÆɮb]Lfnbm==Wx}$m'9Q֝;l^4®;`@xz [a;U*:&%Eb'RL֭C/W?lzB g\ՙ4<fm DL_76;y#Hx|V~6LCC? {O0Tk+tR79.ݓ6qq66˗|iѣ699-[]wVL4}G#F|ӧDЗOMƗ$2_lֺjfoPܺ)(PvB`ѭK/^;K6[]*9sOOe.+|>)o]pV֓fw-z{ gmkre›IoPB~뮨Ձ '$5}vwwY٥K_bKĮKiY[O?Dy[[rQQA+ffF.IKן??߿q㐝=ٵkY+w[[{er/*B/ϏEc&`3_CV\|.?&ܦM"'Hu;9M?g:$y4>' ΉOq`2~j -)N@|=ٚ[E-wڔs|DVp|'uݏQfiw.Խqw`-cqZ@6==⁌gx W>WZ>NC^Ww?w g _ODCO1N5.xx ظ?SŝUU-\-o .'}ss4q=۸۸۸۸۸ß=*IENDB`psi-plus-snapshots-1.4.554/iconsets/roster/default/chatty.png000066400000000000000000000016451342663516400243010ustar00rootroot00000000000000PNG  IHDRagAMA|Q cHRMz%u0`:o_FbKGD pHYs   IDATxڍKh\e5/otIL5M!QӅR_U. MB* "Н.$T IM<ɘy%Lf23܇*HP[$k>uFwoqá8u݉z~.}roWh?~3X9ҟ9l\r 0%MWSo?JߘCK@Jq{7mǧK4uCl Ö<ѡР?t`B J.*l2+UXd3[|.zDwΧZ;AyhVW8h4n1m{ F|wgBCd"#%?e>6qk5րr,>>l>!♿P1976 " `:hCg盋ߜmDveK^4Jlc4*}~A09|G$8~O]VôCy7B qĮoO S`g>MtMB<8[~dFW.(T_RTTn3 O@a#S)fj*^1>m=_17t Sm$:^"/.AHS?y|&K&OWoSv FF `puu.ڔA1<Y σB$7gyQ\O4Dӊϡ6o^XȔT5| d<l%75,Qd٠4  Ycxv]f$ߓ0IENDB`psi-plus-snapshots-1.4.554/iconsets/roster/default/file.fading.png000066400000000000000000000266531342663516400251610ustar00rootroot00000000000000PNG  IHDRPWB pHYsHHFk>bKGDC-KIDATxwtUŷ[z'!!!. 4PQ4A)|AB@ $ {ov&es-g3饨hrW{V^~ᰮ JPaF~#o7'E?mh^^Tqӊ肀GU̫ |!O LFi:{NQ%%ַDj-n5vueݑHO(Z,E-eڔq־v:7e 0Qg4ʒ)K_y"F]6R!t"fx9xp/- Rݒ햇utжiV\n͵ B;9[ Jߩi_^a;9Ι2{:"T2G$ٽ$?8u{zԖ-> NNo&pl Nsyr{JKԜ<|ͥ3w-Eb.+yyl6zx㪩F~dE][?zO # k.^+-Jm^0@vXȊJ4 B1x=y]5cfƍxq:]NCg=,;/zS~ΜVcNtpzA{sveEhe{eI*'&`n׿-u]ln1 V/z \"Xv_WcB!W6jFA, [<ڍ LAA]eg$ޮooo%ک;pS=Bq'צM{^йs}Bsu횔p/(A_m; r-B_nANF~#/ZVqmN΀{v4mb:~3)GK?~zwj:zOy^jav.Z ,wQ{PTLmې++j\n$ۧ;˗L꾭>5*|X<%Woh^ZN,0 !T|`|ey% EE(O'*)\nk-HDԙ#4IBDɅ/q|JzYvN/tL&T-m'i$..6VG].A(y{Tiidf ]}V<g}x/ryuM0O =q#_5lpXR)FOs_)z\|F;L߾? ZiLBvWRVgd2Y/wmbQ]]-rsʏ"9Zq8'Hܸܶ. B!CRx8bTV|f6l5OJGOXN\K-+3^9~2͖ke˗V 34p.BaΘU$~M&O6Th4M݂m <*U"F|7?kװwq;ak~܇{{[3ّSz$T_u $[ ~*թNH |iAgѢW~8dZ>OyłІoo伿r}+=>!uv|;=n/xbY2ĉJ]n={KRjb͉+9bpDx멩99TVB(5UUf33ܟR;5y>|oZAp^'je2!U7ZNXV[RA#)+C!=K SS)ޓ/V(gse2v0=U:8i{|S7|YZ]vߟZ"- \ai a݃Џh" N V Z-S*wT2MV4Drql(FTq6+<C8W.[^_G 11f#͚y 5pBebCFr%66⛢w'>>dzF#,4]O.hJVmS F.^6.J<} JPkw67’l 911 eJ"@ϧ[>hJ6W|zRjvʬV}dH_SԄuŠ^GǴoOQj5;150R( }##q)3:w jȏV/RU,E7a!Յ"{,_:f*L|H%\ݎg1 ҭ[7jaT]'J\͛'Ǩ oB;I<.H변*'DG/FPzZT(25-ԋ7vkj^8pe2&uS#cU t:e04zv˖mWF-45]wͼx"#eiEvԅC&{vv[O+ CM.fs8`5"2v۷G6mjӦeݤJJ ɍax؂oF7PD=>4ׂjeq_ܷ=߽5]Hd"L\T*6RzN6iy }L:@:^Uu=͛us{Jo&mm=Ow o,||@l c6 YPFdk0l]eaYn}ke6_xɢQW?xիs\CYt4M{t8PmN 3G݉  qn |2" дf:L8c>wСrZ&T.,)zeȅC<+v{{{{-Gmwycuyy] {Y|IFAHICu@XTS ؕ+={8Jx QFvt~JJ#M>_┰Ν{VdeÀ#d \q' .Bnի7nܿݽ.c_C\Z' 5UmΝ/Gls<HDX"[n~~1sߤ˚oӺ85Ӌaa['%5ox  |Ap!>c%G*5n773+ <ܹu \(n=;/} x1EN~o]^fZC.(W#Ap:Aد!|Hć\({<. <}Ξ5?xaaQQـ8`C4p^>6IPa*,,(O^ރ>{̽=KӣT ; 4|P362 h_^^RR^M$iNf/0OH}-84rX҇x]-x,zO4rBLFC/6ra2A:qщ)Cj?өEa1;7xs:_R䙔\.KH7h*m*HꆿUaٷ֞unYɿ㟷o? Wܾ3)}r2T/DXXXZ *~X" tga8׭V |CANB3s[p#Qz'<I3IHNii) ?|I:XL&[,f3m}NsCZ47|eKM0 {K܆gH1HgZ,,B,Mϫ_*NNρēO0$@}=J(l55Pn6[m-|IÀM\3bD}򪪑פ T/J w8ZVAP@nn9~<Ҝxtq\NRi(GAB!|&L.$Ea~2f=ɏU>or2k4[%#hK&@gg B:VCz [,!J-Cw>T:-CKQ~~~Ox %|DeaG|G_Sla/L%QNϺ%ONjWMKʑ@pB‡R>;r(ƕ;MSE }o׿ۿ?oޒ7TxyUZK"lv;YQ>>dc0`0h4@jV1ET' `v$hn+G#,K*oE4,ĸ`0B- { K>J%t ._,02*dgJnjIIc( pD/ #P" Is*D%.H-r61O >df"-CVRF\tZ-{5YWOp8n_1֓)'^9XQpL0BˁxT* R)$ˡ\NH+4G4  ʍNޭV̷o??tWĹS%%RI4@ !㈆h>J|Pjow 9-OM}q8pE rˉfQ.'_H"퐔Bܽzsz&vOw>99K @><JBH)sh$$|\$@q4Mv@e΁Y_?㿷䟜] fdT7\PZ>&&2ХK6M4IP̐,׀ޱc~8޽ 0~_Ro{sdYUEɕI~qIiv5x= x$+Q"E*= r%m꾬&NDykŻrrbe.ߢEN횇`8r`b!b0G^vg-#+lv`環5S2.0G~{}:||˖GUGd%|O羘k:.N99{Xݼj矾jt/8{QyM4hĀ0@ L4 `o]u|wZeKr]?cƼqh7͚EFFG/R x@4R$40ikʽYX5׮\,ↂ1tp _gNPŋI'"pVd1&8"ò䙯TVByl<ݦ(lw/\0&ge=)Yfr”cmED "9U*z4PJ'EMM3P\ǫ1azg'ޮooo%wa"{+x3ߵӂ(*+F,d (eX*a+ӓ";H Tgh7ί=g\rU ζw8Z' o_0x 6KRG YyV阂9Ýw"Q^;_؎GfWFUV&U4o CL!ROE`(+]!XG )z:^Nof~~m"\`L\CBCB…DX,/bsFR g$c*[o%wC>u9 L@EDeB"xx=~ee敎`w̋G#. ?h;ԥGHMoҨQGXx0qkLAE\drh^\~~+ N D?r~{Ϟ}{/=e˅T41i@%XdI&`LPt4U3mN% |<[ƵӥG[3' GB2uswa 8w LnC7lO4@. !t|H tg&0 ԥ?YUOk2(W$>B6yy Nx#f@qn'DMaO٤ hF?]gO"n|4wǩ7Pd'l* ?0rELveMd*a4`P&q:ap3Fu/0& h^-),86|xe2r"c!$1 e Hf'.$a;ɷh$Fh>/ymevPAAh`ޗqDIC.3.Uw | гo׿ۿ?oޒZ޵vLcnPBD !bB׉* i6%4߽ɽ3E9_0&+O\r–VzdB58Y+<ߚ9^cr5LIⲰ'W=  pVd*p&Ab #8*U(PBg7+7}˽H3``V$~04Y|"!6&Sp[0\Y']?+?iZ磈E-@MѨ0 &4nwM \ i@Hdg,фP=P8Į:ǽHG/ B MCUKvHN]3!8K <dZ'$!b)?&8]& z_]/"&^#;\DO4@$J%D UgS|b;ЧO%#eda|jMБ ( N'*ƍ׃y|a`;6D}H}bExšwQT(5GytlB  EL eFLDחh70C=w$kH+-೓o׿ۿ?oޒtA-Iw sX"Ij%i$4R,Ҍ\WW_`F~#Yzllj=iY,_*"A(ZY|F@y33߳,}=GOo>4ebFYPH-``ۉ)Gq{8| {pej]b3!ޢ\0.o"+W]?`#_@{A@EVt:㩀8T\ͭп/L_ZՂ`'>Fĉ4 g<_0s^(/ϮLSWa>…mK 3$},Q/7;#OL{Q7@B-\UUO]m-sn^ǂۗ/m.o2 p3y8o Boࣕb3F&3Qұk|lr˵IKno;yd b pY Ӕ_M:&_U4_&[Cm;Ⴘȑ<蔗s8V\;%'!3Ҧ?4Ey s…-)P~5/!|<} ޜ(Q:'h*d˃Ttנ V%"1?ח|l*~+[yp0M'WG9+rovv[_dp/tnq/R(,JcxdӢ8<' $.H0.H'+S ~BSJ E#Mɼ.U&ڃN{#loQ,oja0&c ko0h<0ݰh }[؁q&(Z0x6}Y Rqn(^yZa>w)M-TIl5h">0o줜=UG/XiX?PRo`n|E9Ciշ0jGsEfqZUbuuG鿝RfL0*\>Mm\ +b~o89)_{ZagbL;~|˖t]Х;T//-6f.~טA~yQQEjJWΒ94U'L75yR~eg^@m L XXi7斛kNGwԜ{=L8)_ShGh7}eZd˾3(6$q| |p̨Yai;*u {2"w5 #2b\nB]ėf=Sjץ7%BYWqȾiϷOY6hy̲#>\eOMYvboSܫqN#g-shs@Yj;܍7|ٹs&>_4׌"vT{JiGnq؄anG.sqVIG/ȽEMvt_32ι|l^n9dee3T5JeK=5ʐ߫Wt s \0CXGdϿ㟷oɿf]Ϊ# )i$a,jy#2!;n (>,qV70_|a?V'U|Q#@#M^ЫWd渚OalNR>L=L$|25~ЉbDI$vBq:g0q(.`g9/mU]+)Ŵ<|XVJ(C(T//şIC _6 9:9ܳó&JWX¯ÿVϗɋ"lB?䧱 J%bSbbҎ5Gd{<6o)5[-\Fpơ7.}RɞdOz!ӹZfd/]{[˗+)#6ف͚׋ج6LƯX։jK܌<6 Nq-f..qw̛'m6CYTFޣ|}G\!@pb0 Od/i I$z 2O}xhx#ox]Yh#iwBMQ;?J̟Jݤe+_*nE8X+m:qhXx-f!މ @(Le2 E|p49f*J00JH}Q@|??VH\=@5yk5?;q"$H!q>D Agq D JKiBBOCi"<:/Xğş|oiOG@mv M}φv,-^HK{Vvv߻jƫjƫj6}/IENDB`psi-plus-snapshots-1.4.554/iconsets/roster/default/groupclose.png000066400000000000000000000003641342663516400251640ustar00rootroot00000000000000PNG  IHDR(-SHPLTEiii,,,+++hhh+++iiihhh,,,ئtRNSffffYTIDATx^I0 @ҝuO2,y C܂X$r5|jTg;t|L!IENDB`psi-plus-snapshots-1.4.554/iconsets/roster/default/groupopen.png000066400000000000000000000003661342663516400250220ustar00rootroot00000000000000PNG  IHDR(-SgAMA|Q9PLTE+++hhh,,,iiiiiihhh+++ܓ,,,tRNSffffYUIDATx^K0Eb/V:љ^X}f؁a9` jOzo[:PIƥ:#]`%LJk?pQI HIENDB`psi-plus-snapshots-1.4.554/iconsets/roster/default/headline.png000066400000000000000000000015011342663516400245450ustar00rootroot00000000000000PNG  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`psi-plus-snapshots-1.4.554/iconsets/roster/default/icondef.xml000066400000000000000000000057751342663516400244400ustar00rootroot00000000000000 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.554/iconsets/roster/default/invisible.png000066400000000000000000000016441342663516400247700ustar00rootroot00000000000000PNG  IHDRagAMA|Q cHRMz%u0`:o_FbKGD pHYs  IDATxڍKLceB)g2PCgA,HbfV`Oi&&&% ]1F7D+Ag8\P2\,-ׅb䜳bKGDC.IDATx}TS %"EX@T ]b(JPA@N@zM(JBI8ﮫ/og-g%9gf=g e(Dws4>^13M V'$lg!؈ێێQVUŸWQfqu#(B;woyuŶb["? X o,Yγ?2{{wkΜeF w$lzò=cAZJf1\7y['!񛂹llĢY\L.?KoU!VNϾp2 -? _|a3^ ot) ¢Ƃ))ߏَ{Qџq0 yV/èe-Μ;ww^I WY#9j99=9?98S ie/%%)A^_g2yEA\PAo?/;;=AgY3N~NqNU\V\vW~"/D|${X&󡺅܋G+p-(8rTNc~uaNH|BqC4xʒoN112028__+oaWU{ G.;гLc c|>\r877,,!ec{Cϝߙ~Ai\&<ż%VVvv|;>O66~MccOԪ:+O5}ӧxn? A=cc*+䑾oPP؃-[y~~R?0 "Ol==:DxT'O 򠅷GMG9 J99=9>98)Qֶ7&_ DT:A& ߏ ٨"..fM6?߄MavikprAcbA5oz)F0RQ9EǬGS1?+=?{u O،ϟ߽ ARSFkh()A̢-YCCx\./ e"ff&* s""#& +*#5[MUm %%<5"Ajj?amjoFҎ @Z @xOO|lgL-R=D$&&+*JO@(~]wюg 5Zdhl6=yZc0qқT?2X%$ BR**~AHoA|Xبֻ,byӧ\\̄ddUL0Z$yy4=Fc(.QB鼜pXXu5hoAmm;3/깷""_qF5;wZZ Gn ?7?-"xCfmppkkz˗p>D:=;-yqmmķ_CS:2 x<~1# 34\&cց*z{3P O~75 AQR]Q&J&b)fa1VLOB)2?ttvҬg2V&%X]]`QG&z'hK_DeL_y%."'&*?'D5QP3S=vjrqqz^&54[_P}h(F} ;: oUT 5d, W(# (H!mr#X<ݹ-\W-Tĉחh.D|}]GQ`64"hmGp*,օ+L?67t<\AC?UUEo S]~s/nU8t[c hqK;jܑ!N{"|n޼TV{gϲ{kyR(). p^ܜpE\Λ7oLk 8y3?G?V`ΟOn9##,lamXliqt\2uoDM12dfM'M =qBx2[qϰ ۿ9V302K ڇ/"uuނ2 _Ced @dT**`ݍ !֭GuwKə9?dTg\v7_QQ80Y%K@kdFF&' lnXt sgђccxzlikv&TbCΟė6{k'{݌wr&S`Ws3(Noj<:DUvu#HFF8*R)RyQcFئMҦ4s3D]t{ti{z2φ2==Ҟf+NXC & BvvPQ3|w\~40ZPMƗvQyF],uv~}yIŰg2;:x`p&&ZZ50}}xt,PI#|=;/i -s{-ޔ:K.PRK܇srBBd|mNL50"sB2Ç;wqg kg;;/(,TaBؑ#;Z3'&>9wˍ1_ TjJǏ__v<\\؎Frs;sAR_:20y@\* X??8*<ՠ GP3*rT[`8jm8SSw~elL PXo?N<kxt~}= !HXX@(ֺ:PJ#wBeoZ5:\)--..-C&|k+ (~3>Lď ^Y LA}=L$"hs\;_a#349U28 ׅ6Ԁ^Wp]BOHL FF+) v}/-b㻊|s^__Y " *pLH -Y[ 07^ޗ/OA((P][[FꐐOOB?46=;ZMNf3⛘hjb3>55^^PWWCbA33!$[R!\4ׯwI&&5N"pllE# 7:Jubz6UV5{ OM}Ah(8 tzf+"ML<}Bף9{E&|wzҮ^16 BpM:>~C%gϠ..f399CsbCPf$.nNxpf0""UQ;y~sszszsZqZsa,wN~2*6T`{TTtQa2QE Tިdg#ȾZ< `b;?IO^4{T\L& (--(]\`0`MN޶6Pcb-+ H70(- ^.Z.0:X[[VF&Cifhoyy^(:hoWVfgIH gssa!DxffLwsSh把*2FXՑH4'WSS76uu D֖93Dww0Pgfh6.wĿfjFՑl&Xs3Oc#[`Q\Pp~~ٙ eu^3񂛖BtI _G[-eMMd2uucޞJ20@IB blrڈG`^ HfFKHC"9;? W54IK&T* BYGFʂ64TPG]@w|D櫫+>pkk}|CQooT)ʈymܨluEsɺufNzfdtvɯ^x}}@0P 8`׮43 Wk1-[~lQSA,4I66׮=KYKT0Lf3;4fql,;pkb"3Q_74ahӧիa\hh@TU+)!x͋B̺I|Nϟ#Sj$bR` Xvvu)oNNb"\76z EffT(`sr))@u|}ן:8g3/adn8yɇ㝝99`TPEE[XH$BMI f0> . DC҆t`JEOs~8ipPclzl6+dnAsL[Voo72RWF6) ڸ80tP]Ii0t57CKշ؍؍xr52rT;Kued hU4{{ss--IIUWGF|\/5Ub;mm>>#;(+am CŅrK_M((Gp| M>4ZR҅  tG8MJ#$V__`CC<< $]]sP=40Ï4׭{VhTzx8'dC`dfߟ wl,8T F|Mp|wr0˖sv^eS:1+Ngg>F+sszszsZqZsa0%!n?f݄ 0Gl0Mu@asxXD/`Q}Cr7q  b/.wx8RM!itɄ {5|HccUL8ow@;vWW ΞŪ`UZZ?????bb3Ν 9wLPC 0N9CNP 45*@ /+XVpJNOBBoCH& \BP]] QJO'aeץPH$PdrZ/*#ǻSS\+)Ag_`"ʼVR9P/@ jV%%YYDm$'^Q@ +QgWў:;m!"$!%eLOY^ >ׁ0CC̄6@h'++"͔SPR' Xܰ6hh8nyZbVd(1qz+&yx9(%`x`䔁!\đr6#H<* L`` xdGyyWRzDM^HEHEI|0nÒ%ZKtw鉉9w/5:Z\ ;:RSV"r룣A22 t,82n2n -, ,O&cqU9fgGB"^!gprh4"r/bbn0- ohpuͨ[=E=qL߰FFH ӄ)+;wtj W|`fnp09YW`ool,8 ͎We,,,v'+ FYXYxϳ*<ELLM : 8Vuy(+@I๹R_!HPJ(;DBCCfrrgdp"({--.YYO~lzz_d?aBWTjM Llr!CJK/Kwu^<\V&#ã!ʥʥw4cbkt@;$C=0ccTWUee99LtvVWRa$2peeRo5wrK53LJrIr-lݠ7ݘZZPl>EpꪨMM^uwWVA(.c}MM|{Se}7z_؊?y[[}|ժko1TSJחt 275sˁy.(ޞ9y `͎-m_P JRY77aaikװ٪WBϻT_L\v`s YY @I vȚ&^Z @ L¦ F#a;=~++#<{ҧC!ΐ`>|cd@yh>CP3=- ޚ*ED$Sj6m8`q Fgin니Gvol 0sʢ"~t?` عh74Ry45"Dx$Pq0sFUhyws~A^^H5U VJJ*pIv',*5^QuWWA|_Y9{FTf H\xI{|Ò'_mMecv@vr?H$pd0 d0**  ^[U⤬,|n^wQXXW?????A*qԠ kkKKx򄃿i#I& 6{@Pcjz ׯϝ\ &"<%%\ 6y">zŸ/~$t< .Rt Ε)D]bLoowwP@/9;ss}}hQ(v-^+0%0 ]]0aw&4[mKxw-8MyyVT\tD =.ntt+qUؘlzN^mF Km]Ԟwdcb4 %q_Ǐ׽gM VW3فߥ:G+/{[=FGRR!-*fR9`< 1[ݑlZ㠠Y /E+c0WM/a+XwJ@T-Pb>T@i 6HJ5~? _-ry}[_OŮ](LolLlr\7 KPC{ -簡T|݃݃;+/ebxoeeb忿~!LIENDB`psi-plus-snapshots-1.4.554/iconsets/roster/default/noauth.png000066400000000000000000000016721342663516400243030ustar00rootroot00000000000000PNG  IHDRagAMA|Q cHRMz%u0`:o_FbKGDC pHYs  IDATxmO[est~P00 HLqA7z%B ^l3!xObzaɲ8D# GRVʁZJ?8؏xg$Op эrV ,>/LR7GGGEW^0;@Ӵa=~?DÞjf qyOiMR $x( |>WGwnNM[*οeYH)X3rKpnR"dww^\\'m=S~x56 Vȃ%.r͟#SGWŧ@ZEQ!PtU ^#Z6 bˋki|iA4='m̵zd;=I+@)To)/TP  AqkҒ'st?=RPeRQ*PSLx:|:׀+0 Aw4fz(f`vƔS}xaaM6IENDB`psi-plus-snapshots-1.4.554/iconsets/roster/default/offline.png000066400000000000000000000010441342663516400244200ustar00rootroot00000000000000PNG  IHDR7gAMA|Q cHRMz%u0`:o_F pHYs  bKGD̿IDATMK73;ZPPY:AE :yݺv*ةE*( $HZ\tݴmfw" OWVg&/Ww6n<|DJ@ĝғ%Ԏ:xc,=ܙZDkWkO߮-JHdz2T#{1j'חJ%&bɤRLcˣz 7kLWW'GjWD~=\# E_߬ڲ_0 G Vv2[UDBPQA٢<$KET~A@y4,L~'knחfǣߏ SZ$ʪ~i d+ËYIENDB`psi-plus-snapshots-1.4.554/iconsets/roster/default/online.dimming.png000066400000000000000000000256271342663516400257220ustar00rootroot00000000000000PNG  IHDR@7z pHYsHHFk>bKGDC+7IDATx X?;.e墈dޣ2Tfd%3nfeV*f&(yAKA\",,e.y9Mq؝{{{ff ca"!Q'$'oze箚Lllwqɋ4??mŅLl%QTdR`)9y׋g4{9G#*|7gb-E ]tJz59y7b2d9Td/wH!Y ɟh4Q(w߱Ulrm7'(*tYYo2E;&lH_͎$n#"E IN~/d4ǀp3mxb%1F<-IN=6#jw﨡1֤XF<@Q#znLNNhTհ:ɟJ4FRbNQcJ CyeGMG|!~!q  /dza@5} '"&F6Yh#?ʊd2;:Y|aOvspweZJU_qxɺW5fs;.w%iD7ދ;U|]Y2ğz*fmSRVjeerCڡ?0GI>I2&fKO>Ֆ8rl<5hzBoj u<֟gLVֶwÑ;h]sfk>~`n6&dWWkzVp;Oe6l~#>qok/ĜY%Z٬op]YpHoBFI`{*!Q :]KKC2L* ˝=*%c /[H@0귨+PGGm ;;[Z4x,ˍfĿLMu|<9$\ԋ;%67loPrT.w\Θ}r =;AQ; ;3?#bM!3B^G&xFE &Z]]_㿀˝gnĿHNi,WB.<>"|1g&r. Z]YYrƎ43\5Yko!QI,jH ~? 8|Ph55UTX4i?^z méCmÃB~(⑹3fq:>%%^' 7|C|Gi JKʈ%gw3f?ϴw}=鶇>W=?W.yDu}/ MJ0X (;@6ŅBX\(nh4uktB2E~f7:vpm=E䯞9喇L"x!.uK?,p`8E mm--r/LVheZ#BER_>H|;/R$sGk\\RW6%%yHC( IF{Xb+ 2و`M'a׳?t;&EWI8OH\%%yŃmݭѴX݈A$ğcٕؓXG Fssr9zC%Y-R}-< ])JӠ$yO<+WHU~ss8' DEۍ<'?J}-z$)I4}PX\=h ' ~)ȝ?ϸ3dt>)wΠAnon ǃ;G,. )7n'{(e(#P{G?.dk{F?'O}S* s[1 Ͼ2ĵVOb3,;y2Xf пȯX,OO G}Hb'иڸOKDCC#Q;65Eed,_.77MQ'#A"#:K~ACx{K$>>a2 5Aoo6+o5i꘦mJTVWWjhݘxG=t|yyx8bsbbЃ{P0nCZvvB?\7РCݪR֪T<2[N 3:3>nG_mo93#C9iEt]}P֠!/v\4|zTt:[,z{m2գtAgZ~aJQPMO  GD6 [W@>C>t}  ӺAfHuu=Wןgxej5µ56 i.8 dP`@- >,ENA}wkk;jt:g}2YX'8 00ӣ5JfNۑk@{UQUȌq+MCg,.ccH ?|`u CZ" ~x8l?B?Rhy5:]SjC<24UWVVk22M6RNl¾*KL5xȁFdžz{DDDpm6|Y,lz=K ._jGiZ]hTjs@ [ڃ#9<؇Ta&B&zAS᧥S kC3;:~qq¢#t͉jDbMk32ξ9A0?ϴ3=Y^+|l68S C|دMOX;A~Ҋ%r^sfHzP}۰ш K;pq:|h;=p;`;=PnYߜh3D.i%D+΀`,tw} WO}jWןgx˙?'\xߢ#f4j~p{X,pLp_{=Y"A|wAj4q ǃ"g|ဖğC;:݊lZmؽczd侺>xE.󇖖K$p{v|>crSy`*Zc{##3zWik"ߜ"=Ac;y3D>d3woF[Ot3mLNnlŭ55%\.Zw47(҆&xptXQLΉ`:j+Z[zc߿:t241>kx`GgM; ;8د>>AWP^wfzYY| ra H{O`2E9Wj*Â*7@? 8|8\XܮA[N>3L?㟭1uիʮEDttwȽZ͆u~<^{'TpZx7tj;.hЂՅ;*TErm5UaWVwiG{9pJXL?C|n/p?THM!fo6֫,<ִ%::hlP@ɣ3N.gAf*TYɋ &eY}ZnC *)-Hj1y翬,?qAYYnڒM .s8\n>}ߟ9N.0C9M6CWj0_j&/+#C~.&໸ ktqr vxjN~}JiU̎T-ߎ :deyikm|*::.ߣLJ93]/g( iiA翪*濞.>{vUUy\r`ۖ1zY& 7=iӟo4vv񲼼6[קv4gYv}U~բ8_u W=p! }=̰X>~G_6jQo\ִ)kǝ|O֍UUu^EԄ(5?"bOa<(Q|p83D;|:0bޜv~8ǥgBާ L_V=w7Y UU/U^ΓW6gd\‏3stEd4?d4)+ܜP9~vJ!GN?Ak=ATUֽg8Z^^SH%""bN|XċvO~ 0*sB[ߤ]ݢ;DwL?50Vkp$񂂣ε֮LILZ<a51+J..L`z$CP=rrOk|̣SsUV|EC/=6+J\.r|p(J=G47ߘu/7?U}>Ҳ5 ;Z!!I0Q4"xP3I99M]7wnܤSR462KxIv8 d?|Ϡ{.tⱜJmƮdAgο] ví8pcFMC8WÔ#8Ϣ'_ i-H(nJ)(8? $%8@@@Q< ܥ0 ;Wigz3=lӟN-(8h97a^JVV]X,p1K>EajWWx..cc$v2ioo* ~vf|쀠-qƅ=łKܠVw3srjNtX77{6C vvί]55{8R&O_Z@{BL/&(2/L7F:9?\| Eဇ$qf /sxpu5@voX8ܜʣZtzĭ`K=5olğrfߜ\,)]Wrr*"杫?ϴ35ɿFoi={3$x&VhPo006[(56<`BB`1our> vK%^GU| 7q`>q8ǰh>Oϡ|BsɸEGC qئ(lhކ%#wݪxq𫉃S/0ba Æ&& Db b 5\˥ہ>?̅ג`>? b{_`]ӟ;8 p;>>?ϰ3=TE,X |K܀^Yㆷsp?C==ac˫o=?&"S|8h>\ lx}|/<˛BD@l&,d5r K+|>~щ+>}}1_(/|_>FD֫$a9?N sǡ*"Ņ|g/&\ \늞tuEd;'46GfjqoӟÉ`;hO[X@{Ϋ_+9 Afq82P 3 0_χDlP8;Q7泗rz7xzl48 wkÎ|%Iៈ_D21H |?ͧ=|z3h_O艀\ܤd #S |ڱ|||o?L|W>&ˈQr^ _ <:#Ho_^;Wigz3=9d& |ooÒ4/)>_ 5!*뽛GGg b#t?IDsWWH9< s77C@}g' E|iSbȃ{|_$_"5PX򛚮@!?a|6=w?;dGY}3O;h^8H őnf˜b<+i=8AIX~B+HPY.g=%]' Og,._;snjP rYVxxk⇳#71r" )W?8@^ҙVtiUó#v~<ߗ D]|',܄K^`Efz,w{vpS:ݮ]%ry3#/0Z/^[*`FB/~&N2-E +n_!gku7A(ʣZ>8}]yNwSğJ..{k@`GfDEc<\;ZH%oSjtߌB7ğ5 SG.rZdMoE^w#n|KA[_dLVNMX_dkR_[٠}h'7?$ $b>1=V[3eMIY.ݠT$iŃ'ğCg-1 yLQD:8VuvM̵J+J*ϦG,B㟛G'$7) EIӼjɩwwR_F6tϯ{^~E[ ~%6#>qp3mL4zжю~AS*K68ka>`8jczJ; ;-O(WJ_~|S? Е-,]vSYA=}.tn1u}i/1+m@Q7UCfMe0d>D3sKg6ggk-Je yq?V?u_1 ^ns\~ʂcϖZ-o{QFcuVWԜn=kL>6seҳuUJRdU o"/Ex|ϣߌx!!3DWǴ$ǿq;AFcuHağ]k[ SɧCKPzOvv+=yƯУ+ wOE  -]"QO2:/?:诚6ka9`0,ܛ_c "4,9=>!FOHhs{-M$RԘIk_ b4˛ C Y#H~~ݓTi@xLq:A,HT RVfBw?g|{ڶѱh,iReDW65|ƞi0?!+??RaCOʣƒPoB!5O> ^IHDŋDş7xwRVfȷl}W!-J@OZ̚|`0[t|?rK^{=ʃKKB:_b iCq_ZB_ۧdHg^Ҟ?2.ARNbXXjBZ#ͼ+3so[d~˿ne(R*~Ǜ=Ϛn[LVHtaɊj#ZJ0GsZ\gbZCh c--h %J1</AZj|?5.5,"i6knW#~X0UYh~-?.)a[o=vYBvvTx#=(K8-?P k-E"љ'ǕU-/3g?!'쯟B6rג6u2;ۙWIBQ/'Qon[n[gB۱IENDB`psi-plus-snapshots-1.4.554/iconsets/roster/default/online.png000066400000000000000000000017321342663516400242660ustar00rootroot00000000000000PNG  IHDRa pHYs  gAMA|Q cHRMz%u0`:o_FPIDATxbfXĪj= wy}ۙ;~ ^{V#2Na3ZbBdTMQgxvAZW@C"\A(͎ X6*0 3qp}|̟3ã | zb.^e`Va|]y A4 M%Y+ ,sHsl /~d`S_АX@}pv&?'??!5P';w`x$@1{{+1yA1V@E@ ϗNgx: o|Ꙗ{i6@Chop{wgX!Qc^.3O(|VP1iV`dq2|a_kd2 E*C_jLL<2|@ Y9Rd7Y1pb÷? h@," %(N"(% $#Tz'_LB|̒@3yWOvQO]CJXU# ;ē6ߍ~z~Uᅩrl{c{7GVZ^L.w{o @H~i 1H12 4G>'@ @ 0P- coIENDB`psi-plus-snapshots-1.4.554/iconsets/roster/default/perr.png000066400000000000000000000014301342663516400237450ustar00rootroot00000000000000PNG  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 0p  r8=A@N/ 7=v!B \j8 @.- Ý@| ~π"' PpȀM?H hǷY%]}D.3&(?@ QOffl FJ3@SͶAuIENDB`psi-plus-snapshots-1.4.554/iconsets/roster/default/typing.png000066400000000000000000000030601342663516400243100ustar00rootroot00000000000000PNG  IHDR(-SiCCPICC ProfilexTkA6n"Zkx"IYhE6bk Ed3In6&*Ezd/JZE(ޫ(b-nL~7}ov r4 Ril|Bj A4%UN$As{z[V{wwҶ@G*q Y<ߡ)t9Nyx+=Y"|@5-MS%@H8qR>׋infObN~N>! ?F?aĆ=5`5_M'Tq. VJp8dasZHOLn}&wVQygE0  HPEaP@<14r?#{2u$jtbDA{6=Q<("qCA*Oy\V;噹sM^|vWGyz?W15s-_̗)UKuZ17ߟl;=..s7VgjHUO^gc)1&v!.K `m)m$``/]?[xF QT*d4o(/lșmSqens}nk~8X<R5 vz)Ӗ9R,bRPCRR%eKUbvؙn9BħJeRR~NցoExPLTE@Zc&-$*侅@ &Z_uɬi!&|۟3n#)&+ڞ:ߣ;qfCD]Ɇڛ1BK̙f֙3ޡ8?K\@̙3ڜ3V\}ߤ?RĄv].iܳեϜǏI@2UUUYS?ؖ0vtqnnnaɹH?IN_՜BO1GРߤ?۟8Eؒ.]콅XOSEAڜ3ҩv[ltRNSo"CQ4QiZئ   8L,Q`mpx|d[!3]{~aU4eIDATx^ESsCaFuĶQ۶m۶mGg4]w}z@$ ƏM;fX/mǎC 79?PD,r,$Խt2~2ϳoV ВTRVDJwˏ*TVU_R'V`M:Nz忾ahxdT_%yyEVk76&f:KIENDB`psi-plus-snapshots-1.4.554/iconsets/roster/default/xa.png000066400000000000000000000016711342663516400234140ustar00rootroot00000000000000PNG  IHDRagAMA|Q cHRMz%u0`:o_FbKGD pHYs  IDATxڍOLe²@- BWKĚ` xҘx0^<x3xЄ61^ bcjRb[) +fevof%/=}W>,gIbO͊EΉu?74ؙy8|,zJ'ޟW" j=NDBQGm<X^@4ܙ^w}L]E4Y*#|i+ɵ5f::3u] `2ay:G҆YqՉMFĕ}|!eo3Pr jJ%V ]I˰ŭkRڛjm:rUD{"1ku<AZ8_G6f碧k낹ɛP[/Y3k#_o_'D+@I<XA1=|Ĉ TF4uݷg/rZ_!IENDB`psi-plus-snapshots-1.4.554/iconsets/roster/stellar-1.jisp000066400000000000000000000221651342663516400233460ustar00rootroot00000000000000PK (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.554/iconsets/system/000077500000000000000000000000001342663516400206535ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iconsets/system/README000066400000000000000000000062471342663516400215440ustar00rootroot00000000000000System 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/tip - Tip of the Day 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.554/iconsets/system/default/000077500000000000000000000000001342663516400222775ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iconsets/system/default/account.png000066400000000000000000000015661342663516400244510ustar00rootroot00000000000000PNG  IHDR(-SgAMA|Q PLTEbbb{rrr߂jjjbbb|||\\\```fbbbTrrr=aaaܘ6ڈqqqzzziiipppޙ$=!)  /$QQQUUU___bbb{{{~~~)IY#[+8X;!"%Ch-26ta9(*9G*+Uf{^>^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"!-* !!  /jIDATx^UrP EQ3333 U^5HRTfJ;j[A^e\u-S01|SyOQB;.DO̟zscPa/~NHצR/?|>]Mjs~ &nIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/action_contacts_manager.png000066400000000000000000000012071342663516400276520ustar00rootroot00000000000000PNG  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;IDATx^=c@{m13ڶmm?.{I>V6ȬN6 PnQ1i0WF\RMtPQ ;A/MU#t pz({к .KD"# m,7N[Ӵ /W$}af#0|!(IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/action_direct_presence.png000066400000000000000000000013161342663516400275010ustar00rootroot00000000000000PNG  IHDRaIDAT8;hQ;3H4fk̦1 H@DJ#X*VADPq+V*dC5ٙqwgfw$&jS\9\>sNmn>׺mKE13l8!"3`g;(x&axQtO"O:AÇ+pLQ%HW2~))g^`& -*b9 Z7h_%iߐ߀kC8S x&GcRUL`Z] @͟qKVmoTK·\zc̵V0z$~j$\h!U L9|tNOr'"](-[-K2E#_Ϭ|<:h[ѳ¼[K5d-4s(sCK'/&Wd=8nd,sԄ_O<̾ vm0FcVUDE|T}|Pi'zkj: C2h%ƽ ŋ'Q:Č4h 5*Ҙ3,Q멵IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/action_muc_hide.png000066400000000000000000000013031342663516400261140ustar00rootroot00000000000000PNG  IHDR(-ShPLTEeee2keeeeeeOaNbHa>b=d:dp>q~FH4rJI;^1tRNS3; dZ~ ?bJ#@a=a܌=  ` TIDATx^5cik6N6mߓ&}>L,=Hɹ=SlO26~j/m`#SQUPMUUY8 oeN'?ϸ|xd?DճLÇ=f^~NnŏȢiG-}Ӌun_uJG P. yJIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/action_muc_leave.png000066400000000000000000000015731342663516400263100ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe< 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.554/iconsets/system/default/action_muc_show.png000066400000000000000000000012751342663516400261730ustar00rootroot00000000000000PNG  IHDR(-S_PLTEeee2keeeeeeOaNbHa>b=d:dp>q~f4rRּʕ_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[&oPq|7A"dp3uncF5Aϗa˙Gi -oE3i]l>L)u ;ڂH[Wk6;V>1axap] =6+7Z"Qrw\Z5*VFf`UTݞJ=%bY դ'[x `npͺ-//P=gAMA cHRMz%%m_l<Xx;IDATouiiui8`ɆِLH<8H"B0ċы/$a!1N `2A&֬[׵}^~??qx c!Z Cc/TW:0B8#Ֆ hb<<8>"ߍt6w5JIT˖'HI"&թ-o0?S9L9jnV(-F#Qdz ӭC#(r͸f}_#~N\.ʂ } ã61 Ηy|Rk4XnR2_i'/J@Q ]ѪA6oS͒>uI+ͦ^h;%7i? m+"@bG[HÕtD%"t,ޞ^++{Jg҈娯/MIu{qސmW+9\F`3<)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Ј nIDATx^-B-m۶m۶m3nm۶'-jvO"6_K/WJ)"?C+G;T+޾ѭp2n~ d*Iz.;ŵo5ez3K Diλh"F>X siܺJ{}]c`<˾H^Ul5TqB,1QFcǛf*l9<=9Z[5mkTkb+ 7pS=%1p)TLdcKIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/advanced.png000066400000000000000000000011331342663516400245500ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  ~tIME ⡕IDATx1hqriZZh0-&zUD8tD:P*Ԁ {,lq l% MB5՚\rL,oyumni!HikGxiMMOwEȹGS&5`] 39tDӅ ~[*]8QF45e[z!i)hg/$ẘ HB!H$jLF2: v#e Q'15ebp$}}0vMR&u`|^²,4 P&SjmLV W E*wff<v!:c 5Zc=֏Ձaʤi~!TrM2m3H&NZѝء/Iެ$ߗԌN+e$$zhbmp _%I4J_I&X'K) <- S۵o=83j]wIUI9-=nHP.+9ڳ>StW0y_!#`Bb;B~B XB[? .mmUtV:-70)@^!\hlreHUJNXkHċsh|\|$0˓@ F42RbM]vDhSHMAzqLj·Ia34rQXw]g;z+5K*H 71c%N1ʼ _'o:{J@拦XˆU5b2qR޼E-4;n3lX>3!] tnWqNkз/wꂤOPn ݩQy! g0xJ-5GZI!E<=w+=(G5 `*C7˥Qfz} eK7(<3>;G%x?<6 IR9g/0IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/arrow_down.png000066400000000000000000000013461342663516400251720ustar00rootroot00000000000000PNG  IHDRa pHYs ,tIME &˫^bKGDsIDAT8uRKSQBB՗B栤OAvi? [d- 5F "&%#MȐ>ԭ9km_ {sx=sy߇aװ_ŚftɧgMVֲ|Jp3'aǀ=Lי.7{Xs lɻ<E7(uu"&ƪZּ2%!"g t?"I~h&o 9| т7֞(NO8&zGҗxAඇ|jtQ@u\N1ڢpiEwppsX1ȂN Zz p@(c(?r9V* (?;۠"<(dz_(@qƠIdM]%8$#6Ǎ@2 : .^ CD1GD041עVGHb`HQ:gb9*o1@[f-}(M$/+EisdtKI} By;eι7+^?,q7zԒ]L5О0u?{aJg9|ߛg$8gvx6c(]IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/arrow_left.png000066400000000000000000000012741342663516400251550ustar00rootroot00000000000000PNG  IHDR(-S_PLTEeee2k6eeeeb2hNb:dHa;f7fOaeee2i2i2j2i2i2j;f2j7e2h2h2l2i2i2i2j3h2i2i7e4j3h4i2h;e5j5iMd2h>q>pf~e^Hn;JlFI4rqX=-_c ?4z>r.]Ƙv bsc{GuIQ̩í6uN-<+_'oe'$L2֨loV'dNKt l0t0,.C5WB pTb=|^(,Τ܅IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/arrow_right.png000066400000000000000000000013041342663516400253320ustar00rootroot00000000000000PNG  IHDR(-ShPLTEeee2k6e7f4geeeb2hNb:deee;fOa2l2i2i2i2h2i2j2h2j2h2h3h7e2i2j2i;f2i2j3h2i2i7e4j4i;e5j5iMd>q>pHF~n;ˊC t3:6x(TΧ|KצiC@W7}ǃޟ LI1Po q>nO.W5MJr׾^Hb^U5D4%oFv30e\@0P~hqq >r.˭n \ްSo.@ڗIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/arrow_up.png000066400000000000000000000013301342663516400246400ustar00rootroot00000000000000PNG  IHDRa pHYs ,tIME +w-bKGDeIDAT8˅SKSqO!!BoAEs6b *z ݵf–V,DAJДtFfVn57=Wss=s9KxɯF3V*Nyi?N98 @h ??z=jqdoݼmz#'osHgʈ0K-F$M5!ݶmjܙL09=/=]$jL{۩LedPR:ǦѝQw;ܘGRi}I=FaoSlzc>"EXrw 4pږWp_z":sBֆoHa4D<t("9 Pb椱R^#Sr?Yg`bՖW}lZ`%|{g=QGrXNQ N46M#W|X(J򆎛\@$4Ϻޣ^@k.3F Q[0 VEӦћf"[JB >IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/autojid.png000066400000000000000000000007671342663516400244560ustar00rootroot00000000000000PNG  IHDR(-SPLTEd{}dJQJqNZlqlZORg}lRxITdidYXqл.MZMkضXc9Y ^\;!B?$$$IG9}|{?8Ɲh~z|A@q(nwz=k[ri}h5an_>-z1tRNSw0m1efeg==kЬӧt 4014AIDATx^E@Affٵf?'(nUVï(}hAEYuxu΢x1CHh d*=^:Vl\i^oE-*SLt6_T)5Zt{@_*IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/avcall.png000066400000000000000000000011151342663516400242450ustar00rootroot00000000000000PNG  IHDRaIDAT8?hSa^ۚ<1bdtH,bh蠅AGEP7tRtHТV;CkB$_uиԊ iKwhsRLQ:80Aﻺ{-imm: >I[aZ [IN4fclV |GhFO#n`SS}BL4YZ 4A?cx=c<~!pVOfGwv5-+sG\2!fKJRr)e,`;4,# ]>(pIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/bookmark_add.png000066400000000000000000000014621342663516400254250ustar00rootroot00000000000000PNG  IHDRasRGBbKGD pHYs  tIME5wyIDAT8}kSQƿsMIR >JEDO b+XLW+Fp' *RRQ415$i9gƅ|4bf10H[OSw0>x8QroNN:pm -MmzuWO;_]+jjci3*zL!Ў}$J0! 'L3n>_ȵ-m<^=^OѲ4^,JiB+ 3X*_J>3 .];C"CJ2Z! ,!l)ͼnkm-7XɩEU[ wtAЊ-V L:9&w3MfGS,B63d<=wuV_Xyf,( 2I"|Ʒ^ÑhV@8K-߈0E```bVّXt7G3ry&LR~gYۙ8Va3vot]vֶ rNndpqw&cHWk>Med,>~f6Pqt4vǥ.?BfDb63wxx,<-/-[N9 G)e@+4ثhowKf&![:SAWIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/bookmark_remove.png000066400000000000000000000012671342663516400261750ustar00rootroot00000000000000PNG  IHDRa~IDAT8}RKOQ=N;PChSH5Uԍtc\ E;6+Ԩ7.qaC\Wą& &Jtڙh&'7_N9 "Ta8Ub}qayf8 YQtv\uk;INE^J>SZFʹ;xmw"iCa JDFIlk-SRE2X dҙL6$Z:XXzaQm{:@&(IX$$ ;v;^2erj.\mǎJr(* fELRaˇUFDb<'$HƲPez58_Fb̈p3ļAͽeQ"z9=NC1Z+{nE|& L,'67`Q-L0.zYnI &]_ՀL(ťMY}P׳+ofBcsZۅTN6;Ef@fhҋOL~][y#z60JZ4llPusS nJa$ܜp@'/;0.v 1|)k}ĸ_IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/bookmarks.png000066400000000000000000000012321342663516400247730ustar00rootroot00000000000000PNG  IHDRasRGBbKGD pHYs  tIME;#{IDAT8˅OSQ{n?nK["&]6L&5t3l֍0bń Јz{o{9סW79Iߓ{Tez+;_ZR776Jju\fe}yl^VvnsǷKKjubčd6k7WV֢{`VH$N*5b˷<{Jq "!E0&E&"Bs$ R!$Z['1A ILc a @Jk(h3:Dan}!~l`u 054 B h0NmJǀ8'yILb@.e}Nxn h<=K9I_>]*% 07c_6`A=?VB8$RY{A:yGk;(%1m"_կ'c֩IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/browse.png000066400000000000000000000020701342663516400243050ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxb?% XT1X0|/Ьc'^2Xd'_X8X}>v#uo}uh8?b߼_ q_9YQׯ v V_Yt?FEĢʛ/?  ??I d˗@'108:7@  &6V/L  ~`)_GRW[o?@C/ëtD,$0# 0@t oy0X(Á @/T~p)qn}fQUF  @~s2:O f/_ @sGt`e4{sßOXA?++fv1| r< : 21 A{! h4y؅N\g`xà wcxó><|{!#.c3d*Ѐ1ǯ ~gJ8l$.ƿs/by'*?P 3w0 70&_0>y;܀\4$8} 7`}H߿~,3#@U4733 D (yp{S3nv&`S_7d C0gdeamIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/cancel.png000066400000000000000000000012311342663516400242270ustar00rootroot00000000000000PNG  IHDRa pHYs  ~tIME )2bKGD&IDAT8uOka'ZU[AQR=x"b.Wѻ*JTh]Ѥ qdSCܼ0733#[+CAh >cL~tV]}l~2_7rhΡt&S]?`^&g gKŴ,z'ȫ  M.EEm`A _>gҹ~IzTn v̰ e|R_fOi;\<S#R-1y'jfm(YpA֧Nmnse0뫪| ^y?-T(lu:3 3T$W*}V8L9ۡA|g Me<#Ց;)_xem;cXfm(8߹4g(nx"awo0P[HjD(Vx"amwƶ- PZ߉"Z6@O$,Θ 5QIaguvO7Θ9 e;@703l*BiIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/changeacc.png000066400000000000000000000014161342663516400247030ustar00rootroot00000000000000PNG  IHDRagAMAܲIDATxS[Ha~nӝ?4gL@VJYW"H. « +Ƌn " !$9ԩæsss_7."xy !3`k;*l(YdX ai14fJ|Ib^{{od._5ZkO1*kVnQ&dRB4=w$0@IRw&$*n[q夬 oy<㐋sggΞZCU4_". !/~DŽIÅb/z@ew8 xk}XwU.ԌX/JUG)?zp6 5&fD?3TN-zX|4U--PpU[m>{](Fkժ 9鮀PMJr(MU:ģQDq8YA#rӒ'@@3a/ع e!W@AL/ ƘwvG&/?ef' ̢lknHĤ"9-q|yN|UOC;~r"],@G\oC,a g-ED)3~%rzQqH)@x' ̲tڔBs< 6Xۥ ~=dH6xIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/chatclear.png000066400000000000000000000002151342663516400247310ustar00rootroot00000000000000PNG  IHDR7 pHYs  ~?IDAT(cxǀ2TaeEU9.tӰ؋b}A @DސFd"kIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/close.png000066400000000000000000000015731342663516400241200ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe< 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.554/iconsets/system/default/closetab.png000066400000000000000000000007721342663516400246070ustar00rootroot00000000000000PNG  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(+nHIDATx^0D>;5\fffoT;Rn;ˣğF1`CXHJeTD FdK`,2I\o#|jw(n?Ɠi 3˕` 85 n8>C$hL2-᪂ ;` IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/cm_check.png000066400000000000000000000005031342663516400245370ustar00rootroot00000000000000PNG  IHDR(-SPLTE !""$'(),,<42?>>IRQiWVadbtxw{tRNS@fgIDATx^5@F,!̜_ώ_.DyW_S#,>J/ %eף}&Y&uE-3n9j UAC!/ېcIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/cm_invertcheck.png000066400000000000000000000007571342663516400260020ustar00rootroot00000000000000PNG  IHDR(-SPLTE !""$'(),,<42?>>IRQiWVab`rxw{Șȳﵰ۵þyEtRNSRL \IDATx^enAv2! ;ʇԩU.?pB g&+13AwVeoԇsTH{zpIrxy+^B1h^RX֗CAL6؋u_>q52pVPmfI._Es=ҌJIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/cm_uncheck.png000066400000000000000000000004001342663516400250760ustar00rootroot00000000000000PNG  IHDR(-SWPLTE+tRNS@fWIDATxڝG CQCz/?g@6ʷWO#M4I%V{X;]jըr8R^yWlIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/command.png000066400000000000000000000014341342663516400244250ustar00rootroot00000000000000PNG  IHDRagAMA7IDATx}K\Wܯ3w;S""Eq-utHRiB@p׮\EVm"!$"?ULLa&s=tx)$ٽ_|/Ye1c<>99鮮~+nSP ˶,d4M β*(Jotnc6B`Y˗|;wC2R'eJ0 IRJj?z'~ !mcvAA$xp8v8} G^{8mx4%MSz#۲,q" C$AkMDQDE!ZmjJX}Q#R0p]qZ.iR"< R  2$0 s![Ri"r"DkM$ap8\.S1`Yn*ۤiJ$q֚,y,p]uvv(> uw Q!Q$ ^O_n7lnn @%l6i4B`???~ |fyKtL5t:c1A 緼k599yVRSzGecchkk;ŧ_OMM}o1׃Vu~ppW[¿(s>"и9IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/configure-room.png000066400000000000000000000020371342663516400257420ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxbY --_턦}ųg&~}222LMǏ_ ~adQ 7WW[E`xAggLuu50{υ̻_Z͛XYky{j~NKEE@bfx=×o=93}gx |54DD}``exÇ/ w$O1r 1& A((9: I 14˗/@ ?999yy4~BHL0AD$D}dexm_L/@ʲ[?}ؿ/ܦQ _ L?Xra@1kk[ǿ^`GC_>*gkƗ€g`diʑ3 l322  ¿ r7NQL[}0G_f`} ȸ ^`6i9[03gx#KDpzZh-N{/ 0{~ax`a&IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/configure_toolbars.png000066400000000000000000000017711342663516400267010ustar00rootroot00000000000000PNG  IHDRVΎW pHYs  d_gAMA|Q cHRMz%u0`:o_FoIDAT8O\e܀ִtb)^H@#k,_` Fp],LlҒbB"1{1bf̹̙b>o&" ~|JOO'F"zRzg';k'jF׭2*>1lNݼbĉnZZbJxa;z{;/uW*|'uh<ތU4էfr(L" V O/SΜ9IdiBwݶzy<5e#j^<*Z_kkd^!H%=ӮwNĖeV]cp z{vqq(DQp+%'ZFMOO3?ڦ#Af.lq ǎrѵ=K Lf^  x871M&cxӣ:Ǝ fnrc!xmOesҼ @I(],jlB:_Sx NKW{VÄ#0RJ8]Me^F1W}{3\+=%RtTt:|y60{#4B03J)~rM<;GD/ĢQX\ZͧP(ir~o{VSR)/B4M5R lԔR Cܺ~{V5;4iPq7߮ m/ )Ljx_m*IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/default_avatar.png000066400000000000000000000051211342663516400257660ustar00rootroot00000000000000PNG  IHDR@@`U IDATx^řo\yϜr?,4IdYQ:GĆ XUEq]0->A.cU]@&MUFX(گsf]$ j*yΙy}9'UqThܠŽA=<@z xU L ًaOd䁻vqlE@N61,u=7?7(K hQsLfdm60@/YbwP>)w@qZ1.ڴ0e(D`ݐRs_F1հO/~_ᜫݯxBF,DFn+6ZMWܟ ]iw $HG ehQ;|W4M4o"AH&zA:Szv; ՂƵ˜36TdoH 0h`N|0]/eX.HmDߟd-(Ptɢ[rsVд`=Q荘[Hp.FZ晳+Lrd3&0`"qCz4&)S0ޡ ҈ wSOLxF1J8  L`בL~ݟ4KJ@@66!@)@ pHa Lf} XB-ѥ1-t%_j TZfU7WnC/ꥲ(Q7gTw@5J[H ^&2r+CVfP-qcqj|RC$婒j8V~<]G:TiZN2 g oR1+0;gt2^mRe Omqҿ')\#sk?ɼs}f\Un0 uZuޏ4S7Ja2? ]T`A/0p)cԸ4My9Yii k6k_?Iu55udC;NFhz]??v<~tWUjfv[Da9]Z3Dq|hQenu͸mX8龧}rgΚ008U*՘MIV/YJ Y'䬖}{ 'i`B{rvPBo-*L19EFy uF8g)?fjG9 kگK8Mz˚e@Ekz+~׹1LE5* ZcV_1yWugm-̹oO&(m<|'lY=cD ay3a42F񬱃< :8]+MTٺ .Z0S#`FVm'ONۄhQq-2ffmZLpƞVi[n58wS7BTt=}J+FWN-©)E==jU95Vn'MhGf?)f9&o7{iq@0oAj0D KWF"Au`[ˬ O'f2*QmCn)^cO%҉Ybi߳ut}bۥkv{rE8N|d p&%AmncDߋS6 2a#rt)S߄D&BiI0aØ 2'B *8s6 AQ ڑ21)ux8AYh0B^65r `^4 EQHXSбL=?b%Z&Mf$̒cZOO0H=A0 A`tiF1 c ۺLQ40HYPI}izB p[+Q l!m5֟bFXQ`*7ڽ7֠NgB`QFUwK@_[;,80)/k51ìiIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/events.png000066400000000000000000000010731342663516400243120ustar00rootroot00000000000000PNG  IHDRa pHYs  ~gAMA|Q cHRMz%u0`:o_FIDATxb?% qI fRP .@ÕOAbCk'00 j1l  \b .g`APA$ 4+|Vpq6'03"j!2 BR  5 9TqЂ?c?cw0/9@ =ุep30 (@1! U>0p  r8=A@N/ 7=v!B \j8 @.- Ý@| ~π"' PpȀM?H hǷY%]}D.3&(?@ QOffl FJ3@SͶAuIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/export.png000066400000000000000000000003671342663516400243340ustar00rootroot00000000000000PNG  IHDR(-S?PLTEAAGFHL?AD%%%nUY_nx n?@CJNSY]c"tRNSdIDATx^]I DQϪ& ߲EJsB ·eY{:P ,|iΖ3Zsd S'DqPQ(b&l"aIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/eye_blue.png000066400000000000000000000013041342663516400245740ustar00rootroot00000000000000PNG  IHDRatIME  pHYs<<:YgAMA 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.554/iconsets/system/default/filemanager.png000066400000000000000000000015061342663516400252610ustar00rootroot00000000000000PNG  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`psi-plus-snapshots-1.4.554/iconsets/system/default/geolocation.png000066400000000000000000000017251342663516400253150ustar00rootroot00000000000000PNG  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!Nitj IDATx^-ӖPѓ4Ƕm&ikl۶m۶mצ;km  l]wNf}h[FdF]+_z J 4/ ~nbiUEWOrt\R!?}|M&x12&6یDI^]-KK^c!rD+en ڙn>(BsT8{ Q:8=UgmN1\SۮDKC,\e+L/MIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/groupchat.png000066400000000000000000000017311342663516400250030ustar00rootroot00000000000000PNG  IHDR(-SgAMA|Q:PLTE`+J%L)H# d6UB#bRNBW.4h1v-d=ۅ-ېL9hPT gIE\Uab\@=6VYYk.HMձ@22kq%,;o;?BS U\'X]!fos=KS6U( [`d{{OhsAYlXz&(9M.G)Aj5Q@td ' $F*jC(Z"~ˆ:lM5Uԟ_[I:IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/gstreamer-logo-50.png000066400000000000000000000100031342663516400261500ustar00rootroot00000000000000PNG  IHDR% pHYs  bKGDCzIDAThylqI4g\hԸ@R,(TADM$AEYDD,e-RҖ+]VJ[ t:]}?~N%7'2s=|so(I?եZ[UeVH׫?5٬.\P+W@e,Pǫ#[oѣՔ)j"22TCjoҥj榌F{3b+<\MU߾_?p3-[P>]\BBԩS*7W *:Z0A{yYpKb٩Ր!`V:ZiKZUF@>jbK͞OCU9dbǣ'1ӛX_q xRI≊TCm~a30wԕH5 ̪}kĐC\vI.0+0*ӶjJ(IXlN{Y3mϳC\|>فV[up8"-*Pm)8aЇۜ7ͩ5Y""z6- Gn=~q D!Q}j|;=Y/wOtN ߟs# :Xɥ#s#TؑD/fc14 <5@cNg_ Ivf$#+}zh\J+-5e頲0vƏ;v3\<2/{mrtRbR⾀}W1V:3y;.JK*ULV922rӦM+W j8ԎՑ ^OsN5|᫯.xxCBj SSSǝ K`AQK˟ #sbc)|feÆr7sqpH_9:/8~U!ҮnX'ÆeZc*++kjjL&8ե/YÖdjim|1YÃ7mNR> djs"ǛlF9hkk>,ӱ[pK}GB7,i2m715%a H<+L=kV{e˗KցblnΝ;:q谡Ç&Lx=}v^ڵkϟk!""Xt|rbbQ (FT*5ҧ%''}'~ )}ZO: rB̳71#iMnÇgLcǞ>dH:8IuǏFI.,5 `!!!/r>}o;v>*|xW͛",֮]e,D0mذ!3Ӣٳg׭[T?6nXT\q80FQW'5661*}aaa< d+Wtƈta g!j*СCt70ua Suuu`` ЇÙ+3f܉3gl04 c,G qIBtLIP/T[ȑ8ͅ\-AA#Gv-q*`׍-i{ja#nAcU !l߾ݦ~sGy*'aL:U{ꩧXt(pFvUiQh2E a}{w]y+BxC֬Y+C SKMA1@Rf۝[[Z8"1_evH ap_e.F8 _7v8ZgM@d+ŒQ@ 0Qx]o`ƍ'''a!M 'Ǐ^^{3|ޓQQJ)tUX$Zj=![M^uR+>O?ٳA 3)8H]P%NJ+6Fbg7s^jtEXtCommentCreated with The GIMPd%nIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/help.png000066400000000000000000000012721342663516400237370ustar00rootroot00000000000000PNG  IHDRa pHYs  #utIME 1q,(bKGDGIDAT8ˍ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.554/iconsets/system/default/history.png000066400000000000000000000016331342663516400245110ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<-IDATUkU͌h6qͦ$Z)UV-A< OKn"ZA 9j@4Ф6l6&kf޼5~>bwƮGD¾2P7VFKkIP*afN\c]0` #g͎g۱6CZ2/~X1 di9 WaZ2HTPƟ̝;Gő$J)KN)ܫ$aj |&˙1Ϗϖs-(rs4J WJ,=5[~/ӯ^mI ǡafmv:_F,Dk[TfLa*N%95lc>՜zP #,DHuO?iP25Hic`z "ŨOO ,(%wї=7f|nBıfسiP}"dAXNv5,P^7~vC2a Fc ZXѥ 3WK:^>0 <q9@+09f(dẓ2* }ٶ -{asplA6ݦȏ=o5qinv3BZ& Y1Sh-#<7+jݮk\~o#iEWIVҎR)PPkiF 6v%[7o:-1HBlZT-d@+J/+Շ{ϩ77-pRgչŜڝ+%zw{K|7IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/icondef.xml000066400000000000000000000416001342663516400244310ustar00rootroot00000000000000 Crystal - System (default) 1.0 Crystal System Iconset 2005-11-23 http://psi.affinix.com 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_pgp notification_chat_delivery_ok_pgp.png psi/notification_chat_receive_pgp notification_chat_receive_pgp.png psi/notification_chat_send_pgp notification_chat_send_pgp.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 psiplus/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/tip tip.png psi/browse browse.png psi/play play.png psi/eye eye_blue.png psi/upload upload.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/xmpp xmpp.svg loggerplugin/openlog history.png psi/action_paste_and_send psiplus/action_paste_and_send.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/pin psiplus/pin.png psi/crop psiplus/crop.png psi/draw psiplus/draw.png psi/frame psiplus/frame.png psi/palette psiplus/palette.png psi/undo psiplus/undo.png psi/print psiplus/print.png psi-plus-snapshots-1.4.554/iconsets/system/default/ignore_global_actions.png000066400000000000000000000017351342663516400273360ustar00rootroot00000000000000PNG  IHDRasRGBbKGD pHYs+tIMEX]IDAT8uKLeE?Off`J!DT$`ېIMHbM`7Ĩn55.KZPJi(PB103Ws&ֳ7{z`􍃠O6j 3w]89B-CVOx꧱` *UUl ɹErx,`8pv}c#}Z0+r~/}?(=ި|#Š$o>*)AbX,<(f Ny\^iȚ#g@kTV=aLjD 345TÃPBi[8?mk[S:k=X]2W_] L &Lo\Mue16bY1Ng,9d(N{1T)Bx|Q$z¦7B*g}kGQ=bZLʅ17OoWLoX_-Y\q?1[=tAj"kX<ɨ!4(wX)I6?F:hJ;ߏ\GI'c4o515#,jhb]*{aښ\f<ڽJ˫4c[;L$hptm"r+ԓN$g>e%!,ͭe@+xWy и8DRgرa,'uҥeC \n1;;_iSaeM]q6RsT S|P9o,bEét46Ԗ@`zڙڌD$~|"?^W^ MT: IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/import.png000066400000000000000000000004201342663516400243130ustar00rootroot00000000000000PNG  IHDR(-SNPLTEAAG?ADFHLnUY_nxm?@CJNSUY^Y]c`2ˢtRNSnIDATx^eI1 % b'6 Q}(:8U): H`[^h %].6Q (\#c^<v=/Oo9ǣ(v,[j٣wVG3w<iG/q?gL [9Os= cۥXa"Bn(na L&xIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/key_bad.png000066400000000000000000000015471342663516400244120ustar00rootroot00000000000000PNG  IHDRagAMA7bKGD pHYsHHFk>IDATxڅ[HSƿs[tʜD+h7H`,ˇ^,=dH"(!|(e:KJS6u9$#ZʳE% k>'\tHVTuuul||JJJSRR.E)N1+ZM|>nVXvttU,/... l^kZMݿR[j./a I>$#&=D\^lq^e,rIc2tNpWO4=UҭHݲ +IKvޚTE`m |y=9U{`jx^tG60ָdjkx[ۡ넆ԐӅbN-ež9+'2D#-U8()젗1l ֞&2˲-+@Kx Q4AV&tikll].) ;@ 3T&j11b[Alar4M444\H$9K]&/''dpb*uQiY@NjC)3lXae@M*߽d~)ʪIDATxڅ_HSqǿ۽s.ݝ;r$h. ՞k>CHЋK"ɇcX {t>$8)2 4tKt89G}f2n|>aDBdt:=L]ܬ~Qw +++zAp{S Tjp,+hQ{mmml.naaw040{3 ^G,v6?/Ib6.55Xm PT`/Ʊ[>#(a9V}=Ib[J܁DT谞$0<4.\^߀(#"ܭTdߏ pP4bXR2\bY Ȭ<##`ffFpݵͽGa>M&JEfhny @ʔh4J#1͏tT-9VWBv:-@zzAb1DA544J\[UomltrBTeD #@Oj zoIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/keys_64.png000066400000000000000000000440411342663516400242740ustar00rootroot00000000000000PNG  IHDR@@bKGD X pHYs B(x vpAg@@`G~IDATxݽgtI-#^==44xxNI#adRy`ͬ{ji,eD]8qN$111111Lz&=glflfl؋s2e.>)QbiVz+K꒺Пҝҝk Ȇq &$ڝc['''lc=X m6xxx@riȥ0e픵Sw¿??^=zx$t({=,RS降\XYb4w%qR~Z~Z~h#(I&$Ή:8p%r#M?mBdbWuMB7~ ڙt& )H0P( |-[1j 5z:鬧 ?8mo;tmTQ?g (]:t6H R 9%{&{^>*((k*w(w(w,N/fTO^=5LHH@uO{{'ל\sr q h1ޚٚٚ\K\K\K\5(^{Wk5g[-Vqت`MfJJl"Ӻ|ݺ_zB`\ԟc/>]9p 8p0 *=VzX 5B >>6?H{= {^Tj*-Vb x"EKR:NPi4b{OO^] 2lʰgGlmm٭E"Gmtx={X*_q#ws]u=!KVVj˕S)_PYZvJ]Bk+EA8mbm7;'mtm: Lr|hޝY f[bUU++7Jﻜr-P+ 59P*Ph˶/۾lK>| tfg&pe5,jX԰HHKHKH7og KbI, IADhNN(QRԇX$KR2ec7mHې&TTT[|ne 15 j_~Y^z6xTQ)悫iag?F5 . [yC*0xТdt]OLSŘں$zbH1|d 䁞UTծjjq\G Z&DI%%%bC!NJboδĴĴHo7w~wL&ʳ˳˳s9ќmʷ O?=m t9{Vi@Z MGMD͚|Ƈ$^{nH^$/j 303R;JmT M&HKA Ep*T}Mr^*xam06"}|zsYYLv[{{[%s{eoZ6mغvAuj?_ti#(ԳLqRYk|T}}ϥ4dZ|>٣ͧ6m\%{tIf!p(opXXw)%!%!%!YnYnYn:98hP.,#GT]v* fYb&(Q6*U>WPQQi*\,ljv̤j8k۝;mwr%Z;Eկ) ?~ƀY,(f11zb' ޤqʔ(g9yj'{l3}/Ts\OKH҄4Π3 X灨Q/^DU]Wu]FFF_U97[RJJIWJ\<ݐݐݸ\QWQWQ7{h Xl\xn^ZiNZBac6fct)]JE XaV}I޿<&KˤR[i6$ay ̠|||%|g|g?~H .]our)@z d֒d-&$$11V|sD=G@ 3oeʼԢh- ނ-q]H>L%NJc=GdL"EKk⬯b2IqYqYq3.t]PJn?ή-\;g˓tj7hI9G%i .qy-Sk7o޲i憷9d_OVjUSKlysR HjNf 3:&[  vdTw::::s'wn2ז(gRS%`M2Oq:C2Afxxvqڮ0݌n[|旚~9_^1؇k'F#,BBB(f4!{:"eXӱcMdbMXLwwwbMMd$IFU,v+,K..dGvEJVTmm߸tivy̶azey !~%s3DlͮHڍVo[j$ت8U?QY O*TQe ?M.Wwo]9E Z_!|솴LBy-zY}`gR^E4mV-<(7gIڲ ew glX ?ќ+ʹ4tlll)oYYYE2\n~)藚c1݅@>9¨0*]vڿp* @S4ESP!rZNiP>S>S>,.\TVV^e?S$*ǾGamHChѭΑCSusO"^ҎuZgOJj`7Zt0E ^4e3o:W 9p"dغqAh</SeE.3fn?88TgZ),kϹήߏ|n\*ù>㢗G8)< {=3 WJ^)qjNͩjJ}^=7{nn:G1+^WH$R-Jϝyyy[gn B |&gBzrn<7ֽֽֽP͡C5@gG}G!@ B7;{/Z5n 6xAD&l7 <6 b4i,RP,R VO->7N~WO3ph%aZ@Y8Gg.D/]-ͱ2^x.r**Ai%qu:\:TQQQ\5kפ{/^|3ϴ?DQfid1YLkoo]oR`~~VzzzzJs<-mI~_uSaio2"s'^FK oҊ3m_5m%)6:srd\ǀs|qګ⤅=@|&VJf8VdnJ,e 2S(Sҳ}ەL&K 9X@ swv;7ro|[QAlrG)j-_%2Eƹp. usrgz5M8D8D8D^ý{  )3(%wK-gxy{y{ys9glg[ { q.|c1֯@gkGkGkG]eWUht4~g?~Iů$ҏr\ bѻ>L I]v=t~4`7UHod^[z#*@+J~s `?}_4Fa_)CW+n)[.?r,opb]0[܇A fԈљs*t*sO nTJSo "H.2[[[[[[2NuBuBupf̙f_E4Pg3!!!bkk \K 5߽ ۝ow D}}ԋB ɿ_C1Y=i$WGhfɕ.Ev[/ܒחǝm頭ާvo~imFX`~j* #p RӹJ}%>v%UBrV@qCZJV*qI')**TN|=EˑWe%̲9ImBmVw˱1AnRF d|%nȒeY[Y[Y[@fYeV@  I & 9{G珹xDyTrRI-'};bϞ={~nsß|1_JetȖ][vm%[>wC_EҌ-c)7\ 5tTiSS)<01޲i!9Q$Eg,Ehy2O 0#+ƊPS) RVV>ViU$KR5P!Ægݤ*c1;)tjZzu綠ˀD}Y(s|nm6qA3h@c b: v;Kԉ:Q' I{Ҟ͔͔쳝o˷Y,0:ÀGM5y뿭^lHސ!@2 @ $$c^(dﰅ{aKB\ Zxcf"[VtKiKj,+gי;0X#ֈ5X=VA=ֆam𙕱2V&7o@_'JBIX+jL{nr^mCbji5:ÍM MQ4-#G ڈ6jjvnX{֞Dh*jD+zޢ7 {x[]PC}FQUUUo@W^?o Rtl.[*]x2c=lʵ\CnZ,_9N~7iq `Q>̗00-05w;$iEڂl8AkК6f y>jaQ?hzWj[GOzG|o-O+%; .3mί *n@@ݩ;uzl `@!GrS=hmbAWWWxx` S"$dOUU9,|Kf5 7zec3&-+k,-c+dr'MΕb @bzRM`,]= bX+HB|Ϧl*2Va\鏆>X.CV_ցt4.zoˇȫ$ڍ-ĭVrz3͢Y4 pO'%FQ((bmX_: v]0^l"6B{޹C!K{=+H #appo^ww9&-H}p/@e#[[`kGp[wě#wx_03%<)FXbQc1bV̊A)M L'˓壒^Whtō󙚺SR($~t"=QJeS=/9J# "/ b ab ۈmp@TjQ l ]s].Y,d}2`cc݃t[-Ֆ@YT||>~@wwn p>Ϗ#G 0^ky?,)V0I|cn^@+qpQb JY80#30-g nUz^S)@ң_ %qI\_bZL>Oړ=$! Ih Zl( + CšP@ԊZQb@l:::1ko(o7I='҈/%ӃO>϶3!COt9݀ct#Hw(A *:s%rG=@ZQTѥxǁNeIX*{5kZ/pPQ`s=MO=DCw;m6n@҇!qi\ XM7bKG7`;ζK1D !i6_|tY,9)++gT ,O'##L$!IHdpHpI)idM^2Zњ Oԭr#9_AB~6%;&LLw-{|-C\уא@5TC5D?Oͦ4o\Z>Kπ-fـXK%oX_/@D' G#őrADAHRfc,k\ȉƳlh -%9h %P@`5vb;c1_A/.m G󮫲Fi`!vezXZZC⾝4\v6zՕڀ\\5섃dY٬)bOD!fd=e!uu1н)F派 ~ mn"7kЖ-*ZI+&hmp]Q(@o6@ҩtZNB") _xW OOOR""R(L&} >Sqqlr[S-fsv_70vvvt%]Iot%'+xf㙵ӡV]S]SsC'Gk 8.Q9R#}xT~'fprcֈ*d@h.-Uz@Ah-!B\1WB]. IeR@6Md$Iܓ8VeZȁF$D2L)馯";iccLzO,Hz;v9OH3x$T@kL?#}r֭ ׶O…$&2)y]#W@_MZY8y7ou>.o/x=y0sWϲ7?____'Ib-000>8H[y`zb3/$O"Am9=k >_VeZjZ el[\__@c&ɁRMTt A<+R]T>}b:컵־[9r>|FFF篞&WO7Ƨ@eH9wfrGeT:]Y{F gS+6`k'?x{}4~x$\Xޥ|M)w_>ϯP!š[y! (8U T頖;EJ'842`W ?OQ;T8F]B@Jl!3dr)>\Rg^:l[+WW%@>OfY,քAӃ}6wb}}=Z|Gio]4wm_(Lxx?\_.rnUʖRP,$EjYqPE=F ȿ[tM3܉YmM/i(^r,.B_慩 @n<܎b&˃(͖R}I [jC&. uX%p-777fΠ: TpU8 X PSS>,E36}[e[v!TZ&-:NXXX .^/,,,¼üe:ZV p!Wtmu7QW Ƙmje`m9B`+M њfW\ҟ1}WU}{;xay?N:z'ʫ^Wl59Օn`As89V`r`t&sYedvvv.6 l ڹsk<'d FɱxB {}o%aljljlJq8~pRW 7Ko|Džz&AL0T/R:w.Sl4\X\'rŽ@lFby'}9X[ 'zo1U}.iü-x˧|,a`vpipip$wpT.Q;333!-b 9sllllll ?~(њGk={.vN;PwZi vcg`JC%`ܕc8(v,;6(GT7Rů7(P!&w~s|%$c&v-w n!`ol"v"g/Ke:v|uʠZVmIƕ}v 8öޔc.7;_ [zu~~ҧKʻ;||1v>T yjHUM=Jn΋B+UW/|{nt,"]ESyB)w?FRQ89;RIEfcnj3v̵sTsTsTQQQ.k?###'GN뇈ry\.xoi_; /a˰eeggg:;i@$  Ɇ0lZq*u[򸲗wt!֫V *T.54.Vн Tv;nj7~r2urrr*poW:g?2ODmi"dVh iPΐWײ)!uNFi^"{E^^^CCCoGoGoG,ZSqqqÿޓ 3 3 3{{/om6OF?-koomO?@~qKS&髏0̷<'0Hݺ|DU ̜/9Tgi fVfgwS9]$[KGV9f;g{VgWϮ>D;%7$铖/xTl9Y 4fդS?{., hGKn@ɒf] Ǖ-Ψz` p@e `fmn-ts>XY#CI*t{Y;bx԰n@}k~C0YX]!@ܰ]=F x֏ -)[Rޞ|{IXz(=yyy YmkO[>mE^|(>o 5v5jzDk5()()()gr\#P5j^<2Eo?FO_7 \]J({:6ni?ްcF{ǫa% Y;"N`IZ(Pg|..=F;[JFJ'Y{{j puu@ϧ?ӥKKe%Ɗ_8,2 '']=7:8p>hi/ھhK+++>_UUU :ta]]i9pw߉c'O&7$?8"NEXf(owRoN9(\#Ҹe{wꛉ%^6372QL*cHoFjjNr"vP:&#[!1 _1\j-Y|)]]oUEUEUPd}z^zu\mֿZ*4`lo\ E~yD(]JWggm!5uuuƕ+8V[nq=+hvh4K% HLL7" GaG=e̗< 0d~W]sS\vM:7 /AX1ΛL%2"K]mz{ZXWX[Y5 ʟ0HIoQӁ#[l18|],|B"E@Ny$I>w}ܭe,E.Ij{{{6.];%cXn.@I%,y>khz-@YY4c7"h@1-O7E!zt}NSKd4hSf #̀E 4@ݟO|yTaN3WB2̸گ_k\}uܕү}_.V Mpe܉&.KR9&F o2)))MK-J-J-!\\\ʀ> 8vY\Lhf]gYXGk^|8q0<)y[bٝ@$ ?|=InH7(%JA.F)x3J2J2Jl![ :98989iiiŵkM#mcalU|}hg|l|l| _I~!鿧LpC/]!Ua|F=;OgX۪8#o"v>U}o,Z7m?^,?vt@,^Z800mu mݍ+gݛ[LM,2C+;[ʭӧO>} `"U]R]R]"ݨwލUwUwUwwr5Z+wDS)Ѱ˟? |$ Xq56 b߆ }Fz=@٭[eZ-$Iد8uպjs9ꀈ=#o08~❣aqNychXec푁m}.u?-v1݉{w<9ev=8-:!-"@/UU@&5[tkőc[ ZA %U]{U:}g:N \IF>bWⷿ*!!!!!ооо[nRc7oʙ8g*{Ut*JNv:0Ӎ}]]gvCCC8r\-`PC^ă [ѽqg;h2w{;PZk>K2@HRW4Ac]UON㑫Kye7/3x)`#6'[{blqND@hnW2rTǝwk?yOngK.8̫Cv#S[Om=^#lN_ߗ{C?\?\?\|!JJJ{\s ^{U_=4~?_]ߊoŷb/^({DGG=9n C`>XԦ6]1Do|#;uVh+RQ0 FHOMOM' NWW aYW>H5 dNE?y1Z%J*F- gӲJA>2O2 EWUIkw.bꖐnYf=vPSr a|0lf6'÷_ޘ kMkMkMZ__|{z{{'vi}q\C C C Q* ZjѫS8+GN*' p>|:v;Q'C|vޞQ ֲ`l/ (p)rP W.YqptTrr,GDhY72E0nnRFW:uW^m>,{ uB|^QhFzYR_t=9;0044$͵ʹʹc1ӘYǬrSd[/}|~>?|||a$E" PSSz,XG켲N 622֒;;;6qퟟǿ;xt=Sā@q w`6ܡC/^@_q@TX3u|[xMq ∟+UU ba{b̝XeVYU=R72wʵ-y{iJ҉-,-/ş_pMz)RUUJ+U=,X*,k g`qx.uqŀKKKtބJ;w \tv!_{333oggGf*(('pUލ6FƲ88st+%đ̃SeO毸}ۗ)@ [#n5kr9sGsk̭1ۚkv"~۝NV'hJVR+n76a .\ D5j ?wk,h"zTXtSoftwarex+//.NN,H/J6XS\IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/logo_128.png000066400000000000000000000357631342663516400243550ustar00rootroot00000000000000PNG  IHDR>agAMA;IDATx^GyzCawgcPZre2\\`1m_c &ؾ`ce$ *VNNo>Y]{ne@+ݩsSoNSU9O!N`#Sg5[p7Y1dsuTr tѷ. 6E6ǜN.7g9 3!dr3wM;Br^ȝ  diDUaU#- M5O?v_Y&[DE4BٻwpO7:ww@ց 7 xtǗ=koHY:` S?<`@֟կqsgm*}?sa1$~Z]w?I1B8OJ,?O=Bq̕Ƙ&!$̹on *@$0&Ɩ#{Iw_tۿl Yn8n Gǿ\+fn`/3)p:` 'Mƕ1\,S138ts=Uqk7 +ߘ51Q%Llomּx_+AN5lȗn y-&4Va/Gf=I->\Hh#`1Po-pWigGAzy|'Rӯ#Qy盿4 W=>Oc\br?;G/_v|;/Wb! xv703R_S1?6Fiڊi65k|,(HwȮ c9deMMl65`S|QT5P{_7}[.K>W0jG4wo²˿S=x]f@?+d^Neffd] LU4UKmI1W1>IxhN%|QEuwxXz XM6ӗw^}W׾bڊn7{>5?~_ޢS)å%eQwO͏ mdl%F70&NqY D!D1D qX9|5{ a0G Zz-U0U 7G$wnVg-VT&/fopj{'6$+΂ug0{ۍv-dC@8sb>06-'%01yf!͑~`Ԅ o)K03)P*&Bq9|7 8|ާߌ@U5&gw| =3[J4࿝'Fz3e*Y@p7jԥC`q,#A6J0UCai˴H|i^q_p$$ Y a_;z߾ jݏ|f#+ap ;zy2[), Ȭd֋\uX7&I( %m) (-0fj &B:]ZUxw[_?ܧ?=k?oAVɦ!xMY.~s`;6@w<Bc5A οݵ,N %h+ڀsJQ Ml&sVSMK!a~|gnkQonw.lL&xjs² `A[?٦kf !Aa 2}kH %/d9KtLȦu>nFߣ{@18I%:yhs "K b|s;澿Ϳ ( =%@R?C1*P C}]vK:'=nE'dsCP,$Q"yp {V0g3}P\_bqenl{z;Y4ï\P\f/dVͅo|!8Mh&ܾⰓXx@p% 81Lr"ͰyD`~ۚEIU8e&L2qP@|`~g:|WP\kY^O~ru_7 5V>/?rf xP*yp k޵o]qHeAlXL|eƾ"$4^ G:dYm'6 s >{;~|϶'q@i:pZ,X#3 (U&- UA[$ǃ _HK KN-p:gPE%_[R6-,vk[1ARּ̯uUf/]o5%=~}~yˁc:^mтmXhlA-tu6;PBvhW8#DI YLՐbkveZd@0`,8ZZmfF> Lbs˭:}\q/_c/㛮~_̇폪c[<6!9TTQ';H+ q2 Rl@*+,i7Jes.˱fħgXm xıB6cÀJIDK6d \F0]~镎W*wwucA%Oǃ"Łd{ռDZ@ށl.'bLHk.BgLֹ'g9I~m1$Σ:RlPL#!ܲ.45 g8_`L~`o<<4  N2&^h_ P` %Xix$qXl 6T8< ZV|yj 2<c סAeU0)1$&#LB(x g:pYHAGA' d, zHJEV A3iӞ=;s>tP}7gv}q_AtcLq** QyC?OaUK(oоguR;z$N9ȡ{p Q}$1:JIcz9)拥 K4){O|er,o1_2ckXX];&54uOPXaKă@~.3[g#zSqh8qS[@Ew㇙<㣰fImch쟃pBQDjb`R_hqli@M԰iӒ"iص_΍saEan/Uu*aT3Qw:mSLvgUˌ 5`y~bg 2J6eDj F0ڳ+eYolQp`~q݀ZKV5hKeVB fJ 2}"M,~Q@J@b؄1E`Ahi!..LSn:^__30+Y7 8 $$@X~v  m;3 wh*&0Nw2T?Jb@\4acczaGJG J)A 1@  塮}P,jT׈6*? $ͭRR9IȂ#FC8m j:iX:7O^gM8s1y' H,#>łDd67m .DDAL&jH/lJ7 gB<3j>?鏝' <Q*{t0_s+)m rÿ}mnmo5|10!)0K pRVz @;R%'IƘ s@BqP,esOPPe|@_0ROZKif2^d# 2+C[oy[/ϵmɏKW7R #} A:0߄!^tyZŇ@OUF~1/,84k`L<~Ч퐨s7 1C"nO6xlZ Z 9&ptͺQŸDՂW嗽3UһnFz`n\f5V,F# q8s93.ǻy tC:3GdAd7>aX1\iߔ5[ Lr*l7QE?m q(M"$N&-W~0" xص1iIP$\ x>Kkj!`r@1vJ"[eTP:s)4QNѤȢ^Rg7H4lV">R8#@0QAx[a<@dXU Z؏Eݱ<BׅmΣj$?OV",L9Zj K);g g; Kz-B#baH> b¹<}GXA@.9yX;%}9lZI-DG n8.{ۓcP/bBit41aT :I%Yȴ>RK7ti3@R߶~ɟqсc/G2RDuWs"Rxz6i%!B$L{Q8ЀJ2sbm՛rH= :[2Th 4Hmh7;dlo D :5SY"OuEkkyiҡC[LictH;wc>>dp2\FvMr ADf@[CHQ ߰dNnK,Lj[Ssb7t'20'aJ〤}ܙeHB:Rl3Fa/ 2JpكYfZ͙=Vl7:5nI豼&RÖmf4:L04p_Q9c LJp ~.*ADZQs j,,S ?ӗwlC\9B֒@k(T#¨ jn :t.{Z~ K1+X kDӵPŨPtW7/9+ ޸&A'fO rD¤Z,^ 774RBJfqjˀjvzB҄6+)tJ'7|fC{@ Pc lQD͙ID'yPz''-9DAQ"@Jvs/=3_\ lRP"TܱS'zҘ鿠,?#=Q"Xdi>hPm m?&it BzWf .?]iQt,I*͹{#5όMr)"c+dt:yz9 i54SNZ B_LF:@xxd<1䬎@69}b W̐q0Xǁ L0Fy kt@a ܑKxp=/[4PѨ vtAQZ 5ؗD}$4fuB κ= qtr]=R#[ԨD)Ē~@8s\F.:t7i)!j(*ޫs)g?x ' #NfMA-B?wжd+n@ ^ΡFtQ2.8k :$8t$}3G>Mp|hY|N'Y|D3? HH!G5>COXܾLUIj &Iй~ [ -B<kDAs[vaSPНxϿ$oplت@k1h!^z(b `կ\MrQ@BN貦] MLL%#S9^e| yAwn;>e, qIIchc)Z _P,:86͉8$w$T"vi@\۟>Cik/6D(qZt@:0_K &tg=eoH-qb|J9$ cڮ=G8}߷A==.Rt~m 07C4D]ݖE1Y!5KO+)-ɓc!IcvطxI+ &csb'ԈY\;$Q™I<1NtV F'`.gT'N}Ju%4BM*yӍHNI+ FK}-'{`P\%; f~7+W!]m0 >^h6&kn.SR8VӖσC!*1S9STwJ; ! =.%QriN I\Snc/O;pmY+ejj.BD{1xmzFh5/_m\=XuW26;6jo}<͈Uto<2.4`zJ/GH鸶$'wFRV[r-R\bƨH3B /[5x²C+UM8RR*6:r킞poFF|dGvVPOH bx{hi/[?H. CwBՓ&j}GgpJ@%ի %FG\\uHpO(X/xf:BŚc+X|V,3t៭tsk2YȠu8 .g@Z͕5Yٞ:'n~0z<#C.{N2ÛC0_Ds[q $]LЩ䚗=xmi ߚfc0͙l6e]fs~A.okZ$73m=:]82;xj==0cLšdƧ;,t X~߮rr#BA2/[8ݥ1`V5:Nt~ Fד<{ɂ:]Nރ8=Q2~=l7n<-1aOm?}4oY~"]Om 5縥+! & Imޤ9ݮw!,2|Ñ փ\('QwX(DeלepXr8sc 9m| F3Cp{޶A85q+'=Y"U 4a$ʏh-b/xsjJX" ߃fQQ5> 9N^Kn9xƠrm̱j(X׵kk4 ;H ftdYĜIS8%86(Mc02/ѪqpЊ1!@_S> E#4$;CNO2|[g7fה@ fcZlߗ$z{&'sS$ ;]vm> [wFD`G;uROR3Fnn~1 %bz=2 9Hm^.Ӡ s p)o^~:DWͳvn舻gr<QOh/~f@HBC6fDf-ذb% 0߭l7XtpdJǵ/R5فM8'׭xsOB#t_וПAdض'!iIeQ͹鷦/>)DK%8 N ׶$q W11*-@y_*["b8!Hm¯r%}hO iWԫ^Ot\|_ }A<zo-"4qe>@*Q4쩕{@&GmF u) mȇvW^W8ug'q TwBNˊ(Polh7n*Bd玎y<%ACqrI_p>l<[u\z=BƘSR:JQkh]kՉ2 Jff{{F.zYdly$`7Tu˾9.TZʾEeweazG.yے̮o>l߻N<²< Ԛ&݋/l!E5 o4@zqV~/< C1'PftbF-M5w=,0ȇnoޛYa HhjaWBP{o AIDaֿC? vQU/u8aG_>1rA΃+V:\vGA34JA@t)(y2H`~ť''eY (6@5†tVsEO+Wq趇wb&a@ƛϮng. *6)Zjt gJ]XA- c=0Vh ڴCZԒzԙNp-{s໰w,xȾk:20 Lyw=c5jnߦk9`/ݶ_\Yi9S Aֵ<#VNhٿRɰ$@^60`Lz|X^Zc0̖n{˭:[ezgZ @~)%>kG%a`>OtρLZԾt)?Jf\@;ޗ0[w_e 0elcxk~{K]</=!v}<,P ݥ/1 N&'Z2m{S]C[$"N2v}uo$:P?g4 xoq}~ͻշW{dja<ä$\An c88mΖG{DM| N ,/[mT'N`:t<#W2s>cOPa:Y9a`J(xЗ"ó?|LkI\>07?먦,Ô?Ul؈`!+)1/m Lqx@3uv.?5?aC]{ɕ/!w!cUS-?~f.2,W۶'-'QTpg  cθv>~Çm8mnhV 2.g@SCd3}azm0D6J紞{>6m;Ñ땭{WV߱͜/㵙?5@a%`RZ9~n; 3>9gM^fAu {Z Ȃ vD;#s HOݯ 0qT}7}I t# PHTR w*e%4fwo]&}wm39`_ EK%LjI `1rޤlmr09euueuTe7ǣݓMfTsb[?5@Z Hr@ ՙ3Ve7ʫ2֜BjHBˋL]3C`*>9BRa-Qph9Ȯ7s uJej`U-59tP< $Z~`νLЙ_1;qUf`è۳"~&5gK BAtI/AO毿(]HaT\7Q=JuU; \8g; a+:9E5n&dAk_?wK_B况٠eFށUH$>CGUcFsv|\K@HYSouKU;:vn*tc(}fb7,X,:n$+V@(U[sr vMJDsfv/SI%sU2?M\}^Aдy n Ϯ2 W@C 04=8Te%eͩ8Oua X mk3Nsdž6jc΃VTޕP u%hNnSv0-KQBKQ<.pX@wR62ȔƋo9F5F%FC$~abN 4Rzy,No O-[(Vp<9X=テ>Cc(Qp`FXf;J!d;.f ?ZLZt3 ZM؞tTcWPʖji{',gXV .Z/҇M([S!YؿМy,uRq{f%X o2۴AvoBHy[P*ڶK85[󲞬3HK¶ZH%ĕGSTڗdJJ¹k`JA3N=o2q/)=CJ!<ņA!8i.TUKۧR}aֱg#3Bf\]=0]{aT\85slo3&5q8jtuXy);Y&?YY2(.ƎN/? _]* IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/logo_16.png000066400000000000000000000016661342663516400242640ustar00rootroot00000000000000PNG  IHDRagAMAOX2tEXtSoftwareAdobe ImageReadyqe<HIDATx|mLSW-m+HPq_x1 n(f-Yof#~1i 1.6~B,PPZֶ\gK%9yyd7c)A#(Z$^2@ 8h 3\_V\z(%YţHϠ;ɞ_g21sT|[v~!̻bs&P%eNh}V&QP/kx*cF;Dsby9Iڲ)g[f^;= pIRʦgRH"We%49ZQ6 7i.'?rB\K b^[XS/!BkƼJwABZ GHAٹ6ǹ;ŗRC ٸu( 8#d; j g޺!@vP Ζ 07QYSkCU:S0*ξ`l?iQy248tJ]FV G T8V&":Gd9<> ,϶-_H h4>$LݙKj챕߾ͽ_f=aH'?Y77XhbBPsYDQgwܩ7zV5j i!Z\7ŶWt56۱Lϗ셟Rӏwx$R˔:Cbiipm{$y^5a'j{A>GIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/logo_32.png000066400000000000000000000044511342663516400242550ustar00rootroot00000000000000PNG  IHDR szzgAMAOX2IDATx^ylGb v@DMB p4@U@R!*! (@R*0$n|k]{wfvkU,>O4|ϼ+}$%[iIhhH· |lS$GvEu.[^i+,IŎA"[v2aLZLǟ/]KU؆ηD (oG.U,;-IѴme~;^W˳ sS TDžH(Zm$H&S=gnΏ[Դ;xw郟m=(=z㝷t~VMŒ ߳BB1 v$Hn{y:)af,͖cM]>=Tֆ/wKJo@(.Y)3.4I;ds6mQĩc+ O:qo]^׫o`޲WCHgAi02g,H=!5 u'|*˺Y-YmE'Ot'!  I$SRK2&Էp: rkS';[KN_Z{sͪс,8 –L uVsnCqCI58m(y%)RL݆1?Ҙ>Wvx<:; юcw* $m=X0pi9vJ Fһj:$Vsg͚8"654F'OˮEV/Μe./z1J}.OlMDS.GGzLBCB^w ͵\DGBl=,B/\ ̖SRfl?!!6-(yo!4:^;a$} AGO)׌Dbٛ:x†=0uUܩt+,[W@xx]Ǜmwƭ"1t=nè[8^pղ2qLZ[V4#FQF5Id =$O~eٖٖ>Hw{cbXCjތ0âZYIMV=)jnBiN( D #|hղWț=w%=/πʽkΌ= #l9M IQ2iYyp{ F@ā #I2K]i}``"_2*Gx5, < ǫGZ 5u h{M(\B` E2SY3=VBNIfA20]YS@T"0uY1 7|a#ΒR \0Լ5+/)ʛu6 }iǚjSrM]Ο ##Bow U |W l<'ߓA_GnoW TR?&!-H !i7f"y Tz{6v>!8䲁au6n\>0lp(pW)Llg-6o5`jq>uRsWnɏ=#x%! JJ8UH`wk}LyG7f,%W4k3&oB--7F[Ǝ^& yE߹w@g(Dpx X\ LNq7)H'd@UE(l8d.SoxeeN[4':8fD:|!>$ *c<6f|ߋ-CYXT 9 <sMw0%r=IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/logo_48.png000066400000000000000000000100051342663516400242540ustar00rootroot00000000000000PNG  IHDR00WgAMAOX2IDATx^řy\e?Yz}kbl#A0%FDpuAAGcEeK%% $~}v^*2ToSGzyNPJzWE:ϕ8蘚DW%t 7O.q鼹\G8(פ 1t@+AL0Pe ~+f#x%Ep<{v3OS_ْU;B$g|ޙ``Fhi0ǝ?(;懲޵K7\'TEoy@BHys M)y٤g Α4/Gv|]}:?oYy?KݮyWK@hsQxCIb/pIiu71xdiݱՈl^\umߚ:){ߣwӃ7?]Ss8s=~ 5w>tt#&N\G!h,^ﶰBaߙe`ߟ z gz|7ѱk?vwh}uÖw~֩*,(tM Gx]ʪꐮH%%ss\Nq |p{{J]KzVxKwwahC:ذmQ|@hM4[4V% $SPl=:(ON=q߯oKܴx, ?uavؓ<Ӈfafʿ:1B /DjMimũgϤep^$M /DZiYk,p$L$Ș!xT4ۓGxGJScϦ{ffĀ@'yhf=(6,23TWaz `,Jup,c#@GRzx}%hߟ }/|wB 6z6D0uA0;t2B ([5/n5M?}4`f?m& 5{ECc@%0; 3}Ϟƅ0m+ `зZi0;t =E}xӃʼn1/pͥxNʦ%/k@mQq+=8Tvl_s}5ka4P@ENOۙċ@ K?߸mdl|0|yڮ\bA}c](ZaS#FɒE$RվN~BitA;52}@:ZI?ܺj,&1+q7y(\$ iK@&iiS}-҇@Ic=/ fGHlY#ƍ?zE2tp\LWBmLN@׋7.ZX-f>T7_؀Gl~H 2 `tX1;xJWwo#500`1yLÞg_O`'p\.ٜ/4ʾtɂ.k35bmmڃ=LF^8U[\yےE)Aė5qxɟ#?p H8hMJ<frj)|w &ۚaO#|y. nvɕc( yV )x:Y` BP%|ܹ?/lI04 /IUꢥJLy۟TyJDZMQYdTʯJ۰cᑗ{ݯ([82{s]AAr$ ^k@ !"QL;T2GCI.l˃_j]{) 8/.WnozVCJ2ȳuqɳQ 'X]%r7{ᄒS|Aǀ>@]ٺo~?ooEƂDڟE,fH{Aa`"|.c={^T<[tȏ ow~zjKk29|+(锆 C&}=9jx<JVJ_Ywٗxe⶚慚KTPW 5<*tx?-{I`/ PhCDow|ۥ0M?tOԉ%2X$ʛń*6oSSfPMm֯(ޅ>4DR)PG@܍G3#2Y׵VDgTqIg`bd/ySs/`Dǘ˯fs_r< C03Lbg7Ϧ2<i]'F֑:@҃/tv#Ru߱_d ((F.($:,7έ ԾS@/}w&pn>`7 8cIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/logo_64.png000066400000000000000000000136571342663516400242720ustar00rootroot00000000000000PNG  IHDR@@iqgAMAOX2fIDATx^ݛ \yso3=fiF@BbB&ވmW88Kىml'v0yYsKBX%.ˈYٻgz^rΣꞪlHV:r+7IO"B PJ?d( ཥaFg%~XJBe-XbeoaG]3B~x/QJ"BS(Uoݺ:+ϓ#;63g>@ C!ux/zx˷$߄^SOnMX[M|?R@B{H JSR}ib I%S(RܷsdoG|[Vq--ms?? & aD E;p;Cx*\@Nj7> +T<77ew̨W/Bux"8ދ/dņC*|J XFrӔNV0PyCɣ;w1Gm|MB[_y.qs@h{U!A{ ;\] uJE,l 9 =@;T]K.yM?|OlX޽w~?_G7GNzߡP@)0s(9o7psNM2%)R)|| RnGّ_7%K~o?wk5q5Z|پ/O)$&BX6,QJPvq쁯Ç]L(!JS;pn}x.k> nuUG|* \~tZwi )#H9PDK8._ a_Of۶H#? ^wt׫?c)*#]e_{w=U6t/Yrݸdi!( *mm}G ʎ2ʠ="`XB@:dgv~/]M#[ڂkVC]$G_xyq0vYs1iP@ PYP M!> mcPH)Pswt)*gř󷍭>_.mr~ RZm.gcS*Z@5`NP2BDF+(E:wwtMH'5 86S9pw2cʎ/9oWEz^7#1ipy5zɒWv'VA? 2Ŭ(P"p"0^*F]r P=6'ؘhm;&2@(7 ?v(b3ӓRgF?#o¨* nb>C.Q'ހ!/%xlBz}@Pũ͇Kns9@#j'}ѷtУ7ݙ́6 &2PCEA0+aD\LFk/@x!dXDqۛ o \T 0o'W׿紦0rj'پTш Oo <+&\Psx L|=:1?ى4ۧ[X סІȡEJ@Y+6j(@ф%t^ivuhj XobTcxc/ʝ;zs)`)b\2uMm{ChA+8.(ROa ;ZJ{F K{ˊSo]*-{o ۾yg4pU@ U``0LX$sQ^9O6?ona3ڇ$@".WMk-U;p|D!OuQ%mk-Y6?JiKB $__ ~ːTŽj.à|rymAU$9x{f(Y@m{W&|Rd&\DAPSG(gw'ԟɧ.|R(HL¸_Z PODrk@Ȃiƚ"PaKNP+cl_0z?=H% gEv €$Rx+ QepxLI'#'A~ Т9_mq$L5P@%z `TDžDPfp|xi7/\dZJEم8K(5L, zQ?eȈOx?di05p```XPҩP21vt jz13G?98Ǫ5`?)mҎхइ%gg2Y\G&]F E )&:y&OuA*Qn; d?Œt!L#f&JF!"PCqNܶ250p8< e-O6-ӛ% t!tLTIye^ԅ-suz-c)'+{SH70z~[Y!}@ C/8;q=*4lқkJLLPT@T鏰.hNIsM)Jͮh@ҹ_0lP(A}%%* 5O-0™hF } Ab87U۹&jQ9&w*YH9 qa/kF`߁l/dI?[ 0Tw,3#sb #|Xp0k='ܾu+5JR[u:◎h/maYW{9 n>n2'O 8@U񶞳D*@ >) P m[yS[m3C`0˻3*_eX5=xѳmh+UX$(-sEPEE5u4Cޅ)&Ҫ8> W ֺ|Ms^S?MP~#xmkw]8 6BLd U>"(iBuֶtW`& !Z*q7 &΍7W54 `t7TeV5nxM}7tuYJNjnpN @t 7M!xkcp|2_9$#Ѷ׶vǣܶ J(O>Vy E[P_ӹwys`"L%j#$?%GӘ}0k ~dM[ 7D`eB̆ɱ_iGuGhX"r" $džEQJmV `0:R&?ٓ45KzA QH[+zvw]p%X8 ^&'=2Ě/ r,{NHJ[%>vhIn>ZAX5ݳhPŐ{$ғ@& U]_TW #* v{#iXF/Wk5p$ , @GغGueF,:NL QkZ>L "6(͌g[yU-wwU- ny5SVc346>oa;j+~9MWse8tpP||7' `ϣi|Oi"l9l5/ ~MpM%=K0L2̜ߌD{?֝`8+EM訅wȞ<%K8懈hrOe[\4|Aؼa"+!-X8M m??3֝qSu/t>W,@ ˼F.:e-6'297? -p|F`ʺP732Cyigg]om 8BAD,dͿq[럿ؕ3.L iB</׾Or'g~},zg{z_{~tkUW.6ǗU8084]n\c{a, |XPWM_9r&c@ ۇ&z gȔcAOyo]qd=Ӝ _vFRC5|á%``hr500I$6~Ho {o}[\ATͿ}}Z8 .oJC5U0 ,`( SVWϢf3?9>.On )[\y  pY0CMAԊT|ݱ @n%i({:MLLcU_Jge! LUī_ާ,k#Id(;Kr0E`۠9"uTl O=ۗ<ԟ=Z|H}:7^9~`?| VA\=NbR)0PAVCC$xO[`믞?O{aN?|x0bȻ?+CnC2d"x'E+"J_ϏqŚv*qOpHiMwC;2K{֟Ptuу#CĄk\9x(?5[s-)f_*/<+??]7oxOlYOn!jK 27]ͻb0Hd`<'F=`Df郿8RUz@(w؟cR.7̵[ 5ﲯ^7TGat ݵ0: xx*;B?UHgFSNboB9t RӔ^ti>ZWq*:9-x%'Zf=a<,hosKW{=dŗ~5*/lFP6~<_1(j8IB@f9n()fe4G*C NVKw_彗.i5aT@FNO{~]J~BjB&K5͟]6FM wH8!O2:=a^aE)L"?|޶Ɲ_JvfdM8?|ԡMoF_E(eTp?X[rf .9`JJ/R&M*I ްg8zK Q> }8uB_~SjmL:kꚟ&@0T|1pn .&!i0 ;ihhh񬭍}udeٞ߬ڱ@]TTYZZ\A d&'{?( `YIoKZ9rWtv|`e*FG'`ӃadbAi$^ c qlz)m&;h0j0> P`aKĊ'|EI00Ͳ¹%eX֥1 HJ?@"r '` fA@?T}  U[:,g,˲,] %'[{3wY/ji54J A^,ɐe I=QLWw ƏbZG< 1.QAIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/mood.png000066400000000000000000000013141342663516400237420ustar00rootroot00000000000000PNG  IHDR(-SePLTE777BBBOOOSSSVVV[[[bbbcccfffjjjmmmuuurDtRNSn` CDc?Axz eH-ǺώQeMRklֶ[IDATx^mnA^12Cgf̌aO+VJ^_ "/ B=r;*TlR}0HQV91Jм*qd$ 9]dr-g)NxKvۻpƟN?%L|}PF\[1v>m@"ڀ6h+AJ1\(4&E EmEh3IR*ұ xnp.pL 37'f?H*s^9f1R88u\6#W}TB?:p=w ^k7\ynXɨ@#2i7Sf:QQ4$Q,hkp &/LN<'X/3N/^>F^ /1ƳіʝȝWևr~wA BHAub0`:x&65j6S$~$bGP0 lh@r!]dA¿рhH)6t`AMpńE9 [pxLn&,"`cciWeh`NU:_ٜv8U(IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/notification_chat_delivery_ok_pgp.png000066400000000000000000000013131342663516400317320ustar00rootroot00000000000000PNG  IHDR&/sRGBbKGD pHYs|4ktIME SKKIDAT(ϝOHagfvgfwvvͶe,0; .$Bm*KR.Bc ⡠ÖiDenT+QgDZs|y_IDb./VWO%ni3oٻ\5SyO@\K=X6+(oh|k{%̎@]*<ۗ !^&c uAF7/zFT^8 Fa`:F+x__JߡsMaV zDWlǁa,BbZ}>ZEi>,Os[o xV2PpUJ}%(Z㎌zj"G~(R3\hx?+xN "DU(DI<žyb@@`qʼ N p"42xC|`us6Ѓ$ /G|Mi&_Xk`zr"(DB (əY. Cj Eq^`2ʳY\9 CJmȇoQZQUJPlpPNy I#}{>ݷ\nS5ۣJsw!T&{ ^[![elPpNyMRɷ?g|A'}4t,@LEf%f. q׎ŻZTĠ "xDY@S[=8CYz$D^@$@E,W<"rڋ_eyqAa!Ȭ7YhBd9gd:V$! s$e:{o$"Hq.skpaqQʢDՠX,jp"n9]N]ki7 8` wSo=c~ &NlGvlβl>"Tf#SÀ&q.\[] IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/notification_chat_receive.png000066400000000000000000000011751342663516400302000ustar00rootroot00000000000000PNG  IHDRlYPLTE4EZ8QÏ%i@Zh-wT䤆Pٹ73㣓(=Ph޳{3o)LW"||0tyf|ˤS #kk.|2%𱄏cv}%@H SH,cPJJ PxpmUa]*ۖzoȭ @we-z݊x4)7""RɄ)pWDx>Eh}Š vn'<N9-u×_ǴW/tt Ċ8+Nr47cMimݿywrs=.i(/ JVXKO!d{»w >Đm=T;z_ߔ!]IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/notification_chat_send.png000066400000000000000000000011531342663516400275030ustar00rootroot00000000000000PNG  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.554/iconsets/system/default/notification_chat_send_pgp.png000066400000000000000000000013041342663516400303470ustar00rootroot00000000000000PNG  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[[jeenkjaplqqJqaqsqql4mnnnnn5IDATx^UcsPdm۶U"\vi}'s|{9z ot6_By.Z*!cqDBQ,>ϖ_GJeF+5;ސL b`G_^N`Hg^?B\ mAw{C ;>9!r~qyu}a V!S<IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/notification_chat_time.png000066400000000000000000000012301342663516400275040ustar00rootroot00000000000000PNG  IHDR(-S;PLTENNNPPP ,,,000222555:::>>>BBBFFFJJJMMMPPPSSSSSSTTTUUUUUUUUU`bb///000CDDEEEJKKLLL[]]\^^fhhfii˿555888HHHOOOUUU\\\bbbnnnwwwTђJtRNS <^x2.?_mlZ:(( bS7i-+xxkkxx@IDAT]N@E}c;@ " w-(`Ư9tn YPaly_YܮX5sB_#o(0RG%HYS2ŁVD4[fꅣ!snC]X|@Օwf;>2aacSy07uDsϣU@_+IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/ok.png000066400000000000000000000010061342663516400234130ustar00rootroot00000000000000PNG  IHDRa pHYs  ~tIME '8ribKGDIDAT8Ւ+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.554/iconsets/system/default/options.png000066400000000000000000000016041342663516400245010ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8KO\uΙs@nZ@ U &Ʀ2qg\G5q Z/""a\wѴO]'VW?cx8пHVqV|%s݀(sxXm{819yk~G~2!@Q+%ǍVlo( 燇( 3;;y!AlZd2iFPA̶Zz s2]U}v˭,*xZRhqfټ3ݫԷb\8O__7ǒ 8:*Qoyst@ܽq8Gё&fPԉ~=Z^J4v)8nv>-z喭''w])4S7ޥn8ފi&_N:&,+/|{f\`(8G"eHh-˹lf,?e\#2:0Z$͵mݠNMq#%Hf׳?ֻۭg*g HlĊ8xB)ol٫ mD({=<PU"־ wmH%@-@:(#DJ ˵żbHj'u@,(nrp⾟8U]axjoIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/pgp.png000066400000000000000000000013201342663516400235670ustar00rootroot00000000000000PNG  IHDRasRGBIDAT8},{aͬZIjU뎵UHrw\ƝĴvmbY" ̝r9ܪ떑X9e]]>6Y{6$yj"r>9!}㱇נJ8Dni՝}jx9ERq6$,g'RP`u{/J5zj1f0j(|RT>%V5ɴFv7(\O22W/k; Dk&c$ Ħ5R[XLxKՙ4ED99O^% )`#!{6,K^Z׃=Fi{^y֯SjLJ+&͟[ɹ& f!N@`+my?^ʔG#5=GNL֓.M'O0t ygo>`a]2Cw.h䐈Jgֲ`ܲ8VU7❫(M7$:Z,m85C '6aH+XXゲhMDJ#BX ? -j0r/* w`fXm_O@&ѐa3PN,~=A_}(sP?JF"D.[gvI3SJӶ鵵Q:ÀyۇLCgofnWWdl_bJ1F=sienF)drZjU?ƀg{vo[N@z_^کI?TNշVtpk INtRNS,Ep!Rdxb:!4P QiW* jϐ IDATx^EcP$)m6.m$)m\[9gAd %, bIjf0U 㷩DA󫳹DDZ;6ct8.:zxw(:bI:2|+kH(1Tǵڈ1}>1IǤb5:Z^^^NxVu,H*rV]YYy8d25*_; D.kkOfjns7)CO aӢ(R89UUZ_hU|~T* mzD"K6WWן.-];6+J>]j;!Ʋ0SqI=*rd2e{`s(5HO$XD:7pX$ O0BwTǙltn+òOrgL׏r[ g}1L% 8!j,bo U@|o{ %. U^ \m0830:o/J.a}>8 V`nWXc<#IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/psilogo.png000066400000000000000000000404631342663516400244700ustar00rootroot00000000000000PNG  IHDR@PS;@IDATx^\{^udצ(P$(IQE $a!&xDդQJmRDE"V*y*O"cd{mcݽuΙ~^RΜ9sf}5a݇7셮9jt9z9z0* R//\UС9$Z"r@8*̯_ch`^y8zKfD@#CQ:a[ŁMĠe 4"٫gBZH j()i2h`:5 }0f@RDg@r $FhE/[BbL`m5`r$ Ȏm "CB^:h R`DUF Du*^au%vjr΋ɣ97k(n6+: ]X@ݯ% Of(@9"G_źEByZh=>%h Zxx dU1vD=Q!=`u0bx\-oǭ-njbIhVU y-[uMS 9k9GRp "lŭ6.u! CDқ` YF0}S!pTZPFwJGP} ``M$ 퐥CYU\9LL]^ލbU V&!'5ŐHwLQ >֔t=Yhe>BD-,97@%xwǟnY[A mnhA{AߡA3P/'EMluvʭ )$/ iHuQdE 6&jK&Dc1bl0)h`ɠ ݄?0O+4ߍ9pn fnb;==Ч;m]2х'`ft P_(mlιzc ]0I0!yPu-!K^&][WeY2-^q'm\T% p s40ZN腫oCH D&9,n0-xmkГ!W#P r|k;J,b(PϨ$/4"I^*|Ъw,F"xY'uVշn"L5㙳@խjz{o$-iU(K1P7l'h 0oYdЌRlb )R4heo XA@-'}uhg X[!GMA0d p{1d%ŀJX,c{Y>p>'?\/(<.F ! ~=.#f.N֑e~dJ` &d c{fG/EGŰi@*ax3`ߌ&8-hiJ^dŚWԛ f"V&'/ҽDuD&"md){^4*ͬ{<L{nPaJX>QH/d]Y0; @Uid1`p q,ND3Zp|6Yx ޻mӹ#'əuOK9IԺzӳ1Lr2j:Y]d~А Mڻdk4 ft |gb|ҜRJ}Glڵ-C@ b@ ѭf L/h;i8'zMZ?ۗTb;e8Z]G"^7S^BY۞bUZXOf{,:W2!cҭ dkVu(<^k`ri*`2B>F 9hUAo͘ʤlRND0e2%91 By UZ0#& j I;RHgֆA*g3Tvi"rn(fBC.(_[ E;3#K:u?9v#w4ӿ>|%ЖO {l|ɱŋm~Eg'>XjM6 B 20P5B[sʘruуČ},B{|:OF˃L=±7V+: $4Qhq=bJ4⦉@HZPJ=c8CfD埦Ϭټw_]/,@xpsv篜ஏ]ȷ{a/~?1s}>{Ad=D~e:&m4qxg6$A ځ]«rWc ST^C||a;qිE-=Y\*h]ɽİSBCX e6אgJn^?X2M"2B`i-&UGp񪾼o.o?dY33y(,=_r/r=w/?tǻN?:_~c?yC߿|bcȄ 54TkoS{~h2 J >-A)I9!U+.Tλ+;D(+b>L:aҏO\  *4Av# }Ͽ.Z K7|orʧ^K魗>wj~q{Ow}K޸mE> 룥t+̦_UǾVAh7 a]``{RxD^pm;QK?!ݎecH:+{}7 fLh~w}Sl9GLٱ=GZB3y3W`;0sk-?31`\vt k`\#XpPN`=U0QCSd\WgFj¸/\?\W3A}}{ծ^ Z!!d (2NH+씉J9J IXNA aXҮo{73O4s3=Yi{{}ާ СbXJNwWgYޭ+":uo g>o캰9ڋWec*9﫲ry|fF3ьQhޡ*ϦLVb^48!NfdZakm4}"+#; jdBn۔~S?Ax1ҲX/]qxKd1^X 掃W-4WNxhCFU+7rYwg zt z "tm [(x*g lӍTc~5ݬ#B[8xC'>Վ>^+iI&s]9UAQ@7N&)$𖡰[Xrʹzڗfy<҄mL֥ Va,XV0''!4;vn饝s~rʮ(!Kkm<kfby j9d |g~Wfw5ffei/^{:XYB;36z}j~wܖ:=( >s CO3K߾:^Wɐzǿ{%3Ѻn?Vs+KMdy;~e\cm=ͩ8"0}~(2Վ *Rd< P>r{`(Ldct1۵H[Gr@6,)q:+8'UM2Ʃ~ _Y4 Ж/5uq׆];:tsww͇Nl#=_kCu|C/޴mv]ye [z ΗesFvqzͮ >\Zm0ފ삍 7׿rs؁vTe/=j_ժ>tHB_ 鱊_R=QDŤ%ңµ-T"$ƿ2ᓹ~<( -:1l 0(Y L[<( =j;iX[2VΫz@I8l5NCظ,TSp :loxuYަȝ"pr.|9pfYWW.F|1Ғ'߭b*"_<}޵{O|wv]C^y GNm_>9!r_hbB`.BXA I(LS .Z;å uHyC?4+JJQ wǶ&fW0-gvZ q`H|*a ɘ# Hw88l*#JPal`H Z2b#N9$xDCÔ,+3<LæA{k5]zU;7A ґspОf+ϋ`X9 %ơnjO?ɝeRgaՊ~w۱iw1/~1Ic/>q6˰n 3P2144Dĺ:bIą^ ]u~HM> jK+1z@杰׏69*hnS-N OSF-TaEvl_FJt -J!Wdb%SIHsVs'O?snfO}R|aw~Enh[> ӏ-llЊ*GbP@ aT,+je^` OUWO?{m7;7NǗ+'B A9s'ײ+Wpj@  ؐE@0&HڙvGimj ]1)񬰺ts6Vqʷdnaf&(ͭhQ#J6xPFӁ`r#!L sr,hԆrz/=^Bawtq+61`_=tzfV5fsp=(2@^{*},ˋ>u"TUDy⭷0?3uG?Х"u;fK^!e{15[&m)qiNj/LL_ߛTRTE=3 liSUy`) Qp&IGuLڑk$S+cSFuS')$dP):KbN(k9Ѵ;DpoV  @gwhmt Ƣkr:hvV,ZUt%"sEoE"7/?^?y䱧}(ݗve|lh}Qھr%y ~HKL0i-@X\D?2rl iL= x nĢAc?bȬAss̢7(Dee^,ݧV0NݣA+[ItP^͋ި #[K1yVG'9Wc#`V=qhv¶+/gPC8ܞL#<҆j1Հg>睢˫gVvwNVBrbL\IiP'i2m 3Fw(k 7-?8Fڃϋ׫^%>{nq=j`խYX&hZAH0֎rh4aU)vGM$.]NH`"j O8cypZ׆O"MLBYi!:IO6tb~h%Rm>ar 6i@BPFHU6 ʁ 2%_?:bgiy獯nagO.-sew6Jt=Qo\^[7JM8L"j V7\]#OWkeYo 3_}{9Ȗ=D2WU.'@v{'H}7wPhB\Ҭݖb Dd~@?[cGdKMmn+ @Ҁ#Kn h^1`zKV/n@!T<+FA5Q'LNzOǼSRzbHqUR|jRcF@{GoDN&V/}{#lYy)vv^|Qo{Z:º>dӕWsa.B-i>JQy!&tJrWU;͎FpT]m"ZQk ,=sΡ=-%\SYƇIxfZ#kv6/ +Ѝ|2NN>ThTEH4CR>^Hc'tsR=$8oxJB l/ -:<Aݡ=ZMz1J&8z9 ˁC .B#;9bpE0=}aV,̠}{SZcEa G=Z([1ˁpMWo9L4[8 =ͼC8h'bx]W_^٧7;;aխ VVۮ(TQkĒ 8 .~ܘ$YgUZDHcմ-`rEpdaN]&hAaQ $MGи%n԰J#z_r'bFeMqE$ypO%TBQձb,aD,H,B~3| ̦H$N~*`̶̮O6갺AO[3yYuvZe.f ?\fe Vu}ʲBxl`>/KD@1Qo!Ev˵6MzՖus"|3_֏̕FV[^_߽(ˬ1?sV֡EƁ45Q(e1~^!9wd'gr2y0nF[hԺ=''7#ŚuFqRS_+Uǜn⋖, 6d1RQMcW #m_mA#djϚ:*2e˂lDgXɬo>Ek˝Koj%#Nfx[ftSf;bޜrYq~<=@U񚗵^Usƽ~?8x+kͲ̋`ϵ.;n[\Y{ґe`SqT9⡎ 5 A -:#s J92\VFەC:p}|wܺgOL_c ?$f+PrCfm\E( %,r!ʊL0du!刡0".n)݊]_j [6n \+Ueƣ}vkJMcNx1n#8t_6Jr،04B@R 1Gлt"X_M&΂aQ͗XV$)BFcf5f\Zn#lp@.t~ @I=Yډ-L/f-dz: ~\E~xxmFS/\]]^1]^{W `5߿W\ά5(r 3PW`7nԘq0H(Μjf(G,9 栾93DX˕eݬʽ?xM7_wv_tڏ|'T|Pqz,'S) iP!jR"[玘(K3SEHH\(XRFI0oYF3(""$^!mu|O,}6VT촍( }ݓںgCE) `6wzK?o^X |Nuk׼7k=re 8;%XA1J+O?uoṋk `E#VG"D\eͻ[vlo~׎-]=,e4᜙4$q:EUIצF$c4+(#1ׅR)Rj┾PT$=C !(=CP lؓ 靐|#",}RbV#yd:qɴIT5KU% OCq920Ί4:=w}k>xzxp<ǖ`X/ K:Xn,`ͳp w~G5>=gHTi(!?UoMNtsNgx ww79ń"fB }XE=)Jx!LFuV1(D!򘆜i4qLT~&6F2Y\$^ugBˉbVH8%j28&I@*6+"H!֞wUeI'tN:D,9'6tC!eOmo@ePdK=0 ,}}#_)oio@t,v|G$1%-G9tFcG)Z}XjdJ>ZNC>Qޗ]CBH$F$36l= |k޹*̗8cp<쑽ࡽ?ˇ덼iӋmL|e5vBz&u Ϝ֪ Og]TU\q 9@yh0jQUzT**LH"3[?*b#PD0{)|uypVU mVG]P pIhqv0Th2tD.Wo@7r ɛ'|S(' n2 D j<(Dƛszkkg^xo5_?RlOY;tgye^nXߘYh#L L|S c0r@@VM5j߁WC1D^B)mw_ԫS ËoYFTH;42,2a #oRGX#"6%&bmt!3B&@M8I,{HiWrH6r CiUL &GDK[IOccK0CE($ΑE1rADU]֘nemfiaj,.Ch`ᆪӘ6|7TzgC,8l脰Z;+4i\Bq\y 3{ Y/wfe.1 5:Z]QBFDGy꘦J$s9"aal/f T5x8/k=K vD{M#? 9T8M#x#31 BP$yK.]+s q-ۘ'j:0(0E\Y+:썷޸P*4J<mE"+,>1t_hdn2jE%yqηƫ.?KBܶuۆ^wŋ~7.syfT!WLƈk뉬ɇä́0n1ʌjtƒ1Ѥ-T/1,kJDiRDױ'؅n3)WӪHt$~4&RRtYOJ~xΒ%Uz̪:>t^B@&Ш2BrLE&JkR܅0e=au۝޷=?~0GĥNo[eEQ6zӹ ȟ8u?/,/*(S ]lBWӭ'lHF?_ e ûART` !"J&%m-ۻtǪxDdFC7RjJUUwNlCf eEm"M>@R{.A6`b+m@1 !#P"eP-6WJQ&oK]q/aIǖqR6>kn%\HAKap,="3f^3e. KGtb8#8Ev{Nbǀ.ků|8Jk !]*{)سKYQeYQoalѱ2߳yf g?֩ j 0y2{"!Tz $HM sY+boCUWj҄<6-ׯydi)kvH6ă BCd)db/~IEB]Йc-€O7Tk:һ gEǧZoNDlb%;L< sFhԗ{2예p6uN\)T+cƮZ‘ AZMxo;Q&iy*c2f0Hl/ngV0&؎(`Ͳ(wjJeZ*%7u,,-!cF_~ ƶOPe><vjORB1E枸G>f{n@T()LkoUmGk2m)-pX|փW:?t%BtnNv8tV~n54zC74S4/0r*"~pS!#.I2iLTUᤣ";$=c7A &0?PRUKP0@FNV9UB> 3|Z @S]s`wTJ[T R0jw Jɺ4A?&0cád`Y /$5Xa2UMs:Džq^_ ;`2niJtdCz~;esґE"z*tUGD1xZ0[Is!BԯO4q*R:,=L^]Vs=CChU;zW.(χ˔r!i ֮`ekWAvJ9`</iP <^L EbMPuN/f%1b8ܪ N,J:aPm\ij/@F%kM65 _%ϋd7$%amDuBS""ydiRZ2(N(mBBkaØ~6YT1J)3/{tZgiEǪӫ,ZKy{aE^b .ԂP!*h;rs&[C*jy|&R 7hɚFJ|0&{lmJv1 Pjõ*"$RrpPKZՒmHjSM.kLDdw(iya";'VU! 9Dz/bZ4οf4#3Ʋ]EL1c[rjUI)fm"8EAVOdXۈ(C;oI %uBG-"=Rd[?%hm ,0/˻h%DP3LiYS0&F~.҄mDVa/kDvaX+G3a[pmU’IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/psimain.png000066400000000000000000000020531342663516400244450ustar00rootroot00000000000000PNG  IHDR/\ pHYs  gAMA|Q cHRMz%u0`:o_FIDATxb?1?X~010030WFb`pR16k^8еCn0@11`F2/ ,EgU4~M^!bj&Z XA@t蜃Lv.B^F( m%ϞJ2|/ |d@ ̡""cuzWz) Ϗ7wvR-[|tG6~N<@ 3KR`d`tuc&[ t}ʥ؀ɤ{Wϟ4a㷟)=c ^9v2~/Ѓ xrjBwO]ypLB|rʆgҾd.% w/]=M_-k}y~氜C4w_~u="UB 4S%U!9/|ꐚ$Η1?ճɖa`n_F7 .fcfߙٕ4$-8^ݽ}9^KtExb8y[ s>恰4 +/^jHq21~|ϬG0@A fSxLgg]W?g?Va ]0Xdc{wq U2 b痍ʉ0`0Fbi`~f=߻_ީe Db???>U||`͝wBrIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/psiplus/000077500000000000000000000000001342663516400237765ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iconsets/system/default/psiplus/action_paste_and_send.png000066400000000000000000000016561342663516400310200ustar00rootroot00000000000000PNG  IHDRasRGBbKGD pHYs  tIME" B짫.IDAT8˅MhuƟn7i4&4~MWJT = J ă5("HVj@uC46&Mf7uُߓj=<ݑ#_]]`_=Ó#1^o/qa 08ݨI=(jr] @vyczsPUFJU=h }l# 1 #|~oڀyM}_]xeIPV=؇;ˎمLMu-PIk8O.4K!(ʂ k R o~CmOvD+U9tv XUB))b))̥854]R_q"\<5C^GIWp.vlۆD--4ҌD-plrj|{ktIGP9p"b!2: K{O+Kf2YĢ j*!5R“MT;F0rCݱfPƜ)4l~$,c:,f̟i찬[;SZth9?= 6FS ,S zmNU mJ9קqFښ"n_IWLAxhsUUIt3 i)=ez&u˾Ҕ;ﹶT8v2 5N92j̙s 1pMin.vxkƼN']Wc0T73W~}EP0O(A^HIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/psiplus/action_vcard_restore.png000066400000000000000000000015001342663516400306770ustar00rootroot00000000000000PNG  IHDRatEXtSoftwareAdobe ImageReadyqe<IDATxڤSOQK m-(#XI"AQكoLE<b4ƈb`S @.,Y:,my=ă͛o} c&׉x"#n#8DM@S_t]i.E!EdIJ N,#2%rڝVAc(}Q mV LR5]n`xXr(YQ uhsT֌F p\E 1)ZYV4 [m$a {܂˝C|P}:(`6_w7qR.jiZ842f283lmEˊ+xBb_fUN! @o^Z0UHw7,[NS:\,=rS|]<.6^>WŃu+c+0}'D_ߪԠ0p,lR- @KML:Lb‚qj(qMjQn#0.@cHo5hǚSȝci|?ń>n, IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/psiplus/action_vcard_save_as.png000066400000000000000000000014741342663516400306470ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYs|4ktEXtSoftwarewww.inkscape.org<IDAT8Uk\u?{7o~eLRDZЅnڍ֕P\n$KE.\ePZ m ILd鼙_3˹psϕ#W_yfge㧟ns|ћ7;Y!QDwzb\0. }L1Rt iJ$-~WןGD')ݕЅτjt8QU1"j\Ym>1wA.cU]@&MUFX(گsf]$ j*yΙy}9'UqThܠŽA=<@z xU L ًaOd䁻vqlE@N61,u=7?7(K hQsLfdm60@/YbwP>)w@qZ1.ڴ0e(D`ݐRs_F1հO/~_ᜫݯxBF,DFn+6ZMWܟ ]iw $HG ehQ;|W4M4o"AH&zA:Szv; ՂƵ˜36TdoH 0h`N|0]/eX.HmDߟd-(Ptɢ[rsVд`=Q荘[Hp.FZ晳+Lrd3&0`"qCz4&)S0ޡ ҈ wSOLxF1J8  L`בL~ݟ4KJ@@66!@)@ pHa Lf} XB-ѥ1-t%_j TZfU7WnC/ꥲ(Q7gTw@5J[H ^&2r+CVfP-qcqj|RC$婒j8V~<]G:TiZN2 g oR1+0;gt2^mRe Omqҿ')\#sk?ɼs}f\Un0 uZuޏ4S7Ja2? ]T`A/0p)cԸ4My9Yii k6k_?Iu55udC;NFhz]??v<~tWUjfv[Da9]Z3Dq|hQenu͸mX8龧}rgΚ008U*՘MIV/YJ Y'䬖}{ 'i`B{rvPBo-*L19EFy uF8g)?fjG9 kگK8Mz˚e@Ekz+~׹1LE5* ZcV_1yWugm-̹oO&(m<|'lY=cD ay3a42F񬱃< :8]+MTٺ .Z0S#`FVm'ONۄhQq-2ffmZLpƞVi[n58wS7BTt=}J+FWN-©)E==jU95Vn'MhGf?)f9&o7{iq@0oAj0D KWF"Au`[ˬ O'f2*QmCn)^cO%҉Ybi߳ut}bۥkv{rE8N|d p&%AmncDߋS6 2a#rt)S߄D&BiI0aØ 2'B *8s6 AQ ڑ21)ux8AYh0B^65r `^4 EQHXSбL=?b%Z&Mf$̒cZOO0H=A0 A`tiF1 c ۺLQ40HYPI}izB p[+Q l!m5֟bFXQ`*7ڽ7֠NgB`QFUwK@_[beeeOaNbHaeee=d:d;f6ep>qKuLvT}4rJen܀䍬`SHlfI恟^՘蘱鏬ok셡b^だ4q4r8s5r6v7vD|Q;[FFH^_ld~恣䒬窿P1tRNS3; dZ~ ?bJ#@=aa= PGIDATx^5咃@  w_ww}'~?Nݮj-N'a{yh˹4ַ^YP+a).x$Lɜ06fG2.ALo_߄10n#_? hWik/}U⹶yqpuD@fj~f4 @jPQlo݀C* |IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/psiplus/doublenextarrow.png000066400000000000000000000012051342663516400277260ustar00rootroot00000000000000PNG  IHDR(-S;PLTEeee2keeeeeeOaNbHa>b=d:dp>qKuLvT}SHel쀟䍬4rJnIfo^Ձ`☱菬馿慡4q4r8s5r^b⎭k6v7vD|Q;[FFH^_ld~恣䒬窿Dl1tRNS3; dZ ~?bJ#@a=a=܌  ȬIDATx^5A66 =?SX*=QIx!╨hubY5@J6ԹTV g9{bjitO9ACiٳ9/)h+Q6o(Uyjis?F/<@̻V]͂r~ A$)Z8%~*+ IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/psiplus/draw.png000066400000000000000000000006071342663516400254440ustar00rootroot00000000000000PNG  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.554/iconsets/system/default/psiplus/frame.png000066400000000000000000000010421342663516400255730ustar00rootroot00000000000000PNG  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ٵ΋֌ձtRNSnWIDATxڝ AD'ٸ=@9PPaEpIDATx^dWy.Ċs4 ,2#.`0W6\=l/l# mlAF#&]NU}:E{,Y骮.]/Rm_6RNƃӒ^mn3ZTpR#h?&0;eo'p1\^68lm>[[YjwV~;l|9S)K>n۲e_?Tmvkgotu.$j૛nc@ o 3{o{k_mx3ZbwqI? D\ހn\ *@n ukkf=Lws?v. bT%>7<|asp ?q7Nvp#%^KKT/O}};Y xoĽ>NRCmG+ï ~g{SWCίҿ* )Ɩ݊f-Uv':aw57_K)*j }z>4dK1(~dհ W>thJQc6_&ߺ[Ϊ76~m/X Suk7@D$ yWJ5BX?nWX. M4 @W(H2lһZ;{V&nSO?|+T0;YAեwQBI~P5.w!^=Z<_S[cϿյk:O}gBL? P^Nd-ϓz!sUVl;i-8y?J,tI @ l l-nz`iP.N@+ kϓJ{f>N9ߞps-/ޱۮR.F{9O<ҹ{'=[%7~~I?"G$bbKwwj p.MW .u(I#5< 9uzh@+G@~.&Kv |aG_9;>xe3wȽRoY8??jo7Tu\7(sK׫89i:潉)cMoQ7bn1SP!" ל}GK}Gd|9B$^qv |A0̶A_@Ta§.36 3e@Sۊɤw_15Y0ǽl,6>@;6Ûbm2S._-`1tdUخ7xvD8<1q ̸ok @|H $6Ҁ5LC{?u- ommTGobܾ=<: h p#aA=V_^qsԵ}aSJ l\0ڵk=gÕ3Ej.l]vmZyuȭ"cXM1`Bk_4  6t1 ?6d C7q]CP[[}*2)5|]7?;#pϝ<$7X[kGkk xK@4̓&Zr:I_P@0}yo6i>06 .ۃ IB@o}g;v7gj߷^b_zQGP!&YJ~D輱{ޭ1~ռ%;Q!^hY ѵ{EtI+@1o`)"a]f\!—b=`)s\=JB(:'"8OU~o~~*JTMEϱgЙU`rߜ+L&cG4G\( TlIq[07'zl W3mO apj DH@ cC-wcv$;&#wɞ}=48Щl=k7ɬœ cQND"QUWEϏcE o\` db=@Vv 2@$-@"> _Lo|B91Im|k[/檫_ػ{xdo=v4 8{4=e23T*@J]~|R`"ACZ-!zu%33Ƌűf0 &%g"Q\T! t(ԛ?i7ؽz S"1Jr\Uu;Suq( exxq֖L&Ni+O+Л Hlgz;0_K` L`㰂&s,k<|$*U'8MWӃ>}@)$е%'5ږI(qrA4p pf4;ZY!((ϖxq{KR$=բK!` kZ3ct0)y# EY.8s&w>dmaXK;GBA"lFNZ6C,Nb1>)c u'< N 'gK.=Dt H׋ܽQ~{BLp$"pi^E!d^`,'Gh;{H׀8ta유S''S@_ p$D`5{i IH8-ZH8dWV%0"ah86) &@@\`y k;$Yj+YF㨌ĔGhe4b-I!B>_03LTj_7=Ug(/@Bi@J\+4zKmDhƀ:ZupXgfj1rP+Iw<.33њlg ? @{;ǁ|E5(]>2ΔfwVwP5Nu& N LȫZGKg%?_Xf''CwΛ *y !o=3+⚦^{Ւ?UU'X/1(UԄ͛/NݐhܛBGRsB2=0.=H@^UJK—BӮG_'0&Wh" #/!]8BSU7N놆onV5JlꀢtxK/5*Ef-SAGaDsĀ0]E2)ZJٹm[|ώF2ҒUc Z}QwuꄳcAgvjE40 O;jJ c%`!~(jS[RO6<.vA[q 1 #RBtq'?+=a,c_PVdhK!H&oj Dȗ9@w`\@S]]ꦍc;o7Odur\\xwI#awkвi"[ !vCA( j㮛pUԑL0G(lK é1Ͷj7 [(+o k)@=-}_ z͛^3 U`ryL#7q:s֭ʺmXKq@2ٌU iO)'!U8N! x v߽\ӫe?[,ynB\(̎S?)urYˋD&=6=O H3@ɶ8u8k#-XhA\Gƹq+ʏ"=sVuUi.@SeX ,j6%l$t=#֮{[roj9Ӊ$4)#t8?\TuA&wA2$3k1?Z]Fӈx)uEKSVEk+.$ $+I* ">/x5=~ xԱ)E嗽Ƚ_텯[#{[غz,ibMKͯ#>0;#h8p?Y֎l4s5 #gÇ)*I2QD~(|>ՀgAL;4 [<ēOfjGx<1r DZl;kDw;A^ g~3VmB^ ? E(2&"\2h5cl3b0 ]M ,t=+QoZ߯wwv2\T20EN,_Ào>a@G`#1!D[H+2nMympf.v&i(KW3H, U!<7^:D_l#vP:ڼȃ͕idTA/ᢓ],_۰d<ȐƐN9-<ǎ iXᒿ'.w_= ⾯5zLAoy"|Seyk.DB6hII*Gd2.qH>-s5BZb+`8 z .-j>R.֮Ruc\dZZZ H1RQ?3*]}] |۲!ETCyJ~C2V1MxXDSzH,7]#g &:c1@ƟNKM 40pD!@T&1X;;ëgϮJϷ1E$oU=E\ي% 6 ɀ:@m6"GSju'}wr8UL# \|ES߅8zGdRd,aBK*wnaj\P#I2I+H0"!?&L p=}^05װM][[5'a*D2КH4G>;H8/}I`|£jV|}0 ~x;Zz$ ,|?9Վ/i7 uL4f/{,4P CS-LK4a$ۮbHA(ٱe "ZHf5';GB A5T%v[]C:ڷO @祒@BKD)4k2/o~!tC\LwD ` 'j:f>|31:e@׀Ѽ\?P.v*5qVBOpZG;o8]pZ 4J 5WhF wHіoAYĩP VmBM>{#v$/N4׃1 H؋E^kUϷ*{,@)>vÚUY,8.83S3J )0wxWf>5f;_G;su>Z^iimkUCM^z7KMDϘ ơ d:bg zI~D88)WX(9:p(ZMz  ܷ9.> \ G+ncY๓>FQ?:ogT'{/9`z0U[" hEL2*[s;SeᗪKܾ@Dג#5ѕt:y)LL@:!7P 'BJQ AZl;{rRťaAZT".Y=tG_P<[B$ ‚m}o ?ѹE@B+;Q()Vɂ67?{srEnݫ޺ v-1/"U UT ;{ǏM@$,s9 KJA\~9bzۍlQ.׼hDܙ:,!;TpK5h]?zy4Ub@L`RBWlNNom\1oeSS1D֯&0 ɂ$&\d61b)~Hed);B #0M{);3A=ZʄiO W^rU4Pf57ra^puwϾBE^Q5U(.h8"B’R~4ʘrb6ђR&:RePIs@ ;#\7 6{@{v!]ardMOזa^~ @5DکICMlSsD€%~-n /%kv-"$t8|/.dBaW(`{x"yD`bh%O岯,_IQR>}|3p{7(|L n+%4%DUzf+1-]q 9sc;[l`@2?wtĠjԩ}T^c mTwD.IhK t -*::Z΀Z2wyabrϷT[DDž.[YDRpfA<5]5"\cLx: ;_=/ uY.l[Q|de>je[xoM.9߱i._5&:5x)BBk,zo1(a" RD|B*ND{_qUhdZz{Yd[a$K M۳ F1˻72]]iњh[g^l70%%d8sM# @!tUr.P(BQ'⼙44›˃2Y%ߺy, L2@T V=gMB~/}K:g Txn4֯J2ϗpš{tԭL"5*կeZZgV߸y_eЁXBCꅧgHD[Xq|RŃiPB|y$׃6m_a xWc.z3e"5-?c+҂ vVN< Eж~mm-TJ [?@,\)ԝ y N& 6M]gY.'`lvݵ}KCG,Mc @?~T@6qEg:TrBrAQR>)+~~ ΗΝmȻ:Je 5~*%`Pzb8=5WE1ZwL GKA+2Gp]RAlmc1gꫵwhBUBQ6){Q@_C0;qY|/S!S4P*p-VM R?1TD%[AW\u^K.9#|4 wmۦU-jWP` aU|ȗoC2O +Ų@J0RBelm|_PˍzAӢ-ic@ع3hbx~UPu¸ޑ 드xb2\8kn)!DpQ7`;Ri9 Lv. r^fu0&gT5{ظصؽ+ d~aBRh aͫ+ .WEu |;|h֦#(^)aT?[/cr!jϞ֭[c---j<>EY-8É]@3`G@LM7'U%jj:R@4;iPTU,ØlJLPaMΟ+xASgauh3T=%\P8yD )S `ࣰc_tIC&4:;B2+@Bd:]^9q:{ޮuۖ-mےmdrA >=ۅT*+*Uc'g{}@II ]"`jכ`%;>36LPi^4c݋ox#hϞ{lE\rkR'T= #-C<^ dĴu!V0^ tyɻp>} [ի\O3pދCHQ0M ~s2jeܷ0c/6L3#0!4, |}k٣Y( LP@w[_ՕJ$iVc-[ۀž޽pVT(7YfKpyo)Ji_=3e--3 ^@Qナ"˹5V\9=Ғf!sݺuD:ݣM=H##'NI_d@hD:.@DwB*^pW%fq8R$9wCC~(ր"0W ֟0>N8(t&y: ȫ.!'R@((+ @s#6c280ʤ2-ԩH$@hk֯>C޲UhJs# ƐQ${Qs d} s[sZ?>{suOp8 pn?q؎sZ?j!@B¡05V ?)&i֌eIAnAׁ':}+$`HQ`_}, #'V/๊hObY庀b%K`0U%<dI]& :TUa矟,>D@6kzygJ[Hf2^ϩGfΜhm.9:W!G#sPA"wˆ|ض]iz}e !P YU/@6szj'bbYW:|k~[Nh ,,R+*HƀlHVھɓYq@Gm.B@Xq_~d {>vwyTU ǮaӦf5.pZ *҆;ҧ=+ ~?چ 2IHU L$+B2 ڸW cȌvWp)d0k1٠=[YHʶ/}GlӇJ;G\$`5wܩJL˻\>r'CT>%BGrQ18˹) IDPgغ5hW8q\d# G' _C,FtE.:O]N''A<Łn෾+k䗗fIu >']|]e)H~Vi`OXX$- &+#ڸJMW^ŭ;%J?ꮝ(qZ֡$cILʲof?gZs/NDז'G8$  /%~j2e}a DrR\Hgk-QƇ##u*:5xOWVTy;"R=)xrGsonEx՚}lCGN 2ߢŸjr$ݷ/~4=<'e3:nm#v7c˞ T&C!m`"WTos>jH~”??ygTP}8ćVM|+E?j^l@V}\̸=Ԡ$+Y #u// JsY?ms& =˖珧|$S**ܦU5B 5z@6\{GApg㏞@XE('>7=hsbN:h1j@)'Oe\Lqny#oy9+8P 3 a.2.E.l;;O:/>ZH|1WZ% n/'8硿C ,`Uwy/BZ t VK`Z[_\s]0}-GM)C B=;$ \17sSVў&$3͢ IdK&NIns!9$1|J5aB[²@}ZH~&"_|Y5xׅg;#EuE+(>xW<5*8ʊJkROq3&/-/> z̻w `?Dߞo&7o%wo9W}Yz-Ȍ2x(2Ʋ@( ~g q&qkOUC(J]B0$gK0XX)reܜW)}o|?[ uȫZ~>@9?̽M(}p{  i).%tm!uع#ܵ#K=._ ]#<:#) -&:t'.aO~c|Q/O_0D7_X&?MҭɮVO=5s*p7U3.@X+w(o{pm/55`pN>4BtGa-L"pr(cCY S?0_{/S?{X ^0b!VTOK!PU?W8^,Ya8M|9@<<rލn40n/ bGJ$7.( ]e@BaO!5x}xt晏=˝ݔ&}+|$xHӔ/!9?'Bed,mJT@v(>9f=,ۃʥeG,]wM{g#Pt( j Ò* 2ƫ6#3Nx`BxK?^7t<%G_`HGȷͮmWEʭhMصpv5@L2{wAݿK/\Q`{f2~e9`!'HseO"t`S"{LHe x41E ̫V?¡/,<4"h|m Fz@ E"^^~Mrl[m -X_]PX6o?~͗nbX8f09 xag#\]##@딏3 Չg'ܦ%BԳW7C ܹz}xt}t)NL޸S` lnqP^Qq>C ( u ǀ"I B U0U}畉҉=?#NlIs뭦bhc!'5ZDvVzɎ wؗv|. 4l9>lj-Peǣθ*,8mBX e5@>;ǭ8f(TiΛ,I4KƺNDs>u,|}pGcm;5z6s=^[*qÍp7lF}jp7` Nњ (bq *TSuTկLvqtSs呇م3 –0G:ϷAhe, B%0GHLǟ|>M3$KsG"+0Uȧ**w?Vzaү͗}kfTYH gakz(̝BQGOW#_ >HxAP@%RJb'kۑJ4]fF\ؔp~"{c~ƥJȿ(;0r~2A},#L9A]c:{h8ߺ!Pҿ8o'wX$^ZK|!'@f 1nF#d1/΁"gёzfd;p!-Z/Z č?c=p%vWlV5+N䱹Tod% ," @hI^_MΆT\FR|$yJO4?mΑ ppxcmNp- ntEXrסSO"5~z'X^}QR^'yac#MVcz牚G'ɩQx.us>c#qNߜ @ԏw{Y|X APqC1mZZONb59l]ceGnE]be\ hkskrTy,HU< (ߺ tf+ SBȈO1"b<&ϊ.Rq'"e:Cӛ^_2p2 .@U"v?3O{ݗ\ۈ'Q*dtD̦m6jf<:gOD6t$ -3Lpچ&s--^bڒ떢ř_=Pljg18u[eV<PQI<6<PWO,ل @D-}X%IᆿV?WN=/JxTmK ~o?TWxIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/psiplus/logo_16.png000066400000000000000000000012261342663516400257530ustar00rootroot00000000000000PNG  IHDR(-SGPLTE  #: % (K3&2x&B a-R*q(o#6g!@g)>d.D` 6~/L'D83I;Qo49H_y+8AQ4CIf2]'IUg<`>WTvIoVtbQql|XztXmzvvwoꈫˉ։փ璪着銊aaww𱱐񻻲Ǩ˱kEtRNS 0+QBEY ="]t"!LmcggWPX$wi[ҮǶ\8 IDATx^MCmZ7 mɤ2[ӷCb| . ͛uG;m;2G J̑@MA "c|{s*a@:זxTk=^12!ls ̒,B pG1=$oUErx=γ?f k%>IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/psiplus/logo_32.png000066400000000000000000000033371342663516400257560ustar00rootroot00000000000000PNG  IHDR szzIDATxڽ Teƿ-`C@79(%byТRX%ZFdi\"Hr@4TNc┛mܞ^7Nvd*;9}>;> 0N21ImwU/F iw#)@tX.O!~H zzzJ6_*Vq+4ؑ _NuCN㦢M*:0mq{A h_ywhD-'3mN -1fЦ`D֧&ϭmD;;noH,P! ͠_ia\e-i1%Z`n>u`{u~̋m9Խ{hˉajڃQM5Ps4FvHԇJ` ? (햐 Ca2uBFEb[ZXPWg zZG}u}sﳟNԖ00N]F&Q՚"2%@LEo:w9QP^Y"2H:O]U1<֬YwW^M&=SFYOSQْyO+_)4Q1p/j, )2?F}4=.U[@C4㣤<)NE5JŷsrYDCr"&̅-L}D_mPd_1g2{;S6dQ]ͪfʞ[.04$wxc}yلl~gg A.,&lxDA!ND&ڄ,R,~3!6T Qx拿?ަ/>/|LɲАN #N`S#nڞ>ֽ<En=ooոq2̼1rAo]FʣQyL_k% %lYN |];[r 1-R6cL&!I뉡WZٓXz~)}7VQy'$'i~p9!x\ +(|Hх.!UNs7>*s+HmoLN34"_6 k!$`пQ'9:MMK_bh h ONXkhIAՏY"$ɑFI{?Kt!Mn`=?`uw`͵:8Βxxs oXhb!V{C۝It(\ߗ/}@0 ic\a)>hJG)%[GL3oZnDN+ukPNz31 #F`4 $:<ҥ>RxLQ[o6/]-|-lYKf+7j*]HeN5q}cB= G_ 6]HvXp)_M ߩN!⡷mje!e1k%x}CP2!.󟉈}~5q=̅c)+0a7C$0"rHw웋[drm*DthpNAEƷN{oLzزk.WIϊhH'wՁ_D^7 =A?L/GXX`3yLa^/m}ӄ$t hCop];(*X )F a.A8V ؒT3( H+Wʆhb4|#|^Q>ܼ\9R(.#i $$Y\:" &@W(z, "bLj-]9)vk退~tEm۶=E$Af&,Xs嗃F;B·{kWs[ xwH@n/Cq#0;fQ% Nlݾ}@Q`X: |e ƚ%޶{&Sͪ)# v&MMkIq}<'qV_vRWƔ)oDzYGq>S1ECQ]A+=vU|K4趛 FȀ. wo=xF̙dgqƛW=>H2bbfЧL1cwC4<y t7%Y:7B :t7GkwwRg,K ATE^2EM:wPW_z?+Zh (oX;inV14T6jI3$m5`R ߯^)SV` ƍ04EEG Ocz+?{NFU+-H۾8iyv:9v & ];|/45,3Y}xt7> 6ly)%) K`5r `nu^v5f3eQ@ JAػN,&/,rռrk7BPa 0[Sj6جH۴E6#I^E$V)& B4 `8a%yS@pMlS>Ǖg% fRa$IuMӖY0nJZ*d'WnaCAV!Ѭ` 9()$H_NCr+k[?NH3j~ںގG#ј 7 y)I0r$ "1F8tf~X3-z3z>sԌ<r2}':46ṉu^}D5(zR 䇒tcαZ硻ax*FoV wWV@xtłIUAQfՈo8 ~v_?~hkDH0X(ӧ,#j_Θ9UnoL&T#>VL&H4CVE.4^|D!ė+?C96`Ua 8oyu^,*N`_eMx!ooV0?;ۘd:4>'  !~frAZNsOX&٢@0 JaQO0tnu44va +,WxX1J% t`x2 Ķ4d[DG$Mk2O׬Խ/ NGaD*(J5AJL$%j2eCPKy #T9{OoY!F9૫ؼcW]E\ hnNHQsC9N!ڢ-p xDO&~9OѾ/>K+RT33tBa!Y r79  l2g%Bz"i8;N୭j@<9׬]kkwi"/3  csT>&GzIԪ~E-'͓8ow@ܘbE^{n&;!ϡ1;_@ϑ?Uxo8`1"ǯ=/نǚvõ 7AoB@"PHbo/Ð/9p&B'#~O:U8|lWsG^ 8t1.K._B|)SX,~-kp;RſÚ4w@Ia0뾸cIT@PGe,fң h>y > z2rB$'Ӳr a H=m]7ugoUDo&6aMLE v`Gm+ Ǣ/6,iͻ[ӄ#Cہ00Nկ%5(ճy$ɤ )] ĸ@H?vl}@#}lIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/psiplus/logo_64.png000066400000000000000000000136531342663516400257650ustar00rootroot00000000000000PNG  IHDR@@iqrIDATx^ p\绷7$˖m,yxce $,v'!L3mB!/dBG0@ Wl˻dZnv<(*$/WuJWRLltO (r/n +__|;T+ⷯθdT~[ɟB, {{Y' 92*Ϟ~ w܀ )0o^" 9'?[VO4_j>r:Fj+xWr]_JًJj׽P;e@3p9p4<{vW}86p»PSۿJ=_7;֯=:ܢ/@hscVc;Lţ_ʹU{Ϊ7Z,]]grY\${8qKdK9< `_Yohi:UVt>gL?=\GC *.w#Vنsnnn){⮢n^M{ :kRgw6=Q=^;Xb/v$}Tm/!w*);ap_a mѺ= 1kιCOn?gkǦ:Ų/3s j %~E тcP_ {QSPƒ/NOwLߧO8ͧmQ0s:^p$1"޵/84ΟYѥ@]aXU$X$z /%K%mϩw(DsԯKǮ3ڮZ<6;"8CtB tq`ﯞi:&ĚX7D\4Tq<`İm8jADkNZ0C}e7-^q#x}~lD>T^+Pԍf\ D]6ⴈR P:(#U.iepCE)k5e3W]aez8yPr* 6 _Ws"/(`Y=+Wk}=Ӊ. Lgs6ۻ_"Fu`s/Pv1sp_; UHc].a;SG:27}z׽_PϳVF2up574P(v9{- II-?ļ  E;n@zk>͋4L{OMӼ%]=>݋TTd ٭[ΞVq tm@'h R $ Lr)T4lXQJ}Ζ5Q""qU6~;߂Jbr2w o"מħ[*d\BߘTX[[|Mc+NRϛ'#ìJ44992kڤ@; '0,fQr汊*0!b;ӻ:*~<1g%ɖ} Ię\ (xA,aAN2o? 'PZZ+kVش}+OG Ts|>%J5`d,u t1Qm 8YD/=ˑ2} a;L*7- d}pG/:`mc?9/ Lvt]uE8ѱDkj*}}? "()ȜqZ pBWY0)]3>/ˑj}qy(B PP%[o4˲3oX$SVPQ 2i"7݄ㄮ2`!%wpPFI+"9Pp_gKߓIEg#ካ2mJP9g~E^ڈ=A|ss|[]N9gA+WGEEP~&?(ghXcCr@%.Q_A~}R3%M p"vZ{R JH|ܢ',sݳN;O`O O#0FFGH!Oq"`|@sOw:-Bp}%dX7߱gͯ@<\~W)(qSyaԋƪL&Ž"ޫ GP@ Yq<?3FBwZ[}~}DJ",(E8" fx>x"4zLF{9_r tw>3O/x*×^Oeϓ}ݫuň-; O FPԝ+%QhQ|U%b-9UYo[H,BxEsn78|o!ǻcɱ1@B>0d?ѽs oqr-S:: ; 0Ufĩ"GȂGÖWadD4RG@@ 6+VoU84>UgdcP;/_Zat0yR]# @oMܰPU0(J+h/}A &d`u6lތ,\b0{6[[Ç~OST>&~3 &V,%]E%2¡a#q֠k9I3 sECQ 狺GTAQb1?/# _ _Z/?qt }` 8^y5Me$h]`) YpkFPYzʬʂXL䩉Dm%l CY A  @D]WP ,pimFz oiؼ5@/hΊ?X;9~U#ASJ\7)1G]IYhKAj<̊bL%%@yXnմlm98`opo^FLDmP X %2 7?h>a*ѹ.h 3%ׇw0D#Buh6-CEuAA XwjZ{;>-G{=2_t Xp53O# ¡a[PGG]Ԗ=@F 6PnxͼFf;`nP:ad<5G݌:OgK-m[}^5d\)!7q; SIUg m`CE21v/>p53(Vsl'9tNd 8xپd,Ͻ @O=2SCO[=05LzdK0AL[yhT,Cា9txvccK6َʮ8FE#lzyAh QCIkkCQU71˖g>^9 GG-FxJ&k8PH@E;b eAMRڴ__l)+LyX;1*cLexH\}!h=>)c_k+r4ihO٤ _B+ݼ5؄0#CD+B15Dч a B6DBP_?}zժKu߾M,Z}9zTL: !fXQic1UV]ccY G_ df ]EV 2D¤*&sx8@(HX5UBҿ#ã־5k~4TzzN {L?tQ qxqvdŅ//+,((˗ +NWIöNah#1ptv6RA f@o9 P(>=v߬^tpg)T~3"qPi^94u)wY84|ZsW\#CWi4 |_V+aR| I 7]Âm ²!_}ʲiuu+w3JZ4EMf-1k+Cw3`XBO5HwQ;l3$lAR_)*{z3  ހ%K Q6Ns2۶ԃ+EJ %DogkMz|ҾC'|y,\7%"ۺ a#sѧ^t%(0T?X B,842sCce߾%-J&t@Bl ;;z⬞y^#}Yؖ (MB&˫mA 0nC#p܊*ƠgR.gi{+B 0deSm=?:5W"qmC`B"&4֡_{Hj J'(m1HRcNXJ cn0C.?E#ߗ@piϖ#n ,ZSin $ QT!l`tW/y}`ё] @.ȌVw6>dmrjtg/ j 8uC%t1O:1Ht + 4Oɏ*w9 ڀuƞ>߶q䙧d2 mex= 5p+kUt;mSʼC]6ϓZތ%= =~'~ސ/ j,`hxqdbu-իJ:tE)cl0^?˽w4+㽛} o} V>$- Y%@l, q. ~(|UsL)9Cַ1e)3g/yjݖ#V{1ӛ D:,oo `LK_z4~ҍF1D@wGݤ2 V^FoҡsLRA#B] NH+;3N<:mЮ @P? 1?z_ȿgi\^enP \ȺuweɻP !aHхmn+9uK_|'ȼ_ :h|ɽw6ώ* ^Kg?HOrP t Ҟ9:GZ;R޽?ǯv@\0:KO{2@0P^ms-'EN+5tm>L)\Fd647z/JO~`PqJe @H(˄q,|Kuqn+n,0 $ c7zr -JH(T2硃,"\'VBr cQ/Ü730Y6BDcD@qPZzy`3 ֝s.^%錋eDў/t-w>IJ./چ Š= ^|-: tx9Gp5sd㯁y$@<௢?{/@p?A0A%X9`|_ "FPFp1IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/psiplus/palette.png000066400000000000000000000015271342663516400261470ustar00rootroot00000000000000PNG  IHDRatEXtSoftwareAdobe ImageReadyqe<IDATxڤS[KTQ]23^s&EK2MĴQdRED=D)^ TDTFZc%=c96rۓ=mXgm>^߷9]?C|jO>1A$ZEb!B?|Ey8MĿڨˌ;ܖh4=jJH$b1L??E 9x25qt5\,/G{56+ݩyy.ePWZP\;A G-5VW5eYFΣ4 Qۄ`IjGO ֕YP0T7Jl`vFVcP`AaQlcyyiqXQ "Kk]0W9wlq}\\fn ]&-'yIqs+ YVԙ(c&o-L&|7C,JJDh?Wq+7/dךٮZa0?.?cUyg;%e{*%K[N؛+x(#Bt~UWy,7XMG#Vm[}JtLlcpfp8Nxu{;fd-.2S ,V8c]C]Oȯ[LtP@DžNǪ >9)Ț2;scCߤ^9F=pMǁI=BgTre Аl6RhMtJ?>2<oO}CY<*`G"(XM #5IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/psiplus/pin.png000066400000000000000000000011411342663516400252670ustar00rootroot00000000000000PNG  IHDRgsBIT|d pHYsvvN{&tEXtSoftwarewww.inkscape.org<IDAT8ݒkQ3!iPA֠HC\d7ҭF](*b.ܹhIY€* A Zk[d:LJbKѝx}{xǛt}b|He0\Mwm@Ly?Os~`n=>7W?xl0"@,gܿv{[ovȉ?%#5]€*-rHXpLFXLFJ)rܭVV w$?;Z7}cf^@28)6c-SaJ"],f՚z|W}lj! u)edf"Xm[N?|\xT*M%IvaT#cͲaYTu]*IZKl֏I)}0VO}]lT*ݺ )r7H?/AI2ԩtIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/psiplus/print.png000066400000000000000000000012761342663516400256460ustar00rootroot00000000000000PNG  IHDRabKGDsIDATx}OHTQ7 KMdV)."sa2iU m Z$-6Q Z K*0ZLtY=uyoAcs~ߏ#j:~u\:tF Ab( ~,W7\/FGr (<~/O?n}yW˨4UЊ/?,fj>- к} wږJ[-7f6 HmD:;3tC@>OQƀ҂V\HSB#ăM|>?D,TRcP(yŁ!:򴧧'9pP @#"@K"rɥ7pf @hkH?sss,>m۶eEQYlqa۶!ຮXaR(pX[[kd2JP(`A<J0 }! Cr_*~jT*8jr*.:;; |ߏWVVT6qv6"eaa8<ϻ?11a<@E C(\^Is %WiTXtXML:com.adobe.xmp {csBITO IDATxyx\u'zNzG7$;EQ,J,[eё,R/df8yq3LLg,Nb;/ʋd˲]%J(Q w;5nw3X  P֭{Y p5D$  ke>/\O"jp%"K]l|Sj2%T_ckWi`3{U@]4U~=췟O/״qk+•B#[ cc|m\]6s#PR0ZBKdTt}ik*RW&B" H $ #`rD]xǾzAsfCʴ N9ACAU'&& !9TaQAD 804 F0FC٭w }ld;} q`J 80DbGБ{Hq 0D ,+0=@2 W@DDŽMq&HD/iï TK__-2 `#W0Db0Mw<1&P(ڸ6 f4+_*a!iVqb''xH 1%"N(L]Vg;\>m. ѥ}= v "9DBb!}m\L{ʲ, Ʈ4&"์CA<1*`<0L n%v__atdht}{,qZh~Ǭ82neiPp 88ouJJƒUWEUcnh{o־/z?5ڔ]?R%2M+lI2+b\Ya$ ADHxuu Y_~1N:@+HTý73#LVs r/޺Я|~b7 kw=| @$i1x%^SU1,Lx @':5 epp]ttW0i679Gڗ:0??#?Pr[{_5?`W5K*@` !.OD1@Ki͆xם>4t^] c̸ ?(R.Kѽn~ 6d_)?+Qϫ|F9Sŏ +2i#Dy>rx0\a 5 G ~0J;ǚѮI8;f$d1|[{f-Py oXSMuD{{[*UEHh5MF3L宫(iz`@ YW) X̨[ƅO\n߾}jjjG[@K/}(>E~~[[z `p[ՋO& 3/HCv!\ӴiwB>]ٍLrF+ѪL֬Eyހ* !L&Ѩrrǯ/M!5xQ ("'*GM]yygy?qgee+j7 x4 D1WGF0^ 5N$ Yv㨦X8ync:v'By3F:j+B00 Z.L V9ɷ~{޽mtZ2i/G!#xg_s+qk^}>[azuy8r6)S/~򧞍/U_͝ W/ k{ͱhM2ޜv% H3BtrDg$P!KnۉtIlgmuu,:W y zŗ VXK?E'O=XVV&2'M9뺶mK_y~Ƙ.ysN rJ/,%LҬϭ6)j wyGB*y"rǎR޾'w4JylL>\4 k[dgwpVuDvxjb`Ek wLւPEu18 8\HD_wsGݱ=yB0r) Um\<8p@!c,o[ĬZz횤сC,@_{x2=T :p|Є @p3޿< $Tzýƕ *i`Ÿ|{Ou?T, N@đ ^SllJ,1 `!+_~]iFX\o-9 Y=_-M^L9G;3Q dS\NߔOrRYIPDРm]]]UUUL?$z󚛛F3EUeח744Iw^ҥukCPĪ0wP~mV(]o8.--+m Ozʄ3+dPi0QHrZ"ZjU,+ jN_vرή)Õ%%%urUڂm?X0B7ߙ ݱXW bI𸰁j6D _I^}|?\׀B1:^x@lN(8 HO0=?pv޸20 4Amc-k}r,˒0 ɺ_:w12f8զjcǙ D}7 uKD'7\nBW_٥bu*P>yR@8{!\L:}=۶=.}]ɍJg*Mr,cW_zۇ+++ ղ8pırp8DZ{..˵{GDnjel_y!0ZZ#psq]w:Ïfj8JZ > O|L몘QokN.ZߨD@@:@##pǚw4TUrCͿMC"À/2> k"?ЩeѰa3 ob$b4[]u w:cU@ 9y{|˙(@QV&" a`%2/DFBD۶9Lڎb #1И<$<$Ǖxs|ޮt:9WeH["'rzUVK:22dǧog;x|: LՖ+Y>p=F9 k`N08p!@pr!jHId|4{u>5-CD0a  f7⮨>:q2D_](M ;; eGǦGitˣ .)CዻX"m[".2rBް'/M߶ 5Ԁť \pnls' #fɉ : @!#@,NuP톖m Gźu8|M4ӞOP0 c&G:Bq|Ÿc$8>q\.\Vkʕ+ԩShDĘL&-,y.IVl|>pq,gڲ8c 8pYX^^d"zNU>qvc:ٱc+<;''HLF qA/70IBue=z2|k?L(~u `0B*QEX+q#0ViS5vD.q'4@, CPX=.+cWZJ͌n3ƚ:OۮWH:#X,& ^qj1V__/I_49P=iI6GzCP0G9rE=u(&tw5MS9X8DICXؘr C]7n(efPtUqϟ`jM__>8g[ZZ֮]Ru*4,4̟ (8s8Xef3UK4-IA:x 1‰QN6@L;6Ɵz[I F' fo{&*Q"B$Lv|:!gc[DҕK c;r{-ʕ+YdҏDBZҰ*/ Μk)ȇ1&x_&t ;bdd7 |" شi~6.6Մy_ S+ޓyL0NUUUUd4@@B<0?Rp|€j0l\ϟxw5+Uwߘ,` C&tCak 8'ϥc9/o!%ד-q2_G " 0dXU$lnA=](^}GO=s0WW $|ă^?6n pMMMOO"SN1p</)))--M$es#/0Hf,v,vׯ_/-ҳ |eӡ=z##Ɖsz@?5kּ+A"u=HRh4ZZZFμm'e`3W kE***?Xsf۶%ͩP MLLLNNL%KZ*@Ϲιw&s! U"dEEEmmm(fVVC3 <36XVVv7ٳG:388844$L&/_^__?ͺ@yo*<fʚ*=ĝ|o&$tz"E50nTy\2y67[[ BǙferƔC<B#˽!q"/>Oyk^zܽ"jj!:[,hkƍ$D"JqQؒUmݍ7(.:uɓ'?ɄZm-gWg-H,]Ta2%3H\W(n4[^P=?)>vў Wz{%iڦ+` e4p(:~刞MD"cNqHBc(7P8%3yN:8C=؝ק0 ]aB&ZxۇVnSSaNng$۸|3%[j&˃,JU)U*)f2\.f'''7|3J%Ie<L. .HjZUr&QAps3fP\Wfo, _nݪUFGGf@vɓ'M\~T_1o L@ !1BĈ q-i8Mhq"iC`uO3¦iF, ;KW-p+:;\_x\&GH ]d]FB дað`ٝx KHaXсZ0|&;]].i`;yf&h W\w>@bJT!Ƙ0.s)NGP1v!')]EQ`ww15 IDAT'NX~T*}EE FbtF"wp$tu C]T&&LiB!5c즕SjMP(1) 9A~ e૿'vz|8T8Э OhJC\e\@N^t$)+ŏpu麮iijf2$J)--ݴiSYYulV;Y]j۳RU=i$0 0t] ]15 LxlBQBϖXן&oXb[m׳<86J&HMbQ3UOab3uj3x?k#p, %˛oj-MCfxUw$+|5N@$s<׃˼-^{}: E4 ˄O;ʰTKWX+ oMMАLFy0SG.͋w@GgV"g:׸;TKZb«8sBGb@-TTT \i8V@T͟*G# A>_9^!sy0[p,J//f7O^G$iF?S5,DD:r(^n#>o/85>Ή$ -ZR:ɵ29CW2IFU I =|b庮K]d(wH$~H fR<|@RO gʆuIIcccVY*>|HOOY&inn޲eK8Nni˝z$0\Wsh`T qDi=QuU04N&E*K`9o4dIeY: 똅1go:5:hHF,MMy c(+jKkXt`+O4Ρ4]'yǢn-LN- dK▆4dU=ݻ֭[d? QYȅBARϓɤbٰLʂ[F3R3v,leee~SСC֭@ hj2qA6cDJ~755-[LдSNMNNE"˲dL,rks hʺp(Z'G0 ơtr钦Ғ7EO;}05 dhoZ eC)J @?B;Xvmggm^?xYYT.Smqf8577[:ɤҺȤ&y'Z ?ewZHp]0 -PT9`W/Mǽ/'!lKȟ%%Fvv-K+tJo&=S+Otxo' Cģt=D8 閩s_Mv V"t$+Ydа~܋[lD"rqzub:O3m:̦\- 7[oN>,ILݰ,qaCKQ4q IIp| {e ~O!˳I)n_7Zܟ?(|%pJ{@HVfds,|99rdgGtgpO\4_8Qh1E[V-B {~z9\}/N wOL,mp{s4TdqG7\(Q!VȲ,4u.?S]'-m7wF.E%@(32sm8GC̱?F6[EjJlJ} i?:Ÿ=&Xg% ʽtĤ{eZiíi(ߴZZ9G7lʽ@(9ؙ]O%X q{Lnz8o>|OH"4MV83 B^ϥH'`H˄9N3s[g$9KwXzF WV9pY`_[+[\j﷨ϻKIWy9US7 ",2+**֮'Nɛ{ݭIO~r2͞{iybOqo ѱ.6y<^8qKF(/.-MGc1`aX0݈XyF^bAS$&E9!}/}`8Ë- <ʶp4y,`uumKROì-^2a 摑C*ulI#ȐBDk̰qLM|hۭ\֧:HjVV,AC=HH14A2S6*H$MFq ]NwQ5 0ӅF?V3γl܊wnE曓7tӶmێ=ۛd***:::>zCCC=]݄;B;c}}}[Ν;?vQZZ*{ϽKcohhxoL&ӸdΟaw6Msjjj۶m tzŊp~}}3LccΝ;Ir;vlppp۶m044k׮{q]vfdD`w%i^d2oƑ#GBիof0Z[[;̐{p8ɻv o馛n"Њm/CxVҔp(*KD0? |Cc ]0dq|P4X$*2#{8BK\D"`&[~c(}oիV:rH}}}'xԩOo߿ /t/| D$O/YCDܸ:p8N%ֶhѢzkmm]nSO=sΆFP(Kfɓ'+++ w 2E8ݯ>v|Gg? 2#Pt\O>hѢ__3M}߲mGݻwm۲lKK˧?s-ܲo߾G}_&%fG\6}[|/۟4nۆ3w&@y9$<*̃k+_[h<&Mfk>.prR)u~lF%_j' eᾄU#STuu5lذg5M3=z4Now}=zSZɓ'׭[.]*;vle>|زT*%gfg*{Z[[<ѲeLӔk;~uM|T "944tXYYY4_vYxq&cin۶-"AHTTTƍR3 <Ȗ-[SV\9c!OO3Ny^{5"jnn8055lٲ~#bsssgg}gYֶm}M6#c=vuMMM;=C`pz_وѣG?#~||TW[qq]7<gJgʒe&b$fh߅`ERK)>R.۶-unooo:9---ta";[~[[nrC6[80pw.><8c}~ѿs)`檕MiHભMQWW'uАeYhԟ%׾m1 cպpF3t vt%Km}ttԲ&i ; %KJKKc'O B Ʊ1۶~. jW&\%%fZT]e-CϢCvIDATH@c` M0ɸs\;, .Ԃ maΔ^=MMMxoF ^Hӳ` !HRIprm^U>` @b[U@ii{]o9_"5cἿy~\ |Z)sp T]~Y}_?/lVut2%Ę"W"OR{C:qE+O{\dxq+kch ?3rd.D` 6~/L'D83I;Qo49H_y+8AQ4CIf2]'IUg<`>WTvIoVtbQql|XztXmzvvwoꈫˉ։փ璪着銊aaww𱱐񻻲Ǩ˱kEtRNS 0+QBEY ="]t"!LmcggWPX$wi[ҮǶ\8 IDATx^MCmZ7 mɤ2[ӷCb| . ͛uG;m;2G J̑@MA "c|{s*a@:זxTk=^12!ls ̒,B pG1=$oUErx=γ?f k%>IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/psiplus/psiplus_logo.png000066400000000000000000000073661342663516400272370ustar00rootroot00000000000000PNG  IHDR00WIDATx^yT9ֳO=3Ɩq@Eр nQ\0JDx/E$1wdhe .63tO9 9]*+.cɽ?NuU<~~j]Ynѿ:\ǟ$(DZ,q?qֱ'fT/}o]w+Τ.ok70)Ԅےnz|_%wmMxq$)%{#R7$DuS̻s$!drRކ 3GOKN@SLˡ W*9%i%<>wxc}yلl~gg A.,&lxDA!ND&ڄ,R,~3!6T Qx拿?ަ/>/|LɲАN #N`S#nڞ>ֽ<En=ooոq2̼1rAo]FʣQyL_k% %lYN |];[r 1-R6cL&!I뉡WZٓXz~)}7VQy'$'i~p9!x\ +(|Hх.!UNs7>*s+HmoLN34"_6 k!$`пQ'9:MMK_bh h ONXkhIAՏY"$ɑFI{?Kt!Mn`=?`uw`͵:8Βxxs oXhb!V{C۝It(\ߗ/}@0 ic\a)>hJG)%[GL3oZnDN+ukPNz31 #F`4 $:<ҥ>RxLQ[o6/]-|-lYKf+7j*]HeN5q}cB= G_ 6]HvXp)_M ߩN!⡷mje!e1k%x}CP2!.󟉈}~5q=̅c)+0a7C$0"rHw웋[drm*DthpNAEƷN{oLzزk.WIϊhH'wՁ_D^7 =A?L/GXX`3yLa^/m}ӄ$t hCop];(*X )F a.A8V ؒT3( H+Wʆhb4|#|^Q>ܼ\9R(.#i $$Y\:" &@W(z, "bLj-]9)vk退~tEm۶=E$Af&,Xs嗃F;B·{kWs[ xwH@n/Cq#0;fQ% Nlݾ}@Q`X: |e ƚ%޶{&Sͪ)# v&MMkIq}<'qV_vRWƔ)oDzYGq>S1ECQ]A+=vU|K4趛 FȀ. wo=xF̙dgqƛW=>H2bbfЧL1cwC4<y t7%Y:7B :t7GkwwRg,K ATE^2EM:wPW_z?+Zh (oX;inV14T6jI3$m5`R ߯^)SV` ƍ04EEG Ocz+?{NFU+-H۾8iyv:9v & ];|/45,3Y}xt7> 6ly)%) K`5r `nu^v5f3eQ@ JAػN,&/,rռrk7BPa 0[Sj6جH۴E6#I^E$V)& B4 `8a%yS@pMlS>Ǖg% fRa$IuMӖY0nJZ*d'WnaCAV!Ѭ` 9()$H_NCr+k[?NH3j~ںގG#ј 7 y)I0r$ "1F8tf~X3-z3z>sԌ<r2}':46ṉu^}D5(zR 䇒tcαZ硻ax*FoV wWV@xtłIUAQfՈo8 ~v_?~hkDH0X(ӧ,#j_Θ9UnoL&T#>VL&H4CVE.4^|D!ė+?C96`Ua 8oyu^,*N`_eMx!ooV0?;ۘd:4>'  !~frAZNsOX&٢@0 JaQO0tnu44va +,WxX1J% t`x2 Ķ4d[DG$Mk2O׬Խ/ NGaD*(J5AJL$%j2eCPKy #T9{OoY!F9૫ؼcW]E\ hnNHQsC9N!ڢ-p xDO&~9OѾ/>K+RT33tBa!Y r79  l2g%Bz"i8;N୭j@<9׬]kkwi"/3  csT>&GzIԪ~E-'͓8ow@ܘbE^{n&;!ϡ1;_@ϑ?Uxo8`1"ǯ=/نǚvõ 7AoB@"PHbo/Ð/9p&B'#~O:U8|lWsG^ 8t1.K._B|)SX,~-kp;RſÚ4w@Ia0뾸cIT@PGe,fң h>y > z2rB$'Ӳr a H=m]7ugoUDo&6aMLE v`Gm+ Ǣ/6,iͻ[ӄ#Cہ00Nկ%5(ճy$ɤ )] ĸ@H?vl}@#}lIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/psiplus/tb_ssl_no.png000066400000000000000000000007141342663516400264700ustar00rootroot00000000000000PNG  IHDR7bKGD̿ pHYs  tIME " ]IDATxڅ?/Ca[WThJ$|b X ߁YB b40$? A,*{{=[ML3<99`EmX>^ J~;L2tڿnSkNOD&ϖo ^o IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/psiplus/tb_ssl_yes.png000066400000000000000000000013551342663516400266560ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  tIME :)6zIDATxڭkQƿɤi0Lj[\ѸrНWE7 u bB( R}$՘L'$$wf=.4Z9w=?;̳H$O&NRd"`bRkoLa,.RW1$Rlvp\ǑRcʓz|(bz$odқ[nGGeTU)o;splq-N&t.|aUd,C-ue҉dd2]P_X5W4GzP(ҏaOfZ+6*n~iU?|Ʒ̨ vZyveI2̨{5j5r-g}q6&OcQ~\mf@B 6 h!Q.09FG_u!pX *(#{C(p5hZ` ݦ-B6CIK \B@씜y2jTΠ\e=Ց hٞH . p.aS p` oC#%`` $y@C+Ag`ۻ,|n@[G@)3՝^MӇP:IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/psiplus/undo.png000066400000000000000000000011411342663516400254460ustar00rootroot00000000000000PNG  IHDRasRGBbKGD pHYs  ~tIME E|IDAT8ՒkSa6&1F(XtWi@ AQDp m vjq1|u(hEg<w<kɺ¢$e u#t({7 I}2Q> 1a{xH)3qN [,B5+=Vݚ~&P׿F^pٱ5GPkz]])KrcQSd܋29D_ݷM;}z{vc./z4ׁ 1 5d¶F6L<qF@dtb0ߟ8]Оy׾Q&besbjS^}]U&c~UV/޳LIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/publish_tune.png000066400000000000000000000017341342663516400255130ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<nIDATxbd@ 00I2p32cc`ɰ!) L . 0e?1 ׀]dbbFr†@l@@ v _3t! ( R @gb*4a&C0 ,VN^?/0b8`ϰ P cY!? *2 h%"^S_1Ϡ @prr>~mY30r3A!0`MĪP"=fb ?LLL @>رrOpe'$3^!s10Jӟ?A~=Ǐ&O|(!O,yk\s"Ϡ`llϟ?>|``WAK] ^10п@N*f8u   @yUVfX}x"Q@-fv533#Ó'O>} Hϟ32*KJJ?$&}C21Lax~ uuu>}$ommpm_~ ''2QFFVxU 90\s6FĬ%/7P0ܻwǏ 3hkkcd0\}a)D_z@sdd߿ cprrbpXkh=t_ @1|6]NNm630ܸqw8J"KLS^0]xu_kkS5P+ @ YMIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/quit.png000066400000000000000000000014371342663516400237740ustar00rootroot00000000000000PNG  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~IDATx^ ӂ#N=3m۶m۶>ySo~R)rT"ް:W >w7/C{}HMftK 32v^H""ՈڀX=o®)|Ih:I3/kzSA8p);&s\^,fT u\oc?"urU& @irytN8_!A` ʳۏ1shh.*KTELcFe T%At|8xdd/WU=II}0Bhmwrkk+ᐐd\B(GM0Ӆ_[A2`6x(w:}e׈[[@X'+ܧ\?ܼIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/roster_icon.png000066400000000000000000000012071342663516400253330ustar00rootroot00000000000000PNG  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;IDATx^=c@{m13ڶmm?.{I>V6ȬN6 PnQ1i0WF\RMtPQ ;A/MU#t pz({к .KD"# m,7N[Ӵ /W$}af#0|!(IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/search.png000066400000000000000000000017741342663516400242630ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT] L4aǹ9![0LNV X bO'tOha4: rFIKEl<|@wpQ[PAj-Zܶ;'wh C5 6k^oz+޷QkD wlQ&SCkɧ䴁X8D1 /]y=:+h/vA^^d1e>>QHoZq+4fan]ZY 6*wt\]wA* l UϱyAga3;V٪^7?XwiWI&8V `6r~QTou\fʤyV8ub1hH?궃|O id_Hc#Ջ_lqy[+6O8>0†(}'? AWWqѪ.`=7O8{[lCaa4)#77<:9m?aWq̗ネ+`ih˫}Q;%勀y655EK~:t:3x%GBdf E6#"7&svv~$!Th I  ELpp/Z''H{I$$ ^(1(d'J%L(?IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/self.png000066400000000000000000000014001342663516400237310ustar00rootroot00000000000000PNG  IHDRagAMA7IDATx}Kq?SwpfnPW5,:be..O.""0(C(ҁ,ZiI95}ܻ_Wz}Xr,SenϪμx۩JKGݽ_#B,G <w\/KކN"$$HclƋ^-ag; 2 #ֈMưYϾ˽=hJPlf֙"4mYWXʖ@޺:08N{ ~}hݺ!3XpKta4@C@P@&9u>WN:^ҳ,d4tzxJh_g?={ES!_ 1RG\c[b{Sj_%yLErWhQ(+[|p|4*DxB)!"Q!_xkF^Ϝzeoof&6UYl>oُ1wDR]iϾ]Z2YH `|dT 29vX:`OBX@SJ uz>Gzh|; J7حA& 1;&جPj*LNLΪekLk9MM5mDw·o]LƾHKpfϿ媨())*Q% B?D#ooq<%]IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/send.png000066400000000000000000000012601342663516400237350ustar00rootroot00000000000000PNG  IHDRa pHYs  gAMA|Q cHRMz%u0`:o_F&IDATxb?% (IJrB 15A4f@;;@ 1283 wő~@,w^aЗabfI#2 oc8u@zaKQ#5QKU6VF¿PDu?00|3\ ĸ ǯ`JGA_7H@.ưsU_ Ǯb mlll r ?}r' ؀00g>a_`0 (4Ad?f6u? " 30VFvAX. _@_ l@? L(~"icfg`dab,@o@ rY ̆b FJ3@Qb k>q^IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/shortcuts.png000066400000000000000000000005761342663516400250530ustar00rootroot00000000000000PNG  IHDR7gAMA75IDAT*DaqHlfcel=\[uJ$e5; S!Ɋߏ*ml^ŧq-o㓇9~Ն>i]^]\Ѥgi h58(-&E0(0S$h1 Fl$J@qt/qœc9vR&\%#Ero X:D%EuqLɤ q N2GxN՞Q@B@Jn,̯ V"X7#a`p|F>nU"IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/show_away.png000066400000000000000000000016401342663516400250070ustar00rootroot00000000000000PNG  IHDRagAMA|Q cHRMz%u0`:o_FbKGD pHYs  IDATxڍ[hSwsIIIjh+[TV+&D}7acna8|pLXhKzMc19g}O9U1_1*5mteTwNm{FPnž-X~,GЖeDġ}N-7}qx[[zJ| aI/M&i*lT&m_|yk4Ɏ,͛d->W>=0Qڻ$:G򢴯'IѰ06EK`29[d^P 0_\[ ͳUflmiol,ǦQ0#"H1 Dɥ:c+VGC0un03`eA&3y3u%佡dߺ+PRs;<9hgqp@\E*nQzh2 TE_.*0H-mXͯs] =.'4\! M40$EBٹu`#l+VPe׉;6(E5`vSSKs2fytx" ai ompbrRVN]rqn3٧!ǿz1-ɸQ7cOLA` <#r  ߀Ћ\'E1 xSIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/show_hidden.png000066400000000000000000000017521342663516400253050ustar00rootroot00000000000000PNG  IHDRa pHYs  gAMA|Q cHRMz%u0`:o_F`IDATxb?:d`) ߑ`nh436Ā k%zόc5wgtdabddP@\RJ Ϯ0H늱hHp1Y hPdbb`RTs63g)'3˅Wf{MX߾z{^A4 6%?  ,sA@=2l;~k!4 !1 ͷ1(G99~10|'D#.`t';w`x@1{ 1yAV@E߁1 x˿ '3xa͏ ysO?>?b{LN\0{!47;w/1> {7>d|@h6~|򒁁 fml@/p020p10ps20qa?k*f@,6b&#🕉@7#?N_ <_YXhO'$gyf&9. Ml \$,  \ L" d9@ , (;ß?ozu٤W.| e` <ˈ_9s/W};~nlan @f02CAE@9g<  @1 L`>@IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/show_offline.png000066400000000000000000000010441342663516400254660ustar00rootroot00000000000000PNG  IHDR7gAMA|Q cHRMz%u0`:o_F pHYs  bKGD̿IDATMK73;ZPPY:AE :yݺv*ةE*( $HZ\tݴmfw" OWVg&/Ww6n<|DJ@ĝғ%Ԏ:xc,=ܙZDkWkO߮-JHdz2T#{1j'חJ%&bɤRLcˣz 7kLWW'GjWD~=\# E_߬ڲ_0 G Vv2[UDBPQA٢<$KET~A@y4,L~'knחfǣߏ SZ$ʪ~i d+ËYIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/smile.png000066400000000000000000000014061342663516400241170ustar00rootroot00000000000000PNG  IHDR(-SgAMA|QPLTEߘ ߘߘ  3;V l a`|~H:e`aGO@8` 0PrCEPg - $7  "$A^ J~;L2tڿnSkNOD&ϖo ^o IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/ssl_yes.png000066400000000000000000000013551342663516400244720ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  tIME :)6zIDATxڭkQƿɤi0Lj[\ѸrНWE7 u bB( R}$՘L'$$wf=.4Z9w=?;̳H$O&NRd"`bRkoLa,.RW1$Rlvp\ǑRcʓz|(bz$odқ[nGGeTU)o;splq-N&t.|aUd,C-ue҉dd2]P_X5W4GzP(ҏaOfZ+6*n~iU?|Ʒ̨ vZyveI2̨{5j5r-g}q6&OcQ~\mf@B 6 h!Q.09FG_u!pX *(#{C(p5hZ` ݦ-B6CIK \B@씜y2jTΠ\e=Ց hٞH . p.aS p` oC#%`` $y@C+Ag`ۻ,|n@[G@)3՝^MӇP:IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/start-chat.png000066400000000000000000000007311342663516400250600ustar00rootroot00000000000000PNG  IHDR(-SgAMA7PLTEErErEr,Hr,Hr,HrErErBnErErErErEr2SEr4VErEqErErErBmErEr;bErfDqCoBnDqCndĵR|Lw|͂ЪޮIuhŹErGtWӡrX"tRNS !*/6KTW`kl}IDATx^0"E GCq: fkUc# ˣI)ŬC&UzT*n\' `t >\> WPmy0nr<̹+z};a:nX?>}I~1IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/status.png000066400000000000000000000012351342663516400243310ustar00rootroot00000000000000PNG  IHDRa pHYs  d_gAMA|Q cHRMz%u0`:o_FIDATxb?###:x?T;GՀ/Wϯ}pW}zB6Ĉ`qAnY"H\_q.  g?/7KDi| nH/@1/~ߛaMmZ߁D<9Ht@Ǘ~xp7C^f~ a@`3P-Yh/30ſ~_ ?@gKn~-kLM_?5|hiNq  h| lç@[B4@/z X^k9_?|`.HB\@hdfbhb3 s  `oኡ 6 \W.z b@A\ůs@A{;f10b7Ih4+X+@\֟IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/stop.png000066400000000000000000000013451342663516400237750ustar00rootroot00000000000000PNG  IHDRa pHYs  @AtIME ",ᢂbKGDrIDAT8uRKSa?W!AwuA·Vє^DE4$"ԅC "B>*Эe6Cٚhv\Я9:==s8nI|ϋU!^b+pF[>ؤyq^˘lx dfn[z$ b*x<IDAThoUǿx؉Mi8q a)m"BC[@-TDPUTBC"AT,V !Qv \8 8Ǟ7o҃_5{a"!sK%{b·[ N[p /v2(A*'B9<~qfq#oBUvq҃#_3/hC3_w-q sYl(sZކQNEy{jEEC%>,!%TK˂GMc6-218Ga<iƯn<+j<@^ut7sm݇P>|ߓGޣ ځn 4Ey0 g=䑤g ]A^ o$JZyb`2S|fwRYlMNzO|Lg(80>$g( ߈#8(B#Q1 \.--&g&<%M,G1 E^t*r{٢Qǣ|wh%Ň-Xw#jG^7;`M)嬊{ =!t]:(+y<_XR&ǗLn]Os )UDn~6s$ǙnjdXTie޴T'ʔ0rN ow˜kOeLqͬOp?3B1[Y,8i 2>K6_xZ1KK=%GY|NuZ_-C1U ,g)C=6ZƗ#FHDۅsMLwH)YߌQ&j?SAI*\x*cpSh= JpEp#|ZCEdԢmDŅ\ګUxx+qoiV/e 3<}Q!C"Ek7 ]C+18W( bc\|ΞWIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/time.png000066400000000000000000000017001342663516400237410ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<RIDAT8OL[u^_/-뜨H72EaŸxVn'&F=(]4p iDc&3eM[ھ#&&f0ME2B^M8R5#;\w&'oH3#艎zà\7W Jlj&}1ɥSRJMbLI  Dy*tǻǺ{@hf $ulA7aoF(L'}뺷@*ed,ԙܮb1 _R ; F>§^$L58 ],2wl՜4 IjRjj \]eRr` {P^ V=ngQRC0ߤaT,Ľ{|w_'| ) Ejk KtU#Z.3U -.8NSqqbpqel Uޤ#$;Di)Wx\:> ^nG+nR*WMõ ǹE8}}` (T o=4q%һ Y?Mߙr;#t;<*AlVWpMXcc_ݜMd;dr=>5t*HhZ 7iˬGf]p^:ʅ@01 CJU3O&gwk 򴢨]`:Aj5 `@d NIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/tip.png000066400000000000000000000011141342663516400235760ustar00rootroot00000000000000PNG  IHDRaIDATxjSQszI\`MQP-XT| s'Od$NPP "REKKPkI4MOrrĶɁ׿ֿ>ȿJFcce>4ߥ})_)HQ7bn'-󴑸 ͊Fj3nƆ<OMEcx뀻IPuDsXMN5ǐ x߀JKXj~j88 ZwT}R}*1+-V Ooo@~t\*|JJQ-dbYr^τEHA?"ݑE_@ϫ`̬R 4npNH Tx,Vv@}y,Bx-s6{piSz!zRP@>lA)E&2~vW (6QlVy;~\.wܶqh49G IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/tune.png000066400000000000000000000007731342663516400237670ustar00rootroot00000000000000PNG  IHDRaIDATxc`@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ˬ׌YIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/upload.png000066400000000000000000000006261342663516400242750ustar00rootroot00000000000000PNG  IHDR(-SgAMA|QPLTE..jj0P x)hhbnKK[kZZ[LckLYZYLmZ,d,-KSSqqnR tRNSsdIDATx^UU@ !en&4odYD8u |LStB@'lV`wC ~\v=@!8aq=b+' ͦ/<xmy١L[Q򐳐/ 7kIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/url.png000066400000000000000000000016301342663516400236070ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<*IDATuO[e{iKK)v V>Ɓ[F]K0BYL_` M̌ 2BuL[݀r,K|zgϰ<5WuwZYaBnn3}c`G(А ™kM]iY#,aYϳxPBẕiOΟe)_x nf `kUl/IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/vcard.png000066400000000000000000000007241342663516400241070ustar00rootroot00000000000000PNG  IHDRa pHYs  ~gAMA|Q cHRMz%u0`:o_FJIDATxb?% X@zGMmP']@`0800ʀ Ze'2!8d3A!20lބ @ T"1]4In@ - iͫ[AacؼYn@ <ͦh$v #CT<#P abGCl,Yn@! j[,RhpbP hY>B`FFA8˗n RQqQQ60||pn@1R%I )L@! 0!~ܼIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/whiteboarding/000077500000000000000000000000001342663516400251255ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iconsets/system/default/whiteboarding/add_image.png000066400000000000000000000012151342663516400275240ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˥OhAn RK%JzQAP4 KOyd^*DQT4"RKmNZIcgotד ?v7aZ`|j8q\yR И}>9[TbۢY3CG:1AN0]efB8pA'/h|GWMa:h(⋐R.{!2I{ =p?q4Ot\"M'gS m9?hubָnlÁ1ҫ Rb=`g\9niܦ5ZFͦ7~ynHt*s)^d+Gt?`_A`Sw37Sk#`ƿ#Sk؅逅%Q~9Nf X Y5ͳASdhhJ ܻPjbKq[myzkd@lhZpwIΚbX(d>_M2hYVhrЂIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/whiteboarding/add_text.png000066400000000000000000000010671342663516400274330ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8c?%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.554/iconsets/system/default/whiteboarding/bring_forwards.png000066400000000000000000000011251342663516400306420ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˥?kTQ}{/!vZ:ISX;M&_AA l33X4[̜vIϿݏ02RF48 ]٧gQFo 2!&dd$X#9?[>I^4Bi<ׂtf8]; _it&8M8U*J$DMӉ~xQ`p4ٝ_U"\Җ9/F(KX(޺#0YD-Ս*m5tLRx,/\ڎ454cZz B p]ۋs+.Ƅk#*}hfvxX lJ] 48+΀-Iy֞$8j{ g53~ '{;ᅵ3A QOz5/HeW@޷J4v 'P0IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/whiteboarding/bring_to_front.png000066400000000000000000000011251342663516400306450ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˥?kTQ}{/!vZ:ISX;M&_AA l33X4[̜vIϿݏ02RF48 ]٧gQFo 2!&dd$X#9?[>I^4Bi<ׂtf8]; _it&8M8U*J$DMӉ~xQ`p4ٝ_U"\Җ9/F(KX(޺#0YD-Ս*m5tLRx,/\ڎ454cZz B p]ۋs+.Ƅk#*}hfvxX lJ] 48+΀-Iy֞$8j{ g53~ '{;ᅵ3A QOz5/HeW@޷J4v 'P0IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/whiteboarding/draw_circles.png000066400000000000000000000010121342663516400302660ustar00rootroot00000000000000PNG  IHDR7gAMA|Q cHRMz%u0`:o_F pHYs  bKGD̿rIDAT(ϥJAI6q#5 V) >`g%v>MXx AMhMb%q8?sߜ;`ۃ*z9ƈ;xlM}%ڂ1W̪ԏklNCفՎOPq ~ˑL߰t\U$3N~M4)WOT<)w%àʑ8tE*$CH&Q0>%g`M$Y|=’_ f'VFbOyN(pQzXԒ;^pre%p&F;Āk.k\2 E#B Y%MѐOou-IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/whiteboarding/draw_ellipses.png000066400000000000000000000010121342663516400304620ustar00rootroot00000000000000PNG  IHDR7gAMA|Q cHRMz%u0`:o_F pHYs  bKGD̿rIDAT(ϥJAI6q#5 V) >`g%v>MXx AMhMb%q8?sߜ;`ۃ*z9ƈ;xlM}%ڂ1W̪ԏklNCفՎOPq ~ˑL߰t\U$3N~M4)WOT<)w%àʑ8tE*$CH&Q0>%g`M$Y|=’_ f'VFbOyN(pQzXԒ;^pre%p&F;Āk.k\2 E#B Y%MѐOou-IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/whiteboarding/draw_lines.png000066400000000000000000000006321342663516400277630ustar00rootroot00000000000000PNG  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  nIDATx^}EPDNW75*ivYx9룯gGYӃ<)ƏieSѰZna>̎U zKZU C ,IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/whiteboarding/draw_paths.png000066400000000000000000000006321342663516400277700ustar00rootroot00000000000000PNG  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  nIDATx^}EPDNW75*ivYx9룯gGYӃ<)ƏieSѰZna>̎U zKZU C ,IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/whiteboarding/draw_rectangles.png000066400000000000000000000005461342663516400310040ustar00rootroot00000000000000PNG  IHDR7gAMA|Q cHRMz%u0`:o_F pHYs  bKGD̿IDAT(υ=JAov:2 0fP =`h)T6nhWr^ իzN7w(t |C}n M9zFŐ$FeN/ gSje*7 JP+J]A5̰a7ol!t-O!x}|:+Zv]V'NIIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/whiteboarding/erase.png000066400000000000000000000002151342663516400267300ustar00rootroot00000000000000PNG  IHDR7 pHYs  ~?IDAT(cxǀ2TaeEU9.tӰ؋b}A @DސFd"kIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/whiteboarding/group.png000066400000000000000000000005271342663516400267730ustar00rootroot00000000000000PNG  IHDR7gAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT(cπ2IwViykHܝ]V7TO/C7>]Fl`~'S{ضaC? ]M'sC59QXnfi%A;BvN+I V{O_Hm{뚚`j^Sd){j IJ=UIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/whiteboarding/rotate.png000066400000000000000000000011541342663516400271320ustar00rootroot00000000000000PNG  IHDR7 pHYs  gAMA|Q cHRMz%u0`:o_FIDATMHqyڊYR ^aTBi^u{ѡ[9:<ՈkK mg3#׉cEIj x4 MF75%էCӍ:EH t}03>z|[]1&mdTgxuE~HIfRofX'a<9u= /ơV_-?tGT> vqjF}96?aj$5g@`a3 ACu)uo,eWFnwTUw6xSLyf%Ֆ;Qn ƣ\t!4KArW0Iʼvk,E KL]5~+vu+ 4☕NgدZt;lof8GIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/whiteboarding/save.png000066400000000000000000000022431342663516400265720ustar00rootroot00000000000000PNG  IHDR szzgAMAOX2tEXtSoftwareAdobe ImageReadyqe<5IDATX͏TE{n@FM4DHČL\.M\kXÎč΂htA\ =`PQ` ^` QVR{:ԩz~6>>]vN5ػ」;v<:̗7XSڠkg|p?0x.ory #ӟd}k@E@xp,#˲m00aP|#%"+Օs΃* ,I Iw:琷Ղv+m(r8 㯼IkFؽ{k놮ZVmQg5 ,# /#&|#!Я'aa%% 4M:ólc V_)Fara-р: TIO4/!g iXvj#wPE x:Tx3x$wh6h߱R^DM_ 8ňKvRPZ0}@Ű&WU.2p}Ʉ-Jsf+7QW?תd~')Ü wl 55Y1{X0+WVU!ѐS,-޸Wj'WڣU<_o|w__sl}5ځ?MKFIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/whiteboarding/scale.png000066400000000000000000000005511342663516400267230ustar00rootroot00000000000000PNG  IHDR(-SPLTE555\\\sssHHHMMM###666rrr$$$///}}}tttfffyyyـՉKKK׼~~~|||CCCeeeqqqwwwzzz@@@ǫhtRNS ~ `IDATx^E0)mwz4N~r. @Vˎ)h[U?c/7۲qu:Fashl]?䷮Ҁ&LDmDX[tģ+ڈfl h*a`9ee;iՐ{DOx' 06m`yV&8nC >LɶU[nER*Ѡt 8 )Z,znq-r9EQY|PK V욃~p)ZmhE3&Q(6lc al0%l2$'ZM6}b h1\SUPa!Fp&۔)A>Zcbfrܟ;S D#фzT/_lsX0i#`>{nm%FȮ{O=ۏsu#R^j$Są69|Lw< tH.8iNK<#|fUoml`eAIENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/whiteboarding/select.png000066400000000000000000000005421342663516400271130ustar00rootroot00000000000000PNG  IHDR7gAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT(}1Kq$uzBAib)\(98 `QpPQ"ڥ#A hw'{O ){m=ʳI^4Bi<ׂtf8]; _it&8M8U*J$DMӉ~xQ`p4ٝ_U"\Җ9/F(KX(޺#0YD-Ս*m5tLRx,/\ڎ454cZz B p]ۋs+.Ƅk#*}hfvxX lJ] 48+΀-Iy֞$8j{ g53~ '{;ᅵ3A QOz5/HeW@޷J4v 'P0IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/whiteboarding/send_to_back.png000066400000000000000000000011251342663516400302450ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˥?kTQ}{/!vZ:ISX;M&_AA l33X4[̜vIϿݏ02RF48 ]٧gQFo 2!&dd$X#9?[>I^4Bi<ׂtf8]; _it&8M8U*J$DMӉ~xQ`p4ٝ_U"\Җ9/F(KX(޺#0YD-Ս*m5tLRx,/\ڎ454cZz B p]ۋs+.Ƅk#*}hfvxX lJ] 48+΀-Iy֞$8j{ g53~ '{;ᅵ3A QOz5/HeW@޷J4v 'P0IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/whiteboarding/translate.png000066400000000000000000000012311342663516400276250ustar00rootroot00000000000000PNG  IHDRa pHYs  gAMA|Q cHRMz%u0`:o_FIDATxb?% qv@;'QxSd" I uGlωZl6 ӦMC1 P_@C\\]={h#@1%?@>@ \ Ȇɀ @45m߱͛ ˖-8_?0@p`^}oF@  ؀?B4ի`[~2+*l2 p 7e˖1|a _~j} Y ==(PĐ T;ʊ z>2tuu3꿿0&>kD_?_xO aQbjofK= ~a2u*`hj{ %YhΘ> 5HjDC1 L6`ǎMHiv0v)IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/whiteboarding/ungroup.png000066400000000000000000000011301342663516400273250ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8RAha6~ 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.554/iconsets/system/default/whiteboarding/whiteboard.png000066400000000000000000000012771342663516400277720ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<QIDATkqw~X0$֢Q"BJ1I-QJqA;jv\x(yQvlVc%f]X&kq3@SũPS9 sʄX#uDfEͩhQ+/b,⿲pe3?<挆msݍ=|3%/8 05bNW0\>%2*;|' &yC3׉>j;Y=䭦SGL"*jD ro[A]1S2ɕ !fx#?\o3B$Y\Su!" Br&1g-C/"ѯDK[+,"!/y쇛$n`(u/Ia;Sj88gN ED ~([&"1#70Tl:OQL!@W[3)Tz_fRYt=KR4BjFOuԟ 9)Km>H6UV/ō599鮮~+nSP ˶,d4M β*(Jotnc6B`Y˗|;wC2R'eJ0 IRJj?z'~ !mcvAA$xp8v8} G^{8mx4%MSz#۲,q" C$AkMDQDE!ZmjJX}Q#R0p]qZ.iR"< R  2$0 s![Ri"r"DkM$ap8\.S1`Yn*ۤiJ$q֚,y,p]uvv(> uw Q!Q$ ^O_n7lnn @%l6i4B`???~ |fyKtL5t:c1A 緼k599yVRSzGecchkk;ŧ_OMM}o1׃Vu~ppW[¿(s>"и9IENDB`psi-plus-snapshots-1.4.554/iconsets/system/default/xmpp.svg000066400000000000000000000042051342663516400240050ustar00rootroot00000000000000 psi-plus-snapshots-1.4.554/iris/000077500000000000000000000000001342663516400164465ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/CMakeLists.txt000066400000000000000000000100451342663516400212060ustar00rootroot00000000000000if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Release") endif() project(iris) cmake_minimum_required(VERSION 3.1.0) if(POLICY CMP0071) cmake_policy(SET CMP0071 OLD) endif() if(POLICY CMP0074) cmake_policy(SET CMP0074 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} ) set( CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules" "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/modules" "${PROJECT_SOURCE_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 OFF) 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} ) macro(qt_wrap_cpp) set(_SOURCES ${ARGN}) list(REMOVE_AT _SOURCES 0) foreach(SOURCE ${_SOURCES}) if(SOURCE MATCHES "^(.*/|)([^/.]+)\\.h$") set(DEST "moc_${CMAKE_MATCH_2}.cpp") qt5_generate_moc(${SOURCE} ${DEST}) list(APPEND ${ARGV0} ${DEST}) elseif(SOURCE MATCHES "^(.*/|)([^/.]+)\\.cpp$") set(DEST "${CMAKE_MATCH_2}.moc") qt5_generate_moc(${SOURCE} ${DEST}) list(APPEND ${ARGV0} ${DEST}) else() message(WARNING "Wrong source ${SOURCE}") endif() endforeach() unset(_SOURCES) endmacro() if(NOT USE_QJDNS AND SEPARATE_QJDNS) message(WARNING "SEPARATE_QJDNS flag enabled, but USE_QJDNS flag disabled. Both flags will be disabled") set(SEPARATE_QJDNS OFF) 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) add_definitions(-DIRISNET_STATIC) add_subdirectory(src/irisnet) add_subdirectory(src/xmpp) psi-plus-snapshots-1.4.554/iris/COPYING000066400000000000000000000635041342663516400175110ustar00rootroot00000000000000 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.554/iris/IrisConfig.cmake.in000066400000000000000000000012521342663516400221110ustar00rootroot00000000000000# 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.554/iris/IrisConfigVersion.cmake.in000066400000000000000000000007241342663516400234620ustar00rootroot00000000000000set(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.554/iris/README.md000066400000000000000000000043151342663516400177300ustar00rootroot00000000000000# 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.554/iris/TODO000066400000000000000000000035221342663516400171400ustar00rootroot00000000000000netinterface 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.554/iris/cmake/000077500000000000000000000000001342663516400175265ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/cmake/modules/000077500000000000000000000000001342663516400211765ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/cmake/modules/COPYING-CMAKE-SCRIPTS000066400000000000000000000024571342663516400242040ustar00rootroot00000000000000Redistribution 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.554/iris/cmake/modules/FindIDN.cmake000066400000000000000000000054541342663516400234230ustar00rootroot00000000000000#============================================================================= # 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( IDN_INCLUDE_DIR AND IDN_LIBRARY ) 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( PC_IDN_FOUND ) endif( UNIX AND NOT( APPLE OR CYGWIN ) ) set( IDN_ROOT "" CACHE STRING "Path to libidn library" ) 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} libidn-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( IDN_FOUND ) mark_as_advanced( IDN_INCLUDE_DIR IDN_LIBRARY ) psi-plus-snapshots-1.4.554/iris/cmake/modules/FindQJDns.cmake000066400000000000000000000055641342663516400237720ustar00rootroot00000000000000#============================================================================= # 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 ( 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.554/iris/cmake/modules/FindQca.cmake000066400000000000000000000046611342663516400235140ustar00rootroot00000000000000#============================================================================= # 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 ) 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(Qca_FOUND) mark_as_advanced( Qca_INCLUDE_DIR Qca_LIBRARY ) psi-plus-snapshots-1.4.554/iris/cmake_uninstall.cmake.in000066400000000000000000000014011342663516400232220ustar00rootroot00000000000000if(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.554/iris/common.pri000066400000000000000000000006421342663516400204540ustar00rootroot00000000000000# 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.554/iris/conf_win.pri.example000066400000000000000000000004321342663516400224150ustar00rootroot00000000000000load(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.554/iris/confapp.pri000066400000000000000000000002611342663516400206070ustar00rootroot00000000000000unix: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.554/iris/confapp_win.pri.example000066400000000000000000000004321342663516400231160ustar00rootroot00000000000000load(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.554/iris/configure000077500000000000000000001650161342663516400203660ustar00rootroot00000000000000#!/bin/sh # # Generated by qconf 2.0 ( http://delta.affinix.com/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 ;; --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 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 } }; 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; 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'/' pathes 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; } 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.554/iris/include/000077500000000000000000000000001342663516400200715ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/include/iris/000077500000000000000000000000001342663516400210375ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/include/iris/addressresolver.h000066400000000000000000000000671342663516400244220ustar00rootroot00000000000000#include "../../src/irisnet/corelib/addressresolver.h" psi-plus-snapshots-1.4.554/iris/include/iris/bsocket.h000066400000000000000000000000711342663516400226400ustar00rootroot00000000000000#include "../../src/irisnet/noncore/cutestuff/bsocket.h" psi-plus-snapshots-1.4.554/iris/include/iris/bytestream.h000066400000000000000000000000741342663516400233700ustar00rootroot00000000000000#include "../../src/irisnet/noncore/cutestuff/bytestream.h" psi-plus-snapshots-1.4.554/iris/include/iris/filetransfer.h000066400000000000000000000000611342663516400236710ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/filetransfer.h" psi-plus-snapshots-1.4.554/iris/include/iris/httpconnect.h000066400000000000000000000000751342663516400235430ustar00rootroot00000000000000#include "../../src/irisnet/noncore/cutestuff/httpconnect.h" psi-plus-snapshots-1.4.554/iris/include/iris/httpfileupload.h000066400000000000000000000000631342663516400242330ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/httpfileupload.h" psi-plus-snapshots-1.4.554/iris/include/iris/httppoll.h000066400000000000000000000000721342663516400230550ustar00rootroot00000000000000#include "../../src/irisnet/noncore/cutestuff/httppoll.h" psi-plus-snapshots-1.4.554/iris/include/iris/ice176.h000066400000000000000000000000561342663516400222070ustar00rootroot00000000000000#include "../../src/irisnet/noncore/ice176.h" psi-plus-snapshots-1.4.554/iris/include/iris/im.h000066400000000000000000000000471342663516400216160ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/im.h" psi-plus-snapshots-1.4.554/iris/include/iris/irisnetexport.h000066400000000000000000000000651342663516400241300ustar00rootroot00000000000000#include "../../src/irisnet/corelib/irisnetexport.h" psi-plus-snapshots-1.4.554/iris/include/iris/irisnetglobal.h000066400000000000000000000000651342663516400240470ustar00rootroot00000000000000#include "../../src/irisnet/corelib/irisnetglobal.h" psi-plus-snapshots-1.4.554/iris/include/iris/irisnetplugin.h000066400000000000000000000000651342663516400241050ustar00rootroot00000000000000#include "../../src/irisnet/corelib/irisnetplugin.h" psi-plus-snapshots-1.4.554/iris/include/iris/ndns.h000066400000000000000000000000631342663516400221510ustar00rootroot00000000000000#include "../../src/irisnet/noncore/legacy/ndns.h" psi-plus-snapshots-1.4.554/iris/include/iris/netavailability.h000066400000000000000000000000671342663516400243740ustar00rootroot00000000000000#include "../../src/irisnet/corelib/netavailability.h" psi-plus-snapshots-1.4.554/iris/include/iris/netinterface.h000066400000000000000000000000641342663516400236570ustar00rootroot00000000000000#include "../../src/irisnet/corelib/netinterface.h" psi-plus-snapshots-1.4.554/iris/include/iris/netnames.h000066400000000000000000000000601342663516400230160ustar00rootroot00000000000000#include "../../src/irisnet/corelib/netnames.h" psi-plus-snapshots-1.4.554/iris/include/iris/objectsession.h000066400000000000000000000000651342663516400240630ustar00rootroot00000000000000#include "../../src/irisnet/corelib/objectsession.h" psi-plus-snapshots-1.4.554/iris/include/iris/processquit.h000066400000000000000000000000631342663516400235700ustar00rootroot00000000000000#include "../../src/irisnet/noncore/processquit.h" psi-plus-snapshots-1.4.554/iris/include/iris/s5b.h000066400000000000000000000000501342663516400216740ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/s5b.h" psi-plus-snapshots-1.4.554/iris/include/iris/servsock.h000066400000000000000000000000671342663516400230520ustar00rootroot00000000000000#include "../../src/irisnet/noncore/legacy/servsock.h" psi-plus-snapshots-1.4.554/iris/include/iris/socks.h000066400000000000000000000000671342663516400223350ustar00rootroot00000000000000#include "../../src/irisnet/noncore/cutestuff/socks.h" psi-plus-snapshots-1.4.554/iris/include/iris/srvresolver.h000066400000000000000000000000721342663516400236030ustar00rootroot00000000000000#include "../../src/irisnet/noncore/legacy/srvresolver.h" psi-plus-snapshots-1.4.554/iris/include/iris/stunallocate.h000066400000000000000000000000641342663516400237060ustar00rootroot00000000000000#include "../../src/irisnet/noncore/stunallocate.h" psi-plus-snapshots-1.4.554/iris/include/iris/stunbinding.h000066400000000000000000000000631342663516400235330ustar00rootroot00000000000000#include "../../src/irisnet/noncore/stunbinding.h" psi-plus-snapshots-1.4.554/iris/include/iris/stunmessage.h000066400000000000000000000000631342663516400235450ustar00rootroot00000000000000#include "../../src/irisnet/noncore/stunmessage.h" psi-plus-snapshots-1.4.554/iris/include/iris/stuntransaction.h000066400000000000000000000000671342663516400244520ustar00rootroot00000000000000#include "../../src/irisnet/noncore/stuntransaction.h" psi-plus-snapshots-1.4.554/iris/include/iris/turnclient.h000066400000000000000000000000621342663516400233750ustar00rootroot00000000000000#include "../../src/irisnet/noncore/turnclient.h" psi-plus-snapshots-1.4.554/iris/include/iris/udpportreserver.h000066400000000000000000000000671342663516400244660ustar00rootroot00000000000000#include "../../src/irisnet/noncore/udpportreserver.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp.h000066400000000000000000000000531342663516400221720ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-core/xmpp.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_address.h000066400000000000000000000000611342663516400236760ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_address.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_agentitem.h000066400000000000000000000000631342663516400242300ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_agentitem.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_bitsofbinary.h000066400000000000000000000000661342663516400247510ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_bitsofbinary.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_caps.h000066400000000000000000000000561342663516400232030ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_caps.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_captcha.h000066400000000000000000000000611342663516400236540ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_captcha.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_chatstate.h000066400000000000000000000000631342663516400242330ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_chatstate.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_client.h000066400000000000000000000000601342663516400235260ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_client.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_clientstream.h000066400000000000000000000000701342663516400247430ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-core/xmpp_clientstream.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_discoinfotask.h000066400000000000000000000000671342663516400251170ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_discoinfotask.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_discoitem.h000066400000000000000000000000631342663516400242330ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_discoitem.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_encryptionhandler.h000066400000000000000000000000731342663516400260040ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_encryptionhandler.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_features.h000066400000000000000000000000621342663516400240700ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_features.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_hash.h000066400000000000000000000000561342663516400232000ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_hash.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_htmlelement.h000066400000000000000000000000651342663516400245730ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_htmlelement.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_httpauthrequest.h000066400000000000000000000000711342663516400255240ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_httpauthrequest.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_jid.h000066400000000000000000000000441342663516400230200ustar00rootroot00000000000000#include "../../src/xmpp/jid/jid.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_liveroster.h000066400000000000000000000000641342663516400244520ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_liveroster.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_liverosteritem.h000066400000000000000000000000701342663516400253260ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_liverosteritem.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_message.h000066400000000000000000000000611342663516400236750ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_message.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_muc.h000066400000000000000000000000551342663516400230400ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_muc.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_pubsubitem.h000066400000000000000000000000641342663516400244330ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_pubsubitem.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_pubsubretraction.h000066400000000000000000000000721342663516400256460ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_pubsubretraction.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_resource.h000066400000000000000000000000621342663516400241010ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_resource.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_resourcelist.h000066400000000000000000000000661342663516400250010ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_resourcelist.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_roster.h000066400000000000000000000000601342663516400235660ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_roster.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_rosteritem.h000066400000000000000000000000641342663516400244510ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_rosteritem.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_rosterx.h000066400000000000000000000000611342663516400237570ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_rosterx.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_serverinfomanager.h000066400000000000000000000000731342663516400257710ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_serverinfomanager.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_stanza.h000066400000000000000000000000621342663516400235520ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-core/xmpp_stanza.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_status.h000066400000000000000000000000601342663516400235730ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_status.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_stream.h000066400000000000000000000000621342663516400235450ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-core/xmpp_stream.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_subsets.h000066400000000000000000000000611342663516400237410ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_subsets.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_task.h000066400000000000000000000000561342663516400232170ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_task.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_tasks.h000066400000000000000000000000571342663516400234030ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_tasks.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_url.h000066400000000000000000000000551342663516400230560ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_url.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_vcard.h000066400000000000000000000000571342663516400233550ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_vcard.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_xdata.h000066400000000000000000000000571342663516400233570ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_xdata.h" psi-plus-snapshots-1.4.554/iris/include/iris/xmpp_xmlcommon.h000066400000000000000000000000631342663516400242640ustar00rootroot00000000000000#include "../../src/xmpp/xmpp-im/xmpp_xmlcommon.h" psi-plus-snapshots-1.4.554/iris/iris.pc.in000066400000000000000000000005241342663516400203460ustar00rootroot00000000000000prefix=@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.554/iris/iris.pri000066400000000000000000000021761342663516400201360ustar00rootroot00000000000000IRIS_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.554/iris/iris.pro000066400000000000000000000007001342663516400201330ustar00rootroot00000000000000TEMPLATE = 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.554/iris/iris.qc000066400000000000000000000006761342663516400177520ustar00rootroot00000000000000 Iris iris.pro qcm psi-plus-snapshots-1.4.554/iris/qcm/000077500000000000000000000000001342663516400172265ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/qcm/README000066400000000000000000000002761342663516400201130ustar00rootroot00000000000000qt42, 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.554/iris/qcm/buildmode.qcm000066400000000000000000000105751342663516400217040ustar00rootroot00000000000000/* -----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.554/iris/qcm/extra.qcm000066400000000000000000000033341342663516400210560ustar00rootroot00000000000000/* -----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.554/iris/qcm/idn.qcm000066400000000000000000000032171342663516400205050ustar00rootroot00000000000000/* -----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.554/iris/qcm/qca.qcm000066400000000000000000000124461342663516400205030ustar00rootroot00000000000000/* -----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.554/iris/qcm/qjdns.qcm000066400000000000000000000040671342663516400210560ustar00rootroot00000000000000/* -----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.554/iris/qcm/qt42.qcm000066400000000000000000000006241342663516400205240ustar00rootroot00000000000000/* -----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.554/iris/qcm/universal.qcm000066400000000000000000000022111342663516400217340ustar00rootroot00000000000000/* -----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.554/iris/qcm/zlib.qcm000066400000000000000000000021651342663516400206740ustar00rootroot00000000000000/* -----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.554/iris/src/000077500000000000000000000000001342663516400172355ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/irisnet/000077500000000000000000000000001342663516400207125ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/irisnet/CMakeLists.txt000066400000000000000000000054471342663516400234640ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.1.0) 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" "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/modules" ) set(PLAIN_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/servsock.cpp noncore/legacy/srvresolver.cpp ) set(SOURCES corelib/addressresolver.cpp corelib/netavailability.cpp corelib/netinterface.cpp corelib/netinterface_qtnet.cpp corelib/netnames.cpp corelib/objectsession.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/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() set(PLAIN_HEADERS corelib/irisnetexport.h corelib/irisnetglobal.h corelib/irisnetglobal_p.h noncore/stunmessage.h noncore/stuntypes.h noncore/stunutil.h ) set(HEADERS corelib/addressresolver.h corelib/irisnetplugin.h corelib/netavailability.h corelib/netinterface.h corelib/netnames.h corelib/objectsession.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/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/servsock.h noncore/legacy/srvresolver.h ) qt_wrap_cpp(MOC_SOURCES ${HEADERS} ${SOURCES}) add_library(irisnet STATIC ${SOURCES} ${HEADERS} ${MOC_SOURCES} ${PLAIN_SOURCES} ${PLAIN_HEADERS} ) if(WIN32 AND (SEPARATE_QJDNS OR (NOT USE_QJDNS))) set(EXTRA_LDFLAGS ws2_32) endif() if(NOT USE_QJDNS) set(QJDns_LIBRARY "") 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.554/iris/src/irisnet/appledns/000077500000000000000000000000001342663516400225205ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/irisnet/appledns/appledns.cpp000066400000000000000000000640631342663516400250430ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "irisnetplugin.h" #include #include #include "qdnssd.h" // for ntohl #ifdef Q_OS_WIN # 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; foreach(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); } }; } //---------------------------------------------------------------------------- // 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(); } foreach(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; } foreach(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; foreach(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; foreach(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; foreach(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.554/iris/src/irisnet/appledns/appledns.pri000066400000000000000000000001571342663516400250450ustar00rootroot00000000000000QT *= network HEADERS += $$PWD/qdnssd.h SOURCES += $$PWD/qdnssd.cpp $$PWD/appledns.cpp !mac:LIBS += -ldns_sd psi-plus-snapshots-1.4.554/iris/src/irisnet/appledns/appledns.pro000066400000000000000000000003521342663516400250500ustar00rootroot00000000000000IRIS_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.554/iris/src/irisnet/appledns/qdnssd.cpp000066400000000000000000000716571342663516400245400ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "qdnssd.h" #include #include // for ntohs #ifdef Q_OS_WIN # include #else # include #endif #include "dns_sd.h" 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); } }; } //---------------------------------------------------------------------------- // 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) { foreach(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 foreach(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.554/iris/src/irisnet/appledns/qdnssd.h000066400000000000000000000076761342663516400242050ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #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 psi-plus-snapshots-1.4.554/iris/src/irisnet/appledns/sdtest.cpp000066400000000000000000000322161342663516400245360ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include #include #include "qdnssd.h" // for ntohl #ifdef Q_OS_WIN # 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.554/iris/src/irisnet/appledns/sdtest.pro000066400000000000000000000002141342663516400245450ustar00rootroot00000000000000CONFIG += 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.554/iris/src/irisnet/corelib/000077500000000000000000000000001342663516400223315ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/irisnet/corelib/addressresolver.cpp000066400000000000000000000113501342663516400262440ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "addressresolver.h" #include "objectsession.h" #include "netnames.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(0); 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) { foreach(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) { foreach(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(); } } #include "addressresolver.moc" psi-plus-snapshots-1.4.554/iris/src/irisnet/corelib/addressresolver.h000066400000000000000000000025711342663516400257160ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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 = 0); ~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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/irisnet/corelib/corelib.pri000066400000000000000000000017511342663516400244700ustar00rootroot00000000000000QT *= 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/netinterface_qtnet.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 \ } #include(legacy/legacy.pri) appledns:appledns_bundle { DEFINES += APPLEDNS_STATIC include(../appledns/appledns.pri) } psi-plus-snapshots-1.4.554/iris/src/irisnet/corelib/corelib.pro000066400000000000000000000005751342663516400245010ustar00rootroot00000000000000IRIS_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.554/iris/src/irisnet/corelib/irisnetexport.h000066400000000000000000000020321342663516400254160ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #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 psi-plus-snapshots-1.4.554/iris/src/irisnet/corelib/irisnetglobal.cpp000066400000000000000000000160161342663516400256770ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "irisnetglobal_p.h" #include "irisnetplugin.h" namespace XMPP { // built-in providers extern IrisNetProvider *irisnet_createQtNetProvider(); #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; QObject *_instance; bool _ownInstance; PluginInstance() { } public: static PluginInstance *fromFile(const QString &fname) { QPluginLoader *loader = new QPluginLoader(fname); if(!loader->load()) { delete loader; return 0; } QObject *obj = loader->instance(); if(!obj) { loader->unload(); delete loader; return 0; } 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 = 0; i->_instance = obj; i->_ownInstance = false; return i; } static PluginInstance *fromInstance(QObject *obj) { PluginInstance *i = new PluginInstance; i->_loader = 0; 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(0); } 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) { addBuiltIn(irisnet_createQtNetProvider()); // interfaces. crossplatform. no need to reimplement #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 = 0; static void deinit(); static void init() { QMutexLocker locker(global_mutex()); if(global) return; global = new IrisNetGlobal; qAddPostRoutine(deinit); } void deinit() { if(!global) return; while(!global->cleanupList.isEmpty()) (global->cleanupList.takeFirst())(); delete global; global = 0; } //---------------------------------------------------------------------------- // 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; } } psi-plus-snapshots-1.4.554/iris/src/irisnet/corelib/irisnetglobal.h000066400000000000000000000023001342663516400253330ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef IRISNETGLOBAL_H #define IRISNETGLOBAL_H #include #include #include "irisnetexport.h" 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(); } #endif psi-plus-snapshots-1.4.554/iris/src/irisnet/corelib/irisnetglobal_p.h000066400000000000000000000021231342663516400256550ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #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(); } #endif psi-plus-snapshots-1.4.554/iris/src/irisnet/corelib/irisnetplugin.cpp000066400000000000000000000041761342663516400257410ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "irisnetplugin.h" namespace XMPP { //---------------------------------------------------------------------------- // IrisNetProvider //---------------------------------------------------------------------------- NetInterfaceProvider *IrisNetProvider::createNetInterfaceProvider() { return 0; } NetGatewayProvider *IrisNetProvider::createNetGatewayProvider() { return 0; } NetAvailabilityProvider *IrisNetProvider::createNetAvailabilityProvider() { return 0; } NameProvider *IrisNetProvider::createNameProviderInternet() { return 0; } NameProvider *IrisNetProvider::createNameProviderLocal() { return 0; } ServiceProvider *IrisNetProvider::createServiceProvider() { return 0; } //---------------------------------------------------------------------------- // 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); } } psi-plus-snapshots-1.4.554/iris/src/irisnet/corelib/irisnetplugin.h000066400000000000000000000140151342663516400253770ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef IRISNETPLUGIN_H #define IRISNETPLUGIN_H #include "irisnetglobal.h" #include "netinterface.h" #include "netavailability.h" #include "netnames.h" namespace XMPP { class NetInterfaceProvider; class NetGatewayProvider; class NetAvailabilityProvider; class NameProvider; 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 = 0) : 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 = 0) : 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 = 0) : 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 = 0) : 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 = 0) : 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); }; } 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 psi-plus-snapshots-1.4.554/iris/src/irisnet/corelib/netavailability.cpp000066400000000000000000000024201342663516400262140ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #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; } } #include "netavailability.moc" psi-plus-snapshots-1.4.554/iris/src/irisnet/corelib/netavailability.h000066400000000000000000000022401342663516400256610ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef NETAVAILABILITY_H #define NETAVAILABILITY_H #include "irisnetglobal.h" namespace XMPP { class NetAvailability : public QObject { Q_OBJECT public: NetAvailability(QObject *parent = 0); ~NetAvailability(); bool isAvailable() const; signals: void changed(bool available); private: class Private; friend class Private; Private *d; }; } #endif psi-plus-snapshots-1.4.554/iris/src/irisnet/corelib/netinterface.cpp000066400000000000000000000240021342663516400255020ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "netinterface.h" #include "irisnetplugin.h" #include "irisnetglobal_p.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 = 0; foreach(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 = 0; } } 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 = 0; } 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 = 0; } private: QWaitCondition startCond; QMutex *startMutex; // these are all protected by global nettracker_mutex. int refs; static NetTrackerThread *self; NetTracker *nettracker; }; NetTrackerThread *NetTrackerThread::self = 0; //---------------------------------------------------------------------------- // 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 = 0; } 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 0; } void NetInterfaceManager::unreg(NetInterface *i) { d->listeners.removeAll(i); } } #include "netinterface.moc" psi-plus-snapshots-1.4.554/iris/src/irisnet/corelib/netinterface.h000066400000000000000000000145011342663516400251520ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef NETINTERFACE_H #define NETINTERFACE_H #include "irisnetglobal.h" namespace XMPP { class NetInterfaceManager; class NetInterfacePrivate; class NetInterfaceManagerPrivate; /** \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 = 0); /** \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); }; } #endif psi-plus-snapshots-1.4.554/iris/src/irisnet/corelib/netinterface_qtname.cpp000066400000000000000000000137531342663516400270620ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "irisnetplugin.h" #include namespace XMPP { class IrisQtName : public NameProvider { Q_OBJECT Q_INTERFACES(XMPP::NameProvider) int currentId; QHash lookups; public: IrisQtName(QObject *parent = 0) : NameProvider(parent), currentId(0) { } ~IrisQtName() { qDeleteAll(lookups); } 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 { 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, lookup); QMetaObject::invokeMethod(lookup, "lookup", Qt::QueuedConnection); } return id; } void resolve_stop(int id) { QDnsLookup *lookup = lookups.value(id); if (lookup) { lookup->abort(); // handleLookup will catch it and delete } } private slots: 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; } } #include "netinterface_qtname.moc" psi-plus-snapshots-1.4.554/iris/src/irisnet/corelib/netinterface_qtnet.cpp000066400000000000000000000044621342663516400267250ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #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; for (auto &iface: QNetworkInterface::allInterfaces()) { 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; } } #include "netinterface_qtnet.moc" psi-plus-snapshots-1.4.554/iris/src/irisnet/corelib/netinterface_unix.cpp000066400000000000000000000123651342663516400265560ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ // 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 #include #include // for solaris #ifndef SIOCGIFCONF # include #endif #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); out = str.split('\n', QString::SkipEmptyParts); 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]; QStringList parts = line.simplified().split(' ', QString::SkipEmptyParts); 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(0, 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]; QStringList parts = line.simplified().split(' ', QString::SkipEmptyParts); if(parts.count() < 10) continue; QHostAddress addr = linux_ipv6_to_qaddr(parts[4]); if(addr.isNull()) continue; int iflags = parts[8].toInt(0, 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; } } #include "netinterface_unix.moc" psi-plus-snapshots-1.4.554/iris/src/irisnet/corelib/netnames.cpp000066400000000000000000001231201342663516400246460ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "netnames.h" #include //#include #include "irisnetplugin.h" #include "irisnetglobal_p.h" #include "addressresolver.h" //#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 = 0; 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; foreach (const XMPP::NameRecord &record, *currentPriorityGroup) { totalWeight += record.weight(); } #ifdef NETNAMES_DEBUG NNDEBUG << "Total weight:" << totalWeight; #endif /* Pick a random entry */ int randomWeight = qrand()/static_cast(RAND_MAX)*totalWeight; #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 */ foreach (const WeightedNameRecordPriorityGroup &group, list.priorityGroups) { foreach(const NameRecord& record, group) { append(record); } } /* Reset to beginning */ currentPriorityGroup = priorityGroups.begin(); } void WeightedNameRecordList::append(const QList &list) { foreach (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()) { dbg.nospace() << "current=" << *list.currentPriorityGroup << endl; } dbg.nospace() << "{"; foreach(int priority, list.priorityGroups.keys()) { dbg.nospace() << "\t" << priority << "->" << list.priorityGroups.value(priority) << endl; } 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 = 0) : QObject(parent) { p_net = 0; p_local = 0; 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 = 0; } 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 >("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(); } foreach(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 = 0; } void browse_start(ServiceBrowser::Private *np, const QString &type, const QString &domain) { QMutexLocker locker(nman_mutex()); if(!p_serv) { ServiceProvider *c = 0; 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 = 0; 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("QHostAddress"); qRegisterMetaType< QList >("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 = 0; 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 = 0; 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 >("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 = 0; } 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 = 0; } } 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 */ foreach (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(); } } #include "netnames.moc" psi-plus-snapshots-1.4.554/iris/src/irisnet/corelib/netnames.h000066400000000000000000000533251342663516400243240ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef NETNAMES_H #define NETNAMES_H #include #include #include #include "irisnetglobal.h" // 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 = 0); /** \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 = 0); ~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 = 0); ~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 = 0); ~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; }; } Q_DECLARE_METATYPE(XMPP::NameResolver::Error) #endif psi-plus-snapshots-1.4.554/iris/src/irisnet/corelib/netnames_jdns.cpp000066400000000000000000002065031342663516400256730ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "irisnetplugin.h" #include "objectsession.h" #include "qjdnsshared.h" #include "netinterface.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. foreach(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; foreach(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 foreach(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() { foreach(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; foreach(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; foreach(PublishExtraItem *i, publishExtraItemList.items) { if(static_cast(i->publish->parent()) == pi->publish) remove += i; } foreach(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 foreach(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; } } #include "netnames_jdns.moc" psi-plus-snapshots-1.4.554/iris/src/irisnet/corelib/objectsession.cpp000066400000000000000000000167751342663516400257270ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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] == 0) 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(0); 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 { foreach(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 = 0; 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(); } } #include "objectsession.moc" psi-plus-snapshots-1.4.554/iris/src/irisnet/corelib/objectsession.h000066400000000000000000000052211342663516400253540ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef OBJECTSESSION_H #define OBJECTSESSION_H #include namespace XMPP { class ObjectSessionPrivate; class ObjectSessionWatcherPrivate; class ObjectSession : public QObject { Q_OBJECT public: ObjectSession(QObject *parent = 0); ~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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/irisnet/irisnet.pro000066400000000000000000000005411342663516400231110ustar00rootroot00000000000000TEMPLATE = 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 psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/000077500000000000000000000000001342663516400223555ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/cutestuff/000077500000000000000000000000001342663516400243655ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/cutestuff/bsocket.cpp000066400000000000000000000462061342663516400265330ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include #include #include "bsocket.h" //#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 = 0) :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->setReadBufferSize(READBUFSIZE); sd.relay = new QTcpSocketSignalRelay(sd.sock, this); sd.resolver = 0; connect(sd.relay, SIGNAL(connected()), SLOT(qs_connected())); connect(sd.relay, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(qs_error(QAbstractSocket::SocketError))); 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 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(); } } 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) { delete d->connector; // fixme: deleteLater? } 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, SIGNAL(connected()), SLOT(qs_connected())); connect(d->connector, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(qs_error(QAbstractSocket::SocketError))); } } /* 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(qintptr s) { resetConnection(true); d->qsock = new QTcpSocket(this); d->qsock->setSocketDescriptor(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 if (signalConnected) { emit connected(); } if (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.554/iris/src/irisnet/noncore/cutestuff/bsocket.h000066400000000000000000000060671342663516400262010ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef CS_BSOCKET_H #define CS_BSOCKET_H #include #include #include "bytestream.h" #include "netnames.h" class QString; class QObject; class QByteArray; // 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=0); ~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(qintptr); 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 psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/cutestuff/bytestream.cpp000066400000000000000000000161071342663516400272550ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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.554/iris/src/irisnet/noncore/cutestuff/bytestream.h000066400000000000000000000043231342663516400267170ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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 psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/cutestuff/cutestuff.pri000066400000000000000000000004371342663516400271150ustar00rootroot00000000000000INCLUDEPATH += $$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.554/iris/src/irisnet/noncore/cutestuff/httpconnect.cpp000066400000000000000000000230411342663516400274220ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "httpconnect.h" #include #include #include #include "bsocket.h" //#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().sprintf("\\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.554/iris/src/irisnet/noncore/cutestuff/httpconnect.h000066400000000000000000000034361342663516400270750ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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=0); ~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 psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/cutestuff/httppoll.cpp000066400000000000000000000622731342663516400267510ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "httppoll.h" #include #include #include #include #include #include #include #include "bsocket.h" #ifdef PROX_DEBUG #include #endif #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(0) { } ~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 { foreach (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()); foreach (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 = 0; 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 = 0; } 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 { foreach (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()); foreach (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.554/iris/src/irisnet/noncore/cutestuff/httppoll.h000066400000000000000000000077431342663516400264170ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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=0); ~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=0); ~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=0); ~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 psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/cutestuff/socks.cpp000066400000000000000000000645711342663516400262300ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "socks.h" #include #include #include #include #include #include #ifdef Q_OS_UNIX #include #include #endif #ifdef Q_OS_WIN32 #include #endif #ifdef Q_OS_UNIX #include #include #endif #include "servsock.h" #include "bsocket.h" //#define PROX_DEBUG #ifdef PROX_DEBUG #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.data(), data.size(), 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(qintptr 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; 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: Private(SocksServer *_q) : serv(_q) { } ServSock serv; QList incomingConns; QUdpSocket *sd; }; SocksServer::SocksServer(QObject *parent) :QObject(parent) { d = new Private(this); d->sd = 0; connect(&d->serv, SIGNAL(connectionReady(qintptr)), SLOT(connectionReady(qintptr))); } SocksServer::~SocksServer() { stop(); while (d->incomingConns.count()) { delete d->incomingConns.takeFirst(); } delete d; } bool SocksServer::isActive() const { return d->serv.isActive(); } bool SocksServer::listen(quint16 port, bool udp) { stop(); if(!d->serv.listen(port)) return false; if(udp) { d->sd = new QUdpSocket(this); if(!d->sd->bind(QHostAddress::LocalHost, port)) { delete d->sd; d->sd = 0; d->serv.stop(); return false; } connect(d->sd, SIGNAL(readyRead()), SLOT(sd_activated())); } return true; } void SocksServer::stop() { delete d->sd; d->sd = 0; d->serv.stop(); } int SocksServer::port() const { return d->serv.port(); } QHostAddress SocksServer::address() const { return d->serv.address(); } SocksClient *SocksServer::takeIncoming() { if(d->incomingConns.isEmpty()) return 0; 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::connectionReady(qintptr s) { SocksClient *c = new SocksClient(s, 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; QHostAddress sender; quint16 senderPort; datagram.resize(d->sd->pendingDatagramSize()); d->sd->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort); incomingUDP(sender.toString(), senderPort, d->sd->peerAddress(), d->sd->peerPort(), datagram); } } // CS_NAMESPACE_END psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/cutestuff/socks.h000066400000000000000000000102541342663516400256620ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef CS_SOCKS_H #define CS_SOCKS_H #include "bytestream.h" // CS_NAMESPACE_BEGIN class QHostAddress; 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=0); SocksClient(qintptr, QObject *parent=0); ~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=0); ~SocksServer(); 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 connectionReady(qintptr); void connectionError(); void sd_activated(); private: class Private; Private *d; }; // CS_NAMESPACE_END #endif psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/ice176.cpp000066400000000000000000001167401342663516400240700ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "ice176.h" #include #include #include #include #include "stuntransaction.h" #include "stunbinding.h" #include "stunmessage.h" #include "udpportreserver.h" #include "icelocaltransport.h" #include "iceturntransport.h" #include "icecomponent.h" namespace XMPP { enum { Direct, Relayed }; 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); } static QString randomCredential(int len) { QString out; for(int n = 0; n < len; ++n) out += randomPrintableChar(); return out; } 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; } // see if candidates are considered the same for pruning purposes static bool compare_candidates(const IceComponent::CandidateInfo &a, const IceComponent::CandidateInfo &b) { if(a.addr == b.addr && a.componentId == b.componentId) return true; else return false; } // 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: enum State { Stopped, Starting, Started, Stopping }; enum CandidatePairState { PWaiting, PInProgress, PSucceeded, PFailed, PFrozen }; enum CheckListState { LRunning, LCompleted, LFailed }; class CandidatePair { public: IceComponent::CandidateInfo local, remote; bool isDefault; bool isValid; bool isNominated; CandidatePairState state; qint64 priority; QString foundation; StunBinding *binding; // FIXME: this is wrong i think, it should be in LocalTransport // or such, to multiplex ids StunTransactionPool *pool; CandidatePair() : binding(0), pool(0) { } }; class CheckList { public: QList pairs; CheckListState state; }; class Component { public: int id; IceComponent *ic; bool localFinished; bool stopped; bool lowOverhead; Component() : localFinished(false), stopped(false), lowOverhead(false) { } }; Ice176 *q; Ice176::Mode mode; State state; TurnClient::Proxy proxy; UdpPortReserver *portReserver; int componentCount; 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; QList components; QList localCandidates; QSet iceTransports; CheckList checkList; QList< QList > in; bool useLocal; bool useStunBind; bool useStunRelayUdp; bool useStunRelayTcp; bool useTrickle; QTimer *collectTimer; Private(Ice176 *_q) : QObject(_q), q(_q), state(Stopped), portReserver(0), componentCount(0), useLocal(true), useStunBind(true), useStunRelayUdp(true), useStunRelayTcp(true), useTrickle(false), collectTimer(0) { } ~Private() { if(collectTimer) { collectTimer->disconnect(this); collectTimer->deleteLater(); } foreach(const Component &c, components) delete c.ic; // no need to delete pools and bindings since pools already deleted here // by QObject destructor as children of this(Ice176::Private) object. // Bindings deleted too as children of pool // should be reviewed by Justin =) /*for(int n = 0; n < checkList.pairs.count(); ++n) { StunBinding *binding = checkList.pairs[n].binding; StunTransactionPool *pool = checkList.pairs[n].pool; delete binding; if(pool) { pool->disconnect(this); pool->setParent(0); pool->deleteLater(); } }*/ } void reset() { // 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(); foreach(const LocalAddress &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(); foreach(const ExternalAddress &ea, addrs) { int at = findLocalAddress(ea.base.addr); if(at != -1) extAddrs += ea; } } void start() { Q_ASSERT(state == Stopped); state = Starting; localUser = randomCredential(4); localPass = randomCredential(22); QList socketList; if(portReserver) socketList = portReserver->borrowSockets(componentCount, this); for(int n = 0; n < componentCount; ++n) { Component c; 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, 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); c.ic->setUseStunBind(useStunBind); c.ic->setUseStunRelayUdp(useStunRelayUdp); c.ic->setUseStunRelayTcp(useStunRelayTcp); // create an inbound queue for this component in += QList(); components += c; 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 stop() { Q_ASSERT(state == Starting || state == Started); state = Stopping; if(!components.isEmpty()) { for(int n = 0; n < components.count(); ++n) components[n].ic->stop(); } else { // TODO: hmm, is it possible to have no components? QMetaObject::invokeMethod(this, "postStop", Qt::QueuedConnection); } } void addRemoteCandidates(const QList &list) { QList remoteCandidates; foreach(const Candidate &c, list) { IceComponent::CandidateInfo ci; 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; remoteCandidates += ci; } printf("adding %d remote candidates\n", remoteCandidates.count()); QList pairs; foreach(const IceComponent::Candidate &cc, localCandidates) { const IceComponent::CandidateInfo &lc = cc.info; foreach(const IceComponent::CandidateInfo &rc, remoteCandidates) { if(lc.componentId != rc.componentId) continue; // don't pair ipv4 with ipv6. FIXME: is this right? if(lc.addr.addr.protocol() != rc.addr.addr.protocol()) continue; // 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) continue; CandidatePair pair; pair.state = PFrozen; // FIXME: setting state here may be wrong 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()); pair.isDefault = false; pair.isValid = false; pair.isNominated = false; if(mode == Ice176::Initiator) pair.priority = calc_pair_priority(lc.priority, rc.priority); else pair.priority = calc_pair_priority(rc.priority, lc.priority); pairs += pair; } } printf("%d pairs\n", pairs.count()); // combine pairs with existing, and sort pairs = checkList.pairs + pairs; checkList.pairs.clear(); foreach(const CandidatePair &pair, pairs) { int at; for(at = 0; at < checkList.pairs.count(); ++at) { if(compare_pair(pair, checkList.pairs[at]) < 0) break; } checkList.pairs.insert(at, pair); } // pruning for(int n = 0; n < checkList.pairs.count(); ++n) { CandidatePair &pair = checkList.pairs[n]; if(pair.local.type == IceComponent::ServerReflexiveType) pair.local.addr = pair.local.base; } for(int n = 0; n < checkList.pairs.count(); ++n) { CandidatePair &pair = checkList.pairs[n]; printf("%d, %s:%d -> %s:%d\n", pair.local.componentId, qPrintable(pair.local.addr.addr.toString()), pair.local.addr.port, qPrintable(pair.remote.addr.addr.toString()), pair.remote.addr.port); bool found = false; for(int i = n - 1; i >= 0; --i) { if(compare_candidates(pair.local, checkList.pairs[i].local) && compare_candidates(pair.remote, checkList.pairs[i].remote)) { found = true; break; } } if(found ) { checkList.pairs.removeAt(n); --n; // adjust position } } // max pairs is 100 * number of components int max_pairs = 100 * components.count(); while(checkList.pairs.count() > max_pairs) checkList.pairs.removeLast(); printf("%d after pruning\n", checkList.pairs.count()); // set state for(int n = 0; n < checkList.pairs.count(); ++n) { CandidatePair &pair = checkList.pairs[n]; // only initialize the new pairs if(pair.state != PFrozen) continue; pair.foundation = pair.local.foundation + pair.remote.foundation; // FIXME: for now we just do checks to everything immediately pair.state = PInProgress; int at = findLocalCandidate(pair.local.addr.addr, pair.local.addr.port); Q_ASSERT(at != -1); IceComponent::Candidate &lc = localCandidates[at]; Component &c = components[findComponent(lc.info.componentId)]; pair.pool = new StunTransactionPool(StunTransaction::Udp, this); connect(pair.pool, SIGNAL(outgoingMessage(QByteArray,QHostAddress,int)), SLOT(pool_outgoingMessage(QByteArray,QHostAddress,int))); //pair.pool->setUsername(peerUser + ':' + localUser); //pair.pool->setPassword(peerPass.toUtf8()); pair.binding = new StunBinding(pair.pool); connect(pair.binding, SIGNAL(success()), SLOT(binding_success())); int prflx_priority = c.ic->peerReflexivePriority(lc.iceTransport, lc.path); pair.binding->setPriority(prflx_priority); if(mode == Ice176::Initiator) { pair.binding->setIceControlling(0); pair.binding->setUseCandidate(true); } else pair.binding->setIceControlled(0); pair.binding->setShortTermUsername(peerUser + ':' + localUser); pair.binding->setShortTermPassword(peerPass); pair.binding->start(); } } void write(int componentIndex, const QByteArray &datagram) { int at = -1; for(int n = 0; n < checkList.pairs.count(); ++n) { if(checkList.pairs[n].local.componentId - 1 == componentIndex && checkList.pairs[n].isValid) { at = n; break; } } if(at == -1) return; CandidatePair &pair = checkList.pairs[at]; at = findLocalCandidate(pair.local.addr.addr, pair.local.addr.port); if(at == -1) // FIXME: assert? return; IceComponent::Candidate &lc = localCandidates[at]; IceTransport *sock = lc.iceTransport; int path = lc.path; sock->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 } private: int findComponent(const IceComponent *ic) const { for(int n = 0; n < components.count(); ++n) { if(components[n].ic == ic) return n; } return -1; } int findComponent(int id) const { for(int n = 0; n < components.count(); ++n) { if(components[n].id == id) return n; } return -1; } int findLocalCandidate(const IceTransport *iceTransport, int path) const { for(int n = 0; n < localCandidates.count(); ++n) { const IceComponent::Candidate &cc = localCandidates[n]; if(cc.iceTransport == iceTransport && cc.path == path) 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 int compare_pair(const CandidatePair &a, const CandidatePair &b) { // prefer remote srflx, for leap if(a.remote.type == IceComponent::ServerReflexiveType && b.remote.type != IceComponent::ServerReflexiveType && b.remote.addr.addr.protocol() != QAbstractSocket::IPv6Protocol) return -1; else if(b.remote.type == IceComponent::ServerReflexiveType && a.remote.type != IceComponent::ServerReflexiveType && a.remote.addr.addr.protocol() != QAbstractSocket::IPv6Protocol) return 1; if(a.priority > b.priority) return -1; else if(b.priority > a.priority) return 1; return 0; } private slots: void postStop() { state = Stopped; emit q->stopped(); } void ic_candidateAdded(const XMPP::IceComponent::Candidate &_cc) { IceComponent::Candidate cc = _cc; cc.info.id = randomCredential(10); // FIXME: ensure unique cc.info.foundation = "0"; // FIXME // TODO localCandidates += cc; printf("C%d: candidate added: %s;%d\n", cc.info.componentId, qPrintable(cc.info.addr.addr.toString()), cc.info.addr.port); if(!iceTransports.contains(cc.iceTransport)) { connect(cc.iceTransport, SIGNAL(readyRead(int)), SLOT(it_readyRead(int))); connect(cc.iceTransport, SIGNAL(datagramsWritten(int,int,QHostAddress,int)), SLOT(it_datagramsWritten(int,int,QHostAddress,int))); iceTransports += cc.iceTransport; } if(state == Started && useTrickle) { QList list; Ice176::Candidate c; c.component = cc.info.componentId; c.foundation = cc.info.foundation; c.generation = 0; // TODO c.id = cc.info.id; c.ip = cc.info.addr.addr; c.ip.setScopeId(QString()); c.network = cc.info.network; c.port = cc.info.addr.port; c.priority = cc.info.priority; c.protocol = "udp"; if(cc.info.type != IceComponent::HostType) { c.rel_addr = cc.info.base.addr; c.rel_addr.setScopeId(QString()); c.rel_port = cc.info.base.port; } else { c.rel_addr = QHostAddress(); c.rel_port = -1; } c.rem_addr = QHostAddress(); c.rem_port = -1; c.type = candidateType_to_string(cc.info.type); list += c; emit q->localCandidatesReady(list); } } void ic_candidateRemoved(const XMPP::IceComponent::Candidate &cc) { // TODO printf("C%d: candidate removed: %s;%d\n", 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; foreach(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; StunTransactionPool *pool = checkList.pairs[n].pool; delete binding; if(pool) { pool->disconnect(this); pool->setParent(0); pool->deleteLater(); } checkList.pairs.removeAt(n); --n; // adjust position } } } void ic_localFinished() { IceComponent *ic = static_cast(sender()); int at = findComponent(ic); Q_ASSERT(at != -1); components[at].localFinished = true; bool allFinished = true; foreach(const Component &c, components) { if(!c.localFinished) { allFinished = false; break; } } if(allFinished) { state = Started; emit q->started(); if(!useTrickle) { // FIXME: there should be a way to not wait if // we know for sure there is nothing else // possibly coming collectTimer = new QTimer(this); connect(collectTimer, SIGNAL(timeout()), SLOT(collect_timeout())); collectTimer->setSingleShot(true); collectTimer->start(4000); return; } // FIXME: DOR-SS QList list; foreach(const IceComponent::Candidate &cc, localCandidates) { Ice176::Candidate c; c.component = cc.info.componentId; c.foundation = cc.info.foundation; c.generation = 0; // TODO c.id = cc.info.id; c.ip = cc.info.addr.addr; c.ip.setScopeId(QString()); c.network = cc.info.network; c.port = cc.info.addr.port; c.priority = cc.info.priority; c.protocol = "udp"; if(cc.info.type != IceComponent::HostType) { c.rel_addr = cc.info.base.addr; c.rel_addr.setScopeId(QString()); c.rel_port = cc.info.base.port; } else { c.rel_addr = QHostAddress(); c.rel_port = -1; } c.rem_addr = QHostAddress(); c.rem_port = -1; c.type = candidateType_to_string(cc.info.type); list += c; } if(!list.isEmpty()) emit q->localCandidatesReady(list); } } void ic_stopped() { IceComponent *ic = static_cast(sender()); int at = findComponent(ic); Q_ASSERT(at != -1); components[at].stopped = true; bool allStopped = true; foreach(const Component &c, components) { if(!c.stopped) { allStopped = false; break; } } if(allStopped) postStop(); } void ic_debugLine(const QString &line) { IceComponent *ic = static_cast(sender()); int at = findComponent(ic); Q_ASSERT(at != -1); // FIXME: components are always sorted? printf("C%d: %s\n", at + 1, qPrintable(line)); } void collect_timeout() { collectTimer->disconnect(this); collectTimer->deleteLater(); collectTimer = 0; QList list; foreach(const IceComponent::Candidate &cc, localCandidates) { Ice176::Candidate c; c.component = cc.info.componentId; c.foundation = cc.info.foundation; c.generation = 0; // TODO c.id = cc.info.id; c.ip = cc.info.addr.addr; c.ip.setScopeId(QString()); c.network = cc.info.network; c.port = cc.info.addr.port; c.priority = cc.info.priority; c.protocol = "udp"; if(cc.info.type != IceComponent::HostType) { c.rel_addr = cc.info.base.addr; c.rel_addr.setScopeId(QString()); c.rel_port = cc.info.base.port; } else { c.rel_addr = QHostAddress(); c.rel_port = -1; } c.rem_addr = QHostAddress(); c.rem_port = -1; c.type = candidateType_to_string(cc.info.type); list += c; } if(!list.isEmpty()) emit q->localCandidatesReady(list); } void it_readyRead(int path) { IceTransport *it = static_cast(sender()); int at = findLocalCandidate(it, path); Q_ASSERT(at != -1); IceComponent::Candidate &cc = localCandidates[at]; IceTransport *sock = it; while(sock->hasPendingDatagrams(path)) { QHostAddress fromAddr; int fromPort; QByteArray buf = sock->readDatagram(path, &fromAddr, &fromPort); //printf("port %d: received packet (%d bytes)\n", 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)) { printf("received validated request or indication from %s:%d\n", qPrintable(fromAddr.toString()), fromPort); QString user = QString::fromUtf8(msg.attribute(0x0006)); // USERNAME if(requser != user) { printf("user [%s] is wrong. it should be [%s]. skipping\n", qPrintable(user), qPrintable(requser)); continue; } if(msg.method() != 0x001) { printf("not a binding request. skipping\n"); continue; } StunMessage response; response.setClass(StunMessage::SuccessResponse); response.setMethod(0x001); response.setId(msg.id()); quint16 port16 = fromPort; quint32 addr4 = fromAddr.toIPv4Address(); QByteArray val(8, 0); quint8 *p = (quint8 *)val.data(); const quint8 *magic = response.magic(); p[0] = 0; p[1] = 0x01; p[2] = (port16 >> 8) & 0xff; p[2] ^= magic[0]; p[3] = port16 & 0xff; p[3] ^= magic[1]; p[4] = (addr4 >> 24) & 0xff; p[4] ^= magic[0]; p[5] = (addr4 >> 16) & 0xff; p[5] ^= magic[1]; p[6] = (addr4 >> 8) & 0xff; p[6] ^= magic[2]; p[7] = addr4 & 0xff; p[7] ^= magic[3]; QList list; StunMessage::Attribute attr; attr.type = 0x0020; attr.value = val; list += attr; response.setAttributes(list); QByteArray packet = response.toBinary(StunMessage::MessageIntegrity | StunMessage::Fingerprint, reqkey); sock->writeDatagram(path, packet, fromAddr, fromPort); } 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)) { printf("received validated response\n"); // 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.local.addr.addr == cc.info.addr.addr && pair.local.addr.port == cc.info.addr.port) pair.pool->writeIncomingMessage(msg); } } else { //printf("received some non-stun or invalid stun packet\n"); // FIXME: i don't know if this is good enough if(StunMessage::isProbablyStun(buf)) { printf("unexpected stun packet (loopback?), skipping.\n"); continue; } int at = -1; for(int n = 0; n < checkList.pairs.count(); ++n) { CandidatePair &pair = checkList.pairs[n]; if(pair.local.addr.addr == cc.info.addr.addr && pair.local.addr.port == cc.info.addr.port) { at = n; break; } } if(at == -1) { printf("the local transport does not seem to be associated with a candidate?!\n"); continue; } int componentIndex = checkList.pairs[at].local.componentId - 1; //printf("packet is considered to be application data for component index %d\n", 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); } void pool_outgoingMessage(const QByteArray &packet, const QHostAddress &addr, int port) { Q_UNUSED(addr); Q_UNUSED(port); StunTransactionPool *pool = static_cast(sender()); int at = -1; for(int n = 0; n < checkList.pairs.count(); ++n) { if(checkList.pairs[n].pool == pool) { at = n; break; } } if(at == -1) // FIXME: assert? return; CandidatePair &pair = checkList.pairs[at]; at = findLocalCandidate(pair.local.addr.addr, pair.local.addr.port); if(at == -1) // FIXME: assert? return; IceComponent::Candidate &lc = localCandidates[at]; IceTransport *sock = lc.iceTransport; int path = lc.path; printf("connectivity check from %s:%d to %s:%d\n", qPrintable(pair.local.addr.addr.toString()), pair.local.addr.port, qPrintable(pair.remote.addr.addr.toString()), pair.remote.addr.port); sock->writeDatagram(path, packet, pair.remote.addr.addr, pair.remote.addr.port); } void binding_success() { StunBinding *binding = static_cast(sender()); int at = -1; for(int n = 0; n < checkList.pairs.count(); ++n) { if(checkList.pairs[n].binding == binding) { at = n; break; } } if(at == -1) return; printf("check success\n"); CandidatePair &pair = checkList.pairs[at]; // TODO: if we were cool, we'd do something with the peer // reflexive address received // TODO: we're also supposed to do triggered checks. except // that currently we check everything anyway so this is not // relevant // check if there's a candidate already valid at = -1; for(int n = 0; n < checkList.pairs.count(); ++n) { if(checkList.pairs[n].local.componentId == pair.local.componentId && checkList.pairs[n].isValid) { at = n; break; } } pair.isValid = true; if(at == -1) { int at = findComponent(pair.local.componentId); Component &c = components[at]; if(c.lowOverhead) { printf("component is flagged for low overhead. setting up for %s;%d -> %s;%d\n", qPrintable(pair.local.addr.addr.toString()), pair.local.addr.port, qPrintable(pair.remote.addr.addr.toString()), pair.remote.addr.port); at = findLocalCandidate(pair.local.addr.addr, pair.local.addr.port); IceComponent::Candidate &cc = localCandidates[at]; c.ic->flagPathAsLowOverhead(cc.id, pair.remote.addr.addr, pair.remote.addr.port); } emit q->componentReady(pair.local.componentId - 1); } else { printf("component %d already active, not signalling\n", pair.local.componentId); } } }; 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::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::setLocalCandidateTrickle(bool enabled) { d->useTrickle = enabled; } void Ice176::start(Mode mode) { d->mode = mode; d->start(); } void Ice176::stop() { d->stop(); } 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); } 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; } } #include "ice176.moc" psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/ice176.h000066400000000000000000000115701342663516400235300ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef ICE176_H #define ICE176_H #include #include #include #include "turnclient.h" namespace QCA { class SecureArray; } namespace XMPP { class UdpPortReserver; class Ice176 : public QObject { Q_OBJECT public: enum Error { ErrorGeneric }; enum Mode { Initiator, Responder }; class LocalAddress { public: QHostAddress addr; int network; // -1 = unknown bool isVpn; LocalAddress() : network(-1), isVpn(false) { } }; class ExternalAddress { public: LocalAddress base; QHostAddress addr; int portBase; // -1 = same as base ExternalAddress() : portBase(-1) { } }; class Candidate { public: int component; QString foundation; int generation; QString id; QHostAddress ip; int network; // -1 = unknown int port; int priority; QString protocol; QHostAddress rel_addr; int rel_port; QHostAddress rem_addr; int rem_port; QString type; Candidate() : component(-1), generation(-1), network(-1), port(-1), priority(-1), rel_port(-1), rem_port(-1) { } }; Ice176(QObject *parent = 0); ~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 setComponentCount(int count); void setLocalCandidateTrickle(bool enabled); // default false void start(Mode mode); void stop(); QString localUfrag() const; QString localPassword() const; void setPeerUfrag(const QString &ufrag); void setPeerPassword(const QString &pass); void addRemoteCandidates(const QList &list); 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); 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 componentReady(int index); void readyRead(int componentIndex); void datagramsWritten(int componentIndex, int count); private: class Private; friend class Private; Private *d; }; } #endif psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/icecomponent.cpp000066400000000000000000000725061342663516400255560ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "icecomponent.h" #include #include #include #include "objectsession.h" #include "udpportreserver.h" #include "icelocaltransport.h" #include "iceturntransport.h" 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; 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; bool borrowedSocket; QHostAddress addr; IceLocalTransport *sock; int network; bool isVpn; bool started; bool stun_started; bool stun_finished, turn_finished; QHostAddress extAddr; bool ext_finished; LocalTransport() : qsock(0), borrowedSocket(false), sock(0), 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; Config pending; Config config; bool stopping; QList localLeap; QList localStun; IceTurnTransport *tt; QList localCandidates; QHash > channelPeers; bool useLocal; bool useStunBind; bool useStunRelayUdp; bool useStunRelayTcp; bool local_finished; int debugLevel; Private(IceComponent *_q) : QObject(_q), q(_q), sess(this), portReserver(0), stopping(false), tt(0), useLocal(true), useStunBind(true), useStunRelayUdp(true), useStunRelayTcp(true), local_finished(false), debugLevel(DL_None) { } ~Private() { QList socketsToReturn; for(int n = 0; n < localLeap.count(); ++n) { delete localLeap[n]->sock; if(localLeap[n]->borrowedSocket) socketsToReturn += localLeap[n]->qsock; else localLeap[n]->qsock->deleteLater(); } if(!socketsToReturn.isEmpty()) portReserver->returnSockets(socketsToReturn); qDeleteAll(localLeap); for(int n = 0; n < localStun.count(); ++n) delete localStun[n]->sock; qDeleteAll(localStun); delete tt; } void update(QList *socketList) { Q_ASSERT(!stopping); // for now, only allow setting localAddrs once if(!pending.localAddrs.isEmpty() && config.localAddrs.isEmpty()) { foreach(const Ice176::LocalAddress &la, pending.localAddrs) { // skip duplicate addrs if(findLocalAddr(la.addr) != -1) continue; if(!useLocal) { // skip out, but log the address in // case we need it for stun config.localAddrs += la; continue; } QUdpSocket *qsock = 0; if(socketList) qsock = takeFromSocketList(socketList, la.addr, this); bool borrowedSocket; if(qsock) { borrowedSocket = true; } else { // 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; } borrowedSocket = false; } int port = qsock->localPort(); config.localAddrs += la; LocalTransport *lt = new LocalTransport; lt->addr = la.addr; lt->qsock = qsock; lt->borrowedSocket = borrowedSocket; lt->sock = new IceLocalTransport(this); lt->sock->setDebugLevel((IceTransport::DebugLevel)debugLevel); lt->network = la.network; lt->isVpn = la.isVpn; connect(lt->sock, SIGNAL(started()), SLOT(lt_started())); connect(lt->sock, SIGNAL(stopped()), SLOT(lt_stopped())); connect(lt->sock, SIGNAL(addressesChanged()), SLOT(lt_addressesChanged())); connect(lt->sock, SIGNAL(error(int)), SLOT(lt_error(int))); connect(lt->sock, SIGNAL(debugLine(QString)), SLOT(lt_debugLine(QString))); localLeap += lt; 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; foreach(LocalTransport *lt, localLeap) { // already assigned an ext address? skip if(!lt->extAddr.isNull()) continue; QHostAddress laddr = lt->sock->localAddress(); int lport = lt->sock->localPort(); int at = -1; for(int n = 0; n < config.extAddrs.count(); ++n) { const Ice176::ExternalAddress &ea = config.extAddrs[n]; if(laddr.protocol() != QAbstractSocket::IPv6Protocol && ea.base.addr == laddr && (ea.portBase == -1 || ea.portBase == lport)) { at = n; break; } } if(at != -1) { lt->extAddr = config.extAddrs[at].addr; if(lt->started) need_doExt = true; } } if(need_doExt) sess.defer(this, "doExt"); } // only allow setting stun stuff once if(!pending.stunBindAddr.isNull() && config.stunBindAddr.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; } // localStun sockets created on demand if stun settings are // present, but only once (cannot be changed, for now) if(((useStunBind && !config.stunBindAddr.isNull()) || (useStunRelayUdp && !config.stunRelayUdpAddr.isNull() && !config.stunRelayUdpUser.isEmpty())) && !config.localAddrs.isEmpty() && localStun.isEmpty()) { foreach(const Ice176::LocalAddress &la, config.localAddrs) { // don't setup stun ports for ipv6 if(la.addr.protocol() == QAbstractSocket::IPv6Protocol) continue; LocalTransport *lt = new LocalTransport; lt->addr = la.addr; lt->sock = new IceLocalTransport(this); lt->sock->setDebugLevel((IceTransport::DebugLevel)debugLevel); lt->network = la.network; lt->isVpn = la.isVpn; connect(lt->sock, SIGNAL(started()), SLOT(lt_started())); connect(lt->sock, SIGNAL(stopped()), SLOT(lt_stopped())); connect(lt->sock, SIGNAL(addressesChanged()), SLOT(lt_addressesChanged())); connect(lt->sock, SIGNAL(error(int)), SLOT(lt_error(int))); connect(lt->sock, SIGNAL(debugLine(QString)), SLOT(lt_debugLine(QString))); localStun += lt; lt->sock->setClientSoftwareNameAndVersion(clientSoftware); lt->sock->start(la.addr); emit q->debugLine(QString("starting transport ") + la.addr.toString() + ";(dyn)" + " for component " + QString::number(id)); } } if((!config.stunBindAddr.isNull() || !config.stunRelayUdpAddr.isNull()) && !localStun.isEmpty()) { for(int n = 0; n < localStun.count(); ++n) { if(localStun[n]->started && !localStun[n]->stun_started) tryStun(n); } } if(useStunRelayTcp && !config.stunRelayTcpAddr.isNull() && !config.stunRelayTcpUser.isEmpty() && !tt) { tt = new IceTurnTransport(this); tt->setDebugLevel((IceTransport::DebugLevel)debugLevel); connect(tt, SIGNAL(started()), SLOT(tt_started())); connect(tt, SIGNAL(stopped()), SLOT(tt_stopped())); connect(tt, SIGNAL(error(int)), SLOT(tt_error(int))); connect(tt, SIGNAL(debugLine(QString)), SLOT(tt_debugLine(QString))); tt->setClientSoftwareNameAndVersion(clientSoftware); tt->setProxy(proxy); tt->setUsername(config.stunRelayTcpUser); tt->setPassword(config.stunRelayTcpPass); tt->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(localLeap.isEmpty() && localStun.isEmpty() && !local_finished) { local_finished = true; sess.defer(q, "localFinished"); } } void stop() { Q_ASSERT(!stopping); stopping = true; // nothing to stop? if(allStopped()) { sess.defer(this, "postStop"); return; } foreach(LocalTransport *lt, localLeap) lt->sock->stop(); foreach(LocalTransport *lt, localStun) lt->sock->stop(); if(tt) tt->stop(); } int peerReflexivePriority(const IceTransport *iceTransport, int path) const { int addrAt = -1; const IceLocalTransport *lt = qobject_cast(iceTransport); if(lt) { bool isLocalLeap = false; addrAt = findLocalTransport(lt, &isLocalLeap); if(addrAt != -1 && path == 1) { // lower priority, but not as far as IceTurnTransport addrAt += 512; } } else if(qobject_cast(iceTransport) == tt) { // lower priority by making it seem like the last nic addrAt = 1024; } Q_ASSERT(addrAt != -1); 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); } } 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 = 0) { for(int n = 0; n < socketList->count(); ++n) { if((*socketList)[n]->localAddress() == addr) { QUdpSocket *sock = socketList->takeAt(n); sock->setParent(parent); return sock; } } return 0; } int getId() const { for(int n = 0;; ++n) { bool found = false; foreach(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; } int findLocalTransport(const IceLocalTransport *sock, bool *isLocalLeap) const { for(int n = 0; n < localLeap.count(); ++n) { if(localLeap[n]->sock == sock) { *isLocalLeap = true; return n; } } for(int n = 0; n < localStun.count(); ++n) { if(localStun[n]->sock == sock) { *isLocalLeap = false; return n; } } return -1; } void tryStun(int at) { LocalTransport *lt = localStun[at]; bool atLeastOne = false; if(useStunBind && !config.stunBindAddr.isNull()) { atLeastOne = true; lt->sock->setStunBindService(config.stunBindAddr, config.stunBindPort); } if(useStunRelayUdp && !config.stunRelayUdpAddr.isNull() && !config.stunRelayUdpUser.isEmpty()) { atLeastOne = true; lt->sock->setStunRelayService(config.stunRelayUdpAddr, config.stunRelayUdpPort, config.stunRelayUdpUser, config.stunRelayUdpPass); } Q_ASSERT(atLeastOne); if(!atLeastOne) abort(); lt->stun_started = true; lt->sock->stunStart(); } void ensureExt(LocalTransport *lt, int addrAt) { if(!lt->extAddr.isNull() && !lt->ext_finished) { CandidateInfo ci; 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; Candidate c; c.id = getId(); c.info = ci; c.iceTransport = lt->sock; c.path = 0; localCandidates += c; lt->ext_finished = true; emit q->candidateAdded(c); } } void removeLocalCandidates(const IceTransport *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; } } } bool allStopped() const { if(localLeap.isEmpty() && localStun.isEmpty() && !tt) return true; else return false; } void tryStopped() { if(allStopped()) postStop(); } private slots: void doExt() { if(stopping) return; ObjectSessionWatcher watch(&sess); foreach(LocalTransport *lt, localLeap) { if(lt->started) { int addrAt = findLocalAddr(lt->addr); Q_ASSERT(addrAt != -1); ensureExt(lt, addrAt); if(!watch.isValid()) return; } } } void postStop() { stopping = false; emit q->stopped(); } void lt_started() { IceLocalTransport *sock = static_cast(sender()); bool isLocalLeap = false; int at = findLocalTransport(sock, &isLocalLeap); Q_ASSERT(at != -1); LocalTransport *lt; if(isLocalLeap) lt = localLeap[at]; else lt = localStun[at]; lt->started = true; int addrAt = findLocalAddr(lt->addr); Q_ASSERT(addrAt != -1); ObjectSessionWatcher watch(&sess); if(useLocal && isLocalLeap) { CandidateInfo ci; 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; Candidate c; c.id = getId(); c.info = ci; c.iceTransport = sock; c.path = 0; localCandidates += c; emit q->candidateAdded(c); if(!watch.isValid()) return; ensureExt(lt, addrAt); if(!watch.isValid()) return; } if(!isLocalLeap && !lt->stun_started) tryStun(at); bool allFinished = true; foreach(const LocalTransport *lt, localLeap) { if(!lt->started) { allFinished = false; break; } } if(allFinished) { foreach(const LocalTransport *lt, localStun) { if(!lt->started) { allFinished = false; break; } } } if(allFinished && !local_finished) { local_finished = true; emit q->localFinished(); } } void lt_stopped() { IceLocalTransport *sock = static_cast(sender()); bool isLocalLeap = false; int at = findLocalTransport(sock, &isLocalLeap); Q_ASSERT(at != -1); LocalTransport *lt; if(isLocalLeap) lt = localLeap[at]; else lt = localStun[at]; ObjectSessionWatcher watch(&sess); removeLocalCandidates(lt->sock); if(!watch.isValid()) return; delete lt->sock; lt->sock = 0; if(isLocalLeap) { if(lt->borrowedSocket) portReserver->returnSockets(QList() << lt->qsock); else lt->qsock->deleteLater(); delete lt; localLeap.removeAt(at); } else { delete lt; localStun.removeAt(at); } tryStopped(); } void lt_addressesChanged() { IceLocalTransport *sock = static_cast(sender()); bool isLocalLeap = false; int at = findLocalTransport(sock, &isLocalLeap); Q_ASSERT(at != -1); // leap does not use stun, so we should not get this signal Q_ASSERT(!isLocalLeap); LocalTransport *lt = localStun[at]; 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 foreach(LocalTransport *i, localLeap) { 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; } } } CandidateInfo ci; ci.addr.addr = lt->sock->serverReflexiveAddress(); ci.addr.port = lt->sock->serverReflexivePort(); ci.type = ServerReflexiveType; ci.componentId = id; ci.priority = choose_default_priority(ci.type, 65535 - addrAt, lt->isVpn, ci.componentId); // stun is only used on non-leap sockets, but we don't // announce non-leap local candidates, so make the // base the same as the srflx //ci.base.addr = lt->sock->localAddress(); //ci.base.port = lt->sock->localPort(); ci.base = ci.addr; ci.network = lt->network; Candidate c; c.id = getId(); c.info = ci; c.iceTransport = sock; c.path = 0; localCandidates += c; lt->stun_finished = true; emit q->candidateAdded(c); if(!watch.isValid()) return; } if(!lt->sock->relayedAddress().isNull() && !lt->turn_finished) { CandidateInfo ci; ci.addr.addr = lt->sock->relayedAddress(); ci.addr.port = lt->sock->relayedPort(); ci.type = RelayedType; ci.componentId = id; ci.priority = choose_default_priority(ci.type, 65535 - addrAt, lt->isVpn, ci.componentId); ci.base.addr = lt->sock->serverReflexiveAddress(); ci.base.port = lt->sock->serverReflexivePort(); ci.network = lt->network; Candidate c; c.id = getId(); c.info = ci; c.iceTransport = sock; c.path = 1; localCandidates += c; lt->turn_finished = true; emit q->candidateAdded(c); } } void lt_error(int e) { Q_UNUSED(e); IceLocalTransport *sock = static_cast(sender()); bool isLocalLeap = false; int at = findLocalTransport(sock, &isLocalLeap); Q_ASSERT(at != -1); LocalTransport *lt; if(isLocalLeap) lt = localLeap[at]; else lt = localStun[at]; ObjectSessionWatcher watch(&sess); removeLocalCandidates(lt->sock); if(!watch.isValid()) return; delete lt->sock; lt->sock = 0; if(isLocalLeap) { if(lt->borrowedSocket) portReserver->returnSockets(QList() << lt->qsock); else lt->qsock->deleteLater(); delete lt; localLeap.removeAt(at); } else { delete lt; localStun.removeAt(at); } } 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; CandidateInfo ci; ci.addr.addr = tt->relayedAddress(); ci.addr.port = tt->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 Candidate c; c.id = getId(); c.info = ci; c.iceTransport = tt; c.path = 0; localCandidates += c; emit q->candidateAdded(c); } void tt_stopped() { ObjectSessionWatcher watch(&sess); removeLocalCandidates(tt); if(!watch.isValid()) return; delete tt; tt = 0; tryStopped(); } void tt_error(int e) { Q_UNUSED(e); ObjectSessionWatcher watch(&sess); removeLocalCandidates(tt); if(!watch.isValid()) return; delete tt; tt = 0; } 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; } 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; } 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(const IceTransport *iceTransport, int path) const { return d->peerReflexivePriority(iceTransport, path); } void IceComponent::flagPathAsLowOverhead(int id, const QHostAddress &addr, int port) { return d->flagPathAsLowOverhead(id, addr, port); } void IceComponent::setDebugLevel(DebugLevel level) { d->debugLevel = level; foreach(const Private::LocalTransport *lt, d->localLeap) lt->sock->setDebugLevel((IceTransport::DebugLevel)level); foreach(const Private::LocalTransport *lt, d->localStun) lt->sock->setDebugLevel((IceTransport::DebugLevel)level); if(d->tt) d->tt->setDebugLevel((IceTransport::DebugLevel)level); } } #include "icecomponent.moc" psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/icecomponent.h000066400000000000000000000122431342663516400252130ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef ICECOMPONENT_H #define ICECOMPONENT_H #include #include "turnclient.h" #include "icetransport.h" #include "ice176.h" 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); } }; class CandidateInfo { public: TransportAddress addr; CandidateType type; int priority; QString foundation; int componentId; TransportAddress base; TransportAddress related; QString id; int network; }; 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 info; // note that these may be the same for multiple candidates IceTransport *iceTransport; int path; }; enum DebugLevel { DL_None, DL_Info, DL_Packet }; IceComponent(int id, QObject *parent = 0); ~IceComponent(); int id() const; void setClientSoftwareNameAndVersion(const QString &str); void setProxy(const TurnClient::Proxy &proxy); void setPortReserver(UdpPortReserver *portReserver); // 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); void setUseStunBind(bool enabled); void setUseStunRelayUdp(bool enabled); void setUseStunRelayTcp(bool enabled); // if socketList is not null then port reserver must be set void update(QList *socketList = 0); void stop(); // prflx priority to use when replying from this transport/path int peerReflexivePriority(const IceTransport *iceTransport, int path) const; 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(); 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 psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/icelocaltransport.cpp000066400000000000000000000520621342663516400266160ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "icelocaltransport.h" #include #include #include #include "objectsession.h" #include "stunmessage.h" #include "stuntransaction.h" #include "stunbinding.h" #include "stunallocate.h" #include "turnclient.h" // 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 = 0) : 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(0); QUdpSocket *out = sock; sock = 0; return out; } QHostAddress localAddress() const { return sock->localAddress(); } quint16 localPort() const { return sock->localPort(); } bool hasPendingDatagrams() const { return sock->hasPendingDatagrams(); } QByteArray readDatagram(QHostAddress *address = 0, quint16 *port = 0) { 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 *pool; StunBinding *stunBinding; TurnClient *turn; bool turnActivated; QHostAddress addr; int port; QHostAddress refAddr; int refPort; 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(0), sock(0), pool(0), stunBinding(0), turn(0), 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 = 0; delete turn; turn = 0; turnActivated = false; if(sock) { if(extSock) { sock->release(); extSock = 0; } delete sock; sock = 0; } addr = QHostAddress(); port = -1; refAddr = QHostAddress(); refPort = -1; 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); Q_ASSERT(!stopping); stopping = true; if(turn) turn->close(); else sess.defer(this, "postStop"); } void stunStart() { Q_ASSERT(!pool); pool = new StunTransactionPool(StunTransaction::Udp, this); pool->setDebugLevel((StunTransactionPool::DebugLevel)debugLevel); connect(pool, SIGNAL(outgoingMessage(QByteArray,QHostAddress,int)), SLOT(pool_outgoingMessage(QByteArray,QHostAddress,int))); connect(pool, SIGNAL(needAuthParams()), SLOT(pool_needAuthParams())); connect(pool, SIGNAL(debugLine(QString)), SLOT(pool_debugLine(QString))); pool->setLongTermAuthEnabled(true); if(!stunUser.isEmpty()) { pool->setUsername(stunUser); pool->setPassword(stunPass); } if(!stunBindAddr.isNull()) { stunBinding = new StunBinding(pool); connect(stunBinding, SIGNAL(success()), SLOT(binding_success())); connect(stunBinding, SIGNAL(error(XMPP::StunBinding::Error)), SLOT(binding_error(XMPP::StunBinding::Error))); stunBinding->start(stunBindAddr, stunBindPort); } if(!stunRelayAddr.isNull()) { do_turn(); } } void do_turn() { 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, 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 0; } 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 = 0; // 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; 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; QList rreads; 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()) { foreach(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; 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 binding_success() { refAddr = stunBinding->reflexiveAddress(); refPort = stunBinding->reflexivePort(); delete stunBinding; stunBinding = 0; emit q->addressesChanged(); } void binding_error(XMPP::StunBinding::Error e) { Q_UNUSED(e); delete stunBinding; stunBinding = 0; // don't report any error //if(stunType == IceLocalTransport::Basic || (stunType == IceLocalTransport::Auto && !turn)) // emit q->addressesChanged(); } 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 = 0; 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(); } 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 = 0; 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; } 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::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 = 0; 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); } } #include "icelocaltransport.moc" psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/icelocaltransport.h000066400000000000000000000060271342663516400262630ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef ICELOCALTRANSPORT_H #define ICELOCALTRANSPORT_H #include #include #include "icetransport.h" 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 { Q_OBJECT public: enum Error { ErrorBind = ErrorCustom }; IceLocalTransport(QObject *parent = 0); ~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); // obtain relay / reflexive void stunStart(); QHostAddress localAddress() const; int localPort() const; QHostAddress serverReflexiveAddress() const; int serverReflexivePort() const; QHostAddress relayedAddress() const; int relayedPort() 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); 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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/icetransport.cpp000066400000000000000000000016721342663516400256040ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "icetransport.h" namespace XMPP { IceTransport::IceTransport(QObject *parent) : QObject(parent) { } IceTransport::~IceTransport() { } } psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/icetransport.h000066400000000000000000000035271342663516400252520ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef ICETRANSPORT_H #define ICETRANSPORT_H #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 = 0); ~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; signals: void started(); void stopped(); 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); }; } #endif psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/iceturntransport.cpp000066400000000000000000000144721342663516400265170ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "iceturntransport.h" #include #include "stunallocate.h" 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; int debugLevel; 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; 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; } 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); } } #include "iceturntransport.moc" psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/iceturntransport.h000066400000000000000000000042141342663516400261550ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef ICETURNTRANSPORT_H #define ICETURNTRANSPORT_H #include #include #include #include "turnclient.h" #include "icetransport.h" namespace XMPP { // for the turn transport, only path 0 is used class IceTurnTransport : public IceTransport { Q_OBJECT public: enum Error { ErrorTurn = ErrorCustom }; IceTurnTransport(QObject *parent = 0); ~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; 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); private: class Private; friend class Private; Private *d; }; } #endif psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/legacy/000077500000000000000000000000001342663516400236215ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/legacy/legacy.pri000066400000000000000000000002451342663516400256020ustar00rootroot00000000000000HEADERS += \ $$PWD/ndns.h \ $$PWD/srvresolver.h \ $$PWD/servsock.h SOURCES += \ $$PWD/ndns.cpp \ $$PWD/srvresolver.cpp \ $$PWD/servsock.cpp psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/legacy/ndns.cpp000066400000000000000000000071241342663516400252730ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ //! \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.554/iris/src/irisnet/noncore/legacy/ndns.h000066400000000000000000000026631342663516400247430ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef CS_NDNS_H #define CS_NDNS_H #include #include #include "netnames.h" // CS_NAMESPACE_BEGIN class NDns : public QObject { Q_OBJECT public: NDns(QObject *parent=0); ~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 psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/legacy/servsock.cpp000066400000000000000000000050541342663516400261700ustar00rootroot00000000000000/* * servsock.cpp - simple wrapper to QServerSocket * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "servsock.h" // CS_NAMESPACE_BEGIN //---------------------------------------------------------------------------- // ServSock //---------------------------------------------------------------------------- class ServSock::Private { public: Private() {} ServSockSignal *serv; }; ServSock::ServSock(QObject *parent) :QObject(parent) { d = new Private; d->serv = 0; } ServSock::~ServSock() { stop(); delete d; } bool ServSock::isActive() const { return (d->serv ? true: false); } bool ServSock::listen(quint16 port) { stop(); d->serv = new ServSockSignal(this); if(!d->serv->listen(QHostAddress::Any, port)) { delete d->serv; d->serv = 0; return false; } connect(d->serv, SIGNAL(connectionReady(qintptr)), SLOT(sss_connectionReady(qintptr))); return true; } void ServSock::stop() { delete d->serv; d->serv = 0; } int ServSock::port() const { if(d->serv) return d->serv->serverPort(); else return -1; } QHostAddress ServSock::address() const { if(d->serv) return d->serv->serverAddress(); else return QHostAddress(); } void ServSock::sss_connectionReady(qintptr s) { connectionReady(s); } //---------------------------------------------------------------------------- // ServSockSignal //---------------------------------------------------------------------------- ServSockSignal::ServSockSignal(QObject *parent) :QTcpServer(parent) { setMaxPendingConnections(16); } void ServSockSignal::incomingConnection(qintptr socketDescriptor) { // TODO all these stuff was necessary with Qt3. For now it's better to use pending QTcpSocket object connectionReady(socketDescriptor); } // CS_NAMESPACE_END psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/legacy/servsock.h000066400000000000000000000031201342663516400256250ustar00rootroot00000000000000/* * servsock.h - simple wrapper to QServerSocket * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef CS_SERVSOCK_H #define CS_SERVSOCK_H #include #include // CS_NAMESPACE_BEGIN class ServSock : public QObject { Q_OBJECT public: ServSock(QObject *parent=nullptr); ~ServSock(); bool isActive() const; bool listen(quint16 port); void stop(); int port() const; QHostAddress address() const; signals: void connectionReady(qintptr); private slots: void sss_connectionReady(qintptr); private: class Private; Private *d; }; class ServSockSignal : public QTcpServer { Q_OBJECT public: ServSockSignal(QObject *parent = nullptr); signals: void connectionReady(qintptr); protected: // reimplemented void incomingConnection(qintptr socketDescriptor); }; // CS_NAMESPACE_END #endif psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/legacy/srvresolver.cpp000066400000000000000000000160541342663516400267270ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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.554/iris/src/irisnet/noncore/legacy/srvresolver.h000066400000000000000000000040371342663516400263720ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef CS_SRVRESOLVER_H #define CS_SRVRESOLVER_H #include #include #include "netnames.h" // 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=0); ~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 psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/noncore.pri000066400000000000000000000020761342663516400245410ustar00rootroot00000000000000IRIS_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/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 SOURCES += \ $$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 INCLUDEPATH += $$PWD/legacy include(legacy/legacy.pri) psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/noncore.pro000066400000000000000000000004421342663516400245420ustar00rootroot00000000000000IRIS_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.554/iris/src/irisnet/noncore/processquit.cpp000066400000000000000000000134001342663516400254400ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "processquit.h" #ifndef NO_IRISNET # include "irisnetglobal_p.h" #endif #ifdef QT_GUI_LIB # include #endif #ifdef Q_OS_WIN # include #endif #ifdef Q_OS_UNIX # include # include #endif namespace { // safeobj stuff, from qca void releaseAndDeleteLater(QObject *owner, QObject *obj) { obj->disconnect(owner); obj->setParent(0); obj->deleteLater(); } 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; }; } #ifndef NO_IRISNET namespace XMPP { #endif Q_GLOBAL_STATIC(QMutex, pq_mutex) static ProcessQuit *g_pq = 0; 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, NULL, &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, 0); } void unixWatchRemove(int sig) { struct sigaction sa; sigaction(sig, NULL, &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, 0); } #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 = 0; } #ifndef NO_IRISNET } #endif #include "processquit.moc" psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/processquit.h000066400000000000000000000105601342663516400251110ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #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 psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/stunallocate.cpp000066400000000000000000001130301342663516400255550ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "stunallocate.h" #include #include #include #include #include "objectsession.h" #include "stunutil.h" #include "stunmessage.h" #include "stuntypes.h" #include "stuntransaction.h" // 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(0); 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 *pool; StunTransaction *trans; QHostAddress stunAddr; int stunPort; QHostAddress addr; bool active; enum Error { ErrorGeneric, ErrorProtocol, ErrorCapacity, ErrorForbidden, ErrorRejected, ErrorTimeout }; StunAllocatePermission(StunTransactionPool *_pool, const QHostAddress &_addr) : QObject(_pool), pool(_pool), trans(0), 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 = 0; 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, 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 = 0; 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 *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 *_pool, int _channelId, const QHostAddress &_addr, int _port) : QObject(_pool), pool(_pool), trans(0), 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 = 0; 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, 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 = 0; 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 *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 permsOut; QList channelsOut; int erroringCode; QString erroringString; Private(StunAllocate *_q) : QObject(_q), q(_q), sess(this), pool(0), trans(0), 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; 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 = 0; 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, 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 = 0; 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(); 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 = 0; 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; } 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(); } } #include "stunallocate.moc" psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/stunallocate.h000066400000000000000000000064471342663516400252370ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #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(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 = 0, int *port = 0); QByteArray decode(const StunMessage &encoded, QHostAddress *addr = 0, int *port = 0); 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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/stunbinding.cpp000066400000000000000000000164621342663516400254160ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "stunbinding.h" #include #include "stunmessage.h" #include "stuntypes.h" #include "stuntransaction.h" namespace XMPP { class StunBinding::Private : public QObject { Q_OBJECT public: StunBinding *q; StunTransactionPool *pool; StunTransaction *trans; QHostAddress stunAddr; int stunPort; QHostAddress addr; int port; QString errorString; bool use_extPriority, use_extIceControlling, use_extIceControlled; quint32 extPriority; bool extUseCandidate; quint64 extIceControlling, extIceControlled; QString stuser, stpass; bool fpRequired; Private(StunBinding *_q) : QObject(_q), q(_q), pool(0), trans(0), use_extPriority(false), use_extIceControlling(false), use_extIceControlled(false), extUseCandidate(false), fpRequired(false) { } ~Private() { delete trans; } void start(const QHostAddress &_addr = QHostAddress(), int _port = -1) { Q_ASSERT(!trans); stunAddr = _addr; stunPort = _port; 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))); if(!stuser.isEmpty()) { trans->setShortTermUsername(stuser); trans->setShortTermPassword(stpass); } trans->setFingerprintRequired(fpRequired); trans->start(pool, stunAddr, stunPort); } 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) { delete trans; trans = 0; 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) { delete trans; trans = 0; 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; } StunBinding::~StunBinding() { delete d; } void StunBinding::setPriority(quint32 i) { d->use_extPriority = true; d->extPriority = i; } void StunBinding::setUseCandidate(bool enabled) { d->extUseCandidate = enabled; } 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); } QHostAddress StunBinding::reflexiveAddress() const { return d->addr; } int StunBinding::reflexivePort() const { return d->port; } QString StunBinding::errorString() const { return d->errorString; } } #include "stunbinding.moc" psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/stunbinding.h000066400000000000000000000037031342663516400250550ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #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); void setUseCandidate(bool enabled); 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 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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/stunmessage.cpp000066400000000000000000000472261342663516400254320ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "stunmessage.h" #include #include #include "stunutil.h" #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 = 0) { 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(0) { } 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); foreach(const Attribute &i, d->attribs) { if(i.type == type) return i.value; } return QByteArray(); } 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); foreach(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(); } } psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/stunmessage.h000066400000000000000000000060101342663516400250610ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #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; 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 = 0, 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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/stuntransaction.cpp000066400000000000000000000520651342663516400263300ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "stuntransaction.h" #include #include #include #include #include #include "stunutil.h" #include "stunmessage.h" #include "stuntypes.h" #include "stunbinding.h" 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 *pool; bool active; 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; QTime time; StunTransactionPrivate(StunTransaction *_q) : QObject(_q), q(_q), pool(0), 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(0); t->deleteLater(); } void start(StunTransactionPool *_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(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; } 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(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() { 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, toAddress, toPort); } 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, QObject *parent) : QObject(parent) { 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; foreach(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) { // 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; } } #include "stuntransaction.moc" psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/stuntransaction.h000066400000000000000000000162761342663516400260010ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef STUNTRANSACTION_H #define STUNTRANSACTION_H #include #include #include namespace QCA { class SecureArray; } namespace XMPP { class StunMessage; class StunTransactionPrivate; class StunTransactionPool; class StunTransactionPoolPrivate; // 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 = 0); ~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); // 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 { Q_OBJECT public: enum DebugLevel { DL_None, DL_Info, DL_Packet }; StunTransactionPool(StunTransaction::Mode mode, QObject *parent = 0); ~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 = 0, 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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/stuntypes.cpp000066400000000000000000000432631342663516400251470ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "stuntypes.h" #include #include #include "stunutil.h" #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, 0 } }; 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, 0 } }; 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; foreach(quint16 i, typeList) strList += QString().sprintf("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().sprintf("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()) { foreach(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().sprintf(" 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))); } } } psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/stuntypes.h000066400000000000000000000126111342663516400246050ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef STUNTYPES_H #define STUNTYPES_H #include #include #include #include #include "stunmessage.h" 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); } } #endif psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/stunutil.cpp000066400000000000000000000040501342663516400247470ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #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; } } } psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/stunutil.h000066400000000000000000000022441342663516400244170ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #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); } } #endif psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/turnclient.cpp000066400000000000000000000710021342663516400252500ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "turnclient.h" #include #include "stuntypes.h" #include "stunmessage.h" #include "stuntransaction.h" #include "stunallocate.h" #include "objectsession.h" #include "bytestream.h" #include "bsocket.h" #include "httpconnect.h" #include "socks.h" 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; QHostAddress serverAddr; int serverPort; ObjectSession sess; ByteStream *bs; QCA::TLS *tls; bool tlsHandshaken; QByteArray inStream; bool udp; StunTransactionPool *pool; StunAllocate *allocate; bool allocateStarted; QString user; QCA::SecureArray pass; QString realm; int retryCount; QString errorString; int debugLevel; 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(0), tls(0), udp(false), pool(0), allocate(0), retryCount(0), debugLevel(TurnClient::DL_None), writtenBytes(0), stopping(false), outPendingWrite(0) { } ~Private() { cleanup(); } void cleanup() { delete allocate; allocate = 0; // in udp mode, we don't own the pool if(!udp) delete pool; pool = 0; delete tls; tls = 0; 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 = 0; // in udp mode, we don't own the pool if(!udp) delete pool; pool = 0; if(udp) sess.defer(q, "closed"); else do_transport_close(); } } void do_transport_close() { if(tls && tlsHandshaken) { tls->close(); } else { delete tls; tls = 0; 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 = new StunTransactionPool(StunTransaction::Tcp, this); pool->setDebugLevel((StunTransactionPool::DebugLevel)debugLevel); connect(pool, SIGNAL(outgoingMessage(QByteArray,QHostAddress,int)), SLOT(pool_outgoingMessage(QByteArray,QHostAddress,int))); connect(pool, SIGNAL(needAuthParams()), SLOT(pool_needAuthParams())); connect(pool, 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); 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; foreach(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); foreach(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 *tmp_pool = pool; pool = 0; 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 = 0; 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()); 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 = 0; // in udp mode, we don't own the pool if(!udp) delete pool; pool = 0; 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; 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(); } 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); } } #include "turnclient.moc" psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/turnclient.h000066400000000000000000000123151342663516400247170ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef TURNCLIENT_H #define TURNCLIENT_H #include #include #include #include namespace QCA { class SecureArray; } namespace XMPP { class StunTransactionPool; class StunAllocate; 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; QString v_user, v_pass; }; TurnClient(QObject *parent = 0); ~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); // 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 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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/udpportreserver.cpp000066400000000000000000000241241342663516400263370ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #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 { foreach(const QUdpSocket *sock, sockList) { if(sock->localAddress() == addr) return true; } return false; } }; UdpPortReserver *q; QList addrs; QList ports; // sorted QList items; // in order sorted by port Private(UdpPortReserver *_q) : QObject(_q), q(_q) { } ~Private() { bool lendingAny = false; foreach(const Item &i, items) { if(i.lent) { lendingAny = true; break; } } Q_ASSERT(!lendingAny); if(lendingAny) abort(); foreach(const Item &i, items) { foreach(QUdpSocket *sock, i.sockList) sock->deleteLater(); } } void updateAddresses(const QList &newAddrs) { addrs = newAddrs; tryBind(); tryCleanup(); } void updatePorts(const QList &newPorts) { QList added; foreach(int x, newPorts) { bool found = false; foreach(const Item &i, items) { if(i.port == x) { found = true; break; } } if(!found) added += x; } ports = newPorts; // keep ports in sorted order qSort(ports); foreach(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; foreach(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) { foreach(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(0, 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; foreach(const QHostAddress &a, addrs) { if(!i.haveAddress(a)) neededAddrs += a; } foreach(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)) { foreach(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; foreach(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; foreach(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); } } #include "udpportreserver.moc" psi-plus-snapshots-1.4.554/iris/src/irisnet/noncore/udpportreserver.h000066400000000000000000000050221342663516400260000ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #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 = 0); ~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 = 0); void returnSockets(const QList &sockList); private: class Private; Private *d; }; } #endif psi-plus-snapshots-1.4.554/iris/src/jdns/000077500000000000000000000000001342663516400201735ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/jdns/CMakeLists.txt000066400000000000000000000141761342663516400227440ustar00rootroot00000000000000project(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.554/iris/src/jdns/COPYING000066400000000000000000000020701342663516400212250ustar00rootroot00000000000000Copyright (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.554/iris/src/jdns/JDnsConfig.cmake.in000066400000000000000000000012501342663516400235640ustar00rootroot00000000000000# 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.554/iris/src/jdns/JDnsConfigVersion.cmake.in000066400000000000000000000007161342663516400251400ustar00rootroot00000000000000set(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.554/iris/src/jdns/QJDnsConfig.cmake.in000066400000000000000000000014301342663516400237050ustar00rootroot00000000000000if(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.554/iris/src/jdns/QJDnsConfigVersion.cmake.in000066400000000000000000000007161342663516400252610ustar00rootroot00000000000000set(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.554/iris/src/jdns/README.md000066400000000000000000000066541342663516400214650ustar00rootroot00000000000000### 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.554/iris/src/jdns/TODO000066400000000000000000000017171342663516400206710ustar00rootroot00000000000000(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.554/iris/src/jdns/cmake_uninstall.cmake.in000066400000000000000000000013351342663516400247550ustar00rootroot00000000000000if(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.554/iris/src/jdns/include/000077500000000000000000000000001342663516400216165ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/jdns/include/jdns/000077500000000000000000000000001342663516400225545ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/jdns/include/jdns/jdns.h000066400000000000000000000416131342663516400236700ustar00rootroot00000000000000/* * 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 psi-plus-snapshots-1.4.554/iris/src/jdns/include/jdns/jdns_export.h000066400000000000000000000032031342663516400252620ustar00rootroot00000000000000/* * 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 psi-plus-snapshots-1.4.554/iris/src/jdns/include/jdns/qjdns.h000066400000000000000000000075541342663516400240570ustar00rootroot00000000000000/* * 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 psi-plus-snapshots-1.4.554/iris/src/jdns/include/jdns/qjdnsshared.h000066400000000000000000000523461342663516400252450ustar00rootroot00000000000000/* * 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 QJDnsSharedPrivate; class QJDnsSharedRequestPrivate; class QJDnsSharedDebugPrivate; /** \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 psi-plus-snapshots-1.4.554/iris/src/jdns/jdns.pc.in000066400000000000000000000005121342663516400220600ustar00rootroot00000000000000prefix=@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.554/iris/src/jdns/jdns.pri000066400000000000000000000014731342663516400216520ustar00rootroot00000000000000# 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.554/iris/src/jdns/package.sh000077500000000000000000000005011342663516400221210ustar00rootroot00000000000000#!/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.554/iris/src/jdns/qjdns.pc.in000066400000000000000000000005151342663516400222440ustar00rootroot00000000000000prefix=@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.554/iris/src/jdns/src/000077500000000000000000000000001342663516400207625ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/jdns/src/CMakeLists.txt000066400000000000000000000001241342663516400235170ustar00rootroot00000000000000add_subdirectory(jdns) if(BUILD_QJDNS) add_subdirectory(qjdns) endif(BUILD_QJDNS) psi-plus-snapshots-1.4.554/iris/src/jdns/src/jdns/000077500000000000000000000000001342663516400217205ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/jdns/src/jdns/CMakeLists.txt000066400000000000000000000030551342663516400244630ustar00rootroot00000000000000set(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(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.554/iris/src/jdns/src/jdns/jdns.c000066400000000000000000002677371342663516400230500ustar00rootroot00000000000000/* * 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 #include "jdns_packet.h" #include "jdns_mdnsd.h" #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.554/iris/src/jdns/src/jdns/jdns_mdnsd.c000066400000000000000000001013751342663516400242160ustar00rootroot00000000000000/* * 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.554/iris/src/jdns/src/jdns/jdns_mdnsd.h000066400000000000000000000107341342663516400242210ustar00rootroot00000000000000/* * 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 psi-plus-snapshots-1.4.554/iris/src/jdns/src/jdns/jdns_p.h000066400000000000000000000066451342663516400233610ustar00rootroot00000000000000/* * 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 #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(sun) || defined(__sun) # define JDNS_OS_SOLARIS #elif defined(__APPLE__) && (defined(__GNUC__) || defined(__xlC__) || defined(__xlc__)) # define JDNS_OS_MAC #endif #ifdef JDNS_OS_WIN # include #endif #ifdef JDNS_OS_UNIX # include # include #endif #include "jdns.h" #include "jdns_packet.h" // 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 psi-plus-snapshots-1.4.554/iris/src/jdns/src/jdns/jdns_packet.c000066400000000000000000000634721342663516400243650ustar00rootroot00000000000000/* * 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.554/iris/src/jdns/src/jdns/jdns_packet.h000066400000000000000000000102561342663516400243620ustar00rootroot00000000000000/* * 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 psi-plus-snapshots-1.4.554/iris/src/jdns/src/jdns/jdns_sys.c000066400000000000000000000567271342663516400237410ustar00rootroot00000000000000/* * 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_WIN # include #endif #ifdef JDNS_OS_UNIX # include # include # include # 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) # 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.554/iris/src/jdns/src/jdns/jdns_util.c000066400000000000000000001165351342663516400240720ustar00rootroot00000000000000/* * 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.554/iris/src/jdns/src/qjdns/000077500000000000000000000000001342663516400221015ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/jdns/src/qjdns/CMakeLists.txt000066400000000000000000000036131342663516400246440ustar00rootroot00000000000000set(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.554/iris/src/jdns/src/qjdns/qjdns.cpp000066400000000000000000000613401342663516400237300ustar00rootroot00000000000000/* * 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 #include "qjdns_sock.h" // 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.554/iris/src/jdns/src/qjdns/qjdns_p.h000066400000000000000000000075101342663516400237130ustar00rootroot00000000000000/* * 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 QUdpSocket; class QTimer; 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 psi-plus-snapshots-1.4.554/iris/src/jdns/src/qjdns/qjdns_sock.cpp000066400000000000000000000105141342663516400247440ustar00rootroot00000000000000/* * 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_WIN # include # include #endif #ifdef Q_OS_UNIX # include # include # include # include # include # include # include # include #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 # 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 #endif 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) { 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; } 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 Q_UNUSED(s); Q_UNUSED(addr); Q_UNUSED(errorCode); return false; #endif } bool qjdns_sock_setTTL4(int s, int ttl) { 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; } 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 Q_UNUSED(s); Q_UNUSED(ttl); return false; #endif } 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.554/iris/src/jdns/src/qjdns/qjdns_sock.h000066400000000000000000000026541342663516400244170ustar00rootroot00000000000000/* * 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 psi-plus-snapshots-1.4.554/iris/src/jdns/src/qjdns/qjdnsshared.cpp000066400000000000000000001006611342663516400251170ustar00rootroot00000000000000/* * 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) { QString str; str.sprintf("%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.554/iris/src/jdns/src/qjdns/qjdnsshared_p.h000066400000000000000000000134501342663516400251020ustar00rootroot00000000000000/* * 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 psi-plus-snapshots-1.4.554/iris/src/jdns/tools/000077500000000000000000000000001342663516400213335ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/jdns/tools/jdns/000077500000000000000000000000001342663516400222715ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/jdns/tools/jdns/CMakeLists.txt000066400000000000000000000020341342663516400250300ustar00rootroot00000000000000set(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.554/iris/src/jdns/tools/jdns/main.cpp000066400000000000000000000412031342663516400237210ustar00rootroot00000000000000/* * 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 #include #include "qjdns.h" 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.554/iris/src/jdns/tools/jdns/main.h000066400000000000000000000035251342663516400233730ustar00rootroot00000000000000/* * 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.554/iris/src/libbase.pri000066400000000000000000000002571342663516400213560ustar00rootroot00000000000000IRIS_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.554/iris/src/src.pro000066400000000000000000000002661342663516400205520ustar00rootroot00000000000000TEMPLATE = 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.554/iris/src/xmpp/000077500000000000000000000000001342663516400202215ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/xmpp/CMakeLists.txt000066400000000000000000000100001342663516400227500ustar00rootroot00000000000000add_definitions(-DXMPP_TEST) find_package(ZLIB REQUIRED) include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_LIST_DIR} .. ../irisnet/corelib xmpp-core xmpp-im ${IDN_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS} ${QCA_INCLUDES} ) set(PLAIN_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_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_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_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 xmpp-im/jingle-s5b.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 blake2/blake2.h blake2/blake2-impl.h ) set(HEADERS 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-ft.h zlib/zlibcompressor.h zlib/zlibdecompressor.h ) set(PLAIN_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_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-s5b.cpp xmpp-im/jingle-ft.cpp base/randomnumbergenerator.cpp base/timezone.cpp zlib/zlibcompressor.cpp zlib/zlibdecompressor.cpp blake2/blake2qt.cpp blake2/blake2b-ref.c blake2/blake2s-ref.c jid/jid.cpp sasl/digestmd5proplist.cpp sasl/digestmd5response.cpp sasl/plainmessage.cpp sasl/scramsha1message.cpp sasl/scramsha1response.cpp sasl/scramsha1signature.cpp ) set(SOURCES xmpp-core/securestream.cpp xmpp-core/simplesasl.cpp xmpp-im/s5b.cpp xmpp-im/xmpp_features.cpp xmpp-im/jingle.cpp ) qt_wrap_cpp(MOC_SOURCES ${HEADERS} ${SOURCES}) add_library(iris STATIC ${HEADERS} ${SOURCES} ${MOC_SOURCES} ${PLAIN_SOURCES} ${PLAIN_HEADERS} ) 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 ../../include ../../include/iris ..) psi-plus-snapshots-1.4.554/iris/src/xmpp/base/000077500000000000000000000000001342663516400211335ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/xmpp/base/base.pri000066400000000000000000000003571342663516400225660ustar00rootroot00000000000000INCLUDEPATH += $$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.554/iris/src/xmpp/base/randomnumbergenerator.cpp000066400000000000000000000021141342663516400262350ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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); } } psi-plus-snapshots-1.4.554/iris/src/xmpp/base/randomnumbergenerator.h000066400000000000000000000022571342663516400257120ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/base/randrandomnumbergenerator.h000066400000000000000000000024041342663516400265510ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef RANDRANDOMNUMBERGENERATOR_H #define RANDRANDOMNUMBERGENERATOR_H #include "xmpp/base/randomnumbergenerator.h" namespace XMPP { class RandRandomNumberGenerator : public RandomNumberGenerator { public: RandRandomNumberGenerator() {} virtual double generateNumber() const { return rand(); } virtual double getMaximumGeneratedNumber() const { return RAND_MAX; } }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/base/timezone.cpp000066400000000000000000000066771342663516400235110ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #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 "timezone.h" #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.554/iris/src/xmpp/base/timezone.h000066400000000000000000000020211342663516400231310ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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.554/iris/src/xmpp/base/unittest/000077500000000000000000000000001342663516400230125ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/xmpp/base/unittest/incrementingrandomnumbergenerator.h000066400000000000000000000031041342663516400321640ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef INCREMENTINGRANDOMNUMBERGENERATOR_H #define INCREMENTINGRANDOMNUMBERGENERATOR_H #include #include "xmpp/base/randomnumbergenerator.h" 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_; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/base/unittest/randomnumbergeneratortest.cpp000066400000000000000000000041461342663516400310230ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include "qttestutil/qttestutil.h" #include "xmpp/base/randomnumbergenerator.h" 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.554/iris/src/xmpp/base/unittest/randrandomnumbergeneratortest.cpp000066400000000000000000000025671342663516400316750ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include "qttestutil/qttestutil.h" #include "xmpp/base/randrandomnumbergenerator.h" 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.554/iris/src/xmpp/base/unittest/unittest.pri000066400000000000000000000001431342663516400254030ustar00rootroot00000000000000SOURCES += \ $$PWD/randrandomnumbergeneratortest.cpp \ $$PWD/randomnumbergeneratortest.cpp psi-plus-snapshots-1.4.554/iris/src/xmpp/base/unittest/unittest.pro000066400000000000000000000001721342663516400254130ustar00rootroot00000000000000include(../../modules.pri) include($$IRIS_XMPP_QA_UNITTEST_MODULE) include($$IRIS_XMPP_BASE_MODULE) include(unittest.pri) psi-plus-snapshots-1.4.554/iris/src/xmpp/blake2/000077500000000000000000000000001342663516400213615ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/xmpp/blake2/README.md000066400000000000000000000013351342663516400226420ustar00rootroot00000000000000This 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.554/iris/src/xmpp/blake2/blake2-impl.h000066400000000000000000000101511342663516400236270ustar00rootroot00000000000000/* 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 psi-plus-snapshots-1.4.554/iris/src/xmpp/blake2/blake2.h000066400000000000000000000144671342663516400227060ustar00rootroot00000000000000/* 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; /* Padded structs result in a compile-time error */ enum { BLAKE2_DUMMY_1 = 1/(sizeof(blake2s_param) == BLAKE2S_OUTBYTES), BLAKE2_DUMMY_2 = 1/(sizeof(blake2b_param) == BLAKE2B_OUTBYTES) }; /* 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 psi-plus-snapshots-1.4.554/iris/src/xmpp/blake2/blake2.pri000066400000000000000000000003321342663516400232330ustar00rootroot00000000000000INCLUDEPATH *= $$PWD/../.. DEPENDPATH *= $$PWD/../.. SOURCES += $$PWD/blake2qt.cpp \ $$PWD/blake2s-ref.c \ $$PWD/blake2b-ref.c HEADERS += $$PWD/blake2qt.h \ $$PWD/blake2.h OTHER_FILES += $$PWD/README.md psi-plus-snapshots-1.4.554/iris/src/xmpp/blake2/blake2b-ref.c000066400000000000000000000235111342663516400236030ustar00rootroot00000000000000/* 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 #include #include #include "blake2.h" #include "blake2-impl.h" 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 #include "blake2-kat.h" 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.554/iris/src/xmpp/blake2/blake2qt.cpp000066400000000000000000000011171342663516400235720ustar00rootroot00000000000000#include "blake2qt.h" #include "blake2.h" namespace XMPP { QByteArray computeBlake2Hash(const QByteArray &ba, Blake2DigestSize digestSize) { QByteArray ret; int retCode; if (digestSize == Blake2Digest256) { ret.reserve(BLAKE2S_OUTBYTES); retCode = blake2s(ret.data(), BLAKE2S_OUTBYTES, ba.data(), ba.size(), nullptr, 0); } else { ret.reserve(BLAKE2B_OUTBYTES); retCode = blake2s(ret.data(), BLAKE2B_OUTBYTES, ba.data(), ba.size(), nullptr, 0); } if (retCode != 0) { ret.clear(); } return ret; } } //namespace XMPP psi-plus-snapshots-1.4.554/iris/src/xmpp/blake2/blake2qt.h000066400000000000000000000003601342663516400232360ustar00rootroot00000000000000#ifndef BLAKE2QT_H #define BLAKE2QT_H #include namespace XMPP { enum Blake2DigestSize { Blake2Digest256, Blake2Digest512 }; QByteArray computeBlake2Hash(const QByteArray &ba, Blake2DigestSize digestSize); } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/blake2/blake2s-ref.c000066400000000000000000000227051342663516400236300ustar00rootroot00000000000000/* 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 #include #include #include "blake2.h" #include "blake2-impl.h" 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 #include "blake2-kat.h" 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.554/iris/src/xmpp/common.pri000066400000000000000000000000571342663516400222270ustar00rootroot00000000000000OBJECTS_DIR = .obj MOC_DIR = .moc UI_DIR = .ui psi-plus-snapshots-1.4.554/iris/src/xmpp/jid/000077500000000000000000000000001342663516400207675ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/xmpp/jid/jid.cpp000066400000000000000000000221131342663516400222400ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "xmpp/jid/jid.h" #include #include #ifndef NO_IRISNET #include "irisnetglobal_p.h" #endif 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::null); 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::null); 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::null); 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::null); return false; } QString norm = QString::fromUtf8(cs); that->saslprep_table.insert(in, norm); out = norm; return true; } void StringPrepCache::cleanup() { _instance.reset(0); } 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.554/iris/src/xmpp/jid/jid.h000066400000000000000000000060571342663516400217160ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/jid/jid.pri000066400000000000000000000001631342663516400222510ustar00rootroot00000000000000INCLUDEPATH *= $$PWD/../.. DEPENDPATH *= $$PWD/../.. HEADERS += \ $$PWD/jid.h SOURCES += \ $$PWD/jid.cpp psi-plus-snapshots-1.4.554/iris/src/xmpp/jid/unittest/000077500000000000000000000000001342663516400226465ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/xmpp/jid/unittest/jidtest.cpp000066400000000000000000000024771342663516400250320ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ // FIXME: Complete this #include #include #include "qttestutil/qttestutil.h" #include "xmpp/jid/jid.h" 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.554/iris/src/xmpp/jid/unittest/unittest.pri000066400000000000000000000000431342663516400252360ustar00rootroot00000000000000SOURCES += \ $$PWD/jidtest.cpp psi-plus-snapshots-1.4.554/iris/src/xmpp/jid/unittest/unittest.pro000066400000000000000000000001711342663516400252460ustar00rootroot00000000000000include(../../modules.pri) include($$IRIS_XMPP_QA_UNITTEST_MODULE) include($$IRIS_XMPP_JID_MODULE) include(unittest.pri) psi-plus-snapshots-1.4.554/iris/src/xmpp/modules.pri000066400000000000000000000004241342663516400224050ustar00rootroot00000000000000IRIS_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.554/iris/src/xmpp/qa/000077500000000000000000000000001342663516400206225ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/xmpp/qa/README000066400000000000000000000022701342663516400215030ustar00rootroot00000000000000How 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.554/iris/src/xmpp/qa/qttestutil/000077500000000000000000000000001342663516400230445ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/xmpp/qa/qttestutil/example/000077500000000000000000000000001342663516400244775ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/xmpp/qa/qttestutil/example/example.pro000066400000000000000000000006501342663516400266550ustar00rootroot00000000000000# # 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.554/iris/src/xmpp/qa/qttestutil/example/myfirstclasstest.cpp000066400000000000000000000006241342663516400306300ustar00rootroot00000000000000#include #include #include "qttestutil/qttestutil.h" 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.554/iris/src/xmpp/qa/qttestutil/example/mysecondclasstest.cpp000066400000000000000000000006271342663516400307570ustar00rootroot00000000000000#include #include #include "qttestutil/qttestutil.h" 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.554/iris/src/xmpp/qa/qttestutil/qttestutil.h000066400000000000000000000025311342663516400254400ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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 psi-plus-snapshots-1.4.554/iris/src/xmpp/qa/qttestutil/qttestutil.pri000066400000000000000000000001301342663516400257740ustar00rootroot00000000000000INCLUDEPATH *= $$PWD/.. DEPENDPATH *= $$PWD/.. SOURCES += \ $$PWD/testregistry.cpp psi-plus-snapshots-1.4.554/iris/src/xmpp/qa/qttestutil/simplechecker.cpp000066400000000000000000000020711342663516400263660ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include "qttestutil/testregistry.h" /** * 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.554/iris/src/xmpp/qa/qttestutil/testregistration.h000066400000000000000000000030231342663516400266250ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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_; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/qa/qttestutil/testregistry.cpp000066400000000000000000000023331342663516400263210ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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; } } psi-plus-snapshots-1.4.554/iris/src/xmpp/qa/qttestutil/testregistry.h000066400000000000000000000034571342663516400257760ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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_; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/qa/unittest.pri000066400000000000000000000010201342663516400232060ustar00rootroot00000000000000# # 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.554/iris/src/xmpp/qa/unittest.template/000077500000000000000000000000001342663516400243135ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/xmpp/qa/unittest.template/myclasstest.cpp000066400000000000000000000022551342663516400273760ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include "qttestutil/qttestutil.h" 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.554/iris/src/xmpp/qa/unittest.template/unittest.pri000066400000000000000000000000471342663516400267070ustar00rootroot00000000000000SOURCES += \ $$PWD/myclasstest.cpp psi-plus-snapshots-1.4.554/iris/src/xmpp/qa/unittest.template/unittest.pro000066400000000000000000000001551342663516400267150ustar00rootroot00000000000000include(../../modules.pri) include($$IRIS_XMPP_QA_UNITTEST_MODULE) include($$MYMODULE) include(unittest.pri) psi-plus-snapshots-1.4.554/iris/src/xmpp/qa/unittests.pri000066400000000000000000000001661342663516400234030ustar00rootroot00000000000000# All available unit tests include($$PWD/../base/unittest/unittest.pri) include($$PWD/../sasl/unittest/unittest.pri) psi-plus-snapshots-1.4.554/iris/src/xmpp/qa/unittests/000077500000000000000000000000001342663516400226645ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/xmpp/qa/unittests/unittests.pro000066400000000000000000000002121342663516400254430ustar00rootroot00000000000000include(../../../../iris.pri) include(../unittests.pri) include(../unittest.pri) # FIXME include(../../../../../third-party/qca/qca.pri) psi-plus-snapshots-1.4.554/iris/src/xmpp/sasl/000077500000000000000000000000001342663516400211635ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/xmpp/sasl/digestmd5proplist.cpp000066400000000000000000000073561342663516400253640ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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; } } psi-plus-snapshots-1.4.554/iris/src/xmpp/sasl/digestmd5proplist.h000066400000000000000000000025641342663516400250250ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/sasl/digestmd5response.cpp000066400000000000000000000065271342663516400253450ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "xmpp/sasl/digestmd5response.h" #include #include #include #include "xmpp/sasl/digestmd5proplist.h" #include "xmpp/base/randomnumbergenerator.h" 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(); } } psi-plus-snapshots-1.4.554/iris/src/xmpp/sasl/digestmd5response.h000066400000000000000000000031671342663516400250070ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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_; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/sasl/plainmessage.cpp000066400000000000000000000020061342663516400243350ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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; } } psi-plus-snapshots-1.4.554/iris/src/xmpp/sasl/plainmessage.h000066400000000000000000000022561342663516400240110ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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_; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/sasl/sasl.pri000066400000000000000000000007211342663516400226410ustar00rootroot00000000000000INCLUDEPATH *= $$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.554/iris/src/xmpp/sasl/scramsha1message.cpp000066400000000000000000000042211342663516400251150ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "xmpp/sasl/scramsha1message.h" #include #include #include #include #include "xmpp/base/randomnumbergenerator.h" #include "xmpp/jid/jid.h" 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(); } } psi-plus-snapshots-1.4.554/iris/src/xmpp/sasl/scramsha1message.h000066400000000000000000000025751342663516400245740ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef SCRAMSHA1MESSAGE_H #define SCRAMSHA1MESSAGE_H #include #include #include "xmpp/base/randomnumbergenerator.h" 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_; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/sasl/scramsha1response.cpp000066400000000000000000000121751342663516400253360ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "xmpp/sasl/scramsha1response.h" #include #include #include #include #include #include #include "xmpp/base/randomnumbergenerator.h" #include "xmpp/jid/jid.h" 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_); } } psi-plus-snapshots-1.4.554/iris/src/xmpp/sasl/scramsha1response.h000066400000000000000000000035061342663516400250010ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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_; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/sasl/scramsha1signature.cpp000066400000000000000000000031441342663516400254750ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "xmpp/sasl/scramsha1signature.h" #include #include #include #include #include #include #include "xmpp/base/randomnumbergenerator.h" 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; } } } psi-plus-snapshots-1.4.554/iris/src/xmpp/sasl/scramsha1signature.h000066400000000000000000000023371342663516400251450ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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_; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/sasl/unittest/000077500000000000000000000000001342663516400230425ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/xmpp/sasl/unittest/digestmd5responsetest.cpp000066400000000000000000000062211342663516400301130ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include #include "qttestutil/qttestutil.h" #include "xmpp/sasl/digestmd5response.h" #include "xmpp/base/unittest/incrementingrandomnumbergenerator.h" 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.554/iris/src/xmpp/sasl/unittest/plainmessagetest.cpp000066400000000000000000000033731342663516400271240ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include "xmpp/sasl/plainmessage.h" #include "qttestutil/qttestutil.h" 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.554/iris/src/xmpp/sasl/unittest/scramsha1messagetest.cpp000066400000000000000000000036661342663516400277100ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include #include "qttestutil/qttestutil.h" #include "xmpp/base/unittest/incrementingrandomnumbergenerator.h" #include "xmpp/sasl/scramsha1message.h" 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.554/iris/src/xmpp/sasl/unittest/scramsha1responsetest.cpp000066400000000000000000000042431342663516400301120ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include #include "qttestutil/qttestutil.h" #include "xmpp/sasl/scramsha1response.h" #include "xmpp/sasl/scramsha1signature.h" #include "xmpp/base/unittest/incrementingrandomnumbergenerator.h" 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.554/iris/src/xmpp/sasl/unittest/unittest.pri000066400000000000000000000002351342663516400254350ustar00rootroot00000000000000SOURCES += \ $$PWD/plainmessagetest.cpp \ $$PWD/digestmd5responsetest.cpp \ $$PWD/scramsha1messagetest.cpp \ $$PWD/scramsha1responsetest.cpp psi-plus-snapshots-1.4.554/iris/src/xmpp/sasl/unittest/unittest.pro000066400000000000000000000006071342663516400254460ustar00rootroot00000000000000include(../../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.554/iris/src/xmpp/xmpp-core/000077500000000000000000000000001342663516400221335ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-core/compressionhandler.cpp000066400000000000000000000034341342663516400265420ustar00rootroot00000000000000#include #include #include "compressionhandler.h" #include "xmpp/zlib/zlibcompressor.h" #include "xmpp/zlib/zlibdecompressor.h" 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.554/iris/src/xmpp/xmpp-core/compressionhandler.h000066400000000000000000000012301342663516400261770ustar00rootroot00000000000000#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 psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-core/connector.cpp000066400000000000000000000330271342663516400246360ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ /* 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 "xmpp.h" #include #include #include #include #include #include "bsocket.h" #include "httpconnect.h" #include "httppoll.h" #include "socks.h" //#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 = 0; 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 = 0; 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 0; } 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.554/iris/src/xmpp/xmpp-core/parser.cpp000066400000000000000000000507601342663516400241430ustar00rootroot00000000000000/* * parser.cpp - parse an XMPP "document" * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ /* TODO: For XMPP::Parser to be "perfect", some things must be solved/changed in the Qt library: - Fix weird QDomElement::haveAttributeNS() bug (patch submitted to Trolltech on Aug 31st, 2003). - Fix weird behavior in QXmlSimpleReader of reporting endElement() when the '/' character of a self-closing tag is reached, instead of when the final '>' is reached. - Fix incremental parsing bugs in QXmlSimpleReader. At the moment, the only bug I've found is related to attribute parsing, but there might be more (search for '###' in $QTDIR/src/xml/qxml.cpp). We have workarounds for all of the above problems in the code below. - Deal with the processing instruction as an event type, so that we can feed it back to the application properly. Right now it is completely untrackable and is simply tacked into the first event's actualString. We can't easily do this because QXmlSimpleReader eats an extra byte beyond the processing instruction before reporting it. - Make QXmlInputSource capable of accepting data incrementally, to ensure proper text encoding detection and processing over a network. This is technically not a bug, as we have our own subclass below to do it, but it would be nice if Qt had this already. */ #include "parser.h" #include #include using namespace XMPP; static bool qt_bug_check = false; static bool qt_bug_have; //---------------------------------------------------------------------------- // StreamInput //---------------------------------------------------------------------------- class StreamInput : public QXmlInputSource { public: StreamInput() { dec = 0; reset(); } ~StreamInput() { delete dec; } void reset() { delete dec; dec = 0; in.resize(0); out = ""; at = 0; paused = false; mightChangeEncoding = true; checkBad = true; last = QChar(); v_encoding = ""; resetLastData(); } void resetLastData() { last_string = ""; } QString lastString() const { return last_string; } void appendData(const QByteArray &a) { int oldsize = in.size(); in.resize(oldsize + a.size()); memcpy(in.data() + oldsize, a.data(), a.size()); processBuf(); } QChar lastRead() { return last; } QChar next() { if(paused) return EndOfData; else return readNext(); } // NOTE: setting 'peek' to true allows the same char to be read again, // however this still advances the internal byte processing. QChar readNext(bool peek=false) { QChar c; if(mightChangeEncoding) c = EndOfData; else { if(out.isEmpty()) { QString s; if(!tryExtractPart(&s)) c = EndOfData; else { out = s; c = out[0]; } } else c = out[0]; if(!peek) out.remove(0, 1); } if(c == EndOfData) { #ifdef XMPP_PARSER_DEBUG printf("next() = EOD\n"); #endif } else { #ifdef XMPP_PARSER_DEBUG printf("next() = [%c]\n", c.latin1()); #endif last = c; } return c; } QByteArray unprocessed() const { QByteArray a; a.resize(in.size() - at); memcpy(a.data(), in.data() + at, a.size()); return a; } void pause(bool b) { paused = b; } bool isPaused() { return paused; } QString encoding() const { return v_encoding; } private: QTextDecoder *dec; QByteArray in; QString out; int at; bool paused; bool mightChangeEncoding; QChar last; QString v_encoding; QString last_string; bool checkBad; void processBuf() { #ifdef XMPP_PARSER_DEBUG printf("processing. size=%d, at=%d\n", in.size(), at); #endif if(!dec) { QTextCodec *codec = 0; uchar *p = (uchar *)in.data() + at; int size = in.size() - at; // do we have enough information to determine the encoding? if(size == 0) return; bool utf16 = false; if(p[0] == 0xfe || p[0] == 0xff) { // probably going to be a UTF-16 byte order mark if(size < 2) return; if((p[0] == 0xfe && p[1] == 0xff) || (p[0] == 0xff && p[1] == 0xfe)) { // ok it is UTF-16 utf16 = true; } } if(utf16) codec = QTextCodec::codecForMib(1000); // UTF-16 else codec = QTextCodec::codecForMib(106); // UTF-8 v_encoding = codec->name(); dec = codec->makeDecoder(); // for utf16, put in the byte order mark if(utf16) { out += dec->toUnicode((const char *)p, 2); at += 2; } } if(mightChangeEncoding) { while(1) { int n = out.indexOf('<'); if(n != -1) { // we need a closing bracket int n2 = out.indexOf('>', n); if(n2 != -1) { ++n2; QString h = out.mid(n, n2-n); QString enc = processXmlHeader(h); QTextCodec *codec = 0; if(!enc.isEmpty()) codec = QTextCodec::codecForName(enc.toLatin1()); // changing codecs if(codec) { v_encoding = codec->name(); delete dec; dec = codec->makeDecoder(); } mightChangeEncoding = false; out.truncate(0); at = 0; resetLastData(); break; } } QString s; if(!tryExtractPart(&s)) break; if(checkBad && checkForBadChars(s)) { // go to the parser mightChangeEncoding = false; out.truncate(0); at = 0; resetLastData(); break; } out += s; } } } QString processXmlHeader(const QString &h) { if(h.left(5) != ""); int startPos = h.indexOf("encoding"); if(startPos < endPos && startPos != -1) { QString encoding; do { startPos++; if(startPos > endPos) { return ""; } } while(h[startPos] != '"' && h[startPos] != '\''); startPos++; while(h[startPos] != '"' && h[startPos] != '\'') { encoding += h[startPos]; startPos++; if(startPos > endPos) { return ""; } } return encoding; } else return ""; } bool tryExtractPart(QString *s) { int size = in.size() - at; if(size == 0) return false; uchar *p = (uchar *)in.data() + at; QString nextChars; while(1) { nextChars = dec->toUnicode((const char *)p, 1); ++p; ++at; if(!nextChars.isEmpty()) break; if(at == (int)in.size()) return false; } last_string += nextChars; *s = nextChars; // free processed data? if(at >= 1024) { char *p = in.data(); int size = in.size() - at; memmove(p, p + at, size); in.resize(size); at = 0; } return true; } bool checkForBadChars(const QString &s) { int len = s.indexOf('<'); if(len == -1) len = s.length(); else checkBad = false; for(int n = 0; n < len; ++n) { if(!s.at(n).isSpace()) return true; } return false; } }; //---------------------------------------------------------------------------- // ParserHandler //---------------------------------------------------------------------------- namespace XMPP { class ParserHandler : public QXmlDefaultHandler { public: explicit ParserHandler(StreamInput *_in, QDomDocument *_doc) : in(_in) , doc(_doc) { } ~ParserHandler() { while (!eventList.isEmpty()) { delete eventList.takeFirst(); } } bool startDocument() { depth = 0; return true; } bool endDocument() { return true; } bool startPrefixMapping(const QString &prefix, const QString &uri) { if(depth == 0) { nsnames += prefix; nsvalues += uri; } return true; } bool startElement(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts) { if(depth == 0) { Parser::Event *e = new Parser::Event; QXmlAttributes a; for(int n = 0; n < atts.length(); ++n) { QString uri = atts.uri(n); QString ln = atts.localName(n); if(a.index(uri, ln) == -1) a.append(atts.qName(n), uri, ln, atts.value(n)); } e->setDocumentOpen(namespaceURI, localName, qName, a, nsnames, nsvalues); nsnames.clear(); nsvalues.clear(); e->setActualString(in->lastString()); in->resetLastData(); eventList.append(e); in->pause(true); } else { QDomElement e = doc->createElementNS(namespaceURI, qName); for(int n = 0; n < atts.length(); ++n) { QString uri = atts.uri(n); QString ln = atts.localName(n); bool have; if(!uri.isEmpty()) { have = e.hasAttributeNS(uri, ln); if(qt_bug_have) have = !have; } else have = e.hasAttribute(ln); if(!have) e.setAttributeNS(uri, atts.qName(n), atts.value(n)); } if(depth == 1) { elem = e; current = e; } else { current.appendChild(e); current = e; } } ++depth; return true; } bool endElement(const QString &namespaceURI, const QString &localName, const QString &qName) { --depth; if(depth == 0) { Parser::Event *e = new Parser::Event; e->setDocumentClose(namespaceURI, localName, qName); e->setActualString(in->lastString()); in->resetLastData(); eventList.append(e); in->pause(true); } else { // done with a depth 1 element? if(depth == 1) { Parser::Event *e = new Parser::Event; e->setElement(elem); e->setActualString(in->lastString()); in->resetLastData(); eventList.append(e); in->pause(true); elem = QDomElement(); current = QDomElement(); } else current = current.parentNode().toElement(); } if(in->lastRead() == '/') checkNeedMore(); return true; } bool characters(const QString &str) { if(depth >= 1) { QString content = str; if(content.isEmpty()) return true; if(!current.isNull()) { QDomText text = doc->createTextNode(content); current.appendChild(text); } } return true; } /*bool processingInstruction(const QString &target, const QString &data) { printf("Processing: [%s], [%s]\n", target.latin1(), data.latin1()); in->resetLastData(); return true; }*/ void checkNeedMore() { // Here we will work around QXmlSimpleReader strangeness and self-closing tags. // The problem is that endElement() is called when the '/' is read, not when // the final '>' is read. This is a potential problem when obtaining unprocessed // bytes from StreamInput after this event, as the '>' character will end up // in the unprocessed chunk. To work around this, we need to advance StreamInput's // internal byte processing, but not the xml character data. This way, the '>' // will get processed and will no longer be in the unprocessed return, but // QXmlSimpleReader can still read it. To do this, we call StreamInput::readNext // with 'peek' mode. QChar c = in->readNext(true); // peek if(c == QXmlInputSource::EndOfData) { needMore = true; } else { // We'll assume the next char is a '>'. If it isn't, then // QXmlSimpleReader will deal with that problem on the next // parse. We don't need to take any action here. needMore = false; // there should have been a pending event if (!eventList.isEmpty()) { Parser::Event *e = eventList.first(); e->setActualString(e->actualString() + '>'); in->resetLastData(); } } } Parser::Event *takeEvent() { if(needMore) return 0; if(eventList.isEmpty()) return 0; Parser::Event *e = eventList.takeFirst(); in->pause(false); return e; } StreamInput *in; QDomDocument *doc; int depth = 1; QStringList nsnames, nsvalues; QDomElement elem, current; QList eventList; bool needMore = false; }; }; //---------------------------------------------------------------------------- // Event //---------------------------------------------------------------------------- class Parser::Event::Private { public: int type; QString ns, ln, qn; QXmlAttributes a; QDomElement e; QString str; QStringList nsnames, nsvalues; }; Parser::Event::Event() { d = 0; } Parser::Event::Event(const Event &from) { d = 0; *this = from; } Parser::Event & Parser::Event::operator=(const Event &from) { if(&from == this) return *this; delete d; d = 0; if(from.d) d = new Private(*from.d); return *this; } Parser::Event::~Event() { delete d; } bool Parser::Event::isNull() const { return (d ? false: true); } int Parser::Event::type() const { if(isNull()) return -1; return d->type; } QString Parser::Event::nsprefix(const QString &s) const { QStringList::ConstIterator it = d->nsnames.constBegin(); QStringList::ConstIterator it2 = d->nsvalues.constBegin(); for(; it != d->nsnames.constEnd(); ++it) { if((*it) == s) return (*it2); ++it2; } return QString::null; } QString Parser::Event::namespaceURI() const { return d->ns; } QString Parser::Event::localName() const { return d->ln; } QString Parser::Event::qName() const { return d->qn; } QXmlAttributes Parser::Event::atts() const { return d->a; } QString Parser::Event::actualString() const { return d->str; } QDomElement Parser::Event::element() const { return d->e; } void Parser::Event::setDocumentOpen(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts, const QStringList &nsnames, const QStringList &nsvalues) { if(!d) d = new Private; d->type = DocumentOpen; d->ns = namespaceURI; d->ln = localName; d->qn = qName; d->a = atts; d->nsnames = nsnames; d->nsvalues = nsvalues; } void Parser::Event::setDocumentClose(const QString &namespaceURI, const QString &localName, const QString &qName) { if(!d) d = new Private; d->type = DocumentClose; d->ns = namespaceURI; d->ln = localName; d->qn = qName; } void Parser::Event::setElement(const QDomElement &elem) { if(!d) d = new Private; d->type = Element; d->e = elem; } void Parser::Event::setError() { if(!d) d = new Private; d->type = Error; } void Parser::Event::setActualString(const QString &str) { d->str = str; } //---------------------------------------------------------------------------- // Parser //---------------------------------------------------------------------------- class Parser::Private { public: Private() { doc = 0; in = 0; handler = 0; reader = 0; reset(); } ~Private() { reset(false); } void reset(bool create=true) { delete reader; delete handler; delete in; delete doc; if(create) { doc = new QDomDocument; in = new StreamInput; handler = new ParserHandler(in, doc); reader = new QXmlSimpleReader; reader->setContentHandler(handler); // initialize the reader in->pause(true); reader->parse(in, true); in->pause(false); } else { reader = 0; handler = 0; in = 0; doc = 0; } } QDomDocument *doc; StreamInput *in; ParserHandler *handler; QXmlSimpleReader *reader; }; Parser::Parser() { d = new Private; // check for evil bug in Qt <= 3.2.1 if(!qt_bug_check) { qt_bug_check = true; QDomElement e = d->doc->createElementNS("someuri", "somename"); if(e.hasAttributeNS("someuri", "somename")) qt_bug_have = true; else qt_bug_have = false; } } Parser::~Parser() { delete d; } void Parser::reset() { d->reset(); } void Parser::appendData(const QByteArray &a) { d->in->appendData(a); // if handler was waiting for more, give it a kick if(d->handler->needMore) d->handler->checkNeedMore(); } Parser::Event Parser::readNext() { Event e; if(d->handler->needMore) return e; Event *ep = d->handler->takeEvent(); if(!ep) { if(!d->reader->parseContinue()) { e.setError(); return e; } ep = d->handler->takeEvent(); if(!ep) return e; } e = *ep; delete ep; return e; } QByteArray Parser::unprocessed() const { return d->in->unprocessed(); } QString Parser::encoding() const { return d->in->encoding(); } psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-core/parser.h000066400000000000000000000047301342663516400236040ustar00rootroot00000000000000/* * parser.h - parse an XMPP "document" * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef PARSER_H #define PARSER_H #include #include namespace XMPP { class Parser { public: Parser(); ~Parser(); 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::null) const; // for document open / close QString namespaceURI() const; QString localName() const; QString qName() const; QXmlAttributes 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 QXmlAttributes &atts, const QStringList &nsnames, const QStringList &nsvalues); void setDocumentClose(const QString &namespaceURI, const QString &localName, const QString &qName); void setElement(const QDomElement &elem); void setError(); void setActualString(const QString &); private: class Private; Private *d; }; void reset(); void appendData(const QByteArray &a); Event readNext(); QByteArray unprocessed() const; QString encoding() const; private: class Private; Private *d; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-core/protocol.cpp000066400000000000000000001550411342663516400245060ustar00rootroot00000000000000 /* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ // 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" #include #include #include #include #ifdef XMPP_TEST #include "td.h" #endif 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; str.sprintf("[%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 }, { 0, 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 }, { 0, 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(); } 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("xmlns", defns); for(QStringList::ConstIterator it = list.begin(); it != list.end();) { QString prefix = *(it++); QString uri = *(it++); e.setAttribute(QString("xmlns:") + prefix, uri); } // additional attributes if(!isIncoming() && !to.isEmpty()) e.setAttribute("to", to); if(isIncoming() && !from.isEmpty()) e.setAttribute("from", from); if(!id.isEmpty()) e.setAttribute("id", id); if(!lang.isEmpty()) e.setAttributeNS(NS_XML, "xml:lang", lang); if(version.major > 0 || version.minor > 0) e.setAttribute("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") { QXmlAttributes atts = pe.atts(); // grab the version int major = 0; int minor = 0; QString 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"); QString peerLang = atts.value(NS_XML, "lang"); if(!peerLang.isEmpty()) lang = peerLang; } // outgoing else { from = atts.value("from"); lang = atts.value(NS_XML, "lang"); id = atts.value("id"); } 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"); foreach (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.554/iris/src/xmpp/xmpp-core/protocol.h000066400000000000000000000316421342663516400241530ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef PROTOCOL_H #define PROTOCOL_H #include #include #include #include #include "xmlprotocol.h" #include "xmpp.h" #include "sm.h" #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_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); }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-core/securestream.cpp000066400000000000000000000364051342663516400253510ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ /* 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 #include #include #ifdef USE_TLSHANDLER #include "xmpp.h" #endif #include "compressionhandler.h" //---------------------------------------------------------------------------- // 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 { foreach(SecureLayer *s, layers) { if(s->type == SecureLayer::TLS #ifdef USE_TLSHANDLER || s->type == SecureLayer::TLSH #endif ) { return true; } } return false; } bool haveSASL() const { foreach(SecureLayer *s, layers) { if(s->type == SecureLayer::SASL) return true; } return false; } bool haveCompress() const { foreach(SecureLayer *s, layers) { if(s->type == SecureLayer::Compression) return true; } return false; } void deleteLayers() { qDeleteAll(layers); layers.clear(); } }; SecureStream::SecureStream(ByteStream *s) :ByteStream(0) { 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; foreach(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) { foreach(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.554/iris/src/xmpp/xmpp-core/securestream.h000066400000000000000000000045401342663516400250110ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef SECURESTREAM_H #define SECURESTREAM_H #include #include "bytestream.h" #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 psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-core/simplesasl.cpp000066400000000000000000000326501342663516400250210ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "simplesasl.h" #include #include #include #include #include #include #include #include #include #include "xmpp/sasl/plainmessage.h" #include "xmpp/sasl/digestmd5response.h" #include "xmpp/sasl/scramsha1response.h" #include "xmpp/sasl/scramsha1message.h" #include "xmpp/sasl/scramsha1signature.h" #include "xmpp/base/randrandomnumbergenerator.h" 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(); foreach(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 0; } int qcaVersion() const { return QCA_VERSION; } }; QCA::Provider *createProviderSimpleSASL() { return (new QCASimpleSASL); } } #include "simplesasl.moc" psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-core/simplesasl.h000066400000000000000000000017411342663516400244630ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef SIMPLESASL_H #define SIMPLESASL_H namespace QCA { class Provider; } namespace XMPP { QCA::Provider* createProviderSimpleSASL(); } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-core/sm.cpp000066400000000000000000000117171342663516400232650ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef IRIS_SM_DEBUG #include #endif #include "sm.h" 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.554/iris/src/xmpp/xmpp-core/sm.h000066400000000000000000000056361342663516400227350ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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 = 0); 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; }; } #endif //XMPP_SM_H psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-core/stream.cpp000066400000000000000000001270421342663516400241400ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ /* 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 "xmpp.h" #include #include #include #include #include #include #include //#include #include #include "bytestream.h" #include "simplesasl.h" #include "securestream.h" #include "protocol.h" #ifndef NO_IRISNET #include "irisnetglobal_p.h" #endif #ifdef XMPP_TEST #include "td.h" #endif //#define XMPP_DEBUG using namespace XMPP; static Debug *debug_ptr = 0; 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 = 0; 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 = 0; } //---------------------------------------------------------------------------- // 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 = 0; // reset sasl delete d->sasl; d->sasl = 0; if(all) { while (!d->in.isEmpty()) { delete d->in.takeFirst(); } } else { QSharedPointer sd; foreach (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 = 0; } 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 = 0; } 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().createElement("abort"); // FIXME it's kind of wrong to forge xml here e.setAttribute("xmlns", NS_SASL); 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 0; } 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 foreach (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; foreach(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-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; foreach (const QString &mech, d->mechProviders.keys()) { if (ml.contains(mech)) { saslProvider = d->mechProviders[mech]; break; } } d->sasl = new QCA::SASL(0, 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.554/iris/src/xmpp/xmpp-core/td.h000066400000000000000000000005271342663516400227170ustar00rootroot00000000000000#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 psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-core/tlshandler.cpp000066400000000000000000000277421342663516400250130ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "xmpp.h" #include #include "qca.h" using namespace XMPP; // FIXME: remove this code once qca cert host checking works ... using namespace QCA; #include // 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(':')) { QStringList parts = str.split(':', QString::KeepEmptyParts); 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('.')) { QStringList parts = str.split('.', QString::KeepEmptyParts); 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 QStringList parts_name = name.split('.', QString::KeepEmptyParts); 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; QStringList parts_compare = acedomain.split('.', QString::KeepEmptyParts); if(parts_compare.isEmpty()) return false; // don't allow empty parts foreach(const QString &s, parts_name) { if(s.isEmpty()) return false; } foreach(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 foreach(const QString &s, cert.subjectInfo().values(IPAddress)) { if(cert_match_ipaddress(s, ipaddr)) return true; } // check dNSName foreach(const QString &s, cert.subjectInfo().values(DNS)) { if(cert_match_ipaddress(s, ipaddr)) return true; } // check commonName foreach(const QString &s, cert.subjectInfo().values(CommonName)) { 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 foreach(const QString &s, cert.subjectInfo().values(DNS)) { if(cert_match_domain(s, name)) return true; } // check commonName foreach(const QString &s, cert.subjectInfo().values(CommonName)) { 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); foreach( const QString &idOnXmppAddr, peerCert.primary().subjectInfo().values(QCA::XMPP) ) { 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.554/iris/src/xmpp/xmpp-core/xmlprotocol.cpp000066400000000000000000000436241342663516400252320ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include #include "xmlprotocol.h" #include "bytestream.h" 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); } 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(); } 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) { QString out=sanitizeForStream(s); 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.554/iris/src/xmpp/xmpp-core/xmlprotocol.h000066400000000000000000000120571342663516400246730ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMLPROTOCOL_H #define XMLPROTOCOL_H #include #include #include #include "parser.h" #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); // 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); }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-core/xmpp.h000066400000000000000000000137041342663516400232750ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_H #define XMPP_H #include #include #include #include #include #include #include #include "addressresolver.h" #include "xmpp/jid/jid.h" #include "xmpp_stanza.h" #include "xmpp_stream.h" #include "xmpp_clientstream.h" namespace QCA { class TLS; }; #ifndef CS_XMPP class ByteStream; #endif #include // For QCA::SASL::Params 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=0); 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=0); 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=0); 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; }; }; #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-core/xmpp_clientstream.h000066400000000000000000000174031342663516400260470ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_CLIENTSTREAM_H #define XMPP_CLIENTSTREAM_H #include #include "xmpp_stream.h" class QByteArray; class QString; class QDomDocument; class QDomElement; class QObject; class ByteStream; class QHostAddress; namespace XMPP { class TLSHandler; class Connector; class StreamFeatures; 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=0, QObject *parent=0); ClientStream(const QString &host, const QString &defRealm, ByteStream *bs, QCA::TLS *tls=0, QObject *parent=0); // 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); }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-core/xmpp_stanza.cpp000066400000000000000000000511201342663516400252020ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "xmpp_stanza.h" #include #include "xmpp/jid/jid.h" #include "xmpp_stream.h" #include "xmpp_clientstream.h" #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; } 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 }, { 0, 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 }, { 0, 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 QDomNodeList nl = e.childNodes(); QDomElement t; condition = -1; int n; for(n = 0; n < nl.count(); ++n) { QDomNode i = nl.item(n); t = i.toElement(); if(!t.isNull()) { // FIX-ME: this shouldn't be needed if(t.namespaceURI() == NS_STANZAS || t.attribute("xmlns") == NS_STANZAS) { condition = Private::stringToErrorCond(t.tagName()); if (condition != -1) 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; } // text t = e.elementsByTagNameNS(NS_STANZAS, "text").item(0).toElement(); if(!t.isNull()) text = t.text().trimmed(); else text = e.text().trimmed(); // appspec: find first non-standard namespaced element appSpec = QDomElement(); nl = e.childNodes(); for(n = 0; n < nl.count(); ++n) { QDomNode i = nl.item(n); if(i.isElement() && i.namespaceURI() != NS_STANZAS) { appSpec = i.toElement(); break; } } 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); } //---------------------------------------------------------------------------- // 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 = 0; } 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 = 0; 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 = 0; *this = from; } Stanza & Stanza::operator=(const Stanza &from) { if(&from == this) return *this; delete d; d = 0; 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.554/iris/src/xmpp/xmpp-core/xmpp_stanza.h000066400000000000000000000077621342663516400246640ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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; int code() const; bool fromCode(int code); QPair description() 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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-core/xmpp_stream.h000066400000000000000000000047111342663516400246460ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_STREAM_H #define XMPP_STREAM_H #include #include #include "xmpp_stanza.h" #include "xmpp/jid/jid.h" 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=0); 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); }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/000077500000000000000000000000001342663516400216105ustar00rootroot00000000000000psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/client.cpp000066400000000000000000001116111342663516400235730ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ //! \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 #include #include #include #include #include "im.h" #include "xmpp_tasks.h" #include "xmpp_xmlcommon.h" #include "s5b.h" #include "xmpp_ibb.h" #include "xmpp_bitsofbinary.h" #include "filetransfer.h" #include "xmpp_caps.h" #include "xmpp_hash.h" #include "protocol.h" #include "xmpp_serverinfomanager.h" #include "httpfileupload.h" #include "jingle.h" #include "jingle-ft.h" #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; 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; LiveRoster roster; ResourceList resourceList; CapsManager *capsman = nullptr; S5BManager *s5bman = 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::FTApplication(this); d->jingleManager->registerApp(Jingle::FileTransfer::NS, ft); } Client::~Client() { //fprintf(stderr, "\tClient::~Client\n"); //fflush(stderr); close(true); delete d->ftman; delete d->ibbman; delete d->s5bman; 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::setFileTransferEnabled(bool b) { if(b) { if(!d->ftman) d->ftman = new FileTransferManager(this); } else { if(d->ftman) { delete d->ftman; d->ftman = 0; } } } FileTransferManager *Client::fileTransferManager() const { return d->ftman; } S5BManager *Client::s5bManager() const { return d->s5bman; } IBBManager *Client::ibbManager() const { return d->ibbman; } BoBManager *Client::bobManager() const { return d->bobman; } CapsManager *Client::capsManager() const { return d->capsman; } 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); foreach(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; foreach (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); foreach (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 = 0; } disconnected(); cleanup(); } 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(); //} } /*void Client::streamSSLCertificateReady(const QSSLCert &cert) { sslCertReady(cert); } void Client::streamCloseFinished() { closeFinished(); }*/ 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; } 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 = 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(); foreach (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; s.sprintf("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()->createElement("feature-not-implemented"); error_type.setAttribute("xmlns","urn:ietf:params:xml:ns:xmpp-stanzas"); 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() { 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::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 foreach (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()); int timeout = 0; if (withGroupsDelimiter) { connect(r, SIGNAL(finished()), SLOT(slotRosterDelimiterRequestFinished())); r->getGroupsDelimiter(); // WORKAROUND: Some bad servers (Facebook for example) don't response // 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::slotRosterDelimiterRequestFinished() { JT_Roster *r = qobject_cast(sender()); 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); } 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; str.sprintf(" %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) { d->osName = name; } void Client::setOSVersion(const QString &version) { 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) { d->clientName = s; } void Client::setClientVersion(const QString &s) { 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"); Hash::populateFeatures(features); // Client-specific features foreach (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() { handleIncoming(d->ibbman->takeIncoming()); } 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() { delete d; } LiveRoster::LiveRoster(const LiveRoster &other) : QList(other) , d(new LiveRoster::Private) { d->groupsDelimiter = other.d->groupsDelimiter; } 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.554/iris/src/xmpp/xmpp-im/filetransfer.cpp000066400000000000000000000567021342663516400250120ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "filetransfer.h" #include #include #include #include #include #include "xmpp_xmlcommon.h" #include "s5b.h" #include "xmpp_ibb.h" #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; FTThumbnail 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 = 0; d->c = 0; reset(); } FileTransfer::FileTransfer(const FileTransfer& other) : QObject(other.parent()) { d = new Private; *d = *other.d; d->m = other.d->m; d->ft = 0; 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 = 0; if (d->c) { d->c->disconnect(this); d->c->manager()->deleteConnection(d->c, d->state == Active && !d->sender ? 3000 : 0); d->c = 0; } 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, const FTThumbnail &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 FTThumbnail &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 = 0; 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 0; 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; foreach(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 0; } return d->streamMap.value(ns); } QStringList FileTransferManager::streamPriority() const { QStringList ret; foreach (const QString &ns, d->streamPriority) { if (!d->disabledStreamTypes.contains(ns)) { ret.append(ns); } } return ret; } void FileTransferManager::stream_incomingReady(BSConnection *c) { foreach(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; id = QString("ft_%1").arg(qrand() & 0xffff, 4, 16, QChar('0')); foreach (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, const FTThumbnail &thumb) { QDomElement iq; d->to = to; iq = createIQ(doc(), "set", to.full(), id()); QDomElement si = doc()->createElement("si"); si.setAttribute("xmlns", "http://jabber.org/protocol/si"); si.setAttribute("id", _id); si.setAttribute("profile", "http://jabber.org/protocol/si/profile/file-transfer"); QDomElement file = doc()->createElement("file"); file.setAttribute("xmlns", "http://jabber.org/protocol/si/profile/file-transfer"); 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); QDomElement thel = doc()->createElement("thumbnail"); thel.setAttribute("xmlns", "urn:xmpp:thumbs:0"); thel.setAttribute("cid", data.cid()); thel.setAttribute("mime-type", thumb.mimeType); if (thumb.width && thumb.height) { thel.setAttribute("width", thumb.width); thel.setAttribute("height", thumb.height); } file.appendChild(thel); } si.appendChild(file); QDomElement feature = doc()->createElement("feature"); feature.setAttribute("xmlns", "http://jabber.org/protocol/feature-neg"); QDomElement x = doc()->createElement("x"); x.setAttribute("xmlns", "jabber:x:data"); 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.attribute("xmlns") != "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.attribute("xmlns") == "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()->createElement("si"); si.setAttribute("xmlns", "http://jabber.org/protocol/si"); if(rangeOffset != 0 || rangeLength != 0) { QDomElement file = doc()->createElement("file"); file.setAttribute("xmlns", "http://jabber.org/protocol/si/profile/file-transfer"); 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()->createElement("feature"); feature.setAttribute("xmlns", "http://jabber.org/protocol/feature-neg"); QDomElement x = doc()->createElement("x"); x.setAttribute("xmlns", "jabber:x:data"); 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.attribute("xmlns") != "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.attribute("xmlns") == "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(); } } } } FTThumbnail thumb; QDomElement thel = file.elementsByTagName("thumbnail").item(0).toElement(); if(!thel.isNull() && thel.attribute("xmlns") == QLatin1String("urn:xmpp:thumbs:0")) { thumb.data = thel.attribute("cid").toUtf8(); thumb.mimeType = thel.attribute("mime-type"); thumb.width = thel.attribute("width").toUInt(); thumb.height = thel.attribute("height").toUInt(); } 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 = thumb; emit incoming(r); return true; } psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/filetransfer.h000066400000000000000000000141641342663516400244530ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_FILETRANSFER_H #define XMPP_FILETRANSFER_H #include "im.h" namespace XMPP { //class BSConnection; class BSConnection; class BytestreamManager; struct FTRequest; /*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 FTThumbnail { public: inline FTThumbnail() : width(0), height(0) {} // data - for outgoing it's actual image data. for incoming - cid inline FTThumbnail(const QByteArray &data, const QString &mimeType = QString::null, quint32 width = 0, quint32 height = 0) : data(data), mimeType(mimeType), width(width), height(height) { } inline bool isNull() const { return data.isNull(); } QByteArray data; QString mimeType; quint32 width; quint32 height; }; 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, const FTThumbnail &thumb); qlonglong offset() const; qlonglong length() const; int dataSizeNeeded() const; void writeFileData(const QByteArray &a); const FTThumbnail &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=0); 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, const FTThumbnail &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; FTThumbnail 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); }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/httpfileupload.cpp000066400000000000000000000347521342663516400253530ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include #include #include #include "httpfileupload.h" #include "xmpp_tasks.h" #include "xmpp_xmlcommon.h" #include "xmpp_serverinfomanager.h" 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; 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(); 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 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::finished, this, [this, reply](){ if (reply->error() == QNetworkReply::NoError) { done(State::Success); } else { d->result.statusCode = ErrorCode::HttpFailed; d->result.statusString = reply->errorString(); 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) { d->to = to; d->ver = ver; d->iq = createIQ(doc(), "get", to.full(), id()); QDomElement req = doc()->createElement("request"); switch (ver) { case XEP0363::v0_2_5: req.setAttribute("xmlns", 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: req.setAttribute("xmlns", 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.attribute("xmlns") == xmlns_v0_2_5; getUrl = tagContent(get); putUrl = tagContent(put); break; case XEP0363::v0_3_1: correct_xmlns = slot.attribute("xmlns") == 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; QLinkedList hosts; }; HttpFileUploadManager::HttpFileUploadManager(Client *parent) : QObject(parent), d(new Private) { d->client = parent; } void HttpFileUploadManager::setNetworkAccessManager(QNetworkAccessManager *qnam) { 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); 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); hfu->setNetworkAccessManager(d->qnam); QMetaObject::invokeMethod(hfu, "start", Qt::QueuedConnection); return hfu; } psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/httpfileupload.h000066400000000000000000000134351342663516400250130ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_HTTPFILEUPLOAD_H #define XMPP_HTTPFILEUPLOAD_H #include #include class QNetworkAccessManager; #include "im.h" namespace XMPP { 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::null); 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(); 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: typedef std::function Callback; // params: success, detail. where detail could be a "get" url HttpFileUploadManager(Client *parent); /** * @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: the logic may change eventually and iris will set qnam automatically, * if it's not desired set nullptr explicitly */ 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::null, const QString &mType = QString::null); /** * @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::null); private: class Private; Private *d; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/im.h000066400000000000000000000066461342663516400224020ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_IM_H #define XMPP_IM_H #include //Added by qt3to4: #include #include "xmpp.h" #include "xmpp/jid/jid.h" #include "xmpp_muc.h" #include "xmpp_message.h" #include "xmpp_chatstate.h" #include "xmpp_status.h" #include "xmpp_htmlelement.h" #include "xmpp_features.h" #include "xmpp_httpauthrequest.h" #include "xmpp_url.h" #include "xmpp_task.h" #include "xmpp_resource.h" #include "xmpp_resourcelist.h" #include "xmpp_roster.h" #include "xmpp_rosteritem.h" #include "xmpp_liverosteritem.h" #include "xmpp_liveroster.h" #include "xmpp_rosterx.h" #include "xmpp_xdata.h" #include "xmpp_discoitem.h" #include "xmpp_agentitem.h" #include "xmpp_client.h" #include "xmpp_address.h" #include "xmpp_hash.h" #include "xmpp_pubsubitem.h" #include "xmpp_pubsubretraction.h" 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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/jingle-ft.cpp000066400000000000000000000126321342663516400241770ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "jingle-ft.h" #include "xmpp_client.h" namespace XMPP { namespace Jingle { namespace FileTransfer { const QString NS = QStringLiteral("urn:xmpp:jingle:apps:file-transfer:5"); //---------------------------------------------------------------------------- // File //---------------------------------------------------------------------------- class File::Private : public QSharedData { public: QDateTime date; QString mediaType; QString name; QString desc; QString size; Range range; Hash hash; }; File::File() { } File::~File() { } File::File(const File &other) : d(other.d) { } File::File(const QDomElement &file) { QDateTime date; QString mediaType; QString name; QString desc; size_t size; Range range{}; Hash hash; bool ok; for(QDomElement ce = file.firstChildElement(); !ce.isNull(); ce = ce.nextSiblingElement()) { if (ce.tagName() == QLatin1String("date")) { date = QDateTime::fromString(ce.text().left(19), Qt::ISODate); if (!date.isValid()) { return; } } else if (ce.tagName() == QLatin1String("media-type")) { mediaType = ce.text(); } else if (ce.tagName() == QLatin1String("name")) { name = ce.text(); } else if (ce.tagName() == QLatin1String("size")) { size = ce.text().toULongLong(&ok); if (!ok) { return; } } else if (ce.tagName() == QLatin1String("range")) { if (ce.hasAttribute(QLatin1String("offset"))) { range.offset = ce.attribute(QLatin1String("offset")).toULongLong(&ok); if (!ok) { return; } } if (ce.hasAttribute(QLatin1String("length"))) { range.offset = ce.attribute(QLatin1String("length")).toULongLong(&ok); if (!ok) { return; } } QDomElement hashEl = ce.firstChildElement(QLatin1String("hash")); if (hashEl.namespaceURI() == QLatin1String("urn:xmpp:hashes:2")) { range.hash = Hash(hashEl); if (range.hash.type() == Hash::Type::Unknown) { return; } } } else if (ce.tagName() == QLatin1String("desc")) { desc = ce.text(); } else if (ce.tagName() == QLatin1String("hash")) { if (ce.namespaceURI() == QLatin1String("urn:xmpp:hashes:2")) { hash = Hash(ce); if (hash.type() == Hash::Type::Unknown) { return; } } } else if (ce.tagName() == QLatin1String("hash-used")) { if (ce.namespaceURI() == QLatin1String("urn:xmpp:hashes:2")) { hash = Hash(ce); if (hash.type() == Hash::Type::Unknown) { return; } } } } // TODO make private and fill it } QDomElement File::toXml(QDomDocument *doc) const { Q_UNUSED(doc) // TODO return QDomElement(); // TODO } 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"); } //---------------------------------------------------------------------------- // FTApplication //---------------------------------------------------------------------------- FTApplication::FTApplication(Client *client): Application(client) { } void FTApplication::incomingSession(Session *session) { Q_UNUSED(session) // TODO } QSharedPointer FTApplication::descriptionFromXml(const QDomElement &el) { Q_UNUSED(el) // TODO QSharedPointer(); } } // namespace FileTransfer } // namespace Jingle } // namespace XMPP psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/jingle-ft.h000066400000000000000000000040061342663516400236400ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef JINGLEFT_H #define JINGLEFT_H #include "jingle.h" namespace XMPP { class Client; namespace Jingle { namespace FileTransfer { extern const QString NS; struct Range { quint64 offset = 0; quint64 length = 0; Hash hash; }; class File { public: File(); ~File(); File(const File &other); File(const QDomElement &file); inline bool isValid() const { return d != nullptr; } QDomElement toXml(QDomDocument *doc) const; 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 FTApplication : public Application { Q_OBJECT public: FTApplication(Client *client); void incomingSession(Session *session); QSharedPointer descriptionFromXml(const QDomElement &el); private: Client *client; }; } // namespace FileTransfer } // namespace Jingle } // namespace XMPP #endif // JINGLEFT_H psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/jingle-s5b.cpp000066400000000000000000000046071342663516400242620ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "jingle-s5b.h" #include "xmpp/jid/jid.h" namespace XMPP { namespace Jingle { namespace S5B { const QString NS(QStringLiteral("urn:xmpp:jingle:transports:s5b:1")); class Candidate::Private : public QSharedData { public: QString cid; QString host; Jid jid; quint16 port; quint16 priority; Candidate::Type type; }; Candidate::Candidate(const QDomElement &el) : d(new Private) { bool ok; d->host = el.attribute(QStringLiteral("host")); d->jid = Jid(el.attribute(QStringLiteral("jid"))); auto port = el.attribute(QStringLiteral("port")); if (!port.isEmpty()) { d->port = port.toUShort(&ok); if (!ok) { return; // make the whole candidate invalid } } auto priority = el.attribute(QStringLiteral("priority")); if (!priority.isEmpty()) { d->priority = priority.toUShort(&ok); if (!ok) { return; // make the whole candidate invalid } } d->cid = el.attribute(QStringLiteral("cid")); } Candidate::Candidate(const Candidate &other) : d(other.d) { } Candidate::~Candidate() { } class Negotiation::Private : public QSharedData { public: QList candidates; QString dstaddr; QString sid; Negotiation::Mode mode; }; Negotiation::Negotiation(const QDomElement &el) : d(new Private) { d->sid = el.attribute(QStringLiteral("sid")); // TODO remaining } Negotiation::Negotiation(const Negotiation &other) : d(other.d) { } Negotiation::~Negotiation() { } } // namespace S5B } // namespace Jingle } // namespace XMPP psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/jingle-s5b.h000066400000000000000000000031611342663516400237210ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef JINGLE_S5B_H #define JINGLE_S5B_H #include "jingle.h" namespace XMPP { class Client; namespace Jingle { namespace S5B { extern const QString NS; class Candidate { public: enum Type { Assisted, Direct, Proxy, Tunnel }; Candidate(const QDomElement &el); Candidate(const Candidate &other); ~Candidate(); private: class Private; QSharedDataPointer d; }; class Negotiation { public: enum Mode { Tcp, Udp }; Negotiation(const QDomElement &el); Negotiation(const Negotiation &other); ~Negotiation(); private: class Private; QSharedDataPointer d; }; class Manager : public TransportManager { }; } // namespace S5B } // namespace Jingle } // namespace XMPP #endif // JINGLE_S5B_H psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/jingle.cpp000066400000000000000000000412331342663516400235670ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "jingle.h" #include "xmpp_xmlcommon.h" #include "xmpp/jid/jid.h" #include "xmpp-im/xmpp_hash.h" #include "xmpp_client.h" #include "xmpp_task.h" #include "xmpp_stream.h" #include #include #include #include #include #include namespace XMPP { namespace Jingle { const QString NS(QStringLiteral("urn:xmpp:jingle:1")); //---------------------------------------------------------------------------- // Manager //---------------------------------------------------------------------------- class Manager::Private { public: friend class Content; XMPP::Client *client; // ns -> application QMap> applications; // ns -> parser function QMap(const QDomElement &)>> transportParsers; }; Manager::Manager(Client *client) : d(new Private) { client = client; } Manager::~Manager() { } void Manager::registerApp(const QString &ns, Application *app) { d->applications.insert(ns, app); } Session *Manager::newSession(const Jid &j) { Q_UNUSED(j); // TODO auto s = new Session(this); return s; } QSharedPointer Manager::descriptionFromXml(const QDomElement &el) { auto app = d->applications.value(el.namespaceURI()); if (!app) { return QSharedPointer(); } return app->descriptionFromXml(el); } QSharedPointer Manager::transportFromXml(const QDomElement &el) { auto parser = d->transportParsers.value(el.namespaceURI()); if (!parser) { return QSharedPointer(); } return parser(el); } bool Manager::incomingIQ(const QDomElement &iq) { return false; // TODO remove this when we hae something to test auto jingleEl = iq.firstChildElement(QStringLiteral("jingle")); if (jingleEl.isNull() || jingleEl.namespaceURI() != ::XMPP::Jingle::NS) { return false; } Jingle jingle(this, jingleEl); if (!jingle.isValid()) { auto resp = createIQ(client()->doc(), "error", iq.attribute(QStringLiteral("from")), iq.attribute(QStringLiteral("id"))); Stanza::Error error(Stanza::Error::Cancel, Stanza::Error::BadRequest); resp.appendChild(error.toXml(*client()->doc(), client()->stream().baseNS())); d->client->send(resp); return true; } // TODO check if it comes from known contacts maybe? //if (jingle.) // auto ch = jingleEl.firstChildElement(); // if (ch.tagName() == "content") { // } // auto it = d->nsHandlers.find(iq.namespaceURI()); // if (it == d->nsHandlers.end()) { // return false; // } return true; } //---------------------------------------------------------------------------- // Jingle //---------------------------------------------------------------------------- static const struct { const char *text; Jingle::Action action; } jingleActions[] = { { "content-accept", Jingle::ContentAccept }, { "content-add", Jingle::ContentAdd }, { "content-modify", Jingle::ContentModify }, { "content-reject", Jingle::ContentReject }, { "content-remove", Jingle::ContentRemove }, { "description-info", Jingle::DescriptionInfo }, { "security-info", Jingle::SecurityInfo }, { "session-accept", Jingle::SessionAccept }, { "session-info", Jingle::SessionInfo }, { "session-initiate", Jingle::SessionInitiate }, { "session-terminate", Jingle::SessionTerminate }, { "transport-accept", Jingle::TransportAccept }, { "transport-info", Jingle::TransportInfo }, { "transport-reject", Jingle::TransportReject }, { "transport-replace", Jingle::TransportReplace } }; class Jingle::Private : public QSharedData { public: Jingle::Action action; QString sid; Jid initiator; Jid responder; QList content; Reason reason; }; Jingle::Jingle() { } Jingle::Jingle(Manager *manager, const QDomElement &e) { QString actionStr = e.attribute(QLatin1String("action")); Action action; Reason reason; QString sid = e.attribute(QLatin1String("sid")); QList contentList; Jid initiator; Jid responder; bool found = false; for (unsigned int i = 0; i < sizeof(jingleActions) / sizeof(jingleActions[0]); i++) { if (actionStr == jingleActions[i].text) { found = true; action = jingleActions[i].action; break; } } if (!found || sid.isEmpty()) { return; } QDomElement re = e.firstChildElement(QLatin1String("reason")); if(!re.isNull()) { reason = Reason(re); if (!reason.isValid()) { qDebug("invalid reason"); return; } } QString contentTag(QStringLiteral("content")); for(QDomElement ce = e.firstChildElement(contentTag); !ce.isNull(); ce = ce.nextSiblingElement(contentTag)) { auto c = Content(manager, ce); if (!c.isValid()) { qDebug("invalid content"); return; } contentList += c; } 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->reason = reason; d->content = contentList; d->initiator = initiator; 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 == NoAction || (!d->reason.isValid() && d->content.isEmpty())) { return QDomElement(); } QDomElement query = doc->createElementNS(NS, QLatin1String("jingle")); //query.setAttribute("xmlns", JINGLE_NS); 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); if(d->action != SessionTerminate) { // for session terminate, there is no content list, just // a reason for termination for(const Content &c: d->content) { QDomElement content = c.toXml(doc); query.appendChild(content); } } if (d->reason.isValid()) { query.appendChild(d->reason.toXml(doc)); } return query; } Jingle::Action Jingle::action() const { return d->action; } //---------------------------------------------------------------------------- // 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(const QDomElement &e) { if(e.tagName() != QLatin1String("reason")) return; Condition condition = NoReason; QString text; for (QDomElement c = e.firstChildElement(); !c.isNull(); c = c.nextSiblingElement()) { if (c.tagName() == QLatin1String("text")) { text = c.text(); } else if (c.namespaceURI() != e.namespaceURI()) { // 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::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(const QDomElement &el) { static QMap sendersMap({ {QStringLiteral("initiator"), Senders::Initiator}, {QStringLiteral("none"), Senders::Initiator}, {QStringLiteral("responder"), Senders::Initiator} }); 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 { if (!isValid()) { return QDomElement(); } auto el = doc->createElement(QLatin1String(tagName)); setCreatorAttr(el, creator); el.setAttribute(QLatin1String("name"), name); QString sendersStr; switch (senders) { case Senders::None: sendersStr = QLatin1String("none"); break; case Senders::Initiator: sendersStr = QLatin1String("initiator"); break; case Senders::Responder: sendersStr = QLatin1String("responder"); break; case Senders::Both: default: 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; } ContentBase::Creator ContentBase::creatorAttr(const QDomElement &el) { auto creatorStr = el.attribute(QLatin1String("creator")); if (creatorStr == QLatin1String("initiator")) { return Creator::Initiator; } if (creatorStr == QLatin1String("responder")) { return Creator::Responder; } return Creator::NoCreator; } bool ContentBase::setCreatorAttr(QDomElement &el, Creator creator) { if (creator == Creator::Initiator) { el.setAttribute(QLatin1String("creator"), QLatin1String("initiator")); } else if (creator == Creator::Responder) { el.setAttribute(QLatin1String("creator"), QLatin1String("responder")); } else { return false; } return true; } //---------------------------------------------------------------------------- // Content //---------------------------------------------------------------------------- Content::Content(Manager *manager, const QDomElement &content) : ContentBase(content) { QDomElement descriptionEl = content.firstChildElement(QLatin1String("description")); QDomElement transportEl = content.firstChildElement(QLatin1String("transport")); //QDomElement securityEl = content.firstChildElement(QLatin1String("security")); if (descriptionEl.isNull() || transportEl.isNull()) { return; } /* auto description = manager->descriptionFromXml(descriptionEl); auto transport = manager->transportFromXml(transportEl); if (!securityEl.isNull()) { security = client->jingleManager()->securityFromXml(securityEl); // if security == 0 then then its unsupported? just ignore it atm // according to xtls draft responder may omit security when unsupported. } */ // TODO description // TODO transports // TODO security } QDomElement Content::toXml(QDomDocument *doc) const { auto el = ContentBase::toXml(doc, "content"); if (el.isNull()) { // TODO check/init other elements of content return el; } /* content.appendChild(description->toXml(doc)); content.appendChild(transport->toXml(doc)); if (!security.isNull()) { content.appendChild(security->toXml(doc)); } */ return el; } //---------------------------------------------------------------------------- // JT - Jingle Task //---------------------------------------------------------------------------- class JTPush : public Task { Q_OBJECT public: JTPush(Task *parent) : Task(parent) { } ~JTPush(){} bool take(const QDomElement &el) { if (el.tagName() != QLatin1String("iq") || el.attribute(QLatin1String("type")) != QLatin1String("set")) { return false; } return client()->jingleManager()->incomingIQ(el); } }; //---------------------------------------------------------------------------- // Session //---------------------------------------------------------------------------- class Session::Private { public: Manager *manager; }; Session::Session(Manager *manager) : d(new Private) { d->manager = manager; } Session::~Session() { } void Session::initiate(const Content &content) { Q_UNUSED(content) // TODO } //---------------------------------------------------------------------------- // Session //---------------------------------------------------------------------------- class Application::Private { public: Client *client; }; Application::Application(Client *client) : d(new Private) { d->client = client; } Application::~Application() { } Client *Application::client() const { return d->client; } } // namespace Jingle } // namespace XMPP psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/jingle.h000066400000000000000000000144751342663516400232440ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef JINGLE_H #define JINGLE_H #include "xmpp_hash.h" #include #include class QDomElement; class QDomDocument; namespace XMPP { class Client; namespace Jingle { extern const QString NS; class Manager; class Jingle { public: enum Action { NoAction, // non-standard, just a default ContentAccept, ContentAdd, ContentModify, ContentReject, ContentRemove, DescriptionInfo, SecurityInfo, SessionAccept, SessionInfo, SessionInitiate, SessionTerminate, TransportAccept, TransportInfo, TransportReject, TransportReplace }; Jingle(); Jingle(Manager *manager, const QDomElement &e); Jingle(const Jingle &); ~Jingle(); QDomElement toXml(QDomDocument *doc) const; inline bool isValid() const { return d != nullptr; } Action action() const; 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(const QDomElement &el); Reason(const Reason &other); 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: enum class Creator { NoCreator, // not standard, just a default Initiator, Responder }; enum class Senders { Both, // it's default None, Initiator, Responder }; inline ContentBase(){} ContentBase(const QDomElement &el); inline bool isValid() const { return creator != Creator::NoCreator && !name.isEmpty(); } protected: QDomElement toXml(QDomDocument *doc, const char *tagName) const; static Creator creatorAttr(const QDomElement &el); static bool setCreatorAttr(QDomElement &el, Creator creator); Creator creator = Creator::NoCreator; QString name; Senders senders = Senders::Both; QString disposition; // default "session" }; class Description { public: enum class Type { Unrecognized, // non-standard, just a default FileTransfer, // urn:xmpp:jingle:apps:file-transfer:5 }; private: class Private; Private *ensureD(); QSharedDataPointer d; }; class TransportManager { public: /* Categorization by speed, reliability and connectivity - speed: realtim, fast, slow - reliability: reliable, not reliable - 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 */ enum Feature { // connection establishment HardToConnect = 0x01, AlwaysConnect = 0x02, // reliability NotReliable = 0x10, Reliable = 0x20, // speed Slow = 0x100, Fast = 0x200, RealTime = 0x400 }; Q_DECLARE_FLAGS(Features, Feature) }; class Security { }; class Content : public ContentBase // TODO that's somewhat wrong mixing pimpl with this base { public: inline Content(){} Content(Manager *manager, const QDomElement &content); QDomElement toXml(QDomDocument *doc) const; QSharedPointer description; QSharedPointer transport; QSharedPointer security; Reason reason; }; class Manager; class Session : public QObject { Q_OBJECT public: Session(Manager *manager); ~Session(); void initiate(const Content &content); private: class Private; QScopedPointer d; }; class Application : public QObject { Q_OBJECT public: Application(Client *client); virtual ~Application(); Client *client() const; virtual void incomingSession(Session *session) = 0; virtual QSharedPointer descriptionFromXml(const QDomElement &el) = 0; private: class Private; QScopedPointer d; }; class Manager : public QObject { Q_OBJECT static const int MaxSessions = 1000; //1000? just to have some limit public: explicit Manager(XMPP::Client *client = 0); ~Manager(); XMPP::Client* client() const; //Session* sessionInitiate(const Jid &to, const QDomElement &description, const QDomElement &transport); // TODO void setRedirection(const Jid &to); void registerApp(const QString &ns, Application *app); Session* newSession(const Jid &j); QSharedPointer descriptionFromXml(const QDomElement &el); QSharedPointer transportFromXml(const QDomElement &el); private: friend class JTPush; bool incomingIQ(const QDomElement &iq); class Private; QScopedPointer d; }; } // namespace Jingle } // namespace XMPP #endif // JINGLE_H psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/s5b.cpp000066400000000000000000001702031342663516400230100ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "s5b.h" #include #include #include #include #include #include "xmpp_xmlcommon.h" #include "im.h" #include "socks.h" #ifdef Q_OS_WIN # include #else # include #endif #define MAXSTREAMHOSTS 5 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; SocksClient *client = nullptr; SocksClient *client_out = nullptr; SocksUDP *client_udp = nullptr; SocksUDP *client_out_udp = nullptr; S5BConnector *conn = nullptr; S5BConnector *proxy_conn = 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 = 0; d->su = 0; ++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 = 0; } delete d->su; d->su = 0; 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; QPointer relatedServer; bool udp_init = false; QHostAddress udp_addr; int udp_port = 0; }; class S5BManager::Private { public: Client *client; S5BServer *serv; 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->serv = 0; 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() { setServer(0); 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; } S5BServer *S5BManager::server() const { return d->serv; } void S5BManager::setServer(S5BServer *serv) { if(d->serv) { d->serv->unlink(this); d->serv = 0; } if(serv) { d->serv = serv; d->serv->link(this); } } BSConnection *S5BManager::createConnection() { return new S5BConnection(this); } S5BConnection *S5BManager::takeIncoming() { if(d->incomingConns.isEmpty()) return 0; 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 // if we have a server, then check through it if(d->serv) { if(findServerEntryByHash(key) || findServerEntryByHash(key_out)) return false; } else { if(findEntryByHash(key) || findEntryByHash(key_out)) return false; } return true; } const char* S5BManager::sidPrefix() const { return "s5b_"; } S5BConnection *S5BManager::findIncoming(const Jid &from, const QString &sid) const { foreach(S5BConnection *c, d->incomingConns) { if(c->d->peer.compare(from) && c->d->sid == sid) return c; } return 0; } S5BManager::Entry *S5BManager::findEntry(S5BConnection *c) const { foreach(Entry *e, d->activeList) { if(e->c == c) return e; } return 0; } S5BManager::Entry *S5BManager::findEntry(Item *i) const { foreach(Entry *e, d->activeList) { if(e->i == i) return e; } return 0; } S5BManager::Entry *S5BManager::findEntryByHash(const QString &key) const { foreach(Entry *e, d->activeList) { if(e->i && e->i->key == key) return e; } return 0; } S5BManager::Entry *S5BManager::findEntryBySID(const Jid &peer, const QString &sid) const { foreach(Entry *e, d->activeList) { if(e->i && e->i->peer.compare(peer) && e->sid == sid) return e; } return 0; } S5BManager::Entry *S5BManager::findServerEntryByHash(const QString &key) const { const QList &manList = d->serv->managerList(); foreach(S5BManager *m, manList) { Entry *e = m->findEntryByHash(key); if(e) return e; } return 0; } 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(); sc->deleteLater(); return; } if(e->c->d->mode == S5BConnection::Datagram) sc->grantUDPAssociate("", 0); else sc->grantConnect(); e->relatedServer = static_cast(sender()); 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::srv_unlink() { d->serv = 0; } 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->relatedServer) e->relatedServer->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 = 0; SocksUDP *client_udp = i->client_udp; i->client_udp = 0; // 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 = 0; foreach(Entry* i, d->activeList) { if(i->query == query) { e = i; break; } } if(!e) return; e->query = 0; #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() { 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; S5BServer *serv = m->server(); if(serv && serv->isActive() && !haveHost(in_hosts, self)) { QStringList hostList = serv->hostList(); foreach (const QString & it, hostList) { StreamHost h; h.setJid(self); h.setHost(it); h.setPort(serv->port()); hosts += h; } } // 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 foreach (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; foreach (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))); 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 = 0; #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 = 0; 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 = 0; 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 = 0; 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 = 0; // 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 = 0; 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 = 0; resetConnection(); error(ErrProxy); } } void S5BManager::Item::proxy_finished() { JT_S5B *j = proxy_task; proxy_task = 0; 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 = 0; 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); foreach(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 = 0; if(sc == client) { delete client_out_udp; client_out_udp = 0; sc_udp = client_udp; } else if(sc == client_out) { delete client_udp; client_udp = 0; sc_udp = client_out_udp; } sc->disconnect(this); while (!clientList.isEmpty()) { delete clientList.takeFirst(); } client = sc; client_out = 0; 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 = 0; 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(0) , client(new SocksClient) , client_udp(0) , 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 = 0; delete client; client = 0; } 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 = 0; d->active_udp = 0; 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 = 0; delete d->active; d->active = 0; 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 = 0; return c; } SocksUDP *S5BConnector::takeUDP() { SocksUDP *c = d->active_udp; d->active_udp = 0; 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 = 0; d->active_udp = i->client_udp; i->client_udp = 0; 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? foreach(Item *i, d->itemList) { if(i->host.jid().compare(streamHost) && i->client_udp) { i->udpSuccess(); return; } } } //---------------------------------------------------------------------------- // S5BServer //---------------------------------------------------------------------------- class S5BServer::Item : public QObject { Q_OBJECT public: SocksClient *client; QString host; QTimer expire; Item(SocksClient *c) : QObject(0) { client = c; connect(client, SIGNAL(incomingMethods(int)), SLOT(sc_incomingMethods(int))); connect(client, SIGNAL(incomingConnectRequest(QString,int)), SLOT(sc_incomingConnectRequest(QString,int))); connect(client, SIGNAL(error(int)), SLOT(sc_error(int))); connect(&expire, SIGNAL(timeout()), SLOT(doError())); resetExpiration(); } ~Item() { delete client; } void resetExpiration() { expire.start(30000); } signals: void result(bool); private slots: void doError() { expire.stop(); delete client; client = 0; result(false); } void sc_incomingMethods(int m) { if(m & SocksClient::AuthNone) client->chooseMethod(SocksClient::AuthNone); else doError(); } void sc_incomingConnectRequest(const QString &_host, int port) { if(port == 0) { host = _host; client->disconnect(this); emit result(true); } else doError(); } void sc_error(int) { doError(); } }; class S5BServer::Private { public: SocksServer serv; QStringList hostList; QList manList; QList itemList; }; S5BServer::S5BServer(QObject *parent) :QObject(parent) { d = new Private; connect(&d->serv, SIGNAL(incomingReady()), SLOT(ss_incomingReady())); connect(&d->serv, SIGNAL(incomingUDP(QString,int,QHostAddress,int,QByteArray)), SLOT(ss_incomingUDP(QString,int,QHostAddress,int,QByteArray))); } S5BServer::~S5BServer() { unlinkAll(); delete d; } bool S5BServer::isActive() const { return d->serv.isActive(); } bool S5BServer::start(int port) { d->serv.stop(); //return d->serv.listen(port, true); return d->serv.listen(port); } void S5BServer::stop() { d->serv.stop(); } void S5BServer::setHostList(const QStringList &list) { d->hostList = list; } QStringList S5BServer::hostList() const { return d->hostList; } int S5BServer::port() const { return d->serv.port(); } void S5BServer::ss_incomingReady() { Item *i = new Item(d->serv.takeIncoming()); #ifdef S5B_DEBUG qDebug("S5BServer: incoming connection from %s:%d\n", qPrintable(i->client->peerAddress().toString()), i->client->peerPort()); #endif connect(i, SIGNAL(result(bool)), SLOT(item_result(bool))); d->itemList.append(i); } void S5BServer::ss_incomingUDP(const QString &host, int port, const QHostAddress &addr, int sourcePort, const QByteArray &data) { if(port != 0 && port != 1) return; foreach(S5BManager* m, d->manList) { if(m->srv_ownsHash(host)) { m->srv_incomingUDP(port == 1 ? true : false, addr, sourcePort, host, data); return; } } } void S5BServer::item_result(bool b) { Item *i = static_cast(sender()); #ifdef S5B_DEBUG qDebug("S5BServer item result: %d\n", b); #endif if(!b) { d->itemList.removeAll(i); delete i; return; } SocksClient *c = i->client; i->client = 0; QString key = i->host; d->itemList.removeAll(i); delete i; // find the appropriate manager for this incoming connection foreach(S5BManager *m, d->manList) { if(m->srv_ownsHash(key)) { m->srv_incomingReady(c, key); return; } } #ifdef S5B_DEBUG qDebug("S5BServer item result: unknown hash [%s]\n", qPrintable(key)); #endif // throw it away delete c; } void S5BServer::link(S5BManager *m) { d->manList.append(m); } void S5BServer::unlink(S5BManager *m) { d->manList.removeAll(m); } void S5BServer::unlinkAll() { foreach(S5BManager *m, d->manList) { m->srv_unlink(); } d->manList.clear(); } const QList & S5BServer::managerList() const { return d->manList; } void S5BServer::writeUDP(const QHostAddress &addr, int port, const QByteArray &data) { d->serv.writeUDP(addr, port, data); } //---------------------------------------------------------------------------- // 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()->createElement("query"); query.setAttribute("xmlns", S5B_NS); 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()->createElement("proxy"); p.setAttribute("xmlns", "http://affinix.com/jabber/stream"); shost.appendChild(p); } query.appendChild(shost); } if(fast) { QDomElement e = doc()->createElement("fast"); e.setAttribute("xmlns", "http://affinix.com/jabber/stream"); 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()->createElement("query"); query.setAttribute("xmlns", S5B_NS); 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()->createElement("query"); query.setAttribute("xmlns", S5B_NS); 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.attribute("xmlns") == S5B_NS) { incomingUDPSuccess(Jid(x.attribute("from")), x.attribute("dstaddr")); return true; } x = e.elementsByTagName("activate").item(0).toElement(); if(!x.isNull() && x.attribute("xmlns") == "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.attribute("xmlns") == "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.attribute("xmlns") == "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()->createElement("query"); query.setAttribute("xmlns", S5B_NS); 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()->createElement("udpsuccess"); u.setAttribute("xmlns", S5B_NS); 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()->createElement("activate"); act.setAttribute("xmlns", "http://affinix.com/jabber/stream"); 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; } } #include "s5b.moc" psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/s5b.h000066400000000000000000000240551342663516400224600ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_S5B_H #define XMPP_S5B_H #include #include #include #include "bytestream.h" #include "xmpp_bytestream.h" #include "xmpp/jid/jid.h" #include "xmpp_task.h" #include "xmpp_stanza.h" class SocksClient; class SocksUDP; namespace XMPP { class StreamHost; class Client; class S5BConnection; class S5BManager; class S5BServer; 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=0); }; class S5BManager : public BytestreamManager { Q_OBJECT public: S5BManager(Client *); ~S5BManager(); static const char* ns(); Client *client() const; S5BServer *server() const; void setServer(S5BServer *s); 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; Entry *findServerEntryByHash(const QString &key) 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); void srv_unlink(); 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=0); ~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); }; // listens on a port for serving class S5BServer : public QObject { Q_OBJECT public: S5BServer(QObject *par=0); ~S5BServer(); bool isActive() const; bool start(int port); void stop(); int port() const; void setHostList(const QStringList &); QStringList hostList() const; class Item; private slots: void ss_incomingReady(); void ss_incomingUDP(const QString &host, int port, const QHostAddress &addr, int sourcePort, const QByteArray &data); void item_result(bool); private: class Private; Private *d; friend class S5BManager; void link(S5BManager *); void unlink(S5BManager *); void unlinkAll(); const QList & managerList() const; void writeUDP(const QHostAddress &addr, int port, const QByteArray &data); }; 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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/types.cpp000066400000000000000000002521321342663516400234650ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include "im.h" #include "xmpp_features.h" #include "xmpp_xmlcommon.h" #include "xmpp_bitsofbinary.h" #include "xmpp_ibb.h" #include "xmpp_captcha.h" #include "protocol.h" #include "xmpp/blake2/blake2qt.h" #define NS_XML "http://www.w3.org/XML/1998/namespace" namespace XMPP { //---------------------------------------------------------------------------- // 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 struct { const char *text; Hash::Type hashType; } hashTypes[] = { { "sha-1", Hash::Type::Sha1 }, { "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")); if (!algo.isEmpty()) { for(size_t n = 0; n < sizeof(hashTypes) / sizeof(hashTypes[0]); ++n) { if(algo == QLatin1String(hashTypes[n].text)) { v_type = hashTypes[n].hashType; if (el.tagName() == QLatin1String("hash")) { v_data = QByteArray::fromBase64(el.text().toLatin1()); if (v_data.isEmpty()) { v_type = Type::Unknown; } } // else hash-used break; } } } } bool Hash::computeFromData(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(); } QDomElement Hash::toXml(Stanza &s) 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 = s.createElement(XMPP_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) { 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)); } } //---------------------------------------------------------------------------- // 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; } foreach(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 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 Message::StanzaId stanzaId; // XEP-0359 }; //! \brief Constructs Message with given Jid information. //! //! This function will construct a Message container. //! \param to - specify receiver (default: empty string) 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->to; } //! \brief Return sender's Jid information. Jid Message::from() const { return d->from; } QString Message::id() const { return d->id; } //! \brief Return type information QString Message::type() const { return d->type; } QString Message::lang() const { return d->lang; } //! \brief Return subject information. QString Message::subject(const QString &lang) const { return d->subject.value(lang); } //! \brief Return subject information. QString Message::subject(const QLocale &lang) const { return d->subject.value(lang.bcp47Name()); } StringMap Message::subjectMap() const { return d->subject; } //! \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->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->htmlElements.isEmpty()); } QString Message::thread() const { return d->thread; } Stanza::Error Message::error() const { return d->error; } //! \brief Set receivers information //! //! \param to - Receivers Jabber id void Message::setTo(const Jid &j) { d->to = j; //d->flag = false; } void Message::setFrom(const Jid &j) { d->from = j; //d->flag = false; } void Message::setId(const QString &s) { d->id = s; } //! \brief Set Type of message //! //! \param type - type of message your going to send void Message::setType(const QString &s) { d->type = s; //d->flag = false; } void Message::setLang(const QString &s) { d->lang = s; } //! \brief Set subject //! //! \param subject - Subject information void Message::setSubject(const QString &s, const QString &lang) { d->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) { d->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) { d->htmlElements[lang] = e; } void Message::setThread(const QString &s, bool send) { d->threadSend = send; d->thread = s; } void Message::setError(const Stanza::Error &err) { d->error = err; } const QString& Message::pubsubNode() const { return d->pubsubNode; } const QList& Message::pubsubItems() const { return d->pubsubItems; } const QList& Message::pubsubRetractions() const { return d->pubsubRetractions; } QDateTime Message::timeStamp() const { return d->timeStamp; } void Message::setTimeStamp(const QDateTime &ts, bool send) { d->timeStampSend = send; d->timeStamp = ts; } //! \brief Return list of urls attached to message. UrlList Message::urlList() const { return d->urlList; } //! \brief Add Url to the url list. //! //! \param url - url to append void Message::urlAdd(const Url &u) { d->urlList += u; } //! \brief clear out the url list. void Message::urlsClear() { d->urlList.clear(); } //! \brief Set urls to send //! //! \param urlList - list of urls to send void Message::setUrlList(const UrlList &list) { d->urlList = list; } //! \brief Return list of addresses attached to message. AddressList Message::addresses() const { return d->addressList; } //! \brief Add Address to the address list. //! //! \param address - address to append void Message::addAddress(const Address &a) { d->addressList += a; } //! \brief clear out the address list. void Message::clearAddresses() { d->addressList.clear(); } AddressList Message::findAddresses(Address::Type t) const { AddressList matches; foreach(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) { d->addressList = list; } const RosterExchangeItems& Message::rosterExchangeItems() const { return d->rosterExchangeItems; } void Message::setRosterExchangeItems(const RosterExchangeItems& items) { d->rosterExchangeItems = items; } QString Message::eventId() const { return d->eventId; } void Message::setEventId(const QString& id) { d->eventId = id; } bool Message::containsEvents() const { return !d->eventList.isEmpty(); } bool Message::containsEvent(MsgEvent e) const { return d->eventList.contains(e); } void Message::addEvent(MsgEvent e) { if (!d->eventList.contains(e)) { if (e == CancelEvent || containsEvent(CancelEvent)) d->eventList.clear(); // Reset list d->eventList += e; } } ChatState Message::chatState() const { return d->chatState; } void Message::setChatState(ChatState state) { d->chatState = state; } MessageReceipt Message::messageReceipt() const { return d->messageReceipt; } void Message::setMessageReceipt(MessageReceipt messageReceipt) { d->messageReceipt = messageReceipt; } QString Message::messageReceiptId() const { return d->messageReceiptId; } void Message::setMessageReceiptId(const QString &s) { d->messageReceiptId = s; } QString Message::xsigned() const { return d->xsigned; } void Message::setXSigned(const QString &s) { d->xsigned = s; } QString Message::xencrypted() const { return d->xencrypted; } void Message::setXEncrypted(const QString &s) { d->xencrypted = s; } const QList& Message::getMUCStatuses() const { return d->mucStatuses; } void Message::addMUCStatus(int i) { d->mucStatuses += i; } void Message::addMUCInvite(const MUCInvite& i) { d->mucInvites += i; } const QList& Message::mucInvites() const { return d->mucInvites; } void Message::setMUCDecline(const MUCDecline& de) { d->mucDecline = de; } const MUCDecline& Message::mucDecline() const { return d->mucDecline; } const QString& Message::mucPassword() const { return d->mucPassword; } void Message::setMUCPassword(const QString& p) { d->mucPassword = p; } bool Message::hasMUCUser() const { return d->hasMUCUser; } Message::StanzaId Message::stanzaId() const { return d->stanzaId; } void Message::setStanzaId(const Message::StanzaId &id) { d->stanzaId = id; } QString Message::originId() const { return d->originId; } void Message::setOriginId(const QString &id) { d->originId = id; } QString Message::invite() const { return d->invite; } void Message::setInvite(const QString &s) { d->invite = s; } const QString& Message::nick() const { return d->nick; } void Message::setNick(const QString& n) { d->nick = n; } void Message::setHttpAuthRequest(const HttpAuthRequest &req) { d->httpAuthRequest = req; } HttpAuthRequest Message::httpAuthRequest() const { return d->httpAuthRequest; } void Message::setForm(const XData &form) { d->xdata = form; } const XData& Message::getForm() const { return d->xdata; } const QDomElement& Message::sxe() const { return d->sxe; } void Message::setSxe(const QDomElement& e) { d->sxe = e; } void Message::addBoBData(const BoBData &bob) { d->bobDataList.append(bob); } QList Message::bobDataList() const { return d->bobDataList; } const IBBData& Message::ibbData() const { return d->ibbData; } void Message::setDisabledCarbons(bool disabled) { d->isDisabledCarbons = disabled; } bool Message::isDisabledCarbons() const { return d->isDisabledCarbons; } void Message::setCarbonDirection(Message::CarbonDir cd) { d->carbonDir = cd; } Message::CarbonDir Message::carbonDirection() const { return d->carbonDir; } void Message::setForwardedFrom(const Jid &jid) { d->forwardedFrom = jid; } const Jid &Message::forwardedFrom() const { return d->forwardedFrom; } bool Message::spooled() const { return d->spooled; } void Message::setSpooled(bool b) { d->spooled = b; } bool Message::wasEncrypted() const { return d->wasEncrypted; } void Message::setWasEncrypted(bool b) { d->wasEncrypted = b; } QString Message::replaceId() const { return d->replaceId; } void Message::setReplaceId(const QString& id) { d->replaceId = id; } void Message::setProcessingHints(const ProcessingHints &hints) { d->processingHints = hints; } Message::ProcessingHints Message::processingHints() const { return d->processingHints; } Stanza Message::toStanza(Stream *stream) const { 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); foreach (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 foreach (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)); } foreach (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)); // xencrypted if(!d->xencrypted.isEmpty()) s.appendChild(s.createTextElement("jabber:x:encrypted", "x", d->xencrypted)); // addresses if (!d->addressList.isEmpty()) { QDomElement as = s.createElement("http://jabber.org/protocol/address","addresses"); foreach(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"); foreach(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"); foreach(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 foreach(const BoBData &bd, d->bobDataList) { s.appendChild(bd.toXml(&s.doc())); } // Avoiding Carbons if (isDisabledCarbons() || wasEncrypted()) { 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); } 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; 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"); } 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"); e.setAttribute("xmlns", "http://jabber.org/protocol/http-auth"); 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->createElement("c"); c.setAttribute("xmlns", NS_CAPS); 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()) { cs.ext_ = ext.split(" ", QString::SkipEmptyParts); } } 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; QString 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 QString& Status::photoHash() const { return d->photoHash; } void Status::setPhotoHash(const QString& 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; } } psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_address.h000066400000000000000000000034621342663516400244570ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_ADDRESS_H #define XMPP_ADDRESS_H #include #include "xmpp/jid/jid.h" 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; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_agentitem.h000066400000000000000000000032631342663516400250060ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_AGENTITEM #define XMPP_AGENTITEM #include #include "xmpp/jid/jid.h" #include "xmpp_features.h" 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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_bitsofbinary.cpp000066400000000000000000000113321342663516400260530ustar00rootroot00000000000000/* * Copyright (C) 2010 Rion * * 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 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 * */ #include #include #include "xmpp_bitsofbinary.h" #include "xmpp_xmlcommon.h" #include "xmpp_client.h" #include "xmpp_tasks.h" using namespace XMPP; class BoBData::Private : public QSharedData { public: QByteArray data; QString type; QString cid; unsigned int maxAge; }; 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->cid.isEmpty() || d->data.isNull(); } QString BoBData::cid() const { return d->cid; } void BoBData::setCid(const QString &cid) { d->cid = cid; } 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) { d->cid = data.attribute("cid"); d->maxAge = data.attribute("max-age").toInt(); d->type = data.attribute("type"); d->data = QCA::Base64().stringToArray(data.text().replace("\n","")) .toByteArray(); } QDomElement BoBData::toXml(QDomDocument *doc) const { QDomElement data = doc->createElement("data"); data.setAttribute("xmlns", "urn:xmpp:bob"); data.setAttribute("cid", d->cid); data.setAttribute("max-age", d->maxAge); data.setAttribute("type", d->type); data.appendChild(doc->createTextNode(QCA::Base64().arrayToString(d->data))); return data; } // --------------------------------------------------------- // BoBCache // --------------------------------------------------------- BoBCache::BoBCache(QObject *parent) : QObject(parent) { } //------------------------------------------------------------------------------ // BoBManager //------------------------------------------------------------------------------ BoBManager::BoBManager(Client *client) : QObject(client) , _cache(0) { new JT_BoBServer(client->rootTask()); } void BoBManager::setCache(BoBCache *cache) { _cache = cache; } BoBData BoBManager::bobData(const QString &cid) { BoBData bd; if (_cache) { bd = _cache->get(cid); } if (bd.isNull() && _localFiles.contains(cid)) { QPair fileData = _localFiles.value(cid); QFile file(fileData.first); if (file.open(QIODevice::ReadOnly)) { bd.setCid(cid); 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.setCid(QString("sha1+%1@bob.xmpp.org").arg(QString( QCryptographicHash::hash(data, QCryptographicHash::Sha1).toHex()))); b.setData(data); b.setMaxAge(maxAge); b.setType(type); if (_cache) { _cache->put(b); } return b; } QString BoBManager::append(QFile &file, const QString &type) { bool isOpen = file.isOpen(); if (isOpen || file.open(QIODevice::ReadOnly)) { QString cid = QString("sha1+%1@bob.xmpp.org").arg( QString(QCryptographicHash::hash(file.readAll(), QCryptographicHash::Sha1).toHex())); _localFiles[cid] = QPair(file.fileName(), type); if (!isOpen) { file.close(); } return cid; } return QString(); } void BoBManager::append(const BoBData &data) { if (!data.isNull() && _cache) { _cache->put(data); } } psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_bitsofbinary.h000066400000000000000000000050651342663516400255260ustar00rootroot00000000000000/* * Copyright (C) 2010 Rion * * 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 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 * */ #ifndef XMPP_BITSOFBINARY_H #define XMPP_BITSOFBINARY_H #include #include #include #include #include #include "xmpp/jid/jid.h" namespace XMPP { class JT_BitsOfBinary; class Client; class BoBData { class Private; public: BoBData(); BoBData(const BoBData &other); BoBData(const QDomElement &); ~BoBData(); BoBData &operator=(const BoBData &other); bool isNull() const; QString cid() const; void setCid(const QString &); 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 QString &) = 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); QString append(QFile &file, const QString &type = "application/octet-stream"); void append(const BoBData &); private: BoBCache *_cache; QHash > _localFiles; //cid => (filename, mime) }; } #endif // XMPP_BITSOFBINARY_H psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_bytestream.cpp000066400000000000000000000031171342663516400255410ustar00rootroot00000000000000/* * bytestream_manager.cpp - base class for bytestreams over xmpp * Copyright (C) 2003 Justin Karneges, Rion * * 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 * */ #include #include "xmpp_bytestream.h" #include "xmpp_client.h" namespace XMPP { BytestreamManager::BytestreamManager(Client *parent) : QObject(parent) { } BytestreamManager::~BytestreamManager() { } QString BytestreamManager::genUniqueSID(const Jid &peer) const { // get unused key QString sid; do { sid = QString("%1%2").arg(sidPrefix()) .arg(qrand() & 0xffff, 4, 16, QChar('0')); } 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; } } }psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_bytestream.h000066400000000000000000000041411342663516400252040ustar00rootroot00000000000000/* * bytestream_manager.h - base class for bytestreams over xmpp * Copyright (C) 2003 Justin Karneges, Rion * * 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 * */ #ifndef BYTESTREAM_MANAGER_H #define BYTESTREAM_MANAGER_H #include #include "xmpp/jid/jid.h" #include "bytestream.h" namespace XMPP { class Client; class BytestreamManager; class BSConnection : public ByteStream { public: enum Error { ErrRefused = ErrCustom, ErrConnect, ErrProxy, ErrSocket }; enum State { Idle, Requesting, Connecting, WaitingForAccept, Active }; BSConnection(QObject *parent = 0) : 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(); }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_caps.cpp000066400000000000000000000333251342663516400243140ustar00rootroot00000000000000/* * capsregistry.cpp * Copyright (C) 2006-2016 Remko Troncon, Rion * * 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 library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ /* * 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 #include #include #include #include #include "xmpp_features.h" #include "xmpp_caps.h" #include "xmpp_discoinfotask.h" #include "xmpp_client.h" #include "xmpp_xmlcommon.h" 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) : 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. foreach(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()) { name = cs.node(); if (name.startsWith("http://")) name = name.right(name.length() - 7); else if (name.startsWith("https://")) name = name.right(name.length() - 8); if (name.startsWith("www.")) name = name.right(name.length() - 4); int cut_pos = name.indexOf("/"); if (cut_pos != -1) name = name.left(cut_pos); } 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.554/iris/src/xmpp/xmpp-im/xmpp_caps.h000066400000000000000000000064411342663516400237600ustar00rootroot00000000000000/* * Copyright (C) 2016 Remko Troncon, Rion * * 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 * */ #ifndef XMPP_CAPS_H #define XMPP_CAPS_H #include #include "xmpp_features.h" #include "xmpp_discoitem.h" #include "xmpp_status.h" 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 // CAPS_H psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_captcha.h000066400000000000000000000035751342663516400244420ustar00rootroot00000000000000/* * Copyright (C) 2016 Rion * * 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 * */ #ifndef XMPP_CAPTCHA_H #define XMPP_CAPTCHA_H #include #include "xmpp/jid/jid.h" #include "xmpp_url.h" namespace XMPP { class Message; class XData; class CaptchaChallengePrivate; 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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_chatstate.h000066400000000000000000000020021342663516400247770ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_CHATSTATE #define XMPP_CHATSTATE namespace XMPP { typedef enum { StateNone, StateActive, StateComposing, StatePaused, StateInactive, StateGone } ChatState; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_client.h000066400000000000000000000170551342663516400243130ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_CLIENT_H #define XMPP_CLIENT_H #include #include #include #include "xmpp/jid/jid.h" #include "xmpp_status.h" #include "xmpp_discoitem.h" class QString; class QDomElement; class QDomDocument; class ByteStream; class QNetworkAccessManager; namespace XMPP { class ClientStream; class Features; class FileTransferManager; class IBBManager; class JidLinkManager; class LiveRoster; class LiveRosterItem; class Message; class Resource; class ResourceList; class Roster; class RosterItem; class S5BManager; class BSConnection; class Stream; class Task; class CapsManager; class EncryptionHandler; class ServerInfoManager; class HttpFileUploadManager; namespace Jingle { class Manager; } } namespace XMPP { class Client : public QObject { Q_OBJECT public: Client(QObject *parent=0); ~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 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::null) 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; 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 slotRosterDelimiterRequestFinished(); 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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_discoinfotask.cpp000066400000000000000000000076531342663516400262330ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include #include "xmpp_task.h" #include "xmpp/jid/jid.h" #include "xmpp_discoitem.h" #include "xmpp_discoinfotask.h" #include "xmpp_xmlcommon.h" #include "xmpp_client.h" #include "xmpp_caps.h" 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()->createElement("query"); query.setAttribute("xmlns", "http://jabber.org/protocol/disco#info"); 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.554/iris/src/xmpp/xmpp-im/xmpp_discoinfotask.h000066400000000000000000000033531342663516400256710ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_DISCOINFOTASK_H #define XMPP_DISCOINFOTASK_H #include "xmpp_task.h" #include "xmpp_discoitem.h" namespace XMPP { class Jid; } class QString; class QDomElement; namespace XMPP { 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::null, 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; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_discoitem.cpp000066400000000000000000000205531342663516400253450ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include "xmpp_discoitem.h" 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; qSort(idents); foreach (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(); qSort(fl); prep += fl; QMap forms; foreach (const XData &xd, d->exts) { if (xd.registrarType().isEmpty()) { continue; } if (forms.contains(xd.registrarType())) { return QString(); // ill-formed } forms.insert(xd.registrarType(), xd); } foreach (const XData &xd, forms.values()) { prep << xd.registrarType(); QMap values; foreach (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. } qSort(v); 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.attribute("xmlns") == 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); foreach (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); } } foreach (const QString &f, d->features.list()) { QDomElement fel = q.appendChild(doc->createElement(QLatin1String("feature"))).toElement(); fel.setAttribute("var", f); } foreach (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; } void DiscoItem::setExtensions(const QList &extlist) { d->exts = extlist; } XData DiscoItem::registeredExtension(const QString &ns) const { foreach (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::null; 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.554/iris/src/xmpp/xmpp-im/xmpp_discoitem.h000066400000000000000000000062671342663516400250200ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_DISCOITEM #define XMPP_DISCOITEM #include #include #include "xmpp/jid/jid.h" #include "xmpp_features.h" #include "xmpp_xdata.h" #include "xmpp_agentitem.h" 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::null, const QString &name = QString::null) : 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; 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); } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_encryptionhandler.h000066400000000000000000000022051342663516400265540ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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; }; } #endif //PSI_XMPP_ENCRYPTIONHANDLER_H psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_features.cpp000066400000000000000000000151631342663516400252040ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include #include #include "xmpp_features.h" 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 { return _list.toList(); } void Features::setList(const QStringList &l) { _list = QSet::fromList(l); } void Features::setList(const QSet &l) { _list = l; } void Features::addFeature(const QString& s) { _list += s; } bool Features::test(const QStringList &ns) const { return _list.contains(QSet::fromList(ns)); } 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); } // 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 = 0; 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.554/iris/src/xmpp/xmpp-im/xmpp_features.h000066400000000000000000000072751342663516400246560ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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; [[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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_hash.h000066400000000000000000000040241342663516400237500ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_HASH_H #define XMPP_HASH_H #include "xmpp_stanza.h" #include #define XMPP_HASH_NS "urn:xmpp:hashes:2" // TODO make nsdb.cpp/h with static declarations of all ns class QDomElement; namespace XMPP { 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 }; inline Hash(Type type = Type::Unknown) : v_type(type) {} Hash(const QDomElement&); inline Type type() const { return v_type; } inline void setType(Type t) { v_type = t; } inline QByteArray data() const { return v_data; } inline void setData(const QByteArray &d) { v_data = d; } // sets already computed hash bool computeFromData(const QByteArray &); // computes hash from passed data QDomElement toXml(Stanza&) const; static void populateFeatures(XMPP::Features &); private: Type v_type = Type::Unknown; QByteArray v_data; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_htmlelement.h000066400000000000000000000026011342663516400253420ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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_; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_httpauthrequest.h000066400000000000000000000032001342663516400262720ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_AUTHREQUEST_H #define XMPP_AUTHREQUEST_H #include class QDomElement; class QDomDocument; 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_; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_ibb.cpp000066400000000000000000000366421342663516400241270ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "xmpp_ibb.h" #include #include "xmpp_xmlcommon.h" #include #include #define IBB_PACKET_SIZE 4096 #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 = 0; //QByteArray recvBuf, sendBuf; bool closePending, closing; int id; // connection id }; IBBConnection::IBBConnection(IBBManager *m) : BSConnection(m) { d = new Private; d->m = m; d->j = 0; d->blockSize = IBB_PACKET_SIZE; 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 = 0; 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::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->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) { 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 = 0; 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 = textTag(doc, "data", QString::fromLatin1(data.toBase64())).toElement(); query.setAttribute("xmlns", IBB_NS); 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()? 0 : 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) == NULL; } 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 { foreach(IBBConnection* c, d->activeConns) { if(c->sid() == sid && (peer.isEmpty() || c->peer().compare(peer)) ) return c; } return 0; } 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) { d->mode = ModeRequest; QDomElement iq; d->to = to; iq = createIQ(doc(), "set", to.full(), id()); QDomElement query = doc()->createElement("open"); //genUniqueKey query.setAttribute("xmlns", IBB_NS); query.setAttribute("sid", sid); query.setAttribute("block-size", IBB_PACKET_SIZE); 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()->createElement("close")).toElement(); query.setAttribute("xmlns", IBB_NS); 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.attribute("xmlns") == 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.attribute("xmlns") == 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.attribute("xmlns") == 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.554/iris/src/xmpp/xmpp-im/xmpp_ibb.h000066400000000000000000000121371342663516400235650ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef JABBER_IBB_H #define JABBER_IBB_H #include #include #include #include "bytestream.h" #include "xmpp_bytestream.h" #include "im.h" #include "xmpp_task.h" 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: enum { ErrRequest, ErrData }; enum { Idle, Requesting, WaitingForAccept, Active }; IBBConnection(IBBManager *); ~IBBConnection(); 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); 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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_liveroster.h000066400000000000000000000027511342663516400252300ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_LIVEROSTER_H #define XMPP_LIVEROSTER_H #include #include "xmpp_liverosteritem.h" namespace XMPP { class Jid; class LiveRoster : public QList { public: LiveRoster(); ~LiveRoster(); LiveRoster(const LiveRoster &other); 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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_liverosteritem.h000066400000000000000000000032751342663516400261110ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_LIVEROSTERITEM_H #define XMPP_LIVEROSTERITEM_H #include "xmpp_status.h" #include "xmpp_resourcelist.h" #include "xmpp_rosteritem.h" namespace XMPP { class LiveRosterItem : public RosterItem { public: LiveRosterItem(const Jid &j=""); LiveRosterItem(const RosterItem &); ~LiveRosterItem(); 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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_message.h000066400000000000000000000162101342663516400244510ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_MESSAGE_H #define XMPP_MESSAGE_H #include "xmpp_stanza.h" #include "xmpp_url.h" #include "xmpp_chatstate.h" #include "xmpp_receipts.h" #include "xmpp_address.h" #include "xmpp_rosterx.h" #include "xmpp_muc.h" #include class QString; class QDateTime; namespace XMPP { class Jid; class PubSubItem; class PubSubRetraction; class HTMLElement; class HttpAuthRequest; class XData; class BoBData; class IBBData; 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(const Jid &to=""); Message(const Message &from); Message & operator=(const Message &from); ~Message(); bool operator ==(const Message &from) const; Jid to() const; Jid from() const; QString id() const; QString type() const; QString lang() const; QString subject(const QString &lang=QString::null) 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 const QString& pubsubNode() const; const QList& pubsubItems() const; 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 const RosterExchangeItems& rosterExchangeItems() const; void setRosterExchangeItems(const RosterExchangeItems&); // XEP-172 void setNick(const QString&); const QString& nick() const; // XEP-0070 void setHttpAuthRequest(const HttpAuthRequest&); HttpAuthRequest httpAuthRequest() const; // XEP-0004 void setForm(const XData&); const XData& getForm() const; // XEP-xxxx SXE void setSxe(const QDomElement&); const QDomElement& sxe() const; // XEP-0231 bits of binary void addBoBData(const BoBData &); QList bobDataList() const; // XEP-0047 ibb const 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); const 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); const QList& getMUCStatuses() const; void addMUCInvite(const MUCInvite&); const QList& mucInvites() const; void setMUCDecline(const MUCDecline&); const MUCDecline& mucDecline() const; 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); // 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; }; } Q_DECLARE_OPERATORS_FOR_FLAGS(XMPP::Message::ProcessingHints) #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_muc.h000066400000000000000000000070321342663516400236130ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_MUC_H #define XMPP_MUC_H #include #include #include "xmpp/jid/jid.h" 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_; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_pubsubitem.h000066400000000000000000000022501342663516400252030ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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_; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_pubsubretraction.h000066400000000000000000000021411342663516400264160ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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_; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_receipts.h000066400000000000000000000020071342663516400246420ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef XMPP_RECEIPTS_H #define XMPP_RECEIPTS_H namespace XMPP { typedef enum { ReceiptNone, ReceiptRequest, ReceiptReceived } MessageReceipt; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_resource.h000066400000000000000000000023751342663516400246630ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_RESOURCE_H #define XMPP_RESOURCE_H #include #include "xmpp_status.h" 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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_resourcelist.h000066400000000000000000000023761342663516400255600ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_RESOURCELIST_H #define XMPP_RESOURCELIST_H #include #include "xmpp_resource.h" 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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_roster.h000066400000000000000000000026171342663516400243510ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_ROSTER_H #define XMPP_ROSTER_H #include #include "xmpp_rosteritem.h" class QDomDocument; class QDomElement; namespace XMPP { class Jid; class Roster : public QList { public: Roster(); ~Roster(); Roster(const Roster &other); 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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_rosteritem.h000066400000000000000000000043221342663516400252230ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_ROSTERITEM_H #define XMPP_ROSTERITEM_H #include #include #include "xmpp/jid/jid.h" 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(); 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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_rosterx.h000066400000000000000000000034651342663516400245430ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_ROSTERX_H #define XMPP_ROSTERX_H #include #include #include "xmpp/jid/jid.h" 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; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_serverinfomanager.cpp000066400000000000000000000261361342663516400271050ustar00rootroot00000000000000/* * 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 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 * */ #include "xmpp_serverinfomanager.h" #include "xmpp_tasks.h" #include "xmpp_caps.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.append(si.key()); } else if (sqIt->options & SQ_CheckAllOnNoMatch) { sqIt->spareServicesToQuery.append(si.key()); } } else { sqIt->servicesToQuery.append(si.key()); } } if (sqIt->servicesToQuery.isEmpty()) { sqIt->servicesToQuery = sqIt->spareServicesToQuery; sqIt->spareServicesToQuery.clear(); } if (sqIt->servicesToQuery.isEmpty()) { 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.isEmpty()) { // 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(); foreach(DiscoItem::Identity i, is) { if (i.category == "pubsub" && i.type == "pep") _hasPEP = true; } for (const auto &x: jt->item().extensions()) { if (x.type() == XData::Data_Result && x.registrarType() == QLatin1String("http://jabber.org/network/serverinfo")) { for (const auto &f: x.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.554/iris/src/xmpp/xmpp-im/xmpp_serverinfomanager.h000066400000000000000000000122001342663516400265350ustar00rootroot00000000000000/* * 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 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 * */ #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; QLinkedList servicesToQuery; QLinkedList 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 psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_status.h000066400000000000000000000116531342663516400243560ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_STATUS_H #define XMPP_STATUS_H #include #include #include #include #include #include "xmpp_muc.h" #include "xmpp_bitsofbinary.h" namespace XMPP { class DiscoItem; 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::null); 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 StatusPrivate; 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 QString& photoHash() const; void setPhotoHash(const QString&); bool hasPhotoHash() const; // XEP-0231 bits of binary void addBoBData(const BoBData &); QList bobDataList() const; private: QSharedDataPointer d; }; } Q_DECLARE_METATYPE(XMPP::Status) #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_subsets.cpp000066400000000000000000000165151342663516400250600ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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->createElement(QStringLiteral("set")); e.setAttribute(QStringLiteral("xmlns"), xmlns_ns_rsm); 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::null; d->result.lastId = QString::null; 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.attribute(QLatin1String("xmlns")) == xmlns_ns_rsm) return el; if (child) { QDomElement e = el.firstChildElement(QLatin1String("set")); while (!e.isNull()) { if (e.attribute(QLatin1String("xmlns")) == 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::null); 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.554/iris/src/xmpp/xmpp-im/xmpp_subsets.h000066400000000000000000000032051342663516400245150ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_task.cpp000066400000000000000000000154471342663516400243350ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include "xmpp_task.h" #include "xmpp_client.h" #include "xmpp_xmlcommon.h" #define DEFAULT_TIMEOUT 120 using namespace XMPP; class Task::TaskPrivate { public: TaskPrivate() = default; QString id; bool success = false; int statusCode = 0; QString statusString; 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; } 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; getErrorFromElement(e, d->client->streamBaseNS(), &d->statusCode, &d->statusString); 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; str.vsprintf(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; } psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_task.h000066400000000000000000000044451342663516400237760ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef XMPP_TASK_H #define XMPP_TASK_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; 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=""); private slots: void clientDisconnected(); void timeoutFinished(); void done(); private: void init(); class TaskPrivate; TaskPrivate *d; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_tasks.cpp000066400000000000000000001563111342663516400245140ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include #include "xmpp_tasks.h" #include "xmpp_xmlcommon.h" #include "xmpp_vcard.h" #include "xmpp_bitsofbinary.h" #include "xmpp_captcha.h" #include "xmpp/base/timezone.h" #include "xmpp_caps.h" 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()->createElement("session"); session.setAttribute("xmlns",NS_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()->createElement("query"); query.setAttribute("xmlns", "jabber:iq:register"); 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()->createElement("query"); query.setAttribute("xmlns", "jabber:iq:register"); 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()->createElement("query"); query.setAttribute("xmlns", "jabber:iq:register"); 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()->createElement("query"); query.setAttribute("xmlns", "jabber:iq:register"); 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()->createElement("query"); query.setAttribute("xmlns", "jabber:iq:register"); 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()->createElement("query"); query.setAttribute("xmlns", "jabber:iq:register"); 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.attribute("xmlns") == "jabber:x:data") { d->xdata.fromXml(i); d->hasXData = true; } else if(i.tagName() == "data" && i.attribute("xmlns") == "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 = 0; } 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, 0, this, 0); 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 = 0; } //---------------------------------------------------------------------------- // JT_Roster //---------------------------------------------------------------------------- class JT_Roster::Private { public: Private() = default; Roster roster; QString groupsDelimiter; QList itemList; }; JT_Roster::JT_Roster(Task *parent) :Task(parent) { type = -1; d = new Private; } JT_Roster::~JT_Roster() { delete d; } void JT_Roster::get() { type = 0; //to = client()->host(); iq = createIQ(doc(), "get", to.full(), id()); QDomElement query = doc()->createElement("query"); query.setAttribute("xmlns", "jabber:iq:roster"); iq.appendChild(query); } void JT_Roster::set(const Jid &jid, const QString &name, const QStringList &groups) { type = 1; //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 = 2; //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 = 3; //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 = 4; //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 == 0) { send(iq); } else if(type == 1 || type == 2) { //to = client()->host(); iq = createIQ(doc(), "set", to.full(), id()); QDomElement query = doc()->createElement("query"); query.setAttribute("xmlns", "jabber:iq:roster"); iq.appendChild(query); foreach (const QDomElement& it, d->itemList) query.appendChild(it); send(iq); } else if (type == 3) { send(iq); } else if (type == 4) { 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 != 1) return ""; QDomElement i = doc()->createElement("request"); i.setAttribute("type", "JT_Roster"); foreach (const QDomElement& it, d->itemList) i.appendChild(it); return lineEncode(Stream::xmlToString(i)); return ""; } 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 = 1; 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; // get if(type == 0) { if(x.attribute("type") == "result") { QDomElement q = queryTag(x); d->roster = xmlReadRoster(q, false); setSuccess(); } else { setError(x); } return true; } // set else if(type == 1) { if(x.attribute("type") == "result") setSuccess(); else setError(x); return true; } // remove else if(type == 2) { setSuccess(); return true; } // getGroupsDelimiter else if (type == 3) { 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; } // setGroupsDelimiter else if (type == 4) { 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 = textTag(doc(), "x", s.keyID()); x.setAttribute("xmlns", "http://jabber.org/protocol/e2e"); tag.appendChild(x); } if(!s.xsigned().isEmpty()) { QDomElement x = textTag(doc(), "x", s.xsigned()); x.setAttribute("xmlns", "jabber:x:signed"); tag.appendChild(x); } if (client()->capsManager()->isEnabled()) { CapsSpec cs = client()->caps(); if (cs.isValid()) { tag.appendChild(cs.toXml(doc())); } } if(s.isMUC()) { QDomElement m = doc()->createElement("x"); m.setAttribute("xmlns","http://jabber.org/protocol/muc"); 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()->createElement("x"); m.setAttribute("xmlns", "vcard-temp:x:update"); m.appendChild(textTag(doc(), "photo", s.photoHash())); tag.appendChild(m); } // bits of binary foreach(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 = textTag(doc(),"nick",nick); nick_tag.setAttribute("xmlns","http://jabber.org/protocol/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.attribute("xmlns") == "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.attribute("xmlns") == "jabber:x:delay") { if(i.hasAttribute("stamp") && !stamp.isValid()) { stamp = stamp2TS(i.attribute("stamp")); } } else if(i.tagName() == "delay" && i.attribute("xmlns") == "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.attribute("xmlns") == "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.attribute("xmlns") == "jabber:x:signed") { p.setXSigned(tagContent(i)); } else if(i.tagName() == "x" && i.attribute("xmlns") == "http://jabber.org/protocol/e2e") { p.setKeyID(tagContent(i)); } else if(i.tagName() == "c" && i.attribute("xmlns") == 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.attribute("xmlns") == "vcard-temp:x:update") { QDomElement t; t = i.firstChildElement("photo"); if (!t.isNull()) p.setPhotoHash(tagContent(t).toLower()); // 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.attribute("xmlns") == "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.attribute("xmlns") == "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 = oldStyleNS(s.element()); auto encryptionHandler = client()->encryptionHandler(); bool wasEncrypted = encryptionHandler && encryptionHandler->encryptMessageElement(e); m.setWasEncrypted(wasEncrypted); // 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; bool wasEncrypted = d->m_encryptionHandler != nullptr && d->m_encryptionHandler->decryptMessageElement(e1) && !e1.isNull(); if (wasEncrypted && 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.attribute("xmlns") == 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.attribute(QLatin1String("xmlns")) == 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.attribute(QLatin1String("xmlns")) == 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); } m.setWasEncrypted(wasEncrypted); 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()->createElement("vCard"); v.setAttribute("xmlns", "vcard-temp"); 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()->createElement("query"); query.setAttribute("xmlns", "jabber:iq:search"); 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()->createElement("query"); query.setAttribute("xmlns", "jabber:iq:search"); 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()->createElement("query"); query.setAttribute("xmlns", "jabber:iq:search"); 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.attribute("xmlns") == "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.attribute("xmlns") == "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()->createElement("query"); query.setAttribute("xmlns", "jabber:iq:version"); 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()->createElement("time"); time.setAttribute("xmlns", "urn:xmpp: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()->createElement("query"); query.setAttribute("xmlns", "jabber:iq:version"); 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()->createElement("item-not-found"); error_type.setAttribute("xmlns","urn:ietf:params:xml:ns:xmpp-stanzas"); error.appendChild(error_type); send(error_reply); } return true; } if (!ns.isEmpty()) { return false; } ns = e.firstChildElement("time").attribute("xmlns"); if (ns == "urn:xmpp:time") { QDomElement iq = createIQ(doc(), "result", e.attribute("from"), e.attribute("id")); QDomElement time = doc()->createElement("time"); time.setAttribute("xmlns", ns); 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()->createElement("query"); query.setAttribute("xmlns", "jabber:iq:gateway"); 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()->createElement("query"); query.setAttribute("xmlns", "jabber:iq:gateway"); 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()->createElement("query"); query.setAttribute("xmlns", "http://jabber.org/protocol/disco#items"); 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()->createElement("query"); query.setAttribute("xmlns", "http://jabber.org/protocol/disco#items"); // 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.attribute("xmlns") == "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()->createElement("data"); data.setAttribute("xmlns", "urn:xmpp:bob"); 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.attribute("xmlns") == "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()->createElement("enable"); enable.setAttribute("xmlns", "urn:xmpp:carbons:2"); _iq.appendChild(enable); } void JT_MessageCarbons::disable() { _iq = createIQ(doc(), "set", "", id()); QDomElement disable = doc()->createElement("disable"); disable.setAttribute("xmlns", "urn:xmpp:carbons:2"); _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.554/iris/src/xmpp/xmpp-im/xmpp_tasks.h000066400000000000000000000234541342663516400241620ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef JABBER_TASKS_H #define JABBER_TASKS_H #include #include #include #include "im.h" #include "xmpp_vcard.h" #include "xmpp_discoinfotask.h" #include "xmpp_subsets.h" #include "xmpp_encryptionhandler.h" namespace XMPP { class Roster; class Status; class BoBData; class CaptchaChallenge; 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: 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::null); 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; }; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_url.h000066400000000000000000000023751342663516400236360ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #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; } #endif psi-plus-snapshots-1.4.554/iris/src/xmpp/xmpp-im/xmpp_vcard.cpp000066400000000000000000000752731342663516400244750ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "xmpp_vcard.h" #include #include #include // needed for image format recognition #include #include #include #include #include #include "xmpp_xmlcommon.h" 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"); qWarning() << QString("WARNING! VCard::image2type: unknown format = '%1'").arg(format.isNull() ? QString("UNKNOWN") : format); return QLatin1String("image/unknown"); } QString image2type(const QByteArray &ba) { 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->createElement("vCard"); v.setAttribute("xmlns", "vcard-temp"); 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.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() ) // FIXME: Workaround for Psi prior to 0.9 if ( hasSubTag(i, "VOICE") ) p.number = subTagText(i, "VOICE"); v.d->phoneList.append ( p ); } 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.userid = subTagText(i, "USERID"); if ( m.userid.isEmpty() ) // FIXME: Workaround for Psi prior to 0.9 if ( !i.text().isEmpty() ) m.userid = i.text().trimmed(); v.d->emailList.append ( m ); } 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 = 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.554/iris/src/xmpp/xmpp-im/xmpp_vcard.h000066400000000000000000000153721342663516400241340ustar00rootroot00000000000000/* * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef JABBER_VCARD_H #define JABBER_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