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