send.c 16.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*
 * HTTP handling functions.
 *
 * Copyright 2003 Ferenc Wagner
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * 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
#include <winsock2.h>
22
#include <wininet.h>
23
#include <stdio.h>
24
#include <errno.h>
25 26 27

#include "winetest.h"

28 29 30 31 32 33 34 35 36 37 38 39 40 41
#define USER_AGENT  "Winetest Shell"
#define SERVER_NAME "test.winehq.org"
#define URL_PATH "/submit"
#define SEP "--8<--cut-here--8<--"
#define CONTENT_HEADERS "Content-Type: multipart/form-data; boundary=\"" SEP "\"\r\n" \
                        "Content-Length: %u\r\n\r\n"
static const char body1[] = "--" SEP "\r\n"
    "Content-Disposition: form-data; name=\"reportfile\"; filename=\"%s\"\r\n"
    "Content-Type: application/octet-stream\r\n\r\n";
static const char body2[] = "\r\n--" SEP "\r\n"
    "Content-Disposition: form-data; name=\"submit\"\r\n\r\n"
    "Upload File\r\n"
    "--" SEP "--\r\n";

42
static SOCKET
43
open_http (const char *server)
44 45 46 47 48
{
    WSADATA wsad;
    struct sockaddr_in sa;
    SOCKET s;

49
    report (R_STATUS, "Opening HTTP connection to %s", server);
50 51
    if (WSAStartup (MAKEWORD (2,2), &wsad)) return INVALID_SOCKET;

52 53 54
    sa.sin_family = AF_INET;
    sa.sin_port = htons (80);
    sa.sin_addr.s_addr = inet_addr (server);
55
    memset (sa.sin_zero, 0, sizeof(sa.sin_zero));
56 57 58 59 60
    if (sa.sin_addr.s_addr == INADDR_NONE) {
        struct hostent *host = gethostbyname (server);
        if (!host) {
            report (R_ERROR, "Hostname lookup failed for %s", server);
            goto failure;
61
        }
62 63 64 65 66 67 68
        sa.sin_addr.s_addr = ((struct in_addr *)host->h_addr)->s_addr;
    }
    s = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (s == INVALID_SOCKET) {
        report (R_ERROR, "Can't open network socket: %d",
                WSAGetLastError ());
        goto failure;
69
    }
70 71 72 73 74 75
    if (!connect (s, (struct sockaddr*)&sa, sizeof (struct sockaddr_in)))
        return s;

    report (R_ERROR, "Can't connect: %d", WSAGetLastError ());
    closesocket (s);
 failure:
76 77 78 79
    WSACleanup ();
    return INVALID_SOCKET;
}

80
static int
81 82 83 84 85 86 87 88
close_http (SOCKET s)
{
    int ret;

    ret = closesocket (s);
    return (WSACleanup () || ret);
}

89
static int
90 91 92 93 94 95
send_buf (SOCKET s, const char *buf, size_t length)
{
    int sent;

    while (length > 0) {
        sent = send (s, buf, length, 0);
96 97 98 99
        if (sent == SOCKET_ERROR) {
            if (errno == EINTR) continue;
            return 1;
        }
100 101 102 103 104 105
        buf += sent;
        length -= sent;
    }
    return 0;
}

106
static int
107
send_str (SOCKET s, ...)
108 109 110 111 112 113
{
    va_list ap;
    char *p;
    int ret;
    size_t len;

114 115
    va_start (ap, s);
    p = vstrmake (&len, ap);
116 117 118
    va_end (ap);
    if (!p) return 1;
    ret = send_buf (s, p, len);
119
    heap_free (p);
120 121 122
    return ret;
}

123
static int
124
send_file_direct (const char * url, const char *name)
125 126
{
    SOCKET s;
127
    HANDLE file;
128
#define BUFLEN 8192
Mike McCormack's avatar
Mike McCormack committed
129
    char buffer[BUFLEN+1];
130
    DWORD bytes_read, filesize;
131
    size_t total, count;
132 133 134
    char *str;
    int ret;

135
    /* RFC 2616 */
136 137 138 139 140 141
    static const char head[] = "POST " URL_PATH " HTTP/1.0\r\n"
        "Host: " SERVER_NAME "\r\n"
        "User-Agent: " USER_AGENT "\r\n"
        CONTENT_HEADERS
        "\r\n";

142 143 144 145 146
    if (url) {
        report (R_WARNING, "Can't submit to an alternative URL");
        return 0;
    }

147
    s = open_http (SERVER_NAME);
148
    if (s == INVALID_SOCKET) return 1;
149

150 151 152
    file = CreateFileA( name, GENERIC_READ,
                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                        NULL, OPEN_EXISTING, 0, NULL );
153 154 155 156 157 158 159 160

    if ((file == INVALID_HANDLE_VALUE) &&
        (GetLastError() == ERROR_INVALID_PARAMETER)) {
        /* FILE_SHARE_DELETE not supported on win9x */
        file = CreateFileA( name, GENERIC_READ,
                            FILE_SHARE_READ | FILE_SHARE_WRITE,
                            NULL, OPEN_EXISTING, 0, NULL );
    }
161 162 163
    if (file == INVALID_HANDLE_VALUE)
    {
        report (R_WARNING, "Can't open file '%s': %u", name, GetLastError());
164 165
        goto abort1;
    }
166
    filesize = GetFileSize( file, NULL );
167
    if (filesize > 1.5*1024*1024) {
168
        report (R_WARNING,
169
                "File too big (%.1f MB > 1.5 MB); submitting partial report.",
170
                filesize/1024.0/1024);
171
        filesize = (DWORD) 1.5*1024*1024;
172
    }
173

174
    report (R_STATUS, "Sending header");
175 176 177
    str = strmake (&total, body1, name);
    ret = send_str (s, head, filesize + total + sizeof body2 - 1) ||
        send_buf (s, str, total);
178
    heap_free (str);
179
    if (ret) {
180 181
        report (R_WARNING, "Error sending header: %d, %d",
                errno, WSAGetLastError ());
182 183 184
        goto abort2;
    }

185
    report (R_STATUS, "Sending %u bytes of data", filesize);
186
    report (R_PROGRESS, 2, filesize);
187
    total = 0;
188
    while (total < filesize && ReadFile( file, buffer, BUFLEN/2, &bytes_read, NULL )) {
189
        if (aborting) goto abort2;
190
        if (!bytes_read) break;
191 192
        total += bytes_read;
        if (total > filesize) bytes_read -= total - filesize;
193
        if (send_buf (s, buffer, bytes_read)) {
194 195
            report (R_WARNING, "Error sending body: %d, %d",
                    errno, WSAGetLastError ());
196 197
            goto abort2;
        }
198 199
        report (R_DELTA, bytes_read, "Network transfer: In progress");
    }
200
    CloseHandle( file );
201 202

    if (send_buf (s, body2, sizeof body2 - 1)) {
203 204
        report (R_WARNING, "Error sending trailer: %d, %d",
                errno, WSAGetLastError ());
205
        goto abort1;
206
    }
207
    report (R_DELTA, 0, "Network transfer: Done");
208 209

    total = 0;
210
    while ((bytes_read = recv (s, buffer+total, BUFLEN-total, 0))) {
211
        if ((signed)bytes_read == SOCKET_ERROR) {
212
            if (errno == EINTR) continue;
213 214
            report (R_WARNING, "Error receiving reply: %d, %d",
                    errno, WSAGetLastError ());
215 216 217
            goto abort1;
        }
        total += bytes_read;
218
        if (total == BUFLEN) {
219
            report (R_WARNING, "Buffer overflow");
220 221 222 223
            goto abort1;
        }
    }
    if (close_http (s)) {
224 225
        report (R_WARNING, "Error closing connection: %d, %d",
                errno, WSAGetLastError ());
226 227 228
        return 1;
    }

229
    str = strmake (&count, "Received %s (%d bytes).\n",
230
                   name, filesize);
231
    ret = total < count || memcmp (str, buffer + total - count, count) != 0;
232
    heap_free (str);
233 234 235
    if (ret) {
        buffer[total] = 0;
        str = strstr (buffer, "\r\n\r\n");
236 237
        if (!str) str = buffer;
        else str = str + 4;
238
        report (R_ERROR, "Can't submit logfile '%s'. "
239
                "Server response: %s", name, str);
240 241
    }
    return ret;
242 243

 abort2:
244
    CloseHandle( file );
245 246 247 248
 abort1:
    close_http (s);
    return 1;
}
249 250

static int
251
send_file_wininet (const char *url, const char *name)
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
{
    int ret = 0;
    HMODULE wininet_mod = NULL;
    HINTERNET (WINAPI *pInternetOpen)(LPCSTR agent, DWORD access_type, LPCSTR proxy_name, LPCSTR proxy_bypass, DWORD flags);
    HINTERNET (WINAPI *pInternetConnect)(HINTERNET session, LPCSTR server_name, INTERNET_PORT server_port, LPCSTR username, LPCSTR password, DWORD service, DWORD flags, DWORD_PTR *context);
    HINTERNET (WINAPI *pHttpOpenRequest)(HINTERNET connection, LPCSTR verb, LPCSTR object_name, LPCSTR version, LPCSTR referer, LPCSTR *accept_types, DWORD flags, DWORD_PTR context);
    BOOL (WINAPI *pHttpSendRequestEx)(HINTERNET request, LPINTERNET_BUFFERSA buffers_in, LPINTERNET_BUFFERSA buffers_out, DWORD flags, DWORD_PTR context);
    BOOL (WINAPI *pInternetWriteFile)(HINTERNET file, LPCVOID buffer, DWORD number_of_bytes_to_write, LPDWORD number_of_bytes_written);
    BOOL (WINAPI *pHttpEndRequest)(HINTERNET request, LPINTERNET_BUFFERSA buffers_out, DWORD flags, DWORD_PTR context);
    BOOL (WINAPI *pInternetReadFile)(HINTERNET file, LPCVOID buffer, DWORD number_of_bytes_to_read, LPDWORD number_of_bytes_read);
    BOOL (WINAPI *pInternetCloseHandle)(HINTERNET Handle) = NULL;
    HANDLE file = INVALID_HANDLE_VALUE;
    DWORD filesize, bytes_read, bytes_written;
    size_t total, count;
    char *str = NULL;
    HINTERNET session = NULL;
    HINTERNET connection = NULL;
    HINTERNET request = NULL;
    INTERNET_BUFFERSA buffers_in = { 0 };
    char buffer[BUFLEN+1];
272
    URL_COMPONENTSA uc = { 0 };
273 274 275 276

    static const char extra_headers[] =
        CONTENT_HEADERS;

277
    wininet_mod = LoadLibraryA("wininet.dll");
278 279 280 281 282 283 284 285 286 287 288 289 290 291
    if (wininet_mod == NULL)
        goto done;
    pInternetOpen = (void *)GetProcAddress(wininet_mod, "InternetOpenA");
    pInternetConnect = (void *)GetProcAddress(wininet_mod, "InternetConnectA");
    pHttpOpenRequest = (void *)GetProcAddress(wininet_mod, "HttpOpenRequestA");
    pHttpSendRequestEx = (void *)GetProcAddress(wininet_mod, "HttpSendRequestExA");
    pInternetWriteFile = (void *)GetProcAddress(wininet_mod, "InternetWriteFile");
    pHttpEndRequest = (void *)GetProcAddress(wininet_mod, "HttpEndRequestA");
    pInternetReadFile = (void *)GetProcAddress(wininet_mod, "InternetReadFile");
    pInternetCloseHandle = (void *)GetProcAddress(wininet_mod, "InternetCloseHandle");
    if (pInternetOpen == NULL || pInternetConnect == NULL || pHttpOpenRequest == NULL || pHttpSendRequestEx == NULL || pHttpEndRequest == NULL ||
        pInternetWriteFile == NULL || pInternetReadFile == NULL || pInternetCloseHandle == NULL) {
        goto done;
    }
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
    if (url) {
        BOOL (WINAPI *pInternetCrackUrlA)(const char *url, DWORD url_length, DWORD flags, URL_COMPONENTSA *ret_comp);
        pInternetCrackUrlA = (void *)GetProcAddress(wininet_mod, "InternetCrackUrlA");
        if (pInternetCrackUrlA == NULL)
            goto done;
        uc.dwStructSize = sizeof(uc);
        uc.dwSchemeLength = uc.dwHostNameLength = uc.dwUserNameLength =
            uc.dwPasswordLength = uc.dwUrlPathLength = uc.dwExtraInfoLength =
            strlen(url) + 1;
        uc.lpszScheme = heap_alloc (uc.dwSchemeLength);
        uc.lpszHostName = heap_alloc (uc.dwHostNameLength);
        uc.lpszUserName = heap_alloc (uc.dwUserNameLength);
        uc.lpszPassword = heap_alloc (uc.dwPasswordLength);
        uc.lpszUrlPath = heap_alloc (uc.dwUrlPathLength);
        uc.lpszExtraInfo = heap_alloc (uc.dwExtraInfoLength);
        if (!pInternetCrackUrlA(url, 0, 0, &uc)) {
            report (R_WARNING, "Can't parse submit URL '%s'", url);
            goto done;
        }
        if ((uc.nScheme != INTERNET_SCHEME_HTTP && uc.nScheme != INTERNET_SCHEME_HTTPS) || *uc.lpszExtraInfo) {
            report (R_WARNING, "Can't submit report to scheme %s or extra info '%s'", uc.lpszScheme, uc.lpszExtraInfo);
            goto done;
        }

    } else {
        uc.nScheme = INTERNET_SCHEME_HTTP;
        uc.lpszHostName = heap_strdup (SERVER_NAME);
        uc.nPort = INTERNET_DEFAULT_HTTP_PORT;
        uc.lpszUserName = heap_strdup ("");
        uc.lpszPassword = heap_strdup ("");
        uc.lpszUrlPath = heap_strdup (URL_PATH);
    }
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347

    ret = 1;

    file = CreateFileA( name, GENERIC_READ,
                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                        NULL, OPEN_EXISTING, 0, NULL );

    if ((file == INVALID_HANDLE_VALUE) &&
        (GetLastError() == ERROR_INVALID_PARAMETER)) {
        /* FILE_SHARE_DELETE not supported on win9x */
        file = CreateFileA( name, GENERIC_READ,
                            FILE_SHARE_READ | FILE_SHARE_WRITE,
                            NULL, OPEN_EXISTING, 0, NULL );
    }
    if (file == INVALID_HANDLE_VALUE) {
        report (R_WARNING, "Can't open file '%s': %u", name, GetLastError());
        goto done;
    }

    filesize = GetFileSize( file, NULL );
    if (filesize > 1.5*1024*1024) {
        report (R_WARNING,
                "File too big (%.1f MB > 1.5 MB); submitting partial report.",
                filesize/1024.0/1024);
André Hentschel's avatar
André Hentschel committed
348
        filesize = 1.5*1024*1024;
349 350
    }

351 352 353
    report (R_STATUS, "Opening %s connection to %s:%d",
            (uc.nScheme == INTERNET_SCHEME_HTTP ? "http" : "https"),
            uc.lpszHostName, uc.nPort);
354 355 356 357 358
    session = pInternetOpen (USER_AGENT, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
    if (session == NULL) {
        report (R_WARNING, "Unable to open connection, error %u", GetLastError());
        goto done;
    }
359
    connection = pInternetConnect (session, uc.lpszHostName, uc.nPort, uc.lpszUserName, uc.lpszPassword, INTERNET_SERVICE_HTTP, 0, 0);
360 361 362 363
    if (connection == NULL) {
        report (R_WARNING, "Unable to connect, error %u", GetLastError());
        goto done;
    }
364
    request = pHttpOpenRequest (connection, "POST", uc.lpszUrlPath, NULL, NULL, NULL,
365
                                INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_NO_UI |
366
                                INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_RELOAD | (uc.nScheme == INTERNET_SCHEME_HTTPS ? INTERNET_FLAG_SECURE : 0), 0);
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
    if (request == NULL) {
        report (R_WARNING, "Unable to open request, error %u", GetLastError());
        goto done;
    }

    report (R_STATUS, "Sending request");
    str = strmake (&total, body1, name);
    memset(&buffers_in, 0, sizeof(INTERNET_BUFFERSA));
    buffers_in.dwStructSize = sizeof(INTERNET_BUFFERSA);
    buffers_in.dwBufferTotal = filesize + total + sizeof body2 - 1;
    buffers_in.lpcszHeader = strmake (&count, extra_headers, buffers_in.dwBufferTotal);
    buffers_in.dwHeadersLength = count;
    if (! pHttpSendRequestEx(request, &buffers_in, NULL, 0, 0)) {
        report (R_WARNING, "Unable to send request, error %u", GetLastError());
        goto done;
    }

    if (! pInternetWriteFile(request, str, total, &bytes_written) || bytes_written != total) {
        report (R_WARNING, "Unable to write body data, error %u", GetLastError());
        goto done;
    }

    report (R_STATUS, "Sending %u bytes of data", filesize);
    report (R_PROGRESS, 2, filesize);
    total = 0;
    while (total < filesize && ReadFile( file, buffer, BUFLEN/2, &bytes_read, NULL )) {
393
        if (aborting) goto done;
394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
        if (!bytes_read) break;
        total += bytes_read;
        if (total > filesize) bytes_read -= total - filesize;
        if (! pInternetWriteFile (request, buffer, bytes_read, &bytes_written) || bytes_written != bytes_read) {
            report (R_WARNING, "Error sending body: %u", GetLastError ());
            goto done;
        }
        report (R_DELTA, bytes_read, "Network transfer: In progress");
    }

    if (! pInternetWriteFile(request, body2, sizeof body2 - 1, &bytes_written) || bytes_written != sizeof body2 - 1) {
        report (R_WARNING, "Unable to write final body data, error %u", GetLastError());
        goto done;
    }
    if (! pHttpEndRequest(request, NULL, 0, 0)) {
        report (R_WARNING, "Unable to end request, error %u", GetLastError());
        goto done;
    }
    report (R_DELTA, 0, "Network transfer: Done");

    total = 0;
    do
    {
        if (! pInternetReadFile(request, buffer+total, BUFLEN-total, &bytes_read)) {
            report (R_WARNING, "Error receiving reply: %u", GetLastError ());
            goto done;
        }
        total += bytes_read;
        if (total == BUFLEN) {
            report (R_WARNING, "Buffer overflow");
            goto done;
        }
    }
    while (bytes_read != 0);

    heap_free (str);
    str = strmake (&count, "Received %s (%d bytes).\n",
                   name, filesize);
    if (total < count || memcmp (str, buffer + total - count, count) != 0) {
        buffer[total] = 0;
        report (R_ERROR, "Can't submit logfile '%s'. "
                "Server response: %s", name, buffer);
    }

 done:
439 440 441 442 443 444
    heap_free (uc.lpszScheme);
    heap_free (uc.lpszHostName);
    heap_free (uc.lpszUserName);
    heap_free (uc.lpszPassword);
    heap_free (uc.lpszUrlPath);
    heap_free (uc.lpszExtraInfo);
445 446
    heap_free((void *)buffers_in.lpcszHeader);
    heap_free(str);
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461
    if (pInternetCloseHandle != NULL && request != NULL)
        pInternetCloseHandle (request);
    if (pInternetCloseHandle != NULL && connection != NULL)
        pInternetCloseHandle (connection);
    if (pInternetCloseHandle != NULL && session != NULL)
        pInternetCloseHandle (session);
    if (file != INVALID_HANDLE_VALUE)
        CloseHandle (file);
    if (wininet_mod != NULL)
        FreeLibrary (wininet_mod);

    return ret;
}

int
462
send_file (const char *url, const char *name)
463
{
464
    return send_file_wininet( url, name ) || send_file_direct( url, name );
465
}