Check-in [796dcfe072]
Not logged in

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

Overview
SHA1 Hash:796dcfe0728bb2bc865db84e50432f0205d1d2fd
Date: 2011-11-04 20:57:52
User: drh
Edited Comment: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.
Original Comment:Merge the json branch into trunk. Json is disabled by default for now. Use the --json option to configure, or set FOSSIL_ENABLE_JSON in the makefile to turn json processing on.
Tags And Properties
Changes

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.res > 53 } > 54 print("GOT RESPONSE: "+(('string'===typeof resp) ? resp : WhAjaj.stringi > 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 > 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.stringi > 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 > 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 = '+TestA > 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 = '+Tes > 140 assert( 'string' === typeof TestApp.fossil.auth.name, 'User name = '+TestApp > 141 TestApp.fossil.userName = null; > 142 TestApp.fossil.whoami('/json/whoami'); > 143 assert( 'string' === typeof TestApp.fossil.auth.name, 'User name = '+TestApp > 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), 'Requ > 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 e > 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 inter > 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 wi

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/jqu > 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 opera > 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 opera > 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.stri > 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: o > 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.t > 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 onRespon > 172 if( opt.url ) TheApp.jqe.textPath.val(opt.url.replace(WhAjaj.Connector.o > 173 TheApp.jqe.taResponse.val( val ); > 174 }; > 175 ajcb.onError = function(req,opt) { > 176 TheApp.jqe.taResponse.val( "ERROR SENDING REQUEST:\n"+WhAjaj.stringify(o > 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/1fXViveNhDbiXgCuE7QDX > 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()' > 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/HA > 224 <input type='button' value='version' onclick='TheApp.cgi.sendCommand("/json/vers > 225 <input type='button' value='stat' onclick='TheApp.cgi.sendCommand("/json/stat?fu > 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/ > 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/ > 234 <input type='button' value='timeline/ci' onclick='TheApp.cgi.sendCommand("/json/ > 235 <input type='button' value='timeline/wiki' onclick='TheApp.cgi.sendCommand("/jso > 236 <input type='button' value='timeline/ticket' onclick='TheApp.cgi.sendCommand("/j > 237 <input type='button' value='timeline/branch' onclick='TheApp.cgi.sendCommand("/j > 238 <input type='button' value='wiki/list' onclick='TheApp.cgi.sendCommand("/json/wi > 239 <input type='button' value='wiki/get Fossil' onclick='TheApp.cgi.sendCommand("/j > 240 <input type='button' value='wiki/get/Fossil' onclick='TheApp.cgi.sendCommand("/j > 241 > 242 <br/> > 243 > 244 <input type='button' value='user/list' onclick='TheApp.cgi.sendCommand("/json/us > 245 <input type='button' value='user/get' onclick='TheApp.cgi.sendCommand("/json/use > 246 <input type='button' value='tag/list' onclick='TheApp.cgi.sendCommand("/json/tag > 247 <input type='button' value='tag/list/json' onclick='TheApp.cgi.sendCommand("/jso > 248 <input type='button' value='tag/add' > 249 onclick='TheApp.cgi.sendCommand("/json/tag/add",{name:"json-add-tag-test",ch > 250 <input type='button' value='tag/cancel' > 251 onclick='TheApp.cgi.sendCommand("/json/tag/cancel",{name:"json-add-tag-test" > 252 <input type='button' value='tag/find' > 253 onclick='TheApp.cgi.sendCommand("/json/tag/find",{name:"json",type:"*",raw:f > 254 > 255 <br/> > 256 > 257 <input type='button' value='diff' > 258 onclick='TheApp.cgi.sendCommand("/json/diff",{v1:"b0e9b45baed6f885",v2:"5f22 > 259 <input type='button' value='diff/A/B' > 260 onclick='TheApp.cgi.sendCommand("/json/diff/b0e9b45baed6f885/5f225e261d83628 > 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 > 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/W > 280 <input type='button' value='get client data' onclick='TheApp.cgi.getPageClientDa > 281 <input type='button' value='save client data' onclick='TheApp.cgi.savePageClient > 282 --> > 283 <hr/> > 284 <b>Login:</b> > 285 <br/> > 286 <input type='button' value='Anon. PW' onclick='TheApp.cgi.sendCommand("/json/ano > 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") > 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.gene > 55 authToken:(this.auth ? this.auth.authToken : undefined), > 56 jsonp:('string' === typeof ajajOpt.jsonp) ? ajajOpt.jsonp : undefine > 57 }; > 58 } > 59 ajajOpt.method = req ? 'POST' : 'GET'; > 60 // just for debuggering: ajajOpt.method = 'POST'; if(!req) req={}; > 61 if(command) ajajOpt.url = this.ajaj.derivedOption('url',ajajOpt) + command; > 62 this.ajaj.sendRequest(req,ajajOpt); > 63 }; > 64 > 65 /** > 66 Sends a login request to the back-end. > 67 > 68 ajajOpt is an optional configuration object suitable for passing > 69 to sendCommand(). > 70 > 71 After the response returns, this.auth will be > 72 set to the response payload. > 73 > 74 If name === 'anonymous' (the default if none is passed in) then this > 75 function ignores the pw argument and must make two requests - the first > 76 one gets the captcha code and the second one submits it. > 77 ajajOpt.onResponse() (if set) is only called for the actual login > 78 response (the 2nd one), as opposed to being called for both requests. > 79 However, this.ajaj.callbacks.onResponse() _is_ called for both (because > 80 it happens at a lower level). > 81 > 82 If this object has an onLogin() function it is called (with > 83 no arguments) before the onResponse() handler of the login is called > 84 (that is the 2nd request for anonymous logins). > 85 > 86 */ > 87 FossilAjaj.prototype.login = function(name,pw,ajajOpt) { > 88 name = name || 'anonymous'; > 89 var self = this; > 90 var loginReq = { > 91 name:name, > 92 password:pw > 93 }; > 94 ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} ); > 95 var oldOnResponse = ajajOpt.onResponse; > 96 ajajOpt.onResponse = function(resp,req) { > 97 var thisOpt = this; > 98 //alert('login response:\n'+WhAjaj.stringify(resp)); > 99 if( resp && resp.payload ) { > 100 //self.userName = resp.payload.name; > 101 //self.capabilities = resp.payload.capabilities; > 102 self.auth = resp.payload; > 103 } > 104 if( WhAjaj.isFunction( self.onLogin ) ){ > 105 try{ self.onLogin(); } > 106 catch(e){} > 107 } > 108 if( WhAjaj.isFunction(oldOnResponse) ) { > 109 oldOnResponse.apply(thisOpt,[resp,req]); > 110 } > 111 }; > 112 function doLogin(){ > 113 //alert("Sending login request..."+WhAjaj.stringify(loginReq)); > 114 self.sendCommand('/json/login', loginReq, ajajOpt); > 115 } > 116 if( 'anonymous' === name ){ > 117 this.sendCommand('/json/anonymousPassword',undefined,{ > 118 onResponse:function(resp,req){ > 119 /* > 120 if( WhAjaj.isFunction(oldOnResponse) ){ > 121 oldOnResponse.apply(this, [resp,req]); > 122 }; > 123 */ > 124 if(resp && !resp.resultCode){ > 125 //alert("Got PW. Trying to log in..."+WhAjaj.stringify(resp)); > 126 loginReq.anonymousSeed = resp.payload.seed; > 127 loginReq.password = resp.payload.password; > 128 doLogin(); > 129 } > 130 } > 131 }); > 132 } > 133 else doLogin(); > 134 }; > 135 > 136 /** > 137 Logs out of fossil, invaliding this login token. > 138 > 139 ajajOpt is an optional configuration object suitable for passing > 140 to sendCommand(). > 141 > 142 If this object has an onLogout() function it is called (with > 143 no arguments) before the onResponse() handler is called. > 144 IFF the response succeeds then this.auth is unset. > 145 */ > 146 FossilAjaj.prototype.logout = function(ajajOpt) { > 147 var self = this; > 148 ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} ); > 149 var oldOnResponse = ajajOpt.onResponse; > 150 ajajOpt.onResponse = function(resp,req) { > 151 var thisOpt = this; > 152 self.auth = undefined; > 153 if( WhAjaj.isFunction( self.onLogout ) ){ > 154 try{ self.onLogout(); } > 155 catch(e){} > 156 } > 157 if( WhAjaj.isFunction(oldOnResponse) ) { > 158 oldOnResponse.apply(thisOpt,[resp,req]); > 159 } > 160 }; > 161 this.sendCommand('/json/logout', undefined, ajajOpt ); > 162 }; > 163 > 164 /** > 165 Sends a HAI request to the server. /json/HAI is an alias /json/version. > 166 > 167 ajajOpt is an optional configuration object suitable for passing > 168 to sendCommand(). > 169 */ > 170 FossilAjaj.prototype.HAI = function(ajajOpt) { > 171 this.sendCommand('/json/HAI', undefined, ajajOpt); > 172 }; > 173 > 174 > 175 /** > 176 Sends a /json/whoami request. Updates this.auth to contain > 177 the login info, removing them if the response does not contain > 178 that data. > 179 */ > 180 FossilAjaj.prototype.whoami = function(ajajOpt) { > 181 var self = this; > 182 ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} ); > 183 var oldOnResponse = ajajOpt.onResponse; > 184 ajajOpt.onResponse = function(resp,req) { > 185 var thisOpt = this; > 186 if( resp && resp.payload ){ > 187 if(!self.auth || (self.auth.authToken!==resp.payload.authToken)){ > 188 self.auth = resp.payload; > 189 if( WhAjaj.isFunction(self.onLogin) ){ > 190 self.onLogin(); > 191 } > 192 } > 193 } > 194 else { delete self.auth; } > 195 if( WhAjaj.isFunction(oldOnResponse) ) { > 196 oldOnResponse.apply(thisOpt,[resp,req]); > 197 } > 198 }; > 199 self.sendCommand('/json/whoami', undefined, ajajOpt); > 200 }; > 201 > 202 /** > 203 EXPERIMENTAL concrete WhAjaj.Connector.sendImpl() implementation which > 204 uses Rhino to connect to a local fossil binary for input and output. Its > 205 signature and semantics are as described for > 206 WhAjaj.Connector.prototype.sendImpl(), with a few exceptions and > 207 additions: > 208 > 209 - It does not support timeouts or asynchronous mode. > 210 > 211 - The args.fossilBinary property must point to the local fossil binary > 212 (it need not be a complete path if fossil is in the $PATH). This > 213 function throws (without calling any request callbacks) if > 214 args.fossilBinary is not set. fossilBinary may be set on > 215 WhAjaj.Connector.options.ajax, in the FossilAjaj constructor call, as > 216 the ajax options parameter to any of the FossilAjaj.sendCommand() family > 217 of functions, or by setting > 218 aFossilAjajInstance.ajaj.options.fossilBinary on a specific > 219 FossilAjaj instance. > 220 > 221 - It uses the args.url field to create the "command" property of the > 222 request, constructs a request envelope, spawns a fossil process in JSON > 223 mode, feeds it the request envelope, and returns the response envelope > 224 via the same mechanisms defined for the HTTP-based implementations. > 225 > 226 The interface is otherwise compatible with the "normal" > 227 FossilAjaj.sendCommand() front-end (it is, however, fossil-specific, and > 228 not back-end agnostic like the WhAjaj.sendImpl() interface intends). > 229 > 230 > 231 */ > 232 FossilAjaj.rhinoLocalBinarySendImpl = function(request,args){ > 233 var self = this; > 234 request = request || {}; > 235 if(!args.fossilBinary){ > 236 throw new Error("fossilBinary is not set on AJAX options!"); > 237 } > 238 var url = args.url.split('?')[0].split(/\/+/); > 239 if(url.length>1){ > 240 // 3x shift(): protocol, host, 'json' part of path > 241 request.command = (url.shift(),url.shift(),url.shift(), url.join('/')); > 242 } > 243 delete args.url; > 244 //print("rhinoLocalBinarySendImpl SENDING: "+WhAjaj.stringify(request)); > 245 var json; > 246 try{ > 247 var pargs = [args.fossilBinary, 'json', '--json-input', '-']; > 248 var p = java.lang.Runtime.getRuntime().exec(pargs); > 249 var outs = p.getOutputStream(); > 250 var osr = new java.io.OutputStreamWriter(outs); > 251 var osb = new java.io.BufferedWriter(osr); > 252 > 253 json = JSON.stringify(request); > 254 osb.write(json,0, json.length); > 255 osb.close(); > 256 var ins = p.getInputStream(); > 257 var isr = new java.io.InputStreamReader(ins); > 258 var br = new java.io.BufferedReader(isr); > 259 var line; > 260 json = []; > 261 while( null !== (line=br.readLine())){ > 262 json.push(line); > 263 } > 264 ins.close(); > 265 }catch(e){ > 266 args.errorMessage = e.toString(); > 267 WhAjaj.Connector.sendHelper.onSendError.apply( self, [request, args] ); > 268 return undefined; > 269 } > 270 json = json.join(''); > 271 //print("READ IN JSON: "+json); > 272 WhAjaj.Connector.sendHelper.onSendSuccess.apply( self, [request, json, args] > 273 }/*rhinoLocalBinary*/

Added ajax/js/json2.js

> 1 /* > 2 http://www.JSON.org/json2.js > 3 2009-06-29 > 4 > 5 Public Domain. > 6 > 7 NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. > 8 > 9 See http://www.JSON.org/js.html > 10 > 11 This file creates a global JSON object containing two methods: stringify > 12 and parse. > 13 > 14 JSON.stringify(value, replacer, space) > 15 value any JavaScript value, usually an object or array. > 16 > 17 replacer an optional parameter that determines how object > 18 values are stringified for objects. It can be a > 19 function or an array of strings. > 20 > 21 space an optional parameter that specifies the indentation > 22 of nested structures. If it is omitted, the text will > 23 be packed without extra whitespace. If it is a number, > 24 it will specify the number of spaces to indent at each > 25 level. If it is a string (such as '\t' or '&nbsp;'), > 26 it contains the characters used to indent at each level. > 27 > 28 This method produces a JSON text from a JavaScript value. > 29 > 30 When an object value is found, if the object contains a toJSON > 31 method, its toJSON method will be called and the result will be > 32 stringified. A toJSON method does not serialize: it returns the > 33 value represented by the name/value pair that should be serialized, > 34 or undefined if nothing should be serialized. The toJSON method > 35 will be passed the key associated with the value, and this will be > 36 bound to the object holding the key. > 37 > 38 For example, this would serialize Dates as ISO strings. > 39 > 40 Date.prototype.toJSON = function (key) { > 41 function f(n) { > 42 // Format integers to have at least two digits. > 43 return n < 10 ? '0' + n : n; > 44 } > 45 > 46 return this.getUTCFullYear() + '-' + > 47 f(this.getUTCMonth() + 1) + '-' + > 48 f(this.getUTCDate()) + 'T' + > 49 f(this.getUTCHours()) + ':' + > 50 f(this.getUTCMinutes()) + ':' + > 51 f(this.getUTCSeconds()) + 'Z'; > 52 }; > 53 > 54 You can provide an optional replacer method. It will be passed the > 55 key and value of each member, with this bound to the containing > 56 object. The value that is returned from your method will be > 57 serialized. If your method returns undefined, then the member will > 58 be excluded from the serialization. > 59 > 60 If the replacer parameter is an array of strings, then it will be > 61 used to select the members to be serialized. It filters the results > 62 such that only members with keys listed in the replacer array are > 63 stringified. > 64 > 65 Values that do not have JSON representations, such as undefined or > 66 functions, will not be serialized. Such values in objects will be > 67 dropped; in arrays they will be replaced with null. You can use > 68 a replacer function to replace those with JSON values. > 69 JSON.stringify(undefined) returns undefined. > 70 > 71 The optional space parameter produces a stringification of the > 72 value that is filled with line breaks and indentation to make it > 73 easier to read. > 74 > 75 If the space parameter is a non-empty string, then that string will > 76 be used for indentation. If the space parameter is a number, then > 77 the indentation will be that many spaces. > 78 > 79 Example: > 80 > 81 text = JSON.stringify(['e', {pluribus: 'unum'}]); > 82 // text is '["e",{"pluribus":"unum"}]' > 83 > 84 > 85 text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); > 86 // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' > 87 > 88 text = JSON.stringify([new Date()], function (key, value) { > 89 return this[key] instanceof Date ? > 90 'Date(' + this[key] + ')' : value; > 91 }); > 92 // text is '["Date(---current time---)"]' > 93 > 94 > 95 JSON.parse(text, reviver) > 96 This method parses a JSON text to produce an object or array. > 97 It can throw a SyntaxError exception. > 98 > 99 The optional reviver parameter is a function that can filter and > 100 transform the results. It receives each of the keys and values, > 101 and its return value is used instead of the original value. > 102 If it returns what it received, then the structure is not modified. > 103 If it returns undefined then the member is deleted. > 104 > 105 Example: > 106 > 107 // Parse the text. Values that look like ISO date strings will > 108 // be converted to Date objects. > 109 > 110 myData = JSON.parse(text, function (key, value) { > 111 var a; > 112 if (typeof value === 'string') { > 113 a = > 114 /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); > 115 if (a) { > 116 return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], > 117 +a[5], +a[6])); > 118 } > 119 } > 120 return value; > 121 }); > 122 > 123 myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { > 124 var d; > 125 if (typeof value === 'string' && > 126 value.slice(0, 5) === 'Date(' && > 127 value.slice(-1) === ')') { > 128 d = new Date(value.slice(5, -1)); > 129 if (d) { > 130 return d; > 131 } > 132 } > 133 return value; > 134 }); > 135 > 136 > 137 This is a reference implementation. You are free to copy, modify, or > 138 redistribute. > 139 > 140 This code should be minified before deployment. > 141 See http://javascript.crockford.com/jsmin.html > 142 > 143 USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO > 144 NOT CONTROL. > 145 */ > 146 > 147 /*jslint evil: true */ > 148 > 149 /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, > 150 call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, > 151 getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, > 152 lastIndex, length, parse, prototype, push, replace, slice, stringify, > 153 test, toJSON, toString, valueOf > 154 */ > 155 > 156 // Create a JSON object only if one does not already exist. We create the > 157 // methods in a closure to avoid creating global variables. > 158 > 159 var JSON = JSON || {}; > 160 > 161 (function () { > 162 > 163 function f(n) { > 164 // Format integers to have at least two digits. > 165 return n < 10 ? '0' + n : n; > 166 } > 167 > 168 if (typeof Date.prototype.toJSON !== 'function') { > 169 > 170 Date.prototype.toJSON = function (key) { > 171 > 172 return isFinite(this.valueOf()) ? > 173 this.getUTCFullYear() + '-' + > 174 f(this.getUTCMonth() + 1) + '-' + > 175 f(this.getUTCDate()) + 'T' + > 176 f(this.getUTCHours()) + ':' + > 177 f(this.getUTCMinutes()) + ':' + > 178 f(this.getUTCSeconds()) + 'Z' : null; > 179 }; > 180 > 181 String.prototype.toJSON = > 182 Number.prototype.toJSON = > 183 Boolean.prototype.toJSON = function (key) { > 184 return this.valueOf(); > 185 }; > 186 } > 187 > 188 var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u > 189 escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b > 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 tim > 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.requestI > 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 dat > 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:\ > 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/JSO > 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 > 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 "+(n > 776 WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, > 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, [re > 798 return; > 799 } > 800 else > 801 { > 802 if( undefined === args.errorMessage ) > 803 { > 804 args.errorMessage = "Error sending a '"+args.method+"' A > 805 +"["+args.url+"]: " > 806 +"Status text=["+xhr.statusText+"]" > 807 ; > 808 WhAjaj.Connector.sendHelper.onSendError.apply( whself, [ > 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) && ('watchX > 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.log > 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= > 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, arg > 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, [requ > 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 : > 904 username: (undefined !== args.loginName) ? args.loginName : undefine > 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 > 910 +"Status text=["+textStatus+"]" > 911 +(errorThrown ? ("Error=["+errorThrown+"]") : "") > 912 ; > 913 WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, > 914 }, > 915 success: function(data) > 916 { > 917 WhAjaj.Connector.sendHelper.onSendSuccess.apply( whself, [reques > 918 }, > 919 /* Set dataType=text instead of json to keep jQuery from doing our c > 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, arg > 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, [reques > 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 > 1002 v = self.cookies[k]; > 1003 conn.addRequestProperty("Cookie", encodeURIComponent(k)+'='+enco > 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 > 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.getInputStre > 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, a > 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 Pro > 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 > 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.XMLHttpReques > 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.XMLHttpRequ > 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/jqu > 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 opera > 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 opera > 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.stri > 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: o > 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(o > 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") > 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/ > 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/W > 349 <input type='button' value='get client data' onclick='TheApp.cgi.getPageClientDa > 350 <input type='button' value='save client data' onclick='TheApp.cgi.savePageClient > 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.createNew > 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

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

Added src/Makefile

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

Changes to src/blob.c

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

Changes to src/captcha.c

410 sqlite3_randomness(sizeof(x), &x); 410 sqlite3_randomness(sizeof(x), &x); 411 x &= 0x7fffffff; 411 x &= 0x7fffffff; 412 return x; 412 return x; 413 } 413 } 414 414 415 /* 415 /* 416 ** Translate a captcha seed value into the captcha password string. 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 const char *zSecret; 421 const char *zSecret; 420 const char *z; 422 const char *z; 421 Blob b; 423 Blob b; 422 static char zRes[20]; 424 static char zRes[20]; 423 425 424 zSecret = db_get("captcha-secret", 0); 426 zSecret = db_get("captcha-secret", 0); 425 if( zSecret==0 ){ 427 if( zSecret==0 ){

Changes to src/cgi.c

506 }else{ 506 }else{ 507 if( *z ){ *z++ = 0; } 507 if( *z ){ *z++ = 0; } 508 zValue = ""; 508 zValue = ""; 509 } 509 } 510 if( fossil_islower(zName[0]) ){ 510 if( fossil_islower(zName[0]) ){ 511 cgi_set_parameter_nocopy(zName, zValue); 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 ** *pz is a string that consists of multiple lines of text. This 520 ** *pz is a string that consists of multiple lines of text. This 518 ** routine finds the end of the current line of text and converts 521 ** routine finds the end of the current line of text and converts 519 ** the "\n" or "\r\n" that ends that line into a "\000". It then 522 ** the "\n" or "\r\n" that ends that line into a "\000". It then ................................................................................................................................................................................ 678 cgi_set_parameter_nocopy(mprintf("%s:mimetype",zName), z); 681 cgi_set_parameter_nocopy(mprintf("%s:mimetype",zName), z); 679 } 682 } 680 } 683 } 681 } 684 } 682 } 685 } 683 } 686 } 684 } 687 } 685 688 > 689 > 690 #ifdef FOSSIL_ENABLE_JSON 686 /* 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 > 766 > 767 /* 687 ** Initialize the query parameter database. Information is pulled from 768 ** Initialize the query parameter database. Information is pulled from 688 ** the QUERY_STRING environment variable (if it exists), from standard 769 ** the QUERY_STRING environment variable (if it exists), from standard 689 ** input if there is POST data, and from HTTP_COOKIE. 770 ** input if there is POST data, and from HTTP_COOKIE. 690 */ 771 */ 691 void cgi_init(void){ 772 void cgi_init(void){ 692 char *z; 773 char *z; 693 const char *zType; 774 const char *zType; 694 int len; 775 int len; > 776 #ifdef FOSSIL_ENABLE_JSON > 777 json_main_bootstrap(); > 778 #endif > 779 g.isHTTP = 1; 695 cgi_destination(CGI_BODY); 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 z = (char*)P("QUERY_STRING"); 788 z = (char*)P("QUERY_STRING"); 697 if( z ){ 789 if( z ){ 698 z = mprintf("%s",z); 790 z = mprintf("%s",z); 699 add_param_list(z, '&'); 791 add_param_list(z, '&'); 700 } 792 } 701 793 702 z = (char*)P("REMOTE_ADDR"); 794 z = (char*)P("REMOTE_ADDR"); > 795 if( z ){ 703 if( z ) g.zIpAddr = mprintf("%s", z); | 796 g.zIpAddr = mprintf("%s", z); > 797 } 704 798 705 len = atoi(PD("CONTENT_LENGTH", "0")); 799 len = atoi(PD("CONTENT_LENGTH", "0")); 706 g.zContentType = zType = P("CONTENT_TYPE"); 800 g.zContentType = zType = P("CONTENT_TYPE"); 707 if( len>0 && zType ){ 801 if( len>0 && zType ){ 708 blob_zero(&g.cgiIn); 802 blob_zero(&g.cgiIn); 709 if( fossil_strcmp(zType,"application/x-www-form-urlencoded")==0 803 if( fossil_strcmp(zType,"application/x-www-form-urlencoded")==0 710 || strncmp(zType,"multipart/form-data",19)==0 ){ 804 || strncmp(zType,"multipart/form-data",19)==0 ){ ................................................................................................................................................................................ 720 blob_read_from_channel(&g.cgiIn, g.httpIn, len); 814 blob_read_from_channel(&g.cgiIn, g.httpIn, len); 721 blob_uncompress(&g.cgiIn, &g.cgiIn); 815 blob_uncompress(&g.cgiIn, &g.cgiIn); 722 }else if( fossil_strcmp(zType, "application/x-fossil-debug")==0 ){ 816 }else if( fossil_strcmp(zType, "application/x-fossil-debug")==0 ){ 723 blob_read_from_channel(&g.cgiIn, g.httpIn, len); 817 blob_read_from_channel(&g.cgiIn, g.httpIn, len); 724 }else if( fossil_strcmp(zType, "application/x-fossil-uncompressed")==0 ){ 818 }else if( fossil_strcmp(zType, "application/x-fossil-uncompressed")==0 ){ 725 blob_read_from_channel(&g.cgiIn, g.httpIn, len); 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: 727 } | 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. 728 | 832 729 z = (char*)P("HTTP_COOKIE"); < 730 if( z ){ < 731 z = mprintf("%s",z); < > 833 - After parsing POST JSON, copy the "first layer" of keys/values > 834 to cgi_setenv(), honoring the upper-case distinction used 732 add_param_list(z, ';'); | 835 in add_param_list(). However... 733 } | 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 */ > 845 } > 846 734 } 847 } 735 848 736 /* 849 /* 737 ** This is the comparison function used to sort the aParamQP[] array of 850 ** This is the comparison function used to sort the aParamQP[] array of 738 ** query parameters and cookies. 851 ** query parameters and cookies. 739 */ 852 */ 740 static int qparam_compare(const void *a, const void *b){ 853 static int qparam_compare(const void *a, const void *b){ ................................................................................................................................................................................ 940 1053 941 /* 1054 /* 942 ** Panic and die while processing a webpage. 1055 ** Panic and die while processing a webpage. 943 */ 1056 */ 944 NORETURN void cgi_panic(const char *zFormat, ...){ 1057 NORETURN void cgi_panic(const char *zFormat, ...){ 945 va_list ap; 1058 va_list ap; 946 cgi_reset_content(); 1059 cgi_reset_content(); > 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 { 947 cgi_set_status(500, "Internal Server Error"); | 1072 cgi_set_status(500, "Internal Server Error"); 948 cgi_printf( | 1073 cgi_printf( 949 "<html><body><h1>Internal Server Error</h1>\n" | 1074 "<html><body><h1>Internal Server Error</h1>\n" 950 "<plaintext>" | 1075 "<plaintext>" 951 ); | 1076 ); 952 va_start(ap, zFormat); | 1077 va_start(ap, zFormat); 953 vxprintf(pContent,zFormat,ap); | 1078 vxprintf(pContent,zFormat,ap); 954 va_end(ap); | 1079 va_end(ap); 955 cgi_reply(); | 1080 cgi_reply(); 956 fossil_exit(1); | 1081 fossil_exit(1); > 1082 } 957 } 1083 } 958 1084 959 /* 1085 /* 960 ** Remove the first space-delimited token from a string and return 1086 ** Remove the first space-delimited token from a string and return 961 ** a pointer to it. Add a NULL to the string to terminate the token. 1087 ** a pointer to it. Add a NULL to the string to terminate the token. 962 ** Make *zLeftOver point to the start of the next token. 1088 ** Make *zLeftOver point to the start of the next token. 963 */ 1089 */ ................................................................................................................................................................................ 990 */ 1116 */ 991 void cgi_handle_http_request(const char *zIpAddr){ 1117 void cgi_handle_http_request(const char *zIpAddr){ 992 char *z, *zToken; 1118 char *z, *zToken; 993 int i; 1119 int i; 994 struct sockaddr_in remoteName; 1120 struct sockaddr_in remoteName; 995 socklen_t size = sizeof(struct sockaddr_in); 1121 socklen_t size = sizeof(struct sockaddr_in); 996 char zLine[2000]; /* A single line of input. */ 1122 char zLine[2000]; /* A single line of input. */ 997 < 998 g.fullHttpReply = 1; 1123 g.fullHttpReply = 1; 999 if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ 1124 if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ 1000 malformed_request(); 1125 malformed_request(); 1001 } 1126 } 1002 zToken = extract_token(zLine, &z); 1127 zToken = extract_token(zLine, &z); 1003 if( zToken==0 ){ 1128 if( zToken==0 ){ 1004 malformed_request(); 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 fo > 98 JSON_T_ARRAY_BEGIN, JSON_T_ARRAY_END, JSON_T_OBJECT_BEGIN, JSON_T_OBJECT > 99 JSON_T_NULL, JSON_T_TRUE, and JSON_T_FALSE. String values are always ret > 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 > 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 unl > 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_par > 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 > 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 > 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@googlemai > 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.co > 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@transmissi > 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)(b > 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_comme > 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 > 505 space | { } [ ] : , " \ / + - . 0 | a b c d e f > 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 > 512 /*array AR*/ {AR,AR,-6,__,-5,-7,__,__,SB,__,CB,__,MX,__,ZX,IX,__,__,__,__,__,FA > 513 /*string ST*/ {ST,__,ST,ST,ST,ST,ST,ST,-4,EX,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST > 514 /*escape ES*/ {__,__,__,__,__,__,__,__,ST,ST,ST,__,__,__,__,__,__,ST,__,__,__,ST > 515 /*u1 U1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,U2,U2,U2,U2,U2,U2,U2,U2 > 516 /*u2 U2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,U3,U3,U3,U3,U3,U3,U3,U3 > 517 /*u3 U3*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,U4,U4,U4,U4,U4,U4,U4,U4 > 518 /*u4 U4*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,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,__ > 522 /*frac FR*/ {OK,OK,__,-8,__,-7,__,-3,__,__,CB,__,__,__,FR,FR,__,__,__,__,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*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__ > 527 /*tru T2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__ > 528 /*true T3*/ {__,__,__,__,__,__,__,__,__,__,CB,__,__,__,__,__,__,__,__,__,OK,__ > 529 /*fa F1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,F2,__,__,__,__,__ > 530 /*fal F2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__ > 531 /*fals F3*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__ > 532 /*false F4*/ {__,__,__,__,__,__,__,__,__,__,CB,__,__,__,__,__,__,__,__,__,OK,__ > 533 /*nu N1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__ > 534 /*nul N2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__ > 535 /*null N3*/ {__,__,__,__,__,__,__,__,__,__,CB,__,__,__,__,__,__,__,__,__,__,__ > 536 /*/ C1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__ > 537 /*/* C2*/ {C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2 > 538 /** C3*/ {C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,CE,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2 > 539 /*_. FX*/ {OK,OK,__,-8,__,-7,__,-3,__,__,__,__,__,__,FR,FR,__,__,__,__,E1,__ > 540 /*\ D1*/ {__,__,__,__,__,__,__,__,__,D2,__,__,__,__,__,__,__,__,__,__,__,__ > 541 /*\ D2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__ > 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: > 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->st > 696 jc->stack = (signed char*)JSON_parser_malloc(jc->malloc, bytes_t > 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 != > 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_buffe > 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 traili > 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, & > 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) + > 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 > 938 > 939 for (i = trail_bytes * 6 - 6; i >= 0; i -= 6) { > 940 jc->parse_buffer[jc->parse_buffer_count++] = (char) (((uc >> i) & 0x3F) > 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_clas > 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 > 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, NUL > 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, NUL > 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, N > 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, NU > 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 c8 > 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__); > 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/*ref > 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_emp > 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_d > 1584 static const cson_value_api cson_value_api_undef = { CSON_TYPE_UNDEF, cson_value > 1585 static const cson_value_api cson_value_api_bool = { CSON_TYPE_BOOL, cson_value_d > 1586 static const cson_value_api cson_value_api_integer = { CSON_TYPE_INTEGER, cson_v > 1587 static const cson_value_api cson_value_api_double = { CSON_TYPE_DOUBLE, cson_val > 1588 static const cson_value_api cson_value_api_string = { CSON_TYPE_STRING, cson_val > 1589 static const cson_value_api cson_value_api_array = { CSON_TYPE_ARRAY, cson_value > 1590 static const cson_value_api cson_value_api_object = { CSON_TYPE_OBJECT, cson_val > 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, NU > 1596 static const cson_value cson_value_double_empty = { &cson_value_api_double, NULL > 1597 static const cson_value cson_value_string_empty = { &cson_value_api_string, NULL > 1598 static const cson_value cson_value_array_empty = { &cson_value_api_array, NULL, > 1599 static const cson_value cson_value_object_empty = { &cson_value_api_object, NULL > 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/thr > 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/thr > 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; >