source: lib/xmltree.c @ 098b430

Last change on this file since 098b430 was 5ebff60, checked in by dequis <dx@…>, at 2015-02-20T22:50:54Z

Reindent everything to K&R style with tabs

Used uncrustify, with the configuration file in ./doc/uncrustify.cfg

Commit author set to "Indent <please@…>" so that it's easier to
skip while doing git blame.

  • Property mode set to 100644
File size: 16.9 KB
RevLine 
[8f243ad]1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Simple XML (stream) parse tree handling code (Jabber/XMPP, mainly)       *
5*                                                                           *
[0e788f5]6*  Copyright 2006-2012 Wilmer van der Gaast <wilmer@gaast.net>              *
[8f243ad]7*                                                                           *
[4f7255d]8*  This program is free software; you can redistribute it and/or modify     *
9*  it under the terms of the GNU General Public License as published by     *
10*  the Free Software Foundation; either version 2 of the License, or        *
11*  (at your option) any later version.                                      *
[8f243ad]12*                                                                           *
[4f7255d]13*  This program is distributed in the hope that it will be useful,          *
[8f243ad]14*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
[4f7255d]15*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
16*  GNU General Public License for more details.                             *
[8f243ad]17*                                                                           *
[4f7255d]18*  You should have received a copy of the GNU General Public License along  *
19*  with this program; if not, write to the Free Software Foundation, Inc.,  *
20*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
[8f243ad]21*                                                                           *
22****************************************************************************/
23
24#include <glib.h>
25#include <string.h>
26#include <unistd.h>
27#include <ctype.h>
28#include <stdio.h>
29
30#include "xmltree.h"
31
[7a2a486]32#define g_strcasecmp g_ascii_strcasecmp
33#define g_strncasecmp g_ascii_strncasecmp
34
[5ebff60]35static void xt_start_element(GMarkupParseContext *ctx, const gchar *element_name, const gchar **attr_names,
36                             const gchar **attr_values, gpointer data, GError **error)
[8f243ad]37{
38        struct xt_parser *xt = data;
[5ebff60]39        struct xt_node *node = g_new0(struct xt_node, 1), *nt;
[8f243ad]40        int i;
[5ebff60]41
[8f243ad]42        node->parent = xt->cur;
[5ebff60]43        node->name = g_strdup(element_name);
44
[8f243ad]45        /* First count the number of attributes */
[5ebff60]46        for (i = 0; attr_names[i]; i++) {
47                ;
48        }
49
[8f243ad]50        /* Then allocate a NULL-terminated array. */
[5ebff60]51        node->attr = g_new0(struct xt_attr, i + 1);
52
[8f243ad]53        /* And fill it, saving one variable by starting at the end. */
[5ebff60]54        for (i--; i >= 0; i--) {
55                node->attr[i].key = g_strdup(attr_names[i]);
56                node->attr[i].value = g_strdup(attr_values[i]);
[8f243ad]57        }
[5ebff60]58
[8f243ad]59        /* Add it to the linked list of children nodes, if we have a current
60           node yet. */
[5ebff60]61        if (xt->cur) {
62                if (xt->cur->children) {
63                        for (nt = xt->cur->children; nt->next; nt = nt->next) {
64                                ;
65                        }
[8f243ad]66                        nt->next = node;
[5ebff60]67                } else {
[8f243ad]68                        xt->cur->children = node;
69                }
[5ebff60]70        } else if (xt->root) {
[8f243ad]71                /* ERROR situation: A second root-element??? */
72        }
[5ebff60]73
[8f243ad]74        /* Now this node will be the new current node. */
75        xt->cur = node;
76        /* And maybe this is the root? */
[5ebff60]77        if (xt->root == NULL) {
[8f243ad]78                xt->root = node;
[5ebff60]79        }
[8f243ad]80}
81
[5ebff60]82static void xt_text(GMarkupParseContext *ctx, const gchar *text, gsize text_len, gpointer data, GError **error)
[8f243ad]83{
84        struct xt_parser *xt = data;
85        struct xt_node *node = xt->cur;
[5ebff60]86
87        if (node == NULL) {
[8f243ad]88                return;
[5ebff60]89        }
90
[8f243ad]91        /* FIXME: Does g_renew also OFFICIALLY accept NULL arguments? */
[5ebff60]92        node->text = g_renew(char, node->text, node->text_len + text_len + 1);
93        memcpy(node->text + node->text_len, text, text_len);
[8f243ad]94        node->text_len += text_len;
95        /* Zero termination is always nice to have. */
96        node->text[node->text_len] = 0;
97}
98
[5ebff60]99static void xt_end_element(GMarkupParseContext *ctx, const gchar *element_name, gpointer data, GError **error)
[8f243ad]100{
101        struct xt_parser *xt = data;
[5ebff60]102
[8f243ad]103        xt->cur->flags |= XT_COMPLETE;
104        xt->cur = xt->cur->parent;
105}
106
107GMarkupParser xt_parser_funcs =
108{
109        xt_start_element,
110        xt_end_element,
111        xt_text,
112        NULL,
113        NULL
114};
115
[5ebff60]116struct xt_parser *xt_new(const struct xt_handler_entry *handlers, gpointer data)
[8f243ad]117{
[5ebff60]118        struct xt_parser *xt = g_new0(struct xt_parser, 1);
119
[8f243ad]120        xt->data = data;
[4bbcba3]121        xt->handlers = handlers;
[5ebff60]122        xt_reset(xt);
123
[8f243ad]124        return xt;
125}
126
127/* Reset the parser, flush everything we have so far. For example, we need
128   this for XMPP when doing TLS/SASL to restart the stream. */
[5ebff60]129void xt_reset(struct xt_parser *xt)
[8f243ad]130{
[5ebff60]131        if (xt->parser) {
132                g_markup_parse_context_free(xt->parser);
133        }
134
135        xt->parser = g_markup_parse_context_new(&xt_parser_funcs, 0, xt, NULL);
136
137        if (xt->root) {
138                xt_free_node(xt->root);
[8f243ad]139                xt->root = NULL;
140                xt->cur = NULL;
141        }
142}
143
144/* Feed the parser, don't execute any handler. Returns -1 on errors, 0 on
145   end-of-stream and 1 otherwise. */
[5ebff60]146int xt_feed(struct xt_parser *xt, const char *text, int text_len)
[8f243ad]147{
[5ebff60]148        if (!g_markup_parse_context_parse(xt->parser, text, text_len, &xt->gerr)) {
[8f243ad]149                return -1;
150        }
[5ebff60]151
152        return !(xt->root && xt->root->flags & XT_COMPLETE);
[8f243ad]153}
154
155/* Find completed nodes and see if a handler has to be called. Passing
156   a node isn't necessary if you want to start at the root, just pass
[101d84f]157   NULL. This second argument is needed for recursive calls. */
[5ebff60]158int xt_handle(struct xt_parser *xt, struct xt_node *node, int depth)
[8f243ad]159{
160        struct xt_node *c;
161        xt_status st;
162        int i;
[5ebff60]163
164        if (xt->root == NULL) {
[b0ee720]165                return 1;
[5ebff60]166        }
167
168        if (node == NULL) {
169                return xt_handle(xt, xt->root, depth);
170        }
171
172        if (depth != 0) {
173                for (c = node->children; c; c = c->next) {
174                        if (!xt_handle(xt, c, depth > 0 ? depth - 1 : depth)) {
[101d84f]175                                return 0;
[8f243ad]176                        }
177                }
[5ebff60]178        }
179
180        if (node->flags & XT_COMPLETE && !(node->flags & XT_SEEN)) {
181                if (xt->handlers) {
182                        for (i = 0; xt->handlers[i].func; i++) {
183                                /* This one is fun! \o/ */
184
185                                /* If handler.name == NULL it means it should always match. */
186                                if ((xt->handlers[i].name == NULL ||
187                                     /* If it's not, compare. There should always be a name. */
188                                     g_strcasecmp(xt->handlers[i].name, node->name) == 0) &&
189                                    /* If handler.parent == NULL, it's a match. */
190                                    (xt->handlers[i].parent == NULL ||
191                                     /* If there's a parent node, see if the name matches. */
192                                     (node->parent ? g_strcasecmp(xt->handlers[i].parent, node->parent->name) == 0 :
193                                      /* If there's no parent, the handler should mention <root> as a parent. */
194                                      strcmp(xt->handlers[i].parent, "<root>") == 0))) {
195                                        st = xt->handlers[i].func(node, xt->data);
196
197                                        if (st == XT_ABORT) {
198                                                return 0;
199                                        } else if (st != XT_NEXT) {
200                                                break;
201                                        }
202                                }
203                        }
204                }
205
[8f243ad]206                node->flags |= XT_SEEN;
207        }
[5ebff60]208
[8f243ad]209        return 1;
210}
211
212/* Garbage collection: Cleans up all nodes that are handled. Useful for
213   streams because there's no reason to keep a complete packet history
214   in memory. */
[5ebff60]215void xt_cleanup(struct xt_parser *xt, struct xt_node *node, int depth)
[8f243ad]216{
217        struct xt_node *c, *prev;
[5ebff60]218
219        if (!xt || !xt->root) {
[21167d2]220                return;
[5ebff60]221        }
222
223        if (node == NULL) {
224                xt_cleanup(xt, xt->root, depth);
[daae10f]225                return;
226        }
[5ebff60]227
228        if (node->flags & XT_SEEN && node == xt->root) {
229                xt_free_node(xt->root);
[8f243ad]230                xt->root = xt->cur = NULL;
231                /* xt->cur should be NULL already, BTW... */
[5ebff60]232
[8f243ad]233                return;
234        }
[5ebff60]235
[8f243ad]236        /* c contains the current node, prev the previous node (or NULL).
237           I admit, this one's pretty horrible. */
[5ebff60]238        for (c = node->children, prev = NULL; c; prev = c, c = c ? c->next : node->children) {
239                if (c->flags & XT_SEEN) {
[8f243ad]240                        /* Remove the node from the linked list. */
[5ebff60]241                        if (prev) {
[8f243ad]242                                prev->next = c->next;
[5ebff60]243                        } else {
[8f243ad]244                                node->children = c->next;
[5ebff60]245                        }
246
247                        xt_free_node(c);
248
[8f243ad]249                        /* Since the for loop wants to get c->next, make sure
250                           c points at something that exists (and that c->next
251                           will actually be the next item we should check). c
252                           can be NULL now, if we just removed the first item.
253                           That explains the ? thing in for(). */
254                        c = prev;
[5ebff60]255                } else {
[8f243ad]256                        /* This node can't be cleaned up yet, but maybe a
257                           subnode can. */
[5ebff60]258                        if (depth != 0) {
259                                xt_cleanup(xt, c, depth > 0 ? depth - 1 : depth);
260                        }
[8f243ad]261                }
262        }
263}
264
[5ebff60]265struct xt_node *xt_from_string(const char *in, int len)
[d93c0eb9]266{
267        struct xt_parser *parser;
[6f55bec]268        struct xt_node *ret = NULL;
[5ebff60]269
270        if (len == 0) {
271                len = strlen(in);
272        }
273
274        parser = xt_new(NULL, NULL);
275        xt_feed(parser, in, len);
276        if (parser->cur == NULL) {
[6f55bec]277                ret = parser->root;
278                parser->root = NULL;
279        }
[5ebff60]280        xt_free(parser);
281
[d93c0eb9]282        return ret;
283}
284
[5ebff60]285static void xt_to_string_real(struct xt_node *node, GString *str, int indent)
[8f243ad]286{
287        char *buf;
288        struct xt_node *c;
289        int i;
[5ebff60]290
291        if (indent > 1) {
292                g_string_append_len(str, "\n\t\t\t\t\t\t\t\t",
293                                    indent < 8 ? indent : 8);
294        }
295
296        g_string_append_printf(str, "<%s", node->name);
297
298        for (i = 0; node->attr[i].key; i++) {
299                buf = g_markup_printf_escaped(" %s=\"%s\"", node->attr[i].key, node->attr[i].value);
300                g_string_append(str, buf);
301                g_free(buf);
302        }
303
304        if (node->text == NULL && node->children == NULL) {
305                g_string_append(str, "/>");
[8f243ad]306                return;
307        }
[5ebff60]308
309        g_string_append(str, ">");
310        if (node->text_len > 0) {
311                buf = g_markup_escape_text(node->text, node->text_len);
312                g_string_append(str, buf);
313                g_free(buf);
314        }
315
316        for (c = node->children; c; c = c->next) {
317                xt_to_string_real(c, str, indent ? indent + 1 : 0);
318        }
319
320        if (indent > 0 && node->children) {
321                g_string_append_len(str, "\n\t\t\t\t\t\t\t\t",
322                                    indent < 8 ? indent : 8);
323        }
324
325        g_string_append_printf(str, "</%s>", node->name);
[8f243ad]326}
327
[5ebff60]328char *xt_to_string(struct xt_node *node)
[8f243ad]329{
330        GString *ret;
[5ebff60]331
332        ret = g_string_new("");
333        xt_to_string_real(node, ret, 0);
334        return g_string_free(ret, FALSE);
[222b440]335}
336
337/* WITH indentation! */
[5ebff60]338char *xt_to_string_i(struct xt_node *node)
[222b440]339{
340        GString *ret;
[5ebff60]341
342        ret = g_string_new("");
343        xt_to_string_real(node, ret, 1);
344        return g_string_free(ret, FALSE);
[8f243ad]345}
346
[5ebff60]347void xt_print(struct xt_node *node)
[8f243ad]348{
[5ebff60]349        char *str = xt_to_string_i(node);
350
351        fprintf(stderr, "%s", str);
352        g_free(str);
[8f243ad]353}
354
[5ebff60]355struct xt_node *xt_dup(struct xt_node *node)
[022df46]356{
[5ebff60]357        struct xt_node *dup = g_new0(struct xt_node, 1);
[022df46]358        struct xt_node *c, *dc = NULL;
359        int i;
[5ebff60]360
[022df46]361        /* Let's NOT copy the parent element here BTW! Only do it for children. */
[5ebff60]362
363        dup->name = g_strdup(node->name);
[022df46]364        dup->flags = node->flags;
[5ebff60]365        if (node->text) {
366                dup->text = g_memdup(node->text, node->text_len + 1);
[022df46]367                dup->text_len = node->text_len;
368        }
[5ebff60]369
[022df46]370        /* Count the number of attributes and allocate the new array. */
[5ebff60]371        for (i = 0; node->attr[i].key; i++) {
372                ;
373        }
374        dup->attr = g_new0(struct xt_attr, i + 1);
375
[022df46]376        /* Copy them all! */
[5ebff60]377        for (i--; i >= 0; i--) {
378                dup->attr[i].key = g_strdup(node->attr[i].key);
379                dup->attr[i].value = g_strdup(node->attr[i].value);
[022df46]380        }
[5ebff60]381
[022df46]382        /* This nice mysterious loop takes care of the children. */
[5ebff60]383        for (c = node->children; c; c = c->next) {
384                if (dc == NULL) {
385                        dc = dup->children = xt_dup(c);
386                } else {
387                        dc = (dc->next = xt_dup(c));
388                }
389
[022df46]390                dc->parent = dup;
391        }
[5ebff60]392
[022df46]393        return dup;
394}
395
[8f243ad]396/* Frees a node. This doesn't clean up references to itself from parents! */
[5ebff60]397void xt_free_node(struct xt_node *node)
[8f243ad]398{
399        int i;
[5ebff60]400
401        if (!node) {
[21167d2]402                return;
[5ebff60]403        }
404
405        g_free(node->name);
406        g_free(node->text);
407
408        for (i = 0; node->attr[i].key; i++) {
409                g_free(node->attr[i].key);
410                g_free(node->attr[i].value);
411        }
412        g_free(node->attr);
413
414        while (node->children) {
[8f243ad]415                struct xt_node *next = node->children->next;
[5ebff60]416
417                xt_free_node(node->children);
[8f243ad]418                node->children = next;
419        }
[5ebff60]420
421        g_free(node);
[8f243ad]422}
423
[5ebff60]424void xt_free(struct xt_parser *xt)
[8f243ad]425{
[5ebff60]426        if (!xt) {
[21167d2]427                return;
[5ebff60]428        }
429
430        if (xt->root) {
431                xt_free_node(xt->root);
432        }
433
434        g_markup_parse_context_free(xt->parser);
435
436        g_free(xt);
[8f243ad]437}
438
439/* To find a node's child with a specific name, pass the node's children
440   list, not the node itself! The reason you have to do this by hand: So
441   that you can also use this function as a find-next. */
[5ebff60]442struct xt_node *xt_find_node(struct xt_node *node, const char *name)
[8f243ad]443{
[5ebff60]444        while (node) {
[3742fb6]445                char *colon;
[5ebff60]446
447                if (g_strcasecmp(node->name, name) == 0 ||
448                    ((colon = strchr(node->name, ':')) &&
449                     g_strcasecmp(colon + 1, name) == 0)) {
[8f243ad]450                        break;
[5ebff60]451                }
452
[8f243ad]453                node = node->next;
454        }
[5ebff60]455
[8f243ad]456        return node;
457}
458
[d912fe4]459/* More advanced than the one above, understands something like
460   ../foo/bar to find a subnode bar of a node foo which is a child
461   of node's parent. Pass the node directly, not its list of children. */
[5ebff60]462struct xt_node *xt_find_path(struct xt_node *node, const char *name)
[d912fe4]463{
[5ebff60]464        while (name && *name && node) {
[d912fe4]465                char *colon, *slash;
466                int n;
[5ebff60]467
468                if ((slash = strchr(name, '/'))) {
[d912fe4]469                        n = slash - name;
[5ebff60]470                } else {
471                        n = strlen(name);
[d912fe4]472                }
[5ebff60]473
474                if (strncmp(name, "..", n) == 0) {
475                        node = node->parent;
476                } else {
[d912fe4]477                        node = node->children;
[5ebff60]478
479                        while (node) {
480                                if (g_strncasecmp(node->name, name, n) == 0 ||
481                                    ((colon = strchr(node->name, ':')) &&
482                                     g_strncasecmp(colon + 1, name, n) == 0)) {
[d912fe4]483                                        break;
[5ebff60]484                                }
485
[d912fe4]486                                node = node->next;
487                        }
488                }
[5ebff60]489
[d912fe4]490                name = slash ? slash + 1 : NULL;
491        }
[5ebff60]492
[d912fe4]493        return node;
494}
495
[5ebff60]496char *xt_find_attr(struct xt_node *node, const char *key)
[8f243ad]497{
498        int i;
[3742fb6]499        char *colon;
[5ebff60]500
501        if (!node) {
[21167d2]502                return NULL;
[5ebff60]503        }
504
505        for (i = 0; node->attr[i].key; i++) {
506                if (g_strcasecmp(node->attr[i].key, key) == 0) {
[8f243ad]507                        break;
[5ebff60]508                }
509        }
510
[3742fb6]511        /* This is an awful hack that only takes care of namespace prefixes
512           inside a tag. Since IMHO excessive namespace usage in XMPP is
513           massive overkill anyway (this code exists for almost four years
514           now and never really missed it): Meh. */
[5ebff60]515        if (!node->attr[i].key && strcmp(key, "xmlns") == 0 &&
516            (colon = strchr(node->name, ':'))) {
[3742fb6]517                *colon = '\0';
[5ebff60]518                for (i = 0; node->attr[i].key; i++) {
519                        if (strncmp(node->attr[i].key, "xmlns:", 6) == 0 &&
520                            strcmp(node->attr[i].key + 6, node->name) == 0) {
[3742fb6]521                                break;
[5ebff60]522                        }
523                }
[3742fb6]524                *colon = ':';
525        }
[5ebff60]526
[8f243ad]527        return node->attr[i].value;
528}
529
[5ebff60]530struct xt_node *xt_find_node_by_attr(struct xt_node *xt, const char *tag, const char *key, const char *value)
[9ead105]531{
[7b40f17]532        struct xt_node *c;
533        char *s;
534
[5ebff60]535        for (c = xt; (c = xt_find_node(c, tag)); c = c->next) {
536                if ((s = xt_find_attr(c, key)) && strcmp(s, value) == 0) {
[7b40f17]537                        return c;
538                }
539        }
540        return NULL;
541}
542
543
[e6b41b1]544/* Strip a few non-printable characters that aren't allowed in XML streams
545   (and upset some XMPP servers for example). */
[5ebff60]546void xt_strip_text(char *in)
[e6b41b1]547{
548        char *out = in;
549        static const char nonprint[32] = {
550                0, 0, 0, 0, 0, 0, 0, 0, /* 0..7 */
551                0, 1, 1, 0, 0, 1, 0, 0, /* 9 (tab), 10 (\n), 13 (\r) */
552        };
[5ebff60]553
554        if (!in) {
[e6b41b1]555                return;
[5ebff60]556        }
[e6b41b1]557
[5ebff60]558        while (*in) {
559                if ((unsigned int) *in >= ' ' || nonprint[(unsigned int) *in]) {
560                        *out++ = *in;
561                }
562                in++;
[e6b41b1]563        }
564        *out = *in;
565}
566
[5ebff60]567struct xt_node *xt_new_node(char *name, const char *text, struct xt_node *children)
[8f243ad]568{
569        struct xt_node *node, *c;
[5ebff60]570
571        node = g_new0(struct xt_node, 1);
572        node->name = g_strdup(name);
[8f243ad]573        node->children = children;
[5ebff60]574        node->attr = g_new0(struct xt_attr, 1);
575
576        if (text) {
577                node->text = g_strdup(text);
578                xt_strip_text(node->text);
579                node->text_len = strlen(node->text);
580        }
581
582        for (c = children; c; c = c->next) {
583                if (c->parent != NULL) {
[8f243ad]584                        /* ERROR CONDITION: They seem to have a parent already??? */
585                }
[5ebff60]586
[8f243ad]587                c->parent = node;
588        }
[5ebff60]589
[8f243ad]590        return node;
591}
592
[5ebff60]593void xt_add_child(struct xt_node *parent, struct xt_node *child)
[8f243ad]594{
595        struct xt_node *node;
[5ebff60]596
[8f243ad]597        /* This function can actually be used to add more than one child, so
598           do handle this properly. */
[5ebff60]599        for (node = child; node; node = node->next) {
600                if (node->parent != NULL) {
[8f243ad]601                        /* ERROR CONDITION: They seem to have a parent already??? */
602                }
[5ebff60]603
[8f243ad]604                node->parent = parent;
605        }
[5ebff60]606
607        if (parent->children == NULL) {
[8f243ad]608                parent->children = child;
[5ebff60]609        } else {
610                for (node = parent->children; node->next; node = node->next) {
611                        ;
612                }
[8f243ad]613                node->next = child;
614        }
615}
616
[b46769d]617/* Same, but at the beginning. */
[5ebff60]618void xt_insert_child(struct xt_node *parent, struct xt_node *child)
[b46769d]619{
[afb9ea9]620        struct xt_node *node, *last = NULL;
[5ebff60]621
622        if (child == NULL) {
[afb9ea9]623                return; /* BUG */
[5ebff60]624
625        }
626        for (node = child; node; node = node->next) {
627                if (node->parent != NULL) {
[b46769d]628                        /* ERROR CONDITION: They seem to have a parent already??? */
629                }
[5ebff60]630
[b46769d]631                node->parent = parent;
632                last = node;
633        }
[5ebff60]634
[b46769d]635        last->next = parent->children;
636        parent->children = child;
637}
638
[5ebff60]639void xt_add_attr(struct xt_node *node, const char *key, const char *value)
[8f243ad]640{
641        int i;
[5ebff60]642
[090f1cb]643        /* Now actually it'd be nice if we can also change existing attributes
644           (which actually means this function doesn't have the right name).
645           So let's find out if we have this attribute already... */
[5ebff60]646        for (i = 0; node->attr[i].key; i++) {
647                if (strcmp(node->attr[i].key, key) == 0) {
[090f1cb]648                        break;
[5ebff60]649                }
[090f1cb]650        }
[5ebff60]651
652        if (node->attr[i].key == NULL) {
653                /* If not, allocate space for a new attribute. */
654                node->attr = g_renew(struct xt_attr, node->attr, i + 2);
655                node->attr[i].key = g_strdup(key);
656                node->attr[i + 1].key = NULL;
657        } else {
[090f1cb]658                /* Otherwise, free the old value before setting the new one. */
[5ebff60]659                g_free(node->attr[i].value);
[090f1cb]660        }
[5ebff60]661
662        node->attr[i].value = g_strdup(value);
[8f243ad]663}
[259edd4]664
[5ebff60]665int xt_remove_attr(struct xt_node *node, const char *key)
[259edd4]666{
667        int i, last;
[5ebff60]668
669        for (i = 0; node->attr[i].key; i++) {
670                if (strcmp(node->attr[i].key, key) == 0) {
[259edd4]671                        break;
[5ebff60]672                }
673        }
674
[259edd4]675        /* If we didn't find the attribute... */
[5ebff60]676        if (node->attr[i].key == NULL) {
[259edd4]677                return 0;
[5ebff60]678        }
679
680        g_free(node->attr[i].key);
681        g_free(node->attr[i].value);
682
[259edd4]683        /* If it's the last, this is easy: */
[5ebff60]684        if (node->attr[i + 1].key == NULL) {
[259edd4]685                node->attr[i].key = node->attr[i].value = NULL;
[5ebff60]686        } else { /* It's also pretty easy, actually. */
[259edd4]687                /* Find the last item. */
[5ebff60]688                for (last = i + 1; node->attr[last + 1].key; last++) {
689                        ;
690                }
691
[259edd4]692                node->attr[i] = node->attr[last];
693                node->attr[last].key = NULL;
694                node->attr[last].value = NULL;
695        }
[5ebff60]696
[259edd4]697        /* Let's not bother with reallocating memory here. It takes time and
698           most packets don't stay in memory for long anyway. */
[5ebff60]699
[259edd4]700        return 1;
701}
Note: See TracBrowser for help on using the repository browser.