]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/urllib.qc
Merge remote branch 'origin/master' into samual/hud_updates
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / urllib.qc
1 // files
2 .float url_fh;
3 #define URL_FH_CURL   -1
4 #define URL_FH_STDOUT -2
5
6 // URLs
7 .string url_url;
8 .float url_wbuf;
9 .float url_wbufpos;
10 .float url_rbuf;
11 .float url_rbufpos;
12 .float url_id;
13 .url_ready_func url_ready;
14 .entity url_ready_pass;
15
16 entity url_fromid[NUM_URL_ID];
17 float autocvar__urllib_nextslot;
18
19 float url_URI_Get_Callback(float id, float status, string data)
20 {
21         if(id < MIN_URL_ID)
22                 return 0;
23         id -= MIN_URL_ID;
24         if(id >= NUM_URL_ID)
25                 return 0;
26         entity e;
27         e = url_fromid[id];
28         if(!e)
29                 return 0;
30         if(e.url_rbuf >= 0 || e.url_wbuf >= 0)
31         {
32                 print(sprintf("WARNING: handle %d (%s) has already received data?!?\n", id + NUM_URL_ID, e.url_url));
33                 return 0;
34         }
35
36         // whatever happens, we will remove the URL from the list of IDs
37         url_fromid[id] = world;
38
39         // if we get here, we MUST have both buffers cleared
40         if(e.url_rbuf != -1 || e.url_wbuf != -1 || e.url_fh != URL_FH_CURL)
41                 error("url_URI_Get_Callback: not a request waiting for data");
42
43         if(status == 0)
44         {
45                 // WE GOT DATA!
46                 float n, i;
47                 n = tokenizebyseparator(data, "\n");
48                 e.url_rbuf = buf_create();
49                 if(e.url_rbuf < 0)
50                 {
51                         print("url_URI_Get_Callback: out of memory in buf_create\n");
52                         e.url_ready(e, e.url_ready_pass, URL_READY_ERROR);
53                         strunzone(e.url_url);
54                         remove(e);
55                         return 1;
56                 }
57                 e.url_rbufpos = 0;
58                 if(e.url_rbuf < 0)
59                 {
60                         print("url_URI_Get_Callback: out of memory in buf_create\n");
61                         e.url_ready(e, e.url_ready_pass, URL_READY_ERROR);
62                         strunzone(e.url_url);
63                         remove(e);
64                         return 1;
65                 }
66                 for(i = 0; i < n; ++i)
67                         bufstr_set(e.url_rbuf, i, argv(i));
68                 e.url_ready(e, e.url_ready_pass, URL_READY_CANREAD);
69                 return 1;
70         }
71         else
72         {
73                 // an ERROR
74                 e.url_ready(e, e.url_ready_pass, -fabs(status));
75                 strunzone(e.url_url);
76                 remove(e);
77                 return 1;
78         }
79 }
80
81 void url_single_fopen(string url, float mode, url_ready_func rdy, entity pass)
82 {
83         entity e;
84         float i;
85         if(strstrofs(url, "://", 0) >= 0)
86         {
87                 switch(mode)
88                 {
89                         case FILE_WRITE:
90                         case FILE_APPEND:
91                                 // collect data to a stringbuffer for a POST request
92                                 // attempts to close will result in a reading handle
93
94                                 // create a writing end that does nothing yet
95                                 e = spawn();
96                                 e.classname = "url_single_fopen_file";
97                                 e.url_url = strzone(url);
98                                 e.url_fh = URL_FH_CURL;
99                                 e.url_wbuf = buf_create();
100                                 if(e.url_wbuf < 0)
101                                 {
102                                         print("url_single_fopen: out of memory in buf_create\n");
103                                         rdy(e, pass, URL_READY_ERROR);
104                                         strunzone(e.url_url);
105                                         remove(e);
106                                         return;
107                                 }
108                                 e.url_wbufpos = 0;
109                                 e.url_rbuf = -1;
110                                 e.url_ready = rdy;
111                                 e.url_ready_pass = pass;
112                                 rdy(e, pass, URL_READY_CANWRITE);
113                                 break;
114
115                         case FILE_READ:
116                                 // read data only
117
118                                 // get slot for HTTP request
119                                 for(i = autocvar__urllib_nextslot; i < NUM_URL_ID; ++i)
120                                         if(url_fromid[i] == world)
121                                                 break;
122                                 if(i >= NUM_URL_ID)
123                                 {
124                                         for(i = 0; i < autocvar__urllib_nextslot; ++i)
125                                                 if(url_fromid[i] == world)
126                                                         break;
127                                         if(i >= autocvar__urllib_nextslot)
128                                         {
129                                                 print("url_single_fopen: too many concurrent requests\n");
130                                                 rdy(world, pass, URL_READY_ERROR);
131                                                 return;
132                                         }
133                                 }
134
135                                 // GET the data
136                                 if(!crypto_uri_postbuf(url, i + MIN_URL_ID, string_null, string_null, -1, 0))
137                                 {
138                                         print("url_single_fopen: failure in crypto_uri_postbuf\n");
139                                         rdy(world, pass, URL_READY_ERROR);
140                                         return;
141                                 }
142
143                                 // Make a dummy handle object (no buffers at
144                                 // all). Wait for data to come from the
145                                 // server, then call the callback
146                                 e = spawn();
147                                 e.classname = "url_single_fopen_file";
148                                 e.url_url = strzone(url);
149                                 e.url_fh = URL_FH_CURL;
150                                 e.url_rbuf = -1;
151                                 e.url_wbuf = -1;
152                                 e.url_ready = rdy;
153                                 e.url_ready_pass = pass;
154                                 e.url_id = i;
155                                 url_fromid[i] = e;
156
157                                 // make sure this slot won't be reused quickly even on map change
158                                 cvar_set("_urllib_nextslot", ftos(mod(i + 1, NUM_URL_ID)));
159                                 break;
160                 }
161         }
162         else if(url == "-")
163         {
164                 switch(mode)
165                 {
166                         case FILE_WRITE:
167                         case FILE_APPEND:
168                                 e = spawn();
169                                 e.classname = "url_single_fopen_stdout";
170                                 e.url_fh = URL_FH_STDOUT;
171                                 e.url_ready = rdy;
172                                 e.url_ready_pass = pass;
173                                 rdy(e, pass, URL_READY_CANWRITE);
174                                 break;
175                         case FILE_READ:
176                                 print("url_single_fopen: cannot open '-' for reading\n");
177                                 rdy(world, pass, URL_READY_ERROR);
178                                 break;
179                 }
180         }
181         else
182         {
183                 float fh;
184                 fh = fopen(url, mode);
185                 if(fh < 0)
186                 {
187                         rdy(world, pass, URL_READY_ERROR);
188                         return;
189                 }
190                 else
191                 {
192                         e = spawn();
193                         e.classname = "url_single_fopen_file";
194                         e.url_fh = fh;
195                         e.url_ready = rdy;
196                         e.url_ready_pass = pass;
197                         if(mode == FILE_READ)
198                                 rdy(e, pass, URL_READY_CANREAD);
199                         else
200                                 rdy(e, pass, URL_READY_CANWRITE);
201                 }
202         }
203 }
204
205 // close a file
206 void url_fclose(entity e)
207 {
208         float i;
209
210         if(e.url_fh == URL_FH_CURL)
211         {
212                 if(e.url_rbuf == -1 || e.url_wbuf != -1) // not(post GET/POST request)
213                 if(e.url_rbuf != -1 || e.url_wbuf == -1) // not(pre POST request)
214                         error("url_fclose: not closable in current state");
215
216                 // closing an URL!
217                 if(e.url_wbuf >= 0)
218                 {
219                         // we are closing the write end (HTTP POST request)
220
221                         // get slot for HTTP request
222                         for(i = autocvar__urllib_nextslot; i < NUM_URL_ID; ++i)
223                                 if(url_fromid[i] == world)
224                                         break;
225                         if(i >= NUM_URL_ID)
226                         {
227                                 for(i = 0; i < autocvar__urllib_nextslot; ++i)
228                                         if(url_fromid[i] == world)
229                                                 break;
230                                 if(i >= autocvar__urllib_nextslot)
231                                 {
232                                         print("url_fclose: too many concurrent requests\n");
233                                         e.url_ready(e,e.url_ready_pass, URL_READY_ERROR);
234                                         buf_del(e.url_wbuf);
235                                         strunzone(e.url_url);
236                                         remove(e);
237                                         return;
238                                 }
239                         }
240
241                         // POST the data
242                         if(!crypto_uri_postbuf(e.url_url, i + MIN_URL_ID, "text/plain", "", e.url_wbuf, 0))
243                         {
244                                 print("url_fclose: failure in crypto_uri_postbuf\n");
245                                 e.url_ready(e, e.url_ready_pass, URL_READY_ERROR);
246                                 buf_del(e.url_wbuf);
247                                 strunzone(e.url_url);
248                                 remove(e);
249                                 return;
250                         }
251
252                         // delete write end. File handle is now in unusable
253                         // state. Wait for data to come from the server, then
254                         // call the callback
255                         buf_del(e.url_wbuf);
256                         e.url_wbuf = -1;
257                         e.url_id = i;
258                         url_fromid[i] = e;
259
260                         // make sure this slot won't be reused quickly even on map change
261                         cvar_set("_urllib_nextslot", ftos(mod(i + 1, NUM_URL_ID)));
262                 }
263                 else
264                 {
265                         // we have READ all data, just close
266                         e.url_ready(e, e.url_ready_pass, URL_READY_CLOSED);
267                         buf_del(e.url_rbuf);
268                         strunzone(e.url_url);
269                         remove(e);
270                 }
271         }
272         else if(e.url_fh == URL_FH_STDOUT)
273         {
274                 e.url_ready(e, e.url_ready_pass, URL_READY_CLOSED); // closing creates no reading handle
275                 remove(e);
276         }
277         else
278         {
279                 // file
280                 fclose(e.url_fh);
281                 e.url_ready(e, e.url_ready_pass, URL_READY_CLOSED); // closing creates no reading handle
282                 remove(e);
283         }
284 }
285
286 // with \n (blame FRIK_FILE)
287 string url_fgets(entity e)
288 {
289         if(e.url_fh == URL_FH_CURL)
290         {
291                 if(e.url_rbuf == -1)
292                         error("url_fgets: not readable in current state");
293                 // curl
294                 string s;
295                 s = bufstr_get(e.url_rbuf, e.url_rbufpos);
296                 e.url_rbufpos += 1;
297                 return s;
298         }
299         else if(e.url_fh == URL_FH_STDOUT)
300         {
301                 // stdout
302                 return string_null;
303         }
304         else
305         {
306                 // file
307                 return fgets(e.url_fh);
308         }
309 }
310
311 // without \n (blame FRIK_FILE)
312 void url_fputs(entity e, string s)
313 {
314         if(e.url_fh == URL_FH_CURL)
315         {
316                 if(e.url_wbuf == -1)
317                         error("url_fputs: not writable in current state");
318                 // curl
319                 bufstr_set(e.url_wbuf, e.url_wbufpos, s);
320                 e.url_wbufpos += 1;
321         }
322         else if(e.url_fh == URL_FH_STDOUT)
323         {
324                 // stdout
325                 print(s);
326         }
327         else
328         {
329                 // file
330                 fputs(e.url_fh, s);
331         }
332 }
333
334 // multi URL object, tries URLs separated by space in sequence
335 void url_multi_ready(entity fh, entity me, float status)
336 {
337         float n;
338         if(status == URL_READY_ERROR)
339         {
340                 me.cnt += 1;
341                 n = tokenize_console(me.url_url);
342                 if(n <= me.cnt)
343                 {
344                         me.url_ready(fh, me.url_ready_pass, status);
345                         strunzone(me.url_url);
346                         remove(me);
347                         return;
348                 }
349                 url_single_fopen(argv(me.cnt), me.lip, url_multi_ready, me);
350                 return;
351         }
352         me.url_ready(fh, me.url_ready_pass, status);
353 }
354 void url_multi_fopen(string url, float mode, url_ready_func rdy, entity pass)
355 {
356         float n;
357         n = tokenize_console(url);
358         if(n <= 0)
359         {
360                 print("url_multi_fopen: need at least one URL\n");
361                 rdy(world, pass, URL_READY_ERROR);
362                 return;
363         }
364
365         entity me;
366         me = spawn();
367         me.classname = "url_multi";
368         me.url_url = strzone(url);
369         me.cnt = 0;
370         me.lip = mode;
371         me.url_ready = rdy;
372         me.url_ready_pass = pass;
373         url_single_fopen(argv(0), mode, url_multi_ready, me);
374 }