| 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 */ |
|