<commit_before>/* bzflag
 * Copyright (c) 1993 - 2005 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named LICENSE that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

// Provide BZFS with a list server connection

/* class header */
#include "ListServerConnection.h"

/* system implementation headers */
#include <string.h>
#include <string>
#include <math.h>
#include <errno.h>

/* common implementation headers */
#include "bzfio.h"
#include "version.h"
#include "TextUtils.h"
#include "Protocol.h"
#include "GameKeeper.h"

/* local implementation headers */
#include "CmdLineOptions.h"

// FIXME remove externs!
extern PingPacket getTeamCounts();
extern uint16_t curMaxPlayers;
extern void sendMessage(int playerIndex, PlayerId targetPlayer,
			const char *message);
extern void sendPlayerInfo(void);
extern void sendIPUpdate(int targetPlayer, int playerIndex);
extern CmdLineOptions *clOptions;

const int ListServerLink::NotConnected = -1;

ListServerLink::ListServerLink(std::string listServerURL,
			       std::string publicizedAddress,
			       std::string publicizedTitle)
{
  // parse url
  std::string protocol, _hostname, _pathname;
  int _port = 80;
  bool useDefault = false;

  // use default if it can't be parsed
  if (!BzfNetwork::parseURL(listServerURL, protocol, _hostname, _port,
			    _pathname))
    useDefault = true;

  // use default if wrong protocol or invalid port
  if ((protocol != "http") || (_port < 1) || (_port > 65535))
    useDefault = true;

  // use default if bad address
  Address _address = Address::getHostAddress(_hostname);
  if (_address.isAny())
    useDefault = true;

  // parse default list server URL if we need to; assume default works
  if (useDefault) {
    BzfNetwork::parseURL(DefaultListServerURL, protocol, _hostname, _port,
			 _pathname);
    DEBUG1("Provided list server URL (%s) is invalid.  Using default of %s.\n",
	   listServerURL.c_str(), DefaultListServerURL);
  }

  // add to list
  address          = _address;
  port             = _port;
  pathname         = _pathname;
  hostname         = _hostname;
  this->linkSocket = NotConnected;

  if (clOptions->pingInterface != "")
    localAddress = Address::getHostAddress(clOptions->pingInterface);
  publicizeAddress     = publicizedAddress;
  publicizeDescription = publicizedTitle;
  //if this c'tor is called, it's safe to publicize
  publicizeServer      = true;

  // schedule initial ADD message
  queueMessage(ListServerLink::ADD);
}

ListServerLink::ListServerLink()
{
  // does not create a usable link, so checks should be placed
  // in  all public member functions to ensure that nothing tries
  // to happen if publicizeServer is false
  this->linkSocket = NotConnected;
  this->publicizeServer = false;
}

ListServerLink::~ListServerLink()
{
  // now tell the list server that we're going away.  this can
  // take some time but we don't want to wait too long.  we do
  // our own multiplexing loop and wait for a maximum of 3 seconds
  // total.

  // if we aren't supposed to be publicizing, skip the whole thing
  // and don't waste 3 seconds.
  if (!publicizeServer)
    return;

  queueMessage(ListServerLink::REMOVE);
  TimeKeeper start = TimeKeeper::getCurrent();
  do {
    // compute timeout
    float waitTime = float(3.0 - (TimeKeeper::getCurrent() - start));
    if (waitTime <= 0.0f)
      break;
    if (!isConnected()) //queueMessage should have connected us
      break;
    // check for list server socket connection
    int fdMax = -1;
    fd_set write_set;
    fd_set read_set;
    FD_ZERO(&write_set);
    FD_ZERO(&read_set);
    if (phase == ListServerLink::CONNECTING) {
      FD_SET((unsigned int)linkSocket, &write_set);
    } else {
      FD_SET((unsigned int)linkSocket, &read_set);
    }
    fdMax = linkSocket;

    // wait for socket to connect or timeout
    struct timeval timeout;
    timeout.tv_sec = long(floorf(waitTime));
    timeout.tv_usec = long(1.0e+6f * (waitTime - floorf(waitTime)));
    int nfound = select(fdMax + 1, (fd_set*)&read_set, (fd_set*)&write_set,
			0, &timeout);
    if (nfound == 0)
      // Time has gone, close and go
      break;
    // check for connection to list server
    if (FD_ISSET(linkSocket, &write_set))
      sendQueuedMessages();
    else if (FD_ISSET(linkSocket, &read_set))
      read();
  } while (true);

  // stop list server communication
  closeLink();
}

void ListServerLink::closeLink()
{
  if (isConnected()) {
    close(linkSocket);
    DEBUG4("Closing List Server\n");
    linkSocket = NotConnected;
  }
}

void ListServerLink::read()
{
  if (isConnected()) {
    char buf[2048];
    int bytes = recv(linkSocket, buf, sizeof(buf)-1, 0);
    // TODO don't close unless we've got it all
    closeLink();
    buf[bytes]=0;
    char* base = buf;
    static char *tokGoodIdentifier = "TOKGOOD: ";
    static char *tokBadIdentifier = "TOKBAD: ";
    // walks entire reply including HTTP headers
    while (*base) {
      // find next newline
      char* scan = base;
      while (*scan && *scan != '\r' && *scan != '\n') scan++;
      // if no newline then no more complete replies
      if (*scan != '\r' && *scan != '\n') break;
      while (*scan && (*scan == '\r' || *scan == '\n')) *scan++ = '\0';
      DEBUG4("Got line: \"%s\"\n", base);
      // TODO don't do this if we don't want central logins
      if (strncmp(base, tokGoodIdentifier, strlen(tokGoodIdentifier)) == 0) {
	char *callsign, *group;
	callsign = base + strlen(tokGoodIdentifier);
	DEBUG3("Got: %s\n", base);
	group = callsign;
	while (*group && (*group != ':')) group++;
	while (*group && (*group == ':')) *group++ = 0;
	int playerIndex = GameKeeper::Player::getPlayerIDByName(callsign);
	if (playerIndex < curMaxPlayers) {
	  GameKeeper::Player *playerData
	    = GameKeeper::Player::getPlayerByIndex(playerIndex);
	  if (playerData != NULL) {
	    // don't accept the global auth if there's a local account
	    // of the same name and the local account is not marked as
	    // being the same as the global account
	    if (!playerData->accessInfo.hasRealPassword()
		|| playerData->accessInfo.getUserInfo(callsign)
		.hasGroup("LOCAL.GLOBAL")) {
	      if (!playerData->accessInfo.isRegistered())
		playerData->accessInfo.storeInfo(NULL);
	      playerData->accessInfo.setPermissionRights();
	      while (*group) {
		char *nextgroup = group;
		while (*nextgroup && (*nextgroup != ':')) nextgroup++;
		while (*nextgroup && (*nextgroup == ':')) *nextgroup++ = 0;
		playerData->accessInfo.addGroup(group);
		group = nextgroup;
	      }
		  playerData->authentication.global(true);
	      sendMessage(ServerPlayer, playerIndex, "Global login approved!");
	      sendIPUpdate(playerIndex, -1);
	      sendPlayerInfo();
	    } else {
	      sendMessage(ServerPlayer, playerIndex, "Global login rejected. "
			  "This callsign is registered locally on this "
			  "server.");
	      sendMessage(ServerPlayer, playerIndex,
			  "If the local account is yours, "
			  "/identify, /deregister and reconnnect, "
			  "or ask an admin for the LOCAL.GLOBAL group.");
	      sendMessage(ServerPlayer, playerIndex,
			  "If it is not yours, please ask an admin "
			  "to deregister it so that you may use your global "
			  "callsign.");
	    }
	    playerData->setNeedThisHostbanChecked(true);
	    playerData->player.clearToken();
	  }
	}
      } else if (!strncmp(base, tokBadIdentifier, strlen(tokBadIdentifier))) {
	char *callsign;
	callsign = base + strlen(tokBadIdentifier);
	int playerIndex = GameKeeper::Player::getPlayerIDByName(callsign);
	DEBUG3("Got: [%d] %s\n", playerIndex, base);
	if (playerIndex < curMaxPlayers) {
	  GameKeeper::Player *playerData
	    = GameKeeper::Player::getPlayerByIndex(playerIndex);
	  sendMessage(ServerPlayer, playerIndex,
		      "Global login rejected, bad token.");
	  if (playerData != NULL) {
	    playerData->setNeedThisHostbanChecked(true);
	    playerData->player.clearToken();
	  }
	}
      }

      // next reply
      base = scan;
    }
    if (nextMessageType != ListServerLink::NONE) {
      // There was a pending request arrived after we write:
      // we should redo all the stuff
      openLink();
    }
  }
}

void ListServerLink::openLink()
{
  // start opening connection if not already doing so
  if (!isConnected()) {
    linkSocket = socket(AF_INET, SOCK_STREAM, 0);
    DEBUG4("Opening List Server\n");
    if (!isConnected()) {
      return;
    }

    // set to non-blocking for connect
    if (BzfNetwork::setNonBlocking(linkSocket) < 0) {
      closeLink();
      return;
    }

    // Make our connection come from our serverAddress in case we have
    // multiple/masked IPs so the list server can verify us.
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr = localAddress;

    // assign the address to the socket
    if (bind(linkSocket, (CNCTType*)&addr, sizeof(addr)) < 0) {
      closeLink();
      return;
    }

    // connect.  this should fail with EINPROGRESS but check for
    // success just in case.
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(port);
    addr.sin_addr   = address;
    if (connect(linkSocket, (CNCTType*)&addr, sizeof(addr)) < 0) {
#if defined(_WIN32)
#undef EINPROGRESS
#define EINPROGRESS EWOULDBLOCK
#endif
      if (getErrno() != EINPROGRESS) {
	nerror("connecting to list server");
	// try to lookup dns name again in case it moved
	this->address = Address::getHostAddress(this->hostname);
	closeLink();
      } else {
	phase = CONNECTING;
      }
    } else {
      // shouldn't arrive here. Just in case, clean
      DEBUG3("list server connect and close?\n");
      closeLink();
    }
  }
}

void ListServerLink::queueMessage(MessageType type)
{
  // ignore if the server is not public
  if (!publicizeServer) return;

  // Open network connection only if closed
  if (!isConnected()) openLink();

  // record next message to send.
  nextMessageType = type;
}

void ListServerLink::sendQueuedMessages()
{
  if (!isConnected())
    return;

  if (nextMessageType == ListServerLink::ADD) {
    DEBUG3("Queuing ADD message to list server\n");
    addMe(getTeamCounts(), publicizeAddress,
	  TextUtils::url_encode(publicizeDescription));
    lastAddTime = TimeKeeper::getCurrent();
  } else if (nextMessageType == ListServerLink::REMOVE) {
    DEBUG3("Queuing REMOVE message to list server\n");
    removeMe(publicizeAddress);
  }
}

void ListServerLink::addMe(PingPacket pingInfo,
			   std::string publicizedAddress,
			   std::string publicizedTitle)
{
  std::string msg;

  // encode ping reply as ascii hex digits plus NULL
  char gameInfo[PingPacketHexPackedSize + 1];
  pingInfo.packHex(gameInfo);

  // TODO we probably should convert to a POST. List server now allows either
  // send ADD message (must send blank line)
  msg = TextUtils::format
    ("POST %s?action=ADD&nameport=%s&version=%s&gameinfo=%s&build=%s",
    pathname.c_str(), publicizedAddress.c_str(),
    getServerVersion(), gameInfo,
    getAppVersion());
  msg += "&checktokens=";
  // callsign1@ip1=token1%0D%0Acallsign2@ip2=token2%0D%0A
  for (int i = 0; i < curMaxPlayers; i++) {
    GameKeeper::Player *playerData = GameKeeper::Player::getPlayerByIndex(i);
    if (!playerData)
      continue;
    NetHandler *handler = playerData->netHandler;
    std::string encodedCallsign
      = TextUtils::url_encode(playerData->player.getCallSign());
    if (strlen(playerData->player.getCallSign())
	&& strlen(playerData->player.getToken())) {
      msg += encodedCallsign;
      Address addr = handler->getIPAddress();
      if (!addr.isPrivate()) {
	msg += "@";
	msg += handler->getTargetIP();
      }
      msg += "=";
      msg += playerData->player.getToken();
      msg += "%0D%0A";
    }
  }
  // *groups=GROUP0%0D%0AGROUP1%0D%0A
  msg += "&groups=";
  PlayerAccessMap::iterator itr = groupAccess.begin();
  for ( ; itr != groupAccess.end(); itr++) {
    if (itr->first.substr(0, 6) != "LOCAL.") {
      msg += itr->first.c_str();
      msg += "%0D%0A";
    }
  }

  msg += TextUtils::format("&title=%s HTTP/1.1\r\n"
      "User-Agent: bzfs %s\r\n"
      "Host: %s\r\n"
      "Cache-Control: no-cache\r\n"
      "Connection: close\r\n"
      "\r\n",
    publicizedTitle.c_str(),
    getAppVersion(),
    hostname.c_str());
  sendLSMessage(msg);
}

void ListServerLink::removeMe(std::string publicizedAddress)
{
  std::string msg;
  // send REMOVE (must send blank line)
  msg = TextUtils::format("POST %s?action=REMOVE&nameport=%s HTTP/1.1\r\n"
    "User-Agent: bzfs %s\r\n"
    "Host: %s\r\n"
    "Cache-Control: no-cache\r\n"
    "Connection: close\r\n"
    "\r\n",
    pathname.c_str(),
    publicizedAddress.c_str(),
    getAppVersion(),
    hostname.c_str());
  sendLSMessage(msg);
}

void ListServerLink::sendLSMessage(std::string message)
{
  const int bufsize = 4096;
  char msg[bufsize];
  strncpy(msg, message.c_str(), bufsize);
  msg[bufsize - 1] = 0;
  if (strlen(msg) > 0) {
    DEBUG3("%s\n", msg);
    if (send(linkSocket, msg, strlen(msg), 0) == -1) {
      perror("List server send failed");
      DEBUG3("Unable to send to the list server!\n");
      closeLink();
    } else {
      nextMessageType = ListServerLink::NONE;
      phase	   = ListServerLink::WRITTEN;
    }
  } else {
    closeLink();
  }
}

// Local Variables: ***
// mode:C++ ***
// tab-width: 8 ***
// c-basic-offset: 2 ***
// indent-tabs-mode: t ***
// End: ***
// ex: shiftwidth=2 tabstop=8
<commit_msg>Change Listserver requests from 'GET' to 'POST'. libcurl NOT used.<commit_after>/* bzflag
 * Copyright (c) 1993 - 2005 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named LICENSE that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

// Provide BZFS with a list server connection

/* class header */
#include "ListServerConnection.h"

/* system implementation headers */
#include <string.h>
#include <string>
#include <math.h>
#include <errno.h>

/* common implementation headers */
#include "bzfio.h"
#include "version.h"
#include "TextUtils.h"
#include "Protocol.h"
#include "GameKeeper.h"

/* local implementation headers */
#include "CmdLineOptions.h"

// FIXME remove externs!
extern PingPacket getTeamCounts();
extern uint16_t curMaxPlayers;
extern void sendMessage(int playerIndex, PlayerId targetPlayer,
			const char *message);
extern void sendPlayerInfo(void);
extern void sendIPUpdate(int targetPlayer, int playerIndex);
extern CmdLineOptions *clOptions;

const int ListServerLink::NotConnected = -1;

ListServerLink::ListServerLink(std::string listServerURL,
			       std::string publicizedAddress,
			       std::string publicizedTitle)
{
  // parse url
  std::string protocol, _hostname, _pathname;
  int _port = 80;
  bool useDefault = false;

  // use default if it can't be parsed
  if (!BzfNetwork::parseURL(listServerURL, protocol, _hostname, _port,
			    _pathname))
    useDefault = true;

  // use default if wrong protocol or invalid port
  if ((protocol != "http") || (_port < 1) || (_port > 65535))
    useDefault = true;

  // use default if bad address
  Address _address = Address::getHostAddress(_hostname);
  if (_address.isAny())
    useDefault = true;

  // parse default list server URL if we need to; assume default works
  if (useDefault) {
    BzfNetwork::parseURL(DefaultListServerURL, protocol, _hostname, _port,
			 _pathname);
    DEBUG1("Provided list server URL (%s) is invalid.  Using default of %s.\n",
	   listServerURL.c_str(), DefaultListServerURL);
  }

  // add to list
  address          = _address;
  port             = _port;
  pathname         = _pathname;
  hostname         = _hostname;
  this->linkSocket = NotConnected;

  if (clOptions->pingInterface != "")
    localAddress = Address::getHostAddress(clOptions->pingInterface);
  publicizeAddress     = publicizedAddress;
  publicizeDescription = publicizedTitle;
  //if this c'tor is called, it's safe to publicize
  publicizeServer      = true;

  // schedule initial ADD message
  queueMessage(ListServerLink::ADD);
}

ListServerLink::ListServerLink()
{
  // does not create a usable link, so checks should be placed
  // in  all public member functions to ensure that nothing tries
  // to happen if publicizeServer is false
  this->linkSocket = NotConnected;
  this->publicizeServer = false;
}

ListServerLink::~ListServerLink()
{
  // now tell the list server that we're going away.  this can
  // take some time but we don't want to wait too long.  we do
  // our own multiplexing loop and wait for a maximum of 3 seconds
  // total.

  // if we aren't supposed to be publicizing, skip the whole thing
  // and don't waste 3 seconds.
  if (!publicizeServer)
    return;

  queueMessage(ListServerLink::REMOVE);
  TimeKeeper start = TimeKeeper::getCurrent();
  do {
    // compute timeout
    float waitTime = float(3.0 - (TimeKeeper::getCurrent() - start));
    if (waitTime <= 0.0f)
      break;
    if (!isConnected()) //queueMessage should have connected us
      break;
    // check for list server socket connection
    int fdMax = -1;
    fd_set write_set;
    fd_set read_set;
    FD_ZERO(&write_set);
    FD_ZERO(&read_set);
    if (phase == ListServerLink::CONNECTING) {
      FD_SET((unsigned int)linkSocket, &write_set);
    } else {
      FD_SET((unsigned int)linkSocket, &read_set);
    }
    fdMax = linkSocket;

    // wait for socket to connect or timeout
    struct timeval timeout;
    timeout.tv_sec = long(floorf(waitTime));
    timeout.tv_usec = long(1.0e+6f * (waitTime - floorf(waitTime)));
    int nfound = select(fdMax + 1, (fd_set*)&read_set, (fd_set*)&write_set,
			0, &timeout);
    if (nfound == 0)
      // Time has gone, close and go
      break;
    // check for connection to list server
    if (FD_ISSET(linkSocket, &write_set))
      sendQueuedMessages();
    else if (FD_ISSET(linkSocket, &read_set))
      read();
  } while (true);

  // stop list server communication
  closeLink();
}

void ListServerLink::closeLink()
{
  if (isConnected()) {
    close(linkSocket);
    DEBUG4("Closing List Server\n");
    linkSocket = NotConnected;
  }
}

void ListServerLink::read()
{
  if (isConnected()) {
    char buf[2048];
    int bytes = recv(linkSocket, buf, sizeof(buf)-1, 0);
    // TODO don't close unless we've got it all
    closeLink();
    buf[bytes]=0;
    char* base = buf;
    static char *tokGoodIdentifier = "TOKGOOD: ";
    static char *tokBadIdentifier = "TOKBAD: ";
    // walks entire reply including HTTP headers
    while (*base) {
      // find next newline
      char* scan = base;
      while (*scan && *scan != '\r' && *scan != '\n') scan++;
      // if no newline then no more complete replies
      if (*scan != '\r' && *scan != '\n') break;
      while (*scan && (*scan == '\r' || *scan == '\n')) *scan++ = '\0';
      DEBUG4("Got line: \"%s\"\n", base);
      // TODO don't do this if we don't want central logins
      if (strncmp(base, tokGoodIdentifier, strlen(tokGoodIdentifier)) == 0) {
	char *callsign, *group;
	callsign = base + strlen(tokGoodIdentifier);
	DEBUG3("Got: %s\n", base);
	group = callsign;
	while (*group && (*group != ':')) group++;
	while (*group && (*group == ':')) *group++ = 0;
	int playerIndex = GameKeeper::Player::getPlayerIDByName(callsign);
	if (playerIndex < curMaxPlayers) {
	  GameKeeper::Player *playerData
	    = GameKeeper::Player::getPlayerByIndex(playerIndex);
	  if (playerData != NULL) {
	    // don't accept the global auth if there's a local account
	    // of the same name and the local account is not marked as
	    // being the same as the global account
	    if (!playerData->accessInfo.hasRealPassword()
		|| playerData->accessInfo.getUserInfo(callsign)
		.hasGroup("LOCAL.GLOBAL")) {
	      if (!playerData->accessInfo.isRegistered())
		playerData->accessInfo.storeInfo(NULL);
	      playerData->accessInfo.setPermissionRights();
	      while (*group) {
		char *nextgroup = group;
		while (*nextgroup && (*nextgroup != ':')) nextgroup++;
		while (*nextgroup && (*nextgroup == ':')) *nextgroup++ = 0;
		playerData->accessInfo.addGroup(group);
		group = nextgroup;
	      }
		  playerData->authentication.global(true);
	      sendMessage(ServerPlayer, playerIndex, "Global login approved!");
	      sendIPUpdate(playerIndex, -1);
	      sendPlayerInfo();
	    } else {
	      sendMessage(ServerPlayer, playerIndex, "Global login rejected. "
			  "This callsign is registered locally on this "
			  "server.");
	      sendMessage(ServerPlayer, playerIndex,
			  "If the local account is yours, "
			  "/identify, /deregister and reconnnect, "
			  "or ask an admin for the LOCAL.GLOBAL group.");
	      sendMessage(ServerPlayer, playerIndex,
			  "If it is not yours, please ask an admin "
			  "to deregister it so that you may use your global "
			  "callsign.");
	    }
	    playerData->setNeedThisHostbanChecked(true);
	    playerData->player.clearToken();
	  }
	}
      } else if (!strncmp(base, tokBadIdentifier, strlen(tokBadIdentifier))) {
	char *callsign;
	callsign = base + strlen(tokBadIdentifier);
	int playerIndex = GameKeeper::Player::getPlayerIDByName(callsign);
	DEBUG3("Got: [%d] %s\n", playerIndex, base);
	if (playerIndex < curMaxPlayers) {
	  GameKeeper::Player *playerData
	    = GameKeeper::Player::getPlayerByIndex(playerIndex);
	  sendMessage(ServerPlayer, playerIndex,
		      "Global login rejected, bad token.");
	  if (playerData != NULL) {
	    playerData->setNeedThisHostbanChecked(true);
	    playerData->player.clearToken();
	  }
	}
      }

      // next reply
      base = scan;
    }
    if (nextMessageType != ListServerLink::NONE) {
      // There was a pending request arrived after we write:
      // we should redo all the stuff
      openLink();
    }
  }
}

void ListServerLink::openLink()
{
  // start opening connection if not already doing so
  if (!isConnected()) {
    linkSocket = socket(AF_INET, SOCK_STREAM, 0);
    DEBUG4("Opening List Server\n");
    if (!isConnected()) {
      return;
    }

    // set to non-blocking for connect
    if (BzfNetwork::setNonBlocking(linkSocket) < 0) {
      closeLink();
      return;
    }

    // Make our connection come from our serverAddress in case we have
    // multiple/masked IPs so the list server can verify us.
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr = localAddress;

    // assign the address to the socket
    if (bind(linkSocket, (CNCTType*)&addr, sizeof(addr)) < 0) {
      closeLink();
      return;
    }

    // connect.  this should fail with EINPROGRESS but check for
    // success just in case.
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(port);
    addr.sin_addr   = address;
    if (connect(linkSocket, (CNCTType*)&addr, sizeof(addr)) < 0) {
#if defined(_WIN32)
#undef EINPROGRESS
#define EINPROGRESS EWOULDBLOCK
#endif
      if (getErrno() != EINPROGRESS) {
	nerror("connecting to list server");
	// try to lookup dns name again in case it moved
	this->address = Address::getHostAddress(this->hostname);
	closeLink();
      } else {
	phase = CONNECTING;
      }
    } else {
      // shouldn't arrive here. Just in case, clean
      DEBUG3("list server connect and close?\n");
      closeLink();
    }
  }
}

void ListServerLink::queueMessage(MessageType type)
{
  // ignore if the server is not public
  if (!publicizeServer) return;

  // Open network connection only if closed
  if (!isConnected()) openLink();

  // record next message to send.
  nextMessageType = type;
}

void ListServerLink::sendQueuedMessages()
{
  if (!isConnected())
    return;

  if (nextMessageType == ListServerLink::ADD) {
    DEBUG3("Queuing ADD message to list server\n");
    addMe(getTeamCounts(), publicizeAddress,
	  TextUtils::url_encode(publicizeDescription));
    lastAddTime = TimeKeeper::getCurrent();
  } else if (nextMessageType == ListServerLink::REMOVE) {
    DEBUG3("Queuing REMOVE message to list server\n");
    removeMe(publicizeAddress);
  }
}

void ListServerLink::addMe(PingPacket pingInfo,
			   std::string publicizedAddress,
			   std::string publicizedTitle)
{
  std::string msg;
  std::string hdr;

  // encode ping reply as ascii hex digits plus NULL
  char gameInfo[PingPacketHexPackedSize + 1];
  pingInfo.packHex(gameInfo);

  // send ADD message (must send blank line)
  msg = TextUtils::format
    ("action=ADD&nameport=%s&version=%s&gameinfo=%s&build=%s",
    publicizedAddress.c_str(),
    getServerVersion(), gameInfo,
    getAppVersion());
  msg += "&checktokens=";
  // callsign1@ip1=token1%0D%0Acallsign2@ip2=token2%0D%0A
  for (int i = 0; i < curMaxPlayers; i++) {
    GameKeeper::Player *playerData = GameKeeper::Player::getPlayerByIndex(i);
    if (!playerData)
      continue;
    NetHandler *handler = playerData->netHandler;
    std::string encodedCallsign
      = TextUtils::url_encode(playerData->player.getCallSign());
    if (strlen(playerData->player.getCallSign())
	&& strlen(playerData->player.getToken())) {
      msg += encodedCallsign;
      Address addr = handler->getIPAddress();
      if (!addr.isPrivate()) {
	      msg += "@";
	      msg += handler->getTargetIP();
      }
      msg += "=";
      msg += playerData->player.getToken();
      msg += "%0D%0A";
    }
  }
  // *groups=GROUP0%0D%0AGROUP1%0D%0A
  msg += "&groups=";
  PlayerAccessMap::iterator itr = groupAccess.begin();
  for ( ; itr != groupAccess.end(); itr++) {
    if (itr->first.substr(0, 6) != "LOCAL.") {
      msg += itr->first.c_str();
      msg += "%0D%0A";
    }
  }

  msg += TextUtils::format("&title=%s\r\n", publicizedTitle.c_str());
  hdr = TextUtils::format(
      "POST %s HTTP/1.1\r\n"
      "User-Agent: bzfs %s\r\n"
      "Host: %s\r\n"
      "Cache-Control: no-cache\r\n"
      "Connection: close\r\n"
      "Content-Type: application/x-www-form-urlencoded\r\n"
      "Content-Length: %d\r\n"
      "\r\n",
      pathname.c_str(), getAppVersion(), hostname.c_str(), msg.length());
  sendLSMessage(hdr+msg);
}

void ListServerLink::removeMe(std::string publicizedAddress)
{
  std::string msg;

  // send REMOVE (must send blank line)
  msg = TextUtils::format("action=REMOVE&nameport=%s\r\n",  publicizedAddress.c_str());
  sendLSMessage(TextUtils::format(
      "POST %s HTTP/1.1\r\n"
      "User-Agent: bzfs %s\r\n"
      "Host: %s\r\n"
      "Cache-Control: no-cache\r\n"
      "Connection: close\r\n"
      "Content-Type: application/x-www-form-urlencoded\r\n"
      "Content-Length: %d\r\n"
      "\r\n",
      pathname.c_str(), getAppVersion(), hostname.c_str(), msg.length())   + msg);
}

void ListServerLink::sendLSMessage(std::string message)
{
  const int bufsize = 4096;
  char msg[bufsize];
  strncpy(msg, message.c_str(), bufsize);
  msg[bufsize - 1] = 0;
  if (strlen(msg) > 0) {
    DEBUG3("%s\n", msg);
    if (send(linkSocket, msg, strlen(msg), 0) == -1) {
      perror("List server send failed");
      DEBUG3("Unable to send to the list server!\n");
      closeLink();
    } else {
      nextMessageType = ListServerLink::NONE;
      phase	   = ListServerLink::WRITTEN;
    }
  } else {
    closeLink();
  }
}

// Local Variables: ***
// mode:C++ ***
// tab-width: 8 ***
// c-basic-offset: 2 ***
// indent-tabs-mode: t ***
// End: ***
// ex: shiftwidth=2 tabstop=8
<|endoftext|>


<commit_before>/* bzflag
 * Copyright (c) 1993 - 2005 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named LICENSE that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

// Provide BZFS with a list server connection

/* class header */
#include "ListServerConnection.h"

/* system implementation headers */
#include <string.h>
#include <string>
#include <math.h>
#include <errno.h>
#include <set>

/* common implementation headers */
#include "bzfio.h"
#include "version.h"
#include "TextUtils.h"
#include "Protocol.h"
#include "GameKeeper.h"
#include "bzfsAPI.h"
#include "WorldEventManager.h"

/* local implementation headers */
#include "CmdLineOptions.h"

// FIXME remove externs!
extern PingPacket getTeamCounts();
extern uint16_t curMaxPlayers;
extern void sendMessage(int playerIndex, PlayerId targetPlayer,
			const char *message);
extern CmdLineOptions *clOptions;

const int ListServerLink::NotConnected = -1;

ListServerLink::ListServerLink(std::string listServerURL,
			       std::string publicizedAddress,
			       std::string publicizedTitle,
			       std::string _advertiseGroups)
{

  std::string bzfsUserAgent = "bzfs ";
  bzfsUserAgent            += getAppVersion();

  setURL(listServerURL);
  setUserAgent(bzfsUserAgent);
  setDNSCachingTime(-1);
  setTimeout(10);

  if (clOptions->pingInterface != "")
    setInterface(clOptions->pingInterface);

  publicizeAddress     = publicizedAddress;
  publicizeDescription = publicizedTitle;
  advertiseGroups      = _advertiseGroups;

  //if this c'tor is called, it's safe to publicize
  publicizeServer      = true;
  queuedRequest        = false;
  // schedule initial ADD message
  queueMessage(ListServerLink::ADD);
}

ListServerLink::ListServerLink()
{
  // does not create a usable link, so checks should be placed
  // in  all public member functions to ensure that nothing tries
  // to happen if publicizeServer is false
  publicizeServer = false;
}

ListServerLink::~ListServerLink()
{
  // now tell the list server that we're going away.  this can
  // take some time but we don't want to wait too long.  we do
  // our own multiplexing loop and wait for a maximum of 3 seconds
  // total.

  // if we aren't supposed to be publicizing, skip the whole thing
  // and don't waste 3 seconds.
  if (!publicizeServer)
    return;

  queueMessage(ListServerLink::REMOVE);
  for (int i = 0; i < 12; i++) {
    cURLManager::perform();
    if (!queuedRequest)
      break;
    TimeKeeper::sleep(0.25f);
  }
}

void ListServerLink::finalization(char *data, unsigned int length, bool good)
{
  queuedRequest = false;
  if (good && (length < 2048)) {
    char buf[2048];
    memcpy(buf, data, length);
    int bytes = length;
    buf[bytes]=0;
    char* base = buf;
    static char *tokGoodIdentifier = "TOKGOOD: ";
    static char *tokBadIdentifier = "TOKBAD: ";
    // walks entire reply including HTTP headers
    while (*base) {
      // find next newline
      char* scan = base;
      while (*scan && *scan != '\r' && *scan != '\n') scan++;
      // if no newline then no more complete replies
      if (*scan != '\r' && *scan != '\n') break;
      while (*scan && (*scan == '\r' || *scan == '\n')) *scan++ = '\0';
      DEBUG4("Got line: \"%s\"\n", base);
      // TODO don't do this if we don't want central logins
      if (strncmp(base, tokGoodIdentifier, strlen(tokGoodIdentifier)) == 0) {
	char *callsign, *group;
	callsign = base + strlen(tokGoodIdentifier);
	DEBUG3("Got: %s\n", base);
	group = callsign;
	while (*group && (*group != ':')) group++;
	while (*group && (*group == ':')) *group++ = 0;
	int playerIndex;
	GameKeeper::Player *playerData = NULL;
	for (playerIndex = 0; playerIndex < curMaxPlayers; playerIndex++) {
	  playerData = GameKeeper::Player::getPlayerByIndex(playerIndex);
	  if (!playerData)
	    continue;
	  if (playerData->_LSAState != GameKeeper::Player::checking)
	    continue;
	  if (!TextUtils::compare_nocase(playerData->player.getCallSign(),
					 callsign))
	    break;
	}
	if (playerIndex < curMaxPlayers) {
	  // don't accept the global auth if there's a local account
	  // of the same name and the local account is not marked as
	  // being the same as the global account
	  if (!playerData->accessInfo.hasRealPassword()
              || playerData->accessInfo.getUserInfo(callsign)
	      .hasGroup("LOCAL.GLOBAL")) {
	    playerData->_LSAState = GameKeeper::Player::verified;
	    if (!playerData->accessInfo.isRegistered())
	      playerData->accessInfo.storeInfo(NULL);
	    playerData->accessInfo.setPermissionRights();
	    while (*group) {
              char *nextgroup = group;
	      while (*nextgroup && (*nextgroup != ':')) nextgroup++;
	      while (*nextgroup && (*nextgroup == ':')) *nextgroup++ = 0;
	      playerData->accessInfo.addGroup(group);
	      group = nextgroup;
	    }
	    playerData->authentication.global(true);
	    sendMessage(ServerPlayer, playerIndex, "Global login approved!");
	  } else {
	    playerData->_LSAState = GameKeeper::Player::failed;
	    sendMessage(ServerPlayer, playerIndex, "Global login rejected. "
	                "This callsign is registered locally on this "
			"server.");
	    sendMessage(ServerPlayer, playerIndex,
			"If the local account is yours, "
			"/identify, /deregister and reconnnect, "
			"or ask an admin for the LOCAL.GLOBAL group.");
	    sendMessage(ServerPlayer, playerIndex,
			"If it is not yours, please ask an admin "
			"to deregister it so that you may use your global "
			"callsign.");
	  }
	  playerData->player.clearToken();
	}
      } else if (!strncmp(base, tokBadIdentifier, strlen(tokBadIdentifier))) {
	char *callsign;
	callsign = base + strlen(tokBadIdentifier);
	int playerIndex;
	GameKeeper::Player *playerData = NULL;
	for (playerIndex = 0; playerIndex < curMaxPlayers; playerIndex++) {
	  playerData = GameKeeper::Player::getPlayerByIndex(playerIndex);
	  if (!playerData)
	    continue;
	  if (playerData->_LSAState != GameKeeper::Player::checking)
	    continue;
	  if (!TextUtils::compare_nocase(playerData->player.getCallSign(),
					 callsign))
	    break;
	}
	DEBUG3("Got: [%d] %s\n", playerIndex, base);
	if (playerIndex < curMaxPlayers) {
	  playerData->_LSAState = GameKeeper::Player::failed;
	  sendMessage(ServerPlayer, playerIndex,
		      "Global login rejected, bad token.");
	  playerData->player.clearToken();
	}
      }

      // next reply
      base = scan;
    }
  }
  for (int i = 0; i < curMaxPlayers; i++) {
    GameKeeper::Player *playerData = GameKeeper::Player::getPlayerByIndex(i);
    if (!playerData)
      continue;
    if (playerData->_LSAState != GameKeeper::Player::checking)
      continue;
    playerData->_LSAState = GameKeeper::Player::timed;
  }
  if (nextMessageType != ListServerLink::NONE) {
    // There was a pending request arrived after we write:
    // we should redo all the stuff
    sendQueuedMessages();
  }
}

void ListServerLink::queueMessage(MessageType type)
{
  // ignore if the server is not public
  if (!publicizeServer) return;

  // record next message to send.
  nextMessageType = type;

  if (!queuedRequest)
    sendQueuedMessages();
  else
    DEBUG3("There is a message already queued to the list server: not sending this one yet.\n");
}

void ListServerLink::sendQueuedMessages()
{
  queuedRequest = true;
  if (nextMessageType == ListServerLink::ADD) {
    DEBUG3("Queuing ADD message to list server\n");

	bz_ListServerUpdateEvent	updateEvent;
	updateEvent.address = publicizeAddress;
	updateEvent.description = publicizeDescription;
	updateEvent.groups = advertiseGroups;

	worldEventManager.callEvents(bz_eListServerUpdateEvent,&updateEvent);

	addMe(getTeamCounts(), std::string(updateEvent.address.c_str()), std::string(updateEvent.description.c_str()), std::string(updateEvent.groups.c_str()));
    lastAddTime = TimeKeeper::getCurrent();
  } else if (nextMessageType == ListServerLink::REMOVE) {
    DEBUG3("Queuing REMOVE message to list server\n");
    removeMe(publicizeAddress);
  }
  nextMessageType = ListServerLink::NONE;
}

void ListServerLink::addMe(PingPacket pingInfo,
			   std::string publicizedAddress,
			   std::string publicizedTitle,
			   std::string _advertiseGroups)
{
  std::string msg;
  std::string hdr;

  // encode ping reply as ascii hex digits plus NULL
  char gameInfo[PingPacketHexPackedSize + 1];
  pingInfo.packHex(gameInfo);

  // send ADD message (must send blank line)
  msg  = "action=ADD&nameport=";
  msg += publicizedAddress;
  msg += "&version=";
  msg += getServerVersion();
  msg += "&gameinfo=";
  msg += gameInfo;
  msg += "&build=";
  msg += getAppVersion();
  msg += "&checktokens=";

  std::set<std::string> callSigns;
  // callsign1@ip1=token1%0D%0Acallsign2@ip2=token2%0D%0A
  for (int i = 0; i < curMaxPlayers; i++) {
    GameKeeper::Player *playerData = GameKeeper::Player::getPlayerByIndex(i);
    if (!playerData)
      continue;
    if ((playerData->_LSAState != GameKeeper::Player::required)
	&& (playerData->_LSAState != GameKeeper::Player::requesting))
      continue;
    if (callSigns.count(playerData->player.getCallSign()))
      continue;
    callSigns.insert(playerData->player.getCallSign());
    playerData->_LSAState = GameKeeper::Player::checking;
    NetHandler *handler = playerData->netHandler;
    msg += TextUtils::url_encode(playerData->player.getCallSign());
    Address addr = handler->getIPAddress();
    if (!addr.isPrivate()) {
	msg += "@";
	msg += handler->getTargetIP();
    }
    msg += "=";
    msg += playerData->player.getToken();
    msg += "%0D%0A";
  }

  msg += "&groups=";
  // *groups=GROUP0%0D%0AGROUP1%0D%0A
  PlayerAccessMap::iterator itr = groupAccess.begin();
  for ( ; itr != groupAccess.end(); itr++) {
    if (itr->first.substr(0, 6) != "LOCAL.") {
      msg += itr->first.c_str();
      msg += "%0D%0A";
    }
  }

  msg += "&advertgroups=";
  msg += _advertiseGroups;
  msg += "&title=";
  msg += publicizedTitle;

  setPostMode(msg);
  addHandle();
}

void ListServerLink::removeMe(std::string publicizedAddress)
{
  std::string msg;

  msg  = "action=REMOVE&nameport=";
  msg += publicizedAddress;

  setPostMode(msg);
  addHandle();
}

// Local Variables: ***
// mode:C++ ***
// tab-width: 8 ***
// c-basic-offset: 2 ***
// indent-tabs-mode: t ***
// End: ***
// ex: shiftwidth=2 tabstop=8
<commit_msg>more ws<commit_after>/* bzflag
 * Copyright (c) 1993 - 2005 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named LICENSE that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

// Provide BZFS with a list server connection

/* class header */
#include "ListServerConnection.h"

/* system implementation headers */
#include <string.h>
#include <string>
#include <math.h>
#include <errno.h>
#include <set>

/* common implementation headers */
#include "bzfio.h"
#include "version.h"
#include "TextUtils.h"
#include "Protocol.h"
#include "GameKeeper.h"
#include "bzfsAPI.h"
#include "WorldEventManager.h"

/* local implementation headers */
#include "CmdLineOptions.h"

// FIXME remove externs!
extern PingPacket getTeamCounts();
extern uint16_t curMaxPlayers;
extern void sendMessage(int playerIndex, PlayerId targetPlayer,
			const char *message);
extern CmdLineOptions *clOptions;

const int ListServerLink::NotConnected = -1;

ListServerLink::ListServerLink(std::string listServerURL,
			       std::string publicizedAddress,
			       std::string publicizedTitle,
			       std::string _advertiseGroups)
{

  std::string bzfsUserAgent = "bzfs ";
  bzfsUserAgent            += getAppVersion();

  setURL(listServerURL);
  setUserAgent(bzfsUserAgent);
  setDNSCachingTime(-1);
  setTimeout(10);

  if (clOptions->pingInterface != "")
    setInterface(clOptions->pingInterface);

  publicizeAddress     = publicizedAddress;
  publicizeDescription = publicizedTitle;
  advertiseGroups      = _advertiseGroups;

  //if this c'tor is called, it's safe to publicize
  publicizeServer      = true;
  queuedRequest        = false;
  // schedule initial ADD message
  queueMessage(ListServerLink::ADD);
}

ListServerLink::ListServerLink()
{
  // does not create a usable link, so checks should be placed
  // in  all public member functions to ensure that nothing tries
  // to happen if publicizeServer is false
  publicizeServer = false;
}

ListServerLink::~ListServerLink()
{
  // now tell the list server that we're going away.  this can
  // take some time but we don't want to wait too long.  we do
  // our own multiplexing loop and wait for a maximum of 3 seconds
  // total.

  // if we aren't supposed to be publicizing, skip the whole thing
  // and don't waste 3 seconds.
  if (!publicizeServer)
    return;

  queueMessage(ListServerLink::REMOVE);
  for (int i = 0; i < 12; i++) {
    cURLManager::perform();
    if (!queuedRequest)
      break;
    TimeKeeper::sleep(0.25f);
  }
}

void ListServerLink::finalization(char *data, unsigned int length, bool good)
{
  queuedRequest = false;
  if (good && (length < 2048)) {
    char buf[2048];
    memcpy(buf, data, length);
    int bytes = length;
    buf[bytes]=0;
    char* base = buf;
    static char *tokGoodIdentifier = "TOKGOOD: ";
    static char *tokBadIdentifier = "TOKBAD: ";
    // walks entire reply including HTTP headers
    while (*base) {
      // find next newline
      char* scan = base;
      while (*scan && *scan != '\r' && *scan != '\n') scan++;
      // if no newline then no more complete replies
      if (*scan != '\r' && *scan != '\n') break;
      while (*scan && (*scan == '\r' || *scan == '\n')) *scan++ = '\0';
      DEBUG4("Got line: \"%s\"\n", base);
      // TODO don't do this if we don't want central logins
      if (strncmp(base, tokGoodIdentifier, strlen(tokGoodIdentifier)) == 0) {
	char *callsign, *group;
	callsign = base + strlen(tokGoodIdentifier);
	DEBUG3("Got: %s\n", base);
	group = callsign;
	while (*group && (*group != ':')) group++;
	while (*group && (*group == ':')) *group++ = 0;
	int playerIndex;
	GameKeeper::Player *playerData = NULL;
	for (playerIndex = 0; playerIndex < curMaxPlayers; playerIndex++) {
	  playerData = GameKeeper::Player::getPlayerByIndex(playerIndex);
	  if (!playerData)
	    continue;
	  if (playerData->_LSAState != GameKeeper::Player::checking)
	    continue;
	  if (!TextUtils::compare_nocase(playerData->player.getCallSign(),
					 callsign))
	    break;
	}
	if (playerIndex < curMaxPlayers) {
	  // don't accept the global auth if there's a local account
	  // of the same name and the local account is not marked as
	  // being the same as the global account
	  if (!playerData->accessInfo.hasRealPassword()
              || playerData->accessInfo.getUserInfo(callsign)
	      .hasGroup("LOCAL.GLOBAL")) {
	    playerData->_LSAState = GameKeeper::Player::verified;
	    if (!playerData->accessInfo.isRegistered())
	      playerData->accessInfo.storeInfo(NULL);
	    playerData->accessInfo.setPermissionRights();
	    while (*group) {
              char *nextgroup = group;
	      while (*nextgroup && (*nextgroup != ':')) nextgroup++;
	      while (*nextgroup && (*nextgroup == ':')) *nextgroup++ = 0;
	      playerData->accessInfo.addGroup(group);
	      group = nextgroup;
	    }
	    playerData->authentication.global(true);
	    sendMessage(ServerPlayer, playerIndex, "Global login approved!");
	  } else {
	    playerData->_LSAState = GameKeeper::Player::failed;
	    sendMessage(ServerPlayer, playerIndex, "Global login rejected. "
	                "This callsign is registered locally on this "
			"server.");
	    sendMessage(ServerPlayer, playerIndex,
			"If the local account is yours, "
			"/identify, /deregister and reconnnect, "
			"or ask an admin for the LOCAL.GLOBAL group.");
	    sendMessage(ServerPlayer, playerIndex,
			"If it is not yours, please ask an admin "
			"to deregister it so that you may use your global "
			"callsign.");
	  }
	  playerData->player.clearToken();
	}
      } else if (!strncmp(base, tokBadIdentifier, strlen(tokBadIdentifier))) {
	char *callsign;
	callsign = base + strlen(tokBadIdentifier);
	int playerIndex;
	GameKeeper::Player *playerData = NULL;
	for (playerIndex = 0; playerIndex < curMaxPlayers; playerIndex++) {
	  playerData = GameKeeper::Player::getPlayerByIndex(playerIndex);
	  if (!playerData)
	    continue;
	  if (playerData->_LSAState != GameKeeper::Player::checking)
	    continue;
	  if (!TextUtils::compare_nocase(playerData->player.getCallSign(),
					 callsign))
	    break;
	}
	DEBUG3("Got: [%d] %s\n", playerIndex, base);
	if (playerIndex < curMaxPlayers) {
	  playerData->_LSAState = GameKeeper::Player::failed;
	  sendMessage(ServerPlayer, playerIndex,
		      "Global login rejected, bad token.");
	  playerData->player.clearToken();
	}
      }

      // next reply
      base = scan;
    }
  }
  for (int i = 0; i < curMaxPlayers; i++) {
    GameKeeper::Player *playerData = GameKeeper::Player::getPlayerByIndex(i);
    if (!playerData)
      continue;
    if (playerData->_LSAState != GameKeeper::Player::checking)
      continue;
    playerData->_LSAState = GameKeeper::Player::timed;
  }
  if (nextMessageType != ListServerLink::NONE) {
    // There was a pending request arrived after we write:
    // we should redo all the stuff
    sendQueuedMessages();
  }
}

void ListServerLink::queueMessage(MessageType type)
{
  // ignore if the server is not public
  if (!publicizeServer) return;

  // record next message to send.
  nextMessageType = type;

  if (!queuedRequest)
    sendQueuedMessages();
  else
    DEBUG3("There is a message already queued to the list server: not sending this one yet.\n");
}

void ListServerLink::sendQueuedMessages()
{
  queuedRequest = true;
  if (nextMessageType == ListServerLink::ADD) {
    DEBUG3("Queuing ADD message to list server\n");

    bz_ListServerUpdateEvent	updateEvent;
    updateEvent.address = publicizeAddress;
    updateEvent.description = publicizeDescription;
    updateEvent.groups = advertiseGroups;

    worldEventManager.callEvents(bz_eListServerUpdateEvent,&updateEvent);

    addMe(getTeamCounts(), std::string(updateEvent.address.c_str()), std::string(updateEvent.description.c_str()), std::string(updateEvent.groups.c_str()));
    lastAddTime = TimeKeeper::getCurrent();
  } else if (nextMessageType == ListServerLink::REMOVE) {
    DEBUG3("Queuing REMOVE message to list server\n");
    removeMe(publicizeAddress);
  }
  nextMessageType = ListServerLink::NONE;
}

void ListServerLink::addMe(PingPacket pingInfo,
			   std::string publicizedAddress,
			   std::string publicizedTitle,
			   std::string _advertiseGroups)
{
  std::string msg;
  std::string hdr;

  // encode ping reply as ascii hex digits plus NULL
  char gameInfo[PingPacketHexPackedSize + 1];
  pingInfo.packHex(gameInfo);

  // send ADD message (must send blank line)
  msg  = "action=ADD&nameport=";
  msg += publicizedAddress;
  msg += "&version=";
  msg += getServerVersion();
  msg += "&gameinfo=";
  msg += gameInfo;
  msg += "&build=";
  msg += getAppVersion();
  msg += "&checktokens=";

  std::set<std::string> callSigns;
  // callsign1@ip1=token1%0D%0Acallsign2@ip2=token2%0D%0A
  for (int i = 0; i < curMaxPlayers; i++) {
    GameKeeper::Player *playerData = GameKeeper::Player::getPlayerByIndex(i);
    if (!playerData)
      continue;
    if ((playerData->_LSAState != GameKeeper::Player::required)
	&& (playerData->_LSAState != GameKeeper::Player::requesting))
      continue;
    if (callSigns.count(playerData->player.getCallSign()))
      continue;
    callSigns.insert(playerData->player.getCallSign());
    playerData->_LSAState = GameKeeper::Player::checking;
    NetHandler *handler = playerData->netHandler;
    msg += TextUtils::url_encode(playerData->player.getCallSign());
    Address addr = handler->getIPAddress();
    if (!addr.isPrivate()) {
	msg += "@";
	msg += handler->getTargetIP();
    }
    msg += "=";
    msg += playerData->player.getToken();
    msg += "%0D%0A";
  }

  msg += "&groups=";
  // *groups=GROUP0%0D%0AGROUP1%0D%0A
  PlayerAccessMap::iterator itr = groupAccess.begin();
  for ( ; itr != groupAccess.end(); itr++) {
    if (itr->first.substr(0, 6) != "LOCAL.") {
      msg += itr->first.c_str();
      msg += "%0D%0A";
    }
  }

  msg += "&advertgroups=";
  msg += _advertiseGroups;
  msg += "&title=";
  msg += publicizedTitle;

  setPostMode(msg);
  addHandle();
}

void ListServerLink::removeMe(std::string publicizedAddress)
{
  std::string msg;

  msg  = "action=REMOVE&nameport=";
  msg += publicizedAddress;

  setPostMode(msg);
  addHandle();
}

// Local Variables: ***
// mode:C++ ***
// tab-width: 8 ***
// c-basic-offset: 2 ***
// indent-tabs-mode: t ***
// End: ***
// ex: shiftwidth=2 tabstop=8
<|endoftext|>


<commit_before>/* bzflag
 * Copyright (c) 1993 - 2005 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named LICENSE that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

// Provide BZFS with a list server connection

/* class header */
#include "ListServerConnection.h"

/* system implementation headers */
#include <string.h>
#include <string>
#include <math.h>
#include <errno.h>

/* common implementation headers */
#include "bzfio.h"
#include "version.h"
#include "TextUtils.h"
#include "Protocol.h"
#include "GameKeeper.h"

/* local implementation headers */
#include "CmdLineOptions.h"

// FIXME remove externs!
extern PingPacket getTeamCounts();
extern uint16_t curMaxPlayers;
extern void sendMessage(int playerIndex, PlayerId targetPlayer,
			const char *message);
extern void sendPlayerInfo(void);
extern void sendIPUpdate(int targetPlayer, int playerIndex);
extern CmdLineOptions *clOptions;

const int ListServerLink::NotConnected = -1;

ListServerLink::ListServerLink(std::string listServerURL,
			       std::string publicizedAddress,
			       std::string publicizedTitle)
{

  std::string bzfsUserAgent = "bzfs ";
  bzfsUserAgent            += getAppVersion();

  setURL(listServerURL);
  setUserAgent(bzfsUserAgent);

  if (clOptions->pingInterface != "")
    setInterface(clOptions->pingInterface);

  publicizeAddress     = publicizedAddress;
  publicizeDescription = publicizedTitle;
  //if this c'tor is called, it's safe to publicize
  publicizeServer      = true;
  queuedRequest        = false;
  // schedule initial ADD message
  queueMessage(ListServerLink::ADD);
}

ListServerLink::ListServerLink()
{
  // does not create a usable link, so checks should be placed
  // in  all public member functions to ensure that nothing tries
  // to happen if publicizeServer is false
  publicizeServer = false;
}

ListServerLink::~ListServerLink()
{
  // now tell the list server that we're going away.  this can
  // take some time but we don't want to wait too long.  we do
  // our own multiplexing loop and wait for a maximum of 3 seconds
  // total.

  // if we aren't supposed to be publicizing, skip the whole thing
  // and don't waste 3 seconds.
  if (!publicizeServer)
    return;

  queueMessage(ListServerLink::REMOVE);
  for (int i = 0; i < 12; i++) {
    cURLManager::perform();
    if (!queuedRequest)
      break;
    TimeKeeper::sleep(0.25f);
  }
}

void ListServerLink::finalization(char *data, unsigned int length, bool good)
{
  queuedRequest = false;
  if (good && (length < 2048)) {
    char buf[2048];
    memcpy(buf, data, length);
    int bytes = length;
    buf[bytes]=0;
    char* base = buf;
    static char *tokGoodIdentifier = "TOKGOOD: ";
    static char *tokBadIdentifier = "TOKBAD: ";
    // walks entire reply including HTTP headers
    while (*base) {
      // find next newline
      char* scan = base;
      while (*scan && *scan != '\r' && *scan != '\n') scan++;
      // if no newline then no more complete replies
      if (*scan != '\r' && *scan != '\n') break;
      while (*scan && (*scan == '\r' || *scan == '\n')) *scan++ = '\0';
      DEBUG4("Got line: \"%s\"\n", base);
      // TODO don't do this if we don't want central logins
      if (strncmp(base, tokGoodIdentifier, strlen(tokGoodIdentifier)) == 0) {
	char *callsign, *group;
	callsign = base + strlen(tokGoodIdentifier);
	DEBUG3("Got: %s\n", base);
	group = callsign;
	while (*group && (*group != ':')) group++;
	while (*group && (*group == ':')) *group++ = 0;
	int playerIndex = GameKeeper::Player::getPlayerIDByName(callsign);
	if (playerIndex < curMaxPlayers) {
	  GameKeeper::Player *playerData
	    = GameKeeper::Player::getPlayerByIndex(playerIndex);
	  if (playerData != NULL) {
	    // don't accept the global auth if there's a local account
	    // of the same name and the local account is not marked as
	    // being the same as the global account
	    if (!playerData->accessInfo.hasRealPassword()
		|| playerData->accessInfo.getUserInfo(callsign)
		.hasGroup("LOCAL.GLOBAL")) {
	      if (!playerData->accessInfo.isRegistered())
		playerData->accessInfo.storeInfo(NULL);
	      playerData->accessInfo.setPermissionRights();
	      while (*group) {
		char *nextgroup = group;
		while (*nextgroup && (*nextgroup != ':')) nextgroup++;
		while (*nextgroup && (*nextgroup == ':')) *nextgroup++ = 0;
		playerData->accessInfo.addGroup(group);
		group = nextgroup;
	      }
		  playerData->authentication.global(true);
	      sendMessage(ServerPlayer, playerIndex, "Global login approved!");
	      sendIPUpdate(playerIndex, -1);
	      sendPlayerInfo();
	    } else {
	      sendMessage(ServerPlayer, playerIndex, "Global login rejected. "
			  "This callsign is registered locally on this "
			  "server.");
	      sendMessage(ServerPlayer, playerIndex,
			  "If the local account is yours, "
			  "/identify, /deregister and reconnnect, "
			  "or ask an admin for the LOCAL.GLOBAL group.");
	      sendMessage(ServerPlayer, playerIndex,
			  "If it is not yours, please ask an admin "
			  "to deregister it so that you may use your global "
			  "callsign.");
	    }
	    playerData->setNeedThisHostbanChecked(true);
	    playerData->player.clearToken();
	  }
	}
      } else if (!strncmp(base, tokBadIdentifier, strlen(tokBadIdentifier))) {
	char *callsign;
	callsign = base + strlen(tokBadIdentifier);
	int playerIndex = GameKeeper::Player::getPlayerIDByName(callsign);
	DEBUG3("Got: [%d] %s\n", playerIndex, base);
	if (playerIndex < curMaxPlayers) {
	  GameKeeper::Player *playerData
	    = GameKeeper::Player::getPlayerByIndex(playerIndex);
	  sendMessage(ServerPlayer, playerIndex,
		      "Global login rejected, bad token.");
	  if (playerData != NULL) {
	    playerData->setNeedThisHostbanChecked(true);
	    playerData->player.clearToken();
	  }
	}
      }

      // next reply
      base = scan;
    }
  }
  if (nextMessageType != ListServerLink::NONE) {
    // There was a pending request arrived after we write:
    // we should redo all the stuff
    sendQueuedMessages();
  }
}

void ListServerLink::queueMessage(MessageType type)
{
  // ignore if the server is not public
  if (!publicizeServer) return;

  // record next message to send.
  nextMessageType = type;

  if (!queuedRequest)
    sendQueuedMessages();
}

void ListServerLink::sendQueuedMessages()
{
  queuedRequest = true;
  if (nextMessageType == ListServerLink::ADD) {
    DEBUG3("Queuing ADD message to list server\n");
    addMe(getTeamCounts(), publicizeAddress, publicizeDescription);
    lastAddTime = TimeKeeper::getCurrent();
  } else if (nextMessageType == ListServerLink::REMOVE) {
    DEBUG3("Queuing REMOVE message to list server\n");
    removeMe(publicizeAddress);
  }
  nextMessageType = ListServerLink::NONE;
}

void ListServerLink::addMe(PingPacket pingInfo,
			   std::string publicizedAddress,
			   std::string publicizedTitle)
{
  std::string msg;
  std::string hdr;

  // encode ping reply as ascii hex digits plus NULL
  char gameInfo[PingPacketHexPackedSize + 1];
  pingInfo.packHex(gameInfo);

  // send ADD message (must send blank line)
  addFormData("action",   "ADD");
  addFormData("nameport", publicizedAddress.c_str());
  addFormData("version",  getServerVersion());
  addFormData("gameinfo", gameInfo);
  addFormData("build",    getAppVersion());

  // callsign1@ip1=token1%0D%0Acallsign2@ip2=token2%0D%0A
  for (int i = 0; i < curMaxPlayers; i++) {
    GameKeeper::Player *playerData = GameKeeper::Player::getPlayerByIndex(i);
    if (!playerData)
      continue;
    NetHandler *handler = playerData->netHandler;
    if (strlen(playerData->player.getCallSign())
	&& strlen(playerData->player.getToken())) {
      msg += playerData->player.getCallSign();
      Address addr = handler->getIPAddress();
      if (!addr.isPrivate()) {
	      msg += "@";
	      msg += handler->getTargetIP();
      }
      msg += "=";
      msg += playerData->player.getToken();
      msg += "%0D%0A";
    }
  }
  addFormData("checktokens", msg.c_str());

  // *groups=GROUP0%0D%0AGROUP1%0D%0A
  msg = "";
  PlayerAccessMap::iterator itr = groupAccess.begin();
  for ( ; itr != groupAccess.end(); itr++) {
    if (itr->first.substr(0, 6) != "LOCAL.") {
      msg += itr->first.c_str();
      msg += "%0D%0A";
    }
  }
  addFormData("groups", msg.c_str());

  addFormData("title",  publicizedTitle.c_str());

  setHTTPPostMode();
  addHandle();
}

void ListServerLink::removeMe(std::string publicizedAddress)
{
  std::string msg;

  addFormData("action",   "REMOVE");
  addFormData("nameport", publicizedAddress.c_str());

  setHTTPPostMode();
  addHandle();
}

// Local Variables: ***
// mode:C++ ***
// tab-width: 8 ***
// c-basic-offset: 2 ***
// indent-tabs-mode: t ***
// End: ***
// ex: shiftwidth=2 tabstop=8
<commit_msg>Do url-encoded post (not multipart)<commit_after>/* bzflag
 * Copyright (c) 1993 - 2005 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named LICENSE that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

// Provide BZFS with a list server connection

/* class header */
#include "ListServerConnection.h"

/* system implementation headers */
#include <string.h>
#include <string>
#include <math.h>
#include <errno.h>

/* common implementation headers */
#include "bzfio.h"
#include "version.h"
#include "TextUtils.h"
#include "Protocol.h"
#include "GameKeeper.h"

/* local implementation headers */
#include "CmdLineOptions.h"

// FIXME remove externs!
extern PingPacket getTeamCounts();
extern uint16_t curMaxPlayers;
extern void sendMessage(int playerIndex, PlayerId targetPlayer,
			const char *message);
extern void sendPlayerInfo(void);
extern void sendIPUpdate(int targetPlayer, int playerIndex);
extern CmdLineOptions *clOptions;

const int ListServerLink::NotConnected = -1;

ListServerLink::ListServerLink(std::string listServerURL,
			       std::string publicizedAddress,
			       std::string publicizedTitle)
{

  std::string bzfsUserAgent = "bzfs ";
  bzfsUserAgent            += getAppVersion();

  setURL(listServerURL);
  setUserAgent(bzfsUserAgent);

  if (clOptions->pingInterface != "")
    setInterface(clOptions->pingInterface);

  publicizeAddress     = publicizedAddress;
  publicizeDescription = publicizedTitle;
  //if this c'tor is called, it's safe to publicize
  publicizeServer      = true;
  queuedRequest        = false;
  // schedule initial ADD message
  queueMessage(ListServerLink::ADD);
}

ListServerLink::ListServerLink()
{
  // does not create a usable link, so checks should be placed
  // in  all public member functions to ensure that nothing tries
  // to happen if publicizeServer is false
  publicizeServer = false;
}

ListServerLink::~ListServerLink()
{
  // now tell the list server that we're going away.  this can
  // take some time but we don't want to wait too long.  we do
  // our own multiplexing loop and wait for a maximum of 3 seconds
  // total.

  // if we aren't supposed to be publicizing, skip the whole thing
  // and don't waste 3 seconds.
  if (!publicizeServer)
    return;

  queueMessage(ListServerLink::REMOVE);
  for (int i = 0; i < 12; i++) {
    cURLManager::perform();
    if (!queuedRequest)
      break;
    TimeKeeper::sleep(0.25f);
  }
}

void ListServerLink::finalization(char *data, unsigned int length, bool good)
{
  queuedRequest = false;
  if (good && (length < 2048)) {
    char buf[2048];
    memcpy(buf, data, length);
    int bytes = length;
    buf[bytes]=0;
    char* base = buf;
    static char *tokGoodIdentifier = "TOKGOOD: ";
    static char *tokBadIdentifier = "TOKBAD: ";
    // walks entire reply including HTTP headers
    while (*base) {
      // find next newline
      char* scan = base;
      while (*scan && *scan != '\r' && *scan != '\n') scan++;
      // if no newline then no more complete replies
      if (*scan != '\r' && *scan != '\n') break;
      while (*scan && (*scan == '\r' || *scan == '\n')) *scan++ = '\0';
      DEBUG4("Got line: \"%s\"\n", base);
      // TODO don't do this if we don't want central logins
      if (strncmp(base, tokGoodIdentifier, strlen(tokGoodIdentifier)) == 0) {
	char *callsign, *group;
	callsign = base + strlen(tokGoodIdentifier);
	DEBUG3("Got: %s\n", base);
	group = callsign;
	while (*group && (*group != ':')) group++;
	while (*group && (*group == ':')) *group++ = 0;
	int playerIndex = GameKeeper::Player::getPlayerIDByName(callsign);
	if (playerIndex < curMaxPlayers) {
	  GameKeeper::Player *playerData
	    = GameKeeper::Player::getPlayerByIndex(playerIndex);
	  if (playerData != NULL) {
	    // don't accept the global auth if there's a local account
	    // of the same name and the local account is not marked as
	    // being the same as the global account
	    if (!playerData->accessInfo.hasRealPassword()
		|| playerData->accessInfo.getUserInfo(callsign)
		.hasGroup("LOCAL.GLOBAL")) {
	      if (!playerData->accessInfo.isRegistered())
		playerData->accessInfo.storeInfo(NULL);
	      playerData->accessInfo.setPermissionRights();
	      while (*group) {
		char *nextgroup = group;
		while (*nextgroup && (*nextgroup != ':')) nextgroup++;
		while (*nextgroup && (*nextgroup == ':')) *nextgroup++ = 0;
		playerData->accessInfo.addGroup(group);
		group = nextgroup;
	      }
		  playerData->authentication.global(true);
	      sendMessage(ServerPlayer, playerIndex, "Global login approved!");
	      sendIPUpdate(playerIndex, -1);
	      sendPlayerInfo();
	    } else {
	      sendMessage(ServerPlayer, playerIndex, "Global login rejected. "
			  "This callsign is registered locally on this "
			  "server.");
	      sendMessage(ServerPlayer, playerIndex,
			  "If the local account is yours, "
			  "/identify, /deregister and reconnnect, "
			  "or ask an admin for the LOCAL.GLOBAL group.");
	      sendMessage(ServerPlayer, playerIndex,
			  "If it is not yours, please ask an admin "
			  "to deregister it so that you may use your global "
			  "callsign.");
	    }
	    playerData->setNeedThisHostbanChecked(true);
	    playerData->player.clearToken();
	  }
	}
      } else if (!strncmp(base, tokBadIdentifier, strlen(tokBadIdentifier))) {
	char *callsign;
	callsign = base + strlen(tokBadIdentifier);
	int playerIndex = GameKeeper::Player::getPlayerIDByName(callsign);
	DEBUG3("Got: [%d] %s\n", playerIndex, base);
	if (playerIndex < curMaxPlayers) {
	  GameKeeper::Player *playerData
	    = GameKeeper::Player::getPlayerByIndex(playerIndex);
	  sendMessage(ServerPlayer, playerIndex,
		      "Global login rejected, bad token.");
	  if (playerData != NULL) {
	    playerData->setNeedThisHostbanChecked(true);
	    playerData->player.clearToken();
	  }
	}
      }

      // next reply
      base = scan;
    }
  }
  if (nextMessageType != ListServerLink::NONE) {
    // There was a pending request arrived after we write:
    // we should redo all the stuff
    sendQueuedMessages();
  }
}

void ListServerLink::queueMessage(MessageType type)
{
  // ignore if the server is not public
  if (!publicizeServer) return;

  // record next message to send.
  nextMessageType = type;

  if (!queuedRequest)
    sendQueuedMessages();
}

void ListServerLink::sendQueuedMessages()
{
  queuedRequest = true;
  if (nextMessageType == ListServerLink::ADD) {
    DEBUG3("Queuing ADD message to list server\n");
    addMe(getTeamCounts(), publicizeAddress, publicizeDescription);
    lastAddTime = TimeKeeper::getCurrent();
  } else if (nextMessageType == ListServerLink::REMOVE) {
    DEBUG3("Queuing REMOVE message to list server\n");
    removeMe(publicizeAddress);
  }
  nextMessageType = ListServerLink::NONE;
}

void ListServerLink::addMe(PingPacket pingInfo,
			   std::string publicizedAddress,
			   std::string publicizedTitle)
{
  std::string msg;
  std::string hdr;

  // encode ping reply as ascii hex digits plus NULL
  char gameInfo[PingPacketHexPackedSize + 1];
  pingInfo.packHex(gameInfo);

  // send ADD message (must send blank line)
  msg  = "action=ADD&nameport=";
  msg += publicizedAddress;
  msg += "&version=";
  msg += getServerVersion();
  msg += "&gameinfo=";
  msg += gameInfo;
  msg += "&build=";
  msg += getAppVersion();
  msg += "&checktokens=";

  // callsign1@ip1=token1%0D%0Acallsign2@ip2=token2%0D%0A
  for (int i = 0; i < curMaxPlayers; i++) {
    GameKeeper::Player *playerData = GameKeeper::Player::getPlayerByIndex(i);
    if (!playerData)
      continue;
    NetHandler *handler = playerData->netHandler;
    if (strlen(playerData->player.getCallSign())
	&& strlen(playerData->player.getToken())) {
      msg += TextUtils::url_encode(playerData->player.getCallSign());
      Address addr = handler->getIPAddress();
      if (!addr.isPrivate()) {
	      msg += "@";
	      msg += handler->getTargetIP();
      }
      msg += "=";
      msg += playerData->player.getToken();
      msg += "%0D%0A";
    }
  }

  msg += "&groups=";
  // *groups=GROUP0%0D%0AGROUP1%0D%0A
  PlayerAccessMap::iterator itr = groupAccess.begin();
  for ( ; itr != groupAccess.end(); itr++) {
    if (itr->first.substr(0, 6) != "LOCAL.") {
      msg += itr->first.c_str();
      msg += "%0D%0A";
    }
  }

  msg += "&title=";
  msg += publicizedTitle;

  setPostMode(msg);
  addHandle();
}

void ListServerLink::removeMe(std::string publicizedAddress)
{
  std::string msg;

  msg  = "action=REMOVE&nameport=";
  msg += publicizedAddress;

  setPostMode(msg);
  addHandle();
}

// Local Variables: ***
// mode:C++ ***
// tab-width: 8 ***
// c-basic-offset: 2 ***
// indent-tabs-mode: t ***
// End: ***
// ex: shiftwidth=2 tabstop=8
<|endoftext|>


<commit_before>/******************************************************************************
*       SOFA, Simulation Open-Framework Architecture, version 1.0 beta 4      *
*                (c) 2006-2009 MGH, INRIA, USTL, UJF, CNRS                    *
*                                                                             *
* 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 Foundation,     *
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.          *
*******************************************************************************
*                               SOFA :: Modules                               *
*                                                                             *
* Authors: The SOFA Team and external contributors (see Authors.txt)          *
*                                                                             *
* Contact information: contact@sofa-framework.org                             *
******************************************************************************/
#ifndef SOFA_COMPONENT_TOPOLOGY_TOPOLOGYSUBSETDATA_INL
#define SOFA_COMPONENT_TOPOLOGY_TOPOLOGYSUBSETDATA_INL

#include <sofa/component/topology/TopologySubsetData.h>
#include <sofa/component/topology/TopologyEngine.inl>
#include <sofa/component/topology/TopologySubsetDataHandler.inl>

namespace sofa
{

namespace component
{

namespace topology
{

////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////   Generic Topology Data Implementation   /////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////

template <typename TopologyElementType, typename VecT>
TopologySubsetDataImpl <TopologyElementType, VecT>::~TopologySubsetDataImpl()
{
    if (this->m_topologyHandler)
        delete m_topologyHandler;
}

template <typename TopologyElementType, typename VecT>
void TopologySubsetDataImpl <TopologyElementType, VecT>::createTopologicalEngine(sofa::core::topology::BaseMeshTopology *_topology, sofa::core::topology::TopologySubsetDataHandler *_topologyHandler)
{
    if (_topology)
    {
        this->m_topologicalEngine = new TopologyEngineImpl<VecT>((sofa::component::topology::TopologySubsetDataImpl<TopologyElementType, VecT>*)this, _topology, _topologyHandler);
        this->linkToElementDataArray();
    }
}

template <typename TopologyElementType, typename VecT>
void TopologySubsetDataImpl <TopologyElementType, VecT>::createTopologicalEngine(sofa::core::topology::BaseMeshTopology *_topology)
{
    if (_topology)
    {
        this->m_topologyHandler = new TopologySubsetDataHandler<TopologyElementType, VecT>(this);
        this->m_topologicalEngine = new TopologyEngineImpl<VecT>((sofa::component::topology::TopologySubsetDataImpl<TopologyElementType, VecT>*)this, _topology, m_topologyHandler);
        this->linkToElementDataArray();
    }
}

template <typename TopologyElementType, typename VecT>
void TopologySubsetDataImpl <TopologyElementType, VecT>::registerTopologicalData()
{
    if (this->m_topologicalEngine)
        this->m_topologicalEngine->registerTopology();
#ifndef NDEBUG // too much warnings
    else
        std::cout<<"Error: TopologySubsetDataImpl: " << this->getName() << " has no engine. Use createTopologicalEngine function before." << std::endl;
#endif
}

template <typename TopologyElementType, typename VecT>
void TopologySubsetDataImpl <TopologyElementType, VecT>::addInputData(sofa::core::objectmodel::BaseData *_data)
{
    if (this->m_topologicalEngine)
        this->m_topologicalEngine->addInput(_data);
#ifndef NDEBUG // too much warnings
    else
        std::cout<<"Error: TopologySubsetDataImpl: " << this->getName() << " has no engine. Use createTopologicalEngine function before." << std::endl;
#endif
}

} // namespace topology

} // namespace component

} // namespace sofa

#endif // SOFA_COMPONENT_TOPOLOGY_TOPOLOGYSUBSETDATA_INL

<commit_msg>r11411/sofa-dev : FIX: compilation.<commit_after>/******************************************************************************
*       SOFA, Simulation Open-Framework Architecture, version 1.0 beta 4      *
*                (c) 2006-2009 MGH, INRIA, USTL, UJF, CNRS                    *
*                                                                             *
* 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 Foundation,     *
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.          *
*******************************************************************************
*                               SOFA :: Modules                               *
*                                                                             *
* Authors: The SOFA Team and external contributors (see Authors.txt)          *
*                                                                             *
* Contact information: contact@sofa-framework.org                             *
******************************************************************************/
#ifndef SOFA_COMPONENT_TOPOLOGY_TOPOLOGYSUBSETDATA_INL
#define SOFA_COMPONENT_TOPOLOGY_TOPOLOGYSUBSETDATA_INL

#include <sofa/component/topology/TopologySubsetData.h>
#include <sofa/component/topology/TopologyEngine.inl>
#include <sofa/component/topology/TopologySubsetDataHandler.inl>

namespace sofa
{

namespace component
{

namespace topology
{

////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////   Generic Topology Data Implementation   /////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////

template <typename TopologyElementType, typename VecT>
TopologySubsetDataImpl <TopologyElementType, VecT>::~TopologySubsetDataImpl()
{
    if (this->m_topologyHandler)
        delete m_topologyHandler;
}

template <typename TopologyElementType, typename VecT>
void TopologySubsetDataImpl <TopologyElementType, VecT>::createTopologicalEngine(sofa::core::topology::BaseMeshTopology *_topology, sofa::core::topology::TopologyHandler *_topologyHandler)
{
    if (_topology)
    {
        this->m_topologicalEngine = new TopologyEngineImpl<VecT>((sofa::component::topology::TopologySubsetDataImpl<TopologyElementType, VecT>*)this, _topology, _topologyHandler);
        this->linkToElementDataArray();
    }
}

template <typename TopologyElementType, typename VecT>
void TopologySubsetDataImpl <TopologyElementType, VecT>::createTopologicalEngine(sofa::core::topology::BaseMeshTopology *_topology)
{
    if (_topology)
    {
        this->m_topologyHandler = new TopologySubsetDataHandler<TopologyElementType, VecT>(this);
        this->m_topologicalEngine = new TopologyEngineImpl<VecT>((sofa::component::topology::TopologySubsetDataImpl<TopologyElementType, VecT>*)this, _topology, m_topologyHandler);
        this->linkToElementDataArray();
    }
}

template <typename TopologyElementType, typename VecT>
void TopologySubsetDataImpl <TopologyElementType, VecT>::registerTopologicalData()
{
    if (this->m_topologicalEngine)
        this->m_topologicalEngine->registerTopology();
#ifndef NDEBUG // too much warnings
    else
        std::cout<<"Error: TopologySubsetDataImpl: " << this->getName() << " has no engine. Use createTopologicalEngine function before." << std::endl;
#endif
}

template <typename TopologyElementType, typename VecT>
void TopologySubsetDataImpl <TopologyElementType, VecT>::addInputData(sofa::core::objectmodel::BaseData *_data)
{
    if (this->m_topologicalEngine)
        this->m_topologicalEngine->addInput(_data);
#ifndef NDEBUG // too much warnings
    else
        std::cout<<"Error: TopologySubsetDataImpl: " << this->getName() << " has no engine. Use createTopologicalEngine function before." << std::endl;
#endif
}

} // namespace topology

} // namespace component

} // namespace sofa

#endif // SOFA_COMPONENT_TOPOLOGY_TOPOLOGYSUBSETDATA_INL

<|endoftext|>


<commit_before>/*
 * Copyright (C) 2013 Tommi Maekitalo
 *
 * 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.
 *
 * As a special exception, you may use this file as part of a free
 * software library without restriction. Specifically, if other files
 * instantiate templates or use macros or inline functions from this
 * file, or you compile this file and link it with other files to
 * produce an executable, this file does not by itself cause the
 * resulting executable to be covered by the GNU General Public
 * License. This exception does not however invalidate any other
 * reasons why the executable file might be covered by the GNU Library
 * General Public License.
 *
 * 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
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "cxxtools/unit/testsuite.h"
#include "cxxtools/unit/registertest.h"
#include "cxxtools/query_params.h"
#include "cxxtools/serializationinfo.h"
#include "cxxtools/log.h"
#include <vector>
#include <iterator>

log_define("cxxtools.test.queryparams")

class QueryParamsTest : public cxxtools::unit::TestSuite
{
    public:
        QueryParamsTest()
            : cxxtools::unit::TestSuite("queryparams")
        {
            registerMethod("testQueryParams", *this, &QueryParamsTest::testQueryParams);
            registerMethod("testCopy", *this, &QueryParamsTest::testCopy);
            registerMethod("testParseUrl", *this, &QueryParamsTest::testParseUrl);
            registerMethod("testParseUrlSpecialChar", *this, &QueryParamsTest::testParseUrlSpecialChar);
            registerMethod("testParseDoublePercent", *this, &QueryParamsTest::testParseDoublePercent);
            registerMethod("testCount", *this, &QueryParamsTest::testCount);
            registerMethod("testCombine", *this, &QueryParamsTest::testCombine);
            registerMethod("testSet", *this, &QueryParamsTest::testSet);
            registerMethod("testIterator", *this, &QueryParamsTest::testIterator);
            registerMethod("testGetUrl", *this, &QueryParamsTest::testGetUrl);
            registerMethod("testGetNames", *this, &QueryParamsTest::testGetNames);
            registerMethod("testDeserialization", *this, &QueryParamsTest::testDeserialization);
            registerMethod("testDeserializeSpecial", *this, &QueryParamsTest::testDeserializeSpecial);
        }

        void testQueryParams()
        {
            cxxtools::QueryParams q;
            q.add("p1", "value1");
            q.add("p2", "value2");
            q.add("value3");
            CXXTOOLS_UNIT_ASSERT(q.has("p1"));
            CXXTOOLS_UNIT_ASSERT(q.has("p2"));
            CXXTOOLS_UNIT_ASSERT(!q.has("p3"));
            CXXTOOLS_UNIT_ASSERT_EQUALS(q["p1"], "value1");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q["p2"], "value2");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q[0], "value3");
        }

        void testCopy()
        {
            cxxtools::QueryParams q;
            q.add("p1", "value1");
            q.add("p2", "value2");
            q.add("value3");
            cxxtools::QueryParams q2 = q;
            CXXTOOLS_UNIT_ASSERT(q2.has("p1"));
            CXXTOOLS_UNIT_ASSERT(q2.has("p2"));
            CXXTOOLS_UNIT_ASSERT(!q2.has("p3"));
            CXXTOOLS_UNIT_ASSERT_EQUALS(q2["p1"], "value1");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q2["p2"], "value2");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q2[0], "value3");
        }

        void testParseUrl()
        {
            cxxtools::QueryParams q;
            q.parse_url("p2=value2&value3&p1=value1");
            CXXTOOLS_UNIT_ASSERT(q.has("p1"));
            CXXTOOLS_UNIT_ASSERT(q.has("p2"));
            CXXTOOLS_UNIT_ASSERT(!q.has("p3"));
            CXXTOOLS_UNIT_ASSERT_EQUALS(q["p1"], "value1");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q["p2"], "value2");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q[0], "value3");
        }

        void testParseUrlSpecialChar()
        {
            cxxtools::QueryParams q;
            q.parse_url("p1=value+with%20spaces&m%a4kitalo=tommi+");
            CXXTOOLS_UNIT_ASSERT(q.has("p1"));
            CXXTOOLS_UNIT_ASSERT(q.has("m\xa4kitalo"));
            CXXTOOLS_UNIT_ASSERT_EQUALS(q["p1"], "value with spaces");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q["m\xa4kitalo"], "tommi ");
        }

        void testParseDoublePercent()
        {
            cxxtools::QueryParams q;
            q.parse_url("%%=%%%");
            CXXTOOLS_UNIT_ASSERT(q.has("%%"));
            CXXTOOLS_UNIT_ASSERT_EQUALS(q["%%"], "%%%");
        }

        void testCount()
        {
            cxxtools::QueryParams q;
            q.add("p1", "value1");
            q.add("p2", "value2");
            q.add("p2", "value3");
            q.add("value4");
            q.add("value5");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q.paramcount(), 2);
            CXXTOOLS_UNIT_ASSERT_EQUALS(q.paramcount("p1"), 1);
            CXXTOOLS_UNIT_ASSERT_EQUALS(q.paramcount("p2"), 2);
        }

        void testCombine()
        {
            cxxtools::QueryParams q;
            cxxtools::QueryParams q2;
            q.add("p1", "value1");
            q2.add("p2", "value2");
            q2.add("value3");
            q.add(q2);
        }

        void testSet()
        {
            cxxtools::QueryParams q;

            q.set("a", "Hi");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q.getUrl(), "a=Hi");

            q.set("b", "foo");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q.getUrl(), "a=Hi&b=foo");

            q.set("a", "There");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q.getUrl(), "b=foo&a=There");

            q.clear();
            CXXTOOLS_UNIT_ASSERT_EQUALS(q.getUrl(), "");

            q.add("a", "Hi");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q.getUrl(), "a=Hi");

            q.add("b", "foo");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q.getUrl(), "a=Hi&b=foo");

            q.add("a", "There");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q.getUrl(), "a=Hi&b=foo&a=There");

        }

        void testIterator()
        {
            cxxtools::QueryParams q;
            q.add("p1", "value1");
            q.add("p2", "value2");
            q.add("p2", "value3");
            q.add("value4");
            q.add("value5");

            cxxtools::QueryParams::const_iterator it;

            it = q.begin();
            CXXTOOLS_UNIT_ASSERT(it != q.end());
            CXXTOOLS_UNIT_ASSERT_EQUALS(*it, "value4");
            ++it;
            CXXTOOLS_UNIT_ASSERT(it != q.end());
            CXXTOOLS_UNIT_ASSERT_EQUALS(*it, "value5");
            ++it;
            CXXTOOLS_UNIT_ASSERT(it == q.end());

            it = q.begin("p1");
            CXXTOOLS_UNIT_ASSERT(it != q.end());
            CXXTOOLS_UNIT_ASSERT_EQUALS(*it, "value1");
            ++it;
            CXXTOOLS_UNIT_ASSERT(it == q.end());

            it = q.begin("p2");
            CXXTOOLS_UNIT_ASSERT(it != q.end());
            CXXTOOLS_UNIT_ASSERT_EQUALS(*it, "value2");
            ++it;
            CXXTOOLS_UNIT_ASSERT(it != q.end());
            CXXTOOLS_UNIT_ASSERT_EQUALS(*it, "value3");
            ++it;
            CXXTOOLS_UNIT_ASSERT(it == q.end());
        }

        void testGetUrl()
        {
            cxxtools::QueryParams q;
            q.add("first name", "Tommi");
            q.add("last name", "M\xa4kitalo");
            q.add("some value");

            std::string url = q.getUrl();
            CXXTOOLS_UNIT_ASSERT_EQUALS(url, "first+name=Tommi&last+name=M%A4kitalo&some+value");
        }

        void testGetNames()
        {
            cxxtools::QueryParams q;
            q.add("p1", "value1");
            q.add("p2", "value2");
            q.add("value3");

            std::vector<std::string> names;
            q.getNames(std::back_inserter(names));
            CXXTOOLS_UNIT_ASSERT_EQUALS(names.size(), 3);
            CXXTOOLS_UNIT_ASSERT_EQUALS(names[0], "p1");
            CXXTOOLS_UNIT_ASSERT_EQUALS(names[1], "p2");
            CXXTOOLS_UNIT_ASSERT_EQUALS(names[2], "");
        }

        void testDeserialization();
        void testDeserializeSpecial();
};

cxxtools::unit::RegisterTest<QueryParamsTest> register_QueryParamsTest;

// helper classes for deserialization

namespace
{
    struct Columns
    {
        std::string name;
        std::string value;
    };

    struct TableQuery
    {
        bool draw;
        std::vector<Columns> columns;
        unsigned start;

        TableQuery() : draw(false), start(0) { }
    };

    void operator>>= (const cxxtools::SerializationInfo& si, Columns& column)
    {
        si.getMember("name") >>= column.name;
        si.getMember("value") >>= column.value;
    }

    void operator>>= (const cxxtools::SerializationInfo& si, TableQuery& query)
    {
        si.getMember("draw") >>= query.draw;
        si.getMember("columns") >>= query.columns;
        si.getMember("start") >>= query.start;
    }

}

void QueryParamsTest::testDeserialization()
{
    cxxtools::QueryParams qp;
    qp.add("draw", "1");
    qp.add("columns[0][name]", "somename");
    qp.add("columns[0][value]", "somevalue");
    qp.add("columns[1][name]", "othername");
    qp.add("columns[1][value]", "othervalue");
    qp.add("start", "42");

    cxxtools::SerializationInfo si;
    si <<= qp;
    CXXTOOLS_UNIT_ASSERT_EQUALS(si.memberCount(), 3);
    CXXTOOLS_UNIT_ASSERT(si.findMember("draw") != 0);
    CXXTOOLS_UNIT_ASSERT(si.findMember("columns") != 0);
    CXXTOOLS_UNIT_ASSERT(si.findMember("start") != 0);

    TableQuery query;
    si >>= query;
    CXXTOOLS_UNIT_ASSERT(query.draw);
    CXXTOOLS_UNIT_ASSERT_EQUALS(query.columns.size(), 2);
    CXXTOOLS_UNIT_ASSERT_EQUALS(query.columns[0].name, "somename");
    CXXTOOLS_UNIT_ASSERT_EQUALS(query.columns[0].value, "somevalue");
    CXXTOOLS_UNIT_ASSERT_EQUALS(query.columns[1].name, "othername");
    CXXTOOLS_UNIT_ASSERT_EQUALS(query.columns[1].value, "othervalue");
    CXXTOOLS_UNIT_ASSERT_EQUALS(query.start, 42);
}

void QueryParamsTest::testDeserializeSpecial()
{
    cxxtools::QueryParams qp;
    qp.add("+Key+[Key%2Bwith space+]", "value");
    qp.add("%2bKey+%20[0]", "value");

    cxxtools::SerializationInfo si;
    si <<= qp;
    CXXTOOLS_UNIT_ASSERT_EQUALS(si.memberCount(), 2);

    log_info(si);

    std::string value;
    CXXTOOLS_UNIT_ASSERT_NOTHROW(si.getMember(" Key ").getMember("Key+with space "));
    CXXTOOLS_UNIT_ASSERT_NOTHROW(si.getMember("+Key  ").getMember("0"));
}
<commit_msg>remove now obsolete query params unit test<commit_after>/*
 * Copyright (C) 2013 Tommi Maekitalo
 *
 * 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.
 *
 * As a special exception, you may use this file as part of a free
 * software library without restriction. Specifically, if other files
 * instantiate templates or use macros or inline functions from this
 * file, or you compile this file and link it with other files to
 * produce an executable, this file does not by itself cause the
 * resulting executable to be covered by the GNU General Public
 * License. This exception does not however invalidate any other
 * reasons why the executable file might be covered by the GNU Library
 * General Public License.
 *
 * 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
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "cxxtools/unit/testsuite.h"
#include "cxxtools/unit/registertest.h"
#include "cxxtools/query_params.h"
#include "cxxtools/serializationinfo.h"
#include "cxxtools/log.h"
#include <vector>
#include <iterator>

log_define("cxxtools.test.queryparams")

class QueryParamsTest : public cxxtools::unit::TestSuite
{
    public:
        QueryParamsTest()
            : cxxtools::unit::TestSuite("queryparams")
        {
            registerMethod("testQueryParams", *this, &QueryParamsTest::testQueryParams);
            registerMethod("testCopy", *this, &QueryParamsTest::testCopy);
            registerMethod("testParseUrl", *this, &QueryParamsTest::testParseUrl);
            registerMethod("testParseUrlSpecialChar", *this, &QueryParamsTest::testParseUrlSpecialChar);
            registerMethod("testParseDoublePercent", *this, &QueryParamsTest::testParseDoublePercent);
            registerMethod("testCount", *this, &QueryParamsTest::testCount);
            registerMethod("testCombine", *this, &QueryParamsTest::testCombine);
            registerMethod("testSet", *this, &QueryParamsTest::testSet);
            registerMethod("testIterator", *this, &QueryParamsTest::testIterator);
            registerMethod("testGetUrl", *this, &QueryParamsTest::testGetUrl);
            registerMethod("testGetNames", *this, &QueryParamsTest::testGetNames);
            registerMethod("testDeserialization", *this, &QueryParamsTest::testDeserialization);
        }

        void testQueryParams()
        {
            cxxtools::QueryParams q;
            q.add("p1", "value1");
            q.add("p2", "value2");
            q.add("value3");
            CXXTOOLS_UNIT_ASSERT(q.has("p1"));
            CXXTOOLS_UNIT_ASSERT(q.has("p2"));
            CXXTOOLS_UNIT_ASSERT(!q.has("p3"));
            CXXTOOLS_UNIT_ASSERT_EQUALS(q["p1"], "value1");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q["p2"], "value2");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q[0], "value3");
        }

        void testCopy()
        {
            cxxtools::QueryParams q;
            q.add("p1", "value1");
            q.add("p2", "value2");
            q.add("value3");
            cxxtools::QueryParams q2 = q;
            CXXTOOLS_UNIT_ASSERT(q2.has("p1"));
            CXXTOOLS_UNIT_ASSERT(q2.has("p2"));
            CXXTOOLS_UNIT_ASSERT(!q2.has("p3"));
            CXXTOOLS_UNIT_ASSERT_EQUALS(q2["p1"], "value1");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q2["p2"], "value2");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q2[0], "value3");
        }

        void testParseUrl()
        {
            cxxtools::QueryParams q;
            q.parse_url("p2=value2&value3&p1=value1");
            CXXTOOLS_UNIT_ASSERT(q.has("p1"));
            CXXTOOLS_UNIT_ASSERT(q.has("p2"));
            CXXTOOLS_UNIT_ASSERT(!q.has("p3"));
            CXXTOOLS_UNIT_ASSERT_EQUALS(q["p1"], "value1");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q["p2"], "value2");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q[0], "value3");
        }

        void testParseUrlSpecialChar()
        {
            cxxtools::QueryParams q;
            q.parse_url("p1=value+with%20spaces&m%a4kitalo=tommi+");
            CXXTOOLS_UNIT_ASSERT(q.has("p1"));
            CXXTOOLS_UNIT_ASSERT(q.has("m\xa4kitalo"));
            CXXTOOLS_UNIT_ASSERT_EQUALS(q["p1"], "value with spaces");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q["m\xa4kitalo"], "tommi ");
        }

        void testParseDoublePercent()
        {
            cxxtools::QueryParams q;
            q.parse_url("%%=%%%");
            CXXTOOLS_UNIT_ASSERT(q.has("%%"));
            CXXTOOLS_UNIT_ASSERT_EQUALS(q["%%"], "%%%");
        }

        void testCount()
        {
            cxxtools::QueryParams q;
            q.add("p1", "value1");
            q.add("p2", "value2");
            q.add("p2", "value3");
            q.add("value4");
            q.add("value5");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q.paramcount(), 2);
            CXXTOOLS_UNIT_ASSERT_EQUALS(q.paramcount("p1"), 1);
            CXXTOOLS_UNIT_ASSERT_EQUALS(q.paramcount("p2"), 2);
        }

        void testCombine()
        {
            cxxtools::QueryParams q;
            cxxtools::QueryParams q2;
            q.add("p1", "value1");
            q2.add("p2", "value2");
            q2.add("value3");
            q.add(q2);
        }

        void testSet()
        {
            cxxtools::QueryParams q;

            q.set("a", "Hi");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q.getUrl(), "a=Hi");

            q.set("b", "foo");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q.getUrl(), "a=Hi&b=foo");

            q.set("a", "There");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q.getUrl(), "b=foo&a=There");

            q.clear();
            CXXTOOLS_UNIT_ASSERT_EQUALS(q.getUrl(), "");

            q.add("a", "Hi");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q.getUrl(), "a=Hi");

            q.add("b", "foo");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q.getUrl(), "a=Hi&b=foo");

            q.add("a", "There");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q.getUrl(), "a=Hi&b=foo&a=There");

        }

        void testIterator()
        {
            cxxtools::QueryParams q;
            q.add("p1", "value1");
            q.add("p2", "value2");
            q.add("p2", "value3");
            q.add("value4");
            q.add("value5");

            cxxtools::QueryParams::const_iterator it;

            it = q.begin();
            CXXTOOLS_UNIT_ASSERT(it != q.end());
            CXXTOOLS_UNIT_ASSERT_EQUALS(*it, "value4");
            ++it;
            CXXTOOLS_UNIT_ASSERT(it != q.end());
            CXXTOOLS_UNIT_ASSERT_EQUALS(*it, "value5");
            ++it;
            CXXTOOLS_UNIT_ASSERT(it == q.end());

            it = q.begin("p1");
            CXXTOOLS_UNIT_ASSERT(it != q.end());
            CXXTOOLS_UNIT_ASSERT_EQUALS(*it, "value1");
            ++it;
            CXXTOOLS_UNIT_ASSERT(it == q.end());

            it = q.begin("p2");
            CXXTOOLS_UNIT_ASSERT(it != q.end());
            CXXTOOLS_UNIT_ASSERT_EQUALS(*it, "value2");
            ++it;
            CXXTOOLS_UNIT_ASSERT(it != q.end());
            CXXTOOLS_UNIT_ASSERT_EQUALS(*it, "value3");
            ++it;
            CXXTOOLS_UNIT_ASSERT(it == q.end());
        }

        void testGetUrl()
        {
            cxxtools::QueryParams q;
            q.add("first name", "Tommi");
            q.add("last name", "M\xa4kitalo");
            q.add("some value");

            std::string url = q.getUrl();
            CXXTOOLS_UNIT_ASSERT_EQUALS(url, "first+name=Tommi&last+name=M%A4kitalo&some+value");
        }

        void testGetNames()
        {
            cxxtools::QueryParams q;
            q.add("p1", "value1");
            q.add("p2", "value2");
            q.add("value3");

            std::vector<std::string> names;
            q.getNames(std::back_inserter(names));
            CXXTOOLS_UNIT_ASSERT_EQUALS(names.size(), 3);
            CXXTOOLS_UNIT_ASSERT_EQUALS(names[0], "p1");
            CXXTOOLS_UNIT_ASSERT_EQUALS(names[1], "p2");
            CXXTOOLS_UNIT_ASSERT_EQUALS(names[2], "");
        }

        void testDeserialization();
};

cxxtools::unit::RegisterTest<QueryParamsTest> register_QueryParamsTest;

// helper classes for deserialization

namespace
{
    struct Columns
    {
        std::string name;
        std::string value;
    };

    struct TableQuery
    {
        bool draw;
        std::vector<Columns> columns;
        unsigned start;

        TableQuery() : draw(false), start(0) { }
    };

    void operator>>= (const cxxtools::SerializationInfo& si, Columns& column)
    {
        si.getMember("name") >>= column.name;
        si.getMember("value") >>= column.value;
    }

    void operator>>= (const cxxtools::SerializationInfo& si, TableQuery& query)
    {
        si.getMember("draw") >>= query.draw;
        si.getMember("columns") >>= query.columns;
        si.getMember("start") >>= query.start;
    }

}

void QueryParamsTest::testDeserialization()
{
    cxxtools::QueryParams qp;
    qp.add("draw", "1");
    qp.add("columns[0][name]", "somename");
    qp.add("columns[0][value]", "somevalue");
    qp.add("columns[1][name]", "othername");
    qp.add("columns[1][value]", "othervalue");
    qp.add("start", "42");

    cxxtools::SerializationInfo si;
    si <<= qp;
    CXXTOOLS_UNIT_ASSERT_EQUALS(si.memberCount(), 3);
    CXXTOOLS_UNIT_ASSERT(si.findMember("draw") != 0);
    CXXTOOLS_UNIT_ASSERT(si.findMember("columns") != 0);
    CXXTOOLS_UNIT_ASSERT(si.findMember("start") != 0);

    TableQuery query;
    si >>= query;
    CXXTOOLS_UNIT_ASSERT(query.draw);
    CXXTOOLS_UNIT_ASSERT_EQUALS(query.columns.size(), 2);
    CXXTOOLS_UNIT_ASSERT_EQUALS(query.columns[0].name, "somename");
    CXXTOOLS_UNIT_ASSERT_EQUALS(query.columns[0].value, "somevalue");
    CXXTOOLS_UNIT_ASSERT_EQUALS(query.columns[1].name, "othername");
    CXXTOOLS_UNIT_ASSERT_EQUALS(query.columns[1].value, "othervalue");
    CXXTOOLS_UNIT_ASSERT_EQUALS(query.start, 42);
}
<|endoftext|>