1251876Speter/* Licensed to the Apache Software Foundation (ASF) under one or more 2251876Speter * contributor license agreements. See the NOTICE file distributed with 3251876Speter * this work for additional information regarding copyright ownership. 4251876Speter * The ASF licenses this file to You under the Apache License, Version 2.0 5251876Speter * (the "License"); you may not use this file except in compliance with 6251876Speter * the License. You may obtain a copy of the License at 7251876Speter * 8251876Speter * http://www.apache.org/licenses/LICENSE-2.0 9251876Speter * 10251876Speter * Unless required by applicable law or agreed to in writing, software 11251876Speter * distributed under the License is distributed on an "AS IS" BASIS, 12251876Speter * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13251876Speter * See the License for the specific language governing permissions and 14251876Speter * limitations under the License. 15251876Speter */ 16251876Speter 17251876Speter#include "apr.h" 18251876Speter#include "apr_strings.h" 19251876Speter 20251876Speter#define APR_WANT_STDIO /* for sprintf() */ 21251876Speter#define APR_WANT_STRFUNC 22251876Speter#include "apr_want.h" 23251876Speter 24251876Speter#include "apr_xml.h" 25251876Speter 26251876Speter#include "apu_config.h" 27251876Speter 28251876Speter#if defined(HAVE_XMLPARSE_XMLPARSE_H) 29251876Speter#include <xmlparse/xmlparse.h> 30251876Speter#elif defined(HAVE_XMLTOK_XMLPARSE_H) 31251876Speter#include <xmltok/xmlparse.h> 32251876Speter#elif defined(HAVE_XML_XMLPARSE_H) 33251876Speter#include <xml/xmlparse.h> 34251876Speter#else 35251876Speter#include <expat.h> 36251876Speter#endif 37251876Speter 38251876Speter#define DEBUG_CR "\r\n" 39251876Speter 40251876Speterstatic const char APR_KW_xmlns[] = { 0x78, 0x6D, 0x6C, 0x6E, 0x73, '\0' }; 41251876Speterstatic const char APR_KW_xmlns_lang[] = { 0x78, 0x6D, 0x6C, 0x3A, 0x6C, 0x61, 0x6E, 0x67, '\0' }; 42251876Speterstatic const char APR_KW_DAV[] = { 0x44, 0x41, 0x56, 0x3A, '\0' }; 43251876Speter 44251876Speter/* errors related to namespace processing */ 45251876Speter#define APR_XML_NS_ERROR_UNKNOWN_PREFIX (-1000) 46251876Speter#define APR_XML_NS_ERROR_INVALID_DECL (-1001) 47251876Speter 48251876Speter/* test for a namespace prefix that begins with [Xx][Mm][Ll] */ 49251876Speter#define APR_XML_NS_IS_RESERVED(name) \ 50251876Speter ( (name[0] == 0x58 || name[0] == 0x78) && \ 51251876Speter (name[1] == 0x4D || name[1] == 0x6D) && \ 52251876Speter (name[2] == 0x4C || name[2] == 0x6C) ) 53251876Speter 54251876Speter 55251876Speter/* the real (internal) definition of the parser context */ 56251876Speterstruct apr_xml_parser { 57251876Speter apr_xml_doc *doc; /* the doc we're parsing */ 58251876Speter apr_pool_t *p; /* the pool we allocate from */ 59251876Speter apr_xml_elem *cur_elem; /* current element */ 60251876Speter 61251876Speter int error; /* an error has occurred */ 62251876Speter#define APR_XML_ERROR_EXPAT 1 63251876Speter#define APR_XML_ERROR_PARSE_DONE 2 64251876Speter/* also: public APR_XML_NS_ERROR_* values (if any) */ 65251876Speter 66251876Speter XML_Parser xp; /* the actual (Expat) XML parser */ 67251876Speter enum XML_Error xp_err; /* stored Expat error code */ 68251876Speter}; 69251876Speter 70251876Speter/* struct for scoping namespace declarations */ 71251876Spetertypedef struct apr_xml_ns_scope { 72251876Speter const char *prefix; /* prefix used for this ns */ 73251876Speter int ns; /* index into namespace table */ 74251876Speter int emptyURI; /* the namespace URI is the empty string */ 75251876Speter struct apr_xml_ns_scope *next; /* next scoped namespace */ 76251876Speter} apr_xml_ns_scope; 77251876Speter 78251876Speter 79251876Speter/* return namespace table index for a given prefix */ 80251876Speterstatic int find_prefix(apr_xml_parser *parser, const char *prefix) 81251876Speter{ 82251876Speter apr_xml_elem *elem = parser->cur_elem; 83251876Speter 84251876Speter /* 85251876Speter ** Walk up the tree, looking for a namespace scope that defines this 86251876Speter ** prefix. 87251876Speter */ 88251876Speter for (; elem; elem = elem->parent) { 89362181Sdim apr_xml_ns_scope *ns_scope; 90251876Speter 91251876Speter for (ns_scope = elem->ns_scope; ns_scope; ns_scope = ns_scope->next) { 92251876Speter if (strcmp(prefix, ns_scope->prefix) == 0) { 93251876Speter if (ns_scope->emptyURI) { 94251876Speter /* 95251876Speter ** It is possible to set the default namespace to an 96251876Speter ** empty URI string; this resets the default namespace 97251876Speter ** to mean "no namespace." We just found the prefix 98251876Speter ** refers to an empty URI, so return "no namespace." 99251876Speter */ 100251876Speter return APR_XML_NS_NONE; 101251876Speter } 102251876Speter 103251876Speter return ns_scope->ns; 104251876Speter } 105251876Speter } 106251876Speter } 107251876Speter 108251876Speter /* 109251876Speter * If the prefix is empty (""), this means that a prefix was not 110251876Speter * specified in the element/attribute. The search that was performed 111251876Speter * just above did not locate a default namespace URI (which is stored 112251876Speter * into ns_scope with an empty prefix). This means the element/attribute 113251876Speter * has "no namespace". We have a reserved value for this. 114251876Speter */ 115251876Speter if (*prefix == '\0') { 116251876Speter return APR_XML_NS_NONE; 117251876Speter } 118251876Speter 119251876Speter /* not found */ 120251876Speter return APR_XML_NS_ERROR_UNKNOWN_PREFIX; 121251876Speter} 122251876Speter 123362181Sdim/* return original prefix given ns index */ 124362181Sdimstatic const char * find_prefix_name(const apr_xml_elem *elem, int ns, int parent) 125362181Sdim{ 126362181Sdim /* 127362181Sdim ** Walk up the tree, looking for a namespace scope that defines this 128362181Sdim ** prefix. 129362181Sdim */ 130362181Sdim for (; elem; elem = parent ? elem->parent : NULL) { 131362181Sdim apr_xml_ns_scope *ns_scope = elem->ns_scope; 132362181Sdim 133362181Sdim for (; ns_scope; ns_scope = ns_scope->next) { 134362181Sdim if (ns_scope->ns == ns) 135362181Sdim return ns_scope->prefix; 136362181Sdim } 137362181Sdim } 138362181Sdim /* not found */ 139362181Sdim return ""; 140362181Sdim} 141362181Sdim 142362181Sdim 143251876Speterstatic void start_handler(void *userdata, const char *name, const char **attrs) 144251876Speter{ 145251876Speter apr_xml_parser *parser = userdata; 146251876Speter apr_xml_elem *elem; 147251876Speter apr_xml_attr *attr; 148251876Speter apr_xml_attr *prev; 149251876Speter char *colon; 150251876Speter const char *quoted; 151251876Speter char *elem_name; 152251876Speter 153251876Speter /* punt once we find an error */ 154251876Speter if (parser->error) 155251876Speter return; 156251876Speter 157251876Speter elem = apr_pcalloc(parser->p, sizeof(*elem)); 158251876Speter 159251876Speter /* prep the element */ 160251876Speter elem->name = elem_name = apr_pstrdup(parser->p, name); 161251876Speter 162251876Speter /* fill in the attributes (note: ends up in reverse order) */ 163251876Speter while (*attrs) { 164251876Speter attr = apr_palloc(parser->p, sizeof(*attr)); 165251876Speter attr->name = apr_pstrdup(parser->p, *attrs++); 166251876Speter attr->value = apr_pstrdup(parser->p, *attrs++); 167251876Speter attr->next = elem->attr; 168251876Speter elem->attr = attr; 169251876Speter } 170251876Speter 171251876Speter /* hook the element into the tree */ 172251876Speter if (parser->cur_elem == NULL) { 173251876Speter /* no current element; this also becomes the root */ 174251876Speter parser->cur_elem = parser->doc->root = elem; 175251876Speter } 176251876Speter else { 177251876Speter /* this element appeared within the current elem */ 178251876Speter elem->parent = parser->cur_elem; 179251876Speter 180251876Speter /* set up the child/sibling links */ 181251876Speter if (elem->parent->last_child == NULL) { 182251876Speter /* no first child either */ 183251876Speter elem->parent->first_child = elem->parent->last_child = elem; 184251876Speter } 185251876Speter else { 186251876Speter /* hook onto the end of the parent's children */ 187251876Speter elem->parent->last_child->next = elem; 188251876Speter elem->parent->last_child = elem; 189251876Speter } 190251876Speter 191251876Speter /* this element is now the current element */ 192251876Speter parser->cur_elem = elem; 193251876Speter } 194251876Speter 195251876Speter /* scan the attributes for namespace declarations */ 196251876Speter for (prev = NULL, attr = elem->attr; 197251876Speter attr; 198251876Speter attr = attr->next) { 199251876Speter if (strncmp(attr->name, APR_KW_xmlns, 5) == 0) { 200251876Speter const char *prefix = &attr->name[5]; 201251876Speter apr_xml_ns_scope *ns_scope; 202251876Speter 203251876Speter /* test for xmlns:foo= form and xmlns= form */ 204251876Speter if (*prefix == 0x3A) { 205251876Speter /* a namespace prefix declaration must have a 206251876Speter non-empty value. */ 207251876Speter if (attr->value[0] == '\0') { 208251876Speter parser->error = APR_XML_NS_ERROR_INVALID_DECL; 209251876Speter return; 210251876Speter } 211251876Speter ++prefix; 212251876Speter } 213251876Speter else if (*prefix != '\0') { 214251876Speter /* advance "prev" since "attr" is still present */ 215251876Speter prev = attr; 216251876Speter continue; 217251876Speter } 218251876Speter 219251876Speter /* quote the URI before we ever start working with it */ 220251876Speter quoted = apr_xml_quote_string(parser->p, attr->value, 1); 221251876Speter 222251876Speter /* build and insert the new scope */ 223251876Speter ns_scope = apr_pcalloc(parser->p, sizeof(*ns_scope)); 224251876Speter ns_scope->prefix = prefix; 225251876Speter ns_scope->ns = apr_xml_insert_uri(parser->doc->namespaces, quoted); 226251876Speter ns_scope->emptyURI = *quoted == '\0'; 227251876Speter ns_scope->next = elem->ns_scope; 228251876Speter elem->ns_scope = ns_scope; 229251876Speter 230251876Speter /* remove this attribute from the element */ 231251876Speter if (prev == NULL) 232251876Speter elem->attr = attr->next; 233251876Speter else 234251876Speter prev->next = attr->next; 235251876Speter 236251876Speter /* Note: prev will not be advanced since we just removed "attr" */ 237251876Speter } 238251876Speter else if (strcmp(attr->name, APR_KW_xmlns_lang) == 0) { 239251876Speter /* save away the language (in quoted form) */ 240251876Speter elem->lang = apr_xml_quote_string(parser->p, attr->value, 1); 241251876Speter 242251876Speter /* remove this attribute from the element */ 243251876Speter if (prev == NULL) 244251876Speter elem->attr = attr->next; 245251876Speter else 246251876Speter prev->next = attr->next; 247251876Speter 248251876Speter /* Note: prev will not be advanced since we just removed "attr" */ 249251876Speter } 250251876Speter else { 251251876Speter /* advance "prev" since "attr" is still present */ 252251876Speter prev = attr; 253251876Speter } 254251876Speter } 255251876Speter 256251876Speter /* 257251876Speter ** If an xml:lang attribute didn't exist (lang==NULL), then copy the 258251876Speter ** language from the parent element (if present). 259251876Speter ** 260251876Speter ** NOTE: elem_size() *depends* upon this pointer equality. 261251876Speter */ 262251876Speter if (elem->lang == NULL && elem->parent != NULL) 263251876Speter elem->lang = elem->parent->lang; 264251876Speter 265251876Speter /* adjust the element's namespace */ 266251876Speter colon = strchr(elem_name, 0x3A); 267251876Speter if (colon == NULL) { 268251876Speter /* 269251876Speter * The element is using the default namespace, which will always 270251876Speter * be found. Either it will be "no namespace", or a default 271251876Speter * namespace URI has been specified at some point. 272251876Speter */ 273251876Speter elem->ns = find_prefix(parser, ""); 274251876Speter } 275251876Speter else if (APR_XML_NS_IS_RESERVED(elem->name)) { 276251876Speter elem->ns = APR_XML_NS_NONE; 277251876Speter } 278251876Speter else { 279251876Speter *colon = '\0'; 280251876Speter elem->ns = find_prefix(parser, elem->name); 281251876Speter elem->name = colon + 1; 282251876Speter 283251876Speter if (APR_XML_NS_IS_ERROR(elem->ns)) { 284251876Speter parser->error = elem->ns; 285251876Speter return; 286251876Speter } 287251876Speter } 288251876Speter 289251876Speter /* adjust all remaining attributes' namespaces */ 290251876Speter for (attr = elem->attr; attr; attr = attr->next) { 291251876Speter /* 292251876Speter * apr_xml_attr defines this as "const" but we dup'd it, so we 293251876Speter * know that we can change it. a bit hacky, but the existing 294251876Speter * structure def is best. 295251876Speter */ 296251876Speter char *attr_name = (char *)attr->name; 297251876Speter 298251876Speter colon = strchr(attr_name, 0x3A); 299251876Speter if (colon == NULL) { 300251876Speter /* 301251876Speter * Attributes do NOT use the default namespace. Therefore, 302251876Speter * we place them into the "no namespace" category. 303251876Speter */ 304251876Speter attr->ns = APR_XML_NS_NONE; 305251876Speter } 306251876Speter else if (APR_XML_NS_IS_RESERVED(attr->name)) { 307251876Speter attr->ns = APR_XML_NS_NONE; 308251876Speter } 309251876Speter else { 310251876Speter *colon = '\0'; 311251876Speter attr->ns = find_prefix(parser, attr->name); 312251876Speter attr->name = colon + 1; 313251876Speter 314251876Speter if (APR_XML_NS_IS_ERROR(attr->ns)) { 315251876Speter parser->error = attr->ns; 316251876Speter return; 317251876Speter } 318251876Speter } 319251876Speter } 320251876Speter} 321251876Speter 322251876Speterstatic void end_handler(void *userdata, const char *name) 323251876Speter{ 324251876Speter apr_xml_parser *parser = userdata; 325251876Speter 326251876Speter /* punt once we find an error */ 327251876Speter if (parser->error) 328251876Speter return; 329251876Speter 330251876Speter /* pop up one level */ 331251876Speter parser->cur_elem = parser->cur_elem->parent; 332251876Speter} 333251876Speter 334251876Speterstatic void cdata_handler(void *userdata, const char *data, int len) 335251876Speter{ 336251876Speter apr_xml_parser *parser = userdata; 337251876Speter apr_xml_elem *elem; 338251876Speter apr_text_header *hdr; 339251876Speter const char *s; 340251876Speter 341251876Speter /* punt once we find an error */ 342251876Speter if (parser->error) 343251876Speter return; 344251876Speter 345251876Speter elem = parser->cur_elem; 346251876Speter s = apr_pstrndup(parser->p, data, len); 347251876Speter 348251876Speter if (elem->last_child == NULL) { 349251876Speter /* no children yet. this cdata follows the start tag */ 350251876Speter hdr = &elem->first_cdata; 351251876Speter } 352251876Speter else { 353251876Speter /* child elements exist. this cdata follows the last child. */ 354251876Speter hdr = &elem->last_child->following_cdata; 355251876Speter } 356251876Speter 357251876Speter apr_text_append(parser->p, hdr, s); 358251876Speter} 359251876Speter 360251876Speterstatic apr_status_t cleanup_parser(void *ctx) 361251876Speter{ 362251876Speter apr_xml_parser *parser = ctx; 363251876Speter 364251876Speter XML_ParserFree(parser->xp); 365251876Speter parser->xp = NULL; 366251876Speter 367251876Speter return APR_SUCCESS; 368251876Speter} 369251876Speter 370251876Speter#if XML_MAJOR_VERSION > 1 371251876Speter/* Stop the parser if an entity declaration is hit. */ 372251876Speterstatic void entity_declaration(void *userData, const XML_Char *entityName, 373251876Speter int is_parameter_entity, const XML_Char *value, 374251876Speter int value_length, const XML_Char *base, 375251876Speter const XML_Char *systemId, const XML_Char *publicId, 376251876Speter const XML_Char *notationName) 377251876Speter{ 378251876Speter apr_xml_parser *parser = userData; 379251876Speter 380251876Speter XML_StopParser(parser->xp, XML_FALSE); 381251876Speter} 382251876Speter#else 383251876Speter/* A noop default_handler. */ 384251876Speterstatic void default_handler(void *userData, const XML_Char *s, int len) 385251876Speter{ 386251876Speter} 387251876Speter#endif 388251876Speter 389251876SpeterAPU_DECLARE(apr_xml_parser *) apr_xml_parser_create(apr_pool_t *pool) 390251876Speter{ 391251876Speter apr_xml_parser *parser = apr_pcalloc(pool, sizeof(*parser)); 392251876Speter 393251876Speter parser->p = pool; 394251876Speter parser->doc = apr_pcalloc(pool, sizeof(*parser->doc)); 395251876Speter 396251876Speter parser->doc->namespaces = apr_array_make(pool, 5, sizeof(const char *)); 397251876Speter 398251876Speter /* ### is there a way to avoid hard-coding this? */ 399251876Speter apr_xml_insert_uri(parser->doc->namespaces, APR_KW_DAV); 400251876Speter 401251876Speter parser->xp = XML_ParserCreate(NULL); 402251876Speter if (parser->xp == NULL) { 403251876Speter (*apr_pool_abort_get(pool))(APR_ENOMEM); 404251876Speter return NULL; 405251876Speter } 406251876Speter 407251876Speter apr_pool_cleanup_register(pool, parser, cleanup_parser, 408251876Speter apr_pool_cleanup_null); 409251876Speter 410251876Speter XML_SetUserData(parser->xp, parser); 411251876Speter XML_SetElementHandler(parser->xp, start_handler, end_handler); 412251876Speter XML_SetCharacterDataHandler(parser->xp, cdata_handler); 413251876Speter 414251876Speter /* Prevent the "billion laughs" attack against expat by disabling 415251876Speter * internal entity expansion. With 2.x, forcibly stop the parser 416251876Speter * if an entity is declared - this is safer and a more obvious 417251876Speter * failure mode. With older versions, installing a noop 418251876Speter * DefaultHandler means that internal entities will be expanded as 419251876Speter * the empty string, which is also sufficient to prevent the 420251876Speter * attack. */ 421251876Speter#if XML_MAJOR_VERSION > 1 422251876Speter XML_SetEntityDeclHandler(parser->xp, entity_declaration); 423251876Speter#else 424251876Speter XML_SetDefaultHandler(parser->xp, default_handler); 425251876Speter#endif 426251876Speter 427251876Speter return parser; 428251876Speter} 429251876Speter 430251876Speterstatic apr_status_t do_parse(apr_xml_parser *parser, 431251876Speter const char *data, apr_size_t len, 432251876Speter int is_final) 433251876Speter{ 434251876Speter if (parser->xp == NULL) { 435251876Speter parser->error = APR_XML_ERROR_PARSE_DONE; 436251876Speter } 437251876Speter else { 438251876Speter int rv = XML_Parse(parser->xp, data, (int)len, is_final); 439251876Speter 440251876Speter if (rv == 0) { 441251876Speter parser->error = APR_XML_ERROR_EXPAT; 442251876Speter parser->xp_err = XML_GetErrorCode(parser->xp); 443251876Speter } 444251876Speter } 445251876Speter 446251876Speter /* ### better error code? */ 447251876Speter return parser->error ? APR_EGENERAL : APR_SUCCESS; 448251876Speter} 449251876Speter 450251876SpeterAPU_DECLARE(apr_status_t) apr_xml_parser_feed(apr_xml_parser *parser, 451251876Speter const char *data, 452251876Speter apr_size_t len) 453251876Speter{ 454251876Speter return do_parse(parser, data, len, 0 /* is_final */); 455251876Speter} 456251876Speter 457251876SpeterAPU_DECLARE(apr_status_t) apr_xml_parser_done(apr_xml_parser *parser, 458251876Speter apr_xml_doc **pdoc) 459251876Speter{ 460251876Speter char end; 461251876Speter apr_status_t status = do_parse(parser, &end, 0, 1 /* is_final */); 462251876Speter 463251876Speter /* get rid of the parser */ 464251876Speter (void) apr_pool_cleanup_run(parser->p, parser, cleanup_parser); 465251876Speter 466251876Speter if (status) 467251876Speter return status; 468251876Speter 469251876Speter if (pdoc != NULL) 470251876Speter *pdoc = parser->doc; 471251876Speter return APR_SUCCESS; 472251876Speter} 473251876Speter 474251876SpeterAPU_DECLARE(char *) apr_xml_parser_geterror(apr_xml_parser *parser, 475251876Speter char *errbuf, 476251876Speter apr_size_t errbufsize) 477251876Speter{ 478251876Speter int error = parser->error; 479251876Speter const char *msg; 480251876Speter 481251876Speter /* clear our record of an error */ 482251876Speter parser->error = 0; 483251876Speter 484251876Speter switch (error) { 485251876Speter case 0: 486251876Speter msg = "No error."; 487251876Speter break; 488251876Speter 489251876Speter case APR_XML_NS_ERROR_UNKNOWN_PREFIX: 490251876Speter msg = "An undefined namespace prefix was used."; 491251876Speter break; 492251876Speter 493251876Speter case APR_XML_NS_ERROR_INVALID_DECL: 494251876Speter msg = "A namespace prefix was defined with an empty URI."; 495251876Speter break; 496251876Speter 497251876Speter case APR_XML_ERROR_EXPAT: 498251876Speter (void) apr_snprintf(errbuf, errbufsize, 499251876Speter "XML parser error code: %s (%d)", 500251876Speter XML_ErrorString(parser->xp_err), parser->xp_err); 501251876Speter return errbuf; 502251876Speter 503251876Speter case APR_XML_ERROR_PARSE_DONE: 504251876Speter msg = "The parser is not active."; 505251876Speter break; 506251876Speter 507251876Speter default: 508251876Speter msg = "There was an unknown error within the XML body."; 509251876Speter break; 510251876Speter } 511251876Speter 512251876Speter (void) apr_cpystrn(errbuf, msg, errbufsize); 513251876Speter return errbuf; 514251876Speter} 515251876Speter 516251876SpeterAPU_DECLARE(apr_status_t) apr_xml_parse_file(apr_pool_t *p, 517251876Speter apr_xml_parser **parser, 518251876Speter apr_xml_doc **ppdoc, 519251876Speter apr_file_t *xmlfd, 520251876Speter apr_size_t buffer_length) 521251876Speter{ 522251876Speter apr_status_t rv; 523251876Speter char *buffer; 524251876Speter apr_size_t length; 525251876Speter 526251876Speter *parser = apr_xml_parser_create(p); 527251876Speter if (*parser == NULL) { 528251876Speter /* FIXME: returning an error code would be nice, 529251876Speter * but we dont get one ;( */ 530251876Speter return APR_EGENERAL; 531251876Speter } 532251876Speter buffer = apr_palloc(p, buffer_length); 533251876Speter length = buffer_length; 534251876Speter 535251876Speter rv = apr_file_read(xmlfd, buffer, &length); 536251876Speter 537251876Speter while (rv == APR_SUCCESS) { 538251876Speter rv = apr_xml_parser_feed(*parser, buffer, length); 539251876Speter if (rv != APR_SUCCESS) { 540251876Speter return rv; 541251876Speter } 542251876Speter 543251876Speter length = buffer_length; 544251876Speter rv = apr_file_read(xmlfd, buffer, &length); 545251876Speter } 546251876Speter if (rv != APR_EOF) { 547251876Speter return rv; 548251876Speter } 549251876Speter rv = apr_xml_parser_done(*parser, ppdoc); 550251876Speter *parser = NULL; 551251876Speter return rv; 552251876Speter} 553251876Speter 554251876SpeterAPU_DECLARE(void) apr_text_append(apr_pool_t * p, apr_text_header *hdr, 555251876Speter const char *text) 556251876Speter{ 557251876Speter apr_text *t = apr_palloc(p, sizeof(*t)); 558251876Speter 559251876Speter t->text = text; 560251876Speter t->next = NULL; 561251876Speter 562251876Speter if (hdr->first == NULL) { 563251876Speter /* no text elements yet */ 564251876Speter hdr->first = hdr->last = t; 565251876Speter } 566251876Speter else { 567251876Speter /* append to the last text element */ 568251876Speter hdr->last->next = t; 569251876Speter hdr->last = t; 570251876Speter } 571251876Speter} 572251876Speter 573251876Speter 574251876Speter/* --------------------------------------------------------------- 575251876Speter** 576251876Speter** XML UTILITY FUNCTIONS 577251876Speter*/ 578251876Speter 579251876Speter/* 580251876Speter** apr_xml_quote_string: quote an XML string 581251876Speter** 582251876Speter** Replace '<', '>', and '&' with '<', '>', and '&'. 583251876Speter** If quotes is true, then replace '"' with '"'. 584251876Speter** 585251876Speter** quotes is typically set to true for XML strings that will occur within 586251876Speter** double quotes -- attribute values. 587251876Speter*/ 588251876SpeterAPU_DECLARE(const char *) apr_xml_quote_string(apr_pool_t *p, const char *s, 589251876Speter int quotes) 590251876Speter{ 591251876Speter const char *scan; 592251876Speter apr_size_t len = 0; 593251876Speter apr_size_t extra = 0; 594251876Speter char *qstr; 595251876Speter char *qscan; 596251876Speter char c; 597251876Speter 598251876Speter for (scan = s; (c = *scan) != '\0'; ++scan, ++len) { 599251876Speter if (c == '<' || c == '>') 600251876Speter extra += 3; /* < or > */ 601251876Speter else if (c == '&') 602251876Speter extra += 4; /* & */ 603251876Speter else if (quotes && c == '"') 604251876Speter extra += 5; /* " */ 605251876Speter } 606251876Speter 607251876Speter /* nothing to do? */ 608251876Speter if (extra == 0) 609251876Speter return s; 610251876Speter 611251876Speter qstr = apr_palloc(p, len + extra + 1); 612251876Speter for (scan = s, qscan = qstr; (c = *scan) != '\0'; ++scan) { 613251876Speter if (c == '<') { 614251876Speter *qscan++ = '&'; 615251876Speter *qscan++ = 'l'; 616251876Speter *qscan++ = 't'; 617251876Speter *qscan++ = ';'; 618251876Speter } 619251876Speter else if (c == '>') { 620251876Speter *qscan++ = '&'; 621251876Speter *qscan++ = 'g'; 622251876Speter *qscan++ = 't'; 623251876Speter *qscan++ = ';'; 624251876Speter } 625251876Speter else if (c == '&') { 626251876Speter *qscan++ = '&'; 627251876Speter *qscan++ = 'a'; 628251876Speter *qscan++ = 'm'; 629251876Speter *qscan++ = 'p'; 630251876Speter *qscan++ = ';'; 631251876Speter } 632251876Speter else if (quotes && c == '"') { 633251876Speter *qscan++ = '&'; 634251876Speter *qscan++ = 'q'; 635251876Speter *qscan++ = 'u'; 636251876Speter *qscan++ = 'o'; 637251876Speter *qscan++ = 't'; 638251876Speter *qscan++ = ';'; 639251876Speter } 640251876Speter else { 641251876Speter *qscan++ = c; 642251876Speter } 643251876Speter } 644251876Speter 645251876Speter *qscan = '\0'; 646251876Speter return qstr; 647251876Speter} 648251876Speter 649251876Speter/* how many characters for the given integer? */ 650251876Speter#define APR_XML_NS_LEN(ns) ((ns) < 10 ? 1 : (ns) < 100 ? 2 : (ns) < 1000 ? 3 : \ 651251876Speter (ns) < 10000 ? 4 : (ns) < 100000 ? 5 : \ 652251876Speter (ns) < 1000000 ? 6 : (ns) < 10000000 ? 7 : \ 653251876Speter (ns) < 100000000 ? 8 : (ns) < 1000000000 ? 9 : 10) 654251876Speter 655251876Speterstatic apr_size_t text_size(const apr_text *t) 656251876Speter{ 657251876Speter apr_size_t size = 0; 658251876Speter 659251876Speter for (; t; t = t->next) 660251876Speter size += strlen(t->text); 661251876Speter return size; 662251876Speter} 663251876Speter 664251876Speterstatic apr_size_t elem_size(const apr_xml_elem *elem, int style, 665251876Speter apr_array_header_t *namespaces, int *ns_map) 666251876Speter{ 667251876Speter apr_size_t size; 668251876Speter 669362181Sdim if (style == APR_XML_X2T_FULL || style == APR_XML_X2T_FULL_NS_LANG || 670362181Sdim style == APR_XML_X2T_PARSED) { 671251876Speter const apr_xml_attr *attr; 672251876Speter 673251876Speter size = 0; 674251876Speter 675251876Speter if (style == APR_XML_X2T_FULL_NS_LANG) { 676251876Speter int i; 677251876Speter 678251876Speter /* 679251876Speter ** The outer element will contain xmlns:ns%d="%s" attributes 680251876Speter ** and an xml:lang attribute, if applicable. 681251876Speter */ 682251876Speter 683251876Speter for (i = namespaces->nelts; i--;) { 684251876Speter /* compute size of: ' xmlns:ns%d="%s"' */ 685251876Speter size += (9 + APR_XML_NS_LEN(i) + 2 + 686251876Speter strlen(APR_XML_GET_URI_ITEM(namespaces, i)) + 1); 687251876Speter } 688251876Speter 689251876Speter if (elem->lang != NULL) { 690251876Speter /* compute size of: ' xml:lang="%s"' */ 691251876Speter size += 11 + strlen(elem->lang) + 1; 692251876Speter } 693251876Speter } 694362181Sdim else if (style == APR_XML_X2T_PARSED) { 695362181Sdim apr_xml_ns_scope *ns_scope = elem->ns_scope; 696251876Speter 697362181Sdim /* compute size of: ' xmlns:%s="%s"' */ 698362181Sdim for (; ns_scope; ns_scope = ns_scope->next) { 699362181Sdim size += 10 + strlen(find_prefix_name(elem, ns_scope->ns, 0)) + 700362181Sdim strlen(APR_XML_GET_URI_ITEM(namespaces, ns_scope->ns)); 701362181Sdim } 702362181Sdim 703362181Sdim if (elem->lang != NULL) { 704362181Sdim /* compute size of: ' xml:lang="%s"' */ 705362181Sdim size += 11 + strlen(elem->lang) + 1; 706362181Sdim } 707362181Sdim } 708362181Sdim 709251876Speter if (elem->ns == APR_XML_NS_NONE) { 710251876Speter /* compute size of: <%s> */ 711251876Speter size += 1 + strlen(elem->name) + 1; 712251876Speter } 713362181Sdim else if (style == APR_XML_X2T_PARSED) { 714362181Sdim /* compute size of: <%s:%s> */ 715362181Sdim size += 3 + strlen(find_prefix_name(elem, elem->ns, 1)) + strlen(elem->name); 716362181Sdim } 717251876Speter else { 718251876Speter int ns = ns_map ? ns_map[elem->ns] : elem->ns; 719251876Speter 720251876Speter /* compute size of: <ns%d:%s> */ 721251876Speter size += 3 + APR_XML_NS_LEN(ns) + 1 + strlen(elem->name) + 1; 722251876Speter } 723251876Speter 724251876Speter if (APR_XML_ELEM_IS_EMPTY(elem)) { 725251876Speter /* insert a closing "/" */ 726251876Speter size += 1; 727251876Speter } 728251876Speter else { 729251876Speter /* 730251876Speter * two of above plus "/": 731251876Speter * <ns%d:%s> ... </ns%d:%s> 732251876Speter * OR <%s> ... </%s> 733251876Speter */ 734251876Speter size = 2 * size + 1; 735251876Speter } 736251876Speter 737251876Speter for (attr = elem->attr; attr; attr = attr->next) { 738251876Speter if (attr->ns == APR_XML_NS_NONE) { 739251876Speter /* compute size of: ' %s="%s"' */ 740251876Speter size += 1 + strlen(attr->name) + 2 + strlen(attr->value) + 1; 741251876Speter } 742362181Sdim else if (style == APR_XML_X2T_PARSED) { 743362181Sdim /* compute size of: ' %s:%s="%s"' */ 744362181Sdim size += 5 + strlen(find_prefix_name(elem, attr->ns, 1)) + strlen(attr->name) + strlen(attr->value); 745362181Sdim } 746251876Speter else { 747251876Speter /* compute size of: ' ns%d:%s="%s"' */ 748251876Speter int ns = ns_map ? ns_map[attr->ns] : attr->ns; 749251876Speter size += 3 + APR_XML_NS_LEN(ns) + 1 + strlen(attr->name) + 2 + strlen(attr->value) + 1; 750251876Speter } 751251876Speter } 752251876Speter 753251876Speter /* 754251876Speter ** If the element has an xml:lang value that is *different* from 755251876Speter ** its parent, then add the thing in: ' xml:lang="%s"'. 756251876Speter ** 757251876Speter ** NOTE: we take advantage of the pointer equality established by 758251876Speter ** the parsing for "inheriting" the xml:lang values from parents. 759251876Speter */ 760251876Speter if (elem->lang != NULL && 761251876Speter (elem->parent == NULL || elem->lang != elem->parent->lang)) { 762251876Speter size += 11 + strlen(elem->lang) + 1; 763251876Speter } 764251876Speter } 765251876Speter else if (style == APR_XML_X2T_LANG_INNER) { 766251876Speter /* 767251876Speter * This style prepends the xml:lang value plus a null terminator. 768251876Speter * If a lang value is not present, then we insert a null term. 769251876Speter */ 770251876Speter size = elem->lang ? strlen(elem->lang) + 1 : 1; 771251876Speter } 772251876Speter else 773251876Speter size = 0; 774251876Speter 775251876Speter size += text_size(elem->first_cdata.first); 776251876Speter 777251876Speter for (elem = elem->first_child; elem; elem = elem->next) { 778251876Speter /* the size of the child element plus the CDATA that follows it */ 779362181Sdim size += (elem_size(elem, style == APR_XML_X2T_PARSED ? APR_XML_X2T_PARSED : APR_XML_X2T_FULL, NULL, ns_map) + 780251876Speter text_size(elem->following_cdata.first)); 781251876Speter } 782251876Speter 783251876Speter return size; 784251876Speter} 785251876Speter 786251876Speterstatic char *write_text(char *s, const apr_text *t) 787251876Speter{ 788251876Speter for (; t; t = t->next) { 789251876Speter apr_size_t len = strlen(t->text); 790251876Speter memcpy(s, t->text, len); 791251876Speter s += len; 792251876Speter } 793251876Speter return s; 794251876Speter} 795251876Speter 796251876Speterstatic char *write_elem(char *s, const apr_xml_elem *elem, int style, 797251876Speter apr_array_header_t *namespaces, int *ns_map) 798251876Speter{ 799251876Speter const apr_xml_elem *child; 800251876Speter apr_size_t len; 801251876Speter int ns; 802251876Speter 803362181Sdim if (style == APR_XML_X2T_FULL || style == APR_XML_X2T_FULL_NS_LANG || 804362181Sdim style == APR_XML_X2T_PARSED) { 805251876Speter int empty = APR_XML_ELEM_IS_EMPTY(elem); 806251876Speter const apr_xml_attr *attr; 807251876Speter 808362181Sdim if (elem->ns == APR_XML_NS_NONE) 809251876Speter len = sprintf(s, "<%s", elem->name); 810362181Sdim else if (style == APR_XML_X2T_PARSED) 811362181Sdim len = sprintf(s, "<%s:%s", find_prefix_name(elem, elem->ns, 1), elem->name); 812251876Speter else { 813251876Speter ns = ns_map ? ns_map[elem->ns] : elem->ns; 814251876Speter len = sprintf(s, "<ns%d:%s", ns, elem->name); 815251876Speter } 816251876Speter s += len; 817251876Speter 818251876Speter for (attr = elem->attr; attr; attr = attr->next) { 819251876Speter if (attr->ns == APR_XML_NS_NONE) 820251876Speter len = sprintf(s, " %s=\"%s\"", attr->name, attr->value); 821362181Sdim else if (style == APR_XML_X2T_PARSED) 822362181Sdim len = sprintf(s, " %s:%s=\"%s\"", 823362181Sdim find_prefix_name(elem, attr->ns, 1), attr->name, attr->value); 824362181Sdim else { 825362181Sdim ns = ns_map ? ns_map[attr->ns] : attr->ns; 826362181Sdim len = sprintf(s, " ns%d:%s=\"%s\"", ns, attr->name, attr->value); 827362181Sdim } 828251876Speter s += len; 829251876Speter } 830251876Speter 831251876Speter /* add the xml:lang value if necessary */ 832251876Speter if (elem->lang != NULL && 833251876Speter (style == APR_XML_X2T_FULL_NS_LANG || 834251876Speter elem->parent == NULL || 835251876Speter elem->lang != elem->parent->lang)) { 836251876Speter len = sprintf(s, " xml:lang=\"%s\"", elem->lang); 837251876Speter s += len; 838251876Speter } 839251876Speter 840251876Speter /* add namespace definitions, if required */ 841251876Speter if (style == APR_XML_X2T_FULL_NS_LANG) { 842251876Speter int i; 843251876Speter 844251876Speter for (i = namespaces->nelts; i--;) { 845251876Speter len = sprintf(s, " xmlns:ns%d=\"%s\"", i, 846251876Speter APR_XML_GET_URI_ITEM(namespaces, i)); 847251876Speter s += len; 848251876Speter } 849251876Speter } 850362181Sdim else if (style == APR_XML_X2T_PARSED) { 851362181Sdim apr_xml_ns_scope *ns_scope = elem->ns_scope; 852251876Speter 853362181Sdim for (; ns_scope; ns_scope = ns_scope->next) { 854362181Sdim const char *prefix = find_prefix_name(elem, ns_scope->ns, 0); 855362181Sdim 856362181Sdim len = sprintf(s, " xmlns%s%s=\"%s\"", 857362181Sdim *prefix ? ":" : "", *prefix ? prefix : "", 858362181Sdim APR_XML_GET_URI_ITEM(namespaces, ns_scope->ns)); 859362181Sdim s += len; 860362181Sdim } 861362181Sdim } 862362181Sdim 863251876Speter /* no more to do. close it up and go. */ 864251876Speter if (empty) { 865251876Speter *s++ = '/'; 866251876Speter *s++ = '>'; 867251876Speter return s; 868251876Speter } 869251876Speter 870251876Speter /* just close it */ 871251876Speter *s++ = '>'; 872251876Speter } 873251876Speter else if (style == APR_XML_X2T_LANG_INNER) { 874251876Speter /* prepend the xml:lang value */ 875251876Speter if (elem->lang != NULL) { 876251876Speter len = strlen(elem->lang); 877251876Speter memcpy(s, elem->lang, len); 878251876Speter s += len; 879251876Speter } 880251876Speter *s++ = '\0'; 881251876Speter } 882251876Speter 883251876Speter s = write_text(s, elem->first_cdata.first); 884251876Speter 885251876Speter for (child = elem->first_child; child; child = child->next) { 886362181Sdim s = write_elem(s, child, 887362181Sdim style == APR_XML_X2T_PARSED ? APR_XML_X2T_PARSED : APR_XML_X2T_FULL, 888362181Sdim NULL, ns_map); 889251876Speter s = write_text(s, child->following_cdata.first); 890251876Speter } 891251876Speter 892362181Sdim if (style == APR_XML_X2T_FULL || style == APR_XML_X2T_FULL_NS_LANG || style == APR_XML_X2T_PARSED) { 893362181Sdim if (elem->ns == APR_XML_NS_NONE) 894251876Speter len = sprintf(s, "</%s>", elem->name); 895362181Sdim else if (style == APR_XML_X2T_PARSED) 896362181Sdim len = sprintf(s, "</%s:%s>", find_prefix_name(elem, elem->ns, 1), elem->name); 897251876Speter else { 898251876Speter ns = ns_map ? ns_map[elem->ns] : elem->ns; 899251876Speter len = sprintf(s, "</ns%d:%s>", ns, elem->name); 900251876Speter } 901251876Speter s += len; 902251876Speter } 903251876Speter 904251876Speter return s; 905251876Speter} 906251876Speter 907251876SpeterAPU_DECLARE(void) apr_xml_quote_elem(apr_pool_t *p, apr_xml_elem *elem) 908251876Speter{ 909251876Speter apr_text *scan_txt; 910251876Speter apr_xml_attr *scan_attr; 911251876Speter apr_xml_elem *scan_elem; 912251876Speter 913251876Speter /* convert the element's text */ 914251876Speter for (scan_txt = elem->first_cdata.first; 915251876Speter scan_txt != NULL; 916251876Speter scan_txt = scan_txt->next) { 917251876Speter scan_txt->text = apr_xml_quote_string(p, scan_txt->text, 0); 918251876Speter } 919251876Speter for (scan_txt = elem->following_cdata.first; 920251876Speter scan_txt != NULL; 921251876Speter scan_txt = scan_txt->next) { 922251876Speter scan_txt->text = apr_xml_quote_string(p, scan_txt->text, 0); 923251876Speter } 924251876Speter 925251876Speter /* convert the attribute values */ 926251876Speter for (scan_attr = elem->attr; 927251876Speter scan_attr != NULL; 928251876Speter scan_attr = scan_attr->next) { 929251876Speter scan_attr->value = apr_xml_quote_string(p, scan_attr->value, 1); 930251876Speter } 931251876Speter 932251876Speter /* convert the child elements */ 933251876Speter for (scan_elem = elem->first_child; 934251876Speter scan_elem != NULL; 935251876Speter scan_elem = scan_elem->next) { 936251876Speter apr_xml_quote_elem(p, scan_elem); 937251876Speter } 938251876Speter} 939251876Speter 940251876Speter/* convert an element to a text string */ 941251876SpeterAPU_DECLARE(void) apr_xml_to_text(apr_pool_t * p, const apr_xml_elem *elem, 942251876Speter int style, apr_array_header_t *namespaces, 943251876Speter int *ns_map, const char **pbuf, 944251876Speter apr_size_t *psize) 945251876Speter{ 946251876Speter /* get the exact size, plus a null terminator */ 947251876Speter apr_size_t size = elem_size(elem, style, namespaces, ns_map) + 1; 948251876Speter char *s = apr_palloc(p, size); 949251876Speter 950251876Speter (void) write_elem(s, elem, style, namespaces, ns_map); 951251876Speter s[size - 1] = '\0'; 952251876Speter 953251876Speter *pbuf = s; 954251876Speter if (psize) 955251876Speter *psize = size; 956251876Speter} 957251876Speter 958251876SpeterAPU_DECLARE(const char *) apr_xml_empty_elem(apr_pool_t * p, 959251876Speter const apr_xml_elem *elem) 960251876Speter{ 961251876Speter if (elem->ns == APR_XML_NS_NONE) { 962251876Speter /* 963251876Speter * The prefix (xml...) is already within the prop name, or 964251876Speter * the element simply has no prefix. 965251876Speter */ 966251876Speter return apr_psprintf(p, "<%s/>" DEBUG_CR, elem->name); 967251876Speter } 968251876Speter 969251876Speter return apr_psprintf(p, "<ns%d:%s/>" DEBUG_CR, elem->ns, elem->name); 970251876Speter} 971251876Speter 972251876Speter/* return the URI's (existing) index, or insert it and return a new index */ 973251876SpeterAPU_DECLARE(int) apr_xml_insert_uri(apr_array_header_t *uri_array, 974251876Speter const char *uri) 975251876Speter{ 976251876Speter int i; 977251876Speter const char **pelt; 978251876Speter 979251876Speter /* never insert an empty URI; this index is always APR_XML_NS_NONE */ 980251876Speter if (*uri == '\0') 981251876Speter return APR_XML_NS_NONE; 982251876Speter 983251876Speter for (i = uri_array->nelts; i--;) { 984251876Speter if (strcmp(uri, APR_XML_GET_URI_ITEM(uri_array, i)) == 0) 985251876Speter return i; 986251876Speter } 987251876Speter 988251876Speter pelt = apr_array_push(uri_array); 989251876Speter *pelt = uri; /* assume uri is const or in a pool */ 990251876Speter return uri_array->nelts - 1; 991251876Speter} 992251876Speter 993251876Speter/* convert the element to EBCDIC */ 994251876Speter#if APR_CHARSET_EBCDIC 995251876Speterstatic apr_status_t apr_xml_parser_convert_elem(apr_xml_elem *e, 996251876Speter apr_xlate_t *convset) 997251876Speter{ 998251876Speter apr_xml_attr *a; 999251876Speter apr_xml_elem *ec; 1000251876Speter apr_text *t; 1001251876Speter apr_size_t inbytes_left, outbytes_left; 1002251876Speter apr_status_t status; 1003251876Speter 1004251876Speter inbytes_left = outbytes_left = strlen(e->name); 1005251876Speter status = apr_xlate_conv_buffer(convset, e->name, &inbytes_left, (char *) e->name, &outbytes_left); 1006251876Speter if (status) { 1007251876Speter return status; 1008251876Speter } 1009251876Speter 1010251876Speter for (t = e->first_cdata.first; t != NULL; t = t->next) { 1011251876Speter inbytes_left = outbytes_left = strlen(t->text); 1012251876Speter status = apr_xlate_conv_buffer(convset, t->text, &inbytes_left, (char *) t->text, &outbytes_left); 1013251876Speter if (status) { 1014251876Speter return status; 1015251876Speter } 1016251876Speter } 1017251876Speter 1018251876Speter for (t = e->following_cdata.first; t != NULL; t = t->next) { 1019251876Speter inbytes_left = outbytes_left = strlen(t->text); 1020251876Speter status = apr_xlate_conv_buffer(convset, t->text, &inbytes_left, (char *) t->text, &outbytes_left); 1021251876Speter if (status) { 1022251876Speter return status; 1023251876Speter } 1024251876Speter } 1025251876Speter 1026251876Speter for (a = e->attr; a != NULL; a = a->next) { 1027251876Speter inbytes_left = outbytes_left = strlen(a->name); 1028251876Speter status = apr_xlate_conv_buffer(convset, a->name, &inbytes_left, (char *) a->name, &outbytes_left); 1029251876Speter if (status) { 1030251876Speter return status; 1031251876Speter } 1032251876Speter inbytes_left = outbytes_left = strlen(a->value); 1033251876Speter status = apr_xlate_conv_buffer(convset, a->value, &inbytes_left, (char *) a->value, &outbytes_left); 1034251876Speter if (status) { 1035251876Speter return status; 1036251876Speter } 1037251876Speter } 1038251876Speter 1039251876Speter for (ec = e->first_child; ec != NULL; ec = ec->next) { 1040251876Speter status = apr_xml_parser_convert_elem(ec, convset); 1041251876Speter if (status) { 1042251876Speter return status; 1043251876Speter } 1044251876Speter } 1045251876Speter return APR_SUCCESS; 1046251876Speter} 1047251876Speter 1048251876Speter/* convert the whole document to EBCDIC */ 1049251876SpeterAPU_DECLARE(apr_status_t) apr_xml_parser_convert_doc(apr_pool_t *pool, 1050251876Speter apr_xml_doc *pdoc, 1051251876Speter apr_xlate_t *convset) 1052251876Speter{ 1053251876Speter apr_status_t status; 1054251876Speter /* Don't convert the namespaces: they are constant! */ 1055251876Speter if (pdoc->namespaces != NULL) { 1056251876Speter int i; 1057251876Speter apr_array_header_t *namespaces; 1058251876Speter namespaces = apr_array_make(pool, pdoc->namespaces->nelts, sizeof(const char *)); 1059251876Speter if (namespaces == NULL) 1060251876Speter return APR_ENOMEM; 1061251876Speter for (i = 0; i < pdoc->namespaces->nelts; i++) { 1062251876Speter apr_size_t inbytes_left, outbytes_left; 1063251876Speter char *ptr = (char *) APR_XML_GET_URI_ITEM(pdoc->namespaces, i); 1064251876Speter ptr = apr_pstrdup(pool, ptr); 1065251876Speter if ( ptr == NULL) 1066251876Speter return APR_ENOMEM; 1067251876Speter inbytes_left = outbytes_left = strlen(ptr); 1068251876Speter status = apr_xlate_conv_buffer(convset, ptr, &inbytes_left, ptr, &outbytes_left); 1069251876Speter if (status) { 1070251876Speter return status; 1071251876Speter } 1072251876Speter apr_xml_insert_uri(namespaces, ptr); 1073251876Speter } 1074251876Speter pdoc->namespaces = namespaces; 1075251876Speter } 1076251876Speter return apr_xml_parser_convert_elem(pdoc->root, convset); 1077251876Speter} 1078251876Speter#endif 1079