From 922cb5559b9f2f97279fa24cc9c5862c8b666495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?IOhannes=20m=20zm=C3=B6lnig?= Date: Tue, 8 Mar 2005 10:23:43 +0000 Subject: This commit was generated by cvs2svn to compensate for changes in r2603, which included commits to RCS files with non-trunk default branches. svn path=/trunk/externals/iem/iemxmlrpc/; revision=2604 --- xmlrpc++/src/Doxyfile | 1041 +++++++++++++++++++++++++++++++ xmlrpc++/src/XmlRpc.h | 94 +++ xmlrpc++/src/XmlRpcClient.cpp | 413 ++++++++++++ xmlrpc++/src/XmlRpcClient.h | 125 ++++ xmlrpc++/src/XmlRpcDispatch.cpp | 209 +++++++ xmlrpc++/src/XmlRpcDispatch.h | 88 +++ xmlrpc++/src/XmlRpcException.h | 42 ++ xmlrpc++/src/XmlRpcServer.cpp | 284 +++++++++ xmlrpc++/src/XmlRpcServer.h | 104 +++ xmlrpc++/src/XmlRpcServerConnection.cpp | 371 +++++++++++ xmlrpc++/src/XmlRpcServerConnection.h | 102 +++ xmlrpc++/src/XmlRpcServerMethod.cpp | 21 + xmlrpc++/src/XmlRpcServerMethod.h | 47 ++ xmlrpc++/src/XmlRpcSocket.cpp | 260 ++++++++ xmlrpc++/src/XmlRpcSocket.h | 69 ++ xmlrpc++/src/XmlRpcSource.cpp | 35 ++ xmlrpc++/src/XmlRpcSource.h | 55 ++ xmlrpc++/src/XmlRpcUtil.cpp | 250 ++++++++ xmlrpc++/src/XmlRpcUtil.h | 61 ++ xmlrpc++/src/XmlRpcValue.cpp | 611 ++++++++++++++++++ xmlrpc++/src/XmlRpcValue.h | 189 ++++++ xmlrpc++/src/base64.h | 379 +++++++++++ 22 files changed, 4850 insertions(+) create mode 100644 xmlrpc++/src/Doxyfile create mode 100644 xmlrpc++/src/XmlRpc.h create mode 100644 xmlrpc++/src/XmlRpcClient.cpp create mode 100644 xmlrpc++/src/XmlRpcClient.h create mode 100644 xmlrpc++/src/XmlRpcDispatch.cpp create mode 100644 xmlrpc++/src/XmlRpcDispatch.h create mode 100644 xmlrpc++/src/XmlRpcException.h create mode 100644 xmlrpc++/src/XmlRpcServer.cpp create mode 100644 xmlrpc++/src/XmlRpcServer.h create mode 100644 xmlrpc++/src/XmlRpcServerConnection.cpp create mode 100644 xmlrpc++/src/XmlRpcServerConnection.h create mode 100644 xmlrpc++/src/XmlRpcServerMethod.cpp create mode 100644 xmlrpc++/src/XmlRpcServerMethod.h create mode 100644 xmlrpc++/src/XmlRpcSocket.cpp create mode 100644 xmlrpc++/src/XmlRpcSocket.h create mode 100644 xmlrpc++/src/XmlRpcSource.cpp create mode 100644 xmlrpc++/src/XmlRpcSource.h create mode 100644 xmlrpc++/src/XmlRpcUtil.cpp create mode 100644 xmlrpc++/src/XmlRpcUtil.h create mode 100644 xmlrpc++/src/XmlRpcValue.cpp create mode 100644 xmlrpc++/src/XmlRpcValue.h create mode 100644 xmlrpc++/src/base64.h (limited to 'xmlrpc++/src') diff --git a/xmlrpc++/src/Doxyfile b/xmlrpc++/src/Doxyfile new file mode 100644 index 0000000..2d8a346 --- /dev/null +++ b/xmlrpc++/src/Doxyfile @@ -0,0 +1,1041 @@ +# Doxyfile 1.3-rc3 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# General configuration options +#--------------------------------------------------------------------------- + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = XmlRpc++ + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = 0.7 + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = ../doc + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, +# Finnish, French, German, Greek, Hungarian, Italian, Japanese, Japanese-en +# (Japanese with english messages), Korean, Norwegian, Polish, Portuguese, +# Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish and Ukrainian. + +OUTPUT_LANGUAGE = English + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these class will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = YES + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all inherited +# members of a class in the documentation of that class as if those members were +# ordinary class members. Constructors, destructors and assignment operators of +# the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. It is allowed to use relative paths in the argument list. + +STRIP_FROM_PATH = + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower case letters. If set to YES upper case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# users are adviced to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like the Qt-style comments (thus requiring an +# explict @brief command for a brief description. + +JAVADOC_AUTOBRIEF = YES + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the DETAILS_AT_TOP tag is set to YES then Doxygen +# will output the detailed description near the top, like JavaDoc. +# If set to NO, the detailed description appears after the member +# documentation. + +DETAILS_AT_TOP = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# reimplements. + +INHERIT_DOCS = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consist of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. +# For instance some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources +# only. Doxygen will then generate output that is more tailored for Java. +# For instance namespaces will be presented as packages, qualified scopes +# will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp +# *.h++ *.idl *.odl + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = base64.h + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or directories +# that are symbolic links (a Unix filesystem feature) are excluded from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. + +EXCLUDE_PATTERNS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. + +INPUT_FILTER = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES (the default) +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES (the default) +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output dir. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non empty doxygen will try to run +# the html help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the Html help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be +# generated containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimised for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assigments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_XML = NO + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_PREDEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse the +# parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::addtions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES tag can be used to specify one or more tagfiles. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in Html, RTF and LaTeX) for classes with base or +# super classes. Setting the tag to NO turns the diagrams off. Note that this +# option is superceded by the HAVE_DOT option below. This is only a fallback. It is +# recommended to install and use dot, since it yield more powerful graphs. + +CLASS_DIAGRAMS = YES + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found on the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_WIDTH = 1024 + +# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_HEIGHT = 1024 + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermedate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::addtions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO + +# The CGI_NAME tag should be the name of the CGI script that +# starts the search engine (doxysearch) with the correct parameters. +# A script with this name will be generated by doxygen. + +CGI_NAME = search.cgi + +# The CGI_URL tag should be the absolute URL to the directory where the +# cgi binaries are located. See the documentation of your http daemon for +# details. + +CGI_URL = + +# The DOC_URL tag should be the absolute URL to the directory where the +# documentation is located. If left blank the absolute path to the +# documentation, with file:// prepended to it, will be used. + +DOC_URL = + +# The DOC_ABSPATH tag should be the absolute path to the directory where the +# documentation is located. If left blank the directory on the local machine +# will be used. + +DOC_ABSPATH = + +# The BIN_ABSPATH tag must point to the directory where the doxysearch binary +# is installed. + +BIN_ABSPATH = /usr/local/bin/ + +# The EXT_DOC_PATHS tag can be used to specify one or more paths to +# documentation generated for other projects. This allows doxysearch to search +# the documentation for these projects as well. + +EXT_DOC_PATHS = diff --git a/xmlrpc++/src/XmlRpc.h b/xmlrpc++/src/XmlRpc.h new file mode 100644 index 0000000..9611af7 --- /dev/null +++ b/xmlrpc++/src/XmlRpc.h @@ -0,0 +1,94 @@ +#ifndef _XMLRPC_H_ +#define _XMLRPC_H_ +// +// XmlRpc++ Copyright (c) 2002-2003 by Chris Morley +// 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 +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// 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. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +// + +#if defined(_MSC_VER) +# pragma warning(disable:4786) // identifier was truncated in debug info +#endif + +#ifndef MAKEDEPEND +# include +#endif + +#include "XmlRpcClient.h" +#include "XmlRpcException.h" +#include "XmlRpcServer.h" +#include "XmlRpcServerMethod.h" +#include "XmlRpcValue.h" +#include "XmlRpcUtil.h" + +namespace XmlRpc { + + + //! An interface allowing custom handling of error message reporting. + class XmlRpcErrorHandler { + public: + //! Returns a pointer to the currently installed error handling object. + static XmlRpcErrorHandler* getErrorHandler() + { return _errorHandler; } + + //! Specifies the error handler. + static void setErrorHandler(XmlRpcErrorHandler* eh) + { _errorHandler = eh; } + + //! Report an error. Custom error handlers should define this method. + virtual void error(const char* msg) = 0; + + protected: + static XmlRpcErrorHandler* _errorHandler; + }; + + //! An interface allowing custom handling of informational message reporting. + class XmlRpcLogHandler { + public: + //! Returns a pointer to the currently installed message reporting object. + static XmlRpcLogHandler* getLogHandler() + { return _logHandler; } + + //! Specifies the message handler. + static void setLogHandler(XmlRpcLogHandler* lh) + { _logHandler = lh; } + + //! Returns the level of verbosity of informational messages. 0 is no output, 5 is very verbose. + static int getVerbosity() + { return _verbosity; } + + //! Specify the level of verbosity of informational messages. 0 is no output, 5 is very verbose. + static void setVerbosity(int v) + { _verbosity = v; } + + //! Output a message. Custom error handlers should define this method. + virtual void log(int level, const char* msg) = 0; + + protected: + static XmlRpcLogHandler* _logHandler; + static int _verbosity; + }; + + //! Returns log message verbosity. This is short for XmlRpcLogHandler::getVerbosity() + int getVerbosity(); + //! Sets log message verbosity. This is short for XmlRpcLogHandler::setVerbosity(level) + void setVerbosity(int level); + + + //! Version identifier + extern const char XMLRPC_VERSION[]; + +} // namespace XmlRpc + +#endif // _XMLRPC_H_ diff --git a/xmlrpc++/src/XmlRpcClient.cpp b/xmlrpc++/src/XmlRpcClient.cpp new file mode 100644 index 0000000..e706d0a --- /dev/null +++ b/xmlrpc++/src/XmlRpcClient.cpp @@ -0,0 +1,413 @@ + +#include "XmlRpcClient.h" + +#include "XmlRpcSocket.h" +#include "XmlRpc.h" + +#include +#include + + +using namespace XmlRpc; + +// Static data +const char XmlRpcClient::REQUEST_BEGIN[] = + "\r\n" + ""; +const char XmlRpcClient::REQUEST_END_METHODNAME[] = "\r\n"; +const char XmlRpcClient::PARAMS_TAG[] = ""; +const char XmlRpcClient::PARAMS_ETAG[] = ""; +const char XmlRpcClient::PARAM_TAG[] = ""; +const char XmlRpcClient::PARAM_ETAG[] = ""; +const char XmlRpcClient::REQUEST_END[] = "\r\n"; +const char XmlRpcClient::METHODRESPONSE_TAG[] = ""; +const char XmlRpcClient::FAULT_TAG[] = ""; + + + +XmlRpcClient::XmlRpcClient(const char* host, int port, const char* uri/*=0*/) +{ + XmlRpcUtil::log(1, "XmlRpcClient new client: host %s, port %d.", host, port); + + _host = host; + _port = port; + if (uri) + _uri = uri; + else + _uri = "/RPC2"; + _connectionState = NO_CONNECTION; + _executing = false; + _eof = false; + + // Default to keeping the connection open until an explicit close is done + setKeepOpen(); +} + + +XmlRpcClient::~XmlRpcClient() +{ +} + +// Close the owned fd +void +XmlRpcClient::close() +{ + XmlRpcUtil::log(4, "XmlRpcClient::close: fd %d.", getfd()); + _connectionState = NO_CONNECTION; + _disp.exit(); + _disp.removeSource(this); + XmlRpcSource::close(); +} + + +// Clear the referenced flag even if exceptions or errors occur. +struct ClearFlagOnExit { + ClearFlagOnExit(bool& flag) : _flag(flag) {} + ~ClearFlagOnExit() { _flag = false; } + bool& _flag; +}; + +// Execute the named procedure on the remote server. +// Params should be an array of the arguments for the method. +// Returns true if the request was sent and a result received (although the result +// might be a fault). +bool +XmlRpcClient::execute(const char* method, XmlRpcValue const& params, XmlRpcValue& result) +{ + XmlRpcUtil::log(1, "XmlRpcClient::execute: method %s (_connectionState %d).", method, _connectionState); + + // This is not a thread-safe operation, if you want to do multithreading, use separate + // clients for each thread. If you want to protect yourself from multiple threads + // accessing the same client, replace this code with a real mutex. + if (_executing) + return false; + + _executing = true; + ClearFlagOnExit cf(_executing); + + _sendAttempts = 0; + _isFault = false; + + if ( ! setupConnection()) + return false; + + if ( ! generateRequest(method, params)) + return false; + + result.clear(); + double msTime = -1.0; // Process until exit is called + _disp.work(msTime); + + if (_connectionState != IDLE || ! parseResponse(result)) + return false; + + XmlRpcUtil::log(1, "XmlRpcClient::execute: method %s completed.", method); + _response = ""; + return true; +} + +// XmlRpcSource interface implementation +// Handle server responses. Called by the event dispatcher during execute. +unsigned +XmlRpcClient::handleEvent(unsigned eventType) +{ + if (eventType == XmlRpcDispatch::Exception) + { + if (_connectionState == WRITE_REQUEST && _bytesWritten == 0) + XmlRpcUtil::error("Error in XmlRpcClient::handleEvent: could not connect to server (%s).", + XmlRpcSocket::getErrorMsg().c_str()); + else + XmlRpcUtil::error("Error in XmlRpcClient::handleEvent (state %d): %s.", + _connectionState, XmlRpcSocket::getErrorMsg().c_str()); + return 0; + } + + if (_connectionState == WRITE_REQUEST) + if ( ! writeRequest()) return 0; + + if (_connectionState == READ_HEADER) + if ( ! readHeader()) return 0; + + if (_connectionState == READ_RESPONSE) + if ( ! readResponse()) return 0; + + // This should probably always ask for Exception events too + return (_connectionState == WRITE_REQUEST) + ? XmlRpcDispatch::WritableEvent : XmlRpcDispatch::ReadableEvent; +} + + +// Create the socket connection to the server if necessary +bool +XmlRpcClient::setupConnection() +{ + // If an error occurred last time through, or if the server closed the connection, close our end + if ((_connectionState != NO_CONNECTION && _connectionState != IDLE) || _eof) + close(); + + _eof = false; + if (_connectionState == NO_CONNECTION) + if (! doConnect()) + return false; + + // Prepare to write the request + _connectionState = WRITE_REQUEST; + _bytesWritten = 0; + + // Notify the dispatcher to listen on this source (calls handleEvent when the socket is writable) + _disp.removeSource(this); // Make sure nothing is left over + _disp.addSource(this, XmlRpcDispatch::WritableEvent | XmlRpcDispatch::Exception); + + return true; +} + + +// Connect to the xmlrpc server +bool +XmlRpcClient::doConnect() +{ + int fd = XmlRpcSocket::socket(); + if (fd < 0) + { + XmlRpcUtil::error("Error in XmlRpcClient::doConnect: Could not create socket (%s).", XmlRpcSocket::getErrorMsg().c_str()); + return false; + } + + XmlRpcUtil::log(3, "XmlRpcClient::doConnect: fd %d.", fd); + this->setfd(fd); + + // Don't block on connect/reads/writes + if ( ! XmlRpcSocket::setNonBlocking(fd)) + { + this->close(); + XmlRpcUtil::error("Error in XmlRpcClient::doConnect: Could not set socket to non-blocking IO mode (%s).", XmlRpcSocket::getErrorMsg().c_str()); + return false; + } + + if ( ! XmlRpcSocket::connect(fd, _host, _port)) + { + this->close(); + XmlRpcUtil::error("Error in XmlRpcClient::doConnect: Could not connect to server (%s).", XmlRpcSocket::getErrorMsg().c_str()); + return false; + } + + return true; +} + +// Encode the request to call the specified method with the specified parameters into xml +bool +XmlRpcClient::generateRequest(const char* methodName, XmlRpcValue const& params) +{ + std::string body = REQUEST_BEGIN; + body += methodName; + body += REQUEST_END_METHODNAME; + + // If params is an array, each element is a separate parameter + if (params.valid()) { + body += PARAMS_TAG; + if (params.getType() == XmlRpcValue::TypeArray) + { + for (int i=0; igetfd(), _request, &_bytesWritten)) { + XmlRpcUtil::error("Error in XmlRpcClient::writeRequest: write error (%s).",XmlRpcSocket::getErrorMsg().c_str()); + return false; + } + + XmlRpcUtil::log(3, "XmlRpcClient::writeRequest: wrote %d of %d bytes.", _bytesWritten, _request.length()); + + // Wait for the result + if (_bytesWritten == int(_request.length())) { + _header = ""; + _response = ""; + _connectionState = READ_HEADER; + } + return true; +} + + +// Read the header from the response +bool +XmlRpcClient::readHeader() +{ + // Read available data + if ( ! XmlRpcSocket::nbRead(this->getfd(), _header, &_eof) || + (_eof && _header.length() == 0)) { + + // If we haven't read any data yet and this is a keep-alive connection, the server may + // have timed out, so we try one more time. + if (getKeepOpen() && _header.length() == 0 && _sendAttempts++ == 0) { + XmlRpcUtil::log(4, "XmlRpcClient::readHeader: re-trying connection"); + XmlRpcSource::close(); + _connectionState = NO_CONNECTION; + _eof = false; + return setupConnection(); + } + + XmlRpcUtil::error("Error in XmlRpcClient::readHeader: error while reading header (%s) on fd %d.", + XmlRpcSocket::getErrorMsg().c_str(), getfd()); + return false; + } + + XmlRpcUtil::log(4, "XmlRpcClient::readHeader: client has read %d bytes", _header.length()); + + char *hp = (char*)_header.c_str(); // Start of header + char *ep = hp + _header.length(); // End of string + char *bp = 0; // Start of body + char *lp = 0; // Start of content-length value + + for (char *cp = hp; (bp == 0) && (cp < ep); ++cp) { + if ((ep - cp > 16) && (strncasecmp(cp, "Content-length: ", 16) == 0)) + lp = cp + 16; + else if ((ep - cp > 4) && (strncmp(cp, "\r\n\r\n", 4) == 0)) + bp = cp + 4; + else if ((ep - cp > 2) && (strncmp(cp, "\n\n", 2) == 0)) + bp = cp + 2; + } + + // If we haven't gotten the entire header yet, return (keep reading) + if (bp == 0) { + if (_eof) // EOF in the middle of a response is an error + { + XmlRpcUtil::error("Error in XmlRpcClient::readHeader: EOF while reading header"); + return false; // Close the connection + } + + return true; // Keep reading + } + + // Decode content length + if (lp == 0) { + XmlRpcUtil::error("Error XmlRpcClient::readHeader: No Content-length specified"); + return false; // We could try to figure it out by parsing as we read, but for now... + } + + _contentLength = atoi(lp); + if (_contentLength <= 0) { + XmlRpcUtil::error("Error in XmlRpcClient::readHeader: Invalid Content-length specified (%d).", _contentLength); + return false; + } + + XmlRpcUtil::log(4, "client read content length: %d", _contentLength); + + // Otherwise copy non-header data to response buffer and set state to read response. + _response = bp; + _header = ""; // should parse out any interesting bits from the header (connection, etc)... + _connectionState = READ_RESPONSE; + return true; // Continue monitoring this source +} + + +bool +XmlRpcClient::readResponse() +{ + // If we dont have the entire response yet, read available data + if (int(_response.length()) < _contentLength) { + if ( ! XmlRpcSocket::nbRead(this->getfd(), _response, &_eof)) { + XmlRpcUtil::error("Error in XmlRpcClient::readResponse: read error (%s).",XmlRpcSocket::getErrorMsg().c_str()); + return false; + } + + // If we haven't gotten the entire _response yet, return (keep reading) + if (int(_response.length()) < _contentLength) { + if (_eof) { + XmlRpcUtil::error("Error in XmlRpcClient::readResponse: EOF while reading response"); + return false; + } + return true; + } + } + + // Otherwise, parse and return the result + XmlRpcUtil::log(3, "XmlRpcClient::readResponse (read %d bytes)", _response.length()); + XmlRpcUtil::log(5, "response:\n%s", _response.c_str()); + + _connectionState = IDLE; + + return false; // Stop monitoring this source (causes return from work) +} + + +// Convert the response xml into a result value +bool +XmlRpcClient::parseResponse(XmlRpcValue& result) +{ + // Parse response xml into result + int offset = 0; + if ( ! XmlRpcUtil::findTag(METHODRESPONSE_TAG,_response,&offset)) { + XmlRpcUtil::error("Error in XmlRpcClient::parseResponse: Invalid response - no methodResponse. Response:\n%s", _response.c_str()); + return false; + } + + // Expect either ... or ... + if ((XmlRpcUtil::nextTagIs(PARAMS_TAG,_response,&offset) && + XmlRpcUtil::nextTagIs(PARAM_TAG,_response,&offset)) || + XmlRpcUtil::nextTagIs(FAULT_TAG,_response,&offset) && (_isFault = true)) + { + if ( ! result.fromXml(_response, &offset)) { + XmlRpcUtil::error("Error in XmlRpcClient::parseResponse: Invalid response value. Response:\n%s", _response.c_str()); + _response = ""; + return false; + } + } else { + XmlRpcUtil::error("Error in XmlRpcClient::parseResponse: Invalid response - no param or fault tag. Response:\n%s", _response.c_str()); + _response = ""; + return false; + } + + _response = ""; + return result.valid(); +} + diff --git a/xmlrpc++/src/XmlRpcClient.h b/xmlrpc++/src/XmlRpcClient.h new file mode 100644 index 0000000..ecf5811 --- /dev/null +++ b/xmlrpc++/src/XmlRpcClient.h @@ -0,0 +1,125 @@ + +#ifndef _XMLRPCCLIENT_H_ +#define _XMLRPCCLIENT_H_ +// +// XmlRpc++ Copyright (c) 2002-2003 by Chris Morley +// +#if defined(_MSC_VER) +# pragma warning(disable:4786) // identifier was truncated in debug info +#endif + + +#ifndef MAKEDEPEND +# include +#endif + +#include "XmlRpcDispatch.h" +#include "XmlRpcSource.h" + +namespace XmlRpc { + + // Arguments and results are represented by XmlRpcValues + class XmlRpcValue; + + //! A class to send XML RPC requests to a server and return the results. + class XmlRpcClient : public XmlRpcSource { + public: + // Static data + static const char REQUEST_BEGIN[]; + static const char REQUEST_END_METHODNAME[]; + static const char PARAMS_TAG[]; + static const char PARAMS_ETAG[]; + static const char PARAM_TAG[]; + static const char PARAM_ETAG[]; + static const char REQUEST_END[]; + // Result tags + static const char METHODRESPONSE_TAG[]; + static const char FAULT_TAG[]; + + //! Construct a client to connect to the server at the specified host:port address + //! @param host The name of the remote machine hosting the server + //! @param port The port on the remote machine where the server is listening + //! @param uri An optional string to be sent as the URI in the HTTP GET header + XmlRpcClient(const char* host, int port, const char* uri=0); + + //! Destructor + virtual ~XmlRpcClient(); + + //! Execute the named procedure on the remote server. + //! @param method The name of the remote procedure to execute + //! @param params An array of the arguments for the method + //! @param result The result value to be returned to the client + //! @return true if the request was sent and a result received + //! (although the result might be a fault). + //! + //! Currently this is a synchronous (blocking) implementation (execute + //! does not return until it receives a response or an error). Use isFault() + //! to determine whether the result is a fault response. + bool execute(const char* method, XmlRpcValue const& params, XmlRpcValue& result); + + //! Returns true if the result of the last execute() was a fault response. + bool isFault() const { return _isFault; } + + + // XmlRpcSource interface implementation + //! Close the connection + virtual void close(); + + //! Handle server responses. Called by the event dispatcher during execute. + //! @param eventType The type of event that occurred. + //! @see XmlRpcDispatch::EventType + virtual unsigned handleEvent(unsigned eventType); + + protected: + // Execution processing helpers + virtual bool doConnect(); + virtual bool setupConnection(); + + virtual bool generateRequest(const char* method, XmlRpcValue const& params); + virtual std::string generateHeader(std::string const& body); + virtual bool writeRequest(); + virtual bool readHeader(); + virtual bool readResponse(); + virtual bool parseResponse(XmlRpcValue& result); + + // Possible IO states for the connection + enum ClientConnectionState { NO_CONNECTION, CONNECTING, WRITE_REQUEST, READ_HEADER, READ_RESPONSE, IDLE }; + ClientConnectionState _connectionState; + + // Server location + std::string _host; + std::string _uri; + int _port; + + // The xml-encoded request, http header of response, and response xml + std::string _request; + std::string _header; + std::string _response; + + // Number of times the client has attempted to send the request + int _sendAttempts; + + // Number of bytes of the request that have been written to the socket so far + int _bytesWritten; + + // True if we are currently executing a request. If you want to multithread, + // each thread should have its own client. + bool _executing; + + // True if the server closed the connection + bool _eof; + + // True if a fault response was returned by the server + bool _isFault; + + // Number of bytes expected in the response body (parsed from response header) + int _contentLength; + + // Event dispatcher + XmlRpcDispatch _disp; + + }; // class XmlRpcClient + +} // namespace XmlRpc + +#endif // _XMLRPCCLIENT_H_ diff --git a/xmlrpc++/src/XmlRpcDispatch.cpp b/xmlrpc++/src/XmlRpcDispatch.cpp new file mode 100644 index 0000000..3bbca40 --- /dev/null +++ b/xmlrpc++/src/XmlRpcDispatch.cpp @@ -0,0 +1,209 @@ + +#include "XmlRpcDispatch.h" +#include "XmlRpcSource.h" +#include "XmlRpcUtil.h" + +#include +#include + +#if defined(_WINDOWS) +# include + +# define USE_FTIME +# if defined(_MSC_VER) +# define timeb _timeb +# define ftime _ftime +# endif +#else +# include +#endif // _WINDOWS + + +using namespace XmlRpc; + + +XmlRpcDispatch::XmlRpcDispatch() +{ + _endTime = -1.0; + _doClear = false; + _inWork = false; +} + + +XmlRpcDispatch::~XmlRpcDispatch() +{ +} + +// Monitor this source for the specified events and call its event handler +// when the event occurs +void +XmlRpcDispatch::addSource(XmlRpcSource* source, unsigned mask) +{ + _sources.push_back(MonitoredSource(source, mask)); +} + +// Stop monitoring this source. Does not close the source. +void +XmlRpcDispatch::removeSource(XmlRpcSource* source) +{ + for (SourceList::iterator it=_sources.begin(); it!=_sources.end(); ++it) + if (it->getSource() == source) + { + _sources.erase(it); + break; + } +} + + +// Modify the types of events to watch for on this source +void +XmlRpcDispatch::setSourceEvents(XmlRpcSource* source, unsigned eventMask) +{ + for (SourceList::iterator it=_sources.begin(); it!=_sources.end(); ++it) + if (it->getSource() == source) + { + it->getMask() = eventMask; + break; + } +} + + + +// Watch current set of sources and process events +void +XmlRpcDispatch::work(double timeout) +{ + // Compute end time + _endTime = (timeout < 0.0) ? -1.0 : (getTime() + timeout); + _doClear = false; + _inWork = true; + + // Only work while there is something to monitor + while (_sources.size() > 0) { + + // Construct the sets of descriptors we are interested in + fd_set inFd, outFd, excFd; + FD_ZERO(&inFd); + FD_ZERO(&outFd); + FD_ZERO(&excFd); + + int maxFd = -1; // Not used on windows + SourceList::iterator it; + for (it=_sources.begin(); it!=_sources.end(); ++it) { + int fd = it->getSource()->getfd(); + if (it->getMask() & ReadableEvent) FD_SET(fd, &inFd); + if (it->getMask() & WritableEvent) FD_SET(fd, &outFd); + if (it->getMask() & Exception) FD_SET(fd, &excFd); + if (it->getMask() && fd > maxFd) maxFd = fd; + } + + // Check for events + int nEvents; + if (timeout < 0.0) + nEvents = select(maxFd+1, &inFd, &outFd, &excFd, NULL); + else + { + struct timeval tv; + tv.tv_sec = (int)floor(timeout); + tv.tv_usec = ((int)floor(1000000.0 * (timeout-floor(timeout)))) % 1000000; + nEvents = select(maxFd+1, &inFd, &outFd, &excFd, &tv); + } + + if (nEvents < 0) + { + XmlRpcUtil::error("Error in XmlRpcDispatch::work: error in select (%d).", nEvents); + _inWork = false; + return; + } + + // Process events + for (it=_sources.begin(); it != _sources.end(); ) + { + SourceList::iterator thisIt = it++; + XmlRpcSource* src = thisIt->getSource(); + int fd = src->getfd(); + unsigned newMask = (unsigned) -1; + if (fd <= maxFd) { + // If you select on multiple event types this could be ambiguous + if (FD_ISSET(fd, &inFd)) + newMask &= src->handleEvent(ReadableEvent); + if (FD_ISSET(fd, &outFd)) + newMask &= src->handleEvent(WritableEvent); + if (FD_ISSET(fd, &excFd)) + newMask &= src->handleEvent(Exception); + + if ( ! newMask) { + _sources.erase(thisIt); // Stop monitoring this one + if ( ! src->getKeepOpen()) + src->close(); + } else if (newMask != (unsigned) -1) { + thisIt->getMask() = newMask; + } + } + } + + // Check whether to clear all sources + if (_doClear) + { + SourceList closeList = _sources; + _sources.clear(); + for (SourceList::iterator it=closeList.begin(); it!=closeList.end(); ++it) { + XmlRpcSource *src = it->getSource(); + src->close(); + } + + _doClear = false; + } + + // Check whether end time has passed + if (0 <= _endTime && getTime() > _endTime) + break; + } + + _inWork = false; +} + + +// Exit from work routine. Presumably this will be called from +// one of the source event handlers. +void +XmlRpcDispatch::exit() +{ + _endTime = 0.0; // Return from work asap +} + +// Clear all sources from the monitored sources list +void +XmlRpcDispatch::clear() +{ + if (_inWork) + _doClear = true; // Finish reporting current events before clearing + else + { + SourceList closeList = _sources; + _sources.clear(); + for (SourceList::iterator it=closeList.begin(); it!=closeList.end(); ++it) + it->getSource()->close(); + } +} + + +double +XmlRpcDispatch::getTime() +{ +#ifdef USE_FTIME + struct timeb tbuff; + + ftime(&tbuff); + return ((double) tbuff.time + ((double)tbuff.millitm / 1000.0) + + ((double) tbuff.timezone * 60)); +#else + struct timeval tv; + struct timezone tz; + + gettimeofday(&tv, &tz); + return (tv.tv_sec + tv.tv_usec / 1000000.0); +#endif /* USE_FTIME */ +} + + diff --git a/xmlrpc++/src/XmlRpcDispatch.h b/xmlrpc++/src/XmlRpcDispatch.h new file mode 100644 index 0000000..b3c4ec0 --- /dev/null +++ b/xmlrpc++/src/XmlRpcDispatch.h @@ -0,0 +1,88 @@ + +#ifndef _XMLRPCDISPATCH_H_ +#define _XMLRPCDISPATCH_H_ +// +// XmlRpc++ Copyright (c) 2002-2003 by Chris Morley +// +#if defined(_MSC_VER) +# pragma warning(disable:4786) // identifier was truncated in debug info +#endif + +#ifndef MAKEDEPEND +# include +#endif + +namespace XmlRpc { + + // An RPC source represents a file descriptor to monitor + class XmlRpcSource; + + //! An object which monitors file descriptors for events and performs + //! callbacks when interesting events happen. + class XmlRpcDispatch { + public: + //! Constructor + XmlRpcDispatch(); + ~XmlRpcDispatch(); + + //! Values indicating the type of events a source is interested in + enum EventType { + ReadableEvent = 1, //!< data available to read + WritableEvent = 2, //!< connected/data can be written without blocking + Exception = 4 //!< uh oh + }; + + //! Monitor this source for the event types specified by the event mask + //! and call its event handler when any of the events occur. + //! @param source The source to monitor + //! @param eventMask Which event types to watch for. \see EventType + void addSource(XmlRpcSource* source, unsigned eventMask); + + //! Stop monitoring this source. + //! @param source The source to stop monitoring + void removeSource(XmlRpcSource* source); + + //! Modify the types of events to watch for on this source + void setSourceEvents(XmlRpcSource* source, unsigned eventMask); + + + //! Watch current set of sources and process events for the specified + //! duration (in ms, -1 implies wait forever, or until exit is called) + void work(double msTime); + + //! Exit from work routine + void exit(); + + //! Clear all sources from the monitored sources list. Sources are closed. + void clear(); + + protected: + + // helper + double getTime(); + + // A source to monitor and what to monitor it for + struct MonitoredSource { + MonitoredSource(XmlRpcSource* src, unsigned mask) : _src(src), _mask(mask) {} + XmlRpcSource* getSource() const { return _src; } + unsigned& getMask() { return _mask; } + XmlRpcSource* _src; + unsigned _mask; + }; + + // A list of sources to monitor + typedef std::list< MonitoredSource > SourceList; + + // Sources being monitored + SourceList _sources; + + // When work should stop (-1 implies wait forever, or until exit is called) + double _endTime; + + bool _doClear; + bool _inWork; + + }; +} // namespace XmlRpc + +#endif // _XMLRPCDISPATCH_H_ diff --git a/xmlrpc++/src/XmlRpcException.h b/xmlrpc++/src/XmlRpcException.h new file mode 100644 index 0000000..6090450 --- /dev/null +++ b/xmlrpc++/src/XmlRpcException.h @@ -0,0 +1,42 @@ + +#ifndef _XMLRPCEXCEPTION_H_ +#define _XMLRPCEXCEPTION_H_ +// +// XmlRpc++ Copyright (c) 2002-2003 by Chris Morley +// +#if defined(_MSC_VER) +# pragma warning(disable:4786) // identifier was truncated in debug info +#endif + +#ifndef MAKEDEPEND +# include +#endif + + +namespace XmlRpc { + + //! A class representing an error. + //! If server methods throw this exception, a fault response is returned + //! to the client. + class XmlRpcException { + public: + //! Constructor + //! @param message A descriptive error message + //! @param code An integer error code + XmlRpcException(const std::string& message, int code=-1) : + _message(message), _code(code) {} + + //! Return the error message. + const std::string& getMessage() const { return _message; } + + //! Return the error code. + int getCode() const { return _code; } + + private: + std::string _message; + int _code; + }; + +} + +#endif // _XMLRPCEXCEPTION_H_ diff --git a/xmlrpc++/src/XmlRpcServer.cpp b/xmlrpc++/src/XmlRpcServer.cpp new file mode 100644 index 0000000..f6b4aa5 --- /dev/null +++ b/xmlrpc++/src/XmlRpcServer.cpp @@ -0,0 +1,284 @@ + +#include "XmlRpcServer.h" +#include "XmlRpcServerConnection.h" +#include "XmlRpcServerMethod.h" +#include "XmlRpcSocket.h" +#include "XmlRpcUtil.h" +#include "XmlRpcException.h" + + +using namespace XmlRpc; + + +XmlRpcServer::XmlRpcServer() +{ + _introspectionEnabled = false; + _listMethods = 0; + _methodHelp = 0; +} + + +XmlRpcServer::~XmlRpcServer() +{ + this->shutdown(); + _methods.clear(); + delete _listMethods; + delete _methodHelp; +} + + +// Add a command to the RPC server +void +XmlRpcServer::addMethod(XmlRpcServerMethod* method) +{ + _methods[method->name()] = method; +} + +// Remove a command from the RPC server +void +XmlRpcServer::removeMethod(XmlRpcServerMethod* method) +{ + MethodMap::iterator i = _methods.find(method->name()); + if (i != _methods.end()) + _methods.erase(i); +} + +// Remove a command from the RPC server by name +void +XmlRpcServer::removeMethod(const std::string& methodName) +{ + MethodMap::iterator i = _methods.find(methodName); + if (i != _methods.end()) + _methods.erase(i); +} + + +// Look up a method by name +XmlRpcServerMethod* +XmlRpcServer::findMethod(const std::string& name) const +{ + MethodMap::const_iterator i = _methods.find(name); + if (i == _methods.end()) + return 0; + return i->second; +} + + +// Create a socket, bind to the specified port, and +// set it in listen mode to make it available for clients. +bool +XmlRpcServer::bindAndListen(int port, int backlog /*= 5*/) +{ + int fd = XmlRpcSocket::socket(); + if (fd < 0) + { + XmlRpcUtil::error("XmlRpcServer::bindAndListen: Could not create socket (%s).", XmlRpcSocket::getErrorMsg().c_str()); + return false; + } + + this->setfd(fd); + + // Don't block on reads/writes + if ( ! XmlRpcSocket::setNonBlocking(fd)) + { + this->close(); + XmlRpcUtil::error("XmlRpcServer::bindAndListen: Could not set socket to non-blocking input mode (%s).", XmlRpcSocket::getErrorMsg().c_str()); + return false; + } + + // Allow this port to be re-bound immediately so server re-starts are not delayed + if ( ! XmlRpcSocket::setReuseAddr(fd)) + { + this->close(); + XmlRpcUtil::error("XmlRpcServer::bindAndListen: Could not set SO_REUSEADDR socket option (%s).", XmlRpcSocket::getErrorMsg().c_str()); + return false; + } + + // Bind to the specified port on the default interface + if ( ! XmlRpcSocket::bind(fd, port)) + { + this->close(); + XmlRpcUtil::error("XmlRpcServer::bindAndListen: Could not bind to specified port (%s).", XmlRpcSocket::getErrorMsg().c_str()); + return false; + } + + // Set in listening mode + if ( ! XmlRpcSocket::listen(fd, backlog)) + { + this->close(); + XmlRpcUtil::error("XmlRpcServer::bindAndListen: Could not set socket in listening mode (%s).", XmlRpcSocket::getErrorMsg().c_str()); + return false; + } + + XmlRpcUtil::log(2, "XmlRpcServer::bindAndListen: server listening on port %d fd %d", port, fd); + + // Notify the dispatcher to listen on this source when we are in work() + _disp.addSource(this, XmlRpcDispatch::ReadableEvent); + + return true; +} + + +// Process client requests for the specified time +void +XmlRpcServer::work(double msTime) +{ + XmlRpcUtil::log(2, "XmlRpcServer::work: waiting for a connection"); + _disp.work(msTime); +} + + + +// Handle input on the server socket by accepting the connection +// and reading the rpc request. +unsigned +XmlRpcServer::handleEvent(unsigned mask) +{ + acceptConnection(); + return XmlRpcDispatch::ReadableEvent; // Continue to monitor this fd +} + + +// Accept a client connection request and create a connection to +// handle method calls from the client. +void +XmlRpcServer::acceptConnection() +{ + int s = XmlRpcSocket::accept(this->getfd()); + XmlRpcUtil::log(2, "XmlRpcServer::acceptConnection: socket %d", s); + if (s < 0) + { + //this->close(); + XmlRpcUtil::error("XmlRpcServer::acceptConnection: Could not accept connection (%s).", XmlRpcSocket::getErrorMsg().c_str()); + } + else if ( ! XmlRpcSocket::setNonBlocking(s)) + { + XmlRpcSocket::close(s); + XmlRpcUtil::error("XmlRpcServer::acceptConnection: Could not set socket to non-blocking input mode (%s).", XmlRpcSocket::getErrorMsg().c_str()); + } + else // Notify the dispatcher to listen for input on this source when we are in work() + { + XmlRpcUtil::log(2, "XmlRpcServer::acceptConnection: creating a connection"); + _disp.addSource(this->createConnection(s), XmlRpcDispatch::ReadableEvent); + } +} + + +// Create a new connection object for processing requests from a specific client. +XmlRpcServerConnection* +XmlRpcServer::createConnection(int s) +{ + // Specify that the connection object be deleted when it is closed + return new XmlRpcServerConnection(s, this, true); +} + + +void +XmlRpcServer::removeConnection(XmlRpcServerConnection* sc) +{ + _disp.removeSource(sc); +} + + +// Stop processing client requests +void +XmlRpcServer::exit() +{ + _disp.exit(); +} + + +// Close the server socket file descriptor and stop monitoring connections +void +XmlRpcServer::shutdown() +{ + // This closes and destroys all connections as well as closing this socket + _disp.clear(); +} + + +// Introspection support +static const std::string LIST_METHODS("system.listMethods"); +static const std::string METHOD_HELP("system.methodHelp"); +static const std::string MULTICALL("system.multicall"); + + +// List all methods available on a server +class ListMethods : public XmlRpcServerMethod +{ +public: + ListMethods(XmlRpcServer* s) : XmlRpcServerMethod(LIST_METHODS, s) {} + + void execute(XmlRpcValue& params, XmlRpcValue& result) + { + _server->listMethods(result); + } + + std::string help() { return std::string("List all methods available on a server as an array of strings"); } +}; + + +// Retrieve the help string for a named method +class MethodHelp : public XmlRpcServerMethod +{ +public: + MethodHelp(XmlRpcServer* s) : XmlRpcServerMethod(METHOD_HELP, s) {} + + void execute(XmlRpcValue& params, XmlRpcValue& result) + { + if (params[0].getType() != XmlRpcValue::TypeString) + throw XmlRpcException(METHOD_HELP + ": Invalid argument type"); + + XmlRpcServerMethod* m = _server->findMethod(params[0]); + if ( ! m) + throw XmlRpcException(METHOD_HELP + ": Unknown method name"); + + result = m->help(); + } + + std::string help() { return std::string("Retrieve the help string for a named method"); } +}; + + +// Specify whether introspection is enabled or not. Default is enabled. +void +XmlRpcServer::enableIntrospection(bool enabled) +{ + if (_introspectionEnabled == enabled) + return; + + _introspectionEnabled = enabled; + + if (enabled) + { + if ( ! _listMethods) + { + _listMethods = new ListMethods(this); + _methodHelp = new MethodHelp(this); + } else { + addMethod(_listMethods); + addMethod(_methodHelp); + } + } + else + { + removeMethod(LIST_METHODS); + removeMethod(METHOD_HELP); + } +} + + +void +XmlRpcServer::listMethods(XmlRpcValue& result) +{ + int i = 0; + result.setSize(_methods.size()+1); + for (MethodMap::iterator it=_methods.begin(); it != _methods.end(); ++it) + result[i++] = it->first; + + // Multicall support is built into XmlRpcServerConnection + result[i] = MULTICALL; +} + + + diff --git a/xmlrpc++/src/XmlRpcServer.h b/xmlrpc++/src/XmlRpcServer.h new file mode 100644 index 0000000..8172733 --- /dev/null +++ b/xmlrpc++/src/XmlRpcServer.h @@ -0,0 +1,104 @@ + +#ifndef _XMLRPCSERVER_H_ +#define _XMLRPCSERVER_H_ +// +// XmlRpc++ Copyright (c) 2002-2003 by Chris Morley +// +#if defined(_MSC_VER) +# pragma warning(disable:4786) // identifier was truncated in debug info +#endif + +#ifndef MAKEDEPEND +# include +# include +#endif + +#include "XmlRpcDispatch.h" +#include "XmlRpcSource.h" + +namespace XmlRpc { + + + // An abstract class supporting XML RPC methods + class XmlRpcServerMethod; + + // Class representing connections to specific clients + class XmlRpcServerConnection; + + // Class representing argument and result values + class XmlRpcValue; + + + //! A class to handle XML RPC requests + class XmlRpcServer : public XmlRpcSource { + public: + //! Create a server object. + XmlRpcServer(); + //! Destructor. + virtual ~XmlRpcServer(); + + //! Specify whether introspection is enabled or not. Default is not enabled. + void enableIntrospection(bool enabled=true); + + //! Add a command to the RPC server + void addMethod(XmlRpcServerMethod* method); + + //! Remove a command from the RPC server + void removeMethod(XmlRpcServerMethod* method); + + //! Remove a command from the RPC server by name + void removeMethod(const std::string& methodName); + + //! Look up a method by name + XmlRpcServerMethod* findMethod(const std::string& name) const; + + //! Create a socket, bind to the specified port, and + //! set it in listen mode to make it available for clients. + bool bindAndListen(int port, int backlog = 5); + + //! Process client requests for the specified time + void work(double msTime); + + //! Temporarily stop processing client requests and exit the work() method. + void exit(); + + //! Close all connections with clients and the socket file descriptor + void shutdown(); + + //! Introspection support + void listMethods(XmlRpcValue& result); + + // XmlRpcSource interface implementation + + //! Handle client connection requests + virtual unsigned handleEvent(unsigned eventType); + + //! Remove a connection from the dispatcher + virtual void removeConnection(XmlRpcServerConnection*); + + protected: + + //! Accept a client connection request + virtual void acceptConnection(); + + //! Create a new connection object for processing requests from a specific client. + virtual XmlRpcServerConnection* createConnection(int socket); + + // Whether the introspection API is supported by this server + bool _introspectionEnabled; + + // Event dispatcher + XmlRpcDispatch _disp; + + // Collection of methods. This could be a set keyed on method name if we wanted... + typedef std::map< std::string, XmlRpcServerMethod* > MethodMap; + MethodMap _methods; + + // system methods + XmlRpcServerMethod* _listMethods; + XmlRpcServerMethod* _methodHelp; + + }; +} // namespace XmlRpc + +#endif //_XMLRPCSERVER_H_ diff --git a/xmlrpc++/src/XmlRpcServerConnection.cpp b/xmlrpc++/src/XmlRpcServerConnection.cpp new file mode 100644 index 0000000..b9d6def --- /dev/null +++ b/xmlrpc++/src/XmlRpcServerConnection.cpp @@ -0,0 +1,371 @@ + +#include "XmlRpcServerConnection.h" + +#include "XmlRpcSocket.h" +#include "XmlRpc.h" +#ifndef MAKEDEPEND +# include +# include +#endif + +using namespace XmlRpc; + +// Static data +const char XmlRpcServerConnection::METHODNAME_TAG[] = ""; +const char XmlRpcServerConnection::PARAMS_TAG[] = ""; +const char XmlRpcServerConnection::PARAMS_ETAG[] = ""; +const char XmlRpcServerConnection::PARAM_TAG[] = ""; +const char XmlRpcServerConnection::PARAM_ETAG[] = ""; + +const std::string XmlRpcServerConnection::SYSTEM_MULTICALL = "system.multicall"; +const std::string XmlRpcServerConnection::METHODNAME = "methodName"; +const std::string XmlRpcServerConnection::PARAMS = "params"; + +const std::string XmlRpcServerConnection::FAULTCODE = "faultCode"; +const std::string XmlRpcServerConnection::FAULTSTRING = "faultString"; + + + +// The server delegates handling client requests to a serverConnection object. +XmlRpcServerConnection::XmlRpcServerConnection(int fd, XmlRpcServer* server, bool deleteOnClose /*= false*/) : + XmlRpcSource(fd, deleteOnClose) +{ + XmlRpcUtil::log(2,"XmlRpcServerConnection: new socket %d.", fd); + _server = server; + _connectionState = READ_HEADER; + _keepAlive = true; +} + + +XmlRpcServerConnection::~XmlRpcServerConnection() +{ + XmlRpcUtil::log(4,"XmlRpcServerConnection dtor."); + _server->removeConnection(this); +} + + +// Handle input on the server socket by accepting the connection +// and reading the rpc request. Return true to continue to monitor +// the socket for events, false to remove it from the dispatcher. +unsigned +XmlRpcServerConnection::handleEvent(unsigned /*eventType*/) +{ + if (_connectionState == READ_HEADER) + if ( ! readHeader()) return 0; + + if (_connectionState == READ_REQUEST) + if ( ! readRequest()) return 0; + + if (_connectionState == WRITE_RESPONSE) + if ( ! writeResponse()) return 0; + + return (_connectionState == WRITE_RESPONSE) + ? XmlRpcDispatch::WritableEvent : XmlRpcDispatch::ReadableEvent; +} + + +bool +XmlRpcServerConnection::readHeader() +{ + // Read available data + bool eof; + if ( ! XmlRpcSocket::nbRead(this->getfd(), _header, &eof)) { + // Its only an error if we already have read some data + if (_header.length() > 0) + XmlRpcUtil::error("XmlRpcServerConnection::readHeader: error while reading header (%s).",XmlRpcSocket::getErrorMsg().c_str()); + return false; + } + + XmlRpcUtil::log(4, "XmlRpcServerConnection::readHeader: read %d bytes.", _header.length()); + char *hp = (char*)_header.c_str(); // Start of header + char *ep = hp + _header.length(); // End of string + char *bp = 0; // Start of body + char *lp = 0; // Start of content-length value + char *kp = 0; // Start of connection value + + for (char *cp = hp; (bp == 0) && (cp < ep); ++cp) { + if ((ep - cp > 16) && (strncasecmp(cp, "Content-length: ", 16) == 0)) + lp = cp + 16; + else if ((ep - cp > 12) && (strncasecmp(cp, "Connection: ", 12) == 0)) + kp = cp + 12; + else if ((ep - cp > 4) && (strncmp(cp, "\r\n\r\n", 4) == 0)) + bp = cp + 4; + else if ((ep - cp > 2) && (strncmp(cp, "\n\n", 2) == 0)) + bp = cp + 2; + } + + // If we haven't gotten the entire header yet, return (keep reading) + if (bp == 0) { + // EOF in the middle of a request is an error, otherwise its ok + if (eof) { + XmlRpcUtil::log(4, "XmlRpcServerConnection::readHeader: EOF"); + if (_header.length() > 0) + XmlRpcUtil::error("XmlRpcServerConnection::readHeader: EOF while reading header"); + return false; // Either way we close the connection + } + + return true; // Keep reading + } + + // Decode content length + if (lp == 0) { + XmlRpcUtil::error("XmlRpcServerConnection::readHeader: No Content-length specified"); + return false; // We could try to figure it out by parsing as we read, but for now... + } + + _contentLength = atoi(lp); + if (_contentLength <= 0) { + XmlRpcUtil::error("XmlRpcServerConnection::readHeader: Invalid Content-length specified (%d).", _contentLength); + return false; + } + + XmlRpcUtil::log(3, "XmlRpcServerConnection::readHeader: specified content length is %d.", _contentLength); + + // Otherwise copy non-header data to request buffer and set state to read request. + _request = bp; + + // Parse out any interesting bits from the header (HTTP version, connection) + _keepAlive = true; + if (_header.find("HTTP/1.0") != std::string::npos) { + if (kp == 0 || strncasecmp(kp, "keep-alive", 10) != 0) + _keepAlive = false; // Default for HTTP 1.0 is to close the connection + } else { + if (kp != 0 && strncasecmp(kp, "close", 5) == 0) + _keepAlive = false; + } + XmlRpcUtil::log(3, "KeepAlive: %d", _keepAlive); + + + _header = ""; + _connectionState = READ_REQUEST; + return true; // Continue monitoring this source +} + +bool +XmlRpcServerConnection::readRequest() +{ + // If we dont have the entire request yet, read available data + if (int(_request.length()) < _contentLength) { + bool eof; + if ( ! XmlRpcSocket::nbRead(this->getfd(), _request, &eof)) { + XmlRpcUtil::error("XmlRpcServerConnection::readRequest: read error (%s).",XmlRpcSocket::getErrorMsg().c_str()); + return false; + } + + // If we haven't gotten the entire request yet, return (keep reading) + if (int(_request.length()) < _contentLength) { + if (eof) { + XmlRpcUtil::error("XmlRpcServerConnection::readRequest: EOF while reading request"); + return false; // Either way we close the connection + } + return true; + } + } + + // Otherwise, parse and dispatch the request + XmlRpcUtil::log(3, "XmlRpcServerConnection::readRequest read %d bytes.", _request.length()); + //XmlRpcUtil::log(5, "XmlRpcServerConnection::readRequest:\n%s\n", _request.c_str()); + + _connectionState = WRITE_RESPONSE; + + return true; // Continue monitoring this source +} + + +bool +XmlRpcServerConnection::writeResponse() +{ + if (_response.length() == 0) { + executeRequest(); + _bytesWritten = 0; + if (_response.length() == 0) { + XmlRpcUtil::error("XmlRpcServerConnection::writeResponse: empty response."); + return false; + } + } + + // Try to write the response + if ( ! XmlRpcSocket::nbWrite(this->getfd(), _response, &_bytesWritten)) { + XmlRpcUtil::error("XmlRpcServerConnection::writeResponse: write error (%s).",XmlRpcSocket::getErrorMsg().c_str()); + return false; + } + XmlRpcUtil::log(3, "XmlRpcServerConnection::writeResponse: wrote %d of %d bytes.", _bytesWritten, _response.length()); + + // Prepare to read the next request + if (_bytesWritten == int(_response.length())) { + _header = ""; + _request = ""; + _response = ""; + _connectionState = READ_HEADER; + } + + return _keepAlive; // Continue monitoring this source if true +} + +// Run the method, generate _response string +void +XmlRpcServerConnection::executeRequest() +{ + XmlRpcValue params, resultValue; + std::string methodName = parseRequest(params); + XmlRpcUtil::log(2, "XmlRpcServerConnection::executeRequest: server calling method '%s'", + methodName.c_str()); + + try { + + if ( ! executeMethod(methodName, params, resultValue) && + ! executeMulticall(methodName, params, resultValue)) + generateFaultResponse(methodName + ": unknown method name"); + else + generateResponse(resultValue.toXml()); + + } catch (const XmlRpcException& fault) { + XmlRpcUtil::log(2, "XmlRpcServerConnection::executeRequest: fault %s.", + fault.getMessage().c_str()); + generateFaultResponse(fault.getMessage(), fault.getCode()); + } +} + +// Parse the method name and the argument values from the request. +std::string +XmlRpcServerConnection::parseRequest(XmlRpcValue& params) +{ + int offset = 0; // Number of chars parsed from the request + + std::string methodName = XmlRpcUtil::parseTag(METHODNAME_TAG, _request, &offset); + + if (methodName.size() > 0 && XmlRpcUtil::findTag(PARAMS_TAG, _request, &offset)) + { + int nArgs = 0; + while (XmlRpcUtil::nextTagIs(PARAM_TAG, _request, &offset)) { + params[nArgs++] = XmlRpcValue(_request, &offset); + (void) XmlRpcUtil::nextTagIs(PARAM_ETAG, _request, &offset); + } + + (void) XmlRpcUtil::nextTagIs(PARAMS_ETAG, _request, &offset); + } + + return methodName; +} + +// Execute a named method with the specified params. +bool +XmlRpcServerConnection::executeMethod(const std::string& methodName, + XmlRpcValue& params, XmlRpcValue& result) +{ + XmlRpcServerMethod* method = _server->findMethod(methodName); + + if ( ! method) return false; + + method->execute(params, result); + + // Ensure a valid result value + if ( ! result.valid()) + result = std::string(); + + return true; +} + +// Execute multiple calls and return the results in an array. +bool +XmlRpcServerConnection::executeMulticall(const std::string& methodName, + XmlRpcValue& params, XmlRpcValue& result) +{ + if (methodName != SYSTEM_MULTICALL) return false; + + // There ought to be 1 parameter, an array of structs + if (params.size() != 1 || params[0].getType() != XmlRpcValue::TypeArray) + throw XmlRpcException(SYSTEM_MULTICALL + ": Invalid argument (expected an array)"); + + int nc = params[0].size(); + result.setSize(nc); + + for (int i=0; i\r\n" + "\r\n\t"; + const char RESPONSE_2[] = + "\r\n\r\n"; + + std::string body = RESPONSE_1 + resultXml + RESPONSE_2; + std::string header = generateHeader(body); + + _response = header + body; + XmlRpcUtil::log(5, "XmlRpcServerConnection::generateResponse:\n%s\n", _response.c_str()); +} + +// Prepend http headers +std::string +XmlRpcServerConnection::generateHeader(std::string const& body) +{ + std::string header = + "HTTP/1.1 200 OK\r\n" + "Server: "; + header += XMLRPC_VERSION; + header += "\r\n" + "Content-Type: text/xml\r\n" + "Content-length: "; + + char buffLen[40]; + sprintf(buffLen,"%d\r\n\r\n", body.size()); + + return header + buffLen; +} + + +void +XmlRpcServerConnection::generateFaultResponse(std::string const& errorMsg, int errorCode) +{ + const char RESPONSE_1[] = + "\r\n" + "\r\n\t"; + const char RESPONSE_2[] = + "\r\n\r\n"; + + XmlRpcValue faultStruct; + faultStruct[FAULTCODE] = errorCode; + faultStruct[FAULTSTRING] = errorMsg; + std::string body = RESPONSE_1 + faultStruct.toXml() + RESPONSE_2; + std::string header = generateHeader(body); + + _response = header + body; +} + diff --git a/xmlrpc++/src/XmlRpcServerConnection.h b/xmlrpc++/src/XmlRpcServerConnection.h new file mode 100644 index 0000000..9efbbaf --- /dev/null +++ b/xmlrpc++/src/XmlRpcServerConnection.h @@ -0,0 +1,102 @@ +#ifndef _XMLRPCSERVERCONNECTION_H_ +#define _XMLRPCSERVERCONNECTION_H_ +// +// XmlRpc++ Copyright (c) 2002-2003 by Chris Morley +// +#if defined(_MSC_VER) +# pragma warning(disable:4786) // identifier was truncated in debug info +#endif + +#ifndef MAKEDEPEND +# include +#endif + +#include "XmlRpcValue.h" +#include "XmlRpcSource.h" + +namespace XmlRpc { + + + // The server waits for client connections and provides methods + class XmlRpcServer; + class XmlRpcServerMethod; + + //! A class to handle XML RPC requests from a particular client + class XmlRpcServerConnection : public XmlRpcSource { + public: + // Static data + static const char METHODNAME_TAG[]; + static const char PARAMS_TAG[]; + static const char PARAMS_ETAG[]; + static const char PARAM_TAG[]; + static const char PARAM_ETAG[]; + + static const std::string SYSTEM_MULTICALL; + static const std::string METHODNAME; + static const std::string PARAMS; + + static const std::string FAULTCODE; + static const std::string FAULTSTRING; + + //! Constructor + XmlRpcServerConnection(int fd, XmlRpcServer* server, bool deleteOnClose = false); + //! Destructor + virtual ~XmlRpcServerConnection(); + + // XmlRpcSource interface implementation + //! Handle IO on the client connection socket. + //! @param eventType Type of IO event that occurred. @see XmlRpcDispatch::EventType. + virtual unsigned handleEvent(unsigned eventType); + + protected: + + bool readHeader(); + bool readRequest(); + bool writeResponse(); + + // Parses the request, runs the method, generates the response xml. + virtual void executeRequest(); + + // Parse the methodName and parameters from the request. + std::string parseRequest(XmlRpcValue& params); + + // Execute a named method with the specified params. + bool executeMethod(const std::string& methodName, XmlRpcValue& params, XmlRpcValue& result); + + // Execute multiple calls and return the results in an array. + bool executeMulticall(const std::string& methodName, XmlRpcValue& params, XmlRpcValue& result); + + // Construct a response from the result XML. + void generateResponse(std::string const& resultXml); + void generateFaultResponse(std::string const& msg, int errorCode = -1); + std::string generateHeader(std::string const& body); + + + // The XmlRpc server that accepted this connection + XmlRpcServer* _server; + + // Possible IO states for the connection + enum ServerConnectionState { READ_HEADER, READ_REQUEST, WRITE_RESPONSE }; + ServerConnectionState _connectionState; + + // Request headers + std::string _header; + + // Number of bytes expected in the request body (parsed from header) + int _contentLength; + + // Request body + std::string _request; + + // Response + std::string _response; + + // Number of bytes of the response written so far + int _bytesWritten; + + // Whether to keep the current client connection open for further requests + bool _keepAlive; + }; +} // namespace XmlRpc + +#endif // _XMLRPCSERVERCONNECTION_H_ diff --git a/xmlrpc++/src/XmlRpcServerMethod.cpp b/xmlrpc++/src/XmlRpcServerMethod.cpp new file mode 100644 index 0000000..1616ff4 --- /dev/null +++ b/xmlrpc++/src/XmlRpcServerMethod.cpp @@ -0,0 +1,21 @@ + +#include "XmlRpcServerMethod.h" +#include "XmlRpcServer.h" + +namespace XmlRpc { + + + XmlRpcServerMethod::XmlRpcServerMethod(std::string const& name, XmlRpcServer* server) + { + _name = name; + _server = server; + if (_server) _server->addMethod(this); + } + + XmlRpcServerMethod::~XmlRpcServerMethod() + { + if (_server) _server->removeMethod(this); + } + + +} // namespace XmlRpc diff --git a/xmlrpc++/src/XmlRpcServerMethod.h b/xmlrpc++/src/XmlRpcServerMethod.h new file mode 100644 index 0000000..738a9c8 --- /dev/null +++ b/xmlrpc++/src/XmlRpcServerMethod.h @@ -0,0 +1,47 @@ + +#ifndef _XMLRPCSERVERMETHOD_H_ +#define _XMLRPCSERVERMETHOD_H_ +// +// XmlRpc++ Copyright (c) 2002-2003 by Chris Morley +// +#if defined(_MSC_VER) +# pragma warning(disable:4786) // identifier was truncated in debug info +#endif + +#ifndef MAKEDEPEND +# include +#endif + +namespace XmlRpc { + + // Representation of a parameter or result value + class XmlRpcValue; + + // The XmlRpcServer processes client requests to call RPCs + class XmlRpcServer; + + //! Abstract class representing a single RPC method + class XmlRpcServerMethod { + public: + //! Constructor + XmlRpcServerMethod(std::string const& name, XmlRpcServer* server = 0); + //! Destructor + virtual ~XmlRpcServerMethod(); + + //! Returns the name of the method + std::string& name() { return _name; } + + //! Execute the method. Subclasses must provide a definition for this method. + virtual void execute(XmlRpcValue& params, XmlRpcValue& result) = 0; + + //! Returns a help string for the method. + //! Subclasses should define this method if introspection is being used. + virtual std::string help() { return std::string(); } + + protected: + std::string _name; + XmlRpcServer* _server; + }; +} // namespace XmlRpc + +#endif // _XMLRPCSERVERMETHOD_H_ diff --git a/xmlrpc++/src/XmlRpcSocket.cpp b/xmlrpc++/src/XmlRpcSocket.cpp new file mode 100644 index 0000000..b71ef94 --- /dev/null +++ b/xmlrpc++/src/XmlRpcSocket.cpp @@ -0,0 +1,260 @@ + +#include "XmlRpcSocket.h" +#include "XmlRpcUtil.h" + +#ifndef MAKEDEPEND + +#if defined(_WINDOWS) +# include +# include +//# pragma lib(WS2_32.lib) + +# define EINPROGRESS WSAEINPROGRESS +# define EWOULDBLOCK WSAEWOULDBLOCK +# define ETIMEDOUT WSAETIMEDOUT +#else +extern "C" { +# include +# include +# include +# include +# include +# include +# include +# include +} +#endif // _WINDOWS + +#endif // MAKEDEPEND + + +using namespace XmlRpc; + + + +#if defined(_WINDOWS) + +static void initWinSock() +{ + static bool wsInit = false; + if (! wsInit) + { + WORD wVersionRequested = MAKEWORD( 2, 0 ); + WSADATA wsaData; + WSAStartup(wVersionRequested, &wsaData); + wsInit = true; + } +} + +#else + +#define initWinSock() + +#endif // _WINDOWS + + +// These errors are not considered fatal for an IO operation; the operation will be re-tried. +static inline bool +nonFatalError() +{ + int err = XmlRpcSocket::getError(); + return (err == EINPROGRESS || err == EAGAIN || err == EWOULDBLOCK || err == EINTR); +} + + + +int +XmlRpcSocket::socket() +{ + initWinSock(); + return (int) ::socket(AF_INET, SOCK_STREAM, 0); +} + + +void +XmlRpcSocket::close(int fd) +{ + XmlRpcUtil::log(4, "XmlRpcSocket::close: fd %d.", fd); +#if defined(_WINDOWS) + closesocket(fd); +#else + ::close(fd); +#endif // _WINDOWS +} + + + + +bool +XmlRpcSocket::setNonBlocking(int fd) +{ +#if defined(_WINDOWS) + unsigned long flag = 1; + return (ioctlsocket((SOCKET)fd, FIONBIO, &flag) == 0); +#else + return (fcntl(fd, F_SETFL, O_NONBLOCK) == 0); +#endif // _WINDOWS +} + + +bool +XmlRpcSocket::setReuseAddr(int fd) +{ + // Allow this port to be re-bound immediately so server re-starts are not delayed + int sflag = 1; + return (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&sflag, sizeof(sflag)) == 0); +} + + +// Bind to a specified port +bool +XmlRpcSocket::bind(int fd, int port) +{ + struct sockaddr_in saddr; + memset(&saddr, 0, sizeof(saddr)); + saddr.sin_family = AF_INET; + saddr.sin_addr.s_addr = htonl(INADDR_ANY); + saddr.sin_port = htons((u_short) port); + return (::bind(fd, (struct sockaddr *)&saddr, sizeof(saddr)) == 0); +} + + +// Set socket in listen mode +bool +XmlRpcSocket::listen(int fd, int backlog) +{ + return (::listen(fd, backlog) == 0); +} + + +int +XmlRpcSocket::accept(int fd) +{ + struct sockaddr_in addr; +#if defined(_WINDOWS) + int +#else + socklen_t +#endif + addrlen = sizeof(addr); + + return (int) ::accept(fd, (struct sockaddr*)&addr, &addrlen); +} + + + +// Connect a socket to a server (from a client) +bool +XmlRpcSocket::connect(int fd, std::string& host, int port) +{ + struct sockaddr_in saddr; + memset(&saddr, 0, sizeof(saddr)); + saddr.sin_family = AF_INET; + + struct hostent *hp = gethostbyname(host.c_str()); + if (hp == 0) return false; + + saddr.sin_family = hp->h_addrtype; + memcpy(&saddr.sin_addr, hp->h_addr, hp->h_length); + saddr.sin_port = htons((u_short) port); + + // For asynch operation, this will return EWOULDBLOCK (windows) or + // EINPROGRESS (linux) and we just need to wait for the socket to be writable... + int result = ::connect(fd, (struct sockaddr *)&saddr, sizeof(saddr)); + return result == 0 || nonFatalError(); +} + + + +// Read available text from the specified socket. Returns false on error. +bool +XmlRpcSocket::nbRead(int fd, std::string& s, bool *eof) +{ + const int READ_SIZE = 4096; // Number of bytes to attempt to read at a time + char readBuf[READ_SIZE]; + + bool wouldBlock = false; + *eof = false; + + while ( ! wouldBlock && ! *eof) { +#if defined(_WINDOWS) + int n = recv(fd, readBuf, READ_SIZE-1, 0); +#else + int n = read(fd, readBuf, READ_SIZE-1); +#endif + XmlRpcUtil::log(5, "XmlRpcSocket::nbRead: read/recv returned %d.", n); + + if (n > 0) { + readBuf[n] = 0; + s.append(readBuf, n); + } else if (n == 0) { + *eof = true; + } else if (nonFatalError()) { + wouldBlock = true; + } else { + return false; // Error + } + } + return true; +} + + +// Write text to the specified socket. Returns false on error. +bool +XmlRpcSocket::nbWrite(int fd, std::string& s, int *bytesSoFar) +{ + int nToWrite = int(s.length()) - *bytesSoFar; + char *sp = const_cast(s.c_str()) + *bytesSoFar; + bool wouldBlock = false; + + while ( nToWrite > 0 && ! wouldBlock ) { +#if defined(_WINDOWS) + int n = send(fd, sp, nToWrite, 0); +#else + int n = write(fd, sp, nToWrite); +#endif + XmlRpcUtil::log(5, "XmlRpcSocket::nbWrite: send/write returned %d.", n); + + if (n > 0) { + sp += n; + *bytesSoFar += n; + nToWrite -= n; + } else if (nonFatalError()) { + wouldBlock = true; + } else { + return false; // Error + } + } + return true; +} + + +// Returns last errno +int +XmlRpcSocket::getError() +{ +#if defined(_WINDOWS) + return WSAGetLastError(); +#else + return errno; +#endif +} + + +// Returns message corresponding to last errno +std::string +XmlRpcSocket::getErrorMsg() +{ + return getErrorMsg(getError()); +} + +// Returns message corresponding to errno... well, it should anyway +std::string +XmlRpcSocket::getErrorMsg(int error) +{ + char err[60]; + snprintf(err,sizeof(err),"error %d", error); + return std::string(err); +} + + diff --git a/xmlrpc++/src/XmlRpcSocket.h b/xmlrpc++/src/XmlRpcSocket.h new file mode 100644 index 0000000..fa7f950 --- /dev/null +++ b/xmlrpc++/src/XmlRpcSocket.h @@ -0,0 +1,69 @@ +#ifndef _XMLRPCSOCKET_H_ +#define _XMLRPCSOCKET_H_ +// +// XmlRpc++ Copyright (c) 2002-2003 by Chris Morley +// +#if defined(_MSC_VER) +# pragma warning(disable:4786) // identifier was truncated in debug info +#endif + +#ifndef MAKEDEPEND +# include +#endif + +namespace XmlRpc { + + //! A platform-independent socket API. + class XmlRpcSocket { + public: + + //! Creates a stream (TCP) socket. Returns -1 on failure. + static int socket(); + + //! Closes a socket. + static void close(int socket); + + + //! Sets a stream (TCP) socket to perform non-blocking IO. Returns false on failure. + static bool setNonBlocking(int socket); + + //! Read text from the specified socket. Returns false on error. + static bool nbRead(int socket, std::string& s, bool *eof); + + //! Write text to the specified socket. Returns false on error. + static bool nbWrite(int socket, std::string& s, int *bytesSoFar); + + + // The next four methods are appropriate for servers. + + //! Allow the port the specified socket is bound to to be re-bound immediately so + //! server re-starts are not delayed. Returns false on failure. + static bool setReuseAddr(int socket); + + //! Bind to a specified port + static bool bind(int socket, int port); + + //! Set socket in listen mode + static bool listen(int socket, int backlog); + + //! Accept a client connection request + static int accept(int socket); + + + //! Connect a socket to a server (from a client) + static bool connect(int socket, std::string& host, int port); + + + //! Returns last errno + static int getError(); + + //! Returns message corresponding to last error + static std::string getErrorMsg(); + + //! Returns message corresponding to error + static std::string getErrorMsg(int error); + }; + +} // namespace XmlRpc + +#endif diff --git a/xmlrpc++/src/XmlRpcSource.cpp b/xmlrpc++/src/XmlRpcSource.cpp new file mode 100644 index 0000000..99203b0 --- /dev/null +++ b/xmlrpc++/src/XmlRpcSource.cpp @@ -0,0 +1,35 @@ + +#include "XmlRpcSource.h" +#include "XmlRpcSocket.h" +#include "XmlRpcUtil.h" + +namespace XmlRpc { + + + XmlRpcSource::XmlRpcSource(int fd /*= -1*/, bool deleteOnClose /*= false*/) + : _fd(fd), _deleteOnClose(deleteOnClose), _keepOpen(false) + { + } + + XmlRpcSource::~XmlRpcSource() + { + } + + + void + XmlRpcSource::close() + { + if (_fd != -1) { + XmlRpcUtil::log(2,"XmlRpcSource::close: closing socket %d.", _fd); + XmlRpcSocket::close(_fd); + XmlRpcUtil::log(2,"XmlRpcSource::close: done closing socket %d.", _fd); + _fd = -1; + } + if (_deleteOnClose) { + XmlRpcUtil::log(2,"XmlRpcSource::close: deleting this"); + _deleteOnClose = false; + delete this; + } + } + +} // namespace XmlRpc diff --git a/xmlrpc++/src/XmlRpcSource.h b/xmlrpc++/src/XmlRpcSource.h new file mode 100644 index 0000000..135dce4 --- /dev/null +++ b/xmlrpc++/src/XmlRpcSource.h @@ -0,0 +1,55 @@ + +#ifndef _XMLRPCSOURCE_H_ +#define _XMLRPCSOURCE_H_ +// +// XmlRpc++ Copyright (c) 2002-2003 by Chris Morley +// +#if defined(_MSC_VER) +# pragma warning(disable:4786) // identifier was truncated in debug info +#endif + +namespace XmlRpc { + + //! An RPC source represents a file descriptor to monitor + class XmlRpcSource { + public: + //! Constructor + //! @param fd The socket file descriptor to monitor. + //! @param deleteOnClose If true, the object deletes itself when close is called. + XmlRpcSource(int fd = -1, bool deleteOnClose = false); + + //! Destructor + virtual ~XmlRpcSource(); + + //! Return the file descriptor being monitored. + int getfd() const { return _fd; } + //! Specify the file descriptor to monitor. + void setfd(int fd) { _fd = fd; } + + //! Return whether the file descriptor should be kept open if it is no longer monitored. + bool getKeepOpen() const { return _keepOpen; } + //! Specify whether the file descriptor should be kept open if it is no longer monitored. + void setKeepOpen(bool b=true) { _keepOpen = b; } + + //! Close the owned fd. If deleteOnClose was specified at construction, the object is deleted. + virtual void close(); + + //! Return true to continue monitoring this source + virtual unsigned handleEvent(unsigned eventType) = 0; + + private: + + // Socket. This should really be a SOCKET (an alias for unsigned int*) on windows... + int _fd; + + // In the server, a new source (XmlRpcServerConnection) is created + // for each connected client. When each connection is closed, the + // corresponding source object is deleted. + bool _deleteOnClose; + + // In the client, keep connections open if you intend to make multiple calls. + bool _keepOpen; + }; +} // namespace XmlRpc + +#endif //_XMLRPCSOURCE_H_ diff --git a/xmlrpc++/src/XmlRpcUtil.cpp b/xmlrpc++/src/XmlRpcUtil.cpp new file mode 100644 index 0000000..1bd583a --- /dev/null +++ b/xmlrpc++/src/XmlRpcUtil.cpp @@ -0,0 +1,250 @@ + +#include "XmlRpcUtil.h" + +#ifndef MAKEDEPEND +# include +# include +# include +# include +# include +#endif + +#include "XmlRpc.h" + +using namespace XmlRpc; + + +//#define USE_WINDOWS_DEBUG // To make the error and log messages go to VC++ debug output +#ifdef USE_WINDOWS_DEBUG +#define WIN32_LEAN_AND_MEAN +#include +#endif + +// Version id +const char XmlRpc::XMLRPC_VERSION[] = "XMLRPC++ 0.7"; + +// Default log verbosity: 0 for no messages through 5 (writes everything) +int XmlRpcLogHandler::_verbosity = 0; + +// Default log handler +static class DefaultLogHandler : public XmlRpcLogHandler { +public: + + void log(int level, const char* msg) { +#ifdef USE_WINDOWS_DEBUG + if (level <= _verbosity) { OutputDebugString(msg); OutputDebugString("\n"); } +#else + if (level <= _verbosity) std::cout << msg << std::endl; +#endif + } + +} defaultLogHandler; + +// Message log singleton +XmlRpcLogHandler* XmlRpcLogHandler::_logHandler = &defaultLogHandler; + + +// Default error handler +static class DefaultErrorHandler : public XmlRpcErrorHandler { +public: + + void error(const char* msg) { +#ifdef USE_WINDOWS_DEBUG + OutputDebugString(msg); OutputDebugString("\n"); +#else + std::cerr << msg << std::endl; +#endif + } +} defaultErrorHandler; + + +// Error handler singleton +XmlRpcErrorHandler* XmlRpcErrorHandler::_errorHandler = &defaultErrorHandler; + + +// Easy API for log verbosity +int XmlRpc::getVerbosity() { return XmlRpcLogHandler::getVerbosity(); } +void XmlRpc::setVerbosity(int level) { XmlRpcLogHandler::setVerbosity(level); } + + + +void XmlRpcUtil::log(int level, const char* fmt, ...) +{ + if (level <= XmlRpcLogHandler::getVerbosity()) + { + va_list va; + char buf[1024]; + va_start( va, fmt); + vsnprintf(buf,sizeof(buf)-1,fmt,va); + buf[sizeof(buf)-1] = 0; + XmlRpcLogHandler::getLogHandler()->log(level, buf); + } +} + + +void XmlRpcUtil::error(const char* fmt, ...) +{ + va_list va; + va_start(va, fmt); + char buf[1024]; + vsnprintf(buf,sizeof(buf)-1,fmt,va); + buf[sizeof(buf)-1] = 0; + XmlRpcErrorHandler::getErrorHandler()->error(buf); +} + + +// Returns contents between and , updates offset to char after +std::string +XmlRpcUtil::parseTag(const char* tag, std::string const& xml, int* offset) +{ + if (*offset >= int(xml.length())) return std::string(); + size_t istart = xml.find(tag, *offset); + if (istart == std::string::npos) return std::string(); + istart += strlen(tag); + std::string etag = "= int(xml.length())) return false; + size_t istart = xml.find(tag, *offset); + if (istart == std::string::npos) + return false; + + *offset = int(istart + strlen(tag)); + return true; +} + + +// Returns true if the tag is found at the specified offset (modulo any whitespace) +// and updates offset to the char after the tag +bool +XmlRpcUtil::nextTagIs(const char* tag, std::string const& xml, int* offset) +{ + if (*offset >= int(xml.length())) return false; + const char* cp = xml.c_str() + *offset; + int nc = 0; + while (*cp && isspace(*cp)) { + ++cp; + ++nc; + } + + int len = int(strlen(tag)); + if (*cp && (strncmp(cp, tag, len) == 0)) { + *offset += nc + len; + return true; + } + return false; +} + +// Returns the next tag and updates offset to the char after the tag, or empty string +// if the next non-whitespace character is not '<' +std::string +XmlRpcUtil::getNextTag(std::string const& xml, int* offset) +{ + if (*offset >= int(xml.length())) return std::string(); + + size_t pos = *offset; + const char* cp = xml.c_str() + pos; + while (*cp && isspace(*cp)) { + ++cp; + ++pos; + } + + if (*cp != '<') return std::string(); + + std::string s; + do { + s += *cp; + ++pos; + } while (*cp++ != '>' && *cp != 0); + + *offset = int(pos); + return s; +} + + + +// xml encodings (xml-encoded entities are preceded with '&') +static const char AMP = '&'; +static const char rawEntity[] = { '<', '>', '&', '\'', '\"', 0 }; +static const char* xmlEntity[] = { "lt;", "gt;", "amp;", "apos;", "quot;", 0 }; +static const int xmlEntLen[] = { 3, 3, 4, 5, 5 }; + + +// Replace xml-encoded entities with the raw text equivalents. + +std::string +XmlRpcUtil::xmlDecode(const std::string& encoded) +{ + std::string::size_type iAmp = encoded.find(AMP); + if (iAmp == std::string::npos) + return encoded; + + std::string decoded(encoded, 0, iAmp); + std::string::size_type iSize = encoded.size(); + decoded.reserve(iSize); + + const char* ens = encoded.c_str(); + while (iAmp != iSize) { + if (encoded[iAmp] == AMP && iAmp+1 < iSize) { + int iEntity; + for (iEntity=0; xmlEntity[iEntity] != 0; ++iEntity) + //if (encoded.compare(iAmp+1, xmlEntLen[iEntity], xmlEntity[iEntity]) == 0) + if (strncmp(ens+iAmp+1, xmlEntity[iEntity], xmlEntLen[iEntity]) == 0) + { + decoded += rawEntity[iEntity]; + iAmp += xmlEntLen[iEntity]+1; + break; + } + if (xmlEntity[iEntity] == 0) // unrecognized sequence + decoded += encoded[iAmp++]; + + } else { + decoded += encoded[iAmp++]; + } + } + + return decoded; +} + + +// Replace raw text with xml-encoded entities. + +std::string +XmlRpcUtil::xmlEncode(const std::string& raw) +{ + std::string::size_type iRep = raw.find_first_of(rawEntity); + if (iRep == std::string::npos) + return raw; + + std::string encoded(raw, 0, iRep); + std::string::size_type iSize = raw.size(); + + while (iRep != iSize) { + int iEntity; + for (iEntity=0; rawEntity[iEntity] != 0; ++iEntity) + if (raw[iRep] == rawEntity[iEntity]) + { + encoded += AMP; + encoded += xmlEntity[iEntity]; + break; + } + if (rawEntity[iEntity] == 0) + encoded += raw[iRep]; + ++iRep; + } + return encoded; +} + + + diff --git a/xmlrpc++/src/XmlRpcUtil.h b/xmlrpc++/src/XmlRpcUtil.h new file mode 100644 index 0000000..8128f72 --- /dev/null +++ b/xmlrpc++/src/XmlRpcUtil.h @@ -0,0 +1,61 @@ +#ifndef _XMLRPCUTIL_H_ +#define _XMLRPCUTIL_H_ +// +// XmlRpc++ Copyright (c) 2002-2003 by Chris Morley +// +#if defined(_MSC_VER) +# pragma warning(disable:4786) // identifier was truncated in debug info +#endif + +#ifndef MAKEDEPEND +# include +#endif + +#if defined(_MSC_VER) +# define snprintf _snprintf +# define vsnprintf _vsnprintf +# define strcasecmp _stricmp +# define strncasecmp _strnicmp +#elif defined(__BORLANDC__) +# define strcasecmp stricmp +# define strncasecmp strnicmp +#endif + +namespace XmlRpc { + + //! Utilities for XML parsing, encoding, and decoding and message handlers. + class XmlRpcUtil { + public: + // hokey xml parsing + //! Returns contents between and , updates offset to char after + static std::string parseTag(const char* tag, std::string const& xml, int* offset); + + //! Returns true if the tag is found and updates offset to the char after the tag + static bool findTag(const char* tag, std::string const& xml, int* offset); + + //! Returns the next tag and updates offset to the char after the tag, or empty string + //! if the next non-whitespace character is not '<' + static std::string getNextTag(std::string const& xml, int* offset); + + //! Returns true if the tag is found at the specified offset (modulo any whitespace) + //! and updates offset to the char after the tag + static bool nextTagIs(const char* tag, std::string const& xml, int* offset); + + + //! Convert raw text to encoded xml. + static std::string xmlEncode(const std::string& raw); + + //! Convert encoded xml to raw text + static std::string xmlDecode(const std::string& encoded); + + + //! Dump messages somewhere + static void log(int level, const char* fmt, ...); + + //! Dump error messages somewhere + static void error(const char* fmt, ...); + + }; +} // namespace XmlRpc + +#endif // _XMLRPCUTIL_H_ diff --git a/xmlrpc++/src/XmlRpcValue.cpp b/xmlrpc++/src/XmlRpcValue.cpp new file mode 100644 index 0000000..d6cf3f0 --- /dev/null +++ b/xmlrpc++/src/XmlRpcValue.cpp @@ -0,0 +1,611 @@ + +#include "XmlRpcValue.h" +#include "XmlRpcException.h" +#include "XmlRpcUtil.h" +#include "base64.h" + +#ifndef MAKEDEPEND +# include +# include +# include +# include +#endif + +namespace XmlRpc { + + + static const char VALUE_TAG[] = ""; + static const char VALUE_ETAG[] = ""; + + static const char BOOLEAN_TAG[] = ""; + static const char BOOLEAN_ETAG[] = ""; + static const char DOUBLE_TAG[] = ""; + static const char DOUBLE_ETAG[] = ""; + static const char INT_TAG[] = ""; + static const char I4_TAG[] = ""; + static const char I4_ETAG[] = ""; + static const char STRING_TAG[] = ""; + static const char DATETIME_TAG[] = ""; + static const char DATETIME_ETAG[] = ""; + static const char BASE64_TAG[] = ""; + static const char BASE64_ETAG[] = ""; + + static const char ARRAY_TAG[] = ""; + static const char DATA_TAG[] = ""; + static const char DATA_ETAG[] = ""; + static const char ARRAY_ETAG[] = ""; + + static const char STRUCT_TAG[] = ""; + static const char MEMBER_TAG[] = ""; + static const char NAME_TAG[] = ""; + static const char NAME_ETAG[] = ""; + static const char MEMBER_ETAG[] = ""; + static const char STRUCT_ETAG[] = ""; + + + + // 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 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 == 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 tag + else if (typeTag == VALUE_ETAG) + { + *offset = afterValueOffset; // back up & try again + result = stringFromXml(valueXml, offset); + } + + if (result) // Skip over the tag + XmlRpcUtil::findTag(VALUE_ETAG, valueXml, offset); + else // Unrecognized tag after + *offset = savedOffset; + + return result; + } + + // Encode the Value in xml + std::string XmlRpcValue::toXml() const + { + switch (_type) { + 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 + } + + + // 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]; + // this was fixed for windows use (frauenberger@iem.at) 12.10.2004 + snprintf(buf, sizeof(buf)-1, "%f" , _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 decoder; + std::back_insert_iterator 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 base64data; + int iostatus = 0; + base64 encoder; + std::back_insert_iterator > 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 + (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; iat(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 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 out(os); + base64 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 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); +} + diff --git a/xmlrpc++/src/XmlRpcValue.h b/xmlrpc++/src/XmlRpcValue.h new file mode 100644 index 0000000..7535d4a --- /dev/null +++ b/xmlrpc++/src/XmlRpcValue.h @@ -0,0 +1,189 @@ + +#ifndef _XMLRPCVALUE_H_ +#define _XMLRPCVALUE_H_ +// +// XmlRpc++ Copyright (c) 2002-2003 by Chris Morley +// +#if defined(_MSC_VER) +# pragma warning(disable:4786) // identifier was truncated in debug info +#endif + +#ifndef MAKEDEPEND +# include +# include +# include +# include +#endif + +namespace XmlRpc { + + //! RPC method arguments and results are represented by Values + // should probably refcount them... + class XmlRpcValue { + public: + + + enum Type { + TypeInvalid, + TypeBoolean, + TypeInt, + TypeDouble, + TypeString, + TypeDateTime, + TypeBase64, + TypeArray, + TypeStruct + }; + + // Non-primitive types + typedef std::vector BinaryData; + typedef std::vector ValueArray; + typedef std::map ValueStruct; + + + //! Constructors + XmlRpcValue() : _type(TypeInvalid) { _value.asBinary = 0; } + XmlRpcValue(bool value) : _type(TypeBoolean) { _value.asBool = value; } + XmlRpcValue(int value) : _type(TypeInt) { _value.asInt = value; } + XmlRpcValue(double value) : _type(TypeDouble) { _value.asDouble = value; } + + XmlRpcValue(std::string const& value) : _type(TypeString) + { _value.asString = new std::string(value); } + + XmlRpcValue(const char* value) : _type(TypeString) + { _value.asString = new std::string(value); } + + XmlRpcValue(struct tm* value) : _type(TypeDateTime) + { _value.asTime = new struct tm(*value); } + + + XmlRpcValue(void* value, int nBytes) : _type(TypeBase64) + { + _value.asBinary = new BinaryData((char*)value, ((char*)value)+nBytes); + } + + //! Construct from xml, beginning at *offset chars into the string, updates offset + XmlRpcValue(std::string const& xml, int* offset) : _type(TypeInvalid) + { if ( ! fromXml(xml,offset)) _type = TypeInvalid; } + + //! Copy + XmlRpcValue(XmlRpcValue const& rhs) : _type(TypeInvalid) { *this = rhs; } + + //! Destructor (make virtual if you want to subclass) + /*virtual*/ ~XmlRpcValue() { invalidate(); } + + //! Erase the current value + void clear() { invalidate(); } + + // Operators + XmlRpcValue& operator=(XmlRpcValue const& rhs); + XmlRpcValue& operator=(int const& rhs) { return operator=(XmlRpcValue(rhs)); } + XmlRpcValue& operator=(double const& rhs) { return operator=(XmlRpcValue(rhs)); } + XmlRpcValue& operator=(const char* rhs) { return operator=(XmlRpcValue(std::string(rhs))); } + + bool operator==(XmlRpcValue const& other) const; + bool operator!=(XmlRpcValue const& other) const; + + operator bool&() { assertTypeOrInvalid(TypeBoolean); return _value.asBool; } + operator int&() { assertTypeOrInvalid(TypeInt); return _value.asInt; } + operator double&() { assertTypeOrInvalid(TypeDouble); return _value.asDouble; } + operator std::string&() { assertTypeOrInvalid(TypeString); return *_value.asString; } + operator BinaryData&() { assertTypeOrInvalid(TypeBase64); return *_value.asBinary; } + operator struct tm&() { assertTypeOrInvalid(TypeDateTime); return *_value.asTime; } + + XmlRpcValue const& operator[](int i) const { assertArray(i+1); return _value.asArray->at(i); } + XmlRpcValue& operator[](int i) { assertArray(i+1); return _value.asArray->at(i); } + + XmlRpcValue& operator[](std::string const& k) { assertStruct(); return (*_value.asStruct)[k]; } + XmlRpcValue& operator[](const char* k) { assertStruct(); std::string s(k); return (*_value.asStruct)[s]; } + + // Accessors + //! Return true if the value has been set to something. + bool valid() const { return _type != TypeInvalid; } + + //! Return the type of the value stored. \see Type. + Type const &getType() const { return _type; } + + //! Return the size for string, base64, array, and struct values. + int size() const; + + //! Specify the size for array values. Array values will grow beyond this size if needed. + void setSize(int size) { assertArray(size); } + + //! Check for the existence of a struct member by name. + bool hasMember(const std::string& name) const; + + //! Decode xml. Destroys any existing value. + bool fromXml(std::string const& valueXml, int* offset); + + //! Encode the Value in xml + std::string toXml() const; + + //! Write the value (no xml encoding) + std::ostream& write(std::ostream& os) const; + + // Formatting + //! Return the format used to write double values. + static std::string const& getDoubleFormat() { return _doubleFormat; } + + //! Specify the format used to write double values. + static void setDoubleFormat(const char* f) { _doubleFormat = f; } + + + protected: + // Clean up + void invalidate(); + + // Type checking + void assertTypeOrInvalid(Type t); + void assertArray(int size) const; + void assertArray(int size); + void assertStruct(); + + // XML decoding + bool boolFromXml(std::string const& valueXml, int* offset); + bool intFromXml(std::string const& valueXml, int* offset); + bool doubleFromXml(std::string const& valueXml, int* offset); + bool stringFromXml(std::string const& valueXml, int* offset); + bool timeFromXml(std::string const& valueXml, int* offset); + bool binaryFromXml(std::string const& valueXml, int* offset); + bool arrayFromXml(std::string const& valueXml, int* offset); + bool structFromXml(std::string const& valueXml, int* offset); + + // XML encoding + std::string boolToXml() const; + std::string intToXml() const; + std::string doubleToXml() const; + std::string stringToXml() const; + std::string timeToXml() const; + std::string binaryToXml() const; + std::string arrayToXml() const; + std::string structToXml() const; + + // Format strings + static std::string _doubleFormat; + + // Type tag and values + Type _type; + + // At some point I will split off Arrays and Structs into + // separate ref-counted objects for more efficient copying. + union { + bool asBool; + int asInt; + double asDouble; + struct tm* asTime; + std::string* asString; + BinaryData* asBinary; + ValueArray* asArray; + ValueStruct* asStruct; + } _value; + + }; +} // namespace XmlRpc + + +std::ostream& operator<<(std::ostream& os, XmlRpc::XmlRpcValue& v); + + +#endif // _XMLRPCVALUE_H_ diff --git a/xmlrpc++/src/base64.h b/xmlrpc++/src/base64.h new file mode 100644 index 0000000..519ee0f --- /dev/null +++ b/xmlrpc++/src/base64.h @@ -0,0 +1,379 @@ + + +// base64.hpp +// Autor Konstantin Pilipchuk +// mailto:lostd@ukr.net +// +// + +#if !defined(__BASE64_H_INCLUDED__) +#define __BASE64_H_INCLUDED__ 1 + +#ifndef MAKEDEPEND +# include +#endif + +static +int _base64Chars[]= {'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z', + 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z', + '0','1','2','3','4','5','6','7','8','9', + '+','/' }; + + +#define _0000_0011 0x03 +#define _1111_1100 0xFC +#define _1111_0000 0xF0 +#define _0011_0000 0x30 +#define _0011_1100 0x3C +#define _0000_1111 0x0F +#define _1100_0000 0xC0 +#define _0011_1111 0x3F + +#define _EQUAL_CHAR (-1) +#define _UNKNOWN_CHAR (-2) + +#define _IOS_FAILBIT std::ios_base::failbit +#define _IOS_EOFBIT std::ios_base::eofbit +#define _IOS_BADBIT std::ios_base::badbit +#define _IOS_GOODBIT std::ios_base::goodbit + +// TEMPLATE CLASS base64_put +template > +class base64 +{ +public: + + typedef unsigned char byte_t; + typedef _E char_type; + typedef _Tr traits_type; + + // base64 requires max line length <= 72 characters + // you can fill end of line + // it may be crlf, crlfsp, noline or other class like it + + + struct crlf + { + template + _OI operator()(_OI _To) const{ + *_To = _Tr::to_char_type('\r'); ++_To; + *_To = _Tr::to_char_type('\n'); ++_To; + + return (_To); + } + }; + + + struct crlfsp + { + template + _OI operator()(_OI _To) const{ + *_To = _Tr::to_char_type('\r'); ++_To; + *_To = _Tr::to_char_type('\n'); ++_To; + *_To = _Tr::to_char_type(' '); ++_To; + + return (_To); + } + }; + + struct noline + { + template + _OI operator()(_OI _To) const{ + return (_To); + } + }; + + struct three2four + { + void zero() + { + _data[0] = 0; + _data[1] = 0; + _data[2] = 0; + } + + byte_t get_0() const + { + return _data[0]; + } + byte_t get_1() const + { + return _data[1]; + } + byte_t get_2() const + { + return _data[2]; + } + + void set_0(byte_t _ch) + { + _data[0] = _ch; + } + + void set_1(byte_t _ch) + { + _data[1] = _ch; + } + + void set_2(byte_t _ch) + { + _data[2] = _ch; + } + + // 0000 0000 1111 1111 2222 2222 + // xxxx xxxx xxxx xxxx xxxx xxxx + // 0000 0011 1111 2222 2233 3333 + + int b64_0() const {return (_data[0] & _1111_1100) >> 2;} + int b64_1() const {return ((_data[0] & _0000_0011) << 4) + ((_data[1] & _1111_0000)>>4);} + int b64_2() const {return ((_data[1] & _0000_1111) << 2) + ((_data[2] & _1100_0000)>>6);} + int b64_3() const {return (_data[2] & _0011_1111);} + + void b64_0(int _ch) {_data[0] = ((_ch & _0011_1111) << 2) | (_0000_0011 & _data[0]);} + + void b64_1(int _ch) { + _data[0] = ((_ch & _0011_0000) >> 4) | (_1111_1100 & _data[0]); + _data[1] = ((_ch & _0000_1111) << 4) | (_0000_1111 & _data[1]); } + + void b64_2(int _ch) { + _data[1] = ((_ch & _0011_1100) >> 2) | (_1111_0000 & _data[1]); + _data[2] = ((_ch & _0000_0011) << 6) | (_0011_1111 & _data[2]); } + + void b64_3(int _ch){ + _data[2] = (_ch & _0011_1111) | (_1100_0000 & _data[2]);} + + private: + byte_t _data[3]; + + }; + + + + + template + _II put(_II _First, _II _Last, _OI _To, _State& _St, _Endline _Endl) const + { + three2four _3to4; + int line_octets = 0; + + while(_First != _Last) + { + _3to4.zero(); + + // берём по 3 символа + _3to4.set_0(*_First); + _First++; + + if(_First == _Last) + { + *_To = _Tr::to_char_type(_base64Chars[_3to4.b64_0()]); ++_To; + *_To = _Tr::to_char_type(_base64Chars[_3to4.b64_1()]); ++_To; + *_To = _Tr::to_char_type('='); ++_To; + *_To = _Tr::to_char_type('='); ++_To; + goto __end; + } + + _3to4.set_1(*_First); + _First++; + + if(_First == _Last) + { + *_To = _Tr::to_char_type(_base64Chars[_3to4.b64_0()]); ++_To; + *_To = _Tr::to_char_type(_base64Chars[_3to4.b64_1()]); ++_To; + *_To = _Tr::to_char_type(_base64Chars[_3to4.b64_2()]); ++_To; + *_To = _Tr::to_char_type('='); ++_To; + goto __end; + } + + _3to4.set_2(*_First); + _First++; + + *_To = _Tr::to_char_type(_base64Chars[_3to4.b64_0()]); ++_To; + *_To = _Tr::to_char_type(_base64Chars[_3to4.b64_1()]); ++_To; + *_To = _Tr::to_char_type(_base64Chars[_3to4.b64_2()]); ++_To; + *_To = _Tr::to_char_type(_base64Chars[_3to4.b64_3()]); ++_To; + + if(line_octets == 17) // base64 позволяет длину строки не более 72 символов + { + //_To = _Endl(_To); + *_To = '\n'; ++_To; + line_octets = 0; + } + else + ++line_octets; + } + + __end: ; + + return (_First); + + } + + + template + _II get(_II _First, _II _Last, _OI _To, _State& _St) const + { + three2four _3to4; + int _Char; + + while(_First != _Last) + { + + // Take octet + _3to4.zero(); + + // -- 0 -- + // Search next valid char... + while((_Char = _getCharType(*_First)) < 0 && _Char == _UNKNOWN_CHAR) + { + if(++_First == _Last) + { + _St |= _IOS_FAILBIT|_IOS_EOFBIT; return _First; // unexpected EOF + } + } + + if(_Char == _EQUAL_CHAR){ + // Error! First character in octet can't be '=' + _St |= _IOS_FAILBIT; + return _First; + } + else + _3to4.b64_0(_Char); + + + // -- 1 -- + // Search next valid char... + while(++_First != _Last) + if((_Char = _getCharType(*_First)) != _UNKNOWN_CHAR) + break; + + if(_First == _Last) { + _St |= _IOS_FAILBIT|_IOS_EOFBIT; // unexpected EOF + return _First; + } + + if(_Char == _EQUAL_CHAR){ + // Error! Second character in octet can't be '=' + _St |= _IOS_FAILBIT; + return _First; + } + else + _3to4.b64_1(_Char); + + + // -- 2 -- + // Search next valid char... + while(++_First != _Last) + if((_Char = _getCharType(*_First)) != _UNKNOWN_CHAR) + break; + + if(_First == _Last) { + // Error! Unexpected EOF. Must be '=' or base64 character + _St |= _IOS_FAILBIT|_IOS_EOFBIT; + return _First; + } + + if(_Char == _EQUAL_CHAR){ + // OK! + _3to4.b64_2(0); + _3to4.b64_3(0); + + // chek for EOF + if(++_First == _Last) + { + // Error! Unexpected EOF. Must be '='. Ignore it. + //_St |= _IOS_BADBIT|_IOS_EOFBIT; + _St |= _IOS_EOFBIT; + } + else + if(_getCharType(*_First) != _EQUAL_CHAR) + { + // Error! Must be '='. Ignore it. + //_St |= _IOS_BADBIT; + } + else + ++_First; // Skip '=' + + // write 1 byte to output + *_To = (byte_t) _3to4.get_0(); + return _First; + } + else + _3to4.b64_2(_Char); + + + // -- 3 -- + // Search next valid char... + while(++_First != _Last) + if((_Char = _getCharType(*_First)) != _UNKNOWN_CHAR) + break; + + if(_First == _Last) { + // Unexpected EOF. It's error. But ignore it. + //_St |= _IOS_FAILBIT|_IOS_EOFBIT; + _St |= _IOS_EOFBIT; + + return _First; + } + + if(_Char == _EQUAL_CHAR) + { + // OK! + _3to4.b64_3(0); + + // write to output 2 bytes + *_To = (byte_t) _3to4.get_0(); + *_To = (byte_t) _3to4.get_1(); + + ++_First; // set position to next character + + return _First; + } + else + _3to4.b64_3(_Char); + + + // write to output 3 bytes + *_To = (byte_t) _3to4.get_0(); + *_To = (byte_t) _3to4.get_1(); + *_To = (byte_t) _3to4.get_2(); + + ++_First; + + + } // while(_First != _Last) + + return (_First); + } + +protected: + + int _getCharType(int _Ch) const + { + if(_base64Chars[62] == _Ch) + return 62; + + if(_base64Chars[63] == _Ch) + return 63; + + if((_base64Chars[0] <= _Ch) && (_base64Chars[25] >= _Ch)) + return _Ch - _base64Chars[0]; + + if((_base64Chars[26] <= _Ch) && (_base64Chars[51] >= _Ch)) + return _Ch - _base64Chars[26] + 26; + + if((_base64Chars[52] <= _Ch) && (_base64Chars[61] >= _Ch)) + return _Ch - _base64Chars[52] + 52; + + if(_Ch == _Tr::to_int_type('=')) + return _EQUAL_CHAR; + + return _UNKNOWN_CHAR; + } + + +}; + + +#endif -- cgit v1.2.1