From 74e4e5ee3fdefec5ce57ae91a069debb5e107290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Lehrke?= Date: Sat, 15 Oct 2011 22:46:37 +0000 Subject: [PATCH] * Fix WBXML namespache issue (bug 3048) --- phpgwapi/inc/horde/Horde/SyncML/State.php | 12 +- .../inc/horde/XML/WBXML/ContentHandler.php | 167 +++++ phpgwapi/inc/horde/XML/WBXML/Decoder.php | 688 ++++++++++++++++++ 3 files changed, 861 insertions(+), 6 deletions(-) create mode 100644 phpgwapi/inc/horde/XML/WBXML/ContentHandler.php create mode 100644 phpgwapi/inc/horde/XML/WBXML/Decoder.php diff --git a/phpgwapi/inc/horde/Horde/SyncML/State.php b/phpgwapi/inc/horde/Horde/SyncML/State.php index 14a1cbe8a2..e065bf36ad 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/State.php +++ b/phpgwapi/inc/horde/Horde/SyncML/State.php @@ -138,12 +138,12 @@ define('RESPONSE_ATOMIC_RESPONSE_TOO_LARGE', 517); define('NAME_SPACE_URI_SYNCML_1_0', 'syncml:syncml1.0'); define('NAME_SPACE_URI_SYNCML_1_1', 'syncml:syncml1.1'); define('NAME_SPACE_URI_SYNCML_1_2', 'syncml:syncml1.2'); -define('NAME_SPACE_URI_METINF_1_0', 'syncml:metinf'); -define('NAME_SPACE_URI_METINF_1_1', 'syncml:metinf'); -define('NAME_SPACE_URI_METINF_1_2', 'syncml:metinf'); -define('NAME_SPACE_URI_DEVINF_1_0', 'syncml:devinf'); -define('NAME_SPACE_URI_DEVINF_1_1', 'syncml:devinf'); -define('NAME_SPACE_URI_DEVINF_1_2', 'syncml:devinf'); +define('NAME_SPACE_URI_METINF_1_0', 'syncml:metinf1.0'); +define('NAME_SPACE_URI_METINF_1_1', 'syncml:metinf1.1'); +define('NAME_SPACE_URI_METINF_1_2', 'syncml:metinf1.2'); +define('NAME_SPACE_URI_DEVINF_1_0', 'syncml:devinf1.0'); +define('NAME_SPACE_URI_DEVINF_1_1', 'syncml:devinf1.1'); +define('NAME_SPACE_URI_DEVINF_1_2', 'syncml:devinf1.2'); define('CLIENT_SYNC_STARTED', 1); define('CLIENT_SYNC_FINNISHED', 2); diff --git a/phpgwapi/inc/horde/XML/WBXML/ContentHandler.php b/phpgwapi/inc/horde/XML/WBXML/ContentHandler.php new file mode 100644 index 0000000000..0e1ff72b5f --- /dev/null +++ b/phpgwapi/inc/horde/XML/WBXML/ContentHandler.php @@ -0,0 +1,167 @@ + + * @package XML_WBXML + */ +class XML_WBXML_ContentHandler { + + var $_currentUri; + var $_output = ''; + + var $_opaqueHandler; + + /** + * Charset. + */ + var $_charset = 'UTF-8'; + + /** + * WBXML Version. + * 0, 1 or 2 supported + */ + var $_wbxmlVersion = 2; + + function XML_WBXML_ContentHandler() + { + $this->_currentUri = new XML_WBXML_LifoQueue(); + } + + /** + */ + function raiseError($error) + { + if (!class_exists('PEAR')) { + require 'PEAR.php'; + } + return PEAR::raiseError($error); + } + + function getCharsetStr() + { + return $this->_charset; + } + + function setCharset($cs) + { + $this->_charset = $cs; + } + + function getVersion() + { + return $this->_wbxmlVersion; + } + + function setVersion($v) + { + $this->_wbxmlVersion = $v; + } + + function getOutput() + { + return $this->_output; + } + + function getOutputSize() + { + return strlen($this->_output); + } + + function startElement($uri, $element, $attrs = array()) + { + $this->_output .= '<' . $element; + + $currentUri = $this->_currentUri->top(); + + if (((!$currentUri) || ($currentUri != $uri)) && $uri) { + $this->_output .= ' xmlns="' . $uri . '"'; + } + + $this->_currentUri->push($uri); + + foreach ($attrs as $attr) { + $this->_output .= ' ' . $attr['attribute'] . '="' . $attr['value'] . '"'; + } + + $this->_output .= '>'; + } + + function endElement($uri, $element) + { + $this->_output .= ''; + + $this->_currentUri->pop(); + } + + function characters($str) + { + $this->_output .= $str; + } + + function opaque($o) + { + $this->_output .= $o; + } + + function setOpaqueHandler($opaqueHandler) + { + $this->_opaqueHandler = $opaqueHandler; + } + + function removeOpaqueHandler() + { + unset($this->_opaqueHandler); + } + + function createSubHandler() + { + $name = get_class($this); // clone current class + $sh = new $name(); + $sh->setCharset($this->getCharsetStr()); + $sh->setVersion($this->getVersion()); + return $sh; + } + +} + +class XML_WBXML_LifoQueue { + + var $_queue = array(); + + function XML_WBXML_LifoQueue() + { + } + + function push($obj) + { + $this->_queue[] = $obj; + } + + function pop() + { + if (count($this->_queue)) { + return array_pop($this->_queue); + } else { + return null; + } + } + + function top() + { + if ($count = count($this->_queue)) { + return $this->_queue[$count - 1]; + } else { + return null; + } + } + +} diff --git a/phpgwapi/inc/horde/XML/WBXML/Decoder.php b/phpgwapi/inc/horde/XML/WBXML/Decoder.php new file mode 100644 index 0000000000..a51540eb4f --- /dev/null +++ b/phpgwapi/inc/horde/XML/WBXML/Decoder.php @@ -0,0 +1,688 @@ + + * @package XML_WBXML + */ +class XML_WBXML_Decoder extends XML_WBXML_ContentHandler { + + /** + * Document Public Identifier type + * 1 mb_u_int32 well known type + * 2 string table + * from spec but converted into a string. + * + * Document Public Identifier + * Used with dpiType. + */ + var $_dpi; + + /** + * String table as defined in 5.7 + */ + var $_stringTable = array(); + + /** + * Content handler. + * Currently just outputs raw XML. + */ + var $_ch; + + var $_tagDTD; + + var $_prevAttributeDTD; + + var $_attributeDTD; + + /** + * State variables. + */ + var $_tagStack = array(); + var $_isAttribute; + var $_isData = false; + + var $_error = false; + + /** + * The DTD Manager. + * + * @var XML_WBXML_DTDManager + */ + var $_dtdManager; + + /** + * The string position. + * + * @var integer + */ + var $_strpos; + + /** + * Constructor. + */ + function XML_WBXML_Decoder() + { + $this->_dtdManager = new XML_WBXML_DTDManager(); + } + + /** + * Sets the contentHandler that will receive the output of the + * decoding. + * + * @param XML_WBXML_ContentHandler $ch The contentHandler + */ + function setContentHandler(&$ch) + { + $this->_ch = &$ch; + } + /** + * Return one byte from the input stream. + * + * @param string $input The WBXML input string. + */ + function getByte($input) + { + $value = $input{$this->_strpos++}; + $value = ord($value); + + return $value; + } + + /** + * Takes a WBXML input document and returns decoded XML. + * However the preferred and more effecient method is to + * use decode() rather than decodeToString() and have an + * appropriate contentHandler deal with the decoded data. + * + * @param string $wbxml The WBXML document to decode. + * + * @return string The decoded XML document. + */ + function decodeToString($wbxml) + { + $this->_ch = new XML_WBXML_ContentHandler(); + + $r = $this->decode($wbxml); + if (is_a($r, 'PEAR_Error')) { + return $r; + } + return $this->_ch->getOutput(); + } + + /** + * Takes a WBXML input document and decodes it. + * Decoding result is directly passed to the contentHandler. + * A contenthandler must be set using setContentHandler + * prior to invocation of this method + * + * @param string $wbxml The WBXML document to decode. + * + * @return mixed True on success or PEAR_Error. + */ + function decode($wbxml) + { + $this->_error = false; // reset state + + $this->_strpos = 0; + + if (empty($this->_ch)) { + return $this->raiseError('No Contenthandler defined.'); + } + + // Get Version Number from Section 5.4 + // version = u_int8 + // currently 0, 1 or 2 + $this->_wbxmlVersion = $this->getVersionNumber($wbxml); + #Horde::logMessage("WBXML[" . $this->_strpos . "] version " . $this->_wbxmlVersion, __FILE__, __LINE__, PEAR_LOG_DEBUG); + + // Get Document Public Idetifier from Section 5.5 + // publicid = mb_u_int32 | (zero index) + // zero = u_int8 + // Containing the value zero (0) + // The actual DPI is determined after the String Table is read. + $dpiStruct = $this->getDocumentPublicIdentifier($wbxml); + + // Get Charset from 5.6 + // charset = mb_u_int32 + $this->_charset = $this->getCharset($wbxml); + #Horde::logMessage("WBXML[" . $this->_strpos . "] charset " . $this->_charset, __FILE__, __LINE__, PEAR_LOG_DEBUG); + + // Get String Table from 5.7 + // strb1 = length *byte + $this->retrieveStringTable($wbxml); + + // Get Document Public Idetifier from Section 5.5. + $this->_dpi = $this->getDocumentPublicIdentifierImpl($dpiStruct['dpiType'], + $dpiStruct['dpiNumber']); + #$this->_stringTable); + + // Now the real fun begins. + // From Sections 5.2 and 5.8 + + + // Default content handler. + $this->_dtdManager = new XML_WBXML_DTDManager(); + + // Get the starting DTD. + $this->_tagDTD = $this->_dtdManager->getInstance($this->_dpi); + + if (!$this->_tagDTD) { + return $this->raiseError('No DTD found for ' + . $this->_dpi . '/' + . $dpiStruct['dpiNumber']); + } + + $this->_attributeDTD = $this->_tagDTD; + + while (empty($this->_error) && $this->_strpos < strlen($wbxml)) { + $this->_decode($wbxml); + } + if (!empty($this->_error)) { + return $this->_error; + } + return true; + } + + function getVersionNumber($input) + { + return $this->getByte($input); + } + + function getDocumentPublicIdentifier($input) + { + $i = XML_WBXML::MBUInt32ToInt($input, $this->_strpos); + + if ($i == 0) { + return array('dpiType' => 2, + 'dpiNumber' => $this->getByte($input)); + } else { + return array('dpiType' => 1, + 'dpiNumber' => $i); + } + } + + function getDocumentPublicIdentifierImpl($dpiType, $dpiNumber) + { + if ($dpiType == 1) { + return XML_WBXML::getDPIString($dpiNumber); + } else { + #Horde::logMessage("WBXML string table $dpiNumber:\n" . print_r($this->_stringTable, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); + return $this->getStringTableEntry($dpiNumber); + } + } + + /** + * Returns the character encoding. Only default character + * encodings from J2SE are supported. From + * http://www.iana.org/assignments/character-sets and + * http://java.sun.com/j2se/1.4.2/docs/api/java/nio/charset/Charset.html + */ + function getCharset($input) + { + $cs = XML_WBXML::MBUInt32ToInt($input, $this->_strpos); + return XML_WBXML::getCharsetString($cs); + } + + /** + * Retrieves the string table. + * The string table consists of an mb_u_int32 length + * and then length bytes forming the table. + * References to the string table refer to the + * starting position of the (null terminated) + * string in this table. + */ + function retrieveStringTable($input) + { + $size = XML_WBXML::MBUInt32ToInt($input, $this->_strpos); + $this->_stringTable = $this->_substr($input, $this->_strpos, $size); + $this->_strpos += $size; + // print "stringtable($size):" . $this->_stringTable ."\n"; + } + + function getStringTableEntry($index) + { + if ($index >= strlen($this->_stringTable)) { + $this->_error = + $this->raiseError('Invalid offset ' . $index + . ' value encountered around position ' + . $this->_strpos + . '. Broken wbxml?'); + return ''; + } + + // copy of method termstr but without modification of this->_strpos + + $str = '#'; // must start with nonempty string to allow array access + + $i = 0; + $ch = $this->_stringTable[$index++]; + if (ord($ch) == 0) { + return ''; // don't return '#' + } + + while (ord($ch) != 0) { + $str[$i++] = $ch; + if ($index >= strlen($this->_stringTable)) { + break; + } + $ch = $this->_stringTable[$index++]; + } + // print "string table entry: $str\n"; + return $str; + + } + + function _decode($input) + { + $token = $this->getByte($input); + $str = ''; + + // print "position: " . $this->_strpos . " token: " . $token . " str10: " . substr($input, $this->_strpos, 10) . "\n"; // @todo: remove debug output + + switch ($token) { + case XML_WBXML_GLOBAL_TOKEN_STR_I: + // Section 5.8.4.1 + $str = $this->termstr($input); + $this->_ch->characters($str); + // print "str:$str\n"; // @TODO Remove debug code + break; + + case XML_WBXML_GLOBAL_TOKEN_STR_T: + // Section 5.8.4.1 + $x = XML_WBXML::MBUInt32ToInt($input, $this->_strpos); + $str = $this->getStringTableEntry($x); + $this->_ch->characters($str); + break; + + case XML_WBXML_GLOBAL_TOKEN_EXT_I_0: + case XML_WBXML_GLOBAL_TOKEN_EXT_I_1: + case XML_WBXML_GLOBAL_TOKEN_EXT_I_2: + // Section 5.8.4.2 + $str = $this->termstr($input); + $this->_ch->characters($str); + break; + + case XML_WBXML_GLOBAL_TOKEN_EXT_T_0: + case XML_WBXML_GLOBAL_TOKEN_EXT_T_1: + case XML_WBXML_GLOBAL_TOKEN_EXT_T_2: + // Section 5.8.4.2 + $str = $this->getStringTableEnty(XML_WBXML::MBUInt32ToInt($input, $this->_strpos)); + $this->_ch->characters($str); + break; + + case XML_WBXML_GLOBAL_TOKEN_EXT_0: + case XML_WBXML_GLOBAL_TOKEN_EXT_1: + case XML_WBXML_GLOBAL_TOKEN_EXT_2: + // Section 5.8.4.2 + $extension = $this->getByte($input); + $this->_ch->characters($extension); + break; + + case XML_WBXML_GLOBAL_TOKEN_ENTITY: + // Section 5.8.4.3 + // UCS-4 chracter encoding? + $entity = $this->entity(XML_WBXML::MBUInt32ToInt($input, $this->_strpos)); + + $this->_ch->characters('&#' . $entity . ';'); + break; + + case XML_WBXML_GLOBAL_TOKEN_PI: + // Section 5.8.4.4 + // throw new IOException + // die("WBXML global token processing instruction(PI, " + token + ") is unsupported!\n"); + break; + + case XML_WBXML_GLOBAL_TOKEN_LITERAL: + // Section 5.8.4.5 + $str = $this->getStringTableEntry(XML_WBXML::MBUInt32ToInt($input, $this->_strpos)); + $this->parseTag($input, $str, false, false); + break; + + case XML_WBXML_GLOBAL_TOKEN_LITERAL_A: + // Section 5.8.4.5 + $str = $this->getStringTableEntry(XML_WBXML::MBUInt32ToInt($input, $this->_strpos)); + $this->parseTag($input, $str, true, false); + break; + + case XML_WBXML_GLOBAL_TOKEN_LITERAL_AC: + // Section 5.8.4.5 + $str = $this->getStringTableEntry(XML_WBXML::MBUInt32ToInt($input, $this->_strpos)); + $this->parseTag($input, $string, true, true); + break; + + case XML_WBXML_GLOBAL_TOKEN_LITERAL_C: + // Section 5.8.4.5 + $str = $this->getStringTableEntry(XML_WBXML::MBUInt32ToInt($input, $this->_strpos)); + $this->parseTag($input, $str, false, true); + break; + + case XML_WBXML_GLOBAL_TOKEN_OPAQUE: + // Section 5.8.4.6 + $size = XML_WBXML::MBUInt32ToInt($input, $this->_strpos); + if ($size > 0) { + #Horde::logMessage("WBXML opaque document size=$size, next=" . ord($input{$this->_strpos}), __FILE__, __LINE__, PEAR_LOG_DEBUG); + $b = $this->_substr($input, $this->_strpos, $size); + // print "opaque of size $size: ($b)\n"; // @todo remove debug + $this->_strpos += $size; + // opaque data inside a element may or may not be + // a nested wbxml document (for example devinf data). + // We find out by checking the first byte of the data: if it's + // 1, 2 or 3 we expect it to be the version number of a wbxml + // document and thus start a new wbxml decoder instance on it. + + if ($this->_isData && ord($b) < 10) { + #Horde::logMessage("WBXML opaque document size=$size, \$b[0]=" . ord($b), __FILE__, __LINE__, PEAR_LOG_DEBUG); + $decoder = new XML_WBXML_Decoder(true); + $decoder->setContentHandler($this->_ch); + $s = $decoder->decode($b); + // /* // @todo: FIXME currently we can't decode Nokia + // DevInf data. So ignore error for the time beeing. + if (is_a($s, 'PEAR_Error')) { + $this->_error = $s; + return; + } + // */ + // $this->_ch->characters($s); + } else { + /* normal opaque behaviour: just copy the raw data: */ + // print "opaque handled as string=$b\n"; // @todo remove debug + $this->_ch->characters($b); + } + } + // old approach to deal with opaque data inside ContentHandler: + // FIXME Opaque is used by SYNCML. Opaque data that depends on the context + // if (contentHandler instanceof OpaqueContentHandler) { + // ((OpaqueContentHandler)contentHandler).opaque(b); + // } else { + // String str = new String(b, 0, size, charset); + // char[] chars = str.toCharArray(); + + // contentHandler.characters(chars, 0, chars.length); + // } + + break; + + case XML_WBXML_GLOBAL_TOKEN_END: + // Section 5.8.4.7.1 + $str = $this->endTag(); + break; + + case XML_WBXML_GLOBAL_TOKEN_SWITCH_PAGE: + // Section 5.8.4.7.2 + $codePage = $this->getByte($input); + // print "switch to codepage $codePage\n"; // @todo: remove debug code + $this->switchElementCodePage($codePage); + break; + + default: + // Section 5.8.2 + // Section 5.8.3 + $hasAttributes = (($token & 0x80) != 0); + $hasContent = (($token & 0x40) != 0); + $realToken = $token & 0x3F; + $str = $this->getTag($realToken); + + // print "element:$str\n"; // @TODO Remove debug code + $this->parseTag($input, $str, $hasAttributes, $hasContent); + + if ($realToken == 0x0f) { + // store if we're inside a Data tag. This may contain + // an additional enclosed wbxml document on which we have + // to run a seperate encoder + $this->_isData = true; + } else { + $this->_isData = false; + } + break; + } + } + + function parseTag($input, $tag, $hasAttributes, $hasContent) + { + $attrs = array(); + if ($hasAttributes) { + $attrs = $this->getAttributes($input); + } + + $this->_ch->startElement($this->getCurrentURI(), $tag, $attrs); + + if ($hasContent) { + // FIXME I forgot what does this does. Not sure if this is + // right? + $this->_tagStack[] = $tag; + } else { + $this->_ch->endElement($this->getCurrentURI(), $tag); + } + } + + function endTag() + { + if (count($this->_tagStack)) { + $tag = array_pop($this->_tagStack); + } else { + $tag = 'Unknown'; + } + + $this->_ch->endElement($this->getCurrentURI(), $tag); + + return $tag; + } + + function getAttributes($input) + { + $this->startGetAttributes(); + $hasMoreAttributes = true; + + $attrs = array(); + $attr = null; + $value = null; + $token = null; + + while ($hasMoreAttributes) { + $token = $this->getByte($input); + + switch ($token) { + // Attribute specified. + case XML_WBXML_GLOBAL_TOKEN_LITERAL: + // Section 5.8.4.5 + if (isset($attr)) { + $attrs[] = array('attribute' => $attr, + 'value' => $value); + } + + $attr = $this->getStringTableEntry(XML_WBXML::MBUInt32ToInt($input, $this->_strpos)); + break; + + // Value specified. + case XML_WBXML_GLOBAL_TOKEN_EXT_I_0: + case XML_WBXML_GLOBAL_TOKEN_EXT_I_1: + case XML_WBXML_GLOBAL_TOKEN_EXT_I_2: + // Section 5.8.4.2 + $value .= $this->termstr($input); + break; + + case XML_WBXML_GLOBAL_TOKEN_EXT_T_0: + case XML_WBXML_GLOBAL_TOKEN_EXT_T_1: + case XML_WBXML_GLOBAL_TOKEN_EXT_T_2: + // Section 5.8.4.2 + $value .= $this->getStringTableEntry(XML_WBXML::MBUInt32ToInt($input, $this->_strpos)); + break; + + case XML_WBXML_GLOBAL_TOKEN_EXT_0: + case XML_WBXML_GLOBAL_TOKEN_EXT_1: + case XML_WBXML_GLOBAL_TOKEN_EXT_2: + // Section 5.8.4.2 + $value .= $input[$this->_strpos++]; + break; + + case XML_WBXML_GLOBAL_TOKEN_ENTITY: + // Section 5.8.4.3 + $value .= $this->entity(XML_WBXML::MBUInt32ToInt($input, $this->_strpos)); + break; + + case XML_WBXML_GLOBAL_TOKEN_STR_I: + // Section 5.8.4.1 + $value .= $this->termstr($input); + break; + + case XML_WBXML_GLOBAL_TOKEN_STR_T: + // Section 5.8.4.1 + $value .= $this->getStringTableEntry(XML_WBXML::MBUInt32ToInt($input, $this->_strpos)); + break; + + case XML_WBXML_GLOBAL_TOKEN_OPAQUE: + // Section 5.8.4.6 + $size = XML_WBXML::MBUInt32ToInt($input, $this->_strpos); + $b = $this->_substr($input, $this->_strpos, $this->_strpos + $size); + $this->_strpos += $size; + + $value .= $b; + break; + + case XML_WBXML_GLOBAL_TOKEN_END: + // Section 5.8.4.7.1 + $hasMoreAttributes = false; + if (isset($attr)) { + $attrs[] = array('attribute' => $attr, + 'value' => $value); + } + break; + + case XML_WBXML_GLOBAL_TOKEN_SWITCH_PAGE: + // Section 5.8.4.7.2 + $codePage = $this->getByte($input); + if (!$this->_prevAttributeDTD) { + $this->_prevAttributeDTD = $this->_attributeDTD; + } + + $this->switchAttributeCodePage($codePage); + break; + + default: + if ($token > 128) { + if (isset($attr)) { + $attrs[] = array('attribute' => $attr, + 'value' => $value); + } + $attr = $this->_attributeDTD->toAttribute($token); + } else { + // Value. + $value .= $this->_attributeDTD->toAttribute($token); + } + break; + } + } + + if (!$this->_prevAttributeDTD) { + $this->_attributeDTD = $this->_prevAttributeDTD; + $this->_prevAttributeDTD = false; + } + + $this->stopGetAttributes(); + } + + function startGetAttributes() + { + $this->_isAttribute = true; + } + + function stopGetAttributes() + { + $this->_isAttribute = false; + } + + function getCurrentURI() + { + if ($this->_isAttribute) { + return $this->_tagDTD->getURI(); + } else { + return $this->_attributeDTD->getURI(); + } + } + + function writeString($str) + { + $this->_ch->characters($str); + } + + function getTag($tag) + { + // Should know which state it is in. + return $this->_tagDTD->toTagStr($tag); + } + + function getAttribute($attribute) + { + // Should know which state it is in. + $this->_attributeDTD->toAttributeInt($attribute); + } + + function switchElementCodePage($codePage) + { + $this->_tagDTD = &$this->_dtdManager->getInstance($this->_tagDTD->toCodePageStr($codePage)); + $this->switchAttributeCodePage($codePage); + } + + function switchAttributeCodePage($codePage) + { + $this->_attributeDTD = &$this->_dtdManager->getInstance($this->_attributeDTD->toCodePageStr($codePage)); + } + + /** + * Return the hex version of the base 10 $entity. + */ + function entity($entity) + { + return dechex($entity); + } + + /** + * Reads a null terminated string. + */ + function termstr($input) + { + $str = '#'; // must start with nonempty string to allow array access + $i = 0; + $ch = $input[$this->_strpos++]; + if (ord($ch) == 0) { + return ''; // don't return '#' + } + while (ord($ch) != 0) { + $str[$i++] = $ch; + $ch = $input[$this->_strpos++]; + } + + return $str; + } + + /** + * imitate substr() + * This circumvents a bug in the mbstring overloading in some distributions, + * where the mbstring.func_overload=0 INI-setting does not work, if mod_php + * has another value for that setting in another directory-context + */ + function _substr($input,$start,$size) + { + $ret = ""; + if (!$input) return $ret; + for ($i = $start; $i < $start+$size; $i++) { + $ret .= $input[$i]; + } + return $ret; + } +} +