Mercurial > pidgin
annotate libgaim/xmlnode.c @ 15113:4a8c368df4ea
[gaim-migrate @ 17899]
Some touchups:
* If one of the parallel connection attempts fails immediately (i.e.
does not time out) then don't cancel the other one.
* Make sure we don't continue on to step 2 of the peer connection
process after we kick off the parallel gaim_proxy_connects(). It
looks like this would happen most of the time, because the
connect_timeout_timer would be added for the verified ip, so it
would NOT be added for the client ip, and so we wouldn't hit the
"return" call because it happens to be in the same block as the
second gaim_timeout_add() call.
* Add the connection timeout timer even if the gaim_proxy_connect() to
the verified ip returns NULL for some crazy reason.
I didn't actually test any of this. I should probably do that when
I get home.
committer: Tailor Script <tailor@pidgin.im>
| author | Mark Doliner <mark@kingant.net> |
|---|---|
| date | Wed, 06 Dec 2006 01:29:59 +0000 |
| parents | 58202142e9ad |
| children | c6978ee9ac4d |
| rev | line source |
|---|---|
| 14192 | 1 /** |
| 2 * @file xmlnode.c XML DOM functions | |
| 3 * | |
| 4 * gaim | |
| 5 * | |
| 6 * Gaim is the legal property of its developers, whose names are too numerous | |
| 7 * to list here. Please refer to the COPYRIGHT file distributed with this | |
| 8 * source distribution. | |
| 9 * | |
| 10 * This program is free software; you can redistribute it and/or modify | |
| 11 * it under the terms of the GNU General Public License as published by | |
| 12 * the Free Software Foundation; either version 2 of the License, or | |
| 13 * (at your option) any later version. | |
| 14 * | |
| 15 * This program is distributed in the hope that it will be useful, | |
| 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 18 * GNU General Public License for more details. | |
| 19 * | |
| 20 * You should have received a copy of the GNU General Public License | |
| 21 * along with this program; if not, write to the Free Software | |
| 22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
| 23 */ | |
| 24 | |
| 25 /* A lot of this code at least resembles the code in libxode, but since | |
| 26 * libxode uses memory pools that we simply have no need for, I decided to | |
| 27 * write my own stuff. Also, re-writing this lets me be as lightweight | |
| 28 * as I want to be. Thank you libxode for giving me a good starting point */ | |
| 29 | |
| 30 #include "internal.h" | |
| 31 | |
| 32 #include <libxml/parser.h> | |
| 33 #include <string.h> | |
| 34 #include <glib.h> | |
| 35 | |
| 14324 | 36 #include "dbus-maybe.h" |
| 14192 | 37 #include "util.h" |
| 38 #include "xmlnode.h" | |
| 39 | |
| 40 #ifdef _WIN32 | |
| 41 # define NEWLINE_S "\r\n" | |
| 42 #else | |
| 43 # define NEWLINE_S "\n" | |
| 44 #endif | |
| 45 | |
| 46 static xmlnode* | |
| 47 new_node(const char *name, XMLNodeType type) | |
| 48 { | |
| 49 xmlnode *node = g_new0(xmlnode, 1); | |
| 50 | |
| 51 node->name = g_strdup(name); | |
| 52 node->type = type; | |
| 53 | |
| 14324 | 54 GAIM_DBUS_REGISTER_POINTER(node, xmlnode); |
| 55 | |
| 14192 | 56 return node; |
| 57 } | |
| 58 | |
| 59 xmlnode* | |
| 60 xmlnode_new(const char *name) | |
| 61 { | |
| 62 g_return_val_if_fail(name != NULL, NULL); | |
| 63 | |
| 64 return new_node(name, XMLNODE_TYPE_TAG); | |
| 65 } | |
| 66 | |
| 67 xmlnode * | |
| 68 xmlnode_new_child(xmlnode *parent, const char *name) | |
| 69 { | |
| 70 xmlnode *node; | |
| 71 | |
| 72 g_return_val_if_fail(parent != NULL, NULL); | |
| 73 g_return_val_if_fail(name != NULL, NULL); | |
| 74 | |
| 75 node = new_node(name, XMLNODE_TYPE_TAG); | |
| 76 | |
| 77 xmlnode_insert_child(parent, node); | |
| 78 | |
| 79 return node; | |
| 80 } | |
| 81 | |
| 82 void | |
| 83 xmlnode_insert_child(xmlnode *parent, xmlnode *child) | |
| 84 { | |
| 85 g_return_if_fail(parent != NULL); | |
| 86 g_return_if_fail(child != NULL); | |
| 87 | |
| 88 child->parent = parent; | |
| 89 | |
| 90 if(parent->lastchild) { | |
| 91 parent->lastchild->next = child; | |
| 92 } else { | |
| 93 parent->child = child; | |
| 94 } | |
| 95 | |
| 96 parent->lastchild = child; | |
| 97 } | |
| 98 | |
| 99 void | |
| 100 xmlnode_insert_data(xmlnode *node, const char *data, gssize size) | |
| 101 { | |
| 102 xmlnode *child; | |
| 103 gsize real_size; | |
| 104 | |
| 105 g_return_if_fail(node != NULL); | |
| 106 g_return_if_fail(data != NULL); | |
| 107 g_return_if_fail(size != 0); | |
| 108 | |
| 109 real_size = size == -1 ? strlen(data) : size; | |
| 110 | |
| 111 child = new_node(NULL, XMLNODE_TYPE_DATA); | |
| 112 | |
| 113 child->data = g_memdup(data, real_size); | |
| 114 child->data_sz = real_size; | |
| 115 | |
| 116 xmlnode_insert_child(node, child); | |
| 117 } | |
| 118 | |
| 119 void | |
| 120 xmlnode_remove_attrib(xmlnode *node, const char *attr) | |
| 121 { | |
| 122 xmlnode *attr_node, *sibling = NULL; | |
| 123 | |
| 124 g_return_if_fail(node != NULL); | |
| 125 g_return_if_fail(attr != NULL); | |
| 126 | |
| 127 for(attr_node = node->child; attr_node; attr_node = attr_node->next) | |
| 128 { | |
| 129 if(attr_node->type == XMLNODE_TYPE_ATTRIB && | |
| 130 !strcmp(attr_node->name, attr)) { | |
| 131 if(node->child == attr_node) { | |
| 132 node->child = attr_node->next; | |
| 133 } else if (node->lastchild == attr_node) { | |
| 134 node->lastchild = sibling; | |
| 135 } else { | |
| 136 sibling->next = attr_node->next; | |
| 137 } | |
| 138 xmlnode_free(attr_node); | |
| 139 return; | |
| 140 } | |
| 141 sibling = attr_node; | |
| 142 } | |
| 143 } | |
| 144 | |
| 145 void | |
| 146 xmlnode_set_attrib(xmlnode *node, const char *attr, const char *value) | |
| 147 { | |
| 148 xmlnode *attrib_node; | |
| 149 | |
| 150 g_return_if_fail(node != NULL); | |
| 151 g_return_if_fail(attr != NULL); | |
| 152 g_return_if_fail(value != NULL); | |
| 153 | |
| 154 xmlnode_remove_attrib(node, attr); | |
| 155 | |
| 156 attrib_node = new_node(attr, XMLNODE_TYPE_ATTRIB); | |
| 157 | |
| 158 attrib_node->data = g_strdup(value); | |
| 159 | |
| 160 xmlnode_insert_child(node, attrib_node); | |
| 161 } | |
| 162 | |
| 163 const char * | |
| 164 xmlnode_get_attrib(xmlnode *node, const char *attr) | |
| 165 { | |
| 166 xmlnode *x; | |
| 167 | |
| 168 g_return_val_if_fail(node != NULL, NULL); | |
| 169 | |
| 170 for(x = node->child; x; x = x->next) { | |
| 171 if(x->type == XMLNODE_TYPE_ATTRIB && !strcmp(attr, x->name)) { | |
| 172 return x->data; | |
| 173 } | |
| 174 } | |
| 175 | |
| 176 return NULL; | |
| 177 } | |
| 178 | |
| 179 | |
| 180 void xmlnode_set_namespace(xmlnode *node, const char *xmlns) | |
| 181 { | |
| 182 g_return_if_fail(node != NULL); | |
| 183 | |
| 184 g_free(node->namespace); | |
| 185 node->namespace = g_strdup(xmlns); | |
| 186 } | |
| 187 | |
| 188 const char *xmlnode_get_namespace(xmlnode *node) | |
| 189 { | |
| 190 g_return_val_if_fail(node != NULL, NULL); | |
| 191 | |
| 192 return node->namespace; | |
| 193 } | |
| 194 | |
| 195 void | |
| 196 xmlnode_free(xmlnode *node) | |
| 197 { | |
| 198 xmlnode *x, *y; | |
| 199 | |
| 200 g_return_if_fail(node != NULL); | |
| 201 | |
| 202 x = node->child; | |
| 203 while(x) { | |
| 204 y = x->next; | |
| 205 xmlnode_free(x); | |
| 206 x = y; | |
| 207 } | |
| 208 | |
| 209 g_free(node->name); | |
| 210 g_free(node->data); | |
| 211 g_free(node->namespace); | |
| 14324 | 212 |
| 213 GAIM_DBUS_UNREGISTER_POINTER(node); | |
| 14192 | 214 g_free(node); |
| 215 } | |
| 216 | |
| 217 xmlnode* | |
| 218 xmlnode_get_child(const xmlnode *parent, const char *name) | |
| 219 { | |
| 220 return xmlnode_get_child_with_namespace(parent, name, NULL); | |
| 221 } | |
| 222 | |
| 223 xmlnode * | |
| 224 xmlnode_get_child_with_namespace(const xmlnode *parent, const char *name, const char *ns) | |
| 225 { | |
| 226 xmlnode *x, *ret = NULL; | |
| 227 char **names; | |
| 228 char *parent_name, *child_name; | |
| 229 | |
| 230 g_return_val_if_fail(parent != NULL, NULL); | |
| 231 g_return_val_if_fail(name != NULL, NULL); | |
| 232 | |
| 233 names = g_strsplit(name, "/", 2); | |
| 234 parent_name = names[0]; | |
| 235 child_name = names[1]; | |
| 236 | |
| 237 for(x = parent->child; x; x = x->next) { | |
| 238 const char *xmlns = NULL; | |
| 239 if(ns) | |
| 240 xmlns = xmlnode_get_namespace(x); | |
| 241 | |
| 242 if(x->type == XMLNODE_TYPE_TAG && name && !strcmp(parent_name, x->name) | |
| 243 && (!ns || (xmlns && !strcmp(ns, xmlns)))) { | |
| 244 ret = x; | |
| 245 break; | |
| 246 } | |
| 247 } | |
| 248 | |
| 249 if(child_name && ret) | |
| 250 ret = xmlnode_get_child(ret, child_name); | |
| 251 | |
| 252 g_strfreev(names); | |
| 253 return ret; | |
| 254 } | |
| 255 | |
| 256 char * | |
| 257 xmlnode_get_data(xmlnode *node) | |
| 258 { | |
| 259 GString *str = NULL; | |
| 260 xmlnode *c; | |
| 261 | |
| 262 g_return_val_if_fail(node != NULL, NULL); | |
| 263 | |
| 264 for(c = node->child; c; c = c->next) { | |
| 265 if(c->type == XMLNODE_TYPE_DATA) { | |
| 266 if(!str) | |
| 267 str = g_string_new(""); | |
| 268 str = g_string_append_len(str, c->data, c->data_sz); | |
| 269 } | |
| 270 } | |
| 271 | |
| 272 if (str == NULL) | |
| 273 return NULL; | |
| 274 | |
| 275 return g_string_free(str, FALSE); | |
| 276 } | |
| 277 | |
| 278 static char * | |
| 279 xmlnode_to_str_helper(xmlnode *node, int *len, gboolean formatting, int depth) | |
| 280 { | |
| 281 GString *text = g_string_new(""); | |
| 282 xmlnode *c; | |
| 283 char *node_name, *esc, *esc2, *tab = NULL; | |
| 284 gboolean need_end = FALSE, pretty = formatting; | |
| 285 | |
| 286 g_return_val_if_fail(node != NULL, NULL); | |
| 287 | |
| 288 if(pretty && depth) { | |
| 289 tab = g_strnfill(depth, '\t'); | |
| 290 text = g_string_append(text, tab); | |
| 291 } | |
| 292 | |
| 293 node_name = g_markup_escape_text(node->name, -1); | |
| 294 g_string_append_printf(text, "<%s", node_name); | |
| 295 | |
| 296 if (node->namespace) { | |
| 297 char *namespace = g_markup_escape_text(node->namespace, -1); | |
| 298 g_string_append_printf(text, " xmlns='%s'", namespace); | |
| 299 g_free(namespace); | |
| 300 } | |
| 301 for(c = node->child; c; c = c->next) | |
| 302 { | |
| 303 if(c->type == XMLNODE_TYPE_ATTRIB) { | |
| 304 esc = g_markup_escape_text(c->name, -1); | |
| 305 esc2 = g_markup_escape_text(c->data, -1); | |
| 306 g_string_append_printf(text, " %s='%s'", esc, esc2); | |
| 307 g_free(esc); | |
| 308 g_free(esc2); | |
| 309 } else if(c->type == XMLNODE_TYPE_TAG || c->type == XMLNODE_TYPE_DATA) { | |
| 310 if(c->type == XMLNODE_TYPE_DATA) | |
| 311 pretty = FALSE; | |
| 312 need_end = TRUE; | |
| 313 } | |
| 314 } | |
| 315 | |
| 316 if(need_end) { | |
| 317 g_string_append_printf(text, ">%s", pretty ? NEWLINE_S : ""); | |
| 318 | |
| 319 for(c = node->child; c; c = c->next) | |
| 320 { | |
| 321 if(c->type == XMLNODE_TYPE_TAG) { | |
| 322 int esc_len; | |
| 323 esc = xmlnode_to_str_helper(c, &esc_len, pretty, depth+1); | |
| 324 text = g_string_append_len(text, esc, esc_len); | |
| 325 g_free(esc); | |
| 326 } else if(c->type == XMLNODE_TYPE_DATA && c->data_sz > 0) { | |
| 327 esc = g_markup_escape_text(c->data, c->data_sz); | |
| 328 text = g_string_append(text, esc); | |
| 329 g_free(esc); | |
| 330 } | |
| 331 } | |
| 332 | |
| 333 if(tab && pretty) | |
| 334 text = g_string_append(text, tab); | |
| 335 g_string_append_printf(text, "</%s>%s", node_name, formatting ? NEWLINE_S : ""); | |
| 336 } else { | |
| 337 g_string_append_printf(text, "/>%s", formatting ? NEWLINE_S : ""); | |
| 338 } | |
| 339 | |
| 340 g_free(node_name); | |
| 341 | |
| 342 g_free(tab); | |
| 343 | |
| 344 if(len) | |
| 345 *len = text->len; | |
| 346 | |
| 347 return g_string_free(text, FALSE); | |
| 348 } | |
| 349 | |
| 350 char * | |
| 351 xmlnode_to_str(xmlnode *node, int *len) | |
| 352 { | |
| 353 return xmlnode_to_str_helper(node, len, FALSE, 0); | |
| 354 } | |
| 355 | |
| 356 char * | |
| 357 xmlnode_to_formatted_str(xmlnode *node, int *len) | |
| 358 { | |
| 359 char *xml, *xml_with_declaration; | |
| 360 | |
| 361 g_return_val_if_fail(node != NULL, NULL); | |
| 362 | |
| 363 xml = xmlnode_to_str_helper(node, len, TRUE, 0); | |
| 364 xml_with_declaration = | |
| 365 g_strdup_printf("<?xml version='1.0' encoding='UTF-8' ?>" NEWLINE_S NEWLINE_S "%s", xml); | |
| 366 g_free(xml); | |
| 367 | |
| 368 return xml_with_declaration; | |
| 369 } | |
| 370 | |
| 371 struct _xmlnode_parser_data { | |
| 372 xmlnode *current; | |
| 373 }; | |
| 374 | |
| 375 static void | |
| 376 xmlnode_parser_element_start_libxml(void *user_data, | |
| 377 const xmlChar *element_name, const xmlChar *prefix, const xmlChar *namespace, | |
| 378 int nb_namespaces, const xmlChar **namespaces, | |
| 379 int nb_attributes, int nb_defaulted, const xmlChar **attributes) | |
| 380 { | |
| 381 struct _xmlnode_parser_data *xpd = user_data; | |
| 382 xmlnode *node; | |
| 383 int i; | |
| 384 | |
| 385 if(!element_name) { | |
| 386 return; | |
| 387 } else { | |
| 388 if(xpd->current) | |
|
14628
58202142e9ad
[gaim-migrate @ 17369]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
14436
diff
changeset
|
389 node = xmlnode_new_child(xpd->current, (const char*) element_name); |
| 14192 | 390 else |
|
14628
58202142e9ad
[gaim-migrate @ 17369]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
14436
diff
changeset
|
391 node = xmlnode_new((const char *) element_name); |
| 14192 | 392 |
|
14628
58202142e9ad
[gaim-migrate @ 17369]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
14436
diff
changeset
|
393 xmlnode_set_namespace(node, (const char *) namespace); |
| 14192 | 394 |
| 395 for(i=0; i < nb_attributes * 5; i+=5) { | |
| 14228 | 396 char *txt; |
| 14192 | 397 int attrib_len = attributes[i+4] - attributes[i+3]; |
| 398 char *attrib = g_malloc(attrib_len + 1); | |
| 399 memcpy(attrib, attributes[i+3], attrib_len); | |
| 400 attrib[attrib_len] = '\0'; | |
| 14228 | 401 txt = attrib; |
| 14192 | 402 attrib = gaim_unescape_html(txt); |
| 403 g_free(txt); | |
|
14628
58202142e9ad
[gaim-migrate @ 17369]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
14436
diff
changeset
|
404 xmlnode_set_attrib(node, (const char*) attributes[i], attrib); |
| 14192 | 405 g_free(attrib); |
| 406 } | |
| 407 | |
| 408 xpd->current = node; | |
| 409 } | |
| 410 } | |
| 411 | |
| 412 static void | |
| 413 xmlnode_parser_element_end_libxml(void *user_data, const xmlChar *element_name, | |
| 414 const xmlChar *prefix, const xmlChar *namespace) | |
| 415 { | |
| 416 struct _xmlnode_parser_data *xpd = user_data; | |
| 417 | |
| 418 if(!element_name || !xpd->current) | |
| 419 return; | |
| 420 | |
| 421 if(xpd->current->parent) { | |
|
14628
58202142e9ad
[gaim-migrate @ 17369]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
14436
diff
changeset
|
422 if(!xmlStrcmp((xmlChar*) xpd->current->name, element_name)) |
| 14192 | 423 xpd->current = xpd->current->parent; |
| 424 } | |
| 425 } | |
| 426 | |
| 427 static void | |
| 428 xmlnode_parser_element_text_libxml(void *user_data, const xmlChar *text, int text_len) | |
| 429 { | |
| 430 struct _xmlnode_parser_data *xpd = user_data; | |
| 431 | |
| 432 if(!xpd->current) | |
| 433 return; | |
| 434 | |
| 435 if(!text || !text_len) | |
| 436 return; | |
| 437 | |
|
14628
58202142e9ad
[gaim-migrate @ 17369]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
14436
diff
changeset
|
438 xmlnode_insert_data(xpd->current, (const char*) text, text_len); |
| 14192 | 439 } |
| 440 | |
| 441 static xmlSAXHandler xmlnode_parser_libxml = { | |
| 442 .internalSubset = NULL, | |
| 443 .isStandalone = NULL, | |
| 444 .hasInternalSubset = NULL, | |
| 445 .hasExternalSubset = NULL, | |
| 446 .resolveEntity = NULL, | |
| 447 .getEntity = NULL, | |
| 448 .entityDecl = NULL, | |
| 449 .notationDecl = NULL, | |
| 450 .attributeDecl = NULL, | |
| 451 .elementDecl = NULL, | |
| 452 .unparsedEntityDecl = NULL, | |
| 453 .setDocumentLocator = NULL, | |
| 454 .startDocument = NULL, | |
| 455 .endDocument = NULL, | |
| 456 .startElement = NULL, | |
| 457 .endElement = NULL, | |
| 458 .reference = NULL, | |
| 459 .characters = xmlnode_parser_element_text_libxml, | |
| 460 .ignorableWhitespace = NULL, | |
| 461 .processingInstruction = NULL, | |
| 462 .comment = NULL, | |
| 463 .warning = NULL, | |
| 464 .error = NULL, | |
| 465 .fatalError = NULL, | |
| 466 .getParameterEntity = NULL, | |
| 467 .cdataBlock = NULL, | |
| 468 .externalSubset = NULL, | |
| 469 .initialized = XML_SAX2_MAGIC, | |
| 470 ._private = NULL, | |
| 471 .startElementNs = xmlnode_parser_element_start_libxml, | |
| 472 .endElementNs = xmlnode_parser_element_end_libxml, | |
| 473 .serror = NULL | |
| 474 }; | |
| 475 | |
| 476 xmlnode * | |
| 477 xmlnode_from_str(const char *str, gssize size) | |
| 478 { | |
| 479 struct _xmlnode_parser_data *xpd; | |
| 480 xmlnode *ret; | |
| 481 gsize real_size; | |
| 482 | |
| 483 g_return_val_if_fail(str != NULL, NULL); | |
| 484 | |
| 485 real_size = size < 0 ? strlen(str) : size; | |
| 486 xpd = g_new0(struct _xmlnode_parser_data, 1); | |
| 487 | |
| 14322 | 488 if (xmlSAXUserParseMemory(&xmlnode_parser_libxml, xpd, str, real_size) < 0) { |
| 14192 | 489 while(xpd->current && xpd->current->parent) |
| 490 xpd->current = xpd->current->parent; | |
| 491 if(xpd->current) | |
| 492 xmlnode_free(xpd->current); | |
| 493 xpd->current = NULL; | |
| 494 } | |
| 495 ret = xpd->current; | |
| 496 g_free(xpd); | |
| 497 return ret; | |
| 498 } | |
| 499 | |
| 500 xmlnode * | |
| 501 xmlnode_copy(xmlnode *src) | |
| 502 { | |
| 503 xmlnode *ret; | |
| 504 xmlnode *child; | |
| 505 xmlnode *sibling = NULL; | |
| 506 | |
| 507 g_return_val_if_fail(src != NULL, NULL); | |
| 508 | |
| 509 ret = new_node(src->name, src->type); | |
| 510 if(src->data) { | |
| 511 if(src->data_sz) { | |
| 512 ret->data = g_memdup(src->data, src->data_sz); | |
| 513 ret->data_sz = src->data_sz; | |
| 514 } else { | |
| 515 ret->data = g_strdup(src->data); | |
| 516 } | |
| 517 } | |
| 518 | |
| 519 for(child = src->child; child; child = child->next) { | |
| 520 if(sibling) { | |
| 521 sibling->next = xmlnode_copy(child); | |
| 522 sibling = sibling->next; | |
| 523 } else { | |
| 524 ret->child = xmlnode_copy(child); | |
| 525 sibling = ret->child; | |
| 526 } | |
| 527 sibling->parent = ret; | |
| 528 } | |
| 529 | |
| 530 ret->lastchild = sibling; | |
| 531 | |
| 532 return ret; | |
| 533 } | |
| 534 | |
| 535 xmlnode * | |
| 536 xmlnode_get_next_twin(xmlnode *node) | |
| 537 { | |
| 538 xmlnode *sibling; | |
| 539 const char *ns = xmlnode_get_namespace(node); | |
| 540 | |
| 541 g_return_val_if_fail(node != NULL, NULL); | |
| 542 g_return_val_if_fail(node->type == XMLNODE_TYPE_TAG, NULL); | |
| 543 | |
| 544 for(sibling = node->next; sibling; sibling = sibling->next) { | |
| 545 const char *xmlns = NULL; | |
| 546 if(ns) | |
| 547 xmlns = xmlnode_get_namespace(sibling); | |
| 548 | |
| 549 if(sibling->type == XMLNODE_TYPE_TAG && !strcmp(node->name, sibling->name) && | |
| 550 (!ns || (xmlns && !strcmp(ns, xmlns)))) | |
| 551 return sibling; | |
| 552 } | |
| 553 | |
| 554 return NULL; | |
| 555 } |
