Fix representation of null values and escape control characters in JSON and HTTP query strings.

git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@1094210 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
jsdelfino 2011-04-17 22:14:18 +00:00
parent ff2490e3b4
commit a0f6883021
12 changed files with 215 additions and 68 deletions

View file

@ -36,6 +36,23 @@
namespace tuscany
{
#ifdef WANT_MAINTAINER_MODE
/**
* Force a core dump on assertion violation.
*/
bool assertOrFail(const bool expr) {
if (!expr)
*(char*)NULL = '\0';
return true;
}
#else
#define assertOrFail(expr)
#endif
/**
* Pointer to a value.
*/
@ -117,7 +134,7 @@ private:
apr_pool_t* mkpool() {
apr_pool_t* p = NULL;
apr_pool_create(&p, NULL);
assert(p != NULL);
assertOrFail(p != NULL);
return p;
}
@ -164,7 +181,7 @@ apr_pool_t* gc_current_pool() {
// Create a parent pool for the current thread
apr_pool_create(&apr_pool, NULL);
assert(apr_pool != NULL);
assertOrFail(apr_pool != NULL);
gc_pool_stack = apr_pool;
return apr_pool;
}
@ -196,7 +213,7 @@ public:
gc_scoped_pool() : gc_pool(NULL), prev(gc_current_pool()), owner(true) {
apr_pool_create(&apr_pool, NULL);
assert(apr_pool != NULL);
assertOrFail(apr_pool != NULL);
gc_push_pool(apr_pool);
}
@ -230,7 +247,7 @@ template<typename T> apr_status_t gc_pool_cleanup(void* v) {
template<typename T> T* gc_new(apr_pool_t* p) {
void* gc_new_ptr = apr_palloc(p, sizeof(T));
assert(gc_new_ptr != NULL);
assertOrFail(gc_new_ptr != NULL);
apr_pool_cleanup_register(p, gc_new_ptr, gc_pool_cleanup<T>, apr_pool_cleanup_null) ;
return static_cast<T*>(gc_new_ptr);
}
@ -254,7 +271,7 @@ template<typename T> apr_status_t gc_pool_acleanup(void* v) {
template<typename T> T* gc_anew(apr_pool_t* p, size_t n) {
size_t* gc_anew_ptr = static_cast<size_t*>(apr_palloc(p, sizeof(size_t) + sizeof(T[n])));
assert(gc_anew_ptr != NULL);
assertOrFail(gc_anew_ptr != NULL);
*gc_anew_ptr = n;
apr_pool_cleanup_register(p, gc_anew_ptr, gc_pool_acleanup<T>, apr_pool_cleanup_null) ;
return (T*)(gc_anew_ptr + 1);
@ -273,7 +290,7 @@ template<typename T> T* gc_anew(size_t n) {
*/
char* gc_cnew(apr_pool_t* p, size_t n) {
char* gc_cnew_ptr = static_cast<char*>(apr_palloc(p, n));
assert(gc_cnew_ptr != NULL);
assertOrFail(gc_cnew_ptr != NULL);
return gc_cnew_ptr;
}

View file

@ -301,7 +301,7 @@ template<typename T> const list<T> mklist(const T& a, const T& b, const T& c, co
*/
template<typename T> const T car(const list<T>& p) {
// Abort if trying to access the car of a nil list
assert(!isNil(p.cdr));
assertOrFail(!isNil(p.cdr));
return p.car;
}

View file

@ -95,10 +95,10 @@ class value {
public:
enum ValueType {
Undefined, Symbol, String, List, Number, Bool, Lambda, Ptr
Nil, Symbol, String, List, Number, Bool, Lambda, Ptr
};
value() : type(value::Undefined) {
value() : type(value::Nil) {
debug_inc(countValues);
debug_inc(countEValues);
debug_watchValue();
@ -239,8 +239,8 @@ public:
if(this == &v)
return true;
switch(type) {
case value::Undefined:
return v.type == value::Undefined;
case value::Nil:
return v.type == value::Nil;
case value::List:
return v.type == value::List && lst()() == v.lst()();
case value::Lambda:
@ -447,6 +447,18 @@ const string watchValue(const value& v) {
#endif
/**
* Write an escaped string value to a stream.
*/
ostream& escvwrite(const char* s, ostream& out) {
if (*s == '\0')
return out;
if (*s == '\\' || *s == '"')
out << '\\';
out << *s;
return escvwrite(s + 1, out);
}
/**
* Write a value to a stream.
*/
@ -459,7 +471,9 @@ ostream& operator<<(ostream& out, const value& v) {
case value::Symbol:
return out << v.str()();
case value::String:
return out << '\"' << v.str()() << '\"';
out << '\"';
escvwrite(c_str(v.str()()), out);
return out << '\"';
case value::Number:
return out << v.num()();
case value::Bool:
@ -474,7 +488,7 @@ ostream& operator<<(ostream& out, const value& v) {
return out << "gc_ptr::" << p;
}
default:
return out << "undefined";
return out << "nil";
}
}
@ -489,7 +503,7 @@ const value::ValueType type(const value& v) {
* Returns true if a value is nil.
*/
const bool isNil(const value& v) {
return type(v) == value::Undefined;
return type(v) == value::Nil;
}
/**

View file

@ -30,6 +30,8 @@
#include <curl/curl.h>
#include <curl/types.h>
#include <curl/easy.h>
#include <apr.h>
#include <apr_lib.h>
#include <apr_network_io.h>
#include <apr_portable.h>
#include <apr_poll.h>
@ -93,6 +95,7 @@ private:
friend CURL* handle(const CURLSession& cs);
friend apr_socket_t* sock(const CURLSession& cs);
friend const failable<CURL*> setup(const string& url, const CURLSession& cs);
friend const failable<bool> connect(const string& url, CURLSession& cs);
friend const failable<bool> send(const char* c, const size_t l, const CURLSession& cs);
friend const failable<size_t> recv(char* c, const size_t l, const CURLSession& cs);
@ -142,6 +145,45 @@ const string apreason(apr_status_t rc) {
return apr_strerror(rc, buf, sizeof(buf));
}
/**
* Escape a URI or a query argument.
*/
const char escape_c2x[] = "0123456789ABCDEF";
const string escape(const string& unesc, const char* reserv) {
char* copy = (char*)apr_palloc(gc_current_pool(), 3 * length(unesc) + 3);
const unsigned char* s = (const unsigned char *)c_str(unesc);
unsigned char* d = (unsigned char*)copy;
unsigned c;
while ((c = *s)) {
if (!apr_isalnum(c) && !strchr(reserv, c)) {
*d++ = '%';
*d++ = escape_c2x[c >> 4];
*d++ = escape_c2x[c & 0xf];
}
else {
*d++ = (unsigned char)c;
}
++s;
}
*d = '\0';
return copy;
}
const string escapeURI(const string& uri) {
debug(uri, "http::escapeURI::uri");
const string e = escape(uri, "?$-_.+!*'(),:@&=/~%");
debug(e, "http::escapeURI::result");
return e;
}
const string escapeArg(const string& arg) {
debug(arg, "http::escapeArg::arg");
const string e = escape(arg, "-_.~");
debug(e, "http::escapeArg::result");
return e;
}
/**
* Setup a CURL session
*/
@ -177,7 +219,7 @@ const failable<CURL*> setup(const string& url, const CURLSession& cs) {
}
// Set target URL
curl_easy_setopt(ch, CURLOPT_URL, c_str(url));
curl_easy_setopt(ch, CURLOPT_URL, c_str(escapeURI(url)));
return ch;
}
@ -430,6 +472,22 @@ const failable<value> get(const string& url, const CURLSession& cs) {
debug(val, "http::get::result");
return val;
}
if (contains(ct, "application/x-javascript")) {
// Read a JSON document enclosed in a javascript function call
// Extract the JSON out of the enclosing parenthesis
ostringstream os;
write(ls, os);
const string s = str(os);
const size_t fp = find(s, '(');
const size_t lp = find_last(s, ')');
const list<string> jls = mklist<string>(substr(s, fp + 1, lp - (fp + 1)));
debug(jls, "http::get::javascript::content");
js::JSContext cx;
const value val(json::jsonValues(content(json::readJSON(jls, cx))));
debug(val, "http::get::result");
return val;
}
if (contains(ct, "text/xml") || contains(ct, "application/xml") || isXML(ls)) {
// Read an XML document
const value val(elementsToValues(readXML(ls)));
@ -620,21 +678,6 @@ const failable<size_t> recv(char* c, const size_t l, const CURLSession& cs) {
return recv(c, l, cs);
}
/**
* Filter path segment in a list of arguments.
*/
const bool filterPath(const value& arg) {
return isString(arg);
}
/**
* Filter query string arguments in a list of arguments.
*/
const bool filterQuery(const value& arg) {
return isList(arg);
}
/**
* Converts a list of key value pairs to a query string.
*/
@ -653,6 +696,28 @@ const string queryString(const list<list<value> > args) {
return str(queryString(args, os));
}
/**
* Filter path segment in a list of arguments.
*/
const bool filterPath(const value& arg) {
return isString(arg);
}
/**
* Filter query string arguments in a list of arguments.
*/
const bool filterQuery(const value& arg) {
return isList(arg);
}
/**
* Escape a query string argument.
*/
const value escapeQuery(const value& arg) {
return arg;
//return mklist<value>(car<value>(arg), escapeArg(cadr<value>(arg)));
}
/**
* HTTP client proxy function.
*/
@ -665,8 +730,7 @@ struct proxy {
if (fun == "get") {
const list<value> lp = filter<value>(filterPath, cadr(args));
debug(lp, "http::queryString::arg");
const list<value> lq = filter<value>(filterQuery, cadr(args));
debug(lq, "http::get::query");
const list<value> lq = map<value, value>(escapeQuery, filter<value>(filterQuery, cadr(args)));
const value p = path(lp);
const value q = queryString(lq);
const failable<value> val = get(uri + p + (q != ""? string("?") + q : string("")), cs);

View file

@ -82,9 +82,9 @@ AddCharset utf-8 .js .css
# Configure cache control
SetEnvIf Request_URI "^/app.html$" must-revalidate
Header onsuccess merge Cache-Control public
Header onsuccess merge Cache-Control max-age=31536000
Header onsuccess merge Cache-Control must-revalidate env=must-revalidate
Header onsuccess set Cache-Control "max-age=86400" env=!must-revalidate
Header set Cache-Control "must-revalidate, max-age=0" env=must-revalidate
Header set Expires "Tue, 01 Jan 1980 00:00:00 GMT" env=must-revalidate
# Set default document root
DocumentRoot $htdocs

View file

@ -251,6 +251,20 @@ const string unescape(const string& uri) {
return b;
}
/**
* Unescape a list of key of value pairs representing query args.
*/
const list<value> unescapeArg(const list<value> a) {
return mklist<value>(car(a), unescape(cadr(a)));
}
const list<list<value> > unescapeArgs(const list<list<value> > args) {
debug(args, "httpd::unescape::args");
const list<list<value> > uargs = map<list<value>, list<value>>(unescapeArg, args);
debug(uargs, "httpd::unescape::result");
return uargs;
}
/**
* Returns a list of key value pairs from the args in a query string.
*/
@ -328,11 +342,11 @@ const failable<int> writeResult(const failable<list<string> >& ls, const string&
const string ob(str(os));
// Make sure browsers come back and check for updated dynamic content
apr_table_setn(r->headers_out, "Expires", "Tue, 01 Jan 1980 00:00:00 GMT");
apr_table_setn(r->headers_out, "Cache-Control", "must-revalidate");
// The actual header setup is configured in httpd-conf, based on the must-revalidate env variable
apr_table_set(r->subprocess_env, apr_pstrdup(r->pool, "must-revalidate"), apr_pstrdup(r->pool, "true"));
// Compute and return an Etag for the returned content
const string etag(ap_md5(r->pool, (const unsigned char*)c_str(ob)));
const string etag(ap_md5_binary(r->pool, (const unsigned char*)c_str(ob), (int)length(ob)));
// Check for an If-None-Match header and just return a 304 not-modified status
// if the Etag matches the Etag presented by the client, to save bandwith

View file

@ -177,9 +177,10 @@ const list<value> jsPropertiesToValues(const list<value>& propertiesSoFar, JSObj
jsval idv;
JS_IdToValue(cx, id, &idv);
if(JSVAL_IS_STRING(idv)) {
if (isNil(val) && !isList(val))
return jsPropertiesToValues(propertiesSoFar, o, i, cx);
const string name = JS_GetStringBytes(JSVAL_TO_STRING(idv));
if (isNil(val) && !isList(val))
return jsPropertiesToValues(cons<value> (mklist<value> (element, c_str(name), val), propertiesSoFar), o, i, cx);
//return jsPropertiesToValues(propertiesSoFar, o, i, cx);
if (substr(name, 0, 1) == atsign)
return jsPropertiesToValues(cons<value>(mklist<value>(attribute, c_str(substr(name, 1)), val), propertiesSoFar), o, i, cx);
if (isList(val) && !isJSArray(val))
@ -257,6 +258,9 @@ const jsval valueToJSVal(const value& val, const js::JSContext& cx) {
return OBJECT_TO_JSVAL(valuesToJSElements(JS_NewArrayObject(cx, 0, NULL), val, 0, cx));
return OBJECT_TO_JSVAL(valuesToJSProperties(JS_NewObject(cx, NULL, NULL, NULL), val, cx));
}
case value::Nil: {
return JSVAL_NULL;
}
default: {
return JSVAL_VOID;
}

View file

@ -77,6 +77,8 @@ json.jsPropertiesToValues = function(propertiesSoFar, o, i) {
* Converts a JSON val to a value.
*/
json.jsValToValue = function(jsv) {
if (jsv == null)
return null;
if (isList(jsv))
return json.jsPropertiesToValues(mklist(), jsv, reverse(range(0, jsv.length)));
if (typeof jsv == 'object')

View file

@ -118,8 +118,11 @@ const value definitionVariable(const value& exp) {
const value definitionValue(const value& exp) {
const list<value> exps(exp);
if(isSymbol(car(cdr(exps))))
if(isSymbol(car(cdr(exps)))) {
if (isNil(cdr(cdr(exps))))
return value();
return car(cdr(cdr(exps)));
}
const list<value> lexps(car(cdr(exps)));
return makeLambda(cdr(lexps), cdr(cdr(exps)));
}

View file

@ -72,20 +72,30 @@ bool testRead() {
}
bool testWrite() {
const list<value> i = list<value>()
+ (list<value>() + "item" + "cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"
+ (list<value>() + "item"
+ (list<value>() + "name" + "Apple")
+ (list<value>() + "price" + "$2.99")))
+ (list<value>() + "item" + "cart-53d67a61-aa5e-4e5e-8401-39edeba8b83c"
+ (list<value>() + "item"
+ (list<value>() + "name" + "Orange")
+ (list<value>() + "price" + "$3.55")));
const list<value> a = cons<value>("Feed", cons<value>("feed-1234", i));
ostringstream os;
writeValue(a, os);
istringstream is(str(os));
assert(readValue(is) == a);
{
const list<value> i = list<value>()
+ (list<value>() + "item" + "cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"
+ (list<value>() + "item"
+ (list<value>() + "name" + "Apple")
+ (list<value>() + "price" + "$2.99")))
+ (list<value>() + "item" + "cart-53d67a61-aa5e-4e5e-8401-39edeba8b83c"
+ (list<value>() + "item"
+ (list<value>() + "name" + "Orange")
+ (list<value>() + "price" + "$3.55")));
const list<value> a = cons<value>("Feed", cons<value>("feed-1234", i));
ostringstream os;
writeValue(a, os);
istringstream is(str(os));
assert(readValue(is) == a);
}
{
const list<value> i = mklist<value>("x", value());
const list<value> a = mklist<value>(i);
ostringstream os;
writeValue(a, os);
istringstream is(str(os));
assert(readValue(is) == a);
}
return true;
}

View file

@ -88,14 +88,14 @@ const bool isQuote(const value& token) {
return token == quoteSymbol;
}
const value skipComment(istream& in);
const failable<value> skipComment(istream& in);
const value readQuoted(istream& in);
const value readIdentifier(const char chr, istream& in);
const value readString(istream& in);
const value readNumber(const char chr, istream& in);
const value readValue(istream& in);
const value readToken(istream& in) {
const failable<value> readToken(istream& in) {
const char firstChar = readChar(in);
if(isWhitespace(firstChar))
return readToken(in);
@ -114,12 +114,12 @@ const value readToken(istream& in) {
if(isDigit(firstChar))
return readNumber(firstChar, in);
if(firstChar == -1)
return value();
return mkfailure<value>();
logStream() << "Illegal lexical syntax '" << firstChar << "'" << endl;
return readToken(in);
}
const value skipComment(istream& in) {
const failable<value> skipComment(istream& in) {
const char nextChar = readChar(in);
if (nextChar == '\n')
return readToken(in);
@ -131,8 +131,11 @@ const value readQuoted(istream& in) {
}
const list<value> readList(const list<value>& listSoFar, istream& in) {
const value token = readToken(in);
if(isNil(token) || isRightParenthesis(token))
const failable<value> ftoken = readToken(in);
if (!hasContent(ftoken))
return reverse(listSoFar);
const value token = content(ftoken);
if(isRightParenthesis(token))
return reverse(listSoFar);
if(isLeftParenthesis(token))
return readList(cons(value(readList(list<value> (), in)), listSoFar), in);
@ -159,14 +162,22 @@ const value readIdentifier(const char chr, istream& in) {
return value((bool)false);
if (val == "true")
return value((bool)true);
if (val == "nil")
return value();
return val;
}
const list<char> readStringHelper(const list<char>& listSoFar, istream& in) {
const char nextChar = readChar(in);
if(nextChar != -1 && nextChar != '"')
return readStringHelper(cons(nextChar, listSoFar), in);
return reverse(listSoFar);
if(nextChar == -1 || nextChar == '"')
return reverse(listSoFar);
if (nextChar == '\\') {
const char escapedChar = readChar(in);
if (escapedChar == -1)
return reverse(listSoFar);
return readStringHelper(cons(escapedChar, listSoFar), in);
}
return readStringHelper(cons(nextChar, listSoFar), in);
}
const value readString(istream& in) {
@ -185,17 +196,23 @@ const value readNumber(const char chr, istream& in) {
}
const value readValue(istream& in) {
const value nextToken = readToken(in);
const failable<value> fnextToken = readToken(in);
if (!hasContent(fnextToken))
return value();
const value nextToken = content(fnextToken);
if(isLeftParenthesis(nextToken))
return readList(list<value> (), in);
return readList(list<value>(), in);
return nextToken;
}
const value readValue(const string s) {
istringstream in(s);
const value nextToken = readToken(in);
const failable<value> fnextToken = readToken(in);
if (!hasContent(fnextToken))
return value();
const value nextToken = content(fnextToken);
if(isLeftParenthesis(nextToken))
return readList(list<value> (), in);
return readList(list<value>(), in);
return nextToken;
}

View file

@ -57,6 +57,8 @@ def jsPropertiesToValues(propertiesSoFar, o, i):
# Converts a JSON val to a value
def jsValToValue(jsv):
if jsv is None:
return None
if isinstance(jsv, dict):
return jsPropertiesToValues((), jsv, tuple(jsv.keys()))
if isList(jsv):