Use pointers internally and refcount all objects.
[wine] / dlls / wininet / ftp.c
1 /*
2  * WININET - Ftp implementation
3  *
4  * Copyright 1999 Corel Corporation
5  * Copyright 2004 Mike McCormack for CodeWeavers
6  * Copyright 2004 Kevin Koltzau
7  *
8  * Ulrich Czekalla
9  * Noureddine Jemmali
10  *
11  * Copyright 2000 Andreas Mohr
12  * Copyright 2002 Jaco Greeff
13  *
14  * This library is free software; you can redistribute it and/or
15  * modify it under the terms of the GNU Lesser General Public
16  * License as published by the Free Software Foundation; either
17  * version 2.1 of the License, or (at your option) any later version.
18  *
19  * This library is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
22  * Lesser General Public License for more details.
23  *
24  * You should have received a copy of the GNU Lesser General Public
25  * License along with this library; if not, write to the Free Software
26  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
27  */
28
29 #include "config.h"
30 #include "wine/port.h"
31
32 #include <errno.h>
33 #include <stdarg.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <sys/types.h>
38 #ifdef HAVE_SYS_SOCKET_H
39 # include <sys/socket.h>
40 #endif
41 #ifdef HAVE_UNISTD_H
42 # include <unistd.h>
43 #endif
44 #include <time.h>
45 #include <assert.h>
46
47 #include "windef.h"
48 #include "winbase.h"
49 #include "wingdi.h"
50 #include "winuser.h"
51 #include "wininet.h"
52 #include "winnls.h"
53 #include "winerror.h"
54 #include "winreg.h"
55 #include "winternl.h"
56 #include "shlwapi.h"
57
58 #include "wine/debug.h"
59 #include "internet.h"
60
61 WINE_DEFAULT_DEBUG_CHANNEL(wininet);
62
63 #define DATA_PACKET_SIZE        0x2000
64 #define szCRLF                  "\r\n"
65 #define MAX_BACKLOG             5
66
67 typedef enum {
68   /* FTP commands with arguments. */
69   FTP_CMD_ACCT,
70   FTP_CMD_CWD,
71   FTP_CMD_DELE,
72   FTP_CMD_MKD,
73   FTP_CMD_PASS,
74   FTP_CMD_PORT,
75   FTP_CMD_RETR,
76   FTP_CMD_RMD,
77   FTP_CMD_RNFR,
78   FTP_CMD_RNTO,
79   FTP_CMD_STOR,
80   FTP_CMD_TYPE,
81   FTP_CMD_USER,
82   FTP_CMD_SIZE,
83
84   /* FTP commands without arguments. */
85   FTP_CMD_ABOR,
86   FTP_CMD_LIST,
87   FTP_CMD_NLST,
88   FTP_CMD_PASV,
89   FTP_CMD_PWD,
90   FTP_CMD_QUIT,
91 } FTP_COMMAND;
92
93 static const CHAR *szFtpCommands[] = {
94   "ACCT",
95   "CWD",
96   "DELE",
97   "MKD",
98   "PASS",
99   "PORT",
100   "RETR",
101   "RMD",
102   "RNFR",
103   "RNTO",
104   "STOR",
105   "TYPE",
106   "USER",
107   "SIZE",
108   "ABOR",
109   "LIST",
110   "NLST",
111   "PASV",
112   "PWD",
113   "QUIT",
114 };
115
116 static const CHAR szMonths[] = "JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC";
117 static const WCHAR szNoAccount[] = {'n','o','a','c','c','o','u','n','t','\0'};
118
119 static void FTP_CloseFileTransferHandle(LPWININETHANDLEHEADER hdr);
120 static void FTP_CloseSessionHandle(LPWININETHANDLEHEADER hdr);
121 static void FTP_CloseFindNextHandle(LPWININETHANDLEHEADER hdr);
122 BOOL FTP_SendCommand(INT nSocket, FTP_COMMAND ftpCmd, LPCWSTR lpszParam,
123         INTERNET_STATUS_CALLBACK lpfnStatusCB, LPWININETHANDLEHEADER hdr, DWORD dwContext);
124 BOOL FTP_SendStore(LPWININETFTPSESSIONW lpwfs, LPCWSTR lpszRemoteFile, DWORD dwType);
125 BOOL FTP_GetDataSocket(LPWININETFTPSESSIONW lpwfs, LPINT nDataSocket);
126 BOOL FTP_SendData(LPWININETFTPSESSIONW lpwfs, INT nDataSocket, HANDLE hFile);
127 INT FTP_ReceiveResponse(LPWININETFTPSESSIONW lpwfs, DWORD dwContext);
128 DWORD FTP_SendRetrieve(LPWININETFTPSESSIONW lpwfs, LPCWSTR lpszRemoteFile, DWORD dwType);
129 BOOL FTP_RetrieveFileData(LPWININETFTPSESSIONW lpwfs, INT nDataSocket, DWORD nBytes, HANDLE hFile);
130 BOOL FTP_InitListenSocket(LPWININETFTPSESSIONW lpwfs);
131 BOOL FTP_ConnectToHost(LPWININETFTPSESSIONW lpwfs);
132 BOOL FTP_SendPassword(LPWININETFTPSESSIONW lpwfs);
133 BOOL FTP_SendAccount(LPWININETFTPSESSIONW lpwfs);
134 BOOL FTP_SendType(LPWININETFTPSESSIONW lpwfs, DWORD dwType);
135 BOOL FTP_GetFileSize(LPWININETFTPSESSIONW lpwfs, LPCWSTR lpszRemoteFile, DWORD *dwSize);
136 BOOL FTP_SendPort(LPWININETFTPSESSIONW lpwfs);
137 BOOL FTP_DoPassive(LPWININETFTPSESSIONW lpwfs);
138 BOOL FTP_SendPortOrPasv(LPWININETFTPSESSIONW lpwfs);
139 BOOL FTP_ParsePermission(LPCSTR lpszPermission, LPFILEPROPERTIESW lpfp);
140 BOOL FTP_ParseNextFile(INT nSocket, LPCWSTR lpszSearchFile, LPFILEPROPERTIESW fileprop);
141 BOOL FTP_ParseDirectory(LPWININETFTPSESSIONW lpwfs, INT nSocket, LPCWSTR lpszSearchFile,
142         LPFILEPROPERTIESW *lpafp, LPDWORD dwfp);
143 HINTERNET FTP_ReceiveFileList(LPWININETFTPSESSIONW lpwfs, INT nSocket, LPCWSTR lpszSearchFile,
144         LPWIN32_FIND_DATAW lpFindFileData, DWORD dwContext);
145 DWORD FTP_SetResponseError(DWORD dwResponse);
146
147 /***********************************************************************
148  *           FtpPutFileA (WININET.@)
149  *
150  * Uploads a file to the FTP server
151  *
152  * RETURNS
153  *    TRUE on success
154  *    FALSE on failure
155  *
156  */
157 BOOL WINAPI FtpPutFileA(HINTERNET hConnect, LPCSTR lpszLocalFile,
158     LPCSTR lpszNewRemoteFile, DWORD dwFlags, DWORD dwContext)
159 {
160     LPWSTR lpwzLocalFile;
161     LPWSTR lpwzNewRemoteFile;
162     BOOL ret;
163     
164     lpwzLocalFile = lpszLocalFile?WININET_strdup_AtoW(lpszLocalFile):NULL;
165     lpwzNewRemoteFile = lpszNewRemoteFile?WININET_strdup_AtoW(lpszNewRemoteFile):NULL;
166     ret = FtpPutFileW(hConnect, lpwzLocalFile, lpwzNewRemoteFile,
167                       dwFlags, dwContext);
168     if(lpwzLocalFile) HeapFree(GetProcessHeap(), 0, lpwzLocalFile);
169     if(lpwzNewRemoteFile) HeapFree(GetProcessHeap(), 0, lpwzNewRemoteFile);
170     return ret;
171 }
172
173 /***********************************************************************
174  *           FtpPutFileW (WININET.@)
175  *
176  * Uploads a file to the FTP server
177  *
178  * RETURNS
179  *    TRUE on success
180  *    FALSE on failure
181  *
182  */
183 BOOL WINAPI FtpPutFileW(HINTERNET hConnect, LPCWSTR lpszLocalFile,
184     LPCWSTR lpszNewRemoteFile, DWORD dwFlags, DWORD dwContext)
185 {
186     LPWININETFTPSESSIONW lpwfs;
187     LPWININETAPPINFOW hIC = NULL;
188     BOOL r = FALSE;
189
190     lpwfs = (LPWININETFTPSESSIONW) WININET_GetObject( hConnect );
191     if (NULL == lpwfs || WH_HFTPSESSION != lpwfs->hdr.htype)
192     {
193         INTERNET_SetLastError(ERROR_INTERNET_INCORRECT_HANDLE_TYPE);
194         goto lend;
195     }
196
197     hIC = (LPWININETAPPINFOW) lpwfs->hdr.lpwhparent;
198     if (hIC->hdr.dwFlags & INTERNET_FLAG_ASYNC)
199     {
200         WORKREQUEST workRequest;
201         struct WORKREQ_FTPPUTFILEW *req = &workRequest.u.FtpPutFileW;
202
203         workRequest.asyncall = FTPPUTFILEW;
204         workRequest.hdr = WININET_AddRef( &lpwfs->hdr );
205         req->lpszLocalFile = WININET_strdupW(lpszLocalFile);
206         req->lpszNewRemoteFile = WININET_strdupW(lpszNewRemoteFile);
207         req->dwFlags = dwFlags;
208         req->dwContext = dwContext;
209
210         r = INTERNET_AsyncCall(&workRequest);
211     }
212     else
213     {
214         r = FTP_FtpPutFileW(lpwfs, lpszLocalFile,
215             lpszNewRemoteFile, dwFlags, dwContext);
216     }
217
218 lend:
219     if( lpwfs )
220         WININET_Release( &lpwfs->hdr );
221
222     return r;
223 }
224
225 /***********************************************************************
226  *           FTP_FtpPutFileW (Internal)
227  *
228  * Uploads a file to the FTP server
229  *
230  * RETURNS
231  *    TRUE on success
232  *    FALSE on failure
233  *
234  */
235 BOOL WINAPI FTP_FtpPutFileW(LPWININETFTPSESSIONW lpwfs, LPCWSTR lpszLocalFile,
236     LPCWSTR lpszNewRemoteFile, DWORD dwFlags, DWORD dwContext)
237 {
238     HANDLE hFile = NULL;
239     BOOL bSuccess = FALSE;
240     LPWININETAPPINFOW hIC = NULL;
241     INT nResCode;
242
243     TRACE(" lpszLocalFile(%s) lpszNewRemoteFile(%s)\n", debugstr_w(lpszLocalFile), debugstr_w(lpszNewRemoteFile));
244
245     if (!lpszLocalFile || !lpszNewRemoteFile)
246     {
247         INTERNET_SetLastError(ERROR_INVALID_PARAMETER);
248         return FALSE;
249     }
250
251     assert( WH_HFTPSESSION == lpwfs->hdr.htype);
252
253     /* Clear any error information */
254     INTERNET_SetLastError(0);
255
256     /* Open file to be uploaded */
257     if (INVALID_HANDLE_VALUE ==
258         (hFile = CreateFileW(lpszLocalFile, GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0)))
259     {
260         INTERNET_SetLastError(ERROR_FILE_NOT_FOUND);
261         goto lend;
262     }
263
264     hIC = (LPWININETAPPINFOW) lpwfs->hdr.lpwhparent;
265     SendAsyncCallback(hIC, &lpwfs->hdr, lpwfs->hdr.dwContext, INTERNET_STATUS_SENDING_REQUEST, NULL, 0);
266
267     if (FTP_SendStore(lpwfs, lpszNewRemoteFile, dwFlags))
268     {
269         INT nDataSocket;
270
271         /* Get data socket to server */
272         if (FTP_GetDataSocket(lpwfs, &nDataSocket))
273         {
274             FTP_SendData(lpwfs, nDataSocket, hFile);
275             close(nDataSocket);
276             nResCode = FTP_ReceiveResponse(lpwfs, dwContext);
277             if (nResCode)
278             {
279                 if (nResCode == 226)
280                     bSuccess = TRUE;
281                 else
282                     FTP_SetResponseError(nResCode);
283             }
284         }
285     }
286
287 lend:
288     if (lpwfs->lstnSocket != -1)
289         close(lpwfs->lstnSocket);
290
291     if (hIC->hdr.dwFlags & INTERNET_FLAG_ASYNC  && hIC->lpfnStatusCB)
292     {
293         INTERNET_ASYNC_RESULT iar;
294
295         iar.dwResult = (DWORD)bSuccess;
296         iar.dwError = bSuccess ? ERROR_SUCCESS : INTERNET_GetLastError();
297         SendAsyncCallback(hIC, &lpwfs->hdr, lpwfs->hdr.dwContext, INTERNET_STATUS_REQUEST_COMPLETE,
298             &iar, sizeof(INTERNET_ASYNC_RESULT));
299     }
300
301     if (hFile)
302         CloseHandle(hFile);
303
304     return bSuccess;
305 }
306
307
308 /***********************************************************************
309  *           FtpSetCurrentDirectoryA (WININET.@)
310  *
311  * Change the working directory on the FTP server
312  *
313  * RETURNS
314  *    TRUE on success
315  *    FALSE on failure
316  *
317  */
318 BOOL WINAPI FtpSetCurrentDirectoryA(HINTERNET hConnect, LPCSTR lpszDirectory)
319 {
320     LPWSTR lpwzDirectory;
321     BOOL ret;
322     
323     lpwzDirectory = lpszDirectory?WININET_strdup_AtoW(lpszDirectory):NULL;
324     ret = FtpSetCurrentDirectoryW(hConnect, lpwzDirectory);
325     if(lpwzDirectory) HeapFree(GetProcessHeap(), 0, lpwzDirectory);
326     return ret;
327 }
328
329
330 /***********************************************************************
331  *           FtpSetCurrentDirectoryW (WININET.@)
332  *
333  * Change the working directory on the FTP server
334  *
335  * RETURNS
336  *    TRUE on success
337  *    FALSE on failure
338  *
339  */
340 BOOL WINAPI FtpSetCurrentDirectoryW(HINTERNET hConnect, LPCWSTR lpszDirectory)
341 {
342     LPWININETFTPSESSIONW lpwfs;
343     LPWININETAPPINFOW hIC = NULL;
344     BOOL r = FALSE;
345
346     lpwfs = (LPWININETFTPSESSIONW) WININET_GetObject( hConnect );
347     if (NULL == lpwfs || WH_HFTPSESSION != lpwfs->hdr.htype)
348     {
349         INTERNET_SetLastError(ERROR_INTERNET_INCORRECT_HANDLE_TYPE);
350         goto lend;
351     }
352
353     TRACE("lpszDirectory(%s)\n", debugstr_w(lpszDirectory));
354
355     hIC = (LPWININETAPPINFOW) lpwfs->hdr.lpwhparent;
356     if (hIC->hdr.dwFlags & INTERNET_FLAG_ASYNC)
357     {
358         WORKREQUEST workRequest;
359         struct WORKREQ_FTPSETCURRENTDIRECTORYW *req;
360
361         workRequest.asyncall = FTPSETCURRENTDIRECTORYW;
362         workRequest.hdr = WININET_AddRef( &lpwfs->hdr );
363         req = &workRequest.u.FtpSetCurrentDirectoryW;
364         req->lpszDirectory = WININET_strdupW(lpszDirectory);
365
366         r = INTERNET_AsyncCall(&workRequest);
367     }
368     else
369     {
370         r = FTP_FtpSetCurrentDirectoryW(lpwfs, lpszDirectory);
371     }
372
373 lend:
374     if( lpwfs )
375         WININET_Release( &lpwfs->hdr );
376
377     return r;
378 }
379
380
381 /***********************************************************************
382  *           FTP_FtpSetCurrentDirectoryW (Internal)
383  *
384  * Change the working directory on the FTP server
385  *
386  * RETURNS
387  *    TRUE on success
388  *    FALSE on failure
389  *
390  */
391 BOOL WINAPI FTP_FtpSetCurrentDirectoryW(LPWININETFTPSESSIONW lpwfs, LPCWSTR lpszDirectory)
392 {
393     INT nResCode;
394     LPWININETAPPINFOW hIC = NULL;
395     DWORD bSuccess = FALSE;
396
397     TRACE("lpszDirectory(%s)\n", debugstr_w(lpszDirectory));
398
399     if (NULL == lpwfs || WH_HFTPSESSION != lpwfs->hdr.htype)
400     {
401         INTERNET_SetLastError(ERROR_INTERNET_INCORRECT_HANDLE_TYPE);
402         return FALSE;
403     }
404
405     /* Clear any error information */
406     INTERNET_SetLastError(0);
407
408     hIC = (LPWININETAPPINFOW) lpwfs->hdr.lpwhparent;
409     if (!FTP_SendCommand(lpwfs->sndSocket, FTP_CMD_CWD, lpszDirectory,
410         hIC->lpfnStatusCB, &lpwfs->hdr, lpwfs->hdr.dwContext))
411         goto lend;
412
413     nResCode = FTP_ReceiveResponse(lpwfs, lpwfs->hdr.dwContext);
414
415     if (nResCode)
416     {
417         if (nResCode == 250)
418             bSuccess = TRUE;
419         else
420             FTP_SetResponseError(nResCode);
421     }
422
423 lend:
424     if (hIC->hdr.dwFlags & INTERNET_FLAG_ASYNC && hIC->lpfnStatusCB)
425     {
426         INTERNET_ASYNC_RESULT iar;
427
428         iar.dwResult = (DWORD)bSuccess;
429         iar.dwError = bSuccess ? ERROR_SUCCESS : ERROR_INTERNET_EXTENDED_ERROR;
430         SendAsyncCallback(hIC, &lpwfs->hdr, lpwfs->hdr.dwContext, INTERNET_STATUS_REQUEST_COMPLETE,
431             &iar, sizeof(INTERNET_ASYNC_RESULT));
432     }
433     return bSuccess;
434 }
435
436
437 /***********************************************************************
438  *           FtpCreateDirectoryA (WININET.@)
439  *
440  * Create new directory on the FTP server
441  *
442  * RETURNS
443  *    TRUE on success
444  *    FALSE on failure
445  *
446  */
447 BOOL WINAPI FtpCreateDirectoryA(HINTERNET hConnect, LPCSTR lpszDirectory)
448 {
449     LPWSTR lpwzDirectory;
450     BOOL ret;
451     
452     lpwzDirectory = lpszDirectory?WININET_strdup_AtoW(lpszDirectory):NULL;
453     ret = FtpCreateDirectoryW(hConnect, lpwzDirectory);
454     if(lpwzDirectory) HeapFree(GetProcessHeap(), 0, lpwzDirectory);
455     return ret;
456 }
457
458
459 /***********************************************************************
460  *           FtpCreateDirectoryW (WININET.@)
461  *
462  * Create new directory on the FTP server
463  *
464  * RETURNS
465  *    TRUE on success
466  *    FALSE on failure
467  *
468  */
469 BOOL WINAPI FtpCreateDirectoryW(HINTERNET hConnect, LPCWSTR lpszDirectory)
470 {
471     LPWININETFTPSESSIONW lpwfs;
472     LPWININETAPPINFOW hIC = NULL;
473     BOOL r = FALSE;
474
475     lpwfs = (LPWININETFTPSESSIONW) WININET_GetObject( hConnect );
476     if (NULL == lpwfs || WH_HFTPSESSION != lpwfs->hdr.htype)
477     {
478         INTERNET_SetLastError(ERROR_INTERNET_INCORRECT_HANDLE_TYPE);
479         goto lend;
480     }
481
482     hIC = (LPWININETAPPINFOW) lpwfs->hdr.lpwhparent;
483     if (hIC->hdr.dwFlags & INTERNET_FLAG_ASYNC)
484     {
485         WORKREQUEST workRequest;
486         struct WORKREQ_FTPCREATEDIRECTORYW *req;
487
488         workRequest.asyncall = FTPCREATEDIRECTORYW;
489         workRequest.hdr = WININET_AddRef( &lpwfs->hdr );
490         req = &workRequest.u.FtpCreateDirectoryW;
491         req->lpszDirectory = WININET_strdupW(lpszDirectory);
492
493         r = INTERNET_AsyncCall(&workRequest);
494     }
495     else
496     {
497         r = FTP_FtpCreateDirectoryW(lpwfs, lpszDirectory);
498     }
499 lend:
500     if( lpwfs )
501         WININET_Release( &lpwfs->hdr );
502
503     return r;
504 }
505
506
507 /***********************************************************************
508  *           FTP_FtpCreateDirectoryW (Internal)
509  *
510  * Create new directory on the FTP server
511  *
512  * RETURNS
513  *    TRUE on success
514  *    FALSE on failure
515  *
516  */
517 BOOL WINAPI FTP_FtpCreateDirectoryW(LPWININETFTPSESSIONW lpwfs, LPCWSTR lpszDirectory)
518 {
519     INT nResCode;
520     BOOL bSuccess = FALSE;
521     LPWININETAPPINFOW hIC = NULL;
522
523     TRACE("lpszDirectory(%s)\n", debugstr_w(lpszDirectory));
524
525     if (NULL == lpwfs || WH_HFTPSESSION != lpwfs->hdr.htype)
526     {
527         INTERNET_SetLastError(ERROR_INTERNET_INCORRECT_HANDLE_TYPE);
528         return FALSE;
529     }
530
531     /* Clear any error information */
532     INTERNET_SetLastError(0);
533
534     if (!FTP_SendCommand(lpwfs->sndSocket, FTP_CMD_MKD, lpszDirectory, 0, 0, 0))
535         goto lend;
536
537     nResCode = FTP_ReceiveResponse(lpwfs, lpwfs->hdr.dwContext);
538     if (nResCode)
539     {
540         if (nResCode == 257)
541             bSuccess = TRUE;
542         else
543             FTP_SetResponseError(nResCode);
544     }
545
546 lend:
547     hIC = (LPWININETAPPINFOW) lpwfs->hdr.lpwhparent;
548     if (hIC->hdr.dwFlags & INTERNET_FLAG_ASYNC && hIC->lpfnStatusCB)
549     {
550         INTERNET_ASYNC_RESULT iar;
551
552         iar.dwResult = (DWORD)bSuccess;
553         iar.dwError = bSuccess ? ERROR_SUCCESS : INTERNET_GetLastError();
554         SendAsyncCallback(hIC, &lpwfs->hdr, lpwfs->hdr.dwContext, INTERNET_STATUS_REQUEST_COMPLETE,
555             &iar, sizeof(INTERNET_ASYNC_RESULT));
556     }
557
558     return bSuccess;
559 }
560
561 /***********************************************************************
562  *           FtpFindFirstFileA (WININET.@)
563  *
564  * Search the specified directory
565  *
566  * RETURNS
567  *    HINTERNET on success
568  *    NULL on failure
569  *
570  */
571 HINTERNET WINAPI FtpFindFirstFileA(HINTERNET hConnect,
572     LPCSTR lpszSearchFile, LPWIN32_FIND_DATAA lpFindFileData, DWORD dwFlags, DWORD dwContext)
573 {
574     LPWSTR lpwzSearchFile;
575     WIN32_FIND_DATAW wfd;
576     LPWIN32_FIND_DATAW lpFindFileDataW;
577     HINTERNET ret;
578     
579     lpwzSearchFile = lpszSearchFile?WININET_strdup_AtoW(lpszSearchFile):NULL;
580     lpFindFileDataW = lpFindFileData?&wfd:NULL;
581     ret = FtpFindFirstFileW(hConnect, lpwzSearchFile, lpFindFileDataW, dwFlags, dwContext);
582     if(lpwzSearchFile) HeapFree(GetProcessHeap(), 0, lpwzSearchFile);
583     
584     if(lpFindFileData) {
585         WININET_find_data_WtoA(lpFindFileDataW, lpFindFileData);
586     }
587     return ret;
588 }
589
590
591 /***********************************************************************
592  *           FtpFindFirstFileW (WININET.@)
593  *
594  * Search the specified directory
595  *
596  * RETURNS
597  *    HINTERNET on success
598  *    NULL on failure
599  *
600  */
601 HINTERNET WINAPI FtpFindFirstFileW(HINTERNET hConnect,
602     LPCWSTR lpszSearchFile, LPWIN32_FIND_DATAW lpFindFileData, DWORD dwFlags, DWORD dwContext)
603 {
604     LPWININETFTPSESSIONW lpwfs;
605     LPWININETAPPINFOW hIC = NULL;
606     HINTERNET r = NULL;
607
608     lpwfs = (LPWININETFTPSESSIONW) WININET_GetObject( hConnect );
609     if (NULL == lpwfs || WH_HFTPSESSION != lpwfs->hdr.htype)
610     {
611         INTERNET_SetLastError(ERROR_INTERNET_INCORRECT_HANDLE_TYPE);
612         goto lend;
613     }
614
615     hIC = (LPWININETAPPINFOW) lpwfs->hdr.lpwhparent;
616     if (hIC->hdr.dwFlags & INTERNET_FLAG_ASYNC)
617     {
618         WORKREQUEST workRequest;
619         struct WORKREQ_FTPFINDFIRSTFILEW *req;
620
621         workRequest.asyncall = FTPFINDFIRSTFILEW;
622         workRequest.hdr = WININET_AddRef( &lpwfs->hdr );
623         req = &workRequest.u.FtpFindFirstFileW;
624         req->lpszSearchFile = (lpszSearchFile == NULL) ? NULL : WININET_strdupW(lpszSearchFile);
625         req->lpFindFileData = lpFindFileData;
626         req->dwFlags = dwFlags;
627         req->dwContext= dwContext;
628
629         INTERNET_AsyncCall(&workRequest);
630         r = NULL;
631     }
632     else
633     {
634         r = FTP_FtpFindFirstFileW(lpwfs, lpszSearchFile, lpFindFileData,
635             dwFlags, dwContext);
636     }
637 lend:
638     if( lpwfs )
639         WININET_Release( &lpwfs->hdr );
640
641     return r;
642 }
643
644
645 /***********************************************************************
646  *           FTP_FtpFindFirstFileW (Internal)
647  *
648  * Search the specified directory
649  *
650  * RETURNS
651  *    HINTERNET on success
652  *    NULL on failure
653  *
654  */
655 HINTERNET WINAPI FTP_FtpFindFirstFileW(LPWININETFTPSESSIONW lpwfs,
656     LPCWSTR lpszSearchFile, LPWIN32_FIND_DATAW lpFindFileData, DWORD dwFlags, DWORD dwContext)
657 {
658     INT nResCode;
659     LPWININETAPPINFOW hIC = NULL;
660     HINTERNET hFindNext = NULL;
661
662     TRACE("\n");
663
664     assert(WH_HFTPSESSION == lpwfs->hdr.htype);
665
666     /* Clear any error information */
667     INTERNET_SetLastError(0);
668
669     if (!FTP_InitListenSocket(lpwfs))
670         goto lend;
671
672     if (!FTP_SendType(lpwfs, INTERNET_FLAG_TRANSFER_ASCII))
673         goto lend;
674
675     if (!FTP_SendPortOrPasv(lpwfs))
676         goto lend;
677
678     hIC = (LPWININETAPPINFOW) lpwfs->hdr.lpwhparent;
679     if (!FTP_SendCommand(lpwfs->sndSocket, FTP_CMD_LIST, NULL,
680         hIC->lpfnStatusCB, &lpwfs->hdr, lpwfs->hdr.dwContext))
681         goto lend;
682
683     nResCode = FTP_ReceiveResponse(lpwfs, lpwfs->hdr.dwContext);
684     if (nResCode)
685     {
686         if (nResCode == 125 || nResCode == 150)
687         {
688             INT nDataSocket;
689
690             /* Get data socket to server */
691             if (FTP_GetDataSocket(lpwfs, &nDataSocket))
692             {
693                 hFindNext = FTP_ReceiveFileList(lpwfs, nDataSocket, lpszSearchFile, lpFindFileData, dwContext);
694                 close(nDataSocket);
695                 nResCode = FTP_ReceiveResponse(lpwfs, lpwfs->hdr.dwContext);
696                 if (nResCode != 226 && nResCode != 250)
697                     INTERNET_SetLastError(ERROR_NO_MORE_FILES);
698             }
699         }
700         else
701             FTP_SetResponseError(nResCode);
702     }
703
704 lend:
705     if (lpwfs->lstnSocket != -1)
706         close(lpwfs->lstnSocket);
707
708     if (hIC->hdr.dwFlags & INTERNET_FLAG_ASYNC && hIC->lpfnStatusCB)
709     {
710         INTERNET_ASYNC_RESULT iar;
711
712         if (hFindNext)
713         {
714             iar.dwResult = (DWORD)hFindNext;
715             iar.dwError = ERROR_SUCCESS;
716             SendAsyncCallback(hIC, &lpwfs->hdr, lpwfs->hdr.dwContext, INTERNET_STATUS_HANDLE_CREATED,
717                 &iar, sizeof(INTERNET_ASYNC_RESULT));
718         }
719
720         iar.dwResult = (DWORD)hFindNext;
721         iar.dwError = hFindNext ? ERROR_SUCCESS : INTERNET_GetLastError();
722         SendAsyncCallback(hIC, &lpwfs->hdr, lpwfs->hdr.dwContext, INTERNET_STATUS_REQUEST_COMPLETE,
723             &iar, sizeof(INTERNET_ASYNC_RESULT));
724     }
725
726     return hFindNext;
727 }
728
729
730 /***********************************************************************
731  *           FtpGetCurrentDirectoryA (WININET.@)
732  *
733  * Retrieves the current directory
734  *
735  * RETURNS
736  *    TRUE on success
737  *    FALSE on failure
738  *
739  */
740 BOOL WINAPI FtpGetCurrentDirectoryA(HINTERNET hFtpSession, LPSTR lpszCurrentDirectory,
741     LPDWORD lpdwCurrentDirectory)
742 {
743     WCHAR dir[MAX_PATH];
744     DWORD len;
745     BOOL ret;
746
747     if(lpdwCurrentDirectory) len = *lpdwCurrentDirectory;
748     ret = FtpGetCurrentDirectoryW(hFtpSession, lpszCurrentDirectory?dir:NULL, lpdwCurrentDirectory?&len:NULL);
749     if(lpdwCurrentDirectory) {
750         *lpdwCurrentDirectory = len;
751         if(lpszCurrentDirectory)
752             WideCharToMultiByte(CP_ACP, 0, dir, len, lpszCurrentDirectory, *lpdwCurrentDirectory, NULL, NULL);
753     }
754     return ret;
755 }
756
757
758 /***********************************************************************
759  *           FtpGetCurrentDirectoryW (WININET.@)
760  *
761  * Retrieves the current directory
762  *
763  * RETURNS
764  *    TRUE on success
765  *    FALSE on failure
766  *
767  */
768 BOOL WINAPI FtpGetCurrentDirectoryW(HINTERNET hFtpSession, LPWSTR lpszCurrentDirectory,
769     LPDWORD lpdwCurrentDirectory)
770 {
771     LPWININETFTPSESSIONW lpwfs;
772     LPWININETAPPINFOW hIC = NULL;
773     BOOL r = FALSE;
774
775     TRACE("len(%ld)\n", *lpdwCurrentDirectory);
776
777     lpwfs = (LPWININETFTPSESSIONW) WININET_GetObject( hFtpSession );
778     if (NULL == lpwfs || WH_HFTPSESSION != lpwfs->hdr.htype)
779     {
780         INTERNET_SetLastError(ERROR_INTERNET_INCORRECT_HANDLE_TYPE);
781         goto lend;
782     }
783
784     hIC = (LPWININETAPPINFOW) lpwfs->hdr.lpwhparent;
785     if (hIC->hdr.dwFlags & INTERNET_FLAG_ASYNC)
786     {
787         WORKREQUEST workRequest;
788         struct WORKREQ_FTPGETCURRENTDIRECTORYW *req;
789
790         workRequest.asyncall =  FTPGETCURRENTDIRECTORYW;
791         workRequest.hdr = WININET_AddRef( &lpwfs->hdr );
792         req = &workRequest.u.FtpGetCurrentDirectoryW;
793         req->lpszDirectory = lpszCurrentDirectory;
794         req->lpdwDirectory = lpdwCurrentDirectory;
795
796         r = INTERNET_AsyncCall(&workRequest);
797     }
798     else
799     {
800         r = FTP_FtpGetCurrentDirectoryW(lpwfs, lpszCurrentDirectory,
801             lpdwCurrentDirectory);
802     }
803
804 lend:
805     if( lpwfs )
806         WININET_Release( &lpwfs->hdr );
807
808     return r;
809 }
810
811
812 /***********************************************************************
813  *           FTP_FtpGetCurrentDirectoryA (Internal)
814  *
815  * Retrieves the current directory
816  *
817  * RETURNS
818  *    TRUE on success
819  *    FALSE on failure
820  *
821  */
822 BOOL WINAPI FTP_FtpGetCurrentDirectoryW(LPWININETFTPSESSIONW lpwfs, LPWSTR lpszCurrentDirectory,
823         LPDWORD lpdwCurrentDirectory)
824 {
825     INT nResCode;
826     LPWININETAPPINFOW hIC = NULL;
827     DWORD bSuccess = FALSE;
828
829     TRACE("len(%ld)\n", *lpdwCurrentDirectory);
830
831     if (NULL == lpwfs || WH_HFTPSESSION != lpwfs->hdr.htype)
832     {
833         INTERNET_SetLastError(ERROR_INTERNET_INCORRECT_HANDLE_TYPE);
834         return FALSE;
835     }
836
837     /* Clear any error information */
838     INTERNET_SetLastError(0);
839
840     ZeroMemory(lpszCurrentDirectory, *lpdwCurrentDirectory);
841
842     hIC = (LPWININETAPPINFOW) lpwfs->hdr.lpwhparent;
843     if (!FTP_SendCommand(lpwfs->sndSocket, FTP_CMD_PWD, NULL,
844         hIC->lpfnStatusCB, &lpwfs->hdr, lpwfs->hdr.dwContext))
845         goto lend;
846
847     nResCode = FTP_ReceiveResponse(lpwfs, lpwfs->hdr.dwContext);
848     if (nResCode)
849     {
850         if (nResCode == 257) /* Extract directory name */
851         {
852             INT firstpos, lastpos, len;
853             LPWSTR lpszResponseBuffer = WININET_strdup_AtoW(INTERNET_GetResponseBuffer());
854
855             for (firstpos = 0, lastpos = 0; lpszResponseBuffer[lastpos]; lastpos++)
856             {
857                 if ('"' == lpszResponseBuffer[lastpos])
858                 {
859                     if (!firstpos)
860                         firstpos = lastpos;
861                     else
862                         break;
863                 }
864             }
865
866             len = lastpos - firstpos - 1;
867             strncpyW(lpszCurrentDirectory, &lpszResponseBuffer[firstpos+1],
868                 len < *lpdwCurrentDirectory ? len : *lpdwCurrentDirectory);
869             HeapFree(GetProcessHeap(), 0, lpszResponseBuffer);
870             *lpdwCurrentDirectory = len;
871             bSuccess = TRUE;
872         }
873         else
874             FTP_SetResponseError(nResCode);
875     }
876
877 lend:
878     if (hIC->hdr.dwFlags & INTERNET_FLAG_ASYNC && hIC->lpfnStatusCB)
879     {
880         INTERNET_ASYNC_RESULT iar;
881
882         iar.dwResult = (DWORD)bSuccess;
883         iar.dwError = bSuccess ? ERROR_SUCCESS : ERROR_INTERNET_EXTENDED_ERROR;
884         SendAsyncCallback(hIC, &lpwfs->hdr, lpwfs->hdr.dwContext, INTERNET_STATUS_REQUEST_COMPLETE,
885             &iar, sizeof(INTERNET_ASYNC_RESULT));
886     }
887
888     return (DWORD) bSuccess;
889 }
890
891 /***********************************************************************
892  *           FtpOpenFileA (WININET.@)
893  *
894  * Open a remote file for writing or reading
895  *
896  * RETURNS
897  *    HINTERNET handle on success
898  *    NULL on failure
899  *
900  */
901 HINTERNET WINAPI FtpOpenFileA(HINTERNET hFtpSession,
902     LPCSTR lpszFileName, DWORD fdwAccess, DWORD dwFlags,
903     DWORD dwContext)
904 {
905     LPWSTR lpwzFileName;
906     HINTERNET ret;
907     
908     lpwzFileName = lpszFileName?WININET_strdup_AtoW(lpszFileName):NULL;
909     ret = FtpOpenFileW(hFtpSession, lpwzFileName, fdwAccess, dwFlags, dwContext);
910     if(lpwzFileName) HeapFree(GetProcessHeap(), 0, lpwzFileName);
911     return ret;
912 }
913
914
915 /***********************************************************************
916  *           FtpOpenFileW (WININET.@)
917  *
918  * Open a remote file for writing or reading
919  *
920  * RETURNS
921  *    HINTERNET handle on success
922  *    NULL on failure
923  *
924  */
925 HINTERNET WINAPI FtpOpenFileW(HINTERNET hFtpSession,
926     LPCWSTR lpszFileName, DWORD fdwAccess, DWORD dwFlags,
927     DWORD dwContext)
928 {
929     LPWININETFTPSESSIONW lpwfs;
930     LPWININETAPPINFOW hIC = NULL;
931     HINTERNET r = NULL;
932     
933     TRACE("(%p,%s,0x%08lx,0x%08lx,0x%08lx)\n", hFtpSession,
934         debugstr_w(lpszFileName), fdwAccess, dwFlags, dwContext);
935
936     lpwfs = (LPWININETFTPSESSIONW) WININET_GetObject( hFtpSession );
937     if (NULL == lpwfs || WH_HFTPSESSION != lpwfs->hdr.htype)
938     {
939         INTERNET_SetLastError(ERROR_INTERNET_INCORRECT_HANDLE_TYPE);
940         goto lend;
941     }
942
943     if (lpwfs->download_in_progress != NULL) {
944         INTERNET_SetLastError(ERROR_FTP_TRANSFER_IN_PROGRESS);
945         goto lend;
946     }
947     hIC = (LPWININETAPPINFOW) lpwfs->hdr.lpwhparent;
948     if (hIC->hdr.dwFlags & INTERNET_FLAG_ASYNC)
949     {
950         WORKREQUEST workRequest;
951         struct WORKREQ_FTPOPENFILEW *req;
952
953         workRequest.asyncall = FTPOPENFILEW;
954         workRequest.hdr = WININET_AddRef( &lpwfs->hdr );
955         req = &workRequest.u.FtpOpenFileW;
956         req->lpszFilename = WININET_strdupW(lpszFileName);
957         req->dwAccess = fdwAccess;
958         req->dwFlags = dwFlags;
959         req->dwContext = dwContext;
960
961         INTERNET_AsyncCall(&workRequest);
962         r = NULL;
963     }
964     else
965     {
966         r = FTP_FtpOpenFileW(lpwfs, lpszFileName, fdwAccess, dwFlags, dwContext);
967     }
968
969 lend:
970     if( lpwfs )
971         WININET_Release( &lpwfs->hdr );
972
973     return r;
974 }
975
976
977 /***********************************************************************
978  *           FTP_FtpOpenFileW (Internal)
979  *
980  * Open a remote file for writing or reading
981  *
982  * RETURNS
983  *    HINTERNET handle on success
984  *    NULL on failure
985  *
986  */
987 HINTERNET FTP_FtpOpenFileW(LPWININETFTPSESSIONW lpwfs,
988         LPCWSTR lpszFileName, DWORD fdwAccess, DWORD dwFlags,
989         DWORD dwContext)
990 {
991     INT nDataSocket;
992     BOOL bSuccess = FALSE;
993     LPWININETFILE lpwh = NULL;
994     LPWININETAPPINFOW hIC = NULL;
995     HINTERNET handle = NULL;
996
997     TRACE("\n");
998
999     assert (WH_HFTPSESSION == lpwfs->hdr.htype);
1000
1001     /* Clear any error information */
1002     INTERNET_SetLastError(0);
1003
1004     if (GENERIC_READ == fdwAccess)
1005     {
1006         /* Set up socket to retrieve data */
1007         bSuccess = FTP_SendRetrieve(lpwfs, lpszFileName, dwFlags);
1008     }
1009     else if (GENERIC_WRITE == fdwAccess)
1010     {
1011         /* Set up socket to send data */
1012         bSuccess = FTP_SendStore(lpwfs, lpszFileName, dwFlags);
1013     }
1014
1015     /* Get data socket to server */
1016     if (bSuccess && FTP_GetDataSocket(lpwfs, &nDataSocket))
1017     {
1018         lpwh = HeapAlloc(GetProcessHeap(), 0, sizeof(WININETFILE));
1019         lpwh->hdr.htype = WH_HFILE;
1020         lpwh->hdr.dwFlags = dwFlags;
1021         lpwh->hdr.dwContext = dwContext;
1022         lpwh->hdr.lpwhparent = WININET_AddRef( &lpwfs->hdr );
1023         lpwh->hdr.dwRefCount = 1;
1024         lpwh->hdr.destroy = FTP_CloseFileTransferHandle;
1025         lpwh->nDataSocket = nDataSocket;
1026         lpwh->session_deleted = FALSE;
1027         
1028         handle = WININET_AllocHandle( &lpwh->hdr );
1029         if( !handle )
1030             goto lend;
1031
1032         /* Indicate that a download is currently in progress */
1033         lpwfs->download_in_progress = lpwh;
1034     }
1035
1036     if (lpwfs->lstnSocket != -1)
1037         close(lpwfs->lstnSocket);
1038
1039     hIC = (LPWININETAPPINFOW) lpwfs->hdr.lpwhparent;
1040     if (hIC->hdr.dwFlags & INTERNET_FLAG_ASYNC && hIC->lpfnStatusCB)
1041     {
1042         INTERNET_ASYNC_RESULT iar;
1043
1044         if (lpwh)
1045         {
1046             iar.dwResult = (DWORD)handle;
1047             iar.dwError = ERROR_SUCCESS;
1048             SendAsyncCallback(hIC, &lpwfs->hdr, lpwfs->hdr.dwContext, INTERNET_STATUS_HANDLE_CREATED,
1049                 &iar, sizeof(INTERNET_ASYNC_RESULT));
1050         }
1051
1052         iar.dwResult = (DWORD)bSuccess;
1053         iar.dwError = bSuccess ? ERROR_SUCCESS : INTERNET_GetLastError();
1054         SendAsyncCallback(hIC, &lpwfs->hdr, lpwfs->hdr.dwContext, INTERNET_STATUS_REQUEST_COMPLETE,
1055             &iar, sizeof(INTERNET_ASYNC_RESULT));
1056     }
1057
1058 lend:
1059     if( lpwh )
1060         WININET_Release( &lpwh->hdr );
1061
1062     return handle;
1063 }
1064
1065
1066 /***********************************************************************
1067  *           FtpGetFileA (WININET.@)
1068  *
1069  * Retrieve file from the FTP server
1070  *
1071  * RETURNS
1072  *    TRUE on success
1073  *    FALSE on failure
1074  *
1075  */
1076 BOOL WINAPI FtpGetFileA(HINTERNET hInternet, LPCSTR lpszRemoteFile, LPCSTR lpszNewFile,
1077     BOOL fFailIfExists, DWORD dwLocalFlagsAttribute, DWORD dwInternetFlags,
1078     DWORD dwContext)
1079 {
1080     LPWSTR lpwzRemoteFile;
1081     LPWSTR lpwzNewFile;
1082     BOOL ret;
1083     
1084     lpwzRemoteFile = lpszRemoteFile?WININET_strdup_AtoW(lpszRemoteFile):NULL;
1085     lpwzNewFile = lpszNewFile?WININET_strdup_AtoW(lpszNewFile):NULL;
1086     ret = FtpGetFileW(hInternet, lpwzRemoteFile, lpwzNewFile, fFailIfExists,
1087         dwLocalFlagsAttribute, dwInternetFlags, dwContext);
1088     if(lpwzRemoteFile) HeapFree(GetProcessHeap(), 0, lpwzRemoteFile);
1089     if(lpwzNewFile) HeapFree(GetProcessHeap(), 0, lpwzNewFile);
1090     return ret;
1091 }
1092
1093
1094 /***********************************************************************
1095  *           FtpGetFileW (WININET.@)
1096  *
1097  * Retrieve file from the FTP server
1098  *
1099  * RETURNS
1100  *    TRUE on success
1101  *    FALSE on failure
1102  *
1103  */
1104 BOOL WINAPI FtpGetFileW(HINTERNET hInternet, LPCWSTR lpszRemoteFile, LPCWSTR lpszNewFile,
1105     BOOL fFailIfExists, DWORD dwLocalFlagsAttribute, DWORD dwInternetFlags,
1106     DWORD dwContext)
1107 {
1108     LPWININETFTPSESSIONW lpwfs;
1109     LPWININETAPPINFOW hIC = NULL;
1110     BOOL r = FALSE;
1111
1112     lpwfs = (LPWININETFTPSESSIONW) WININET_GetObject( hInternet );
1113     if (NULL == lpwfs || WH_HFTPSESSION != lpwfs->hdr.htype)
1114     {
1115         INTERNET_SetLastError(ERROR_INTERNET_INCORRECT_HANDLE_TYPE);
1116         goto lend;
1117     }
1118
1119     if (lpwfs->download_in_progress != NULL) {
1120         INTERNET_SetLastError(ERROR_FTP_TRANSFER_IN_PROGRESS);
1121         goto lend;
1122     }
1123     
1124     hIC = (LPWININETAPPINFOW) lpwfs->hdr.lpwhparent;
1125     if (hIC->hdr.dwFlags & INTERNET_FLAG_ASYNC)
1126     {
1127         WORKREQUEST workRequest;
1128         struct WORKREQ_FTPGETFILEW *req;
1129
1130         workRequest.asyncall = FTPGETFILEW;
1131         workRequest.hdr = WININET_AddRef( &lpwfs->hdr );
1132         req = &workRequest.u.FtpGetFileW;
1133         req->lpszRemoteFile = WININET_strdupW(lpszRemoteFile);
1134         req->lpszNewFile = WININET_strdupW(lpszNewFile);
1135         req->dwLocalFlagsAttribute = dwLocalFlagsAttribute;
1136         req->fFailIfExists = fFailIfExists;
1137         req->dwFlags = dwInternetFlags;
1138         req->dwContext = dwContext;
1139
1140         r = INTERNET_AsyncCall(&workRequest);
1141     }
1142     else
1143     {
1144         r = FTP_FtpGetFileW(lpwfs, lpszRemoteFile, lpszNewFile,
1145            fFailIfExists, dwLocalFlagsAttribute, dwInternetFlags, dwContext);
1146     }
1147
1148 lend:
1149     if( lpwfs )
1150         WININET_Release( &lpwfs->hdr );
1151
1152     return r;
1153 }
1154
1155
1156 /***********************************************************************
1157  *           FTP_FtpGetFileW (Internal)
1158  *
1159  * Retrieve file from the FTP server
1160  *
1161  * RETURNS
1162  *    TRUE on success
1163  *    FALSE on failure
1164  *
1165  */
1166 BOOL WINAPI FTP_FtpGetFileW(LPWININETFTPSESSIONW lpwfs, LPCWSTR lpszRemoteFile, LPCWSTR lpszNewFile,
1167         BOOL fFailIfExists, DWORD dwLocalFlagsAttribute, DWORD dwInternetFlags,
1168         DWORD dwContext)
1169 {
1170     DWORD nBytes;
1171     BOOL bSuccess = FALSE;
1172     HANDLE hFile;
1173     LPWININETAPPINFOW hIC = NULL;
1174
1175     TRACE("lpszRemoteFile(%s) lpszNewFile(%s)\n", debugstr_w(lpszRemoteFile), debugstr_w(lpszNewFile));
1176
1177     assert (WH_HFTPSESSION == lpwfs->hdr.htype);
1178
1179     /* Clear any error information */
1180     INTERNET_SetLastError(0);
1181
1182     /* Ensure we can write to lpszNewfile by opening it */
1183     hFile = CreateFileW(lpszNewFile, GENERIC_WRITE, 0, 0, fFailIfExists ?
1184         CREATE_NEW : CREATE_ALWAYS, dwLocalFlagsAttribute, 0);
1185     if (INVALID_HANDLE_VALUE == hFile)
1186         goto lend;
1187
1188     /* Set up socket to retrieve data */
1189     nBytes = FTP_SendRetrieve(lpwfs, lpszRemoteFile, dwInternetFlags);
1190
1191     if (nBytes > 0)
1192     {
1193         INT nDataSocket;
1194
1195         /* Get data socket to server */
1196         if (FTP_GetDataSocket(lpwfs, &nDataSocket))
1197         {
1198             INT nResCode;
1199
1200             /* Receive data */
1201             FTP_RetrieveFileData(lpwfs, nDataSocket, nBytes, hFile);
1202             nResCode = FTP_ReceiveResponse(lpwfs, dwContext);
1203             if (nResCode)
1204             {
1205                 if (nResCode == 226)
1206                     bSuccess = TRUE;
1207                 else
1208                     FTP_SetResponseError(nResCode);
1209             }
1210             close(nDataSocket);
1211         }
1212     }
1213
1214 lend:
1215     if (lpwfs->lstnSocket != -1)
1216         close(lpwfs->lstnSocket);
1217
1218     if (hFile)
1219         CloseHandle(hFile);
1220
1221     hIC = (LPWININETAPPINFOW) lpwfs->hdr.lpwhparent;
1222     if (hIC->hdr.dwFlags & INTERNET_FLAG_ASYNC && hIC->lpfnStatusCB)
1223     {
1224         INTERNET_ASYNC_RESULT iar;
1225
1226         iar.dwResult = (DWORD)bSuccess;
1227         iar.dwError = bSuccess ? ERROR_SUCCESS : INTERNET_GetLastError();
1228         SendAsyncCallback(hIC, &lpwfs->hdr, lpwfs->hdr.dwContext, INTERNET_STATUS_REQUEST_COMPLETE,
1229             &iar, sizeof(INTERNET_ASYNC_RESULT));
1230     }
1231
1232     return bSuccess;
1233 }
1234
1235
1236 /***********************************************************************
1237  *           FtpDeleteFileA  (WININET.@)
1238  *
1239  * Delete a file on the ftp server
1240  *
1241  * RETURNS
1242  *    TRUE on success
1243  *    FALSE on failure
1244  *
1245  */
1246 BOOL WINAPI FtpDeleteFileA(HINTERNET hFtpSession, LPCSTR lpszFileName)
1247 {
1248     LPWSTR lpwzFileName;
1249     BOOL ret;
1250     
1251     lpwzFileName = lpszFileName?WININET_strdup_AtoW(lpszFileName):NULL;
1252     ret = FtpDeleteFileW(hFtpSession, lpwzFileName);
1253     if(lpwzFileName) HeapFree(GetProcessHeap(), 0, lpwzFileName);
1254     return ret;
1255 }
1256
1257 /***********************************************************************
1258  *           FtpDeleteFileW  (WININET.@)
1259  *
1260  * Delete a file on the ftp server
1261  *
1262  * RETURNS
1263  *    TRUE on success
1264  *    FALSE on failure
1265  *
1266  */
1267 BOOL WINAPI FtpDeleteFileW(HINTERNET hFtpSession, LPCWSTR lpszFileName)
1268 {
1269     LPWININETFTPSESSIONW lpwfs;
1270     LPWININETAPPINFOW hIC = NULL;
1271     BOOL r = FALSE;
1272
1273     lpwfs = (LPWININETFTPSESSIONW) WININET_GetObject( hFtpSession );
1274     if (NULL == lpwfs || WH_HFTPSESSION != lpwfs->hdr.htype)
1275     {
1276         INTERNET_SetLastError(ERROR_INTERNET_INCORRECT_HANDLE_TYPE);
1277         goto lend;
1278     }
1279
1280     hIC = (LPWININETAPPINFOW) lpwfs->hdr.lpwhparent;
1281     if (hIC->hdr.dwFlags & INTERNET_FLAG_ASYNC)
1282     {
1283         WORKREQUEST workRequest;
1284         struct WORKREQ_FTPDELETEFILEW *req;
1285
1286         workRequest.asyncall = FTPDELETEFILEW;
1287         workRequest.hdr = WININET_AddRef( &lpwfs->hdr );
1288         req = &workRequest.u.FtpDeleteFileW;
1289         req->lpszFilename = WININET_strdupW(lpszFileName);
1290
1291         r = INTERNET_AsyncCall(&workRequest);
1292     }
1293     else
1294     {
1295         r = FTP_FtpDeleteFileW(hFtpSession, lpszFileName);
1296     }
1297
1298 lend:
1299     if( lpwfs )
1300         WININET_Release( &lpwfs->hdr );
1301
1302     return r;
1303 }
1304
1305 /***********************************************************************
1306  *           FTP_FtpDeleteFileW  (Internal)
1307  *
1308  * Delete a file on the ftp server
1309  *
1310  * RETURNS
1311  *    TRUE on success
1312  *    FALSE on failure
1313  *
1314  */
1315 BOOL FTP_FtpDeleteFileW(LPWININETFTPSESSIONW lpwfs, LPCWSTR lpszFileName)
1316 {
1317     INT nResCode;
1318     BOOL bSuccess = FALSE;
1319     LPWININETAPPINFOW hIC = NULL;
1320
1321     TRACE("%p\n", lpwfs);
1322
1323     assert (WH_HFTPSESSION == lpwfs->hdr.htype);
1324
1325     /* Clear any error information */
1326     INTERNET_SetLastError(0);
1327
1328     if (!FTP_SendCommand(lpwfs->sndSocket, FTP_CMD_DELE, lpszFileName, 0, 0, 0))
1329         goto lend;
1330
1331     nResCode = FTP_ReceiveResponse(lpwfs, lpwfs->hdr.dwContext);
1332     if (nResCode)
1333     {
1334         if (nResCode == 250)
1335             bSuccess = TRUE;
1336         else
1337             FTP_SetResponseError(nResCode);
1338     }
1339 lend:
1340     hIC = (LPWININETAPPINFOW) lpwfs->hdr.lpwhparent;
1341     if (hIC->hdr.dwFlags & INTERNET_FLAG_ASYNC && hIC->lpfnStatusCB)
1342     {
1343         INTERNET_ASYNC_RESULT iar;
1344
1345         iar.dwResult = (DWORD)bSuccess;
1346         iar.dwError = bSuccess ? ERROR_SUCCESS : INTERNET_GetLastError();
1347         SendAsyncCallback(hIC, &lpwfs->hdr, lpwfs->hdr.dwContext, INTERNET_STATUS_REQUEST_COMPLETE,
1348             &iar, sizeof(INTERNET_ASYNC_RESULT));
1349     }
1350
1351     return bSuccess;
1352 }
1353
1354
1355 /***********************************************************************
1356  *           FtpRemoveDirectoryA  (WININET.@)
1357  *
1358  * Remove a directory on the ftp server
1359  *
1360  * RETURNS
1361  *    TRUE on success
1362  *    FALSE on failure
1363  *
1364  */
1365 BOOL WINAPI FtpRemoveDirectoryA(HINTERNET hFtpSession, LPCSTR lpszDirectory)
1366 {
1367     LPWSTR lpwzDirectory;
1368     BOOL ret;
1369     
1370     lpwzDirectory = lpszDirectory?WININET_strdup_AtoW(lpszDirectory):NULL;
1371     ret = FtpRemoveDirectoryW(hFtpSession, lpwzDirectory);
1372     if(lpwzDirectory) HeapFree(GetProcessHeap(), 0, lpwzDirectory);
1373     return ret;
1374 }
1375
1376 /***********************************************************************
1377  *           FtpRemoveDirectoryW  (WININET.@)
1378  *
1379  * Remove a directory on the ftp server
1380  *
1381  * RETURNS
1382  *    TRUE on success
1383  *    FALSE on failure
1384  *
1385  */
1386 BOOL WINAPI FtpRemoveDirectoryW(HINTERNET hFtpSession, LPCWSTR lpszDirectory)
1387 {
1388     LPWININETFTPSESSIONW lpwfs;
1389     LPWININETAPPINFOW hIC = NULL;
1390     BOOL r = FALSE;
1391
1392     lpwfs = (LPWININETFTPSESSIONW) WININET_GetObject( hFtpSession );
1393     if (NULL == lpwfs || WH_HFTPSESSION != lpwfs->hdr.htype)
1394     {
1395         INTERNET_SetLastError(ERROR_INTERNET_INCORRECT_HANDLE_TYPE);
1396         goto lend;
1397     }
1398
1399     hIC = (LPWININETAPPINFOW) lpwfs->hdr.lpwhparent;
1400     if (hIC->hdr.dwFlags & INTERNET_FLAG_ASYNC)
1401     {
1402         WORKREQUEST workRequest;
1403         struct WORKREQ_FTPREMOVEDIRECTORYW *req;
1404
1405         workRequest.asyncall = FTPREMOVEDIRECTORYW;
1406         workRequest.hdr = WININET_AddRef( &lpwfs->hdr );
1407         req = &workRequest.u.FtpRemoveDirectoryW;
1408         req->lpszDirectory = WININET_strdupW(lpszDirectory);
1409
1410         r = INTERNET_AsyncCall(&workRequest);
1411     }
1412     else
1413     {
1414         r = FTP_FtpRemoveDirectoryW(lpwfs, lpszDirectory);
1415     }
1416
1417 lend:
1418     if( lpwfs )
1419         WININET_Release( &lpwfs->hdr );
1420
1421     return r;
1422 }
1423
1424 /***********************************************************************
1425  *           FTP_FtpRemoveDirectoryW  (Internal)
1426  *
1427  * Remove a directory on the ftp server
1428  *
1429  * RETURNS
1430  *    TRUE on success
1431  *    FALSE on failure
1432  *
1433  */
1434 BOOL FTP_FtpRemoveDirectoryW(LPWININETFTPSESSIONW lpwfs, LPCWSTR lpszDirectory)
1435 {
1436     INT nResCode;
1437     BOOL bSuccess = FALSE;
1438     LPWININETAPPINFOW hIC = NULL;
1439
1440     TRACE("\n");
1441
1442     assert (WH_HFTPSESSION == lpwfs->hdr.htype);
1443
1444     /* Clear any error information */
1445     INTERNET_SetLastError(0);
1446
1447     if (!FTP_SendCommand(lpwfs->sndSocket, FTP_CMD_RMD, lpszDirectory, 0, 0, 0))
1448         goto lend;
1449
1450     nResCode = FTP_ReceiveResponse(lpwfs, lpwfs->hdr.dwContext);
1451     if (nResCode)
1452     {
1453         if (nResCode == 250)
1454             bSuccess = TRUE;
1455         else
1456             FTP_SetResponseError(nResCode);
1457     }
1458
1459 lend:
1460     hIC = (LPWININETAPPINFOW) lpwfs->hdr.lpwhparent;
1461     if (hIC->hdr.dwFlags & INTERNET_FLAG_ASYNC && hIC->lpfnStatusCB)
1462     {
1463         INTERNET_ASYNC_RESULT iar;
1464
1465         iar.dwResult = (DWORD)bSuccess;
1466         iar.dwError = bSuccess ? ERROR_SUCCESS : INTERNET_GetLastError();
1467         SendAsyncCallback(hIC, &lpwfs->hdr, lpwfs->hdr.dwContext, INTERNET_STATUS_REQUEST_COMPLETE,
1468             &iar, sizeof(INTERNET_ASYNC_RESULT));
1469     }
1470
1471     return bSuccess;
1472 }
1473
1474
1475 /***********************************************************************
1476  *           FtpRenameFileA  (WININET.@)
1477  *
1478  * Rename a file on the ftp server
1479  *
1480  * RETURNS
1481  *    TRUE on success
1482  *    FALSE on failure
1483  *
1484  */
1485 BOOL WINAPI FtpRenameFileA(HINTERNET hFtpSession, LPCSTR lpszSrc, LPCSTR lpszDest)
1486 {
1487     LPWSTR lpwzSrc;
1488     LPWSTR lpwzDest;
1489     BOOL ret;
1490     
1491     lpwzSrc = lpszSrc?WININET_strdup_AtoW(lpszSrc):NULL;
1492     lpwzDest = lpszDest?WININET_strdup_AtoW(lpszDest):NULL;
1493     ret = FtpRenameFileW(hFtpSession, lpwzSrc, lpwzDest);
1494     if(lpwzSrc) HeapFree(GetProcessHeap(), 0, lpwzSrc);
1495     if(lpwzDest) HeapFree(GetProcessHeap(), 0, lpwzDest);
1496     return ret;
1497 }
1498
1499 /***********************************************************************
1500  *           FtpRenameFileW  (WININET.@)
1501  *
1502  * Rename a file on the ftp server
1503  *
1504  * RETURNS
1505  *    TRUE on success
1506  *    FALSE on failure
1507  *
1508  */
1509 BOOL WINAPI FtpRenameFileW(HINTERNET hFtpSession, LPCWSTR lpszSrc, LPCWSTR lpszDest)
1510 {
1511     LPWININETFTPSESSIONW lpwfs;
1512     LPWININETAPPINFOW hIC = NULL;
1513     BOOL r = FALSE;
1514
1515     lpwfs = (LPWININETFTPSESSIONW) WININET_GetObject( hFtpSession );
1516     if (NULL == lpwfs || WH_HFTPSESSION != lpwfs->hdr.htype)
1517     {
1518         INTERNET_SetLastError(ERROR_INTERNET_INCORRECT_HANDLE_TYPE);
1519         goto lend;
1520     }
1521
1522     hIC = (LPWININETAPPINFOW) lpwfs->hdr.lpwhparent;
1523     if (hIC->hdr.dwFlags & INTERNET_FLAG_ASYNC)
1524     {
1525         WORKREQUEST workRequest;
1526         struct WORKREQ_FTPRENAMEFILEW *req;
1527
1528         workRequest.asyncall = FTPRENAMEFILEW;
1529         workRequest.hdr = WININET_AddRef( &lpwfs->hdr );
1530         req = &workRequest.u.FtpRenameFileW;
1531         req->lpszSrcFile = WININET_strdupW(lpszSrc);
1532         req->lpszDestFile = WININET_strdupW(lpszDest);
1533
1534         r = INTERNET_AsyncCall(&workRequest);
1535     }
1536     else
1537     {
1538         r = FTP_FtpRenameFileW(hFtpSession, lpszSrc, lpszDest);
1539     }
1540
1541 lend:
1542     if( lpwfs )
1543         WININET_Release( &lpwfs->hdr );
1544
1545     return r;
1546 }
1547
1548 /***********************************************************************
1549  *           FTP_FtpRenameFileA  (Internal)
1550  *
1551  * Rename a file on the ftp server
1552  *
1553  * RETURNS
1554  *    TRUE on success
1555  *    FALSE on failure
1556  *
1557  */
1558 BOOL FTP_FtpRenameFileW( LPWININETFTPSESSIONW lpwfs,
1559                          LPCWSTR lpszSrc, LPCWSTR lpszDest)
1560 {
1561     INT nResCode;
1562     BOOL bSuccess = FALSE;
1563     LPWININETAPPINFOW hIC = NULL;
1564
1565     TRACE("\n");
1566
1567     assert (WH_HFTPSESSION == lpwfs->hdr.htype);
1568
1569     /* Clear any error information */
1570     INTERNET_SetLastError(0);
1571
1572     if (!FTP_SendCommand(lpwfs->sndSocket, FTP_CMD_RNFR, lpszSrc, 0, 0, 0))
1573         goto lend;
1574
1575     nResCode = FTP_ReceiveResponse(lpwfs, lpwfs->hdr.dwContext);
1576     if (nResCode == 350)
1577     {
1578         if (!FTP_SendCommand(lpwfs->sndSocket, FTP_CMD_RNTO, lpszDest, 0, 0, 0))
1579             goto lend;
1580
1581         nResCode = FTP_ReceiveResponse(lpwfs, lpwfs->hdr.dwContext);
1582     }
1583
1584     if (nResCode == 250)
1585         bSuccess = TRUE;
1586     else
1587         FTP_SetResponseError(nResCode);
1588
1589 lend:
1590     hIC = (LPWININETAPPINFOW) lpwfs->hdr.lpwhparent;
1591     if (hIC->hdr.dwFlags & INTERNET_FLAG_ASYNC && hIC->lpfnStatusCB)
1592     {
1593         INTERNET_ASYNC_RESULT iar;
1594
1595         iar.dwResult = (DWORD)bSuccess;
1596         iar.dwError = bSuccess ? ERROR_SUCCESS : INTERNET_GetLastError();
1597         SendAsyncCallback(hIC, &lpwfs->hdr, lpwfs->hdr.dwContext, INTERNET_STATUS_REQUEST_COMPLETE,
1598             &iar, sizeof(INTERNET_ASYNC_RESULT));
1599     }
1600
1601     return bSuccess;
1602 }
1603
1604
1605 /***********************************************************************
1606  *           FTP_Connect (internal)
1607  *
1608  * Connect to a ftp server
1609  *
1610  * RETURNS
1611  *   HINTERNET a session handle on success
1612  *   NULL on failure
1613  *
1614  */
1615
1616 HINTERNET FTP_Connect(LPWININETAPPINFOW hIC, LPCWSTR lpszServerName,
1617         INTERNET_PORT nServerPort, LPCWSTR lpszUserName,
1618         LPCWSTR lpszPassword, DWORD dwFlags, DWORD dwContext,
1619         DWORD dwInternalFlags)
1620 {
1621     static const WCHAR szDefaultUsername[] = {'a','n','o','n','y','m','o','u','s','\0'};
1622     static const WCHAR szDefaultPassword[] = {'u','s','e','r','@','s','e','r','v','e','r','\0'};
1623     struct sockaddr_in socketAddr;
1624     struct hostent *phe = NULL;
1625     INT nsocket = -1, sock_namelen;
1626     BOOL bSuccess = FALSE;
1627     LPWININETFTPSESSIONW lpwfs = NULL;
1628     HINTERNET handle = NULL;
1629
1630     TRACE("%p  Server(%s) Port(%d) User(%s) Paswd(%s)\n",
1631             hIC, debugstr_w(lpszServerName),
1632             nServerPort, debugstr_w(lpszUserName), debugstr_w(lpszPassword));
1633
1634     assert( hIC->hdr.htype != WH_HINIT );
1635
1636     if (NULL == lpszUserName && NULL != lpszPassword)
1637     {
1638         INTERNET_SetLastError(ERROR_INVALID_PARAMETER);
1639         goto lerror;
1640     }
1641     
1642     lpwfs = HeapAlloc(GetProcessHeap(), 0, sizeof(WININETFTPSESSIONW));
1643     if (NULL == lpwfs)
1644     {
1645         INTERNET_SetLastError(ERROR_OUTOFMEMORY);
1646         goto lerror;
1647     }
1648
1649     if (nServerPort == INTERNET_INVALID_PORT_NUMBER)
1650         nServerPort = INTERNET_DEFAULT_FTP_PORT;
1651
1652     lpwfs->hdr.htype = WH_HFTPSESSION;
1653     lpwfs->hdr.lpwhparent = WININET_AddRef( &hIC->hdr );
1654     lpwfs->hdr.dwFlags = dwFlags;
1655     lpwfs->hdr.dwContext = dwContext;
1656     lpwfs->hdr.dwInternalFlags = dwInternalFlags;
1657     lpwfs->hdr.dwRefCount = 1;
1658     lpwfs->hdr.destroy = FTP_CloseSessionHandle;
1659     lpwfs->download_in_progress = NULL;
1660
1661     handle = WININET_AllocHandle( &lpwfs->hdr );
1662     if( !handle )
1663     {
1664         ERR("Failed to alloc handle\n");
1665         INTERNET_SetLastError(ERROR_OUTOFMEMORY);
1666         goto lerror;
1667     }
1668
1669     if(hIC->lpszProxy && hIC->dwAccessType == INTERNET_OPEN_TYPE_PROXY) {
1670         if(strchrW(hIC->lpszProxy, ' '))
1671             FIXME("Several proxies not implemented.\n");
1672         if(hIC->lpszProxyBypass)
1673             FIXME("Proxy bypass is ignored.\n");
1674     }
1675     if ( !lpszUserName) {
1676         lpwfs->lpszUserName = WININET_strdupW(szDefaultUsername);
1677         lpwfs->lpszPassword = WININET_strdupW(szDefaultPassword);
1678     }
1679     else {
1680         lpwfs->lpszUserName = WININET_strdupW(lpszUserName);
1681         lpwfs->lpszPassword = WININET_strdupW(lpszPassword);
1682     }
1683     
1684     /* Don't send a handle created callback if this handle was created with InternetOpenUrl */
1685     if (hIC->lpfnStatusCB  && !(lpwfs->hdr.dwInternalFlags & INET_OPENURL))
1686     {
1687         INTERNET_ASYNC_RESULT iar;
1688
1689         iar.dwResult = (DWORD)handle;
1690         iar.dwError = ERROR_SUCCESS;
1691
1692         SendAsyncCallback(hIC, &hIC->hdr, dwContext,
1693                       INTERNET_STATUS_HANDLE_CREATED, &iar,
1694                       sizeof(INTERNET_ASYNC_RESULT));
1695     }
1696         
1697     SendAsyncCallback(hIC, &hIC->hdr, dwContext, INTERNET_STATUS_RESOLVING_NAME,
1698         (LPWSTR) lpszServerName, strlenW(lpszServerName));
1699
1700     if (!GetAddress(lpszServerName, nServerPort, &phe, &socketAddr))
1701     {
1702         INTERNET_SetLastError(ERROR_INTERNET_NAME_NOT_RESOLVED);
1703         goto lerror;
1704     }
1705
1706     SendAsyncCallback(hIC, &hIC->hdr, dwContext, INTERNET_STATUS_NAME_RESOLVED,
1707         (LPWSTR) lpszServerName, strlenW(lpszServerName));
1708
1709     nsocket = socket(AF_INET,SOCK_STREAM,0);
1710     if (nsocket == -1)
1711     {
1712         INTERNET_SetLastError(ERROR_INTERNET_CANNOT_CONNECT);
1713         goto lerror;
1714     }
1715
1716     SendAsyncCallback(hIC, &hIC->hdr, dwContext, INTERNET_STATUS_CONNECTING_TO_SERVER,
1717         &socketAddr, sizeof(struct sockaddr_in));
1718
1719     if (connect(nsocket, (struct sockaddr *)&socketAddr, sizeof(socketAddr)) < 0)
1720     {
1721         ERR("Unable to connect (%s)\n", strerror(errno));
1722         INTERNET_SetLastError(ERROR_INTERNET_CANNOT_CONNECT);
1723     }
1724     else
1725     {
1726         TRACE("Connected to server\n");
1727         lpwfs->sndSocket = nsocket;
1728         SendAsyncCallback(hIC, &hIC->hdr, dwContext, INTERNET_STATUS_CONNECTED_TO_SERVER,
1729             &socketAddr, sizeof(struct sockaddr_in));
1730
1731         sock_namelen = sizeof(lpwfs->socketAddress);
1732         getsockname(nsocket, (struct sockaddr *) &lpwfs->socketAddress, &sock_namelen);
1733         lpwfs->phostent = phe;
1734
1735         if (FTP_ConnectToHost(lpwfs))
1736         {
1737             TRACE("Successfully logged into server\n");
1738             bSuccess = TRUE;
1739         }
1740     }
1741
1742 lerror:
1743     if (!bSuccess && nsocket == -1)
1744         close(nsocket);
1745
1746     if (!bSuccess && lpwfs)
1747     {
1748         HeapFree(GetProcessHeap(), 0, lpwfs);
1749         WININET_FreeHandle( handle );
1750         lpwfs = NULL;
1751     }
1752
1753     if (hIC->hdr.dwFlags & INTERNET_FLAG_ASYNC && hIC->lpfnStatusCB)
1754     {
1755         INTERNET_ASYNC_RESULT iar;
1756
1757         iar.dwResult = (DWORD)lpwfs;
1758         iar.dwError = bSuccess ? ERROR_SUCCESS : INTERNET_GetLastError();
1759         SendAsyncCallback(hIC, &hIC->hdr, dwContext, INTERNET_STATUS_REQUEST_COMPLETE,
1760             &iar, sizeof(INTERNET_ASYNC_RESULT));
1761     }
1762
1763     return handle;
1764 }
1765
1766
1767 /***********************************************************************
1768  *           FTP_ConnectToHost (internal)
1769  *
1770  * Connect to a ftp server
1771  *
1772  * RETURNS
1773  *   TRUE on success
1774  *   NULL on failure
1775  *
1776  */
1777 BOOL FTP_ConnectToHost(LPWININETFTPSESSIONW lpwfs)
1778 {
1779     INT nResCode;
1780     BOOL bSuccess = FALSE;
1781
1782     TRACE("\n");
1783     FTP_ReceiveResponse(lpwfs, lpwfs->hdr.dwContext);
1784
1785     if (!FTP_SendCommand(lpwfs->sndSocket, FTP_CMD_USER, lpwfs->lpszUserName, 0, 0, 0))
1786         goto lend;
1787
1788     nResCode = FTP_ReceiveResponse(lpwfs, lpwfs->hdr.dwContext);
1789     if (nResCode)
1790     {
1791         /* Login successful... */
1792         if (nResCode == 230)
1793             bSuccess = TRUE;
1794         /* User name okay, need password... */
1795         else if (nResCode == 331)
1796             bSuccess = FTP_SendPassword(lpwfs);
1797         /* Need account for login... */
1798         else if (nResCode == 332)
1799             bSuccess = FTP_SendAccount(lpwfs);
1800         else
1801             FTP_SetResponseError(nResCode);
1802     }
1803
1804     TRACE("Returning %d\n", bSuccess);
1805 lend:
1806     return bSuccess;
1807 }
1808
1809
1810 /***********************************************************************
1811  *           FTP_SendCommandA (internal)
1812  *
1813  * Send command to server
1814  *
1815  * RETURNS
1816  *   TRUE on success
1817  *   NULL on failure
1818  *
1819  */
1820 BOOL FTP_SendCommandA(INT nSocket, FTP_COMMAND ftpCmd, LPCSTR lpszParam,
1821         INTERNET_STATUS_CALLBACK lpfnStatusCB, LPWININETHANDLEHEADER hdr, DWORD dwContext)
1822 {
1823         DWORD len;
1824         CHAR *buf;
1825         DWORD nBytesSent = 0;
1826         DWORD nRC = 0;
1827         DWORD dwParamLen;
1828
1829         TRACE("%d: (%s) %d\n", ftpCmd, lpszParam, nSocket);
1830
1831         if (lpfnStatusCB)
1832         {
1833              HINTERNET hHandle = WININET_FindHandle( hdr );
1834              if( hHandle )
1835              {
1836                  lpfnStatusCB(hHandle, dwContext, INTERNET_STATUS_SENDING_REQUEST, NULL, 0);
1837                  WININET_Release( hdr );
1838              }
1839         }
1840
1841         dwParamLen = lpszParam?strlen(lpszParam)+1:0;
1842         len = dwParamLen + strlen(szFtpCommands[ftpCmd]) + strlen(szCRLF);
1843         if (NULL == (buf = HeapAlloc(GetProcessHeap(), 0, len+1)))
1844         {
1845             INTERNET_SetLastError(ERROR_OUTOFMEMORY);
1846             return FALSE;
1847         }
1848         sprintf(buf, "%s%s%s%s", szFtpCommands[ftpCmd], dwParamLen ? " " : "",
1849                 dwParamLen ? lpszParam : "", szCRLF);
1850
1851         TRACE("Sending (%s) len(%ld)\n", buf, len);
1852         while((nBytesSent < len) && (nRC != -1))
1853         {
1854                 nRC = send(nSocket, buf+nBytesSent, len - nBytesSent, 0);
1855                 nBytesSent += nRC;
1856         }
1857
1858         HeapFree(GetProcessHeap(), 0, (LPVOID)buf);
1859
1860         if (lpfnStatusCB)
1861         {
1862              HINTERNET hHandle = WININET_FindHandle( hdr );
1863              if( hHandle )
1864              {
1865                  lpfnStatusCB(hHandle, dwContext, INTERNET_STATUS_REQUEST_SENT,
1866                               &nBytesSent, sizeof(DWORD));
1867                  WININET_Release( hdr );
1868              }
1869         }
1870
1871         TRACE("Sent %ld bytes\n", nBytesSent);
1872         return (nRC != -1);
1873 }
1874
1875 /***********************************************************************
1876  *           FTP_SendCommand (internal)
1877  *
1878  * Send command to server
1879  *
1880  * RETURNS
1881  *   TRUE on success
1882  *   NULL on failure
1883  *
1884  */
1885 BOOL FTP_SendCommand(INT nSocket, FTP_COMMAND ftpCmd, LPCWSTR lpszParam,
1886         INTERNET_STATUS_CALLBACK lpfnStatusCB, LPWININETHANDLEHEADER hdr, DWORD dwContext)
1887 {
1888     BOOL ret;
1889     LPSTR lpszParamA = lpszParam?WININET_strdup_WtoA(lpszParam):NULL;
1890     ret = FTP_SendCommandA(nSocket, ftpCmd, lpszParamA, lpfnStatusCB, hdr, dwContext);
1891     HeapFree(GetProcessHeap(), 0, lpszParamA);
1892     return ret;
1893 }
1894
1895 /***********************************************************************
1896  *           FTP_ReceiveResponse (internal)
1897  *
1898  * Receive response from server
1899  *
1900  * RETURNS
1901  *   Reply code on success
1902  *   0 on failure
1903  *
1904  */
1905 INT FTP_ReceiveResponse(LPWININETFTPSESSIONW lpwfs, DWORD dwContext)
1906 {
1907     LPSTR lpszResponse = INTERNET_GetResponseBuffer();
1908     DWORD nRecv;
1909     INT rc = 0;
1910     char firstprefix[5];
1911     BOOL multiline = FALSE;
1912     LPWININETAPPINFOW hIC = NULL;
1913
1914     TRACE("socket(%d) \n", lpwfs->sndSocket);
1915
1916     hIC = (LPWININETAPPINFOW) lpwfs->hdr.lpwhparent;
1917     SendAsyncCallback(hIC, &lpwfs->hdr, dwContext, INTERNET_STATUS_RECEIVING_RESPONSE, NULL, 0);
1918
1919     while(1)
1920     {
1921         if (!INTERNET_GetNextLine(lpwfs->sndSocket, &nRecv))
1922             goto lerror;
1923
1924         if (nRecv >= 3)
1925         {
1926             if(!multiline)
1927             {
1928                 if(lpszResponse[3] != '-')
1929                     break;
1930                 else
1931                 {  /* Start of multiline repsonse.  Loop until we get "nnn " */
1932                     multiline = TRUE;
1933                     memcpy(firstprefix, lpszResponse, 3);
1934                     firstprefix[3] = ' ';
1935                     firstprefix[4] = '\0';
1936                 }
1937             }
1938             else
1939             {
1940                 if(!memcmp(firstprefix, lpszResponse, 4))
1941                     break;
1942             }
1943         }
1944     }
1945
1946     if (nRecv >= 3)
1947     {
1948         rc = atoi(lpszResponse);
1949
1950         SendAsyncCallback(hIC, &lpwfs->hdr, dwContext, INTERNET_STATUS_RESPONSE_RECEIVED,
1951                     &nRecv, sizeof(DWORD));
1952     }
1953
1954 lerror:
1955     TRACE("return %d\n", rc);
1956     return rc;
1957 }
1958
1959
1960 /***********************************************************************
1961  *           FTP_SendPassword (internal)
1962  *
1963  * Send password to ftp server
1964  *
1965  * RETURNS
1966  *   TRUE on success
1967  *   NULL on failure
1968  *
1969  */
1970 BOOL FTP_SendPassword(LPWININETFTPSESSIONW lpwfs)
1971 {
1972     INT nResCode;
1973     BOOL bSuccess = FALSE;
1974
1975     TRACE("\n");
1976     if (!FTP_SendCommand(lpwfs->sndSocket, FTP_CMD_PASS, lpwfs->lpszPassword, 0, 0, 0))
1977         goto lend;
1978
1979     nResCode = FTP_ReceiveResponse(lpwfs, lpwfs->hdr.dwContext);
1980     if (nResCode)
1981     {
1982         TRACE("Received reply code %d\n", nResCode);
1983         /* Login successful... */
1984         if (nResCode == 230)
1985             bSuccess = TRUE;
1986         /* Command not implemented, superfluous at the server site... */
1987         /* Need account for login... */
1988         else if (nResCode == 332)
1989             bSuccess = FTP_SendAccount(lpwfs);
1990         else
1991             FTP_SetResponseError(nResCode);
1992     }
1993
1994 lend:
1995     TRACE("Returning %d\n", bSuccess);
1996     return bSuccess;
1997 }
1998
1999
2000 /***********************************************************************
2001  *           FTP_SendAccount (internal)
2002  *
2003  *
2004  *
2005  * RETURNS
2006  *   TRUE on success
2007  *   FALSE on failure
2008  *
2009  */
2010 BOOL FTP_SendAccount(LPWININETFTPSESSIONW lpwfs)
2011 {
2012     INT nResCode;
2013     BOOL bSuccess = FALSE;
2014
2015     TRACE("\n");
2016     if (!FTP_SendCommand(lpwfs->sndSocket, FTP_CMD_ACCT, szNoAccount, 0, 0, 0))
2017         goto lend;
2018
2019     nResCode = FTP_ReceiveResponse(lpwfs, lpwfs->hdr.dwContext);
2020     if (nResCode)
2021         bSuccess = TRUE;
2022     else
2023         FTP_SetResponseError(nResCode);
2024
2025 lend:
2026     return bSuccess;
2027 }
2028
2029
2030 /***********************************************************************
2031  *           FTP_SendStore (internal)
2032  *
2033  * Send request to upload file to ftp server
2034  *
2035  * RETURNS
2036  *   TRUE on success
2037  *   FALSE on failure
2038  *
2039  */
2040 BOOL FTP_SendStore(LPWININETFTPSESSIONW lpwfs, LPCWSTR lpszRemoteFile, DWORD dwType)
2041 {
2042     INT nResCode;
2043     BOOL bSuccess = FALSE;
2044
2045     TRACE("\n");
2046     if (!FTP_InitListenSocket(lpwfs))
2047         goto lend;
2048
2049     if (!FTP_SendType(lpwfs, dwType))
2050         goto lend;
2051
2052     if (!FTP_SendPortOrPasv(lpwfs))
2053         goto lend;
2054
2055     if (!FTP_SendCommand(lpwfs->sndSocket, FTP_CMD_STOR, lpszRemoteFile, 0, 0, 0))
2056             goto lend;
2057     nResCode = FTP_ReceiveResponse(lpwfs, lpwfs->hdr.dwContext);
2058     if (nResCode)
2059     {
2060         if (nResCode == 150)
2061             bSuccess = TRUE;
2062         else
2063             FTP_SetResponseError(nResCode);
2064     }
2065
2066 lend:
2067     if (!bSuccess && lpwfs->lstnSocket != -1)
2068     {
2069         close(lpwfs->lstnSocket);
2070         lpwfs->lstnSocket = -1;
2071     }
2072
2073     return bSuccess;
2074 }
2075
2076
2077 /***********************************************************************
2078  *           FTP_InitListenSocket (internal)
2079  *
2080  * Create a socket to listen for server response
2081  *
2082  * RETURNS
2083  *   TRUE on success
2084  *   FALSE on failure
2085  *
2086  */
2087 BOOL FTP_InitListenSocket(LPWININETFTPSESSIONW lpwfs)
2088 {
2089     BOOL bSuccess = FALSE;
2090     size_t namelen = sizeof(struct sockaddr_in);
2091
2092     TRACE("\n");
2093
2094     lpwfs->lstnSocket = socket(PF_INET, SOCK_STREAM, 0);
2095     if (lpwfs->lstnSocket == -1)
2096     {
2097         TRACE("Unable to create listening socket\n");
2098             goto lend;
2099     }
2100
2101     /* We obtain our ip addr from the name of the command channel socket */
2102     lpwfs->lstnSocketAddress = lpwfs->socketAddress;
2103
2104     /* and get the system to assign us a port */
2105     lpwfs->lstnSocketAddress.sin_port = htons((u_short) 0);
2106
2107     if (bind(lpwfs->lstnSocket,(struct sockaddr *) &lpwfs->lstnSocketAddress, sizeof(struct sockaddr_in)) == -1)
2108     {
2109         TRACE("Unable to bind socket\n");
2110         goto lend;
2111     }
2112
2113     if (listen(lpwfs->lstnSocket, MAX_BACKLOG) == -1)
2114     {
2115         TRACE("listen failed\n");
2116         goto lend;
2117     }
2118
2119     if (getsockname(lpwfs->lstnSocket, (struct sockaddr *) &lpwfs->lstnSocketAddress, &namelen) != -1)
2120         bSuccess = TRUE;
2121
2122 lend:
2123     if (!bSuccess && lpwfs->lstnSocket == -1)
2124     {
2125         close(lpwfs->lstnSocket);
2126         lpwfs->lstnSocket = -1;
2127     }
2128
2129     return bSuccess;
2130 }
2131
2132
2133 /***********************************************************************
2134  *           FTP_SendType (internal)
2135  *
2136  * Tell server type of data being transferred
2137  *
2138  * RETURNS
2139  *   TRUE on success
2140  *   FALSE on failure
2141  *
2142  * W98SE doesn't cache the type that's currently set
2143  * (i.e. it sends it always),
2144  * so we probably don't want to do that either.
2145  */
2146 BOOL FTP_SendType(LPWININETFTPSESSIONW lpwfs, DWORD dwType)
2147 {
2148     INT nResCode;
2149     WCHAR type[] = { 'I','\0' };
2150     BOOL bSuccess = FALSE;
2151
2152     TRACE("\n");
2153     if (dwType & INTERNET_FLAG_TRANSFER_ASCII)
2154         type[0] = 'A';
2155
2156     if (!FTP_SendCommand(lpwfs->sndSocket, FTP_CMD_TYPE, type, 0, 0, 0))
2157         goto lend;
2158
2159     nResCode = FTP_ReceiveResponse(lpwfs, lpwfs->hdr.dwContext)/100;
2160     if (nResCode)
2161     {
2162         if (nResCode == 2)
2163             bSuccess = TRUE;
2164         else
2165             FTP_SetResponseError(nResCode);
2166     }
2167
2168 lend:
2169     return bSuccess;
2170 }
2171
2172 /***********************************************************************
2173  *           FTP_GetFileSize (internal)
2174  *
2175  * Retrieves from the server the size of the given file
2176  *
2177  * RETURNS
2178  *   TRUE on success
2179  *   FALSE on failure
2180  *
2181  */
2182 BOOL FTP_GetFileSize(LPWININETFTPSESSIONW lpwfs, LPCWSTR lpszRemoteFile, DWORD *dwSize)
2183 {
2184     INT nResCode;
2185     BOOL bSuccess = FALSE;
2186
2187     TRACE("\n");
2188
2189     if (!FTP_SendCommand(lpwfs->sndSocket, FTP_CMD_SIZE, lpszRemoteFile, 0, 0, 0))
2190         goto lend;
2191
2192     nResCode = FTP_ReceiveResponse(lpwfs, lpwfs->hdr.dwContext);
2193     if (nResCode)
2194     {
2195         if (nResCode == 213) {
2196             /* Now parses the output to get the actual file size */
2197             int i;
2198             LPSTR lpszResponseBuffer = INTERNET_GetResponseBuffer();
2199
2200             for (i = 0; (lpszResponseBuffer[i] != ' ') && (lpszResponseBuffer[i] != '\0'); i++) ;
2201             if (lpszResponseBuffer[i] == '\0') return FALSE;
2202             *dwSize = atol(&(lpszResponseBuffer[i + 1]));
2203             
2204             bSuccess = TRUE;
2205         } else {
2206             FTP_SetResponseError(nResCode);
2207         }
2208     }
2209
2210 lend:
2211     return bSuccess;
2212 }
2213
2214
2215 /***********************************************************************
2216  *           FTP_SendPort (internal)
2217  *
2218  * Tell server which port to use
2219  *
2220  * RETURNS
2221  *   TRUE on success
2222  *   FALSE on failure
2223  *
2224  */
2225 BOOL FTP_SendPort(LPWININETFTPSESSIONW lpwfs)
2226 {
2227     static const WCHAR szIPFormat[] = {'%','d',',','%','d',',','%','d',',','%','d',',','%','d',',','%','d','\0'};
2228     INT nResCode;
2229     WCHAR szIPAddress[64];
2230     BOOL bSuccess = FALSE;
2231     TRACE("\n");
2232
2233     sprintfW(szIPAddress, szIPFormat,
2234          lpwfs->lstnSocketAddress.sin_addr.s_addr&0x000000FF,
2235         (lpwfs->lstnSocketAddress.sin_addr.s_addr&0x0000FF00)>>8,
2236         (lpwfs->lstnSocketAddress.sin_addr.s_addr&0x00FF0000)>>16,
2237         (lpwfs->lstnSocketAddress.sin_addr.s_addr&0xFF000000)>>24,
2238         lpwfs->lstnSocketAddress.sin_port & 0xFF,
2239         (lpwfs->lstnSocketAddress.sin_port & 0xFF00)>>8);
2240
2241     if (!FTP_SendCommand(lpwfs->sndSocket, FTP_CMD_PORT, szIPAddress, 0, 0, 0))
2242         goto lend;
2243
2244     nResCode = FTP_ReceiveResponse(lpwfs, lpwfs->hdr.dwContext);
2245     if (nResCode)
2246     {
2247         if (nResCode == 200)
2248             bSuccess = TRUE;
2249         else
2250             FTP_SetResponseError(nResCode);
2251     }
2252
2253 lend:
2254     return bSuccess;
2255 }
2256
2257
2258 /***********************************************************************
2259  *           FTP_DoPassive (internal)
2260  *
2261  * Tell server that we want to do passive transfers
2262  * and connect data socket
2263  *
2264  * RETURNS
2265  *   TRUE on success
2266  *   FALSE on failure
2267  *
2268  */
2269 BOOL FTP_DoPassive(LPWININETFTPSESSIONW lpwfs)
2270 {
2271     INT nResCode;
2272     BOOL bSuccess = FALSE;
2273
2274     TRACE("\n");
2275     if (!FTP_SendCommand(lpwfs->sndSocket, FTP_CMD_PASV, NULL, 0, 0, 0))
2276         goto lend;
2277
2278     nResCode = FTP_ReceiveResponse(lpwfs, lpwfs->hdr.dwContext);
2279     if (nResCode)
2280     {
2281         if (nResCode == 227)
2282         {
2283             LPSTR lpszResponseBuffer = INTERNET_GetResponseBuffer();
2284             LPSTR p;
2285             int f[6];
2286             int i;
2287             char *pAddr, *pPort;
2288             INT nsocket = -1;
2289             struct sockaddr_in dataSocketAddress;
2290
2291             p = lpszResponseBuffer+4; /* skip status code */
2292
2293             /* do a very strict check; we can improve that later. */
2294
2295             if (strncmp(p, "Entering Passive Mode", 21))
2296             {
2297                 ERR("unknown response '%.*s', aborting\n", 21, p);
2298                 goto lend;
2299             }
2300             p += 21; /* skip string */
2301             if ((*p++ != ' ') || (*p++ != '('))
2302             {
2303                 ERR("unknown response format, aborting\n");
2304                 goto lend;
2305             }
2306
2307             if (sscanf(p, "%d,%d,%d,%d,%d,%d",  &f[0], &f[1], &f[2], &f[3],
2308                                                 &f[4], &f[5]) != 6)
2309             {
2310                 ERR("unknown response address format '%s', aborting\n", p);
2311                 goto lend;
2312             }
2313             for (i=0; i < 6; i++)
2314                 f[i] = f[i] & 0xff;
2315
2316             dataSocketAddress = lpwfs->socketAddress;
2317             pAddr = (char *)&(dataSocketAddress.sin_addr.s_addr);
2318             pPort = (char *)&(dataSocketAddress.sin_port);
2319             pAddr[0] = f[0];
2320             pAddr[1] = f[1];
2321             pAddr[2] = f[2];
2322             pAddr[3] = f[3];
2323             pPort[0] = f[4];
2324             pPort[1] = f[5];
2325
2326             nsocket = socket(AF_INET,SOCK_STREAM,0);
2327             if (nsocket == -1)
2328                 goto lend;
2329
2330             if (connect(nsocket, (struct sockaddr *)&dataSocketAddress, sizeof(dataSocketAddress)))
2331             {
2332                 ERR("can't connect passive FTP data port.\n");
2333                 goto lend;
2334             }
2335             lpwfs->pasvSocket = nsocket;
2336             bSuccess = TRUE;
2337         }
2338         else
2339             FTP_SetResponseError(nResCode);
2340     }
2341
2342 lend:
2343     return bSuccess;
2344 }
2345
2346
2347 BOOL FTP_SendPortOrPasv(LPWININETFTPSESSIONW lpwfs)
2348 {
2349     if (lpwfs->hdr.dwFlags & INTERNET_FLAG_PASSIVE)
2350     {
2351         if (!FTP_DoPassive(lpwfs))
2352             return FALSE;
2353     }
2354     else
2355     {
2356         if (!FTP_SendPort(lpwfs))
2357             return FALSE;
2358     }
2359     return TRUE;
2360 }
2361
2362
2363 /***********************************************************************
2364  *           FTP_GetDataSocket (internal)
2365  *
2366  * Either accepts an incoming data socket connection from the server
2367  * or just returns the already opened socket after a PASV command
2368  * in case of passive FTP.
2369  *
2370  *
2371  * RETURNS
2372  *   TRUE on success
2373  *   FALSE on failure
2374  *
2375  */
2376 BOOL FTP_GetDataSocket(LPWININETFTPSESSIONW lpwfs, LPINT nDataSocket)
2377 {
2378     struct sockaddr_in saddr;
2379     size_t addrlen = sizeof(struct sockaddr);
2380
2381     TRACE("\n");
2382     if (lpwfs->hdr.dwFlags & INTERNET_FLAG_PASSIVE)
2383     {
2384         *nDataSocket = lpwfs->pasvSocket;
2385     }
2386     else
2387     {
2388         *nDataSocket = accept(lpwfs->lstnSocket, (struct sockaddr *) &saddr, &addrlen);
2389         close(lpwfs->lstnSocket);
2390         lpwfs->lstnSocket = -1;
2391     }
2392     return *nDataSocket != -1;
2393 }
2394
2395
2396 /***********************************************************************
2397  *           FTP_SendData (internal)
2398  *
2399  * Send data to the server
2400  *
2401  * RETURNS
2402  *   TRUE on success
2403  *   FALSE on failure
2404  *
2405  */
2406 BOOL FTP_SendData(LPWININETFTPSESSIONW lpwfs, INT nDataSocket, HANDLE hFile)
2407 {
2408     BY_HANDLE_FILE_INFORMATION fi;
2409     DWORD nBytesRead = 0;
2410     DWORD nBytesSent = 0;
2411     DWORD nTotalSent = 0;
2412     DWORD nBytesToSend, nLen, nRC = 1;
2413     time_t s_long_time, e_long_time;
2414     LONG nSeconds;
2415     CHAR *lpszBuffer;
2416
2417     TRACE("\n");
2418     lpszBuffer = HeapAlloc(GetProcessHeap(), 0, sizeof(CHAR)*DATA_PACKET_SIZE);
2419     memset(lpszBuffer, 0, sizeof(CHAR)*DATA_PACKET_SIZE);
2420
2421     /* Get the size of the file. */
2422     GetFileInformationByHandle(hFile, &fi);
2423     time(&s_long_time);
2424
2425     do
2426     {
2427         nBytesToSend = nBytesRead - nBytesSent;
2428
2429         if (nBytesToSend <= 0)
2430         {
2431             /* Read data from file. */
2432             nBytesSent = 0;
2433             if (!ReadFile(hFile, lpszBuffer, DATA_PACKET_SIZE, &nBytesRead, 0))
2434             ERR("Failed reading from file\n");
2435
2436             if (nBytesRead > 0)
2437                 nBytesToSend = nBytesRead;
2438             else
2439                 break;
2440         }
2441
2442         nLen = DATA_PACKET_SIZE < nBytesToSend ?
2443             DATA_PACKET_SIZE : nBytesToSend;
2444         nRC  = send(nDataSocket, lpszBuffer, nLen, 0);
2445
2446         if (nRC != -1)
2447         {
2448             nBytesSent += nRC;
2449             nTotalSent += nRC;
2450         }
2451
2452         /* Do some computation to display the status. */
2453         time(&e_long_time);
2454         nSeconds = e_long_time - s_long_time;
2455         if( nSeconds / 60 > 0 )
2456         {
2457             TRACE( "%ld bytes of %ld bytes (%ld%%) in %ld min %ld sec estimated remainig time %ld sec\n",
2458             nTotalSent, fi.nFileSizeLow, nTotalSent*100/fi.nFileSizeLow, nSeconds / 60,
2459             nSeconds % 60, (fi.nFileSizeLow - nTotalSent) * nSeconds / nTotalSent );
2460         }
2461         else
2462         {
2463             TRACE( "%ld bytes of %ld bytes (%ld%%) in %ld sec estimated remainig time %ld sec\n",
2464             nTotalSent, fi.nFileSizeLow, nTotalSent*100/fi.nFileSizeLow, nSeconds,
2465             (fi.nFileSizeLow - nTotalSent) * nSeconds / nTotalSent);
2466         }
2467     } while (nRC != -1);
2468
2469     TRACE("file transfer complete!\n");
2470
2471     if(lpszBuffer != NULL)
2472         HeapFree(GetProcessHeap(), 0, lpszBuffer);
2473
2474     return nTotalSent;
2475 }
2476
2477
2478 /***********************************************************************
2479  *           FTP_SendRetrieve (internal)
2480  *
2481  * Send request to retrieve a file
2482  *
2483  * RETURNS
2484  *   Number of bytes to be received on success
2485  *   0 on failure
2486  *
2487  */
2488 DWORD FTP_SendRetrieve(LPWININETFTPSESSIONW lpwfs, LPCWSTR lpszRemoteFile, DWORD dwType)
2489 {
2490     INT nResCode;
2491     DWORD nResult = 0;
2492
2493     TRACE("\n");
2494     if (!FTP_InitListenSocket(lpwfs))
2495         goto lend;
2496
2497     if (!FTP_SendType(lpwfs, dwType))
2498         goto lend;
2499
2500     if (!FTP_SendPortOrPasv(lpwfs))
2501         goto lend;
2502
2503     if (!FTP_GetFileSize(lpwfs, lpszRemoteFile, &nResult))
2504         goto lend;
2505
2506     TRACE("Waiting to receive %ld bytes\n", nResult);
2507     
2508     if (!FTP_SendCommand(lpwfs->sndSocket, FTP_CMD_RETR, lpszRemoteFile, 0, 0, 0))
2509         goto lend;
2510
2511     nResCode = FTP_ReceiveResponse(lpwfs, lpwfs->hdr.dwContext);
2512     if ((nResCode != 125) && (nResCode != 150)) {
2513         /* That means that we got an error getting the file. */
2514         nResult = 0;
2515     }
2516
2517 lend:
2518     if (0 == nResult && lpwfs->lstnSocket != -1)
2519     {
2520         close(lpwfs->lstnSocket);
2521         lpwfs->lstnSocket = -1;
2522     }
2523
2524     return nResult;
2525 }
2526
2527
2528 /***********************************************************************
2529  *           FTP_RetrieveData  (internal)
2530  *
2531  * Retrieve data from server
2532  *
2533  * RETURNS
2534  *   TRUE on success
2535  *   FALSE on failure
2536  *
2537  */
2538 BOOL FTP_RetrieveFileData(LPWININETFTPSESSIONW lpwfs, INT nDataSocket, DWORD nBytes, HANDLE hFile)
2539 {
2540     DWORD nBytesWritten;
2541     DWORD nBytesReceived = 0;
2542     INT nRC = 0;
2543     CHAR *lpszBuffer;
2544
2545     TRACE("\n");
2546
2547     if (INVALID_HANDLE_VALUE == hFile)
2548         return FALSE;
2549
2550     lpszBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(CHAR)*DATA_PACKET_SIZE);
2551     if (NULL == lpszBuffer)
2552     {
2553         INTERNET_SetLastError(ERROR_OUTOFMEMORY);
2554         return FALSE;
2555     }
2556
2557     while (nBytesReceived < nBytes && nRC != -1)
2558     {
2559         nRC = recv(nDataSocket, lpszBuffer, DATA_PACKET_SIZE, 0);
2560         if (nRC != -1)
2561         {
2562             /* other side closed socket. */
2563             if (nRC == 0)
2564                 goto recv_end;
2565             WriteFile(hFile, lpszBuffer, nRC, &nBytesWritten, NULL);
2566             nBytesReceived += nRC;
2567         }
2568
2569         TRACE("%ld bytes of %ld (%ld%%)\r", nBytesReceived, nBytes,
2570            nBytesReceived * 100 / nBytes);
2571     }
2572
2573     TRACE("Data transfer complete\n");
2574     if (NULL != lpszBuffer)
2575         HeapFree(GetProcessHeap(), 0, lpszBuffer);
2576
2577 recv_end:
2578     return  (nRC != -1);
2579 }
2580
2581
2582 /***********************************************************************
2583  *           FTP_CloseSessionHandle (internal)
2584  *
2585  * Deallocate session handle
2586  *
2587  * RETURNS
2588  *   TRUE on success
2589  *   FALSE on failure
2590  *
2591  */
2592 static void FTP_CloseSessionHandle(LPWININETHANDLEHEADER hdr)
2593 {
2594     LPWININETFTPSESSIONW lpwfs = (LPWININETFTPSESSIONW) hdr;
2595
2596     TRACE("\n");
2597
2598     if (lpwfs->download_in_progress != NULL)
2599         lpwfs->download_in_progress->session_deleted = TRUE;
2600     
2601     if (lpwfs->sndSocket != -1)
2602         close(lpwfs->sndSocket);
2603
2604     if (lpwfs->lstnSocket != -1)
2605         close(lpwfs->lstnSocket);
2606
2607     if (lpwfs->lpszPassword)
2608         HeapFree(GetProcessHeap(), 0, lpwfs->lpszPassword);
2609
2610     if (lpwfs->lpszUserName)
2611         HeapFree(GetProcessHeap(), 0, lpwfs->lpszUserName);
2612
2613     HeapFree(GetProcessHeap(), 0, lpwfs);
2614 }
2615
2616
2617 /***********************************************************************
2618  *           FTP_CloseFindNextHandle (internal)
2619  *
2620  * Deallocate session handle
2621  *
2622  * RETURNS
2623  *   TRUE on success
2624  *   FALSE on failure
2625  *
2626  */
2627 static void FTP_CloseFindNextHandle(LPWININETHANDLEHEADER hdr)
2628 {
2629     LPWININETFINDNEXTW lpwfn = (LPWININETFINDNEXTW) hdr;
2630     INT i;
2631
2632     TRACE("\n");
2633
2634     for (i = 0; i < lpwfn->size; i++)
2635     {
2636         if (NULL != lpwfn->lpafp[i].lpszName)
2637             HeapFree(GetProcessHeap(), 0, lpwfn->lpafp[i].lpszName);
2638     }
2639
2640     HeapFree(GetProcessHeap(), 0, lpwfn->lpafp);
2641     HeapFree(GetProcessHeap(), 0, lpwfn);
2642 }
2643
2644 /***********************************************************************
2645  *           FTP_CloseFileTransferHandle (internal)
2646  *
2647  * Closes the file transfer handle. This also 'cleans' the data queue of
2648  * the 'transfer conplete' message (this is a bit of a hack though :-/ )
2649  *
2650  */
2651 static void FTP_CloseFileTransferHandle(LPWININETHANDLEHEADER hdr)
2652 {
2653     LPWININETFILE lpwh = (LPWININETFILE) hdr;
2654     LPWININETFTPSESSIONW lpwfs = (LPWININETFTPSESSIONW) lpwh->hdr.lpwhparent;
2655     INT nResCode;
2656
2657     TRACE("\n");
2658
2659     if (!lpwh->session_deleted)
2660         lpwfs->download_in_progress = NULL;
2661
2662     /* This just serves to flush the control socket of any spurrious lines written
2663        to it (like '226 Transfer complete.').
2664
2665        Wonder what to do if the server sends us an error code though...
2666     */
2667     nResCode = FTP_ReceiveResponse(lpwfs, lpwfs->hdr.dwContext);
2668     
2669     if (lpwh->nDataSocket != -1)
2670         close(lpwh->nDataSocket);
2671
2672     HeapFree(GetProcessHeap(), 0, lpwh);
2673 }
2674
2675 /***********************************************************************
2676  *           FTP_ReceiveFileList (internal)
2677  *
2678  * Read file list from server
2679  *
2680  * RETURNS
2681  *   Handle to file list on success
2682  *   NULL on failure
2683  *
2684  */
2685 HINTERNET FTP_ReceiveFileList(LPWININETFTPSESSIONW lpwfs, INT nSocket, LPCWSTR lpszSearchFile,
2686         LPWIN32_FIND_DATAW lpFindFileData, DWORD dwContext)
2687 {
2688     DWORD dwSize = 0;
2689     LPFILEPROPERTIESW lpafp = NULL;
2690     LPWININETFINDNEXTW lpwfn = NULL;
2691     HINTERNET handle = 0;
2692
2693     TRACE("(%p,%d,%s,%p,%ld)\n", lpwfs, nSocket, debugstr_w(lpszSearchFile), lpFindFileData, dwContext);
2694
2695     if (FTP_ParseDirectory(lpwfs, nSocket, lpszSearchFile, &lpafp, &dwSize))
2696     {
2697         if(lpFindFileData)
2698             FTP_ConvertFileProp(lpafp, lpFindFileData);
2699
2700         lpwfn = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WININETFINDNEXTW));
2701         if (lpwfn)
2702         {
2703             lpwfn->hdr.htype = WH_HFINDNEXT;
2704             lpwfn->hdr.lpwhparent = WININET_AddRef( &lpwfs->hdr );
2705             lpwfn->hdr.dwContext = dwContext;
2706             lpwfn->hdr.dwRefCount = 1;
2707             lpwfn->hdr.destroy = FTP_CloseFindNextHandle;
2708             lpwfn->index = 1; /* Next index is 1 since we return index 0 */
2709             lpwfn->size = dwSize;
2710             lpwfn->lpafp = lpafp;
2711
2712             handle = WININET_AllocHandle( &lpwfn->hdr );
2713         }
2714     }
2715
2716     if( lpwfn )
2717         WININET_Release( &lpwfn->hdr );
2718
2719     TRACE("Matched %ld files\n", dwSize);
2720     return handle;
2721 }
2722
2723
2724 /***********************************************************************
2725  *           FTP_ConvertFileProp (internal)
2726  *
2727  * Converts FILEPROPERTIESW struct to WIN32_FIND_DATAA
2728  *
2729  * RETURNS
2730  *   TRUE on success
2731  *   FALSE on failure
2732  *
2733  */
2734 BOOL FTP_ConvertFileProp(LPFILEPROPERTIESW lpafp, LPWIN32_FIND_DATAW lpFindFileData)
2735 {
2736     BOOL bSuccess = FALSE;
2737
2738     ZeroMemory(lpFindFileData, sizeof(WIN32_FIND_DATAW));
2739
2740     if (lpafp)
2741     {
2742         /* Convert 'Unix' time to Windows time */
2743         RtlSecondsSince1970ToTime(mktime(&lpafp->tmLastModified),
2744                                   (LARGE_INTEGER *) &(lpFindFileData->ftLastAccessTime));
2745         
2746         /* Not all fields are filled in */
2747         lpFindFileData->nFileSizeHigh = 0; /* We do not handle files bigger than 0xFFFFFFFF bytes yet :-) */
2748         lpFindFileData->nFileSizeLow = lpafp->nSize;
2749
2750         if (lpafp->bIsDirectory)
2751             lpFindFileData->dwFileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
2752
2753         if (lpafp->lpszName)
2754             strncpyW(lpFindFileData->cFileName, lpafp->lpszName, MAX_PATH);
2755
2756         bSuccess = TRUE;
2757     }
2758
2759     return bSuccess;
2760 }
2761
2762 /***********************************************************************
2763  *           FTP_ParseNextFile (internal)
2764  *
2765  * Parse the next line in file listing
2766  *
2767  * RETURNS
2768  *   TRUE on success
2769  *   FALSE on failure
2770  */
2771 BOOL FTP_ParseNextFile(INT nSocket, LPCWSTR lpszSearchFile, LPFILEPROPERTIESW lpfp)
2772 {
2773     static const char szSpace[] = " \t";
2774     DWORD nBufLen;
2775     char *pszLine;
2776     char *pszToken;
2777     char *pszTmp;
2778     BOOL found = FALSE;
2779     int i;
2780     
2781     lpfp->lpszName = NULL;
2782     do {
2783         if(!(pszLine = INTERNET_GetNextLine(nSocket, &nBufLen)))
2784             return FALSE;
2785     
2786         pszToken = strtok(pszLine, szSpace);
2787         /* ls format
2788          * <Permissions> <NoLinks> <owner>   <group> <size> <date>  <time or year> <filename>
2789          *
2790          * For instance:
2791          * drwx--s---     2         pcarrier  ens     512    Sep 28  1995           pcarrier
2792          */
2793         if(!isdigit(pszToken[0]) && 10 == strlen(pszToken)) {
2794             if(!FTP_ParsePermission(pszToken, lpfp))
2795                 lpfp->bIsDirectory = FALSE;
2796             for(i=0; i<=3; i++) {
2797               if(!(pszToken = strtok(NULL, szSpace)))
2798                   break;
2799             }
2800             if(!pszToken) continue;
2801             if(lpfp->bIsDirectory) {
2802                 TRACE("Is directory\n");
2803                 lpfp->nSize = 0;
2804             }
2805             else {
2806                 TRACE("Size: %s\n", pszToken);
2807                 lpfp->nSize = atol(pszToken);
2808             }
2809             
2810             lpfp->tmLastModified.tm_sec  = 0;
2811             lpfp->tmLastModified.tm_min  = 0;
2812             lpfp->tmLastModified.tm_hour = 0;
2813             lpfp->tmLastModified.tm_mday = 0;
2814             lpfp->tmLastModified.tm_mon  = 0;
2815             lpfp->tmLastModified.tm_year = 0;
2816             
2817             /* Determine month */
2818             pszToken = strtok(NULL, szSpace);
2819             if(!pszToken) continue;
2820             if(strlen(pszToken) >= 3) {
2821                 pszToken[3] = 0;
2822                 if((pszTmp = StrStrIA(szMonths, pszToken)))
2823                     lpfp->tmLastModified.tm_mon = ((pszTmp - szMonths) / 3)+1;
2824             }
2825             /* Determine day */
2826             pszToken = strtok(NULL, szSpace);
2827             if(!pszToken) continue;
2828             lpfp->tmLastModified.tm_mday = atoi(pszToken);
2829             /* Determine time or year */
2830             pszToken = strtok(NULL, szSpace);
2831             if(!pszToken) continue;
2832             if((pszTmp = strchr(pszToken, ':'))) {
2833                 struct tm* apTM;
2834                 time_t aTime;
2835                 *pszTmp = 0;
2836                 pszTmp++;
2837                 lpfp->tmLastModified.tm_min = atoi(pszTmp);
2838                 lpfp->tmLastModified.tm_hour = atoi(pszToken);
2839                 time(&aTime);
2840                 apTM = localtime(&aTime);
2841                 lpfp->tmLastModified.tm_year = apTM->tm_year;
2842             }
2843             else {
2844                 lpfp->tmLastModified.tm_year = atoi(pszToken) - 1900;
2845                 lpfp->tmLastModified.tm_hour = 12;
2846             }
2847             TRACE("Mod time: %02d:%02d:%02d  %02d/%02d/%02d\n",
2848                 lpfp->tmLastModified.tm_hour, lpfp->tmLastModified.tm_min, lpfp->tmLastModified.tm_sec,
2849                 (lpfp->tmLastModified.tm_year >= 100) ? lpfp->tmLastModified.tm_year - 100 : lpfp->tmLastModified.tm_year,
2850                 lpfp->tmLastModified.tm_mon, lpfp->tmLastModified.tm_mday);
2851
2852             pszToken = strtok(NULL, szSpace);
2853             if(!pszToken) continue;
2854             lpfp->lpszName = WININET_strdup_AtoW(pszToken);
2855             TRACE("File: %s\n", debugstr_w(lpfp->lpszName));
2856         }
2857         /* NT way of parsing ... :
2858             
2859                 07-13-03  08:55PM       <DIR>          sakpatch
2860                 05-09-03  06:02PM             12656686 2003-04-21bgm_cmd_e.rgz
2861         */
2862         else if(isdigit(pszToken[0]) && 8 == strlen(pszToken)) {
2863             lpfp->permissions = 0xFFFF; /* No idea, put full permission :-) */
2864             
2865             sscanf(pszToken, "%d-%d-%d",
2866                 &lpfp->tmLastModified.tm_mon,
2867                 &lpfp->tmLastModified.tm_mday,
2868                 &lpfp->tmLastModified.tm_year);
2869
2870             /* Hacky and bad Y2K protection :-) */
2871             if (lpfp->tmLastModified.tm_year < 70)
2872                 lpfp->tmLastModified.tm_year += 100;
2873             
2874             pszToken = strtok(NULL, szSpace);
2875             if(!pszToken) continue;
2876             sscanf(pszToken, "%d:%d",
2877                 &lpfp->tmLastModified.tm_hour,
2878                 &lpfp->tmLastModified.tm_min);
2879             if((pszToken[5] == 'P') && (pszToken[6] == 'M')) {
2880                 lpfp->tmLastModified.tm_hour += 12;
2881             }
2882             lpfp->tmLastModified.tm_sec = 0;
2883
2884             TRACE("Mod time: %02d:%02d:%02d  %02d/%02d/%02d\n",
2885                 lpfp->tmLastModified.tm_hour, lpfp->tmLastModified.tm_min, lpfp->tmLastModified.tm_sec,
2886                 (lpfp->tmLastModified.tm_year >= 100) ? lpfp->tmLastModified.tm_year - 100 : lpfp->tmLastModified.tm_year,
2887                 lpfp->tmLastModified.tm_mon, lpfp->tmLastModified.tm_mday);
2888             
2889             pszToken = strtok(NULL, szSpace);
2890             if(!pszToken) continue;
2891             if(!strcasecmp(pszToken, "<DIR>")) {
2892                 lpfp->bIsDirectory = TRUE;
2893                 lpfp->nSize = 0;
2894                 TRACE("Is directory\n");
2895             }
2896             else {
2897                 lpfp->bIsDirectory = FALSE;
2898                 lpfp->nSize = atol(pszToken);
2899                 TRACE("Size: %ld\n", lpfp->nSize);
2900             }
2901             
2902             pszToken = strtok(NULL, szSpace);
2903             if(!pszToken) continue;
2904             lpfp->lpszName = WININET_strdup_AtoW(pszToken);
2905             TRACE("Name: %s\n", debugstr_w(lpfp->lpszName));
2906         }
2907         /* EPLF format - http://cr.yp.to/ftp/list/eplf.html */
2908         else if(pszToken[0] == '+') {
2909             FIXME("EPLF Format not implemented\n");
2910         }
2911         
2912         if(lpfp->lpszName) {
2913             if((lpszSearchFile == NULL) ||
2914                (PathMatchSpecW(lpfp->lpszName, lpszSearchFile))) {
2915                 found = TRUE;
2916                 TRACE("Matched: %s\n", debugstr_w(lpfp->lpszName));
2917             }
2918             else {
2919                 HeapFree(GetProcessHeap(), 0, lpfp->lpszName);
2920                 lpfp->lpszName = NULL;
2921             }
2922         }
2923     } while(!found);
2924     return TRUE;
2925 }
2926
2927 /***********************************************************************
2928  *           FTP_ParseDirectory (internal)
2929  *
2930  * Parse string of directory information
2931  *
2932  * RETURNS
2933  *   TRUE on success
2934  *   FALSE on failure
2935  */
2936 BOOL FTP_ParseDirectory(LPWININETFTPSESSIONW lpwfs, INT nSocket, LPCWSTR lpszSearchFile,
2937     LPFILEPROPERTIESW *lpafp, LPDWORD dwfp)
2938 {
2939     BOOL bSuccess = TRUE;
2940     INT sizeFilePropArray = 500;/*20; */
2941     INT indexFilePropArray = -1;
2942
2943     TRACE("\n");
2944
2945     /* Allocate intial file properties array */
2946     *lpafp = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(FILEPROPERTIESW)*(sizeFilePropArray));
2947     if (!*lpafp)
2948         return FALSE;
2949
2950     do {
2951         if (indexFilePropArray+1 >= sizeFilePropArray)
2952         {
2953             LPFILEPROPERTIESW tmpafp;
2954             
2955             sizeFilePropArray *= 2;
2956             tmpafp = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, *lpafp,
2957                 sizeof(FILEPROPERTIESW)*sizeFilePropArray);
2958             if (NULL == tmpafp)
2959             {
2960                 bSuccess = FALSE;
2961                 break;
2962             }
2963
2964             *lpafp = tmpafp;
2965         }
2966         indexFilePropArray++;
2967     } while (FTP_ParseNextFile(nSocket, lpszSearchFile, &(*lpafp)[indexFilePropArray]));
2968
2969     if (bSuccess && indexFilePropArray)
2970     {
2971         if (indexFilePropArray < sizeFilePropArray - 1)
2972         {
2973             LPFILEPROPERTIESW tmpafp;
2974
2975             tmpafp = HeapReAlloc(GetProcessHeap(), 0, *lpafp,
2976                 sizeof(FILEPROPERTIESW)*indexFilePropArray);
2977             if (NULL == tmpafp)
2978                 *lpafp = tmpafp;
2979         }
2980         *dwfp = indexFilePropArray;
2981     }
2982     else
2983     {
2984         HeapFree(GetProcessHeap(), 0, *lpafp);
2985         INTERNET_SetLastError(ERROR_NO_MORE_FILES);
2986         bSuccess = FALSE;
2987     }
2988
2989     return bSuccess;
2990 }
2991
2992
2993 /***********************************************************************
2994  *           FTP_ParsePermission (internal)
2995  *
2996  * Parse permission string of directory information
2997  *
2998  * RETURNS
2999  *   TRUE on success
3000  *   FALSE on failure
3001  *
3002  */
3003 BOOL FTP_ParsePermission(LPCSTR lpszPermission, LPFILEPROPERTIESW lpfp)
3004 {
3005     BOOL bSuccess = TRUE;
3006     unsigned short nPermission = 0;
3007     INT nPos = 1;
3008     INT nLast  = 9;
3009
3010     TRACE("\n");
3011     if ((*lpszPermission != 'd') && (*lpszPermission != '-') && (*lpszPermission != 'l'))
3012     {
3013         bSuccess = FALSE;
3014         return bSuccess;
3015     }
3016
3017     lpfp->bIsDirectory = (*lpszPermission == 'd');
3018     do
3019     {
3020         switch (nPos)
3021         {
3022             case 1:
3023                 nPermission |= (*(lpszPermission+1) == 'r' ? 1 : 0) << 8;
3024                 break;
3025             case 2:
3026                 nPermission |= (*(lpszPermission+2) == 'w' ? 1 : 0) << 7;
3027                 break;
3028             case 3:
3029                 nPermission |= (*(lpszPermission+3) == 'x' ? 1 : 0) << 6;
3030                 break;
3031             case 4:
3032                 nPermission |= (*(lpszPermission+4) == 'r' ? 1 : 0) << 5;
3033                 break;
3034             case 5:
3035                 nPermission |= (*(lpszPermission+5) == 'w' ? 1 : 0) << 4;
3036                 break;
3037             case 6:
3038                 nPermission |= (*(lpszPermission+6) == 'x' ? 1 : 0) << 3;
3039                 break;
3040             case 7:
3041                 nPermission |= (*(lpszPermission+7) == 'r' ? 1 : 0) << 2;
3042                 break;
3043             case 8:
3044                 nPermission |= (*(lpszPermission+8) == 'w' ? 1 : 0) << 1;
3045                 break;
3046             case 9:
3047                 nPermission |= (*(lpszPermission+9) == 'x' ? 1 : 0);
3048                 break;
3049         }
3050         nPos++;
3051     }while (nPos <= nLast);
3052
3053     lpfp->permissions = nPermission;
3054     return bSuccess;
3055 }
3056
3057
3058 /***********************************************************************
3059  *           FTP_SetResponseError (internal)
3060  *
3061  * Set the appropriate error code for a given response from the server
3062  *
3063  * RETURNS
3064  *
3065  */
3066 DWORD FTP_SetResponseError(DWORD dwResponse)
3067 {
3068     DWORD dwCode = 0;
3069
3070     switch(dwResponse)
3071     {
3072         case 421: /* Service not available - Server may be shutting down. */
3073             dwCode = ERROR_INTERNET_TIMEOUT;
3074             break;
3075
3076         case 425: /* Cannot open data connection. */
3077             dwCode = ERROR_INTERNET_CANNOT_CONNECT;
3078             break;
3079
3080         case 426: /* Connection closed, transer aborted. */
3081             dwCode = ERROR_INTERNET_CONNECTION_ABORTED;
3082             break;
3083
3084         case 500: /* Syntax error. Command unrecognized. */
3085         case 501: /* Syntax error. Error in parameters or arguments. */
3086             dwCode = ERROR_INTERNET_INCORRECT_FORMAT;
3087             break;
3088
3089         case 530: /* Not logged in. Login incorrect. */
3090             dwCode = ERROR_INTERNET_LOGIN_FAILURE;
3091             break;
3092
3093         case 550: /* File action not taken. File not found or no access. */
3094             dwCode = ERROR_INTERNET_ITEM_NOT_FOUND;
3095             break;
3096
3097         case 450: /* File action not taken. File may be busy. */
3098         case 451: /* Action aborted. Server error. */
3099         case 452: /* Action not taken. Insufficient storage space on server. */
3100         case 502: /* Command not implemented. */
3101         case 503: /* Bad sequence of command. */
3102         case 504: /* Command not implemented for that parameter. */
3103         case 532: /* Need account for storing files */
3104         case 551: /* Requested action aborted. Page type unknown */
3105         case 552: /* Action aborted. Exceeded storage allocation */
3106         case 553: /* Action not taken. File name not allowed. */
3107
3108         default:
3109             dwCode = ERROR_INTERNET_INTERNAL_ERROR;
3110             break;
3111     }
3112
3113     INTERNET_SetLastError(dwCode);
3114     return dwCode;
3115 }