LibOFX
ofx_preproc.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  ofx_preproc.cpp
3  -------------------
4  copyright : (C) 2002 by Benoit Gr�oir
5  email : benoitg@coeus.ca
6 ***************************************************************************/
12 /***************************************************************************
13  * *
14  * This program is free software; you can redistribute it and/or modify *
15  * it under the terms of the GNU General Public License as published by *
16  * the Free Software Foundation; either version 2 of the License, or *
17  * (at your option) any later version. *
18  * *
19  ***************************************************************************/
20 #include "../config.h"
21 #include <iostream>
22 #include <fstream>
23 #include <cstdlib>
24 #include <stdio.h>
25 #include <string>
26 #include "ParserEventGeneratorKit.h"
27 #include "libofx.h"
28 #include "messages.hh"
29 #include "ofx_sgml.hh"
30 #include "ofc_sgml.hh"
31 #include "ofx_preproc.hh"
32 #include "ofx_utilities.hh"
33 #ifdef HAVE_ICONV
34 #include <iconv.h>
35 #endif
36 
37 #ifdef OS_WIN32
38 # define DIRSEP "\\"
39 #else
40 # define DIRSEP "/"
41 #endif
42 
43 #ifdef OS_WIN32
44 # include "win32.hh"
45 # include <windows.h> // for GetModuleFileName()
46 # undef ERROR
47 # undef DELETE
48 #endif
49 
50 #define LIBOFX_DEFAULT_INPUT_ENCODING "CP1252"
51 #define LIBOFX_DEFAULT_OUTPUT_ENCODING "UTF-8"
52 
53 using namespace std;
57 #ifdef MAKEFILE_DTD_PATH
58 const int DTD_SEARCH_PATH_NUM = 4;
59 #else
60 const int DTD_SEARCH_PATH_NUM = 3;
61 #endif
62 
67 {
68 #ifdef MAKEFILE_DTD_PATH
69  MAKEFILE_DTD_PATH ,
70 #endif
71  "/usr/local/share/libofx/dtd",
72  "/usr/share/libofx/dtd",
73  "~"
74 };
75 const unsigned int READ_BUFFER_SIZE = 1024;
76 
81 int ofx_proc_file(LibofxContextPtr ctx, const char * p_filename)
82 {
83  LibofxContext *libofx_context;
84  bool ofx_start = false;
85  bool ofx_end = false;
86  bool file_is_xml = false;
87 
88  ifstream input_file;
89  ofstream tmp_file;
90  char buffer[READ_BUFFER_SIZE];
91  string s_buffer;
92  char *filenames[3];
93  char tmp_filename[256];
94  int tmp_file_fd;
95 #ifdef HAVE_ICONV
96  iconv_t conversion_descriptor;
97 #endif
98  libofx_context = (LibofxContext*)ctx;
99 
100  if (p_filename != NULL && strcmp(p_filename, "") != 0)
101  {
102  message_out(DEBUG, string("ofx_proc_file():Opening file: ") + p_filename);
103 
104  input_file.open(p_filename);
105  if (!input_file)
106  {
107  message_out(ERROR, "ofx_proc_file():Unable to open the input file " + string(p_filename));
108  }
109 
110  mkTempFileName("libofxtmpXXXXXX", tmp_filename, sizeof(tmp_filename));
111 
112  message_out(DEBUG, "ofx_proc_file(): Creating temp file: " + string(tmp_filename));
113  tmp_file_fd = mkstemp(tmp_filename);
114  if (tmp_file_fd)
115  {
116  tmp_file.open(tmp_filename);
117  if (!tmp_file)
118  {
119  message_out(ERROR, "ofx_proc_file():Unable to open the created temp file " + string(tmp_filename));
120  return -1;
121  }
122  }
123  else
124  {
125  message_out(ERROR, "ofx_proc_file():Unable to create a temp file at " + string(tmp_filename));
126  return -1;
127  }
128 
129  if (input_file && tmp_file)
130  {
131  int header_separator_idx;
132  string header_name;
133  string header_value;
134  string ofx_encoding;
135  string ofx_charset;
136  do
137  {
138  s_buffer.clear();
139  bool end_of_line = false;
140  do
141  {
142  input_file.get(buffer, sizeof(buffer), '\n');
143  //cout<< "got: \"" << buffer<<"\"\n";
144  s_buffer.append(buffer);
145 
146  // Watch out: If input_file is in eof(), any subsequent read or
147  // peek() will fail and we must exit this loop.
148  if (input_file.eof())
149  break;
150 
151  //cout<<"input_file.gcount(): "<<input_file.gcount()<< " s_buffer.size=" << s_buffer.size()<<" sizeof(buffer): "<<sizeof(buffer) << " peek=\"" << int(input_file.peek()) << "\"" <<endl;
152  if (input_file.fail()) // If no characters were extracted above, the failbit is set.
153  {
154  // No characters extracted means that we've reached the newline
155  // delimiter (because we already checked for EOF). We will check
156  // for and remove that newline in the next if-clause, but must
157  // remove the failbit so that peek() will work again.
158  input_file.clear();
159  }
160 
161  // Is the next character really the newline?
162  if (input_file.peek() == '\n')
163  {
164  // Yes. Then discard that newline character from the stream and
165  // append it manually to the output string.
166  input_file.get();
167  s_buffer.append("\n");
168  end_of_line = true; // We found the end-of-line.
169  }
170  }
171  // Continue reading as long as we're not at EOF *and* we've not yet
172  // reached an end-of-line.
173  while (!input_file.eof() && !end_of_line);
174 
175  if (ofx_start == false && (s_buffer.find("<?xml") != string::npos))
176  {
177  message_out(DEBUG, "ofx_proc_file(): File is an actual XML file, iconv conversion will be skipped.");
178  file_is_xml = true;
179  }
180 
181  int ofx_start_idx;
182  if (ofx_start == false &&
183  (
184  (libofx_context->currentFileType() == OFX &&
185  ((ofx_start_idx = s_buffer.find("<OFX>")) !=
186  string::npos || (ofx_start_idx = s_buffer.find("<ofx>")) != string::npos))
187  || (libofx_context->currentFileType() == OFC &&
188  ((ofx_start_idx = s_buffer.find("<OFC>")) != string::npos ||
189  (ofx_start_idx = s_buffer.find("<ofc>")) != string::npos))
190  )
191  )
192  {
193  ofx_start = true;
194  if (file_is_xml == false)
195  {
196  s_buffer.erase(0, ofx_start_idx); //Fix for really broken files that don't have a newline after the header.
197  }
198  message_out(DEBUG, "ofx_proc_file():<OFX> or <OFC> has been found");
199 
200  if (file_is_xml == true)
201  {
202  static char sp_charset_fixed[] = "SP_CHARSET_FIXED=1";
203  if (putenv(sp_charset_fixed) != 0)
204  {
205  message_out(ERROR, "ofx_proc_file(): putenv failed");
206  }
207  /* Normally the following would be "xml".
208  * Unfortunately, opensp's generic api will garble UTF-8 if this is
209  * set to xml. So we set any single byte encoding to avoid messing
210  * up UTF-8. Unfortunately this means that non-UTF-8 files will not
211  * get properly translated. We'd need to manually detect the
212  * encoding in the XML header and convert the xml with iconv like we
213  * do for SGML to work around the problem. Most unfortunate. */
214  static char sp_encoding[] = "SP_ENCODING=ms-dos";
215  if (putenv(sp_encoding) != 0)
216  {
217  message_out(ERROR, "ofx_proc_file(): putenv failed");
218  }
219  }
220  else
221  {
222  static char sp_charset_fixed[] = "SP_CHARSET_FIXED=1";
223  if (putenv(sp_charset_fixed) != 0)
224  {
225  message_out(ERROR, "ofx_proc_file(): putenv failed");
226  }
227  static char sp_encoding[] = "SP_ENCODING=ms-dos"; //Any single byte encoding will do, we don't want opensp messing up UTF-8;
228  if (putenv(sp_encoding) != 0)
229  {
230  message_out(ERROR, "ofx_proc_file(): putenv failed");
231  }
232 #ifdef HAVE_ICONV
233  string fromcode;
234  string tocode;
235  if (ofx_encoding.compare("USASCII") == 0)
236  {
237  if (ofx_charset.compare("ISO-8859-1") == 0 || ofx_charset.compare("8859-1") == 0)
238  {
239  //Only "ISO-8859-1" is actually a legal value, but since the banks follows the spec SO well...
240  fromcode = "ISO-8859-1";
241  }
242  else if (ofx_charset.compare("1252") == 0 || ofx_charset.compare("CP1252") == 0)
243  {
244  //Only "1252" is actually a legal value, but since the banks follows the spec SO well...
245  fromcode = "CP1252";
246  }
247  else if (ofx_charset.compare("NONE") == 0)
248  {
249  fromcode = LIBOFX_DEFAULT_INPUT_ENCODING;
250  }
251  else
252  {
253  fromcode = LIBOFX_DEFAULT_INPUT_ENCODING;
254  }
255  }
256  else if (ofx_encoding.compare("UTF-8") == 0 || ofx_encoding.compare("UNICODE") == 0)
257  {
258  //While "UNICODE" isn't a legal value, some cyrilic files do specify it as such...
259  fromcode = "UTF-8";
260  }
261  else
262  {
263  fromcode = LIBOFX_DEFAULT_INPUT_ENCODING;
264  }
265  tocode = LIBOFX_DEFAULT_OUTPUT_ENCODING;
266  message_out(DEBUG, "ofx_proc_file(): Setting up iconv for fromcode: " + fromcode + ", tocode: " + tocode);
267  conversion_descriptor = iconv_open (tocode.c_str(), fromcode.c_str());
268 #endif
269  }
270  }
271  else
272  {
273  //We are still in the headers
274  if ((header_separator_idx = s_buffer.find(':')) != string::npos)
275  {
276  //Header processing
277  header_name.assign(s_buffer.substr(0, header_separator_idx));
278  header_value.assign(s_buffer.substr(header_separator_idx + 1));
279  while ( header_value[header_value.length() -1 ] == '\n' ||
280  header_value[header_value.length() -1 ] == '\r' )
281  header_value.erase(header_value.length() - 1);
282  message_out(DEBUG, "ofx_proc_file():Header: " + header_name + " with value: " + header_value + " has been found");
283  if (header_name.compare("ENCODING") == 0)
284  {
285  ofx_encoding.assign(header_value);
286  }
287  if (header_name.compare("CHARSET") == 0)
288  {
289  ofx_charset.assign(header_value);
290  }
291  }
292  }
293 
294  if (file_is_xml == true || (ofx_start == true && ofx_end == false))
295  {
296  if (ofx_start == true)
297  {
298  /* The above test won't help us if the <OFX> tag is on the same line
299  * as the xml header, but as opensp can't be used to parse it anyway
300  * this isn't a great loss for now.
301  */
302  s_buffer = sanitize_proprietary_tags(s_buffer);
303  }
304  //cout<< s_buffer<<"\n";
305  if (file_is_xml == false)
306  {
307 #ifdef HAVE_ICONV
308  size_t inbytesleft = s_buffer.size();
309  size_t outbytesleft = inbytesleft * 2 - 1;
310  char * iconv_buffer = (char*) malloc (inbytesleft * 2);
311  memset(iconv_buffer, 0, inbytesleft * 2);
312 #if defined(OS_WIN32) || defined(__sun) || defined(__NetBSD__)
313  const char * inchar = (const char *)s_buffer.c_str();
314 #else
315  char * inchar = (char *)s_buffer.c_str();
316 #endif
317  char * outchar = iconv_buffer;
318  int iconv_retval = iconv (conversion_descriptor,
319  &inchar, &inbytesleft,
320  &outchar, &outbytesleft);
321  if (iconv_retval == -1)
322  {
323  message_out(ERROR, "ofx_proc_file(): Iconv conversion error");
324  }
325  // All validly converted bytes will be copied to the
326  // original buffer
327  s_buffer = std::string(iconv_buffer, outchar - iconv_buffer);
328  free (iconv_buffer);
329 #endif
330  }
331  //cout << s_buffer << "\n";
332  tmp_file.write(s_buffer.c_str(), s_buffer.length());
333  }
334 
335  if (ofx_start == true &&
336  (
337  (libofx_context->currentFileType() == OFX &&
338  ((ofx_start_idx = s_buffer.find("</OFX>")) != string::npos ||
339  (ofx_start_idx = s_buffer.find("</ofx>")) != string::npos))
340  || (libofx_context->currentFileType() == OFC &&
341  ((ofx_start_idx = s_buffer.find("</OFC>")) != string::npos ||
342  (ofx_start_idx = s_buffer.find("</ofc>")) != string::npos))
343  )
344  )
345  {
346  ofx_end = true;
347  message_out(DEBUG, "ofx_proc_file():</OFX> or </OFC> has been found");
348  }
349 
350  }
351  while (!input_file.eof() && !input_file.bad());
352  }
353  input_file.close();
354  tmp_file.close();
355 #ifdef HAVE_ICONV
356  if (file_is_xml == false)
357  {
358  iconv_close(conversion_descriptor);
359  }
360 #endif
361  char filename_openspdtd[255];
362  char filename_dtd[255];
363  char filename_ofx[255];
364  strncpy(filename_openspdtd, find_dtd(ctx, OPENSPDCL_FILENAME).c_str(), 255); //The opensp sgml dtd file
365  if (libofx_context->currentFileType() == OFX)
366  {
367  strncpy(filename_dtd, find_dtd(ctx, OFX160DTD_FILENAME).c_str(), 255); //The ofx dtd file
368  }
369  else if (libofx_context->currentFileType() == OFC)
370  {
371  strncpy(filename_dtd, find_dtd(ctx, OFCDTD_FILENAME).c_str(), 255); //The ofc dtd file
372  }
373  else
374  {
375  message_out(ERROR, string("ofx_proc_file(): Error unknown file format for the OFX parser"));
376  }
377 
378  if ((string)filename_dtd != "" && (string)filename_openspdtd != "")
379  {
380  strncpy(filename_ofx, tmp_filename, 255); //The processed ofx file
381  filenames[0] = filename_openspdtd;
382  filenames[1] = filename_dtd;
383  filenames[2] = filename_ofx;
384  if (libofx_context->currentFileType() == OFX)
385  {
386  ofx_proc_sgml(libofx_context, 3, filenames);
387  }
388  else if (libofx_context->currentFileType() == OFC)
389  {
390  ofc_proc_sgml(libofx_context, 3, filenames);
391  }
392  else
393  {
394  message_out(ERROR, string("ofx_proc_file(): Error unknown file format for the OFX parser"));
395  }
396  if (remove(tmp_filename) != 0)
397  {
398  message_out(ERROR, "ofx_proc_file(): Error deleting temporary file " + string(tmp_filename));
399  }
400  }
401  else
402  {
403  message_out(ERROR, "ofx_proc_file(): FATAL: Missing DTD, aborting");
404  }
405  }
406  else
407  {
408  message_out(ERROR, "ofx_proc_file():No input file specified");
409  }
410  return 0;
411 }
412 
413 
418 string sanitize_proprietary_tags(string input_string)
419 {
420  unsigned int i;
421  bool strip = false;
422  bool tag_open = false;
423  int tag_open_idx = 0; //Are we within < > ?
424  bool closing_tag_open = false; //Are we within </ > ?
425  int orig_tag_open_idx = 0;
426  bool proprietary_tag = false; //Are we within a proprietary element?
427  bool proprietary_closing_tag = false;
428  int crop_end_idx = 0;
429  char buffer[READ_BUFFER_SIZE] = "";
430  char tagname[READ_BUFFER_SIZE] = "";
431  int tagname_idx = 0;
432  char close_tagname[READ_BUFFER_SIZE] = "";
433 
434  for (i = 0; i < READ_BUFFER_SIZE; i++)
435  {
436  buffer[i] = 0;
437  tagname[i] = 0;
438  close_tagname[i] = 0;
439  }
440 
441  size_t input_string_size = input_string.size();
442 
443  // Minimum workaround to prevent buffer overflow: Stop iterating
444  // once the (fixed!) size of the output buffers is reached. In
445  // response to
446  // https://www.talosintelligence.com/vulnerability_reports/TALOS-2017-0317
447  //
448  // However, this code is a huge mess anyway and is in no way
449  // anything like up-to-date C++ code. Please, anyone, replace it
450  // with something more modern. Thanks. - cstim, 2017-09-17.
451  for (i = 0; i < std::min(input_string_size, size_t(READ_BUFFER_SIZE)); i++)
452  {
453  if (input_string.c_str()[i] == '<')
454  {
455  tag_open = true;
456  tag_open_idx = i;
457  if (proprietary_tag == true && input_string.c_str()[i+1] == '/')
458  {
459  //We are now in a closing tag
460  closing_tag_open = true;
461  //cout<<"Comparaison: "<<tagname<<"|"<<&(input_string.c_str()[i+2])<<"|"<<strlen(tagname)<<endl;
462  if (strncmp(tagname, &(input_string.c_str()[i+2]), strlen(tagname)) != 0)
463  {
464  //If it is the begining of an other tag
465  //cout<<"DIFFERENT!"<<endl;
466  crop_end_idx = i - 1;
467  strip = true;
468  }
469  else
470  {
471  //Otherwise, it is the start of the closing tag of the proprietary tag
472  proprietary_closing_tag = true;
473  }
474  }
475  else if (proprietary_tag == true)
476  {
477  //It is the start of a new tag, following a proprietary tag
478  crop_end_idx = i - 1;
479  strip = true;
480  }
481  }
482  else if (input_string.c_str()[i] == '>')
483  {
484  tag_open = false;
485  closing_tag_open = false;
486  tagname[tagname_idx] = 0;
487  tagname_idx = 0;
488  if (proprietary_closing_tag == true)
489  {
490  crop_end_idx = i;
491  strip = true;
492  }
493  }
494  else if (tag_open == true && closing_tag_open == false)
495  {
496  if (input_string.c_str()[i] == '.')
497  {
498  if (proprietary_tag != true)
499  {
500  orig_tag_open_idx = tag_open_idx;
501  proprietary_tag = true;
502  }
503  }
504  tagname[tagname_idx] = input_string.c_str()[i];
505  tagname_idx++;
506  }
507  //cerr <<i<<endl;
508  if (strip == true && orig_tag_open_idx < input_string.size())
509  {
510  input_string.copy(buffer, (crop_end_idx - orig_tag_open_idx) + 1, orig_tag_open_idx);
511  message_out(INFO, "sanitize_proprietary_tags() (end tag or new tag) removed: " + string(buffer));
512  input_string.erase(orig_tag_open_idx, (crop_end_idx - orig_tag_open_idx) + 1);
513  i = orig_tag_open_idx - 1;
514  proprietary_tag = false;
515  proprietary_closing_tag = false;
516  closing_tag_open = false;
517  tag_open = false;
518  strip = false;
519 
520  input_string_size = input_string.size();
521  }
522 
523  }//end for
524  if (proprietary_tag == true && orig_tag_open_idx < input_string.size())
525  {
526  if (crop_end_idx == 0) //no closing tag
527  {
528  crop_end_idx = input_string.size() - 1;
529  }
530  input_string.copy(buffer, (crop_end_idx - orig_tag_open_idx) + 1, orig_tag_open_idx);
531  message_out(INFO, "sanitize_proprietary_tags() (end of line) removed: " + string(buffer));
532  input_string.erase(orig_tag_open_idx, (crop_end_idx - orig_tag_open_idx) + 1);
533  input_string_size = input_string.size();
534  }
535  return input_string;
536 }
537 
538 
539 #ifdef OS_WIN32
540 static std::string get_dtd_installation_directory()
541 {
542  // Partial implementation of
543  // http://developer.gnome.org/doc/API/2.0/glib/glib-Windows-Compatibility-Functions.html#g-win32-get-package-installation-directory
544  char ch_fn[MAX_PATH], *p;
545  std::string str_fn;
546 
547  if (!GetModuleFileName(NULL, ch_fn, MAX_PATH)) return "";
548 
549  if ((p = strrchr(ch_fn, '\\')) != NULL)
550  * p = '\0';
551 
552  p = strrchr(ch_fn, '\\');
553  if (p && (_stricmp(p + 1, "bin") == 0 ||
554  _stricmp(p + 1, "lib") == 0))
555  *p = '\0';
556 
557  str_fn = ch_fn;
558  str_fn += "\\share\\libofx\\dtd";
559 
560  return str_fn;
561 }
562 #endif
563 
564 
577 std::string find_dtd(LibofxContextPtr ctx, const std::string& dtd_filename)
578 {
579  string dtd_path_filename;
580  char *env_dtd_path;
581 
582  dtd_path_filename = reinterpret_cast<const LibofxContext*>(ctx)->dtdDir();
583  if (!dtd_path_filename.empty())
584  {
585  dtd_path_filename.append(dtd_filename);
586  ifstream dtd_file(dtd_path_filename.c_str());
587  if (dtd_file)
588  {
589  message_out(STATUS, "find_dtd():DTD found: " + dtd_path_filename);
590  return dtd_path_filename;
591  }
592  }
593 
594 #ifdef OS_WIN32
595  dtd_path_filename = get_dtd_installation_directory();
596  if (!dtd_path_filename.empty())
597  {
598  dtd_path_filename.append(DIRSEP);
599  dtd_path_filename.append(dtd_filename);
600  ifstream dtd_file(dtd_path_filename.c_str());
601  if (dtd_file)
602  {
603  message_out(STATUS, "find_dtd():DTD found: " + dtd_path_filename);
604  return dtd_path_filename;
605  }
606  }
607 #endif
608  /* Search in environement variable OFX_DTD_PATH */
609  env_dtd_path = getenv("OFX_DTD_PATH");
610  if (env_dtd_path)
611  {
612  dtd_path_filename.append(env_dtd_path);
613  dtd_path_filename.append(DIRSEP);
614  dtd_path_filename.append(dtd_filename);
615  ifstream dtd_file(dtd_path_filename.c_str());
616  if (!dtd_file)
617  {
618  message_out(STATUS, "find_dtd():OFX_DTD_PATH env variable was was present, but unable to open the file " + dtd_path_filename);
619  }
620  else
621  {
622  message_out(STATUS, "find_dtd():DTD found: " + dtd_path_filename);
623  return dtd_path_filename;
624  }
625  }
626 
627  for (int i = 0; i < DTD_SEARCH_PATH_NUM; i++)
628  {
629  dtd_path_filename = DTD_SEARCH_PATH[i];
630  dtd_path_filename.append(DIRSEP);
631  dtd_path_filename.append(dtd_filename);
632  ifstream dtd_file(dtd_path_filename.c_str());
633  if (!dtd_file)
634  {
635  message_out(DEBUG, "find_dtd():Unable to open the file " + dtd_path_filename);
636  }
637  else
638  {
639  message_out(STATUS, "find_dtd():DTD found: " + dtd_path_filename);
640  return dtd_path_filename;
641  }
642  }
643 
644  /* Last resort, look in source tree relative path (useful for development) */
645  dtd_path_filename = "";
646  dtd_path_filename.append("..");
647  dtd_path_filename.append(DIRSEP);
648  dtd_path_filename.append("dtd");
649  dtd_path_filename.append(DIRSEP);
650  dtd_path_filename.append(dtd_filename);
651  ifstream dtd_file(dtd_path_filename.c_str());
652  if (!dtd_file)
653  {
654  message_out(DEBUG, "find_dtd(): Unable to open the file " + dtd_path_filename + ", most likely we are not in the source tree.");
655  }
656  else
657  {
658  message_out(STATUS, "find_dtd():DTD found: " + dtd_path_filename);
659  return dtd_path_filename;
660  }
661 
662 
663  message_out(ERROR, "find_dtd():Unable to find the DTD named " + dtd_filename);
664  return "";
665 }
666 
667 
Definition: messages.hh:32
int ofx_proc_file(LibofxContextPtr ctx, const char *p_filename)
File pre-processing of OFX AND for OFC files.
Definition: ofx_preproc.cpp:81
Main header file containing the LibOfx API.
const int DTD_SEARCH_PATH_NUM
The number of different paths to search for DTDs.
Definition: ofx_preproc.cpp:60
int message_out(OfxMsgType error_type, const string message)
Message output function.
Definition: messages.cpp:60
OFX/SGML parsing functionnality.
const char * DTD_SEARCH_PATH[DTD_SEARCH_PATH_NUM]
The list of paths to search for the DTDs.
Definition: ofx_preproc.cpp:66
Definition: libofx.h:127
int ofc_proc_sgml(LibofxContext *libofx_context, int argc, char *const *argv)
Parses a DTD and OFX file(s)
Definition: ofc_sgml.cpp:353
Various simple functions for type conversion & al.
int ofx_proc_sgml(LibofxContext *libofx_context, int argc, char *const *argv)
Parses a DTD and OFX file(s)
Definition: ofx_sgml.cpp:372
Definition: libofx.h:126
string sanitize_proprietary_tags(string input_string)
Removes proprietary tags and comments.
OFX/SGML parsing functionnality.
Message IO functionality.
Preprocessing of the OFX files before parsing.
std::string find_dtd(LibofxContextPtr ctx, const std::string &dtd_filename)
Find the appropriate DTD for the file version.