Vuo  2.0.2
VuoUrlFetch.c
Go to the documentation of this file.
1 
10 #include <string.h>
11 #include <sys/errno.h>
12 #include <unistd.h>
13 
14 #include <CoreFoundation/CoreFoundation.h>
15 
16 #include <curl/curl.h>
17 
18 #include "module.h"
19 #include "VuoBase64.h"
20 
21 #ifdef VUO_COMPILER
23  "title" : "VuoUrlFetch",
24  "dependencies" : [
25  "VuoBase64",
26  "VuoText",
27  "VuoUrl",
28  "curl",
29  "crypto",
30  "ssl",
31  "z"
32  ]
33  });
34 #endif
35 
39 __attribute__((constructor)) static void VuoUrl_init(void)
40 {
41  curl_global_init(CURL_GLOBAL_DEFAULT);
42 }
43 
48 {
49  char *memory;
50  size_t size;
51 };
52 
57 static size_t VuoUrl_curlCallback(void *contents, size_t size, size_t nmemb, void *userp)
58 {
59  size_t realsize = size * nmemb;
60  struct VuoUrl_curlBuffer *mem = (struct VuoUrl_curlBuffer *)userp;
61 
62  mem->memory = (char *)realloc(mem->memory, mem->size + realsize + 1);
63  if(mem->memory == NULL)
64  {
65  VUserLog("Error: realloc() returned NULL (out of memory?).");
66  return 0;
67  }
68 
69  memcpy(&(mem->memory[mem->size]), contents, realsize);
70  mem->size += realsize;
71  mem->memory[mem->size] = 0;
72 
73  return realsize;
74 }
75 
79 void VuoUrlFetch_convertToUTF8(struct VuoUrl_curlBuffer *buffer, CFStringEncoding sourceEncoding)
80 {
81  CFStringRef cf = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, (const UInt8 *)buffer->memory, strlen(buffer->memory), sourceEncoding, true, kCFAllocatorMalloc);
82  if (!cf)
83  {
84  VuoText sourceEncodingName = VuoText_makeFromCFString(CFStringGetNameOfEncoding(sourceEncoding));
85  VuoLocal(sourceEncodingName);
86  VUserLog("Error: Couldn't convert response from %s to UTF-8.", sourceEncodingName);
87  return;
88  }
89 
90  CFIndex maxBytes = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cf), kCFStringEncodingUTF8) + 1;
91  char *outBuffer = calloc(1, maxBytes);
92  CFStringGetCString(cf, outBuffer, maxBytes, kCFStringEncodingUTF8);
93  CFRelease(cf);
94 
95  buffer->memory = outBuffer;
96  buffer->size = strlen(outBuffer);
97 
98  if (VuoIsDebugEnabled())
99  {
100  VuoText sourceEncodingName = VuoText_makeFromCFString(CFStringGetNameOfEncoding(sourceEncoding));
101  VuoLocal(sourceEncodingName);
102  VUserLog("Converted response from %s to UTF-8.", sourceEncodingName);
103  }
104 }
105 
117 bool VuoUrl_fetch(const char *url, void **data, unsigned int *dataLength)
118 {
119  if (VuoText_isEmpty(url))
120  return false;
121 
122  if (strncmp(url, "data:", 5) == 0)
123  {
124  // https://tools.ietf.org/html/rfc2397
125  // data:[<mediatype>][;base64],<data>
126 
127  const char *urlData = url + 5;
128 
129  // Skip past the <mediatype> tag(s); we only care about the <data> part.
130  urlData = strchr(urlData, ',');
131  if (!urlData)
132  return false;
133 
134  // Skip past the comma.
135  ++urlData;
136 
137  // Does the pre-<data> part of the URI end with `;base64`?
138  bool isBase64 = (urlData - url >= 5 /* data: */ + 7 /* ;base64 */ + 1)
139  && (strncmp(urlData - 7 - 1, ";base64", 7) == 0);
140 
141  VuoText decoded = VuoUrl_decodeRFC3986(urlData);
142  VuoRetain(decoded);
143 
144  if (isBase64)
145  {
146  long long outputLength;
147  *data = VuoBase64_decode(decoded, &outputLength);
148  *dataLength = outputLength;
149  }
150  else
151  {
152  *data = strdup(decoded);
153  *dataLength = strlen(decoded);
154  }
155 
156  VuoRelease(decoded);
157  return true;
158  }
159 
161  VuoLocal(resolvedUrl);
162 
163  VuoText posixPath = VuoUrl_getPosixPath(resolvedUrl);
164  VuoLocal(posixPath);
165  if (posixPath)
166  {
167  FILE *fp = fopen(posixPath, "rb");
168  if (!fp)
169  {
170  VUserLog("Error: Could not read file \"%s\"", posixPath);
171  return false;
172  }
173  VuoDefer(^{ fclose(fp); });
174 
175  fseek(fp, 0, SEEK_END);
176  *dataLength = ftell(fp);
177  rewind(fp);
178  *data = (char *)malloc(*dataLength + 1);
179  if (fread(*data, 1, *dataLength, fp) != *dataLength)
180  {
181  free(*data);
182  VUserLog("Error: Could not read all the data from file \"%s\": %s", posixPath, strerror(errno));
183  return false;
184  }
185  ((char *)*data)[*dataLength] = 0;
186  return true;
187  }
188 
189 
190  struct VuoUrl_curlBuffer buffer = {NULL, 0};
191  CURL *curl;
192  CURLcode res;
193 
194  curl = curl_easy_init();
195  if (!curl)
196  {
197  VUserLog("Error: cURL initialization failed.");
198  return false;
199  }
200  VuoDefer(^{ curl_easy_cleanup(curl); });
201 
202  curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); // Don't use signals for the timeout logic, since they're not thread-safe.
203 
204  VDebugLog("GET %s", resolvedUrl);
205  curl_easy_setopt(curl, CURLOPT_URL, resolvedUrl);
206 
207  curl_easy_setopt(curl, CURLOPT_USERAGENT, "Vuo/" VUO_VERSION_STRING " (https://vuo.org/)");
208 
209  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
210  curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
211 
212  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, VuoUrl_curlCallback);
213  curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&buffer);
214 
215  res = curl_easy_perform(curl);
216  if(res != CURLE_OK)
217  {
218  VUserLog("Error: cURL request failed: %s (%d)", curl_easy_strerror(res), res);
219  return false;
220  }
221 
222  VDebugLog("Received %zu bytes.", buffer.size);
223 
224  // Convert non-UTF-8 charsets to UTF-8.
225  char *contentType = NULL;
226  res = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &contentType);
227  if (res == CURLE_OK && contentType)
228  {
229  // https://tools.ietf.org/html/rfc7231#section-3.1.1.5
230  VDebugLog("Content-Type: %s", contentType);
231 
232  // UTF-8 is a superset of US-ASCII, so no need to convert.
233  if (strcasecmp(contentType, "text/html; charset=utf-8") == 0
234  || strcasecmp(contentType, "text/html; charset=us-ascii") == 0)
235  ;
236 
237  // According to https://www.ietf.org/rfc/rfc2854.txt, the default charset for `text/html` is `ISO-8859-1`, so fall back to that if no charset is specified.
238  else if (strcasecmp(contentType, "text/html; charset=iso-8859-1") == 0
239  || strcasecmp(contentType, "text/html") == 0)
240  VuoUrlFetch_convertToUTF8(&buffer, kCFStringEncodingISOLatin1);
241 
242  // We're not currently attempting to handle charsets other than UTF-8, US-ASCII, and ISO-8859-1.
243  }
244 
245  *data = buffer.memory;
246  *dataLength = buffer.size;
247  return true;
248 }