Vuo  2.0.0
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 <curl/curl.h>
15 
16 #include "module.h"
17 #include "VuoBase64.h"
18 
19 #ifdef VUO_COMPILER
21  "title" : "VuoUrlFetch",
22  "dependencies" : [
23  "VuoBase64",
24  "VuoText",
25  "VuoUrl",
26  "curl",
27  "crypto",
28  "ssl",
29  "z"
30  ]
31  });
32 #endif
33 
37 __attribute__((constructor)) static void VuoUrl_init(void)
38 {
39  curl_global_init(CURL_GLOBAL_DEFAULT);
40 }
41 
46 {
47  char *memory;
48  size_t size;
49 };
50 
55 static size_t VuoUrl_curlCallback(void *contents, size_t size, size_t nmemb, void *userp)
56 {
57  size_t realsize = size * nmemb;
58  struct VuoUrl_curlBuffer *mem = (struct VuoUrl_curlBuffer *)userp;
59 
60  mem->memory = (char *)realloc(mem->memory, mem->size + realsize + 1);
61  if(mem->memory == NULL)
62  {
63  VUserLog("Error: realloc() returned NULL (out of memory?).");
64  return 0;
65  }
66 
67  memcpy(&(mem->memory[mem->size]), contents, realsize);
68  mem->size += realsize;
69  mem->memory[mem->size] = 0;
70 
71  return realsize;
72 }
73 
85 bool VuoUrl_fetch(const char *url, void **data, unsigned int *dataLength)
86 {
87  if (VuoText_isEmpty(url))
88  return false;
89 
90  if (strncmp(url, "data:", 5) == 0)
91  {
92  // https://tools.ietf.org/html/rfc2397
93  // data:[<mediatype>][;base64],<data>
94 
95  const char *urlData = url + 5;
96 
97  // Skip past the <mediatype> tag(s); we only care about the <data> part.
98  urlData = strchr(urlData, ',');
99  if (!urlData)
100  return false;
101 
102  // Skip past the comma.
103  ++urlData;
104 
105  // Does the pre-<data> part of the URI end with `;base64`?
106  bool isBase64 = (urlData - url >= 5 /* data: */ + 7 /* ;base64 */ + 1)
107  && (strncmp(urlData - 7 - 1, ";base64", 7) == 0);
108 
109  VuoText decoded = VuoUrl_decodeRFC3986(urlData);
110  VuoRetain(decoded);
111 
112  if (isBase64)
113  {
114  long long outputLength;
115  *data = VuoBase64_decode(decoded, &outputLength);
116  *dataLength = outputLength;
117  }
118  else
119  {
120  *data = strdup(decoded);
121  *dataLength = strlen(decoded);
122  }
123 
124  VuoRelease(decoded);
125  return true;
126  }
127 
128  struct VuoUrl_curlBuffer buffer = {NULL, 0};
129  CURL *curl;
130  CURLcode res;
131 
132  curl = curl_easy_init();
133  if (!curl)
134  {
135  VUserLog("Error: cURL initialization failed.");
136  return false;
137  }
138  VuoDefer(^{ curl_easy_cleanup(curl); });
139 
140  curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); // Don't use signals for the timeout logic, since they're not thread-safe.
141 
143  VuoLocal(resolvedUrl);
144  curl_easy_setopt(curl, CURLOPT_URL, resolvedUrl);
145 
146  curl_easy_setopt(curl, CURLOPT_USERAGENT, "Vuo/" VUO_VERSION_STRING " (https://vuo.org/)");
147 
148  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
149  curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
150 
151  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, VuoUrl_curlCallback);
152  curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&buffer);
153 
154  res = curl_easy_perform(curl);
155  if(res != CURLE_OK)
156  {
157  if (res == CURLE_FILE_COULDNT_READ_FILE)
158  {
159  // If the path has colons (which aren't valid path characters on macOS),
160  // maybe they've been escaped as UTF-8 "Modifier Letter Colon".
161  // Try escaping them and see if that leads us to the file.
162  // https://b33p.net/kosada/node/14924
163  VuoText posixPath = VuoUrl_getPosixPath(resolvedUrl);
164  VuoLocal(posixPath);
165  if (!posixPath)
166  {
167  VUserLog("Error: Could not read URL \"%s\"", resolvedUrl);
168  return false;
169  }
170 
171  FILE *fp = fopen(posixPath, "rb");
172  if (!fp)
173  {
174  VUserLog("Error: Could not read file \"%s\"", posixPath);
175  return false;
176  }
177  VuoDefer(^{ fclose(fp); });
178 
179  fseek(fp, 0, SEEK_END);
180  *dataLength = ftell(fp);
181  rewind(fp);
182  *data = (char *)malloc(*dataLength);
183  if (fread(*data, 1, *dataLength, fp) != *dataLength)
184  {
185  VUserLog("Error: Could not read all the data from file \"%s\": %s", posixPath, strerror(errno));
186  return false;
187  }
188  return true;
189  }
190  else
191  VUserLog("Error: cURL request failed: %s (%d)", curl_easy_strerror(res), res);
192  return false;
193  }
194 
195  *data = buffer.memory;
196  *dataLength = buffer.size;
197  return true;
198 }