]>
Commit | Line | Data |
---|---|---|
769a03c3 MF |
1 | // Written by Mike Frysinger <vapier@gmail.com>. Released into the public domain. Suck it. |
2 | ||
3 | #include <assert.h> | |
4 | #include <inttypes.h> | |
5 | #include <pthread.h> | |
6 | #include <stdbool.h> | |
7 | #include <stdint.h> | |
8 | #include <stdio.h> | |
9 | #include <stdlib.h> | |
10 | #include <string.h> | |
11 | ||
12 | #include "ftplib/ftplib.h" | |
13 | ||
14 | #include "ppapi/c/pp_errors.h" | |
15 | #include "ppapi/c/pp_module.h" | |
16 | #include "ppapi/c/pp_var.h" | |
17 | #include "ppapi/c/ppb.h" | |
18 | #include "ppapi/c/ppb_console.h" | |
19 | #include "ppapi/c/ppb_instance.h" | |
20 | #include "ppapi/c/ppb_messaging.h" | |
21 | #include "ppapi/c/ppb_url_loader.h" | |
22 | #include "ppapi/c/ppb_url_request_info.h" | |
23 | #include "ppapi/c/ppb_url_response_info.h" | |
24 | #include "ppapi/c/ppb_var.h" | |
25 | #include "ppapi/c/ppb_var_array.h" | |
26 | #include "ppapi/c/ppp.h" | |
27 | #include "ppapi/c/ppp_instance.h" | |
28 | #include "ppapi/c/ppp_messaging.h" | |
29 | #include "nacl_io/nacl_io.h" | |
30 | ||
31 | typedef struct PP_Var PP_Var; | |
32 | typedef struct PP_CompletionCallback PP_CompletionCallback; | |
33 | ||
34 | static PP_Instance pp_instance = 0; | |
35 | static PPB_GetInterface ppb_get_browser_interface = NULL; | |
36 | static const PPB_Console *ppb_console_interface = NULL; | |
37 | static const PPB_Messaging *ppb_messaging_interface = NULL; | |
38 | static const PPB_URLLoader *ppb_urlloader_interface = NULL; | |
39 | static const PPB_URLRequestInfo *ppb_urlrequestinfo_interface = NULL; | |
40 | static const PPB_URLResponseInfo *ppb_urlresponseinfo_interface = NULL; | |
41 | static const PPB_Var *ppb_var_interface = NULL; | |
42 | static const PPB_VarArray *ppb_var_array_interface = NULL; | |
43 | ||
44 | #include "util.h" | |
45 | #include "queue.h" | |
46 | ||
47 | /** A handle to the thread the handles messages. */ | |
48 | static pthread_t handle_message_thread; | |
49 | ||
50 | static netbuf *pp_conn = NULL; | |
51 | ||
52 | enum { | |
53 | STATUS_DEBUG = 0, | |
54 | STATUS_RESPONSE, | |
55 | STATUS_CMD, | |
56 | STATUS_ERROR, | |
57 | STATUS_RELEASE, | |
58 | }; | |
59 | __attribute__((format(printf, 2, 3))) | |
60 | static void SendStatus(int32_t status, const char *format, ...) | |
61 | { | |
62 | va_list args; | |
63 | PP_Var var_array, var_status, var_msg; | |
64 | char *string; | |
65 | ||
66 | va_start(args, format); | |
67 | string = VprintfToNewString(format, args); | |
68 | va_end(args); | |
69 | ||
70 | var_status = PP_MakeInt32(status); | |
71 | var_msg = ppb_var_interface->VarFromUtf8(string, strlen(string)); | |
72 | free(string); | |
73 | ||
74 | var_array = ppb_var_array_interface->Create(); | |
75 | ppb_var_array_interface->Set(var_array, 0, var_status); | |
76 | ppb_var_array_interface->Set(var_array, 1, var_msg); | |
77 | ||
78 | ppb_messaging_interface->PostMessage(pp_instance, var_array); | |
79 | ppb_var_interface->Release(var_msg); | |
80 | ppb_var_interface->Release(var_array); | |
81 | } | |
82 | ||
83 | static int | |
84 | ftplib_data_debug_callback(char *buf, int len) | |
85 | { | |
86 | SendStatus(STATUS_DEBUG, "%*s", len, buf); | |
87 | return len; | |
88 | } | |
89 | ||
90 | static int | |
91 | ftplib_data_response_callback(char *buf, int len) | |
92 | { | |
93 | SendStatus(STATUS_RESPONSE, "%*s", len, buf); | |
94 | return len; | |
95 | } | |
96 | ||
97 | static int _FtpQuit(netbuf *conn) | |
98 | { | |
99 | /* Make sure to invalidate our handle since the core freed it. */ | |
100 | pp_conn = NULL; | |
101 | ||
102 | /* First try a simple quit. If that fails, close it harder. */ | |
103 | return FtpQuit(conn) ? : FtpClose(conn); | |
104 | } | |
105 | static int _FtpDir(const char *path, netbuf *conn) | |
106 | { | |
107 | return FtpDir(ftplib_data_response_callback, path, conn); | |
108 | } | |
109 | static int _FtpNlst(const char *path, netbuf *conn) | |
110 | { | |
111 | return FtpNlst(ftplib_data_response_callback, path, conn); | |
112 | } | |
113 | static int _FtpPwd(netbuf *conn) | |
114 | { | |
115 | char b[1]; | |
116 | return FtpPwd(b, sizeof(b), conn); | |
117 | } | |
118 | static int _FtpSize(const char *path, netbuf *conn) | |
119 | { | |
120 | fsz_t size; | |
121 | return FtpSizeLong(path, &size, FTPLIB_BINARY, conn); | |
122 | } | |
123 | static int _FtpSysType(netbuf *conn) | |
124 | { | |
125 | char b[1]; | |
126 | return FtpSysType(b, sizeof(b), conn); | |
127 | } | |
128 | static int _FtpCat(const char *path, netbuf *conn) | |
129 | { | |
130 | return FtpGet(ftplib_data_debug_callback, path, FTPLIB_ASCII, conn); | |
131 | } | |
132 | ||
133 | static int _FtpPut(const char *url, const char *name, netbuf *conn) | |
134 | { | |
135 | int32_t result; | |
136 | int ret; | |
137 | netbuf *data; | |
138 | char buf[8192]; | |
139 | ||
140 | /* Start the FTP data connection. */ | |
141 | ret = FtpAccess(name, FTPLIB_FILE_WRITE, FTPLIB_BINARY, conn, &data); | |
142 | if (!ret) | |
143 | return ret; | |
144 | SendStatus(STATUS_DEBUG, "Data connection established"); | |
145 | ||
146 | /* Set up the URL reader. */ | |
147 | PP_Resource url_rc = ppb_urlloader_interface->Create(pp_instance); | |
148 | ||
149 | PP_Resource url_request_rc = ppb_urlrequestinfo_interface->Create(pp_instance); | |
150 | PP_Var var_url = CStrToVar(url); | |
151 | PP_Var var_method = CStrToVar("GET"); | |
152 | ppb_urlrequestinfo_interface->SetProperty(url_request_rc, PP_URLREQUESTPROPERTY_URL, var_url); | |
153 | ppb_urlrequestinfo_interface->SetProperty(url_request_rc, PP_URLREQUESTPROPERTY_METHOD, var_method); | |
154 | ||
155 | result = ppb_urlloader_interface->Open(url_rc, url_request_rc, PP_BlockUntilComplete()); | |
156 | if (result != PP_OK) { | |
157 | SendStatus(STATUS_ERROR, "internal error: open url failed %s", url); | |
158 | goto done; | |
159 | } | |
160 | ||
161 | /* Start streaming the data to the server. */ | |
162 | do { | |
163 | result = ppb_urlloader_interface->ReadResponseBody(url_rc, buf, sizeof(buf), PP_BlockUntilComplete()); | |
164 | if (result > 0) | |
165 | FtpWrite(buf, result, data); | |
166 | } while (result > 0); | |
167 | if (result != PP_OK) | |
168 | SendStatus(STATUS_ERROR, "internal error: reading url failed %i", result); | |
169 | ||
170 | done: | |
171 | /* Break down the URL streamer and FTP connection. */ | |
172 | SendStatus(STATUS_RELEASE, "%s", url); | |
173 | SendStatus(STATUS_DEBUG, "Data connection finished"); | |
174 | ppb_urlloader_interface->Close(url_rc); | |
175 | ppb_var_interface->Release(var_method); | |
176 | ppb_var_interface->Release(var_url); | |
177 | ppb_urlloader_interface->Close(url_rc); | |
178 | ||
179 | FtpClose(data); | |
180 | ||
181 | return ret; | |
182 | } | |
183 | ||
184 | static int _FtpGet(const char *url, const char *name, netbuf *conn) | |
185 | { | |
186 | int32_t result; | |
187 | int ret; | |
188 | netbuf *data; | |
189 | char buf[8192]; | |
190 | ||
191 | /* Start the FTP data connection. */ | |
192 | ret = FtpAccess(name, FTPLIB_FILE_READ, FTPLIB_BINARY, conn, &data); | |
193 | if (!ret) | |
194 | return ret; | |
195 | SendStatus(STATUS_DEBUG, "Data connection established"); | |
196 | ||
197 | /* Set up the URL writer. */ | |
198 | PP_Resource url_rc = ppb_urlloader_interface->Create(pp_instance); | |
199 | ||
200 | PP_Resource url_request_rc = ppb_urlrequestinfo_interface->Create(pp_instance); | |
201 | PP_Var var_url = CStrToVar(url); | |
202 | PP_Var var_method = CStrToVar("PUT"); | |
203 | ppb_urlrequestinfo_interface->SetProperty(url_request_rc, PP_URLREQUESTPROPERTY_URL, var_url); | |
204 | ppb_urlrequestinfo_interface->SetProperty(url_request_rc, PP_URLREQUESTPROPERTY_METHOD, var_method); | |
205 | ||
206 | /* Start streaming the data from the server. */ | |
207 | do { | |
208 | ret = FtpRead(buf, sizeof(buf), data); | |
209 | if (ret > 0) { | |
210 | result = ppb_urlrequestinfo_interface->AppendDataToBody(url_request_rc, buf, ret); | |
211 | if (result != PP_TRUE) { | |
212 | SendStatus(STATUS_ERROR, "internal error: appending body failed %i", result); | |
213 | break; | |
214 | } | |
215 | } | |
216 | //SendStatus(STATUS_DEBUG, "ret = %i result = %i", ret, result); | |
217 | } while (ret > 0); | |
218 | ||
219 | /* Save the data to the URL. */ | |
220 | result = ppb_urlloader_interface->Open(url_rc, url_request_rc, PP_BlockUntilComplete()); | |
221 | if (result != PP_OK) | |
222 | SendStatus(STATUS_ERROR, "internal error: open url failed %s", url); | |
223 | ||
224 | /* Break down the URL streamer and FTP connection. */ | |
225 | SendStatus(STATUS_RELEASE, "%s", url); | |
226 | SendStatus(STATUS_DEBUG, "Data connection finished"); | |
227 | ppb_urlloader_interface->Close(url_rc); | |
228 | ppb_var_interface->Release(var_method); | |
229 | ppb_var_interface->Release(var_url); | |
230 | ppb_urlloader_interface->Close(url_rc); | |
231 | ||
232 | FtpClose(data); | |
233 | ||
234 | return ret; | |
235 | } | |
236 | ||
237 | struct cmd { | |
238 | const char *cmd; | |
239 | const char *usage; | |
240 | uint32_t argc; | |
241 | union { | |
242 | int (*a0)(netbuf *); | |
243 | int (*a1)(const char *, netbuf *); | |
244 | int (*a2)(const char *, const char *, netbuf *); | |
245 | } cb; | |
246 | const char * const aliases[10]; | |
247 | }; | |
248 | #define A(n, f) n, .cb.a##n = f | |
249 | static const struct cmd cmds[] = { | |
250 | { "cat", "<path", A(1, _FtpCat), }, | |
251 | { "cdup", NULL, A(0, FtpCDUp), }, | |
252 | { "chdir", "<path>", A(1, FtpChdir), | |
253 | {"cd"}, }, | |
254 | { "delete", "<path>", A(1, FtpDelete), | |
255 | {"del", | |
256 | "dele", | |
257 | "rm", | |
258 | "remove", | |
259 | "unlink"}, }, | |
260 | { "dir", "<path>", A(1, _FtpDir), }, | |
261 | { "quit", NULL, A(0, _FtpQuit), | |
262 | {"close", | |
263 | "disconnect", | |
264 | "exit"}, }, | |
265 | { "get", "<local> <remote>", A(2, _FtpGet), }, | |
266 | { "list", "<path>", A(1, _FtpNlst), | |
267 | {"ls"}, }, | |
268 | { "login", "<user> <pass>", A(2, FtpLogin), }, | |
269 | { "mkdir", "<path>", A(1, FtpMkdir), | |
270 | {"mkd"}, }, | |
271 | { "pwd", NULL, A(0, _FtpPwd), | |
272 | {"cwd"}, }, | |
273 | { "put", "<local> <remote>", A(2, _FtpPut), | |
274 | {"stor", | |
275 | "store"}, }, | |
276 | { "rename", "<src> <dst>", A(2, FtpRename), | |
277 | {"mv"} }, | |
278 | { "rmdir", "<path>", A(1, FtpRmdir), | |
279 | {"rmd"}, }, | |
280 | { "site", "<cmd>", A(1, FtpSite), }, | |
281 | { "size", "<path>", A(1, _FtpSize), }, | |
282 | { "syst", NULL, A(0, _FtpSysType), | |
283 | {"systyp", | |
284 | "systype"}, }, | |
285 | }; | |
286 | ||
287 | static const struct cmd *lookup_cmd(const char *scmd) | |
288 | { | |
289 | size_t i; | |
290 | ||
291 | for (i = 0; i < ARRAY_SIZE(cmds); ++i) { | |
292 | const struct cmd *cmd = &cmds[i]; | |
293 | if (!strcmp(cmd->cmd, scmd)) | |
294 | return cmd; | |
295 | ||
296 | const char * const *aliases = cmd->aliases; | |
297 | size_t a; | |
298 | for (a = 0; aliases[a]; ++a) | |
299 | if (!strcmp(aliases[a], scmd)) | |
300 | return cmd; | |
301 | } | |
302 | ||
303 | return NULL; | |
304 | } | |
305 | ||
306 | static const struct cmd *check_cmd(const char *scmd, uint32_t argc) | |
307 | { | |
308 | const struct cmd *cmd = lookup_cmd(scmd); | |
309 | if (!cmd) | |
310 | return cmd; | |
311 | ||
312 | if (cmd->argc != argc) { | |
313 | SendStatus(STATUS_ERROR, "usage: %s %s", scmd, cmd->usage); | |
314 | return NULL; | |
315 | } else | |
316 | return cmd; | |
317 | } | |
318 | ||
319 | static int | |
320 | ftplib_response_callback(netbuf *conn, const char *resp) | |
321 | { | |
322 | SendStatus(STATUS_RESPONSE, "%s", resp); | |
323 | return 0; | |
324 | } | |
325 | ||
326 | static int | |
327 | ftplib_sendcmd_callback(netbuf *conn, const char *resp) | |
328 | { | |
329 | SendStatus(STATUS_CMD, "%s", resp); | |
330 | return 0; | |
331 | } | |
332 | ||
333 | /* Process each message the main thread gave us. */ | |
334 | static void | |
335 | HandleMessage(char **argv, uint32_t argc) | |
336 | { | |
337 | int ret; | |
338 | uint32_t i; | |
339 | const struct cmd *cmd; | |
340 | ||
341 | console.log("DEBUG"); | |
342 | for (i = 0; i < argc; ++i) | |
343 | console.log(argv[i] ? : "<NULL>"); | |
344 | ||
345 | /* Handle the connect command specially to deal with state. */ | |
346 | if (!strcmp(argv[0], "connect")) { | |
347 | if (argc != 2) { | |
348 | SendStatus(STATUS_ERROR, "usage: connect <host[:port]>"); | |
349 | return; | |
350 | } | |
351 | ||
352 | if (pp_conn) | |
353 | FtpClose(pp_conn); | |
354 | ||
355 | SendStatus(STATUS_DEBUG, "Connecting to %s ...", argv[1]); | |
356 | if (!FtpConnect(argv[1], ftplib_response_callback, &pp_conn)) { | |
357 | SendStatus(STATUS_ERROR, "Connection failed"); | |
358 | } else { | |
359 | SendStatus(STATUS_DEBUG, "Connection established"); | |
360 | FtpSetSendCmdCallback(ftplib_sendcmd_callback, pp_conn); | |
361 | } | |
362 | ||
363 | return; | |
364 | } else if (pp_conn == NULL) { | |
365 | SendStatus(STATUS_ERROR, "Not connected!"); | |
366 | return; | |
367 | } | |
368 | ||
369 | cmd = check_cmd(argv[0], argc - 1); | |
370 | if (cmd == NULL) { | |
371 | SendStatus(STATUS_ERROR, "unknown command: %s", argv[0]); | |
372 | return; | |
373 | } | |
374 | ||
375 | switch (cmd->argc) { | |
376 | case 0: ret = cmd->cb.a0(pp_conn); break; | |
377 | case 1: ret = cmd->cb.a1(argv[1], pp_conn); break; | |
378 | case 2: ret = cmd->cb.a2(argv[1], argv[2], pp_conn); break; | |
379 | default: | |
380 | ret = 0; | |
381 | console.log("unhandled arg count"); | |
382 | } | |
383 | if (ret == 0) | |
384 | SendStatus(STATUS_ERROR, "error %s", FtpLastResponse(pp_conn)); | |
385 | } | |
386 | ||
387 | /* Background worker thread. */ | |
388 | static void * | |
389 | HandleMessageThread(void *arg) | |
390 | { | |
391 | while (1) { | |
392 | struct q_ele *msg = DequeueMessage(); | |
393 | HandleMessage(msg->argv, msg->argc); | |
394 | CArrFree(msg->argv, msg->argc); | |
395 | } | |
396 | } | |
397 | ||
398 | /* Callback when the <embed> is created. */ | |
399 | static PP_Bool | |
400 | Instance_DidCreate(PP_Instance instance, uint32_t argc, | |
401 | const char *argn[], const char *argv[]) | |
402 | { | |
403 | pp_instance = instance; | |
404 | nacl_io_init_ppapi(instance, ppb_get_browser_interface); | |
405 | ||
406 | pthread_create(&handle_message_thread, NULL, &HandleMessageThread, NULL); | |
407 | InitializeMessageQueue(); | |
408 | ||
409 | console.tip("CrFTP module created (written by Mike Frysinger <vapier@gmail.com>)"); | |
410 | extern char *version; | |
411 | console.tip(version); | |
412 | ||
413 | return PP_TRUE; | |
414 | } | |
415 | ||
416 | /* Callback when the instance is destroyed. */ | |
417 | static void | |
418 | Instance_DidDestroy(PP_Instance instance) | |
419 | { | |
420 | if (pp_conn) | |
421 | FtpClose(pp_conn); | |
422 | pp_conn = NULL; | |
423 | } | |
424 | ||
425 | /* Callback when the <embed> changes. */ | |
426 | static void | |
427 | Instance_DidChangeView(PP_Instance instance, PP_Resource view_resource) | |
428 | {} | |
429 | ||
430 | /* Callback when the <embed> focus changes. */ | |
431 | static void | |
432 | Instance_DidChangeFocus(PP_Instance instance, PP_Bool has_focus) | |
433 | {} | |
434 | ||
435 | /* Callback when the document finishes loading. */ | |
436 | static PP_Bool | |
437 | Instance_HandleDocumentLoad(PP_Instance instance, PP_Resource url_loader) | |
438 | { | |
439 | /* NaCl modules do not need to handle the document load function. */ | |
440 | return PP_FALSE; | |
441 | } | |
442 | ||
443 | /* Callback when the JS sends us a message. | |
444 | * | |
445 | * We expect messages to be an array of commands for the ftplib core. | |
446 | */ | |
447 | static void | |
448 | Messaging_HandleMessage(PP_Instance instance, PP_Var message) | |
449 | { | |
450 | if (message.type != PP_VARTYPE_ARRAY) { | |
451 | console.error("Invalid message received"); | |
452 | return; | |
453 | } | |
454 | ||
455 | char **argv; | |
456 | uint32_t argc; | |
457 | if (!VarToCArr(message, &argv, &argc)) { | |
458 | console.error("Could not read message"); | |
459 | return; | |
460 | } | |
461 | ||
462 | if (argc == 0) { | |
463 | console.log("Missing command"); | |
464 | CArrFree(argv, argc); | |
465 | } else if (!EnqueueMessage(argv, argc)) { | |
466 | SendStatus(STATUS_ERROR, "internal error: dropped message due to full queue"); | |
467 | CArrFree(argv, argc); | |
468 | } | |
469 | } | |
470 | ||
471 | /* Callback to get pointers to the other module funcs. */ | |
472 | PP_EXPORT const void * | |
473 | PPP_GetInterface(const char *interface_name) | |
474 | { | |
475 | if (strcmp(interface_name, PPP_INSTANCE_INTERFACE) == 0) { | |
476 | static PPP_Instance instance_interface = { | |
477 | &Instance_DidCreate, | |
478 | &Instance_DidDestroy, | |
479 | &Instance_DidChangeView, | |
480 | &Instance_DidChangeFocus, | |
481 | &Instance_HandleDocumentLoad, | |
482 | }; | |
483 | return &instance_interface; | |
484 | } else if (strcmp(interface_name, PPP_MESSAGING_INTERFACE) == 0) { | |
485 | static PPP_Messaging messaging_interface = { | |
486 | &Messaging_HandleMessage, | |
487 | }; | |
488 | return &messaging_interface; | |
489 | } | |
490 | return NULL; | |
491 | } | |
492 | ||
493 | /* Callback to initialize the module. */ | |
494 | PP_EXPORT int32_t | |
495 | PPP_InitializeModule(PP_Module a_module_id, PPB_GetInterface get_browser) | |
496 | { | |
497 | FtpInit(); | |
498 | ||
499 | ppb_get_browser_interface = get_browser; | |
500 | ppb_console_interface = get_browser(PPB_CONSOLE_INTERFACE); | |
501 | ppb_messaging_interface = get_browser(PPB_MESSAGING_INTERFACE); | |
502 | ppb_var_interface = get_browser(PPB_VAR_INTERFACE); | |
503 | ppb_var_array_interface = get_browser(PPB_VAR_ARRAY_INTERFACE); | |
504 | ppb_urlloader_interface = get_browser(PPB_URLLOADER_INTERFACE); | |
505 | ppb_urlrequestinfo_interface = get_browser(PPB_URLREQUESTINFO_INTERFACE); | |
506 | ppb_urlresponseinfo_interface = get_browser(PPB_URLRESPONSEINFO_INTERFACE); | |
507 | ||
508 | return PP_OK; | |
509 | } | |
510 | ||
511 | /* Callback before we shutdown the module. */ | |
512 | PP_EXPORT void | |
513 | PPP_ShutdownModule(void) | |
514 | { | |
515 | } |