#include "XMLHTTPDatabase.h" #include "debug.h" #include //timing on the sql statements #include using namespace std; XMLHTTPDatabase::XMLHTTPDatabase(const char *_host, const char *_db, const char *_username, const char *_password): Database(_host, _db, _username, _password), m_DC(m_host), m_domain(0, m_host), m_buffersize(XMLHTTPDB_MAXBODYBUFFER * 1024), m_connected(false) { m_buffer = (char*)mallocCheck(m_buffersize); MEMBER_INIT_MUTEX(m_hSingleThreaded_mutex); } XMLHTTPDatabase::~XMLHTTPDatabase() { pthread_mutex_destroy(&m_hSingleThreaded_mutex); if (m_buffer) free(m_buffer); } bool XMLHTTPDatabase::connect() { pthread_mutex_lock(&m_hSingleThreaded_mutex); if (!m_connected) { m_DC.DNSLookup(); //will throw DNSFailure on fail: let bubble up m_DC.connect(); //asynchronos pre-imptive connect (DomainConnection simply connects at the socket level) m_connected = true; } pthread_mutex_unlock(&m_hSingleThreaded_mutex); return connected(); } bool XMLHTTPDatabase::connected() const {return m_connected;} //stateless int XMLHTTPDatabase::execute(const char *sql, RecordSet **result, char **sError, const bool manageResults) { //caller also frees sError if there is an error and it has been requested //the template class defines the return type. Not all are supported: RecordSet, char, int //uses application/x-www-form-urlencoded to send the query int ret = 1; if (result) *result = 0; //in the case of errors the results pointer will be null if (sError) *sError = 0; if (!m_connected) return 1; //maybe working in offline mode. DomainConnection would hang as no response will come from other end... #ifdef _DEBUG time_t tStart, tEnd; time(&tStart); DEBUGPRINT("[XMLHTTPDB]: sql [%s]", DEBUG_CHECK, sql); #endif InternetResource *resource = 0; Protocol *p; size_t urllen = 0, bodylen = 0; char *url = 0, *body = 0; //create the URL object //note that the IRQ will deal with any required URL escaping of the URL and body urllen = strlen(m_host) + strlen(m_db) + 26; url = (char*)malloc(urllen + 1); //0 terminator _SNPRINTF(url, urllen + 1, "http://%s/%s", m_host, m_db); InternetURIRequest ir(&m_domain, url); //url is copied by InternetURIRequest //Protocol will deal with the escaping of the parameters according to it ir.addParameter("usr", m_username); ir.addParameter("pwd", m_password); ir.addParameter("sql", sql); ir.parse(); DEBUGPRINT("[XMLHTTPDB]: get page [%s]", DEBUG_CHECK, url); free(url); //url is copied by InternetURIRequest //use the Protocol to retrieve the XML (maybe with a custom buffer if there are overlapping queries) pthread_mutex_lock(&m_hSingleThreaded_mutex); //DC and m_buffer is shared p = Protocol::createProtocol(0, &m_DC, &ir, &resource, m_buffer, m_buffersize); if (p->getResource() == Protocol::ok && resource) { //synchronous DEBUG_RESULT_OK; if (!resource->body() || _STRNCMP(resource->body(), "", 11)) { //HTTP request successful but the query failed (return format in body is invalid) if (sError && resource->body()) { *sError = (char *) mallocCheck(strlen(resource->body())+1); strcpy(*sError, resource->body()); } DEBUGERROR("[XMLHTTPDB]: QueryFailed(\n--------------------------\n%s\n--------------------------\n%s)", sql, resource->body()); delete p; delete resource; pthread_mutex_unlock(&m_hSingleThreaded_mutex); throw QueryFailed(sql, sError ? *sError : 0); } else { //request, query and format all ok //resource will be freed by the RecordSet //resource buffer will also be freed if it is requested (overlapping requests) if (result) *result = new XMLHTTPRecordSet(resource, manageResults); ret = 0; } } else { //HTTP request failed DEBUG_RESULT_FAIL; DEBUGERROR("[XMLHTTPDB]: RequestFailed(%s)", sql); if (resource) delete resource; delete p; pthread_mutex_unlock(&m_hSingleThreaded_mutex); throw RequestFailed(sql); } pthread_mutex_unlock(&m_hSingleThreaded_mutex); delete p; #ifdef _DEBUG time(&tEnd); DEBUGPRINT("[XMLHTTPDB]: %f seconds taken", DEBUG_LINE, difftime(tEnd, tStart)); #endif return ret; } int XMLHTTPDatabase::execute(const char *procedure, const int argc, const char *argv[], const bool argt[], RecordSet **result, char **sError, const bool manageResults) { //calculate entire length of statement: //*2 for the potential escaped backslashes //+3 for potential quotes and comma //start at 100 for the "select...", brackets and quotes //include length of the SPROC name bool bQuote; size_t sqlLength; char *sql, *sqlcurrent; const char *arg; //calculate length sqlLength = 100 + strlen(procedure); for (int i = 0; i < argc; i++) { arg = argv[i]; sqlLength += (arg ? strlen(arg) * 2 : 6); //escaping, 6 = null } sql = (char*) mallocCheck(sqlLength); sqlcurrent = sql; //SPROC basics const char *sqlstart = (result ? "select * from \"" : "select \""); strcpy(sqlcurrent, sqlstart); sqlcurrent += strlen(sqlstart); strcpy(sqlcurrent, procedure); sqlcurrent += strlen(procedure); *sqlcurrent++ = '"'; *sqlcurrent++ = '('; //SPROC args if (argc && procedure) { for (int i = 0; i < argc; i++) { arg = argv[i]; bQuote = (arg && (!argt||argt[i])); //if to add quotes if (i) *sqlcurrent++ = ','; if (bQuote) *sqlcurrent++ = '\''; if (arg) PQescapeString(sqlcurrent, arg, strlen(arg)); else strcpy(sqlcurrent, "null"); while (*sqlcurrent) sqlcurrent++; //PQescapeString will 0 terminate the string if (bQuote) *sqlcurrent++ = '\''; } } *sqlcurrent++ = ')'; *sqlcurrent = 0; //terminate const int zEST = execute(sql, result, sError, manageResults); free(sql); return zEST; } //----------------------------------------------- PostGresRecordSet --------------------------------------------------- XMLHTTPRecordSet::XMLHTTPRecordSet(InternetResource *_xml, const bool _manageResults, const bool _manageBuffer): RecordSet(_manageResults), m_xml(_xml), m_manageBuffer(_manageBuffer) { m_rowStarts.reserve(100); } XMLHTTPRecordSet::~XMLHTTPRecordSet() { if (m_manageBuffer) m_xml->freeBody(); if (m_xml) delete m_xml; //if the InternetResource has a managed buffer then it will release the body } size_t XMLHTTPRecordSet::size() { const char *rowStart; //find row start size_t knownRows = m_rowStarts.size(); //row 0 needs 1 entry in m_rowStarts if (knownRows) rowStart = m_rowStarts[knownRows-1]; //last known row start else rowStart = m_xml->body(); rowStart = strstr(rowStart+1, "<"); while (rowStart) { m_rowStarts.push_back(rowStart); rowStart = strstr(rowStart+1, "<"); } return m_rowStarts.size(); } int XMLHTTPRecordSet::value(char **value, const unsigned int row, const unsigned int column) { *value = 0; int ret = 1; const char *rowStart, *columnStart, *columnEnd; size_t iResultLen; //find row start size_t knownRows = m_rowStarts.size(); //row 0 needs 1 entry in m_rowStarts if (row >= knownRows) { if (knownRows) rowStart = m_rowStarts[knownRows-1]; //last known row start else rowStart = m_xml->body(); for (unsigned int i = (unsigned int)knownRows - 1; i != row && rowStart; i++) { rowStart = strstr(rowStart+1, "<"); if (rowStart) m_rowStarts.push_back(rowStart); } } else rowStart = m_rowStarts[row]; //find column start if (rowStart) { columnStart = rowStart; for (unsigned int i = 0; i != column + 1 && columnStart; i++) columnStart = strstr(columnStart+1, "><")) { iResultLen = columnEnd - columnStart - 10; *value = (char*) mallocCheck(iResultLen + 1); //plus trailing zero if (m_manageResults) recordMalloc(*value); strncpy(*value, columnStart + 10, iResultLen); (*value)[iResultLen] = 0; ret = 0; } } } return ret; } int XMLHTTPRecordSet::length(size_t *iResultLen, const unsigned int row, const unsigned int column) { *iResultLen = 0; int ret = 1; char *colValue; if (!value(&colValue, row, column) && colValue) { *iResultLen = strlen(colValue); free(colValue); ret = 0; } return ret; }