| |
| #include "XmlRpcValue.h" |
| #include "XmlRpcException.h" |
| #include "XmlRpcUtil.h" |
| #include "base64.h" |
| |
| #ifndef MAKEDEPEND |
| # include <iostream> |
| # include <ostream> |
| # include <stdlib.h> |
| # include <stdio.h> |
| #endif |
| |
| namespace XmlRpc { |
| |
| |
| static const char VALUE_TAG[] = "<value>"; |
| static const char VALUE_ETAG[] = "</value>"; |
| |
| static const char BOOLEAN_TAG[] = "<boolean>"; |
| static const char BOOLEAN_ETAG[] = "</boolean>"; |
| static const char DOUBLE_TAG[] = "<double>"; |
| static const char DOUBLE_ETAG[] = "</double>"; |
| static const char INT_TAG[] = "<int>"; |
| static const char I4_TAG[] = "<i4>"; |
| static const char I4_ETAG[] = "</i4>"; |
| static const char STRING_TAG[] = "<string>"; |
| static const char DATETIME_TAG[] = "<dateTime.iso8601>"; |
| static const char DATETIME_ETAG[] = "</dateTime.iso8601>"; |
| static const char BASE64_TAG[] = "<base64>"; |
| static const char BASE64_ETAG[] = "</base64>"; |
| static const char NIL_TAG[] = "<nil/>"; |
| |
| static const char ARRAY_TAG[] = "<array>"; |
| static const char DATA_TAG[] = "<data>"; |
| static const char DATA_ETAG[] = "</data>"; |
| static const char ARRAY_ETAG[] = "</array>"; |
| |
| static const char STRUCT_TAG[] = "<struct>"; |
| static const char MEMBER_TAG[] = "<member>"; |
| static const char NAME_TAG[] = "<name>"; |
| static const char NAME_ETAG[] = "</name>"; |
| static const char MEMBER_ETAG[] = "</member>"; |
| static const char STRUCT_ETAG[] = "</struct>"; |
| |
| |
| |
| // Format strings |
| std::string XmlRpcValue::_doubleFormat("%f"); |
| |
| |
| |
| // Clean up |
| void XmlRpcValue::invalidate() |
| { |
| switch (_type) { |
| case TypeString: delete _value.asString; break; |
| case TypeDateTime: delete _value.asTime; break; |
| case TypeBase64: delete _value.asBinary; break; |
| case TypeArray: delete _value.asArray; break; |
| case TypeStruct: delete _value.asStruct; break; |
| default: break; |
| } |
| _type = TypeInvalid; |
| _value.asBinary = 0; |
| } |
| |
| |
| // Type checking |
| void XmlRpcValue::assertTypeOrInvalid(Type t) |
| { |
| if (_type == TypeInvalid) |
| { |
| _type = t; |
| switch (_type) { // Ensure there is a valid value for the type |
| case TypeString: _value.asString = new std::string(); break; |
| case TypeDateTime: _value.asTime = new struct tm(); break; |
| case TypeBase64: _value.asBinary = new BinaryData(); break; |
| case TypeArray: _value.asArray = new ValueArray(); break; |
| case TypeStruct: _value.asStruct = new ValueStruct(); break; |
| default: _value.asBinary = 0; break; |
| } |
| } |
| else if (_type != t) |
| throw XmlRpcException("type error"); |
| } |
| |
| void XmlRpcValue::assertArray(int size) const |
| { |
| if (_type != TypeArray) |
| throw XmlRpcException("type error: expected an array"); |
| else if (int(_value.asArray->size()) < size) |
| throw XmlRpcException("range error: array index too large"); |
| } |
| |
| |
| void XmlRpcValue::assertArray(int size) |
| { |
| if (_type == TypeInvalid) { |
| _type = TypeArray; |
| _value.asArray = new ValueArray(size); |
| } else if (_type == TypeArray) { |
| if (int(_value.asArray->size()) < size) |
| _value.asArray->resize(size); |
| } else |
| throw XmlRpcException("type error: expected an array"); |
| } |
| |
| void XmlRpcValue::assertStruct() |
| { |
| if (_type == TypeInvalid) { |
| _type = TypeStruct; |
| _value.asStruct = new ValueStruct(); |
| } else if (_type != TypeStruct) |
| throw XmlRpcException("type error: expected a struct"); |
| } |
| |
| |
| // Operators |
| XmlRpcValue& XmlRpcValue::operator=(XmlRpcValue const& rhs) |
| { |
| if (this != &rhs) |
| { |
| invalidate(); |
| _type = rhs._type; |
| switch (_type) { |
| case TypeBoolean: _value.asBool = rhs._value.asBool; break; |
| case TypeInt: _value.asInt = rhs._value.asInt; break; |
| case TypeDouble: _value.asDouble = rhs._value.asDouble; break; |
| case TypeDateTime: _value.asTime = new struct tm(*rhs._value.asTime); break; |
| case TypeString: _value.asString = new std::string(*rhs._value.asString); break; |
| case TypeBase64: _value.asBinary = new BinaryData(*rhs._value.asBinary); break; |
| case TypeArray: _value.asArray = new ValueArray(*rhs._value.asArray); break; |
| case TypeStruct: _value.asStruct = new ValueStruct(*rhs._value.asStruct); break; |
| default: _value.asBinary = 0; break; |
| } |
| } |
| return *this; |
| } |
| |
| |
| // Predicate for tm equality |
| static bool tmEq(struct tm const& t1, struct tm const& t2) { |
| return t1.tm_sec == t2.tm_sec && t1.tm_min == t2.tm_min && |
| t1.tm_hour == t2.tm_hour && t1.tm_mday == t1.tm_mday && |
| t1.tm_mon == t2.tm_mon && t1.tm_year == t2.tm_year; |
| } |
| |
| bool XmlRpcValue::operator==(XmlRpcValue const& other) const |
| { |
| if (_type != other._type) |
| return false; |
| |
| switch (_type) { |
| case TypeBoolean: return ( !_value.asBool && !other._value.asBool) || |
| ( _value.asBool && other._value.asBool); |
| case TypeInt: return _value.asInt == other._value.asInt; |
| case TypeDouble: return _value.asDouble == other._value.asDouble; |
| case TypeDateTime: return tmEq(*_value.asTime, *other._value.asTime); |
| case TypeString: return *_value.asString == *other._value.asString; |
| case TypeBase64: return *_value.asBinary == *other._value.asBinary; |
| case TypeArray: return *_value.asArray == *other._value.asArray; |
| |
| // The map<>::operator== requires the definition of value< for kcc |
| case TypeStruct: //return *_value.asStruct == *other._value.asStruct; |
| { |
| if (_value.asStruct->size() != other._value.asStruct->size()) |
| return false; |
| |
| ValueStruct::const_iterator it1=_value.asStruct->begin(); |
| ValueStruct::const_iterator it2=other._value.asStruct->begin(); |
| while (it1 != _value.asStruct->end()) { |
| const XmlRpcValue& v1 = it1->second; |
| const XmlRpcValue& v2 = it2->second; |
| if ( ! (v1 == v2)) |
| return false; |
| it1++; |
| it2++; |
| } |
| return true; |
| } |
| default: break; |
| } |
| return true; // Both invalid values ... |
| } |
| |
| bool XmlRpcValue::operator!=(XmlRpcValue const& other) const |
| { |
| return !(*this == other); |
| } |
| |
| |
| // Works for strings, binary data, arrays, and structs. |
| int XmlRpcValue::size() const |
| { |
| switch (_type) { |
| case TypeString: return int(_value.asString->size()); |
| case TypeBase64: return int(_value.asBinary->size()); |
| case TypeArray: return int(_value.asArray->size()); |
| case TypeStruct: return int(_value.asStruct->size()); |
| default: break; |
| } |
| |
| throw XmlRpcException("type error"); |
| } |
| |
| // Checks for existence of struct member |
| bool XmlRpcValue::hasMember(const std::string& name) const |
| { |
| return _type == TypeStruct && _value.asStruct->find(name) != _value.asStruct->end(); |
| } |
| |
| // Set the value from xml. The chars at *offset into valueXml |
| // should be the start of a <value> tag. Destroys any existing value. |
| bool XmlRpcValue::fromXml(std::string const& valueXml, int* offset) |
| { |
| int savedOffset = *offset; |
| |
| invalidate(); |
| if ( ! XmlRpcUtil::nextTagIs(VALUE_TAG, valueXml, offset)) |
| return false; // Not a value, offset not updated |
| |
| int afterValueOffset = *offset; |
| std::string typeTag = XmlRpcUtil::getNextTag(valueXml, offset); |
| bool result = false; |
| if (typeTag == NIL_TAG) |
| result = nilFromXml(valueXml, offset); |
| else if (typeTag == BOOLEAN_TAG) |
| result = boolFromXml(valueXml, offset); |
| else if (typeTag == I4_TAG || typeTag == INT_TAG) |
| result = intFromXml(valueXml, offset); |
| else if (typeTag == DOUBLE_TAG) |
| result = doubleFromXml(valueXml, offset); |
| else if (typeTag.empty() || typeTag == STRING_TAG) |
| result = stringFromXml(valueXml, offset); |
| else if (typeTag == DATETIME_TAG) |
| result = timeFromXml(valueXml, offset); |
| else if (typeTag == BASE64_TAG) |
| result = binaryFromXml(valueXml, offset); |
| else if (typeTag == ARRAY_TAG) |
| result = arrayFromXml(valueXml, offset); |
| else if (typeTag == STRUCT_TAG) |
| result = structFromXml(valueXml, offset); |
| // Watch for empty/blank strings with no <string>tag |
| else if (typeTag == VALUE_ETAG) |
| { |
| *offset = afterValueOffset; // back up & try again |
| result = stringFromXml(valueXml, offset); |
| } |
| |
| if (result) // Skip over the </value> tag |
| XmlRpcUtil::findTag(VALUE_ETAG, valueXml, offset); |
| else // Unrecognized tag after <value> |
| *offset = savedOffset; |
| |
| return result; |
| } |
| |
| // Encode the Value in xml |
| std::string XmlRpcValue::toXml() const |
| { |
| switch (_type) { |
| case TypeNil: return nilToXml(); |
| case TypeBoolean: return boolToXml(); |
| case TypeInt: return intToXml(); |
| case TypeDouble: return doubleToXml(); |
| case TypeString: return stringToXml(); |
| case TypeDateTime: return timeToXml(); |
| case TypeBase64: return binaryToXml(); |
| case TypeArray: return arrayToXml(); |
| case TypeStruct: return structToXml(); |
| default: break; |
| } |
| return std::string(); // Invalid value |
| } |
| |
| // Nil |
| bool XmlRpcValue::nilFromXml(std::string const& /* valueXml */, int* /* offset */) |
| { |
| _type = TypeNil; |
| _value.asBinary = 0; |
| return true; |
| } |
| |
| std::string XmlRpcValue::nilToXml() const |
| { |
| std::string xml = VALUE_TAG; |
| xml += NIL_TAG; |
| xml += VALUE_ETAG; |
| return xml; |
| } |
| |
| // Boolean |
| bool XmlRpcValue::boolFromXml(std::string const& valueXml, int* offset) |
| { |
| const char* valueStart = valueXml.c_str() + *offset; |
| char* valueEnd; |
| long ivalue = strtol(valueStart, &valueEnd, 10); |
| if (valueEnd == valueStart || (ivalue != 0 && ivalue != 1)) |
| return false; |
| |
| _type = TypeBoolean; |
| _value.asBool = (ivalue == 1); |
| *offset += int(valueEnd - valueStart); |
| return true; |
| } |
| |
| std::string XmlRpcValue::boolToXml() const |
| { |
| std::string xml = VALUE_TAG; |
| xml += BOOLEAN_TAG; |
| xml += (_value.asBool ? "1" : "0"); |
| xml += BOOLEAN_ETAG; |
| xml += VALUE_ETAG; |
| return xml; |
| } |
| |
| // Int |
| bool XmlRpcValue::intFromXml(std::string const& valueXml, int* offset) |
| { |
| const char* valueStart = valueXml.c_str() + *offset; |
| char* valueEnd; |
| long ivalue = strtol(valueStart, &valueEnd, 10); |
| if (valueEnd == valueStart) |
| return false; |
| |
| _type = TypeInt; |
| _value.asInt = int(ivalue); |
| *offset += int(valueEnd - valueStart); |
| return true; |
| } |
| |
| std::string XmlRpcValue::intToXml() const |
| { |
| char buf[256]; |
| snprintf(buf, sizeof(buf)-1, "%d", _value.asInt); |
| buf[sizeof(buf)-1] = 0; |
| std::string xml = VALUE_TAG; |
| xml += I4_TAG; |
| xml += buf; |
| xml += I4_ETAG; |
| xml += VALUE_ETAG; |
| return xml; |
| } |
| |
| // Double |
| bool XmlRpcValue::doubleFromXml(std::string const& valueXml, int* offset) |
| { |
| const char* valueStart = valueXml.c_str() + *offset; |
| char* valueEnd; |
| double dvalue = strtod(valueStart, &valueEnd); |
| if (valueEnd == valueStart) |
| return false; |
| |
| _type = TypeDouble; |
| _value.asDouble = dvalue; |
| *offset += int(valueEnd - valueStart); |
| return true; |
| } |
| |
| std::string XmlRpcValue::doubleToXml() const |
| { |
| char buf[256]; |
| snprintf(buf, sizeof(buf)-1, getDoubleFormat().c_str(), _value.asDouble); |
| buf[sizeof(buf)-1] = 0; |
| |
| std::string xml = VALUE_TAG; |
| xml += DOUBLE_TAG; |
| xml += buf; |
| xml += DOUBLE_ETAG; |
| xml += VALUE_ETAG; |
| return xml; |
| } |
| |
| // String |
| bool XmlRpcValue::stringFromXml(std::string const& valueXml, int* offset) |
| { |
| size_t valueEnd = valueXml.find('<', *offset); |
| if (valueEnd == std::string::npos) |
| return false; // No end tag; |
| |
| _type = TypeString; |
| _value.asString = new std::string(XmlRpcUtil::xmlDecode(valueXml.substr(*offset, valueEnd-*offset))); |
| *offset += int(_value.asString->length()); |
| return true; |
| } |
| |
| std::string XmlRpcValue::stringToXml() const |
| { |
| std::string xml = VALUE_TAG; |
| //xml += STRING_TAG; optional |
| xml += XmlRpcUtil::xmlEncode(*_value.asString); |
| //xml += STRING_ETAG; |
| xml += VALUE_ETAG; |
| return xml; |
| } |
| |
| // DateTime (stored as a struct tm) |
| bool XmlRpcValue::timeFromXml(std::string const& valueXml, int* offset) |
| { |
| size_t valueEnd = valueXml.find('<', *offset); |
| if (valueEnd == std::string::npos) |
| return false; // No end tag; |
| |
| std::string stime = valueXml.substr(*offset, valueEnd-*offset); |
| |
| struct tm t; |
| if (sscanf(stime.c_str(),"%4d%2d%2dT%2d:%2d:%2d",&t.tm_year,&t.tm_mon,&t.tm_mday,&t.tm_hour,&t.tm_min,&t.tm_sec) != 6) |
| return false; |
| |
| t.tm_isdst = -1; |
| _type = TypeDateTime; |
| _value.asTime = new struct tm(t); |
| *offset += int(stime.length()); |
| return true; |
| } |
| |
| std::string XmlRpcValue::timeToXml() const |
| { |
| struct tm* t = _value.asTime; |
| char buf[20]; |
| snprintf(buf, sizeof(buf)-1, "%4d%02d%02dT%02d:%02d:%02d", |
| t->tm_year,t->tm_mon,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec); |
| buf[sizeof(buf)-1] = 0; |
| |
| std::string xml = VALUE_TAG; |
| xml += DATETIME_TAG; |
| xml += buf; |
| xml += DATETIME_ETAG; |
| xml += VALUE_ETAG; |
| return xml; |
| } |
| |
| |
| // Base64 |
| bool XmlRpcValue::binaryFromXml(std::string const& valueXml, int* offset) |
| { |
| size_t valueEnd = valueXml.find('<', *offset); |
| if (valueEnd == std::string::npos) |
| return false; // No end tag; |
| |
| _type = TypeBase64; |
| std::string asString = valueXml.substr(*offset, valueEnd-*offset); |
| _value.asBinary = new BinaryData(); |
| // check whether base64 encodings can contain chars xml encodes... |
| |
| // convert from base64 to binary |
| int iostatus = 0; |
| base64<char> decoder; |
| std::back_insert_iterator<BinaryData> ins = std::back_inserter(*(_value.asBinary)); |
| decoder.get(asString.begin(), asString.end(), ins, iostatus); |
| |
| *offset += int(asString.length()); |
| return true; |
| } |
| |
| |
| std::string XmlRpcValue::binaryToXml() const |
| { |
| // convert to base64 |
| std::vector<char> base64data; |
| int iostatus = 0; |
| base64<char> encoder; |
| std::back_insert_iterator<std::vector<char> > ins = std::back_inserter(base64data); |
| encoder.put(_value.asBinary->begin(), _value.asBinary->end(), ins, iostatus, base64<>::crlf()); |
| |
| // Wrap with xml |
| std::string xml = VALUE_TAG; |
| xml += BASE64_TAG; |
| xml.append(base64data.begin(), base64data.end()); |
| xml += BASE64_ETAG; |
| xml += VALUE_ETAG; |
| return xml; |
| } |
| |
| |
| // Array |
| bool XmlRpcValue::arrayFromXml(std::string const& valueXml, int* offset) |
| { |
| if ( ! XmlRpcUtil::nextTagIs(DATA_TAG, valueXml, offset)) |
| return false; |
| |
| _type = TypeArray; |
| _value.asArray = new ValueArray; |
| XmlRpcValue v; |
| while (v.fromXml(valueXml, offset)) |
| _value.asArray->push_back(v); // copy... |
| |
| // Skip the trailing </data> |
| (void) XmlRpcUtil::nextTagIs(DATA_ETAG, valueXml, offset); |
| return true; |
| } |
| |
| |
| // In general, its preferable to generate the xml of each element of the |
| // array as it is needed rather than glomming up one big string. |
| std::string XmlRpcValue::arrayToXml() const |
| { |
| std::string xml = VALUE_TAG; |
| xml += ARRAY_TAG; |
| xml += DATA_TAG; |
| |
| int s = int(_value.asArray->size()); |
| for (int i=0; i<s; ++i) |
| xml += _value.asArray->at(i).toXml(); |
| |
| xml += DATA_ETAG; |
| xml += ARRAY_ETAG; |
| xml += VALUE_ETAG; |
| return xml; |
| } |
| |
| |
| // Struct |
| bool XmlRpcValue::structFromXml(std::string const& valueXml, int* offset) |
| { |
| _type = TypeStruct; |
| _value.asStruct = new ValueStruct; |
| |
| while (XmlRpcUtil::nextTagIs(MEMBER_TAG, valueXml, offset)) { |
| // name |
| const std::string name = XmlRpcUtil::parseTag(NAME_TAG, valueXml, offset); |
| // value |
| XmlRpcValue val(valueXml, offset); |
| if ( ! val.valid()) { |
| invalidate(); |
| return false; |
| } |
| const std::pair<const std::string, XmlRpcValue> p(name, val); |
| _value.asStruct->insert(p); |
| |
| (void) XmlRpcUtil::nextTagIs(MEMBER_ETAG, valueXml, offset); |
| } |
| return true; |
| } |
| |
| |
| // In general, its preferable to generate the xml of each element |
| // as it is needed rather than glomming up one big string. |
| std::string XmlRpcValue::structToXml() const |
| { |
| std::string xml = VALUE_TAG; |
| xml += STRUCT_TAG; |
| |
| ValueStruct::const_iterator it; |
| for (it=_value.asStruct->begin(); it!=_value.asStruct->end(); ++it) { |
| xml += MEMBER_TAG; |
| xml += NAME_TAG; |
| xml += XmlRpcUtil::xmlEncode(it->first); |
| xml += NAME_ETAG; |
| xml += it->second.toXml(); |
| xml += MEMBER_ETAG; |
| } |
| |
| xml += STRUCT_ETAG; |
| xml += VALUE_ETAG; |
| return xml; |
| } |
| |
| |
| |
| // Write the value without xml encoding it |
| std::ostream& XmlRpcValue::write(std::ostream& os) const { |
| switch (_type) { |
| default: break; |
| case TypeBoolean: os << _value.asBool; break; |
| case TypeInt: os << _value.asInt; break; |
| case TypeDouble: os << _value.asDouble; break; |
| case TypeString: os << *_value.asString; break; |
| case TypeDateTime: |
| { |
| struct tm* t = _value.asTime; |
| char buf[20]; |
| snprintf(buf, sizeof(buf)-1, "%4d%02d%02dT%02d:%02d:%02d", |
| t->tm_year,t->tm_mon,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec); |
| buf[sizeof(buf)-1] = 0; |
| os << buf; |
| break; |
| } |
| case TypeBase64: |
| { |
| int iostatus = 0; |
| std::ostreambuf_iterator<char> out(os); |
| base64<char> encoder; |
| encoder.put(_value.asBinary->begin(), _value.asBinary->end(), out, iostatus, base64<>::crlf()); |
| break; |
| } |
| case TypeArray: |
| { |
| int s = int(_value.asArray->size()); |
| os << '{'; |
| for (int i=0; i<s; ++i) |
| { |
| if (i > 0) os << ','; |
| _value.asArray->at(i).write(os); |
| } |
| os << '}'; |
| break; |
| } |
| case TypeStruct: |
| { |
| os << '['; |
| ValueStruct::const_iterator it; |
| for (it=_value.asStruct->begin(); it!=_value.asStruct->end(); ++it) |
| { |
| if (it!=_value.asStruct->begin()) os << ','; |
| os << it->first << ':'; |
| it->second.write(os); |
| } |
| os << ']'; |
| break; |
| } |
| |
| } |
| |
| return os; |
| } |
| |
| } // namespace XmlRpc |
| |
| |
| // ostream |
| std::ostream& operator<<(std::ostream& os, XmlRpc::XmlRpcValue& v) |
| { |
| // If you want to output in xml format: |
| //return os << v.toXml(); |
| return v.write(os); |
| } |
| |