mirror of https://github.com/BOINC/boinc.git
1134 lines
36 KiB
C
1134 lines
36 KiB
C
// This file is part of BOINC.
|
|
// http://boinc.berkeley.edu
|
|
// Copyright (C) 2008 University of California
|
|
//
|
|
// BOINC 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 3 of the License, or (at your option) any later version.
|
|
//
|
|
// BOINC 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 BOINC. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#include "cpp.h"
|
|
|
|
#ifdef _WIN32
|
|
#include "boinc_win.h"
|
|
#else
|
|
#include "config.h"
|
|
#include <cstring>
|
|
#include <sstream>
|
|
#include <algorithm>
|
|
#include <sys/stat.h>
|
|
#include <cerrno>
|
|
#include <unistd.h>
|
|
#ifdef HAVE_SYS_SOCKET_H
|
|
#include <sys/socket.h>
|
|
#endif
|
|
#endif
|
|
|
|
#include "error_numbers.h"
|
|
#include "filesys.h"
|
|
#include "client_msgs.h"
|
|
#include "log_flags.h"
|
|
#include "str_util.h"
|
|
#include "util.h"
|
|
|
|
#include "network.h"
|
|
#include "file_names.h"
|
|
#include "client_msgs.h"
|
|
#include "base64.h"
|
|
#include "http_curl.h"
|
|
#include "client_state.h"
|
|
|
|
using std::min;
|
|
using std::vector;
|
|
|
|
static CURLM* g_curlMulti = NULL;
|
|
|
|
static char g_user_agent_string[256] = {""};
|
|
static const char g_content_type[] = {"Content-Type: application/x-www-form-urlencoded"};
|
|
|
|
|
|
// Breaks a HTTP URL down into its server, port and file components
|
|
// format of url:
|
|
// [http[s]://]host.dom.dom[:port][/dir/file]
|
|
//
|
|
void parse_url(const char* url, char* host, int &port, char* file) {
|
|
char* p;
|
|
char buf[256];
|
|
bool bSSL = false;
|
|
|
|
//const short csiLen = bSSL ? 8 : 7; // get right size of http/s string comparator
|
|
|
|
// strip off http:// if present
|
|
//
|
|
//if (strncmp(url, (bSSL ? "https://" : "http://"), csiLen) == 0) {
|
|
if (strncmp(url, "http://", 7) == 0) {
|
|
safe_strcpy(buf, url+7);
|
|
} else {
|
|
// wait, may be https://
|
|
if (strncmp(url, "https://", 8) == 0) {
|
|
safe_strcpy(buf, url+8);
|
|
bSSL = true; // retain the fact that this was a secure http url
|
|
}
|
|
else { // no http:// or https:// prepended on url
|
|
safe_strcpy(buf, url);
|
|
}
|
|
}
|
|
|
|
// parse and strip off file part if present
|
|
//
|
|
p = strchr(buf, '/');
|
|
if (p) {
|
|
strcpy(file, p+1);
|
|
*p = 0;
|
|
} else {
|
|
strcpy(file, "");
|
|
}
|
|
|
|
// parse and strip off port if present
|
|
//
|
|
p = strchr(buf,':');
|
|
if (p) {
|
|
port = atol(p+1);
|
|
*p = 0;
|
|
} else {
|
|
// CMC note: if they didn't pass in a port #,
|
|
// but the url starts with https://, assume they
|
|
// want a secure port (HTTPS, port 443)
|
|
port = (bSSL ? 443 : 80);
|
|
}
|
|
|
|
// what remains is the host
|
|
//
|
|
strcpy(host, buf);
|
|
}
|
|
|
|
void get_user_agent_string() {
|
|
sprintf(g_user_agent_string, "BOINC client (%s %d.%d.%d)",
|
|
gstate.get_primary_platform(),
|
|
BOINC_MAJOR_VERSION, BOINC_MINOR_VERSION, BOINC_RELEASE
|
|
);
|
|
}
|
|
|
|
void HTTP_OP::init() {
|
|
reset();
|
|
start_time = gstate.now;
|
|
start_bytes_xferred = 0;
|
|
}
|
|
|
|
void HTTP_OP::reset() {
|
|
req1 = NULL;
|
|
strcpy(infile, "");
|
|
strcpy(outfile, "");
|
|
strcpy(error_msg, "");
|
|
CurlResult = CURLE_OK;
|
|
bTempOutfile = true;
|
|
want_download = false;
|
|
want_upload = false;
|
|
fileIn = NULL;
|
|
fileOut = NULL;
|
|
connect_error = 0;
|
|
bytes_xferred = 0;
|
|
bSentHeader = false;
|
|
close_socket();
|
|
}
|
|
|
|
|
|
HTTP_OP::HTTP_OP() {
|
|
strcpy(m_url, "");
|
|
strcpy(m_curl_ca_bundle_location, "");
|
|
content_length = 0;
|
|
file_offset = 0;
|
|
strcpy(request_header, "");
|
|
http_op_state = HTTP_STATE_IDLE;
|
|
http_op_type = HTTP_OP_NONE;
|
|
http_op_retval = 0;
|
|
trace_id = 0;
|
|
pcurlList = NULL; // these have to be NULL, just in constructor
|
|
curlEasy = NULL;
|
|
pcurlFormStart = NULL;
|
|
pcurlFormEnd = NULL;
|
|
pByte = NULL;
|
|
lSeek = 0;
|
|
xfer_speed = 0;
|
|
reset();
|
|
}
|
|
|
|
HTTP_OP::~HTTP_OP() {
|
|
close_socket();
|
|
close_file();
|
|
}
|
|
|
|
// Initialize HTTP GET operation;
|
|
// output goes to the given file, starting at given offset
|
|
//
|
|
int HTTP_OP::init_get(
|
|
const char* url, const char* out, bool del_old_file, double off
|
|
) {
|
|
if (del_old_file) {
|
|
unlink(out);
|
|
}
|
|
req1 = NULL; // not using req1, but init_post2 uses it
|
|
file_offset = off;
|
|
HTTP_OP::init();
|
|
// usually have an outfile on a get
|
|
if (off != 0) {
|
|
bytes_xferred = off;
|
|
start_bytes_xferred = off;
|
|
}
|
|
http_op_type = HTTP_OP_GET;
|
|
http_op_state = HTTP_STATE_CONNECTING;
|
|
if (log_flags.http_debug) {
|
|
msg_printf(0, MSG_INFO, "[http_debug] HTTP_OP::init_get(): %s", url);
|
|
}
|
|
return HTTP_OP::libcurl_exec(url, NULL, out, off, false);
|
|
}
|
|
|
|
// Initialize HTTP POST operation where
|
|
// the input is a file, and the output is a file,
|
|
// and both are read/written from the beginning (no resumption of partial ops)
|
|
// This is used for scheduler requests and account mgr RPCs.
|
|
//
|
|
int HTTP_OP::init_post(
|
|
const char* url, const char* in, const char* out
|
|
) {
|
|
int retval;
|
|
double size;
|
|
|
|
req1 = NULL; // not using req1, but init_post2 uses it
|
|
|
|
if (in) {
|
|
strcpy(infile, in);
|
|
retval = file_size(infile, size);
|
|
if (retval) return retval; // this will return 0 or ERR_NOT_FOUND
|
|
content_length = (int)size;
|
|
}
|
|
HTTP_OP::init();
|
|
http_op_type = HTTP_OP_POST;
|
|
http_op_state = HTTP_STATE_CONNECTING;
|
|
if (log_flags.http_debug) {
|
|
msg_printf(0, MSG_INFO, "[http_debug] HTTP_OP::init_post(): %s", url);
|
|
}
|
|
return HTTP_OP::libcurl_exec(url, in, out, 0.0, true);
|
|
}
|
|
|
|
// Initialize an HTTP POST operation,
|
|
// where the input is a memory string (r1) followed by an optional file (in)
|
|
// with optional offset,
|
|
// and the output goes to memory (also r1)
|
|
// This is used for file upload (both get_file_size and file_upload)
|
|
//
|
|
int HTTP_OP::init_post2(
|
|
const char* url, char* r1, int r1_len, const char* in, double offset
|
|
) {
|
|
int retval;
|
|
double size;
|
|
|
|
init();
|
|
req1 = r1;
|
|
req1_len = r1_len;
|
|
if (in) {
|
|
safe_strcpy(infile, in);
|
|
file_offset = offset;
|
|
retval = file_size(infile, size);
|
|
if (retval) {
|
|
printf("HTTP::init_post2: couldn't get file size\n");
|
|
return retval; // this will be 0 or ERR_NOT_FOUND
|
|
}
|
|
content_length = (int)size - (int)offset;
|
|
}
|
|
content_length += (int)strlen(req1);
|
|
http_op_type = HTTP_OP_POST2;
|
|
http_op_state = HTTP_STATE_CONNECTING;
|
|
return HTTP_OP::libcurl_exec(url, in, NULL, offset, true);
|
|
}
|
|
|
|
// the following will do an HTTP GET or POST using libcurl
|
|
//
|
|
int HTTP_OP::libcurl_exec(
|
|
const char* url, const char* in, const char* out, double offset, bool bPost
|
|
) {
|
|
CURLMcode curlMErr;
|
|
CURLcode curlErr;
|
|
char strTmp[128];
|
|
static int outfile_seqno=0;
|
|
|
|
safe_strcpy(m_url, url);
|
|
|
|
if (g_user_agent_string[0] == 0x00) {
|
|
get_user_agent_string();
|
|
}
|
|
|
|
if (in) {
|
|
strcpy(infile, in);
|
|
}
|
|
if (out) {
|
|
bTempOutfile = false;
|
|
strcpy(outfile, out);
|
|
} else {
|
|
// always want an outfile for the server response, delete when op done
|
|
bTempOutfile = true;
|
|
sprintf(outfile, "http_temp_%d", outfile_seqno++);
|
|
}
|
|
|
|
curlEasy = curl_easy_init(); // get a curl_easy handle to use
|
|
if (!curlEasy) {
|
|
msg_printf(0, MSG_INTERNAL_ERROR, "Couldn't create curlEasy handle");
|
|
return ERR_HTTP_ERROR; // returns 0 (CURLM_OK) on successful handle creation
|
|
}
|
|
|
|
// the following seems to be a no-op
|
|
//curlErr = curl_easy_setopt(curlEasy, CURLOPT_ERRORBUFFER, error_msg);
|
|
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_URL, m_url);
|
|
|
|
|
|
// This option determines whether curl verifies that the server
|
|
// claims to be who you want it to be.
|
|
// When negotiating an SSL connection,
|
|
// the server sends a certificate indicating its identity.
|
|
// When CURLOPT_SSL_VERIFYHOST is 2,
|
|
// that certificate must indicate that the server is the server
|
|
// to which you meant to connect, or the connection fails.
|
|
// Curl considers the server the intended one when the
|
|
// Common Name field or a Subject Alternate Name field in the certificate
|
|
// matches the host name in the URL to which you told Curl to connect.
|
|
// When the value is 1, the certificate must contain a Common Name field,
|
|
// but it doesn't matter what name it says.
|
|
// (This is not ordinarily a useful setting).
|
|
// When the value is 0, the connection succeeds
|
|
// regardless of the names in the certificate.
|
|
// The default, since 7.10, is 2.
|
|
// The checking this option controls is of the identity that
|
|
// the server claims. The server could be lying.
|
|
// To control lying, see CURLOPT_SSL_VERIFYPEER.
|
|
//
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_SSL_VERIFYHOST, 2L);
|
|
|
|
// the following sets "tough" certificate checking
|
|
// (i.e. whether self-signed is OK)
|
|
// if zero below, will accept self-signed certificates
|
|
// (cert not 3rd party trusted)
|
|
// if non-zero below, you need a valid 3rd party CA (i.e. Verisign, Thawte)
|
|
//
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_SSL_VERIFYPEER, 1L);
|
|
|
|
// if the above is nonzero, you need the following:
|
|
//
|
|
#ifdef _WIN32
|
|
if (strlen(m_curl_ca_bundle_location) == 0) {
|
|
TCHAR szPath[MAX_PATH-1];
|
|
GetModuleFileName(NULL, szPath, (sizeof(szPath)/sizeof(TCHAR)));
|
|
|
|
TCHAR *pszProg = strrchr(szPath, '\\');
|
|
if (pszProg) {
|
|
szPath[pszProg - szPath + 1] = 0;
|
|
|
|
strncat(
|
|
m_curl_ca_bundle_location,
|
|
szPath,
|
|
sizeof(m_curl_ca_bundle_location)-strlen(m_curl_ca_bundle_location)
|
|
);
|
|
strncat(
|
|
m_curl_ca_bundle_location,
|
|
CA_BUNDLE_FILENAME,
|
|
sizeof(m_curl_ca_bundle_location)-strlen(m_curl_ca_bundle_location)
|
|
);
|
|
|
|
if (log_flags.http_debug) {
|
|
msg_printf(
|
|
0,
|
|
MSG_INFO,
|
|
"[http_debug] HTTP_OP::libcurl_exec(): ca-bundle '%s'",
|
|
m_curl_ca_bundle_location
|
|
);
|
|
}
|
|
}
|
|
}
|
|
if (boinc_file_exists(m_curl_ca_bundle_location)) {
|
|
// call this only if a local copy of ca-bundle.crt exists;
|
|
// otherwise, let's hope that it exists in the default place
|
|
//
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_CAINFO, m_curl_ca_bundle_location);
|
|
if (log_flags.http_debug) {
|
|
msg_printf(
|
|
0,
|
|
MSG_INFO,
|
|
"[http_debug] HTTP_OP::libcurl_exec(): ca-bundle set"
|
|
);
|
|
}
|
|
}
|
|
#else
|
|
if (boinc_file_exists(CA_BUNDLE_FILENAME)) {
|
|
// call this only if a local copy of ca-bundle.crt exists;
|
|
// otherwise, let's hope that it exists in the default place
|
|
//
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_CAINFO, CA_BUNDLE_FILENAME);
|
|
}
|
|
#endif
|
|
|
|
// set the user agent as this boinc client & version
|
|
//
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_USERAGENT, g_user_agent_string);
|
|
|
|
// bypass any signal handlers that curl may want to install
|
|
//
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_NOSIGNAL, 1L);
|
|
// bypass progress meter
|
|
//
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_NOPROGRESS, 1L);
|
|
|
|
// setup timeouts
|
|
//
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_TIMEOUT, 0L);
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_LOW_SPEED_LIMIT, 10L);
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_LOW_SPEED_TIME, 300L);
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_CONNECTTIMEOUT, 120L);
|
|
|
|
// force curl to use HTTP/1.0 if config specifies it
|
|
// (curl uses 1.1 by default)
|
|
//
|
|
if (config.http_1_0 || (config.force_auth == "ntlm")) {
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
|
|
}
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_MAXREDIRS, 50L);
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_AUTOREFERER, 1L);
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_FOLLOWLOCATION, 1L);
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_POST301, 1L);
|
|
|
|
// if we tell Curl to accept any encoding (e.g. deflate)
|
|
// it seems to accept them all, which screws up projects that
|
|
// use gzip at the application level.
|
|
// So, detect this and don't accept any encoding in that case
|
|
//
|
|
if (!out || !ends_with(std::string(out), std::string(".gz"))) {
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_ENCODING, "");
|
|
}
|
|
|
|
// setup any proxy they may need
|
|
//
|
|
setupProxyCurl();
|
|
|
|
// set the content type in the header
|
|
//
|
|
pcurlList = curl_slist_append(pcurlList, g_content_type);
|
|
|
|
// set the file offset for resumable downloads
|
|
//
|
|
if (!bPost && offset>0.0f) {
|
|
file_offset = offset;
|
|
sprintf(strTmp, "Range: bytes=%.0f-", offset);
|
|
pcurlList = curl_slist_append(pcurlList, strTmp);
|
|
}
|
|
|
|
// set up an output file for the reply
|
|
//
|
|
if (strlen(outfile)) {
|
|
if (file_offset>0.0) {
|
|
fileOut = boinc_fopen(outfile, "ab+");
|
|
} else {
|
|
fileOut = boinc_fopen(outfile, "wb+");
|
|
}
|
|
if (!fileOut) {
|
|
msg_printf(NULL, MSG_INTERNAL_ERROR,
|
|
"Can't create HTTP response output file %s", outfile
|
|
);
|
|
http_op_retval = ERR_FOPEN;
|
|
http_op_state = HTTP_STATE_DONE;
|
|
return ERR_FOPEN;
|
|
}
|
|
// we can make the libcurl_write "fancier" in the future,
|
|
// for now it just fwrite's to the file request, which is sufficient
|
|
//
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_WRITEFUNCTION, libcurl_write);
|
|
// note that in my lib_write I'm sending in a pointer
|
|
// to this instance of HTTP_OP
|
|
//
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_WRITEDATA, this);
|
|
}
|
|
|
|
if (bPost) {
|
|
want_upload = true;
|
|
want_download = false;
|
|
if (infile && strlen(infile)>0) {
|
|
fileIn = boinc_fopen(infile, "rb");
|
|
if (!fileIn) {
|
|
msg_printf(NULL, MSG_INTERNAL_ERROR, "No HTTP input file %s", infile);
|
|
http_op_retval = ERR_FOPEN;
|
|
http_op_state = HTTP_STATE_DONE;
|
|
return ERR_FOPEN;
|
|
}
|
|
}
|
|
|
|
if (pcurlList) { // send custom headers if required
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_HTTPHEADER, pcurlList);
|
|
}
|
|
|
|
// set the data file info to read for the PUT/POST
|
|
// note the use of this curl typedef for large filesizes
|
|
|
|
#if 0
|
|
// HTTP PUT method
|
|
curl_off_t fs = (curl_off_t) content_length;
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_POSTFIELDS, NULL);
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_INFILESIZE, content_length);
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_READDATA, fileIn);
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_INFILESIZE_LARGE, fs);
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_PUT, 1L);
|
|
#endif
|
|
|
|
// HTTP POST method
|
|
// set the multipart form for the file --
|
|
// boinc just has the one section (file)
|
|
|
|
#if 0
|
|
// if we ever want to do POST as multipart forms someday
|
|
// (many seem to prefer it that way, i.e. libcurl)
|
|
//
|
|
pcurlFormStart = pcurlFormEnd = NULL;
|
|
curl_formadd(&pcurlFormStart, &pcurlFormEnd,
|
|
CURLFORM_FILECONTENT, infile,
|
|
CURLFORM_CONTENTSLENGTH, content_length,
|
|
CURLFORM_CONTENTTYPE, g_content_type,
|
|
CURLFORM_END);
|
|
curl_formadd(&post, &last,
|
|
CURLFORM_COPYNAME, "logotype-image",
|
|
CURLFORM_FILECONTENT, "curl.png", CURLFORM_END);
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_HTTPPOST, pcurlFormStart);
|
|
#endif
|
|
|
|
curl_off_t fs = (curl_off_t) content_length;
|
|
|
|
pByte = NULL;
|
|
lSeek = 0; // initialize the vars we're going to use for byte transfers
|
|
|
|
// we can make the libcurl_read "fancier" in the future,
|
|
// for now it just fwrite's to the file request, which is sufficient
|
|
//
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_POSTFIELDS, NULL);
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_POSTFIELDSIZE_LARGE, fs);
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_READFUNCTION, libcurl_read);
|
|
// in my lib_write I'm sending in a pointer to this instance of HTTP_OP
|
|
//
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_READDATA, this);
|
|
|
|
// callback function to rewind input file
|
|
//
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_IOCTLFUNCTION, libcurl_ioctl);
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_IOCTLDATA, this);
|
|
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_POST, 1L);
|
|
} else { // GET
|
|
want_upload = false;
|
|
want_download = true;
|
|
|
|
// now write the header, pcurlList gets freed in net_xfer_curl
|
|
//
|
|
if (pcurlList) { // send custom headers if required
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_HTTPHEADER, pcurlList);
|
|
}
|
|
|
|
// setup the GET!
|
|
//
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_HTTPGET, 1L);
|
|
}
|
|
|
|
// turn on debug info if tracing enabled
|
|
//
|
|
if (log_flags.http_debug) {
|
|
static int trace_count = 0;
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_DEBUGFUNCTION, libcurl_debugfunction);
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_DEBUGDATA, this );
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_VERBOSE, 1L);
|
|
trace_id = trace_count++;
|
|
}
|
|
|
|
// last but not least, add this to the curl_multi
|
|
|
|
curlMErr = curl_multi_add_handle(g_curlMulti, curlEasy);
|
|
if (curlMErr != CURLM_OK && curlMErr != CURLM_CALL_MULTI_PERFORM) {
|
|
// bad error, couldn't attach easy curl handle
|
|
msg_printf(0, MSG_INTERNAL_ERROR,
|
|
"Couldn't add curlEasy handle to curlMulti"
|
|
);
|
|
return ERR_HTTP_ERROR;
|
|
// returns 0 (CURLM_OK) on successful handle creation
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Returns true if the HTTP operation is complete
|
|
//
|
|
bool HTTP_OP::http_op_done() {
|
|
return (http_op_state == HTTP_STATE_DONE);
|
|
}
|
|
|
|
HTTP_OP_SET::HTTP_OP_SET() {
|
|
bytes_up = 0;
|
|
bytes_down = 0;
|
|
}
|
|
|
|
// Adds an HTTP_OP to the set
|
|
//
|
|
int HTTP_OP_SET::insert(HTTP_OP* ho) {
|
|
http_ops.push_back(ho);
|
|
return 0;
|
|
}
|
|
|
|
// Remove an HTTP_OP from the set
|
|
//
|
|
int HTTP_OP_SET::remove(HTTP_OP* p) {
|
|
vector<HTTP_OP*>::iterator iter;
|
|
|
|
iter = http_ops.begin();
|
|
while (iter != http_ops.end()) {
|
|
if (*iter == p) {
|
|
iter = http_ops.erase(iter);
|
|
return 0;
|
|
}
|
|
iter++;
|
|
}
|
|
msg_printf(NULL, MSG_INTERNAL_ERROR, "HTTP operation not found");
|
|
return ERR_NOT_FOUND;
|
|
}
|
|
|
|
int HTTP_OP_SET::nops() {
|
|
return (int)http_ops.size();
|
|
}
|
|
|
|
|
|
int HTTP_OP::set_proxy(PROXY_INFO *new_pi) {
|
|
pi.use_http_proxy = new_pi->use_http_proxy;
|
|
strcpy(pi.http_user_name, new_pi->http_user_name);
|
|
strcpy(pi.http_user_passwd, new_pi->http_user_passwd);
|
|
strcpy(pi.http_server_name, new_pi->http_server_name);
|
|
pi.http_server_port = new_pi->http_server_port;
|
|
pi.use_http_auth = new_pi->use_http_auth;
|
|
|
|
pi.use_socks_proxy = new_pi->use_socks_proxy;
|
|
strcpy(pi.socks5_user_name, new_pi->socks5_user_name);
|
|
strcpy(pi.socks5_user_passwd, new_pi->socks5_user_passwd);
|
|
strcpy(pi.socks_server_name, new_pi->socks_server_name);
|
|
pi.socks_server_port = new_pi->socks_server_port;
|
|
pi.socks_version = new_pi->socks_version;
|
|
|
|
return 0;
|
|
}
|
|
|
|
size_t libcurl_write(void *ptr, size_t size, size_t nmemb, HTTP_OP* phop) {
|
|
// take the stream param as a FILE* and write to disk
|
|
// TODO: maybe assert stRead == size*nmemb,
|
|
// add exception handling on phop members
|
|
//
|
|
size_t stWrite = fwrite(ptr, size, nmemb, phop->fileOut);
|
|
if (log_flags.http_xfer_debug) {
|
|
msg_printf(NULL, MSG_INFO,
|
|
"[http_xfer_debug] HTTP: wrote %d bytes", (int)stWrite
|
|
);
|
|
}
|
|
phop->bytes_xferred += (double)(stWrite);
|
|
phop->update_speed(); // this should update the transfer speed
|
|
return stWrite;
|
|
}
|
|
|
|
size_t libcurl_read( void *ptr, size_t size, size_t nmemb, HTTP_OP* phop) {
|
|
// OK here's the deal -- phop points to the calling object,
|
|
// which has already pre-opened the file. we'll want to
|
|
// use pByte as a pointer for fseek calls into the file, and
|
|
// write out size*nmemb # of bytes to ptr
|
|
|
|
// take the stream param as a FILE* and write to disk
|
|
// if (pByte) delete [] pByte;
|
|
// pByte = new unsigned char[content_length];
|
|
// memset(pByte, 0x00, content_length); // may as will initialize it!
|
|
|
|
// note that fileIn was opened earlier,
|
|
// go to lSeek from the top and read from there
|
|
//
|
|
size_t stSend = size * nmemb;
|
|
int stRead = 0;
|
|
|
|
if (phop->req1 && !phop->bSentHeader) {
|
|
// need to send headers first, then data file
|
|
// so requests from 0 to strlen(req1)-1 are from memory,
|
|
// and from strlen(req1) to content_length are from the file
|
|
if (phop->lSeek < (long) strlen(phop->req1)) {
|
|
// need to read header, either just starting to read
|
|
// (i.e. this is the first time in this function for this phop)
|
|
// or the last read didn't ask for the entire header
|
|
|
|
stRead = (int)strlen(phop->req1) - phop->lSeek;
|
|
// how much of header left to read
|
|
|
|
// only memcpy if request isn't out of bounds
|
|
if (stRead < 0) {
|
|
stRead = 0;
|
|
} else {
|
|
memcpy(ptr, (void*)(phop->req1 + phop->lSeek), stRead);
|
|
}
|
|
phop->lSeek += (long) stRead; // increment lSeek to new position
|
|
|
|
// Don't count header in bytes transferred.
|
|
// Otherwise the GUI will show e.g. "400 out of 300 bytes xferred"
|
|
//phop->bytes_xferred += (double)(stRead);
|
|
|
|
// see if we're done with headers
|
|
if (phop->lSeek >= (long) strlen(phop->req1)) {
|
|
phop->bSentHeader = true;
|
|
phop->lSeek = 0;
|
|
}
|
|
return stRead;
|
|
} else {
|
|
// shouldn't happen
|
|
phop->bSentHeader = true;
|
|
phop->lSeek = 0;
|
|
}
|
|
}
|
|
if (phop->fileIn) {
|
|
long lFileSeek = phop->lSeek + (long) phop->file_offset;
|
|
fseek(phop->fileIn, lFileSeek, SEEK_SET);
|
|
if (!feof(phop->fileIn)) {
|
|
stRead = (int)fread(ptr, 1, stSend, phop->fileIn);
|
|
}
|
|
phop->lSeek += (long) stRead;
|
|
phop->bytes_xferred += (double)(stRead);
|
|
}
|
|
phop->update_speed();
|
|
return stRead;
|
|
}
|
|
|
|
curlioerr libcurl_ioctl(CURL*, curliocmd cmd, HTTP_OP* phop) {
|
|
// reset input stream to beginning - resends header
|
|
// and restarts data back to starting point
|
|
|
|
switch(cmd) {
|
|
case CURLIOCMD_RESTARTREAD:
|
|
phop->lSeek = 0;
|
|
phop->bytes_xferred = phop->file_offset;
|
|
phop->bSentHeader = false;
|
|
break;
|
|
default: // should never get here
|
|
return CURLIOE_UNKNOWNCMD;
|
|
}
|
|
return CURLIOE_OK;
|
|
}
|
|
|
|
int libcurl_debugfunction(
|
|
CURL*, curl_infotype type,
|
|
unsigned char *data, size_t size, HTTP_OP* phop
|
|
) {
|
|
const char *text;
|
|
char hdr[100];
|
|
char buf[1024];
|
|
size_t mysize;
|
|
|
|
switch (type) {
|
|
case CURLINFO_TEXT:
|
|
if (log_flags.http_debug) {
|
|
msg_printf(0, MSG_INFO,
|
|
"[http_debug] [ID#%i] info: %s\n", phop->trace_id, data
|
|
);
|
|
}
|
|
return 0;
|
|
case CURLINFO_HEADER_OUT:
|
|
text = "Sent header to server:";
|
|
break;
|
|
case CURLINFO_HEADER_IN:
|
|
text = "Received header from server:";
|
|
break;
|
|
default: /* in case a new one is introduced to shock us */
|
|
return 0;
|
|
}
|
|
|
|
sprintf( hdr,"[ID#%i] %s", phop->trace_id, text);
|
|
mysize = min(size, sizeof(buf)-1);
|
|
strncpy(buf, (char *)data, mysize);
|
|
buf[mysize]='\0';
|
|
if (log_flags.http_debug) {
|
|
msg_printf(0, MSG_INFO,
|
|
"[http_debug] %s %s\n", hdr, buf
|
|
);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
void HTTP_OP::setupProxyCurl() {
|
|
// PROXY_INFO pi useful members:
|
|
// pi.http_server_name
|
|
// pi.http_server_port
|
|
// pi.http_user_name
|
|
// pi.http_user_passwd
|
|
// pi.socks5_user_name
|
|
// pi.socks5_user_passwd
|
|
// pi.socks_server_name
|
|
// pi.socks_server_port
|
|
// pi.socks_version
|
|
// pi.use_http_auth
|
|
// pi.use_http_proxy
|
|
// pi.use_socks_proxy
|
|
//
|
|
// Curl self-explanatory setopt params for proxies:
|
|
// CURLOPT_HTTPPROXYTUNNEL
|
|
// CURLOPT_PROXYTYPE (pass in CURLPROXY_HTTP or CURLPROXY_SOCKS5)
|
|
// CURLOPT_PROXYPORT -- a long port #
|
|
// CURLOPT_PROXY - pass in char* of the proxy url
|
|
// CURLOPT_PROXYUSERPWD -- a char* in the format username:password
|
|
// CURLOPT_HTTPAUTH -- pass in one of CURLAUTH_BASIC, CURLAUTH_DIGEST,
|
|
// CURLAUTH_GSSNEGOTIATE, CURLAUTH_NTLM, CURLAUTH_ANY, CURLAUTH_ANYSAFE
|
|
// CURLOPT_PROXYAUTH -- "or" | the above bitmasks -- only basic, digest, ntlm work
|
|
|
|
CURLcode curlErr;
|
|
|
|
// CMC Note: the string szCurlProxyUserPwd must remain in memory
|
|
// outside of this method (libcurl relies on it later when it makes
|
|
// the proxy connection), so it has been placed as a member data for HTTP_OP
|
|
memset(szCurlProxyUserPwd,0x00,128);
|
|
|
|
if (pi.use_http_proxy) {
|
|
// setup a basic http proxy
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_PROXYPORT, (long) pi.http_server_port);
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_PROXY, (char*) pi.http_server_name);
|
|
|
|
if (pi.use_http_auth) {
|
|
if (config.force_auth == "basic") {
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
|
|
} else if (config.force_auth == "digest") {
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_PROXYAUTH, CURLAUTH_DIGEST);
|
|
} else if (config.force_auth == "gss-negotiate") {
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_PROXYAUTH, CURLAUTH_GSSNEGOTIATE);
|
|
} else if (config.force_auth == "ntlm") {
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_PROXYAUTH, CURLAUTH_NTLM);
|
|
} else {
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
|
|
}
|
|
sprintf(szCurlProxyUserPwd, "%s:%s", pi.http_user_name, pi.http_user_passwd);
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_PROXYUSERPWD, szCurlProxyUserPwd);
|
|
}
|
|
} else {
|
|
if (pi.use_socks_proxy) {
|
|
//pi.socks_version -- picks between socks5 & socks4 -- but libcurl only socks5!
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_PROXYPORT, (long) pi.socks_server_port);
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_PROXY, (char*) pi.socks_server_name);
|
|
// libcurl uses blocking sockets with socks proxy, so limit timeout.
|
|
// - imlemented with local patch to libcurl
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_CONNECTTIMEOUT, 20L);
|
|
|
|
if (
|
|
strlen(pi.socks5_user_passwd)>0 || strlen(pi.socks5_user_name)>0
|
|
) {
|
|
sprintf(szCurlProxyUserPwd, "%s:%s", pi.socks5_user_name, pi.socks5_user_passwd);
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_PROXYUSERPWD, szCurlProxyUserPwd);
|
|
curlErr = curl_easy_setopt(curlEasy, CURLOPT_PROXYAUTH, CURLAUTH_ANY & ~CURLAUTH_NTLM);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// the file descriptor sets need to be global so libcurl has access always
|
|
//
|
|
fd_set read_fds, write_fds, error_fds;
|
|
|
|
// call these once at the start of the program and once at the end
|
|
//
|
|
int curl_init() {
|
|
curl_global_init(CURL_GLOBAL_ALL);
|
|
g_curlMulti = curl_multi_init();
|
|
return (int)(g_curlMulti == NULL);
|
|
}
|
|
|
|
int curl_cleanup() {
|
|
if (g_curlMulti) {
|
|
curl_multi_cleanup(g_curlMulti);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void HTTP_OP::close_socket() {
|
|
// this cleans up the curlEasy, and "spoofs" the old close_socket
|
|
//
|
|
if (pcurlList) {
|
|
curl_slist_free_all(pcurlList);
|
|
pcurlList = NULL;
|
|
}
|
|
if (curlEasy && pcurlFormStart) {
|
|
curl_formfree(pcurlFormStart);
|
|
curl_formfree(pcurlFormEnd);
|
|
pcurlFormStart = pcurlFormEnd = NULL;
|
|
}
|
|
if (curlEasy && g_curlMulti) { // release this handle
|
|
curl_multi_remove_handle(g_curlMulti, curlEasy);
|
|
curl_easy_cleanup(curlEasy);
|
|
curlEasy = NULL;
|
|
}
|
|
}
|
|
|
|
void HTTP_OP::close_file() {
|
|
if (fileIn) {
|
|
fclose(fileIn);
|
|
fileIn = NULL;
|
|
}
|
|
if (fileOut) {
|
|
fclose(fileOut);
|
|
fileOut = NULL;
|
|
}
|
|
if (pByte) { //free any read memory used
|
|
delete [] pByte;
|
|
pByte = NULL;
|
|
}
|
|
}
|
|
|
|
void HTTP_OP_SET::get_fdset(FDSET_GROUP& fg) {
|
|
CURLMcode curlMErr;
|
|
curlMErr = curl_multi_fdset(
|
|
g_curlMulti, &fg.read_fds, &fg.write_fds, &fg.exc_fds, &fg.max_fd
|
|
);
|
|
}
|
|
|
|
// we have a message for this HTTP_OP.
|
|
// get the response code for this request
|
|
//
|
|
void HTTP_OP::handle_messages(CURLMsg *pcurlMsg) {
|
|
CURLcode curlErr;
|
|
int retval;
|
|
|
|
curlErr = curl_easy_getinfo(curlEasy,
|
|
CURLINFO_RESPONSE_CODE, &response
|
|
);
|
|
|
|
// CURLINFO_LONG+25 is a workaround for a bug in the gcc version
|
|
// included with Mac OS X 10.3.9
|
|
//
|
|
curlErr = curl_easy_getinfo(curlEasy,
|
|
(CURLINFO)(CURLINFO_LONG+25) /*CURLINFO_OS_ERRNO*/, &connect_error
|
|
);
|
|
|
|
// update byte counts and transfer speed
|
|
//
|
|
if (want_download) {
|
|
// SIZE_DOWNLOAD is the byte count "on the wire"
|
|
// (possible with compression)
|
|
// TOTAL_TIME is the elapsed time of the download
|
|
// STARTTRANSFER_TIME is portion of elapsed time involved
|
|
// with setup (connection establishment etc.)
|
|
// SPEED_DOWNLOAD is bytes/sec based on uncompressed size
|
|
// (we don't use it)
|
|
//
|
|
double size_download, total_time, starttransfer_time;
|
|
curlErr = curl_easy_getinfo(curlEasy,
|
|
CURLINFO_SIZE_DOWNLOAD, &size_download
|
|
);
|
|
curlErr = curl_easy_getinfo(curlEasy,
|
|
CURLINFO_TOTAL_TIME, &total_time
|
|
);
|
|
curlErr = curl_easy_getinfo(curlEasy,
|
|
CURLINFO_STARTTRANSFER_TIME, &starttransfer_time
|
|
);
|
|
double dt = total_time - starttransfer_time;
|
|
if (dt > 0) {
|
|
gstate.net_stats.down.update(size_download, dt);
|
|
}
|
|
}
|
|
if (want_upload) {
|
|
double size_upload, total_time, starttransfer_time;
|
|
curlErr = curl_easy_getinfo(curlEasy,
|
|
CURLINFO_SIZE_UPLOAD, &size_upload
|
|
);
|
|
curlErr = curl_easy_getinfo(curlEasy,
|
|
CURLINFO_TOTAL_TIME, &total_time
|
|
);
|
|
curlErr = curl_easy_getinfo(curlEasy,
|
|
CURLINFO_STARTTRANSFER_TIME, &starttransfer_time
|
|
);
|
|
double dt = total_time - starttransfer_time;
|
|
if (dt > 0) {
|
|
gstate.net_stats.up.update(size_upload, dt);
|
|
}
|
|
}
|
|
|
|
// the op is done if curl_multi_msg_read gave us a msg for this http_op
|
|
//
|
|
http_op_state = HTTP_STATE_DONE;
|
|
CurlResult = pcurlMsg->data.result;
|
|
|
|
if (CurlResult == CURLE_OK) {
|
|
if ((response/100)*100 == HTTP_STATUS_OK) {
|
|
http_op_retval = 0;
|
|
} else if ((response/100)*100 == HTTP_STATUS_CONTINUE) {
|
|
return;
|
|
} else {
|
|
// Got a response from server but its not OK or CONTINUE,
|
|
// so save response with error message to display later.
|
|
//
|
|
if (response >= 400) {
|
|
strcpy(error_msg, boincerror(response));
|
|
} else {
|
|
sprintf(error_msg, "HTTP error %ld", response);
|
|
}
|
|
switch (response) {
|
|
case HTTP_STATUS_NOT_FOUND:
|
|
http_op_retval = ERR_FILE_NOT_FOUND;
|
|
break;
|
|
default:
|
|
http_op_retval = ERR_HTTP_ERROR;
|
|
}
|
|
}
|
|
net_status.need_physical_connection = false;
|
|
} else {
|
|
strcpy(error_msg, curl_easy_strerror(CurlResult));
|
|
switch(CurlResult) {
|
|
case CURLE_COULDNT_RESOLVE_HOST:
|
|
http_op_retval = ERR_GETHOSTBYNAME;
|
|
break;
|
|
case CURLE_COULDNT_CONNECT:
|
|
http_op_retval = ERR_CONNECT;
|
|
break;
|
|
default:
|
|
http_op_retval = ERR_HTTP_ERROR;
|
|
}
|
|
net_status.got_http_error();
|
|
if (log_flags.http_debug) {
|
|
msg_printf(NULL, MSG_INFO,
|
|
"[http_debug] HTTP error: %s", error_msg
|
|
);
|
|
}
|
|
}
|
|
|
|
if (!http_op_retval && http_op_type == HTTP_OP_POST2) {
|
|
// for a successfully completed request on a "post2" --
|
|
// read in the temp file into req1 memory
|
|
//
|
|
size_t dSize = ftell(fileOut);
|
|
retval = fseek(fileOut, 0, SEEK_SET);
|
|
if (retval) {
|
|
// flag as a bad response for a possible retry later
|
|
response = 1;
|
|
msg_printf(NULL, MSG_INTERNAL_ERROR,
|
|
"[http_debug] can't rewind post output file %s",
|
|
outfile
|
|
);
|
|
} else {
|
|
strcpy(req1, "");
|
|
if (dSize > (size_t)req1_len) {
|
|
dSize = req1_len;
|
|
}
|
|
size_t nread = fread(req1, 1, dSize, fileOut);
|
|
if (nread != dSize) {
|
|
if (log_flags.http_debug) {
|
|
msg_printf(NULL, MSG_INFO,
|
|
"[http_debug] post output file read failed %ld",
|
|
nread
|
|
);
|
|
}
|
|
}
|
|
req1[req1_len-1] = 0; // make sure null-terminated
|
|
}
|
|
}
|
|
|
|
// close files and "sockets" (i.e. libcurl handles)
|
|
//
|
|
close_file();
|
|
close_socket();
|
|
|
|
// finally remove the tmpfile if not explicitly set
|
|
//
|
|
if (bTempOutfile) {
|
|
boinc_delete_file(outfile);
|
|
}
|
|
}
|
|
|
|
void HTTP_OP_SET::got_select(FDSET_GROUP&, double timeout) {
|
|
int iNumMsg;
|
|
HTTP_OP* hop = NULL;
|
|
CURLMsg *pcurlMsg = NULL;
|
|
|
|
int iRunning = 0; // curl flags for max # of fds & # running queries
|
|
CURLMcode curlMErr;
|
|
|
|
// get the data waiting for transfer in or out
|
|
// use timeout value so that we don't hog CPU in this loop
|
|
//
|
|
while (1) {
|
|
curlMErr = curl_multi_perform(g_curlMulti, &iRunning);
|
|
if (curlMErr != CURLM_CALL_MULTI_PERFORM) break;
|
|
if (dtime() - gstate.now > timeout) break;
|
|
}
|
|
|
|
// read messages from curl that may have come in from the above loop
|
|
//
|
|
while (1) {
|
|
pcurlMsg = curl_multi_info_read(g_curlMulti, &iNumMsg);
|
|
if (!pcurlMsg) break;
|
|
|
|
// if we have a msg, then somebody finished
|
|
// can check also with pcurlMsg->msg == CURLMSG_DONE
|
|
//
|
|
hop = lookup_curl(pcurlMsg->easy_handle);
|
|
if (!hop) continue;
|
|
hop->handle_messages(pcurlMsg);
|
|
}
|
|
}
|
|
|
|
// Return the HTTP_OP object with given Curl object
|
|
//
|
|
HTTP_OP* HTTP_OP_SET::lookup_curl(CURL* pcurl) {
|
|
for (unsigned int i=0; i<http_ops.size(); i++) {
|
|
if (http_ops[i]->curlEasy == pcurl) {
|
|
return http_ops[i];
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Update the transfer speed for this HTTP_OP
|
|
// called on every I/O
|
|
//
|
|
void HTTP_OP::update_speed() {
|
|
double delta_t = dtime() - start_time;
|
|
if (delta_t > 0) {
|
|
xfer_speed = (bytes_xferred-start_bytes_xferred) / delta_t;
|
|
}
|
|
}
|
|
|
|
void HTTP_OP::set_speed_limit(bool is_upload, double bytes_sec) {
|
|
#if LIBCURL_VERSION_NUM >= 0x070f05
|
|
CURLcode cc = CURLE_OK;
|
|
curl_off_t bs = (curl_off_t)bytes_sec;
|
|
|
|
if (is_upload) {
|
|
cc = curl_easy_setopt(curlEasy, CURLOPT_MAX_SEND_SPEED_LARGE, bs);
|
|
} else {
|
|
cc = curl_easy_setopt(curlEasy, CURLOPT_MAX_RECV_SPEED_LARGE, bs);
|
|
}
|
|
if (cc && log_flags.http_debug) {
|
|
msg_printf(NULL, MSG_INFO, "[http_debug] Curl error in set_speed_limit(): %s", curl_easy_strerror(cc));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void HTTP_OP_SET::cleanup_temp_files() {
|
|
char filename[256];
|
|
|
|
DIRREF d = dir_open(".");
|
|
while (1) {
|
|
int retval = dir_scan(filename, d, sizeof(filename));
|
|
if (retval) break;
|
|
if (strstr(filename, "blc") != filename) continue;
|
|
if (!is_file(filename)) continue;
|
|
boinc_delete_file(filename);
|
|
}
|
|
dir_close(d);
|
|
}
|
|
|
|
const char *BOINC_RCSID_57f273bb60 = "$Id$";
|