1 /************** tabrest C++ Program Source Code File (.CPP) ************/
2 /* PROGRAM NAME: tabrest Version 2.1 */
3 /* (C) Copyright to the author Olivier BERTRAND 2018 - 2021 */
4 /* This program is the REST Web API support for MariaDB. */
5 /* The way Connect handles NOSQL data returned by REST queries is */
6 /* just by retrieving it as a file and then leave the existing data */
7 /* type tables (JSON, XML or CSV) process it as usual. */
8 /***********************************************************************/
9
10 /***********************************************************************/
11 /* Definitions needed by the included files. */
12 /***********************************************************************/
13 #include <my_global.h> // All MariaDB stuff
14 #include <mysqld.h>
15 #include <sql_error.h>
16 #if !defined(_WIN32) && !defined(_WINDOWS)
17 #include <sys/types.h>
18 #include <sys/wait.h>
19 #endif // !_WIN32 && !_WINDOWS
20
21 /***********************************************************************/
22 /* Include application header files: */
23 /* global.h is header containing all global declarations. */
24 /* plgdbsem.h is header containing the DB application declarations. */
25 /***********************************************************************/
26 #include "global.h"
27 #include "plgdbsem.h"
28 #include "xtable.h"
29 #include "filamtxt.h"
30 #include "tabdos.h"
31 #include "plgxml.h"
32 #if defined(XML_SUPPORT)
33 #include "tabxml.h"
34 #endif // XML_SUPPORT
35 #include "tabjson.h"
36 #include "tabfmt.h"
37 #include "tabrest.h"
38
39 #if defined(connect_EXPORTS)
40 #define PUSH_WARNING(M) push_warning(current_thd, Sql_condition::WARN_LEVEL_NOTE, 0, M)
41 #else
42 #define PUSH_WARNING(M) htrc(M)
43 #endif
44
45 static XGETREST getRestFnc = NULL;
46 static int Xcurl(PGLOBAL g, PCSZ Http, PCSZ Uri, PCSZ filename);
47
48 /***********************************************************************/
49 /* Xcurl: retrieve the REST answer by executing cURL. */
50 /***********************************************************************/
Xcurl(PGLOBAL g,PCSZ Http,PCSZ Uri,PCSZ filename)51 int Xcurl(PGLOBAL g, PCSZ Http, PCSZ Uri, PCSZ filename)
52 {
53 char buf[512];
54 int rc = 0;
55
56 if (strchr(filename, '"')) {
57 strcpy(g->Message, "Invalid file name");
58 return 1;
59 } // endif filename
60
61 if (Uri) {
62 if (*Uri == '/' || Http[strlen(Http) - 1] == '/')
63 my_snprintf(buf, sizeof(buf)-1, "%s%s", Http, Uri);
64 else
65 my_snprintf(buf, sizeof(buf)-1, "%s/%s", Http, Uri);
66
67 } else
68 my_snprintf(buf, sizeof(buf)-1, "%s", Http);
69
70 #if defined(_WIN32)
71 char cmd[1024];
72 STARTUPINFO si;
73 PROCESS_INFORMATION pi;
74
75 sprintf(cmd, "curl \"%s\" -o \"%s\"", buf, filename);
76
77 ZeroMemory(&si, sizeof(si));
78 si.cb = sizeof(si);
79 ZeroMemory(&pi, sizeof(pi));
80
81 // Start the child process.
82 if (CreateProcess(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
83 // Wait until child process exits.
84 WaitForSingleObject(pi.hProcess, INFINITE);
85
86 // Close process and thread handles.
87 CloseHandle(pi.hProcess);
88 CloseHandle(pi.hThread);
89 } else {
90 sprintf(g->Message, "CreateProcess curl failed (%d)", GetLastError());
91 rc = 1;
92 } // endif CreateProcess
93 #else // !_WIN32
94 char fn[600];
95 pid_t pID;
96
97 // Check if curl package is availabe by executing subprocess
98 FILE *f= popen("command -v curl", "r");
99
100 if (!f) {
101 strcpy(g->Message, "Problem in allocating memory.");
102 return 1;
103 } else {
104 char temp_buff[50];
105 size_t len = fread(temp_buff,1, 50, f);
106
107 if(!len) {
108 strcpy(g->Message, "Curl not installed.");
109 return 1;
110 } else
111 pclose(f);
112
113 } // endif f
114
115 pID = vfork();
116 sprintf(fn, "-o%s", filename);
117
118 if (pID == 0) {
119 // Code executed by child process
120 execlp("curl", "curl", buf, fn, (char*)NULL);
121
122 // If execlp() is successful, we should not reach this next line.
123 strcpy(g->Message, "Unsuccessful execlp from vfork()");
124 exit(1);
125 } else if (pID < 0) {
126 // failed to fork
127 strcpy(g->Message, "Failed to fork");
128 rc = 1;
129 } else {
130 // Parent process
131 wait(NULL); // Wait for the child to terminate
132 } // endif pID
133 #endif // !_WIN32
134
135 return rc;
136 } // end of Xcurl
137
138 /***********************************************************************/
139 /* GetREST: load the Rest lib and get the Rest function. */
140 /***********************************************************************/
GetRestFunction(PGLOBAL g)141 XGETREST GetRestFunction(PGLOBAL g)
142 {
143 if (getRestFnc)
144 return getRestFnc;
145
146 #if !defined(REST_SOURCE)
147 if (trace(515))
148 htrc("Looking for GetRest library\n");
149
150 #if defined(_WIN32) || defined(_WINDOWS)
151 HANDLE Hdll;
152 const char* soname = "GetRest.dll"; // Module name
153
154 if (!(Hdll = LoadLibrary(soname))) {
155 char buf[256];
156 DWORD rc = GetLastError();
157
158 sprintf(g->Message, MSG(DLL_LOAD_ERROR), rc, soname);
159 FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
160 FORMAT_MESSAGE_IGNORE_INSERTS, NULL, rc, 0,
161 (LPTSTR)buf, sizeof(buf), NULL);
162 strcat(strcat(g->Message, ": "), buf);
163 return NULL;
164 } // endif Hdll
165
166 // Get the function returning an instance of the external DEF class
167 if (!(getRestFnc = (XGETREST)GetProcAddress((HINSTANCE)Hdll, "restGetFile"))) {
168 char buf[256];
169 DWORD rc = GetLastError();
170
171 sprintf(g->Message, MSG(PROCADD_ERROR), rc, "restGetFile");
172 FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
173 FORMAT_MESSAGE_IGNORE_INSERTS, NULL, rc, 0,
174 (LPTSTR)buf, sizeof(buf), NULL);
175 strcat(strcat(g->Message, ": "), buf);
176 FreeLibrary((HMODULE)Hdll);
177 return NULL;
178 } // endif getRestFnc
179 #else // !_WIN32
180 void* Hso;
181 const char* error = NULL;
182 const char* soname = "GetRest.so"; // Module name
183
184 // Load the desired shared library
185 if (!(Hso = dlopen(soname, RTLD_LAZY))) {
186 error = dlerror();
187 sprintf(g->Message, MSG(SHARED_LIB_ERR), soname, SVP(error));
188 return NULL;
189 } // endif Hdll
190
191 // Get the function returning an instance of the external DEF class
192 if (!(getRestFnc = (XGETREST)dlsym(Hso, "restGetFile"))) {
193 error = dlerror();
194 sprintf(g->Message, MSG(GET_FUNC_ERR), "restGetFile", SVP(error));
195 dlclose(Hso);
196 return NULL;
197 } // endif getdef
198 #endif // !_WIN32
199 #else // REST_SOURCE
200 getRestFnc = restGetFile;
201 #endif // REST_SOURCE
202
203 return getRestFnc;
204 } // end of GetRestFunction
205
206 /***********************************************************************/
207 /* Return the columns definition to MariaDB. */
208 /***********************************************************************/
RESTColumns(PGLOBAL g,PTOS tp,char * tab,char * db,bool info)209 PQRYRES RESTColumns(PGLOBAL g, PTOS tp, char *tab, char *db, bool info)
210 {
211 PQRYRES qrp= NULL;
212 char filename[_MAX_PATH + 1]; // MAX PATH ???
213 int rc;
214 PCSZ http, uri, fn, ftype;
215 XGETREST grf = NULL;
216 bool curl = GetBooleanTableOption(g, tp, "Curl", false);
217
218 if (!curl && !(grf = GetRestFunction(g)))
219 curl = true;
220
221 http = GetStringTableOption(g, tp, "Http", NULL);
222 uri = GetStringTableOption(g, tp, "Uri", NULL);
223 ftype = GetStringTableOption(g, tp, "Type", "JSON");
224 fn = GetStringTableOption(g, tp, "Filename", NULL);
225
226 if (!fn) {
227 int n, m = strlen(ftype) + 1;
228
229 strcat(strcpy(filename, tab), ".");
230 n = strlen(filename);
231
232 // Fold ftype to lower case
233 for (int i = 0; i < m; i++)
234 filename[n + i] = tolower(ftype[i]);
235
236 fn = filename;
237 tp->subtype = PlugDup(g, fn);
238 sprintf(g->Message, "No file name. Table will use %s", fn);
239 PUSH_WARNING(g->Message);
240 } // endif fn
241
242 // We used the file name relative to recorded datapath
243 PlugSetPath(filename, fn, db);
244 remove(filename);
245
246 // Retrieve the file from the web and copy it locally
247 if (curl)
248 rc = Xcurl(g, http, uri, filename);
249 else
250 rc = grf(g->Message, trace(515), http, uri, filename);
251
252 if (rc) {
253 strcpy(g->Message, "Cannot access to curl nor casablanca");
254 return NULL;
255 } else if (!stricmp(ftype, "JSON"))
256 qrp = JSONColumns(g, db, NULL, tp, info);
257 else if (!stricmp(ftype, "CSV"))
258 qrp = CSVColumns(g, NULL, tp, info);
259 #if defined(XML_SUPPORT)
260 else if (!stricmp(ftype, "XML"))
261 qrp = XMLColumns(g, db, tab, tp, info);
262 #endif // XML_SUPPORT
263 else
264 sprintf(g->Message, "Usupported file type %s", ftype);
265
266 return qrp;
267 } // end of RESTColumns
268
269 /* -------------------------- Class RESTDEF -------------------------- */
270
271 /***********************************************************************/
272 /* DefineAM: define specific AM block values. */
273 /***********************************************************************/
DefineAM(PGLOBAL g,LPCSTR am,int poff)274 bool RESTDEF::DefineAM(PGLOBAL g, LPCSTR am, int poff)
275 {
276 char filename[_MAX_PATH + 1];
277 int rc = 0, n;
278 bool xt = trace(515);
279 LPCSTR ftype;
280 XGETREST grf = NULL;
281 bool curl = GetBoolCatInfo("Curl", false);
282
283 if (!curl && !(grf = GetRestFunction(g)))
284 curl = true;
285
286 ftype = GetStringCatInfo(g, "Type", "JSON");
287
288 if (xt)
289 htrc("ftype = %s am = %s\n", ftype, SVP(am));
290
291 n = (!stricmp(ftype, "JSON")) ? 1
292 #if defined(XML_SUPPORT)
293 : (!stricmp(ftype, "XML")) ? 2
294 #endif // XML_SUPPORT
295 : (!stricmp(ftype, "CSV")) ? 3 : 0;
296
297 if (n == 0) {
298 htrc("DefineAM: Unsupported REST table type %s\n", ftype);
299 sprintf(g->Message, "Unsupported REST table type %s", ftype);
300 return true;
301 } // endif n
302
303 Http = GetStringCatInfo(g, "Http", NULL);
304 Uri = GetStringCatInfo(g, "Uri", NULL);
305 Fn = GetStringCatInfo(g, "Filename", NULL);
306
307 // We used the file name relative to recorded datapath
308 PlugSetPath(filename, Fn, GetPath());
309 remove(filename);
310
311 // Retrieve the file from the web and copy it locally
312 if (curl) {
313 rc = Xcurl(g, Http, Uri, filename);
314 xtrc(515, "Return from Xcurl: rc=%d\n", rc);
315 } else {
316 rc = grf(g->Message, xt, Http, Uri, filename);
317 xtrc(515, "Return from restGetFile: rc=%d\n", rc);
318 } // endelse
319
320 if (rc) {
321 // strcpy(g->Message, "Cannot access to curl nor casablanca");
322 return true;
323 } else switch (n) {
324 case 1: Tdp = new (g) JSONDEF; break;
325 #if defined(XML_SUPPORT)
326 case 2: Tdp = new (g) XMLDEF; break;
327 #endif // XML_SUPPORT
328 case 3: Tdp = new (g) CSVDEF; break;
329 default: Tdp = NULL;
330 } // endswitch n
331
332 // Do make the table/view definition
333 if (Tdp && Tdp->Define(g, Cat, Name, Schema, "REST"))
334 Tdp = NULL; // Error occured
335
336 if (xt)
337 htrc("Tdp defined\n", rc);
338
339 // Return true in case of error
340 return (Tdp == NULL);
341 } // end of DefineAM
342
343 /***********************************************************************/
344 /* GetTable: makes a new Table Description Block. */
345 /***********************************************************************/
GetTable(PGLOBAL g,MODE m)346 PTDB RESTDEF::GetTable(PGLOBAL g, MODE m)
347 {
348 if (trace(515))
349 htrc("REST GetTable mode=%d\n", m);
350
351 if (m != MODE_READ && m != MODE_READX && m != MODE_ANY) {
352 strcpy(g->Message, "REST tables are currently read only");
353 return NULL;
354 } // endif m
355
356 return Tdp->GetTable(g, m); // Leave file type do the job
357 } // end of GetTable
358
359 /* ---------------------- End of Class RESTDEF ----------------------- */
360