Changes On Branch json
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Changes In Branch json Excluding Merge-Ins

This is equivalent to a diff from 1e3cae8068 to 8a4e81cf93

2011-11-04
20:57
Merge the json branch into trunk. Json is disabled by default for now. Use the --enable-json option to configure, or set FOSSIL_ENABLE_JSON in the makefile to turn json processing on. check-in: 796dcfe072 user: drh tags: trunk
20:48
Added missing #ifdefs for json mode. Closed-Leaf check-in: 8a4e81cf93 user: json-demo tags: json-multitag-test, json
20:46
Added missing #ifdefs for json mode. check-in: d92aad2919 user: json-demo tags: json-multitag-test, json
19:39
merged in trunk [1e3cae806885d] and set up the json command/page to be elided when FOSSIL_DISABLE_JSON is defined at build time. check-in: 44bba06ce6 user: json-demo tags: json-multitag-test, json
19:10
Merge the steveb-fixes branch into trunk. check-in: aeec10b900 user: drh tags: trunk
18:55
Enhance the mkindex.c utility so that it honors #if statements in the source code. check-in: 1e3cae8068 user: drh tags: trunk
17:59
Remove the "commands" command and replace it with --all, --aux, and --test options to the "help" command. check-in: d6a93abf2c user: drh tags: trunk

Added ajax/README.

            1  +This is the README for how to set up the Fossil/JSON test web page
            2  +under Apache on Unix systems. This is only intended only for
            3  +Fossil/JSON developers/tinkerers:
            4  +
            5  +First, copy cgi-bin/fossil-json.cgi.example to
            6  +cgi-bin/fossil-json.cgi.  Edit it and correct the paths to the fossil
            7  +binary and the repo you want to serve. Make it executable.
            8  +
            9  +MAKE SURE that the fossil repo you use is world-writable OR that your
           10  +Web/CGI server is set up to run as the user ID of the owner of the
           11  +fossil file. ALSO: the DIRECTORY CONTAINING the repo file must be
           12  +writable by the CGI process.
           13  +
           14  +Next, set up an apache vhost entry. Mine looks like:
           15  +
           16  +<VirtualHost *:80>
           17  +    ServerAlias fjson
           18  +    ScriptAlias /cgi-bin/ /home/stephan/cvs/fossil/fossil-json/ajax/cgi-bin/
           19  +    DocumentRoot /home/stephan/cvs/fossil/fossil-json/ajax
           20  +</VirtualHost>
           21  +
           22  +Now add your preferred vhost name (fjson in the above example) to /etc/hosts:
           23  +
           24  +  127.0.0.1 ...other aliases... fjson
           25  +
           26  +Restart your Apache.
           27  +
           28  +Now visit: http://fjson/
           29  +
           30  +that will show the test/demo page. If it doesn't, edit index.html and
           31  +make sure that:
           32  +
           33  +  WhAjaj.Connector.options.ajax.url = ...;
           34  +
           35  +points to your CGI script. In theory you can also do this over fossil
           36  +standalone server mode, but i haven't yet tested that particular test
           37  +page in that mode.
           38  +

Added ajax/cgi-bin/fossil-json.cgi.example.

            1  +#!/path/to/fossil/binary
            2  +repository: /path/to/repo.fsl

Added ajax/i-test/rhino-shell.js.

            1  +var FShell = {
            2  +    serverUrl:
            3  +        'http://localhost:8080'
            4  +        //'http://fjson/cgi-bin/fossil-json.cgi'
            5  +        //'http://192.168.1.62:8080'
            6  +        //'http://fossil.wanderinghorse.net/repos/fossil-json-java/index.cgi'
            7  +        ,
            8  +    verbose:false,
            9  +    prompt:"fossil shell > ",
           10  +    wiki:{},
           11  +    consol:java.lang.System.console(),
           12  +    v:function(msg){
           13  +        if(this.verbose){
           14  +            print("VERBOSE: "+msg);
           15  +        }
           16  +    }
           17  +};
           18  +(function bootstrap() {
           19  +    var srcdir = '../js/';
           20  +    var includes = [srcdir+'json2.js',
           21  +                    srcdir+'whajaj.js',
           22  +                    srcdir+'fossil-ajaj.js'
           23  +                    ];
           24  +    for( var i in includes ) {
           25  +        load(includes[i]);
           26  +    }
           27  +    WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.rhino;
           28  +    FShell.fossil = new FossilAjaj({
           29  +        asynchronous:false, /* rhino-based impl doesn't support async. */
           30  +        timeout:10000,
           31  +        url:FShell.serverUrl
           32  +    });
           33  +    print("Server: "+FShell.serverUrl);
           34  +    var cb = FShell.fossil.ajaj.callbacks;
           35  +    cb.beforeSend = function(req,opt){
           36  +        if(!FShell.verbose) return;
           37  +        print("SENDING REQUEST: AJAJ options="+JSON.stringify(opt));
           38  +        if(req) print("Request envelope="+WhAjaj.stringify(req));
           39  +    };
           40  +    cb.afterSend = function(req,opt){
           41  +        //if(!FShell.verbose) return;
           42  +        //print("REQUEST RETURNED: opt="+JSON.stringify(opt));
           43  +        //if(req) print("Request="+WhAjaj.stringify(req));
           44  +    };
           45  +    cb.onError = function(req,opt){
           46  +        //if(!FShell.verbose) return;
           47  +        print("ERROR: "+WhAjaj.stringify(opt));
           48  +    };
           49  +    cb.onResponse = function(resp,req){
           50  +        if(!FShell.verbose) return;
           51  +        if(resp && resp.resultCode){
           52  +            print("Response contains error info: "+resp.resultCode+": "+resp.resultText);
           53  +        }
           54  +        print("GOT RESPONSE: "+(('string'===typeof resp) ? resp : WhAjaj.stringify(resp)));
           55  +    };
           56  +    FShell.fossil.HAI({
           57  +        onResponse:function(resp,opt){
           58  +            assertResponseOK(resp);
           59  +        }
           60  +    });
           61  +})();
           62  +
           63  +/**
           64  +    Throws an exception of cond is a falsy value.
           65  +*/
           66  +function assert(cond, descr){
           67  +    descr = descr || "Undescribed condition.";
           68  +    if(!cond){
           69  +        throw new Error("Assertion failed: "+descr);
           70  +    }else{
           71  +        //print("Assertion OK: "+descr);
           72  +    }
           73  +}
           74  +
           75  +/**
           76  +    Convenience form of FShell.fossil.sendCommand(command,payload,ajajOpt).
           77  +*/
           78  +function send(command,payload, ajajOpt){
           79  +    FShell.fossil.sendCommand(command,payload,ajajOpt);
           80  +}
           81  +
           82  +/**
           83  +    Asserts that resp is-a Object, resp.fossil is-a string, and
           84  +    !resp.resultCode.
           85  +*/
           86  +function assertResponseOK(resp){
           87  +    assert('object' === typeof resp,'Response is-a object.');
           88  +    assert( 'string' === typeof resp.fossil, 'Response contains fossil property.');
           89  +    assert( !resp.resultCode, 'resp.resultCode='+resp.resultCode);
           90  +}
           91  +/**
           92  +    Asserts that resp is-a Object, resp.fossil is-a string, and
           93  +    resp.resultCode is a truthy value. If expectCode is set then
           94  +    it also asserts that (resp.resultCode=='FOSSIL-'+expectCode).
           95  +*/
           96  +function assertResponseError(resp,expectCode){
           97  +    assert('object' === typeof resp,'Response is-a object.');
           98  +    assert( 'string' === typeof resp.fossil, 'Response contains fossil property.');
           99  +    assert( resp.resultCode, 'resp.resultCode='+resp.resultCode);
          100  +    if(expectCode){
          101  +        assert( 'FOSSIL-'+expectCode == resp.resultCode, 'Expecting result code '+expectCode );
          102  +    }
          103  +}
          104  +
          105  +FShell.readline = (typeof readline === 'function') ? (readline) : (function() {
          106  +     importPackage(java.io);
          107  +     importPackage(java.lang);
          108  +     var stdin = new BufferedReader(new InputStreamReader(System['in']));
          109  +     var self = this;
          110  +     return function(prompt) {
          111  +        if(prompt) print(prompt);
          112  +        var x = stdin.readLine();
          113  +        return null===x ? x : String(x) /*convert to JS string!*/;
          114  +     };
          115  +}());
          116  +
          117  +FShell.dispatchLine = function(line){
          118  +    var av = line.split(' '); // FIXME: to shell-like tokenization. Too tired!
          119  +    var cmd = av[0];
          120  +    var key, h;
          121  +    if('/' == cmd[0]) key = '/';
          122  +    else key = this.commandAliases[cmd];
          123  +    if(!key) key = cmd;
          124  +    h = this.commandHandlers[key];
          125  +    if(!h){
          126  +        print("Command not known: "+cmd +" ("+key+")");
          127  +    }else if(!WhAjaj.isFunction(h)){
          128  +        print("Not a function: "+key);
          129  +    }
          130  +    else{
          131  +        print("Sending ["+key+"] command... ");
          132  +        try{h(av);}
          133  +        catch(e){ print("EXCEPTION: "+e); }
          134  +    }
          135  +};
          136  +
          137  +FShell.onResponseDefault = function(callback){
          138  +    return function(resp,req){
          139  +        assertResponseOK(resp);
          140  +        print("Payload: "+(resp.payload ? WhAjaj.stringify(resp.payload) : "none"));
          141  +        if(WhAjaj.isFunction(callback)){
          142  +            callback(resp,req);
          143  +        }
          144  +    };
          145  +};
          146  +FShell.commandHandlers = {
          147  +    "?":function(args){
          148  +        var k;
          149  +        print("Available commands...\n");
          150  +        var o = FShell.commandHandlers;
          151  +        for(k in o){
          152  +            if(! o.hasOwnProperty(k)) continue;
          153  +            print("\t"+k);
          154  +        }
          155  +    },
          156  +    "/":function(args){
          157  +        FShell.fossil.sendCommand('/json'+args[0],undefined,{
          158  +            beforeSend:function(req,opt){
          159  +                print("Sending to: "+opt.url);
          160  +            },
          161  +            onResponse:FShell.onResponseDefault()
          162  +        });
          163  +    },
          164  +    "eval":function(args){
          165  +        eval(args.join(' '));
          166  +    },
          167  +    "login":function(args){
          168  +        FShell.fossil.login(args[1], args[2], {
          169  +            onResponse:FShell.onResponseDefault()
          170  +        });
          171  +    },
          172  +    "whoami":function(args){
          173  +        FShell.fossil.whoami({
          174  +            onResponse:FShell.onResponseDefault()
          175  +        });
          176  +    },
          177  +    "HAI":function(args){
          178  +        FShell.fossil.HAI({
          179  +            onResponse:FShell.onResponseDefault()
          180  +        });
          181  +    }
          182  +
          183  +};
          184  +FShell.commandAliases = {
          185  +    "li":"login",
          186  +    "lo":"logout",
          187  +    "who":"whoami",
          188  +    "hi":"HAI",
          189  +    "tci":"/timeline/ci?limit=3"
          190  +};
          191  +FShell.mainLoop = function(){
          192  +    var line;
          193  +    var check = /\S/;
          194  +    //var isJavaNull = /java\.lang\.null/;
          195  +    //print(typeof java.lang['null']);
          196  +    while( null != (line=this.readline(this.prompt)) ){
          197  +        if(null===line) break /*EOF*/;
          198  +        else if( "" === line ) continue;
          199  +        //print("Got line: "+line);
          200  +        else if(!check.test(line)) continue;
          201  +        print('typeof line = '+typeof line);
          202  +        this.dispatchLine(line);
          203  +        print("");
          204  +    }
          205  +    print("Bye!");
          206  +};
          207  +
          208  +FShell.mainLoop();

Added ajax/i-test/rhino-test.js.

            1  +var TestApp = {
            2  +    serverUrl:
            3  +        'http://localhost:8080'
            4  +        //'http://fjson/cgi-bin/fossil-json.cgi'
            5  +        //'http://192.168.1.62:8080'
            6  +        //'http://fossil.wanderinghorse.net/repos/fossil-json-java/index.cgi'
            7  +        ,
            8  +    verbose:false,
            9  +    fossilBinary:'fossil',
           10  +    wiki:{}
           11  +};
           12  +(function bootstrap() {
           13  +    var srcdir = '../js/';
           14  +    var includes = [srcdir+'json2.js',
           15  +                    srcdir+'whajaj.js',
           16  +                    srcdir+'fossil-ajaj.js'
           17  +                    ];
           18  +    for( var i in includes ) {
           19  +        load(includes[i]);
           20  +    }
           21  +    WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.rhino;
           22  +    TestApp.fossil = new FossilAjaj({
           23  +        asynchronous:false, /* rhino-based impl doesn't support async or timeout. */
           24  +        timeout:0,
           25  +        url:TestApp.serverUrl,
           26  +        fossilBinary:TestApp.fossilBinary
           27  +    });
           28  +    var cb = TestApp.fossil.ajaj.callbacks;
           29  +    cb.beforeSend = function(req,opt){
           30  +        if(!TestApp.verbose) return;
           31  +        print("SENDING REQUEST: AJAJ options="+JSON.stringify(opt));
           32  +        if(req) print("Request envelope="+WhAjaj.stringify(req));
           33  +    };
           34  +    cb.afterSend = function(req,opt){
           35  +        //if(!TestApp.verbose) return;
           36  +        //print("REQUEST RETURNED: opt="+JSON.stringify(opt));
           37  +        //if(req) print("Request="+WhAjaj.stringify(req));
           38  +    };
           39  +    cb.onError = function(req,opt){
           40  +        if(!TestApp.verbose) return;
           41  +        print("ERROR: "+WhAjaj.stringify(opt));
           42  +    };
           43  +    cb.onResponse = function(resp,req){
           44  +        if(!TestApp.verbose) return;
           45  +        print("GOT RESPONSE: "+(('string'===typeof resp) ? resp : WhAjaj.stringify(resp)));
           46  +    };
           47  +    
           48  +})();
           49  +
           50  +/**
           51  +    Throws an exception of cond is a falsy value.
           52  +*/
           53  +function assert(cond, descr){
           54  +    descr = descr || "Undescribed condition.";
           55  +    if(!cond){
           56  +        print("Assertion FAILED: "+descr);
           57  +        throw new Error("Assertion failed: "+descr);
           58  +        // aarrgghh. Exceptions are of course swallowed by
           59  +        // the AJAX layer, to keep from killing a browser's
           60  +        // script environment.
           61  +    }else{
           62  +        if(TestApp.verbose) print("Assertion OK: "+descr);
           63  +    }
           64  +}
           65  +
           66  +/**
           67  +    Calls func() in a try/catch block and throws an exception if
           68  +    func() does NOT throw.
           69  +*/
           70  +function assertThrows(func, descr){
           71  +    descr = descr || "Undescribed condition failed.";
           72  +    var ex;
           73  +    try{
           74  +        func();
           75  +    }catch(e){
           76  +        ex = e;
           77  +    }
           78  +    if(!ex){
           79  +        throw new Error("Function did not throw (as expected): "+descr);
           80  +    }else{
           81  +        if(TestApp.verbose) print("Function threw (as expected): "+descr+": "+ex);
           82  +    }
           83  +}
           84  +
           85  +/**
           86  +    Convenience form of TestApp.fossil.sendCommand(command,payload,ajajOpt).
           87  +*/
           88  +function send(command,payload, ajajOpt){
           89  +    TestApp.fossil.sendCommand(command,payload,ajajOpt);
           90  +}
           91  +
           92  +/**
           93  +    Asserts that resp is-a Object, resp.fossil is-a string, and
           94  +    !resp.resultCode.
           95  +*/
           96  +function assertResponseOK(resp){
           97  +    assert('object' === typeof resp,'Response is-a object.');
           98  +    assert( 'string' === typeof resp.fossil, 'Response contains fossil property.');
           99  +    assert( undefined === resp.resultCode, 'resp.resultCode is not set');
          100  +}
          101  +/**
          102  +    Asserts that resp is-a Object, resp.fossil is-a string, and
          103  +    resp.resultCode is a truthy value. If expectCode is set then
          104  +    it also asserts that (resp.resultCode=='FOSSIL-'+expectCode).
          105  +*/
          106  +function assertResponseError(resp,expectCode){
          107  +    assert('object' === typeof resp,'Response is-a object.');
          108  +    assert( 'string' === typeof resp.fossil, 'Response contains fossil property.');
          109  +    assert( !!resp.resultCode, 'resp.resultCode='+resp.resultCode);
          110  +    if(expectCode){
          111  +        assert( 'FOSSIL-'+expectCode == resp.resultCode, 'Expecting result code '+expectCode );
          112  +    }
          113  +}
          114  +
          115  +function testHAI(){
          116  +    var rs;
          117  +    TestApp.fossil.HAI({
          118  +        onResponse:function(resp,req){
          119  +            rs = resp;
          120  +        }
          121  +    });
          122  +    assertResponseOK(rs);
          123  +    TestApp.serverVersion = rs.fossil;
          124  +    assert( 'string' === typeof TestApp.serverVersion, 'server version = '+TestApp.serverVersion);
          125  +}
          126  +testHAI.description = 'Get server version info.';
          127  +
          128  +function testIAmNobody(){
          129  +    TestApp.fossil.whoami('/json/whoami');
          130  +    assert('nobody' === TestApp.fossil.auth.name, 'User == nobody.' );
          131  +    assert(!TestApp.fossil.auth.authToken, 'authToken is not set.' );
          132  +   
          133  +}
          134  +testIAmNobody.description = 'Ensure that current user is "nobody".';
          135  +
          136  +
          137  +function testAnonymousLogin(){
          138  +    TestApp.fossil.login();
          139  +    assert('string' === typeof TestApp.fossil.auth.authToken, 'authToken = '+TestApp.fossil.auth.authToken);
          140  +    assert( 'string' === typeof TestApp.fossil.auth.name, 'User name = '+TestApp.fossil.auth.name);
          141  +    TestApp.fossil.userName = null;
          142  +    TestApp.fossil.whoami('/json/whoami');
          143  +    assert( 'string' === typeof TestApp.fossil.auth.name, 'User name = '+TestApp.fossil.auth.name);
          144  +}
          145  +testAnonymousLogin.description = 'Perform anonymous login.';
          146  +
          147  +function testAnonWiki(){
          148  +    var rs;
          149  +    TestApp.fossil.sendCommand('/json/wiki/list',undefined,{
          150  +        beforeSend:function(req,opt){
          151  +            assert( req && (req.authToken==TestApp.fossil.auth.authToken), 'Request envelope contains expected authToken.'  );
          152  +        },
          153  +        onResponse:function(resp,req){
          154  +            rs = resp;
          155  +        }
          156  +    });
          157  +    assertResponseOK(rs);
          158  +    assert( (typeof [] === typeof rs.payload) && rs.payload.length,
          159  +        "Wiki list seems to be okay.");
          160  +    TestApp.wiki.list = rs.payload;
          161  +
          162  +    TestApp.fossil.sendCommand('/json/wiki/get',{
          163  +        name:TestApp.wiki.list[0]
          164  +    },{
          165  +        onResponse:function(resp,req){
          166  +            rs = resp;
          167  +        }
          168  +    });
          169  +    assertResponseOK(rs);
          170  +    assert(rs.payload.name == TestApp.wiki.list[0], "Fetched page name matches expectations.");
          171  +    print("Got first wiki page: "+WhAjaj.stringify(rs.payload));
          172  +
          173  +}
          174  +testAnonWiki.description = 'Fetch wiki list as anonymous user.';
          175  +
          176  +function testAnonLogout(){
          177  +    var rs;
          178  +    TestApp.fossil.logout({
          179  +        onResponse:function(resp,req){
          180  +            rs = resp;
          181  +        }
          182  +    });
          183  +    assertResponseOK(rs);
          184  +    print("Ensure that second logout attempt fails...");
          185  +    TestApp.fossil.logout({
          186  +        onResponse:function(resp,req){
          187  +            rs = resp;
          188  +        }
          189  +    });
          190  +    assertResponseError(rs);
          191  +}
          192  +testAnonLogout.description = 'Log out anonymous user.';
          193  +
          194  +function testExternalProcess(){
          195  +
          196  +    var req = { command:"HAI", requestId:'testExternalProcess()' };
          197  +    var args = [TestApp.fossilBinary, 'json', '--json-input', '-'];
          198  +    var p = java.lang.Runtime.getRuntime().exec(args);
          199  +    var outs = p.getOutputStream();
          200  +    var osr = new java.io.OutputStreamWriter(outs);
          201  +    var osb = new java.io.BufferedWriter(osr);
          202  +    var json = JSON.stringify(req);
          203  +    osb.write(json,0, json.length);
          204  +    //osb.flush();
          205  +    osb.close();
          206  +    var ins = p.getInputStream();
          207  +    var isr = new java.io.InputStreamReader(ins);
          208  +    var br = new java.io.BufferedReader(isr);
          209  +    var line;
          210  +    
          211  +    while( null !== (line=br.readLine())){
          212  +        print(line);
          213  +    }
          214  +    //outs.close();
          215  +    ins.close();
          216  +}
          217  +testExternalProcess.description = 'Run fossil as external process.';
          218  +
          219  +function testExternalProcessHandler(){
          220  +    var aj = TestApp.fossil.ajaj;
          221  +    var oldImpl = aj.sendImpl;
          222  +    aj.sendImpl = FossilAjaj.rhinoLocalBinarySendImpl;
          223  +    var rs;
          224  +    TestApp.fossil.sendCommand('/json/HAI',undefined,{
          225  +        onResponse:function(resp,opt){
          226  +            rs = resp;
          227  +        }
          228  +    });
          229  +    aj.sendImpl = oldImpl;
          230  +    assertResponseOK(rs);
          231  +    print("Using local fossil binary via AJAX interface, we fetched: "+
          232  +        WhAjaj.stringify(rs));
          233  +}
          234  +testExternalProcessHandler.description = 'Try local fossil binary via AJAX interface.';
          235  +
          236  +(function runAllTests(){
          237  +    var testList = [
          238  +        testHAI,
          239  +        testIAmNobody,
          240  +        testAnonymousLogin,
          241  +        testAnonWiki,
          242  +        testAnonLogout,
          243  +        //testExternalProcess,
          244  +        testExternalProcessHandler
          245  +    ];
          246  +    var i, f;
          247  +    for( i = 0; i < testList.length; ++i ){
          248  +        f = testList[i];
          249  +        try{
          250  +            print("Running test #"+(i+1)+": "+(f.description || "no description."));
          251  +            f();
          252  +        }catch(e){
          253  +            print("Test #"+(i+1)+" failed: "+e);
          254  +            throw e;
          255  +        }
          256  +    }
          257  +
          258  +})();
          259  +
          260  +print("Done! If you don't see an exception message in the last few lines, you win!");

Added ajax/index.html.

            1  +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
            2  +	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
            3  +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
            4  +
            5  +<head>
            6  +	<title>Fossil/JSON raw request sending</title>
            7  +	<meta http-equiv="content-type" content="text/html;charset=utf-8" />
            8  +    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script> 
            9  +    <script type="text/javascript" src="js/whajaj.js"></script>
           10  +    <script type="text/javascript" src="js/fossil-ajaj.js"></script>
           11  +
           12  +<style type='text/css'>
           13  +th {
           14  +  text-align: left;
           15  +  background-color: #ececec;  
           16  +}
           17  +
           18  +.dangerWillRobinson {
           19  +    background-color: yellow;
           20  +}
           21  +</style>
           22  +
           23  +<script type='text/javascript'>
           24  +WhAjaj.Connector.options.ajax.url =
           25  +/*
           26  +    Change this to your CGI/server root path:
           27  +*/
           28  +     //'http://fjson/cgi-bin/fossil.cgi'
           29  +     //'/repos/fossil-sgb/json.cgi'
           30  +    '/cgi-bin/fossil-json.cgi'
           31  +     ;
           32  +var TheApp = {
           33  +      response:null,
           34  +      sessionID:null,
           35  +      jqe:{}/*jqe==jQuery Elements*/,
           36  +      ajaxCount:0,
           37  +      cgi: new FossilAjaj()
           38  +};
           39  +
           40  +
           41  +TheApp.startAjaxNotif = function()
           42  +{
           43  +    ++this.ajaxCount;
           44  +    TheApp.jqe.responseContainer.removeClass('dangerWillRobinson');
           45  +    this.jqe.ajaxNotification.attr( 'title', this.ajaxCount+" pending AJAX operation(s)..." );
           46  +    if( 1 == this.ajaxCount ) this.jqe.ajaxNotification.fadeIn();
           47  +};
           48  +
           49  +TheApp.endAjaxNotif = function()
           50  +{
           51  +    --this.ajaxCount;
           52  +    this.jqe.ajaxNotification.attr( 'title', this.ajaxCount+" pending AJAX operation(s)..." );
           53  +    if( 0 == this.ajaxCount ) this.jqe.ajaxNotification.fadeOut();
           54  +};
           55  +
           56  +TheApp.responseContainsError = function(resp) {
           57  +    if( resp && resp.resultCode ) {
           58  +        //alert("Error response:\n"+JSON.stringify(resp,0,4));
           59  +        TheApp.jqe.taResponse.val( "RESPONSE CONTAINS ERROR INFO:\n"+WhAjaj.stringify(resp) );
           60  +        //TheApp.jqe.responseContainer.css({backgroundColor:'yellow'});
           61  +        //TheApp.jqe.responseContainer.addClass('dangerWillRobinson');
           62  +        TheApp.jqe.responseContainer.flash( '255,0,0', 1500 );
           63  +        return true;
           64  +    }
           65  +    return false;
           66  +};
           67  +
           68  +
           69  +TheApp.sendRequest = function() {
           70  +    var path = this.jqe.textPath.val();
           71  +    var self = this;
           72  +    var data = this.jqe.taRequest.val();
           73  +    var doPost = (data && data.length);
           74  +    var req;
           75  +    if( doPost ) try {
           76  +        req = JSON.parse(data);
           77  +    }
           78  +    catch(e) {
           79  +        TheApp.jqe.taResponse.val("Request is not valid JSON.\n"+e);
           80  +        return;
           81  +    }
           82  +    if( req ) {
           83  +        req.requestId = this.cgi.generateRequestId();
           84  +    }
           85  +    var self = this;
           86  +    var opt = {
           87  +        url: WhAjaj.Connector.options.ajax.url + path,
           88  +        method: doPost ? 'POST' : 'GET'
           89  +    };
           90  +    this.cgi.sendRequest( req, opt );
           91  +};
           92  +jQuery.fn.animateHighlight = function(highlightColor, duration) {
           93  +    var highlightBg = highlightColor || "#FFFF9C";
           94  +    var animateMs = duration || 1500;
           95  +    var originalBg = this.css("backgroundColor");
           96  +    this.stop().css("background-color", highlightBg).animate({backgroundColor: originalBg}, animateMs);
           97  +};
           98  +jQuery.fn.flash = function( color, duration )
           99  +{
          100  +    var current = this.css( 'color' );
          101  +    this.animate( { color: 'rgb(' + color + ')' }, duration / 2);
          102  +    this.animate( { color: current }, duration / 2 );
          103  +};
          104  +
          105  +function myJsonPCallback(obj){
          106  +    alert("JSONP callback got:\n"+WhAjaj.stringify(obj));
          107  +}
          108  +
          109  +jQuery(document).ready(function(){
          110  +    var ids = [// list of HTML element IDs we use often.
          111  +        'btnSend',
          112  +        'ajaxNotification',
          113  +        'currentAuthToken',
          114  +        'responseContainer',
          115  +        'taRequest',
          116  +        'taRequestOpt',
          117  +        'taResponse',
          118  +        'textPath',
          119  +        'timer'
          120  +    ];
          121  +    var i, k;
          122  +    for( i = 0; i < ids.length; ++i ) {
          123  +        k = ids[i];
          124  +        TheApp.jqe[k] = jQuery('#'+k);
          125  +    }
          126  +    TheApp.jqe.textPath.
          127  +        keyup(function(event){
          128  +            if(event.keyCode == 13){
          129  +                TheApp.sendRequest();
          130  +            }
          131  +        });
          132  +    TheApp.timer = {
          133  +        _tstart:0,_tend:0,duration:0,
          134  +        start:function(){
          135  +            this._tstart = (new Date()).getTime();
          136  +        },
          137  +        end:function(){
          138  +            this._tend = (new Date()).getTime();
          139  +            return this.duration = this._tend - this._tstart;
          140  +        }
          141  +    };
          142  +
          143  +    var ajcb = TheApp.cgi.ajaj.callbacks;
          144  +    ajcb.beforeSend = function(req,opt) {
          145  +        TheApp.timer.start();
          146  +        var val =
          147  +            req ?
          148  +            (('string'===typeof req) ? req : WhAjaj.stringify(req))
          149  +            : '';
          150  +        TheApp.jqe.taResponse.val('');
          151  +        TheApp.jqe.taRequest.val( val );
          152  +        TheApp.jqe.taRequestOpt.val( opt ? WhAjaj.stringify(opt) : '' );
          153  +        TheApp.startAjaxNotif();
          154  +    };
          155  +    ajcb.afterSend = function(req,opt) {
          156  +        TheApp.timer.end();
          157  +        TheApp.endAjaxNotif();
          158  +        TheApp.jqe.timer.text( "(Round-trip time (incl. JS overhead): "+TheApp.timer.duration+'ms)' );
          159  +    };
          160  +    ajcb.onResponse = function(resp,req, opt) {
          161  +        var val;
          162  +        if(this.jsonp) return /*was already handled*/;
          163  +        try {
          164  +            val = WhAjaj.stringify(resp);
          165  +        }
          166  +        catch(e) {
          167  +            val = WhAjaj.stringify(e)
          168  +        }
          169  +        //alert("onResponse this:"+WhAjaj.stringify(this));
          170  +        //alert("val="+val);
          171  +        // FIXME: this.url is hosed for login because of how i overload onResponse()
          172  +        if( opt.url ) TheApp.jqe.textPath.val(opt.url.replace(WhAjaj.Connector.options.ajax.url,''));
          173  +        TheApp.jqe.taResponse.val( val );
          174  +    };
          175  +    ajcb.onError = function(req,opt) {
          176  +        TheApp.jqe.taResponse.val( "ERROR SENDING REQUEST:\n"+WhAjaj.stringify(opt) );
          177  +    };
          178  +
          179  +    TheApp.cgi.onLogin = function(){
          180  +      TheApp.jqe.taResponse.val( "Logged in:\n"+WhAjaj.stringify(this.auth));
          181  +      TheApp.jqe.currentAuthToken.html("Logged in: "+WhAjaj.stringify(this.auth));
          182  +    };
          183  +    TheApp.cgi.onLogout = function(){
          184  +      TheApp.jqe.taResponse.val( "Logged out!" );
          185  +      TheApp.jqe.currentAuthToken.text("Not logged in");
          186  +    };
          187  +    TheApp.cgi.whoami();
          188  +    jQuery('#headerArea').click(function(){
          189  +        jQuery(this).slideUp('fast',function(){
          190  +            jQuery(this).remove();
          191  +        });
          192  +    });
          193  +});
          194  +
          195  +</script>
          196  +
          197  +</head>
          198  +
          199  +<body>
          200  +<span id='ajaxNotification'></span>
          201  +<div id='headerArea'>
          202  +<h1>You know, for sending raw JSON requests to Fossil...</h1>
          203  +
          204  +If you're actually using this page, then you know what you're doing and don't
          205  +need help text, hoverhelp, and a snazzy interface.
          206  +
          207  +<br><br>
          208  +
          209  +
          210  +JSON API docs: <a href='https://docs.google.com/document/d/1fXViveNhDbiXgCuE7QDXQOKeFzf2qNUkBEgiUvoqFN4/edit'>https://docs.google.com/document/d/1fXViveNhDbiXgCuE7QDXQOKeFzf2qNUkBEgiUvoqFN4/edit</a>
          211  +
          212  +</div><!-- #headerArea -->
          213  +See also: <a href='wiki-editor.html'>prototype wiki editor</a>.
          214  +
          215  +<h2>Request...</h2>
          216  +
          217  +Path: <input type='text' size='40' id='textPath' value='/json/HAI'/>
          218  +<input type='button' value='Send...' id='btnSend' onclick='TheApp.sendRequest()' /><br/>
          219  +If the POST textarea is not empty then it will be posted with the request.
          220  +<hr/>
          221  +<strong>Quick-posts:</strong><br/>
          222  +<input type='button' value='HAI' onclick='TheApp.cgi.HAI()' />
          223  +<input type='button' value='HAI JSONP' onclick='TheApp.cgi.sendCommand("/json/HAI",undefined,{jsonp:"myJsonPCallback"});' />
          224  +<input type='button' value='version' onclick='TheApp.cgi.sendCommand("/json/version")' />
          225  +<input type='button' value='stat' onclick='TheApp.cgi.sendCommand("/json/stat?full=0")' />
          226  +<input type='button' value='whoami' onclick='TheApp.cgi.whoami()' />
          227  +<input type='button' value='cap' onclick='TheApp.cgi.sendCommand("/json/cap")' />
          228  +<input type='button' value='resultCodes' onclick='TheApp.cgi.sendCommand("/json/resultCodes")' />
          229  +<input type='button' value='g' onclick='TheApp.cgi.sendCommand("/json/g")' />
          230  +
          231  +<br/>
          232  +
          233  +<input type='button' value='branch/list' onclick='TheApp.cgi.sendCommand("/json/branch/list")' />
          234  +<input type='button' value='timeline/ci' onclick='TheApp.cgi.sendCommand("/json/timeline/ci?files=true")' />
          235  +<input type='button' value='timeline/wiki' onclick='TheApp.cgi.sendCommand("/json/timeline/wiki")' />
          236  +<input type='button' value='timeline/ticket' onclick='TheApp.cgi.sendCommand("/json/timeline/ticket")' />
          237  +<input type='button' value='timeline/branch' onclick='TheApp.cgi.sendCommand("/json/timeline/branch")' />
          238  +<input type='button' value='wiki/list' onclick='TheApp.cgi.sendCommand("/json/wiki/list")' />
          239  +<input type='button' value='wiki/get Fossil' onclick='TheApp.cgi.sendCommand("/json/wiki/get",{name:"Fossil"})' />
          240  +<input type='button' value='wiki/get/Fossil' onclick='TheApp.cgi.sendCommand("/json/wiki/get/Fossil")' />
          241  +
          242  +<br/>
          243  +
          244  +<input type='button' value='user/list' onclick='TheApp.cgi.sendCommand("/json/user/list")' />
          245  +<input type='button' value='user/get' onclick='TheApp.cgi.sendCommand("/json/user/get?name=anonymous")' />
          246  +<input type='button' value='tag/list' onclick='TheApp.cgi.sendCommand("/json/tag/list?includeTickets=false&raw=false")' />
          247  +<input type='button' value='tag/list/json' onclick='TheApp.cgi.sendCommand("/json/tag/list/json?raw=false")' />
          248  +<input type='button' value='tag/add'
          249  +    onclick='TheApp.cgi.sendCommand("/json/tag/add",{name:"json-add-tag-test",checkin:"json",value:"tag test",propagate:false,raw:false})' />
          250  +<input type='button' value='tag/cancel'
          251  +    onclick='TheApp.cgi.sendCommand("/json/tag/cancel",{name:"json-add-tag-test",checkin:"json",raw:false})' />
          252  +<input type='button' value='tag/find'
          253  +    onclick='TheApp.cgi.sendCommand("/json/tag/find",{name:"json",type:"*",raw:false,limit:5})' />
          254  +
          255  +<br/>
          256  +
          257  +<input type='button' value='diff'
          258  +    onclick='TheApp.cgi.sendCommand("/json/diff",{v1:"b0e9b45baed6f885",v2:"5f225e261d836287",context:2})' />
          259  +<input type='button' value='diff/A/B'
          260  +    onclick='TheApp.cgi.sendCommand("/json/diff/b0e9b45baed6f885/5f225e261d836287?context=2")' />
          261  +
          262  +<input type='button' value='query'
          263  +    onclick='TheApp.cgi.sendCommand("/json/query?format=o","SELECT * from user")' />
          264  +
          265  +<input type='button' value='report list'
          266  +    onclick='TheApp.cgi.sendCommand("/json/report/list")' />
          267  +<input type='button' value='report get'
          268  +    onclick='TheApp.cgi.sendCommand("/json/report/get",2)' />
          269  +
          270  +<input type='button' value='report run'
          271  +    onclick='TheApp.cgi.sendCommand("/json/report/run",{"report":2,"format":"o"})' />
          272  +
          273  +<!-- not yet ready...
          274  +<input type='button' value='artifact/XYZ' onclick='TheApp.cgi.sendCommand("/json/artifact?uuid=json")' />
          275  +-->
          276  +
          277  +<!--
          278  +<input type='button' value='get whiki' onclick='TheApp.cgi.getPages("whiki")' />
          279  +<input type='button' value='get more' onclick='TheApp.cgi.getPages("HelloWorld/WhikiNews")' />
          280  +<input type='button' value='get client data' onclick='TheApp.cgi.getPageClientData("HelloWorld/whiki/WhikiCommands")' />
          281  +<input type='button' value='save client data' onclick='TheApp.cgi.savePageClientData({"HelloWorld":[1,3,5]})' />
          282  +-->
          283  +<hr/>
          284  +<b>Login:</b>
          285  +<br/>
          286  +<input type='button' value='Anon. PW' onclick='TheApp.cgi.sendCommand("/json/anonymousPassword")' />
          287  +<input type='button' value='Anon. PW+Login' onclick='TheApp.cgi.login()' />
          288  +<br/>
          289  +name:<input type='text' id='textUser' value='json-demo' size='12'/>
          290  +pw:<input type='password' id='textPassword' value='json-demo' size='12'/>
          291  +<input type='button' value='login' onclick='TheApp.cgi.login(jQuery("#textUser").val(),jQuery("#textPassword").val(),{onResponse:TheApp.onLogin})' />
          292  +<input type='button' value='logout' onclick='TheApp.cgi.logout()' />
          293  +<br/>
          294  +<span id='currentAuthToken' style='font-family:monospaced'></span>
          295  +
          296  +<br/>
          297  +
          298  +<hr/>
          299  +
          300  +<table>
          301  +    <tr>
          302  +        <th>POST data</th>
          303  +        <th>Request AJAJ options</th>
          304  +    </tr>
          305  +    <tr>
          306  +        <td width='50%' valign='top'>
          307  +            <textarea id='taRequest' rows='10' cols='50'></textarea>
          308  +        </td>
          309  +        <td width='50%' valign='top'>
          310  +            <textarea id='taRequestOpt' rows='10' cols='40' readonly></textarea>
          311  +        </td>
          312  +    </tr>
          313  +    <tr>
          314  +        <th colspan='2'>Response <span id='timer'></span></th>
          315  +    </tr>
          316  +    <tr>
          317  +        <td colspan='2' id='responseContainer' valign='top'>
          318  +            <textarea id='taResponse' rows='20' cols='80' readonly></textarea>
          319  +        </td>
          320  +    </tr>
          321  +</table>
          322  +<div></div>
          323  +<div></div>
          324  +<div></div>
          325  +
          326  +</body></html>

Added ajax/js/fossil-ajaj.js.

            1  +/**
            2  +    This file contains a WhAjaj extension for use with Fossil/JSON.
            3  +
            4  +    Author: Stephan Beal (sgbeal@googlemail.com)
            5  +
            6  +    License: Public Domain
            7  +*/
            8  +
            9  +/**
           10  +    Constructor for a new Fossil AJAJ client. ajajOpt may be an optional
           11  +    object suitable for passing to the WhAjaj.Connector() constructor.
           12  +    
           13  +    On returning, this.ajaj is-a WhAjaj.Connector instance which can 
           14  +    be used to send requests to the back-end (though the convenience 
           15  +    functions of this class are the preferred way to do it). Clients 
           16  +    are encouraged to use FossilAjaj.sendCommand() (and friends) instead 
           17  +    of the underlying WhAjaj.Connector API, since this class' API 
           18  +    contains Fossil-specific request-calling handling (e.g. of authentication
           19  +    info) whereas WhAjaj is more generic.
           20  +*/
           21  +function FossilAjaj(ajajOpt)
           22  +{
           23  +    this.ajaj = new WhAjaj.Connector(ajajOpt);
           24  +    return this;
           25  +}
           26  +
           27  +FossilAjaj.prototype.generateRequestId = function() {
           28  +    return this.ajaj.generateRequestId();
           29  +};
           30  +
           31  +/**
           32  +   Proxy for this.ajaj.sendRequest().
           33  +*/
           34  +FossilAjaj.prototype.sendRequest = function(req,opt) {
           35  +    return this.ajaj.sendRequest(req,opt);
           36  +};
           37  +
           38  +/**
           39  +    Sends a command to the fossil back-end. Command should be the
           40  +    path part of the URL, e.g. /json/stat, payload is a request-specific
           41  +    value type (may often be null/undefined). ajajOpt is an optional object
           42  +    holding WhAjaj.sendRequest()-compatible options.
           43  +    
           44  +    This function constructs a Fossil/JSON request envelope based
           45  +    on the given arguments and adds this.auth.authToken and a requestId
           46  +    to it.
           47  +*/
           48  +FossilAjaj.prototype.sendCommand = function(command, payload, ajajOpt) {
           49  +    var req;
           50  +    ajajOpt = ajajOpt || {};
           51  +    if(payload || (this.auth && this.auth.authToken) || ajajOpt.jsonp) {
           52  +        req = {
           53  +            payload:payload,
           54  +            requestId:('function' === typeof this.generateRequestId) ? this.generateRequestId() : undefined,
           55  +            authToken:(this.auth ? this.auth.authToken : undefined),
           56  +            jsonp:('string' === typeof ajajOpt.jsonp) ? ajajOpt.jsonp : undefined
           57  +        };
           58  +    }
           59  +    ajajOpt.method = req ? 'POST' : 'GET';
           60  +    // just for debuggering: ajajOpt.method = 'POST'; if(!req) req={};
           61  +    if(command) ajajOpt.url = this.ajaj.derivedOption('url',ajajOpt) + command;
           62  +    this.ajaj.sendRequest(req,ajajOpt);
           63  +};
           64  +
           65  +/**
           66  +    Sends a login request to the back-end.
           67  +
           68  +    ajajOpt is an optional configuration object suitable for passing 
           69  +    to sendCommand().
           70  +
           71  +    After the response returns, this.auth will be
           72  +    set to the response payload.
           73  +    
           74  +    If name === 'anonymous' (the default if none is passed in) then this
           75  +    function ignores the pw argument and must make two requests - the first
           76  +    one gets the captcha code and the second one submits it.
           77  +    ajajOpt.onResponse() (if set) is only called for the actual login
           78  +    response (the 2nd one), as opposed to being called for both requests.
           79  +    However, this.ajaj.callbacks.onResponse() _is_ called for both (because
           80  +    it happens at a lower level).
           81  +    
           82  +    If this object has an onLogin() function it is called (with
           83  +    no arguments) before the onResponse() handler of the login is called
           84  +    (that is the 2nd request for anonymous logins).
           85  +
           86  +*/
           87  +FossilAjaj.prototype.login = function(name,pw,ajajOpt) {
           88  +    name = name || 'anonymous';
           89  +    var self = this;
           90  +    var loginReq = {
           91  +        name:name,
           92  +        password:pw
           93  +    };
           94  +    ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} );
           95  +    var oldOnResponse = ajajOpt.onResponse;
           96  +    ajajOpt.onResponse = function(resp,req) {
           97  +        var thisOpt = this;
           98  +        //alert('login response:\n'+WhAjaj.stringify(resp));
           99  +        if( resp && resp.payload ) {
          100  +            //self.userName = resp.payload.name;
          101  +            //self.capabilities = resp.payload.capabilities;
          102  +            self.auth = resp.payload;
          103  +        }
          104  +        if( WhAjaj.isFunction( self.onLogin ) ){
          105  +            try{ self.onLogin(); }
          106  +            catch(e){}
          107  +        }
          108  +        if( WhAjaj.isFunction(oldOnResponse) ) {
          109  +            oldOnResponse.apply(thisOpt,[resp,req]);
          110  +        }
          111  +    };
          112  +    function doLogin(){
          113  +        //alert("Sending login request..."+WhAjaj.stringify(loginReq));
          114  +        self.sendCommand('/json/login', loginReq, ajajOpt);
          115  +    }
          116  +    if( 'anonymous' === name ){
          117  +      this.sendCommand('/json/anonymousPassword',undefined,{
          118  +          onResponse:function(resp,req){
          119  +/*
          120  +            if( WhAjaj.isFunction(oldOnResponse) ){
          121  +                oldOnResponse.apply(this, [resp,req]);
          122  +            };
          123  +*/
          124  +            if(resp && !resp.resultCode){
          125  +                //alert("Got PW. Trying to log in..."+WhAjaj.stringify(resp));
          126  +                loginReq.anonymousSeed = resp.payload.seed;
          127  +                loginReq.password = resp.payload.password;
          128  +                doLogin();
          129  +            }
          130  +          }
          131  +      });
          132  +    }
          133  +    else doLogin();
          134  +};
          135  +
          136  +/**
          137  +    Logs out of fossil, invaliding this login token.
          138  +
          139  +    ajajOpt is an optional configuration object suitable for passing 
          140  +    to sendCommand().
          141  +    
          142  +    If this object has an onLogout() function it is called (with
          143  +    no arguments) before the onResponse() handler is called.
          144  +    IFF the response succeeds then this.auth is unset.
          145  +*/
          146  +FossilAjaj.prototype.logout = function(ajajOpt) {
          147  +    var self = this;
          148  +    ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} );
          149  +    var oldOnResponse = ajajOpt.onResponse;
          150  +    ajajOpt.onResponse = function(resp,req) {
          151  +        var thisOpt = this;
          152  +        self.auth = undefined;
          153  +        if( WhAjaj.isFunction( self.onLogout ) ){
          154  +            try{ self.onLogout(); }
          155  +            catch(e){}
          156  +        }
          157  +        if( WhAjaj.isFunction(oldOnResponse) ) {
          158  +            oldOnResponse.apply(thisOpt,[resp,req]);
          159  +        }
          160  +    };
          161  +    this.sendCommand('/json/logout', undefined, ajajOpt );
          162  +};
          163  +
          164  +/**
          165  +    Sends a HAI request to the server. /json/HAI is an alias /json/version.
          166  +
          167  +    ajajOpt is an optional configuration object suitable for passing 
          168  +    to sendCommand().
          169  +*/
          170  +FossilAjaj.prototype.HAI = function(ajajOpt) {
          171  +    this.sendCommand('/json/HAI', undefined, ajajOpt);
          172  +};
          173  +
          174  +
          175  +/**
          176  +    Sends a /json/whoami request. Updates this.auth to contain
          177  +    the login info, removing them if the response does not contain
          178  +    that data.
          179  +*/
          180  +FossilAjaj.prototype.whoami = function(ajajOpt) {
          181  +    var self = this;
          182  +    ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} );
          183  +    var oldOnResponse = ajajOpt.onResponse;
          184  +    ajajOpt.onResponse = function(resp,req) {
          185  +        var thisOpt = this;
          186  +        if( resp && resp.payload ){
          187  +            if(!self.auth || (self.auth.authToken!==resp.payload.authToken)){
          188  +                self.auth = resp.payload;
          189  +                if( WhAjaj.isFunction(self.onLogin) ){
          190  +                    self.onLogin();
          191  +                }
          192  +            }
          193  +        }
          194  +        else { delete self.auth; }
          195  +        if( WhAjaj.isFunction(oldOnResponse) ) {
          196  +            oldOnResponse.apply(thisOpt,[resp,req]);
          197  +        }
          198  +    };
          199  +    self.sendCommand('/json/whoami', undefined, ajajOpt);
          200  +};
          201  +
          202  +/**
          203  +    EXPERIMENTAL concrete WhAjaj.Connector.sendImpl() implementation which
          204  +    uses Rhino to connect to a local fossil binary for input and output. Its
          205  +    signature and semantics are as described for
          206  +    WhAjaj.Connector.prototype.sendImpl(), with a few exceptions and
          207  +    additions:
          208  +
          209  +    - It does not support timeouts or asynchronous mode.
          210  +
          211  +    - The args.fossilBinary property must point to the local fossil binary
          212  +    (it need not be a complete path if fossil is in the $PATH). This
          213  +    function throws (without calling any request callbacks) if
          214  +    args.fossilBinary is not set. fossilBinary may be set on
          215  +    WhAjaj.Connector.options.ajax, in the FossilAjaj constructor call, as
          216  +    the ajax options parameter to any of the FossilAjaj.sendCommand() family
          217  +    of functions, or by setting
          218  +    aFossilAjajInstance.ajaj.options.fossilBinary on a specific
          219  +    FossilAjaj instance.
          220  +
          221  +    - It uses the args.url field to create the "command" property of the
          222  +    request, constructs a request envelope, spawns a fossil process in JSON
          223  +    mode, feeds it the request envelope, and returns the response envelope
          224  +    via the same mechanisms defined for the HTTP-based implementations.
          225  +
          226  +    The interface is otherwise compatible with the "normal"
          227  +    FossilAjaj.sendCommand() front-end (it is, however, fossil-specific, and
          228  +    not back-end agnostic like the WhAjaj.sendImpl() interface intends).
          229  +
          230  +    
          231  +*/
          232  +FossilAjaj.rhinoLocalBinarySendImpl = function(request,args){
          233  +    var self = this;
          234  +    request = request || {};
          235  +    if(!args.fossilBinary){
          236  +        throw new Error("fossilBinary is not set on AJAX options!");
          237  +    }
          238  +    var url = args.url.split('?')[0].split(/\/+/);
          239  +    if(url.length>1){
          240  +        // 3x shift(): protocol, host, 'json' part of path
          241  +        request.command = (url.shift(),url.shift(),url.shift(), url.join('/'));
          242  +    }
          243  +    delete args.url;
          244  +    //print("rhinoLocalBinarySendImpl SENDING: "+WhAjaj.stringify(request));    
          245  +    var json;
          246  +    try{
          247  +        var pargs = [args.fossilBinary, 'json', '--json-input', '-'];
          248  +        var p = java.lang.Runtime.getRuntime().exec(pargs);
          249  +        var outs = p.getOutputStream();
          250  +        var osr = new java.io.OutputStreamWriter(outs);
          251  +        var osb = new java.io.BufferedWriter(osr);
          252  +        
          253  +        json = JSON.stringify(request);
          254  +        osb.write(json,0, json.length);
          255  +        osb.close();
          256  +        var ins = p.getInputStream();
          257  +        var isr = new java.io.InputStreamReader(ins);
          258  +        var br = new java.io.BufferedReader(isr);
          259  +        var line;
          260  +        json = [];
          261  +        while( null !== (line=br.readLine())){
          262  +            json.push(line);
          263  +        }
          264  +        ins.close();
          265  +    }catch(e){
          266  +        args.errorMessage = e.toString();
          267  +        WhAjaj.Connector.sendHelper.onSendError.apply( self, [request, args] );
          268  +        return undefined;
          269  +    }
          270  +    json = json.join('');
          271  +    //print("READ IN JSON: "+json);
          272  +    WhAjaj.Connector.sendHelper.onSendSuccess.apply( self, [request, json, args] );
          273  +}/*rhinoLocalBinary*/

Added ajax/js/json2.js.

            1  +/*
            2  +    http://www.JSON.org/json2.js
            3  +    2009-06-29
            4  +
            5  +    Public Domain.
            6  +
            7  +    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
            8  +
            9  +    See http://www.JSON.org/js.html
           10  +
           11  +    This file creates a global JSON object containing two methods: stringify
           12  +    and parse.
           13  +
           14  +        JSON.stringify(value, replacer, space)
           15  +            value       any JavaScript value, usually an object or array.
           16  +
           17  +            replacer    an optional parameter that determines how object
           18  +                        values are stringified for objects. It can be a
           19  +                        function or an array of strings.
           20  +
           21  +            space       an optional parameter that specifies the indentation
           22  +                        of nested structures. If it is omitted, the text will
           23  +                        be packed without extra whitespace. If it is a number,
           24  +                        it will specify the number of spaces to indent at each
           25  +                        level. If it is a string (such as '\t' or '&nbsp;'),
           26  +                        it contains the characters used to indent at each level.
           27  +
           28  +            This method produces a JSON text from a JavaScript value.
           29  +
           30  +            When an object value is found, if the object contains a toJSON
           31  +            method, its toJSON method will be called and the result will be
           32  +            stringified. A toJSON method does not serialize: it returns the
           33  +            value represented by the name/value pair that should be serialized,
           34  +            or undefined if nothing should be serialized. The toJSON method
           35  +            will be passed the key associated with the value, and this will be
           36  +            bound to the object holding the key.
           37  +
           38  +            For example, this would serialize Dates as ISO strings.
           39  +
           40  +                Date.prototype.toJSON = function (key) {
           41  +                    function f(n) {
           42  +                        // Format integers to have at least two digits.
           43  +                        return n < 10 ? '0' + n : n;
           44  +                    }
           45  +
           46  +                    return this.getUTCFullYear()   + '-' +
           47  +                         f(this.getUTCMonth() + 1) + '-' +
           48  +                         f(this.getUTCDate())      + 'T' +
           49  +                         f(this.getUTCHours())     + ':' +
           50  +                         f(this.getUTCMinutes())   + ':' +
           51  +                         f(this.getUTCSeconds())   + 'Z';
           52  +                };
           53  +
           54  +            You can provide an optional replacer method. It will be passed the
           55  +            key and value of each member, with this bound to the containing
           56  +            object. The value that is returned from your method will be
           57  +            serialized. If your method returns undefined, then the member will
           58  +            be excluded from the serialization.
           59  +
           60  +            If the replacer parameter is an array of strings, then it will be
           61  +            used to select the members to be serialized. It filters the results
           62  +            such that only members with keys listed in the replacer array are
           63  +            stringified.
           64  +
           65  +            Values that do not have JSON representations, such as undefined or
           66  +            functions, will not be serialized. Such values in objects will be
           67  +            dropped; in arrays they will be replaced with null. You can use
           68  +            a replacer function to replace those with JSON values.
           69  +            JSON.stringify(undefined) returns undefined.
           70  +
           71  +            The optional space parameter produces a stringification of the
           72  +            value that is filled with line breaks and indentation to make it
           73  +            easier to read.
           74  +
           75  +            If the space parameter is a non-empty string, then that string will
           76  +            be used for indentation. If the space parameter is a number, then
           77  +            the indentation will be that many spaces.
           78  +
           79  +            Example:
           80  +
           81  +            text = JSON.stringify(['e', {pluribus: 'unum'}]);
           82  +            // text is '["e",{"pluribus":"unum"}]'
           83  +
           84  +
           85  +            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
           86  +            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
           87  +
           88  +            text = JSON.stringify([new Date()], function (key, value) {
           89  +                return this[key] instanceof Date ?
           90  +                    'Date(' + this[key] + ')' : value;
           91  +            });
           92  +            // text is '["Date(---current time---)"]'
           93  +
           94  +
           95  +        JSON.parse(text, reviver)
           96  +            This method parses a JSON text to produce an object or array.
           97  +            It can throw a SyntaxError exception.
           98  +
           99  +            The optional reviver parameter is a function that can filter and
          100  +            transform the results. It receives each of the keys and values,
          101  +            and its return value is used instead of the original value.
          102  +            If it returns what it received, then the structure is not modified.
          103  +            If it returns undefined then the member is deleted.
          104  +
          105  +            Example:
          106  +
          107  +            // Parse the text. Values that look like ISO date strings will
          108  +            // be converted to Date objects.
          109  +
          110  +            myData = JSON.parse(text, function (key, value) {
          111  +                var a;
          112  +                if (typeof value === 'string') {
          113  +                    a =
          114  +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
          115  +                    if (a) {
          116  +                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
          117  +                            +a[5], +a[6]));
          118  +                    }
          119  +                }
          120  +                return value;
          121  +            });
          122  +
          123  +            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
          124  +                var d;
          125  +                if (typeof value === 'string' &&
          126  +                        value.slice(0, 5) === 'Date(' &&
          127  +                        value.slice(-1) === ')') {
          128  +                    d = new Date(value.slice(5, -1));
          129  +                    if (d) {
          130  +                        return d;
          131  +                    }
          132  +                }
          133  +                return value;
          134  +            });
          135  +
          136  +
          137  +    This is a reference implementation. You are free to copy, modify, or
          138  +    redistribute.
          139  +
          140  +    This code should be minified before deployment.
          141  +    See http://javascript.crockford.com/jsmin.html
          142  +
          143  +    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
          144  +    NOT CONTROL.
          145  +*/
          146  +
          147  +/*jslint evil: true */
          148  +
          149  +/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
          150  +    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
          151  +    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
          152  +    lastIndex, length, parse, prototype, push, replace, slice, stringify,
          153  +    test, toJSON, toString, valueOf
          154  +*/
          155  +
          156  +// Create a JSON object only if one does not already exist. We create the
          157  +// methods in a closure to avoid creating global variables.
          158  +
          159  +var JSON = JSON || {};
          160  +
          161  +(function () {
          162  +
          163  +    function f(n) {
          164  +        // Format integers to have at least two digits.
          165  +        return n < 10 ? '0' + n : n;
          166  +    }
          167  +
          168  +    if (typeof Date.prototype.toJSON !== 'function') {
          169  +
          170  +        Date.prototype.toJSON = function (key) {
          171  +
          172  +            return isFinite(this.valueOf()) ?
          173  +                   this.getUTCFullYear()   + '-' +
          174  +                 f(this.getUTCMonth() + 1) + '-' +
          175  +                 f(this.getUTCDate())      + 'T' +
          176  +                 f(this.getUTCHours())     + ':' +
          177  +                 f(this.getUTCMinutes())   + ':' +
          178  +                 f(this.getUTCSeconds())   + 'Z' : null;
          179  +        };
          180  +
          181  +        String.prototype.toJSON =
          182  +        Number.prototype.toJSON =
          183  +        Boolean.prototype.toJSON = function (key) {
          184  +            return this.valueOf();
          185  +        };
          186  +    }
          187  +
          188  +    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
          189  +        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
          190  +        gap,
          191  +        indent,
          192  +        meta = {    // table of character substitutions
          193  +            '\b': '\\b',
          194  +            '\t': '\\t',
          195  +            '\n': '\\n',
          196  +            '\f': '\\f',
          197  +            '\r': '\\r',
          198  +            '"' : '\\"',
          199  +            '\\': '\\\\'
          200  +        },
          201  +        rep;
          202  +
          203  +
          204  +    function quote(string) {
          205  +
          206  +// If the string contains no control characters, no quote characters, and no
          207  +// backslash characters, then we can safely slap some quotes around it.
          208  +// Otherwise we must also replace the offending characters with safe escape
          209  +// sequences.
          210  +
          211  +        escapable.lastIndex = 0;
          212  +        return escapable.test(string) ?
          213  +            '"' + string.replace(escapable, function (a) {
          214  +                var c = meta[a];
          215  +                return typeof c === 'string' ? c :
          216  +                    '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
          217  +            }) + '"' :
          218  +            '"' + string + '"';
          219  +    }
          220  +
          221  +
          222  +    function str(key, holder) {
          223  +
          224  +// Produce a string from holder[key].
          225  +
          226  +        var i,          // The loop counter.
          227  +            k,          // The member key.
          228  +            v,          // The member value.
          229  +            length,
          230  +            mind = gap,
          231  +            partial,
          232  +            value = holder[key];
          233  +
          234  +// If the value has a toJSON method, call it to obtain a replacement value.
          235  +
          236  +        if (value && typeof value === 'object' &&
          237  +                typeof value.toJSON === 'function') {
          238  +            value = value.toJSON(key);
          239  +        }
          240  +
          241  +// If we were called with a replacer function, then call the replacer to
          242  +// obtain a replacement value.
          243  +
          244  +        if (typeof rep === 'function') {
          245  +            value = rep.call(holder, key, value);
          246  +        }
          247  +
          248  +// What happens next depends on the value's type.
          249  +
          250  +        switch (typeof value) {
          251  +        case 'string':
          252  +            return quote(value);
          253  +
          254  +        case 'number':
          255  +
          256  +// JSON numbers must be finite. Encode non-finite numbers as null.
          257  +
          258  +            return isFinite(value) ? String(value) : 'null';
          259  +
          260  +        case 'boolean':
          261  +        case 'null':
          262  +
          263  +// If the value is a boolean or null, convert it to a string. Note:
          264  +// typeof null does not produce 'null'. The case is included here in
          265  +// the remote chance that this gets fixed someday.
          266  +
          267  +            return String(value);
          268  +
          269  +// If the type is 'object', we might be dealing with an object or an array or
          270  +// null.
          271  +
          272  +        case 'object':
          273  +
          274  +// Due to a specification blunder in ECMAScript, typeof null is 'object',
          275  +// so watch out for that case.
          276  +
          277  +            if (!value) {
          278  +                return 'null';
          279  +            }
          280  +
          281  +// Make an array to hold the partial results of stringifying this object value.
          282  +
          283  +            gap += indent;
          284  +            partial = [];
          285  +
          286  +// Is the value an array?
          287  +
          288  +            if (Object.prototype.toString.apply(value) === '[object Array]') {
          289  +
          290  +// The value is an array. Stringify every element. Use null as a placeholder
          291  +// for non-JSON values.
          292  +
          293  +                length = value.length;
          294  +                for (i = 0; i < length; i += 1) {
          295  +                    partial[i] = str(i, value) || 'null';
          296  +                }
          297  +
          298  +// Join all of the elements together, separated with commas, and wrap them in
          299  +// brackets.
          300  +
          301  +                v = partial.length === 0 ? '[]' :
          302  +                    gap ? '[\n' + gap +
          303  +                            partial.join(',\n' + gap) + '\n' +
          304  +                                mind + ']' :
          305  +                          '[' + partial.join(',') + ']';
          306  +                gap = mind;
          307  +                return v;
          308  +            }
          309  +
          310  +// If the replacer is an array, use it to select the members to be stringified.
          311  +
          312  +            if (rep && typeof rep === 'object') {
          313  +                length = rep.length;
          314  +                for (i = 0; i < length; i += 1) {
          315  +                    k = rep[i];
          316  +                    if (typeof k === 'string') {
          317  +                        v = str(k, value);
          318  +                        if (v) {
          319  +                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
          320  +                        }
          321  +                    }
          322  +                }
          323  +            } else {
          324  +
          325  +// Otherwise, iterate through all of the keys in the object.
          326  +
          327  +                for (k in value) {
          328  +                    if (Object.hasOwnProperty.call(value, k)) {
          329  +                        v = str(k, value);
          330  +                        if (v) {
          331  +                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
          332  +                        }
          333  +                    }
          334  +                }
          335  +            }
          336  +
          337  +// Join all of the member texts together, separated with commas,
          338  +// and wrap them in braces.
          339  +
          340  +            v = partial.length === 0 ? '{}' :
          341  +                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
          342  +                        mind + '}' : '{' + partial.join(',') + '}';
          343  +            gap = mind;
          344  +            return v;
          345  +        }
          346  +    }
          347  +
          348  +// If the JSON object does not yet have a stringify method, give it one.
          349  +
          350  +    if (typeof JSON.stringify !== 'function') {
          351  +        JSON.stringify = function (value, replacer, space) {
          352  +
          353  +// The stringify method takes a value and an optional replacer, and an optional
          354  +// space parameter, and returns a JSON text. The replacer can be a function
          355  +// that can replace values, or an array of strings that will select the keys.
          356  +// A default replacer method can be provided. Use of the space parameter can
          357  +// produce text that is more easily readable.
          358  +
          359  +            var i;
          360  +            gap = '';
          361  +            indent = '';
          362  +
          363  +// If the space parameter is a number, make an indent string containing that
          364  +// many spaces.
          365  +
          366  +            if (typeof space === 'number') {
          367  +                for (i = 0; i < space; i += 1) {
          368  +                    indent += ' ';
          369  +                }
          370  +
          371  +// If the space parameter is a string, it will be used as the indent string.
          372  +
          373  +            } else if (typeof space === 'string') {
          374  +                indent = space;
          375  +            }
          376  +
          377  +// If there is a replacer, it must be a function or an array.
          378  +// Otherwise, throw an error.
          379  +
          380  +            rep = replacer;
          381  +            if (replacer && typeof replacer !== 'function' &&
          382  +                    (typeof replacer !== 'object' ||
          383  +                     typeof replacer.length !== 'number')) {
          384  +                throw new Error('JSON.stringify');
          385  +            }
          386  +
          387  +// Make a fake root object containing our value under the key of ''.
          388  +// Return the result of stringifying the value.
          389  +
          390  +            return str('', {'': value});
          391  +        };
          392  +    }
          393  +
          394  +
          395  +// If the JSON object does not yet have a parse method, give it one.
          396  +
          397  +    if (typeof JSON.parse !== 'function') {
          398  +        JSON.parse = function (text, reviver) {
          399  +
          400  +// The parse method takes a text and an optional reviver function, and returns
          401  +// a JavaScript value if the text is a valid JSON text.
          402  +
          403  +            var j;
          404  +
          405  +            function walk(holder, key) {
          406  +
          407  +// The walk method is used to recursively walk the resulting structure so
          408  +// that modifications can be made.
          409  +
          410  +                var k, v, value = holder[key];
          411  +                if (value && typeof value === 'object') {
          412  +                    for (k in value) {
          413  +                        if (Object.hasOwnProperty.call(value, k)) {
          414  +                            v = walk(value, k);
          415  +                            if (v !== undefined) {
          416  +                                value[k] = v;
          417  +                            } else {
          418  +                                delete value[k];
          419  +                            }
          420  +                        }
          421  +                    }
          422  +                }
          423  +                return reviver.call(holder, key, value);
          424  +            }
          425  +
          426  +
          427  +// Parsing happens in four stages. In the first stage, we replace certain
          428  +// Unicode characters with escape sequences. JavaScript handles many characters
          429  +// incorrectly, either silently deleting them, or treating them as line endings.
          430  +
          431  +            cx.lastIndex = 0;
          432  +            if (cx.test(text)) {
          433  +                text = text.replace(cx, function (a) {
          434  +                    return '\\u' +
          435  +                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
          436  +                });
          437  +            }
          438  +
          439  +// In the second stage, we run the text against regular expressions that look
          440  +// for non-JSON patterns. We are especially concerned with '()' and 'new'
          441  +// because they can cause invocation, and '=' because it can cause mutation.
          442  +// But just to be safe, we want to reject all unexpected forms.
          443  +
          444  +// We split the second stage into 4 regexp operations in order to work around
          445  +// crippling inefficiencies in IE's and Safari's regexp engines. First we
          446  +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
          447  +// replace all simple value tokens with ']' characters. Third, we delete all
          448  +// open brackets that follow a colon or comma or that begin the text. Finally,
          449  +// we look to see that the remaining characters are only whitespace or ']' or
          450  +// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
          451  +
          452  +            if (/^[\],:{}\s]*$/.
          453  +test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
          454  +replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
          455  +replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
          456  +
          457  +// In the third stage we use the eval function to compile the text into a
          458  +// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
          459  +// in JavaScript: it can begin a block or an object literal. We wrap the text
          460  +// in parens to eliminate the ambiguity.
          461  +
          462  +                j = eval('(' + text + ')');
          463  +
          464  +// In the optional fourth stage, we recursively walk the new structure, passing
          465  +// each name/value pair to a reviver function for possible transformation.
          466  +
          467  +                return typeof reviver === 'function' ?
          468  +                    walk({'': j}, '') : j;
          469  +            }
          470  +
          471  +// If the text is not JSON parseable, then a SyntaxError is thrown.
          472  +
          473  +            throw new SyntaxError('JSON.parse');
          474  +        };
          475  +    }
          476  +}());

Added ajax/js/whajaj.js.

            1  +/**
            2  +    This file provides a JS interface into the core functionality of
            3  +    JSON-centric back-ends. It sends GET or JSON POST requests to
            4  +    a back-end and expects JSON responses. The exact semantics of
            5  +    the underlying back-end and overlying front-end are not its concern,
            6  +    and it leaves the interpretation of the data up to the client/server
            7  +    insofar as possible.
            8  +
            9  +    All functionality is part of a class named WhAjaj, and that class
           10  +    acts as namespace for this framework.
           11  +
           12  +    Author: Stephan Beal (http://wanderinghorse.net/home/stephan/)
           13  +
           14  +    License: Public Domain
           15  +    
           16  +    This framework is directly derived from code originally found in 
           17  +    http://code.google.com/p/jsonmessage, and later in 
           18  +    http://whiki.wanderinghorse.net, where it contained quite a bit 
           19  +    of application-specific logic. It was eventually (the 3rd time i 
           20  +    needed it) split off into its own library to simplify inclusion 
           21  +    into my many mini-projects.
           22  +*/
           23  +
           24  +
           25  +/**
           26  +    The WhAjaj function is primarily a namespace, and not intended 
           27  +    to called or instantiated via the 'new' operator.
           28  +*/
           29  +function WhAjaj()
           30  +{
           31  +}
           32  +
           33  +/** Returns a millisecond Unix Epoch timestamp. */
           34  +WhAjaj.msTimestamp = function()
           35  +{
           36  +    return (new Date()).getTime();
           37  +};
           38  +
           39  +/** Returns a Unix Epoch timestamp (in seconds) in integer format.
           40  +
           41  +    Reminder to self: (1.1 %1.2) evaluates to a floating-point value 
           42  +    in JS, and thus this implementation is less than optimal.
           43  +*/
           44  +WhAjaj.unixTimestamp = function()
           45  +{
           46  +    var ts = (new Date()).getTime();
           47  +    return parseInt( ""+((ts / 1000) % ts) );
           48  +};
           49  +
           50  +/**
           51  +    Returns true if v is-a Array instance.
           52  +*/
           53  +WhAjaj.isArray = function( v )
           54  +{
           55  +    return (v &&
           56  +            (v instanceof Array) ||
           57  +            (Object.prototype.toString.call(v) === "[object Array]")
           58  +            );
           59  +    /* Reminders to self:
           60  +        typeof [] == "object"
           61  +        toString.call([]) == "[object Array]"
           62  +        ([]).toString() == empty
           63  +    */
           64  +};
           65  +
           66  +/**
           67  +    Returns true if v is-a Object instance.
           68  +*/
           69  +WhAjaj.isObject = function( v )
           70  +{
           71  +    return v &&
           72  +        (v instanceof Object) &&
           73  +        ('[object Object]' === Object.prototype.toString.apply(v) );
           74  +};
           75  +
           76  +/**
           77  +    Returns true if v is-a Function instance.
           78  +*/
           79  +WhAjaj.isFunction = function(obj)
           80  +{
           81  +    return obj
           82  +    && (
           83  +    (obj instanceof Function)
           84  +    || ('function' === typeof obj)
           85  +    || ("[object Function]" === Object.prototype.toString.call(obj))
           86  +    )
           87  +    ;
           88  +};
           89  +
           90  +/**
           91  +    Parses window.location.search-style string into an object
           92  +    containing key/value pairs of URL arguments (already urldecoded).
           93  +    
           94  +    If the str argument is not passed (arguments.length==0) then 
           95  +    window.location.search.substring(1) is used by default. If 
           96  +    neither str is passed in nor window exists then false is returned.
           97  +
           98  +    On success it returns an Object containing the key/value pairs
           99  +    parsed from the string.
          100  +    
          101  +    FIXME: for keys in the form "name[]", build an array of results,
          102  +    like PHP does.
          103  + 
          104  +*/
          105  +WhAjaj.processUrlArgs = function(str) {
          106  +    if( 0 === arguments.length ) {
          107  +        if( (undefined === typeof window) ||
          108  +            !window.location ||
          109  +            !window.location.search )  return false;
          110  +        else str = (''+window.location.search).substring(1);
          111  +    }
          112  +    if( ! str ) return false;
          113  +    var args = {};
          114  +    var sp = str.split(/&+/);
          115  +    var rx = /^([^=]+)(=(.+))?/;
          116  +    var i, m;
          117  +    for( i in sp ) {
          118  +        m = rx.exec( sp[i] );
          119  +        if( ! m ) continue;
          120  +        args[decodeURIComponent(m[1])] = (m[3] ? decodeURIComponent(m[3]) : true);
          121  +    }
          122  +    return args;
          123  +};
          124  +
          125  +/**
          126  +    A simple wrapper around JSON.stringify(), using my own personal
          127  +    preferred values for the 2nd and 3rd parameters. To globally
          128  +    set its indentation level, assign WhAjaj.stringify.indent to
          129  +    an integer value (0 for no intendation).
          130  +    
          131  +    This function is intended only for human-readable output, not
          132  +    generic over-the-wire JSON output (where JSON.stringify(val) will
          133  +    produce smaller results).
          134  +*/
          135  +WhAjaj.stringify = function(val) {
          136  +    if( ! arguments.callee.indent ) arguments.callee.indent = 4;
          137  +    return JSON.stringify(val,0,arguments.callee.indent);
          138  +};
          139  +
          140  +/**
          141  +    Each instance of this class holds state information for making 
          142  +    AJAJ requests to a back-end system. While clients may use one 
          143  +    "requester" object per connection attempt, for connections to the
          144  +    same back-end, using an instance configured for that back-end 
          145  +    can simplify usage. This class is designed so that the actual 
          146  +    connection-related details (i.e. _how_ it connects to the 
          147  +    back-end) may be re-implemented to use a client's preferred 
          148  +    connection mechanism (e.g. jQuery).
          149  +    
          150  +    The optional opt paramater may be an object with any (or all) of 
          151  +    the properties documented for WhAjaj.Connector.options.ajax. 
          152  +    Properties set here (or later via modification of the "options" 
          153  +    property of this object) will be used in calls to 
          154  +    WhAjaj.Connector.sendRequest(), and these override (normally) any
          155  +    options set in WhAjaj.Connector.options.ajax. Note that 
          156  +    WhAjaj.Connector.sendRequest() _also_ takes an options object, 
          157  +    and ones passed there will override, for purposes of that one 
          158  +    request, any options passed in here or defined in 
          159  +    WhAjaj.Connector.options.ajax. See WhAjaj.Connector.options.ajax
          160  +    and WhAjaj.Connector.prototype.sendRequest() for more details
          161  +    about the precedence of options.
          162  +    
          163  +    Sample usage:
          164  +    
          165  +    @code
          166  +    // Set up common connection-level options:
          167  +    var cgi = new WhAjaj.Connector({
          168  +        url: '/cgi-bin/my.cgi',
          169  +        timeout:10000,
          170  +        onResponse(resp,req) { alert(JSON.stringify(resp,0.4)); },
          171  +        onError(req,opt) {
          172  +            alert(opt.errorMessage);
          173  +        }
          174  +    });
          175  +    // Any of those options may optionally be set globally in
          176  +    // WhAjaj.Connector.options.ajax (onError(), beforeSend(), and afterSend()
          177  +    // are often easiest/most useful to set globally).
          178  +
          179  +    // Get list of pages...
          180  +    cgi.sendRequest( null, {
          181  +        onResponse(resp,req){ alert(WhAjaj.stringify(resp)); }
          182  +    });
          183  +    @endcode
          184  +
          185  +    For common request types, clients can add functions to this
          186  +    object which act as wrappers for backend-specific functionality. As
          187  +    a simple example:
          188  +    
          189  +    @code
          190  +    cgi.login = function(name,pw,ajajOpt) {
          191  +        this.sendRequest( {name:name, password:pw}, ajajOpt );
          192  +    };
          193  +    @endcode
          194  +    
          195  +    TODOs:
          196  +    
          197  +    - Caching of page-load requests, with a configurable lifetime.
          198  +    
          199  +    - Use-cases like the above login() function are a tiny bit 
          200  +    problematic to implement when each request has a different URL 
          201  +    path (i know this from the whiki implementation). This is partly 
          202  +    a side-effect of design descisions made back in the very first 
          203  +    days of this code's life. i need to go through and see where i 
          204  +    can bend those conventions a bit (where it won't break my other 
          205  +    apps unduly).
          206  +*/
          207  +WhAjaj.Connector = function(opt)
          208  +{
          209  +    if(WhAjaj.isObject(opt)) this.options = opt;
          210  +    //TODO?: this.$cache = {};
          211  +};
          212  +
          213  +/**
          214  +    The core options used by WhAjaj.Connector instances for performing
          215  +    network operations. These options can (and some _should_)
          216  +    be changed by a client application. They can also be changed
          217  +    on specific instances of WhAjaj.Connector, but for most applications
          218  +    it is simpler to set them here and not have to bother with configuring
          219  +    each WhAjaj.Connector instance. Apps which use multiple back-ends at one time,
          220  +    however, will need to customize each instance for a given back-end.
          221  +*/
          222  +WhAjaj.Connector.options = {
          223  +    /**
          224  +        A (meaningless) prefix to apply to WhAjaj.Connector-generated
          225  +        request IDs.
          226  +    */
          227  +    requestIdPrefix:'WhAjaj.Connector-',
          228  +    /**
          229  +        Default options for WhAjaj.Connector.sendRequest() connection 
          230  +        parameters. This object holds only connection-related 
          231  +        options and callbacks (all optional), and not options 
          232  +        related to the required JSON structure of any given request. 
          233  +        i.e. the page name used in a get-page request are not set 
          234  +        here but are specified as part of the request object.
          235  +
          236  +        These connection options are a "normalized form" of options 
          237  +        often found in various AJAX libraries like jQuery, 
          238  +        Prototype, dojo, etc. This approach allows us to swap out 
          239  +        the real connection-related parts by writing a simple proxy 
          240  +        which transforms our "normalized" form to the 
          241  +        backend-specific form. For examples, see the various 
          242  +        implementations stored in WhAjaj.Connector.sendImpls.
          243  +
          244  +        The following callback options are, in practice, almost 
          245  +        always set globally to some app-wide defaults:
          246  +
          247  +        - onError() to report errors using a common mechanism.
          248  +        - beforeSend() to start a visual activity notification
          249  +        - afterSend() to disable the visual activity notification
          250  +
          251  +        However, be aware that if any given WhAjaj.Connector instance is 
          252  +        given its own before/afterSend callback then those will 
          253  +        override these. Mixing shared/global and per-instance
          254  +        callbacks can potentially lead to confusing results if, e.g.,
          255  +        the beforeSend() and afterSend() functions have side-effects
          256  +        but are not used with their proper before/after partner.
          257  +        
          258  +        TODO: rename this to 'ajaj' (the name is historical). The 
          259  +        problem with renaming it is is that the word 'ajax' is 
          260  +        pretty prevelant in the source tree, so i can't globally 
          261  +        swap it out.
          262  +    */
          263  +    ajax: {
          264  +        /**
          265  +            URL of the back-end server/CGI.
          266  +        */
          267  +        url: '/some/path',
          268  +
          269  +        /**
          270  +            Connection method. Some connection-related functions might
          271  +            override any client-defined setting.
          272  +            
          273  +            Must be one of 'GET' or 'POST'. For custom connection 
          274  +            implementation, it may optionally be some 
          275  +            implementation-specified value.
          276  +
          277  +            Normally the API can derive this value automatically - if the
          278  +            request uses JSON data it is POSTed, else it is GETted.
          279  +        */
          280  +        method:'GET',
          281  +
          282  +        /**
          283  +            A hint whether to run the operation asynchronously or 
          284  +            not. Not all concrete WhAjaj.Connector.sendImpl() 
          285  +            implementations can support this. Interestingly, at 
          286  +            least one popular AJAX toolkit does not document 
          287  +            supporting _synchronous_ AJAX operations. All common 
          288  +            browser-side implementations support async operation, but 
          289  +            non-browser implementations migth not.
          290  +        */
          291  +        asynchronous:true,
          292  +
          293  +        /**
          294  +            A HTTP authentication login name for the AJAX 
          295  +            connection. Not all concrete WhAjaj.Connector.sendImpl() 
          296  +            implementations can support this.
          297  +        */
          298  +        loginName:undefined,
          299  +
          300  +        /**
          301  +            An HTTP authentication login password for the AJAJ 
          302  +            connection. Not all concrete WhAjaj.Connector.sendImpl() 
          303  +            implementations can support this.
          304  +        */
          305  +        loginPassword:undefined,
          306  +
          307  +        /**
          308  +            A connection timeout, in milliseconds, for establishing 
          309  +            an AJAJ connection. Not all concrete 
          310  +            WhAjaj.Connector.sendImpl() implementations can support this.
          311  +        */
          312  +        timeout:10000,
          313  +
          314  +        /**
          315  +            If an AJAJ request receives JSON data from the back-end, that
          316  +            data is passed as a plain Object as the response parameter
          317  +            (exception: in jsonp mode it is passed a string). The initiating
          318  +            request object is passed as the second parameter, but clients
          319  +            can normally ignore it (only those which need a way to map
          320  +            specific requests to responses will need it). The 3rd parameter
          321  +            is the same as the 'this' object for the context of the callback,
          322  +            but is provided because the instance-level callbacks (set in
          323  +            (WhAjaj.Connector instance).callbacks, require it in some
          324  +            cases (because their 'this' is different!).
          325  +            
          326  +            Note that the response might contain error information which
          327  +            comes from the back-end. The difference between this error info
          328  +            and the info passed to the onError() callback is that this data
          329  +            indicates an application-level error, whereas onError() is used
          330  +            to report connection-level problems or when the backend produces
          331  +            non-JSON data (which, when not in jsonp mode, is unexpected and
          332  +            is as fatal to the request as a connection error).
          333  +        */
          334  +        onResponse: function(response, request, opt){},
          335  +
          336  +        /**
          337  +            If an AJAX request fails to establish a connection or it 
          338  +            receives non-JSON data from the back-end, this function 
          339  +            is called (e.g. timeout error or host name not 
          340  +            resolvable). It is passed the originating request and the
          341  +            "normalized" connection parameters used for that 
          342  +            request. The connectOpt object "should" (or "might") 
          343  +            have an "errorMessage" property which describes the 
          344  +            nature of the problem.
          345  +            
          346  +            Clients will almost always want to replace the default 
          347  +            implementation with something which integrates into 
          348  +            their application.
          349  +        */
          350  +        onError: function(request, connectOpt)
          351  +        {
          352  +            alert('AJAJ request failed:\n'
          353  +                +'Connection information:\n'
          354  +                +JSON.stringify(connectOpt,0,4)
          355  +            );
          356  +        },
          357  +
          358  +        /**
          359  +            Called before each connection attempt is made. Clients 
          360  +            can use this to, e.g.,  enable a visual "network activity
          361  +            notification" for the user. It is passed the original 
          362  +            request object and the normalized connection parameters 
          363  +            for the request. If this function changes opt, those 
          364  +            changes _are_ applied to the subsequent request. If this 
          365  +            function throws, neither the onError() nor afterSend() 
          366  +            callbacks are triggered and WhAjaj.Connector.sendImpl() 
          367  +            propagates the exception back to the caller.
          368  +        */
          369  +        beforeSend: function(request,opt){},
          370  +
          371  +        /**
          372  +            Called after an AJAJ connection attempt completes, 
          373  +            regardless of success or failure. Passed the same 
          374  +            parameters as beforeSend() (see that function for 
          375  +            details).
          376  +            
          377  +            Here's an example of setting up a visual notification on 
          378  +            ajax operations using jQuery (but it's also easy to do 
          379  +            without jQuery as well):
          380  +            
          381  +            @code
          382  +            function startAjaxNotif(req,opt) {
          383  +                var me = arguments.callee;
          384  +                var c = ++me.ajaxCount;
          385  +                me.element.text( c + " pending AJAX operation(s)..." );
          386  +                if( 1 == c ) me.element.stop().fadeIn();
          387  +            }
          388  +            startAjaxNotif.ajaxCount = 0.
          389  +            startAjaxNotif.element = jQuery('#whikiAjaxNotification');
          390  +            
          391  +            function endAjaxNotif() {
          392  +                var c = --startAjaxNotif.ajaxCount;
          393  +                startAjaxNotif.element.text( c+" pending AJAX operation(s)..." );
          394  +                if( 0 == c ) startAjaxNotif.element.stop().fadeOut();
          395  +            }
          396  +            @endcode
          397  +
          398  +            Set the beforeSend/afterSend properties to those 
          399  +            functions to enable the notifications by default.            
          400  +        */
          401  +        afterSend: function(request,opt){},
          402  +
          403  +        /**
          404  +            If jsonp is a string then the WhAjaj-internal response
          405  +            handling code ASSUMES that the response contains a JSONP-style
          406  +            construct and eval()s it after afterSend() but before onResponse().
          407  +            In this case, onResponse() will get a string value for the response
          408  +            instead of a response object parsed from JSON. 
          409  +        */
          410  +        jsonp:undefined,
          411  +        /**
          412  +            Don't use yet. Planned future option.
          413  +        */
          414  +        propagateExceptions:false
          415  +    }
          416  +};
          417  +
          418  +
          419  +/**
          420  +    WhAjaj.Connector.prototype.callbacks defines callbacks analog
          421  +    to the onXXX callbacks defined in WhAjaj.Connector.options.ajax,
          422  +    with two notable differences:
          423  +
          424  +    1) these callbacks, if set, are called in addition to any
          425  +    request-specific callback. The intention is to allow a framework to set
          426  +    "framework-level" callbacks which should be called independently of the
          427  +    request-specific callbacks (without interfering with them, e.g.
          428  +    requiring special re-forwarding features).
          429  +
          430  +    2) The 'this' object in these callbacks is the Connector instance
          431  +    associated with the callback, whereas the "other" onXXX form has its
          432  +    "ajax options" object as its this.
          433  +
          434  +    When this API says that an onXXX callback will be called for a request,
          435  +    both the request's onXXX (if set) and this one (if set) will be called.
          436  +*/
          437  +WhAjaj.Connector.prototype.callbacks = {};
          438  +/**
          439  +    Instance-specific values for AJAJ-level properties (as opposed to
          440  +    application-level request properties). Options set here "override" those
          441  +    specified in WhAjaj.Connector.options.ajax and are "overridden" by
          442  +    options passed to sendRequest().
          443  +*/
          444  +WhAjaj.Connector.prototype.options = {};
          445  +
          446  +
          447  +/**
          448  +    Tries to find the given key in any of the following, returning
          449  +    the first match found: opt, this.options, WhAjaj.Connector.options.ajax.
          450  +
          451  +    Returns undefined if key is not found.
          452  +*/
          453  +WhAjaj.Connector.prototype.derivedOption = function(key,opt) {
          454  +    var v = opt ? opt[key] : undefined;
          455  +    if( undefined !== v ) return v;
          456  +    else v = this.options[key];
          457  +    if( undefined !== v ) return v;
          458  +    else v = WhAjaj.Connector.options.ajax[key];
          459  +    return v;
          460  +};
          461  +
          462  +/**
          463  +    Returns a unique string on each call containing a generic 
          464  +    reandom request identifier string. This is not used by the core 
          465  +    API but can be used by client code to generate unique IDs for 
          466  +    each request (if needed).
          467  +
          468  +    The exact format is unspecified and may change in the future.
          469  +
          470  +    Request IDs can be used by clients to "match up" responses to 
          471  +    specific requests if needed. In practice, however, they are 
          472  +    seldom, if ever, needed. When passing several concurrent 
          473  +    requests through the same response callback, it might be useful 
          474  +    for some clients to be able to distinguish, possibly re-routing 
          475  +    them through other handlers based on the originating request type.
          476  +    
          477  +    If this.options.requestIdPrefix or 
          478  +    WhAjaj.Connector.options.requestIdPrefix is set then that text
          479  +    is prefixed to the returned string.
          480  +*/
          481  +WhAjaj.Connector.prototype.generateRequestId = function()
          482  +{
          483  +    if( undefined === arguments.callee.sequence )
          484  +    {
          485  +        arguments.callee.sequence = 0;
          486  +    }
          487  +    var pref = this.options.requestIdPrefix || WhAjaj.Connector.options.requestIdPrefix || '';
          488  +    return pref +
          489  +        WhAjaj.msTimestamp() +
          490  +        '/'+(Math.round( Math.random() * 100000000) )+
          491  +        ':'+(++arguments.callee.sequence);
          492  +};
          493  +
          494  +/**
          495  +    Copies (SHALLOWLY) all properties in opt to this.options.
          496  +*/
          497  +WhAjaj.Connector.prototype.addOptions = function(opt) {
          498  +    var k, v;
          499  +    for( k in opt ) {
          500  +        if( ! opt.hasOwnProperty(k) ) continue /* proactive Prototype kludge! */;
          501  +        this.options[k] = opt[k];
          502  +    }
          503  +    return this.options;
          504  +};
          505  +
          506  +/**
          507  +    An internal helper object which holds several functions intended 
          508  +    to simplify the creation of concrete communication channel 
          509  +    implementations for WhAjaj.Connector.sendImpl(). These operations
          510  +    take care of some of the more error-prone parts of ensuring that
          511  +    onResponse(), onError(), etc. callbacks are called consistently 
          512  +    using the same rules.
          513  +*/
          514  +WhAjaj.Connector.sendHelper = {
          515  +    /**
          516  +        opt is assumed to be a normalized set of 
          517  +        WhAjaj.Connector.sendRequest() options. This function 
          518  +        creates a url by concatenating opt.url and some form of 
          519  +        opt.urlParam.
          520  +        
          521  +        If opt.urlParam is an object or string then it is appended 
          522  +        to the url. An object is assumed to be a one-dimensional set 
          523  +        of simple (urlencodable) key/value pairs, and not larger 
          524  +        data structures. A string value is assumed to be a 
          525  +        well-formed, urlencoded set of key/value pairs separated by 
          526  +        '&' characters.
          527  +        
          528  +        The new/normalized URL is returned (opt is not modified). If 
          529  +        opt.urlParam is not set then opt.url is returned (or an 
          530  +        empty string if opt.url is itself a false value).
          531  +        
          532  +        TODO: if opt is-a Object and any key points to an array, 
          533  +        build up a list of keys in the form "keyname[]". We could 
          534  +        arguably encode sub-objects like "keyname[subkey]=...", but 
          535  +        i don't know if that's conventions-compatible with other 
          536  +        frameworks.
          537  +    */
          538  +    normalizeURL: function(opt) {
          539  +        var u = opt.url || '';
          540  +        if( opt.urlParam ) {
          541  +            var addQ = (u.indexOf('?') >= 0) ? false : true;
          542  +            var addA = addQ ? false : ((u.indexOf('&')>=0) ? true : false);
          543  +            var tail = '';
          544  +            if( WhAjaj.isObject(opt.urlParam) ) {
          545  +                var li = [], k;
          546  +                for( k in opt.urlParam) {
          547  +                    li.push( k+'='+encodeURIComponent( opt.urlParam[k] ) );
          548  +                }
          549  +                tail = li.join('&');
          550  +            }
          551  +            else if( 'string' === typeof opt.urlParam ) {
          552  +                tail = opt.urlParam;
          553  +            }
          554  +            u = u + (addQ ? '?' : '') + (addA ? '&' : '') + tail;
          555  +        }
          556  +        return u;
          557  +    },
          558  +    /**
          559  +        Should be called by WhAjaj.Connector.sendImpl() 
          560  +        implementations after a response has come back. This 
          561  +        function takes care of most of ensuring that framework-level 
          562  +        conventions involving WhAjaj.Connector.options.ajax 
          563  +        properties are followed.
          564  +        
          565  +        The request argument must be the original request passed to 
          566  +        the sendImpl() function. It may legally be null for GET requests.
          567  +        
          568  +        The opt object should be the normalized AJAX options used 
          569  +        for the connection.
          570  +        
          571  +        The resp argument may be either a plain Object or a string 
          572  +        (in which case it is assumed to be JSON).
          573  +
          574  +        The 'this' object for this call MUST be a WhAjaj.Connector
          575  +        instance in order for callback processing to work properly.
          576  +        
          577  +        This function takes care of the following:
          578  +        
          579  +        - Calling opt.afterSend()
          580  +        
          581  +        - If resp is a string, de-JSON-izing it to an object.
          582  +        
          583  +        - Calling opt.onResponse()
          584  +        
          585  +        - Calling opt.onError() in several common (potential) error 
          586  +        cases.
          587  +
          588  +        - If resp is-a String and opt.jsonp then resp is assumed to be
          589  +        a JSONP-form construct and is eval()d BEFORE opt.onResponse()
          590  +        is called. It is arguable to eval() it first, but the logic
          591  +        integrates better with the non-jsonp handler.
          592  +
          593  +        The sendImpl() should return immediately after calling this.
          594  +        
          595  +        The sendImpl() must call only one of onSendSuccess() or 
          596  +        onSendError(). It must call one of them or it must implement 
          597  +        its own response/error handling, which is not recommended 
          598  +        because getting the documented semantics of the 
          599  +        onError/onResponse/afterSend handling correct can be tedious.
          600  +    */
          601  +    onSendSuccess:function(request,resp,opt) {
          602  +        var cb = this.callbacks || {};
          603  +        if( WhAjaj.isFunction(cb.afterSend) ) {
          604  +            try {cb.afterSend( request, opt );}
          605  +            catch(e){}
          606  +        }
          607  +        if( WhAjaj.isFunction(opt.afterSend) ) {
          608  +            try {opt.afterSend( request, opt );}
          609  +            catch(e){}
          610  +        }
          611  +        function doErr(){
          612  +            if( WhAjaj.isFunction(cb.onError) ) {
          613  +                try {cb.onError( request, opt );}
          614  +                catch(e){}
          615  +            }
          616  +            if( WhAjaj.isFunction(opt.onError) ) {
          617  +                try {opt.onError( request, opt );}
          618  +                catch(e){}
          619  +            }
          620  +        }
          621  +        if( ! resp ) {
          622  +            opt.errorMessage = "Sending of request succeeded but returned no data!";
          623  +            doErr();
          624  +            return false;
          625  +        }
          626  +
          627  +        if( 'string' === typeof resp ) {
          628  +            try {
          629  +                resp = opt.jsonp ? eval(resp) : JSON.parse(resp);
          630  +            } catch(e) {
          631  +                opt.errorMessage = e.toString();
          632  +                doErr();
          633  +                return;
          634  +            }
          635  +        }
          636  +        try {
          637  +            if( WhAjaj.isFunction( cb.onResponse  ) ) {
          638  +                cb.onResponse( resp, request, opt );
          639  +            }
          640  +            if( WhAjaj.isFunction( opt.onResponse  ) ) {
          641  +                opt.onResponse( resp, request, opt );
          642  +            }
          643  +            return true;
          644  +        }
          645  +        catch(e) {
          646  +            opt.errorMessage = "Exception while handling inbound JSON response:\n"
          647  +                + e
          648  +                +"\nOriginal response data:\n"+JSON.stringify(resp,0,2)
          649  +                ;
          650  +            ;
          651  +            doErr();
          652  +            return false;
          653  +        }
          654  +    },
          655  +   /**
          656  +        Should be called by sendImpl() implementations after a response
          657  +        has failed to connect (e.g. could not resolve host or timeout
          658  +        reached). This function takes care of most of ensuring that
          659  +        framework-level conventions involving WhAjaj.Connector.options.ajax
          660  +        properties are followed.
          661  +        
          662  +        The request argument must be the original request passed to 
          663  +        the sendImpl() function. It may legally be null for GET 
          664  +        requests.
          665  +
          666  +        The 'this' object for this call MUST be a WhAjaj.Connector
          667  +        instance in order for callback processing to work properly.
          668  +        
          669  +        The opt object should be the normalized AJAX options used 
          670  +        for the connection. By convention, the caller of this 
          671  +        function "should" set opt.errorMessage to contain a 
          672  +        human-readable description of the error.
          673  +        
          674  +        The sendImpl() should return immediately after calling this. The
          675  +        return value from this function is unspecified.
          676  +    */
          677  +    onSendError: function(request,opt) {
          678  +        var cb = this.callbacks || {};
          679  +        if( WhAjaj.isFunction(cb.afterSend) ) {
          680  +            try {cb.afterSend( request, opt );}
          681  +            catch(e){}
          682  +        }
          683  +        if( WhAjaj.isFunction(opt.afterSend) ) {
          684  +            try {opt.afterSend( request, opt );}
          685  +            catch(e){}
          686  +        }
          687  +        if( WhAjaj.isFunction( cb.onError ) ) {
          688  +            try {cb.onError( request, opt );}
          689  +            catch(e) {/*ignore*/}
          690  +        }
          691  +        if( WhAjaj.isFunction( opt.onError ) ) {
          692  +            try {opt.onError( request, opt );}
          693  +            catch(e) {/*ignore*/}
          694  +        }
          695  +    }
          696  +};
          697  +
          698  +/**
          699  +    WhAjaj.Connector.sendImpls holds several concrete 
          700  +    implementations of WhAjaj.Connector.prototype.sendImpl(). To use 
          701  +    a specific implementation by default assign 
          702  +    WhAjaj.Connector.prototype.sendImpl to one of these functions.
          703  +    
          704  +    The functions defined here require that the 'this' object be-a
          705  +    WhAjaj.Connector instance.
          706  +    
          707  +    Historical notes:
          708  +
          709  +    a) We once had an implementation based on Prototype, but that 
          710  +    library just pisses me off (they change base-most types' 
          711  +    prototypes, introducing side-effects in client code which 
          712  +    doesn't even use Prototype). The Prototype version at the time 
          713  +    had a serious toJSON() bug which caused empty arrays to 
          714  +    serialize as the string "[]", which broke a bunch of my code. 
          715  +    (That has been fixed in the mean time, but i don't use 
          716  +    Prototype.)
          717  +    
          718  +    b) We once had an implementation for the dojo library, 
          719  +    
          720  +    If/when the time comes to add Prototype/dojo support, we simply
          721  +    need to port:
          722  +    
          723  +    http://code.google.com/p/jsonmessage/source/browse/trunk/lib/JSONMessage/JSONMessage.inc.js
          724  +    
          725  +    (search that file for "dojo" and "Prototype") to this tree. That 
          726  +    code is this code's generic grandfather and they are still very 
          727  +    similar, so a port is trivial.    
          728  +    
          729  +*/
          730  +WhAjaj.Connector.sendImpls = {
          731  +    /**
          732  +        This is a concrete implementation of 
          733  +        WhAjaj.Connector.prototype.sendImpl() which uses the 
          734  +        environment's native XMLHttpRequest class to send whiki 
          735  +        requests and fetch the responses.
          736  +
          737  +        The only argument must be a connection properties object, as 
          738  +        constructed by WhAjaj.Connector.normalizeAjaxParameters().
          739  +
          740  +        If window.firebug is set then window.firebug.watchXHR() is 
          741  +        called to enable monitoring of the XMLHttpRequest object.
          742  +
          743  +        This implementation honors the loginName and loginPassword 
          744  +        connection parameters.
          745  +
          746  +        Returns the XMLHttpRequest object.
          747  +
          748  +        This implementation requires that the 'this' object be-a 
          749  +        WhAjaj.Connector.
          750  +        
          751  +        This implementation uses setTimeout() to implement the 
          752  +        timeout support, and thus the JS engine must provide that 
          753  +        functionality.
          754  +    */
          755  +    XMLHttpRequest: function(request, args)
          756  +    {
          757  +        var json = WhAjaj.isObject(request) ? JSON.stringify(request) : request;
          758  +        var xhr = new XMLHttpRequest();
          759  +        var startTime = (new Date()).getTime();
          760  +        var timeout = args.timeout || 10000/*arbitrary!*/;
          761  +        var hitTimeout = false;
          762  +        var done = false;
          763  +        var tmid /* setTimeout() ID */;
          764  +        var whself = this;
          765  +        //if( json ) json = json.replace(/ö/g,"\\u00f6") /* ONLY FOR A SPECIFIC TEST */;
          766  +        //alert( 'json=\n'+json );
          767  +        function handleTimeout()
          768  +        {
          769  +            hitTimeout = true;
          770  +            if( ! done )
          771  +            {
          772  +                var now = (new Date()).getTime();
          773  +                try { xhr.abort(); } catch(e) {/*ignore*/}
          774  +                // see: http://www.w3.org/TR/XMLHttpRequest/#the-abort-method
          775  +                args.errorMessage = "Timeout of "+timeout+"ms reached after "+(now-startTime)+"ms during AJAX request.";
          776  +                WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] );
          777  +            }
          778  +            return;
          779  +        }
          780  +        function onStateChange()
          781  +        { // reminder to self: apparently 'this' is-not-a XHR :/
          782  +            if( hitTimeout )
          783  +            { /* we're too late - the error was already triggered. */
          784  +                return;
          785  +            }
          786  +
          787  +            if( 4 == xhr.readyState )
          788  +            {
          789  +                done = true;
          790  +                if( tmid )
          791  +                {
          792  +                    clearTimeout( tmid );
          793  +                    tmid = null;
          794  +                }
          795  +                if( (xhr.status >= 200) && (xhr.status < 300) )
          796  +                {
          797  +                    WhAjaj.Connector.sendHelper.onSendSuccess.apply( whself, [request, xhr.responseText, args] );
          798  +                    return;
          799  +                }
          800  +                else
          801  +                {
          802  +                    if( undefined === args.errorMessage )
          803  +                    {
          804  +                        args.errorMessage = "Error sending a '"+args.method+"' AJAX request to "
          805  +                                +"["+args.url+"]: "
          806  +                                +"Status text=["+xhr.statusText+"]"
          807  +                            ;
          808  +                        WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] );
          809  +                    }
          810  +                    else { /*maybe it was was set by the timeout handler. */ }
          811  +                    return;
          812  +                }
          813  +            }
          814  +        };
          815  +
          816  +        xhr.onreadystatechange = onStateChange;
          817  +        if( ('undefined'!==(typeof window)) && ('firebug' in window) && ('watchXHR' in window.firebug) )
          818  +        { /* plug in to firebug lite's XHR monitor... */
          819  +            window.firebug.watchXHR( xhr );
          820  +        }
          821  +        try
          822  +        {
          823  +            //alert( JSON.stringify( args  ));
          824  +            function xhrOpen()
          825  +            {
          826  +                if( ('loginName' in args) && args.loginName )
          827  +                {
          828  +                    xhr.open( args.method, args.url, args.asynchronous, args.loginName, args.loginPassword );
          829  +                }
          830  +                else
          831  +                {
          832  +                    xhr.open( args.method, args.url, args.asynchronous  );
          833  +                }
          834  +            }
          835  +            if( json && ('POST' ===  args.method.toUpperCase()) )
          836  +            {
          837  +                xhrOpen();
          838  +                xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");
          839  +                // Google Chrome warns that it refuses to set these
          840  +                // "unsafe" headers (his words, not mine):
          841  +                // xhr.setRequestHeader("Content-length", json.length);
          842  +                // xhr.setRequestHeader("Connection", "close");
          843  +                xhr.send( json );
          844  +            }
          845  +            else /* assume GET */
          846  +            {
          847  +                xhrOpen();
          848  +                xhr.send(null);
          849  +            }
          850  +            tmid = setTimeout( handleTimeout, timeout );
          851  +            return xhr;
          852  +        }
          853  +        catch(e)
          854  +        {
          855  +            args.errorMessage = e.toString();
          856  +            WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] );
          857  +            return undefined;
          858  +        }
          859  +    }/*XMLHttpRequest()*/,
          860  +    /**
          861  +        This is a concrete implementation of 
          862  +        WhAjaj.Connector.prototype.sendImpl() which uses the jQuery 
          863  +        AJAX API to send requests and fetch the responses.
          864  +
          865  +        The first argument may be either null/false, an Object 
          866  +        containing toJSON-able data to post to the back-end, or such an
          867  +        object in JSON string form.
          868  +
          869  +        The second argument must be a connection properties object, as 
          870  +        constructed by WhAjaj.Connector.normalizeAjaxParameters().
          871  +
          872  +        If window.firebug is set then window.firebug.watchXHR() is 
          873  +        called to enable monitoring of the XMLHttpRequest object.
          874  +
          875  +        This implementation honors the loginName and loginPassword 
          876  +        connection parameters.
          877  +
          878  +        Returns the XMLHttpRequest object.
          879  +
          880  +        This implementation requires that the 'this' object be-a 
          881  +        WhAjaj.Connector.
          882  +    */
          883  +    jQuery:function(request,args)
          884  +    {
          885  +        var data = request || undefined;
          886  +        var whself = this;
          887  +        if( data ) {
          888  +            if('string'!==typeof data) {
          889  +                try {
          890  +                    data = JSON.stringify(data);
          891  +                }
          892  +                catch(e) {
          893  +                    WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] );
          894  +                    return;
          895  +                }
          896  +            }
          897  +        }
          898  +        var ajopt = {
          899  +            url: args.url,
          900  +            data: data,
          901  +            type: args.method,
          902  +            async: args.asynchronous,
          903  +            password: (undefined !== args.loginPassword) ? args.loginPassword : undefined,
          904  +            username: (undefined !== args.loginName) ? args.loginName : undefined,
          905  +            contentType: 'application/json; charset=utf-8',
          906  +            error: function(xhr, textStatus, errorThrown)
          907  +            {
          908  +                //this === the options for this ajax request
          909  +                args.errorMessage = "Error sending a '"+ajopt.type+"' request to ["+ajopt.url+"]: "
          910  +                        +"Status text=["+textStatus+"]"
          911  +                        +(errorThrown ? ("Error=["+errorThrown+"]") : "")
          912  +                    ;
          913  +                WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] );
          914  +            },
          915  +            success: function(data)
          916  +            {
          917  +                WhAjaj.Connector.sendHelper.onSendSuccess.apply( whself, [request, data, args] );
          918  +            },
          919  +            /* Set dataType=text instead of json to keep jQuery from doing our carefully
          920  +                written response handling for us.
          921  +            */
          922  +            dataType: 'text'
          923  +        };
          924  +        if( undefined !== args.timeout )
          925  +        {
          926  +            ajopt.timeout = args.timeout;
          927  +        }
          928  +        try
          929  +        {
          930  +            return jQuery.ajax(ajopt);
          931  +        }
          932  +        catch(e)
          933  +        {
          934  +            args.errorMessage = e.toString();
          935  +            WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] );
          936  +            return undefined;
          937  +        }
          938  +    }/*jQuery()*/,
          939  +    /**
          940  +        This is a concrete implementation of 
          941  +        WhAjaj.Connector.prototype.sendImpl() which uses the rhino 
          942  +        Java API to send requests and fetch the responses.
          943  +
          944  +        Limitations vis-a-vis the interface:
          945  +
          946  +        - timeouts are not supported.
          947  +
          948  +        - asynchronous mode is not supported because implementing it
          949  +        requires the ability to kill a running thread (which is deprecated
          950  +        in the Java API).
          951  +
          952  +        TODOs:
          953  +
          954  +        - add socket timeouts.
          955  +
          956  +        - support HTTP proxy.
          957  +
          958  +        The Java APIs support this, it just hasn't been added here yet.
          959  +    */
          960  +    rhino:function(request,args)
          961  +    {
          962  +        var self = this;
          963  +        var data = request || undefined;
          964  +        if( data ) {
          965  +            if('string'!==typeof data) {
          966  +                try {
          967  +                    data = JSON.stringify(data);
          968  +                }
          969  +                catch(e) {
          970  +                    WhAjaj.Connector.sendHelper.onSendError.apply( self, [request, args] );
          971  +                    return;
          972  +                }
          973  +            }
          974  +        }
          975  +        var url;
          976  +        var con;
          977  +        var IO = new JavaImporter(java.io);
          978  +        var wr;
          979  +        var rd, ln, json = [];
          980  +        function setIncomingCookies(list){
          981  +            if(!list || !list.length) return;
          982  +            if( !self.cookies ) self.cookies = {};
          983  +            var k, v, i;
          984  +            for( i = 0; i < list.length; ++i ){
          985  +                v = list[i].split('=',2);
          986  +                k = decodeURIComponent(v[0])
          987  +                v = v[0] ? decodeURIComponent(v[0].split(';',2)[0]) : null;
          988  +                //print("RECEIVED COOKIE: "+k+"="+v);
          989  +                if(!v) {
          990  +                    delete self.cookies[k];
          991  +                    continue;
          992  +                }else{
          993  +                    self.cookies[k] = v;
          994  +                }
          995  +            }
          996  +        };
          997  +        function setOutboundCookies(conn){
          998  +            if(!self.cookies) return;
          999  +            var k, v;
         1000  +            for( k in self.cookies ){
         1001  +                if(!self.cookies.hasOwnProperty(k)) continue /*kludge for broken JS libs*/;
         1002  +                v = self.cookies[k];
         1003  +                conn.addRequestProperty("Cookie", encodeURIComponent(k)+'='+encodeURIComponent(v));
         1004  +                //print("SENDING COOKIE: "+k+"="+v);
         1005  +            }
         1006  +        };
         1007  +        try{
         1008  +            url = new java.net.URL( args.url )
         1009  +            con = url.openConnection(/*FIXME: add proxy support!*/);
         1010  +            con.setRequestProperty("Accept-Charset","utf-8");
         1011  +            setOutboundCookies(con);
         1012  +            if(data){
         1013  +                con.setRequestProperty("Content-Type","application/json; charset=utf-8");
         1014  +                con.setDoOutput( true );
         1015  +                wr = new IO.OutputStreamWriter(con.getOutputStream())
         1016  +                wr.write(data);
         1017  +                wr.flush();
         1018  +                wr.close();
         1019  +                wr = null;
         1020  +                //print("POSTED: "+data);
         1021  +            }
         1022  +            rd = new IO.BufferedReader(new IO.InputStreamReader(con.getInputStream()));
         1023  +            //var skippedHeaders = false;
         1024  +            while ((line = rd.readLine()) !== null) {
         1025  +                //print("LINE: "+line);
         1026  +                //if(!line.length && !skippedHeaders){
         1027  +                //    skippedHeaders = true;
         1028  +                // json = [];
         1029  +                //    continue;
         1030  +                //}
         1031  +                json.push(line);
         1032  +            }
         1033  +            setIncomingCookies(con.getHeaderFields().get("Set-Cookie"));
         1034  +        }catch(e){
         1035  +            args.errorMessage = e.toString();
         1036  +            WhAjaj.Connector.sendHelper.onSendError.apply( self, [request, args] );
         1037  +            return undefined;
         1038  +        }
         1039  +        try { if(wr) wr.close(); } catch(e) { /*ignore*/}
         1040  +        try { if(rd) rd.close(); } catch(e) { /*ignore*/}
         1041  +        json = json.join('');
         1042  +        //print("READ IN JSON: "+json);
         1043  +        WhAjaj.Connector.sendHelper.onSendSuccess.apply( self, [request, json, args] );
         1044  +    }/*rhino*/
         1045  +};
         1046  +
         1047  +/**
         1048  +    An internal function which takes an object containing properties 
         1049  +    for a WhAjaj.Connector network request. This function creates a new 
         1050  +    object containing a superset of the properties from:
         1051  +
         1052  +    a) opt
         1053  +    b) this.options
         1054  +    c) WhAjaj.Connector.options.ajax
         1055  +
         1056  +    in that order, using the first one it finds.
         1057  +
         1058  +    All non-function properties are _deeply_ copied via JSON cloning 
         1059  +    in order to prevent accidental "cross-request pollenation" (been 
         1060  +    there, done that). Functions cannot be cloned and are simply 
         1061  +    copied by reference.
         1062  +    
         1063  +    This function throws if JSON-copying one of the options fails
         1064  +    (e.g. due to cyclic data structures).
         1065  +    
         1066  +    Reminder to self: this function does not "normalize" opt.urlParam
         1067  +    by encoding it into opt.url, mainly for historical reasons, but
         1068  +    also because that behaviour was specifically undesirable in this
         1069  +    code's genetic father.
         1070  +*/
         1071  +WhAjaj.Connector.prototype.normalizeAjaxParameters = function (opt)
         1072  +{
         1073  +    var rc = {};
         1074  +    function merge(k,v)
         1075  +    {
         1076  +        if( rc.hasOwnProperty(k) ) return;
         1077  +        else if( WhAjaj.isFunction(v) ) {}
         1078  +        else if( WhAjaj.isObject(v) ) v = JSON.parse( JSON.stringify(v) );
         1079  +        rc[k]=v;
         1080  +    }
         1081  +    function cp(obj) {
         1082  +        if( ! WhAjaj.isObject(obj) ) return;
         1083  +        var k;
         1084  +        for( k in obj ) {
         1085  +            if( ! obj.hasOwnProperty(k) ) continue /* i will always hate the Prototype designers for this. */;
         1086  +            merge(k, obj[k]);
         1087  +        }
         1088  +    }
         1089  +    cp( opt );
         1090  +    cp( this.options );
         1091  +    cp( WhAjaj.Connector.options.ajax );
         1092  +    // no, not here: rc.url = WhAjaj.Connector.sendHelper.normalizeURL(rc);
         1093  +    return rc;
         1094  +};
         1095  +
         1096  +/**
         1097  +    This is the generic interface for making calls to a back-end 
         1098  +    JSON-producing request handler. It is a simple wrapper around 
         1099  +    WhAjaj.Connector.prototype.sendImpl(), which just normalizes the 
         1100  +    connection options for sendImpl() and makes sure that 
         1101  +    opt.beforeSend() is (possibly) called.
         1102  +    
         1103  +    The request parameter must either be false/null/empty or a 
         1104  +    fully-populated JSON-able request object (which will be sent as 
         1105  +    unencoded application/json text), depending on the type of 
         1106  +    request being made. It is never semantically legal (in this API) 
         1107  +    for request to be a string/number/true/array value. As a rule, 
         1108  +    only POST requests use the request data. GET requests should 
         1109  +    encode their data in opt.url or opt.urlParam (see below).
         1110  +    
         1111  +    opt must contain the network-related parameters for the request. 
         1112  +    Paramters _not_ set in opt are pulled from this.options or 
         1113  +    WhAjaj.Connector.options.ajax (in that order, using the first 
         1114  +    value it finds). Thus the set of connection-level options used 
         1115  +    for the request are a superset of those various sources.
         1116  +    
         1117  +    The "normalized" (or "superimposed") opt object's URL may be 
         1118  +    modified before the request is sent, as follows:
         1119  +
         1120  +    if opt.urlParam is a string then it is assumed to be properly 
         1121  +    URL-encoded parameters and is appended to the opt.url. If it is 
         1122  +    an Object then it is assumed to be a one-dimensional set of 
         1123  +    key/value pairs with simple values (numbers, strings, booleans, 
         1124  +    null, and NOT objects/arrays). The keys/values are URL-encoded 
         1125  +    and appended to the URL.
         1126  +
         1127  +    The beforeSend() callback (see below) can modify the options
         1128  +    object before the request attempt is made.
         1129  +   
         1130  +    The callbacks in the normalized opt object will be triggered as
         1131  +    follows (if they are set to Function values):
         1132  +    
         1133  +    - beforeSend(request,opt) will be called before any network 
         1134  +    processing starts. If beforeSend() throws then no other 
         1135  +    callbacks are triggered and this function propagates the 
         1136  +    exception. This function is passed normalized connection options
         1137  +    as its second parameter, and changes this function makes to that
         1138  +    object _will_ be used for the pending connection attempt.
         1139  +    
         1140  +    - onError(request,opt) will be called if a connection to the 
         1141  +    back-end cannot be established. It will be passed the original 
         1142  +    request object (which might be null, depending on the request 
         1143  +    type) and the normalized options object. In the error case, the 
         1144  +    opt object passed to onError() "should" have a property called 
         1145  +    "errorMessage" which contains a description of the problem.
         1146  +    
         1147  +    - onError(request,opt) will also be called if connection 
         1148  +    succeeds but the response is not JSON data.
         1149  +    
         1150  +    - onResponse(response,request) will be called if the response 
         1151  +    returns JSON data. That data might hold an error response code - 
         1152  +    clients need to check for that. It is passed the response object 
         1153  +    (a plain object) and the original request object.
         1154  +    
         1155  +    - afterSend(request,opt) will be called directly after the
         1156  +    AJAX request is finished, before onError() or onResonse() are
         1157  +    called. Possible TODO: we explicitly do NOT pass the response to
         1158  +    this function in order to keep the line between the responsibilities
         1159  +    of the various callback clear (otherwise this could be used the same
         1160  +    as onResponse()). In practice it would sometimes be useful have the
         1161  +    response passed to this function, mainly for logging/debugging
         1162  +    purposes.
         1163  +
         1164  +    The return value from this function is meaningless because
         1165  +    AJAX operations tend to take place asynchronously.
         1166  +
         1167  +*/
         1168  +WhAjaj.Connector.prototype.sendRequest = function(request,opt)
         1169  +{
         1170  +    if( !WhAjaj.isFunction(this.sendImpl) )
         1171  +    {
         1172  +        throw new Error("This object has no sendImpl() member function! I don't know how to send the request!");
         1173  +    }
         1174  +    var ex = false;
         1175  +    var av = Array.prototype.slice.apply( arguments, [0] );
         1176  +    
         1177  +    /**
         1178  +        FIXME: how to handle the error, vis-a-vis- the callbacks, if 
         1179  +        normalizeAjaxParameters() throws? It can throw if 
         1180  +        (de)JSON-izing fails.
         1181  +    */
         1182  +    var norm = this.normalizeAjaxParameters( WhAjaj.isObject(opt) ? opt : {} );
         1183  +    norm.url = WhAjaj.Connector.sendHelper.normalizeURL(norm);
         1184  +    if( ! request ) norm.method = 'GET';
         1185  +    var cb = this.callbacks || {};
         1186  +    if( this.callbacks && WhAjaj.isFunction(this.callbacks.beforeSend) ) {
         1187  +        this.callbacks.beforeSend( request, norm );
         1188  +    }
         1189  +    if( WhAjaj.isFunction(norm.beforeSend) ){
         1190  +        norm.beforeSend( request, norm );
         1191  +    }
         1192  +    //alert( WhAjaj.stringify(request)+'\n'+WhAjaj.stringify(norm));
         1193  +    try { this.sendImpl( request, norm ); }
         1194  +    catch(e) { ex = e; }
         1195  +    if(ex) throw ex;
         1196  +};
         1197  +
         1198  +/**
         1199  +    sendImpl() holds a concrete back-end connection implementation. It
         1200  +    can be replaced with a custom implementation if one follows the rules
         1201  +    described throughout this API. See WhAjaj.Connector.sendImpls for
         1202  +    the concrete implementations included with this API.
         1203  +*/
         1204  +//WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.XMLHttpRequest;
         1205  +//WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.rhino;
         1206  +//WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.jQuery;
         1207  +
         1208  +if( 'undefined' !== typeof jQuery ){
         1209  +    WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.jQuery;
         1210  +}
         1211  +else {
         1212  +    WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.XMLHttpRequest;
         1213  +}

Added ajax/wiki-editor.html.

            1  +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
            2  +	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
            3  +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
            4  +
            5  +<head>
            6  +	<title>Fossil/JSON Wiki Editor Prototype</title>
            7  +	<meta http-equiv="content-type" content="text/html;charset=utf-8" />
            8  +    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script> 
            9  +    <script type="text/javascript" src="js/whajaj.js"></script>
           10  +    <script type="text/javascript" src="js/fossil-ajaj.js"></script>
           11  +
           12  +<style type='text/css'>
           13  +th {
           14  +  text-align: left;
           15  +  background-color: #ececec;  
           16  +}
           17  +
           18  +.dangerWillRobinson {
           19  +    background-color: yellow;
           20  +}
           21  +
           22  +.wikiPageLink {
           23  +    text-decoration: underline;
           24  +}
           25  +</style>
           26  +
           27  +<script type='text/javascript'>
           28  +WhAjaj.Connector.options.ajax.url =
           29  +/*
           30  +    Change this to your CGI/server root path:
           31  +*/
           32  +     //'http://fjson/cgi-bin/fossil.cgi'
           33  +     //'/repos/fossil-sgb/json.cgi'
           34  +    '/cgi-bin/fossil-json.cgi'
           35  +     ;
           36  +var TheApp = {
           37  +      response:null,
           38  +      sessionID:null,
           39  +      jqe:{}/*jqe==jQuery Elements*/,
           40  +      ajaxCount:0,
           41  +      cgi: new FossilAjaj(),
           42  +      pages:{}
           43  +};
           44  +
           45  +
           46  +TheApp.startAjaxNotif = function()
           47  +{
           48  +    ++this.ajaxCount;
           49  +    TheApp.jqe.responseContainer.removeClass('dangerWillRobinson');
           50  +    this.jqe.ajaxNotification.attr( 'title', this.ajaxCount+" pending AJAX operation(s)..." );
           51  +    if( 1 == this.ajaxCount ) this.jqe.ajaxNotification.fadeIn();
           52  +};
           53  +
           54  +TheApp.endAjaxNotif = function()
           55  +{
           56  +    --this.ajaxCount;
           57  +    this.jqe.ajaxNotification.attr( 'title', this.ajaxCount+" pending AJAX operation(s)..." );
           58  +    if( 0 == this.ajaxCount ) this.jqe.ajaxNotification.fadeOut();
           59  +};
           60  +
           61  +TheApp.responseContainsError = function(resp) {
           62  +    if( !resp || resp.resultCode ) {
           63  +        //alert("Error response:\n"+JSON.stringify(resp,0,4));
           64  +        TheApp.jqe.taResponse.val( "RESPONSE CONTAINS ERROR INFO:\n"+WhAjaj.stringify(resp) );
           65  +        //TheApp.jqe.responseContainer.css({backgroundColor:'yellow'});
           66  +        //TheApp.jqe.responseContainer.addClass('dangerWillRobinson');
           67  +        TheApp.jqe.responseContainer.flash( '255,0,0', 1500 );
           68  +        return true;
           69  +    }
           70  +    return false;
           71  +};
           72  +
           73  +
           74  +TheApp.sendRequest = function() {
           75  +    var path = this.jqe.textPath.val();
           76  +    var self = this;
           77  +    var data = this.jqe.pageListArea.val();
           78  +    var doPost = (data && data.length);
           79  +    var req;
           80  +    if( doPost ) try {
           81  +        req = JSON.parse(data);
           82  +    }
           83  +    catch(e) {
           84  +        TheApp.jqe.taResponse.val("Request is not valid JSON.\n"+e);
           85  +        return;
           86  +    }
           87  +    if( req ) {
           88  +        req.requestId = this.cgi.generateRequestId();
           89  +    }
           90  +    var self = this;
           91  +    var opt = {
           92  +        url: WhAjaj.Connector.options.ajax.url + path,
           93  +        method: doPost ? 'POST' : 'GET'
           94  +    };
           95  +    this.cgi.sendRequest( req, opt );
           96  +};
           97  +jQuery.fn.animateHighlight = function(highlightColor, duration) {
           98  +    var highlightBg = highlightColor || "#FFFF9C";
           99  +    var animateMs = duration || 1500;
          100  +    var originalBg = this.css("backgroundColor");
          101  +    this.stop().css("background-color", highlightBg).animate({backgroundColor: originalBg}, animateMs);
          102  +};
          103  +jQuery.fn.flash = function( color, duration )
          104  +{
          105  +    var current = this.css( 'color' );
          106  +    this.animate( { color: 'rgb(' + color + ')' }, duration / 2);
          107  +    this.animate( { color: current }, duration / 2 );
          108  +};
          109  +
          110  +jQuery(document).ready(function(){
          111  +    var ids = [
          112  +        'btnSend',
          113  +        'ajaxNotification',
          114  +        'currentAuthToken',
          115  +        'responseContainer',
          116  +        'spanPageName',
          117  +        'pageListArea',
          118  +        'taPageContent',
          119  +        'taResponse',
          120  +        'textPath', // list of HTML element IDs we use often.
          121  +        'timer'
          122  +    ];
          123  +    var i, k;
          124  +    for( i = 0; i < ids.length; ++i ) {
          125  +        k = ids[i];
          126  +        TheApp.jqe[k] = jQuery('#'+k);
          127  +    }
          128  +    TheApp.jqe.textPath.
          129  +        keyup(function(event){
          130  +            if(event.keyCode == 13){
          131  +                TheApp.sendRequest();
          132  +            }
          133  +        });
          134  +    TheApp.timer = {
          135  +        _tstart:0,_tend:0,duration:0,
          136  +        start:function(){
          137  +            this._tstart = (new Date()).getTime();
          138  +        },
          139  +        end:function(){
          140  +            this._tend = (new Date()).getTime();
          141  +            return this.duration = this._tend - this._tstart;
          142  +        }
          143  +    };
          144  +    var ajcb = TheApp.cgi.ajaj.callbacks;
          145  +    ajcb.beforeSend = TheApp.beforeSend = function(req,opt) {
          146  +        TheApp.timer.start();
          147  +        var val =
          148  +            req ?
          149  +            (('string'===typeof req) ? req : WhAjaj.stringify(req))
          150  +            : '';
          151  +        TheApp.jqe.taResponse.val('');
          152  +        TheApp.startAjaxNotif();
          153  +    };
          154  +    ajcb.afterSend = TheApp.afterSend = function(req,opt) {
          155  +        TheApp.timer.end();
          156  +        TheApp.endAjaxNotif();
          157  +        TheApp.jqe.timer.text( "(Round-trip time: "+TheApp.timer.duration+'ms)' );
          158  +    };
          159  +    ajcb.onResponse = TheApp.onResponse = function(resp,req) {
          160  +        var val;
          161  +        try {
          162  +            val = WhAjaj.stringify(resp);
          163  +        }
          164  +        catch(e) {
          165  +            val = WhAjaj.stringify(e)
          166  +        }
          167  +        if(resp.resultCode){
          168  +            alert("Response contains error info:\n"+val);
          169  +        }
          170  +        TheApp.jqe.taResponse.val( val );
          171  +    };
          172  +    ajcb.onError = function(req,opt) {
          173  +        TheApp.jqe.taResponse.val( "ERROR SENDING REQUEST:\n"+WhAjaj.stringify(opt) );
          174  +    };
          175  +
          176  +    TheApp.jqe.taPageContent.blur(function(){
          177  +        var p = TheApp.currentPage;
          178  +        if(! p ) return;
          179  +        p.content = TheApp.jqe.taPageContent.val();
          180  +    });
          181  +
          182  +    TheApp.cgi.onLogin = function(){
          183  +      TheApp.jqe.taResponse.val( "Logged in: "+WhAjaj.stringify(this.auth));
          184  +      TheApp.jqe.currentAuthToken.text("Logged in: "+WhAjaj.stringify(this.auth));
          185  +    };
          186  +    TheApp.cgi.onLogout = function(){
          187  +      TheApp.jqe.taResponse.val( "Logged out!" );
          188  +      TheApp.jqe.currentAuthToken.text("");
          189  +    };
          190  +
          191  +    TheApp.showPage = function(name){
          192  +        function doShow(page){
          193  +            TheApp.currentPage = page;
          194  +            TheApp.jqe.spanPageName.text('('+page.name+')');
          195  +            TheApp.jqe.taPageContent.val(page.content);
          196  +        }
          197  +        var p = ('object' === typeof name) ? name : TheApp.pages[name];
          198  +        if(('object' === typeof p) && p.content) {
          199  +            doShow(p);
          200  +            return;
          201  +        }
          202  +        TheApp.cgi.sendCommand('/json/wiki/get',{
          203  +            name:name
          204  +        },{
          205  +            onResponse:function(resp,req){
          206  +                TheApp.onResponse(resp,req);
          207  +                if(resp.resultCode) return;
          208  +                var p = resp.payload;
          209  +                doShow( TheApp.pages[p.name] = p );
          210  +            }
          211  +        });
          212  +    };
          213  +    TheApp.refreshPageListView = function(){
          214  +        var list = (function(){
          215  +            var k, v, li = [];
          216  +            for( k in TheApp.pages ){
          217  +                if(!TheApp.pages.hasOwnProperty(k)) continue;
          218  +                li.push(k);
          219  +            }
          220  +            return li;                
          221  +        })();
          222  +        var i, p, a, tgt = TheApp.jqe.pageListArea;
          223  +        tgt.text('');
          224  +        function makeLink(name){
          225  +            var link = jQuery('<span></span>');
          226  +            link.text(name);
          227  +            link.addClass('wikiPageLink');
          228  +            link.click(function(e){
          229  +                TheApp.showPage(name);
          230  +                e.preventDefault();
          231  +                return false;
          232  +            });
          233  +            return link;
          234  +        }
          235  +        list.sort();
          236  +        for( i = 0; i < list.length; ++i ){
          237  +            tgt.append(makeLink(list[i]));
          238  +            tgt.append('<br/>');
          239  +        }
          240  +    };
          241  +
          242  +    TheApp.loadPageList = function(){
          243  +        TheApp.cgi.sendCommand('/json/wiki/list',null,{
          244  +            onResponse:function(resp,req){
          245  +                TheApp.onResponse(resp,req);
          246  +                if(resp.resultCode) return;
          247  +                var i, v, p, ar = resp.payload;
          248  +                for( i = 0; i < ar.length; ++i ){
          249  +                    v = ar[i];
          250  +                    p = TheApp.pages[v];
          251  +                    if( !p ) TheApp.pages[v] = {name:v};
          252  +                }
          253  +                TheApp.refreshPageListView();
          254  +            }
          255  +        });
          256  +        return false /*for click handlers*/;
          257  +    }
          258  +
          259  +    TheApp.savePage = function(p){
          260  +        p = p || TheApp.currentPage;
          261  +        if( 'object' !== typeof p ){
          262  +            p = TheApp.pages[p];
          263  +        }
          264  +        if('object' !== typeof p){
          265  +            alert("savePage() argument is not a page object or known page name.");
          266  +        }
          267  +        TheApp.pages[p.name] = p;
          268  +        p.content = TheApp.jqe.taPageContent.val();
          269  +        var req = {
          270  +            name:p.name,
          271  +            content:p.content
          272  +        };
          273  +        if(! confirm("Really save wiki page ["+p.name+"]?") ) return;
          274  +        TheApp.cgi.sendCommand('/json/wiki/'+(p.isNew?'create':'save'),req,{
          275  +            onResponse:function(resp,req){
          276  +                TheApp.onResponse(resp,req);
          277  +                if(resp.resultCode) return;
          278  +                delete p.isNew;
          279  +                p.timestamp = resp.payload.timestamp;
          280  +            }
          281  +        });
          282  +        
          283  +    };
          284  +
          285  +    TheApp.createNewPage = function(){
          286  +        var name = prompt("New page name?");
          287  +        if(!name) return;
          288  +        var p = {
          289  +            name:name,
          290  +            content:"New, empty page.",
          291  +            isNew:true
          292  +        };
          293  +        TheApp.pages[name] = p;
          294  +        TheApp.refreshPageListView();
          295  +        TheApp.showPage(p);
          296  +/*
          297  +        if(! confirm("Really create new wiki page ["+name+"]?") ) return;
          298  +        TheApp.cgi.sendCommand('/json/wiki/create',req,{
          299  +            onResponse:function(resp,req){
          300  +                TheApp.onResponse(resp,req);
          301  +                if(resp.resultCode) return;
          302  +                TheApp.pages[p.name] = p;
          303  +                TheApp.refreshPageListView();
          304  +            }
          305  +        });
          306  +*/
          307  +    };
          308  +
          309  +    TheApp.cgi.whoami();
          310  +
          311  +});
          312  +
          313  +</script>
          314  +
          315  +</head>
          316  +
          317  +<body>
          318  +<span id='ajaxNotification'></span>
          319  +<h1>PROTOTYPE JSON-based Fossil Wiki Editor</h1>
          320  +
          321  +See also: <a href='index.html'>main test page</a>.
          322  +
          323  +<br>
          324  +<b>Login:</b>
          325  +<br/>
          326  +<input type='button' value='Anon. Login' onclick='TheApp.cgi.login()' />
          327  +or: 
          328  +name:<input type='text' id='textUser' value='json-demo' size='12'/>
          329  +pw:<input type='password' id='textPassword' value='json-demo' size='12'/>
          330  +<input type='button' value='login' onclick='TheApp.cgi.login(jQuery("#textUser").val(),jQuery("#textPassword").val(),{onResponse:TheApp.onLogin})' />
          331  +<input type='button' value='logout' onclick='TheApp.cgi.logout()' />
          332  +
          333  +<br/>
          334  +<span id='currentAuthToken' style='font-family:monospaced'></span>
          335  +
          336  +<hr/>
          337  +<strong>Quick-posts:</strong><br/>
          338  +<input type='button' value='HAI' onclick='TheApp.cgi.HAI()' />
          339  +<input type='button' value='stat' onclick='TheApp.cgi.sendCommand("/json/stat")' />
          340  +<input type='button' value='whoami' onclick='TheApp.cgi.whoami()' />
          341  +<input type='button' value='wiki/list' onclick='TheApp.loadPageList()' />
          342  +<!--
          343  +<input type='button' value='timeline/ci' onclick='TheApp.cgi.sendCommand("/json/timeline/ci")' />
          344  +-->
          345  +
          346  +<!--
          347  +<input type='button' value='get whiki' onclick='TheApp.cgi.getPages("whiki")' />
          348  +<input type='button' value='get more' onclick='TheApp.cgi.getPages("HelloWorld/WhikiNews")' />
          349  +<input type='button' value='get client data' onclick='TheApp.cgi.getPageClientData("HelloWorld/whiki/WhikiCommands")' />
          350  +<input type='button' value='save client data' onclick='TheApp.cgi.savePageClientData({"HelloWorld":[1,3,5]})' />
          351  +-->
          352  +<hr/>
          353  +
          354  +<table>
          355  +    <tr>
          356  +        <th>Page List</th>
          357  +        <th>Content <span id='spanPageName'></span></th>
          358  +    </tr>
          359  +    <tr>
          360  +        <td width='25%' valign='top'>
          361  +            <input type='button' value='Create new...' onclick='TheApp.createNewPage()' /><br/>
          362  +            <div id='pageListArea'></div>
          363  +        </td>
          364  +        <td width='75%' valign='top'>
          365  +            <input type='button' value='Save' onclick='TheApp.savePage()' /><br/>
          366  +            <textarea id='taPageContent' rows='20' cols='60'></textarea>
          367  +        </td>
          368  +    </tr>
          369  +    <tr>
          370  +        <th colspan='2'>Response <span id='timer'></span></th>
          371  +    </tr>
          372  +    <tr>
          373  +        <td colspan='2' id='responseContainer'>
          374  +            <textarea id='taResponse' rows='20' cols='80' readonly></textarea>
          375  +        </td>
          376  +    </tr>
          377  +</table>
          378  +<div></div>
          379  +<div></div>
          380  +<div></div>
          381  +
          382  +</body></html>

Changes to auto.def.

     6      6       with-openssl:path|auto|none
     7      7                            => {Look for openssl in the given path, or auto or none}
     8      8       with-zlib:path       => {Look for zlib in the given path}
     9      9       internal-sqlite=1    => {Don't use the internal sqlite, use the system one}
    10     10       static=0             => {Link a static executable}
    11     11       lineedit=1           => {Disable line editing}
    12     12       fossil-debug=0       => {Build with fossil debugging enabled}
           13  +    json=0        => {Build with fossil JSON API enabled}
    13     14   }
    14     15   
    15     16   # sqlite wants these types if possible
    16     17   cc-with {-includes {stdint.h inttypes.h}} {
    17     18       cc-check-types uint32_t uint16_t int16_t uint8_t
    18     19   }
    19     20   
................................................................................
    59     60   
    60     61     find_internal_sqlite
    61     62   }
    62     63   
    63     64   if {[opt-bool fossil-debug]} {
    64     65       define-append EXTRA_CFLAGS -DFOSSIL_DEBUG
    65     66   }
           67  +
           68  +if {[opt-bool json]} {
           69  +    define-append EXTRA_CFLAGS -DFOSSIL_ENABLE_JSON
           70  +}
    66     71   
    67     72   if {[opt-bool static]} {
    68     73       # XXX: This will not work on all systems.
    69     74       define-append EXTRA_LDFLAGS -static
    70     75   }
    71     76   
    72     77   

Added src/Makefile.

            1  +all:
            2  +	$(MAKE) -C ..

Changes to src/blob.c.

  1049   1049           if( z[i]=='"' ) z[i] = '_';
  1050   1050         }
  1051   1051         return;
  1052   1052       }
  1053   1053     }
  1054   1054     blob_append(pBlob, zIn, -1);
  1055   1055   }
         1056  +
         1057  +/*
         1058  +** A read(2)-like impl for the Blob class. Reads (copies) up to nLen
         1059  +** bytes from pIn, starting at position pIn->iCursor, and copies them
         1060  +** to pDest (which must be valid memory at least nLen bytes long).
         1061  +**
         1062  +** Returns the number of bytes read/copied, which may be less than
         1063  +** nLen (if end-of-blob is encountered).
         1064  +**
         1065  +** Updates pIn's cursor.
         1066  +** 
         1067  +** Returns 0 if pIn contains no data.
         1068  +*/
         1069  +unsigned int blob_read(Blob *pIn, void * pDest, unsigned int nLen ){
         1070  +  if( !pIn->aData || (pIn->iCursor >= pIn->nUsed) ){
         1071  +    return 0;
         1072  +  } else if( (pIn->iCursor + nLen) > (unsigned int)pIn->nUsed ){
         1073  +    nLen = (unsigned int) (pIn->nUsed - pIn->iCursor);
         1074  +  }
         1075  +  assert( pIn->nUsed > pIn->iCursor );
         1076  +  assert( (pIn->iCursor+nLen)  <= pIn->nUsed );
         1077  +  if( nLen ){
         1078  +    memcpy( pDest, pIn->aData, nLen );
         1079  +    pIn->iCursor += nLen;
         1080  +  }
         1081  +  return nLen;
         1082  +}

Changes to src/branch.c.

   176    176     db_end_transaction(0);
   177    177     
   178    178     /* Do an autosync push, if requested */
   179    179     if( !isPrivate ) autosync(AUTOSYNC_PUSH);
   180    180   }
   181    181   
   182    182   /*
   183         -** Prepare a query that will list all branches.
          183  +** Prepare a query that will list branches.
          184  +**
          185  +** If (which<0) then the query pulls only closed branches. If
          186  +** (which>0) then the query pulls all (closed and opened)
          187  +** branches. Else the query pulls currently-opened branches.
   184    188   */
   185         -static void prepareBranchQuery(Stmt *pQuery, int showAll, int showClosed){
   186         -  if( showClosed ){
          189  +void branch_prepare_list_query(Stmt *pQuery, int which ){
          190  +  if( which < 0 ){
   187    191       db_prepare(pQuery,
   188    192         "SELECT value FROM tagxref"
   189    193         " WHERE tagid=%d AND value NOT NULL "
   190    194         "EXCEPT "
   191    195         "SELECT value FROM tagxref"
   192    196         " WHERE tagid=%d"
   193    197         "   AND rid IN leaf"
   194    198         "   AND NOT %z"
   195    199         " ORDER BY value COLLATE nocase /*sort*/",
   196    200         TAG_BRANCH, TAG_BRANCH, leaf_is_closed_sql("tagxref.rid")
   197    201       );
   198         -  }else if( showAll ){
          202  +  }else if( which>0 ){
   199    203       db_prepare(pQuery,
   200    204         "SELECT DISTINCT value FROM tagxref"
   201    205         " WHERE tagid=%d AND value NOT NULL"
   202    206         "   AND rid IN leaf"
   203    207         " ORDER BY value COLLATE nocase /*sort*/",
   204    208         TAG_BRANCH
   205    209       );
................................................................................
   258    262       int showClosed = find_option("closed",0,0)!=0;
   259    263   
   260    264       if( g.localOpen ){
   261    265         vid = db_lget_int("checkout", 0);
   262    266         zCurrent = db_text(0, "SELECT value FROM tagxref"
   263    267                               " WHERE rid=%d AND tagid=%d", vid, TAG_BRANCH);
   264    268       }
   265         -    prepareBranchQuery(&q, showAll, showClosed);
          269  +    branch_prepare_list_query(&q, showAll?1:(showClosed?-1:0));
   266    270       while( db_step(&q)==SQLITE_ROW ){
   267    271         const char *zBr = db_column_text(&q, 0);
   268    272         int isCur = zCurrent!=0 && fossil_strcmp(zCurrent,zBr)==0;
   269    273         fossil_print("%s%s\n", (isCur ? "* " : "  "), zBr);
   270    274       }
   271    275       db_finalize(&q);
   272    276     }else{
................................................................................
   325    329     @ <div class="sideboxDescribed"><a href="leaves?closed">
   326    330     @ closed leaves</a></div>.
   327    331     @ Closed branches are fixed and do not change (unless they are first
   328    332     @ reopened)</li>
   329    333     @ </ol>
   330    334     style_sidebox_end();
   331    335   
   332         -  prepareBranchQuery(&q, showAll, showClosed);
          336  +  branch_prepare_list_query(&q, showAll?1:(showClosed?-1:0));
   333    337     cnt = 0;
   334    338     while( db_step(&q)==SQLITE_ROW ){
   335    339       const char *zBr = db_column_text(&q, 0);
   336    340       if( cnt==0 ){
   337    341         if( colorTest ){
   338    342           @ <h2>Default background colors for all branches:</h2>
   339    343         }else if( showAll ){

Changes to src/captcha.c.

   410    410     sqlite3_randomness(sizeof(x), &x);
   411    411     x &= 0x7fffffff;
   412    412     return x;
   413    413   }
   414    414   
   415    415   /*
   416    416   ** Translate a captcha seed value into the captcha password string.
          417  +** The returned string is static and overwritten on each call to
          418  +** this function.
   417    419   */
   418         -char *captcha_decode(unsigned int seed){
          420  +char const *captcha_decode(unsigned int seed){
   419    421     const char *zSecret;
   420    422     const char *z;
   421    423     Blob b;
   422    424     static char zRes[20];
   423    425   
   424    426     zSecret = db_get("captcha-secret", 0);
   425    427     if( zSecret==0 ){

Changes to src/cgi.c.

   506    506       }else{
   507    507         if( *z ){ *z++ = 0; }
   508    508         zValue = "";
   509    509       }
   510    510       if( fossil_islower(zName[0]) ){
   511    511         cgi_set_parameter_nocopy(zName, zValue);
   512    512       }
          513  +#ifdef FOSSIL_ENABLE_JSON
          514  +    json_setenv( zName, cson_value_new_string(zValue,strlen(zValue)) );
          515  +#endif /* FOSSIL_ENABLE_JSON */
   513    516     }
   514    517   }
   515    518   
   516    519   /*
   517    520   ** *pz is a string that consists of multiple lines of text.  This
   518    521   ** routine finds the end of the current line of text and converts
   519    522   ** the "\n" or "\r\n" that ends that line into a "\000".  It then
................................................................................
   678    681               cgi_set_parameter_nocopy(mprintf("%s:mimetype",zName), z);
   679    682             }
   680    683           }
   681    684         }
   682    685       }
   683    686     }        
   684    687   }
          688  +
          689  +
          690  +#ifdef FOSSIL_ENABLE_JSON
          691  +/*
          692  +** Internal helper for cson_data_source_FILE_n().
          693  +*/
          694  +typedef struct CgiPostReadState_ {
          695  +    FILE * fh;
          696  +    unsigned int len;
          697  +    unsigned int pos;
          698  +} CgiPostReadState;
          699  +
          700  +/*
          701  +** cson_data_source_f() impl which reads only up to
          702  +** a specified amount of data from its input FILE.
          703  +** state MUST be a full populated (CgiPostReadState*).
          704  +*/
          705  +static int cson_data_source_FILE_n( void * state,
          706  +                                    void * dest,
          707  +                                    unsigned int * n ){
          708  +    if( ! state || !dest || !n ) return cson_rc.ArgError;
          709  +    else {
          710  +      CgiPostReadState * st = (CgiPostReadState *)state;
          711  +      if( st->pos >= st->len ){
          712  +        *n = 0;
          713  +        return 0;
          714  +      } else if( !*n || ((st->pos + *n) > st->len) ){
          715  +        return cson_rc.RangeError;
          716  +      }else{
          717  +        unsigned int rsz = (unsigned int)fread( dest, 1, *n, st->fh );
          718  +        if( ! rsz ){
          719  +          *n = rsz;
          720  +          return feof(st->fh) ? 0 : cson_rc.IOError;
          721  +        }else{
          722  +          *n = rsz;
          723  +          st->pos += *n;
          724  +          return 0;
          725  +        }
          726  +      }
          727  +    }
          728  +}
          729  +
          730  +/*
          731  +** Reads a JSON object from the first contentLen bytes of zIn.  On
          732  +** g.json.post is updated to hold the content. On error a
          733  +** FSL_JSON_E_INVALID_REQUEST response is output and fossil_exit() is
          734  +** called (in HTTP mode exit code 0 is used).
          735  +**
          736  +** If contentLen is 0 then the whole file is read.
          737  +*/
          738  +void cgi_parse_POST_JSON( FILE * zIn, unsigned int contentLen ){
          739  +  cson_value * jv = NULL;
          740  +  int rc;
          741  +  CgiPostReadState state;
          742  +  state.fh = zIn;
          743  +  state.len = contentLen;
          744  +  state.pos = 0;
          745  +  rc = cson_parse( &jv,
          746  +                   contentLen ? cson_data_source_FILE_n : cson_data_source_FILE,
          747  +                   contentLen ? (void *)&state : (void *)zIn, NULL, NULL );
          748  +  if(rc){
          749  +    goto invalidRequest;
          750  +  }else{
          751  +    json_gc_add( "POST.JSON", jv );
          752  +    g.json.post.v = jv;
          753  +    g.json.post.o = cson_value_get_object( jv );
          754  +    if( !g.json.post.o ){ /* we don't support non-Object (Array) requests */
          755  +      goto invalidRequest;
          756  +    }
          757  +  }
          758  +  return;
          759  +  invalidRequest:
          760  +  cgi_set_content_type(json_guess_content_type());
          761  +  json_err( FSL_JSON_E_INVALID_REQUEST, NULL, 1 );
          762  +  fossil_exit( g.isHTTP ? 0 : 1);
          763  +}
          764  +#endif /* FOSSIL_ENABLE_JSON */
          765  +
   685    766   
   686    767   /*
   687    768   ** Initialize the query parameter database.  Information is pulled from
   688    769   ** the QUERY_STRING environment variable (if it exists), from standard
   689    770   ** input if there is POST data, and from HTTP_COOKIE.
   690    771   */
   691    772   void cgi_init(void){
   692    773     char *z;
   693    774     const char *zType;
   694    775     int len;
          776  +#ifdef FOSSIL_ENABLE_JSON
          777  +  json_main_bootstrap();
          778  +#endif
          779  +  g.isHTTP = 1;
   695    780     cgi_destination(CGI_BODY);
          781  +
          782  +  z = (char*)P("HTTP_COOKIE");
          783  +  if( z ){
          784  +    z = mprintf("%s",z);
          785  +    add_param_list(z, ';');
          786  +  }
          787  +  
   696    788     z = (char*)P("QUERY_STRING");
   697    789     if( z ){
   698    790       z = mprintf("%s",z);
   699    791       add_param_list(z, '&');
   700    792     }
   701    793   
   702    794     z = (char*)P("REMOTE_ADDR");
   703         -  if( z ) g.zIpAddr = mprintf("%s", z);
          795  +  if( z ){
          796  +    g.zIpAddr = mprintf("%s", z);
          797  +  }
   704    798   
   705    799     len = atoi(PD("CONTENT_LENGTH", "0"));
   706    800     g.zContentType = zType = P("CONTENT_TYPE");
   707    801     if( len>0 && zType ){
   708    802       blob_zero(&g.cgiIn);
   709    803       if( fossil_strcmp(zType,"application/x-www-form-urlencoded")==0 
   710    804            || strncmp(zType,"multipart/form-data",19)==0 ){
................................................................................
   720    814         blob_read_from_channel(&g.cgiIn, g.httpIn, len);
   721    815         blob_uncompress(&g.cgiIn, &g.cgiIn);
   722    816       }else if( fossil_strcmp(zType, "application/x-fossil-debug")==0 ){
   723    817         blob_read_from_channel(&g.cgiIn, g.httpIn, len);
   724    818       }else if( fossil_strcmp(zType, "application/x-fossil-uncompressed")==0 ){
   725    819         blob_read_from_channel(&g.cgiIn, g.httpIn, len);
   726    820       }
          821  +#ifdef FOSSIL_ENABLE_JSON
          822  +    else if( fossil_strcmp(zType, "application/json")
          823  +              || fossil_strcmp(zType,"text/plain")/*assume this MIGHT be JSON*/
          824  +              || fossil_strcmp(zType,"application/javascript")){
          825  +      g.json.isJsonMode = 1;
          826  +      cgi_parse_POST_JSON(g.httpIn, (unsigned int)len);
          827  +      /* FIXMEs:
          828  +
          829  +      - See if fossil really needs g.cgiIn to be set for this purpose
          830  +      (i don't think it does). If it does then fill g.cgiIn and
          831  +      refactor to parse the JSON from there.
          832  +      
          833  +      - After parsing POST JSON, copy the "first layer" of keys/values
          834  +      to cgi_setenv(), honoring the upper-case distinction used
          835  +      in add_param_list(). However...
          836  +
          837  +      - If we do that then we might get a disconnect in precedence of
          838  +      GET/POST arguments. i prefer for GET entries to take precedence
          839  +      over like-named POST entries, but in order for that to happen we
          840  +      need to process QUERY_STRING _after_ reading the POST data.
          841  +      */
          842  +      cgi_set_content_type(json_guess_content_type());
          843  +    }
          844  +#endif /* FOSSIL_ENABLE_JSON */
   727    845     }
   728    846   
   729         -  z = (char*)P("HTTP_COOKIE");
   730         -  if( z ){
   731         -    z = mprintf("%s",z);
   732         -    add_param_list(z, ';');
   733         -  }
   734    847   }
   735    848   
   736    849   /*
   737    850   ** This is the comparison function used to sort the aParamQP[] array of
   738    851   ** query parameters and cookies.
   739    852   */
   740    853   static int qparam_compare(const void *a, const void *b){
................................................................................
   940   1053   
   941   1054   /*
   942   1055   ** Panic and die while processing a webpage.
   943   1056   */
   944   1057   NORETURN void cgi_panic(const char *zFormat, ...){
   945   1058     va_list ap;
   946   1059     cgi_reset_content();
   947         -  cgi_set_status(500, "Internal Server Error");
   948         -  cgi_printf(
   949         -    "<html><body><h1>Internal Server Error</h1>\n"
   950         -    "<plaintext>"
   951         -  );
   952         -  va_start(ap, zFormat);
   953         -  vxprintf(pContent,zFormat,ap);
   954         -  va_end(ap);
   955         -  cgi_reply();
   956         -  fossil_exit(1);
         1060  +#ifdef FOSSIL_ENABLE_JSON
         1061  +  if( g.json.isJsonMode ){
         1062  +    char * zMsg;
         1063  +    va_start(ap, zFormat);
         1064  +    zMsg = vmprintf(zFormat,ap);
         1065  +    va_end(ap);
         1066  +    json_err( FSL_JSON_E_PANIC, zMsg, 1 );
         1067  +    free(zMsg);
         1068  +    fossil_exit( g.isHTTP ? 0 : 1 );
         1069  +  }else
         1070  +#endif /* FOSSIL_ENABLE_JSON */
         1071  +  {
         1072  +    cgi_set_status(500, "Internal Server Error");
         1073  +    cgi_printf(
         1074  +               "<html><body><h1>Internal Server Error</h1>\n"
         1075  +               "<plaintext>"
         1076  +               );
         1077  +    va_start(ap, zFormat);
         1078  +    vxprintf(pContent,zFormat,ap);
         1079  +    va_end(ap);
         1080  +    cgi_reply();
         1081  +    fossil_exit(1);
         1082  +  }
   957   1083   }
   958   1084   
   959   1085   /*
   960   1086   ** Remove the first space-delimited token from a string and return
   961   1087   ** a pointer to it.  Add a NULL to the string to terminate the token.
   962   1088   ** Make *zLeftOver point to the start of the next token.
   963   1089   */
................................................................................
   990   1116   */
   991   1117   void cgi_handle_http_request(const char *zIpAddr){
   992   1118     char *z, *zToken;
   993   1119     int i;
   994   1120     struct sockaddr_in remoteName;
   995   1121     socklen_t size = sizeof(struct sockaddr_in);
   996   1122     char zLine[2000];     /* A single line of input. */
   997         -
   998   1123     g.fullHttpReply = 1;
   999   1124     if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
  1000   1125       malformed_request();
  1001   1126     }
  1002   1127     zToken = extract_token(zLine, &z);
  1003   1128     if( zToken==0 ){
  1004   1129       malformed_request();

Added src/cson_amalgamation.c.

            1  +#ifdef FOSSIL_ENABLE_JSON
            2  +/* auto-generated! Do not edit! */
            3  +#include "cson_amalgamation.h"
            4  +/* begin file parser/JSON_parser.h */
            5  +/* See JSON_parser.c for copyright information and licensing. */
            6  +
            7  +#ifndef JSON_PARSER_H
            8  +#define JSON_PARSER_H
            9  +
           10  +/* JSON_parser.h */
           11  +
           12  +
           13  +#include <stddef.h>
           14  +
           15  +/* Windows DLL stuff */
           16  +#ifdef JSON_PARSER_DLL
           17  +#   ifdef _MSC_VER
           18  +#	    ifdef JSON_PARSER_DLL_EXPORTS
           19  +#		    define JSON_PARSER_DLL_API __declspec(dllexport)
           20  +#	    else
           21  +#		    define JSON_PARSER_DLL_API __declspec(dllimport)
           22  +#       endif
           23  +#   else
           24  +#	    define JSON_PARSER_DLL_API 
           25  +#   endif
           26  +#else
           27  +#	define JSON_PARSER_DLL_API 
           28  +#endif
           29  +
           30  +/* Determine the integer type use to parse non-floating point numbers */
           31  +#if __STDC_VERSION__ >= 199901L || HAVE_LONG_LONG == 1
           32  +typedef long long JSON_int_t;
           33  +#define JSON_PARSER_INTEGER_SSCANF_TOKEN "%lld"
           34  +#define JSON_PARSER_INTEGER_SPRINTF_TOKEN "%lld"
           35  +#else 
           36  +typedef long JSON_int_t;
           37  +#define JSON_PARSER_INTEGER_SSCANF_TOKEN "%ld"
           38  +#define JSON_PARSER_INTEGER_SPRINTF_TOKEN "%ld"
           39  +#endif
           40  +
           41  +
           42  +#ifdef __cplusplus
           43  +extern "C" {
           44  +#endif 
           45  +
           46  +typedef enum 
           47  +{
           48  +    JSON_E_NONE = 0,
           49  +    JSON_E_INVALID_CHAR,
           50  +    JSON_E_INVALID_KEYWORD,
           51  +    JSON_E_INVALID_ESCAPE_SEQUENCE,
           52  +    JSON_E_INVALID_UNICODE_SEQUENCE,
           53  +    JSON_E_INVALID_NUMBER,
           54  +    JSON_E_NESTING_DEPTH_REACHED,
           55  +    JSON_E_UNBALANCED_COLLECTION,
           56  +    JSON_E_EXPECTED_KEY,
           57  +    JSON_E_EXPECTED_COLON,
           58  +    JSON_E_OUT_OF_MEMORY
           59  +} JSON_error;
           60  +
           61  +typedef enum 
           62  +{
           63  +    JSON_T_NONE = 0,
           64  +    JSON_T_ARRAY_BEGIN,
           65  +    JSON_T_ARRAY_END,
           66  +    JSON_T_OBJECT_BEGIN,
           67  +    JSON_T_OBJECT_END,
           68  +    JSON_T_INTEGER,
           69  +    JSON_T_FLOAT,
           70  +    JSON_T_NULL,
           71  +    JSON_T_TRUE,
           72  +    JSON_T_FALSE,
           73  +    JSON_T_STRING,
           74  +    JSON_T_KEY,
           75  +    JSON_T_MAX
           76  +} JSON_type;
           77  +
           78  +typedef struct JSON_value_struct {
           79  +    union {
           80  +        JSON_int_t integer_value;
           81  +        
           82  +        double float_value;
           83  +        
           84  +        struct {
           85  +            const char* value;
           86  +            size_t length;
           87  +        } str;
           88  +    } vu;
           89  +} JSON_value;
           90  +
           91  +typedef struct JSON_parser_struct* JSON_parser;
           92  +
           93  +/*! \brief JSON parser callback 
           94  +
           95  +    \param ctx The pointer passed to new_JSON_parser.
           96  +    \param type An element of JSON_type but not JSON_T_NONE.    
           97  +    \param value A representation of the parsed value. This parameter is NULL for
           98  +        JSON_T_ARRAY_BEGIN, JSON_T_ARRAY_END, JSON_T_OBJECT_BEGIN, JSON_T_OBJECT_END,
           99  +        JSON_T_NULL, JSON_T_TRUE, and JSON_T_FALSE. String values are always returned
          100  +        as zero-terminated C strings.
          101  +
          102  +    \return Non-zero if parsing should continue, else zero.
          103  +*/    
          104  +typedef int (*JSON_parser_callback)(void* ctx, int type, const struct JSON_value_struct* value);
          105  +
          106  +
          107  +/**
          108  +   A typedef for allocator functions semantically compatible with malloc().
          109  +*/
          110  +typedef void* (*JSON_malloc_t)(size_t n);
          111  +/**
          112  +   A typedef for deallocator functions semantically compatible with free().
          113  +*/
          114  +typedef void (*JSON_free_t)(void* mem);
          115  +
          116  +/*! \brief The structure used to configure a JSON parser object 
          117  +*/
          118  +typedef struct {
          119  +    /** Pointer to a callback, called when the parser has something to tell
          120  +        the user. This parameter may be NULL. In this case the input is
          121  +        merely checked for validity.
          122  +    */
          123  +    JSON_parser_callback    callback;
          124  +    /**
          125  +       Callback context - client-specified data to pass to the
          126  +       callback function. This parameter may be NULL.
          127  +    */
          128  +    void*                   callback_ctx;
          129  +    /** Specifies the levels of nested JSON to allow. Negative numbers yield unlimited nesting.
          130  +        If negative, the parser can parse arbitrary levels of JSON, otherwise
          131  +        the depth is the limit.
          132  +    */
          133  +    int                     depth;
          134  +    /**
          135  +       To allow C style comments in JSON, set to non-zero.
          136  +    */
          137  +    int                     allow_comments;
          138  +    /**
          139  +       To decode floating point numbers manually set this parameter to
          140  +       non-zero.
          141  +    */
          142  +    int                     handle_floats_manually;
          143  +    /**
          144  +       The memory allocation routine, which must be semantically
          145  +       compatible with malloc(3). If set to NULL, malloc(3) is used.
          146  +
          147  +       If this is set to a non-NULL value then the 'free' member MUST be
          148  +       set to the proper deallocation counterpart for this function.
          149  +       Failure to do so results in undefined behaviour at deallocation
          150  +       time.
          151  +    */
          152  +    JSON_malloc_t       malloc;
          153  +    /**
          154  +       The memory deallocation routine, which must be semantically
          155  +       compatible with free(3). If set to NULL, free(3) is used.
          156  +
          157  +       If this is set to a non-NULL value then the 'alloc' member MUST be
          158  +       set to the proper allocation counterpart for this function.
          159  +       Failure to do so results in undefined behaviour at deallocation
          160  +       time.
          161  +    */
          162  +    JSON_free_t         free;
          163  +} JSON_config;
          164  +
          165  +/*! \brief Initializes the JSON parser configuration structure to default values.
          166  +
          167  +    The default configuration is
          168  +    - 127 levels of nested JSON (depends on JSON_PARSER_STACK_SIZE, see json_parser.c)
          169  +    - no parsing, just checking for JSON syntax
          170  +    - no comments
          171  +    - Uses realloc() for memory de/allocation.
          172  +
          173  +    \param config. Used to configure the parser.
          174  +*/
          175  +JSON_PARSER_DLL_API void init_JSON_config(JSON_config * config);
          176  +
          177  +/*! \brief Create a JSON parser object 
          178  +
          179  +    \param config. Used to configure the parser. Set to NULL to use
          180  +        the default configuration. See init_JSON_config.  Its contents are
          181  +        copied by this function, so it need not outlive the returned
          182  +        object.
          183  +    
          184  +    \return The parser object, which is owned by the caller and must eventually
          185  +    be freed by calling delete_JSON_parser().
          186  +*/
          187  +JSON_PARSER_DLL_API JSON_parser new_JSON_parser(JSON_config const* config);
          188  +
          189  +/*! \brief Destroy a previously created JSON parser object. */
          190  +JSON_PARSER_DLL_API void delete_JSON_parser(JSON_parser jc);
          191  +
          192  +/*! \brief Parse a character.
          193  +
          194  +    \return Non-zero, if all characters passed to this function are part of are valid JSON.
          195  +*/
          196  +JSON_PARSER_DLL_API int JSON_parser_char(JSON_parser jc, int next_char);
          197  +
          198  +/*! \brief Finalize parsing.
          199  +
          200  +    Call this method once after all input characters have been consumed.
          201  +    
          202  +    \return Non-zero, if all parsed characters are valid JSON, zero otherwise.
          203  +*/
          204  +JSON_PARSER_DLL_API int JSON_parser_done(JSON_parser jc);
          205  +
          206  +/*! \brief Determine if a given string is valid JSON white space 
          207  +
          208  +    \return Non-zero if the string is valid, zero otherwise.
          209  +*/
          210  +JSON_PARSER_DLL_API int JSON_parser_is_legal_white_space_string(const char* s);
          211  +
          212  +/*! \brief Gets the last error that occurred during the use of JSON_parser.
          213  +
          214  +    \return A value from the JSON_error enum.
          215  +*/
          216  +JSON_PARSER_DLL_API int JSON_parser_get_last_error(JSON_parser jc);
          217  +
          218  +/*! \brief Re-sets the parser to prepare it for another parse run.
          219  +
          220  +    \return True (non-zero) on success, 0 on error (e.g. !jc).
          221  +*/
          222  +JSON_PARSER_DLL_API int JSON_parser_reset(JSON_parser jc);
          223  +
          224  +
          225  +#ifdef __cplusplus
          226  +}
          227  +#endif 
          228  +    
          229  +
          230  +#endif /* JSON_PARSER_H */
          231  +/* end file parser/JSON_parser.h */
          232  +/* begin file parser/JSON_parser.c */
          233  +/*
          234  +Copyright (c) 2005 JSON.org
          235  +
          236  +Permission is hereby granted, free of charge, to any person obtaining a copy
          237  +of this software and associated documentation files (the "Software"), to deal
          238  +in the Software without restriction, including without limitation the rights
          239  +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
          240  +copies of the Software, and to permit persons to whom the Software is
          241  +furnished to do so, subject to the following conditions:
          242  +
          243  +The above copyright notice and this permission notice shall be included in all
          244  +copies or substantial portions of the Software.
          245  +
          246  +The Software shall be used for Good, not Evil.
          247  +
          248  +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
          249  +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
          250  +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
          251  +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
          252  +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
          253  +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
          254  +SOFTWARE.
          255  +*/
          256  +
          257  +/*
          258  +    Callbacks, comments, Unicode handling by Jean Gressmann (jean@0x42.de), 2007-2010.
          259  +    
          260  +    
          261  +    Changelog:
          262  +        2010-11-25
          263  +            Support for custom memory allocation (sgbeal@googlemail.com).
          264  +                        
          265  +        2010-05-07
          266  +            Added error handling for memory allocation failure (sgbeal@googlemail.com). 
          267  +            Added diagnosis errors for invalid JSON.
          268  +            
          269  +        2010-03-25
          270  +            Fixed buffer overrun in grow_parse_buffer & cleaned up code.
          271  +            
          272  +        2009-10-19
          273  +            Replaced long double in JSON_value_struct with double after reports 
          274  +            of strtold being broken on some platforms (charles@transmissionbt.com).
          275  +            
          276  +        2009-05-17 
          277  +            Incorporated benrudiak@googlemail.com fix for UTF16 decoding.
          278  +            
          279  +        2009-05-14 
          280  +            Fixed float parsing bug related to a locale being set that didn't
          281  +            use '.' as decimal point character (charles@transmissionbt.com).
          282  +            
          283  +        2008-10-14 
          284  +            Renamed states.IN to states.IT to avoid name clash which IN macro
          285  +            defined in windef.h (alexey.pelykh@gmail.com)
          286  +            
          287  +        2008-07-19 
          288  +            Removed some duplicate code & debugging variable (charles@transmissionbt.com)
          289  +        
          290  +        2008-05-28 
          291  +            Made JSON_value structure ansi C compliant. This bug was report by 
          292  +            trisk@acm.jhu.edu
          293  +        
          294  +        2008-05-20 
          295  +            Fixed bug reported by charles@transmissionbt.com where the switching 
          296  +            from static to dynamic parse buffer did not copy the static parse 
          297  +            buffer's content.
          298  +*/
          299  +
          300  +
          301  +
          302  +#include <assert.h>
          303  +#include <ctype.h>
          304  +#include <float.h>
          305  +#include <stddef.h>
          306  +#include <stdio.h>
          307  +#include <stdlib.h>
          308  +#include <string.h>
          309  +#include <locale.h>
          310  +
          311  +
          312  +#ifdef _MSC_VER
          313  +#   if _MSC_VER >= 1400 /* Visual Studio 2005 and up */
          314  +#      pragma warning(disable:4996) /* unsecure sscanf */
          315  +#      pragma warning(disable:4127) /* conditional expression is constant */
          316  +#   endif
          317  +#endif
          318  +
          319  +
          320  +#define true  1
          321  +#define false 0
          322  +#define __   -1     /* the universal error code */
          323  +
          324  +/* values chosen so that the object size is approx equal to one page (4K) */
          325  +#ifndef JSON_PARSER_STACK_SIZE
          326  +#   define JSON_PARSER_STACK_SIZE 128
          327  +#endif
          328  +
          329  +#ifndef JSON_PARSER_PARSE_BUFFER_SIZE
          330  +#   define JSON_PARSER_PARSE_BUFFER_SIZE 3500
          331  +#endif
          332  +
          333  +typedef void* (*JSON_debug_malloc_t)(size_t bytes, const char* reason);
          334  +
          335  +#ifdef JSON_PARSER_DEBUG_MALLOC
          336  +#   define JSON_parser_malloc(func, bytes, reason) ((JSON_debug_malloc_t)func)(bytes, reason)
          337  +#else
          338  +#   define JSON_parser_malloc(func, bytes, reason) func(bytes)
          339  +#endif
          340  +
          341  +typedef unsigned short UTF16;
          342  +
          343  +struct JSON_parser_struct {
          344  +    JSON_parser_callback callback;
          345  +    void* ctx;
          346  +    signed char state, before_comment_state, type, escaped, comment, allow_comments, handle_floats_manually, error;
          347  +    char decimal_point;
          348  +    UTF16 utf16_high_surrogate;
          349  +    int current_char;
          350  +    int depth;
          351  +    int top;
          352  +    int stack_capacity;
          353  +    signed char* stack;
          354  +    char* parse_buffer;
          355  +    size_t parse_buffer_capacity;
          356  +    size_t parse_buffer_count;
          357  +    signed char static_stack[JSON_PARSER_STACK_SIZE];
          358  +    char static_parse_buffer[JSON_PARSER_PARSE_BUFFER_SIZE];
          359  +    JSON_malloc_t malloc;
          360  +    JSON_free_t free;
          361  +};
          362  +
          363  +#define COUNTOF(x) (sizeof(x)/sizeof(x[0])) 
          364  +
          365  +/*
          366  +    Characters are mapped into these character classes. This allows for
          367  +    a significant reduction in the size of the state transition table.
          368  +*/
          369  +
          370  +
          371  +
          372  +enum classes {
          373  +    C_SPACE,  /* space */
          374  +    C_WHITE,  /* other whitespace */
          375  +    C_LCURB,  /* {  */
          376  +    C_RCURB,  /* } */
          377  +    C_LSQRB,  /* [ */
          378  +    C_RSQRB,  /* ] */
          379  +    C_COLON,  /* : */
          380  +    C_COMMA,  /* , */
          381  +    C_QUOTE,  /* " */
          382  +    C_BACKS,  /* \ */
          383  +    C_SLASH,  /* / */
          384  +    C_PLUS,   /* + */
          385  +    C_MINUS,  /* - */
          386  +    C_POINT,  /* . */
          387  +    C_ZERO ,  /* 0 */
          388  +    C_DIGIT,  /* 123456789 */
          389  +    C_LOW_A,  /* a */
          390  +    C_LOW_B,  /* b */
          391  +    C_LOW_C,  /* c */
          392  +    C_LOW_D,  /* d */
          393  +    C_LOW_E,  /* e */
          394  +    C_LOW_F,  /* f */
          395  +    C_LOW_L,  /* l */
          396  +    C_LOW_N,  /* n */
          397  +    C_LOW_R,  /* r */
          398  +    C_LOW_S,  /* s */
          399  +    C_LOW_T,  /* t */
          400  +    C_LOW_U,  /* u */
          401  +    C_ABCDF,  /* ABCDF */
          402  +    C_E,      /* E */
          403  +    C_ETC,    /* everything else */
          404  +    C_STAR,   /* * */   
          405  +    NR_CLASSES
          406  +};
          407  +
          408  +static const signed char ascii_class[128] = {
          409  +/*
          410  +    This array maps the 128 ASCII characters into character classes.
          411  +    The remaining Unicode characters should be mapped to C_ETC.
          412  +    Non-whitespace control characters are errors.
          413  +*/
          414  +    __,      __,      __,      __,      __,      __,      __,      __,
          415  +    __,      C_WHITE, C_WHITE, __,      __,      C_WHITE, __,      __,
          416  +    __,      __,      __,      __,      __,      __,      __,      __,
          417  +    __,      __,      __,      __,      __,      __,      __,      __,
          418  +
          419  +    C_SPACE, C_ETC,   C_QUOTE, C_ETC,   C_ETC,   C_ETC,   C_ETC,   C_ETC,
          420  +    C_ETC,   C_ETC,   C_STAR,   C_PLUS,  C_COMMA, C_MINUS, C_POINT, C_SLASH,
          421  +    C_ZERO,  C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT,
          422  +    C_DIGIT, C_DIGIT, C_COLON, C_ETC,   C_ETC,   C_ETC,   C_ETC,   C_ETC,
          423  +
          424  +    C_ETC,   C_ABCDF, C_ABCDF, C_ABCDF, C_ABCDF, C_E,     C_ABCDF, C_ETC,
          425  +    C_ETC,   C_ETC,   C_ETC,   C_ETC,   C_ETC,   C_ETC,   C_ETC,   C_ETC,
          426  +    C_ETC,   C_ETC,   C_ETC,   C_ETC,   C_ETC,   C_ETC,   C_ETC,   C_ETC,
          427  +    C_ETC,   C_ETC,   C_ETC,   C_LSQRB, C_BACKS, C_RSQRB, C_ETC,   C_ETC,
          428  +
          429  +    C_ETC,   C_LOW_A, C_LOW_B, C_LOW_C, C_LOW_D, C_LOW_E, C_LOW_F, C_ETC,
          430  +    C_ETC,   C_ETC,   C_ETC,   C_ETC,   C_LOW_L, C_ETC,   C_LOW_N, C_ETC,
          431  +    C_ETC,   C_ETC,   C_LOW_R, C_LOW_S, C_LOW_T, C_LOW_U, C_ETC,   C_ETC,
          432  +    C_ETC,   C_ETC,   C_ETC,   C_LCURB, C_ETC,   C_RCURB, C_ETC,   C_ETC
          433  +};
          434  +
          435  +
          436  +/*
          437  +    The state codes.
          438  +*/
          439  +enum states {
          440  +    GO,  /* start    */
          441  +    OK,  /* ok       */
          442  +    OB,  /* object   */
          443  +    KE,  /* key      */
          444  +    CO,  /* colon    */
          445  +    VA,  /* value    */
          446  +    AR,  /* array    */
          447  +    ST,  /* string   */
          448  +    ES,  /* escape   */
          449  +    U1,  /* u1       */
          450  +    U2,  /* u2       */
          451  +    U3,  /* u3       */
          452  +    U4,  /* u4       */
          453  +    MI,  /* minus    */
          454  +    ZE,  /* zero     */
          455  +    IT,  /* integer  */
          456  +    FR,  /* fraction */
          457  +    E1,  /* e        */
          458  +    E2,  /* ex       */
          459  +    E3,  /* exp      */
          460  +    T1,  /* tr       */
          461  +    T2,  /* tru      */
          462  +    T3,  /* true     */
          463  +    F1,  /* fa       */
          464  +    F2,  /* fal      */
          465  +    F3,  /* fals     */
          466  +    F4,  /* false    */
          467  +    N1,  /* nu       */
          468  +    N2,  /* nul      */
          469  +    N3,  /* null     */
          470  +    C1,  /* /        */
          471  +    C2,  /* / *     */
          472  +    C3,  /* *        */
          473  +    FX,  /* *.* *eE* */
          474  +    D1,  /* second UTF-16 character decoding started by \ */
          475  +    D2,  /* second UTF-16 character proceeded by u */
          476  +    NR_STATES
          477  +};
          478  +
          479  +enum actions
          480  +{
          481  +    CB = -10, /* comment begin */
          482  +    CE = -11, /* comment end */
          483  +    FA = -12, /* false */
          484  +    TR = -13, /* false */
          485  +    NU = -14, /* null */
          486  +    DE = -15, /* double detected by exponent e E */
          487  +    DF = -16, /* double detected by fraction . */
          488  +    SB = -17, /* string begin */
          489  +    MX = -18, /* integer detected by minus */
          490  +    ZX = -19, /* integer detected by zero */
          491  +    IX = -20, /* integer detected by 1-9 */
          492  +    EX = -21, /* next char is escaped */
          493  +    UC = -22  /* Unicode character read */
          494  +};
          495  +
          496  +
          497  +static const signed char state_transition_table[NR_STATES][NR_CLASSES] = {
          498  +/*
          499  +    The state transition table takes the current state and the current symbol,
          500  +    and returns either a new state or an action. An action is represented as a
          501  +    negative number. A JSON text is accepted if at the end of the text the
          502  +    state is OK and if the mode is MODE_DONE.
          503  +
          504  +                 white                                      1-9                                   ABCDF  etc
          505  +             space |  {  }  [  ]  :  ,  "  \  /  +  -  .  0  |  a  b  c  d  e  f  l  n  r  s  t  u  |  E  |  * */
          506  +/*start  GO*/ {GO,GO,-6,__,-5,__,__,__,__,__,CB,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
          507  +/*ok     OK*/ {OK,OK,__,-8,__,-7,__,-3,__,__,CB,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
          508  +/*object OB*/ {OB,OB,__,-9,__,__,__,__,SB,__,CB,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
          509  +/*key    KE*/ {KE,KE,__,__,__,__,__,__,SB,__,CB,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
          510  +/*colon  CO*/ {CO,CO,__,__,__,__,-2,__,__,__,CB,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
          511  +/*value  VA*/ {VA,VA,-6,__,-5,__,__,__,SB,__,CB,__,MX,__,ZX,IX,__,__,__,__,__,FA,__,NU,__,__,TR,__,__,__,__,__},
          512  +/*array  AR*/ {AR,AR,-6,__,-5,-7,__,__,SB,__,CB,__,MX,__,ZX,IX,__,__,__,__,__,FA,__,NU,__,__,TR,__,__,__,__,__},
          513  +/*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},
          514  +/*escape ES*/ {__,__,__,__,__,__,__,__,ST,ST,ST,__,__,__,__,__,__,ST,__,__,__,ST,__,ST,ST,__,ST,U1,__,__,__,__},
          515  +/*u1     U1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,U2,U2,U2,U2,U2,U2,U2,U2,__,__,__,__,__,__,U2,U2,__,__},
          516  +/*u2     U2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,U3,U3,U3,U3,U3,U3,U3,U3,__,__,__,__,__,__,U3,U3,__,__},
          517  +/*u3     U3*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,U4,U4,U4,U4,U4,U4,U4,U4,__,__,__,__,__,__,U4,U4,__,__},
          518  +/*u4     U4*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,UC,UC,UC,UC,UC,UC,UC,UC,__,__,__,__,__,__,UC,UC,__,__},
          519  +/*minus  MI*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,ZE,IT,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
          520  +/*zero   ZE*/ {OK,OK,__,-8,__,-7,__,-3,__,__,CB,__,__,DF,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
          521  +/*int    IT*/ {OK,OK,__,-8,__,-7,__,-3,__,__,CB,__,__,DF,IT,IT,__,__,__,__,DE,__,__,__,__,__,__,__,__,DE,__,__},
          522  +/*frac   FR*/ {OK,OK,__,-8,__,-7,__,-3,__,__,CB,__,__,__,FR,FR,__,__,__,__,E1,__,__,__,__,__,__,__,__,E1,__,__},
          523  +/*e      E1*/ {__,__,__,__,__,__,__,__,__,__,__,E2,E2,__,E3,E3,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
          524  +/*ex     E2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,E3,E3,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
          525  +/*exp    E3*/ {OK,OK,__,-8,__,-7,__,-3,__,__,__,__,__,__,E3,E3,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
          526  +/*tr     T1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,T2,__,__,__,__,__,__,__},
          527  +/*tru    T2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,T3,__,__,__,__},
          528  +/*true   T3*/ {__,__,__,__,__,__,__,__,__,__,CB,__,__,__,__,__,__,__,__,__,OK,__,__,__,__,__,__,__,__,__,__,__},
          529  +/*fa     F1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,F2,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
          530  +/*fal    F2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,F3,__,__,__,__,__,__,__,__,__},
          531  +/*fals   F3*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,F4,__,__,__,__,__,__},
          532  +/*false  F4*/ {__,__,__,__,__,__,__,__,__,__,CB,__,__,__,__,__,__,__,__,__,OK,__,__,__,__,__,__,__,__,__,__,__},
          533  +/*nu     N1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,N2,__,__,__,__},
          534  +/*nul    N2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,N3,__,__,__,__,__,__,__,__,__},
          535  +/*null   N3*/ {__,__,__,__,__,__,__,__,__,__,CB,__,__,__,__,__,__,__,__,__,__,__,OK,__,__,__,__,__,__,__,__,__},
          536  +/*/      C1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,C2},
          537  +/*/*     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},
          538  +/**      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},
          539  +/*_.     FX*/ {OK,OK,__,-8,__,-7,__,-3,__,__,__,__,__,__,FR,FR,__,__,__,__,E1,__,__,__,__,__,__,__,__,E1,__,__},
          540  +/*\      D1*/ {__,__,__,__,__,__,__,__,__,D2,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
          541  +/*\      D2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,U1,__,__,__,__},
          542  +};
          543  +
          544  +
          545  +/*
          546  +    These modes can be pushed on the stack.
          547  +*/
          548  +enum modes {
          549  +    MODE_ARRAY = 1, 
          550  +    MODE_DONE = 2,  
          551  +    MODE_KEY = 3,   
          552  +    MODE_OBJECT = 4
          553  +};
          554  +
          555  +static void set_error(JSON_parser jc)
          556  +{
          557  +    switch (jc->state) {
          558  +        case GO:
          559  +            switch (jc->current_char) {
          560  +            case '{': case '}': case '[': case ']': 
          561  +                jc->error = JSON_E_UNBALANCED_COLLECTION;
          562  +                break;
          563  +            default:
          564  +                jc->error = JSON_E_INVALID_CHAR;
          565  +                break;    
          566  +            }
          567  +            break;
          568  +        case OB:
          569  +            jc->error = JSON_E_EXPECTED_KEY;
          570  +            break;
          571  +        case AR:
          572  +            jc->error = JSON_E_UNBALANCED_COLLECTION;
          573  +            break;
          574  +        case CO:
          575  +            jc->error = JSON_E_EXPECTED_COLON;
          576  +            break;
          577  +        case KE:
          578  +            jc->error = JSON_E_EXPECTED_KEY;
          579  +            break;
          580  +        /* \uXXXX\uYYYY */
          581  +        case U1: case U2: case U3: case U4: case D1: case D2:
          582  +            jc->error = JSON_E_INVALID_UNICODE_SEQUENCE;
          583  +            break;
          584  +        /* true, false, null */
          585  +        case T1: case T2: case T3: case F1: case F2: case F3: case F4: case N1: case N2: case N3:
          586  +            jc->error = JSON_E_INVALID_KEYWORD;
          587  +            break;
          588  +        /* minus, integer, fraction, exponent */
          589  +        case MI: case ZE: case IT: case FR: case E1: case E2: case E3:
          590  +            jc->error = JSON_E_INVALID_NUMBER;
          591  +            break;
          592  +        default:
          593  +            jc->error = JSON_E_INVALID_CHAR;
          594  +            break;
          595  +    }
          596  +}
          597  +
          598  +static int
          599  +push(JSON_parser jc, int mode)
          600  +{
          601  +/*
          602  +    Push a mode onto the stack. Return false if there is overflow.
          603  +*/
          604  +    assert(jc->top <= jc->stack_capacity);
          605  +    
          606  +    if (jc->depth < 0) {
          607  +        if (jc->top == jc->stack_capacity) {
          608  +            const size_t bytes_to_copy = jc->stack_capacity * sizeof(jc->stack[0]);
          609  +            const size_t new_capacity = jc->stack_capacity * 2;
          610  +            const size_t bytes_to_allocate = new_capacity * sizeof(jc->stack[0]);
          611  +            void* mem = JSON_parser_malloc(jc->malloc, bytes_to_allocate, "stack");
          612  +            if (!mem) {
          613  +                jc->error = JSON_E_OUT_OF_MEMORY;
          614  +                return false;
          615  +            }
          616  +            jc->stack_capacity = (int)new_capacity;
          617  +            memcpy(mem, jc->stack, bytes_to_copy);
          618  +            if (jc->stack != &jc->static_stack[0]) {
          619  +                jc->free(jc->stack);
          620  +            }
          621  +            jc->stack = (signed char*)mem;
          622  +        }
          623  +    } else {
          624  +        if (jc->top == jc->depth) {
          625  +            jc->error = JSON_E_NESTING_DEPTH_REACHED;
          626  +            return false;
          627  +        }
          628  +    }
          629  +    jc->stack[++jc->top] = (signed char)mode;
          630  +    return true;
          631  +}
          632  +
          633  +
          634  +static int
          635  +pop(JSON_parser jc, int mode)
          636  +{
          637  +/*
          638  +    Pop the stack, assuring that the current mode matches the expectation.
          639  +    Return false if there is underflow or if the modes mismatch.
          640  +*/
          641  +    if (jc->top < 0 || jc->stack[jc->top] != mode) {
          642  +        return false;
          643  +    }
          644  +    jc->top -= 1;
          645  +    return true;
          646  +}
          647  +
          648  +
          649  +#define parse_buffer_clear(jc) \
          650  +    do {\
          651  +        jc->parse_buffer_count = 0;\
          652  +        jc->parse_buffer[0] = 0;\
          653  +    } while (0)
          654  +    
          655  +#define parse_buffer_pop_back_char(jc)\
          656  +    do {\
          657  +        assert(jc->parse_buffer_count >= 1);\
          658  +        --jc->parse_buffer_count;\
          659  +        jc->parse_buffer[jc->parse_buffer_count] = 0;\
          660  +    } while (0)    
          661  +
          662  +
          663  +
          664  +void delete_JSON_parser(JSON_parser jc)
          665  +{
          666  +    if (jc) {
          667  +        if (jc->stack != &jc->static_stack[0]) {
          668  +            jc->free((void*)jc->stack);
          669  +        }
          670  +        if (jc->parse_buffer != &jc->static_parse_buffer[0]) {
          671  +            jc->free((void*)jc->parse_buffer);
          672  +        }
          673  +        jc->free((void*)jc);
          674  +     }   
          675  +}
          676  +
          677  +int JSON_parser_reset(JSON_parser jc)
          678  +{
          679  +    if (NULL == jc) {
          680  +        return false;
          681  +    }
          682  +    
          683  +    jc->state = GO;
          684  +    jc->top = -1;
          685  +
          686  +    /* parser has been used previously? */
          687  +    if (NULL == jc->parse_buffer) {
          688  +    
          689  +        /* Do we want non-bound stack? */
          690  +        if (jc->depth > 0) {
          691  +            jc->stack_capacity = jc->depth;
          692  +            if (jc->depth <= (int)COUNTOF(jc->static_stack)) {
          693  +                jc->stack = &jc->static_stack[0];
          694  +            } else {
          695  +                const size_t bytes_to_alloc = jc->stack_capacity * sizeof(jc->stack[0]);
          696  +                jc->stack = (signed char*)JSON_parser_malloc(jc->malloc, bytes_to_alloc, "stack");
          697  +                if (jc->stack == NULL) {
          698  +                    return false;
          699  +                }
          700  +            }
          701  +        } else {
          702  +            jc->stack_capacity = (int)COUNTOF(jc->static_stack);
          703  +            jc->depth = -1;
          704  +            jc->stack = &jc->static_stack[0];
          705  +        }
          706  +        
          707  +        /* set up the parse buffer */
          708  +        jc->parse_buffer = &jc->static_parse_buffer[0];
          709  +        jc->parse_buffer_capacity = COUNTOF(jc->static_parse_buffer);
          710  +    }
          711  +    
          712  +    /* set parser to start */
          713  +    push(jc, MODE_DONE);
          714  +    parse_buffer_clear(jc);
          715  +    
          716  +    return true;
          717  +}
          718  +
          719  +JSON_parser
          720  +new_JSON_parser(JSON_config const * config)
          721  +{
          722  +/*
          723  +    new_JSON_parser starts the checking process by constructing a JSON_parser
          724  +    object. It takes a depth parameter that restricts the level of maximum
          725  +    nesting.
          726  +
          727  +    To continue the process, call JSON_parser_char for each character in the
          728  +    JSON text, and then call JSON_parser_done to obtain the final result.
          729  +    These functions are fully reentrant.
          730  +*/
          731  +
          732  +    int use_std_malloc = false;
          733  +    JSON_config default_config;
          734  +    JSON_parser jc;
          735  +    JSON_malloc_t alloc;
          736  +    
          737  +    /* set to default configuration if none was provided */
          738  +    if (NULL == config) {
          739  +        /* initialize configuration */
          740  +        init_JSON_config(&default_config);
          741  +        config = &default_config;
          742  +    }
          743  +    
          744  +    /* use std malloc if either the allocator or deallocator function isn't set */
          745  +    use_std_malloc = NULL == config->malloc || NULL == config->free;
          746  +    
          747  +    alloc = use_std_malloc ? malloc : config->malloc;
          748  +    
          749  +    jc = JSON_parser_malloc(alloc, sizeof(*jc), "parser");    
          750  +    
          751  +    if (NULL == jc) {
          752  +        return NULL;
          753  +    }
          754  +    
          755  +    /* configure the parser */
          756  +    memset(jc, 0, sizeof(*jc));
          757  +    jc->malloc = alloc;
          758  +    jc->free = use_std_malloc ? free : config->free;
          759  +    jc->callback = config->callback;
          760  +    jc->ctx = config->callback_ctx;
          761  +    jc->allow_comments = (signed char)(config->allow_comments != 0);
          762  +    jc->handle_floats_manually = (signed char)(config->handle_floats_manually != 0);
          763  +    jc->decimal_point = *localeconv()->decimal_point;
          764  +    /* We need to be able to push at least one object */
          765  +    jc->depth = config->depth == 0 ? 1 : config->depth;
          766  +    
          767  +    /* reset the parser */
          768  +    if (!JSON_parser_reset(jc)) {
          769  +        jc->free(jc);
          770  +        return NULL;
          771  +    }
          772  +    
          773  +    return jc;
          774  +}
          775  +
          776  +static int parse_buffer_grow(JSON_parser jc)
          777  +{
          778  +    const size_t bytes_to_copy = jc->parse_buffer_count * sizeof(jc->parse_buffer[0]);
          779  +    const size_t new_capacity = jc->parse_buffer_capacity * 2;
          780  +    const size_t bytes_to_allocate = new_capacity * sizeof(jc->parse_buffer[0]);
          781  +    void* mem = JSON_parser_malloc(jc->malloc, bytes_to_allocate, "parse buffer");
          782  +    
          783  +    if (mem == NULL) {
          784  +        jc->error = JSON_E_OUT_OF_MEMORY;
          785  +        return false;
          786  +    }
          787  +    
          788  +    assert(new_capacity > 0);
          789  +    memcpy(mem, jc->parse_buffer, bytes_to_copy);
          790  +    
          791  +    if (jc->parse_buffer != &jc->static_parse_buffer[0]) {
          792  +        jc->free(jc->parse_buffer);
          793  +    }
          794  +    
          795  +    jc->parse_buffer = (char*)mem;
          796  +    jc->parse_buffer_capacity = new_capacity;
          797  +    
          798  +    return true;
          799  +}
          800  +
          801  +static int parse_buffer_reserve_for(JSON_parser jc, unsigned chars)
          802  +{
          803  +    while (jc->parse_buffer_count + chars + 1 > jc->parse_buffer_capacity) {
          804  +        if (!parse_buffer_grow(jc)) {
          805  +            assert(jc->error == JSON_E_OUT_OF_MEMORY);
          806  +            return false;
          807  +        }
          808  +    }
          809  +    
          810  +    return true;
          811  +}
          812  +
          813  +#define parse_buffer_has_space_for(jc, count) \
          814  +    (jc->parse_buffer_count + (count) + 1 <= jc->parse_buffer_capacity)
          815  +
          816  +#define parse_buffer_push_back_char(jc, c)\
          817  +    do {\
          818  +        assert(parse_buffer_has_space_for(jc, 1)); \
          819  +        jc->parse_buffer[jc->parse_buffer_count++] = c;\
          820  +        jc->parse_buffer[jc->parse_buffer_count]   = 0;\
          821  +    } while (0)
          822  +
          823  +#define assert_is_non_container_type(jc) \
          824  +    assert( \
          825  +        jc->type == JSON_T_NULL || \
          826  +        jc->type == JSON_T_FALSE || \
          827  +        jc->type == JSON_T_TRUE || \
          828  +        jc->type == JSON_T_FLOAT || \
          829  +        jc->type == JSON_T_INTEGER || \
          830  +        jc->type == JSON_T_STRING)
          831  +    
          832  +
          833  +static int parse_parse_buffer(JSON_parser jc)
          834  +{
          835  +    if (jc->callback) {
          836  +        JSON_value value, *arg = NULL;
          837  +        
          838  +        if (jc->type != JSON_T_NONE) {
          839  +            assert_is_non_container_type(jc);
          840  +        
          841  +            switch(jc->type) {
          842  +                case JSON_T_FLOAT:
          843  +                    arg = &value;
          844  +                    if (jc->handle_floats_manually) {
          845  +                        value.vu.str.value = jc->parse_buffer;
          846  +                        value.vu.str.length = jc->parse_buffer_count;
          847  +                    } else { 
          848  +                        /* not checking with end pointer b/c there may be trailing ws */
          849  +                        value.vu.float_value = strtod(jc->parse_buffer, NULL);
          850  +                    }
          851  +                    break;
          852  +                case JSON_T_INTEGER:
          853  +                    arg = &value;
          854  +                    sscanf(jc->parse_buffer, JSON_PARSER_INTEGER_SSCANF_TOKEN, &value.vu.integer_value);
          855  +                    break;
          856  +                case JSON_T_STRING:
          857  +                    arg = &value;
          858  +                    value.vu.str.value = jc->parse_buffer;
          859  +                    value.vu.str.length = jc->parse_buffer_count;
          860  +                    break;
          861  +            }
          862  +            
          863  +            if (!(*jc->callback)(jc->ctx, jc->type, arg)) {
          864  +                return false;
          865  +            }
          866  +        }
          867  +    }
          868  +    
          869  +    parse_buffer_clear(jc);
          870  +    
          871  +    return true;
          872  +}
          873  +
          874  +#define IS_HIGH_SURROGATE(uc) (((uc) & 0xFC00) == 0xD800)
          875  +#define IS_LOW_SURROGATE(uc)  (((uc) & 0xFC00) == 0xDC00)
          876  +#define DECODE_SURROGATE_PAIR(hi,lo) ((((hi) & 0x3FF) << 10) + ((lo) & 0x3FF) + 0x10000)
          877  +static const unsigned char utf8_lead_bits[4] = { 0x00, 0xC0, 0xE0, 0xF0 };
          878  +
          879  +static int decode_unicode_char(JSON_parser jc)
          880  +{
          881  +    int i;
          882  +    unsigned uc = 0;
          883  +    char* p;
          884  +    int trail_bytes;
          885  +    
          886  +    assert(jc->parse_buffer_count >= 6);
          887  +    
          888  +    p = &jc->parse_buffer[jc->parse_buffer_count - 4];
          889  +    
          890  +    for (i = 12; i >= 0; i -= 4, ++p) {
          891  +        unsigned x = *p;
          892  +        
          893  +        if (x >= 'a') {
          894  +            x -= ('a' - 10);
          895  +        } else if (x >= 'A') {
          896  +            x -= ('A' - 10);
          897  +        } else {
          898  +            x &= ~0x30u;
          899  +        }
          900  +        
          901  +        assert(x < 16);
          902  +        
          903  +        uc |= x << i;
          904  +    }
          905  +    
          906  +    /* clear UTF-16 char from buffer */
          907  +    jc->parse_buffer_count -= 6;
          908  +    jc->parse_buffer[jc->parse_buffer_count] = 0;
          909  +    
          910  +    /* attempt decoding ... */
          911  +    if (jc->utf16_high_surrogate) {
          912  +        if (IS_LOW_SURROGATE(uc)) {
          913  +            uc = DECODE_SURROGATE_PAIR(jc->utf16_high_surrogate, uc);
          914  +            trail_bytes = 3;
          915  +            jc->utf16_high_surrogate = 0;
          916  +        } else {
          917  +            /* high surrogate without a following low surrogate */
          918  +            return false;
          919  +        }
          920  +    } else {
          921  +        if (uc < 0x80) {
          922  +            trail_bytes = 0;
          923  +        } else if (uc < 0x800) {
          924  +            trail_bytes = 1;
          925  +        } else if (IS_HIGH_SURROGATE(uc)) {
          926  +            /* save the high surrogate and wait for the low surrogate */
          927  +            jc->utf16_high_surrogate = (UTF16)uc;
          928  +            return true;
          929  +        } else if (IS_LOW_SURROGATE(uc)) {
          930  +            /* low surrogate without a preceding high surrogate */
          931  +            return false;
          932  +        } else {
          933  +            trail_bytes = 2;
          934  +        }
          935  +    }
          936  +    
          937  +    jc->parse_buffer[jc->parse_buffer_count++] = (char) ((uc >> (trail_bytes * 6)) | utf8_lead_bits[trail_bytes]);
          938  +    
          939  +    for (i = trail_bytes * 6 - 6; i >= 0; i -= 6) {
          940  +        jc->parse_buffer[jc->parse_buffer_count++] = (char) (((uc >> i) & 0x3F) | 0x80);
          941  +    }
          942  +
          943  +    jc->parse_buffer[jc->parse_buffer_count] = 0;
          944  +    
          945  +    return true;
          946  +}
          947  +
          948  +static int add_escaped_char_to_parse_buffer(JSON_parser jc, int next_char)
          949  +{
          950  +    assert(parse_buffer_has_space_for(jc, 1));
          951  +    
          952  +    jc->escaped = 0;
          953  +    /* remove the backslash */
          954  +    parse_buffer_pop_back_char(jc);
          955  +    switch(next_char) {
          956  +        case 'b':
          957  +            parse_buffer_push_back_char(jc, '\b');
          958  +            break;
          959  +        case 'f':
          960  +            parse_buffer_push_back_char(jc, '\f');
          961  +            break;
          962  +        case 'n':
          963  +            parse_buffer_push_back_char(jc, '\n');
          964  +            break;
          965  +        case 'r':
          966  +            parse_buffer_push_back_char(jc, '\r');
          967  +            break;
          968  +        case 't':
          969  +            parse_buffer_push_back_char(jc, '\t');
          970  +            break;
          971  +        case '"':
          972  +            parse_buffer_push_back_char(jc, '"');
          973  +            break;
          974  +        case '\\':
          975  +            parse_buffer_push_back_char(jc, '\\');
          976  +            break;
          977  +        case '/':
          978  +            parse_buffer_push_back_char(jc, '/');
          979  +            break;
          980  +        case 'u':
          981  +            parse_buffer_push_back_char(jc, '\\');
          982  +            parse_buffer_push_back_char(jc, 'u');
          983  +            break;
          984  +        default:
          985  +            return false;
          986  +    }
          987  +
          988  +    return true;
          989  +}
          990  +
          991  +static int add_char_to_parse_buffer(JSON_parser jc, int next_char, int next_class)
          992  +{
          993  +    if (!parse_buffer_reserve_for(jc, 1)) {
          994  +        assert(JSON_E_OUT_OF_MEMORY == jc->error);
          995  +        return false;
          996  +    }
          997  +    
          998  +    if (jc->escaped) {
          999  +        if (!add_escaped_char_to_parse_buffer(jc, next_char)) {
         1000  +            jc->error = JSON_E_INVALID_ESCAPE_SEQUENCE;
         1001  +            return false; 
         1002  +        }
         1003  +    } else if (!jc->comment) {
         1004  +        if ((jc->type != JSON_T_NONE) | !((next_class == C_SPACE) | (next_class == C_WHITE)) /* non-white-space */) {
         1005  +            parse_buffer_push_back_char(jc, (char)next_char);
         1006  +        }
         1007  +    }
         1008  +    
         1009  +    return true;
         1010  +}
         1011  +
         1012  +#define assert_type_isnt_string_null_or_bool(jc) \
         1013  +    assert(jc->type != JSON_T_FALSE); \
         1014  +    assert(jc->type != JSON_T_TRUE); \
         1015  +    assert(jc->type != JSON_T_NULL); \
         1016  +    assert(jc->type != JSON_T_STRING)
         1017  +
         1018  +
         1019  +int
         1020  +JSON_parser_char(JSON_parser jc, int next_char)
         1021  +{
         1022  +/*
         1023  +    After calling new_JSON_parser, call this function for each character (or
         1024  +    partial character) in your JSON text. It can accept UTF-8, UTF-16, or
         1025  +    UTF-32. It returns true if things are looking ok so far. If it rejects the
         1026  +    text, it returns false.
         1027  +*/
         1028  +    int next_class, next_state;
         1029  +
         1030  +/*
         1031  +    Store the current char for error handling
         1032  +*/    
         1033  +    jc->current_char = next_char;
         1034  +    
         1035  +/*
         1036  +    Determine the character's class.
         1037  +*/
         1038  +    if (next_char < 0) {
         1039  +        jc->error = JSON_E_INVALID_CHAR;
         1040  +        return false;
         1041  +    }
         1042  +    if (next_char >= 128) {
         1043  +        next_class = C_ETC;
         1044  +    } else {
         1045  +        next_class = ascii_class[next_char];
         1046  +        if (next_class <= __) {
         1047  +            set_error(jc);
         1048  +            return false;
         1049  +        }
         1050  +    }
         1051  +    
         1052  +    if (!add_char_to_parse_buffer(jc, next_char, next_class)) {
         1053  +        return false;
         1054  +    }
         1055  +    
         1056  +/*
         1057  +    Get the next state from the state transition table.
         1058  +*/
         1059  +    next_state = state_transition_table[jc->state][next_class];
         1060  +    if (next_state >= 0) {
         1061  +/*
         1062  +    Change the state.
         1063  +*/
         1064  +        jc->state = (signed char)next_state;
         1065  +    } else {
         1066  +/*
         1067  +    Or perform one of the actions.
         1068  +*/
         1069  +        switch (next_state) {
         1070  +/* Unicode character */        
         1071  +        case UC:
         1072  +            if(!decode_unicode_char(jc)) {
         1073  +                jc->error = JSON_E_INVALID_UNICODE_SEQUENCE;
         1074  +                return false;
         1075  +            }
         1076  +            /* check if we need to read a second UTF-16 char */
         1077  +            if (jc->utf16_high_surrogate) {
         1078  +                jc->state = D1;
         1079  +            } else {
         1080  +                jc->state = ST;
         1081  +            }
         1082  +            break;
         1083  +/* escaped char */
         1084  +        case EX:
         1085  +            jc->escaped = 1;
         1086  +            jc->state = ES;
         1087  +            break;
         1088  +/* integer detected by minus */
         1089  +        case MX:
         1090  +            jc->type = JSON_T_INTEGER;
         1091  +            jc->state = MI;
         1092  +            break;  
         1093  +/* integer detected by zero */            
         1094  +        case ZX:
         1095  +            jc->type = JSON_T_INTEGER;
         1096  +            jc->state = ZE;
         1097  +            break;  
         1098  +/* integer detected by 1-9 */            
         1099  +        case IX:
         1100  +            jc->type = JSON_T_INTEGER;
         1101  +            jc->state = IT;
         1102  +            break;  
         1103  +            
         1104  +/* floating point number detected by exponent*/
         1105  +        case DE:
         1106  +            assert_type_isnt_string_null_or_bool(jc);
         1107  +            jc->type = JSON_T_FLOAT;
         1108  +            jc->state = E1;
         1109  +            break;   
         1110  +        
         1111  +/* floating point number detected by fraction */
         1112  +        case DF:
         1113  +            assert_type_isnt_string_null_or_bool(jc);
         1114  +            if (!jc->handle_floats_manually) {
         1115  +/*
         1116  +    Some versions of strtod (which underlies sscanf) don't support converting 
         1117  +    C-locale formated floating point values.
         1118  +*/           
         1119  +                assert(jc->parse_buffer[jc->parse_buffer_count-1] == '.');
         1120  +                jc->parse_buffer[jc->parse_buffer_count-1] = jc->decimal_point;
         1121  +            }            
         1122  +            jc->type = JSON_T_FLOAT;
         1123  +            jc->state = FX;
         1124  +            break;   
         1125  +/* string begin " */
         1126  +        case SB:
         1127  +            parse_buffer_clear(jc);
         1128  +            assert(jc->type == JSON_T_NONE);
         1129  +            jc->type = JSON_T_STRING;
         1130  +            jc->state = ST;
         1131  +            break;        
         1132  +        
         1133  +/* n */
         1134  +        case NU:
         1135  +            assert(jc->type == JSON_T_NONE);
         1136  +            jc->type = JSON_T_NULL;
         1137  +            jc->state = N1;
         1138  +            break;        
         1139  +/* f */
         1140  +        case FA:
         1141  +            assert(jc->type == JSON_T_NONE);
         1142  +            jc->type = JSON_T_FALSE;
         1143  +            jc->state = F1;
         1144  +            break;        
         1145  +/* t */
         1146  +        case TR:
         1147  +            assert(jc->type == JSON_T_NONE);
         1148  +            jc->type = JSON_T_TRUE;
         1149  +            jc->state = T1;
         1150  +            break;        
         1151  +        
         1152  +/* closing comment */
         1153  +        case CE:
         1154  +            jc->comment = 0;
         1155  +            assert(jc->parse_buffer_count == 0);
         1156  +            assert(jc->type == JSON_T_NONE);
         1157  +            jc->state = jc->before_comment_state;
         1158  +            break;        
         1159  +        
         1160  +/* opening comment  */
         1161  +        case CB:
         1162  +            if (!jc->allow_comments) {
         1163  +                return false;
         1164  +            }
         1165  +            parse_buffer_pop_back_char(jc);
         1166  +            if (!parse_parse_buffer(jc)) {
         1167  +                return false;
         1168  +            }
         1169  +            assert(jc->parse_buffer_count == 0);
         1170  +            assert(jc->type != JSON_T_STRING);
         1171  +            switch (jc->stack[jc->top]) {
         1172  +            case MODE_ARRAY:
         1173  +            case MODE_OBJECT:   
         1174  +                switch(jc->state) {
         1175  +                case VA:
         1176  +                case AR:
         1177  +                    jc->before_comment_state = jc->state;
         1178  +                    break;
         1179  +                default:
         1180  +                    jc->before_comment_state = OK;
         1181  +                    break;
         1182  +                }
         1183  +                break;
         1184  +            default:
         1185  +                jc->before_comment_state = jc->state;
         1186  +                break;
         1187  +            }
         1188  +            jc->type = JSON_T_NONE;
         1189  +            jc->state = C1;
         1190  +            jc->comment = 1;
         1191  +            break;
         1192  +/* empty } */
         1193  +        case -9:        
         1194  +            parse_buffer_clear(jc);
         1195  +            if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_OBJECT_END, NULL)) {
         1196  +                return false;
         1197  +            }
         1198  +            if (!pop(jc, MODE_KEY)) {
         1199  +                return false;
         1200  +            }
         1201  +            jc->state = OK;
         1202  +            break;
         1203  +
         1204  +/* } */ case -8:
         1205  +            parse_buffer_pop_back_char(jc);
         1206  +            if (!parse_parse_buffer(jc)) {
         1207  +                return false;
         1208  +            }
         1209  +            if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_OBJECT_END, NULL)) {
         1210  +                return false;
         1211  +            }
         1212  +            if (!pop(jc, MODE_OBJECT)) {
         1213  +                jc->error = JSON_E_UNBALANCED_COLLECTION;
         1214  +                return false;
         1215  +            }
         1216  +            jc->type = JSON_T_NONE;
         1217  +            jc->state = OK;
         1218  +            break;
         1219  +
         1220  +/* ] */ case -7:
         1221  +            parse_buffer_pop_back_char(jc);
         1222  +            if (!parse_parse_buffer(jc)) {
         1223  +                return false;
         1224  +            }
         1225  +            if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_ARRAY_END, NULL)) {
         1226  +                return false;
         1227  +            }
         1228  +            if (!pop(jc, MODE_ARRAY)) {
         1229  +                jc->error = JSON_E_UNBALANCED_COLLECTION;
         1230  +                return false;
         1231  +            }
         1232  +            
         1233  +            jc->type = JSON_T_NONE;
         1234  +            jc->state = OK;
         1235  +            break;
         1236  +
         1237  +/* { */ case -6:
         1238  +            parse_buffer_pop_back_char(jc);
         1239  +            if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_OBJECT_BEGIN, NULL)) {
         1240  +                return false;
         1241  +            }
         1242  +            if (!push(jc, MODE_KEY)) {
         1243  +                return false;
         1244  +            }
         1245  +            assert(jc->type == JSON_T_NONE);
         1246  +            jc->state = OB;
         1247  +            break;
         1248  +
         1249  +/* [ */ case -5:
         1250  +            parse_buffer_pop_back_char(jc);
         1251  +            if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_ARRAY_BEGIN, NULL)) {
         1252  +                return false;
         1253  +            }
         1254  +            if (!push(jc, MODE_ARRAY)) {
         1255  +                return false;
         1256  +            }
         1257  +            assert(jc->type == JSON_T_NONE);
         1258  +            jc->state = AR;
         1259  +            break;
         1260  +
         1261  +/* string end " */ case -4:
         1262  +            parse_buffer_pop_back_char(jc);
         1263  +            switch (jc->stack[jc->top]) {
         1264  +            case MODE_KEY:
         1265  +                assert(jc->type == JSON_T_STRING);
         1266  +                jc->type = JSON_T_NONE;
         1267  +                jc->state = CO;
         1268  +                
         1269  +                if (jc->callback) {
         1270  +                    JSON_value value;
         1271  +                    value.vu.str.value = jc->parse_buffer;
         1272  +                    value.vu.str.length = jc->parse_buffer_count;
         1273  +                    if (!(*jc->callback)(jc->ctx, JSON_T_KEY, &value)) {
         1274  +                        return false;
         1275  +                    }
         1276  +                }
         1277  +                parse_buffer_clear(jc);
         1278  +                break;
         1279  +            case MODE_ARRAY:
         1280  +            case MODE_OBJECT:
         1281  +                assert(jc->type == JSON_T_STRING);
         1282  +                if (!parse_parse_buffer(jc)) {
         1283  +                    return false;
         1284  +                }
         1285  +                jc->type = JSON_T_NONE;
         1286  +                jc->state = OK;
         1287  +                break;
         1288  +            default:
         1289  +                return false;
         1290  +            }
         1291  +            break;
         1292  +
         1293  +/* , */ case -3:
         1294  +            parse_buffer_pop_back_char(jc);
         1295  +            if (!parse_parse_buffer(jc)) {
         1296  +                return false;
         1297  +            }
         1298  +            switch (jc->stack[jc->top]) {
         1299  +            case MODE_OBJECT:
         1300  +/*
         1301  +    A comma causes a flip from object mode to key mode.
         1302  +*/
         1303  +                if (!pop(jc, MODE_OBJECT) || !push(jc, MODE_KEY)) {
         1304  +                    return false;
         1305  +                }
         1306  +                assert(jc->type != JSON_T_STRING);
         1307  +                jc->type = JSON_T_NONE;
         1308  +                jc->state = KE;
         1309  +                break;
         1310  +            case MODE_ARRAY:
         1311  +                assert(jc->type != JSON_T_STRING);
         1312  +                jc->type = JSON_T_NONE;
         1313  +                jc->state = VA;
         1314  +                break;
         1315  +            default:
         1316  +                return false;
         1317  +            }
         1318  +            break;
         1319  +
         1320  +/* : */ case -2:
         1321  +/*
         1322  +    A colon causes a flip from key mode to object mode.
         1323  +*/
         1324  +            parse_buffer_pop_back_char(jc);
         1325  +            if (!pop(jc, MODE_KEY) || !push(jc, MODE_OBJECT)) {
         1326  +                return false;
         1327  +            }
         1328  +            assert(jc->type == JSON_T_NONE);
         1329  +            jc->state = VA;
         1330  +            break;
         1331  +/*
         1332  +    Bad action.
         1333  +*/
         1334  +        default:
         1335  +            set_error(jc);
         1336  +            return false;
         1337  +        }
         1338  +    }
         1339  +    return true;
         1340  +}
         1341  +
         1342  +int
         1343  +JSON_parser_done(JSON_parser jc)
         1344  +{
         1345  +    if ((jc->state == OK || jc->state == GO) && pop(jc, MODE_DONE))
         1346  +    {
         1347  +        return true;
         1348  +    }
         1349  +
         1350  +    jc->error = JSON_E_UNBALANCED_COLLECTION;
         1351  +    return false;
         1352  +}
         1353  +
         1354  +
         1355  +int JSON_parser_is_legal_white_space_string(const char* s)
         1356  +{
         1357  +    int c, char_class;
         1358  +    
         1359  +    if (s == NULL) {
         1360  +        return false;
         1361  +    }
         1362  +    
         1363  +    for (; *s; ++s) {   
         1364  +        c = *s;
         1365  +        
         1366  +        if (c < 0 || c >= 128) {
         1367  +            return false;
         1368  +        }
         1369  +        
         1370  +        char_class = ascii_class[c];
         1371  +        
         1372  +        if (char_class != C_SPACE && char_class != C_WHITE) {
         1373  +            return false;
         1374  +        }
         1375  +    }
         1376  +    
         1377  +    return true;
         1378  +}
         1379  +
         1380  +int JSON_parser_get_last_error(JSON_parser jc)
         1381  +{
         1382  +    return jc->error;
         1383  +}
         1384  +
         1385  +
         1386  +void init_JSON_config(JSON_config* config)
         1387  +{
         1388  +    if (config) {
         1389  +        memset(config, 0, sizeof(*config));
         1390  +        
         1391  +        config->depth = JSON_PARSER_STACK_SIZE - 1;
         1392  +        config->malloc = malloc;
         1393  +        config->free = free;
         1394  +    }
         1395  +}
         1396  +/* end file parser/JSON_parser.c */
         1397  +/* begin file ./cson.c */
         1398  +#include <assert.h>
         1399  +#include <stdlib.h> /* malloc()/free() */
         1400  +#include <string.h>
         1401  +
         1402  +#ifdef _MSC_VER
         1403  +#   if _MSC_VER >= 1400 /* Visual Studio 2005 and up */
         1404  +#     pragma warning( push )
         1405  +#     pragma warning(disable:4996) /* unsecure sscanf (but snscanf() isn't in c89) */
         1406  +#     pragma warning(disable:4244) /* complaining about data loss due
         1407  +                                      to integer precision in the
         1408  +                                      sqlite3 utf decoding routines */
         1409  +#   endif
         1410  +#endif
         1411  +
         1412  +#if 1
         1413  +#include <stdio.h>
         1414  +#define MARKER if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf
         1415  +#else
         1416  +static void noop_printf(char const * fmt, ...) {}
         1417  +#define MARKER if(0) printf
         1418  +#endif
         1419  +
         1420  +#if defined(__cplusplus)
         1421  +extern "C" {
         1422  +#endif
         1423  +
         1424  +
         1425  +    
         1426  +/**
         1427  +   Type IDs corresponding to JavaScript/JSON types.
         1428  +*/
         1429  +enum cson_type_id {
         1430  +  /**
         1431  +    The special "null" value constant.
         1432  +
         1433  +    Its value must be 0 for internal reasons.
         1434  + */
         1435  + CSON_TYPE_UNDEF = 0,
         1436  + /**
         1437  +    The special "null" value constant.
         1438  + */
         1439  + CSON_TYPE_NULL = 1,
         1440  + /**
         1441  +    The bool value type.
         1442  + */
         1443  + CSON_TYPE_BOOL = 2,
         1444  + /**
         1445  +    The integer value type, represented in this library
         1446  +    by cson_int_t.
         1447  + */
         1448  + CSON_TYPE_INTEGER = 3,
         1449  + /**
         1450  +    The double value type, represented in this library
         1451  +    by cson_double_t.
         1452  + */
         1453  + CSON_TYPE_DOUBLE = 4,
         1454  + /** The immutable string type. This library stores strings
         1455  +    as immutable UTF8.
         1456  + */
         1457  + CSON_TYPE_STRING = 5,
         1458  + /** The "Array" type. */
         1459  + CSON_TYPE_ARRAY = 6,
         1460  + /** The "Object" type. */
         1461  + CSON_TYPE_OBJECT = 7
         1462  +};
         1463  +typedef enum cson_type_id cson_type_id;
         1464  +
         1465  +/**
         1466  +   This type holds the "vtbl" for type-specific operations when
         1467  +   working with cson_value objects.
         1468  +
         1469  +   All cson_values of a given logical type share a pointer to a single
         1470  +   library-internal instance of this class.
         1471  +*/
         1472  +struct cson_value_api
         1473  +{
         1474  +    /**
         1475  +       The logical JavaScript/JSON type associated with
         1476  +       this object.
         1477  +     */
         1478  +    const cson_type_id typeID;
         1479  +    /**
         1480  +       Must free any memory associated with self,
         1481  +       but not free self. If self is NULL then
         1482  +       this function must do nothing.
         1483  +    */
         1484  +    void (*cleanup)( cson_value * self );
         1485  +    /**
         1486  +       POSSIBLE TODOs:
         1487  +
         1488  +       // Deep copy.
         1489  +       int (*clone)( cson_value const * self, cson_value ** tgt );
         1490  +
         1491  +       // Using JS semantics for true/value
         1492  +       char (*bool_value)( cson_value const * self );
         1493  +
         1494  +       // memcmp() return value semantics
         1495  +       int (*compare)( cson_value const * self, cson_value const * other );
         1496  +     */
         1497  +};
         1498  +
         1499  +typedef struct cson_value_api cson_value_api;
         1500  +
         1501  +/**
         1502  +   Empty-initialized cson_value_api object.
         1503  +*/
         1504  +#define cson_value_api_empty_m {           \
         1505  +        CSON_TYPE_UNDEF/*typeID*/,         \
         1506  +        NULL/*cleanup*/\
         1507  +      }
         1508  +/**
         1509  +   Empty-initialized cson_value_api object.
         1510  +*/
         1511  +static const cson_value_api cson_value_api_empty = cson_value_api_empty_m;
         1512  +
         1513  +
         1514  +typedef unsigned int cson_counter_t;
         1515  +struct cson_value
         1516  +{
         1517  +    /** The "vtbl" of type-specific operations. All instances
         1518  +        of a given logical value type share a single api instance.
         1519  +
         1520  +        Results are undefined if this value is NULL.
         1521  +    */
         1522  +    cson_value_api const * api;
         1523  +
         1524  +    /** The raw value. Its interpretation depends on the value of the
         1525  +        api member. Some value types require dynamically-allocated
         1526  +        memory, so one must always call cson_value_free() to destroy a
         1527  +        value when it is no longer needed. For stack-allocated values
         1528  +        (which client could SHOULD NOT USE unless they are intimately
         1529  +        familiar with the memory management rules and don't mind an
         1530  +        occasional leak or crash), use cson_value_clean() instead of
         1531  +        cson_value_free().
         1532  +    */
         1533  +    void * value;
         1534  +
         1535  +    /**
         1536  +       We use this to allow us to store cson_value instances in
         1537  +       multiple containers or multiple times within a single container
         1538  +       (provided no cycles are introduced).
         1539  +
         1540  +       Notes about the rc implementation:
         1541  +
         1542  +       - The refcount is for the cson_value instance itself, not its
         1543  +       value pointer.
         1544  +
         1545  +       - Instances start out with a refcount of 0 (not 1). Adding them
         1546  +       to a container will increase the refcount. Cleaning up the container
         1547  +       will decrement the count.
         1548  +
         1549  +       - cson_value_free() decrements the refcount (if it is not already
         1550  +       0) and cleans/frees the value only when the refcount is 0.
         1551  +
         1552  +       - Some places in the internals add an "extra" reference to
         1553  +       objects to avoid a premature deletion. Don't try this at home.
         1554  +    */
         1555  +    cson_counter_t refcount;
         1556  +};
         1557  +
         1558  +
         1559  +/**
         1560  +   Empty-initialized cson_value object.
         1561  +*/
         1562  +#define cson_value_empty_m { &cson_value_api_empty/*api*/, NULL/*value*/, 0/*refcount*/ }
         1563  +/**
         1564  +   Empty-initialized cson_value object.
         1565  +*/
         1566  +extern const cson_value cson_value_empty;
         1567  +
         1568  +const cson_value cson_value_empty = cson_value_empty_m;
         1569  +const cson_parse_opt cson_parse_opt_empty = cson_parse_opt_empty_m;
         1570  +const cson_output_opt cson_output_opt_empty = cson_output_opt_empty_m;
         1571  +const cson_object_iterator cson_object_iterator_empty = cson_object_iterator_empty_m;
         1572  +const cson_buffer cson_buffer_empty = cson_buffer_empty_m;
         1573  +const cson_parse_info cson_parse_info_empty = cson_parse_info_empty_m;
         1574  +
         1575  +static void cson_value_destroy_zero_it( cson_value * self );
         1576  +static void cson_value_destroy_object( cson_value * self );
         1577  +/**
         1578  +   If self is-a array then this function destroys its contents,
         1579  +   else this function does nothing.
         1580  +*/
         1581  +static void cson_value_destroy_array( cson_value * self );
         1582  +
         1583  +static const cson_value_api cson_value_api_null = { CSON_TYPE_NULL, cson_value_destroy_zero_it };
         1584  +static const cson_value_api cson_value_api_undef = { CSON_TYPE_UNDEF, cson_value_destroy_zero_it };
         1585  +static const cson_value_api cson_value_api_bool = { CSON_TYPE_BOOL, cson_value_destroy_zero_it };
         1586  +static const cson_value_api cson_value_api_integer = { CSON_TYPE_INTEGER, cson_value_destroy_zero_it };
         1587  +static const cson_value_api cson_value_api_double = { CSON_TYPE_DOUBLE, cson_value_destroy_zero_it };
         1588  +static const cson_value_api cson_value_api_string = { CSON_TYPE_STRING, cson_value_destroy_zero_it };
         1589  +static const cson_value_api cson_value_api_array = { CSON_TYPE_ARRAY, cson_value_destroy_array };
         1590  +static const cson_value_api cson_value_api_object = { CSON_TYPE_OBJECT, cson_value_destroy_object };
         1591  +
         1592  +static const cson_value cson_value_undef = { &cson_value_api_undef, NULL, 0 };
         1593  +static const cson_value cson_value_null_empty = { &cson_value_api_null, NULL, 0 };
         1594  +static const cson_value cson_value_bool_empty = { &cson_value_api_bool, NULL, 0 };
         1595  +static const cson_value cson_value_integer_empty = { &cson_value_api_integer, NULL, 0 };
         1596  +static const cson_value cson_value_double_empty = { &cson_value_api_double, NULL, 0 };
         1597  +static const cson_value cson_value_string_empty = { &cson_value_api_string, NULL, 0 };
         1598  +static const cson_value cson_value_array_empty = { &cson_value_api_array, NULL, 0 };
         1599  +static const cson_value cson_value_object_empty = { &cson_value_api_object, NULL, 0 };
         1600  +
         1601  +struct cson_string
         1602  +{
         1603  +    unsigned int length;
         1604  +};
         1605  +#define cson_string_empty_m {0/*length*/}
         1606  +static const cson_string cson_string_empty = cson_string_empty_m;
         1607  +
         1608  +
         1609  +
         1610  +#define CSON_CAST(T,V) ((T*)((V)->value))
         1611  +#define CSON_VCAST(V) ((cson_value *)(((unsigned char *)(V))-sizeof(cson_value)))
         1612  +#define CSON_INT(V) ((cson_int_t*)(V)->value)
         1613  +#define CSON_DBL(V) CSON_CAST(cson_double_t,(V))
         1614  +#define CSON_STR(V) CSON_CAST(cson_string,(V))
         1615  +#define CSON_OBJ(V) CSON_CAST(cson_object,(V))
         1616  +#define CSON_ARRAY(V) CSON_CAST(cson_array,(V))
         1617  +
         1618  +/**
         1619  + 
         1620  + Holds special shared "constant" (though they are non-const)
         1621  + values.
         1622  + 
         1623  +*/
         1624  +static struct CSON_EMPTY_HOLDER_
         1625  +{
         1626  +    char trueValue;
         1627  +    cson_string stringValue;
         1628  +} CSON_EMPTY_HOLDER = {
         1629  +    1/*trueValue*/,
         1630  +    cson_string_empty_m
         1631  +};
         1632  +
         1633  +/**
         1634  +    Indexes into the CSON_SPECIAL_VALUES array.
         1635  +    
         1636  +    If this enum changes in any way,
         1637  +    makes damned sure that CSON_SPECIAL_VALUES is updated
         1638  +    to match!!!
         1639  +*/
         1640  +enum CSON_INTERNAL_VALUES {
         1641  +    
         1642  +    CSON_VAL_UNDEF = 0,
         1643  +    CSON_VAL_NULL = 1,
         1644  +    CSON_VAL_TRUE = 2,
         1645  +    CSON_VAL_FALSE = 3,
         1646  +    CSON_VAL_INT_0 = 4,
         1647  +    CSON_VAL_DBL_0 = 5,
         1648  +    CSON_VAL_STR_EMPTY = 6,
         1649  +    CSON_INTERNAL_VALUES_LENGTH
         1650  +};
         1651  +
         1652  +/**
         1653  +  Some "special" shared cson_value instances.
         1654  +
         1655  +  These values MUST be initialized in the order specified
         1656  +  by the CSON_INTERNAL_VALUES enum.
         1657  +   
         1658  +  Note that they are not const because they are used as
         1659  +  shared-allocation objects in non-const contexts. However, the
         1660  +  public API provides no way to modifying them, and clients who
         1661  +  modify values directly are subject to The Wrath of Undefined
         1662  +  Behaviour.
         1663  +*/
         1664  +static cson_value CSON_SPECIAL_VALUES[] = {
         1665  +{ &cson_value_api_undef, NULL, 0 }, /* UNDEF */
         1666  +{ &cson_value_api_null, NULL, 0 }, /* NULL */
         1667  +{ &cson_value_api_bool, &CSON_EMPTY_HOLDER.trueValue, 0 }, /* TRUE */
         1668  +{ &cson_value_api_bool, NULL, 0 }, /* FALSE */
         1669  +{ &cson_value_api_integer, NULL, 0 }, /* INT_0 */
         1670  +{ &cson_value_api_double, NULL, 0 }, /* DBL_0 */
         1671  +{ &cson_value_api_string, &CSON_EMPTY_HOLDER.stringValue, 0 }, /* STR_EMPTY */
         1672  +{ 0, NULL, 0 }
         1673  +};
         1674  +
         1675  +
         1676  +/**
         1677  +    Returns non-0 (true) if m is one of our special
         1678  +    "built-in" values, e.g. from CSON_SPECIAL_VALUES and some
         1679  +    "empty" values.
         1680  +     
         1681  +    If this returns true, m MUST NOT be free()d!
         1682  + */
         1683  +static char cson_value_is_builtin( void const * m )
         1684  +{
         1685  +    if((m >= (void const *)&CSON_EMPTY_HOLDER)
         1686  +        && ( m < (void const *)(&CSON_EMPTY_HOLDER+1)))
         1687  +        return 1;
         1688  +    else return
         1689  +        ((m > (void const *)&CSON_SPECIAL_VALUES[0])
         1690  +        && ( m < (void const *)&CSON_SPECIAL_VALUES[CSON_INTERNAL_VALUES_LENGTH]) )
         1691  +        ? 1
         1692  +        : 0;
         1693  +}
         1694  +
         1695  +char const * cson_rc_string(int rc)
         1696  +{
         1697  +    if(0 == rc) return "OK";
         1698  +#define CHECK(N) else if(cson_rc.N == rc ) return #N
         1699  +    CHECK(OK);
         1700  +    CHECK(ArgError);
         1701  +    CHECK(RangeError);
         1702  +    CHECK(TypeError);
         1703  +    CHECK(IOError);
         1704  +    CHECK(AllocError);
         1705  +    CHECK(NYIError);
         1706  +    CHECK(InternalError);
         1707  +    CHECK(UnsupportedError);
         1708  +    CHECK(NotFoundError);
         1709  +    CHECK(UnknownError);
         1710  +    CHECK(Parse_INVALID_CHAR);
         1711  +    CHECK(Parse_INVALID_KEYWORD);
         1712  +    CHECK(Parse_INVALID_ESCAPE_SEQUENCE);
         1713  +    CHECK(Parse_INVALID_UNICODE_SEQUENCE);
         1714  +    CHECK(Parse_INVALID_NUMBER);
         1715  +    CHECK(Parse_NESTING_DEPTH_REACHED);
         1716  +    CHECK(Parse_UNBALANCED_COLLECTION);
         1717  +    CHECK(Parse_EXPECTED_KEY);
         1718  +    CHECK(Parse_EXPECTED_COLON);
         1719  +    else return "UnknownError";
         1720  +#undef CHECK
         1721  +}
         1722  +
         1723  +/**
         1724  +   If CSON_LOG_ALLOC is true then the cson_malloc/realloc/free() routines
         1725  +   will log a message to stderr.
         1726  +*/
         1727  +#define CSON_LOG_ALLOC 0
         1728  +
         1729  +
         1730  +/**
         1731  +   CSON_FOSSIL_MODE is only for use in the Fossil
         1732  +   source tree, so that we can plug in to its allocators.
         1733  +   We can't do this by, e.g., defining macros for the
         1734  +   malloc/free funcs because fossil's lack of header files
         1735  +   means we would have to #include "main.c" here to
         1736  +   get the declarations.
         1737  + */
         1738  +#if defined(CSON_FOSSIL_MODE)
         1739  +void *fossil_malloc(size_t n);
         1740  +void fossil_free(void *p);
         1741  +void *fossil_realloc(void *p, size_t n);
         1742  +#  define CSON_MALLOC_IMPL fossil_malloc
         1743  +#  define CSON_FREE_IMPL fossil_free
         1744  +#  define CSON_REALLOC_IMPL fossil_realloc
         1745  +#endif
         1746  +
         1747  +#if !defined CSON_MALLOC_IMPL
         1748  +#  define CSON_MALLOC_IMPL malloc
         1749  +#endif
         1750  +#if !defined CSON_FREE_IMPL
         1751  +#  define CSON_FREE_IMPL free
         1752  +#endif
         1753  +#if !defined CSON_REALLOC_IMPL
         1754  +#  define CSON_REALLOC_IMPL realloc
         1755  +#endif
         1756  +
         1757  +/**
         1758  +   A test/debug macro for simulating an OOM after the given number of
         1759  +   bytes have been allocated.
         1760  +*/
         1761  +#define CSON_SIMULATE_OOM 0
         1762  +#if CSON_SIMULATE_OOM
         1763  +static unsigned int cson_totalAlloced = 0;
         1764  +#endif
         1765  +
         1766  +/** Simple proxy for malloc(). descr is a description of the allocation. */
         1767  +static void * cson_malloc( size_t n, char const * descr )
         1768  +{
         1769  +#if CSON_LOG_ALLOC
         1770  +    fprintf(stderr, "Allocating %u bytes [%s].\n", (unsigned int)n, descr);
         1771  +#endif
         1772  +#if CSON_SIMULATE_OOM
         1773  +    cson_totalAlloced += n;
         1774  +    if( cson_totalAlloced > CSON_SIMULATE_OOM )
         1775  +    {
         1776  +        return NULL;
         1777  +    }
         1778  +#endif
         1779  +    return CSON_MALLOC_IMPL(n);
         1780  +}
         1781  +
         1782  +/** Simple proxy for free(). descr is a description of the memory being freed. */
         1783  +static void cson_free( void * p, char const * descr )
         1784  +{
         1785  +#if CSON_LOG_ALLOC
         1786  +    fprintf(stderr, "Freeing @%p [%s].\n", p, descr);
         1787  +#endif
         1788  +    if( !cson_value_is_builtin(p) )
         1789  +    {
         1790  +        CSON_FREE_IMPL( p );
         1791  +    }
         1792  +}
         1793  +/** Simple proxy for realloc(). descr is a description of the (re)allocation. */
         1794  +static void * cson_realloc( void * hint, size_t n, char const * descr )
         1795  +{
         1796  +#if CSON_LOG_ALLOC
         1797  +    fprintf(stderr, "%sllocating %u bytes [%s].\n",
         1798  +            hint ? "Rea" : "A",
         1799  +            (unsigned int)n, descr);
         1800  +#endif
         1801  +#if CSON_SIMULATE_OOM
         1802  +    cson_totalAlloced += n;
         1803  +    if( cson_totalAlloced > CSON_SIMULATE_OOM )
         1804  +    {
         1805  +        return NULL;
         1806  +    }
         1807  +#endif
         1808  +    if( 0==n )
         1809  +    {
         1810  +         cson_free(hint, descr);
         1811  +         return NULL;
         1812  +    }
         1813  +    else
         1814  +    {
         1815  +        return CSON_REALLOC_IMPL( hint, n );
         1816  +    }
         1817  +}
         1818  +
         1819  +
         1820  +#undef CSON_LOG_ALLOC
         1821  +#undef CSON_SIMULATE_OOM
         1822  +
         1823  +
         1824  +
         1825  +/**
         1826  +   CLIENTS CODE SHOULD NEVER USE THIS because it opens up doors to
         1827  +   memory leaks if it is not used in very controlled circumstances.
         1828  +   Users must be very aware of how the underlying memory management
         1829  +   works.
         1830  +
         1831  +   Frees any resources owned by val, but does not free val itself
         1832  +   (which may be stack-allocated). If !val or val->api or
         1833  +   val->api->cleanup are NULL then this is a no-op.
         1834  +
         1835  +   If v is a container type (object or array) its children are also
         1836  +   cleaned up (BUT NOT FREED), recursively.
         1837  +
         1838  +   After calling this, val will have the special "undefined" type.
         1839  +*/
         1840  +static void cson_value_clean( cson_value * val );
         1841  +
         1842  +/**
         1843  +   Increments cv's reference count by 1.  As a special case, values
         1844  +   for which cson_value_is_builtin() returns true are not
         1845  +   modified. assert()s if (NULL==cv).
         1846  +*/
         1847  +static void cson_refcount_incr( cson_value * cv )
         1848  +{
         1849  +    assert( NULL != cv );
         1850  +    if( cson_value_is_builtin( cv ) )
         1851  +    { /* do nothing: we do not want to modify the shared
         1852  +         instances.
         1853  +      */
         1854  +        return;
         1855  +    }
         1856  +    else
         1857  +    {
         1858  +        ++cv->refcount;
         1859  +    }
         1860  +}
         1861  +
         1862  +#if 0
         1863  +int cson_value_refcount_set( cson_value * cv, unsigned short rc )
         1864  +{
         1865  +    if( NULL == cv ) return cson_rc.ArgError;
         1866  +    else
         1867  +    {
         1868  +        cv->refcount = rc;
         1869  +        return 0;
         1870  +    }
         1871  +}
         1872  +#endif
         1873  +
         1874  +int cson_value_add_reference( cson_value * cv )
         1875  +{
         1876  +    if( NULL == cv ) return cson_rc.ArgError;
         1877  +    else if( (cv->refcount+1) < cv->refcount )
         1878  +    {
         1879  +        return cson_rc.RangeError;
         1880  +    }
         1881  +    else
         1882  +    {
         1883  +        cson_refcount_incr( cv );
         1884  +        return 0;
         1885  +    }
         1886  +}
         1887  +
         1888  +/**
         1889  +   If cv is NULL or cson_value_is_builtin(cv) returns true then this
         1890  +   function does nothing and returns 0, otherwise...  If
         1891  +   cv->refcount is 0 or 1 then cson_value_clean(cv) is called, cv is
         1892  +   freed, and 0 is returned. If cv->refcount is any other value then
         1893  +   it is decremented and the new value is returned.
         1894  +*/
         1895  +static cson_counter_t cson_refcount_decr( cson_value * cv )
         1896  +{
         1897  +    if( (NULL == cv) || cson_value_is_builtin(cv) ) return 0;
         1898  +    else if( (0 == cv->refcount) || (0 == --cv->refcount) )
         1899  +    {
         1900  +        cson_value_clean(cv);
         1901  +        cson_free(cv,"cson_value::refcount=0");
         1902  +        return 0;
         1903  +    }
         1904  +    else return cv->refcount;
         1905  +}
         1906  +
         1907  +unsigned int cson_string_length_bytes( cson_string const * str )
         1908  +{
         1909  +    return str ? str->length : 0;
         1910  +}
         1911  +
         1912  +
         1913  +/**
         1914  +   Fetches v's string value as a non-const string.
         1915  +
         1916  +   cson_strings are supposed to be immutable, but this form provides
         1917  +   access to the immutable bits, which are v->length bytes long. A
         1918  +   length-0 string is returned as NULL from here, as opposed to
         1919  +   "". (This is a side-effect of the string allocation mechanism.)
         1920  +   Returns NULL if !v.
         1921  +*/
         1922  +static char * cson_string_str(cson_string *v)
         1923  +{
         1924  +    /*
         1925  +      See http://groups.google.com/group/comp.lang.c.moderated/browse_thread/thread/2e0c0df5e8a0cd6a
         1926  +    */
         1927  +#if 1
         1928  +    if( !v || (&CSON_EMPTY_HOLDER.stringValue == v) ) return NULL;
         1929  +    else return (char *)((unsigned char *)( v+1 ));
         1930  +#else
         1931  +    static char empty[2] = {0,0};
         1932  +    return ( NULL == v )
         1933  +        ? NULL
         1934  +        : (v->length
         1935  +           ? (char *) (((unsigned char *)v) + sizeof(cson_string))
         1936  +           : empty)
         1937  +        ;
         1938  +#endif
         1939  +}
         1940  +
         1941  +/**
         1942  +   Fetches v's string value as a const string.
         1943  +*/
         1944  +char const * cson_string_cstr(cson_string const *v)
         1945  +{
         1946  +    /*
         1947  +      See http://groups.google.com/group/comp.lang.c.moderated/browse_thread/thread/2e0c0df5e8a0cd6a
         1948  +    */
         1949  +#if 1
         1950  +    if( ! v ) return NULL;
         1951  +    else if( v == &CSON_EMPTY_HOLDER.stringValue ) return "";
         1952  +    else return (char *)((unsigned char *)(v+1));
         1953  +#else
         1954  +    return (NULL == v)
         1955  +        ? NULL
         1956  +        : (v->length
         1957  +           ? (char const *) ((unsigned char const *)(v+1))
         1958  +           : "");
         1959  +#endif
         1960  +}
         1961  +
         1962  +
         1963  +#if 0
         1964  +/**
         1965  +   Just like strndup(3), in that neither are C89/C99-standard and both
         1966  +   are documented in detail in strndup(3).
         1967  +*/
         1968  +static char * cson_strdup( char const * src, size_t n )
         1969  +{
         1970  +    char * rc = (char *)cson_malloc(n+1, "cson_strdup");
         1971  +    if( ! rc ) return NULL;
         1972  +    memset( rc, 0, n+1 );
         1973  +    rc[n] = 0;
         1974  +    return strncpy( rc, src, n );
         1975  +}
         1976  +#endif
         1977  +
         1978  +int cson_string_cmp_cstr_n( cson_string const * str, char const * other, unsigned int otherLen )
         1979  +{
         1980  +    if( ! other && !str ) return 0;
         1981  +    else if( other && !str ) return 1;
         1982  +    else if( str && !other ) return -1;
         1983  +    else if( !otherLen ) return  str->length ? 1 : 0;
         1984  +    else if( !str->length ) return otherLen ? -1 : 0;
         1985  +    else
         1986  +    {
         1987  +        unsigned const int max = (otherLen > str->length) ? otherLen : str->length;
         1988  +        int const rc = strncmp( cson_string_cstr(str), other, max );
         1989  +        return ( (0 == rc) && (otherLen != str->length) )
         1990  +            ? (str->length < otherLen) ? -1 : 1
         1991  +            : rc;
         1992  +    }
         1993  +}
         1994  +
         1995  +int cson_string_cmp_cstr( cson_string const * lhs, char const * rhs )
         1996  +{
         1997  +    return cson_string_cmp_cstr_n( lhs, rhs, (rhs&&*rhs) ? strlen(rhs) : 0 );
         1998  +}
         1999  +int cson_string_cmp( cson_string const * lhs, cson_string const * rhs )
         2000  +{
         2001  +    return cson_string_cmp_cstr_n( lhs, cson_string_cstr(rhs), rhs ? rhs->length : 0 );
         2002  +}
         2003  +
         2004  +
         2005  +/**
         2006  +   If self is not NULL, *self is overwritten to have the undefined
         2007  +   type. self is not cleaned up or freed.
         2008  +*/
         2009  +void cson_value_destroy_zero_it( cson_value * self )
         2010  +{
         2011  +    if( self )
         2012  +    {
         2013  +        *self = cson_value_undef;
         2014  +    }
         2015  +}
         2016  +
         2017  +/**
         2018  +   A key/value pair collection.
         2019  +
         2020  +   Each of these objects owns its key/value pointers, and they
         2021  +   are cleaned up by cson_kvp_clean().
         2022  +*/
         2023  +struct cson_kvp
         2024  +{
         2025  +    cson_value * key;
         2026  +    cson_value * value;
         2027  +};
         2028  +#define cson_kvp_empty_m {NULL,NULL}
         2029  +static const cson_kvp cson_kvp_empty = cson_kvp_empty_m;
         2030  +
         2031  +/** @def CSON_OBJECT_PROPS_SORT
         2032  +
         2033  +   If CSON_OBJECT_PROPS_SORT is set to a true value then
         2034  +   qsort() and bsearch() are used to sort (upon insertion)
         2035  +   and search cson_object::kvp property lists. This costs us
         2036  +   a re-sort on each insertion but searching is O(log n)
         2037  +   average/worst case (and O(1) best-case).
         2038  +
         2039  +   i'm not yet convinced that the overhead of the qsort() justifies
         2040  +   the potentially decreased search times - it has not been
         2041  +   measured. Object property lists tend to be relatively short in
         2042  +   JSON, and a linear search which uses the cson_string::length
         2043  +   property as a quick check is quite fast when one compares it with
         2044  +   the sort overhead required by the bsearch() approach.
         2045  +*/
         2046  +#define CSON_OBJECT_PROPS_SORT 0
         2047  +
         2048  +/** @def CSON_OBJECT_PROPS_SORT_USE_LENGTH
         2049  +
         2050  +    Don't use this - i'm not sure that it works how i'd like.
         2051  +
         2052  +    If CSON_OBJECT_PROPS_SORT_USE_LENGTH is true then
         2053  +    we use string lengths as quick checks when sorting
         2054  +    property keys. This leads to a non-intuitive sorting
         2055  +    order but "should" be faster.
         2056  +
         2057  +    This is ignored if CSON_OBJECT_PROPS_SORT is false.
         2058  +
         2059  +*/
         2060  +#define CSON_OBJECT_PROPS_SORT_USE_LENGTH 0
         2061  +
         2062  +#if CSON_OBJECT_PROPS_SORT
         2063  +
         2064  +/**
         2065  +   cson_kvp comparator for use with qsort(). ALMOST compares with
         2066  +   strcmp() semantics, but it uses the strings' lengths as a quicker
         2067  +   approach. This might give non-intuitive results, but it's faster.
         2068  + */
         2069  +static int cson_kvp_cmp( void const * lhs, void const * rhs )
         2070  +{
         2071  +    cson_kvp const * lk = *((cson_kvp const * const*)lhs);
         2072  +    cson_kvp const * rk = *((cson_kvp const * const*)rhs);
         2073  +    cson_string const * l = cson_string_value(lk->key);
         2074  +    cson_string const * r = cson_string_value(rk->key);
         2075  +#if CSON_OBJECT_PROPS_SORT_USE_LENGTH
         2076  +    if( l->length < r->length ) return -1;
         2077  +    else if( l->length > r->length ) return 1;
         2078  +    else return strcmp( cson_string_cstr( l ), cson_string_cstr( r ) );
         2079  +#else
         2080  +    return strcmp( cson_string_cstr( l ),
         2081  +                   cson_string_cstr( r ) );
         2082  +#endif /*CSON_OBJECT_PROPS_SORT_USE_LENGTH*/
         2083  +}
         2084  +#endif /*CSON_OBJECT_PROPS_SORT*/
         2085  +
         2086  +
         2087  +#if CSON_OBJECT_PROPS_SORT
         2088  +#error "Need to rework this for cson_string-to-cson_value refactoring"
         2089  +/**
         2090  +   A bsearch() comparison function which requires that lhs be a (char
         2091  +   const *) and rhs be-a (cson_kvp const * const *). It compares lhs
         2092  +   to rhs->key's value, using strcmp() semantics.
         2093  + */
         2094  +static int cson_kvp_cmp_vs_cstr( void const * lhs, void const * rhs )
         2095  +{
         2096  +    char const * lk = (char const *)lhs;
         2097  +    cson_kvp const * rk =
         2098  +        *((cson_kvp const * const*)rhs)
         2099  +        ;
         2100  +#if CSON_OBJECT_PROPS_SORT_USE_LENGTH
         2101  +    unsigned int llen = strlen(lk);
         2102  +    if( llen < rk->key->length ) return -1;
         2103  +    else if( llen > rk->key->length ) return 1;
         2104  +    else return strcmp( lk, cson_string_cstr( rk->key ) );
         2105  +#else
         2106  +    return strcmp( lk, cson_string_cstr( rk->key ) );
         2107  +#endif /*CSON_OBJECT_PROPS_SORT_USE_LENGTH*/
         2108  +}
         2109  +#endif /*CSON_OBJECT_PROPS_SORT*/
         2110  +
         2111  +
         2112  +struct cson_kvp_list
         2113  +{
         2114  +    cson_kvp ** list;
         2115  +    unsigned int count;
         2116  +    unsigned int alloced;
         2117  +};
         2118  +typedef struct cson_kvp_list cson_kvp_list;
         2119  +#define cson_kvp_list_empty_m {NULL/*list*/,0/*count*/,0/*alloced*/}
         2120  +static const cson_kvp_list cson_kvp_list_empty = cson_kvp_list_empty_m;
         2121  +
         2122  +struct cson_object
         2123  +{
         2124  +    cson_kvp_list kvp;
         2125  +};
         2126  +/*typedef struct cson_object cson_object;*/
         2127  +#define cson_object_empty_m { cson_kvp_list_empty_m/*kvp*/ }
         2128  +static const cson_object cson_object_empty = cson_object_empty_m;
         2129  +
         2130  +struct cson_value_list
         2131  +{
         2132  +    cson_value ** list;
         2133  +    unsigned int count;
         2134  +    unsigned int alloced;
         2135  +};
         2136  +typedef struct cson_value_list cson_value_list;
         2137  +#define cson_value_list_empty_m {NULL/*list*/,0/*count*/,0/*alloced*/}
         2138  +static const cson_value_list cson_value_list_empty = cson_value_list_empty_m;
         2139  +
         2140  +struct cson_array
         2141  +{
         2142  +    cson_value_list list;
         2143  +};
         2144  +/*typedef struct cson_array cson_array;*/
         2145  +#define cson_array_empty_m { cson_value_list_empty_m/*list*/ }
         2146  +static const cson_array cson_array_empty = cson_array_empty_m;
         2147  +
         2148  +
         2149  +struct cson_parser
         2150  +{
         2151  +    JSON_parser p;
         2152  +    cson_value * root;
         2153  +    cson_value * node;
         2154  +    cson_array stack;
         2155  +    cson_string * ckey;
         2156  +    int errNo;
         2157  +    unsigned int totalKeyCount;
         2158  +    unsigned int totalValueCount;
         2159  +};
         2160  +typedef struct cson_parser cson_parser;
         2161  +static const cson_parser cson_parser_empty = {
         2162  +NULL/*p*/,
         2163  +NULL/*root*/,
         2164  +NULL/*node*/,
         2165  +cson_array_empty_m/*stack*/,
         2166  +NULL/*ckey*/,
         2167  +0/*errNo*/,
         2168  +0/*totalKeyCount*/,
         2169  +0/*totalValueCount*/
         2170  +};
         2171  +
         2172  +#if 1
         2173  +/* The following funcs are declared in generated code (cson_lists.h),
         2174  +   but we need early access to their decls for the Amalgamation build.
         2175  +*/
         2176  +static unsigned int cson_value_list_reserve( cson_value_list * self, unsigned int n );
         2177  +static unsigned int cson_kvp_list_reserve( cson_kvp_list * self, unsigned int n );
         2178  +static int cson_kvp_list_append( cson_kvp_list * self, cson_kvp * cp );
         2179  +static void cson_kvp_list_clean( cson_kvp_list * self,
         2180  +                                 void (*cleaner)(cson_kvp * obj) );
         2181  +#if 0
         2182  +static int cson_value_list_append( cson_value_list * self, cson_value * cp );
         2183  +static void cson_value_list_clean( cson_value_list * self, void (*cleaner)(cson_value * obj));
         2184  +static int cson_kvp_list_visit( cson_kvp_list * self,
         2185  +                                int (*visitor)(cson_kvp * obj, void * visitorState ),
         2186  +                                void * visitorState );
         2187  +static int cson_value_list_visit( cson_value_list * self,
         2188  +                                  int (*visitor)(cson_value * obj, void * visitorState ),
         2189  +                                  void * visitorState );
         2190  +#endif
         2191  +#endif
         2192  +    
         2193  +#if 0
         2194  +#  define LIST_T cson_value_list
         2195  +#  define VALUE_T cson_value *
         2196  +#  define VALUE_T_IS_PTR 1
         2197  +#  define LIST_T cson_kvp_list
         2198  +#  define VALUE_T cson_kvp *
         2199  +#  define VALUE_T_IS_PTR 1
         2200  +#else
         2201  +#endif
         2202  +
         2203  +/**
         2204  +   Allocates a new value of the specified type ownership of it to the
         2205  +   caller. It must eventually be destroyed, by the caller or its
         2206  +   owning container, by passing it to cson_value_free() or transfering
         2207  +   ownership to a container.
         2208  +
         2209  +   extra is only valid for type CSON_TYPE_STRING, and must be the length
         2210  +   of the string to allocate + 1 byte (for the NUL).
         2211  +
         2212  +   The returned value->api member will be set appropriately and
         2213  +   val->value will be set to point to the memory allocated to hold the
         2214  +   native value type. Use the internal CSON_CAST() family of macros to
         2215  +   convert them.
         2216  +
         2217  +   Returns NULL on allocation error.
         2218  +
         2219  +   @see cson_value_new_array()
         2220  +   @see cson_value_new_object()
         2221  +   @see cson_value_new_string()
         2222  +   @see cson_value_new_integer()
         2223  +   @see cson_value_new_double()
         2224  +   @see cson_value_new_bool()
         2225  +   @see cson_value_free()
         2226  +*/
         2227  +static cson_value * cson_value_new(cson_type_id t, size_t extra);
         2228  +
         2229  +cson_value * cson_value_new(cson_type_id t, size_t extra)
         2230  +{
         2231  +    static const size_t vsz = sizeof(cson_value);
         2232  +    const size_t sz = vsz + extra;
         2233  +    size_t tx = 0;
         2234  +    cson_value def = cson_value_undef;
         2235  +    cson_value * v = NULL;
         2236  +    char const * reason = "cson_value_new";
         2237  +    switch(t)
         2238  +    {
         2239  +      case CSON_TYPE_ARRAY:
         2240  +          assert( 0 == extra );
         2241  +          def = cson_value_array_empty;
         2242  +          tx = sizeof(cson_array);
         2243  +          reason = "cson_value:array";
         2244  +          break;
         2245  +      case CSON_TYPE_DOUBLE:
         2246  +          assert( 0 == extra );
         2247  +          def = cson_value_double_empty;
         2248  +          tx = sizeof(cson_double_t);
         2249  +          reason = "cson_value:double";
         2250  +          break;
         2251  +      case CSON_TYPE_INTEGER:
         2252  +          assert( 0 == extra );
         2253  +          def = cson_value_integer_empty;
         2254  +          tx = sizeof(cson_int_t);
         2255  +          reason = "cson_value:int";
         2256  +          break;
         2257  +      case CSON_TYPE_STRING:
         2258  +          assert( 0 != extra );
         2259  +          def = cson_value_string_empty;
         2260  +          tx = sizeof(cson_string);
         2261  +          reason = "cson_value:string";
         2262  +          break;
         2263  +      case CSON_TYPE_OBJECT:
         2264  +          assert( 0 == extra );
         2265  +          def = cson_value_object_empty;
         2266  +          tx = sizeof(cson_object);
         2267  +          reason = "cson_value:object";
         2268  +          break;
         2269  +      default:
         2270  +          assert(0 && "Unhandled type in cson_value_new()!");
         2271  +          return NULL;
         2272  +    }
         2273  +    assert( def.api->typeID != CSON_TYPE_UNDEF );
         2274  +    v = (cson_value *)cson_malloc(sz+tx, reason);
         2275  +    if( v ) {
         2276  +        *v = def;
         2277  +        if(tx || extra){
         2278  +            memset(v+1, 0, tx + extra);
         2279  +            v->value = (void *)(v+1);
         2280  +        }
         2281  +    }
         2282  +    return v;
         2283  +}
         2284  +
         2285  +
         2286  +void cson_value_free(cson_value *v)
         2287  +{
         2288  +    cson_refcount_decr( v );
         2289  +}
         2290  +
         2291  +#if 0 /* we might actually want this later on. */
         2292  +/** Returns true if v is not NULL and has the given type ID. */
         2293  +static char cson_value_is_a( cson_value const * v, cson_type_id is )
         2294  +{
         2295  +    return (v && v->api && (v->api->typeID == is)) ? 1 : 0;
         2296  +}
         2297  +#endif
         2298  +
         2299  +#if 0
         2300  +cson_type_id cson_value_type_id( cson_value const * v )
         2301  +{
         2302  +    return (v && v->api) ? v->api->typeID : CSON_TYPE_UNDEF;
         2303  +}
         2304  +#endif
         2305  +
         2306  +char cson_value_is_undef( cson_value const * v )
         2307  +{
         2308  +    /**
         2309  +       This special-case impl is needed because the underlying
         2310  +       (generic) list operations do not know how to populate
         2311  +       new entries
         2312  +     */
         2313  +    return ( !v || !v->api || (v->api==&cson_value_api_undef))
         2314  +        ? 1 : 0;
         2315  +}
         2316  +#define ISA(T,TID) char cson_value_is_##T( cson_value const * v ) {       \
         2317  +        /*return (v && v->api) ? cson_value_is_a(v,CSON_TYPE_##TID) : 0;*/ \
         2318  +        return (v && (v->api == &cson_value_api_##T)) ? 1 : 0; \
         2319  +    } static const char bogusPlaceHolderForEmacsIndention##TID = CSON_TYPE_##TID
         2320  +ISA(null,NULL);
         2321  +ISA(bool,BOOL);
         2322  +ISA(integer,INTEGER);
         2323  +ISA(double,DOUBLE);
         2324  +ISA(string,STRING);
         2325  +ISA(array,ARRAY);
         2326  +ISA(object,OBJECT);
         2327  +#undef ISA
         2328  +char cson_value_is_number( cson_value const * v )
         2329  +{
         2330  +    return cson_value_is_integer(v) || cson_value_is_double(v);
         2331  +}
         2332  +
         2333  +
         2334  +void cson_value_clean( cson_value * val )
         2335  +{
         2336  +    if( val && val->api && val->api->cleanup )
         2337  +    {
         2338  +        if( ! cson_value_is_builtin( val ) )
         2339  +        {
         2340  +            cson_counter_t const rc = val->refcount;
         2341  +            val->api->cleanup(val);
         2342  +            *val = cson_value_undef;
         2343  +            val->refcount = rc;
         2344  +        }
         2345  +    }
         2346  +}
         2347  +
         2348  +static cson_value * cson_value_array_alloc()
         2349  +{
         2350  +    cson_value * v = cson_value_new(CSON_TYPE_ARRAY,0);
         2351  +    if( NULL != v )
         2352  +    {
         2353  +        cson_array * ar = CSON_ARRAY(v);
         2354  +        assert(NULL != ar);
         2355  +        *ar = cson_array_empty;
         2356  +    }
         2357  +    return v;
         2358  +}
         2359  +
         2360  +static cson_value * cson_value_object_alloc()
         2361  +{
         2362  +    cson_value * v = cson_value_new(CSON_TYPE_OBJECT,0);
         2363  +    if( NULL != v )
         2364  +    {
         2365  +        cson_object * obj = CSON_OBJ(v);
         2366  +        assert(NULL != obj);
         2367  +        *obj = cson_object_empty;
         2368  +    }
         2369  +    return v;
         2370  +}
         2371  +
         2372  +cson_value * cson_value_new_object()
         2373  +{
         2374  +    return cson_value_object_alloc();
         2375  +}
         2376  +
         2377  +cson_object * cson_new_object()
         2378  +{
         2379  +    
         2380  +    return cson_value_get_object( cson_value_new_object() );
         2381  +}
         2382  +
         2383  +cson_value * cson_value_new_array()
         2384  +{
         2385  +    return cson_value_array_alloc();
         2386  +}
         2387  +
         2388  +
         2389  +cson_array * cson_new_array()
         2390  +{
         2391  +    return cson_value_get_array( cson_value_new_array() );
         2392  +}
         2393  +
         2394  +/**
         2395  +   Frees kvp->key and kvp->value and sets them to NULL, but does not free
         2396  +   kvp. If !kvp then this is a no-op.
         2397  +*/
         2398  +static void cson_kvp_clean( cson_kvp * kvp )
         2399  +{
         2400  +    if( kvp )
         2401  +    {
         2402  +        if(kvp->key)
         2403  +        {
         2404  +            cson_value_free(kvp->key);
         2405  +            kvp->key = NULL;
         2406  +        }
         2407  +        if(kvp->value)
         2408  +        {
         2409  +            cson_value_free( kvp->value );
         2410  +            kvp->value = NULL;
         2411  +        }
         2412  +    }
         2413  +}
         2414  +
         2415  +cson_string * cson_kvp_key( cson_kvp const * kvp )
         2416  +{
         2417  +    return kvp ? cson_value_get_string(kvp->key) : NULL;
         2418  +}
         2419  +cson_value * cson_kvp_value( cson_kvp const * kvp )
         2420  +{
         2421  +    return kvp ? kvp->value : NULL;
         2422  +}
         2423  +
         2424  +
         2425  +/**
         2426  +   Calls cson_kvp_clean(kvp) and then frees kvp.
         2427  +*/
         2428  +static void cson_kvp_free( cson_kvp * kvp )
         2429  +{
         2430  +    if( kvp )
         2431  +    {
         2432  +        cson_kvp_clean(kvp);
         2433  +        cson_free(kvp,"cson_kvp");
         2434  +    }
         2435  +}
         2436  +
         2437  +
         2438  +/**
         2439  +   cson_value_api::destroy_value() impl for Object
         2440  +   values. Cleans up self-owned memory and overwrites
         2441  +   self to have the undefined value, but does not
         2442  +   free self.
         2443  +*/
         2444  +static void cson_value_destroy_object( cson_value * self )
         2445  +{
         2446  +    if(self && self->value) {
         2447  +        cson_object * obj = (cson_object *)self->value;
         2448  +        assert( self->value == obj );
         2449  +        cson_kvp_list_clean( &obj->kvp, cson_kvp_free );
         2450  +        *self = cson_value_undef;
         2451  +    }
         2452  +}
         2453  +
         2454  +/**
         2455  +   Cleans up the contents of ar->list, but does not free ar.
         2456  +
         2457  +   After calling this, ar will have a length of 0.
         2458  +
         2459  +   If properlyCleanValues is 1 then cson_value_free() is called on
         2460  +   each non-NULL item, otherwise the outer list is destroyed but the
         2461  +   individual items are assumed to be owned by someone else and are
         2462  +   not freed.
         2463  +*/
         2464  +static void cson_array_clean( cson_array * ar, char properlyCleanValues )
         2465  +{
         2466  +    if( ar )
         2467  +    {
         2468  +        unsigned int i = 0;
         2469  +        cson_value * val = NULL;
         2470  +        for( ; i < ar->list.count; ++i )
         2471  +        {
         2472  +            val = ar->list.list[i];
         2473  +            if(val)
         2474  +            {
         2475  +                ar->list.list[i] = NULL;
         2476  +                if( properlyCleanValues )
         2477  +                {
         2478  +                    cson_value_free( val );
         2479  +                }
         2480  +            }
         2481  +        }
         2482  +        cson_value_list_reserve(&ar->list,0);
         2483  +        ar->list = cson_value_list_empty
         2484  +            /* Pedantic note: reserve(0) already clears the list-specific
         2485  +               fields, but we do this just in case we ever add new fields
         2486  +               to cson_value_list which are not used in the reserve() impl.
         2487  +             */
         2488  +            ;
         2489  +    }
         2490  +}
         2491  +
         2492  +/**
         2493  +   cson_value_api::destroy_value() impl for Array
         2494  +   values. Cleans up self-owned memory and overwrites
         2495  +   self to have the undefined value, but does not
         2496  +   free self.
         2497  +*/
         2498  +static void cson_value_destroy_array( cson_value * self )
         2499  +{
         2500  +    cson_array * ar = cson_value_get_array(self);
         2501  +    if(ar) {
         2502  +        assert( self->value == ar );
         2503  +        cson_array_clean( ar, 1 );
         2504  +        *self = cson_value_undef;
         2505  +    }
         2506  +}
         2507  +
         2508  +int cson_buffer_fill_from( cson_buffer * dest, cson_data_source_f src, void * state )
         2509  +{
         2510  +    int rc;
         2511  +    enum { BufSize = 1024 * 4 };
         2512  +    char rbuf[BufSize];
         2513  +    size_t total = 0;
         2514  +    unsigned int rlen = 0;
         2515  +    if( ! dest || ! src ) return cson_rc.ArgError;
         2516  +    dest->used = 0;
         2517  +    while(1)
         2518  +    {
         2519  +        rlen = BufSize;
         2520  +        rc = src( state, rbuf, &rlen );
         2521  +        if( rc ) break;
         2522  +        total += rlen;
         2523  +        if( dest->capacity < (total+1) )
         2524  +        {
         2525  +            rc = cson_buffer_reserve( dest, total + 1);
         2526  +            if( 0 != rc ) break;
         2527  +        }
         2528  +        memcpy( dest->mem + dest->used, rbuf, rlen );
         2529  +        dest->used += rlen;
         2530  +        if( rlen < BufSize ) break;
         2531  +    }
         2532  +    if( !rc && dest->used )
         2533  +    {
         2534  +        assert( dest->used < dest->capacity );
         2535  +        dest->mem[dest->used] = 0;
         2536  +    }
         2537  +    return rc;
         2538  +}
         2539  +
         2540  +int cson_data_source_FILE( void * state, void * dest, unsigned int * n )
         2541  +{
         2542  +    FILE * f = (FILE*) state;
         2543  +    if( ! state || ! n || !dest ) return cson_rc.ArgError;
         2544  +    else if( !*n ) return cson_rc.RangeError;
         2545  +    *n = (unsigned int)fread( dest, 1, *n, f );
         2546  +    if( !*n )
         2547  +    {
         2548  +        return feof(f) ? 0 : cson_rc.IOError;
         2549  +    }
         2550  +    return 0;
         2551  +}
         2552  +
         2553  +int cson_parse_FILE( cson_value ** tgt, FILE * src,
         2554  +                     cson_parse_opt const * opt, cson_parse_info * err )
         2555  +{
         2556  +    return cson_parse( tgt, cson_data_source_FILE, src, opt, err );
         2557  +}
         2558  +
         2559  +
         2560  +int cson_value_fetch_bool( cson_value const * val, char * v )
         2561  +{
         2562  +    /**
         2563  +       FIXME: move the to-bool operation into cson_value_api, like we
         2564  +       do in the C++ API.
         2565  +     */
         2566  +    if( ! val || !val->api ) return cson_rc.ArgError;
         2567  +    else
         2568  +    {
         2569  +        int rc = 0;
         2570  +        char b = 0;
         2571  +        switch( val->api->typeID )
         2572  +        {
         2573  +          case CSON_TYPE_ARRAY:
         2574  +          case CSON_TYPE_OBJECT:
         2575  +              b = 1;
         2576  +              break;
         2577  +          case CSON_TYPE_STRING: {
         2578  +              char const * str = cson_string_cstr(cson_value_get_string(val));
         2579  +              b = (str && *str) ? 1 : 0;
         2580  +              break;
         2581  +          }
         2582  +          case CSON_TYPE_UNDEF:
         2583  +          case CSON_TYPE_NULL:
         2584  +              break;
         2585  +          case CSON_TYPE_BOOL:
         2586  +              b = (NULL==val->value) ? 0 : 1;
         2587  +              break;
         2588  +          case CSON_TYPE_INTEGER: {
         2589  +              cson_int_t i = 0;
         2590  +              cson_value_fetch_integer( val, &i );
         2591  +              b = i ? 1 : 0;
         2592  +              break;
         2593  +          }
         2594  +          case CSON_TYPE_DOUBLE: {
         2595  +              cson_double_t d = 0.0;
         2596  +              cson_value_fetch_double( val, &d );
         2597  +              b = (0.0==d) ? 0 : 1;
         2598  +              break;
         2599  +          }
         2600  +          default:
         2601  +              rc = cson_rc.TypeError;
         2602  +              break;
         2603  +        }
         2604  +        if( v ) *v = b;
         2605  +        return rc;
         2606  +    }
         2607  +}
         2608  +
         2609  +char cson_value_get_bool( cson_value const * val )
         2610  +{
         2611  +    char i = 0;
         2612  +    cson_value_fetch_bool( val, &i );
         2613  +    return i;
         2614  +}
         2615  +
         2616  +int cson_value_fetch_integer( cson_value const * val, cson_int_t * v )
         2617  +{
         2618  +    if( ! val || !val->api ) return cson_rc.ArgError;
         2619  +    else
         2620  +    {
         2621  +        cson_int_t i = 0;
         2622  +        int rc = 0;
         2623  +        switch(val->api->typeID)
         2624  +        {
         2625  +            case CSON_TYPE_UNDEF: 
         2626  +            case CSON_TYPE_NULL:
         2627  +              i = 0;
         2628  +              break;
         2629  +            case CSON_TYPE_BOOL: {
         2630  +              char b = 0;
         2631  +              cson_value_fetch_bool( val, &b );
         2632  +              i = b;
         2633  +              break;
         2634  +            }
         2635  +            case CSON_TYPE_INTEGER: {
         2636  +                cson_int_t const * x = CSON_INT(val);
         2637  +                if(!x)
         2638  +                {
         2639  +                    assert( val == &CSON_SPECIAL_VALUES[CSON_VAL_INT_0] );
         2640  +                }
         2641  +                i = x ? *x : 0;
         2642  +                break;
         2643  +            }
         2644  +            case CSON_TYPE_DOUBLE: {
         2645  +              cson_double_t d = 0.0;
         2646  +              cson_value_fetch_double( val, &d );
         2647  +              i = (cson_int_t)d;
         2648  +              break;
         2649  +            }
         2650  +            case CSON_TYPE_STRING:
         2651  +            case CSON_TYPE_ARRAY:
         2652  +            case CSON_TYPE_OBJECT:
         2653  +            default:
         2654  +              break;
         2655  +        }
         2656  +        if(v) *v = i;
         2657  +        return rc;
         2658  +    }
         2659  +}
         2660  +
         2661  +cson_int_t cson_value_get_integer( cson_value const * val )
         2662  +{
         2663  +    cson_int_t i = 0;
         2664  +    cson_value_fetch_integer( val, &i );
         2665  +    return i;
         2666  +}
         2667  +
         2668  +int cson_value_fetch_double( cson_value const * val, cson_double_t * v )
         2669  +{
         2670  +    if( ! val || !val->api ) return cson_rc.ArgError;
         2671  +    else
         2672  +    {
         2673  +        cson_double_t d = 0.0;
         2674  +        int rc = 0;
         2675  +        switch(val->api->typeID)
         2676  +        {
         2677  +          case CSON_TYPE_UNDEF: 
         2678  +          case CSON_TYPE_NULL:
         2679  +              d = 0;
         2680  +              break;
         2681  +          case CSON_TYPE_BOOL: {
         2682  +              char b = 0;
         2683  +              cson_value_fetch_bool( val, &b );
         2684  +              d = b ? 1.0 : 0.0;
         2685  +              break;
         2686  +          }
         2687  +          case CSON_TYPE_INTEGER: {
         2688  +              cson_int_t i = 0;
         2689  +              cson_value_fetch_integer( val, &i );
         2690  +              d = i;
         2691  +              break;
         2692  +          }
         2693  +          case CSON_TYPE_DOUBLE: {
         2694  +              cson_double_t const* dv = CSON_DBL(val);
         2695  +              d = dv ? *dv : 0.0;
         2696  +              break;
         2697  +          }
         2698  +          default:
         2699  +              rc = cson_rc.TypeError;
         2700  +              break;
         2701  +        }
         2702  +        if(v) *v = d;
         2703  +        return rc;
         2704  +    }
         2705  +}
         2706  +
         2707  +cson_double_t cson_value_get_double( cson_value const * val )
         2708  +{
         2709  +    cson_double_t i = 0.0;
         2710  +    cson_value_fetch_double( val, &i );
         2711  +    return i;
         2712  +}
         2713  +
         2714  +int cson_value_fetch_string( cson_value const * val, cson_string ** dest )
         2715  +{
         2716  +    if( ! val || ! dest ) return cson_rc.ArgError;
         2717  +    else if( ! cson_value_is_string(val) ) return cson_rc.TypeError;
         2718  +    else
         2719  +    {
         2720  +        if( dest ) *dest = CSON_STR(val);
         2721  +        return 0;
         2722  +    }
         2723  +}
         2724  +
         2725  +cson_string * cson_value_get_string( cson_value const * val )
         2726  +{
         2727  +    cson_string * rc = NULL;
         2728  +    cson_value_fetch_string( val, &rc );
         2729  +    return rc;
         2730  +}
         2731  +
         2732  +char const * cson_value_get_cstr( cson_value const * val )
         2733  +{
         2734  +    return cson_string_cstr( cson_value_get_string(val) );
         2735  +}
         2736  +
         2737  +int cson_value_fetch_object( cson_value const * val, cson_object ** obj )
         2738  +{
         2739  +    if( ! val ) return cson_rc.ArgError;
         2740  +    else if( ! cson_value_is_object(val) ) return cson_rc.TypeError;
         2741  +    else
         2742  +    {
         2743  +        if(obj) *obj = CSON_OBJ(val);
         2744  +        return 0;
         2745  +    }
         2746  +}
         2747  +cson_object * cson_value_get_object( cson_value const * v )
         2748  +{
         2749  +    cson_object * obj = NULL;
         2750  +    cson_value_fetch_object( v, &obj );
         2751  +    return obj;
         2752  +}
         2753  +
         2754  +int cson_value_fetch_array( cson_value const * val, cson_array ** ar)
         2755  +{
         2756  +    if( ! val ) return cson_rc.ArgError;
         2757  +    else if( !cson_value_is_array(val) ) return cson_rc.TypeError;
         2758  +    else
         2759  +    {
         2760  +        if(ar) *ar = CSON_ARRAY(val);
         2761  +        return 0;
         2762  +    }
         2763  +}
         2764  +
         2765  +cson_array * cson_value_get_array( cson_value const * v )
         2766  +{
         2767  +    cson_array * ar = NULL;
         2768  +    cson_value_fetch_array( v, &ar );
         2769  +    return ar;
         2770  +}
         2771  +
         2772  +cson_kvp * cson_kvp_alloc()
         2773  +{
         2774  +    cson_kvp * kvp = (cson_kvp*)cson_malloc(sizeof(cson_kvp),"cson_kvp");
         2775  +    if( kvp )
         2776  +    {
         2777  +        *kvp = cson_kvp_empty;
         2778  +    }
         2779  +    return kvp;
         2780  +}
         2781  +
         2782  +
         2783  +
         2784  +int cson_array_append( cson_array * ar, cson_value * v )
         2785  +{
         2786  +    if( !ar || !v ) return cson_rc.ArgError;
         2787  +    else if( (ar->list.count+1) < ar->list.count ) return cson_rc.RangeError;
         2788  +    else
         2789  +    {
         2790  +        if( !ar->list.alloced || (ar->list.count == ar->list.alloced-1))
         2791  +        {
         2792  +            unsigned int const n = ar->list.count ? (ar->list.count*2) : 7;
         2793  +            if( n > cson_value_list_reserve( &ar->list, n ) )
         2794  +            {
         2795  +                return cson_rc.AllocError;
         2796  +            }
         2797  +        }
         2798  +        return cson_array_set( ar, ar->list.count, v );
         2799  +    }
         2800  +}
         2801  +
         2802  +#if 0
         2803  +/**
         2804  +   Removes and returns the last value from the given array,
         2805  +   shrinking its size by 1. Returns NULL if ar is NULL,
         2806  +   ar->list.count is 0, or the element at that index is NULL.
         2807  +   
         2808  +
         2809  +   If removeRef is true then cson_value_free() is called to remove
         2810  +   ar's reference count for the value. In that case NULL is returned,
         2811  +   even if the object still has live references. If removeRef is false
         2812  +   then the caller takes over ownership of that reference count point.
         2813  +
         2814  +   If removeRef is false then the caller takes over ownership
         2815  +   of the return value, otherwise ownership is effectively
         2816  +   determined by any remaining references for the returned
         2817  +   value.
         2818  +*/
         2819  +static cson_value * cson_array_pop_back( cson_array * ar,
         2820  +                                         char removeRef )
         2821  +{
         2822  +    if( !ar ) return NULL;
         2823  +    else if( ! ar->list.count ) return NULL;
         2824  +    else
         2825  +    {
         2826  +        unsigned int const ndx = --ar->list.count;
         2827  +        cson_value * v = ar->list.list[ndx];
         2828  +        ar->list.list[ndx] = NULL;
         2829  +        if( removeRef )
         2830  +        {
         2831  +            cson_value_free( v );
         2832  +            v = NULL;
         2833  +        }
         2834  +        return v;
         2835  +    }
         2836  +}
         2837  +#endif
         2838  +
         2839  +cson_value * cson_value_new_bool( char v )
         2840  +{
         2841  +    return v ? &CSON_SPECIAL_VALUES[CSON_VAL_TRUE] : &CSON_SPECIAL_VALUES[CSON_VAL_FALSE];
         2842  +}
         2843  +
         2844  +cson_value * cson_value_true()
         2845  +{
         2846  +    return &CSON_SPECIAL_VALUES[CSON_VAL_TRUE];
         2847  +}
         2848  +cson_value * cson_value_false()
         2849  +{
         2850  +    return &CSON_SPECIAL_VALUES[CSON_VAL_FALSE];
         2851  +}
         2852  +
         2853  +cson_value * cson_value_null()
         2854  +{
         2855  +    return &CSON_SPECIAL_VALUES[CSON_VAL_NULL];
         2856  +}
         2857  +
         2858  +cson_value * cson_new_int( cson_int_t v )
         2859  +{
         2860  +    return cson_value_new_integer(v);
         2861  +}
         2862  +
         2863  +cson_value * cson_value_new_integer( cson_int_t v )
         2864  +{
         2865  +    if( 0 == v ) return &CSON_SPECIAL_VALUES[CSON_VAL_INT_0];
         2866  +    else
         2867  +    {
         2868  +        cson_value * c = cson_value_new(CSON_TYPE_INTEGER,0);
         2869  +
         2870  +        if( c )
         2871  +        {
         2872  +            *CSON_INT(c) = v;
         2873  +        }
         2874  +        return c;
         2875  +    }
         2876  +}
         2877  +
         2878  +cson_value * cson_new_double( cson_double_t v )
         2879  +{
         2880  +    return cson_value_new_double(v);
         2881  +}
         2882  +
         2883  +cson_value * cson_value_new_double( cson_double_t v )
         2884  +{
         2885  +    if( 0.0 == v ) return &CSON_SPECIAL_VALUES[CSON_VAL_DBL_0];
         2886  +    else
         2887  +    {
         2888  +        cson_value * c = cson_value_new(CSON_TYPE_DOUBLE,0);
         2889  +        if( c )
         2890  +        {
         2891  +            *CSON_DBL(c) = v;
         2892  +        }
         2893  +        return c;
         2894  +    }
         2895  +}
         2896  +
         2897  +cson_string * cson_new_string(char const * str, unsigned int len)
         2898  +{
         2899  +    if( !str || !*str || !len ) return &CSON_EMPTY_HOLDER.stringValue;
         2900  +    else
         2901  +    {
         2902  +        cson_value * c = cson_value_new(CSON_TYPE_STRING, len + 1/*NUL byte*/);
         2903  +        cson_string * s = NULL;
         2904  +        if( c )
         2905  +        {
         2906  +            char * dest = NULL;
         2907  +            s = CSON_STR(c);
         2908  +            *s = cson_string_empty;
         2909  +            assert( NULL != s );
         2910  +            s->length = len;
         2911  +            dest = cson_string_str(s);
         2912  +            assert( NULL != dest );
         2913  +            memcpy( dest, str, len );
         2914  +            dest[len] = 0;
         2915  +        }
         2916  +        return s;
         2917  +    }
         2918  +}
         2919  +
         2920  +cson_value * cson_value_new_string( char const * str, unsigned int len )
         2921  +{
         2922  +    return cson_string_value( cson_new_string(str, len) );
         2923  +}
         2924  +
         2925  +int cson_array_value_fetch( cson_array const * ar, unsigned int pos, cson_value ** v )
         2926  +{
         2927  +    if( !ar) return cson_rc.ArgError;
         2928  +    if( pos >= ar->list.count ) return cson_rc.RangeError;
         2929  +    else
         2930  +    {
         2931  +        if(v) *v = ar->list.list[pos];
         2932  +        return 0;
         2933  +    }
         2934  +}
         2935  +
         2936  +cson_value * cson_array_get( cson_array const * ar, unsigned int pos )
         2937  +{
         2938  +    cson_value *v = NULL;
         2939  +    cson_array_value_fetch(ar, pos, &v);
         2940  +    return v;
         2941  +}
         2942  +
         2943  +int cson_array_length_fetch( cson_array const * ar, unsigned int * v )
         2944  +{
         2945  +    if( ! ar || !v ) return cson_rc.ArgError;
         2946  +    else
         2947  +    {
         2948  +        if(v) *v = ar->list.count;
         2949  +        return 0;
         2950  +    }
         2951  +}
         2952  +
         2953  +unsigned int cson_array_length_get( cson_array const * ar )
         2954  +{
         2955  +    unsigned int i = 0;
         2956  +    cson_array_length_fetch(ar, &i);
         2957  +    return i;
         2958  +}
         2959  +
         2960  +int cson_array_reserve( cson_array * ar, unsigned int size )
         2961  +{
         2962  +    if( ! ar ) return cson_rc.ArgError;
         2963  +    else if( size <= ar->list.alloced )
         2964  +    {
         2965  +        /* We don't want to introduce a can of worms by trying to
         2966  +           handle the cleanup from here.
         2967  +        */
         2968  +        return 0;
         2969  +    }
         2970  +    else
         2971  +    {
         2972  +        return (ar->list.alloced > cson_value_list_reserve( &ar->list, size ))
         2973  +            ? cson_rc.AllocError
         2974  +            : 0
         2975  +            ;
         2976  +    }
         2977  +}
         2978  +
         2979  +int cson_array_set( cson_array * ar, unsigned int ndx, cson_value * v )
         2980  +{
         2981  +    if( !ar || !v ) return cson_rc.ArgError;
         2982  +    else if( (ndx+1) < ndx) /* overflow */return cson_rc.RangeError;
         2983  +    else
         2984  +    {
         2985  +        unsigned const int len = cson_value_list_reserve( &ar->list, ndx+1 );
         2986  +        if( len <= ndx ) return cson_rc.AllocError;
         2987  +        else
         2988  +        {
         2989  +            cson_value * old = ar->list.list[ndx];
         2990  +            if( old )
         2991  +            {
         2992  +                if(old == v) return 0;
         2993  +                else cson_value_free(old);
         2994  +            }
         2995  +            cson_refcount_incr( v );
         2996  +            ar->list.list[ndx] = v;
         2997  +            if( ndx >= ar->list.count )
         2998  +            {
         2999  +                ar->list.count = ndx+1;
         3000  +            }
         3001  +            return 0;
         3002  +        }
         3003  +    }
         3004  +}
         3005  +
         3006  +/** @internal
         3007  +
         3008  +   Searchs for the given key in the given object.
         3009  +
         3010  +   Returns the found item on success, NULL on error.  If ndx is not
         3011  +   NULL, it is set to the index (in obj->kvp.list) of the found
         3012  +   item. *ndx is not modified if no entry is found.
         3013  +*/
         3014  +static cson_kvp * cson_object_search_impl( cson_object const * obj, char const * key, unsigned int * ndx )
         3015  +{
         3016  +    if( obj && key && *key && obj->kvp.count)
         3017  +    {
         3018  +#if CSON_OBJECT_PROPS_SORT
         3019  +        cson_kvp ** s = (cson_kvp**)
         3020  +            bsearch( key, obj->kvp.list,
         3021  +                     obj->kvp.count, sizeof(cson_kvp*),
         3022  +                     cson_kvp_cmp_vs_cstr );
         3023  +        if( ndx && s )
         3024  +        { /* index of found record is required by
         3025  +             cson_object_unset(). Calculate the offset based on s...*/
         3026  +#if 0
         3027  +            *ndx = (((unsigned char const *)s - ((unsigned char const *)obj->kvp.list))
         3028  +                   / sizeof(cson_kvp*));
         3029  +#else
         3030  +            *ndx = s - obj->kvp.list;
         3031  +#endif
         3032  +        }
         3033  +        return s ? *s : NULL;
         3034  +#else
         3035  +        cson_kvp_list const * li = &obj->kvp;
         3036  +        unsigned int i = 0;
         3037  +        cson_kvp * kvp;
         3038  +        const unsigned int klen = strlen(key);
         3039  +        for( ; i < li->count; ++i )
         3040  +        {
         3041  +            cson_string const * sKey;
         3042  +            kvp = li->list[i];
         3043  +            assert( kvp && kvp->key );
         3044  +            sKey = cson_value_get_string(kvp->key);
         3045  +            assert(sKey);
         3046  +            if( sKey->length != klen ) continue;
         3047  +            else if(0==strcmp(key,cson_string_cstr(sKey)))
         3048  +            {
         3049  +                if(ndx) *ndx = i;
         3050  +                return kvp;
         3051  +            }
         3052  +        }
         3053  +#endif
         3054  +    }
         3055  +    return NULL;
         3056  +}
         3057  +
         3058  +cson_value * cson_object_get( cson_object const * obj, char const * key )
         3059  +{
         3060  +    cson_kvp * kvp = cson_object_search_impl( obj, key, NULL );
         3061  +    return kvp ? kvp->value : NULL;
         3062  +}
         3063  +
         3064  +cson_value * cson_object_get_s( cson_object const * obj, cson_string const *key )
         3065  +{
         3066  +    cson_kvp * kvp = cson_object_search_impl( obj, cson_string_cstr(key), NULL );
         3067  +    return kvp ? kvp->value : NULL;
         3068  +}
         3069  +
         3070  +
         3071  +#if CSON_OBJECT_PROPS_SORT
         3072  +static void cson_object_sort_props( cson_object * obj )
         3073  +{
         3074  +    assert( NULL != obj );
         3075  +    if( obj->kvp.count )
         3076  +    {
         3077  +        qsort( obj->kvp.list, obj->kvp.count, sizeof(cson_kvp*),
         3078  +               cson_kvp_cmp );
         3079  +    }
         3080  +
         3081  +}
         3082  +#endif    
         3083  +
         3084  +int cson_object_unset( cson_object * obj, char const * key )
         3085  +{
         3086  +    if( ! obj || !key || !*key ) return cson_rc.ArgError;
         3087  +    else
         3088  +    {
         3089  +        unsigned int ndx = 0;
         3090  +        cson_kvp * kvp = cson_object_search_impl( obj, key, &ndx );
         3091  +        if( ! kvp )
         3092  +        {
         3093  +            return cson_rc.NotFoundError;
         3094  +        }
         3095  +        assert( obj->kvp.count > 0 );
         3096  +        assert( obj->kvp.list[ndx] == kvp );
         3097  +        cson_kvp_free( kvp );
         3098  +        obj->kvp.list[ndx] = NULL;
         3099  +        { /* if my brain were bigger i'd use memmove(). */
         3100  +            unsigned int i = ndx;
         3101  +            for( ; i < obj->kvp.count; ++i )
         3102  +            {
         3103  +                obj->kvp.list[i] =
         3104  +                    (i < (obj->kvp.alloced-1))
         3105  +                    ? obj->kvp.list[i+1]
         3106  +                    : NULL;
         3107  +            }
         3108  +        }
         3109  +        obj->kvp.list[--obj->kvp.count] = NULL;
         3110  +#if CSON_OBJECT_PROPS_SORT
         3111  +        cson_object_sort_props( obj );
         3112  +#endif
         3113  +        return 0;
         3114  +    }
         3115  +}
         3116  +
         3117  +int cson_object_set_s( cson_object * obj, cson_string * key, cson_value * v )
         3118  +{
         3119  +    if( !obj || !key ) return cson_rc.ArgError;
         3120  +    else if( NULL == v ) return cson_object_unset( obj, cson_string_cstr(key) );
         3121  +    else
         3122  +    {
         3123  +        char const * cKey;
         3124  +        cson_value * vKey;
         3125  +        cson_kvp * kvp;
         3126  +        vKey = cson_string_value(key);
         3127  +        assert(vKey && (key==CSON_STR(vKey)));
         3128  +        if( vKey == CSON_VCAST(obj) ){
         3129  +            return cson_rc.ArgError;
         3130  +        }
         3131  +        cKey =  cson_string_cstr(key);
         3132  +        kvp = cson_object_search_impl( obj, cKey, NULL );
         3133  +        if( kvp )
         3134  +        { /* "I told 'em we've already got one!" */
         3135  +            if( kvp->key != vKey ){
         3136  +                cson_value_free( kvp->key );
         3137  +                cson_refcount_incr(vKey);
         3138  +                kvp->key = vKey;
         3139  +            }
         3140  +            if(kvp->value != v){
         3141  +                cson_value_free( kvp->value );
         3142  +                cson_refcount_incr( v );
         3143  +                kvp->value = v;
         3144  +            }
         3145  +            return 0;
         3146  +        }
         3147  +        if( !obj->kvp.alloced || (obj->kvp.count == obj->kvp.alloced-1))
         3148  +        {
         3149  +            unsigned int const n = obj->kvp.count ? (obj->kvp.count*2) : 6;
         3150  +            if( n > cson_kvp_list_reserve( &obj->kvp, n ) )
         3151  +            {
         3152  +                return cson_rc.AllocError;
         3153  +            }
         3154  +        }
         3155  +        { /* insert new item... */
         3156  +            int rc = 0;
         3157  +            kvp = cson_kvp_alloc();
         3158  +            if( ! kvp )
         3159  +            {
         3160  +                return cson_rc.AllocError;
         3161  +            }
         3162  +            rc = cson_kvp_list_append( &obj->kvp, kvp );
         3163  +            if( 0 != rc )
         3164  +            {
         3165  +                cson_kvp_free(kvp);
         3166  +            }
         3167  +            else
         3168  +            {
         3169  +                cson_refcount_incr(vKey);
         3170  +                cson_refcount_incr(v);
         3171  +                kvp->key = vKey;
         3172  +                kvp->value = v;
         3173  +#if CSON_OBJECT_PROPS_SORT
         3174  +                cson_object_sort_props( obj );
         3175  +#endif
         3176  +            }
         3177  +            return rc;
         3178  +        }
         3179  +    }
         3180  +
         3181  +}
         3182  +int cson_object_set( cson_object * obj, char const * key, cson_value * v )
         3183  +{
         3184  +    if( ! obj || !key || !*key ) return cson_rc.ArgError;
         3185  +    else if( NULL == v )
         3186  +    {
         3187  +        return cson_object_unset( obj, key );
         3188  +    }
         3189  +    else
         3190  +    {
         3191  +        cson_string * cs = cson_new_string(key,strlen(key));
         3192  +        if(!cs) return cson_rc.AllocError;
         3193  +        else
         3194  +        {
         3195  +            int const rc = cson_object_set_s(obj, cs, v);
         3196  +            if(rc) cson_value_free(cson_string_value(cs));
         3197  +            return rc;
         3198  +        }
         3199  +    }
         3200  +}
         3201  +
         3202  +cson_value * cson_object_take( cson_object * obj, char const * key )
         3203  +{
         3204  +    if( ! obj || !key || !*key ) return NULL;
         3205  +    else
         3206  +    {
         3207  +        /* FIXME: this is 90% identical to cson_object_unset(),
         3208  +           only with different refcount handling.
         3209  +           Consolidate them.
         3210  +        */
         3211  +        unsigned int ndx = 0;
         3212  +        cson_kvp * kvp = cson_object_search_impl( obj, key, &ndx );
         3213  +        cson_value * rc = NULL;
         3214  +        if( ! kvp )
         3215  +        {
         3216  +            return NULL;
         3217  +        }
         3218  +        assert( obj->kvp.count > 0 );
         3219  +        assert( obj->kvp.list[ndx] == kvp );
         3220  +        rc = kvp->value;
         3221  +        assert( rc );
         3222  +        kvp->value = NULL;
         3223  +        cson_kvp_free( kvp );
         3224  +        assert( rc->refcount > 0 );
         3225  +        --rc->refcount;
         3226  +        obj->kvp.list[ndx] = NULL;
         3227  +        { /* if my brain were bigger i'd use memmove(). */
         3228  +            unsigned int i = ndx;
         3229  +            for( ; i < obj->kvp.count; ++i )
         3230  +            {
         3231  +                obj->kvp.list[i] =
         3232  +                    (i < (obj->kvp.alloced-1))
         3233  +                    ? obj->kvp.list[i+1]
         3234  +                    : NULL;
         3235  +            }
         3236  +        }
         3237  +        obj->kvp.list[--obj->kvp.count] = NULL;
         3238  +#if CSON_OBJECT_PROPS_SORT
         3239  +        cson_object_sort_props( obj );
         3240  +#endif
         3241  +        return rc;
         3242  +    }
         3243  +}
         3244  +/** @internal
         3245  +
         3246  +   If p->node is-a Object then value is inserted into the object
         3247  +   using p->key. In any other case cson_rc.InternalError is returned.
         3248  +
         3249  +   Returns cson_rc.AllocError if an allocation fails.
         3250  +
         3251  +   Returns 0 on success. On error, parsing must be ceased immediately.
         3252  +   
         3253  +   Ownership of val is ALWAYS TRANSFERED to this function. If this
         3254  +   function fails, val will be cleaned up and destroyed. (This
         3255  +   simplifies error handling in the core parser.)
         3256  +*/
         3257  +static int cson_parser_set_key( cson_parser * p, cson_value * val )
         3258  +{
         3259  +    assert( p && val );
         3260  +
         3261  +    if( p->ckey && cson_value_is_object(p->node) )
         3262  +    {
         3263  +        int rc;
         3264  +        cson_object * obj = cson_value_get_object(p->node);
         3265  +        cson_kvp * kvp = NULL;
         3266  +        assert( obj && (p->node->value == obj) );
         3267  +        /**
         3268  +           FIXME? Use cson_object_set() instead of our custom
         3269  +           finagling with the object? We do it this way to avoid an
         3270  +           extra alloc/strcpy of the key data.
         3271  +        */
         3272  +        if( !obj->kvp.alloced || (obj->kvp.count == obj->kvp.alloced-1))
         3273  +        {
         3274  +            if( obj->kvp.alloced > cson_kvp_list_reserve( &obj->kvp, obj->kvp.count ? (obj->kvp.count*2) : 5 ) )
         3275  +            {
         3276  +                cson_value_free(val);
         3277  +                return cson_rc.AllocError;
         3278  +            }
         3279  +        }
         3280  +        kvp = cson_kvp_alloc();
         3281  +        if( ! kvp )
         3282  +        {
         3283  +            cson_value_free(val);
         3284  +            return cson_rc.AllocError;
         3285  +        }
         3286  +        kvp->key = cson_string_value(p->ckey)/*transfer ownership*/;
         3287  +        p->ckey = NULL;
         3288  +        kvp->value = val;
         3289  +        cson_refcount_incr( val );
         3290  +        rc = cson_kvp_list_append( &obj->kvp, kvp );
         3291  +        if( 0 != rc )
         3292  +        {
         3293  +            cson_kvp_free( kvp );
         3294  +        }
         3295  +        else
         3296  +        {
         3297  +            ++p->totalValueCount;
         3298  +        }
         3299  +        return rc;
         3300  +    }
         3301  +    else
         3302  +    {
         3303  +        if(val) cson_value_free(val);
         3304  +        return p->errNo = cson_rc.InternalError;
         3305  +    }
         3306  +
         3307  +}
         3308  +
         3309  +/** @internal
         3310  +
         3311  +    Pushes val into the current object/array parent node, depending on the
         3312  +    internal state of the parser.
         3313  +
         3314  +    Ownership of val is always transfered to this function, regardless of
         3315  +    success or failure.
         3316  +
         3317  +    Returns 0 on success. On error, parsing must be ceased immediately.
         3318  +*/
         3319  +static int cson_parser_push_value( cson_parser * p, cson_value * val )
         3320  +{
         3321  +    if( p->ckey )
         3322  +    { /* we're in Object mode */
         3323  +        assert( cson_value_is_object( p->node ) );
         3324  +        return cson_parser_set_key( p, val );
         3325  +    }
         3326  +    else if( cson_value_is_array( p->node ) )
         3327  +    { /* we're in Array mode */
         3328  +        cson_array * ar = cson_value_get_array( p->node );
         3329  +        int rc;
         3330  +        assert( ar && (ar == p->node->value) );
         3331  +        rc = cson_array_append( ar, val );
         3332  +        if( 0 != rc )
         3333  +        {
         3334  +            cson_value_free(val);
         3335  +        }
         3336  +        else
         3337  +        {
         3338  +            ++p->totalValueCount;
         3339  +        }
         3340  +        return rc;
         3341  +    }
         3342  +    else
         3343  +    { /* WTF? */
         3344  +        assert( 0 && "Internal error in cson_parser code" );
         3345  +        return p->errNo = cson_rc.InternalError;
         3346  +    }
         3347  +}
         3348  +
         3349  +/**
         3350  +   Callback for JSON_parser API. Reminder: it returns 0 (meaning false)
         3351  +   on error!
         3352  +*/
         3353  +static int cson_parse_callback( void * cx, int type, JSON_value const * value )
         3354  +{
         3355  +    cson_parser * p = (cson_parser *)cx;
         3356  +    int rc = 0;
         3357  +#define ALLOC_V(T,V) cson_value * v = cson_value_new_##T(V); if( ! v ) { rc = cson_rc.AllocError; break; }
         3358  +    switch(type) {
         3359  +      case JSON_T_ARRAY_BEGIN:
         3360  +      case JSON_T_OBJECT_BEGIN: {
         3361  +          cson_value * obja = (JSON_T_ARRAY_BEGIN == type)
         3362  +              ? cson_value_new_array()
         3363  +              : cson_value_new_object();
         3364  +          if( ! obja )
         3365  +          {
         3366  +              p->errNo = cson_rc.AllocError;
         3367  +              return 0;
         3368  +          }
         3369  +          if( 0 != rc ) break;
         3370  +          if( ! p->root )
         3371  +          {
         3372  +              p->root = p->node = obja;
         3373  +              rc = cson_array_append( &p->stack, obja );
         3374  +              if( 0 != rc )
         3375  +              { /* work around a (potential) corner case in the cleanup code. */
         3376  +                  cson_value_free( p->root );
         3377  +                  p->root = NULL;
         3378  +              }
         3379  +              else
         3380  +              {
         3381  +                  cson_refcount_incr( p->root )
         3382  +                      /* simplifies cleanup later on. */
         3383  +                      ;
         3384  +                  ++p->totalValueCount;
         3385  +              }
         3386  +          }
         3387  +          else
         3388  +          {
         3389  +              rc = cson_array_append( &p->stack, obja );
         3390  +              if( 0 == rc ) rc = cson_parser_push_value( p, obja );
         3391  +              if( 0 == rc ) p->node = obja;
         3392  +          }
         3393  +          break;
         3394  +      }
         3395  +      case JSON_T_ARRAY_END:
         3396  +      case JSON_T_OBJECT_END: {
         3397  +          if( 0 == p->stack.list.count )
         3398  +          {
         3399  +              rc = cson_rc.RangeError;
         3400  +              break;
         3401  +          }
         3402  +#if CSON_OBJECT_PROPS_SORT
         3403  +          if( cson_value_is_object(p->node) )
         3404  +          {/* kludge: the parser uses custom cson_object property
         3405  +              insertion as a malloc/strcpy-reduction optimization.
         3406  +              Because of that, we have to sort the property list
         3407  +              ourselves...
         3408  +           */
         3409  +              cson_object * obj = cson_value_get_object(p->node);
         3410  +              assert( NULL != obj );
         3411  +              cson_object_sort_props( obj );
         3412  +          }
         3413  +#endif
         3414  +
         3415  +#if 1
         3416  +          /* Reminder: do not use cson_array_pop_back( &p->stack )
         3417  +             because that will clean up the object, and we don't want
         3418  +             that.  We just want to forget this reference
         3419  +             to it. The object is either the root or was pushed into
         3420  +             an object/array in the parse tree (and is owned by that
         3421  +             object/array).
         3422  +          */
         3423  +          --p->stack.list.count;
         3424  +          assert( p->node == p->stack.list.list[p->stack.list.count] );
         3425  +          cson_refcount_decr( p->node )
         3426  +              /* p->node might be owned by an outer object but we
         3427  +                 need to remove the list's reference. For the
         3428  +                 root node we manually add a reference to
         3429  +                 avoid a special case here. Thus when we close
         3430  +                 the root node, its refcount is still 1.
         3431  +              */;
         3432  +          p->stack.list.list[p->stack.list.count] = NULL;
         3433  +          if( p->stack.list.count )
         3434  +          {
         3435  +              p->node = p->stack.list.list[p->stack.list.count-1];
         3436  +          }
         3437  +          else
         3438  +          {
         3439  +              p->node = p->root;
         3440  +          }
         3441  +#else
         3442  +          /*
         3443  +             Causing a leak?
         3444  +           */
         3445  +          cson_array_pop_back( &p->stack, 1 );
         3446  +          if( p->stack.list.count )
         3447  +          {
         3448  +              p->node = p->stack.list.list[p->stack.list.count-1];
         3449  +          }
         3450  +          else
         3451  +          {
         3452  +              p->node = p->root;
         3453  +          }
         3454  +          assert( p->node && (1==p->node->refcount) );
         3455  +#endif
         3456  +          break;
         3457  +      }
         3458  +      case JSON_T_INTEGER: {
         3459  +          ALLOC_V(integer, value->vu.integer_value );
         3460  +          rc = cson_parser_push_value( p, v );
         3461  +          break;
         3462  +      }
         3463  +      case JSON_T_FLOAT: {
         3464  +          ALLOC_V(double, value->vu.float_value );
         3465  +          rc =  cson_parser_push_value( p, v );
         3466  +          break;
         3467  +      }
         3468  +      case JSON_T_NULL: {
         3469  +          rc = cson_parser_push_value( p, cson_value_null() );
         3470  +          break;
         3471  +      }
         3472  +      case JSON_T_TRUE: {
         3473  +          rc = cson_parser_push_value( p, cson_value_true() );
         3474  +          break;
         3475  +      }
         3476  +      case JSON_T_FALSE: {
         3477  +          rc = cson_parser_push_value( p, cson_value_false() );
         3478  +          break;
         3479  +      }
         3480  +      case JSON_T_KEY: {
         3481  +          assert(!p->ckey);
         3482  +          p->ckey = cson_new_string( value->vu.str.value, value->vu.str.length );
         3483  +          if( ! p->ckey )
         3484  +          {
         3485  +              rc = cson_rc.AllocError;
         3486  +              break;
         3487  +          }
         3488  +          ++p->totalKeyCount;
         3489  +          break;
         3490  +      }
         3491  +      case JSON_T_STRING: {
         3492  +          cson_value * v = cson_value_new_string( value->vu.str.value, value->vu.str.length );
         3493  +          rc = ( NULL == v ) 
         3494  +            ? cson_rc.AllocError
         3495  +            : cson_parser_push_value( p, v );
         3496  +          break;
         3497  +      }
         3498  +      default:
         3499  +          assert(0);
         3500  +          rc = cson_rc.InternalError;
         3501  +          break;
         3502  +    }
         3503  +#undef ALLOC_V
         3504  +    return ((p->errNo = rc)) ? 0 : 1;
         3505  +}
         3506  +
         3507  +
         3508  +/**
         3509  +   Converts a JSON_error code to one of the cson_rc values.
         3510  +*/
         3511  +static int cson_json_err_to_rc( JSON_error jrc )
         3512  +{
         3513  +    switch(jrc)
         3514  +    {
         3515  +      case JSON_E_NONE: return 0;
         3516  +      case JSON_E_INVALID_CHAR: return cson_rc.Parse_INVALID_CHAR;
         3517  +      case JSON_E_INVALID_KEYWORD: return cson_rc.Parse_INVALID_KEYWORD;
         3518  +      case JSON_E_INVALID_ESCAPE_SEQUENCE: return cson_rc.Parse_INVALID_ESCAPE_SEQUENCE;
         3519  +      case JSON_E_INVALID_UNICODE_SEQUENCE: return cson_rc.Parse_INVALID_UNICODE_SEQUENCE;
         3520  +      case JSON_E_INVALID_NUMBER: return cson_rc.Parse_INVALID_NUMBER;
         3521  +      case JSON_E_NESTING_DEPTH_REACHED: return cson_rc.Parse_NESTING_DEPTH_REACHED;
         3522  +      case JSON_E_UNBALANCED_COLLECTION: return cson_rc.Parse_UNBALANCED_COLLECTION;
         3523  +      case JSON_E_EXPECTED_KEY: return cson_rc.Parse_EXPECTED_KEY;
         3524  +      case JSON_E_EXPECTED_COLON: return cson_rc.Parse_EXPECTED_COLON;
         3525  +      case JSON_E_OUT_OF_MEMORY: return cson_rc.AllocError;
         3526  +      default:
         3527  +          return cson_rc.InternalError;
         3528  +    }
         3529  +}
         3530  +
         3531  +/** @internal
         3532  +
         3533  +   Cleans up all contents of p but does not free p.
         3534  +
         3535  +   To properly take over ownership of the parser's root node on a
         3536  +   successful parse:
         3537  +
         3538  +   - Copy p->root's pointer and set p->root to NULL.
         3539  +   - Eventually free up p->root with cson_value_free().
         3540  +   
         3541  +   If you do not set p->root to NULL, p->root will be freed along with
         3542  +   any other items inserted into it (or under it) during the parsing
         3543  +   process.
         3544  +*/
         3545  +static int cson_parser_clean( cson_parser * p )
         3546  +{
         3547  +    if( ! p ) return cson_rc.ArgError;
         3548  +    else
         3549  +    {
         3550  +        if( p->p )
         3551  +        {
         3552  +            delete_JSON_parser(p->p);
         3553  +            p->p = NULL;
         3554  +        }
         3555  +        if( p->ckey ){
         3556  +            cson_value_free(cson_string_value(p->ckey));
         3557  +        }
         3558  +        cson_array_clean( &p->stack, 1 );
         3559  +        if( p->root )
         3560  +        {
         3561  +            cson_value_free( p->root );
         3562  +        }
         3563  +        *p = cson_parser_empty;
         3564  +        return 0;
         3565  +    }
         3566  +}
         3567  +
         3568  +
         3569  +int cson_parse( cson_value ** tgt, cson_data_source_f src, void * state,
         3570  +                cson_parse_opt const * opt_, cson_parse_info * info_ )
         3571  +{
         3572  +    unsigned char ch[2] = {0,0};
         3573  +    cson_parse_opt const opt = opt_ ? *opt_ : cson_parse_opt_empty;
         3574  +    int rc = 0;
         3575  +    unsigned int len = 1;
         3576  +    cson_parse_info info = info_ ? *info_ : cson_parse_info_empty;
         3577  +    cson_parser p = cson_parser_empty;
         3578  +    if( ! tgt || ! src ) return cson_rc.ArgError;
         3579  +    
         3580  +    {
         3581  +        JSON_config jopt = {0};
         3582  +        init_JSON_config( &jopt );
         3583  +        jopt.allow_comments = opt.allowComments;
         3584  +        jopt.depth = opt.maxDepth;
         3585  +        jopt.callback_ctx = &p;
         3586  +        jopt.handle_floats_manually = 0;
         3587  +        jopt.callback = cson_parse_callback;
         3588  +        p.p = new_JSON_parser(&jopt);
         3589  +        if( ! p.p )
         3590  +        {
         3591  +            return cson_rc.AllocError;
         3592  +        }
         3593  +    }
         3594  +
         3595  +    do
         3596  +    { /* FIXME: buffer the input in multi-kb chunks. */
         3597  +        len = 1;
         3598  +        ch[0] = 0;
         3599  +        rc = src( state, ch, &len );
         3600  +        if( 0 != rc ) break;
         3601  +        else if( !len /* EOF */ ) break;
         3602  +        ++info.length;
         3603  +        if('\n' == ch[0])
         3604  +        {
         3605  +            ++info.line;
         3606  +            info.col = 0;
         3607  +        }
         3608  +        if( ! JSON_parser_char(p.p, ch[0]) )
         3609  +        {
         3610  +            rc = cson_json_err_to_rc( JSON_parser_get_last_error(p.p) );
         3611  +            if(0==rc) rc = p.errNo;
         3612  +            if(0==rc) rc = cson_rc.InternalError;
         3613  +            info.errorCode = rc;
         3614  +            break;
         3615  +        }
         3616  +        if( '\n' != ch[0]) ++info.col;
         3617  +    } while(1);
         3618  +    if( info_ )
         3619  +    {
         3620  +        info.totalKeyCount = p.totalKeyCount;
         3621  +        info.totalValueCount = p.totalValueCount;
         3622  +        *info_ = info;
         3623  +    }
         3624  +    if( 0 != rc )
         3625  +    {
         3626  +        cson_parser_clean(&p);
         3627  +        return rc;
         3628  +    }
         3629  +    if( ! JSON_parser_done(p.p) )
         3630  +    {
         3631  +        rc = cson_json_err_to_rc( JSON_parser_get_last_error(p.p) );
         3632  +        cson_parser_clean(&p);
         3633  +        if(0==rc) rc = p.errNo;
         3634  +        if(0==rc) rc = cson_rc.InternalError;
         3635  +    }
         3636  +    else
         3637  +    {
         3638  +        cson_value * root = p.root;
         3639  +        p.root = NULL;
         3640  +        cson_parser_clean(&p);
         3641  +        if( root )
         3642  +        {
         3643  +            assert( (1 == root->refcount) && "Detected memory mismanagement in the parser." );
         3644  +            root->refcount = 0
         3645  +                /* HUGE KLUDGE! Avoids having one too many references
         3646  +                   in some client code, leading to a leak. Here we're
         3647  +                   accommodating a memory management workaround in the
         3648  +                   parser code which manually adds a reference to the
         3649  +                   root node to keep it from being cleaned up
         3650  +                   prematurely.
         3651  +                */;
         3652  +            *tgt = root;
         3653  +        }
         3654  +        else
         3655  +        { /* then can happen on empty input. */
         3656  +            rc = cson_rc.UnknownError;
         3657  +        }
         3658  +    }
         3659  +    return rc;
         3660  +}
         3661  +
         3662  +/**
         3663  +   The UTF code was originally taken from sqlite3's public-domain
         3664  +   source code (http://sqlite.org), modified only slightly for use
         3665  +   here. This code generates some "possible data loss" warnings on
         3666  +   MSVC, but if this code is good enough for sqlite3 then it's damned
         3667  +   well good enough for me, so we disable that warning for Windows
         3668  +   builds.
         3669  +*/
         3670  +
         3671  +/*
         3672  +** This lookup table is used to help decode the first byte of
         3673  +** a multi-byte UTF8 character.
         3674  +*/
         3675  +static const unsigned char cson_utfTrans1[] = {
         3676  +  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
         3677  +  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
         3678  +  0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
         3679  +  0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
         3680  +  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
         3681  +  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
         3682  +  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
         3683  +  0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00
         3684  +};
         3685  +
         3686  +
         3687  +/*
         3688  +** Translate a single UTF-8 character.  Return the unicode value.
         3689  +**
         3690  +** During translation, assume that the byte that zTerm points
         3691  +** is a 0x00.
         3692  +**
         3693  +** Write a pointer to the next unread byte back into *pzNext.
         3694  +**
         3695  +** Notes On Invalid UTF-8:
         3696  +**
         3697  +**  *  This routine never allows a 7-bit character (0x00 through 0x7f) to
         3698  +**     be encoded as a multi-byte character.  Any multi-byte character that
         3699  +**     attempts to encode a value between 0x00 and 0x7f is rendered as 0xfffd.
         3700  +**
         3701  +**  *  This routine never allows a UTF16 surrogate value to be encoded.
         3702  +**     If a multi-byte character attempts to encode a value between
         3703  +**     0xd800 and 0xe000 then it is rendered as 0xfffd.
         3704  +**
         3705  +**  *  Bytes in the range of 0x80 through 0xbf which occur as the first
         3706  +**     byte of a character are interpreted as single-byte characters
         3707  +**     and rendered as themselves even though they are technically
         3708  +**     invalid characters.
         3709  +**
         3710  +**  *  This routine accepts an infinite number of different UTF8 encodings
         3711  +**     for unicode values 0x80 and greater.  It do not change over-length
         3712  +**     encodings to 0xfffd as some systems recommend.
         3713  +*/
         3714  +#define READ_UTF8(zIn, zTerm, c)                           \
         3715  +  c = *(zIn++);                                            \
         3716  +  if( c>=0xc0 ){                                           \
         3717  +    c = cson_utfTrans1[c-0xc0];                          \
         3718  +    while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ){            \
         3719  +      c = (c<<6) + (0x3f & *(zIn++));                      \
         3720  +    }                                                      \
         3721  +    if( c<0x80                                             \
         3722  +        || (c&0xFFFFF800)==0xD800                          \
         3723  +        || (c&0xFFFFFFFE)==0xFFFE ){  c = 0xFFFD; }        \
         3724  +  }
         3725  +static int cson_utf8Read(
         3726  +  const unsigned char *z,         /* First byte of UTF-8 character */
         3727  +  const unsigned char *zTerm,     /* Pretend this byte is 0x00 */
         3728  +  const unsigned char **pzNext    /* Write first byte past UTF-8 char here */
         3729  +){
         3730  +  int c;
         3731  +  READ_UTF8(z, zTerm, c);
         3732  +  *pzNext = z;
         3733  +  return c;
         3734  +}
         3735  +#undef READ_UTF8
         3736  +
         3737  +#if defined(_WIN32)
         3738  +#  pragma warning( pop )
         3739  +#endif
         3740  +
         3741  +unsigned int cson_string_length_utf8( cson_string const * str )
         3742  +{
         3743  +    if( ! str ) return 0;
         3744  +    else
         3745  +    {
         3746  +        char unsigned const * pos = (char unsigned const *)cson_string_cstr(str);
         3747  +        char unsigned const * end = pos + str->length;
         3748  +        unsigned int rc = 0;
         3749  +        for( ; (pos < end) && cson_utf8Read(pos, end, &pos);
         3750  +            ++rc )
         3751  +        {
         3752  +        };
         3753  +        return rc;
         3754  +    }
         3755  +}
         3756  +
         3757  +/**
         3758  +   Escapes the first len bytes of the given string as JSON and sends
         3759  +   it to the given output function (which will be called often - once
         3760  +   for each logical character). The output is also surrounded by
         3761  +   double-quotes.
         3762  +
         3763  +   A NULL str will be escaped as an empty string, though we should
         3764  +   arguably export it as "null" (without quotes). We do this because
         3765  +   in JavaScript (typeof null === "object"), and by outputing null
         3766  +   here we would effectively change the data type from string to
         3767  +   object.
         3768  +*/
         3769  +static int cson_str_to_json( char const * str, unsigned int len,
         3770  +                             char escapeFwdSlash,
         3771  +                             cson_data_dest_f f, void * state )
         3772  +{
         3773  +    if( NULL == f ) return cson_rc.ArgError;
         3774  +    else if( !str || !*str || (0 == len) )
         3775  +    { /* special case for 0-length strings. */
         3776  +        return f( state, "\"\"", 2 );
         3777  +    }
         3778  +    else
         3779  +    {
         3780  +        unsigned char const * pos = (unsigned char const *)str;
         3781  +        unsigned char const * end = (unsigned char const *)(str ? (str + len) : NULL);
         3782  +        unsigned char const * next = NULL;
         3783  +        int ch;
         3784  +        unsigned char clen = 0;
         3785  +        char escChar[3] = {'\\',0,0};
         3786  +        enum { UBLen = 8 };
         3787  +        char ubuf[UBLen];
         3788  +        int rc = 0;
         3789  +        rc = f(state, "\"", 1 );
         3790  +        for( ; (pos < end) && (0 == rc); pos += clen )
         3791  +        {
         3792  +            ch = cson_utf8Read(pos, end, &next);
         3793  +            if( 0 == ch ) break;
         3794  +            assert( next > pos );
         3795  +            clen = next - pos;
         3796  +            assert( clen );
         3797  +            if( 1 == clen )
         3798  +            { /* ASCII */
         3799  +                assert( *pos == ch );
         3800  +                escChar[1] = 0;
         3801  +                switch(ch)
         3802  +                {
         3803  +                  case '\t': escChar[1] = 't'; break;
         3804  +                  case '\r': escChar[1] = 'r'; break;
         3805  +                  case '\n': escChar[1] = 'n'; break;
         3806  +                  case '\f': escChar[1] = 'f'; break;
         3807  +                  case '\b': escChar[1] = 'b'; break;
         3808  +                  case '/':
         3809  +      /*
         3810  +        Regarding escaping of forward-slashes. See the main exchange below...
         3811  +
         3812  +        --------------
         3813  +        From: Douglas Crockford <douglas@crockford.com>
         3814  +        To: Stephan Beal <sgbeal@googlemail.com>
         3815  +        Subject: Re: Is escaping of forward slashes required?
         3816  +
         3817  +        It is allowed, not required. It is allowed so that JSON can be safely
         3818  +        embedded in HTML, which can freak out when seeing strings containing
         3819  +        "</". JSON tolerates "<\/" for this reason.
         3820  +
         3821  +        On 4/8/2011 2:09 PM, Stephan Beal wrote:
         3822  +        > Hello, Jsonites,
         3823  +        >
         3824  +        > i'm a bit confused on a small grammatic detail of JSON:
         3825  +        >
         3826  +        > if i'm reading the grammar chart on http://www.json.org/ correctly,
         3827  +        > forward slashes (/) are supposed to be escaped in JSON. However, the
         3828  +        > JSON class provided with my browsers (Chrome and FF, both of which i
         3829  +        > assume are fairly standards/RFC-compliant) do not escape such characters.
         3830  +        >
         3831  +        > Is backslash-escaping forward slashes required? If so, what is the
         3832  +        > justification for it? (i ask because i find it unnecessary and hard to
         3833  +        > look at.)
         3834  +        --------------
         3835  +      */
         3836  +                      if( escapeFwdSlash ) escChar[1] = '/';
         3837  +                      break;
         3838  +                  case '\\': escChar[1] = '\\'; break;
         3839  +                  case '"': escChar[1] = '"'; break;
         3840  +                  default: break;
         3841  +                }
         3842  +                if( escChar[1])
         3843  +                {
         3844  +                    rc = f(state, escChar, 2);
         3845  +                }
         3846  +                else
         3847  +                {
         3848  +                    rc = f(state, (char const *)pos, clen);
         3849  +                }
         3850  +                continue;
         3851  +            }
         3852  +            else
         3853  +            { /* UTF: transform it to \uXXXX */
         3854  +                memset(ubuf,0,UBLen);
         3855  +                rc = sprintf(ubuf, "\\u%04x",ch);
         3856  +                if( rc != 6 )
         3857  +                {
         3858  +                    rc = cson_rc.RangeError;
         3859  +                    break;
         3860  +                }
         3861  +                rc = f( state, ubuf, 6 );
         3862  +                continue;
         3863  +            }
         3864  +        }
         3865  +        if( 0 == rc )
         3866  +        {
         3867  +            rc = f(state, "\"", 1 );
         3868  +        }
         3869  +        return rc;
         3870  +    }
         3871  +}
         3872  +
         3873  +int cson_object_iter_init( cson_object const * obj, cson_object_iterator * iter )
         3874  +{
         3875  +    if( ! obj || !iter ) return cson_rc.ArgError;
         3876  +    else
         3877  +    {
         3878  +        iter->obj = obj;
         3879  +        iter->pos = 0;
         3880  +        return 0;
         3881  +    }
         3882  +}
         3883  +
         3884  +cson_kvp * cson_object_iter_next( cson_object_iterator * iter )
         3885  +{
         3886  +    if( ! iter || !iter->obj ) return NULL;
         3887  +    else if( iter->pos >= iter->obj->kvp.count ) return NULL;
         3888  +    else
         3889  +    {
         3890  +        cson_kvp * rc = iter->obj->kvp.list[iter->pos++];
         3891  +        while( (NULL==rc) && (iter->pos < iter->obj->kvp.count))
         3892  +        {
         3893  +            rc = iter->obj->kvp.list[iter->pos++];
         3894  +        }
         3895  +        return rc;
         3896  +    }
         3897  +}
         3898  +
         3899  +static int cson_output_null( cson_data_dest_f f, void * state )
         3900  +{
         3901  +    if( !f ) return cson_rc.ArgError;
         3902  +    else
         3903  +    {
         3904  +        return f(state, "null", 4);
         3905  +    }
         3906  +}
         3907  +
         3908  +static int cson_output_bool( cson_value const * src, cson_data_dest_f f, void * state )
         3909  +{
         3910  +    if( !f ) return cson_rc.ArgError;
         3911  +    else
         3912  +    {
         3913  +        char const v = cson_value_get_bool(src);
         3914  +        return f(state, v ? "true" : "false", v ? 4 : 5);
         3915  +    }
         3916  +}
         3917  +
         3918  +static int cson_output_integer( cson_value const * src, cson_data_dest_f f, void * state )
         3919  +{
         3920  +    if( !f ) return cson_rc.ArgError;
         3921  +    else if( !cson_value_is_integer(src) ) return cson_rc.TypeError;
         3922  +    else
         3923  +    {
         3924  +        enum { BufLen = 100 };
         3925  +        char b[BufLen];
         3926  +        int rc;
         3927  +        memset( b, 0, BufLen );
         3928  +        rc = sprintf( b, "%"CSON_INT_T_PFMT, cson_value_get_integer(src) )
         3929  +            /* Reminder: snprintf() is C99 */
         3930  +            ;
         3931  +        return ( rc<=0 )
         3932  +            ? cson_rc.RangeError
         3933  +            : f( state, b, (unsigned int)rc )
         3934  +            ;
         3935  +    }
         3936  +}
         3937  +
         3938  +static int cson_output_double( cson_value const * src, cson_data_dest_f f, void * state )
         3939  +{
         3940  +    if( !f ) return cson_rc.ArgError;
         3941  +    else if( !cson_value_is_double(src) ) return cson_rc.TypeError;
         3942  +    else
         3943  +    {
         3944  +        enum { BufLen = 128 /* this must be relatively large or huge
         3945  +                               doubles can cause us to overrun here,
         3946  +                               resulting in stack-smashing errors.
         3947  +                            */};
         3948  +        char b[BufLen];
         3949  +        int rc;
         3950  +        memset( b, 0, BufLen );
         3951  +        rc = sprintf( b, "%"CSON_DOUBLE_T_PFMT, cson_value_get_double(src) )
         3952  +            /* Reminder: snprintf() is C99 */
         3953  +            ;
         3954  +        if( rc<=0 ) return cson_rc.RangeError;
         3955  +        else if(1)
         3956  +        { /* Strip trailing zeroes before passing it on... */
         3957  +            unsigned int urc = (unsigned int)rc;
         3958  +            char * pos = b + urc - 1;
         3959  +            for( ; ('0' == *pos) && urc && (*(pos-1) != '.'); --pos, --urc )
         3960  +            {
         3961  +                *pos = 0;
         3962  +            }
         3963  +            assert(urc && *pos);
         3964  +            return f( state, b, urc );
         3965  +        }
         3966  +        else
         3967  +        {
         3968  +            unsigned int urc = (unsigned int)rc;
         3969  +            return f( state, b, urc );
         3970  +        }
         3971  +        return 0;
         3972  +    }
         3973  +}
         3974  +
         3975  +static int cson_output_string( cson_value const * src, char escapeFwdSlash, cson_data_dest_f f, void * state )
         3976  +{
         3977  +    if( !f ) return cson_rc.ArgError;
         3978  +    else if( ! cson_value_is_string(src) ) return cson_rc.TypeError;
         3979  +    else
         3980  +    {
         3981  +        cson_string const * str = cson_value_get_string(src);
         3982  +        assert( NULL != str );
         3983  +        return cson_str_to_json(cson_string_cstr(str), str->length, escapeFwdSlash, f, state);
         3984  +    }
         3985  +}
         3986  +
         3987  +
         3988  +/**
         3989  +   Outputs indention spacing to f().
         3990  +
         3991  +   blanks: (0)=no indentation, (1)=1 TAB per/level, (>1)=n spaces/level
         3992  +
         3993  +   depth is the current depth of the output tree, and determines how much
         3994  +   indentation to generate.
         3995  +
         3996  +   If blanks is 0 this is a no-op. Returns non-0 on error, and the
         3997  +   error code will always come from f().
         3998  +*/
         3999  +static int cson_output_indent( cson_data_dest_f f, void * state,
         4000  +                               unsigned char blanks, unsigned int depth )
         4001  +{
         4002  +    if( 0 == blanks ) return 0;
         4003  +    else
         4004  +    {
         4005  +#if 0
         4006  +        /* FIXME: stuff the indention into the buffer and make a single
         4007  +           call to f().
         4008  +        */
         4009  +        enum { BufLen = 200 };
         4010  +        char buf[BufLen];
         4011  +#endif
         4012  +        unsigned int i;
         4013  +        unsigned int x;
         4014  +        char const ch = (1==blanks) ? '\t' : ' ';
         4015  +        int rc = f(state, "\n", 1 );
         4016  +        for( i = 0; (i < depth) && (0 == rc); ++i )
         4017  +        {
         4018  +            for( x = 0; (x < blanks) && (0 == rc); ++x )
         4019  +            {
         4020  +                rc = f(state, &ch, 1);
         4021  +            }
         4022  +        }
         4023  +        return rc;
         4024  +    }
         4025  +}
         4026  +
         4027  +static int cson_output_array( cson_value const * src, cson_data_dest_f f, void * state,
         4028  +                              cson_output_opt const * fmt, unsigned int level );
         4029  +static int cson_output_object( cson_value const * src, cson_data_dest_f f, void * state,
         4030  +                               cson_output_opt const * fmt, unsigned int level );
         4031  +/**
         4032  +   Main cson_output() implementation. Dispatches to a different impl depending
         4033  +   on src->api->typeID.
         4034  +
         4035  +   Returns 0 on success.
         4036  +*/
         4037  +static int cson_output_impl( cson_value const * src, cson_data_dest_f f, void * state,
         4038  +                             cson_output_opt const * fmt, unsigned int level )
         4039  +{
         4040  +    if( ! src || !f || !src->api ) return cson_rc.ArgError;
         4041  +    else
         4042  +    {
         4043  +        int rc = 0;
         4044  +        assert(fmt);
         4045  +        switch( src->api->typeID )
         4046  +        {
         4047  +          case CSON_TYPE_UNDEF:
         4048  +          case CSON_TYPE_NULL:
         4049  +              rc = cson_output_null(f, state);
         4050  +              break;
         4051  +          case CSON_TYPE_BOOL:
         4052  +              rc = cson_output_bool(src, f, state);
         4053  +              break;
         4054  +          case CSON_TYPE_INTEGER:
         4055  +              rc = cson_output_integer(src, f, state);
         4056  +              break;
         4057  +          case CSON_TYPE_DOUBLE:
         4058  +              rc = cson_output_double(src, f, state);
         4059  +              break;
         4060  +          case CSON_TYPE_STRING:
         4061  +              rc = cson_output_string(src, fmt->escapeForwardSlashes, f, state);
         4062  +              break;
         4063  +          case CSON_TYPE_ARRAY:
         4064  +              rc = cson_output_array( src, f, state, fmt, level );
         4065  +              break;
         4066  +          case CSON_TYPE_OBJECT:
         4067  +              rc = cson_output_object( src, f, state, fmt, level );
         4068  +              break;
         4069  +          default:
         4070  +              rc = cson_rc.TypeError;
         4071  +              break;
         4072  +        }
         4073  +        return rc;
         4074  +    }
         4075  +}
         4076  +
         4077  +
         4078  +static int cson_output_array( cson_value const * src, cson_data_dest_f f, void * state,
         4079  +                              cson_output_opt const * fmt, unsigned int level )
         4080  +{
         4081  +    if( !src || !f || !fmt ) return cson_rc.ArgError;
         4082  +    else if( ! cson_value_is_array(src) ) return cson_rc.TypeError;
         4083  +    else if( level > fmt->maxDepth ) return cson_rc.RangeError;
         4084  +    else
         4085  +    {
         4086  +        int rc;
         4087  +        unsigned int i;
         4088  +        cson_value const * v;
         4089  +        char doIndent = fmt->indentation ? 1 : 0;
         4090  +        cson_array const * ar = cson_value_get_array(src);
         4091  +        assert( NULL != ar );
         4092  +        if( 0 == ar->list.count )
         4093  +        {
         4094  +            return f(state, "[]", 2 );
         4095  +        }
         4096  +        else if( (1 == ar->list.count) && !fmt->indentSingleMemberValues ) doIndent = 0;
         4097  +        rc = f(state, "[", 1);
         4098  +        ++level;
         4099  +        if( doIndent )
         4100  +        {
         4101  +            rc = cson_output_indent( f, state, fmt->indentation, level );
         4102  +        }
         4103  +        for( i = 0; (i < ar->list.count) && (0 == rc); ++i )
         4104  +        {
         4105  +            v = ar->list.list[i];
         4106  +            if( v )
         4107  +            {
         4108  +                rc = cson_output_impl( v, f, state, fmt, level );
         4109  +            }
         4110  +            else
         4111  +            {
         4112  +                rc = cson_output_null( f, state );
         4113  +            }
         4114  +            if( 0 == rc )
         4115  +            {
         4116  +                if(i < (ar->list.count-1))
         4117  +                {
         4118  +                    rc = f(state, ",", 1);
         4119  +                    if( 0 == rc )
         4120  +                    {
         4121  +                        rc = doIndent
         4122  +                            ? cson_output_indent( f, state, fmt->indentation, level )
         4123  +                            : f( state, " ", 1 );
         4124  +                    }
         4125  +                }
         4126  +            }
         4127  +        }
         4128  +        --level;
         4129  +        if( doIndent && (0 == rc) )
         4130  +        {
         4131  +            rc = cson_output_indent( f, state, fmt->indentation, level );
         4132  +        }
         4133  +        return (0 == rc)
         4134  +            ? f(state, "]", 1)
         4135  +            : rc;
         4136  +    }
         4137  +}
         4138  +
         4139  +static int cson_output_object( cson_value const * src, cson_data_dest_f f, void * state,
         4140  +                               cson_output_opt const * fmt, unsigned int level )
         4141  +{
         4142  +    if( !src || !f || !fmt ) return cson_rc.ArgError;
         4143  +    else if( ! cson_value_is_object(src) ) return cson_rc.TypeError;
         4144  +    else if( level > fmt->maxDepth ) return cson_rc.RangeError;
         4145  +    else
         4146  +    {
         4147  +        int rc;
         4148  +        unsigned int i;
         4149  +        cson_kvp const * kvp;
         4150  +        char doIndent = fmt->indentation ? 1 : 0;
         4151  +        cson_object const * obj = cson_value_get_object(src);
         4152  +        assert( (NULL != obj) && (NULL != fmt));
         4153  +        if( 0 == obj->kvp.count )
         4154  +        {
         4155  +            return f(state, "{}", 2 );
         4156  +        }
         4157  +        else if( (1 == obj->kvp.count) && !fmt->indentSingleMemberValues ) doIndent = 0;
         4158  +        rc = f(state, "{", 1);
         4159  +        ++level;
         4160  +        if( doIndent )
         4161  +        {
         4162  +            rc = cson_output_indent( f, state, fmt->indentation, level );
         4163  +        }
         4164  +        for( i = 0; (i < obj->kvp.count) && (0 == rc); ++i )
         4165  +        {
         4166  +            kvp = obj->kvp.list[i];
         4167  +            if( kvp && kvp->key )
         4168  +            {
         4169  +                cson_string const * sKey = cson_value_get_string(kvp->key);
         4170  +                char const * cKey = cson_string_cstr(sKey);
         4171  +                rc = cson_str_to_json(cKey, sKey->length,
         4172  +                                      fmt->escapeForwardSlashes, f, state);
         4173  +                if( 0 == rc )
         4174  +                {
         4175  +                    rc = fmt->addSpaceAfterColon
         4176  +                        ? f(state, ": ", 2 )
         4177  +                        : f(state, ":", 1 )
         4178  +                        ;
         4179  +                }
         4180  +                if( 0 == rc)
         4181  +                {
         4182  +                    rc = ( kvp->value )
         4183  +                        ? cson_output_impl( kvp->value, f, state, fmt, level )
         4184  +                        : cson_output_null( f, state );
         4185  +                }
         4186  +            }
         4187  +            else
         4188  +            {
         4189  +                assert( 0 && "Possible internal error." );
         4190  +                continue /* internal error? */;
         4191  +            }
         4192  +            if( 0 == rc )
         4193  +            {
         4194  +                if(i < (obj->kvp.count-1))
         4195  +                {
         4196  +                    rc = f(state, ",", 1);
         4197  +                    if( 0 == rc )
         4198  +                    {
         4199  +                        rc = doIndent
         4200  +                            ? cson_output_indent( f, state, fmt->indentation, level )
         4201  +                            : f( state, " ", 1 );
         4202  +                    }
         4203  +                }
         4204  +            }
         4205  +        }
         4206  +        --level;
         4207  +        if( doIndent && (0 == rc) )
         4208  +        {
         4209  +            rc = cson_output_indent( f, state, fmt->indentation, level );
         4210  +        }
         4211  +        return (0 == rc)
         4212  +            ? f(state, "}", 1)
         4213  +            : rc;
         4214  +    }
         4215  +}
         4216  +
         4217  +int cson_output( cson_value const * src, cson_data_dest_f f,
         4218  +                 void * state, cson_output_opt const * fmt )
         4219  +{
         4220  +    int rc;
         4221  +    if(! fmt ) fmt = &cson_output_opt_empty;
         4222  +    rc = cson_output_impl(src, f, state, fmt, 0 );
         4223  +    if( (0 == rc) && fmt->addNewline )
         4224  +    {
         4225  +        rc = f(state, "\n", 1);
         4226  +    }
         4227  +    return rc;
         4228  +}
         4229  +
         4230  +int cson_data_dest_FILE( void * state, void const * src, unsigned int n )
         4231  +{
         4232  +    if( ! state ) return cson_rc.ArgError;
         4233  +    else if( !src || !n ) return 0;
         4234  +    else
         4235  +    {
         4236  +        return ( 1 == fwrite( src, n, 1, (FILE*) state ) )
         4237  +            ? 0
         4238  +            : cson_rc.IOError;
         4239  +    }
         4240  +}
         4241  +
         4242  +int cson_output_FILE( cson_value const * src, FILE * dest, cson_output_opt const * fmt )
         4243  +{
         4244  +    int rc = 0;
         4245  +    if( fmt )
         4246  +    {
         4247  +        rc = cson_output( src, cson_data_dest_FILE, dest, fmt );
         4248  +    }
         4249  +    else
         4250  +    {
         4251  +        /* We normally want a newline on FILE output. */
         4252  +        cson_output_opt opt = cson_output_opt_empty;
         4253  +        opt.addNewline = 1;
         4254  +        rc = cson_output( src, cson_data_dest_FILE, dest, &opt );
         4255  +    }
         4256  +    if( 0 == rc )
         4257  +    {
         4258  +        fflush( dest );
         4259  +    }
         4260  +    return rc;
         4261  +}
         4262  +
         4263  +int cson_output_filename( cson_value const * src, char const * dest, cson_output_opt const * fmt )
         4264  +{
         4265  +    if( !src || !dest ) return cson_rc.ArgError;
         4266  +    else
         4267  +    {
         4268  +        FILE * f = fopen(dest,"wb");
         4269  +        if( !f ) return cson_rc.IOError;
         4270  +        else
         4271  +        {
         4272  +            int const rc = cson_output_FILE( src, f, fmt );
         4273  +            fclose(f);
         4274  +            return rc;
         4275  +        }
         4276  +    }
         4277  +}
         4278  +
         4279  +int cson_parse_filename( cson_value ** tgt, char const * src,
         4280  +                         cson_parse_opt const * opt, cson_parse_info * err )
         4281  +{
         4282  +    if( !src || !tgt ) return cson_rc.ArgError;
         4283  +    else
         4284  +    {
         4285  +        FILE * f = fopen(src, "r");
         4286  +        if( !f ) return cson_rc.IOError;
         4287  +        else
         4288  +        {
         4289  +            int const rc = cson_parse_FILE( tgt, f, opt, err );
         4290  +            fclose(f);
         4291  +            return rc;
         4292  +        }
         4293  +    }
         4294  +}
         4295  +
         4296  +/** Internal type to hold state for a JSON input string.
         4297  + */
         4298  +typedef struct cson_data_source_StringSource_
         4299  +{
         4300  +    /** Start of input string. */
         4301  +    char const * str;
         4302  +    /** Current iteration position. Must initially be == str. */
         4303  +    char const * pos;
         4304  +    /** Logical EOF, one-past-the-end of str. */
         4305  +    char const * end;
         4306  +}  cson_data_source_StringSource_t;
         4307  +
         4308  +/**
         4309  +   A cson_data_source_f() implementation which requires the state argument
         4310  +   to be a properly populated (cson_data_source_StringSource_t*).
         4311  +*/
         4312  +static int cson_data_source_StringSource( void * state, void * dest, unsigned int * n )
         4313  +{
         4314  +    if( !state || !n || !dest ) return cson_rc.ArgError;
         4315  +    else if( !*n ) return 0 /* ignore this */;
         4316  +    else
         4317  +    {
         4318  +        unsigned int i;
         4319  +        cson_data_source_StringSource_t * ss = (cson_data_source_StringSource_t*) state;
         4320  +        unsigned char * tgt = (unsigned char *)dest;
         4321  +        for( i = 0; (i < *n) && (ss->pos < ss->end); ++i, ++ss->pos, ++tgt )
         4322  +        {
         4323  +            *tgt = *ss->pos;
         4324  +        }
         4325  +        *n = i;
         4326  +        return 0;
         4327  +    }
         4328  +}
         4329  +
         4330  +int cson_parse_string( cson_value ** tgt, char const * src, unsigned int len,
         4331  +                       cson_parse_opt const * opt, cson_parse_info * err )
         4332  +{
         4333  +    if( ! tgt || !src ) return cson_rc.ArgError;
         4334  +    else if( !*src || (len<2/*2==len of {} and []*/) ) return cson_rc.RangeError;
         4335  +    else
         4336  +    {
         4337  +        cson_data_source_StringSource_t ss;
         4338  +        ss.str = ss.pos = src;
         4339  +        ss.end = src + len;
         4340  +        return cson_parse( tgt, cson_data_source_StringSource, &ss, opt, err );
         4341  +    }
         4342  +
         4343  +}
         4344  +
         4345  +int cson_parse_buffer( cson_value ** tgt,
         4346  +                       cson_buffer const * buf,
         4347  +                       cson_parse_opt const * opt,
         4348  +                       cson_parse_info * err )
         4349  +{
         4350  +    return ( !tgt || !buf || !buf->mem || !buf->used )
         4351  +        ? cson_rc.ArgError
         4352  +        : cson_parse_string( tgt, (char const *)buf->mem,
         4353  +                             buf->used, opt, err );
         4354  +}
         4355  +
         4356  +int cson_buffer_reserve( cson_buffer * buf, cson_size_t n )
         4357  +{
         4358  +    if( ! buf ) return cson_rc.ArgError;
         4359  +    else if( 0 == n )
         4360  +    {
         4361  +        cson_free(buf->mem, "cson_buffer::mem");
         4362  +        *buf = cson_buffer_empty;
         4363  +        return 0;
         4364  +    }
         4365  +    else if( buf->capacity >= n )
         4366  +    {
         4367  +        return 0;
         4368  +    }
         4369  +    else
         4370  +    {
         4371  +        unsigned char * x = (unsigned char *)realloc( buf->mem, n );
         4372  +        if( ! x ) return cson_rc.AllocError;
         4373  +        memset( x + buf->used, 0, n - buf->used );
         4374  +        buf->mem = x;
         4375  +        buf->capacity = n;
         4376  +        ++buf->timesExpanded;
         4377  +        return 0;
         4378  +    }
         4379  +}
         4380  +
         4381  +cson_size_t cson_buffer_fill( cson_buffer * buf, char c )
         4382  +{
         4383  +    if( !buf || !buf->capacity || !buf->mem ) return 0;
         4384  +    else
         4385  +    {
         4386  +        memset( buf->mem, c, buf->capacity );
         4387  +        return buf->capacity;
         4388  +    }
         4389  +}
         4390  +
         4391  +/**
         4392  +   cson_data_dest_f() implementation, used by cson_output_buffer().
         4393  +
         4394  +   arg MUST be a (cson_buffer*). This function appends n bytes at
         4395  +   position arg->used, expanding the buffer as necessary.
         4396  +*/
         4397  +static int cson_data_dest_cson_buffer( void * arg, void const * data_, unsigned int n )
         4398  +{
         4399  +    if( ! arg || (n<0) ) return cson_rc.ArgError;
         4400  +    else if( ! n ) return 0;
         4401  +    else
         4402  +    {
         4403  +        cson_buffer * sb = (cson_buffer*)arg;
         4404  +        char const * data = (char const *)data_;
         4405  +        cson_size_t npos = sb->used + n;
         4406  +        unsigned int i;
         4407  +        if( npos >= sb->capacity )
         4408  +        {
         4409  +            const cson_size_t oldCap = sb->capacity;
         4410  +            const cson_size_t asz = npos * 2;
         4411  +            if( asz < npos ) return cson_rc.ArgError; /* overflow */
         4412  +            else if( 0 != cson_buffer_reserve( sb, asz ) ) return cson_rc.AllocError;
         4413  +            assert( (sb->capacity > oldCap) && "Internal error in memory buffer management!" );
         4414  +            /* make sure it gets NULL terminated. */
         4415  +            memset( sb->mem + oldCap, 0, (sb->capacity - oldCap) );
         4416  +        }
         4417  +        for( i = 0; i < n; ++i, ++sb->used )
         4418  +        {
         4419  +            sb->mem[sb->used] = data[i];
         4420  +        }
         4421  +        return 0;
         4422  +    }
         4423  +}
         4424  +
         4425  +
         4426  +int cson_output_buffer( cson_value const * v, cson_buffer * buf,
         4427  +                        cson_output_opt const * opt )
         4428  +{
         4429  +    int rc = cson_output( v, cson_data_dest_cson_buffer, buf, opt );
         4430  +    if( 0 == rc )
         4431  +    { /* Ensure that the buffer is null-terminated. */
         4432  +        rc = cson_buffer_reserve( buf, buf->used + 1 );
         4433  +        if( 0 == rc )
         4434  +        {
         4435  +            buf->mem[buf->used] = 0;
         4436  +        }
         4437  +    }
         4438  +    return rc;
         4439  +}
         4440  +
         4441  +/** @internal
         4442  +
         4443  +Tokenizes an input string on a given separator. Inputs are:
         4444  +
         4445  +- (inp) = is a pointer to the pointer to the start of the input.
         4446  +
         4447  +- (separator) = the separator character
         4448  +
         4449  +- (end) = a pointer to NULL. i.e. (*end == NULL)
         4450  +
         4451  +This function scans *inp for the given separator char or a NULL char.
         4452  +Successive separators at the start of *inp are skipped. The effect is
         4453  +that, when this function is called in a loop, all neighboring
         4454  +separators are ignored. e.g. the string "aa.bb...cc" will tokenize to
         4455  +the list (aa,bb,cc) if the separator is '.' and to (aa.,...cc) if the
         4456  +separator is 'b'.
         4457  +
         4458  +Returns 0 (false) if it finds no token, else non-0 (true).
         4459  +
         4460  +Output:
         4461  +
         4462  +- (*inp) will be set to the first character of the next token.
         4463  +
         4464  +- (*end) will point to the one-past-the-end point of the token.
         4465  +
         4466  +If (*inp == *end) then the end of the string has been reached
         4467  +without finding a token.
         4468  +
         4469  +Post-conditions:
         4470  +
         4471  +- (*end == *inp) if no token is found.
         4472  +
         4473  +- (*end > *inp) if a token is found.
         4474  +
         4475  +It is intolerant of NULL values for (inp, end), and will assert() in
         4476  +debug builds if passed NULL as either parameter.
         4477  +*/
         4478  +static char cson_next_token( char const ** inp, char separator, char const ** end )
         4479  +{
         4480  +    char const * pos = NULL;
         4481  +    assert( inp && end && *inp );
         4482  +    if( *inp == *end ) return 0;
         4483  +    pos = *inp;
         4484  +    if( !*pos )
         4485  +    {
         4486  +        *end = pos;
         4487  +        return 0;
         4488  +    }
         4489  +    for( ; *pos && (*pos == separator); ++pos) { /* skip preceeding splitters */ }
         4490  +    *inp = pos;
         4491  +    for( ; *pos && (*pos != separator); ++pos) { /* find next splitter */ }
         4492  +    *end = pos;
         4493  +    return (pos > *inp) ? 1 : 0;
         4494  +}
         4495  +
         4496  +int cson_object_fetch_sub2( cson_object const * obj, cson_value ** tgt, char const * path )
         4497  +{
         4498  +    if( ! obj || !path ) return cson_rc.ArgError;
         4499  +    else if( !*path || !*(1+path) ) return cson_rc.RangeError;
         4500  +    else return cson_object_fetch_sub(obj, tgt, path+1, *path);
         4501  +}
         4502  +
         4503  +int cson_object_fetch_sub( cson_object const * obj, cson_value ** tgt, char const * path, char sep )
         4504  +{
         4505  +    if( ! obj || !path ) return cson_rc.ArgError;
         4506  +    else if( !*path || !sep ) return cson_rc.RangeError;
         4507  +    else
         4508  +    {
         4509  +        char const * beg = path;
         4510  +        char const * end = NULL;
         4511  +        int rc;
         4512  +        unsigned int i, len;
         4513  +        unsigned int tokenCount = 0;
         4514  +        cson_value * cv = NULL;
         4515  +        cson_object const * curObj = obj;
         4516  +        enum { BufSize = 128 };
         4517  +        char buf[BufSize];
         4518  +        memset( buf, 0, BufSize );
         4519  +        rc = cson_rc.RangeError;
         4520  +
         4521  +        while( cson_next_token( &beg, sep, &end ) )
         4522  +        {
         4523  +            if( beg == end ) break;
         4524  +            else
         4525  +            {
         4526  +                ++tokenCount;
         4527  +                beg = end;
         4528  +                end = NULL;
         4529  +            }
         4530  +        }
         4531  +        if( 0 == tokenCount ) return cson_rc.RangeError;
         4532  +        beg = path;
         4533  +        end = NULL;
         4534  +        for( i = 0; i < tokenCount; ++i, beg=end, end=NULL )
         4535  +        {
         4536  +            rc = cson_next_token( &beg, sep, &end );
         4537  +            assert( 1 == rc );
         4538  +            assert( beg != end );
         4539  +            assert( end > beg );
         4540  +            len = end - beg;
         4541  +            if( len > (BufSize-1) ) return cson_rc.RangeError;
         4542  +            memset( buf, 0, len + 1 );
         4543  +            memcpy( buf, beg, len );
         4544  +            buf[len] = 0;
         4545  +            cv = cson_object_get( curObj, buf );
         4546  +            if( NULL == cv ) return cson_rc.NotFoundError;
         4547  +            else if( i == (tokenCount-1) )
         4548  +            {
         4549  +                if(tgt) *tgt = cv;
         4550  +                return 0;
         4551  +            }
         4552  +            else if( cson_value_is_object(cv) )
         4553  +            {
         4554  +                curObj = cson_value_get_object(cv);
         4555  +                assert((NULL != curObj) && "Detected mis-management of internal memory!");
         4556  +            }
         4557  +            /* TODO: arrays. Requires numeric parsing for the index. */
         4558  +            else
         4559  +            {
         4560  +                return cson_rc.NotFoundError;
         4561  +            }
         4562  +        }
         4563  +        assert( i == tokenCount );
         4564  +        return cson_rc.NotFoundError;
         4565  +    }
         4566  +}
         4567  +
         4568  +cson_value * cson_object_get_sub( cson_object const * obj, char const * path, char sep )
         4569  +{
         4570  +    cson_value * v = NULL;
         4571  +    cson_object_fetch_sub( obj, &v, path, sep );
         4572  +    return v;
         4573  +}
         4574  +
         4575  +cson_value * cson_object_get_sub2( cson_object const * obj, char const * path )
         4576  +{
         4577  +    cson_value * v = NULL;
         4578  +    cson_object_fetch_sub2( obj, &v, path );
         4579  +    return v;
         4580  +}
         4581  +
         4582  +static cson_value * cson_value_clone_array( cson_value const * orig )
         4583  +{
         4584  +    unsigned int i = 0;
         4585  +    cson_array const * asrc = cson_value_get_array( orig );
         4586  +    unsigned int alen = cson_array_length_get( asrc );
         4587  +    cson_value * destV = NULL;
         4588  +    cson_array * destA = NULL;
         4589  +    assert( orig && asrc );
         4590  +    destV = cson_value_new_array();
         4591  +    if( NULL == destV ) return NULL;
         4592  +    destA = cson_value_get_array( destV );
         4593  +    assert( destA );
         4594  +    if( 0 != cson_array_reserve( destA, alen ) )
         4595  +    {
         4596  +        cson_value_free( destV );
         4597  +        return NULL;
         4598  +    }
         4599  +    for( ; i < alen; ++i )
         4600  +    {
         4601  +        cson_value * ch = cson_array_get( asrc, i );
         4602  +        if( NULL != ch )
         4603  +        {
         4604  +            cson_value * cl = cson_value_clone( ch );
         4605  +            if( NULL == cl )
         4606  +            {
         4607  +                cson_value_free( destV );
         4608  +                return NULL;
         4609  +            }
         4610  +            if( 0 != cson_array_set( destA, i, cl ) )
         4611  +            {
         4612  +                cson_value_free( cl );
         4613  +                cson_value_free( destV );
         4614  +                return NULL;
         4615  +            }
         4616  +        }
         4617  +    }
         4618  +    return destV;
         4619  +}
         4620  +
         4621  +static cson_value * cson_value_clone_object( cson_value const * orig )
         4622  +{
         4623  +    cson_object const * src = cson_value_get_object( orig );
         4624  +    cson_value * destV = NULL;
         4625  +    cson_object * dest = NULL;
         4626  +    cson_kvp const * kvp = NULL;
         4627  +    cson_object_iterator iter = cson_object_iterator_empty;
         4628  +    assert( orig && src );
         4629  +    if( 0 != cson_object_iter_init( src, &iter ) )
         4630  +    {
         4631  +        return NULL;
         4632  +    }
         4633  +    destV = cson_value_new_object();
         4634  +    if( NULL == destV ) return NULL;
         4635  +    dest = cson_value_get_object( destV );
         4636  +    assert( dest );
         4637  +    if( src->kvp.count > cson_kvp_list_reserve( &dest->kvp, src->kvp.count ) ){
         4638  +        cson_value_free( destV );
         4639  +        return NULL;
         4640  +    }
         4641  +    while( (kvp = cson_object_iter_next( &iter )) )
         4642  +    {
         4643  +        /*
         4644  +          FIXME: refcount the keys! We first need a setter which takes
         4645  +          a cson_string or cson_value key type.
         4646  +         */
         4647  +        cson_value * key = NULL;
         4648  +        cson_value * val = NULL;
         4649  +        key = cson_value_clone(kvp->key);
         4650  +        val = key ? cson_value_clone( kvp->value ) : NULL;
         4651  +        if( ! key || !val ){
         4652  +            cson_value_free(key);
         4653  +            cson_value_free(val);
         4654  +            cson_value_free(destV);
         4655  +            return NULL;
         4656  +        }
         4657  +        assert( CSON_STR(key) );
         4658  +        if( 0 != cson_object_set_s( dest, CSON_STR(key), val ) )
         4659  +        {
         4660  +            cson_value_free(key);
         4661  +            cson_value_free(val);
         4662  +            cson_value_free(destV);
         4663  +            return NULL;
         4664  +        }
         4665  +    }
         4666  +    return destV;
         4667  +}
         4668  +
         4669  +cson_value * cson_value_clone( cson_value const * orig )
         4670  +{
         4671  +    if( NULL == orig ) return NULL;
         4672  +    else
         4673  +    {
         4674  +        switch( orig->api->typeID )
         4675  +        {
         4676  +          case CSON_TYPE_UNDEF:
         4677  +              assert(0 && "This should never happen.");
         4678  +              return NULL;
         4679  +          case CSON_TYPE_NULL:
         4680  +              return cson_value_null();
         4681  +          case CSON_TYPE_BOOL:
         4682  +              return cson_value_new_bool( cson_value_get_bool( orig ) );
         4683  +          case CSON_TYPE_INTEGER:
         4684  +              return cson_value_new_integer( cson_value_get_integer( orig ) );
         4685  +              break;
         4686  +          case CSON_TYPE_DOUBLE:
         4687  +              return cson_value_new_double( cson_value_get_double( orig ) );
         4688  +              break;
         4689  +          case CSON_TYPE_STRING: {
         4690  +              cson_string const * str = cson_value_get_string( orig );
         4691  +              return cson_value_new_string( cson_string_cstr( str ),
         4692  +                                            cson_string_length_bytes( str ) );
         4693  +          }
         4694  +          case CSON_TYPE_ARRAY:
         4695  +              return cson_value_clone_array( orig );
         4696  +          case CSON_TYPE_OBJECT:
         4697  +              return cson_value_clone_object( orig );
         4698  +        }
         4699  +        assert( 0 && "We can't get this far." );
         4700  +        return NULL;
         4701  +    }
         4702  +}
         4703  +
         4704  +cson_value * cson_string_value(cson_string const * s)
         4705  +{
         4706  +#define MT CSON_SPECIAL_VALUES[CSON_VAL_STR_EMPTY]
         4707  +    return s
         4708  +        ? ((s==MT.value) ? &MT : CSON_VCAST(s))
         4709  +        : NULL;
         4710  +#undef MT
         4711  +}
         4712  +
         4713  +cson_value * cson_object_value(cson_object const * s)
         4714  +{
         4715  +    return s
         4716  +        ? CSON_VCAST(s)
         4717  +        : NULL;
         4718  +}
         4719  +
         4720  +
         4721  +cson_value * cson_array_value(cson_array const * s)
         4722  +{
         4723  +    return s
         4724  +        ? CSON_VCAST(s)
         4725  +        : NULL;
         4726  +}
         4727  +
         4728  +void cson_free_object(cson_object *x)
         4729  +{
         4730  +    if(x) cson_value_free(cson_object_value(x));
         4731  +}
         4732  +void cson_free_array(cson_array *x)
         4733  +{
         4734  +    if(x) cson_value_free(cson_array_value(x));
         4735  +}
         4736  +
         4737  +void cson_free_string(cson_string const *x)
         4738  +{
         4739  +    if(x) cson_value_free(cson_string_value(x));
         4740  +}
         4741  +void cson_free_value(cson_value *x)
         4742  +{
         4743  +    cson_value_free(x);
         4744  +}
         4745  +
         4746  +
         4747  +#if 0
         4748  +/* i'm not happy with this... */
         4749  +char * cson_pod_to_string( cson_value const * orig )
         4750  +{
         4751  +    if( ! orig ) return NULL;
         4752  +    else
         4753  +    {
         4754  +        enum { BufSize = 64 };
         4755  +        char * v = NULL;
         4756  +        switch( orig->api->typeID )
         4757  +        {
         4758  +          case CSON_TYPE_BOOL: {
         4759  +              char const bv = cson_value_get_bool(orig);
         4760  +              v = cson_strdup( bv ? "true" : "false",
         4761  +                               bv ? 4 : 5 );
         4762  +              break;
         4763  +          }
         4764  +          case CSON_TYPE_UNDEF:
         4765  +          case CSON_TYPE_NULL: {
         4766  +              v = cson_strdup( "null", 4 );
         4767  +              break;
         4768  +          }
         4769  +          case CSON_TYPE_STRING: {
         4770  +              cson_string const * jstr = cson_value_get_string(orig);
         4771  +              unsigned const int slen = cson_string_length_bytes( jstr );
         4772  +              assert( NULL != jstr );
         4773  +              v = cson_strdup( cson_string_cstr( jstr ), slen ); 
         4774  +              break;
         4775  +          }
         4776  +          case CSON_TYPE_INTEGER: {
         4777  +              char buf[BufSize] = {0};
         4778  +              if( 0 < sprintf( v, "%"CSON_INT_T_PFMT, cson_value_get_integer(orig)) )
         4779  +              {
         4780  +                  v = cson_strdup( buf, strlen(buf) );
         4781  +              }
         4782  +              break;
         4783  +          }
         4784  +          case CSON_TYPE_DOUBLE: {
         4785  +              char buf[BufSize] = {0};
         4786  +              if( 0 < sprintf( v, "%"CSON_DOUBLE_T_PFMT, cson_value_get_double(orig)) )
         4787  +              {
         4788  +                  v = cson_strdup( buf, strlen(buf) );
         4789  +              }
         4790  +              break;
         4791  +          }
         4792  +          default:
         4793  +              break;
         4794  +        }
         4795  +        return v;
         4796  +    }
         4797  +}
         4798  +#endif
         4799  +
         4800  +#if 0
         4801  +/* i'm not happy with this... */
         4802  +char * cson_pod_to_string( cson_value const * orig )
         4803  +{
         4804  +    if( ! orig ) return NULL;
         4805  +    else
         4806  +    {
         4807  +        enum { BufSize = 64 };
         4808  +        char * v = NULL;
         4809  +        switch( orig->api->typeID )
         4810  +        {
         4811  +          case CSON_TYPE_BOOL: {
         4812  +              char const bv = cson_value_get_bool(orig);
         4813  +              v = cson_strdup( bv ? "true" : "false",
         4814  +                               bv ? 4 : 5 );
         4815  +              break;
         4816  +          }
         4817  +          case CSON_TYPE_UNDEF:
         4818  +          case CSON_TYPE_NULL: {
         4819  +              v = cson_strdup( "null", 4 );
         4820  +              break;
         4821  +          }
         4822  +          case CSON_TYPE_STRING: {
         4823  +              cson_string const * jstr = cson_value_get_string(orig);
         4824  +              unsigned const int slen = cson_string_length_bytes( jstr );
         4825  +              assert( NULL != jstr );
         4826  +              v = cson_strdup( cson_string_cstr( jstr ), slen ); 
         4827  +              break;
         4828  +          }
         4829  +          case CSON_TYPE_INTEGER: {
         4830  +              char buf[BufSize] = {0};
         4831  +              if( 0 < sprintf( v, "%"CSON_INT_T_PFMT, cson_value_get_integer(orig)) )
         4832  +              {
         4833  +                  v = cson_strdup( buf, strlen(buf) );
         4834  +              }
         4835  +              break;
         4836  +          }
         4837  +          case CSON_TYPE_DOUBLE: {
         4838  +              char buf[BufSize] = {0};
         4839  +              if( 0 < sprintf( v, "%"CSON_DOUBLE_T_PFMT, cson_value_get_double(orig)) )
         4840  +              {
         4841  +                  v = cson_strdup( buf, strlen(buf) );
         4842  +              }
         4843  +              break;
         4844  +          }
         4845  +          default:
         4846  +              break;
         4847  +        }
         4848  +        return v;
         4849  +    }
         4850  +}
         4851  +#endif
         4852  +
         4853  +unsigned int cson_value_msize(cson_value const * v)
         4854  +{
         4855  +    if(!v) return 0;
         4856  +    else if( cson_value_is_builtin(v) ) return 0;
         4857  +    else {
         4858  +        unsigned int rc = sizeof(cson_value);
         4859  +        assert(NULL != v->api);
         4860  +        switch(v->api->typeID){
         4861  +          case CSON_TYPE_INTEGER:
         4862  +              assert( v != &CSON_SPECIAL_VALUES[CSON_VAL_INT_0]);
         4863  +              rc += sizeof(cson_int_t);
         4864  +              break;
         4865  +          case CSON_TYPE_DOUBLE:
         4866  +              assert( v != &CSON_SPECIAL_VALUES[CSON_VAL_DBL_0]);
         4867  +              rc += sizeof(cson_double_t);
         4868  +              break;
         4869  +          case CSON_TYPE_STRING:
         4870  +              rc += sizeof(cson_string)
         4871  +                  + CSON_STR(v)->length + 1/*NUL*/;
         4872  +              break;
         4873  +          case CSON_TYPE_ARRAY:{
         4874  +              cson_array const * ar = CSON_ARRAY(v);
         4875  +              cson_value_list const * li;
         4876  +              unsigned int i = 0;
         4877  +              assert( NULL != ar );
         4878  +              li = &ar->list;
         4879  +              rc += sizeof(cson_array)
         4880  +                  + (li->alloced * sizeof(cson_value *));
         4881  +              for( ; i < li->count; ++i ){
         4882  +                  cson_value const * e = ar->list.list[i];
         4883  +                  if( e ) rc += cson_value_msize( e );
         4884  +              }
         4885  +              break;
         4886  +          }
         4887  +          case CSON_TYPE_OBJECT:{
         4888  +              cson_object const * obj = CSON_OBJ(v);
         4889  +              unsigned int i = 0;
         4890  +              cson_kvp_list const * kl;
         4891  +              assert(NULL != obj);
         4892  +              kl = &obj->kvp;
         4893  +              rc += sizeof(cson_object)
         4894  +                  + (kl->alloced * sizeof(cson_kvp*));
         4895  +              for( ; i < kl->count; ++i ){
         4896  +                  cson_kvp const * kvp = kl->list[i];
         4897  +                  assert(NULL != kvp);
         4898  +                  rc += cson_value_msize(kvp->key);
         4899  +                  rc += cson_value_msize(kvp->value);
         4900  +              }
         4901  +              break;
         4902  +          }
         4903  +          case CSON_TYPE_UNDEF:
         4904  +          case CSON_TYPE_NULL:
         4905  +          case CSON_TYPE_BOOL:
         4906  +              assert( 0 && "Should have been caught by is-builtin check!" );
         4907  +              break;
         4908  +          default:
         4909  +              assert(0 && "Invalid typeID!");
         4910  +              return 0;
         4911  +
         4912  +        }
         4913  +        return rc;
         4914  +    }
         4915  +}
         4916  +
         4917  +int cson_object_merge( cson_object * dest, cson_object const * src, int flags ){
         4918  +    cson_object_iterator iter = cson_object_iterator_empty;
         4919  +    int rc;
         4920  +    char const replace = (flags & CSON_MERGE_REPLACE);
         4921  +    char const recurse = !(flags & CSON_MERGE_NO_RECURSE);
         4922  +    cson_kvp const * kvp;
         4923  +    if((!dest || !src) || (dest==src)) return cson_rc.ArgError;
         4924  +    rc = cson_object_iter_init( src, &iter );
         4925  +    if(rc) return rc;
         4926  +    while( (kvp = cson_object_iter_next(&iter) ) )
         4927  +    {
         4928  +        cson_string * key = cson_kvp_key(kvp);
         4929  +        cson_value * val = cson_kvp_value(kvp);
         4930  +        cson_value * check = cson_object_get_s( dest, key );
         4931  +        if(!check){
         4932  +            cson_object_set_s( dest, key, val );
         4933  +            continue;
         4934  +        }
         4935  +        else if(!replace && !recurse) continue;
         4936  +        else if(replace && !recurse){
         4937  +            cson_object_set_s( dest, key, val );
         4938  +            continue;
         4939  +        }
         4940  +        else if( recurse ){
         4941  +            if( cson_value_is_object(check) &&
         4942  +                cson_value_is_object(val) ){
         4943  +                rc = cson_object_merge( cson_value_get_object(check),
         4944  +                                        cson_value_get_object(val),
         4945  +                                        flags );
         4946  +                if(rc) return rc;
         4947  +                else continue;
         4948  +            }
         4949  +            else continue;
         4950  +        }
         4951  +        else continue;
         4952  +    }
         4953  +    return 0;
         4954  +}
         4955  +
         4956  +#if defined(__cplusplus)
         4957  +} /*extern "C"*/
         4958  +#endif
         4959  +
         4960  +#undef MARKER
         4961  +#undef CSON_OBJECT_PROPS_SORT
         4962  +#undef CSON_OBJECT_PROPS_SORT_USE_LENGTH
         4963  +#undef CSON_CAST
         4964  +#undef CSON_INT
         4965  +#undef CSON_DBL
         4966  +#undef CSON_STR
         4967  +#undef CSON_OBJ
         4968  +#undef CSON_ARRAY
         4969  +#undef CSON_VCAST
         4970  +#undef CSON_MALLOC_IMPL
         4971  +#undef CSON_FREE_IMPL
         4972  +#undef CSON_REALLOC_IMPL
         4973  +/* end file ./cson.c */
         4974  +/* begin file ./cson_lists.h */
         4975  +/* Auto-generated from cson_list.h. Edit at your own risk! */
         4976  +unsigned int cson_value_list_reserve( cson_value_list * self, unsigned int n )
         4977  +{
         4978  +    if( !self ) return 0;
         4979  +    else if(0 == n)
         4980  +    {
         4981  +        if(0 == self->alloced) return 0;
         4982  +        cson_free(self->list, "cson_value_list_reserve");
         4983  +        self->list = NULL;
         4984  +        self->alloced = self->count = 0;
         4985  +        return 0;
         4986  +    }
         4987  +    else if( self->alloced >= n )
         4988  +    {
         4989  +        return self->alloced;
         4990  +    }
         4991  +    else
         4992  +    {
         4993  +        size_t const sz = sizeof(cson_value *) * n;
         4994  +        cson_value * * m = (cson_value **)cson_realloc( self->list, sz, "cson_value_list_reserve" );
         4995  +        if( ! m ) return self->alloced;
         4996  +
         4997  +        memset( m + self->alloced, 0, (sizeof(cson_value *)*(n-self->alloced)));
         4998  +        self->alloced = n;
         4999  +        self->list = m;
         5000  +        return n;
         5001  +    }
         5002  +}
         5003  +int cson_value_list_append( cson_value_list * self, cson_value * cp )
         5004  +{
         5005  +    if( !self || !cp ) return cson_rc.ArgError;
         5006  +    else if( self->alloced > cson_value_list_reserve(self, self->count+1) )
         5007  +    {
         5008  +        return cson_rc.AllocError;
         5009  +    }
         5010  +    else
         5011  +    {
         5012  +        self->list[self->count++] = cp;
         5013  +        return 0;
         5014  +    }
         5015  +}
         5016  +int cson_value_list_visit( cson_value_list * self,
         5017  +
         5018  +                        int (*visitor)(cson_value * obj, void * visitorState ),
         5019  +
         5020  +
         5021  +
         5022  +                        void * visitorState )
         5023  +{
         5024  +    int rc = cson_rc.ArgError;
         5025  +    if( self && visitor )
         5026  +    {
         5027  +        unsigned int i = 0;
         5028  +        for( rc = 0; (i < self->count) && (0 == rc); ++i )
         5029  +        {
         5030  +
         5031  +            cson_value * obj = self->list[i];
         5032  +
         5033  +
         5034  +
         5035  +            if(obj) rc = visitor( obj, visitorState );
         5036  +        }
         5037  +    }
         5038  +    return rc;
         5039  +}
         5040  +void cson_value_list_clean( cson_value_list * self,
         5041  +
         5042  +                         void (*cleaner)(cson_value * obj)
         5043  +
         5044  +
         5045  +
         5046  +                         )
         5047  +{
         5048  +    if( self && cleaner && self->count )
         5049  +    {
         5050  +        unsigned int i = 0;
         5051  +        for( ; i < self->count; ++i )
         5052  +        {
         5053  +
         5054  +            cson_value * obj = self->list[i];
         5055  +
         5056  +
         5057  +
         5058  +            if(obj) cleaner(obj);
         5059  +        }
         5060  +    }
         5061  +    cson_value_list_reserve(self,0);
         5062  +}
         5063  +unsigned int cson_kvp_list_reserve( cson_kvp_list * self, unsigned int n )
         5064  +{
         5065  +    if( !self ) return 0;
         5066  +    else if(0 == n)
         5067  +    {
         5068  +        if(0 == self->alloced) return 0;
         5069  +        cson_free(self->list, "cson_kvp_list_reserve");
         5070  +        self->list = NULL;
         5071  +        self->alloced = self->count = 0;
         5072  +        return 0;
         5073  +    }
         5074  +    else if( self->alloced >= n )
         5075  +    {
         5076  +        return self->alloced;
         5077  +    }
         5078  +    else
         5079  +    {
         5080  +        size_t const sz = sizeof(cson_kvp *) * n;
         5081  +        cson_kvp * * m = (cson_kvp **)cson_realloc( self->list, sz, "cson_kvp_list_reserve" );
         5082  +        if( ! m ) return self->alloced;
         5083  +
         5084  +        memset( m + self->alloced, 0, (sizeof(cson_kvp *)*(n-self->alloced)));
         5085  +        self->alloced = n;
         5086  +        self->list = m;
         5087  +        return n;
         5088  +    }
         5089  +}
         5090  +int cson_kvp_list_append( cson_kvp_list * self, cson_kvp * cp )
         5091  +{
         5092  +    if( !self || !cp ) return cson_rc.ArgError;
         5093  +    else if( self->alloced > cson_kvp_list_reserve(self, self->count+1) )
         5094  +    {
         5095  +        return cson_rc.AllocError;
         5096  +    }
         5097  +    else
         5098  +    {
         5099  +        self->list[self->count++] = cp;
         5100  +        return 0;
         5101  +    }
         5102  +}
         5103  +int cson_kvp_list_visit( cson_kvp_list * self,
         5104  +
         5105  +                        int (*visitor)(cson_kvp * obj, void * visitorState ),
         5106  +
         5107  +
         5108  +
         5109  +                        void * visitorState )
         5110  +{
         5111  +    int rc = cson_rc.ArgError;
         5112  +    if( self && visitor )
         5113  +    {
         5114  +        unsigned int i = 0;
         5115  +        for( rc = 0; (i < self->count) && (0 == rc); ++i )
         5116  +        {
         5117  +
         5118  +            cson_kvp * obj = self->list[i];
         5119  +
         5120  +
         5121  +
         5122  +            if(obj) rc = visitor( obj, visitorState );
         5123  +        }
         5124  +    }
         5125  +    return rc;
         5126  +}
         5127  +void cson_kvp_list_clean( cson_kvp_list * self,
         5128  +
         5129  +                         void (*cleaner)(cson_kvp * obj)
         5130  +
         5131  +
         5132  +
         5133  +                         )
         5134  +{
         5135  +    if( self && cleaner && self->count )
         5136  +    {
         5137  +        unsigned int i = 0;
         5138  +        for( ; i < self->count; ++i )
         5139  +        {
         5140  +
         5141  +            cson_kvp * obj = self->list[i];
         5142  +
         5143  +
         5144  +
         5145  +            if(obj) cleaner(obj);
         5146  +        }
         5147  +    }
         5148  +    cson_kvp_list_reserve(self,0);
         5149  +}
         5150  +/* end file ./cson_lists.h */
         5151  +/* begin file ./cson_sqlite3.c */
         5152  +/** @file cson_sqlite3.c
         5153  +
         5154  +This file contains the implementation code for the cson
         5155  +sqlite3-to-JSON API.
         5156  +
         5157  +License: the same as the cson core library.
         5158  +
         5159  +Author: Stephan Beal (http://wanderinghorse.net/home/stephan)
         5160  +*/
         5161  +#if CSON_ENABLE_SQLITE3 /* we do this here for the sake of the amalgamation build */
         5162  +#include <assert.h>
         5163  +#include <string.h> /* strlen() */
         5164  +
         5165  +#if 0
         5166  +#include <stdio.h>
         5167  +#define MARKER if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf
         5168  +#else
         5169  +#define MARKER if(0) printf
         5170  +#endif
         5171  +
         5172  +#if defined(__cplusplus)
         5173  +extern "C" {
         5174  +#endif
         5175  +
         5176  +cson_value * cson_sqlite3_column_to_value( sqlite3_stmt * st, int col )
         5177  +{
         5178  +    if( ! st ) return NULL;
         5179  +    else
         5180  +    {
         5181  +#if 0
         5182  +        sqlite3_value * val = sqlite3_column_type(st,col);
         5183  +        int const vtype = val ? sqlite3_value_type(val) : -1;
         5184  +        if( ! val ) return cson_value_null();
         5185  +#else
         5186  +        int const vtype = sqlite3_column_type(st,col);
         5187  +#endif
         5188  +        switch( vtype )
         5189  +        {
         5190  +          case SQLITE_NULL:
         5191  +              return cson_value_null();
         5192  +          case SQLITE_INTEGER:
         5193  +              /* FIXME: for large integers fall back to Double instead. */
         5194  +              return cson_value_new_integer( (cson_int_t) sqlite3_column_int64(st, col)  );
         5195  +          case SQLITE_FLOAT:
         5196  +              return cson_value_new_double( sqlite3_column_double(st, col) );
         5197  +          case SQLITE_BLOB: /* arguably fall through... */
         5198  +          case SQLITE_TEXT: {
         5199  +              char const * str = (char const *)sqlite3_column_text(st,col);
         5200  +              return cson_value_new_string(str, str ? strlen(str) : 0);
         5201  +          }
         5202  +          default:
         5203  +              return NULL;
         5204  +        }
         5205  +    }
         5206  +}
         5207  +
         5208  +cson_value * cson_sqlite3_column_names( sqlite3_stmt * st )
         5209  +{
         5210  +    cson_value * aryV = NULL;
         5211  +    cson_array * ary = NULL;
         5212  +    char const * colName = NULL;
         5213  +    int i = 0;
         5214  +    int rc = 0;
         5215  +    int colCount = 0;
         5216  +    assert(st);
         5217  +    colCount = sqlite3_column_count(st);
         5218  +    if( colCount <= 0 ) return NULL;
         5219  +    
         5220  +    aryV = cson_value_new_array();
         5221  +    if( ! aryV ) return NULL;
         5222  +    ary = cson_value_get_array(aryV);
         5223  +    assert(ary);
         5224  +    for( i = 0; (0 ==rc) && (i < colCount); ++i )
         5225  +    {
         5226  +        colName = sqlite3_column_name( st, i );
         5227  +        if( ! colName ) rc = cson_rc.AllocError;
         5228  +        else
         5229  +        {
         5230  +            rc = cson_array_set( ary, (unsigned int)i,
         5231  +                    cson_value_new_string(colName, strlen(colName)) );
         5232  +        }
         5233  +    }
         5234  +    if( 0 == rc ) return aryV;
         5235  +    else
         5236  +    {
         5237  +        cson_value_free(aryV);
         5238  +        return NULL;
         5239  +    }
         5240  +}
         5241  +
         5242  +
         5243  +cson_value * cson_sqlite3_row_to_object2( sqlite3_stmt * st,
         5244  +                                          cson_array * colNames )
         5245  +{
         5246  +    cson_value * rootV = NULL;
         5247  +    cson_object * root = NULL;
         5248  +    cson_string * colName = NULL;
         5249  +    int i = 0;
         5250  +    int rc = 0;
         5251  +    cson_value * currentValue = NULL;
         5252  +    int const colCount = sqlite3_column_count(st);
         5253  +    if( !colCount || (colCount>cson_array_length_get(colNames)) ) {
         5254  +        return NULL;
         5255  +    }
         5256  +    rootV = cson_value_new_object();
         5257  +    if(!rootV) return NULL;
         5258  +    root = cson_value_get_object(rootV);
         5259  +    for( i = 0; i < colCount; ++i )
         5260  +    {
         5261  +        colName = cson_value_get_string( cson_array_get( colNames, i ) );
         5262  +        if( ! colName ) goto error;
         5263  +        currentValue = cson_sqlite3_column_to_value(st,i);
         5264  +        if( ! currentValue ) currentValue = cson_value_null();
         5265  +        rc = cson_object_set_s( root, colName, currentValue );
         5266  +        if( 0 != rc )
         5267  +        {
         5268  +            cson_value_free( currentValue );
         5269  +            goto error;
         5270  +        }
         5271  +    }
         5272  +    goto end;
         5273  +    error:
         5274  +    cson_value_free( rootV );
         5275  +    rootV = NULL;
         5276  +    end:
         5277  +    return rootV;
         5278  +}
         5279  +
         5280  +
         5281  +cson_value * cson_sqlite3_row_to_object( sqlite3_stmt * st )
         5282  +{
         5283  +#if 0
         5284  +    cson_value * arV = cson_sqlite3_column_names(st);
         5285  +    cson_array * ar = NULL;
         5286  +    cson_value * rc = NULL;
         5287  +    if(!arV) return NULL;
         5288  +    ar = cson_value_get_array(arV);
         5289  +    assert( NULL != ar );
         5290  +    rc = cson_sqlite3_row_to_object2(st, ar);
         5291  +    cson_value_free(arV);
         5292  +    return rc;
         5293  +#else
         5294  +    cson_value * rootV = NULL;
         5295  +    cson_object * root = NULL;
         5296  +    char const * colName = NULL;
         5297  +    int i = 0;
         5298  +    int rc = 0;
         5299  +    cson_value * currentValue = NULL;
         5300  +    int const colCount = sqlite3_column_count(st);
         5301  +    if( !colCount ) return NULL;
         5302  +    rootV = cson_value_new_object();
         5303  +    if(!rootV) return NULL;
         5304  +    root = cson_value_get_object(rootV);
         5305  +    for( i = 0; i < colCount; ++i )
         5306  +    {
         5307  +        colName = sqlite3_column_name( st, i );
         5308  +        if( ! colName ) goto error;
         5309  +        currentValue = cson_sqlite3_column_to_value(st,i);
         5310  +        if( ! currentValue ) currentValue = cson_value_null();
         5311  +        rc = cson_object_set( root, colName, currentValue );
         5312  +        if( 0 != rc )
         5313  +        {
         5314  +            cson_value_free( currentValue );
         5315  +            goto error;
         5316  +        }
         5317  +    }
         5318  +    goto end;
         5319  +    error:
         5320  +    cson_value_free( rootV );
         5321  +    rootV = NULL;
         5322  +    end:
         5323  +    return rootV;
         5324  +#endif
         5325  +}
         5326  +
         5327  +cson_value * cson_sqlite3_row_to_array( sqlite3_stmt * st )
         5328  +{
         5329  +    cson_value * aryV = NULL;
         5330  +    cson_array * ary = NULL;
         5331  +    int i = 0;
         5332  +    int rc = 0;
         5333  +    int const colCount = sqlite3_column_count(st);
         5334  +    if( ! colCount ) return NULL;
         5335  +    aryV = cson_value_new_array();
         5336  +    if( ! aryV ) return NULL;
         5337  +    ary = cson_value_get_array(aryV);
         5338  +    rc = cson_array_reserve(ary, (unsigned int) colCount );
         5339  +    if( 0 != rc ) goto error;
         5340  +
         5341  +    for( i = 0; i < colCount; ++i ){
         5342  +        cson_value * elem = cson_sqlite3_column_to_value(st,i);
         5343  +        if( ! elem ) goto error;
         5344  +        rc = cson_array_append(ary,elem);
         5345  +        if(0!=rc)
         5346  +        {
         5347  +            cson_value_free( elem );
         5348  +            goto end;
         5349  +        }
         5350  +    }
         5351  +    goto end;
         5352  +    error:
         5353  +    cson_value_free(aryV);
         5354  +    aryV = NULL;
         5355  +    end:
         5356  +    return aryV;
         5357  +}
         5358  +
         5359  +    
         5360  +/**
         5361  +    Internal impl of cson_sqlite3_stmt_to_json() when the 'fat'
         5362  +    parameter is non-0.
         5363  +*/
         5364  +static int cson_sqlite3_stmt_to_json_fat( sqlite3_stmt * st, cson_value ** tgt )
         5365  +{
         5366  +#define RETURN(RC) { if(rootV) cson_value_free(rootV); return RC; }
         5367  +    if( ! tgt || !st ) return cson_rc.ArgError;
         5368  +    else
         5369  +    {
         5370  +        cson_value * rootV = NULL;
         5371  +        cson_object * root = NULL;
         5372  +        cson_value * colsV = NULL;
         5373  +        cson_array * cols = NULL;
         5374  +        cson_value * rowsV = NULL;
         5375  +        cson_array * rows = NULL;
         5376  +        cson_value * objV = NULL;
         5377  +        int rc = 0;
         5378  +        int const colCount = sqlite3_column_count(st);
         5379  +        if( colCount <= 0 ) return cson_rc.ArgError;
         5380  +        rootV = cson_value_new_object();
         5381  +        if( ! rootV ) return cson_rc.AllocError;
         5382  +        colsV = cson_sqlite3_column_names(st);
         5383  +        if( ! colsV )
         5384  +        {
         5385  +            cson_value_free( rootV );
         5386  +            RETURN(cson_rc.AllocError);
         5387  +        }
         5388  +        cols = cson_value_get_array(colsV);
         5389  +        assert(NULL != cols);
         5390  +        root = cson_value_get_object(rootV);
         5391  +        rc = cson_object_set( root, "columns", colsV );
         5392  +        if( rc )
         5393  +        {
         5394  +            cson_value_free( colsV );
         5395  +            RETURN(rc);
         5396  +        }
         5397  +        rowsV = cson_value_new_array();
         5398  +        if( ! rowsV ) RETURN(cson_rc.AllocError);
         5399  +        rc = cson_object_set( root, "rows", rowsV );
         5400  +        if( rc )
         5401  +        {
         5402  +            cson_value_free( rowsV );
         5403  +            RETURN(rc);
         5404  +        }
         5405  +        rows = cson_value_get_array(rowsV);
         5406  +        assert(rows);
         5407  +        while( SQLITE_ROW == sqlite3_step(st) )
         5408  +        {
         5409  +            objV = cson_sqlite3_row_to_object2(st, cols);
         5410  +            if( ! objV ) RETURN(cson_rc.UnknownError);
         5411  +            rc = cson_array_append( rows, objV );
         5412  +            if( rc )
         5413  +            {
         5414  +                cson_value_free( objV );
         5415  +                RETURN(rc);
         5416  +            }
         5417  +        }
         5418  +        *tgt = rootV;
         5419  +        return 0;
         5420  +    }
         5421  +#undef RETURN
         5422  +}
         5423  +
         5424  +/**
         5425  +    Internal impl of cson_sqlite3_stmt_to_json() when the 'fat'
         5426  +    parameter is 0.
         5427  +*/
         5428  +static int cson_sqlite3_stmt_to_json_slim( sqlite3_stmt * st, cson_value ** tgt )
         5429  +{
         5430  +#define RETURN(RC) { if(rootV) cson_value_free(rootV); return RC; }
         5431  +    if( ! tgt || !st ) return cson_rc.ArgError;
         5432  +    else
         5433  +    {
         5434  +        cson_value * rootV = NULL;
         5435  +        cson_object * root = NULL;
         5436  +        cson_value * aryV = NULL;
         5437  +        cson_array * ary = NULL;
         5438  +        cson_value * rowsV = NULL;
         5439  +        cson_array * rows = NULL;
         5440  +        int rc = 0;
         5441  +        int const colCount = sqlite3_column_count(st);
         5442  +        if( colCount <= 0 ) return cson_rc.ArgError;
         5443  +        rootV = cson_value_new_object();
         5444  +        if( ! rootV ) return cson_rc.AllocError;
         5445  +        aryV = cson_sqlite3_column_names(st);
         5446  +        if( ! aryV )
         5447  +        {
         5448  +            cson_value_free( rootV );
         5449  +            RETURN(cson_rc.AllocError);
         5450  +        }
         5451  +        root = cson_value_get_object(rootV);
         5452  +        rc = cson_object_set( root, "columns", aryV );
         5453  +        if( rc )
         5454  +        {
         5455  +            cson_value_free( aryV );
         5456  +            RETURN(rc);
         5457  +        }
         5458  +        aryV = NULL;
         5459  +        ary = NULL;
         5460  +        rowsV = cson_value_new_array();
         5461  +        if( ! rowsV ) RETURN(cson_rc.AllocError);
         5462  +        rc = cson_object_set( root, "rows", rowsV );
         5463  +        if( 0 != rc )
         5464  +        {
         5465  +            cson_value_free( rowsV );
         5466  +            RETURN(rc);
         5467  +        }
         5468  +        rows = cson_value_get_array(rowsV);
         5469  +        assert(rows);
         5470  +        while( SQLITE_ROW == sqlite3_step(st) )
         5471  +        {
         5472  +            aryV = cson_sqlite3_row_to_array(st);
         5473  +            if( ! aryV ) RETURN(cson_rc.UnknownError);
         5474  +            rc = cson_array_append( rows, aryV );
         5475  +            if( 0 != rc )
         5476  +            {
         5477  +                cson_value_free( aryV );
         5478  +                RETURN(rc);
         5479  +            }
         5480  +        }
         5481  +        *tgt = rootV;
         5482  +        return 0;
         5483  +    }
         5484  +#undef RETURN
         5485  +}
         5486  +
         5487  +int cson_sqlite3_stmt_to_json( sqlite3_stmt * st, cson_value ** tgt, char fat )
         5488  +{
         5489  +    return fat
         5490  +        ? cson_sqlite3_stmt_to_json_fat(st,tgt)
         5491  +        : cson_sqlite3_stmt_to_json_slim(st,tgt)
         5492  +        ;
         5493  +}
         5494  +
         5495  +int cson_sqlite3_sql_to_json( sqlite3 * db, cson_value ** tgt, char const * sql, char fat )
         5496  +{
         5497  +    if( !db || !tgt || !sql || !*sql ) return cson_rc.ArgError;
         5498  +    else
         5499  +    {
         5500  +        sqlite3_stmt * st = NULL;
         5501  +        int rc = sqlite3_prepare_v2( db, sql, -1, &st, NULL );
         5502  +        if( 0 != rc ) return cson_rc.IOError /* FIXME: Better error code? */;
         5503  +        rc = cson_sqlite3_stmt_to_json( st, tgt, fat );
         5504  +        sqlite3_finalize( st );
         5505  +        return rc;
         5506  +    }        
         5507  +}
         5508  +
         5509  +#if defined(__cplusplus)
         5510  +} /*extern "C"*/
         5511  +#endif
         5512  +#undef MARKER
         5513  +#endif /* CSON_ENABLE_SQLITE3 */
         5514  +/* end file ./cson_sqlite3.c */
         5515  +#endif /* FOSSIL_ENABLE_JSON */

Added src/cson_amalgamation.h.

            1  +#ifdef FOSSIL_ENABLE_JSON
            2  +/* auto-generated! Do not edit! */
            3  +/* begin file include/wh/cson/cson.h */
            4  +#if !defined(WANDERINGHORSE_NET_CSON_H_INCLUDED)
            5  +#define WANDERINGHORSE_NET_CSON_H_INCLUDED 1
            6  +
            7  +/*#include <stdint.h> C99: fixed-size int types. */
            8  +#include <stdio.h> /* FILE decl */
            9  +
           10  +/** @page page_cson cson JSON API
           11  +
           12  +cson (pronounced "season") is an object-oriented C API for generating
           13  +and consuming JSON (http://www.json.org) data.
           14  +
           15  +Its main claim to fame is that it can parse JSON from, and output it
           16  +to, damned near anywhere. The i/o routines use a callback function to
           17  +fetch/emit JSON data, allowing clients to easily plug in their own
           18  +implementations. Implementations are provided for string- and
           19  +FILE-based i/o.
           20  +
           21  +Project home page: http://fossil.wanderinghorse.net/repos/cson
           22  +
           23  +Author: Stephan Beal (http://www.wanderinghorse.net/home/stephan/)
           24  +
           25  +License: Dual Public Domain/MIT
           26  +
           27  +The full license text is at the bottom of the main header file
           28  +(cson.h).
           29  +
           30  +Examples of how to use the library are scattered throughout
           31  +the API documentation, in the test.c file in the source repo,
           32  +and in the wiki on the project's home page.
           33  +
           34  +
           35  +*/
           36  +
           37  +#if defined(__cplusplus)
           38  +extern "C" {
           39  +#endif
           40  +
           41  +#if defined(_WIN32) || defined(_WIN64)
           42  +#  define CSON_ENABLE_UNIX 0
           43  +#else
           44  +#  define CSON_ENABLE_UNIX 1
           45  +#endif
           46  +
           47  +
           48  +/** @typedef some_long_int_type cson_int_t
           49  +
           50  +Typedef for JSON-like integer types. This is (long long) where feasible,
           51  +otherwise (long).
           52  +*/
           53  +#if (__STDC_VERSION__ >= 199901L) || (HAVE_LONG_LONG == 1)
           54  +typedef long long cson_int_t;
           55  +#define CSON_INT_T_SFMT "lld"
           56  +#define CSON_INT_T_PFMT "lld"
           57  +#else 
           58  +typedef long cson_int_t;
           59  +#define CSON_INT_T_SFMT "ld"
           60  +#define CSON_INT_T_PFMT "ld"
           61  +#endif
           62  +
           63  +/** @typedef double_or_long_double cson_double_t
           64  +
           65  +This is the type of double value used by the library.
           66  +It is only lightly tested with long double, and when using
           67  +long double the memory requirements for such values goes
           68  +up.
           69  +*/
           70  +#if 0
           71  +typedef long double cson_double_t;
           72  +#define CSON_DOUBLE_T_SFMT "Lf"
           73  +#define CSON_DOUBLE_T_PFMT "Lf"
           74  +#else
           75  +typedef double cson_double_t;
           76  +#define CSON_DOUBLE_T_SFMT "f"
           77  +#define CSON_DOUBLE_T_PFMT "f"
           78  +#endif
           79  +
           80  +/** @def CSON_INT_T_SFMT
           81  +
           82  +scanf()-compatible format token for cson_int_t.
           83  +*/
           84  +
           85  +/** @def CSON_INT_T_PFMT
           86  +
           87  +printf()-compatible format token for cson_int_t.
           88  +*/
           89  +
           90  +
           91  +/** @def CSON_DOUBLE_T_SFMT
           92  +
           93  +scanf()-compatible format token for cson_double_t.
           94  +*/
           95  +
           96  +/** @def CSON_DOUBLE_T_PFMT
           97  +
           98  +printf()-compatible format token for cson_double_t.
           99  +*/
          100  +
          101  +typedef struct cson_value cson_value;
          102  +
          103  +/** @struct cson_value
          104  +   
          105  +   The core value type of this API. It is opaque to clients, and
          106  +   only the cson public API should be used for setting or
          107  +   inspecting their values.
          108  +
          109  +   This class is opaque because stack-based usage can easily cause
          110  +   leaks if one does not intimately understand the underlying
          111  +   internal memory management (which sometimes changes).
          112  +
          113  +   It is (as of 20110323) legal to insert a given value instance into
          114  +   multiple containers (they will share ownership using reference
          115  +   counting) as long as those insertions do not cause cycles. However,
          116  +   be very aware that such value re-use uses a reference to the
          117  +   original copy, meaning that if its value is changed once, it is
          118  +   changed everywhere. Also beware that multi-threaded write
          119  +   operations on such references leads to undefined behaviour.
          120  +   
          121  +   PLEASE read the ACHTUNGEN below...
          122  +
          123  +   ACHTUNG #1:
          124  +
          125  +   cson_values MUST NOT form cycles (e.g. via object or array
          126  +   entries).
          127  +
          128  +   Not abiding th Holy Law Of No Cycles will lead to double-frees and
          129  +   the like (i.e. undefined behaviour, likely crashes due to infinite
          130  +   recursion or stepping on invalid (freed) pointers).
          131  +
          132  +   ACHTUNG #2:
          133  +   
          134  +   ALL cson_values returned as non-const cson_value pointers from any
          135  +   public functions in the cson API are to be treated as if they are
          136  +   heap-allocated, and MUST be freed by client by doing ONE of:
          137  +   
          138  +   - Passing it to cson_value_free().
          139  +   
          140  +   - Adding it to an Object or Array, in which case the object/array
          141  +   takes over ownership. As of 20110323, a value may be inserted into
          142  +   a single container multiple times, or into multiple containers,
          143  +   in which case they all share ownership (via reference counting)
          144  +   of the original value (meaning any changes to it are visible in
          145  +   all references to it).
          146  +   
          147  +   Each call to cson_value_new_xxx() MUST eventually be followed up
          148  +   by one of those options.
          149  +   
          150  +   Some cson_value_new_XXX() implementations do not actually allocate
          151  +   memory, but this is an internal implementation detail. Client code
          152  +   MUST NOT rely on this behaviour and MUST treat each object
          153  +   returned by such a function as if it was a freshly-allocated copy
          154  +   (even if their pointer addresses are the same).
          155  +   
          156  +   ACHTUNG #3:
          157  +
          158  +   Note that ACHTUNG #2 tells us that we must always free (or transfer
          159  +   ownership of) all pointers returned bycson_value_new_xxx(), but
          160  +   that two calls to (e.g.) cson_value_new_bool(1) will (or might)
          161  +   return the same address. The client must not rely on the
          162  +   "non-allocation" policy of such special cases, and must pass each
          163  +   returned value to cson_value_free(), even if two of them have the
          164  +   same address.  Some special values (e.g. null, true, false, integer
          165  +   0, double 0.0, and empty strings) use shared copies and in other
          166  +   places reference counting is used internally to figure out when it
          167  +   is safe to destroy an object.
          168  +
          169  +
          170  +   @see cson_value_new_array()
          171  +   @see cson_value_new_object()
          172  +   @see cson_value_new_string()
          173  +   @see cson_value_new_integer()
          174  +   @see cson_value_new_double()
          175  +   @see cson_value_new_bool()
          176  +   @see cson_value_true()
          177  +   @see cson_value_false()
          178  +   @see cson_value_null()
          179  +   @see cson_value_free()
          180  +*/
          181  +
          182  +/** @var cson_rc
          183  +
          184  +   This object defines the error codes used by cson.
          185  +
          186  +   Library routines which return int values almost always return a
          187  +   value from this structure. None of the members in this struct have
          188  +   published values except for the OK member, which has the value 0.
          189  +   All other values might be incidentally defined where clients
          190  +   can see them, but the numbers might change from release to
          191  +   release, so clients should only use the symbolic names.
          192  +
          193  +   Client code is expected to access these values via the shared
          194  +   cson_rc object, and use them as demonstrated here:
          195  +
          196  +   @code
          197  +   int rc = cson_some_func(...);
          198  +   if( 0 == rc ) {...success...}
          199  +   else if( cson_rc.ArgError == rc ) { ... some argument was wrong ... }
          200  +   else if( cson_rc.AllocError == rc ) { ... allocation error ... }
          201  +   ...
          202  +   @endcode
          203  +   
          204  +   The entries named Parse_XXX are generally only returned by
          205  +   cson_parse() and friends.
          206  +*/
          207  +
          208  +/** @struct cson_rc_
          209  +   See \ref cson_rc for details.
          210  +*/
          211  +static const struct cson_rc_
          212  +{
          213  +    /** The generic success value. Guaranteed to be 0. */
          214  +    const int OK;
          215  +    /** Signifies an error in one or more arguments (e.g. NULL where it is not allowed). */
          216  +    const int ArgError;
          217  +    /** Signifies that some argument is not in a valid range. */
          218  +    const int RangeError;
          219  +    /** Signifies that some argument is not of the correct logical cson type. */
          220  +    const int TypeError;
          221  +    /** Signifies an input/ouput error. */
          222  +    const int IOError;
          223  +    /** Signifies an out-of-memory error. */
          224  +    const int AllocError;
          225  +    /** Signifies that the called code is "NYI" (Not Yet Implemented). */
          226  +    const int NYIError;
          227  +    /** Signifies that an internal error was triggered. If it happens, please report this as a bug! */
          228  +    const int InternalError;
          229  +    /** Signifies that the called operation is not supported in the
          230  +        current environment. e.g.  missing support from 3rd-party or
          231  +        platform-specific code.
          232  +    */
          233  +    const int UnsupportedError;
          234  +    /**
          235  +       Signifies that the request resource could not be found.
          236  +     */
          237  +    const int NotFoundError;
          238  +    /**
          239  +       Signifies an unknown error, possibly because an underlying
          240  +       3rd-party API produced an error and we have no other reasonable
          241  +       error code to convert it to.
          242  +     */
          243  +    const int UnknownError;
          244  +    /**
          245  +       Signifies that the parser found an unexpected character.
          246  +     */
          247  +    const int Parse_INVALID_CHAR;
          248  +    /**
          249  +       Signifies that the parser found an invalid keyword (possibly
          250  +       an unquoted string).
          251  +     */
          252  +    const int Parse_INVALID_KEYWORD;
          253  +    /**
          254  +       Signifies that the parser found an invalid escape sequence.
          255  +     */
          256  +    const int Parse_INVALID_ESCAPE_SEQUENCE;
          257  +    /**
          258  +       Signifies that the parser found an invalid Unicode character
          259  +       sequence.
          260  +     */
          261  +    const int Parse_INVALID_UNICODE_SEQUENCE;
          262  +    /**
          263  +       Signifies that the parser found an invalid numeric token.
          264  +     */
          265  +    const int Parse_INVALID_NUMBER;
          266  +    /**
          267  +       Signifies that the parser reached its maximum defined
          268  +       parsing depth before finishing the input.
          269  +     */
          270  +    const int Parse_NESTING_DEPTH_REACHED;
          271  +    /**
          272  +       Signifies that the parser found an unclosed object or array.
          273  +     */
          274  +    const int Parse_UNBALANCED_COLLECTION;
          275  +    /**
          276  +       Signifies that the parser found an key in an unexpected place.
          277  +     */
          278  +    const int Parse_EXPECTED_KEY;
          279  +    /**
          280  +       Signifies that the parser expected to find a colon but
          281  +       found none (e.g. between keys and values in an object).
          282  +     */
          283  +    const int Parse_EXPECTED_COLON;
          284  +} cson_rc = {
          285  +0/*OK*/,
          286  +1/*ArgError*/,
          287  +2/*RangeError*/,
          288  +3/*TypeError*/,
          289  +4/*IOError*/,
          290  +5/*AllocError*/,
          291  +6/*NYIError*/,
          292  +7/*InternalError*/,
          293  +8/*UnsupportedError*/,
          294  +9/*NotFoundError*/,
          295  +10/*UnknownError*/,
          296  +11/*Parse_INVALID_CHAR*/,
          297  +12/*Parse_INVALID_KEYWORD*/,
          298  +13/*Parse_INVALID_ESCAPE_SEQUENCE*/,
          299  +14/*Parse_INVALID_UNICODE_SEQUENCE*/,
          300  +15/*Parse_INVALID_NUMBER*/,
          301  +16/*Parse_NESTING_DEPTH_REACHED*/,
          302  +17/*Parse_UNBALANCED_COLLECTION*/,
          303  +18/*Parse_EXPECTED_KEY*/,
          304  +19/*Parse_EXPECTED_COLON*/
          305  +};
          306  +
          307  +/**
          308  +   Returns the string form of the cson_rc code corresponding to rc, or
          309  +   some unspecified, non-NULL string if it is an unknown code.
          310  +
          311  +   The returned bytes are static and do not changing during the
          312  +   lifetime of the application.
          313  +*/
          314  +char const * cson_rc_string(int rc);
          315  +
          316  +/** @struct cson_parse_opt
          317  +   Client-configurable options for the cson_parse() family of
          318  +   functions.
          319  +*/
          320  +struct cson_parse_opt
          321  +{
          322  +    /**
          323  +       Maximum object/array depth to traverse.
          324  +    */
          325  +    unsigned short maxDepth;
          326  +    /**
          327  +       Whether or not to allow C-style comments.  Do not rely on this
          328  +       option being available. If the underlying parser is replaced,
          329  +       this option might no longer be supported.
          330  +    */
          331  +    char allowComments;
          332  +};
          333  +typedef struct cson_parse_opt cson_parse_opt;
          334  +
          335  +/**
          336  +   Empty-initialized cson_parse_opt object.
          337  +*/
          338  +#define cson_parse_opt_empty_m { 25/*maxDepth*/, 0/*allowComments*/}
          339  +
          340  +
          341  +/**
          342  +   A class for holding JSON parser information. It is primarily
          343  +   intended for finding the position of a parse error.
          344  +*/
          345  +struct cson_parse_info
          346  +{
          347  +    /**
          348  +       1-based line number.
          349  +    */
          350  +    unsigned int line;
          351  +    /**
          352  +       0-based column number.
          353  +     */
          354  +    unsigned int col;
          355  +
          356  +    /**
          357  +       Length, in bytes.
          358  +    */
          359  +    unsigned int length;
          360  +    
          361  +    /**
          362  +       Error code of the parse run (0 for no error).
          363  +    */
          364  +    int errorCode;
          365  +
          366  +    /**
          367  +       The total number of object keys successfully processed by the
          368  +       parser.
          369  +    */
          370  +    unsigned int totalKeyCount;
          371  +
          372  +    /**
          373  +       The total number of object/array values successfully processed
          374  +       by the parser, including the root node.
          375  +     */
          376  +    unsigned int totalValueCount;
          377  +};
          378  +typedef struct cson_parse_info cson_parse_info;
          379  +
          380  +/**
          381  +   Empty-initialized cson_parse_info object.
          382  +*/
          383  +#define cson_parse_info_empty_m {1/*line*/,\
          384  +            0/*col*/,                                   \
          385  +            0/*length*/,                                \
          386  +            0/*errorCode*/,                             \
          387  +            0/*totalKeyCount*/,                         \
          388  +            0/*totalValueCount*/                        \
          389  +            }
          390  +/**
          391  +   Empty-initialized cson_parse_info object.
          392  +*/
          393  +extern const cson_parse_info cson_parse_info_empty;
          394  +
          395  +/**
          396  +   Empty-initialized cson_parse_opt object.
          397  +*/
          398  +extern const cson_parse_opt cson_parse_opt_empty;
          399  +
          400  +/**
          401  +    Client-configurable options for the cson_output() family of
          402  +    functions.
          403  +*/
          404  +struct cson_output_opt
          405  +{
          406  +    /**
          407  +       Specifies how to indent (or not) output. The values
          408  +       are:
          409  +
          410  +       (0) == no extra indentation.
          411  +       
          412  +       (1) == 1 TAB character for each level.
          413  +
          414  +       (>1) == that number of SPACES for each level.
          415  +    */
          416  +    unsigned char indentation;
          417  +
          418  +    /**
          419  +       Maximum object/array depth to traverse. Traversing deeply can
          420  +       be indicative of cycles in the object/array tree, and this
          421  +       value is used to figure out when to abort the traversal.
          422  +    */
          423  +    unsigned short maxDepth;
          424  +    
          425  +    /**
          426  +       If true, a newline will be added to generated output,
          427  +       else not.
          428  +    */
          429  +    char addNewline;
          430  +
          431  +    /**
          432  +       If true, a space will be added after the colon operator
          433  +       in objects' key/value pairs.
          434  +    */
          435  +    char addSpaceAfterColon;
          436  +
          437  +    /**
          438  +       If set to 1 then objects/arrays containing only a single value
          439  +       will not indent an extra level for that value (but will indent
          440  +       on subsequent levels if that value contains multiple values).
          441  +    */
          442  +    char indentSingleMemberValues;
          443  +
          444  +    /**
          445  +       The JSON format allows, but does not require, JSON generators
          446  +       to backslash-escape forward slashes. This option enables/disables
          447  +       that feature. According to JSON's inventor, Douglas Crockford:
          448  +
          449  +       <quote>
          450  +       It is allowed, not required. It is allowed so that JSON can be
          451  +       safely embedded in HTML, which can freak out when seeing
          452  +       strings containing "</". JSON tolerates "<\/" for this reason.
          453  +       </quote>
          454  +
          455  +       (from an email on 2011-04-08)
          456  +
          457  +       The default value is 0 (because it's just damned ugly).
          458  +    */
          459  +    char escapeForwardSlashes;
          460  +};
          461  +typedef struct cson_output_opt cson_output_opt;
          462  +
          463  +/**
          464  +   Empty-initialized cson_output_opt object.
          465  +*/
          466  +#define cson_output_opt_empty_m { 0/*indentation*/,\
          467  +            25/*maxDepth*/,                             \
          468  +            0/*addNewline*/,                            \
          469  +            0/*addSpaceAfterColon*/,                    \
          470  +            0/*indentSingleMemberValues*/,              \
          471  +            0/*escapeForwardSlashes*/                   \
          472  +            }
          473  +
          474  +/**
          475  +   Empty-initialized cson_output_opt object.
          476  +*/
          477  +extern const cson_output_opt cson_output_opt_empty;
          478  +
          479  +/**
          480  +   Typedef for functions which act as an input source for
          481  +   the cson JSON parser.
          482  +
          483  +   The arguments are:
          484  +
          485  +   - state: implementation-specific state needed by the function.
          486  +
          487  +   - n: when called, *n will be the number of bytes the function
          488  +   should read and copy to dest. The function MUST NOT copy more than
          489  +   *n bytes to dest. Before returning, *n must be set to the number of
          490  +   bytes actually copied to dest. If that number is smaller than the
          491  +   original *n value, the input is assumed to be completed (thus this
          492  +   is not useful with non-blocking readers).
          493  +
          494  +   - dest: the destination memory to copy the data do.
          495  +
          496  +   Must return 0 on success, non-0 on error (preferably a value from
          497  +   cson_rc).
          498  +
          499  +   The parser allows this routine to return a partial character from a
          500  +   UTF multi-byte character. The input routine does not need to
          501  +   concern itself with character boundaries.
          502  +*/
          503  +typedef int (*cson_data_source_f)( void * state, void * dest, unsigned int * n );
          504  +
          505  +/**
          506  +   Typedef for functions which act as an output destination for
          507  +   generated JSON.
          508  +
          509  +   The arguments are:
          510  +
          511  +   - state: implementation-specific state needed by the function.
          512  +
          513  +   - n: the length, in bytes, of src.
          514  +
          515  +   - src: the source bytes which the output function should consume.
          516  +   The src pointer will be invalidated shortly after this function
          517  +   returns, so the implementation must copy or ignore the data, but not
          518  +   hold a copy of the src pointer.
          519  +
          520  +   Must return 0 on success, non-0 on error (preferably a value from
          521  +   cson_rc).
          522  +
          523  +   These functions are called relatively often during the JSON-output
          524  +   process, and should try to be fast.   
          525  +*/
          526  +typedef int (*cson_data_dest_f)( void * state, void const * src, unsigned int n );
          527  +
          528  +/**
          529  +    Reads JSON-formatted string data (in ASCII, UTF8, or UTF16), using the
          530  +    src function to fetch all input. This function fetches each input character
          531  +    from the source function, which is calls like src(srcState, buffer, bufferSize),
          532  +    and processes them. If anything is not JSON-kosher then this function
          533  +    fails and returns one of the non-0 cson_rc codes.
          534  +
          535  +    This function is only intended to read root nodes of a JSON tree, either
          536  +    a single object or a single array, containing any number of child elements.
          537  +
          538  +    On success, *tgt is assigned the value of the root node of the
          539  +    JSON input, and the caller takes over ownership of that memory.
          540  +    On error, *tgt is not modified and the caller need not do any
          541  +    special cleanup, except possibly for the input source.
          542  +
          543  +
          544  +    The opt argument may point to an initialized cson_parse_opt object
          545  +    which contains any settings the caller wants. If it is NULL then
          546  +    default settings (the values defined in cson_parse_opt_empty) are
          547  +    used.
          548  +
          549  +    The info argument may be NULL. If it is not NULL then the parser
          550  +    populates it with information which is useful in error
          551  +    reporting. Namely, it contains the line/column of parse errors.
          552  +    
          553  +    The srcState argument is ignored by this function but is passed on to src,
          554  +    so any output-destination-specific state can be stored there and accessed
          555  +    via the src callback.
          556  +    
          557  +    Non-parse error conditions include:
          558  +
          559  +    - (!tgt) or !src: cson_rc.ArgError
          560  +    - cson_rc.AllocError can happen at any time during the input phase
          561  +
          562  +    Here's a complete example of using a custom input source:
          563  +
          564  +    @code
          565  +    // Internal type to hold state for a JSON input string.
          566  +    typedef struct
          567  +    {
          568  +        char const * str; // start of input string
          569  +        char const * pos; // current internal cursor position
          570  +        char const * end; // logical EOF (one-past-the-end)
          571  +    } StringSource;
          572  +
          573  +    // cson_data_source_f() impl which uses StringSource.
          574  +    static int cson_data_source_StringSource( void * state, void * dest,
          575  +                                              unsigned int * n )
          576  +    {
          577  +        StringSource * ss = (StringSource*) state;
          578  +        unsigned int i;
          579  +        unsigned char * tgt = (unsigned char *)dest;
          580  +        if( ! ss || ! n || !dest ) return cson_rc.ArgError;
          581  +        else if( !*n ) return cson_rc.RangeError;
          582  +        for( i = 0;
          583  +             (i < *n) && (ss->pos < ss->end);
          584  +             ++i, ++ss->pos, ++tgt )
          585  +        {
          586  +             *tgt = *ss->pos;
          587  +        }
          588  +        *n = i;
          589  +        return 0;
          590  +    }
          591  +
          592  +    ...
          593  +    // Now use StringSource together with cson_parse()
          594  +    StringSource ss;
          595  +    cson_value * root = NULL;
          596  +    char const * json = "{\"k1\":123}";
          597  +    ss.str = ss.pos = json;
          598  +    ss.end = json + strlen(json);
          599  +    int rc = cson_parse( &root, cson_data_source_StringSource, &ss, NULL, NULL );
          600  +    @endcode
          601  +
          602  +    It is recommended that clients wrap such utility code into
          603  +    type-safe wrapper functions which also initialize the internal
          604  +    state object and check the user-provided parameters for legality
          605  +    before passing them on to cson_parse(). For examples of this, see
          606  +    cson_parse_FILE() or cson_parse_string().
          607  +
          608  +    TODOs:
          609  +
          610  +    - Buffer the input in larger chunks. We currently read
          611  +    byte-by-byte, but i'm too tired to write/test the looping code for
          612  +    the buffering.
          613  +    
          614  +    @see cson_parse_FILE()
          615  +    @see cson_parse_string()
          616  +*/
          617  +int cson_parse( cson_value ** tgt, cson_data_source_f src, void * srcState,
          618  +                cson_parse_opt const * opt, cson_parse_info * info );
          619  +/**
          620  +   A cson_data_source_f() implementation which requires the state argument
          621  +   to be a readable (FILE*) handle.
          622  +*/
          623  +int cson_data_source_FILE( void * state, void * dest, unsigned int * n );
          624  +
          625  +/**
          626  +   Equivalent to cson_parse( tgt, cson_data_source_FILE, src, opt ).
          627  +
          628  +   @see cson_parse_filename()
          629  +*/
          630  +int cson_parse_FILE( cson_value ** tgt, FILE * src,
          631  +                     cson_parse_opt const * opt, cson_parse_info * info );
          632  +
          633  +/**
          634  +   Convenience wrapper around cson_parse_FILE() which opens the given filename.
          635  +
          636  +   Returns cson_rc.IOError if the file cannot be opened.
          637  +
          638  +   @see cson_parse_FILE()
          639  +*/
          640  +int cson_parse_filename( cson_value ** tgt, char const * src,
          641  +                         cson_parse_opt const * opt, cson_parse_info * info );
          642  +
          643  +/**
          644  +   Uses an internal helper class to pass src through cson_parse().
          645  +   See that function for the return value and argument semantics.
          646  +
          647  +   src must be a string containing JSON code, at least len bytes long,
          648  +   and the parser will attempt to parse exactly len bytes from src.
          649  +
          650  +   If len is less than 2 (the minimum length of a legal top-node JSON
          651  +   object) then cson_rc.RangeError is returned.
          652  +*/
          653  +int cson_parse_string( cson_value ** tgt, char const * src, unsigned int len,
          654  +                       cson_parse_opt const * opt, cson_parse_info * info );
          655  +
          656  +
          657  +
          658  +/**
          659  +   Outputs the given value as a JSON-formatted string, sending all
          660  +   output to the given callback function. It is intended for top-level
          661  +   objects or arrays, but can be used with any cson_value.
          662  +
          663  +   If opt is NULL then default options (the values defined in
          664  +   cson_output_opt_empty) are used.
          665  +
          666  +   If opt->maxDepth is exceeded while traversing the value tree,
          667  +   cson_rc.RangeError is returned.
          668  +
          669  +   The destState parameter is ignored by this function and is passed
          670  +   on to the dest function.
          671  +
          672  +   Returns 0 on success. On error, any amount of output might have been
          673  +   generated before the error was triggered.
          674  +   
          675  +   Example:
          676  +
          677  +   @code
          678  +   int rc = cson_output( myValue, cson_data_dest_FILE, stdout, NULL );
          679  +   // basically equivalent to: cson_output_FILE( myValue, stdout, NULL );
          680  +   // but note that cson_output_FILE() actually uses different defaults
          681  +   // for the output options.
          682  +   @endcode
          683  +*/
          684  +int cson_output( cson_value const * src, cson_data_dest_f dest, void * destState, cson_output_opt const * opt );
          685  +
          686  +
          687  +/**
          688  +   A cson_data_dest_f() implementation which requires the state argument
          689  +   to be a writable (FILE*) handle.
          690  +*/
          691  +int cson_data_dest_FILE( void * state, void const * src, unsigned int n );
          692  +
          693  +/**
          694  +   Almost equivalent to cson_output( src, cson_data_dest_FILE, dest, opt ),
          695  +   with one minor difference: if opt is NULL then the default options
          696  +   always include the addNewline option, since that is normally desired
          697  +   for FILE output.
          698  +
          699  +   @see cson_output_filename()
          700  +*/
          701  +int cson_output_FILE( cson_value const * src, FILE * dest, cson_output_opt const * opt );
          702  +/**
          703  +   Convenience wrapper around cson_output_FILE() which writes to the given filename, destroying
          704  +   any existing contents. Returns cson_rc.IOError if the file cannot be opened.
          705  +
          706  +   @see cson_output_FILE()
          707  +*/
          708  +int cson_output_filename( cson_value const * src, char const * dest, cson_output_opt const * fmt );
          709  +
          710  +/** Returns true if v is null, v->api is NULL, or v holds the special undefined value. */
          711  +char cson_value_is_undef( cson_value const * v );
          712  +/** Returns true if v contains a null value. */
          713  +char cson_value_is_null( cson_value const * v );
          714  +/** Returns true if v contains a bool value. */
          715  +char cson_value_is_bool( cson_value const * v );
          716  +/** Returns true if v contains an integer value. */
          717  +char cson_value_is_integer( cson_value const * v );
          718  +/** Returns true if v contains a double value. */
          719  +char cson_value_is_double( cson_value const * v );
          720  +/** Returns true if v contains a number (double, integer) value. */
          721  +char cson_value_is_number( cson_value const * v );
          722  +/** Returns true if v contains a string value. */
          723  +char cson_value_is_string( cson_value const * v );
          724  +/** Returns true if v contains an array value. */
          725  +char cson_value_is_array( cson_value const * v );
          726  +/** Returns true if v contains an object value. */
          727  +char cson_value_is_object( cson_value const * v );
          728  +
          729  +/** @struct cson_object
          730  +
          731  +    cson_object is an opaque handle to an Object value.
          732  +
          733  +    They are used like:
          734  +
          735  +    @code
          736  +    cson_object * obj = cson_value_get_object(myValue);
          737  +    ...
          738  +    @endcode
          739  +
          740  +    They can be created like:
          741  +
          742  +    @code
          743  +    cson_value * objV = cson_value_new_object();
          744  +    cson_object * obj = cson_value_get_object(objV);
          745  +    // obj is owned by objV and objV must eventually be freed
          746  +    // using cson_value_free() or added to a container
          747  +    // object/array (which transfers ownership to that container).
          748  +    @endcode
          749  +
          750  +    @see cson_value_new_object()
          751  +    @see cson_value_get_object()
          752  +    @see cson_value_free()
          753  +*/
          754  +
          755  +typedef struct cson_object cson_object;
          756  +
          757  +/** @struct cson_array
          758  +
          759  +    cson_array is an opaque handle to an Array value.
          760  +
          761  +    They are used like:
          762  +
          763  +    @code
          764  +    cson_array * obj = cson_value_get_array(myValue);
          765  +    ...
          766  +    @endcode
          767  +
          768  +    They can be created like:
          769  +
          770  +    @code
          771  +    cson_value * arV = cson_value_new_array();
          772  +    cson_array * ar = cson_value_get_array(arV);
          773  +    // ar is owned by arV and arV must eventually be freed
          774  +    // using cson_value_free() or added to a container
          775  +    // object/array (which transfers ownership to that container).
          776  +    @endcode
          777  +
          778  +    @see cson_value_new_array()
          779  +    @see cson_value_get_array()
          780  +    @see cson_value_free()
          781  +
          782  +*/
          783  +typedef struct cson_array cson_array;
          784  +
          785  +/** @struct cson_string
          786  +
          787  +   cson-internal string type, opaque to client code. Strings in cson
          788  +   are immutable and allocated only by library internals, never
          789  +   directly by client code.
          790  +
          791  +   The actual string bytes are to be allocated together in the same
          792  +   memory chunk as the cson_string object, which saves us 1 malloc()
          793  +   and 1 pointer member in this type (because we no longer have a
          794  +   direct pointer to the memory).
          795  +
          796  +   Potential TODOs:
          797  +
          798  +   @see cson_string_cstr()
          799  +*/
          800  +typedef struct cson_string cson_string;
          801  +
          802  +/**
          803  +   Converts the given value to a boolean, using JavaScript semantics depending
          804  +   on the concrete type of val:
          805  +
          806  +   undef or null: false
          807  +   
          808  +   boolean: same
          809  +   
          810  +   integer, double: 0 or 0.0 == false, else true
          811  +   
          812  +   object, array: true
          813  +
          814  +   string: length-0 string is false, else true.
          815  +
          816  +   Returns 0 on success and assigns *v (if v is not NULL) to either 0 or 1.
          817  +   On error (val is NULL) then v is not modified.
          818  +*/
          819  +int cson_value_fetch_bool( cson_value const * val, char * v );
          820  +
          821  +/**
          822  +   Similar to cson_value_fetch_bool(), but fetches an integer value.
          823  +
          824  +   The conversion, if any, depends on the concrete type of val:
          825  +
          826  +   NULL, null, undefined: *v is set to 0 and 0 is returned.
          827  +   
          828  +   string, object, array: *v is set to 0 and
          829  +   cson_rc.TypeError is returned. The error may normally be safely
          830  +   ignored, but it is provided for those wanted to know whether a direct
          831  +   conversion was possible.
          832  +
          833  +   integer: *v is set to the int value and 0 is returned.
          834  +   
          835  +   double: *v is set to the value truncated to int and 0 is returned.
          836  +*/
          837  +int cson_value_fetch_integer( cson_value const * val, cson_int_t * v );
          838  +
          839  +/**
          840  +   The same conversions and return values as
          841  +   cson_value_fetch_integer(), except that the roles of int/double are
          842  +   swapped.
          843  +*/
          844  +int cson_value_fetch_double( cson_value const * val, cson_double_t * v );
          845  +
          846  +/**
          847  +   If cson_value_is_string(val) then this function assigns *str to the
          848  +   contents of the string. str may be NULL, in which case this function
          849  +   functions like cson_value_is_string() but returns 0 on success.
          850  +
          851  +   Returns 0 if val is-a string, else non-0, in which case *str is not
          852  +   modified.
          853  +
          854  +   The bytes are owned by the given value and may be invalidated in any of
          855  +   the following ways:
          856  +
          857  +   - The value is cleaned up or freed.
          858  +
          859  +   - An array or object containing the value peforms a re-allocation
          860  +   (it shrinks or grows).
          861  +
          862  +   And thus the bytes should be consumed before any further operations
          863  +   on val or any container which holds it.
          864  +
          865  +   Note that this routine does not convert non-String values to their
          866  +   string representations. (Adding that ability would add more
          867  +   overhead to every cson_value instance.)
          868  +*/
          869  +int cson_value_fetch_string( cson_value const * val, cson_string ** str );
          870  +
          871  +/**
          872  +   If cson_value_is_object(val) then this function assigns *obj to the underlying
          873  +   object value and returns 0, otherwise non-0 is returned and *obj is not modified.
          874  +
          875  +   obj may be NULL, in which case this function works like cson_value_is_object()
          876  +   but with inverse return value semantics (0==success) (and it's a few
          877  +   CPU cycles slower).
          878  +
          879  +   The *obj pointer is owned by val, and will be invalidated when val
          880  +   is cleaned up.
          881  +
          882  +   Achtung: for best results, ALWAYS pass a pointer to NULL as the
          883  +   second argument, e.g.:
          884  +
          885  +   @code
          886  +   cson_object * obj = NULL;
          887  +   int rc = cson_value_fetch_object( val, &obj );
          888  +
          889  +   // Or, more simply:
          890  +   obj = cson_value_get_object( val );
          891  +   @endcode
          892  +
          893  +   @see cson_value_get_object()
          894  +*/
          895  +int cson_value_fetch_object( cson_value const * val, cson_object ** obj );
          896  +
          897  +/**
          898  +   Identical to cson_value_fetch_object(), but works on array values.
          899  +
          900  +   @see cson_value_get_array()
          901  +*/
          902  +int cson_value_fetch_array( cson_value const * val, cson_array ** tgt );
          903  +
          904  +/**
          905  +   Simplified form of cson_value_fetch_bool(). Returns 0 if val
          906  +   is NULL.
          907  +*/
          908  +char cson_value_get_bool( cson_value const * val );
          909  +
          910  +/**
          911  +   Simplified form of cson_value_fetch_integer(). Returns 0 if val
          912  +   is NULL.
          913  +*/
          914  +cson_int_t cson_value_get_integer( cson_value const * val );
          915  +
          916  +/**
          917  +   Simplified form of cson_value_fetch_double(). Returns 0.0 if val
          918  +   is NULL.
          919  +*/
          920  +cson_double_t cson_value_get_double( cson_value const * val );
          921  +
          922  +/**
          923  +   Simplified form of cson_value_fetch_string(). Returns NULL if val
          924  +   is-not-a string value.
          925  +*/
          926  +cson_string * cson_value_get_string( cson_value const * val );
          927  +
          928  +/**
          929  +   Returns a pointer to the NULL-terminated string bytes of str.
          930  +   The bytes are owned by string and will be invalided when it
          931  +   is cleaned up.
          932  +
          933  +   If str is NULL then NULL is returned.
          934  +
          935  +   @see cson_string_length_bytes()
          936  +   @see cson_value_get_string()
          937  +*/
          938  +char const * cson_string_cstr( cson_string const * str );
          939  +
          940  +/**
          941  +   Convenience function which returns the string bytes of
          942  +   the given value if it is-a string, otherwise it returns
          943  +   NULL. Note that this does no conversion of non-string types
          944  +   to strings.
          945  +
          946  +   Equivalent to cson_string_cstr(cson_value_get_string(val)).
          947  +*/
          948  +char const * cson_value_get_cstr( cson_value const * val );
          949  +
          950  +/**
          951  +   Equivalent to cson_string_cmp_cstr_n(lhs, cson_string_cstr(rhs), cson_string_length_bytes(rhs)).
          952  +*/
          953  +int cson_string_cmp( cson_string const * lhs, cson_string const * rhs );
          954  +
          955  +/**
          956  +   Compares lhs to rhs using memcmp()/strcmp() semantics. Generically
          957  +   speaking it returns a negative number if lhs is less-than rhs, 0 if
          958  +   they are equivalent, or a positive number if lhs is greater-than
          959  +   rhs. It has the following rules for equivalence:
          960  +
          961  +   - The maximum number of bytes compared is the lesser of rhsLen and
          962  +   the length of lhs. If the strings do not match, but compare equal
          963  +   up to the just-described comparison length, the shorter string is
          964  +   considered to be less-than the longer one.
          965  +   
          966  +   - If lhs and rhs are both NULL, or both have a length of 0 then they will
          967  +   compare equal.
          968  +
          969  +   - If lhs is null/length-0 but rhs is not then lhs is considered to be less-than
          970  +   rhs.
          971  +
          972  +   - If rhs is null/length-0 but lhs is not then rhs is considered to be less-than
          973  +   rhs.
          974  +
          975  +   - i have no clue if the results are exactly correct for UTF strings.
          976  +
          977  +*/
          978  +int cson_string_cmp_cstr_n( cson_string const * lhs, char const * rhs, unsigned int rhsLen );
          979  +
          980  +/**
          981  +   Equivalent to cson_string_cmp_cstr_n( lhs, rhs, (rhs&&*rhs)?strlen(rhs):0 ).
          982  +*/
          983  +int cson_string_cmp_cstr( cson_string const * lhs, char const * rhs );
          984  +
          985  +/**
          986  +   Returns the length, in bytes, of str, or 0 if str is NULL. This is
          987  +   an O(1) operation.
          988  +
          989  +   TODO: add cson_string_length_chars() (is O(N) unless we add another
          990  +   member to store the char length).
          991  +   
          992  +   @see cson_string_cstr()
          993  +*/
          994  +unsigned int cson_string_length_bytes( cson_string const * str );
          995  +
          996  +/**
          997  +    Returns the number of UTF8 characters in str. This value will
          998  +    be at most as long as cson_string_length_bytes() for the
          999  +    same string, and less if it has multi-byte characters.
         1000  +
         1001  +    Returns 0 if str is NULL.
         1002  +*/
         1003  +unsigned int cson_string_length_utf8( cson_string const * str );
         1004  +
         1005  +/**
         1006  +   Like cson_value_get_string(), but returns a copy of the underying
         1007  +   string bytes, which the caller owns and must eventually free
         1008  +   using free().
         1009  +*/
         1010  +char * cson_value_get_string_copy( cson_value const * val );
         1011  +
         1012  +/**
         1013  +   Simplified form of cson_value_fetch_object(). Returns NULL if val
         1014  +   is-not-a object value.
         1015  +*/
         1016  +cson_object * cson_value_get_object( cson_value const * val );
         1017  +
         1018  +/**
         1019  +   Simplified form of cson_value_fetch_array(). Returns NULL if val
         1020  +   is-not-a array value.
         1021  +*/
         1022  +cson_array * cson_value_get_array( cson_value const * val );
         1023  +
         1024  +/**
         1025  +   Const-correct form of cson_value_get_array().
         1026  +*/
         1027  +cson_array const * cson_value_get_array_c( cson_value const * val );
         1028  +
         1029  +/**
         1030  +   If ar is-a array and is at least (pos+1) entries long then *v (if v is not NULL)
         1031  +   is assigned to the value at that position (which may be NULL).
         1032  +
         1033  +   Ownership of the *v return value is unchanged by this call. (The
         1034  +   containing array may share ownership of the value with other
         1035  +   containers.)
         1036  +
         1037  +   If pos is out of range, non-0 is returned and *v is not modified.
         1038  +
         1039  +   If v is NULL then this function returns 0 if pos is in bounds, but does not
         1040  +   otherwise return a value to the caller.
         1041  +*/
         1042  +int cson_array_value_fetch( cson_array const * ar, unsigned int pos, cson_value ** v );
         1043  +
         1044  +/**
         1045  +   Simplified form of cson_array_value_fetch() which returns NULL if
         1046  +   ar is NULL, pos is out of bounds or if ar has no element at that
         1047  +   position.
         1048  +*/
         1049  +cson_value * cson_array_get( cson_array const * ar, unsigned int pos );
         1050  +
         1051  +/**
         1052  +   Ensures that ar has allocated space for at least the given
         1053  +   number of entries. This never shrinks the array and never
         1054  +   changes its logical size, but may pre-allocate space in the
         1055  +   array for storing new (as-yet-unassigned) values.
         1056  +
         1057  +   Returns 0 on success, or non-zero on error:
         1058  +
         1059  +   - If ar is NULL: cson_rc.ArgError
         1060  +
         1061  +   - If allocation fails: cson_rc.AllocError
         1062  +*/
         1063  +int cson_array_reserve( cson_array * ar, unsigned int size );
         1064  +
         1065  +/**
         1066  +   If ar is not NULL, sets *v (if v is not NULL) to the length of the array
         1067  +   and returns 0. Returns cson_rc.ArgError if ar is NULL.
         1068  +*/
         1069  +int cson_array_length_fetch( cson_array const * ar, unsigned int * v );
         1070  +
         1071  +/**
         1072  +   Simplified form of cson_array_length_fetch() which returns 0 if ar
         1073  +   is NULL.
         1074  +*/
         1075  +unsigned int cson_array_length_get( cson_array const * ar );
         1076  +
         1077  +/**
         1078  +   Sets the given index of the given array to the given value.
         1079  +
         1080  +   If ar already has an item at that index then it is cleaned up and
         1081  +   freed before inserting the new item.
         1082  +
         1083  +   ar is expanded, if needed, to be able to hold at least (ndx+1)
         1084  +   items, and any new entries created by that expansion are empty
         1085  +   (NULL values).
         1086  +
         1087  +   On success, 0 is returned and ownership of v is transfered to ar.
         1088  +  
         1089  +   On error ownership of v is NOT modified, and the caller may still
         1090  +   need to clean it up. For example, the following code will introduce
         1091  +   a leak if this function fails:
         1092  +
         1093  +   @code
         1094  +   cson_array_append( myArray, cson_value_new_integer(42) );
         1095  +   @endcode
         1096  +
         1097  +   Because the value created by cson_value_new_integer() has no owner
         1098  +   and is not cleaned up. The "more correct" way to do this is:
         1099  +
         1100  +   @code
         1101  +   cson_value * v = cson_value_new_integer(42);
         1102  +   int rc = cson_array_append( myArray, v );
         1103  +   if( 0 != rc ) {
         1104  +      cson_value_free( v );
         1105  +      ... handle error ...
         1106  +   }
         1107  +   @endcode
         1108  +
         1109  +*/
         1110  +int cson_array_set( cson_array * ar, unsigned int ndx, cson_value * v );
         1111  +
         1112  +/**
         1113  +   Appends the given value to the given array, transfering ownership of
         1114  +   v to ar. On error, ownership of v is not modified. Ownership of ar
         1115  +   is never changed by this function.
         1116  +
         1117  +   This is functionally equivalent to
         1118  +   cson_array_set(ar,cson_array_length_get(ar),v), but this
         1119  +   implementation has slightly different array-preallocation policy
         1120  +   (it grows more eagerly).
         1121  +   
         1122  +   Returns 0 on success, non-zero on error. Error cases include:
         1123  +
         1124  +   - ar or v are NULL: cson_rc.ArgError
         1125  +
         1126  +   - Array cannot be expanded to hold enough elements: cson_rc.AllocError.
         1127  +
         1128  +   - Appending would cause a numeric overlow in the array's size:
         1129  +   cson_rc.RangeError.  (However, you'll get an AllocError long before
         1130  +   that happens!)
         1131  +
         1132  +   On error ownership of v is NOT modified, and the caller may still
         1133  +   need to clean it up. See cson_array_set() for the details.
         1134  +
         1135  +*/
         1136  +int cson_array_append( cson_array * ar, cson_value * v );
         1137  +
         1138  +
         1139  +/**
         1140  +   Creates a new cson_value from the given boolean value.
         1141  +
         1142  +   Ownership of the new value is passed to the caller, who must
         1143  +   eventually either free the value using cson_value_free() or
         1144  +   inserting it into a container (array or object), which transfers
         1145  +   ownership to the container. See the cson_value class documentation
         1146  +   for more details.
         1147  +
         1148  +   Returns NULL on allocation error.
         1149  +*/
         1150  +cson_value * cson_value_new_bool( char v );
         1151  +
         1152  +
         1153  +/**
         1154  +   Alias for cson_value_new_bool(v).
         1155  +*/
         1156  +cson_value * cson_new_bool(char v);
         1157  +
         1158  +/**
         1159  +   Returns the special JSON "null" value. When outputing JSON,
         1160  +   its string representation is "null" (without the quotes).
         1161  +   
         1162  +   See cson_value_new_bool() for notes regarding the returned
         1163  +   value's memory.
         1164  +*/
         1165  +cson_value * cson_value_null();
         1166  +
         1167  +/**
         1168  +   Equivalent to cson_value_new_bool(1).
         1169  +*/
         1170  +cson_value * cson_value_true();
         1171  +
         1172  +/**
         1173  +   Equivalent to cson_value_new_bool(0).
         1174  +*/
         1175  +cson_value * cson_value_false();
         1176  +
         1177  +/**
         1178  +   Semantically the same as cson_value_new_bool(), but for integers.
         1179  +*/
         1180  +cson_value * cson_value_new_integer( cson_int_t v );
         1181  +
         1182  +/**
         1183  +   Alias for cson_value_new_integer(v).
         1184  +*/
         1185  +cson_value * cson_new_int(cson_int_t v);
         1186  +
         1187  +/**
         1188  +   Semantically the same as cson_value_new_bool(), but for doubles.
         1189  +*/
         1190  +cson_value * cson_value_new_double( cson_double_t v );
         1191  +
         1192  +/**
         1193  +   Alias for cson_value_new_double(v).
         1194  +*/
         1195  +cson_value * cson_new_double(cson_double_t v);
         1196  +
         1197  +/**
         1198  +   Semantically the same as cson_value_new_bool(), but for strings.
         1199  +   This creates a JSON value which copies the first n bytes of str.
         1200  +   The string will automatically be NUL-terminated.
         1201  +   
         1202  +   Note that if str is NULL or n is 0, this function still
         1203  +   returns non-NULL value representing that empty string.
         1204  +   
         1205  +   Returns NULL on allocation error.
         1206  +   
         1207  +   See cson_value_new_bool() for important information about the
         1208  +   returned memory.
         1209  +*/
         1210  +cson_value * cson_value_new_string( char const * str, unsigned int n );
         1211  +
         1212  +/**
         1213  +   Allocates a new "object" value and transfers ownership of it to the
         1214  +   caller. It must eventually be destroyed, by the caller or its
         1215  +   owning container, by passing it to cson_value_free().
         1216  +
         1217  +   Returns NULL on allocation error.
         1218  +
         1219  +   Post-conditions: cson_value_is_object(value) will return true.
         1220  +
         1221  +   @see cson_value_new_array()
         1222  +   @see cson_value_free()
         1223  +*/
         1224  +cson_value * cson_value_new_object();
         1225  +
         1226  +/**
         1227  +   This works like cson_value_new_object() but returns an Object
         1228  +   handle directly.
         1229  +
         1230  +   The value handle for the returned object can be fetched with
         1231  +   cson_object_value(theObject).
         1232  +   
         1233  +   Ownership is transfered to the caller, who must eventually free it
         1234  +   by passing the Value handle (NOT the Object handle) to
         1235  +   cson_value_free() or passing ownership to a parent container.
         1236  +
         1237  +   Returns NULL on error (out of memory).
         1238  +*/
         1239  +cson_object * cson_new_object();
         1240  +
         1241  +/**
         1242  +   Identical to cson_new_object() except that it creates
         1243  +   an Array.
         1244  +*/
         1245  +cson_array * cson_new_array();
         1246  +
         1247  +/**
         1248  +   Identical to cson_new_object() except that it creates
         1249  +   a String.
         1250  +*/
         1251  +cson_string * cson_new_string(char const * val, unsigned int len);
         1252  +
         1253  +/**
         1254  +   Equivalent to cson_value_free(cson_object_value(x)).
         1255  +*/
         1256  +void cson_free_object(cson_object *x);
         1257  +
         1258  +/**
         1259  +   Equivalent to cson_value_free(cson_array_value(x)).
         1260  +*/
         1261  +void cson_free_array(cson_array *x);
         1262  +
         1263  +/**
         1264  +   Equivalent to cson_value_free(cson_string_value(x)).
         1265  +*/
         1266  +void cson_free_string(cson_string const *x);
         1267  +
         1268  +
         1269  +/**
         1270  +   Allocates a new "array" value and transfers ownership of it to the
         1271  +   caller. It must eventually be destroyed, by the caller or its
         1272  +   owning container, by passing it to cson_value_free().
         1273  +
         1274  +   Returns NULL on allocation error.
         1275  +
         1276  +   Post-conditions: cson_value_is_array(value) will return true.
         1277  +
         1278  +   @see cson_value_new_object()
         1279  +   @see cson_value_free()
         1280  +*/
         1281  +cson_value * cson_value_new_array();
         1282  +
         1283  +/**
         1284  +   Frees any resources owned by v, then frees v. If v is a container
         1285  +   type (object or array) its children are also freed (recursively).
         1286  +
         1287  +   If v is NULL, this is a no-op.
         1288  +
         1289  +   This function decrements a reference count and only destroys the
         1290  +   value if its reference count drops to 0. Reference counts are
         1291  +   increased by either inserting the value into a container or via
         1292  +   cson_value_add_reference(). Even if this function does not
         1293  +   immediately destroy the value, the value must be considered, from
         1294  +   the perspective of that client code, to have been
         1295  +   destroyed/invalidated by this call.
         1296  +
         1297  +   
         1298  +   @see cson_value_new_object()
         1299  +   @see cson_value_new_array()
         1300  +   @see cson_value_add_reference()
         1301  +*/
         1302  +void cson_value_free(cson_value * v);
         1303  +
         1304  +/**
         1305  +   Alias for cson_value_free().
         1306  +*/
         1307  +void cson_free_value(cson_value * v);
         1308  +
         1309  +
         1310  +/**
         1311  +   Functionally similar to cson_array_set(), but uses a string key
         1312  +   as an index. Like arrays, if a value already exists for the given key,
         1313  +   it is destroyed by this function before inserting the new value.
         1314  +
         1315  +   If v is NULL then this call is equivalent to
         1316  +   cson_object_unset(obj,key). Note that (v==NULL) is treated
         1317  +   differently from v having the special null value. In the latter
         1318  +   case, the key is set to the special null value.
         1319  +
         1320  +   The key may be encoded as ASCII or UTF8. Results are undefined
         1321  +   with other encodings, and the errors won't show up here, but may
         1322  +   show up later, e.g. during output.
         1323  +   
         1324  +   Returns 0 on success, non-0 on error. It has the following error
         1325  +   cases:
         1326  +
         1327  +   - cson_rc.ArgError: obj or key are NULL or strlen(key) is 0.
         1328  +
         1329  +   - cson_rc.AllocError: an out-of-memory error
         1330  +
         1331  +   On error ownership of v is NOT modified, and the caller may still
         1332  +   need to clean it up. For example, the following code will introduce
         1333  +   a leak if this function fails:
         1334  +
         1335  +   @code
         1336  +   cson_object_set( myObj, "foo", cson_value_new_integer(42) );
         1337  +   @endcode
         1338  +
         1339  +   Because the value created by cson_value_new_integer() has no owner
         1340  +   and is not cleaned up. The "more correct" way to do this is:
         1341  +
         1342  +   @code
         1343  +   cson_value * v = cson_value_new_integer(42);
         1344  +   int rc = cson_object_set( myObj, "foo", v );
         1345  +   if( 0 != rc ) {
         1346  +      cson_value_free( v );
         1347  +      ... handle error ...
         1348  +   }
         1349  +   @endcode
         1350  +
         1351  +   Potential TODOs:
         1352  +
         1353  +   - Add an overload which takes a cson_value key instead. To get
         1354  +   any value out of that we first need to be able to convert arbitrary
         1355  +   value types to strings. We could simply to-JSON them and use those
         1356  +   as keys.
         1357  +*/
         1358  +int cson_object_set( cson_object * obj, char const * key, cson_value * v );
         1359  +
         1360  +/**
         1361  +   Functionaly equivalent to cson_object_set(), but takes a
         1362  +   cson_string() as its KEY type. The string will be reference-counted
         1363  +   like any other values, and the key may legally be used within this
         1364  +   same container (as a value) or others (as a key or value) at the
         1365  +   same time.
         1366  +
         1367  +   Returns 0 on success. On error, ownership (i.e. refcounts) of key
         1368  +   and value are not modified. On success key and value will get
         1369  +   increased refcounts unless they are replacing themselves (which is
         1370  +   a harmless no-op).
         1371  +*/
         1372  +int cson_object_set_s( cson_object * obj, cson_string * key, cson_value * v );
         1373  +
         1374  +/**
         1375  +   Removes a property from an object.
         1376  +   
         1377  +   If obj contains the given key, it is removed and 0 is returned. If
         1378  +   it is not found, cson_rc.NotFoundError is returned (which can
         1379  +   normally be ignored by client code).
         1380  +
         1381  +   cson_rc.ArgError is returned if obj or key are NULL or key has
         1382  +   a length of 0.
         1383  +
         1384  +   Returns 0 if the given key is found and removed.
         1385  +
         1386  +   This is functionally equivalent calling
         1387  +   cson_object_set(obj,key,NULL).
         1388  +*/
         1389  +int cson_object_unset( cson_object * obj, char const * key );
         1390  +
         1391  +/**
         1392  +   Searches the given object for a property with the given key. If found,
         1393  +   it is returned. If no match is found, or any arguments are NULL, NULL is
         1394  +   returned. The returned object is owned by obj, and may be invalidated
         1395  +   by ANY operations which change obj's property list (i.e. add or remove
         1396  +   properties).
         1397  +
         1398  +   FIXME: allocate the key/value pairs like we do for cson_array,
         1399  +   to get improve the lifetimes of fetched values.
         1400  +
         1401  +   @see cson_object_fetch_sub()
         1402  +   @see cson_object_get_sub()
         1403  +*/
         1404  +cson_value * cson_object_get( cson_object const * obj, char const * key );
         1405  +
         1406  +/**
         1407  +   Equivalent to cson_object_get() but takes a cson_string argument
         1408  +   instead of a C-style string.
         1409  +*/
         1410  +cson_value * cson_object_get_s( cson_object const * obj, cson_string const *key );
         1411  +
         1412  +/**
         1413  +   Similar to cson_object_get(), but removes the value from the parent
         1414  +   object's ownership. If no item is found then NULL is returned, else
         1415  +   the object (now owned by the caller or possibly shared with other
         1416  +   containers) is returned.
         1417  +
         1418  +   Returns NULL if either obj or key are NULL or key has a length
         1419  +   of 0.
         1420  +
         1421  +   This function reduces the returned value's reference count but has
         1422  +   the specific property that it does not treat refcounts 0 and 1
         1423  +   identically, meaning that the returned object may have a refcount
         1424  +   of 0. This behaviour works around a corner-case where we want to
         1425  +   extract a child element from its parent and then destroy the parent
         1426  +   (which leaves us in an undesireable (normally) reference count
         1427  +   state).
         1428  +*/
         1429  +cson_value * cson_object_take( cson_object * obj, char const * key );
         1430  +
         1431  +/**
         1432  +    Fetches a property from a child (or [great-]*grand-child) object.
         1433  +
         1434  +    obj is the object to search.
         1435  +
         1436  +    path is a delimited string, where the delimiter is the given
         1437  +    separator character.
         1438  +
         1439  +    This function searches for the given path, starting at the given object
         1440  +    and traversing its properties as the path specifies. If a given part of the
         1441  +    path is not found, then this function fails with cson_rc.NotFoundError.
         1442  +
         1443  +    If it finds the given path, it returns the value by assiging *tgt
         1444  +    to it.  If tgt is NULL then this function has no side-effects but
         1445  +    will return 0 if the given path is found within the object, so it can be used
         1446  +    to test for existence without fetching it.
         1447  +    
         1448  +    Returns 0 if it finds an entry, cson_rc.NotFoundError if it finds
         1449  +    no item, and any other non-zero error code on a "real" error. Errors include:
         1450  +
         1451  +   - obj or path are NULL: cson_rc.ArgError
         1452  +    
         1453  +    - separator is 0, or path is an empty string or contains only
         1454  +    separator characters: cson_rc.RangeError
         1455  +
         1456  +    - There is an upper limit on how long a single path component may
         1457  +    be (some "reasonable" internal size), and cson_rc.RangeError is
         1458  +    returned if that length is violated.
         1459  +
         1460  +    
         1461  +    Limitations:
         1462  +
         1463  +    - It has no way to fetch data from arrays this way. i could
         1464  +    imagine, e.g., a path of "subobj.subArray.0" for
         1465  +    subobj.subArray[0], or "0.3.1" for [0][3][1]. But i'm too
         1466  +    lazy/tired to add this.
         1467  +
         1468  +    Example usage:
         1469  +    
         1470  +
         1471  +    Assume we have a JSON structure which abstractly looks like:
         1472  +
         1473  +    @code
         1474  +    {"subobj":{"subsubobj":{"myValue":[1,2,3]}}}
         1475  +    @endcode
         1476  +
         1477  +    Out goal is to get the value of myValue. We can do that with:
         1478  +
         1479  +    @code
         1480  +    cson_value * v = NULL;
         1481  +    int rc = cson_object_fetch_sub( object, &v, "subobj.subsubobj.myValue", '.' );
         1482  +    @endcode
         1483  +
         1484  +    Note that because keys in JSON may legally contain a '.', the
         1485  +    separator must be specified by the caller. e.g. the path
         1486  +    "subobj/subsubobj/myValue" with separator='/' is equivalent the
         1487  +    path "subobj.subsubobj.myValue" with separator='.'. The value of 0
         1488  +    is not legal as a separator character because we cannot
         1489  +    distinguish that use from the real end-of-string without requiring
         1490  +    the caller to also pass in the length of the string.
         1491  +   
         1492  +    Multiple successive separators in the list are collapsed into a
         1493  +    single separator for parsing purposes. e.g. the path "a...b...c"
         1494  +    (separator='.') is equivalent to "a.b.c".
         1495  +
         1496  +    @see cson_object_get_sub()
         1497  +    @see cson_object_get_sub2()
         1498  +*/
         1499  +int cson_object_fetch_sub( cson_object const * obj, cson_value ** tgt, char const * path, char separator );
         1500  +
         1501  +/**
         1502  +   Similar to cson_object_fetch_sub(), but derives the path separator
         1503  +   character from the first byte of the path argument. e.g. the
         1504  +   following arg equivalent:
         1505  +
         1506  +   @code
         1507  +   cson_object_fetch_sub( obj, &tgt, "foo.bar.baz", '.' );
         1508  +   cson_object_fetch_sub2( obj, &tgt, ".foo.bar.baz" );
         1509  +   @endcode
         1510  +*/
         1511  +int cson_object_fetch_sub2( cson_object const * obj, cson_value ** tgt, char const * path );
         1512  +
         1513  +/**
         1514  +   Convenience form of cson_object_fetch_sub() which returns NULL if the given
         1515  +   item is not found.
         1516  +*/
         1517  +cson_value * cson_object_get_sub( cson_object const * obj, char const * path, char sep );
         1518  +
         1519  +/**
         1520  +   Convenience form of cson_object_fetch_sub2() which returns NULL if the given
         1521  +   item is not found.
         1522  +*/
         1523  +cson_value * cson_object_get_sub2( cson_object const * obj, char const * path );
         1524  +
         1525  +/** @enum CSON_MERGE_FLAGS
         1526  +
         1527  +    Flags for cson_object_merge().
         1528  +*/
         1529  +enum CSON_MERGE_FLAGS {
         1530  +    CSON_MERGE_DEFAULT = 0,
         1531  +    CSON_MERGE_REPLACE = 0x01,
         1532  +    CSON_MERGE_NO_RECURSE = 0x02
         1533  +};
         1534  +
         1535  +/**
         1536  +   "Merges" the src object's properties into dest. Each property in
         1537  +   src is copied (using reference counting, not cloning) into dest. If
         1538  +   dest already has the given property then behaviour depends on the
         1539  +   flags argument:
         1540  +
         1541  +   If flag has the CSON_MERGE_REPLACE bit set then this function will
         1542  +   by default replace non-object properties with the src property. If
         1543  +   src and dest both have the property AND it is an Object then this
         1544  +   function operates recursively on those objects. If
         1545  +   CSON_MERGE_NO_RECURSE is set then objects are not recursed in this
         1546  +   manner, and will be completely replaced if CSON_MERGE_REPLACE is
         1547  +   set.
         1548  +
         1549  +   Array properties in dest are NOT recursed for merging - they are
         1550  +   either replaced or left as-is, depending on whether flags contains
         1551  +   he CSON_MERGE_REPLACE bit.
         1552  +
         1553  +   Returns 0 on success. The error conditions are:
         1554  +
         1555  +   - dest or src are NULL or (dest==src) returns cson_rc.ArgError.
         1556  +
         1557  +   - dest or src contain cyclic references - this will likely cause a
         1558  +   crash due to endless recursion.
         1559  +
         1560  +   Potential TODOs:
         1561  +
         1562  +   - Add a flag to copy clones, not the original values.
         1563  +*/
         1564  +int cson_object_merge( cson_object * dest, cson_object const * src, int flags );
         1565  +
         1566  +
         1567  +/**
         1568  +   An iterator type for traversing object properties.
         1569  +
         1570  +   Its values must be considered private, not to be touched by client
         1571  +   code.
         1572  +
         1573  +   @see cson_object_iter_init()
         1574  +   @see cson_object_iter_next()
         1575  +*/
         1576  +struct cson_object_iterator
         1577  +{
         1578  +    
         1579  +    /** @internal
         1580  +        The underlying object.
         1581  +    */
         1582  +    cson_object const * obj;
         1583  +    /** @internal
         1584  +        Current position in the property list.
         1585  +     */
         1586  +    unsigned int pos;
         1587  +};
         1588  +typedef struct cson_object_iterator cson_object_iterator;
         1589  +
         1590  +/**
         1591  +   Empty-initialized cson_object_iterator object.
         1592  +*/
         1593  +#define cson_object_iterator_empty_m {NULL/*obj*/,0/*pos*/}
         1594  +
         1595  +/**
         1596  +   Empty-initialized cson_object_iterator object.
         1597  +*/
         1598  +extern const cson_object_iterator cson_object_iterator_empty;
         1599  +
         1600  +/**
         1601  +   Initializes the given iterator to point at the start of obj's
         1602  +   properties. Returns 0 on success or cson_rc.ArgError if !obj
         1603  +   or !iter.
         1604  +
         1605  +   obj must outlive iter, or results are undefined. Results are also
         1606  +   undefined if obj is modified while the iterator is active.
         1607  +
         1608  +   @see cson_object_iter_next()
         1609  +*/
         1610  +int cson_object_iter_init( cson_object const * obj, cson_object_iterator * iter );
         1611  +
         1612  +/** @struct cson_kvp
         1613  +
         1614  +This class represents a key/value pair and is used for storing
         1615  +object properties. It is opaque to client code, and the public
         1616  +API only uses this type for purposes of iterating over cson_object
         1617  +properties using the cson_object_iterator interfaces.
         1618  +*/
         1619  +
         1620  +typedef struct cson_kvp cson_kvp;
         1621  +
         1622  +/**
         1623  +   Returns the next property from the given iterator's object, or NULL
         1624  +   if the end of the property list as been reached.
         1625  +
         1626  +   Note that the order of object properties is undefined by the API,
         1627  +   and may change from version to version.
         1628  +
         1629  +   The returned memory belongs to the underlying object and may be
         1630  +   invalidated by any changes to that object.
         1631  +
         1632  +   Example usage:
         1633  +
         1634  +   @code
         1635  +   cson_object_iterator it;
         1636  +   cson_object_iter_init( myObject, &it ); // only fails if either arg is 0
         1637  +   cson_kvp * kvp;
         1638  +   cson_string const * key;
         1639  +   cson_value const * val;
         1640  +   while( (kvp = cson_object_iter_next(&it) ) )
         1641  +   {
         1642  +       key = cson_kvp_key(kvp);
         1643  +       val = cson_kvp_value(kvp);
         1644  +       ...
         1645  +   }
         1646  +   @endcode
         1647  +
         1648  +   There is no need to clean up an iterator, as it holds no dynamic resources.
         1649  +   
         1650  +   @see cson_kvp_key()
         1651  +   @see cson_kvp_value()
         1652  +*/
         1653  +cson_kvp * cson_object_iter_next( cson_object_iterator * iter );
         1654  +
         1655  +
         1656  +/**
         1657  +   Returns the key associated with the given key/value pair,
         1658  +   or NULL if !kvp. The memory is owned by the object which contains
         1659  +   the key/value pair, and may be invalidated by any modifications
         1660  +   to that object.
         1661  +*/
         1662  +cson_string * cson_kvp_key( cson_kvp const * kvp );
         1663  +
         1664  +/**
         1665  +   Returns the value associated with the given key/value pair,
         1666  +   or NULL if !kvp. The memory is owned by the object which contains
         1667  +   the key/value pair, and may be invalidated by any modifications
         1668  +   to that object.
         1669  +*/
         1670  +cson_value * cson_kvp_value( cson_kvp const * kvp );
         1671  +
         1672  +/** @typedef some unsigned int type cson_size_t
         1673  +
         1674  +*/
         1675  +typedef unsigned int cson_size_t;
         1676  +
         1677  +/**
         1678  +   A generic buffer class.
         1679  +
         1680  +   They can be used like this:
         1681  +
         1682  +   @code
         1683  +   cson_buffer b = cson_buffer_empty;
         1684  +   int rc = cson_buffer_reserve( &buf, 100 );
         1685  +   if( 0 != rc ) { ... allocation error ... }
         1686  +   ... use buf.mem ...
         1687  +   ... then free it up ...
         1688  +   cson_buffer_reserve( &buf, 0 );
         1689  +   @endcode
         1690  +
         1691  +   To take over ownership of a buffer's memory:
         1692  +
         1693  +   @code
         1694  +   void * mem = b.mem;
         1695  +   // mem is b.capacity bytes long, but only b.used
         1696  +   // bytes of it has been "used" by the API.
         1697  +   b = cson_buffer_empty;
         1698  +   @endcode
         1699  +
         1700  +   The memory now belongs to the caller and must eventually be
         1701  +   free()d.
         1702  +*/
         1703  +struct cson_buffer
         1704  +{
         1705  +    /**
         1706  +       The number of bytes allocated for this object.
         1707  +       Use cson_buffer_reserve() to change its value.
         1708  +     */
         1709  +    cson_size_t capacity;
         1710  +    /**
         1711  +       The number of bytes "used" by this object. It is not needed for
         1712  +       all use cases, and management of this value (if needed) is up
         1713  +       to the client. The cson_buffer public API does not use this
         1714  +       member. The intention is that this can be used to track the
         1715  +       length of strings which are allocated via cson_buffer, since
         1716  +       they need an explicit length and/or null terminator.
         1717  +     */
         1718  +    cson_size_t used;
         1719  +
         1720  +    /**
         1721  +       This is a debugging/metric-counting value
         1722  +       intended to help certain malloc()-conscious
         1723  +       clients tweak their memory reservation sizes.
         1724  +       Each time cson_buffer_reserve() expands the
         1725  +       buffer, it increments this value by 1.
         1726  +    */
         1727  +    cson_size_t timesExpanded;
         1728  +
         1729  +    /**
         1730  +       The memory allocated for and owned by this buffer.
         1731  +       Use cson_buffer_reserve() to change its size or
         1732  +       free it. To take over ownership, do:
         1733  +
         1734  +       @code
         1735  +       void * myptr = buf.mem;
         1736  +       buf = cson_buffer_empty;
         1737  +       @endcode
         1738  +
         1739  +       (You might also need to store buf.used and buf.capacity,
         1740  +       depending on what you want to do with the memory.)
         1741  +       
         1742  +       When doing so, the memory must eventually be passed to free()
         1743  +       to deallocate it.
         1744  +    */
         1745  +    unsigned char * mem;
         1746  +};
         1747  +/** Convenience typedef. */
         1748  +typedef struct cson_buffer cson_buffer;
         1749  +
         1750  +/** An empty-initialized cson_buffer object. */
         1751  +#define cson_buffer_empty_m {0/*capacity*/,0/*used*/,0/*timesExpanded*/,NULL/*mem*/}
         1752  +/** An empty-initialized cson_buffer object. */
         1753  +extern const cson_buffer cson_buffer_empty;
         1754  +
         1755  +/**
         1756  +   Uses cson_output() to append all JSON output to the given buffer
         1757  +   object. The semantics for the (v, opt) parameters, and the return
         1758  +   value, are as documented for cson_output(). buf must be a non-NULL
         1759  +   pointer to a properly initialized buffer (see example below).
         1760  +
         1761  +   Ownership of buf is not changed by calling this.
         1762  +
         1763  +   On success 0 is returned and the contents of buf.mem are guaranteed
         1764  +   to be NULL-terminated. On error the buffer might contain partial
         1765  +   contents, and it should not be used except to free its contents.
         1766  +
         1767  +   On error non-zero is returned. Errors include:
         1768  +
         1769  +   - Invalid arguments: cson_rc.ArgError
         1770  +
         1771  +   - Buffer cannot be expanded (runs out of memory): cson_rc.AllocError
         1772  +   
         1773  +   Example usage:
         1774  +
         1775  +   @code
         1776  +   cson_buffer buf = cson_buffer_empty;
         1777  +   // optional: cson_buffer_reserve(&buf, 1024 * 10);
         1778  +   int rc = cson_output_buffer( myValue, &buf, NULL );
         1779  +   if( 0 != rc ) {
         1780  +       ... error! ...
         1781  +   }
         1782  +   else {
         1783  +       ... use buffer ...
         1784  +       puts((char const*)buf.mem);
         1785  +   }
         1786  +   // In both cases, we eventually need to clean up the buffer:
         1787  +   cson_buffer_reserve( &buf, 0 );
         1788  +   // Or take over ownership of its memory:
         1789  +   {
         1790  +       char * mem = (char *)buf.mem;
         1791  +       buf = cson_buffer_empty;
         1792  +       ...
         1793  +       free(mem);
         1794  +   }
         1795  +   @endcode
         1796  +   
         1797  +   @see cson_output()
         1798  +   
         1799  +*/
         1800  +int cson_output_buffer( cson_value const * v, cson_buffer * buf,
         1801  +                        cson_output_opt const * opt );
         1802  +
         1803  +/**
         1804  +   This works identically to cson_parse_string(), but takes a
         1805  +   cson_buffer object as its input.  buf->used bytes of buf->mem are
         1806  +   assumed to be valid JSON input, but it need not be NUL-terminated
         1807  +   (we only read up to buf->used bytes). The value of buf->used is
         1808  +   assumed to be the "string length" of buf->mem, i.e. not including
         1809  +   the NUL terminator.
         1810  +
         1811  +   Returns 0 on success, non-0 on error.
         1812  +
         1813  +   See cson_parse() for the semantics of the tgt, opt, and err
         1814  +   parameters.
         1815  +*/
         1816  +int cson_parse_buffer( cson_value ** tgt, cson_buffer const * buf,
         1817  +                       cson_parse_opt const * opt, cson_parse_info * err );
         1818  +
         1819  +
         1820  +/**
         1821  +   Reserves the given amount of memory for the given buffer object.
         1822  +
         1823  +   If n is 0 then buf->mem is freed and its state is set to
         1824  +   NULL/0 values.
         1825  +
         1826  +   If buf->capacity is less than or equal to n then 0 is returned and
         1827  +   buf is not modified.
         1828  +
         1829  +   If n is larger than buf->capacity then buf->mem is (re)allocated
         1830  +   and buf->capacity contains the new length. Newly-allocated bytes
         1831  +   are filled with zeroes.
         1832  +
         1833  +   On success 0 is returned. On error non-0 is returned and buf is not
         1834  +   modified.
         1835  +
         1836  +   buf->mem is owned by buf and must eventually be freed by passing an
         1837  +   n value of 0 to this function.
         1838  +
         1839  +   buf->used is never modified by this function.
         1840  +*/
         1841  +int cson_buffer_reserve( cson_buffer * buf, cson_size_t n );
         1842  +
         1843  +/**
         1844  +   Fills all bytes of the given buffer with the given character.
         1845  +   Returns the number of bytes set (buf->capacity), or 0 if
         1846  +   !buf or buf has no memory allocated to it.
         1847  +*/
         1848  +cson_size_t cson_buffer_fill( cson_buffer * buf, char c );
         1849  +
         1850  +/**
         1851  +    Uses a cson_data_source_f() function to buffer input into a
         1852  +    cson_buffer.
         1853  +
         1854  +   dest must be a non-NULL, initialized (though possibly empty)
         1855  +   cson_buffer object. Its contents, if any, will be overwritten by
         1856  +   this function, and any memory it holds might be re-used.
         1857  +
         1858  +   The src function is called, and passed the state parameter, to
         1859  +   fetch the input. If it returns non-0, this function returns that
         1860  +   error code. src() is called, possibly repeatedly, until it reports
         1861  +   that there is no more data.
         1862  +
         1863  +   Whether or not this function succeeds, dest still owns any memory
         1864  +   pointed to by dest->mem, and the client must eventually free it by
         1865  +   calling cson_buffer_reserve(dest,0).
         1866  +
         1867  +   dest->mem might (and possibly will) be (re)allocated by this
         1868  +   function, so any pointers to it held from before this call might be
         1869  +   invalidated by this call.
         1870  +   
         1871  +   On error non-0 is returned and dest has almost certainly been
         1872  +   modified but its state must be considered incomplete.
         1873  +
         1874  +   Errors include:
         1875  +
         1876  +   - dest or src are NULL (cson_rc.ArgError)
         1877  +
         1878  +   - Allocation error (cson_rc.AllocError)
         1879  +
         1880  +   - src() returns an error code
         1881  +
         1882  +   Whether or not the state parameter may be NULL depends on
         1883  +   the src implementation requirements.
         1884  +
         1885  +   On success dest will contain the contents read from the input
         1886  +   source. dest->used will be the length of the read-in data, and
         1887  +   dest->mem will point to the memory. dest->mem is automatically
         1888  +   NUL-terminated if this function succeeds, but dest->used does not
         1889  +   count that terminator. On error the state of dest->mem must be
         1890  +   considered incomplete, and is not guaranteed to be NUL-terminated.
         1891  +
         1892  +    Example usage:
         1893  +
         1894  +    @code
         1895  +    cson_buffer buf = cson_buffer_empty;
         1896  +    int rc = cson_buffer_fill_from( &buf,
         1897  +                                    cson_data_source_FILE,
         1898  +                                    stdin );
         1899  +    if( rc )
         1900  +    {
         1901  +        fprintf(stderr,"Error %d (%s) while filling buffer.\n",
         1902  +                rc, cson_rc_string(rc));
         1903  +        cson_buffer_reserve( &buf, 0 );
         1904  +        return ...;
         1905  +    }
         1906  +    ... use the buf->mem ...
         1907  +    ... clean up the buffer ...
         1908  +    cson_buffer_reserve( &buf, 0 );
         1909  +    @endcode
         1910  +
         1911  +    To take over ownership of the buffer's memory, do:
         1912  +
         1913  +    @code
         1914  +    void * mem = buf.mem;
         1915  +    buf = cson_buffer_empty;
         1916  +    @endcode
         1917  +
         1918  +    In which case the memory must eventually be passed to free() to
         1919  +    free it.    
         1920  +*/
         1921  +int cson_buffer_fill_from( cson_buffer * dest, cson_data_source_f src, void * state );
         1922  +
         1923  +
         1924  +/**
         1925  +   Increments the reference count for the given value. This is a
         1926  +   low-level operation and should not normally be used by client code
         1927  +   without understanding exactly what side-effects it introduces.
         1928  +   Mis-use can lead to premature destruction or cause a value instance
         1929  +   to never be properly destructed (i.e. a memory leak).
         1930  +
         1931  +   This function is probably only useful for the following cases:
         1932  +
         1933  +   - You want to hold a reference to a value which is itself contained
         1934  +   in one or more containers, and you need to be sure that your
         1935  +   reference outlives the container(s) and/or that you can free your
         1936  +   copy of the reference without invaliding any references to the same
         1937  +   value held in containers.
         1938  +
         1939  +   - You want to implement "value sharing" behaviour without using an
         1940  +   object or array to contain the shared value. This can be used to
         1941  +   ensure the lifetime of the shared value instance. Each sharing
         1942  +   point adds a reference and simply passed the value to
         1943  +   cson_value_free() when they're done. The object will be kept alive
         1944  +   for other sharing points which added a reference.
         1945  +
         1946  +   Normally any such value handles would be invalidated when the
         1947  +   parent container(s) is/are cleaned up, but this function can be
         1948  +   used to effectively delay the cleanup.
         1949  +   
         1950  +   This function, at its lowest level, increments the value's
         1951  +   reference count by 1.
         1952  +
         1953  +   To decrement the reference count, pass the value to
         1954  +   cson_value_free(), after which the value must be considered, from
         1955  +   the perspective of that client code, to be destroyed (though it
         1956  +   will not be if there are still other live references to
         1957  +   it). cson_value_free() will not _actually_ destroy the value until
         1958  +   its reference count drops to 0.
         1959  +
         1960  +   Returns 0 on success. The only error conditions are if v is NULL
         1961  +   (cson_rc.ArgError) or if the reference increment would overflow
         1962  +   (cson_rc.RangeError). In theory a client would get allocation
         1963  +   errors long before the reference count could overflow (assuming
         1964  +   those reference counts come from container insertions, as opposed
         1965  +   to via this function).
         1966  +
         1967  +   Insider notes which clients really need to know:
         1968  +   
         1969  +   For shared/constant value instances, such as those returned by
         1970  +   cson_value_true() and cson_value_null(), this function has no side
         1971  +   effects - it does not actually modify the reference count because
         1972  +   (A) those instances are shared across all client code and (B) those
         1973  +   objects are static and never get cleaned up. However, that is an
         1974  +   implementation detail which client code should not rely on. In
         1975  +   other words, if you call cson_value_add_reference() 3 times using
         1976  +   the value returned by cson_value_true() (which is incidentally a
         1977  +   shared cson_value instance), you must eventually call
         1978  +   cson_value_free() 3 times to (semantically) remove those
         1979  +   references. However, internally the reference count for that
         1980  +   specific cson_value instance will not be modified and those
         1981  +   objects will never be freed (they're stack-allocated).
         1982  +
         1983  +   It might be interesting to note that newly-created objects
         1984  +   have a reference count of 0 instead of 1. This is partly because
         1985  +   if the initial reference is counted then it makes ownership
         1986  +   problematic when inserting values into containers. e.g. consider the
         1987  +   following code:
         1988  +
         1989  +   @code
         1990  +   // ACHTUNG: this code is hypothetical and does not reflect
         1991  +   // what actually happens!
         1992  +   cson_value * v =
         1993  +        cson_value_new_integer( 42 ); // v's refcount = 1
         1994  +   cson_array_append( myArray, v ); // v's refcount = 2
         1995  +   @endcode
         1996  +
         1997  +   If that were the case, the client would be forced to free his own
         1998  +   reference after inserting it into the container (which is a bit
         1999  +   counter-intuitive as well as intrusive). It would look a bit like
         2000  +   the following and would have to be done after every create/insert
         2001  +   operation:
         2002  +
         2003  +   @code
         2004  +   // ACHTUNG: this code is hypothetical and does not reflect
         2005  +   // what actually happens!
         2006  +   cson_array_append( myArray, v ); // v's refcount = 2
         2007  +   cson_value_free( v ); // v's refcount = 1
         2008  +   @endcode
         2009  +
         2010  +   (As i said: it's counter-intuitive and intrusive.)
         2011  +
         2012  +   Instead, values start with a refcount of 0 and it is only increased
         2013  +   when the value is added to an object/array container or when this
         2014  +   function is used to manually increment it. cson_value_free() treats
         2015  +   a refcount of 0 or 1 equivalently, destroying the value
         2016  +   instance. The only semantic difference between 0 and 1, for
         2017  +   purposes of cleaning up, is that a value with a non-0 refcount has
         2018  +   been had its refcount adjusted, whereas a 0 refcount indicates a
         2019  +   fresh, "unowned" reference.
         2020  +*/
         2021  +int cson_value_add_reference( cson_value * v );
         2022  +
         2023  +#if 0
         2024  +/**
         2025  +   DO NOT use this unless you know EXACTLY what you're doing.
         2026  +   It is only in the public API to work around a couple corner
         2027  +   cases involving extracting child elements and discarding
         2028  +   their parents.
         2029  +
         2030  +   This function sets v's reference count to the given value.
         2031  +   It does not clean up the object if rc is 0.
         2032  +
         2033  +   Returns 0 on success, non-0 on error.
         2034  +*/
         2035  +int cson_value_refcount_set( cson_value * v, unsigned short rc );
         2036  +#endif
         2037  +
         2038  +/**
         2039  +   Deeply copies a JSON value, be it an object/array or a "plain"
         2040  +   value (e.g. number/string/boolean). If cv is not NULL then this
         2041  +   function makes a deep clone of it and returns that clone. Ownership
         2042  +   of the clone is transfered to the caller, who must eventually free
         2043  +   the value using cson_value_free() or add it to a container
         2044  +   object/array to transfer ownership to the container. The returned
         2045  +   object will be of the same logical type as orig.
         2046  +
         2047  +   ACHTUNG: if orig contains any cyclic references at any depth level
         2048  +   this function will endlessly recurse. (Having _any_ cyclic
         2049  +   references violates this library's requirements.)
         2050  +   
         2051  +   Returns NULL if orig is NULL or if cloning fails. Assuming that
         2052  +   orig is in a valid state, the only "likely" error case is that an
         2053  +   allocation fails while constructing the clone. In other words, if
         2054  +   cloning fails due to something other than an allocation error then
         2055  +   either orig is in an invalid state or there is a bug.
         2056  +*/
         2057  +cson_value * cson_value_clone( cson_value const * orig );
         2058  +
         2059  +/**
         2060  +   Returns the value handle associated with s. The handle itself owns
         2061  +   s, and ownership of the handle is not changed by calling this
         2062  +   function. If the returned handle is part of a container, calling
         2063  +   cson_value_free() on the returned handle invoked undefined
         2064  +   behaviour (quite possibly downstream when the container tries to
         2065  +   use it).
         2066  +
         2067  +   This function only returns NULL if s. is NULL.
         2068  +*/
         2069  +cson_value * cson_string_value(cson_string const * s);
         2070  +/**
         2071  +   The Object form of cson_string_value(). See that function
         2072  +   for full details.
         2073  +*/
         2074  +cson_value * cson_object_value(cson_object const * s);
         2075  +
         2076  +/**
         2077  +   The Array form of cson_string_value(). See that function
         2078  +   for full details.
         2079  +*/
         2080  +cson_value * cson_array_value(cson_array const * s);
         2081  +
         2082  +
         2083  +/**
         2084  +   Calculates the in-memory-allocated size of v, recursively if it is
         2085  +   a container type, with the following caveats and limitations:
         2086  +
         2087  +   If a given value is reference counted and multiple times within a
         2088  +   traversed container, each reference is counted at full cost. We
         2089  +   have no what of knowing if a given reference has been visited
         2090  +   already and whether it should or should not be counted, so we
         2091  +   pessimistically count them even though the _might_ not really count
         2092  +   for the given object tree (it depends on where the other open
         2093  +   references live).
         2094  +
         2095  +   This function returns 0 if any of the following are true:
         2096  +
         2097  +   - v is NULL
         2098  +
         2099  +   - v is one of the special singleton values (null, bools, empty
         2100  +   string, int 0, double 0.0)
         2101  +
         2102  +   All other values require an allocation, and this will return their
         2103  +   total memory cost, including the cson-specific internals and the
         2104  +   native value(s).
         2105  +
         2106  +   Note that because arrays and objects might have more internal slots
         2107  +   allocated than used, the alloced size of a container does not
         2108  +   necessarily increase when a new item is inserted into it. An interesting
         2109  +   side-effect of this is that when cson_clone()ing an array or object, the
         2110  +   size of the clone can actually be less than the original.
         2111  +*/
         2112  +unsigned int cson_value_msize(cson_value const * v);
         2113  +
         2114  +/* LICENSE
         2115  +
         2116  +This software's source code, including accompanying documentation and
         2117  +demonstration applications, are licensed under the following
         2118  +conditions...
         2119  +
         2120  +Certain files are imported from external projects and have their own
         2121  +licensing terms. Namely, the JSON_parser.* files. See their files for
         2122  +their official licenses, but the summary is "do what you want [with
         2123  +them] but leave the license text and copyright in place."
         2124  +
         2125  +The author (Stephan G. Beal [http://wanderinghorse.net/home/stephan/])
         2126  +explicitly disclaims copyright in all jurisdictions which recognize
         2127  +such a disclaimer. In such jurisdictions, this software is released
         2128  +into the Public Domain.
         2129  +
         2130  +In jurisdictions which do not recognize Public Domain property
         2131  +(e.g. Germany as of 2011), this software is Copyright (c) 2011 by
         2132  +Stephan G. Beal, and is released under the terms of the MIT License
         2133  +(see below).
         2134  +
         2135  +In jurisdictions which recognize Public Domain property, the user of
         2136  +this software may choose to accept it either as 1) Public Domain, 2)
         2137  +under the conditions of the MIT License (see below), or 3) under the
         2138  +terms of dual Public Domain/MIT License conditions described here, as
         2139  +they choose.
         2140  +
         2141  +The MIT License is about as close to Public Domain as a license can
         2142  +get, and is described in clear, concise terms at:
         2143  +
         2144  +    http://en.wikipedia.org/wiki/MIT_License
         2145  +
         2146  +The full text of the MIT License follows:
         2147  +
         2148  +--
         2149  +Copyright (c) 2011 Stephan G. Beal (http://wanderinghorse.net/home/stephan/)
         2150  +
         2151  +Permission is hereby granted, free of charge, to any person
         2152  +obtaining a copy of this software and associated documentation
         2153  +files (the "Software"), to deal in the Software without
         2154  +restriction, including without limitation the rights to use,
         2155  +copy, modify, merge, publish, distribute, sublicense, and/or sell
         2156  +copies of the Software, and to permit persons to whom the
         2157  +Software is furnished to do so, subject to the following
         2158  +conditions:
         2159  +
         2160  +The above copyright notice and this permission notice shall be
         2161  +included in all copies or substantial portions of the Software.
         2162  +
         2163  +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         2164  +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
         2165  +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         2166  +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
         2167  +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
         2168  +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
         2169  +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
         2170  +OTHER DEALINGS IN THE SOFTWARE.
         2171  +
         2172  +--END OF MIT LICENSE--
         2173  +
         2174  +For purposes of the above license, the term "Software" includes
         2175  +documentation and demonstration source code which accompanies
         2176  +this software. ("Accompanies" = is contained in the Software's
         2177  +primary public source code repository.)
         2178  +
         2179  +*/
         2180  +
         2181  +#if defined(__cplusplus)
         2182  +} /*extern "C"*/
         2183  +#endif
         2184  +
         2185  +#endif /* WANDERINGHORSE_NET_CSON_H_INCLUDED */
         2186  +/* end file include/wh/cson/cson.h */
         2187  +/* begin file include/wh/cson/cson_sqlite3.h */
         2188  +/** @file cson_sqlite3.h
         2189  +
         2190  +This file contains cson's public sqlite3-to-JSON API declarations
         2191  +and API documentation. If CSON_ENABLE_SQLITE3 is not defined,
         2192  +or is defined to 0, then including this file will have no side-effects
         2193  +other than defining CSON_ENABLE_SQLITE3 (if it was not defined) to 0
         2194  +and defining a few include guard macros. i.e. if CSON_ENABLE_SQLITE3
         2195  +is not set to a true value then the API is not visible.
         2196  +
         2197  +This API requires that <sqlite3.h> be in the INCLUDES path and that
         2198  +the client eventually link to (or directly embed) the sqlite3 library.
         2199  +*/
         2200  +#if !defined(WANDERINGHORSE_NET_CSON_SQLITE3_H_INCLUDED)
         2201  +#define WANDERINGHORSE_NET_CSON_SQLITE3_H_INCLUDED 1
         2202  +#if !defined(CSON_ENABLE_SQLITE3)
         2203  +#  if defined(DOXYGEN)
         2204  +#define CSON_ENABLE_SQLITE3 1
         2205  +#  else
         2206  +#define CSON_ENABLE_SQLITE3 1
         2207  +#  endif
         2208  +#endif
         2209  +
         2210  +#if CSON_ENABLE_SQLITE3 /* we do this here for the sake of the amalgamation build */
         2211  +#include <sqlite3.h>
         2212  +
         2213  +#if defined(__cplusplus)
         2214  +extern "C" {
         2215  +#endif
         2216  +
         2217  +/**
         2218  +   Converts a single value from a single 0-based column index to its JSON
         2219  +   equivalent.
         2220  +
         2221  +   On success it returns a new JSON value, which will have a different concrete
         2222  +   type depending on the field type reported by sqlite3_column_type(st,col):
         2223  +
         2224  +   Integer, double, null, or string (TEXT and BLOB data, though not
         2225  +   all blob data is legal for a JSON string).
         2226  +
         2227  +   st must be a sqlite3_step()'d row and col must be a 0-based column
         2228  +   index within that result row.
         2229  + */       
         2230  +cson_value * cson_sqlite3_column_to_value( sqlite3_stmt * st, int col );
         2231  +
         2232  +/**
         2233  +   Creates a JSON Array object containing the names of all columns
         2234  +   of the given prepared statement handle. 
         2235  +    
         2236  +   Returns a new array value on success, which the caller owns. Its elements
         2237  +   are in the same order as in the underlying query.
         2238  +
         2239  +   On error NULL is returned.
         2240  +    
         2241  +   st is not traversed or freed by this function - only the column
         2242  +   count and names are read.
         2243  +*/
         2244  +cson_value * cson_sqlite3_column_names( sqlite3_stmt * st );
         2245  +
         2246  +/**
         2247  +   Creates a JSON Object containing key/value pairs corresponding
         2248  +   to the result columns in the current row of the given statement
         2249  +   handle. st must be a sqlite3_step()'d row result.
         2250  +
         2251  +   On success a new Object is returned which is owned by the
         2252  +   caller. On error NULL is returned.
         2253  +
         2254  +   cson_sqlite3_column_to_value() is used to convert each column to a
         2255  +   JSON value, and the column names are taken from
         2256  +   sqlite3_column_name().
         2257  +*/
         2258  +cson_value * cson_sqlite3_row_to_object( sqlite3_stmt * st );
         2259  +/**
         2260  +   Functionally almost identical to cson_sqlite3_row_to_object(), the
         2261  +   only difference being how the result objects gets its column names.
         2262  +   st must be a freshly-step()'d handle holding a result row.
         2263  +   colNames must be an Array with at least the same number of columns
         2264  +   as st. If it has fewer, NULL is returned and this function has
         2265  +   no side-effects.
         2266  +
         2267  +   For each column in the result set, the colNames entry at the same
         2268  +   index is used for the column key. If a given entry is-not-a String
         2269  +   then conversion will fail and NULL will be returned.
         2270  +
         2271  +   The one reason to prefer this over cson_sqlite3_row_to_object() is
         2272  +   that this one can share the keys across multiple rows (or even
         2273  +   other JSON containers), whereas the former makes fresh copies of
         2274  +   the column names for each row.
         2275  +
         2276  +*/
         2277  +cson_value * cson_sqlite3_row_to_object2( sqlite3_stmt * st,
         2278  +                                          cson_array * colNames );
         2279  +
         2280  +/**
         2281  +   Similar to cson_sqlite3_row_to_object(), but creates an Array
         2282  +   value which contains the JSON-form values of the given result
         2283  +   set row.
         2284  +*/
         2285  +cson_value * cson_sqlite3_row_to_array( sqlite3_stmt * st );
         2286  +/**
         2287  +    Converts the results of an sqlite3 SELECT statement to JSON,
         2288  +    in the form of a cson_value object tree.
         2289  +    
         2290  +    st must be a prepared, but not yet traversed, SELECT query.
         2291  +    tgt must be a pointer to NULL (see the example below). If
         2292  +    either of those arguments are NULL, cson_rc.ArgError is returned.
         2293  +    
         2294  +    This walks the query results and returns a JSON object which
         2295  +    has a different structure depending on the value of the 'fat'
         2296  +    argument.
         2297  +    
         2298  +    
         2299  +    If 'fat' is 0 then the structure is:
         2300  +    
         2301  +    @code
         2302  +    {
         2303  +        "columns":["colName1",..."colNameN"],
         2304  +        "rows":[
         2305  +            [colVal0, ... colValN],
         2306  +            [colVal0, ... colValN],
         2307  +            ...
         2308  +        ]
         2309  +    }
         2310  +    @endcode
         2311  +    
         2312  +    In the "non-fat" format the order of the columns and row values is
         2313  +    guaranteed to be the same as that of the underlying query.
         2314  +    
         2315  +    If 'fat' is not 0 then the structure is:
         2316  +    
         2317  +    @code
         2318  +    {
         2319  +        "columns":["colName1",..."colNameN"],
         2320  +        "rows":[
         2321  +            {"colName1":value1,..."colNameN":valueN},
         2322  +            {"colName1":value1,..."colNameN":valueN},
         2323  +            ...
         2324  +        ]
         2325  +    }
         2326  +    @endcode
         2327  +
         2328  +    In the "fat" format, the order of the "columns" entries is guaranteed
         2329  +    to be the same as the underlying query fields, but the order
         2330  +    of the keys in the "rows" might be different and might in fact
         2331  +    change when passed through different JSON implementations,
         2332  +    depending on how they implement object key/value pairs.
         2333  +
         2334  +    On success it returns 0 and assigns *tgt to a newly-allocated
         2335  +    JSON object tree (using the above structure), which the caller owns.
         2336  +    If the query returns no rows, the "rows" value will be an empty
         2337  +    array, as opposed to null.
         2338  +    
         2339  +    On error non-0 is returned and *tgt is not modified.
         2340  +    
         2341  +    The error code cson_rc.IOError is used to indicate a db-level
         2342  +    error, and cson_rc.TypeError is returned if sqlite3_column_count(st)
         2343  +    returns 0 or less (indicating an invalid or non-SELECT statement).
         2344  +    
         2345  +    The JSON data types are determined by the column type as reported
         2346  +    by sqlite3_column_type():
         2347  +    
         2348  +    SQLITE_INTEGER: integer
         2349  +    
         2350  +    SQLITE_FLOAT: double
         2351  +    
         2352  +    SQLITE_TEXT or SQLITE_BLOB: string, and this will only work if
         2353  +    the data is UTF8 compatible.
         2354  +    
         2355  +    If the db returns a literal or SQL NULL for a value it is converted
         2356  +    to a JSON null. If it somehow finds a column type it cannot handle,
         2357  +    the value is also converted to a NULL in the output.
         2358  +
         2359  +    Example
         2360  +    
         2361  +    @code
         2362  +    cson_value * json = NULL;
         2363  +    int rc = cson_sqlite3_stmt_to_json( myStatement, &json, 1 );
         2364  +    if( 0 != rc ) { ... error ... }
         2365  +    else {
         2366  +        cson_output_FILE( json, stdout, NULL );
         2367  +        cson_value_free( json );
         2368  +    }
         2369  +    @endcode
         2370  +*/
         2371  +int cson_sqlite3_stmt_to_json( sqlite3_stmt * st, cson_value ** tgt, char fat );
         2372  +
         2373  +/**
         2374  +    A convenience wrapper around cson_sqlite3_stmt_to_json(), which
         2375  +    takes SQL instead of a sqlite3_stmt object. It has the same
         2376  +    return value and argument semantics as that function.
         2377  +*/
         2378  +int cson_sqlite3_sql_to_json( sqlite3 * db, cson_value ** tgt, char const * sql, char fat );
         2379  +
         2380  +#if defined(__cplusplus)
         2381  +} /*extern "C"*/
         2382  +#endif
         2383  +    
         2384  +#endif /* CSON_ENABLE_SQLITE3 */
         2385  +#endif /* WANDERINGHORSE_NET_CSON_SQLITE3_H_INCLUDED */
         2386  +/* end file include/wh/cson/cson_sqlite3.h */
         2387  +
         2388  +#endif /* FOSSIL_ENABLE_JSON */

Changes to src/db.c.

    46     46   */
    47     47   struct Stmt {
    48     48     Blob sql;               /* The SQL for this statement */
    49     49     sqlite3_stmt *pStmt;    /* The results of sqlite3_prepare() */
    50     50     Stmt *pNext, *pPrev;    /* List of all unfinalized statements */
    51     51     int nStep;              /* Number of sqlite3_step() calls */
    52     52   };
           53  +
           54  +/*
           55  +** Copy this to initialize a Stmt object to a clean/empty state. This
           56  +** is useful to help avoid assertions when performing cleanup in some
           57  +** error handling cases.
           58  +*/
           59  +#define empty_Stmt_m {BLOB_INITIALIZER,NULL, NULL, NULL, 0}
    53     60   #endif /* INTERFACE */
           61  +const struct Stmt empty_Stmt = empty_Stmt_m;
    54     62   
    55     63   /*
    56     64   ** Call this routine when a database error occurs.
    57     65   */
    58     66   static void db_err(const char *zFormat, ...){
    59     67     va_list ap;
    60     68     char *z;
           69  +  int rc = 1;
    61     70     static const char zRebuildMsg[] = 
    62     71         "If you have recently updated your fossil executable, you might\n"
    63     72         "need to run \"fossil all rebuild\" to bring the repository\n"
    64     73         "schemas up to date.\n";
    65     74     va_start(ap, zFormat);
    66     75     z = vmprintf(zFormat, ap);
    67     76     va_end(ap);
           77  +#ifdef FOSSIL_ENABLE_JSON
           78  +  if( g.json.isJsonMode ){
           79  +    json_err( 0, z, 1 );
           80  +    if( g.isHTTP ){
           81  +      rc = 0 /* avoid HTTP 500 */;
           82  +    }
           83  +  }
           84  +  else
           85  +#endif /* FOSSIL_ENABLE_JSON */
    68     86     if( g.xferPanic ){
    69     87       cgi_reset_content();
    70     88       @ error Database\serror:\s%F(z)
    71         -    cgi_reply();
           89  +      cgi_reply();
    72     90     }
    73         -  if( g.cgiOutput ){
           91  +  else if( g.cgiOutput ){
    74     92       g.cgiOutput = 0;
    75     93       cgi_printf("<h1>Database Error</h1>\n"
    76     94                  "<pre>%h</pre><p>%s</p>", z, zRebuildMsg);
    77     95       cgi_reply();
    78     96     }else{
    79     97       fprintf(stderr, "%s: %s\n\n%s", fossil_nameofexe(), z, zRebuildMsg);
    80     98     }
           99  +  free(z);
    81    100     db_force_rollback();
    82         -  fossil_exit(1);
          101  +  fossil_exit(rc);
    83    102   }
    84    103   
    85    104   static int nBegin = 0;      /* Nesting depth of BEGIN */
    86    105   static int doRollback = 0;  /* True to force a rollback */
    87    106   static int nCommitHook = 0; /* Number of commit hooks */
    88    107   static struct sCommitHook {
    89    108     int (*xHook)(void);  /* Functions to call at db_end_transaction() */
................................................................................
   559    578   
   560    579   /*
   561    580   ** Execute a query.  Return the first column of the first row
   562    581   ** of the result set as a string.  Space to hold the string is
   563    582   ** obtained from malloc().  If the result set is empty, return
   564    583   ** zDefault instead.
   565    584   */
   566         -char *db_text(char *zDefault, const char *zSql, ...){
          585  +char *db_text(char const *zDefault, const char *zSql, ...){
   567    586     va_list ap;
   568    587     Stmt s;
   569    588     char *z;
   570    589     va_start(ap, zSql);
   571    590     db_vprepare(&s, 0, zSql, ap);
   572    591     va_end(ap);
   573    592     if( db_step(&s)==SQLITE_ROW ){
................................................................................
   861    880       }
   862    881       if( zDbName==0 ){
   863    882         db_err("unable to find the name of a repository database");
   864    883       }
   865    884     }
   866    885     if( file_access(zDbName, R_OK) || file_size(zDbName)<1024 ){
   867    886       if( file_access(zDbName, 0) ){
          887  +#ifdef FOSSIL_ENABLE_JSON
          888  +      g.json.resultCode = FSL_JSON_E_DB_NOT_FOUND;
          889  +#endif
   868    890         fossil_panic("repository does not exist or"
   869    891                      " is in an unreadable directory: %s", zDbName);
   870    892       }else if( file_access(zDbName, R_OK) ){
          893  +#ifdef FOSSIL_ENABLE_JSON
          894  +      g.json.resultCode = FSL_JSON_E_DENIED;
          895  +#endif
   871    896         fossil_panic("read permission denied for repository %s", zDbName);
   872    897       }else{
          898  +#ifdef FOSSIL_ENABLE_JSON
          899  +      g.json.resultCode = FSL_JSON_E_DB_NOT_VALID;
          900  +#endif
   873    901         fossil_panic("not a valid repository: %s", zDbName);
   874    902       }
   875    903     }
   876    904     db_open_or_attach(zDbName, "repository");
   877    905     g.repositoryOpen = 1;
   878    906     g.zRepositoryName = mprintf("%s", zDbName);
   879    907     /* Cache "allow-symlinks" option, because we'll need it on every stat call */
................................................................................
   900    928     if( zRep==0 && nArgUsed && g.argc==nArgUsed+1 ){
   901    929       zRep = g.argv[nArgUsed];
   902    930     }
   903    931     if( zRep==0 ){
   904    932       if( db_open_local()==0 ){
   905    933         goto rep_not_found;
   906    934       }
   907         -    zRep = db_lget("repository", 0);
          935  +    zRep = db_lget("repository", 0)/*leak here*/;
   908    936       if( zRep==0 ){
   909    937         goto rep_not_found;
   910    938       }
   911    939     }
   912    940     db_open_repository(zRep);
   913    941     if( g.repositoryOpen ){
   914    942       if( (bFlags & OPEN_ANY_SCHEMA)==0 ) db_verify_schema();
   915    943       return;
   916    944     }
   917    945   rep_not_found:
   918    946     if( (bFlags & OPEN_OK_NOT_FOUND)==0 ){
          947  +#ifdef FOSSIL_ENABLE_JSON
          948  +    g.json.resultCode = FSL_JSON_E_DB_NOT_FOUND;
          949  +#endif
   919    950       fossil_fatal("use --repository or -R to specify the repository database");
   920    951     }
   921    952   }
   922    953   
   923    954   /*
   924    955   ** Return the name of the database "localdb", "configdb", or "repository".
   925    956   */
................................................................................
   942    973   
   943    974   /*
   944    975   ** Verify that the repository schema is correct.  If it is not correct,
   945    976   ** issue a fatal error and die.
   946    977   */
   947    978   void db_verify_schema(void){
   948    979     if( db_schema_is_outofdate() ){
          980  +#ifdef FOSSIL_ENABLE_JSON
          981  +    g.json.resultCode = FSL_JSON_E_DB_NEEDS_REBUILD;
          982  +#endif
   949    983       fossil_warning("incorrect repository schema version");
   950    984       fossil_warning("your repository has schema version \"%s\" "
   951    985             "but this binary expects version \"%s\"",
   952    986             db_get("aux-schema",0), AUX_SCHEMA);
   953    987       fossil_fatal("run \"fossil rebuild\" to fix this problem");
   954    988     }
   955    989   }

Added src/json.c.

            1  +#ifdef FOSSIL_ENABLE_JSON
            2  +/*
            3  +** Copyright (c) 2011 D. Richard Hipp
            4  +**
            5  +** This program is free software; you can redistribute it and/or
            6  +** modify it under the terms of the Simplified BSD License (also
            7  +** known as the "2-Clause License" or "FreeBSD License".)
            8  +
            9  +** This program is distributed in the hope that it will be useful,
           10  +** but without any warranty; without even the implied warranty of
           11  +** merchantability or fitness for a particular purpose.
           12  +**
           13  +** Author contact information:
           14  +**   drh@hwaci.com
           15  +**   http://www.hwaci.com/drh/
           16  +**
           17  +*******************************************************************************
           18  +**  
           19  +** Code for the JSON API.
           20  +**
           21  +** For notes regarding the public JSON interface, please see:
           22  +**
           23  +** https://docs.google.com/document/d/1fXViveNhDbiXgCuE7QDXQOKeFzf2qNUkBEgiUvoqFN4/edit
           24  +**
           25  +**
           26  +** Notes for hackers...
           27  +**
           28  +** Here's how command/page dispatching works: json_page_top() (in HTTP mode) or
           29  +** json_cmd_top() (in CLI mode) catch the "json" path/command. Those functions then
           30  +** dispatch to a JSON-mode-specific command/page handler with the type fossil_json_f().
           31  +** See the API docs for that typedef (below) for the semantics of the callbacks.
           32  +**
           33  +**
           34  +*/
           35  +#include "config.h"
           36  +#include "VERSION.h"
           37  +#include "json.h"
           38  +#include <assert.h>
           39  +#include <time.h>
           40  +
           41  +#if INTERFACE
           42  +#include "json_detail.h" /* workaround for apparent enum limitation in makeheaders */
           43  +#endif
           44  +
           45  +const FossilJsonKeys_ FossilJsonKeys = {
           46  +  "anonymousSeed" /*anonymousSeed*/,
           47  +  "authToken"  /*authToken*/,
           48  +  "COMMAND_PATH" /*commandPath*/,
           49  +  "mtime" /*mtime*/,
           50  +  "payload" /* payload */,
           51  +  "requestId" /*requestId*/,
           52  +  "resultCode" /*resultCode*/,
           53  +  "resultText" /*resultText*/,
           54  +  "timestamp" /*timestamp*/
           55  +};
           56  +
           57  +/*
           58  +** Internal helpers to manipulate a byte array as a bitset. The B
           59  +** argument must be-a array at least (BIT/8+1) bytes long.
           60  +** The BIT argument is the bit number to query/set/clear/toggle.
           61  +*/
           62  +#define BITSET_BYTEFOR(B,BIT) ((B)[ BIT / 8 ])
           63  +#define BITSET_SET(B,BIT) ((BITSET_BYTEFOR(B,BIT) |= (0x01 << (BIT%8))),0x01)
           64  +#define BITSET_UNSET(B,BIT) ((BITSET_BYTEFOR(B,BIT) &= ~(0x01 << (BIT%8))),0x00)
           65  +#define BITSET_GET(B,BIT) ((BITSET_BYTEFOR(B,BIT) & (0x01 << (BIT%8))) ? 0x01 : 0x00)
           66  +#define BITSET_TOGGLE(B,BIT) (BITSET_GET(B,BIT) ? (BITSET_UNSET(B,BIT)) : (BITSET_SET(B,BIT)))
           67  +
           68  +
           69  +/* Timer code taken from sqlite3's shell.c, modified slightly.
           70  +   FIXME: move the timer into the fossil core API so that we can
           71  +   start the timer early on in the app init phase. Right now we're
           72  +   just timing the json ops themselves.
           73  +*/
           74  +#if !defined(_WIN32) && !defined(WIN32) && !defined(__OS2__) && !defined(__RTP__) && !defined(_WRS_KERNEL)
           75  +#include <sys/time.h>
           76  +#include <sys/resource.h>
           77  +
           78  +/* Saved resource information for the beginning of an operation */
           79  +static struct rusage sBegin;
           80  +
           81  +/*
           82  +** Begin timing an operation
           83  +*/
           84  +static void beginTimer(void){
           85  +  getrusage(RUSAGE_SELF, &sBegin);
           86  +}
           87  +
           88  +/* Return the difference of two time_structs in milliseconds */
           89  +static double timeDiff(struct timeval *pStart, struct timeval *pEnd){
           90  +  return ((pEnd->tv_usec - pStart->tv_usec)*0.001 + 
           91  +          (double)((pEnd->tv_sec - pStart->tv_sec)*1000.0));
           92  +}
           93  +
           94  +/*
           95  +** Print the timing results.
           96  +*/
           97  +static double endTimer(void){
           98  +  struct rusage sEnd;
           99  +  getrusage(RUSAGE_SELF, &sEnd);
          100  +  return timeDiff(&sBegin.ru_utime, &sEnd.ru_utime)
          101  +    + timeDiff(&sBegin.ru_stime, &sEnd.ru_stime);
          102  +#if 0
          103  +  printf("CPU Time: user %f sys %f\n",
          104  +         timeDiff(&sBegin.ru_utime, &sEnd.ru_utime),
          105  +         timeDiff(&sBegin.ru_stime, &sEnd.ru_stime));
          106  +#endif
          107  +}
          108  +
          109  +#define BEGIN_TIMER beginTimer()
          110  +#define END_TIMER endTimer()
          111  +#define HAS_TIMER 1
          112  +
          113  +#elif (defined(_WIN32) || defined(WIN32))
          114  +
          115  +#include <windows.h>
          116  +
          117  +/* Saved resource information for the beginning of an operation */
          118  +static HANDLE hProcess;
          119  +static FILETIME ftKernelBegin;
          120  +static FILETIME ftUserBegin;
          121  +typedef BOOL (WINAPI *GETPROCTIMES)(HANDLE, LPFILETIME, LPFILETIME, LPFILETIME, LPFILETIME);
          122  +static GETPROCTIMES getProcessTimesAddr = NULL;
          123  +
          124  +/*
          125  +** Check to see if we have timer support.  Return 1 if necessary
          126  +** support found (or found previously).
          127  +*/
          128  +static int hasTimer(void){
          129  +  if( getProcessTimesAddr ){
          130  +    return 1;
          131  +  } else {
          132  +    /* GetProcessTimes() isn't supported in WIN95 and some other Windows versions.
          133  +    ** See if the version we are running on has it, and if it does, save off
          134  +    ** a pointer to it and the current process handle.
          135  +    */
          136  +    hProcess = GetCurrentProcess();
          137  +    if( hProcess ){
          138  +      HINSTANCE hinstLib = LoadLibrary(TEXT("Kernel32.dll"));
          139  +      if( NULL != hinstLib ){
          140  +        getProcessTimesAddr = (GETPROCTIMES) GetProcAddress(hinstLib, "GetProcessTimes");
          141  +        if( NULL != getProcessTimesAddr ){
          142  +          return 1;
          143  +        }
          144  +        FreeLibrary(hinstLib); 
          145  +      }
          146  +    }
          147  +  }
          148  +  return 0;
          149  +}
          150  +
          151  +/*
          152  +** Begin timing an operation
          153  +*/
          154  +static void beginTimer(void){
          155  +  if( getProcessTimesAddr ){
          156  +    FILETIME ftCreation, ftExit;
          157  +    getProcessTimesAddr(hProcess, &ftCreation, &ftExit, &ftKernelBegin, &ftUserBegin);
          158  +  }
          159  +}
          160  +
          161  +/* Return the difference of two FILETIME structs in milliseconds */
          162  +static double timeDiff(FILETIME *pStart, FILETIME *pEnd){
          163  +  sqlite_int64 i64Start = *((sqlite_int64 *) pStart);
          164  +  sqlite_int64 i64End = *((sqlite_int64 *) pEnd);
          165  +  return (double) ((i64End - i64Start) / 10000.0);
          166  +}
          167  +
          168  +/*
          169  +** Print the timing results.
          170  +*/
          171  +static double endTimer(void){
          172  +  if(getProcessTimesAddr){
          173  +    FILETIME ftCreation, ftExit, ftKernelEnd, ftUserEnd;
          174  +    getProcessTimesAddr(hProcess, &ftCreation, &ftExit, &ftKernelEnd, &ftUserEnd);
          175  +    return timeDiff(&ftUserBegin, &ftUserEnd) +
          176  +      timeDiff(&ftKernelBegin, &ftKernelEnd);
          177  +  }
          178  +}
          179  +
          180  +#define BEGIN_TIMER beginTimer()
          181  +#define END_TIMER endTimer()
          182  +#define HAS_TIMER hasTimer()
          183  +
          184  +#else
          185  +#define BEGIN_TIMER 
          186  +#define END_TIMER 0.0
          187  +#define HAS_TIMER 0
          188  +#endif
          189  +
          190  +
          191  +char fossil_has_json(){
          192  +  return g.json.isJsonMode && (g.isHTTP || g.json.post.o);
          193  +}
          194  +
          195  +/*
          196  +** Placeholder /json/XXX page impl for NYI (Not Yet Implemented)
          197  +** (but planned) pages/commands.
          198  +*/
          199  +cson_value * json_page_nyi(){
          200  +  g.json.resultCode = FSL_JSON_E_NYI;
          201  +  return NULL;
          202  +}
          203  +
          204  +/*
          205  +** Given a FossilJsonCodes value, it returns a string suitable for use
          206  +** as a resultCode string. Returns some unspecified non-empty string
          207  +** if errCode is not one of the FossilJsonCodes values.
          208  +*/
          209  +static char const * json_err_cstr( int errCode ){
          210  +  switch( errCode ){
          211  +    case 0: return "Success";
          212  +#define C(X,V) case FSL_JSON_E_ ## X: return V
          213  +
          214  +    C(GENERIC,"Generic error");
          215  +    C(INVALID_REQUEST,"Invalid request");
          216  +    C(UNKNOWN_COMMAND,"Unknown Command");
          217  +    C(UNKNOWN,"Unknown error");
          218  +    C(TIMEOUT,"Timeout reached");
          219  +    C(ASSERT,"Assertion failed");
          220  +    C(ALLOC,"Resource allocation failed");
          221  +    C(NYI,"Not yet implemented");
          222  +    C(PANIC,"x");
          223  +    C(MANIFEST_READ_FAILED,"Reading artifact manifest failed");
          224  +    C(FILE_OPEN_FAILED,"Opening file failed");
          225  +    
          226  +    C(AUTH,"Authentication error");
          227  +    C(MISSING_AUTH,"Authentication info missing from request");
          228  +    C(DENIED,"Access denied");
          229  +    C(WRONG_MODE,"Request not allowed (wrong operation mode)");
          230  +    C(LOGIN_FAILED,"Login failed");
          231  +    C(LOGIN_FAILED_NOSEED,"Anonymous login attempt was missing password seed");
          232  +    C(LOGIN_FAILED_NONAME,"Login failed - name not supplied");
          233  +    C(LOGIN_FAILED_NOPW,"Login failed - password not supplied");
          234  +    C(LOGIN_FAILED_NOTFOUND,"Login failed - no match found");
          235  +
          236  +    C(USAGE,"Usage error");
          237  +    C(INVALID_ARGS,"Invalid argument(s)");
          238  +    C(MISSING_ARGS,"Missing argument(s)");
          239  +    C(AMBIGUOUS_UUID,"Resource identifier is ambiguous");
          240  +    C(UNRESOLVED_UUID,"Provided uuid/tag/branch could not be resolved");
          241  +    C(RESOURCE_ALREADY_EXISTS,"Resource already exists");
          242  +    C(RESOURCE_NOT_FOUND,"Resource not found");
          243  +
          244  +    C(DB,"Database error");
          245  +    C(STMT_PREP,"Statement preparation failed");
          246  +    C(STMT_BIND,"Statement parameter binding failed");
          247  +    C(STMT_EXEC,"Statement execution/stepping failed");
          248  +    C(DB_LOCKED,"Database is locked");
          249  +    C(DB_NEEDS_REBUILD,"Fossil repository needs to be rebuilt");
          250  +    C(DB_NOT_FOUND,"Fossil repository db file could not be found.");
          251  +    C(DB_NOT_VALID, "Fossil repository db file is not valid.");
          252  +#undef C
          253  +    default:
          254  +      return "Unknown Error";
          255  +  }
          256  +}
          257  +
          258  +/*
          259  +** Implements the cson_data_dest_f() interface and outputs the data to
          260  +** a fossil Blob object.  pState must be-a initialized (Blob*), to
          261  +** which n bytes of src will be appended.
          262  +**/
          263  +int cson_data_dest_Blob(void * pState, void const * src, unsigned int n){
          264  +  Blob * b = (Blob*)pState;
          265  +  blob_append( b, (char const *)src, (int)n ) /* will die on OOM */;
          266  +  return 0;
          267  +}
          268  +
          269  +/*
          270  +** Implements the cson_data_source_f() interface and reads input from
          271  +** a fossil Blob object. pState must be-a (Blob*) populated with JSON
          272  +** data.
          273  +*/
          274  +int cson_data_src_Blob(void * pState, void * dest, unsigned int * n){
          275  +  Blob * b = (Blob*)pState;
          276  +  *n = blob_read( b, dest, *n );
          277  +  return 0;
          278  +}
          279  +
          280  +/*
          281  +** Convenience wrapper around cson_output() which appends the output
          282  +** to pDest. pOpt may be NULL, in which case g.json.outOpt will be used.
          283  +*/
          284  +int cson_output_Blob( cson_value const * pVal, Blob * pDest, cson_output_opt const * pOpt ){
          285  +  return cson_output( pVal, cson_data_dest_Blob,
          286  +                      pDest, pOpt ? pOpt : &g.json.outOpt );
          287  +}
          288  +
          289  +/*
          290  +** Convenience wrapper around cson_parse() which reads its input
          291  +** from pSrc. pSrc is rewound before parsing.
          292  +**
          293  +** pInfo may be NULL. If it is not NULL then it will contain details
          294  +** about the parse state when this function returns.
          295  +**
          296  +** On success a new JSON Object or Array is returned (owned by the
          297  +** caller). On error NULL is returned.
          298  +*/
          299  +cson_value * cson_parse_Blob( Blob * pSrc, cson_parse_info * pInfo ){
          300  +  cson_value * root = NULL;
          301  +  blob_rewind( pSrc );
          302  +  cson_parse( &root, cson_data_src_Blob, pSrc, NULL, pInfo );
          303  +  return root;
          304  +}
          305  +
          306  +/*
          307  +** Implements the cson_data_dest_f() interface and outputs the data to
          308  +** cgi_append_content(). pState is ignored.
          309  +**/
          310  +int cson_data_dest_cgi(void * pState, void const * src, unsigned int n){
          311  +  cgi_append_content( (char const *)src, (int)n );
          312  +  return 0;
          313  +}
          314  +
          315  +/*
          316  +** Returns a string in the form FOSSIL-XXXX, where XXXX is a
          317  +** left-zero-padded value of code. The returned buffer is static, and
          318  +** must be copied if needed for later.  The returned value will always
          319  +** be 11 bytes long (not including the trailing NUL byte).
          320  +**
          321  +** In practice we will only ever call this one time per app execution
          322  +** when constructing the JSON response envelope, so the static buffer
          323  +** "shouldn't" be a problem.
          324  +**
          325  +*/
          326  +char const * json_rc_cstr( int code ){
          327  +  enum { BufSize = 12 };
          328  +  static char buf[BufSize] = {'F','O','S','S','I','L','-',0};
          329  +  assert((code >= 1000) && (code <= 9999) && "Invalid Fossil/JSON code.");
          330  +  sprintf(buf+7,"%04d", code);
          331  +  return buf;
          332  +}
          333  +
          334  +/*
          335  +** Adds v to the API-internal cleanup mechanism. key is ingored
          336  +** (legacy) but might be re-introduced and "should" be a unique
          337  +** (app-wide) value.  Failure to insert an item may be caused by any
          338  +** of the following:
          339  +**
          340  +** - Allocation error.
          341  +** - g.json.gc.a is NULL
          342  +** - key is NULL or empty.
          343  +**
          344  +** Returns 0 on success.
          345  +**
          346  +** Ownership of v is transfered to (or shared with) g.json.gc, and v
          347  +** will be valid until that object is cleaned up or some internal code
          348  +** incorrectly removes it from the gc (which we never do). If this
          349  +** function fails, it is fatal to the app (as it indicates an
          350  +** allocation error (more likely than not) or a serious internal error
          351  +** such as numeric overflow).
          352  +*/
          353  +void json_gc_add( char const * key, cson_value * v ){
          354  +  int const rc = cson_array_append( g.json.gc.a, v );
          355  +  assert( NULL != g.json.gc.a );
          356  +  if( 0 != rc ){
          357  +    cson_value_free( v );
          358  +  }
          359  +  assert( (0==rc) && "Adding item to GC failed." );
          360  +  if(0!=rc){
          361  +    fprintf(stderr,"%s: FATAL: alloc error.\n", fossil_nameofexe())
          362  +        /* reminder: allocation error is the only reasonable cause of
          363  +           error here, provided g.json.gc.a and v are not NULL.
          364  +        */
          365  +        ;
          366  +    fossil_exit(1)/*not fossil_panic() b/c it might land us somewhere
          367  +                    where this function is called again.
          368  +                  */;
          369  +  }
          370  +}
          371  +
          372  +
          373  +/*
          374  +** Returns the value of json_rc_cstr(code) as a new JSON
          375  +** string, which is owned by the caller and must eventually
          376  +** be cson_value_free()d or transfered to a JSON container.
          377  +*/
          378  +cson_value * json_rc_string( int code ){
          379  +  return cson_value_new_string( json_rc_cstr(code), 11 );
          380  +}
          381  +
          382  +cson_value * json_new_string( char const * str ){
          383  +  return str
          384  +    ? cson_value_new_string(str,strlen(str))
          385  +    : NULL;
          386  +}
          387  +
          388  +cson_value * json_new_string_f( char const * fmt, ... ){
          389  +  cson_value * v;
          390  +  char * zStr;
          391  +  va_list vargs;
          392  +  va_start(vargs,fmt);
          393  +  zStr = vmprintf(fmt,vargs);
          394  +  va_end(vargs);
          395  +  v = cson_value_new_string(zStr, strlen(zStr));
          396  +  free(zStr);
          397  +  return v;  
          398  +}
          399  +
          400  +cson_value * json_new_int( int v ){
          401  +  return cson_value_new_integer((cson_int_t)v);
          402  +}
          403  +
          404  +/*
          405  +** Gets a POST/POST.payload/GET/COOKIE/ENV value. The returned memory
          406  +** is owned by the g.json object (one of its sub-objects). Returns
          407  +** NULL if no match is found.
          408  +**
          409  +** ENV means the system environment (getenv()).
          410  +**
          411  +** Precedence: POST.payload, GET/COOKIE/non-JSON POST, JSON POST, ENV.
          412  +**
          413  +** FIXME: the precedence SHOULD be: GET, POST.payload, POST, COOKIE,
          414  +** ENV, but the amalgamation of the GET/POST vars makes it difficult
          415  +** for me to do that. Since fossil only uses one cookie, cookie
          416  +** precedence isn't a real/high-priority problem.
          417  +*/
          418  +cson_value * json_getenv( char const * zKey ){
          419  +  cson_value * rc;
          420  +  rc = g.json.reqPayload.o
          421  +    ? cson_object_get( g.json.reqPayload.o, zKey )
          422  +    : NULL;
          423  +  if(rc){
          424  +    return rc;
          425  +  }
          426  +  rc = cson_object_get( g.json.param.o, zKey );
          427  +  if( rc ){
          428  +    return rc;
          429  +  }
          430  +  rc = cson_object_get( g.json.post.o, zKey );
          431  +  if(rc){
          432  +    return rc;
          433  +  }else{
          434  +    char const * cv = PD(zKey,NULL);
          435  +    if(!cv && !g.isHTTP){
          436  +      /* reminder to self: in CLI mode i'd like to try
          437  +         find_option(zKey,NULL,XYZ) here, but we don't have a sane
          438  +         default for the XYZ param here.
          439  +      */
          440  +      cv = getenv(zKey);
          441  +    }
          442  +    if(cv){/*transform it to JSON for later use.*/
          443  +      /* use sscanf() to figure out if it's an int,
          444  +         and transform it to JSON int if it is.
          445  +
          446  +         FIXME: use strtol(), since it has more accurate
          447  +         error handling.
          448  +      */
          449  +      int intVal = -1;
          450  +      char endOfIntCheck;
          451  +      int const scanRc = sscanf(cv,"%d%c",&intVal, &endOfIntCheck)
          452  +        /* The %c bit there is to make sure that we don't accept 123x
          453  +          as a number. sscanf() returns the number of tokens
          454  +          successfully parsed, so an RC of 1 will be correct for "123"
          455  +          but "123x" will have RC==2.
          456  +
          457  +          But it appears to not be working that way :/
          458  +        */
          459  +        ;
          460  +      if(1==scanRc){
          461  +        json_setenv( zKey, cson_value_new_integer(intVal) );
          462  +      }else{
          463  +        rc = cson_value_new_string(cv,strlen(cv));
          464  +        json_setenv( zKey, rc );
          465  +      }
          466  +      return rc;
          467  +    }else{
          468  +      return NULL;
          469  +    }
          470  +  }
          471  +}
          472  +
          473  +/*
          474  +** Wrapper around json_getenv() which...
          475  +**
          476  +** If it finds a value and that value is-a JSON number or is a string
          477  +** which looks like an integer or is-a JSON bool/null then it is
          478  +** converted to an int. If none of those apply then dflt is returned.
          479  +*/
          480  +int json_getenv_int(char const * pKey, int dflt ){
          481  +  cson_value const * v = json_getenv(pKey);
          482  +  if(!v){
          483  +    return dflt;
          484  +  }else if( cson_value_is_number(v) ){
          485  +    return (int)cson_value_get_integer(v);
          486  +  }else if( cson_value_is_string(v) ){
          487  +    char const * sv = cson_string_cstr(cson_value_get_string(v));
          488  +    assert( (NULL!=sv) && "This is quite unexpected." );
          489  +    return sv ? atoi(sv) : dflt;
          490  +  }else if( cson_value_is_bool(v) ){
          491  +    return cson_value_get_bool(v) ? 1 : 0;
          492  +  }else if( cson_value_is_null(v) ){
          493  +    return 0;
          494  +  }else{
          495  +    /* we should arguably treat JSON null as 0. */
          496  +    return dflt;
          497  +  }
          498  +}
          499  +
          500  +
          501  +/*
          502  +** Wrapper around json_getenv() which tries to evaluate a payload/env
          503  +** value as a boolean. Uses mostly the same logic as
          504  +** json_getenv_int(), with the addition that string values which
          505  +** either start with a digit 1..9 or the letters [tT] are considered
          506  +** to be true. If this function cannot find a matching key/value then
          507  +** dflt is returned. e.g. if it finds the key but the value is-a
          508  +** Object then dftl is returned.
          509  +**
          510  +** If an entry is found, this function guarantees that it will return
          511  +** either 0 or 1, as opposed to "0 or non-zero", so that clients can
          512  +** pass a different value as dflt. Thus they can use, e.g. -1 to know
          513  +** whether or not this function found a match (it will return -1 in
          514  +** that case).
          515  +*/
          516  +char json_getenv_bool(char const * pKey, char dflt ){
          517  +  cson_value const * v = json_getenv(pKey);
          518  +  if(!v){
          519  +    return dflt;
          520  +  }else if( cson_value_is_number(v) ){
          521  +    return cson_value_get_integer(v) ? 1 : 0;
          522  +  }else if( cson_value_is_string(v) ){
          523  +    char const * sv = cson_string_cstr(cson_value_get_string(v));
          524  +    if(!*sv || ('0'==*sv)){
          525  +      return 0;
          526  +    }else{
          527  +      return (('t'==*sv) || ('T'==*sv)
          528  +              || (('1'<=*sv) && ('9'>=*sv)))
          529  +        ? 1 : 0;
          530  +    }
          531  +  }else if( cson_value_is_bool(v) ){
          532  +    return cson_value_get_bool(v) ? 1 : 0;
          533  +  }else if( cson_value_is_null(v) ){
          534  +    return 0;
          535  +  }else{
          536  +    return dflt;
          537  +  }
          538  +}
          539  +
          540  +/*
          541  +** Returns the string form of a json_getenv() value, but ONLY If that
          542  +** value is-a String. Non-strings are not converted to strings for
          543  +** this purpose. Returned memory is owned by g.json or fossil and is
          544  +** valid until end-of-app or the given key is replaced in fossil's
          545  +** internals via cgi_replace_parameter() and friends or json_setenv().
          546  +*/
          547  +char const * json_getenv_cstr( char const * zKey ){
          548  +  return cson_value_get_cstr( json_getenv(zKey) );
          549  +}
          550  +
          551  +/*
          552  +** An extended form of find_option() which tries to look up a combo
          553  +** GET/POST/CLI argument.
          554  +**
          555  +** zKey must be the GET/POST parameter key. zCLILong must be the "long
          556  +** form" CLI flag (NULL means to use zKey). zCLIShort may be NULL or
          557  +** the "short form" CLI flag (if NULL, no short form is used).
          558  +**
          559  +** If argPos is >=0 and no other match is found,
          560  +** json_command_arg(argPos) is also checked.
          561  +**
          562  +** On error (no match found) NULL is returned.
          563  +**
          564  +** This ONLY works for String JSON/GET/CLI values, not JSON
          565  +** booleans and whatnot.
          566  +*/
          567  +char const * json_find_option_cstr2(char const * zKey,
          568  +                                    char const * zCLILong,
          569  +                                    char const * zCLIShort,
          570  +                                    int argPos){
          571  +  char const * rc = NULL;
          572  +  assert(NULL != zKey);
          573  +  if(!g.isHTTP){
          574  +    rc = find_option(zCLILong ? zCLILong : zKey,
          575  +                     zCLIShort, 1);
          576  +  }
          577  +  if(!rc && fossil_has_json()){
          578  +    rc = json_getenv_cstr(zKey);
          579  +  }
          580  +  if(!rc && (argPos>=0)){
          581  +    rc = json_command_arg((unsigned char)argPos);
          582  +  }
          583  +  return rc;
          584  +}
          585  +
          586  +char const * json_find_option_cstr(char const * zKey,
          587  +                                   char const * zCLILong,
          588  +                                   char const * zCLIShort){
          589  +  return json_find_option_cstr2(zKey, zCLIShort, zCLIShort, -1);
          590  +}
          591  +
          592  +/*
          593  +** The boolean equivalent of json_find_option_cstr().
          594  +** If the option is not found, dftl is returned.
          595  +*/
          596  +char json_find_option_bool(char const * zKey,
          597  +                           char const * zCLILong,
          598  +                           char const * zCLIShort,
          599  +                           char dflt ){
          600  +  char rc = -1;
          601  +  if(!g.isHTTP){
          602  +    if(NULL != find_option(zCLILong ? zCLILong : zKey,
          603  +                           zCLIShort, 0)){
          604  +      rc = 1;
          605  +    }
          606  +  }
          607  +  if((-1==rc) && fossil_has_json()){
          608  +    rc = json_getenv_bool(zKey,-1);
          609  +  }
          610  +  return (-1==rc) ? dflt : rc;
          611  +}
          612  +
          613  +/*
          614  +** The integer equivalent of json_find_option_cstr().
          615  +** If the option is not found, dftl is returned.
          616  +*/
          617  +int json_find_option_int(char const * zKey,
          618  +                         char const * zCLILong,
          619  +                         char const * zCLIShort,
          620  +                         int dflt ){
          621  +  enum { Magic = -947 };
          622  +  int rc = Magic;
          623  +  if(!g.isHTTP){
          624  +    /* FIXME: use strtol() for better error/dflt handling. */
          625  +    char const * opt = find_option(zCLILong ? zCLILong : zKey,
          626  +                                   zCLIShort, 1);
          627  +    if(NULL!=opt){
          628  +      rc = atoi(opt);
          629  +    }
          630  +  }
          631  +  if(Magic==rc){
          632  +    rc = json_getenv_int(zKey,Magic);
          633  +  }
          634  +  return (Magic==rc) ? dflt : rc;
          635  +}
          636  +
          637  +
          638  +/*
          639  +** Adds v to g.json.param.o using the given key. May cause any prior
          640  +** item with that key to be destroyed (depends on current reference
          641  +** count for that value). On success, transfers (or shares) ownership
          642  +** of v to (or with) g.json.param.o. On error ownership of v is not
          643  +** modified.
          644  +*/
          645  +int json_setenv( char const * zKey, cson_value * v ){
          646  +  return cson_object_set( g.json.param.o, zKey, v );
          647  +}
          648  +
          649  +/*
          650  +** Guesses a RESPONSE Content-Type value based (primarily) on the
          651  +** HTTP_ACCEPT header.
          652  +**
          653  +** It will try to figure out if the client can support
          654  +** application/json or application/javascript, and will fall back to
          655  +** text/plain if it cannot figure out anything more specific.
          656  +**
          657  +** Returned memory is static and immutable, but if the environment
          658  +** changes after calling this then subsequent calls to this function
          659  +** might return different (also static/immutable) values.
          660  +*/
          661  +char const * json_guess_content_type(){
          662  +  char const * cset;
          663  +  char doUtf8;
          664  +  cset = PD("HTTP_ACCEPT_CHARSET",NULL);
          665  +  doUtf8 = ((NULL == cset) || (NULL!=strstr("utf-8",cset)))
          666  +    ? 1 : 0;
          667  +  if( g.json.jsonp ){
          668  +    return doUtf8
          669  +      ? "application/javascript; charset=utf-8"
          670  +      : "application/javascript";
          671  +  }else{
          672  +    /*
          673  +      Content-type
          674  +      
          675  +      If the browser does not sent an ACCEPT for application/json
          676  +      then we fall back to text/plain.
          677  +    */
          678  +    char const * cstr;
          679  +    cstr = PD("HTTP_ACCEPT",NULL);
          680  +    if( NULL == cstr ){
          681  +      return doUtf8
          682  +        ? "application/json; charset=utf-8"
          683  +        : "application/json";
          684  +    }else{
          685  +      if( strstr( cstr, "application/json" )
          686  +          || strstr( cstr, "*/*" ) ){
          687  +        return doUtf8
          688  +          ? "application/json; charset=utf-8"
          689  +          : "application/json";
          690  +      }else{
          691  +        return "text/plain";
          692  +      }
          693  +    }
          694  +  }
          695  +}
          696  +
          697  +/*
          698  +** Sends pResponse to the output stream as the response object.  This
          699  +** function does no validation of pResponse except to assert() that it
          700  +** is not NULL. The caller is responsible for ensuring that it meets
          701  +** API response envelope conventions.
          702  +**
          703  +** In CLI mode pResponse is sent to stdout immediately. In HTTP
          704  +** mode pResponse replaces any current CGI content but cgi_reply()
          705  +** is not called to flush the output.
          706  +**
          707  +** If g.json.jsonp is not NULL then the content type is set to
          708  +** application/javascript and the output is wrapped in a jsonp
          709  +** wrapper.
          710  +*/
          711  +void json_send_response( cson_value const * pResponse ){
          712  +  assert( NULL != pResponse );
          713  +  if( g.isHTTP ){
          714  +    cgi_reset_content();
          715  +    if( g.json.jsonp ){
          716  +      cgi_printf("%s(",g.json.jsonp);
          717  +    }
          718  +    cson_output( pResponse, cson_data_dest_cgi, NULL, &g.json.outOpt );
          719  +    if( g.json.jsonp ){
          720  +      cgi_append_content(")",1);
          721  +    }
          722  +  }else{/*CLI mode*/
          723  +    if( g.json.jsonp ){
          724  +      fprintf(stdout,"%s(",g.json.jsonp);
          725  +    }
          726  +    cson_output_FILE( pResponse, stdout, &g.json.outOpt );
          727  +    if( g.json.jsonp ){
          728  +      fwrite(")\n", 2, 1, stdout);
          729  +    }
          730  +  }
          731  +}
          732  +
          733  +/*
          734  +** Returns the current request's JSON authentication token, or NULL if
          735  +** none is found. The token's memory is owned by (or shared with)
          736  +** g.json.
          737  +**
          738  +** If an auth token is found in the GET/POST request data then fossil
          739  +** is given that data for use in authentication for this
          740  +** session. i.e. the GET/POST data overrides fossil's authentication
          741  +** cookie value (if any) and also works with clients which do not
          742  +** support cookies.
          743  +**
          744  +** Must be called once before login_check_credentials() is called or
          745  +** we will not be able to replace fossil's internal idea of the auth
          746  +** info in time (and future changes to that state may cause unexpected
          747  +** results).
          748  +**
          749  +** The result of this call are cached for future calls.
          750  +*/
          751  +cson_value * json_auth_token(){
          752  +  if( !g.json.authToken ){
          753  +    /* Try to get an authorization token from GET parameter, POSTed
          754  +       JSON, or fossil cookie (in that order). */
          755  +    g.json.authToken = json_getenv(FossilJsonKeys.authToken);
          756  +    if(g.json.authToken
          757  +       && cson_value_is_string(g.json.authToken)
          758  +       && !PD(login_cookie_name(),NULL)){
          759  +      /* tell fossil to use this login info.
          760  +
          761  +      FIXME?: because the JSON bits don't carry around
          762  +      login_cookie_name(), there is a potential login hijacking
          763  +      window here. We may need to change the JSON auth token to be
          764  +      in the form: login_cookie_name()=...
          765  +
          766  +      Then again, the hardened cookie value helps ensure that
          767  +      only a proper key/value match is valid.
          768  +      */
          769  +      cgi_replace_parameter( login_cookie_name(), cson_value_get_cstr(g.json.authToken) );
          770  +    }else if( g.isHTTP ){
          771  +      /* try fossil's conventional cookie. */
          772  +      /* Reminder: chicken/egg scenario regarding db access in CLI
          773  +         mode because login_cookie_name() needs the db. CLI
          774  +         mode does not use any authentication, so we don't need
          775  +         to support it here.
          776  +      */
          777  +      char const * zCookie = P(login_cookie_name());
          778  +      if( zCookie && *zCookie ){
          779  +        /* Transfer fossil's cookie to JSON for downstream convenience... */
          780  +        cson_value * v = cson_value_new_string(zCookie, strlen(zCookie));
          781  +        json_gc_add( FossilJsonKeys.authToken, v );
          782  +        g.json.authToken = v;
          783  +      }
          784  +    }
          785  +  }
          786  +  return g.json.authToken;
          787  +}
          788  +
          789  +/*
          790  +** IFF json.reqPayload.o is not NULL then this returns
          791  +** cson_object_get(json.reqPayload.o,pKey), else it returns NULL.
          792  +**
          793  +** The returned value is owned by (or shared with) json.reqPayload.v.
          794  +*/
          795  +cson_value * json_req_payload_get(char const *pKey){
          796  +  return g.json.reqPayload.o
          797  +    ? cson_object_get(g.json.reqPayload.o,pKey)
          798  +    : NULL;
          799  +}
          800  +
          801  +/*
          802  +** Initializes some JSON bits which need to be initialized relatively
          803  +** early on. It should only be called from cgi_init() or
          804  +** json_cmd_top() (early on in those functions).
          805  +**
          806  +** Initializes g.json.gc and g.json.param. This code does not (and
          807  +** must not) rely on any of the fossil environment having been set
          808  +** up. e.g. it must not use cgi_parameter() and friends because this
          809  +** must be called before those data are initialized.
          810  +*/
          811  +void json_main_bootstrap(){
          812  +  cson_value * v;
          813  +  assert( (NULL == g.json.gc.v) && "cgi_json_bootstrap() was called twice!" );
          814  +
          815  +  /* g.json.gc is our "garbage collector" - where we put JSON values
          816  +     which need a long lifetime but don't have a logical parent to put
          817  +     them in.
          818  +  */
          819  +  v = cson_value_new_array();
          820  +  g.json.gc.v = v;
          821  +  g.json.gc.a = cson_value_get_array(v);
          822  +  cson_value_add_reference(v)
          823  +    /* Needed to allow us to include this value in other JSON
          824  +       containers without transfering ownership to those containers.
          825  +       All other persistent g.json.XXX.v values get appended to
          826  +       g.json.gc.a, and therefore already have a live reference
          827  +       for this purpose.
          828  +    */
          829  +    ;
          830  +
          831  +  /*
          832  +    g.json.param holds the JSONized counterpart of fossil's
          833  +    cgi_parameter_xxx() family of data. We store them as JSON, as
          834  +    opposed to using fossil's data directly, because we can retain
          835  +    full type information for data this way (as opposed to it always
          836  +    being of type string).
          837  +  */
          838  +  v = cson_value_new_object();
          839  +  g.json.param.v = v;
          840  +  g.json.param.o = cson_value_get_object(v);
          841  +  json_gc_add("$PARAMS", v);
          842  +}
          843  +
          844  +/*
          845  +** Appends a warning object to the (pending) JSON response.
          846  +**
          847  +** Code must be a FSL_JSON_W_xxx value from the FossilJsonCodes enum.
          848  +**
          849  +** A Warning object has this JSON structure:
          850  +**
          851  +** { "code":integer, "text":"string" }
          852  +**
          853  +** But the text part is optional.
          854  +**
          855  +** If msg is non-NULL and not empty then it is used as the "text"
          856  +** property's value. It is copied, and need not refer to static
          857  +** memory.
          858  +**
          859  +** CURRENTLY this code only allows a given warning code to be
          860  +** added one time, and elides subsequent warnings. The intention
          861  +** is to remove that burden from loops which produce warnings.
          862  +**
          863  +** FIXME: if msg is NULL then use a standard string for
          864  +** the given code. If !*msg then elide the "text" property,
          865  +** for consistency with how json_err() works.
          866  +*/
          867  +void json_warn( int code, char const * fmt, ... ){
          868  +  cson_object * obj = NULL;
          869  +  assert( (code>FSL_JSON_W_START)
          870  +          && (code<FSL_JSON_W_END)
          871  +          && "Invalid warning code.");
          872  +  if(!g.json.warnings.v){
          873  +    g.json.warnings.v = cson_value_new_array();
          874  +    assert((NULL != g.json.warnings.v) && "Alloc error.");
          875  +    g.json.warnings.a = cson_value_get_array(g.json.warnings.v);
          876  +    json_gc_add("$WARNINGS",g.json.warnings.v);
          877  +  }
          878  +  obj = cson_new_object();
          879  +  cson_array_append(g.json.warnings.a, cson_object_value(obj));
          880  +  cson_object_set(obj,"code",cson_value_new_integer(code));
          881  +  if(fmt && *fmt){
          882  +    /* FIXME: treat NULL fmt as standard warning message for
          883  +       the code, but we don't have those yet.
          884  +    */
          885  +    va_list vargs;
          886  +    char * msg;
          887  +    va_start(vargs,fmt);
          888  +    msg = vmprintf(fmt,vargs);
          889  +    va_end(vargs);
          890  +    cson_object_set(obj,"text", cson_value_new_string(msg,strlen(msg)));
          891  +    free(msg);
          892  +  }
          893  +}
          894  +
          895  +/*
          896  +** Splits zStr (which must not be NULL) into tokens separated by the
          897  +** given separator character. If doDeHttp is true then each element
          898  +** will be passed through dehttpize(), otherwise they are used
          899  +** as-is. Note that tokenization happens before dehttpize(),
          900  +** which is significant if the ENcoded tokens might contain the
          901  +** separator character.
          902  +**
          903  +** Each new element is appended to the given target array object,
          904  +** which must not be NULL and ownership of it is not changed by this
          905  +** call.
          906  +**
          907  +** On success, returns the number of tokens _encountered_. On error a
          908  +** NEGATIVE number is returned - its absolute value is the number of
          909  +** tokens encountered (i.e. it reveals which token in zStr was
          910  +** problematic).
          911  +**
          912  +** Achtung: leading and trailing whitespace of elements are elided.
          913  +**
          914  +** Achtung: empty elements will be skipped, meaning consecutive empty
          915  +** elements are collapsed.
          916  +*/
          917  +int json_string_split( char const * zStr,
          918  +                       char separator,
          919  +                       char doDeHttp,
          920  +                       cson_array * target ){
          921  +  char const * p = zStr /* current byte */;
          922  +  char const * head  /* current start-of-token */;
          923  +  unsigned int len = 0   /* current token's length */;
          924  +  int rc = 0   /* return code (number of added elements)*/;
          925  +  char skipWs = fossil_isspace(separator) ? 0 : 1;
          926  +  assert( zStr && target );
          927  +  while( fossil_isspace(*p) ){
          928  +    ++p;
          929  +  }
          930  +  head = p;
          931  +  for( ; ; ++p){
          932  +    if( !*p || (separator == *p) ){
          933  +      if( len ){/* append head..(head+len) as next array
          934  +                   element. */
          935  +        cson_value * part = NULL;
          936  +        char * zPart = NULL;
          937  +        ++rc;
          938  +        assert( head != p );
          939  +        zPart = (char*)malloc(len+1);
          940  +        assert( (zPart != NULL) && "malloc failure" );
          941  +        memcpy(zPart, head, len);
          942  +        zPart[len] = 0;
          943  +        if(doDeHttp){
          944  +          dehttpize(zPart);
          945  +        }
          946  +        if( *zPart ){ /* should only fail if someone manages to url-encoded a NUL byte */
          947  +          part = cson_value_new_string(zPart, strlen(zPart));
          948  +          if( 0 != cson_array_append( target, part ) ){
          949  +            cson_value_free(part);
          950  +            rc = -rc;
          951  +            break;
          952  +          }
          953  +        }else{
          954  +          assert(0 && "i didn't think this was possible!");
          955  +          fprintf(stderr,"%s:%d: My God! It's full of stars!\n",
          956  +                  __FILE__, __LINE__);
          957  +          fossil_exit(1)
          958  +            /* Not fossil_panic() b/c this code needs to be able to
          959  +              run before some of the fossil/json bits are initialized,
          960  +              and fossil_panic() calls into the JSON API.
          961  +            */
          962  +            ;
          963  +        }
          964  +        free(zPart);
          965  +        len = 0;
          966  +      }
          967  +      if( !*p ){
          968  +        break;
          969  +      }
          970  +      head = p+1;
          971  +      while( *head && fossil_isspace(*head) ){
          972  +        ++head;
          973  +        ++p;
          974  +      }
          975  +      if(!*head){
          976  +        break;
          977  +      }
          978  +      continue;
          979  +    }
          980  +    ++len;
          981  +  }
          982  +  return rc;
          983  +}
          984  +
          985  +/*
          986  +** Wrapper around json_string_split(), taking the same first 3
          987  +** parameters as this function, but returns the results as a JSON
          988  +** Array (if splitting produced tokens) or NULL (if splitting failed
          989  +** in any way or produced no tokens).
          990  +**
          991  +** The returned value is owned by the caller. If not NULL then it
          992  +** _will_ have a JSON type of Array or Null.
          993  +*/
          994  +cson_value * json_string_split2( char const * zStr,
          995  +                                 char separator,
          996  +                                 char doDeHttp ){
          997  +  cson_value * v = cson_value_new_array();
          998  +  cson_array * a = cson_value_get_array(v);
          999  +  int rc = json_string_split( zStr, separator, doDeHttp, a );
         1000  +  if( 0 == rc ){
         1001  +    cson_value_free(v);
         1002  +    v = NULL;
         1003  +  }else if(rc<0){
         1004  +    cson_value_free(v);
         1005  +    v = NULL;
         1006  +  }
         1007  +  return v;
         1008  +}
         1009  +
         1010  +
         1011  +/*
         1012  +** Performs some common initialization of JSON-related state.  Must be
         1013  +** called by the json_page_top() and json_cmd_top() dispatching
         1014  +** functions to set up the JSON stat used by the dispatched functions.
         1015  +**
         1016  +** Implicitly sets up the login information state in CGI mode, but
         1017  +** does not perform any permissions checking. It _might_ (haven't
         1018  +** tested this) die with an error if an auth cookie is malformed.
         1019  +**
         1020  +** This must be called by the top-level JSON command dispatching code
         1021  +** before they do any work.
         1022  +**
         1023  +** This must only be called once, or an assertion may be triggered.
         1024  +*/
         1025  +static void json_mode_bootstrap(){
         1026  +  static char once = 0  /* guard against multiple runs */;
         1027  +  char const * zPath = P("PATH_INFO");
         1028  +  cson_value * pathSplit = NULL;
         1029  +  assert( (0==once) && "json_mode_bootstrap() called too many times!");
         1030  +  if( once ){
         1031  +    return;
         1032  +  }else{
         1033  +    once = 1;
         1034  +  }
         1035  +  g.json.isJsonMode = 1;
         1036  +  g.json.resultCode = 0;
         1037  +  g.json.cmd.offset = -1;
         1038  +  g.json.jsonp = PD("jsonp",NULL)
         1039  +    /* FIXME: do some sanity checking on g.json.jsonp and ignore it
         1040  +       if it is not halfway reasonable.
         1041  +    */
         1042  +    ;
         1043  +  if( !g.isHTTP && g.fullHttpReply ){
         1044  +    /* workaround for server mode, so we see it as CGI mode. */
         1045  +    g.isHTTP = 1;
         1046  +  }
         1047  +
         1048  +  if(g.isHTTP){
         1049  +    cgi_set_content_type(json_guess_content_type())
         1050  +      /* reminder: must be done after g.json.jsonp is initialized */
         1051  +      ;
         1052  +#if 0
         1053  +    /* Calling this seems to trigger an SQLITE_MISUSE warning???
         1054  +       Maybe it's not legal to set the logger more than once?
         1055  +    */
         1056  +    sqlite3_config(SQLITE_CONFIG_LOG, NULL, 0)
         1057  +        /* avoids debug messages on stderr in JSON mode */
         1058  +        ;
         1059  +#endif
         1060  +  }
         1061  +
         1062  +  g.json.cmd.v = cson_value_new_array();
         1063  +  g.json.cmd.a = cson_value_get_array(g.json.cmd.v);
         1064  +  json_gc_add( FossilJsonKeys.commandPath, g.json.cmd.v );
         1065  +  /*
         1066  +    The following if/else block translates the PATH_INFO path (in
         1067  +    CLI/server modes) or g.argv (CLI mode) into an internal list so
         1068  +    that we can simplify command dispatching later on.
         1069  +
         1070  +    Note that translating g.argv this way is overkill but allows us to
         1071  +    avoid CLI-only special-case handling in other code, e.g.
         1072  +    json_command_arg().
         1073  +  */
         1074  +  if( zPath ){/* Either CGI or server mode... */
         1075  +    /* Translate PATH_INFO into JSON array for later convenience. */
         1076  +    json_string_split(zPath, '/', 1, g.json.cmd.a);
         1077  +  }else{/* assume CLI mode */
         1078  +    int i;
         1079  +    char const * arg;
         1080  +    cson_value * part;
         1081  +    for(i = 1/*skip argv[0]*/; i < g.argc; ++i ){
         1082  +      arg = g.argv[i];
         1083  +      if( !arg || !*arg ){
         1084  +        continue;
         1085  +      }
         1086  +      if('-' == *arg){
         1087  +        /* workaround to skip CLI args so that
         1088  +           json_command_arg() does not see them.
         1089  +           This assumes that all arguments come LAST
         1090  +           on the command line.
         1091  +        */
         1092  +        break;
         1093  +      }
         1094  +      part = cson_value_new_string(arg,strlen(arg));
         1095  +      cson_array_append(g.json.cmd.a, part);
         1096  +    }
         1097  +  }
         1098  +
         1099  +  while(!g.isHTTP){
         1100  +    /* Simulate JSON POST data via input file.  Pedantic reminder:
         1101  +       error handling does not honor user-supplied g.json.outOpt
         1102  +       because outOpt cannot (generically) be configured until after
         1103  +       POST-reading is finished.
         1104  +    */
         1105  +    FILE * inFile = NULL;
         1106  +    char const * jfile = find_option("json-input",NULL,1);
         1107  +    if(!jfile || !*jfile){
         1108  +      break;
         1109  +    }
         1110  +    inFile = (0==strcmp("-",jfile))
         1111  +      ? stdin
         1112  +      : fopen(jfile,"rb");
         1113  +    if(!inFile){
         1114  +      g.json.resultCode = FSL_JSON_E_FILE_OPEN_FAILED;
         1115  +      fossil_fatal("Could not open JSON file [%s].",jfile)
         1116  +        /* Does not return. */
         1117  +        ;
         1118  +    }
         1119  +    cgi_parse_POST_JSON(inFile, 0);
         1120  +    if( stdin != inFile ){
         1121  +      fclose(inFile);
         1122  +    }
         1123  +    break;
         1124  +  }
         1125  +  
         1126  +  /* g.json.reqPayload exists only to simplify some of our access to
         1127  +     the request payload. We currently only use this in the context of
         1128  +     Object payloads, not Arrays, strings, etc.
         1129  +  */
         1130  +  g.json.reqPayload.v = cson_object_get( g.json.post.o, FossilJsonKeys.payload );
         1131  +  if( g.json.reqPayload.v ){
         1132  +    g.json.reqPayload.o = cson_value_get_object( g.json.reqPayload.v )
         1133  +        /* g.json.reqPayload.o may legally be NULL, which means only that
         1134  +           g.json.reqPayload.v is-not-a Object.
         1135  +        */;
         1136  +  }
         1137  +
         1138  +  /* Anything which needs json_getenv() and friends should go after
         1139  +     this point.
         1140  +  */
         1141  +
         1142  +  if(1 == cson_array_length_get(g.json.cmd.a)){
         1143  +    /* special case: if we're at the top path, look for
         1144  +       a "command" request arg which specifies which command
         1145  +       to run.
         1146  +    */
         1147  +    char const * cmd = json_getenv_cstr("command");
         1148  +    if(cmd){
         1149  +      json_string_split(cmd, '/', 0, g.json.cmd.a);
         1150  +      g.json.cmd.commandStr = cmd;
         1151  +    }
         1152  +  }
         1153  +
         1154  +  
         1155  +  if(!g.json.jsonp){
         1156  +    g.json.jsonp = json_find_option_cstr("jsonp",NULL,NULL);
         1157  +  }
         1158  +  if(!g.isHTTP){
         1159  +    g.json.errorDetailParanoia = 0 /*disable error code dumb-down for CLI mode*/;
         1160  +  }
         1161  +
         1162  +  {/* set up JSON output formatting options. */
         1163  +    int indent = -1;
         1164  +    char const * indentStr = NULL;
         1165  +    indent = json_find_option_int("indent",NULL,"I",-1);
         1166  +    g.json.outOpt.indentation = (0>indent)
         1167  +      ? (g.isHTTP ? 0 : 1)
         1168  +      : (unsigned char)indent;
         1169  +    g.json.outOpt.addNewline = g.isHTTP
         1170  +      ? 0
         1171  +      : (g.json.jsonp ? 0 : 1);
         1172  +  }
         1173  +
         1174  +  if( g.isHTTP ){
         1175  +    json_auth_token()/* will copy our auth token, if any, to fossil's
         1176  +                        core, which we need before we call
         1177  +                        login_check_credentials(). */;
         1178  +    login_check_credentials()/* populates g.perm */;
         1179  +  }
         1180  +  else{
         1181  +    db_find_and_open_repository(OPEN_ANY_SCHEMA,0);
         1182  +  }
         1183  +}
         1184  +
         1185  +/*
         1186  +** Returns the ndx'th item in the "command path", where index 0 is the
         1187  +** position of the "json" part of the path. Returns NULL if ndx is out
         1188  +** of bounds or there is no "json" path element.
         1189  +**
         1190  +** In CLI mode the "path" is the list of arguments (skipping argv[0]).
         1191  +** In server/CGI modes the path is taken from PATH_INFO.
         1192  +**
         1193  +** The returned bytes are owned by g.json.cmd.v and _may_ be
         1194  +** invalidated if that object is modified (depending on how it is
         1195  +** modified).
         1196  +**
         1197  +** Note that CLI options are not included in the command path. Use
         1198  +** find_option() to get those.
         1199  +**
         1200  +*/
         1201  +char const * json_command_arg(unsigned char ndx){
         1202  +  cson_array * ar = g.json.cmd.a;
         1203  +  assert((NULL!=ar) && "Internal error. Was json_mode_bootstrap() called?");
         1204  +  assert((g.argc>1) && "Internal error - we never should have gotten this far.");
         1205  +  if( g.json.cmd.offset < 0 ){
         1206  +    /* first-time setup. */
         1207  +    short i = 0;
         1208  +#define NEXT cson_string_cstr(          \
         1209  +                 cson_value_get_string( \
         1210  +                   cson_array_get(ar,i) \
         1211  +                   ))
         1212  +    char const * tok = NEXT;
         1213  +    while( tok ){
         1214  +      if( !g.isHTTP/*workaround for "abbreviated name" in CLI mode*/
         1215  +          ? (0==strcmp(g.argv[1],tok))
         1216  +          : (0==strncmp("json",tok,4))
         1217  +          ){
         1218  +        g.json.cmd.offset = i;
         1219  +        break;
         1220  +      }
         1221  +      ++i;
         1222  +      tok = NEXT;
         1223  +    }
         1224  +  }
         1225  +#undef NEXT
         1226  +  if(g.json.cmd.offset < 0){
         1227  +    return NULL;
         1228  +  }else{
         1229  +    ndx = g.json.cmd.offset + ndx;
         1230  +    return cson_string_cstr(cson_value_get_string(cson_array_get( ar, g.json.cmd.offset + ndx )));
         1231  +  }
         1232  +}
         1233  +
         1234  +/*
         1235  +** If g.json.reqPayload.o is NULL then NULL is returned, else the
         1236  +** given property is searched for in the request payload.  If found it
         1237  +** is returned. The returned value is owned by (or shares ownership
         1238  +** with) g.json, and must NOT be cson_value_free()'d by the
         1239  +** caller.
         1240  +*/
         1241  +cson_value * json_payload_property( char const * key ){
         1242  +  return g.json.reqPayload.o ?
         1243  +    cson_object_get( g.json.reqPayload.o, key )
         1244  +    : NULL;
         1245  +}
         1246  +
         1247  +
         1248  +/* Returns the C-string form of json_auth_token(), or NULL
         1249  +** if json_auth_token() returns NULL.
         1250  +*/
         1251  +char const * json_auth_token_cstr(){
         1252  +  return cson_value_get_cstr( json_auth_token() );
         1253  +}
         1254  +
         1255  +/*
         1256  +** Returns the JsonPageDef with the given name, or NULL if no match is
         1257  +** found.
         1258  +**
         1259  +** head must be a pointer to an array of JsonPageDefs in which the
         1260  +** last entry has a NULL name.
         1261  +*/
         1262  +JsonPageDef const * json_handler_for_name( char const * name, JsonPageDef const * head ){
         1263  +  JsonPageDef const * pageDef = head;
         1264  +  assert( head != NULL );
         1265  +  if(name && *name) for( ; pageDef->name; ++pageDef ){
         1266  +    if( 0 == strcmp(name, pageDef->name) ){
         1267  +      return pageDef;
         1268  +    }
         1269  +  }
         1270  +  return NULL;
         1271  +}
         1272  +
         1273  +/*
         1274  +** Given a Fossil/JSON result code, this function "dumbs it down"
         1275  +** according to the current value of g.json.errorDetailParanoia. The
         1276  +** dumbed-down value is returned.
         1277  +**
         1278  +** This function assert()s that code is in the inclusive range 0 to
         1279  +** 9999.
         1280  +**
         1281  +** Note that WARNING codes (1..999) are never dumbed down.
         1282  +**
         1283  +*/
         1284  +static int json_dumbdown_rc( int code ){
         1285  +  if(!g.json.errorDetailParanoia
         1286  +     || !code
         1287  +     || ((code>=FSL_JSON_W_START) && (code<FSL_JSON_W_END))){
         1288  +    return code;
         1289  +  }else{
         1290  +    int modulo = 0;
         1291  +    assert((code >= 1000) && (code <= 9999) && "Invalid Fossil/JSON code.");
         1292  +    switch( g.json.errorDetailParanoia ){
         1293  +      case 1: modulo = 10; break;
         1294  +      case 2: modulo = 100; break;
         1295  +      case 3: modulo = 1000; break;
         1296  +      default: break;
         1297  +    }
         1298  +    if( modulo ) code = code - (code % modulo);
         1299  +    return code;
         1300  +  }
         1301  +}
         1302  +
         1303  +/*
         1304  +** Convenience routine which converts a Julian time value into a Unix
         1305  +** Epoch timestamp. Requires the db, so this cannot be used before the
         1306  +** repo is opened (will trigger a fatal error in db_xxx()).
         1307  +*/
         1308  +cson_value * json_julian_to_timestamp(double j){
         1309  +  return cson_value_new_integer((cson_int_t)
         1310  +           db_int64(0,"SELECT cast(strftime('%%s',%lf) as int)",j)
         1311  +                                );
         1312  +}
         1313  +
         1314  +/*
         1315  +** Returns a timestamp value.
         1316  +*/
         1317  +cson_int_t json_timestamp(){
         1318  +  return (cson_int_t)time(0);
         1319  +}
         1320  +/*
         1321  +** Returns a new JSON value (owned by the caller) representing
         1322  +** a timestamp. If timeVal is < 0 then time(0) is used to fetch
         1323  +** the time, else timeVal is used as-is
         1324  +*/
         1325  +cson_value * json_new_timestamp(cson_int_t timeVal){
         1326  +  return cson_value_new_integer((timeVal<0) ? (cson_int_t)time(0) : timeVal);
         1327  +}
         1328  +
         1329  +/*
         1330  +** Internal helper for json_create_response(). Appends the first
         1331  +** g.json.dispatchDepth elements of g.json.cmd.a, skipping the first
         1332  +** one (the "json" part), to a string and returns that string value
         1333  +** (which is owned by the caller).
         1334  +*/
         1335  +static cson_value * json_response_command_path(){
         1336  +  if(!g.json.cmd.a){
         1337  +    return NULL;
         1338  +  }else{
         1339  +    cson_value * rc = NULL;
         1340  +    Blob path = empty_blob;
         1341  +    char const * part;
         1342  +    unsigned int aLen = g.json.dispatchDepth+1; /*cson_array_length_get(g.json.cmd.a);*/
         1343  +    unsigned int i = 1;
         1344  +    for( ; i < aLen; ++i ){
         1345  +      char const * part = cson_string_cstr(cson_value_get_string(cson_array_get(g.json.cmd.a, i)));
         1346  +      if(!part){
         1347  +        fossil_warning("Iterating further than expected in %s.",
         1348  +                       __FILE__);
         1349  +        break;
         1350  +      }
         1351  +      blob_appendf(&path,"%s%s", (i>1 ? "/": ""), part);
         1352  +    }
         1353  +    rc = json_new_string((blob_size(&path)>0)
         1354  +                         ? blob_buffer(&path)
         1355  +                         : "")
         1356  +      /* reminder; we need an empty string instead of NULL
         1357  +         in this case, to avoid what outwardly looks like
         1358  +         (but is not) an allocation error in
         1359  +         json_create_response().
         1360  +      */
         1361  +      ;
         1362  +    blob_reset(&path);
         1363  +    return rc;
         1364  +  }
         1365  +}
         1366  +
         1367  +/*
         1368  +** Returns a JSON Object representation of the global g object.
         1369  +** Returned value is owned by the caller.
         1370  +*/
         1371  +cson_value * json_g_to_json(){
         1372  +  cson_object * o = NULL;
         1373  +  cson_object * pay = NULL;
         1374  +  pay = o = cson_new_object();
         1375  +
         1376  +#define INT(OBJ,K) cson_object_set(o, #K, json_new_int(OBJ.K))
         1377  +#define CSTR(OBJ,K) cson_object_set(o, #K, OBJ.K ? json_new_string(OBJ.K) : cson_value_null())
         1378  +#define VAL(K,V) cson_object_set(o, #K, (V) ? (V) : cson_value_null())
         1379  +  VAL(capabilities, json_cap_value());
         1380  +  INT(g, argc);
         1381  +  INT(g, isConst);
         1382  +  INT(g, useAttach);
         1383  +  INT(g, configOpen);
         1384  +  INT(g, repositoryOpen);
         1385  +  INT(g, localOpen);
         1386  +  INT(g, minPrefix);
         1387  +  INT(g, fSqlTrace);
         1388  +  INT(g, fSqlStats);
         1389  +  INT(g, fSqlPrint);
         1390  +  INT(g, fQuiet);
         1391  +  INT(g, fHttpTrace);
         1392  +  INT(g, fSystemTrace);
         1393  +  INT(g, fNoSync);
         1394  +  INT(g, iErrPriority);
         1395  +  INT(g, sslNotAvailable);
         1396  +  INT(g, cgiOutput);
         1397  +  INT(g, xferPanic);
         1398  +  INT(g, fullHttpReply);
         1399  +  INT(g, xlinkClusterOnly);
         1400  +  INT(g, fTimeFormat);
         1401  +  INT(g, markPrivate);
         1402  +  INT(g, clockSkewSeen);
         1403  +  INT(g, isHTTP);
         1404  +  INT(g, urlIsFile);
         1405  +  INT(g, urlIsHttps);
         1406  +  INT(g, urlIsSsh);
         1407  +  INT(g, urlPort);
         1408  +  INT(g, urlDfltPort);
         1409  +  INT(g, dontKeepUrl);
         1410  +  INT(g, useLocalauth);
         1411  +  INT(g, noPswd);
         1412  +  INT(g, userUid);
         1413  +  INT(g, rcvid);
         1414  +  INT(g, okCsrf);
         1415  +  INT(g, thTrace);
         1416  +  INT(g, isHome);
         1417  +  INT(g, nAux);
         1418  +  INT(g, allowSymlinks);
         1419  +
         1420  +  CSTR(g, zMainDbType);
         1421  +  CSTR(g, zHome);
         1422  +  CSTR(g, zLocalRoot);
         1423  +  CSTR(g, zPath);
         1424  +  CSTR(g, zExtra);
         1425  +  CSTR(g, zBaseURL);
         1426  +  CSTR(g, zTop);
         1427  +  CSTR(g, zContentType);
         1428  +  CSTR(g, zErrMsg);
         1429  +  CSTR(g, urlName);
         1430  +  CSTR(g, urlHostname);
         1431  +  CSTR(g, urlProtocol);
         1432  +  CSTR(g, urlPath);
         1433  +  CSTR(g, urlUser);
         1434  +  CSTR(g, urlPasswd);
         1435  +  CSTR(g, urlCanonical);
         1436  +  CSTR(g, urlProxyAuth);
         1437  +  CSTR(g, urlFossil);
         1438  +  CSTR(g, zLogin);
         1439  +  CSTR(g, zSSLIdentity);
         1440  +  CSTR(g, zIpAddr);
         1441  +  CSTR(g, zNonce);
         1442  +  CSTR(g, zCsrfToken);
         1443  +
         1444  +  o = cson_new_object();
         1445  +  cson_object_set(pay, "json", cson_object_value(o) );
         1446  +  INT(g.json, isJsonMode);
         1447  +  INT(g.json, resultCode);
         1448  +  INT(g.json, errorDetailParanoia);
         1449  +  INT(g.json, dispatchDepth);
         1450  +  VAL(authToken, g.json.authToken);
         1451  +  CSTR(g.json, jsonp);
         1452  +  VAL(gc, g.json.gc.v);
         1453  +  VAL(cmd, g.json.cmd.v);
         1454  +  VAL(param, g.json.param.v);
         1455  +  VAL(POST, g.json.post.v);
         1456  +  VAL(warnings, g.json.warnings.v);
         1457  +  /*cson_output_opt outOpt;*/
         1458  +
         1459  +  
         1460  +#undef INT
         1461  +#undef CSTR
         1462  +#undef VAL
         1463  +  return cson_object_value(pay);
         1464  +}
         1465  +
         1466  +
         1467  +/*
         1468  +** Creates a new Fossil/JSON response envelope skeleton.  It is owned
         1469  +** by the caller, who must eventually free it using cson_value_free(),
         1470  +** or add it to a cson container to transfer ownership. Returns NULL
         1471  +** on error.
         1472  +**
         1473  +** If payload is not NULL and resultCode is 0 then it is set as the
         1474  +** "payload" property of the returned object.  If resultCode is
         1475  +** non-zero and payload is not NULL then this function calls
         1476  +** cson_value_free(payload) and does not insert the payload into the
         1477  +** response. In either case, onwership of payload is transfered to (or
         1478  +** shared with, if the caller holds a reference) this function.
         1479  +**
         1480  +** pMsg is an optional message string property (resultText) of the
         1481  +** response. If resultCode is non-0 and pMsg is NULL then
         1482  +** json_err_cstr() is used to get the error string. The caller may
         1483  +** provide his own or may use an empty string to suppress the
         1484  +** resultText property.
         1485  +**
         1486  +*/
         1487  +cson_value * json_create_response( int resultCode,
         1488  +                                   char const * pMsg,
         1489  +                                   cson_value * payload){
         1490  +  cson_value * v = NULL;
         1491  +  cson_value * tmp = NULL;
         1492  +  cson_object * o = NULL;
         1493  +  int rc;
         1494  +  resultCode = json_dumbdown_rc(resultCode);
         1495  +  o = cson_new_object();
         1496  +  v = cson_object_value(o);
         1497  +  if( ! o ) return NULL;
         1498  +#define SET(K) if(!tmp) goto cleanup; \
         1499  +  rc = cson_object_set( o, K, tmp ); \
         1500  +  if(rc) do{\
         1501  +    cson_value_free(tmp); \
         1502  +    tmp = NULL; \
         1503  +    goto cleanup; \
         1504  +  }while(0)
         1505  +
         1506  +  tmp = cson_value_new_string(MANIFEST_UUID,strlen(MANIFEST_UUID));
         1507  +  SET("fossil");
         1508  +
         1509  +  tmp = json_new_timestamp(-1);
         1510  +  SET(FossilJsonKeys.timestamp);
         1511  +
         1512  +  if( 0 != resultCode ){
         1513  +    if( ! pMsg ){
         1514  +      pMsg = g.zErrMsg;
         1515  +      if(!pMsg){
         1516  +        pMsg = json_err_cstr(resultCode);
         1517  +      }
         1518  +    }
         1519  +    tmp = json_new_string(json_rc_cstr(resultCode));
         1520  +    SET(FossilJsonKeys.resultCode);
         1521  +  }
         1522  +
         1523  +  if( pMsg && *pMsg ){
         1524  +    tmp = json_new_string(pMsg);
         1525  +    SET(FossilJsonKeys.resultText);
         1526  +  }
         1527  +
         1528  +  if(g.json.cmd.commandStr){
         1529  +    tmp = json_new_string(g.json.cmd.commandStr);
         1530  +  }else{
         1531  +    tmp = json_response_command_path();
         1532  +  }
         1533  +  SET("command");
         1534  +  
         1535  +  tmp = json_getenv(FossilJsonKeys.requestId);
         1536  +  if( tmp ) cson_object_set( o, FossilJsonKeys.requestId, tmp );
         1537  +
         1538  +  if(0){/* these are only intended for my own testing...*/
         1539  +    if(g.json.cmd.v){
         1540  +      tmp = g.json.cmd.v;
         1541  +      SET("$commandPath");
         1542  +    }
         1543  +    if(g.json.param.v){
         1544  +      tmp = g.json.param.v;
         1545  +      SET("$params");
         1546  +    }
         1547  +    if(0){/*Only for debuggering, add some info to the response.*/
         1548  +      tmp = cson_value_new_integer( g.json.cmd.offset );
         1549  +      cson_object_set( o, "cmd.offset", tmp );
         1550  +      cson_object_set( o, "isCGI", cson_value_new_bool( g.isHTTP ) );
         1551  +    }
         1552  +  }
         1553  +
         1554  +  if(HAS_TIMER){
         1555  +    /* This is, philosophically speaking, not quite the right place
         1556  +       for ending the timer, but this is the one function which all of
         1557  +       the JSON exit paths use (and they call it after processing,
         1558  +       just before they end).
         1559  +    */
         1560  +    double span;
         1561  +    span = END_TIMER;
         1562  +    /* i'm actually seeing sub-ms runtimes in some tests, but a time of
         1563  +       0 is "just wrong", so we'll bump that up to 1ms.
         1564  +    */
         1565  +    cson_object_set(o,"procTimeMs", cson_value_new_integer((cson_int_t)((span>1.0)?span:1)));
         1566  +  }
         1567  +  if(g.json.warnings.v){
         1568  +    tmp = g.json.warnings.v;
         1569  +    SET("warnings");
         1570  +  }
         1571  +  
         1572  +  /* Only add the payload to SUCCESS responses. Else delete it. */
         1573  +  if( NULL != payload ){
         1574  +    if( resultCode ){
         1575  +      cson_value_free(payload);
         1576  +      payload = NULL;
         1577  +    }else{
         1578  +      tmp = payload;
         1579  +      SET(FossilJsonKeys.payload);
         1580  +    }
         1581  +  }
         1582  +
         1583  +  if(json_find_option_bool("debugFossilG","json-debug-g",NULL,0)
         1584  +     &&(g.perm.Admin||g.perm.Setup)){
         1585  +    tmp = json_g_to_json();
         1586  +    SET("g");
         1587  +  }
         1588  +  
         1589  +#undef SET
         1590  +  goto ok;
         1591  +  cleanup:
         1592  +  cson_value_free(v);
         1593  +  v = NULL;
         1594  +  ok:
         1595  +  return v;
         1596  +}
         1597  +
         1598  +/*
         1599  +** Outputs a JSON error response to either the cgi_xxx() family of
         1600  +** buffers (in CGI/server mode) or stdout (in CLI mode). If rc is 0
         1601  +** then g.json.resultCode is used. If that is also 0 then the "Unknown
         1602  +** Error" code is used.
         1603  +**
         1604  +** If g.isHTTP then the generated JSON error response object replaces
         1605  +** any currently buffered page output. Because the output goes via
         1606  +** the cgi_xxx() family of functions, this function inherits any
         1607  +** compression which fossil does for its output.
         1608  +**
         1609  +** If alsoOutput is true AND g.isHTTP then cgi_reply() is called to
         1610  +** flush the output (and headers). Generally only do this if you are
         1611  +** about to call exit().
         1612  +**
         1613  +** !g.isHTTP then alsoOutput is ignored and all output is sent to
         1614  +** stdout immediately.
         1615  +**
         1616  +** For generating the resultText property: if msg is not NULL then it
         1617  +** is used as-is. If it is NULL then g.zErrMsg is checked, and if that
         1618  +** is NULL then json_err_cstr(code) is used.
         1619  +*/
         1620  +void json_err( int code, char const * msg, char alsoOutput ){
         1621  +  int rc = code ? code : (g.json.resultCode
         1622  +                          ? g.json.resultCode
         1623  +                          : FSL_JSON_E_UNKNOWN);
         1624  +  cson_value * resp = NULL;
         1625  +  rc = json_dumbdown_rc(rc);
         1626  +  if( rc && !msg ){
         1627  +    msg = g.zErrMsg;
         1628  +    if(!msg){
         1629  +      msg = json_err_cstr(rc);
         1630  +    }
         1631  +  }
         1632  +  resp = json_create_response(rc, msg, NULL);
         1633  +  if(!resp){
         1634  +    /* about the only error case here is out-of-memory. DO NOT
         1635  +       call fossil_panic() here because that calls this function.
         1636  +    */
         1637  +    fprintf(stderr, "%s: Fatal error: could not allocate "
         1638  +            "response object.\n", fossil_nameofexe());
         1639  +    fossil_exit(1);
         1640  +  }
         1641  +  if( g.isHTTP ){
         1642  +    if(alsoOutput){
         1643  +      json_send_response(resp);
         1644  +    }else{
         1645  +      /* almost a duplicate of json_send_response() :( */
         1646  +      cgi_reset_content();
         1647  +      if( g.json.jsonp ){
         1648  +        cgi_printf("%s(",g.json.jsonp);
         1649  +      }
         1650  +      cson_output( resp, cson_data_dest_cgi, NULL, &g.json.outOpt );
         1651  +      if( g.json.jsonp ){
         1652  +        cgi_append_content(")",1);
         1653  +      }
         1654  +    }
         1655  +  }else{
         1656  +    json_send_response(resp);
         1657  +  }
         1658  +  cson_value_free(resp);
         1659  +}
         1660  +
         1661  +/*
         1662  +** Sets g.json.resultCode and g.zErrMsg, but does not report the error
         1663  +** via json_err(). Returns the code passed to it.
         1664  +**
         1665  +** code must be in the inclusive range 1000..9999.
         1666  +*/
         1667  +int json_set_err( int code, char const * fmt, ... ){
         1668  +  assert( (code>=1000) && (code<=9999) );
         1669  +  free(g.zErrMsg);
         1670  +  g.json.resultCode = code;
         1671  +  if(!fmt || !*fmt){
         1672  +    g.zErrMsg = mprintf("%s", json_err_cstr(code));
         1673  +  }else{
         1674  +    va_list vargs;
         1675  +    va_start(vargs,fmt);
         1676  +    char * msg = vmprintf(fmt, vargs);
         1677  +    va_end(vargs);
         1678  +    g.zErrMsg = msg;
         1679  +  }
         1680  +  return code;
         1681  +}
         1682  +
         1683  +/*
         1684  +** Iterates through a prepared SELECT statement and converts each row
         1685  +** to a JSON object. If pTgt is not NULL then this function will
         1686  +** append the results to pTgt and return cson_array_value(pTgt). If
         1687  +** pTgt is NULL then a new Array object is created and returned (owned
         1688  +** by the caller). Each row of pStmt is converted to an Object and
         1689  +** appended to the array. If the result set has no rows AND pTgt is
         1690  +** NULL then NULL (not an empty array) is returned.
         1691  +*/
         1692  +cson_value * json_stmt_to_array_of_obj(Stmt *pStmt,
         1693  +                                       cson_array * pTgt){
         1694  +  cson_array * a = pTgt;
         1695  +  char const * warnMsg = NULL;
         1696  +  cson_value * colNamesV = NULL;
         1697  +  cson_array * colNames = NULL;
         1698  +  while( (SQLITE_ROW==db_step(pStmt)) ){
         1699  +    cson_value * row = NULL;
         1700  +    if(!a){
         1701  +      a = cson_new_array();
         1702  +      assert(NULL!=a);
         1703  +    }
         1704  +    if(!colNames){
         1705  +      colNamesV = cson_sqlite3_column_names(pStmt->pStmt);
         1706  +      assert(NULL != colNamesV);
         1707  +      cson_value_add_reference(colNamesV);
         1708  +      colNames = cson_value_get_array(colNamesV);
         1709  +      assert(NULL != colNames);
         1710  +    }      
         1711  +    row = cson_sqlite3_row_to_object2(pStmt->pStmt, colNames);
         1712  +    if(!row && !warnMsg){
         1713  +      warnMsg = "Could not convert at least one result row to JSON.";
         1714  +      continue;
         1715  +    }
         1716  +    if( 0 != cson_array_append(a, row) ){
         1717  +      cson_value_free(row);
         1718  +      assert( 0 && "Alloc error.");
         1719  +      if(pTgt != a) {
         1720  +        cson_free_array(a);
         1721  +      }
         1722  +      return NULL;
         1723  +    }
         1724  +  }
         1725  +  cson_value_free(colNamesV);
         1726  +  if(warnMsg){
         1727  +    json_warn( FSL_JSON_W_ROW_TO_JSON_FAILED, warnMsg );
         1728  +  }
         1729  +  return cson_array_value(a);  
         1730  +}
         1731  +
         1732  +/*
         1733  +** Works just like json_stmt_to_array_of_obj(), but each row in the
         1734  +** result set is represented as an Array of values instead of an
         1735  +** Object (key/value pairs). If pTgt is NULL and the statement
         1736  +** has no results then NULL is returned, not an empty array.
         1737  +*/
         1738  +cson_value * json_stmt_to_array_of_array(Stmt *pStmt,
         1739  +                                         cson_array * pTgt){
         1740  +  cson_array * a = pTgt;
         1741  +  while( (SQLITE_ROW==db_step(pStmt)) ){
         1742  +    cson_value * row = NULL;
         1743  +    if(!a){
         1744  +      a = cson_new_array();
         1745  +      assert(NULL!=a);
         1746  +    }
         1747  +    row = cson_sqlite3_row_to_array(pStmt->pStmt);
         1748  +    cson_array_append(a, row);
         1749  +  }
         1750  +  return cson_array_value(a);
         1751  +}
         1752  +
         1753  +
         1754  +/*
         1755  +** Executes the given SQL and runs it through
         1756  +** json_stmt_to_array_of_obj(), returning the result of that
         1757  +** function. If resetBlob is true then blob_reset(pSql) is called
         1758  +** after preparing the query.
         1759  +**
         1760  +** pTgt has the same semantics as described for
         1761  +** json_stmt_to_array_of_obj().
         1762  +*/
         1763  +cson_value * json_sql_to_array_of_obj(Blob * pSql, cson_array * pTgt,
         1764  +                                      char resetBlob){
         1765  +  Stmt q = empty_Stmt;
         1766  +  cson_value * pay = NULL;
         1767  +  assert( blob_size(pSql) > 0 );
         1768  +  db_prepare(&q, "%s", blob_str(pSql));
         1769  +  if(resetBlob){
         1770  +    blob_reset(pSql);
         1771  +  }
         1772  +  pay = json_stmt_to_array_of_obj(&q, pTgt);
         1773  +  db_finalize(&q);
         1774  +  return pay;
         1775  +
         1776  +}
         1777  +
         1778  +/*
         1779  +** If the given rid has any tags associated with it, this function
         1780  +** returns a JSON Array containing the tag names, else it returns
         1781  +** NULL.
         1782  +**
         1783  +** See info_tags_of_checkin() for more details (this is simply a JSON
         1784  +** wrapper for that function).
         1785  +*/
         1786  +cson_value * json_tags_for_rid(int rid, char propagatingOnly){
         1787  +  cson_value * v = NULL;
         1788  +  char * tags = info_tags_of_checkin(rid, propagatingOnly);
         1789  +  if(tags){
         1790  +    if(*tags){
         1791  +      v = json_string_split2(tags,',',0);
         1792  +    }
         1793  +    free(tags);
         1794  +  }
         1795  +  return v;  
         1796  +}
         1797  +
         1798  +/*
         1799  + ** Returns a "new" value representing the boolean value of zVal
         1800  + ** (false if zVal is NULL). Note that cson does not really allocate
         1801  + ** any memory for boolean values, but they "should" (for reasons of
         1802  + ** style and philosophy) be cleaned up like any other values (but
         1803  + ** it's a no-op for bools).
         1804  + */
         1805  +cson_value * json_value_to_bool(cson_value const * zVal){
         1806  +  return cson_value_get_bool(zVal)
         1807  +    ? cson_value_true()
         1808  +    : cson_value_false();
         1809  +}
         1810  +
         1811  +/*
         1812  +** Impl of /json/resultCodes
         1813  +**
         1814  +*/
         1815  +cson_value * json_page_resultCodes(){
         1816  +    cson_value * listV = cson_value_new_array();
         1817  +    cson_array * list = cson_value_get_array(listV);
         1818  +    cson_value * objV = NULL;
         1819  +    cson_object * obj = NULL;
         1820  +    cson_string * kRC;
         1821  +    cson_string * kSymbol;
         1822  +    cson_string * kNumber;
         1823  +    cson_string * kDesc;
         1824  +    int rc = cson_array_reserve( list, 35 );
         1825  +    if(rc){
         1826  +        assert( 0 && "Allocation error.");
         1827  +        exit(1);
         1828  +    }
         1829  +    kRC = cson_new_string("resultCode",10);
         1830  +    kSymbol = cson_new_string("cSymbol",7);
         1831  +    kNumber = cson_new_string("number",6);
         1832  +    kDesc = cson_new_string("description",11);
         1833  +#define C(K) objV = cson_value_new_object(); obj = cson_value_get_object(objV); \
         1834  +    cson_object_set_s(obj, kRC, json_new_string(json_rc_cstr(FSL_JSON_E_##K)) ); \
         1835  +    cson_object_set_s(obj, kSymbol, json_new_string("FSL_JSON_E_"#K) );             \
         1836  +    cson_object_set_s(obj, kNumber, cson_value_new_integer(FSL_JSON_E_##K) );        \
         1837  +    cson_object_set_s(obj, kDesc, json_new_string(json_err_cstr(FSL_JSON_E_##K))); \
         1838  +    cson_array_append( list, objV ); obj = NULL; objV = NULL
         1839  +
         1840  +    C(GENERIC);
         1841  +    C(INVALID_REQUEST);
         1842  +    C(UNKNOWN_COMMAND);
         1843  +    C(UNKNOWN);
         1844  +    C(TIMEOUT);
         1845  +    C(ASSERT);
         1846  +    C(ALLOC);
         1847  +    C(NYI);
         1848  +    C(PANIC);
         1849  +    C(MANIFEST_READ_FAILED);
         1850  +    C(FILE_OPEN_FAILED);
         1851  +    
         1852  +    C(AUTH);
         1853  +    C(MISSING_AUTH);
         1854  +    C(DENIED);
         1855  +    C(WRONG_MODE);
         1856  +    C(LOGIN_FAILED);
         1857  +    C(LOGIN_FAILED_NOSEED);
         1858  +    C(LOGIN_FAILED_NONAME);
         1859  +    C(LOGIN_FAILED_NOPW);
         1860  +    C(LOGIN_FAILED_NOTFOUND);
         1861  +
         1862  +    C(USAGE);
         1863  +    C(INVALID_ARGS);
         1864  +    C(MISSING_ARGS);
         1865  +    C(AMBIGUOUS_UUID);
         1866  +    C(UNRESOLVED_UUID);
         1867  +    C(RESOURCE_ALREADY_EXISTS);
         1868  +    C(RESOURCE_NOT_FOUND);
         1869  +
         1870  +    C(DB);
         1871  +    C(STMT_PREP);
         1872  +    C(STMT_BIND);
         1873  +    C(STMT_EXEC);
         1874  +    C(DB_LOCKED);
         1875  +    C(DB_NEEDS_REBUILD);
         1876  +    C(DB_NOT_FOUND);
         1877  +    C(DB_NOT_VALID);
         1878  +#undef C
         1879  +    return listV;
         1880  +}
         1881  +
         1882  +
         1883  +/*
         1884  +** /json/version implementation.
         1885  +**
         1886  +** Returns the payload object (owned by the caller).
         1887  +*/
         1888  +cson_value * json_page_version(){
         1889  +  cson_value * jval = NULL;
         1890  +  cson_object * jobj = NULL;
         1891  +  jval = cson_value_new_object();
         1892  +  jobj = cson_value_get_object(jval);
         1893  +#define FSET(X,K) cson_object_set( jobj, K, cson_value_new_string(X,strlen(X)))
         1894  +  FSET(MANIFEST_UUID,"manifestUuid");
         1895  +  FSET(MANIFEST_VERSION,"manifestVersion");
         1896  +  FSET(MANIFEST_DATE,"manifestDate");
         1897  +  FSET(MANIFEST_YEAR,"manifestYear");
         1898  +  FSET(RELEASE_VERSION,"releaseVersion");
         1899  +#undef FSET
         1900  +  cson_object_set( jobj, "releaseVersionNumber",
         1901  +                   cson_value_new_integer(RELEASE_VERSION_NUMBER) );
         1902  +  cson_object_set( jobj, "resultCodeParanoiaLevel",
         1903  +                   cson_value_new_integer(g.json.errorDetailParanoia) );
         1904  +  return jval;
         1905  +}
         1906  +
         1907  +
         1908  +/*
         1909  +** Returns the current user's capabilities string as a String value.
         1910  +** Returned value is owned by the caller, and will only be NULL if
         1911  +** g.userUid is invalid or an out of memory error. Or, it turns out,
         1912  +** in CLI mode (where there is no logged-in user).
         1913  +*/
         1914  +cson_value * json_cap_value(){
         1915  +  if(g.userUid<=0){
         1916  +    return NULL;
         1917  +  }else{
         1918  +    Stmt q = empty_Stmt;
         1919  +    cson_value * val = NULL;
         1920  +    db_prepare(&q, "SELECT cap FROM user WHERE uid=%d", g.userUid);
         1921  +    if( db_step(&q)==SQLITE_ROW ){
         1922  +      char const * str = (char const *)sqlite3_column_text(q.pStmt,0);
         1923  +      if( str ){
         1924  +        val = json_new_string(str);
         1925  +      }
         1926  +    }
         1927  +    db_finalize(&q);
         1928  +    return val;
         1929  +  }
         1930  +}
         1931  +
         1932  +/*
         1933  +** Implementation for /json/cap
         1934  +**
         1935  +** Returned object contains details about the "capabilities" of the
         1936  +** current user (what he may/may not do).
         1937  +**
         1938  +** This is primarily intended for debuggering, but may have
         1939  +** a use in client code. (?)
         1940  +*/
         1941  +cson_value * json_page_cap(){
         1942  +  cson_value * payload = cson_value_new_object();
         1943  +  cson_value * sub = cson_value_new_object();
         1944  +  Stmt q;
         1945  +  cson_object * obj = cson_value_get_object(payload);
         1946  +  db_prepare(&q, "SELECT login, cap FROM user WHERE uid=%d", g.userUid);
         1947  +  if( db_step(&q)==SQLITE_ROW ){
         1948  +    /* reminder: we don't use g.zLogin because it's 0 for the guest
         1949  +       user and the HTML UI appears to currently allow the name to be
         1950  +       changed (but doing so would break other code). */
         1951  +    char const * str = (char const *)sqlite3_column_text(q.pStmt,0);
         1952  +    if( str ){
         1953  +      cson_object_set( obj, "name",
         1954  +                       cson_value_new_string(str,strlen(str)) );
         1955  +    }
         1956  +    str = (char const *)sqlite3_column_text(q.pStmt,1);
         1957  +    if( str ){
         1958  +      cson_object_set( obj, "capabilities",
         1959  +                       cson_value_new_string(str,strlen(str)) );
         1960  +    }
         1961  +  }
         1962  +  db_finalize(&q);
         1963  +  cson_object_set( obj, "permissionFlags", sub );
         1964  +  obj = cson_value_get_object(sub);
         1965  +
         1966  +#define ADD(X,K) cson_object_set(obj, K, cson_value_new_bool(g.perm.X))
         1967  +  ADD(Setup,"setup");
         1968  +  ADD(Admin,"admin");
         1969  +  ADD(Delete,"delete");
         1970  +  ADD(Password,"password");
         1971  +  ADD(Query,"query"); /* don't think this one is actually used */
         1972  +  ADD(Write,"checkin");
         1973  +  ADD(Read,"checkout");
         1974  +  ADD(History,"history");
         1975  +  ADD(Clone,"clone");
         1976  +  ADD(RdWiki,"readWiki");
         1977  +  ADD(NewWiki,"createWiki");
         1978  +  ADD(ApndWiki,"appendWiki");
         1979  +  ADD(WrWiki,"editWiki");
         1980  +  ADD(RdTkt,"readTicket");
         1981  +  ADD(NewTkt,"createTicket");
         1982  +  ADD(ApndTkt,"appendTicket");
         1983  +  ADD(WrTkt,"editTicket");
         1984  +  ADD(Attach,"attachFile");
         1985  +  ADD(TktFmt,"createTicketReport");
         1986  +  ADD(RdAddr,"readPrivate");
         1987  +  ADD(Zip,"zip");
         1988  +  ADD(Private,"xferPrivate");
         1989  +#undef ADD
         1990  +  return payload;
         1991  +}
         1992  +
         1993  +/*
         1994  +** Implementation of the /json/stat page/command.
         1995  +**
         1996  +*/
         1997  +cson_value * json_page_stat(){
         1998  +  i64 t, fsize;
         1999  +  int n, m;
         2000  +  int full;
         2001  +  const char *zDb;
         2002  +  enum { BufLen = 1000 };
         2003  +  char zBuf[BufLen];
         2004  +  cson_value * jv = NULL;
         2005  +  cson_object * jo = NULL;
         2006  +  cson_value * jv2 = NULL;
         2007  +  cson_object * jo2 = NULL;
         2008  +  if( !g.perm.Read ){
         2009  +    json_set_err(FSL_JSON_E_DENIED,
         2010  +                 "Requires 'o' permissions.");
         2011  +    return NULL;
         2012  +  }
         2013  +  full = json_find_option_bool("full",NULL,"f",0);
         2014  +#define SETBUF(O,K) cson_object_set(O, K, cson_value_new_string(zBuf, strlen(zBuf)));
         2015  +
         2016  +  jv = cson_value_new_object();
         2017  +  jo = cson_value_get_object(jv);
         2018  +
         2019  +  sqlite3_snprintf(BufLen, zBuf, db_get("project-name",""));
         2020  +  SETBUF(jo, "projectName");
         2021  +  /* FIXME: don't include project-description until we ensure that
         2022  +     zBuf will always be big enough. We "should" replace zBuf
         2023  +     with a blob for this purpose.
         2024  +  */
         2025  +  fsize = file_size(g.zRepositoryName);
         2026  +  cson_object_set(jo, "repositorySize", cson_value_new_integer((cson_int_t)fsize));
         2027  +
         2028  +  if(full){
         2029  +    n = db_int(0, "SELECT count(*) FROM blob");
         2030  +    m = db_int(0, "SELECT count(*) FROM delta");
         2031  +    cson_object_set(jo, "blobCount", cson_value_new_integer((cson_int_t)n));
         2032  +    cson_object_set(jo, "deltaCount", cson_value_new_integer((cson_int_t)m));
         2033  +    if( n>0 ){
         2034  +      int a, b;
         2035  +      Stmt q;
         2036  +      db_prepare(&q, "SELECT total(size), avg(size), max(size)"
         2037  +                 " FROM blob WHERE size>0");
         2038  +      db_step(&q);
         2039  +      t = db_column_int64(&q, 0);
         2040  +      cson_object_set(jo, "uncompressedArtifactSize",
         2041  +                      cson_value_new_integer((cson_int_t)t));
         2042  +      cson_object_set(jo, "averageArtifactSize",
         2043  +                      cson_value_new_integer((cson_int_t)db_column_int(&q, 1)));
         2044  +      cson_object_set(jo, "maxArtifactSize",
         2045  +                      cson_value_new_integer((cson_int_t)db_column_int(&q, 2)));
         2046  +      db_finalize(&q);
         2047  +      if( t/fsize < 5 ){
         2048  +        b = 10;
         2049  +        fsize /= 10;
         2050  +      }else{
         2051  +        b = 1;
         2052  +      }
         2053  +      a = t/fsize;
         2054  +      sqlite3_snprintf(BufLen,zBuf, "%d:%d", a, b);
         2055  +      SETBUF(jo, "compressionRatio");
         2056  +    }
         2057  +    n = db_int(0, "SELECT count(distinct mid) FROM mlink /*scan*/");
         2058  +    cson_object_set(jo, "checkinCount", cson_value_new_integer((cson_int_t)n));
         2059  +    n = db_int(0, "SELECT count(*) FROM filename /*scan*/");
         2060  +    cson_object_set(jo, "fileCount", cson_value_new_integer((cson_int_t)n));
         2061  +    n = db_int(0, "SELECT count(*) FROM tag  /*scan*/"
         2062  +               " WHERE +tagname GLOB 'wiki-*'");
         2063  +    cson_object_set(jo, "wikiPageCount", cson_value_new_integer((cson_int_t)n));
         2064  +    n = db_int(0, "SELECT count(*) FROM tag  /*scan*/"
         2065  +               " WHERE +tagname GLOB 'tkt-*'");
         2066  +    cson_object_set(jo, "ticketCount", cson_value_new_integer((cson_int_t)n));
         2067  +  }/*full*/
         2068  +  n = db_int(0, "SELECT julianday('now') - (SELECT min(mtime) FROM event)"
         2069  +                " + 0.99");
         2070  +  cson_object_set(jo, "ageDays", cson_value_new_integer((cson_int_t)n));
         2071  +  cson_object_set(jo, "ageYears", cson_value_new_double(n/365.24));
         2072  +  sqlite3_snprintf(BufLen, zBuf, db_get("project-code",""));
         2073  +  SETBUF(jo, "projectCode");
         2074  +  sqlite3_snprintf(BufLen, zBuf, db_get("server-code",""));
         2075  +  SETBUF(jo, "serverCode");
         2076  +  cson_object_set(jo, "compiler", cson_value_new_string(COMPILER_NAME, strlen(COMPILER_NAME)));
         2077  +
         2078  +  jv2 = cson_value_new_object();
         2079  +  jo2 = cson_value_get_object(jv2);
         2080  +  cson_object_set(jo, "sqlite", jv2);
         2081  +  sqlite3_snprintf(BufLen, zBuf, "%.19s [%.10s] (%s)",
         2082  +                   SQLITE_SOURCE_ID, &SQLITE_SOURCE_ID[20], SQLITE_VERSION);
         2083  +  SETBUF(jo2, "version");
         2084  +  zDb = db_name("repository");
         2085  +  cson_object_set(jo2, "pageCount", cson_value_new_integer((cson_int_t)db_int(0, "PRAGMA %s.page_count", zDb)));
         2086  +  cson_object_set(jo2, "pageSize", cson_value_new_integer((cson_int_t)db_int(0, "PRAGMA %s.page_size", zDb)));
         2087  +  cson_object_set(jo2, "freeList", cson_value_new_integer((cson_int_t)db_int(0, "PRAGMA %s.freelist_count", zDb)));
         2088  +  sqlite3_snprintf(BufLen, zBuf, "%s", db_text(0, "PRAGMA %s.encoding", zDb));
         2089  +  SETBUF(jo2, "encoding");
         2090  +  sqlite3_snprintf(BufLen, zBuf, "%s", db_text(0, "PRAGMA %s.journal_mode", zDb));
         2091  +  cson_object_set(jo2, "journalMode", *zBuf ? cson_value_new_string(zBuf, strlen(zBuf)) : cson_value_null());
         2092  +  return jv;
         2093  +#undef SETBUF
         2094  +}
         2095  +
         2096  +
         2097  +
         2098  +
         2099  +/*
         2100  +** Creates a comma-separated list of command names
         2101  +** taken from zPages. zPages must be an array of objects
         2102  +** whose final entry MUST have a NULL name value or results
         2103  +** are undefined.
         2104  +**
         2105  +** The list is appended to pOut. The number of items (not bytes)
         2106  +** appended are returned.
         2107  +*/
         2108  +static int json_pagedefs_to_string(JsonPageDef const * zPages,
         2109  +                                   Blob * pOut){
         2110  +  int i = 0;
         2111  +  for( ; zPages->name; ++zPages, ++i ){
         2112  +    if(g.isHTTP && zPages->runMode < 0) continue;
         2113  +    else if(zPages->runMode > 0) continue;
         2114  +    blob_appendf(pOut, zPages->name, -1);
         2115  +    if((zPages+1)->name){
         2116  +      blob_append(pOut, ", ",2);
         2117  +    }
         2118  +  }
         2119  +  return i;
         2120  +}
         2121  +
         2122  +
         2123  +cson_value * json_page_dispatch_helper(JsonPageDef const * pages){
         2124  +  JsonPageDef const * def;
         2125  +  char const * cmd = json_command_arg(1+g.json.dispatchDepth);
         2126  +  assert( NULL != pages );
         2127  +  if( ! cmd ){
         2128  +    Blob cmdNames = empty_blob;
         2129  +    json_pagedefs_to_string(pages, &cmdNames);
         2130  +    json_set_err(FSL_JSON_E_MISSING_ARGS,
         2131  +                 "No subcommand specified. Try one of (%s).",
         2132  +                 blob_str(&cmdNames));
         2133  +    blob_reset(&cmdNames);
         2134  +    return NULL;
         2135  +  }
         2136  +  def = json_handler_for_name( cmd, pages );
         2137  +  if(!def){
         2138  +    g.json.resultCode = FSL_JSON_E_UNKNOWN_COMMAND;
         2139  +    return NULL;
         2140  +  }
         2141  +  else{
         2142  +    ++g.json.dispatchDepth;
         2143  +    return (*def->func)();
         2144  +  }
         2145  +}
         2146  +
         2147  +
         2148  +/*
         2149  +** Impl of /json/rebuild. Requires admin previleges.
         2150  +*/
         2151  +static cson_value * json_page_rebuild(){
         2152  +  if( !g.perm.Admin ){
         2153  +    json_set_err(FSL_JSON_E_DENIED,"Requires 'a' privileges.");
         2154  +    return NULL;
         2155  +  }else{
         2156  +  /* Reminder: the db_xxx() ops "should" fail via the fossil core
         2157  +     error handlers, which will cause a JSON error and exit(). i.e. we
         2158  +     don't handle the errors here. TODO: confirm that all these db
         2159  +     routine fail gracefully in JSON mode.
         2160  +
         2161  +     On large repos (e.g. fossil's) this operation is likely to take
         2162  +     longer than the client timeout, which will cause it to fail (but
         2163  +     it's sqlite3, so it'll fail gracefully).
         2164  +  */
         2165  +    db_close(1);
         2166  +    db_open_repository(g.zRepositoryName);
         2167  +    db_begin_transaction();
         2168  +    rebuild_db(0, 0, 0);
         2169  +    db_end_transaction(0);
         2170  +    return NULL;
         2171  +  }
         2172  +}
         2173  +
         2174  +/*
         2175  +** Impl of /json/g. Requires admin/setup rights.
         2176  +*/
         2177  +static cson_value * json_page_g(){
         2178  +  if(!g.perm.Admin || !g.perm.Setup){
         2179  +    json_set_err(FSL_JSON_E_DENIED,
         2180  +                 "Requires 'a' or 's' privileges.");
         2181  +    return NULL;
         2182  +  }
         2183  +  return json_g_to_json();
         2184  +}
         2185  +
         2186  +/* Impl in json_login.c. */
         2187  +cson_value * json_page_anon_password();
         2188  +/* Impl in json_artifact.c. */
         2189  +cson_value * json_page_artifact();
         2190  +/* Impl in json_branch.c. */
         2191  +cson_value * json_page_branch();
         2192  +/* Impl in json_diff.c. */
         2193  +cson_value * json_page_diff();
         2194  +/* Impl in json_login.c. */
         2195  +cson_value * json_page_login();
         2196  +/* Impl in json_login.c. */
         2197  +cson_value * json_page_logout();
         2198  +/* Impl in json_query.c. */
         2199  +cson_value * json_page_query();
         2200  +/* Impl in json_report.c. */
         2201  +cson_value * json_page_report();
         2202  +/* Impl in json_tag.c. */
         2203  +cson_value * json_page_tag();
         2204  +/* Impl in json_user.c. */
         2205  +cson_value * json_page_user();
         2206  +
         2207  +/*
         2208  +** Mapping of names to JSON pages/commands.  Each name is a subpath of
         2209  +** /json (in CGI mode) or a subcommand of the json command in CLI mode
         2210  +*/
         2211  +static const JsonPageDef JsonPageDefs[] = {
         2212  +/* please keep alphabetically sorted (case-insensitive) for maintenance reasons. */
         2213  +{"anonymousPassword", json_page_anon_password, 1},
         2214  +{"artifact", json_page_artifact, 0},
         2215  +{"branch", json_page_branch,0},
         2216  +{"cap", json_page_cap, 0},
         2217  +{"diff", json_page_diff, 0},
         2218  +{"dir", json_page_nyi, 0},
         2219  +{"g", json_page_g, 0},
         2220  +{"HAI",json_page_version,0},
         2221  +{"login",json_page_login,1},
         2222  +{"logout",json_page_logout,1},
         2223  +{"query",json_page_query,0},
         2224  +{"rebuild",json_page_rebuild,0},
         2225  +{"report", json_page_report, 0},
         2226  +{"resultCodes", json_page_resultCodes,0},
         2227  +{"stat",json_page_stat,0},
         2228  +{"tag", json_page_tag,0},
         2229  +{"ticket", json_page_nyi,0},
         2230  +{"timeline", json_page_timeline,0},
         2231  +{"user",json_page_user,0},
         2232  +{"version",json_page_version,0},
         2233  +{"whoami",json_page_whoami,0/*FIXME: work in CLI mode*/},
         2234  +{"wiki",json_page_wiki,0},
         2235  +/* Last entry MUST have a NULL name. */
         2236  +{NULL,NULL,0}
         2237  +};
         2238  +
         2239  +
         2240  +/*
         2241  +** Mapping of /json/ticket/XXX commands/paths to callbacks.
         2242  +*/
         2243  +static const JsonPageDef JsonPageDefs_Ticket[] = {
         2244  +{"get", json_page_nyi, 0},
         2245  +{"list", json_page_nyi, 0},
         2246  +{"save", json_page_nyi, 1},
         2247  +{"create", json_page_nyi, 1},
         2248  +/* Last entry MUST have a NULL name. */
         2249  +{NULL,NULL,0}
         2250  +};
         2251  +
         2252  +/*
         2253  +** Mapping of /json/artifact/XXX commands/paths to callbacks.
         2254  +*/
         2255  +static const JsonPageDef JsonPageDefs_Artifact[] = {
         2256  +{"vinfo", json_page_nyi, 0},
         2257  +{"finfo", json_page_nyi, 0},
         2258  +/* Last entry MUST have a NULL name. */
         2259  +{NULL,NULL,0}
         2260  +};
         2261  +
         2262  +/*
         2263  +** Mapping of /json/tag/XXX commands/paths to callbacks.
         2264  +*/
         2265  +static const JsonPageDef JsonPageDefs_Tag[] = {
         2266  +{"list", json_page_nyi, 0},
         2267  +{"create", json_page_nyi, 1},
         2268  +/* Last entry MUST have a NULL name. */
         2269  +{NULL,NULL,0}
         2270  +};
         2271  +
         2272  +
         2273  +#ifdef FOSSIL_ENABLE_JSON /* dupe ifdef needed for mkindex */
         2274  +/*
         2275  +** WEBPAGE: json
         2276  +**
         2277  +** Pages under /json/... must be entered into JsonPageDefs.
         2278  +** This function dispatches them, and is the HTTP equivalent of
         2279  +** json_cmd_top().
         2280  +*/
         2281  +void json_page_top(void){
         2282  +  int rc = FSL_JSON_E_UNKNOWN_COMMAND;
         2283  +  char const * cmd;
         2284  +  cson_value * payload = NULL;
         2285  +  JsonPageDef const * pageDef = NULL;
         2286  +  BEGIN_TIMER;
         2287  +  json_mode_bootstrap();
         2288  +  cmd = json_command_arg(1);
         2289  +  if(!cmd || !*cmd){
         2290  +    goto usage;
         2291  +  }
         2292  +  /*cgi_printf("{\"cmd\":\"%s\"}\n",cmd); return;*/
         2293  +  pageDef = json_handler_for_name(cmd,&JsonPageDefs[0]);
         2294  +  if( ! pageDef ){
         2295  +    json_err( FSL_JSON_E_UNKNOWN_COMMAND, NULL, 0 );
         2296  +    return;
         2297  +  }else if( pageDef->runMode < 0 /*CLI only*/){
         2298  +    rc = FSL_JSON_E_WRONG_MODE;
         2299  +  }else{
         2300  +    rc = 0;
         2301  +    g.json.dispatchDepth = 1;
         2302  +    payload = (*pageDef->func)();
         2303  +  }
         2304  +  if( g.json.resultCode ){
         2305  +    cson_value_free(payload);
         2306  +    json_err(g.json.resultCode, NULL, 0);
         2307  +  }else{
         2308  +    cson_value * root = json_create_response(rc, NULL, payload);
         2309  +    json_send_response(root);
         2310  +    cson_value_free(root);
         2311  +  }
         2312  +
         2313  +  return;
         2314  +  usage:
         2315  +  {
         2316  +    Blob cmdNames = empty_blob;
         2317  +    blob_init(&cmdNames,
         2318  +              "No command (sub-path) specified. Try one of: ",
         2319  +              -1);
         2320  +    json_pagedefs_to_string(&JsonPageDefs[0], &cmdNames);
         2321  +    json_err(FSL_JSON_E_MISSING_ARGS,
         2322  +             blob_str(&cmdNames), 0);
         2323  +    blob_reset(&cmdNames);
         2324  +  }
         2325  +
         2326  +}
         2327  +#endif /* FOSSIL_ENABLE_JSON */
         2328  +
         2329  +#ifdef FOSSIL_ENABLE_JSON /* dupe ifdef needed for mkindex */
         2330  +/*
         2331  +** This function dispatches json commands and is the CLI equivalent of
         2332  +** json_page_top().
         2333  +**
         2334  +** COMMAND: json
         2335  +**
         2336  +** Usage: %fossil json SUBCOMMAND
         2337  +**
         2338  +** The commands include:
         2339  +**
         2340  +**   branch
         2341  +**   cap
         2342  +**   stat
         2343  +**   timeline
         2344  +**   version (alias: HAI)
         2345  +**   wiki
         2346  +**
         2347  +**
         2348  +** TODOs:
         2349  +**
         2350  +**   tag
         2351  +**   ticket
         2352  +**   ...
         2353  +**
         2354  +*/
         2355  +void json_cmd_top(void){
         2356  +  char const * cmd = NULL;
         2357  +  int rc = FSL_JSON_E_UNKNOWN_COMMAND;
         2358  +  cson_value * payload = NULL;
         2359  +  JsonPageDef const * pageDef;
         2360  +  BEGIN_TIMER;
         2361  +  memset( &g.perm, 0xff, sizeof(g.perm) )
         2362  +    /* In CLI mode fossil does not use permissions
         2363  +       and they all default to false. We enable them
         2364  +       here because (A) fossil doesn't use them in local
         2365  +       mode but (B) having them set gives us one less
         2366  +       difference in the CLI/CGI/Server-mode JSON
         2367  +       handling.
         2368  +    */
         2369  +    ;
         2370  +  json_main_bootstrap();
         2371  +  json_mode_bootstrap();
         2372  +  if( 2 > cson_array_length_get(g.json.cmd.a) ){
         2373  +    goto usage;
         2374  +  }
         2375  +  db_find_and_open_repository(0, 0);
         2376  +#if 0
         2377  +  json_warn(FSL_JSON_W_ROW_TO_JSON_FAILED, "Just testing.");
         2378  +  json_warn(FSL_JSON_W_ROW_TO_JSON_FAILED, "Just testing again.");
         2379  +#endif
         2380  +  cmd = json_command_arg(1);
         2381  +  if( !cmd || !*cmd ){
         2382  +    goto usage;
         2383  +  }
         2384  +  pageDef = json_handler_for_name(cmd,&JsonPageDefs[0]);
         2385  +  if( ! pageDef ){
         2386  +    json_err( FSL_JSON_E_UNKNOWN_COMMAND, NULL, 1 );
         2387  +    return;
         2388  +  }else if( pageDef->runMode > 0 /*HTTP only*/){
         2389  +    rc = FSL_JSON_E_WRONG_MODE;
         2390  +  }else{
         2391  +    rc = 0;
         2392  +    g.json.dispatchDepth = 1;
         2393  +    payload = (*pageDef->func)();
         2394  +  }
         2395  +  if( g.json.resultCode ){
         2396  +    cson_value_free(payload);
         2397  +    json_err(g.json.resultCode, NULL, 1);
         2398  +  }else{
         2399  +    payload = json_create_response(rc, NULL, payload);
         2400  +    json_send_response(payload);
         2401  +    cson_value_free( payload );
         2402  +    if((0 != rc) && !g.isHTTP){
         2403  +      /* FIXME: we need a way of passing this error back
         2404  +         up to the routine which called this callback.
         2405  +         e.g. add g.errCode.
         2406  +      */
         2407  +      fossil_exit(1);
         2408  +    }
         2409  +  }
         2410  +  return;
         2411  +  usage:
         2412  +  {
         2413  +    Blob cmdNames = empty_blob;
         2414  +    blob_init(&cmdNames,
         2415  +              "No subcommand specified. Try one of: ", -1);
         2416  +    json_pagedefs_to_string(&JsonPageDefs[0], &cmdNames);
         2417  +    json_err(FSL_JSON_E_MISSING_ARGS,
         2418  +             blob_str(&cmdNames), 1);
         2419  +    blob_reset(&cmdNames);
         2420  +    fossil_exit(1);
         2421  +  }
         2422  +}
         2423  +#endif /* FOSSIL_ENABLE_JSON */
         2424  +
         2425  +#undef BITSET_BYTEFOR
         2426  +#undef BITSET_SET
         2427  +#undef BITSET_UNSET
         2428  +#undef BITSET_GET
         2429  +#undef BITSET_TOGGLE
         2430  +#endif /* FOSSIL_ENABLE_JSON */

Added src/json_artifact.c.

            1  +#ifdef FOSSIL_ENABLE_JSON
            2  +/*
            3  +** Copyright (c) 2011 D. Richard Hipp
            4  +**
            5  +** This program is free software; you can redistribute it and/or
            6  +** modify it under the terms of the Simplified BSD License (also
            7  +** known as the "2-Clause License" or "FreeBSD License".)
            8  +**
            9  +** This program is distributed in the hope that it will be useful,
           10  +** but without any warranty; without even the implied warranty of
           11  +** merchantability or fitness for a particular purpose.
           12  +**
           13  +** Author contact information:
           14  +**   drh@hwaci.com
           15  +**   http://www.hwaci.com/drh/
           16  +**
           17  +*/
           18  +#include "VERSION.h"
           19  +#include "config.h"
           20  +#include "json_artifact.h"
           21  +
           22  +#if INTERFACE
           23  +#include "json_detail.h"
           24  +#endif
           25  +
           26  +/*
           27  +** Internal callback for /json/artifact handlers. rid refers to
           28  +** the rid of a given type of artifact, and each callback is
           29  +** specialized to return a JSON form of one type of artifact.
           30  +**
           31  +** Implementations may assert() that rid refers to requested artifact
           32  +** type, since mismatches in the artifact types come from
           33  +** json_page_artifact() as opposed to client data.
           34  +*/
           35  +typedef cson_value * (*artifact_f)( int rid );
           36  +
           37  +/*
           38  +** Internal per-artifact-type dispatching helper.
           39  +*/
           40  +typedef struct ArtifactDispatchEntry {
           41  +  /**
           42  +     Artifact type name, e.g. "checkin", "ticket", "wiki".
           43  +   */
           44  +  char const * name;
           45  +
           46  +  /**
           47  +     JSON construction callback. Creates the contents for the
           48  +     payload.artifact property of /json/artifact responses.
           49  +  */
           50  +  artifact_f func;
           51  +} ArtifactDispatchEntry;
           52  +
           53  +
           54  +/*
           55  +** Generates an artifact Object for the given rid,
           56  +** which must refer to a Checkin.
           57  +**
           58  +** Returned value is NULL or an Object owned by the caller.
           59  +*/
           60  +cson_value * json_artifact_for_ci( int rid, char showFiles ){
           61  +  char * zParent = NULL;
           62  +  cson_value * v = NULL;
           63  +  Stmt q;
           64  +  static cson_value * eventTypeLabel = NULL;
           65  +  if(!eventTypeLabel){
           66  +    eventTypeLabel = json_new_string("checkin");
           67  +    json_gc_add("$EVENT_TYPE_LABEL(commit)", eventTypeLabel);
           68  +  }
           69  +  zParent = db_text(0,
           70  +    "SELECT uuid FROM plink, blob"
           71  +    " WHERE plink.cid=%d AND blob.rid=plink.pid AND plink.isprim",
           72  +    rid
           73  +  );
           74  +
           75  +  db_prepare(&q, 
           76  +             "SELECT uuid, "
           77  +             " cast(strftime('%%s',mtime) as int), "
           78  +             " user, "
           79  +             " comment,"
           80  +             " strftime('%%s',omtime)"
           81  +             " FROM blob, event"
           82  +             " WHERE blob.rid=%d"
           83  +             "   AND event.objid=%d",
           84  +             rid, rid
           85  +             );
           86  +  if( db_step(&q)==SQLITE_ROW ){
           87  +    cson_object * o;
           88  +    cson_value * tmpV = NULL;
           89  +    const char *zUuid = db_column_text(&q, 0);
           90  +    char * zTmp;
           91  +    const char *zUser;
           92  +    const char *zComment;
           93  +    char * zEUser, * zEComment;
           94  +    int mtime, omtime;
           95  +    v = cson_value_new_object();
           96  +    o = cson_value_get_object(v);
           97  +#define SET(K,V) cson_object_set(o,(K), (V))
           98  +    SET("type", eventTypeLabel );
           99  +    SET("uuid",json_new_string(zUuid));
          100  +    SET("isLeaf", cson_value_new_bool(is_a_leaf(rid)));
          101  +    zUser = db_column_text(&q,2);
          102  +    zEUser = db_text(0,
          103  +                   "SELECT value FROM tagxref WHERE tagid=%d AND rid=%d",
          104  +                   TAG_USER, rid);
          105  +    if(zEUser){
          106  +      SET("user", json_new_string(zEUser));
          107  +      if(0!=strcmp(zEUser,zUser)){
          108  +        SET("originUser",json_new_string(zUser));
          109  +      }
          110  +      free(zEUser);
          111  +    }else{
          112  +      SET("user",json_new_string(zUser));
          113  +    }
          114  +
          115  +    zComment = db_column_text(&q,3);
          116  +    zEComment = db_text(0, 
          117  +                   "SELECT value FROM tagxref WHERE tagid=%d AND rid=%d",
          118  +                   TAG_COMMENT, rid);
          119  +    if(zEComment){
          120  +      SET("comment",json_new_string(zEComment));
          121  +      if(0 != strcmp(zEComment,zComment)){
          122  +        SET("originComment", json_new_string(zComment));
          123  +      }
          124  +      free(zEComment);
          125  +    }else{
          126  +      SET("comment",json_new_string(zComment));
          127  +    }
          128  +
          129  +    mtime = db_column_int(&q,1);
          130  +    SET("mtime",json_new_int(mtime));
          131  +    omtime = db_column_int(&q,4);
          132  +    if(omtime && (omtime!=mtime)){
          133  +      SET("originTime",json_new_int(omtime));
          134  +    }
          135  +
          136  +    if(zParent){
          137  +      SET("parentUuid", json_new_string(zParent));
          138  +    }
          139  +
          140  +    tmpV = json_tags_for_rid(rid,0);
          141  +    if(tmpV){
          142  +      SET("tags",tmpV);
          143  +    }
          144  +
          145  +    if( showFiles ){
          146  +      cson_value * fileList = json_get_changed_files(rid);
          147  +      if(fileList){
          148  +        SET("files",fileList);
          149  +      }
          150  +    }
          151  +
          152  +
          153  +#undef SET
          154  +  }
          155  +  free(zParent);
          156  +  db_finalize(&q);
          157  +  return v;
          158  +}
          159  +
          160  +/*
          161  +** Very incomplete/incorrect impl of /json/artifact/TICKET_ID.
          162  +*/
          163  +cson_value * json_artifact_ticket( int rid ){
          164  +  cson_object * pay = NULL;
          165  +  Manifest *pTktChng = NULL;
          166  +  static cson_value * eventTypeLabel = NULL;
          167  +  if(! g.perm.RdTkt ){
          168  +    g.json.resultCode = FSL_JSON_E_DENIED;
          169  +    return NULL;
          170  +  }
          171  +  if(!eventTypeLabel){
          172  +    eventTypeLabel = json_new_string("ticket");
          173  +    json_gc_add("$EVENT_TYPE_LABEL(ticket)", eventTypeLabel);
          174  +  }
          175  +
          176  +  pTktChng = manifest_get(rid, CFTYPE_TICKET);
          177  +  if( pTktChng==0 ){
          178  +    g.json.resultCode = FSL_JSON_E_MANIFEST_READ_FAILED;
          179  +    return NULL;
          180  +  }
          181  +  pay = cson_new_object();
          182  +  cson_object_set(pay, "eventType", eventTypeLabel );
          183  +  cson_object_set(pay, "uuid", json_new_string(pTktChng->zTicketUuid));
          184  +  cson_object_set(pay, "user", json_new_string(pTktChng->zUser));
          185  +  cson_object_set(pay, "timestamp", json_julian_to_timestamp(pTktChng->rDate));
          186  +  manifest_destroy(pTktChng);
          187  +  return cson_object_value(pay);
          188  +}
          189  +
          190  +/*
          191  +** Sub-impl of /json/artifact for checkins.
          192  +*/
          193  +static cson_value * json_artifact_ci( int rid ){
          194  +  if(! g.perm.Read ){
          195  +    g.json.resultCode = FSL_JSON_E_DENIED;
          196  +    return NULL;
          197  +  }else{
          198  +    return json_artifact_for_ci(rid, 1);
          199  +  }
          200  +}
          201  +
          202  +/*
          203  +** Internal mapping of /json/artifact/FOO commands/callbacks.
          204  +*/
          205  +static ArtifactDispatchEntry ArtifactDispatchList[] = {
          206  +{"checkin", json_artifact_ci},
          207  +{"file", json_artifact_file},
          208  +{"tag", NULL},
          209  +{"ticket", json_artifact_ticket},
          210  +{"wiki", json_artifact_wiki},
          211  +/* Final entry MUST have a NULL name. */
          212  +{NULL,NULL}
          213  +};
          214  +
          215  +/*
          216  +** Internal helper which returns true (non-0) if the includeContent
          217  +** (HTTP) or -content|-c flags (CLI) are set. 
          218  +*/ 
          219  +static char json_artifact_include_content_flag(){
          220  +  return json_find_option_bool("includeContent","content","c",0);
          221  +}
          222  +
          223  +cson_value * json_artifact_wiki(int rid){
          224  +  if( ! g.perm.RdWiki ){
          225  +    json_set_err(FSL_JSON_E_DENIED,
          226  +                 "Requires 'j' privileges.");
          227  +    return NULL;
          228  +  }else{
          229  +    return json_get_wiki_page_by_rid(rid, 0);
          230  +  }
          231  +}
          232  +
          233  +cson_value * json_artifact_file(int rid){
          234  +  cson_object * pay = NULL;
          235  +  const char *zMime;
          236  +  Blob content = empty_blob;
          237  +  Stmt q = empty_Stmt;
          238  +  cson_array * checkin_arr = NULL;
          239  +#if 0
          240  +    /*see next #if block below*/
          241  +      cson_string * tagKey = NULL;
          242  +      cson_value * checkinV = NULL;
          243  +      cson_object * checkin = NULL;
          244  +#endif
          245  +
          246  +  if( ! g.perm.Read ){
          247  +    json_set_err(FSL_JSON_E_DENIED,
          248  +                 "Requires 'o' privileges.");
          249  +    return NULL;
          250  +  }
          251  +  
          252  +  pay = cson_new_object();
          253  +
          254  +  content_get(rid, &content);
          255  +  cson_object_set(pay, "contentLength",
          256  +                  json_new_int( blob_size(&content) )
          257  +                  /* achtung: overflow potential on 32-bit builds! */);
          258  +  zMime = mimetype_from_content(&content);
          259  +
          260  +  cson_object_set(pay, "contentType",
          261  +                  json_new_string(zMime ? zMime : "text/plain"));
          262  +  if( json_artifact_include_content_flag() && !zMime ){
          263  +      cson_object_set(pay, "content",
          264  +                      cson_value_new_string(blob_str(&content),
          265  +                                            (unsigned int)blob_size(&content)));
          266  +  }
          267  +  blob_reset(&content);
          268  +
          269  +  db_prepare(&q,
          270  +      "SELECT filename.name AS name, "
          271  +      "       cast(strftime('%%s',event.mtime) as int) AS mtime,"
          272  +      "       coalesce(event.ecomment,event.comment) as comment,"
          273  +      "       coalesce(event.euser,event.user) as user,"
          274  +      "       b.uuid as uuid, mlink.mperm as mperm,"/* WTF is mperm?*/
          275  +      "       coalesce((SELECT value FROM tagxref"
          276  +                      "  WHERE tagid=%d AND tagtype>0 AND rid=mlink.mid),'trunk') as branch"
          277  +      "  FROM mlink, filename, event, blob a, blob b"
          278  +      " WHERE filename.fnid=mlink.fnid"
          279  +      "   AND event.objid=mlink.mid"
          280  +      "   AND a.rid=mlink.fid"
          281  +      "   AND b.rid=mlink.mid"
          282  +      "   AND mlink.fid=%d"
          283  +      "   ORDER BY filename.name, event.mtime",
          284  +      TAG_BRANCH, rid
          285  +    );
          286  +  checkin_arr = cson_new_array(); 
          287  +  cson_object_set(pay, "checkins", cson_array_value(checkin_arr));
          288  +#if 0
          289  +  /* Damn: json_tags_for_rid() only works for commits.
          290  +
          291  +  FIXME: extend json_tags_for_rid() to accept file rids and then
          292  +  implement this loop to add the tags to each object.
          293  +  */
          294  +
          295  +  while( SQLITE_ROW == db_step(&q) ){
          296  +    checkinV = cson_sqlite3_row_to_object( q.pStmt );
          297  +    if(!checkinV){
          298  +      continue;
          299  +    }
          300  +    if(!tagKey) {
          301  +      tagKey = cson_new_string("tags",4);
          302  +      json_gc_add("artifact/file/tags", cson_string_value(tagKey))
          303  +        /*avoids a potential lifetime issue*/;
          304  +    }
          305  +    checkin = cson_value_get_object(checkinV);
          306  +    cson_object_set_s(checkin, tagKey, json_tags_for_rid(rid,0));
          307  +    cson_array_append( checkin_arr, checkinV );
          308  +  }   
          309  +#else
          310  +  json_stmt_to_array_of_obj( &q, checkin_arr );
          311  +#endif
          312  +  db_finalize(&q);
          313  +  return cson_object_value(pay);
          314  +}
          315  +
          316  +/*
          317  +** Impl of /json/artifact. This basically just determines the type of
          318  +** an artifact and forwards the real work to another function.
          319  +*/
          320  +cson_value * json_page_artifact(){
          321  +  cson_object * pay = NULL;
          322  +  char const * zName = NULL;
          323  +  char const * zType = NULL;
          324  +  char const * zUuid = NULL;
          325  +  cson_value * entry = NULL;
          326  +  Blob uuid = empty_blob;
          327  +  int rc;
          328  +  int rid = 0;
          329  +  ArtifactDispatchEntry const * dispatcher = &ArtifactDispatchList[0];
          330  +  zName = json_find_option_cstr2("uuid", NULL, NULL, 2);
          331  +  if(!zName || !*zName) {
          332  +    json_set_err(FSL_JSON_E_MISSING_ARGS,
          333  +                 "Missing 'uuid' argument.");
          334  +    return NULL;
          335  +  }
          336  +
          337  +  if( validate16(zName, strlen(zName)) ){
          338  +    if( db_exists("SELECT 1 FROM ticket WHERE tkt_uuid GLOB '%q*'", zName) ){
          339  +      zType = "ticket";
          340  +      goto handle_entry;
          341  +    }
          342  +    if( db_exists("SELECT 1 FROM tag WHERE tagname GLOB 'event-%q*'", zName) ){
          343  +      zType = "tag";
          344  +      goto handle_entry;
          345  +    }
          346  +  }
          347  +  blob_set(&uuid,zName);
          348  +  rc = name_to_uuid(&uuid,-1,"*");
          349  +  if(1==rc){
          350  +    g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND;
          351  +    goto error;
          352  +  }else if(2==rc){
          353  +    g.json.resultCode = FSL_JSON_E_AMBIGUOUS_UUID;
          354  +    goto error;
          355  +  }
          356  +  zUuid = blob_str(&uuid);
          357  +  rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zUuid);
          358  +  if(0==rid){
          359  +    g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND;
          360  +    goto error;
          361  +  }
          362  +
          363  +  if( db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid)
          364  +      || db_exists("SELECT 1 FROM plink WHERE cid=%d", rid)
          365  +      || db_exists("SELECT 1 FROM plink WHERE pid=%d", rid)){
          366  +    zType = "checkin";
          367  +    goto handle_entry;
          368  +  }else if( db_exists("SELECT 1 FROM tagxref JOIN tag USING(tagid)"
          369  +                      " WHERE rid=%d AND tagname LIKE 'wiki-%%'", rid) ){
          370  +    zType = "wiki";
          371  +    goto handle_entry;
          372  +  }else if( db_exists("SELECT 1 FROM tagxref JOIN tag USING(tagid)"
          373  +                      " WHERE rid=%d AND tagname LIKE 'tkt-%%'", rid) ){
          374  +    zType = "ticket";
          375  +    goto handle_entry;
          376  +  }else if ( db_exists("SELECT 1 FROM mlink WHERE fid = %d", rid) ){
          377  +    zType = "file";
          378  +    goto handle_entry;
          379  +  }else{
          380  +    g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND;
          381  +    goto error;
          382  +  }
          383  +
          384  +  error:
          385  +  assert( 0 != g.json.resultCode );
          386  +  goto veryend;
          387  +
          388  +  handle_entry:
          389  +  assert( (NULL != zType) && "Internal dispatching error." );
          390  +  for( ; dispatcher->name; ++dispatcher ){
          391  +    if(0!=strcmp(dispatcher->name, zType)){
          392  +      continue;
          393  +    }else{
          394  +      entry = (*dispatcher->func)(rid);
          395  +      break;
          396  +    }
          397  +  }
          398  +  if(!g.json.resultCode){
          399  +    assert( NULL != entry );
          400  +    assert( NULL != zType );
          401  +    pay = cson_new_object();
          402  +    cson_object_set( pay, "type", json_new_string(zType) );
          403  +    /*cson_object_set( pay, "uuid", json_new_string(zUuid) );*/
          404  +    cson_object_set( pay, "name", json_new_string(zName ? zName : zUuid) );
          405  +    cson_object_set( pay, "rid", cson_value_new_integer(rid) );
          406  +    if(entry){
          407  +      cson_object_set(pay, "artifact", entry);
          408  +    }
          409  +  }
          410  +  veryend:
          411  +  blob_reset(&uuid);
          412  +  return cson_object_value(pay);
          413  +}
          414  +
          415  +#endif /* FOSSIL_ENABLE_JSON */

Added src/json_branch.c.

            1  +#ifdef FOSSIL_ENABLE_JSON
            2  +/*
            3  +** Copyright (c) 2011 D. Richard Hipp
            4  +**
            5  +** This program is free software; you can redistribute it and/or
            6  +** modify it under the terms of the Simplified BSD License (also
            7  +** known as the "2-Clause License" or "FreeBSD License".)
            8  +**
            9  +** This program is distributed in the hope that it will be useful,
           10  +** but without any warranty; without even the implied warranty of
           11  +** merchantability or fitness for a particular purpose.
           12  +**
           13  +** Author contact information:
           14  +**   drh@hwaci.com
           15  +**   http://www.hwaci.com/drh/
           16  +**
           17  +*/
           18  +#include "VERSION.h"
           19  +#include "config.h"
           20  +#include "json_branch.h"
           21  +
           22  +#if INTERFACE
           23  +#include "json_detail.h"
           24  +#endif
           25  +
           26  +
           27  +static cson_value * json_branch_list();
           28  +static cson_value * json_branch_create();
           29  +/*
           30  +** Mapping of /json/branch/XXX commands/paths to callbacks.
           31  +*/
           32  +static const JsonPageDef JsonPageDefs_Branch[] = {
           33  +{"create", json_branch_create, 0},
           34  +{"list", json_branch_list, 0},
           35  +{"new", json_branch_create, -1/* for compat with non-JSON branch command.*/},
           36  +/* Last entry MUST have a NULL name. */
           37  +{NULL,NULL,0}
           38  +};
           39  +
           40  +/*
           41  +** Implements the /json/branch family of pages/commands. Far from
           42  +** complete.
           43  +**
           44  +*/
           45  +cson_value * json_page_branch(){
           46  +  return json_page_dispatch_helper(&JsonPageDefs_Branch[0]);
           47  +}
           48  +
           49  +/*
           50  +** Impl for /json/branch/list
           51  +**
           52  +**
           53  +** CLI mode options:
           54  +**
           55  +**  --range X | -r X, where X is one of (open,closed,all)
           56  +**    (only the first letter is significant, default=open).
           57  +**  -a (same as --range a)
           58  +**  -c (same as --range c)
           59  +**
           60  +** HTTP mode options:
           61  +**
           62  +** "range" GET/POST.payload parameter. FIXME: currently we also use
           63  +** POST, but really want to restrict this to POST.payload.
           64  +*/
           65  +static cson_value * json_branch_list(){
           66  +  cson_value * payV;
           67  +  cson_object * pay;
           68  +  cson_value * listV;
           69  +  cson_array * list;
           70  +  char const * range = NULL;
           71  +  int which = 0;
           72  +  char * sawConversionError = NULL;
           73  +  Stmt q;
           74  +  if( !g.perm.Read ){
           75  +    json_set_err(FSL_JSON_E_DENIED,
           76  +                 "Requires 'o' permissions.");
           77  +    return NULL;
           78  +  }
           79  +  payV = cson_value_new_object();
           80  +  pay = cson_value_get_object(payV);
           81  +  listV = cson_value_new_array();
           82  +  list = cson_value_get_array(listV);
           83  +  if(fossil_has_json()){
           84  +      range = json_getenv_cstr("range");
           85  +  }
           86  +
           87  +  range = json_find_option_cstr("range",NULL,"r");
           88  +  if((!range||!*range) && !g.isHTTP){
           89  +    range = find_option("all","a",0);
           90  +    if(range && *range){
           91  +      range = "a";
           92  +    }else{
           93  +      range = find_option("closed","c",0);
           94  +      if(range&&*range){
           95  +        range = "c";
           96  +      }
           97  +    }
           98  +  }
           99  +
          100  +  if(!range || !*range){
          101  +    range = "o";
          102  +  }
          103  +  /* Normalize range values... */
          104  +  switch(*range){
          105  +    case 'c':
          106  +      range = "closed";
          107  +      which = -1;
          108  +      break;
          109  +    case 'a':
          110  +      range = "all";
          111  +      which = 1;
          112  +      break;
          113  +    default:
          114  +      range = "open";
          115  +      which = 0;
          116  +      break;
          117  +  };
          118  +  cson_object_set(pay,"range",json_new_string(range));
          119  +
          120  +  if( g.localOpen ){ /* add "current" property (branch name). */
          121  +    int vid = db_lget_int("checkout", 0);
          122  +    char const * zCurrent = vid
          123  +      ? db_text(0, "SELECT value FROM tagxref"
          124  +                " WHERE rid=%d AND tagid=%d",
          125  +                vid, TAG_BRANCH)
          126  +      : 0;
          127  +    if(zCurrent){
          128  +      cson_object_set(pay,"current",json_new_string(zCurrent));
          129  +    }
          130  +  }
          131  +
          132  +  
          133  +  branch_prepare_list_query(&q, which);
          134  +  cson_object_set(pay,"branches",listV);
          135  +  while((SQLITE_ROW==db_step(&q))){
          136  +    cson_value * v = cson_sqlite3_column_to_value(q.pStmt,0);
          137  +    if(v){
          138  +      cson_array_append(list,v);
          139  +    }else if(!sawConversionError){
          140  +      sawConversionError = mprintf("Column-to-json failed @ %s:%d",
          141  +                                   __FILE__,__LINE__);
          142  +    }
          143  +  }
          144  +  if( sawConversionError ){
          145  +    json_warn(FSL_JSON_W_COL_TO_JSON_FAILED,sawConversionError);
          146  +    free(sawConversionError);
          147  +  }
          148  +  return payV;
          149  +}
          150  +
          151  +/*
          152  +** Parameters for the create-branch operation.
          153  +*/
          154  +typedef struct BranchCreateOptions{
          155  +  char const * zName;
          156  +  char const * zBasis;
          157  +  char const * zColor;
          158  +  char isPrivate;
          159  +  /**
          160  +     Might be set to an error string by
          161  +     json_branch_new().
          162  +   */
          163  +  char const * rcErrMsg;
          164  +} BranchCreateOptions;
          165  +
          166  +/*
          167  +** Tries to create a new branch based on the options set in zOpt. If
          168  +** an error is encountered, zOpt->rcErrMsg _might_ be set to a
          169  +** descriptive string and one of the FossilJsonCodes values will be
          170  +** returned. Or fossil_fatal() (or similar) might be called, exiting
          171  +** the app.
          172  +**
          173  +** On success 0 is returned and if zNewRid is not NULL then the rid of
          174  +** the new branch is assigned to it.
          175  +**
          176  +** If zOpt->isPrivate is 0 but the parent branch is private,
          177  +** zOpt->isPrivate will be set to a non-zero value and the new branch
          178  +** will be private.
          179  +*/
          180  +static int json_branch_new(BranchCreateOptions * zOpt,
          181  +                           int *zNewRid){
          182  +  /* Mostly copied from branch.c:branch_new(), but refactored a small
          183  +     bit to not produce output or interact with the user. The
          184  +     down-side to that is that we dropped the gpg-signing. It was
          185  +     either that or abort the creation if we couldn't sign. We can't
          186  +     sign over HTTP mode, anyway.
          187  +  */
          188  +  char const * zBranch = zOpt->zName;
          189  +  char const * zBasis = zOpt->zBasis;
          190  +  char const * zColor = zOpt->zColor;
          191  +  int rootid;            /* RID of the root check-in - what we branch off of */
          192  +  int brid;              /* RID of the branch check-in */
          193  +  int i;                 /* Loop counter */
          194  +  char *zUuid;           /* Artifact ID of origin */
          195  +  Stmt q;                /* Generic query */
          196  +  char *zDate;           /* Date that branch was created */
          197  +  char *zComment;        /* Check-in comment for the new branch */
          198  +  Blob branch;           /* manifest for the new branch */
          199  +  Manifest *pParent;     /* Parsed parent manifest */
          200  +  Blob mcksum;           /* Self-checksum on the manifest */
          201  +
          202  +  /* fossil branch new name */
          203  +  if( zBranch==0 || zBranch[0]==0 ){
          204  +    zOpt->rcErrMsg = "Branch name may not be null/empty.";
          205  +    return FSL_JSON_E_INVALID_ARGS;
          206  +  }
          207  +  if( db_exists(
          208  +        "SELECT 1 FROM tagxref"
          209  +        " WHERE tagtype>0"
          210  +        "   AND tagid=(SELECT tagid FROM tag WHERE tagname='sym-%q')",
          211  +        zBranch)!=0 ){
          212  +    zOpt->rcErrMsg = "Branch already exists.";
          213  +    return FSL_JSON_E_RESOURCE_ALREADY_EXISTS;
          214  +  }
          215  +
          216  +  db_begin_transaction();
          217  +  rootid = name_to_typed_rid(zBasis, "ci");
          218  +  if( rootid==0 ){
          219  +    zOpt->rcErrMsg = "Basis branch not found.";
          220  +    return FSL_JSON_E_RESOURCE_NOT_FOUND;
          221  +  }
          222  +
          223  +  pParent = manifest_get(rootid, CFTYPE_MANIFEST);
          224  +  if( pParent==0 ){
          225  +    zOpt->rcErrMsg = "Could not read parent manifest.";
          226  +    return FSL_JSON_E_UNKNOWN;
          227  +  }
          228  +
          229  +  /* Create a manifest for the new branch */
          230  +  blob_zero(&branch);
          231  +  if( pParent->zBaseline ){
          232  +    blob_appendf(&branch, "B %s\n", pParent->zBaseline);
          233  +  }
          234  +  zComment = mprintf("Create new branch named \"%s\" "
          235  +                     "from \"%s\".", zBranch, zBasis);
          236  +  blob_appendf(&branch, "C %F\n", zComment);
          237  +  free(zComment);
          238  +  zDate = date_in_standard_format("now");
          239  +  blob_appendf(&branch, "D %s\n", zDate);
          240  +  free(zDate);
          241  +
          242  +  /* Copy all of the content from the parent into the branch */
          243  +  for(i=0; i<pParent->nFile; ++i){
          244  +    blob_appendf(&branch, "F %F", pParent->aFile[i].zName);
          245  +    if( pParent->aFile[i].zUuid ){
          246  +      blob_appendf(&branch, " %s", pParent->aFile[i].zUuid);
          247  +      if( pParent->aFile[i].zPerm && pParent->aFile[i].zPerm[0] ){
          248  +        blob_appendf(&branch, " %s", pParent->aFile[i].zPerm);
          249  +      }
          250  +    }
          251  +    blob_append(&branch, "\n", 1);
          252  +  }
          253  +  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rootid);
          254  +  blob_appendf(&branch, "P %s\n", zUuid);
          255  +  free(zUuid);
          256  +  if( pParent->zRepoCksum ){
          257  +    blob_appendf(&branch, "R %s\n", pParent->zRepoCksum);
          258  +  }
          259  +  manifest_destroy(pParent);
          260  +
          261  +  /* Add the symbolic branch name and the "branch" tag to identify
          262  +  ** this as a new branch */
          263  +  if( content_is_private(rootid) ) zOpt->isPrivate = 1;
          264  +  if( zOpt->isPrivate && zColor==0 ) zColor = "#fec084";
          265  +  if( zColor!=0 ){
          266  +    blob_appendf(&branch, "T *bgcolor * %F\n", zColor);
          267  +  }
          268  +  blob_appendf(&branch, "T *branch * %F\n", zBranch);
          269  +  blob_appendf(&branch, "T *sym-%F *\n", zBranch);
          270  +  if( zOpt->isPrivate ){
          271  +    blob_appendf(&branch, "T +private *\n");
          272  +  }
          273  +
          274  +  /* Cancel all other symbolic tags */
          275  +  db_prepare(&q,
          276  +      "SELECT tagname FROM tagxref, tag"
          277  +      " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid"
          278  +      "   AND tagtype>0 AND tagname GLOB 'sym-*'"
          279  +      " ORDER BY tagname",
          280  +      rootid);
          281  +  while( db_step(&q)==SQLITE_ROW ){
          282  +    const char *zTag = db_column_text(&q, 0);
          283  +    blob_appendf(&branch, "T -%F *\n", zTag);
          284  +  }
          285  +  db_finalize(&q);
          286  +  
          287  +  blob_appendf(&branch, "U %F\n", g.zLogin);
          288  +  md5sum_blob(&branch, &mcksum);
          289  +  blob_appendf(&branch, "Z %b\n", &mcksum);
          290  +
          291  +  brid = content_put(&branch);
          292  +  if( brid==0 ){
          293  +    fossil_panic("Problem committing manifest: %s", g.zErrMsg);
          294  +  }
          295  +  db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", brid);
          296  +  if( manifest_crosslink(brid, &branch)==0 ){
          297  +    fossil_panic("unable to install new manifest");
          298  +  }
          299  +  assert( blob_is_reset(&branch) );
          300  +  content_deltify(rootid, brid, 0);
          301  +  if( zNewRid ){
          302  +    *zNewRid = brid;
          303  +  }
          304  +
          305  +  /* Commit */
          306  +  db_end_transaction(0);
          307  +  
          308  +#if 0 /* Do an autosync push, if requested */
          309  +  /* arugable for JSON mode? */
          310  +  if( !g.isHTTP && !isPrivate ) autosync(AUTOSYNC_PUSH);
          311  +#endif
          312  +  return 0;
          313  +}
          314  +
          315  +
          316  +/*
          317  +** Impl of /json/branch/create.
          318  +*/
          319  +static cson_value * json_branch_create(){
          320  +  cson_value * payV = NULL;
          321  +  cson_object * pay = NULL;
          322  +  int rc = 0;
          323  +  BranchCreateOptions opt;
          324  +  char * zUuid = NULL;
          325  +  int rid = 0;
          326  +  if( !g.perm.Write ){
          327  +    json_set_err(FSL_JSON_E_DENIED,
          328  +                 "Requires 'i' permissions.");
          329  +    return NULL;
          330  +  }
          331  +  memset(&opt,0,sizeof(BranchCreateOptions));
          332  +  if(fossil_has_json()){
          333  +    opt.zName = json_getenv_cstr("name");
          334  +  }
          335  +
          336  +  if(!opt.zName){
          337  +    opt.zName = json_command_arg(g.json.dispatchDepth+1);
          338  +  }
          339  +
          340  +  if(!opt.zName){
          341  +    json_set_err(FSL_JSON_E_MISSING_ARGS, "'name' parameter was not specified." );
          342  +    return NULL;
          343  +  }
          344  +
          345  +  opt.zColor = json_find_option_cstr("bgColor","bgcolor",NULL);
          346  +  opt.zBasis = json_find_option_cstr("basis",NULL,NULL);
          347  +  if(!opt.zBasis && !g.isHTTP){
          348  +    opt.zBasis = json_command_arg(g.json.dispatchDepth+2);
          349  +  }
          350  +  if(!opt.zBasis){
          351  +    opt.zBasis = "trunk";
          352  +  }
          353  +  opt.isPrivate = json_find_option_bool("private",NULL,NULL,-1);
          354  +  if(-1==opt.isPrivate){
          355  +    if(!g.isHTTP){
          356  +      opt.isPrivate = (NULL != find_option("private","",0));
          357  +    }else{
          358  +      opt.isPrivate = 0;
          359  +    }
          360  +  }
          361  +  
          362  +  rc = json_branch_new( &opt, &rid );
          363  +  if(rc){
          364  +    json_set_err(rc, opt.rcErrMsg);
          365  +    goto error;
          366  +  }
          367  +  assert(0 != rid);
          368  +  payV = cson_value_new_object();
          369  +  pay = cson_value_get_object(payV);
          370  +
          371  +  cson_object_set(pay,"name",json_new_string(opt.zName));
          372  +  cson_object_set(pay,"basis",json_new_string(opt.zBasis));
          373  +  cson_object_set(pay,"rid",json_new_int(rid));
          374  +  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
          375  +  cson_object_set(pay,"uuid", json_new_string(zUuid));
          376  +  cson_object_set(pay, "isPrivate", cson_value_new_bool(opt.isPrivate));
          377  +  free(zUuid);
          378  +  if(opt.zColor){
          379  +    cson_object_set(pay,"bgColor",json_new_string(opt.zColor));
          380  +  }
          381  +
          382  +  goto ok;
          383  +  error:
          384  +  assert( 0 != g.json.resultCode );
          385  +  cson_value_free(payV);
          386  +  payV = NULL;
          387  +  ok:
          388  +  return payV;
          389  +}
          390  +
          391  +#endif /* FOSSIL_ENABLE_JSON */

Added src/json_detail.h.

            1  +#ifdef FOSSIL_ENABLE_JSON
            2  +#if !defined(FOSSIL_JSON_DETAIL_H_INCLUDED)
            3  +#define FOSSIL_JSON_DETAIL_H_INCLUDED
            4  +/*
            5  +** Copyright (c) 2011 D. Richard Hipp
            6  +**
            7  +** This program is free software; you can redistribute it and/or
            8  +** modify it under the terms of the Simplified BSD License (also
            9  +** known as the "2-Clause License" or "FreeBSD License".)
           10  +**
           11  +** This program is distributed in the hope that it will be useful,
           12  +** but without any warranty; without even the implied warranty of
           13  +** merchantability or fitness for a particular purpose.
           14  +**
           15  +** Author contact information:
           16  +**   drh@hwaci.com
           17  +**   http://www.hwaci.com/drh/
           18  +**
           19  +*/
           20  +
           21  +#include "cson_amalgamation.h"
           22  +/*
           23  +** Impl details for the JSON API which need to be shared
           24  +** across multiple C files.
           25  +*/
           26  +
           27  +/*
           28  +** The "official" list of Fossil/JSON error codes.  Their values might
           29  +** very well change during initial development but after their first
           30  +** public release they must stay stable.
           31  +**
           32  +** Values must be in the range 1000..9999 for error codes and 1..999
           33  +** for warning codes.
           34  +**
           35  +** Numbers evenly dividable by 100 are "categories", and error codes
           36  +** for a given category have their high bits set to the category
           37  +** value.
           38  +**
           39  +*/
           40  +enum FossilJsonCodes {
           41  +FSL_JSON_W_START = 0,
           42  +FSL_JSON_W_UNKNOWN /*+1*/,
           43  +FSL_JSON_W_ROW_TO_JSON_FAILED /*+2*/,
           44  +FSL_JSON_W_COL_TO_JSON_FAILED /*+3*/,
           45  +FSL_JSON_W_STRING_TO_ARRAY_FAILED /*+4*/,
           46  +FSL_JSON_W_TAG_NOT_FOUND /*+5*/,
           47  +
           48  +FSL_JSON_W_END = 1000,
           49  +FSL_JSON_E_GENERIC = 1000,
           50  +FSL_JSON_E_GENERIC_SUB1 = FSL_JSON_E_GENERIC + 100,
           51  +FSL_JSON_E_INVALID_REQUEST /*+1*/,
           52  +FSL_JSON_E_UNKNOWN_COMMAND /*+2*/,
           53  +FSL_JSON_E_UNKNOWN /*+3*/,
           54  +/*REUSE: +4*/
           55  +FSL_JSON_E_TIMEOUT /*+5*/,
           56  +FSL_JSON_E_ASSERT /*+6*/,
           57  +FSL_JSON_E_ALLOC /*+7*/,
           58  +FSL_JSON_E_NYI /*+8*/,
           59  +FSL_JSON_E_PANIC /*+9*/,
           60  +FSL_JSON_E_MANIFEST_READ_FAILED /*+10*/,
           61  +FSL_JSON_E_FILE_OPEN_FAILED /*+11*/,
           62  +
           63  +FSL_JSON_E_AUTH = 2000,
           64  +FSL_JSON_E_MISSING_AUTH /*+1*/,
           65  +FSL_JSON_E_DENIED /*+2*/,
           66  +FSL_JSON_E_WRONG_MODE /*+3*/,
           67  +
           68  +FSL_JSON_E_LOGIN_FAILED = FSL_JSON_E_AUTH +100,
           69  +FSL_JSON_E_LOGIN_FAILED_NOSEED /*+1*/,
           70  +FSL_JSON_E_LOGIN_FAILED_NONAME /*+2*/,
           71  +FSL_JSON_E_LOGIN_FAILED_NOPW /*+3*/,
           72  +FSL_JSON_E_LOGIN_FAILED_NOTFOUND /*+4*/,
           73  +
           74  +FSL_JSON_E_USAGE = 3000,
           75  +FSL_JSON_E_INVALID_ARGS /*+1*/,
           76  +FSL_JSON_E_MISSING_ARGS /*+2*/,
           77  +FSL_JSON_E_AMBIGUOUS_UUID /*+3*/,
           78  +FSL_JSON_E_UNRESOLVED_UUID /*+4*/,
           79  +FSL_JSON_E_RESOURCE_ALREADY_EXISTS /*+5*/,
           80  +FSL_JSON_E_RESOURCE_NOT_FOUND /*+6*/,
           81  +
           82  +FSL_JSON_E_DB = 4000,
           83  +FSL_JSON_E_STMT_PREP /*+1*/,
           84  +FSL_JSON_E_STMT_BIND /*+2*/,
           85  +FSL_JSON_E_STMT_EXEC /*+3*/,
           86  +FSL_JSON_E_DB_LOCKED /*+4*/,
           87  +
           88  +FSL_JSON_E_DB_NEEDS_REBUILD = FSL_JSON_E_DB + 101,
           89  +FSL_JSON_E_DB_NOT_FOUND = FSL_JSON_E_DB + 102,
           90  +FSL_JSON_E_DB_NOT_VALID = FSL_JSON_E_DB + 103
           91  +
           92  +};
           93  +
           94  +
           95  +/*
           96  +** Signature for JSON page/command callbacks. Each callback is
           97  +** responsible for handling one JSON request/command and/or
           98  +** dispatching to sub-commands.
           99  +**
          100  +** By the time the callback is called, json_page_top() (HTTP mode) or
          101  +** json_cmd_top() (CLI mode) will have set up the JSON-related
          102  +** environment. Implementations may generate a "result payload" of any
          103  +** JSON type by returning its value from this function (ownership is
          104  +** tranferred to the caller). On error they should set
          105  +** g.json.resultCode to one of the FossilJsonCodes values and return
          106  +** either their payload object or NULL. Note that NULL is a legal
          107  +** success value - it simply means the response will contain no
          108  +** payload. If g.json.resultCode is non-zero when this function
          109  +** returns then the top-level dispatcher will destroy any payload
          110  +** returned by this function and will output a JSON error response
          111  +** instead.
          112  +**
          113  +** All of the setup/response code is handled by the top dispatcher
          114  +** functions and the callbacks concern themselves only with:
          115  +**
          116  +** a) Permissions checking (inspecting g.perm).
          117  +** b) generating a response payload (if applicable)
          118  +** c) Setting g.json's error state (if applicable). See json_set_err().
          119  +**
          120  +** It is imperitive that NO callback functions EVER output ANYTHING to
          121  +** stdout, as that will effectively corrupt any JSON output, and
          122  +** almost certainly will corrupt any HTTP response headers. Output
          123  +** sent to stderr ends up in my apache log, so that might be useful
          124  +** for debuggering in some cases, but no such code should be left
          125  +** enabled for non-debuggering builds.
          126  +*/
          127  +typedef cson_value * (*fossil_json_f)();
          128  +
          129  +/*
          130  +** Holds name-to-function mappings for JSON page/command dispatching.
          131  +**
          132  +** Internally we model page dispatching lists as arrays of these
          133  +** objects, where the final entry in the array has a NULL name value
          134  +** to act as the end-of-list sentinel.
          135  +**
          136  +*/
          137  +typedef struct JsonPageDef{
          138  +  /*
          139  +  ** The commmand/page's name (path, not including leading /json/).
          140  +  **
          141  +  ** Reminder to self: we cannot use sub-paths with commands this way
          142  +  ** without additional string-splitting downstream. e.g. foo/bar.
          143  +  ** Alternately, we can create different JsonPageDef arrays for each
          144  +  ** subset.
          145  +  */
          146  +  char const * name;
          147  +  /*
          148  +  ** Returns a payload object for the response.  If it returns a
          149  +  ** non-NULL value, the caller owns it.  To trigger an error this
          150  +  ** function should set g.json.resultCode to a value from the
          151  +  ** FossilJsonCodes enum. If it sets an error value and returns
          152  +  ** a payload, the payload will be destroyed (not sent with the
          153  +  ** response).
          154  +  */
          155  +  fossil_json_f func;
          156  +  /*
          157  +  ** Which mode(s) of execution does func() support:
          158  +  **
          159  +  ** <0 = CLI only, >0 = HTTP only, 0==both
          160  +  */
          161  +  char runMode;
          162  +} JsonPageDef;
          163  +
          164  +/*
          165  +** Holds common keys used for various JSON API properties.
          166  +*/
          167  +typedef struct FossilJsonKeys_{
          168  +  /** maintainers: please keep alpha sorted (case-insensitive) */
          169  +  char const * anonymousSeed;
          170  +  char const * authToken;
          171  +  char const * commandPath;
          172  +  char const * mtime;
          173  +  char const * payload;
          174  +  char const * requestId;
          175  +  char const * resultCode;
          176  +  char const * resultText;
          177  +  char const * timestamp;
          178  +} FossilJsonKeys_;
          179  +const FossilJsonKeys_ FossilJsonKeys;
          180  +
          181  +/*
          182  +** A page/command dispatch helper for fossil_json_f() implementations.
          183  +** pages must be an array of JsonPageDef commands which we can
          184  +** dispatch. The final item in the array MUST have a NULL name
          185  +** element.
          186  +**
          187  +** This function takes the command specified in
          188  +** json_comand_arg(1+g.json.dispatchDepth) and searches pages for a
          189  +** matching name. If found then that page's func() is called to fetch
          190  +** the payload, which is returned to the caller.
          191  +**
          192  +** On error, g.json.resultCode is set to one of the FossilJsonCodes
          193  +** values and NULL is returned. If non-NULL is returned, ownership is
          194  +** transfered to the caller (but the g.json error state might still be
          195  +** set in that case, so the caller must check that or pass it on up
          196  +** the dispatch chain).
          197  +*/
          198  +cson_value * json_page_dispatch_helper(JsonPageDef const * pages);
          199  +
          200  +/*
          201  +** Implements the /json/wiki family of pages/commands.
          202  +**
          203  +*/
          204  +cson_value * json_page_wiki();
          205  +
          206  +/*
          207  +** Implements /json/timeline/wiki and /json/wiki/timeline.
          208  +*/
          209  +cson_value * json_timeline_wiki();
          210  +
          211  +/*
          212  +** Implements /json/timeline family of functions.
          213  +*/
          214  +cson_value * json_page_timeline();
          215  +
          216  +/*
          217  +** Convenience wrapper around cson_value_new_string().
          218  +** Returns NULL if str is NULL or on allocation error.
          219  +*/
          220  +cson_value * json_new_string( char const * str );
          221  +
          222  +/*
          223  +** Similar to json_new_string(), but takes a printf()-style format
          224  +** specifiers. Supports the printf extensions supported by fossil's
          225  +** mprintf().  Returns NULL if str is NULL or on allocation error.
          226  +**
          227  +** Maintenance note: json_new_string() is NOT variadic because by the
          228  +** time the variadic form was introduced we already had use cases
          229  +** which segfaulted via json_new_string() because they contain printf
          230  +** markup (e.g. wiki content). Been there, debugged that.
          231  +*/
          232  +cson_value * json_new_string_f( char const * fmt, ... );
          233  +
          234  +/*
          235  +** Returns true if fossil is running in JSON mode and we are either
          236  +** running in HTTP mode OR g.json.post.o is not NULL (meaning POST
          237  +** data was fed in from CLI mode).
          238  +**
          239  +** Specifically, it will return false when any of these apply:
          240  +**
          241  +** a) Not running in JSON mode (via json command or /json path).
          242  +**
          243  +** b) We are running in JSON CLI mode, but no POST data has been fed
          244  +** in.
          245  +**
          246  +** Whether or not we need to take args from CLI or POST data makes a
          247  +** difference in argument/parameter handling in many JSON rountines,
          248  +** and thus this distinction.
          249  +*/
          250  +char fossil_has_json();
          251  +
          252  +
          253  +#endif/*FOSSIL_JSON_DETAIL_H_INCLUDED*/
          254  +#endif /* FOSSIL_ENABLE_JSON */

Added src/json_diff.c.

            1  +#ifdef FOSSIL_ENABLE_JSON
            2  +/*
            3  +** Copyright (c) 2011 D. Richard Hipp
            4  +**
            5  +** This program is free software; you can redistribute it and/or
            6  +** modify it under the terms of the Simplified BSD License (also
            7  +** known as the "2-Clause License" or "FreeBSD License".)
            8  +**
            9  +** This program is distributed in the hope that it will be useful,
           10  +** but without any warranty; without even the implied warranty of
           11  +** merchantability or fitness for a particular purpose.
           12  +**
           13  +** Author contact information:
           14  +**   drh@hwaci.com
           15  +**   http://www.hwaci.com/drh/
           16  +**
           17  +*/
           18  +
           19  +#include "config.h"
           20  +#include "json_diff.h"
           21  +
           22  +#if INTERFACE
           23  +#include "json_detail.h"
           24  +#endif
           25  +
           26  +
           27  +
           28  +/*
           29  +** Generates a diff between two versions (zFrom and zTo), using nContext
           30  +** content lines in the output. On success, returns a new JSON String
           31  +** object. On error it sets g.json's error state and returns NULL.
           32  +**
           33  +** If fSbs is true (non-0) them side-by-side diffs are used.
           34  +*/
           35  +cson_value * json_generate_diff(const char *zFrom, const char *zTo,
           36  +                                int nContext, char fSbs){
           37  +  int fromid;
           38  +  int toid;
           39  +  int outLen;
           40  +  Blob from = empty_blob, to = empty_blob, out = empty_blob;
           41  +  cson_value * rc = NULL;
           42  +  char const * zType = "ci";
           43  +  int flags = (DIFF_CONTEXT_MASK & nContext)
           44  +      | (fSbs ? DIFF_SIDEBYSIDE : 0);
           45  +  fromid = name_to_typed_rid(zFrom, "*");
           46  +  if(fromid<=0){
           47  +      json_set_err(FSL_JSON_E_UNRESOLVED_UUID,
           48  +                   "Could not resolve 'from' ID.");
           49  +      return NULL;
           50  +  }
           51  +  toid = name_to_typed_rid(zTo, "*");
           52  +  if(toid<=0){
           53  +      json_set_err(FSL_JSON_E_UNRESOLVED_UUID,
           54  +                   "Could not resolve 'to' ID.");
           55  +      return NULL;
           56  +  }
           57  +  content_get(fromid, &from);
           58  +  content_get(toid, &to);
           59  +  blob_zero(&out);
           60  +  text_diff(&from, &to, &out, flags);
           61  +  blob_reset(&from);
           62  +  blob_reset(&to);
           63  +  outLen = blob_size(&out);
           64  +  if(outLen>0){
           65  +    rc = cson_value_new_string(blob_buffer(&out), blob_size(&out));
           66  +  }
           67  +  blob_reset(&out);
           68  +  return rc;
           69  +}
           70  +
           71  +/*
           72  +** Implementation of the /json/diff page.
           73  +**
           74  +** Arguments:
           75  +**
           76  +** v1=1st version to diff
           77  +** v2=2nd version to diff
           78  +**
           79  +** Can come from GET, POST.payload, CLI -v1/-v2 or as positional
           80  +** parameters following the command name (in HTTP and CLI modes).
           81  +**
           82  +*/
           83  +cson_value * json_page_diff(){
           84  +  cson_object * pay = NULL;
           85  +  cson_value * v = NULL;
           86  +  char const * zFrom;
           87  +  char const * zTo;
           88  +  int nContext = 0;
           89  +  char doSBS;
           90  +  if(!g.perm.Read){
           91  +    json_set_err(FSL_JSON_E_DENIED,
           92  +                 "Requires 'o' permissions.");
           93  +    return NULL;
           94  +  }
           95  +  zFrom = json_find_option_cstr("v1",NULL,NULL);
           96  +  if(!zFrom){
           97  +    zFrom = json_command_arg(2);
           98  +  }
           99  +  if(!zFrom){
          100  +    json_set_err(FSL_JSON_E_MISSING_ARGS,
          101  +                 "Required 'v1' parameter is missing.");
          102  +    return NULL;
          103  +  }
          104  +  zTo = json_find_option_cstr("v2",NULL,NULL);
          105  +  if(!zTo){
          106  +    zTo = json_command_arg(3);
          107  +  }
          108  +  if(!zTo){
          109  +    json_set_err(FSL_JSON_E_MISSING_ARGS,
          110  +                 "Required 'v2' parameter is missing.");
          111  +    return NULL;
          112  +  }
          113  +  nContext = json_find_option_int("context",NULL,"c",5);
          114  +  doSBS = json_find_option_bool("sbs",NULL,"y",0);
          115  +  v = json_generate_diff(zFrom, zTo, nContext, doSBS);
          116  +  if(!v){
          117  +    if(!g.json.resultCode){
          118  +      json_set_err(FSL_JSON_E_UNKNOWN,
          119  +                   "Generating diff failed for unknown reason.");
          120  +    }
          121  +    return NULL;
          122  +  }
          123  +  pay = cson_new_object();
          124  +  cson_object_set(pay, "from", json_new_string(zFrom));
          125  +  cson_object_set(pay, "to", json_new_string(zTo));
          126  +  cson_object_set(pay, "diff", v);
          127  +  v = 0;
          128  +  
          129  +  return pay ? cson_object_value(pay) : NULL;
          130  +}
          131  +
          132  +#endif /* FOSSIL_ENABLE_JSON */

Added src/json_login.c.

            1  +#ifdef FOSSIL_ENABLE_JSON
            2  +/*
            3  +** Copyright (c) 2011 D. Richard Hipp
            4  +**
            5  +** This program is free software; you can redistribute it and/or
            6  +** modify it under the terms of the Simplified BSD License (also
            7  +** known as the "2-Clause License" or "FreeBSD License".)
            8  +**
            9  +** This program is distributed in the hope that it will be useful,
           10  +** but without any warranty; without even the implied warranty of
           11  +** merchantability or fitness for a particular purpose.
           12  +**
           13  +** Author contact information:
           14  +**   drh@hwaci.com
           15  +**   http://www.hwaci.com/drh/
           16  +**
           17  +*/
           18  +
           19  +#include "config.h"
           20  +#include "json_login.h"
           21  +
           22  +#if INTERFACE
           23  +#include "json_detail.h"
           24  +#endif
           25  +
           26  +
           27  +/*
           28  +** Implementation of the /json/login page.
           29  +**
           30  +*/
           31  +cson_value * json_page_login(){
           32  +  char preciseErrors = /* if true, "complete" JSON error codes are used,
           33  +                          else they are "dumbed down" to a generic login
           34  +                          error code.
           35  +                       */
           36  +#if 1
           37  +    g.json.errorDetailParanoia ? 0 : 1
           38  +#else
           39  +    0
           40  +#endif
           41  +    ;
           42  +  /*
           43  +    FIXME: we want to check the GET/POST args in this order:
           44  +
           45  +    - GET: name, n, password, p
           46  +    - POST: name, password
           47  +
           48  +    but a bug in cgi_parameter() is breaking that, causing PD() to
           49  +    return the last element of the PATH_INFO instead.
           50  +
           51  +    Summary: If we check for P("name") first, then P("n"),
           52  +    then ONLY a GET param of "name" will match ("n"
           53  +    is not recognized). If we reverse the order of the
           54  +    checks then both forms work. Strangely enough, the
           55  +    "p"/"password" check is not affected by this.
           56  +   */
           57  +  char const * name = cson_value_get_cstr(json_payload_property("name"));
           58  +  char const * pw = NULL;
           59  +  char const * anonSeed = NULL;
           60  +  cson_value * payload = NULL;
           61  +  int uid = 0;
           62  +  /* reminder to self:
           63  +     Fossil internally (for the sake of /wiki) interprets
           64  +     paths in the form /foo/bar/baz such that
           65  +     P("name") == "bar/baz". This collides with our
           66  +     name/password checking, and thus we check for the
           67  +     password first.
           68  +  */
           69  +  pw = cson_value_get_cstr(json_payload_property("password"));
           70  +  if( !pw ){
           71  +    pw = PD("p",NULL);
           72  +    if( !pw ){
           73  +      pw = PD("password",NULL);
           74  +    }
           75  +  }
           76  +  if(!pw){
           77  +    g.json.resultCode = preciseErrors
           78  +      ? FSL_JSON_E_LOGIN_FAILED_NOPW
           79  +      : FSL_JSON_E_LOGIN_FAILED;
           80  +    return NULL;
           81  +  }
           82  +
           83  +  if( !name ){
           84  +    name = PD("n",NULL);
           85  +    if( !name ){
           86  +      name = PD("name",NULL);
           87  +      if( !name ){
           88  +        g.json.resultCode = preciseErrors
           89  +          ? FSL_JSON_E_LOGIN_FAILED_NONAME
           90  +          : FSL_JSON_E_LOGIN_FAILED;
           91  +        return NULL;
           92  +      }
           93  +    }
           94  +  }
           95  +
           96  +  if(0 == strcmp("anonymous",name)){
           97  +    /* check captcha/seed values... */
           98  +    enum { SeedBufLen = 100 /* in some JSON tests i once actually got an
           99  +                           80-digit number.
          100  +                        */
          101  +    };
          102  +    static char seedBuffer[SeedBufLen];
          103  +    cson_value const * jseed = json_getenv(FossilJsonKeys.anonymousSeed);
          104  +    seedBuffer[0] = 0;
          105  +    if( !jseed ){
          106  +      jseed = json_payload_property(FossilJsonKeys.anonymousSeed);
          107  +      if( !jseed ){
          108  +        jseed = json_getenv("cs") /* name used by HTML interface */;
          109  +      }
          110  +    }
          111  +    if(jseed){
          112  +      if( cson_value_is_number(jseed) ){
          113  +        sprintf(seedBuffer, "%"CSON_INT_T_PFMT, cson_value_get_integer(jseed));
          114  +        anonSeed = seedBuffer;
          115  +      }else if( cson_value_is_string(jseed) ){
          116  +        anonSeed = cson_string_cstr(cson_value_get_string(jseed));
          117  +      }
          118  +    }
          119  +    if(!anonSeed){
          120  +      g.json.resultCode = preciseErrors
          121  +        ? FSL_JSON_E_LOGIN_FAILED_NOSEED
          122  +        : FSL_JSON_E_LOGIN_FAILED;
          123  +      return NULL;
          124  +    }
          125  +  }
          126  +
          127  +#if 0
          128  +  {
          129  +    /* only for debugging the PD()-incorrect-result problem */
          130  +    cson_object * o = NULL;
          131  +    uid = login_search_uid( name, pw );
          132  +    payload = cson_value_new_object();
          133  +    o = cson_value_get_object(payload);
          134  +    cson_object_set( o, "n", cson_value_new_string(name,strlen(name)));
          135  +    cson_object_set( o, "p", cson_value_new_string(pw,strlen(pw)));
          136  +    return payload;
          137  +  }
          138  +#endif
          139  +  uid = anonSeed
          140  +    ? login_is_valid_anonymous(name, pw, anonSeed)
          141  +    : login_search_uid(name, pw)
          142  +    ;
          143  +  if( !uid ){
          144  +    g.json.resultCode = preciseErrors
          145  +      ? FSL_JSON_E_LOGIN_FAILED_NOTFOUND
          146  +      : FSL_JSON_E_LOGIN_FAILED;
          147  +    return NULL;
          148  +  }else{
          149  +    char * cookie = NULL;
          150  +    cson_object * po;
          151  +    char * cap = NULL;
          152  +    if(anonSeed){
          153  +      login_set_anon_cookie(NULL, &cookie);
          154  +    }else{
          155  +      login_set_user_cookie(name, uid, &cookie);
          156  +    }
          157  +    payload = cson_value_new_object();
          158  +    po = cson_value_get_object(payload);
          159  +    cson_object_set(po, "authToken", json_new_string(cookie));
          160  +    free(cookie);
          161  +    cson_object_set(po, "name", json_new_string(name));
          162  +    cap = db_text(NULL, "SELECT cap FROM user WHERE login=%Q",name);
          163  +    cson_object_set(po, "capabilities", json_new_string(cap));
          164  +    free(cap);        
          165  +    return payload;
          166  +  }
          167  +}
          168  +
          169  +/*
          170  +** Impl of /json/logout.
          171  +**
          172  +*/
          173  +cson_value * json_page_logout(){
          174  +  cson_value const *token = g.json.authToken;
          175  +    /* Remember that json_mode_bootstrap() replaces the login cookie
          176  +       with the JSON auth token if the request contains it. If the
          177  +       reqest is missing the auth token then this will fetch fossil's
          178  +       original cookie. Either way, it's what we want :).
          179  +
          180  +       We require the auth token to avoid someone maliciously
          181  +       trying to log someone else out (not 100% sure if that
          182  +       would be possible, given fossil's hardened cookie, but
          183  +       i'll assume it would be for the time being).
          184  +    */
          185  +    ;
          186  +  if(!token){
          187  +    g.json.resultCode = FSL_JSON_E_MISSING_AUTH;
          188  +  }else{
          189  +    login_clear_login_data();
          190  +    g.json.authToken = NULL /* memory is owned elsewhere.*/;
          191  +    json_setenv(FossilJsonKeys.authToken, NULL);
          192  +  }
          193  +  return json_page_whoami();
          194  +}
          195  +
          196  +/*
          197  +** Implementation of the /json/anonymousPassword page.
          198  +*/
          199  +cson_value * json_page_anon_password(){
          200  +  cson_value * v = cson_value_new_object();
          201  +  cson_object * o = cson_value_get_object(v);
          202  +  unsigned const int seed = captcha_seed();
          203  +  char const * zCaptcha = captcha_decode(seed);
          204  +  cson_object_set(o, "seed",
          205  +                  cson_value_new_integer( (cson_int_t)seed )
          206  +                  );
          207  +  cson_object_set(o, "password",
          208  +                  cson_value_new_string( zCaptcha, strlen(zCaptcha) )
          209  +                  );
          210  +  return v;
          211  +}
          212  +
          213  +
          214  +
          215  +/*
          216  +** Implements the /json/whoami page/command.
          217  +*/
          218  +cson_value * json_page_whoami(){
          219  +  cson_value * payload = NULL;
          220  +  cson_object * obj = NULL;
          221  +  Stmt q;
          222  +  if(!g.json.authToken){
          223  +      /* assume we just logged out. */
          224  +      db_prepare(&q, "SELECT login, cap FROM user WHERE login='nobody'");
          225  +  }
          226  +  else{
          227  +      db_prepare(&q, "SELECT login, cap FROM user WHERE uid=%d",
          228  +                 g.userUid);
          229  +  }
          230  +  if( db_step(&q)==SQLITE_ROW ){
          231  +
          232  +    /* reminder: we don't use g.zLogin because it's 0 for the guest
          233  +       user and the HTML UI appears to currently allow the name to be
          234  +       changed (but doing so would break other code). */
          235  +    char const * str;
          236  +    payload = cson_value_new_object();
          237  +    obj = cson_value_get_object(payload);
          238  +    str = (char const *)sqlite3_column_text(q.pStmt,0);
          239  +    if( str ){
          240  +      cson_object_set( obj, "name",
          241  +                       cson_value_new_string(str,strlen(str)) );
          242  +    }
          243  +    str = (char const *)sqlite3_column_text(q.pStmt,1);
          244  +    if( str ){
          245  +      cson_object_set( obj, "capabilities",
          246  +                       cson_value_new_string(str,strlen(str)) );
          247  +    }
          248  +    if( g.json.authToken ){
          249  +      cson_object_set( obj, "authToken", g.json.authToken );
          250  +    }
          251  +  }else{
          252  +    g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND;
          253  +  }
          254  +  db_finalize(&q);
          255  +  return payload;
          256  +}
          257  +#endif /* FOSSIL_ENABLE_JSON */

Added src/json_query.c.

            1  +#ifdef FOSSIL_ENABLE_JSON
            2  +/*
            3  +** Copyright (c) 2011 D. Richard Hipp
            4  +**
            5  +** This program is free software; you can redistribute it and/or
            6  +** modify it under the terms of the Simplified BSD License (also
            7  +** known as the "2-Clause License" or "FreeBSD License".)
            8  +**
            9  +** This program is distributed in the hope that it will be useful,
           10  +** but without any warranty; without even the implied warranty of
           11  +** merchantability or fitness for a particular purpose.
           12  +**
           13  +** Author contact information:
           14  +**   drh@hwaci.com
           15  +**   http://www.hwaci.com/drh/
           16  +**
           17  +*/
           18  +
           19  +#include "config.h"
           20  +#include "json_query.h"
           21  +
           22  +#if INTERFACE
           23  +#include "json_detail.h"
           24  +#endif
           25  +
           26  +
           27  +/*
           28  +** Implementation of the /json/query page.
           29  +**
           30  +** Requires admin privileges. Intended primarily to assist me in
           31  +** coming up with JSON output structures for pending features.
           32  +**
           33  +** Options/parameters:
           34  +**
           35  +** sql=string - a SELECT statement
           36  +**
           37  +** format=string 'a' means each row is an Array of values, 'o'
           38  +** (default) creates each row as an Object.
           39  +**
           40  +** TODO: in CLI mode (only) use -S FILENAME to read the sql
           41  +** from a file.
           42  +*/
           43  +cson_value * json_page_query(){
           44  +  char const * zSql = NULL;
           45  +  cson_value * payV;
           46  +  char const * zFmt;
           47  +  Stmt q = empty_Stmt;
           48  +  int check;
           49  +  if(!g.perm.Admin && !g.perm.Setup){
           50  +    json_set_err(FSL_JSON_E_DENIED,
           51  +                 "Requires 'a' or 's' privileges.");
           52  +    return NULL;
           53  +  }
           54  +
           55  +  if( cson_value_is_string(g.json.reqPayload.v) ){
           56  +    zSql = cson_string_cstr(cson_value_get_string(g.json.reqPayload.v));
           57  +  }else{
           58  +    zSql = json_find_option_cstr2("sql",NULL,"s",2);
           59  +  }
           60  +
           61  +  if(!zSql || !*zSql){
           62  +    json_set_err(FSL_JSON_E_MISSING_ARGS,
           63  +                 "'sql' (-s) argument is missing.");
           64  +    return NULL;
           65  +  }
           66  +
           67  +  zFmt = json_find_option_cstr2("format",NULL,"f",3);
           68  +  if(!zFmt) zFmt = "o";
           69  +  db_prepare(&q,"%s", zSql);
           70  +  switch(*zFmt){
           71  +    case 'a':
           72  +      check = cson_sqlite3_stmt_to_json(q.pStmt, &payV, 0);
           73  +      break;
           74  +    case 'o':
           75  +    default:
           76  +      check = cson_sqlite3_stmt_to_json(q.pStmt, &payV, 1);
           77  +  };
           78  +  db_finalize(&q);
           79  +  if(0 != check){
           80  +    json_set_err(FSL_JSON_E_UNKNOWN,
           81  +                 "Conversion to JSON failed with cson code #%d (%s).",
           82  +                 check, cson_rc_string(check));
           83  +    assert(NULL==payV);
           84  +  }
           85  +  return payV;
           86  +
           87  +}
           88  +
           89  +#endif /* FOSSIL_ENABLE_JSON */

Added src/json_report.c.

            1  +#ifdef FOSSIL_ENABLE_JSON
            2  +/*
            3  +** Copyright (c) 2011 D. Richard Hipp
            4  +**
            5  +** This program is free software; you can redistribute it and/or
            6  +** modify it under the terms of the Simplified BSD License (also
            7  +** known as the "2-Clause License" or "FreeBSD License".)
            8  +**
            9  +** This program is distributed in the hope that it will be useful,
           10  +** but without any warranty; without even the implied warranty of
           11  +** merchantability or fitness for a particular purpose.
           12  +**
           13  +** Author contact information:
           14  +**   drh@hwaci.com
           15  +**   http://www.hwaci.com/drh/
           16  +**
           17  +*/
           18  +
           19  +#include "config.h"
           20  +#include "json_report.h"
           21  +
           22  +#if INTERFACE
           23  +#include "json_detail.h"
           24  +#endif
           25  +
           26  +
           27  +static cson_value * json_report_create();
           28  +static cson_value * json_report_get();
           29  +static cson_value * json_report_list();
           30  +static cson_value * json_report_run();
           31  +static cson_value * json_report_save();
           32  +
           33  +/*
           34  +** Mapping of /json/report/XXX commands/paths to callbacks.
           35  +*/
           36  +static const JsonPageDef JsonPageDefs_Report[] = {
           37  +{"create", json_report_create, 0},
           38  +{"get", json_report_get, 0},
           39  +{"list", json_report_list, 0},
           40  +{"run", json_report_run, 0},
           41  +{"save", json_report_save, 0},
           42  +/* Last entry MUST have a NULL name. */
           43  +{NULL,NULL,0}
           44  +};
           45  +/*
           46  +** Implementation of the /json/report page.
           47  +**
           48  +**
           49  +*/
           50  +cson_value * json_page_report(){
           51  +  if(!g.perm.RdTkt && !g.perm.NewTkt ){
           52  +    json_set_err(FSL_JSON_E_DENIED,
           53  +                 "Requires 'r' or 'n' permissions.");
           54  +    return NULL;
           55  +  }
           56  +  return json_page_dispatch_helper(&JsonPageDefs_Report[0]);
           57  +}
           58  +
           59  +/*
           60  +** Searches the environment for a "report" parameter
           61  +** (CLI: -report/-r #).
           62  +**
           63  +** If one is not found and argPos is >0 then json_command_arg()
           64  +** is checked.
           65  +** 
           66  +** Returns >0 (the report number) on success .
           67  +*/
           68  +static int json_report_get_number(int argPos){
           69  +  int nReport = json_find_option_int("report",NULL,"r",-1);
           70  +  if( (nReport<=0) && cson_value_is_integer(g.json.reqPayload.v)){
           71  +    nReport = cson_value_get_integer(g.json.reqPayload.v);
           72  +  }
           73  +  if( (nReport <= 0) && (argPos>0) ){
           74  +    char const * arg = json_command_arg(argPos);
           75  +    if(arg && fossil_isdigit(*arg)) {
           76  +      nReport = atoi(arg);
           77  +    }
           78  +  }
           79  +  return nReport;
           80  +}
           81  +
           82  +static cson_value * json_report_create(){
           83  +  return NULL;
           84  +}
           85  +
           86  +static cson_value * json_report_get(){
           87  +  int nReport;
           88  +  Stmt q = empty_Stmt;
           89  +  cson_value * pay = NULL;
           90  +
           91  +  if(!g.perm.TktFmt){
           92  +    json_set_err(FSL_JSON_E_DENIED,
           93  +                 "Requires 't' privileges.");
           94  +    return NULL;
           95  +  }
           96  +  nReport = json_report_get_number(3);
           97  +  if(nReport <=0){
           98  +    json_set_err(FSL_JSON_E_MISSING_ARGS,
           99  +                 "Missing or invalid 'number' (-n) parameter.");
          100  +    return NULL;
          101  +  }
          102  +
          103  +  db_prepare(&q,"SELECT rn AS report,"
          104  +             " owner AS owner,"
          105  +             " title AS title,"
          106  +             " cast(strftime('%%s',mtime) as int) as mtime,"
          107  +             " cols as columns,"
          108  +             " sqlcode as sqlCode"
          109  +             " FROM reportfmt"
          110  +             " WHERE rn=%d",
          111  +             nReport);
          112  +  if( SQLITE_ROW != db_step(&q) ){
          113  +    db_finalize(&q);
          114  +    json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND,
          115  +                 "Report #%d not found.", nReport);
          116  +    return NULL;
          117  +  }
          118  +  pay = cson_sqlite3_row_to_object(q.pStmt);
          119  +  db_finalize(&q);
          120  +  return pay;
          121  +}
          122  +
          123  +/*
          124  +** Impl of /json/report/list.
          125  +*/
          126  +static cson_value * json_report_list(){
          127  +  Blob sql = empty_blob;
          128  +  cson_value * pay = NULL;
          129  +  if(!g.perm.RdTkt){
          130  +    json_set_err(FSL_JSON_E_DENIED,
          131  +                 "Requires 'r' privileges.");
          132  +    return NULL;
          133  +  }
          134  +  blob_append(&sql, "SELECT"
          135  +              " rn AS report,"
          136  +              " title as title,"
          137  +              " owner as owner"
          138  +              " FROM reportfmt"
          139  +              " WHERE 1"
          140  +              " ORDER BY title",
          141  +              -1);
          142  +  pay = json_sql_to_array_of_obj(&sql, NULL, 1);
          143  +  if(!pay){
          144  +    json_set_err(FSL_JSON_E_UNKNOWN,
          145  +                 "Quite unexpected: no ticket reports found.");
          146  +  }
          147  +  return pay;
          148  +}
          149  +
          150  +/*
          151  +** Impl for /json/report/run
          152  +**
          153  +** Options/arguments:
          154  +**
          155  +** report=int (CLI: -report # or -r #) is the report number to run.
          156  +**
          157  +** limit=int (CLI: -limit # or -n #) -n is for compat. with other commands.
          158  +**
          159  +** format=a|o Specifies result format: a=each row is an arry, o=each
          160  +** row is an object.  Default=o.
          161  +*/
          162  +static cson_value * json_report_run(){
          163  +  int nReport;
          164  +  Stmt q = empty_Stmt;
          165  +  cson_object * pay = NULL;
          166  +  cson_array * tktList = NULL;
          167  +  char const * zFmt;
          168  +  char * zTitle = NULL;
          169  +  Blob sql = empty_blob;
          170  +  int limit = 0;
          171  +  cson_value * colNames = NULL;
          172  +  int i;
          173  +
          174  +  if(!g.perm.RdTkt){
          175  +    json_set_err(FSL_JSON_E_DENIED,
          176  +                 "Requires 'r' privileges.");
          177  +    return NULL;
          178  +  }
          179  +  nReport = json_report_get_number(3);
          180  +  if(nReport <=0){
          181  +    json_set_err(FSL_JSON_E_MISSING_ARGS,
          182  +                 "Missing or invalid 'number' (-n) parameter.");
          183  +    goto error;
          184  +  }
          185  +  zFmt = json_find_option_cstr2("format",NULL,"f",3);
          186  +  if(!zFmt) zFmt = "o";
          187  +  db_prepare(&q,
          188  +             "SELECT sqlcode, "
          189  +             " title"
          190  +             " FROM reportfmt"
          191  +             " WHERE rn=%d",
          192  +             nReport);
          193  +  if(SQLITE_ROW != db_step(&q)){
          194  +    json_set_err(FSL_JSON_E_INVALID_ARGS,
          195  +                 "Report number %d not found.",
          196  +                 nReport);
          197  +    db_finalize(&q);
          198  +    goto error;
          199  +  }
          200  +
          201  +  limit = json_find_option_int("limit",NULL,"n",-1);
          202  +
          203  +  
          204  +  /* Copy over report's SQL...*/
          205  +  blob_append(&sql, db_column_text(&q,0), -1);
          206  +  zTitle = mprintf("%s", db_column_text(&q,1));
          207  +  db_finalize(&q);
          208  +  db_prepare(&q, "%s", blob_str(&sql));
          209  +
          210  +  /** Build the response... */
          211  +  pay = cson_new_object();
          212  +
          213  +  cson_object_set(pay, "report", json_new_int(nReport));
          214  +  cson_object_set(pay, "title", json_new_string(zTitle));
          215  +  if(limit>0){
          216  +    cson_object_set(pay, "limit", json_new_int((limit<0) ? 0 : limit));
          217  +  }
          218  +  free(zTitle);
          219  +  zTitle = NULL;
          220  +
          221  +  if(g.perm.TktFmt){
          222  +    cson_object_set(pay, "sqlcode",
          223  +                    cson_value_new_string(blob_str(&sql),
          224  +                                          (unsigned int)blob_size(&sql)));
          225  +  }
          226  +  blob_reset(&sql);
          227  +
          228  +  colNames = cson_sqlite3_column_names(q.pStmt);
          229  +  cson_object_set( pay, "columnNames", colNames);
          230  +  for( i = 0 ; ((limit>0) ?(i < limit) : 1)
          231  +         && (SQLITE_ROW == db_step(&q));
          232  +       ++i){
          233  +    cson_value * row = ('a'==*zFmt)
          234  +      ? cson_sqlite3_row_to_array(q.pStmt)
          235  +      : cson_sqlite3_row_to_object2(q.pStmt,
          236  +                                    cson_value_get_array(colNames));
          237  +    ;
          238  +    if(row && !tktList){
          239  +      tktList = cson_new_array();
          240  +    }
          241  +    cson_array_append(tktList, row);
          242  +  }
          243  +  db_finalize(&q);
          244  +  cson_object_set(pay, "tickets",
          245  +                  tktList ? cson_array_value(tktList) : cson_value_null());
          246  +
          247  +  goto end;
          248  +
          249  +  error:
          250  +  assert(0 != g.json.resultCode);
          251  +  cson_value_free( cson_object_value(pay) );
          252  +  pay = NULL;
          253  +    end:
          254  +
          255  +  return pay ? cson_object_value(pay) : NULL;
          256  +
          257  +}
          258  +
          259  +static cson_value * json_report_save(){
          260  +  return NULL;
          261  +}
          262  +#endif /* FOSSIL_ENABLE_JSON */

Added src/json_tag.c.

            1  +#ifdef FOSSIL_ENABLE_JSON
            2  +/*
            3  +** Copyright (c) 2011 D. Richard Hipp
            4  +**
            5  +** This program is free software; you can redistribute it and/or
            6  +** modify it under the terms of the Simplified BSD License (also
            7  +** known as the "2-Clause License" or "FreeBSD License".)
            8  +**
            9  +** This program is distributed in the hope that it will be useful,
           10  +** but without any warranty; without even the implied warranty of
           11  +** merchantability or fitness for a particular purpose.
           12  +**
           13  +** Author contact information:
           14  +**   drh@hwaci.com
           15  +**   http://www.hwaci.com/drh/
           16  +**
           17  +*/
           18  +#include "VERSION.h"
           19  +#include "config.h"
           20  +#include "json_tag.h"
           21  +
           22  +#if INTERFACE
           23  +#include "json_detail.h"
           24  +#endif
           25  +
           26  +
           27  +static cson_value * json_tag_add();
           28  +static cson_value * json_tag_cancel();
           29  +static cson_value * json_tag_find();
           30  +static cson_value * json_tag_list();
           31  +/*
           32  +** Mapping of /json/tag/XXX commands/paths to callbacks.
           33  +*/
           34  +static const JsonPageDef JsonPageDefs_Tag[] = {
           35  +{"add", json_tag_add, 0},
           36  +{"cancel", json_tag_cancel, 0},
           37  +{"find", json_tag_find, 0},
           38  +{"list", json_tag_list, 0},
           39  +/* Last entry MUST have a NULL name. */
           40  +{NULL,NULL,0}
           41  +};
           42  +
           43  +/*
           44  +** Implements the /json/tag family of pages/commands.
           45  +**
           46  +*/
           47  +cson_value * json_page_tag(){
           48  +  return json_page_dispatch_helper(&JsonPageDefs_Tag[0]);
           49  +}
           50  +
           51  +
           52  +/*
           53  +** Impl of /json/tag/add.
           54  +*/
           55  +static cson_value * json_tag_add(){
           56  +  cson_value * payV = NULL;
           57  +  cson_object * pay = NULL;
           58  +  char const * zName = NULL;
           59  +  char const * zCheckin = NULL;
           60  +  char fRaw = 0;
           61  +  char fPropagate = 0;
           62  +  char const * zValue = NULL;
           63  +  const char *zPrefix = NULL;
           64  +
           65  +  if( !g.perm.Write ){
           66  +    json_set_err(FSL_JSON_E_DENIED,
           67  +                 "Requires 'i' permissions.");
           68  +    return NULL;
           69  +  }
           70  +  fRaw = json_find_option_bool("raw",NULL,NULL,0);
           71  +  fPropagate = json_find_option_bool("propagate",NULL,NULL,0);
           72  +  zName = json_find_option_cstr("name",NULL,NULL);
           73  +  zPrefix = fRaw ? "" : "sym-";
           74  +  if(!zName || !*zName){
           75  +    if(!fossil_has_json()){
           76  +      zName = json_command_arg(3);
           77  +    }
           78  +    if(!zName || !*zName){
           79  +      json_set_err(FSL_JSON_E_MISSING_ARGS,
           80  +                   "'name' parameter is missing.");
           81  +      return NULL;
           82  +    }
           83  +  }
           84  +  
           85  +  zCheckin = json_find_option_cstr("checkin",NULL,NULL);
           86  +  if( !zCheckin ){
           87  +    if(!fossil_has_json()){
           88  +      zCheckin = json_command_arg(4);
           89  +    }
           90  +    if(!zCheckin || !*zCheckin){
           91  +      json_set_err(FSL_JSON_E_MISSING_ARGS,
           92  +                   "'checkin' parameter is missing.");
           93  +      return NULL;
           94  +    }
           95  +  }
           96  +
           97  +
           98  +  zValue = json_find_option_cstr("value",NULL,NULL);
           99  +  if(!zValue && !fossil_has_json()){
          100  +    zValue = json_command_arg(5);
          101  +  }
          102  +
          103  +  db_begin_transaction();
          104  +  tag_add_artifact(zPrefix, zName, zCheckin, zValue,
          105  +                   1+fPropagate,NULL/*DateOvrd*/,NULL/*UserOvrd*/);
          106  +  db_end_transaction(0);
          107  +
          108  +  payV = cson_value_new_object();
          109  +  pay = cson_value_get_object(payV);
          110  +  cson_object_set(pay, "name", json_new_string(zName) );
          111  +  cson_object_set(pay, "value", (zValue&&*zValue)
          112  +                  ? json_new_string(zValue)
          113  +                  : cson_value_null());
          114  +  cson_object_set(pay, "propagate", cson_value_new_bool(fPropagate));
          115  +  cson_object_set(pay, "raw", cson_value_new_bool(fRaw));
          116  +  {
          117  +    Blob uu = empty_blob;
          118  +    blob_append(&uu, zName, -1);
          119  +    int const rc = name_to_uuid(&uu, 9, "*");
          120  +    if(0!=rc){
          121  +      json_set_err(FSL_JSON_E_UNKNOWN,"Could not convert name back to UUID!");
          122  +      blob_reset(&uu);
          123  +      goto error;
          124  +    }
          125  +    cson_object_set(pay, "appliedTo", json_new_string(blob_buffer(&uu)));
          126  +    blob_reset(&uu);
          127  +  }
          128  +
          129  +  goto ok;
          130  +  error:
          131  +  assert( 0 != g.json.resultCode );
          132  +  cson_value_free(payV);
          133  +  payV = NULL;
          134  +  ok:
          135  +  return payV;
          136  +}
          137  +
          138  +
          139  +/*
          140  +** Impl of /json/tag/cancel.
          141  +*/
          142  +static cson_value * json_tag_cancel(){
          143  +  char const * zName = NULL;
          144  +  char const * zCheckin = NULL;
          145  +  char fRaw = 0;
          146  +  const char *zPrefix = NULL;
          147  +
          148  +  if( !g.perm.Write ){
          149  +    json_set_err(FSL_JSON_E_DENIED,
          150  +                 "Requires 'i' permissions.");
          151  +    return NULL;
          152  +  }
          153  +
          154  +  fRaw = json_find_option_bool("raw",NULL,NULL,0);
          155  +  zPrefix = fRaw ? "" : "sym-";
          156  +  zName = json_find_option_cstr("name",NULL,NULL);
          157  +  if(!zName || !*zName){
          158  +    if(!fossil_has_json()){
          159  +      zName = json_command_arg(3);
          160  +    }
          161  +    if(!zName || !*zName){
          162  +      json_set_err(FSL_JSON_E_MISSING_ARGS,
          163  +                   "'name' parameter is missing.");
          164  +      return NULL;
          165  +    }
          166  +  }
          167  +  
          168  +  zCheckin = json_find_option_cstr("checkin",NULL,NULL);
          169  +  if( !zCheckin ){
          170  +    if(!fossil_has_json()){
          171  +      zCheckin = json_command_arg(4);
          172  +    }
          173  +    if(!zCheckin || !*zCheckin){
          174  +      json_set_err(FSL_JSON_E_MISSING_ARGS,
          175  +                   "'checkin' parameter is missing.");
          176  +      return NULL;
          177  +    }
          178  +  }
          179  +  /* FIXME?: verify that the tag is currently active. We have no real
          180  +     error case unless we do that.
          181  +  */
          182  +  db_begin_transaction();
          183  +  tag_add_artifact(zPrefix, zName, zCheckin, NULL, 0, 0, 0);
          184  +  db_end_transaction(0);
          185  +  return NULL;
          186  +}
          187  +
          188  +
          189  +/*
          190  +** Impl of /json/tag/find.
          191  +*/
          192  +static cson_value * json_tag_find(){
          193  +  cson_value * payV = NULL;
          194  +  cson_object * pay = NULL;
          195  +  cson_value * listV = NULL;
          196  +  cson_array * list = NULL;
          197  +  char const * zName = NULL;
          198  +  char const * zType = NULL;
          199  +  char const * zType2 = NULL;
          200  +  char fRaw = 0;
          201  +  Stmt q = empty_Stmt;
          202  +  int limit = 0;
          203  +  int tagid = 0;
          204  +
          205  +  if( !g.perm.Read ){
          206  +    json_set_err(FSL_JSON_E_DENIED,
          207  +                 "Requires 'o' permissions.");
          208  +    return NULL;
          209  +  }
          210  +  zName = json_find_option_cstr("name",NULL,NULL);
          211  +  if(!zName || !*zName){
          212  +    if(!fossil_has_json()){
          213  +      zName = json_command_arg(3);
          214  +    }
          215  +    if(!zName || !*zName){
          216  +      json_set_err(FSL_JSON_E_MISSING_ARGS,
          217  +                   "'name' parameter is missing.");
          218  +      return NULL;
          219  +    }
          220  +  }
          221  +  zType = json_find_option_cstr("type",NULL,"t");
          222  +  if(!zType || !*zType){
          223  +    zType = "*";
          224  +    zType2 = zType;
          225  +  }else{
          226  +    switch(*zType){
          227  +      case 'c': zType = "ci"; zType2 = "checkin"; break;
          228  +      case 'e': zType = "e"; zType2 = "event"; break;
          229  +      case 'w': zType = "w"; zType2 = "wiki"; break;
          230  +      case 't': zType = "t"; zType2 = "ticket"; break;
          231  +    }
          232  +  }
          233  +
          234  +  limit = json_find_option_int("limit",NULL,"n",0);
          235  +  fRaw = json_find_option_bool("raw",NULL,NULL,0);
          236  +  
          237  +  tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='%s' || %Q",
          238  +                 fRaw ? "" : "sym-",
          239  +                 zName);
          240  +  
          241  +  payV = cson_value_new_object();
          242  +  pay = cson_value_get_object(payV);
          243  +  cson_object_set(pay, "name", json_new_string(zName));
          244  +  cson_object_set(pay, "raw", cson_value_new_bool(fRaw));
          245  +  cson_object_set(pay, "type", json_new_string(zType2));
          246  +  cson_object_set(pay, "limit", json_new_int(limit));
          247  +
          248  +#if 1
          249  +  if( tagid<=0 ){
          250  +    cson_object_set(pay,"artifacts", cson_value_null());
          251  +    json_warn(FSL_JSON_W_TAG_NOT_FOUND, "Tag not found.");
          252  +    return payV;
          253  +  }
          254  +#endif
          255  +
          256  +  if( fRaw ){
          257  +    db_prepare(&q,
          258  +               "SELECT blob.uuid FROM tagxref, blob"
          259  +               " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
          260  +               "   AND tagxref.tagtype>0"
          261  +               "   AND blob.rid=tagxref.rid"
          262  +               "%s LIMIT %d",
          263  +               zName,
          264  +               (limit>0)?"":"--", limit
          265  +               );
          266  +    while( db_step(&q)==SQLITE_ROW ){
          267  +      if(!listV){
          268  +        listV = cson_value_new_array();
          269  +        list = cson_value_get_array(listV);
          270  +      }
          271  +      cson_array_append(list, cson_sqlite3_column_to_value(q.pStmt,0));
          272  +    }
          273  +    db_finalize(&q);
          274  +  }else{
          275  +    char const * zSqlBase = /*modified from timeline_query_for_tty()*/
          276  +      " SELECT"
          277  +#if 0
          278  +      "   blob.rid AS rid,"
          279  +#endif
          280  +      "   uuid AS uuid,"
          281  +      "   cast(strftime('%s',event.mtime) as int) AS mtime,"
          282  +      "   coalesce(ecomment,comment) AS comment,"
          283  +      "   coalesce(euser,user) AS user,"
          284  +      "   CASE event.type"
          285  +      "     WHEN 'ci' THEN 'checkin'"
          286  +      "     WHEN 'w' THEN 'wiki'"
          287  +      "     WHEN 'e' THEN 'event'"
          288  +      "     WHEN 't' THEN 'ticket'"
          289  +      "     ELSE 'WTF?'"
          290  +      "   END"
          291  +      "   AS eventType"
          292  +      " FROM event, blob"
          293  +      " WHERE blob.rid=event.objid"
          294  +      ;
          295  +    /* FIXME: re-add tags. */
          296  +    db_prepare(&q,
          297  +               "%s"
          298  +               "  AND event.type GLOB '%q'"
          299  +               "  AND blob.rid IN ("
          300  +               "    SELECT rid FROM tagxref"
          301  +               "      WHERE tagtype>0 AND tagid=%d"
          302  +               "  )"
          303  +               " ORDER BY event.mtime DESC"
          304  +               "%s LIMIT %d",
          305  +               zSqlBase, zType, tagid,
          306  +               (limit>0)?"":"--", limit
          307  +               );
          308  +    listV = json_stmt_to_array_of_obj(&q, NULL);
          309  +    db_finalize(&q);
          310  +  }
          311  +
          312  +  if(!listV) {
          313  +    listV = cson_value_null();
          314  +  }
          315  +  cson_object_set(pay, "artifacts", listV);
          316  +  return payV;
          317  +}
          318  +
          319  +
          320  +/*
          321  +** Impl for /json/tag/list
          322  +**
          323  +** TODOs:
          324  +**
          325  +** Add -type TYPE (ci, w, e, t)
          326  +*/
          327  +static cson_value * json_tag_list(){
          328  +  cson_value * payV = NULL;
          329  +  cson_object * pay = NULL;
          330  +  cson_value const * tagsVal = NULL;
          331  +  char const * zCheckin = NULL;
          332  +  char fRaw = 0;
          333  +  char fTicket = 0;
          334  +  Stmt q = empty_Stmt;
          335  +
          336  +  if( !g.perm.Read ){
          337  +    json_set_err(FSL_JSON_E_DENIED,
          338  +                 "Requires 'o' permissions.");
          339  +    return NULL;
          340  +  }
          341  +
          342  +  fRaw = json_find_option_bool("raw",NULL,NULL,0);
          343  +  fTicket = json_find_option_bool("includeTickets","tkt","t",0);
          344  +  zCheckin = json_find_option_cstr("checkin",NULL,NULL);
          345  +  if( !zCheckin ){
          346  +    zCheckin = json_command_arg( g.json.dispatchDepth + 1);
          347  +    if( !zCheckin && cson_value_is_string(g.json.reqPayload.v) ){
          348  +      zCheckin = cson_string_cstr(cson_value_get_string(g.json.reqPayload.v));
          349  +      assert(zCheckin);
          350  +    }
          351  +  }
          352  +  payV = cson_value_new_object();
          353  +  pay = cson_value_get_object(payV);
          354  +  cson_object_set(pay, "raw", cson_value_new_bool(fRaw) );
          355  +  if( zCheckin ){
          356  +    /**
          357  +       Tags for a specific checkin. Output format:
          358  +
          359  +       RAW mode:
          360  +    
          361  +       {
          362  +           "sym-tagname": (value || null),
          363  +           ...other tags...
          364  +       }
          365  +
          366  +       Non-raw:
          367  +
          368  +       {
          369  +          "tagname": (value || null),
          370  +          ...other tags...
          371  +       }
          372  +    */
          373  +    cson_value * objV = NULL;
          374  +    cson_object * obj = NULL;
          375  +    int const rid = name_to_rid(zCheckin);
          376  +    if(0==rid){
          377  +      json_set_err(FSL_JSON_E_UNRESOLVED_UUID,
          378  +                   "Could not find artifact for checkin [%s].",
          379  +                   zCheckin);
          380  +      goto error;
          381  +    }
          382  +    cson_object_set(pay, "checkin", json_new_string(zCheckin));
          383  +    db_prepare(&q,
          384  +               "SELECT tagname, value FROM tagxref, tag"
          385  +               " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid"
          386  +               "   AND tagtype>%d"
          387  +               " ORDER BY tagname",
          388  +               rid,
          389  +               fRaw ? -1 : 0
          390  +               );
          391  +    while( SQLITE_ROW == db_step(&q) ){
          392  +      const char *zName = db_column_text(&q, 0);
          393  +      const char *zValue = db_column_text(&q, 1);
          394  +      if( fRaw==0 ){
          395  +        if( 0!=strncmp(zName, "sym-", 4) ) continue;
          396  +        zName += 4;
          397  +        assert( *zName );
          398  +      }
          399  +      if(NULL==objV){
          400  +        objV = cson_value_new_object();
          401  +        obj = cson_value_get_object(objV);
          402  +        tagsVal = objV;
          403  +        cson_object_set( pay, "tags", objV );
          404  +      }
          405  +      if( zValue && zValue[0] ){
          406  +        cson_object_set( obj, zName, json_new_string(zValue) );
          407  +      }else{
          408  +        cson_object_set( obj, zName, cson_value_null() );
          409  +      }
          410  +    }
          411  +    db_finalize(&q);
          412  +  }else{/* all tags */
          413  +    /* Output format:
          414  +
          415  +    RAW mode:
          416  +    
          417  +    ["tagname", "sym-tagname2",...]
          418  +
          419  +    Non-raw:
          420  +
          421  +    ["tagname", "tagname2",...]
          422  +
          423  +    i don't really like the discrepancy in the format but this list
          424  +    can get really long and (A) most tags don't have values, (B) i
          425  +    don't want to bloat it more, and (C) cson_object_set() is O(N)
          426  +    (N=current number of properties) because it uses an unsorted list
          427  +    internally (for memory reasons), so this can slow down appreciably
          428  +    on a long list. The culprit is really tkt- tags, as there is one
          429  +    for each ticket (941 in the main fossil repo as of this writing).
          430  +    */
          431  +    Blob sql = empty_blob;
          432  +    cson_value * arV = NULL;
          433  +    cson_array * ar = NULL;
          434  +    blob_append(&sql,
          435  +                "SELECT tagname FROM tag"
          436  +                " WHERE EXISTS(SELECT 1 FROM tagxref"
          437  +                "               WHERE tagid=tag.tagid"
          438  +                "                 AND tagtype>0)",
          439  +                -1
          440  +                );
          441  +    if(!fTicket){
          442  +      blob_append(&sql, " AND tagname NOT GLOB('tkt-*') ", -1);
          443  +    }
          444  +    blob_append(&sql,
          445  +                " ORDER BY tagname", -1);
          446  +    db_prepare(&q, blob_buffer(&sql));
          447  +    blob_reset(&sql);
          448  +    cson_object_set(pay, "includeTickets", cson_value_new_bool(fTicket) );
          449  +    while( SQLITE_ROW == db_step(&q) ){
          450  +      const char *zName = db_column_text(&q, 0);
          451  +      if(NULL==arV){
          452  +        arV = cson_value_new_array();
          453  +        ar = cson_value_get_array(arV);
          454  +        cson_object_set(pay, "tags", arV);
          455  +        tagsVal = arV;
          456  +      }
          457  +      else if( !fRaw && (0==strncmp(zName, "sym-", 4))){
          458  +        zName += 4;
          459  +        assert( *zName );
          460  +      }
          461  +      cson_array_append(ar, json_new_string(zName));
          462  +    }
          463  +    db_finalize(&q);
          464  +  }
          465  +
          466  +  goto end;
          467  +  error:
          468  +  assert(0 != g.json.resultCode);
          469  +  cson_value_free(payV);
          470  +  payV = NULL;
          471  +  end:
          472  +  if( payV && !tagsVal ){
          473  +    cson_object_set( pay, "tags", cson_value_null() );
          474  +  }
          475  +  return payV;
          476  +}
          477  +#endif /* FOSSIL_ENABLE_JSON */

Added src/json_timeline.c.

            1  +#ifdef FOSSIL_ENABLE_JSON
            2  +/*
            3  +** Copyright (c) 2011 D. Richard Hipp
            4  +**
            5  +** This program is free software; you can redistribute it and/or
            6  +** modify it under the terms of the Simplified BSD License (also
            7  +** known as the "2-Clause License" or "FreeBSD License".)
            8  +**
            9  +** This program is distributed in the hope that it will be useful,
           10  +** but without any warranty; without even the implied warranty of
           11  +** merchantability or fitness for a particular purpose.
           12  +**
           13  +** Author contact information:
           14  +**   drh@hwaci.com
           15  +**   http://www.hwaci.com/drh/
           16  +**
           17  +*/
           18  +
           19  +#include "VERSION.h"
           20  +#include "config.h"
           21  +#include "json_timeline.h"
           22  +
           23  +#if INTERFACE
           24  +#include "json_detail.h"
           25  +#endif
           26  +
           27  +static cson_value * json_timeline_branch();
           28  +static cson_value * json_timeline_ci();
           29  +static cson_value * json_timeline_ticket();
           30  +/*
           31  +** Mapping of /json/timeline/XXX commands/paths to callbacks.
           32  +*/
           33  +static const JsonPageDef JsonPageDefs_Timeline[] = {
           34  +/* the short forms are only enabled in CLI mode, to avoid
           35  +   that we end up with HTTP clients using 3 different names
           36  +   for the same requests.
           37  +*/
           38  +{"branch", json_timeline_branch, 0},
           39  +{"checkin", json_timeline_ci, 0},
           40  +{"ci", json_timeline_ci, -1},
           41  +{"t", json_timeline_ticket, -1},
           42  +{"ticket", json_timeline_ticket, 0},
           43  +{"w", json_timeline_wiki, -1},
           44  +{"wiki", json_timeline_wiki, 0},
           45  +/* Last entry MUST have a NULL name. */
           46  +{NULL,NULL,0}
           47  +};
           48  +
           49  +
           50  +/*
           51  +** Implements the /json/timeline family of pages/commands. Far from
           52  +** complete.
           53  +**
           54  +*/
           55  +cson_value * json_page_timeline(){
           56  +#if 0
           57  +  /* The original timeline code does not require 'h' access,
           58  +     but it arguably should. For JSON mode i think one could argue
           59  +     that History permissions are required.
           60  +  */
           61  +  if(! g.perm.History && !g.perm.Read ){
           62  +    json_set_err(FSL_JSON_E_DENIED, "Timeline requires 'h' or 'o' access.");
           63  +    return NULL;
           64  +  }
           65  +#endif
           66  +  return json_page_dispatch_helper(&JsonPageDefs_Timeline[0]);
           67  +}
           68  +
           69  +/*
           70  +** Create a temporary table suitable for storing timeline data.
           71  +*/
           72  +static void json_timeline_temp_table(void){
           73  +  /* Field order MUST match that from json_timeline_query()!!! */
           74  +  static const char zSql[] = 
           75  +    @ CREATE TEMP TABLE IF NOT EXISTS json_timeline(
           76  +    @   sortId INTEGER PRIMARY KEY,
           77  +    @   rid INTEGER,
           78  +    @   uuid TEXT,
           79  +    @   mtime INTEGER,
           80  +    @   timestampString TEXT,
           81  +    @   comment TEXT,
           82  +    @   user TEXT,
           83  +    @   isLeaf BOOLEAN,
           84  +    @   bgColor TEXT,
           85  +    @   eventType TEXT,
           86  +    @   tags TEXT,
           87  +    @   tagId INTEGER,
           88  +    @   brief TEXT
           89  +    @ )
           90  +  ;
           91  +  db_multi_exec(zSql);
           92  +}
           93  +
           94  +/*
           95  +** Return a pointer to a constant string that forms the basis
           96  +** for a timeline query for the JSON interface.
           97  +*/
           98  +const char const * json_timeline_query(void){
           99  +  /* Field order MUST match that from json_timeline_temp_table()!!! */
          100  +  static const char zBaseSql[] =
          101  +    @ SELECT
          102  +    @   NULL,
          103  +    @   blob.rid,
          104  +    @   uuid,
          105  +    @   strftime('%%s',event.mtime),
          106  +    @   datetime(event.mtime,'utc'),
          107  +    @   coalesce(ecomment, comment),
          108  +    @   coalesce(euser, user),
          109  +    @   blob.rid IN leaf,
          110  +    @   bgcolor,
          111  +    @   event.type,
          112  +    @   (SELECT group_concat(substr(tagname,5), ',') FROM tag, tagxref
          113  +    @     WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid
          114  +    @       AND tagxref.rid=blob.rid AND tagxref.tagtype>0) as tags,
          115  +    @   tagid as tagId,
          116  +    @   brief as brief
          117  +    @  FROM event JOIN blob 
          118  +    @ WHERE blob.rid=event.objid
          119  +  ;
          120  +  return zBaseSql;
          121  +}
          122  +
          123  +/*
          124  +** Internal helper to append query information if the
          125  +** "tag" or "branch" request properties (CLI: --tag/--branch)
          126  +** are set. Limits the query to a particular branch/tag.
          127  +**
          128  +** tag works like HTML mode's "t" option and branch works like HTML
          129  +** mode's "r" option. They are very similar, but subtly different -
          130  +** tag mode shows only entries with a given tag but branch mode can
          131  +** also reveal some with "related" tags (meaning they were merged into
          132  +** the requested branch).
          133  +**
          134  +** pSql is the target blob to append the query [subset]
          135  +** to.
          136  +**
          137  +** Returns a positive value if it modifies pSql, 0 if it
          138  +** does not. It returns a negative value if the tag
          139  +** provided to the request was not found (pSql is not modified
          140  +** in that case.
          141  +**
          142  +** If payload is not NULL then on success its "tag" or "branch"
          143  +** property is set to the tag/branch name found in the request.
          144  +**
          145  +** Only one of "tag" or "branch" modes will work at a time, and if
          146  +** both are specified, which one takes precedence is unspecified.
          147  +*/
          148  +static char json_timeline_add_tag_branch_clause(Blob *pSql,
          149  +                                                cson_object * pPayload){
          150  +  char const * zTag = NULL;
          151  +  char const * zBranch = NULL;
          152  +  int tagid = 0;
          153  +  if(! g.perm.Read ){
          154  +    return 0;
          155  +  }
          156  +  zTag = json_find_option_cstr("tag",NULL,NULL);
          157  +  if(!zTag || !*zTag){
          158  +    zBranch = json_find_option_cstr("branch",NULL,NULL);
          159  +    if(!zBranch || !*zBranch){
          160  +      return 0;
          161  +    }
          162  +    zTag = zBranch;
          163  +  }
          164  +  tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'",
          165  +                 zTag);
          166  +  if(tagid<=0){
          167  +    return -1;
          168  +  }
          169  +  if(pPayload){
          170  +    cson_object_set( pPayload, zBranch ? "branch" : "tag", json_new_string(zTag) );
          171  +  }
          172  +  blob_appendf(pSql,
          173  +               " AND ("
          174  +               " EXISTS(SELECT 1 FROM tagxref"
          175  +               "        WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)",
          176  +               tagid);
          177  +  if(zBranch){
          178  +    /* from "r" flag code in page_timeline().*/
          179  +    blob_appendf(pSql,
          180  +                 " OR EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=cid"
          181  +                 "    WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)",
          182  +                 tagid);
          183  +#if 0 /* from the undocumented "mionly" flag in page_timeline() */
          184  +    blob_appendf(pSql,
          185  +                 " OR EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid"
          186  +                 "    WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)",
          187  +                 tagid);
          188  +#endif
          189  +  }
          190  +  blob_append(pSql," ) ",3);
          191  +  return 1;
          192  +}
          193  +/*
          194  +** Helper for the timeline family of functions.  Possibly appends 1
          195  +** AND clause and an ORDER BY clause to pSql, depending on the state
          196  +** of the "after" ("a") or "before" ("b") environment parameters.
          197  +** This function gives "after" precedence over "before", and only
          198  +** applies one of them.
          199  +**
          200  +** Returns -1 if it adds a "before" clause, 1 if it adds
          201  +** an "after" clause, and 0 if adds only an order-by clause.
          202  +*/
          203  +static char json_timeline_add_time_clause(Blob *pSql){
          204  +  char const * zAfter = NULL;
          205  +  char const * zBefore = NULL;
          206  +  int rc = 0;
          207  +  zAfter = json_find_option_cstr("after",NULL,"a");
          208  +  zBefore = zAfter ? NULL : json_find_option_cstr("before",NULL,"b");
          209  +
          210  +  if(zAfter&&*zAfter){
          211  +    while( fossil_isspace(*zAfter) ) ++zAfter;
          212  +    blob_appendf(pSql,
          213  +                 " AND event.mtime>=(SELECT julianday(%Q,'utc')) "
          214  +                 " ORDER BY event.mtime ASC ",
          215  +                 zAfter);
          216  +    rc = 1;
          217  +  }else if(zBefore && *zBefore){
          218  +    while( fossil_isspace(*zBefore) ) ++zBefore;
          219  +    blob_appendf(pSql,
          220  +                 " AND event.mtime<=(SELECT julianday(%Q,'utc')) "
          221  +                 " ORDER BY event.mtime DESC ",
          222  +                 zBefore);
          223  +    rc = -1;
          224  +  }else{
          225  +    blob_append(pSql, " ORDER BY event.mtime DESC ", -1);
          226  +    rc = 0;
          227  +  }
          228  +  return rc;
          229  +}
          230  +
          231  +/*
          232  +** Tries to figure out a timeline query length limit base on
          233  +** environment parameters. If it can it returns that value,
          234  +** else it returns some statically defined default value.
          235  +**
          236  +** Never returns a negative value. 0 means no limit.
          237  +*/
          238  +static int json_timeline_limit(){
          239  +  static const int defaultLimit = 20;
          240  +  int limit = -1;
          241  +  if(!g.isHTTP){/* CLI mode */
          242  +    char const * arg = find_option("limit","n",1);
          243  +    if(arg && *arg){
          244  +      limit = atoi(arg);
          245  +    }
          246  +  }
          247  +  if( (limit<0) && fossil_has_json() ){
          248  +    limit = json_getenv_int("limit",-1);
          249  +  }
          250  +  return (limit<0) ? defaultLimit : limit;
          251  +}
          252  +
          253  +/*
          254  +** Internal helper for the json_timeline_EVENTTYPE() family of
          255  +** functions. zEventType must be one of (ci, w, t). pSql must be a
          256  +** cleanly-initialized, empty Blob to store the sql in. If pPayload is
          257  +** not NULL it is assumed to be the pending response payload. If
          258  +** json_timeline_limit() returns non-0, this function adds a LIMIT
          259  +** clause to the generated SQL.
          260  +**
          261  +** If pPayload is not NULL then this might add properties to pPayload,
          262  +** reflecting options set in the request environment.
          263  +**
          264  +** Returns 0 on success. On error processing should not continue and
          265  +** the returned value should be used as g.json.resultCode.
          266  +*/
          267  +static int json_timeline_setup_sql( char const * zEventType,
          268  +                                    Blob * pSql,
          269  +                                    cson_object * pPayload ){
          270  +  int limit;
          271  +  assert( zEventType && *zEventType && pSql );
          272  +  json_timeline_temp_table();
          273  +  blob_append(pSql, "INSERT OR IGNORE INTO json_timeline ", -1);
          274  +  blob_append(pSql, json_timeline_query(), -1 );
          275  +  blob_appendf(pSql, " AND event.type IN(%Q) ", zEventType);
          276  +  if( json_timeline_add_tag_branch_clause(pSql, pPayload) < 0 ){
          277  +    return FSL_JSON_E_INVALID_ARGS;
          278  +  }
          279  +  json_timeline_add_time_clause(pSql);
          280  +  limit = json_timeline_limit();
          281  +  if(limit>=0){
          282  +    blob_appendf(pSql,"LIMIT %d ",limit);
          283  +  }
          284  +  if(pPayload){
          285  +    cson_object_set(pPayload, "limit", json_new_int(limit));
          286  +  }
          287  +  return 0;
          288  +}
          289  +
          290  +/*
          291  +** If any files are associated with the given rid, a JSON array
          292  +** containing information about them is returned (and is owned by the
          293  +** caller). If no files are associated with it then NULL is returned.
          294  +*/
          295  +cson_value * json_get_changed_files(int rid){
          296  +  cson_value * rowsV = NULL;
          297  +  cson_array * rows = NULL;
          298  +  Stmt q = empty_Stmt;
          299  +  db_prepare(&q, 
          300  +#if 0
          301  +             "SELECT (mlink.pid==0) AS isNew,"
          302  +             "       (mlink.fid==0) AS isDel,"
          303  +             "       filename.name AS name"
          304  +             " FROM mlink, filename"
          305  +             " WHERE mid=%d"
          306  +             " AND pid!=fid"
          307  +             " AND filename.fnid=mlink.fnid"
          308  +             " ORDER BY 3 /*sort*/",
          309  +#else
          310  +           "SELECT (pid==0) AS isnew,"
          311  +           "       (fid==0) AS isdel,"
          312  +           "       (SELECT name FROM filename WHERE fnid=mlink.fnid) AS name,"
          313  +           "       (SELECT uuid FROM blob WHERE rid=fid) as uuid,"
          314  +           "       (SELECT uuid FROM blob WHERE rid=pid) as prevUuid"
          315  +           "  FROM mlink"
          316  +           " WHERE mid=%d AND pid!=fid"
          317  +           " ORDER BY name /*sort*/",
          318  +#endif
          319  +             rid
          320  +             );
          321  +  while( (SQLITE_ROW == db_step(&q)) ){
          322  +    cson_value * rowV = cson_value_new_object();
          323  +    cson_object * row = cson_value_get_object(rowV);
          324  +    int const isNew = db_column_int(&q,0);
          325  +    int const isDel = db_column_int(&q,1);
          326  +    char * zDownload = NULL;
          327  +    if(!rowsV){
          328  +      rowsV = cson_value_new_array();
          329  +      rows = cson_value_get_array(rowsV);
          330  +    }
          331  +    cson_array_append( rows, rowV );
          332  +    cson_object_set(row, "name", json_new_string(db_column_text(&q,2)));
          333  +    cson_object_set(row, "uuid", json_new_string(db_column_text(&q,3)));
          334  +    if(!isNew){
          335  +      cson_object_set(row, "prevUuid", json_new_string(db_column_text(&q,4)));
          336  +    }
          337  +    cson_object_set(row, "state",
          338  +                    json_new_string(isNew
          339  +                                    ? "added"
          340  +                                    : (isDel
          341  +                                       ? "removed"
          342  +                                       : "modified")));
          343  +    zDownload = mprintf("/raw/%s?name=%s",
          344  +                        /* reminder: g.zBaseURL is of course not set for CLI mode. */
          345  +                        db_column_text(&q,2),
          346  +                        db_column_text(&q,3));
          347  +    cson_object_set(row, "downloadPath", json_new_string(zDownload));
          348  +    free(zDownload);
          349  +  }
          350  +  db_finalize(&q);
          351  +  return rowsV;
          352  +}
          353  +
          354  +static cson_value * json_timeline_branch(){
          355  +  cson_value * pay = NULL;
          356  +  Blob sql = empty_blob;
          357  +  Stmt q = empty_Stmt;
          358  +  if(!g.perm.Read){
          359  +    json_set_err(FSL_JSON_E_DENIED,
          360  +                 "Requires 'o' permissions.");
          361  +    return NULL;
          362  +  }
          363  +  json_timeline_temp_table();
          364  +  blob_append(&sql,
          365  +              "SELECT"
          366  +              "  blob.rid AS rid,"
          367  +              "  uuid AS uuid,"
          368  +              "  datetime(event.mtime,'utc') as mtime,"
          369  +              "  coalesce(ecomment, comment) as comment,"
          370  +              "  coalesce(euser, user) as user,"
          371  +              "  blob.rid IN leaf as isLeaf,"
          372  +              "  bgcolor as bgColor"
          373  +              " FROM event JOIN blob"
          374  +              " WHERE blob.rid=event.objid",
          375  +              -1);
          376  +
          377  +  blob_appendf(&sql,
          378  +               " AND event.type='ci'"
          379  +               " AND blob.rid IN (SELECT rid FROM tagxref"
          380  +               "  WHERE tagtype>0 AND tagid=%d AND srcid!=0)"
          381  +               " ORDER BY event.mtime DESC",
          382  +               TAG_BRANCH);
          383  +  db_prepare(&q,"%s", blob_str(&sql));
          384  +  blob_reset(&sql);
          385  +  pay = json_stmt_to_array_of_obj(&q, NULL);
          386  +  db_finalize(&q);
          387  +  assert(NULL != pay);
          388  +  if(pay){
          389  +    /* get the array-form tags of each record. */
          390  +    cson_string * tags = cson_new_string("tags",4);
          391  +    cson_string * isLeaf = cson_new_string("isLeaf",6);
          392  +    cson_value_add_reference( cson_string_value(tags) );
          393  +    cson_value_add_reference( cson_string_value(isLeaf) );
          394  +    cson_array * ar = cson_value_get_array(pay);
          395  +    unsigned int i = 0;
          396  +    unsigned int len = cson_array_length_get(ar);
          397  +    for( ; i < len; ++i ){
          398  +      cson_object * row = cson_value_get_object(cson_array_get(ar,i));
          399  +      int rid = cson_value_get_integer(cson_object_get(row,"rid"));
          400  +      if(row>0) {
          401  +        cson_object_set_s(row, tags, json_tags_for_rid(rid,0));
          402  +        cson_object_set_s(row, isLeaf, json_value_to_bool(cson_object_get(row,"isLeaf")));
          403  +      }
          404  +    }
          405  +    cson_value_free( cson_string_value(tags) );
          406  +    cson_value_free( cson_string_value(isLeaf) );
          407  +  }
          408  +   
          409  +  goto end;
          410  +  error:
          411  +  assert( 0 != g.json.resultCode );
          412  +  cson_value_free(pay);
          413  +
          414  +  end:
          415  +  return pay;
          416  +}
          417  +
          418  +/*
          419  +** Implementation of /json/timeline/ci.
          420  +**
          421  +** Still a few TODOs (like figuring out how to structure
          422  +** inheritance info).
          423  +*/
          424  +static cson_value * json_timeline_ci(){
          425  +  cson_value * payV = NULL;
          426  +  cson_object * pay = NULL;
          427  +  cson_value * tmp = NULL;
          428  +  cson_value * listV = NULL;
          429  +  cson_array * list = NULL;
          430  +  int check = 0;
          431  +  char showFiles = -1/*magic number*/;
          432  +  Stmt q = empty_Stmt;
          433  +  char warnRowToJsonFailed = 0;
          434  +  char warnStringToArrayFailed = 0;
          435  +  Blob sql = empty_blob;
          436  +  if( !g.perm.Read ){
          437  +    /* IMO this falls more under the category of g.perm.History, but
          438  +       i'm following the original timeline impl here.
          439  +    */
          440  +    json_set_err( FSL_JSON_E_DENIED, "Checkin timeline requires 'o' access." );
          441  +    return NULL;
          442  +  }
          443  +  showFiles = json_find_option_bool("files",NULL,"f",0);
          444  +  payV = cson_value_new_object();
          445  +  pay = cson_value_get_object(payV);
          446  +  check = json_timeline_setup_sql( "ci", &sql, pay );
          447  +  if(check){
          448  +    json_set_err(check, "Query initialization failed.");
          449  +    goto error;
          450  +  }
          451  +#define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \
          452  +    json_set_err((cson_rc.AllocError==check)        \
          453  +                 ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN,\
          454  +                 "Object property insertion failed");     \
          455  +    goto error;\
          456  +  } (void)0
          457  +
          458  +#if 0
          459  +  /* only for testing! */
          460  +  tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql)));
          461  +  SET("timelineSql");
          462  +#endif
          463  +  db_multi_exec(blob_buffer(&sql));
          464  +  blob_reset(&sql);
          465  +  db_prepare(&q, "SELECT "
          466  +             " rid AS rid"
          467  +#if 0
          468  +             " uuid AS uuid,"
          469  +             " mtime AS timestamp,"
          470  +#  if 0
          471  +             " timestampString AS timestampString,"
          472  +#  endif
          473  +             " comment AS comment, "
          474  +             " user AS user,"
          475  +             " isLeaf AS isLeaf," /*FIXME: convert to JSON bool */
          476  +             " bgColor AS bgColor," /* why always null? */
          477  +             " eventType AS eventType"
          478  +#  if 0
          479  +             " tags AS tags"
          480  +             /*tagId is always null?*/
          481  +             " tagId AS tagId"
          482  +#  endif
          483  +#endif
          484  +             " FROM json_timeline"
          485  +             " ORDER BY rowid");
          486  +  listV = cson_value_new_array();
          487  +  list = cson_value_get_array(listV);
          488  +  tmp = listV;
          489  +  SET("timeline");
          490  +  while( (SQLITE_ROW == db_step(&q) )){
          491  +    /* convert each row into a JSON object...*/
          492  +    int const rid = db_column_int(&q,0);
          493  +    cson_value * rowV = json_artifact_for_ci(rid, showFiles);
          494  +    cson_object * row = cson_value_get_object(rowV);
          495  +    if(!row){
          496  +      if( !warnRowToJsonFailed ){
          497  +        warnRowToJsonFailed = 1;
          498  +        json_warn( FSL_JSON_W_ROW_TO_JSON_FAILED,
          499  +                   "Could not convert at least one timeline result row to JSON." );
          500  +      }
          501  +      continue;
          502  +    }
          503  +    cson_array_append(list, rowV);
          504  +  }
          505  +#undef SET
          506  +  goto ok;
          507  +  error:
          508  +  assert( 0 != g.json.resultCode );
          509  +  cson_value_free(payV);
          510  +  payV = NULL;
          511  +  ok:
          512  +  db_finalize(&q);
          513  +  return payV;
          514  +}
          515  +
          516  +/*
          517  +** Implementation of /json/timeline/wiki.
          518  +**
          519  +*/
          520  +cson_value * json_timeline_wiki(){
          521  +  /* This code is 95% the same as json_timeline_ci(), by the way. */
          522  +  cson_value * payV = NULL;
          523  +  cson_object * pay = NULL;
          524  +  cson_value * tmp = NULL;
          525  +  cson_array * list = NULL;
          526  +  int check = 0;
          527  +  Stmt q = empty_Stmt;
          528  +  Blob sql = empty_blob;
          529  +  if( !g.perm.RdWiki && !g.perm.Read ){
          530  +    json_set_err( FSL_JSON_E_DENIED, "Wiki timeline requires 'o' or 'j' access.");
          531  +    return NULL;
          532  +  }
          533  +  payV = cson_value_new_object();
          534  +  pay = cson_value_get_object(payV);
          535  +  check = json_timeline_setup_sql( "w", &sql, pay );
          536  +  if(check){
          537  +    json_set_err(check, "Query initialization failed.");
          538  +    goto error;
          539  +  }
          540  +
          541  +#define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \
          542  +    json_set_err((cson_rc.AllocError==check)        \
          543  +                 ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN,       \
          544  +                 "Object property insertion failed."); \
          545  +    goto error;\
          546  +  } (void)0
          547  +#if 0
          548  +  /* only for testing! */
          549  +  tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql)));
          550  +  SET("timelineSql");
          551  +#endif
          552  +  db_multi_exec(blob_buffer(&sql));
          553  +  blob_reset(&sql);
          554  +  db_prepare(&q, "SELECT rid AS rid,"
          555  +             " uuid AS uuid,"
          556  +             " mtime AS timestamp,"
          557  +#if 0
          558  +             " timestampString AS timestampString,"
          559  +#endif
          560  +             " comment AS comment, "
          561  +             " user AS user,"
          562  +             " eventType AS eventType"
          563  +#if 0
          564  +             /* can wiki pages have tags? */
          565  +             " tags AS tags," /*FIXME: split this into
          566  +                                a JSON array*/
          567  +             " tagId AS tagId,"
          568  +#endif
          569  +             " FROM json_timeline"
          570  +             " ORDER BY rowid",
          571  +             -1);
          572  +  list = cson_new_array();
          573  +  tmp = cson_array_value(list);
          574  +  SET("timeline");
          575  +  json_stmt_to_array_of_obj(&q, list);
          576  +#undef SET
          577  +  goto ok;
          578  +  error:
          579  +  assert( 0 != g.json.resultCode );
          580  +  cson_value_free(payV);
          581  +  payV = NULL;
          582  +  ok:
          583  +  db_finalize(&q);
          584  +  blob_reset(&sql);
          585  +  return payV;
          586  +}
          587  +
          588  +/*
          589  +** Implementation of /json/timeline/ticket.
          590  +**
          591  +*/
          592  +static cson_value * json_timeline_ticket(){
          593  +  /* This code is 95% the same as json_timeline_ci(), by the way. */
          594  +  cson_value * payV = NULL;
          595  +  cson_object * pay = NULL;
          596  +  cson_value * tmp = NULL;
          597  +  cson_value * listV = NULL;
          598  +  cson_array * list = NULL;
          599  +  int check = 0;
          600  +  Stmt q = empty_Stmt;
          601  +  Blob sql = empty_blob;
          602  +  if( !g.perm.RdTkt && !g.perm.Read ){
          603  +    json_set_err(FSL_JSON_E_DENIED, "Ticket timeline requires 'o' or 'r' access.");
          604  +    return NULL;
          605  +  }
          606  +  payV = cson_value_new_object();
          607  +  pay = cson_value_get_object(payV);
          608  +  check = json_timeline_setup_sql( "t", &sql, pay );
          609  +  if(check){
          610  +    json_set_err(check, "Query initialization failed.");
          611  +    goto error;
          612  +  }
          613  +
          614  +  db_multi_exec(blob_buffer(&sql));
          615  +#define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \
          616  +    json_set_err((cson_rc.AllocError==check)        \
          617  +                 ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN,      \
          618  +                 "Object property insertion failed."); \
          619  +    goto error;\
          620  +  } (void)0
          621  +
          622  +#if 0
          623  +  /* only for testing! */
          624  +  tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql)));
          625  +  SET("timelineSql");
          626  +#endif
          627  +
          628  +  blob_reset(&sql);
          629  +  /*
          630  +    REMINDER/FIXME(?): we have both uuid (the change uuid?)  and
          631  +    ticketUuid (the actual ticket). This is different from the wiki
          632  +    timeline, where we only have the wiki page uuid.
          633  +   */
          634  +  db_prepare(&q, "SELECT rid AS rid,"
          635  +             " uuid AS uuid,"
          636  +             " mtime AS timestamp,"
          637  +#if 0
          638  +             " timestampString AS timestampString,"
          639  +#endif
          640  +             " user AS user,"
          641  +             " eventType AS eventType,"
          642  +             " comment AS comment,"
          643  +             " brief AS briefComment"
          644  +             " FROM json_timeline"
          645  +             " ORDER BY rowid",
          646  +             -1);
          647  +  listV = cson_value_new_array();
          648  +  list = cson_value_get_array(listV);
          649  +  tmp = listV;
          650  +  SET("timeline");
          651  +  while( (SQLITE_ROW == db_step(&q) )){
          652  +    /* convert each row into a JSON object...*/
          653  +    int rc;
          654  +    int const rid = db_column_int(&q,0);
          655  +    Manifest * pMan = NULL;
          656  +    cson_value * rowV = cson_sqlite3_row_to_object(q.pStmt);
          657  +    cson_object * row = cson_value_get_object(rowV);
          658  +    if(!row){
          659  +      json_warn( FSL_JSON_W_ROW_TO_JSON_FAILED,
          660  +                 "Could not convert at least one timeline result row to JSON." );
          661  +      continue;
          662  +    }
          663  +    pMan = manifest_get(rid, CFTYPE_TICKET);
          664  +    assert( pMan && "Manifest is NULL!?!" );
          665  +    if( pMan ){
          666  +      /* FIXME: certainly there's a more efficient way for use to get
          667  +         the ticket UUIDs?
          668  +      */
          669  +      cson_object_set(row,"ticketUuid",json_new_string(pMan->zTicketUuid));
          670  +      manifest_destroy(pMan);
          671  +    }
          672  +    rc = cson_array_append( list, rowV );
          673  +    if( 0 != rc ){
          674  +      cson_value_free(rowV);
          675  +      g.json.resultCode = (cson_rc.AllocError==rc)
          676  +        ? FSL_JSON_E_ALLOC
          677  +        : FSL_JSON_E_UNKNOWN;
          678  +      goto error;
          679  +    }
          680  +  }
          681  +#undef SET
          682  +  goto ok;
          683  +  error:
          684  +  assert( 0 != g.json.resultCode );
          685  +  cson_value_free(payV);
          686  +  payV = NULL;
          687  +  ok:
          688  +  blob_reset(&sql);
          689  +  db_finalize(&q);
          690  +  return payV;
          691  +}
          692  +
          693  +#endif /* FOSSIL_ENABLE_JSON */

Added src/json_user.c.

            1  +#ifdef FOSSIL_ENABLE_JSON
            2  +/*
            3  +** Copyright (c) 2011 D. Richard Hipp
            4  +**
            5  +** This program is free software; you can redistribute it and/or
            6  +** modify it under the terms of the Simplified BSD License (also
            7  +** known as the "2-Clause License" or "FreeBSD License".)
            8  +**
            9  +** This program is distributed in the hope that it will be useful,
           10  +** but without any warranty; without even the implied warranty of
           11  +** merchantability or fitness for a particular purpose.
           12  +**
           13  +** Author contact information:
           14  +**   drh@hwaci.com
           15  +**   http://www.hwaci.com/drh/
           16  +**
           17  +*/
           18  +#include "VERSION.h"
           19  +#include "config.h"
           20  +#include "json_user.h"
           21  +
           22  +#if INTERFACE
           23  +#include "json_detail.h"
           24  +#endif
           25  +
           26  +static cson_value * json_user_get();
           27  +static cson_value * json_user_list();
           28  +static cson_value * json_user_save();
           29  +#if 0
           30  +static cson_value * json_user_create();
           31  +
           32  +#endif
           33  +
           34  +/*
           35  +** Mapping of /json/user/XXX commands/paths to callbacks.
           36  +*/
           37  +static const JsonPageDef JsonPageDefs_User[] = {
           38  +{"create", json_page_nyi, 0},
           39  +{"save", json_user_save, 0},
           40  +{"get", json_user_get, 0},
           41  +{"list", json_user_list, 0},
           42  +/* Last entry MUST have a NULL name. */
           43  +{NULL,NULL,0}
           44  +};
           45  +
           46  +
           47  +/*
           48  +** Implements the /json/user family of pages/commands.
           49  +**
           50  +*/
           51  +cson_value * json_page_user(){
           52  +  return json_page_dispatch_helper(&JsonPageDefs_User[0]);
           53  +}
           54  +
           55  +
           56  +/*
           57  +** Impl of /json/user/list. Requires admin rights.
           58  +*/
           59  +static cson_value * json_user_list(){
           60  +  cson_value * payV = NULL;
           61  +  Stmt q;
           62  +  if(!g.perm.Admin){
           63  +    g.json.resultCode = FSL_JSON_E_DENIED;
           64  +    return NULL;
           65  +  }
           66  +  db_prepare(&q,"SELECT uid AS uid,"
           67  +             " login AS name,"
           68  +             " cap AS capabilities,"
           69  +             " info AS info,"
           70  +             " mtime AS mtime"
           71  +             " FROM user ORDER BY login");
           72  +  payV = json_stmt_to_array_of_obj(&q, NULL);
           73  +  db_finalize(&q);
           74  +  if(NULL == payV){
           75  +    json_set_err(FSL_JSON_E_UNKNOWN,
           76  +                 "Could not convert user list to JSON.");
           77  +  }
           78  +  return payV;  
           79  +}
           80  +
           81  +/*
           82  +** Impl of /json/user/get. Requires admin rights.
           83  +*/
           84  +static cson_value * json_user_get(){
           85  +  cson_value * payV = NULL;
           86  +  char const * pUser = NULL;
           87  +  Stmt q;
           88  +  if(!g.perm.Admin){
           89  +    json_set_err(FSL_JSON_E_DENIED,
           90  +                 "Requires 'a' privileges.");
           91  +    return NULL;
           92  +  }
           93  +  pUser = json_command_arg(g.json.dispatchDepth+1);
           94  +  if( g.isHTTP && (!pUser || !*pUser) ){
           95  +    pUser = json_getenv_cstr("name")
           96  +      /* ACHTUNG: fossil apparently internally sets name=user/get/XYZ
           97  +         if we pass the name as part of the path, which is why we check
           98  +         with json_command_path() before trying to get("name").
           99  +      */;
          100  +  }
          101  +  if(!pUser || !*pUser){
          102  +    json_set_err(FSL_JSON_E_MISSING_ARGS,"Missing 'name' property.");
          103  +    return NULL;
          104  +  }
          105  +  db_prepare(&q,"SELECT uid AS uid,"
          106  +             " login AS name,"
          107  +             " cap AS capabilities,"
          108  +             " info AS info,"
          109  +             " mtime AS mtime"
          110  +             " FROM user"
          111  +             " WHERE login=%Q",
          112  +             pUser);
          113  +  if( (SQLITE_ROW == db_step(&q)) ){
          114  +    payV = cson_sqlite3_row_to_object(q.pStmt);
          115  +    if(!payV){
          116  +      json_set_err(FSL_JSON_E_UNKNOWN,"Could not convert user row to JSON.");
          117  +    }
          118  +  }else{
          119  +    json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND,"User not found.");
          120  +  }
          121  +  db_finalize(&q);
          122  +  return payV;  
          123  +}
          124  +
          125  +/*
          126  +** Expects pUser to contain fossil user fields in JSON form: name,
          127  +** uid, info, capabilities, password.
          128  +**
          129  +** At least one of (name, uid) must be included. All others are
          130  +** optional and their db fields will not be updated if those fields
          131  +** are not included in pUser.
          132  +**
          133  +** If uid is specified then name may refer to a _new_ name
          134  +** for a user, otherwise the name must refer to an existing user.
          135  +**
          136  +** On error g.json's error state is set one of the FSL_JSON_E_xxx
          137  +** values from FossilJsonCodes is returned.
          138  +**
          139  +** On success the db record for the given user is updated.
          140  +**
          141  +** Requires either Admin, Setup, or Password access. Non-admin/setup
          142  +** users can only change their own information.
          143  +**
          144  +** TODOs:
          145  +**
          146  +** - Admin non-Setup users cannot change the information for Setup
          147  +** users.
          148  +**
          149  +*/
          150  +int json_user_update_from_json( cson_object const * pUser ){
          151  +#define CSTR(X) cson_string_cstr(cson_value_get_string( cson_object_get(pUser, X ) ))
          152  +  char const * zName = CSTR("name");
          153  +  char const * zNameOrig = zName;
          154  +  char * zNameFree = NULL;
          155  +  char const * zInfo = CSTR("info");
          156  +  char const * zCap = CSTR("capabilities");
          157  +  char const * zPW = CSTR("password");
          158  +  cson_value const * forceLogout = cson_object_get(pUser, "forceLogout");
          159  +  int gotFields = 0;
          160  +#undef CSTR
          161  +  cson_int_t uid = cson_value_get_integer( cson_object_get(pUser, "uid") );
          162  +  Blob sql = empty_blob;
          163  +  Stmt q = empty_Stmt;
          164  +
          165  +  if(!g.perm.Admin && !g.perm.Setup && !g.perm.Password){
          166  +    return json_set_err( FSL_JSON_E_DENIED,
          167  +                         "Password change requires 'a', 's', "
          168  +                         "or 'p' permissions.");
          169  +  }
          170  +  
          171  +  if(uid<=0 && (!zName||!*zName)){
          172  +    return json_set_err(FSL_JSON_E_MISSING_ARGS,
          173  +                        "One of 'uid' or 'name' is required.");
          174  +  }else if(uid>0){
          175  +    zNameFree = db_text(NULL, "SELECT login FROM user WHERE uid=%d",uid);
          176  +    if(!zNameFree){
          177  +      return json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND,
          178  +                          "No login found for uid %d.", uid);
          179  +    }
          180  +    zName = zNameFree;
          181  +  }else{
          182  +    uid = db_int(0,"SELECT uid FROM user WHERE login=%Q",
          183  +                 zName);
          184  +    if(uid<=0){
          185  +      return json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND,
          186  +                          "No login found for user [%s].", zName);
          187  +    }
          188  +  }
          189  +  /*
          190  +    Todo: reserve the uid=-1 to mean that the user should be created
          191  +    by this request.
          192  +
          193  +    Todo: when changing an existing user's name we need to invalidate
          194  +    or recalculate the login hash because the user's name is part of
          195  +    the hash.
          196  +  */
          197  +
          198  +  /* Maintenance note: all error-returns from here on out should go
          199  +     via goto error in order to clean up.
          200  +  */
          201  +  
          202  +  if(uid != g.userUid){
          203  +    /*
          204  +      TODO: do not allow an admin user to modify a setup user
          205  +      unless the admin is also a setup user. setup.c uses
          206  +      that logic.
          207  +    */
          208  +    if(!g.perm.Admin && !g.perm.Setup){
          209  +      json_set_err(FSL_JSON_E_DENIED,
          210  +                   "Changing another user's data requires "
          211  +                   "'a' or 's' privileges.");
          212  +    }
          213  +  }
          214  +  
          215  +  blob_append(&sql, "UPDATE USER SET",-1 );
          216  +  blob_append(&sql, " mtime=cast(strftime('%s') AS INTEGER)", -1);
          217  +
          218  +  if((uid>0) && zName){
          219  +    /* Only change the name if the uid is explicitly set and name
          220  +       would actually change. */
          221  +    if( zNameOrig && (zName != zNameOrig)
          222  +        && (0!=strcmp(zNameOrig,zName))){
          223  +      if(!g.perm.Admin && !g.perm.Setup) {
          224  +        json_set_err( FSL_JSON_E_DENIED,
          225  +                      "Modifying user names requires 'a' or 's' privileges.");
          226  +        goto error;
          227  +      }
          228  +    }
          229  +    blob_appendf(&sql, ", login=%Q", zNameOrig);
          230  +    ++gotFields;
          231  +  }
          232  +
          233  +  if( zCap ){
          234  +    blob_appendf(&sql, ", cap=%Q", zCap);
          235  +    ++gotFields;
          236  +  }
          237  +
          238  +  if( zPW ){
          239  +    char * zPWHash = NULL;
          240  +    ++gotFields;
          241  +    zPWHash = sha1_shared_secret(zPW, zName, NULL);
          242  +    blob_appendf(&sql, ", pw=%Q", zPWHash);
          243  +    free(zPWHash);
          244  +  }
          245  +
          246  +  if( zInfo ){
          247  +    blob_appendf(&sql, ", info=%Q", zInfo);
          248  +    ++gotFields;
          249  +  }
          250  +
          251  +  if((g.perm.Admin || g.perm.Setup)
          252  +     && forceLogout && cson_value_get_bool(forceLogout)){
          253  +    blob_append(&sql, ", cookie=NULL, cexpire=NULL", -1);
          254  +    ++gotFields;
          255  +  }
          256  +  
          257  +  if(!gotFields){
          258  +    json_set_err( FSL_JSON_E_MISSING_ARGS,
          259  +                  "Required user data are missing.");
          260  +    goto error;
          261  +  }
          262  +  assert(uid>0);
          263  +  blob_appendf(&sql, " WHERE uid=%d", uid);
          264  +  free( zNameFree );
          265  +  /*puts(blob_str(&sql));*/
          266  +  db_prepare(&q, "%s", blob_str(&sql));
          267  +  blob_reset(&sql);
          268  +  db_exec(&q);
          269  +  db_finalize(&q);
          270  +  return 0;
          271  +
          272  +  error:
          273  +  assert(0 != g.json.resultCode);
          274  +  free(zNameFree);
          275  +  blob_reset(&sql);
          276  +  return g.json.resultCode;
          277  +}
          278  +
          279  +
          280  +/*
          281  +** Impl of /json/user/save.
          282  +**
          283  +** TODOs:
          284  +**
          285  +** - Return something useful in the payload (at least the id of the
          286  +** modified/created user).
          287  +*/
          288  +static cson_value * json_user_save(){
          289  +  /* try to get user info from GET/CLI args and construct
          290  +     a JSON form of it... */
          291  +  cson_object * u = cson_new_object();
          292  +  char const * str = NULL;
          293  +  char b = -1;
          294  +  int i = -1;
          295  +#define PROP(LK) str = json_find_option_cstr(LK,NULL,NULL);             \
          296  +  if(str){ cson_object_set(u, LK, json_new_string(str)); } (void)0
          297  +  PROP("name");
          298  +  PROP("password");
          299  +  PROP("info");
          300  +  PROP("capabilities");
          301  +#undef PROP
          302  +  
          303  +#define PROP(LK,DFLT) b = json_find_option_bool(LK,NULL,NULL,DFLT);     \
          304  +  if(DFLT!=b){ cson_object_set(u, LK, cson_value_new_bool(b)); } (void)0
          305  +  PROP("forceLogout",-1);
          306  +#undef PROP
          307  +
          308  +#define PROP(LK,DFLT) i = json_find_option_int(LK,NULL,NULL,DFLT);      \
          309  +  if(DFLT != i){ cson_object_set(u, LK, cson_value_new_integer(i)); } (void)0
          310  +  PROP("uid",-99);
          311  +#undef PROP
          312  +  if( g.json.reqPayload.o ){
          313  +    cson_object_merge( u, g.json.reqPayload.o, CSON_MERGE_NO_RECURSE );
          314  +  }
          315  +  json_user_update_from_json( u );
          316  +  cson_free_object(u);
          317  +  return NULL;
          318  +}
          319  +#endif /* FOSSIL_ENABLE_JSON */

Added src/json_wiki.c.

            1  +#ifdef FOSSIL_ENABLE_JSON
            2  +/*
            3  +** Copyright (c) 2011 D. Richard Hipp
            4  +**
            5  +** This program is free software; you can redistribute it and/or
            6  +** modify it under the terms of the Simplified BSD License (also
            7  +** known as the "2-Clause License" or "FreeBSD License".)
            8  +**
            9  +** This program is distributed in the hope that it will be useful,
           10  +** but without any warranty; without even the implied warranty of
           11  +** merchantability or fitness for a particular purpose.
           12  +**
           13  +** Author contact information:
           14  +**   drh@hwaci.com
           15  +**   http://www.hwaci.com/drh/
           16  +**
           17  +*/
           18  +#include "VERSION.h"
           19  +#include "config.h"
           20  +#include "json_wiki.h"
           21  +
           22  +#if INTERFACE
           23  +#include "json_detail.h"
           24  +#endif
           25  +
           26  +static cson_value * json_wiki_create();
           27  +static cson_value * json_wiki_get();
           28  +static cson_value * json_wiki_list();
           29  +static cson_value * json_wiki_save();
           30  +
           31  +/*
           32  +** Mapping of /json/wiki/XXX commands/paths to callbacks.
           33  +*/
           34  +static const JsonPageDef JsonPageDefs_Wiki[] = {
           35  +{"create", json_wiki_create, 1},
           36  +{"get", json_wiki_get, 0},
           37  +{"list", json_wiki_list, 0},
           38  +{"save", json_wiki_save, 1},
           39  +{"timeline", json_timeline_wiki,0},
           40  +/* Last entry MUST have a NULL name. */
           41  +{NULL,NULL,0}
           42  +};
           43  +
           44  +
           45  +/*
           46  +** Implements the /json/wiki family of pages/commands.
           47  +**
           48  +*/
           49  +cson_value * json_page_wiki(){
           50  +  return json_page_dispatch_helper(&JsonPageDefs_Wiki[0]);
           51  +}
           52  +
           53  +
           54  +/*
           55  +** Loads the given wiki page and creates a JSON object representation
           56  +** of it. If the page is not found then NULL is returned. If doParse
           57  +** is true then the page content is HTML-ized using fossil's
           58  +** conventional wiki format, else it is not parsed.
           59  +**
           60  +** The returned value, if not NULL, is-a JSON Object owned by the
           61  +** caller.
           62  +*/
           63  +cson_value * json_get_wiki_page_by_name(char const * zPageName, char doParse){
           64  +  int rid;
           65  +  Manifest *pWiki = 0;
           66  +  char const * zBody = NULL;
           67  +  char const * zFormat = NULL;
           68  +  char * zUuid = NULL;
           69  +  Stmt q;
           70  +  db_prepare(&q,
           71  +             "SELECT x.rid, b.uuid FROM tag t, tagxref x, blob b"
           72  +             " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q' "
           73  +             " AND b.rid=x.rid"
           74  +             " ORDER BY x.mtime DESC LIMIT 1",
           75  +             zPageName 
           76  +             );
           77  +  if( (SQLITE_ROW != db_step(&q)) ){
           78  +    db_finalize(&q);
           79  +    return NULL;
           80  +  }
           81  +  rid = db_column_int(&q,0);
           82  +  zUuid = db_column_malloc(&q,1);
           83  +  db_finalize(&q);
           84  +  if( (pWiki = manifest_get(rid, CFTYPE_WIKI))!=0 ){
           85  +    zBody = pWiki->zWiki;
           86  +  }
           87  +
           88  +  {
           89  +    unsigned int len;
           90  +    cson_object * pay = cson_new_object();
           91  +    cson_object_set(pay,"name",json_new_string(zPageName));
           92  +    cson_object_set(pay,"uuid",json_new_string(zUuid));
           93  +    free(zUuid);
           94  +    zUuid = NULL;
           95  +    /*cson_object_set(pay,"rid",json_new_int((cson_int_t)rid));*/
           96  +    cson_object_set(pay,"lastSavedBy",json_new_string(pWiki->zUser));
           97  +    cson_object_set(pay,FossilJsonKeys.timestamp, json_julian_to_timestamp(pWiki->rDate));
           98  +    cson_object_set(pay,"contentFormat",json_new_string(zFormat));
           99  +    if( doParse ){
          100  +      Blob content = empty_blob;
          101  +      Blob raw = empty_blob;
          102  +      blob_append(&raw,zBody,-1);
          103  +      wiki_convert(&raw,&content,0);
          104  +      len = strlen(zBody);
          105  +      len = (unsigned int)blob_size(&content);
          106  +      cson_object_set(pay,"contentLength",json_new_int((cson_int_t)len));
          107  +      cson_object_set(pay,"content",
          108  +                      cson_value_new_string(blob_buffer(&content),len));
          109  +      blob_reset(&content);
          110  +      blob_reset(&raw);
          111  +    }else{
          112  +      len = zBody ? strlen(zBody) : 0;
          113  +      cson_object_set(pay,"contentLength",json_new_int((cson_int_t)len));
          114  +      cson_object_set(pay,"content",cson_value_new_string(zBody,len));
          115  +    }
          116  +    /*TODO: add 'T' (tag) fields*/
          117  +    /*TODO: add the 'A' card (file attachment) entries?*/
          118  +    manifest_destroy(pWiki);
          119  +    return cson_object_value(pay);
          120  +  }
          121  +}
          122  +
          123  +
          124  +/*
          125  +** Searches for a wiki page with the given rid. If found it behaves
          126  +** like json_get_wiki_page_by_name(pageName, doParse), else it returns
          127  +** NULL.
          128  +*/
          129  +cson_value * json_get_wiki_page_by_rid(int rid, char doParse){
          130  +  char * zPageName = NULL;
          131  +  cson_value * rc = NULL;
          132  +  zPageName = db_text(NULL,
          133  +                      "SELECT substr(t.tagname,6) AS name "
          134  +                      " FROM tag t, tagxref x, blob b "
          135  +                      " WHERE b.rid=%d "
          136  +                      " AND t.tagname GLOB 'wiki-*'"
          137  +                      " AND x.tagid=t.tagid AND b.rid=x.rid ",
          138  +                      rid);
          139  +  if( zPageName ){
          140  +    rc = json_get_wiki_page_by_name(zPageName, doParse);
          141  +    free(zPageName);
          142  +  }
          143  +  return rc;
          144  +}
          145  +
          146  +/*
          147  +** Implementation of /json/wiki/get.
          148  +**
          149  +*/
          150  +static cson_value * json_wiki_get(){
          151  +  int rid;
          152  +  Manifest *pWiki = 0;
          153  +  char const * zBody = NULL;
          154  +  char const * zPageName;
          155  +  char const * zFormat = NULL;
          156  +  char * zUuid = NULL;
          157  +  Stmt q;
          158  +  if( !g.perm.RdWiki && !g.perm.Read ){
          159  +    json_set_err(FSL_JSON_E_DENIED,
          160  +                 "Requires 'o' or 'j' access.");
          161  +    return NULL;
          162  +  }
          163  +  zPageName = json_find_option_cstr("name",NULL,"n")
          164  +      /* Damn... fossil automatically sets name to the PATH
          165  +         part after /json, so we need a workaround down here....
          166  +      */
          167  +      ;
          168  +  if( zPageName && (NULL != strstr(zPageName, "/"))){
          169  +      /* Assume that we picked up a path remnant. */
          170  +      zPageName = NULL;
          171  +  }
          172  +  if( !zPageName && cson_value_is_string(g.json.reqPayload.v) ){
          173  +      zPageName = cson_string_cstr(cson_value_get_string(g.json.reqPayload.v));
          174  +  }
          175  +  if(!zPageName){
          176  +    zPageName = json_command_arg(g.json.dispatchDepth+1);
          177  +  }
          178  +  if(!zPageName||!*zPageName){
          179  +    json_set_err(FSL_JSON_E_MISSING_ARGS,
          180  +                 "'name' argument is missing.");
          181  +    return NULL;
          182  +  }
          183  +
          184  +  zFormat = json_find_option_cstr("format",NULL,"f");
          185  +  if(!zFormat || !*zFormat){
          186  +    zFormat = "raw";
          187  +  }
          188  +  if( 'r' != *zFormat ){
          189  +    zFormat = "html";
          190  +  }
          191  +  return json_get_wiki_page_by_name(zPageName, 'h'==*zFormat);
          192  +}
          193  +
          194  +/*
          195  +** Internal impl of /wiki/save and /wiki/create. If createMode is 0
          196  +** and the page already exists then a
          197  +** FSL_JSON_E_RESOURCE_ALREADY_EXISTS error is triggered.  If
          198  +** createMode is false then the FSL_JSON_E_RESOURCE_NOT_FOUND is
          199  +** triggered if the page does not already exists.
          200  +**
          201  +** Note that the error triggered when createMode==0 and no such page
          202  +** exists is rather arbitrary - we could just as well create the entry
          203  +** here if it doesn't already exist. With that, save/create would
          204  +** become one operation. That said, i expect there are people who
          205  +** would categorize such behaviour as "being too clever" or "doing too
          206  +** much automatically" (and i would likely agree with them).
          207  +**
          208  +** If allowCreateIfExists is true then this function will allow a new
          209  +** page to be created even if createMode is false.
          210  +*/
          211  +static cson_value * json_wiki_create_or_save(char createMode,
          212  +                                             char allowCreateIfExists){
          213  +  Blob content = empty_blob;
          214  +  cson_value * nameV;
          215  +  cson_value * contentV;
          216  +  cson_value * emptyContent = NULL;
          217  +  cson_value * payV = NULL;
          218  +  cson_object * pay = NULL;
          219  +  cson_string const * jstr = NULL;
          220  +  char const * zContent;
          221  +  char const * zBody = NULL;
          222  +  char const * zPageName;
          223  +  unsigned int contentLen = 0;
          224  +  int rid;
          225  +  if( (createMode && !g.perm.NewWiki)
          226  +      || (!createMode && !g.perm.WrWiki)){
          227  +    json_set_err(FSL_JSON_E_DENIED,
          228  +                 "Requires '%c' permissions.",
          229  +                 (createMode ? 'f' : 'k'));
          230  +    return NULL;
          231  +  }
          232  +  nameV = json_req_payload_get("name");
          233  +  if(!nameV){
          234  +    json_set_err( FSL_JSON_E_MISSING_ARGS,
          235  +                  "'name' parameter is missing.");
          236  +    return NULL;
          237  +  }
          238  +  zPageName = cson_string_cstr(cson_value_get_string(nameV));
          239  +  rid = db_int(0,
          240  +     "SELECT x.rid FROM tag t, tagxref x"
          241  +     " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
          242  +     " ORDER BY x.mtime DESC LIMIT 1",
          243  +     zPageName
          244  +  );
          245  +
          246  +  if(rid){
          247  +    if(createMode){
          248  +      json_set_err(FSL_JSON_E_RESOURCE_ALREADY_EXISTS,
          249  +                   "Wiki page '%s' already exists.",
          250  +                   zPageName);
          251  +      goto error;
          252  +    }
          253  +  }else if(!allowCreateIfExists){
          254  +    json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND,
          255  +                 "Wiki page '%s' not found.",
          256  +                 zPageName);
          257  +    goto error;
          258  +  }
          259  +
          260  +  contentV = json_req_payload_get("content");
          261  +  if( !contentV ){
          262  +    if( createMode || (!rid && allowCreateIfExists) ){
          263  +      contentV = emptyContent = cson_value_new_string("",0);
          264  +    }else{
          265  +      json_set_err(FSL_JSON_E_MISSING_ARGS,
          266  +                   "'content' parameter is missing.");
          267  +      goto error;
          268  +    }
          269  +  }
          270  +  if( !cson_value_is_string(nameV)
          271  +      || !cson_value_is_string(contentV)){
          272  +    json_set_err(FSL_JSON_E_INVALID_ARGS,
          273  +                 "'name' and 'content' parameters must be strings.");
          274  +    goto error;
          275  +  }
          276  +  jstr = cson_value_get_string(contentV);
          277  +  contentLen = (int)cson_string_length_bytes(jstr);
          278  +  if(contentLen){
          279  +    blob_append(&content, cson_string_cstr(jstr),contentLen);
          280  +  }
          281  +  wiki_cmd_commit(zPageName, 0==rid, &content);
          282  +  blob_reset(&content);
          283  +
          284  +  payV = cson_value_new_object();
          285  +  pay = cson_value_get_object(payV);
          286  +  cson_object_set( pay, "name", nameV );
          287  +  cson_object_set( pay, FossilJsonKeys.timestamp,
          288  +                   json_new_timestamp(-1) );
          289  +
          290  +  goto ok;
          291  +  error:
          292  +  assert( 0 != g.json.resultCode );
          293  +  cson_value_free(payV);
          294  +  payV = NULL;
          295  +  ok:
          296  +  if( emptyContent ){
          297  +    /* We have some potentially tricky memory ownership
          298  +       here, which is why we handle emptyContent separately.
          299  +
          300  +       This is, in fact, overkill because cson_value_new_string("",0)
          301  +       actually returns a shared singleton instance (i.e. doesn't
          302  +       allocate), but that is a cson implementation detail which i
          303  +       don't want leaking into this code...
          304  +    */
          305  +    cson_value_free(emptyContent);
          306  +  }
          307  +  return payV;
          308  +
          309  +}
          310  +
          311  +/*
          312  +** Implementation of /json/wiki/create.
          313  +*/
          314  +static cson_value * json_wiki_create(){
          315  +  return json_wiki_create_or_save(1,0);
          316  +}
          317  +
          318  +/*
          319  +** Implementation of /json/wiki/save.
          320  +*/
          321  +static cson_value * json_wiki_save(){
          322  +  char const createIfNotExists = json_getenv_bool("createIfNotExists",0);
          323  +  return json_wiki_create_or_save(0,createIfNotExists);
          324  +}
          325  +
          326  +/*
          327  +** Implementation of /json/wiki/list.
          328  +*/
          329  +static cson_value * json_wiki_list(){
          330  +  cson_value * listV = NULL;
          331  +  cson_array * list = NULL;
          332  +  Stmt q;
          333  +  if( !g.perm.RdWiki && !g.perm.Read ){
          334  +    json_set_err(FSL_JSON_E_DENIED,
          335  +                 "Requires 'j' or 'o' permissions.");
          336  +    return NULL;
          337  +  }
          338  +  db_prepare(&q,"SELECT"
          339  +             " substr(tagname,6) as name"
          340  +             " FROM tag WHERE tagname GLOB 'wiki-*'"
          341  +             " ORDER BY lower(name)");
          342  +  listV = cson_value_new_array();
          343  +  list = cson_value_get_array(listV);
          344  +  while( SQLITE_ROW == db_step(&q) ){
          345  +    cson_value * v = cson_sqlite3_column_to_value(q.pStmt,0);
          346  +    if(!v){
          347  +      json_set_err(FSL_JSON_E_UNKNOWN,
          348  +                   "Could not convert wiki name column to JSON.");
          349  +      goto error;
          350  +    }else if( 0 != cson_array_append( list, v ) ){
          351  +      cson_value_free(v);
          352  +      json_set_err(FSL_JSON_E_ALLOC,"Could not append wiki page name to array.")
          353  +        /* OOM (or maybe numeric overflow) are the only realistic
          354  +           error codes for that particular failure.*/;
          355  +      goto error;
          356  +    }
          357  +  }
          358  +  goto end;
          359  +  error:
          360  +  assert(0 != g.json.resultCode);
          361  +  cson_value_free(listV);
          362  +  listV = NULL;
          363  +  end:
          364  +  db_finalize(&q);
          365  +  return listV;
          366  +}
          367  +#endif /* FOSSIL_ENABLE_JSON */

Changes to src/login.c.

    82     82   /*
    83     83   ** Return the name of the login cookie.
    84     84   **
    85     85   ** The login cookie name is always of the form:  fossil-XXXXXXXXXXXXXXXX
    86     86   ** where the Xs are the first 16 characters of the login-group-code or
    87     87   ** of the project-code if we are not a member of any login-group.
    88     88   */
    89         -static char *login_cookie_name(void){
           89  +char *login_cookie_name(void){
    90     90     static char *zCookieName = 0;
    91     91     if( zCookieName==0 ){
    92     92       zCookieName = db_text(0,
    93     93          "SELECT 'fossil-' || substr(value,1,16)"
    94     94          "  FROM config"
    95     95          " WHERE name IN ('project-code','login-group-code')"
    96     96          " ORDER BY name /*sort*/"
................................................................................
   144    144     return mprintf("%.16s", zFullCode);
   145    145   }
   146    146   
   147    147   
   148    148   /*
   149    149   ** Check to see if the anonymous login is valid.  If it is valid, return
   150    150   ** the userid of the anonymous user.
          151  +**
          152  +** The zCS parameter is the "captcha seed" used for a specific
          153  +** anonymous login request.
   151    154   */
   152         -static int isValidAnonymousLogin(
          155  +int login_is_valid_anonymous(
   153    156     const char *zUsername,  /* The username.  Must be "anonymous" */
   154         -  const char *zPassword   /* The supplied password */
          157  +  const char *zPassword,  /* The supplied password */
          158  +  const char *zCS         /* The captcha seed value */
   155    159   ){
   156         -  const char *zCS;        /* The captcha seed value */
   157    160     const char *zPw;        /* The correct password shown in the captcha */
   158    161     int uid;                /* The user ID of anonymous */
   159    162   
   160    163     if( zUsername==0 ) return 0;
   161         -  if( zPassword==0 ) return 0;
   162         -  if( fossil_strcmp(zUsername,"anonymous")!=0 ) return 0;
   163         -  zCS = P("cs");   /* The "cs" parameter is the "captcha seed" */
   164         -  if( zCS==0 ) return 0;
          164  +  else if( zPassword==0 ) return 0;
          165  +  else if( zCS==0 ) return 0;
          166  +  else if( fossil_strcmp(zUsername,"anonymous")!=0 ) return 0;
   165    167     zPw = captcha_decode((unsigned int)atoi(zCS));
   166    168     if( fossil_stricmp(zPw, zPassword)!=0 ) return 0;
   167    169     uid = db_int(0, "SELECT uid FROM user WHERE login='anonymous'"
   168    170                     " AND length(pw)>0 AND length(cap)>0");
   169    171     return uid;
   170    172   }
   171    173   
................................................................................
   195    197     create_accesslog_table();
   196    198     db_multi_exec(
   197    199       "INSERT INTO accesslog(uname,ipaddr,success,mtime)"
   198    200       "VALUES(%Q,%Q,%d,julianday('now'));",
   199    201       zUsername, zIpAddr, bSuccess
   200    202     );
   201    203   }
          204  +
          205  +/*
          206  +** Searches for the user ID matching the given name and password.
          207  +** On success it returns a positive value. On error it returns 0.
          208  +** On serious (DB-level) error it will probably exit.
          209  +**
          210  +** zPassword may be either the plain-text form or the encrypted
          211  +** form of the user's password.
          212  +*/
          213  +int login_search_uid(char const *zUsername, char const *zPasswd){
          214  +  char * zSha1Pw = sha1_shared_secret(zPasswd, zUsername, 0);
          215  +  int const uid =
          216  +      db_int(0,
          217  +             "SELECT uid FROM user"
          218  +             " WHERE login=%Q"
          219  +             "   AND length(cap)>0 AND length(pw)>0"
          220  +             "   AND login NOT IN ('anonymous','nobody','developer','reader')"
          221  +             "   AND (pw=%Q OR pw=%Q)",
          222  +             zUsername, zPasswd, zSha1Pw
          223  +             );
          224  +  free(zSha1Pw);
          225  +  return uid;
          226  +}
          227  +
          228  +/*
          229  +** Generates a login cookie value for a non-anonymous user.
          230  +**
          231  +** The zHash parameter must be a random value which must be
          232  +** subsequently stored in user.cookie for later validation.
          233  +**
          234  +** The returned memory should be free()d after use.
          235  +*/
          236  +char * login_gen_user_cookie_value(char const *zUsername, char const * zHash){
          237  +  char * zProjCode = db_get("project-code",NULL);
          238  +  char *zCode = abbreviated_project_code(zProjCode);
          239  +  free(zProjCode);
          240  +  assert((zUsername && *zUsername) && "Invalid user data.");
          241  +  return mprintf("%s/%z/%s", zHash, zCode, zUsername);
          242  +}
          243  +
          244  +/*
          245  +** Generates a login cookie for NON-ANONYMOUS users.  Note that this
          246  +** function "could" figure out the uid by itself but it currently
          247  +** doesn't because the code which calls this already has the uid.
          248  +**
          249  +** This function also updates the user.cookie, user.ipaddr,
          250  +** and user.cexpire fields for the given user.
          251  +**
          252  +** If zDest is not NULL then the generated cookie is copied to
          253  +** *zDdest and ownership is transfered to the caller (who should
          254  +** eventually pass it to free()).
          255  +*/
          256  +void login_set_user_cookie(
          257  +  char const * zUsername, /* User's name */
          258  +  int uid,                /* User's ID */
          259  +  char ** zDest           /* Optional: store generated cookie value. */
          260  +){
          261  +  const char *zCookieName = login_cookie_name();
          262  +  const char *zExpire = db_get("cookie-expire","8766");
          263  +  int expires = atoi(zExpire)*3600;
          264  +  char *zHash;
          265  +  char *zCookie;
          266  +  char const * zIpAddr = PD("REMOTE_ADDR","nil");   /* Complete IP address for logging */
          267  +  char * zRemoteAddr = ipPrefix(zIpAddr);     /* Abbreviated IP address */
          268  +  assert((zUsername && *zUsername) && (uid > 0) && "Invalid user data.");
          269  +  zHash = db_text(0, "SELECT hex(randomblob(25))");
          270  +  zCookie = login_gen_user_cookie_value(zUsername, zHash);
          271  +  cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires);
          272  +  record_login_attempt(zUsername, zIpAddr, 1);
          273  +  db_multi_exec(
          274  +                "UPDATE user SET cookie=%Q, ipaddr=%Q, "
          275  +                "  cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
          276  +                zHash, zRemoteAddr, expires, uid
          277  +                );
          278  +  free(zRemoteAddr);
          279  +  free(zHash);
          280  +  if( zDest ){
          281  +    *zDest = zCookie;
          282  +  }else{
          283  +    free(zCookie);
          284  +  }
          285  +}
          286  +
          287  +/* Sets a cookie for an anonymous user login, which looks like this:
          288  +**
          289  +**    HASH/TIME/anonymous
          290  +**
          291  +** Where HASH is the sha1sum of TIME/IPADDR/SECRET, in which IPADDR
          292  +** is the abbreviated IP address and SECRET is captcha-secret.
          293  +**
          294  +** If either zIpAddr or zRemoteAddr are NULL then REMOTE_ADDR
          295  +** is used.
          296  +**
          297  +** If zCookieDest is not NULL then the generated cookie is assigned to
          298  +** *zCookieDest and the caller must eventually free() it.
          299  +*/
          300  +void login_set_anon_cookie(char const * zIpAddr, char ** zCookieDest ){
          301  +  char const *zNow;            /* Current time (julian day number) */
          302  +  char *zCookie;               /* The login cookie */
          303  +  char const *zCookieName;     /* Name of the login cookie */
          304  +  Blob b;                      /* Blob used during cookie construction */
          305  +  char * zRemoteAddr;     /* Abbreviated IP address */
          306  +  if(!zIpAddr){
          307  +    zIpAddr = PD("REMOTE_ADDR","nil");
          308  +  }
          309  +  zRemoteAddr = ipPrefix(zIpAddr);
          310  +  zCookieName = login_cookie_name();
          311  +  zNow = db_text("0", "SELECT julianday('now')");
          312  +  assert( zCookieName && zRemoteAddr && zIpAddr && zNow );
          313  +  blob_init(&b, zNow, -1);
          314  +  blob_appendf(&b, "/%s/%s", zRemoteAddr, db_get("captcha-secret",""));
          315  +  sha1sum_blob(&b, &b);
          316  +  zCookie = mprintf("%s/%s/anonymous", blob_buffer(&b), zNow);
          317  +  blob_reset(&b);
          318  +  cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), 6*3600);
          319  +  if( zCookieDest ){
          320  +    *zCookieDest = zCookie;
          321  +  }else{
          322  +    free(zCookie);
          323  +  }
          324  +
          325  +}
          326  +
          327  +/*
          328  +** "Unsets" the login cookie (insofar as cookies can be unset) and
          329  +** clears the current user's (g.userUid) login information from the
          330  +** user table. Sets: user.cookie, user.ipaddr, user.cexpire.
          331  +**
          332  +** We could/should arguably clear out g.userUid and g.perm here, but
          333  +** we don't currently do not.
          334  +**
          335  +** This is a no-op if g.userUid is 0.
          336  +*/
          337  +void login_clear_login_data(){
          338  +  if(!g.userUid){
          339  +    return;
          340  +  }else{
          341  +    char const * cookie = login_cookie_name(); 
          342  +    /* To logout, change the cookie value to an empty string */
          343  +    cgi_set_cookie(cookie, "",
          344  +                   login_cookie_path(), -86400);
          345  +    db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, "
          346  +                  "  cexpire=0 WHERE uid=%d"
          347  +                  "  AND login NOT IN ('anonymous','nobody',"
          348  +                  "  'developer','reader')", g.userUid);
          349  +    cgi_replace_parameter(cookie, NULL)
          350  +      /* At the time of this writing, cgi_replace_parameter() was
          351  +      ** "NULL-value-safe", and i'm hoping the NULL doesn't cause any
          352  +      ** downstream problems here. We could alternately use "" here.
          353  +      */
          354  +      ;
          355  +  }
          356  +}
   202    357   
   203    358   /*
   204    359   ** Look at the HTTP_USER_AGENT parameter and try to determine if the user agent
   205    360   ** is a manually operated browser or a bot.  When in doubt, assume a bot.  Return
   206    361   ** true if we believe the agent is a real person.
   207    362   */
   208    363   static int isHuman(const char *zAgent){
................................................................................
   279    434     const char *zNew1, *zNew2;
   280    435     const char *zAnonPw = 0;
   281    436     int anonFlag;
   282    437     char *zErrMsg = "";
   283    438     int uid;                     /* User id loged in user */
   284    439     char *zSha1Pw;
   285    440     const char *zIpAddr;         /* IP address of requestor */
   286         -  char *zRemoteAddr;           /* Abbreviated IP address of requestor */
   287    441   
   288    442     login_check_credentials();
   289    443     sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0,
   290    444   		  constant_time_cmp_function, 0, 0);
   291    445     zUsername = P("u");
   292    446     zPasswd = P("p");
   293    447     anonFlag = P("anon")!=0;
   294    448     if( P("out")!=0 ){
   295         -    /* To logout, change the cookie value to an empty string */
   296         -    const char *zCookieName = login_cookie_name();
   297         -    cgi_set_cookie(zCookieName, "", login_cookie_path(), -86400);
          449  +    login_clear_login_data();
   298    450       redirect_to_g();
   299    451     }
   300    452     if( g.perm.Password && zPasswd
   301    453      && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0
   302    454     ){
   303    455       /* The user requests a password change */
   304    456       zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0);
................................................................................
   342    494         }else{
   343    495           redirect_to_g();
   344    496           return;
   345    497         }
   346    498       }
   347    499     }
   348    500     zIpAddr = PD("REMOTE_ADDR","nil");   /* Complete IP address for logging */
   349         -  zRemoteAddr = ipPrefix(zIpAddr);     /* Abbreviated IP address */
   350         -  uid = isValidAnonymousLogin(zUsername, zPasswd);
          501  +  uid = login_is_valid_anonymous(zUsername, zPasswd, P("cs"));
   351    502     if( uid>0 ){
   352         -    /* Successful login as anonymous.  Set a cookie that looks like
   353         -    ** this:
   354         -    **
   355         -    **    HASH/TIME/anonymous
   356         -    **
   357         -    ** Where HASH is the sha1sum of TIME/IPADDR/SECRET, in which IPADDR
   358         -    ** is the abbreviated IP address and SECRET is captcha-secret.
   359         -    */
   360         -    char *zNow;                  /* Current time (julian day number) */
   361         -    char *zCookie;               /* The login cookie */
   362         -    const char *zCookieName;     /* Name of the login cookie */
   363         -    Blob b;                      /* Blob used during cookie construction */
   364         -
   365         -    zCookieName = login_cookie_name();
   366         -    zNow = db_text("0", "SELECT julianday('now')");
   367         -    blob_init(&b, zNow, -1);
   368         -    blob_appendf(&b, "/%s/%s", zRemoteAddr, db_get("captcha-secret",""));
   369         -    sha1sum_blob(&b, &b);
   370         -    zCookie = sqlite3_mprintf("%s/%s/anonymous", blob_buffer(&b), zNow);
   371         -    blob_reset(&b);
   372         -    free(zNow);
   373         -    cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), 6*3600);
          503  +    login_set_anon_cookie(zIpAddr, NULL);
   374    504       record_login_attempt("anonymous", zIpAddr, 1);
   375    505       redirect_to_g();
   376    506     }
   377    507     if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){
   378    508       /* Attempting to log in as a user other than anonymous.
   379    509       */
   380         -    zSha1Pw = sha1_shared_secret(zPasswd, zUsername, 0);
   381         -    uid = db_int(0,
   382         -        "SELECT uid FROM user"
   383         -        " WHERE login=%Q"
   384         -        "   AND length(cap)>0 AND length(pw)>0"
   385         -        "   AND login NOT IN ('anonymous','nobody','developer','reader')"
   386         -        "   AND (constant_time_cmp(pw,%Q)=0 OR constant_time_cmp(pw,%Q)=0)",
   387         -        zUsername, zSha1Pw, zPasswd
   388         -    );
          510  +    uid = login_search_uid(zUsername, zPasswd);
   389    511       if( uid<=0 ){
   390    512         sleep(1);
   391    513         zErrMsg = 
   392    514            @ <p><span class="loginError">
   393    515            @ You entered an unknown user or an incorrect password.
   394    516            @ </span></p>
   395    517         ;
................................................................................
   398    520         /* Non-anonymous login is successful.  Set a cookie of the form:
   399    521         **
   400    522         **    HASH/PROJECT/LOGIN
   401    523         **
   402    524         ** where HASH is a random hex number, PROJECT is either project
   403    525         ** code prefix, and LOGIN is the user name.
   404    526         */
   405         -      char *zCookie;
   406         -      const char *zCookieName = login_cookie_name();
   407         -      const char *zExpire = db_get("cookie-expire","8766");
   408         -      int expires = atoi(zExpire)*3600;
   409         -      char *zCode = abbreviated_project_code(db_get("project-code",""));
   410         -      char *zHash;
   411         -  
   412         -      zHash = db_text(0, "SELECT hex(randomblob(25))");
   413         -      zCookie = mprintf("%s/%s/%s", zHash, zCode, zUsername);
   414         -      cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires);
   415         -      record_login_attempt(zUsername, zIpAddr, 1);
   416         -      db_multi_exec(
   417         -        "UPDATE user SET cookie=%Q, ipaddr=%Q, "
   418         -        "  cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
   419         -        zHash, zRemoteAddr, expires, uid
   420         -      );
          527  +      login_set_user_cookie(zUsername, uid, NULL);
   421    528         redirect_to_g();
   422    529       }
   423    530     }
   424    531     style_header("Login/Logout");
   425    532     @ %s(zErrMsg)
   426    533     @ <form action="login" method="post">
   427    534     if( P("g") ){
................................................................................
   584    691     }
   585    692     sqlite3_close(pOther);
   586    693     fossil_free(zOtherRepo);
   587    694     return nXfer;
   588    695   }
   589    696   
   590    697   /*
   591         -** Lookup the uid for a user with zLogin and zCookie and zRemoteAddr.
   592         -** Return 0 if not found.
          698  +** Lookup the uid for a non-built-in user with zLogin and zCookie and
          699  +** zRemoteAddr.  Return 0 if not found.
          700  +**
          701  +** Note that this only searches for logged-in entries with matching
          702  +** zCookie (db: user.cookie) and zRemoteAddr (db: user.ipaddr)
          703  +** entries.
   593    704   */
   594    705   static int login_find_user(
   595    706     const char *zLogin,            /* User name */
   596    707     const char *zCookie,           /* Login cookie value */
   597    708     const char *zRemoteAddr        /* Abbreviated IP address for valid login */
   598    709   ){
   599    710     int uid;
................................................................................
   611    722       "   AND constant_time_cmp(cookie,%Q)=0",
   612    723       zLogin, zRemoteAddr, zCookie
   613    724     );
   614    725     return uid;
   615    726   }
   616    727   
   617    728   /*
   618         -** This routine examines the login cookie to see if it exists and
   619         -** and is valid.  If the login cookie checks out, it then sets 
   620         -** global variables appropriately.  Global variables set include
   621         -** g.userUid and g.zLogin and of the g.perm.Read family of permission
   622         -** booleans.
   623         -**
          729  +** This routine examines the login cookie to see if it exists and and
          730  +** is valid.  If the login cookie checks out, it then sets global
          731  +** variables appropriately.  Global variables set include g.userUid
          732  +** and g.zLogin and the g.perm family of permission booleans.
   624    733   */
   625    734   void login_check_credentials(void){
   626    735     int uid = 0;                  /* User id */
   627    736     const char *zCookie;          /* Text of the login cookie */
   628    737     const char *zIpAddr;          /* Raw IP address of the requestor */
   629    738     char *zRemoteAddr;            /* Abbreviated IP address of the requestor */
   630    739     const char *zCap = 0;         /* Capability string */
................................................................................
   708    817           if( uid ) record_login_attempt(zUser, zIpAddr, 1);
   709    818         }
   710    819       }
   711    820       sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "%.10s", zHash);
   712    821     }
   713    822   
   714    823     /* If no user found and the REMOTE_USER environment variable is set,
   715         -  ** the accept the value of REMOTE_USER as the user.
          824  +  ** then accept the value of REMOTE_USER as the user.
   716    825     */
   717    826     if( uid==0 ){
   718    827       const char *zRemoteUser = P("REMOTE_USER");
   719    828       if( zRemoteUser && db_get_boolean("remote_user_ok",0) ){
   720    829         uid = db_int(0, "SELECT uid FROM user WHERE login=%Q"
   721    830                         " AND length(cap)>0 AND length(pw)>0", zRemoteUser);
   722    831       }
................................................................................
   758    867     */
   759    868     g.userUid = uid;
   760    869     if( fossil_strcmp(g.zLogin,"nobody")==0 ){
   761    870       g.zLogin = 0;
   762    871     }
   763    872   
   764    873     /* Set the capabilities */
   765         -  login_set_capabilities(zCap, 0);
          874  +  login_replace_capabilities(zCap, 0);
   766    875     login_set_anon_nobody_capabilities();
   767    876     if( zCap[0] && !g.perm.History && db_get_boolean("auto-enable-hyperlinks",1)
   768    877         && isHuman(P("HTTP_USER_AGENT")) ){
   769    878       g.perm.History = 1;
   770    879     }
   771    880   }
   772    881   
................................................................................
   791    900         login_set_capabilities(zCap, 0);
   792    901       }
   793    902       login_anon_once = 0;
   794    903     }
   795    904   }
   796    905   
   797    906   /*
   798         -** Flags passed into the 2nd argument of login_set_capabilities().
          907  +** Flags passed into the 2nd argument of login_set/replace_capabilities().
   799    908   */
   800    909   #if INTERFACE
   801    910   #define LOGIN_IGNORE_U   0x01         /* Ignore "u" */
   802    911   #define LOGIN_IGNORE_V   0x01         /* Ignore "v" */
   803    912   #endif
   804    913   
   805    914   /*
   806         -** Set the global capability flags based on a capability string.
          915  +** Adds all capability flags in zCap to g.perm.
   807    916   */
   808    917   void login_set_capabilities(const char *zCap, unsigned flags){
   809    918     int i;
          919  +  if(NULL==zCap){
          920  +    return;
          921  +  }
   810    922     for(i=0; zCap[i]; i++){
   811    923       switch( zCap[i] ){
   812    924         case 's':   g.perm.Setup = 1;  /* Fall thru into Admin */
   813    925         case 'a':   g.perm.Admin = g.perm.RdTkt = g.perm.WrTkt = g.perm.Zip =
   814    926                                 g.perm.RdWiki = g.perm.WrWiki = g.perm.NewWiki =
   815    927                                 g.perm.ApndWiki = g.perm.History = g.perm.Clone = 
   816    928                                 g.perm.NewTkt = g.perm.Password = g.perm.RdAddr =
................................................................................
   860    972             login_set_capabilities(zDev, flags | LOGIN_IGNORE_V);
   861    973           }
   862    974           break;
   863    975         }
   864    976       }
   865    977     }
   866    978   }
          979  +
          980  +/*
          981  +** Zeroes out g.perm and calls login_set_capabilities(zCap,flags).
          982  +*/
          983  +void login_replace_capabilities(const char *zCap, unsigned flags){
          984  +  memset(&g.perm, 0, sizeof(g.perm));
          985  +  return login_set_capabilities(zCap, flags);
          986  +}
   867    987   
   868    988   /*
   869    989   ** If the current login lacks any of the capabilities listed in
   870    990   ** the input, then return 0.  If all capabilities are present, then
   871    991   ** return 1.
   872    992   */
   873    993   int login_has_capability(const char *zCap, int nCap){
................................................................................
   938   1058   }
   939   1059   
   940   1060   /*
   941   1061   ** Call this routine when the credential check fails.  It causes
   942   1062   ** a redirect to the "login" page.
   943   1063   */
   944   1064   void login_needed(void){
   945         -  const char *zUrl = PD("REQUEST_URI", "index");
   946         -  cgi_redirect(mprintf("login?g=%T", zUrl));
   947         -  /* NOTREACHED */
   948         -  assert(0);
         1065  +#ifdef FOSSIL_ENABLE_JSON
         1066  +  if(g.json.isJsonMode){
         1067  +    json_err( FSL_JSON_E_DENIED, NULL, 1 );
         1068  +    fossil_exit(0);
         1069  +    /* NOTREACHED */
         1070  +    assert(0);
         1071  +  }else
         1072  +#endif /* FOSSIL_ENABLE_JSON */
         1073  +  {
         1074  +    const char *zUrl = PD("REQUEST_URI", "index");
         1075  +    cgi_redirect(mprintf("login?g=%T", zUrl));
         1076  +    /* NOTREACHED */
         1077  +    assert(0);
         1078  +  }
   949   1079   }
   950   1080   
   951   1081   /*
   952   1082   ** Call this routine if the user lacks okHistory permission.  If
   953   1083   ** the anonymous user has okHistory permission, then paint a mesage
   954   1084   ** to inform the user that much more information is available by
   955   1085   ** logging in as anonymous.

Changes to src/main.c.

    21     21   #include "config.h"
    22     22   #include "main.h"
    23     23   #include <string.h>
    24     24   #include <time.h>
    25     25   #include <fcntl.h>
    26     26   #include <sys/types.h>
    27     27   #include <sys/stat.h>
    28         -
           28  +#include <stdlib.h> /* atexit() */
    29     29   
    30     30   #if INTERFACE
    31         -
           31  +#ifdef FOSSIL_ENABLE_JSON
           32  +#  include "cson_amalgamation.h" /* JSON API. Needed inside the INTERFACE block! */
           33  +#  include "json_detail.h"
           34  +#endif
    32     35   /*
    33     36   ** Number of elements in an array
    34     37   */
    35     38   #define count(X)  (sizeof(X)/sizeof(X[0]))
    36     39   
    37     40   /*
    38     41   ** Size of a UUID in characters
................................................................................
   113    116     FILE *httpIn;           /* Accept HTTP input from here */
   114    117     FILE *httpOut;          /* Send HTTP output here */
   115    118     int xlinkClusterOnly;   /* Set when cloning.  Only process clusters */
   116    119     int fTimeFormat;        /* 1 for UTC.  2 for localtime.  0 not yet selected */
   117    120     int *aCommitFile;       /* Array of files to be committed */
   118    121     int markPrivate;        /* All new artifacts are private if true */
   119    122     int clockSkewSeen;      /* True if clocks on client and server out of sync */
          123  +  int isHTTP;             /* True if running in server/CGI modes, else assume CLI. */
   120    124   
   121    125     int urlIsFile;          /* True if a "file:" url */
   122    126     int urlIsHttps;         /* True if a "https:" url */
   123    127     int urlIsSsh;           /* True if an "ssh:" url */
   124    128     char *urlName;          /* Hostname for http: or filename for file: */
   125    129     char *urlHostname;      /* The HOST: parameter on http headers */
   126    130     char *urlProtocol;      /* "http" or "https" */
................................................................................
   129    133     char *urlPath;          /* Pathname for http: */
   130    134     char *urlUser;          /* User id for http: */
   131    135     char *urlPasswd;        /* Password for http: */
   132    136     char *urlCanonical;     /* Canonical representation of the URL */
   133    137     char *urlProxyAuth;     /* Proxy-Authorizer: string */
   134    138     char *urlFossil;        /* The path of the ?fossil=path suffix on ssh: */
   135    139     int dontKeepUrl;        /* Do not persist the URL */
   136         -
          140  +  
   137    141     const char *zLogin;     /* Login name.  "" if not logged in. */
   138    142     const char *zSSLIdentity;  /* Value of --ssl-identity option, filename of SSL client identity */
   139    143     int useLocalauth;       /* No login required if from 127.0.0.1 */
   140    144     int noPswd;             /* Logged in without password (on 127.0.0.1) */
   141    145     int userUid;            /* Integer user id */
   142    146   
   143    147     /* Information used to populate the RCVFROM table */
................................................................................
   164    168     const char *azAuxName[MX_AUX]; /* Name of each aux() or option() value */
   165    169     char *azAuxParam[MX_AUX];      /* Param of each aux() or option() value */
   166    170     const char *azAuxVal[MX_AUX];  /* Value of each aux() or option() value */
   167    171     const char **azAuxOpt[MX_AUX]; /* Options of each option() value */
   168    172     int anAuxCols[MX_AUX];         /* Number of columns for option() values */
   169    173     
   170    174     int allowSymlinks;             /* Cached "allow-symlinks" option */
          175  +
          176  +#ifdef FOSSIL_ENABLE_JSON
          177  +  struct FossilJsonBits {
          178  +    int isJsonMode;            /* True if running in JSON mode, else
          179  +                                  false. This changes how errors are
          180  +                                  reported. In JSON mode we try to
          181  +                                  always output JSON-form error
          182  +                                  responses and always exit() with
          183  +                                  code 0 to avoid an HTTP 500 error.
          184  +                               */
          185  +    int resultCode;            /* used for passing back specific codes from /json callbacks. */
          186  +    int errorDetailParanoia;   /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */
          187  +    cson_output_opt outOpt;    /* formatting options for JSON mode. */
          188  +    cson_value * authToken;    /* authentication token */
          189  +    char const * jsonp;        /* Name of JSONP function wrapper. */
          190  +    unsigned char dispatchDepth /* Tells JSON command dispatching
          191  +                                   which argument we are currently
          192  +                                   working on. For this purpose, arg#0
          193  +                                   is the "json" path/CLI arg.
          194  +                                */;
          195  +    struct {                   /* "garbage collector" */
          196  +      cson_value * v;
          197  +      cson_array * a;
          198  +    } gc;
          199  +    struct {                   /* JSON POST data. */
          200  +      cson_value * v;
          201  +      cson_array * a;
          202  +      int offset;              /* Tells us which PATH_INFO/CLI args
          203  +                                  part holds the "json" command, so
          204  +                                  that we can account for sub-repos
          205  +                                  and path prefixes.  This is handled
          206  +                                  differently for CLI and CGI modes.
          207  +                               */
          208  +      char const * commandStr  /*"command" request param.*/;
          209  +    } cmd;
          210  +    struct {                   /* JSON POST data. */
          211  +      cson_value * v;
          212  +      cson_object * o;
          213  +    } post;
          214  +    struct {                   /* GET/COOKIE params in JSON mode.
          215  +                                  FIXME (stephan): verify that this is
          216  +                                  still used and remove if it is not.
          217  +                               */
          218  +      cson_value * v;
          219  +      cson_object * o;
          220  +    } param;
          221  +    struct {
          222  +      cson_value * v;
          223  +      cson_object * o;
          224  +    } reqPayload;              /* request payload object (if any) */
          225  +    struct {                   /* response warnings */
          226  +      cson_value * v;
          227  +      cson_array * a;
          228  +    } warnings;
          229  +  } json;
          230  +#endif /* FOSSIL_ENABLE_JSON */
   171    231   };
   172    232   
   173    233   /*
   174    234   ** Macro for debugging:
   175    235   */
   176    236   #define CGIDEBUG(X)  if( g.fDebug ) cgi_debug X
   177    237   
................................................................................
   228    288     }
   229    289     if( cnt==1 ){
   230    290       *pIndex = m;
   231    291       return 0;
   232    292     }
   233    293     return 1+(cnt>1);
   234    294   }
          295  +
          296  +/*
          297  +** atexit() handler which frees up "some" of the resources
          298  +** used by fossil.
          299  +*/
          300  +void fossil_atexit() {
          301  +#ifdef FOSSIL_ENABLE_JSON
          302  +  cson_value_free(g.json.gc.v);
          303  +  memset(&g.json, 0, sizeof(g.json));
          304  +#endif
          305  +  free(g.zErrMsg);
          306  +  if(g.db){
          307  +    db_close(0);
          308  +  }
          309  +}
   235    310   
   236    311   /*
   237    312   ** Search g.argv for arguments "--args FILENAME".  If found, then
   238    313   ** (1) remove the two arguments from g.argv
   239    314   ** (2) Read the file FILENAME
   240    315   ** (3) Use the contents of FILE to replace the two removed arguments:
   241    316   **     (a) Ignore blank lines in the file
................................................................................
   313    388   int main(int argc, char **argv){
   314    389     const char *zCmdName = "unknown";
   315    390     int idx;
   316    391     int rc;
   317    392     int i;
   318    393   
   319    394     sqlite3_config(SQLITE_CONFIG_LOG, fossil_sqlite_log, 0);
          395  +  memset(&g, 0, sizeof(g));
   320    396     g.now = time(0);
   321    397     g.argc = argc;
   322    398     g.argv = argv;
          399  +#ifdef FOSSIL_ENABLE_JSON
          400  +#if defined(NDEBUG)
          401  +  g.json.errorDetailParanoia = 2 /* FIXME: make configurable
          402  +                                    One problem we have here is that this
          403  +                                    code is needed before the db is opened,
          404  +                                    so we can't sql for it.*/;
          405  +#else
          406  +  g.json.errorDetailParanoia = 0;
          407  +#endif
          408  +  g.json.outOpt = cson_output_opt_empty;
          409  +  g.json.outOpt.addNewline = 1;
          410  +  g.json.outOpt.indentation = 1 /* in CGI/server mode this can be configured */;
          411  +#endif /* FOSSIL_ENABLE_JSON */
   323    412     expand_args_option();
   324    413     argc = g.argc;
   325    414     argv = g.argv;
   326    415     for(i=0; i<argc; i++) g.argv[i] = fossil_mbcs_to_utf8(argv[i]);
   327    416     if( getenv("GATEWAY_INTERFACE")!=0 && !find_option("nocgi", 0, 0)){
   328    417       zCmdName = "cgi";
          418  +    g.isHTTP = 1;
   329    419     }else if( argc<2 ){
   330    420       fossil_print(
   331    421          "Usage: %s COMMAND ...\n"
   332    422          "   or: %s help           -- for a list of common commands\n"
   333    423          "   or: %s help COMMMAND  -- for help with the named command\n"
   334    424          "   or: %s commands       -- for a list of all commands\n",
   335    425          argv[0], argv[0], argv[0], argv[0]);
   336    426       fossil_exit(1);
   337    427     }else{
          428  +    g.isHTTP = 0;
   338    429       g.fQuiet = find_option("quiet", 0, 0)!=0;
   339    430       g.fSqlTrace = find_option("sqltrace", 0, 0)!=0;
   340    431       g.fSqlStats = find_option("sqlstats", 0, 0)!=0;
   341    432       g.fSystemTrace = find_option("systemtrace", 0, 0)!=0;
   342    433       if( g.fSqlTrace ) g.fSqlStats = 1;
   343    434       g.fSqlPrint = find_option("sqlprint", 0, 0)!=0;
   344    435       g.fHttpTrace = find_option("httptrace", 0, 0)!=0;
................................................................................
   375    466       }
   376    467       fossil_print("%s: ambiguous command prefix: %s\n"
   377    468                    "%s: could be any of:%s\n"
   378    469                    "%s: use \"help\" for more information\n",
   379    470                    argv[0], zCmdName, argv[0], blob_str(&couldbe), argv[0]);
   380    471       fossil_exit(1);
   381    472     }
          473  +  atexit( fossil_atexit );
   382    474     aCommand[idx].xFunc();
   383    475     fossil_exit(0);
   384    476     /*NOT_REACHED*/
   385    477     return 0;
   386    478   }
   387    479   
   388    480   /*
................................................................................
   414    506   /*
   415    507   ** Print an error message, rollback all databases, and quit.  These
   416    508   ** routines never return.
   417    509   */
   418    510   NORETURN void fossil_panic(const char *zFormat, ...){
   419    511     char *z;
   420    512     va_list ap;
          513  +  int rc = 1;
   421    514     static int once = 1;
   422    515     mainInFatalError = 1;
   423    516     va_start(ap, zFormat);
   424    517     z = vmprintf(zFormat, ap);
   425    518     va_end(ap);
   426         -  if( g.cgiOutput && once ){
   427         -    once = 0;
   428         -    cgi_printf("<p class=\"generalError\">%h</p>", z);
   429         -    cgi_reply();
   430         -  }else{
   431         -    char *zOut = mprintf("%s: %s\n", fossil_nameofexe(), z);
   432         -    fossil_puts(zOut, 1);
          519  +#ifdef FOSSIL_ENABLE_JSON
          520  +  if( g.json.isJsonMode ){
          521  +    json_err( 0, z, 1 );
          522  +    if( g.isHTTP ){
          523  +      rc = 0 /* avoid HTTP 500 */;
          524  +    }
   433    525     }
          526  +  else
          527  +#endif
          528  +  {
          529  +    if( g.cgiOutput && once ){
          530  +      once = 0;
          531  +      cgi_printf("<p class=\"generalError\">%h</p>", z);
          532  +      cgi_reply();
          533  +    }else{
          534  +      char *zOut = mprintf("%s: %s\n", fossil_nameofexe(), z);
          535  +      fossil_puts(zOut, 1);
          536  +    }
          537  +  }
          538  +  free(z);
   434    539     db_force_rollback();
   435         -  fossil_exit(1);
          540  +  fossil_exit(rc);
   436    541   }
          542  +
   437    543   NORETURN void fossil_fatal(const char *zFormat, ...){
   438    544     char *z;
          545  +  int rc = 1;
   439    546     va_list ap;
   440    547     mainInFatalError = 1;
   441    548     va_start(ap, zFormat);
   442    549     z = vmprintf(zFormat, ap);
   443    550     va_end(ap);
   444         -  if( g.cgiOutput ){
   445         -    g.cgiOutput = 0;
   446         -    cgi_printf("<p class=\"generalError\">%h</p>", z);
   447         -    cgi_reply();
   448         -  }else{
   449         -    char *zOut = mprintf("\r%s: %s\n", fossil_nameofexe(), z);
   450         -    fossil_puts(zOut, 1);
          551  +#ifdef FOSSIL_ENABLE_JSON
          552  +  if( g.json.isJsonMode ){
          553  +    json_err( g.json.resultCode, z, 1 );
          554  +    if( g.isHTTP ){
          555  +      rc = 0 /* avoid HTTP 500 */;
          556  +    }
   451    557     }
          558  +  else
          559  +#endif
          560  +  {
          561  +    if( g.cgiOutput ){
          562  +      g.cgiOutput = 0;
          563  +      cgi_printf("<p class=\"generalError\">%h</p>", z);
          564  +      cgi_reply();
          565  +    }else{
          566  +      char *zOut = mprintf("\r%s: %s\n", fossil_nameofexe(), z);
          567  +      fossil_puts(zOut, 1);
          568  +    }
          569  +  }
          570  +  free(z);
   452    571     db_force_rollback();
   453         -  fossil_exit(1);
          572  +  fossil_exit(rc);
   454    573   }
   455    574   
   456    575   /* This routine works like fossil_fatal() except that if called
   457    576   ** recursively, the recursive call is a no-op.
   458    577   **
   459    578   ** Use this in places where an error might occur while doing
   460    579   ** fatal error shutdown processing.  Unlike fossil_panic() and
................................................................................
   461    580   ** fossil_fatal() which never return, this routine might return if
   462    581   ** the fatal error handing is already in process.  The caller must
   463    582   ** be prepared for this routine to return.
   464    583   */
   465    584   void fossil_fatal_recursive(const char *zFormat, ...){
   466    585     char *z;
   467    586     va_list ap;
          587  +  int rc = 1;
   468    588     if( mainInFatalError ) return;
   469    589     mainInFatalError = 1;
   470    590     va_start(ap, zFormat);
   471    591     z = vmprintf(zFormat, ap);
   472    592     va_end(ap);
   473         -  if( g.cgiOutput ){
   474         -    g.cgiOutput = 0;
   475         -    cgi_printf("<p class=\"generalError\">%h</p>", z);
   476         -    cgi_reply();
   477         -  }else{
   478         -    char *zOut = mprintf("\r%s: %s\n", fossil_nameofexe(), z);
   479         -    fossil_puts(zOut, 1);
          593  +#ifdef FOSSIL_ENABLE_JSON
          594  +  if( g.json.isJsonMode ){
          595  +    json_err( g.json.resultCode, z, 1 );
          596  +    if( g.isHTTP ){
          597  +      rc = 0 /* avoid HTTP 500 */;
          598  +    }
          599  +  } else
          600  +#endif
          601  +  {
          602  +    if( g.cgiOutput ){
          603  +      g.cgiOutput = 0;
          604  +      cgi_printf("<p class=\"generalError\">%h</p>", z);
          605  +      cgi_reply();
          606  +    }else{
          607  +      char *zOut = mprintf("\r%s: %s\n", fossil_nameofexe(), z);
          608  +      fossil_puts(zOut, 1);
          609  +      free(zOut);
          610  +    }
   480    611     }
   481    612     db_force_rollback();
   482         -  fossil_exit(1);
          613  +  fossil_exit(rc);
   483    614   }
   484    615   
   485    616   
   486    617   /* Print a warning message */
   487    618   void fossil_warning(const char *zFormat, ...){
   488    619     char *z;
   489    620     va_list ap;
   490    621     va_start(ap, zFormat);
   491    622     z = vmprintf(zFormat, ap);
   492    623     va_end(ap);
   493         -  if( g.cgiOutput ){
   494         -    cgi_printf("<p class=\"generalError\">%h</p>", z);
   495         -  }else{
   496         -    char *zOut = mprintf("\r%s: %s\n", fossil_nameofexe(), z);
   497         -    fossil_puts(zOut, 1);
   498         -    free(zOut);
          624  +#ifdef FOSSIL_ENABLE_JSON
          625  +  if(g.json.isJsonMode){
          626  +    json_warn( FSL_JSON_W_UNKNOWN, z );
          627  +  }else
          628  +#endif
          629  +  {
          630  +    if( g.cgiOutput ){
          631  +      cgi_printf("<p class=\"generalError\">%h</p>", z);
          632  +    }else{
          633  +      char *zOut = mprintf("\r%s: %s\n", fossil_nameofexe(), z);
          634  +      fossil_puts(zOut, 1);
          635  +      free(zOut);
          636  +    }
   499    637     }
          638  +  free(z);
   500    639   }
   501    640   
   502    641   /*
   503    642   ** Malloc and free routines that cannot fail
   504    643   */
   505    644   void *fossil_malloc(size_t n){
   506    645     void *p = malloc(n==0 ? 1 : n);
................................................................................
  1035   1174           zRepo[j] = '.';
  1036   1175         }
  1037   1176   
  1038   1177         if( szFile<1024 ){
  1039   1178           if( zNotFound ){
  1040   1179             cgi_redirect(zNotFound);
  1041   1180           }else{
         1181  +#ifdef FOSSIL_ENABLE_JSON
         1182  +          if(g.json.isJsonMode){
         1183  +            json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
         1184  +            return;
         1185  +          }
         1186  +#endif
  1042   1187             @ <h1>Not Found</h1>
  1043   1188             cgi_set_status(404, "not found");
  1044   1189             cgi_reply();
  1045   1190           }
  1046   1191           return;
  1047   1192         }
  1048   1193         break;
................................................................................
  1066   1211     */
  1067   1212     if( g.zContentType && memcmp(g.zContentType, "application/x-fossil", 20)==0 ){
  1068   1213       zPathInfo = "/xfer";
  1069   1214     }
  1070   1215     set_base_url();
  1071   1216     if( zPathInfo==0 || zPathInfo[0]==0 
  1072   1217         || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
  1073         -    fossil_redirect_home();
         1218  +#ifdef FOSSIL_ENABLE_JSON
         1219  +    if(g.json.isJsonMode){
         1220  +      json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
         1221  +      fossil_exit(0);
         1222  +    }
         1223  +#endif
         1224  +    fossil_redirect_home() /*does not return*/;
  1074   1225     }else{
  1075   1226       zPath = mprintf("%s", zPathInfo);
  1076   1227     }
  1077   1228   
  1078   1229     /* Make g.zPath point to the first element of the path.  Make
  1079   1230     ** g.zExtra point to everything past that point.
  1080   1231     */
................................................................................
  1127   1278         g.zExtra = 0;
  1128   1279       }
  1129   1280       break;
  1130   1281     }
  1131   1282     if( g.zExtra ){
  1132   1283       /* CGI parameters get this treatment elsewhere, but places like getfile
  1133   1284       ** will use g.zExtra directly.
         1285  +    ** Reminder: the login mechanism uses 'name' differently, and may
         1286  +    ** eventually have a problem/collision with this.
  1134   1287       */
  1135   1288       dehttpize(g.zExtra);
  1136   1289       cgi_set_parameter_nocopy("name", g.zExtra);
  1137   1290     }
  1138   1291   
  1139   1292     /* Locate the method specified by the path and execute the function
  1140   1293     ** that implements that method.
  1141   1294     */
  1142   1295     if( name_search(g.zPath, aWebpage, count(aWebpage), &idx) &&
  1143   1296         name_search("not_found", aWebpage, count(aWebpage), &idx) ){
  1144         -    cgi_set_status(404,"Not Found");
  1145         -    @ <h1>Not Found</h1>
  1146         -    @ <p>Page not found: %h(g.zPath)</p>
         1297  +#ifdef FOSSIL_ENABLE_JSON
         1298  +    if(g.json.isJsonMode){
         1299  +      json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,0);
         1300  +    }else
         1301  +#endif
         1302  +    {
         1303  +      cgi_set_status(404,"Not Found");
         1304  +      @ <h1>Not Found</h1>
         1305  +      @ <p>Page not found: %h(g.zPath)</p>
         1306  +    }
  1147   1307     }else if( aWebpage[idx].xFunc!=page_xfer && db_schema_is_outofdate() ){
  1148         -    @ <h1>Server Configuration Error</h1>
  1149         -    @ <p>The database schema on the server is out-of-date.  Please ask
  1150         -    @ the administrator to run <b>fossil rebuild</b>.</p>
         1308  +#ifdef FOSSIL_ENABLE_JSON
         1309  +    if(g.json.isJsonMode){
         1310  +      json_err(FSL_JSON_E_DB_NEEDS_REBUILD,NULL,0);
         1311  +    }else
         1312  +#endif
         1313  +    {
         1314  +      @ <h1>Server Configuration Error</h1>
         1315  +      @ <p>The database schema on the server is out-of-date.  Please ask
         1316  +      @ the administrator to run <b>fossil rebuild</b>.</p>
         1317  +    }
  1151   1318     }else{
  1152   1319       aWebpage[idx].xFunc();
  1153   1320     }
  1154   1321   
  1155   1322     /* Return the result.
  1156   1323     */
  1157   1324     cgi_reply();

Changes to src/main.mk.

    45     45     $(SRCDIR)/gzip.c \
    46     46     $(SRCDIR)/http.c \
    47     47     $(SRCDIR)/http_socket.c \
    48     48     $(SRCDIR)/http_ssl.c \
    49     49     $(SRCDIR)/http_transport.c \
    50     50     $(SRCDIR)/import.c \
    51     51     $(SRCDIR)/info.c \
           52  +  $(SRCDIR)/json.c \
           53  +  $(SRCDIR)/json_artifact.c \
           54  +  $(SRCDIR)/json_branch.c \
           55  +  $(SRCDIR)/json_diff.c \
           56  +  $(SRCDIR)/json_login.c \
           57  +  $(SRCDIR)/json_query.c \
           58  +  $(SRCDIR)/json_report.c \
           59  +  $(SRCDIR)/json_tag.c \
           60  +  $(SRCDIR)/json_timeline.c \
           61  +  $(SRCDIR)/json_user.c \
           62  +  $(SRCDIR)/json_wiki.c \
    52     63     $(SRCDIR)/leaf.c \
    53     64     $(SRCDIR)/login.c \
    54     65     $(SRCDIR)/main.c \
    55     66     $(SRCDIR)/manifest.c \
    56     67     $(SRCDIR)/md5.c \
    57     68     $(SRCDIR)/merge.c \
    58     69     $(SRCDIR)/merge3.c \
................................................................................
   129    140     $(OBJDIR)/gzip_.c \
   130    141     $(OBJDIR)/http_.c \
   131    142     $(OBJDIR)/http_socket_.c \
   132    143     $(OBJDIR)/http_ssl_.c \
   133    144     $(OBJDIR)/http_transport_.c \
   134    145     $(OBJDIR)/import_.c \
   135    146     $(OBJDIR)/info_.c \
          147  +  $(OBJDIR)/json_.c \
          148  +  $(OBJDIR)/json_artifact_.c \
          149  +  $(OBJDIR)/json_branch_.c \
          150  +  $(OBJDIR)/json_diff_.c \
          151  +  $(OBJDIR)/json_login_.c \
          152  +  $(OBJDIR)/json_query_.c \
          153  +  $(OBJDIR)/json_report_.c \
          154  +  $(OBJDIR)/json_tag_.c \
          155  +  $(OBJDIR)/json_timeline_.c \
          156  +  $(OBJDIR)/json_user_.c \
          157  +  $(OBJDIR)/json_wiki_.c \
   136    158     $(OBJDIR)/leaf_.c \
   137    159     $(OBJDIR)/login_.c \
   138    160     $(OBJDIR)/main_.c \
   139    161     $(OBJDIR)/manifest_.c \
   140    162     $(OBJDIR)/md5_.c \
   141    163     $(OBJDIR)/merge_.c \
   142    164     $(OBJDIR)/merge3_.c \
................................................................................
   213    235    $(OBJDIR)/gzip.o \
   214    236    $(OBJDIR)/http.o \
   215    237    $(OBJDIR)/http_socket.o \
   216    238    $(OBJDIR)/http_ssl.o \
   217    239    $(OBJDIR)/http_transport.o \
   218    240    $(OBJDIR)/import.o \
   219    241    $(OBJDIR)/info.o \
          242  + $(OBJDIR)/json.o \
          243  + $(OBJDIR)/json_artifact.o \
          244  + $(OBJDIR)/json_branch.o \
          245  + $(OBJDIR)/json_diff.o \
          246  + $(OBJDIR)/json_login.o \
          247  + $(OBJDIR)/json_query.o \
          248  + $(OBJDIR)/json_report.o \
          249  + $(OBJDIR)/json_tag.o \
          250  + $(OBJDIR)/json_timeline.o \
          251  + $(OBJDIR)/json_user.o \
          252  + $(OBJDIR)/json_wiki.o \
   220    253    $(OBJDIR)/leaf.o \
   221    254    $(OBJDIR)/login.o \
   222    255    $(OBJDIR)/main.o \
   223    256    $(OBJDIR)/manifest.o \
   224    257    $(OBJDIR)/md5.o \
   225    258    $(OBJDIR)/merge.o \
   226    259    $(OBJDIR)/merge3.o \
................................................................................
   299    332   # to 1. If it is set to 1, then there is no need to build or link
   300    333   # the sqlite3.o object. Instead, the system sqlite will be linked
   301    334   # using -lsqlite3.
   302    335   SQLITE3_OBJ.1 = 
   303    336   SQLITE3_OBJ.0 = $(OBJDIR)/sqlite3.o
   304    337   SQLITE3_OBJ.  = $(SQLITE3_OBJ.0)
   305    338   
   306         -EXTRAOBJ =  $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE))  $(OBJDIR)/shell.o  $(OBJDIR)/th.o  $(OBJDIR)/th_lang.o
          339  +EXTRAOBJ =  $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE))  $(OBJDIR)/shell.o  $(OBJDIR)/th.o  $(OBJDIR)/th_lang.o  $(OBJDIR)/cson_amalgamation.o
   307    340   
   308    341   $(APPNAME):	$(OBJDIR)/headers $(OBJ) $(EXTRAOBJ)
   309    342   	$(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB)
   310    343   
   311    344   # This rule prevents make from using its default rules to try build
   312    345   # an executable named "manifest" out of the file named "manifest.c"
   313    346   #
................................................................................
   317    350   clean:	
   318    351   	rm -rf $(OBJDIR)/* $(APPNAME)
   319    352   
   320    353   
   321    354   $(OBJDIR)/page_index.h: $(TRANS_SRC) $(OBJDIR)/mkindex
   322    355   	$(OBJDIR)/mkindex $(TRANS_SRC) >$@
   323    356   $(OBJDIR)/headers:	$(OBJDIR)/page_index.h $(OBJDIR)/makeheaders $(OBJDIR)/VERSION.h
   324         -	$(OBJDIR)/makeheaders  $(OBJDIR)/add_.c:$(OBJDIR)/add.h $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h $(OBJDIR)/browse_.c:$(OBJDIR)/browse.h $(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h $(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h $(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h $(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h $(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h $(OBJDIR)/clone_.c:$(OBJDIR)/clone.h $(OBJDIR)/comformat_.c:$(OBJDIR)/comformat.h $(OBJDIR)/configure_.c:$(OBJDIR)/configure.h $(OBJDIR)/content_.c:$(OBJDIR)/content.h $(OBJDIR)/db_.c:$(OBJDIR)/db.h $(OBJDIR)/delta_.c:$(OBJDIR)/delta.h $(OBJDIR)/deltacmd_.c:$(OBJDIR)/deltacmd.h $(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h $(OBJDIR)/diff_.c:$(OBJDIR)/diff.h $(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h $(OBJDIR)/event_.c:$(OBJDIR)/event.h $(OBJDIR)/export_.c:$(OBJDIR)/export.h $(OBJDIR)/file_.c:$(OBJDIR)/file.h $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h $(OBJDIR)/http_.c:$(OBJDIR)/http.h $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h $(OBJDIR)/import_.c:$(OBJDIR)/import.h $(OBJDIR)/info_.c:$(OBJDIR)/info.h $(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h $(OBJDIR)/login_.c:$(OBJDIR)/login.h $(OBJDIR)/main_.c:$(OBJDIR)/main.h $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h $(OBJDIR)/name_.c:$(OBJDIR)/name.h $(OBJDIR)/path_.c:$(OBJDIR)/path.h $(OBJDIR)/pivot_.c:$(OBJDIR)/pivot.h $(OBJDIR)/popen_.c:$(OBJDIR)/popen.h $(OBJDIR)/pqueue_.c:$(OBJDIR)/pqueue.h $(OBJDIR)/printf_.c:$(OBJDIR)/printf.h $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h $(OBJDIR)/report_.c:$(OBJDIR)/report.h $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h $(OBJDIR)/search_.c:$(OBJDIR)/search.h $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h $(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h $(OBJDIR)/style_.c:$(OBJDIR)/style.h $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h $(OBJDIR)/update_.c:$(OBJDIR)/update.h $(OBJDIR)/url_.c:$(OBJDIR)/url.h $(OBJDIR)/user_.c:$(OBJDIR)/user.h $(OBJDIR)/verify_.c:$(OBJDIR)/verify.h $(OBJDIR)/vfile_.c:$(OBJDIR)/vfile.h $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h $(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h $(OBJDIR)/zip_.c:$(OBJDIR)/zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h $(OBJDIR)/VERSION.h
          357  +	$(OBJDIR)/makeheaders  $(OBJDIR)/add_.c:$(OBJDIR)/add.h $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h $(OBJDIR)/browse_.c:$(OBJDIR)/browse.h $(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h $(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h $(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h $(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h $(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h $(OBJDIR)/clone_.c:$(OBJDIR)/clone.h $(OBJDIR)/comformat_.c:$(OBJDIR)/comformat.h $(OBJDIR)/configure_.c:$(OBJDIR)/configure.h $(OBJDIR)/content_.c:$(OBJDIR)/content.h $(OBJDIR)/db_.c:$(OBJDIR)/db.h $(OBJDIR)/delta_.c:$(OBJDIR)/delta.h $(OBJDIR)/deltacmd_.c:$(OBJDIR)/deltacmd.h $(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h $(OBJDIR)/diff_.c:$(OBJDIR)/diff.h $(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h $(OBJDIR)/event_.c:$(OBJDIR)/event.h $(OBJDIR)/export_.c:$(OBJDIR)/export.h $(OBJDIR)/file_.c:$(OBJDIR)/file.h $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h $(OBJDIR)/http_.c:$(OBJDIR)/http.h $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h $(OBJDIR)/import_.c:$(OBJDIR)/import.h $(OBJDIR)/info_.c:$(OBJDIR)/info.h $(OBJDIR)/json_.c:$(OBJDIR)/json.h $(OBJDIR)/json_artifact_.c:$(OBJDIR)/json_artifact.h $(OBJDIR)/json_branch_.c:$(OBJDIR)/json_branch.h $(OBJDIR)/json_diff_.c:$(OBJDIR)/json_diff.h $(OBJDIR)/json_login_.c:$(OBJDIR)/json_login.h $(OBJDIR)/json_query_.c:$(OBJDIR)/json_query.h $(OBJDIR)/json_report_.c:$(OBJDIR)/json_report.h $(OBJDIR)/json_tag_.c:$(OBJDIR)/json_tag.h $(OBJDIR)/json_timeline_.c:$(OBJDIR)/json_timeline.h $(OBJDIR)/json_user_.c:$(OBJDIR)/json_user.h $(OBJDIR)/json_wiki_.c:$(OBJDIR)/json_wiki.h $(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h $(OBJDIR)/login_.c:$(OBJDIR)/login.h $(OBJDIR)/main_.c:$(OBJDIR)/main.h $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h $(OBJDIR)/name_.c:$(OBJDIR)/name.h $(OBJDIR)/path_.c:$(OBJDIR)/path.h $(OBJDIR)/pivot_.c:$(OBJDIR)/pivot.h $(OBJDIR)/popen_.c:$(OBJDIR)/popen.h $(OBJDIR)/pqueue_.c:$(OBJDIR)/pqueue.h $(OBJDIR)/printf_.c:$(OBJDIR)/printf.h $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h $(OBJDIR)/report_.c:$(OBJDIR)/report.h $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h $(OBJDIR)/search_.c:$(OBJDIR)/search.h $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h $(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h $(OBJDIR)/style_.c:$(OBJDIR)/style.h $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h $(OBJDIR)/update_.c:$(OBJDIR)/update.h $(OBJDIR)/url_.c:$(OBJDIR)/url.h $(OBJDIR)/user_.c:$(OBJDIR)/user.h $(OBJDIR)/verify_.c:$(OBJDIR)/verify.h $(OBJDIR)/vfile_.c:$(OBJDIR)/vfile.h $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h $(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h $(OBJDIR)/zip_.c:$(OBJDIR)/zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h $(OBJDIR)/VERSION.h
   325    358   	touch $(OBJDIR)/headers
   326    359   $(OBJDIR)/headers: Makefile
          360  +$(OBJDIR)/json.o $(OBJDIR)/json_artifact.o $(OBJDIR)/json_branch.o $(OBJDIR)/json_diff.o $(OBJDIR)/json_login.o $(OBJDIR)/json_query.o $(OBJDIR)/json_report.o $(OBJDIR)/json_tag.o $(OBJDIR)/json_timeline.o $(OBJDIR)/json_user.o $(OBJDIR)/json_wiki.o : $(SRCDIR)/json_detail.h
   327    361   Makefile:
   328    362   $(OBJDIR)/add_.c:	$(SRCDIR)/add.c $(OBJDIR)/translate
   329    363   	$(OBJDIR)/translate $(SRCDIR)/add.c >$(OBJDIR)/add_.c
   330    364   
   331    365   $(OBJDIR)/add.o:	$(OBJDIR)/add_.c $(OBJDIR)/add.h  $(SRCDIR)/config.h
   332    366   	$(XTCC) -o $(OBJDIR)/add.o -c $(OBJDIR)/add_.c
   333    367   
................................................................................
   587    621   $(OBJDIR)/info_.c:	$(SRCDIR)/info.c $(OBJDIR)/translate
   588    622   	$(OBJDIR)/translate $(SRCDIR)/info.c >$(OBJDIR)/info_.c
   589    623   
   590    624   $(OBJDIR)/info.o:	$(OBJDIR)/info_.c $(OBJDIR)/info.h  $(SRCDIR)/config.h
   591    625   	$(XTCC) -o $(OBJDIR)/info.o -c $(OBJDIR)/info_.c
   592    626   
   593    627   $(OBJDIR)/info.h:	$(OBJDIR)/headers
          628  +$(OBJDIR)/json_.c:	$(SRCDIR)/json.c $(OBJDIR)/translate
          629  +	$(OBJDIR)/translate $(SRCDIR)/json.c >$(OBJDIR)/json_.c
          630  +
          631  +$(OBJDIR)/json.o:	$(OBJDIR)/json_.c $(OBJDIR)/json.h  $(SRCDIR)/config.h
          632  +	$(XTCC) -o $(OBJDIR)/json.o -c $(OBJDIR)/json_.c
          633  +
          634  +$(OBJDIR)/json.h:	$(OBJDIR)/headers
          635  +$(OBJDIR)/json_artifact_.c:	$(SRCDIR)/json_artifact.c $(OBJDIR)/translate
          636  +	$(OBJDIR)/translate $(SRCDIR)/json_artifact.c >$(OBJDIR)/json_artifact_.c
          637  +
          638  +$(OBJDIR)/json_artifact.o:	$(OBJDIR)/json_artifact_.c $(OBJDIR)/json_artifact.h  $(SRCDIR)/config.h
          639  +	$(XTCC) -o $(OBJDIR)/json_artifact.o -c $(OBJDIR)/json_artifact_.c
          640  +
          641  +$(OBJDIR)/json_artifact.h:	$(OBJDIR)/headers
          642  +$(OBJDIR)/json_branch_.c:	$(SRCDIR)/json_branch.c $(OBJDIR)/translate
          643  +	$(OBJDIR)/translate $(SRCDIR)/json_branch.c >$(OBJDIR)/json_branch_.c
          644  +
          645  +$(OBJDIR)/json_branch.o:	$(OBJDIR)/json_branch_.c $(OBJDIR)/json_branch.h  $(SRCDIR)/config.h
          646  +	$(XTCC) -o $(OBJDIR)/json_branch.o -c $(OBJDIR)/json_branch_.c
          647  +
          648  +$(OBJDIR)/json_branch.h:	$(OBJDIR)/headers
          649  +$(OBJDIR)/json_diff_.c:	$(SRCDIR)/json_diff.c $(OBJDIR)/translate
          650  +	$(OBJDIR)/translate $(SRCDIR)/json_diff.c >$(OBJDIR)/json_diff_.c
          651  +
          652  +$(OBJDIR)/json_diff.o:	$(OBJDIR)/json_diff_.c $(OBJDIR)/json_diff.h  $(SRCDIR)/config.h
          653  +	$(XTCC) -o $(OBJDIR)/json_diff.o -c $(OBJDIR)/json_diff_.c
          654  +
          655  +$(OBJDIR)/json_diff.h:	$(OBJDIR)/headers
          656  +$(OBJDIR)/json_login_.c:	$(SRCDIR)/json_login.c $(OBJDIR)/translate
          657  +	$(OBJDIR)/translate $(SRCDIR)/json_login.c >$(OBJDIR)/json_login_.c
          658  +
          659  +$(OBJDIR)/json_login.o:	$(OBJDIR)/json_login_.c $(OBJDIR)/json_login.h  $(SRCDIR)/config.h
          660  +	$(XTCC) -o $(OBJDIR)/json_login.o -c $(OBJDIR)/json_login_.c
          661  +
          662  +$(OBJDIR)/json_login.h:	$(OBJDIR)/headers
          663  +$(OBJDIR)/json_query_.c:	$(SRCDIR)/json_query.c $(OBJDIR)/translate
          664  +	$(OBJDIR)/translate $(SRCDIR)/json_query.c >$(OBJDIR)/json_query_.c
          665  +
          666  +$(OBJDIR)/json_query.o:	$(OBJDIR)/json_query_.c $(OBJDIR)/json_query.h  $(SRCDIR)/config.h
          667  +	$(XTCC) -o $(OBJDIR)/json_query.o -c $(OBJDIR)/json_query_.c
          668  +
          669  +$(OBJDIR)/json_query.h:	$(OBJDIR)/headers
          670  +$(OBJDIR)/json_report_.c:	$(SRCDIR)/json_report.c $(OBJDIR)/translate
          671  +	$(OBJDIR)/translate $(SRCDIR)/json_report.c >$(OBJDIR)/json_report_.c
          672  +
          673  +$(OBJDIR)/json_report.o:	$(OBJDIR)/json_report_.c $(OBJDIR)/json_report.h  $(SRCDIR)/config.h
          674  +	$(XTCC) -o $(OBJDIR)/json_report.o -c $(OBJDIR)/json_report_.c
          675  +
          676  +$(OBJDIR)/json_report.h:	$(OBJDIR)/headers
          677  +$(OBJDIR)/json_tag_.c:	$(SRCDIR)/json_tag.c $(OBJDIR)/translate
          678  +	$(OBJDIR)/translate $(SRCDIR)/json_tag.c >$(OBJDIR)/json_tag_.c
          679  +
          680  +$(OBJDIR)/json_tag.o:	$(OBJDIR)/json_tag_.c $(OBJDIR)/json_tag.h  $(SRCDIR)/config.h
          681  +	$(XTCC) -o $(OBJDIR)/json_tag.o -c $(OBJDIR)/json_tag_.c
          682  +
          683  +$(OBJDIR)/json_tag.h:	$(OBJDIR)/headers
          684  +$(OBJDIR)/json_timeline_.c:	$(SRCDIR)/json_timeline.c $(OBJDIR)/translate
          685  +	$(OBJDIR)/translate $(SRCDIR)/json_timeline.c >$(OBJDIR)/json_timeline_.c
          686  +
          687  +$(OBJDIR)/json_timeline.o:	$(OBJDIR)/json_timeline_.c $(OBJDIR)/json_timeline.h  $(SRCDIR)/config.h
          688  +	$(XTCC) -o $(OBJDIR)/json_timeline.o -c $(OBJDIR)/json_timeline_.c
          689  +
          690  +$(OBJDIR)/json_timeline.h:	$(OBJDIR)/headers
          691  +$(OBJDIR)/json_user_.c:	$(SRCDIR)/json_user.c $(OBJDIR)/translate
          692  +	$(OBJDIR)/translate $(SRCDIR)/json_user.c >$(OBJDIR)/json_user_.c
          693  +
          694  +$(OBJDIR)/json_user.o:	$(OBJDIR)/json_user_.c $(OBJDIR)/json_user.h  $(SRCDIR)/config.h
          695  +	$(XTCC) -o $(OBJDIR)/json_user.o -c $(OBJDIR)/json_user_.c
          696  +
          697  +$(OBJDIR)/json_user.h:	$(OBJDIR)/headers
          698  +$(OBJDIR)/json_wiki_.c:	$(SRCDIR)/json_wiki.c $(OBJDIR)/translate
          699  +	$(OBJDIR)/translate $(SRCDIR)/json_wiki.c >$(OBJDIR)/json_wiki_.c
          700  +
          701  +$(OBJDIR)/json_wiki.o:	$(OBJDIR)/json_wiki_.c $(OBJDIR)/json_wiki.h  $(SRCDIR)/config.h
          702  +	$(XTCC) -o $(OBJDIR)/json_wiki.o -c $(OBJDIR)/json_wiki_.c
          703  +
          704  +$(OBJDIR)/json_wiki.h:	$(OBJDIR)/headers
   594    705   $(OBJDIR)/leaf_.c:	$(SRCDIR)/leaf.c $(OBJDIR)/translate
   595    706   	$(OBJDIR)/translate $(SRCDIR)/leaf.c >$(OBJDIR)/leaf_.c
   596    707   
   597    708   $(OBJDIR)/leaf.o:	$(OBJDIR)/leaf_.c $(OBJDIR)/leaf.h  $(SRCDIR)/config.h
   598    709   	$(XTCC) -o $(OBJDIR)/leaf.o -c $(OBJDIR)/leaf_.c
   599    710   
   600    711   $(OBJDIR)/leaf.h:	$(OBJDIR)/headers
................................................................................
   907   1018   
   908   1019   $(OBJDIR)/th.o:	$(SRCDIR)/th.c
   909   1020   	$(XTCC) -I$(SRCDIR) -c $(SRCDIR)/th.c -o $(OBJDIR)/th.o
   910   1021   
   911   1022   $(OBJDIR)/th_lang.o:	$(SRCDIR)/th_lang.c
   912   1023   	$(XTCC) -I$(SRCDIR) -c $(SRCDIR)/th_lang.c -o $(OBJDIR)/th_lang.o
   913   1024   
         1025  +
         1026  +$(OBJDIR)/cson_amalgamation.o: $(SRCDIR)/cson_amalgamation.c
         1027  +	$(XTCC) -I$(SRCDIR) -c $(SRCDIR)/cson_amalgamation.c -o $(OBJDIR)/cson_amalgamation.o -DCSON_FOSSIL_MODE
         1028  +

Changes to src/makemake.tcl.

    51     51     graph
    52     52     gzip
    53     53     http
    54     54     http_socket
    55     55     http_transport
    56     56     import
    57     57     info
           58  +  json
           59  +  json_artifact
           60  +  json_branch
           61  +  json_diff
           62  +  json_login
           63  +  json_query
           64  +  json_report
           65  +  json_tag
           66  +  json_timeline
           67  +  json_user
           68  +  json_wiki
    58     69     leaf
    59     70     login
    60     71     main
    61     72     manifest
    62     73     md5
    63     74     merge
    64     75     merge3
................................................................................
   200    211   SQLITE3_OBJ.0 = $(OBJDIR)/sqlite3.o
   201    212   SQLITE3_OBJ.  = $(SQLITE3_OBJ.0)
   202    213   
   203    214   EXTRAOBJ = \
   204    215     $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE)) \
   205    216     $(OBJDIR)/shell.o \
   206    217     $(OBJDIR)/th.o \
   207         -  $(OBJDIR)/th_lang.o
          218  +  $(OBJDIR)/th_lang.o \
          219  +  $(OBJDIR)/cson_amalgamation.o
   208    220   
   209    221   $(APPNAME):	$(OBJDIR)/headers $(OBJ) $(EXTRAOBJ)
   210    222   	$(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB)
   211    223   
   212    224   # This rule prevents make from using its default rules to try build
   213    225   # an executable named "manifest" out of the file named "manifest.c"
   214    226   #
................................................................................
   223    235   set mhargs {}
   224    236   foreach s [lsort $src] {
   225    237     append mhargs " \$(OBJDIR)/${s}_.c:\$(OBJDIR)/$s.h"
   226    238     set extra_h($s) {}
   227    239   }
   228    240   append mhargs " \$(SRCDIR)/sqlite3.h"
   229    241   append mhargs " \$(SRCDIR)/th.h"
          242  +#append mhargs " \$(SRCDIR)/cson_amalgamation.h"
   230    243   append mhargs " \$(OBJDIR)/VERSION.h"
   231    244   writeln "\$(OBJDIR)/page_index.h: \$(TRANS_SRC) \$(OBJDIR)/mkindex"
   232    245   writeln "\t\$(OBJDIR)/mkindex \$(TRANS_SRC) >$@"
   233    246   writeln "\$(OBJDIR)/headers:\t\$(OBJDIR)/page_index.h \$(OBJDIR)/makeheaders \$(OBJDIR)/VERSION.h"
   234    247   writeln "\t\$(OBJDIR)/makeheaders $mhargs"
   235    248   writeln "\ttouch \$(OBJDIR)/headers"
   236    249   writeln "\$(OBJDIR)/headers: Makefile"
          250  +writeln "\$(OBJDIR)/json.o \$(OBJDIR)/json_artifact.o \$(OBJDIR)/json_branch.o \$(OBJDIR)/json_diff.o \$(OBJDIR)/json_login.o \$(OBJDIR)/json_query.o \$(OBJDIR)/json_report.o \$(OBJDIR)/json_tag.o \$(OBJDIR)/json_timeline.o \$(OBJDIR)/json_user.o \$(OBJDIR)/json_wiki.o : \$(SRCDIR)/json_detail.h"
   237    251   writeln "Makefile:"
   238    252   set extra_h(main) \$(OBJDIR)/page_index.h
   239    253   
   240    254   foreach s [lsort $src] {
   241    255     writeln "\$(OBJDIR)/${s}_.c:\t\$(SRCDIR)/$s.c \$(OBJDIR)/translate"
   242    256     writeln "\t\$(OBJDIR)/translate \$(SRCDIR)/$s.c >\$(OBJDIR)/${s}_.c\n"
   243    257     writeln "\$(OBJDIR)/$s.o:\t\$(OBJDIR)/${s}_.c \$(OBJDIR)/$s.h $extra_h($s) \$(SRCDIR)/config.h"
................................................................................
   262    276   writeln "\t\$(XTCC) $opt -c \$(SRCDIR)/shell.c -o \$(OBJDIR)/shell.o\n"
   263    277   
   264    278   writeln "\$(OBJDIR)/th.o:\t\$(SRCDIR)/th.c"
   265    279   writeln "\t\$(XTCC) -I\$(SRCDIR) -c \$(SRCDIR)/th.c -o \$(OBJDIR)/th.o\n"
   266    280   
   267    281   writeln "\$(OBJDIR)/th_lang.o:\t\$(SRCDIR)/th_lang.c"
   268    282   writeln "\t\$(XTCC) -I\$(SRCDIR) -c \$(SRCDIR)/th_lang.c -o \$(OBJDIR)/th_lang.o\n"
          283  +
          284  +set opt {}
          285  +writeln {
          286  +$(OBJDIR)/cson_amalgamation.o: $(SRCDIR)/cson_amalgamation.c
          287  +	$(XTCC) -I$(SRCDIR) -c $(SRCDIR)/cson_amalgamation.c -o $(OBJDIR)/cson_amalgamation.o -DCSON_FOSSIL_MODE
          288  +}
          289  +
   269    290   
   270    291   close $output_file
   271    292   #
   272    293   # End of the main.mk output
   273    294   ##############################################################################
   274    295   ##############################################################################
   275    296   ##############################################################################
................................................................................
   410    431   $(OBJDIR)/VERSION.h:	$(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(VERSION)
   411    432   	$(VERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h
   412    433   
   413    434   EXTRAOBJ = \
   414    435     $(OBJDIR)/sqlite3.o \
   415    436     $(OBJDIR)/shell.o \
   416    437     $(OBJDIR)/th.o \
   417         -  $(OBJDIR)/th_lang.o
          438  +  $(OBJDIR)/th_lang.o \
          439  +  $(OBJDIR)/cson_amalgamation.o
   418    440   
   419    441   $(APPNAME):	$(OBJDIR)/headers $(OBJ) $(EXTRAOBJ) $(OBJDIR)/icon.o
   420    442   	$(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB) $(OBJDIR)/icon.o
   421    443   
   422    444   # This rule prevents make from using its default rules to try build
   423    445   # an executable named "manifest" out of the file named "manifest.c"
   424    446   #
................................................................................
   464    486   }
   465    487   
   466    488   
   467    489   writeln "\$(OBJDIR)/sqlite3.o:\t\$(SRCDIR)/sqlite3.c"
   468    490   set opt $SQLITE_OPTIONS
   469    491   writeln "\t\$(XTCC) $opt -c \$(SRCDIR)/sqlite3.c -o \$(OBJDIR)/sqlite3.o\n"
   470    492   
          493  +set opt {}
          494  +writeln "\$(OBJDIR)/cson_amalgamation.o:\t\$(SRCDIR)/cson_amalgamation.c"
          495  +writeln "\t\$(XTCC) $opt -c \$(SRCDIR)/cson_amalgamation.c -o \$(OBJDIR)/cson_amalgamation.o -DCSON_FOSSIL_MODE\n"
          496  +writeln "\$(OBJDIR)/json.o \$(OBJDIR)/json_artifact.o \$(OBJDIR)/json_branch.o \$(OBJDIR)/json_diff.o \$(OBJDIR)/json_login.o \$(OBJDIR)/json_query.o \$(OBJDIR)/json_report.o \$(OBJDIR)/json_tag.o \$(OBJDIR)/json_timeline.o \$(OBJDIR)/json_user.o \$(OBJDIR)/json_wiki.o : \$(SRCDIR)/json_detail.h"
          497  +
   471    498   writeln "\$(OBJDIR)/shell.o:\t\$(SRCDIR)/shell.c \$(SRCDIR)/sqlite3.h"
   472    499   set opt {-Dmain=sqlite3_shell}
   473    500   append opt " -DSQLITE_OMIT_LOAD_EXTENSION=1"
   474    501   writeln "\t\$(XTCC) $opt -c \$(SRCDIR)/shell.c -o \$(OBJDIR)/shell.o\n"
   475    502   
   476    503   writeln "\$(OBJDIR)/th.o:\t\$(SRCDIR)/th.c"
   477    504   writeln "\t\$(XTCC) -I\$(SRCDIR) -c \$(SRCDIR)/th.c -o \$(OBJDIR)/th.o\n"
................................................................................
   575    602   	$(TCC) -o$@ -c $(SQLITE_OPTIONS) $**
   576    603   
   577    604   $(OBJDIR)\th$O : $(SRCDIR)\th.c
   578    605   	$(TCC) -o$@ -c $**
   579    606   
   580    607   $(OBJDIR)\th_lang$O : $(SRCDIR)\th_lang.c
   581    608   	$(TCC) -o$@ -c $**
          609  +
          610  +$(OBJDIR)\cson_amalgamation.h : $(SRCDIR)\cson_amalgamation.h
          611  +	cp $@ $@
   582    612   
   583    613   VERSION.h : version$E $B\manifest.uuid $B\manifest $B\VERSION
   584    614   	+$** > $@
   585    615   
   586    616   page_index.h: mkindex$E $(SRC) 
   587    617   	+$** > $@
   588    618   
   589    619   clean:
   590    620   	-del $(OBJDIR)\*.obj
   591    621   	-del *.obj *_.c *.h *.map
   592    622   
   593    623   realclean:
   594    624   	-del $(APPNAME) translate$E mkindex$E makeheaders$E mkversion$E
          625  +
          626  +$(OBJDIR)\json$O : $(SRCDIR)\json_detail.h
          627  +$(OBJDIR)\json_artifact$O : $(SRCDIR)\json_detail.h
          628  +$(OBJDIR)\json_branch$O : $(SRCDIR)\json_detail.h
          629  +$(OBJDIR)\json_diff$O : $(SRCDIR)\json_detail.h
          630  +$(OBJDIR)\json_login$O : $(SRCDIR)\json_detail.h
          631  +$(OBJDIR)\json_query$O : $(SRCDIR)\json_detail.h
          632  +$(OBJDIR)\json_report$O : $(SRCDIR)\json_detail.h
          633  +$(OBJDIR)\json_tag$O : $(SRCDIR)\json_detail.h
          634  +$(OBJDIR)\json_timeline$O : $(SRCDIR)\json_detail.h
          635  +$(OBJDIR)\json_user$O : $(SRCDIR)\json_detail.h
          636  +$(OBJDIR)\json_wiki$O : $(SRCDIR)\json_detail.h
          637  +
   595    638   
   596    639   }
   597    640   foreach s [lsort $src] {
   598    641     writeln "\$(OBJDIR)\\$s\$O : ${s}_.c ${s}.h"
   599    642     writeln "\t\$(TCC) -o\$@ -c ${s}_.c\n"
   600    643     writeln "${s}_.c : \$(SRCDIR)\\$s.c"
   601    644     writeln "\t+translate\$E \$** > \$@\n"
   602    645   }
   603    646   
   604    647   writeln -nonewline "headers: makeheaders\$E page_index.h VERSION.h\n\t +makeheaders\$E "
   605    648   foreach s [lsort $src] {
   606    649     writeln -nonewline "${s}_.c:$s.h "
   607    650   }
   608         -writeln "\$(SRCDIR)\\sqlite3.h \$(SRCDIR)\\th.h VERSION.h"
          651  +writeln "\$(SRCDIR)\\sqlite3.h \$(SRCDIR)\\th.h VERSION.h \$(SRCDIR)\\cson_amalgamation.h"
   609    652   writeln "\t@copy /Y nul: headers"
   610    653   
   611    654   close $output_file
   612    655   #
   613    656   # End of the win/Makefile.dmc output
   614    657   ##############################################################################
   615    658   ##############################################################################
................................................................................
   715    758   	$(TCC) /Fo$@ -c $**
   716    759   
   717    760   $(OX)\th_lang$O : $(SRCDIR)\th_lang.c
   718    761   	$(TCC) /Fo$@ -c $**
   719    762   
   720    763   VERSION.h : mkversion$E $B\manifest.uuid $B\manifest $B\VERSION
   721    764   	$** > $@
          765  +$(OBJDIR)\cson_amalgamation.h : $(SRCDIR)\cson_amalgamation.h
          766  +	cp $(SRCDIR)\cson_amalgamation.h $@
   722    767   
   723    768   page_index.h: mkindex$E $(SRC) 
   724    769   	$** > $@
   725    770   
   726    771   clean:
   727    772   	-del $(OX)\*.obj
   728    773   	-del *.obj *_.c *.h *.map
   729    774   	-del headers linkopts
   730    775   
   731    776   realclean:
   732    777   	-del $(APPNAME) translate$E mkindex$E makeheaders$E mkversion$E
   733    778   
          779  +$(OBJDIR)\json$O : $(SRCDIR)\json_detail.h
          780  +$(OBJDIR)\json_artifact$O : $(SRCDIR)\json_detail.h
          781  +$(OBJDIR)\json_branch$O : $(SRCDIR)\json_detail.h
          782  +$(OBJDIR)\json_diff$O : $(SRCDIR)\json_detail.h
          783  +$(OBJDIR)\json_login$O : $(SRCDIR)\json_detail.h
          784  +$(OBJDIR)\json_query$O : $(SRCDIR)\json_detail.h
          785  +$(OBJDIR)\json_report$O : $(SRCDIR)\json_detail.h
          786  +$(OBJDIR)\json_tag$O : $(SRCDIR)\json_detail.h
          787  +$(OBJDIR)\json_timeline$O : $(SRCDIR)\json_detail.h
          788  +$(OBJDIR)\json_user$O : $(SRCDIR)\json_detail.h
          789  +$(OBJDIR)\json_wiki$O : $(SRCDIR)\json_detail.h
          790  +
   734    791   }
   735    792   foreach s [lsort $src] {
   736    793     writeln "\$(OX)\\$s\$O : ${s}_.c ${s}.h"
   737    794     writeln "\t\$(TCC) /Fo\$@ -c ${s}_.c\n"
   738    795     writeln "${s}_.c : \$(SRCDIR)\\$s.c"
   739    796     writeln "\ttranslate\$E \$** > \$@\n"
   740    797   }
   741    798   
   742    799   writeln -nonewline "headers: makeheaders\$E page_index.h VERSION.h\n\tmakeheaders\$E "
   743    800   foreach s [lsort $src] {
   744    801     writeln -nonewline "${s}_.c:$s.h "
   745    802   }
   746         -writeln "\$(SRCDIR)\\sqlite3.h \$(SRCDIR)\\th.h VERSION.h"
          803  +writeln "\$(SRCDIR)\\sqlite3.h \$(SRCDIR)\\th.h VERSION.h \$(SRCDIR)\\cson_amalgamation.h"
   747    804   writeln "\t@copy /Y nul: headers"
   748    805   
   749    806   
   750    807   close $output_file
   751    808   #
   752    809   # End of the win/Makefile.msc output
   753    810   ##############################################################################

Changes to src/name.c.

   364    364   ** rid.  If the CGI parameter is missing or is not a valid artifact tag,
   365    365   ** return 0.  If the CGI parameter is ambiguous, redirect to a page that
   366    366   ** shows all possibilities and do not return.
   367    367   */
   368    368   int name_to_rid_www(const char *zParamName){
   369    369     int rid;
   370    370     const char *zName = P(zParamName);
   371         -
          371  +#ifdef FOSSIL_ENABLE_JSON
          372  +  if(!zName && fossil_has_json()){
          373  +    zName = json_find_option_cstr(zParamName,NULL,NULL);
          374  +  }
          375  +#endif
   372    376     if( zName==0 || zName[0]==0 ) return 0;
   373    377     rid = symbolic_name_to_rid(zName, "*");
   374    378     if( rid<0 ){
   375    379       cgi_redirectf("%s/ambiguous/%T?src=%t", g.zTop, zName, g.zPath);
   376    380       rid = 0;
   377    381     }
   378    382     return rid;

Changes to src/report.c.

    17     17   **  
    18     18   ** Code to generate the ticket listings
    19     19   */
    20     20   #include "config.h"
    21     21   #include <time.h>
    22     22   #include "report.h"
    23     23   #include <assert.h>
           24  +#ifdef FOSSIL_ENABLE_JSON
           25  +#  include "cson_amalgamation.h"
           26  +#endif
    24     27   
    25     28   /* Forward references to static routines */
    26     29   static void report_format_hints(void);
    27     30   
    28     31   /*
    29     32   ** WEBPAGE: /reportlist
    30     33   */
................................................................................
  1136   1139     report_restrict_sql(&zErr1);
  1137   1140     sqlite3_exec_readonly(g.db, zSql, output_separated_file, &count, &zErr2);
  1138   1141     report_unrestrict_sql();
  1139   1142     if( zFilter ){
  1140   1143       free(zSql);
  1141   1144     }
  1142   1145   }
         1146  +

Changes to src/timeline.c.

    18     18   ** This file contains code to implement the timeline web page
    19     19   **
    20     20   */
    21     21   #include <string.h>
    22     22   #include <time.h>
    23     23   #include "config.h"
    24     24   #include "timeline.h"
           25  +#ifdef FOSSIL_ENABLE_JSON
           26  +#  include "cson_amalgamation.h"
           27  +#endif
    25     28   
    26     29   /*
    27     30   ** Shorten a UUID so that is the minimum length needed to contain
    28     31   ** at least one digit in the range 'a'..'f'.  The minimum length is 10.
    29     32   */
    30     33   static void shorten_uuid(char *zDest, const char *zSrc){
    31     34     int i;
................................................................................
   766    769   ** Return a pointer to a constant string that forms the basis
   767    770   ** for a timeline query for the WWW interface.
   768    771   */
   769    772   const char *timeline_query_for_www(void){
   770    773     static char *zBase = 0;
   771    774     static const char zBaseSql[] =
   772    775       @ SELECT
   773         -    @   blob.rid,
   774         -    @   uuid,
          776  +    @   blob.rid AS blobRid,
          777  +    @   uuid AS uuid,
   775    778       @   datetime(event.mtime,'localtime') AS timestamp,
   776         -    @   coalesce(ecomment, comment),
   777         -    @   coalesce(euser, user),
   778         -    @   blob.rid IN leaf,
   779         -    @   bgcolor,
   780         -    @   event.type,
          779  +    @   coalesce(ecomment, comment) AS comment,
          780  +    @   coalesce(euser, user) AS user,
          781  +    @   blob.rid IN leaf AS leaf,
          782  +    @   bgcolor AS bgColor,
          783  +    @   event.type AS eventType,
   781    784       @   (SELECT group_concat(substr(tagname,5), ', ') FROM tag, tagxref
   782    785       @     WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid
   783         -    @       AND tagxref.rid=blob.rid AND tagxref.tagtype>0),
   784         -    @   tagid,
   785         -    @   brief,
   786         -    @   event.mtime
          786  +    @       AND tagxref.rid=blob.rid AND tagxref.tagtype>0) AS tags,
          787  +    @   tagid AS tagid,
          788  +    @   brief AS brief,
          789  +    @   event.mtime AS mtime
   787    790       @  FROM event JOIN blob 
   788    791       @ WHERE blob.rid=event.objid
   789    792     ;
   790    793     if( zBase==0 ){
   791    794       zBase = mprintf(zBaseSql, TAG_BRANCH, TAG_BRANCH);
   792    795     }
   793    796     return zBase;
................................................................................
  1379   1382   /*
  1380   1383   ** Return a pointer to a static string that forms the basis for
  1381   1384   ** a timeline query for display on a TTY.
  1382   1385   */
  1383   1386   const char *timeline_query_for_tty(void){
  1384   1387     static const char zBaseSql[] = 
  1385   1388       @ SELECT
  1386         -    @   blob.rid,
         1389  +    @   blob.rid AS rid,
  1387   1390       @   uuid,
  1388         -    @   datetime(event.mtime,'localtime'),
         1391  +    @   datetime(event.mtime,'localtime') AS mDateTime,
  1389   1392       @   coalesce(ecomment,comment)
  1390   1393       @     || ' (user: ' || coalesce(euser,user,'?')
  1391   1394       @     || (SELECT case when length(x)>0 then ' tags: ' || x else '' end
  1392   1395       @           FROM (SELECT group_concat(substr(tagname,5), ', ') AS x
  1393   1396       @                   FROM tag, tagxref
  1394   1397       @                  WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid
  1395   1398       @                    AND tagxref.rid=blob.rid AND tagxref.tagtype>0))
  1396         -    @     || ')',
  1397         -    @   (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim),
  1398         -    @   (SELECT count(*) FROM plink WHERE cid=blob.rid),
  1399         -    @   event.mtime
         1399  +    @     || ')' as comment,
         1400  +    @   (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim) AS primPlinkCount,
         1401  +    @   (SELECT count(*) FROM plink WHERE cid=blob.rid) AS plinkCount,
         1402  +    @   event.mtime AS mtime
  1400   1403       @ FROM event, blob
  1401   1404       @ WHERE blob.rid=event.objid
  1402   1405     ;
  1403   1406     return zBaseSql;
  1404   1407   }
  1405   1408   
  1406   1409   /*
................................................................................
  1439   1442   ** The optional TYPE argument may any types supported by the /timeline
  1440   1443   ** page. For example:
  1441   1444   **
  1442   1445   **     w  = wiki commits only
  1443   1446   **     ci = file commits only
  1444   1447   **     t  = tickets only
  1445   1448   **
  1446         -** The optional showfiles argument if specified prints the list of
  1447         -** files changed in a checkin after the checkin comment
         1449  +** The optional showfiles argument, if specified, prints the list of
         1450  +** files changed in a checkin after the checkin comment.
  1448   1451   **
  1449   1452   */
  1450   1453   void timeline_cmd(void){
  1451   1454     Stmt q;
  1452   1455     int n, k;
  1453   1456     const char *zCount;
  1454   1457     const char *zType;
................................................................................
  1538   1541         compute_ancestors(objid, n);
  1539   1542       }
  1540   1543       blob_appendf(&sql, " AND blob.rid IN ok");
  1541   1544     }
  1542   1545     if( zType && (zType[0]!='a') ){
  1543   1546       blob_appendf(&sql, " AND event.type=%Q ", zType);
  1544   1547     }
  1545         -
  1546   1548     blob_appendf(&sql, " ORDER BY event.mtime DESC");
  1547   1549     db_prepare(&q, blob_str(&sql));
  1548   1550     blob_reset(&sql);
  1549   1551     print_timeline(&q, n, showfilesFlag);
  1550   1552     db_finalize(&q);
  1551   1553   }
  1552   1554   

Changes to src/tkt.c.

   851    851   **           ?-q|--quote?
   852    852   **           ?-R|--repository FILE?
   853    853   **
   854    854   **         Run the ticket report, identified by the report format title
   855    855   **         used in the gui. The data is written as flat file on stdout,
   856    856   **         using "," as separator. The separator "," can be changed using
   857    857   **         the -l or --limit option.
          858  +**
   858    859   **         If TICKETFILTER is given on the commandline, the query is
   859    860   **         limited with a new WHERE-condition.
   860    861   **           example:  Report lists a column # with the uuid
   861    862   **                     TICKETFILTER may be [#]='uuuuuuuuu'
   862    863   **           example:  Report only lists rows with status not open
   863    864   **                     TICKETFILTER: status != 'open'
   864    865   **         If the option -q|--quote is used, the tickets are encoded by
   865    866   **         quoting special chars(space -> \\s, tab -> \\t, newline -> \\n,
   866    867   **         cr -> \\r, formfeed -> \\f, vtab -> \\v, nul -> \\0, \\ -> \\\\).
   867    868   **         Otherwise, the simplified encoding as on the show report raw
   868         -**         page in the gui is used.
          869  +**         page in the gui is used. This has no effect in JSON mode.
   869    870   **
   870    871   **         Instead of the report title its possible to use the report
   871    872   **         number. Using the special report number 0 list all columns,
   872    873   **         defined in the ticket table.
   873    874   **
   874    875   **     %fossil ticket list fields
   875    876   **
................................................................................
   955    956         if( strncmp(g.argv[2],"show",n)==0 ){
   956    957           if( g.argc==3 ){
   957    958             usage("show REPORTNR");
   958    959           }else{
   959    960             const char *zRep = 0;
   960    961             const char *zSep = 0;
   961    962             const char *zFilterUuid = 0;
   962         -
   963    963             zSep = find_option("limit","l",1);
   964    964             zRep = g.argv[3];
   965    965             if( !strcmp(zRep,"0") ){
   966    966               zRep = 0;
   967    967             }
   968    968             if( g.argc>4 ){
   969    969               zFilterUuid = g.argv[4];
   970    970             }
   971         -
   972    971             rptshow( zRep, zSep, zFilterUuid, tktEncoding );
   973         -
   974    972           }
   975    973         }else{
   976    974           /* add a new ticket or update an existing ticket */
   977    975           enum { set,add,history,err } eCmd = err;
   978    976           int i = 0;
   979    977           int rid;
   980    978           const char *zTktUuid = 0;

Changes to src/wiki.c.

   628    628     @ %h(blob_str(&d))
   629    629     @ </pre>
   630    630     manifest_destroy(pW1);
   631    631     manifest_destroy(pW2);
   632    632     style_footer();
   633    633   }
   634    634   
          635  +/*
          636  +** prepare()s pStmt with a query requesting:
          637  +**
          638  +** - wiki page name
          639  +** - tagxref (whatever that really is!)
          640  +**
          641  +** Used by wcontent_page() and the JSON wiki code.
          642  +*/
          643  +void wiki_prepare_page_list( Stmt * pStmt ){
          644  +  db_prepare(pStmt, 
          645  +    "SELECT"
          646  +    "  substr(tagname, 6) as name,"
          647  +    "  (SELECT value FROM tagxref WHERE tagid=tag.tagid ORDER BY mtime DESC) as tagXref"
          648  +    "  FROM tag WHERE tagname GLOB 'wiki-*'"
          649  +    " ORDER BY lower(tagname) /*sort*/"
          650  +  );
          651  +}
   635    652   /*
   636    653   ** WEBPAGE: wcontent
   637    654   **
   638    655   **     all=1         Show deleted pages
   639    656   **
   640    657   ** List all available wiki pages with date created and last modified.
   641    658   */
................................................................................
   648    665     style_header("Available Wiki Pages");
   649    666     if( showAll ){
   650    667       style_submenu_element("Active", "Only Active Pages", "%s/wcontent", g.zTop);
   651    668     }else{
   652    669       style_submenu_element("All", "All", "%s/wcontent?all=1", g.zTop);
   653    670     }
   654    671     @ <ul>
   655         -  db_prepare(&q, 
   656         -    "SELECT"
   657         -    "  substr(tagname, 6),"
   658         -    "  (SELECT value FROM tagxref WHERE tagid=tag.tagid ORDER BY mtime DESC)"
   659         -    "  FROM tag WHERE tagname GLOB 'wiki-*'"
   660         -    " ORDER BY lower(tagname) /*sort*/"
   661         -  );
          672  +  wiki_prepare_page_list(&q);
   662    673     while( db_step(&q)==SQLITE_ROW ){
   663    674       const char *zName = db_column_text(&q, 0);
   664    675       int size = db_column_int(&q, 1);
   665    676       if( size>0 ){
   666    677         @ <li><a href="%s(g.zTop)/wiki?name=%T(zName)">%h(zName)</a></li>
   667    678       }else if( showAll ){
   668    679         @ <li><a href="%s(g.zTop)/wiki?name=%T(zName)"><s>%h(zName)</s></a></li>
................................................................................
   788    799     rid = db_int(0,
   789    800        "SELECT x.rid FROM tag t, tagxref x"
   790    801        " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
   791    802        " ORDER BY x.mtime DESC LIMIT 1",
   792    803        zPageName
   793    804     );
   794    805     if( rid==0 && !isNew ){
          806  +#ifdef FOSSIL_ENABLE_JSON
          807  +    g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND;
          808  +#endif
   795    809       fossil_fatal("no such wiki page: %s", zPageName);
   796    810     }
   797    811     if( rid!=0 && isNew ){
          812  +#ifdef FOSSIL_ENABLE_JSON
          813  +    g.json.resultCode = FSL_JSON_E_RESOURCE_ALREADY_EXISTS;
          814  +#endif
   798    815       fossil_fatal("wiki page %s already exists", zPageName);
   799    816     }
   800    817   
   801    818     blob_zero(&wiki);
   802    819     zDate = date_in_standard_format("now");
   803    820     blob_appendf(&wiki, "D %s\n", zDate);
   804    821     free(zDate);

Changes to win/Makefile.dmc.

    20     20   CFLAGS = -o
    21     21   BCC    = $(DMDIR)\bin\dmc $(CFLAGS)
    22     22   TCC    = $(DMDIR)\bin\dmc $(CFLAGS) $(DMCDEF) $(SSL) $(INCL)
    23     23   LIBS   = $(DMDIR)\extra\lib\ zlib wsock32
    24     24   
    25     25   SQLITE_OPTIONS = -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_STAT3 -Dlocaltime=fossil_localtime -DSQLITE_ENABLE_LOCKING_STYLE=0
    26     26   
    27         -SRC   = add_.c allrepo_.c attach_.c bag_.c bisect_.c blob_.c branch_.c browse_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c db_.c delta_.c deltacmd_.c descendants_.c diff_.c diffcmd_.c doc_.c encode_.c event_.c export_.c file_.c finfo_.c glob_.c graph_.c gzip_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c leaf_.c login_.c main_.c manifest_.c md5_.c merge_.c merge3_.c name_.c path_.c pivot_.c popen_.c pqueue_.c printf_.c rebuild_.c report_.c rss_.c schema_.c search_.c setup_.c sha1_.c shun_.c skins_.c sqlcmd_.c stash_.c stat_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c update_.c url_.c user_.c verify_.c vfile_.c wiki_.c wikiformat_.c winhttp_.c xfer_.c zip_.c 
           27  +SRC   = add_.c allrepo_.c attach_.c bag_.c bisect_.c blob_.c branch_.c browse_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c db_.c delta_.c deltacmd_.c descendants_.c diff_.c diffcmd_.c doc_.c encode_.c event_.c export_.c file_.c finfo_.c glob_.c graph_.c gzip_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c json_.c json_artifact_.c json_branch_.c json_diff_.c json_login_.c json_query_.c json_report_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c login_.c main_.c manifest_.c md5_.c merge_.c merge3_.c name_.c path_.c pivot_.c popen_.c pqueue_.c printf_.c rebuild_.c report_.c rss_.c schema_.c search_.c setup_.c sha1_.c shun_.c skins_.c sqlcmd_.c stash_.c stat_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c update_.c url_.c user_.c verify_.c vfile_.c wiki_.c wikiformat_.c winhttp_.c xfer_.c zip_.c 
    28     28   
    29         -OBJ   = $(OBJDIR)\add$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\file$O $(OBJDIR)\finfo$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\leaf$O $(OBJDIR)\login$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\name$O $(OBJDIR)\path$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\rebuild$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\setup$O $(OBJDIR)\sha1$O $(OBJDIR)\shun$O $(OBJDIR)\skins$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winhttp$O $(OBJDIR)\xfer$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O 
           29  +OBJ   = $(OBJDIR)\add$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\file$O $(OBJDIR)\finfo$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\login$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\name$O $(OBJDIR)\path$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\rebuild$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\setup$O $(OBJDIR)\sha1$O $(OBJDIR)\shun$O $(OBJDIR)\skins$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winhttp$O $(OBJDIR)\xfer$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O 
    30     30   
    31     31   
    32     32   RC=$(DMDIR)\bin\rcc
    33     33   RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__
    34     34   
    35     35   APPNAME = $(OBJDIR)\fossil$(E)
    36     36   
................................................................................
    40     40   	cd $(OBJDIR) 
    41     41   	$(DMDIR)\bin\link @link
    42     42   
    43     43   $(OBJDIR)\fossil.res:	$B\win\fossil.rc
    44     44   	$(RC) $(RCFLAGS) -o$@ $**
    45     45   
    46     46   $(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res
    47         -	+echo add allrepo attach bag bisect blob branch browse captcha cgi checkin checkout clearsign clone comformat configure content db delta deltacmd descendants diff diffcmd doc encode event export file finfo glob graph gzip http http_socket http_ssl http_transport import info leaf login main manifest md5 merge merge3 name path pivot popen pqueue printf rebuild report rss schema search setup sha1 shun skins sqlcmd stash stat style sync tag tar th_main timeline tkt tktsetup undo update url user verify vfile wiki wikiformat winhttp xfer zip shell sqlite3 th th_lang > $@
           47  +	+echo add allrepo attach bag bisect blob branch browse captcha cgi checkin checkout clearsign clone comformat configure content db delta deltacmd descendants diff diffcmd doc encode event export file finfo glob graph gzip http http_socket http_ssl http_transport import info json json_artifact json_branch json_diff json_login json_query json_report json_tag json_timeline json_user json_wiki leaf login main manifest md5 merge merge3 name path pivot popen pqueue printf rebuild report rss schema search setup sha1 shun skins sqlcmd stash stat style sync tag tar th_main timeline tkt tktsetup undo update url user verify vfile wiki wikiformat winhttp xfer zip shell sqlite3 th th_lang > $@
    48     48   	+echo fossil >> $@
    49     49   	+echo fossil >> $@
    50     50   	+echo $(LIBS) >> $@
    51     51   	+echo. >> $@
    52     52   	+echo fossil >> $@
    53     53   
    54     54   translate$E: $(SRCDIR)\translate.c
................................................................................
    70     70   	$(TCC) -o$@ -c $(SQLITE_OPTIONS) $**
    71     71   
    72     72   $(OBJDIR)\th$O : $(SRCDIR)\th.c
    73     73   	$(TCC) -o$@ -c $**
    74     74   
    75     75   $(OBJDIR)\th_lang$O : $(SRCDIR)\th_lang.c
    76     76   	$(TCC) -o$@ -c $**
           77  +
           78  +$(OBJDIR)\cson_amalgamation.h : $(SRCDIR)\cson_amalgamation.h
           79  +	cp $@ $@
    77     80   
    78     81   VERSION.h : version$E $B\manifest.uuid $B\manifest $B\VERSION
    79     82   	+$** > $@
    80     83   
    81     84   page_index.h: mkindex$E $(SRC) 
    82     85   	+$** > $@
    83     86   
    84     87   clean:
    85     88   	-del $(OBJDIR)\*.obj
    86     89   	-del *.obj *_.c *.h *.map
    87     90   
    88     91   realclean:
    89     92   	-del $(APPNAME) translate$E mkindex$E makeheaders$E mkversion$E
           93  +
           94  +$(OBJDIR)\json$O : $(SRCDIR)\json_detail.h
           95  +$(OBJDIR)\json_artifact$O : $(SRCDIR)\json_detail.h
           96  +$(OBJDIR)\json_branch$O : $(SRCDIR)\json_detail.h
           97  +$(OBJDIR)\json_diff$O : $(SRCDIR)\json_detail.h
           98  +$(OBJDIR)\json_login$O : $(SRCDIR)\json_detail.h
           99  +$(OBJDIR)\json_query$O : $(SRCDIR)\json_detail.h
          100  +$(OBJDIR)\json_report$O : $(SRCDIR)\json_detail.h
          101  +$(OBJDIR)\json_tag$O : $(SRCDIR)\json_detail.h
          102  +$(OBJDIR)\json_timeline$O : $(SRCDIR)\json_detail.h
          103  +$(OBJDIR)\json_user$O : $(SRCDIR)\json_detail.h
          104  +$(OBJDIR)\json_wiki$O : $(SRCDIR)\json_detail.h
          105  +
    90    106   
    91    107   
    92    108   $(OBJDIR)\add$O : add_.c add.h
    93    109   	$(TCC) -o$@ -c add_.c
    94    110   
    95    111   add_.c : $(SRCDIR)\add.c
    96    112   	+translate$E $** > $@
................................................................................
   312    328   	+translate$E $** > $@
   313    329   
   314    330   $(OBJDIR)\info$O : info_.c info.h
   315    331   	$(TCC) -o$@ -c info_.c
   316    332   
   317    333   info_.c : $(SRCDIR)\info.c
   318    334   	+translate$E $** > $@
          335  +
          336  +$(OBJDIR)\json$O : json_.c json.h
          337  +	$(TCC) -o$@ -c json_.c
          338  +
          339  +json_.c : $(SRCDIR)\json.c
          340  +	+translate$E $** > $@
          341  +
          342  +$(OBJDIR)\json_artifact$O : json_artifact_.c json_artifact.h
          343  +	$(TCC) -o$@ -c json_artifact_.c
          344  +
          345  +json_artifact_.c : $(SRCDIR)\json_artifact.c
          346  +	+translate$E $** > $@
          347  +
          348  +$(OBJDIR)\json_branch$O : json_branch_.c json_branch.h
          349  +	$(TCC) -o$@ -c json_branch_.c
          350  +
          351  +json_branch_.c : $(SRCDIR)\json_branch.c
          352  +	+translate$E $** > $@
          353  +
          354  +$(OBJDIR)\json_diff$O : json_diff_.c json_diff.h
          355  +	$(TCC) -o$@ -c json_diff_.c
          356  +
          357  +json_diff_.c : $(SRCDIR)\json_diff.c
          358  +	+translate$E $** > $@
          359  +
          360  +$(OBJDIR)\json_login$O : json_login_.c json_login.h
          361  +	$(TCC) -o$@ -c json_login_.c
          362  +
          363  +json_login_.c : $(SRCDIR)\json_login.c
          364  +	+translate$E $** > $@
          365  +
          366  +$(OBJDIR)\json_query$O : json_query_.c json_query.h
          367  +	$(TCC) -o$@ -c json_query_.c
          368  +
          369  +json_query_.c : $(SRCDIR)\json_query.c
          370  +	+translate$E $** > $@
          371  +
          372  +$(OBJDIR)\json_report$O : json_report_.c json_report.h
          373  +	$(TCC) -o$@ -c json_report_.c
          374  +
          375  +json_report_.c : $(SRCDIR)\json_report.c
          376  +	+translate$E $** > $@
          377  +
          378  +$(OBJDIR)\json_tag$O : json_tag_.c json_tag.h
          379  +	$(TCC) -o$@ -c json_tag_.c
          380  +
          381  +json_tag_.c : $(SRCDIR)\json_tag.c
          382  +	+translate$E $** > $@
          383  +
          384  +$(OBJDIR)\json_timeline$O : json_timeline_.c json_timeline.h
          385  +	$(TCC) -o$@ -c json_timeline_.c
          386  +
          387  +json_timeline_.c : $(SRCDIR)\json_timeline.c
          388  +	+translate$E $** > $@
          389  +
          390  +$(OBJDIR)\json_user$O : json_user_.c json_user.h
          391  +	$(TCC) -o$@ -c json_user_.c
          392  +
          393  +json_user_.c : $(SRCDIR)\json_user.c
          394  +	+translate$E $** > $@
          395  +
          396  +$(OBJDIR)\json_wiki$O : json_wiki_.c json_wiki.h
          397  +	$(TCC) -o$@ -c json_wiki_.c
          398  +
          399  +json_wiki_.c : $(SRCDIR)\json_wiki.c
          400  +	+translate$E $** > $@
   319    401   
   320    402   $(OBJDIR)\leaf$O : leaf_.c leaf.h
   321    403   	$(TCC) -o$@ -c leaf_.c
   322    404   
   323    405   leaf_.c : $(SRCDIR)\leaf.c
   324    406   	+translate$E $** > $@
   325    407   
................................................................................
   578    660   $(OBJDIR)\zip$O : zip_.c zip.h
   579    661   	$(TCC) -o$@ -c zip_.c
   580    662   
   581    663   zip_.c : $(SRCDIR)\zip.c
   582    664   	+translate$E $** > $@
   583    665   
   584    666   headers: makeheaders$E page_index.h VERSION.h
   585         -	 +makeheaders$E add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h event_.c:event.h export_.c:export.h file_.c:file.h finfo_.c:finfo.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h leaf_.c:leaf.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h path_.c:path.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h
          667  +	 +makeheaders$E add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h event_.c:event.h export_.c:export.h file_.c:file.h finfo_.c:finfo.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_diff_.c:json_diff.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h path_.c:path.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR)\cson_amalgamation.h
   586    668   	@copy /Y nul: headers

Changes to win/Makefile.mingw.

   108    108     $(SRCDIR)/gzip.c \
   109    109     $(SRCDIR)/http.c \
   110    110     $(SRCDIR)/http_socket.c \
   111    111     $(SRCDIR)/http_ssl.c \
   112    112     $(SRCDIR)/http_transport.c \
   113    113     $(SRCDIR)/import.c \
   114    114     $(SRCDIR)/info.c \
          115  +  $(SRCDIR)/json.c \
          116  +  $(SRCDIR)/json_artifact.c \
          117  +  $(SRCDIR)/json_branch.c \
          118  +  $(SRCDIR)/json_diff.c \
          119  +  $(SRCDIR)/json_login.c \
          120  +  $(SRCDIR)/json_query.c \
          121  +  $(SRCDIR)/json_report.c \
          122  +  $(SRCDIR)/json_tag.c \
          123  +  $(SRCDIR)/json_timeline.c \
          124  +  $(SRCDIR)/json_user.c \
          125  +  $(SRCDIR)/json_wiki.c \
   115    126     $(SRCDIR)/leaf.c \
   116    127     $(SRCDIR)/login.c \
   117    128     $(SRCDIR)/main.c \
   118    129     $(SRCDIR)/manifest.c \
   119    130     $(SRCDIR)/md5.c \
   120    131     $(SRCDIR)/merge.c \
   121    132     $(SRCDIR)/merge3.c \
................................................................................
   192    203     $(OBJDIR)/gzip_.c \
   193    204     $(OBJDIR)/http_.c \
   194    205     $(OBJDIR)/http_socket_.c \
   195    206     $(OBJDIR)/http_ssl_.c \
   196    207     $(OBJDIR)/http_transport_.c \
   197    208     $(OBJDIR)/import_.c \
   198    209     $(OBJDIR)/info_.c \
          210  +  $(OBJDIR)/json_.c \
          211  +  $(OBJDIR)/json_artifact_.c \
          212  +  $(OBJDIR)/json_branch_.c \
          213  +  $(OBJDIR)/json_diff_.c \
          214  +  $(OBJDIR)/json_login_.c \
          215  +  $(OBJDIR)/json_query_.c \
          216  +  $(OBJDIR)/json_report_.c \
          217  +  $(OBJDIR)/json_tag_.c \
          218  +  $(OBJDIR)/json_timeline_.c \
          219  +  $(OBJDIR)/json_user_.c \
          220  +  $(OBJDIR)/json_wiki_.c \
   199    221     $(OBJDIR)/leaf_.c \
   200    222     $(OBJDIR)/login_.c \
   201    223     $(OBJDIR)/main_.c \
   202    224     $(OBJDIR)/manifest_.c \
   203    225     $(OBJDIR)/md5_.c \
   204    226     $(OBJDIR)/merge_.c \
   205    227     $(OBJDIR)/merge3_.c \
................................................................................
   276    298    $(OBJDIR)/gzip.o \
   277    299    $(OBJDIR)/http.o \
   278    300    $(OBJDIR)/http_socket.o \
   279    301    $(OBJDIR)/http_ssl.o \
   280    302    $(OBJDIR)/http_transport.o \
   281    303    $(OBJDIR)/import.o \
   282    304    $(OBJDIR)/info.o \
          305  + $(OBJDIR)/json.o \
          306  + $(OBJDIR)/json_artifact.o \
          307  + $(OBJDIR)/json_branch.o \
          308  + $(OBJDIR)/json_diff.o \
          309  + $(OBJDIR)/json_login.o \
          310  + $(OBJDIR)/json_query.o \
          311  + $(OBJDIR)/json_report.o \
          312  + $(OBJDIR)/json_tag.o \
          313  + $(OBJDIR)/json_timeline.o \
          314  + $(OBJDIR)/json_user.o \
          315  + $(OBJDIR)/json_wiki.o \
   283    316    $(OBJDIR)/leaf.o \
   284    317    $(OBJDIR)/login.o \
   285    318    $(OBJDIR)/main.o \
   286    319    $(OBJDIR)/manifest.o \
   287    320    $(OBJDIR)/md5.o \
   288    321    $(OBJDIR)/merge.o \
   289    322    $(OBJDIR)/merge3.o \
................................................................................
   361    394   # the repository after running the tests.
   362    395   test:	$(APPNAME)
   363    396   	$(TCLSH) test/tester.tcl $(APPNAME)
   364    397   
   365    398   $(OBJDIR)/VERSION.h:	$(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(VERSION)
   366    399   	$(VERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h
   367    400   
   368         -EXTRAOBJ =  $(OBJDIR)/sqlite3.o  $(OBJDIR)/shell.o  $(OBJDIR)/th.o  $(OBJDIR)/th_lang.o
          401  +EXTRAOBJ =  $(OBJDIR)/sqlite3.o  $(OBJDIR)/shell.o  $(OBJDIR)/th.o  $(OBJDIR)/th_lang.o  $(OBJDIR)/cson_amalgamation.o
   369    402   
   370    403   $(APPNAME):	$(OBJDIR)/headers $(OBJ) $(EXTRAOBJ) $(OBJDIR)/icon.o
   371    404   	$(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB) $(OBJDIR)/icon.o
   372    405   
   373    406   # This rule prevents make from using its default rules to try build
   374    407   # an executable named "manifest" out of the file named "manifest.c"
   375    408   #
................................................................................
   386    419   setup: $(OBJDIR) $(APPNAME)
   387    420   	$(MAKENSIS) ./fossil.nsi
   388    421   
   389    422   
   390    423   $(OBJDIR)/page_index.h: $(TRANS_SRC) $(OBJDIR)/mkindex
   391    424   	$(MKINDEX) $(TRANS_SRC) >$@
   392    425   $(OBJDIR)/headers:	$(OBJDIR)/page_index.h $(OBJDIR)/makeheaders $(OBJDIR)/VERSION.h
   393         -	$(MAKEHEADERS)  $(OBJDIR)/add_.c:$(OBJDIR)/add.h $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h $(OBJDIR)/browse_.c:$(OBJDIR)/browse.h $(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h $(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h $(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h $(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h $(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h $(OBJDIR)/clone_.c:$(OBJDIR)/clone.h $(OBJDIR)/comformat_.c:$(OBJDIR)/comformat.h $(OBJDIR)/configure_.c:$(OBJDIR)/configure.h $(OBJDIR)/content_.c:$(OBJDIR)/content.h $(OBJDIR)/db_.c:$(OBJDIR)/db.h $(OBJDIR)/delta_.c:$(OBJDIR)/delta.h $(OBJDIR)/deltacmd_.c:$(OBJDIR)/deltacmd.h $(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h $(OBJDIR)/diff_.c:$(OBJDIR)/diff.h $(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h $(OBJDIR)/event_.c:$(OBJDIR)/event.h $(OBJDIR)/export_.c:$(OBJDIR)/export.h $(OBJDIR)/file_.c:$(OBJDIR)/file.h $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h $(OBJDIR)/http_.c:$(OBJDIR)/http.h $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h $(OBJDIR)/import_.c:$(OBJDIR)/import.h $(OBJDIR)/info_.c:$(OBJDIR)/info.h $(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h $(OBJDIR)/login_.c:$(OBJDIR)/login.h $(OBJDIR)/main_.c:$(OBJDIR)/main.h $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h $(OBJDIR)/name_.c:$(OBJDIR)/name.h $(OBJDIR)/path_.c:$(OBJDIR)/path.h $(OBJDIR)/pivot_.c:$(OBJDIR)/pivot.h $(OBJDIR)/popen_.c:$(OBJDIR)/popen.h $(OBJDIR)/pqueue_.c:$(OBJDIR)/pqueue.h $(OBJDIR)/printf_.c:$(OBJDIR)/printf.h $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h $(OBJDIR)/report_.c:$(OBJDIR)/report.h $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h $(OBJDIR)/search_.c:$(OBJDIR)/search.h $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h $(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h $(OBJDIR)/style_.c:$(OBJDIR)/style.h $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h $(OBJDIR)/update_.c:$(OBJDIR)/update.h $(OBJDIR)/url_.c:$(OBJDIR)/url.h $(OBJDIR)/user_.c:$(OBJDIR)/user.h $(OBJDIR)/verify_.c:$(OBJDIR)/verify.h $(OBJDIR)/vfile_.c:$(OBJDIR)/vfile.h $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h $(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h $(OBJDIR)/zip_.c:$(OBJDIR)/zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h $(OBJDIR)/VERSION.h
          426  +	$(MAKEHEADERS)  $(OBJDIR)/add_.c:$(OBJDIR)/add.h $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h $(OBJDIR)/browse_.c:$(OBJDIR)/browse.h $(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h $(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h $(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h $(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h $(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h $(OBJDIR)/clone_.c:$(OBJDIR)/clone.h $(OBJDIR)/comformat_.c:$(OBJDIR)/comformat.h $(OBJDIR)/configure_.c:$(OBJDIR)/configure.h $(OBJDIR)/content_.c:$(OBJDIR)/content.h $(OBJDIR)/db_.c:$(OBJDIR)/db.h $(OBJDIR)/delta_.c:$(OBJDIR)/delta.h $(OBJDIR)/deltacmd_.c:$(OBJDIR)/deltacmd.h $(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h $(OBJDIR)/diff_.c:$(OBJDIR)/diff.h $(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h $(OBJDIR)/event_.c:$(OBJDIR)/event.h $(OBJDIR)/export_.c:$(OBJDIR)/export.h $(OBJDIR)/file_.c:$(OBJDIR)/file.h $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h $(OBJDIR)/http_.c:$(OBJDIR)/http.h $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h $(OBJDIR)/import_.c:$(OBJDIR)/import.h $(OBJDIR)/info_.c:$(OBJDIR)/info.h $(OBJDIR)/json_.c:$(OBJDIR)/json.h $(OBJDIR)/json_artifact_.c:$(OBJDIR)/json_artifact.h $(OBJDIR)/json_branch_.c:$(OBJDIR)/json_branch.h $(OBJDIR)/json_diff_.c:$(OBJDIR)/json_diff.h $(OBJDIR)/json_login_.c:$(OBJDIR)/json_login.h $(OBJDIR)/json_query_.c:$(OBJDIR)/json_query.h $(OBJDIR)/json_report_.c:$(OBJDIR)/json_report.h $(OBJDIR)/json_tag_.c:$(OBJDIR)/json_tag.h $(OBJDIR)/json_timeline_.c:$(OBJDIR)/json_timeline.h $(OBJDIR)/json_user_.c:$(OBJDIR)/json_user.h $(OBJDIR)/json_wiki_.c:$(OBJDIR)/json_wiki.h $(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h $(OBJDIR)/login_.c:$(OBJDIR)/login.h $(OBJDIR)/main_.c:$(OBJDIR)/main.h $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h $(OBJDIR)/name_.c:$(OBJDIR)/name.h $(OBJDIR)/path_.c:$(OBJDIR)/path.h $(OBJDIR)/pivot_.c:$(OBJDIR)/pivot.h $(OBJDIR)/popen_.c:$(OBJDIR)/popen.h $(OBJDIR)/pqueue_.c:$(OBJDIR)/pqueue.h $(OBJDIR)/printf_.c:$(OBJDIR)/printf.h $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h $(OBJDIR)/report_.c:$(OBJDIR)/report.h $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h $(OBJDIR)/search_.c:$(OBJDIR)/search.h $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h $(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h $(OBJDIR)/style_.c:$(OBJDIR)/style.h $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h $(OBJDIR)/update_.c:$(OBJDIR)/update.h $(OBJDIR)/url_.c:$(OBJDIR)/url.h $(OBJDIR)/user_.c:$(OBJDIR)/user.h $(OBJDIR)/verify_.c:$(OBJDIR)/verify.h $(OBJDIR)/vfile_.c:$(OBJDIR)/vfile.h $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h $(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h $(OBJDIR)/zip_.c:$(OBJDIR)/zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h $(OBJDIR)/VERSION.h
   394    427   	echo Done >$(OBJDIR)/headers
   395    428   
   396    429   $(OBJDIR)/headers: Makefile
   397    430   Makefile:
   398    431   $(OBJDIR)/add_.c:	$(SRCDIR)/add.c $(OBJDIR)/translate
   399    432   	$(TRANSLATE) $(SRCDIR)/add.c >$(OBJDIR)/add_.c
   400    433   
................................................................................
   657    690   $(OBJDIR)/info_.c:	$(SRCDIR)/info.c $(OBJDIR)/translate
   658    691   	$(TRANSLATE) $(SRCDIR)/info.c >$(OBJDIR)/info_.c
   659    692   
   660    693   $(OBJDIR)/info.o:	$(OBJDIR)/info_.c $(OBJDIR)/info.h  $(SRCDIR)/config.h
   661    694   	$(XTCC) -o $(OBJDIR)/info.o -c $(OBJDIR)/info_.c
   662    695   
   663    696   info.h:	$(OBJDIR)/headers
          697  +$(OBJDIR)/json_.c:	$(SRCDIR)/json.c $(OBJDIR)/translate
          698  +	$(TRANSLATE) $(SRCDIR)/json.c >$(OBJDIR)/json_.c
          699  +
          700  +$(OBJDIR)/json.o:	$(OBJDIR)/json_.c $(OBJDIR)/json.h  $(SRCDIR)/config.h
          701  +	$(XTCC) -o $(OBJDIR)/json.o -c $(OBJDIR)/json_.c
          702  +
          703  +json.h:	$(OBJDIR)/headers
          704  +$(OBJDIR)/json_artifact_.c:	$(SRCDIR)/json_artifact.c $(OBJDIR)/translate
          705  +	$(TRANSLATE) $(SRCDIR)/json_artifact.c >$(OBJDIR)/json_artifact_.c
          706  +
          707  +$(OBJDIR)/json_artifact.o:	$(OBJDIR)/json_artifact_.c $(OBJDIR)/json_artifact.h  $(SRCDIR)/config.h
          708  +	$(XTCC) -o $(OBJDIR)/json_artifact.o -c $(OBJDIR)/json_artifact_.c
          709  +
          710  +json_artifact.h:	$(OBJDIR)/headers
          711  +$(OBJDIR)/json_branch_.c:	$(SRCDIR)/json_branch.c $(OBJDIR)/translate
          712  +	$(TRANSLATE) $(SRCDIR)/json_branch.c >$(OBJDIR)/json_branch_.c
          713  +
          714  +$(OBJDIR)/json_branch.o:	$(OBJDIR)/json_branch_.c $(OBJDIR)/json_branch.h  $(SRCDIR)/config.h
          715  +	$(XTCC) -o $(OBJDIR)/json_branch.o -c $(OBJDIR)/json_branch_.c
          716  +
          717  +json_branch.h:	$(OBJDIR)/headers
          718  +$(OBJDIR)/json_diff_.c:	$(SRCDIR)/json_diff.c $(OBJDIR)/translate
          719  +	$(TRANSLATE) $(SRCDIR)/json_diff.c >$(OBJDIR)/json_diff_.c
          720  +
          721  +$(OBJDIR)/json_diff.o:	$(OBJDIR)/json_diff_.c $(OBJDIR)/json_diff.h  $(SRCDIR)/config.h
          722  +	$(XTCC) -o $(OBJDIR)/json_diff.o -c $(OBJDIR)/json_diff_.c
          723  +
          724  +json_diff.h:	$(OBJDIR)/headers
          725  +$(OBJDIR)/json_login_.c:	$(SRCDIR)/json_login.c $(OBJDIR)/translate
          726  +	$(TRANSLATE) $(SRCDIR)/json_login.c >$(OBJDIR)/json_login_.c
          727  +
          728  +$(OBJDIR)/json_login.o:	$(OBJDIR)/json_login_.c $(OBJDIR)/json_login.h  $(SRCDIR)/config.h
          729  +	$(XTCC) -o $(OBJDIR)/json_login.o -c $(OBJDIR)/json_login_.c
          730  +
          731  +json_login.h:	$(OBJDIR)/headers
          732  +$(OBJDIR)/json_query_.c:	$(SRCDIR)/json_query.c $(OBJDIR)/translate
          733  +	$(TRANSLATE) $(SRCDIR)/json_query.c >$(OBJDIR)/json_query_.c
          734  +
          735  +$(OBJDIR)/json_query.o:	$(OBJDIR)/json_query_.c $(OBJDIR)/json_query.h  $(SRCDIR)/config.h
          736  +	$(XTCC) -o $(OBJDIR)/json_query.o -c $(OBJDIR)/json_query_.c
          737  +
          738  +json_query.h:	$(OBJDIR)/headers
          739  +$(OBJDIR)/json_report_.c:	$(SRCDIR)/json_report.c $(OBJDIR)/translate
          740  +	$(TRANSLATE) $(SRCDIR)/json_report.c >$(OBJDIR)/json_report_.c
          741  +
          742  +$(OBJDIR)/json_report.o:	$(OBJDIR)/json_report_.c $(OBJDIR)/json_report.h  $(SRCDIR)/config.h
          743  +	$(XTCC) -o $(OBJDIR)/json_report.o -c $(OBJDIR)/json_report_.c
          744  +
          745  +json_report.h:	$(OBJDIR)/headers
          746  +$(OBJDIR)/json_tag_.c:	$(SRCDIR)/json_tag.c $(OBJDIR)/translate
          747  +	$(TRANSLATE) $(SRCDIR)/json_tag.c >$(OBJDIR)/json_tag_.c
          748  +
          749  +$(OBJDIR)/json_tag.o:	$(OBJDIR)/json_tag_.c $(OBJDIR)/json_tag.h  $(SRCDIR)/config.h
          750  +	$(XTCC) -o $(OBJDIR)/json_tag.o -c $(OBJDIR)/json_tag_.c
          751  +
          752  +json_tag.h:	$(OBJDIR)/headers
          753  +$(OBJDIR)/json_timeline_.c:	$(SRCDIR)/json_timeline.c $(OBJDIR)/translate
          754  +	$(TRANSLATE) $(SRCDIR)/json_timeline.c >$(OBJDIR)/json_timeline_.c
          755  +
          756  +$(OBJDIR)/json_timeline.o:	$(OBJDIR)/json_timeline_.c $(OBJDIR)/json_timeline.h  $(SRCDIR)/config.h
          757  +	$(XTCC) -o $(OBJDIR)/json_timeline.o -c $(OBJDIR)/json_timeline_.c
          758  +
          759  +json_timeline.h:	$(OBJDIR)/headers
          760  +$(OBJDIR)/json_user_.c:	$(SRCDIR)/json_user.c $(OBJDIR)/translate
          761  +	$(TRANSLATE) $(SRCDIR)/json_user.c >$(OBJDIR)/json_user_.c
          762  +
          763  +$(OBJDIR)/json_user.o:	$(OBJDIR)/json_user_.c $(OBJDIR)/json_user.h  $(SRCDIR)/config.h
          764  +	$(XTCC) -o $(OBJDIR)/json_user.o -c $(OBJDIR)/json_user_.c
          765  +
          766  +json_user.h:	$(OBJDIR)/headers
          767  +$(OBJDIR)/json_wiki_.c:	$(SRCDIR)/json_wiki.c $(OBJDIR)/translate
          768  +	$(TRANSLATE) $(SRCDIR)/json_wiki.c >$(OBJDIR)/json_wiki_.c
          769  +
          770  +$(OBJDIR)/json_wiki.o:	$(OBJDIR)/json_wiki_.c $(OBJDIR)/json_wiki.h  $(SRCDIR)/config.h
          771  +	$(XTCC) -o $(OBJDIR)/json_wiki.o -c $(OBJDIR)/json_wiki_.c
          772  +
          773  +json_wiki.h:	$(OBJDIR)/headers
   664    774   $(OBJDIR)/leaf_.c:	$(SRCDIR)/leaf.c $(OBJDIR)/translate
   665    775   	$(TRANSLATE) $(SRCDIR)/leaf.c >$(OBJDIR)/leaf_.c
   666    776   
   667    777   $(OBJDIR)/leaf.o:	$(OBJDIR)/leaf_.c $(OBJDIR)/leaf.h  $(SRCDIR)/config.h
   668    778   	$(XTCC) -o $(OBJDIR)/leaf.o -c $(OBJDIR)/leaf_.c
   669    779   
   670    780   leaf.h:	$(OBJDIR)/headers
................................................................................
   968   1078   $(OBJDIR)/zip.o:	$(OBJDIR)/zip_.c $(OBJDIR)/zip.h  $(SRCDIR)/config.h
   969   1079   	$(XTCC) -o $(OBJDIR)/zip.o -c $(OBJDIR)/zip_.c
   970   1080   
   971   1081   zip.h:	$(OBJDIR)/headers
   972   1082   $(OBJDIR)/sqlite3.o:	$(SRCDIR)/sqlite3.c
   973   1083   	$(XTCC) -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_STAT3 -Dlocaltime=fossil_localtime -DSQLITE_ENABLE_LOCKING_STYLE=0 -c $(SRCDIR)/sqlite3.c -o $(OBJDIR)/sqlite3.o
   974   1084   
         1085  +$(OBJDIR)/cson_amalgamation.o:	$(SRCDIR)/cson_amalgamation.c
         1086  +	$(XTCC)  -c $(SRCDIR)/cson_amalgamation.c -o $(OBJDIR)/cson_amalgamation.o -DCSON_FOSSIL_MODE
         1087  +
         1088  +$(OBJDIR)/json.o $(OBJDIR)/json_artifact.o $(OBJDIR)/json_branch.o $(OBJDIR)/json_diff.o $(OBJDIR)/json_login.o $(OBJDIR)/json_query.o $(OBJDIR)/json_report.o $(OBJDIR)/json_tag.o $(OBJDIR)/json_timeline.o $(OBJDIR)/json_user.o $(OBJDIR)/json_wiki.o : $(SRCDIR)/json_detail.h
   975   1089   $(OBJDIR)/shell.o:	$(SRCDIR)/shell.c $(SRCDIR)/sqlite3.h
   976   1090   	$(XTCC) -Dmain=sqlite3_shell -DSQLITE_OMIT_LOAD_EXTENSION=1 -c $(SRCDIR)/shell.c -o $(OBJDIR)/shell.o
   977   1091   
   978   1092   $(OBJDIR)/th.o:	$(SRCDIR)/th.c
   979   1093   	$(XTCC) -I$(SRCDIR) -c $(SRCDIR)/th.c -o $(OBJDIR)/th.o
   980   1094   
   981   1095   $(OBJDIR)/th_lang.o:	$(SRCDIR)/th_lang.c
   982   1096   	$(XTCC) -I$(SRCDIR) -c $(SRCDIR)/th_lang.c -o $(OBJDIR)/th_lang.o
   983   1097   

Changes to win/Makefile.msc.

    34     34   BCC    = $(CC) $(CFLAGS)
    35     35   TCC    = $(CC) -c $(CFLAGS) $(MSCDEF) $(SSL) $(INCL)
    36     36   LIBS   = $(ZLIB) ws2_32.lib advapi32.lib $(SSLLIB)
    37     37   LIBDIR = -LIBPATH:$(MSCDIR)\extra\lib -LIBPATH:$(ZLIBDIR)
    38     38   
    39     39   SQLITE_OPTIONS = /DSQLITE_OMIT_LOAD_EXTENSION=1 /DSQLITE_THREADSAFE=0 /DSQLITE_DEFAULT_FILE_FORMAT=4 /DSQLITE_ENABLE_STAT3 /Dlocaltime=fossil_localtime /DSQLITE_ENABLE_LOCKING_STYLE=0
    40     40   
    41         -SRC   = add_.c allrepo_.c attach_.c bag_.c bisect_.c blob_.c branch_.c browse_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c db_.c delta_.c deltacmd_.c descendants_.c diff_.c diffcmd_.c doc_.c encode_.c event_.c export_.c file_.c finfo_.c glob_.c graph_.c gzip_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c leaf_.c login_.c main_.c manifest_.c md5_.c merge_.c merge3_.c name_.c path_.c pivot_.c popen_.c pqueue_.c printf_.c rebuild_.c report_.c rss_.c schema_.c search_.c setup_.c sha1_.c shun_.c skins_.c sqlcmd_.c stash_.c stat_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c update_.c url_.c user_.c verify_.c vfile_.c wiki_.c wikiformat_.c winhttp_.c xfer_.c zip_.c 
           41  +SRC   = add_.c allrepo_.c attach_.c bag_.c bisect_.c blob_.c branch_.c browse_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c db_.c delta_.c deltacmd_.c descendants_.c diff_.c diffcmd_.c doc_.c encode_.c event_.c export_.c file_.c finfo_.c glob_.c graph_.c gzip_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c json_.c json_artifact_.c json_branch_.c json_diff_.c json_login_.c json_query_.c json_report_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c login_.c main_.c manifest_.c md5_.c merge_.c merge3_.c name_.c path_.c pivot_.c popen_.c pqueue_.c printf_.c rebuild_.c report_.c rss_.c schema_.c search_.c setup_.c sha1_.c shun_.c skins_.c sqlcmd_.c stash_.c stat_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c update_.c url_.c user_.c verify_.c vfile_.c wiki_.c wikiformat_.c winhttp_.c xfer_.c zip_.c 
    42     42   
    43         -OBJ   = $(OX)\add$O $(OX)\allrepo$O $(OX)\attach$O $(OX)\bag$O $(OX)\bisect$O $(OX)\blob$O $(OX)\branch$O $(OX)\browse$O $(OX)\captcha$O $(OX)\cgi$O $(OX)\checkin$O $(OX)\checkout$O $(OX)\clearsign$O $(OX)\clone$O $(OX)\comformat$O $(OX)\configure$O $(OX)\content$O $(OX)\db$O $(OX)\delta$O $(OX)\deltacmd$O $(OX)\descendants$O $(OX)\diff$O $(OX)\diffcmd$O $(OX)\doc$O $(OX)\encode$O $(OX)\event$O $(OX)\export$O $(OX)\file$O $(OX)\finfo$O $(OX)\glob$O $(OX)\graph$O $(OX)\gzip$O $(OX)\http$O $(OX)\http_socket$O $(OX)\http_ssl$O $(OX)\http_transport$O $(OX)\import$O $(OX)\info$O $(OX)\leaf$O $(OX)\login$O $(OX)\main$O $(OX)\manifest$O $(OX)\md5$O $(OX)\merge$O $(OX)\merge3$O $(OX)\name$O $(OX)\path$O $(OX)\pivot$O $(OX)\popen$O $(OX)\pqueue$O $(OX)\printf$O $(OX)\rebuild$O $(OX)\report$O $(OX)\rss$O $(OX)\schema$O $(OX)\search$O $(OX)\setup$O $(OX)\sha1$O $(OX)\shun$O $(OX)\skins$O $(OX)\sqlcmd$O $(OX)\stash$O $(OX)\stat$O $(OX)\style$O $(OX)\sync$O $(OX)\tag$O $(OX)\tar$O $(OX)\th_main$O $(OX)\timeline$O $(OX)\tkt$O $(OX)\tktsetup$O $(OX)\undo$O $(OX)\update$O $(OX)\url$O $(OX)\user$O $(OX)\verify$O $(OX)\vfile$O $(OX)\wiki$O $(OX)\wikiformat$O $(OX)\winhttp$O $(OX)\xfer$O $(OX)\zip$O $(OX)\shell$O $(OX)\sqlite3$O $(OX)\th$O $(OX)\th_lang$O 
           43  +OBJ   = $(OX)\add$O $(OX)\allrepo$O $(OX)\attach$O $(OX)\bag$O $(OX)\bisect$O $(OX)\blob$O $(OX)\branch$O $(OX)\browse$O $(OX)\captcha$O $(OX)\cgi$O $(OX)\checkin$O $(OX)\checkout$O $(OX)\clearsign$O $(OX)\clone$O $(OX)\comformat$O $(OX)\configure$O $(OX)\content$O $(OX)\db$O $(OX)\delta$O $(OX)\deltacmd$O $(OX)\descendants$O $(OX)\diff$O $(OX)\diffcmd$O $(OX)\doc$O $(OX)\encode$O $(OX)\event$O $(OX)\export$O $(OX)\file$O $(OX)\finfo$O $(OX)\glob$O $(OX)\graph$O $(OX)\gzip$O $(OX)\http$O $(OX)\http_socket$O $(OX)\http_ssl$O $(OX)\http_transport$O $(OX)\import$O $(OX)\info$O $(OX)\json$O $(OX)\json_artifact$O $(OX)\json_branch$O $(OX)\json_diff$O $(OX)\json_login$O $(OX)\json_query$O $(OX)\json_report$O $(OX)\json_tag$O $(OX)\json_timeline$O $(OX)\json_user$O $(OX)\json_wiki$O $(OX)\leaf$O $(OX)\login$O $(OX)\main$O $(OX)\manifest$O $(OX)\md5$O $(OX)\merge$O $(OX)\merge3$O $(OX)\name$O $(OX)\path$O $(OX)\pivot$O $(OX)\popen$O $(OX)\pqueue$O $(OX)\printf$O $(OX)\rebuild$O $(OX)\report$O $(OX)\rss$O $(OX)\schema$O $(OX)\search$O $(OX)\setup$O $(OX)\sha1$O $(OX)\shun$O $(OX)\skins$O $(OX)\sqlcmd$O $(OX)\stash$O $(OX)\stat$O $(OX)\style$O $(OX)\sync$O $(OX)\tag$O $(OX)\tar$O $(OX)\th_main$O $(OX)\timeline$O $(OX)\tkt$O $(OX)\tktsetup$O $(OX)\undo$O $(OX)\update$O $(OX)\url$O $(OX)\user$O $(OX)\verify$O $(OX)\vfile$O $(OX)\wiki$O $(OX)\wikiformat$O $(OX)\winhttp$O $(OX)\xfer$O $(OX)\zip$O $(OX)\shell$O $(OX)\sqlite3$O $(OX)\th$O $(OX)\th_lang$O 
    44     44   
    45     45   
    46     46   APPNAME = $(OX)\fossil$(E)
    47     47   
    48     48   all: $(OX) $(APPNAME)
    49     49   
    50     50   $(APPNAME) : translate$E mkindex$E headers $(OBJ) $(OX)\linkopts
................................................................................
    86     86   	echo $(OX)\gzip.obj >> $@
    87     87   	echo $(OX)\http.obj >> $@
    88     88   	echo $(OX)\http_socket.obj >> $@
    89     89   	echo $(OX)\http_ssl.obj >> $@
    90     90   	echo $(OX)\http_transport.obj >> $@
    91     91   	echo $(OX)\import.obj >> $@
    92     92   	echo $(OX)\info.obj >> $@
           93  +	echo $(OX)\json.obj >> $@
           94  +	echo $(OX)\json_artifact.obj >> $@
           95  +	echo $(OX)\json_branch.obj >> $@
           96  +	echo $(OX)\json_diff.obj >> $@
           97  +	echo $(OX)\json_login.obj >> $@
           98  +	echo $(OX)\json_query.obj >> $@
           99  +	echo $(OX)\json_report.obj >> $@
          100  +	echo $(OX)\json_tag.obj >> $@
          101  +	echo $(OX)\json_timeline.obj >> $@
          102  +	echo $(OX)\json_user.obj >> $@
          103  +	echo $(OX)\json_wiki.obj >> $@
    93    104   	echo $(OX)\leaf.obj >> $@
    94    105   	echo $(OX)\login.obj >> $@
    95    106   	echo $(OX)\main.obj >> $@
    96    107   	echo $(OX)\manifest.obj >> $@
    97    108   	echo $(OX)\md5.obj >> $@
    98    109   	echo $(OX)\merge.obj >> $@
    99    110   	echo $(OX)\merge3.obj >> $@
................................................................................
   168    179   	$(TCC) /Fo$@ -c $**
   169    180   
   170    181   $(OX)\th_lang$O : $(SRCDIR)\th_lang.c
   171    182   	$(TCC) /Fo$@ -c $**
   172    183   
   173    184   VERSION.h : mkversion$E $B\manifest.uuid $B\manifest $B\VERSION
   174    185   	$** > $@
          186  +$(OBJDIR)\cson_amalgamation.h : $(SRCDIR)\cson_amalgamation.h
          187  +	cp $(SRCDIR)\cson_amalgamation.h $@
   175    188   
   176    189   page_index.h: mkindex$E $(SRC) 
   177    190   	$** > $@
   178    191   
   179    192   clean:
   180    193   	-del $(OX)\*.obj
   181    194   	-del *.obj *_.c *.h *.map
   182    195   	-del headers linkopts
   183    196   
   184    197   realclean:
   185    198   	-del $(APPNAME) translate$E mkindex$E makeheaders$E mkversion$E
   186    199   
          200  +$(OBJDIR)\json$O : $(SRCDIR)\json_detail.h
          201  +$(OBJDIR)\json_artifact$O : $(SRCDIR)\json_detail.h
          202  +$(OBJDIR)\json_branch$O : $(SRCDIR)\json_detail.h
          203  +$(OBJDIR)\json_diff$O : $(SRCDIR)\json_detail.h
          204  +$(OBJDIR)\json_login$O : $(SRCDIR)\json_detail.h
          205  +$(OBJDIR)\json_query$O : $(SRCDIR)\json_detail.h
          206  +$(OBJDIR)\json_report$O : $(SRCDIR)\json_detail.h
          207  +$(OBJDIR)\json_tag$O : $(SRCDIR)\json_detail.h
          208  +$(OBJDIR)\json_timeline$O : $(SRCDIR)\json_detail.h
          209  +$(OBJDIR)\json_user$O : $(SRCDIR)\json_detail.h
          210  +$(OBJDIR)\json_wiki$O : $(SRCDIR)\json_detail.h
          211  +
   187    212   
   188    213   $(OX)\add$O : add_.c add.h
   189    214   	$(TCC) /Fo$@ -c add_.c
   190    215   
   191    216   add_.c : $(SRCDIR)\add.c
   192    217   	translate$E $** > $@
   193    218   
................................................................................
   408    433   	translate$E $** > $@
   409    434   
   410    435   $(OX)\info$O : info_.c info.h
   411    436   	$(TCC) /Fo$@ -c info_.c
   412    437   
   413    438   info_.c : $(SRCDIR)\info.c
   414    439   	translate$E $** > $@
          440  +
          441  +$(OX)\json$O : json_.c json.h
          442  +	$(TCC) /Fo$@ -c json_.c
          443  +
          444  +json_.c : $(SRCDIR)\json.c
          445  +	translate$E $** > $@
          446  +
          447  +$(OX)\json_artifact$O : json_artifact_.c json_artifact.h
          448  +	$(TCC) /Fo$@ -c json_artifact_.c
          449  +
          450  +json_artifact_.c : $(SRCDIR)\json_artifact.c
          451  +	translate$E $** > $@
          452  +
          453  +$(OX)\json_branch$O : json_branch_.c json_branch.h
          454  +	$(TCC) /Fo$@ -c json_branch_.c
          455  +
          456  +json_branch_.c : $(SRCDIR)\json_branch.c
          457  +	translate$E $** > $@
          458  +
          459  +$(OX)\json_diff$O : json_diff_.c json_diff.h
          460  +	$(TCC) /Fo$@ -c json_diff_.c
          461  +
          462  +json_diff_.c : $(SRCDIR)\json_diff.c
          463  +	translate$E $** > $@
          464  +
          465  +$(OX)\json_login$O : json_login_.c json_login.h
          466  +	$(TCC) /Fo$@ -c json_login_.c
          467  +
          468  +json_login_.c : $(SRCDIR)\json_login.c
          469  +	translate$E $** > $@
          470  +
          471  +$(OX)\json_query$O : json_query_.c json_query.h
          472  +	$(TCC) /Fo$@ -c json_query_.c
          473  +
          474  +json_query_.c : $(SRCDIR)\json_query.c
          475  +	translate$E $** > $@
          476  +
          477  +$(OX)\json_report$O : json_report_.c json_report.h
          478  +	$(TCC) /Fo$@ -c json_report_.c
          479  +
          480  +json_report_.c : $(SRCDIR)\json_report.c
          481  +	translate$E $** > $@
          482  +
          483  +$(OX)\json_tag$O : json_tag_.c json_tag.h
          484  +	$(TCC) /Fo$@ -c json_tag_.c
          485  +
          486  +json_tag_.c : $(SRCDIR)\json_tag.c
          487  +	translate$E $** > $@
          488  +
          489  +$(OX)\json_timeline$O : json_timeline_.c json_timeline.h
          490  +	$(TCC) /Fo$@ -c json_timeline_.c
          491  +
          492  +json_timeline_.c : $(SRCDIR)\json_timeline.c
          493  +	translate$E $** > $@
          494  +
          495  +$(OX)\json_user$O : json_user_.c json_user.h
          496  +	$(TCC) /Fo$@ -c json_user_.c
          497  +
          498  +json_user_.c : $(SRCDIR)\json_user.c
          499  +	translate$E $** > $@
          500  +
          501  +$(OX)\json_wiki$O : json_wiki_.c json_wiki.h
          502  +	$(TCC) /Fo$@ -c json_wiki_.c
          503  +
          504  +json_wiki_.c : $(SRCDIR)\json_wiki.c
          505  +	translate$E $** > $@
   415    506   
   416    507   $(OX)\leaf$O : leaf_.c leaf.h
   417    508   	$(TCC) /Fo$@ -c leaf_.c
   418    509   
   419    510   leaf_.c : $(SRCDIR)\leaf.c
   420    511   	translate$E $** > $@
   421    512   
................................................................................
   674    765   $(OX)\zip$O : zip_.c zip.h
   675    766   	$(TCC) /Fo$@ -c zip_.c
   676    767   
   677    768   zip_.c : $(SRCDIR)\zip.c
   678    769   	translate$E $** > $@
   679    770   
   680    771   headers: makeheaders$E page_index.h VERSION.h
   681         -	makeheaders$E add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h event_.c:event.h export_.c:export.h file_.c:file.h finfo_.c:finfo.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h leaf_.c:leaf.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h path_.c:path.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h
          772  +	makeheaders$E add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h event_.c:event.h export_.c:export.h file_.c:file.h finfo_.c:finfo.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_diff_.c:json_diff.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h path_.c:path.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR)\cson_amalgamation.h
   682    773   	@copy /Y nul: headers