]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/urllib.qc
Merge remote-tracking branch 'origin/master' into samual/updatecommands
[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_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_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_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                                 rdy(e, pass, URL_READY_CANWRITE);
111                                 break;
112
113                         case FILE_READ:
114                                 // read data only
115
116                                 // get slot for HTTP request
117                                 for(i = autocvar__urllib_nextslot; i < NUM_URL_ID; ++i)
118                                         if(url_fromid[i] == world)
119                                                 break;
120                                 if(i >= NUM_URL_ID)
121                                 {
122                                         for(i = 0; i < autocvar__urllib_nextslot; ++i)
123                                                 if(url_fromid[i] == world)
124                                                         break;
125                                         if(i >= autocvar__urllib_nextslot)
126                                         {
127                                                 print("url_fopen: too many concurrent requests\n");
128                                                 rdy(world, pass, URL_READY_ERROR);
129                                                 return;
130                                         }
131                                 }
132
133                                 // GET the data
134                                 if(!crypto_uri_postbuf(url, i + MIN_URL_ID, string_null, string_null, -1, 0))
135                                 {
136                                         print("url_fopen: failure in crypto_uri_postbuf\n");
137                                         rdy(world, pass, URL_READY_ERROR);
138                                         return;
139                                 }
140
141                                 // Make a dummy handle object (no buffers at
142                                 // all). Wait for data to come from the
143                                 // server, then call the callback
144                                 e = spawn();
145                                 e.classname = "url_fopen_file";
146                                 e.url_url = strzone(url);
147                                 e.url_fh = URL_FH_CURL;
148                                 e.url_rbuf = -1;
149                                 e.url_wbuf = -1;
150                                 e.url_ready = rdy;
151                                 e.url_ready_pass = pass;
152                                 e.url_id = i;
153                                 url_fromid[i] = e;
154
155                                 // make sure this slot won't be reused quickly even on map change
156                                 cvar_set("_urllib_nextslot", ftos(mod(i + 1, NUM_URL_ID)));
157                                 break;
158                 }
159         }
160         else if(url == "-")
161         {
162                 switch(mode)
163                 {
164                         case FILE_WRITE:
165                         case FILE_APPEND:
166                                 e = spawn();
167                                 e.classname = "url_fopen_stdout";
168                                 e.url_fh = URL_FH_STDOUT;
169                                 rdy(e, pass, URL_READY_CANWRITE);
170                                 break;
171                         case FILE_READ:
172                                 print("url_fopen: cannot open '-' for reading\n");
173                                 rdy(world, pass, URL_READY_ERROR);
174                                 break;
175                 }
176         }
177         else
178         {
179                 float fh;
180                 fh = fopen(url, mode);
181                 if(fh < 0)
182                 {
183                         rdy(world, pass, URL_READY_ERROR);
184                         return;
185                 }
186                 else
187                 {
188                         e = spawn();
189                         e.classname = "url_fopen_file";
190                         e.url_fh = fh;
191                         if(mode == FILE_READ)
192                                 rdy(e, pass, URL_READY_CANREAD);
193                         else
194                                 rdy(e, pass, URL_READY_CANWRITE);
195                 }
196         }
197 }
198
199 // close a file
200 void url_fclose(entity e, url_ready_func rdy, entity pass)
201 {
202         float i;
203
204         if(e.url_fh == URL_FH_CURL)
205         {
206                 if(e.url_rbuf == -1 || e.url_wbuf != -1) // not(post GET/POST request)
207                 if(e.url_rbuf != -1 || e.url_wbuf == -1) // not(pre POST request)
208                         error("url_fclose: not closable in current state");
209
210                 // closing an URL!
211                 if(e.url_wbuf >= 0)
212                 {
213                         // we are closing the write end (HTTP POST request)
214
215                         // get slot for HTTP request
216                         for(i = autocvar__urllib_nextslot; i < NUM_URL_ID; ++i)
217                                 if(url_fromid[i] == world)
218                                         break;
219                         if(i >= NUM_URL_ID)
220                         {
221                                 for(i = 0; i < autocvar__urllib_nextslot; ++i)
222                                         if(url_fromid[i] == world)
223                                                 break;
224                                 if(i >= autocvar__urllib_nextslot)
225                                 {
226                                         print("url_fclose: too many concurrent requests\n");
227                                         rdy(e, pass, URL_READY_ERROR);
228                                         buf_del(e.url_wbuf);
229                                         strunzone(e.url_url);
230                                         remove(e);
231                                         return;
232                                 }
233                         }
234
235                         // POST the data
236                         if(!crypto_uri_postbuf(e.url_url, i + MIN_URL_ID, "text/plain", "", e.url_wbuf, 0))
237                         {
238                                 print("url_fclose: failure in crypto_uri_postbuf\n");
239                                 rdy(e, pass, URL_READY_ERROR);
240                                 buf_del(e.url_wbuf);
241                                 strunzone(e.url_url);
242                                 remove(e);
243                                 return;
244                         }
245
246                         // delete write end. File handle is now in unusable
247                         // state. Wait for data to come from the server, then
248                         // call the callback
249                         buf_del(e.url_wbuf);
250                         e.url_wbuf = -1;
251                         e.url_ready = rdy;
252                         e.url_ready_pass = pass;
253                         e.url_id = i;
254                         url_fromid[i] = e;
255
256                         // make sure this slot won't be reused quickly even on map change
257                         cvar_set("_urllib_nextslot", ftos(mod(i + 1, NUM_URL_ID)));
258                 }
259                 else
260                 {
261                         // we have READ all data, just close
262                         rdy(e, pass, URL_READY_CLOSED);
263                         buf_del(e.url_rbuf);
264                         strunzone(e.url_url);
265                         remove(e);
266                 }
267         }
268         else if(e.url_fh == URL_FH_STDOUT)
269         {
270                 rdy(e, pass, URL_READY_CLOSED); // closing creates no reading handle
271                 remove(e);
272         }
273         else
274         {
275                 // file
276                 fclose(e.url_fh);
277                 rdy(e, pass, URL_READY_CLOSED); // closing creates no reading handle
278                 remove(e);
279         }
280 }
281
282 // with \n (blame FRIK_FILE)
283 string url_fgets(entity e)
284 {
285         if(e.url_fh == URL_FH_CURL)
286         {
287                 if(e.url_rbuf == -1)
288                         error("url_fgets: not readable in current state");
289                 // curl
290                 string s;
291                 s = bufstr_get(e.url_rbuf, e.url_rbufpos);
292                 e.url_rbufpos += 1;
293                 return s;
294         }
295         else if(e.url_fh == URL_FH_STDOUT)
296         {
297                 // stdout
298                 return string_null;
299         }
300         else
301         {
302                 // file
303                 return fgets(e.url_fh);
304         }
305 }
306
307 // without \n (blame FRIK_FILE)
308 void url_fputs(entity e, string s)
309 {
310         if(e.url_fh == URL_FH_CURL)
311         {
312                 if(e.url_wbuf == -1)
313                         error("url_fputs: not writable in current state");
314                 // curl
315                 bufstr_set(e.url_wbuf, e.url_wbufpos, s);
316                 e.url_wbufpos += 1;
317         }
318         else if(e.url_fh == URL_FH_STDOUT)
319         {
320                 // stdout
321                 print(s);
322         }
323         else
324         {
325                 // file
326                 fputs(e.url_fh, s);
327         }
328 }