gmarkup.c 89.6 KB
Newer Older
1
2
/* gmarkup.c - Simple XML-like parser
 *
3
 *  Copyright 2000, 2003 Red Hat, Inc.
4
 *  Copyright 2007, 2008 Ryan Lortie <desrt@desrt.ca>
5
 *
6
7
8
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
9
 * version 2.1 of the License, or (at your option) any later version.
10
 *
11
 * This library is distributed in the hope that it will be useful,
12
13
14
15
 * 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.
 *
16
17
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, see <http://www.gnu.org/licenses/>.
18
19
 */

20
#include "config.h"
21

22
#include <stdarg.h>
23
24
25
26
27
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

28
29
#include "gmarkup.h"

30
#include "gatomic.h"
31
#include "gslice.h"
32
33
34
35
#include "galloca.h"
#include "gstrfuncs.h"
#include "gstring.h"
#include "gtestutils.h"
Owen Taylor's avatar
Owen Taylor committed
36
#include "glibintl.h"
Matthias Clasen's avatar
Matthias Clasen committed
37
#include "gthread.h"
38

Matthias Clasen's avatar
Matthias Clasen committed
39
40
/**
 * SECTION:markup
Sébastien Wilmet's avatar
Sébastien Wilmet committed
41
42
 * @Title: Simple XML Subset Parser
 * @Short_description: parses a subset of XML
43
 * @See_also: [XML Specification](http://www.w3.org/TR/REC-xml/)
Matthias Clasen's avatar
Matthias Clasen committed
44
45
46
47
 *
 * The "GMarkup" parser is intended to parse a simple markup format
 * that's a subset of XML. This is a small, efficient, easy-to-use
 * parser. It should not be used if you expect to interoperate with
48
49
 * other applications generating full-scale XML, and must not be used if you
 * expect to parse untrusted input. However, it's very
Matthias Clasen's avatar
Matthias Clasen committed
50
51
52
53
54
55
56
57
 * useful for application data files, config files, etc. where you
 * know your application will be the only one writing the file.
 * Full-scale XML parsers should be able to parse the subset used by
 * GMarkup, so you can easily migrate to full-scale XML at a later
 * time if the need arises.
 *
 * GMarkup is not guaranteed to signal an error on all invalid XML;
 * the parser may accept documents that an XML parser would not.
58
59
 * However, XML documents which are not well-formed (which is a
 * weaker condition than being valid. See the
60
61
62
 * [XML specification](http://www.w3.org/TR/REC-xml/)
 * for definitions of these terms.) are not considered valid GMarkup
 * documents.
Matthias Clasen's avatar
Matthias Clasen committed
63
64
 *
 * Simplifications to XML include:
65
66
67
68
69
70
71
72
73
 *
 * - Only UTF-8 encoding is allowed
 *
 * - No user-defined entities
 *
 * - Processing instructions, comments and the doctype declaration
 *   are "passed through" but are not interpreted in any way
 *
 * - No DTD or validation
Matthias Clasen's avatar
Matthias Clasen committed
74
75
 *
 * The markup format does support:
76
77
78
79
80
 *
 * - Elements
 *
 * - Attributes
 *
81
 * - 5 standard entities: &amp; &lt; &gt; &quot; &apos;
82
83
84
85
 *
 * - Character references
 *
 * - Sections marked as CDATA
Matthias Clasen's avatar
Matthias Clasen committed
86
87
 */

88
G_DEFINE_QUARK (g-markup-error-quark, g_markup_error)
89
90
91
92
93
94
95
96
97

typedef enum
{
  STATE_START,
  STATE_AFTER_OPEN_ANGLE,
  STATE_AFTER_CLOSE_ANGLE,
  STATE_AFTER_ELISION_SLASH, /* the slash that obviates need for end element */
  STATE_INSIDE_OPEN_TAG_NAME,
  STATE_INSIDE_ATTRIBUTE_NAME,
98
  STATE_AFTER_ATTRIBUTE_NAME,
99
100
  STATE_BETWEEN_ATTRIBUTES,
  STATE_AFTER_ATTRIBUTE_EQUALS_SIGN,
101
102
  STATE_INSIDE_ATTRIBUTE_VALUE_SQ,
  STATE_INSIDE_ATTRIBUTE_VALUE_DQ,
103
104
105
  STATE_INSIDE_TEXT,
  STATE_AFTER_CLOSE_TAG_SLASH,
  STATE_INSIDE_CLOSE_TAG_NAME,
106
  STATE_AFTER_CLOSE_TAG_NAME,
107
108
109
110
  STATE_INSIDE_PASSTHROUGH,
  STATE_ERROR
} GMarkupParseState;

111
112
113
114
115
116
117
typedef struct
{
  const char *prev_element;
  const GMarkupParser *prev_parser;
  gpointer prev_user_data;
} GMarkupRecursionTracker;

118
119
120
121
struct _GMarkupParseContext
{
  const GMarkupParser *parser;

122
123
  volatile gint ref_count;

124
125
126
127
128
  GMarkupParseFlags flags;

  gint line_number;
  gint char_number;

129
130
  GMarkupParseState state;

131
132
133
134
135
136
137
138
  gpointer user_data;
  GDestroyNotify dnotify;

  /* A piece of character data or an element that
   * hasn't "ended" yet so we haven't yet called
   * the callback for it.
   */
  GString *partial_chunk;
139
  GSList *spare_chunks;
140
141

  GSList *tag_stack;
142
143
144
145
146
  GSList *tag_stack_gstr;
  GSList *spare_list_nodes;

  GString **attr_names;
  GString **attr_values;
147
148
  gint cur_attr;
  gint alloc_attrs;
149
150

  const gchar *current_text;
Matthias Clasen's avatar
Matthias Clasen committed
151
  gssize       current_text_len;
152
153
154
155
156
157
158
159
160
  const gchar *current_text_end;

  /* used to save the start of the last interesting thingy */
  const gchar *start;

  const gchar *iter;

  guint document_empty : 1;
  guint parsing : 1;
161
  guint awaiting_pop : 1;
Matthias Clasen's avatar
Matthias Clasen committed
162
  gint balance;
163
164
165
166
167

  /* subparser support */
  GSList *subparser_stack; /* (GMarkupRecursionTracker *) */
  const char *subparser_element;
  gpointer held_user_data;
168
169
};

170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
/*
 * Helpers to reduce our allocation overhead, we have
 * a well defined allocation lifecycle.
 */
static GSList *
get_list_node (GMarkupParseContext *context, gpointer data)
{
  GSList *node;
  if (context->spare_list_nodes != NULL)
    {
      node = context->spare_list_nodes;
      context->spare_list_nodes = g_slist_remove_link (context->spare_list_nodes, node);
    }
  else
    node = g_slist_alloc();
  node->data = data;
  return node;
}

static void
free_list_node (GMarkupParseContext *context, GSList *node)
{
  node->data = NULL;
  context->spare_list_nodes = g_slist_concat (node, context->spare_list_nodes);
}

static inline void
string_blank (GString *string)
{
  string->str[0] = '\0';
  string->len = 0;
}

203
204
205
206
207
/**
 * g_markup_parse_context_new:
 * @parser: a #GMarkupParser
 * @flags: one or more #GMarkupParseFlags
 * @user_data: user data to pass to #GMarkupParser functions
Matthias Clasen's avatar
Matthias Clasen committed
208
209
210
 * @user_data_dnotify: user data destroy notifier called when
 *     the parse context is freed
 *
211
212
213
 * Creates a new parse context. A parse context is used to parse
 * marked-up documents. You can feed any number of documents into
 * a context, as long as no errors occur; once an error occurs,
Matthias Clasen's avatar
Matthias Clasen committed
214
215
216
 * the parse context can't continue to parse text (you have to
 * free it and create a new parse context).
 *
217
 * Returns: a new #GMarkupParseContext
218
 **/
219
220
221
222
223
224
225
226
227
228
229
230
GMarkupParseContext *
g_markup_parse_context_new (const GMarkupParser *parser,
                            GMarkupParseFlags    flags,
                            gpointer             user_data,
                            GDestroyNotify       user_data_dnotify)
{
  GMarkupParseContext *context;

  g_return_val_if_fail (parser != NULL, NULL);

  context = g_new (GMarkupParseContext, 1);

231
  context->ref_count = 1;
232
233
234
235
236
237
238
239
240
  context->parser = parser;
  context->flags = flags;
  context->user_data = user_data;
  context->dnotify = user_data_dnotify;

  context->line_number = 1;
  context->char_number = 1;

  context->partial_chunk = NULL;
241
242
  context->spare_chunks = NULL;
  context->spare_list_nodes = NULL;
243
244
245

  context->state = STATE_START;
  context->tag_stack = NULL;
246
  context->tag_stack_gstr = NULL;
247
248
249
250
  context->attr_names = NULL;
  context->attr_values = NULL;
  context->cur_attr = -1;
  context->alloc_attrs = 0;
251
252
253
254
255
256
257
258
259
260
261

  context->current_text = NULL;
  context->current_text_len = -1;
  context->current_text_end = NULL;

  context->start = NULL;
  context->iter = NULL;

  context->document_empty = TRUE;
  context->parsing = FALSE;

262
263
264
265
266
267
268
  context->awaiting_pop = FALSE;
  context->subparser_stack = NULL;
  context->subparser_element = NULL;

  /* this is only looked at if awaiting_pop = TRUE.  initialise anyway. */
  context->held_user_data = NULL;

Matthias Clasen's avatar
Matthias Clasen committed
269
270
  context->balance = 0;

271
272
273
  return context;
}

274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
/**
 * g_markup_parse_context_ref:
 * @context: a #GMarkupParseContext
 *
 * Increases the reference count of @context.
 *
 * Returns: the same @context
 *
 * Since: 2.36
 **/
GMarkupParseContext *
g_markup_parse_context_ref (GMarkupParseContext *context)
{
  g_return_val_if_fail (context != NULL, NULL);
  g_return_val_if_fail (context->ref_count > 0, NULL);

  g_atomic_int_inc (&context->ref_count);

  return context;
}

/**
 * g_markup_parse_context_unref:
 * @context: a #GMarkupParseContext
 *
 * Decreases the reference count of @context.  When its reference count
 * drops to 0, it is freed.
 *
 * Since: 2.36
 **/
void
g_markup_parse_context_unref (GMarkupParseContext *context)
{
  g_return_if_fail (context != NULL);
  g_return_if_fail (context->ref_count > 0);

  if (g_atomic_int_dec_and_test (&context->ref_count))
    g_markup_parse_context_free (context);
}

314
static void
Matthias Clasen's avatar
Matthias Clasen committed
315
string_full_free (gpointer ptr)
316
317
318
319
320
321
{
  g_string_free (ptr, TRUE);
}

static void clear_attributes (GMarkupParseContext *context);

322
323
324
/**
 * g_markup_parse_context_free:
 * @context: a #GMarkupParseContext
Matthias Clasen's avatar
Matthias Clasen committed
325
326
327
328
329
330
 *
 * Frees a #GMarkupParseContext.
 *
 * This function can't be called from inside one of the
 * #GMarkupParser functions or while a subparser is pushed.
 */
331
332
333
334
335
void
g_markup_parse_context_free (GMarkupParseContext *context)
{
  g_return_if_fail (context != NULL);
  g_return_if_fail (!context->parsing);
336
337
  g_return_if_fail (!context->subparser_stack);
  g_return_if_fail (!context->awaiting_pop);
338
339
340
341

  if (context->dnotify)
    (* context->dnotify) (context->user_data);

342
343
344
  clear_attributes (context);
  g_free (context->attr_names);
  g_free (context->attr_values);
345

Matthias Clasen's avatar
Matthias Clasen committed
346
  g_slist_free_full (context->tag_stack_gstr, string_full_free);
347
348
  g_slist_free (context->tag_stack);

Matthias Clasen's avatar
Matthias Clasen committed
349
  g_slist_free_full (context->spare_chunks, string_full_free);
350
351
  g_slist_free (context->spare_list_nodes);

352
353
354
355
356
357
  if (context->partial_chunk)
    g_string_free (context->partial_chunk, TRUE);

  g_free (context);
}

358
359
static void pop_subparser_stack (GMarkupParseContext *context);

360
361
362
363
364
365
366
367
static void
mark_error (GMarkupParseContext *context,
            GError              *error)
{
  context->state = STATE_ERROR;

  if (context->parser->error)
    (*context->parser->error) (context, error, context->user_data);
368
369
370
371
372
373
374
375
376
377

  /* report the error all the way up to free all the user-data */
  while (context->subparser_stack)
    {
      pop_subparser_stack (context);
      context->awaiting_pop = FALSE; /* already been freed */

      if (context->parser->error)
        (*context->parser->error) (context, error, context->user_data);
    }
378
379
}

Matthias Clasen's avatar
Matthias Clasen committed
380
381
382
383
384
385
static void
set_error (GMarkupParseContext  *context,
           GError              **error,
           GMarkupError          code,
           const gchar          *format,
           ...) G_GNUC_PRINTF (4, 5);
386

387
static void
Matthias Clasen's avatar
Matthias Clasen committed
388
389
390
391
set_error_literal (GMarkupParseContext  *context,
                   GError              **error,
                   GMarkupError          code,
                   const gchar          *message)
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
{
  GError *tmp_error;

  tmp_error = g_error_new_literal (G_MARKUP_ERROR, code, message);

  g_prefix_error (&tmp_error,
                  _("Error on line %d char %d: "),
                  context->line_number,
                  context->char_number);

  mark_error (context, tmp_error);

  g_propagate_error (error, tmp_error);
}

407
G_GNUC_PRINTF(4, 5)
408
static void
Matthias Clasen's avatar
Matthias Clasen committed
409
410
411
412
set_error (GMarkupParseContext  *context,
           GError              **error,
           GMarkupError          code,
           const gchar          *format,
413
414
415
           ...)
{
  gchar *s;
416
  gchar *s_valid;
417
418
419
420
421
422
  va_list args;

  va_start (args, format);
  s = g_strdup_vprintf (format, args);
  va_end (args);

Matthias Clasen's avatar
Matthias Clasen committed
423
424
425
  /* Make sure that the GError message is valid UTF-8
   * even if it is complaining about invalid UTF-8 in the markup
   */
426
  s_valid = g_utf8_make_valid (s, -1);
427
  set_error_literal (context, error, code, s);
428

429
  g_free (s);
430
  g_free (s_valid);
431
432
}

433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
static void
propagate_error (GMarkupParseContext  *context,
                 GError              **dest,
                 GError               *src)
{
  if (context->flags & G_MARKUP_PREFIX_ERROR_POSITION)
    g_prefix_error (&src,
                    _("Error on line %d char %d: "),
                    context->line_number,
                    context->char_number);

  mark_error (context, src);

  g_propagate_error (dest, src);
}
448
449
450
451

#define IS_COMMON_NAME_END_CHAR(c) \
  ((c) == '=' || (c) == '/' || (c) == '>' || (c) == ' ')

452
static gboolean
Matthias Clasen's avatar
Matthias Clasen committed
453
454
455
slow_name_validate (GMarkupParseContext  *context,
                    const gchar          *name,
                    GError              **error)
456
{
Matthias Clasen's avatar
Matthias Clasen committed
457
  const gchar *p = name;
458

459
  if (!g_utf8_validate (name, -1, NULL))
460
461
    {
      set_error (context, error, G_MARKUP_ERROR_BAD_UTF8,
462
                 _("Invalid UTF-8 encoded text in name — not valid “%s”"), name);
463
464
465
466
      return FALSE;
    }

  if (!(g_ascii_isalpha (*p) ||
Matthias Clasen's avatar
Matthias Clasen committed
467
468
469
470
        (!IS_COMMON_NAME_END_CHAR (*p) &&
         (*p == '_' ||
          *p == ':' ||
          g_unichar_isalpha (g_utf8_get_char (p))))))
471
472
    {
      set_error (context, error, G_MARKUP_ERROR_PARSE,
473
                 _("“%s” is not a valid name"), name);
474
475
476
477
478
479
480
      return FALSE;
    }

  for (p = g_utf8_next_char (name); *p != '\0'; p = g_utf8_next_char (p))
    {
      /* is_name_char */
      if (!(g_ascii_isalnum (*p) ||
Matthias Clasen's avatar
Matthias Clasen committed
481
482
483
484
485
486
487
488
            (!IS_COMMON_NAME_END_CHAR (*p) &&
             (*p == '.' ||
              *p == '-' ||
              *p == '_' ||
              *p == ':' ||
              g_unichar_isalpha (g_utf8_get_char (p))))))
        {
          set_error (context, error, G_MARKUP_ERROR_PARSE,
489
                     _("“%s” is not a valid name: “%c”"), name, *p);
Matthias Clasen's avatar
Matthias Clasen committed
490
491
          return FALSE;
        }
492
493
    }
  return TRUE;
494
495
}

Matthias Clasen's avatar
Matthias Clasen committed
496
/*
497
498
 * Use me for elements, attributes etc.
 */
499
static gboolean
Matthias Clasen's avatar
Matthias Clasen committed
500
501
502
name_validate (GMarkupParseContext  *context,
               const gchar          *name,
               GError              **error)
503
{
504
505
506
507
508
509
  char mask;
  const char *p;

  /* name start char */
  p = name;
  if (G_UNLIKELY (IS_COMMON_NAME_END_CHAR (*p) ||
Matthias Clasen's avatar
Matthias Clasen committed
510
                  !(g_ascii_isalpha (*p) || *p == '_' || *p == ':')))
511
    goto slow_validate;
Matthias Clasen's avatar
Matthias Clasen committed
512

513
514
515
516
517
518
  for (mask = *p++; *p != '\0'; p++)
    {
      mask |= *p;

      /* is_name_char */
      if (G_UNLIKELY (!(g_ascii_isalnum (*p) ||
Matthias Clasen's avatar
Matthias Clasen committed
519
520
521
522
523
524
                        (!IS_COMMON_NAME_END_CHAR (*p) &&
                         (*p == '.' ||
                          *p == '-' ||
                          *p == '_' ||
                          *p == ':')))))
        goto slow_validate;
525
526
527
528
529
530
531
532
533
    }

  if (mask & 0x80) /* un-common / non-ascii */
    goto slow_validate;

  return TRUE;

 slow_validate:
  return slow_name_validate (context, name, error);
534
535
}

536
static gboolean
Matthias Clasen's avatar
Matthias Clasen committed
537
538
539
540
text_validate (GMarkupParseContext  *context,
               const gchar          *p,
               gint                  len,
               GError              **error)
541
{
542
  if (!g_utf8_validate_len (p, len, NULL))
543
544
    {
      set_error (context, error, G_MARKUP_ERROR_BAD_UTF8,
545
                 _("Invalid UTF-8 encoded text in name — not valid “%s”"), p);
546
547
548
549
550
      return FALSE;
    }
  else
    return TRUE;
}
551
552
553
554
555

static gchar*
char_str (gunichar c,
          gchar   *buf)
{
Matthias Clasen's avatar
Matthias Clasen committed
556
  memset (buf, 0, 8);
557
558
559
560
  g_unichar_to_utf8 (c, buf);
  return buf;
}

561
562
563
/* Format the next UTF-8 character as a gchar* for printing in error output
 * when we encounter a syntax error. This correctly handles invalid UTF-8,
 * emitting it as hex escapes. */
564
565
static gchar*
utf8_str (const gchar *utf8,
566
          gsize        max_len,
567
568
          gchar       *buf)
{
569
  gunichar c = g_utf8_get_char_validated (utf8, max_len);
570
571
  if (c == (gunichar) -1 || c == (gunichar) -2)
    {
572
573
      guchar ch = (max_len > 0) ? (guchar) *utf8 : 0;
      gchar *temp = g_strdup_printf ("\\x%02x", (guint) ch);
574
575
576
577
578
579
      memset (buf, 0, 8);
      memcpy (buf, temp, strlen (temp));
      g_free (temp);
    }
  else
    char_str (c, buf);
580
581
582
  return buf;
}

583
G_GNUC_PRINTF(5, 6)
584
static void
Matthias Clasen's avatar
Matthias Clasen committed
585
586
587
588
589
set_unescape_error (GMarkupParseContext  *context,
                    GError              **error,
                    const gchar          *remaining_text,
                    GMarkupError          code,
                    const gchar          *format,
590
591
592
593
594
595
596
597
598
599
                    ...)
{
  GError *tmp_error;
  gchar *s;
  va_list args;
  gint remaining_newlines;
  const gchar *p;

  remaining_newlines = 0;
  p = remaining_text;
600
  while (*p != '\0')
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
    {
      if (*p == '\n')
        ++remaining_newlines;
      ++p;
    }

  va_start (args, format);
  s = g_strdup_vprintf (format, args);
  va_end (args);

  tmp_error = g_error_new (G_MARKUP_ERROR,
                           code,
                           _("Error on line %d: %s"),
                           context->line_number - remaining_newlines,
                           s);

  g_free (s);

  mark_error (context, tmp_error);

  g_propagate_error (error, tmp_error);
}

624
625
626
627
628
/*
 * re-write the GString in-place, unescaping anything that escaped.
 * most XML does not contain entities, or escaping.
 */
static gboolean
Matthias Clasen's avatar
Matthias Clasen committed
629
630
631
632
unescape_gstring_inplace (GMarkupParseContext  *context,
                          GString              *string,
                          gboolean             *is_ascii,
                          GError              **error)
633
{
634
635
  char mask, *to;
  const char *from;
636
  gboolean normalize_attribute;
637

638
639
640
641
642
  *is_ascii = FALSE;

  /* are we unescaping an attribute or not ? */
  if (context->state == STATE_INSIDE_ATTRIBUTE_VALUE_SQ ||
      context->state == STATE_INSIDE_ATTRIBUTE_VALUE_DQ)
643
644
645
646
    normalize_attribute = TRUE;
  else
    normalize_attribute = FALSE;

647
  /*
648
   * Meeks' theorem: unescaping can only shrink text.
649
650
651
652
   * for &lt; etc. this is obvious, for &#xffff; more
   * thought is required, but this is patently so.
   */
  mask = 0;
Matthias Clasen's avatar
Matthias Clasen committed
653
  for (from = to = string->str; *from != '\0'; from++, to++)
654
    {
655
656
657
658
      *to = *from;

      mask |= *to;
      if (normalize_attribute && (*to == '\t' || *to == '\n'))
Matthias Clasen's avatar
Matthias Clasen committed
659
        *to = ' ';
660
      if (*to == '\r')
Matthias Clasen's avatar
Matthias Clasen committed
661
662
663
664
665
        {
          *to = normalize_attribute ? ' ' : '\n';
          if (from[1] == '\n')
            from++;
        }
666
      if (*from == '&')
Matthias Clasen's avatar
Matthias Clasen committed
667
668
669
670
        {
          from++;
          if (*from == '#')
            {
Matthias Clasen's avatar
Matthias Clasen committed
671
              gint base = 10;
Matthias Clasen's avatar
Matthias Clasen committed
672
673
674
675
676
677
678
              gulong l;
              gchar *end = NULL;

              from++;

              if (*from == 'x')
                {
Matthias Clasen's avatar
Matthias Clasen committed
679
                  base = 16;
Matthias Clasen's avatar
Matthias Clasen committed
680
681
682
683
                  from++;
                }

              errno = 0;
Matthias Clasen's avatar
Matthias Clasen committed
684
              l = strtoul (from, &end, base);
Matthias Clasen's avatar
Matthias Clasen committed
685
686
687
688
689

              if (end == from || errno != 0)
                {
                  set_unescape_error (context, error,
                                      from, G_MARKUP_ERROR_PARSE,
690
                                      _("Failed to parse “%-.*s”, which "
Matthias Clasen's avatar
Matthias Clasen committed
691
692
                                        "should have been a digit "
                                        "inside a character reference "
693
                                        "(&#234; for example) — perhaps "
Matthias Clasen's avatar
Matthias Clasen committed
694
                                        "the digit is too large"),
695
                                      (int)(end - from), from);
Matthias Clasen's avatar
Matthias Clasen committed
696
697
698
699
700
701
702
703
704
705
                  return FALSE;
                }
              else if (*end != ';')
                {
                  set_unescape_error (context, error,
                                      from, G_MARKUP_ERROR_PARSE,
                                      _("Character reference did not end with a "
                                        "semicolon; "
                                        "most likely you used an ampersand "
                                        "character without intending to start "
706
                                        "an entity — escape ampersand as &amp;"));
Matthias Clasen's avatar
Matthias Clasen committed
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
                  return FALSE;
                }
              else
                {
                  /* characters XML 1.1 permits */
                  if ((0 < l && l <= 0xD7FF) ||
                      (0xE000 <= l && l <= 0xFFFD) ||
                      (0x10000 <= l && l <= 0x10FFFF))
                    {
                      gchar buf[8];
                      char_str (l, buf);
                      strcpy (to, buf);
                      to += strlen (buf) - 1;
                      from = end;
                      if (l >= 0x80) /* not ascii */
                        mask |= 0x80;
                    }
                  else
                    {
                      set_unescape_error (context, error,
                                          from, G_MARKUP_ERROR_PARSE,
728
                                          _("Character reference “%-.*s” does not "
Matthias Clasen's avatar
Matthias Clasen committed
729
                                            "encode a permitted character"),
730
                                          (int)(end - from), from);
Matthias Clasen's avatar
Matthias Clasen committed
731
732
733
                      return FALSE;
                    }
                }
734
735
            }

736
          else if (strncmp (from, "lt;", 3) == 0)
Matthias Clasen's avatar
Matthias Clasen committed
737
738
739
740
            {
              *to = '<';
              from += 2;
            }
741
          else if (strncmp (from, "gt;", 3) == 0)
Matthias Clasen's avatar
Matthias Clasen committed
742
743
744
745
            {
              *to = '>';
              from += 2;
            }
746
          else if (strncmp (from, "amp;", 4) == 0)
Matthias Clasen's avatar
Matthias Clasen committed
747
748
749
750
            {
              *to = '&';
              from += 3;
            }
751
          else if (strncmp (from, "quot;", 5) == 0)
Matthias Clasen's avatar
Matthias Clasen committed
752
753
754
755
756
757
758
759
760
761
762
763
764
765
            {
              *to = '"';
              from += 4;
            }
          else if (strncmp (from, "apos;", 5) == 0)
            {
              *to = '\'';
              from += 4;
            }
          else
            {
              if (*from == ';')
                set_unescape_error (context, error,
                                    from, G_MARKUP_ERROR_PARSE,
766
                                    _("Empty entity “&;” seen; valid "
Matthias Clasen's avatar
Matthias Clasen committed
767
768
769
770
771
772
773
                                      "entities are: &amp; &quot; &lt; &gt; &apos;"));
              else
                {
                  const char *end = strchr (from, ';');
                  if (end)
                    set_unescape_error (context, error,
                                        from, G_MARKUP_ERROR_PARSE,
774
                                        _("Entity name “%-.*s” is not known"),
775
                                        (int)(end - from), from);
Matthias Clasen's avatar
Matthias Clasen committed
776
777
778
779
780
781
                  else
                    set_unescape_error (context, error,
                                        from, G_MARKUP_ERROR_PARSE,
                                        _("Entity did not end with a semicolon; "
                                          "most likely you used an ampersand "
                                          "character without intending to start "
782
                                          "an entity — escape ampersand as &amp;"));
Matthias Clasen's avatar
Matthias Clasen committed
783
784
785
786
                }
              return FALSE;
            }
        }
787
788
    }

789
790
  g_assert (to - string->str <= (gssize) string->len);
  if (to - string->str != (gssize) string->len)
791
    g_string_truncate (string, to - string->str);
792

793
  *is_ascii = !(mask & 0x80);
794

795
  return TRUE;
796
797
}

798
static inline gboolean
799
advance_char (GMarkupParseContext *context)
Matthias Clasen's avatar
Matthias Clasen committed
800
{
801
802
  context->iter++;
  context->char_number++;
803

804
  if (G_UNLIKELY (context->iter == context->current_text_end))
805
      return FALSE;
806
807

  else if (G_UNLIKELY (*context->iter == '\n'))
808
    {
809
      context->line_number++;
810
811
      context->char_number = 1;
    }
Matthias Clasen's avatar
Matthias Clasen committed
812

813
  return TRUE;
814
815
}

816
static inline gboolean
817
818
819
820
821
xml_isspace (char c)
{
  return c == ' ' || c == '\t' || c == '\n' || c == '\r';
}

822
823
824
825
826
static void
skip_spaces (GMarkupParseContext *context)
{
  do
    {
827
      if (!xml_isspace (*context->iter))
828
829
830
831
832
833
834
835
836
837
        return;
    }
  while (advance_char (context));
}

static void
advance_to_name_end (GMarkupParseContext *context)
{
  do
    {
838
      if (IS_COMMON_NAME_END_CHAR (*(context->iter)))
839
        return;
840
      if (xml_isspace (*(context->iter)))
Matthias Clasen's avatar
Matthias Clasen committed
841
        return;
842
843
844
845
    }
  while (advance_char (context));
}

846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
static void
release_chunk (GMarkupParseContext *context, GString *str)
{
  GSList *node;
  if (!str)
    return;
  if (str->allocated_len > 256)
    { /* large strings are unusual and worth freeing */
      g_string_free (str, TRUE);
      return;
    }
  string_blank (str);
  node = get_list_node (context, str);
  context->spare_chunks = g_slist_concat (node, context->spare_chunks);
}

862
863
864
865
866
867
static void
add_to_partial (GMarkupParseContext *context,
                const gchar         *text_start,
                const gchar         *text_end)
{
  if (context->partial_chunk == NULL)
868
    { /* allocate a new chunk to parse into */
869

870
      if (context->spare_chunks != NULL)
Matthias Clasen's avatar
Matthias Clasen committed
871
872
873
874
875
876
        {
          GSList *node = context->spare_chunks;
          context->spare_chunks = g_slist_remove_link (context->spare_chunks, node);
          context->partial_chunk = node->data;
          free_list_node (context, node);
        }
877
      else
Matthias Clasen's avatar
Matthias Clasen committed
878
        context->partial_chunk = g_string_sized_new (MAX (28, text_end - text_start));
879
    }
880

881
882
  if (text_start != text_end)
    g_string_insert_len (context->partial_chunk, -1,
Matthias Clasen's avatar
Matthias Clasen committed
883
                         text_start, text_end - text_start);
884
885
}

886
static inline void
887
truncate_partial (GMarkupParseContext *context)
888
889
{
  if (context->partial_chunk != NULL)
890
    string_blank (context->partial_chunk);
891
892
}

893
static inline const gchar*
894
895
896
897
898
current_element (GMarkupParseContext *context)
{
  return context->tag_stack->data;
}

899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
static void
pop_subparser_stack (GMarkupParseContext *context)
{
  GMarkupRecursionTracker *tracker;

  g_assert (context->subparser_stack);

  tracker = context->subparser_stack->data;

  context->awaiting_pop = TRUE;
  context->held_user_data = context->user_data;

  context->user_data = tracker->prev_user_data;
  context->parser = tracker->prev_parser;
  context->subparser_element = tracker->prev_element;
  g_slice_free (GMarkupRecursionTracker, tracker);

  context->subparser_stack = g_slist_delete_link (context->subparser_stack,
                                                  context->subparser_stack);
}

920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
static void
push_partial_as_tag (GMarkupParseContext *context)
{
  GString *str = context->partial_chunk;
  /* sadly, this is exported by gmarkup_get_element_stack as-is */
  context->tag_stack = g_slist_concat (get_list_node (context, str->str), context->tag_stack);
  context->tag_stack_gstr = g_slist_concat (get_list_node (context, str), context->tag_stack_gstr);
  context->partial_chunk = NULL;
}

static void
pop_tag (GMarkupParseContext *context)
{
  GSList *nodea, *nodeb;

  nodea = context->tag_stack;
  nodeb = context->tag_stack_gstr;
  release_chunk (context, nodeb->data);
  context->tag_stack = g_slist_remove_link (context->tag_stack, nodea);
  context->tag_stack_gstr = g_slist_remove_link (context->tag_stack_gstr, nodeb);
  free_list_node (context, nodea);
  free_list_node (context, nodeb);
}

944
945
946
947
948
949
950
951
952
953
954
955
static void
possibly_finish_subparser (GMarkupParseContext *context)
{
  if (current_element (context) == context->subparser_element)
    pop_subparser_stack (context);
}

static void
ensure_no_outstanding_subparser (GMarkupParseContext *context)
{
  if (context->awaiting_pop)
    g_critical ("During the first end_element call after invoking a "
Matthias Clasen's avatar
Matthias Clasen committed
956
957
958
959
                "subparser you must pop the subparser stack and handle "
                "the freeing of the subparser user_data.  This can be "
                "done by calling the end function of the subparser.  "
                "Very probably, your program just leaked memory.");
960
961
962
963
964
965

  /* let valgrind watch the pointer disappear... */
  context->held_user_data = NULL;
  context->awaiting_pop = FALSE;
}

966
967
968
static const gchar*
current_attribute (GMarkupParseContext *context)
{
969
  g_assert (context->cur_attr >= 0);
970
  return context->attr_names[context->cur_attr]->str;
971
972
}

973
static gboolean
974
add_attribute (GMarkupParseContext *context, GString *str)
975
{
976
977
978
979
  /* Sanity check on the number of attributes. */
  if (context->cur_attr >= 1000)
    return FALSE;

980
981
982
  if (context->cur_attr + 2 >= context->alloc_attrs)
    {
      context->alloc_attrs += 5; /* silly magic number */
983
984
      context->attr_names = g_realloc (context->attr_names, sizeof(GString*)*context->alloc_attrs);
      context->attr_values = g_realloc (context->attr_values, sizeof(GString*)*context->alloc_attrs);
985
986
    }
  context->cur_attr++;
987
  context->attr_names[context->cur_attr] = str;
988
989
  context->attr_values[context->cur_attr] = NULL;
  context->attr_names[context->cur_attr+1] = NULL;
990
  context->attr_values[context->cur_attr+1] = NULL;
991
992

  return TRUE;
993
994
}

995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
static void
clear_attributes (GMarkupParseContext *context)
{
  /* Go ahead and free the attributes. */
  for (; context->cur_attr >= 0; context->cur_attr--)
    {
      int pos = context->cur_attr;
      release_chunk (context, context->attr_names[pos]);
      release_chunk (context, context->attr_values[pos]);
      context->attr_names[pos] = context->attr_values[pos] = NULL;
    }
  g_assert (context->cur_attr == -1);
  g_assert (context->attr_names == NULL ||
Matthias Clasen's avatar
Matthias Clasen committed
1008
            context->attr_names[0] == NULL);
1009
  g_assert (context->attr_values == NULL ||
Matthias Clasen's avatar
Matthias Clasen committed
1010
            context->attr_values[0] == NULL);
1011
}
1012
1013

/* This has to be a separate function to ensure the alloca's
Matthias Clasen's avatar
Matthias Clasen committed
1014
1015
1016
 * are unwound on exit - otherwise we grow & blow the stack
 * with large documents
 */
1017
static inline void
Matthias Clasen's avatar
Matthias Clasen committed
1018
1019
emit_start_element (GMarkupParseContext  *context,
                    GError              **error)
1020
{
1021
  int i, j = 0;
1022
1023
1024
1025
  const gchar *start_name;
  const gchar **attr_names;
  const gchar **attr_values;
  GError *tmp_error;
Matthias Clasen's avatar
Matthias Clasen committed
1026

1027
1028
1029
1030
1031
1032
  /* In case we want to ignore qualified tags and we see that we have
   * one here, we push a subparser.  This will ignore all tags inside of
   * the qualified tag.
   *
   * We deal with the end of the subparser from emit_end_element.
   */
1033
  if ((context->flags & G_MARKUP_IGNORE_QUALIFIED) && strchr (current_element (context), ':'))
1034
1035
1036
    {
      static const GMarkupParser ignore_parser;
      g_markup_parse_context_push (context, &ignore_parser, NULL);
1037
      clear_attributes (context);
1038
1039
1040
      return;
    }

1041
1042
1043
1044
  attr_names = g_newa (const gchar *, context->cur_attr + 2);
  attr_values = g_newa (const gchar *, context->cur_attr + 2);
  for (i = 0; i < context->cur_attr + 1; i++)
    {
1045
1046
1047
1048
1049
1050
1051
      /* Possibly omit qualified attribute names from the list */
      if ((context->flags & G_MARKUP_IGNORE_QUALIFIED) && strchr (context->attr_names[i]->str, ':'))
        continue;

      attr_names[j] = context->attr_names[i]->str;
      attr_values[j] = context->attr_values[i]->str;
      j++;
1052
    }
1053
1054
  attr_names[j] = NULL;
  attr_values[j] = NULL;
Matthias Clasen's avatar
Matthias Clasen committed
1055

1056
1057
1058
  /* Call user callback for element start */
  tmp_error = NULL;
  start_name = current_element (context);
Matthias Clasen's avatar
Matthias Clasen committed
1059

1060
1061
1062
1063
  if (!name_validate (context, start_name, error))
    return;

  if (context->parser->start_element)
1064
    (* context->parser->start_element) (context,
Matthias Clasen's avatar
Matthias Clasen committed
1065
1066
1067
1068
1069
                                        start_name,
                                        (const gchar **)attr_names,
                                        (const gchar **)attr_values,
                                        context->user_data,
                                        &tmp_error);
1070
  clear_attributes (context);
Matthias Clasen's avatar
Matthias Clasen committed
1071

1072
1073
1074
1075
  if (tmp_error != NULL)
    propagate_error (context, error, tmp_error);
}

1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
static void
emit_end_element (GMarkupParseContext  *context,
                  GError              **error)
{
  /* We need to pop the tag stack and call the end_element
   * function, since this is the close tag
   */
  GError *tmp_error = NULL;

  g_assert (context->tag_stack != NULL);

  possibly_finish_subparser (context);

1089
  /* We might have just returned from our ignore subparser */
1090
  if ((context->flags & G_MARKUP_IGNORE_QUALIFIED) && strchr (current_element (context), ':'))
1091
1092
1093
1094
1095
1096
    {
      g_markup_parse_context_pop (context);
      pop_tag (context);
      return;
    }

1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
  tmp_error = NULL;
  if (context->parser->end_element)
    (* context->parser->end_element) (context,
                                      current_element (context),
                                      context->user_data,
                                      &tmp_error);

  ensure_no_outstanding_subparser (context);

  if (tmp_error)
    {
      mark_error (context, tmp_error);
      g_propagate_error (error, tmp_error);
    }

  pop_tag (context);
}

1115
1116
1117
1118
1119
1120
/**
 * g_markup_parse_context_parse:
 * @context: a #GMarkupParseContext
 * @text: chunk of text to parse
 * @text_len: length of @text in bytes
 * @error: return location for a #GError
Matthias Clasen's avatar
Matthias Clasen committed
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
 *
 * Feed some data to the #GMarkupParseContext.
 *
 * The data need not be valid UTF-8; an error will be signaled if
 * it's invalid. The data need not be an entire document; you can
 * feed a document into the parser incrementally, via multiple calls
 * to this function. Typically, as you receive data from a network
 * connection or file, you feed each received chunk of data into this
 * function, aborting the process if an error occurs. Once an error
 * is reported, no further data may be fed to the #GMarkupParseContext;
 * all errors are fatal.
 *
1133
 * Returns: %FALSE if an error occurred, %TRUE on success
Matthias Clasen's avatar
Matthias Clasen committed
1134
 */
1135
gboolean
Matthias Clasen's avatar
Matthias Clasen committed
1136
1137
1138
1139
g_markup_parse_context_parse (GMarkupParseContext  *context,
                              const gchar          *text,
                              gssize                text_len,
                              GError              **error)
1140
1141
1142
1143
1144
{
  g_return_val_if_fail (context != NULL, FALSE);
  g_return_val_if_fail (text != NULL, FALSE);
  g_return_val_if_fail (context->state != STATE_ERROR, FALSE);
  g_return_val_if_fail (!context->parsing, FALSE);
Matthias Clasen's avatar
Matthias Clasen committed
1145

1146
1147
1148
1149
1150
  if (text_len < 0)
    text_len = strlen (text);

  if (text_len == 0)
    return TRUE;
Matthias Clasen's avatar
Matthias Clasen committed
1151

1152
  context->parsing = TRUE;
Matthias Clasen's avatar
Matthias Clasen committed
1153

1154
1155
1156

  context->current_text = text;
  context->current_text_len = text_len;
1157
  context->current_text_end = context->current_text + text_len;
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
  context->iter = context->current_text;
  context->start = context->iter;

  while (context->iter != context->current_text_end)
    {
      switch (context->state)
        {
        case STATE_START:
          /* Possible next state: AFTER_OPEN_ANGLE */

          g_assert (context->tag_stack == NULL);

          /* whitespace is ignored outside of any elements */
          skip_spaces (context);

          if (context->iter != context->current_text_end)
            {
              if (*context->iter == '<')
                {
                  /* Move after the open angle */
                  advance_char (context);

                  context->state = STATE_AFTER_OPEN_ANGLE;

                  /* this could start a passthrough */
                  context->start = context->iter;

                  /* document is now non-empty */
                  context->document_empty = FALSE;
                }
              else
                {
1190
1191
1192
1193
                  set_error_literal (context,
                                     error,
                                     G_MARKUP_ERROR_PARSE,
                                     _("Document must begin with an element (e.g. <book>)"));
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
                }
            }
          break;

        case STATE_AFTER_OPEN_ANGLE:
          /* Possible next states: INSIDE_OPEN_TAG_NAME,
           *  AFTER_CLOSE_TAG_SLASH, INSIDE_PASSTHROUGH
           */
          if (*context->iter == '?' ||
              *context->iter == '!')
            {
              /* include < in the passthrough */
              const gchar *openangle = "<";
              add_to_partial (context, openangle, openangle + 1);
              context->start = context->iter;
Matthias Clasen's avatar
Matthias Clasen committed
1209
              context->balance = 1;
1210
1211
1212
1213
1214
1215
1216
1217
1218
              context->state = STATE_INSIDE_PASSTHROUGH;
            }
          else if (*context->iter == '/')
            {
              /* move after it */
              advance_char (context);

              context->state = STATE_AFTER_CLOSE_TAG_SLASH;
            }
1219
          else if (!IS_COMMON_NAME_END_CHAR (*(context->iter)))
1220
1221
1222
1223
1224
1225
1226
1227
            {
              context->state = STATE_INSIDE_OPEN_TAG_NAME;

              /* start of tag name */
              context->start = context->iter;
            }
          else
            {
Matthias Clasen's avatar
Matthias Clasen committed
1228
1229
              gchar buf[8];

1230
1231
1232
              set_error (context,
                         error,
                         G_MARKUP_ERROR_PARSE,
1233
1234
                         _("“%s” is not a valid character following "
                           "a “<” character; it may not begin an "
1235
                           "element name"),
1236
1237
                         utf8_str (context->iter,
                                   context->current_text_end - context->iter, buf));
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
            }
          break;

          /* The AFTER_CLOSE_ANGLE state is actually sort of
           * broken, because it doesn't correspond to a range
           * of characters in the input stream as the others do,
           * and thus makes things harder to conceptualize
           */
        case STATE_AFTER_CLOSE_ANGLE:
          /* Possible next states: INSIDE_TEXT, STATE_START */
          if (context->tag_stack == NULL)
            {
              context->start = NULL;
              context->state = STATE_START;
            }
          else
            {
              context->start = context->iter;
              context->state = STATE_INSIDE_TEXT;
            }
          break;

        case STATE_AFTER_ELISION_SLASH:
          /* Possible next state: AFTER_CLOSE_ANGLE */
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
          if (*context->iter == '>')
            {
              /* move after the close angle */
              advance_char (context);
              context->state = STATE_AFTER_CLOSE_ANGLE;
              emit_end_element (context, error);
            }
          else
            {
              gchar buf[8];
1272

1273
1274
1275
              set_error (context,
                         error,
                         G_MARKUP_ERROR_PARSE,
1276
1277
                         _("Odd character “%s”, expected a “>” character "
                           "to end the empty-element tag “%s”"),
1278
1279
                         utf8_str (context->iter,
                                   context->current_text_end - context->iter, buf),
1280
1281
                         current_element (context));
            }
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
          break;

        case STATE_INSIDE_OPEN_TAG_NAME:
          /* Possible next states: BETWEEN_ATTRIBUTES */

          /* if there's a partial chunk then it's the first part of the
           * tag name. If there's a context->start then it's the start
           * of the tag name in current_text, the partial chunk goes
           * before that start though.
           */
          advance_to_name_end (context);

          if (context->iter == context->current_text_end)
            {
              /* The name hasn't necessarily ended. Merge with
               * partial chunk, leave state unchanged.
               */
              add_to_partial (context, context->start, context->iter);
            }
          else
            {
              /* The name has ended. Combine it with the partial chunk
               * if any; push it on the stack; enter next state.
               */
              add_to_partial (context, context->start, context->iter);
Matthias Clasen's avatar
Matthias Clasen committed
1307
              push_partial_as_tag (context);
1308
1309
1310
1311
1312
1313
1314

              context->state = STATE_BETWEEN_ATTRIBUTES;
              context->start = NULL;
            }
          break;

        case STATE_INSIDE_ATTRIBUTE_NAME:
1315
1316
1317
          /* Possible next states: AFTER_ATTRIBUTE_NAME */

          advance_to_name_end (context);
Matthias Clasen's avatar
Matthias Clasen committed
1318
          add_to_partial (context, context->start, context->iter);
1319
1320
1321
1322
1323

          /* read the full name, if we enter the equals sign state
           * then add the attribute to the list (without the value),
           * otherwise store a partial chunk to be prepended later.
           */
1324
          if (context->iter != context->current_text_end)
Matthias Clasen's avatar
Matthias Clasen committed
1325
1326
            context->state = STATE_AFTER_ATTRIBUTE_NAME;
          break;
1327

Matthias Clasen's avatar
Matthias Clasen committed
1328
        case STATE_AFTER_ATTRIBUTE_NAME:
1329
          /* Possible next states: AFTER_ATTRIBUTE_EQUALS_SIGN */
1330

Matthias Clasen's avatar
Matthias Clasen committed
1331
1332
1333
1334
1335
1336
1337
1338
1339
          skip_spaces (context);

          if (context->iter != context->current_text_end)
            {
              /* The name has ended. Combine it with the partial chunk
               * if any; push it on the stack; enter next state.
               */
              if (!name_validate (context, context->partial_chunk->str, error))
                break;
1340

1341
1342
1343
1344
1345
1346
1347
1348
1349
              if (!add_attribute (context, context->partial_chunk))
                {
                  set_error (context,
                             error,
                             G_MARKUP_ERROR_PARSE,
                             _("Too many attributes in element “%s”"),
                             current_element (context));
                  break;
                }
1350

1351
1352
              context->partial_chunk = NULL;
              context->start = NULL;
Matthias Clasen's avatar
Matthias Clasen committed
1353

1354
1355
1356
1357
1358
1359
1360
              if (*context->iter == '=')
                {
                  advance_char (context);
                  context->state = STATE_AFTER_ATTRIBUTE_EQUALS_SIGN;
                }
              else
                {