pidgin/pidgin

IRC: fill required command parameter counts (part 3)
release-2.x.y
2014-01-16, Tomasz Wasilczyk
6b0e0566af20
IRC: fill required command parameter counts (part 3)
/*
* (C) Copyright 2003 Wojtek Kaniewski <wojtekka@irc.pl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License Version
* 2.1 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
* USA.
*/
/**
* \file pubdir50.c
*
* \brief Obsługa katalogu publicznego od wersji Gadu-Gadu 5.x
*
* \todo Zoptymalizować konwersję CP1250<->UTF8. Obecnie robiona jest
* testowa konwersja, żeby poznać długość tekstu wynikowego.
*/
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "libgadu.h"
#include "libgadu-config.h"
#include "libgadu-internal.h"
#include "encoding.h"
/**
* Tworzy nowe zapytanie katalogu publicznego.
*
* \param type Rodzaj zapytania
*
* \return Zmienna \c gg_pubdir50_t lub \c NULL w przypadku błędu.
*
* \ingroup pubdir50
*/
gg_pubdir50_t gg_pubdir50_new(int type)
{
gg_pubdir50_t res = malloc(sizeof(struct gg_pubdir50_s));
gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_new(%d);\n", type);
if (!res) {
gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_new() out of memory\n");
return NULL;
}
memset(res, 0, sizeof(struct gg_pubdir50_s));
res->type = type;
return res;
}
/**
* \internal Dodaje lub zastępuje pole zapytania lub odpowiedzi katalogu
* publicznego.
*
* \param req Zapytanie lub odpowiedź
* \param num Numer wyniku odpowiedzi (0 dla zapytania)
* \param field Nazwa pola
* \param value Wartość pola
*
* \return 0 jeśli się powiodło, -1 w przypadku błędu
*/
static int gg_pubdir50_add_n(gg_pubdir50_t req, int num, const char *field, const char *value)
{
struct gg_pubdir50_entry *tmp = NULL, *entry;
char *dupfield, *dupvalue;
int i;
gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_add_n(%p, %d, \"%s\", \"%s\");\n", req, num, field, value);
if (!(dupvalue = strdup(value))) {
gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_add_n() out of memory\n");
return -1;
}
for (i = 0; i < req->entries_count; i++) {
if (req->entries[i].num != num || strcmp(req->entries[i].field, field))
continue;
free(req->entries[i].value);
req->entries[i].value = dupvalue;
return 0;
}
if (!(dupfield = strdup(field))) {
gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_add_n() out of memory\n");
free(dupvalue);
return -1;
}
if (!(tmp = realloc(req->entries, sizeof(struct gg_pubdir50_entry) * (req->entries_count + 1)))) {
gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_add_n() out of memory\n");
free(dupfield);
free(dupvalue);
return -1;
}
req->entries = tmp;
entry = &req->entries[req->entries_count];
entry->num = num;
entry->field = dupfield;
entry->value = dupvalue;
req->entries_count++;
return 0;
}
/**
* Dodaje pole zapytania.
*
* \param req Zapytanie
* \param field Nazwa pola
* \param value Wartość pola
*
* \return 0 jeśli się powiodło, -1 w przypadku błędu
*
* \ingroup pubdir50
*/
int gg_pubdir50_add(gg_pubdir50_t req, const char *field, const char *value)
{
return gg_pubdir50_add_n(req, 0, field, value);
}
/**
* Ustawia numer sekwencyjny zapytania.
*
* \param req Zapytanie
* \param seq Numer sekwencyjny
*
* \return 0 jeśli się powiodło, -1 w przypadku błędu
*
* \ingroup pubdir50
*/
int gg_pubdir50_seq_set(gg_pubdir50_t req, uint32_t seq)
{
gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_seq_set(%p, %d);\n", req, seq);
if (!req) {
gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_seq_set() invalid arguments\n");
errno = EFAULT;
return -1;
}
req->seq = seq;
return 0;
}
/**
* Zwalnia zasoby po zapytaniu lub odpowiedzi katalogu publicznego.
*
* \param s Zapytanie lub odpowiedź
*
* \ingroup pubdir50
*/
void gg_pubdir50_free(gg_pubdir50_t s)
{
int i;
if (!s)
return;
for (i = 0; i < s->entries_count; i++) {
free(s->entries[i].field);
free(s->entries[i].value);
}
free(s->entries);
free(s);
}
/**
* Wysyła zapytanie katalogu publicznego do serwera.
*
* \param sess Struktura sesji
* \param req Zapytanie
*
* \return Numer sekwencyjny zapytania lub 0 w przypadku błędu
*
* \ingroup pubdir50
*/
uint32_t gg_pubdir50(struct gg_session *sess, gg_pubdir50_t req)
{
int i, size = 5;
uint32_t res;
char *buf, *p;
struct gg_pubdir50_request *r;
gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_pubdir50(%p, %p);\n", sess, req);
if (!sess || !req) {
gg_debug_session(sess, GG_DEBUG_MISC, "// gg_pubdir50() invalid arguments\n");
errno = EFAULT;
return 0;
}
if (sess->state != GG_STATE_CONNECTED) {
gg_debug_session(sess, GG_DEBUG_MISC, "// gg_pubdir50() not connected\n");
errno = ENOTCONN;
return 0;
}
for (i = 0; i < req->entries_count; i++) {
/* wyszukiwanie bierze tylko pierwszy wpis */
if (req->entries[i].num)
continue;
if (sess->encoding == GG_ENCODING_CP1250) {
size += strlen(req->entries[i].field) + 1;
size += strlen(req->entries[i].value) + 1;
} else {
char *tmp;
// XXX \todo zoptymalizować
tmp = gg_encoding_convert(req->entries[i].field, sess->encoding, GG_ENCODING_CP1250, -1, -1);
if (tmp == NULL)
return -1;
size += strlen(tmp) + 1;
free(tmp);
// XXX \todo zoptymalizować
tmp = gg_encoding_convert(req->entries[i].value, sess->encoding, GG_ENCODING_CP1250, -1, -1);
if (tmp == NULL)
return -1;
size += strlen(tmp) + 1;
free(tmp);
}
}
if (!(buf = malloc(size))) {
gg_debug_session(sess, GG_DEBUG_MISC, "// gg_pubdir50() out of memory (%d bytes)\n", size);
return 0;
}
if (!req->seq)
req->seq = time(NULL);
res = req->seq;
r = (struct gg_pubdir50_request*) buf;
r->type = req->type;
r->seq = gg_fix32(req->seq);
for (i = 0, p = buf + 5; i < req->entries_count; i++) {
if (req->entries[i].num)
continue;
if (sess->encoding == GG_ENCODING_CP1250) {
strcpy(p, req->entries[i].field);
p += strlen(p) + 1;
strcpy(p, req->entries[i].value);
p += strlen(p) + 1;
} else {
char *tmp;
// XXX \todo zoptymalizować
tmp = gg_encoding_convert(req->entries[i].field, sess->encoding, GG_ENCODING_CP1250, -1, -1);
if (tmp == NULL) {
free(buf);
return -1;
}
strcpy(p, tmp);
p += strlen(tmp) + 1;
free(tmp);
// XXX \todo zoptymalizować
tmp = gg_encoding_convert(req->entries[i].value, sess->encoding, GG_ENCODING_CP1250, -1, -1);
if (tmp == NULL) {
free(buf);
return -1;
}
strcpy(p, tmp);
p += strlen(tmp) + 1;
free(tmp);
}
}
if (gg_send_packet(sess, GG_PUBDIR50_REQUEST, buf, size, NULL, 0) == -1)
res = 0;
free(buf);
return res;
}
/*
* \internal Analizuje przychodzący pakiet odpowiedzi i zapisuje wynik
* w strukturze \c gg_event.
*
* \param sess Struktura sesji
* \param e Struktura zdarzenia
* \param packet Pakiet odpowiedzi
* \param length Długość pakietu odpowiedzi
*
* \return 0 jeśli się powiodło, -1 w przypadku błędu
*/
int gg_pubdir50_handle_reply_sess(struct gg_session *sess, struct gg_event *e, const char *packet, int length)
{
const char *end = packet + length, *p;
struct gg_pubdir50_reply *r = (struct gg_pubdir50_reply*) packet;
gg_pubdir50_t res;
int num = 0;
gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_handle_reply_sess(%p, %p, %p, %d);\n", sess, e, packet, length);
if (!sess || !e || !packet) {
gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() invalid arguments\n");
errno = EFAULT;
return -1;
}
if (length < 5) {
gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() packet too short\n");
errno = EINVAL;
return -1;
}
if (!(res = gg_pubdir50_new(r->type))) {
gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() unable to allocate reply\n");
return -1;
}
e->event.pubdir50 = res;
res->seq = gg_fix32(r->seq);
switch (res->type) {
case GG_PUBDIR50_READ:
e->type = GG_EVENT_PUBDIR50_READ;
break;
case GG_PUBDIR50_WRITE:
e->type = GG_EVENT_PUBDIR50_WRITE;
break;
default:
e->type = GG_EVENT_PUBDIR50_SEARCH_REPLY;
break;
}
/* brak wyników? */
if (length == 5)
return 0;
/* pomiń początek odpowiedzi */
p = packet + 5;
while (p < end) {
const char *field, *value;
field = p;
/* sprawdź, czy nie mamy podziału na kolejne pole */
if (!*field) {
num++;
field++;
}
value = NULL;
for (p = field; p < end; p++) {
/* jeśli mamy koniec tekstu... */
if (!*p) {
/* ...i jeszcze nie mieliśmy wartości pola to
* wiemy, że po tym zerze jest wartość... */
if (!value)
value = p + 1;
else
/* ...w przeciwym wypadku koniec
* wartości i możemy wychodzić
* grzecznie z pętli */
break;
}
}
/* sprawdźmy, czy pole nie wychodzi poza pakiet, żeby nie
* mieć segfaultów, jeśli serwer przestanie zakańczać pakietów
* przez \0 */
if (p == end) {
gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() premature end of packet\n");
goto failure;
}
p++;
/* jeśli dostaliśmy namier na następne wyniki, to znaczy że
* mamy koniec wyników i nie jest to kolejna osoba. */
if (!strcasecmp(field, "nextstart")) {
res->next = atoi(value);
num--;
} else {
if (sess->encoding == GG_ENCODING_CP1250) {
if (gg_pubdir50_add_n(res, num, field, value) == -1)
goto failure;
} else {
char *tmp;
tmp = gg_encoding_convert(value, GG_ENCODING_CP1250, sess->encoding, -1, -1);
if (tmp == NULL)
goto failure;
if (gg_pubdir50_add_n(res, num, field, tmp) == -1) {
free(tmp);
goto failure;
}
free(tmp);
}
}
}
res->count = num + 1;
return 0;
failure:
gg_pubdir50_free(res);
return -1;
}
/**
* Pobiera pole z odpowiedzi katalogu publicznego.
*
* \param res Odpowiedź
* \param num Numer wyniku odpowiedzi
* \param field Nazwa pola (wielkość liter nie ma znaczenia)
*
* \return Wartość pola lub \c NULL jeśli nie znaleziono
*
* \ingroup pubdir50
*/
const char *gg_pubdir50_get(gg_pubdir50_t res, int num, const char *field)
{
char *value = NULL;
int i;
gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_get(%p, %d, \"%s\");\n", res, num, field);
if (!res || num < 0 || !field) {
gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_get() invalid arguments\n");
errno = EINVAL;
return NULL;
}
for (i = 0; i < res->entries_count; i++) {
if (res->entries[i].num == num && !strcasecmp(res->entries[i].field, field)) {
value = res->entries[i].value;
break;
}
}
return value;
}
/**
* Zwraca liczbę wyników odpowiedzi.
*
* \param res Odpowiedź
*
* \return Liczba wyników lub -1 w przypadku błędu
*
* \ingroup pubdir50
*/
int gg_pubdir50_count(gg_pubdir50_t res)
{
return (!res) ? -1 : res->count;
}
/**
* Zwraca rodzaj zapytania lub odpowiedzi.
*
* \param res Zapytanie lub odpowiedź
*
* \return Rodzaj lub -1 w przypadku błędu
*
* \ingroup pubdir50
*/
int gg_pubdir50_type(gg_pubdir50_t res)
{
return (!res) ? -1 : res->type;
}
/**
* Zwraca numer, od którego należy rozpocząc kolejne wyszukiwanie.
*
* Dłuższe odpowiedzi katalogu publicznego są wysyłane przez serwer
* w mniejszych paczkach. Po otrzymaniu odpowiedzi, jeśli numer kolejnego
* wyszukiwania jest większy od zera, dalsze wyniki można otrzymać przez
* wywołanie kolejnego zapytania z określonym numerem początkowym.
*
* \param res Odpowiedź
*
* \return Numer lub -1 w przypadku błędu
*
* \ingroup pubdir50
*/
uin_t gg_pubdir50_next(gg_pubdir50_t res)
{
return (!res) ? (unsigned) -1 : res->next;
}
/**
* Zwraca numer sekwencyjny zapytania lub odpowiedzi.
*
* \param res Zapytanie lub odpowiedź
*
* \return Numer sekwencyjny lub -1 w przypadku błędu
*
* \ingroup pubdir50
*/
uint32_t gg_pubdir50_seq(gg_pubdir50_t res)
{
return (!res) ? (unsigned) -1 : res->seq;
}
/*
* Local variables:
* c-indentation-style: k&r
* c-basic-offset: 8
* indent-tabs-mode: notnil
* End:
*
* vim: shiftwidth=8:
*/