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 ' '), 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