Code to implement HTTP requests done. Also, rewrote str_to_int64, str_to_double
This commit is contained in:
@@ -297,25 +297,25 @@ bool AnimStep::from_string(const eng::string &config) {
|
||||
if (key == "action") {
|
||||
action_ = val;
|
||||
} else if (key == "id") {
|
||||
int64_t id = util::strtoint(val, -1);
|
||||
int64_t id = util::str_to_int64(val, -1);
|
||||
if (id < 0) return false;
|
||||
id_ = id;
|
||||
} else if (key == "plane") {
|
||||
set_plane(val);
|
||||
} else if (key == "x") {
|
||||
double v = util::strtodouble(val);
|
||||
double v = util::str_to_double(val);
|
||||
if (std::isnan(v)) return false;
|
||||
set_x(v);
|
||||
} else if (key == "y") {
|
||||
double v = util::strtodouble(val);
|
||||
double v = util::str_to_double(val);
|
||||
if (std::isnan(v)) return false;
|
||||
set_y(v);
|
||||
} else if (key == "z") {
|
||||
double v = util::strtodouble(val);
|
||||
double v = util::str_to_double(val);
|
||||
if (std::isnan(v)) return false;
|
||||
set_z(v);
|
||||
} else if (key == "facing") {
|
||||
double v = util::strtodouble(val);
|
||||
double v = util::str_to_double(val);
|
||||
if (std::isnan(v)) return false;
|
||||
set_facing(v);
|
||||
} else if (key == "graphic") {
|
||||
|
||||
2979
luprex/core/cpp/fast-float.hpp
Normal file
2979
luprex/core/cpp/fast-float.hpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -39,8 +39,8 @@ bool Gui::has_action(const eng::string &action) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
eng::string Gui::get_action(int index) {
|
||||
if ((index < 0) || (index >= int(elts_.size()))) {
|
||||
eng::string Gui::get_action(int64_t index) {
|
||||
if ((index < 0) || (index >= int64_t(elts_.size()))) {
|
||||
return "";
|
||||
}
|
||||
return elts_[index].action();
|
||||
|
||||
@@ -40,7 +40,7 @@ public:
|
||||
void clear(int64_t p) { place_ = p; elts_.clear(); }
|
||||
bool has_action(const eng::string &action) const;
|
||||
void menu_item(const eng::string &action, const eng::string &label);
|
||||
eng::string get_action(int index);
|
||||
eng::string get_action(int64_t index);
|
||||
eng::string menu_debug_string() const;
|
||||
|
||||
// Put a pointer to a gui into the lua registry.
|
||||
|
||||
@@ -1,26 +1,414 @@
|
||||
|
||||
#include "http.hpp"
|
||||
#include "wrap-sstream.hpp"
|
||||
#include "wrap-string.hpp"
|
||||
#include "luastack.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
static eng::string url_encode(const eng::string &value) {
|
||||
eng::ostringstream escaped;
|
||||
escaped.fill('0');
|
||||
escaped << hex;
|
||||
|
||||
static void url_encode(const eng::string &value, StreamBuffer *sb) {
|
||||
const char *hexdigits = "0123456789ABCDEF";
|
||||
for (int i = 0; i < int(value.size()); i++) {
|
||||
char c = value[i];
|
||||
|
||||
// Keep alphanumeric and other accepted characters intact
|
||||
// Any other characters are percent-encoded
|
||||
if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
|
||||
escaped << c;
|
||||
if (util::ascii_isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~' || (c == '/')) {
|
||||
sb->write_char(c);
|
||||
} else if (c == ' ') {
|
||||
sb->write_char('+');
|
||||
} else {
|
||||
escaped << std::uppercase;
|
||||
escaped << '%' << std::setw(2) << int((unsigned char) c);
|
||||
escaped << std::nouppercase;
|
||||
sb->write_char('%');
|
||||
sb->write_char(hexdigits[c>>4]);
|
||||
sb->write_char(hexdigits[c&15]);
|
||||
}
|
||||
}
|
||||
|
||||
return escaped.str();
|
||||
}
|
||||
|
||||
class ErrorStringStream : public eng::ostringstream {
|
||||
private:
|
||||
eng::string *target_;
|
||||
public:
|
||||
ErrorStringStream(eng::string *target) : target_(target) {}
|
||||
~ErrorStringStream() {
|
||||
if (target_->empty()) {
|
||||
(*target_) = str();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
HttpRequest::HttpRequest() {
|
||||
verify_certificate_ = true;
|
||||
port_ = 0;
|
||||
}
|
||||
|
||||
void HttpRequest::set_verify_certificate(bool flag) {
|
||||
verify_certificate_ = flag;
|
||||
}
|
||||
|
||||
eng::string HttpRequest::target() const {
|
||||
assert(check().empty());
|
||||
eng::ostringstream oss;
|
||||
oss << (verify_certificate_ ? "cert" : "nocert");
|
||||
oss << ':' << host_ << ':' << port_;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
void HttpRequest::set_method(const eng::string &s) {
|
||||
eng::string method = util::ascii_toupper(s);
|
||||
if ((method != "GET") && (method != "HEAD")) {
|
||||
ErrorStringStream error(&error_);
|
||||
error << "HTTPS method not implemented: " << method;
|
||||
error << ". Currently, only HEAD and GET are implemented.";
|
||||
return;
|
||||
}
|
||||
if ((!method_.empty()) && (method_ != method)) {
|
||||
ErrorStringStream error(&error_);
|
||||
error << "HTTPS method specified twice: " << method_ << " and " << method;
|
||||
return;
|
||||
}
|
||||
method_ = method;
|
||||
}
|
||||
|
||||
void HttpRequest::set_host(const eng::string &s) {
|
||||
eng::string host = util::ascii_tolower(s);
|
||||
if (host.empty()) {
|
||||
ErrorStringStream error(&error_);
|
||||
error << "HTTPS hostname cannot be empty string.";
|
||||
return;
|
||||
}
|
||||
// This is not quite strict, but it's close. I believe
|
||||
// the DNS lookup will fail for invalid hostnames anyway.
|
||||
for (char c : host) {
|
||||
if ((c != '-') && (c != '.') && (!util::ascii_isalnum(c))) {
|
||||
ErrorStringStream error(&error_);
|
||||
error << "HTTPS hostnames can only contain letters, digits, and hyphen: " << host;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!host_.empty()) {
|
||||
ErrorStringStream error(&error_);
|
||||
error << "HTTPS hostname specified twice: " << host_ << " and " << host;
|
||||
return;
|
||||
}
|
||||
host_ = host;
|
||||
}
|
||||
|
||||
void HttpRequest::set_port(int port) {
|
||||
if ((port < 1) || (port > 65535)) {
|
||||
ErrorStringStream error(&error_);
|
||||
error << "HTTP port must be between 1 and 65535: " << port;
|
||||
return;
|
||||
}
|
||||
if (port_ != 0) {
|
||||
ErrorStringStream error(&error_);
|
||||
error << "HTTPS port specified twice: " << port_ << " and " << port;
|
||||
return;
|
||||
}
|
||||
port_ = port;
|
||||
}
|
||||
|
||||
void HttpRequest::set_url(const eng::string &url) {
|
||||
if (util::has_prefix(url, "https://")) {
|
||||
ErrorStringStream error(&error_);
|
||||
error << "set_url(full_url) not implemented yet.";
|
||||
return;
|
||||
} else if (util::has_prefix(url, "/")) {
|
||||
if (!path_.empty()) {
|
||||
ErrorStringStream error(&error_);
|
||||
error << "HTTP path specified twice: " << path_ << " and " << url;
|
||||
return;
|
||||
}
|
||||
path_ = url;
|
||||
} else {
|
||||
ErrorStringStream error(&error_);
|
||||
error << "HTTP url must start with https://, or with /";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequest::set_param(const eng::string &key, const eng::string &val) {
|
||||
if (params_.find(key) != params_.end()) {
|
||||
ErrorStringStream error(&error_);
|
||||
error << "HTTP url parameter specified twice: " << key;
|
||||
return;
|
||||
}
|
||||
params_[key] = val;
|
||||
}
|
||||
|
||||
void HttpRequest::set_verify_certificate(LuaStack &LS, LuaSlot val) {
|
||||
if (!LS.isboolean(val)) {
|
||||
ErrorStringStream error(&error_);
|
||||
error << "HTTP verify_certificate must be a boolean";
|
||||
return;
|
||||
}
|
||||
set_verify_certificate(LS.ckboolean(val));
|
||||
}
|
||||
|
||||
void HttpRequest::set_method(LuaStack &LS, LuaSlot val) {
|
||||
if (!LS.isstring(val)) {
|
||||
ErrorStringStream error(&error_);
|
||||
error << "HTTP method must be a string";
|
||||
return;
|
||||
}
|
||||
set_method(LS.ckstring(val));
|
||||
}
|
||||
|
||||
void HttpRequest::set_host(LuaStack &LS, LuaSlot val) {
|
||||
if (!LS.isstring(val)) {
|
||||
ErrorStringStream error(&error_);
|
||||
error << "HTTP host must be a string";
|
||||
return;
|
||||
}
|
||||
set_host(LS.ckstring(val));
|
||||
}
|
||||
|
||||
void HttpRequest::set_port(LuaStack &LS, LuaSlot val) {
|
||||
if (!LS.isint(val)) {
|
||||
ErrorStringStream error(&error_);
|
||||
error << "HTTP port must be an int";
|
||||
return;
|
||||
}
|
||||
set_port(LS.ckint(val));
|
||||
}
|
||||
|
||||
void HttpRequest::set_url(LuaStack &LS, LuaSlot val) {
|
||||
if (!LS.isstring(val)) {
|
||||
ErrorStringStream error(&error_);
|
||||
error << "HTTP url must be a string";
|
||||
return;
|
||||
}
|
||||
set_url(LS.ckstring(val));
|
||||
}
|
||||
|
||||
void HttpRequest::set_param(LuaStack &LS, LuaSlot key, LuaSlot val) {
|
||||
if (!LS.isstring(key)) {
|
||||
ErrorStringStream error(&error_);
|
||||
error << "HTTP url parameter key must be a string";
|
||||
return;
|
||||
}
|
||||
if (!LS.isstring(val)) {
|
||||
ErrorStringStream error(&error_);
|
||||
error << "HTTP url parameter val must be a string";
|
||||
return;
|
||||
}
|
||||
set_param(LS.ckstring(key), LS.ckstring(val));
|
||||
}
|
||||
|
||||
void HttpRequest::set_params(LuaStack &LS0, LuaSlot tab) {
|
||||
if (!LS0.istable(tab)) {
|
||||
ErrorStringStream error(&error_);
|
||||
error << "HTTP params must be a table";
|
||||
return;
|
||||
}
|
||||
LuaVar key, val;
|
||||
LuaStack LS(LS0.state(), key, val);
|
||||
LS.set(key, LuaNil);
|
||||
while (LS.next(tab, key, val)) {
|
||||
set_param(LS, key, val);
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequest::set_defaults() {
|
||||
if (method_.empty()) {
|
||||
method_ = "GET";
|
||||
}
|
||||
if (port_ == 0) {
|
||||
port_ = 443;
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequest::set_config(LuaStack &LS0, LuaSlot tab) {
|
||||
LuaVar key, val;
|
||||
LuaStack LS(LS0.state(), key, val);
|
||||
LS.set(key, LuaNil);
|
||||
while (LS.next(tab, key, val)) {
|
||||
eng::string kstr;
|
||||
if (LS.isstring(key)) kstr = LS.ckstring(key);
|
||||
if (kstr == "method") {
|
||||
set_method(LS, val);
|
||||
} else if (kstr == "host") {
|
||||
set_host(LS, val);
|
||||
} else if (kstr == "port") {
|
||||
set_port(LS, val);
|
||||
} else if (kstr == "url") {
|
||||
set_url(LS, val);
|
||||
} else if (kstr == "params") {
|
||||
set_params(LS, val);
|
||||
} else if (kstr == "verifycertificate") {
|
||||
set_verify_certificate(LS, val);
|
||||
} else if (kstr == "") {
|
||||
ErrorStringStream error(&error_);
|
||||
error << "HTTP config parameter names must be strings.";
|
||||
} else {
|
||||
ErrorStringStream error(&error_);
|
||||
error << "HTTP unrecognized config parameter: " << kstr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eng::string HttpRequest::check() const {
|
||||
if (!error_.empty()) {
|
||||
return error_;
|
||||
}
|
||||
if (method_.empty()) {
|
||||
return "HTTP method has not been set";
|
||||
}
|
||||
if (host_.empty()) {
|
||||
return "HTTP host has not been set";
|
||||
}
|
||||
if (port_ == 0) {
|
||||
return "HTTP port has not been set";
|
||||
}
|
||||
if (path_.empty()) {
|
||||
return "HTTP url has not been set";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void HttpRequest::send_internal(StreamBuffer *sb, bool debug_string) const {
|
||||
// If there's an error in the request, handle it. In debug string mode,
|
||||
// we just put the error into the output. In production mode, we assert
|
||||
// fail.
|
||||
eng::string error = check();
|
||||
if (debug_string) {
|
||||
if (!error.empty()) {
|
||||
sb->write_bytes(error);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
assert(error.empty());
|
||||
}
|
||||
|
||||
// Choose a linebreak.
|
||||
eng::string linebreak = (debug_string) ? "\n" : "\r\n";
|
||||
|
||||
// Send the command.
|
||||
sb->write_bytes(method_);
|
||||
sb->write_char(' ');
|
||||
url_encode(path_, sb);
|
||||
bool first_param = true;
|
||||
for (const auto &pair : params_) {
|
||||
sb->write_char(first_param ? '?' : '&');
|
||||
url_encode(pair.first, sb);
|
||||
sb->write_char('=');
|
||||
url_encode(pair.second, sb);
|
||||
first_param = false;
|
||||
}
|
||||
sb->write_bytes(" HTTP/1.1");
|
||||
sb->write_bytes(linebreak);
|
||||
|
||||
// Send the host header.
|
||||
sb->write_bytes("Host: ");
|
||||
sb->write_bytes(host_);
|
||||
sb->write_char(':');
|
||||
sb->ostream() << port_;
|
||||
sb->write_bytes(linebreak);
|
||||
|
||||
// The empty accept-encoding header notifies the
|
||||
// server that we don't support gzip, deflate, or
|
||||
// other content compression.
|
||||
sb->write_bytes("Accept-encoding:");
|
||||
sb->write_bytes(linebreak);
|
||||
|
||||
// Add a user-agent header. Not sure why.
|
||||
sb->write_bytes("User-agent: Mozilla 5.0 (luprex)");
|
||||
sb->write_bytes(linebreak);
|
||||
|
||||
// Send the extra linebreak.
|
||||
if (!debug_string) {
|
||||
sb->write_bytes(linebreak);
|
||||
}
|
||||
}
|
||||
|
||||
eng::string HttpRequest::DebugString() {
|
||||
StreamBuffer sb;
|
||||
send_internal(&sb, true);
|
||||
return eng::string(sb.view());
|
||||
}
|
||||
|
||||
HttpResponse::HttpResponse() {
|
||||
response_code_ = 0;
|
||||
response_length_ = 0;
|
||||
mime_type_ = "application/empty";
|
||||
}
|
||||
|
||||
void HttpResponse::fail(int response_code, const eng::string &error) {
|
||||
response_code_ = response_code;
|
||||
error_ = error;
|
||||
response_length_ = 0;
|
||||
mime_type_ = "application/empty";
|
||||
content_ = "";
|
||||
}
|
||||
|
||||
static std::string_view readline(std::string_view &v) {
|
||||
std::string_view result = util::sv_split_one(v, '\n');
|
||||
return util::sv_rtrim(result, '\r');
|
||||
}
|
||||
|
||||
void HttpResponse::parse(const StreamBuffer *sb) {
|
||||
// We're not going to modify the StreamBuffer at all.
|
||||
// Instead, we work entirely on a view.
|
||||
std::string_view view = sb->view();
|
||||
|
||||
// Special case this.
|
||||
if (view.empty()) {
|
||||
fail(500, "HTTP server response completely empty");
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the status line.
|
||||
std::string_view status = readline(view);
|
||||
if (status.empty()) {
|
||||
fail(500, "HTTP status-line not present in response");
|
||||
return;
|
||||
}
|
||||
//std::string_view status_code = util::sv_split_one(status, ' ');
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
LuaDefine(http_request, "reqtab",
|
||||
"|Given an HTTP request in the form of a table, returns the same "
|
||||
"|request as a string, to assist with debugging."
|
||||
"|"
|
||||
"|The table can contain:"
|
||||
"|"
|
||||
"| method (ie, GET, HEAD, POST, etc)"
|
||||
"| host (ie, 'google.com')"
|
||||
"| port (default: 443)"
|
||||
"| url (ie, '/index.html')"
|
||||
"| params (a table of url parameters)"
|
||||
"| verifycertificate (default: true)"
|
||||
"|"
|
||||
"|The url can start with 'https://', or with '/'. If it starts"
|
||||
"|with 'https://', then the URL includes the host and port, which"
|
||||
"|then must not be specified separately."
|
||||
"|"
|
||||
"|Note that plain HTTP is not supported - we only allow HTTPS."
|
||||
"|However, you can talk to a server that has a dummy certificate"
|
||||
"|by specifying verifycertificate=false."
|
||||
"|"
|
||||
"|This module will automatically url encode everything for you."
|
||||
"|Therefore, you shouldn't url encode anything, otherwise,"
|
||||
"|you'll end up double-encoding."
|
||||
"|"
|
||||
"|You cannot include url parameters as part of the url. If you try,"
|
||||
"|then your ?, &, and = characters will get url encoded, which will"
|
||||
"|cause them to not function. To use url parameters, you must"
|
||||
"|use the separate params table."
|
||||
"|") {
|
||||
LuaArg tab;
|
||||
LuaRet str;
|
||||
LuaStack LS(L, tab, str);
|
||||
HttpRequest req;
|
||||
req.set_config(LS, tab);
|
||||
req.set_defaults();
|
||||
eng::string error = req.check();
|
||||
if (!error.empty()) {
|
||||
luaL_error(L, "%s", error.c_str());
|
||||
return 0;
|
||||
}
|
||||
LS.set(str, req.DebugString());
|
||||
return LS.result();
|
||||
}
|
||||
|
||||
|
||||
@@ -16,26 +16,143 @@
|
||||
#include "eng-malloc.hpp"
|
||||
#include "wrap-string.hpp"
|
||||
#include "wrap-vector.hpp"
|
||||
#include "wrap-map.hpp"
|
||||
#include "luastack.hpp"
|
||||
#include "streambuffer.hpp"
|
||||
|
||||
class HttpRequest : public eng::nevernew {
|
||||
public:
|
||||
enum Method { INVALID, GET, POST, HEAD };
|
||||
class HttpRequest : public eng::nevernew {
|
||||
private:
|
||||
// If the request contains an error, the error
|
||||
// message is stored here.
|
||||
eng::string error_;
|
||||
|
||||
public:
|
||||
// If true, verify the server's certificate.
|
||||
// True is the default.
|
||||
bool verify_certificate_;
|
||||
|
||||
// Method: GET, HEAD, POST, etc.
|
||||
eng::string method_;
|
||||
|
||||
// The hostname. This is used both for DNS lookup,
|
||||
// and to create an HTTP Host header.
|
||||
eng::string host_;
|
||||
|
||||
// Port number.
|
||||
int port_;
|
||||
Method method_;
|
||||
eng::string encoded_url_;
|
||||
eng::string content_type_;
|
||||
|
||||
// The path is always UTF-8. This field should not be urlencoded.
|
||||
// Instead, urlencoding is done automatically when the request
|
||||
// is sent. Should not include protocol, host, port, or parameters.
|
||||
eng::string path_;
|
||||
|
||||
// If params is nonempty, then we will add URL parameters
|
||||
// to the URL. The contents of the params field should not be
|
||||
// urlencoded, the urlencoding is done automatically when the
|
||||
// request is sent.
|
||||
eng::map<eng::string, eng::string> params_;
|
||||
|
||||
private:
|
||||
void send_internal(StreamBuffer *target, bool debug_string) const;
|
||||
|
||||
public:
|
||||
// Construct an empty HTTP request.
|
||||
// All of the fields have empty values.
|
||||
HttpRequest();
|
||||
|
||||
// Get fields.
|
||||
const eng::string &error() const { return error_; }
|
||||
bool verify_certificate() const { return verify_certificate_; }
|
||||
const eng::string &method() const { return method_; }
|
||||
const eng::string &host() const { return host_; }
|
||||
int port() const { return port_; }
|
||||
const eng::string &path() const { return path_; }
|
||||
|
||||
// Get the network target, eg, "cert:host:port"
|
||||
eng::string target() const;
|
||||
|
||||
// Populate an HTTP request a piece at a time.
|
||||
// If you pass an invalid value, or if the field is
|
||||
// already set, the routine will generate an error message
|
||||
// and store it in the error field. In that case, the set
|
||||
// will not happen. If there's already an error in the error
|
||||
// field, it will not be overwritten.
|
||||
void set_verify_certificate(bool flag);
|
||||
void set_method(const eng::string &method);
|
||||
void set_host(const eng::string &host);
|
||||
void set_port(int port);
|
||||
void set_url(const eng::string &url);
|
||||
void set_param(const eng::string &key, const eng::string &value);
|
||||
|
||||
// Same as above, but using Lua values.
|
||||
void set_verify_certificate(LuaStack &LS, LuaSlot val);
|
||||
void set_method(LuaStack &LS, LuaSlot val);
|
||||
void set_host(LuaStack &LS, LuaSlot val);
|
||||
void set_port(LuaStack &LS, LuaSlot val);
|
||||
void set_url(LuaStack &LS, LuaSlot val);
|
||||
void set_param(LuaStack &LS, LuaSlot key, LuaSlot val);
|
||||
void set_params(LuaStack &LS, LuaSlot tab);
|
||||
|
||||
// Set default values for any fields that should have
|
||||
// defaults. This must be done after setting regular
|
||||
// values.
|
||||
void set_defaults();
|
||||
|
||||
// Verify that the request is error free and that
|
||||
// defaults have been set.
|
||||
eng::string check() const;
|
||||
|
||||
// Populate an HTTP request from a Lua table.
|
||||
void set_config(LuaStack &LS0, LuaSlot tab);
|
||||
|
||||
// Put the request into the stream, assuming HTTP/1.1
|
||||
void send(StreamBuffer *target) const { send_internal(target, false); }
|
||||
|
||||
// Get the request as a debug string.
|
||||
eng::string DebugString();
|
||||
};
|
||||
|
||||
class HttpReply : public eng::nevernew {
|
||||
public:
|
||||
int error_code_;
|
||||
eng::string error_message_;
|
||||
class HttpResponse {
|
||||
private:
|
||||
// The HTTP response code.
|
||||
int response_code_;
|
||||
|
||||
// If the HTTP response contains an error, the
|
||||
// error message is stored here. If the HTTP response
|
||||
// is a success such as "200 OK" or "201 Created", this
|
||||
// is the empty string, not "OK" or "Created".
|
||||
eng::string error_;
|
||||
|
||||
// The length in bytes of the entire response.
|
||||
// May be zero, which means that the response
|
||||
// was so garbled that we couldn't determine the length.
|
||||
bool response_length_;
|
||||
|
||||
// MIME type of the content.
|
||||
eng::string mime_type_;
|
||||
|
||||
// The content as string.
|
||||
eng::string content_;
|
||||
|
||||
private:
|
||||
|
||||
public:
|
||||
// Construct a blank response.
|
||||
HttpResponse();
|
||||
|
||||
// Store an error message. This is used when the client detects an error,
|
||||
// such as a DNS lookup fail, a connection failed, an SSL negotiation
|
||||
// failed, or the like. Clears the content, leaving only the error
|
||||
// and response code.
|
||||
void fail(int response_code, const eng::string &error);
|
||||
|
||||
// Parse the HTTP response. Note that the response is not
|
||||
// removed from the StreamBuffer, which is always unmodified.
|
||||
// If you want to remove the response from the StreamBuffer, see
|
||||
// response_length.
|
||||
void parse(const StreamBuffer *sb);
|
||||
|
||||
// Convert the HTTP response to a lua table.
|
||||
void store(LuaStack &LS, LuaSlot tab);
|
||||
};
|
||||
|
||||
#endif // HTTP_HPP
|
||||
|
||||
@@ -125,13 +125,13 @@ public:
|
||||
|
||||
void do_menu_command(const StringVec &cmd) {
|
||||
world_to_asynchronous();
|
||||
int64_t place = util::strtoint(cmd[1], actor_id_);
|
||||
int64_t place = util::str_to_int64(cmd[1], actor_id_);
|
||||
world_->update_gui(actor_id_, place, &gui_);
|
||||
stdostream() << gui_.menu_debug_string();
|
||||
}
|
||||
|
||||
void do_choose_command(const StringVec &cmd) {
|
||||
eng::string action = gui_.get_action(util::strtoint(cmd[1], -1));
|
||||
eng::string action = gui_.get_action(util::str_to_int64(cmd[1]));
|
||||
if (action == "") {
|
||||
stdostream() << "Invalid menu item #" << std::endl;
|
||||
return;
|
||||
|
||||
@@ -53,7 +53,7 @@ void LuaConsole::simplify(const StringVec &words) {
|
||||
words_ = words;
|
||||
if (words.size() == 0) {
|
||||
return;
|
||||
} else if (util::validinteger(words[0])) {
|
||||
} else if (util::valid_int64(words[0])) {
|
||||
if (words.size() == 1) {
|
||||
words_.clear();
|
||||
words_.push_back("choose");
|
||||
@@ -62,7 +62,7 @@ void LuaConsole::simplify(const StringVec &words) {
|
||||
synerr("/choose command takes no arguments");
|
||||
}
|
||||
} else if (words[0] == "choose") {
|
||||
if ((words.size() == 2)&&(util::validinteger(words[1]))) {
|
||||
if ((words.size() == 2)&&(util::valid_int64(words[1]))) {
|
||||
// OK
|
||||
} else {
|
||||
synerr("/choose [menu-line-number]");
|
||||
@@ -74,7 +74,7 @@ void LuaConsole::simplify(const StringVec &words) {
|
||||
} else if (words[0] == "menu") {
|
||||
if (words.size() == 1) {
|
||||
words_.push_back("-");
|
||||
} else if ((words.size() == 2)&&(util::validinteger(words[1]))) {
|
||||
} else if ((words.size() == 2)&&(util::valid_int64(words[1]))) {
|
||||
// OK
|
||||
} else {
|
||||
synerr("/menu [optional-tangible-id]");
|
||||
|
||||
@@ -52,6 +52,22 @@ lua_State *LuaStack::newstate (lua_Alloc allocf) {
|
||||
return L;
|
||||
}
|
||||
|
||||
bool LuaStack::isinteger(LuaSlot s) const {
|
||||
if (lua_type(L_, s) == LUA_TNUMBER) {
|
||||
lua_Number result = lua_tonumber(L_, s);
|
||||
if (lua_Integer(result) == result) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LuaStack::isint(LuaSlot s) const {
|
||||
if (lua_type(L_, s) == LUA_TNUMBER) {
|
||||
lua_Number result = lua_tonumber(L_, s);
|
||||
if (int(result) == result) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LuaStack::ckboolean(LuaSlot s) const {
|
||||
luaL_checktype(L_, s, LUA_TBOOLEAN);
|
||||
return lua_toboolean(L_, s) ? true:false;
|
||||
@@ -59,12 +75,24 @@ bool LuaStack::ckboolean(LuaSlot s) const {
|
||||
|
||||
lua_Integer LuaStack::ckinteger(LuaSlot s) const {
|
||||
luaL_checktype(L_, s, LUA_TNUMBER);
|
||||
return lua_tointeger(L_, s);
|
||||
lua_Number result = lua_tonumber(L_, s);
|
||||
lua_Integer iresult(result);
|
||||
if (iresult != result) {
|
||||
luaL_error(L_, "not a valid integer");
|
||||
return 0;
|
||||
}
|
||||
return iresult;
|
||||
}
|
||||
|
||||
int LuaStack::ckint(LuaSlot s) const {
|
||||
luaL_checktype(L_, s, LUA_TNUMBER);
|
||||
return (int)lua_tointeger(L_, s);
|
||||
lua_Number result = lua_tonumber(L_, s);
|
||||
int iresult(result);
|
||||
if (iresult != result) {
|
||||
luaL_error(L_, "not a valid int");
|
||||
return 0;
|
||||
}
|
||||
return iresult;
|
||||
}
|
||||
|
||||
lua_Number LuaStack::cknumber(LuaSlot s) const {
|
||||
|
||||
@@ -348,6 +348,8 @@ public:
|
||||
bool istable(LuaSlot s) const { return lua_type(L_, s) == LUA_TTABLE; }
|
||||
bool isstring(LuaSlot s) const { return lua_type(L_, s) == LUA_TSTRING; }
|
||||
bool isnumber(LuaSlot s) const { return lua_type(L_, s) == LUA_TNUMBER; }
|
||||
bool isinteger(LuaSlot s) const;
|
||||
bool isint(LuaSlot s) const;
|
||||
bool isthread(LuaSlot s) const { return lua_type(L_, s) == LUA_TTHREAD; }
|
||||
bool isfunction(LuaSlot s) const { return lua_type(L_, s) == LUA_TFUNCTION; }
|
||||
bool isboolean(LuaSlot s) const { return lua_type(L_, s) == LUA_TBOOLEAN; }
|
||||
|
||||
@@ -263,6 +263,12 @@ void StreamBuffer::write_uint64(uint64_t vv) {
|
||||
write_cursor_ += 8;
|
||||
}
|
||||
|
||||
void StreamBuffer::write_char(char c) {
|
||||
make_space(1);
|
||||
write_cursor_[0] = c;
|
||||
write_cursor_ += 1;
|
||||
}
|
||||
|
||||
void StreamBuffer::write_float(float f) {
|
||||
make_space(4);
|
||||
memcpy(write_cursor_, &f, 4);
|
||||
@@ -307,6 +313,13 @@ int64_t StreamBuffer::read_int64() {
|
||||
return v;
|
||||
}
|
||||
|
||||
char StreamBuffer::read_char() {
|
||||
check_available(1);
|
||||
char c = read_cursor_[0];
|
||||
read_cursor_ += 1;
|
||||
return c;
|
||||
}
|
||||
|
||||
float StreamBuffer::read_float() {
|
||||
check_available(4);
|
||||
float f;
|
||||
|
||||
@@ -310,6 +310,7 @@ public:
|
||||
void write_uint16(uint64_t v);
|
||||
void write_uint32(uint64_t v);
|
||||
void write_uint64(uint64_t v);
|
||||
void write_char(char c);
|
||||
void write_float(float f);
|
||||
void write_double(double d);
|
||||
|
||||
@@ -325,6 +326,7 @@ public:
|
||||
uint16_t read_uint16() { return read_int16(); }
|
||||
uint32_t read_uint32() { return read_int32(); }
|
||||
uint64_t read_uint64() { return read_int64(); }
|
||||
char read_char();
|
||||
float read_float();
|
||||
double read_double();
|
||||
|
||||
|
||||
@@ -47,13 +47,13 @@ private:
|
||||
}
|
||||
|
||||
void do_menu_command(const StringVec &cmd) {
|
||||
int64_t place = util::strtoint(cmd[1], actor_id_);
|
||||
int64_t place = util::str_to_int64(cmd[1], actor_id_);
|
||||
world_->update_gui(actor_id_, place, &gui_);
|
||||
stdostream() << gui_.menu_debug_string();
|
||||
}
|
||||
|
||||
void do_choose_command(const StringVec &cmd) {
|
||||
eng::string action = gui_.get_action(util::strtoint(cmd[1], -1));
|
||||
eng::string action = gui_.get_action(util::str_to_int64(cmd[1]));
|
||||
if (action == "") {
|
||||
stdostream() << "Invalid menu item #" << std::endl;
|
||||
return;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "wrap-string.hpp"
|
||||
#include "wrap-vector.hpp"
|
||||
#include "fast-float.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@@ -11,6 +12,8 @@
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <cmath>
|
||||
#include <charconv>
|
||||
|
||||
|
||||
#ifdef WIN32
|
||||
#endif
|
||||
@@ -22,12 +25,24 @@
|
||||
|
||||
namespace util {
|
||||
|
||||
static bool ascii_isalpha(char c) {
|
||||
return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'));
|
||||
eng::string ascii_tolower(const eng::string &s) {
|
||||
eng::string mod = s;
|
||||
for (int i = 0; i < int(mod.size()); i++) {
|
||||
if (ascii_isupper(mod[i])) {
|
||||
mod[i] += 'a' - 'A';
|
||||
}
|
||||
}
|
||||
return mod;
|
||||
}
|
||||
|
||||
static bool ascii_isdigit(char c) {
|
||||
return ((c >= '0') && (c <= '9'));
|
||||
eng::string ascii_toupper(const eng::string &s) {
|
||||
eng::string mod = s;
|
||||
for (int i = 0; i < int(mod.size()); i++) {
|
||||
if (ascii_islower(mod[i])) {
|
||||
mod[i] += 'A' - 'a';
|
||||
}
|
||||
}
|
||||
return mod;
|
||||
}
|
||||
|
||||
bool is_identifier(const eng::string &str) {
|
||||
@@ -271,33 +286,40 @@ bool has_suffix(const eng::string &s, const eng::string &suffix) {
|
||||
}
|
||||
}
|
||||
|
||||
bool validinteger(const eng::string &value) {
|
||||
char *endptr;
|
||||
if (value.size() == 0) return false;
|
||||
strtoll(value.c_str(), &endptr, 10);
|
||||
return (endptr == value.c_str() + value.size());
|
||||
bool valid_int64(std::string_view value) {
|
||||
int64_t result;
|
||||
const char *last = value.data() + value.size();
|
||||
auto r = std::from_chars(value.data(), last, result, 10);
|
||||
if (r.ec != std::errc()) return false;
|
||||
if (r.ptr != last) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
int64_t strtoint(const eng::string &value, int64_t errval) {
|
||||
char *endptr;
|
||||
if (value.size() == 0) return errval;
|
||||
int64_t result = strtoll(value.c_str(), &endptr, 10);
|
||||
if (endptr == value.c_str() + value.size()) {
|
||||
return result;
|
||||
} else {
|
||||
return errval;
|
||||
}
|
||||
int64_t str_to_int64(std::string_view value, int64_t errval) {
|
||||
int64_t result;
|
||||
const char *last = value.data() + value.size();
|
||||
auto r = std::from_chars(value.data(), last, result, 10);
|
||||
if (r.ec != std::errc()) return errval;
|
||||
if (r.ptr != last) return errval;
|
||||
return result;
|
||||
}
|
||||
|
||||
double strtodouble(const eng::string &value) {
|
||||
char *endptr;
|
||||
if (value.size() == 0) return std::nan("");
|
||||
double result = strtod(value.c_str(), &endptr);
|
||||
if (endptr == value.c_str() + value.size()) {
|
||||
return result;
|
||||
} else {
|
||||
return std::nan("");
|
||||
}
|
||||
bool valid_double(std::string_view value) {
|
||||
double result;
|
||||
const char *last = value.data() + value.size();
|
||||
auto r = fast_float::from_chars(value.data(), last, result);
|
||||
if (r.ec != std::errc()) return false;
|
||||
if (r.ptr != last) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
double str_to_double(std::string_view value, double errval) {
|
||||
double result;
|
||||
const char *last = value.data() + value.size();
|
||||
auto r = fast_float::from_chars(value.data(), last, result);
|
||||
if (r.ec != std::errc()) return errval;
|
||||
if (r.ptr != last) return errval;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string_view sv_ltrim(std::string_view v) {
|
||||
@@ -330,6 +352,30 @@ std::string_view sv_trim(std::string_view v) {
|
||||
return std::string_view(b, e-b);
|
||||
}
|
||||
|
||||
std::string_view sv_ltrim(std::string_view v, char c) {
|
||||
while ((!v.empty()) && (v.front() == c)) {
|
||||
v.remove_prefix(1);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
std::string_view sv_rtrim(std::string_view v, char c) {
|
||||
while ((!v.empty()) && (v.back() == c)) {
|
||||
v.remove_suffix(1);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
std::string_view sv_trim(std::string_view v, char c) {
|
||||
while ((!v.empty()) && (v.front() == c)) {
|
||||
v.remove_prefix(1);
|
||||
}
|
||||
while ((!v.empty()) && (v.back() == c)) {
|
||||
v.remove_suffix(1);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
eng::string ltrim(std::string_view v) {
|
||||
return eng::string(sv_ltrim(v));
|
||||
}
|
||||
@@ -342,24 +388,34 @@ eng::string trim(std::string_view v) {
|
||||
return eng::string(sv_trim(v));
|
||||
}
|
||||
|
||||
std::string_view sv_split_one(std::string_view &v, char sep) {
|
||||
size_t pos = v.find(sep);
|
||||
|
||||
// If there's no separator in the buffer, return a null view.
|
||||
if (pos == std::string_view::npos) {
|
||||
return std::string_view();
|
||||
}
|
||||
|
||||
// Split into stuff before the separator, and stuff after.
|
||||
std::string_view result = v.substr(0, pos);
|
||||
v = v.substr(pos + 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string_view sv_read_line(std::string_view &source) {
|
||||
size_t pos = source.find('\n');
|
||||
std::string_view result;
|
||||
if (pos == std::string_view::npos) {
|
||||
result = source;
|
||||
source = "";
|
||||
source = std::string_view();
|
||||
} else {
|
||||
result = source.substr(0, pos);
|
||||
source = source.substr(pos + 1);
|
||||
}
|
||||
int fsize = result.size();
|
||||
if ((fsize >= 1) && (result[fsize - 1] == '\r')) {
|
||||
result.remove_suffix(1);
|
||||
}
|
||||
result = sv_rtrim(result, '\r');
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
double distance_squared(double x1, double y1, double x2, double y2) {
|
||||
double dx = x1 - x2;
|
||||
double dy = y1 - y2;
|
||||
@@ -456,18 +512,14 @@ LuaDefine(unittests_util, "", "some unit tests") {
|
||||
LuaAssert(L, util::toupper("fooBar") == "FOOBAR");
|
||||
LuaAssert(L, util::tolower("fooBar") == "foobar");
|
||||
|
||||
// test validinteger, strtoint, strtodouble
|
||||
LuaAssert(L, util::validinteger("123") == true);
|
||||
LuaAssert(L, util::validinteger("123.4") == false);
|
||||
LuaAssert(L, util::validinteger("12ab") == false);
|
||||
LuaAssert(L, util::validinteger("") == false);
|
||||
LuaAssert(L, util::strtoint("123", -5) == 123);
|
||||
LuaAssert(L, util::strtoint("123.4", -5) == -5);
|
||||
LuaAssert(L, util::strtoint("12ab", -5) == -5);
|
||||
LuaAssert(L, util::strtoint("", -5) == -5);
|
||||
LuaAssert(L, util::strtodouble("123.5") == 123.5);
|
||||
LuaAssert(L, std::isnan(util::strtodouble("12ab")));
|
||||
LuaAssert(L, std::isnan(util::strtodouble("")));
|
||||
// test str_to_int64, str_to_double
|
||||
LuaAssert(L, util::str_to_int64("123") == 123);
|
||||
LuaAssert(L, util::str_to_int64("123.4") == INT64_MIN);
|
||||
LuaAssert(L, util::str_to_int64("12ab") == INT64_MIN);
|
||||
LuaAssert(L, util::str_to_int64("") == INT64_MIN);
|
||||
LuaAssert(L, util::str_to_double("123.5") == 123.5);
|
||||
LuaAssert(L, std::isnan(util::str_to_double("12ab")));
|
||||
LuaAssert(L, std::isnan(util::str_to_double("")));
|
||||
|
||||
// Test trim, ltrim, rtrim
|
||||
LuaAssert(L, util::ltrim(" foo ") == "foo ");
|
||||
|
||||
@@ -38,6 +38,19 @@ using LuaSourceVec = eng::vector<StringPair>;
|
||||
using LuaSourcePtr = std::unique_ptr<LuaSourceVec>;
|
||||
using HashValue = std::pair<uint64_t, uint64_t>;
|
||||
using IdVector = eng::vector<int64_t>;
|
||||
using OptionalInt64 = std::optional<int64_t>;
|
||||
|
||||
// This value is sometimes used to represent 'bad integer'
|
||||
const int64_t BADINT64 = std::numeric_limits<int64_t>::min();
|
||||
|
||||
// Test character class ignoring 'current locale'.
|
||||
inline bool ascii_isupper(char c) { return (c >= 'A') && (c <= 'Z'); }
|
||||
inline bool ascii_islower(char c) { return (c >= 'a') && (c <= 'z'); }
|
||||
inline bool ascii_isdigit(char c) { return (c >= '0') && (c <= '9'); }
|
||||
inline bool ascii_isalpha(char c) { return ascii_isupper(c) || ascii_islower(c); }
|
||||
inline bool ascii_isalnum(char c) { return ascii_isalpha(c) || ascii_isdigit(c); }
|
||||
eng::string ascii_tolower(const eng::string &c);
|
||||
eng::string ascii_toupper(const eng::string &c);
|
||||
|
||||
// Return seconds elapsed, for profiling purposes.
|
||||
double profiling_clock();
|
||||
@@ -99,28 +112,39 @@ eng::string toupper(eng::string input);
|
||||
bool has_prefix(const eng::string &s, const eng::string &prefix);
|
||||
bool has_suffix(const eng::string &s, const eng::string &suffix);
|
||||
|
||||
// Return true if the string can be parsed as an integer.
|
||||
bool validinteger(const eng::string &value);
|
||||
// Check if numbers can be parsed as int64/double
|
||||
bool valid_int64(std::string_view v);
|
||||
bool valid_double(std::string_view v);
|
||||
|
||||
// String to integer. Returns errval if the number is not parseable.
|
||||
int64_t strtoint(const eng::string &value, int64_t errval);
|
||||
|
||||
// String to double. Returns NAN if the number is not parseable.
|
||||
double strtodouble(const eng::string &value);
|
||||
// Parse numbers as int64/double. Returns errval on failure.
|
||||
int64_t str_to_int64(std::string_view v, int64_t errval = INT64_MIN);
|
||||
double str_to_double(std::string_view v, double errval = std::numeric_limits<double>::quiet_NaN());
|
||||
|
||||
// Trim a string_view
|
||||
std::string_view sv_ltrim(std::string_view v);
|
||||
std::string_view sv_rtrim(std::string_view v);
|
||||
std::string_view sv_trim(std::string_view v);
|
||||
|
||||
std::string_view sv_ltrim(std::string_view v, char c);
|
||||
std::string_view sv_rtrim(std::string_view v, char c);
|
||||
std::string_view sv_trim(std::string_view v, char c);
|
||||
|
||||
// sv_is_null is different from checking for empty.
|
||||
inline bool sv_is_null(const std::string_view &v) { return v.data() == nullptr; }
|
||||
|
||||
// Split a string view into stuff before and after separator.
|
||||
// If the separator doesn't occur, returns a null string view
|
||||
// and doesn't modify the source.
|
||||
std::string_view sv_split_one(std::string_view &source, char sep);
|
||||
|
||||
// Read a line from a string_view.
|
||||
std::string_view sv_read_line(std::string_view &source);
|
||||
|
||||
// Trim strings: left end, right end, both ends.
|
||||
eng::string ltrim(std::string_view s);
|
||||
eng::string rtrim(std::string_view s);
|
||||
eng::string trim(std::string_view s);
|
||||
|
||||
// Read a line from a string_view
|
||||
std::string_view sv_read_line(std::string_view &source);
|
||||
|
||||
// Calculate distance between two points
|
||||
double distance_squared(double x1, double y1, double x2, double y2);
|
||||
|
||||
|
||||
@@ -434,8 +434,8 @@ void World::invoke_flush_prints(int64_t actor_id, int64_t place_id, const eng::s
|
||||
if (actor_id != place_id) {
|
||||
return;
|
||||
}
|
||||
int line = util::strtoint(action, -1);
|
||||
if (line < 0) {
|
||||
int64_t line = util::str_to_int64(action, -1);
|
||||
if ((line < 0)||(line > INT_MAX)) {
|
||||
return;
|
||||
}
|
||||
Tangible *tactor = tangible_get(actor_id);
|
||||
|
||||
Reference in New Issue
Block a user