+
+Path:
+
+If the POST textarea is not empty then it will be posted with the request.
+
+Quick-posts:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Login:
+
+
+
+
+name:
+pw:
+
+
+
+
+
+
+
+
+
+
+
+
POST data
+
Request AJAJ options
+
+
+
+
+
+
+
+
+
+
+
Response
+
+
+
+
+
+
+
+
+
+
+
+
ADDED ajax/js/fossil-ajaj.js
Index: ajax/js/fossil-ajaj.js
==================================================================
--- /dev/null
+++ ajax/js/fossil-ajaj.js
@@ -0,0 +1,275 @@
+/**
+ This file contains a WhAjaj extension for use with Fossil/JSON.
+
+ Author: Stephan Beal (sgbeal@googlemail.com)
+
+ License: Public Domain
+*/
+
+/**
+ Constructor for a new Fossil AJAJ client. ajajOpt may be an optional
+ object suitable for passing to the WhAjaj.Connector() constructor.
+
+ On returning, this.ajaj is-a WhAjaj.Connector instance which can
+ be used to send requests to the back-end (though the convenience
+ functions of this class are the preferred way to do it). Clients
+ are encouraged to use FossilAjaj.sendCommand() (and friends) instead
+ of the underlying WhAjaj.Connector API, since this class' API
+ contains Fossil-specific request-calling handling (e.g. of authentication
+ info) whereas WhAjaj is more generic.
+*/
+function FossilAjaj(ajajOpt)
+{
+ this.ajaj = new WhAjaj.Connector(ajajOpt);
+ return this;
+}
+
+FossilAjaj.prototype.generateRequestId = function() {
+ return this.ajaj.generateRequestId();
+};
+
+/**
+ Proxy for this.ajaj.sendRequest().
+*/
+FossilAjaj.prototype.sendRequest = function(req,opt) {
+ return this.ajaj.sendRequest(req,opt);
+};
+
+/**
+ Sends a command to the fossil back-end. Command should be the
+ path part of the URL, e.g. /json/stat, payload is a request-specific
+ value type (may often be null/undefined). ajajOpt is an optional object
+ holding WhAjaj.sendRequest()-compatible options.
+
+ This function constructs a Fossil/JSON request envelope based
+ on the given arguments and adds this.auth.authToken and a requestId
+ to it.
+*/
+FossilAjaj.prototype.sendCommand = function(command, payload, ajajOpt) {
+ var req;
+ ajajOpt = ajajOpt || {};
+ if(payload || (this.auth && this.auth.authToken) || ajajOpt.jsonp) {
+ req = {
+ payload:payload,
+ requestId:('function' === typeof this.generateRequestId) ? this.generateRequestId() : undefined,
+ authToken:(this.auth ? this.auth.authToken : undefined),
+ jsonp:('string' === typeof ajajOpt.jsonp) ? ajajOpt.jsonp : undefined
+ };
+ }
+ ajajOpt.method = req ? 'POST' : 'GET';
+ // just for debuggering: ajajOpt.method = 'POST'; if(!req) req={};
+ if(command) ajajOpt.url = this.ajaj.derivedOption('url',ajajOpt) + command;
+ this.ajaj.sendRequest(req,ajajOpt);
+};
+
+/**
+ Sends a login request to the back-end.
+
+ ajajOpt is an optional configuration object suitable for passing
+ to sendCommand().
+
+ After the response returns, this.auth will be
+ set to the response payload.
+
+ If name === 'anonymous' (the default if none is passed in) then this
+ function ignores the pw argument and must make two requests - the first
+ one gets the captcha code and the second one submits it.
+ ajajOpt.onResponse() (if set) is only called for the actual login
+ response (the 2nd one), as opposed to being called for both requests.
+ However, this.ajaj.callbacks.onResponse() _is_ called for both (because
+ it happens at a lower level).
+
+ If this object has an onLogin() function it is called (with
+ no arguments) before the onResponse() handler of the login is called
+ (that is the 2nd request for anonymous logins).
+
+*/
+FossilAjaj.prototype.login = function(name,pw,ajajOpt) {
+ name = name || 'anonymous';
+ var self = this;
+ var loginReq = {
+ name:name,
+ password:pw
+ };
+ ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} );
+ var oldOnResponse = ajajOpt.onResponse;
+ ajajOpt.onResponse = function(resp,req) {
+ var thisOpt = this;
+ //alert('login response:\n'+WhAjaj.stringify(resp));
+ if( resp && resp.payload ) {
+ //self.userName = resp.payload.name;
+ //self.capabilities = resp.payload.capabilities;
+ self.auth = resp.payload;
+ }
+ if( WhAjaj.isFunction( self.onLogin ) ){
+ try{ self.onLogin(); }
+ catch(e){}
+ }
+ if( WhAjaj.isFunction(oldOnResponse) ) {
+ oldOnResponse.apply(thisOpt,[resp,req]);
+ }
+ };
+ function doLogin(){
+ //alert("Sending login request..."+WhAjaj.stringify(loginReq));
+ self.sendCommand('/json/login', loginReq, ajajOpt);
+ }
+ if( 'anonymous' === name ){
+ this.sendCommand('/json/anonymousPassword',undefined,{
+ onResponse:function(resp,req){
+/*
+ if( WhAjaj.isFunction(oldOnResponse) ){
+ oldOnResponse.apply(this, [resp,req]);
+ };
+*/
+ if(resp && !resp.resultCode){
+ //alert("Got PW. Trying to log in..."+WhAjaj.stringify(resp));
+ loginReq.anonymousSeed = resp.payload.seed;
+ loginReq.password = resp.payload.password;
+ doLogin();
+ }
+ }
+ });
+ }
+ else doLogin();
+};
+
+/**
+ Logs out of fossil, invaliding this login token.
+
+ ajajOpt is an optional configuration object suitable for passing
+ to sendCommand().
+
+ If this object has an onLogout() function it is called (with
+ no arguments) before the onResponse() handler is called.
+ IFF the response succeeds then this.auth is unset.
+*/
+FossilAjaj.prototype.logout = function(ajajOpt) {
+ var self = this;
+ ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} );
+ var oldOnResponse = ajajOpt.onResponse;
+ ajajOpt.onResponse = function(resp,req) {
+ var thisOpt = this;
+ if( resp && !resp.payload ){
+ delete self.auth;
+ }
+ if( WhAjaj.isFunction( self.onLogout ) ){
+ try{ self.onLogout(); }
+ catch(e){}
+ }
+ if( WhAjaj.isFunction(oldOnResponse) ) {
+ oldOnResponse.apply(thisOpt,[resp,req]);
+ }
+ };
+ this.sendCommand('/json/logout', undefined, ajajOpt );
+};
+
+/**
+ Sends a HAI request to the server. /json/HAI is an alias /json/version.
+
+ ajajOpt is an optional configuration object suitable for passing
+ to sendCommand().
+*/
+FossilAjaj.prototype.HAI = function(ajajOpt) {
+ this.sendCommand('/json/HAI', undefined, ajajOpt);
+};
+
+
+/**
+ Sends a /json/whoami request. Updates this.auth to contain
+ the login info, removing them if the response does not contain
+ that data.
+*/
+FossilAjaj.prototype.whoami = function(ajajOpt) {
+ var self = this;
+ ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} );
+ var oldOnResponse = ajajOpt.onResponse;
+ ajajOpt.onResponse = function(resp,req) {
+ var thisOpt = this;
+ if( resp && resp.payload ){
+ if(!self.auth || (self.auth.authToken!==resp.payload.authToken)){
+ self.auth = resp.payload;
+ if( WhAjaj.isFunction(self.onLogin) ){
+ self.onLogin();
+ }
+ }
+ }
+ else { delete self.auth; }
+ if( WhAjaj.isFunction(oldOnResponse) ) {
+ oldOnResponse.apply(thisOpt,[resp,req]);
+ }
+ };
+ self.sendCommand('/json/whoami', undefined, ajajOpt);
+};
+
+/**
+ EXPERIMENTAL concrete WhAjaj.Connector.sendImpl() implementation which
+ uses Rhino to connect to a local fossil binary for input and output. Its
+ signature and semantics are as described for
+ WhAjaj.Connector.prototype.sendImpl(), with a few exceptions and
+ additions:
+
+ - It does not support timeouts or asynchronous mode.
+
+ - The args.fossilBinary property must point to the local fossil binary
+ (it need not be a complete path if fossil is in the $PATH). This
+ function throws (without calling any request callbacks) if
+ args.fossilBinary is not set. fossilBinary may be set on
+ WhAjaj.Connector.options.ajax, in the FossilAjaj constructor call, as
+ the ajax options parameter to any of the FossilAjaj.sendCommand() family
+ of functions, or by setting
+ aFossilAjajInstance.ajaj.options.fossilBinary on a specific
+ FossilAjaj instance.
+
+ - It uses the args.url field to create the "command" property of the
+ request, constructs a request envelope, spawns a fossil process in JSON
+ mode, feeds it the request envelope, and returns the response envelope
+ via the same mechanisms defined for the HTTP-based implementations.
+
+ The interface is otherwise compatible with the "normal"
+ FossilAjaj.sendCommand() front-end (it is, however, fossil-specific, and
+ not back-end agnostic like the WhAjaj.sendImpl() interface intends).
+
+
+*/
+FossilAjaj.rhinoLocalBinarySendImpl = function(request,args){
+ var self = this;
+ request = request || {};
+ if(!args.fossilBinary){
+ throw new Error("fossilBinary is not set on AJAX options!");
+ }
+ var url = args.url.split('?')[0].split(/\/+/);
+ if(url.length>1){
+ // 3x shift(): protocol, host, 'json' part of path
+ request.command = (url.shift(),url.shift(),url.shift(), url.join('/'));
+ }
+ delete args.url;
+ //print("rhinoLocalBinarySendImpl SENDING: "+WhAjaj.stringify(request));
+ var json;
+ try{
+ var pargs = [args.fossilBinary, 'json', '--json-input', '-'];
+ var p = java.lang.Runtime.getRuntime().exec(pargs);
+ var outs = p.getOutputStream();
+ var osr = new java.io.OutputStreamWriter(outs);
+ var osb = new java.io.BufferedWriter(osr);
+
+ json = JSON.stringify(request);
+ osb.write(json,0, json.length);
+ osb.close();
+ var ins = p.getInputStream();
+ var isr = new java.io.InputStreamReader(ins);
+ var br = new java.io.BufferedReader(isr);
+ var line;
+ json = [];
+ while( null !== (line=br.readLine())){
+ json.push(line);
+ }
+ ins.close();
+ }catch(e){
+ args.errorMessage = e.toString();
+ WhAjaj.Connector.sendHelper.onSendError.apply( self, [request, args] );
+ return undefined;
+ }
+ json = json.join('');
+ //print("READ IN JSON: "+json);
+ WhAjaj.Connector.sendHelper.onSendSuccess.apply( self, [request, json, args] );
+}/*rhinoLocalBinary*/
ADDED ajax/js/json2.js
Index: ajax/js/json2.js
==================================================================
--- /dev/null
+++ ajax/js/json2.js
@@ -0,0 +1,476 @@
+/*
+ http://www.JSON.org/json2.js
+ 2009-06-29
+
+ Public Domain.
+
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+ See http://www.JSON.org/js.html
+
+ This file creates a global JSON object containing two methods: stringify
+ and parse.
+
+ JSON.stringify(value, replacer, space)
+ value any JavaScript value, usually an object or array.
+
+ replacer an optional parameter that determines how object
+ values are stringified for objects. It can be a
+ function or an array of strings.
+
+ space an optional parameter that specifies the indentation
+ of nested structures. If it is omitted, the text will
+ be packed without extra whitespace. If it is a number,
+ it will specify the number of spaces to indent at each
+ level. If it is a string (such as '\t' or ' '),
+ it contains the characters used to indent at each level.
+
+ This method produces a JSON text from a JavaScript value.
+
+ When an object value is found, if the object contains a toJSON
+ method, its toJSON method will be called and the result will be
+ stringified. A toJSON method does not serialize: it returns the
+ value represented by the name/value pair that should be serialized,
+ or undefined if nothing should be serialized. The toJSON method
+ will be passed the key associated with the value, and this will be
+ bound to the object holding the key.
+
+ For example, this would serialize Dates as ISO strings.
+
+ Date.prototype.toJSON = function (key) {
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ return this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z';
+ };
+
+ You can provide an optional replacer method. It will be passed the
+ key and value of each member, with this bound to the containing
+ object. The value that is returned from your method will be
+ serialized. If your method returns undefined, then the member will
+ be excluded from the serialization.
+
+ If the replacer parameter is an array of strings, then it will be
+ used to select the members to be serialized. It filters the results
+ such that only members with keys listed in the replacer array are
+ stringified.
+
+ Values that do not have JSON representations, such as undefined or
+ functions, will not be serialized. Such values in objects will be
+ dropped; in arrays they will be replaced with null. You can use
+ a replacer function to replace those with JSON values.
+ JSON.stringify(undefined) returns undefined.
+
+ The optional space parameter produces a stringification of the
+ value that is filled with line breaks and indentation to make it
+ easier to read.
+
+ If the space parameter is a non-empty string, then that string will
+ be used for indentation. If the space parameter is a number, then
+ the indentation will be that many spaces.
+
+ Example:
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}]);
+ // text is '["e",{"pluribus":"unum"}]'
+
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+ text = JSON.stringify([new Date()], function (key, value) {
+ return this[key] instanceof Date ?
+ 'Date(' + this[key] + ')' : value;
+ });
+ // text is '["Date(---current time---)"]'
+
+
+ JSON.parse(text, reviver)
+ This method parses a JSON text to produce an object or array.
+ It can throw a SyntaxError exception.
+
+ The optional reviver parameter is a function that can filter and
+ transform the results. It receives each of the keys and values,
+ and its return value is used instead of the original value.
+ If it returns what it received, then the structure is not modified.
+ If it returns undefined then the member is deleted.
+
+ Example:
+
+ // Parse the text. Values that look like ISO date strings will
+ // be converted to Date objects.
+
+ myData = JSON.parse(text, function (key, value) {
+ var a;
+ if (typeof value === 'string') {
+ a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+ if (a) {
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+ +a[5], +a[6]));
+ }
+ }
+ return value;
+ });
+
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+ var d;
+ if (typeof value === 'string' &&
+ value.slice(0, 5) === 'Date(' &&
+ value.slice(-1) === ')') {
+ d = new Date(value.slice(5, -1));
+ if (d) {
+ return d;
+ }
+ }
+ return value;
+ });
+
+
+ This is a reference implementation. You are free to copy, modify, or
+ redistribute.
+
+ This code should be minified before deployment.
+ See http://javascript.crockford.com/jsmin.html
+
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+ NOT CONTROL.
+*/
+
+/*jslint evil: true */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
+ call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+ getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+ lastIndex, length, parse, prototype, push, replace, slice, stringify,
+ test, toJSON, toString, valueOf
+*/
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+var JSON = JSON || {};
+
+(function () {
+
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ if (typeof Date.prototype.toJSON !== 'function') {
+
+ Date.prototype.toJSON = function (key) {
+
+ return isFinite(this.valueOf()) ?
+ this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z' : null;
+ };
+
+ String.prototype.toJSON =
+ Number.prototype.toJSON =
+ Boolean.prototype.toJSON = function (key) {
+ return this.valueOf();
+ };
+ }
+
+ var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ gap,
+ indent,
+ meta = { // table of character substitutions
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ },
+ rep;
+
+
+ function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+ escapable.lastIndex = 0;
+ return escapable.test(string) ?
+ '"' + string.replace(escapable, function (a) {
+ var c = meta[a];
+ return typeof c === 'string' ? c :
+ '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ }) + '"' :
+ '"' + string + '"';
+ }
+
+
+ function str(key, holder) {
+
+// Produce a string from holder[key].
+
+ var i, // The loop counter.
+ k, // The member key.
+ v, // The member value.
+ length,
+ mind = gap,
+ partial,
+ value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+ if (value && typeof value === 'object' &&
+ typeof value.toJSON === 'function') {
+ value = value.toJSON(key);
+ }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+ if (typeof rep === 'function') {
+ value = rep.call(holder, key, value);
+ }
+
+// What happens next depends on the value's type.
+
+ switch (typeof value) {
+ case 'string':
+ return quote(value);
+
+ case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+ return isFinite(value) ? String(value) : 'null';
+
+ case 'boolean':
+ case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+ return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+ case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+ if (!value) {
+ return 'null';
+ }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+ gap += indent;
+ partial = [];
+
+// Is the value an array?
+
+ if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+ length = value.length;
+ for (i = 0; i < length; i += 1) {
+ partial[i] = str(i, value) || 'null';
+ }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+ v = partial.length === 0 ? '[]' :
+ gap ? '[\n' + gap +
+ partial.join(',\n' + gap) + '\n' +
+ mind + ']' :
+ '[' + partial.join(',') + ']';
+ gap = mind;
+ return v;
+ }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+ if (rep && typeof rep === 'object') {
+ length = rep.length;
+ for (i = 0; i < length; i += 1) {
+ k = rep[i];
+ if (typeof k === 'string') {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+ v = partial.length === 0 ? '{}' :
+ gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
+ mind + '}' : '{' + partial.join(',') + '}';
+ gap = mind;
+ return v;
+ }
+ }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+ if (typeof JSON.stringify !== 'function') {
+ JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+ var i;
+ gap = '';
+ indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+ if (typeof space === 'number') {
+ for (i = 0; i < space; i += 1) {
+ indent += ' ';
+ }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+ } else if (typeof space === 'string') {
+ indent = space;
+ }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+ rep = replacer;
+ if (replacer && typeof replacer !== 'function' &&
+ (typeof replacer !== 'object' ||
+ typeof replacer.length !== 'number')) {
+ throw new Error('JSON.stringify');
+ }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+ return str('', {'': value});
+ };
+ }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+ if (typeof JSON.parse !== 'function') {
+ JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+ var j;
+
+ function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+ var k, v, value = holder[key];
+ if (value && typeof value === 'object') {
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = walk(value, k);
+ if (v !== undefined) {
+ value[k] = v;
+ } else {
+ delete value[k];
+ }
+ }
+ }
+ }
+ return reviver.call(holder, key, value);
+ }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+ cx.lastIndex = 0;
+ if (cx.test(text)) {
+ text = text.replace(cx, function (a) {
+ return '\\u' +
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ });
+ }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+ if (/^[\],:{}\s]*$/.
+test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+ j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+ return typeof reviver === 'function' ?
+ walk({'': j}, '') : j;
+ }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+ throw new SyntaxError('JSON.parse');
+ };
+ }
+}());
ADDED ajax/js/whajaj.js
Index: ajax/js/whajaj.js
==================================================================
--- /dev/null
+++ ajax/js/whajaj.js
@@ -0,0 +1,1213 @@
+/**
+ This file provides a JS interface into the core functionality of
+ JSON-centric back-ends. It sends GET or JSON POST requests to
+ a back-end and expects JSON responses. The exact semantics of
+ the underlying back-end and overlying front-end are not its concern,
+ and it leaves the interpretation of the data up to the client/server
+ insofar as possible.
+
+ All functionality is part of a class named WhAjaj, and that class
+ acts as namespace for this framework.
+
+ Author: Stephan Beal (http://wanderinghorse.net/home/stephan/)
+
+ License: Public Domain
+
+ This framework is directly derived from code originally found in
+ http://code.google.com/p/jsonmessage, and later in
+ http://whiki.wanderinghorse.net, where it contained quite a bit
+ of application-specific logic. It was eventually (the 3rd time i
+ needed it) split off into its own library to simplify inclusion
+ into my many mini-projects.
+*/
+
+
+/**
+ The WhAjaj function is primarily a namespace, and not intended
+ to called or instantiated via the 'new' operator.
+*/
+function WhAjaj()
+{
+}
+
+/** Returns a millisecond Unix Epoch timestamp. */
+WhAjaj.msTimestamp = function()
+{
+ return (new Date()).getTime();
+};
+
+/** Returns a Unix Epoch timestamp (in seconds) in integer format.
+
+ Reminder to self: (1.1 %1.2) evaluates to a floating-point value
+ in JS, and thus this implementation is less than optimal.
+*/
+WhAjaj.unixTimestamp = function()
+{
+ var ts = (new Date()).getTime();
+ return parseInt( ""+((ts / 1000) % ts) );
+};
+
+/**
+ Returns true if v is-a Array instance.
+*/
+WhAjaj.isArray = function( v )
+{
+ return (v &&
+ (v instanceof Array) ||
+ (Object.prototype.toString.call(v) === "[object Array]")
+ );
+ /* Reminders to self:
+ typeof [] == "object"
+ toString.call([]) == "[object Array]"
+ ([]).toString() == empty
+ */
+};
+
+/**
+ Returns true if v is-a Object instance.
+*/
+WhAjaj.isObject = function( v )
+{
+ return v &&
+ (v instanceof Object) &&
+ ('[object Object]' === Object.prototype.toString.apply(v) );
+};
+
+/**
+ Returns true if v is-a Function instance.
+*/
+WhAjaj.isFunction = function(obj)
+{
+ return obj
+ && (
+ (obj instanceof Function)
+ || ('function' === typeof obj)
+ || ("[object Function]" === Object.prototype.toString.call(obj))
+ )
+ ;
+};
+
+/**
+ Parses window.location.search-style string into an object
+ containing key/value pairs of URL arguments (already urldecoded).
+
+ If the str argument is not passed (arguments.length==0) then
+ window.location.search.substring(1) is used by default. If
+ neither str is passed in nor window exists then false is returned.
+
+ On success it returns an Object containing the key/value pairs
+ parsed from the string.
+
+ FIXME: for keys in the form "name[]", build an array of results,
+ like PHP does.
+
+*/
+WhAjaj.processUrlArgs = function(str) {
+ if( 0 === arguments.length ) {
+ if( (undefined === typeof window) ||
+ !window.location ||
+ !window.location.search ) return false;
+ else str = (''+window.location.search).substring(1);
+ }
+ if( ! str ) return false;
+ var args = {};
+ var sp = str.split(/&+/);
+ var rx = /^([^=]+)(=(.+))?/;
+ var i, m;
+ for( i in sp ) {
+ m = rx.exec( sp[i] );
+ if( ! m ) continue;
+ args[decodeURIComponent(m[1])] = (m[3] ? decodeURIComponent(m[3]) : true);
+ }
+ return args;
+};
+
+/**
+ A simple wrapper around JSON.stringify(), using my own personal
+ preferred values for the 2nd and 3rd parameters. To globally
+ set its indentation level, assign WhAjaj.stringify.indent to
+ an integer value (0 for no intendation).
+
+ This function is intended only for human-readable output, not
+ generic over-the-wire JSON output (where JSON.stringify(val) will
+ produce smaller results).
+*/
+WhAjaj.stringify = function(val) {
+ if( ! arguments.callee.indent ) arguments.callee.indent = 4;
+ return JSON.stringify(val,0,arguments.callee.indent);
+};
+
+/**
+ Each instance of this class holds state information for making
+ AJAJ requests to a back-end system. While clients may use one
+ "requester" object per connection attempt, for connections to the
+ same back-end, using an instance configured for that back-end
+ can simplify usage. This class is designed so that the actual
+ connection-related details (i.e. _how_ it connects to the
+ back-end) may be re-implemented to use a client's preferred
+ connection mechanism (e.g. jQuery).
+
+ The optional opt paramater may be an object with any (or all) of
+ the properties documented for WhAjaj.Connector.options.ajax.
+ Properties set here (or later via modification of the "options"
+ property of this object) will be used in calls to
+ WhAjaj.Connector.sendRequest(), and these override (normally) any
+ options set in WhAjaj.Connector.options.ajax. Note that
+ WhAjaj.Connector.sendRequest() _also_ takes an options object,
+ and ones passed there will override, for purposes of that one
+ request, any options passed in here or defined in
+ WhAjaj.Connector.options.ajax. See WhAjaj.Connector.options.ajax
+ and WhAjaj.Connector.prototype.sendRequest() for more details
+ about the precedence of options.
+
+ Sample usage:
+
+ @code
+ // Set up common connection-level options:
+ var cgi = new WhAjaj.Connector({
+ url: '/cgi-bin/my.cgi',
+ timeout:10000,
+ onResponse(resp,req) { alert(JSON.stringify(resp,0.4)); },
+ onError(req,opt) {
+ alert(opt.errorMessage);
+ }
+ });
+ // Any of those options may optionally be set globally in
+ // WhAjaj.Connector.options.ajax (onError(), beforeSend(), and afterSend()
+ // are often easiest/most useful to set globally).
+
+ // Get list of pages...
+ cgi.sendRequest( null, {
+ onResponse(resp,req){ alert(WhAjaj.stringify(resp)); }
+ });
+ @endcode
+
+ For common request types, clients can add functions to this
+ object which act as wrappers for backend-specific functionality. As
+ a simple example:
+
+ @code
+ cgi.login = function(name,pw,ajajOpt) {
+ this.sendRequest( {name:name, password:pw}, ajajOpt );
+ };
+ @endcode
+
+ TODOs:
+
+ - Caching of page-load requests, with a configurable lifetime.
+
+ - Use-cases like the above login() function are a tiny bit
+ problematic to implement when each request has a different URL
+ path (i know this from the whiki implementation). This is partly
+ a side-effect of design descisions made back in the very first
+ days of this code's life. i need to go through and see where i
+ can bend those conventions a bit (where it won't break my other
+ apps unduly).
+*/
+WhAjaj.Connector = function(opt)
+{
+ if(WhAjaj.isObject(opt)) this.options = opt;
+ //TODO?: this.$cache = {};
+};
+
+/**
+ The core options used by WhAjaj.Connector instances for performing
+ network operations. These options can (and some _should_)
+ be changed by a client application. They can also be changed
+ on specific instances of WhAjaj.Connector, but for most applications
+ it is simpler to set them here and not have to bother with configuring
+ each WhAjaj.Connector instance. Apps which use multiple back-ends at one time,
+ however, will need to customize each instance for a given back-end.
+*/
+WhAjaj.Connector.options = {
+ /**
+ A (meaningless) prefix to apply to WhAjaj.Connector-generated
+ request IDs.
+ */
+ requestIdPrefix:'WhAjaj.Connector-',
+ /**
+ Default options for WhAjaj.Connector.sendRequest() connection
+ parameters. This object holds only connection-related
+ options and callbacks (all optional), and not options
+ related to the required JSON structure of any given request.
+ i.e. the page name used in a get-page request are not set
+ here but are specified as part of the request object.
+
+ These connection options are a "normalized form" of options
+ often found in various AJAX libraries like jQuery,
+ Prototype, dojo, etc. This approach allows us to swap out
+ the real connection-related parts by writing a simple proxy
+ which transforms our "normalized" form to the
+ backend-specific form. For examples, see the various
+ implementations stored in WhAjaj.Connector.sendImpls.
+
+ The following callback options are, in practice, almost
+ always set globally to some app-wide defaults:
+
+ - onError() to report errors using a common mechanism.
+ - beforeSend() to start a visual activity notification
+ - afterSend() to disable the visual activity notification
+
+ However, be aware that if any given WhAjaj.Connector instance is
+ given its own before/afterSend callback then those will
+ override these. Mixing shared/global and per-instance
+ callbacks can potentially lead to confusing results if, e.g.,
+ the beforeSend() and afterSend() functions have side-effects
+ but are not used with their proper before/after partner.
+
+ TODO: rename this to 'ajaj' (the name is historical). The
+ problem with renaming it is is that the word 'ajax' is
+ pretty prevelant in the source tree, so i can't globally
+ swap it out.
+ */
+ ajax: {
+ /**
+ URL of the back-end server/CGI.
+ */
+ url: '/some/path',
+
+ /**
+ Connection method. Some connection-related functions might
+ override any client-defined setting.
+
+ Must be one of 'GET' or 'POST'. For custom connection
+ implementation, it may optionally be some
+ implementation-specified value.
+
+ Normally the API can derive this value automatically - if the
+ request uses JSON data it is POSTed, else it is GETted.
+ */
+ method:'GET',
+
+ /**
+ A hint whether to run the operation asynchronously or
+ not. Not all concrete WhAjaj.Connector.sendImpl()
+ implementations can support this. Interestingly, at
+ least one popular AJAX toolkit does not document
+ supporting _synchronous_ AJAX operations. All common
+ browser-side implementations support async operation, but
+ non-browser implementations migth not.
+ */
+ asynchronous:true,
+
+ /**
+ A HTTP authentication login name for the AJAX
+ connection. Not all concrete WhAjaj.Connector.sendImpl()
+ implementations can support this.
+ */
+ loginName:undefined,
+
+ /**
+ An HTTP authentication login password for the AJAJ
+ connection. Not all concrete WhAjaj.Connector.sendImpl()
+ implementations can support this.
+ */
+ loginPassword:undefined,
+
+ /**
+ A connection timeout, in milliseconds, for establishing
+ an AJAJ connection. Not all concrete
+ WhAjaj.Connector.sendImpl() implementations can support this.
+ */
+ timeout:10000,
+
+ /**
+ If an AJAJ request receives JSON data from the back-end, that
+ data is passed as a plain Object as the response parameter
+ (exception: in jsonp mode it is passed a string). The initiating
+ request object is passed as the second parameter, but clients
+ can normally ignore it (only those which need a way to map
+ specific requests to responses will need it). The 3rd parameter
+ is the same as the 'this' object for the context of the callback,
+ but is provided because the instance-level callbacks (set in
+ (WhAjaj.Connector instance).callbacks, require it in some
+ cases (because their 'this' is different!).
+
+ Note that the response might contain error information which
+ comes from the back-end. The difference between this error info
+ and the info passed to the onError() callback is that this data
+ indicates an application-level error, whereas onError() is used
+ to report connection-level problems or when the backend produces
+ non-JSON data (which, when not in jsonp mode, is unexpected and
+ is as fatal to the request as a connection error).
+ */
+ onResponse: function(response, request, opt){},
+
+ /**
+ If an AJAX request fails to establish a connection or it
+ receives non-JSON data from the back-end, this function
+ is called (e.g. timeout error or host name not
+ resolvable). It is passed the originating request and the
+ "normalized" connection parameters used for that
+ request. The connectOpt object "should" (or "might")
+ have an "errorMessage" property which describes the
+ nature of the problem.
+
+ Clients will almost always want to replace the default
+ implementation with something which integrates into
+ their application.
+ */
+ onError: function(request, connectOpt)
+ {
+ alert('AJAJ request failed:\n'
+ +'Connection information:\n'
+ +JSON.stringify(connectOpt,0,4)
+ );
+ },
+
+ /**
+ Called before each connection attempt is made. Clients
+ can use this to, e.g., enable a visual "network activity
+ notification" for the user. It is passed the original
+ request object and the normalized connection parameters
+ for the request. If this function changes opt, those
+ changes _are_ applied to the subsequent request. If this
+ function throws, neither the onError() nor afterSend()
+ callbacks are triggered and WhAjaj.Connector.sendImpl()
+ propagates the exception back to the caller.
+ */
+ beforeSend: function(request,opt){},
+
+ /**
+ Called after an AJAJ connection attempt completes,
+ regardless of success or failure. Passed the same
+ parameters as beforeSend() (see that function for
+ details).
+
+ Here's an example of setting up a visual notification on
+ ajax operations using jQuery (but it's also easy to do
+ without jQuery as well):
+
+ @code
+ function startAjaxNotif(req,opt) {
+ var me = arguments.callee;
+ var c = ++me.ajaxCount;
+ me.element.text( c + " pending AJAX operation(s)..." );
+ if( 1 == c ) me.element.stop().fadeIn();
+ }
+ startAjaxNotif.ajaxCount = 0.
+ startAjaxNotif.element = jQuery('#whikiAjaxNotification');
+
+ function endAjaxNotif() {
+ var c = --startAjaxNotif.ajaxCount;
+ startAjaxNotif.element.text( c+" pending AJAX operation(s)..." );
+ if( 0 == c ) startAjaxNotif.element.stop().fadeOut();
+ }
+ @endcode
+
+ Set the beforeSend/afterSend properties to those
+ functions to enable the notifications by default.
+ */
+ afterSend: function(request,opt){},
+
+ /**
+ If jsonp is a string then the WhAjaj-internal response
+ handling code ASSUMES that the response contains a JSONP-style
+ construct and eval()s it after afterSend() but before onResponse().
+ In this case, onResponse() will get a string value for the response
+ instead of a response object parsed from JSON.
+ */
+ jsonp:undefined,
+ /**
+ Don't use yet. Planned future option.
+ */
+ propagateExceptions:false
+ }
+};
+
+
+/**
+ WhAjaj.Connector.prototype.callbacks defines callbacks analog
+ to the onXXX callbacks defined in WhAjaj.Connector.options.ajax,
+ with two notable differences:
+
+ 1) these callbacks, if set, are called in addition to any
+ request-specific callback. The intention is to allow a framework to set
+ "framework-level" callbacks which should be called independently of the
+ request-specific callbacks (without interfering with them, e.g.
+ requiring special re-forwarding features).
+
+ 2) The 'this' object in these callbacks is the Connector instance
+ associated with the callback, whereas the "other" onXXX form has its
+ "ajax options" object as its this.
+
+ When this API says that an onXXX callback will be called for a request,
+ both the request's onXXX (if set) and this one (if set) will be called.
+*/
+WhAjaj.Connector.prototype.callbacks = {};
+/**
+ Instance-specific values for AJAJ-level properties (as opposed to
+ application-level request properties). Options set here "override" those
+ specified in WhAjaj.Connector.options.ajax and are "overridden" by
+ options passed to sendRequest().
+*/
+WhAjaj.Connector.prototype.options = {};
+
+
+/**
+ Tries to find the given key in any of the following, returning
+ the first match found: opt, this.options, WhAjaj.Connector.options.ajax.
+
+ Returns undefined if key is not found.
+*/
+WhAjaj.Connector.prototype.derivedOption = function(key,opt) {
+ var v = opt ? opt[key] : undefined;
+ if( undefined !== v ) return v;
+ else v = this.options[key];
+ if( undefined !== v ) return v;
+ else v = WhAjaj.Connector.options.ajax[key];
+ return v;
+};
+
+/**
+ Returns a unique string on each call containing a generic
+ reandom request identifier string. This is not used by the core
+ API but can be used by client code to generate unique IDs for
+ each request (if needed).
+
+ The exact format is unspecified and may change in the future.
+
+ Request IDs can be used by clients to "match up" responses to
+ specific requests if needed. In practice, however, they are
+ seldom, if ever, needed. When passing several concurrent
+ requests through the same response callback, it might be useful
+ for some clients to be able to distinguish, possibly re-routing
+ them through other handlers based on the originating request type.
+
+ If this.options.requestIdPrefix or
+ WhAjaj.Connector.options.requestIdPrefix is set then that text
+ is prefixed to the returned string.
+*/
+WhAjaj.Connector.prototype.generateRequestId = function()
+{
+ if( undefined === arguments.callee.sequence )
+ {
+ arguments.callee.sequence = 0;
+ }
+ var pref = this.options.requestIdPrefix || WhAjaj.Connector.options.requestIdPrefix || '';
+ return pref +
+ WhAjaj.msTimestamp() +
+ '/'+(Math.round( Math.random() * 100000000) )+
+ ':'+(++arguments.callee.sequence);
+};
+
+/**
+ Copies (SHALLOWLY) all properties in opt to this.options.
+*/
+WhAjaj.Connector.prototype.addOptions = function(opt) {
+ var k, v;
+ for( k in opt ) {
+ if( ! opt.hasOwnProperty(k) ) continue /* proactive Prototype kludge! */;
+ this.options[k] = opt[k];
+ }
+ return this.options;
+};
+
+/**
+ An internal helper object which holds several functions intended
+ to simplify the creation of concrete communication channel
+ implementations for WhAjaj.Connector.sendImpl(). These operations
+ take care of some of the more error-prone parts of ensuring that
+ onResponse(), onError(), etc. callbacks are called consistently
+ using the same rules.
+*/
+WhAjaj.Connector.sendHelper = {
+ /**
+ opt is assumed to be a normalized set of
+ WhAjaj.Connector.sendRequest() options. This function
+ creates a url by concatenating opt.url and some form of
+ opt.urlParam.
+
+ If opt.urlParam is an object or string then it is appended
+ to the url. An object is assumed to be a one-dimensional set
+ of simple (urlencodable) key/value pairs, and not larger
+ data structures. A string value is assumed to be a
+ well-formed, urlencoded set of key/value pairs separated by
+ '&' characters.
+
+ The new/normalized URL is returned (opt is not modified). If
+ opt.urlParam is not set then opt.url is returned (or an
+ empty string if opt.url is itself a false value).
+
+ TODO: if opt is-a Object and any key points to an array,
+ build up a list of keys in the form "keyname[]". We could
+ arguably encode sub-objects like "keyname[subkey]=...", but
+ i don't know if that's conventions-compatible with other
+ frameworks.
+ */
+ normalizeURL: function(opt) {
+ var u = opt.url || '';
+ if( opt.urlParam ) {
+ var addQ = (u.indexOf('?') >= 0) ? false : true;
+ var addA = addQ ? false : ((u.indexOf('&')>=0) ? true : false);
+ var tail = '';
+ if( WhAjaj.isObject(opt.urlParam) ) {
+ var li = [], k;
+ for( k in opt.urlParam) {
+ li.push( k+'='+encodeURIComponent( opt.urlParam[k] ) );
+ }
+ tail = li.join('&');
+ }
+ else if( 'string' === typeof opt.urlParam ) {
+ tail = opt.urlParam;
+ }
+ u = u + (addQ ? '?' : '') + (addA ? '&' : '') + tail;
+ }
+ return u;
+ },
+ /**
+ Should be called by WhAjaj.Connector.sendImpl()
+ implementations after a response has come back. This
+ function takes care of most of ensuring that framework-level
+ conventions involving WhAjaj.Connector.options.ajax
+ properties are followed.
+
+ The request argument must be the original request passed to
+ the sendImpl() function. It may legally be null for GET requests.
+
+ The opt object should be the normalized AJAX options used
+ for the connection.
+
+ The resp argument may be either a plain Object or a string
+ (in which case it is assumed to be JSON).
+
+ The 'this' object for this call MUST be a WhAjaj.Connector
+ instance in order for callback processing to work properly.
+
+ This function takes care of the following:
+
+ - Calling opt.afterSend()
+
+ - If resp is a string, de-JSON-izing it to an object.
+
+ - Calling opt.onResponse()
+
+ - Calling opt.onError() in several common (potential) error
+ cases.
+
+ - If resp is-a String and opt.jsonp then resp is assumed to be
+ a JSONP-form construct and is eval()d BEFORE opt.onResponse()
+ is called. It is arguable to eval() it first, but the logic
+ integrates better with the non-jsonp handler.
+
+ The sendImpl() should return immediately after calling this.
+
+ The sendImpl() must call only one of onSendSuccess() or
+ onSendError(). It must call one of them or it must implement
+ its own response/error handling, which is not recommended
+ because getting the documented semantics of the
+ onError/onResponse/afterSend handling correct can be tedious.
+ */
+ onSendSuccess:function(request,resp,opt) {
+ var cb = this.callbacks || {};
+ if( WhAjaj.isFunction(cb.afterSend) ) {
+ try {cb.afterSend( request, opt );}
+ catch(e){}
+ }
+ if( WhAjaj.isFunction(opt.afterSend) ) {
+ try {opt.afterSend( request, opt );}
+ catch(e){}
+ }
+ function doErr(){
+ if( WhAjaj.isFunction(cb.onError) ) {
+ try {cb.onError( request, opt );}
+ catch(e){}
+ }
+ if( WhAjaj.isFunction(opt.onError) ) {
+ try {opt.onError( request, opt );}
+ catch(e){}
+ }
+ }
+ if( ! resp ) {
+ opt.errorMessage = "Sending of request succeeded but returned no data!";
+ doErr();
+ return false;
+ }
+
+ if( 'string' === typeof resp ) {
+ try {
+ resp = opt.jsonp ? eval(resp) : JSON.parse(resp);
+ } catch(e) {
+ opt.errorMessage = e.toString();
+ doErr();
+ return;
+ }
+ }
+ try {
+ if( WhAjaj.isFunction( cb.onResponse ) ) {
+ cb.onResponse( resp, request, opt );
+ }
+ if( WhAjaj.isFunction( opt.onResponse ) ) {
+ opt.onResponse( resp, request, opt );
+ }
+ return true;
+ }
+ catch(e) {
+ opt.errorMessage = "Exception while handling inbound JSON response:\n"
+ + e
+ +"\nOriginal response data:\n"+JSON.stringify(resp,0,2)
+ ;
+ ;
+ doErr();
+ return false;
+ }
+ },
+ /**
+ Should be called by sendImpl() implementations after a response
+ has failed to connect (e.g. could not resolve host or timeout
+ reached). This function takes care of most of ensuring that
+ framework-level conventions involving WhAjaj.Connector.options.ajax
+ properties are followed.
+
+ The request argument must be the original request passed to
+ the sendImpl() function. It may legally be null for GET
+ requests.
+
+ The 'this' object for this call MUST be a WhAjaj.Connector
+ instance in order for callback processing to work properly.
+
+ The opt object should be the normalized AJAX options used
+ for the connection. By convention, the caller of this
+ function "should" set opt.errorMessage to contain a
+ human-readable description of the error.
+
+ The sendImpl() should return immediately after calling this. The
+ return value from this function is unspecified.
+ */
+ onSendError: function(request,opt) {
+ var cb = this.callbacks || {};
+ if( WhAjaj.isFunction(cb.afterSend) ) {
+ try {cb.afterSend( request, opt );}
+ catch(e){}
+ }
+ if( WhAjaj.isFunction(opt.afterSend) ) {
+ try {opt.afterSend( request, opt );}
+ catch(e){}
+ }
+ if( WhAjaj.isFunction( cb.onError ) ) {
+ try {cb.onError( request, opt );}
+ catch(e) {/*ignore*/}
+ }
+ if( WhAjaj.isFunction( opt.onError ) ) {
+ try {opt.onError( request, opt );}
+ catch(e) {/*ignore*/}
+ }
+ }
+};
+
+/**
+ WhAjaj.Connector.sendImpls holds several concrete
+ implementations of WhAjaj.Connector.prototype.sendImpl(). To use
+ a specific implementation by default assign
+ WhAjaj.Connector.prototype.sendImpl to one of these functions.
+
+ The functions defined here require that the 'this' object be-a
+ WhAjaj.Connector instance.
+
+ Historical notes:
+
+ a) We once had an implementation based on Prototype, but that
+ library just pisses me off (they change base-most types'
+ prototypes, introducing side-effects in client code which
+ doesn't even use Prototype). The Prototype version at the time
+ had a serious toJSON() bug which caused empty arrays to
+ serialize as the string "[]", which broke a bunch of my code.
+ (That has been fixed in the mean time, but i don't use
+ Prototype.)
+
+ b) We once had an implementation for the dojo library,
+
+ If/when the time comes to add Prototype/dojo support, we simply
+ need to port:
+
+ http://code.google.com/p/jsonmessage/source/browse/trunk/lib/JSONMessage/JSONMessage.inc.js
+
+ (search that file for "dojo" and "Prototype") to this tree. That
+ code is this code's generic grandfather and they are still very
+ similar, so a port is trivial.
+
+*/
+WhAjaj.Connector.sendImpls = {
+ /**
+ This is a concrete implementation of
+ WhAjaj.Connector.prototype.sendImpl() which uses the
+ environment's native XMLHttpRequest class to send whiki
+ requests and fetch the responses.
+
+ The only argument must be a connection properties object, as
+ constructed by WhAjaj.Connector.normalizeAjaxParameters().
+
+ If window.firebug is set then window.firebug.watchXHR() is
+ called to enable monitoring of the XMLHttpRequest object.
+
+ This implementation honors the loginName and loginPassword
+ connection parameters.
+
+ Returns the XMLHttpRequest object.
+
+ This implementation requires that the 'this' object be-a
+ WhAjaj.Connector.
+
+ This implementation uses setTimeout() to implement the
+ timeout support, and thus the JS engine must provide that
+ functionality.
+ */
+ XMLHttpRequest: function(request, args)
+ {
+ var json = WhAjaj.isObject(request) ? JSON.stringify(request) : request;
+ var xhr = new XMLHttpRequest();
+ var startTime = (new Date()).getTime();
+ var timeout = args.timeout || 10000/*arbitrary!*/;
+ var hitTimeout = false;
+ var done = false;
+ var tmid /* setTimeout() ID */;
+ var whself = this;
+ //if( json ) json = json.replace(/ö/g,"\\u00f6") /* ONLY FOR A SPECIFIC TEST */;
+ //alert( 'json=\n'+json );
+ function handleTimeout()
+ {
+ hitTimeout = true;
+ if( ! done )
+ {
+ var now = (new Date()).getTime();
+ try { xhr.abort(); } catch(e) {/*ignore*/}
+ // see: http://www.w3.org/TR/XMLHttpRequest/#the-abort-method
+ args.errorMessage = "Timeout of "+timeout+"ms reached after "+(now-startTime)+"ms during AJAX request.";
+ WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] );
+ }
+ return;
+ }
+ function onStateChange()
+ { // reminder to self: apparently 'this' is-not-a XHR :/
+ if( hitTimeout )
+ { /* we're too late - the error was already triggered. */
+ return;
+ }
+
+ if( 4 == xhr.readyState )
+ {
+ done = true;
+ if( tmid )
+ {
+ clearTimeout( tmid );
+ tmid = null;
+ }
+ if( (xhr.status >= 200) && (xhr.status < 300) )
+ {
+ WhAjaj.Connector.sendHelper.onSendSuccess.apply( whself, [request, xhr.responseText, args] );
+ return;
+ }
+ else
+ {
+ if( undefined === args.errorMessage )
+ {
+ args.errorMessage = "Error sending a '"+args.method+"' AJAX request to "
+ +"["+args.url+"]: "
+ +"Status text=["+xhr.statusText+"]"
+ ;
+ WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] );
+ }
+ else { /*maybe it was was set by the timeout handler. */ }
+ return;
+ }
+ }
+ };
+
+ xhr.onreadystatechange = onStateChange;
+ if( ('undefined'!==(typeof window)) && ('firebug' in window) && ('watchXHR' in window.firebug) )
+ { /* plug in to firebug lite's XHR monitor... */
+ window.firebug.watchXHR( xhr );
+ }
+ try
+ {
+ //alert( JSON.stringify( args ));
+ function xhrOpen()
+ {
+ if( ('loginName' in args) && args.loginName )
+ {
+ xhr.open( args.method, args.url, args.asynchronous, args.loginName, args.loginPassword );
+ }
+ else
+ {
+ xhr.open( args.method, args.url, args.asynchronous );
+ }
+ }
+ if( json && ('POST' === args.method.toUpperCase()) )
+ {
+ xhrOpen();
+ xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");
+ // Google Chrome warns that it refuses to set these
+ // "unsafe" headers (his words, not mine):
+ // xhr.setRequestHeader("Content-length", json.length);
+ // xhr.setRequestHeader("Connection", "close");
+ xhr.send( json );
+ }
+ else /* assume GET */
+ {
+ xhrOpen();
+ xhr.send(null);
+ }
+ tmid = setTimeout( handleTimeout, timeout );
+ return xhr;
+ }
+ catch(e)
+ {
+ args.errorMessage = e.toString();
+ WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] );
+ return undefined;
+ }
+ }/*XMLHttpRequest()*/,
+ /**
+ This is a concrete implementation of
+ WhAjaj.Connector.prototype.sendImpl() which uses the jQuery
+ AJAX API to send requests and fetch the responses.
+
+ The first argument may be either null/false, an Object
+ containing toJSON-able data to post to the back-end, or such an
+ object in JSON string form.
+
+ The second argument must be a connection properties object, as
+ constructed by WhAjaj.Connector.normalizeAjaxParameters().
+
+ If window.firebug is set then window.firebug.watchXHR() is
+ called to enable monitoring of the XMLHttpRequest object.
+
+ This implementation honors the loginName and loginPassword
+ connection parameters.
+
+ Returns the XMLHttpRequest object.
+
+ This implementation requires that the 'this' object be-a
+ WhAjaj.Connector.
+ */
+ jQuery:function(request,args)
+ {
+ var data = request || undefined;
+ var whself = this;
+ if( data ) {
+ if('string'!==typeof data) {
+ try {
+ data = JSON.stringify(data);
+ }
+ catch(e) {
+ WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] );
+ return;
+ }
+ }
+ }
+ var ajopt = {
+ url: args.url,
+ data: data,
+ type: args.method,
+ async: args.asynchronous,
+ password: (undefined !== args.loginPassword) ? args.loginPassword : undefined,
+ username: (undefined !== args.loginName) ? args.loginName : undefined,
+ contentType: 'application/json; charset=utf-8',
+ error: function(xhr, textStatus, errorThrown)
+ {
+ //this === the options for this ajax request
+ args.errorMessage = "Error sending a '"+ajopt.type+"' request to ["+ajopt.url+"]: "
+ +"Status text=["+textStatus+"]"
+ +(errorThrown ? ("Error=["+errorThrown+"]") : "")
+ ;
+ WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] );
+ },
+ success: function(data)
+ {
+ WhAjaj.Connector.sendHelper.onSendSuccess.apply( whself, [request, data, args] );
+ },
+ /* Set dataType=text instead of json to keep jQuery from doing our carefully
+ written response handling for us.
+ */
+ dataType: 'text'
+ };
+ if( undefined !== args.timeout )
+ {
+ ajopt.timeout = args.timeout;
+ }
+ try
+ {
+ return jQuery.ajax(ajopt);
+ }
+ catch(e)
+ {
+ args.errorMessage = e.toString();
+ WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] );
+ return undefined;
+ }
+ }/*jQuery()*/,
+ /**
+ This is a concrete implementation of
+ WhAjaj.Connector.prototype.sendImpl() which uses the rhino
+ Java API to send requests and fetch the responses.
+
+ Limitations vis-a-vis the interface:
+
+ - timeouts are not supported.
+
+ - asynchronous mode is not supported because implementing it
+ requires the ability to kill a running thread (which is deprecated
+ in the Java API).
+
+ TODOs:
+
+ - add socket timeouts.
+
+ - support HTTP proxy.
+
+ The Java APIs support this, it just hasn't been added here yet.
+ */
+ rhino:function(request,args)
+ {
+ var self = this;
+ var data = request || undefined;
+ if( data ) {
+ if('string'!==typeof data) {
+ try {
+ data = JSON.stringify(data);
+ }
+ catch(e) {
+ WhAjaj.Connector.sendHelper.onSendError.apply( self, [request, args] );
+ return;
+ }
+ }
+ }
+ var url;
+ var con;
+ var IO = new JavaImporter(java.io);
+ var wr;
+ var rd, ln, json = [];
+ function setIncomingCookies(list){
+ if(!list || !list.length) return;
+ if( !self.cookies ) self.cookies = {};
+ var k, v, i;
+ for( i = 0; i < list.length; ++i ){
+ v = list[i].split('=',2);
+ k = decodeURIComponent(v[0])
+ v = v[0] ? decodeURIComponent(v[0].split(';',2)[0]) : null;
+ //print("RECEIVED COOKIE: "+k+"="+v);
+ if(!v) {
+ delete self.cookies[k];
+ continue;
+ }else{
+ self.cookies[k] = v;
+ }
+ }
+ };
+ function setOutboundCookies(conn){
+ if(!self.cookies) return;
+ var k, v;
+ for( k in self.cookies ){
+ if(!self.cookies.hasOwnProperty(k)) continue /*kludge for broken JS libs*/;
+ v = self.cookies[k];
+ conn.addRequestProperty("Cookie", encodeURIComponent(k)+'='+encodeURIComponent(v));
+ //print("SENDING COOKIE: "+k+"="+v);
+ }
+ };
+ try{
+ url = new java.net.URL( args.url )
+ con = url.openConnection(/*FIXME: add proxy support!*/);
+ con.setRequestProperty("Accept-Charset","utf-8");
+ setOutboundCookies(con);
+ if(data){
+ con.setRequestProperty("Content-Type","application/json; charset=utf-8");
+ con.setDoOutput( true );
+ wr = new IO.OutputStreamWriter(con.getOutputStream())
+ wr.write(data);
+ wr.flush();
+ wr.close();
+ wr = null;
+ //print("POSTED: "+data);
+ }
+ rd = new IO.BufferedReader(new IO.InputStreamReader(con.getInputStream()));
+ //var skippedHeaders = false;
+ while ((line = rd.readLine()) !== null) {
+ //print("LINE: "+line);
+ //if(!line.length && !skippedHeaders){
+ // skippedHeaders = true;
+ // json = [];
+ // continue;
+ //}
+ json.push(line);
+ }
+ setIncomingCookies(con.getHeaderFields().get("Set-Cookie"));
+ }catch(e){
+ args.errorMessage = e.toString();
+ WhAjaj.Connector.sendHelper.onSendError.apply( self, [request, args] );
+ return undefined;
+ }
+ try { if(wr) wr.close(); } catch(e) { /*ignore*/}
+ try { if(rd) rd.close(); } catch(e) { /*ignore*/}
+ json = json.join('');
+ //print("READ IN JSON: "+json);
+ WhAjaj.Connector.sendHelper.onSendSuccess.apply( self, [request, json, args] );
+ }/*rhino*/
+};
+
+/**
+ An internal function which takes an object containing properties
+ for a WhAjaj.Connector network request. This function creates a new
+ object containing a superset of the properties from:
+
+ a) opt
+ b) this.options
+ c) WhAjaj.Connector.options.ajax
+
+ in that order, using the first one it finds.
+
+ All non-function properties are _deeply_ copied via JSON cloning
+ in order to prevent accidental "cross-request pollenation" (been
+ there, done that). Functions cannot be cloned and are simply
+ copied by reference.
+
+ This function throws if JSON-copying one of the options fails
+ (e.g. due to cyclic data structures).
+
+ Reminder to self: this function does not "normalize" opt.urlParam
+ by encoding it into opt.url, mainly for historical reasons, but
+ also because that behaviour was specifically undesirable in this
+ code's genetic father.
+*/
+WhAjaj.Connector.prototype.normalizeAjaxParameters = function (opt)
+{
+ var rc = {};
+ function merge(k,v)
+ {
+ if( rc.hasOwnProperty(k) ) return;
+ else if( WhAjaj.isFunction(v) ) {}
+ else if( WhAjaj.isObject(v) ) v = JSON.parse( JSON.stringify(v) );
+ rc[k]=v;
+ }
+ function cp(obj) {
+ if( ! WhAjaj.isObject(obj) ) return;
+ var k;
+ for( k in obj ) {
+ if( ! obj.hasOwnProperty(k) ) continue /* i will always hate the Prototype designers for this. */;
+ merge(k, obj[k]);
+ }
+ }
+ cp( opt );
+ cp( this.options );
+ cp( WhAjaj.Connector.options.ajax );
+ // no, not here: rc.url = WhAjaj.Connector.sendHelper.normalizeURL(rc);
+ return rc;
+};
+
+/**
+ This is the generic interface for making calls to a back-end
+ JSON-producing request handler. It is a simple wrapper around
+ WhAjaj.Connector.prototype.sendImpl(), which just normalizes the
+ connection options for sendImpl() and makes sure that
+ opt.beforeSend() is (possibly) called.
+
+ The request parameter must either be false/null/empty or a
+ fully-populated JSON-able request object (which will be sent as
+ unencoded application/json text), depending on the type of
+ request being made. It is never semantically legal (in this API)
+ for request to be a string/number/true/array value. As a rule,
+ only POST requests use the request data. GET requests should
+ encode their data in opt.url or opt.urlParam (see below).
+
+ opt must contain the network-related parameters for the request.
+ Paramters _not_ set in opt are pulled from this.options or
+ WhAjaj.Connector.options.ajax (in that order, using the first
+ value it finds). Thus the set of connection-level options used
+ for the request are a superset of those various sources.
+
+ The "normalized" (or "superimposed") opt object's URL may be
+ modified before the request is sent, as follows:
+
+ if opt.urlParam is a string then it is assumed to be properly
+ URL-encoded parameters and is appended to the opt.url. If it is
+ an Object then it is assumed to be a one-dimensional set of
+ key/value pairs with simple values (numbers, strings, booleans,
+ null, and NOT objects/arrays). The keys/values are URL-encoded
+ and appended to the URL.
+
+ The beforeSend() callback (see below) can modify the options
+ object before the request attempt is made.
+
+ The callbacks in the normalized opt object will be triggered as
+ follows (if they are set to Function values):
+
+ - beforeSend(request,opt) will be called before any network
+ processing starts. If beforeSend() throws then no other
+ callbacks are triggered and this function propagates the
+ exception. This function is passed normalized connection options
+ as its second parameter, and changes this function makes to that
+ object _will_ be used for the pending connection attempt.
+
+ - onError(request,opt) will be called if a connection to the
+ back-end cannot be established. It will be passed the original
+ request object (which might be null, depending on the request
+ type) and the normalized options object. In the error case, the
+ opt object passed to onError() "should" have a property called
+ "errorMessage" which contains a description of the problem.
+
+ - onError(request,opt) will also be called if connection
+ succeeds but the response is not JSON data.
+
+ - onResponse(response,request) will be called if the response
+ returns JSON data. That data might hold an error response code -
+ clients need to check for that. It is passed the response object
+ (a plain object) and the original request object.
+
+ - afterSend(request,opt) will be called directly after the
+ AJAX request is finished, before onError() or onResonse() are
+ called. Possible TODO: we explicitly do NOT pass the response to
+ this function in order to keep the line between the responsibilities
+ of the various callback clear (otherwise this could be used the same
+ as onResponse()). In practice it would sometimes be useful have the
+ response passed to this function, mainly for logging/debugging
+ purposes.
+
+ The return value from this function is meaningless because
+ AJAX operations tend to take place asynchronously.
+
+*/
+WhAjaj.Connector.prototype.sendRequest = function(request,opt)
+{
+ if( !WhAjaj.isFunction(this.sendImpl) )
+ {
+ throw new Error("This object has no sendImpl() member function! I don't know how to send the request!");
+ }
+ var ex = false;
+ var av = Array.prototype.slice.apply( arguments, [0] );
+
+ /**
+ FIXME: how to handle the error, vis-a-vis- the callbacks, if
+ normalizeAjaxParameters() throws? It can throw if
+ (de)JSON-izing fails.
+ */
+ var norm = this.normalizeAjaxParameters( WhAjaj.isObject(opt) ? opt : {} );
+ norm.url = WhAjaj.Connector.sendHelper.normalizeURL(norm);
+ if( ! request ) norm.method = 'GET';
+ var cb = this.callbacks || {};
+ if( this.callbacks && WhAjaj.isFunction(this.callbacks.beforeSend) ) {
+ this.callbacks.beforeSend( request, norm );
+ }
+ if( WhAjaj.isFunction(norm.beforeSend) ){
+ norm.beforeSend( request, norm );
+ }
+ //alert( WhAjaj.stringify(request)+'\n'+WhAjaj.stringify(norm));
+ try { this.sendImpl( request, norm ); }
+ catch(e) { ex = e; }
+ if(ex) throw ex;
+};
+
+/**
+ sendImpl() holds a concrete back-end connection implementation. It
+ can be replaced with a custom implementation if one follows the rules
+ described throughout this API. See WhAjaj.Connector.sendImpls for
+ the concrete implementations included with this API.
+*/
+//WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.XMLHttpRequest;
+//WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.rhino;
+//WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.jQuery;
+
+if( 'undefined' !== typeof jQuery ){
+ WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.jQuery;
+}
+else {
+ WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.XMLHttpRequest;
+}
ADDED ajax/wiki-editor.html
Index: ajax/wiki-editor.html
==================================================================
--- /dev/null
+++ ajax/wiki-editor.html
@@ -0,0 +1,382 @@
+
+
+
+
+ Fossil/JSON Wiki Editor Prototype
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
ADDED src/Makefile
Index: src/Makefile
==================================================================
--- /dev/null
+++ src/Makefile
@@ -0,0 +1,2 @@
+all:
+ $(MAKE) -C ..
Index: src/blob.c
==================================================================
--- src/blob.c
+++ src/blob.c
@@ -1039,5 +1039,32 @@
return;
}
}
blob_append(pBlob, zIn, -1);
}
+
+/*
+** A read(2)-like impl for the Blob class. Reads (copies) up to nLen
+** bytes from pIn, starting at position pIn->iCursor, and copies them
+** to pDest (which must be valid memory at least nLen bytes long).
+**
+** Returns the number of bytes read/copied, which may be less than
+** nLen (if end-of-blob is encountered).
+**
+** Updates pIn's cursor.
+**
+** Returns 0 if pIn contains no data.
+*/
+unsigned int blob_read(Blob *pIn, void * pDest, unsigned int nLen ){
+ if( !pIn->aData || (pIn->iCursor >= pIn->nUsed) ){
+ return 0;
+ } else if( (pIn->iCursor + nLen) > (unsigned int)pIn->nUsed ){
+ nLen = (unsigned int) (pIn->nUsed - pIn->iCursor);
+ }
+ assert( pIn->nUsed > pIn->iCursor );
+ assert( (pIn->iCursor+nLen) <= pIn->nUsed );
+ if( nLen ){
+ memcpy( pDest, pIn->aData, nLen );
+ pIn->iCursor += nLen;
+ }
+ return nLen;
+}
Index: src/branch.c
==================================================================
--- src/branch.c
+++ src/branch.c
@@ -178,14 +178,18 @@
/* Do an autosync push, if requested */
if( !isPrivate ) autosync(AUTOSYNC_PUSH);
}
/*
-** Prepare a query that will list all branches.
+** Prepare a query that will list branches.
+**
+** If (which<0) then the query pulls only closed branches. If
+** (which>0) then the query pulls all (closed and opened)
+** branches. Else the query pulls currently-opened branches.
*/
-static void prepareBranchQuery(Stmt *pQuery, int showAll, int showClosed){
- if( showClosed ){
+void branch_prepare_list_query(Stmt *pQuery, int which ){
+ if( which < 0 ){
db_prepare(pQuery,
"SELECT value FROM tagxref"
" WHERE tagid=%d AND value NOT NULL "
"EXCEPT "
"SELECT value FROM tagxref"
@@ -193,11 +197,11 @@
" AND rid IN leaf"
" AND NOT %z"
" ORDER BY value COLLATE nocase /*sort*/",
TAG_BRANCH, TAG_BRANCH, leaf_is_closed_sql("tagxref.rid")
);
- }else if( showAll ){
+ }else if( which>0 ){
db_prepare(pQuery,
"SELECT DISTINCT value FROM tagxref"
" WHERE tagid=%d AND value NOT NULL"
" AND rid IN leaf"
" ORDER BY value COLLATE nocase /*sort*/",
@@ -260,11 +264,11 @@
if( g.localOpen ){
vid = db_lget_int("checkout", 0);
zCurrent = db_text(0, "SELECT value FROM tagxref"
" WHERE rid=%d AND tagid=%d", vid, TAG_BRANCH);
}
- prepareBranchQuery(&q, showAll, showClosed);
+ branch_prepare_list_query(&q, showAll?1:(showClosed?-1:0));
while( db_step(&q)==SQLITE_ROW ){
const char *zBr = db_column_text(&q, 0);
int isCur = zCurrent!=0 && fossil_strcmp(zCurrent,zBr)==0;
fossil_print("%s%s\n", (isCur ? "* " : " "), zBr);
}
@@ -327,11 +331,11 @@
@ Closed branches are fixed and do not change (unless they are first
@ reopened)
@
style_sidebox_end();
- prepareBranchQuery(&q, showAll, showClosed);
+ branch_prepare_list_query(&q, showAll?1:(showClosed?-1:0));
cnt = 0;
while( db_step(&q)==SQLITE_ROW ){
const char *zBr = db_column_text(&q, 0);
if( cnt==0 ){
if( colorTest ){
Index: src/captcha.c
==================================================================
--- src/captcha.c
+++ src/captcha.c
@@ -412,12 +412,14 @@
return x;
}
/*
** Translate a captcha seed value into the captcha password string.
+** The returned string is static and overwritten on each call to
+** this function.
*/
-char *captcha_decode(unsigned int seed){
+char const *captcha_decode(unsigned int seed){
const char *zSecret;
const char *z;
Blob b;
static char zRes[20];
Index: src/cgi.c
==================================================================
--- src/cgi.c
+++ src/cgi.c
@@ -508,10 +508,11 @@
zValue = "";
}
if( fossil_islower(zName[0]) ){
cgi_set_parameter_nocopy(zName, zValue);
}
+ json_setenv( zName, cson_value_new_string(zValue,strlen(zValue)) );
}
}
/*
** *pz is a string that consists of multiple lines of text. This
@@ -680,10 +681,86 @@
}
}
}
}
}
+
+
+/*
+** Internal helper for cson_data_source_FILE_n().
+*/
+typedef struct CgiPostReadState_ {
+ FILE * fh;
+ unsigned int len;
+ unsigned int pos;
+} CgiPostReadState;
+
+/*
+** cson_data_source_f() impl which reads only up to
+** a specified amount of data from its input FILE.
+** state MUST be a full populated (CgiPostReadState*).
+*/
+static int cson_data_source_FILE_n( void * state,
+ void * dest,
+ unsigned int * n ){
+ if( ! state || !dest || !n ) return cson_rc.ArgError;
+ else {
+ CgiPostReadState * st = (CgiPostReadState *)state;
+ if( st->pos >= st->len ){
+ *n = 0;
+ return 0;
+ } else if( !*n || ((st->pos + *n) > st->len) ){
+ return cson_rc.RangeError;
+ }else{
+ unsigned int rsz = (unsigned int)fread( dest, 1, *n, st->fh );
+ if( ! rsz ){
+ *n = rsz;
+ return feof(st->fh) ? 0 : cson_rc.IOError;
+ }else{
+ *n = rsz;
+ st->pos += *n;
+ return 0;
+ }
+ }
+ }
+}
+
+/*
+** Reads a JSON object from the first contentLen bytes of zIn. On
+** g.json.post is updated to hold the content. On error a
+** FSL_JSON_E_INVALID_REQUEST response is output and fossil_exit() is
+** called (in HTTP mode exit code 0 is used).
+**
+** If contentLen is 0 then the whole file is read.
+*/
+void cgi_parse_POST_JSON( FILE * zIn, unsigned int contentLen ){
+ cson_value * jv = NULL;
+ int rc;
+ CgiPostReadState state;
+ state.fh = zIn;
+ state.len = contentLen;
+ state.pos = 0;
+ rc = cson_parse( &jv,
+ contentLen ? cson_data_source_FILE_n : cson_data_source_FILE,
+ contentLen ? (void *)&state : (void *)zIn, NULL, NULL );
+ if(rc){
+ goto invalidRequest;
+ }else{
+ json_gc_add( "POST.JSON", jv );
+ g.json.post.v = jv;
+ g.json.post.o = cson_value_get_object( jv );
+ if( !g.json.post.o ){ /* we don't support non-Object (Array) requests */
+ goto invalidRequest;
+ }
+ }
+ return;
+ invalidRequest:
+ cgi_set_content_type(json_guess_content_type());
+ json_err( FSL_JSON_E_INVALID_REQUEST, NULL, 1 );
+ fossil_exit( g.isHTTP ? 0 : 1);
+}
+
/*
** Initialize the query parameter database. Information is pulled from
** the QUERY_STRING environment variable (if it exists), from standard
** input if there is POST data, and from HTTP_COOKIE.
@@ -690,19 +767,30 @@
*/
void cgi_init(void){
char *z;
const char *zType;
int len;
+ json_main_bootstrap();
+ g.isHTTP = 1;
cgi_destination(CGI_BODY);
+
+ z = (char*)P("HTTP_COOKIE");
+ if( z ){
+ z = mprintf("%s",z);
+ add_param_list(z, ';');
+ }
+
z = (char*)P("QUERY_STRING");
if( z ){
z = mprintf("%s",z);
add_param_list(z, '&');
}
z = (char*)P("REMOTE_ADDR");
- if( z ) g.zIpAddr = mprintf("%s", z);
+ if( z ){
+ g.zIpAddr = mprintf("%s", z);
+ }
len = atoi(PD("CONTENT_LENGTH", "0"));
g.zContentType = zType = P("CONTENT_TYPE");
if( len>0 && zType ){
blob_zero(&g.cgiIn);
@@ -721,18 +809,34 @@
blob_uncompress(&g.cgiIn, &g.cgiIn);
}else if( fossil_strcmp(zType, "application/x-fossil-debug")==0 ){
blob_read_from_channel(&g.cgiIn, g.httpIn, len);
}else if( fossil_strcmp(zType, "application/x-fossil-uncompressed")==0 ){
blob_read_from_channel(&g.cgiIn, g.httpIn, len);
+ }else if( fossil_strcmp(zType, "application/json")
+ || fossil_strcmp(zType,"text/plain")/*assume this MIGHT be JSON*/
+ || fossil_strcmp(zType,"application/javascript")){
+ g.json.isJsonMode = 1;
+ cgi_parse_POST_JSON(g.httpIn, (unsigned int)len);
+ /* FIXMEs:
+
+ - See if fossil really needs g.cgiIn to be set for this purpose
+ (i don't think it does). If it does then fill g.cgiIn and
+ refactor to parse the JSON from there.
+
+ - After parsing POST JSON, copy the "first layer" of keys/values
+ to cgi_setenv(), honoring the upper-case distinction used
+ in add_param_list(). However...
+
+ - If we do that then we might get a disconnect in precedence of
+ GET/POST arguments. i prefer for GET entries to take precedence
+ over like-named POST entries, but in order for that to happen we
+ need to process QUERY_STRING _after_ reading the POST data.
+ */
+ cgi_set_content_type(json_guess_content_type());
}
}
- z = (char*)P("HTTP_COOKIE");
- if( z ){
- z = mprintf("%s",z);
- add_param_list(z, ';');
- }
}
/*
** This is the comparison function used to sort the aParamQP[] array of
** query parameters and cookies.
@@ -942,20 +1046,30 @@
** Panic and die while processing a webpage.
*/
NORETURN void cgi_panic(const char *zFormat, ...){
va_list ap;
cgi_reset_content();
- cgi_set_status(500, "Internal Server Error");
- cgi_printf(
- "
\n"
+ ""
+ );
+ va_start(ap, zFormat);
+ vxprintf(pContent,zFormat,ap);
+ va_end(ap);
+ cgi_reply();
+ fossil_exit(1);
+ }
}
/*
** Remove the first space-delimited token from a string and return
** a pointer to it. Add a NULL to the string to terminate the token.
@@ -992,11 +1106,10 @@
char *z, *zToken;
int i;
struct sockaddr_in remoteName;
socklen_t size = sizeof(struct sockaddr_in);
char zLine[2000]; /* A single line of input. */
-
g.fullHttpReply = 1;
if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
malformed_request();
}
zToken = extract_token(zLine, &z);
ADDED src/cson_amalgamation.c
Index: src/cson_amalgamation.c
==================================================================
--- /dev/null
+++ src/cson_amalgamation.c
@@ -0,0 +1,5513 @@
+/* auto-generated! Do not edit! */
+#include "cson_amalgamation.h"
+/* begin file parser/JSON_parser.h */
+/* See JSON_parser.c for copyright information and licensing. */
+
+#ifndef JSON_PARSER_H
+#define JSON_PARSER_H
+
+/* JSON_parser.h */
+
+
+#include
+
+/* Windows DLL stuff */
+#ifdef JSON_PARSER_DLL
+# ifdef _MSC_VER
+# ifdef JSON_PARSER_DLL_EXPORTS
+# define JSON_PARSER_DLL_API __declspec(dllexport)
+# else
+# define JSON_PARSER_DLL_API __declspec(dllimport)
+# endif
+# else
+# define JSON_PARSER_DLL_API
+# endif
+#else
+# define JSON_PARSER_DLL_API
+#endif
+
+/* Determine the integer type use to parse non-floating point numbers */
+#if __STDC_VERSION__ >= 199901L || HAVE_LONG_LONG == 1
+typedef long long JSON_int_t;
+#define JSON_PARSER_INTEGER_SSCANF_TOKEN "%lld"
+#define JSON_PARSER_INTEGER_SPRINTF_TOKEN "%lld"
+#else
+typedef long JSON_int_t;
+#define JSON_PARSER_INTEGER_SSCANF_TOKEN "%ld"
+#define JSON_PARSER_INTEGER_SPRINTF_TOKEN "%ld"
+#endif
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum
+{
+ JSON_E_NONE = 0,
+ JSON_E_INVALID_CHAR,
+ JSON_E_INVALID_KEYWORD,
+ JSON_E_INVALID_ESCAPE_SEQUENCE,
+ JSON_E_INVALID_UNICODE_SEQUENCE,
+ JSON_E_INVALID_NUMBER,
+ JSON_E_NESTING_DEPTH_REACHED,
+ JSON_E_UNBALANCED_COLLECTION,
+ JSON_E_EXPECTED_KEY,
+ JSON_E_EXPECTED_COLON,
+ JSON_E_OUT_OF_MEMORY
+} JSON_error;
+
+typedef enum
+{
+ JSON_T_NONE = 0,
+ JSON_T_ARRAY_BEGIN,
+ JSON_T_ARRAY_END,
+ JSON_T_OBJECT_BEGIN,
+ JSON_T_OBJECT_END,
+ JSON_T_INTEGER,
+ JSON_T_FLOAT,
+ JSON_T_NULL,
+ JSON_T_TRUE,
+ JSON_T_FALSE,
+ JSON_T_STRING,
+ JSON_T_KEY,
+ JSON_T_MAX
+} JSON_type;
+
+typedef struct JSON_value_struct {
+ union {
+ JSON_int_t integer_value;
+
+ double float_value;
+
+ struct {
+ const char* value;
+ size_t length;
+ } str;
+ } vu;
+} JSON_value;
+
+typedef struct JSON_parser_struct* JSON_parser;
+
+/*! \brief JSON parser callback
+
+ \param ctx The pointer passed to new_JSON_parser.
+ \param type An element of JSON_type but not JSON_T_NONE.
+ \param value A representation of the parsed value. This parameter is NULL for
+ JSON_T_ARRAY_BEGIN, JSON_T_ARRAY_END, JSON_T_OBJECT_BEGIN, JSON_T_OBJECT_END,
+ JSON_T_NULL, JSON_T_TRUE, and JSON_T_FALSE. String values are always returned
+ as zero-terminated C strings.
+
+ \return Non-zero if parsing should continue, else zero.
+*/
+typedef int (*JSON_parser_callback)(void* ctx, int type, const struct JSON_value_struct* value);
+
+
+/**
+ A typedef for allocator functions semantically compatible with malloc().
+*/
+typedef void* (*JSON_malloc_t)(size_t n);
+/**
+ A typedef for deallocator functions semantically compatible with free().
+*/
+typedef void (*JSON_free_t)(void* mem);
+
+/*! \brief The structure used to configure a JSON parser object
+*/
+typedef struct {
+ /** Pointer to a callback, called when the parser has something to tell
+ the user. This parameter may be NULL. In this case the input is
+ merely checked for validity.
+ */
+ JSON_parser_callback callback;
+ /**
+ Callback context - client-specified data to pass to the
+ callback function. This parameter may be NULL.
+ */
+ void* callback_ctx;
+ /** Specifies the levels of nested JSON to allow. Negative numbers yield unlimited nesting.
+ If negative, the parser can parse arbitrary levels of JSON, otherwise
+ the depth is the limit.
+ */
+ int depth;
+ /**
+ To allow C style comments in JSON, set to non-zero.
+ */
+ int allow_comments;
+ /**
+ To decode floating point numbers manually set this parameter to
+ non-zero.
+ */
+ int handle_floats_manually;
+ /**
+ The memory allocation routine, which must be semantically
+ compatible with malloc(3). If set to NULL, malloc(3) is used.
+
+ If this is set to a non-NULL value then the 'free' member MUST be
+ set to the proper deallocation counterpart for this function.
+ Failure to do so results in undefined behaviour at deallocation
+ time.
+ */
+ JSON_malloc_t malloc;
+ /**
+ The memory deallocation routine, which must be semantically
+ compatible with free(3). If set to NULL, free(3) is used.
+
+ If this is set to a non-NULL value then the 'alloc' member MUST be
+ set to the proper allocation counterpart for this function.
+ Failure to do so results in undefined behaviour at deallocation
+ time.
+ */
+ JSON_free_t free;
+} JSON_config;
+
+/*! \brief Initializes the JSON parser configuration structure to default values.
+
+ The default configuration is
+ - 127 levels of nested JSON (depends on JSON_PARSER_STACK_SIZE, see json_parser.c)
+ - no parsing, just checking for JSON syntax
+ - no comments
+ - Uses realloc() for memory de/allocation.
+
+ \param config. Used to configure the parser.
+*/
+JSON_PARSER_DLL_API void init_JSON_config(JSON_config * config);
+
+/*! \brief Create a JSON parser object
+
+ \param config. Used to configure the parser. Set to NULL to use
+ the default configuration. See init_JSON_config. Its contents are
+ copied by this function, so it need not outlive the returned
+ object.
+
+ \return The parser object, which is owned by the caller and must eventually
+ be freed by calling delete_JSON_parser().
+*/
+JSON_PARSER_DLL_API JSON_parser new_JSON_parser(JSON_config const* config);
+
+/*! \brief Destroy a previously created JSON parser object. */
+JSON_PARSER_DLL_API void delete_JSON_parser(JSON_parser jc);
+
+/*! \brief Parse a character.
+
+ \return Non-zero, if all characters passed to this function are part of are valid JSON.
+*/
+JSON_PARSER_DLL_API int JSON_parser_char(JSON_parser jc, int next_char);
+
+/*! \brief Finalize parsing.
+
+ Call this method once after all input characters have been consumed.
+
+ \return Non-zero, if all parsed characters are valid JSON, zero otherwise.
+*/
+JSON_PARSER_DLL_API int JSON_parser_done(JSON_parser jc);
+
+/*! \brief Determine if a given string is valid JSON white space
+
+ \return Non-zero if the string is valid, zero otherwise.
+*/
+JSON_PARSER_DLL_API int JSON_parser_is_legal_white_space_string(const char* s);
+
+/*! \brief Gets the last error that occurred during the use of JSON_parser.
+
+ \return A value from the JSON_error enum.
+*/
+JSON_PARSER_DLL_API int JSON_parser_get_last_error(JSON_parser jc);
+
+/*! \brief Re-sets the parser to prepare it for another parse run.
+
+ \return True (non-zero) on success, 0 on error (e.g. !jc).
+*/
+JSON_PARSER_DLL_API int JSON_parser_reset(JSON_parser jc);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* JSON_PARSER_H */
+/* end file parser/JSON_parser.h */
+/* begin file parser/JSON_parser.c */
+/*
+Copyright (c) 2005 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+/*
+ Callbacks, comments, Unicode handling by Jean Gressmann (jean@0x42.de), 2007-2010.
+
+
+ Changelog:
+ 2010-11-25
+ Support for custom memory allocation (sgbeal@googlemail.com).
+
+ 2010-05-07
+ Added error handling for memory allocation failure (sgbeal@googlemail.com).
+ Added diagnosis errors for invalid JSON.
+
+ 2010-03-25
+ Fixed buffer overrun in grow_parse_buffer & cleaned up code.
+
+ 2009-10-19
+ Replaced long double in JSON_value_struct with double after reports
+ of strtold being broken on some platforms (charles@transmissionbt.com).
+
+ 2009-05-17
+ Incorporated benrudiak@googlemail.com fix for UTF16 decoding.
+
+ 2009-05-14
+ Fixed float parsing bug related to a locale being set that didn't
+ use '.' as decimal point character (charles@transmissionbt.com).
+
+ 2008-10-14
+ Renamed states.IN to states.IT to avoid name clash which IN macro
+ defined in windef.h (alexey.pelykh@gmail.com)
+
+ 2008-07-19
+ Removed some duplicate code & debugging variable (charles@transmissionbt.com)
+
+ 2008-05-28
+ Made JSON_value structure ansi C compliant. This bug was report by
+ trisk@acm.jhu.edu
+
+ 2008-05-20
+ Fixed bug reported by charles@transmissionbt.com where the switching
+ from static to dynamic parse buffer did not copy the static parse
+ buffer's content.
+*/
+
+
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+#ifdef _MSC_VER
+# if _MSC_VER >= 1400 /* Visual Studio 2005 and up */
+# pragma warning(disable:4996) /* unsecure sscanf */
+# pragma warning(disable:4127) /* conditional expression is constant */
+# endif
+#endif
+
+
+#define true 1
+#define false 0
+#define __ -1 /* the universal error code */
+
+/* values chosen so that the object size is approx equal to one page (4K) */
+#ifndef JSON_PARSER_STACK_SIZE
+# define JSON_PARSER_STACK_SIZE 128
+#endif
+
+#ifndef JSON_PARSER_PARSE_BUFFER_SIZE
+# define JSON_PARSER_PARSE_BUFFER_SIZE 3500
+#endif
+
+typedef void* (*JSON_debug_malloc_t)(size_t bytes, const char* reason);
+
+#ifdef JSON_PARSER_DEBUG_MALLOC
+# define JSON_parser_malloc(func, bytes, reason) ((JSON_debug_malloc_t)func)(bytes, reason)
+#else
+# define JSON_parser_malloc(func, bytes, reason) func(bytes)
+#endif
+
+typedef unsigned short UTF16;
+
+struct JSON_parser_struct {
+ JSON_parser_callback callback;
+ void* ctx;
+ signed char state, before_comment_state, type, escaped, comment, allow_comments, handle_floats_manually, error;
+ char decimal_point;
+ UTF16 utf16_high_surrogate;
+ int current_char;
+ int depth;
+ int top;
+ int stack_capacity;
+ signed char* stack;
+ char* parse_buffer;
+ size_t parse_buffer_capacity;
+ size_t parse_buffer_count;
+ signed char static_stack[JSON_PARSER_STACK_SIZE];
+ char static_parse_buffer[JSON_PARSER_PARSE_BUFFER_SIZE];
+ JSON_malloc_t malloc;
+ JSON_free_t free;
+};
+
+#define COUNTOF(x) (sizeof(x)/sizeof(x[0]))
+
+/*
+ Characters are mapped into these character classes. This allows for
+ a significant reduction in the size of the state transition table.
+*/
+
+
+
+enum classes {
+ C_SPACE, /* space */
+ C_WHITE, /* other whitespace */
+ C_LCURB, /* { */
+ C_RCURB, /* } */
+ C_LSQRB, /* [ */
+ C_RSQRB, /* ] */
+ C_COLON, /* : */
+ C_COMMA, /* , */
+ C_QUOTE, /* " */
+ C_BACKS, /* \ */
+ C_SLASH, /* / */
+ C_PLUS, /* + */
+ C_MINUS, /* - */
+ C_POINT, /* . */
+ C_ZERO , /* 0 */
+ C_DIGIT, /* 123456789 */
+ C_LOW_A, /* a */
+ C_LOW_B, /* b */
+ C_LOW_C, /* c */
+ C_LOW_D, /* d */
+ C_LOW_E, /* e */
+ C_LOW_F, /* f */
+ C_LOW_L, /* l */
+ C_LOW_N, /* n */
+ C_LOW_R, /* r */
+ C_LOW_S, /* s */
+ C_LOW_T, /* t */
+ C_LOW_U, /* u */
+ C_ABCDF, /* ABCDF */
+ C_E, /* E */
+ C_ETC, /* everything else */
+ C_STAR, /* * */
+ NR_CLASSES
+};
+
+static const signed char ascii_class[128] = {
+/*
+ This array maps the 128 ASCII characters into character classes.
+ The remaining Unicode characters should be mapped to C_ETC.
+ Non-whitespace control characters are errors.
+*/
+ __, __, __, __, __, __, __, __,
+ __, C_WHITE, C_WHITE, __, __, C_WHITE, __, __,
+ __, __, __, __, __, __, __, __,
+ __, __, __, __, __, __, __, __,
+
+ C_SPACE, C_ETC, C_QUOTE, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,
+ C_ETC, C_ETC, C_STAR, C_PLUS, C_COMMA, C_MINUS, C_POINT, C_SLASH,
+ C_ZERO, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT,
+ C_DIGIT, C_DIGIT, C_COLON, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,
+
+ C_ETC, C_ABCDF, C_ABCDF, C_ABCDF, C_ABCDF, C_E, C_ABCDF, C_ETC,
+ C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,
+ C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,
+ C_ETC, C_ETC, C_ETC, C_LSQRB, C_BACKS, C_RSQRB, C_ETC, C_ETC,
+
+ C_ETC, C_LOW_A, C_LOW_B, C_LOW_C, C_LOW_D, C_LOW_E, C_LOW_F, C_ETC,
+ C_ETC, C_ETC, C_ETC, C_ETC, C_LOW_L, C_ETC, C_LOW_N, C_ETC,
+ C_ETC, C_ETC, C_LOW_R, C_LOW_S, C_LOW_T, C_LOW_U, C_ETC, C_ETC,
+ C_ETC, C_ETC, C_ETC, C_LCURB, C_ETC, C_RCURB, C_ETC, C_ETC
+};
+
+
+/*
+ The state codes.
+*/
+enum states {
+ GO, /* start */
+ OK, /* ok */
+ OB, /* object */
+ KE, /* key */
+ CO, /* colon */
+ VA, /* value */
+ AR, /* array */
+ ST, /* string */
+ ES, /* escape */
+ U1, /* u1 */
+ U2, /* u2 */
+ U3, /* u3 */
+ U4, /* u4 */
+ MI, /* minus */
+ ZE, /* zero */
+ IT, /* integer */
+ FR, /* fraction */
+ E1, /* e */
+ E2, /* ex */
+ E3, /* exp */
+ T1, /* tr */
+ T2, /* tru */
+ T3, /* true */
+ F1, /* fa */
+ F2, /* fal */
+ F3, /* fals */
+ F4, /* false */
+ N1, /* nu */
+ N2, /* nul */
+ N3, /* null */
+ C1, /* / */
+ C2, /* / * */
+ C3, /* * */
+ FX, /* *.* *eE* */
+ D1, /* second UTF-16 character decoding started by \ */
+ D2, /* second UTF-16 character proceeded by u */
+ NR_STATES
+};
+
+enum actions
+{
+ CB = -10, /* comment begin */
+ CE = -11, /* comment end */
+ FA = -12, /* false */
+ TR = -13, /* false */
+ NU = -14, /* null */
+ DE = -15, /* double detected by exponent e E */
+ DF = -16, /* double detected by fraction . */
+ SB = -17, /* string begin */
+ MX = -18, /* integer detected by minus */
+ ZX = -19, /* integer detected by zero */
+ IX = -20, /* integer detected by 1-9 */
+ EX = -21, /* next char is escaped */
+ UC = -22 /* Unicode character read */
+};
+
+
+static const signed char state_transition_table[NR_STATES][NR_CLASSES] = {
+/*
+ The state transition table takes the current state and the current symbol,
+ and returns either a new state or an action. An action is represented as a
+ negative number. A JSON text is accepted if at the end of the text the
+ state is OK and if the mode is MODE_DONE.
+
+ white 1-9 ABCDF etc
+ space | { } [ ] : , " \ / + - . 0 | a b c d e f l n r s t u | E | * */
+/*start GO*/ {GO,GO,-6,__,-5,__,__,__,__,__,CB,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
+/*ok OK*/ {OK,OK,__,-8,__,-7,__,-3,__,__,CB,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
+/*object OB*/ {OB,OB,__,-9,__,__,__,__,SB,__,CB,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
+/*key KE*/ {KE,KE,__,__,__,__,__,__,SB,__,CB,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
+/*colon CO*/ {CO,CO,__,__,__,__,-2,__,__,__,CB,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
+/*value VA*/ {VA,VA,-6,__,-5,__,__,__,SB,__,CB,__,MX,__,ZX,IX,__,__,__,__,__,FA,__,NU,__,__,TR,__,__,__,__,__},
+/*array AR*/ {AR,AR,-6,__,-5,-7,__,__,SB,__,CB,__,MX,__,ZX,IX,__,__,__,__,__,FA,__,NU,__,__,TR,__,__,__,__,__},
+/*string ST*/ {ST,__,ST,ST,ST,ST,ST,ST,-4,EX,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST},
+/*escape ES*/ {__,__,__,__,__,__,__,__,ST,ST,ST,__,__,__,__,__,__,ST,__,__,__,ST,__,ST,ST,__,ST,U1,__,__,__,__},
+/*u1 U1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,U2,U2,U2,U2,U2,U2,U2,U2,__,__,__,__,__,__,U2,U2,__,__},
+/*u2 U2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,U3,U3,U3,U3,U3,U3,U3,U3,__,__,__,__,__,__,U3,U3,__,__},
+/*u3 U3*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,U4,U4,U4,U4,U4,U4,U4,U4,__,__,__,__,__,__,U4,U4,__,__},
+/*u4 U4*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,UC,UC,UC,UC,UC,UC,UC,UC,__,__,__,__,__,__,UC,UC,__,__},
+/*minus MI*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,ZE,IT,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
+/*zero ZE*/ {OK,OK,__,-8,__,-7,__,-3,__,__,CB,__,__,DF,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
+/*int IT*/ {OK,OK,__,-8,__,-7,__,-3,__,__,CB,__,__,DF,IT,IT,__,__,__,__,DE,__,__,__,__,__,__,__,__,DE,__,__},
+/*frac FR*/ {OK,OK,__,-8,__,-7,__,-3,__,__,CB,__,__,__,FR,FR,__,__,__,__,E1,__,__,__,__,__,__,__,__,E1,__,__},
+/*e E1*/ {__,__,__,__,__,__,__,__,__,__,__,E2,E2,__,E3,E3,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
+/*ex E2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,E3,E3,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
+/*exp E3*/ {OK,OK,__,-8,__,-7,__,-3,__,__,__,__,__,__,E3,E3,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
+/*tr T1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,T2,__,__,__,__,__,__,__},
+/*tru T2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,T3,__,__,__,__},
+/*true T3*/ {__,__,__,__,__,__,__,__,__,__,CB,__,__,__,__,__,__,__,__,__,OK,__,__,__,__,__,__,__,__,__,__,__},
+/*fa F1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,F2,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
+/*fal F2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,F3,__,__,__,__,__,__,__,__,__},
+/*fals F3*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,F4,__,__,__,__,__,__},
+/*false F4*/ {__,__,__,__,__,__,__,__,__,__,CB,__,__,__,__,__,__,__,__,__,OK,__,__,__,__,__,__,__,__,__,__,__},
+/*nu N1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,N2,__,__,__,__},
+/*nul N2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,N3,__,__,__,__,__,__,__,__,__},
+/*null N3*/ {__,__,__,__,__,__,__,__,__,__,CB,__,__,__,__,__,__,__,__,__,__,__,OK,__,__,__,__,__,__,__,__,__},
+/*/ C1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,C2},
+/*/* C2*/ {C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C3},
+/** C3*/ {C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,CE,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C3},
+/*_. FX*/ {OK,OK,__,-8,__,-7,__,-3,__,__,__,__,__,__,FR,FR,__,__,__,__,E1,__,__,__,__,__,__,__,__,E1,__,__},
+/*\ D1*/ {__,__,__,__,__,__,__,__,__,D2,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
+/*\ D2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,U1,__,__,__,__},
+};
+
+
+/*
+ These modes can be pushed on the stack.
+*/
+enum modes {
+ MODE_ARRAY = 1,
+ MODE_DONE = 2,
+ MODE_KEY = 3,
+ MODE_OBJECT = 4
+};
+
+static void set_error(JSON_parser jc)
+{
+ switch (jc->state) {
+ case GO:
+ switch (jc->current_char) {
+ case '{': case '}': case '[': case ']':
+ jc->error = JSON_E_UNBALANCED_COLLECTION;
+ break;
+ default:
+ jc->error = JSON_E_INVALID_CHAR;
+ break;
+ }
+ break;
+ case OB:
+ jc->error = JSON_E_EXPECTED_KEY;
+ break;
+ case AR:
+ jc->error = JSON_E_UNBALANCED_COLLECTION;
+ break;
+ case CO:
+ jc->error = JSON_E_EXPECTED_COLON;
+ break;
+ case KE:
+ jc->error = JSON_E_EXPECTED_KEY;
+ break;
+ /* \uXXXX\uYYYY */
+ case U1: case U2: case U3: case U4: case D1: case D2:
+ jc->error = JSON_E_INVALID_UNICODE_SEQUENCE;
+ break;
+ /* true, false, null */
+ case T1: case T2: case T3: case F1: case F2: case F3: case F4: case N1: case N2: case N3:
+ jc->error = JSON_E_INVALID_KEYWORD;
+ break;
+ /* minus, integer, fraction, exponent */
+ case MI: case ZE: case IT: case FR: case E1: case E2: case E3:
+ jc->error = JSON_E_INVALID_NUMBER;
+ break;
+ default:
+ jc->error = JSON_E_INVALID_CHAR;
+ break;
+ }
+}
+
+static int
+push(JSON_parser jc, int mode)
+{
+/*
+ Push a mode onto the stack. Return false if there is overflow.
+*/
+ assert(jc->top <= jc->stack_capacity);
+
+ if (jc->depth < 0) {
+ if (jc->top == jc->stack_capacity) {
+ const size_t bytes_to_copy = jc->stack_capacity * sizeof(jc->stack[0]);
+ const size_t new_capacity = jc->stack_capacity * 2;
+ const size_t bytes_to_allocate = new_capacity * sizeof(jc->stack[0]);
+ void* mem = JSON_parser_malloc(jc->malloc, bytes_to_allocate, "stack");
+ if (!mem) {
+ jc->error = JSON_E_OUT_OF_MEMORY;
+ return false;
+ }
+ jc->stack_capacity = (int)new_capacity;
+ memcpy(mem, jc->stack, bytes_to_copy);
+ if (jc->stack != &jc->static_stack[0]) {
+ jc->free(jc->stack);
+ }
+ jc->stack = (signed char*)mem;
+ }
+ } else {
+ if (jc->top == jc->depth) {
+ jc->error = JSON_E_NESTING_DEPTH_REACHED;
+ return false;
+ }
+ }
+ jc->stack[++jc->top] = (signed char)mode;
+ return true;
+}
+
+
+static int
+pop(JSON_parser jc, int mode)
+{
+/*
+ Pop the stack, assuring that the current mode matches the expectation.
+ Return false if there is underflow or if the modes mismatch.
+*/
+ if (jc->top < 0 || jc->stack[jc->top] != mode) {
+ return false;
+ }
+ jc->top -= 1;
+ return true;
+}
+
+
+#define parse_buffer_clear(jc) \
+ do {\
+ jc->parse_buffer_count = 0;\
+ jc->parse_buffer[0] = 0;\
+ } while (0)
+
+#define parse_buffer_pop_back_char(jc)\
+ do {\
+ assert(jc->parse_buffer_count >= 1);\
+ --jc->parse_buffer_count;\
+ jc->parse_buffer[jc->parse_buffer_count] = 0;\
+ } while (0)
+
+
+
+void delete_JSON_parser(JSON_parser jc)
+{
+ if (jc) {
+ if (jc->stack != &jc->static_stack[0]) {
+ jc->free((void*)jc->stack);
+ }
+ if (jc->parse_buffer != &jc->static_parse_buffer[0]) {
+ jc->free((void*)jc->parse_buffer);
+ }
+ jc->free((void*)jc);
+ }
+}
+
+int JSON_parser_reset(JSON_parser jc)
+{
+ if (NULL == jc) {
+ return false;
+ }
+
+ jc->state = GO;
+ jc->top = -1;
+
+ /* parser has been used previously? */
+ if (NULL == jc->parse_buffer) {
+
+ /* Do we want non-bound stack? */
+ if (jc->depth > 0) {
+ jc->stack_capacity = jc->depth;
+ if (jc->depth <= (int)COUNTOF(jc->static_stack)) {
+ jc->stack = &jc->static_stack[0];
+ } else {
+ const size_t bytes_to_alloc = jc->stack_capacity * sizeof(jc->stack[0]);
+ jc->stack = (signed char*)JSON_parser_malloc(jc->malloc, bytes_to_alloc, "stack");
+ if (jc->stack == NULL) {
+ return false;
+ }
+ }
+ } else {
+ jc->stack_capacity = (int)COUNTOF(jc->static_stack);
+ jc->depth = -1;
+ jc->stack = &jc->static_stack[0];
+ }
+
+ /* set up the parse buffer */
+ jc->parse_buffer = &jc->static_parse_buffer[0];
+ jc->parse_buffer_capacity = COUNTOF(jc->static_parse_buffer);
+ }
+
+ /* set parser to start */
+ push(jc, MODE_DONE);
+ parse_buffer_clear(jc);
+
+ return true;
+}
+
+JSON_parser
+new_JSON_parser(JSON_config const * config)
+{
+/*
+ new_JSON_parser starts the checking process by constructing a JSON_parser
+ object. It takes a depth parameter that restricts the level of maximum
+ nesting.
+
+ To continue the process, call JSON_parser_char for each character in the
+ JSON text, and then call JSON_parser_done to obtain the final result.
+ These functions are fully reentrant.
+*/
+
+ int use_std_malloc = false;
+ JSON_config default_config;
+ JSON_parser jc;
+ JSON_malloc_t alloc;
+
+ /* set to default configuration if none was provided */
+ if (NULL == config) {
+ /* initialize configuration */
+ init_JSON_config(&default_config);
+ config = &default_config;
+ }
+
+ /* use std malloc if either the allocator or deallocator function isn't set */
+ use_std_malloc = NULL == config->malloc || NULL == config->free;
+
+ alloc = use_std_malloc ? malloc : config->malloc;
+
+ jc = JSON_parser_malloc(alloc, sizeof(*jc), "parser");
+
+ if (NULL == jc) {
+ return NULL;
+ }
+
+ /* configure the parser */
+ memset(jc, 0, sizeof(*jc));
+ jc->malloc = alloc;
+ jc->free = use_std_malloc ? free : config->free;
+ jc->callback = config->callback;
+ jc->ctx = config->callback_ctx;
+ jc->allow_comments = (signed char)(config->allow_comments != 0);
+ jc->handle_floats_manually = (signed char)(config->handle_floats_manually != 0);
+ jc->decimal_point = *localeconv()->decimal_point;
+ /* We need to be able to push at least one object */
+ jc->depth = config->depth == 0 ? 1 : config->depth;
+
+ /* reset the parser */
+ if (!JSON_parser_reset(jc)) {
+ jc->free(jc);
+ return NULL;
+ }
+
+ return jc;
+}
+
+static int parse_buffer_grow(JSON_parser jc)
+{
+ const size_t bytes_to_copy = jc->parse_buffer_count * sizeof(jc->parse_buffer[0]);
+ const size_t new_capacity = jc->parse_buffer_capacity * 2;
+ const size_t bytes_to_allocate = new_capacity * sizeof(jc->parse_buffer[0]);
+ void* mem = JSON_parser_malloc(jc->malloc, bytes_to_allocate, "parse buffer");
+
+ if (mem == NULL) {
+ jc->error = JSON_E_OUT_OF_MEMORY;
+ return false;
+ }
+
+ assert(new_capacity > 0);
+ memcpy(mem, jc->parse_buffer, bytes_to_copy);
+
+ if (jc->parse_buffer != &jc->static_parse_buffer[0]) {
+ jc->free(jc->parse_buffer);
+ }
+
+ jc->parse_buffer = (char*)mem;
+ jc->parse_buffer_capacity = new_capacity;
+
+ return true;
+}
+
+static int parse_buffer_reserve_for(JSON_parser jc, unsigned chars)
+{
+ while (jc->parse_buffer_count + chars + 1 > jc->parse_buffer_capacity) {
+ if (!parse_buffer_grow(jc)) {
+ assert(jc->error == JSON_E_OUT_OF_MEMORY);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+#define parse_buffer_has_space_for(jc, count) \
+ (jc->parse_buffer_count + (count) + 1 <= jc->parse_buffer_capacity)
+
+#define parse_buffer_push_back_char(jc, c)\
+ do {\
+ assert(parse_buffer_has_space_for(jc, 1)); \
+ jc->parse_buffer[jc->parse_buffer_count++] = c;\
+ jc->parse_buffer[jc->parse_buffer_count] = 0;\
+ } while (0)
+
+#define assert_is_non_container_type(jc) \
+ assert( \
+ jc->type == JSON_T_NULL || \
+ jc->type == JSON_T_FALSE || \
+ jc->type == JSON_T_TRUE || \
+ jc->type == JSON_T_FLOAT || \
+ jc->type == JSON_T_INTEGER || \
+ jc->type == JSON_T_STRING)
+
+
+static int parse_parse_buffer(JSON_parser jc)
+{
+ if (jc->callback) {
+ JSON_value value, *arg = NULL;
+
+ if (jc->type != JSON_T_NONE) {
+ assert_is_non_container_type(jc);
+
+ switch(jc->type) {
+ case JSON_T_FLOAT:
+ arg = &value;
+ if (jc->handle_floats_manually) {
+ value.vu.str.value = jc->parse_buffer;
+ value.vu.str.length = jc->parse_buffer_count;
+ } else {
+ /* not checking with end pointer b/c there may be trailing ws */
+ value.vu.float_value = strtod(jc->parse_buffer, NULL);
+ }
+ break;
+ case JSON_T_INTEGER:
+ arg = &value;
+ sscanf(jc->parse_buffer, JSON_PARSER_INTEGER_SSCANF_TOKEN, &value.vu.integer_value);
+ break;
+ case JSON_T_STRING:
+ arg = &value;
+ value.vu.str.value = jc->parse_buffer;
+ value.vu.str.length = jc->parse_buffer_count;
+ break;
+ }
+
+ if (!(*jc->callback)(jc->ctx, jc->type, arg)) {
+ return false;
+ }
+ }
+ }
+
+ parse_buffer_clear(jc);
+
+ return true;
+}
+
+#define IS_HIGH_SURROGATE(uc) (((uc) & 0xFC00) == 0xD800)
+#define IS_LOW_SURROGATE(uc) (((uc) & 0xFC00) == 0xDC00)
+#define DECODE_SURROGATE_PAIR(hi,lo) ((((hi) & 0x3FF) << 10) + ((lo) & 0x3FF) + 0x10000)
+static const unsigned char utf8_lead_bits[4] = { 0x00, 0xC0, 0xE0, 0xF0 };
+
+static int decode_unicode_char(JSON_parser jc)
+{
+ int i;
+ unsigned uc = 0;
+ char* p;
+ int trail_bytes;
+
+ assert(jc->parse_buffer_count >= 6);
+
+ p = &jc->parse_buffer[jc->parse_buffer_count - 4];
+
+ for (i = 12; i >= 0; i -= 4, ++p) {
+ unsigned x = *p;
+
+ if (x >= 'a') {
+ x -= ('a' - 10);
+ } else if (x >= 'A') {
+ x -= ('A' - 10);
+ } else {
+ x &= ~0x30u;
+ }
+
+ assert(x < 16);
+
+ uc |= x << i;
+ }
+
+ /* clear UTF-16 char from buffer */
+ jc->parse_buffer_count -= 6;
+ jc->parse_buffer[jc->parse_buffer_count] = 0;
+
+ /* attempt decoding ... */
+ if (jc->utf16_high_surrogate) {
+ if (IS_LOW_SURROGATE(uc)) {
+ uc = DECODE_SURROGATE_PAIR(jc->utf16_high_surrogate, uc);
+ trail_bytes = 3;
+ jc->utf16_high_surrogate = 0;
+ } else {
+ /* high surrogate without a following low surrogate */
+ return false;
+ }
+ } else {
+ if (uc < 0x80) {
+ trail_bytes = 0;
+ } else if (uc < 0x800) {
+ trail_bytes = 1;
+ } else if (IS_HIGH_SURROGATE(uc)) {
+ /* save the high surrogate and wait for the low surrogate */
+ jc->utf16_high_surrogate = (UTF16)uc;
+ return true;
+ } else if (IS_LOW_SURROGATE(uc)) {
+ /* low surrogate without a preceding high surrogate */
+ return false;
+ } else {
+ trail_bytes = 2;
+ }
+ }
+
+ jc->parse_buffer[jc->parse_buffer_count++] = (char) ((uc >> (trail_bytes * 6)) | utf8_lead_bits[trail_bytes]);
+
+ for (i = trail_bytes * 6 - 6; i >= 0; i -= 6) {
+ jc->parse_buffer[jc->parse_buffer_count++] = (char) (((uc >> i) & 0x3F) | 0x80);
+ }
+
+ jc->parse_buffer[jc->parse_buffer_count] = 0;
+
+ return true;
+}
+
+static int add_escaped_char_to_parse_buffer(JSON_parser jc, int next_char)
+{
+ assert(parse_buffer_has_space_for(jc, 1));
+
+ jc->escaped = 0;
+ /* remove the backslash */
+ parse_buffer_pop_back_char(jc);
+ switch(next_char) {
+ case 'b':
+ parse_buffer_push_back_char(jc, '\b');
+ break;
+ case 'f':
+ parse_buffer_push_back_char(jc, '\f');
+ break;
+ case 'n':
+ parse_buffer_push_back_char(jc, '\n');
+ break;
+ case 'r':
+ parse_buffer_push_back_char(jc, '\r');
+ break;
+ case 't':
+ parse_buffer_push_back_char(jc, '\t');
+ break;
+ case '"':
+ parse_buffer_push_back_char(jc, '"');
+ break;
+ case '\\':
+ parse_buffer_push_back_char(jc, '\\');
+ break;
+ case '/':
+ parse_buffer_push_back_char(jc, '/');
+ break;
+ case 'u':
+ parse_buffer_push_back_char(jc, '\\');
+ parse_buffer_push_back_char(jc, 'u');
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+static int add_char_to_parse_buffer(JSON_parser jc, int next_char, int next_class)
+{
+ if (!parse_buffer_reserve_for(jc, 1)) {
+ assert(JSON_E_OUT_OF_MEMORY == jc->error);
+ return false;
+ }
+
+ if (jc->escaped) {
+ if (!add_escaped_char_to_parse_buffer(jc, next_char)) {
+ jc->error = JSON_E_INVALID_ESCAPE_SEQUENCE;
+ return false;
+ }
+ } else if (!jc->comment) {
+ if ((jc->type != JSON_T_NONE) | !((next_class == C_SPACE) | (next_class == C_WHITE)) /* non-white-space */) {
+ parse_buffer_push_back_char(jc, (char)next_char);
+ }
+ }
+
+ return true;
+}
+
+#define assert_type_isnt_string_null_or_bool(jc) \
+ assert(jc->type != JSON_T_FALSE); \
+ assert(jc->type != JSON_T_TRUE); \
+ assert(jc->type != JSON_T_NULL); \
+ assert(jc->type != JSON_T_STRING)
+
+
+int
+JSON_parser_char(JSON_parser jc, int next_char)
+{
+/*
+ After calling new_JSON_parser, call this function for each character (or
+ partial character) in your JSON text. It can accept UTF-8, UTF-16, or
+ UTF-32. It returns true if things are looking ok so far. If it rejects the
+ text, it returns false.
+*/
+ int next_class, next_state;
+
+/*
+ Store the current char for error handling
+*/
+ jc->current_char = next_char;
+
+/*
+ Determine the character's class.
+*/
+ if (next_char < 0) {
+ jc->error = JSON_E_INVALID_CHAR;
+ return false;
+ }
+ if (next_char >= 128) {
+ next_class = C_ETC;
+ } else {
+ next_class = ascii_class[next_char];
+ if (next_class <= __) {
+ set_error(jc);
+ return false;
+ }
+ }
+
+ if (!add_char_to_parse_buffer(jc, next_char, next_class)) {
+ return false;
+ }
+
+/*
+ Get the next state from the state transition table.
+*/
+ next_state = state_transition_table[jc->state][next_class];
+ if (next_state >= 0) {
+/*
+ Change the state.
+*/
+ jc->state = (signed char)next_state;
+ } else {
+/*
+ Or perform one of the actions.
+*/
+ switch (next_state) {
+/* Unicode character */
+ case UC:
+ if(!decode_unicode_char(jc)) {
+ jc->error = JSON_E_INVALID_UNICODE_SEQUENCE;
+ return false;
+ }
+ /* check if we need to read a second UTF-16 char */
+ if (jc->utf16_high_surrogate) {
+ jc->state = D1;
+ } else {
+ jc->state = ST;
+ }
+ break;
+/* escaped char */
+ case EX:
+ jc->escaped = 1;
+ jc->state = ES;
+ break;
+/* integer detected by minus */
+ case MX:
+ jc->type = JSON_T_INTEGER;
+ jc->state = MI;
+ break;
+/* integer detected by zero */
+ case ZX:
+ jc->type = JSON_T_INTEGER;
+ jc->state = ZE;
+ break;
+/* integer detected by 1-9 */
+ case IX:
+ jc->type = JSON_T_INTEGER;
+ jc->state = IT;
+ break;
+
+/* floating point number detected by exponent*/
+ case DE:
+ assert_type_isnt_string_null_or_bool(jc);
+ jc->type = JSON_T_FLOAT;
+ jc->state = E1;
+ break;
+
+/* floating point number detected by fraction */
+ case DF:
+ assert_type_isnt_string_null_or_bool(jc);
+ if (!jc->handle_floats_manually) {
+/*
+ Some versions of strtod (which underlies sscanf) don't support converting
+ C-locale formated floating point values.
+*/
+ assert(jc->parse_buffer[jc->parse_buffer_count-1] == '.');
+ jc->parse_buffer[jc->parse_buffer_count-1] = jc->decimal_point;
+ }
+ jc->type = JSON_T_FLOAT;
+ jc->state = FX;
+ break;
+/* string begin " */
+ case SB:
+ parse_buffer_clear(jc);
+ assert(jc->type == JSON_T_NONE);
+ jc->type = JSON_T_STRING;
+ jc->state = ST;
+ break;
+
+/* n */
+ case NU:
+ assert(jc->type == JSON_T_NONE);
+ jc->type = JSON_T_NULL;
+ jc->state = N1;
+ break;
+/* f */
+ case FA:
+ assert(jc->type == JSON_T_NONE);
+ jc->type = JSON_T_FALSE;
+ jc->state = F1;
+ break;
+/* t */
+ case TR:
+ assert(jc->type == JSON_T_NONE);
+ jc->type = JSON_T_TRUE;
+ jc->state = T1;
+ break;
+
+/* closing comment */
+ case CE:
+ jc->comment = 0;
+ assert(jc->parse_buffer_count == 0);
+ assert(jc->type == JSON_T_NONE);
+ jc->state = jc->before_comment_state;
+ break;
+
+/* opening comment */
+ case CB:
+ if (!jc->allow_comments) {
+ return false;
+ }
+ parse_buffer_pop_back_char(jc);
+ if (!parse_parse_buffer(jc)) {
+ return false;
+ }
+ assert(jc->parse_buffer_count == 0);
+ assert(jc->type != JSON_T_STRING);
+ switch (jc->stack[jc->top]) {
+ case MODE_ARRAY:
+ case MODE_OBJECT:
+ switch(jc->state) {
+ case VA:
+ case AR:
+ jc->before_comment_state = jc->state;
+ break;
+ default:
+ jc->before_comment_state = OK;
+ break;
+ }
+ break;
+ default:
+ jc->before_comment_state = jc->state;
+ break;
+ }
+ jc->type = JSON_T_NONE;
+ jc->state = C1;
+ jc->comment = 1;
+ break;
+/* empty } */
+ case -9:
+ parse_buffer_clear(jc);
+ if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_OBJECT_END, NULL)) {
+ return false;
+ }
+ if (!pop(jc, MODE_KEY)) {
+ return false;
+ }
+ jc->state = OK;
+ break;
+
+/* } */ case -8:
+ parse_buffer_pop_back_char(jc);
+ if (!parse_parse_buffer(jc)) {
+ return false;
+ }
+ if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_OBJECT_END, NULL)) {
+ return false;
+ }
+ if (!pop(jc, MODE_OBJECT)) {
+ jc->error = JSON_E_UNBALANCED_COLLECTION;
+ return false;
+ }
+ jc->type = JSON_T_NONE;
+ jc->state = OK;
+ break;
+
+/* ] */ case -7:
+ parse_buffer_pop_back_char(jc);
+ if (!parse_parse_buffer(jc)) {
+ return false;
+ }
+ if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_ARRAY_END, NULL)) {
+ return false;
+ }
+ if (!pop(jc, MODE_ARRAY)) {
+ jc->error = JSON_E_UNBALANCED_COLLECTION;
+ return false;
+ }
+
+ jc->type = JSON_T_NONE;
+ jc->state = OK;
+ break;
+
+/* { */ case -6:
+ parse_buffer_pop_back_char(jc);
+ if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_OBJECT_BEGIN, NULL)) {
+ return false;
+ }
+ if (!push(jc, MODE_KEY)) {
+ return false;
+ }
+ assert(jc->type == JSON_T_NONE);
+ jc->state = OB;
+ break;
+
+/* [ */ case -5:
+ parse_buffer_pop_back_char(jc);
+ if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_ARRAY_BEGIN, NULL)) {
+ return false;
+ }
+ if (!push(jc, MODE_ARRAY)) {
+ return false;
+ }
+ assert(jc->type == JSON_T_NONE);
+ jc->state = AR;
+ break;
+
+/* string end " */ case -4:
+ parse_buffer_pop_back_char(jc);
+ switch (jc->stack[jc->top]) {
+ case MODE_KEY:
+ assert(jc->type == JSON_T_STRING);
+ jc->type = JSON_T_NONE;
+ jc->state = CO;
+
+ if (jc->callback) {
+ JSON_value value;
+ value.vu.str.value = jc->parse_buffer;
+ value.vu.str.length = jc->parse_buffer_count;
+ if (!(*jc->callback)(jc->ctx, JSON_T_KEY, &value)) {
+ return false;
+ }
+ }
+ parse_buffer_clear(jc);
+ break;
+ case MODE_ARRAY:
+ case MODE_OBJECT:
+ assert(jc->type == JSON_T_STRING);
+ if (!parse_parse_buffer(jc)) {
+ return false;
+ }
+ jc->type = JSON_T_NONE;
+ jc->state = OK;
+ break;
+ default:
+ return false;
+ }
+ break;
+
+/* , */ case -3:
+ parse_buffer_pop_back_char(jc);
+ if (!parse_parse_buffer(jc)) {
+ return false;
+ }
+ switch (jc->stack[jc->top]) {
+ case MODE_OBJECT:
+/*
+ A comma causes a flip from object mode to key mode.
+*/
+ if (!pop(jc, MODE_OBJECT) || !push(jc, MODE_KEY)) {
+ return false;
+ }
+ assert(jc->type != JSON_T_STRING);
+ jc->type = JSON_T_NONE;
+ jc->state = KE;
+ break;
+ case MODE_ARRAY:
+ assert(jc->type != JSON_T_STRING);
+ jc->type = JSON_T_NONE;
+ jc->state = VA;
+ break;
+ default:
+ return false;
+ }
+ break;
+
+/* : */ case -2:
+/*
+ A colon causes a flip from key mode to object mode.
+*/
+ parse_buffer_pop_back_char(jc);
+ if (!pop(jc, MODE_KEY) || !push(jc, MODE_OBJECT)) {
+ return false;
+ }
+ assert(jc->type == JSON_T_NONE);
+ jc->state = VA;
+ break;
+/*
+ Bad action.
+*/
+ default:
+ set_error(jc);
+ return false;
+ }
+ }
+ return true;
+}
+
+int
+JSON_parser_done(JSON_parser jc)
+{
+ if ((jc->state == OK || jc->state == GO) && pop(jc, MODE_DONE))
+ {
+ return true;
+ }
+
+ jc->error = JSON_E_UNBALANCED_COLLECTION;
+ return false;
+}
+
+
+int JSON_parser_is_legal_white_space_string(const char* s)
+{
+ int c, char_class;
+
+ if (s == NULL) {
+ return false;
+ }
+
+ for (; *s; ++s) {
+ c = *s;
+
+ if (c < 0 || c >= 128) {
+ return false;
+ }
+
+ char_class = ascii_class[c];
+
+ if (char_class != C_SPACE && char_class != C_WHITE) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+int JSON_parser_get_last_error(JSON_parser jc)
+{
+ return jc->error;
+}
+
+
+void init_JSON_config(JSON_config* config)
+{
+ if (config) {
+ memset(config, 0, sizeof(*config));
+
+ config->depth = JSON_PARSER_STACK_SIZE - 1;
+ config->malloc = malloc;
+ config->free = free;
+ }
+}
+/* end file parser/JSON_parser.c */
+/* begin file ./cson.c */
+#include
+#include /* malloc()/free() */
+#include
+
+#ifdef _MSC_VER
+# if _MSC_VER >= 1400 /* Visual Studio 2005 and up */
+# pragma warning( push )
+# pragma warning(disable:4996) /* unsecure sscanf (but snscanf() isn't in c89) */
+# pragma warning(disable:4244) /* complaining about data loss due
+ to integer precision in the
+ sqlite3 utf decoding routines */
+# endif
+#endif
+
+#if 1
+#include
+#define MARKER if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf
+#else
+static void noop_printf(char const * fmt, ...) {}
+#define MARKER if(0) printf
+#endif
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+
+
+/**
+ Type IDs corresponding to JavaScript/JSON types.
+*/
+enum cson_type_id {
+ /**
+ The special "null" value constant.
+
+ Its value must be 0 for internal reasons.
+ */
+ CSON_TYPE_UNDEF = 0,
+ /**
+ The special "null" value constant.
+ */
+ CSON_TYPE_NULL = 1,
+ /**
+ The bool value type.
+ */
+ CSON_TYPE_BOOL = 2,
+ /**
+ The integer value type, represented in this library
+ by cson_int_t.
+ */
+ CSON_TYPE_INTEGER = 3,
+ /**
+ The double value type, represented in this library
+ by cson_double_t.
+ */
+ CSON_TYPE_DOUBLE = 4,
+ /** The immutable string type. This library stores strings
+ as immutable UTF8.
+ */
+ CSON_TYPE_STRING = 5,
+ /** The "Array" type. */
+ CSON_TYPE_ARRAY = 6,
+ /** The "Object" type. */
+ CSON_TYPE_OBJECT = 7
+};
+typedef enum cson_type_id cson_type_id;
+
+/**
+ This type holds the "vtbl" for type-specific operations when
+ working with cson_value objects.
+
+ All cson_values of a given logical type share a pointer to a single
+ library-internal instance of this class.
+*/
+struct cson_value_api
+{
+ /**
+ The logical JavaScript/JSON type associated with
+ this object.
+ */
+ const cson_type_id typeID;
+ /**
+ Must free any memory associated with self,
+ but not free self. If self is NULL then
+ this function must do nothing.
+ */
+ void (*cleanup)( cson_value * self );
+ /**
+ POSSIBLE TODOs:
+
+ // Deep copy.
+ int (*clone)( cson_value const * self, cson_value ** tgt );
+
+ // Using JS semantics for true/value
+ char (*bool_value)( cson_value const * self );
+
+ // memcmp() return value semantics
+ int (*compare)( cson_value const * self, cson_value const * other );
+ */
+};
+
+typedef struct cson_value_api cson_value_api;
+
+/**
+ Empty-initialized cson_value_api object.
+*/
+#define cson_value_api_empty_m { \
+ CSON_TYPE_UNDEF/*typeID*/, \
+ NULL/*cleanup*/\
+ }
+/**
+ Empty-initialized cson_value_api object.
+*/
+static const cson_value_api cson_value_api_empty = cson_value_api_empty_m;
+
+
+typedef unsigned int cson_counter_t;
+struct cson_value
+{
+ /** The "vtbl" of type-specific operations. All instances
+ of a given logical value type share a single api instance.
+
+ Results are undefined if this value is NULL.
+ */
+ cson_value_api const * api;
+
+ /** The raw value. Its interpretation depends on the value of the
+ api member. Some value types require dynamically-allocated
+ memory, so one must always call cson_value_free() to destroy a
+ value when it is no longer needed. For stack-allocated values
+ (which client could SHOULD NOT USE unless they are intimately
+ familiar with the memory management rules and don't mind an
+ occasional leak or crash), use cson_value_clean() instead of
+ cson_value_free().
+ */
+ void * value;
+
+ /**
+ We use this to allow us to store cson_value instances in
+ multiple containers or multiple times within a single container
+ (provided no cycles are introduced).
+
+ Notes about the rc implementation:
+
+ - The refcount is for the cson_value instance itself, not its
+ value pointer.
+
+ - Instances start out with a refcount of 0 (not 1). Adding them
+ to a container will increase the refcount. Cleaning up the container
+ will decrement the count.
+
+ - cson_value_free() decrements the refcount (if it is not already
+ 0) and cleans/frees the value only when the refcount is 0.
+
+ - Some places in the internals add an "extra" reference to
+ objects to avoid a premature deletion. Don't try this at home.
+ */
+ cson_counter_t refcount;
+};
+
+
+/**
+ Empty-initialized cson_value object.
+*/
+#define cson_value_empty_m { &cson_value_api_empty/*api*/, NULL/*value*/, 0/*refcount*/ }
+/**
+ Empty-initialized cson_value object.
+*/
+extern const cson_value cson_value_empty;
+
+const cson_value cson_value_empty = cson_value_empty_m;
+const cson_parse_opt cson_parse_opt_empty = cson_parse_opt_empty_m;
+const cson_output_opt cson_output_opt_empty = cson_output_opt_empty_m;
+const cson_object_iterator cson_object_iterator_empty = cson_object_iterator_empty_m;
+const cson_buffer cson_buffer_empty = cson_buffer_empty_m;
+const cson_parse_info cson_parse_info_empty = cson_parse_info_empty_m;
+
+static void cson_value_destroy_zero_it( cson_value * self );
+static void cson_value_destroy_object( cson_value * self );
+/**
+ If self is-a array then this function destroys its contents,
+ else this function does nothing.
+*/
+static void cson_value_destroy_array( cson_value * self );
+
+static const cson_value_api cson_value_api_null = { CSON_TYPE_NULL, cson_value_destroy_zero_it };
+static const cson_value_api cson_value_api_undef = { CSON_TYPE_UNDEF, cson_value_destroy_zero_it };
+static const cson_value_api cson_value_api_bool = { CSON_TYPE_BOOL, cson_value_destroy_zero_it };
+static const cson_value_api cson_value_api_integer = { CSON_TYPE_INTEGER, cson_value_destroy_zero_it };
+static const cson_value_api cson_value_api_double = { CSON_TYPE_DOUBLE, cson_value_destroy_zero_it };
+static const cson_value_api cson_value_api_string = { CSON_TYPE_STRING, cson_value_destroy_zero_it };
+static const cson_value_api cson_value_api_array = { CSON_TYPE_ARRAY, cson_value_destroy_array };
+static const cson_value_api cson_value_api_object = { CSON_TYPE_OBJECT, cson_value_destroy_object };
+
+static const cson_value cson_value_undef = { &cson_value_api_undef, NULL, 0 };
+static const cson_value cson_value_null_empty = { &cson_value_api_null, NULL, 0 };
+static const cson_value cson_value_bool_empty = { &cson_value_api_bool, NULL, 0 };
+static const cson_value cson_value_integer_empty = { &cson_value_api_integer, NULL, 0 };
+static const cson_value cson_value_double_empty = { &cson_value_api_double, NULL, 0 };
+static const cson_value cson_value_string_empty = { &cson_value_api_string, NULL, 0 };
+static const cson_value cson_value_array_empty = { &cson_value_api_array, NULL, 0 };
+static const cson_value cson_value_object_empty = { &cson_value_api_object, NULL, 0 };
+
+struct cson_string
+{
+ unsigned int length;
+};
+#define cson_string_empty_m {0/*length*/}
+static const cson_string cson_string_empty = cson_string_empty_m;
+
+
+
+#define CSON_CAST(T,V) ((T*)((V)->value))
+#define CSON_VCAST(V) ((cson_value *)(((unsigned char *)(V))-sizeof(cson_value)))
+#define CSON_INT(V) ((cson_int_t*)(V)->value)
+#define CSON_DBL(V) CSON_CAST(cson_double_t,(V))
+#define CSON_STR(V) CSON_CAST(cson_string,(V))
+#define CSON_OBJ(V) CSON_CAST(cson_object,(V))
+#define CSON_ARRAY(V) CSON_CAST(cson_array,(V))
+
+/**
+
+ Holds special shared "constant" (though they are non-const)
+ values.
+
+*/
+static struct CSON_EMPTY_HOLDER_
+{
+ char trueValue;
+ cson_string stringValue;
+} CSON_EMPTY_HOLDER = {
+ 1/*trueValue*/,
+ cson_string_empty_m
+};
+
+/**
+ Indexes into the CSON_SPECIAL_VALUES array.
+
+ If this enum changes in any way,
+ makes damned sure that CSON_SPECIAL_VALUES is updated
+ to match!!!
+*/
+enum CSON_INTERNAL_VALUES {
+
+ CSON_VAL_UNDEF = 0,
+ CSON_VAL_NULL = 1,
+ CSON_VAL_TRUE = 2,
+ CSON_VAL_FALSE = 3,
+ CSON_VAL_INT_0 = 4,
+ CSON_VAL_DBL_0 = 5,
+ CSON_VAL_STR_EMPTY = 6,
+ CSON_INTERNAL_VALUES_LENGTH
+};
+
+/**
+ Some "special" shared cson_value instances.
+
+ These values MUST be initialized in the order specified
+ by the CSON_INTERNAL_VALUES enum.
+
+ Note that they are not const because they are used as
+ shared-allocation objects in non-const contexts. However, the
+ public API provides no way to modifying them, and clients who
+ modify values directly are subject to The Wrath of Undefined
+ Behaviour.
+*/
+static cson_value CSON_SPECIAL_VALUES[] = {
+{ &cson_value_api_undef, NULL, 0 }, /* UNDEF */
+{ &cson_value_api_null, NULL, 0 }, /* NULL */
+{ &cson_value_api_bool, &CSON_EMPTY_HOLDER.trueValue, 0 }, /* TRUE */
+{ &cson_value_api_bool, NULL, 0 }, /* FALSE */
+{ &cson_value_api_integer, NULL, 0 }, /* INT_0 */
+{ &cson_value_api_double, NULL, 0 }, /* DBL_0 */
+{ &cson_value_api_string, &CSON_EMPTY_HOLDER.stringValue, 0 }, /* STR_EMPTY */
+{ 0, NULL, 0 }
+};
+
+
+/**
+ Returns non-0 (true) if m is one of our special
+ "built-in" values, e.g. from CSON_SPECIAL_VALUES and some
+ "empty" values.
+
+ If this returns true, m MUST NOT be free()d!
+ */
+static char cson_value_is_builtin( void const * m )
+{
+ if((m >= (void const *)&CSON_EMPTY_HOLDER)
+ && ( m < (void const *)(&CSON_EMPTY_HOLDER+1)))
+ return 1;
+ else return
+ ((m > (void const *)&CSON_SPECIAL_VALUES[0])
+ && ( m < (void const *)&CSON_SPECIAL_VALUES[CSON_INTERNAL_VALUES_LENGTH]) )
+ ? 1
+ : 0;
+}
+
+char const * cson_rc_string(int rc)
+{
+ if(0 == rc) return "OK";
+#define CHECK(N) else if(cson_rc.N == rc ) return #N
+ CHECK(OK);
+ CHECK(ArgError);
+ CHECK(RangeError);
+ CHECK(TypeError);
+ CHECK(IOError);
+ CHECK(AllocError);
+ CHECK(NYIError);
+ CHECK(InternalError);
+ CHECK(UnsupportedError);
+ CHECK(NotFoundError);
+ CHECK(UnknownError);
+ CHECK(Parse_INVALID_CHAR);
+ CHECK(Parse_INVALID_KEYWORD);
+ CHECK(Parse_INVALID_ESCAPE_SEQUENCE);
+ CHECK(Parse_INVALID_UNICODE_SEQUENCE);
+ CHECK(Parse_INVALID_NUMBER);
+ CHECK(Parse_NESTING_DEPTH_REACHED);
+ CHECK(Parse_UNBALANCED_COLLECTION);
+ CHECK(Parse_EXPECTED_KEY);
+ CHECK(Parse_EXPECTED_COLON);
+ else return "UnknownError";
+#undef CHECK
+}
+
+/**
+ If CSON_LOG_ALLOC is true then the cson_malloc/realloc/free() routines
+ will log a message to stderr.
+*/
+#define CSON_LOG_ALLOC 0
+
+
+/**
+ CSON_FOSSIL_MODE is only for use in the Fossil
+ source tree, so that we can plug in to its allocators.
+ We can't do this by, e.g., defining macros for the
+ malloc/free funcs because fossil's lack of header files
+ means we would have to #include "main.c" here to
+ get the declarations.
+ */
+#if defined(CSON_FOSSIL_MODE)
+void *fossil_malloc(size_t n);
+void fossil_free(void *p);
+void *fossil_realloc(void *p, size_t n);
+# define CSON_MALLOC_IMPL fossil_malloc
+# define CSON_FREE_IMPL fossil_free
+# define CSON_REALLOC_IMPL fossil_realloc
+#endif
+
+#if !defined CSON_MALLOC_IMPL
+# define CSON_MALLOC_IMPL malloc
+#endif
+#if !defined CSON_FREE_IMPL
+# define CSON_FREE_IMPL free
+#endif
+#if !defined CSON_REALLOC_IMPL
+# define CSON_REALLOC_IMPL realloc
+#endif
+
+/**
+ A test/debug macro for simulating an OOM after the given number of
+ bytes have been allocated.
+*/
+#define CSON_SIMULATE_OOM 0
+#if CSON_SIMULATE_OOM
+static unsigned int cson_totalAlloced = 0;
+#endif
+
+/** Simple proxy for malloc(). descr is a description of the allocation. */
+static void * cson_malloc( size_t n, char const * descr )
+{
+#if CSON_LOG_ALLOC
+ fprintf(stderr, "Allocating %u bytes [%s].\n", (unsigned int)n, descr);
+#endif
+#if CSON_SIMULATE_OOM
+ cson_totalAlloced += n;
+ if( cson_totalAlloced > CSON_SIMULATE_OOM )
+ {
+ return NULL;
+ }
+#endif
+ return CSON_MALLOC_IMPL(n);
+}
+
+/** Simple proxy for free(). descr is a description of the memory being freed. */
+static void cson_free( void * p, char const * descr )
+{
+#if CSON_LOG_ALLOC
+ fprintf(stderr, "Freeing @%p [%s].\n", p, descr);
+#endif
+ if( !cson_value_is_builtin(p) )
+ {
+ CSON_FREE_IMPL( p );
+ }
+}
+/** Simple proxy for realloc(). descr is a description of the (re)allocation. */
+static void * cson_realloc( void * hint, size_t n, char const * descr )
+{
+#if CSON_LOG_ALLOC
+ fprintf(stderr, "%sllocating %u bytes [%s].\n",
+ hint ? "Rea" : "A",
+ (unsigned int)n, descr);
+#endif
+#if CSON_SIMULATE_OOM
+ cson_totalAlloced += n;
+ if( cson_totalAlloced > CSON_SIMULATE_OOM )
+ {
+ return NULL;
+ }
+#endif
+ if( 0==n )
+ {
+ cson_free(hint, descr);
+ return NULL;
+ }
+ else
+ {
+ return CSON_REALLOC_IMPL( hint, n );
+ }
+}
+
+
+#undef CSON_LOG_ALLOC
+#undef CSON_SIMULATE_OOM
+
+
+
+/**
+ CLIENTS CODE SHOULD NEVER USE THIS because it opens up doors to
+ memory leaks if it is not used in very controlled circumstances.
+ Users must be very aware of how the underlying memory management
+ works.
+
+ Frees any resources owned by val, but does not free val itself
+ (which may be stack-allocated). If !val or val->api or
+ val->api->cleanup are NULL then this is a no-op.
+
+ If v is a container type (object or array) its children are also
+ cleaned up (BUT NOT FREED), recursively.
+
+ After calling this, val will have the special "undefined" type.
+*/
+static void cson_value_clean( cson_value * val );
+
+/**
+ Increments cv's reference count by 1. As a special case, values
+ for which cson_value_is_builtin() returns true are not
+ modified. assert()s if (NULL==cv).
+*/
+static void cson_refcount_incr( cson_value * cv )
+{
+ assert( NULL != cv );
+ if( cson_value_is_builtin( cv ) )
+ { /* do nothing: we do not want to modify the shared
+ instances.
+ */
+ return;
+ }
+ else
+ {
+ ++cv->refcount;
+ }
+}
+
+#if 0
+int cson_value_refcount_set( cson_value * cv, unsigned short rc )
+{
+ if( NULL == cv ) return cson_rc.ArgError;
+ else
+ {
+ cv->refcount = rc;
+ return 0;
+ }
+}
+#endif
+
+int cson_value_add_reference( cson_value * cv )
+{
+ if( NULL == cv ) return cson_rc.ArgError;
+ else if( (cv->refcount+1) < cv->refcount )
+ {
+ return cson_rc.RangeError;
+ }
+ else
+ {
+ cson_refcount_incr( cv );
+ return 0;
+ }
+}
+
+/**
+ If cv is NULL or cson_value_is_builtin(cv) returns true then this
+ function does nothing and returns 0, otherwise... If
+ cv->refcount is 0 or 1 then cson_value_clean(cv) is called, cv is
+ freed, and 0 is returned. If cv->refcount is any other value then
+ it is decremented and the new value is returned.
+*/
+static cson_counter_t cson_refcount_decr( cson_value * cv )
+{
+ if( (NULL == cv) || cson_value_is_builtin(cv) ) return 0;
+ else if( (0 == cv->refcount) || (0 == --cv->refcount) )
+ {
+ cson_value_clean(cv);
+ cson_free(cv,"cson_value::refcount=0");
+ return 0;
+ }
+ else return cv->refcount;
+}
+
+unsigned int cson_string_length_bytes( cson_string const * str )
+{
+ return str ? str->length : 0;
+}
+
+
+/**
+ Fetches v's string value as a non-const string.
+
+ cson_strings are supposed to be immutable, but this form provides
+ access to the immutable bits, which are v->length bytes long. A
+ length-0 string is returned as NULL from here, as opposed to
+ "". (This is a side-effect of the string allocation mechanism.)
+ Returns NULL if !v.
+*/
+static char * cson_string_str(cson_string *v)
+{
+ /*
+ See http://groups.google.com/group/comp.lang.c.moderated/browse_thread/thread/2e0c0df5e8a0cd6a
+ */
+#if 1
+ if( !v || (&CSON_EMPTY_HOLDER.stringValue == v) ) return NULL;
+ else return (char *)((unsigned char *)( v+1 ));
+#else
+ static char empty[2] = {0,0};
+ return ( NULL == v )
+ ? NULL
+ : (v->length
+ ? (char *) (((unsigned char *)v) + sizeof(cson_string))
+ : empty)
+ ;
+#endif
+}
+
+/**
+ Fetches v's string value as a const string.
+*/
+char const * cson_string_cstr(cson_string const *v)
+{
+ /*
+ See http://groups.google.com/group/comp.lang.c.moderated/browse_thread/thread/2e0c0df5e8a0cd6a
+ */
+#if 1
+ if( ! v ) return NULL;
+ else if( v == &CSON_EMPTY_HOLDER.stringValue ) return "";
+ else return (char *)((unsigned char *)(v+1));
+#else
+ return (NULL == v)
+ ? NULL
+ : (v->length
+ ? (char const *) ((unsigned char const *)(v+1))
+ : "");
+#endif
+}
+
+
+#if 0
+/**
+ Just like strndup(3), in that neither are C89/C99-standard and both
+ are documented in detail in strndup(3).
+*/
+static char * cson_strdup( char const * src, size_t n )
+{
+ char * rc = (char *)cson_malloc(n+1, "cson_strdup");
+ if( ! rc ) return NULL;
+ memset( rc, 0, n+1 );
+ rc[n] = 0;
+ return strncpy( rc, src, n );
+}
+#endif
+
+int cson_string_cmp_cstr_n( cson_string const * str, char const * other, unsigned int otherLen )
+{
+ if( ! other && !str ) return 0;
+ else if( other && !str ) return 1;
+ else if( str && !other ) return -1;
+ else if( !otherLen ) return str->length ? 1 : 0;
+ else if( !str->length ) return otherLen ? -1 : 0;
+ else
+ {
+ unsigned const int max = (otherLen > str->length) ? otherLen : str->length;
+ int const rc = strncmp( cson_string_cstr(str), other, max );
+ return ( (0 == rc) && (otherLen != str->length) )
+ ? (str->length < otherLen) ? -1 : 1
+ : rc;
+ }
+}
+
+int cson_string_cmp_cstr( cson_string const * lhs, char const * rhs )
+{
+ return cson_string_cmp_cstr_n( lhs, rhs, (rhs&&*rhs) ? strlen(rhs) : 0 );
+}
+int cson_string_cmp( cson_string const * lhs, cson_string const * rhs )
+{
+ return cson_string_cmp_cstr_n( lhs, cson_string_cstr(rhs), rhs ? rhs->length : 0 );
+}
+
+
+/**
+ If self is not NULL, *self is overwritten to have the undefined
+ type. self is not cleaned up or freed.
+*/
+void cson_value_destroy_zero_it( cson_value * self )
+{
+ if( self )
+ {
+ *self = cson_value_undef;
+ }
+}
+
+/**
+ A key/value pair collection.
+
+ Each of these objects owns its key/value pointers, and they
+ are cleaned up by cson_kvp_clean().
+*/
+struct cson_kvp
+{
+ cson_value * key;
+ cson_value * value;
+};
+#define cson_kvp_empty_m {NULL,NULL}
+static const cson_kvp cson_kvp_empty = cson_kvp_empty_m;
+
+/** @def CSON_OBJECT_PROPS_SORT
+
+ If CSON_OBJECT_PROPS_SORT is set to a true value then
+ qsort() and bsearch() are used to sort (upon insertion)
+ and search cson_object::kvp property lists. This costs us
+ a re-sort on each insertion but searching is O(log n)
+ average/worst case (and O(1) best-case).
+
+ i'm not yet convinced that the overhead of the qsort() justifies
+ the potentially decreased search times - it has not been
+ measured. Object property lists tend to be relatively short in
+ JSON, and a linear search which uses the cson_string::length
+ property as a quick check is quite fast when one compares it with
+ the sort overhead required by the bsearch() approach.
+*/
+#define CSON_OBJECT_PROPS_SORT 0
+
+/** @def CSON_OBJECT_PROPS_SORT_USE_LENGTH
+
+ Don't use this - i'm not sure that it works how i'd like.
+
+ If CSON_OBJECT_PROPS_SORT_USE_LENGTH is true then
+ we use string lengths as quick checks when sorting
+ property keys. This leads to a non-intuitive sorting
+ order but "should" be faster.
+
+ This is ignored if CSON_OBJECT_PROPS_SORT is false.
+
+*/
+#define CSON_OBJECT_PROPS_SORT_USE_LENGTH 0
+
+#if CSON_OBJECT_PROPS_SORT
+
+/**
+ cson_kvp comparator for use with qsort(). ALMOST compares with
+ strcmp() semantics, but it uses the strings' lengths as a quicker
+ approach. This might give non-intuitive results, but it's faster.
+ */
+static int cson_kvp_cmp( void const * lhs, void const * rhs )
+{
+ cson_kvp const * lk = *((cson_kvp const * const*)lhs);
+ cson_kvp const * rk = *((cson_kvp const * const*)rhs);
+ cson_string const * l = cson_string_value(lk->key);
+ cson_string const * r = cson_string_value(rk->key);
+#if CSON_OBJECT_PROPS_SORT_USE_LENGTH
+ if( l->length < r->length ) return -1;
+ else if( l->length > r->length ) return 1;
+ else return strcmp( cson_string_cstr( l ), cson_string_cstr( r ) );
+#else
+ return strcmp( cson_string_cstr( l ),
+ cson_string_cstr( r ) );
+#endif /*CSON_OBJECT_PROPS_SORT_USE_LENGTH*/
+}
+#endif /*CSON_OBJECT_PROPS_SORT*/
+
+
+#if CSON_OBJECT_PROPS_SORT
+#error "Need to rework this for cson_string-to-cson_value refactoring"
+/**
+ A bsearch() comparison function which requires that lhs be a (char
+ const *) and rhs be-a (cson_kvp const * const *). It compares lhs
+ to rhs->key's value, using strcmp() semantics.
+ */
+static int cson_kvp_cmp_vs_cstr( void const * lhs, void const * rhs )
+{
+ char const * lk = (char const *)lhs;
+ cson_kvp const * rk =
+ *((cson_kvp const * const*)rhs)
+ ;
+#if CSON_OBJECT_PROPS_SORT_USE_LENGTH
+ unsigned int llen = strlen(lk);
+ if( llen < rk->key->length ) return -1;
+ else if( llen > rk->key->length ) return 1;
+ else return strcmp( lk, cson_string_cstr( rk->key ) );
+#else
+ return strcmp( lk, cson_string_cstr( rk->key ) );
+#endif /*CSON_OBJECT_PROPS_SORT_USE_LENGTH*/
+}
+#endif /*CSON_OBJECT_PROPS_SORT*/
+
+
+struct cson_kvp_list
+{
+ cson_kvp ** list;
+ unsigned int count;
+ unsigned int alloced;
+};
+typedef struct cson_kvp_list cson_kvp_list;
+#define cson_kvp_list_empty_m {NULL/*list*/,0/*count*/,0/*alloced*/}
+static const cson_kvp_list cson_kvp_list_empty = cson_kvp_list_empty_m;
+
+struct cson_object
+{
+ cson_kvp_list kvp;
+};
+/*typedef struct cson_object cson_object;*/
+#define cson_object_empty_m { cson_kvp_list_empty_m/*kvp*/ }
+static const cson_object cson_object_empty = cson_object_empty_m;
+
+struct cson_value_list
+{
+ cson_value ** list;
+ unsigned int count;
+ unsigned int alloced;
+};
+typedef struct cson_value_list cson_value_list;
+#define cson_value_list_empty_m {NULL/*list*/,0/*count*/,0/*alloced*/}
+static const cson_value_list cson_value_list_empty = cson_value_list_empty_m;
+
+struct cson_array
+{
+ cson_value_list list;
+};
+/*typedef struct cson_array cson_array;*/
+#define cson_array_empty_m { cson_value_list_empty_m/*list*/ }
+static const cson_array cson_array_empty = cson_array_empty_m;
+
+
+struct cson_parser
+{
+ JSON_parser p;
+ cson_value * root;
+ cson_value * node;
+ cson_array stack;
+ cson_string * ckey;
+ int errNo;
+ unsigned int totalKeyCount;
+ unsigned int totalValueCount;
+};
+typedef struct cson_parser cson_parser;
+static const cson_parser cson_parser_empty = {
+NULL/*p*/,
+NULL/*root*/,
+NULL/*node*/,
+cson_array_empty_m/*stack*/,
+NULL/*ckey*/,
+0/*errNo*/,
+0/*totalKeyCount*/,
+0/*totalValueCount*/
+};
+
+#if 1
+/* The following funcs are declared in generated code (cson_lists.h),
+ but we need early access to their decls for the Amalgamation build.
+*/
+static unsigned int cson_value_list_reserve( cson_value_list * self, unsigned int n );
+static unsigned int cson_kvp_list_reserve( cson_kvp_list * self, unsigned int n );
+static int cson_kvp_list_append( cson_kvp_list * self, cson_kvp * cp );
+static void cson_kvp_list_clean( cson_kvp_list * self,
+ void (*cleaner)(cson_kvp * obj) );
+#if 0
+static int cson_value_list_append( cson_value_list * self, cson_value * cp );
+static void cson_value_list_clean( cson_value_list * self, void (*cleaner)(cson_value * obj));
+static int cson_kvp_list_visit( cson_kvp_list * self,
+ int (*visitor)(cson_kvp * obj, void * visitorState ),
+ void * visitorState );
+static int cson_value_list_visit( cson_value_list * self,
+ int (*visitor)(cson_value * obj, void * visitorState ),
+ void * visitorState );
+#endif
+#endif
+
+#if 0
+# define LIST_T cson_value_list
+# define VALUE_T cson_value *
+# define VALUE_T_IS_PTR 1
+# define LIST_T cson_kvp_list
+# define VALUE_T cson_kvp *
+# define VALUE_T_IS_PTR 1
+#else
+#endif
+
+/**
+ Allocates a new value of the specified type ownership of it to the
+ caller. It must eventually be destroyed, by the caller or its
+ owning container, by passing it to cson_value_free() or transfering
+ ownership to a container.
+
+ extra is only valid for type CSON_TYPE_STRING, and must be the length
+ of the string to allocate + 1 byte (for the NUL).
+
+ The returned value->api member will be set appropriately and
+ val->value will be set to point to the memory allocated to hold the
+ native value type. Use the internal CSON_CAST() family of macros to
+ convert them.
+
+ Returns NULL on allocation error.
+
+ @see cson_value_new_array()
+ @see cson_value_new_object()
+ @see cson_value_new_string()
+ @see cson_value_new_integer()
+ @see cson_value_new_double()
+ @see cson_value_new_bool()
+ @see cson_value_free()
+*/
+static cson_value * cson_value_new(cson_type_id t, size_t extra);
+
+cson_value * cson_value_new(cson_type_id t, size_t extra)
+{
+ static const size_t vsz = sizeof(cson_value);
+ const size_t sz = vsz + extra;
+ size_t tx = 0;
+ cson_value def = cson_value_undef;
+ cson_value * v = NULL;
+ char const * reason = "cson_value_new";
+ switch(t)
+ {
+ case CSON_TYPE_ARRAY:
+ assert( 0 == extra );
+ def = cson_value_array_empty;
+ tx = sizeof(cson_array);
+ reason = "cson_value:array";
+ break;
+ case CSON_TYPE_DOUBLE:
+ assert( 0 == extra );
+ def = cson_value_double_empty;
+ tx = sizeof(cson_double_t);
+ reason = "cson_value:double";
+ break;
+ case CSON_TYPE_INTEGER:
+ assert( 0 == extra );
+ def = cson_value_integer_empty;
+ tx = sizeof(cson_int_t);
+ reason = "cson_value:int";
+ break;
+ case CSON_TYPE_STRING:
+ assert( 0 != extra );
+ def = cson_value_string_empty;
+ tx = sizeof(cson_string);
+ reason = "cson_value:string";
+ break;
+ case CSON_TYPE_OBJECT:
+ assert( 0 == extra );
+ def = cson_value_object_empty;
+ tx = sizeof(cson_object);
+ reason = "cson_value:object";
+ break;
+ default:
+ assert(0 && "Unhandled type in cson_value_new()!");
+ return NULL;
+ }
+ assert( def.api->typeID != CSON_TYPE_UNDEF );
+ v = (cson_value *)cson_malloc(sz+tx, reason);
+ if( v ) {
+ *v = def;
+ if(tx || extra){
+ memset(v+1, 0, tx + extra);
+ v->value = (void *)(v+1);
+ }
+ }
+ return v;
+}
+
+
+void cson_value_free(cson_value *v)
+{
+ cson_refcount_decr( v );
+}
+
+#if 0 /* we might actually want this later on. */
+/** Returns true if v is not NULL and has the given type ID. */
+static char cson_value_is_a( cson_value const * v, cson_type_id is )
+{
+ return (v && v->api && (v->api->typeID == is)) ? 1 : 0;
+}
+#endif
+
+#if 0
+cson_type_id cson_value_type_id( cson_value const * v )
+{
+ return (v && v->api) ? v->api->typeID : CSON_TYPE_UNDEF;
+}
+#endif
+
+char cson_value_is_undef( cson_value const * v )
+{
+ /**
+ This special-case impl is needed because the underlying
+ (generic) list operations do not know how to populate
+ new entries
+ */
+ return ( !v || !v->api || (v->api==&cson_value_api_undef))
+ ? 1 : 0;
+}
+#define ISA(T,TID) char cson_value_is_##T( cson_value const * v ) { \
+ /*return (v && v->api) ? cson_value_is_a(v,CSON_TYPE_##TID) : 0;*/ \
+ return (v && (v->api == &cson_value_api_##T)) ? 1 : 0; \
+ } static const char bogusPlaceHolderForEmacsIndention##TID = CSON_TYPE_##TID
+ISA(null,NULL);
+ISA(bool,BOOL);
+ISA(integer,INTEGER);
+ISA(double,DOUBLE);
+ISA(string,STRING);
+ISA(array,ARRAY);
+ISA(object,OBJECT);
+#undef ISA
+char cson_value_is_number( cson_value const * v )
+{
+ return cson_value_is_integer(v) || cson_value_is_double(v);
+}
+
+
+void cson_value_clean( cson_value * val )
+{
+ if( val && val->api && val->api->cleanup )
+ {
+ if( ! cson_value_is_builtin( val ) )
+ {
+ cson_counter_t const rc = val->refcount;
+ val->api->cleanup(val);
+ *val = cson_value_undef;
+ val->refcount = rc;
+ }
+ }
+}
+
+static cson_value * cson_value_array_alloc()
+{
+ cson_value * v = cson_value_new(CSON_TYPE_ARRAY,0);
+ if( NULL != v )
+ {
+ cson_array * ar = CSON_ARRAY(v);
+ assert(NULL != ar);
+ *ar = cson_array_empty;
+ }
+ return v;
+}
+
+static cson_value * cson_value_object_alloc()
+{
+ cson_value * v = cson_value_new(CSON_TYPE_OBJECT,0);
+ if( NULL != v )
+ {
+ cson_object * obj = CSON_OBJ(v);
+ assert(NULL != obj);
+ *obj = cson_object_empty;
+ }
+ return v;
+}
+
+cson_value * cson_value_new_object()
+{
+ return cson_value_object_alloc();
+}
+
+cson_object * cson_new_object()
+{
+
+ return cson_value_get_object( cson_value_new_object() );
+}
+
+cson_value * cson_value_new_array()
+{
+ return cson_value_array_alloc();
+}
+
+
+cson_array * cson_new_array()
+{
+ return cson_value_get_array( cson_value_new_array() );
+}
+
+/**
+ Frees kvp->key and kvp->value and sets them to NULL, but does not free
+ kvp. If !kvp then this is a no-op.
+*/
+static void cson_kvp_clean( cson_kvp * kvp )
+{
+ if( kvp )
+ {
+ if(kvp->key)
+ {
+ cson_value_free(kvp->key);
+ kvp->key = NULL;
+ }
+ if(kvp->value)
+ {
+ cson_value_free( kvp->value );
+ kvp->value = NULL;
+ }
+ }
+}
+
+cson_string * cson_kvp_key( cson_kvp const * kvp )
+{
+ return kvp ? cson_value_get_string(kvp->key) : NULL;
+}
+cson_value * cson_kvp_value( cson_kvp const * kvp )
+{
+ return kvp ? kvp->value : NULL;
+}
+
+
+/**
+ Calls cson_kvp_clean(kvp) and then frees kvp.
+*/
+static void cson_kvp_free( cson_kvp * kvp )
+{
+ if( kvp )
+ {
+ cson_kvp_clean(kvp);
+ cson_free(kvp,"cson_kvp");
+ }
+}
+
+
+/**
+ cson_value_api::destroy_value() impl for Object
+ values. Cleans up self-owned memory and overwrites
+ self to have the undefined value, but does not
+ free self.
+*/
+static void cson_value_destroy_object( cson_value * self )
+{
+ if(self && self->value) {
+ cson_object * obj = (cson_object *)self->value;
+ assert( self->value == obj );
+ cson_kvp_list_clean( &obj->kvp, cson_kvp_free );
+ *self = cson_value_undef;
+ }
+}
+
+/**
+ Cleans up the contents of ar->list, but does not free ar.
+
+ After calling this, ar will have a length of 0.
+
+ If properlyCleanValues is 1 then cson_value_free() is called on
+ each non-NULL item, otherwise the outer list is destroyed but the
+ individual items are assumed to be owned by someone else and are
+ not freed.
+*/
+static void cson_array_clean( cson_array * ar, char properlyCleanValues )
+{
+ if( ar )
+ {
+ unsigned int i = 0;
+ cson_value * val = NULL;
+ for( ; i < ar->list.count; ++i )
+ {
+ val = ar->list.list[i];
+ if(val)
+ {
+ ar->list.list[i] = NULL;
+ if( properlyCleanValues )
+ {
+ cson_value_free( val );
+ }
+ }
+ }
+ cson_value_list_reserve(&ar->list,0);
+ ar->list = cson_value_list_empty
+ /* Pedantic note: reserve(0) already clears the list-specific
+ fields, but we do this just in case we ever add new fields
+ to cson_value_list which are not used in the reserve() impl.
+ */
+ ;
+ }
+}
+
+/**
+ cson_value_api::destroy_value() impl for Array
+ values. Cleans up self-owned memory and overwrites
+ self to have the undefined value, but does not
+ free self.
+*/
+static void cson_value_destroy_array( cson_value * self )
+{
+ cson_array * ar = cson_value_get_array(self);
+ if(ar) {
+ assert( self->value == ar );
+ cson_array_clean( ar, 1 );
+ *self = cson_value_undef;
+ }
+}
+
+int cson_buffer_fill_from( cson_buffer * dest, cson_data_source_f src, void * state )
+{
+ int rc;
+ enum { BufSize = 1024 * 4 };
+ char rbuf[BufSize];
+ size_t total = 0;
+ unsigned int rlen = 0;
+ if( ! dest || ! src ) return cson_rc.ArgError;
+ dest->used = 0;
+ while(1)
+ {
+ rlen = BufSize;
+ rc = src( state, rbuf, &rlen );
+ if( rc ) break;
+ total += rlen;
+ if( dest->capacity < (total+1) )
+ {
+ rc = cson_buffer_reserve( dest, total + 1);
+ if( 0 != rc ) break;
+ }
+ memcpy( dest->mem + dest->used, rbuf, rlen );
+ dest->used += rlen;
+ if( rlen < BufSize ) break;
+ }
+ if( !rc && dest->used )
+ {
+ assert( dest->used < dest->capacity );
+ dest->mem[dest->used] = 0;
+ }
+ return rc;
+}
+
+int cson_data_source_FILE( void * state, void * dest, unsigned int * n )
+{
+ FILE * f = (FILE*) state;
+ if( ! state || ! n || !dest ) return cson_rc.ArgError;
+ else if( !*n ) return cson_rc.RangeError;
+ *n = (unsigned int)fread( dest, 1, *n, f );
+ if( !*n )
+ {
+ return feof(f) ? 0 : cson_rc.IOError;
+ }
+ return 0;
+}
+
+int cson_parse_FILE( cson_value ** tgt, FILE * src,
+ cson_parse_opt const * opt, cson_parse_info * err )
+{
+ return cson_parse( tgt, cson_data_source_FILE, src, opt, err );
+}
+
+
+int cson_value_fetch_bool( cson_value const * val, char * v )
+{
+ /**
+ FIXME: move the to-bool operation into cson_value_api, like we
+ do in the C++ API.
+ */
+ if( ! val || !val->api ) return cson_rc.ArgError;
+ else
+ {
+ int rc = 0;
+ char b = 0;
+ switch( val->api->typeID )
+ {
+ case CSON_TYPE_ARRAY:
+ case CSON_TYPE_OBJECT:
+ b = 1;
+ break;
+ case CSON_TYPE_STRING: {
+ char const * str = cson_string_cstr(cson_value_get_string(val));
+ b = (str && *str) ? 1 : 0;
+ break;
+ }
+ case CSON_TYPE_UNDEF:
+ case CSON_TYPE_NULL:
+ break;
+ case CSON_TYPE_BOOL:
+ b = (NULL==val->value) ? 0 : 1;
+ break;
+ case CSON_TYPE_INTEGER: {
+ cson_int_t i = 0;
+ cson_value_fetch_integer( val, &i );
+ b = i ? 1 : 0;
+ break;
+ }
+ case CSON_TYPE_DOUBLE: {
+ cson_double_t d = 0.0;
+ cson_value_fetch_double( val, &d );
+ b = (0.0==d) ? 0 : 1;
+ break;
+ }
+ default:
+ rc = cson_rc.TypeError;
+ break;
+ }
+ if( v ) *v = b;
+ return rc;
+ }
+}
+
+char cson_value_get_bool( cson_value const * val )
+{
+ char i = 0;
+ cson_value_fetch_bool( val, &i );
+ return i;
+}
+
+int cson_value_fetch_integer( cson_value const * val, cson_int_t * v )
+{
+ if( ! val || !val->api ) return cson_rc.ArgError;
+ else
+ {
+ cson_int_t i = 0;
+ int rc = 0;
+ switch(val->api->typeID)
+ {
+ case CSON_TYPE_UNDEF:
+ case CSON_TYPE_NULL:
+ i = 0;
+ break;
+ case CSON_TYPE_BOOL: {
+ char b = 0;
+ cson_value_fetch_bool( val, &b );
+ i = b;
+ break;
+ }
+ case CSON_TYPE_INTEGER: {
+ cson_int_t const * x = CSON_INT(val);
+ if(!x)
+ {
+ assert( val == &CSON_SPECIAL_VALUES[CSON_VAL_INT_0] );
+ }
+ i = x ? *x : 0;
+ break;
+ }
+ case CSON_TYPE_DOUBLE: {
+ cson_double_t d = 0.0;
+ cson_value_fetch_double( val, &d );
+ i = (cson_int_t)d;
+ break;
+ }
+ case CSON_TYPE_STRING:
+ case CSON_TYPE_ARRAY:
+ case CSON_TYPE_OBJECT:
+ default:
+ break;
+ }
+ if(v) *v = i;
+ return rc;
+ }
+}
+
+cson_int_t cson_value_get_integer( cson_value const * val )
+{
+ cson_int_t i = 0;
+ cson_value_fetch_integer( val, &i );
+ return i;
+}
+
+int cson_value_fetch_double( cson_value const * val, cson_double_t * v )
+{
+ if( ! val || !val->api ) return cson_rc.ArgError;
+ else
+ {
+ cson_double_t d = 0.0;
+ int rc = 0;
+ switch(val->api->typeID)
+ {
+ case CSON_TYPE_UNDEF:
+ case CSON_TYPE_NULL:
+ d = 0;
+ break;
+ case CSON_TYPE_BOOL: {
+ char b = 0;
+ cson_value_fetch_bool( val, &b );
+ d = b ? 1.0 : 0.0;
+ break;
+ }
+ case CSON_TYPE_INTEGER: {
+ cson_int_t i = 0;
+ cson_value_fetch_integer( val, &i );
+ d = i;
+ break;
+ }
+ case CSON_TYPE_DOUBLE: {
+ cson_double_t const* dv = CSON_DBL(val);
+ d = dv ? *dv : 0.0;
+ break;
+ }
+ default:
+ rc = cson_rc.TypeError;
+ break;
+ }
+ if(v) *v = d;
+ return rc;
+ }
+}
+
+cson_double_t cson_value_get_double( cson_value const * val )
+{
+ cson_double_t i = 0.0;
+ cson_value_fetch_double( val, &i );
+ return i;
+}
+
+int cson_value_fetch_string( cson_value const * val, cson_string ** dest )
+{
+ if( ! val || ! dest ) return cson_rc.ArgError;
+ else if( ! cson_value_is_string(val) ) return cson_rc.TypeError;
+ else
+ {
+ if( dest ) *dest = CSON_STR(val);
+ return 0;
+ }
+}
+
+cson_string * cson_value_get_string( cson_value const * val )
+{
+ cson_string * rc = NULL;
+ cson_value_fetch_string( val, &rc );
+ return rc;
+}
+
+char const * cson_value_get_cstr( cson_value const * val )
+{
+ return cson_string_cstr( cson_value_get_string(val) );
+}
+
+int cson_value_fetch_object( cson_value const * val, cson_object ** obj )
+{
+ if( ! val ) return cson_rc.ArgError;
+ else if( ! cson_value_is_object(val) ) return cson_rc.TypeError;
+ else
+ {
+ if(obj) *obj = CSON_OBJ(val);
+ return 0;
+ }
+}
+cson_object * cson_value_get_object( cson_value const * v )
+{
+ cson_object * obj = NULL;
+ cson_value_fetch_object( v, &obj );
+ return obj;
+}
+
+int cson_value_fetch_array( cson_value const * val, cson_array ** ar)
+{
+ if( ! val ) return cson_rc.ArgError;
+ else if( !cson_value_is_array(val) ) return cson_rc.TypeError;
+ else
+ {
+ if(ar) *ar = CSON_ARRAY(val);
+ return 0;
+ }
+}
+
+cson_array * cson_value_get_array( cson_value const * v )
+{
+ cson_array * ar = NULL;
+ cson_value_fetch_array( v, &ar );
+ return ar;
+}
+
+cson_kvp * cson_kvp_alloc()
+{
+ cson_kvp * kvp = (cson_kvp*)cson_malloc(sizeof(cson_kvp),"cson_kvp");
+ if( kvp )
+ {
+ *kvp = cson_kvp_empty;
+ }
+ return kvp;
+}
+
+
+
+int cson_array_append( cson_array * ar, cson_value * v )
+{
+ if( !ar || !v ) return cson_rc.ArgError;
+ else if( (ar->list.count+1) < ar->list.count ) return cson_rc.RangeError;
+ else
+ {
+ if( !ar->list.alloced || (ar->list.count == ar->list.alloced-1))
+ {
+ unsigned int const n = ar->list.count ? (ar->list.count*2) : 7;
+ if( n > cson_value_list_reserve( &ar->list, n ) )
+ {
+ return cson_rc.AllocError;
+ }
+ }
+ return cson_array_set( ar, ar->list.count, v );
+ }
+}
+
+#if 0
+/**
+ Removes and returns the last value from the given array,
+ shrinking its size by 1. Returns NULL if ar is NULL,
+ ar->list.count is 0, or the element at that index is NULL.
+
+
+ If removeRef is true then cson_value_free() is called to remove
+ ar's reference count for the value. In that case NULL is returned,
+ even if the object still has live references. If removeRef is false
+ then the caller takes over ownership of that reference count point.
+
+ If removeRef is false then the caller takes over ownership
+ of the return value, otherwise ownership is effectively
+ determined by any remaining references for the returned
+ value.
+*/
+static cson_value * cson_array_pop_back( cson_array * ar,
+ char removeRef )
+{
+ if( !ar ) return NULL;
+ else if( ! ar->list.count ) return NULL;
+ else
+ {
+ unsigned int const ndx = --ar->list.count;
+ cson_value * v = ar->list.list[ndx];
+ ar->list.list[ndx] = NULL;
+ if( removeRef )
+ {
+ cson_value_free( v );
+ v = NULL;
+ }
+ return v;
+ }
+}
+#endif
+
+cson_value * cson_value_new_bool( char v )
+{
+ return v ? &CSON_SPECIAL_VALUES[CSON_VAL_TRUE] : &CSON_SPECIAL_VALUES[CSON_VAL_FALSE];
+}
+
+cson_value * cson_value_true()
+{
+ return &CSON_SPECIAL_VALUES[CSON_VAL_TRUE];
+}
+cson_value * cson_value_false()
+{
+ return &CSON_SPECIAL_VALUES[CSON_VAL_FALSE];
+}
+
+cson_value * cson_value_null()
+{
+ return &CSON_SPECIAL_VALUES[CSON_VAL_NULL];
+}
+
+cson_value * cson_new_int( cson_int_t v )
+{
+ return cson_value_new_integer(v);
+}
+
+cson_value * cson_value_new_integer( cson_int_t v )
+{
+ if( 0 == v ) return &CSON_SPECIAL_VALUES[CSON_VAL_INT_0];
+ else
+ {
+ cson_value * c = cson_value_new(CSON_TYPE_INTEGER,0);
+
+ if( c )
+ {
+ *CSON_INT(c) = v;
+ }
+ return c;
+ }
+}
+
+cson_value * cson_new_double( cson_double_t v )
+{
+ return cson_value_new_double(v);
+}
+
+cson_value * cson_value_new_double( cson_double_t v )
+{
+ if( 0.0 == v ) return &CSON_SPECIAL_VALUES[CSON_VAL_DBL_0];
+ else
+ {
+ cson_value * c = cson_value_new(CSON_TYPE_DOUBLE,0);
+ if( c )
+ {
+ *CSON_DBL(c) = v;
+ }
+ return c;
+ }
+}
+
+cson_string * cson_new_string(char const * str, unsigned int len)
+{
+ if( !str || !*str || !len ) return &CSON_EMPTY_HOLDER.stringValue;
+ else
+ {
+ cson_value * c = cson_value_new(CSON_TYPE_STRING, len + 1/*NUL byte*/);
+ cson_string * s = NULL;
+ if( c )
+ {
+ char * dest = NULL;
+ s = CSON_STR(c);
+ *s = cson_string_empty;
+ assert( NULL != s );
+ s->length = len;
+ dest = cson_string_str(s);
+ assert( NULL != dest );
+ memcpy( dest, str, len );
+ dest[len] = 0;
+ }
+ return s;
+ }
+}
+
+cson_value * cson_value_new_string( char const * str, unsigned int len )
+{
+ return cson_string_value( cson_new_string(str, len) );
+}
+
+int cson_array_value_fetch( cson_array const * ar, unsigned int pos, cson_value ** v )
+{
+ if( !ar) return cson_rc.ArgError;
+ if( pos >= ar->list.count ) return cson_rc.RangeError;
+ else
+ {
+ if(v) *v = ar->list.list[pos];
+ return 0;
+ }
+}
+
+cson_value * cson_array_get( cson_array const * ar, unsigned int pos )
+{
+ cson_value *v = NULL;
+ cson_array_value_fetch(ar, pos, &v);
+ return v;
+}
+
+int cson_array_length_fetch( cson_array const * ar, unsigned int * v )
+{
+ if( ! ar || !v ) return cson_rc.ArgError;
+ else
+ {
+ if(v) *v = ar->list.count;
+ return 0;
+ }
+}
+
+unsigned int cson_array_length_get( cson_array const * ar )
+{
+ unsigned int i = 0;
+ cson_array_length_fetch(ar, &i);
+ return i;
+}
+
+int cson_array_reserve( cson_array * ar, unsigned int size )
+{
+ if( ! ar ) return cson_rc.ArgError;
+ else if( size <= ar->list.alloced )
+ {
+ /* We don't want to introduce a can of worms by trying to
+ handle the cleanup from here.
+ */
+ return 0;
+ }
+ else
+ {
+ return (ar->list.alloced > cson_value_list_reserve( &ar->list, size ))
+ ? cson_rc.AllocError
+ : 0
+ ;
+ }
+}
+
+int cson_array_set( cson_array * ar, unsigned int ndx, cson_value * v )
+{
+ if( !ar || !v ) return cson_rc.ArgError;
+ else if( (ndx+1) < ndx) /* overflow */return cson_rc.RangeError;
+ else
+ {
+ unsigned const int len = cson_value_list_reserve( &ar->list, ndx+1 );
+ if( len <= ndx ) return cson_rc.AllocError;
+ else
+ {
+ cson_value * old = ar->list.list[ndx];
+ if( old )
+ {
+ if(old == v) return 0;
+ else cson_value_free(old);
+ }
+ cson_refcount_incr( v );
+ ar->list.list[ndx] = v;
+ if( ndx >= ar->list.count )
+ {
+ ar->list.count = ndx+1;
+ }
+ return 0;
+ }
+ }
+}
+
+/** @internal
+
+ Searchs for the given key in the given object.
+
+ Returns the found item on success, NULL on error. If ndx is not
+ NULL, it is set to the index (in obj->kvp.list) of the found
+ item. *ndx is not modified if no entry is found.
+*/
+static cson_kvp * cson_object_search_impl( cson_object const * obj, char const * key, unsigned int * ndx )
+{
+ if( obj && key && *key && obj->kvp.count)
+ {
+#if CSON_OBJECT_PROPS_SORT
+ cson_kvp ** s = (cson_kvp**)
+ bsearch( key, obj->kvp.list,
+ obj->kvp.count, sizeof(cson_kvp*),
+ cson_kvp_cmp_vs_cstr );
+ if( ndx && s )
+ { /* index of found record is required by
+ cson_object_unset(). Calculate the offset based on s...*/
+#if 0
+ *ndx = (((unsigned char const *)s - ((unsigned char const *)obj->kvp.list))
+ / sizeof(cson_kvp*));
+#else
+ *ndx = s - obj->kvp.list;
+#endif
+ }
+ return s ? *s : NULL;
+#else
+ cson_kvp_list const * li = &obj->kvp;
+ unsigned int i = 0;
+ cson_kvp * kvp;
+ const unsigned int klen = strlen(key);
+ for( ; i < li->count; ++i )
+ {
+ cson_string const * sKey;
+ kvp = li->list[i];
+ assert( kvp && kvp->key );
+ sKey = cson_value_get_string(kvp->key);
+ assert(sKey);
+ if( sKey->length != klen ) continue;
+ else if(0==strcmp(key,cson_string_cstr(sKey)))
+ {
+ if(ndx) *ndx = i;
+ return kvp;
+ }
+ }
+#endif
+ }
+ return NULL;
+}
+
+cson_value * cson_object_get( cson_object const * obj, char const * key )
+{
+ cson_kvp * kvp = cson_object_search_impl( obj, key, NULL );
+ return kvp ? kvp->value : NULL;
+}
+
+cson_value * cson_object_get_s( cson_object const * obj, cson_string const *key )
+{
+ cson_kvp * kvp = cson_object_search_impl( obj, cson_string_cstr(key), NULL );
+ return kvp ? kvp->value : NULL;
+}
+
+
+#if CSON_OBJECT_PROPS_SORT
+static void cson_object_sort_props( cson_object * obj )
+{
+ assert( NULL != obj );
+ if( obj->kvp.count )
+ {
+ qsort( obj->kvp.list, obj->kvp.count, sizeof(cson_kvp*),
+ cson_kvp_cmp );
+ }
+
+}
+#endif
+
+int cson_object_unset( cson_object * obj, char const * key )
+{
+ if( ! obj || !key || !*key ) return cson_rc.ArgError;
+ else
+ {
+ unsigned int ndx = 0;
+ cson_kvp * kvp = cson_object_search_impl( obj, key, &ndx );
+ if( ! kvp )
+ {
+ return cson_rc.NotFoundError;
+ }
+ assert( obj->kvp.count > 0 );
+ assert( obj->kvp.list[ndx] == kvp );
+ cson_kvp_free( kvp );
+ obj->kvp.list[ndx] = NULL;
+ { /* if my brain were bigger i'd use memmove(). */
+ unsigned int i = ndx;
+ for( ; i < obj->kvp.count; ++i )
+ {
+ obj->kvp.list[i] =
+ (i < (obj->kvp.alloced-1))
+ ? obj->kvp.list[i+1]
+ : NULL;
+ }
+ }
+ obj->kvp.list[--obj->kvp.count] = NULL;
+#if CSON_OBJECT_PROPS_SORT
+ cson_object_sort_props( obj );
+#endif
+ return 0;
+ }
+}
+
+int cson_object_set_s( cson_object * obj, cson_string * key, cson_value * v )
+{
+ if( !obj || !key ) return cson_rc.ArgError;
+ else if( NULL == v ) return cson_object_unset( obj, cson_string_cstr(key) );
+ else
+ {
+ char const * cKey;
+ cson_value * vKey;
+ cson_kvp * kvp;
+ vKey = cson_string_value(key);
+ assert(vKey && (key==CSON_STR(vKey)));
+ if( vKey == CSON_VCAST(obj) ){
+ return cson_rc.ArgError;
+ }
+ cKey = cson_string_cstr(key);
+ kvp = cson_object_search_impl( obj, cKey, NULL );
+ if( kvp )
+ { /* "I told 'em we've already got one!" */
+ if( kvp->key != vKey ){
+ cson_value_free( kvp->key );
+ cson_refcount_incr(vKey);
+ kvp->key = vKey;
+ }
+ if(kvp->value != v){
+ cson_value_free( kvp->value );
+ cson_refcount_incr( v );
+ kvp->value = v;
+ }
+ return 0;
+ }
+ if( !obj->kvp.alloced || (obj->kvp.count == obj->kvp.alloced-1))
+ {
+ unsigned int const n = obj->kvp.count ? (obj->kvp.count*2) : 6;
+ if( n > cson_kvp_list_reserve( &obj->kvp, n ) )
+ {
+ return cson_rc.AllocError;
+ }
+ }
+ { /* insert new item... */
+ int rc = 0;
+ kvp = cson_kvp_alloc();
+ if( ! kvp )
+ {
+ return cson_rc.AllocError;
+ }
+ rc = cson_kvp_list_append( &obj->kvp, kvp );
+ if( 0 != rc )
+ {
+ cson_kvp_free(kvp);
+ }
+ else
+ {
+ cson_refcount_incr(vKey);
+ cson_refcount_incr(v);
+ kvp->key = vKey;
+ kvp->value = v;
+#if CSON_OBJECT_PROPS_SORT
+ cson_object_sort_props( obj );
+#endif
+ }
+ return rc;
+ }
+ }
+
+}
+int cson_object_set( cson_object * obj, char const * key, cson_value * v )
+{
+ if( ! obj || !key || !*key ) return cson_rc.ArgError;
+ else if( NULL == v )
+ {
+ return cson_object_unset( obj, key );
+ }
+ else
+ {
+ cson_string * cs = cson_new_string(key,strlen(key));
+ if(!cs) return cson_rc.AllocError;
+ else
+ {
+ int const rc = cson_object_set_s(obj, cs, v);
+ if(rc) cson_value_free(cson_string_value(cs));
+ return rc;
+ }
+ }
+}
+
+cson_value * cson_object_take( cson_object * obj, char const * key )
+{
+ if( ! obj || !key || !*key ) return NULL;
+ else
+ {
+ /* FIXME: this is 90% identical to cson_object_unset(),
+ only with different refcount handling.
+ Consolidate them.
+ */
+ unsigned int ndx = 0;
+ cson_kvp * kvp = cson_object_search_impl( obj, key, &ndx );
+ cson_value * rc = NULL;
+ if( ! kvp )
+ {
+ return NULL;
+ }
+ assert( obj->kvp.count > 0 );
+ assert( obj->kvp.list[ndx] == kvp );
+ rc = kvp->value;
+ assert( rc );
+ kvp->value = NULL;
+ cson_kvp_free( kvp );
+ assert( rc->refcount > 0 );
+ --rc->refcount;
+ obj->kvp.list[ndx] = NULL;
+ { /* if my brain were bigger i'd use memmove(). */
+ unsigned int i = ndx;
+ for( ; i < obj->kvp.count; ++i )
+ {
+ obj->kvp.list[i] =
+ (i < (obj->kvp.alloced-1))
+ ? obj->kvp.list[i+1]
+ : NULL;
+ }
+ }
+ obj->kvp.list[--obj->kvp.count] = NULL;
+#if CSON_OBJECT_PROPS_SORT
+ cson_object_sort_props( obj );
+#endif
+ return rc;
+ }
+}
+/** @internal
+
+ If p->node is-a Object then value is inserted into the object
+ using p->key. In any other case cson_rc.InternalError is returned.
+
+ Returns cson_rc.AllocError if an allocation fails.
+
+ Returns 0 on success. On error, parsing must be ceased immediately.
+
+ Ownership of val is ALWAYS TRANSFERED to this function. If this
+ function fails, val will be cleaned up and destroyed. (This
+ simplifies error handling in the core parser.)
+*/
+static int cson_parser_set_key( cson_parser * p, cson_value * val )
+{
+ assert( p && val );
+
+ if( p->ckey && cson_value_is_object(p->node) )
+ {
+ int rc;
+ cson_object * obj = cson_value_get_object(p->node);
+ cson_kvp * kvp = NULL;
+ assert( obj && (p->node->value == obj) );
+ /**
+ FIXME? Use cson_object_set() instead of our custom
+ finagling with the object? We do it this way to avoid an
+ extra alloc/strcpy of the key data.
+ */
+ if( !obj->kvp.alloced || (obj->kvp.count == obj->kvp.alloced-1))
+ {
+ if( obj->kvp.alloced > cson_kvp_list_reserve( &obj->kvp, obj->kvp.count ? (obj->kvp.count*2) : 5 ) )
+ {
+ cson_value_free(val);
+ return cson_rc.AllocError;
+ }
+ }
+ kvp = cson_kvp_alloc();
+ if( ! kvp )
+ {
+ cson_value_free(val);
+ return cson_rc.AllocError;
+ }
+ kvp->key = cson_string_value(p->ckey)/*transfer ownership*/;
+ p->ckey = NULL;
+ kvp->value = val;
+ cson_refcount_incr( val );
+ rc = cson_kvp_list_append( &obj->kvp, kvp );
+ if( 0 != rc )
+ {
+ cson_kvp_free( kvp );
+ }
+ else
+ {
+ ++p->totalValueCount;
+ }
+ return rc;
+ }
+ else
+ {
+ if(val) cson_value_free(val);
+ return p->errNo = cson_rc.InternalError;
+ }
+
+}
+
+/** @internal
+
+ Pushes val into the current object/array parent node, depending on the
+ internal state of the parser.
+
+ Ownership of val is always transfered to this function, regardless of
+ success or failure.
+
+ Returns 0 on success. On error, parsing must be ceased immediately.
+*/
+static int cson_parser_push_value( cson_parser * p, cson_value * val )
+{
+ if( p->ckey )
+ { /* we're in Object mode */
+ assert( cson_value_is_object( p->node ) );
+ return cson_parser_set_key( p, val );
+ }
+ else if( cson_value_is_array( p->node ) )
+ { /* we're in Array mode */
+ cson_array * ar = cson_value_get_array( p->node );
+ int rc;
+ assert( ar && (ar == p->node->value) );
+ rc = cson_array_append( ar, val );
+ if( 0 != rc )
+ {
+ cson_value_free(val);
+ }
+ else
+ {
+ ++p->totalValueCount;
+ }
+ return rc;
+ }
+ else
+ { /* WTF? */
+ assert( 0 && "Internal error in cson_parser code" );
+ return p->errNo = cson_rc.InternalError;
+ }
+}
+
+/**
+ Callback for JSON_parser API. Reminder: it returns 0 (meaning false)
+ on error!
+*/
+static int cson_parse_callback( void * cx, int type, JSON_value const * value )
+{
+ cson_parser * p = (cson_parser *)cx;
+ int rc = 0;
+#define ALLOC_V(T,V) cson_value * v = cson_value_new_##T(V); if( ! v ) { rc = cson_rc.AllocError; break; }
+ switch(type) {
+ case JSON_T_ARRAY_BEGIN:
+ case JSON_T_OBJECT_BEGIN: {
+ cson_value * obja = (JSON_T_ARRAY_BEGIN == type)
+ ? cson_value_new_array()
+ : cson_value_new_object();
+ if( ! obja )
+ {
+ p->errNo = cson_rc.AllocError;
+ return 0;
+ }
+ if( 0 != rc ) break;
+ if( ! p->root )
+ {
+ p->root = p->node = obja;
+ rc = cson_array_append( &p->stack, obja );
+ if( 0 != rc )
+ { /* work around a (potential) corner case in the cleanup code. */
+ cson_value_free( p->root );
+ p->root = NULL;
+ }
+ else
+ {
+ cson_refcount_incr( p->root )
+ /* simplifies cleanup later on. */
+ ;
+ ++p->totalValueCount;
+ }
+ }
+ else
+ {
+ rc = cson_array_append( &p->stack, obja );
+ if( 0 == rc ) rc = cson_parser_push_value( p, obja );
+ if( 0 == rc ) p->node = obja;
+ }
+ break;
+ }
+ case JSON_T_ARRAY_END:
+ case JSON_T_OBJECT_END: {
+ if( 0 == p->stack.list.count )
+ {
+ rc = cson_rc.RangeError;
+ break;
+ }
+#if CSON_OBJECT_PROPS_SORT
+ if( cson_value_is_object(p->node) )
+ {/* kludge: the parser uses custom cson_object property
+ insertion as a malloc/strcpy-reduction optimization.
+ Because of that, we have to sort the property list
+ ourselves...
+ */
+ cson_object * obj = cson_value_get_object(p->node);
+ assert( NULL != obj );
+ cson_object_sort_props( obj );
+ }
+#endif
+
+#if 1
+ /* Reminder: do not use cson_array_pop_back( &p->stack )
+ because that will clean up the object, and we don't want
+ that. We just want to forget this reference
+ to it. The object is either the root or was pushed into
+ an object/array in the parse tree (and is owned by that
+ object/array).
+ */
+ --p->stack.list.count;
+ assert( p->node == p->stack.list.list[p->stack.list.count] );
+ cson_refcount_decr( p->node )
+ /* p->node might be owned by an outer object but we
+ need to remove the list's reference. For the
+ root node we manually add a reference to
+ avoid a special case here. Thus when we close
+ the root node, its refcount is still 1.
+ */;
+ p->stack.list.list[p->stack.list.count] = NULL;
+ if( p->stack.list.count )
+ {
+ p->node = p->stack.list.list[p->stack.list.count-1];
+ }
+ else
+ {
+ p->node = p->root;
+ }
+#else
+ /*
+ Causing a leak?
+ */
+ cson_array_pop_back( &p->stack, 1 );
+ if( p->stack.list.count )
+ {
+ p->node = p->stack.list.list[p->stack.list.count-1];
+ }
+ else
+ {
+ p->node = p->root;
+ }
+ assert( p->node && (1==p->node->refcount) );
+#endif
+ break;
+ }
+ case JSON_T_INTEGER: {
+ ALLOC_V(integer, value->vu.integer_value );
+ rc = cson_parser_push_value( p, v );
+ break;
+ }
+ case JSON_T_FLOAT: {
+ ALLOC_V(double, value->vu.float_value );
+ rc = cson_parser_push_value( p, v );
+ break;
+ }
+ case JSON_T_NULL: {
+ rc = cson_parser_push_value( p, cson_value_null() );
+ break;
+ }
+ case JSON_T_TRUE: {
+ rc = cson_parser_push_value( p, cson_value_true() );
+ break;
+ }
+ case JSON_T_FALSE: {
+ rc = cson_parser_push_value( p, cson_value_false() );
+ break;
+ }
+ case JSON_T_KEY: {
+ assert(!p->ckey);
+ p->ckey = cson_new_string( value->vu.str.value, value->vu.str.length );
+ if( ! p->ckey )
+ {
+ rc = cson_rc.AllocError;
+ break;
+ }
+ ++p->totalKeyCount;
+ break;
+ }
+ case JSON_T_STRING: {
+ cson_value * v = cson_value_new_string( value->vu.str.value, value->vu.str.length );
+ rc = ( NULL == v )
+ ? cson_rc.AllocError
+ : cson_parser_push_value( p, v );
+ break;
+ }
+ default:
+ assert(0);
+ rc = cson_rc.InternalError;
+ break;
+ }
+#undef ALLOC_V
+ return ((p->errNo = rc)) ? 0 : 1;
+}
+
+
+/**
+ Converts a JSON_error code to one of the cson_rc values.
+*/
+static int cson_json_err_to_rc( JSON_error jrc )
+{
+ switch(jrc)
+ {
+ case JSON_E_NONE: return 0;
+ case JSON_E_INVALID_CHAR: return cson_rc.Parse_INVALID_CHAR;
+ case JSON_E_INVALID_KEYWORD: return cson_rc.Parse_INVALID_KEYWORD;
+ case JSON_E_INVALID_ESCAPE_SEQUENCE: return cson_rc.Parse_INVALID_ESCAPE_SEQUENCE;
+ case JSON_E_INVALID_UNICODE_SEQUENCE: return cson_rc.Parse_INVALID_UNICODE_SEQUENCE;
+ case JSON_E_INVALID_NUMBER: return cson_rc.Parse_INVALID_NUMBER;
+ case JSON_E_NESTING_DEPTH_REACHED: return cson_rc.Parse_NESTING_DEPTH_REACHED;
+ case JSON_E_UNBALANCED_COLLECTION: return cson_rc.Parse_UNBALANCED_COLLECTION;
+ case JSON_E_EXPECTED_KEY: return cson_rc.Parse_EXPECTED_KEY;
+ case JSON_E_EXPECTED_COLON: return cson_rc.Parse_EXPECTED_COLON;
+ case JSON_E_OUT_OF_MEMORY: return cson_rc.AllocError;
+ default:
+ return cson_rc.InternalError;
+ }
+}
+
+/** @internal
+
+ Cleans up all contents of p but does not free p.
+
+ To properly take over ownership of the parser's root node on a
+ successful parse:
+
+ - Copy p->root's pointer and set p->root to NULL.
+ - Eventually free up p->root with cson_value_free().
+
+ If you do not set p->root to NULL, p->root will be freed along with
+ any other items inserted into it (or under it) during the parsing
+ process.
+*/
+static int cson_parser_clean( cson_parser * p )
+{
+ if( ! p ) return cson_rc.ArgError;
+ else
+ {
+ if( p->p )
+ {
+ delete_JSON_parser(p->p);
+ p->p = NULL;
+ }
+ if( p->ckey ){
+ cson_value_free(cson_string_value(p->ckey));
+ }
+ cson_array_clean( &p->stack, 1 );
+ if( p->root )
+ {
+ cson_value_free( p->root );
+ }
+ *p = cson_parser_empty;
+ return 0;
+ }
+}
+
+
+int cson_parse( cson_value ** tgt, cson_data_source_f src, void * state,
+ cson_parse_opt const * opt_, cson_parse_info * info_ )
+{
+ unsigned char ch[2] = {0,0};
+ cson_parse_opt const opt = opt_ ? *opt_ : cson_parse_opt_empty;
+ int rc = 0;
+ unsigned int len = 1;
+ cson_parse_info info = info_ ? *info_ : cson_parse_info_empty;
+ cson_parser p = cson_parser_empty;
+ if( ! tgt || ! src ) return cson_rc.ArgError;
+
+ {
+ JSON_config jopt = {0};
+ init_JSON_config( &jopt );
+ jopt.allow_comments = opt.allowComments;
+ jopt.depth = opt.maxDepth;
+ jopt.callback_ctx = &p;
+ jopt.handle_floats_manually = 0;
+ jopt.callback = cson_parse_callback;
+ p.p = new_JSON_parser(&jopt);
+ if( ! p.p )
+ {
+ return cson_rc.AllocError;
+ }
+ }
+
+ do
+ { /* FIXME: buffer the input in multi-kb chunks. */
+ len = 1;
+ ch[0] = 0;
+ rc = src( state, ch, &len );
+ if( 0 != rc ) break;
+ else if( !len /* EOF */ ) break;
+ ++info.length;
+ if('\n' == ch[0])
+ {
+ ++info.line;
+ info.col = 0;
+ }
+ if( ! JSON_parser_char(p.p, ch[0]) )
+ {
+ rc = cson_json_err_to_rc( JSON_parser_get_last_error(p.p) );
+ if(0==rc) rc = p.errNo;
+ if(0==rc) rc = cson_rc.InternalError;
+ info.errorCode = rc;
+ break;
+ }
+ if( '\n' != ch[0]) ++info.col;
+ } while(1);
+ if( info_ )
+ {
+ info.totalKeyCount = p.totalKeyCount;
+ info.totalValueCount = p.totalValueCount;
+ *info_ = info;
+ }
+ if( 0 != rc )
+ {
+ cson_parser_clean(&p);
+ return rc;
+ }
+ if( ! JSON_parser_done(p.p) )
+ {
+ rc = cson_json_err_to_rc( JSON_parser_get_last_error(p.p) );
+ cson_parser_clean(&p);
+ if(0==rc) rc = p.errNo;
+ if(0==rc) rc = cson_rc.InternalError;
+ }
+ else
+ {
+ cson_value * root = p.root;
+ p.root = NULL;
+ cson_parser_clean(&p);
+ if( root )
+ {
+ assert( (1 == root->refcount) && "Detected memory mismanagement in the parser." );
+ root->refcount = 0
+ /* HUGE KLUDGE! Avoids having one too many references
+ in some client code, leading to a leak. Here we're
+ accommodating a memory management workaround in the
+ parser code which manually adds a reference to the
+ root node to keep it from being cleaned up
+ prematurely.
+ */;
+ *tgt = root;
+ }
+ else
+ { /* then can happen on empty input. */
+ rc = cson_rc.UnknownError;
+ }
+ }
+ return rc;
+}
+
+/**
+ The UTF code was originally taken from sqlite3's public-domain
+ source code (http://sqlite.org), modified only slightly for use
+ here. This code generates some "possible data loss" warnings on
+ MSVC, but if this code is good enough for sqlite3 then it's damned
+ well good enough for me, so we disable that warning for Windows
+ builds.
+*/
+
+/*
+** This lookup table is used to help decode the first byte of
+** a multi-byte UTF8 character.
+*/
+static const unsigned char cson_utfTrans1[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00
+};
+
+
+/*
+** Translate a single UTF-8 character. Return the unicode value.
+**
+** During translation, assume that the byte that zTerm points
+** is a 0x00.
+**
+** Write a pointer to the next unread byte back into *pzNext.
+**
+** Notes On Invalid UTF-8:
+**
+** * This routine never allows a 7-bit character (0x00 through 0x7f) to
+** be encoded as a multi-byte character. Any multi-byte character that
+** attempts to encode a value between 0x00 and 0x7f is rendered as 0xfffd.
+**
+** * This routine never allows a UTF16 surrogate value to be encoded.
+** If a multi-byte character attempts to encode a value between
+** 0xd800 and 0xe000 then it is rendered as 0xfffd.
+**
+** * Bytes in the range of 0x80 through 0xbf which occur as the first
+** byte of a character are interpreted as single-byte characters
+** and rendered as themselves even though they are technically
+** invalid characters.
+**
+** * This routine accepts an infinite number of different UTF8 encodings
+** for unicode values 0x80 and greater. It do not change over-length
+** encodings to 0xfffd as some systems recommend.
+*/
+#define READ_UTF8(zIn, zTerm, c) \
+ c = *(zIn++); \
+ if( c>=0xc0 ){ \
+ c = cson_utfTrans1[c-0xc0]; \
+ while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ){ \
+ c = (c<<6) + (0x3f & *(zIn++)); \
+ } \
+ if( c<0x80 \
+ || (c&0xFFFFF800)==0xD800 \
+ || (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; } \
+ }
+static int cson_utf8Read(
+ const unsigned char *z, /* First byte of UTF-8 character */
+ const unsigned char *zTerm, /* Pretend this byte is 0x00 */
+ const unsigned char **pzNext /* Write first byte past UTF-8 char here */
+){
+ int c;
+ READ_UTF8(z, zTerm, c);
+ *pzNext = z;
+ return c;
+}
+#undef READ_UTF8
+
+#if defined(_WIN32)
+# pragma warning( pop )
+#endif
+
+unsigned int cson_string_length_utf8( cson_string const * str )
+{
+ if( ! str ) return 0;
+ else
+ {
+ char unsigned const * pos = (char unsigned const *)cson_string_cstr(str);
+ char unsigned const * end = pos + str->length;
+ unsigned int rc = 0;
+ for( ; (pos < end) && cson_utf8Read(pos, end, &pos);
+ ++rc )
+ {
+ };
+ return rc;
+ }
+}
+
+/**
+ Escapes the first len bytes of the given string as JSON and sends
+ it to the given output function (which will be called often - once
+ for each logical character). The output is also surrounded by
+ double-quotes.
+
+ A NULL str will be escaped as an empty string, though we should
+ arguably export it as "null" (without quotes). We do this because
+ in JavaScript (typeof null === "object"), and by outputing null
+ here we would effectively change the data type from string to
+ object.
+*/
+static int cson_str_to_json( char const * str, unsigned int len,
+ char escapeFwdSlash,
+ cson_data_dest_f f, void * state )
+{
+ if( NULL == f ) return cson_rc.ArgError;
+ else if( !str || !*str || (0 == len) )
+ { /* special case for 0-length strings. */
+ return f( state, "\"\"", 2 );
+ }
+ else
+ {
+ unsigned char const * pos = (unsigned char const *)str;
+ unsigned char const * end = (unsigned char const *)(str ? (str + len) : NULL);
+ unsigned char const * next = NULL;
+ int ch;
+ unsigned char clen = 0;
+ char escChar[3] = {'\\',0,0};
+ enum { UBLen = 8 };
+ char ubuf[UBLen];
+ int rc = 0;
+ rc = f(state, "\"", 1 );
+ for( ; (pos < end) && (0 == rc); pos += clen )
+ {
+ ch = cson_utf8Read(pos, end, &next);
+ if( 0 == ch ) break;
+ assert( next > pos );
+ clen = next - pos;
+ assert( clen );
+ if( 1 == clen )
+ { /* ASCII */
+ assert( *pos == ch );
+ escChar[1] = 0;
+ switch(ch)
+ {
+ case '\t': escChar[1] = 't'; break;
+ case '\r': escChar[1] = 'r'; break;
+ case '\n': escChar[1] = 'n'; break;
+ case '\f': escChar[1] = 'f'; break;
+ case '\b': escChar[1] = 'b'; break;
+ case '/':
+ /*
+ Regarding escaping of forward-slashes. See the main exchange below...
+
+ --------------
+ From: Douglas Crockford
+ To: Stephan Beal
+ Subject: Re: Is escaping of forward slashes required?
+
+ It is allowed, not required. It is allowed so that JSON can be safely
+ embedded in HTML, which can freak out when seeing strings containing
+ "". JSON tolerates "<\/" for this reason.
+
+ On 4/8/2011 2:09 PM, Stephan Beal wrote:
+ > Hello, Jsonites,
+ >
+ > i'm a bit confused on a small grammatic detail of JSON:
+ >
+ > if i'm reading the grammar chart on http://www.json.org/ correctly,
+ > forward slashes (/) are supposed to be escaped in JSON. However, the
+ > JSON class provided with my browsers (Chrome and FF, both of which i
+ > assume are fairly standards/RFC-compliant) do not escape such characters.
+ >
+ > Is backslash-escaping forward slashes required? If so, what is the
+ > justification for it? (i ask because i find it unnecessary and hard to
+ > look at.)
+ --------------
+ */
+ if( escapeFwdSlash ) escChar[1] = '/';
+ break;
+ case '\\': escChar[1] = '\\'; break;
+ case '"': escChar[1] = '"'; break;
+ default: break;
+ }
+ if( escChar[1])
+ {
+ rc = f(state, escChar, 2);
+ }
+ else
+ {
+ rc = f(state, (char const *)pos, clen);
+ }
+ continue;
+ }
+ else
+ { /* UTF: transform it to \uXXXX */
+ memset(ubuf,0,UBLen);
+ rc = sprintf(ubuf, "\\u%04x",ch);
+ if( rc != 6 )
+ {
+ rc = cson_rc.RangeError;
+ break;
+ }
+ rc = f( state, ubuf, 6 );
+ continue;
+ }
+ }
+ if( 0 == rc )
+ {
+ rc = f(state, "\"", 1 );
+ }
+ return rc;
+ }
+}
+
+int cson_object_iter_init( cson_object const * obj, cson_object_iterator * iter )
+{
+ if( ! obj || !iter ) return cson_rc.ArgError;
+ else
+ {
+ iter->obj = obj;
+ iter->pos = 0;
+ return 0;
+ }
+}
+
+cson_kvp * cson_object_iter_next( cson_object_iterator * iter )
+{
+ if( ! iter || !iter->obj ) return NULL;
+ else if( iter->pos >= iter->obj->kvp.count ) return NULL;
+ else
+ {
+ cson_kvp * rc = iter->obj->kvp.list[iter->pos++];
+ while( (NULL==rc) && (iter->pos < iter->obj->kvp.count))
+ {
+ rc = iter->obj->kvp.list[iter->pos++];
+ }
+ return rc;
+ }
+}
+
+static int cson_output_null( cson_data_dest_f f, void * state )
+{
+ if( !f ) return cson_rc.ArgError;
+ else
+ {
+ return f(state, "null", 4);
+ }
+}
+
+static int cson_output_bool( cson_value const * src, cson_data_dest_f f, void * state )
+{
+ if( !f ) return cson_rc.ArgError;
+ else
+ {
+ char const v = cson_value_get_bool(src);
+ return f(state, v ? "true" : "false", v ? 4 : 5);
+ }
+}
+
+static int cson_output_integer( cson_value const * src, cson_data_dest_f f, void * state )
+{
+ if( !f ) return cson_rc.ArgError;
+ else if( !cson_value_is_integer(src) ) return cson_rc.TypeError;
+ else
+ {
+ enum { BufLen = 100 };
+ char b[BufLen];
+ int rc;
+ memset( b, 0, BufLen );
+ rc = sprintf( b, "%"CSON_INT_T_PFMT, cson_value_get_integer(src) )
+ /* Reminder: snprintf() is C99 */
+ ;
+ return ( rc<=0 )
+ ? cson_rc.RangeError
+ : f( state, b, (unsigned int)rc )
+ ;
+ }
+}
+
+static int cson_output_double( cson_value const * src, cson_data_dest_f f, void * state )
+{
+ if( !f ) return cson_rc.ArgError;
+ else if( !cson_value_is_double(src) ) return cson_rc.TypeError;
+ else
+ {
+ enum { BufLen = 128 /* this must be relatively large or huge
+ doubles can cause us to overrun here,
+ resulting in stack-smashing errors.
+ */};
+ char b[BufLen];
+ int rc;
+ memset( b, 0, BufLen );
+ rc = sprintf( b, "%"CSON_DOUBLE_T_PFMT, cson_value_get_double(src) )
+ /* Reminder: snprintf() is C99 */
+ ;
+ if( rc<=0 ) return cson_rc.RangeError;
+ else if(1)
+ { /* Strip trailing zeroes before passing it on... */
+ unsigned int urc = (unsigned int)rc;
+ char * pos = b + urc - 1;
+ for( ; ('0' == *pos) && urc && (*(pos-1) != '.'); --pos, --urc )
+ {
+ *pos = 0;
+ }
+ assert(urc && *pos);
+ return f( state, b, urc );
+ }
+ else
+ {
+ unsigned int urc = (unsigned int)rc;
+ return f( state, b, urc );
+ }
+ return 0;
+ }
+}
+
+static int cson_output_string( cson_value const * src, char escapeFwdSlash, cson_data_dest_f f, void * state )
+{
+ if( !f ) return cson_rc.ArgError;
+ else if( ! cson_value_is_string(src) ) return cson_rc.TypeError;
+ else
+ {
+ cson_string const * str = cson_value_get_string(src);
+ assert( NULL != str );
+ return cson_str_to_json(cson_string_cstr(str), str->length, escapeFwdSlash, f, state);
+ }
+}
+
+
+/**
+ Outputs indention spacing to f().
+
+ blanks: (0)=no indentation, (1)=1 TAB per/level, (>1)=n spaces/level
+
+ depth is the current depth of the output tree, and determines how much
+ indentation to generate.
+
+ If blanks is 0 this is a no-op. Returns non-0 on error, and the
+ error code will always come from f().
+*/
+static int cson_output_indent( cson_data_dest_f f, void * state,
+ unsigned char blanks, unsigned int depth )
+{
+ if( 0 == blanks ) return 0;
+ else
+ {
+#if 0
+ /* FIXME: stuff the indention into the buffer and make a single
+ call to f().
+ */
+ enum { BufLen = 200 };
+ char buf[BufLen];
+#endif
+ unsigned int i;
+ unsigned int x;
+ char const ch = (1==blanks) ? '\t' : ' ';
+ int rc = f(state, "\n", 1 );
+ for( i = 0; (i < depth) && (0 == rc); ++i )
+ {
+ for( x = 0; (x < blanks) && (0 == rc); ++x )
+ {
+ rc = f(state, &ch, 1);
+ }
+ }
+ return rc;
+ }
+}
+
+static int cson_output_array( cson_value const * src, cson_data_dest_f f, void * state,
+ cson_output_opt const * fmt, unsigned int level );
+static int cson_output_object( cson_value const * src, cson_data_dest_f f, void * state,
+ cson_output_opt const * fmt, unsigned int level );
+/**
+ Main cson_output() implementation. Dispatches to a different impl depending
+ on src->api->typeID.
+
+ Returns 0 on success.
+*/
+static int cson_output_impl( cson_value const * src, cson_data_dest_f f, void * state,
+ cson_output_opt const * fmt, unsigned int level )
+{
+ if( ! src || !f || !src->api ) return cson_rc.ArgError;
+ else
+ {
+ int rc = 0;
+ assert(fmt);
+ switch( src->api->typeID )
+ {
+ case CSON_TYPE_UNDEF:
+ case CSON_TYPE_NULL:
+ rc = cson_output_null(f, state);
+ break;
+ case CSON_TYPE_BOOL:
+ rc = cson_output_bool(src, f, state);
+ break;
+ case CSON_TYPE_INTEGER:
+ rc = cson_output_integer(src, f, state);
+ break;
+ case CSON_TYPE_DOUBLE:
+ rc = cson_output_double(src, f, state);
+ break;
+ case CSON_TYPE_STRING:
+ rc = cson_output_string(src, fmt->escapeForwardSlashes, f, state);
+ break;
+ case CSON_TYPE_ARRAY:
+ rc = cson_output_array( src, f, state, fmt, level );
+ break;
+ case CSON_TYPE_OBJECT:
+ rc = cson_output_object( src, f, state, fmt, level );
+ break;
+ default:
+ rc = cson_rc.TypeError;
+ break;
+ }
+ return rc;
+ }
+}
+
+
+static int cson_output_array( cson_value const * src, cson_data_dest_f f, void * state,
+ cson_output_opt const * fmt, unsigned int level )
+{
+ if( !src || !f || !fmt ) return cson_rc.ArgError;
+ else if( ! cson_value_is_array(src) ) return cson_rc.TypeError;
+ else if( level > fmt->maxDepth ) return cson_rc.RangeError;
+ else
+ {
+ int rc;
+ unsigned int i;
+ cson_value const * v;
+ char doIndent = fmt->indentation ? 1 : 0;
+ cson_array const * ar = cson_value_get_array(src);
+ assert( NULL != ar );
+ if( 0 == ar->list.count )
+ {
+ return f(state, "[]", 2 );
+ }
+ else if( (1 == ar->list.count) && !fmt->indentSingleMemberValues ) doIndent = 0;
+ rc = f(state, "[", 1);
+ ++level;
+ if( doIndent )
+ {
+ rc = cson_output_indent( f, state, fmt->indentation, level );
+ }
+ for( i = 0; (i < ar->list.count) && (0 == rc); ++i )
+ {
+ v = ar->list.list[i];
+ if( v )
+ {
+ rc = cson_output_impl( v, f, state, fmt, level );
+ }
+ else
+ {
+ rc = cson_output_null( f, state );
+ }
+ if( 0 == rc )
+ {
+ if(i < (ar->list.count-1))
+ {
+ rc = f(state, ",", 1);
+ if( 0 == rc )
+ {
+ rc = doIndent
+ ? cson_output_indent( f, state, fmt->indentation, level )
+ : f( state, " ", 1 );
+ }
+ }
+ }
+ }
+ --level;
+ if( doIndent && (0 == rc) )
+ {
+ rc = cson_output_indent( f, state, fmt->indentation, level );
+ }
+ return (0 == rc)
+ ? f(state, "]", 1)
+ : rc;
+ }
+}
+
+static int cson_output_object( cson_value const * src, cson_data_dest_f f, void * state,
+ cson_output_opt const * fmt, unsigned int level )
+{
+ if( !src || !f || !fmt ) return cson_rc.ArgError;
+ else if( ! cson_value_is_object(src) ) return cson_rc.TypeError;
+ else if( level > fmt->maxDepth ) return cson_rc.RangeError;
+ else
+ {
+ int rc;
+ unsigned int i;
+ cson_kvp const * kvp;
+ char doIndent = fmt->indentation ? 1 : 0;
+ cson_object const * obj = cson_value_get_object(src);
+ assert( (NULL != obj) && (NULL != fmt));
+ if( 0 == obj->kvp.count )
+ {
+ return f(state, "{}", 2 );
+ }
+ else if( (1 == obj->kvp.count) && !fmt->indentSingleMemberValues ) doIndent = 0;
+ rc = f(state, "{", 1);
+ ++level;
+ if( doIndent )
+ {
+ rc = cson_output_indent( f, state, fmt->indentation, level );
+ }
+ for( i = 0; (i < obj->kvp.count) && (0 == rc); ++i )
+ {
+ kvp = obj->kvp.list[i];
+ if( kvp && kvp->key )
+ {
+ cson_string const * sKey = cson_value_get_string(kvp->key);
+ char const * cKey = cson_string_cstr(sKey);
+ rc = cson_str_to_json(cKey, sKey->length,
+ fmt->escapeForwardSlashes, f, state);
+ if( 0 == rc )
+ {
+ rc = fmt->addSpaceAfterColon
+ ? f(state, ": ", 2 )
+ : f(state, ":", 1 )
+ ;
+ }
+ if( 0 == rc)
+ {
+ rc = ( kvp->value )
+ ? cson_output_impl( kvp->value, f, state, fmt, level )
+ : cson_output_null( f, state );
+ }
+ }
+ else
+ {
+ assert( 0 && "Possible internal error." );
+ continue /* internal error? */;
+ }
+ if( 0 == rc )
+ {
+ if(i < (obj->kvp.count-1))
+ {
+ rc = f(state, ",", 1);
+ if( 0 == rc )
+ {
+ rc = doIndent
+ ? cson_output_indent( f, state, fmt->indentation, level )
+ : f( state, " ", 1 );
+ }
+ }
+ }
+ }
+ --level;
+ if( doIndent && (0 == rc) )
+ {
+ rc = cson_output_indent( f, state, fmt->indentation, level );
+ }
+ return (0 == rc)
+ ? f(state, "}", 1)
+ : rc;
+ }
+}
+
+int cson_output( cson_value const * src, cson_data_dest_f f,
+ void * state, cson_output_opt const * fmt )
+{
+ int rc;
+ if(! fmt ) fmt = &cson_output_opt_empty;
+ rc = cson_output_impl(src, f, state, fmt, 0 );
+ if( (0 == rc) && fmt->addNewline )
+ {
+ rc = f(state, "\n", 1);
+ }
+ return rc;
+}
+
+int cson_data_dest_FILE( void * state, void const * src, unsigned int n )
+{
+ if( ! state ) return cson_rc.ArgError;
+ else if( !src || !n ) return 0;
+ else
+ {
+ return ( 1 == fwrite( src, n, 1, (FILE*) state ) )
+ ? 0
+ : cson_rc.IOError;
+ }
+}
+
+int cson_output_FILE( cson_value const * src, FILE * dest, cson_output_opt const * fmt )
+{
+ int rc = 0;
+ if( fmt )
+ {
+ rc = cson_output( src, cson_data_dest_FILE, dest, fmt );
+ }
+ else
+ {
+ /* We normally want a newline on FILE output. */
+ cson_output_opt opt = cson_output_opt_empty;
+ opt.addNewline = 1;
+ rc = cson_output( src, cson_data_dest_FILE, dest, &opt );
+ }
+ if( 0 == rc )
+ {
+ fflush( dest );
+ }
+ return rc;
+}
+
+int cson_output_filename( cson_value const * src, char const * dest, cson_output_opt const * fmt )
+{
+ if( !src || !dest ) return cson_rc.ArgError;
+ else
+ {
+ FILE * f = fopen(dest,"wb");
+ if( !f ) return cson_rc.IOError;
+ else
+ {
+ int const rc = cson_output_FILE( src, f, fmt );
+ fclose(f);
+ return rc;
+ }
+ }
+}
+
+int cson_parse_filename( cson_value ** tgt, char const * src,
+ cson_parse_opt const * opt, cson_parse_info * err )
+{
+ if( !src || !tgt ) return cson_rc.ArgError;
+ else
+ {
+ FILE * f = fopen(src, "r");
+ if( !f ) return cson_rc.IOError;
+ else
+ {
+ int const rc = cson_parse_FILE( tgt, f, opt, err );
+ fclose(f);
+ return rc;
+ }
+ }
+}
+
+/** Internal type to hold state for a JSON input string.
+ */
+typedef struct cson_data_source_StringSource_
+{
+ /** Start of input string. */
+ char const * str;
+ /** Current iteration position. Must initially be == str. */
+ char const * pos;
+ /** Logical EOF, one-past-the-end of str. */
+ char const * end;
+} cson_data_source_StringSource_t;
+
+/**
+ A cson_data_source_f() implementation which requires the state argument
+ to be a properly populated (cson_data_source_StringSource_t*).
+*/
+static int cson_data_source_StringSource( void * state, void * dest, unsigned int * n )
+{
+ if( !state || !n || !dest ) return cson_rc.ArgError;
+ else if( !*n ) return 0 /* ignore this */;
+ else
+ {
+ unsigned int i;
+ cson_data_source_StringSource_t * ss = (cson_data_source_StringSource_t*) state;
+ unsigned char * tgt = (unsigned char *)dest;
+ for( i = 0; (i < *n) && (ss->pos < ss->end); ++i, ++ss->pos, ++tgt )
+ {
+ *tgt = *ss->pos;
+ }
+ *n = i;
+ return 0;
+ }
+}
+
+int cson_parse_string( cson_value ** tgt, char const * src, unsigned int len,
+ cson_parse_opt const * opt, cson_parse_info * err )
+{
+ if( ! tgt || !src ) return cson_rc.ArgError;
+ else if( !*src || (len<2/*2==len of {} and []*/) ) return cson_rc.RangeError;
+ else
+ {
+ cson_data_source_StringSource_t ss;
+ ss.str = ss.pos = src;
+ ss.end = src + len;
+ return cson_parse( tgt, cson_data_source_StringSource, &ss, opt, err );
+ }
+
+}
+
+int cson_parse_buffer( cson_value ** tgt,
+ cson_buffer const * buf,
+ cson_parse_opt const * opt,
+ cson_parse_info * err )
+{
+ return ( !tgt || !buf || !buf->mem || !buf->used )
+ ? cson_rc.ArgError
+ : cson_parse_string( tgt, (char const *)buf->mem,
+ buf->used, opt, err );
+}
+
+int cson_buffer_reserve( cson_buffer * buf, cson_size_t n )
+{
+ if( ! buf ) return cson_rc.ArgError;
+ else if( 0 == n )
+ {
+ cson_free(buf->mem, "cson_buffer::mem");
+ *buf = cson_buffer_empty;
+ return 0;
+ }
+ else if( buf->capacity >= n )
+ {
+ return 0;
+ }
+ else
+ {
+ unsigned char * x = (unsigned char *)realloc( buf->mem, n );
+ if( ! x ) return cson_rc.AllocError;
+ memset( x + buf->used, 0, n - buf->used );
+ buf->mem = x;
+ buf->capacity = n;
+ ++buf->timesExpanded;
+ return 0;
+ }
+}
+
+cson_size_t cson_buffer_fill( cson_buffer * buf, char c )
+{
+ if( !buf || !buf->capacity || !buf->mem ) return 0;
+ else
+ {
+ memset( buf->mem, c, buf->capacity );
+ return buf->capacity;
+ }
+}
+
+/**
+ cson_data_dest_f() implementation, used by cson_output_buffer().
+
+ arg MUST be a (cson_buffer*). This function appends n bytes at
+ position arg->used, expanding the buffer as necessary.
+*/
+static int cson_data_dest_cson_buffer( void * arg, void const * data_, unsigned int n )
+{
+ if( ! arg || (n<0) ) return cson_rc.ArgError;
+ else if( ! n ) return 0;
+ else
+ {
+ cson_buffer * sb = (cson_buffer*)arg;
+ char const * data = (char const *)data_;
+ cson_size_t npos = sb->used + n;
+ unsigned int i;
+ if( npos >= sb->capacity )
+ {
+ const cson_size_t oldCap = sb->capacity;
+ const cson_size_t asz = npos * 2;
+ if( asz < npos ) return cson_rc.ArgError; /* overflow */
+ else if( 0 != cson_buffer_reserve( sb, asz ) ) return cson_rc.AllocError;
+ assert( (sb->capacity > oldCap) && "Internal error in memory buffer management!" );
+ /* make sure it gets NULL terminated. */
+ memset( sb->mem + oldCap, 0, (sb->capacity - oldCap) );
+ }
+ for( i = 0; i < n; ++i, ++sb->used )
+ {
+ sb->mem[sb->used] = data[i];
+ }
+ return 0;
+ }
+}
+
+
+int cson_output_buffer( cson_value const * v, cson_buffer * buf,
+ cson_output_opt const * opt )
+{
+ int rc = cson_output( v, cson_data_dest_cson_buffer, buf, opt );
+ if( 0 == rc )
+ { /* Ensure that the buffer is null-terminated. */
+ rc = cson_buffer_reserve( buf, buf->used + 1 );
+ if( 0 == rc )
+ {
+ buf->mem[buf->used] = 0;
+ }
+ }
+ return rc;
+}
+
+/** @internal
+
+Tokenizes an input string on a given separator. Inputs are:
+
+- (inp) = is a pointer to the pointer to the start of the input.
+
+- (separator) = the separator character
+
+- (end) = a pointer to NULL. i.e. (*end == NULL)
+
+This function scans *inp for the given separator char or a NULL char.
+Successive separators at the start of *inp are skipped. The effect is
+that, when this function is called in a loop, all neighboring
+separators are ignored. e.g. the string "aa.bb...cc" will tokenize to
+the list (aa,bb,cc) if the separator is '.' and to (aa.,...cc) if the
+separator is 'b'.
+
+Returns 0 (false) if it finds no token, else non-0 (true).
+
+Output:
+
+- (*inp) will be set to the first character of the next token.
+
+- (*end) will point to the one-past-the-end point of the token.
+
+If (*inp == *end) then the end of the string has been reached
+without finding a token.
+
+Post-conditions:
+
+- (*end == *inp) if no token is found.
+
+- (*end > *inp) if a token is found.
+
+It is intolerant of NULL values for (inp, end), and will assert() in
+debug builds if passed NULL as either parameter.
+*/
+static char cson_next_token( char const ** inp, char separator, char const ** end )
+{
+ char const * pos = NULL;
+ assert( inp && end && *inp );
+ if( *inp == *end ) return 0;
+ pos = *inp;
+ if( !*pos )
+ {
+ *end = pos;
+ return 0;
+ }
+ for( ; *pos && (*pos == separator); ++pos) { /* skip preceeding splitters */ }
+ *inp = pos;
+ for( ; *pos && (*pos != separator); ++pos) { /* find next splitter */ }
+ *end = pos;
+ return (pos > *inp) ? 1 : 0;
+}
+
+int cson_object_fetch_sub2( cson_object const * obj, cson_value ** tgt, char const * path )
+{
+ if( ! obj || !path ) return cson_rc.ArgError;
+ else if( !*path || !*(1+path) ) return cson_rc.RangeError;
+ else return cson_object_fetch_sub(obj, tgt, path+1, *path);
+}
+
+int cson_object_fetch_sub( cson_object const * obj, cson_value ** tgt, char const * path, char sep )
+{
+ if( ! obj || !path ) return cson_rc.ArgError;
+ else if( !*path || !sep ) return cson_rc.RangeError;
+ else
+ {
+ char const * beg = path;
+ char const * end = NULL;
+ int rc;
+ unsigned int i, len;
+ unsigned int tokenCount = 0;
+ cson_value * cv = NULL;
+ cson_object const * curObj = obj;
+ enum { BufSize = 128 };
+ char buf[BufSize];
+ memset( buf, 0, BufSize );
+ rc = cson_rc.RangeError;
+
+ while( cson_next_token( &beg, sep, &end ) )
+ {
+ if( beg == end ) break;
+ else
+ {
+ ++tokenCount;
+ beg = end;
+ end = NULL;
+ }
+ }
+ if( 0 == tokenCount ) return cson_rc.RangeError;
+ beg = path;
+ end = NULL;
+ for( i = 0; i < tokenCount; ++i, beg=end, end=NULL )
+ {
+ rc = cson_next_token( &beg, sep, &end );
+ assert( 1 == rc );
+ assert( beg != end );
+ assert( end > beg );
+ len = end - beg;
+ if( len > (BufSize-1) ) return cson_rc.RangeError;
+ memset( buf, 0, len + 1 );
+ memcpy( buf, beg, len );
+ buf[len] = 0;
+ cv = cson_object_get( curObj, buf );
+ if( NULL == cv ) return cson_rc.NotFoundError;
+ else if( i == (tokenCount-1) )
+ {
+ if(tgt) *tgt = cv;
+ return 0;
+ }
+ else if( cson_value_is_object(cv) )
+ {
+ curObj = cson_value_get_object(cv);
+ assert((NULL != curObj) && "Detected mis-management of internal memory!");
+ }
+ /* TODO: arrays. Requires numeric parsing for the index. */
+ else
+ {
+ return cson_rc.NotFoundError;
+ }
+ }
+ assert( i == tokenCount );
+ return cson_rc.NotFoundError;
+ }
+}
+
+cson_value * cson_object_get_sub( cson_object const * obj, char const * path, char sep )
+{
+ cson_value * v = NULL;
+ cson_object_fetch_sub( obj, &v, path, sep );
+ return v;
+}
+
+cson_value * cson_object_get_sub2( cson_object const * obj, char const * path )
+{
+ cson_value * v = NULL;
+ cson_object_fetch_sub2( obj, &v, path );
+ return v;
+}
+
+static cson_value * cson_value_clone_array( cson_value const * orig )
+{
+ unsigned int i = 0;
+ cson_array const * asrc = cson_value_get_array( orig );
+ unsigned int alen = cson_array_length_get( asrc );
+ cson_value * destV = NULL;
+ cson_array * destA = NULL;
+ assert( orig && asrc );
+ destV = cson_value_new_array();
+ if( NULL == destV ) return NULL;
+ destA = cson_value_get_array( destV );
+ assert( destA );
+ if( 0 != cson_array_reserve( destA, alen ) )
+ {
+ cson_value_free( destV );
+ return NULL;
+ }
+ for( ; i < alen; ++i )
+ {
+ cson_value * ch = cson_array_get( asrc, i );
+ if( NULL != ch )
+ {
+ cson_value * cl = cson_value_clone( ch );
+ if( NULL == cl )
+ {
+ cson_value_free( destV );
+ return NULL;
+ }
+ if( 0 != cson_array_set( destA, i, cl ) )
+ {
+ cson_value_free( cl );
+ cson_value_free( destV );
+ return NULL;
+ }
+ }
+ }
+ return destV;
+}
+
+static cson_value * cson_value_clone_object( cson_value const * orig )
+{
+ cson_object const * src = cson_value_get_object( orig );
+ cson_value * destV = NULL;
+ cson_object * dest = NULL;
+ cson_kvp const * kvp = NULL;
+ cson_object_iterator iter = cson_object_iterator_empty;
+ assert( orig && src );
+ if( 0 != cson_object_iter_init( src, &iter ) )
+ {
+ return NULL;
+ }
+ destV = cson_value_new_object();
+ if( NULL == destV ) return NULL;
+ dest = cson_value_get_object( destV );
+ assert( dest );
+ if( src->kvp.count > cson_kvp_list_reserve( &dest->kvp, src->kvp.count ) ){
+ cson_value_free( destV );
+ return NULL;
+ }
+ while( (kvp = cson_object_iter_next( &iter )) )
+ {
+ /*
+ FIXME: refcount the keys! We first need a setter which takes
+ a cson_string or cson_value key type.
+ */
+ cson_value * key = NULL;
+ cson_value * val = NULL;
+ key = cson_value_clone(kvp->key);
+ val = key ? cson_value_clone( kvp->value ) : NULL;
+ if( ! key || !val ){
+ cson_value_free(key);
+ cson_value_free(val);
+ cson_value_free(destV);
+ return NULL;
+ }
+ assert( CSON_STR(key) );
+ if( 0 != cson_object_set_s( dest, CSON_STR(key), val ) )
+ {
+ cson_value_free(key);
+ cson_value_free(val);
+ cson_value_free(destV);
+ return NULL;
+ }
+ }
+ return destV;
+}
+
+cson_value * cson_value_clone( cson_value const * orig )
+{
+ if( NULL == orig ) return NULL;
+ else
+ {
+ switch( orig->api->typeID )
+ {
+ case CSON_TYPE_UNDEF:
+ assert(0 && "This should never happen.");
+ return NULL;
+ case CSON_TYPE_NULL:
+ return cson_value_null();
+ case CSON_TYPE_BOOL:
+ return cson_value_new_bool( cson_value_get_bool( orig ) );
+ case CSON_TYPE_INTEGER:
+ return cson_value_new_integer( cson_value_get_integer( orig ) );
+ break;
+ case CSON_TYPE_DOUBLE:
+ return cson_value_new_double( cson_value_get_double( orig ) );
+ break;
+ case CSON_TYPE_STRING: {
+ cson_string const * str = cson_value_get_string( orig );
+ return cson_value_new_string( cson_string_cstr( str ),
+ cson_string_length_bytes( str ) );
+ }
+ case CSON_TYPE_ARRAY:
+ return cson_value_clone_array( orig );
+ case CSON_TYPE_OBJECT:
+ return cson_value_clone_object( orig );
+ }
+ assert( 0 && "We can't get this far." );
+ return NULL;
+ }
+}
+
+cson_value * cson_string_value(cson_string const * s)
+{
+#define MT CSON_SPECIAL_VALUES[CSON_VAL_STR_EMPTY]
+ return s
+ ? ((s==MT.value) ? &MT : CSON_VCAST(s))
+ : NULL;
+#undef MT
+}
+
+cson_value * cson_object_value(cson_object const * s)
+{
+ return s
+ ? CSON_VCAST(s)
+ : NULL;
+}
+
+
+cson_value * cson_array_value(cson_array const * s)
+{
+ return s
+ ? CSON_VCAST(s)
+ : NULL;
+}
+
+void cson_free_object(cson_object *x)
+{
+ if(x) cson_value_free(cson_object_value(x));
+}
+void cson_free_array(cson_array *x)
+{
+ if(x) cson_value_free(cson_array_value(x));
+}
+
+void cson_free_string(cson_string const *x)
+{
+ if(x) cson_value_free(cson_string_value(x));
+}
+void cson_free_value(cson_value *x)
+{
+ cson_value_free(x);
+}
+
+
+#if 0
+/* i'm not happy with this... */
+char * cson_pod_to_string( cson_value const * orig )
+{
+ if( ! orig ) return NULL;
+ else
+ {
+ enum { BufSize = 64 };
+ char * v = NULL;
+ switch( orig->api->typeID )
+ {
+ case CSON_TYPE_BOOL: {
+ char const bv = cson_value_get_bool(orig);
+ v = cson_strdup( bv ? "true" : "false",
+ bv ? 4 : 5 );
+ break;
+ }
+ case CSON_TYPE_UNDEF:
+ case CSON_TYPE_NULL: {
+ v = cson_strdup( "null", 4 );
+ break;
+ }
+ case CSON_TYPE_STRING: {
+ cson_string const * jstr = cson_value_get_string(orig);
+ unsigned const int slen = cson_string_length_bytes( jstr );
+ assert( NULL != jstr );
+ v = cson_strdup( cson_string_cstr( jstr ), slen );
+ break;
+ }
+ case CSON_TYPE_INTEGER: {
+ char buf[BufSize] = {0};
+ if( 0 < sprintf( v, "%"CSON_INT_T_PFMT, cson_value_get_integer(orig)) )
+ {
+ v = cson_strdup( buf, strlen(buf) );
+ }
+ break;
+ }
+ case CSON_TYPE_DOUBLE: {
+ char buf[BufSize] = {0};
+ if( 0 < sprintf( v, "%"CSON_DOUBLE_T_PFMT, cson_value_get_double(orig)) )
+ {
+ v = cson_strdup( buf, strlen(buf) );
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ return v;
+ }
+}
+#endif
+
+#if 0
+/* i'm not happy with this... */
+char * cson_pod_to_string( cson_value const * orig )
+{
+ if( ! orig ) return NULL;
+ else
+ {
+ enum { BufSize = 64 };
+ char * v = NULL;
+ switch( orig->api->typeID )
+ {
+ case CSON_TYPE_BOOL: {
+ char const bv = cson_value_get_bool(orig);
+ v = cson_strdup( bv ? "true" : "false",
+ bv ? 4 : 5 );
+ break;
+ }
+ case CSON_TYPE_UNDEF:
+ case CSON_TYPE_NULL: {
+ v = cson_strdup( "null", 4 );
+ break;
+ }
+ case CSON_TYPE_STRING: {
+ cson_string const * jstr = cson_value_get_string(orig);
+ unsigned const int slen = cson_string_length_bytes( jstr );
+ assert( NULL != jstr );
+ v = cson_strdup( cson_string_cstr( jstr ), slen );
+ break;
+ }
+ case CSON_TYPE_INTEGER: {
+ char buf[BufSize] = {0};
+ if( 0 < sprintf( v, "%"CSON_INT_T_PFMT, cson_value_get_integer(orig)) )
+ {
+ v = cson_strdup( buf, strlen(buf) );
+ }
+ break;
+ }
+ case CSON_TYPE_DOUBLE: {
+ char buf[BufSize] = {0};
+ if( 0 < sprintf( v, "%"CSON_DOUBLE_T_PFMT, cson_value_get_double(orig)) )
+ {
+ v = cson_strdup( buf, strlen(buf) );
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ return v;
+ }
+}
+#endif
+
+unsigned int cson_value_msize(cson_value const * v)
+{
+ if(!v) return 0;
+ else if( cson_value_is_builtin(v) ) return 0;
+ else {
+ unsigned int rc = sizeof(cson_value);
+ assert(NULL != v->api);
+ switch(v->api->typeID){
+ case CSON_TYPE_INTEGER:
+ assert( v != &CSON_SPECIAL_VALUES[CSON_VAL_INT_0]);
+ rc += sizeof(cson_int_t);
+ break;
+ case CSON_TYPE_DOUBLE:
+ assert( v != &CSON_SPECIAL_VALUES[CSON_VAL_DBL_0]);
+ rc += sizeof(cson_double_t);
+ break;
+ case CSON_TYPE_STRING:
+ rc += sizeof(cson_string)
+ + CSON_STR(v)->length + 1/*NUL*/;
+ break;
+ case CSON_TYPE_ARRAY:{
+ cson_array const * ar = CSON_ARRAY(v);
+ cson_value_list const * li;
+ unsigned int i = 0;
+ assert( NULL != ar );
+ li = &ar->list;
+ rc += sizeof(cson_array)
+ + (li->alloced * sizeof(cson_value *));
+ for( ; i < li->count; ++i ){
+ cson_value const * e = ar->list.list[i];
+ if( e ) rc += cson_value_msize( e );
+ }
+ break;
+ }
+ case CSON_TYPE_OBJECT:{
+ cson_object const * obj = CSON_OBJ(v);
+ unsigned int i = 0;
+ cson_kvp_list const * kl;
+ assert(NULL != obj);
+ kl = &obj->kvp;
+ rc += sizeof(cson_object)
+ + (kl->alloced * sizeof(cson_kvp*));
+ for( ; i < kl->count; ++i ){
+ cson_kvp const * kvp = kl->list[i];
+ assert(NULL != kvp);
+ rc += cson_value_msize(kvp->key);
+ rc += cson_value_msize(kvp->value);
+ }
+ break;
+ }
+ case CSON_TYPE_UNDEF:
+ case CSON_TYPE_NULL:
+ case CSON_TYPE_BOOL:
+ assert( 0 && "Should have been caught by is-builtin check!" );
+ break;
+ default:
+ assert(0 && "Invalid typeID!");
+ return 0;
+
+ }
+ return rc;
+ }
+}
+
+int cson_object_merge( cson_object * dest, cson_object const * src, int flags ){
+ cson_object_iterator iter = cson_object_iterator_empty;
+ int rc;
+ char const replace = (flags & CSON_MERGE_REPLACE);
+ char const recurse = !(flags & CSON_MERGE_NO_RECURSE);
+ cson_kvp const * kvp;
+ if((!dest || !src) || (dest==src)) return cson_rc.ArgError;
+ rc = cson_object_iter_init( src, &iter );
+ if(rc) return rc;
+ while( (kvp = cson_object_iter_next(&iter) ) )
+ {
+ cson_string * key = cson_kvp_key(kvp);
+ cson_value * val = cson_kvp_value(kvp);
+ cson_value * check = cson_object_get_s( dest, key );
+ if(!check){
+ cson_object_set_s( dest, key, val );
+ continue;
+ }
+ else if(!replace && !recurse) continue;
+ else if(replace && !recurse){
+ cson_object_set_s( dest, key, val );
+ continue;
+ }
+ else if( recurse ){
+ if( cson_value_is_object(check) &&
+ cson_value_is_object(val) ){
+ rc = cson_object_merge( cson_value_get_object(check),
+ cson_value_get_object(val),
+ flags );
+ if(rc) return rc;
+ else continue;
+ }
+ else continue;
+ }
+ else continue;
+ }
+ return 0;
+}
+
+#if defined(__cplusplus)
+} /*extern "C"*/
+#endif
+
+#undef MARKER
+#undef CSON_OBJECT_PROPS_SORT
+#undef CSON_OBJECT_PROPS_SORT_USE_LENGTH
+#undef CSON_CAST
+#undef CSON_INT
+#undef CSON_DBL
+#undef CSON_STR
+#undef CSON_OBJ
+#undef CSON_ARRAY
+#undef CSON_VCAST
+#undef CSON_MALLOC_IMPL
+#undef CSON_FREE_IMPL
+#undef CSON_REALLOC_IMPL
+/* end file ./cson.c */
+/* begin file ./cson_lists.h */
+/* Auto-generated from cson_list.h. Edit at your own risk! */
+unsigned int cson_value_list_reserve( cson_value_list * self, unsigned int n )
+{
+ if( !self ) return 0;
+ else if(0 == n)
+ {
+ if(0 == self->alloced) return 0;
+ cson_free(self->list, "cson_value_list_reserve");
+ self->list = NULL;
+ self->alloced = self->count = 0;
+ return 0;
+ }
+ else if( self->alloced >= n )
+ {
+ return self->alloced;
+ }
+ else
+ {
+ size_t const sz = sizeof(cson_value *) * n;
+ cson_value * * m = (cson_value **)cson_realloc( self->list, sz, "cson_value_list_reserve" );
+ if( ! m ) return self->alloced;
+
+ memset( m + self->alloced, 0, (sizeof(cson_value *)*(n-self->alloced)));
+ self->alloced = n;
+ self->list = m;
+ return n;
+ }
+}
+int cson_value_list_append( cson_value_list * self, cson_value * cp )
+{
+ if( !self || !cp ) return cson_rc.ArgError;
+ else if( self->alloced > cson_value_list_reserve(self, self->count+1) )
+ {
+ return cson_rc.AllocError;
+ }
+ else
+ {
+ self->list[self->count++] = cp;
+ return 0;
+ }
+}
+int cson_value_list_visit( cson_value_list * self,
+
+ int (*visitor)(cson_value * obj, void * visitorState ),
+
+
+
+ void * visitorState )
+{
+ int rc = cson_rc.ArgError;
+ if( self && visitor )
+ {
+ unsigned int i = 0;
+ for( rc = 0; (i < self->count) && (0 == rc); ++i )
+ {
+
+ cson_value * obj = self->list[i];
+
+
+
+ if(obj) rc = visitor( obj, visitorState );
+ }
+ }
+ return rc;
+}
+void cson_value_list_clean( cson_value_list * self,
+
+ void (*cleaner)(cson_value * obj)
+
+
+
+ )
+{
+ if( self && cleaner && self->count )
+ {
+ unsigned int i = 0;
+ for( ; i < self->count; ++i )
+ {
+
+ cson_value * obj = self->list[i];
+
+
+
+ if(obj) cleaner(obj);
+ }
+ }
+ cson_value_list_reserve(self,0);
+}
+unsigned int cson_kvp_list_reserve( cson_kvp_list * self, unsigned int n )
+{
+ if( !self ) return 0;
+ else if(0 == n)
+ {
+ if(0 == self->alloced) return 0;
+ cson_free(self->list, "cson_kvp_list_reserve");
+ self->list = NULL;
+ self->alloced = self->count = 0;
+ return 0;
+ }
+ else if( self->alloced >= n )
+ {
+ return self->alloced;
+ }
+ else
+ {
+ size_t const sz = sizeof(cson_kvp *) * n;
+ cson_kvp * * m = (cson_kvp **)cson_realloc( self->list, sz, "cson_kvp_list_reserve" );
+ if( ! m ) return self->alloced;
+
+ memset( m + self->alloced, 0, (sizeof(cson_kvp *)*(n-self->alloced)));
+ self->alloced = n;
+ self->list = m;
+ return n;
+ }
+}
+int cson_kvp_list_append( cson_kvp_list * self, cson_kvp * cp )
+{
+ if( !self || !cp ) return cson_rc.ArgError;
+ else if( self->alloced > cson_kvp_list_reserve(self, self->count+1) )
+ {
+ return cson_rc.AllocError;
+ }
+ else
+ {
+ self->list[self->count++] = cp;
+ return 0;
+ }
+}
+int cson_kvp_list_visit( cson_kvp_list * self,
+
+ int (*visitor)(cson_kvp * obj, void * visitorState ),
+
+
+
+ void * visitorState )
+{
+ int rc = cson_rc.ArgError;
+ if( self && visitor )
+ {
+ unsigned int i = 0;
+ for( rc = 0; (i < self->count) && (0 == rc); ++i )
+ {
+
+ cson_kvp * obj = self->list[i];
+
+
+
+ if(obj) rc = visitor( obj, visitorState );
+ }
+ }
+ return rc;
+}
+void cson_kvp_list_clean( cson_kvp_list * self,
+
+ void (*cleaner)(cson_kvp * obj)
+
+
+
+ )
+{
+ if( self && cleaner && self->count )
+ {
+ unsigned int i = 0;
+ for( ; i < self->count; ++i )
+ {
+
+ cson_kvp * obj = self->list[i];
+
+
+
+ if(obj) cleaner(obj);
+ }
+ }
+ cson_kvp_list_reserve(self,0);
+}
+/* end file ./cson_lists.h */
+/* begin file ./cson_sqlite3.c */
+/** @file cson_sqlite3.c
+
+This file contains the implementation code for the cson
+sqlite3-to-JSON API.
+
+License: the same as the cson core library.
+
+Author: Stephan Beal (http://wanderinghorse.net/home/stephan)
+*/
+#if CSON_ENABLE_SQLITE3 /* we do this here for the sake of the amalgamation build */
+#include
+#include /* strlen() */
+
+#if 0
+#include
+#define MARKER if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf
+#else
+#define MARKER if(0) printf
+#endif
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+cson_value * cson_sqlite3_column_to_value( sqlite3_stmt * st, int col )
+{
+ if( ! st ) return NULL;
+ else
+ {
+#if 0
+ sqlite3_value * val = sqlite3_column_type(st,col);
+ int const vtype = val ? sqlite3_value_type(val) : -1;
+ if( ! val ) return cson_value_null();
+#else
+ int const vtype = sqlite3_column_type(st,col);
+#endif
+ switch( vtype )
+ {
+ case SQLITE_NULL:
+ return cson_value_null();
+ case SQLITE_INTEGER:
+ /* FIXME: for large integers fall back to Double instead. */
+ return cson_value_new_integer( (cson_int_t) sqlite3_column_int64(st, col) );
+ case SQLITE_FLOAT:
+ return cson_value_new_double( sqlite3_column_double(st, col) );
+ case SQLITE_BLOB: /* arguably fall through... */
+ case SQLITE_TEXT: {
+ char const * str = (char const *)sqlite3_column_text(st,col);
+ return cson_value_new_string(str, str ? strlen(str) : 0);
+ }
+ default:
+ return NULL;
+ }
+ }
+}
+
+cson_value * cson_sqlite3_column_names( sqlite3_stmt * st )
+{
+ cson_value * aryV = NULL;
+ cson_array * ary = NULL;
+ char const * colName = NULL;
+ int i = 0;
+ int rc = 0;
+ int colCount = 0;
+ assert(st);
+ colCount = sqlite3_column_count(st);
+ if( colCount <= 0 ) return NULL;
+
+ aryV = cson_value_new_array();
+ if( ! aryV ) return NULL;
+ ary = cson_value_get_array(aryV);
+ assert(ary);
+ for( i = 0; (0 ==rc) && (i < colCount); ++i )
+ {
+ colName = sqlite3_column_name( st, i );
+ if( ! colName ) rc = cson_rc.AllocError;
+ else
+ {
+ rc = cson_array_set( ary, (unsigned int)i,
+ cson_value_new_string(colName, strlen(colName)) );
+ }
+ }
+ if( 0 == rc ) return aryV;
+ else
+ {
+ cson_value_free(aryV);
+ return NULL;
+ }
+}
+
+
+cson_value * cson_sqlite3_row_to_object2( sqlite3_stmt * st,
+ cson_array * colNames )
+{
+ cson_value * rootV = NULL;
+ cson_object * root = NULL;
+ cson_string * colName = NULL;
+ int i = 0;
+ int rc = 0;
+ cson_value * currentValue = NULL;
+ int const colCount = sqlite3_column_count(st);
+ if( !colCount || (colCount>cson_array_length_get(colNames)) ) {
+ return NULL;
+ }
+ rootV = cson_value_new_object();
+ if(!rootV) return NULL;
+ root = cson_value_get_object(rootV);
+ for( i = 0; i < colCount; ++i )
+ {
+ colName = cson_value_get_string( cson_array_get( colNames, i ) );
+ if( ! colName ) goto error;
+ currentValue = cson_sqlite3_column_to_value(st,i);
+ if( ! currentValue ) currentValue = cson_value_null();
+ rc = cson_object_set_s( root, colName, currentValue );
+ if( 0 != rc )
+ {
+ cson_value_free( currentValue );
+ goto error;
+ }
+ }
+ goto end;
+ error:
+ cson_value_free( rootV );
+ rootV = NULL;
+ end:
+ return rootV;
+}
+
+
+cson_value * cson_sqlite3_row_to_object( sqlite3_stmt * st )
+{
+#if 0
+ cson_value * arV = cson_sqlite3_column_names(st);
+ cson_array * ar = NULL;
+ cson_value * rc = NULL;
+ if(!arV) return NULL;
+ ar = cson_value_get_array(arV);
+ assert( NULL != ar );
+ rc = cson_sqlite3_row_to_object2(st, ar);
+ cson_value_free(arV);
+ return rc;
+#else
+ cson_value * rootV = NULL;
+ cson_object * root = NULL;
+ char const * colName = NULL;
+ int i = 0;
+ int rc = 0;
+ cson_value * currentValue = NULL;
+ int const colCount = sqlite3_column_count(st);
+ if( !colCount ) return NULL;
+ rootV = cson_value_new_object();
+ if(!rootV) return NULL;
+ root = cson_value_get_object(rootV);
+ for( i = 0; i < colCount; ++i )
+ {
+ colName = sqlite3_column_name( st, i );
+ if( ! colName ) goto error;
+ currentValue = cson_sqlite3_column_to_value(st,i);
+ if( ! currentValue ) currentValue = cson_value_null();
+ rc = cson_object_set( root, colName, currentValue );
+ if( 0 != rc )
+ {
+ cson_value_free( currentValue );
+ goto error;
+ }
+ }
+ goto end;
+ error:
+ cson_value_free( rootV );
+ rootV = NULL;
+ end:
+ return rootV;
+#endif
+}
+
+cson_value * cson_sqlite3_row_to_array( sqlite3_stmt * st )
+{
+ cson_value * aryV = NULL;
+ cson_array * ary = NULL;
+ int i = 0;
+ int rc = 0;
+ int const colCount = sqlite3_column_count(st);
+ if( ! colCount ) return NULL;
+ aryV = cson_value_new_array();
+ if( ! aryV ) return NULL;
+ ary = cson_value_get_array(aryV);
+ rc = cson_array_reserve(ary, (unsigned int) colCount );
+ if( 0 != rc ) goto error;
+
+ for( i = 0; i < colCount; ++i ){
+ cson_value * elem = cson_sqlite3_column_to_value(st,i);
+ if( ! elem ) goto error;
+ rc = cson_array_append(ary,elem);
+ if(0!=rc)
+ {
+ cson_value_free( elem );
+ goto end;
+ }
+ }
+ goto end;
+ error:
+ cson_value_free(aryV);
+ aryV = NULL;
+ end:
+ return aryV;
+}
+
+
+/**
+ Internal impl of cson_sqlite3_stmt_to_json() when the 'fat'
+ parameter is non-0.
+*/
+static int cson_sqlite3_stmt_to_json_fat( sqlite3_stmt * st, cson_value ** tgt )
+{
+#define RETURN(RC) { if(rootV) cson_value_free(rootV); return RC; }
+ if( ! tgt || !st ) return cson_rc.ArgError;
+ else
+ {
+ cson_value * rootV = NULL;
+ cson_object * root = NULL;
+ cson_value * colsV = NULL;
+ cson_array * cols = NULL;
+ cson_value * rowsV = NULL;
+ cson_array * rows = NULL;
+ cson_value * objV = NULL;
+ int rc = 0;
+ int const colCount = sqlite3_column_count(st);
+ if( colCount <= 0 ) return cson_rc.ArgError;
+ rootV = cson_value_new_object();
+ if( ! rootV ) return cson_rc.AllocError;
+ colsV = cson_sqlite3_column_names(st);
+ if( ! colsV )
+ {
+ cson_value_free( rootV );
+ RETURN(cson_rc.AllocError);
+ }
+ cols = cson_value_get_array(colsV);
+ assert(NULL != cols);
+ root = cson_value_get_object(rootV);
+ rc = cson_object_set( root, "columns", colsV );
+ if( rc )
+ {
+ cson_value_free( colsV );
+ RETURN(rc);
+ }
+ rowsV = cson_value_new_array();
+ if( ! rowsV ) RETURN(cson_rc.AllocError);
+ rc = cson_object_set( root, "rows", rowsV );
+ if( rc )
+ {
+ cson_value_free( rowsV );
+ RETURN(rc);
+ }
+ rows = cson_value_get_array(rowsV);
+ assert(rows);
+ while( SQLITE_ROW == sqlite3_step(st) )
+ {
+ objV = cson_sqlite3_row_to_object2(st, cols);
+ if( ! objV ) RETURN(cson_rc.UnknownError);
+ rc = cson_array_append( rows, objV );
+ if( rc )
+ {
+ cson_value_free( objV );
+ RETURN(rc);
+ }
+ }
+ *tgt = rootV;
+ return 0;
+ }
+#undef RETURN
+}
+
+/**
+ Internal impl of cson_sqlite3_stmt_to_json() when the 'fat'
+ parameter is 0.
+*/
+static int cson_sqlite3_stmt_to_json_slim( sqlite3_stmt * st, cson_value ** tgt )
+{
+#define RETURN(RC) { if(rootV) cson_value_free(rootV); return RC; }
+ if( ! tgt || !st ) return cson_rc.ArgError;
+ else
+ {
+ cson_value * rootV = NULL;
+ cson_object * root = NULL;
+ cson_value * aryV = NULL;
+ cson_array * ary = NULL;
+ cson_value * rowsV = NULL;
+ cson_array * rows = NULL;
+ int rc = 0;
+ int const colCount = sqlite3_column_count(st);
+ if( colCount <= 0 ) return cson_rc.ArgError;
+ rootV = cson_value_new_object();
+ if( ! rootV ) return cson_rc.AllocError;
+ aryV = cson_sqlite3_column_names(st);
+ if( ! aryV )
+ {
+ cson_value_free( rootV );
+ RETURN(cson_rc.AllocError);
+ }
+ root = cson_value_get_object(rootV);
+ rc = cson_object_set( root, "columns", aryV );
+ if( rc )
+ {
+ cson_value_free( aryV );
+ RETURN(rc);
+ }
+ aryV = NULL;
+ ary = NULL;
+ rowsV = cson_value_new_array();
+ if( ! rowsV ) RETURN(cson_rc.AllocError);
+ rc = cson_object_set( root, "rows", rowsV );
+ if( 0 != rc )
+ {
+ cson_value_free( rowsV );
+ RETURN(rc);
+ }
+ rows = cson_value_get_array(rowsV);
+ assert(rows);
+ while( SQLITE_ROW == sqlite3_step(st) )
+ {
+ aryV = cson_sqlite3_row_to_array(st);
+ if( ! aryV ) RETURN(cson_rc.UnknownError);
+ rc = cson_array_append( rows, aryV );
+ if( 0 != rc )
+ {
+ cson_value_free( aryV );
+ RETURN(rc);
+ }
+ }
+ *tgt = rootV;
+ return 0;
+ }
+#undef RETURN
+}
+
+int cson_sqlite3_stmt_to_json( sqlite3_stmt * st, cson_value ** tgt, char fat )
+{
+ return fat
+ ? cson_sqlite3_stmt_to_json_fat(st,tgt)
+ : cson_sqlite3_stmt_to_json_slim(st,tgt)
+ ;
+}
+
+int cson_sqlite3_sql_to_json( sqlite3 * db, cson_value ** tgt, char const * sql, char fat )
+{
+ if( !db || !tgt || !sql || !*sql ) return cson_rc.ArgError;
+ else
+ {
+ sqlite3_stmt * st = NULL;
+ int rc = sqlite3_prepare_v2( db, sql, -1, &st, NULL );
+ if( 0 != rc ) return cson_rc.IOError /* FIXME: Better error code? */;
+ rc = cson_sqlite3_stmt_to_json( st, tgt, fat );
+ sqlite3_finalize( st );
+ return rc;
+ }
+}
+
+#if defined(__cplusplus)
+} /*extern "C"*/
+#endif
+#undef MARKER
+#endif /* CSON_ENABLE_SQLITE3 */
+/* end file ./cson_sqlite3.c */
ADDED src/cson_amalgamation.h
Index: src/cson_amalgamation.h
==================================================================
--- /dev/null
+++ src/cson_amalgamation.h
@@ -0,0 +1,2385 @@
+/* auto-generated! Do not edit! */
+/* begin file include/wh/cson/cson.h */
+#if !defined(WANDERINGHORSE_NET_CSON_H_INCLUDED)
+#define WANDERINGHORSE_NET_CSON_H_INCLUDED 1
+
+/*#include C99: fixed-size int types. */
+#include /* FILE decl */
+
+/** @page page_cson cson JSON API
+
+cson (pronounced "season") is an object-oriented C API for generating
+and consuming JSON (http://www.json.org) data.
+
+Its main claim to fame is that it can parse JSON from, and output it
+to, damned near anywhere. The i/o routines use a callback function to
+fetch/emit JSON data, allowing clients to easily plug in their own
+implementations. Implementations are provided for string- and
+FILE-based i/o.
+
+Project home page: http://fossil.wanderinghorse.net/repos/cson
+
+Author: Stephan Beal (http://www.wanderinghorse.net/home/stephan/)
+
+License: Dual Public Domain/MIT
+
+The full license text is at the bottom of the main header file
+(cson.h).
+
+Examples of how to use the library are scattered throughout
+the API documentation, in the test.c file in the source repo,
+and in the wiki on the project's home page.
+
+
+*/
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#if defined(_WIN32) || defined(_WIN64)
+# define CSON_ENABLE_UNIX 0
+#else
+# define CSON_ENABLE_UNIX 1
+#endif
+
+
+/** @typedef some_long_int_type cson_int_t
+
+Typedef for JSON-like integer types. This is (long long) where feasible,
+otherwise (long).
+*/
+#if (__STDC_VERSION__ >= 199901L) || (HAVE_LONG_LONG == 1)
+typedef long long cson_int_t;
+#define CSON_INT_T_SFMT "lld"
+#define CSON_INT_T_PFMT "lld"
+#else
+typedef long cson_int_t;
+#define CSON_INT_T_SFMT "ld"
+#define CSON_INT_T_PFMT "ld"
+#endif
+
+/** @typedef double_or_long_double cson_double_t
+
+This is the type of double value used by the library.
+It is only lightly tested with long double, and when using
+long double the memory requirements for such values goes
+up.
+*/
+#if 0
+typedef long double cson_double_t;
+#define CSON_DOUBLE_T_SFMT "Lf"
+#define CSON_DOUBLE_T_PFMT "Lf"
+#else
+typedef double cson_double_t;
+#define CSON_DOUBLE_T_SFMT "f"
+#define CSON_DOUBLE_T_PFMT "f"
+#endif
+
+/** @def CSON_INT_T_SFMT
+
+scanf()-compatible format token for cson_int_t.
+*/
+
+/** @def CSON_INT_T_PFMT
+
+printf()-compatible format token for cson_int_t.
+*/
+
+
+/** @def CSON_DOUBLE_T_SFMT
+
+scanf()-compatible format token for cson_double_t.
+*/
+
+/** @def CSON_DOUBLE_T_PFMT
+
+printf()-compatible format token for cson_double_t.
+*/
+
+typedef struct cson_value cson_value;
+
+/** @struct cson_value
+
+ The core value type of this API. It is opaque to clients, and
+ only the cson public API should be used for setting or
+ inspecting their values.
+
+ This class is opaque because stack-based usage can easily cause
+ leaks if one does not intimately understand the underlying
+ internal memory management (which sometimes changes).
+
+ It is (as of 20110323) legal to insert a given value instance into
+ multiple containers (they will share ownership using reference
+ counting) as long as those insertions do not cause cycles. However,
+ be very aware that such value re-use uses a reference to the
+ original copy, meaning that if its value is changed once, it is
+ changed everywhere. Also beware that multi-threaded write
+ operations on such references leads to undefined behaviour.
+
+ PLEASE read the ACHTUNGEN below...
+
+ ACHTUNG #1:
+
+ cson_values MUST NOT form cycles (e.g. via object or array
+ entries).
+
+ Not abiding th Holy Law Of No Cycles will lead to double-frees and
+ the like (i.e. undefined behaviour, likely crashes due to infinite
+ recursion or stepping on invalid (freed) pointers).
+
+ ACHTUNG #2:
+
+ ALL cson_values returned as non-const cson_value pointers from any
+ public functions in the cson API are to be treated as if they are
+ heap-allocated, and MUST be freed by client by doing ONE of:
+
+ - Passing it to cson_value_free().
+
+ - Adding it to an Object or Array, in which case the object/array
+ takes over ownership. As of 20110323, a value may be inserted into
+ a single container multiple times, or into multiple containers,
+ in which case they all share ownership (via reference counting)
+ of the original value (meaning any changes to it are visible in
+ all references to it).
+
+ Each call to cson_value_new_xxx() MUST eventually be followed up
+ by one of those options.
+
+ Some cson_value_new_XXX() implementations do not actually allocate
+ memory, but this is an internal implementation detail. Client code
+ MUST NOT rely on this behaviour and MUST treat each object
+ returned by such a function as if it was a freshly-allocated copy
+ (even if their pointer addresses are the same).
+
+ ACHTUNG #3:
+
+ Note that ACHTUNG #2 tells us that we must always free (or transfer
+ ownership of) all pointers returned bycson_value_new_xxx(), but
+ that two calls to (e.g.) cson_value_new_bool(1) will (or might)
+ return the same address. The client must not rely on the
+ "non-allocation" policy of such special cases, and must pass each
+ returned value to cson_value_free(), even if two of them have the
+ same address. Some special values (e.g. null, true, false, integer
+ 0, double 0.0, and empty strings) use shared copies and in other
+ places reference counting is used internally to figure out when it
+ is safe to destroy an object.
+
+
+ @see cson_value_new_array()
+ @see cson_value_new_object()
+ @see cson_value_new_string()
+ @see cson_value_new_integer()
+ @see cson_value_new_double()
+ @see cson_value_new_bool()
+ @see cson_value_true()
+ @see cson_value_false()
+ @see cson_value_null()
+ @see cson_value_free()
+*/
+
+/** @var cson_rc
+
+ This object defines the error codes used by cson.
+
+ Library routines which return int values almost always return a
+ value from this structure. None of the members in this struct have
+ published values except for the OK member, which has the value 0.
+ All other values might be incidentally defined where clients
+ can see them, but the numbers might change from release to
+ release, so clients should only use the symbolic names.
+
+ Client code is expected to access these values via the shared
+ cson_rc object, and use them as demonstrated here:
+
+ @code
+ int rc = cson_some_func(...);
+ if( 0 == rc ) {...success...}
+ else if( cson_rc.ArgError == rc ) { ... some argument was wrong ... }
+ else if( cson_rc.AllocError == rc ) { ... allocation error ... }
+ ...
+ @endcode
+
+ The entries named Parse_XXX are generally only returned by
+ cson_parse() and friends.
+*/
+
+/** @struct cson_rc_
+ See \ref cson_rc for details.
+*/
+static const struct cson_rc_
+{
+ /** The generic success value. Guaranteed to be 0. */
+ const int OK;
+ /** Signifies an error in one or more arguments (e.g. NULL where it is not allowed). */
+ const int ArgError;
+ /** Signifies that some argument is not in a valid range. */
+ const int RangeError;
+ /** Signifies that some argument is not of the correct logical cson type. */
+ const int TypeError;
+ /** Signifies an input/ouput error. */
+ const int IOError;
+ /** Signifies an out-of-memory error. */
+ const int AllocError;
+ /** Signifies that the called code is "NYI" (Not Yet Implemented). */
+ const int NYIError;
+ /** Signifies that an internal error was triggered. If it happens, please report this as a bug! */
+ const int InternalError;
+ /** Signifies that the called operation is not supported in the
+ current environment. e.g. missing support from 3rd-party or
+ platform-specific code.
+ */
+ const int UnsupportedError;
+ /**
+ Signifies that the request resource could not be found.
+ */
+ const int NotFoundError;
+ /**
+ Signifies an unknown error, possibly because an underlying
+ 3rd-party API produced an error and we have no other reasonable
+ error code to convert it to.
+ */
+ const int UnknownError;
+ /**
+ Signifies that the parser found an unexpected character.
+ */
+ const int Parse_INVALID_CHAR;
+ /**
+ Signifies that the parser found an invalid keyword (possibly
+ an unquoted string).
+ */
+ const int Parse_INVALID_KEYWORD;
+ /**
+ Signifies that the parser found an invalid escape sequence.
+ */
+ const int Parse_INVALID_ESCAPE_SEQUENCE;
+ /**
+ Signifies that the parser found an invalid Unicode character
+ sequence.
+ */
+ const int Parse_INVALID_UNICODE_SEQUENCE;
+ /**
+ Signifies that the parser found an invalid numeric token.
+ */
+ const int Parse_INVALID_NUMBER;
+ /**
+ Signifies that the parser reached its maximum defined
+ parsing depth before finishing the input.
+ */
+ const int Parse_NESTING_DEPTH_REACHED;
+ /**
+ Signifies that the parser found an unclosed object or array.
+ */
+ const int Parse_UNBALANCED_COLLECTION;
+ /**
+ Signifies that the parser found an key in an unexpected place.
+ */
+ const int Parse_EXPECTED_KEY;
+ /**
+ Signifies that the parser expected to find a colon but
+ found none (e.g. between keys and values in an object).
+ */
+ const int Parse_EXPECTED_COLON;
+} cson_rc = {
+0/*OK*/,
+1/*ArgError*/,
+2/*RangeError*/,
+3/*TypeError*/,
+4/*IOError*/,
+5/*AllocError*/,
+6/*NYIError*/,
+7/*InternalError*/,
+8/*UnsupportedError*/,
+9/*NotFoundError*/,
+10/*UnknownError*/,
+11/*Parse_INVALID_CHAR*/,
+12/*Parse_INVALID_KEYWORD*/,
+13/*Parse_INVALID_ESCAPE_SEQUENCE*/,
+14/*Parse_INVALID_UNICODE_SEQUENCE*/,
+15/*Parse_INVALID_NUMBER*/,
+16/*Parse_NESTING_DEPTH_REACHED*/,
+17/*Parse_UNBALANCED_COLLECTION*/,
+18/*Parse_EXPECTED_KEY*/,
+19/*Parse_EXPECTED_COLON*/
+};
+
+/**
+ Returns the string form of the cson_rc code corresponding to rc, or
+ some unspecified, non-NULL string if it is an unknown code.
+
+ The returned bytes are static and do not changing during the
+ lifetime of the application.
+*/
+char const * cson_rc_string(int rc);
+
+/** @struct cson_parse_opt
+ Client-configurable options for the cson_parse() family of
+ functions.
+*/
+struct cson_parse_opt
+{
+ /**
+ Maximum object/array depth to traverse.
+ */
+ unsigned short maxDepth;
+ /**
+ Whether or not to allow C-style comments. Do not rely on this
+ option being available. If the underlying parser is replaced,
+ this option might no longer be supported.
+ */
+ char allowComments;
+};
+typedef struct cson_parse_opt cson_parse_opt;
+
+/**
+ Empty-initialized cson_parse_opt object.
+*/
+#define cson_parse_opt_empty_m { 25/*maxDepth*/, 0/*allowComments*/}
+
+
+/**
+ A class for holding JSON parser information. It is primarily
+ intended for finding the position of a parse error.
+*/
+struct cson_parse_info
+{
+ /**
+ 1-based line number.
+ */
+ unsigned int line;
+ /**
+ 0-based column number.
+ */
+ unsigned int col;
+
+ /**
+ Length, in bytes.
+ */
+ unsigned int length;
+
+ /**
+ Error code of the parse run (0 for no error).
+ */
+ int errorCode;
+
+ /**
+ The total number of object keys successfully processed by the
+ parser.
+ */
+ unsigned int totalKeyCount;
+
+ /**
+ The total number of object/array values successfully processed
+ by the parser, including the root node.
+ */
+ unsigned int totalValueCount;
+};
+typedef struct cson_parse_info cson_parse_info;
+
+/**
+ Empty-initialized cson_parse_info object.
+*/
+#define cson_parse_info_empty_m {1/*line*/,\
+ 0/*col*/, \
+ 0/*length*/, \
+ 0/*errorCode*/, \
+ 0/*totalKeyCount*/, \
+ 0/*totalValueCount*/ \
+ }
+/**
+ Empty-initialized cson_parse_info object.
+*/
+extern const cson_parse_info cson_parse_info_empty;
+
+/**
+ Empty-initialized cson_parse_opt object.
+*/
+extern const cson_parse_opt cson_parse_opt_empty;
+
+/**
+ Client-configurable options for the cson_output() family of
+ functions.
+*/
+struct cson_output_opt
+{
+ /**
+ Specifies how to indent (or not) output. The values
+ are:
+
+ (0) == no extra indentation.
+
+ (1) == 1 TAB character for each level.
+
+ (>1) == that number of SPACES for each level.
+ */
+ unsigned char indentation;
+
+ /**
+ Maximum object/array depth to traverse. Traversing deeply can
+ be indicative of cycles in the object/array tree, and this
+ value is used to figure out when to abort the traversal.
+ */
+ unsigned short maxDepth;
+
+ /**
+ If true, a newline will be added to generated output,
+ else not.
+ */
+ char addNewline;
+
+ /**
+ If true, a space will be added after the colon operator
+ in objects' key/value pairs.
+ */
+ char addSpaceAfterColon;
+
+ /**
+ If set to 1 then objects/arrays containing only a single value
+ will not indent an extra level for that value (but will indent
+ on subsequent levels if that value contains multiple values).
+ */
+ char indentSingleMemberValues;
+
+ /**
+ The JSON format allows, but does not require, JSON generators
+ to backslash-escape forward slashes. This option enables/disables
+ that feature. According to JSON's inventor, Douglas Crockford:
+
+
+ It is allowed, not required. It is allowed so that JSON can be
+ safely embedded in HTML, which can freak out when seeing
+ strings containing "". JSON tolerates "<\/" for this reason.
+
+
+ (from an email on 2011-04-08)
+
+ The default value is 0 (because it's just damned ugly).
+ */
+ char escapeForwardSlashes;
+};
+typedef struct cson_output_opt cson_output_opt;
+
+/**
+ Empty-initialized cson_output_opt object.
+*/
+#define cson_output_opt_empty_m { 0/*indentation*/,\
+ 25/*maxDepth*/, \
+ 0/*addNewline*/, \
+ 0/*addSpaceAfterColon*/, \
+ 0/*indentSingleMemberValues*/, \
+ 0/*escapeForwardSlashes*/ \
+ }
+
+/**
+ Empty-initialized cson_output_opt object.
+*/
+extern const cson_output_opt cson_output_opt_empty;
+
+/**
+ Typedef for functions which act as an input source for
+ the cson JSON parser.
+
+ The arguments are:
+
+ - state: implementation-specific state needed by the function.
+
+ - n: when called, *n will be the number of bytes the function
+ should read and copy to dest. The function MUST NOT copy more than
+ *n bytes to dest. Before returning, *n must be set to the number of
+ bytes actually copied to dest. If that number is smaller than the
+ original *n value, the input is assumed to be completed (thus this
+ is not useful with non-blocking readers).
+
+ - dest: the destination memory to copy the data do.
+
+ Must return 0 on success, non-0 on error (preferably a value from
+ cson_rc).
+
+ The parser allows this routine to return a partial character from a
+ UTF multi-byte character. The input routine does not need to
+ concern itself with character boundaries.
+*/
+typedef int (*cson_data_source_f)( void * state, void * dest, unsigned int * n );
+
+/**
+ Typedef for functions which act as an output destination for
+ generated JSON.
+
+ The arguments are:
+
+ - state: implementation-specific state needed by the function.
+
+ - n: the length, in bytes, of src.
+
+ - src: the source bytes which the output function should consume.
+ The src pointer will be invalidated shortly after this function
+ returns, so the implementation must copy or ignore the data, but not
+ hold a copy of the src pointer.
+
+ Must return 0 on success, non-0 on error (preferably a value from
+ cson_rc).
+
+ These functions are called relatively often during the JSON-output
+ process, and should try to be fast.
+*/
+typedef int (*cson_data_dest_f)( void * state, void const * src, unsigned int n );
+
+/**
+ Reads JSON-formatted string data (in ASCII, UTF8, or UTF16), using the
+ src function to fetch all input. This function fetches each input character
+ from the source function, which is calls like src(srcState, buffer, bufferSize),
+ and processes them. If anything is not JSON-kosher then this function
+ fails and returns one of the non-0 cson_rc codes.
+
+ This function is only intended to read root nodes of a JSON tree, either
+ a single object or a single array, containing any number of child elements.
+
+ On success, *tgt is assigned the value of the root node of the
+ JSON input, and the caller takes over ownership of that memory.
+ On error, *tgt is not modified and the caller need not do any
+ special cleanup, except possibly for the input source.
+
+
+ The opt argument may point to an initialized cson_parse_opt object
+ which contains any settings the caller wants. If it is NULL then
+ default settings (the values defined in cson_parse_opt_empty) are
+ used.
+
+ The info argument may be NULL. If it is not NULL then the parser
+ populates it with information which is useful in error
+ reporting. Namely, it contains the line/column of parse errors.
+
+ The srcState argument is ignored by this function but is passed on to src,
+ so any output-destination-specific state can be stored there and accessed
+ via the src callback.
+
+ Non-parse error conditions include:
+
+ - (!tgt) or !src: cson_rc.ArgError
+ - cson_rc.AllocError can happen at any time during the input phase
+
+ Here's a complete example of using a custom input source:
+
+ @code
+ // Internal type to hold state for a JSON input string.
+ typedef struct
+ {
+ char const * str; // start of input string
+ char const * pos; // current internal cursor position
+ char const * end; // logical EOF (one-past-the-end)
+ } StringSource;
+
+ // cson_data_source_f() impl which uses StringSource.
+ static int cson_data_source_StringSource( void * state, void * dest,
+ unsigned int * n )
+ {
+ StringSource * ss = (StringSource*) state;
+ unsigned int i;
+ unsigned char * tgt = (unsigned char *)dest;
+ if( ! ss || ! n || !dest ) return cson_rc.ArgError;
+ else if( !*n ) return cson_rc.RangeError;
+ for( i = 0;
+ (i < *n) && (ss->pos < ss->end);
+ ++i, ++ss->pos, ++tgt )
+ {
+ *tgt = *ss->pos;
+ }
+ *n = i;
+ return 0;
+ }
+
+ ...
+ // Now use StringSource together with cson_parse()
+ StringSource ss;
+ cson_value * root = NULL;
+ char const * json = "{\"k1\":123}";
+ ss.str = ss.pos = json;
+ ss.end = json + strlen(json);
+ int rc = cson_parse( &root, cson_data_source_StringSource, &ss, NULL, NULL );
+ @endcode
+
+ It is recommended that clients wrap such utility code into
+ type-safe wrapper functions which also initialize the internal
+ state object and check the user-provided parameters for legality
+ before passing them on to cson_parse(). For examples of this, see
+ cson_parse_FILE() or cson_parse_string().
+
+ TODOs:
+
+ - Buffer the input in larger chunks. We currently read
+ byte-by-byte, but i'm too tired to write/test the looping code for
+ the buffering.
+
+ @see cson_parse_FILE()
+ @see cson_parse_string()
+*/
+int cson_parse( cson_value ** tgt, cson_data_source_f src, void * srcState,
+ cson_parse_opt const * opt, cson_parse_info * info );
+/**
+ A cson_data_source_f() implementation which requires the state argument
+ to be a readable (FILE*) handle.
+*/
+int cson_data_source_FILE( void * state, void * dest, unsigned int * n );
+
+/**
+ Equivalent to cson_parse( tgt, cson_data_source_FILE, src, opt ).
+
+ @see cson_parse_filename()
+*/
+int cson_parse_FILE( cson_value ** tgt, FILE * src,
+ cson_parse_opt const * opt, cson_parse_info * info );
+
+/**
+ Convenience wrapper around cson_parse_FILE() which opens the given filename.
+
+ Returns cson_rc.IOError if the file cannot be opened.
+
+ @see cson_parse_FILE()
+*/
+int cson_parse_filename( cson_value ** tgt, char const * src,
+ cson_parse_opt const * opt, cson_parse_info * info );
+
+/**
+ Uses an internal helper class to pass src through cson_parse().
+ See that function for the return value and argument semantics.
+
+ src must be a string containing JSON code, at least len bytes long,
+ and the parser will attempt to parse exactly len bytes from src.
+
+ If len is less than 2 (the minimum length of a legal top-node JSON
+ object) then cson_rc.RangeError is returned.
+*/
+int cson_parse_string( cson_value ** tgt, char const * src, unsigned int len,
+ cson_parse_opt const * opt, cson_parse_info * info );
+
+
+
+/**
+ Outputs the given value as a JSON-formatted string, sending all
+ output to the given callback function. It is intended for top-level
+ objects or arrays, but can be used with any cson_value.
+
+ If opt is NULL then default options (the values defined in
+ cson_output_opt_empty) are used.
+
+ If opt->maxDepth is exceeded while traversing the value tree,
+ cson_rc.RangeError is returned.
+
+ The destState parameter is ignored by this function and is passed
+ on to the dest function.
+
+ Returns 0 on success. On error, any amount of output might have been
+ generated before the error was triggered.
+
+ Example:
+
+ @code
+ int rc = cson_output( myValue, cson_data_dest_FILE, stdout, NULL );
+ // basically equivalent to: cson_output_FILE( myValue, stdout, NULL );
+ // but note that cson_output_FILE() actually uses different defaults
+ // for the output options.
+ @endcode
+*/
+int cson_output( cson_value const * src, cson_data_dest_f dest, void * destState, cson_output_opt const * opt );
+
+
+/**
+ A cson_data_dest_f() implementation which requires the state argument
+ to be a writable (FILE*) handle.
+*/
+int cson_data_dest_FILE( void * state, void const * src, unsigned int n );
+
+/**
+ Almost equivalent to cson_output( src, cson_data_dest_FILE, dest, opt ),
+ with one minor difference: if opt is NULL then the default options
+ always include the addNewline option, since that is normally desired
+ for FILE output.
+
+ @see cson_output_filename()
+*/
+int cson_output_FILE( cson_value const * src, FILE * dest, cson_output_opt const * opt );
+/**
+ Convenience wrapper around cson_output_FILE() which writes to the given filename, destroying
+ any existing contents. Returns cson_rc.IOError if the file cannot be opened.
+
+ @see cson_output_FILE()
+*/
+int cson_output_filename( cson_value const * src, char const * dest, cson_output_opt const * fmt );
+
+/** Returns true if v is null, v->api is NULL, or v holds the special undefined value. */
+char cson_value_is_undef( cson_value const * v );
+/** Returns true if v contains a null value. */
+char cson_value_is_null( cson_value const * v );
+/** Returns true if v contains a bool value. */
+char cson_value_is_bool( cson_value const * v );
+/** Returns true if v contains an integer value. */
+char cson_value_is_integer( cson_value const * v );
+/** Returns true if v contains a double value. */
+char cson_value_is_double( cson_value const * v );
+/** Returns true if v contains a number (double, integer) value. */
+char cson_value_is_number( cson_value const * v );
+/** Returns true if v contains a string value. */
+char cson_value_is_string( cson_value const * v );
+/** Returns true if v contains an array value. */
+char cson_value_is_array( cson_value const * v );
+/** Returns true if v contains an object value. */
+char cson_value_is_object( cson_value const * v );
+
+/** @struct cson_object
+
+ cson_object is an opaque handle to an Object value.
+
+ They are used like:
+
+ @code
+ cson_object * obj = cson_value_get_object(myValue);
+ ...
+ @endcode
+
+ They can be created like:
+
+ @code
+ cson_value * objV = cson_value_new_object();
+ cson_object * obj = cson_value_get_object(objV);
+ // obj is owned by objV and objV must eventually be freed
+ // using cson_value_free() or added to a container
+ // object/array (which transfers ownership to that container).
+ @endcode
+
+ @see cson_value_new_object()
+ @see cson_value_get_object()
+ @see cson_value_free()
+*/
+
+typedef struct cson_object cson_object;
+
+/** @struct cson_array
+
+ cson_array is an opaque handle to an Array value.
+
+ They are used like:
+
+ @code
+ cson_array * obj = cson_value_get_array(myValue);
+ ...
+ @endcode
+
+ They can be created like:
+
+ @code
+ cson_value * arV = cson_value_new_array();
+ cson_array * ar = cson_value_get_array(arV);
+ // ar is owned by arV and arV must eventually be freed
+ // using cson_value_free() or added to a container
+ // object/array (which transfers ownership to that container).
+ @endcode
+
+ @see cson_value_new_array()
+ @see cson_value_get_array()
+ @see cson_value_free()
+
+*/
+typedef struct cson_array cson_array;
+
+/** @struct cson_string
+
+ cson-internal string type, opaque to client code. Strings in cson
+ are immutable and allocated only by library internals, never
+ directly by client code.
+
+ The actual string bytes are to be allocated together in the same
+ memory chunk as the cson_string object, which saves us 1 malloc()
+ and 1 pointer member in this type (because we no longer have a
+ direct pointer to the memory).
+
+ Potential TODOs:
+
+ @see cson_string_cstr()
+*/
+typedef struct cson_string cson_string;
+
+/**
+ Converts the given value to a boolean, using JavaScript semantics depending
+ on the concrete type of val:
+
+ undef or null: false
+
+ boolean: same
+
+ integer, double: 0 or 0.0 == false, else true
+
+ object, array: true
+
+ string: length-0 string is false, else true.
+
+ Returns 0 on success and assigns *v (if v is not NULL) to either 0 or 1.
+ On error (val is NULL) then v is not modified.
+*/
+int cson_value_fetch_bool( cson_value const * val, char * v );
+
+/**
+ Similar to cson_value_fetch_bool(), but fetches an integer value.
+
+ The conversion, if any, depends on the concrete type of val:
+
+ NULL, null, undefined: *v is set to 0 and 0 is returned.
+
+ string, object, array: *v is set to 0 and
+ cson_rc.TypeError is returned. The error may normally be safely
+ ignored, but it is provided for those wanted to know whether a direct
+ conversion was possible.
+
+ integer: *v is set to the int value and 0 is returned.
+
+ double: *v is set to the value truncated to int and 0 is returned.
+*/
+int cson_value_fetch_integer( cson_value const * val, cson_int_t * v );
+
+/**
+ The same conversions and return values as
+ cson_value_fetch_integer(), except that the roles of int/double are
+ swapped.
+*/
+int cson_value_fetch_double( cson_value const * val, cson_double_t * v );
+
+/**
+ If cson_value_is_string(val) then this function assigns *str to the
+ contents of the string. str may be NULL, in which case this function
+ functions like cson_value_is_string() but returns 0 on success.
+
+ Returns 0 if val is-a string, else non-0, in which case *str is not
+ modified.
+
+ The bytes are owned by the given value and may be invalidated in any of
+ the following ways:
+
+ - The value is cleaned up or freed.
+
+ - An array or object containing the value peforms a re-allocation
+ (it shrinks or grows).
+
+ And thus the bytes should be consumed before any further operations
+ on val or any container which holds it.
+
+ Note that this routine does not convert non-String values to their
+ string representations. (Adding that ability would add more
+ overhead to every cson_value instance.)
+*/
+int cson_value_fetch_string( cson_value const * val, cson_string ** str );
+
+/**
+ If cson_value_is_object(val) then this function assigns *obj to the underlying
+ object value and returns 0, otherwise non-0 is returned and *obj is not modified.
+
+ obj may be NULL, in which case this function works like cson_value_is_object()
+ but with inverse return value semantics (0==success) (and it's a few
+ CPU cycles slower).
+
+ The *obj pointer is owned by val, and will be invalidated when val
+ is cleaned up.
+
+ Achtung: for best results, ALWAYS pass a pointer to NULL as the
+ second argument, e.g.:
+
+ @code
+ cson_object * obj = NULL;
+ int rc = cson_value_fetch_object( val, &obj );
+
+ // Or, more simply:
+ obj = cson_value_get_object( val );
+ @endcode
+
+ @see cson_value_get_object()
+*/
+int cson_value_fetch_object( cson_value const * val, cson_object ** obj );
+
+/**
+ Identical to cson_value_fetch_object(), but works on array values.
+
+ @see cson_value_get_array()
+*/
+int cson_value_fetch_array( cson_value const * val, cson_array ** tgt );
+
+/**
+ Simplified form of cson_value_fetch_bool(). Returns 0 if val
+ is NULL.
+*/
+char cson_value_get_bool( cson_value const * val );
+
+/**
+ Simplified form of cson_value_fetch_integer(). Returns 0 if val
+ is NULL.
+*/
+cson_int_t cson_value_get_integer( cson_value const * val );
+
+/**
+ Simplified form of cson_value_fetch_double(). Returns 0.0 if val
+ is NULL.
+*/
+cson_double_t cson_value_get_double( cson_value const * val );
+
+/**
+ Simplified form of cson_value_fetch_string(). Returns NULL if val
+ is-not-a string value.
+*/
+cson_string * cson_value_get_string( cson_value const * val );
+
+/**
+ Returns a pointer to the NULL-terminated string bytes of str.
+ The bytes are owned by string and will be invalided when it
+ is cleaned up.
+
+ If str is NULL then NULL is returned.
+
+ @see cson_string_length_bytes()
+ @see cson_value_get_string()
+*/
+char const * cson_string_cstr( cson_string const * str );
+
+/**
+ Convenience function which returns the string bytes of
+ the given value if it is-a string, otherwise it returns
+ NULL. Note that this does no conversion of non-string types
+ to strings.
+
+ Equivalent to cson_string_cstr(cson_value_get_string(val)).
+*/
+char const * cson_value_get_cstr( cson_value const * val );
+
+/**
+ Equivalent to cson_string_cmp_cstr_n(lhs, cson_string_cstr(rhs), cson_string_length_bytes(rhs)).
+*/
+int cson_string_cmp( cson_string const * lhs, cson_string const * rhs );
+
+/**
+ Compares lhs to rhs using memcmp()/strcmp() semantics. Generically
+ speaking it returns a negative number if lhs is less-than rhs, 0 if
+ they are equivalent, or a positive number if lhs is greater-than
+ rhs. It has the following rules for equivalence:
+
+ - The maximum number of bytes compared is the lesser of rhsLen and
+ the length of lhs. If the strings do not match, but compare equal
+ up to the just-described comparison length, the shorter string is
+ considered to be less-than the longer one.
+
+ - If lhs and rhs are both NULL, or both have a length of 0 then they will
+ compare equal.
+
+ - If lhs is null/length-0 but rhs is not then lhs is considered to be less-than
+ rhs.
+
+ - If rhs is null/length-0 but lhs is not then rhs is considered to be less-than
+ rhs.
+
+ - i have no clue if the results are exactly correct for UTF strings.
+
+*/
+int cson_string_cmp_cstr_n( cson_string const * lhs, char const * rhs, unsigned int rhsLen );
+
+/**
+ Equivalent to cson_string_cmp_cstr_n( lhs, rhs, (rhs&&*rhs)?strlen(rhs):0 ).
+*/
+int cson_string_cmp_cstr( cson_string const * lhs, char const * rhs );
+
+/**
+ Returns the length, in bytes, of str, or 0 if str is NULL. This is
+ an O(1) operation.
+
+ TODO: add cson_string_length_chars() (is O(N) unless we add another
+ member to store the char length).
+
+ @see cson_string_cstr()
+*/
+unsigned int cson_string_length_bytes( cson_string const * str );
+
+/**
+ Returns the number of UTF8 characters in str. This value will
+ be at most as long as cson_string_length_bytes() for the
+ same string, and less if it has multi-byte characters.
+
+ Returns 0 if str is NULL.
+*/
+unsigned int cson_string_length_utf8( cson_string const * str );
+
+/**
+ Like cson_value_get_string(), but returns a copy of the underying
+ string bytes, which the caller owns and must eventually free
+ using free().
+*/
+char * cson_value_get_string_copy( cson_value const * val );
+
+/**
+ Simplified form of cson_value_fetch_object(). Returns NULL if val
+ is-not-a object value.
+*/
+cson_object * cson_value_get_object( cson_value const * val );
+
+/**
+ Simplified form of cson_value_fetch_array(). Returns NULL if val
+ is-not-a array value.
+*/
+cson_array * cson_value_get_array( cson_value const * val );
+
+/**
+ Const-correct form of cson_value_get_array().
+*/
+cson_array const * cson_value_get_array_c( cson_value const * val );
+
+/**
+ If ar is-a array and is at least (pos+1) entries long then *v (if v is not NULL)
+ is assigned to the value at that position (which may be NULL).
+
+ Ownership of the *v return value is unchanged by this call. (The
+ containing array may share ownership of the value with other
+ containers.)
+
+ If pos is out of range, non-0 is returned and *v is not modified.
+
+ If v is NULL then this function returns 0 if pos is in bounds, but does not
+ otherwise return a value to the caller.
+*/
+int cson_array_value_fetch( cson_array const * ar, unsigned int pos, cson_value ** v );
+
+/**
+ Simplified form of cson_array_value_fetch() which returns NULL if
+ ar is NULL, pos is out of bounds or if ar has no element at that
+ position.
+*/
+cson_value * cson_array_get( cson_array const * ar, unsigned int pos );
+
+/**
+ Ensures that ar has allocated space for at least the given
+ number of entries. This never shrinks the array and never
+ changes its logical size, but may pre-allocate space in the
+ array for storing new (as-yet-unassigned) values.
+
+ Returns 0 on success, or non-zero on error:
+
+ - If ar is NULL: cson_rc.ArgError
+
+ - If allocation fails: cson_rc.AllocError
+*/
+int cson_array_reserve( cson_array * ar, unsigned int size );
+
+/**
+ If ar is not NULL, sets *v (if v is not NULL) to the length of the array
+ and returns 0. Returns cson_rc.ArgError if ar is NULL.
+*/
+int cson_array_length_fetch( cson_array const * ar, unsigned int * v );
+
+/**
+ Simplified form of cson_array_length_fetch() which returns 0 if ar
+ is NULL.
+*/
+unsigned int cson_array_length_get( cson_array const * ar );
+
+/**
+ Sets the given index of the given array to the given value.
+
+ If ar already has an item at that index then it is cleaned up and
+ freed before inserting the new item.
+
+ ar is expanded, if needed, to be able to hold at least (ndx+1)
+ items, and any new entries created by that expansion are empty
+ (NULL values).
+
+ On success, 0 is returned and ownership of v is transfered to ar.
+
+ On error ownership of v is NOT modified, and the caller may still
+ need to clean it up. For example, the following code will introduce
+ a leak if this function fails:
+
+ @code
+ cson_array_append( myArray, cson_value_new_integer(42) );
+ @endcode
+
+ Because the value created by cson_value_new_integer() has no owner
+ and is not cleaned up. The "more correct" way to do this is:
+
+ @code
+ cson_value * v = cson_value_new_integer(42);
+ int rc = cson_array_append( myArray, v );
+ if( 0 != rc ) {
+ cson_value_free( v );
+ ... handle error ...
+ }
+ @endcode
+
+*/
+int cson_array_set( cson_array * ar, unsigned int ndx, cson_value * v );
+
+/**
+ Appends the given value to the given array, transfering ownership of
+ v to ar. On error, ownership of v is not modified. Ownership of ar
+ is never changed by this function.
+
+ This is functionally equivalent to
+ cson_array_set(ar,cson_array_length_get(ar),v), but this
+ implementation has slightly different array-preallocation policy
+ (it grows more eagerly).
+
+ Returns 0 on success, non-zero on error. Error cases include:
+
+ - ar or v are NULL: cson_rc.ArgError
+
+ - Array cannot be expanded to hold enough elements: cson_rc.AllocError.
+
+ - Appending would cause a numeric overlow in the array's size:
+ cson_rc.RangeError. (However, you'll get an AllocError long before
+ that happens!)
+
+ On error ownership of v is NOT modified, and the caller may still
+ need to clean it up. See cson_array_set() for the details.
+
+*/
+int cson_array_append( cson_array * ar, cson_value * v );
+
+
+/**
+ Creates a new cson_value from the given boolean value.
+
+ Ownership of the new value is passed to the caller, who must
+ eventually either free the value using cson_value_free() or
+ inserting it into a container (array or object), which transfers
+ ownership to the container. See the cson_value class documentation
+ for more details.
+
+ Returns NULL on allocation error.
+*/
+cson_value * cson_value_new_bool( char v );
+
+
+/**
+ Alias for cson_value_new_bool(v).
+*/
+cson_value * cson_new_bool(char v);
+
+/**
+ Returns the special JSON "null" value. When outputing JSON,
+ its string representation is "null" (without the quotes).
+
+ See cson_value_new_bool() for notes regarding the returned
+ value's memory.
+*/
+cson_value * cson_value_null();
+
+/**
+ Equivalent to cson_value_new_bool(1).
+*/
+cson_value * cson_value_true();
+
+/**
+ Equivalent to cson_value_new_bool(0).
+*/
+cson_value * cson_value_false();
+
+/**
+ Semantically the same as cson_value_new_bool(), but for integers.
+*/
+cson_value * cson_value_new_integer( cson_int_t v );
+
+/**
+ Alias for cson_value_new_integer(v).
+*/
+cson_value * cson_new_int(cson_int_t v);
+
+/**
+ Semantically the same as cson_value_new_bool(), but for doubles.
+*/
+cson_value * cson_value_new_double( cson_double_t v );
+
+/**
+ Alias for cson_value_new_double(v).
+*/
+cson_value * cson_new_double(cson_double_t v);
+
+/**
+ Semantically the same as cson_value_new_bool(), but for strings.
+ This creates a JSON value which copies the first n bytes of str.
+ The string will automatically be NUL-terminated.
+
+ Note that if str is NULL or n is 0, this function still
+ returns non-NULL value representing that empty string.
+
+ Returns NULL on allocation error.
+
+ See cson_value_new_bool() for important information about the
+ returned memory.
+*/
+cson_value * cson_value_new_string( char const * str, unsigned int n );
+
+/**
+ Allocates a new "object" value and transfers ownership of it to the
+ caller. It must eventually be destroyed, by the caller or its
+ owning container, by passing it to cson_value_free().
+
+ Returns NULL on allocation error.
+
+ Post-conditions: cson_value_is_object(value) will return true.
+
+ @see cson_value_new_array()
+ @see cson_value_free()
+*/
+cson_value * cson_value_new_object();
+
+/**
+ This works like cson_value_new_object() but returns an Object
+ handle directly.
+
+ The value handle for the returned object can be fetched with
+ cson_object_value(theObject).
+
+ Ownership is transfered to the caller, who must eventually free it
+ by passing the Value handle (NOT the Object handle) to
+ cson_value_free() or passing ownership to a parent container.
+
+ Returns NULL on error (out of memory).
+*/
+cson_object * cson_new_object();
+
+/**
+ Identical to cson_new_object() except that it creates
+ an Array.
+*/
+cson_array * cson_new_array();
+
+/**
+ Identical to cson_new_object() except that it creates
+ a String.
+*/
+cson_string * cson_new_string(char const * val, unsigned int len);
+
+/**
+ Equivalent to cson_value_free(cson_object_value(x)).
+*/
+void cson_free_object(cson_object *x);
+
+/**
+ Equivalent to cson_value_free(cson_array_value(x)).
+*/
+void cson_free_array(cson_array *x);
+
+/**
+ Equivalent to cson_value_free(cson_string_value(x)).
+*/
+void cson_free_string(cson_string const *x);
+
+
+/**
+ Allocates a new "array" value and transfers ownership of it to the
+ caller. It must eventually be destroyed, by the caller or its
+ owning container, by passing it to cson_value_free().
+
+ Returns NULL on allocation error.
+
+ Post-conditions: cson_value_is_array(value) will return true.
+
+ @see cson_value_new_object()
+ @see cson_value_free()
+*/
+cson_value * cson_value_new_array();
+
+/**
+ Frees any resources owned by v, then frees v. If v is a container
+ type (object or array) its children are also freed (recursively).
+
+ If v is NULL, this is a no-op.
+
+ This function decrements a reference count and only destroys the
+ value if its reference count drops to 0. Reference counts are
+ increased by either inserting the value into a container or via
+ cson_value_add_reference(). Even if this function does not
+ immediately destroy the value, the value must be considered, from
+ the perspective of that client code, to have been
+ destroyed/invalidated by this call.
+
+
+ @see cson_value_new_object()
+ @see cson_value_new_array()
+ @see cson_value_add_reference()
+*/
+void cson_value_free(cson_value * v);
+
+/**
+ Alias for cson_value_free().
+*/
+void cson_free_value(cson_value * v);
+
+
+/**
+ Functionally similar to cson_array_set(), but uses a string key
+ as an index. Like arrays, if a value already exists for the given key,
+ it is destroyed by this function before inserting the new value.
+
+ If v is NULL then this call is equivalent to
+ cson_object_unset(obj,key). Note that (v==NULL) is treated
+ differently from v having the special null value. In the latter
+ case, the key is set to the special null value.
+
+ The key may be encoded as ASCII or UTF8. Results are undefined
+ with other encodings, and the errors won't show up here, but may
+ show up later, e.g. during output.
+
+ Returns 0 on success, non-0 on error. It has the following error
+ cases:
+
+ - cson_rc.ArgError: obj or key are NULL or strlen(key) is 0.
+
+ - cson_rc.AllocError: an out-of-memory error
+
+ On error ownership of v is NOT modified, and the caller may still
+ need to clean it up. For example, the following code will introduce
+ a leak if this function fails:
+
+ @code
+ cson_object_set( myObj, "foo", cson_value_new_integer(42) );
+ @endcode
+
+ Because the value created by cson_value_new_integer() has no owner
+ and is not cleaned up. The "more correct" way to do this is:
+
+ @code
+ cson_value * v = cson_value_new_integer(42);
+ int rc = cson_object_set( myObj, "foo", v );
+ if( 0 != rc ) {
+ cson_value_free( v );
+ ... handle error ...
+ }
+ @endcode
+
+ Potential TODOs:
+
+ - Add an overload which takes a cson_value key instead. To get
+ any value out of that we first need to be able to convert arbitrary
+ value types to strings. We could simply to-JSON them and use those
+ as keys.
+*/
+int cson_object_set( cson_object * obj, char const * key, cson_value * v );
+
+/**
+ Functionaly equivalent to cson_object_set(), but takes a
+ cson_string() as its KEY type. The string will be reference-counted
+ like any other values, and the key may legally be used within this
+ same container (as a value) or others (as a key or value) at the
+ same time.
+
+ Returns 0 on success. On error, ownership (i.e. refcounts) of key
+ and value are not modified. On success key and value will get
+ increased refcounts unless they are replacing themselves (which is
+ a harmless no-op).
+*/
+int cson_object_set_s( cson_object * obj, cson_string * key, cson_value * v );
+
+/**
+ Removes a property from an object.
+
+ If obj contains the given key, it is removed and 0 is returned. If
+ it is not found, cson_rc.NotFoundError is returned (which can
+ normally be ignored by client code).
+
+ cson_rc.ArgError is returned if obj or key are NULL or key has
+ a length of 0.
+
+ Returns 0 if the given key is found and removed.
+
+ This is functionally equivalent calling
+ cson_object_set(obj,key,NULL).
+*/
+int cson_object_unset( cson_object * obj, char const * key );
+
+/**
+ Searches the given object for a property with the given key. If found,
+ it is returned. If no match is found, or any arguments are NULL, NULL is
+ returned. The returned object is owned by obj, and may be invalidated
+ by ANY operations which change obj's property list (i.e. add or remove
+ properties).
+
+ FIXME: allocate the key/value pairs like we do for cson_array,
+ to get improve the lifetimes of fetched values.
+
+ @see cson_object_fetch_sub()
+ @see cson_object_get_sub()
+*/
+cson_value * cson_object_get( cson_object const * obj, char const * key );
+
+/**
+ Equivalent to cson_object_get() but takes a cson_string argument
+ instead of a C-style string.
+*/
+cson_value * cson_object_get_s( cson_object const * obj, cson_string const *key );
+
+/**
+ Similar to cson_object_get(), but removes the value from the parent
+ object's ownership. If no item is found then NULL is returned, else
+ the object (now owned by the caller or possibly shared with other
+ containers) is returned.
+
+ Returns NULL if either obj or key are NULL or key has a length
+ of 0.
+
+ This function reduces the returned value's reference count but has
+ the specific property that it does not treat refcounts 0 and 1
+ identically, meaning that the returned object may have a refcount
+ of 0. This behaviour works around a corner-case where we want to
+ extract a child element from its parent and then destroy the parent
+ (which leaves us in an undesireable (normally) reference count
+ state).
+*/
+cson_value * cson_object_take( cson_object * obj, char const * key );
+
+/**
+ Fetches a property from a child (or [great-]*grand-child) object.
+
+ obj is the object to search.
+
+ path is a delimited string, where the delimiter is the given
+ separator character.
+
+ This function searches for the given path, starting at the given object
+ and traversing its properties as the path specifies. If a given part of the
+ path is not found, then this function fails with cson_rc.NotFoundError.
+
+ If it finds the given path, it returns the value by assiging *tgt
+ to it. If tgt is NULL then this function has no side-effects but
+ will return 0 if the given path is found within the object, so it can be used
+ to test for existence without fetching it.
+
+ Returns 0 if it finds an entry, cson_rc.NotFoundError if it finds
+ no item, and any other non-zero error code on a "real" error. Errors include:
+
+ - obj or path are NULL: cson_rc.ArgError
+
+ - separator is 0, or path is an empty string or contains only
+ separator characters: cson_rc.RangeError
+
+ - There is an upper limit on how long a single path component may
+ be (some "reasonable" internal size), and cson_rc.RangeError is
+ returned if that length is violated.
+
+
+ Limitations:
+
+ - It has no way to fetch data from arrays this way. i could
+ imagine, e.g., a path of "subobj.subArray.0" for
+ subobj.subArray[0], or "0.3.1" for [0][3][1]. But i'm too
+ lazy/tired to add this.
+
+ Example usage:
+
+
+ Assume we have a JSON structure which abstractly looks like:
+
+ @code
+ {"subobj":{"subsubobj":{"myValue":[1,2,3]}}}
+ @endcode
+
+ Out goal is to get the value of myValue. We can do that with:
+
+ @code
+ cson_value * v = NULL;
+ int rc = cson_object_fetch_sub( object, &v, "subobj.subsubobj.myValue", '.' );
+ @endcode
+
+ Note that because keys in JSON may legally contain a '.', the
+ separator must be specified by the caller. e.g. the path
+ "subobj/subsubobj/myValue" with separator='/' is equivalent the
+ path "subobj.subsubobj.myValue" with separator='.'. The value of 0
+ is not legal as a separator character because we cannot
+ distinguish that use from the real end-of-string without requiring
+ the caller to also pass in the length of the string.
+
+ Multiple successive separators in the list are collapsed into a
+ single separator for parsing purposes. e.g. the path "a...b...c"
+ (separator='.') is equivalent to "a.b.c".
+
+ @see cson_object_get_sub()
+ @see cson_object_get_sub2()
+*/
+int cson_object_fetch_sub( cson_object const * obj, cson_value ** tgt, char const * path, char separator );
+
+/**
+ Similar to cson_object_fetch_sub(), but derives the path separator
+ character from the first byte of the path argument. e.g. the
+ following arg equivalent:
+
+ @code
+ cson_object_fetch_sub( obj, &tgt, "foo.bar.baz", '.' );
+ cson_object_fetch_sub2( obj, &tgt, ".foo.bar.baz" );
+ @endcode
+*/
+int cson_object_fetch_sub2( cson_object const * obj, cson_value ** tgt, char const * path );
+
+/**
+ Convenience form of cson_object_fetch_sub() which returns NULL if the given
+ item is not found.
+*/
+cson_value * cson_object_get_sub( cson_object const * obj, char const * path, char sep );
+
+/**
+ Convenience form of cson_object_fetch_sub2() which returns NULL if the given
+ item is not found.
+*/
+cson_value * cson_object_get_sub2( cson_object const * obj, char const * path );
+
+/** @enum CSON_MERGE_FLAGS
+
+ Flags for cson_object_merge().
+*/
+enum CSON_MERGE_FLAGS {
+ CSON_MERGE_DEFAULT = 0,
+ CSON_MERGE_REPLACE = 0x01,
+ CSON_MERGE_NO_RECURSE = 0x02
+};
+
+/**
+ "Merges" the src object's properties into dest. Each property in
+ src is copied (using reference counting, not cloning) into dest. If
+ dest already has the given property then behaviour depends on the
+ flags argument:
+
+ If flag has the CSON_MERGE_REPLACE bit set then this function will
+ by default replace non-object properties with the src property. If
+ src and dest both have the property AND it is an Object then this
+ function operates recursively on those objects. If
+ CSON_MERGE_NO_RECURSE is set then objects are not recursed in this
+ manner, and will be completely replaced if CSON_MERGE_REPLACE is
+ set.
+
+ Array properties in dest are NOT recursed for merging - they are
+ either replaced or left as-is, depending on whether flags contains
+ he CSON_MERGE_REPLACE bit.
+
+ Returns 0 on success. The error conditions are:
+
+ - dest or src are NULL or (dest==src) returns cson_rc.ArgError.
+
+ - dest or src contain cyclic references - this will likely cause a
+ crash due to endless recursion.
+
+ Potential TODOs:
+
+ - Add a flag to copy clones, not the original values.
+*/
+int cson_object_merge( cson_object * dest, cson_object const * src, int flags );
+
+
+/**
+ An iterator type for traversing object properties.
+
+ Its values must be considered private, not to be touched by client
+ code.
+
+ @see cson_object_iter_init()
+ @see cson_object_iter_next()
+*/
+struct cson_object_iterator
+{
+
+ /** @internal
+ The underlying object.
+ */
+ cson_object const * obj;
+ /** @internal
+ Current position in the property list.
+ */
+ unsigned int pos;
+};
+typedef struct cson_object_iterator cson_object_iterator;
+
+/**
+ Empty-initialized cson_object_iterator object.
+*/
+#define cson_object_iterator_empty_m {NULL/*obj*/,0/*pos*/}
+
+/**
+ Empty-initialized cson_object_iterator object.
+*/
+extern const cson_object_iterator cson_object_iterator_empty;
+
+/**
+ Initializes the given iterator to point at the start of obj's
+ properties. Returns 0 on success or cson_rc.ArgError if !obj
+ or !iter.
+
+ obj must outlive iter, or results are undefined. Results are also
+ undefined if obj is modified while the iterator is active.
+
+ @see cson_object_iter_next()
+*/
+int cson_object_iter_init( cson_object const * obj, cson_object_iterator * iter );
+
+/** @struct cson_kvp
+
+This class represents a key/value pair and is used for storing
+object properties. It is opaque to client code, and the public
+API only uses this type for purposes of iterating over cson_object
+properties using the cson_object_iterator interfaces.
+*/
+
+typedef struct cson_kvp cson_kvp;
+
+/**
+ Returns the next property from the given iterator's object, or NULL
+ if the end of the property list as been reached.
+
+ Note that the order of object properties is undefined by the API,
+ and may change from version to version.
+
+ The returned memory belongs to the underlying object and may be
+ invalidated by any changes to that object.
+
+ Example usage:
+
+ @code
+ cson_object_iterator it;
+ cson_object_iter_init( myObject, &it ); // only fails if either arg is 0
+ cson_kvp * kvp;
+ cson_string const * key;
+ cson_value const * val;
+ while( (kvp = cson_object_iter_next(&it) ) )
+ {
+ key = cson_kvp_key(kvp);
+ val = cson_kvp_value(kvp);
+ ...
+ }
+ @endcode
+
+ There is no need to clean up an iterator, as it holds no dynamic resources.
+
+ @see cson_kvp_key()
+ @see cson_kvp_value()
+*/
+cson_kvp * cson_object_iter_next( cson_object_iterator * iter );
+
+
+/**
+ Returns the key associated with the given key/value pair,
+ or NULL if !kvp. The memory is owned by the object which contains
+ the key/value pair, and may be invalidated by any modifications
+ to that object.
+*/
+cson_string * cson_kvp_key( cson_kvp const * kvp );
+
+/**
+ Returns the value associated with the given key/value pair,
+ or NULL if !kvp. The memory is owned by the object which contains
+ the key/value pair, and may be invalidated by any modifications
+ to that object.
+*/
+cson_value * cson_kvp_value( cson_kvp const * kvp );
+
+/** @typedef some unsigned int type cson_size_t
+
+*/
+typedef unsigned int cson_size_t;
+
+/**
+ A generic buffer class.
+
+ They can be used like this:
+
+ @code
+ cson_buffer b = cson_buffer_empty;
+ int rc = cson_buffer_reserve( &buf, 100 );
+ if( 0 != rc ) { ... allocation error ... }
+ ... use buf.mem ...
+ ... then free it up ...
+ cson_buffer_reserve( &buf, 0 );
+ @endcode
+
+ To take over ownership of a buffer's memory:
+
+ @code
+ void * mem = b.mem;
+ // mem is b.capacity bytes long, but only b.used
+ // bytes of it has been "used" by the API.
+ b = cson_buffer_empty;
+ @endcode
+
+ The memory now belongs to the caller and must eventually be
+ free()d.
+*/
+struct cson_buffer
+{
+ /**
+ The number of bytes allocated for this object.
+ Use cson_buffer_reserve() to change its value.
+ */
+ cson_size_t capacity;
+ /**
+ The number of bytes "used" by this object. It is not needed for
+ all use cases, and management of this value (if needed) is up
+ to the client. The cson_buffer public API does not use this
+ member. The intention is that this can be used to track the
+ length of strings which are allocated via cson_buffer, since
+ they need an explicit length and/or null terminator.
+ */
+ cson_size_t used;
+
+ /**
+ This is a debugging/metric-counting value
+ intended to help certain malloc()-conscious
+ clients tweak their memory reservation sizes.
+ Each time cson_buffer_reserve() expands the
+ buffer, it increments this value by 1.
+ */
+ cson_size_t timesExpanded;
+
+ /**
+ The memory allocated for and owned by this buffer.
+ Use cson_buffer_reserve() to change its size or
+ free it. To take over ownership, do:
+
+ @code
+ void * myptr = buf.mem;
+ buf = cson_buffer_empty;
+ @endcode
+
+ (You might also need to store buf.used and buf.capacity,
+ depending on what you want to do with the memory.)
+
+ When doing so, the memory must eventually be passed to free()
+ to deallocate it.
+ */
+ unsigned char * mem;
+};
+/** Convenience typedef. */
+typedef struct cson_buffer cson_buffer;
+
+/** An empty-initialized cson_buffer object. */
+#define cson_buffer_empty_m {0/*capacity*/,0/*used*/,0/*timesExpanded*/,NULL/*mem*/}
+/** An empty-initialized cson_buffer object. */
+extern const cson_buffer cson_buffer_empty;
+
+/**
+ Uses cson_output() to append all JSON output to the given buffer
+ object. The semantics for the (v, opt) parameters, and the return
+ value, are as documented for cson_output(). buf must be a non-NULL
+ pointer to a properly initialized buffer (see example below).
+
+ Ownership of buf is not changed by calling this.
+
+ On success 0 is returned and the contents of buf.mem are guaranteed
+ to be NULL-terminated. On error the buffer might contain partial
+ contents, and it should not be used except to free its contents.
+
+ On error non-zero is returned. Errors include:
+
+ - Invalid arguments: cson_rc.ArgError
+
+ - Buffer cannot be expanded (runs out of memory): cson_rc.AllocError
+
+ Example usage:
+
+ @code
+ cson_buffer buf = cson_buffer_empty;
+ // optional: cson_buffer_reserve(&buf, 1024 * 10);
+ int rc = cson_output_buffer( myValue, &buf, NULL );
+ if( 0 != rc ) {
+ ... error! ...
+ }
+ else {
+ ... use buffer ...
+ puts((char const*)buf.mem);
+ }
+ // In both cases, we eventually need to clean up the buffer:
+ cson_buffer_reserve( &buf, 0 );
+ // Or take over ownership of its memory:
+ {
+ char * mem = (char *)buf.mem;
+ buf = cson_buffer_empty;
+ ...
+ free(mem);
+ }
+ @endcode
+
+ @see cson_output()
+
+*/
+int cson_output_buffer( cson_value const * v, cson_buffer * buf,
+ cson_output_opt const * opt );
+
+/**
+ This works identically to cson_parse_string(), but takes a
+ cson_buffer object as its input. buf->used bytes of buf->mem are
+ assumed to be valid JSON input, but it need not be NUL-terminated
+ (we only read up to buf->used bytes). The value of buf->used is
+ assumed to be the "string length" of buf->mem, i.e. not including
+ the NUL terminator.
+
+ Returns 0 on success, non-0 on error.
+
+ See cson_parse() for the semantics of the tgt, opt, and err
+ parameters.
+*/
+int cson_parse_buffer( cson_value ** tgt, cson_buffer const * buf,
+ cson_parse_opt const * opt, cson_parse_info * err );
+
+
+/**
+ Reserves the given amount of memory for the given buffer object.
+
+ If n is 0 then buf->mem is freed and its state is set to
+ NULL/0 values.
+
+ If buf->capacity is less than or equal to n then 0 is returned and
+ buf is not modified.
+
+ If n is larger than buf->capacity then buf->mem is (re)allocated
+ and buf->capacity contains the new length. Newly-allocated bytes
+ are filled with zeroes.
+
+ On success 0 is returned. On error non-0 is returned and buf is not
+ modified.
+
+ buf->mem is owned by buf and must eventually be freed by passing an
+ n value of 0 to this function.
+
+ buf->used is never modified by this function.
+*/
+int cson_buffer_reserve( cson_buffer * buf, cson_size_t n );
+
+/**
+ Fills all bytes of the given buffer with the given character.
+ Returns the number of bytes set (buf->capacity), or 0 if
+ !buf or buf has no memory allocated to it.
+*/
+cson_size_t cson_buffer_fill( cson_buffer * buf, char c );
+
+/**
+ Uses a cson_data_source_f() function to buffer input into a
+ cson_buffer.
+
+ dest must be a non-NULL, initialized (though possibly empty)
+ cson_buffer object. Its contents, if any, will be overwritten by
+ this function, and any memory it holds might be re-used.
+
+ The src function is called, and passed the state parameter, to
+ fetch the input. If it returns non-0, this function returns that
+ error code. src() is called, possibly repeatedly, until it reports
+ that there is no more data.
+
+ Whether or not this function succeeds, dest still owns any memory
+ pointed to by dest->mem, and the client must eventually free it by
+ calling cson_buffer_reserve(dest,0).
+
+ dest->mem might (and possibly will) be (re)allocated by this
+ function, so any pointers to it held from before this call might be
+ invalidated by this call.
+
+ On error non-0 is returned and dest has almost certainly been
+ modified but its state must be considered incomplete.
+
+ Errors include:
+
+ - dest or src are NULL (cson_rc.ArgError)
+
+ - Allocation error (cson_rc.AllocError)
+
+ - src() returns an error code
+
+ Whether or not the state parameter may be NULL depends on
+ the src implementation requirements.
+
+ On success dest will contain the contents read from the input
+ source. dest->used will be the length of the read-in data, and
+ dest->mem will point to the memory. dest->mem is automatically
+ NUL-terminated if this function succeeds, but dest->used does not
+ count that terminator. On error the state of dest->mem must be
+ considered incomplete, and is not guaranteed to be NUL-terminated.
+
+ Example usage:
+
+ @code
+ cson_buffer buf = cson_buffer_empty;
+ int rc = cson_buffer_fill_from( &buf,
+ cson_data_source_FILE,
+ stdin );
+ if( rc )
+ {
+ fprintf(stderr,"Error %d (%s) while filling buffer.\n",
+ rc, cson_rc_string(rc));
+ cson_buffer_reserve( &buf, 0 );
+ return ...;
+ }
+ ... use the buf->mem ...
+ ... clean up the buffer ...
+ cson_buffer_reserve( &buf, 0 );
+ @endcode
+
+ To take over ownership of the buffer's memory, do:
+
+ @code
+ void * mem = buf.mem;
+ buf = cson_buffer_empty;
+ @endcode
+
+ In which case the memory must eventually be passed to free() to
+ free it.
+*/
+int cson_buffer_fill_from( cson_buffer * dest, cson_data_source_f src, void * state );
+
+
+/**
+ Increments the reference count for the given value. This is a
+ low-level operation and should not normally be used by client code
+ without understanding exactly what side-effects it introduces.
+ Mis-use can lead to premature destruction or cause a value instance
+ to never be properly destructed (i.e. a memory leak).
+
+ This function is probably only useful for the following cases:
+
+ - You want to hold a reference to a value which is itself contained
+ in one or more containers, and you need to be sure that your
+ reference outlives the container(s) and/or that you can free your
+ copy of the reference without invaliding any references to the same
+ value held in containers.
+
+ - You want to implement "value sharing" behaviour without using an
+ object or array to contain the shared value. This can be used to
+ ensure the lifetime of the shared value instance. Each sharing
+ point adds a reference and simply passed the value to
+ cson_value_free() when they're done. The object will be kept alive
+ for other sharing points which added a reference.
+
+ Normally any such value handles would be invalidated when the
+ parent container(s) is/are cleaned up, but this function can be
+ used to effectively delay the cleanup.
+
+ This function, at its lowest level, increments the value's
+ reference count by 1.
+
+ To decrement the reference count, pass the value to
+ cson_value_free(), after which the value must be considered, from
+ the perspective of that client code, to be destroyed (though it
+ will not be if there are still other live references to
+ it). cson_value_free() will not _actually_ destroy the value until
+ its reference count drops to 0.
+
+ Returns 0 on success. The only error conditions are if v is NULL
+ (cson_rc.ArgError) or if the reference increment would overflow
+ (cson_rc.RangeError). In theory a client would get allocation
+ errors long before the reference count could overflow (assuming
+ those reference counts come from container insertions, as opposed
+ to via this function).
+
+ Insider notes which clients really need to know:
+
+ For shared/constant value instances, such as those returned by
+ cson_value_true() and cson_value_null(), this function has no side
+ effects - it does not actually modify the reference count because
+ (A) those instances are shared across all client code and (B) those
+ objects are static and never get cleaned up. However, that is an
+ implementation detail which client code should not rely on. In
+ other words, if you call cson_value_add_reference() 3 times using
+ the value returned by cson_value_true() (which is incidentally a
+ shared cson_value instance), you must eventually call
+ cson_value_free() 3 times to (semantically) remove those
+ references. However, internally the reference count for that
+ specific cson_value instance will not be modified and those
+ objects will never be freed (they're stack-allocated).
+
+ It might be interesting to note that newly-created objects
+ have a reference count of 0 instead of 1. This is partly because
+ if the initial reference is counted then it makes ownership
+ problematic when inserting values into containers. e.g. consider the
+ following code:
+
+ @code
+ // ACHTUNG: this code is hypothetical and does not reflect
+ // what actually happens!
+ cson_value * v =
+ cson_value_new_integer( 42 ); // v's refcount = 1
+ cson_array_append( myArray, v ); // v's refcount = 2
+ @endcode
+
+ If that were the case, the client would be forced to free his own
+ reference after inserting it into the container (which is a bit
+ counter-intuitive as well as intrusive). It would look a bit like
+ the following and would have to be done after every create/insert
+ operation:
+
+ @code
+ // ACHTUNG: this code is hypothetical and does not reflect
+ // what actually happens!
+ cson_array_append( myArray, v ); // v's refcount = 2
+ cson_value_free( v ); // v's refcount = 1
+ @endcode
+
+ (As i said: it's counter-intuitive and intrusive.)
+
+ Instead, values start with a refcount of 0 and it is only increased
+ when the value is added to an object/array container or when this
+ function is used to manually increment it. cson_value_free() treats
+ a refcount of 0 or 1 equivalently, destroying the value
+ instance. The only semantic difference between 0 and 1, for
+ purposes of cleaning up, is that a value with a non-0 refcount has
+ been had its refcount adjusted, whereas a 0 refcount indicates a
+ fresh, "unowned" reference.
+*/
+int cson_value_add_reference( cson_value * v );
+
+#if 0
+/**
+ DO NOT use this unless you know EXACTLY what you're doing.
+ It is only in the public API to work around a couple corner
+ cases involving extracting child elements and discarding
+ their parents.
+
+ This function sets v's reference count to the given value.
+ It does not clean up the object if rc is 0.
+
+ Returns 0 on success, non-0 on error.
+*/
+int cson_value_refcount_set( cson_value * v, unsigned short rc );
+#endif
+
+/**
+ Deeply copies a JSON value, be it an object/array or a "plain"
+ value (e.g. number/string/boolean). If cv is not NULL then this
+ function makes a deep clone of it and returns that clone. Ownership
+ of the clone is transfered to the caller, who must eventually free
+ the value using cson_value_free() or add it to a container
+ object/array to transfer ownership to the container. The returned
+ object will be of the same logical type as orig.
+
+ ACHTUNG: if orig contains any cyclic references at any depth level
+ this function will endlessly recurse. (Having _any_ cyclic
+ references violates this library's requirements.)
+
+ Returns NULL if orig is NULL or if cloning fails. Assuming that
+ orig is in a valid state, the only "likely" error case is that an
+ allocation fails while constructing the clone. In other words, if
+ cloning fails due to something other than an allocation error then
+ either orig is in an invalid state or there is a bug.
+*/
+cson_value * cson_value_clone( cson_value const * orig );
+
+/**
+ Returns the value handle associated with s. The handle itself owns
+ s, and ownership of the handle is not changed by calling this
+ function. If the returned handle is part of a container, calling
+ cson_value_free() on the returned handle invoked undefined
+ behaviour (quite possibly downstream when the container tries to
+ use it).
+
+ This function only returns NULL if s. is NULL.
+*/
+cson_value * cson_string_value(cson_string const * s);
+/**
+ The Object form of cson_string_value(). See that function
+ for full details.
+*/
+cson_value * cson_object_value(cson_object const * s);
+
+/**
+ The Array form of cson_string_value(). See that function
+ for full details.
+*/
+cson_value * cson_array_value(cson_array const * s);
+
+
+/**
+ Calculates the in-memory-allocated size of v, recursively if it is
+ a container type, with the following caveats and limitations:
+
+ If a given value is reference counted and multiple times within a
+ traversed container, each reference is counted at full cost. We
+ have no what of knowing if a given reference has been visited
+ already and whether it should or should not be counted, so we
+ pessimistically count them even though the _might_ not really count
+ for the given object tree (it depends on where the other open
+ references live).
+
+ This function returns 0 if any of the following are true:
+
+ - v is NULL
+
+ - v is one of the special singleton values (null, bools, empty
+ string, int 0, double 0.0)
+
+ All other values require an allocation, and this will return their
+ total memory cost, including the cson-specific internals and the
+ native value(s).
+
+ Note that because arrays and objects might have more internal slots
+ allocated than used, the alloced size of a container does not
+ necessarily increase when a new item is inserted into it. An interesting
+ side-effect of this is that when cson_clone()ing an array or object, the
+ size of the clone can actually be less than the original.
+*/
+unsigned int cson_value_msize(cson_value const * v);
+
+/* LICENSE
+
+This software's source code, including accompanying documentation and
+demonstration applications, are licensed under the following
+conditions...
+
+Certain files are imported from external projects and have their own
+licensing terms. Namely, the JSON_parser.* files. See their files for
+their official licenses, but the summary is "do what you want [with
+them] but leave the license text and copyright in place."
+
+The author (Stephan G. Beal [http://wanderinghorse.net/home/stephan/])
+explicitly disclaims copyright in all jurisdictions which recognize
+such a disclaimer. In such jurisdictions, this software is released
+into the Public Domain.
+
+In jurisdictions which do not recognize Public Domain property
+(e.g. Germany as of 2011), this software is Copyright (c) 2011 by
+Stephan G. Beal, and is released under the terms of the MIT License
+(see below).
+
+In jurisdictions which recognize Public Domain property, the user of
+this software may choose to accept it either as 1) Public Domain, 2)
+under the conditions of the MIT License (see below), or 3) under the
+terms of dual Public Domain/MIT License conditions described here, as
+they choose.
+
+The MIT License is about as close to Public Domain as a license can
+get, and is described in clear, concise terms at:
+
+ http://en.wikipedia.org/wiki/MIT_License
+
+The full text of the MIT License follows:
+
+--
+Copyright (c) 2011 Stephan G. Beal (http://wanderinghorse.net/home/stephan/)
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+--END OF MIT LICENSE--
+
+For purposes of the above license, the term "Software" includes
+documentation and demonstration source code which accompanies
+this software. ("Accompanies" = is contained in the Software's
+primary public source code repository.)
+
+*/
+
+#if defined(__cplusplus)
+} /*extern "C"*/
+#endif
+
+#endif /* WANDERINGHORSE_NET_CSON_H_INCLUDED */
+/* end file include/wh/cson/cson.h */
+/* begin file include/wh/cson/cson_sqlite3.h */
+/** @file cson_sqlite3.h
+
+This file contains cson's public sqlite3-to-JSON API declarations
+and API documentation. If CSON_ENABLE_SQLITE3 is not defined,
+or is defined to 0, then including this file will have no side-effects
+other than defining CSON_ENABLE_SQLITE3 (if it was not defined) to 0
+and defining a few include guard macros. i.e. if CSON_ENABLE_SQLITE3
+is not set to a true value then the API is not visible.
+
+This API requires that be in the INCLUDES path and that
+the client eventually link to (or directly embed) the sqlite3 library.
+*/
+#if !defined(WANDERINGHORSE_NET_CSON_SQLITE3_H_INCLUDED)
+#define WANDERINGHORSE_NET_CSON_SQLITE3_H_INCLUDED 1
+#if !defined(CSON_ENABLE_SQLITE3)
+# if defined(DOXYGEN)
+#define CSON_ENABLE_SQLITE3 1
+# else
+#define CSON_ENABLE_SQLITE3 1
+# endif
+#endif
+
+#if CSON_ENABLE_SQLITE3 /* we do this here for the sake of the amalgamation build */
+#include
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/**
+ Converts a single value from a single 0-based column index to its JSON
+ equivalent.
+
+ On success it returns a new JSON value, which will have a different concrete
+ type depending on the field type reported by sqlite3_column_type(st,col):
+
+ Integer, double, null, or string (TEXT and BLOB data, though not
+ all blob data is legal for a JSON string).
+
+ st must be a sqlite3_step()'d row and col must be a 0-based column
+ index within that result row.
+ */
+cson_value * cson_sqlite3_column_to_value( sqlite3_stmt * st, int col );
+
+/**
+ Creates a JSON Array object containing the names of all columns
+ of the given prepared statement handle.
+
+ Returns a new array value on success, which the caller owns. Its elements
+ are in the same order as in the underlying query.
+
+ On error NULL is returned.
+
+ st is not traversed or freed by this function - only the column
+ count and names are read.
+*/
+cson_value * cson_sqlite3_column_names( sqlite3_stmt * st );
+
+/**
+ Creates a JSON Object containing key/value pairs corresponding
+ to the result columns in the current row of the given statement
+ handle. st must be a sqlite3_step()'d row result.
+
+ On success a new Object is returned which is owned by the
+ caller. On error NULL is returned.
+
+ cson_sqlite3_column_to_value() is used to convert each column to a
+ JSON value, and the column names are taken from
+ sqlite3_column_name().
+*/
+cson_value * cson_sqlite3_row_to_object( sqlite3_stmt * st );
+/**
+ Functionally almost identical to cson_sqlite3_row_to_object(), the
+ only difference being how the result objects gets its column names.
+ st must be a freshly-step()'d handle holding a result row.
+ colNames must be an Array with at least the same number of columns
+ as st. If it has fewer, NULL is returned and this function has
+ no side-effects.
+
+ For each column in the result set, the colNames entry at the same
+ index is used for the column key. If a given entry is-not-a String
+ then conversion will fail and NULL will be returned.
+
+ The one reason to prefer this over cson_sqlite3_row_to_object() is
+ that this one can share the keys across multiple rows (or even
+ other JSON containers), whereas the former makes fresh copies of
+ the column names for each row.
+
+*/
+cson_value * cson_sqlite3_row_to_object2( sqlite3_stmt * st,
+ cson_array * colNames );
+
+/**
+ Similar to cson_sqlite3_row_to_object(), but creates an Array
+ value which contains the JSON-form values of the given result
+ set row.
+*/
+cson_value * cson_sqlite3_row_to_array( sqlite3_stmt * st );
+/**
+ Converts the results of an sqlite3 SELECT statement to JSON,
+ in the form of a cson_value object tree.
+
+ st must be a prepared, but not yet traversed, SELECT query.
+ tgt must be a pointer to NULL (see the example below). If
+ either of those arguments are NULL, cson_rc.ArgError is returned.
+
+ This walks the query results and returns a JSON object which
+ has a different structure depending on the value of the 'fat'
+ argument.
+
+
+ If 'fat' is 0 then the structure is:
+
+ @code
+ {
+ "columns":["colName1",..."colNameN"],
+ "rows":[
+ [colVal0, ... colValN],
+ [colVal0, ... colValN],
+ ...
+ ]
+ }
+ @endcode
+
+ In the "non-fat" format the order of the columns and row values is
+ guaranteed to be the same as that of the underlying query.
+
+ If 'fat' is not 0 then the structure is:
+
+ @code
+ {
+ "columns":["colName1",..."colNameN"],
+ "rows":[
+ {"colName1":value1,..."colNameN":valueN},
+ {"colName1":value1,..."colNameN":valueN},
+ ...
+ ]
+ }
+ @endcode
+
+ In the "fat" format, the order of the "columns" entries is guaranteed
+ to be the same as the underlying query fields, but the order
+ of the keys in the "rows" might be different and might in fact
+ change when passed through different JSON implementations,
+ depending on how they implement object key/value pairs.
+
+ On success it returns 0 and assigns *tgt to a newly-allocated
+ JSON object tree (using the above structure), which the caller owns.
+ If the query returns no rows, the "rows" value will be an empty
+ array, as opposed to null.
+
+ On error non-0 is returned and *tgt is not modified.
+
+ The error code cson_rc.IOError is used to indicate a db-level
+ error, and cson_rc.TypeError is returned if sqlite3_column_count(st)
+ returns 0 or less (indicating an invalid or non-SELECT statement).
+
+ The JSON data types are determined by the column type as reported
+ by sqlite3_column_type():
+
+ SQLITE_INTEGER: integer
+
+ SQLITE_FLOAT: double
+
+ SQLITE_TEXT or SQLITE_BLOB: string, and this will only work if
+ the data is UTF8 compatible.
+
+ If the db returns a literal or SQL NULL for a value it is converted
+ to a JSON null. If it somehow finds a column type it cannot handle,
+ the value is also converted to a NULL in the output.
+
+ Example
+
+ @code
+ cson_value * json = NULL;
+ int rc = cson_sqlite3_stmt_to_json( myStatement, &json, 1 );
+ if( 0 != rc ) { ... error ... }
+ else {
+ cson_output_FILE( json, stdout, NULL );
+ cson_value_free( json );
+ }
+ @endcode
+*/
+int cson_sqlite3_stmt_to_json( sqlite3_stmt * st, cson_value ** tgt, char fat );
+
+/**
+ A convenience wrapper around cson_sqlite3_stmt_to_json(), which
+ takes SQL instead of a sqlite3_stmt object. It has the same
+ return value and argument semantics as that function.
+*/
+int cson_sqlite3_sql_to_json( sqlite3 * db, cson_value ** tgt, char const * sql, char fat );
+
+#if defined(__cplusplus)
+} /*extern "C"*/
+#endif
+
+#endif /* CSON_ENABLE_SQLITE3 */
+#endif /* WANDERINGHORSE_NET_CSON_SQLITE3_H_INCLUDED */
+/* end file include/wh/cson/cson_sqlite3.h */
Index: src/db.c
==================================================================
--- src/db.c
+++ src/db.c
@@ -48,40 +48,57 @@
Blob sql; /* The SQL for this statement */
sqlite3_stmt *pStmt; /* The results of sqlite3_prepare() */
Stmt *pNext, *pPrev; /* List of all unfinalized statements */
int nStep; /* Number of sqlite3_step() calls */
};
+
+/*
+** Copy this to initialize a Stmt object to a clean/empty state. This
+** is useful to help avoid assertions when performing cleanup in some
+** error handling cases.
+*/
+#define empty_Stmt_m {BLOB_INITIALIZER,NULL, NULL, NULL, 0}
#endif /* INTERFACE */
+const struct Stmt empty_Stmt = empty_Stmt_m;
/*
** Call this routine when a database error occurs.
*/
static void db_err(const char *zFormat, ...){
va_list ap;
char *z;
+ int rc = 1;
static const char zRebuildMsg[] =
"If you have recently updated your fossil executable, you might\n"
"need to run \"fossil all rebuild\" to bring the repository\n"
"schemas up to date.\n";
va_start(ap, zFormat);
z = vmprintf(zFormat, ap);
va_end(ap);
- if( g.xferPanic ){
- cgi_reset_content();
- @ error Database\serror:\s%F(z)
- cgi_reply();
- }
- if( g.cgiOutput ){
- g.cgiOutput = 0;
- cgi_printf("
", z, zRebuildMsg);
+ cgi_reply();
+ }else{
+ fprintf(stderr, "%s: %s\n\n%s", fossil_nameofexe(), z, zRebuildMsg);
+ }
}
+ free(z);
db_force_rollback();
- fossil_exit(1);
+ fossil_exit(rc);
}
static int nBegin = 0; /* Nesting depth of BEGIN */
static int doRollback = 0; /* True to force a rollback */
static int nCommitHook = 0; /* Number of commit hooks */
@@ -561,11 +578,11 @@
** Execute a query. Return the first column of the first row
** of the result set as a string. Space to hold the string is
** obtained from malloc(). If the result set is empty, return
** zDefault instead.
*/
-char *db_text(char *zDefault, const char *zSql, ...){
+char *db_text(char const *zDefault, const char *zSql, ...){
va_list ap;
Stmt s;
char *z;
va_start(ap, zSql);
db_vprepare(&s, 0, zSql, ap);
@@ -863,15 +880,18 @@
db_err("unable to find the name of a repository database");
}
}
if( file_access(zDbName, R_OK) || file_size(zDbName)<1024 ){
if( file_access(zDbName, 0) ){
+ g.json.resultCode = FSL_JSON_E_DB_NOT_FOUND;
fossil_panic("repository does not exist or"
" is in an unreadable directory: %s", zDbName);
}else if( file_access(zDbName, R_OK) ){
+ g.json.resultCode = FSL_JSON_E_DENIED;
fossil_panic("read permission denied for repository %s", zDbName);
}else{
+ g.json.resultCode = FSL_JSON_E_DB_NOT_VALID;
fossil_panic("not a valid repository: %s", zDbName);
}
}
db_open_or_attach(zDbName, "repository");
g.repositoryOpen = 1;
@@ -902,11 +922,11 @@
}
if( zRep==0 ){
if( db_open_local()==0 ){
goto rep_not_found;
}
- zRep = db_lget("repository", 0);
+ zRep = db_lget("repository", 0)/*leak here*/;
if( zRep==0 ){
goto rep_not_found;
}
}
db_open_repository(zRep);
@@ -914,10 +934,11 @@
if( (bFlags & OPEN_ANY_SCHEMA)==0 ) db_verify_schema();
return;
}
rep_not_found:
if( (bFlags & OPEN_OK_NOT_FOUND)==0 ){
+ g.json.resultCode = FSL_JSON_E_DB_NOT_FOUND;
fossil_fatal("use --repository or -R to specify the repository database");
}
}
/*
@@ -944,10 +965,11 @@
** Verify that the repository schema is correct. If it is not correct,
** issue a fatal error and die.
*/
void db_verify_schema(void){
if( db_schema_is_outofdate() ){
+ g.json.resultCode = FSL_JSON_E_DB_NEEDS_REBUILD;
fossil_warning("incorrect repository schema version");
fossil_warning("your repository has schema version \"%s\" "
"but this binary expects version \"%s\"",
db_get("aux-schema",0), AUX_SCHEMA);
fossil_fatal("run \"fossil rebuild\" to fix this problem");
ADDED src/json.c
Index: src/json.c
==================================================================
--- /dev/null
+++ src/json.c
@@ -0,0 +1,2424 @@
+/*
+** Copyright (c) 2011 D. Richard Hipp
+**
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the Simplified BSD License (also
+** known as the "2-Clause License" or "FreeBSD License".)
+
+** This program is distributed in the hope that it will be useful,
+** but without any warranty; without even the implied warranty of
+** merchantability or fitness for a particular purpose.
+**
+** Author contact information:
+** drh@hwaci.com
+** http://www.hwaci.com/drh/
+**
+*******************************************************************************
+**
+** Code for the JSON API.
+**
+** For notes regarding the public JSON interface, please see:
+**
+** https://docs.google.com/document/d/1fXViveNhDbiXgCuE7QDXQOKeFzf2qNUkBEgiUvoqFN4/edit
+**
+**
+** Notes for hackers...
+**
+** Here's how command/page dispatching works: json_page_top() (in HTTP mode) or
+** json_cmd_top() (in CLI mode) catch the "json" path/command. Those functions then
+** dispatch to a JSON-mode-specific command/page handler with the type fossil_json_f().
+** See the API docs for that typedef (below) for the semantics of the callbacks.
+**
+**
+*/
+#include "config.h"
+#include "VERSION.h"
+#include "json.h"
+#include
+#include
+
+#if INTERFACE
+#include "json_detail.h" /* workaround for apparent enum limitation in makeheaders */
+#endif
+
+const FossilJsonKeys_ FossilJsonKeys = {
+ "anonymousSeed" /*anonymousSeed*/,
+ "authToken" /*authToken*/,
+ "COMMAND_PATH" /*commandPath*/,
+ "mtime" /*mtime*/,
+ "payload" /* payload */,
+ "requestId" /*requestId*/,
+ "resultCode" /*resultCode*/,
+ "resultText" /*resultText*/,
+ "timestamp" /*timestamp*/
+};
+
+/*
+** Internal helpers to manipulate a byte array as a bitset. The B
+** argument must be-a array at least (BIT/8+1) bytes long.
+** The BIT argument is the bit number to query/set/clear/toggle.
+*/
+#define BITSET_BYTEFOR(B,BIT) ((B)[ BIT / 8 ])
+#define BITSET_SET(B,BIT) ((BITSET_BYTEFOR(B,BIT) |= (0x01 << (BIT%8))),0x01)
+#define BITSET_UNSET(B,BIT) ((BITSET_BYTEFOR(B,BIT) &= ~(0x01 << (BIT%8))),0x00)
+#define BITSET_GET(B,BIT) ((BITSET_BYTEFOR(B,BIT) & (0x01 << (BIT%8))) ? 0x01 : 0x00)
+#define BITSET_TOGGLE(B,BIT) (BITSET_GET(B,BIT) ? (BITSET_UNSET(B,BIT)) : (BITSET_SET(B,BIT)))
+
+
+/* Timer code taken from sqlite3's shell.c, modified slightly.
+ FIXME: move the timer into the fossil core API so that we can
+ start the timer early on in the app init phase. Right now we're
+ just timing the json ops themselves.
+*/
+#if !defined(_WIN32) && !defined(WIN32) && !defined(__OS2__) && !defined(__RTP__) && !defined(_WRS_KERNEL)
+#include
+#include
+
+/* Saved resource information for the beginning of an operation */
+static struct rusage sBegin;
+
+/*
+** Begin timing an operation
+*/
+static void beginTimer(void){
+ getrusage(RUSAGE_SELF, &sBegin);
+}
+
+/* Return the difference of two time_structs in milliseconds */
+static double timeDiff(struct timeval *pStart, struct timeval *pEnd){
+ return ((pEnd->tv_usec - pStart->tv_usec)*0.001 +
+ (double)((pEnd->tv_sec - pStart->tv_sec)*1000.0));
+}
+
+/*
+** Print the timing results.
+*/
+static double endTimer(void){
+ struct rusage sEnd;
+ getrusage(RUSAGE_SELF, &sEnd);
+ return timeDiff(&sBegin.ru_utime, &sEnd.ru_utime)
+ + timeDiff(&sBegin.ru_stime, &sEnd.ru_stime);
+#if 0
+ printf("CPU Time: user %f sys %f\n",
+ timeDiff(&sBegin.ru_utime, &sEnd.ru_utime),
+ timeDiff(&sBegin.ru_stime, &sEnd.ru_stime));
+#endif
+}
+
+#define BEGIN_TIMER beginTimer()
+#define END_TIMER endTimer()
+#define HAS_TIMER 1
+
+#elif (defined(_WIN32) || defined(WIN32))
+
+#include
+
+/* Saved resource information for the beginning of an operation */
+static HANDLE hProcess;
+static FILETIME ftKernelBegin;
+static FILETIME ftUserBegin;
+typedef BOOL (WINAPI *GETPROCTIMES)(HANDLE, LPFILETIME, LPFILETIME, LPFILETIME, LPFILETIME);
+static GETPROCTIMES getProcessTimesAddr = NULL;
+
+/*
+** Check to see if we have timer support. Return 1 if necessary
+** support found (or found previously).
+*/
+static int hasTimer(void){
+ if( getProcessTimesAddr ){
+ return 1;
+ } else {
+ /* GetProcessTimes() isn't supported in WIN95 and some other Windows versions.
+ ** See if the version we are running on has it, and if it does, save off
+ ** a pointer to it and the current process handle.
+ */
+ hProcess = GetCurrentProcess();
+ if( hProcess ){
+ HINSTANCE hinstLib = LoadLibrary(TEXT("Kernel32.dll"));
+ if( NULL != hinstLib ){
+ getProcessTimesAddr = (GETPROCTIMES) GetProcAddress(hinstLib, "GetProcessTimes");
+ if( NULL != getProcessTimesAddr ){
+ return 1;
+ }
+ FreeLibrary(hinstLib);
+ }
+ }
+ }
+ return 0;
+}
+
+/*
+** Begin timing an operation
+*/
+static void beginTimer(void){
+ if( getProcessTimesAddr ){
+ FILETIME ftCreation, ftExit;
+ getProcessTimesAddr(hProcess, &ftCreation, &ftExit, &ftKernelBegin, &ftUserBegin);
+ }
+}
+
+/* Return the difference of two FILETIME structs in milliseconds */
+static double timeDiff(FILETIME *pStart, FILETIME *pEnd){
+ sqlite_int64 i64Start = *((sqlite_int64 *) pStart);
+ sqlite_int64 i64End = *((sqlite_int64 *) pEnd);
+ return (double) ((i64End - i64Start) / 10000.0);
+}
+
+/*
+** Print the timing results.
+*/
+static double endTimer(void){
+ if(getProcessTimesAddr){
+ FILETIME ftCreation, ftExit, ftKernelEnd, ftUserEnd;
+ getProcessTimesAddr(hProcess, &ftCreation, &ftExit, &ftKernelEnd, &ftUserEnd);
+ return timeDiff(&ftUserBegin, &ftUserEnd) +
+ timeDiff(&ftKernelBegin, &ftKernelEnd);
+ }
+}
+
+#define BEGIN_TIMER beginTimer()
+#define END_TIMER endTimer()
+#define HAS_TIMER hasTimer()
+
+#else
+#define BEGIN_TIMER
+#define END_TIMER 0.0
+#define HAS_TIMER 0
+#endif
+
+
+char fossil_has_json(){
+ return g.json.isJsonMode && (g.isHTTP || g.json.post.o);
+}
+
+/*
+** Placeholder /json/XXX page impl for NYI (Not Yet Implemented)
+** (but planned) pages/commands.
+*/
+cson_value * json_page_nyi(){
+ g.json.resultCode = FSL_JSON_E_NYI;
+ return NULL;
+}
+
+/*
+** Given a FossilJsonCodes value, it returns a string suitable for use
+** as a resultCode string. Returns some unspecified non-empty string
+** if errCode is not one of the FossilJsonCodes values.
+*/
+static char const * json_err_cstr( int errCode ){
+ switch( errCode ){
+ case 0: return "Success";
+#define C(X,V) case FSL_JSON_E_ ## X: return V
+
+ C(GENERIC,"Generic error");
+ C(INVALID_REQUEST,"Invalid request");
+ C(UNKNOWN_COMMAND,"Unknown Command");
+ C(UNKNOWN,"Unknown error");
+ C(TIMEOUT,"Timeout reached");
+ C(ASSERT,"Assertion failed");
+ C(ALLOC,"Resource allocation failed");
+ C(NYI,"Not yet implemented");
+ C(PANIC,"x");
+ C(MANIFEST_READ_FAILED,"Reading artifact manifest failed");
+ C(FILE_OPEN_FAILED,"Opening file failed");
+
+ C(AUTH,"Authentication error");
+ C(MISSING_AUTH,"Authentication info missing from request");
+ C(DENIED,"Access denied");
+ C(WRONG_MODE,"Request not allowed (wrong operation mode)");
+ C(LOGIN_FAILED,"Login failed");
+ C(LOGIN_FAILED_NOSEED,"Anonymous login attempt was missing password seed");
+ C(LOGIN_FAILED_NONAME,"Login failed - name not supplied");
+ C(LOGIN_FAILED_NOPW,"Login failed - password not supplied");
+ C(LOGIN_FAILED_NOTFOUND,"Login failed - no match found");
+
+ C(USAGE,"Usage error");
+ C(INVALID_ARGS,"Invalid argument(s)");
+ C(MISSING_ARGS,"Missing argument(s)");
+ C(AMBIGUOUS_UUID,"Resource identifier is ambiguous");
+ C(UNRESOLVED_UUID,"Provided uuid/tag/branch could not be resolved");
+ C(RESOURCE_ALREADY_EXISTS,"Resource already exists");
+ C(RESOURCE_NOT_FOUND,"Resource not found");
+
+ C(DB,"Database error");
+ C(STMT_PREP,"Statement preparation failed");
+ C(STMT_BIND,"Statement parameter binding failed");
+ C(STMT_EXEC,"Statement execution/stepping failed");
+ C(DB_LOCKED,"Database is locked");
+ C(DB_NEEDS_REBUILD,"Fossil repository needs to be rebuilt");
+ C(DB_NOT_FOUND,"Fossil repository db file could not be found.");
+ C(DB_NOT_VALID, "Fossil repository db file is not valid.");
+#undef C
+ default:
+ return "Unknown Error";
+ }
+}
+
+/*
+** Implements the cson_data_dest_f() interface and outputs the data to
+** a fossil Blob object. pState must be-a initialized (Blob*), to
+** which n bytes of src will be appended.
+**/
+int cson_data_dest_Blob(void * pState, void const * src, unsigned int n){
+ Blob * b = (Blob*)pState;
+ blob_append( b, (char const *)src, (int)n ) /* will die on OOM */;
+ return 0;
+}
+
+/*
+** Implements the cson_data_source_f() interface and reads input from
+** a fossil Blob object. pState must be-a (Blob*) populated with JSON
+** data.
+*/
+int cson_data_src_Blob(void * pState, void * dest, unsigned int * n){
+ Blob * b = (Blob*)pState;
+ *n = blob_read( b, dest, *n );
+ return 0;
+}
+
+/*
+** Convenience wrapper around cson_output() which appends the output
+** to pDest. pOpt may be NULL, in which case g.json.outOpt will be used.
+*/
+int cson_output_Blob( cson_value const * pVal, Blob * pDest, cson_output_opt const * pOpt ){
+ return cson_output( pVal, cson_data_dest_Blob,
+ pDest, pOpt ? pOpt : &g.json.outOpt );
+}
+
+/*
+** Convenience wrapper around cson_parse() which reads its input
+** from pSrc. pSrc is rewound before parsing.
+**
+** pInfo may be NULL. If it is not NULL then it will contain details
+** about the parse state when this function returns.
+**
+** On success a new JSON Object or Array is returned (owned by the
+** caller). On error NULL is returned.
+*/
+cson_value * cson_parse_Blob( Blob * pSrc, cson_parse_info * pInfo ){
+ cson_value * root = NULL;
+ blob_rewind( pSrc );
+ cson_parse( &root, cson_data_src_Blob, pSrc, NULL, pInfo );
+ return root;
+}
+
+/*
+** Implements the cson_data_dest_f() interface and outputs the data to
+** cgi_append_content(). pState is ignored.
+**/
+int cson_data_dest_cgi(void * pState, void const * src, unsigned int n){
+ cgi_append_content( (char const *)src, (int)n );
+ return 0;
+}
+
+/*
+** Returns a string in the form FOSSIL-XXXX, where XXXX is a
+** left-zero-padded value of code. The returned buffer is static, and
+** must be copied if needed for later. The returned value will always
+** be 11 bytes long (not including the trailing NUL byte).
+**
+** In practice we will only ever call this one time per app execution
+** when constructing the JSON response envelope, so the static buffer
+** "shouldn't" be a problem.
+**
+*/
+char const * json_rc_cstr( int code ){
+ enum { BufSize = 12 };
+ static char buf[BufSize] = {'F','O','S','S','I','L','-',0};
+ assert((code >= 1000) && (code <= 9999) && "Invalid Fossil/JSON code.");
+ sprintf(buf+7,"%04d", code);
+ return buf;
+}
+
+/*
+** Adds v to the API-internal cleanup mechanism. key is ingored
+** (legacy) but might be re-introduced and "should" be a unique
+** (app-wide) value. Failure to insert an item may be caused by any
+** of the following:
+**
+** - Allocation error.
+** - g.json.gc.a is NULL
+** - key is NULL or empty.
+**
+** Returns 0 on success.
+**
+** Ownership of v is transfered to (or shared with) g.json.gc, and v
+** will be valid until that object is cleaned up or some internal code
+** incorrectly removes it from the gc (which we never do). If this
+** function fails, it is fatal to the app (as it indicates an
+** allocation error (more likely than not) or a serious internal error
+** such as numeric overflow).
+*/
+void json_gc_add( char const * key, cson_value * v ){
+ int const rc = cson_array_append( g.json.gc.a, v );
+ assert( NULL != g.json.gc.a );
+ if( 0 != rc ){
+ cson_value_free( v );
+ }
+ assert( (0==rc) && "Adding item to GC failed." );
+ if(0!=rc){
+ fprintf(stderr,"%s: FATAL: alloc error.\n", fossil_nameofexe())
+ /* reminder: allocation error is the only reasonable cause of
+ error here, provided g.json.gc.a and v are not NULL.
+ */
+ ;
+ fossil_exit(1)/*not fossil_panic() b/c it might land us somewhere
+ where this function is called again.
+ */;
+ }
+}
+
+
+/*
+** Returns the value of json_rc_cstr(code) as a new JSON
+** string, which is owned by the caller and must eventually
+** be cson_value_free()d or transfered to a JSON container.
+*/
+cson_value * json_rc_string( int code ){
+ return cson_value_new_string( json_rc_cstr(code), 11 );
+}
+
+cson_value * json_new_string( char const * str ){
+ return str
+ ? cson_value_new_string(str,strlen(str))
+ : NULL;
+}
+
+cson_value * json_new_string_f( char const * fmt, ... ){
+ cson_value * v;
+ char * zStr;
+ va_list vargs;
+ va_start(vargs,fmt);
+ zStr = vmprintf(fmt,vargs);
+ va_end(vargs);
+ v = cson_value_new_string(zStr, strlen(zStr));
+ free(zStr);
+ return v;
+}
+
+cson_value * json_new_int( int v ){
+ return cson_value_new_integer((cson_int_t)v);
+}
+
+/*
+** Gets a POST/POST.payload/GET/COOKIE/ENV value. The returned memory
+** is owned by the g.json object (one of its sub-objects). Returns
+** NULL if no match is found.
+**
+** ENV means the system environment (getenv()).
+**
+** Precedence: POST.payload, GET/COOKIE/non-JSON POST, JSON POST, ENV.
+**
+** FIXME: the precedence SHOULD be: GET, POST.payload, POST, COOKIE,
+** ENV, but the amalgamation of the GET/POST vars makes it difficult
+** for me to do that. Since fossil only uses one cookie, cookie
+** precedence isn't a real/high-priority problem.
+*/
+cson_value * json_getenv( char const * zKey ){
+ cson_value * rc;
+ rc = g.json.reqPayload.o
+ ? cson_object_get( g.json.reqPayload.o, zKey )
+ : NULL;
+ if(rc){
+ return rc;
+ }
+ rc = cson_object_get( g.json.param.o, zKey );
+ if( rc ){
+ return rc;
+ }
+ rc = cson_object_get( g.json.post.o, zKey );
+ if(rc){
+ return rc;
+ }else{
+ char const * cv = PD(zKey,NULL);
+ if(!cv && !g.isHTTP){
+ /* reminder to self: in CLI mode i'd like to try
+ find_option(zKey,NULL,XYZ) here, but we don't have a sane
+ default for the XYZ param here.
+ */
+ cv = getenv(zKey);
+ }
+ if(cv){/*transform it to JSON for later use.*/
+ /* use sscanf() to figure out if it's an int,
+ and transform it to JSON int if it is.
+
+ FIXME: use strtol(), since it has more accurate
+ error handling.
+ */
+ int intVal = -1;
+ char endOfIntCheck;
+ int const scanRc = sscanf(cv,"%d%c",&intVal, &endOfIntCheck)
+ /* The %c bit there is to make sure that we don't accept 123x
+ as a number. sscanf() returns the number of tokens
+ successfully parsed, so an RC of 1 will be correct for "123"
+ but "123x" will have RC==2.
+
+ But it appears to not be working that way :/
+ */
+ ;
+ if(1==scanRc){
+ json_setenv( zKey, cson_value_new_integer(intVal) );
+ }else{
+ rc = cson_value_new_string(cv,strlen(cv));
+ json_setenv( zKey, rc );
+ }
+ return rc;
+ }else{
+ return NULL;
+ }
+ }
+}
+
+/*
+** Wrapper around json_getenv() which...
+**
+** If it finds a value and that value is-a JSON number or is a string
+** which looks like an integer or is-a JSON bool/null then it is
+** converted to an int. If none of those apply then dflt is returned.
+*/
+int json_getenv_int(char const * pKey, int dflt ){
+ cson_value const * v = json_getenv(pKey);
+ if(!v){
+ return dflt;
+ }else if( cson_value_is_number(v) ){
+ return (int)cson_value_get_integer(v);
+ }else if( cson_value_is_string(v) ){
+ char const * sv = cson_string_cstr(cson_value_get_string(v));
+ assert( (NULL!=sv) && "This is quite unexpected." );
+ return sv ? atoi(sv) : dflt;
+ }else if( cson_value_is_bool(v) ){
+ return cson_value_get_bool(v) ? 1 : 0;
+ }else if( cson_value_is_null(v) ){
+ return 0;
+ }else{
+ /* we should arguably treat JSON null as 0. */
+ return dflt;
+ }
+}
+
+
+/*
+** Wrapper around json_getenv() which tries to evaluate a payload/env
+** value as a boolean. Uses mostly the same logic as
+** json_getenv_int(), with the addition that string values which
+** either start with a digit 1..9 or the letters [tT] are considered
+** to be true. If this function cannot find a matching key/value then
+** dflt is returned. e.g. if it finds the key but the value is-a
+** Object then dftl is returned.
+**
+** If an entry is found, this function guarantees that it will return
+** either 0 or 1, as opposed to "0 or non-zero", so that clients can
+** pass a different value as dflt. Thus they can use, e.g. -1 to know
+** whether or not this function found a match (it will return -1 in
+** that case).
+*/
+char json_getenv_bool(char const * pKey, char dflt ){
+ cson_value const * v = json_getenv(pKey);
+ if(!v){
+ return dflt;
+ }else if( cson_value_is_number(v) ){
+ return cson_value_get_integer(v) ? 1 : 0;
+ }else if( cson_value_is_string(v) ){
+ char const * sv = cson_string_cstr(cson_value_get_string(v));
+ if(!*sv || ('0'==*sv)){
+ return 0;
+ }else{
+ return (('t'==*sv) || ('T'==*sv)
+ || (('1'<=*sv) && ('9'>=*sv)))
+ ? 1 : 0;
+ }
+ }else if( cson_value_is_bool(v) ){
+ return cson_value_get_bool(v) ? 1 : 0;
+ }else if( cson_value_is_null(v) ){
+ return 0;
+ }else{
+ return dflt;
+ }
+}
+
+/*
+** Returns the string form of a json_getenv() value, but ONLY If that
+** value is-a String. Non-strings are not converted to strings for
+** this purpose. Returned memory is owned by g.json or fossil and is
+** valid until end-of-app or the given key is replaced in fossil's
+** internals via cgi_replace_parameter() and friends or json_setenv().
+*/
+char const * json_getenv_cstr( char const * zKey ){
+ return cson_value_get_cstr( json_getenv(zKey) );
+}
+
+/*
+** An extended form of find_option() which tries to look up a combo
+** GET/POST/CLI argument.
+**
+** zKey must be the GET/POST parameter key. zCLILong must be the "long
+** form" CLI flag (NULL means to use zKey). zCLIShort may be NULL or
+** the "short form" CLI flag (if NULL, no short form is used).
+**
+** If argPos is >=0 and no other match is found,
+** json_command_arg(argPos) is also checked.
+**
+** On error (no match found) NULL is returned.
+**
+** This ONLY works for String JSON/GET/CLI values, not JSON
+** booleans and whatnot.
+*/
+char const * json_find_option_cstr2(char const * zKey,
+ char const * zCLILong,
+ char const * zCLIShort,
+ int argPos){
+ char const * rc = NULL;
+ assert(NULL != zKey);
+ if(!g.isHTTP){
+ rc = find_option(zCLILong ? zCLILong : zKey,
+ zCLIShort, 1);
+ }
+ if(!rc && fossil_has_json()){
+ rc = json_getenv_cstr(zKey);
+ }
+ if(!rc && (argPos>=0)){
+ rc = json_command_arg((unsigned char)argPos);
+ }
+ return rc;
+}
+
+char const * json_find_option_cstr(char const * zKey,
+ char const * zCLILong,
+ char const * zCLIShort){
+ return json_find_option_cstr2(zKey, zCLIShort, zCLIShort, -1);
+}
+
+/*
+** The boolean equivalent of json_find_option_cstr().
+** If the option is not found, dftl is returned.
+*/
+char json_find_option_bool(char const * zKey,
+ char const * zCLILong,
+ char const * zCLIShort,
+ char dflt ){
+ char rc = -1;
+ if(!g.isHTTP){
+ if(NULL != find_option(zCLILong ? zCLILong : zKey,
+ zCLIShort, 0)){
+ rc = 1;
+ }
+ }
+ if((-1==rc) && fossil_has_json()){
+ rc = json_getenv_bool(zKey,-1);
+ }
+ return (-1==rc) ? dflt : rc;
+}
+
+/*
+** The integer equivalent of json_find_option_cstr().
+** If the option is not found, dftl is returned.
+*/
+int json_find_option_int(char const * zKey,
+ char const * zCLILong,
+ char const * zCLIShort,
+ int dflt ){
+ enum { Magic = -947 };
+ int rc = Magic;
+ if(!g.isHTTP){
+ /* FIXME: use strtol() for better error/dflt handling. */
+ char const * opt = find_option(zCLILong ? zCLILong : zKey,
+ zCLIShort, 1);
+ if(NULL!=opt){
+ rc = atoi(opt);
+ }
+ }
+ if(Magic==rc){
+ rc = json_getenv_int(zKey,Magic);
+ }
+ return (Magic==rc) ? dflt : rc;
+}
+
+
+/*
+** Adds v to g.json.param.o using the given key. May cause any prior
+** item with that key to be destroyed (depends on current reference
+** count for that value). On success, transfers (or shares) ownership
+** of v to (or with) g.json.param.o. On error ownership of v is not
+** modified.
+*/
+int json_setenv( char const * zKey, cson_value * v ){
+ return cson_object_set( g.json.param.o, zKey, v );
+}
+
+/*
+** Guesses a RESPONSE Content-Type value based (primarily) on the
+** HTTP_ACCEPT header.
+**
+** It will try to figure out if the client can support
+** application/json or application/javascript, and will fall back to
+** text/plain if it cannot figure out anything more specific.
+**
+** Returned memory is static and immutable, but if the environment
+** changes after calling this then subsequent calls to this function
+** might return different (also static/immutable) values.
+*/
+char const * json_guess_content_type(){
+ char const * cset;
+ char doUtf8;
+ cset = PD("HTTP_ACCEPT_CHARSET",NULL);
+ doUtf8 = ((NULL == cset) || (NULL!=strstr("utf-8",cset)))
+ ? 1 : 0;
+ if( g.json.jsonp ){
+ return doUtf8
+ ? "application/javascript; charset=utf-8"
+ : "application/javascript";
+ }else{
+ /*
+ Content-type
+
+ If the browser does not sent an ACCEPT for application/json
+ then we fall back to text/plain.
+ */
+ char const * cstr;
+ cstr = PD("HTTP_ACCEPT",NULL);
+ if( NULL == cstr ){
+ return doUtf8
+ ? "application/json; charset=utf-8"
+ : "application/json";
+ }else{
+ if( strstr( cstr, "application/json" )
+ || strstr( cstr, "*/*" ) ){
+ return doUtf8
+ ? "application/json; charset=utf-8"
+ : "application/json";
+ }else{
+ return "text/plain";
+ }
+ }
+ }
+}
+
+/*
+** Sends pResponse to the output stream as the response object. This
+** function does no validation of pResponse except to assert() that it
+** is not NULL. The caller is responsible for ensuring that it meets
+** API response envelope conventions.
+**
+** In CLI mode pResponse is sent to stdout immediately. In HTTP
+** mode pResponse replaces any current CGI content but cgi_reply()
+** is not called to flush the output.
+**
+** If g.json.jsonp is not NULL then the content type is set to
+** application/javascript and the output is wrapped in a jsonp
+** wrapper.
+*/
+void json_send_response( cson_value const * pResponse ){
+ assert( NULL != pResponse );
+ if( g.isHTTP ){
+ cgi_reset_content();
+ if( g.json.jsonp ){
+ cgi_printf("%s(",g.json.jsonp);
+ }
+ cson_output( pResponse, cson_data_dest_cgi, NULL, &g.json.outOpt );
+ if( g.json.jsonp ){
+ cgi_append_content(")",1);
+ }
+ }else{/*CLI mode*/
+ if( g.json.jsonp ){
+ fprintf(stdout,"%s(",g.json.jsonp);
+ }
+ cson_output_FILE( pResponse, stdout, &g.json.outOpt );
+ if( g.json.jsonp ){
+ fwrite(")\n", 2, 1, stdout);
+ }
+ }
+}
+
+/*
+** Returns the current request's JSON authentication token, or NULL if
+** none is found. The token's memory is owned by (or shared with)
+** g.json.
+**
+** If an auth token is found in the GET/POST request data then fossil
+** is given that data for use in authentication for this
+** session. i.e. the GET/POST data overrides fossil's authentication
+** cookie value (if any) and also works with clients which do not
+** support cookies.
+**
+** Must be called once before login_check_credentials() is called or
+** we will not be able to replace fossil's internal idea of the auth
+** info in time (and future changes to that state may cause unexpected
+** results).
+**
+** The result of this call are cached for future calls.
+*/
+cson_value * json_auth_token(){
+ if( !g.json.authToken ){
+ /* Try to get an authorization token from GET parameter, POSTed
+ JSON, or fossil cookie (in that order). */
+ g.json.authToken = json_getenv(FossilJsonKeys.authToken);
+ if(g.json.authToken
+ && cson_value_is_string(g.json.authToken)
+ && !PD(login_cookie_name(),NULL)){
+ /* tell fossil to use this login info.
+
+ FIXME?: because the JSON bits don't carry around
+ login_cookie_name(), there is a potential login hijacking
+ window here. We may need to change the JSON auth token to be
+ in the form: login_cookie_name()=...
+
+ Then again, the hardened cookie value helps ensure that
+ only a proper key/value match is valid.
+ */
+ cgi_replace_parameter( login_cookie_name(), cson_value_get_cstr(g.json.authToken) );
+ }else if( g.isHTTP ){
+ /* try fossil's conventional cookie. */
+ /* Reminder: chicken/egg scenario regarding db access in CLI
+ mode because login_cookie_name() needs the db. CLI
+ mode does not use any authentication, so we don't need
+ to support it here.
+ */
+ char const * zCookie = P(login_cookie_name());
+ if( zCookie && *zCookie ){
+ /* Transfer fossil's cookie to JSON for downstream convenience... */
+ cson_value * v = cson_value_new_string(zCookie, strlen(zCookie));
+ json_gc_add( FossilJsonKeys.authToken, v );
+ g.json.authToken = v;
+ }
+ }
+ }
+ return g.json.authToken;
+}
+
+/*
+** IFF json.reqPayload.o is not NULL then this returns
+** cson_object_get(json.reqPayload.o,pKey), else it returns NULL.
+**
+** The returned value is owned by (or shared with) json.reqPayload.v.
+*/
+cson_value * json_req_payload_get(char const *pKey){
+ return g.json.reqPayload.o
+ ? cson_object_get(g.json.reqPayload.o,pKey)
+ : NULL;
+}
+
+/*
+** Initializes some JSON bits which need to be initialized relatively
+** early on. It should only be called from cgi_init() or
+** json_cmd_top() (early on in those functions).
+**
+** Initializes g.json.gc and g.json.param. This code does not (and
+** must not) rely on any of the fossil environment having been set
+** up. e.g. it must not use cgi_parameter() and friends because this
+** must be called before those data are initialized.
+*/
+void json_main_bootstrap(){
+ cson_value * v;
+ assert( (NULL == g.json.gc.v) && "cgi_json_bootstrap() was called twice!" );
+
+ /* g.json.gc is our "garbage collector" - where we put JSON values
+ which need a long lifetime but don't have a logical parent to put
+ them in.
+ */
+ v = cson_value_new_array();
+ g.json.gc.v = v;
+ g.json.gc.a = cson_value_get_array(v);
+ cson_value_add_reference(v)
+ /* Needed to allow us to include this value in other JSON
+ containers without transfering ownership to those containers.
+ All other persistent g.json.XXX.v values get appended to
+ g.json.gc.a, and therefore already have a live reference
+ for this purpose.
+ */
+ ;
+
+ /*
+ g.json.param holds the JSONized counterpart of fossil's
+ cgi_parameter_xxx() family of data. We store them as JSON, as
+ opposed to using fossil's data directly, because we can retain
+ full type information for data this way (as opposed to it always
+ being of type string).
+ */
+ v = cson_value_new_object();
+ g.json.param.v = v;
+ g.json.param.o = cson_value_get_object(v);
+ json_gc_add("$PARAMS", v);
+}
+
+/*
+** Appends a warning object to the (pending) JSON response.
+**
+** Code must be a FSL_JSON_W_xxx value from the FossilJsonCodes enum.
+**
+** A Warning object has this JSON structure:
+**
+** { "code":integer, "text":"string" }
+**
+** But the text part is optional.
+**
+** If msg is non-NULL and not empty then it is used as the "text"
+** property's value. It is copied, and need not refer to static
+** memory.
+**
+** CURRENTLY this code only allows a given warning code to be
+** added one time, and elides subsequent warnings. The intention
+** is to remove that burden from loops which produce warnings.
+**
+** FIXME: if msg is NULL then use a standard string for
+** the given code. If !*msg then elide the "text" property,
+** for consistency with how json_err() works.
+*/
+void json_warn( int code, char const * fmt, ... ){
+ cson_object * obj = NULL;
+ assert( (code>FSL_JSON_W_START)
+ && (codeindent)
+ ? (g.isHTTP ? 0 : 1)
+ : (unsigned char)indent;
+ g.json.outOpt.addNewline = g.isHTTP
+ ? 0
+ : (g.json.jsonp ? 0 : 1);
+ }
+
+ if( g.isHTTP ){
+ json_auth_token()/* will copy our auth token, if any, to fossil's
+ core, which we need before we call
+ login_check_credentials(). */;
+ login_check_credentials()/* populates g.perm */;
+ }
+ else{
+ db_find_and_open_repository(OPEN_ANY_SCHEMA,0);
+ }
+}
+
+/*
+** Returns the ndx'th item in the "command path", where index 0 is the
+** position of the "json" part of the path. Returns NULL if ndx is out
+** of bounds or there is no "json" path element.
+**
+** In CLI mode the "path" is the list of arguments (skipping argv[0]).
+** In server/CGI modes the path is taken from PATH_INFO.
+**
+** The returned bytes are owned by g.json.cmd.v and _may_ be
+** invalidated if that object is modified (depending on how it is
+** modified).
+**
+** Note that CLI options are not included in the command path. Use
+** find_option() to get those.
+**
+*/
+char const * json_command_arg(unsigned char ndx){
+ cson_array * ar = g.json.cmd.a;
+ assert((NULL!=ar) && "Internal error. Was json_mode_bootstrap() called?");
+ assert((g.argc>1) && "Internal error - we never should have gotten this far.");
+ if( g.json.cmd.offset < 0 ){
+ /* first-time setup. */
+ short i = 0;
+#define NEXT cson_string_cstr( \
+ cson_value_get_string( \
+ cson_array_get(ar,i) \
+ ))
+ char const * tok = NEXT;
+ while( tok ){
+ if( !g.isHTTP/*workaround for "abbreviated name" in CLI mode*/
+ ? (0==strcmp(g.argv[1],tok))
+ : (0==strncmp("json",tok,4))
+ ){
+ g.json.cmd.offset = i;
+ break;
+ }
+ ++i;
+ tok = NEXT;
+ }
+ }
+#undef NEXT
+ if(g.json.cmd.offset < 0){
+ return NULL;
+ }else{
+ ndx = g.json.cmd.offset + ndx;
+ return cson_string_cstr(cson_value_get_string(cson_array_get( ar, g.json.cmd.offset + ndx )));
+ }
+}
+
+/*
+** If g.json.reqPayload.o is NULL then NULL is returned, else the
+** given property is searched for in the request payload. If found it
+** is returned. The returned value is owned by (or shares ownership
+** with) g.json, and must NOT be cson_value_free()'d by the
+** caller.
+*/
+cson_value * json_payload_property( char const * key ){
+ return g.json.reqPayload.o ?
+ cson_object_get( g.json.reqPayload.o, key )
+ : NULL;
+}
+
+
+/* Returns the C-string form of json_auth_token(), or NULL
+** if json_auth_token() returns NULL.
+*/
+char const * json_auth_token_cstr(){
+ return cson_value_get_cstr( json_auth_token() );
+}
+
+/*
+** Returns the JsonPageDef with the given name, or NULL if no match is
+** found.
+**
+** head must be a pointer to an array of JsonPageDefs in which the
+** last entry has a NULL name.
+*/
+JsonPageDef const * json_handler_for_name( char const * name, JsonPageDef const * head ){
+ JsonPageDef const * pageDef = head;
+ assert( head != NULL );
+ if(name && *name) for( ; pageDef->name; ++pageDef ){
+ if( 0 == strcmp(name, pageDef->name) ){
+ return pageDef;
+ }
+ }
+ return NULL;
+}
+
+/*
+** Given a Fossil/JSON result code, this function "dumbs it down"
+** according to the current value of g.json.errorDetailParanoia. The
+** dumbed-down value is returned.
+**
+** This function assert()s that code is in the inclusive range 0 to
+** 9999.
+**
+** Note that WARNING codes (1..999) are never dumbed down.
+**
+*/
+static int json_dumbdown_rc( int code ){
+ if(!g.json.errorDetailParanoia
+ || !code
+ || ((code>=FSL_JSON_W_START) && (code= 1000) && (code <= 9999) && "Invalid Fossil/JSON code.");
+ switch( g.json.errorDetailParanoia ){
+ case 1: modulo = 10; break;
+ case 2: modulo = 100; break;
+ case 3: modulo = 1000; break;
+ default: break;
+ }
+ if( modulo ) code = code - (code % modulo);
+ return code;
+ }
+}
+
+/*
+** Convenience routine which converts a Julian time value into a Unix
+** Epoch timestamp. Requires the db, so this cannot be used before the
+** repo is opened (will trigger a fatal error in db_xxx()).
+*/
+cson_value * json_julian_to_timestamp(double j){
+ return cson_value_new_integer((cson_int_t)
+ db_int64(0,"SELECT cast(strftime('%%s',%lf) as int)",j)
+ );
+}
+
+/*
+** Returns a timestamp value.
+*/
+cson_int_t json_timestamp(){
+ return (cson_int_t)time(0);
+}
+/*
+** Returns a new JSON value (owned by the caller) representing
+** a timestamp. If timeVal is < 0 then time(0) is used to fetch
+** the time, else timeVal is used as-is
+*/
+cson_value * json_new_timestamp(cson_int_t timeVal){
+ return cson_value_new_integer((timeVal<0) ? (cson_int_t)time(0) : timeVal);
+}
+
+/*
+** Internal helper for json_create_response(). Appends the first
+** g.json.dispatchDepth elements of g.json.cmd.a, skipping the first
+** one (the "json" part), to a string and returns that string value
+** (which is owned by the caller).
+*/
+static cson_value * json_response_command_path(){
+ if(!g.json.cmd.a){
+ return NULL;
+ }else{
+ cson_value * rc = NULL;
+ Blob path = empty_blob;
+ char const * part;
+ unsigned int aLen = g.json.dispatchDepth+1; /*cson_array_length_get(g.json.cmd.a);*/
+ unsigned int i = 1;
+ for( ; i < aLen; ++i ){
+ char const * part = cson_string_cstr(cson_value_get_string(cson_array_get(g.json.cmd.a, i)));
+ if(!part){
+ fossil_warning("Iterating further than expected in %s.",
+ __FILE__);
+ break;
+ }
+ blob_appendf(&path,"%s%s", (i>1 ? "/": ""), part);
+ }
+ rc = json_new_string((blob_size(&path)>0)
+ ? blob_buffer(&path)
+ : "")
+ /* reminder; we need an empty string instead of NULL
+ in this case, to avoid what outwardly looks like
+ (but is not) an allocation error in
+ json_create_response().
+ */
+ ;
+ blob_reset(&path);
+ return rc;
+ }
+}
+
+/*
+** Returns a JSON Object representation of the global g object.
+** Returned value is owned by the caller.
+*/
+cson_value * json_g_to_json(){
+ cson_object * o = NULL;
+ cson_object * pay = NULL;
+ pay = o = cson_new_object();
+
+#define INT(OBJ,K) cson_object_set(o, #K, json_new_int(OBJ.K))
+#define CSTR(OBJ,K) cson_object_set(o, #K, OBJ.K ? json_new_string(OBJ.K) : cson_value_null())
+#define VAL(K,V) cson_object_set(o, #K, (V) ? (V) : cson_value_null())
+ VAL(capabilities, json_cap_value());
+ INT(g, argc);
+ INT(g, isConst);
+ INT(g, useAttach);
+ INT(g, configOpen);
+ INT(g, repositoryOpen);
+ INT(g, localOpen);
+ INT(g, minPrefix);
+ INT(g, fSqlTrace);
+ INT(g, fSqlStats);
+ INT(g, fSqlPrint);
+ INT(g, fQuiet);
+ INT(g, fHttpTrace);
+ INT(g, fSystemTrace);
+ INT(g, fNoSync);
+ INT(g, iErrPriority);
+ INT(g, sslNotAvailable);
+ INT(g, cgiOutput);
+ INT(g, xferPanic);
+ INT(g, fullHttpReply);
+ INT(g, xlinkClusterOnly);
+ INT(g, fTimeFormat);
+ INT(g, markPrivate);
+ INT(g, clockSkewSeen);
+ INT(g, isHTTP);
+ INT(g, urlIsFile);
+ INT(g, urlIsHttps);
+ INT(g, urlIsSsh);
+ INT(g, urlPort);
+ INT(g, urlDfltPort);
+ INT(g, dontKeepUrl);
+ INT(g, useLocalauth);
+ INT(g, noPswd);
+ INT(g, userUid);
+ INT(g, rcvid);
+ INT(g, okCsrf);
+ INT(g, thTrace);
+ INT(g, isHome);
+ INT(g, nAux);
+ INT(g, allowSymlinks);
+
+ CSTR(g, zMainDbType);
+ CSTR(g, zHome);
+ CSTR(g, zLocalRoot);
+ CSTR(g, zPath);
+ CSTR(g, zExtra);
+ CSTR(g, zBaseURL);
+ CSTR(g, zTop);
+ CSTR(g, zContentType);
+ CSTR(g, zErrMsg);
+ CSTR(g, urlName);
+ CSTR(g, urlHostname);
+ CSTR(g, urlProtocol);
+ CSTR(g, urlPath);
+ CSTR(g, urlUser);
+ CSTR(g, urlPasswd);
+ CSTR(g, urlCanonical);
+ CSTR(g, urlProxyAuth);
+ CSTR(g, urlFossil);
+ CSTR(g, zLogin);
+ CSTR(g, zSSLIdentity);
+ CSTR(g, zIpAddr);
+ CSTR(g, zNonce);
+ CSTR(g, zCsrfToken);
+
+ o = cson_new_object();
+ cson_object_set(pay, "json", cson_object_value(o) );
+ INT(g.json, isJsonMode);
+ INT(g.json, resultCode);
+ INT(g.json, errorDetailParanoia);
+ INT(g.json, dispatchDepth);
+ VAL(authToken, g.json.authToken);
+ CSTR(g.json, jsonp);
+ VAL(gc, g.json.gc.v);
+ VAL(cmd, g.json.cmd.v);
+ VAL(param, g.json.param.v);
+ VAL(POST, g.json.post.v);
+ VAL(warnings, g.json.warnings.v);
+ /*cson_output_opt outOpt;*/
+
+
+#undef INT
+#undef CSTR
+#undef VAL
+ return cson_object_value(pay);
+}
+
+
+/*
+** Creates a new Fossil/JSON response envelope skeleton. It is owned
+** by the caller, who must eventually free it using cson_value_free(),
+** or add it to a cson container to transfer ownership. Returns NULL
+** on error.
+**
+** If payload is not NULL and resultCode is 0 then it is set as the
+** "payload" property of the returned object. If resultCode is
+** non-zero and payload is not NULL then this function calls
+** cson_value_free(payload) and does not insert the payload into the
+** response. In either case, onwership of payload is transfered to (or
+** shared with, if the caller holds a reference) this function.
+**
+** pMsg is an optional message string property (resultText) of the
+** response. If resultCode is non-0 and pMsg is NULL then
+** json_err_cstr() is used to get the error string. The caller may
+** provide his own or may use an empty string to suppress the
+** resultText property.
+**
+*/
+cson_value * json_create_response( int resultCode,
+ char const * pMsg,
+ cson_value * payload){
+ cson_value * v = NULL;
+ cson_value * tmp = NULL;
+ cson_object * o = NULL;
+ int rc;
+ resultCode = json_dumbdown_rc(resultCode);
+ o = cson_new_object();
+ v = cson_object_value(o);
+ if( ! o ) return NULL;
+#define SET(K) if(!tmp) goto cleanup; \
+ rc = cson_object_set( o, K, tmp ); \
+ if(rc) do{\
+ cson_value_free(tmp); \
+ tmp = NULL; \
+ goto cleanup; \
+ }while(0)
+
+ tmp = cson_value_new_string(MANIFEST_UUID,strlen(MANIFEST_UUID));
+ SET("fossil");
+
+ tmp = json_new_timestamp(-1);
+ SET(FossilJsonKeys.timestamp);
+
+ if( 0 != resultCode ){
+ if( ! pMsg ){
+ pMsg = g.zErrMsg;
+ if(!pMsg){
+ pMsg = json_err_cstr(resultCode);
+ }
+ }
+ tmp = json_new_string(json_rc_cstr(resultCode));
+ SET(FossilJsonKeys.resultCode);
+ }
+
+ if( pMsg && *pMsg ){
+ tmp = json_new_string(pMsg);
+ SET(FossilJsonKeys.resultText);
+ }
+
+ if(g.json.cmd.commandStr){
+ tmp = json_new_string(g.json.cmd.commandStr);
+ }else{
+ tmp = json_response_command_path();
+ }
+ SET("command");
+
+ tmp = json_getenv(FossilJsonKeys.requestId);
+ if( tmp ) cson_object_set( o, FossilJsonKeys.requestId, tmp );
+
+ if(0){/* these are only intended for my own testing...*/
+ if(g.json.cmd.v){
+ tmp = g.json.cmd.v;
+ SET("$commandPath");
+ }
+ if(g.json.param.v){
+ tmp = g.json.param.v;
+ SET("$params");
+ }
+ if(0){/*Only for debuggering, add some info to the response.*/
+ tmp = cson_value_new_integer( g.json.cmd.offset );
+ cson_object_set( o, "cmd.offset", tmp );
+ cson_object_set( o, "isCGI", cson_value_new_bool( g.isHTTP ) );
+ }
+ }
+
+ if(HAS_TIMER){
+ /* This is, philosophically speaking, not quite the right place
+ for ending the timer, but this is the one function which all of
+ the JSON exit paths use (and they call it after processing,
+ just before they end).
+ */
+ double span;
+ span = END_TIMER;
+ /* i'm actually seeing sub-ms runtimes in some tests, but a time of
+ 0 is "just wrong", so we'll bump that up to 1ms.
+ */
+ cson_object_set(o,"procTimeMs", cson_value_new_integer((cson_int_t)((span>1.0)?span:1)));
+ }
+ if(g.json.warnings.v){
+ tmp = g.json.warnings.v;
+ SET("warnings");
+ }
+
+ /* Only add the payload to SUCCESS responses. Else delete it. */
+ if( NULL != payload ){
+ if( resultCode ){
+ cson_value_free(payload);
+ payload = NULL;
+ }else{
+ tmp = payload;
+ SET(FossilJsonKeys.payload);
+ }
+ }
+
+ if(json_find_option_bool("debugFossilG","json-debug-g",NULL,0)
+ &&(g.perm.Admin||g.perm.Setup)){
+ tmp = json_g_to_json();
+ SET("g");
+ }
+
+#undef SET
+ goto ok;
+ cleanup:
+ cson_value_free(v);
+ v = NULL;
+ ok:
+ return v;
+}
+
+/*
+** Outputs a JSON error response to either the cgi_xxx() family of
+** buffers (in CGI/server mode) or stdout (in CLI mode). If rc is 0
+** then g.json.resultCode is used. If that is also 0 then the "Unknown
+** Error" code is used.
+**
+** If g.isHTTP then the generated JSON error response object replaces
+** any currently buffered page output. Because the output goes via
+** the cgi_xxx() family of functions, this function inherits any
+** compression which fossil does for its output.
+**
+** If alsoOutput is true AND g.isHTTP then cgi_reply() is called to
+** flush the output (and headers). Generally only do this if you are
+** about to call exit().
+**
+** !g.isHTTP then alsoOutput is ignored and all output is sent to
+** stdout immediately.
+**
+** For generating the resultText property: if msg is not NULL then it
+** is used as-is. If it is NULL then g.zErrMsg is checked, and if that
+** is NULL then json_err_cstr(code) is used.
+*/
+void json_err( int code, char const * msg, char alsoOutput ){
+ int rc = code ? code : (g.json.resultCode
+ ? g.json.resultCode
+ : FSL_JSON_E_UNKNOWN);
+ cson_value * resp = NULL;
+ rc = json_dumbdown_rc(rc);
+ if( rc && !msg ){
+ msg = g.zErrMsg;
+ if(!msg){
+ msg = json_err_cstr(rc);
+ }
+ }
+ resp = json_create_response(rc, msg, NULL);
+ if(!resp){
+ /* about the only error case here is out-of-memory. DO NOT
+ call fossil_panic() here because that calls this function.
+ */
+ fprintf(stderr, "%s: Fatal error: could not allocate "
+ "response object.\n", fossil_nameofexe());
+ fossil_exit(1);
+ }
+ if( g.isHTTP ){
+ if(alsoOutput){
+ json_send_response(resp);
+ }else{
+ /* almost a duplicate of json_send_response() :( */
+ cgi_reset_content();
+ if( g.json.jsonp ){
+ cgi_printf("%s(",g.json.jsonp);
+ }
+ cson_output( resp, cson_data_dest_cgi, NULL, &g.json.outOpt );
+ if( g.json.jsonp ){
+ cgi_append_content(")",1);
+ }
+ }
+ }else{
+ json_send_response(resp);
+ }
+ cson_value_free(resp);
+}
+
+/*
+** Sets g.json.resultCode and g.zErrMsg, but does not report the error
+** via json_err(). Returns the code passed to it.
+**
+** code must be in the inclusive range 1000..9999.
+*/
+int json_set_err( int code, char const * fmt, ... ){
+ assert( (code>=1000) && (code<=9999) );
+ free(g.zErrMsg);
+ g.json.resultCode = code;
+ if(!fmt || !*fmt){
+ g.zErrMsg = mprintf("%s", json_err_cstr(code));
+ }else{
+ va_list vargs;
+ va_start(vargs,fmt);
+ char * msg = vmprintf(fmt, vargs);
+ va_end(vargs);
+ g.zErrMsg = msg;
+ }
+ return code;
+}
+
+/*
+** Iterates through a prepared SELECT statement and converts each row
+** to a JSON object. If pTgt is not NULL then this function will
+** append the results to pTgt and return cson_array_value(pTgt). If
+** pTgt is NULL then a new Array object is created and returned (owned
+** by the caller). Each row of pStmt is converted to an Object and
+** appended to the array. If the result set has no rows AND pTgt is
+** NULL then NULL (not an empty array) is returned.
+*/
+cson_value * json_stmt_to_array_of_obj(Stmt *pStmt,
+ cson_array * pTgt){
+ cson_array * a = pTgt;
+ char const * warnMsg = NULL;
+ cson_value * colNamesV = NULL;
+ cson_array * colNames = NULL;
+ while( (SQLITE_ROW==db_step(pStmt)) ){
+ cson_value * row = NULL;
+ if(!a){
+ a = cson_new_array();
+ assert(NULL!=a);
+ }
+ if(!colNames){
+ colNamesV = cson_sqlite3_column_names(pStmt->pStmt);
+ assert(NULL != colNamesV);
+ cson_value_add_reference(colNamesV);
+ colNames = cson_value_get_array(colNamesV);
+ assert(NULL != colNames);
+ }
+ row = cson_sqlite3_row_to_object2(pStmt->pStmt, colNames);
+ if(!row && !warnMsg){
+ warnMsg = "Could not convert at least one result row to JSON.";
+ continue;
+ }
+ if( 0 != cson_array_append(a, row) ){
+ cson_value_free(row);
+ assert( 0 && "Alloc error.");
+ if(pTgt != a) {
+ cson_free_array(a);
+ }
+ return NULL;
+ }
+ }
+ cson_value_free(colNamesV);
+ if(warnMsg){
+ json_warn( FSL_JSON_W_ROW_TO_JSON_FAILED, warnMsg );
+ }
+ return cson_array_value(a);
+}
+
+/*
+** Works just like json_stmt_to_array_of_obj(), but each row in the
+** result set is represented as an Array of values instead of an
+** Object (key/value pairs). If pTgt is NULL and the statement
+** has no results then NULL is returned, not an empty array.
+*/
+cson_value * json_stmt_to_array_of_array(Stmt *pStmt,
+ cson_array * pTgt){
+ cson_array * a = pTgt;
+ while( (SQLITE_ROW==db_step(pStmt)) ){
+ cson_value * row = NULL;
+ if(!a){
+ a = cson_new_array();
+ assert(NULL!=a);
+ }
+ row = cson_sqlite3_row_to_array(pStmt->pStmt);
+ cson_array_append(a, row);
+ }
+ return cson_array_value(a);
+}
+
+
+/*
+** Executes the given SQL and runs it through
+** json_stmt_to_array_of_obj(), returning the result of that
+** function. If resetBlob is true then blob_reset(pSql) is called
+** after preparing the query.
+**
+** pTgt has the same semantics as described for
+** json_stmt_to_array_of_obj().
+*/
+cson_value * json_sql_to_array_of_obj(Blob * pSql, cson_array * pTgt,
+ char resetBlob){
+ Stmt q = empty_Stmt;
+ cson_value * pay = NULL;
+ assert( blob_size(pSql) > 0 );
+ db_prepare(&q, "%s", blob_str(pSql));
+ if(resetBlob){
+ blob_reset(pSql);
+ }
+ pay = json_stmt_to_array_of_obj(&q, pTgt);
+ db_finalize(&q);
+ return pay;
+
+}
+
+/*
+** If the given rid has any tags associated with it, this function
+** returns a JSON Array containing the tag names, else it returns
+** NULL.
+**
+** See info_tags_of_checkin() for more details (this is simply a JSON
+** wrapper for that function).
+*/
+cson_value * json_tags_for_rid(int rid, char propagatingOnly){
+ cson_value * v = NULL;
+ char * tags = info_tags_of_checkin(rid, propagatingOnly);
+ if(tags){
+ if(*tags){
+ v = json_string_split2(tags,',',0);
+ }
+ free(tags);
+ }
+ return v;
+}
+
+/*
+ ** Returns a "new" value representing the boolean value of zVal
+ ** (false if zVal is NULL). Note that cson does not really allocate
+ ** any memory for boolean values, but they "should" (for reasons of
+ ** style and philosophy) be cleaned up like any other values (but
+ ** it's a no-op for bools).
+ */
+cson_value * json_value_to_bool(cson_value const * zVal){
+ return cson_value_get_bool(zVal)
+ ? cson_value_true()
+ : cson_value_false();
+}
+
+/*
+** Impl of /json/resultCodes
+**
+*/
+cson_value * json_page_resultCodes(){
+ cson_value * listV = cson_value_new_array();
+ cson_array * list = cson_value_get_array(listV);
+ cson_value * objV = NULL;
+ cson_object * obj = NULL;
+ cson_string * kRC;
+ cson_string * kSymbol;
+ cson_string * kNumber;
+ cson_string * kDesc;
+ int rc = cson_array_reserve( list, 35 );
+ if(rc){
+ assert( 0 && "Allocation error.");
+ exit(1);
+ }
+ kRC = cson_new_string("resultCode",10);
+ kSymbol = cson_new_string("cSymbol",7);
+ kNumber = cson_new_string("number",6);
+ kDesc = cson_new_string("description",11);
+#define C(K) objV = cson_value_new_object(); obj = cson_value_get_object(objV); \
+ cson_object_set_s(obj, kRC, json_new_string(json_rc_cstr(FSL_JSON_E_##K)) ); \
+ cson_object_set_s(obj, kSymbol, json_new_string("FSL_JSON_E_"#K) ); \
+ cson_object_set_s(obj, kNumber, cson_value_new_integer(FSL_JSON_E_##K) ); \
+ cson_object_set_s(obj, kDesc, json_new_string(json_err_cstr(FSL_JSON_E_##K))); \
+ cson_array_append( list, objV ); obj = NULL; objV = NULL
+
+ C(GENERIC);
+ C(INVALID_REQUEST);
+ C(UNKNOWN_COMMAND);
+ C(UNKNOWN);
+ C(TIMEOUT);
+ C(ASSERT);
+ C(ALLOC);
+ C(NYI);
+ C(PANIC);
+ C(MANIFEST_READ_FAILED);
+ C(FILE_OPEN_FAILED);
+
+ C(AUTH);
+ C(MISSING_AUTH);
+ C(DENIED);
+ C(WRONG_MODE);
+ C(LOGIN_FAILED);
+ C(LOGIN_FAILED_NOSEED);
+ C(LOGIN_FAILED_NONAME);
+ C(LOGIN_FAILED_NOPW);
+ C(LOGIN_FAILED_NOTFOUND);
+
+ C(USAGE);
+ C(INVALID_ARGS);
+ C(MISSING_ARGS);
+ C(AMBIGUOUS_UUID);
+ C(UNRESOLVED_UUID);
+ C(RESOURCE_ALREADY_EXISTS);
+ C(RESOURCE_NOT_FOUND);
+
+ C(DB);
+ C(STMT_PREP);
+ C(STMT_BIND);
+ C(STMT_EXEC);
+ C(DB_LOCKED);
+ C(DB_NEEDS_REBUILD);
+ C(DB_NOT_FOUND);
+ C(DB_NOT_VALID);
+#undef C
+ return listV;
+}
+
+
+/*
+** /json/version implementation.
+**
+** Returns the payload object (owned by the caller).
+*/
+cson_value * json_page_version(){
+ cson_value * jval = NULL;
+ cson_object * jobj = NULL;
+ jval = cson_value_new_object();
+ jobj = cson_value_get_object(jval);
+#define FSET(X,K) cson_object_set( jobj, K, cson_value_new_string(X,strlen(X)))
+ FSET(MANIFEST_UUID,"manifestUuid");
+ FSET(MANIFEST_VERSION,"manifestVersion");
+ FSET(MANIFEST_DATE,"manifestDate");
+ FSET(MANIFEST_YEAR,"manifestYear");
+ FSET(RELEASE_VERSION,"releaseVersion");
+#undef FSET
+ cson_object_set( jobj, "releaseVersionNumber",
+ cson_value_new_integer(RELEASE_VERSION_NUMBER) );
+ cson_object_set( jobj, "resultCodeParanoiaLevel",
+ cson_value_new_integer(g.json.errorDetailParanoia) );
+ return jval;
+}
+
+
+/*
+** Returns the current user's capabilities string as a String value.
+** Returned value is owned by the caller, and will only be NULL if
+** g.userUid is invalid or an out of memory error. Or, it turns out,
+** in CLI mode (where there is no logged-in user).
+*/
+cson_value * json_cap_value(){
+ if(g.userUid<=0){
+ return NULL;
+ }else{
+ Stmt q = empty_Stmt;
+ cson_value * val = NULL;
+ db_prepare(&q, "SELECT cap FROM user WHERE uid=%d", g.userUid);
+ if( db_step(&q)==SQLITE_ROW ){
+ char const * str = (char const *)sqlite3_column_text(q.pStmt,0);
+ if( str ){
+ val = json_new_string(str);
+ }
+ }
+ db_finalize(&q);
+ return val;
+ }
+}
+
+/*
+** Implementation for /json/cap
+**
+** Returned object contains details about the "capabilities" of the
+** current user (what he may/may not do).
+**
+** This is primarily intended for debuggering, but may have
+** a use in client code. (?)
+*/
+cson_value * json_page_cap(){
+ cson_value * payload = cson_value_new_object();
+ cson_value * sub = cson_value_new_object();
+ Stmt q;
+ cson_object * obj = cson_value_get_object(payload);
+ db_prepare(&q, "SELECT login, cap FROM user WHERE uid=%d", g.userUid);
+ if( db_step(&q)==SQLITE_ROW ){
+ /* reminder: we don't use g.zLogin because it's 0 for the guest
+ user and the HTML UI appears to currently allow the name to be
+ changed (but doing so would break other code). */
+ char const * str = (char const *)sqlite3_column_text(q.pStmt,0);
+ if( str ){
+ cson_object_set( obj, "name",
+ cson_value_new_string(str,strlen(str)) );
+ }
+ str = (char const *)sqlite3_column_text(q.pStmt,1);
+ if( str ){
+ cson_object_set( obj, "capabilities",
+ cson_value_new_string(str,strlen(str)) );
+ }
+ }
+ db_finalize(&q);
+ cson_object_set( obj, "permissionFlags", sub );
+ obj = cson_value_get_object(sub);
+
+#define ADD(X,K) cson_object_set(obj, K, cson_value_new_bool(g.perm.X))
+ ADD(Setup,"setup");
+ ADD(Admin,"admin");
+ ADD(Delete,"delete");
+ ADD(Password,"password");
+ ADD(Query,"query"); /* don't think this one is actually used */
+ ADD(Write,"checkin");
+ ADD(Read,"checkout");
+ ADD(History,"history");
+ ADD(Clone,"clone");
+ ADD(RdWiki,"readWiki");
+ ADD(NewWiki,"createWiki");
+ ADD(ApndWiki,"appendWiki");
+ ADD(WrWiki,"editWiki");
+ ADD(RdTkt,"readTicket");
+ ADD(NewTkt,"createTicket");
+ ADD(ApndTkt,"appendTicket");
+ ADD(WrTkt,"editTicket");
+ ADD(Attach,"attachFile");
+ ADD(TktFmt,"createTicketReport");
+ ADD(RdAddr,"readPrivate");
+ ADD(Zip,"zip");
+ ADD(Private,"xferPrivate");
+#undef ADD
+ return payload;
+}
+
+/*
+** Implementation of the /json/stat page/command.
+**
+*/
+cson_value * json_page_stat(){
+ i64 t, fsize;
+ int n, m;
+ int full;
+ const char *zDb;
+ enum { BufLen = 1000 };
+ char zBuf[BufLen];
+ cson_value * jv = NULL;
+ cson_object * jo = NULL;
+ cson_value * jv2 = NULL;
+ cson_object * jo2 = NULL;
+ if( !g.perm.Read ){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'o' permissions.");
+ return NULL;
+ }
+ full = json_find_option_bool("full",NULL,"f",0);
+#define SETBUF(O,K) cson_object_set(O, K, cson_value_new_string(zBuf, strlen(zBuf)));
+
+ jv = cson_value_new_object();
+ jo = cson_value_get_object(jv);
+
+ sqlite3_snprintf(BufLen, zBuf, db_get("project-name",""));
+ SETBUF(jo, "projectName");
+ /* FIXME: don't include project-description until we ensure that
+ zBuf will always be big enough. We "should" replace zBuf
+ with a blob for this purpose.
+ */
+ fsize = file_size(g.zRepositoryName);
+ cson_object_set(jo, "repositorySize", cson_value_new_integer((cson_int_t)fsize));
+
+ if(full){
+ n = db_int(0, "SELECT count(*) FROM blob");
+ m = db_int(0, "SELECT count(*) FROM delta");
+ cson_object_set(jo, "blobCount", cson_value_new_integer((cson_int_t)n));
+ cson_object_set(jo, "deltaCount", cson_value_new_integer((cson_int_t)m));
+ if( n>0 ){
+ int a, b;
+ Stmt q;
+ db_prepare(&q, "SELECT total(size), avg(size), max(size)"
+ " FROM blob WHERE size>0");
+ db_step(&q);
+ t = db_column_int64(&q, 0);
+ cson_object_set(jo, "uncompressedArtifactSize",
+ cson_value_new_integer((cson_int_t)t));
+ cson_object_set(jo, "averageArtifactSize",
+ cson_value_new_integer((cson_int_t)db_column_int(&q, 1)));
+ cson_object_set(jo, "maxArtifactSize",
+ cson_value_new_integer((cson_int_t)db_column_int(&q, 2)));
+ db_finalize(&q);
+ if( t/fsize < 5 ){
+ b = 10;
+ fsize /= 10;
+ }else{
+ b = 1;
+ }
+ a = t/fsize;
+ sqlite3_snprintf(BufLen,zBuf, "%d:%d", a, b);
+ SETBUF(jo, "compressionRatio");
+ }
+ n = db_int(0, "SELECT count(distinct mid) FROM mlink /*scan*/");
+ cson_object_set(jo, "checkinCount", cson_value_new_integer((cson_int_t)n));
+ n = db_int(0, "SELECT count(*) FROM filename /*scan*/");
+ cson_object_set(jo, "fileCount", cson_value_new_integer((cson_int_t)n));
+ n = db_int(0, "SELECT count(*) FROM tag /*scan*/"
+ " WHERE +tagname GLOB 'wiki-*'");
+ cson_object_set(jo, "wikiPageCount", cson_value_new_integer((cson_int_t)n));
+ n = db_int(0, "SELECT count(*) FROM tag /*scan*/"
+ " WHERE +tagname GLOB 'tkt-*'");
+ cson_object_set(jo, "ticketCount", cson_value_new_integer((cson_int_t)n));
+ }/*full*/
+ n = db_int(0, "SELECT julianday('now') - (SELECT min(mtime) FROM event)"
+ " + 0.99");
+ cson_object_set(jo, "ageDays", cson_value_new_integer((cson_int_t)n));
+ cson_object_set(jo, "ageYears", cson_value_new_double(n/365.24));
+ sqlite3_snprintf(BufLen, zBuf, db_get("project-code",""));
+ SETBUF(jo, "projectCode");
+ sqlite3_snprintf(BufLen, zBuf, db_get("server-code",""));
+ SETBUF(jo, "serverCode");
+ cson_object_set(jo, "compiler", cson_value_new_string(COMPILER_NAME, strlen(COMPILER_NAME)));
+
+ jv2 = cson_value_new_object();
+ jo2 = cson_value_get_object(jv2);
+ cson_object_set(jo, "sqlite", jv2);
+ sqlite3_snprintf(BufLen, zBuf, "%.19s [%.10s] (%s)",
+ SQLITE_SOURCE_ID, &SQLITE_SOURCE_ID[20], SQLITE_VERSION);
+ SETBUF(jo2, "version");
+ zDb = db_name("repository");
+ cson_object_set(jo2, "pageCount", cson_value_new_integer((cson_int_t)db_int(0, "PRAGMA %s.page_count", zDb)));
+ cson_object_set(jo2, "pageSize", cson_value_new_integer((cson_int_t)db_int(0, "PRAGMA %s.page_size", zDb)));
+ cson_object_set(jo2, "freeList", cson_value_new_integer((cson_int_t)db_int(0, "PRAGMA %s.freelist_count", zDb)));
+ sqlite3_snprintf(BufLen, zBuf, "%s", db_text(0, "PRAGMA %s.encoding", zDb));
+ SETBUF(jo2, "encoding");
+ sqlite3_snprintf(BufLen, zBuf, "%s", db_text(0, "PRAGMA %s.journal_mode", zDb));
+ cson_object_set(jo2, "journalMode", *zBuf ? cson_value_new_string(zBuf, strlen(zBuf)) : cson_value_null());
+ return jv;
+#undef SETBUF
+}
+
+
+
+
+/*
+** Creates a comma-separated list of command names
+** taken from zPages. zPages must be an array of objects
+** whose final entry MUST have a NULL name value or results
+** are undefined.
+**
+** The list is appended to pOut. The number of items (not bytes)
+** appended are returned.
+*/
+static int json_pagedefs_to_string(JsonPageDef const * zPages,
+ Blob * pOut){
+ int i = 0;
+ for( ; zPages->name; ++zPages, ++i ){
+ if(g.isHTTP && zPages->runMode < 0) continue;
+ else if(zPages->runMode > 0) continue;
+ blob_appendf(pOut, zPages->name, -1);
+ if((zPages+1)->name){
+ blob_append(pOut, ", ",2);
+ }
+ }
+ return i;
+}
+
+
+cson_value * json_page_dispatch_helper(JsonPageDef const * pages){
+ JsonPageDef const * def;
+ char const * cmd = json_command_arg(1+g.json.dispatchDepth);
+ assert( NULL != pages );
+ if( ! cmd ){
+ Blob cmdNames = empty_blob;
+ json_pagedefs_to_string(pages, &cmdNames);
+ json_set_err(FSL_JSON_E_MISSING_ARGS,
+ "No subcommand specified. Try one of (%s).",
+ blob_str(&cmdNames));
+ blob_reset(&cmdNames);
+ return NULL;
+ }
+ def = json_handler_for_name( cmd, pages );
+ if(!def){
+ g.json.resultCode = FSL_JSON_E_UNKNOWN_COMMAND;
+ return NULL;
+ }
+ else{
+ ++g.json.dispatchDepth;
+ return (*def->func)();
+ }
+}
+
+
+/*
+** Impl of /json/rebuild. Requires admin previleges.
+*/
+static cson_value * json_page_rebuild(){
+ if( !g.perm.Admin ){
+ json_set_err(FSL_JSON_E_DENIED,"Requires 'a' privileges.");
+ return NULL;
+ }else{
+ /* Reminder: the db_xxx() ops "should" fail via the fossil core
+ error handlers, which will cause a JSON error and exit(). i.e. we
+ don't handle the errors here. TODO: confirm that all these db
+ routine fail gracefully in JSON mode.
+
+ On large repos (e.g. fossil's) this operation is likely to take
+ longer than the client timeout, which will cause it to fail (but
+ it's sqlite3, so it'll fail gracefully).
+ */
+ db_close(1);
+ db_open_repository(g.zRepositoryName);
+ db_begin_transaction();
+ rebuild_db(0, 0, 0);
+ db_end_transaction(0);
+ return NULL;
+ }
+}
+
+/*
+** Impl of /json/g. Requires admin/setup rights.
+*/
+static cson_value * json_page_g(){
+ if(!g.perm.Admin || !g.perm.Setup){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'a' or 's' privileges.");
+ return NULL;
+ }
+ return json_g_to_json();
+}
+
+/* Impl in json_login.c. */
+cson_value * json_page_anon_password();
+/* Impl in json_artifact.c. */
+cson_value * json_page_artifact();
+/* Impl in json_branch.c. */
+cson_value * json_page_branch();
+/* Impl in json_diff.c. */
+cson_value * json_page_diff();
+/* Impl in json_login.c. */
+cson_value * json_page_login();
+/* Impl in json_login.c. */
+cson_value * json_page_logout();
+/* Impl in json_query.c. */
+cson_value * json_page_query();
+/* Impl in json_report.c. */
+cson_value * json_page_report();
+/* Impl in json_tag.c. */
+cson_value * json_page_tag();
+/* Impl in json_user.c. */
+cson_value * json_page_user();
+
+/*
+** Mapping of names to JSON pages/commands. Each name is a subpath of
+** /json (in CGI mode) or a subcommand of the json command in CLI mode
+*/
+static const JsonPageDef JsonPageDefs[] = {
+/* please keep alphabetically sorted (case-insensitive) for maintenance reasons. */
+{"anonymousPassword", json_page_anon_password, 1},
+{"artifact", json_page_artifact, 0},
+{"branch", json_page_branch,0},
+{"cap", json_page_cap, 0},
+{"diff", json_page_diff, 0},
+{"dir", json_page_nyi, 0},
+{"g", json_page_g, 0},
+{"HAI",json_page_version,0},
+{"login",json_page_login,1},
+{"logout",json_page_logout,1},
+{"query",json_page_query,0},
+{"rebuild",json_page_rebuild,0},
+{"report", json_page_report, 0},
+{"resultCodes", json_page_resultCodes,0},
+{"stat",json_page_stat,0},
+{"tag", json_page_tag,0},
+{"ticket", json_page_nyi,0},
+{"timeline", json_page_timeline,0},
+{"user",json_page_user,0},
+{"version",json_page_version,0},
+{"whoami",json_page_whoami,0/*FIXME: work in CLI mode*/},
+{"wiki",json_page_wiki,0},
+/* Last entry MUST have a NULL name. */
+{NULL,NULL,0}
+};
+
+
+/*
+** Mapping of /json/ticket/XXX commands/paths to callbacks.
+*/
+static const JsonPageDef JsonPageDefs_Ticket[] = {
+{"get", json_page_nyi, 0},
+{"list", json_page_nyi, 0},
+{"save", json_page_nyi, 1},
+{"create", json_page_nyi, 1},
+/* Last entry MUST have a NULL name. */
+{NULL,NULL,0}
+};
+
+/*
+** Mapping of /json/artifact/XXX commands/paths to callbacks.
+*/
+static const JsonPageDef JsonPageDefs_Artifact[] = {
+{"vinfo", json_page_nyi, 0},
+{"finfo", json_page_nyi, 0},
+/* Last entry MUST have a NULL name. */
+{NULL,NULL,0}
+};
+
+/*
+** Mapping of /json/tag/XXX commands/paths to callbacks.
+*/
+static const JsonPageDef JsonPageDefs_Tag[] = {
+{"list", json_page_nyi, 0},
+{"create", json_page_nyi, 1},
+/* Last entry MUST have a NULL name. */
+{NULL,NULL,0}
+};
+
+
+/*
+** WEBPAGE: json
+**
+** Pages under /json/... must be entered into JsonPageDefs.
+** This function dispatches them, and is the HTTP equivalent of
+** json_cmd_top().
+*/
+void json_page_top(void){
+ int rc = FSL_JSON_E_UNKNOWN_COMMAND;
+ char const * cmd;
+ cson_value * payload = NULL;
+ JsonPageDef const * pageDef = NULL;
+ BEGIN_TIMER;
+ json_mode_bootstrap();
+ cmd = json_command_arg(1);
+ if(!cmd || !*cmd){
+ goto usage;
+ }
+ /*cgi_printf("{\"cmd\":\"%s\"}\n",cmd); return;*/
+ pageDef = json_handler_for_name(cmd,&JsonPageDefs[0]);
+ if( ! pageDef ){
+ json_err( FSL_JSON_E_UNKNOWN_COMMAND, NULL, 0 );
+ return;
+ }else if( pageDef->runMode < 0 /*CLI only*/){
+ rc = FSL_JSON_E_WRONG_MODE;
+ }else{
+ rc = 0;
+ g.json.dispatchDepth = 1;
+ payload = (*pageDef->func)();
+ }
+ if( g.json.resultCode ){
+ cson_value_free(payload);
+ json_err(g.json.resultCode, NULL, 0);
+ }else{
+ cson_value * root = json_create_response(rc, NULL, payload);
+ json_send_response(root);
+ cson_value_free(root);
+ }
+
+ return;
+ usage:
+ {
+ Blob cmdNames = empty_blob;
+ blob_init(&cmdNames,
+ "No command (sub-path) specified. Try one of: ",
+ -1);
+ json_pagedefs_to_string(&JsonPageDefs[0], &cmdNames);
+ json_err(FSL_JSON_E_MISSING_ARGS,
+ blob_str(&cmdNames), 0);
+ blob_reset(&cmdNames);
+ }
+
+}
+
+/*
+** This function dispatches json commands and is the CLI equivalent of
+** json_page_top().
+**
+** COMMAND: json
+**
+** Usage: %fossil json SUBCOMMAND
+**
+** The commands include:
+**
+** branch
+** cap
+** stat
+** timeline
+** version (alias: HAI)
+** wiki
+**
+**
+** TODOs:
+**
+** tag
+** ticket
+** ...
+**
+*/
+void json_cmd_top(void){
+ char const * cmd = NULL;
+ int rc = FSL_JSON_E_UNKNOWN_COMMAND;
+ cson_value * payload = NULL;
+ JsonPageDef const * pageDef;
+ BEGIN_TIMER;
+ memset( &g.perm, 0xff, sizeof(g.perm) )
+ /* In CLI mode fossil does not use permissions
+ and they all default to false. We enable them
+ here because (A) fossil doesn't use them in local
+ mode but (B) having them set gives us one less
+ difference in the CLI/CGI/Server-mode JSON
+ handling.
+ */
+ ;
+ json_main_bootstrap();
+ json_mode_bootstrap();
+ if( 2 > cson_array_length_get(g.json.cmd.a) ){
+ goto usage;
+ }
+ db_find_and_open_repository(0, 0);
+#if 0
+ json_warn(FSL_JSON_W_ROW_TO_JSON_FAILED, "Just testing.");
+ json_warn(FSL_JSON_W_ROW_TO_JSON_FAILED, "Just testing again.");
+#endif
+ cmd = json_command_arg(1);
+ if( !cmd || !*cmd ){
+ goto usage;
+ }
+ pageDef = json_handler_for_name(cmd,&JsonPageDefs[0]);
+ if( ! pageDef ){
+ json_err( FSL_JSON_E_UNKNOWN_COMMAND, NULL, 1 );
+ return;
+ }else if( pageDef->runMode > 0 /*HTTP only*/){
+ rc = FSL_JSON_E_WRONG_MODE;
+ }else{
+ rc = 0;
+ g.json.dispatchDepth = 1;
+ payload = (*pageDef->func)();
+ }
+ if( g.json.resultCode ){
+ cson_value_free(payload);
+ json_err(g.json.resultCode, NULL, 1);
+ }else{
+ payload = json_create_response(rc, NULL, payload);
+ json_send_response(payload);
+ cson_value_free( payload );
+ if((0 != rc) && !g.isHTTP){
+ /* FIXME: we need a way of passing this error back
+ up to the routine which called this callback.
+ e.g. add g.errCode.
+ */
+ fossil_exit(1);
+ }
+ }
+ return;
+ usage:
+ {
+ Blob cmdNames = empty_blob;
+ blob_init(&cmdNames,
+ "No subcommand specified. Try one of: ", -1);
+ json_pagedefs_to_string(&JsonPageDefs[0], &cmdNames);
+ json_err(FSL_JSON_E_MISSING_ARGS,
+ blob_str(&cmdNames), 1);
+ blob_reset(&cmdNames);
+ fossil_exit(1);
+ }
+}
+
+#undef BITSET_BYTEFOR
+#undef BITSET_SET
+#undef BITSET_UNSET
+#undef BITSET_GET
+#undef BITSET_TOGGLE
ADDED src/json_artifact.c
Index: src/json_artifact.c
==================================================================
--- /dev/null
+++ src/json_artifact.c
@@ -0,0 +1,413 @@
+/*
+** Copyright (c) 2011 D. Richard Hipp
+**
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the Simplified BSD License (also
+** known as the "2-Clause License" or "FreeBSD License".)
+**
+** This program is distributed in the hope that it will be useful,
+** but without any warranty; without even the implied warranty of
+** merchantability or fitness for a particular purpose.
+**
+** Author contact information:
+** drh@hwaci.com
+** http://www.hwaci.com/drh/
+**
+*/
+#include "VERSION.h"
+#include "config.h"
+#include "json_artifact.h"
+
+#if INTERFACE
+#include "json_detail.h"
+#endif
+
+/*
+** Internal callback for /json/artifact handlers. rid refers to
+** the rid of a given type of artifact, and each callback is
+** specialized to return a JSON form of one type of artifact.
+**
+** Implementations may assert() that rid refers to requested artifact
+** type, since mismatches in the artifact types come from
+** json_page_artifact() as opposed to client data.
+*/
+typedef cson_value * (*artifact_f)( int rid );
+
+/*
+** Internal per-artifact-type dispatching helper.
+*/
+typedef struct ArtifactDispatchEntry {
+ /**
+ Artifact type name, e.g. "checkin", "ticket", "wiki".
+ */
+ char const * name;
+
+ /**
+ JSON construction callback. Creates the contents for the
+ payload.artifact property of /json/artifact responses.
+ */
+ artifact_f func;
+} ArtifactDispatchEntry;
+
+
+/*
+** Generates an artifact Object for the given rid,
+** which must refer to a Checkin.
+**
+** Returned value is NULL or an Object owned by the caller.
+*/
+cson_value * json_artifact_for_ci( int rid, char showFiles ){
+ char * zParent = NULL;
+ cson_value * v = NULL;
+ Stmt q;
+ static cson_value * eventTypeLabel = NULL;
+ if(!eventTypeLabel){
+ eventTypeLabel = json_new_string("checkin");
+ json_gc_add("$EVENT_TYPE_LABEL(commit)", eventTypeLabel);
+ }
+ zParent = db_text(0,
+ "SELECT uuid FROM plink, blob"
+ " WHERE plink.cid=%d AND blob.rid=plink.pid AND plink.isprim",
+ rid
+ );
+
+ db_prepare(&q,
+ "SELECT uuid, "
+ " cast(strftime('%%s',mtime) as int), "
+ " user, "
+ " comment,"
+ " strftime('%%s',omtime)"
+ " FROM blob, event"
+ " WHERE blob.rid=%d"
+ " AND event.objid=%d",
+ rid, rid
+ );
+ if( db_step(&q)==SQLITE_ROW ){
+ cson_object * o;
+ cson_value * tmpV = NULL;
+ const char *zUuid = db_column_text(&q, 0);
+ char * zTmp;
+ const char *zUser;
+ const char *zComment;
+ char * zEUser, * zEComment;
+ int mtime, omtime;
+ v = cson_value_new_object();
+ o = cson_value_get_object(v);
+#define SET(K,V) cson_object_set(o,(K), (V))
+ SET("type", eventTypeLabel );
+ SET("uuid",json_new_string(zUuid));
+ SET("isLeaf", cson_value_new_bool(is_a_leaf(rid)));
+ zUser = db_column_text(&q,2);
+ zEUser = db_text(0,
+ "SELECT value FROM tagxref WHERE tagid=%d AND rid=%d",
+ TAG_USER, rid);
+ if(zEUser){
+ SET("user", json_new_string(zEUser));
+ if(0!=strcmp(zEUser,zUser)){
+ SET("originUser",json_new_string(zUser));
+ }
+ free(zEUser);
+ }else{
+ SET("user",json_new_string(zUser));
+ }
+
+ zComment = db_column_text(&q,3);
+ zEComment = db_text(0,
+ "SELECT value FROM tagxref WHERE tagid=%d AND rid=%d",
+ TAG_COMMENT, rid);
+ if(zEComment){
+ SET("comment",json_new_string(zEComment));
+ if(0 != strcmp(zEComment,zComment)){
+ SET("originComment", json_new_string(zComment));
+ }
+ free(zEComment);
+ }else{
+ SET("comment",json_new_string(zComment));
+ }
+
+ mtime = db_column_int(&q,1);
+ SET("mtime",json_new_int(mtime));
+ omtime = db_column_int(&q,4);
+ if(omtime && (omtime!=mtime)){
+ SET("originTime",json_new_int(omtime));
+ }
+
+ if(zParent){
+ SET("parentUuid", json_new_string(zParent));
+ }
+
+ tmpV = json_tags_for_rid(rid,0);
+ if(tmpV){
+ SET("tags",tmpV);
+ }
+
+ if( showFiles ){
+ cson_value * fileList = json_get_changed_files(rid);
+ if(fileList){
+ SET("files",fileList);
+ }
+ }
+
+
+#undef SET
+ }
+ free(zParent);
+ db_finalize(&q);
+ return v;
+}
+
+/*
+** Very incomplete/incorrect impl of /json/artifact/TICKET_ID.
+*/
+cson_value * json_artifact_ticket( int rid ){
+ cson_object * pay = NULL;
+ Manifest *pTktChng = NULL;
+ static cson_value * eventTypeLabel = NULL;
+ if(! g.perm.RdTkt ){
+ g.json.resultCode = FSL_JSON_E_DENIED;
+ return NULL;
+ }
+ if(!eventTypeLabel){
+ eventTypeLabel = json_new_string("ticket");
+ json_gc_add("$EVENT_TYPE_LABEL(ticket)", eventTypeLabel);
+ }
+
+ pTktChng = manifest_get(rid, CFTYPE_TICKET);
+ if( pTktChng==0 ){
+ g.json.resultCode = FSL_JSON_E_MANIFEST_READ_FAILED;
+ return NULL;
+ }
+ pay = cson_new_object();
+ cson_object_set(pay, "eventType", eventTypeLabel );
+ cson_object_set(pay, "uuid", json_new_string(pTktChng->zTicketUuid));
+ cson_object_set(pay, "user", json_new_string(pTktChng->zUser));
+ cson_object_set(pay, "timestamp", json_julian_to_timestamp(pTktChng->rDate));
+ manifest_destroy(pTktChng);
+ return cson_object_value(pay);
+}
+
+/*
+** Sub-impl of /json/artifact for checkins.
+*/
+static cson_value * json_artifact_ci( int rid ){
+ if(! g.perm.Read ){
+ g.json.resultCode = FSL_JSON_E_DENIED;
+ return NULL;
+ }else{
+ return json_artifact_for_ci(rid, 1);
+ }
+}
+
+/*
+** Internal mapping of /json/artifact/FOO commands/callbacks.
+*/
+static ArtifactDispatchEntry ArtifactDispatchList[] = {
+{"checkin", json_artifact_ci},
+{"file", json_artifact_file},
+{"tag", NULL},
+{"ticket", json_artifact_ticket},
+{"wiki", json_artifact_wiki},
+/* Final entry MUST have a NULL name. */
+{NULL,NULL}
+};
+
+/*
+** Internal helper which returns true (non-0) if the includeContent
+** (HTTP) or -content|-c flags (CLI) are set.
+*/
+static char json_artifact_include_content_flag(){
+ return json_find_option_bool("includeContent","content","c",0);
+}
+
+cson_value * json_artifact_wiki(int rid){
+ if( ! g.perm.RdWiki ){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'j' privileges.");
+ return NULL;
+ }else{
+ return json_get_wiki_page_by_rid(rid, 0);
+ }
+}
+
+cson_value * json_artifact_file(int rid){
+ cson_object * pay = NULL;
+ const char *zMime;
+ Blob content = empty_blob;
+ Stmt q = empty_Stmt;
+ cson_array * checkin_arr = NULL;
+#if 0
+ /*see next #if block below*/
+ cson_string * tagKey = NULL;
+ cson_value * checkinV = NULL;
+ cson_object * checkin = NULL;
+#endif
+
+ if( ! g.perm.Read ){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'o' privileges.");
+ return NULL;
+ }
+
+ pay = cson_new_object();
+
+ content_get(rid, &content);
+ cson_object_set(pay, "contentLength",
+ json_new_int( blob_size(&content) )
+ /* achtung: overflow potential on 32-bit builds! */);
+ zMime = mimetype_from_content(&content);
+
+ cson_object_set(pay, "contentType",
+ json_new_string(zMime ? zMime : "text/plain"));
+ if( json_artifact_include_content_flag() && !zMime ){
+ cson_object_set(pay, "content",
+ cson_value_new_string(blob_str(&content),
+ (unsigned int)blob_size(&content)));
+ }
+ blob_reset(&content);
+
+ db_prepare(&q,
+ "SELECT filename.name AS name, "
+ " cast(strftime('%%s',event.mtime) as int) AS mtime,"
+ " coalesce(event.ecomment,event.comment) as comment,"
+ " coalesce(event.euser,event.user) as user,"
+ " b.uuid as uuid, mlink.mperm as mperm,"/* WTF is mperm?*/
+ " coalesce((SELECT value FROM tagxref"
+ " WHERE tagid=%d AND tagtype>0 AND rid=mlink.mid),'trunk') as branch"
+ " FROM mlink, filename, event, blob a, blob b"
+ " WHERE filename.fnid=mlink.fnid"
+ " AND event.objid=mlink.mid"
+ " AND a.rid=mlink.fid"
+ " AND b.rid=mlink.mid"
+ " AND mlink.fid=%d"
+ " ORDER BY filename.name, event.mtime",
+ TAG_BRANCH, rid
+ );
+ checkin_arr = cson_new_array();
+ cson_object_set(pay, "checkins", cson_array_value(checkin_arr));
+#if 0
+ /* Damn: json_tags_for_rid() only works for commits.
+
+ FIXME: extend json_tags_for_rid() to accept file rids and then
+ implement this loop to add the tags to each object.
+ */
+
+ while( SQLITE_ROW == db_step(&q) ){
+ checkinV = cson_sqlite3_row_to_object( q.pStmt );
+ if(!checkinV){
+ continue;
+ }
+ if(!tagKey) {
+ tagKey = cson_new_string("tags",4);
+ json_gc_add("artifact/file/tags", cson_string_value(tagKey))
+ /*avoids a potential lifetime issue*/;
+ }
+ checkin = cson_value_get_object(checkinV);
+ cson_object_set_s(checkin, tagKey, json_tags_for_rid(rid,0));
+ cson_array_append( checkin_arr, checkinV );
+ }
+#else
+ json_stmt_to_array_of_obj( &q, checkin_arr );
+#endif
+ db_finalize(&q);
+ return cson_object_value(pay);
+}
+
+/*
+** Impl of /json/artifact. This basically just determines the type of
+** an artifact and forwards the real work to another function.
+*/
+cson_value * json_page_artifact(){
+ cson_object * pay = NULL;
+ char const * zName = NULL;
+ char const * zType = NULL;
+ char const * zUuid = NULL;
+ cson_value * entry = NULL;
+ Blob uuid = empty_blob;
+ int rc;
+ int rid = 0;
+ ArtifactDispatchEntry const * dispatcher = &ArtifactDispatchList[0];
+ zName = json_find_option_cstr2("uuid", NULL, NULL, 2);
+ if(!zName || !*zName) {
+ json_set_err(FSL_JSON_E_MISSING_ARGS,
+ "Missing 'uuid' argument.");
+ return NULL;
+ }
+
+ if( validate16(zName, strlen(zName)) ){
+ if( db_exists("SELECT 1 FROM ticket WHERE tkt_uuid GLOB '%q*'", zName) ){
+ zType = "ticket";
+ goto handle_entry;
+ }
+ if( db_exists("SELECT 1 FROM tag WHERE tagname GLOB 'event-%q*'", zName) ){
+ zType = "tag";
+ goto handle_entry;
+ }
+ }
+ blob_set(&uuid,zName);
+ rc = name_to_uuid(&uuid,-1,"*");
+ if(1==rc){
+ g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND;
+ goto error;
+ }else if(2==rc){
+ g.json.resultCode = FSL_JSON_E_AMBIGUOUS_UUID;
+ goto error;
+ }
+ zUuid = blob_str(&uuid);
+ rid = db_int(0, "SELECT rid FROM blob WHERE uuid='%s'", zUuid);
+ if(0==rid){
+ g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND;
+ goto error;
+ }
+
+ if( db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid)
+ || db_exists("SELECT 1 FROM plink WHERE cid=%d", rid)
+ || db_exists("SELECT 1 FROM plink WHERE pid=%d", rid)){
+ zType = "checkin";
+ goto handle_entry;
+ }else if( db_exists("SELECT 1 FROM tagxref JOIN tag USING(tagid)"
+ " WHERE rid=%d AND tagname LIKE 'wiki-%%'", rid) ){
+ zType = "wiki";
+ goto handle_entry;
+ }else if( db_exists("SELECT 1 FROM tagxref JOIN tag USING(tagid)"
+ " WHERE rid=%d AND tagname LIKE 'tkt-%%'", rid) ){
+ zType = "ticket";
+ goto handle_entry;
+ }else if ( db_exists("SELECT 1 FROM mlink WHERE fid = %d", rid) ){
+ zType = "file";
+ goto handle_entry;
+ }else{
+ g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND;
+ goto error;
+ }
+
+ error:
+ assert( 0 != g.json.resultCode );
+ goto veryend;
+
+ handle_entry:
+ assert( (NULL != zType) && "Internal dispatching error." );
+ for( ; dispatcher->name; ++dispatcher ){
+ if(0!=strcmp(dispatcher->name, zType)){
+ continue;
+ }else{
+ entry = (*dispatcher->func)(rid);
+ break;
+ }
+ }
+ if(!g.json.resultCode){
+ assert( NULL != entry );
+ assert( NULL != zType );
+ pay = cson_new_object();
+ cson_object_set( pay, "type", json_new_string(zType) );
+ /*cson_object_set( pay, "uuid", json_new_string(zUuid) );*/
+ cson_object_set( pay, "name", json_new_string(zName ? zName : zUuid) );
+ cson_object_set( pay, "rid", cson_value_new_integer(rid) );
+ if(entry){
+ cson_object_set(pay, "artifact", entry);
+ }
+ }
+ veryend:
+ blob_reset(&uuid);
+ return cson_object_value(pay);
+}
+
ADDED src/json_branch.c
Index: src/json_branch.c
==================================================================
--- /dev/null
+++ src/json_branch.c
@@ -0,0 +1,389 @@
+/*
+** Copyright (c) 2011 D. Richard Hipp
+**
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the Simplified BSD License (also
+** known as the "2-Clause License" or "FreeBSD License".)
+**
+** This program is distributed in the hope that it will be useful,
+** but without any warranty; without even the implied warranty of
+** merchantability or fitness for a particular purpose.
+**
+** Author contact information:
+** drh@hwaci.com
+** http://www.hwaci.com/drh/
+**
+*/
+#include "VERSION.h"
+#include "config.h"
+#include "json_branch.h"
+
+#if INTERFACE
+#include "json_detail.h"
+#endif
+
+
+static cson_value * json_branch_list();
+static cson_value * json_branch_create();
+/*
+** Mapping of /json/branch/XXX commands/paths to callbacks.
+*/
+static const JsonPageDef JsonPageDefs_Branch[] = {
+{"create", json_branch_create, 0},
+{"list", json_branch_list, 0},
+{"new", json_branch_create, -1/* for compat with non-JSON branch command.*/},
+/* Last entry MUST have a NULL name. */
+{NULL,NULL,0}
+};
+
+/*
+** Implements the /json/branch family of pages/commands. Far from
+** complete.
+**
+*/
+cson_value * json_page_branch(){
+ return json_page_dispatch_helper(&JsonPageDefs_Branch[0]);
+}
+
+/*
+** Impl for /json/branch/list
+**
+**
+** CLI mode options:
+**
+** --range X | -r X, where X is one of (open,closed,all)
+** (only the first letter is significant, default=open).
+** -a (same as --range a)
+** -c (same as --range c)
+**
+** HTTP mode options:
+**
+** "range" GET/POST.payload parameter. FIXME: currently we also use
+** POST, but really want to restrict this to POST.payload.
+*/
+static cson_value * json_branch_list(){
+ cson_value * payV;
+ cson_object * pay;
+ cson_value * listV;
+ cson_array * list;
+ char const * range = NULL;
+ int which = 0;
+ char * sawConversionError = NULL;
+ Stmt q;
+ if( !g.perm.Read ){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'o' permissions.");
+ return NULL;
+ }
+ payV = cson_value_new_object();
+ pay = cson_value_get_object(payV);
+ listV = cson_value_new_array();
+ list = cson_value_get_array(listV);
+ if(fossil_has_json()){
+ range = json_getenv_cstr("range");
+ }
+
+ range = json_find_option_cstr("range",NULL,"r");
+ if((!range||!*range) && !g.isHTTP){
+ range = find_option("all","a",0);
+ if(range && *range){
+ range = "a";
+ }else{
+ range = find_option("closed","c",0);
+ if(range&&*range){
+ range = "c";
+ }
+ }
+ }
+
+ if(!range || !*range){
+ range = "o";
+ }
+ /* Normalize range values... */
+ switch(*range){
+ case 'c':
+ range = "closed";
+ which = -1;
+ break;
+ case 'a':
+ range = "all";
+ which = 1;
+ break;
+ default:
+ range = "open";
+ which = 0;
+ break;
+ };
+ cson_object_set(pay,"range",json_new_string(range));
+
+ if( g.localOpen ){ /* add "current" property (branch name). */
+ int vid = db_lget_int("checkout", 0);
+ char const * zCurrent = vid
+ ? db_text(0, "SELECT value FROM tagxref"
+ " WHERE rid=%d AND tagid=%d",
+ vid, TAG_BRANCH)
+ : 0;
+ if(zCurrent){
+ cson_object_set(pay,"current",json_new_string(zCurrent));
+ }
+ }
+
+
+ branch_prepare_list_query(&q, which);
+ cson_object_set(pay,"branches",listV);
+ while((SQLITE_ROW==db_step(&q))){
+ cson_value * v = cson_sqlite3_column_to_value(q.pStmt,0);
+ if(v){
+ cson_array_append(list,v);
+ }else if(!sawConversionError){
+ sawConversionError = mprintf("Column-to-json failed @ %s:%d",
+ __FILE__,__LINE__);
+ }
+ }
+ if( sawConversionError ){
+ json_warn(FSL_JSON_W_COL_TO_JSON_FAILED,sawConversionError);
+ free(sawConversionError);
+ }
+ return payV;
+}
+
+/*
+** Parameters for the create-branch operation.
+*/
+typedef struct BranchCreateOptions{
+ char const * zName;
+ char const * zBasis;
+ char const * zColor;
+ char isPrivate;
+ /**
+ Might be set to an error string by
+ json_branch_new().
+ */
+ char const * rcErrMsg;
+} BranchCreateOptions;
+
+/*
+** Tries to create a new branch based on the options set in zOpt. If
+** an error is encountered, zOpt->rcErrMsg _might_ be set to a
+** descriptive string and one of the FossilJsonCodes values will be
+** returned. Or fossil_fatal() (or similar) might be called, exiting
+** the app.
+**
+** On success 0 is returned and if zNewRid is not NULL then the rid of
+** the new branch is assigned to it.
+**
+** If zOpt->isPrivate is 0 but the parent branch is private,
+** zOpt->isPrivate will be set to a non-zero value and the new branch
+** will be private.
+*/
+static int json_branch_new(BranchCreateOptions * zOpt,
+ int *zNewRid){
+ /* Mostly copied from branch.c:branch_new(), but refactored a small
+ bit to not produce output or interact with the user. The
+ down-side to that is that we dropped the gpg-signing. It was
+ either that or abort the creation if we couldn't sign. We can't
+ sign over HTTP mode, anyway.
+ */
+ char const * zBranch = zOpt->zName;
+ char const * zBasis = zOpt->zBasis;
+ char const * zColor = zOpt->zColor;
+ int rootid; /* RID of the root check-in - what we branch off of */
+ int brid; /* RID of the branch check-in */
+ int i; /* Loop counter */
+ char *zUuid; /* Artifact ID of origin */
+ Stmt q; /* Generic query */
+ char *zDate; /* Date that branch was created */
+ char *zComment; /* Check-in comment for the new branch */
+ Blob branch; /* manifest for the new branch */
+ Manifest *pParent; /* Parsed parent manifest */
+ Blob mcksum; /* Self-checksum on the manifest */
+
+ /* fossil branch new name */
+ if( zBranch==0 || zBranch[0]==0 ){
+ zOpt->rcErrMsg = "Branch name may not be null/empty.";
+ return FSL_JSON_E_INVALID_ARGS;
+ }
+ if( db_exists(
+ "SELECT 1 FROM tagxref"
+ " WHERE tagtype>0"
+ " AND tagid=(SELECT tagid FROM tag WHERE tagname='sym-%s')",
+ zBranch)!=0 ){
+ zOpt->rcErrMsg = "Branch already exists.";
+ return FSL_JSON_E_RESOURCE_ALREADY_EXISTS;
+ }
+
+ db_begin_transaction();
+ rootid = name_to_typed_rid(zBasis, "ci");
+ if( rootid==0 ){
+ zOpt->rcErrMsg = "Basis branch not found.";
+ return FSL_JSON_E_RESOURCE_NOT_FOUND;
+ }
+
+ pParent = manifest_get(rootid, CFTYPE_MANIFEST);
+ if( pParent==0 ){
+ zOpt->rcErrMsg = "Could not read parent manifest.";
+ return FSL_JSON_E_UNKNOWN;
+ }
+
+ /* Create a manifest for the new branch */
+ blob_zero(&branch);
+ if( pParent->zBaseline ){
+ blob_appendf(&branch, "B %s\n", pParent->zBaseline);
+ }
+ zComment = mprintf("Create new branch named \"%s\" "
+ "from \"%s\".", zBranch, zBasis);
+ blob_appendf(&branch, "C %F\n", zComment);
+ free(zComment);
+ zDate = date_in_standard_format("now");
+ blob_appendf(&branch, "D %s\n", zDate);
+ free(zDate);
+
+ /* Copy all of the content from the parent into the branch */
+ for(i=0; inFile; ++i){
+ blob_appendf(&branch, "F %F", pParent->aFile[i].zName);
+ if( pParent->aFile[i].zUuid ){
+ blob_appendf(&branch, " %s", pParent->aFile[i].zUuid);
+ if( pParent->aFile[i].zPerm && pParent->aFile[i].zPerm[0] ){
+ blob_appendf(&branch, " %s", pParent->aFile[i].zPerm);
+ }
+ }
+ blob_append(&branch, "\n", 1);
+ }
+ zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rootid);
+ blob_appendf(&branch, "P %s\n", zUuid);
+ free(zUuid);
+ if( pParent->zRepoCksum ){
+ blob_appendf(&branch, "R %s\n", pParent->zRepoCksum);
+ }
+ manifest_destroy(pParent);
+
+ /* Add the symbolic branch name and the "branch" tag to identify
+ ** this as a new branch */
+ if( content_is_private(rootid) ) zOpt->isPrivate = 1;
+ if( zOpt->isPrivate && zColor==0 ) zColor = "#fec084";
+ if( zColor!=0 ){
+ blob_appendf(&branch, "T *bgcolor * %F\n", zColor);
+ }
+ blob_appendf(&branch, "T *branch * %F\n", zBranch);
+ blob_appendf(&branch, "T *sym-%F *\n", zBranch);
+ if( zOpt->isPrivate ){
+ blob_appendf(&branch, "T +private *\n");
+ }
+
+ /* Cancel all other symbolic tags */
+ db_prepare(&q,
+ "SELECT tagname FROM tagxref, tag"
+ " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid"
+ " AND tagtype>0 AND tagname GLOB 'sym-*'"
+ " ORDER BY tagname",
+ rootid);
+ while( db_step(&q)==SQLITE_ROW ){
+ const char *zTag = db_column_text(&q, 0);
+ blob_appendf(&branch, "T -%F *\n", zTag);
+ }
+ db_finalize(&q);
+
+ blob_appendf(&branch, "U %F\n", g.zLogin);
+ md5sum_blob(&branch, &mcksum);
+ blob_appendf(&branch, "Z %b\n", &mcksum);
+
+ brid = content_put(&branch);
+ if( brid==0 ){
+ fossil_panic("Problem committing manifest: %s", g.zErrMsg);
+ }
+ db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", brid);
+ if( manifest_crosslink(brid, &branch)==0 ){
+ fossil_panic("unable to install new manifest");
+ }
+ assert( blob_is_reset(&branch) );
+ content_deltify(rootid, brid, 0);
+ if( zNewRid ){
+ *zNewRid = brid;
+ }
+
+ /* Commit */
+ db_end_transaction(0);
+
+#if 0 /* Do an autosync push, if requested */
+ /* arugable for JSON mode? */
+ if( !g.isHTTP && !isPrivate ) autosync(AUTOSYNC_PUSH);
+#endif
+ return 0;
+}
+
+
+/*
+** Impl of /json/branch/create.
+*/
+static cson_value * json_branch_create(){
+ cson_value * payV = NULL;
+ cson_object * pay = NULL;
+ int rc = 0;
+ BranchCreateOptions opt;
+ char * zUuid = NULL;
+ int rid = 0;
+ if( !g.perm.Write ){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'i' permissions.");
+ return NULL;
+ }
+ memset(&opt,0,sizeof(BranchCreateOptions));
+ if(fossil_has_json()){
+ opt.zName = json_getenv_cstr("name");
+ }
+
+ if(!opt.zName){
+ opt.zName = json_command_arg(g.json.dispatchDepth+1);
+ }
+
+ if(!opt.zName){
+ json_set_err(FSL_JSON_E_MISSING_ARGS, "'name' parameter was not specified." );
+ return NULL;
+ }
+
+ opt.zColor = json_find_option_cstr("bgColor","bgcolor",NULL);
+ opt.zBasis = json_find_option_cstr("basis",NULL,NULL);
+ if(!opt.zBasis && !g.isHTTP){
+ opt.zBasis = json_command_arg(g.json.dispatchDepth+2);
+ }
+ if(!opt.zBasis){
+ opt.zBasis = "trunk";
+ }
+ opt.isPrivate = json_find_option_bool("private",NULL,NULL,-1);
+ if(-1==opt.isPrivate){
+ if(!g.isHTTP){
+ opt.isPrivate = (NULL != find_option("private","",0));
+ }else{
+ opt.isPrivate = 0;
+ }
+ }
+
+ rc = json_branch_new( &opt, &rid );
+ if(rc){
+ json_set_err(rc, opt.rcErrMsg);
+ goto error;
+ }
+ assert(0 != rid);
+ payV = cson_value_new_object();
+ pay = cson_value_get_object(payV);
+
+ cson_object_set(pay,"name",json_new_string(opt.zName));
+ cson_object_set(pay,"basis",json_new_string(opt.zBasis));
+ cson_object_set(pay,"rid",json_new_int(rid));
+ zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
+ cson_object_set(pay,"uuid", json_new_string(zUuid));
+ cson_object_set(pay, "isPrivate", cson_value_new_bool(opt.isPrivate));
+ free(zUuid);
+ if(opt.zColor){
+ cson_object_set(pay,"bgColor",json_new_string(opt.zColor));
+ }
+
+ goto ok;
+ error:
+ assert( 0 != g.json.resultCode );
+ cson_value_free(payV);
+ payV = NULL;
+ ok:
+ return payV;
+}
+
ADDED src/json_detail.h
Index: src/json_detail.h
==================================================================
--- /dev/null
+++ src/json_detail.h
@@ -0,0 +1,252 @@
+#if !defined(FOSSIL_JSON_DETAIL_H_INCLUDED)
+#define FOSSIL_JSON_DETAIL_H_INCLUDED
+/*
+** Copyright (c) 2011 D. Richard Hipp
+**
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the Simplified BSD License (also
+** known as the "2-Clause License" or "FreeBSD License".)
+**
+** This program is distributed in the hope that it will be useful,
+** but without any warranty; without even the implied warranty of
+** merchantability or fitness for a particular purpose.
+**
+** Author contact information:
+** drh@hwaci.com
+** http://www.hwaci.com/drh/
+**
+*/
+
+#include "cson_amalgamation.h"
+/*
+** Impl details for the JSON API which need to be shared
+** across multiple C files.
+*/
+
+/*
+** The "official" list of Fossil/JSON error codes. Their values might
+** very well change during initial development but after their first
+** public release they must stay stable.
+**
+** Values must be in the range 1000..9999 for error codes and 1..999
+** for warning codes.
+**
+** Numbers evenly dividable by 100 are "categories", and error codes
+** for a given category have their high bits set to the category
+** value.
+**
+*/
+enum FossilJsonCodes {
+FSL_JSON_W_START = 0,
+FSL_JSON_W_UNKNOWN /*+1*/,
+FSL_JSON_W_ROW_TO_JSON_FAILED /*+2*/,
+FSL_JSON_W_COL_TO_JSON_FAILED /*+3*/,
+FSL_JSON_W_STRING_TO_ARRAY_FAILED /*+4*/,
+FSL_JSON_W_TAG_NOT_FOUND /*+5*/,
+
+FSL_JSON_W_END = 1000,
+FSL_JSON_E_GENERIC = 1000,
+FSL_JSON_E_GENERIC_SUB1 = FSL_JSON_E_GENERIC + 100,
+FSL_JSON_E_INVALID_REQUEST /*+1*/,
+FSL_JSON_E_UNKNOWN_COMMAND /*+2*/,
+FSL_JSON_E_UNKNOWN /*+3*/,
+/*REUSE: +4*/
+FSL_JSON_E_TIMEOUT /*+5*/,
+FSL_JSON_E_ASSERT /*+6*/,
+FSL_JSON_E_ALLOC /*+7*/,
+FSL_JSON_E_NYI /*+8*/,
+FSL_JSON_E_PANIC /*+9*/,
+FSL_JSON_E_MANIFEST_READ_FAILED /*+10*/,
+FSL_JSON_E_FILE_OPEN_FAILED /*+11*/,
+
+FSL_JSON_E_AUTH = 2000,
+FSL_JSON_E_MISSING_AUTH /*+1*/,
+FSL_JSON_E_DENIED /*+2*/,
+FSL_JSON_E_WRONG_MODE /*+3*/,
+
+FSL_JSON_E_LOGIN_FAILED = FSL_JSON_E_AUTH +100,
+FSL_JSON_E_LOGIN_FAILED_NOSEED /*+1*/,
+FSL_JSON_E_LOGIN_FAILED_NONAME /*+2*/,
+FSL_JSON_E_LOGIN_FAILED_NOPW /*+3*/,
+FSL_JSON_E_LOGIN_FAILED_NOTFOUND /*+4*/,
+
+FSL_JSON_E_USAGE = 3000,
+FSL_JSON_E_INVALID_ARGS /*+1*/,
+FSL_JSON_E_MISSING_ARGS /*+2*/,
+FSL_JSON_E_AMBIGUOUS_UUID /*+3*/,
+FSL_JSON_E_UNRESOLVED_UUID /*+4*/,
+FSL_JSON_E_RESOURCE_ALREADY_EXISTS /*+5*/,
+FSL_JSON_E_RESOURCE_NOT_FOUND /*+6*/,
+
+FSL_JSON_E_DB = 4000,
+FSL_JSON_E_STMT_PREP /*+1*/,
+FSL_JSON_E_STMT_BIND /*+2*/,
+FSL_JSON_E_STMT_EXEC /*+3*/,
+FSL_JSON_E_DB_LOCKED /*+4*/,
+
+FSL_JSON_E_DB_NEEDS_REBUILD = FSL_JSON_E_DB + 101,
+FSL_JSON_E_DB_NOT_FOUND = FSL_JSON_E_DB + 102,
+FSL_JSON_E_DB_NOT_VALID = FSL_JSON_E_DB + 103
+
+};
+
+
+/*
+** Signature for JSON page/command callbacks. Each callback is
+** responsible for handling one JSON request/command and/or
+** dispatching to sub-commands.
+**
+** By the time the callback is called, json_page_top() (HTTP mode) or
+** json_cmd_top() (CLI mode) will have set up the JSON-related
+** environment. Implementations may generate a "result payload" of any
+** JSON type by returning its value from this function (ownership is
+** tranferred to the caller). On error they should set
+** g.json.resultCode to one of the FossilJsonCodes values and return
+** either their payload object or NULL. Note that NULL is a legal
+** success value - it simply means the response will contain no
+** payload. If g.json.resultCode is non-zero when this function
+** returns then the top-level dispatcher will destroy any payload
+** returned by this function and will output a JSON error response
+** instead.
+**
+** All of the setup/response code is handled by the top dispatcher
+** functions and the callbacks concern themselves only with:
+**
+** a) Permissions checking (inspecting g.perm).
+** b) generating a response payload (if applicable)
+** c) Setting g.json's error state (if applicable). See json_set_err().
+**
+** It is imperitive that NO callback functions EVER output ANYTHING to
+** stdout, as that will effectively corrupt any JSON output, and
+** almost certainly will corrupt any HTTP response headers. Output
+** sent to stderr ends up in my apache log, so that might be useful
+** for debuggering in some cases, but no such code should be left
+** enabled for non-debuggering builds.
+*/
+typedef cson_value * (*fossil_json_f)();
+
+/*
+** Holds name-to-function mappings for JSON page/command dispatching.
+**
+** Internally we model page dispatching lists as arrays of these
+** objects, where the final entry in the array has a NULL name value
+** to act as the end-of-list sentinel.
+**
+*/
+typedef struct JsonPageDef{
+ /*
+ ** The commmand/page's name (path, not including leading /json/).
+ **
+ ** Reminder to self: we cannot use sub-paths with commands this way
+ ** without additional string-splitting downstream. e.g. foo/bar.
+ ** Alternately, we can create different JsonPageDef arrays for each
+ ** subset.
+ */
+ char const * name;
+ /*
+ ** Returns a payload object for the response. If it returns a
+ ** non-NULL value, the caller owns it. To trigger an error this
+ ** function should set g.json.resultCode to a value from the
+ ** FossilJsonCodes enum. If it sets an error value and returns
+ ** a payload, the payload will be destroyed (not sent with the
+ ** response).
+ */
+ fossil_json_f func;
+ /*
+ ** Which mode(s) of execution does func() support:
+ **
+ ** <0 = CLI only, >0 = HTTP only, 0==both
+ */
+ char runMode;
+} JsonPageDef;
+
+/*
+** Holds common keys used for various JSON API properties.
+*/
+typedef struct FossilJsonKeys_{
+ /** maintainers: please keep alpha sorted (case-insensitive) */
+ char const * anonymousSeed;
+ char const * authToken;
+ char const * commandPath;
+ char const * mtime;
+ char const * payload;
+ char const * requestId;
+ char const * resultCode;
+ char const * resultText;
+ char const * timestamp;
+} FossilJsonKeys_;
+const FossilJsonKeys_ FossilJsonKeys;
+
+/*
+** A page/command dispatch helper for fossil_json_f() implementations.
+** pages must be an array of JsonPageDef commands which we can
+** dispatch. The final item in the array MUST have a NULL name
+** element.
+**
+** This function takes the command specified in
+** json_comand_arg(1+g.json.dispatchDepth) and searches pages for a
+** matching name. If found then that page's func() is called to fetch
+** the payload, which is returned to the caller.
+**
+** On error, g.json.resultCode is set to one of the FossilJsonCodes
+** values and NULL is returned. If non-NULL is returned, ownership is
+** transfered to the caller (but the g.json error state might still be
+** set in that case, so the caller must check that or pass it on up
+** the dispatch chain).
+*/
+cson_value * json_page_dispatch_helper(JsonPageDef const * pages);
+
+/*
+** Implements the /json/wiki family of pages/commands.
+**
+*/
+cson_value * json_page_wiki();
+
+/*
+** Implements /json/timeline/wiki and /json/wiki/timeline.
+*/
+cson_value * json_timeline_wiki();
+
+/*
+** Implements /json/timeline family of functions.
+*/
+cson_value * json_page_timeline();
+
+/*
+** Convenience wrapper around cson_value_new_string().
+** Returns NULL if str is NULL or on allocation error.
+*/
+cson_value * json_new_string( char const * str );
+
+/*
+** Similar to json_new_string(), but takes a printf()-style format
+** specifiers. Supports the printf extensions supported by fossil's
+** mprintf(). Returns NULL if str is NULL or on allocation error.
+**
+** Maintenance note: json_new_string() is NOT variadic because by the
+** time the variadic form was introduced we already had use cases
+** which segfaulted via json_new_string() because they contain printf
+** markup (e.g. wiki content). Been there, debugged that.
+*/
+cson_value * json_new_string_f( char const * fmt, ... );
+
+/*
+** Returns true if fossil is running in JSON mode and we are either
+** running in HTTP mode OR g.json.post.o is not NULL (meaning POST
+** data was fed in from CLI mode).
+**
+** Specifically, it will return false when any of these apply:
+**
+** a) Not running in JSON mode (via json command or /json path).
+**
+** b) We are running in JSON CLI mode, but no POST data has been fed
+** in.
+**
+** Whether or not we need to take args from CLI or POST data makes a
+** difference in argument/parameter handling in many JSON rountines,
+** and thus this distinction.
+*/
+char fossil_has_json();
+
+
+#endif/*FOSSIL_JSON_DETAIL_H_INCLUDED*/
ADDED src/json_diff.c
Index: src/json_diff.c
==================================================================
--- /dev/null
+++ src/json_diff.c
@@ -0,0 +1,124 @@
+/*
+** Copyright (c) 2011 D. Richard Hipp
+**
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the Simplified BSD License (also
+** known as the "2-Clause License" or "FreeBSD License".)
+**
+** This program is distributed in the hope that it will be useful,
+** but without any warranty; without even the implied warranty of
+** merchantability or fitness for a particular purpose.
+**
+** Author contact information:
+** drh@hwaci.com
+** http://www.hwaci.com/drh/
+**
+*/
+
+#include "config.h"
+#include "json_diff.h"
+
+#if INTERFACE
+#include "json_detail.h"
+#endif
+
+
+
+/*
+** Generates a diff between two versions (zFrom and zTo), using nContext
+** content lines in the output. On success, returns a new JSON String
+** object. On error it sets g.json's error state and returns NULL.
+*/
+cson_value * json_generate_diff(const char *zFrom, const char *zTo,
+ int nContext){
+ int fromid;
+ int toid;
+ int outLen;
+ Blob from = empty_blob, to = empty_blob, out = empty_blob;
+ cson_value * rc = NULL;
+ char const * zType = "ci";
+ fromid = name_to_typed_rid(zFrom, "*");
+ if(fromid<=0){
+ json_set_err(FSL_JSON_E_UNRESOLVED_UUID,
+ "Could not resolve 'from' ID.");
+ return NULL;
+ }
+ toid = name_to_typed_rid(zTo, "*");
+ if(toid<=0){
+ json_set_err(FSL_JSON_E_UNRESOLVED_UUID,
+ "Could not resolve 'to' ID.");
+ return NULL;
+ }
+ content_get(fromid, &from);
+ content_get(toid, &to);
+ blob_zero(&out);
+ text_diff(&from, &to, &out, nContext, 1);
+ blob_reset(&from);
+ blob_reset(&to);
+ outLen = blob_size(&out);
+ if(outLen>0){
+ rc = cson_value_new_string(blob_buffer(&out), blob_size(&out));
+ }
+ blob_reset(&out);
+ return rc;
+}
+
+/*
+** Implementation of the /json/diff page.
+**
+** Arguments:
+**
+** v1=1st version to diff
+** v2=2nd version to diff
+**
+** Can come from GET, POST.payload, CLI -v1/-v2 or as positional
+** parameters following the command name (in HTTP and CLI modes).
+**
+*/
+cson_value * json_page_diff(){
+ cson_object * pay = NULL;
+ cson_value * v = NULL;
+ char const * zFrom;
+ char const * zTo;
+ int nContext = 0;
+ if(!g.perm.Read){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'o' permissions.");
+ return NULL;
+ }
+ zFrom = json_find_option_cstr("v1",NULL,NULL);
+ if(!zFrom){
+ zFrom = json_command_arg(2);
+ }
+ if(!zFrom){
+ json_set_err(FSL_JSON_E_MISSING_ARGS,
+ "Required 'v1' parameter is missing.");
+ return NULL;
+ }
+ zTo = json_find_option_cstr("v2",NULL,NULL);
+ if(!zTo){
+ zTo = json_command_arg(3);
+ }
+ if(!zTo){
+ json_set_err(FSL_JSON_E_MISSING_ARGS,
+ "Required 'v2' parameter is missing.");
+ return NULL;
+ }
+ nContext = json_find_option_int("context",NULL,"c",5);
+ v = json_generate_diff(zFrom, zTo, nContext);
+ if(!v){
+ if(!g.json.resultCode){
+ json_set_err(FSL_JSON_E_UNKNOWN,
+ "Generating diff failed for unknown reason.");
+ }
+ return NULL;
+ }
+ pay = cson_new_object();
+ cson_object_set(pay, "from", json_new_string(zFrom));
+ cson_object_set(pay, "to", json_new_string(zTo));
+ cson_object_set(pay, "diff", v);
+ v = 0;
+
+ return pay ? cson_object_value(pay) : NULL;
+}
+
ADDED src/json_login.c
Index: src/json_login.c
==================================================================
--- /dev/null
+++ src/json_login.c
@@ -0,0 +1,247 @@
+/*
+** Copyright (c) 2011 D. Richard Hipp
+**
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the Simplified BSD License (also
+** known as the "2-Clause License" or "FreeBSD License".)
+**
+** This program is distributed in the hope that it will be useful,
+** but without any warranty; without even the implied warranty of
+** merchantability or fitness for a particular purpose.
+**
+** Author contact information:
+** drh@hwaci.com
+** http://www.hwaci.com/drh/
+**
+*/
+
+#include "config.h"
+#include "json_login.h"
+
+#if INTERFACE
+#include "json_detail.h"
+#endif
+
+
+/*
+** Implementation of the /json/login page.
+**
+*/
+cson_value * json_page_login(){
+ char preciseErrors = /* if true, "complete" JSON error codes are used,
+ else they are "dumbed down" to a generic login
+ error code.
+ */
+#if 1
+ g.json.errorDetailParanoia ? 0 : 1
+#else
+ 0
+#endif
+ ;
+ /*
+ FIXME: we want to check the GET/POST args in this order:
+
+ - GET: name, n, password, p
+ - POST: name, password
+
+ but a bug in cgi_parameter() is breaking that, causing PD() to
+ return the last element of the PATH_INFO instead.
+
+ Summary: If we check for P("name") first, then P("n"),
+ then ONLY a GET param of "name" will match ("n"
+ is not recognized). If we reverse the order of the
+ checks then both forms work. Strangely enough, the
+ "p"/"password" check is not affected by this.
+ */
+ char const * name = cson_value_get_cstr(json_payload_property("name"));
+ char const * pw = NULL;
+ char const * anonSeed = NULL;
+ cson_value * payload = NULL;
+ int uid = 0;
+ /* reminder to self:
+ Fossil internally (for the sake of /wiki) interprets
+ paths in the form /foo/bar/baz such that
+ P("name") == "bar/baz". This collides with our
+ name/password checking, and thus we check for the
+ password first.
+ */
+ pw = cson_value_get_cstr(json_payload_property("password"));
+ if( !pw ){
+ pw = PD("p",NULL);
+ if( !pw ){
+ pw = PD("password",NULL);
+ }
+ }
+ if(!pw){
+ g.json.resultCode = preciseErrors
+ ? FSL_JSON_E_LOGIN_FAILED_NOPW
+ : FSL_JSON_E_LOGIN_FAILED;
+ return NULL;
+ }
+
+ if( !name ){
+ name = PD("n",NULL);
+ if( !name ){
+ name = PD("name",NULL);
+ if( !name ){
+ g.json.resultCode = preciseErrors
+ ? FSL_JSON_E_LOGIN_FAILED_NONAME
+ : FSL_JSON_E_LOGIN_FAILED;
+ return NULL;
+ }
+ }
+ }
+
+ if(0 == strcmp("anonymous",name)){
+ /* check captcha/seed values... */
+ enum { SeedBufLen = 100 /* in some JSON tests i once actually got an
+ 80-digit number.
+ */
+ };
+ static char seedBuffer[SeedBufLen];
+ cson_value const * jseed = json_getenv(FossilJsonKeys.anonymousSeed);
+ seedBuffer[0] = 0;
+ if( !jseed ){
+ jseed = json_payload_property(FossilJsonKeys.anonymousSeed);
+ if( !jseed ){
+ jseed = json_getenv("cs") /* name used by HTML interface */;
+ }
+ }
+ if(jseed){
+ if( cson_value_is_number(jseed) ){
+ sprintf(seedBuffer, "%"CSON_INT_T_PFMT, cson_value_get_integer(jseed));
+ anonSeed = seedBuffer;
+ }else if( cson_value_is_string(jseed) ){
+ anonSeed = cson_string_cstr(cson_value_get_string(jseed));
+ }
+ }
+ if(!anonSeed){
+ g.json.resultCode = preciseErrors
+ ? FSL_JSON_E_LOGIN_FAILED_NOSEED
+ : FSL_JSON_E_LOGIN_FAILED;
+ return NULL;
+ }
+ }
+
+#if 0
+ {
+ /* only for debugging the PD()-incorrect-result problem */
+ cson_object * o = NULL;
+ uid = login_search_uid( name, pw );
+ payload = cson_value_new_object();
+ o = cson_value_get_object(payload);
+ cson_object_set( o, "n", cson_value_new_string(name,strlen(name)));
+ cson_object_set( o, "p", cson_value_new_string(pw,strlen(pw)));
+ return payload;
+ }
+#endif
+ uid = anonSeed
+ ? login_is_valid_anonymous(name, pw, anonSeed)
+ : login_search_uid(name, pw)
+ ;
+ if( !uid ){
+ g.json.resultCode = preciseErrors
+ ? FSL_JSON_E_LOGIN_FAILED_NOTFOUND
+ : FSL_JSON_E_LOGIN_FAILED;
+ return NULL;
+ }else{
+ char * cookie = NULL;
+ cson_object * po;
+ char * cap = NULL;
+ if(anonSeed){
+ login_set_anon_cookie(NULL, &cookie);
+ }else{
+ login_set_user_cookie(name, uid, &cookie);
+ }
+ payload = cson_value_new_object();
+ po = cson_value_get_object(payload);
+ cson_object_set(po, "authToken", json_new_string(cookie));
+ free(cookie);
+ cson_object_set(po, "name", json_new_string(name));
+ cap = db_text(NULL, "SELECT cap FROM user WHERE login=%Q",name);
+ cson_object_set(po, "capabilities", json_new_string(cap));
+ free(cap);
+ return payload;
+ }
+}
+
+/*
+** Impl of /json/logout.
+**
+*/
+cson_value * json_page_logout(){
+ cson_value const *token = g.json.authToken;
+ /* Remember that json_mode_bootstrap() replaces the login cookie
+ with the JSON auth token if the request contains it. If the
+ reqest is missing the auth token then this will fetch fossil's
+ original cookie. Either way, it's what we want :).
+
+ We require the auth token to avoid someone maliciously
+ trying to log someone else out (not 100% sure if that
+ would be possible, given fossil's hardened cookie, but
+ i'll assume it would be for the time being).
+ */
+ ;
+ if(!token){
+ g.json.resultCode = FSL_JSON_E_MISSING_AUTH;
+ }else{
+ login_clear_login_data();
+ g.json.authToken = NULL /* memory is owned elsewhere.*/;
+ }
+ return NULL;
+}
+
+/*
+** Implementation of the /json/anonymousPassword page.
+*/
+cson_value * json_page_anon_password(){
+ cson_value * v = cson_value_new_object();
+ cson_object * o = cson_value_get_object(v);
+ unsigned const int seed = captcha_seed();
+ char const * zCaptcha = captcha_decode(seed);
+ cson_object_set(o, "seed",
+ cson_value_new_integer( (cson_int_t)seed )
+ );
+ cson_object_set(o, "password",
+ cson_value_new_string( zCaptcha, strlen(zCaptcha) )
+ );
+ return v;
+}
+
+
+
+/*
+** Implements the /json/whoami page/command.
+*/
+cson_value * json_page_whoami(){
+ cson_value * payload = NULL;
+ cson_object * obj = NULL;
+ Stmt q;
+ db_prepare(&q, "SELECT login, cap FROM user WHERE uid=%d", g.userUid);
+ if( db_step(&q)==SQLITE_ROW ){
+
+ /* reminder: we don't use g.zLogin because it's 0 for the guest
+ user and the HTML UI appears to currently allow the name to be
+ changed (but doing so would break other code). */
+ char const * str;
+ payload = cson_value_new_object();
+ obj = cson_value_get_object(payload);
+ str = (char const *)sqlite3_column_text(q.pStmt,0);
+ if( str ){
+ cson_object_set( obj, "name",
+ cson_value_new_string(str,strlen(str)) );
+ }
+ str = (char const *)sqlite3_column_text(q.pStmt,1);
+ if( str ){
+ cson_object_set( obj, "capabilities",
+ cson_value_new_string(str,strlen(str)) );
+ }
+ if( g.json.authToken ){
+ cson_object_set( obj, "authToken", g.json.authToken );
+ }
+ }else{
+ g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND;
+ }
+ db_finalize(&q);
+ return payload;
+}
ADDED src/json_query.c
Index: src/json_query.c
==================================================================
--- /dev/null
+++ src/json_query.c
@@ -0,0 +1,87 @@
+/*
+** Copyright (c) 2011 D. Richard Hipp
+**
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the Simplified BSD License (also
+** known as the "2-Clause License" or "FreeBSD License".)
+**
+** This program is distributed in the hope that it will be useful,
+** but without any warranty; without even the implied warranty of
+** merchantability or fitness for a particular purpose.
+**
+** Author contact information:
+** drh@hwaci.com
+** http://www.hwaci.com/drh/
+**
+*/
+
+#include "config.h"
+#include "json_query.h"
+
+#if INTERFACE
+#include "json_detail.h"
+#endif
+
+
+/*
+** Implementation of the /json/query page.
+**
+** Requires admin privileges. Intended primarily to assist me in
+** coming up with JSON output structures for pending features.
+**
+** Options/parameters:
+**
+** sql=string - a SELECT statement
+**
+** format=string 'a' means each row is an Array of values, 'o'
+** (default) creates each row as an Object.
+**
+** TODO: in CLI mode (only) use -S FILENAME to read the sql
+** from a file.
+*/
+cson_value * json_page_query(){
+ char const * zSql = NULL;
+ cson_value * payV;
+ char const * zFmt;
+ Stmt q = empty_Stmt;
+ int check;
+ if(!g.perm.Admin && !g.perm.Setup){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'a' or 's' privileges.");
+ return NULL;
+ }
+
+ if( cson_value_is_string(g.json.reqPayload.v) ){
+ zSql = cson_string_cstr(cson_value_get_string(g.json.reqPayload.v));
+ }else{
+ zSql = json_find_option_cstr2("sql",NULL,"s",2);
+ }
+
+ if(!zSql || !*zSql){
+ json_set_err(FSL_JSON_E_MISSING_ARGS,
+ "'sql' (-s) argument is missing.");
+ return NULL;
+ }
+
+ zFmt = json_find_option_cstr2("format",NULL,"f",3);
+ if(!zFmt) zFmt = "o";
+ db_prepare(&q,"%s", zSql);
+ switch(*zFmt){
+ case 'a':
+ check = cson_sqlite3_stmt_to_json(q.pStmt, &payV, 0);
+ break;
+ case 'o':
+ default:
+ check = cson_sqlite3_stmt_to_json(q.pStmt, &payV, 1);
+ };
+ db_finalize(&q);
+ if(0 != check){
+ json_set_err(FSL_JSON_E_UNKNOWN,
+ "Conversion to JSON failed with cson code #%d (%s).",
+ check, cson_rc_string(check));
+ assert(NULL==payV);
+ }
+ return payV;
+
+}
+
ADDED src/json_report.c
Index: src/json_report.c
==================================================================
--- /dev/null
+++ src/json_report.c
@@ -0,0 +1,260 @@
+/*
+** Copyright (c) 2011 D. Richard Hipp
+**
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the Simplified BSD License (also
+** known as the "2-Clause License" or "FreeBSD License".)
+**
+** This program is distributed in the hope that it will be useful,
+** but without any warranty; without even the implied warranty of
+** merchantability or fitness for a particular purpose.
+**
+** Author contact information:
+** drh@hwaci.com
+** http://www.hwaci.com/drh/
+**
+*/
+
+#include "config.h"
+#include "json_report.h"
+
+#if INTERFACE
+#include "json_detail.h"
+#endif
+
+
+static cson_value * json_report_create();
+static cson_value * json_report_get();
+static cson_value * json_report_list();
+static cson_value * json_report_run();
+static cson_value * json_report_save();
+
+/*
+** Mapping of /json/report/XXX commands/paths to callbacks.
+*/
+static const JsonPageDef JsonPageDefs_Report[] = {
+{"create", json_report_create, 0},
+{"get", json_report_get, 0},
+{"list", json_report_list, 0},
+{"run", json_report_run, 0},
+{"save", json_report_save, 0},
+/* Last entry MUST have a NULL name. */
+{NULL,NULL,0}
+};
+/*
+** Implementation of the /json/report page.
+**
+**
+*/
+cson_value * json_page_report(){
+ if(!g.perm.RdTkt && !g.perm.NewTkt ){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'r' or 'n' permissions.");
+ return NULL;
+ }
+ return json_page_dispatch_helper(&JsonPageDefs_Report[0]);
+}
+
+/*
+** Searches the environment for a "report" parameter
+** (CLI: -report/-r #).
+**
+** If one is not found and argPos is >0 then json_command_arg()
+** is checked.
+**
+** Returns >0 (the report number) on success .
+*/
+static int json_report_get_number(int argPos){
+ int nReport = json_find_option_int("report",NULL,"r",-1);
+ if( (nReport<=0) && cson_value_is_integer(g.json.reqPayload.v)){
+ nReport = cson_value_get_integer(g.json.reqPayload.v);
+ }
+ if( (nReport <= 0) && (argPos>0) ){
+ char const * arg = json_command_arg(argPos);
+ if(arg && fossil_isdigit(*arg)) {
+ nReport = atoi(arg);
+ }
+ }
+ return nReport;
+}
+
+static cson_value * json_report_create(){
+ return NULL;
+}
+
+static cson_value * json_report_get(){
+ int nReport;
+ Stmt q = empty_Stmt;
+ cson_value * pay = NULL;
+
+ if(!g.perm.TktFmt){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 't' privileges.");
+ return NULL;
+ }
+ nReport = json_report_get_number(3);
+ if(nReport <=0){
+ json_set_err(FSL_JSON_E_MISSING_ARGS,
+ "Missing or invalid 'number' (-n) parameter.");
+ return NULL;
+ }
+
+ db_prepare(&q,"SELECT rn AS report,"
+ " owner AS owner,"
+ " title AS title,"
+ " cast(strftime('%%s',mtime) as int) as mtime,"
+ " cols as columns,"
+ " sqlcode as sqlCode"
+ " FROM reportfmt"
+ " WHERE rn=%d",
+ nReport);
+ if( SQLITE_ROW != db_step(&q) ){
+ db_finalize(&q);
+ json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND,
+ "Report #%d not found.", nReport);
+ return NULL;
+ }
+ pay = cson_sqlite3_row_to_object(q.pStmt);
+ db_finalize(&q);
+ return pay;
+}
+
+/*
+** Impl of /json/report/list.
+*/
+static cson_value * json_report_list(){
+ Blob sql = empty_blob;
+ cson_value * pay = NULL;
+ if(!g.perm.RdTkt){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'r' privileges.");
+ return NULL;
+ }
+ blob_append(&sql, "SELECT"
+ " rn AS report,"
+ " title as title,"
+ " owner as owner"
+ " FROM reportfmt"
+ " WHERE 1"
+ " ORDER BY title",
+ -1);
+ pay = json_sql_to_array_of_obj(&sql, NULL, 1);
+ if(!pay){
+ json_set_err(FSL_JSON_E_UNKNOWN,
+ "Quite unexpected: no ticket reports found.");
+ }
+ return pay;
+}
+
+/*
+** Impl for /json/report/run
+**
+** Options/arguments:
+**
+** report=int (CLI: -report # or -r #) is the report number to run.
+**
+** limit=int (CLI: -limit # or -n #) -n is for compat. with other commands.
+**
+** format=a|o Specifies result format: a=each row is an arry, o=each
+** row is an object. Default=o.
+*/
+static cson_value * json_report_run(){
+ int nReport;
+ Stmt q = empty_Stmt;
+ cson_object * pay = NULL;
+ cson_array * tktList = NULL;
+ char const * zFmt;
+ char * zTitle = NULL;
+ Blob sql = empty_blob;
+ int limit = 0;
+ cson_value * colNames = NULL;
+ int i;
+
+ if(!g.perm.RdTkt){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'r' privileges.");
+ return NULL;
+ }
+ nReport = json_report_get_number(3);
+ if(nReport <=0){
+ json_set_err(FSL_JSON_E_MISSING_ARGS,
+ "Missing or invalid 'number' (-n) parameter.");
+ goto error;
+ }
+ zFmt = json_find_option_cstr2("format",NULL,"f",3);
+ if(!zFmt) zFmt = "o";
+ db_prepare(&q,
+ "SELECT sqlcode, "
+ " title"
+ " FROM reportfmt"
+ " WHERE rn=%d",
+ nReport);
+ if(SQLITE_ROW != db_step(&q)){
+ json_set_err(FSL_JSON_E_INVALID_ARGS,
+ "Report number %d not found.",
+ nReport);
+ db_finalize(&q);
+ goto error;
+ }
+
+ limit = json_find_option_int("limit",NULL,"n",-1);
+
+
+ /* Copy over report's SQL...*/
+ blob_append(&sql, db_column_text(&q,0), -1);
+ zTitle = mprintf("%s", db_column_text(&q,1));
+ db_finalize(&q);
+ db_prepare(&q, "%s", blob_str(&sql));
+
+ /** Build the response... */
+ pay = cson_new_object();
+
+ cson_object_set(pay, "report", json_new_int(nReport));
+ cson_object_set(pay, "title", json_new_string(zTitle));
+ if(limit>0){
+ cson_object_set(pay, "limit", json_new_int((limit<0) ? 0 : limit));
+ }
+ free(zTitle);
+ zTitle = NULL;
+
+ if(g.perm.TktFmt){
+ cson_object_set(pay, "sqlcode",
+ cson_value_new_string(blob_str(&sql),
+ (unsigned int)blob_size(&sql)));
+ }
+ blob_reset(&sql);
+
+ colNames = cson_sqlite3_column_names(q.pStmt);
+ cson_object_set( pay, "columnNames", colNames);
+ for( i = 0 ; ((limit>0) ?(i < limit) : 1)
+ && (SQLITE_ROW == db_step(&q));
+ ++i){
+ cson_value * row = ('a'==*zFmt)
+ ? cson_sqlite3_row_to_array(q.pStmt)
+ : cson_sqlite3_row_to_object2(q.pStmt,
+ cson_value_get_array(colNames));
+ ;
+ if(row && !tktList){
+ tktList = cson_new_array();
+ }
+ cson_array_append(tktList, row);
+ }
+ db_finalize(&q);
+ cson_object_set(pay, "tickets",
+ tktList ? cson_array_value(tktList) : cson_value_null());
+
+ goto end;
+
+ error:
+ assert(0 != g.json.resultCode);
+ cson_value_free( cson_object_value(pay) );
+ pay = NULL;
+ end:
+
+ return pay ? cson_object_value(pay) : NULL;
+
+}
+
+static cson_value * json_report_save(){
+ return NULL;
+}
ADDED src/json_tag.c
Index: src/json_tag.c
==================================================================
--- /dev/null
+++ src/json_tag.c
@@ -0,0 +1,475 @@
+/*
+** Copyright (c) 2011 D. Richard Hipp
+**
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the Simplified BSD License (also
+** known as the "2-Clause License" or "FreeBSD License".)
+**
+** This program is distributed in the hope that it will be useful,
+** but without any warranty; without even the implied warranty of
+** merchantability or fitness for a particular purpose.
+**
+** Author contact information:
+** drh@hwaci.com
+** http://www.hwaci.com/drh/
+**
+*/
+#include "VERSION.h"
+#include "config.h"
+#include "json_tag.h"
+
+#if INTERFACE
+#include "json_detail.h"
+#endif
+
+
+static cson_value * json_tag_add();
+static cson_value * json_tag_cancel();
+static cson_value * json_tag_find();
+static cson_value * json_tag_list();
+/*
+** Mapping of /json/tag/XXX commands/paths to callbacks.
+*/
+static const JsonPageDef JsonPageDefs_Tag[] = {
+{"add", json_tag_add, 0},
+{"cancel", json_tag_cancel, 0},
+{"find", json_tag_find, 0},
+{"list", json_tag_list, 0},
+/* Last entry MUST have a NULL name. */
+{NULL,NULL,0}
+};
+
+/*
+** Implements the /json/tag family of pages/commands.
+**
+*/
+cson_value * json_page_tag(){
+ return json_page_dispatch_helper(&JsonPageDefs_Tag[0]);
+}
+
+
+/*
+** Impl of /json/tag/add.
+*/
+static cson_value * json_tag_add(){
+ cson_value * payV = NULL;
+ cson_object * pay = NULL;
+ char const * zName = NULL;
+ char const * zCheckin = NULL;
+ char fRaw = 0;
+ char fPropagate = 0;
+ char const * zValue = NULL;
+ const char *zPrefix = NULL;
+
+ if( !g.perm.Write ){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'i' permissions.");
+ return NULL;
+ }
+ fRaw = json_find_option_bool("raw",NULL,NULL,0);
+ fPropagate = json_find_option_bool("propagate",NULL,NULL,0);
+ zName = json_find_option_cstr("name",NULL,NULL);
+ zPrefix = fRaw ? "" : "sym-";
+ if(!zName || !*zName){
+ if(!fossil_has_json()){
+ zName = json_command_arg(3);
+ }
+ if(!zName || !*zName){
+ json_set_err(FSL_JSON_E_MISSING_ARGS,
+ "'name' parameter is missing.");
+ return NULL;
+ }
+ }
+
+ zCheckin = json_find_option_cstr("checkin",NULL,NULL);
+ if( !zCheckin ){
+ if(!fossil_has_json()){
+ zCheckin = json_command_arg(4);
+ }
+ if(!zCheckin || !*zCheckin){
+ json_set_err(FSL_JSON_E_MISSING_ARGS,
+ "'checkin' parameter is missing.");
+ return NULL;
+ }
+ }
+
+
+ zValue = json_find_option_cstr("value",NULL,NULL);
+ if(!zValue && !fossil_has_json()){
+ zValue = json_command_arg(5);
+ }
+
+ db_begin_transaction();
+ tag_add_artifact(zPrefix, zName, zCheckin, zValue,
+ 1+fPropagate,NULL/*DateOvrd*/,NULL/*UserOvrd*/);
+ db_end_transaction(0);
+
+ payV = cson_value_new_object();
+ pay = cson_value_get_object(payV);
+ cson_object_set(pay, "name", json_new_string(zName) );
+ cson_object_set(pay, "value", (zValue&&*zValue)
+ ? json_new_string(zValue)
+ : cson_value_null());
+ cson_object_set(pay, "propagate", cson_value_new_bool(fPropagate));
+ cson_object_set(pay, "raw", cson_value_new_bool(fRaw));
+ {
+ Blob uu = empty_blob;
+ blob_append(&uu, zName, -1);
+ int const rc = name_to_uuid(&uu, 9, "*");
+ if(0!=rc){
+ json_set_err(FSL_JSON_E_UNKNOWN,"Could not convert name back to UUID!");
+ blob_reset(&uu);
+ goto error;
+ }
+ cson_object_set(pay, "appliedTo", json_new_string(blob_buffer(&uu)));
+ blob_reset(&uu);
+ }
+
+ goto ok;
+ error:
+ assert( 0 != g.json.resultCode );
+ cson_value_free(payV);
+ payV = NULL;
+ ok:
+ return payV;
+}
+
+
+/*
+** Impl of /json/tag/cancel.
+*/
+static cson_value * json_tag_cancel(){
+ char const * zName = NULL;
+ char const * zCheckin = NULL;
+ char fRaw = 0;
+ const char *zPrefix = NULL;
+
+ if( !g.perm.Write ){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'i' permissions.");
+ return NULL;
+ }
+
+ fRaw = json_find_option_bool("raw",NULL,NULL,0);
+ zPrefix = fRaw ? "" : "sym-";
+ zName = json_find_option_cstr("name",NULL,NULL);
+ if(!zName || !*zName){
+ if(!fossil_has_json()){
+ zName = json_command_arg(3);
+ }
+ if(!zName || !*zName){
+ json_set_err(FSL_JSON_E_MISSING_ARGS,
+ "'name' parameter is missing.");
+ return NULL;
+ }
+ }
+
+ zCheckin = json_find_option_cstr("checkin",NULL,NULL);
+ if( !zCheckin ){
+ if(!fossil_has_json()){
+ zCheckin = json_command_arg(4);
+ }
+ if(!zCheckin || !*zCheckin){
+ json_set_err(FSL_JSON_E_MISSING_ARGS,
+ "'checkin' parameter is missing.");
+ return NULL;
+ }
+ }
+ /* FIXME?: verify that the tag is currently active. We have no real
+ error case unless we do that.
+ */
+ db_begin_transaction();
+ tag_add_artifact(zPrefix, zName, zCheckin, NULL, 0, 0, 0);
+ db_end_transaction(0);
+ return NULL;
+}
+
+
+/*
+** Impl of /json/tag/find.
+*/
+static cson_value * json_tag_find(){
+ cson_value * payV = NULL;
+ cson_object * pay = NULL;
+ cson_value * listV = NULL;
+ cson_array * list = NULL;
+ char const * zName = NULL;
+ char const * zType = NULL;
+ char const * zType2 = NULL;
+ char fRaw = 0;
+ Stmt q = empty_Stmt;
+ int limit = 0;
+ int tagid = 0;
+
+ if( !g.perm.Read ){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'o' permissions.");
+ return NULL;
+ }
+ zName = json_find_option_cstr("name",NULL,NULL);
+ if(!zName || !*zName){
+ if(!fossil_has_json()){
+ zName = json_command_arg(3);
+ }
+ if(!zName || !*zName){
+ json_set_err(FSL_JSON_E_MISSING_ARGS,
+ "'name' parameter is missing.");
+ return NULL;
+ }
+ }
+ zType = json_find_option_cstr("type",NULL,"t");
+ if(!zType || !*zType){
+ zType = "*";
+ zType2 = zType;
+ }else{
+ switch(*zType){
+ case 'c': zType = "ci"; zType2 = "checkin"; break;
+ case 'e': zType = "e"; zType2 = "event"; break;
+ case 'w': zType = "w"; zType2 = "wiki"; break;
+ case 't': zType = "t"; zType2 = "ticket"; break;
+ }
+ }
+
+ limit = json_find_option_int("limit",NULL,"n",0);
+ fRaw = json_find_option_bool("raw",NULL,NULL,0);
+
+ tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='%s' || %Q",
+ fRaw ? "" : "sym-",
+ zName);
+
+ payV = cson_value_new_object();
+ pay = cson_value_get_object(payV);
+ cson_object_set(pay, "name", json_new_string(zName));
+ cson_object_set(pay, "raw", cson_value_new_bool(fRaw));
+ cson_object_set(pay, "type", json_new_string(zType2));
+ cson_object_set(pay, "limit", json_new_int(limit));
+
+#if 1
+ if( tagid<=0 ){
+ cson_object_set(pay,"artifacts", cson_value_null());
+ json_warn(FSL_JSON_W_TAG_NOT_FOUND, "Tag not found.");
+ return payV;
+ }
+#endif
+
+ if( fRaw ){
+ db_prepare(&q,
+ "SELECT blob.uuid FROM tagxref, blob"
+ " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
+ " AND tagxref.tagtype>0"
+ " AND blob.rid=tagxref.rid"
+ "%s LIMIT %d",
+ zName,
+ (limit>0)?"":"--", limit
+ );
+ while( db_step(&q)==SQLITE_ROW ){
+ if(!listV){
+ listV = cson_value_new_array();
+ list = cson_value_get_array(listV);
+ }
+ cson_array_append(list, cson_sqlite3_column_to_value(q.pStmt,0));
+ }
+ db_finalize(&q);
+ }else{
+ char const * zSqlBase = /*modified from timeline_query_for_tty()*/
+ " SELECT"
+#if 0
+ " blob.rid AS rid,"
+#endif
+ " uuid AS uuid,"
+ " cast(strftime('%s',event.mtime) as int) AS mtime,"
+ " coalesce(ecomment,comment) AS comment,"
+ " coalesce(euser,user) AS user,"
+ " CASE event.type"
+ " WHEN 'ci' THEN 'checkin'"
+ " WHEN 'w' THEN 'wiki'"
+ " WHEN 'e' THEN 'event'"
+ " WHEN 't' THEN 'ticket'"
+ " ELSE 'WTF?'"
+ " END"
+ " AS eventType"
+ " FROM event, blob"
+ " WHERE blob.rid=event.objid"
+ ;
+ /* FIXME: re-add tags. */
+ db_prepare(&q,
+ "%s"
+ " AND event.type GLOB '%q'"
+ " AND blob.rid IN ("
+ " SELECT rid FROM tagxref"
+ " WHERE tagtype>0 AND tagid=%d"
+ " )"
+ " ORDER BY event.mtime DESC"
+ "%s LIMIT %d",
+ zSqlBase, zType, tagid,
+ (limit>0)?"":"--", limit
+ );
+ listV = json_stmt_to_array_of_obj(&q, NULL);
+ db_finalize(&q);
+ }
+
+ if(!listV) {
+ listV = cson_value_null();
+ }
+ cson_object_set(pay, "artifacts", listV);
+ return payV;
+}
+
+
+/*
+** Impl for /json/tag/list
+**
+** TODOs:
+**
+** Add -type TYPE (ci, w, e, t)
+*/
+static cson_value * json_tag_list(){
+ cson_value * payV = NULL;
+ cson_object * pay = NULL;
+ cson_value const * tagsVal = NULL;
+ char const * zCheckin = NULL;
+ char fRaw = 0;
+ char fTicket = 0;
+ Stmt q = empty_Stmt;
+
+ if( !g.perm.Read ){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'o' permissions.");
+ return NULL;
+ }
+
+ fRaw = json_find_option_bool("raw",NULL,NULL,0);
+ fTicket = json_find_option_bool("includeTickets","tkt","t",0);
+ zCheckin = json_find_option_cstr("checkin",NULL,NULL);
+ if( !zCheckin ){
+ zCheckin = json_command_arg( g.json.dispatchDepth + 1);
+ if( !zCheckin && cson_value_is_string(g.json.reqPayload.v) ){
+ zCheckin = cson_string_cstr(cson_value_get_string(g.json.reqPayload.v));
+ assert(zCheckin);
+ }
+ }
+ payV = cson_value_new_object();
+ pay = cson_value_get_object(payV);
+ cson_object_set(pay, "raw", cson_value_new_bool(fRaw) );
+ if( zCheckin ){
+ /**
+ Tags for a specific checkin. Output format:
+
+ RAW mode:
+
+ {
+ "sym-tagname": (value || null),
+ ...other tags...
+ }
+
+ Non-raw:
+
+ {
+ "tagname": (value || null),
+ ...other tags...
+ }
+ */
+ cson_value * objV = NULL;
+ cson_object * obj = NULL;
+ int const rid = name_to_rid(zCheckin);
+ if(0==rid){
+ json_set_err(FSL_JSON_E_UNRESOLVED_UUID,
+ "Could not find artifact for checkin [%s].",
+ zCheckin);
+ goto error;
+ }
+ cson_object_set(pay, "checkin", json_new_string(zCheckin));
+ db_prepare(&q,
+ "SELECT tagname, value FROM tagxref, tag"
+ " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid"
+ " AND tagtype>%d"
+ " ORDER BY tagname",
+ rid,
+ fRaw ? -1 : 0
+ );
+ while( SQLITE_ROW == db_step(&q) ){
+ const char *zName = db_column_text(&q, 0);
+ const char *zValue = db_column_text(&q, 1);
+ if( fRaw==0 ){
+ if( 0!=strncmp(zName, "sym-", 4) ) continue;
+ zName += 4;
+ assert( *zName );
+ }
+ if(NULL==objV){
+ objV = cson_value_new_object();
+ obj = cson_value_get_object(objV);
+ tagsVal = objV;
+ cson_object_set( pay, "tags", objV );
+ }
+ if( zValue && zValue[0] ){
+ cson_object_set( obj, zName, json_new_string(zValue) );
+ }else{
+ cson_object_set( obj, zName, cson_value_null() );
+ }
+ }
+ db_finalize(&q);
+ }else{/* all tags */
+ /* Output format:
+
+ RAW mode:
+
+ ["tagname", "sym-tagname2",...]
+
+ Non-raw:
+
+ ["tagname", "tagname2",...]
+
+ i don't really like the discrepancy in the format but this list
+ can get really long and (A) most tags don't have values, (B) i
+ don't want to bloat it more, and (C) cson_object_set() is O(N)
+ (N=current number of properties) because it uses an unsorted list
+ internally (for memory reasons), so this can slow down appreciably
+ on a long list. The culprit is really tkt- tags, as there is one
+ for each ticket (941 in the main fossil repo as of this writing).
+ */
+ Blob sql = empty_blob;
+ cson_value * arV = NULL;
+ cson_array * ar = NULL;
+ blob_append(&sql,
+ "SELECT tagname FROM tag"
+ " WHERE EXISTS(SELECT 1 FROM tagxref"
+ " WHERE tagid=tag.tagid"
+ " AND tagtype>0)",
+ -1
+ );
+ if(!fTicket){
+ blob_append(&sql, " AND tagname NOT GLOB('tkt-*') ", -1);
+ }
+ blob_append(&sql,
+ " ORDER BY tagname", -1);
+ db_prepare(&q, blob_buffer(&sql));
+ blob_reset(&sql);
+ cson_object_set(pay, "includeTickets", cson_value_new_bool(fTicket) );
+ while( SQLITE_ROW == db_step(&q) ){
+ const char *zName = db_column_text(&q, 0);
+ if(NULL==arV){
+ arV = cson_value_new_array();
+ ar = cson_value_get_array(arV);
+ cson_object_set(pay, "tags", arV);
+ tagsVal = arV;
+ }
+ else if( !fRaw && (0==strncmp(zName, "sym-", 4))){
+ zName += 4;
+ assert( *zName );
+ }
+ cson_array_append(ar, json_new_string(zName));
+ }
+ db_finalize(&q);
+ }
+
+ goto end;
+ error:
+ assert(0 != g.json.resultCode);
+ cson_value_free(payV);
+ payV = NULL;
+ end:
+ if( payV && !tagsVal ){
+ cson_object_set( pay, "tags", cson_value_null() );
+ }
+ return payV;
+}
ADDED src/json_timeline.c
Index: src/json_timeline.c
==================================================================
--- /dev/null
+++ src/json_timeline.c
@@ -0,0 +1,691 @@
+/*
+** Copyright (c) 2011 D. Richard Hipp
+**
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the Simplified BSD License (also
+** known as the "2-Clause License" or "FreeBSD License".)
+**
+** This program is distributed in the hope that it will be useful,
+** but without any warranty; without even the implied warranty of
+** merchantability or fitness for a particular purpose.
+**
+** Author contact information:
+** drh@hwaci.com
+** http://www.hwaci.com/drh/
+**
+*/
+
+#include "VERSION.h"
+#include "config.h"
+#include "json_timeline.h"
+
+#if INTERFACE
+#include "json_detail.h"
+#endif
+
+static cson_value * json_timeline_branch();
+static cson_value * json_timeline_ci();
+static cson_value * json_timeline_ticket();
+/*
+** Mapping of /json/timeline/XXX commands/paths to callbacks.
+*/
+static const JsonPageDef JsonPageDefs_Timeline[] = {
+/* the short forms are only enabled in CLI mode, to avoid
+ that we end up with HTTP clients using 3 different names
+ for the same requests.
+*/
+{"branch", json_timeline_branch, 0},
+{"checkin", json_timeline_ci, 0},
+{"ci", json_timeline_ci, -1},
+{"t", json_timeline_ticket, -1},
+{"ticket", json_timeline_ticket, 0},
+{"w", json_timeline_wiki, -1},
+{"wiki", json_timeline_wiki, 0},
+/* Last entry MUST have a NULL name. */
+{NULL,NULL,0}
+};
+
+
+/*
+** Implements the /json/timeline family of pages/commands. Far from
+** complete.
+**
+*/
+cson_value * json_page_timeline(){
+#if 0
+ /* The original timeline code does not require 'h' access,
+ but it arguably should. For JSON mode i think one could argue
+ that History permissions are required.
+ */
+ if(! g.perm.History && !g.perm.Read ){
+ json_set_err(FSL_JSON_E_DENIED, "Timeline requires 'h' or 'o' access.");
+ return NULL;
+ }
+#endif
+ return json_page_dispatch_helper(&JsonPageDefs_Timeline[0]);
+}
+
+/*
+** Create a temporary table suitable for storing timeline data.
+*/
+static void json_timeline_temp_table(void){
+ /* Field order MUST match that from json_timeline_query()!!! */
+ static const char zSql[] =
+ @ CREATE TEMP TABLE IF NOT EXISTS json_timeline(
+ @ sortId INTEGER PRIMARY KEY,
+ @ rid INTEGER,
+ @ uuid TEXT,
+ @ mtime INTEGER,
+ @ timestampString TEXT,
+ @ comment TEXT,
+ @ user TEXT,
+ @ isLeaf BOOLEAN,
+ @ bgColor TEXT,
+ @ eventType TEXT,
+ @ tags TEXT,
+ @ tagId INTEGER,
+ @ brief TEXT
+ @ )
+ ;
+ db_multi_exec(zSql);
+}
+
+/*
+** Return a pointer to a constant string that forms the basis
+** for a timeline query for the JSON interface.
+*/
+const char const * json_timeline_query(void){
+ /* Field order MUST match that from json_timeline_temp_table()!!! */
+ static const char zBaseSql[] =
+ @ SELECT
+ @ NULL,
+ @ blob.rid,
+ @ uuid,
+ @ strftime('%%s',event.mtime),
+ @ datetime(event.mtime,'utc'),
+ @ coalesce(ecomment, comment),
+ @ coalesce(euser, user),
+ @ blob.rid IN leaf,
+ @ bgcolor,
+ @ event.type,
+ @ (SELECT group_concat(substr(tagname,5), ',') FROM tag, tagxref
+ @ WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid
+ @ AND tagxref.rid=blob.rid AND tagxref.tagtype>0) as tags,
+ @ tagid as tagId,
+ @ brief as brief
+ @ FROM event JOIN blob
+ @ WHERE blob.rid=event.objid
+ ;
+ return zBaseSql;
+}
+
+/*
+** Internal helper to append query information if the
+** "tag" or "branch" request properties (CLI: --tag/--branch)
+** are set. Limits the query to a particular branch/tag.
+**
+** tag works like HTML mode's "t" option and branch works like HTML
+** mode's "r" option. They are very similar, but subtly different -
+** tag mode shows only entries with a given tag but branch mode can
+** also reveal some with "related" tags (meaning they were merged into
+** the requested branch).
+**
+** pSql is the target blob to append the query [subset]
+** to.
+**
+** Returns a positive value if it modifies pSql, 0 if it
+** does not. It returns a negative value if the tag
+** provided to the request was not found (pSql is not modified
+** in that case.
+**
+** If payload is not NULL then on success its "tag" or "branch"
+** property is set to the tag/branch name found in the request.
+**
+** Only one of "tag" or "branch" modes will work at a time, and if
+** both are specified, which one takes precedence is unspecified.
+*/
+static char json_timeline_add_tag_branch_clause(Blob *pSql,
+ cson_object * pPayload){
+ char const * zTag = NULL;
+ char const * zBranch = NULL;
+ int tagid = 0;
+ if(! g.perm.Read ){
+ return 0;
+ }
+ zTag = json_find_option_cstr("tag",NULL,NULL);
+ if(!zTag || !*zTag){
+ zBranch = json_find_option_cstr("branch",NULL,NULL);
+ if(!zBranch || !*zBranch){
+ return 0;
+ }
+ zTag = zBranch;
+ }
+ tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'",
+ zTag);
+ if(tagid<=0){
+ return -1;
+ }
+ if(pPayload){
+ cson_object_set( pPayload, zBranch ? "branch" : "tag", json_new_string(zTag) );
+ }
+ blob_appendf(pSql,
+ " AND ("
+ " EXISTS(SELECT 1 FROM tagxref"
+ " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)",
+ tagid);
+ if(zBranch){
+ /* from "r" flag code in page_timeline().*/
+ blob_appendf(pSql,
+ " OR EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=cid"
+ " WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)",
+ tagid);
+#if 0 /* from the undocumented "mionly" flag in page_timeline() */
+ blob_appendf(pSql,
+ " OR EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid"
+ " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)",
+ tagid);
+#endif
+ }
+ blob_append(pSql," ) ",3);
+ return 1;
+}
+/*
+** Helper for the timeline family of functions. Possibly appends 1
+** AND clause and an ORDER BY clause to pSql, depending on the state
+** of the "after" ("a") or "before" ("b") environment parameters.
+** This function gives "after" precedence over "before", and only
+** applies one of them.
+**
+** Returns -1 if it adds a "before" clause, 1 if it adds
+** an "after" clause, and 0 if adds only an order-by clause.
+*/
+static char json_timeline_add_time_clause(Blob *pSql){
+ char const * zAfter = NULL;
+ char const * zBefore = NULL;
+ int rc = 0;
+ zAfter = json_find_option_cstr("after",NULL,"a");
+ zBefore = zAfter ? NULL : json_find_option_cstr("before",NULL,"b");
+
+ if(zAfter&&*zAfter){
+ while( fossil_isspace(*zAfter) ) ++zAfter;
+ blob_appendf(pSql,
+ " AND event.mtime>=(SELECT julianday(%Q,'utc')) "
+ " ORDER BY event.mtime ASC ",
+ zAfter);
+ rc = 1;
+ }else if(zBefore && *zBefore){
+ while( fossil_isspace(*zBefore) ) ++zBefore;
+ blob_appendf(pSql,
+ " AND event.mtime<=(SELECT julianday(%Q,'utc')) "
+ " ORDER BY event.mtime DESC ",
+ zBefore);
+ rc = -1;
+ }else{
+ blob_append(pSql, " ORDER BY event.mtime DESC ", -1);
+ rc = 0;
+ }
+ return rc;
+}
+
+/*
+** Tries to figure out a timeline query length limit base on
+** environment parameters. If it can it returns that value,
+** else it returns some statically defined default value.
+**
+** Never returns a negative value. 0 means no limit.
+*/
+static int json_timeline_limit(){
+ static const int defaultLimit = 20;
+ int limit = -1;
+ if(!g.isHTTP){/* CLI mode */
+ char const * arg = find_option("limit","n",1);
+ if(arg && *arg){
+ limit = atoi(arg);
+ }
+ }
+ if( (limit<0) && fossil_has_json() ){
+ limit = json_getenv_int("limit",-1);
+ }
+ return (limit<0) ? defaultLimit : limit;
+}
+
+/*
+** Internal helper for the json_timeline_EVENTTYPE() family of
+** functions. zEventType must be one of (ci, w, t). pSql must be a
+** cleanly-initialized, empty Blob to store the sql in. If pPayload is
+** not NULL it is assumed to be the pending response payload. If
+** json_timeline_limit() returns non-0, this function adds a LIMIT
+** clause to the generated SQL.
+**
+** If pPayload is not NULL then this might add properties to pPayload,
+** reflecting options set in the request environment.
+**
+** Returns 0 on success. On error processing should not continue and
+** the returned value should be used as g.json.resultCode.
+*/
+static int json_timeline_setup_sql( char const * zEventType,
+ Blob * pSql,
+ cson_object * pPayload ){
+ int limit;
+ assert( zEventType && *zEventType && pSql );
+ json_timeline_temp_table();
+ blob_append(pSql, "INSERT OR IGNORE INTO json_timeline ", -1);
+ blob_append(pSql, json_timeline_query(), -1 );
+ blob_appendf(pSql, " AND event.type IN(%Q) ", zEventType);
+ if( json_timeline_add_tag_branch_clause(pSql, pPayload) < 0 ){
+ return FSL_JSON_E_INVALID_ARGS;
+ }
+ json_timeline_add_time_clause(pSql);
+ limit = json_timeline_limit();
+ if(limit>=0){
+ blob_appendf(pSql,"LIMIT %d ",limit);
+ }
+ if(pPayload){
+ cson_object_set(pPayload, "limit", json_new_int(limit));
+ }
+ return 0;
+}
+
+/*
+** If any files are associated with the given rid, a JSON array
+** containing information about them is returned (and is owned by the
+** caller). If no files are associated with it then NULL is returned.
+*/
+cson_value * json_get_changed_files(int rid){
+ cson_value * rowsV = NULL;
+ cson_array * rows = NULL;
+ Stmt q = empty_Stmt;
+ db_prepare(&q,
+#if 0
+ "SELECT (mlink.pid==0) AS isNew,"
+ " (mlink.fid==0) AS isDel,"
+ " filename.name AS name"
+ " FROM mlink, filename"
+ " WHERE mid=%d"
+ " AND pid!=fid"
+ " AND filename.fnid=mlink.fnid"
+ " ORDER BY 3 /*sort*/",
+#else
+ "SELECT (pid==0) AS isnew,"
+ " (fid==0) AS isdel,"
+ " (SELECT name FROM filename WHERE fnid=mlink.fnid) AS name,"
+ " (SELECT uuid FROM blob WHERE rid=fid) as uuid,"
+ " (SELECT uuid FROM blob WHERE rid=pid) as prevUuid"
+ " FROM mlink"
+ " WHERE mid=%d AND pid!=fid"
+ " ORDER BY name /*sort*/",
+#endif
+ rid
+ );
+ while( (SQLITE_ROW == db_step(&q)) ){
+ cson_value * rowV = cson_value_new_object();
+ cson_object * row = cson_value_get_object(rowV);
+ int const isNew = db_column_int(&q,0);
+ int const isDel = db_column_int(&q,1);
+ char * zDownload = NULL;
+ if(!rowsV){
+ rowsV = cson_value_new_array();
+ rows = cson_value_get_array(rowsV);
+ }
+ cson_array_append( rows, rowV );
+ cson_object_set(row, "name", json_new_string(db_column_text(&q,2)));
+ cson_object_set(row, "uuid", json_new_string(db_column_text(&q,3)));
+ if(!isNew){
+ cson_object_set(row, "prevUuid", json_new_string(db_column_text(&q,4)));
+ }
+ cson_object_set(row, "state",
+ json_new_string(isNew
+ ? "added"
+ : (isDel
+ ? "removed"
+ : "modified")));
+ zDownload = mprintf("/raw/%s?name=%s",
+ /* reminder: g.zBaseURL is of course not set for CLI mode. */
+ db_column_text(&q,2),
+ db_column_text(&q,3));
+ cson_object_set(row, "downloadPath", json_new_string(zDownload));
+ free(zDownload);
+ }
+ db_finalize(&q);
+ return rowsV;
+}
+
+static cson_value * json_timeline_branch(){
+ cson_value * pay = NULL;
+ Blob sql = empty_blob;
+ Stmt q = empty_Stmt;
+ if(!g.perm.Read){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'o' permissions.");
+ return NULL;
+ }
+ json_timeline_temp_table();
+ blob_append(&sql,
+ "SELECT"
+ " blob.rid AS rid,"
+ " uuid AS uuid,"
+ " datetime(event.mtime,'utc') as mtime,"
+ " coalesce(ecomment, comment) as comment,"
+ " coalesce(euser, user) as user,"
+ " blob.rid IN leaf as isLeaf,"
+ " bgcolor as bgColor"
+ " FROM event JOIN blob"
+ " WHERE blob.rid=event.objid",
+ -1);
+
+ blob_appendf(&sql,
+ " AND event.type='ci'"
+ " AND blob.rid IN (SELECT rid FROM tagxref"
+ " WHERE tagtype>0 AND tagid=%d AND srcid!=0)"
+ " ORDER BY event.mtime DESC",
+ TAG_BRANCH);
+ db_prepare(&q,"%s", blob_str(&sql));
+ blob_reset(&sql);
+ pay = json_stmt_to_array_of_obj(&q, NULL);
+ db_finalize(&q);
+ assert(NULL != pay);
+ if(pay){
+ /* get the array-form tags of each record. */
+ cson_string * tags = cson_new_string("tags",4);
+ cson_string * isLeaf = cson_new_string("isLeaf",6);
+ cson_value_add_reference( cson_string_value(tags) );
+ cson_value_add_reference( cson_string_value(isLeaf) );
+ cson_array * ar = cson_value_get_array(pay);
+ unsigned int i = 0;
+ unsigned int len = cson_array_length_get(ar);
+ for( ; i < len; ++i ){
+ cson_object * row = cson_value_get_object(cson_array_get(ar,i));
+ int rid = cson_value_get_integer(cson_object_get(row,"rid"));
+ if(row>0) {
+ cson_object_set_s(row, tags, json_tags_for_rid(rid,0));
+ cson_object_set_s(row, isLeaf, json_value_to_bool(cson_object_get(row,"isLeaf")));
+ }
+ }
+ cson_value_free( cson_string_value(tags) );
+ cson_value_free( cson_string_value(isLeaf) );
+ }
+
+ goto end;
+ error:
+ assert( 0 != g.json.resultCode );
+ cson_value_free(pay);
+
+ end:
+ return pay;
+}
+
+/*
+** Implementation of /json/timeline/ci.
+**
+** Still a few TODOs (like figuring out how to structure
+** inheritance info).
+*/
+static cson_value * json_timeline_ci(){
+ cson_value * payV = NULL;
+ cson_object * pay = NULL;
+ cson_value * tmp = NULL;
+ cson_value * listV = NULL;
+ cson_array * list = NULL;
+ int check = 0;
+ char showFiles = -1/*magic number*/;
+ Stmt q = empty_Stmt;
+ char warnRowToJsonFailed = 0;
+ char warnStringToArrayFailed = 0;
+ Blob sql = empty_blob;
+ if( !g.perm.Read ){
+ /* IMO this falls more under the category of g.perm.History, but
+ i'm following the original timeline impl here.
+ */
+ json_set_err( FSL_JSON_E_DENIED, "Checkin timeline requires 'o' access." );
+ return NULL;
+ }
+ showFiles = json_find_option_bool("files",NULL,"f",0);
+ payV = cson_value_new_object();
+ pay = cson_value_get_object(payV);
+ check = json_timeline_setup_sql( "ci", &sql, pay );
+ if(check){
+ json_set_err(check, "Query initialization failed.");
+ goto error;
+ }
+#define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \
+ json_set_err((cson_rc.AllocError==check) \
+ ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN,\
+ "Object property insertion failed"); \
+ goto error;\
+ } (void)0
+
+#if 0
+ /* only for testing! */
+ tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql)));
+ SET("timelineSql");
+#endif
+ db_multi_exec(blob_buffer(&sql));
+ blob_reset(&sql);
+ db_prepare(&q, "SELECT "
+ " rid AS rid"
+#if 0
+ " uuid AS uuid,"
+ " mtime AS timestamp,"
+# if 0
+ " timestampString AS timestampString,"
+# endif
+ " comment AS comment, "
+ " user AS user,"
+ " isLeaf AS isLeaf," /*FIXME: convert to JSON bool */
+ " bgColor AS bgColor," /* why always null? */
+ " eventType AS eventType"
+# if 0
+ " tags AS tags"
+ /*tagId is always null?*/
+ " tagId AS tagId"
+# endif
+#endif
+ " FROM json_timeline"
+ " ORDER BY rowid");
+ listV = cson_value_new_array();
+ list = cson_value_get_array(listV);
+ tmp = listV;
+ SET("timeline");
+ while( (SQLITE_ROW == db_step(&q) )){
+ /* convert each row into a JSON object...*/
+ int const rid = db_column_int(&q,0);
+ cson_value * rowV = json_artifact_for_ci(rid, showFiles);
+ cson_object * row = cson_value_get_object(rowV);
+ if(!row){
+ if( !warnRowToJsonFailed ){
+ warnRowToJsonFailed = 1;
+ json_warn( FSL_JSON_W_ROW_TO_JSON_FAILED,
+ "Could not convert at least one timeline result row to JSON." );
+ }
+ continue;
+ }
+ cson_array_append(list, rowV);
+ }
+#undef SET
+ goto ok;
+ error:
+ assert( 0 != g.json.resultCode );
+ cson_value_free(payV);
+ payV = NULL;
+ ok:
+ db_finalize(&q);
+ return payV;
+}
+
+/*
+** Implementation of /json/timeline/wiki.
+**
+*/
+cson_value * json_timeline_wiki(){
+ /* This code is 95% the same as json_timeline_ci(), by the way. */
+ cson_value * payV = NULL;
+ cson_object * pay = NULL;
+ cson_value * tmp = NULL;
+ cson_array * list = NULL;
+ int check = 0;
+ Stmt q = empty_Stmt;
+ Blob sql = empty_blob;
+ if( !g.perm.RdWiki && !g.perm.Read ){
+ json_set_err( FSL_JSON_E_DENIED, "Wiki timeline requires 'o' or 'j' access.");
+ return NULL;
+ }
+ payV = cson_value_new_object();
+ pay = cson_value_get_object(payV);
+ check = json_timeline_setup_sql( "w", &sql, pay );
+ if(check){
+ json_set_err(check, "Query initialization failed.");
+ goto error;
+ }
+
+#define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \
+ json_set_err((cson_rc.AllocError==check) \
+ ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN, \
+ "Object property insertion failed."); \
+ goto error;\
+ } (void)0
+#if 0
+ /* only for testing! */
+ tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql)));
+ SET("timelineSql");
+#endif
+ db_multi_exec(blob_buffer(&sql));
+ blob_reset(&sql);
+ db_prepare(&q, "SELECT rid AS rid,"
+ " uuid AS uuid,"
+ " mtime AS timestamp,"
+#if 0
+ " timestampString AS timestampString,"
+#endif
+ " comment AS comment, "
+ " user AS user,"
+ " eventType AS eventType"
+#if 0
+ /* can wiki pages have tags? */
+ " tags AS tags," /*FIXME: split this into
+ a JSON array*/
+ " tagId AS tagId,"
+#endif
+ " FROM json_timeline"
+ " ORDER BY rowid",
+ -1);
+ list = cson_new_array();
+ tmp = cson_array_value(list);
+ SET("timeline");
+ json_stmt_to_array_of_obj(&q, list);
+#undef SET
+ goto ok;
+ error:
+ assert( 0 != g.json.resultCode );
+ cson_value_free(payV);
+ payV = NULL;
+ ok:
+ db_finalize(&q);
+ blob_reset(&sql);
+ return payV;
+}
+
+/*
+** Implementation of /json/timeline/ticket.
+**
+*/
+static cson_value * json_timeline_ticket(){
+ /* This code is 95% the same as json_timeline_ci(), by the way. */
+ cson_value * payV = NULL;
+ cson_object * pay = NULL;
+ cson_value * tmp = NULL;
+ cson_value * listV = NULL;
+ cson_array * list = NULL;
+ int check = 0;
+ Stmt q = empty_Stmt;
+ Blob sql = empty_blob;
+ if( !g.perm.RdTkt && !g.perm.Read ){
+ json_set_err(FSL_JSON_E_DENIED, "Ticket timeline requires 'o' or 'r' access.");
+ return NULL;
+ }
+ payV = cson_value_new_object();
+ pay = cson_value_get_object(payV);
+ check = json_timeline_setup_sql( "t", &sql, pay );
+ if(check){
+ json_set_err(check, "Query initialization failed.");
+ goto error;
+ }
+
+ db_multi_exec(blob_buffer(&sql));
+#define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \
+ json_set_err((cson_rc.AllocError==check) \
+ ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN, \
+ "Object property insertion failed."); \
+ goto error;\
+ } (void)0
+
+#if 0
+ /* only for testing! */
+ tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql)));
+ SET("timelineSql");
+#endif
+
+ blob_reset(&sql);
+ /*
+ REMINDER/FIXME(?): we have both uuid (the change uuid?) and
+ ticketUuid (the actual ticket). This is different from the wiki
+ timeline, where we only have the wiki page uuid.
+ */
+ db_prepare(&q, "SELECT rid AS rid,"
+ " uuid AS uuid,"
+ " mtime AS timestamp,"
+#if 0
+ " timestampString AS timestampString,"
+#endif
+ " user AS user,"
+ " eventType AS eventType,"
+ " comment AS comment,"
+ " brief AS briefComment"
+ " FROM json_timeline"
+ " ORDER BY rowid",
+ -1);
+ listV = cson_value_new_array();
+ list = cson_value_get_array(listV);
+ tmp = listV;
+ SET("timeline");
+ while( (SQLITE_ROW == db_step(&q) )){
+ /* convert each row into a JSON object...*/
+ int rc;
+ int const rid = db_column_int(&q,0);
+ Manifest * pMan = NULL;
+ cson_value * rowV = cson_sqlite3_row_to_object(q.pStmt);
+ cson_object * row = cson_value_get_object(rowV);
+ if(!row){
+ json_warn( FSL_JSON_W_ROW_TO_JSON_FAILED,
+ "Could not convert at least one timeline result row to JSON." );
+ continue;
+ }
+ pMan = manifest_get(rid, CFTYPE_TICKET);
+ assert( pMan && "Manifest is NULL!?!" );
+ if( pMan ){
+ /* FIXME: certainly there's a more efficient way for use to get
+ the ticket UUIDs?
+ */
+ cson_object_set(row,"ticketUuid",json_new_string(pMan->zTicketUuid));
+ manifest_destroy(pMan);
+ }
+ rc = cson_array_append( list, rowV );
+ if( 0 != rc ){
+ cson_value_free(rowV);
+ g.json.resultCode = (cson_rc.AllocError==rc)
+ ? FSL_JSON_E_ALLOC
+ : FSL_JSON_E_UNKNOWN;
+ goto error;
+ }
+ }
+#undef SET
+ goto ok;
+ error:
+ assert( 0 != g.json.resultCode );
+ cson_value_free(payV);
+ payV = NULL;
+ ok:
+ blob_reset(&sql);
+ db_finalize(&q);
+ return payV;
+}
+
ADDED src/json_user.c
Index: src/json_user.c
==================================================================
--- /dev/null
+++ src/json_user.c
@@ -0,0 +1,285 @@
+/*
+** Copyright (c) 2011 D. Richard Hipp
+**
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the Simplified BSD License (also
+** known as the "2-Clause License" or "FreeBSD License".)
+**
+** This program is distributed in the hope that it will be useful,
+** but without any warranty; without even the implied warranty of
+** merchantability or fitness for a particular purpose.
+**
+** Author contact information:
+** drh@hwaci.com
+** http://www.hwaci.com/drh/
+**
+*/
+#include "VERSION.h"
+#include "config.h"
+#include "json_user.h"
+
+#if INTERFACE
+#include "json_detail.h"
+#endif
+
+static cson_value * json_user_get();
+static cson_value * json_user_list();
+static cson_value * json_user_save();
+#if 0
+static cson_value * json_user_create();
+
+#endif
+
+/*
+** Mapping of /json/user/XXX commands/paths to callbacks.
+*/
+static const JsonPageDef JsonPageDefs_User[] = {
+{"create", json_page_nyi, 1},
+{"save", json_user_save, 1},
+{"get", json_user_get, 0},
+{"list", json_user_list, 0},
+/* Last entry MUST have a NULL name. */
+{NULL,NULL,0}
+};
+
+
+/*
+** Implements the /json/user family of pages/commands.
+**
+*/
+cson_value * json_page_user(){
+ return json_page_dispatch_helper(&JsonPageDefs_User[0]);
+}
+
+
+/*
+** Impl of /json/user/list. Requires admin rights.
+*/
+static cson_value * json_user_list(){
+ cson_value * payV = NULL;
+ Stmt q;
+ if(!g.perm.Admin){
+ g.json.resultCode = FSL_JSON_E_DENIED;
+ return NULL;
+ }
+ db_prepare(&q,"SELECT uid AS uid,"
+ " login AS name,"
+ " cap AS capabilities,"
+ " info AS info,"
+ " mtime AS mtime"
+ " FROM user ORDER BY login");
+ payV = json_stmt_to_array_of_obj(&q, NULL);
+ db_finalize(&q);
+ if(NULL == payV){
+ json_set_err(FSL_JSON_E_UNKNOWN,
+ "Could not convert user list to JSON.");
+ }
+ return payV;
+}
+
+/*
+** Impl of /json/user/get. Requires admin rights.
+*/
+static cson_value * json_user_get(){
+ cson_value * payV = NULL;
+ char const * pUser = NULL;
+ Stmt q;
+ if(!g.perm.Admin){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'a' privileges.");
+ return NULL;
+ }
+ pUser = json_command_arg(g.json.dispatchDepth+1);
+ if( g.isHTTP && (!pUser || !*pUser) ){
+ pUser = json_getenv_cstr("name")
+ /* ACHTUNG: fossil apparently internally sets name=user/get/XYZ
+ if we pass the name as part of the path, which is why we check
+ with json_command_path() before trying to get("name").
+ */;
+ }
+ if(!pUser || !*pUser){
+ json_set_err(FSL_JSON_E_MISSING_ARGS,"Missing 'name' property.");
+ return NULL;
+ }
+ db_prepare(&q,"SELECT uid AS uid,"
+ " login AS name,"
+ " cap AS capabilities,"
+ " info AS info,"
+ " mtime AS mtime"
+ " FROM user"
+ " WHERE login=%Q",
+ pUser);
+ if( (SQLITE_ROW == db_step(&q)) ){
+ payV = cson_sqlite3_row_to_object(q.pStmt);
+ if(!payV){
+ json_set_err(FSL_JSON_E_UNKNOWN,"Could not convert user row to JSON.");
+ }
+ }else{
+ json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND,"User not found.");
+ }
+ db_finalize(&q);
+ return payV;
+}
+
+/*
+** Expects pUser to contain fossil user fields in JSON form: name,
+** uid, info, capabilities, password.
+**
+** At least one of (name, uid) must be included. All others are
+** optional and their db fields will not be updated if those fields
+** are not included in pUser.
+**
+** If uid is specified then name may refer to a _new_ name
+** for a user, otherwise the name must refer to an existing user.
+**
+** On error g.json's error state is set one of the FSL_JSON_E_xxx
+** values from FossilJsonCodes is returned.
+**
+** On success the db record for the given user is updated.
+**
+** Requires either Admin, Setup, or Password access. Non-admin/setup
+** users can only change their own information.
+**
+** TODOs:
+**
+** - Admin non-Setup users cannot change the information for Setup
+** users.
+**
+*/
+int json_user_update_from_json( cson_object const * pUser ){
+#define CSTR(X) cson_string_cstr(cson_value_get_string( cson_object_get(pUser, X ) ))
+ char const * zName = CSTR("name");
+ char const * zNameOrig = zName;
+ char * zNameFree = NULL;
+ char const * zInfo = CSTR("info");
+ char const * zCap = CSTR("capabilities");
+ char const * zPW = CSTR("password");
+ cson_value const * forceLogout = cson_object_get(pUser, "forceLogout");
+ int gotFields = 0;
+#undef CSTR
+ cson_int_t uid = cson_value_get_integer( cson_object_get(pUser, "uid") );
+ Blob sql = empty_blob;
+ Stmt q = empty_Stmt;
+
+ if(!g.perm.Admin && !g.perm.Setup && !g.perm.Password){
+ return json_set_err( FSL_JSON_E_DENIED,
+ "Password change requires 'a', 's', "
+ "or 'p' permissions.");
+ }
+
+ if(uid<=0 && (!zName||!*zName)){
+ return json_set_err(FSL_JSON_E_MISSING_ARGS,
+ "One of 'uid' or 'name' is required.");
+ }else if(uid>0){
+ zNameFree = db_text(NULL, "SELECT login FROM user WHERE uid=%d",uid);
+ if(!zNameFree){
+ return json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND,
+ "No login found for uid %d.", uid);
+ }
+ zName = zNameFree;
+ }else{
+ uid = db_int(0,"SELECT uid FROM user WHERE login=%Q",
+ zName);
+ if(uid<=0){
+ return json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND,
+ "No login found for user [%s].", zName);
+ }
+ }
+ /* Maintenance note: all error-returns from here on out should go
+ via goto error in order to clean up.
+ */
+
+ if(uid != g.userUid){
+ /*
+ TODO: do not allow an admin user to modify a setup user
+ unless the admin is also a setup user. setup.c uses
+ that logic.
+ */
+ if(!g.perm.Admin && !g.perm.Setup){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Changing another user's data requires "
+ "'a' or 's' privileges.");
+ }
+ }
+
+ blob_append(&sql, "UPDATE USER SET",-1 );
+ blob_append(&sql, " mtime=cast(strftime('%s') AS INTEGER)", -1);
+
+ if((uid>0) && zName
+ && zNameOrig && (zName != zNameOrig)
+ && (0!=strcmp(zNameOrig,zName))){
+ /* Only change the name if the uid is explicitly set and name
+ would actually change. */
+ if(!g.perm.Admin && !g.perm.Setup) {
+ json_set_err( FSL_JSON_E_DENIED,
+ "Modifying user names requires 'a' or 's' privileges.");
+ goto error;
+ }
+ blob_appendf(&sql, ", login=%Q", zNameOrig);
+ ++gotFields;
+ }
+
+ if( zCap ){
+ blob_appendf(&sql, ", cap=%Q", zCap);
+ ++gotFields;
+ }
+
+ if( zPW ){
+ char * zPWHash = NULL;
+ ++gotFields;
+ zPWHash = sha1_shared_secret(zPW, zName, NULL);
+ blob_appendf(&sql, ", pw=%Q", zPWHash);
+ free(zPWHash);
+ }
+
+ if( zInfo ){
+ blob_appendf(&sql, ", info=%Q", zInfo);
+ ++gotFields;
+ }
+
+ if((g.perm.Admin || g.perm.Setup)
+ && forceLogout && cson_value_get_bool(forceLogout)){
+ blob_append(&sql, ", cookie=NULL, cexpire=NULL", -1);
+ ++gotFields;
+ }
+
+ if(!gotFields){
+ json_set_err( FSL_JSON_E_MISSING_ARGS,
+ "Required user data are missing.");
+ goto error;
+ }
+ assert(uid>0);
+ blob_appendf(&sql, " WHERE uid=%d", uid);
+ free( zNameFree );
+ /*puts(blob_str(&sql));*/
+ db_prepare(&q, "%s", blob_str(&sql));
+ blob_reset(&sql);
+ db_exec(&q);
+ db_finalize(&q);
+ return 0;
+
+ error:
+ assert(0 != g.json.resultCode);
+ free(zNameFree);
+ blob_reset(&sql);
+ return g.json.resultCode;
+}
+
+
+/*
+** Don't use - not yet finished.
+*/
+static cson_value * json_user_save(){
+ if( !g.perm.Admin || !g.perm.Setup ){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'a' or 's' privileges.");
+ }
+ if(! g.json.reqPayload.o ){
+ json_set_err(FSL_JSON_E_MISSING_ARGS,
+ "User data must be contained in the request payload.");
+ return NULL;
+
+ }
+ json_user_update_from_json( g.json.reqPayload.o );
+ return NULL;
+}
ADDED src/json_wiki.c
Index: src/json_wiki.c
==================================================================
--- /dev/null
+++ src/json_wiki.c
@@ -0,0 +1,365 @@
+/*
+** Copyright (c) 2011 D. Richard Hipp
+**
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the Simplified BSD License (also
+** known as the "2-Clause License" or "FreeBSD License".)
+**
+** This program is distributed in the hope that it will be useful,
+** but without any warranty; without even the implied warranty of
+** merchantability or fitness for a particular purpose.
+**
+** Author contact information:
+** drh@hwaci.com
+** http://www.hwaci.com/drh/
+**
+*/
+#include "VERSION.h"
+#include "config.h"
+#include "json_wiki.h"
+
+#if INTERFACE
+#include "json_detail.h"
+#endif
+
+static cson_value * json_wiki_create();
+static cson_value * json_wiki_get();
+static cson_value * json_wiki_list();
+static cson_value * json_wiki_save();
+
+/*
+** Mapping of /json/wiki/XXX commands/paths to callbacks.
+*/
+static const JsonPageDef JsonPageDefs_Wiki[] = {
+{"create", json_wiki_create, 1},
+{"get", json_wiki_get, 0},
+{"list", json_wiki_list, 0},
+{"save", json_wiki_save, 1},
+{"timeline", json_timeline_wiki,0},
+/* Last entry MUST have a NULL name. */
+{NULL,NULL,0}
+};
+
+
+/*
+** Implements the /json/wiki family of pages/commands.
+**
+*/
+cson_value * json_page_wiki(){
+ return json_page_dispatch_helper(&JsonPageDefs_Wiki[0]);
+}
+
+
+/*
+** Loads the given wiki page and creates a JSON object representation
+** of it. If the page is not found then NULL is returned. If doParse
+** is true then the page content is HTML-ized using fossil's
+** conventional wiki format, else it is not parsed.
+**
+** The returned value, if not NULL, is-a JSON Object owned by the
+** caller.
+*/
+cson_value * json_get_wiki_page_by_name(char const * zPageName, char doParse){
+ int rid;
+ Manifest *pWiki = 0;
+ char const * zBody = NULL;
+ char const * zFormat = NULL;
+ char * zUuid = NULL;
+ Stmt q;
+ db_prepare(&q,
+ "SELECT x.rid, b.uuid FROM tag t, tagxref x, blob b"
+ " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q' "
+ " AND b.rid=x.rid"
+ " ORDER BY x.mtime DESC LIMIT 1",
+ zPageName
+ );
+ if( (SQLITE_ROW != db_step(&q)) ){
+ db_finalize(&q);
+ return NULL;
+ }
+ rid = db_column_int(&q,0);
+ zUuid = db_column_malloc(&q,1);
+ db_finalize(&q);
+ if( (pWiki = manifest_get(rid, CFTYPE_WIKI))!=0 ){
+ zBody = pWiki->zWiki;
+ }
+
+ {
+ unsigned int len;
+ cson_object * pay = cson_new_object();
+ cson_object_set(pay,"name",json_new_string(zPageName));
+ cson_object_set(pay,"uuid",json_new_string(zUuid));
+ free(zUuid);
+ zUuid = NULL;
+ /*cson_object_set(pay,"rid",json_new_int((cson_int_t)rid));*/
+ cson_object_set(pay,"lastSavedBy",json_new_string(pWiki->zUser));
+ cson_object_set(pay,FossilJsonKeys.timestamp, json_julian_to_timestamp(pWiki->rDate));
+ cson_object_set(pay,"contentFormat",json_new_string(zFormat));
+ if( doParse ){
+ Blob content = empty_blob;
+ Blob raw = empty_blob;
+ blob_append(&raw,zBody,-1);
+ wiki_convert(&raw,&content,0);
+ len = strlen(zBody);
+ len = (unsigned int)blob_size(&content);
+ cson_object_set(pay,"contentLength",json_new_int((cson_int_t)len));
+ cson_object_set(pay,"content",
+ cson_value_new_string(blob_buffer(&content),len));
+ blob_reset(&content);
+ blob_reset(&raw);
+ }else{
+ len = zBody ? strlen(zBody) : 0;
+ cson_object_set(pay,"contentLength",json_new_int((cson_int_t)len));
+ cson_object_set(pay,"content",cson_value_new_string(zBody,len));
+ }
+ /*TODO: add 'T' (tag) fields*/
+ /*TODO: add the 'A' card (file attachment) entries?*/
+ manifest_destroy(pWiki);
+ return cson_object_value(pay);
+ }
+}
+
+
+/*
+** Searches for a wiki page with the given rid. If found it behaves
+** like json_get_wiki_page_by_name(pageName, doParse), else it returns
+** NULL.
+*/
+cson_value * json_get_wiki_page_by_rid(int rid, char doParse){
+ char * zPageName = NULL;
+ cson_value * rc = NULL;
+ zPageName = db_text(NULL,
+ "SELECT substr(t.tagname,6) AS name "
+ " FROM tag t, tagxref x, blob b "
+ " WHERE b.rid=%d "
+ " AND t.tagname GLOB 'wiki-*'"
+ " AND x.tagid=t.tagid AND b.rid=x.rid ",
+ rid);
+ if( zPageName ){
+ rc = json_get_wiki_page_by_name(zPageName, doParse);
+ free(zPageName);
+ }
+ return rc;
+}
+
+/*
+** Implementation of /json/wiki/get.
+**
+*/
+static cson_value * json_wiki_get(){
+ int rid;
+ Manifest *pWiki = 0;
+ char const * zBody = NULL;
+ char const * zPageName;
+ char const * zFormat = NULL;
+ char * zUuid = NULL;
+ Stmt q;
+ if( !g.perm.RdWiki && !g.perm.Read ){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'o' or 'j' access.");
+ return NULL;
+ }
+ zPageName = json_find_option_cstr("name",NULL,"n")
+ /* Damn... fossil automatically sets name to the PATH
+ part after /json, so we need a workaround down here....
+ */
+ ;
+ if( zPageName && (NULL != strstr(zPageName, "/"))){
+ /* Assume that we picked up a path remnant. */
+ zPageName = NULL;
+ }
+ if( !zPageName && cson_value_is_string(g.json.reqPayload.v) ){
+ zPageName = cson_string_cstr(cson_value_get_string(g.json.reqPayload.v));
+ }
+ if(!zPageName){
+ zPageName = json_command_arg(g.json.dispatchDepth+1);
+ }
+ if(!zPageName||!*zPageName){
+ json_set_err(FSL_JSON_E_MISSING_ARGS,
+ "'name' argument is missing.");
+ return NULL;
+ }
+
+ zFormat = json_find_option_cstr("format",NULL,"f");
+ if(!zFormat || !*zFormat){
+ zFormat = "raw";
+ }
+ if( 'r' != *zFormat ){
+ zFormat = "html";
+ }
+ return json_get_wiki_page_by_name(zPageName, 'h'==*zFormat);
+}
+
+/*
+** Internal impl of /wiki/save and /wiki/create. If createMode is 0
+** and the page already exists then a
+** FSL_JSON_E_RESOURCE_ALREADY_EXISTS error is triggered. If
+** createMode is false then the FSL_JSON_E_RESOURCE_NOT_FOUND is
+** triggered if the page does not already exists.
+**
+** Note that the error triggered when createMode==0 and no such page
+** exists is rather arbitrary - we could just as well create the entry
+** here if it doesn't already exist. With that, save/create would
+** become one operation. That said, i expect there are people who
+** would categorize such behaviour as "being too clever" or "doing too
+** much automatically" (and i would likely agree with them).
+**
+** If allowCreateIfExists is true then this function will allow a new
+** page to be created even if createMode is false.
+*/
+static cson_value * json_wiki_create_or_save(char createMode,
+ char allowCreateIfExists){
+ Blob content = empty_blob;
+ cson_value * nameV;
+ cson_value * contentV;
+ cson_value * emptyContent = NULL;
+ cson_value * payV = NULL;
+ cson_object * pay = NULL;
+ cson_string const * jstr = NULL;
+ char const * zContent;
+ char const * zBody = NULL;
+ char const * zPageName;
+ unsigned int contentLen = 0;
+ int rid;
+ if( (createMode && !g.perm.NewWiki)
+ || (!createMode && !g.perm.WrWiki)){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires '%c' permissions.",
+ (createMode ? 'f' : 'k'));
+ return NULL;
+ }
+ nameV = json_req_payload_get("name");
+ if(!nameV){
+ json_set_err( FSL_JSON_E_MISSING_ARGS,
+ "'name' parameter is missing.");
+ return NULL;
+ }
+ zPageName = cson_string_cstr(cson_value_get_string(nameV));
+ rid = db_int(0,
+ "SELECT x.rid FROM tag t, tagxref x"
+ " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
+ " ORDER BY x.mtime DESC LIMIT 1",
+ zPageName
+ );
+
+ if(rid){
+ if(createMode){
+ json_set_err(FSL_JSON_E_RESOURCE_ALREADY_EXISTS,
+ "Wiki page '%s' already exists.",
+ zPageName);
+ goto error;
+ }
+ }else if(!allowCreateIfExists){
+ json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND,
+ "Wiki page '%s' not found.",
+ zPageName);
+ goto error;
+ }
+
+ contentV = json_req_payload_get("content");
+ if( !contentV ){
+ if( createMode || (!rid && allowCreateIfExists) ){
+ contentV = emptyContent = cson_value_new_string("",0);
+ }else{
+ json_set_err(FSL_JSON_E_MISSING_ARGS,
+ "'content' parameter is missing.");
+ goto error;
+ }
+ }
+ if( !cson_value_is_string(nameV)
+ || !cson_value_is_string(contentV)){
+ json_set_err(FSL_JSON_E_INVALID_ARGS,
+ "'name' and 'content' parameters must be strings.");
+ goto error;
+ }
+ jstr = cson_value_get_string(contentV);
+ contentLen = (int)cson_string_length_bytes(jstr);
+ if(contentLen){
+ blob_append(&content, cson_string_cstr(jstr),contentLen);
+ }
+ wiki_cmd_commit(zPageName, 0==rid, &content);
+ blob_reset(&content);
+
+ payV = cson_value_new_object();
+ pay = cson_value_get_object(payV);
+ cson_object_set( pay, "name", nameV );
+ cson_object_set( pay, FossilJsonKeys.timestamp,
+ json_new_timestamp(-1) );
+
+ goto ok;
+ error:
+ assert( 0 != g.json.resultCode );
+ cson_value_free(payV);
+ payV = NULL;
+ ok:
+ if( emptyContent ){
+ /* We have some potentially tricky memory ownership
+ here, which is why we handle emptyContent separately.
+
+ This is, in fact, overkill because cson_value_new_string("",0)
+ actually returns a shared singleton instance (i.e. doesn't
+ allocate), but that is a cson implementation detail which i
+ don't want leaking into this code...
+ */
+ cson_value_free(emptyContent);
+ }
+ return payV;
+
+}
+
+/*
+** Implementation of /json/wiki/create.
+*/
+static cson_value * json_wiki_create(){
+ return json_wiki_create_or_save(1,0);
+}
+
+/*
+** Implementation of /json/wiki/save.
+*/
+static cson_value * json_wiki_save(){
+ char const createIfNotExists = json_getenv_bool("createIfNotExists",0);
+ return json_wiki_create_or_save(0,createIfNotExists);
+}
+
+/*
+** Implementation of /json/wiki/list.
+*/
+static cson_value * json_wiki_list(){
+ cson_value * listV = NULL;
+ cson_array * list = NULL;
+ Stmt q;
+ if( !g.perm.RdWiki && !g.perm.Read ){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'j' or 'o' permissions.");
+ return NULL;
+ }
+ db_prepare(&q,"SELECT"
+ " substr(tagname,6) as name"
+ " FROM tag WHERE tagname GLOB 'wiki-*'"
+ " ORDER BY lower(name)");
+ listV = cson_value_new_array();
+ list = cson_value_get_array(listV);
+ while( SQLITE_ROW == db_step(&q) ){
+ cson_value * v = cson_sqlite3_column_to_value(q.pStmt,0);
+ if(!v){
+ json_set_err(FSL_JSON_E_UNKNOWN,
+ "Could not convert wiki name column to JSON.");
+ goto error;
+ }else if( 0 != cson_array_append( list, v ) ){
+ cson_value_free(v);
+ json_set_err(FSL_JSON_E_ALLOC,"Could not append wiki page name to array.")
+ /* OOM (or maybe numeric overflow) are the only realistic
+ error codes for that particular failure.*/;
+ goto error;
+ }
+ }
+ goto end;
+ error:
+ assert(0 != g.json.resultCode);
+ cson_value_free(listV);
+ listV = NULL;
+ end:
+ db_finalize(&q);
+ return listV;
+}
Index: src/login.c
==================================================================
--- src/login.c
+++ src/login.c
@@ -84,11 +84,11 @@
**
** The login cookie name is always of the form: fossil-XXXXXXXXXXXXXXXX
** where the Xs are the first 16 characters of the login-group-code or
** of the project-code if we are not a member of any login-group.
*/
-static char *login_cookie_name(void){
+char *login_cookie_name(void){
static char *zCookieName = 0;
if( zCookieName==0 ){
zCookieName = db_text(0,
"SELECT 'fossil-' || substr(value,1,16)"
" FROM config"
@@ -141,24 +141,26 @@
/*
** Check to see if the anonymous login is valid. If it is valid, return
** the userid of the anonymous user.
+**
+** The zCS parameter is the "captcha seed" used for a specific
+** anonymous login request.
*/
-static int isValidAnonymousLogin(
+int login_is_valid_anonymous(
const char *zUsername, /* The username. Must be "anonymous" */
- const char *zPassword /* The supplied password */
+ const char *zPassword, /* The supplied password */
+ const char *zCS /* The captcha seed value */
){
- const char *zCS; /* The captcha seed value */
const char *zPw; /* The correct password shown in the captcha */
int uid; /* The user ID of anonymous */
if( zUsername==0 ) return 0;
- if( zPassword==0 ) return 0;
- if( fossil_strcmp(zUsername,"anonymous")!=0 ) return 0;
- zCS = P("cs"); /* The "cs" parameter is the "captcha seed" */
- if( zCS==0 ) return 0;
+ else if( zPassword==0 ) return 0;
+ else if( zCS==0 ) return 0;
+ else if( fossil_strcmp(zUsername,"anonymous")!=0 ) return 0;
zPw = captcha_decode((unsigned int)atoi(zCS));
if( fossil_stricmp(zPw, zPassword)!=0 ) return 0;
uid = db_int(0, "SELECT uid FROM user WHERE login='anonymous'"
" AND length(pw)>0 AND length(cap)>0");
return uid;
@@ -192,10 +194,163 @@
"INSERT INTO accesslog(uname,ipaddr,success,mtime)"
"VALUES(%Q,%Q,%d,julianday('now'));",
zUsername, zIpAddr, bSuccess
);
}
+
+/*
+** Searches for the user ID matching the given name and password.
+** On success it returns a positive value. On error it returns 0.
+** On serious (DB-level) error it will probably exit.
+**
+** zPassword may be either the plain-text form or the encrypted
+** form of the user's password.
+*/
+int login_search_uid(char const *zUsername, char const *zPasswd){
+ char * zSha1Pw = sha1_shared_secret(zPasswd, zUsername, 0);
+ int const uid =
+ db_int(0,
+ "SELECT uid FROM user"
+ " WHERE login=%Q"
+ " AND length(cap)>0 AND length(pw)>0"
+ " AND login NOT IN ('anonymous','nobody','developer','reader')"
+ " AND (pw=%Q OR pw=%Q)",
+ zUsername, zPasswd, zSha1Pw
+ );
+ free(zSha1Pw);
+ return uid;
+}
+
+/*
+** Generates a login cookie value for a non-anonymous user.
+**
+** The zHash parameter must be a random value which must be
+** subsequently stored in user.cookie for later validation.
+**
+** The returned memory should be free()d after use.
+*/
+char * login_gen_user_cookie_value(char const *zUsername, char const * zHash){
+ char * zProjCode = db_get("project-code",NULL);
+ char *zCode = abbreviated_project_code(zProjCode);
+ free(zProjCode);
+ assert((zUsername && *zUsername) && "Invalid user data.");
+ return mprintf("%s/%z/%s", zHash, zCode, zUsername);
+}
+
+/*
+** Generates a login cookie for NON-ANONYMOUS users. Note that this
+** function "could" figure out the uid by itself but it currently
+** doesn't because the code which calls this already has the uid.
+**
+** This function also updates the user.cookie, user.ipaddr,
+** and user.cexpire fields for the given user.
+**
+** If zDest is not NULL then the generated cookie is copied to
+** *zDdest and ownership is transfered to the caller (who should
+** eventually pass it to free()).
+*/
+void login_set_user_cookie(
+ char const * zUsername, /* User's name */
+ int uid, /* User's ID */
+ char ** zDest /* Optional: store generated cookie value. */
+){
+ const char *zCookieName = login_cookie_name();
+ const char *zExpire = db_get("cookie-expire","8766");
+ int expires = atoi(zExpire)*3600;
+ char *zHash;
+ char *zCookie;
+ char const * zIpAddr = PD("REMOTE_ADDR","nil"); /* Complete IP address for logging */
+ char * zRemoteAddr = ipPrefix(zIpAddr); /* Abbreviated IP address */
+ assert((zUsername && *zUsername) && (uid > 0) && "Invalid user data.");
+ zHash = db_text(0, "SELECT hex(randomblob(25))");
+ zCookie = login_gen_user_cookie_value(zUsername, zHash);
+ cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires);
+ record_login_attempt(zUsername, zIpAddr, 1);
+ db_multi_exec(
+ "UPDATE user SET cookie=%Q, ipaddr=%Q, "
+ " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
+ zHash, zRemoteAddr, expires, uid
+ );
+ free(zRemoteAddr);
+ free(zHash);
+ if( zDest ){
+ *zDest = zCookie;
+ }else{
+ free(zCookie);
+ }
+}
+
+/* Sets a cookie for an anonymous user login, which looks like this:
+**
+** HASH/TIME/anonymous
+**
+** Where HASH is the sha1sum of TIME/IPADDR/SECRET, in which IPADDR
+** is the abbreviated IP address and SECRET is captcha-secret.
+**
+** If either zIpAddr or zRemoteAddr are NULL then REMOTE_ADDR
+** is used.
+**
+** If zCookieDest is not NULL then the generated cookie is assigned to
+** *zCookieDest and the caller must eventually free() it.
+*/
+void login_set_anon_cookie(char const * zIpAddr, char ** zCookieDest ){
+ char const *zNow; /* Current time (julian day number) */
+ char *zCookie; /* The login cookie */
+ char const *zCookieName; /* Name of the login cookie */
+ Blob b; /* Blob used during cookie construction */
+ char * zRemoteAddr; /* Abbreviated IP address */
+ if(!zIpAddr){
+ zIpAddr = PD("REMOTE_ADDR","nil");
+ }
+ zRemoteAddr = ipPrefix(zIpAddr);
+ zCookieName = login_cookie_name();
+ zNow = db_text("0", "SELECT julianday('now')");
+ assert( zCookieName && zRemoteAddr && zIpAddr && zNow );
+ blob_init(&b, zNow, -1);
+ blob_appendf(&b, "/%s/%s", zRemoteAddr, db_get("captcha-secret",""));
+ sha1sum_blob(&b, &b);
+ zCookie = mprintf("%s/%s/anonymous", blob_buffer(&b), zNow);
+ blob_reset(&b);
+ cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), 6*3600);
+ if( zCookieDest ){
+ *zCookieDest = zCookie;
+ }else{
+ free(zCookie);
+ }
+
+}
+
+/*
+** "Unsets" the login cookie (insofar as cookies can be unset) and
+** clears the current user's (g.userUid) login information from the
+** user table. Sets: user.cookie, user.ipaddr, user.cexpire.
+**
+** We could/should arguably clear out g.userUid and g.perm here, but
+** we don't currently do not.
+**
+** This is a no-op if g.userUid is 0.
+*/
+void login_clear_login_data(){
+ if(!g.userUid){
+ return;
+ }else{
+ char const * cookie = login_cookie_name();
+ /* To logout, change the cookie value to an empty string */
+ cgi_set_cookie(cookie, "",
+ login_cookie_path(), -86400);
+ db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, "
+ " cexpire=0 WHERE uid=%d"
+ " AND login NOT IN ('anonymous','guest',"
+ " 'developer','reader')", g.userUid);
+ cgi_replace_parameter(cookie, NULL)
+ /* At the time of this writing, cgi_replace_parameter() was
+ ** "NULL-value-safe", and i'm hoping the NULL doesn't cause any
+ ** downstream problems here. We could alternately use "" here.
+ */
+ ;
+ }
+}
/*
** SQL function for constant time comparison of two values.
** Sets result to 0 if two values are equal.
*/
@@ -241,22 +396,19 @@
int anonFlag;
char *zErrMsg = "";
int uid; /* User id loged in user */
char *zSha1Pw;
const char *zIpAddr; /* IP address of requestor */
- char *zRemoteAddr; /* Abbreviated IP address of requestor */
login_check_credentials();
sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0,
constant_time_cmp_function, 0, 0);
zUsername = P("u");
zPasswd = P("p");
anonFlag = P("anon")!=0;
if( P("out")!=0 ){
- /* To logout, change the cookie value to an empty string */
- const char *zCookieName = login_cookie_name();
- cgi_set_cookie(zCookieName, "", login_cookie_path(), -86400);
+ login_clear_login_data();
redirect_to_g();
}
if( g.perm.Password && zPasswd
&& (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0
){
@@ -304,50 +456,20 @@
return;
}
}
}
zIpAddr = PD("REMOTE_ADDR","nil"); /* Complete IP address for logging */
- zRemoteAddr = ipPrefix(zIpAddr); /* Abbreviated IP address */
- uid = isValidAnonymousLogin(zUsername, zPasswd);
+ uid = login_is_valid_anonymous(zUsername, zPasswd, P("cs"));
if( uid>0 ){
- /* Successful login as anonymous. Set a cookie that looks like
- ** this:
- **
- ** HASH/TIME/anonymous
- **
- ** Where HASH is the sha1sum of TIME/IPADDR/SECRET, in which IPADDR
- ** is the abbreviated IP address and SECRET is captcha-secret.
- */
- char *zNow; /* Current time (julian day number) */
- char *zCookie; /* The login cookie */
- const char *zCookieName; /* Name of the login cookie */
- Blob b; /* Blob used during cookie construction */
-
- zCookieName = login_cookie_name();
- zNow = db_text("0", "SELECT julianday('now')");
- blob_init(&b, zNow, -1);
- blob_appendf(&b, "/%s/%s", zRemoteAddr, db_get("captcha-secret",""));
- sha1sum_blob(&b, &b);
- zCookie = sqlite3_mprintf("%s/%s/anonymous", blob_buffer(&b), zNow);
- blob_reset(&b);
- free(zNow);
- cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), 6*3600);
+ login_set_anon_cookie(zIpAddr, NULL);
record_login_attempt("anonymous", zIpAddr, 1);
redirect_to_g();
}
if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){
/* Attempting to log in as a user other than anonymous.
*/
- zSha1Pw = sha1_shared_secret(zPasswd, zUsername, 0);
- uid = db_int(0,
- "SELECT uid FROM user"
- " WHERE login=%Q"
- " AND length(cap)>0 AND length(pw)>0"
- " AND login NOT IN ('anonymous','nobody','developer','reader')"
- " AND (constant_time_cmp(pw,%Q)=0 OR constant_time_cmp(pw,%Q)=0)",
- zUsername, zSha1Pw, zPasswd
- );
+ uid = login_search_uid(zUsername, zPasswd);
if( uid<=0 ){
sleep(1);
zErrMsg =
@
@ You entered an unknown user or an incorrect password.
@@ -360,26 +482,11 @@
** HASH/PROJECT/LOGIN
**
** where HASH is a random hex number, PROJECT is either project
** code prefix, and LOGIN is the user name.
*/
- char *zCookie;
- const char *zCookieName = login_cookie_name();
- const char *zExpire = db_get("cookie-expire","8766");
- int expires = atoi(zExpire)*3600;
- char *zCode = abbreviated_project_code(db_get("project-code",""));
- char *zHash;
-
- zHash = db_text(0, "SELECT hex(randomblob(25))");
- zCookie = mprintf("%s/%s/%s", zHash, zCode, zUsername);
- cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires);
- record_login_attempt(zUsername, zIpAddr, 1);
- db_multi_exec(
- "UPDATE user SET cookie=%Q, ipaddr=%Q, "
- " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
- zHash, zRemoteAddr, expires, uid
- );
+ login_set_user_cookie(zUsername, uid, NULL);
redirect_to_g();
}
}
style_header("Login/Logout");
@ %s(zErrMsg)
@@ -546,12 +653,16 @@
fossil_free(zOtherRepo);
return nXfer;
}
/*
-** Lookup the uid for a user with zLogin and zCookie and zRemoteAddr.
-** Return 0 if not found.
+** Lookup the uid for a non-built-in user with zLogin and zCookie and
+** zRemoteAddr. Return 0 if not found.
+**
+** Note that this only searches for logged-in entries with matching
+** zCookie (db: user.cookie) and zRemoteAddr (db: user.ipaddr)
+** entries.
*/
static int login_find_user(
const char *zLogin, /* User name */
const char *zCookie, /* Login cookie value */
const char *zRemoteAddr /* Abbreviated IP address for valid login */
@@ -573,16 +684,14 @@
);
return uid;
}
/*
-** This routine examines the login cookie to see if it exists and
-** and is valid. If the login cookie checks out, it then sets
-** global variables appropriately. Global variables set include
-** g.userUid and g.zLogin and of the g.perm.Read family of permission
-** booleans.
-**
+** This routine examines the login cookie to see if it exists and and
+** is valid. If the login cookie checks out, it then sets global
+** variables appropriately. Global variables set include g.userUid
+** and g.zLogin and the g.perm family of permission booleans.
*/
void login_check_credentials(void){
int uid = 0; /* User id */
const char *zCookie; /* Text of the login cookie */
const char *zIpAddr; /* Raw IP address of the requestor */
@@ -670,11 +779,11 @@
}
sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "%.10s", zHash);
}
/* If no user found and the REMOTE_USER environment variable is set,
- ** the accept the value of REMOTE_USER as the user.
+ ** then accept the value of REMOTE_USER as the user.
*/
if( uid==0 ){
const char *zRemoteUser = P("REMOTE_USER");
if( zRemoteUser && db_get_boolean("remote_user_ok",0) ){
uid = db_int(0, "SELECT uid FROM user WHERE login=%Q"
@@ -720,11 +829,11 @@
if( fossil_strcmp(g.zLogin,"nobody")==0 ){
g.zLogin = 0;
}
/* Set the capabilities */
- login_set_capabilities(zCap, 0);
+ login_replace_capabilities(zCap, 0);
login_set_anon_nobody_capabilities();
}
/*
** Memory of settings
@@ -749,22 +858,25 @@
login_anon_once = 0;
}
}
/*
-** Flags passed into the 2nd argument of login_set_capabilities().
+** Flags passed into the 2nd argument of login_set/replace_capabilities().
*/
#if INTERFACE
#define LOGIN_IGNORE_U 0x01 /* Ignore "u" */
#define LOGIN_IGNORE_V 0x01 /* Ignore "v" */
#endif
/*
-** Set the global capability flags based on a capability string.
+** Adds all capability flags in zCap to g.perm.
*/
void login_set_capabilities(const char *zCap, unsigned flags){
int i;
+ if(NULL==zCap){
+ return;
+ }
for(i=0; zCap[i]; i++){
switch( zCap[i] ){
case 's': g.perm.Setup = 1; /* Fall thru into Admin */
case 'a': g.perm.Admin = g.perm.RdTkt = g.perm.WrTkt = g.perm.Zip =
g.perm.RdWiki = g.perm.WrWiki = g.perm.NewWiki =
@@ -818,10 +930,18 @@
break;
}
}
}
}
+
+/*
+** Zeroes out g.perm and calls login_set_capabilities(zCap,flags).
+*/
+void login_replace_capabilities(const char *zCap, unsigned flags){
+ memset(&g.perm, 0, sizeof(g.perm));
+ return login_set_capabilities(zCap, flags);
+}
/*
** If the current login lacks any of the capabilities listed in
** the input, then return 0. If all capabilities are present, then
** return 1.
@@ -896,14 +1016,19 @@
/*
** Call this routine when the credential check fails. It causes
** a redirect to the "login" page.
*/
void login_needed(void){
- const char *zUrl = PD("REQUEST_URI", "index");
- cgi_redirect(mprintf("login?g=%T", zUrl));
- /* NOTREACHED */
- assert(0);
+ if(g.json.isJsonMode){
+ json_err( FSL_JSON_E_DENIED, NULL, 1 );
+ fossil_exit(0);
+ }else{
+ const char *zUrl = PD("REQUEST_URI", "index");
+ cgi_redirect(mprintf("login?g=%T", zUrl));
+ /* NOTREACHED */
+ assert(0);
+ }
}
/*
** Call this routine if the user lacks okHistory permission. If
** the anonymous user has okHistory permission, then paint a mesage
Index: src/main.c
==================================================================
--- src/main.c
+++ src/main.c
@@ -23,13 +23,15 @@
#include
#include
#include
#include
#include
-
+#include /* atexit() */
#if INTERFACE
+#include "cson_amalgamation.h" /* JSON API. Needed inside the INTERFACE block! */
+#include "json_detail.h"
/*
** Number of elements in an array
*/
#define count(X) (sizeof(X)/sizeof(X[0]))
@@ -115,10 +117,11 @@
int xlinkClusterOnly; /* Set when cloning. Only process clusters */
int fTimeFormat; /* 1 for UTC. 2 for localtime. 0 not yet selected */
int *aCommitFile; /* Array of files to be committed */
int markPrivate; /* All new artifacts are private if true */
int clockSkewSeen; /* True if clocks on client and server out of sync */
+ int isHTTP; /* True if running in server/CGI modes, else assume CLI. */
int urlIsFile; /* True if a "file:" url */
int urlIsHttps; /* True if a "https:" url */
int urlIsSsh; /* True if an "ssh:" url */
char *urlName; /* Hostname for http: or filename for file: */
@@ -131,11 +134,11 @@
char *urlPasswd; /* Password for http: */
char *urlCanonical; /* Canonical representation of the URL */
char *urlProxyAuth; /* Proxy-Authorizer: string */
char *urlFossil; /* The path of the ?fossil=path suffix on ssh: */
int dontKeepUrl; /* Do not persist the URL */
-
+
const char *zLogin; /* Login name. "" if not logged in. */
const char *zSSLIdentity; /* Value of --ssl-identity option, filename of SSL client identity */
int useLocalauth; /* No login required if from 127.0.0.1 */
int noPswd; /* Logged in without password (on 127.0.0.1) */
int userUid; /* Integer user id */
@@ -166,10 +169,64 @@
const char *azAuxVal[MX_AUX]; /* Value of each aux() or option() value */
const char **azAuxOpt[MX_AUX]; /* Options of each option() value */
int anAuxCols[MX_AUX]; /* Number of columns for option() values */
int allowSymlinks; /* Cached "allow-symlinks" option */
+
+ struct FossilJsonBits {
+ int isJsonMode; /* True if running in JSON mode, else
+ false. This changes how errors are
+ reported. In JSON mode we try to
+ always output JSON-form error
+ responses and always exit() with
+ code 0 to avoid an HTTP 500 error.
+ */
+ int resultCode; /* used for passing back specific codes from /json callbacks. */
+ int errorDetailParanoia; /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */
+ cson_output_opt outOpt; /* formatting options for JSON mode. */
+ cson_value * authToken; /* authentication token */
+ char const * jsonp; /* Name of JSONP function wrapper. */
+ unsigned char dispatchDepth /* Tells JSON command dispatching
+ which argument we are currently
+ working on. For this purpose, arg#0
+ is the "json" path/CLI arg.
+ */;
+ struct { /* "garbage collector" */
+ cson_value * v;
+ cson_array * a;
+ } gc;
+ struct { /* JSON POST data. */
+ cson_value * v;
+ cson_array * a;
+ int offset; /* Tells us which PATH_INFO/CLI args
+ part holds the "json" command, so
+ that we can account for sub-repos
+ and path prefixes. This is handled
+ differently for CLI and CGI modes.
+ */
+ char const * commandStr /*"command" request param.*/;
+ } cmd;
+ struct { /* JSON POST data. */
+ cson_value * v;
+ cson_object * o;
+ } post;
+ struct { /* GET/COOKIE params in JSON mode.
+ FIXME (stephan): verify that this is
+ still used and remove if it is not.
+ */
+ cson_value * v;
+ cson_object * o;
+ } param;
+ struct {
+ cson_value * v;
+ cson_object * o;
+ } reqPayload; /* request payload object (if any) */
+ struct { /* response warnings */
+ cson_value * v;
+ cson_array * a;
+ } warnings;
+ } json;
};
/*
** Macro for debugging:
*/
@@ -230,10 +287,24 @@
*pIndex = m;
return 0;
}
return 1+(cnt>1);
}
+
+/*
+** atexit() handler which frees up "some" of the resources
+** used by fossil.
+*/
+void fossil_atexit() {
+
+ cson_value_free(g.json.gc.v);
+ free(g.zErrMsg);
+ memset(&g.json, 0, sizeof(g.json));
+ if(g.db){
+ db_close(0);
+ }
+}
/*
** Search g.argv for arguments "--args FILENAME". If found, then
** (1) remove the two arguments from g.argv
** (2) Read the file FILENAME
@@ -315,25 +386,39 @@
int idx;
int rc;
int i;
sqlite3_config(SQLITE_CONFIG_LOG, fossil_sqlite_log, 0);
+ memset(&g, 0, sizeof(g));
g.now = time(0);
g.argc = argc;
g.argv = argv;
+#if defined(NDEBUG)
+ g.json.errorDetailParanoia = 2 /* FIXME: make configurable
+ One problem we have here is that this
+ code is needed before the db is opened,
+ so we can't sql for it.*/;
+#else
+ g.json.errorDetailParanoia = 0;
+#endif
+ g.json.outOpt = cson_output_opt_empty;
+ g.json.outOpt.addNewline = 1;
+ g.json.outOpt.indentation = 1 /* in CGI/server mode this can be configured */;
expand_args_option();
argc = g.argc;
argv = g.argv;
for(i=0; i%h
+ cgi_set_status(404, "not found");
+ cgi_reply();
+ }
}
return;
}
break;
}
@@ -1061,11 +1182,16 @@
zPathInfo = "/xfer";
}
set_base_url();
if( zPathInfo==0 || zPathInfo[0]==0
|| (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
- fossil_redirect_home();
+ if(g.json.isJsonMode){
+ json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
+ fossil_exit(0);
+ }else{
+ fossil_redirect_home() /*does not return*/;
+ }
}else{
zPath = mprintf("%s", zPathInfo);
}
/* Make g.zPath point to the first element of the path. Make
@@ -1122,10 +1248,12 @@
break;
}
if( g.zExtra ){
/* CGI parameters get this treatment elsewhere, but places like getfile
** will use g.zExtra directly.
+ ** Reminder: the login mechanism uses 'name' differently, and may
+ ** eventually have a problem/collision with this.
*/
dehttpize(g.zExtra);
cgi_set_parameter_nocopy("name", g.zExtra);
}
@@ -1132,17 +1260,25 @@
/* Locate the method specified by the path and execute the function
** that implements that method.
*/
if( name_search(g.zPath, aWebpage, count(aWebpage), &idx) &&
name_search("not_found", aWebpage, count(aWebpage), &idx) ){
- cgi_set_status(404,"Not Found");
- @