tdaq-develop-2025-02-12
ECLConnection.cpp
1 #include <openssl/md5.h>
2 #include <otsdaq-utilities/ECLWriter/ECLConnection.h>
3 #include <boost/algorithm/string.hpp>
4 #include <cstring>
5 #include <fstream>
6 #include <iomanip>
7 #include <sstream>
8 #include "otsdaq/Macros/CoutMacros.h"
9 #include "otsdaq/Macros/StringMacros.h"
10 #include "otsdaq/MessageFacility/MessageFacility.h"
11 
12 //==============================================================================
13 ECLConnection::ECLConnection(std::string user, std::string pwd, std::string url)
14 {
15  _user = user;
16  _pwd = pwd;
17  _url = url;
18 
19  if(!Get("/secureURL", _safe_url))
20  {
21  __SS__ << "Could not retrieve safe URL from input url '" << _url << "'" << __E__;
22  __SS_THROW__;
23  }
24  __COUTTV__(_safe_url);
25 
26  srand(time(NULL));
27 } //end ECLConnection()
28 
29 //==============================================================================
30 size_t ECLConnection::WriteMemoryCallback(char* data,
31  size_t size,
32  size_t nmemb,
33  std::string* buffer)
34 {
35  size_t realsize = 0;
36 
37  if(buffer != NULL)
38  {
39  buffer->append(data, size * nmemb);
40  realsize = size * nmemb;
41  }
42 
43  return realsize;
44 } //end WriteMemoryCallback()
45 
46 //==============================================================================
48 bool ECLConnection::Get(std::string s, std::string& response)
49 {
50  response = "NULL";
51 
52  std::string rndString = MakeSaltString();
53  std::string mySalt = "salt=" + rndString;
54  std::string fullURL;
55  bool needSignature = false;
56  if(s != "/secureURL") //add salt
57  {
58  needSignature = true;
59  //in case of dynamic server downtime, if safe_url is blank, get safe_url
60 
61  if(time(0) - _lastOperationTime > 5 * 60 /* 5 minutes */)
62  {
63  __COUTT__ << "Clearing safe URL and re-requesting..." << __E__;
64  _safe_url = "";
65  }
66 
67  __COUTTV__(_safe_url);
68  if(_safe_url == "" && !Get("/secureURL", _safe_url))
69  {
70  __SS__ << "Could not retrieve safe URL from input url '" << _url << "'"
71  << __E__;
72  __SS_THROW__;
73  }
74  __COUTTV__(_safe_url);
75 
76  fullURL = _safe_url + s;
77  if(s.size() && s[s.size() - 1] == '?')
78  ; //do nothing
79  else if(fullURL.find('?') != std::string::npos)
80  fullURL += '&';
81  else
82  fullURL += '?';
83  fullURL += mySalt;
84  }
85  else
86  fullURL = _url + s;
87 
88  __COUT__ << "ECL GET request to " << fullURL << std::endl;
89  __COUTVS__(20, needSignature);
90 
91  std::string xSig;
92  if(needSignature)
93  {
94  std::string myData = fullURL.substr(fullURL.find('?') + 1);
95  __COUTTV__(myData);
96  myData += ":" + _pwd + ":";
97  // myData is now the ECL Hash string -- DO NOT PRINT contains pw
98 
99  unsigned char resultMD5[MD5_DIGEST_LENGTH];
100  MD5((unsigned char*)myData.c_str(), myData.size(), resultMD5);
101 
102  char buf[3];
103  for(auto i = 0; i < MD5_DIGEST_LENGTH; i++)
104  {
105  sprintf(buf, "%02x", resultMD5[i]);
106  xSig.append(buf);
107  }
108  __COUT_TYPE__(TLVL_DEBUG + 20)
109  << __COUT_HDR__ << "ECL MD5 Signature is: " << xSig << std::endl;
110  }
111 
112  char errorBuffer[CURL_ERROR_SIZE];
113  std::string responseBuffer;
114  CURL* curl_handle;
115 
116  curl_global_init(CURL_GLOBAL_ALL);
117 
118  /* init the curl session */
119  curl_handle = curl_easy_init();
120 
121  curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, errorBuffer);
122 
123  /* specify URL to get */
124 
125  if(needSignature)
126  {
127  struct curl_slist* headers = NULL;
128  std::string buff = "X-User: " + _user;
129  headers = curl_slist_append(headers, buff.c_str());
130  headers = curl_slist_append(headers, "Content-type: text/xml");
131  headers = curl_slist_append(headers, "X-Signature-Method: md5");
132  buff = "X-Signature: " + xSig;
133  headers = curl_slist_append(headers, buff.c_str());
134 
135  curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headers);
136  }
137  curl_easy_setopt(curl_handle, CURLOPT_URL, fullURL.c_str());
138  curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1); // Allow redirects
139 
140  /* send all data to this function */
141  curl_easy_setopt(
142  curl_handle, CURLOPT_WRITEFUNCTION, ECLConnection::WriteMemoryCallback);
143 
144  /* we pass our 'chunk' struct to the callback function */
145  curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &responseBuffer);
146 
147  /* some servers don't like requests that are made without a user-agent
148  field, so we provide one */
149  curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0");
150 
151  /* get it! */
152  CURLcode result = curl_easy_perform(curl_handle);
153 
154  if(result != CURLE_OK)
155  {
156  _safe_url = ""; //clear on error
157  __SS__ << "Error: [" << result << "] - " << errorBuffer << std::endl;
158 
159  __COUT_TYPE__(TLVL_DEBUG + 20) << __COUT_HDR__ << "ECL Cleanup" << std::endl;
160  // cleanup curl stuff
161  curl_easy_cleanup(curl_handle);
162  // curl_slist_free_all(headers);
163  curl_global_cleanup();
164  __SS_THROW__;
165  }
166 
167  __COUT_TYPE__(TLVL_DEBUG + 20) << __COUT_HDR__ << "ECL Cleanup" << std::endl;
168  // cleanup curl stuff
169  curl_easy_cleanup(curl_handle);
170  // curl_slist_free_all(headers);
171  curl_global_cleanup();
172 
173  if(responseBuffer.find("Error") != std::string::npos ||
174  responseBuffer.find("301 Moved Permanently") != std::string::npos)
175  {
176  _safe_url = ""; //clear on error
177  __SS__ << "Error found in request: " << responseBuffer << __E__;
178  __SS_THROW__;
179  }
180 
181  __COUTVS__(2, responseBuffer);
182  response = responseBuffer;
183 
184  _lastOperationTime = time(0);
185  return true;
186 } //end Get()
187 
188 //==============================================================================
189 bool ECLConnection::Search(std::string /*s*/) { return false; }
190 
191 //==============================================================================
192 std::string ECLConnection::MakeSaltString()
193 {
194  std::string rndString = "";
195 
196  std::string chars(
197  "abcdefghijklmnopqrstuvwxyz"
198  // "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
199  "1234567890");
200  for(int i = 0; i < 10; ++i)
201  {
202  rndString += chars[rand() % chars.size()];
203  }
204 
205  return rndString;
206 } //end MakeSaltString()
207 
208 //==============================================================================
209 bool ECLConnection::Post(ECLEntry_t& e)
210 {
211  //in case of dynamic server downtime, get safe_url for each POST
212  if(!Get("/secureURL", _safe_url))
213  {
214  __SS__ << "Could not retrieve safe URL from input url '" << _url << "'" << __E__;
215  __SS_THROW__;
216  }
217  __COUTTV__(_safe_url);
218 
219  std::string rndString = MakeSaltString();
220 
221  std::string myURL = "/E/xml_post?";
222  std::string mySalt = "salt=" + rndString;
223  std::string fullURL = _safe_url + myURL + mySalt;
224 
225  std::string myData = mySalt + ":" + _pwd + ":";
226 
227  // create text from xml form, but need to remove all \n's
228  std::ostringstream oss;
229  entry(oss, e);
230  std::string eclString = oss.str();
231  __COUT__ << "ECL XML is: " << eclString << std::endl;
232  // std::string eclString = e.entry();
233  eclString = eclString.substr(eclString.find_first_of(">") + 2);
234 
235  while(eclString.find('\n') != std::string::npos)
236  {
237  eclString = eclString.erase(eclString.find('\n'), 1);
238  }
239  while(eclString.find('\r') != std::string::npos)
240  {
241  eclString = eclString.erase(eclString.find('\r'), 1);
242  }
243  while(eclString.find(" <") != std::string::npos)
244  {
245  eclString = eclString.erase(eclString.find(" <"), 1);
246  }
247  boost::trim(eclString);
248  myData += eclString;
249 
250  // myData is now the ECL Hash string -- DO NOT PRINT contains pw
251 
252  unsigned char resultMD5[MD5_DIGEST_LENGTH];
253  MD5((unsigned char*)myData.c_str(), myData.size(), resultMD5);
254 
255  std::string xSig;
256  char buf[3];
257  for(auto i = 0; i < MD5_DIGEST_LENGTH; i++)
258  {
259  sprintf(buf, "%02x", resultMD5[i]);
260  xSig.append(buf);
261  }
262  __COUT__ << "ECL MD5 Signature is: " << xSig << std::endl;
263 
264  CURL* curl_handle;
265  char errorBuffer[CURL_ERROR_SIZE];
266 
267  curl_global_init(CURL_GLOBAL_ALL);
268 
269  /* init the curl session */
270 
271  curl_handle = curl_easy_init();
272 
273  curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, errorBuffer);
274 
275  /* specify URL to get */
276 
277  struct curl_slist* headers = NULL;
278  std::string buff = "X-User: " + _user;
279  headers = curl_slist_append(headers, buff.c_str());
280  headers = curl_slist_append(headers, "Content-type: text/xml");
281  headers = curl_slist_append(headers, "X-Signature-Method: md5");
282  buff = "X-Signature: " + xSig;
283  headers = curl_slist_append(headers, buff.c_str());
284 
285  const char* estr = eclString.c_str();
286 
287  __COUT__ << "ECL Setting message headers" << std::endl;
288  curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, estr);
289  curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headers);
290  curl_easy_setopt(curl_handle, CURLOPT_URL, fullURL.c_str());
291  curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1); // Allow redirects
292  // curl_easy_setopt(curl_handle, CURLOPT_VERBOSE,1);
293 
294  // send all data to this function
295  std::string responseBuffer;
296  curl_easy_setopt(
297  curl_handle, CURLOPT_WRITEFUNCTION, ECLConnection::WriteMemoryCallback);
298  // we pass our 'memoryspace' struct to the callback function
299  curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void*)&responseBuffer);
300 
301  // post it!
302 
303  __COUT__ << "ECL Posting message to " << fullURL << std::endl;
304  CURLcode result = curl_easy_perform(curl_handle);
305 
306  if(result != CURLE_OK)
307  {
308  _safe_url = ""; //clear on error
309  __SS__ << "Error: [" << result << "] - " << errorBuffer << std::endl;
310 
311  __COUTT__ << "ECL Cleanup" << std::endl;
312  // cleanup curl stuff
313  curl_easy_cleanup(curl_handle);
314  curl_slist_free_all(headers);
315  curl_global_cleanup();
316  __SS_THROW__;
317  }
318 
319  __COUTT__ << "ECL Cleanup" << std::endl;
320  // cleanup curl stuff
321  curl_easy_cleanup(curl_handle);
322  curl_slist_free_all(headers);
323  curl_global_cleanup();
324 
325  if(responseBuffer.find("Error") != std::string::npos ||
326  responseBuffer.find("301 Moved Permanently") != std::string::npos)
327  {
328  _safe_url = ""; //clear on error
329  __SS__ << "Error found in request: " << responseBuffer << __E__;
330  __SS_THROW__;
331  }
332 
333  __COUTTV__(responseBuffer);
334 
335  return true;
336 } //end Post()
337 
338 //==============================================================================
339 std::string ECLConnection::EscapeECLString(std::string input)
340 {
341  std::string output = input;
342  size_t pos = output.find('&');
343  while(pos != std::string::npos)
344  {
345  output = output.replace(pos, 1, "&amp;");
346  pos = output.find('&', pos + 2);
347  }
348 
349  pos = output.find('"');
350  while(pos != std::string::npos)
351  {
352  output = output.replace(pos, 1, "&quot;");
353  pos = output.find('"', pos + 1);
354  }
355 
356  pos = output.find('\'');
357  while(pos != std::string::npos)
358  {
359  output = output.replace(pos, 1, "&apos;");
360  pos = output.find('\'', pos + 1);
361  }
362 
363  pos = output.find('<');
364  while(pos != std::string::npos)
365  {
366  output = output.replace(pos, 1, "&lt;");
367  pos = output.find('<', pos + 1);
368  }
369 
370  pos = output.find('>');
371  while(pos != std::string::npos)
372  {
373  output = output.replace(pos, 1, "&gt;");
374  pos = output.find('>', pos + 1);
375  }
376 
377  return output;
378 } //end EscapeECLString()
379 
380 //==============================================================================
381 Attachment_t ECLConnection::MakeAttachmentImage(std::string const& imageFileName)
382 {
383  Attachment_t attachment;
384  std::string fileNameShort = imageFileName;
385  if(fileNameShort.rfind('/') != std::string::npos)
386  {
387  fileNameShort = fileNameShort.substr(imageFileName.rfind('/'));
388  }
389  std::ifstream fin(imageFileName, std::ios::in | std::ios::binary);
390  fin.seekg(0, std::ios::end);
391  std::streamsize size = fin.tellg();
392  fin.seekg(0, std::ios::beg);
393  std::vector<char> buffer(size);
394  if(!fin.read(buffer.data(), size))
395  {
396  __COUT__ << "ECLConnection: Error reading file: " << imageFileName << std::endl;
397  attachment = Attachment_t("Image=none", fileNameShort);
398  }
399  else
400  {
401  attachment = Attachment_t(
402  ::xml_schema::base64_binary(&buffer[0], size), "image", fileNameShort);
403  }
404  return attachment;
405 } //end MakeAttachmentImage()
406 
407 //==============================================================================
408 Attachment_t ECLConnection::MakeAttachmentFile(std::string const& fileName)
409 {
410  Attachment_t attachment;
411  std::string fileNameShort = fileName;
412  if(fileNameShort.rfind('/') != std::string::npos)
413  {
414  fileNameShort = fileNameShort.substr(fileName.rfind('/'));
415  }
416  std::ifstream fin(fileName, std::ios::in | std::ios::binary);
417  fin.seekg(0, std::ios::end);
418  std::streamsize size = fin.tellg();
419  fin.seekg(0, std::ios::beg);
420 
421  std::vector<char> buffer(size);
422  if(!fin.read(buffer.data(), size))
423  {
424  __COUT__ << "ECLConnection: Error reading file: " << fileName;
425  attachment = Attachment_t("File=none", fileNameShort);
426  }
427  else
428  {
429  attachment = Attachment_t(
430  ::xml_schema::base64_binary(&buffer[0], size), "file", fileNameShort);
431  }
432  return attachment;
433 } //end MakeAttachmentFile()
bool Get(std::string, std::string &)
Note: make sure GET url parameter 's' is URI encoded.
Definition: ECL.hxx:495