コード例 #1
0
 void PhpResultGenerator::generateAtom (StringBuffer& output, string const& value) const {
   output.appendText("s:");
   output.appendInteger(value.size());
   output.appendText(":\"");
   output.appendText(value);
   output.appendText("\";");
 }
コード例 #2
0
 void XmlResultGenerator::generateAtom (StringBuffer& output, char const* value) const {
   if (value == 0) {
     output.appendText("");
   }
   else {
     output.appendText(StringUtils::escapeXml(value));
   }
 }
コード例 #3
0
 void JsonResultGenerator::generateAtom (StringBuffer& output, bool value) const {
   if (value) {
     output.appendText("true", 4);
   }
   else {
     output.appendText("false", 5);
   }
 }
コード例 #4
0
 void XmlResultGenerator::generateAtom (StringBuffer& output, char const* value, size_t length, bool) const {
   if (value == 0) {
     output.appendText("");
   }
   else {
     string v(value, length);
     output.appendText(StringUtils::escapeXml(v));
   }
 }
コード例 #5
0
 void JsonResultGenerator::generateAtom (StringBuffer& output, char const* value) const {
   if (value == 0) {
     output.appendText("null");
   }
   else {
     output.appendText("\"");
     output.appendText(StringUtils::escapeUnicode(value));
     output.appendText("\"");
   }
 }
コード例 #6
0
 void PhpResultGenerator::generateAtom (StringBuffer& output, char const* value) const {
   if (value == 0) {
     output.appendText("N;");
   }
   else {
     output.appendText("s:");
     output.appendInteger(strlen(value));
     output.appendText(":\"");
     output.appendText(value);
     output.appendText("\";");
   }
 }
コード例 #7
0
 void PhpResultGenerator::generateAtom (StringBuffer& output, char const* value, size_t length, bool) const {
   if (value == 0) {
     output.appendText("N;");
   }
   else {
     output.appendText("s:");
     output.appendInteger(length);
     output.appendText(":\"");
     output.appendText(value, length);
     output.appendText("\";");
   }
 }
コード例 #8
0
    void XmlResultGenerator::generateAtom (StringBuffer& output, float value) const {
      if (value == 0.0) {
        output.appendText("0.0", 3);
        return;
      }

      int intType = _fpclass(value);

      switch (intType) {
        case _FPCLASS_PN:
        case _FPCLASS_NN:
        case _FPCLASS_NZ:
        case _FPCLASS_PZ: {
          output.appendDecimal(value);
          break;
        }
        case _FPCLASS_NINF: {
          generateAtom(output, "-INF");
          break;
        }
        case _FPCLASS_PINF: {
          generateAtom(output, "INF");
          break;
        }
        default: {
          generateAtom(output, "NAN");
          break;
        }
      }
    }
コード例 #9
0
    void JsonResultGenerator::generateAtom (StringBuffer& output, char const* value, size_t length, bool quote) const {
      if (value == 0) {
        if (quote) {
          output.appendText("null");
        }
      }
      else {
        string v(value, length);

        if (quote) {
          output.appendText("\"");
          output.appendText(StringUtils::escapeUnicode(v));
          output.appendText("\"");
        }
        else {
          output.appendText(v);
        }
      }
    }
コード例 #10
0
ファイル: CubeWorker.cpp プロジェクト: Dalboz/molap
ResultStatus CubeWorker::setCellValue(const string& areaIdentifier, const string& sessionIdentifier, const IdentifiersType& path, double value, SplashMode splashMode, bool addValue)
{
	StringBuffer sb;

	sb.appendText("DOUBLE;");
	sb.appendInteger(dbid);
	sb.appendChar(';');
	sb.appendInteger(cubeid);
	sb.appendChar(';');
	sb.appendText(StringUtils::escapeString(areaIdentifier));
	sb.appendChar(';');
	sb.appendText(StringUtils::escapeString(sessionIdentifier));
	sb.appendChar(';');
	sb.appendText(buildPathString(&path));
	sb.appendChar(';');
	sb.appendDecimal(value);
	sb.appendChar(';');
	sb.appendInteger(PaloJob::splashNumber(splashMode));
	sb.appendChar(';');
	sb.appendInteger((int)addValue);

	// we need to change the job-type
	//    dispatcher->downgradeCurrentJobs();

	// send set-cell-value request to worker
	vector<string> result;

	ResultStatus status = execute(sb.c_str(), result);

	if (status != RESULT_OK) {
		return status;
	}

	if (isErrorStatus(result)) {
		throw WorkerException(result[0].substr(6), true);
	} else if (isExceptionStatus(result)) {
		throw WorkerException(result[0].substr(10), true);
	}

	numFailures = 0;
	return RESULT_OK;
}
コード例 #11
0
    void PhpResultGenerator::generateAtom (StringBuffer& output, float value) const {
      output.appendText("d:");

      if (value == 0.0) {
        output.appendText("0.0", 3);
      }
      else if (isnormal(value)) {
        output.appendDecimal(value);
      }
      else {
        int a = isinf(value);

        if (a == -1) {
          output.appendText("-INF");
        }
        else if (a == 1) {
          output.appendText("INF");
        }
        else /* if (isnan(value)) */ {
          output.appendText("NAN");
        }
      }

      output.appendText(";");
    }
コード例 #12
0
void HttpCommTask::finishedChunked () {
  StringBuffer* buffer = new StringBuffer(TRI_UNKNOWN_MEM_ZONE, 6);
  buffer->appendText("0\r\n\r\n");

  _writeBuffers.push_back(buffer);

#ifdef TRI_ENABLE_FIGURES
  _writeBuffersStats.push_back(0);
#endif

  _isChunked = false;
  _requestPending = false;

  fillWriteBuffer();
  processRead();
}
コード例 #13
0
ファイル: HttpServerTask.cpp プロジェクト: Dalboz/molap
void HttpServerTask::addResponse(HttpResponse* response)
{
	StringBuffer * buffer;

	// save header
	buffer = new StringBuffer();
	buffer->replaceText(response->getHeader());
	buffer->appendText(response->getBody());

	writeBuffers.push_back(buffer);

	// clear body
	response->getBody().clear();

	// start output
	fillWriteBuffer();
}
コード例 #14
0
    void XmlResultGenerator::generateAtom (StringBuffer& output, double value) const {
      if (value == 0.0) {
        output.appendText("0.0", 3);
      }
      else if (isnormal(value)) {
        output.appendDecimal(value);
      }
      else {
        int a = isinf(value);

        if (a == -1) {
          generateAtom(output, "-INF");
        }
        else if (a == 1) {
          generateAtom(output, "INF");
        }
        else /* if (isnan(value)) */ {
          generateAtom(output, "NAN");
        }
      }
    }
コード例 #15
0
ファイル: FileUtils.cpp プロジェクト: MohdVara/ArangoDB
      void slurp (string const& filename, StringBuffer& result) {
        int fd = TRI_OPEN(filename.c_str(), O_RDONLY);

        if (fd == -1) {
          THROW_FILE_OPEN_ERROR("open", filename, "O_RDONLY", errno);
        }
        
        // reserve space in the output buffer
        off_t fileSize = size(filename);
        if (fileSize > 0) {
          result.reserve((size_t) fileSize);
        }

        char buffer[10240];

        while (true) {
          ssize_t n = TRI_READ(fd, buffer, sizeof(buffer));

          if (n == 0) {
            break;
          }

          if (n < 0) {
            TRI_CLOSE(fd);
            LOG_TRACE("read failed for '%s' with %s and result %d on fd %d",
                      filename.c_str(),
                      strerror(errno),
                      (int) n,
                      fd); 
            THROW_FILE_FUNC_ERROR("read", "", errno);
          }

          result.appendText(buffer, n);
        }

        TRI_CLOSE(fd);
      }
コード例 #16
0
ファイル: VariantInt64.cpp プロジェクト: eranshir/ArangoDB
void VariantInt64::print (StringBuffer& buffer, size_t) const {
    buffer.appendText("(int64) ");
    buffer.appendInteger(getValue());
    buffer.appendEol();
}
コード例 #17
0
ファイル: VariantString.cpp プロジェクト: eranshir/ArangoDB
void VariantString::print (StringBuffer& buffer, size_t) const {
  buffer.appendText(_value.c_str(), _value.length());
  buffer.appendEol();
}
コード例 #18
0
 void PhpResultGenerator::generateAtom (StringBuffer& output, uint64_t value) const {
   output.appendText("i:");
   output.appendInteger(value);
   output.appendText(";");
 }
コード例 #19
0
 void PhpResultGenerator::generateAtom (StringBuffer& output, bool value) const {
   output.appendText(value ? "b:1;" : "b:0;");
 }
コード例 #20
0
 void XmlResultGenerator::generateResultEnd (StringBuffer& output, VariantObject*) const {
   output.appendText("</result>");
 }
コード例 #21
0
ファイル: VariantDate.cpp プロジェクト: santanu1122/ArangoDB
void VariantDate::print (StringBuffer& buffer, size_t) const {
  buffer.appendText("(date) ");
  buffer.appendDecimal(getValue());
  buffer.appendEol();
}
コード例 #22
0
 void XmlResultGenerator::generateAtom (StringBuffer& output, bool value) const {
   output.appendText(value ? "true" : "false");
 }
コード例 #23
0
 void JsonResultGenerator::generateAtom (StringBuffer& output, string const& value) const {
   output.appendText("\"");
   output.appendText(StringUtils::escapeUnicode(value));
   output.appendText("\"");
 }
コード例 #24
0
 void XmlResultGenerator::generateAtom (StringBuffer& output, string const& value) const {
   output.appendText(StringUtils::escapeXml(value));
 }
コード例 #25
0
bool HttpCommTask::processRead () {
  if (_requestPending || _readBuffer->c_str() == nullptr) {
    return false;
  }

  bool handleRequest = false;

  // still trying to read the header fields
  if (! _readRequestBody) {

    // starting a new request
    if (_newRequest) {
#ifdef TRI_ENABLE_FIGURES
      RequestStatisticsAgent::acquire();
      RequestStatisticsAgentSetReadStart(this);
#endif

      _newRequest      = false;
      _startPosition   = _readPosition;
      _httpVersion     = HttpRequest::HTTP_UNKNOWN;
      _requestType     = HttpRequest::HTTP_REQUEST_ILLEGAL;
      _fullUrl         = "";
      _denyCredentials = false;
      _acceptDeflate   = false;

      _sinceCompactification++;
    }

    const char * ptr = _readBuffer->c_str() + _readPosition;
    const char * end = _readBuffer->end() - 3;

    for (;  ptr < end;  ptr++) {
      if (ptr[0] == '\r' && ptr[1] == '\n' && ptr[2] == '\r' && ptr[3] == '\n') {
        break;
      }
    }

    // check if header is too large
    size_t headerLength = ptr - (_readBuffer->c_str() + _startPosition);

    if (headerLength > _maximalHeaderSize) {
      LOG_WARNING("maximal header size is %d, request header size is %d",
                  (int) _maximalHeaderSize,
                  (int) headerLength);

      // header is too large
      HttpResponse response(HttpResponse::REQUEST_HEADER_FIELDS_TOO_LARGE, getCompatibility());

      // we need to close the connection, because there is no way we 
      // know what to remove and then continue
      resetState(true);
      handleResponse(&response);

      return false;
    }

    // header is complete
    if (ptr < end) {
      _readPosition = ptr - _readBuffer->c_str() + 4;

      LOG_TRACE("HTTP READ FOR %p: %s", (void*) this,
                string(_readBuffer->c_str() + _startPosition,
                            _readPosition - _startPosition).c_str());

      // check that we know, how to serve this request
      // and update the connection information, i. e. client and server addresses and ports
      // and create a request context for that request
      _request = _server->handlerFactory()->createRequest(
        _connectionInfo,
        _readBuffer->c_str() + _startPosition,
        _readPosition - _startPosition);

      if (_request == nullptr) {
        LOG_ERROR("cannot generate request");

        // internal server error
        HttpResponse response(HttpResponse::SERVER_ERROR, getCompatibility());

        // we need to close the connection, because there is no way we 
        // know how to remove the body and then continue
        resetState(true);
        handleResponse(&response);

        return false;
      }

      _request->setClientTaskId(_taskId);

      // check HTTP protocol version
      _httpVersion = _request->httpVersion();

      if (_httpVersion != HttpRequest::HTTP_1_0 &&
          _httpVersion != HttpRequest::HTTP_1_1) {

        HttpResponse response(HttpResponse::HTTP_VERSION_NOT_SUPPORTED, getCompatibility());

        // we need to close the connection, because there is no way we 
        // know what to remove and then continue
        resetState(true);
        handleResponse(&response);

        return false;
      }

      // check max URL length
      _fullUrl = _request->fullUrl();

      if (_fullUrl.size() > 16384) {
        HttpResponse response(HttpResponse::REQUEST_URI_TOO_LONG, getCompatibility());

        // we need to close the connection, because there is no way we 
        // know what to remove and then continue
        resetState(true);
        handleResponse(&response);

        return false;
      }

      // update the connection information, i. e. client and server addresses and ports
      _request->setProtocol(_server->protocol());

      LOG_TRACE("server port %d, client port %d",
                (int) _connectionInfo.serverPort,
                (int) _connectionInfo.clientPort);

      // set body start to current position
      _bodyPosition = _readPosition;
      _bodyLength = 0;

      // keep track of the original value of the "origin" request header (if any)
      // we need this value to handle CORS requests
      _origin = _request->header("origin");

      if (! _origin.empty()) {

        // check for Access-Control-Allow-Credentials header
        bool found;
        string const& allowCredentials = _request->header("access-control-allow-credentials", found);

        if (found) {
          _denyCredentials = ! StringUtils::boolean(allowCredentials);
        }
      }

      // store the original request's type. we need it later when responding
      // (original request object gets deleted before responding)
      _requestType = _request->requestType();

#ifdef TRI_ENABLE_FIGURES
      RequestStatisticsAgentSetRequestType(this, _requestType);
#endif

      // handle different HTTP methods
      switch (_requestType) {
        case HttpRequest::HTTP_REQUEST_GET:
        case HttpRequest::HTTP_REQUEST_DELETE:
        case HttpRequest::HTTP_REQUEST_HEAD:
        case HttpRequest::HTTP_REQUEST_OPTIONS:
        case HttpRequest::HTTP_REQUEST_POST:
        case HttpRequest::HTTP_REQUEST_PUT:
        case HttpRequest::HTTP_REQUEST_PATCH: {
          // technically, sending a body for an HTTP DELETE request is not forbidden, but it is not explicitly supported
          const bool expectContentLength = (_requestType == HttpRequest::HTTP_REQUEST_POST
                                            || _requestType == HttpRequest::HTTP_REQUEST_PUT
                                            || _requestType == HttpRequest::HTTP_REQUEST_PATCH
                                            || _requestType == HttpRequest::HTTP_REQUEST_OPTIONS
                                            || _requestType == HttpRequest::HTTP_REQUEST_DELETE);

          if (! checkContentLength(expectContentLength)) {
            return false;
          }

          if (_bodyLength == 0) {
            handleRequest = true;
          }

          break;
        }

        default: {
          size_t l = _readPosition - _startPosition;

          if (6 < l) {
            l = 6;
          }

          LOG_WARNING("got corrupted HTTP request '%s'",
                      string(_readBuffer->c_str() + _startPosition, l).c_str());

          // bad request, method not allowed
          HttpResponse response(HttpResponse::METHOD_NOT_ALLOWED, getCompatibility());

          // we need to close the connection, because there is no way we 
          // know what to remove and then continue
          resetState(true);

          // force a socket close, response will be ignored!
          TRI_CLOSE_SOCKET(_commSocket);
          TRI_invalidatesocket(&_commSocket);

          // might delete this
          handleResponse(&response);

          return false;
        }
      }

      // .............................................................................
      // check if server is active
      // .............................................................................

      Scheduler const* scheduler = _server->scheduler();

      if (scheduler != nullptr && ! scheduler->isActive()) {
        // server is inactive and will intentionally respond with HTTP 503
        LOG_TRACE("cannot serve request - server is inactive");

        HttpResponse response(HttpResponse::SERVICE_UNAVAILABLE, getCompatibility());

        // we need to close the connection, because there is no way we 
        // know what to remove and then continue
        resetState(true);
        handleResponse(&response);

        return false;
      }

      // check for a 100-continue
      if (_readRequestBody) {
        bool found;
        string const& expect = _request->header("expect", found);

        if (found && StringUtils::trim(expect) == "100-continue") {
          LOG_TRACE("received a 100-continue request");

          StringBuffer* buffer = new StringBuffer(TRI_UNKNOWN_MEM_ZONE);
          buffer->appendText("HTTP/1.1 100 (Continue)\r\n\r\n");

          _writeBuffers.push_back(buffer);

#ifdef TRI_ENABLE_FIGURES
          _writeBuffersStats.push_back(0);
#endif

          fillWriteBuffer();
        }
      }
    }
    else {
      size_t l = (_readBuffer->end() - _readBuffer->c_str());

      if (_startPosition + 4 <= l) {
        _readPosition = l - 4;
      }
    }
  }

  // readRequestBody might have changed, so cannot use else
  if (_readRequestBody) {
    if (_readBuffer->length() - _bodyPosition < _bodyLength) {
      setKeepAliveTimeout(60.0);

      // let client send more
      return false;
    }

    // read "bodyLength" from read buffer and add this body to "httpRequest"
    _request->setBody(_readBuffer->c_str() + _bodyPosition, _bodyLength);

    LOG_TRACE("%s", string(_readBuffer->c_str() + _bodyPosition, _bodyLength).c_str());

    // remove body from read buffer and reset read position
    _readRequestBody = false;
    handleRequest = true;
  }

  // .............................................................................
  // request complete
  //
  // we have to delete request in here or pass it to a handler, which will delete
  // it
  // .............................................................................

  if (! handleRequest) {
    return false;
  }

  RequestStatisticsAgentSetReadEnd(this);
  RequestStatisticsAgentAddReceivedBytes(this, _bodyPosition - _startPosition + _bodyLength);

  bool isOptions = (_requestType == HttpRequest::HTTP_REQUEST_OPTIONS);
  resetState(false);

  // .............................................................................
  // keep-alive handling
  // .............................................................................

  string connectionType = StringUtils::tolower(_request->header("connection"));

  if (connectionType == "close") {
    // client has sent an explicit "Connection: Close" header. we should close the connection
    LOG_DEBUG("connection close requested by client");
    _closeRequested = true;
  }
  else if (_request->isHttp10() && connectionType != "keep-alive") {
    // HTTP 1.0 request, and no "Connection: Keep-Alive" header sent
    // we should close the connection
    LOG_DEBUG("no keep-alive, connection close requested by client");
    _closeRequested = true;
  }
  else if (_keepAliveTimeout <= 0.0) {
    // if keepAliveTimeout was set to 0.0, we'll close even keep-alive connections immediately
    LOG_DEBUG("keep-alive disabled by admin");
    _closeRequested = true;
  }

  // we keep the connection open in all other cases (HTTP 1.1 or Keep-Alive header sent)

  // .............................................................................
  // authenticate
  // .............................................................................

  auto const compatibility = _request->compatibility();

  HttpResponse::HttpResponseCode authResult = _server->handlerFactory()->authenticateRequest(_request);

  // authenticated or an OPTIONS request. OPTIONS requests currently go unauthenticated
  if (authResult == HttpResponse::OK || isOptions) {

    // handle HTTP OPTIONS requests directly
    if (isOptions) {
      processCorsOptions(compatibility);
    }
    else {
      processRequest(compatibility);
    }
  }

  // not found
  else if (authResult == HttpResponse::NOT_FOUND) {
    HttpResponse response(authResult, compatibility);
    response.setContentType("application/json; charset=utf-8");

    response.body()
    .appendText("{\"error\":true,\"errorMessage\":\"")
    .appendText(TRI_errno_string(TRI_ERROR_ARANGO_DATABASE_NOT_FOUND))
    .appendText("\",\"code\":")
    .appendInteger((int) authResult)
    .appendText(",\"errorNum\":")
    .appendInteger(TRI_ERROR_ARANGO_DATABASE_NOT_FOUND)
    .appendText("}");

    clearRequest();
    handleResponse(&response);
  }

  // forbidden
  else if (authResult == HttpResponse::FORBIDDEN) {
    HttpResponse response(authResult, compatibility);
    response.setContentType("application/json; charset=utf-8");

    response.body()
    .appendText("{\"error\":true,\"errorMessage\":\"change password\",\"code\":")
    .appendInteger((int) authResult)
    .appendText(",\"errorNum\":")
    .appendInteger(TRI_ERROR_USER_CHANGE_PASSWORD)
    .appendText("}");

    clearRequest();
    handleResponse(&response);
  }

  // not authenticated
  else {
    HttpResponse response(HttpResponse::UNAUTHORIZED, compatibility);
    const string realm = "basic realm=\"" + _server->handlerFactory()->authenticationRealm(_request) + "\"";

    if (sendWwwAuthenticateHeader()) {
      response.setHeader("www-authenticate", strlen("www-authenticate"), realm.c_str());
    }

    clearRequest();
    handleResponse(&response);
  }

  return true;
}
コード例 #26
0
ファイル: VariantNull.cpp プロジェクト: mokerjoke/ArangoDB
void VariantNull::print (StringBuffer& buffer, size_t) const {
  buffer.appendText("(null)");
  buffer.appendEol();
}
コード例 #27
0
void HttpCommTask::addResponse (HttpResponse* response) {

  // CORS response handling
  if (! _origin.empty()) {

    // the request contained an Origin header. We have to send back the
    // access-control-allow-origin header now
    LOG_TRACE("handling CORS response");

    response->setHeader("access-control-expose-headers",
                        strlen("access-control-expose-headers"),
                        "etag, content-encoding, content-length, location, server, x-arango-errors, x-arango-async-id");

    // TODO: check whether anyone actually needs these headers in the browser:
    // x-arango-replication-checkmore, x-arango-replication-lastincluded,
    // x-arango-replication-lasttick, x-arango-replication-active");

    // send back original value of "Origin" header
    response->setHeader("access-control-allow-origin", strlen("access-control-allow-origin"), _origin);

    // send back "Access-Control-Allow-Credentials" header
    if (_denyCredentials) {
      response->setHeader("access-control-allow-credentials", "false");
    }
    else {
      response->setHeader("access-control-allow-credentials", "true");
    }
  }
  // CORS request handling EOF

  // set "connection" header
  if (_closeRequested) {
    response->setHeader("connection", strlen("connection"), "Close");
  }
  else {
    // keep-alive is the default
    response->setHeader("connection", strlen("connection"), "Keep-Alive");
  }

  size_t responseBodyLength = response->bodySize();

  if (_requestType == HttpRequest::HTTP_REQUEST_HEAD) {
    // clear body if this is an HTTP HEAD request
    // HEAD must not return a body
    response->headResponse(responseBodyLength);
  }
  // else {
  //    // to enable automatic deflating of responses, active this.
  //   // deflate takes a lot of CPU time so it should only be enabled for
  //   // dedicated purposes and not generally
  //   if (responseBodyLength > 16384  && _acceptDeflate) {
  //     response->deflate();
  //     responseBodyLength = response->bodySize();
  //   }
  // }

  // reserve some outbuffer size
  StringBuffer* buffer
    = new StringBuffer(TRI_UNKNOWN_MEM_ZONE, responseBodyLength + 128);

  // write header
  response->writeHeader(buffer);

  // write body
  if (_requestType != HttpRequest::HTTP_REQUEST_HEAD) {
    if (_isChunked) {
      if (0 != responseBodyLength) {
        buffer->appendHex(response->body().length());
        buffer->appendText("\r\n");
        buffer->appendText(response->body());
        buffer->appendText("\r\n");
      }
    }
    else {
      buffer->appendText(response->body());
    }
  }

  _writeBuffers.push_back(buffer);
          
  LOG_TRACE("HTTP WRITE FOR %p: %s", (void*) this, buffer->c_str());
          
  // clear body
  response->body().clear();
          
  double totalTime;

#ifdef TRI_ENABLE_FIGURES
  _writeBuffersStats.push_back(RequestStatisticsAgent::transfer());
  totalTime = RequestStatisticsAgent::elapsedSinceReadStart();
#else
  totalTime = 0.0;
#endif

  // disable the following statement to prevent excessive logging of incoming requests
  LOG_USAGE(",\"http-request\",\"%s\",\"%s\",\"%s\",%d,%llu,%llu,\"%s\",%.6f",
            _connectionInfo.clientAddress.c_str(),
            HttpRequest::translateMethod(_requestType).c_str(),
            HttpRequest::translateVersion(_httpVersion).c_str(),
            (int) response->responseCode(),
            (unsigned long long) _originalBodyLength,
            (unsigned long long) responseBodyLength,
            _fullUrl.c_str(),
            totalTime);
          
  // start output
  fillWriteBuffer();
}
コード例 #28
0
 void XmlResultGenerator::generateResultBegin (StringBuffer& output, VariantObject*) const {
   output.appendText("<?xml version=\"1.0\"?>\n<result>");
 }