user32/tests: Capture tests should not require no active window.
[wine] / dlls / inetcomm / internettransport.c
1 /*
2  * Internet Messaging Transport Base Class
3  *
4  * Copyright 2006 Robert Shearman for CodeWeavers
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #define COBJMACROS
22
23 #include <stdarg.h>
24 #include <stdio.h>
25
26 #include "windef.h"
27 #include "winbase.h"
28 #include "winnt.h"
29 #include "winuser.h"
30 #include "winsock2.h"
31 #include "ws2tcpip.h"
32 #include "objbase.h"
33 #include "ole2.h"
34 #include "mimeole.h"
35
36 #include "wine/debug.h"
37
38 #include "inetcomm_private.h"
39
40 WINE_DEFAULT_DEBUG_CHANNEL(inetcomm);
41
42 static const WCHAR wszClassName[] = {'T','h','o','r','C','o','n','n','W','n','d','C','l','a','s','s',0};
43
44 #define IX_READ     (WM_USER + 0)
45 #define IX_READLINE (WM_USER + 1)
46 #define IX_WRITE    (WM_USER + 2)
47
48 HRESULT InternetTransport_Init(InternetTransport *This)
49 {
50     This->pCallback = NULL;
51     This->Status = IXP_DISCONNECTED;
52     This->Socket = -1;
53     This->fCommandLogging = FALSE;
54     This->fnCompletion = NULL;
55
56     return S_OK;
57 }
58
59 HRESULT InternetTransport_GetServerInfo(InternetTransport *This, LPINETSERVER pInetServer)
60 {
61     if (This->Status == IXP_DISCONNECTED)
62         return IXP_E_NOT_CONNECTED;
63
64     *pInetServer = This->ServerInfo;
65     return S_OK;
66 }
67
68 HRESULT InternetTransport_InetServerFromAccount(InternetTransport *This,
69     IImnAccount *pAccount, LPINETSERVER pInetServer)
70 {
71     FIXME("(%p, %p): stub\n", pAccount, pInetServer);
72     return E_NOTIMPL;
73 }
74
75 HRESULT InternetTransport_Connect(InternetTransport *This,
76     LPINETSERVER pInetServer, boolean fAuthenticate, boolean fCommandLogging)
77 {
78     struct addrinfo *ai;
79     struct addrinfo *ai_cur;
80     struct addrinfo hints;
81     int ret;
82     char szPort[10];
83
84     if (This->Status != IXP_DISCONNECTED)
85         return IXP_E_ALREADY_CONNECTED;
86
87     This->ServerInfo = *pInetServer;
88     This->fCommandLogging = fCommandLogging;
89
90     This->hwnd = CreateWindowW(wszClassName, wszClassName, 0, 0, 0, 0, 0, NULL, NULL, NULL, 0);
91     if (!This->hwnd)
92         return HRESULT_FROM_WIN32(GetLastError());
93     SetWindowLongPtrW(This->hwnd, GWLP_USERDATA, (LONG_PTR)This);
94
95     hints.ai_flags          = 0;
96     hints.ai_family         = PF_UNSPEC;
97     hints.ai_socktype       = SOCK_STREAM;
98     hints.ai_protocol       = IPPROTO_TCP;
99     hints.ai_addrlen        = 0;
100     hints.ai_addr           = NULL;
101     hints.ai_canonname      = NULL;
102     hints.ai_next           = NULL;
103
104     snprintf(szPort, sizeof(szPort), "%d", (unsigned short)pInetServer->dwPort);
105
106     InternetTransport_ChangeStatus(This, IXP_FINDINGHOST);
107
108     ret = getaddrinfo(pInetServer->szServerName, szPort, &hints, &ai);
109     if (ret)
110     {
111         ERR("getaddrinfo failed: %d\n", ret);
112         return IXP_E_CANT_FIND_HOST;
113     }
114
115     for (ai_cur = ai; ai_cur; ai_cur = ai->ai_next)
116     {
117         int so;
118
119         if (TRACE_ON(inetcomm))
120         {
121             char host[256];
122             char service[256];
123             getnameinfo(ai_cur->ai_addr, ai_cur->ai_addrlen,
124                 host, sizeof(host), service, sizeof(service),
125                 NI_NUMERICHOST | NI_NUMERICSERV);
126             TRACE("trying %s:%s\n", host, service);
127         }
128
129         InternetTransport_ChangeStatus(This, IXP_CONNECTING);
130
131         so = socket(ai_cur->ai_family, ai_cur->ai_socktype, ai_cur->ai_protocol);
132         if (so == -1)
133         {
134             WARN("socket() failed\n");
135             continue;
136         }
137         This->Socket = so;
138
139         /* FIXME: set to async */
140
141         if (0 > connect(This->Socket, ai_cur->ai_addr, ai_cur->ai_addrlen))
142         {
143             WARN("connect() failed\n");
144             closesocket(This->Socket);
145             continue;
146         }
147         InternetTransport_ChangeStatus(This, IXP_CONNECTED);
148
149         /* FIXME: call WSAAsyncSelect */
150
151         freeaddrinfo(ai);
152         TRACE("connected\n");
153         return S_OK;
154     }
155
156     freeaddrinfo(ai);
157
158     return IXP_E_CANT_FIND_HOST;
159 }
160
161 HRESULT InternetTransport_HandsOffCallback(InternetTransport *This)
162 {
163     if (!This->pCallback)
164         return S_FALSE;
165
166     ITransportCallback_Release(This->pCallback);
167     This->pCallback = NULL;
168
169     return S_OK;
170 }
171
172 HRESULT InternetTransport_DropConnection(InternetTransport *This)
173 {
174     int ret;
175
176     if (This->Status == IXP_DISCONNECTED)
177         return IXP_E_NOT_CONNECTED;
178
179     ret = shutdown(This->Socket, SD_BOTH);
180
181     ret = closesocket(This->Socket);
182
183     DestroyWindow(This->hwnd);
184     This->hwnd = NULL;
185
186     InternetTransport_ChangeStatus(This, IXP_DISCONNECTED);
187
188     return S_OK;
189 }
190
191 HRESULT InternetTransport_GetStatus(InternetTransport *This,
192     IXPSTATUS *pCurrentStatus)
193 {
194     *pCurrentStatus = This->Status;
195     return S_OK;
196 }
197
198 HRESULT InternetTransport_ChangeStatus(InternetTransport *This, IXPSTATUS Status)
199 {
200     This->Status = Status;
201     if (This->pCallback)
202         ITransportCallback_OnStatus(This->pCallback, Status,
203             (IInternetTransport *)&This->u.vtbl);
204     return S_OK;
205 }
206
207 HRESULT InternetTransport_Read(InternetTransport *This, int cbBuffer,
208     INETXPORT_COMPLETION_FUNCTION fnCompletion)
209 {
210     if (This->Status == IXP_DISCONNECTED)
211         return IXP_E_NOT_CONNECTED;
212
213     if (This->fnCompletion)
214         return IXP_E_BUSY;
215
216     This->fnCompletion = fnCompletion;
217
218     This->cbBuffer = cbBuffer;
219     This->pBuffer = HeapAlloc(GetProcessHeap(), 0, This->cbBuffer);
220     This->iCurrentBufferOffset = 0;
221
222     if (WSAAsyncSelect(This->Socket, This->hwnd, IX_READ, FD_READ) == SOCKET_ERROR)
223     {
224         ERR("WSAAsyncSelect failed with error %d\n", WSAGetLastError());
225         /* FIXME: handle error */
226     }
227     return S_OK;
228 }
229
230 HRESULT InternetTransport_ReadLine(InternetTransport *This,
231     INETXPORT_COMPLETION_FUNCTION fnCompletion)
232 {
233     if (This->Status == IXP_DISCONNECTED)
234         return IXP_E_NOT_CONNECTED;
235
236     if (This->fnCompletion)
237         return IXP_E_BUSY;
238
239     This->fnCompletion = fnCompletion;
240
241     This->cbBuffer = 1024;
242     This->pBuffer = HeapAlloc(GetProcessHeap(), 0, This->cbBuffer);
243     This->iCurrentBufferOffset = 0;
244
245     if (WSAAsyncSelect(This->Socket, This->hwnd, IX_READLINE, FD_READ) == SOCKET_ERROR)
246     {
247         ERR("WSAAsyncSelect failed with error %d\n", WSAGetLastError());
248         /* FIXME: handle error */
249     }
250     return S_OK;
251 }
252
253 HRESULT InternetTransport_Write(InternetTransport *This, const char *pvData,
254     int cbSize, INETXPORT_COMPLETION_FUNCTION fnCompletion)
255 {
256     int ret;
257
258     if (This->Status == IXP_DISCONNECTED)
259         return IXP_E_NOT_CONNECTED;
260
261     if (This->fnCompletion)
262         return IXP_E_BUSY;
263
264     /* FIXME: do this asynchronously */
265     ret = send(This->Socket, pvData, cbSize, 0);
266     if (ret == SOCKET_ERROR)
267     {
268         ERR("send failed with error %d\n", WSAGetLastError());
269         /* FIXME: handle error */
270     }
271
272     fnCompletion((IInternetTransport *)&This->u.vtbl, NULL, 0);
273
274     return S_OK;
275 }
276
277 HRESULT InternetTransport_DoCommand(InternetTransport *This,
278     LPCSTR pszCommand, INETXPORT_COMPLETION_FUNCTION fnCompletion)
279 {
280     if (This->Status == IXP_DISCONNECTED)
281         return IXP_E_NOT_CONNECTED;
282
283     if (This->fnCompletion)
284         return IXP_E_BUSY;
285
286     if (This->pCallback && This->fCommandLogging)
287     {
288         ITransportCallback_OnCommand(This->pCallback, CMD_SEND, (LPSTR)pszCommand, 0,
289             (IInternetTransport *)&This->u.vtbl);
290     }
291     return InternetTransport_Write(This, pszCommand, strlen(pszCommand), fnCompletion);
292 }
293
294 static LRESULT CALLBACK InternetTransport_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
295 {
296     if (uMsg == IX_READ)
297     {
298         InternetTransport *This = (InternetTransport *)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
299
300         /* no work to do */
301         if (!This->fnCompletion)
302             return 0;
303
304         while (This->iCurrentBufferOffset < This->cbBuffer)
305         {
306             if (recv(This->Socket, &This->pBuffer[This->iCurrentBufferOffset], 1, 0) <= 0)
307             {
308                 if (WSAGetLastError() == WSAEWOULDBLOCK)
309                     break;
310
311                 ERR("recv failed with error %d\n", WSAGetLastError());
312                 /* FIXME: handle error */
313             }
314
315             This->iCurrentBufferOffset++;
316         }
317         if (This->iCurrentBufferOffset == This->cbBuffer)
318         {
319             INETXPORT_COMPLETION_FUNCTION fnCompletion = This->fnCompletion;
320             char *pBuffer;
321
322             This->fnCompletion = NULL;
323             pBuffer = This->pBuffer;
324             This->pBuffer = NULL;
325             fnCompletion((IInternetTransport *)&This->u.vtbl, pBuffer,
326                 This->iCurrentBufferOffset);
327             HeapFree(GetProcessHeap(), 0, pBuffer);
328             return 0;
329         }
330
331         if (WSAAsyncSelect(This->Socket, hwnd, uMsg, FD_READ) == SOCKET_ERROR)
332         {
333             ERR("WSAAsyncSelect failed with error %d\n", WSAGetLastError());
334             /* FIXME: handle error */
335         }
336         return 0;
337     }
338     else if (uMsg == IX_READLINE)
339     {
340         InternetTransport *This = (InternetTransport *)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
341
342         /* no work to do */
343         if (!This->fnCompletion)
344             return 0;
345
346         while (This->iCurrentBufferOffset < This->cbBuffer - 1)
347         {
348             fd_set infd;
349
350             if (recv(This->Socket, &This->pBuffer[This->iCurrentBufferOffset], 1, 0) <= 0)
351             {
352                 if (WSAGetLastError() == WSAEWOULDBLOCK)
353                     break;
354
355                 ERR("recv failed with error %d\n", WSAGetLastError());
356                 /* FIXME: handle error */
357                 return 0;
358             }
359
360             if (This->pBuffer[This->iCurrentBufferOffset] == '\n')
361             {
362                 INETXPORT_COMPLETION_FUNCTION fnCompletion = This->fnCompletion;
363                 char *pBuffer;
364
365                 This->fnCompletion = NULL;
366                 This->pBuffer[This->iCurrentBufferOffset++] = '\0';
367                 pBuffer = This->pBuffer;
368                 This->pBuffer = NULL;
369
370                 fnCompletion((IInternetTransport *)&This->u.vtbl, pBuffer,
371                     This->iCurrentBufferOffset);
372
373                 HeapFree(GetProcessHeap(), 0, pBuffer);
374                 return 0;
375             }
376             if (This->pBuffer[This->iCurrentBufferOffset] != '\r')
377                 This->iCurrentBufferOffset++;
378
379             FD_ZERO(&infd);
380             FD_SET(This->Socket, &infd);
381         }
382         if (This->iCurrentBufferOffset == This->cbBuffer - 1)
383             return 0;
384
385         if (WSAAsyncSelect(This->Socket, hwnd, uMsg, FD_READ) == SOCKET_ERROR)
386         {
387             ERR("WSAAsyncSelect failed with error %d\n", WSAGetLastError());
388             /* FIXME: handle error */
389         }
390         return 0;
391     }
392     else
393         return DefWindowProcW(hwnd, uMsg, wParam, lParam);
394 }
395
396 BOOL InternetTransport_RegisterClass(HINSTANCE hInstance)
397 {
398     WNDCLASSW cls;
399     WSADATA wsadata;
400
401     if (WSAStartup(MAKEWORD(2, 2), &wsadata))
402         return FALSE;
403
404     memset(&cls, 0, sizeof(cls));
405     cls.hInstance     = hInstance;
406     cls.lpfnWndProc   = InternetTransport_WndProc;
407     cls.lpszClassName = wszClassName;
408
409     return RegisterClassW(&cls);
410 }
411
412 void InternetTransport_UnregisterClass(HINSTANCE hInstance)
413 {
414     UnregisterClassW(wszClassName, hInstance);
415     WSACleanup();
416 }