GCC Code Coverage Report


Directory: src/gate/
File: src/gate/net/ftpclients.c
Date: 2025-09-14 13:10:38
Exec Total Coverage
Lines: 270 401 67.3%
Functions: 21 31 67.7%
Branches: 106 243 43.6%

Line Branch Exec Source
1 /* GATE PROJECT LICENSE:
2 +----------------------------------------------------------------------------+
3 | Copyright(c) 2018-2025, Stefan Meislinger <sm@opengate.at> |
4 | All rights reserved. |
5 | |
6 | Redistribution and use in source and binary forms, with or without |
7 | modification, are permitted provided that the following conditions are met:|
8 | |
9 | 1. Redistributions of source code must retain the above copyright notice, |
10 | this list of conditions and the following disclaimer. |
11 | 2. Redistributions in binary form must reproduce the above copyright |
12 | notice, this list of conditions and the following disclaimer in the |
13 | documentation and/or other materials provided with the distribution. |
14 | |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"|
16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
18 | ARE DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
19 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
21 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
22 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
23 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
24 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
25 | THE POSSIBILITY OF SUCH DAMAGE. |
26 +----------------------------------------------------------------------------+
27 */
28
29 #include "gate/net/ftpclients.h"
30 #include "gate/files.h"
31 #include "gate/debugging.h"
32
33 #if defined(GATE_SYS_WIN)
34 # if defined(GATE_SYS_WINSTORE) || defined(GATE_SYS_WIN16)
35 # define GATE_NET_FTPCLIENT_NO_IMPL
36 # else
37 # define GATE_NET_FTPCLIENT_USE_WININET
38 # endif
39 #elif defined(GATE_SYS_DOS) || defined(GATE_SYS_ANDROID)
40 # define GATE_NET_FTPCLIENT_NO_IMPL
41 #else
42 # define GATE_NET_FTPCLIENT_USE_CURL
43 #endif
44
45
46 /* generic implementation: */
47
48 gate_result_t gate_ftpclient_upload(gate_ftpclient_t* client, gate_stream_t* source_stream, gate_string_t const* dest_path)
49 {
50 gate_result_t ret;
51 gate_stream_t* ftp_stream = NULL;
52 do
53 {
54 ret = gate_ftpclient_open_stream(client, dest_path, GATE_STREAM_OPEN_WRITE, &ftp_stream);
55 GATE_BREAK_IF_FAILED(ret);
56
57 ret = gate_stream_transfer(source_stream, ftp_stream);
58 GATE_BREAK_IF_FAILED(ret);
59
60 gate_stream_flush(ftp_stream);
61
62 } while (0);
63
64 if (ftp_stream)
65 {
66 gate_object_release(ftp_stream);
67 }
68
69 return ret;
70 }
71
72 1 gate_result_t gate_ftpclient_download(gate_ftpclient_t* client, gate_string_t const* source_path, gate_stream_t* dest_stream)
73 {
74 gate_result_t ret;
75 1 gate_stream_t* ftp_stream = NULL;
76 1 ret = gate_ftpclient_open_stream(client, source_path, GATE_STREAM_OPEN_READ, &ftp_stream);
77
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (GATE_FAILED(ret))
78 {
79 return ret;
80 }
81 1 ret = gate_stream_transfer(ftp_stream, dest_stream);
82 1 gate_object_release(ftp_stream);
83 1 return ret;
84 }
85
86 gate_result_t gate_ftpclient_upload_file(gate_ftpclient_t* client, gate_string_t const* source_file, gate_string_t const* dest_path)
87 {
88 gate_result_t ret;
89 gate_controlstream_t* source_stream = NULL;
90 ret = gate_file_openstream(source_file, GATE_STREAM_OPEN_READ, &source_stream);
91 if (GATE_SUCCEEDED(ret))
92 {
93 ret = gate_ftpclient_upload(client, (gate_stream_t*)source_stream, dest_path);
94 gate_object_release(source_stream);
95 }
96 return ret;
97 }
98
99 gate_result_t gate_ftpclient_download_file(gate_ftpclient_t* client, gate_string_t const* source_path, gate_string_t const* dest_file)
100 {
101 gate_result_t ret;
102 gate_controlstream_t* dest_stream = NULL;
103 ret = gate_file_openstream(dest_file, GATE_STREAM_OPEN_WRITE, &dest_stream);
104 if (GATE_SUCCEEDED(ret))
105 {
106 ret = gate_ftpclient_download(client, source_path, (gate_stream_t*)dest_stream);
107 gate_object_release(dest_stream);
108 }
109 return ret;
110 }
111
112
113
114
115 #if defined(GATE_NET_FTPCLIENT_USE_WININET)
116
117 #include "gate/net/platform/wininet_api.h"
118 #include "gate/platforms.h"
119
120 gate_result_t gate_ftpclient_get_error()
121 {
122 gate_result_t ret = GATE_RESULT_EXECUTIONFAILED;
123 DWORD last_error = gate_win32_getlasterror();
124 TCHAR error_msg[1024];
125 DWORD error_msg_len = sizeof(error_msg) / sizeof(error_msg[0]);
126
127 if (ERROR_INTERNET_EXTENDED_ERROR == last_error)
128 {
129 WinInet.InetGetLastResponseInfo(&last_error, error_msg, &error_msg_len);
130 }
131 return ret;
132 }
133
134 gate_result_t gate_ftpclient_create(gate_ftpclient_t* client, gate_string_t const* server, gate_uint16_t port,
135 gate_string_t const* user, gate_string_t const* password, gate_enumint_t flags)
136 {
137 static gate_size_t const config_len = sizeof(client->config);
138 gate_result_t ret = GATE_RESULT_FAILED;
139 HINTERNET hclient = NULL;
140 HINTERNET hsession = NULL;
141 TCHAR server_name[1024] = GATE_INIT_EMPTY;
142 TCHAR user_name[1024] = GATE_INIT_EMPTY;
143 TCHAR user_pass[1024] = GATE_INIT_EMPTY;
144 gate_size_t user_len;
145 gate_bool_t passive = GATE_FLAG_ENABLED(flags, GATE_FTPCLIENT_FLAG_PASSIVE);
146
147 do
148 {
149 gate_mem_clear(client, sizeof(gate_ftpclient_t));
150 gate_string_to_buffer(server, client->server, sizeof(client->server));
151 user_len = gate_string_to_buffer(user, &client->config[0], config_len);
152 gate_string_to_buffer(password, &client->config[user_len + 1], config_len - user_len - 1);
153
154 client->port = port ? port : 21;
155
156 ret = load_wininet_api();
157 GATE_BREAK_IF_FAILED(ret);
158
159 gate_win32_string_2_winstr(server, server_name, sizeof(server_name) / sizeof(server_name[0]));
160
161 gate_win32_string_2_winstr(user, user_name, sizeof(user_name) / sizeof(user_name[0]));
162 gate_win32_string_2_winstr(password, user_pass, sizeof(user_pass) / sizeof(user_pass[0]));
163
164 client->flags = flags;
165
166
167 hclient = WinInet.InetOpen(_T("GATE WinInet FTP Client"), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
168 if (NULL == hclient)
169 {
170 ret = GATE_RESULT_NOTSUPPORTED;
171 break;
172 }
173
174 hsession = WinInet.InetConnect(hclient, server_name, (gate_uint16_t)(port == 0 ? 21 : port),
175 user_name, user_pass,
176 INTERNET_SERVICE_FTP,
177 passive ? INTERNET_FLAG_PASSIVE : 0,
178 (DWORD_PTR)(void*)client);
179 if (NULL == hsession)
180 {
181 gate_ftpclient_get_error();
182 ret = GATE_RESULT_NOTAVAILABLE;
183 break;
184 }
185
186 client->handles[0] = (void*)hclient;
187 client->handles[1] = (void*)hsession;
188 hclient = NULL;
189 hsession = NULL;
190
191 ret = GATE_RESULT_OK;
192
193 } while (0);
194
195 if (hsession != NULL)
196 {
197 WinInet.InetCloseHandle(hsession);
198 }
199 if (hclient != NULL)
200 {
201 WinInet.InetCloseHandle(hclient);
202 }
203 return ret;
204 }
205
206 static HINTERNET gate_ftpclient_new_session(gate_ftpclient_t* client)
207 {
208 HINTERNET hclient = (HINTERNET)client->handles[0];
209 HINTERNET hsession = NULL;
210 TCHAR server_name[1024] = GATE_INIT_EMPTY;
211 TCHAR user_name[1024] = GATE_INIT_EMPTY;
212 TCHAR user_pass[1024] = GATE_INIT_EMPTY;
213 gate_size_t user_len;
214 char const* ptr_pass;
215 gate_size_t pass_len;
216 gate_bool_t passive = GATE_FLAG_ENABLED(client->flags, GATE_FTPCLIENT_FLAG_PASSIVE);
217
218 do
219 {
220 gate_win32_utf8_2_winstr(client->server, gate_str_length(client->server),
221 server_name, sizeof(server_name) / sizeof(server_name[0]));
222 user_len = gate_str_length(client->config);
223 gate_win32_utf8_2_winstr(client->config, user_len,
224 user_name, sizeof(user_name) / sizeof(user_name[0]));
225 ptr_pass = &client->config[0] + user_len + 1;
226 pass_len = gate_str_length(ptr_pass);
227 gate_win32_utf8_2_winstr(ptr_pass, pass_len,
228 user_pass, sizeof(user_pass) / sizeof(user_pass[0]));
229
230 hsession = WinInet.InetConnect(hclient, server_name, client->port == 0 ? 21 : client->port,
231 user_name, user_pass,
232 INTERNET_SERVICE_FTP,
233 passive ? INTERNET_FLAG_PASSIVE : 0,
234 (DWORD_PTR)(void*)client);
235 } while (0);
236 return hsession;
237 }
238
239 gate_result_t gate_ftpclient_release(gate_ftpclient_t* client)
240 {
241 HINTERNET hclient = (HINTERNET)client->handles[0];
242 HINTERNET hsession = (HINTERNET)client->handles[1];
243 if (hsession != NULL)
244 {
245 WinInet.InetCloseHandle(hsession);
246 }
247 if (hclient != NULL)
248 {
249 WinInet.InetCloseHandle(hclient);
250 }
251 gate_mem_clear(client, sizeof(gate_ftpclient_t));
252 return GATE_RESULT_OK;
253 }
254
255 static gate_timestamp_t convert_filetime(FILETIME const* ft)
256 {
257 return (gate_timestamp_t)((((gate_uint64_t)ft->dwHighDateTime << 32) + (gate_uint64_t)ft->dwLowDateTime) / 10);
258 }
259
260 gate_result_t gate_ftpclient_list_files(gate_ftpclient_t* client, gate_string_t const* path,
261 gate_file_list_callback_t cb, void* user_param)
262 {
263 gate_result_t ret = GATE_RESULT_FAILED;
264 HINTERNET hsession = (HINTERNET)client->handles[1];
265 TCHAR str_path[GATE_MAX_FILEPATH_LENGTH];
266 gate_size_t str_path_len;
267 HINTERNET hfind = NULL;
268 WIN32_FIND_DATA find_data;
269 gate_file_entry_t entry;
270 gate_size_t cFileNameLen;
271 gate_size_t name_len;
272
273 do
274 {
275 str_path_len = gate_win32_string_2_winstr(path, str_path, sizeof(str_path) / sizeof(str_path[0]));
276 if (str_path_len == 0)
277 {
278 ret = GATE_RESULT_INVALIDARG;
279 break;
280 }
281 if (str_path[str_path_len - 1] != '/')
282 {
283 str_path[str_path_len] = '/';
284 ++str_path_len;
285 str_path[str_path_len] = '\0';
286 }
287
288 hfind = WinInet.FTPFindFirstFile(hsession, str_path, &find_data, INTERNET_FLAG_RELOAD, (DWORD_PTR)client);
289 if (hfind == NULL)
290 {
291 ret = GATE_RESULT_EXECUTIONFAILED;
292 break;
293 }
294
295 ret = GATE_RESULT_OK;
296 do
297 {
298 gate_mem_clear(&entry, sizeof(entry));
299
300 cFileNameLen = gate_win32_winstr_length(find_data.cFileName);
301 name_len = gate_win32_winstr_2_utf8(find_data.cFileName, cFileNameLen, entry.name, sizeof(entry.name));
302 if ((gate_str_compare(entry.name, name_len, "..", 2) == 0) || (gate_str_compare(entry.name, name_len, "..", 2) == 0))
303 {
304 continue;
305 }
306
307 entry.props.time_accessed = convert_filetime(&find_data.ftLastAccessTime);
308 entry.props.time_created = convert_filetime(&find_data.ftCreationTime);
309 entry.props.time_modified = convert_filetime(&find_data.ftLastWriteTime);
310
311 entry.props.size = ((((gate_uint64_t)find_data.nFileSizeHigh) << 32) + ((gate_uint64_t)find_data.nFileSizeLow));
312
313 if (GATE_FLAG_ENABLED(find_data.dwFileAttributes, FILE_ATTRIBUTE_DIRECTORY))
314 {
315 entry.props.attribs = GATE_FILEENTRY_ATTRIB_DIRECTORY;
316 }
317 else
318 {
319 entry.props.attribs = GATE_FILEENTRY_ATTRIB_FILE;
320 }
321 if (GATE_FLAG_ENABLED(find_data.dwFileAttributes, FILE_ATTRIBUTE_READONLY)) entry.props.attribs |= GATE_FILEENTRY_ATTRIB_READONLY;
322 if (GATE_FLAG_ENABLED(find_data.dwFileAttributes, FILE_ATTRIBUTE_HIDDEN)) entry.props.attribs |= GATE_FILEENTRY_ATTRIB_HIDDEN;
323 if (GATE_FLAG_ENABLED(find_data.dwFileAttributes, FILE_ATTRIBUTE_SYSTEM)) entry.props.attribs |= GATE_FILEENTRY_ATTRIB_SYSTEM;
324 if (GATE_FLAG_ENABLED(find_data.dwFileAttributes, FILE_ATTRIBUTE_ARCHIVE)) entry.props.attribs |= GATE_FILEENTRY_ATTRIB_ARCHIVE;
325
326 if (cb)
327 {
328 if (!cb(&entry, user_param))
329 {
330 break;
331 }
332 }
333
334 } while (WinInet.InetFindNextFile(hfind, &find_data));
335 } while (0);
336
337 if (hfind != NULL)
338 {
339 WinInet.InetCloseHandle(hfind);
340 }
341 return ret;
342 }
343
344 gate_result_t gate_ftpclient_create_directory(gate_ftpclient_t* client, gate_string_t const* parent_dir,
345 gate_string_t const* newname)
346 {
347 gate_result_t ret = GATE_RESULT_FAILED;
348 HINTERNET hsession = (HINTERNET)client->handles[1];
349 TCHAR str_dir[GATE_MAX_FILEPATH_LENGTH] = GATE_INIT_EMPTY;
350 TCHAR str_name[GATE_MAX_FILEPATH_LENGTH] = GATE_INIT_EMPTY;
351 do
352 {
353 if (!gate_string_is_empty(parent_dir))
354 {
355 gate_win32_string_2_winstr(parent_dir, str_dir, sizeof(str_dir) / sizeof(str_dir[0]));
356 if (!WinInet.FTPSetCurrentDirectory(hsession, str_dir))
357 {
358 ret = gate_ftpclient_get_error();
359 break;
360 }
361 }
362 gate_win32_string_2_winstr(newname, str_name, sizeof(str_name) / sizeof(str_name[0]));
363
364 if (WinInet.FTPCreateDirectory(hsession, str_name))
365 {
366 ret = GATE_RESULT_OK;
367 }
368 else
369 {
370 ret = gate_ftpclient_get_error();
371 }
372 } while (0);
373 return ret;
374 }
375
376 gate_result_t gate_ftpclient_delete_directory(gate_ftpclient_t* client, gate_string_t const* path)
377 {
378 gate_result_t ret = GATE_RESULT_FAILED;
379 HINTERNET hsession = (HINTERNET)client->handles[1];
380 TCHAR str_path[GATE_MAX_FILEPATH_LENGTH] = GATE_INIT_EMPTY;
381
382 do
383 {
384 gate_win32_string_2_winstr(path, str_path, sizeof(str_path) / sizeof(str_path[0]));
385 if (!WinInet.FTPRemoveDirectory(hsession, str_path))
386 {
387 ret = gate_ftpclient_get_error();
388 }
389 else
390 {
391 ret = GATE_RESULT_OK;
392 }
393 } while (0);
394 return ret;
395 }
396
397 gate_result_t gate_ftpclient_delete_file(gate_ftpclient_t* client, gate_string_t const* path)
398 {
399 gate_result_t ret = GATE_RESULT_FAILED;
400 HINTERNET hsession = (HINTERNET)client->handles[1];
401 TCHAR str_path[GATE_MAX_FILEPATH_LENGTH] = GATE_INIT_EMPTY;
402
403 do
404 {
405 gate_win32_string_2_winstr(path, str_path, sizeof(str_path) / sizeof(str_path[0]));
406 if (!WinInet.FTPDeleteFile(hsession, str_path))
407 {
408 ret = GATE_RESULT_EXECUTIONFAILED;
409 }
410 else
411 {
412 ret = GATE_RESULT_OK;
413 }
414 } while (0);
415 return ret;
416 }
417
418 gate_result_t gate_ftpclient_rename(gate_ftpclient_t* client,
419 gate_string_t const* from_path, gate_string_t const* to_path)
420 {
421 gate_result_t ret = GATE_RESULT_FAILED;
422 HINTERNET hsession = (HINTERNET)client->handles[1];
423 TCHAR str_path_from[GATE_MAX_FILEPATH_LENGTH] = GATE_INIT_EMPTY;
424 TCHAR str_path_to[GATE_MAX_FILEPATH_LENGTH] = GATE_INIT_EMPTY;
425
426 do
427 {
428 gate_win32_string_2_winstr(from_path, str_path_from, sizeof(str_path_from) / sizeof(str_path_from[0]));
429 gate_win32_string_2_winstr(to_path, str_path_to, sizeof(str_path_to) / sizeof(str_path_to[0]));
430 if (!WinInet.FTPRenameFile(hsession, str_path_from, str_path_to))
431 {
432 ret = gate_ftpclient_get_error();
433 }
434 else
435 {
436 ret = GATE_RESULT_OK;
437 }
438 } while (0);
439 return ret;
440 }
441
442 gate_result_t gate_ftpclient_get_current_directory(gate_ftpclient_t* client,
443 gate_string_t* path)
444 {
445 gate_result_t ret = GATE_RESULT_FAILED;
446 HINTERNET hsession = (HINTERNET)client->handles[1];
447 TCHAR str_path[GATE_MAX_FILEPATH_LENGTH] = GATE_INIT_EMPTY;
448 DWORD str_path_len = (sizeof(str_path) / sizeof(str_path[0])) - 1;
449
450 do
451 {
452 if (!WinInet.FTPGetCurrentDirectory(hsession, str_path, &str_path_len))
453 {
454 ret = gate_ftpclient_get_error();
455 }
456 else
457 {
458 if (NULL == gate_win32_winstr_2_utf8_string(str_path, str_path_len, path))
459 {
460 ret = GATE_RESULT_OUTOFMEMORY;
461 }
462 else
463 {
464 ret = GATE_RESULT_OK;
465 }
466 }
467 } while (0);
468 return ret;
469 }
470
471 gate_result_t gate_ftpclient_set_current_directory(gate_ftpclient_t* client,
472 gate_string_t const* path)
473 {
474 gate_result_t ret = GATE_RESULT_FAILED;
475 HINTERNET hsession = (HINTERNET)client->handles[1];
476 TCHAR str_path[GATE_MAX_FILEPATH_LENGTH] = GATE_INIT_EMPTY;
477
478 do
479 {
480 gate_win32_string_2_winstr(path, str_path, sizeof(str_path) / sizeof(str_path[0]));
481 if (!WinInet.FTPSetCurrentDirectory(hsession, str_path))
482 {
483 ret = gate_ftpclient_get_error();
484 }
485 else
486 {
487 ret = GATE_RESULT_OK;
488 }
489 } while (0);
490 return ret;
491 }
492
493
494 static char const* gate_ftpstream_get_interface_name(void* obj);
495 static void gate_ftpstream_release(void* obj);
496 static int gate_ftpstream_retain(void* obj);
497
498 static gate_result_t gate_ftpstream_read(void* obj, char* buffer, gate_size_t bufferlength, gate_size_t* returned);
499 static gate_result_t gate_ftpstream_peek(void* obj, char* buffer, gate_size_t bufferlength, gate_size_t* returned);
500 static gate_result_t gate_ftpstream_write(void* obj, char const* buffer, gate_size_t bufferlength, gate_size_t* written);
501 static gate_result_t gate_ftpstream_flush(void* obj);
502
503
504 static GATE_INTERFACE_VTBL(gate_stream) gate_ftpstream_vtbl;
505 static void gate_init_ftpstream_vtbl()
506 {
507 if (!gate_ftpstream_vtbl.get_interface_name)
508 {
509 GATE_INTERFACE_VTBL(gate_stream) const local_vtbl =
510 {
511 &gate_ftpstream_get_interface_name,
512 &gate_ftpstream_release,
513 &gate_ftpstream_retain,
514
515 &gate_ftpstream_read,
516 &gate_ftpstream_peek,
517 &gate_ftpstream_write,
518 &gate_ftpstream_flush
519 };
520 gate_ftpstream_vtbl = local_vtbl;
521 }
522 }
523
524 typedef struct gate_ftpstream_class
525 {
526 GATE_INTERFACE_VTBL(gate_stream) const* vtbl;
527
528 gate_atomic_int_t ref_counter;
529 HINTERNET session;
530 gate_bool_t upload;
531 } gate_ftpstream_t;
532
533 static char const* gate_ftpstream_get_interface_name(void* obj)
534 {
535 GATE_UNUSED_ARG(obj);
536 return GATE_INTERFACE_NAME_STREAM;
537 }
538 static void gate_ftpstream_release(void* obj)
539 {
540 gate_ftpstream_t* stream = (gate_ftpstream_t*)obj;
541 if (0 == gate_atomic_int_dec(&stream->ref_counter))
542 {
543 WinInet.InetCloseHandle(stream->session);
544 gate_mem_dealloc(obj);
545 }
546 }
547 static int gate_ftpstream_retain(void* obj)
548 {
549 gate_ftpstream_t* stream = (gate_ftpstream_t*)obj;
550 return gate_atomic_int_inc(&stream->ref_counter);
551 }
552
553 static gate_result_t gate_ftpstream_read(void* obj, char* buffer, gate_size_t bufferlength, gate_size_t* returned)
554 {
555 gate_result_t ret = GATE_RESULT_FAILED;
556 gate_ftpstream_t* stream = (gate_ftpstream_t*)obj;
557 DWORD bytes_returned;
558 do
559 {
560 if (stream->upload)
561 {
562 ret = GATE_RESULT_INVALIDSTATE;
563 break;
564 }
565 if (WinInet.InetReadFile(stream->session, buffer, (DWORD)bufferlength, &bytes_returned))
566 {
567 if (returned)
568 {
569 *returned = (gate_size_t)bytes_returned;
570 }
571 ret = GATE_RESULT_OK;
572 }
573 } while (0);
574 return ret;
575 }
576 static gate_result_t gate_ftpstream_peek(void* obj, char* buffer, gate_size_t bufferlength, gate_size_t* returned)
577 {
578 gate_ftpstream_t* stream = (gate_ftpstream_t*)obj;
579 GATE_UNUSED_ARG(stream);
580 GATE_UNUSED_ARG(buffer);
581 GATE_UNUSED_ARG(bufferlength);
582 GATE_UNUSED_ARG(returned);
583 return GATE_RESULT_NOTSUPPORTED;
584 }
585 static gate_result_t gate_ftpstream_write(void* obj, char const* buffer, gate_size_t bufferlength, gate_size_t* written)
586 {
587 gate_result_t ret = GATE_RESULT_FAILED;
588 gate_ftpstream_t* stream = (gate_ftpstream_t*)obj;
589 DWORD bytes_written;
590 do
591 {
592 if (!stream->upload)
593 {
594 ret = GATE_RESULT_INVALIDSTATE;
595 break;
596 }
597 if (WinInet.InetWriteFile(stream->session, buffer, (DWORD)bufferlength, &bytes_written))
598 {
599 if (written)
600 {
601 *written = (gate_size_t)bytes_written;
602 }
603 ret = GATE_RESULT_OK;
604 }
605 } while (0);
606 return ret;
607 }
608 static gate_result_t gate_ftpstream_flush(void* obj)
609 {
610 GATE_UNUSED_ARG(obj);
611 return GATE_RESULT_OK;
612 }
613
614
615 gate_result_t gate_ftpclient_open_stream(gate_ftpclient_t* client, gate_string_t const* path,
616 gate_enumint_t stream_open_flags, gate_stream_t** ptr_stream)
617 {
618 gate_result_t ret = GATE_RESULT_FAILED;
619 HINTERNET hsession = (HINTERNET)client->handles[1];
620 gate_bool_t is_upload = GATE_FLAG_ENABLED(stream_open_flags, GATE_STREAM_OPEN_WRITE);
621 DWORD dwAccess = is_upload ? GENERIC_WRITE : GENERIC_READ;
622 DWORD dwFlags = FTP_TRANSFER_TYPE_BINARY;
623 HINTERNET hfile = NULL;
624 gate_ftpstream_t* ftpstream = NULL;
625 do
626 {
627 TCHAR ftppath[GATE_MAX_FILEPATH_LENGTH];
628 gate_win32_string_2_winstr(path, ftppath, sizeof(ftppath) / sizeof(ftppath[0]));
629
630 hfile = WinInet.FTPOpenFile(hsession, ftppath, dwAccess, dwFlags, (DWORD_PTR)client);
631 if (hfile == NULL)
632 {
633 ret = GATE_RESULT_FAILED;
634 break;
635 }
636 ftpstream = gate_mem_alloc(sizeof(gate_ftpstream_t));
637 if (ftpstream == NULL)
638 {
639 ret = GATE_RESULT_OUTOFMEMORY;
640 break;
641 }
642 gate_mem_clear(ftpstream, sizeof(gate_ftpstream_t));
643 gate_init_ftpstream_vtbl();
644 ftpstream->vtbl = &gate_ftpstream_vtbl;
645 gate_atomic_int_init(&ftpstream->ref_counter, 1);
646 ftpstream->upload = is_upload;
647 ftpstream->session = hfile;
648 hfile = NULL;
649 *ptr_stream = (gate_stream_t*)ftpstream;
650
651 ret = GATE_RESULT_OK;
652 } while (0);
653
654 if (hfile != NULL)
655 {
656 WinInet.InetCloseHandle(hfile);
657 }
658
659 return ret;
660 }
661
662
663
664 #endif
665
666 #if defined(GATE_NET_FTPCLIENT_USE_CURL)
667
668 #include "gate/net/platform/libcurl_api.h"
669 #include "gate/uris.h"
670 #include "gate/times.h"
671
672 static gate_result_t gate_curl_build_ftp_url(gate_ftpclient_t const* client, gate_string_t const* svr_path, gate_string_t* uri_path)
673 {
674 gate_result_t ret = GATE_RESULT_FAILED;
675 gate_uri_t uri;
676
677 do
678 {
679 gate_uri_init(&uri);
680 gate_string_create_static(&uri.scheme, GATE_URI_SCHEME_FTP);
681 gate_string_create_static(&uri.user_info, &client->config[0]);
682 gate_string_create_static(&uri.host, &client->server[0]);
683 uri.port = (client->port == 0) ? GATE_FTPCLIENT_DEFAULT_PORT : client->port;
684 if (!gate_string_is_empty(svr_path))
685 {
686 gate_string_duplicate(&uri.absolute_path, svr_path);
687 }
688 else
689 {
690 gate_string_create_static(&uri.absolute_path, "/");
691 }
692 ret = gate_uri_to_string(&uri, uri_path, false);
693 } while (0);
694
695 gate_uri_destroy(&uri);
696
697 return ret;
698 }
699
700 3 static gate_result_t gate_curl_ftp_setup(CURL* ptr_curl, gate_string_t const* url, gate_bool_t passive,
701 gate_stream_t* input_stream, gate_stream_t* output_stream)
702 {
703 3 gate_result_t ret = GATE_RESULT_FAILED;
704 static char const port_range[] = "-";
705
706 do
707 {
708 3 ret = gate_curl_setup(ptr_curl, url, input_stream, output_stream);
709
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 GATE_BREAK_IF_FAILED(ret);
710
711
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (!passive)
712 {
713 if (CURLE_OK != curl.easy_setopt(ptr_curl, CURLOPT_FTPPORT, &port_range[0]))
714 {
715 ret = GATE_RESULT_FAILED;
716 break;
717 }
718 }
719
720 3 curl.easy_setopt(ptr_curl, CURLOPT_DNS_CACHE_TIMEOUT, (long)60);
721 3 curl.easy_setopt(ptr_curl, CURLOPT_CONNECTTIMEOUT, (long)5);
722 3 curl.easy_setopt(ptr_curl, CURLOPT_FTP_RESPONSE_TIMEOUT, (long)5);
723 3 curl.easy_setopt(ptr_curl, CURLOPT_ACCEPTTIMEOUT_MS, (long)5000);
724
725 } while (0);
726
727 3 return ret;
728 }
729
730 3 static gate_result_t gate_curl_ftp_setup_str(CURL* ptr_curl, char const* url, gate_bool_t passive,
731 gate_stream_t* input_stream, gate_stream_t* output_stream)
732 {
733 gate_string_t urlstr;
734 3 gate_string_create_static(&urlstr, url);
735 3 return gate_curl_ftp_setup(ptr_curl, &urlstr, passive, input_stream, output_stream);
736 }
737
738
739 1 gate_result_t gate_ftpclient_create(gate_ftpclient_t* client, gate_string_t const* server, gate_uint16_t port,
740 gate_string_t const* user, gate_string_t const* password,
741 gate_enumint_t flags)
742 {
743 static gate_string_t default_anonymous_login = GATE_STRING_INIT_STATIC("anonymous:anonymous%40opengate.at");
744 1 gate_result_t ret = GATE_RESULT_FAILED;
745 1 CURL* pcurl = NULL;
746 1 gate_string_t tmp = GATE_STRING_INIT_EMPTY;
747 gate_size_t len;
748
749 do
750 {
751
752 1 ret = load_libcurl_functions();
753
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 GATE_BREAK_IF_FAILED(ret);
754
755 1 gate_mem_clear(client, sizeof(gate_ftpclient_t));
756
757
758 /* store server and base-url */
759 1 gate_string_to_buffer(server, client->server, sizeof(client->server));
760
761
1/4
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
1 if (!gate_string_is_empty(user) || !gate_string_is_empty(password))
762 {
763 1 len = 0;
764
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 if (!gate_string_is_empty(user))
765 {
766 1 ret = gate_uri_escape(user, &tmp);
767
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 GATE_BREAK_IF_FAILED(ret);
768 1 len = gate_string_to_buffer(&tmp, client->config, sizeof(client->config));
769 1 gate_string_release(&tmp);
770
771
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 if (!gate_string_is_empty(password))
772 {
773 1 client->config[len] = ':';
774 1 ++len;
775
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (len >= sizeof(client->config))
776 {
777 len = sizeof(client->config) - 1;
778 client->config[len] = 0;
779 }
780 else
781 {
782 1 ret = gate_uri_escape(password, &tmp);
783
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 GATE_BREAK_IF_FAILED(ret);
784 1 len = gate_string_to_buffer(&tmp, &client->config[len], sizeof(client->config) - len);
785 1 gate_string_release(&tmp);
786 }
787 }
788 }
789 else
790 {
791 gate_string_to_buffer(&default_anonymous_login, client->config, sizeof(client->config));
792 }
793 }
794
795 1 pcurl = curl.easy_init();
796
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!pcurl)
797 {
798 ret = GATE_RESULT_NOTAVAILABLE;
799 break;
800 }
801
802 1 client->port = port;
803 1 client->handles[0] = (void*)pcurl;
804 1 client->flags = flags;
805
806 1 pcurl = NULL;
807 1 ret = GATE_RESULT_OK;
808 } while (0);
809
810
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (pcurl)
811 {
812 curl.easy_cleanup(pcurl);
813 }
814 1 gate_string_release(&tmp);
815
816 1 return ret;
817 }
818
819 1 gate_result_t gate_ftpclient_release(gate_ftpclient_t* client)
820 {
821 1 CURL* ptr_curl = NULL;
822
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (client)
823 {
824 1 ptr_curl = (CURL*)client->handles[0];
825
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (ptr_curl)
826 {
827 1 curl.easy_cleanup(ptr_curl);
828 }
829 1 gate_mem_clear(client, sizeof(gate_ftpclient_t));
830 }
831 1 return GATE_RESULT_OK;
832 }
833
834 3 static gate_size_t build_ftp_path(gate_ftpclient_t* client, gate_string_t const* path, gate_string_t const* subelement,
835 char* buffer, gate_size_t buffer_capacity, gate_size_t buffer_used)
836 {
837 gate_strbuilder_t builder;
838 3 gate_strbuilder_create_static(&builder, buffer, buffer_capacity, buffer_used);
839 3 gate_bool_t server_path_ends_with_slash = false;
840
841
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (client != NULL)
842 {
843 3 buffer_used += gate_strbuilder_append_cstr(&builder, GATE_URI_SCHEME_FTP);
844 3 buffer_used += gate_strbuilder_append_cstr(&builder, GATE_URI_SCHEME_SEPARATOR);
845 3 buffer_used += gate_strbuilder_append_cstr(&builder, client->config);
846 3 buffer_used += gate_strbuilder_append_cstr(&builder, "@");
847 3 buffer_used += gate_strbuilder_append_cstr(&builder, client->server);
848
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
3 if ((client->port != GATE_FTPCLIENT_DEFAULT_PORT) && (client->port != 0))
849 {
850 buffer_used += gate_strbuilder_append_cstr(&builder, ":");
851 buffer_used += gate_strbuilder_append_uint16(&builder, client->port);
852 }
853 3 server_path_ends_with_slash = (buffer[buffer_used - 1] == '/');
854
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (!server_path_ends_with_slash)
855 {
856 /* we need a path separator */
857 3 buffer_used += gate_strbuilder_append_chars(&builder, 1, '/');
858 3 server_path_ends_with_slash = true;
859 }
860 }
861
862
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 1 times.
3 if (!gate_string_is_empty(path))
863 {
864 /* skip path separator, if server path ends with one */
865 2 const gate_size_t offset =
866
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
2 (server_path_ends_with_slash && gate_string_starts_with_char(path, '/')) ? 1 : 0;
867 2 buffer_used += gate_strbuilder_append_text(&builder, gate_string_ptr(path, offset), gate_string_length(path) - offset);
868 2 server_path_ends_with_slash = (buffer[buffer_used - 1] == '/');
869 }
870
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if (!gate_string_is_empty(subelement))
871 {
872 const gate_bool_t starts_with_slash = gate_string_starts_with_char(path, '/');
873 if (server_path_ends_with_slash && starts_with_slash)
874 {
875 /* we need to cut one path separator */
876 buffer_used += gate_strbuilder_append_text(&builder, gate_string_ptr(subelement, 1), gate_string_length(subelement) - 1);
877 }
878 else
879 {
880 const gate_bool_t has_path = (client != NULL) || !gate_string_is_empty(path);
881 if (has_path && !server_path_ends_with_slash && !starts_with_slash)
882 {
883 /* we need a path separator */
884 buffer_used += gate_strbuilder_append_chars(&builder, 1, '/');
885 }
886 buffer_used += gate_strbuilder_append_text(&builder, gate_string_ptr(subelement, 0), gate_string_length(subelement));
887 }
888 }
889 3 return gate_strbuilder_length(&builder);
890 }
891
892 3 static gate_result_t gate_curl_ftp_perform(CURL* ptr_curl)
893 {
894 3 char const* errmsg = "Unknown CURL error";
895 3 CURLcode curlcode = curl.easy_perform(ptr_curl);
896 3 gate_result_t ret = gate_curl_map_result(curlcode, &errmsg);
897
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 GATE_DEBUG_TRACE_FAILED(ret, errmsg);
898 3 return ret;
899 }
900
901 1 static gate_result_t gate_curl_ftp_execute(CURL* ptr_curl, struct curl_slist* pre_cmd, struct curl_slist* cmd, struct curl_slist* post_cmd)
902 {
903 gate_result_t result;
904 CURLcode curlcode;
905 do
906 {
907
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!cmd)
908 {
909 result = GATE_RESULT_INVALIDARG;
910 break;
911 }
912
913 /* setup parameters */
914 1 curlcode = curl.easy_setopt(ptr_curl, CURLOPT_PREQUOTE, pre_cmd);
915 1 curlcode = curl.easy_setopt(ptr_curl, CURLOPT_QUOTE, cmd);
916 1 curlcode = curl.easy_setopt(ptr_curl, CURLOPT_POSTQUOTE, post_cmd);
917
918 /* execute FTP command */
919 1 result = gate_curl_ftp_perform(ptr_curl);
920
921 /* reset parameters */
922 1 curl.easy_setopt(ptr_curl, CURLOPT_POSTQUOTE, NULL);
923 1 curl.easy_setopt(ptr_curl, CURLOPT_QUOTE, NULL);
924 1 curl.easy_setopt(ptr_curl, CURLOPT_PREQUOTE, NULL);
925
926 } while (0);
927 1 return result;
928 }
929
930 3 static gate_result_t gate_curl_ftp_setup_default(gate_ftpclient_t* client, gate_string_t const* path, gate_stream_t* in_stream, gate_stream_t* out_stream)
931 {
932 3 CURL* const ptr_curl = (CURL*)client->handles[0];
933 3 gate_bool_t const passive = GATE_FLAG_ENABLED(client->flags, GATE_FTPCLIENT_FLAG_PASSIVE);
934 3 char url[GATE_MAX_FILEPATH_LENGTH] = "";
935 3 gate_size_t url_used = build_ftp_path(client, path, NULL, url, sizeof(url), 0);
936
937 3 return gate_curl_ftp_setup_str(ptr_curl, url, passive, in_stream, out_stream);
938 }
939
940 1 static gate_result_t gate_curl_ftp_execute_command(gate_ftpclient_t* client, char const* pre_cmd, char const* cmd, char const* post_cmd)
941 {
942 gate_result_t result;
943 1 struct curl_slist* ptr_pre_list = NULL;
944 1 struct curl_slist* ptr_cmd_list = NULL;
945 1 struct curl_slist* ptr_post_list = NULL;
946
947
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if ((client == NULL) || (cmd == NULL))
948 {
949 return GATE_RESULT_INVALIDARG;
950 }
951
952 do
953 {
954 1 CURL* const ptr_curl = (CURL*)client->handles[0];
955
956 1 result = gate_curl_ftp_setup_default(client, NULL, NULL, NULL);
957
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 GATE_BREAK_IF_FAILED(result);
958
959
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (pre_cmd)
960 {
961 ptr_pre_list = curl.slist_append(NULL, pre_cmd);
962 }
963 1 ptr_cmd_list = curl.slist_append(NULL, cmd);
964
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (post_cmd)
965 {
966 ptr_post_list = curl.slist_append(NULL, post_cmd);
967 }
968
969 1 result = gate_curl_ftp_execute(ptr_curl, ptr_pre_list, ptr_cmd_list, ptr_post_list);
970
971 1 gate_curl_reset(ptr_curl);
972 } while (0);
973
974
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ptr_pre_list != NULL) curl.slist_free_all(ptr_pre_list);
975
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (ptr_cmd_list != NULL) curl.slist_free_all(ptr_cmd_list);
976
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ptr_post_list != NULL) curl.slist_free_all(ptr_post_list);
977
978 1 return result;
979 }
980
981 1073 static gate_bool_t is_whitespace(char chr)
982 {
983
6/8
✓ Branch 0 taken 845 times.
✓ Branch 1 taken 228 times.
✓ Branch 2 taken 845 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 845 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 16 times.
✓ Branch 7 taken 829 times.
1073 return (chr == ' ') || (chr == '\t') || (chr == '\r') || (chr == '\n');
984 }
985
986 16 static gate_size_t tokenize_directory_entry(char const* line, gate_size_t linelen, gate_string_t* tokens, gate_size_t tokens_capacity)
987 {
988 16 char const* ptr_start = NULL;
989 16 char const* ptr = line;
990 16 char const* ptr_end = line + linelen;
991 16 gate_size_t tokens_used = 0;
992
993
3/4
✓ Branch 0 taken 1073 times.
✓ Branch 1 taken 16 times.
✓ Branch 2 taken 1073 times.
✗ Branch 3 not taken.
1089 for (; (ptr != ptr_end) && (tokens_used != tokens_capacity); ++ptr)
994 {
995 1073 const gate_bool_t at_space = is_whitespace(*ptr);
996
4/4
✓ Branch 0 taken 244 times.
✓ Branch 1 taken 829 times.
✓ Branch 2 taken 144 times.
✓ Branch 3 taken 100 times.
1073 if (at_space && (ptr_start != NULL))
997 {
998 /* end of token */
999 144 gate_string_create_static_len(&tokens[tokens_used], ptr_start, ptr - ptr_start);
1000 144 ++tokens_used;
1001 144 ptr_start = NULL;
1002 }
1003
4/4
✓ Branch 0 taken 244 times.
✓ Branch 1 taken 685 times.
✓ Branch 2 taken 144 times.
✓ Branch 3 taken 100 times.
929 else if ((ptr_start == NULL) && !at_space)
1004 {
1005 /* begin of new token */
1006 144 ptr_start = ptr;
1007 }
1008 }
1009
1010 /* add last entry, if one was started */
1011
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
16 if ((ptr_start != NULL) && (tokens_used != tokens_capacity))
1012 {
1013 gate_string_create_static_len(&tokens[tokens_used], ptr_start, ptr - ptr_start);
1014 ++tokens_used;
1015 }
1016
1017 16 return tokens_used;
1018 }
1019
1020
1021 16 static void parse_access_bits(gate_string_t const* bits, gate_enumint_t* ptr_attribs, gate_enumint_t* ptr_access)
1022 {
1023 16 gate_enumint_t attribs = 0;
1024 16 gate_enumint_t access = 0;
1025 16 char const* ptr = gate_string_ptr(bits, 0);
1026 16 gate_size_t len = gate_string_length(bits);
1027
1028
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 GATE_FLAG_SET(attribs, GATE_FILEENTRY_ATTRIB_DIRECTORY, (ptr[0] == 'd'));
1029
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 GATE_FLAG_SET(attribs, GATE_FILEENTRY_ATTRIB_LINK, (ptr[0] == 'l'));
1030
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 GATE_FLAG_SET(attribs, GATE_FILEENTRY_ATTRIB_DEVICE, (ptr[0] == 'c'));
1031
3/6
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
16 GATE_FLAG_SET(attribs, GATE_FILEENTRY_ATTRIB_FILE, (ptr[0] != 'd') && (ptr[0] != 'd') && (ptr[0] != 'c'));
1032
1033
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 if (len >= 10)
1034 {
1035
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 GATE_FLAG_SET(access, GATE_FILEENTRY_ACCESS_OWNERREAD, (ptr[1] == 'r'));
1036
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 6 times.
16 GATE_FLAG_SET(access, GATE_FILEENTRY_ACCESS_OWNERWRITE, (ptr[2] == 'w'));
1037
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 GATE_FLAG_SET(access, GATE_FILEENTRY_ACCESS_OWNEREXECUTE, (ptr[3] == 'x'));
1038
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 GATE_FLAG_SET(access, GATE_FILEENTRY_ACCESS_GROUPREAD, (ptr[4] == 'r'));
1039
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 GATE_FLAG_SET(access, GATE_FILEENTRY_ACCESS_GROUPWRITE, (ptr[5] == 'w'));
1040
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 GATE_FLAG_SET(access, GATE_FILEENTRY_ACCESS_GROUPEXECUTE, (ptr[6] == 'x'));
1041
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 GATE_FLAG_SET(access, GATE_FILEENTRY_ACCESS_ALLREAD, (ptr[7] == 'r'));
1042
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 GATE_FLAG_SET(access, GATE_FILEENTRY_ACCESS_ALLWRITE, (ptr[8] == 'w'));
1043
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 GATE_FLAG_SET(access, GATE_FILEENTRY_ACCESS_ALLEXECUTE, (ptr[9] == 'x'));
1044
1045
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 GATE_FLAG_SET(access, GATE_FILEENTRY_ACCESS_OWNEREXECUTE | GATE_FILEENTRY_ACCESS_OWNERSETID, (ptr[3] == 's'));
1046
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 GATE_FLAG_SET(access, GATE_FILEENTRY_ACCESS_GROUPEXECUTE | GATE_FILEENTRY_ACCESS_GROUPSETID, (ptr[6] == 's'));
1047
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 GATE_FLAG_SET(access, GATE_FILEENTRY_ACCESS_ALLEXECUTE | GATE_FILEENTRY_ACCESS_NODELETE, (ptr[9] == 't'));
1048 }
1049
1050
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 10 times.
16 if ((access & (GATE_FILEENTRY_ACCESS_OWNERWRITE | GATE_FILEENTRY_ACCESS_GROUPWRITE | GATE_FILEENTRY_ACCESS_ALLWRITE)) == 0)
1051 {
1052 6 attribs |= GATE_FILEENTRY_ATTRIB_READONLY;
1053 }
1054
1055
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 if (ptr_attribs) *ptr_attribs = attribs;
1056
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 if (ptr_access) *ptr_access = access;
1057 16 }
1058
1059 16 static void parse_year_time(gate_string_t const* text, gate_uint16_t* year, gate_uint8_t* hour, gate_uint8_t* minute)
1060 {
1061 16 char const* ptr = gate_string_ptr(text, 0);
1062 16 gate_size_t len = gate_string_length(text);
1063 16 gate_size_t pos = gate_str_char_pos(ptr, len, ':', 0);
1064 16 gate_uint64_t num = 0;
1065
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 if (pos == GATE_STR_NPOS)
1066 {
1067 /* we have year */
1068 16 gate_str_parse_uint64(ptr, len, &num);
1069 16 *year = (gate_uint16_t)num;
1070
1071 16 *hour = 0;
1072 16 *minute = 0;
1073 }
1074 else
1075 {
1076 gate_time_t tm;
1077 gate_datetime_t dt;
1078 gate_time_now(&tm);
1079 gate_time_to_datetime(&tm, &dt);
1080
1081 /* set current year */
1082 *year = dt.date.year;
1083
1084 gate_str_parse_uint64(ptr, pos, &num);
1085 *hour = (gate_uint8_t)num;
1086
1087 gate_str_parse_uint64(ptr + pos + 1, len - pos - 1, &num);
1088 *minute = (gate_uint8_t)num;
1089 }
1090 16 }
1091
1092 /* /////////////// some unix directory format samples /////////////// */
1093 /* "-rw-r--r-- 1 root other 531 Jan 29 03:26 README" */
1094 /* "dr-xr-xr-x 2 root other 512 Apr 8 1994 etc" */
1095 /* "dr-xr-xr-x 2 root 512 Apr 8 1994 etc" */
1096 /* "lrwxrwxrwx 1 root other 7 Jan 25 00:17 bin -> usr/bin" */
1097
1098
1099 /* "---------- 1 owner group 1803128 Jul 10 10:18 ls-lR.Z" */
1100 /* "d--------- 1 owner group 0 May 9 19:45 Softlib" */
1101
1102 /* "-rwxrwxrwx 1 noone nogroup 322 Aug 19 1996 message.ftp" */
1103
1104 /* "d [R----F--] supervisor 512 Jan 16 18:53 login" */
1105 /* "- [R----F--] rhesus 214059 Oct 20 15:27 cx.exe" */
1106
1107 /* "-------r-- 326 1391972 1392298 Nov 22 1995 MegaPhone.sit" */
1108 /* "drwxrwxr-x folder 2 May 10 1996 network" */
1109
1110 16 static gate_bool_t parse_unix_directory_entry(char const* line, gate_size_t linelen, gate_file_entry_t* ptr_entry)
1111 {
1112 gate_string_t tokens[16];
1113 16 gate_size_t tokens_used = tokenize_directory_entry(line, linelen, &tokens[0], sizeof(tokens) / sizeof(tokens[0]));
1114
1115 16 gate_mem_clear(ptr_entry, sizeof(gate_file_entry_t));
1116
1117
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 if (tokens_used >= 9)
1118 {
1119 16 gate_string_t const* permbits = &tokens[0];
1120 /* gate_string_t const* linkcount = &tokens[1]; */
1121 /* gate_string_t const* owner = &tokens[2]; */
1122 /* gate_string_t const* group = &tokens[3]; */
1123 16 gate_string_t const* filesize = &tokens[4];
1124 16 gate_string_t const* month = &tokens[5];
1125 16 gate_string_t const* day = &tokens[6];
1126 16 gate_string_t const* year_time = &tokens[7];
1127 16 gate_string_t const* filename = &tokens[8];
1128
1129
1130 16 parse_access_bits(permbits, &ptr_entry->props.attribs, &ptr_entry->props.access);
1131 16 gate_str_parse_uint64(gate_string_ptr(filesize, 0), gate_string_length(filesize), &ptr_entry->props.size);
1132
1133 {
1134 /* modification date / time */
1135 16 gate_datetime_t dt = GATE_INIT_EMPTY;
1136 16 gate_time_t tm = GATE_INIT_EMPTY;
1137 16 gate_uint64_t num = 0;
1138
1139 16 parse_year_time(year_time, &dt.date.year, &dt.time.hour, &dt.time.minute);
1140 16 gate_date_parse_month(gate_string_ptr(month, 0), gate_string_length(month), &dt.date.month);
1141 16 gate_string_parse_uint(day, &num);
1142 16 dt.date.day = (gate_uint8_t)num;
1143
1144 16 gate_date_to_time(&dt, &tm);
1145
1146 16 ptr_entry->props.time_modified = tm.timestamp;
1147 }
1148
1149 {
1150 /* get file name */
1151 16 char const* name_start = gate_string_ptr(filename, 0);
1152 16 char const* name_end = line + linelen;
1153 gate_string_t name;
1154
1155 16 GATE_DEBUG_ASSERT(name_end >= name_start);
1156 16 gate_string_create_static_len(&name, name_start, name_end - name_start);
1157
1158
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 if (GATE_FLAG_ENABLED(ptr_entry->props.attribs, GATE_FILEENTRY_ATTRIB_LINK))
1159 {
1160 /* remove link target path */
1161 static gate_string_t const arrow = GATE_STRING_INIT_STATIC("->");
1162 gate_size_t pos = gate_string_pos(&name, &arrow, 0);
1163 if (pos != GATE_STR_NPOS)
1164 {
1165 gate_string_substr(&name, &name, 0, pos);
1166 }
1167 }
1168
1169 16 gate_string_trim(&name, &name);
1170 16 gate_string_to_buffer(&name, ptr_entry->name, sizeof(ptr_entry->name));
1171 }
1172 16 return true;
1173 }
1174 return false;
1175 }
1176
1177 16 static gate_bool_t parse_directory_entry(char const* line, gate_size_t linelen, gate_file_entry_t* ptr_entry)
1178 {
1179 16 return parse_unix_directory_entry(line, linelen, ptr_entry);
1180 }
1181
1182 1 static gate_result_t parse_directory_listing(gate_stream_t* listing, gate_string_t const* dirpath, gate_file_list_callback_t cb, void* user_param)
1183 {
1184 static gate_string_t const dot = GATE_STRING_INIT_STATIC(".");
1185 static gate_string_t const dotdot = GATE_STRING_INIT_STATIC("..");
1186
1187 gate_result_t result;
1188 1 gate_bool_t continue_parsing = true;
1189
1190 do
1191 {
1192 char line[4096];
1193 17 gate_size_t linelen = 0;
1194 gate_file_entry_t entry;
1195
1196 17 result = gate_stream_read_line(listing, line, sizeof(line), &linelen);
1197
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
18 GATE_BREAK_IF_FAILED(result);
1198
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 16 times.
17 if (linelen == 0)
1199 {
1200 /* end of stream */
1201 1 break;
1202 }
1203
2/4
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 16 times.
✗ Branch 4 not taken.
16 if (cb && parse_directory_entry(line, linelen, &entry))
1204 {
1205 16 const gate_bool_t is_filtered_entry =
1206 16 gate_string_equals_str(&dot, entry.name)
1207
2/4
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
16 || gate_string_equals_str(&dotdot, entry.name);
1208
1209
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 if (!is_filtered_entry)
1210 {
1211 /* generate full FTP file path from parent dir + file name */
1212 16 gate_size_t pathlen = gate_string_to_buffer(dirpath, entry.path, sizeof(entry.path) - 1);
1213
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 if (entry.path[pathlen - 1] != '/')
1214 {
1215 entry.path[pathlen] = '/';
1216 ++pathlen;
1217 entry.path[pathlen] = '\0';
1218 }
1219 16 gate_str_print_text(&entry.path[pathlen], sizeof(entry.path) - pathlen, entry.name, gate_str_length(entry.name));
1220
1221 16 continue_parsing &= cb(&entry, user_param);
1222 }
1223 }
1224
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 } while (continue_parsing);
1225
1226 1 return result;
1227 }
1228
1229 1 gate_result_t gate_ftpclient_list_files(gate_ftpclient_t* client, gate_string_t const* path,
1230 gate_file_list_callback_t cb, void* user_param)
1231 {
1232 gate_result_t ret;
1233 1 gate_memstream_t* ptr_stream = NULL;
1234
1235
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
1 if (!client || gate_string_is_empty(path))
1236 {
1237 return GATE_RESULT_INVALIDARG;
1238 }
1239
1240 do
1241 {
1242 1 CURL* const ptr_curl = (CURL*)client->handles[0];
1243
1244 1 ptr_stream = gate_memstream_create(1024);
1245
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (NULL == ptr_stream)
1246 {
1247 ret = GATE_RESULT_OUTOFMEMORY;
1248 break;
1249 }
1250 1 ret = gate_curl_ftp_setup_default(client, path, NULL, (gate_stream_t*)ptr_stream);
1251
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 GATE_BREAK_IF_FAILED(ret);
1252
1253 1 ret = gate_curl_ftp_perform(ptr_curl);
1254
1255 1 gate_curl_reset(ptr_curl);
1256
1257
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 GATE_BREAK_IF_FAILED(ret);
1258
1259 1 ret = parse_directory_listing((gate_stream_t*)ptr_stream, path, cb, user_param);
1260 } while (0);
1261
1262
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (ptr_stream != NULL)
1263 {
1264 1 gate_object_release(ptr_stream);
1265 }
1266
1267 1 return ret;
1268 }
1269
1270 gate_result_t gate_ftpclient_create_directory(gate_ftpclient_t* client, gate_string_t const* parent_dir,
1271 gate_string_t const* newname)
1272 {
1273 char cmd_buffer[GATE_MAX_FILEPATH_LENGTH] = "MKD ";
1274 gate_size_t cmd_buffer_used = build_ftp_path(NULL, parent_dir, newname, cmd_buffer, sizeof(cmd_buffer), 4);
1275 gate_result_t result = gate_curl_ftp_execute_command(client, NULL, cmd_buffer, NULL);
1276 GATE_DEBUG_TRACE_FAILED(result, "gate_curl_ftp_execute_command(MKD) failed");
1277 return result;
1278 }
1279
1280 gate_result_t gate_ftpclient_delete_directory(gate_ftpclient_t* client, gate_string_t const* path)
1281 {
1282 char cmd_buffer[GATE_MAX_FILEPATH_LENGTH] = "RMD ";
1283 gate_size_t cmd_buffer_used = build_ftp_path(NULL, path, NULL, cmd_buffer, sizeof(cmd_buffer), 4);
1284 gate_result_t result = gate_curl_ftp_execute_command(client, NULL, cmd_buffer, NULL);
1285 GATE_DEBUG_TRACE_FAILED(result, "gate_curl_ftp_execute_command(RMD) failed");
1286 return result;
1287 }
1288
1289 gate_result_t gate_ftpclient_delete_file(gate_ftpclient_t* client, gate_string_t const* path)
1290 {
1291 char cmd_buffer[GATE_MAX_FILEPATH_LENGTH] = "DELE ";
1292 gate_size_t cmd_buffer_used = build_ftp_path(NULL, path, NULL, cmd_buffer, sizeof(cmd_buffer), 5);
1293 gate_result_t result = gate_curl_ftp_execute_command(client, NULL, cmd_buffer, NULL);
1294 GATE_DEBUG_TRACE_FAILED(result, "gate_curl_ftp_execute_command(DELE) failed");
1295 return result;
1296 }
1297
1298 gate_result_t gate_ftpclient_rename(gate_ftpclient_t* client, gate_string_t const* oldpath,
1299 gate_string_t const* newname)
1300 {
1301 char cmd1_buffer[GATE_MAX_FILEPATH_LENGTH] = "RNFR ";
1302 char cmd2_buffer[GATE_MAX_FILEPATH_LENGTH] = "RNTO ";
1303 gate_result_t result;
1304 CURL* const ptr_curl = (CURL*)client->handles[0];
1305 gate_size_t cmd1_buffer_used = build_ftp_path(NULL, oldpath, NULL, cmd1_buffer, sizeof(cmd1_buffer), 5);
1306 gate_size_t cmd2_buffer_used = build_ftp_path(NULL, newname, NULL, cmd2_buffer, sizeof(cmd2_buffer), 5);
1307 struct curl_slist* ptr_cmd_list = NULL;
1308
1309 do
1310 {
1311 ptr_cmd_list = curl.slist_append(NULL, cmd1_buffer);
1312 ptr_cmd_list = curl.slist_append(ptr_cmd_list, cmd2_buffer);
1313
1314 result = gate_curl_ftp_setup_default(client, NULL, NULL, NULL);
1315 GATE_BREAK_IF_FAILED(result);
1316
1317 result = gate_curl_ftp_execute(ptr_curl, NULL, ptr_cmd_list, NULL);
1318
1319 gate_curl_reset(ptr_curl);
1320 } while (0);
1321
1322 if (ptr_cmd_list) curl.slist_free_all(ptr_cmd_list);
1323
1324 return result;
1325 }
1326
1327 1 gate_result_t gate_ftpclient_get_current_directory(gate_ftpclient_t* client, gate_string_t* path)
1328 {
1329 gate_result_t result;
1330 do
1331 {
1332 1 CURL* const ptr_curl = (CURL*)client->handles[0];
1333 CURLcode curlcode;
1334 1 char* entry_path = NULL;
1335
1336 1 result = gate_curl_ftp_execute_command(client, NULL, "PWD", NULL);
1337
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 GATE_BREAK_IF_FAILED(result);
1338
1339 1 curlcode = curl.easy_getinfo(ptr_curl, CURLINFO_FTP_ENTRY_PATH, &entry_path);
1340
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (CURLE_OK != curlcode)
1341 {
1342 char const* errmsg = NULL;
1343 result = gate_curl_map_result(curlcode, &errmsg);
1344 GATE_DEBUG_TRACE_FAILED_RESULT(result);
1345 GATE_DEBUG_TRACE_FAILED(result, errmsg);
1346 break;
1347 }
1348
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (NULL == gate_string_create(path, entry_path, gate_str_length(entry_path)))
1349 {
1350 result = GATE_RESULT_OUTOFMEMORY;
1351 break;
1352 }
1353 /* success case reached */
1354 } while (0);
1355
1356 1 return result;
1357 }
1358
1359 gate_result_t gate_ftpclient_set_current_directory(gate_ftpclient_t* client, gate_string_t const* path)
1360 {
1361 return GATE_RESULT_NOTIMPLEMENTED;
1362 }
1363
1364 1 gate_result_t get_download_stream(gate_ftpclient_t* client, gate_string_t const* path, gate_stream_t** ptr_stream)
1365 {
1366 gate_result_t ret;
1367 1 gate_memstream_t* ptr_memstream = NULL;
1368
1369
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
1 if (!client || gate_string_is_empty(path))
1370 {
1371 return GATE_RESULT_INVALIDARG;
1372 }
1373
1374 do
1375 {
1376 1 CURL* const ptr_curl = (CURL*)client->handles[0];
1377
1378 1 ptr_memstream = gate_memstream_create(1024);
1379
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (NULL == ptr_memstream)
1380 {
1381 ret = GATE_RESULT_OUTOFMEMORY;
1382 break;
1383 }
1384 1 ret = gate_curl_ftp_setup_default(client, path, NULL, (gate_stream_t*)ptr_memstream);
1385
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 GATE_BREAK_IF_FAILED(ret);
1386
1387 1 ret = gate_curl_ftp_perform(ptr_curl);
1388
1389 1 gate_curl_reset(ptr_curl);
1390
1391
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 GATE_BREAK_IF_FAILED(ret);
1392
1393
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (ptr_stream)
1394 {
1395 1 *ptr_stream = (gate_stream_t*)ptr_memstream;
1396 1 ptr_memstream = NULL;
1397 }
1398 1 ret = GATE_RESULT_OK;
1399 } while (0);
1400
1401
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ptr_memstream != NULL)
1402 {
1403 gate_object_release(ptr_memstream);
1404 }
1405
1406 1 return ret;
1407 }
1408
1409 gate_result_t get_upload_stream(gate_ftpclient_t* client, gate_string_t const* path, gate_stream_t** ptr_stream)
1410 {
1411 /* TODO */
1412 return GATE_RESULT_NOTSUPPORTED;
1413 }
1414
1415 1 gate_result_t gate_ftpclient_open_stream(gate_ftpclient_t* client, gate_string_t const* path,
1416 gate_enumint_t stream_open_flags, gate_stream_t** ptr_stream)
1417 {
1418
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
1 if (!client || gate_string_is_empty(path))
1419 {
1420 return GATE_RESULT_INVALIDARG;
1421 }
1422
1423
1/3
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
1 switch (stream_open_flags)
1424 {
1425 1 case GATE_STREAM_OPEN_READ: return get_download_stream(client, path, ptr_stream);
1426 case GATE_STREAM_OPEN_WRITE: return get_upload_stream(client, path, ptr_stream);
1427 default: return GATE_RESULT_NOTSUPPORTED;
1428 }
1429 }
1430
1431 #endif
1432
1433
1434 #if defined(GATE_NET_FTPCLIENT_NO_IMPL)
1435
1436 gate_result_t gate_ftpclient_create(gate_ftpclient_t* client, gate_string_t const* server, gate_uint16_t port,
1437 gate_string_t const* user, gate_string_t const* password,
1438 gate_enumint_t flags)
1439 {
1440 GATE_UNUSED_ARG(client);
1441 GATE_UNUSED_ARG(server);
1442 GATE_UNUSED_ARG(port);
1443 GATE_UNUSED_ARG(user);
1444 GATE_UNUSED_ARG(password);
1445 GATE_UNUSED_ARG(flags);
1446 return GATE_RESULT_NOTIMPLEMENTED;
1447 }
1448
1449 gate_result_t gate_ftpclient_release(gate_ftpclient_t* client)
1450 {
1451 GATE_UNUSED_ARG(client);
1452 return GATE_RESULT_NOTIMPLEMENTED;
1453 }
1454
1455 gate_result_t gate_ftpclient_list_files(gate_ftpclient_t* client, gate_string_t const* path,
1456 gate_file_list_callback_t cb, void* user_param)
1457 {
1458 GATE_UNUSED_ARG(client);
1459 GATE_UNUSED_ARG(path);
1460 GATE_UNUSED_ARG(cb);
1461 GATE_UNUSED_ARG(user_param);
1462 return GATE_RESULT_NOTIMPLEMENTED;
1463 }
1464
1465 gate_result_t gate_ftpclient_create_directory(gate_ftpclient_t* client, gate_string_t const* parent_dir,
1466 gate_string_t const* newname)
1467 {
1468 GATE_UNUSED_ARG(client);
1469 GATE_UNUSED_ARG(parent_dir);
1470 GATE_UNUSED_ARG(newname);
1471 return GATE_RESULT_NOTIMPLEMENTED;
1472 }
1473
1474 gate_result_t gate_ftpclient_delete_directory(gate_ftpclient_t* client, gate_string_t const* path)
1475 {
1476 GATE_UNUSED_ARG(client);
1477 GATE_UNUSED_ARG(path);
1478 return GATE_RESULT_NOTIMPLEMENTED;
1479 }
1480
1481 gate_result_t gate_ftpclient_delete_file(gate_ftpclient_t* client, gate_string_t const* path)
1482 {
1483 GATE_UNUSED_ARG(client);
1484 GATE_UNUSED_ARG(path);
1485 return GATE_RESULT_NOTIMPLEMENTED;
1486 }
1487
1488 gate_result_t gate_ftpclient_rename(gate_ftpclient_t* client, gate_string_t const* oldpath,
1489 gate_string_t const* newname)
1490 {
1491 GATE_UNUSED_ARG(client);
1492 GATE_UNUSED_ARG(oldpath);
1493 GATE_UNUSED_ARG(newname);
1494 return GATE_RESULT_NOTIMPLEMENTED;
1495 }
1496
1497 gate_result_t gate_ftpclient_get_current_directory(gate_ftpclient_t* client, gate_string_t* path)
1498 {
1499 GATE_UNUSED_ARG(client);
1500 GATE_UNUSED_ARG(path);
1501 return GATE_RESULT_NOTIMPLEMENTED;
1502 }
1503
1504 gate_result_t gate_ftpclient_set_current_directory(gate_ftpclient_t* client, gate_string_t const* path)
1505 {
1506 GATE_UNUSED_ARG(client);
1507 GATE_UNUSED_ARG(path);
1508 return GATE_RESULT_NOTIMPLEMENTED;
1509 }
1510
1511 gate_result_t gate_ftpclient_open_stream(gate_ftpclient_t* client, gate_string_t const* path,
1512 gate_enumint_t stream_open_flags, gate_stream_t** ptr_stream)
1513 {
1514 GATE_UNUSED_ARG(client);
1515 GATE_UNUSED_ARG(path);
1516 GATE_UNUSED_ARG(stream_open_flags);
1517 GATE_UNUSED_ARG(ptr_stream);
1518 return GATE_RESULT_NOTIMPLEMENTED;
1519 }
1520
1521 #endif /* GATE_NET_FTPCLIENT_NO_IMPL */
1522