/************** tabrest C++ Program Source Code File (.CPP) ************/ /* PROGRAM NAME: tabrest Version 2.1 */ /* (C) Copyright to the author Olivier BERTRAND 2018 - 2021 */ /* This program is the REST Web API support for MariaDB. */ /* The way Connect handles NOSQL data returned by REST queries is */ /* just by retrieving it as a file and then leave the existing data */ /* type tables (JSON, XML or CSV) process it as usual. */ /***********************************************************************/ /***********************************************************************/ /* Definitions needed by the included files. */ /***********************************************************************/ #include // All MariaDB stuff #include #include #include #if !defined(_WIN32) && !defined(_WINDOWS) #include #include #endif // !_WIN32 && !_WINDOWS /***********************************************************************/ /* Include application header files: */ /* global.h is header containing all global declarations. */ /* plgdbsem.h is header containing the DB application declarations. */ /***********************************************************************/ #include "global.h" #include "plgdbsem.h" #include "xtable.h" #include "filamtxt.h" #include "tabdos.h" #include "plgxml.h" #if defined(XML_SUPPORT) #include "tabxml.h" #endif // XML_SUPPORT #include "tabjson.h" #include "tabfmt.h" #include "tabrest.h" #if defined(connect_EXPORTS) #define PUSH_WARNING(M) push_warning(current_thd, Sql_condition::WARN_LEVEL_NOTE, ER_UNKNOWN_ERROR, M) #else #define PUSH_WARNING(M) htrc(M) #endif static XGETREST getRestFnc = NULL; static int Xcurl(PGLOBAL g, PCSZ Http, PCSZ Uri, PCSZ filename); /***********************************************************************/ /* Xcurl: retrieve the REST answer by executing cURL. */ /***********************************************************************/ int Xcurl(PGLOBAL g, PCSZ Http, PCSZ Uri, PCSZ filename) { char buf[512]; int rc = 0; if (strchr(filename, '"')) { strcpy(g->Message, "Invalid file name"); return 1; } // endif filename if (Uri) { if (*Uri == '/' || Http[strlen(Http) - 1] == '/') my_snprintf(buf, sizeof(buf)-1, "%s%s", Http, Uri); else my_snprintf(buf, sizeof(buf)-1, "%s/%s", Http, Uri); } else my_snprintf(buf, sizeof(buf)-1, "%s", Http); #if defined(_WIN32) char cmd[1024]; STARTUPINFO si; PROCESS_INFORMATION pi; sprintf(cmd, "curl \"%s\" -o \"%s\"", buf, filename); ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); ZeroMemory(&pi, sizeof(pi)); // Start the child process. if (CreateProcess(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { // Wait until child process exits. WaitForSingleObject(pi.hProcess, INFINITE); // Close process and thread handles. CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } else { snprintf(g->Message, sizeof(g->Message), "CreateProcess curl failed (%d)", GetLastError()); rc = 1; } // endif CreateProcess #else // !_WIN32 char fn[600]; pid_t pID; // Check if curl package is availabe by executing subprocess FILE *f= popen("command -v curl", "r"); if (!f) { strcpy(g->Message, "Problem in allocating memory."); return 1; } else { char temp_buff[50]; size_t len = fread(temp_buff,1, 50, f); if(!len) { strcpy(g->Message, "Curl not installed."); return 1; } else pclose(f); } // endif f #ifdef HAVE_VFORK pID = vfork(); #else pID = fork(); #endif sprintf(fn, "-o%s", filename); if (pID == 0) { // Code executed by child process execlp("curl", "curl", buf, fn, (char*)NULL); // If execlp() is successful, we should not reach this next line. strcpy(g->Message, "Unsuccessful execlp from vfork()"); exit(1); } else if (pID < 0) { // failed to fork strcpy(g->Message, "Failed to fork"); rc = 1; } else { // Parent process wait(NULL); // Wait for the child to terminate } // endif pID #endif // !_WIN32 return rc; } // end of Xcurl /***********************************************************************/ /* GetREST: load the Rest lib and get the Rest function. */ /***********************************************************************/ XGETREST GetRestFunction(PGLOBAL g) { if (getRestFnc) return getRestFnc; #if !defined(REST_SOURCE) if (trace(515)) htrc("Looking for GetRest library\n"); #if defined(_WIN32) || defined(_WINDOWS) HANDLE Hdll; const char* soname = "GetRest.dll"; // Module name if (!(Hdll = LoadLibrary(soname))) { char buf[256]; DWORD rc = GetLastError(); snprintf(g->Message, sizeof(g->Message), MSG(DLL_LOAD_ERROR), rc, soname); FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, rc, 0, (LPTSTR)buf, sizeof(buf), NULL); strcat(strcat(g->Message, ": "), buf); return NULL; } // endif Hdll // Get the function returning an instance of the external DEF class if (!(getRestFnc = (XGETREST)GetProcAddress((HINSTANCE)Hdll, "restGetFile"))) { char buf[256]; DWORD rc = GetLastError(); snprintf(g->Message, sizeof(g->Message), MSG(PROCADD_ERROR), rc, "restGetFile"); FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, rc, 0, (LPTSTR)buf, sizeof(buf), NULL); strcat(strcat(g->Message, ": "), buf); FreeLibrary((HMODULE)Hdll); return NULL; } // endif getRestFnc #else // !_WIN32 void* Hso; const char* error = NULL; const char* soname = "GetRest.so"; // Module name // Load the desired shared library if (!(Hso = dlopen(soname, RTLD_LAZY))) { error = dlerror(); snprintf(g->Message, sizeof(g->Message), MSG(SHARED_LIB_ERR), soname, SVP(error)); return NULL; } // endif Hdll // Get the function returning an instance of the external DEF class if (!(getRestFnc = (XGETREST)dlsym(Hso, "restGetFile"))) { error = dlerror(); snprintf(g->Message, sizeof(g->Message), MSG(GET_FUNC_ERR), "restGetFile", SVP(error)); dlclose(Hso); return NULL; } // endif getdef #endif // !_WIN32 #else // REST_SOURCE getRestFnc = restGetFile; #endif // REST_SOURCE return getRestFnc; } // end of GetRestFunction /***********************************************************************/ /* Return the columns definition to MariaDB. */ /***********************************************************************/ PQRYRES RESTColumns(PGLOBAL g, PTOS tp, char *tab, char *db, bool info) { PQRYRES qrp= NULL; char filename[_MAX_PATH + 1]; // MAX PATH ??? int rc; PCSZ http, uri, fn, ftype; XGETREST grf = NULL; bool curl = GetBooleanTableOption(g, tp, "Curl", false); if (!curl && !(grf = GetRestFunction(g))) curl = true; http = GetStringTableOption(g, tp, "Http", NULL); uri = GetStringTableOption(g, tp, "Uri", NULL); ftype = GetStringTableOption(g, tp, "Type", "JSON"); fn = GetStringTableOption(g, tp, "Filename", NULL); if (!fn) { int n, m = strlen(ftype) + 1; strcat(strcpy(filename, tab), "."); n = strlen(filename); // Fold ftype to lower case for (int i = 0; i < m; i++) filename[n + i] = tolower(ftype[i]); fn = filename; tp->subtype = PlugDup(g, fn); snprintf(g->Message, sizeof(g->Message), "No file name. Table will use %s", fn); PUSH_WARNING(g->Message); } // endif fn // We used the file name relative to recorded datapath PlugSetPath(filename, fn, db); remove(filename); // Retrieve the file from the web and copy it locally if (curl) rc = Xcurl(g, http, uri, filename); else rc = grf(g->Message, trace(515), http, uri, filename); if (rc) { strcpy(g->Message, "Cannot access to curl nor casablanca"); return NULL; } else if (!stricmp(ftype, "JSON")) qrp = JSONColumns(g, db, NULL, tp, info); else if (!stricmp(ftype, "CSV")) qrp = CSVColumns(g, NULL, tp, info); #if defined(XML_SUPPORT) else if (!stricmp(ftype, "XML")) qrp = XMLColumns(g, db, tab, tp, info); #endif // XML_SUPPORT else snprintf(g->Message, sizeof(g->Message), "Usupported file type %s", ftype); return qrp; } // end of RESTColumns /* -------------------------- Class RESTDEF -------------------------- */ /***********************************************************************/ /* DefineAM: define specific AM block values. */ /***********************************************************************/ bool RESTDEF::DefineAM(PGLOBAL g, LPCSTR am, int poff) { char filename[_MAX_PATH + 1]; int rc = 0, n; bool xt = trace(515); LPCSTR ftype; XGETREST grf = NULL; bool curl = GetBoolCatInfo("Curl", false); if (!curl && !(grf = GetRestFunction(g))) curl = true; ftype = GetStringCatInfo(g, "Type", "JSON"); if (xt) htrc("ftype = %s am = %s\n", ftype, SVP(am)); n = (!stricmp(ftype, "JSON")) ? 1 #if defined(XML_SUPPORT) : (!stricmp(ftype, "XML")) ? 2 #endif // XML_SUPPORT : (!stricmp(ftype, "CSV")) ? 3 : 0; if (n == 0) { htrc("DefineAM: Unsupported REST table type %s\n", ftype); snprintf(g->Message, sizeof(g->Message), "Unsupported REST table type %s", ftype); return true; } // endif n Http = GetStringCatInfo(g, "Http", NULL); Uri = GetStringCatInfo(g, "Uri", NULL); Fn = GetStringCatInfo(g, "Filename", NULL); // We used the file name relative to recorded datapath PlugSetPath(filename, Fn, GetPath()); remove(filename); // Retrieve the file from the web and copy it locally if (curl) { rc = Xcurl(g, Http, Uri, filename); xtrc(515, "Return from Xcurl: rc=%d\n", rc); } else { rc = grf(g->Message, xt, Http, Uri, filename); xtrc(515, "Return from restGetFile: rc=%d\n", rc); } // endelse if (rc) { // strcpy(g->Message, "Cannot access to curl nor casablanca"); return true; } else switch (n) { case 1: Tdp = new (g) JSONDEF; break; #if defined(XML_SUPPORT) case 2: Tdp = new (g) XMLDEF; break; #endif // XML_SUPPORT case 3: Tdp = new (g) CSVDEF; break; default: Tdp = NULL; } // endswitch n // Do make the table/view definition if (Tdp && Tdp->Define(g, Cat, Name, Schema, "REST")) Tdp = NULL; // Error occurred if (xt) htrc("Tdp defined\n", rc); // Return true in case of error return (Tdp == NULL); } // end of DefineAM /***********************************************************************/ /* GetTable: makes a new Table Description Block. */ /***********************************************************************/ PTDB RESTDEF::GetTable(PGLOBAL g, MODE m) { if (trace(515)) htrc("REST GetTable mode=%d\n", m); if (m != MODE_READ && m != MODE_READX && m != MODE_ANY) { strcpy(g->Message, "REST tables are currently read only"); return NULL; } // endif m return Tdp->GetTable(g, m); // Leave file type do the job } // end of GetTable /* ---------------------- End of Class RESTDEF ----------------------- */