libpurple/protocols/gg/lib/pubdir50.c

changeset 38067
3c2c551feeb9
parent 38066
2e94b6fa06a0
child 38068
fd6805c0df15
equal deleted inserted replaced
38066:2e94b6fa06a0 38067:3c2c551feeb9
1 /*
2 * (C) Copyright 2003 Wojtek Kaniewski <wojtekka@irc.pl>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License Version
6 * 2.1 as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
16 * USA.
17 */
18
19 /**
20 * \file pubdir50.c
21 *
22 * \brief Obsługa katalogu publicznego od wersji Gadu-Gadu 5.x
23 *
24 * \todo Zoptymalizować konwersję CP1250<->UTF8. Obecnie robiona jest
25 * testowa konwersja, żeby poznać długość tekstu wynikowego.
26 */
27
28 #include "network.h"
29 #include "strman.h"
30 #include <errno.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <time.h>
34
35 #include "libgadu.h"
36 #include "internal.h"
37 #include "encoding.h"
38
39 /**
40 * Tworzy nowe zapytanie katalogu publicznego.
41 *
42 * \param type Rodzaj zapytania
43 *
44 * \return Zmienna \c gg_pubdir50_t lub \c NULL w przypadku błędu.
45 *
46 * \ingroup pubdir50
47 */
48 gg_pubdir50_t gg_pubdir50_new(int type)
49 {
50 gg_pubdir50_t res = malloc(sizeof(struct gg_pubdir50_s));
51
52 gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_new(%d);\n", type);
53
54 if (!res) {
55 gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_new() out of memory\n");
56 return NULL;
57 }
58
59 memset(res, 0, sizeof(struct gg_pubdir50_s));
60
61 res->type = type;
62
63 return res;
64 }
65
66 /**
67 * \internal Dodaje lub zastępuje pole zapytania lub odpowiedzi katalogu
68 * publicznego.
69 *
70 * \param req Zapytanie lub odpowiedź
71 * \param num Numer wyniku odpowiedzi (0 dla zapytania)
72 * \param field Nazwa pola
73 * \param value Wartość pola
74 *
75 * \return 0 jeśli się powiodło, -1 w przypadku błędu
76 */
77 static int gg_pubdir50_add_n(gg_pubdir50_t req, int num, const char *field, const char *value)
78 {
79 struct gg_pubdir50_entry *tmp = NULL, *entry;
80 char *dupfield, *dupvalue;
81 int i;
82
83 gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_add_n(%p, %d, \"%s\", \"%s\");\n", req, num, field, value);
84
85 if (!(dupvalue = strdup(value))) {
86 gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_add_n() out of memory\n");
87 return -1;
88 }
89
90 for (i = 0; i < req->entries_count; i++) {
91 if (req->entries[i].num != num || strcmp(req->entries[i].field, field))
92 continue;
93
94 free(req->entries[i].value);
95 req->entries[i].value = dupvalue;
96
97 return 0;
98 }
99
100 if (!(dupfield = strdup(field))) {
101 gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_add_n() out of memory\n");
102 free(dupvalue);
103 return -1;
104 }
105
106 if (!(tmp = realloc(req->entries, sizeof(struct gg_pubdir50_entry) * (req->entries_count + 1)))) {
107 gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_add_n() out of memory\n");
108 free(dupfield);
109 free(dupvalue);
110 return -1;
111 }
112
113 req->entries = tmp;
114
115 entry = &req->entries[req->entries_count];
116 entry->num = num;
117 entry->field = dupfield;
118 entry->value = dupvalue;
119
120 req->entries_count++;
121
122 return 0;
123 }
124
125 /**
126 * Dodaje pole zapytania.
127 *
128 * \param req Zapytanie
129 * \param field Nazwa pola
130 * \param value Wartość pola
131 *
132 * \return 0 jeśli się powiodło, -1 w przypadku błędu
133 *
134 * \ingroup pubdir50
135 */
136 int gg_pubdir50_add(gg_pubdir50_t req, const char *field, const char *value)
137 {
138 return gg_pubdir50_add_n(req, 0, field, value);
139 }
140
141 /**
142 * Ustawia numer sekwencyjny zapytania.
143 *
144 * \param req Zapytanie
145 * \param seq Numer sekwencyjny
146 *
147 * \return 0 jeśli się powiodło, -1 w przypadku błędu
148 *
149 * \ingroup pubdir50
150 */
151 int gg_pubdir50_seq_set(gg_pubdir50_t req, uint32_t seq)
152 {
153 gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_seq_set(%p, %d);\n", req, seq);
154
155 if (!req) {
156 gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_seq_set() invalid arguments\n");
157 errno = EFAULT;
158 return -1;
159 }
160
161 req->seq = seq;
162
163 return 0;
164 }
165
166 /**
167 * Zwalnia zasoby po zapytaniu lub odpowiedzi katalogu publicznego.
168 *
169 * \param s Zapytanie lub odpowiedź
170 *
171 * \ingroup pubdir50
172 */
173 void gg_pubdir50_free(gg_pubdir50_t s)
174 {
175 int i;
176
177 if (!s)
178 return;
179
180 for (i = 0; i < s->entries_count; i++) {
181 free(s->entries[i].field);
182 free(s->entries[i].value);
183 }
184
185 free(s->entries);
186 free(s);
187 }
188
189 /**
190 * Wysyła zapytanie katalogu publicznego do serwera.
191 *
192 * \param sess Struktura sesji
193 * \param req Zapytanie
194 *
195 * \return Numer sekwencyjny zapytania lub 0 w przypadku błędu
196 *
197 * \ingroup pubdir50
198 */
199 uint32_t gg_pubdir50(struct gg_session *sess, gg_pubdir50_t req)
200 {
201 int i, size = 5;
202 uint32_t res;
203 char *buf, *p;
204 struct gg_pubdir50_request *r;
205
206 gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_pubdir50(%p, %p);\n", sess, req);
207
208 if (!sess || !req) {
209 gg_debug_session(sess, GG_DEBUG_MISC, "// gg_pubdir50() invalid arguments\n");
210 errno = EFAULT;
211 return 0;
212 }
213
214 if (sess->state != GG_STATE_CONNECTED) {
215 gg_debug_session(sess, GG_DEBUG_MISC, "// gg_pubdir50() not connected\n");
216 errno = ENOTCONN;
217 return 0;
218 }
219
220 for (i = 0; i < req->entries_count; i++) {
221 /* wyszukiwanie bierze tylko pierwszy wpis */
222 if (req->entries[i].num)
223 continue;
224
225 if (sess->encoding == GG_ENCODING_CP1250) {
226 size += strlen(req->entries[i].field) + 1;
227 size += strlen(req->entries[i].value) + 1;
228 } else {
229 char *tmp;
230
231 /* XXX \todo zoptymalizować */
232 tmp = gg_encoding_convert(req->entries[i].field, sess->encoding, GG_ENCODING_CP1250, -1, -1);
233
234 if (tmp == NULL)
235 return -1;
236
237 size += strlen(tmp) + 1;
238
239 free(tmp);
240
241 /* XXX \todo zoptymalizować */
242 tmp = gg_encoding_convert(req->entries[i].value, sess->encoding, GG_ENCODING_CP1250, -1, -1);
243
244 if (tmp == NULL)
245 return -1;
246
247 size += strlen(tmp) + 1;
248
249 free(tmp);
250 }
251 }
252
253 if (!(buf = malloc(size))) {
254 gg_debug_session(sess, GG_DEBUG_MISC, "// gg_pubdir50() out of memory (%d bytes)\n", size);
255 return 0;
256 }
257
258 if (!req->seq)
259 req->seq = time(NULL);
260
261 res = req->seq;
262
263 r = (struct gg_pubdir50_request*) buf;
264 r->type = req->type;
265 r->seq = gg_fix32(req->seq);
266
267 for (i = 0, p = buf + 5; i < req->entries_count; i++) {
268 if (req->entries[i].num)
269 continue;
270
271 if (sess->encoding == GG_ENCODING_CP1250) {
272 strcpy(p, req->entries[i].field);
273 p += strlen(p) + 1;
274
275 strcpy(p, req->entries[i].value);
276 p += strlen(p) + 1;
277 } else {
278 char *tmp;
279
280 /* XXX \todo zoptymalizować */
281 tmp = gg_encoding_convert(req->entries[i].field, sess->encoding, GG_ENCODING_CP1250, -1, -1);
282
283 if (tmp == NULL) {
284 free(buf);
285 return -1;
286 }
287
288 strcpy(p, tmp);
289 p += strlen(tmp) + 1;
290 free(tmp);
291
292 /* XXX \todo zoptymalizować */
293 tmp = gg_encoding_convert(req->entries[i].value, sess->encoding, GG_ENCODING_CP1250, -1, -1);
294
295
296 if (tmp == NULL) {
297 free(buf);
298 return -1;
299 }
300
301 strcpy(p, tmp);
302 p += strlen(tmp) + 1;
303 free(tmp);
304 }
305 }
306
307 if (gg_send_packet(sess, GG_PUBDIR50_REQUEST, buf, size, NULL, 0) == -1)
308 res = 0;
309
310 free(buf);
311
312 return res;
313 }
314
315 /*
316 * \internal Analizuje przychodzący pakiet odpowiedzi i zapisuje wynik
317 * w strukturze \c gg_event.
318 *
319 * \param sess Struktura sesji
320 * \param e Struktura zdarzenia
321 * \param packet Pakiet odpowiedzi
322 * \param length Długość pakietu odpowiedzi
323 *
324 * \return 0 jeśli się powiodło, -1 w przypadku błędu
325 */
326 int gg_pubdir50_handle_reply_sess(struct gg_session *sess, struct gg_event *e, const char *packet, int length)
327 {
328 const char *end = packet + length, *p;
329 const struct gg_pubdir50_reply *r = (const struct gg_pubdir50_reply*) packet;
330 gg_pubdir50_t res;
331 int num = 0;
332
333 gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_handle_reply_sess(%p, %p, %p, %d);\n", sess, e, packet, length);
334
335 if (!sess || !e || !packet) {
336 gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() invalid arguments\n");
337 errno = EFAULT;
338 return -1;
339 }
340
341 if (length < 5) {
342 gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() packet too short\n");
343 errno = EINVAL;
344 return -1;
345 }
346
347 if (!(res = gg_pubdir50_new(r->type))) {
348 gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() unable to allocate reply\n");
349 return -1;
350 }
351
352 e->event.pubdir50 = res;
353
354 res->seq = gg_fix32(r->seq);
355
356 switch (res->type) {
357 case GG_PUBDIR50_READ:
358 e->type = GG_EVENT_PUBDIR50_READ;
359 break;
360
361 case GG_PUBDIR50_WRITE:
362 e->type = GG_EVENT_PUBDIR50_WRITE;
363 break;
364
365 default:
366 e->type = GG_EVENT_PUBDIR50_SEARCH_REPLY;
367 break;
368 }
369
370 /* brak wyników? */
371 if (length == 5)
372 return 0;
373
374 /* pomiń początek odpowiedzi */
375 p = packet + 5;
376
377 while (p < end) {
378 const char *field, *value;
379
380 field = p;
381
382 /* sprawdź, czy nie mamy podziału na kolejne pole */
383 if (!*field) {
384 num++;
385 field++;
386 }
387
388 value = NULL;
389
390 for (p = field; p < end; p++) {
391 /* jeśli mamy koniec tekstu... */
392 if (!*p) {
393 /* ...i jeszcze nie mieliśmy wartości pola to
394 * wiemy, że po tym zerze jest wartość... */
395 if (!value)
396 value = p + 1;
397 else
398 /* ...w przeciwym wypadku koniec
399 * wartości i możemy wychodzić
400 * grzecznie z pętli */
401 break;
402 }
403 }
404
405 /* sprawdźmy, czy pole nie wychodzi poza pakiet, żeby nie
406 * mieć segfaultów, jeśli serwer przestanie zakańczać pakietów
407 * przez \0 */
408
409 if (p == end) {
410 gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() premature end of packet\n");
411 goto failure;
412 }
413
414 p++;
415
416 /* jeśli dostaliśmy namier na następne wyniki, to znaczy że
417 * mamy koniec wyników i nie jest to kolejna osoba. */
418 if (!strcasecmp(field, "nextstart")) {
419 res->next = value ? atoi(value) : 0;
420 num--;
421 } else {
422 if (sess->encoding == GG_ENCODING_CP1250) {
423 if (gg_pubdir50_add_n(res, num, field, value) == -1)
424 goto failure;
425 } else {
426 char *tmp;
427
428 tmp = gg_encoding_convert(value, GG_ENCODING_CP1250, sess->encoding, -1, -1);
429
430 if (tmp == NULL)
431 goto failure;
432
433 if (gg_pubdir50_add_n(res, num, field, tmp) == -1) {
434 free(tmp);
435 goto failure;
436 }
437
438 free(tmp);
439 }
440 }
441 }
442
443 res->count = num + 1;
444
445 return 0;
446
447 failure:
448 gg_pubdir50_free(res);
449 return -1;
450 }
451
452 /**
453 * Pobiera pole z odpowiedzi katalogu publicznego.
454 *
455 * \param res Odpowiedź
456 * \param num Numer wyniku odpowiedzi
457 * \param field Nazwa pola (wielkość liter nie ma znaczenia)
458 *
459 * \return Wartość pola lub \c NULL jeśli nie znaleziono
460 *
461 * \ingroup pubdir50
462 */
463 const char *gg_pubdir50_get(gg_pubdir50_t res, int num, const char *field)
464 {
465 char *value = NULL;
466 int i;
467
468 gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_get(%p, %d, \"%s\");\n", res, num, field);
469
470 if (!res || num < 0 || !field) {
471 gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_get() invalid arguments\n");
472 errno = EINVAL;
473 return NULL;
474 }
475
476 for (i = 0; i < res->entries_count; i++) {
477 if (res->entries[i].num == num && !strcasecmp(res->entries[i].field, field)) {
478 value = res->entries[i].value;
479 break;
480 }
481 }
482
483 return value;
484 }
485
486 /**
487 * Zwraca liczbę wyników odpowiedzi.
488 *
489 * \param res Odpowiedź
490 *
491 * \return Liczba wyników lub -1 w przypadku błędu
492 *
493 * \ingroup pubdir50
494 */
495 int gg_pubdir50_count(gg_pubdir50_t res)
496 {
497 return (!res) ? -1 : res->count;
498 }
499
500 /**
501 * Zwraca rodzaj zapytania lub odpowiedzi.
502 *
503 * \param res Zapytanie lub odpowiedź
504 *
505 * \return Rodzaj lub -1 w przypadku błędu
506 *
507 * \ingroup pubdir50
508 */
509 int gg_pubdir50_type(gg_pubdir50_t res)
510 {
511 return (!res) ? -1 : res->type;
512 }
513
514 /**
515 * Zwraca numer, od którego należy rozpocząc kolejne wyszukiwanie.
516 *
517 * Dłuższe odpowiedzi katalogu publicznego są wysyłane przez serwer
518 * w mniejszych paczkach. Po otrzymaniu odpowiedzi, jeśli numer kolejnego
519 * wyszukiwania jest większy od zera, dalsze wyniki można otrzymać przez
520 * wywołanie kolejnego zapytania z określonym numerem początkowym.
521 *
522 * \param res Odpowiedź
523 *
524 * \return Numer lub -1 w przypadku błędu
525 *
526 * \ingroup pubdir50
527 */
528 uin_t gg_pubdir50_next(gg_pubdir50_t res)
529 {
530 return (!res) ? (unsigned) -1 : res->next;
531 }
532
533 /**
534 * Zwraca numer sekwencyjny zapytania lub odpowiedzi.
535 *
536 * \param res Zapytanie lub odpowiedź
537 *
538 * \return Numer sekwencyjny lub -1 w przypadku błędu
539 *
540 * \ingroup pubdir50
541 */
542 uint32_t gg_pubdir50_seq(gg_pubdir50_t res)
543 {
544 return (!res) ? (unsigned) -1 : res->seq;
545 }
546
547 /*
548 * Local variables:
549 * c-indentation-style: k&r
550 * c-basic-offset: 8
551 * indent-tabs-mode: notnil
552 * End:
553 *
554 * vim: shiftwidth=8:
555 */

mercurial