1 #ifndef UTILS_H
2 #define UTILS_H
3 
4 #include <algorithm>
5 #include <stdio.h>
6 #include <unistd.h>
7 #include <stdarg.h>
8 #include <stdio.h>
9 #include <string>
10 #include <vector>
11 #include <Rcpp.h>
12 #include "optional.h"
13 #include "thread.h"
14 #include "timegm.h"
15 
16 // A callback for deleting objects on the main thread using later(). This is
17 // needed when the object is an Rcpp object or contains one, because deleting
18 // such objects invoke R's memory management functions.
19 template <typename T>
deleter_main(void * obj)20 void deleter_main(void* obj) {
21   ASSERT_MAIN_THREAD()
22   // later() passes a void* to the callback, so we have to cast it.
23   T* typed_obj = reinterpret_cast<T*>(obj);
24 
25   try {
26     delete typed_obj;
27   } catch (...) {}
28 }
29 
30 // Does the same as deleter_main, but checks that it's running on the
31 // background thread (when thread debugging is enabled).
32 template <typename T>
deleter_background(void * obj)33 void deleter_background(void* obj) {
34   ASSERT_BACKGROUND_THREAD()
35   T* typed_obj = reinterpret_cast<T*>(obj);
36 
37   try {
38     delete typed_obj;
39   } catch (...) {}
40 }
41 
42 // It's not safe to call REprintf from the background thread but we need some
43 // way to output error messages. R CMD check does not it if the code uses the
44 // symbols stdout, stderr, and printf, so this function is a way to avoid
45 // those. It's to calling `fprintf(stderr, ...)`.
err_printf(const char * fmt,...)46 inline void err_printf(const char *fmt, ...) {
47   const size_t max_size = 4096;
48   char buf[max_size];
49 
50   va_list args;
51   va_start(args, fmt);
52   int n = vsnprintf(buf, max_size, fmt, args);
53   va_end(args);
54 
55   if (n == -1)
56     return;
57 
58   ssize_t res = write(STDERR_FILENO, buf, n);
59   // This is here simply to avoid a warning about "ignoring return value" of
60   // the write(), on some compilers. (Seen with gcc 4.4.7 on RHEL 6)
61   res += 0;
62 }
63 
64 
65 // ============================================================================
66 // Logging
67 // ============================================================================
68 
69 enum LogLevel {
70   LOG_OFF,
71   LOG_ERROR,
72   LOG_WARN,
73   LOG_INFO,
74   LOG_DEBUG
75 };
76 
77 void debug_log(const std::string& msg, LogLevel level);
78 
79 // ============================================================================
80 
81 
82 // Indexing into an empty vector causes assertion failures on some platforms
83 template <typename T>
safe_vec_addr(std::vector<T> & vec)84 T* safe_vec_addr(std::vector<T>& vec) {
85   return vec.size() ? &vec[0] : NULL;
86 }
87 
88 // Indexing into an empty vector causes assertion failures on some platforms
safe_str_addr(const std::string & str)89 inline const char* safe_str_addr(const std::string& str) {
90   return str.size() ? &str[0] : NULL;
91 }
92 
to_lower(const std::string & str)93 inline std::string to_lower(const std::string& str) {
94   std::string lowered = str;
95   std::transform(lowered.begin(), lowered.end(), lowered.begin(), tolower);
96   return lowered;
97 }
98 
99 template <typename T>
toString(T x)100 std::string toString(T x) {
101   std::stringstream ss;
102   ss << x;
103   return ss.str();
104 }
105 
106 // This is used for converting an Rcpp named vector (T2) to a std::map.
107 template <typename T1, typename T2>
toMap(T2 x)108 std::map<std::string, T1> toMap(T2 x) {
109   ASSERT_MAIN_THREAD()
110 
111   std::map<std::string, T1> strmap;
112 
113   if (x.size() == 0) {
114     return strmap;
115   }
116 
117   Rcpp::CharacterVector names = x.names();
118   if (names.isNULL()) {
119     throw Rcpp::exception("Error converting R object to map<string, T>: vector does not have names.");
120   }
121 
122   for (int i=0; i<x.size(); i++) {
123     std::string name  = Rcpp::as<std::string>(names[i]);
124     T1          value = Rcpp::as<T1>         (x[i]);
125     if (name == "") {
126       throw Rcpp::exception("Error converting R object to map<string, T>: element has empty name.");
127     }
128 
129     strmap.insert(
130       std::pair<std::string, T1>(name, value)
131     );
132   }
133 
134   return strmap;
135 }
136 
137 // A wrapper for Rcpp::as. If the R value is NULL, this returns nullopt;
138 // otherwise it returns the usual value that Rcpp::as returns, wrapped in
139 // std::experimental::optional<T2>.
140 template <typename T1, typename T2>
optional_as(T2 value)141 std::experimental::optional<T1> optional_as(T2 value) {
142   if (value.isNULL()) {
143     return std::experimental::nullopt;
144   }
145   return std::experimental::optional<T1>( Rcpp::as<T1>(value) );
146 }
147 
148 // A wrapper for Rcpp::wrap. If the C++ value is missing, this returns the
149 // R value NULL; otherwise it returns the usual value that Rcpp::wrap returns, after
150 // unwrapping from the std::experimental::optional<T>.
151 template <typename T>
optional_wrap(std::experimental::optional<T> value)152 Rcpp::RObject optional_wrap(std::experimental::optional<T> value) {
153   if (!value.has_value()) {
154     return R_NilValue;
155   }
156   return Rcpp::wrap(*value);
157 }
158 
159 
160 // as() and wrap() for ResponseHeaders. Since the ResponseHeaders typedef is
161 // in constants.h and this file doesn't include constants.h, we'll define them
162 // using the actual vector type instead of the ResponseHeaders typedef.
163 // (constants.h doesn't include Rcpp.h so we can't define these functions
164 // there.)
165 namespace Rcpp {
as(SEXP x)166   template <> inline std::vector<std::pair<std::string, std::string> > as(SEXP x) {
167     ASSERT_MAIN_THREAD()
168     Rcpp::CharacterVector headers(x);
169     Rcpp::CharacterVector names = headers.names();
170 
171     if (names.isNULL()) {
172       throw Rcpp::exception("All values must be named.");
173     }
174 
175     std::vector<std::pair<std::string, std::string> > result;
176 
177     for (int i=0; i<headers.size(); i++) {
178       std::string name = Rcpp::as<std::string>(names[i]);
179       if (name == "") {
180         throw Rcpp::exception("All values must be named.");
181       }
182 
183       std::string value = Rcpp::as<std::string>(headers[i]);
184 
185       result.push_back(std::make_pair(name, value));
186     }
187 
188     return result;
189   }
190 
wrap(const std::vector<std::pair<std::string,std::string>> & x)191   template <> inline SEXP wrap(const std::vector<std::pair<std::string, std::string> > &x) {
192     ASSERT_MAIN_THREAD()
193 
194     std::vector<std::string> values(x.size());
195     std::vector<std::string> names(x.size());
196 
197     for (unsigned int i=0; i<x.size(); i++) {
198       names[i]  = x[i].first;
199       values[i] = x[i].second;
200     }
201 
202     Rcpp::CharacterVector result = Rcpp::wrap(values);
203     result.attr("names") = Rcpp::wrap(names);
204 
205     return result;
206   }
207 }
208 
209 
210 // Return a date string in the format required for the HTTP Date header. For
211 // example: "Wed, 21 Oct 2015 07:28:00 GMT"
http_date_string(const time_t & t)212 inline std::string http_date_string(const time_t& t) {
213   struct tm timeptr;
214   #ifdef _WIN32
215   gmtime_s(&timeptr, &t);
216   #else
217   gmtime_r(&t, &timeptr);
218   #endif
219 
220   std::string day_name;
221   switch(timeptr.tm_wday) {
222     case 0:  day_name = "Sun"; break;
223     case 1:  day_name = "Mon"; break;
224     case 2:  day_name = "Tue"; break;
225     case 3:  day_name = "Wed"; break;
226     case 4:  day_name = "Thu"; break;
227     case 5:  day_name = "Fri"; break;
228     case 6:  day_name = "Sat"; break;
229     default: return "";
230   }
231 
232   std::string month_name;
233   switch(timeptr.tm_mon) {
234     case 0:  month_name = "Jan"; break;
235     case 1:  month_name = "Feb"; break;
236     case 2:  month_name = "Mar"; break;
237     case 3:  month_name = "Apr"; break;
238     case 4:  month_name = "May"; break;
239     case 5:  month_name = "Jun"; break;
240     case 6:  month_name = "Jul"; break;
241     case 7:  month_name = "Aug"; break;
242     case 8:  month_name = "Sep"; break;
243     case 9:  month_name = "Oct"; break;
244     case 10: month_name = "Nov"; break;
245     case 11: month_name = "Dec"; break;
246     default: return "";
247   }
248 
249   const int maxlen = 50;
250   char res[maxlen];
251   snprintf(res, maxlen, "%s, %02d %s %04d %02d:%02d:%02d GMT",
252     day_name.c_str(),
253     timeptr.tm_mday,
254     month_name.c_str(),
255     timeptr.tm_year + 1900,
256     timeptr.tm_hour,
257     timeptr.tm_min,
258     timeptr.tm_sec
259   );
260 
261   return std::string(res);
262 }
263 
264 // Given a date string of format "Wed, 21 Oct 2015 07:28:00 GMT", return a
265 // time_t representing that time. If the date is malformed, then return 0.
266 time_t parse_http_date_string(const std::string& date);
267 
268 // Compares two strings in constant time. Returns true if they are the same;
269 // false otherwise.
constant_time_compare(const std::string & a,const std::string & b)270 inline bool constant_time_compare(const std::string& a, const std::string& b) {
271   if (a.length() != b.length())
272     return false;
273 
274   volatile const char* ac = a.c_str();
275   volatile const char* bc = b.c_str();
276   volatile char result = 0;
277   int len = a.length();
278 
279   for (int i=0; i<len; i++) {
280     result |= ac[i] ^ bc[i];
281   }
282 
283   return (result == 0);
284 }
285 
286 #endif
287