Files
barrier/server/CHTTPServer.cpp
crs d2135af0d9 fixes, mainly for windows. first, had to add a notification from
CServer to the primary screen when the configuration changes so it
can make necessary adjustments (the win32 primary screen must tell
the hook dll about the new jump zones).

changed includes of some std c++ library files to go through
our own include files.  these wrap the include with stuff to
keep vc++ quiet when compiling at warning level 4, which is
what it does now.  it also works around missing <istream> and
<ostream> on g++2.96.

added missing std:: where necessary.  g++ doesn't really support
namespaces so it lets references without the namespace slip
through.

added workaround or fix.  not sure if istringstream::str(string)
should reset eofbit.  it does on g++ but does not on vc++.
added clear() after str() so it works either way.

added low-level keyboard hook to win32.  if available (it's only
available on NT SP3 and up) it allows us to catch and handle
alt+tab, alt+esc, ctrl+esc, and windows key hot keys.  i think
that leaves only ctrl+alt+del and accessibility functions
uncaught on those systems.
2002-06-01 19:26:11 +00:00

770 lines
18 KiB
C++

#include "CHTTPServer.h"
#include "CHTTPProtocol.h"
#include "XHTTP.h"
#include "CServer.h"
#include "CConfig.h"
#include "CLog.h"
#include "XThread.h"
#include "ISocket.h"
#include "stdset.h"
#include "stdsstream.h"
#include <assert.h>
//
// CHTTPServer
//
CHTTPServer::CHTTPServer(CServer* server) : m_server(server)
{
// do nothing
}
CHTTPServer::~CHTTPServer()
{
// do nothing
}
void CHTTPServer::processRequest(ISocket* socket)
{
assert(socket != NULL);
CHTTPRequest* request = NULL;
try {
// parse request
request = CHTTPProtocol::readRequest(socket->getInputStream());
if (request == NULL) {
throw XHTTP(400);
}
// if absolute uri then strip off scheme and host
if (request->m_uri[0] != '/') {
CString::size_type n = request->m_uri.find('/');
if (n == CString::npos) {
throw XHTTP(404);
}
request->m_uri = request->m_uri.substr(n);
}
// process
CHTTPReply reply;
doProcessRequest(*request, reply);
// send reply
CHTTPProtocol::reply(socket->getOutputStream(), reply);
log((CLOG_INFO "HTTP reply %d for %s %s", reply.m_status, request->m_method.c_str(), request->m_uri.c_str()));
// clean up
delete request;
}
catch (XHTTP& e) {
log((CLOG_WARN "returning HTTP error %d %s for %s", e.getStatus(), e.getReason().c_str(), (request != NULL) ? request->m_uri.c_str() : "<unknown>"));
// clean up
delete request;
// return error
CHTTPReply reply;
reply.m_majorVersion = 1;
reply.m_minorVersion = 0;
reply.m_status = e.getStatus();
reply.m_reason = e.getReason();
reply.m_method = "GET";
// FIXME -- use a nicer error page
reply.m_headers.push_back(std::make_pair(CString("Content-Type"),
CString("text/plain")));
reply.m_body = e.getReason();
e.addHeaders(reply);
CHTTPProtocol::reply(socket->getOutputStream(), reply);
}
catch (...) {
// ignore other exceptions
RETHROW_XTHREAD
}
}
void CHTTPServer::doProcessRequest(
CHTTPRequest& request,
CHTTPReply& reply)
{
reply.m_majorVersion = request.m_majorVersion;
reply.m_minorVersion = request.m_minorVersion;
reply.m_status = 200;
reply.m_reason = "OK";
reply.m_method = request.m_method;
reply.m_headers.push_back(std::make_pair(CString("Content-Type"),
CString("text/html")));
// switch based on uri
if (request.m_uri == "/editmap") {
if (request.m_method == "GET" || request.m_method == "HEAD") {
doProcessGetEditMap(request, reply);
}
else if (request.m_method == "POST") {
doProcessPostEditMap(request, reply);
}
else {
throw XHTTPAllow("GET, HEAD, POST");
}
}
else {
// unknown page
throw XHTTP(404);
}
}
void CHTTPServer::doProcessGetEditMap(
CHTTPRequest& /*request*/,
CHTTPReply& reply)
{
static const char* s_editMapProlog1 =
"<html>\r\n"
"<head>\r\n"
" <title>Synergy -- Edit Screens</title>\r\n"
"</head>\r\n"
"<body>\r\n"
" <form method=\"POST\" action=\"editmap\" "
"enctype=\"multipart/form-data\">\r\n"
" <input type=hidden name=\"size\" value=\"";
static const char* s_editMapProlog2 =
"\">\r\n";
static const char* s_editMapEpilog =
" <input type=submit name=\"submit\">\r\n"
" <input type=reset>\r\n"
" <br>\r\n"
" </form>\r\n"
"</body>\r\n"
"</html>\r\n";
static const char* s_editMapTableProlog =
"<table border=\"1\" cellspacing=\"0\" cellpadding=\"0\"><tr><td>"
" <table border=\"0\" cellspacing=\"2\" cellpadding=\"6\">\r\n";
static const char* s_editMapTableEpilog =
" </table>"
"</td></tr></table>\r\n";
static const char* s_editMapRowProlog =
"<tr align=\"center\" valign=\"top\">\r\n";
static const char* s_editMapRowEpilog =
"</tr>\r\n";
static const char* s_editMapScreenDummy =
"<td>";
static const char* s_editMapScreenPrimary =
"<td bgcolor=\"#2222288\"><input type=\"text\" readonly value=\"";
static const char* s_editMapScreenLive =
"<td bgcolor=\"#cccccc\"><input type=\"text\" value=\"";
static const char* s_editMapScreenDead =
"<td><input type=\"text\" value=\"";
static const char* s_editMapScreenLiveDead1 =
"\" size=\"16\" maxlength=\"64\" name=\"";
static const char* s_editMapScreenLiveDead2 =
"\">";
static const char* s_editMapScreenEnd =
"</td>\r\n";
std::ostringstream s;
// convert screen map into a temporary screen map
CScreenArray screens;
{
CConfig config;
m_server->getConfig(&config);
screens.convertFrom(config);
// FIXME -- note to user if config couldn't be exactly represented
}
// insert blank columns and rows around array (to allow the user
// to insert new screens)
screens.insertColumn(0);
screens.insertColumn(screens.getWidth());
screens.insertRow(0);
screens.insertRow(screens.getHeight());
// get array size
const SInt32 w = screens.getWidth();
const SInt32 h = screens.getHeight();
// construct reply
reply.m_body += s_editMapProlog1;
s << w << "x" << h;
reply.m_body += s.str();
reply.m_body += s_editMapProlog2;
// add screen map for editing
const CString primaryName = m_server->getPrimaryScreenName();
reply.m_body += s_editMapTableProlog;
for (SInt32 y = 0; y < h; ++y) {
reply.m_body += s_editMapRowProlog;
for (SInt32 x = 0; x < w; ++x) {
s.str("");
if (!screens.isAllowed(x, y) && screens.get(x, y) != primaryName) {
s << s_editMapScreenDummy;
}
else {
if (!screens.isSet(x, y)) {
s << s_editMapScreenDead;
}
else if (screens.get(x, y) == primaryName) {
s << s_editMapScreenPrimary;
}
else {
s << s_editMapScreenLive;
}
s << screens.get(x, y) <<
s_editMapScreenLiveDead1 <<
"n" << x << "x" << y <<
s_editMapScreenLiveDead2;
}
s << s_editMapScreenEnd;
reply.m_body += s.str();
}
reply.m_body += s_editMapRowEpilog;
}
reply.m_body += s_editMapTableEpilog;
reply.m_body += s_editMapEpilog;
}
void CHTTPServer::doProcessPostEditMap(
CHTTPRequest& request,
CHTTPReply& reply)
{
typedef std::vector<CString> ScreenArray;
typedef std::set<CString> ScreenSet;
// parse the result
CHTTPProtocol::CFormParts parts;
if (!CHTTPProtocol::parseFormData(request, parts)) {
log((CLOG_WARN "editmap: cannot parse form data"));
throw XHTTP(400);
}
try {
std::ostringstream s;
// convert post data into a temporary screen map. also check
// that no screen name is invalid or used more than once.
SInt32 w, h;
CHTTPProtocol::CFormParts::iterator index = parts.find("size");
if (index == parts.end() ||
!parseXY(index->second, w, h) ||
w <= 0 || h <= 0) {
log((CLOG_WARN "editmap: cannot parse size or size is invalid"));
throw XHTTP(400);
}
ScreenSet screenNames;
CScreenArray screens;
screens.resize(w, h);
for (SInt32 y = 0; y < h; ++y) {
for (SInt32 x = 0; x < w; ++x) {
// find part
s.str("");
s << "n" << x << "x" << y;
index = parts.find(s.str());
if (index == parts.end()) {
// FIXME -- screen is missing. error?
continue;
}
// skip blank names
const CString& name = index->second;
if (name.empty()) {
continue;
}
// check name. name must be legal and must not have
// already been seen.
if (screenNames.count(name)) {
// FIXME -- better error message
log((CLOG_WARN "editmap: duplicate name %s", name.c_str()));
throw XHTTP(400);
}
// FIXME -- check that name is legal
// save name. if we've already seen the name then
// report an error.
screens.set(x, y, name);
screenNames.insert(name);
}
}
// if new map is invalid then return error. map is invalid if:
// there are no screens, or
// the screens are not 4-connected.
if (screenNames.empty()) {
// no screens
// FIXME -- need better no screens
log((CLOG_WARN "editmap: no screens"));
throw XHTTP(400);
}
if (!screens.isValid()) {
// FIXME -- need better unconnected screens error
log((CLOG_WARN "editmap: unconnected screens"));
throw XHTTP(400);
}
// convert temporary screen map into a regular map
CConfig config;
screens.convertTo(config);
// set new screen map on server
m_server->setConfig(config);
// now reply with current map
doProcessGetEditMap(request, reply);
}
catch (XHTTP&) {
// FIXME -- construct a more meaningful error?
throw;
}
}
bool CHTTPServer::parseXY(
const CString& xy, SInt32& x, SInt32& y)
{
std::istringstream s(xy);
char delimiter;
s >> x;
s.get(delimiter);
s >> y;
return (!!s && delimiter == 'x');
}
//
// CHTTPServer::CScreenArray
//
CHTTPServer::CScreenArray::CScreenArray() : m_w(0), m_h(0)
{
// do nothing
}
CHTTPServer::CScreenArray::~CScreenArray()
{
// do nothing
}
void CHTTPServer::CScreenArray::resize(SInt32 w, SInt32 h)
{
m_screens.clear();
m_screens.resize(w * h);
m_w = w;
m_h = h;
}
void CHTTPServer::CScreenArray::insertRow(SInt32 i)
{
assert(i >= 0 && i <= m_h);
CNames newScreens;
newScreens.resize(m_w * (m_h + 1));
for (SInt32 y = 0; y < i; ++y) {
for (SInt32 x = 0; x < m_w; ++x) {
newScreens[x + y * m_w] = m_screens[x + y * m_w];
}
}
for (SInt32 y = i; y < m_h; ++y) {
for (SInt32 x = 0; x < m_w; ++x) {
newScreens[x + (y + 1) * m_w] = m_screens[x + y * m_w];
}
}
m_screens.swap(newScreens);
++m_h;
}
void CHTTPServer::CScreenArray::insertColumn(SInt32 i)
{
assert(i >= 0 && i <= m_w);
CNames newScreens;
newScreens.resize((m_w + 1) * m_h);
for (SInt32 y = 0; y < m_h; ++y) {
for (SInt32 x = 0; x < i; ++x) {
newScreens[x + y * (m_w + 1)] = m_screens[x + y * m_w];
}
for (SInt32 x = i; x < m_w; ++x) {
newScreens[(x + 1) + y * (m_w + 1)] = m_screens[x + y * m_w];
}
}
m_screens.swap(newScreens);
++m_w;
}
void CHTTPServer::CScreenArray::eraseRow(SInt32 i)
{
assert(i >= 0 && i < m_h);
CNames newScreens;
newScreens.resize(m_w * (m_h - 1));
for (SInt32 y = 0; y < i; ++y) {
for (SInt32 x = 0; x < m_w; ++x) {
newScreens[x + y * m_w] = m_screens[x + y * m_w];
}
}
for (SInt32 y = i + 1; y < m_h; ++y) {
for (SInt32 x = 0; x < m_w; ++x) {
newScreens[x + (y - 1) * m_w] = m_screens[x + y * m_w];
}
}
m_screens.swap(newScreens);
--m_h;
}
void CHTTPServer::CScreenArray::eraseColumn(SInt32 i)
{
assert(i >= 0 && i < m_w);
CNames newScreens;
newScreens.resize((m_w - 1) * m_h);
for (SInt32 y = 0; y < m_h; ++y) {
for (SInt32 x = 0; x < m_w; ++x) {
newScreens[x + y * (m_w - 1)] = m_screens[x + y * m_w];
}
for (SInt32 x = i + 1; x < m_w; ++x) {
newScreens[(x - 1) + y * (m_w - 1)] = m_screens[x + y * m_w];
}
}
m_screens.swap(newScreens);
--m_w;
}
void CHTTPServer::CScreenArray::rotateRows(SInt32 i)
{
// nothing to do if no rows
if (m_h == 0) {
return;
}
// convert to canonical form
if (i < 0) {
i = m_h - ((-i) % m_h);
}
else {
i %= m_h;
}
if (i == 0 || i == m_h) {
return;
}
while (i > 0) {
// rotate one row
for (SInt32 x = 0; x < m_w; ++x) {
CString tmp = m_screens[x];
for (SInt32 y = 1; y < m_h; ++y) {
m_screens[x + (y - 1) * m_w] = m_screens[x + y * m_w];
}
m_screens[x + (m_h - 1) * m_w] = tmp;
}
}
}
void CHTTPServer::CScreenArray::rotateColumns(SInt32 i)
{
// nothing to do if no columns
if (m_h == 0) {
return;
}
// convert to canonical form
if (i < 0) {
i = m_w - ((-i) % m_w);
}
else {
i %= m_w;
}
if (i == 0 || i == m_w) {
return;
}
while (i > 0) {
// rotate one column
for (SInt32 y = 0; y < m_h; ++y) {
CString tmp = m_screens[0 + y * m_w];
for (SInt32 x = 1; x < m_w; ++x) {
m_screens[x - 1 + y * m_w] = m_screens[x + y * m_w];
}
m_screens[m_w - 1 + y * m_w] = tmp;
}
}
}
void CHTTPServer::CScreenArray::remove(SInt32 x, SInt32 y)
{
set(x, y, CString());
}
void CHTTPServer::CScreenArray::set(
SInt32 x, SInt32 y, const CString& name)
{
assert(x >= 0 && x < m_w);
assert(y >= 0 && y < m_h);
m_screens[x + y * m_w] = name;
}
bool CHTTPServer::CScreenArray::isAllowed(
SInt32 x, SInt32 y) const
{
assert(x >= 0 && x < m_w);
assert(y >= 0 && y < m_h);
if (x > 0 && !m_screens[(x - 1) + y * m_w].empty()) {
return true;
}
if (x < m_w - 1 && !m_screens[(x + 1) + y * m_w].empty()) {
return true;
}
if (y > 0 && !m_screens[x + (y - 1) * m_w].empty()) {
return true;
}
if (y < m_h - 1 && !m_screens[x + (y + 1) * m_w].empty()) {
return true;
}
return false;
}
bool CHTTPServer::CScreenArray::isSet(
SInt32 x, SInt32 y) const
{
assert(x >= 0 && x < m_w);
assert(y >= 0 && y < m_h);
return !m_screens[x + y * m_w].empty();
}
CString CHTTPServer::CScreenArray::get(
SInt32 x, SInt32 y) const
{
assert(x >= 0 && x < m_w);
assert(y >= 0 && y < m_h);
return m_screens[x + y * m_w];
}
bool CHTTPServer::CScreenArray::find(
const CString& name,
SInt32& xOut, SInt32& yOut) const
{
for (SInt32 y = 0; y < m_h; ++y) {
for (SInt32 x = 0; x < m_w; ++x) {
if (m_screens[x + y * m_w] == name) {
xOut = x;
yOut = y;
return true;
}
}
}
return false;
}
bool CHTTPServer::CScreenArray::isValid() const
{
SInt32 count = 0, isolated = 0;
for (SInt32 y = 0; y < m_h; ++y) {
for (SInt32 x = 0; x < m_w; ++x) {
if (isSet(x, y)) {
++count;
if (!isAllowed(x, y)) {
++isolated;
}
}
}
}
return (count <= 1 || isolated == 0);
}
bool CHTTPServer::CScreenArray::convertFrom(
const CConfig& config)
{
typedef std::set<CString> ScreenSet;
// insert the first screen
CConfig::const_iterator index = config.begin();
if (index == config.end()) {
// no screens
resize(0, 0);
return true;
}
CString name = *index;
resize(1, 1);
set(0, 0, name);
// flood fill state
CNames screenStack;
ScreenSet doneSet;
// put all but the first screen on the stack
// note -- if all screens are 4-connected then we can skip this
while (++index != config.end()) {
screenStack.push_back(*index);
}
// put the first screen on the stack last so we process it first
screenStack.push_back(name);
// perform a flood fill using the stack as the seeds
while (!screenStack.empty()) {
// get next screen from stack
CString name = screenStack.back();
screenStack.pop_back();
// skip screen if we've seen it before
if (doneSet.count(name) > 0) {
continue;
}
// add this screen to doneSet so we don't process it again
doneSet.insert(name);
// find the screen. if it's not found then not all of the
// screens are 4-connected. discard disconnected screens.
SInt32 x, y;
if (!find(name, x, y)) {
continue;
}
// insert the screen's neighbors
// FIXME -- handle edge wrapping
CString neighbor;
neighbor = config.getNeighbor(name, CConfig::kLeft);
if (!neighbor.empty() && doneSet.count(neighbor) == 0) {
// insert left neighbor, adding a column if necessary
if (x == 0 || get(x - 1, y) != neighbor) {
++x;
insertColumn(x - 1);
set(x - 1, y, neighbor);
}
screenStack.push_back(neighbor);
}
neighbor = config.getNeighbor(name, CConfig::kRight);
if (!neighbor.empty() && doneSet.count(neighbor) == 0) {
// insert right neighbor, adding a column if necessary
if (x == m_w - 1 || get(x + 1, y) != neighbor) {
insertColumn(x + 1);
set(x + 1, y, neighbor);
}
screenStack.push_back(neighbor);
}
neighbor = config.getNeighbor(name, CConfig::kTop);
if (!neighbor.empty() && doneSet.count(neighbor) == 0) {
// insert top neighbor, adding a row if necessary
if (y == 0 || get(x, y - 1) != neighbor) {
++y;
insertRow(y - 1);
set(x, y - 1, neighbor);
}
screenStack.push_back(neighbor);
}
neighbor = config.getNeighbor(name, CConfig::kBottom);
if (!neighbor.empty() && doneSet.count(neighbor) == 0) {
// insert bottom neighbor, adding a row if necessary
if (y == m_h - 1 || get(x, y + 1) != neighbor) {
insertRow(y + 1);
set(x, y + 1, neighbor);
}
screenStack.push_back(neighbor);
}
}
// check symmetry
// FIXME -- handle edge wrapping
for (index = config.begin(); index != config.end(); ++index) {
const CString& name = *index;
SInt32 x, y;
if (!find(name, x, y)) {
return false;
}
CString neighbor;
neighbor = config.getNeighbor(name, CConfig::kLeft);
if ((x == 0 && !neighbor.empty()) ||
(x > 0 && get(x - 1, y) != neighbor)) {
return false;
}
neighbor = config.getNeighbor(name, CConfig::kRight);
if ((x == m_w - 1 && !neighbor.empty()) ||
(x < m_w - 1 && get(x + 1, y) != neighbor)) {
return false;
}
neighbor = config.getNeighbor(name, CConfig::kTop);
if ((y == 0 && !neighbor.empty()) ||
(y > 0 && get(x, y - 1) != neighbor)) {
return false;
}
neighbor = config.getNeighbor(name, CConfig::kBottom);
if ((y == m_h - 1 && !neighbor.empty()) ||
(y < m_h - 1 && get(x, y + 1) != neighbor)) {
return false;
}
}
return true;
}
void CHTTPServer::CScreenArray::convertTo(
CConfig& config) const
{
// add screens and find smallest box containing all screens
SInt32 x0 = m_w, x1 = 0, y0 = m_h, y1 = 0;
for (SInt32 y = 0; y < m_h; ++y) {
for (SInt32 x = 0; x < m_w; ++x) {
if (isSet(x, y)) {
config.addScreen(get(x, y));
if (x < x0) {
x0 = x;
}
if (x > x1) {
x1 = x;
}
if (y < y0) {
y0 = y;
}
if (y > y1) {
y1 = y;
}
}
}
}
// make connections between screens
// FIXME -- add support for wrapping
// FIXME -- mark topmost and leftmost screens
for (SInt32 y = 0; y < m_h; ++y) {
for (SInt32 x = 0; x < m_w; ++x) {
if (!isSet(x, y)) {
continue;
}
if (x > x0 && isSet(x - 1, y)) {
config.connect(get(x, y),
CConfig::kLeft,
get(x - 1, y));
}
if (x < x1 && isSet(x + 1, y)) {
config.connect(get(x, y),
CConfig::kRight,
get(x + 1, y));
}
if (y > y0 && isSet(x, y - 1)) {
config.connect(get(x, y),
CConfig::kTop,
get(x, y - 1));
}
if (y < y1 && isSet(x, y + 1)) {
config.connect(get(x, y),
CConfig::kBottom,
get(x, y + 1));
}
}
}
}