text: /**
   * @file win32/net.cpp
   * @brief Win32 network access layer (using WinHTTP)
   *
   * (c) 2013-2014 by Mega Limited, Auckland, New Zealand
   *
   * This file is part of the MEGA SDK - Client Access Engine.
   *
   * Applications using the MEGA API must present a valid application key
   * and comply with the the rules set forth in the Terms of Service.
   *
   * The MEGA SDK 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.
   *
   * @copyright Simplified (2-clause) BSD License.
   *
   * You should have received a copy of the license along with this
   * program.
   */
  
  // FIXME: Work around WinHTTP's inability to POST additional data
  // after having read from the HTTP connection
  
  #include "meganet.h"
  
  namespace mega {
  
  extern PGTC pGTC;
  
  WinHttpIO::WinHttpIO()
  {
      InitializeCriticalSection(&csHTTP);
      EnterCriticalSection(&csHTTP);
  
      hWakeupEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
  
      waiter = NULL;    
  }
  
  WinHttpIO::~WinHttpIO()
  {
      WinHttpCloseHandle(hSession);
      LeaveCriticalSection(&csHTTP);
  }
  
  void WinHttpIO::setuseragent(string* useragent)
  {
      string wuseragent;
  
      wuseragent.resize((useragent->size() + 1) * sizeof(wchar_t));
      wuseragent.resize(sizeof(wchar_t)
                        * (MultiByteToWideChar(CP_UTF8, 0, useragent->c_str(),
                                                -1, (wchar_t*)wuseragent.data(),
                                                int(wuseragent.size() / sizeof(wchar_t) + 1))
                            - 1));
  
      // create the session handle using the default settings.
      hSession = WinHttpOpen((LPCWSTR)wuseragent.data(),
                             WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
                             WINHTTP_NO_PROXY_NAME,
                             WINHTTP_NO_PROXY_BYPASS,
                             WINHTTP_FLAG_ASYNC);
  
      DWORD protocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 |
              WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 |
              WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2;
  
      WinHttpSetOption(hSession, WINHTTP_OPTION_SECURE_PROTOCOLS, &protocols, sizeof (protocols));
  }
  
  
  void WinHttpIO::setproxy(Proxy* proxy)
  {
      Proxy* autoProxy = NULL;
  
      proxyUsername.clear();
      proxyPassword.clear();
  
      if (proxy->getProxyType() == Proxy::AUTO)
      {
          autoProxy = getautoproxy();
          proxy = autoProxy;
      }
  
      if (proxy->getProxyType() == Proxy::NONE)
      {
          WINHTTP_PROXY_INFO proxyInfo;
          proxyInfo.dwAccessType = WINHTTP_ACCESS_TYPE_NO_PROXY;
          proxyInfo.lpszProxy = WINHTTP_NO_PROXY_NAME;
          proxyInfo.lpszProxyBypass = WINHTTP_NO_PROXY_BYPASS;
          WinHttpSetOption(hSession, WINHTTP_OPTION_PROXY, &proxyInfo, sizeof(proxyInfo));
          LOG_info << "Proxy disabled";
      }
      else if (proxy->getProxyType() == Proxy::CUSTOM)
      {
          string proxyURL = proxy->getProxyURL();
          proxyURL.append("", 1);
          WINHTTP_PROXY_INFO proxyInfo;
          proxyInfo.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
          proxyInfo.lpszProxy = (LPWSTR)proxyURL.data();
          proxyInfo.lpszProxyBypass = WINHTTP_NO_PROXY_BYPASS;
          WinHttpSetOption(hSession, WINHTTP_OPTION_PROXY, &proxyInfo, sizeof(proxyInfo));
  
          LOG_info << "Proxy enabled";
          if (proxy->credentialsNeeded())
          {
              proxyUsername = proxy->getUsername();
  
              if (proxyUsername.size())
              {
                  proxyUsername.append("", 1);
              }
  
              proxyPassword = proxy->getPassword();
  
              if(proxyPassword.size())
              {
                  proxyPassword.append("", 1);
              }
  
              LOG_info << "Proxy requires authentication";
          }
      }
  
      delete autoProxy;
  }
  
  // trigger wakeup
  void WinHttpIO::httpevent()
  {
      SetEvent(hWakeupEvent);
  }
  
  // (WinHTTP unfortunately uses threads, hence the need for a mutex)
  void WinHttpIO::lock()
  {
      EnterCriticalSection(&csHTTP);
  }
  
  void WinHttpIO::unlock()
  {
      LeaveCriticalSection(&csHTTP);
  }
  
  // ensure wakeup from WinHttpIO events
  void WinHttpIO::addevents(Waiter* cwaiter, int flags)
  {
      waiter = (WinWaiter*)cwaiter;
      waiter->addhandle(hWakeupEvent, flags);
      waiter->pcsHTTP = &csHTTP;
  }
  
  // handle WinHTTP callbacks (which can be in a worker thread context)
  VOID CALLBACK WinHttpIO::asynccallback(HINTERNET hInternet, DWORD_PTR dwContext,
                                         DWORD dwInternetStatus,
                                         LPVOID lpvStatusInformation,
                                         DWORD dwStatusInformationLength)
  {
      WinHttpContext* httpctx = (WinHttpContext*)dwContext;
      WinHttpIO* httpio = (WinHttpIO*)httpctx->httpio;
  
      if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING)
      {
          LOG_verbose << "Closing request";
          assert(!httpctx->req);
  
          if (httpctx->gzip)
          {
              inflateEnd(&httpctx->z);
          }
          
          delete httpctx;
          return;
      }
  
      httpio->lock();
  
      HttpReq* req = httpctx->req;
  
      // request cancellations that occured after asynccallback() was entered are caught here
      if (!req)
      {
          LOG_verbose << "Request cancelled";
          httpio->unlock();
          return;
      }
  
      switch (dwInternetStatus)
      {
          case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE:
          {
              DWORD size = *(DWORD*)lpvStatusInformation;
  
              if (!size)
              {
                  if (req->binary)
                  {
                      LOG_debug << req->logname << "[received " << (req->buf ? req->buflen : req->in.size()) << " bytes of raw data]";
                  }
                  else
                  {
                      if(req->in.size() < 2048)
                      {
                          LOG_debug << req->logname << "Received: " << req->in.c_str();
                      }
                      else
                      {
                          LOG_debug << req->logname << "Received: " << req->in.substr(0,2048).c_str();
                      }
                  }
  
                  LOG_debug << "Request finished with HTTP status: " << req->httpstatus;
                  req->status = (req->httpstatus == 200
                              && (req->contentlength < 0
                               || req->contentlength == (req->buf ? req->bufpos : (int)req->in.size())))
                               ? REQ_SUCCESS : REQ_FAILURE;
  
                  if (req->status == REQ_SUCCESS)
                  {
                      httpio->lastdata = Waiter::ds;
                      req->lastdata = Waiter::ds;
                  }
                  httpio->success = true;
              }
              else
              {
                  LOG_verbose << "Data available. Remaining: " << size << " bytes";
  
                  char* ptr;
  
                  if (httpctx->gzip)
                  {                    
                      m_off_t zprevsize = httpctx->zin.size();
                      httpctx->zin.resize(size_t(zprevsize + size));
                      ptr = (char*)httpctx->zin.data() + zprevsize;
                  }
                  else
                  {                    
                      ptr = (char*)req->reserveput((unsigned*)&size);
                      req->bufpos += size;
                  }
  
                  if (!WinHttpReadData(hInternet, ptr, size, NULL))
                  {
                      LOG_err << "Unable to get more data. Code: " << GetLastError();
                      httpio->cancel(req);
                  }
              }
  
              httpio->httpevent();
              break;
          }
  
          case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
              LOG_verbose << "Read complete";
  
              if (dwStatusInformationLength)
              {
                  LOG_verbose << dwStatusInformationLength << " bytes received";
                  if (req->httpio)
                  {
                      req->httpio->lastdata = Waiter::ds;
                      req->lastdata = Waiter::ds;
                  }
              
                  if (httpctx->gzip)
                  {                    
                      httpctx->z.next_in = (Bytef*)lpvStatusInformation;
                      httpctx->z.avail_in = dwStatusInformationLength;
  
                      req->bufpos += httpctx->z.avail_out;
                      int t = inflate(&httpctx->z, Z_SYNC_FLUSH);
                      req->bufpos -= httpctx->z.avail_out;
  
                      if ((char*)lpvStatusInformation + dwStatusInformationLength ==
                               httpctx->zin.data() + httpctx->zin.size())
                      {
                          httpctx->zin.clear();
                      }
  
                      if (t != Z_OK && (t != Z_STREAM_END || httpctx->z.avail_out))
                      {
                          LOG_err << "GZIP error";
                          httpio->cancel(req);
                      }
                  }
                  else
                  {
                      // data was copied direct to buf or to req->in.
                      assert(req->buf && (byte*)lpvStatusInformation >= req->buf && (byte*)lpvStatusInformation + dwStatusInformationLength <= req->buf + req->buflen ||
                             !req->buf && (char*)lpvStatusInformation >= req->in.data() && (char*)lpvStatusInformation + dwStatusInformationLength <= req->in.data() + req->in.size());
                  }
  
                  if (!WinHttpQueryDataAvailable(httpctx->hRequest, NULL))
                  {
                      LOG_err << "Error on WinHttpQueryDataAvailable. Code: " << GetLastError();
                      httpio->cancel(req);
                      httpio->httpevent();
                  }
              }
              break;
  
          case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
          {
              DWORD statusCode;
              DWORD statusCodeSize = sizeof(statusCode);
  
              if (!WinHttpQueryHeaders(httpctx->hRequest,
                                       WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
                                       WINHTTP_HEADER_NAME_BY_INDEX,
                                       &statusCode,
                                       &statusCodeSize,
                                       WINHTTP_NO_HEADER_INDEX))
              {
                  LOG_err << "Error getting headers. Code: " << GetLastError();
                  httpio->cancel(req);
                  httpio->httpevent();
              }
              else
              {
                  LOG_verbose << "Headers available";
  
                  req->httpstatus = statusCode;
  
                  if (req->httpio)
                  {
                      req->httpio->lastdata = Waiter::ds;
                      req->lastdata = Waiter::ds;
                  }
  
                  if (!req->buf)
                  {
                      DWORD timeLeft;
                      DWORD timeLeftSize = sizeof(timeLeft);
                      if (WinHttpQueryHeaders(httpctx->hRequest,
                                              WINHTTP_QUERY_CUSTOM | WINHTTP_QUERY_FLAG_NUMBER,
                                              L"X-MEGA-Time-Left",
                                              &timeLeft,
                                              &timeLeftSize,
                                              WINHTTP_NO_HEADER_INDEX))
                      {
                          LOG_verbose << "Seconds left until more bandwidth quota: " << timeLeft;
                          req->timeleft = timeLeft;
                      }
  
                      // check for gzip content encoding
                      WCHAR contentEncoding[16];
                      DWORD contentEncodingSize = sizeof(contentEncoding);
  
                      httpctx->gzip = WinHttpQueryHeaders(httpctx->hRequest,
                          WINHTTP_QUERY_CONTENT_ENCODING,
                          WINHTTP_HEADER_NAME_BY_INDEX,
                          &contentEncoding,
                          &contentEncodingSize,
                          WINHTTP_NO_HEADER_INDEX)
                          && !wcscmp(contentEncoding, L"gzip");
  
  
                      // obtain original content length - always present if gzip is in use
                      DWORD contentLength;
                      DWORD contentLengthSize = sizeof(contentLength);
  
                      if (WinHttpQueryHeaders(httpctx->hRequest,
                                              WINHTTP_QUERY_CUSTOM | WINHTTP_QUERY_FLAG_NUMBER,
                                              L"Original-Content-Length",
                                              &contentLength,
                                              &contentLengthSize,
                                              WINHTTP_NO_HEADER_INDEX))
                      {
                          LOG_verbose << "Content-Length: " << contentLength;
                          req->setcontentlength(contentLength);
  
                          if (httpctx->gzip)
                          {
                              LOG_verbose << "Using GZIP";
  
                              httpctx->z.zalloc = Z_NULL;
                              httpctx->z.zfree = Z_NULL;
                              httpctx->z.opaque = Z_NULL;
                              httpctx->z.avail_in = 0;
                              httpctx->z.next_in = Z_NULL;
  
                              inflateInit2(&httpctx->z, MAX_WBITS+16);
  
                              req->in.resize(contentLength);
                              httpctx->z.avail_out = contentLength;
                              httpctx->z.next_out = (unsigned char*)req->in.data();
                          }
                          else
                          {
                              LOG_verbose << "Not using GZIP";
                          }
                      }
                      else if (httpctx->gzip)
                      {
                          LOG_err << "Content is gzipped but length is missing";
                          httpio->cancel(req);
                          httpio->httpevent();
                          break;
                      }
                      else
                      {
                          // not zipped and no original size - check for Content-Length instead, plain data arriving (eg. keep-alive)
                          if (WinHttpQueryHeaders(httpctx->hRequest,
                              WINHTTP_QUERY_CUSTOM | WINHTTP_QUERY_FLAG_NUMBER,
                              L"Content-Length",
                              &contentLength,
                              &contentLengthSize,
                              WINHTTP_NO_HEADER_INDEX))
                          {
                              LOG_verbose << "Content-Length: " << contentLength;
                              req->setcontentlength(contentLength);
                          }
                          else
                          {
                              LOG_verbose << "Content-Length not available";
                          }
                      }
                  }
  
                  if (!WinHttpQueryDataAvailable(httpctx->hRequest, NULL))
                  {
                      LOG_err << "Unable to query data. Code: " << GetLastError();
  
                      httpio->cancel(req);
                      httpio->httpevent();
                  }
                  else if (httpio->waiter && httpio->noinetds)
                  {
                      httpio->inetstatus(true);
                  }
              }
  
              break;
          }
  
          case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
          {
              DWORD e = GetLastError();
  
              LOG_err << "Request error. Code: " << e;
  
              if (httpio->waiter && e != ERROR_WINHTTP_TIMEOUT)
              {
                  httpio->inetstatus(false);
              }
          }
          // fall through
          case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE:
              if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_SECURE_FAILURE)
              {
                  LOG_err << "Security check failed. Code: " << (*(DWORD*)lpvStatusInformation);
              }
  
              httpio->cancel(req);
              httpio->httpevent();
              break;
  
          case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST:
          {
              if (MegaClient::disablepkp || !req->protect)
              {
                  break;
              }
  
              PCCERT_CONTEXT cert;
              DWORD len = sizeof cert;
  
              if (WinHttpQueryOption(httpctx->hRequest, WINHTTP_OPTION_SERVER_CERT_CONTEXT, &cert, &len))
              {
                  CRYPT_BIT_BLOB* pkey = &cert->pCertInfo->SubjectPublicKeyInfo.PublicKey;
  
                  // this is an SSL connection: verify public key to prevent MITM attacks
                  if (pkey->cbData != 270
                   || (memcmp(pkey->pbData, "\x30\x82\x01\x0a\x02\x82\x01\x01\x00" APISSLMODULUS1
                                            "\x02" APISSLEXPONENTSIZE APISSLEXPONENT, 270)
                    && memcmp(pkey->pbData, "\x30\x82\x01\x0a\x02\x82\x01\x01\x00" APISSLMODULUS2
                                            "\x02" APISSLEXPONENTSIZE APISSLEXPONENT, 270)))
                  {
                      LOG_err << "Invalid public key. Possible MITM attack!!";
                      req->sslcheckfailed = true;
  
                      CertFreeCertificateContext(cert);
                      httpio->cancel(req);
                      httpio->httpevent();
                      break;
                  }
  
                  CertFreeCertificateContext(cert);
              }
  
              break;
          }
  
          case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
          case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE:
              if (httpctx->postpos < httpctx->postlen)
              {
                  LOG_verbose << "Chunk written";
                  unsigned pos = httpctx->postpos;
                  unsigned t = httpctx->postlen - pos;
  
                  if (t > HTTP_POST_CHUNK_SIZE)
                  {
                      t = HTTP_POST_CHUNK_SIZE;
                  }
  
                  httpctx->postpos += t;
  
                  if (!WinHttpWriteData(httpctx->hRequest, (LPVOID)(httpctx->postdata + pos), t, NULL))
                  {
                      LOG_err << "Error writing data. Code: " << GetLastError();
                      req->httpio->cancel(req);
                  }
  
                  httpio->httpevent();
              }
              else
              {
                  LOG_verbose << "Request written";
                  if (!WinHttpReceiveResponse(httpctx->hRequest, NULL))
                  {
                      LOG_err << "Error receiving response. Code: " << GetLastError();
                      httpio->cancel(req);
                      httpio->httpevent();
                  }
  
                  httpctx->postdata = NULL;
              }
      }
  
      httpio->unlock();
  }
  
  // POST request to URL
  void WinHttpIO::post(HttpReq* req, const char* data, unsigned len)
  {
      if (SimpleLogger::logCurrentLevel >= logDebug)
      {
          string safeurl = req->posturl;
          size_t sid = safeurl.find("sid=");
          if (sid != string::npos)
          {
              sid += 4;
              size_t end = safeurl.find("&", sid);
              if (end == string::npos)
              {
                  end = safeurl.size();
              }
              memset((char *)safeurl.data() + sid, 'X', end - sid);
          }
          LOG_debug << "POST target URL: " << safeurl;
      }
  
      if (req->binary)
      {
          LOG_debug << req->logname << "[sending " << (data ? len : req->out->size()) << " bytes of raw data]";
      }
      else
      {
          LOG_debug << req->logname << "Sending: " << *req->out;
      }
  
      WinHttpContext* httpctx;
  
      WCHAR szURL[8192];
      WCHAR szHost[256];
      URL_COMPONENTS urlComp = { sizeof urlComp };
  
      urlComp.lpszHostName = szHost;
      urlComp.dwHostNameLength = sizeof szHost / sizeof *szHost;
      urlComp.dwUrlPathLength = (DWORD)-1;
      urlComp.dwSchemeLength = (DWORD)-1;
  
      httpctx = new WinHttpContext;
  
      httpctx->httpio = this;
      httpctx->req = req;
      httpctx->gzip = false;
  
      req->httpiohandle = (void*)httpctx;
  
      if (MultiByteToWideChar(CP_UTF8, 0, req->posturl.c_str(), -1, szURL,
                              sizeof szURL / sizeof *szURL)
       && WinHttpCrackUrl(szURL, 0, 0, &urlComp))
      {
          if ((httpctx->hConnect = WinHttpConnect(hSession, szHost, urlComp.nPort, 0)))
          {
              httpctx->hRequest = WinHttpOpenRequest(httpctx->hConnect, L"POST",
                                                     urlComp.lpszUrlPath, NULL,
                                                     WINHTTP_NO_REFERER,
                                                     WINHTTP_DEFAULT_ACCEPT_TYPES,
                                                     (urlComp.nScheme == INTERNET_SCHEME_HTTPS)
                                                     ? WINHTTP_FLAG_SECURE
                                                     : 0);
  
              if (httpctx->hRequest)
              {
                  if (proxyUsername.size())
                  {
                      LOG_verbose << "Setting proxy credentials";
  
                      WinHttpSetCredentials(httpctx->hRequest, WINHTTP_AUTH_TARGET_PROXY,
                                            WINHTTP_AUTH_SCHEME_BASIC,
                                            (LPWSTR)proxyUsername.data(), (LPWSTR)proxyPassword.data(), NULL);
                  }
  
                  WinHttpSetTimeouts(httpctx->hRequest, 58000, 58000, 0, 0);
  
                  WinHttpSetStatusCallback(httpctx->hRequest, asynccallback,
                                           WINHTTP_CALLBACK_FLAG_DATA_AVAILABLE
                                         | WINHTTP_CALLBACK_FLAG_READ_COMPLETE
                                         | WINHTTP_CALLBACK_FLAG_HEADERS_AVAILABLE
                                         | WINHTTP_CALLBACK_FLAG_REQUEST_ERROR
                                         | WINHTTP_CALLBACK_FLAG_SECURE_FAILURE
                                         | WINHTTP_CALLBACK_FLAG_SENDREQUEST_COMPLETE
                                         | WINHTTP_CALLBACK_FLAG_SEND_REQUEST
                                         | WINHTTP_CALLBACK_FLAG_WRITE_COMPLETE
                                         | WINHTTP_CALLBACK_FLAG_HANDLES,
                                           0);
  
                  LPCWSTR pwszHeaders = req->type == REQ_JSON || !req->buf
                                      ? L"Content-Type: application/json\r\nAccept-Encoding: gzip"
                                      : L"Content-Type: application/octet-stream";
  
                  httpctx->postlen = int(data ? len : req->out->size());
                  httpctx->postdata = data ? data : req->out->data();
  
                  if (urlComp.nPort == 80)
                  {
                      LOG_verbose << "HTTP connection";
  
                      // HTTP connection: send a chunk of data immediately
                      httpctx->postpos = (httpctx->postlen < HTTP_POST_CHUNK_SIZE)
                                        ? httpctx->postlen
                                        : HTTP_POST_CHUNK_SIZE;
                  }
                  else
                  {
                      LOG_verbose << "HTTPS connection";
  
                      // HTTPS connection: ignore certificate errors, send no data yet
                      DWORD flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID
                                  | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID
                                  | SECURITY_FLAG_IGNORE_UNKNOWN_CA;
  
                      WinHttpSetOption(httpctx->hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &flags, sizeof flags);
  
                      httpctx->postpos = 0;
                  }
  
                  if (WinHttpSendRequest(httpctx->hRequest, pwszHeaders,
                                         DWORD(wcslen(pwszHeaders)),
                                         (LPVOID)httpctx->postdata,
                                         httpctx->postpos,
                                         httpctx->postlen,
                                         (DWORD_PTR)httpctx))
                  {
                      LOG_verbose << "Request sent";
                      req->status = REQ_INFLIGHT;
                      return;
                  }
  
                  LOG_err << "Error sending request: " << req->posturl << "  Code: " << GetLastError();
              }
              else
              {
                  LOG_err << "Error opening request: " << req->posturl << "  Code: " << GetLastError();
              }
          }
          else
          {
              LOG_err << "Error connecting to " << req->posturl << "  Code: " << GetLastError();
              httpctx->hRequest = NULL;
          }
      }
      else
      {
          LOG_err << "Error parsing POST URL: " << req->posturl << "  Code: " << GetLastError();
          httpctx->hRequest = NULL;
          httpctx->hConnect = NULL;
      }
  
      LOG_err << "Request failed";
      req->status = REQ_FAILURE;
  }
  
  // cancel pending HTTP request
  void WinHttpIO::cancel(HttpReq* req)
  {
      WinHttpContext* httpctx;
  
      if ((httpctx = (WinHttpContext*)req->httpiohandle))
      {
          httpctx->req = NULL;
  
          req->httpstatus = 0;
          req->status = REQ_FAILURE;
          req->httpiohandle = NULL;
  
          if (httpctx->hConnect)
          {
              WinHttpCloseHandle(httpctx->hConnect);
          }
  
          if (httpctx->hRequest)
          {
              WinHttpCloseHandle(httpctx->hRequest);
          }
      }
  }
  
  // supply progress information on POST data
  m_off_t WinHttpIO::postpos(void* handle)
  {
      return ((WinHttpContext*)handle)->postpos;
  }
  
  // process events
  bool WinHttpIO::doio()
  {
      return false;
  }
  } // namespace
  