DELETED ajax/README Index: ajax/README ================================================================== --- ajax/README +++ /dev/null @@ -1,38 +0,0 @@ -This is the README for how to set up the Fossil/JSON test web page -under Apache on Unix systems. This is only intended only for -Fossil/JSON developers/tinkerers: - -First, copy cgi-bin/fossil-json.cgi.example to -cgi-bin/fossil-json.cgi. Edit it and correct the paths to the fossil -binary and the repo you want to serve. Make it executable. - -MAKE SURE that the fossil repo you use is world-writable OR that your -Web/CGI server is set up to run as the user ID of the owner of the -fossil file. ALSO: the DIRECTORY CONTAINING the repo file must be -writable by the CGI process. - -Next, set up an apache vhost entry. Mine looks like: - - - ServerAlias fjson - ScriptAlias /cgi-bin/ /home/stephan/cvs/fossil/fossil-json/ajax/cgi-bin/ - DocumentRoot /home/stephan/cvs/fossil/fossil-json/ajax - - -Now add your preferred vhost name (fjson in the above example) to /etc/hosts: - - 127.0.0.1 ...other aliases... fjson - -Restart your Apache. - -Now visit: http://fjson/ - -that will show the test/demo page. If it doesn't, edit index.html and -make sure that: - - WhAjaj.Connector.options.ajax.url = ...; - -points to your CGI script. In theory you can also do this over fossil -standalone server mode, but i haven't yet tested that particular test -page in that mode. - DELETED ajax/cgi-bin/fossil-json.cgi.example Index: ajax/cgi-bin/fossil-json.cgi.example ================================================================== --- ajax/cgi-bin/fossil-json.cgi.example +++ /dev/null @@ -1,2 +0,0 @@ -#!/path/to/fossil/binary -repository: /path/to/repo.fsl DELETED ajax/i-test/rhino-test.js Index: ajax/i-test/rhino-test.js ================================================================== --- ajax/i-test/rhino-test.js +++ /dev/null @@ -1,184 +0,0 @@ -var TestApp = { - serverUrl: - 'http://localhost:8080' - //'http://fjson/cgi-bin/fossil-json.cgi' - //'http://192.168.1.62:8080' - , - verbose:true, - wiki:{} -}; -(function bootstrap() { - var srcdir = '../js/'; - var includes = [srcdir+'json2.js', - srcdir+'whajaj.js', - srcdir+'fossil-ajaj.js' - ]; - for( var i in includes ) { - load(includes[i]); - } - WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.rhino; - TestApp.fossil = new FossilAjaj({ - asynchronous:false, /* rhino-based impl doesn't support async or timeout. */ - url:TestApp.serverUrl, - beforeSend:function(req,opt){ - if(!TestApp.verbose) return; - print("SENDING REQUEST: AJAJ options="+JSON.stringify(opt)); - if(req) print("Request envelope="+WhAjaj.stringify(req)); - }, - afterSend:function(req,opt){ - //if(!TestApp.verbose) return; - //print("REQUEST RETURNED: opt="+JSON.stringify(opt)); - //if(req) print("Request="+WhAjaj.stringify(req)); - }, - onError:function(req,opt){ - if(!TestApp.verbose) return; - print("ERROR: "+WhAjaj.stringify(opt)); - }, - onResponse:function(resp,req){ - if(!TestApp.verbose) return; - print("GOT RESPONSE: "+(('string'===typeof resp) ? resp : WhAjaj.stringify(resp))); - } - }); -})(); - -/** - Throws an exception of cond is a falsy value. -*/ -function assert(cond, descr){ - descr = descr || "Undescribed condition."; - if(!cond){ - throw new Error("Assertion failed: "+descr); - }else{ - print("Assertion OK: "+descr); - } -} - -/** - Calls func() in a try/catch block and throws an exception if - func() does NOT throw. -*/ -function assertThrows(func, descr){ - descr = descr || "Undescribed condition failed."; - var ex; - try{ - func(); - }catch(e){ - ex = e; - } - if(!ex){ - throw new Error("Function did not throw (as expected): "+descr); - }else{ - print("Function threw (as expected): "+descr+": "+ex); - } -} - -/** - Convenience form of TestApp.fossil.sendCommand(command,payload,ajajOpt). -*/ -function send(command,payload, ajajOpt){ - TestApp.fossil.sendCommand(command,payload,ajajOpt); -} - -/** - Asserts that resp is-a Object, resp.fossil is-a string, and - !resp.resultCode. -*/ -function assertResponseOK(resp){ - assert('object' === typeof resp,'Response is-a object.'); - assert( 'string' === typeof resp.fossil, 'Response contains fossil property.'); - assert( !resp.resultCode, 'resp.resultCode='+resp.resultCode); -} -/** - Asserts that resp is-a Object, resp.fossil is-a string, and - resp.resultCode is a truthy value. If expectCode is set then - it also asserts that (resp.resultCode=='FOSSIL-'+expectCode). -*/ -function assertResponseError(resp,expectCode){ - assert('object' === typeof resp,'Response is-a object.'); - assert( 'string' === typeof resp.fossil, 'Response contains fossil property.'); - assert( resp.resultCode, 'resp.resultCode='+resp.resultCode); - if(expectCode){ - assert( 'FOSSIL-'+expectCode == resp.resultCode, 'Expecting result code '+expectCode ); - } -} - -function testHAI(){ - TestApp.fossil.HAI({ - onResponse:function(resp,req){ - assertResponseOK(resp); - TestApp.serverVersion = resp.fossil; - } - }); - assert( 'string' === typeof TestApp.serverVersion, 'server version = '+TestApp.serverVersion); -} -testHAI.description = 'Get server version info.'; - -function testIAmNobody(){ - TestApp.fossil.whoami('/json/whoami'); - assert('nobody' === TestApp.fossil.userName, 'User == nobody.' ); - assert(!TestApp.fossil.authToken, 'authToken is not set.' ); - -} -testIAmNobody.description = 'Ensure that current user is "nobody".'; - - -function testAnonymousLogin(){ - TestApp.fossil.login(); - assert('string' === typeof TestApp.fossil.authToken, 'authToken = '+TestApp.fossil.authToken); - assert( 'string' === typeof TestApp.fossil.userName, 'User name = '+TestApp.fossil.userName); -} -testAnonymousLogin.description = 'Perform anonymous login.'; - -function testAnonWikiList(){ - TestApp.fossil.sendCommand('/json/wiki/list',undefined,{ - beforeSend:function(req,opt){ - TestApp.fossil.ajaj.options.beforeSend(req,opt); - assert( req && (req.authToken==TestApp.fossil.authToken), 'Request envelope contains expected authToken.' ); - }, - onResponse:function(resp,req){ - assertResponseOK(resp); - assert( (typeof [] === typeof resp.payload) && resp.payload.length, - "Wiki list seems to be okay."); - TestApp.wiki.list = resp.payload; - } - }); -} -testAnonWikiList.description = 'Fetch wiki list as anonymous user.'; - -function testAnonLogout(){ - TestApp.fossil.logout({ - onResponse:function(resp,req){ - assertResponseOK(resp); - } - }); - TestApp.fossil.logout({ - onResponse:function(resp,req){ - assertResponseError(resp); - } - }); -} -testAnonLogout.description = 'Log out anonymous user.'; - -(function runAllTests(){ - var testList = [ - testHAI, - testIAmNobody, - testAnonymousLogin, - testAnonWikiList, - testAnonLogout - ]; - var i, f; - for( i = 0; i < testList.length; ++i ){ - f = testList[i]; - try{ - print("Running test #"+(i+1)+": "+(f.description || "no description.")); - f(); - }catch(e){ - print("Test failed: "+e); - throw e; - } - } - -})(); - -print("Done!"); DELETED ajax/index.html Index: ajax/index.html ================================================================== --- ajax/index.html +++ /dev/null @@ -1,277 +0,0 @@ - - - - - Fossil/JSON raw request sending - - - - - - - - - - - - - -

You know, for sending raw JSON requests to Fossil...

- -If you're actually using this page, then you know what you're doing and don't -need help text, hoverhelp, and a snazzy interface. - -

-See also: prototype wiki editor. - -

Request...

- -Path: -
-If the POST textarea is not empty then it will be posted with the request. -
-Quick-posts:
- - - - - - - - - - - - - - - - - -
-Login: -
- - -
-name: -pw: - - -
- - -
- -
- - - - - - - - - - - - - - - - -
POST dataRequest AJAJ options
- - - - - -
Response
- -
-
-
-
- - DELETED ajax/js/fossil-ajaj.js Index: ajax/js/fossil-ajaj.js ================================================================== --- ajax/js/fossil-ajaj.js +++ /dev/null @@ -1,197 +0,0 @@ -/** - This file contains a WhAjaj extension for use with Fossil/JSON. - - Author: Stephan Beal (sgbeal@googlemail.com) - - License: Public Domain -*/ - -/** - Constructor for a new Fossil AJAJ client. ajajOpt may be an optional - object suitable for passing to the WhAjaj.Connector() constructor. - - On returning, this.ajaj is-a WhAjaj.Connector instance which can - be used to send requests to the back-end (though the convenience - functions of this class are the preferred way to do it). Clients - are encouraged to use FossilAjaj.sendCommand() (and friends) instead - of the underlying WhAjaj.Connector API, since this class' API - contains Fossil-specific request-calling handling (e.g. of authentication - info) whereas WhAjaj is more generic. -*/ -function FossilAjaj(ajajOpt) -{ - this.ajaj = new WhAjaj.Connector(ajajOpt); - return this; -} - -FossilAjaj.prototype.generateRequestId = function() { - return this.ajaj.generateRequestId(); -}; - -/** - Proxy for this.ajaj.sendRequest(). -*/ -FossilAjaj.prototype.sendRequest = function(req,opt) { - return this.ajaj.sendRequest(req,opt); -}; - -/** - Sends a command to the fossil back-end. Command should be the - path part of the URL, e.g. /json/stat, payload is a request-specific - value type (may often be null/undefined). ajajOpt is an optional object - holding WhAjaj.sendRequest()-compatible options. - - This function constructs a Fossil/JSON request envelope based - on the given arguments and adds this.authToken and a requestId - to it. -*/ -FossilAjaj.prototype.sendCommand = function(command, payload, ajajOpt) { - var req; - ajajOpt = ajajOpt || {}; - if(payload || this.authToken || ajajOpt.jsonp) { - req = { - payload:payload, - requestId:('function' === typeof this.generateRequestId) ? this.generateRequestId() : undefined, - authToken:this.authToken || undefined, - jsonp:('string' === typeof ajajOpt.jsonp) ? ajajOpt.jsonp : undefined - }; - } - ajajOpt.method = req ? 'POST' : 'GET'; - // just for debuggering: ajajOpt.method = 'POST'; if(!req) req={}; - if(command) ajajOpt.url = this.ajaj.derivedOption('url',ajajOpt) + command; - this.ajaj.sendRequest(req,ajajOpt); -}; - -/** - Sends a login request to the back-end. - - ajajOpt is an optional configuration object suitable for passing - to sendCommand(). - - After the response returns, this.authToken will be - set to the response payload. - - If name === 'anonymous' (the default if none is passed in) then - this function must make two requests - the first one gets the - captcha code and the second one submits it. - - If this object has an onLogin() function it is called (with - no arguments) before the onResponse() handler of the login is called - (that is the 2nd request for anonymous logins). - -*/ -FossilAjaj.prototype.login = function(name,pw,ajajOpt) { - name = name || 'anonymous'; - var self = this; - var loginReq = { - name:name, - password:pw - }; - ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} ); - var oldOnResponse = ajajOpt.onResponse; - ajajOpt.onResponse = function(resp,req) { - var thisOpt = this; - //alert('login response:\n'+WhAjaj.stringify(resp)); - if( resp && resp.payload ) { - self.authToken = resp.payload.authToken; - self.userName = resp.payload.name; - self.capabilities = resp.payload.capabilities; - } - if( WhAjaj.isFunction( self.onLogin ) ){ - try{ self.onLogin(); } - catch(e){} - } - if( WhAjaj.isFunction(oldOnResponse) ) { - try { oldOnResponse.apply(thisOpt,[resp,req]); } - catch(e) {} - } - }; - function doLogin(){ - //alert("Sending login request..."+WhAjaj.stringify(loginReq)); - self.sendCommand('/json/login', loginReq, ajajOpt); - } - if( 'anonymous' === name ){ - this.sendCommand('/json/anonymousPassword',undefined,{ - onResponse:function(resp,req){ - if( WhAjaj.isFunction(oldOnResponse) ){ - oldOnResponse.apply(this, [resp,req]); - }; - if(resp && !resp.resultCode){ - //alert("Got PW. Trying to log in..."+WhAjaj.stringify(resp)); - loginReq.anonymousSeed = resp.payload.seed; - loginReq.password = resp.payload.password; - doLogin(); - } - } - }); - } - else doLogin(); -}; - -/** - Logs out of fossil, invaliding this login token. - - ajajOpt is an optional configuration object suitable for passing - to sendCommand(). - - If this object has an onLogout() function it is called (with - no arguments) before the onResponse() handler is called. - IFF the response succeeds then this.authToken is unset. -*/ -FossilAjaj.prototype.logout = function(ajajOpt) { - var self = this; - ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} ); - var oldOnResponse = ajajOpt.onResponse; - ajajOpt.onResponse = function(resp,req) { - var thisOpt = this; - if( resp && !resp.payload ) delete self.authToken; - if( WhAjaj.isFunction( self.onLogout ) ){ - try{ self.onLogout(); } - catch(e){} - } - if( WhAjaj.isFunction(oldOnResponse) ) { - try { oldOnResponse.apply(thisOpt,[resp,req]); } - catch(e) {} - } - }; - this.sendCommand('/json/logout', undefined, ajajOpt ); -}; - -/** - Sends a HAI request to the server. /json/HAI is an alias /json/version. - - ajajOpt is an optional configuration object suitable for passing - to sendCommand(). -*/ -FossilAjaj.prototype.HAI = function(ajajOpt) { - this.sendCommand('/json/HAI', undefined, ajajOpt); -}; - - -/** - Sends a /json/whoami request. Updates this.userName and this.authToken - based on the response, removing them if the response does not contain - that data. -*/ -FossilAjaj.prototype.whoami = function(ajajOpt) { - var self = this; - ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} ); - var oldOnResponse = ajajOpt.onResponse; - ajajOpt.onResponse = function(resp,req) { - var thisOpt = this; - //alert('login response:\n'+WhAjaj.stringify(resp)); - if( resp && resp.payload ){ - if( resp.payload.authToken ) self.authToken = resp.payload.authToken; - if( resp.payload.name ) self.userName = resp.payload.name; - } - else { - delete self.userName; - delete self.authToken - } - if( WhAjaj.isFunction(oldOnResponse) ) { - try { oldOnResponse.apply(thisOpt,[resp,req]); } - catch(e) {} - } - }; - self.sendCommand('/json/whoami', undefined, ajajOpt); -}; DELETED ajax/js/whajaj.js Index: ajax/js/whajaj.js ================================================================== --- ajax/js/whajaj.js +++ /dev/null @@ -1,1133 +0,0 @@ -/** - This file provides a JS interface into the core functionality of - JSON-centric back-ends. It sends GET or JSON POST requests to - a back-end and expects JSON responses. The exact semantics of - the underlying back-end and overlying front-end are not its concern, - and it leaves the interpretation of the data up to the client/server - insofar as possible. - - All functionality is part of a class named WhAjaj, and that class - acts as namespace for this framework. - - Author: Stephan Beal (http://wanderinghorse.net/home/stephan/) - - License: Public Domain - - This framework is directly derived from code originally found in - http://code.google.com/p/jsonmessage, and later in - http://whiki.wanderinghorse.net, where it contained quite a bit - of application-specific logic. It was eventually (the 3rd time i - needed it) split off into its own library to simplify inclusion - into my many mini-projects. -*/ - - -/** - The WhAjaj function is primarily a namespace, and not intended - to called or instantiated via the 'new' operator. -*/ -function WhAjaj() -{ -} - -/** Returns a millisecond Unix Epoch timestamp. */ -WhAjaj.msTimestamp = function() -{ - return (new Date()).getTime(); -}; - -/** Returns a Unix Epoch timestamp (in seconds) in integer format. - - Reminder to self: (1.1 %1.2) evaluates to a floating-point value - in JS, and thus this implementation is less than optimal. -*/ -WhAjaj.unixTimestamp = function() -{ - var ts = (new Date()).getTime(); - return parseInt( ""+((ts / 1000) % ts) ); -}; - -/** - Returns true if v is-a Array instance. -*/ -WhAjaj.isArray = function( v ) -{ - return (v && - (v instanceof Array) || - (Object.prototype.toString.call(v) === "[object Array]") - ); - /* Reminders to self: - typeof [] == "object" - toString.call([]) == "[object Array]" - ([]).toString() == empty - */ -}; - -/** - Returns true if v is-a Object instance. -*/ -WhAjaj.isObject = function( v ) -{ - return v && - (v instanceof Object) && - ('[object Object]' === Object.prototype.toString.apply(v) ); -}; - -/** - Returns true if v is-a Function instance. -*/ -WhAjaj.isFunction = function(obj) -{ - return obj - && ( - (obj instanceof Function) - || ('function' === typeof obj) - || ("[object Function]" === Object.prototype.toString.call(obj)) - ) - ; -}; - -/** - Parses window.location.search-style string into an object - containing key/value pairs of URL arguments (already urldecoded). - - If the str argument is not passed (arguments.length==0) then - window.location.search.substring(1) is used by default. If - neither str is passed in nor window exists then false is returned. - - On success it returns an Object containing the key/value pairs - parsed from the string. - - FIXME: for keys in the form "name[]", build an array of results, - like PHP does. - -*/ -WhAjaj.processUrlArgs = function(str) { - if( 0 === arguments.length ) { - if( (undefined === typeof window) || - !window.location || - !window.location.search ) return false; - else str = (''+window.location.search).substring(1); - } - if( ! str ) return false; - var args = {}; - var sp = str.split(/&+/); - var rx = /^([^=]+)(=(.+))?/; - var i, m; - for( i in sp ) { - m = rx.exec( sp[i] ); - if( ! m ) continue; - args[decodeURIComponent(m[1])] = (m[3] ? decodeURIComponent(m[3]) : true); - } - return args; -}; - -/** - A simple wrapper around JSON.stringify(), using my own personal - preferred values for the 2nd and 3rd parameters. To globally - set its indentation level, assign WhAjaj.stringify.indent to - an integer value (0 for no intendation). - - This function is intended only for human-readable output, not - generic over-the-wire JSON output (where JSON.stringify(val) will - produce smaller results). -*/ -WhAjaj.stringify = function(val) { - if( ! arguments.callee.indent ) arguments.callee.indent = 4; - return JSON.stringify(val,0,arguments.callee.indent); -}; - -/** - Each instance of this class holds state information for making - AJAJ requests to a back-end system. While clients may use one - "requester" object per connection attempt, for connections to the - same back-end, using an instance configured for that back-end - can simplify usage. This class is designed so that the actual - connection-related details (i.e. _how_ it connects to the - back-end) may be re-implemented to use a client's preferred - connection mechanism (e.g. jQuery). - - The optional opt paramater may be an object with any (or all) of - the properties documented for WhAjaj.Connector.options.ajax. - Properties set here (or later via modification of the "options" - property of this object) will be used in calls to - WhAjaj.Connector.sendRequest(), and these override (normally) any - options set in WhAjaj.Connector.options.ajax. Note that - WhAjaj.Connector.sendRequest() _also_ takes an options object, - and ones passed there will override, for purposes of that one - request, any options passed in here or defined in - WhAjaj.Connector.options.ajax. See WhAjaj.Connector.options.ajax - and WhAjaj.Connector.prototype.sendRequest() for more details - about the precedence of options. - - Sample usage: - - @code - // Set up common connection-level options: - var cgi = new WhAjaj.Connector({ - url: '/cgi-bin/my.cgi', - timeout:10000, - onResponse(resp,req) { alert(JSON.stringify(resp,0.4)); }, - onError(req,opt) { - alert(opt.errorMessage); - } - }); - // Any of those options may optionally be set globally in - // WhAjaj.Connector.options.ajax (onError(), beforeSend(), and afterSend() - // are often easiest/most useful to set globally). - - // Get list of pages... - cgi.sendRequest( null, { - onResponse(resp,req){ alert(WhAjaj.stringify(resp)); } - }); - @endcode - - For common request types, clients can add functions to this - object which act as wrappers for backend-specific functionality. As - a simple example: - - @code - cgi.login = function(name,pw,ajajOpt) { - this.sendRequest( {name:name, password:pw}, ajajOpt ); - }; - @endcode - - TODOs: - - - Caching of page-load requests, with a configurable lifetime. - - - Use-cases like the above login() function are a tiny bit - problematic to implement when each request has a different URL - path (i know this from the whiki implementation). This is partly - a side-effect of design descisions made back in the very first - days of this code's life. i need to go through and see where i - can bend those conventions a bit (where it won't break my other - apps unduly). -*/ -WhAjaj.Connector = function(opt) -{ - this.options = WhAjaj.isObject(opt) ? opt : {}; - //TODO?: this.$cache = {}; -}; - -/** - The core options used by WhAjaj.Connector instances for performing - network operations. These options can (and some _should_) - be changed by a client application. They can also be changed - on specific instances of WhAjaj.Connector, but for most applications - it is simpler to set them here and not have to bother with configuring - each WhAjaj.Connector instance. Apps which use multiple back-ends at one time, - however, will need to customize each instance for a given back-end. -*/ -WhAjaj.Connector.options = { - /** - A (meaningless) prefix to apply to WhAjaj.Connector-generated - request IDs. - */ - requestIdPrefix:'WhAjaj.Connector-', - /** - Default options for WhAjaj.Connector.sendRequest() connection - parameters. This object holds only connection-related - options and callbacks (all optional), and not options - related to the required JSON structure of any given request. - i.e. the page name used in a get-page request are not set - here but are specified as part of the request object. - - These connection options are a "normalized form" of options - often found in various AJAX libraries like jQuery, - Prototype, dojo, etc. This approach allows us to swap out - the real connection-related parts by writing a simple proxy - which transforms our "normalized" form to the - backend-specific form. For examples, see the various - implementations stored in WhAjaj.Connector.sendImpls. - - The following callback options are, in practice, almost - always set globally to some app-wide defaults: - - - onError() to report errors using a common mechanism. - - beforeSend() to start a visual activity notification - - afterSend() to disable the visual activity notification - - However, be aware that if any given WhAjaj.Connector instance is - given its own before/afterSend callback then those will - override these. Mixing shared/global and per-instance - callbacks can potentially lead to confusing results if, e.g., - the beforeSend() and afterSend() functions have side-effects - but are not used with their proper before/after partner. - - TODO: rename this to 'ajaj' (the name is historical). The - problem with renaming it is is that the word 'ajax' is - pretty prevelant in the source tree, so i can't globally - swap it out. - */ - ajax: { - /** - URL of the whiki back-end CGI binary. - */ - url: '/some/path', - - /** - Connection method. Some connection-related functions might - override any client-defined setting. - - Must be one of 'GET' or 'POST'. For custom connection - implementation, it may optionally be some - implementation-specified value. - - Normally the API can derive this value automatically - if the - request uses JSON data it is POSTed, else it is GETted. - */ - method:'GET', - - /** - A hint whether to run the operation asynchronously or - not. Not all concrete WhAjaj.Connector.sendImpl() - implementations can support this. Interestingly, at - least one popular AJAX toolkit does not document - supporting _synchronous_ AJAX operations. All common - browser-side implementations support async operation, but - non-browser implementations migth not. - */ - asynchronous:true, - - /** - A HTTP authentication login name for the AJAX - connection. Not all concrete WhAjaj.Connector.sendImpl() - implementations can support this. - */ - loginName:undefined, - - /** - An HTTP authentication login password for the AJAJ - connection. Not all concrete WhAjaj.Connector.sendImpl() - implementations can support this. - */ - loginPassword:undefined, - - /** - A connection timeout, in milliseconds, for establishing - an AJAJ connection. Not all concrete - WhAjaj.Connector.sendImpl() implementations can support this. - */ - timeout:10000, - - /** - If an AJAJ request receives JSON data from the back-end, that - data is passed as a plain Object as the response parameter - (exception: in jsonp mode it is passed a string). The initiating - request object is passed as the second parameter, but clients - can normally ignore it (only those which need a way to map - specific requests to responses will need it). - - Note that the response might contain error information which - comes from the back-end. The difference between this error info - and the info passed to the onError() callback is that this data - indicates an application-level error, whereas onError() is used - to report connection-level problems or when the backend produces - non-JSON data (which, when not in jsonp mode, is unexpected and - is as fatal to the request as a connection error). - */ - onResponse: function(response, request){}, - - /** - If an AJAX request fails to establish a connection or it - receives non-JSON data from the back-end, this function - is called (e.g. timeout error or host name not - resolvable). It is passed the originating request and the - "normalized" connection parameters used for that - request. The connectOpt object "should" (or "might") - have an "errorMessage" property which describes the - nature of the problem. - - Clients will almost always want to replace the default - implementation with something which integrates into - their application. - */ - onError: function(request, connectOpt) - { - alert('AJAJ request failed:\n' - +'Connection information:\n' - +JSON.stringify(connectOpt,0,4) - ); - }, - - /** - Called before each connection attempt is made. Clients - can use this to, e.g., enable a visual "network activity - notification" for the user. It is passed the original - request object and the normalized connection parameters - for the request. If this function changes opt, those - changes _are_ applied to the subsequent request. If this - function throws, neither the onError() nor afterSend() - callbacks are triggered and WhAjaj.Connector.sendImpl() - propagates the exception back to the caller. - */ - beforeSend: function(request,opt){}, - - /** - Called after an AJAJ connection attempt completes, - regardless of success or failure. Passed the same - parameters as beforeSend() (see that function for - details). - - Here's an example of setting up a visual notification on - ajax operations using jQuery (but it's also easy to do - without jQuery as well): - - @code - function startAjaxNotif(req,opt) { - var me = arguments.callee; - var c = ++me.ajaxCount; - me.element.text( c + " pending AJAX operation(s)..." ); - if( 1 == c ) me.element.stop().fadeIn(); - } - startAjaxNotif.ajaxCount = 0. - startAjaxNotif.element = jQuery('#whikiAjaxNotification'); - - function endAjaxNotif() { - var c = --startAjaxNotif.ajaxCount; - startAjaxNotif.element.text( c+" pending AJAX operation(s)..." ); - if( 0 == c ) startAjaxNotif.element.stop().fadeOut(); - } - @endcode - - Set the beforeSend/afterSend properties to those - functions to enable the notifications by default. - */ - afterSend: function(request,opt){}, - - /** - If jsonp is a string then the WhAjaj-internal response - handling code ASSUMES that the response contains a JSONP-style - construct and eval()s it after afterSend() but before onResponse(). - In this case, onResponse() will get a string value for the response - instead of a response object parsed from JSON. - */ - jsonp:undefined - } -}; - -/** - Tries to find the given key in any of the following, returning - the first match found: opt, this.options, WhAjaj.Connector.options.ajax. - - Returns undefined if key is not found. -*/ -WhAjaj.Connector.prototype.derivedOption = function(key,opt) { - var v = opt ? opt[key] : undefined; - if( undefined !== v ) return v; - else v = this.options[key]; - if( undefined !== v ) return v; - else v = WhAjaj.Connector.options.ajax[key]; - return v; -}; -/** - Returns a unique string on each call containing a generic - reandom request identifier string. This is not used by the core - API but can be used by client code to generate unique IDs for - each request (if needed). - - The exact format is unspecified and may change in the future. - - Request IDs can be used by clients to "match up" responses to - specific requests if needed. In practice, however, they are - seldom, if ever, needed. When passing several concurrent - requests through the same response callback, it might be useful - for some clients to be able to distinguish, possibly re-routing - them through other handlers based on the originating request type. - - If this.options.requestIdPrefix or - WhAjaj.Connector.options.requestIdPrefix is set then that text - is prefixed to the returned string. -*/ -WhAjaj.Connector.prototype.generateRequestId = function() -{ - if( undefined === arguments.callee.sequence ) - { - arguments.callee.sequence = 0; - } - var pref = this.options.requestIdPrefix || WhAjaj.Connector.options.requestIdPrefix || ''; - return pref + - WhAjaj.msTimestamp() + - '/'+(Math.round( Math.random() * 100000000) )+ - ':'+(++arguments.callee.sequence); -}; - -/** - Copies (SHALLOWLY) all properties in opt to this.options. -*/ -WhAjaj.Connector.prototype.addOptions = function(opt) { - var k, v; - for( k in opt ) { - if( ! opt.hasOwnProperty(k) ) continue /* proactive Prototype kludge! */; - this.options[k] = opt[k]; - } - return this.options; -}; - -/** - An internal helper object which holds several functions intended - to simplify the creation of concrete communication channel - implementations for WhAjaj.Connector.sendImpl(). These operations - take care of some of the more error-prone parts of ensuring that - onResponse(), onError(), etc. callbacks are called consistently - using the same rules. -*/ -WhAjaj.Connector.sendHelper = { - /** - opt is assumed to be a normalized set of - WhAjaj.Connector.sendRequest() options. This function - creates a url by concatenating opt.url and some form of - opt.urlParam. - - If opt.urlParam is an object or string then it is appended - to the url. An object is assumed to be a one-dimensional set - of simple (urlencodable) key/value pairs, and not larger - data structures. A string value is assumed to be a - well-formed, urlencoded set of key/value pairs separated by - '&' characters. - - The new/normalized URL is returned (opt is not modified). If - opt.urlParam is not set then opt.url is returned (or an - empty string if opt.url is itself a false value). - - TODO: if opt is-a Object and any key points to an array, - build up a list of keys in the form "keyname[]". We could - arguably encode sub-objects like "keyname[subkey]=...", but - i don't know if that's conventions-compatible with other - frameworks. - */ - normalizeURL: function(opt) { - var u = opt.url || ''; - if( opt.urlParam ) { - var addQ = (u.indexOf('?') >= 0) ? false : true; - var addA = addQ ? false : ((u.indexOf('&')>=0) ? true : false); - var tail = ''; - if( WhAjaj.isObject(opt.urlParam) ) { - var li = [], k; - for( k in opt.urlParam) { - li.push( k+'='+encodeURIComponent( opt.urlParam[k] ) ); - } - tail = li.join('&'); - } - else if( 'string' === typeof opt.urlParam ) { - tail = opt.urlParam; - } - u = u + (addQ ? '?' : '') + (addA ? '&' : '') + tail; - } - return u; - }, - /** - Should be called by WhAjaj.Connector.sendImpl() - implementations after a response has come back. This - function takes care of most of ensuring that framework-level - conventions involving WhAjaj.Connector.options.ajax - properties are followed. - - The request argument must be the original request passed to - the sendImpl() function. It may legally be null for GET requests. - - The opt object should be the normalized AJAX options used - for the connection. - - The resp argument may be either a plain Object or a string - (in which case it is assumed to be JSON). - - This function takes care of the following: - - - Calling opt.afterSend() - - - If resp is a string, de-JSON-izing it to an object. - - - Calling opt.onSuccess() - - - Calling opt.onError() in several common (potential) error - cases. - - - If resp is-a String and opt.jsonp then resp is assumed to be - a JSONP-form construct and is eval()d BEFORE opt.onResponse() - is called. It is arguable to eval() it first, but the logic - integrates better with the non-jsonp handler. - - The sendImpl() should return immediately after calling this. - - The sendImpl() must call only one of onSendSuccess() or - onSendError(). It must call one of them or it must implement - its own response/error handling, which is not recommended - because getting the documented semantics of the - onError/onResponse/afterSend handling correct can be tedious. - */ - onSendSuccess:function(request,resp,opt) { - if( WhAjaj.isFunction(opt.afterSend) ) { - try { - opt.afterSend( request, opt ); - } catch(e){} - } - var onError = WhAjaj.isFunction(opt.onError) ? opt.onError : function(){}; - if( ! resp ) { - opt.errorMessage = "Sending of request succeeded but returned no data!"; - onError.apply( opt, [request, opt] ); - return false; - } - - if( 'string' === typeof resp ) { - try { - resp = opt.jsonp ? eval(resp) : JSON.parse(resp); - } catch(e) { - opt.errorMessage = e.toString(); - onError.apply( opt, [request, opt] ); - return; - } - } - try { - if( WhAjaj.isFunction( opt.onResponse ) ) { - opt.onResponse( resp, request ); - } - return true; - } - catch(e) { - opt.errorMessage = "Exception while handling inbound JSON response:\n" - + e - +"\nOriginal response data:\n"+JSON.stringify(resp) - ; - ; - onError.apply( opt, [request, opt] ); - return false; - } - }, - /** - Should be called by sendImpl() implementations after a response - has failed to connect (e.g. could not resolve host or timeout - reached). This function takes care of most of ensuring that - framework-level conventions involving WhAjaj.Connector.options.ajax - properties are followed. - - The request argument must be the original request passed to - the sendImpl() function. It may legally be null for GET - requests. - - The opt object should be the normalized AJAX options used - for the connection. By convention, the caller of this - function "should" set opt.errorMessage to contain a - human-readable description of the error. - - The sendImpl() should return immediately after calling this. The - return value from this function is unspecified. - */ - onSendError: function(request,opt) { - if( WhAjaj.isFunction(opt.afterSend) ) { - try { - opt.afterSend( request, opt ); - } - catch(e){} - } - if( WhAjaj.isFunction( opt.onError ) ) { - try { - opt.onError( request, opt ); - } - catch(e) {/*ignore*/} - } - } -}; - -/** - WhAjaj.Connector.sendImpls holds several concrete - implementations of WhAjaj.Connector.prototype.sendImpl(). To use - a specific implementation by default assign - WhAjaj.Connector.prototype.sendImpl to one of these functions. - - The functions defined here require that the 'this' object be-a - WhAjaj.Connector instance. - - Historical notes: - - a) We once had an implementation based on Prototype, but that - library just pisses me off (they change base-most types' - prototypes, introducing side-effects in client code which - doesn't even use Prototype). The Prototype version at the time - had a serious toJSON() bug which caused empty arrays to - serialize as the string "[]", which broke a bunch of my code. - (That has been fixed in the mean time, but i don't use - Prototype.) - - b) We once had an implementation for the dojo library, - - If/when the time comes to add Prototype/dojo support, we simply - need to port: - - http://code.google.com/p/jsonmessage/source/browse/trunk/lib/JSONMessage/JSONMessage.inc.js - - (search that file for "dojo" and "Prototype") to this tree. That - code is this code's generic grandfather and they are still very - similar, so a port is trivial. - -*/ -WhAjaj.Connector.sendImpls = { - /** - This is a concrete implementation of - WhAjaj.Connector.prototype.sendImpl() which uses the - environment's native XMLHttpRequest class to send whiki - requests and fetch the responses. - - The only argument must be a connection properties object, as - constructed by WhAjaj.Connector.normalizeAjaxParameters(). - - If window.firebug is set then window.firebug.watchXHR() is - called to enable monitoring of the XMLHttpRequest object. - - This implementation honors the loginName and loginPassword - connection parameters. - - Returns the XMLHttpRequest object. - - This implementation requires that the 'this' object be-a - WhAjaj.Connector. - - This implementation uses setTimeout() to implement the - timeout support, and thus the JS engine must provide that - functionality. - */ - XMLHttpRequest: function(request, args) - { - var json = WhAjaj.isObject(request) ? JSON.stringify(request) : request; - var xhr = new XMLHttpRequest(); - var startTime = (new Date()).getTime(); - var timeout = args.timeout || 10000/*arbitrary!*/; - var hitTimeout = false; - var done = false; - var tmid /* setTimeout() ID */; - //if( json ) json = json.replace(/รถ/g,"\\u00f6") /* ONLY FOR A SPECIFIC TEST */; - //alert( 'json=\n'+json ); - function handleTimeout() - { - hitTimeout = true; - if( ! done ) - { - var now = (new Date()).getTime(); - try { xhr.abort(); } catch(e) {/*ignore*/} - // see: http://www.w3.org/TR/XMLHttpRequest/#the-abort-method - args.errorMessage = "Timeout of "+timeout+"ms reached after "+(now-startTime)+"ms during AJAX request."; - WhAjaj.Connector.sendHelper.onSendError( request, args ); - } - return; - } - function onStateChange() - { // reminder to self: apparently 'this' is-not-a XHR :/ - if( hitTimeout ) - { /* we're too late - the error was already triggered. */ - return; - } - - if( 4 == xhr.readyState ) - { - done = true; - if( tmid ) - { - clearTimeout( tmid ); - tmid = null; - } - if( (xhr.status >= 200) && (xhr.status < 300) ) - { - WhAjaj.Connector.sendHelper.onSendSuccess( request, xhr.responseText, args ); - return; - } - else - { - if( undefined === args.errorMessage ) - { - args.errorMessage = "Error sending a '"+args.method+"' AJAX request to " - +"["+args.url+"]: " - +"Status text=["+xhr.statusText+"]" - ; - WhAjaj.Connector.sendHelper.onSendError( request, args ); - } - else { /*maybe it was was set by the timeout handler. */ } - return; - } - } - }; - - xhr.onreadystatechange = onStateChange; - if( ('undefined'!==(typeof window)) && ('firebug' in window) && ('watchXHR' in window.firebug) ) - { /* plug in to firebug lite's XHR monitor... */ - window.firebug.watchXHR( xhr ); - } - try - { - //alert( JSON.stringify( args )); - function xhrOpen() - { - if( ('loginName' in args) && args.loginName ) - { - xhr.open( args.method, args.url, args.asynchronous, args.loginName, args.loginPassword ); - } - else - { - xhr.open( args.method, args.url, args.asynchronous ); - } - } - if( json && ('POST' === args.method.toUpperCase()) ) - { - xhrOpen(); - xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8"); - // Google Chrome warns that it refuses to set these - // "unsafe" headers (his words, not mine): - // xhr.setRequestHeader("Content-length", json.length); - // xhr.setRequestHeader("Connection", "close"); - xhr.send( json ); - } - else /* assume GET */ - { - xhrOpen(); - xhr.send(null); - } - tmid = setTimeout( handleTimeout, timeout ); - return xhr; - } - catch(e) - { - args.errorMessage = e.toString(); - WhAjaj.Connector.sendHelper.onSendError( request, args ); - return undefined; - } - }/*XMLHttpRequest()*/, - /** - This is a concrete implementation of - WhAjaj.Connector.prototype.sendImpl() which uses the jQuery - AJAX API to send requests and fetch the responses. - - The first argument may be either null/false, an Object - containing toJSON-able data to post to the back-end, or such an - object in JSON string form. - - The second argument must be a connection properties object, as - constructed by WhAjaj.Connector.normalizeAjaxParameters(). - - If window.firebug is set then window.firebug.watchXHR() is - called to enable monitoring of the XMLHttpRequest object. - - This implementation honors the loginName and loginPassword - connection parameters. - - Returns the XMLHttpRequest object. - - This implementation requires that the 'this' object be-a - WhAjaj.Connector. - */ - jQuery:function(request,args) - { - var data = request || undefined; - if( data ) { - if('string'!==typeof data) { - try { - data = JSON.stringify(data); - } - catch(e) { - WhAjaj.Connector.sendHelper.onSendError( request, args ); - return; - } - } - } - var ajopt = { - url: args.url, - data: data, - type: args.method, - async: args.asynchronous, - password: (undefined !== args.loginPassword) ? args.loginPassword : undefined, - username: (undefined !== args.loginName) ? args.loginName : undefined, - contentType: 'application/json; charset=utf-8', - error: function(xhr, textStatus, errorThrown) - { - //this === the options for this ajax request - args.errorMessage = "Error sending a '"+ajopt.type+"' request to ["+ajopt.url+"]: " - +"Status text=["+textStatus+"]" - +(errorThrown ? ("Error=["+errorThrown+"]") : "") - ; - WhAjaj.Connector.sendHelper.onSendError( request, args ); - }, - success: function(data) - { - WhAjaj.Connector.sendHelper.onSendSuccess( request, data, args ); - }, - /* Set dataType=text instead of json for deeply archaic reasons which - might no longer apply. - */ - dataType: 'text' - }; - if( undefined !== args.timeout ) - { - ajopt.timeout = args.timeout; - } - try - { - var xhr = jQuery.ajax(ajopt); - if( xhr && ('undefined'!==(typeof window)) && ('firebug' in window) && ('watchXHR' in window.firebug) ) - { /* plug in to firebug lite's XHR monitor... */ - window.firebug.watchXHR( xhr ); - } - return xhr; - } - catch(e) - { - args.errorMessage = e.toString(); - WhAjaj.Connector.sendHelper.onSendError( request, args ); - return undefined; - } - }/*jQuery()*/, - /** - This is a concrete implementation of - WhAjaj.Connector.prototype.sendImpl() which uses the rhino - Java API to send requests and fetch the responses. - - Limitations vis-a-vis the interface: - - - timeouts are not supported. - - - asynchronous mode is not supported because implementing it - requires the ability to kill a running thread (which is deprecated - in the Java API). - */ - rhino:function(request,args) - { - var self = this; - var data = request || undefined; - if( data ) { - if('string'!==typeof data) { - try { - data = JSON.stringify(data); - } - catch(e) { - WhAjaj.Connector.sendHelper.onSendError( request, args ); - return; - } - } - } - var url; - var con; - var IO = new JavaImporter(java.io); - var wr; - var rd, ln, json = []; - function setIncomingCookies(list){ - if(!list || !list.length) return; - if( !self.cookies ) self.cookies = {}; - var k, v, i; - for( i = 0; i < list.length; ++i ){ - v = list[i].split('=',2); - k = decodeURIComponent(v[0]) - v = v[0] ? decodeURIComponent(v[0].split(';',2)[0]) : null; - //print("RECEIVED COOKIE: "+k+"="+v); - if(!v) { - delete self.cookies[k]; - continue; - }else{ - self.cookies[k] = v; - } - } - }; - function setOutboundCookies(conn){ - if(!self.cookies) return; - var k, v; - for( k in self.cookies ){ - if(!self.cookies.hasOwnProperty(k)) continue /*kludge for broken JS libs*/; - v = self.cookies[k]; - conn.addRequestProperty("Cookie", encodeURIComponent(k)+'='+encodeURIComponent(v)); - //print("SENDING COOKIE: "+k+"="+v); - } - }; - try{ - url = new java.net.URL( args.url ) - con = url.openConnection(/*FIXME: add proxy support!*/); - con.setRequestProperty("Accept-Charset","utf-8"); - setOutboundCookies(con); - if(data){ - con.setRequestProperty("Content-Type","application/json; charset=utf-8"); - con.setDoOutput( true ); - wr = new IO.OutputStreamWriter(con.getOutputStream()) - wr.write(data); - wr.flush(); - wr.close(); - wr = null; - //print("POSTED: "+data); - } - rd = new IO.BufferedReader(new IO.InputStreamReader(con.getInputStream())); - //var skippedHeaders = false; - while ((line = rd.readLine()) !== null) { - //print("LINE: "+line); - //if(!line.length && !skippedHeaders){ - // skippedHeaders = true; - // json = []; - // continue; - //} - json.push(line); - } - setIncomingCookies(con.getHeaderFields().get("Set-Cookie")); - }catch(e){ - args.errorMessage = e.toString(); - WhAjaj.Connector.sendHelper.onSendError( request, args ); - return undefined; - } - try { if(wr) wr.close(); } catch(e) { /*ignore*/} - try { if(rd) rd.close(); } catch(e) { /*ignore*/} - json = json.join(''); - //print("READ IN JSON: "+json); - WhAjaj.Connector.sendHelper.onSendSuccess( request, json, args ); - }/*rhino()*/ -}; - -/** - An internal function which takes an object containing properties - for a WhAjaj.Connector network request. This function creates a new - object containing a superset of the properties from: - - a) opt - b) this.options - c) WhAjaj.Connector.options.ajax - - in that order, using the first one it finds. - - All non-function properties are _deeply_ copied via JSON cloning - in order to prevent accidental "cross-request pollenation" (been - there, done that). Functions cannot be cloned and are simply - copied by reference. - - This function throws if JSON-copying one of the options fails - (e.g. due to cyclic data structures). - - Reminder to self: this function does not "normalize" opt.urlParam - by encoding it into opt.url, mainly for historical reasons, but - also because that behaviour was specifically undesirable in this - code's genetic father. -*/ -WhAjaj.Connector.prototype.normalizeAjaxParameters = function (opt) -{ - var rc = {}; - function merge(k,v) - { - if( rc.hasOwnProperty(k) ) return; - else if( WhAjaj.isFunction(v) ) {} - else if( WhAjaj.isObject(v) ) v = JSON.parse( JSON.stringify(v) ); - rc[k]=v; - } - function cp(obj) { - if( ! WhAjaj.isObject(obj) ) return; - var k; - for( k in obj ) { - if( ! obj.hasOwnProperty(k) ) continue /* i will always hate the Prototype designers for this. */; - merge(k, obj[k]); - } - } - cp( opt ); - cp( this.options ); - cp( WhAjaj.Connector.options.ajax ); - // no, not here: rc.url = WhAjaj.Connector.sendHelper.normalizeURL(rc); - return rc; -}; - -/** - This is the generic interface for making calls to a back-end - JSON-producing request handler. It is a simple wrapper around - WhAjaj.Connector.prototype.sendImpl(), which just normalizes the - connection options for sendImpl() and makes sure that - opt.beforeSend() is (possibly) called. - - The request parameter must either be false/null/empty or a - fully-populated JSON-able request object (which will be sent as - unencoded application/json text), depending on the type of - request being made. It is never semantically legal (in this API) - for request to be a string/number/true/array value. As a rule, - only POST requests use the request data. GET requests should - encode their data in opt.url or opt.urlParam (see below). - - opt must contain the network-related parameters for the request. - Paramters _not_ set in opt are pulled from this.options or - WhAjaj.Connector.options.ajax (in that order, using the first - value it finds). Thus the set of connection-level options used - for the request are a superset of those various sources. - - The "normalized" (or "superimposed") opt object's URL may be - modified before the request is sent, as follows: - - if opt.urlParam is a string then it is assumed to be properly - URL-encoded parameters and is appended to the opt.url. If it is - an Object then it is assumed to be a one-dimensional set of - key/value pairs with simple values (numbers, strings, booleans, - null, and NOT objects/arrays). The keys/values are URL-encoded - and appended to the URL. - - The beforeSend() callback (see below) can modify the options - object before the request attempt is made. - - The callbacks in the normalized opt object will be triggered as - follows (if they are set to Function values): - - - beforeSend(request,opt) will be called before any network - processing starts. If beforeSend() throws then no other - callbacks are triggered and this function propagates the - exception. This function is passed normalized connection options - as its second parameter, and changes this function makes to that - object _will_ be used for the pending connection attempt. - - - onError(request,opt) will be called if a connection to the - back-end cannot be established. It will be passed the original - request object (which might be null, depending on the request - type) and the normalized options object. In the error case, the - opt object passed to onError() "should" have a property called - "errorMessage" which contains a description of the problem. - - - onError(request,opt) will also be called if connection - succeeds but the response is not JSON data. - - - onResponse(response,request) will be called if the response - returns JSON data. That data might hold an error response code - - clients need to check for that. It is passed the response object - (a plain object) and the original request object. - - - afterSend(request,opt) will be called directly after the - AJAX request is finished, before onError() or onResonse() are - called. Possible TODO: we explicitly do NOT pass the response to - this function in order to keep the line between the responsibilities - of the various callback clear (otherwise this could be used the same - as onResponse()). In practice it would sometimes be useful have the - response passed to this function, mainly for logging/debugging - purposes. - - The return value from this function is meaningless because - AJAX operations tend to take place asynchronously. - -*/ -WhAjaj.Connector.prototype.sendRequest = function(request,opt) -{ - if( !WhAjaj.isFunction(this.sendImpl) ) - { - throw new Error("This object has no sendImpl() member function! I don't know how to send the request!"); - } - var ex = false; - var av = Array.prototype.slice.apply( arguments, [0] ); - - /** - FIXME: how to handle the error, vis-a-vis- the callbacks, if - normalizeAjaxParameters() throws? It can throw if - (de)JSON-izing fails. - */ - var norm = this.normalizeAjaxParameters( WhAjaj.isObject(opt) ? opt : {} ); - norm.url = WhAjaj.Connector.sendHelper.normalizeURL(norm); - if( ! request ) norm.method = 'GET'; - if( WhAjaj.isFunction(norm.beforeSend) ) - { - norm.beforeSend( request, norm ); - } - //alert( WhAjaj.stringify(request)+'\n'+WhAjaj.stringify(norm)); - try { this.sendImpl( request, norm ); } - catch(e) { ex = e; } - if(ex) throw ex; -}; - -/** - sendImpl() holds a concrete back-end connection implementation. It - can be replaced with a custom implementation if one follows the rules - described throughout this API. See WhAjaj.Connector.sendImpls for - the concrete implementations included with this API. -*/ -WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.XMLHttpRequest; -//WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.rhino; -//WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.jQuery; DELETED ajax/wiki-editor.html Index: ajax/wiki-editor.html ================================================================== --- ajax/wiki-editor.html +++ /dev/null @@ -1,386 +0,0 @@ - - - - - Fossil/JSON Wiki Editor Prototype - - - - - - - - - - - - - -

PROTOTYPE JSON-based Fossil Wiki Editor

- -See also: main test page. - -
-Login: -
- -or: -name: -pw: - - - -
- - -
-Quick-posts:
- - - - - - - - - -
- - - - - - - - - - - - - - - - -
Page ListContent
-
-
-
-
- -
Response
- -
-
-
-
- - DELETED src/Makefile Index: src/Makefile ================================================================== --- src/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -all: - $(MAKE) -C .. Index: src/allrepo.c ================================================================== --- src/allrepo.c +++ src/allrepo.c @@ -74,13 +74,13 @@ ** rebuild Rebuild on all repositories ** ** sync Run a "sync" on all repositories ** ** Respositories are automatically added to the set of known repositories -** when one of the following commands against the repository: clone, info, -** pull, push, or sync. Even previously ignored repositories are added back -** to the list of repositories by these commands. +** when one of the following commands are run against the repository: clone, +** info, pull, push, or sync. Even previously ignored repositories are +** added back to the list of repositories by these commands. */ void all_cmd(void){ int n; Stmt q; const char *zCmd; Index: src/blob.c ================================================================== --- src/blob.c +++ src/blob.c @@ -313,10 +313,36 @@ if( rc==0 ){ rc = szA - szB; } return rc; } + +/* +** Compare two blobs in constant time and return zero if they are equal. +** Constant time comparison only applies for blobs of the same length. +** If lengths are different, immediately returns 1. +*/ +int blob_constant_time_cmp(Blob *pA, Blob *pB){ + int szA, szB, i; + unsigned char *buf1, *buf2; + unsigned char rc = 0; + + blob_is_init(pA); + blob_is_init(pB); + szA = blob_size(pA); + szB = blob_size(pB); + if( szA!=szB || szA==0 ) return 1; + + buf1 = (unsigned char*)blob_buffer(pA); + buf2 = (unsigned char*)blob_buffer(pB); + + for( i=0; iiCursor, and copies them -** to pDest (which must be valid memory at least nLen bytes long). -** -** Returns the number of bytes read/copied, which may be less than -** nLen (if end-of-blob is encountered). -** -** Updates pIn's cursor. -** -** Returns 0 if pIn contains no data. -*/ -unsigned int blob_read(Blob *pIn, void * pDest, unsigned int nLen ){ - if( !pIn->aData || (pIn->iCursor >= pIn->nUsed) ){ - return 0; - } else if( (pIn->iCursor + nLen) > (unsigned int)pIn->nUsed ){ - nLen = (unsigned int) (pIn->nUsed - pIn->iCursor); - } - assert( pIn->nUsed > pIn->iCursor ); - assert( (pIn->iCursor+nLen) <= pIn->nUsed ); - if( nLen ){ - memcpy( pDest, pIn->aData, nLen ); - pIn->iCursor += nLen; - } - return nLen; -} Index: src/branch.c ================================================================== --- src/branch.c +++ src/branch.c @@ -178,18 +178,14 @@ /* Do an autosync push, if requested */ if( !isPrivate ) autosync(AUTOSYNC_PUSH); } /* -** Prepare a query that will list branches. -** -** If (which<0) then the query pulls only closed branches. If -** (which>0) then the query pulls all (closed and opened) -** branches. Else the query pulls currently-opened branches. +** Prepare a query that will list all branches. */ -void branch_prepare_list_query(Stmt *pQuery, int which ){ - if( which < 0 ){ +static void prepareBranchQuery(Stmt *pQuery, int showAll, int showClosed){ + if( showClosed ){ db_prepare(pQuery, "SELECT value FROM tagxref" " WHERE tagid=%d AND value NOT NULL " "EXCEPT " "SELECT value FROM tagxref" @@ -197,11 +193,11 @@ " AND rid IN leaf" " AND NOT %z" " ORDER BY value COLLATE nocase /*sort*/", TAG_BRANCH, TAG_BRANCH, leaf_is_closed_sql("tagxref.rid") ); - }else if( which>0 ){ + }else if( showAll ){ db_prepare(pQuery, "SELECT DISTINCT value FROM tagxref" " WHERE tagid=%d AND value NOT NULL" " AND rid IN leaf" " ORDER BY value COLLATE nocase /*sort*/", @@ -264,11 +260,11 @@ if( g.localOpen ){ vid = db_lget_int("checkout", 0); zCurrent = db_text(0, "SELECT value FROM tagxref" " WHERE rid=%d AND tagid=%d", vid, TAG_BRANCH); } - branch_prepare_list_query(&q, showAll?1:(showClosed?-1:0)); + prepareBranchQuery(&q, showAll, showClosed); while( db_step(&q)==SQLITE_ROW ){ const char *zBr = db_column_text(&q, 0); int isCur = zCurrent!=0 && fossil_strcmp(zCurrent,zBr)==0; fossil_print("%s%s\n", (isCur ? "* " : " "), zBr); } @@ -331,11 +327,11 @@ @ Closed branches are fixed and do not change (unless they are first @ reopened) @ style_sidebox_end(); - branch_prepare_list_query(&q, showAll?1:(showClosed?-1:0)); + prepareBranchQuery(&q, showAll, showClosed); cnt = 0; while( db_step(&q)==SQLITE_ROW ){ const char *zBr = db_column_text(&q, 0); if( cnt==0 ){ if( colorTest ){ Index: src/captcha.c ================================================================== --- src/captcha.c +++ src/captcha.c @@ -412,14 +412,12 @@ return x; } /* ** Translate a captcha seed value into the captcha password string. -** The returned string is static and overwritten on each call to -** this function. */ -char const *captcha_decode(unsigned int seed){ +char *captcha_decode(unsigned int seed){ const char *zSecret; const char *z; Blob b; static char zRes[20]; Index: src/cgi.c ================================================================== --- src/cgi.c +++ src/cgi.c @@ -325,11 +325,11 @@ */ /*time_t expires = time(0) + atoi(db_config("constant_expires","604800"));*/ time_t expires = time(0) + 604800; fprintf(g.httpOut, "Expires: %s\r\n", cgi_rfc822_datestamp(expires)); }else{ - fprintf(g.httpOut, "Cache-control: no-cache, no-store\r\n"); + fprintf(g.httpOut, "Cache-control: no-cache\r\n"); } /* Content intended for logged in users should only be cached in ** the browser, not some shared location. */ @@ -362,11 +362,11 @@ /* ** Do a redirect request to the URL given in the argument. ** ** The URL must be relative to the base of the fossil server. */ -void cgi_redirect(const char *zURL){ +NORETURN void cgi_redirect(const char *zURL){ char *zLocation; CGIDEBUG(("redirect to %s\n", zURL)); if( strncmp(zURL,"http:",5)==0 || strncmp(zURL,"https:",6)==0 ){ zLocation = mprintf("Location: %s\r\n", zURL); }else if( *zURL=='/' ){ @@ -381,11 +381,11 @@ cgi_set_status(302, "Moved Temporarily"); free(zLocation); cgi_reply(); fossil_exit(0); } -void cgi_redirectf(const char *zFormat, ...){ +NORETURN void cgi_redirectf(const char *zFormat, ...){ va_list ap; va_start(ap, zFormat); cgi_redirect(vmprintf(zFormat, ap)); va_end(ap); } @@ -508,11 +508,10 @@ zValue = ""; } if( fossil_islower(zName[0]) ){ cgi_set_parameter_nocopy(zName, zValue); } - json_setenv( zName, cson_value_new_string(zValue,strlen(zValue)) ); } } /* ** *pz is a string that consists of multiple lines of text. This @@ -682,112 +681,28 @@ } } } } - -/* -** Internal helper for cson_data_source_FILE_n(). -*/ -typedef struct CgiPostReadState_ { - FILE * fh; - unsigned int len; - unsigned int pos; -} CgiPostReadState; - -/* -** cson_data_source_f() impl which reads only up to -** a specified amount of data from its input FILE. -** state MUST be a full populated (CgiPostReadState*). -*/ -static int cson_data_source_FILE_n( void * state, - void * dest, - unsigned int * n ){ - if( ! state || !dest || !n ) return cson_rc.ArgError; - else { - CgiPostReadState * st = (CgiPostReadState *)state; - if( st->pos >= st->len ){ - *n = 0; - return 0; - } else if( !*n || ((st->pos + *n) > st->len) ){ - return cson_rc.RangeError; - }else{ - unsigned int rsz = (unsigned int)fread( dest, 1, *n, st->fh ); - if( ! rsz ){ - *n = rsz; - return feof(st->fh) ? 0 : cson_rc.IOError; - }else{ - *n = rsz; - st->pos += *n; - return 0; - } - } - } -} - -/* -** Reads a JSON object from the first contentLen bytes of zIn. On -** g.json.post is updated to hold the content. On error a -** FSL_JSON_E_INVALID_REQUEST response is output and fossil_exit(0) is -** called. -*/ -static void cgi_parse_POST_JSON( FILE * zIn, unsigned int contentLen ){ - cson_value * jv = NULL; - int rc; - CgiPostReadState state; - assert( 0 != contentLen ); - state.fh = zIn; - state.len = contentLen; - state.pos = 0; - rc = cson_parse( &jv, cson_data_source_FILE_n, &state, NULL, NULL ); - if(rc){ - goto invalidRequest; - }else{ - json_gc_add( "POST.JSON", jv, 1 ); - g.json.post.v = jv; - g.json.post.o = cson_value_get_object( jv ); - if( !g.json.post.o ){ /* we don't support non-Object (Array) requests */ - goto invalidRequest; - } - } - return; - invalidRequest: - cgi_set_content_type(json_guess_content_type()); - json_err( FSL_JSON_E_INVALID_REQUEST, NULL, 1 ); - fossil_exit(0); -} - - /* ** Initialize the query parameter database. Information is pulled from ** the QUERY_STRING environment variable (if it exists), from standard ** input if there is POST data, and from HTTP_COOKIE. */ void cgi_init(void){ char *z; const char *zType; int len; - json_main_bootstrap(); - g.isHTTP = 1; cgi_destination(CGI_BODY); - - z = (char*)P("HTTP_COOKIE"); - if( z ){ - z = mprintf("%s",z); - add_param_list(z, ';'); - } - z = (char*)P("QUERY_STRING"); if( z ){ z = mprintf("%s",z); add_param_list(z, '&'); } z = (char*)P("REMOTE_ADDR"); - if( z ){ - g.zIpAddr = mprintf("%s", z); - } + if( z ) g.zIpAddr = mprintf("%s", z); len = atoi(PD("CONTENT_LENGTH", "0")); g.zContentType = zType = P("CONTENT_TYPE"); if( len>0 && zType ){ blob_zero(&g.cgiIn); @@ -806,34 +721,18 @@ blob_uncompress(&g.cgiIn, &g.cgiIn); }else if( fossil_strcmp(zType, "application/x-fossil-debug")==0 ){ blob_read_from_channel(&g.cgiIn, g.httpIn, len); }else if( fossil_strcmp(zType, "application/x-fossil-uncompressed")==0 ){ blob_read_from_channel(&g.cgiIn, g.httpIn, len); - }else if( fossil_strcmp(zType, "application/json") - || fossil_strcmp(zType,"text/plain")/*assume this MIGHT be JSON*/ - || fossil_strcmp(zType,"application/javascript")){ - g.json.isJsonMode = 1; - cgi_parse_POST_JSON(g.httpIn, (unsigned int)len); - /* FIXMEs: - - - See if fossil really needs g.cgiIn to be set for this purpose - (i don't think it does). If it does then fill g.cgiIn and - refactor to parse the JSON from there. - - - After parsing POST JSON, copy the "first layer" of keys/values - to cgi_setenv(), honoring the upper-case distinction used - in add_param_list(). However... - - - If we do that then we might get a disconnect in precedence of - GET/POST arguments. i prefer for GET entries to take precedence - over like-named POST entries, but in order for that to happen we - need to process QUERY_STRING _after_ reading the POST data. - */ - cgi_set_content_type(json_guess_content_type()); - } - } - + } + } + + z = (char*)P("HTTP_COOKIE"); + if( z ){ + z = mprintf("%s",z); + add_param_list(z, ';'); + } } /* ** This is the comparison function used to sort the aParamQP[] array of ** query parameters and cookies. @@ -989,26 +888,23 @@ } /* ** Print all query parameters on standard output. Format the ** parameters as HTML. This is used for testing and debugging. -** Release builds omit the values of the cookies to avoid defeating -** the purpose of setting HttpOnly cookies. +** +** Omit the values of the cookies unless showAll is true. */ -void cgi_print_all(void){ +void cgi_print_all(int showAll){ int i; - int showAll = 0; -#ifdef FOSSIL_DEBUG - /* Show the values of cookies in debug mode. */ - showAll = 1; -#endif cgi_parameter("",""); /* Force the parameters into sorted order */ for(i=0; i\n", - htmlize(aParamQP[i].zName, -1), htmlize(aParamQP[i].zValue, -1)); + const char *zName = aParamQP[i].zName; + if( !showAll ){ + if( fossil_stricmp("HTTP_COOKIE",zName)==0 ) continue; + if( fossil_strnicmp("fossil-",zName,7)==0 ) continue; } + cgi_printf("%h = %h
\n", zName, aParamQP[i].zValue); } } /* ** This routine works like "printf" except that it has the @@ -1031,11 +927,11 @@ /* ** Send a reply indicating that the HTTP request was malformed */ -static void malformed_request(void){ +static NORETURN void malformed_request(void){ cgi_set_status(501, "Not Implemented"); cgi_printf( "Unrecognized HTTP Request\n" ); cgi_reply(); @@ -1043,33 +939,23 @@ } /* ** Panic and die while processing a webpage. */ -void cgi_panic(const char *zFormat, ...){ +NORETURN void cgi_panic(const char *zFormat, ...){ va_list ap; cgi_reset_content(); - if( g.json.isJsonMode ){ - char * zMsg; - va_start(ap, zFormat); - zMsg = vmprintf(zFormat,ap); - va_end(ap); - json_err( FSL_JSON_E_PANIC, zMsg, 1 ); - free(zMsg); - fossil_exit( g.isHTTP ? 0 : 1 ); - }else{ - cgi_set_status(500, "Internal Server Error"); - cgi_printf( - "

Internal Server Error

\n" - "" - ); - va_start(ap, zFormat); - vxprintf(pContent,zFormat,ap); - va_end(ap); - cgi_reply(); - fossil_exit(1); - } + cgi_set_status(500, "Internal Server Error"); + cgi_printf( + "<html><body><h1>Internal Server Error</h1>\n" + "<plaintext>" + ); + va_start(ap, zFormat); + vxprintf(pContent,zFormat,ap); + va_end(ap); + cgi_reply(); + fossil_exit(1); } /* ** Remove the first space-delimited token from a string and return ** a pointer to it. Add a NULL to the string to terminate the token. @@ -1106,10 +992,11 @@ char *z, *zToken; int i; struct sockaddr_in remoteName; socklen_t size = sizeof(struct sockaddr_in); char zLine[2000]; /* A single line of input. */ + g.fullHttpReply = 1; if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ malformed_request(); } zToken = extract_token(zLine, &z); @@ -1275,10 +1162,11 @@ sleep( nchildren-MAX_PARALLEL ); } delay.tv_sec = 60; delay.tv_usec = 0; FD_ZERO(&readfds); + assert( listener>=0 ); FD_SET( listener, &readfds); select( listener+1, &readfds, 0, 0, &delay); if( FD_ISSET(listener, &readfds) ){ lenaddr = sizeof(inaddr); connection = accept(listener, (struct sockaddr*)&inaddr, &lenaddr); Index: src/config.h ================================================================== --- src/config.h +++ src/config.h @@ -134,6 +134,15 @@ #else /* Generates a warning - but it always works */ # define FOSSIL_INT_TO_PTR(X) ((void*)(X)) # define FOSSIL_PTR_TO_INT(X) ((int)(X)) #endif +/* +** A marker for functions that never return. +*/ +#if defined(__GNUC__) || defined(__clang__) +# define NORETURN __attribute__((__noreturn__)) +#else +# define NORETURN +#endif + #endif /* _RC_COMPILE_ */ Index: src/configure.c ================================================================== --- src/configure.c +++ src/configure.c @@ -785,10 +785,11 @@ const char *zMethod; if( g.argc<3 ){ usage("export|import|merge|pull|reset ..."); } db_find_and_open_repository(0, 0); + db_open_config(0); zMethod = g.argv[2]; n = strlen(zMethod); if( strncmp(zMethod, "export", n)==0 ){ int mask; const char *zSince = find_option("since",0,1); DELETED src/cson_amalgamation.c Index: src/cson_amalgamation.c ================================================================== --- src/cson_amalgamation.c +++ /dev/null @@ -1,5449 +0,0 @@ -/* auto-generated! Do not edit! */ -#include "cson_amalgamation.h" -/* begin file parser/JSON_parser.h */ -/* See JSON_parser.c for copyright information and licensing. */ - -#ifndef JSON_PARSER_H -#define JSON_PARSER_H - -/* JSON_parser.h */ - - -#include <stddef.h> - -/* Windows DLL stuff */ -#ifdef JSON_PARSER_DLL -# ifdef _MSC_VER -# ifdef JSON_PARSER_DLL_EXPORTS -# define JSON_PARSER_DLL_API __declspec(dllexport) -# else -# define JSON_PARSER_DLL_API __declspec(dllimport) -# endif -# else -# define JSON_PARSER_DLL_API -# endif -#else -# define JSON_PARSER_DLL_API -#endif - -/* Determine the integer type use to parse non-floating point numbers */ -#if __STDC_VERSION__ >= 199901L || HAVE_LONG_LONG == 1 -typedef long long JSON_int_t; -#define JSON_PARSER_INTEGER_SSCANF_TOKEN "%lld" -#define JSON_PARSER_INTEGER_SPRINTF_TOKEN "%lld" -#else -typedef long JSON_int_t; -#define JSON_PARSER_INTEGER_SSCANF_TOKEN "%ld" -#define JSON_PARSER_INTEGER_SPRINTF_TOKEN "%ld" -#endif - - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum -{ - JSON_E_NONE = 0, - JSON_E_INVALID_CHAR, - JSON_E_INVALID_KEYWORD, - JSON_E_INVALID_ESCAPE_SEQUENCE, - JSON_E_INVALID_UNICODE_SEQUENCE, - JSON_E_INVALID_NUMBER, - JSON_E_NESTING_DEPTH_REACHED, - JSON_E_UNBALANCED_COLLECTION, - JSON_E_EXPECTED_KEY, - JSON_E_EXPECTED_COLON, - JSON_E_OUT_OF_MEMORY -} JSON_error; - -typedef enum -{ - JSON_T_NONE = 0, - JSON_T_ARRAY_BEGIN, - JSON_T_ARRAY_END, - JSON_T_OBJECT_BEGIN, - JSON_T_OBJECT_END, - JSON_T_INTEGER, - JSON_T_FLOAT, - JSON_T_NULL, - JSON_T_TRUE, - JSON_T_FALSE, - JSON_T_STRING, - JSON_T_KEY, - JSON_T_MAX -} JSON_type; - -typedef struct JSON_value_struct { - union { - JSON_int_t integer_value; - - double float_value; - - struct { - const char* value; - size_t length; - } str; - } vu; -} JSON_value; - -typedef struct JSON_parser_struct* JSON_parser; - -/*! \brief JSON parser callback - - \param ctx The pointer passed to new_JSON_parser. - \param type An element of JSON_type but not JSON_T_NONE. - \param value A representation of the parsed value. This parameter is NULL for - JSON_T_ARRAY_BEGIN, JSON_T_ARRAY_END, JSON_T_OBJECT_BEGIN, JSON_T_OBJECT_END, - JSON_T_NULL, JSON_T_TRUE, and JSON_T_FALSE. String values are always returned - as zero-terminated C strings. - - \return Non-zero if parsing should continue, else zero. -*/ -typedef int (*JSON_parser_callback)(void* ctx, int type, const struct JSON_value_struct* value); - - -/** - A typedef for allocator functions semantically compatible with malloc(). -*/ -typedef void* (*JSON_malloc_t)(size_t n); -/** - A typedef for deallocator functions semantically compatible with free(). -*/ -typedef void (*JSON_free_t)(void* mem); - -/*! \brief The structure used to configure a JSON parser object -*/ -typedef struct { - /** Pointer to a callback, called when the parser has something to tell - the user. This parameter may be NULL. In this case the input is - merely checked for validity. - */ - JSON_parser_callback callback; - /** - Callback context - client-specified data to pass to the - callback function. This parameter may be NULL. - */ - void* callback_ctx; - /** Specifies the levels of nested JSON to allow. Negative numbers yield unlimited nesting. - If negative, the parser can parse arbitrary levels of JSON, otherwise - the depth is the limit. - */ - int depth; - /** - To allow C style comments in JSON, set to non-zero. - */ - int allow_comments; - /** - To decode floating point numbers manually set this parameter to - non-zero. - */ - int handle_floats_manually; - /** - The memory allocation routine, which must be semantically - compatible with malloc(3). If set to NULL, malloc(3) is used. - - If this is set to a non-NULL value then the 'free' member MUST be - set to the proper deallocation counterpart for this function. - Failure to do so results in undefined behaviour at deallocation - time. - */ - JSON_malloc_t malloc; - /** - The memory deallocation routine, which must be semantically - compatible with free(3). If set to NULL, free(3) is used. - - If this is set to a non-NULL value then the 'alloc' member MUST be - set to the proper allocation counterpart for this function. - Failure to do so results in undefined behaviour at deallocation - time. - */ - JSON_free_t free; -} JSON_config; - -/*! \brief Initializes the JSON parser configuration structure to default values. - - The default configuration is - - 127 levels of nested JSON (depends on JSON_PARSER_STACK_SIZE, see json_parser.c) - - no parsing, just checking for JSON syntax - - no comments - - Uses realloc() for memory de/allocation. - - \param config. Used to configure the parser. -*/ -JSON_PARSER_DLL_API void init_JSON_config(JSON_config * config); - -/*! \brief Create a JSON parser object - - \param config. Used to configure the parser. Set to NULL to use - the default configuration. See init_JSON_config. Its contents are - copied by this function, so it need not outlive the returned - object. - - \return The parser object, which is owned by the caller and must eventually - be freed by calling delete_JSON_parser(). -*/ -JSON_PARSER_DLL_API JSON_parser new_JSON_parser(JSON_config const* config); - -/*! \brief Destroy a previously created JSON parser object. */ -JSON_PARSER_DLL_API void delete_JSON_parser(JSON_parser jc); - -/*! \brief Parse a character. - - \return Non-zero, if all characters passed to this function are part of are valid JSON. -*/ -JSON_PARSER_DLL_API int JSON_parser_char(JSON_parser jc, int next_char); - -/*! \brief Finalize parsing. - - Call this method once after all input characters have been consumed. - - \return Non-zero, if all parsed characters are valid JSON, zero otherwise. -*/ -JSON_PARSER_DLL_API int JSON_parser_done(JSON_parser jc); - -/*! \brief Determine if a given string is valid JSON white space - - \return Non-zero if the string is valid, zero otherwise. -*/ -JSON_PARSER_DLL_API int JSON_parser_is_legal_white_space_string(const char* s); - -/*! \brief Gets the last error that occurred during the use of JSON_parser. - - \return A value from the JSON_error enum. -*/ -JSON_PARSER_DLL_API int JSON_parser_get_last_error(JSON_parser jc); - -/*! \brief Re-sets the parser to prepare it for another parse run. - - \return True (non-zero) on success, 0 on error (e.g. !jc). -*/ -JSON_PARSER_DLL_API int JSON_parser_reset(JSON_parser jc); - - -#ifdef __cplusplus -} -#endif - - -#endif /* JSON_PARSER_H */ -/* end file parser/JSON_parser.h */ -/* begin file parser/JSON_parser.c */ -/* -Copyright (c) 2005 JSON.org - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -The Software shall be used for Good, not Evil. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -/* - Callbacks, comments, Unicode handling by Jean Gressmann (jean@0x42.de), 2007-2010. - - - Changelog: - 2010-11-25 - Support for custom memory allocation (sgbeal@googlemail.com). - - 2010-05-07 - Added error handling for memory allocation failure (sgbeal@googlemail.com). - Added diagnosis errors for invalid JSON. - - 2010-03-25 - Fixed buffer overrun in grow_parse_buffer & cleaned up code. - - 2009-10-19 - Replaced long double in JSON_value_struct with double after reports - of strtold being broken on some platforms (charles@transmissionbt.com). - - 2009-05-17 - Incorporated benrudiak@googlemail.com fix for UTF16 decoding. - - 2009-05-14 - Fixed float parsing bug related to a locale being set that didn't - use '.' as decimal point character (charles@transmissionbt.com). - - 2008-10-14 - Renamed states.IN to states.IT to avoid name clash which IN macro - defined in windef.h (alexey.pelykh@gmail.com) - - 2008-07-19 - Removed some duplicate code & debugging variable (charles@transmissionbt.com) - - 2008-05-28 - Made JSON_value structure ansi C compliant. This bug was report by - trisk@acm.jhu.edu - - 2008-05-20 - Fixed bug reported by charles@transmissionbt.com where the switching - from static to dynamic parse buffer did not copy the static parse - buffer's content. -*/ - - - -#include <assert.h> -#include <ctype.h> -#include <float.h> -#include <stddef.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <locale.h> - - -#ifdef _MSC_VER -# if _MSC_VER >= 1400 /* Visual Studio 2005 and up */ -# pragma warning(disable:4996) /* unsecure sscanf */ -# pragma warning(disable:4127) /* conditional expression is constant */ -# endif -#endif - - -#define true 1 -#define false 0 -#define __ -1 /* the universal error code */ - -/* values chosen so that the object size is approx equal to one page (4K) */ -#ifndef JSON_PARSER_STACK_SIZE -# define JSON_PARSER_STACK_SIZE 128 -#endif - -#ifndef JSON_PARSER_PARSE_BUFFER_SIZE -# define JSON_PARSER_PARSE_BUFFER_SIZE 3500 -#endif - -typedef void* (*JSON_debug_malloc_t)(size_t bytes, const char* reason); - -#ifdef JSON_PARSER_DEBUG_MALLOC -# define JSON_parser_malloc(func, bytes, reason) ((JSON_debug_malloc_t)func)(bytes, reason) -#else -# define JSON_parser_malloc(func, bytes, reason) func(bytes) -#endif - -typedef unsigned short UTF16; - -struct JSON_parser_struct { - JSON_parser_callback callback; - void* ctx; - signed char state, before_comment_state, type, escaped, comment, allow_comments, handle_floats_manually, error; - char decimal_point; - UTF16 utf16_high_surrogate; - int current_char; - int depth; - int top; - int stack_capacity; - signed char* stack; - char* parse_buffer; - size_t parse_buffer_capacity; - size_t parse_buffer_count; - signed char static_stack[JSON_PARSER_STACK_SIZE]; - char static_parse_buffer[JSON_PARSER_PARSE_BUFFER_SIZE]; - JSON_malloc_t malloc; - JSON_free_t free; -}; - -#define COUNTOF(x) (sizeof(x)/sizeof(x[0])) - -/* - Characters are mapped into these character classes. This allows for - a significant reduction in the size of the state transition table. -*/ - - - -enum classes { - C_SPACE, /* space */ - C_WHITE, /* other whitespace */ - C_LCURB, /* { */ - C_RCURB, /* } */ - C_LSQRB, /* [ */ - C_RSQRB, /* ] */ - C_COLON, /* : */ - C_COMMA, /* , */ - C_QUOTE, /* " */ - C_BACKS, /* \ */ - C_SLASH, /* / */ - C_PLUS, /* + */ - C_MINUS, /* - */ - C_POINT, /* . */ - C_ZERO , /* 0 */ - C_DIGIT, /* 123456789 */ - C_LOW_A, /* a */ - C_LOW_B, /* b */ - C_LOW_C, /* c */ - C_LOW_D, /* d */ - C_LOW_E, /* e */ - C_LOW_F, /* f */ - C_LOW_L, /* l */ - C_LOW_N, /* n */ - C_LOW_R, /* r */ - C_LOW_S, /* s */ - C_LOW_T, /* t */ - C_LOW_U, /* u */ - C_ABCDF, /* ABCDF */ - C_E, /* E */ - C_ETC, /* everything else */ - C_STAR, /* * */ - NR_CLASSES -}; - -static const signed char ascii_class[128] = { -/* - This array maps the 128 ASCII characters into character classes. - The remaining Unicode characters should be mapped to C_ETC. - Non-whitespace control characters are errors. -*/ - __, __, __, __, __, __, __, __, - __, C_WHITE, C_WHITE, __, __, C_WHITE, __, __, - __, __, __, __, __, __, __, __, - __, __, __, __, __, __, __, __, - - C_SPACE, C_ETC, C_QUOTE, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, - C_ETC, C_ETC, C_STAR, C_PLUS, C_COMMA, C_MINUS, C_POINT, C_SLASH, - C_ZERO, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, - C_DIGIT, C_DIGIT, C_COLON, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, - - C_ETC, C_ABCDF, C_ABCDF, C_ABCDF, C_ABCDF, C_E, C_ABCDF, C_ETC, - C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, - C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, - C_ETC, C_ETC, C_ETC, C_LSQRB, C_BACKS, C_RSQRB, C_ETC, C_ETC, - - C_ETC, C_LOW_A, C_LOW_B, C_LOW_C, C_LOW_D, C_LOW_E, C_LOW_F, C_ETC, - C_ETC, C_ETC, C_ETC, C_ETC, C_LOW_L, C_ETC, C_LOW_N, C_ETC, - C_ETC, C_ETC, C_LOW_R, C_LOW_S, C_LOW_T, C_LOW_U, C_ETC, C_ETC, - C_ETC, C_ETC, C_ETC, C_LCURB, C_ETC, C_RCURB, C_ETC, C_ETC -}; - - -/* - The state codes. -*/ -enum states { - GO, /* start */ - OK, /* ok */ - OB, /* object */ - KE, /* key */ - CO, /* colon */ - VA, /* value */ - AR, /* array */ - ST, /* string */ - ES, /* escape */ - U1, /* u1 */ - U2, /* u2 */ - U3, /* u3 */ - U4, /* u4 */ - MI, /* minus */ - ZE, /* zero */ - IT, /* integer */ - FR, /* fraction */ - E1, /* e */ - E2, /* ex */ - E3, /* exp */ - T1, /* tr */ - T2, /* tru */ - T3, /* true */ - F1, /* fa */ - F2, /* fal */ - F3, /* fals */ - F4, /* false */ - N1, /* nu */ - N2, /* nul */ - N3, /* null */ - C1, /* / */ - C2, /* / * */ - C3, /* * */ - FX, /* *.* *eE* */ - D1, /* second UTF-16 character decoding started by \ */ - D2, /* second UTF-16 character proceeded by u */ - NR_STATES -}; - -enum actions -{ - CB = -10, /* comment begin */ - CE = -11, /* comment end */ - FA = -12, /* false */ - TR = -13, /* false */ - NU = -14, /* null */ - DE = -15, /* double detected by exponent e E */ - DF = -16, /* double detected by fraction . */ - SB = -17, /* string begin */ - MX = -18, /* integer detected by minus */ - ZX = -19, /* integer detected by zero */ - IX = -20, /* integer detected by 1-9 */ - EX = -21, /* next char is escaped */ - UC = -22 /* Unicode character read */ -}; - - -static const signed char state_transition_table[NR_STATES][NR_CLASSES] = { -/* - The state transition table takes the current state and the current symbol, - and returns either a new state or an action. An action is represented as a - negative number. A JSON text is accepted if at the end of the text the - state is OK and if the mode is MODE_DONE. - - white 1-9 ABCDF etc - space | { } [ ] : , " \ / + - . 0 | a b c d e f l n r s t u | E | * */ -/*start GO*/ {GO,GO,-6,__,-5,__,__,__,__,__,CB,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, -/*ok OK*/ {OK,OK,__,-8,__,-7,__,-3,__,__,CB,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, -/*object OB*/ {OB,OB,__,-9,__,__,__,__,SB,__,CB,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, -/*key KE*/ {KE,KE,__,__,__,__,__,__,SB,__,CB,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, -/*colon CO*/ {CO,CO,__,__,__,__,-2,__,__,__,CB,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, -/*value VA*/ {VA,VA,-6,__,-5,__,__,__,SB,__,CB,__,MX,__,ZX,IX,__,__,__,__,__,FA,__,NU,__,__,TR,__,__,__,__,__}, -/*array AR*/ {AR,AR,-6,__,-5,-7,__,__,SB,__,CB,__,MX,__,ZX,IX,__,__,__,__,__,FA,__,NU,__,__,TR,__,__,__,__,__}, -/*string ST*/ {ST,__,ST,ST,ST,ST,ST,ST,-4,EX,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST}, -/*escape ES*/ {__,__,__,__,__,__,__,__,ST,ST,ST,__,__,__,__,__,__,ST,__,__,__,ST,__,ST,ST,__,ST,U1,__,__,__,__}, -/*u1 U1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,U2,U2,U2,U2,U2,U2,U2,U2,__,__,__,__,__,__,U2,U2,__,__}, -/*u2 U2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,U3,U3,U3,U3,U3,U3,U3,U3,__,__,__,__,__,__,U3,U3,__,__}, -/*u3 U3*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,U4,U4,U4,U4,U4,U4,U4,U4,__,__,__,__,__,__,U4,U4,__,__}, -/*u4 U4*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,UC,UC,UC,UC,UC,UC,UC,UC,__,__,__,__,__,__,UC,UC,__,__}, -/*minus MI*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,ZE,IT,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, -/*zero ZE*/ {OK,OK,__,-8,__,-7,__,-3,__,__,CB,__,__,DF,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, -/*int IT*/ {OK,OK,__,-8,__,-7,__,-3,__,__,CB,__,__,DF,IT,IT,__,__,__,__,DE,__,__,__,__,__,__,__,__,DE,__,__}, -/*frac FR*/ {OK,OK,__,-8,__,-7,__,-3,__,__,CB,__,__,__,FR,FR,__,__,__,__,E1,__,__,__,__,__,__,__,__,E1,__,__}, -/*e E1*/ {__,__,__,__,__,__,__,__,__,__,__,E2,E2,__,E3,E3,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, -/*ex E2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,E3,E3,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, -/*exp E3*/ {OK,OK,__,-8,__,-7,__,-3,__,__,__,__,__,__,E3,E3,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, -/*tr T1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,T2,__,__,__,__,__,__,__}, -/*tru T2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,T3,__,__,__,__}, -/*true T3*/ {__,__,__,__,__,__,__,__,__,__,CB,__,__,__,__,__,__,__,__,__,OK,__,__,__,__,__,__,__,__,__,__,__}, -/*fa F1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,F2,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, -/*fal F2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,F3,__,__,__,__,__,__,__,__,__}, -/*fals F3*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,F4,__,__,__,__,__,__}, -/*false F4*/ {__,__,__,__,__,__,__,__,__,__,CB,__,__,__,__,__,__,__,__,__,OK,__,__,__,__,__,__,__,__,__,__,__}, -/*nu N1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,N2,__,__,__,__}, -/*nul N2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,N3,__,__,__,__,__,__,__,__,__}, -/*null N3*/ {__,__,__,__,__,__,__,__,__,__,CB,__,__,__,__,__,__,__,__,__,__,__,OK,__,__,__,__,__,__,__,__,__}, -/*/ C1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,C2}, -/*/* C2*/ {C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C3}, -/** C3*/ {C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,CE,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C3}, -/*_. FX*/ {OK,OK,__,-8,__,-7,__,-3,__,__,__,__,__,__,FR,FR,__,__,__,__,E1,__,__,__,__,__,__,__,__,E1,__,__}, -/*\ D1*/ {__,__,__,__,__,__,__,__,__,D2,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, -/*\ D2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,U1,__,__,__,__}, -}; - - -/* - These modes can be pushed on the stack. -*/ -enum modes { - MODE_ARRAY = 1, - MODE_DONE = 2, - MODE_KEY = 3, - MODE_OBJECT = 4 -}; - -static void set_error(JSON_parser jc) -{ - switch (jc->state) { - case GO: - switch (jc->current_char) { - case '{': case '}': case '[': case ']': - jc->error = JSON_E_UNBALANCED_COLLECTION; - break; - default: - jc->error = JSON_E_INVALID_CHAR; - break; - } - break; - case OB: - jc->error = JSON_E_EXPECTED_KEY; - break; - case AR: - jc->error = JSON_E_UNBALANCED_COLLECTION; - break; - case CO: - jc->error = JSON_E_EXPECTED_COLON; - break; - case KE: - jc->error = JSON_E_EXPECTED_KEY; - break; - /* \uXXXX\uYYYY */ - case U1: case U2: case U3: case U4: case D1: case D2: - jc->error = JSON_E_INVALID_UNICODE_SEQUENCE; - break; - /* true, false, null */ - case T1: case T2: case T3: case F1: case F2: case F3: case F4: case N1: case N2: case N3: - jc->error = JSON_E_INVALID_KEYWORD; - break; - /* minus, integer, fraction, exponent */ - case MI: case ZE: case IT: case FR: case E1: case E2: case E3: - jc->error = JSON_E_INVALID_NUMBER; - break; - default: - jc->error = JSON_E_INVALID_CHAR; - break; - } -} - -static int -push(JSON_parser jc, int mode) -{ -/* - Push a mode onto the stack. Return false if there is overflow. -*/ - assert(jc->top <= jc->stack_capacity); - - if (jc->depth < 0) { - if (jc->top == jc->stack_capacity) { - const size_t bytes_to_copy = jc->stack_capacity * sizeof(jc->stack[0]); - const size_t new_capacity = jc->stack_capacity * 2; - const size_t bytes_to_allocate = new_capacity * sizeof(jc->stack[0]); - void* mem = JSON_parser_malloc(jc->malloc, bytes_to_allocate, "stack"); - if (!mem) { - jc->error = JSON_E_OUT_OF_MEMORY; - return false; - } - jc->stack_capacity = (int)new_capacity; - memcpy(mem, jc->stack, bytes_to_copy); - if (jc->stack != &jc->static_stack[0]) { - jc->free(jc->stack); - } - jc->stack = (signed char*)mem; - } - } else { - if (jc->top == jc->depth) { - jc->error = JSON_E_NESTING_DEPTH_REACHED; - return false; - } - } - jc->stack[++jc->top] = (signed char)mode; - return true; -} - - -static int -pop(JSON_parser jc, int mode) -{ -/* - Pop the stack, assuring that the current mode matches the expectation. - Return false if there is underflow or if the modes mismatch. -*/ - if (jc->top < 0 || jc->stack[jc->top] != mode) { - return false; - } - jc->top -= 1; - return true; -} - - -#define parse_buffer_clear(jc) \ - do {\ - jc->parse_buffer_count = 0;\ - jc->parse_buffer[0] = 0;\ - } while (0) - -#define parse_buffer_pop_back_char(jc)\ - do {\ - assert(jc->parse_buffer_count >= 1);\ - --jc->parse_buffer_count;\ - jc->parse_buffer[jc->parse_buffer_count] = 0;\ - } while (0) - - - -void delete_JSON_parser(JSON_parser jc) -{ - if (jc) { - if (jc->stack != &jc->static_stack[0]) { - jc->free((void*)jc->stack); - } - if (jc->parse_buffer != &jc->static_parse_buffer[0]) { - jc->free((void*)jc->parse_buffer); - } - jc->free((void*)jc); - } -} - -int JSON_parser_reset(JSON_parser jc) -{ - if (NULL == jc) { - return false; - } - - jc->state = GO; - jc->top = -1; - - /* parser has been used previously? */ - if (NULL == jc->parse_buffer) { - - /* Do we want non-bound stack? */ - if (jc->depth > 0) { - jc->stack_capacity = jc->depth; - if (jc->depth <= (int)COUNTOF(jc->static_stack)) { - jc->stack = &jc->static_stack[0]; - } else { - const size_t bytes_to_alloc = jc->stack_capacity * sizeof(jc->stack[0]); - jc->stack = (signed char*)JSON_parser_malloc(jc->malloc, bytes_to_alloc, "stack"); - if (jc->stack == NULL) { - return false; - } - } - } else { - jc->stack_capacity = (int)COUNTOF(jc->static_stack); - jc->depth = -1; - jc->stack = &jc->static_stack[0]; - } - - /* set up the parse buffer */ - jc->parse_buffer = &jc->static_parse_buffer[0]; - jc->parse_buffer_capacity = COUNTOF(jc->static_parse_buffer); - } - - /* set parser to start */ - push(jc, MODE_DONE); - parse_buffer_clear(jc); - - return true; -} - -JSON_parser -new_JSON_parser(JSON_config const * config) -{ -/* - new_JSON_parser starts the checking process by constructing a JSON_parser - object. It takes a depth parameter that restricts the level of maximum - nesting. - - To continue the process, call JSON_parser_char for each character in the - JSON text, and then call JSON_parser_done to obtain the final result. - These functions are fully reentrant. -*/ - - int use_std_malloc = false; - JSON_config default_config; - JSON_parser jc; - JSON_malloc_t alloc; - - /* set to default configuration if none was provided */ - if (NULL == config) { - /* initialize configuration */ - init_JSON_config(&default_config); - config = &default_config; - } - - /* use std malloc if either the allocator or deallocator function isn't set */ - use_std_malloc = NULL == config->malloc || NULL == config->free; - - alloc = use_std_malloc ? malloc : config->malloc; - - jc = JSON_parser_malloc(alloc, sizeof(*jc), "parser"); - - if (NULL == jc) { - return NULL; - } - - /* configure the parser */ - memset(jc, 0, sizeof(*jc)); - jc->malloc = alloc; - jc->free = use_std_malloc ? free : config->free; - jc->callback = config->callback; - jc->ctx = config->callback_ctx; - jc->allow_comments = (signed char)(config->allow_comments != 0); - jc->handle_floats_manually = (signed char)(config->handle_floats_manually != 0); - jc->decimal_point = *localeconv()->decimal_point; - /* We need to be able to push at least one object */ - jc->depth = config->depth == 0 ? 1 : config->depth; - - /* reset the parser */ - if (!JSON_parser_reset(jc)) { - jc->free(jc); - return NULL; - } - - return jc; -} - -static int parse_buffer_grow(JSON_parser jc) -{ - const size_t bytes_to_copy = jc->parse_buffer_count * sizeof(jc->parse_buffer[0]); - const size_t new_capacity = jc->parse_buffer_capacity * 2; - const size_t bytes_to_allocate = new_capacity * sizeof(jc->parse_buffer[0]); - void* mem = JSON_parser_malloc(jc->malloc, bytes_to_allocate, "parse buffer"); - - if (mem == NULL) { - jc->error = JSON_E_OUT_OF_MEMORY; - return false; - } - - assert(new_capacity > 0); - memcpy(mem, jc->parse_buffer, bytes_to_copy); - - if (jc->parse_buffer != &jc->static_parse_buffer[0]) { - jc->free(jc->parse_buffer); - } - - jc->parse_buffer = (char*)mem; - jc->parse_buffer_capacity = new_capacity; - - return true; -} - -static int parse_buffer_reserve_for(JSON_parser jc, unsigned chars) -{ - while (jc->parse_buffer_count + chars + 1 > jc->parse_buffer_capacity) { - if (!parse_buffer_grow(jc)) { - assert(jc->error == JSON_E_OUT_OF_MEMORY); - return false; - } - } - - return true; -} - -#define parse_buffer_has_space_for(jc, count) \ - (jc->parse_buffer_count + (count) + 1 <= jc->parse_buffer_capacity) - -#define parse_buffer_push_back_char(jc, c)\ - do {\ - assert(parse_buffer_has_space_for(jc, 1)); \ - jc->parse_buffer[jc->parse_buffer_count++] = c;\ - jc->parse_buffer[jc->parse_buffer_count] = 0;\ - } while (0) - -#define assert_is_non_container_type(jc) \ - assert( \ - jc->type == JSON_T_NULL || \ - jc->type == JSON_T_FALSE || \ - jc->type == JSON_T_TRUE || \ - jc->type == JSON_T_FLOAT || \ - jc->type == JSON_T_INTEGER || \ - jc->type == JSON_T_STRING) - - -static int parse_parse_buffer(JSON_parser jc) -{ - if (jc->callback) { - JSON_value value, *arg = NULL; - - if (jc->type != JSON_T_NONE) { - assert_is_non_container_type(jc); - - switch(jc->type) { - case JSON_T_FLOAT: - arg = &value; - if (jc->handle_floats_manually) { - value.vu.str.value = jc->parse_buffer; - value.vu.str.length = jc->parse_buffer_count; - } else { - /* not checking with end pointer b/c there may be trailing ws */ - value.vu.float_value = strtod(jc->parse_buffer, NULL); - } - break; - case JSON_T_INTEGER: - arg = &value; - sscanf(jc->parse_buffer, JSON_PARSER_INTEGER_SSCANF_TOKEN, &value.vu.integer_value); - break; - case JSON_T_STRING: - arg = &value; - value.vu.str.value = jc->parse_buffer; - value.vu.str.length = jc->parse_buffer_count; - break; - } - - if (!(*jc->callback)(jc->ctx, jc->type, arg)) { - return false; - } - } - } - - parse_buffer_clear(jc); - - return true; -} - -#define IS_HIGH_SURROGATE(uc) (((uc) & 0xFC00) == 0xD800) -#define IS_LOW_SURROGATE(uc) (((uc) & 0xFC00) == 0xDC00) -#define DECODE_SURROGATE_PAIR(hi,lo) ((((hi) & 0x3FF) << 10) + ((lo) & 0x3FF) + 0x10000) -static const unsigned char utf8_lead_bits[4] = { 0x00, 0xC0, 0xE0, 0xF0 }; - -static int decode_unicode_char(JSON_parser jc) -{ - int i; - unsigned uc = 0; - char* p; - int trail_bytes; - - assert(jc->parse_buffer_count >= 6); - - p = &jc->parse_buffer[jc->parse_buffer_count - 4]; - - for (i = 12; i >= 0; i -= 4, ++p) { - unsigned x = *p; - - if (x >= 'a') { - x -= ('a' - 10); - } else if (x >= 'A') { - x -= ('A' - 10); - } else { - x &= ~0x30u; - } - - assert(x < 16); - - uc |= x << i; - } - - /* clear UTF-16 char from buffer */ - jc->parse_buffer_count -= 6; - jc->parse_buffer[jc->parse_buffer_count] = 0; - - /* attempt decoding ... */ - if (jc->utf16_high_surrogate) { - if (IS_LOW_SURROGATE(uc)) { - uc = DECODE_SURROGATE_PAIR(jc->utf16_high_surrogate, uc); - trail_bytes = 3; - jc->utf16_high_surrogate = 0; - } else { - /* high surrogate without a following low surrogate */ - return false; - } - } else { - if (uc < 0x80) { - trail_bytes = 0; - } else if (uc < 0x800) { - trail_bytes = 1; - } else if (IS_HIGH_SURROGATE(uc)) { - /* save the high surrogate and wait for the low surrogate */ - jc->utf16_high_surrogate = (UTF16)uc; - return true; - } else if (IS_LOW_SURROGATE(uc)) { - /* low surrogate without a preceding high surrogate */ - return false; - } else { - trail_bytes = 2; - } - } - - jc->parse_buffer[jc->parse_buffer_count++] = (char) ((uc >> (trail_bytes * 6)) | utf8_lead_bits[trail_bytes]); - - for (i = trail_bytes * 6 - 6; i >= 0; i -= 6) { - jc->parse_buffer[jc->parse_buffer_count++] = (char) (((uc >> i) & 0x3F) | 0x80); - } - - jc->parse_buffer[jc->parse_buffer_count] = 0; - - return true; -} - -static int add_escaped_char_to_parse_buffer(JSON_parser jc, int next_char) -{ - assert(parse_buffer_has_space_for(jc, 1)); - - jc->escaped = 0; - /* remove the backslash */ - parse_buffer_pop_back_char(jc); - switch(next_char) { - case 'b': - parse_buffer_push_back_char(jc, '\b'); - break; - case 'f': - parse_buffer_push_back_char(jc, '\f'); - break; - case 'n': - parse_buffer_push_back_char(jc, '\n'); - break; - case 'r': - parse_buffer_push_back_char(jc, '\r'); - break; - case 't': - parse_buffer_push_back_char(jc, '\t'); - break; - case '"': - parse_buffer_push_back_char(jc, '"'); - break; - case '\\': - parse_buffer_push_back_char(jc, '\\'); - break; - case '/': - parse_buffer_push_back_char(jc, '/'); - break; - case 'u': - parse_buffer_push_back_char(jc, '\\'); - parse_buffer_push_back_char(jc, 'u'); - break; - default: - return false; - } - - return true; -} - -static int add_char_to_parse_buffer(JSON_parser jc, int next_char, int next_class) -{ - if (!parse_buffer_reserve_for(jc, 1)) { - assert(JSON_E_OUT_OF_MEMORY == jc->error); - return false; - } - - if (jc->escaped) { - if (!add_escaped_char_to_parse_buffer(jc, next_char)) { - jc->error = JSON_E_INVALID_ESCAPE_SEQUENCE; - return false; - } - } else if (!jc->comment) { - if ((jc->type != JSON_T_NONE) | !((next_class == C_SPACE) | (next_class == C_WHITE)) /* non-white-space */) { - parse_buffer_push_back_char(jc, (char)next_char); - } - } - - return true; -} - -#define assert_type_isnt_string_null_or_bool(jc) \ - assert(jc->type != JSON_T_FALSE); \ - assert(jc->type != JSON_T_TRUE); \ - assert(jc->type != JSON_T_NULL); \ - assert(jc->type != JSON_T_STRING) - - -int -JSON_parser_char(JSON_parser jc, int next_char) -{ -/* - After calling new_JSON_parser, call this function for each character (or - partial character) in your JSON text. It can accept UTF-8, UTF-16, or - UTF-32. It returns true if things are looking ok so far. If it rejects the - text, it returns false. -*/ - int next_class, next_state; - -/* - Store the current char for error handling -*/ - jc->current_char = next_char; - -/* - Determine the character's class. -*/ - if (next_char < 0) { - jc->error = JSON_E_INVALID_CHAR; - return false; - } - if (next_char >= 128) { - next_class = C_ETC; - } else { - next_class = ascii_class[next_char]; - if (next_class <= __) { - set_error(jc); - return false; - } - } - - if (!add_char_to_parse_buffer(jc, next_char, next_class)) { - return false; - } - -/* - Get the next state from the state transition table. -*/ - next_state = state_transition_table[jc->state][next_class]; - if (next_state >= 0) { -/* - Change the state. -*/ - jc->state = (signed char)next_state; - } else { -/* - Or perform one of the actions. -*/ - switch (next_state) { -/* Unicode character */ - case UC: - if(!decode_unicode_char(jc)) { - jc->error = JSON_E_INVALID_UNICODE_SEQUENCE; - return false; - } - /* check if we need to read a second UTF-16 char */ - if (jc->utf16_high_surrogate) { - jc->state = D1; - } else { - jc->state = ST; - } - break; -/* escaped char */ - case EX: - jc->escaped = 1; - jc->state = ES; - break; -/* integer detected by minus */ - case MX: - jc->type = JSON_T_INTEGER; - jc->state = MI; - break; -/* integer detected by zero */ - case ZX: - jc->type = JSON_T_INTEGER; - jc->state = ZE; - break; -/* integer detected by 1-9 */ - case IX: - jc->type = JSON_T_INTEGER; - jc->state = IT; - break; - -/* floating point number detected by exponent*/ - case DE: - assert_type_isnt_string_null_or_bool(jc); - jc->type = JSON_T_FLOAT; - jc->state = E1; - break; - -/* floating point number detected by fraction */ - case DF: - assert_type_isnt_string_null_or_bool(jc); - if (!jc->handle_floats_manually) { -/* - Some versions of strtod (which underlies sscanf) don't support converting - C-locale formated floating point values. -*/ - assert(jc->parse_buffer[jc->parse_buffer_count-1] == '.'); - jc->parse_buffer[jc->parse_buffer_count-1] = jc->decimal_point; - } - jc->type = JSON_T_FLOAT; - jc->state = FX; - break; -/* string begin " */ - case SB: - parse_buffer_clear(jc); - assert(jc->type == JSON_T_NONE); - jc->type = JSON_T_STRING; - jc->state = ST; - break; - -/* n */ - case NU: - assert(jc->type == JSON_T_NONE); - jc->type = JSON_T_NULL; - jc->state = N1; - break; -/* f */ - case FA: - assert(jc->type == JSON_T_NONE); - jc->type = JSON_T_FALSE; - jc->state = F1; - break; -/* t */ - case TR: - assert(jc->type == JSON_T_NONE); - jc->type = JSON_T_TRUE; - jc->state = T1; - break; - -/* closing comment */ - case CE: - jc->comment = 0; - assert(jc->parse_buffer_count == 0); - assert(jc->type == JSON_T_NONE); - jc->state = jc->before_comment_state; - break; - -/* opening comment */ - case CB: - if (!jc->allow_comments) { - return false; - } - parse_buffer_pop_back_char(jc); - if (!parse_parse_buffer(jc)) { - return false; - } - assert(jc->parse_buffer_count == 0); - assert(jc->type != JSON_T_STRING); - switch (jc->stack[jc->top]) { - case MODE_ARRAY: - case MODE_OBJECT: - switch(jc->state) { - case VA: - case AR: - jc->before_comment_state = jc->state; - break; - default: - jc->before_comment_state = OK; - break; - } - break; - default: - jc->before_comment_state = jc->state; - break; - } - jc->type = JSON_T_NONE; - jc->state = C1; - jc->comment = 1; - break; -/* empty } */ - case -9: - parse_buffer_clear(jc); - if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_OBJECT_END, NULL)) { - return false; - } - if (!pop(jc, MODE_KEY)) { - return false; - } - jc->state = OK; - break; - -/* } */ case -8: - parse_buffer_pop_back_char(jc); - if (!parse_parse_buffer(jc)) { - return false; - } - if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_OBJECT_END, NULL)) { - return false; - } - if (!pop(jc, MODE_OBJECT)) { - jc->error = JSON_E_UNBALANCED_COLLECTION; - return false; - } - jc->type = JSON_T_NONE; - jc->state = OK; - break; - -/* ] */ case -7: - parse_buffer_pop_back_char(jc); - if (!parse_parse_buffer(jc)) { - return false; - } - if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_ARRAY_END, NULL)) { - return false; - } - if (!pop(jc, MODE_ARRAY)) { - jc->error = JSON_E_UNBALANCED_COLLECTION; - return false; - } - - jc->type = JSON_T_NONE; - jc->state = OK; - break; - -/* { */ case -6: - parse_buffer_pop_back_char(jc); - if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_OBJECT_BEGIN, NULL)) { - return false; - } - if (!push(jc, MODE_KEY)) { - return false; - } - assert(jc->type == JSON_T_NONE); - jc->state = OB; - break; - -/* [ */ case -5: - parse_buffer_pop_back_char(jc); - if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_ARRAY_BEGIN, NULL)) { - return false; - } - if (!push(jc, MODE_ARRAY)) { - return false; - } - assert(jc->type == JSON_T_NONE); - jc->state = AR; - break; - -/* string end " */ case -4: - parse_buffer_pop_back_char(jc); - switch (jc->stack[jc->top]) { - case MODE_KEY: - assert(jc->type == JSON_T_STRING); - jc->type = JSON_T_NONE; - jc->state = CO; - - if (jc->callback) { - JSON_value value; - value.vu.str.value = jc->parse_buffer; - value.vu.str.length = jc->parse_buffer_count; - if (!(*jc->callback)(jc->ctx, JSON_T_KEY, &value)) { - return false; - } - } - parse_buffer_clear(jc); - break; - case MODE_ARRAY: - case MODE_OBJECT: - assert(jc->type == JSON_T_STRING); - if (!parse_parse_buffer(jc)) { - return false; - } - jc->type = JSON_T_NONE; - jc->state = OK; - break; - default: - return false; - } - break; - -/* , */ case -3: - parse_buffer_pop_back_char(jc); - if (!parse_parse_buffer(jc)) { - return false; - } - switch (jc->stack[jc->top]) { - case MODE_OBJECT: -/* - A comma causes a flip from object mode to key mode. -*/ - if (!pop(jc, MODE_OBJECT) || !push(jc, MODE_KEY)) { - return false; - } - assert(jc->type != JSON_T_STRING); - jc->type = JSON_T_NONE; - jc->state = KE; - break; - case MODE_ARRAY: - assert(jc->type != JSON_T_STRING); - jc->type = JSON_T_NONE; - jc->state = VA; - break; - default: - return false; - } - break; - -/* : */ case -2: -/* - A colon causes a flip from key mode to object mode. -*/ - parse_buffer_pop_back_char(jc); - if (!pop(jc, MODE_KEY) || !push(jc, MODE_OBJECT)) { - return false; - } - assert(jc->type == JSON_T_NONE); - jc->state = VA; - break; -/* - Bad action. -*/ - default: - set_error(jc); - return false; - } - } - return true; -} - -int -JSON_parser_done(JSON_parser jc) -{ - if ((jc->state == OK || jc->state == GO) && pop(jc, MODE_DONE)) - { - return true; - } - - jc->error = JSON_E_UNBALANCED_COLLECTION; - return false; -} - - -int JSON_parser_is_legal_white_space_string(const char* s) -{ - int c, char_class; - - if (s == NULL) { - return false; - } - - for (; *s; ++s) { - c = *s; - - if (c < 0 || c >= 128) { - return false; - } - - char_class = ascii_class[c]; - - if (char_class != C_SPACE && char_class != C_WHITE) { - return false; - } - } - - return true; -} - -int JSON_parser_get_last_error(JSON_parser jc) -{ - return jc->error; -} - - -void init_JSON_config(JSON_config* config) -{ - if (config) { - memset(config, 0, sizeof(*config)); - - config->depth = JSON_PARSER_STACK_SIZE - 1; - config->malloc = malloc; - config->free = free; - } -} -/* end file parser/JSON_parser.c */ -/* begin file ./cson.c */ -#include <assert.h> -#include <stdlib.h> /* malloc()/free() */ -#include <string.h> - -#ifdef _MSC_VER -# if _MSC_VER >= 1400 /* Visual Studio 2005 and up */ -# pragma warning( push ) -# pragma warning(disable:4996) /* unsecure sscanf (but snscanf() isn't in c89) */ -# pragma warning(disable:4244) /* complaining about data loss due - to integer precision in the - sqlite3 utf decoding routines */ -# endif -#endif - -#if 1 -#include <stdio.h> -#define MARKER if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf -#else -static void noop_printf(char const * fmt, ...) {} -#define MARKER if(0) printf -#endif - -#if defined(__cplusplus) -extern "C" { -#endif - - - -/** - Type IDs corresponding to JavaScript/JSON types. -*/ -enum cson_type_id { - /** - The special "null" value constant. - - Its value must be 0 for internal reasons. - */ - CSON_TYPE_UNDEF = 0, - /** - The special "null" value constant. - */ - CSON_TYPE_NULL = 1, - /** - The bool value type. - */ - CSON_TYPE_BOOL = 2, - /** - The integer value type, represented in this library - by cson_int_t. - */ - CSON_TYPE_INTEGER = 3, - /** - The double value type, represented in this library - by cson_double_t. - */ - CSON_TYPE_DOUBLE = 4, - /** The immutable string type. This library stores strings - as immutable UTF8. - */ - CSON_TYPE_STRING = 5, - /** The "Array" type. */ - CSON_TYPE_ARRAY = 6, - /** The "Object" type. */ - CSON_TYPE_OBJECT = 7 -}; -typedef enum cson_type_id cson_type_id; - -/** - This type holds the "vtbl" for type-specific operations when - working with cson_value objects. - - All cson_values of a given logical type share a pointer to a single - library-internal instance of this class. -*/ -struct cson_value_api -{ - /** - The logical JavaScript/JSON type associated with - this object. - */ - const cson_type_id typeID; - /** - Must free any memory associated with self, - but not free self. If self is NULL then - this function must do nothing. - */ - void (*cleanup)( cson_value * self ); - /** - POSSIBLE TODOs: - - // Deep copy. - int (*clone)( cson_value const * self, cson_value ** tgt ); - - // Using JS semantics for true/value - char (*bool_value)( cson_value const * self ); - - // memcmp() return value semantics - int (*compare)( cson_value const * self, cson_value const * other ); - */ -}; - -typedef struct cson_value_api cson_value_api; - -/** - Empty-initialized cson_value_api object. -*/ -#define cson_value_api_empty_m { \ - CSON_TYPE_UNDEF/*typeID*/, \ - NULL/*cleanup*/\ - } -/** - Empty-initialized cson_value_api object. -*/ -static const cson_value_api cson_value_api_empty = cson_value_api_empty_m; - - -typedef unsigned int cson_counter_t; -struct cson_value -{ - /** The "vtbl" of type-specific operations. All instances - of a given logical value type share a single api instance. - - Results are undefined if this value is NULL. - */ - cson_value_api const * api; - - /** The raw value. Its interpretation depends on the value of the - api member. Some value types require dynamically-allocated - memory, so one must always call cson_value_free() to destroy a - value when it is no longer needed. For stack-allocated values - (which client could SHOULD NOT USE unless they are intimately - familiar with the memory management rules and don't mind an - occasional leak or crash), use cson_value_clean() instead of - cson_value_free(). - */ - void * value; - - /** - We use this to allow us to store cson_value instances in - multiple containers or multiple times within a single container - (provided no cycles are introduced). - - Notes about the rc implementation: - - - The refcount is for the cson_value instance itself, not its - value pointer. - - - Instances start out with a refcount of 0 (not 1). Adding them - to a container will increase the refcount. Cleaning up the container - will decrement the count. - - - cson_value_free() decrements the refcount (if it is not already - 0) and cleans/frees the value only when the refcount is 0. - - - Some places in the internals add an "extra" reference to - objects to avoid a premature deletion. Don't try this at home. - */ - cson_counter_t refcount; -}; - - -/** - Empty-initialized cson_value object. -*/ -#define cson_value_empty_m { &cson_value_api_empty/*api*/, NULL/*value*/, 0/*refcount*/ } -/** - Empty-initialized cson_value object. -*/ -extern const cson_value cson_value_empty; - -const cson_value cson_value_empty = cson_value_empty_m; -const cson_parse_opt cson_parse_opt_empty = cson_parse_opt_empty_m; -const cson_output_opt cson_output_opt_empty = cson_output_opt_empty_m; -const cson_object_iterator cson_object_iterator_empty = cson_object_iterator_empty_m; -const cson_buffer cson_buffer_empty = cson_buffer_empty_m; -const cson_parse_info cson_parse_info_empty = cson_parse_info_empty_m; - -static void cson_value_destroy_zero_it( cson_value * self ); -static void cson_value_destroy_free( cson_value * self ); -static void cson_value_destroy_object( cson_value * self ); -static void cson_value_destroy_integer( cson_value * self ); -/** - If self is-a array then this function destroys its contents, - else this function does nothing. -*/ -static void cson_value_destroy_array( cson_value * self ); - -/** - If self is-a string then this function destroys its contents, - else this function does nothing. -*/ -static void cson_value_destroy_string( cson_value * self ); - -static const cson_value_api cson_value_api_null = { CSON_TYPE_NULL, cson_value_destroy_zero_it }; -static const cson_value_api cson_value_api_undef = { CSON_TYPE_UNDEF, cson_value_destroy_zero_it }; -static const cson_value_api cson_value_api_bool = { CSON_TYPE_BOOL, cson_value_destroy_zero_it }; -static const cson_value_api cson_value_api_integer = { CSON_TYPE_INTEGER, cson_value_destroy_integer }; -static const cson_value_api cson_value_api_double = { CSON_TYPE_DOUBLE, cson_value_destroy_free }; -static const cson_value_api cson_value_api_string = { CSON_TYPE_STRING, cson_value_destroy_string }; -static const cson_value_api cson_value_api_array = { CSON_TYPE_ARRAY, cson_value_destroy_array }; -static const cson_value_api cson_value_api_object = { CSON_TYPE_OBJECT, cson_value_destroy_object }; - -static const cson_value cson_value_undef = { &cson_value_api_undef, NULL, 0 }; -static const cson_value cson_value_bool_empty = { &cson_value_api_bool, NULL, 0 }; -static const cson_value cson_value_integer_empty = { &cson_value_api_integer, NULL, 0 }; -static const cson_value cson_value_double_empty = { &cson_value_api_double, NULL, 0 }; -static const cson_value cson_value_string_empty = { &cson_value_api_string, NULL, 0 }; -static const cson_value cson_value_array_empty = { &cson_value_api_array, NULL, 0 }; -static const cson_value cson_value_object_empty = { &cson_value_api_object, NULL, 0 }; - - -struct cson_string -{ - unsigned int length; -}; -#define cson_string_empty_m {0/*length*/} - -/** - - Holds special shared "constant" (though they are non-const) - values. - -*/ -static struct CSON_EMPTY_HOLDER_ -{ - char trueValue; - cson_string stringValue; -} CSON_EMPTY_HOLDER = { - 1/*trueValue*/, - cson_string_empty_m -}; - -/** - Indexes into the CSON_SPECIAL_VALUES array. - - If this enum changes in any way, - makes damned sure that CSON_SPECIAL_VALUES is updated - to match!!! -*/ -enum CSON_INTERNAL_VALUES { - - CSON_VAL_UNDEF = 0, - CSON_VAL_NULL = 1, - CSON_VAL_TRUE = 2, - CSON_VAL_FALSE = 3, - CSON_VAL_INT_0 = 4, - CSON_VAL_DBL_0 = 5, - CSON_VAL_STR_EMPTY = 6, - CSON_INTERNAL_VALUES_LENGTH -}; - -/** - Some "special" shared cson_value instances. - - These values MUST be initialized in the order specified - by the CSON_INTERNAL_VALUES enum. - - Note that they are not const because they are used as - shared-allocation objects in non-const contexts. However, the - public API provides no way to modifying them, and clients who - modify values directly are subject to The Wrath of Undefined - Behaviour. -*/ -static cson_value CSON_SPECIAL_VALUES[] = { -{ &cson_value_api_undef, NULL, 0 }, /* UNDEF */ -{ &cson_value_api_null, NULL, 0 }, /* NULL */ -{ &cson_value_api_bool, &CSON_EMPTY_HOLDER.trueValue, 0 }, /* TRUE */ -{ &cson_value_api_bool, NULL, 0 }, /* FALSE */ -{ &cson_value_api_integer, NULL, 0 }, /* INT_0 */ -{ &cson_value_api_double, NULL, 0 }, /* DBL_0 */ -{ &cson_value_api_string, &CSON_EMPTY_HOLDER.stringValue, 0 }, /* STR_EMPTY */ -{ 0, NULL, 0 } -}; - - -/** - Returns non-0 (true) if m is one of our special - "built-in" values, e.g. from CSON_SPECIAL_VALUES and some - "empty" values. - - If this returns true, m MUST NOT be free()d! - */ -static char cson_value_is_builtin( void const * m ) -{ - if((m >= (void const *)&CSON_EMPTY_HOLDER) - && ( m < (void const *)(&CSON_EMPTY_HOLDER+1))) - return 1; - else return - ((m > (void const *)&CSON_SPECIAL_VALUES[0]) - && ( m < (void const *)&CSON_SPECIAL_VALUES[CSON_INTERNAL_VALUES_LENGTH]) ) - ? 1 - : 0; -} - -char const * cson_rc_string(int rc) -{ - if(0 == rc) return "OK"; -#define CHECK(N) else if(cson_rc.N == rc ) return #N - CHECK(OK); - CHECK(ArgError); - CHECK(RangeError); - CHECK(TypeError); - CHECK(IOError); - CHECK(AllocError); - CHECK(NYIError); - CHECK(InternalError); - CHECK(UnsupportedError); - CHECK(NotFoundError); - CHECK(UnknownError); - CHECK(Parse_INVALID_CHAR); - CHECK(Parse_INVALID_KEYWORD); - CHECK(Parse_INVALID_ESCAPE_SEQUENCE); - CHECK(Parse_INVALID_UNICODE_SEQUENCE); - CHECK(Parse_INVALID_NUMBER); - CHECK(Parse_NESTING_DEPTH_REACHED); - CHECK(Parse_UNBALANCED_COLLECTION); - CHECK(Parse_EXPECTED_KEY); - CHECK(Parse_EXPECTED_COLON); - else return "UnknownError"; -#undef CHECK -} - -/** - If CSON_LOG_ALLOC is true then the cson_malloc/realloc/free() routines - will log a message to stderr. -*/ -#define CSON_LOG_ALLOC 0 - -/** - A test/debug macro for simulating an OOM after the given number of - bytes have been allocated. -*/ -#define CSON_SIMULATE_OOM 0 -#if CSON_SIMULATE_OOM -static unsigned int cson_totalAlloced = 0; -#endif - -/** Simple proxy for malloc(). descr is a description of the allocation. */ -static void * cson_malloc( size_t n, char const * descr ) -{ -#if CSON_LOG_ALLOC - fprintf(stderr, "Allocating %u bytes [%s].\n", (unsigned int)n, descr); -#endif -#if CSON_SIMULATE_OOM - cson_totalAlloced += n; - if( cson_totalAlloced > CSON_SIMULATE_OOM ) - { - return NULL; - } -#endif - return malloc(n); -} - -/** Simple proxy for free(). descr is a description of the memory being freed. */ -static void cson_free( void * p, char const * descr ) -{ -#if CSON_LOG_ALLOC - fprintf(stderr, "Freeing @%p [%s].\n", p, descr); -#endif - if( !cson_value_is_builtin(p) ) - { - free( p ); - } -} -/** Simple proxy for realloc(). descr is a description of the (re)allocation. */ -static void * cson_realloc( void * hint, size_t n, char const * descr ) -{ -#if CSON_LOG_ALLOC - fprintf(stderr, "%sllocating %u bytes [%s].\n", - hint ? "Rea" : "A", - (unsigned int)n, descr); -#endif -#if CSON_SIMULATE_OOM - cson_totalAlloced += n; - if( cson_totalAlloced > CSON_SIMULATE_OOM ) - { - return NULL; - } -#endif - if( 0==n ) - { - cson_free(hint, descr); - return NULL; - } - else - { - return realloc( hint, n ); - } -} - - -#undef CSON_LOG_ALLOC -#undef CSON_SIMULATE_OOM - - - -/** - CLIENTS CODE SHOULD NEVER USE THIS because it opens up doors to - memory leaks if it is not used in very controlled circumstances. - Users must be very aware of how the underlying memory management - works. - - Frees any resources owned by val, but does not free val itself - (which may be stack-allocated). If !val or val->api or - val->api->cleanup are NULL then this is a no-op. - - If v is a container type (object or array) its children are also - cleaned up (BUT NOT FREED), recursively. - - After calling this, val will have the special "undefined" type. -*/ -static void cson_value_clean( cson_value * val ); - -/** - Increments cv's reference count by 1. As a special case, values - for which cson_value_is_builtin() returns true are not - modified. assert()s if (NULL==cv). -*/ -static void cson_refcount_incr( cson_value * cv ) -{ - assert( NULL != cv ); - if( cson_value_is_builtin( cv ) ) - { /* do nothing: we do not want to modify the shared - instances. - */ - return; - } - else - { - ++cv->refcount; - } -} - -#if 0 -int cson_value_refcount_set( cson_value * cv, unsigned short rc ) -{ - if( NULL == cv ) return cson_rc.ArgError; - else - { - cv->refcount = rc; - return 0; - } -} -#endif - -int cson_value_add_reference( cson_value * cv ) -{ - if( NULL == cv ) return cson_rc.ArgError; - else if( (cv->refcount+1) < cv->refcount ) - { - return cson_rc.RangeError; - } - else - { - cson_refcount_incr( cv ); - return 0; - } -} - -/** - If cv is NULL or cson_value_is_builtin(cv) returns true then this - function does nothing and returns 0, otherwise... If - cv->refcount is 0 or 1 then cson_value_clean(cv) is called, cv is - freed, and 0 is returned. If cv->refcount is any other value then - it is decremented and the new value is returned. -*/ -static cson_counter_t cson_refcount_decr( cson_value * cv ) -{ - if( (NULL == cv) || cson_value_is_builtin(cv) ) return 0; - else if( (0 == cv->refcount) || (0 == --cv->refcount) ) - { - cson_value_clean(cv); - cson_free(cv,"cson_value::refcount=0"); - return 0; - } - else return cv->refcount; -} - -/** - Allocates a new cson_string object with enough space for - the given number of bytes. A byte for a NUL terminator - is added automatically. Use cson_string_str() to get - access to the string bytes, which will be len bytes long. - - len may be 0, in which case the internal string is "", as opposed - to null. This is because the string bytes and the cson_string are - allocated in a single chunk of memory, and the cson_string object - does not directly provide (or have) a pointer to the string bytes. -*/ -static cson_string * cson_string_alloc(unsigned int len) -{ - if( ! len ) return &CSON_EMPTY_HOLDER.stringValue; - else - { - cson_string * s = NULL; - const size_t msz = sizeof(cson_string) + len + 1 /*NUL*/; - unsigned char * mem = NULL; - if( msz < (sizeof(cson_string)+len) ) /*overflow*/ return NULL; - mem = (unsigned char *)cson_malloc( msz, "cson_string_alloc" ); - if( mem ) - { - memset( mem, 0, msz ); - s = (cson_string *)mem; - s->length = len; - } - return s; - } -} - -unsigned int cson_string_length_bytes( cson_string const * str ) -{ - return str ? str->length : 0; -} - - -/** - Fetches v's string value as a non-const string. - - cson_strings are supposed to be immutable, but this form provides - access to the immutable bits, which are v->length bytes long. A - length-0 string is returned as NULL from here, as opposed to - "". (This is a side-effect of the string allocation mechanism.) - Returns NULL if !v. -*/ -static char * cson_string_str(cson_string *v) -{ - /* - See http://groups.google.com/group/comp.lang.c.moderated/browse_thread/thread/2e0c0df5e8a0cd6a - */ -#if 1 - if( !v || (&CSON_EMPTY_HOLDER.stringValue == v) ) return NULL; - else return (char *)((unsigned char *)( v+1 )); -#else - static char empty[2] = {0,0}; - return ( NULL == v ) - ? NULL - : (v->length - ? (char *) (((unsigned char *)v) + sizeof(cson_string)) - : empty) - ; -#endif -} - -/** - Fetches v's string value as a const string. -*/ -char const * cson_string_cstr(cson_string const *v) -{ - /* - See http://groups.google.com/group/comp.lang.c.moderated/browse_thread/thread/2e0c0df5e8a0cd6a - */ -#if 1 - if( ! v ) return NULL; - else if( v == &CSON_EMPTY_HOLDER.stringValue ) return ""; - else return (char *)((unsigned char *)(v+1)); -#else - return (NULL == v) - ? NULL - : (v->length - ? (char const *) ((unsigned char const *)(v+1)) - : ""); -#endif -} - - -#if 0 -/** - Just like strndup(3), in that neither are C89/C99-standard and both - are documented in detail in strndup(3). -*/ -static char * cson_strdup( char const * src, size_t n ) -{ - char * rc = (char *)cson_malloc(n+1, "cson_strdup"); - if( ! rc ) return NULL; - memset( rc, 0, n+1 ); - rc[n] = 0; - return strncpy( rc, src, n ); -} -#endif - -/** - Allocates a new cson_string() from the the first n bytes of src. - Returns NULL on allocation error, else the caller owns the returned - object and must eventually free() it. -*/ -static cson_string * cson_string_strdup( char const * src, size_t n ) -{ - cson_string * cs = cson_string_alloc(n); - if( ! cs ) return NULL; - else if( &CSON_EMPTY_HOLDER.stringValue == cs ) return cs; - else - { - char * cstr = cson_string_str(cs); - assert( cs->length == n ); - if( cstr && n ) - { - strncpy( cstr, src, n ); - } - return cs; - } -} - - -int cson_string_cmp_cstr_n( cson_string const * str, char const * other, unsigned int otherLen ) -{ - if( ! other && !str ) return 0; - else if( other && !str ) return 1; - else if( str && !other ) return -1; - else if( !otherLen ) return str->length ? 1 : 0; - else if( !str->length ) return otherLen ? -1 : 0; - else - { - unsigned const int max = (otherLen > str->length) ? otherLen : str->length; - int const rc = strncmp( cson_string_cstr(str), other, max ); - return ( (0 == rc) && (otherLen != str->length) ) - ? (str->length < otherLen) ? -1 : 1 - : rc; - } -} - -int cson_string_cmp_cstr( cson_string const * lhs, char const * rhs ) -{ - return cson_string_cmp_cstr_n( lhs, rhs, (rhs&&*rhs) ? strlen(rhs) : 0 ); -} -int cson_string_cmp( cson_string const * lhs, cson_string const * rhs ) -{ - return cson_string_cmp_cstr_n( lhs, cson_string_cstr(rhs), rhs ? rhs->length : 0 ); -} - - -/** - If self is not NULL, *self is overwritten to have the undefined - type. self is not cleaned up or freed. -*/ -void cson_value_destroy_zero_it( cson_value * self ) -{ - if( self ) - { - *self = cson_value_undef; - } -} - -/** - If self is not null, free(self->value) is called. *self is then - overwritten to have the undefined type. self is not freed. - -*/ -void cson_value_destroy_free( cson_value * self ) -{ - if(self) { - if( self->value ) - { - cson_free(self->value,"cson_value_destroy_free()"); - } - *self = cson_value_undef; - } -} - - - -/** - A key/value pair collection. - - Each of these objects owns its key/value pointers, and they - are cleaned up by cson_kvp_clean(). -*/ -struct cson_kvp -{ - cson_string * key; - cson_value * value; -}; -#define cson_kvp_empty_m {NULL,NULL} -static const cson_kvp cson_kvp_empty = cson_kvp_empty_m; - -/** @def CSON_OBJECT_PROPS_SORT - - If CSON_OBJECT_PROPS_SORT is set to a true value then - qsort() and bsearch() are used to sort (upon insertion) - and search cson_object::kvp property lists. This costs us - a re-sort on each insertion but searching is O(log n) - average/worst case (and O(1) best-case). - - i'm not yet convinced that the overhead of the qsort() justifies - the potentially decreased search times - it has not been - measured. Object property lists tend to be relatively short in - JSON, and a linear search which uses the cson_string::length - property as a quick check is quite fast when one compares it with - the sort overhead required by the bsearch() approach. -*/ -#define CSON_OBJECT_PROPS_SORT 0 - -/** @def CSON_OBJECT_PROPS_SORT_USE_LENGTH - - Don't use this - i'm not sure that it works how i'd like. - - If CSON_OBJECT_PROPS_SORT_USE_LENGTH is true then - we use string lengths as quick checks when sorting - property keys. This leads to a non-intuitive sorting - order but "should" be faster. - - This is ignored if CSON_OBJECT_PROPS_SORT is false. - -*/ -#define CSON_OBJECT_PROPS_SORT_USE_LENGTH 0 - -#if CSON_OBJECT_PROPS_SORT - -#if 0 -/* i would prefer case-insensitive sorting but keys need - to be case-sensitive for search purposes. - - TODO? write a comparitor which reverses the default - sort order of upper/lower-case. The current behaviour - is a bit non-intuitive, where 'Z' < 'a'. -*/ -static int cson_kvp_strcmp( char const * l, char const * r ) -{ - char cl; - char cr; - for( ; *l && *r; ++l, ++r ) - { - cl = tolower(*l); - cr = tolower(*r); - if( cl < cr ) return -1; - else if( cl > cr ) return 1; - } - if( !*l && !*r ) return 0; - else return (*l) ? 1 : -1; -} -#endif - -/** - cson_kvp comparator for use with qsort(). ALMOST compares with - strcmp() semantics, but it uses the strings' lengths as a quicker - approach. This might give non-intuitive results, but it's faster. - */ -static int cson_kvp_cmp( void const * lhs, void const * rhs ) -{ - cson_kvp const * lk = *((cson_kvp const * const*)lhs); - cson_kvp const * rk = *((cson_kvp const * const*)rhs); - cson_string const * l = lk->key; - cson_string const * r = rk->key; -#if CSON_OBJECT_PROPS_SORT_USE_LENGTH - if( l->length < r->length ) return -1; - else if( l->length > r->length ) return 1; - else return strcmp( cson_string_cstr( l ), cson_string_cstr( r ) ); -#else - return strcmp( cson_string_cstr( l ), - cson_string_cstr( r ) ); -#endif /*CSON_OBJECT_PROPS_SORT_USE_LENGTH*/ -} -#endif /*CSON_OBJECT_PROPS_SORT*/ - - -#if CSON_OBJECT_PROPS_SORT -/** - A bsearch() comparison function which requires that lhs be a (char - const *) and rhs be-a (cson_kvp const * const *). It compares lhs - to rhs->key's value, using strcmp() semantics. - */ -static int cson_kvp_cmp_vs_cstr( void const * lhs, void const * rhs ) -{ - char const * lk = (char const *)lhs; - cson_kvp const * rk = - *((cson_kvp const * const*)rhs) - ; -#if CSON_OBJECT_PROPS_SORT_USE_LENGTH - unsigned int llen = strlen(lk); - if( llen < rk->key->length ) return -1; - else if( llen > rk->key->length ) return 1; - else return strcmp( lk, cson_string_cstr( rk->key ) ); -#else - return strcmp( lk, cson_string_cstr( rk->key ) ); -#endif /*CSON_OBJECT_PROPS_SORT_USE_LENGTH*/ -} -#endif /*CSON_OBJECT_PROPS_SORT*/ - - -struct cson_kvp_list -{ - cson_kvp ** list; - unsigned int count; - unsigned int alloced; -}; -typedef struct cson_kvp_list cson_kvp_list; -#define cson_kvp_list_empty_m {NULL/*list*/,0/*count*/,0/*alloced*/} -static const cson_kvp_list cson_kvp_list_empty = cson_kvp_list_empty_m; - -struct cson_object -{ - cson_kvp_list kvp; -}; -/*typedef struct cson_object cson_object;*/ -#define cson_object_empty_m { cson_kvp_list_empty_m/*kvp*/ } -static const cson_object cson_object_empty = cson_object_empty_m; - -struct cson_value_list -{ - cson_value ** list; - unsigned int count; - unsigned int alloced; -}; -typedef struct cson_value_list cson_value_list; -#define cson_value_list_empty_m {NULL/*list*/,0/*count*/,0/*alloced*/} -static const cson_value_list cson_value_list_empty = cson_value_list_empty_m; - -struct cson_array -{ - cson_value_list list; -}; -/*typedef struct cson_array cson_array;*/ -#define cson_array_empty_m { cson_value_list_empty_m/*list*/ } -static const cson_array cson_array_empty = cson_array_empty_m; - - -struct cson_parser -{ - JSON_parser p; - cson_value * root; - cson_value * node; - cson_array stack; - cson_string * ckey; - int errNo; - unsigned int totalKeyCount; - unsigned int totalValueCount; -}; -typedef struct cson_parser cson_parser; -static const cson_parser cson_parser_empty = { -NULL/*p*/, -NULL/*root*/, -NULL/*node*/, -cson_array_empty_m/*stack*/, -NULL/*ckey*/, -0/*errNo*/, -0/*totalKeyCount*/, -0/*totalValueCount*/ -}; - -#if 1 -/* The following funcs are declared in generated code (cson_lists.h), - but we need early access to their decls for the Amalgamation build. -*/ -static unsigned int cson_value_list_reserve( cson_value_list * self, unsigned int n ); -static unsigned int cson_kvp_list_reserve( cson_kvp_list * self, unsigned int n ); -static int cson_kvp_list_append( cson_kvp_list * self, cson_kvp * cp ); -static void cson_kvp_list_clean( cson_kvp_list * self, - void (*cleaner)(cson_kvp * obj) ); -#if 0 -static int cson_value_list_append( cson_value_list * self, cson_value * cp ); -static void cson_value_list_clean( cson_value_list * self, void (*cleaner)(cson_value * obj)); -static int cson_kvp_list_visit( cson_kvp_list * self, - int (*visitor)(cson_kvp * obj, void * visitorState ), - void * visitorState ); -static int cson_value_list_visit( cson_value_list * self, - int (*visitor)(cson_value * obj, void * visitorState ), - void * visitorState ); -#endif -#endif - -#if 0 -# define LIST_T cson_value_list -# define VALUE_T cson_value * -# define VALUE_T_IS_PTR 1 -# define LIST_T cson_kvp_list -# define VALUE_T cson_kvp * -# define VALUE_T_IS_PTR 1 -#else -#endif - -/* - Reminders to self: - - - 20110126: moved cson_value_new() and cson_value_set_xxx() out of the - public API because: - - a) They can be easily mis-used to cause memory leaks, even when used in - a manner which seems relatively intuitive. - - b) Having them in the API prohibits us from eventually doing certain - allocation optimizations like not allocating Booleans, - Integer/Doubles with the value 0, or empty Strings. The main problem - is that cson_value_set_xxx() cannot be implemented properly if we - add that type of optimization. -*/ - -/** - Allocates a new value with the "undefined" value and transfers - ownership of it to the caller. Use The cson_value_set_xxx() family - of functions to assign a typed value to it. It must eventually be - destroyed, by the caller or its owning container, by passing it to - cson_value_free(). - - Returns NULL on allocation error. - - @see cson_value_new_array() - @see cson_value_new_object() - @see cson_value_new_string() - @see cson_value_new_integer() - @see cson_value_new_double() - @see cson_value_new_bool() - @see cson_value_free() -*/ -static cson_value * cson_value_new(); -/** - Cleans any existing contents of val and sets its new value - to the special NULL value. - - Returns 0 on success. - -*/ -#if 0 -static int cson_value_set_null( cson_value * val ); -#endif -/** - Cleans any existing contents of val and sets its new value - to v. - - Returns 0 on success. - -*/ -#if 0 -static int cson_value_set_bool( cson_value * val, char v ); -#endif -/** - Cleans any existing contents of val and sets its new value - to v. - - Returns 0 on success. - -*/ -static int cson_value_set_integer( cson_value * val, cson_int_t v ); -/** - Cleans any existing contents of val and sets its new value - to v. - - Returns 0 on success. -*/ -static int cson_value_set_double( cson_value * val, cson_double_t v ); - -/** - Cleans any existing contents of val and sets its new value to - str. On success, ownership of str is passed on to val. On error - ownership is not changed. - - Returns 0 on success. - - If str is NULL, (!*str), or (!len) then this function does not - allocate any memory for a new string, and cson_value_fetch_string() - will return an empty string as opposed to a NULL string. -*/ -static int cson_value_set_string( cson_value * val, char const * str, unsigned int len ); - - -cson_value * cson_value_new() -{ - cson_value * v = (cson_value *)cson_malloc(sizeof(cson_value),"cson_value_new"); - if( v ) *v = cson_value_undef; - return v; -} - - -void cson_value_free(cson_value *v) -{ - cson_refcount_decr( v ); -} - -#if 0 /* we might actually want this later on. */ -/** Returns true if v is not NULL and has the given type ID. */ -static char cson_value_is_a( cson_value const * v, cson_type_id is ) -{ - return (v && v->api && (v->api->typeID == is)) ? 1 : 0; -} -#endif - -#if 0 -cson_type_id cson_value_type_id( cson_value const * v ) -{ - return (v && v->api) ? v->api->typeID : CSON_TYPE_UNDEF; -} -#endif - -char cson_value_is_undef( cson_value const * v ) -{ - /** - This special-case impl is needed because the underlying - (generic) list operations do not know how to populate - new entries - */ - return ( !v || !v->api || (v->api==&cson_value_api_undef)) - ? 1 : 0; -} -#define ISA(T,TID) char cson_value_is_##T( cson_value const * v ) { \ - /*return (v && v->api) ? cson_value_is_a(v,CSON_TYPE_##TID) : 0;*/ \ - return (v && (v->api == &cson_value_api_##T)) ? 1 : 0; \ - } static const char bogusPlaceHolderForEmacsIndention##TID = CSON_TYPE_##TID -ISA(null,NULL); -ISA(bool,BOOL); -ISA(integer,INTEGER); -ISA(double,DOUBLE); -ISA(string,STRING); -ISA(array,ARRAY); -ISA(object,OBJECT); -#undef ISA -char cson_value_is_number( cson_value const * v ) -{ - return cson_value_is_integer(v) || cson_value_is_double(v); -} - - -void cson_value_clean( cson_value * val ) -{ - if( val && val->api && val->api->cleanup ) - { - if( ! cson_value_is_builtin( val ) ) - { - cson_counter_t const rc = val->refcount; - val->api->cleanup(val); - *val = cson_value_undef; - val->refcount = rc; - } - } -} - -static void cson_value_destroy_integer( cson_value * self ) -{ - if( self ) - { -#if !CSON_VOID_PTR_IS_BIG - cson_free(self->value,"cson_int_t"); -#endif - *self = cson_value_empty; - } -} -static int cson_value_set_integer( cson_value * val, cson_int_t v ) -{ - if( ! val ) return cson_rc.ArgError; - else - { -#if CSON_VOID_PTR_IS_BIG - cson_value_clean( val ); - val->value = (void *)v; -#else - cson_int_t * iv = NULL; - iv = (cson_int_t*)cson_malloc(sizeof(cson_int_t), "cson_int_t"); - if( ! iv ) return cson_rc.AllocError; - cson_value_clean( val ); - *iv = v; - val->value = iv; -#endif - val->api = &cson_value_api_integer; - return 0; - } -} - -static int cson_value_set_double( cson_value * val, cson_double_t v ) -{ - if( ! val ) return cson_rc.ArgError; - else - { - cson_double_t * rv = NULL; - cson_value_clean( val ); - val->api = &cson_value_api_double; - if( 0.0 != v ) - { - /* - Reminder: we can't re-use val if it alreay is-a double - because we have no reference counting. - */ - rv = (cson_double_t*)cson_malloc(sizeof(cson_double_t),"double"); - if( ! rv ) return cson_rc.AllocError; - } - if(NULL != rv) *rv = v; - val->value = rv; - return 0; - } -} - -static cson_string * cson_string_shared_empty() -{ - /** - We have code in place elsewhere to avoid that - cson_string_cstr(&bob) and cson_string_str(&bob) will misbehave - by accessing the bytes directly after bob (which are undefined - in this case). - */ -#if 0 - static cson_string bob[2] = {cson_string_empty_m, - cson_string_empty_m/*trick to 0-init bob[0]'s tail*/}; - return &bob[0]; -#else - static cson_string bob = cson_string_empty_m; - return &bob; -#endif -} - -static int cson_value_set_string( cson_value * val, char const * str, unsigned int len ) -{ - cson_string * jstr = NULL; - if( ! val ) return cson_rc.ArgError; - cson_value_clean( val ); - val->api = &cson_value_api_string; - if( !str || !*str || !len ) - { - val->value = cson_string_shared_empty(); - return 0; - } - else - { - jstr = cson_string_alloc( len ); - if( NULL == jstr ) return cson_rc.AllocError; - else - { - if( len ) - { - char * dest = cson_string_str( jstr ); - val->value = jstr; - strncpy( dest, str, len ); - } - /* else it's the empty string special value */ - return 0; - } - } -} - - -static cson_value * cson_value_array_alloc() -{ - cson_value * v = (cson_value*)cson_malloc(sizeof(cson_value),"cson_value_array"); - if( NULL != v ) - { - cson_array * ar = (cson_array *)cson_malloc(sizeof(cson_array),"cson_array"); - if( ! ar ) - { - cson_free(v,"cson_array"); - v = NULL; - } - else - { - *ar = cson_array_empty; - *v = cson_value_array_empty; - v->value = ar; - } - } - return v; -} - -static cson_value * cson_value_object_alloc() -{ - cson_value * v = (cson_value*)cson_malloc(sizeof(cson_value),"cson_value_object"); - if( NULL != v ) - { - cson_object * obj = (cson_object*)cson_malloc(sizeof(cson_object),"cson_value"); - if( ! obj ) - { - cson_free(v,"cson_value_object"); - v = NULL; - } - else - { - *obj = cson_object_empty; - *v = cson_value_object_empty; - v->value = obj; - } - } - return v; -} - -cson_value * cson_value_new_object() -{ - return cson_value_object_alloc(); -} - -cson_value * cson_value_new_array() -{ - return cson_value_array_alloc(); -} - -/** - Frees kvp->key and kvp->value and sets them to NULL, but does not free - kvp. If !kvp then this is a no-op. -*/ -static void cson_kvp_clean( cson_kvp * kvp ) -{ - if( kvp ) - { - if(kvp->key) - { - cson_free(kvp->key,"cson_kvp::key"); - kvp->key = NULL; - } - if(kvp->value) - { - cson_value_free( kvp->value ); - kvp->value = NULL; - } - } -} - -cson_string const * cson_kvp_key( cson_kvp const * kvp ) -{ - return kvp ? kvp->key : NULL; -} -cson_value * cson_kvp_value( cson_kvp const * kvp ) -{ - return kvp ? kvp->value : NULL; -} - - -/** - Calls cson_kvp_clean(kvp) and then frees kvp. -*/ -static void cson_kvp_free( cson_kvp * kvp ) -{ - if( kvp ) - { - cson_kvp_clean(kvp); - cson_free(kvp,"cson_kvp"); - } -} - -/** - cson_value_api::destroy_value() impl for Object - values. Cleans up self-owned memory and overwrites - self to have the undefined value, but does not - free self. - - If self->value == cson_string_shared_empty() - then this function does not actually free it. -*/ -static void cson_value_destroy_string( cson_value * self ) -{ - if(self && self->value) { - cson_string * obj = (cson_string *)self->value; - if( obj != cson_string_shared_empty() ) - { - cson_free(self->value,"cson_string"); - } - *self = cson_value_undef; - } -} - - -/** - cson_value_api::destroy_value() impl for Object - values. Cleans up self-owned memory and overwrites - self to have the undefined value, but does not - free self. -*/ -static void cson_value_destroy_object( cson_value * self ) -{ - if(self && self->value) { - cson_object * obj = (cson_object *)self->value; - assert( self->value == obj ); - cson_kvp_list_clean( &obj->kvp, cson_kvp_free ); - cson_free(self->value,"cson_object"); - *self = cson_value_undef; - } -} - -/** - Cleans up the contents of ar->list, but does not free ar. - - After calling this, ar will have a length of 0. - - If properlyCleanValues is 1 then cson_value_free() is called on - each non-NULL item, otherwise the outer list is destroyed but the - individual items are assumed to be owned by someone else and are - not freed. -*/ -static void cson_array_clean( cson_array * ar, char properlyCleanValues ) -{ - if( ar ) - { - unsigned int i = 0; - cson_value * val = NULL; - for( ; i < ar->list.count; ++i ) - { - val = ar->list.list[i]; - if(val) - { - ar->list.list[i] = NULL; - if( properlyCleanValues ) - { - cson_value_free( val ); - } - } - } - cson_value_list_reserve(&ar->list,0); - ar->list = cson_value_list_empty - /* Pedantic note: reserve(0) already clears the list-specific - fields, but we do this just in case we ever add new fields - to cson_value_list which are not used in the reserve() impl. - */ - ; - } -} - -/** - cson_value_api::destroy_value() impl for Array - values. Cleans up self-owned memory and overwrites - self to have the undefined value, but does not - free self. -*/ -static void cson_value_destroy_array( cson_value * self ) -{ - cson_array * ar = cson_value_get_array(self); - if(ar) { - assert( self->value == ar ); - cson_array_clean( ar, 1 ); - cson_free(ar,"cson_array"); - *self = cson_value_undef; - } -} - - -#if 0 -static void cson_kvp_list_item_clean( void * kvp ); -static void cson_value_list_item_clean( void * val ); -void cson_value_list_item_clean( void * val_ ) -{ - cson_value * val = (cson_value*)val_; - if( val ) - { - cson_value_clean(val); - } -} -void cson_kvp_list_item_clean( void * val ) -{ - cson_kvp * kvp = (cson_kvp *)val; - if( kvp ) - { - cson_free(kvp->key,"cson_kvp::key"); - cson_value_clean( &kvp->value ); - } -} -#endif - -int cson_buffer_fill_from( cson_buffer * dest, cson_data_source_f src, void * state ) -{ - int rc; - enum { BufSize = 1024 * 4 }; - char rbuf[BufSize]; - size_t total = 0; - unsigned int rlen = 0; - if( ! dest || ! src ) return cson_rc.ArgError; - dest->used = 0; - while(1) - { - rlen = BufSize; - rc = src( state, rbuf, &rlen ); - if( rc ) break; - total += rlen; - if( dest->capacity < (total+1) ) - { - rc = cson_buffer_reserve( dest, total + 1); - if( 0 != rc ) break; - } - memcpy( dest->mem + dest->used, rbuf, rlen ); - dest->used += rlen; - if( rlen < BufSize ) break; - } - if( !rc && dest->used ) - { - assert( dest->used < dest->capacity ); - dest->mem[dest->used] = 0; - } - return rc; -} - -int cson_data_source_FILE( void * state, void * dest, unsigned int * n ) -{ - FILE * f = (FILE*) state; - if( ! state || ! n || !dest ) return cson_rc.ArgError; - else if( !*n ) return cson_rc.RangeError; - *n = (unsigned int)fread( dest, 1, *n, f ); - if( !*n ) - { - return feof(f) ? 0 : cson_rc.IOError; - } - return 0; -} - -int cson_parse_FILE( cson_value ** tgt, FILE * src, - cson_parse_opt const * opt, cson_parse_info * err ) -{ - return cson_parse( tgt, cson_data_source_FILE, src, opt, err ); -} - - -int cson_value_fetch_bool( cson_value const * val, char * v ) -{ - /** - FIXME: move the to-bool operation into cson_value_api, like we - do in the C++ API. - */ - if( ! val || !val->api ) return cson_rc.ArgError; - else - { - int rc = 0; - char b = 0; - switch( val->api->typeID ) - { - case CSON_TYPE_ARRAY: - case CSON_TYPE_OBJECT: - b = 1; - break; - case CSON_TYPE_STRING: { - char const * str = cson_string_cstr(cson_value_get_string(val)); - b = (str && *str) ? 1 : 0; - break; - } - case CSON_TYPE_UNDEF: - case CSON_TYPE_NULL: - break; - case CSON_TYPE_BOOL: - b = (NULL==val->value) ? 0 : 1; - break; - case CSON_TYPE_INTEGER: { - cson_int_t i = 0; - cson_value_fetch_integer( val, &i ); - b = i ? 1 : 0; - break; - } - case CSON_TYPE_DOUBLE: { - cson_double_t d = 0.0; - cson_value_fetch_double( val, &d ); - b = (0.0==d) ? 0 : 1; - break; - } - default: - rc = cson_rc.TypeError; - break; - } - if( v ) *v = b; - return rc; - } -} - -char cson_value_get_bool( cson_value const * val ) -{ - char i = 0; - cson_value_fetch_bool( val, &i ); - return i; -} - -int cson_value_fetch_integer( cson_value const * val, cson_int_t * v ) -{ - if( ! val || !val->api ) return cson_rc.ArgError; - else - { - cson_int_t i = 0; - int rc = 0; - switch(val->api->typeID) - { - case CSON_TYPE_UNDEF: - case CSON_TYPE_NULL: - i = 0; - break; - case CSON_TYPE_BOOL: { - char b = 0; - cson_value_fetch_bool( val, &b ); - i = b; - break; - } - case CSON_TYPE_INTEGER: { -#if CSON_VOID_PTR_IS_BIG - i = (cson_int_t)val->value; -#else - cson_int_t const * x = (cson_int_t const *)val->value; - i = x ? *x : 0; -#endif - break; - } - case CSON_TYPE_DOUBLE: { - cson_double_t d = 0.0; - cson_value_fetch_double( val, &d ); - i = (cson_int_t)d; - break; - } - case CSON_TYPE_STRING: - case CSON_TYPE_ARRAY: - case CSON_TYPE_OBJECT: - default: - break; - } - if(v) *v = i; - return rc; - } -} - -cson_int_t cson_value_get_integer( cson_value const * val ) -{ - cson_int_t i = 0; - cson_value_fetch_integer( val, &i ); - return i; -} - -int cson_value_fetch_double( cson_value const * val, cson_double_t * v ) -{ - if( ! val || !val->api ) return cson_rc.ArgError; - else - { - cson_double_t d = 0.0; - int rc = 0; - switch(val->api->typeID) - { - case CSON_TYPE_UNDEF: - case CSON_TYPE_NULL: - d = 0; - break; - case CSON_TYPE_BOOL: { - char b = 0; - cson_value_fetch_bool( val, &b ); - d = b ? 1.0 : 0.0; - break; - } - case CSON_TYPE_INTEGER: { - cson_int_t i = 0; - cson_value_fetch_integer( val, &i ); - d = i; - break; - } - case CSON_TYPE_DOUBLE: { - cson_double_t const* dv = (cson_double_t const *)val->value; - d = dv ? *dv : 0.0; - break; - } - default: - rc = cson_rc.TypeError; - break; - } - if(v) *v = d; - return rc; - } -} - -cson_double_t cson_value_get_double( cson_value const * val ) -{ - cson_double_t i = 0.0; - cson_value_fetch_double( val, &i ); - return i; -} - -int cson_value_fetch_string( cson_value const * val, cson_string const ** dest ) -{ - if( ! val || ! dest ) return cson_rc.ArgError; - else if( ! cson_value_is_string(val) ) return cson_rc.TypeError; - else - { - if( dest ) *dest = (cson_string const *)val->value; - return 0; - } -} - -cson_string const * cson_value_get_string( cson_value const * val ) -{ - cson_string const * rc = NULL; - cson_value_fetch_string( val, &rc ); - return rc; -} - -char const * cson_value_get_cstr( cson_value const * val ) -{ - return cson_string_cstr( cson_value_get_string(val) ); -} - -int cson_value_fetch_object( cson_value const * val, cson_object ** obj ) -{ - if( ! val ) return cson_rc.ArgError; - else if( ! cson_value_is_object(val) ) return cson_rc.TypeError; - else - { - if(obj) *obj = (cson_object*)val->value; - return 0; - } -} -cson_object * cson_value_get_object( cson_value const * v ) -{ - cson_object * obj = NULL; - cson_value_fetch_object( v, &obj ); - return obj; -} - -int cson_value_fetch_array( cson_value const * val, cson_array ** ar) -{ - if( ! val ) return cson_rc.ArgError; - else if( ! cson_value_is_array(val) ) return cson_rc.TypeError; - else - { - if(ar) *ar = (cson_array*)val->value; - return 0; - } -} - -cson_array * cson_value_get_array( cson_value const * v ) -{ - cson_array * ar = NULL; - cson_value_fetch_array( v, &ar ); - return ar; -} - -cson_kvp * cson_kvp_alloc() -{ - cson_kvp * kvp = (cson_kvp*)cson_malloc(sizeof(cson_kvp),"cson_kvp"); - if( kvp ) - { - *kvp = cson_kvp_empty; - } - return kvp; -} - - - -int cson_array_append( cson_array * ar, cson_value * v ) -{ - if( !ar || !v ) return cson_rc.ArgError; - else if( (ar->list.count+1) < ar->list.count ) return cson_rc.RangeError; - else - { - if( !ar->list.alloced || (ar->list.count == ar->list.alloced-1)) - { - unsigned int const n = ar->list.count ? (ar->list.count*2) : 7; - if( n > cson_value_list_reserve( &ar->list, n ) ) - { - return cson_rc.AllocError; - } - } - return cson_array_set( ar, ar->list.count, v ); - } -} - -#if 0 -/** - Removes and returns the last value from the given array, - shrinking its size by 1. Returns NULL if ar is NULL, - ar->list.count is 0, or the element at that index is NULL. - - - If removeRef is true then cson_value_free() is called to remove - ar's reference count for the value. In that case NULL is returned, - even if the object still has live references. If removeRef is false - then the caller takes over ownership of that reference count point. - - If removeRef is false then the caller takes over ownership - of the return value, otherwise ownership is effectively - determined by any remaining references for the returned - value. -*/ -static cson_value * cson_array_pop_back( cson_array * ar, - char removeRef ) -{ - if( !ar ) return NULL; - else if( ! ar->list.count ) return NULL; - else - { - unsigned int const ndx = --ar->list.count; - cson_value * v = ar->list.list[ndx]; - ar->list.list[ndx] = NULL; - if( removeRef ) - { - cson_value_free( v ); - v = NULL; - } - return v; - } -} -#endif - -cson_value * cson_value_new_bool( char v ) -{ - return v ? &CSON_SPECIAL_VALUES[CSON_VAL_TRUE] : &CSON_SPECIAL_VALUES[CSON_VAL_FALSE]; -} - -cson_value * cson_value_true() -{ - return &CSON_SPECIAL_VALUES[CSON_VAL_TRUE]; -} -cson_value * cson_value_false() -{ - return &CSON_SPECIAL_VALUES[CSON_VAL_FALSE]; -} - -cson_value * cson_value_null() -{ - return &CSON_SPECIAL_VALUES[CSON_VAL_NULL]; -} - -cson_value * cson_value_new_integer( cson_int_t v ) -{ - if( 0 == v ) return &CSON_SPECIAL_VALUES[CSON_VAL_INT_0]; - else - { - cson_value * c = cson_value_new(); - if( c ) - { - if( 0 != cson_value_set_integer( c, v ) ) - { - cson_value_free(c); - c = NULL; - } - } - return c; - } -} - -cson_value * cson_value_new_double( cson_double_t v ) -{ - if( 0.0 == v ) return &CSON_SPECIAL_VALUES[CSON_VAL_DBL_0]; - else - { - cson_value * c = cson_value_new(); - if( c ) - { - if( 0 != cson_value_set_double( c, v ) ) - { - cson_value_free(c); - c = NULL; - } - } - return c; - } -} -cson_value * cson_value_new_string( char const * str, unsigned int len ) -{ - if( !str || !len ) return &CSON_SPECIAL_VALUES[CSON_VAL_STR_EMPTY]; - else - { - cson_value * c = cson_value_new(); - if( c ) - { - if( 0 != cson_value_set_string( c, str, len ) ) - { - cson_value_free(c); - c = NULL; - } - } - return c; - } -} - -int cson_array_value_fetch( cson_array const * ar, unsigned int pos, cson_value ** v ) -{ - if( !ar) return cson_rc.ArgError; - if( pos >= ar->list.count ) return cson_rc.RangeError; - else - { - if(v) *v = ar->list.list[pos]; - return 0; - } -} - -cson_value * cson_array_get( cson_array const * ar, unsigned int pos ) -{ - cson_value *v = NULL; - cson_array_value_fetch(ar, pos, &v); - return v; -} - -int cson_array_length_fetch( cson_array const * ar, unsigned int * v ) -{ - if( ! ar || !v ) return cson_rc.ArgError; - else - { - if(v) *v = ar->list.count; - return 0; - } -} - -unsigned int cson_array_length_get( cson_array const * ar ) -{ - unsigned int i = 0; - cson_array_length_fetch(ar, &i); - return i; -} - -int cson_array_reserve( cson_array * ar, unsigned int size ) -{ - if( ! ar ) return cson_rc.ArgError; - else if( size <= ar->list.alloced ) - { - /* We don't want to introduce a can of worms by trying to - handle the cleanup from here. - */ - return 0; - } - else - { - return (ar->list.alloced > cson_value_list_reserve( &ar->list, size )) - ? cson_rc.AllocError - : 0 - ; - } -} - -int cson_array_set( cson_array * ar, unsigned int ndx, cson_value * v ) -{ - if( !ar || !v ) return cson_rc.ArgError; - else if( (ndx+1) < ndx) /* overflow */return cson_rc.RangeError; - else - { - unsigned const int len = cson_value_list_reserve( &ar->list, ndx+1 ); - if( len <= ndx ) return cson_rc.AllocError; - else - { - cson_value * old = ar->list.list[ndx]; - if( old ) - { - if(old == v) return 0; - else cson_value_free(old); - } - cson_refcount_incr( v ); - ar->list.list[ndx] = v; - if( ndx >= ar->list.count ) - { - ar->list.count = ndx+1; - } - return 0; - } - } -} - -/** @internal - - Searchs for the given key in the given object. - - Returns the found item on success, NULL on error. If ndx is not - NULL, it is set to the index (in obj->kvp.list) of the found - item. *ndx is not modified if no entry is found. -*/ -static cson_kvp * cson_object_search_impl( cson_object const * obj, char const * key, unsigned int * ndx ) -{ - if( obj && key && *key && obj->kvp.count) - { -#if CSON_OBJECT_PROPS_SORT - cson_kvp ** s = (cson_kvp**) - bsearch( key, obj->kvp.list, - obj->kvp.count, sizeof(cson_kvp*), - cson_kvp_cmp_vs_cstr ); - if( ndx && s ) - { /* index of found record is required by - cson_object_unset(). Calculate the offset based on s...*/ -#if 0 - *ndx = (((unsigned char const *)s - ((unsigned char const *)obj->kvp.list)) - / sizeof(cson_kvp*)); -#else - *ndx = s - obj->kvp.list; -#endif - } - return s ? *s : NULL; -#else - cson_kvp_list const * li = &obj->kvp; - unsigned int i = 0; - cson_kvp * kvp; - const unsigned int klen = strlen(key); - for( ; i < li->count; ++i ) - { - kvp = li->list[i]; - assert( kvp && kvp->key ); - if( kvp->key->length != klen ) continue; - else if(0==strcmp(key,cson_string_cstr(kvp->key))) - { - if(ndx) *ndx = i; - return kvp; - } - } -#endif - } - return NULL; -} - -cson_value * cson_object_get( cson_object const * obj, char const * key ) -{ - cson_kvp * kvp = cson_object_search_impl( obj, key, NULL ); - return kvp ? kvp->value : NULL; -} - -#if CSON_OBJECT_PROPS_SORT -static void cson_object_sort_props( cson_object * obj ) -{ - assert( NULL != obj ); - if( obj->kvp.count ) - { - qsort( obj->kvp.list, obj->kvp.count, sizeof(cson_kvp*), - cson_kvp_cmp ); - } - -} -#endif - -int cson_object_unset( cson_object * obj, char const * key ) -{ - if( ! obj || !key || !*key ) return cson_rc.ArgError; - else - { - unsigned int ndx = 0; - cson_kvp * kvp = cson_object_search_impl( obj, key, &ndx ); - if( ! kvp ) - { - return cson_rc.NotFoundError; - } - assert( obj->kvp.count > 0 ); - assert( obj->kvp.list[ndx] == kvp ); - cson_kvp_free( kvp ); - obj->kvp.list[ndx] = NULL; - { /* if my brain were bigger i'd use memmove(). */ - unsigned int i = ndx; - for( ; i < obj->kvp.count; ++i ) - { - obj->kvp.list[i] = - (i < (obj->kvp.alloced-1)) - ? obj->kvp.list[i+1] - : NULL; - } - } - obj->kvp.list[--obj->kvp.count] = NULL; -#if CSON_OBJECT_PROPS_SORT - cson_object_sort_props( obj ); -#endif - return 0; - } -} - -int cson_object_set( cson_object * obj, char const * key, cson_value * v ) -{ - if( ! obj || !key || !*key ) return cson_rc.ArgError; - else if( NULL == v ) - { - return cson_object_unset( obj, key ); - } - else - { - cson_kvp * kvp = cson_object_search_impl( obj, key, NULL ); - if( kvp ) - { /* "I told 'em we've already got one!" */ - if( v == kvp->value ) return 0 /* actually a usage error */; - else - { - cson_value_free( kvp->value ); - cson_refcount_incr( v ); - kvp->value = v; - return 0; - } - } - if( !obj->kvp.alloced || (obj->kvp.count == obj->kvp.alloced-1)) - { - unsigned int const n = obj->kvp.count ? (obj->kvp.count*2) : 7; - if( n > cson_kvp_list_reserve( &obj->kvp, n ) ) - { - return cson_rc.AllocError; - } - } - { /* insert new item... */ - int rc = 0; - cson_string * keycp = cson_string_strdup(key, strlen(key)); - if( ! keycp ) - { - return cson_rc.AllocError; - } - kvp = cson_kvp_alloc(); - if( ! kvp ) - { - cson_free(keycp,"cson_parser::key"); - return cson_rc.AllocError; - } - kvp->key = keycp /* transfer ownership */; - rc = cson_kvp_list_append( &obj->kvp, kvp ); - if( 0 != rc ) - { - cson_kvp_free(kvp); - } - else - { - cson_refcount_incr( v ); - kvp->value = v /* transfer ownership */; -#if CSON_OBJECT_PROPS_SORT - cson_object_sort_props( obj ); -#endif - } - return 0; - } - } -} - -cson_value * cson_object_take( cson_object * obj, char const * key ) -{ - if( ! obj || !key || !*key ) return NULL; - else - { - /* FIXME: this is 90% identical to cson_object_unset(), - only with different refcount handling. - Consolidate them. - */ - unsigned int ndx = 0; - cson_kvp * kvp = cson_object_search_impl( obj, key, &ndx ); - cson_value * rc = NULL; - if( ! kvp ) - { - return NULL; - } - assert( obj->kvp.count > 0 ); - assert( obj->kvp.list[ndx] == kvp ); - rc = kvp->value; - assert( rc ); - kvp->value = NULL; - cson_kvp_free( kvp ); - assert( rc->refcount > 0 ); - --rc->refcount; - obj->kvp.list[ndx] = NULL; - { /* if my brain were bigger i'd use memmove(). */ - unsigned int i = ndx; - for( ; i < obj->kvp.count; ++i ) - { - obj->kvp.list[i] = - (i < (obj->kvp.alloced-1)) - ? obj->kvp.list[i+1] - : NULL; - } - } - obj->kvp.list[--obj->kvp.count] = NULL; -#if CSON_OBJECT_PROPS_SORT - cson_object_sort_props( obj ); -#endif - return rc; - } -} -/** @internal - - If p->node is-a Object then value is inserted into the object - using p->key. In any other case cson_rc.InternalError is returned. - - Returns cson_rc.AllocError if an allocation fails. - - Returns 0 on success. On error, parsing must be ceased immediately. - - Ownership of val is ALWAYS TRANSFERED to this function. If this - function fails, val will be cleaned up and destroyed. (This - simplifies error handling in the core parser.) -*/ -static int cson_parser_set_key( cson_parser * p, cson_value * val ) -{ - assert( p && val ); - - if( p->ckey && cson_value_is_object(p->node) ) - { - int rc; - cson_object * obj = cson_value_get_object(p->node); - cson_kvp * kvp = NULL; - assert( obj && (p->node->value == obj) ); - /** - FIXME? Use cson_object_set() instead of our custom - finagling with the object? We do it this way to avoid an - extra alloc/strcpy of the key data. - */ - if( !obj->kvp.alloced || (obj->kvp.count == obj->kvp.alloced-1)) - { - if( obj->kvp.alloced > cson_kvp_list_reserve( &obj->kvp, obj->kvp.count ? (obj->kvp.count*2) : 5 ) ) - { - cson_value_free(val); - return cson_rc.AllocError; - } - } - kvp = cson_kvp_alloc(); - if( ! kvp ) - { - cson_value_free(val); - return cson_rc.AllocError; - } - kvp->key = p->ckey/*transfer ownership*/; - p->ckey = NULL; - kvp->value = val; - cson_refcount_incr( val ); - rc = cson_kvp_list_append( &obj->kvp, kvp ); - if( 0 != rc ) - { - cson_kvp_free( kvp ); - } - else - { - ++p->totalValueCount; - } - return rc; - } - else - { - if(val) cson_value_free(val); - return p->errNo = cson_rc.InternalError; - } - -} - -/** @internal - - Pushes val into the current object/array parent node, depending on the - internal state of the parser. - - Ownership of val is always transfered to this function, regardless of - success or failure. - - Returns 0 on success. On error, parsing must be ceased immediately. -*/ -static int cson_parser_push_value( cson_parser * p, cson_value * val ) -{ - if( p->ckey ) - { /* we're in Object mode */ - assert( cson_value_is_object( p->node ) ); - return cson_parser_set_key( p, val ); - } - else if( cson_value_is_array( p->node ) ) - { /* we're in Array mode */ - cson_array * ar = cson_value_get_array( p->node ); - int rc; - assert( ar && (ar == p->node->value) ); - rc = cson_array_append( ar, val ); - if( 0 != rc ) - { - cson_value_free(val); - } - else - { - ++p->totalValueCount; - } - return rc; - } - else - { /* WTF? */ - assert( 0 && "Internal error in cson_parser code" ); - return p->errNo = cson_rc.InternalError; - } -} - -/** - Callback for JSON_parser API. Reminder: it returns 0 (meaning false) - on error! -*/ -static int cson_parse_callback( void * cx, int type, JSON_value const * value ) -{ - cson_parser * p = (cson_parser *)cx; - int rc = 0; -#define ALLOC_V(T,V) cson_value * v = cson_value_new_##T(V); if( ! v ) { rc = cson_rc.AllocError; break; } - switch(type) { - case JSON_T_ARRAY_BEGIN: - case JSON_T_OBJECT_BEGIN: { - cson_value * obja = (JSON_T_ARRAY_BEGIN == type) - ? cson_value_new_array() - : cson_value_new_object(); - if( ! obja ) - { - p->errNo = cson_rc.AllocError; - return 0; - } - if( 0 != rc ) break; - if( ! p->root ) - { - p->root = p->node = obja; - rc = cson_array_append( &p->stack, obja ); - if( 0 != rc ) - { /* work around a (potential) corner case in the cleanup code. */ - cson_value_free( p->root ); - p->root = NULL; - } - else - { - cson_refcount_incr( p->root ) - /* simplifies cleanup later on. */ - ; - ++p->totalValueCount; - } - } - else - { - rc = cson_array_append( &p->stack, obja ); - if( 0 == rc ) rc = cson_parser_push_value( p, obja ); - if( 0 == rc ) p->node = obja; - } - break; - } - case JSON_T_ARRAY_END: - case JSON_T_OBJECT_END: { - if( 0 == p->stack.list.count ) - { - rc = cson_rc.RangeError; - break; - } -#if CSON_OBJECT_PROPS_SORT - if( cson_value_is_object(p->node) ) - {/* kludge: the parser uses custom cson_object property - insertion as a malloc/strcpy-reduction optimization. - Because of that, we have to sort the property list - ourselves... - */ - cson_object * obj = cson_value_get_object(p->node); - assert( NULL != obj ); - cson_object_sort_props( obj ); - } -#endif - -#if 1 - /* Reminder: do not use cson_array_pop_back( &p->stack ) - because that will clean up the object, and we don't want - that. We just want to forget this reference - to it. The object is either the root or was pushed into - an object/array in the parse tree (and is owned by that - object/array). - */ - --p->stack.list.count; - assert( p->node == p->stack.list.list[p->stack.list.count] ); - cson_refcount_decr( p->node ) - /* p->node might be owned by an outer object but we - need to remove the list's reference. For the - root node we manually add a reference to - avoid a special case here. Thus when we close - the root node, its refcount is still 1. - */; - p->stack.list.list[p->stack.list.count] = NULL; - if( p->stack.list.count ) - { - p->node = p->stack.list.list[p->stack.list.count-1]; - } - else - { - p->node = p->root; - } -#else - /* - Causing a leak? - */ - cson_array_pop_back( &p->stack, 1 ); - if( p->stack.list.count ) - { - p->node = p->stack.list.list[p->stack.list.count-1]; - } - else - { - p->node = p->root; - } - assert( p->node && (1==p->node->refcount) ); -#endif - break; - } - case JSON_T_INTEGER: { - ALLOC_V(integer, value->vu.integer_value ); - rc = cson_parser_push_value( p, v ); - break; - } - case JSON_T_FLOAT: { - ALLOC_V(double, value->vu.float_value ); - rc = cson_parser_push_value( p, v ); - break; - } - case JSON_T_NULL: { - rc = cson_parser_push_value( p, cson_value_null() ); - break; - } - case JSON_T_TRUE: { - rc = cson_parser_push_value( p, cson_value_true() ); - break; - } - case JSON_T_FALSE: { - rc = cson_parser_push_value( p, cson_value_false() ); - break; - } - case JSON_T_KEY: { - assert(!p->ckey); - p->ckey = cson_string_strdup( value->vu.str.value, value->vu.str.length ); - if( ! p->ckey ) - { - rc = cson_rc.AllocError; - break; - } - ++p->totalKeyCount; - break; - } - case JSON_T_STRING: { - cson_value * v = cson_value_new_string( value->vu.str.value, value->vu.str.length ); - rc = ( NULL == v ) - ? cson_rc.AllocError - : cson_parser_push_value( p, v ); - break; - } - default: - assert(0); - rc = cson_rc.InternalError; - break; - } -#undef ALLOC_V - return ((p->errNo = rc)) ? 0 : 1; -} - - -/** - Converts a JSON_error code to one of the cson_rc values. -*/ -static int cson_json_err_to_rc( JSON_error jrc ) -{ - switch(jrc) - { - case JSON_E_NONE: return 0; - case JSON_E_INVALID_CHAR: return cson_rc.Parse_INVALID_CHAR; - case JSON_E_INVALID_KEYWORD: return cson_rc.Parse_INVALID_KEYWORD; - case JSON_E_INVALID_ESCAPE_SEQUENCE: return cson_rc.Parse_INVALID_ESCAPE_SEQUENCE; - case JSON_E_INVALID_UNICODE_SEQUENCE: return cson_rc.Parse_INVALID_UNICODE_SEQUENCE; - case JSON_E_INVALID_NUMBER: return cson_rc.Parse_INVALID_NUMBER; - case JSON_E_NESTING_DEPTH_REACHED: return cson_rc.Parse_NESTING_DEPTH_REACHED; - case JSON_E_UNBALANCED_COLLECTION: return cson_rc.Parse_UNBALANCED_COLLECTION; - case JSON_E_EXPECTED_KEY: return cson_rc.Parse_EXPECTED_KEY; - case JSON_E_EXPECTED_COLON: return cson_rc.Parse_EXPECTED_COLON; - case JSON_E_OUT_OF_MEMORY: return cson_rc.AllocError; - default: - return cson_rc.InternalError; - } -} - -/** @internal - - Cleans up all contents of p but does not free p. - - To properly take over ownership of the parser's root node on a - successful parse: - - - Copy p->root's pointer and set p->root to NULL. - - Eventually free up p->root with cson_value_free(). - - If you do not set p->root to NULL, p->root will be freed along with - any other items inserted into it (or under it) during the parsing - process. -*/ -static int cson_parser_clean( cson_parser * p ) -{ - if( ! p ) return cson_rc.ArgError; - else - { - if( p->p ) - { - delete_JSON_parser(p->p); - p->p = NULL; - } - cson_free( p->ckey, "cson_parser::key" ); - cson_array_clean( &p->stack, 1 ); - if( p->root ) - { - cson_value_free( p->root ); - } - *p = cson_parser_empty; - return 0; - } -} - - -int cson_parse( cson_value ** tgt, cson_data_source_f src, void * state, - cson_parse_opt const * opt_, cson_parse_info * info_ ) -{ - unsigned char ch[2] = {0,0}; - cson_parse_opt const opt = opt_ ? *opt_ : cson_parse_opt_empty; - int rc = 0; - unsigned int len = 1; - cson_parse_info info = info_ ? *info_ : cson_parse_info_empty; - cson_parser p = cson_parser_empty; - if( ! tgt || ! src ) return cson_rc.ArgError; - - { - JSON_config jopt = {0}; - init_JSON_config( &jopt ); - jopt.allow_comments = opt.allowComments; - jopt.depth = opt.maxDepth; - jopt.callback_ctx = &p; - jopt.handle_floats_manually = 0; - jopt.callback = cson_parse_callback; - p.p = new_JSON_parser(&jopt); - if( ! p.p ) - { - return cson_rc.AllocError; - } - } - - do - { /* FIXME: buffer the input in multi-kb chunks. */ - len = 1; - ch[0] = 0; - rc = src( state, ch, &len ); - if( 0 != rc ) break; - else if( !len /* EOF */ ) break; - ++info.length; - if('\n' == ch[0]) - { - ++info.line; - info.col = 0; - } - if( ! JSON_parser_char(p.p, ch[0]) ) - { - rc = cson_json_err_to_rc( JSON_parser_get_last_error(p.p) ); - if(0==rc) rc = p.errNo; - if(0==rc) rc = cson_rc.InternalError; - info.errorCode = rc; - break; - } - if( '\n' != ch[0]) ++info.col; - } while(1); - if( info_ ) - { - info.totalKeyCount = p.totalKeyCount; - info.totalValueCount = p.totalValueCount; - *info_ = info; - } - if( 0 != rc ) - { - cson_parser_clean(&p); - return rc; - } - if( ! JSON_parser_done(p.p) ) - { - rc = cson_json_err_to_rc( JSON_parser_get_last_error(p.p) ); - cson_parser_clean(&p); - if(0==rc) rc = p.errNo; - if(0==rc) rc = cson_rc.InternalError; - } - else - { - cson_value * root = p.root; - p.root = NULL; - cson_parser_clean(&p); - if( root ) - { - assert( (1 == root->refcount) && "Detected memory mismanagement in the parser." ); - root->refcount = 0 - /* HUGE KLUDGE! Avoids having one too many references - in some client code, leading to a leak. Here we're - accommodating a memory management workaround in the - parser code which manually adds a reference to the - root node to keep it from being cleaned up - prematurely. - */; - *tgt = root; - } - else - { /* then can happen on empty input. */ - rc = cson_rc.UnknownError; - } - } - return rc; -} - -/** - The UTF code was originally taken from sqlite3's public-domain - source code (http://sqlite.org), modified only slightly for use - here. This code generates some "possible data loss" warnings on - MSVC, but if this code is good enough for sqlite3 then it's damned - well good enough for me, so we disable that warning for Windows - builds. -*/ - -/* -** This lookup table is used to help decode the first byte of -** a multi-byte UTF8 character. -*/ -static const unsigned char cson_utfTrans1[] = { - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, - 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00 -}; - - -/* -** Translate a single UTF-8 character. Return the unicode value. -** -** During translation, assume that the byte that zTerm points -** is a 0x00. -** -** Write a pointer to the next unread byte back into *pzNext. -** -** Notes On Invalid UTF-8: -** -** * This routine never allows a 7-bit character (0x00 through 0x7f) to -** be encoded as a multi-byte character. Any multi-byte character that -** attempts to encode a value between 0x00 and 0x7f is rendered as 0xfffd. -** -** * This routine never allows a UTF16 surrogate value to be encoded. -** If a multi-byte character attempts to encode a value between -** 0xd800 and 0xe000 then it is rendered as 0xfffd. -** -** * Bytes in the range of 0x80 through 0xbf which occur as the first -** byte of a character are interpreted as single-byte characters -** and rendered as themselves even though they are technically -** invalid characters. -** -** * This routine accepts an infinite number of different UTF8 encodings -** for unicode values 0x80 and greater. It do not change over-length -** encodings to 0xfffd as some systems recommend. -*/ -#define READ_UTF8(zIn, zTerm, c) \ - c = *(zIn++); \ - if( c>=0xc0 ){ \ - c = cson_utfTrans1[c-0xc0]; \ - while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ){ \ - c = (c<<6) + (0x3f & *(zIn++)); \ - } \ - if( c<0x80 \ - || (c&0xFFFFF800)==0xD800 \ - || (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; } \ - } -static int cson_utf8Read( - const unsigned char *z, /* First byte of UTF-8 character */ - const unsigned char *zTerm, /* Pretend this byte is 0x00 */ - const unsigned char **pzNext /* Write first byte past UTF-8 char here */ -){ - int c; - READ_UTF8(z, zTerm, c); - *pzNext = z; - return c; -} -#undef READ_UTF8 - -#if defined(_WIN32) -# pragma warning( pop ) -#endif - -unsigned int cson_string_length_utf8( cson_string const * str ) -{ - if( ! str ) return 0; - else - { - char unsigned const * pos = (char unsigned const *)cson_string_cstr(str); - char unsigned const * end = pos + str->length; - unsigned int rc = 0; - for( ; (pos < end) && cson_utf8Read(pos, end, &pos); - ++rc ) - { - }; - return rc; - } -} - -/** - Escapes the first len bytes of the given string as JSON and sends - it to the given output function (which will be called often - once - for each logical character). The output is also surrounded by - double-quotes. - - A NULL str will be escaped as an empty string, though we should - arguably export it as "null" (without quotes). We do this because - in JavaScript (typeof null === "object"), and by outputing null - here we would effectively change the data type from string to - object. -*/ -static int cson_str_to_json( char const * str, unsigned int len, - char escapeFwdSlash, - cson_data_dest_f f, void * state ) -{ - if( NULL == f ) return cson_rc.ArgError; - else if( !str || !*str || (0 == len) ) - { /* special case for 0-length strings. */ - return f( state, "\"\"", 2 ); - } - else - { - unsigned char const * pos = (unsigned char const *)str; - unsigned char const * end = (unsigned char const *)(str ? (str + len) : NULL); - unsigned char const * next = NULL; - int ch; - unsigned char clen = 0; - char escChar[3] = {'\\',0,0}; - enum { UBLen = 8 }; - char ubuf[UBLen]; - int rc = 0; - rc = f(state, "\"", 1 ); - for( ; (pos < end) && (0 == rc); pos += clen ) - { - ch = cson_utf8Read(pos, end, &next); - if( 0 == ch ) break; - assert( next > pos ); - clen = next - pos; - assert( clen ); - if( 1 == clen ) - { /* ASCII */ - assert( *pos == ch ); - escChar[1] = 0; - switch(ch) - { - case '\t': escChar[1] = 't'; break; - case '\r': escChar[1] = 'r'; break; - case '\n': escChar[1] = 'n'; break; - case '\f': escChar[1] = 'f'; break; - case '\b': escChar[1] = 'b'; break; - case '/': - /* - Regarding escaping of forward-slashes. See the main exchange below... - - -------------- - From: Douglas Crockford <douglas@crockford.com> - To: Stephan Beal <sgbeal@googlemail.com> - Subject: Re: Is escaping of forward slashes required? - - It is allowed, not required. It is allowed so that JSON can be safely - embedded in HTML, which can freak out when seeing strings containing - "</". JSON tolerates "<\/" for this reason. - - On 4/8/2011 2:09 PM, Stephan Beal wrote: - > Hello, Jsonites, - > - > i'm a bit confused on a small grammatic detail of JSON: - > - > if i'm reading the grammar chart on http://www.json.org/ correctly, - > forward slashes (/) are supposed to be escaped in JSON. However, the - > JSON class provided with my browsers (Chrome and FF, both of which i - > assume are fairly standards/RFC-compliant) do not escape such characters. - > - > Is backslash-escaping forward slashes required? If so, what is the - > justification for it? (i ask because i find it unnecessary and hard to - > look at.) - -------------- - */ - if( escapeFwdSlash ) escChar[1] = '/'; - break; - case '\\': escChar[1] = '\\'; break; - case '"': escChar[1] = '"'; break; - default: break; - } - if( escChar[1]) - { - rc = f(state, escChar, 2); - } - else - { - rc = f(state, (char const *)pos, clen); - } - continue; - } - else - { /* UTF: transform it to \uXXXX */ - memset(ubuf,0,UBLen); - rc = sprintf(ubuf, "\\u%04x",ch); - if( rc != 6 ) - { - rc = cson_rc.RangeError; - break; - } - rc = f( state, ubuf, 6 ); - continue; - } - } - if( 0 == rc ) - { - rc = f(state, "\"", 1 ); - } - return rc; - } -} - -int cson_object_iter_init( cson_object const * obj, cson_object_iterator * iter ) -{ - if( ! obj || !iter ) return cson_rc.ArgError; - else - { - iter->obj = obj; - iter->pos = 0; - return 0; - } -} - -cson_kvp * cson_object_iter_next( cson_object_iterator * iter ) -{ - if( ! iter || !iter->obj ) return NULL; - else if( iter->pos >= iter->obj->kvp.count ) return NULL; - else - { - cson_kvp * rc = iter->obj->kvp.list[iter->pos++]; - while( (NULL==rc) && (iter->pos < iter->obj->kvp.count)) - { - rc = iter->obj->kvp.list[iter->pos++]; - } - return rc; - } -} - -static int cson_output_null( cson_data_dest_f f, void * state ) -{ - if( !f ) return cson_rc.ArgError; - else - { - return f(state, "null", 4); - } -} - -static int cson_output_bool( cson_value const * src, cson_data_dest_f f, void * state ) -{ - if( !f ) return cson_rc.ArgError; - else - { - char const v = cson_value_get_bool(src); - return f(state, v ? "true" : "false", v ? 4 : 5); - } -} - -static int cson_output_integer( cson_value const * src, cson_data_dest_f f, void * state ) -{ - if( !f ) return cson_rc.ArgError; - else if( !cson_value_is_integer(src) ) return cson_rc.TypeError; - else - { - enum { BufLen = 100 }; - char b[BufLen]; - int rc; - memset( b, 0, BufLen ); - rc = sprintf( b, "%"CSON_INT_T_PFMT, cson_value_get_integer(src) ) - /* Reminder: snprintf() is C99 */ - ; - return ( rc<=0 ) - ? cson_rc.RangeError - : f( state, b, (unsigned int)rc ) - ; - } -} - -static int cson_output_double( cson_value const * src, cson_data_dest_f f, void * state ) -{ - if( !f ) return cson_rc.ArgError; - else if( !cson_value_is_double(src) ) return cson_rc.TypeError; - else - { - enum { BufLen = 128 /* this must be relatively large or huge - doubles can cause us to overrun here, - resulting in stack-smashing errors. - */}; - char b[BufLen]; - int rc; - memset( b, 0, BufLen ); - rc = sprintf( b, "%"CSON_DOUBLE_T_PFMT, cson_value_get_double(src) ) - /* Reminder: snprintf() is C99 */ - ; - if( rc<=0 ) return cson_rc.RangeError; - else if(1) - { /* Strip trailing zeroes before passing it on... */ - unsigned int urc = (unsigned int)rc; - char * pos = b + urc - 1; - for( ; ('0' == *pos) && urc && (*(pos-1) != '.'); --pos, --urc ) - { - *pos = 0; - } - assert(urc && *pos); - return f( state, b, urc ); - } - else - { - unsigned int urc = (unsigned int)rc; - return f( state, b, urc ); - } - return 0; - } -} - -static int cson_output_string( cson_value const * src, char escapeFwdSlash, cson_data_dest_f f, void * state ) -{ - if( !f ) return cson_rc.ArgError; - else if( ! cson_value_is_string(src) ) return cson_rc.TypeError; - else - { - cson_string const * str = cson_value_get_string(src); - assert( NULL != str ); - return cson_str_to_json(cson_string_cstr(str), str->length, escapeFwdSlash, f, state); - } -} - - -/** - Outputs indention spacing to f(). - - blanks: (0)=no indentation, (1)=1 TAB per/level, (>1)=n spaces/level - - depth is the current depth of the output tree, and determines how much - indentation to generate. - - If blanks is 0 this is a no-op. Returns non-0 on error, and the - error code will always come from f(). -*/ -static int cson_output_indent( cson_data_dest_f f, void * state, - unsigned char blanks, unsigned int depth ) -{ - if( 0 == blanks ) return 0; - else - { -#if 0 - /* FIXME: stuff the indention into the buffer and make a single - call to f(). - */ - enum { BufLen = 200 }; - char buf[BufLen]; -#endif - unsigned int i; - unsigned int x; - char const ch = (1==blanks) ? '\t' : ' '; - int rc = f(state, "\n", 1 ); - for( i = 0; (i < depth) && (0 == rc); ++i ) - { - for( x = 0; (x < blanks) && (0 == rc); ++x ) - { - rc = f(state, &ch, 1); - } - } - return rc; - } -} - -static int cson_output_array( cson_value const * src, cson_data_dest_f f, void * state, - cson_output_opt const * fmt, unsigned int level ); -static int cson_output_object( cson_value const * src, cson_data_dest_f f, void * state, - cson_output_opt const * fmt, unsigned int level ); -/** - Main cson_output() implementation. Dispatches to a different impl depending - on src->api->typeID. - - Returns 0 on success. -*/ -static int cson_output_impl( cson_value const * src, cson_data_dest_f f, void * state, - cson_output_opt const * fmt, unsigned int level ) -{ - if( ! src || !f || !src->api ) return cson_rc.ArgError; - else - { - int rc = 0; - assert(fmt); - switch( src->api->typeID ) - { - case CSON_TYPE_UNDEF: - case CSON_TYPE_NULL: - rc = cson_output_null(f, state); - break; - case CSON_TYPE_BOOL: - rc = cson_output_bool(src, f, state); - break; - case CSON_TYPE_INTEGER: - rc = cson_output_integer(src, f, state); - break; - case CSON_TYPE_DOUBLE: - rc = cson_output_double(src, f, state); - break; - case CSON_TYPE_STRING: - rc = cson_output_string(src, fmt->escapeForwardSlashes, f, state); - break; - case CSON_TYPE_ARRAY: - rc = cson_output_array( src, f, state, fmt, level ); - break; - case CSON_TYPE_OBJECT: - rc = cson_output_object( src, f, state, fmt, level ); - break; - default: - rc = cson_rc.TypeError; - break; - } - return rc; - } -} - - -static int cson_output_array( cson_value const * src, cson_data_dest_f f, void * state, - cson_output_opt const * fmt, unsigned int level ) -{ - if( !src || !f || !fmt ) return cson_rc.ArgError; - else if( ! cson_value_is_array(src) ) return cson_rc.TypeError; - else if( level > fmt->maxDepth ) return cson_rc.RangeError; - else - { - int rc; - unsigned int i; - cson_value const * v; - char doIndent = fmt->indentation ? 1 : 0; - cson_array const * ar = cson_value_get_array(src); - assert( NULL != ar ); - if( 0 == ar->list.count ) - { - return f(state, "[]", 2 ); - } - else if( (1 == ar->list.count) && !fmt->indentSingleMemberValues ) doIndent = 0; - rc = f(state, "[", 1); - ++level; - if( doIndent ) - { - rc = cson_output_indent( f, state, fmt->indentation, level ); - } - for( i = 0; (i < ar->list.count) && (0 == rc); ++i ) - { - v = ar->list.list[i]; - if( v ) - { - rc = cson_output_impl( v, f, state, fmt, level ); - } - else - { - rc = cson_output_null( f, state ); - } - if( 0 == rc ) - { - if(i < (ar->list.count-1)) - { - rc = f(state, ",", 1); - if( 0 == rc ) - { - rc = doIndent - ? cson_output_indent( f, state, fmt->indentation, level ) - : f( state, " ", 1 ); - } - } - } - } - --level; - if( doIndent && (0 == rc) ) - { - rc = cson_output_indent( f, state, fmt->indentation, level ); - } - return (0 == rc) - ? f(state, "]", 1) - : rc; - } -} - -static int cson_output_object( cson_value const * src, cson_data_dest_f f, void * state, - cson_output_opt const * fmt, unsigned int level ) -{ - if( !src || !f || !fmt ) return cson_rc.ArgError; - else if( ! cson_value_is_object(src) ) return cson_rc.TypeError; - else if( level > fmt->maxDepth ) return cson_rc.RangeError; - else - { - int rc; - unsigned int i; - cson_kvp const * kvp; - char doIndent = fmt->indentation ? 1 : 0; - cson_object const * obj = cson_value_get_object(src); - assert( (NULL != obj) && (NULL != fmt)); - if( 0 == obj->kvp.count ) - { - return f(state, "{}", 2 ); - } - else if( (1 == obj->kvp.count) && !fmt->indentSingleMemberValues ) doIndent = 0; - rc = f(state, "{", 1); - ++level; - if( doIndent ) - { - rc = cson_output_indent( f, state, fmt->indentation, level ); - } - for( i = 0; (i < obj->kvp.count) && (0 == rc); ++i ) - { - kvp = obj->kvp.list[i]; - if( kvp && kvp->key ) - { - rc = cson_str_to_json(cson_string_cstr(kvp->key), kvp->key->length, - fmt->escapeForwardSlashes, f, state); - if( 0 == rc ) - { - rc = fmt->addSpaceAfterColon - ? f(state, ": ", 2 ) - : f(state, ":", 1 ) - ; - } - if( 0 == rc) - { - rc = ( kvp->value ) - ? cson_output_impl( kvp->value, f, state, fmt, level ) - : cson_output_null( f, state ); - } - } - else - { - assert( 0 && "Possible internal error." ); - continue /* internal error? */; - } - if( 0 == rc ) - { - if(i < (obj->kvp.count-1)) - { - rc = f(state, ",", 1); - if( 0 == rc ) - { - rc = doIndent - ? cson_output_indent( f, state, fmt->indentation, level ) - : f( state, " ", 1 ); - } - } - } - } - --level; - if( doIndent && (0 == rc) ) - { - rc = cson_output_indent( f, state, fmt->indentation, level ); - } - return (0 == rc) - ? f(state, "}", 1) - : rc; - } -} - -int cson_output( cson_value const * src, cson_data_dest_f f, - void * state, cson_output_opt const * fmt ) -{ - int rc; - if(! fmt ) fmt = &cson_output_opt_empty; - rc = cson_output_impl(src, f, state, fmt, 0 ); - if( (0 == rc) && fmt->addNewline ) - { - rc = f(state, "\n", 1); - } - return rc; -} - -int cson_data_dest_FILE( void * state, void const * src, unsigned int n ) -{ - if( ! state ) return cson_rc.ArgError; - else if( !src || !n ) return 0; - else - { - return ( 1 == fwrite( src, n, 1, (FILE*) state ) ) - ? 0 - : cson_rc.IOError; - } -} - -int cson_output_FILE( cson_value const * src, FILE * dest, cson_output_opt const * fmt ) -{ - int rc = 0; - if( fmt ) - { - rc = cson_output( src, cson_data_dest_FILE, dest, fmt ); - } - else - { - /* We normally want a newline on FILE output. */ - cson_output_opt opt = cson_output_opt_empty; - opt.addNewline = 1; - rc = cson_output( src, cson_data_dest_FILE, dest, &opt ); - } - if( 0 == rc ) - { - fflush( dest ); - } - return rc; -} - -int cson_output_filename( cson_value const * src, char const * dest, cson_output_opt const * fmt ) -{ - if( !src || !dest ) return cson_rc.ArgError; - else - { - FILE * f = fopen(dest,"wb"); - if( !f ) return cson_rc.IOError; - else - { - int const rc = cson_output_FILE( src, f, fmt ); - fclose(f); - return rc; - } - } -} - -int cson_parse_filename( cson_value ** tgt, char const * src, - cson_parse_opt const * opt, cson_parse_info * err ) -{ - if( !src || !tgt ) return cson_rc.ArgError; - else - { - FILE * f = fopen(src, "r"); - if( !f ) return cson_rc.IOError; - else - { - int const rc = cson_parse_FILE( tgt, f, opt, err ); - fclose(f); - return rc; - } - } -} - -/** Internal type to hold state for a JSON input string. - */ -typedef struct cson_data_source_StringSource_ -{ - /** Start of input string. */ - char const * str; - /** Current iteration position. Must initially be == str. */ - char const * pos; - /** Logical EOF, one-past-the-end of str. */ - char const * end; -} cson_data_source_StringSource_t; - -/** - A cson_data_source_f() implementation which requires the state argument - to be a properly populated (cson_data_source_StringSource_t*). -*/ -static int cson_data_source_StringSource( void * state, void * dest, unsigned int * n ) -{ - if( !state || !n || !dest ) return cson_rc.ArgError; - else if( !*n ) return 0 /* ignore this */; - else - { - unsigned int i; - cson_data_source_StringSource_t * ss = (cson_data_source_StringSource_t*) state; - unsigned char * tgt = (unsigned char *)dest; - for( i = 0; (i < *n) && (ss->pos < ss->end); ++i, ++ss->pos, ++tgt ) - { - *tgt = *ss->pos; - } - *n = i; - return 0; - } -} - -int cson_parse_string( cson_value ** tgt, char const * src, unsigned int len, - cson_parse_opt const * opt, cson_parse_info * err ) -{ - if( ! tgt || !src ) return cson_rc.ArgError; - else if( !*src || (len<2/*2==len of {} and []*/) ) return cson_rc.RangeError; - else - { - cson_data_source_StringSource_t ss; - ss.str = ss.pos = src; - ss.end = src + len; - return cson_parse( tgt, cson_data_source_StringSource, &ss, opt, err ); - } - -} - -int cson_parse_buffer( cson_value ** tgt, - cson_buffer const * buf, - cson_parse_opt const * opt, - cson_parse_info * err ) -{ - return ( !tgt || !buf || !buf->mem || !buf->used ) - ? cson_rc.ArgError - : cson_parse_string( tgt, (char const *)buf->mem, - buf->used, opt, err ); -} - -int cson_buffer_reserve( cson_buffer * buf, cson_size_t n ) -{ - if( ! buf ) return cson_rc.ArgError; - else if( 0 == n ) - { - cson_free(buf->mem, "cson_buffer::mem"); - *buf = cson_buffer_empty; - return 0; - } - else if( buf->capacity >= n ) - { - return 0; - } - else - { - unsigned char * x = (unsigned char *)realloc( buf->mem, n ); - if( ! x ) return cson_rc.AllocError; - memset( x + buf->used, 0, n - buf->used ); - buf->mem = x; - buf->capacity = n; - ++buf->timesExpanded; - return 0; - } -} - -cson_size_t cson_buffer_fill( cson_buffer * buf, char c ) -{ - if( !buf || !buf->capacity || !buf->mem ) return 0; - else - { - memset( buf->mem, c, buf->capacity ); - return buf->capacity; - } -} - -/** - cson_data_dest_f() implementation, used by cson_output_buffer(). - - arg MUST be a (cson_buffer*). This function appends n bytes at - position arg->used, expanding the buffer as necessary. -*/ -static int cson_data_dest_cson_buffer( void * arg, void const * data_, unsigned int n ) -{ - if( ! arg || (n<0) ) return cson_rc.ArgError; - else if( ! n ) return 0; - else - { - cson_buffer * sb = (cson_buffer*)arg; - char const * data = (char const *)data_; - cson_size_t npos = sb->used + n; - unsigned int i; - if( npos >= sb->capacity ) - { - const cson_size_t oldCap = sb->capacity; - const cson_size_t asz = npos * 2; - if( asz < npos ) return cson_rc.ArgError; /* overflow */ - else if( 0 != cson_buffer_reserve( sb, asz ) ) return cson_rc.AllocError; - assert( (sb->capacity > oldCap) && "Internal error in memory buffer management!" ); - /* make sure it gets NULL terminated. */ - memset( sb->mem + oldCap, 0, (sb->capacity - oldCap) ); - } - for( i = 0; i < n; ++i, ++sb->used ) - { - sb->mem[sb->used] = data[i]; - } - return 0; - } -} - - -int cson_output_buffer( cson_value const * v, cson_buffer * buf, - cson_output_opt const * opt ) -{ - int rc = cson_output( v, cson_data_dest_cson_buffer, buf, opt ); - if( 0 == rc ) - { /* Ensure that the buffer is null-terminated. */ - rc = cson_buffer_reserve( buf, buf->used + 1 ); - if( 0 == rc ) - { - buf->mem[buf->used] = 0; - } - } - return rc; -} - -/** @internal - -Tokenizes an input string on a given separator. Inputs are: - -- (inp) = is a pointer to the pointer to the start of the input. - -- (separator) = the separator character - -- (end) = a pointer to NULL. i.e. (*end == NULL) - -This function scans *inp for the given separator char or a NULL char. -Successive separators at the start of *inp are skipped. The effect is -that, when this function is called in a loop, all neighboring -separators are ignored. e.g. the string "aa.bb...cc" will tokenize to -the list (aa,bb,cc) if the separator is '.' and to (aa.,...cc) if the -separator is 'b'. - -Returns 0 (false) if it finds no token, else non-0 (true). - -Output: - -- (*inp) will be set to the first character of the next token. - -- (*end) will point to the one-past-the-end point of the token. - -If (*inp == *end) then the end of the string has been reached -without finding a token. - -Post-conditions: - -- (*end == *inp) if no token is found. - -- (*end > *inp) if a token is found. - -It is intolerant of NULL values for (inp, end), and will assert() in -debug builds if passed NULL as either parameter. -*/ -static char cson_next_token( char const ** inp, char separator, char const ** end ) -{ - char const * pos = NULL; - assert( inp && end && *inp ); - if( *inp == *end ) return 0; - pos = *inp; - if( !*pos ) - { - *end = pos; - return 0; - } - for( ; *pos && (*pos == separator); ++pos) { /* skip preceeding splitters */ } - *inp = pos; - for( ; *pos && (*pos != separator); ++pos) { /* find next splitter */ } - *end = pos; - return (pos > *inp) ? 1 : 0; -} - -int cson_object_fetch_sub( cson_object const * obj, cson_value ** tgt, char const * path, char sep ) -{ - if( ! obj || !path ) return cson_rc.ArgError; - else if( !*path || !sep ) return cson_rc.RangeError; - else - { - char const * beg = path; - char const * end = NULL; - int rc; - unsigned int i, len; - unsigned int tokenCount = 0; - cson_value * cv = NULL; - cson_object const * curObj = obj; - enum { BufSize = 128 }; - char buf[BufSize]; - memset( buf, 0, BufSize ); - rc = cson_rc.RangeError; - - while( cson_next_token( &beg, sep, &end ) ) - { - if( beg == end ) break; - else - { - ++tokenCount; - beg = end; - end = NULL; - } - } - if( 0 == tokenCount ) return cson_rc.RangeError; - beg = path; - end = NULL; - for( i = 0; i < tokenCount; ++i, beg=end, end=NULL ) - { - rc = cson_next_token( &beg, sep, &end ); - assert( 1 == rc ); - assert( beg != end ); - assert( end > beg ); - len = end - beg; - if( len > (BufSize-1) ) return cson_rc.RangeError; - memset( buf, 0, len + 1 ); - memcpy( buf, beg, len ); - buf[len] = 0; - cv = cson_object_get( curObj, buf ); - if( NULL == cv ) return cson_rc.NotFoundError; - else if( i == (tokenCount-1) ) - { - if(tgt) *tgt = cv; - return 0; - } - else if( cson_value_is_object(cv) ) - { - curObj = cson_value_get_object(cv); - assert((NULL != curObj) && "Detected mis-management of internal memory!"); - } - /* TODO: arrays. Requires numeric parsing for the index. */ - else - { - return cson_rc.NotFoundError; - } - } - assert( i == tokenCount ); - return cson_rc.NotFoundError; - } -} - -cson_value * cson_object_get_sub( cson_object const * obj, char const * path, char sep ) -{ - cson_value * v = NULL; - cson_object_fetch_sub( obj, &v, path, sep ); - return v; -} - -static cson_value * cson_value_clone_array( cson_value const * orig ) -{ - unsigned int i = 0; - cson_array const * asrc = cson_value_get_array( orig ); - unsigned int alen = cson_array_length_get( asrc ); - cson_value * destV = NULL; - cson_array * destA = NULL; - assert( orig && asrc ); - destV = cson_value_new_array(); - if( NULL == destV ) return NULL; - destA = cson_value_get_array( destV ); - assert( destA ); - if( 0 != cson_array_reserve( destA, alen ) ) - { - cson_value_free( destV ); - return NULL; - } - for( ; i < alen; ++i ) - { - cson_value * ch = cson_array_get( asrc, i ); - if( NULL != ch ) - { - cson_value * cl = cson_value_clone( ch ); - if( NULL == cl ) - { - cson_value_free( destV ); - return NULL; - } - if( 0 != cson_array_set( destA, i, cl ) ) - { - cson_value_free( cl ); - cson_value_free( destV ); - return NULL; - } - } - } - return destV; -} - -static cson_value * cson_value_clone_object( cson_value const * orig ) -{ - cson_object const * src = cson_value_get_object( orig ); - cson_value * destV = NULL; - cson_object * dest = NULL; - cson_kvp const * kvp = NULL; - cson_object_iterator iter = cson_object_iterator_empty; - assert( orig && src ); - if( 0 != cson_object_iter_init( src, &iter ) ) - { - cson_value_free( destV ); - return NULL; - } - destV = cson_value_new_object(); - if( NULL == destV ) return NULL; - dest = cson_value_get_object( destV ); - assert( dest ); - while( (kvp = cson_object_iter_next( &iter )) ) - { - cson_string const * key = cson_kvp_key( kvp ); - cson_value const * val = cson_kvp_value( kvp ); - if( 0 != cson_object_set( dest, - cson_string_cstr( key ), - cson_value_clone( val ) ) ) - { - cson_value_free( destV ); - return NULL; - } - } - return destV; -} - -cson_value * cson_value_clone( cson_value const * orig ) -{ - if( NULL == orig ) return NULL; - else - { - switch( orig->api->typeID ) - { - case CSON_TYPE_UNDEF: - return cson_value_new(); - case CSON_TYPE_NULL: - return cson_value_null(); - case CSON_TYPE_BOOL: - return cson_value_new_bool( cson_value_get_bool( orig ) ); - case CSON_TYPE_INTEGER: - return cson_value_new_integer( cson_value_get_integer( orig ) ); - break; - case CSON_TYPE_DOUBLE: - return cson_value_new_double( cson_value_get_double( orig ) ); - break; - case CSON_TYPE_STRING: { - cson_string const * str = cson_value_get_string( orig ); - return cson_value_new_string( cson_string_cstr( str ), - cson_string_length_bytes( str ) ); - } - case CSON_TYPE_ARRAY: - return cson_value_clone_array( orig ); - case CSON_TYPE_OBJECT: - return cson_value_clone_object( orig ); - } - assert( 0 && "We can't get this far." ); - return NULL; - } -} - -#if 0 -/* i'm not happy with this... */ -char * cson_pod_to_string( cson_value const * orig ) -{ - if( ! orig ) return NULL; - else - { - enum { BufSize = 64 }; - char * v = NULL; - switch( orig->api->typeID ) - { - case CSON_TYPE_BOOL: { - char const bv = cson_value_get_bool(orig); - v = cson_strdup( bv ? "true" : "false", - bv ? 4 : 5 ); - break; - } - case CSON_TYPE_UNDEF: - case CSON_TYPE_NULL: { - v = cson_strdup( "null", 4 ); - break; - } - case CSON_TYPE_STRING: { - cson_string const * jstr = cson_value_get_string(orig); - unsigned const int slen = cson_string_length_bytes( jstr ); - assert( NULL != jstr ); - v = cson_strdup( cson_string_cstr( jstr ), slen ); - break; - } - case CSON_TYPE_INTEGER: { - char buf[BufSize] = {0}; - if( 0 < sprintf( v, "%"CSON_INT_T_PFMT, cson_value_get_integer(orig)) ) - { - v = cson_strdup( buf, strlen(buf) ); - } - break; - } - case CSON_TYPE_DOUBLE: { - char buf[BufSize] = {0}; - if( 0 < sprintf( v, "%"CSON_DOUBLE_T_PFMT, cson_value_get_double(orig)) ) - { - v = cson_strdup( buf, strlen(buf) ); - } - break; - } - default: - break; - } - return v; - } -} -#endif - -#if 0 -/* i'm not happy with this... */ -char * cson_pod_to_string( cson_value const * orig ) -{ - if( ! orig ) return NULL; - else - { - enum { BufSize = 64 }; - char * v = NULL; - switch( orig->api->typeID ) - { - case CSON_TYPE_BOOL: { - char const bv = cson_value_get_bool(orig); - v = cson_strdup( bv ? "true" : "false", - bv ? 4 : 5 ); - break; - } - case CSON_TYPE_UNDEF: - case CSON_TYPE_NULL: { - v = cson_strdup( "null", 4 ); - break; - } - case CSON_TYPE_STRING: { - cson_string const * jstr = cson_value_get_string(orig); - unsigned const int slen = cson_string_length_bytes( jstr ); - assert( NULL != jstr ); - v = cson_strdup( cson_string_cstr( jstr ), slen ); - break; - } - case CSON_TYPE_INTEGER: { - char buf[BufSize] = {0}; - if( 0 < sprintf( v, "%"CSON_INT_T_PFMT, cson_value_get_integer(orig)) ) - { - v = cson_strdup( buf, strlen(buf) ); - } - break; - } - case CSON_TYPE_DOUBLE: { - char buf[BufSize] = {0}; - if( 0 < sprintf( v, "%"CSON_DOUBLE_T_PFMT, cson_value_get_double(orig)) ) - { - v = cson_strdup( buf, strlen(buf) ); - } - break; - } - default: - break; - } - return v; - } -} -#endif - -#if defined(__cplusplus) -} /*extern "C"*/ -#endif - -#undef MARKER -#undef CSON_OBJECT_PROPS_SORT -#undef CSON_OBJECT_PROPS_SORT_USE_LENGTH -/* end file ./cson.c */ -/* begin file ./cson_lists.h */ -/* Auto-generated from cson_list.h. Edit at your own risk! */ -unsigned int cson_value_list_reserve( cson_value_list * self, unsigned int n ) -{ - if( !self ) return 0; - else if(0 == n) - { - if(0 == self->alloced) return 0; - cson_free(self->list, "cson_value_list_reserve"); - self->list = NULL; - self->alloced = self->count = 0; - return 0; - } - else if( self->alloced >= n ) - { - return self->alloced; - } - else - { - size_t const sz = sizeof(cson_value *) * n; - cson_value * * m = (cson_value **)cson_realloc( self->list, sz, "cson_value_list_reserve" ); - if( ! m ) return self->alloced; - - memset( m + self->alloced, 0, (sizeof(cson_value *)*(n-self->alloced))); - self->alloced = n; - self->list = m; - return n; - } -} -int cson_value_list_append( cson_value_list * self, cson_value * cp ) -{ - if( !self || !cp ) return cson_rc.ArgError; - else if( self->alloced > cson_value_list_reserve(self, self->count+1) ) - { - return cson_rc.AllocError; - } - else - { - self->list[self->count++] = cp; - return 0; - } -} -int cson_value_list_visit( cson_value_list * self, - - int (*visitor)(cson_value * obj, void * visitorState ), - - - - void * visitorState ) -{ - int rc = cson_rc.ArgError; - if( self && visitor ) - { - unsigned int i = 0; - for( rc = 0; (i < self->count) && (0 == rc); ++i ) - { - - cson_value * obj = self->list[i]; - - - - if(obj) rc = visitor( obj, visitorState ); - } - } - return rc; -} -void cson_value_list_clean( cson_value_list * self, - - void (*cleaner)(cson_value * obj) - - - - ) -{ - if( self && cleaner && self->count ) - { - unsigned int i = 0; - for( ; i < self->count; ++i ) - { - - cson_value * obj = self->list[i]; - - - - if(obj) cleaner(obj); - } - } - cson_value_list_reserve(self,0); -} -unsigned int cson_kvp_list_reserve( cson_kvp_list * self, unsigned int n ) -{ - if( !self ) return 0; - else if(0 == n) - { - if(0 == self->alloced) return 0; - cson_free(self->list, "cson_kvp_list_reserve"); - self->list = NULL; - self->alloced = self->count = 0; - return 0; - } - else if( self->alloced >= n ) - { - return self->alloced; - } - else - { - size_t const sz = sizeof(cson_kvp *) * n; - cson_kvp * * m = (cson_kvp **)cson_realloc( self->list, sz, "cson_kvp_list_reserve" ); - if( ! m ) return self->alloced; - - memset( m + self->alloced, 0, (sizeof(cson_kvp *)*(n-self->alloced))); - self->alloced = n; - self->list = m; - return n; - } -} -int cson_kvp_list_append( cson_kvp_list * self, cson_kvp * cp ) -{ - if( !self || !cp ) return cson_rc.ArgError; - else if( self->alloced > cson_kvp_list_reserve(self, self->count+1) ) - { - return cson_rc.AllocError; - } - else - { - self->list[self->count++] = cp; - return 0; - } -} -int cson_kvp_list_visit( cson_kvp_list * self, - - int (*visitor)(cson_kvp * obj, void * visitorState ), - - - - void * visitorState ) -{ - int rc = cson_rc.ArgError; - if( self && visitor ) - { - unsigned int i = 0; - for( rc = 0; (i < self->count) && (0 == rc); ++i ) - { - - cson_kvp * obj = self->list[i]; - - - - if(obj) rc = visitor( obj, visitorState ); - } - } - return rc; -} -void cson_kvp_list_clean( cson_kvp_list * self, - - void (*cleaner)(cson_kvp * obj) - - - - ) -{ - if( self && cleaner && self->count ) - { - unsigned int i = 0; - for( ; i < self->count; ++i ) - { - - cson_kvp * obj = self->list[i]; - - - - if(obj) cleaner(obj); - } - } - cson_kvp_list_reserve(self,0); -} -/* end file ./cson_lists.h */ -/* begin file ./cson_sqlite3.c */ -/** @file cson_sqlite3.c - -This file contains the implementation code for the cson -sqlite3-to-JSON API. - -License: the same as the cson core library. - -Author: Stephan Beal (http://wanderinghorse.net/home/stephan) -*/ -#if CSON_ENABLE_SQLITE3 /* we do this here for the sake of the amalgamation build */ -#include <assert.h> -#include <string.h> /* strlen() */ - -#if 0 -#include <stdio.h> -#define MARKER if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf -#else -#define MARKER if(0) printf -#endif - -#if defined(__cplusplus) -extern "C" { -#endif - -cson_value * cson_sqlite3_column_to_value( sqlite3_stmt * st, int col ) -{ - if( ! st ) return NULL; - else - { -#if 0 - sqlite3_value * val = sqlite3_column_type(st,col); - int const vtype = val ? sqlite3_value_type(val) : -1; - if( ! val ) return cson_value_null(); -#else - int const vtype = sqlite3_column_type(st,col); -#endif - switch( vtype ) - { - case SQLITE_NULL: - return cson_value_null(); - case SQLITE_INTEGER: - /* FIXME: for large integers fall back to Double instead. */ - return cson_value_new_integer( (cson_int_t) sqlite3_column_int64(st, col) ); - case SQLITE_FLOAT: - return cson_value_new_double( sqlite3_column_double(st, col) ); - case SQLITE_BLOB: /* arguably fall through... */ - case SQLITE_TEXT: { - char const * str = (char const *)sqlite3_column_text(st,col); - return cson_value_new_string(str, str ? strlen(str) : 0); - } - default: - return NULL; - } - } -} - -cson_value * cson_sqlite3_column_names( sqlite3_stmt * st ) -{ - cson_value * aryV = NULL; - cson_array * ary = NULL; - char const * colName = NULL; - int i = 0; - int rc = 0; - int colCount = 0; - assert(st); - colCount = sqlite3_column_count(st); - if( colCount <= 0 ) return NULL; - - aryV = cson_value_new_array(); - if( ! aryV ) return NULL; - ary = cson_value_get_array(aryV); - assert(ary); - for( i = 0; (0 ==rc) && (i < colCount); ++i ) - { - colName = sqlite3_column_name( st, i ); - if( ! colName ) rc = cson_rc.AllocError; - else - { - rc = cson_array_set( ary, (unsigned int)i, - cson_value_new_string(colName, strlen(colName)) ); - } - } - if( 0 == rc ) return aryV; - else - { - cson_value_free(aryV); - return NULL; - } -} - - -cson_value * cson_sqlite3_row_to_object( sqlite3_stmt * st ) -{ - cson_value * rootV = NULL; - cson_object * root = NULL; - char const * colName = NULL; - int i = 0; - int rc = 0; - cson_value * currentValue = NULL; - int const colCount = sqlite3_column_count(st); - if( !colCount ) return NULL; - rootV = cson_value_new_object(); - if(!rootV) return NULL; - root = cson_value_get_object(rootV); - for( i = 0; i < colCount; ++i ) - { - colName = sqlite3_column_name( st, i ); - if( ! colName ) goto error; - currentValue = cson_sqlite3_column_to_value(st,i); - if( ! currentValue ) currentValue = cson_value_null(); - rc = cson_object_set( root, colName, currentValue ); - if( 0 != rc ) - { - cson_value_free( currentValue ); - goto error; - } - } - goto end; - error: - cson_value_free( rootV ); - rootV = NULL; - end: - return rootV; -} - -cson_value * cson_sqlite3_row_to_array( sqlite3_stmt * st ) -{ - cson_value * aryV = NULL; - cson_array * ary = NULL; - int i = 0; - int rc = 0; - int const colCount = sqlite3_column_count(st); - if( ! colCount ) return NULL; - aryV = cson_value_new_array(); - if( ! aryV ) return NULL; - ary = cson_value_get_array(aryV); - rc = cson_array_reserve(ary, (unsigned int) colCount ); - if( 0 != rc ) goto error; - - for( i = 0; i < colCount; ++i ){ - cson_value * elem = cson_sqlite3_column_to_value(st,i); - if( ! elem ) goto error; - rc = cson_array_append(ary,elem); - if(0!=rc) - { - cson_value_free( elem ); - goto end; - } - } - goto end; - error: - cson_value_free(aryV); - aryV = NULL; - end: - return aryV; -} - - -/** - Internal impl of cson_sqlite3_stmt_to_json() when the 'fat' - parameter is non-0. -*/ -static int cson_sqlite3_stmt_to_json_fat( sqlite3_stmt * st, cson_value ** tgt ) -{ -#define RETURN(RC) { if(rootV) cson_value_free(rootV); return RC; } - if( ! tgt || !st ) return cson_rc.ArgError; - else - { - cson_value * rootV = NULL; - cson_object * root = NULL; - cson_value * colsV = NULL; - cson_value * rowsV = NULL; - cson_array * rows = NULL; - cson_value * objV = NULL; - int rc = 0; - int const colCount = sqlite3_column_count(st); - if( colCount <= 0 ) return cson_rc.ArgError; - rootV = cson_value_new_object(); - if( ! rootV ) return cson_rc.AllocError; - colsV = cson_sqlite3_column_names(st); - if( ! colsV ) - { - cson_value_free( rootV ); - RETURN(cson_rc.AllocError); - } - root = cson_value_get_object(rootV); - rc = cson_object_set( root, "columns", colsV ); - if( rc ) - { - cson_value_free( colsV ); - RETURN(rc); - } - colsV = NULL; - rowsV = cson_value_new_array(); - if( ! rowsV ) RETURN(cson_rc.AllocError); - rc = cson_object_set( root, "rows", rowsV ); - if( rc ) - { - cson_value_free( rowsV ); - RETURN(rc); - } - rows = cson_value_get_array(rowsV); - assert(rows); - while( SQLITE_ROW == sqlite3_step(st) ) - { - objV = cson_sqlite3_row_to_object(st); - if( ! objV ) RETURN(cson_rc.UnknownError); - rc = cson_array_append( rows, objV ); - if( rc ) - { - cson_value_free( objV ); - RETURN(rc); - } - } - *tgt = rootV; - return 0; - } -#undef RETURN -} - -/** - Internal impl of cson_sqlite3_stmt_to_json() when the 'fat' - parameter is 0. -*/ -static int cson_sqlite3_stmt_to_json_slim( sqlite3_stmt * st, cson_value ** tgt ) -{ -#define RETURN(RC) { if(rootV) cson_value_free(rootV); return RC; } - if( ! tgt || !st ) return cson_rc.ArgError; - else - { - cson_value * rootV = NULL; - cson_object * root = NULL; - cson_value * aryV = NULL; - cson_array * ary = NULL; - cson_value * rowsV = NULL; - cson_array * rows = NULL; - int rc = 0; - int const colCount = sqlite3_column_count(st); - if( colCount <= 0 ) return cson_rc.ArgError; - rootV = cson_value_new_object(); - if( ! rootV ) return cson_rc.AllocError; - aryV = cson_sqlite3_column_names(st); - if( ! aryV ) - { - cson_value_free( rootV ); - RETURN(cson_rc.AllocError); - } - root = cson_value_get_object(rootV); - rc = cson_object_set( root, "columns", aryV ); - if( rc ) - { - cson_value_free( aryV ); - RETURN(rc); - } - aryV = NULL; - ary = NULL; - rowsV = cson_value_new_array(); - if( ! rowsV ) RETURN(cson_rc.AllocError); - rc = cson_object_set( root, "rows", rowsV ); - if( 0 != rc ) - { - cson_value_free( rowsV ); - RETURN(rc); - } - rows = cson_value_get_array(rowsV); - assert(rows); - while( SQLITE_ROW == sqlite3_step(st) ) - { - aryV = cson_sqlite3_row_to_array(st); - if( ! aryV ) RETURN(cson_rc.UnknownError); - rc = cson_array_append( rows, aryV ); - if( 0 != rc ) - { - cson_value_free( aryV ); - RETURN(rc); - } - } - *tgt = rootV; - return 0; - } -#undef RETURN -} - -int cson_sqlite3_stmt_to_json( sqlite3_stmt * st, cson_value ** tgt, char fat ) -{ - return fat - ? cson_sqlite3_stmt_to_json_fat(st,tgt) - : cson_sqlite3_stmt_to_json_slim(st,tgt) - ; -} - -int cson_sqlite3_sql_to_json( sqlite3 * db, cson_value ** tgt, char const * sql, char fat ) -{ - if( !db || !tgt || !sql || !*sql ) return cson_rc.ArgError; - else - { - sqlite3_stmt * st = NULL; - int rc = sqlite3_prepare_v2( db, sql, -1, &st, NULL ); - if( 0 != rc ) return cson_rc.IOError /* FIXME: Better error code? */; - rc = cson_sqlite3_stmt_to_json( st, tgt, fat ); - sqlite3_finalize( st ); - return rc; - } -} - -#if defined(__cplusplus) -} /*extern "C"*/ -#endif -#undef MARKER -#endif /* CSON_ENABLE_SQLITE3 */ -/* end file ./cson_sqlite3.c */ DELETED src/cson_amalgamation.h Index: src/cson_amalgamation.h ================================================================== --- src/cson_amalgamation.h +++ /dev/null @@ -1,2198 +0,0 @@ -/* auto-generated! Do not edit! */ -/* begin file include/wh/cson/cson.h */ -#if !defined(WANDERINGHORSE_NET_CSON_H_INCLUDED) -#define WANDERINGHORSE_NET_CSON_H_INCLUDED 1 - -/*#include <stdint.h> C99: fixed-size int types. */ -#include <stdio.h> /* FILE decl */ - -/** @page page_cson cson JSON API - -cson (pronounced "season") is an object-oriented C API for generating -and consuming JSON (http://www.json.org) data. - -Its main claim to fame is that it can parse JSON from, and output it -to, damned near anywhere. The i/o routines use a callback function to -fetch/emit JSON data, allowing clients to easily plug in their own -implementations. Implementations are provided for string- and -FILE-based i/o. - -Project home page: http://fossil.wanderinghorse.net/repos/cson - -Author: Stephan Beal (http://www.wanderinghorse.net/home/stephan/) - -License: Dual Public Domain/MIT - -The full license text is at the bottom of the main header file -(cson.h). - -Examples of how to use the library are scattered throughout -the API documentation, in the test.c file in the source repo, -and in the wiki on the project's home page. - - -*/ - -#if defined(__cplusplus) -extern "C" { -#endif - -#if defined(_WIN32) || defined(_WIN64) -# define CSON_ENABLE_UNIX 0 -#else -# define CSON_ENABLE_UNIX 1 -#endif - - -/** @typedef some_long_int_type cson_int_t - -Typedef for JSON-like integer types. This is (long long) where feasible, -otherwise (long). -*/ -#if (__STDC_VERSION__ >= 199901L) || (HAVE_LONG_LONG == 1) -typedef long long cson_int_t; -#define CSON_INT_T_SFMT "lld" -#define CSON_INT_T_PFMT "lld" -#else -typedef long cson_int_t; -#define CSON_INT_T_SFMT "ld" -#define CSON_INT_T_PFMT "ld" -#endif - -/** @def CSON_VOID_PTR_IS_BIG - -ONLY define this to a true value if you know that - -(sizeof(cson_int_t) <= sizeof(void*)) - -If that is the case, cson does not need to dynamically -allocate integers. However, enabling this may cause -compilation warnings in 32-bit builds even though the code -being warned about cannot ever be called. To get around such -warnings, when building on a 64-bit environment you can define -this to 1 to get "big" integer support. HOWEVER, all clients must -also use the same value for this macro. If i knew a halfway reliable -way to determine this automatically at preprocessor-time, i would -automate this. We might be able to do halfway reliably by looking -for a large INT_MAX value? -*/ -#if !defined(CSON_VOID_PTR_IS_BIG) - -/* Largely taken from http://predef.sourceforge.net/prearch.html - -See also: http://poshlib.hookatooka.com/poshlib/trac.cgi/browser/posh.h -*/ -# if defined(_WIN64) || defined(__LP64__)/*gcc*/ \ - || defined(_M_X64) || defined(__amd64__) || defined(__amd64) \ - || defined(__x86_64__) || defined(__x86_64) \ - || defined(__ia64__) || defined(__ia64) || defined(_IA64) || defined(__IA64__) \ - || defined(_M_IA64) \ - || defined(__sparc_v9__) || defined(__sparcv9) || defined(_ADDR64) \ - || defined(__64BIT__) -# define CSON_VOID_PTR_IS_BIG 1 -# else -# define CSON_VOID_PTR_IS_BIG 0 -# endif -#endif - -/** @typedef double_or_long_double cson_double_t - -This is the type of double value used by the library. -It is only lightly tested with long double, and when using -long double the memory requirements for such values goes -up. -*/ -#if 0 -typedef long double cson_double_t; -#define CSON_DOUBLE_T_SFMT "Lf" -#define CSON_DOUBLE_T_PFMT "Lf" -#else -typedef double cson_double_t; -#define CSON_DOUBLE_T_SFMT "f" -#define CSON_DOUBLE_T_PFMT "f" -#endif - -/** @def CSON_INT_T_SFMT - -scanf()-compatible format token for cson_int_t. -*/ - -/** @def CSON_INT_T_PFMT - -printf()-compatible format token for cson_int_t. -*/ - - -/** @def CSON_DOUBLE_T_SFMT - -scanf()-compatible format token for cson_double_t. -*/ - -/** @def CSON_DOUBLE_T_PFMT - -printf()-compatible format token for cson_double_t. -*/ - -typedef struct cson_value cson_value; - -/** @struct cson_value - - The core value type of this API. It is opaque to clients, and - only the cson public API should be used for setting or - inspecting their values. - - This class is opaque because stack-based usage can easily cause - leaks if one does not intimately understand the underlying - internal memory management (which sometimes changes). - - It is (as of 20110323) legal to insert a given value instance into - multiple containers (they will share ownership using reference - counting) as long as those insertions do not cause cycles. However, - be very aware that such value re-use uses a reference to the - original copy, meaning that if its value is changed once, it is - changed everywhere. Also beware that multi-threaded write - operations on such references leads to undefined behaviour. - - PLEASE read the ACHTUNGEN below... - - ACHTUNG #1: - - cson_values MUST NOT form cycles (e.g. via object or array - entries). - - Not abiding th Holy Law Of No Cycles will lead to double-frees and - the like (i.e. undefined behaviour, likely crashes due to infinite - recursion or stepping on invalid (freed) pointers). - - ACHTUNG #2: - - ALL cson_values returned as non-const cson_value pointers from any - public functions in the cson API are to be treated as if they are - heap-allocated, and MUST be freed by client by doing ONE of: - - - Passing it to cson_value_free(). - - - Adding it to an Object or Array, in which case the object/array - takes over ownership. As of 20110323, a value may be inserted into - a single container multiple times, or into multiple containers, - in which case they all share ownership (via reference counting) - of the original value (meaning any changes to it are visible in - all references to it). - - Each call to cson_value_new_xxx() MUST eventually be followed up - by one of those options. - - Some cson_value_new_XXX() implementations do not actually allocate - memory, but this is an internal implementation detail. Client code - MUST NOT rely on this behaviour and MUST treat each object - returned by such a function as if it was a freshly-allocated copy - (even if their pointer addresses are the same). - - ACHTUNG #3: - - Note that ACHTUNG #2 tells us that we must always free (or transfer - ownership of) all pointers returned bycson_value_new_xxx(), but - that two calls to (e.g.) cson_value_new_bool(1) will (or might) - return the same address. The client must not rely on the - "non-allocation" policy of such special cases, and must pass each - returned value to cson_value_free(), even if two of them have the - same address. Some special values (e.g. null, true, false, integer - 0, double 0.0, and empty strings) use shared copies and in other - places reference counting is used internally to figure out when it - is safe to destroy an object. - - - @see cson_value_new_array() - @see cson_value_new_object() - @see cson_value_new_string() - @see cson_value_new_integer() - @see cson_value_new_double() - @see cson_value_new_bool() - @see cson_value_true() - @see cson_value_false() - @see cson_value_null() - @see cson_value_free() -*/ - -/** @var cson_rc - - This object defines the error codes used by cson. - - Library routines which return int values almost always return a - value from this structure. None of the members in this struct have - published values except for the OK member, which has the value 0. - All other values might be incidentally defined where clients - can see them, but the numbers might change from release to - release, so clients should only use the symbolic names. - - Client code is expected to access these values via the shared - cson_rc object, and use them as demonstrated here: - - @code - int rc = cson_some_func(...); - if( 0 == rc ) {...success...} - else if( cson_rc.ArgError == rc ) { ... some argument was wrong ... } - else if( cson_rc.AllocError == rc ) { ... allocation error ... } - ... - @endcode - - The entries named Parse_XXX are generally only returned by - cson_parse() and friends. -*/ - -/** @struct cson_rc_ - See \ref cson_rc for details. -*/ -static const struct cson_rc_ -{ - /** The generic success value. Guaranteed to be 0. */ - const int OK; - /** Signifies an error in one or more arguments (e.g. NULL where it is not allowed). */ - const int ArgError; - /** Signifies that some argument is not in a valid range. */ - const int RangeError; - /** Signifies that some argument is not of the correct logical cson type. */ - const int TypeError; - /** Signifies an input/ouput error. */ - const int IOError; - /** Signifies an out-of-memory error. */ - const int AllocError; - /** Signifies that the called code is "NYI" (Not Yet Implemented). */ - const int NYIError; - /** Signifies that an internal error was triggered. If it happens, please report this as a bug! */ - const int InternalError; - /** Signifies that the called operation is not supported in the - current environment. e.g. missing support from 3rd-party or - platform-specific code. - */ - const int UnsupportedError; - /** - Signifies that the request resource could not be found. - */ - const int NotFoundError; - /** - Signifies an unknown error, possibly because an underlying - 3rd-party API produced an error and we have no other reasonable - error code to convert it to. - */ - const int UnknownError; - /** - Signifies that the parser found an unexpected character. - */ - const int Parse_INVALID_CHAR; - /** - Signifies that the parser found an invalid keyword (possibly - an unquoted string). - */ - const int Parse_INVALID_KEYWORD; - /** - Signifies that the parser found an invalid escape sequence. - */ - const int Parse_INVALID_ESCAPE_SEQUENCE; - /** - Signifies that the parser found an invalid Unicode character - sequence. - */ - const int Parse_INVALID_UNICODE_SEQUENCE; - /** - Signifies that the parser found an invalid numeric token. - */ - const int Parse_INVALID_NUMBER; - /** - Signifies that the parser reached its maximum defined - parsing depth before finishing the input. - */ - const int Parse_NESTING_DEPTH_REACHED; - /** - Signifies that the parser found an unclosed object or array. - */ - const int Parse_UNBALANCED_COLLECTION; - /** - Signifies that the parser found an key in an unexpected place. - */ - const int Parse_EXPECTED_KEY; - /** - Signifies that the parser expected to find a colon but - found none (e.g. between keys and values in an object). - */ - const int Parse_EXPECTED_COLON; -} cson_rc = { -0/*OK*/, -1/*ArgError*/, -2/*RangeError*/, -3/*TypeError*/, -4/*IOError*/, -5/*AllocError*/, -6/*NYIError*/, -7/*InternalError*/, -8/*UnsupportedError*/, -9/*NotFoundError*/, -10/*UnknownError*/, -11/*Parse_INVALID_CHAR*/, -12/*Parse_INVALID_KEYWORD*/, -13/*Parse_INVALID_ESCAPE_SEQUENCE*/, -14/*Parse_INVALID_UNICODE_SEQUENCE*/, -15/*Parse_INVALID_NUMBER*/, -16/*Parse_NESTING_DEPTH_REACHED*/, -17/*Parse_UNBALANCED_COLLECTION*/, -18/*Parse_EXPECTED_KEY*/, -19/*Parse_EXPECTED_COLON*/ -}; - -/** - Returns the string form of the cson_rc code corresponding to rc, or - some unspecified, non-NULL string if it is an unknown code. - - The returned bytes are static and do not changing during the - lifetime of the application. -*/ -char const * cson_rc_string(int rc); - -/** @struct cson_parse_opt - Client-configurable options for the cson_parse() family of - functions. -*/ -struct cson_parse_opt -{ - /** - Maximum object/array depth to traverse. - */ - unsigned short maxDepth; - /** - Whether or not to allow C-style comments. Do not rely on this - option being available. If the underlying parser is replaced, - this option might no longer be supported. - */ - char allowComments; -}; -typedef struct cson_parse_opt cson_parse_opt; - -/** - Empty-initialized cson_parse_opt object. -*/ -#define cson_parse_opt_empty_m { 25/*maxDepth*/, 0/*allowComments*/} - - -/** - A class for holding JSON parser information. It is primarily - intended for finding the position of a parse error. -*/ -struct cson_parse_info -{ - /** - 1-based line number. - */ - unsigned int line; - /** - 0-based column number. - */ - unsigned int col; - - /** - Length, in bytes. - */ - unsigned int length; - - /** - Error code of the parse run (0 for no error). - */ - int errorCode; - - /** - The total number of object keys successfully processed by the - parser. - */ - unsigned int totalKeyCount; - - /** - The total number of object/array values successfully processed - by the parser, including the root node. - */ - unsigned int totalValueCount; -}; -typedef struct cson_parse_info cson_parse_info; - -/** - Empty-initialized cson_parse_info object. -*/ -#define cson_parse_info_empty_m {1/*line*/,\ - 0/*col*/, \ - 0/*length*/, \ - 0/*errorCode*/, \ - 0/*totalKeyCount*/, \ - 0/*totalValueCount*/ \ - } -/** - Empty-initialized cson_parse_info object. -*/ -extern const cson_parse_info cson_parse_info_empty; - -/** - Empty-initialized cson_parse_opt object. -*/ -extern const cson_parse_opt cson_parse_opt_empty; - -/** - Client-configurable options for the cson_output() family of - functions. -*/ -struct cson_output_opt -{ - /** - Specifies how to indent (or not) output. The values - are: - - (0) == no extra indentation. - - (1) == 1 TAB character for each level. - - (>1) == that number of SPACES for each level. - */ - unsigned char indentation; - - /** - Maximum object/array depth to traverse. Traversing deeply can - be indicative of cycles in the object/array tree, and this - value is used to figure out when to abort the traversal. - */ - unsigned short maxDepth; - - /** - If true, a newline will be added to generated output, - else not. - */ - char addNewline; - - /** - If true, a space will be added after the colon operator - in objects' key/value pairs. - */ - char addSpaceAfterColon; - - /** - If set to 1 then objects/arrays containing only a single value - will not indent an extra level for that value (but will indent - on subsequent levels if that value contains multiple values). - */ - char indentSingleMemberValues; - - /** - The JSON format allows, but does not require, JSON generators - to backslash-escape forward slashes. This option enables/disables - that feature. According to JSON's inventor, Douglas Crockford: - - <quote> - It is allowed, not required. It is allowed so that JSON can be - safely embedded in HTML, which can freak out when seeing - strings containing "</". JSON tolerates "<\/" for this reason. - </quote> - - (from an email on 2011-04-08) - - The default value is 0 (because it's just damned ugly). - */ - char escapeForwardSlashes; -}; -typedef struct cson_output_opt cson_output_opt; - -/** - Empty-initialized cson_output_opt object. -*/ -#define cson_output_opt_empty_m { 0/*indentation*/,\ - 25/*maxDepth*/, \ - 0/*addNewline*/, \ - 0/*addSpaceAfterColon*/, \ - 0/*indentSingleMemberValues*/, \ - 0/*escapeForwardSlashes*/ \ - } - -/** - Empty-initialized cson_output_opt object. -*/ -extern const cson_output_opt cson_output_opt_empty; - -/** - Typedef for functions which act as an input source for - the cson JSON parser. - - The arguments are: - - - state: implementation-specific state needed by the function. - - - n: when called, *n will be the number of bytes the function - should read and copy to dest. The function MUST NOT copy more than - *n bytes to dest. Before returning, *n must be set to the number of - bytes actually copied to dest. If that number is smaller than the - original *n value, the input is assumed to be completed (thus this - is not useful with non-blocking readers). - - - dest: the destination memory to copy the data do. - - Must return 0 on success, non-0 on error (preferably a value from - cson_rc). - - The parser allows this routine to return a partial character from a - UTF multi-byte character. The input routine does not need to - concern itself with character boundaries. -*/ -typedef int (*cson_data_source_f)( void * state, void * dest, unsigned int * n ); - -/** - Typedef for functions which act as an output destination for - generated JSON. - - The arguments are: - - - state: implementation-specific state needed by the function. - - - n: the length, in bytes, of src. - - - src: the source bytes which the output function should consume. - The src pointer will be invalidated shortly after this function - returns, so the implementation must copy or ignore the data, but not - hold a copy of the src pointer. - - Must return 0 on success, non-0 on error (preferably a value from - cson_rc). - - These functions are called relatively often during the JSON-output - process, and should try to be fast. -*/ -typedef int (*cson_data_dest_f)( void * state, void const * src, unsigned int n ); - -/** - Reads JSON-formatted string data (in ASCII, UTF8, or UTF16), using the - src function to fetch all input. This function fetches each input character - from the source function, which is calls like src(srcState, buffer, bufferSize), - and processes them. If anything is not JSON-kosher then this function - fails and returns one of the non-0 cson_rc codes. - - This function is only intended to read root nodes of a JSON tree, either - a single object or a single array, containing any number of child elements. - - On success, *tgt is assigned the value of the root node of the - JSON input, and the caller takes over ownership of that memory. - On error, *tgt is not modified and the caller need not do any - special cleanup, except possibly for the input source. - - - The opt argument may point to an initialized cson_parse_opt object - which contains any settings the caller wants. If it is NULL then - default settings (the values defined in cson_parse_opt_empty) are - used. - - The info argument may be NULL. If it is not NULL then the parser - populates it with information which is useful in error - reporting. Namely, it contains the line/column of parse errors. - - The srcState argument is ignored by this function but is passed on to src, - so any output-destination-specific state can be stored there and accessed - via the src callback. - - Non-parse error conditions include: - - - (!tgt) or !src: cson_rc.ArgError - - cson_rc.AllocError can happen at any time during the input phase - - Here's a complete example of using a custom input source: - - @code - // Internal type to hold state for a JSON input string. - typedef struct - { - char const * str; // start of input string - char const * pos; // current internal cursor position - char const * end; // logical EOF (one-past-the-end) - } StringSource; - - // cson_data_source_f() impl which uses StringSource. - static int cson_data_source_StringSource( void * state, void * dest, - unsigned int * n ) - { - StringSource * ss = (StringSource*) state; - unsigned int i; - unsigned char * tgt = (unsigned char *)dest; - if( ! ss || ! n || !dest ) return cson_rc.ArgError; - else if( !*n ) return cson_rc.RangeError; - for( i = 0; - (i < *n) && (ss->pos < ss->end); - ++i, ++ss->pos, ++tgt ) - { - *tgt = *ss->pos; - } - *n = i; - return 0; - } - - ... - // Now use StringSource together with cson_parse() - StringSource ss; - cson_value * root = NULL; - char const * json = "{\"k1\":123}"; - ss.str = ss.pos = json; - ss.end = json + strlen(json); - int rc = cson_parse( &root, cson_data_source_StringSource, &ss, NULL, NULL ); - @endcode - - It is recommended that clients wrap such utility code into - type-safe wrapper functions which also initialize the internal - state object and check the user-provided parameters for legality - before passing them on to cson_parse(). For examples of this, see - cson_parse_FILE() or cson_parse_string(). - - TODOs: - - - Buffer the input in larger chunks. We currently read - byte-by-byte, but i'm too tired to write/test the looping code for - the buffering. - - @see cson_parse_FILE() - @see cson_parse_string() -*/ -int cson_parse( cson_value ** tgt, cson_data_source_f src, void * srcState, - cson_parse_opt const * opt, cson_parse_info * info ); -/** - A cson_data_source_f() implementation which requires the state argument - to be a readable (FILE*) handle. -*/ -int cson_data_source_FILE( void * state, void * dest, unsigned int * n ); - -/** - Equivalent to cson_parse( tgt, cson_data_source_FILE, src, opt ). - - @see cson_parse_filename() -*/ -int cson_parse_FILE( cson_value ** tgt, FILE * src, - cson_parse_opt const * opt, cson_parse_info * info ); - -/** - Convenience wrapper around cson_parse_FILE() which opens the given filename. - - Returns cson_rc.IOError if the file cannot be opened. - - @see cson_parse_FILE() -*/ -int cson_parse_filename( cson_value ** tgt, char const * src, - cson_parse_opt const * opt, cson_parse_info * info ); - -/** - Uses an internal helper class to pass src through cson_parse(). - See that function for the return value and argument semantics. - - src must be a string containing JSON code, at least len bytes long, - and the parser will attempt to parse exactly len bytes from src. - - If len is less than 2 (the minimum length of a legal top-node JSON - object) then cson_rc.RangeError is returned. -*/ -int cson_parse_string( cson_value ** tgt, char const * src, unsigned int len, - cson_parse_opt const * opt, cson_parse_info * info ); - - - -/** - Outputs the given value as a JSON-formatted string, sending all - output to the given callback function. It is intended for top-level - objects or arrays, but can be used with any cson_value. - - If opt is NULL then default options (the values defined in - cson_output_opt_empty) are used. - - If opt->maxDepth is exceeded while traversing the value tree, - cson_rc.RangeError is returned. - - The destState parameter is ignored by this function and is passed - on to the dest function. - - Returns 0 on success. On error, any amount of output might have been - generated before the error was triggered. - - Example: - - @code - int rc = cson_output( myValue, cson_data_dest_FILE, stdout, NULL ); - // basically equivalent to: cson_output_FILE( myValue, stdout, NULL ); - // but note that cson_output_FILE() actually uses different defaults - // for the output options. - @endcode -*/ -int cson_output( cson_value const * src, cson_data_dest_f dest, void * destState, cson_output_opt const * opt ); - - -/** - A cson_data_dest_f() implementation which requires the state argument - to be a writable (FILE*) handle. -*/ -int cson_data_dest_FILE( void * state, void const * src, unsigned int n ); - -/** - Almost equivalent to cson_output( src, cson_data_dest_FILE, dest, opt ), - with one minor difference: if opt is NULL then the default options - always include the addNewline option, since that is normally desired - for FILE output. - - @see cson_output_filename() -*/ -int cson_output_FILE( cson_value const * src, FILE * dest, cson_output_opt const * opt ); -/** - Convenience wrapper around cson_output_FILE() which writes to the given filename, destroying - any existing contents. Returns cson_rc.IOError if the file cannot be opened. - - @see cson_output_FILE() -*/ -int cson_output_filename( cson_value const * src, char const * dest, cson_output_opt const * fmt ); - -/** Returns true if v is null, v->api is NULL, or v holds the special undefined value. */ -char cson_value_is_undef( cson_value const * v ); -/** Returns true if v contains a null value. */ -char cson_value_is_null( cson_value const * v ); -/** Returns true if v contains a bool value. */ -char cson_value_is_bool( cson_value const * v ); -/** Returns true if v contains an integer value. */ -char cson_value_is_integer( cson_value const * v ); -/** Returns true if v contains a double value. */ -char cson_value_is_double( cson_value const * v ); -/** Returns true if v contains a number (double, integer) value. */ -char cson_value_is_number( cson_value const * v ); -/** Returns true if v contains a string value. */ -char cson_value_is_string( cson_value const * v ); -/** Returns true if v contains an array value. */ -char cson_value_is_array( cson_value const * v ); -/** Returns true if v contains an object value. */ -char cson_value_is_object( cson_value const * v ); - -/** @struct cson_object - - cson_object is an opaque handle to an Object value. - - They are used like: - - @code - cson_object * obj = cson_value_get_object(myValue); - ... - @endcode - - They can be created like: - - @code - cson_value * objV = cson_value_new_object(); - cson_object * obj = cson_value_get_object(objV); - // obj is owned by objV and objV must eventually be freed - // using cson_value_free() or added to a container - // object/array (which transfers ownership to that container). - @endcode - - @see cson_value_new_object() - @see cson_value_get_object() - @see cson_value_free() -*/ - -typedef struct cson_object cson_object; - -/** @struct cson_array - - cson_array is an opaque handle to an Array value. - - They are used like: - - @code - cson_array * obj = cson_value_get_array(myValue); - ... - @endcode - - They can be created like: - - @code - cson_value * arV = cson_value_new_array(); - cson_array * ar = cson_value_get_array(arV); - // ar is owned by arV and arV must eventually be freed - // using cson_value_free() or added to a container - // object/array (which transfers ownership to that container). - @endcode - - @see cson_value_new_array() - @see cson_value_get_array() - @see cson_value_free() - -*/ -typedef struct cson_array cson_array; - -/** @struct cson_string - - cson-internal string type, opaque to client code. Strings in cson - are immutable and allocated only by library internals, never - directly by client code. - - The actual string bytes are to be allocated together in the same - memory chunk as the cson_string object, which saves us 1 malloc() - and 1 pointer member in this type (because we no longer have a - direct pointer to the memory). - - Potential TODOs: - - @see cson_string_cstr() -*/ -typedef struct cson_string cson_string; - -/** - Converts the given value to a boolean, using JavaScript semantics depending - on the concrete type of val: - - undef or null: false - - boolean: same - - integer, double: 0 or 0.0 == false, else true - - object, array: true - - Returns 0 on success and assigns *v (if v is not NULL) to either 0 or 1. - On error (val is NULL) then v is not modified. -*/ -int cson_value_fetch_bool( cson_value const * val, char * v ); -/** - Similar to cson_value_fetch_bool(), but fetches an integer value. - - The conversion, if any, depends on the concrete type of val: - - NULL, null, undefined: *v is set to 0 and 0 is returned. - - string, object, array: *v is set to 0 and - cson_rc.TypeError is returned. The error may normally be safely - ignored, but it is provided for those wanted to know whether a direct - conversion was possible. - - integer: *v is set to the int value and 0 is returned. - - double: *v is set to the value truncated to int and 0 is returned. -*/ -int cson_value_fetch_integer( cson_value const * val, cson_int_t * v ); -/** - The same conversions and return values as - cson_value_fetch_integer(), except that the roles of int/double are - swapped. -*/ -int cson_value_fetch_double( cson_value const * val, cson_double_t * v ); - -/** - If cson_value_is_string(val) then this function assigns *str to the - contents of the string. str may be NULL, in which case this function - functions like cson_value_is_string() but returns 0 on success. - - Returns 0 if val is-a string, else non-0, in which case *str is not - modified. - - The bytes are owned by the given value and may be invalidated in any of - the following ways: - - - The value is cleaned up or freed. - - - An array or object containing the value peforms a re-allocation - (it shrinks or grows). - - And thus the bytes should be consumed before any further operations - on val or any container which holds it. - - Note that this routine does not convert non-String values to their - string representations. (Adding that ability would add more - overhead to every cson_value instance.) -*/ -int cson_value_fetch_string( cson_value const * val, cson_string const ** str ); - -/** - If cson_value_is_object(val) then this function assigns *obj to the underlying - object value and returns 0, otherwise non-0 is returned and *obj is not modified. - - obj may be NULL, in which case this function works like cson_value_is_object() - but with inverse return value semantics (0==success) (and it's a few - CPU cycles slower). - - The *obj pointer is owned by val, and will be invalidated when val - is cleaned up. - - Achtung: for best results, ALWAYS pass a pointer to NULL as the - second argument, e.g.: - - @code - cson_object * obj = NULL; - int rc = cson_value_fetch_object( val, &obj ); - - // Or, more simply: - obj = cson_value_get_object( val ); - @endcode - - @see cson_value_get_object() -*/ -int cson_value_fetch_object( cson_value const * val, cson_object ** obj ); - -/** - Identical to cson_value_fetch_object(), but works on array values. - - @see cson_value_get_array() -*/ -int cson_value_fetch_array( cson_value const * val, cson_array ** tgt ); - -/** - Simplified form of cson_value_fetch_bool(). Returns 0 if val - is NULL. -*/ -char cson_value_get_bool( cson_value const * val ); - -/** - Simplified form of cson_value_fetch_integer(). Returns 0 if val - is NULL. -*/ -cson_int_t cson_value_get_integer( cson_value const * val ); - -/** - Simplified form of cson_value_fetch_double(). Returns 0.0 if val - is NULL. -*/ -cson_double_t cson_value_get_double( cson_value const * val ); - -/** - Simplified form of cson_value_fetch_string(). Returns NULL if val - is-not-a string value. -*/ -cson_string const * cson_value_get_string( cson_value const * val ); - -/** - Returns a pointer to the NULL-terminated string bytes of str. - The bytes are owned by string and will be invalided when it - is cleaned up. - - If str is NULL then NULL is returned. - - @see cson_string_length_bytes() - @see cson_value_get_string() -*/ -char const * cson_string_cstr( cson_string const * str ); - -/** - Convenience function which returns the string bytes of - the given value if it is-a string, otherwise it returns - NULL. Note that this does no conversion of non-string types - to strings. - - Equivalent to cson_string_cstr(cson_value_get_string(val)). -*/ -char const * cson_value_get_cstr( cson_value const * val ); - -/** - Equivalent to cson_string_cmp_cstr_n(lhs, cson_string_cstr(rhs), cson_string_length_bytes(rhs)). -*/ -int cson_string_cmp( cson_string const * lhs, cson_string const * rhs ); - -/** - Compares lhs to rhs using memcmp()/strcmp() semantics. Generically - speaking it returns a negative number if lhs is less-than rhs, 0 if - they are equivalent, or a positive number if lhs is greater-than - rhs. It has the following rules for equivalence: - - - The maximum number of bytes compared is the lesser of rhsLen and - the length of lhs. If the strings do not match, but compare equal - up to the just-described comparison length, the shorter string is - considered to be less-than the longer one. - - - If lhs and rhs are both NULL, or both have a length of 0 then they will - compare equal. - - - If lhs is null/length-0 but rhs is not then lhs is considered to be less-than - rhs. - - - If rhs is null/length-0 but lhs is not then rhs is considered to be less-than - rhs. - - - i have no clue if the results are exactly correct for UTF strings. - -*/ -int cson_string_cmp_cstr_n( cson_string const * lhs, char const * rhs, unsigned int rhsLen ); - -/** - Equivalent to cson_string_cmp_cstr_n( lhs, rhs, (rhs&&*rhs)?strlen(rhs):0 ). -*/ -int cson_string_cmp_cstr( cson_string const * lhs, char const * rhs ); - -/** - Returns the length, in bytes, of str, or 0 if str is NULL. This is - an O(1) operation. - - TODO: add cson_string_length_chars() (is O(N) unless we add another - member to store the char length). - - @see cson_string_cstr() -*/ -unsigned int cson_string_length_bytes( cson_string const * str ); - -/** - Returns the number of UTF8 characters in str. This value will - be at most as long as cson_string_length_bytes() for the - same string, and less if it has multi-byte characters. - - Returns 0 if str is NULL. -*/ -unsigned int cson_string_length_utf8( cson_string const * str ); - -/** - Like cson_value_get_string(), but returns a copy of the underying - string bytes, which the caller owns and must eventually free - using free(). -*/ -char * cson_value_get_string_copy( cson_value const * val ); - -/** - Simplified form of cson_value_fetch_object(). Returns NULL if val - is-not-a object value. -*/ -cson_object * cson_value_get_object( cson_value const * val ); - -/** - Simplified form of cson_value_fetch_array(). Returns NULL if val - is-not-a array value. -*/ -cson_array * cson_value_get_array( cson_value const * val ); - -/** - Const-correct form of cson_value_get_array(). -*/ -cson_array const * cson_value_get_array_c( cson_value const * val ); - -/** - If ar is-a array and is at least (pos+1) entries long then *v (if v is not NULL) - is assigned to the value at that position (which may be NULL). - - Ownership of the *v return value is unchanged by this call. (The - containing array may share ownership of the value with other - containers.) - - If pos is out of range, non-0 is returned and *v is not modified. - - If v is NULL then this function returns 0 if pos is in bounds, but does not - otherwise return a value to the caller. -*/ -int cson_array_value_fetch( cson_array const * ar, unsigned int pos, cson_value ** v ); - -/** - Simplified form of cson_array_value_fetch() which returns NULL if - ar is NULL, pos is out of bounds or if ar has no element at that - position. -*/ -cson_value * cson_array_get( cson_array const * ar, unsigned int pos ); - -/** - Ensures that ar has allocated space for at least the given - number of entries. This never shrinks the array and never - changes its logical size, but may pre-allocate space in the - array for storing new (as-yet-unassigned) values. - - Returns 0 on success, or non-zero on error: - - - If ar is NULL: cson_rc.ArgError - - - If allocation fails: cson_rc.AllocError -*/ -int cson_array_reserve( cson_array * ar, unsigned int size ); - -/** - If ar is not NULL, sets *v (if v is not NULL) to the length of the array - and returns 0. Returns cson_rc.ArgError if ar is NULL. -*/ -int cson_array_length_fetch( cson_array const * ar, unsigned int * v ); - -/** - Simplified form of cson_array_length_fetch() which returns 0 if ar - is NULL. -*/ -unsigned int cson_array_length_get( cson_array const * ar ); - -/** - Sets the given index of the given array to the given value. - - If ar already has an item at that index then it is cleaned up and - freed before inserting the new item. - - ar is expanded, if needed, to be able to hold at least (ndx+1) - items, and any new entries created by that expansion are empty - (NULL values). - - On success, 0 is returned and ownership of v is transfered to ar. - - On error ownership of v is NOT modified, and the caller may still - need to clean it up. For example, the following code will introduce - a leak if this function fails: - - @code - cson_array_append( myArray, cson_value_new_integer(42) ); - @endcode - - Because the value created by cson_value_new_integer() has no owner - and is not cleaned up. The "more correct" way to do this is: - - @code - cson_value * v = cson_value_new_integer(42); - int rc = cson_array_append( myArray, v ); - if( 0 != rc ) { - cson_value_free( v ); - ... handle error ... - } - @endcode - -*/ -int cson_array_set( cson_array * ar, unsigned int ndx, cson_value * v ); - -/** - Appends the given value to the given array, transfering ownership of - v to ar. On error, ownership of v is not modified. Ownership of ar - is never changed by this function. - - This is functionally equivalent to - cson_array_set(ar,cson_array_length_get(ar),v), but this - implementation has slightly different array-preallocation policy - (it grows more eagerly). - - Returns 0 on success, non-zero on error. Error cases include: - - - ar or v are NULL: cson_rc.ArgError - - - Array cannot be expanded to hold enough elements: cson_rc.AllocError. - - - Appending would cause a numeric overlow in the array's size: - cson_rc.RangeError. (However, you'll get an AllocError long before - that happens!) - - On error ownership of v is NOT modified, and the caller may still - need to clean it up. See cson_array_set() for the details. - -*/ -int cson_array_append( cson_array * ar, cson_value * v ); - - -/** - Creates a new cson_value from the given boolean value. - - Ownership of the new value is passed to the caller, who must - eventually either free the value using cson_value_free() or - inserting it into a container (array or object), which transfers - ownership to the container. See the cson_value class documentation - for more details. - - Returns NULL on allocation error. -*/ -cson_value * cson_value_new_bool( char v ); - - -/** - Returns the special JSON "null" value. When outputing JSON, - its string representation is "null" (without the quotes). - - See cson_value_new_bool() for notes regarding the returned - value's memory. -*/ -cson_value * cson_value_null(); - -/** - Equivalent to cson_value_new_bool(1). -*/ -cson_value * cson_value_true(); - -/** - Equivalent to cson_value_new_bool(0). -*/ -cson_value * cson_value_false(); - -/** - Semantically the same as cson_value_new_bool(), but for integers. -*/ -cson_value * cson_value_new_integer( cson_int_t v ); - -/** - Semantically the same as cson_value_new_bool(), but for doubles. -*/ -cson_value * cson_value_new_double( cson_double_t v ); - -/** - Semantically the same as cson_value_new_bool(), but for strings. - This creates a JSON value which copies the first n bytes of str. - The string will automatically be NUL-terminated. - - Note that if str is NULL or n is 0, this function still - returns non-NULL value representing that empty string. - - Returns NULL on allocation error. - - See cson_value_new_bool() for important information about the - returned memory. -*/ -cson_value * cson_value_new_string( char const * str, unsigned int n ); - -/** - Allocates a new "object" value and transfers ownership of it to the - caller. It must eventually be destroyed, by the caller or its - owning container, by passing it to cson_value_free(). - - Returns NULL on allocation error. - - Post-conditions: cson_value_is_object(value) will return true. - - @see cson_value_new_array() - @see cson_value_free() -*/ -cson_value * cson_value_new_object(); - -/** - Allocates a new "array" value and transfers ownership of it to the - caller. It must eventually be destroyed, by the caller or its - owning container, by passing it to cson_value_free(). - - Returns NULL on allocation error. - - Post-conditions: cson_value_is_array(value) will return true. - - @see cson_value_new_object() - @see cson_value_free() -*/ -cson_value * cson_value_new_array(); - -/** - Frees any resources owned by v, then frees v. If v is a container - type (object or array) its children are also freed (recursively). - - If v is NULL, this is a no-op. - - This function decrements a reference count and only destroys the - value if its reference count drops to 0. Reference counts are - increased by either inserting the value into a container or via - cson_value_add_reference(). Even if this function does not - immediately destroy the value, the value must be considered, from - the perspective of that client code, to have been - destroyed/invalidated by this call. - - - @see cson_value_new_object() - @see cson_value_new_array() - @see cson_value_add_reference() -*/ -void cson_value_free(cson_value * v); - -/** - Functionally similar to cson_array_set(), but uses a string key - as an index. Like arrays, if a value already exists for the given key, - it is destroyed by this function before inserting the new value. - - If v is NULL then this call is equivalent to - cson_object_unset(obj,key). Note that (v==NULL) is treated - differently from v having the special null value. In the latter - case, the key is set to the special null value. - - The key may be encoded as ASCII or UTF8. Results are undefined - with other encodings, and the errors won't show up here, but may - show up later, e.g. during output. - - Returns 0 on success, non-0 on error. It has the following error - cases: - - - cson_rc.ArgError: obj or key are NULL or strlen(key) is 0. - - - cson_rc.AllocError: an out-of-memory error - - On error ownership of v is NOT modified, and the caller may still - need to clean it up. For example, the following code will introduce - a leak if this function fails: - - @code - cson_object_set( myObj, "foo", cson_value_new_integer(42) ); - @endcode - - Because the value created by cson_value_new_integer() has no owner - and is not cleaned up. The "more correct" way to do this is: - - @code - cson_value * v = cson_value_new_integer(42); - int rc = cson_object_set( myObj, "foo", v ); - if( 0 != rc ) { - cson_value_free( v ); - ... handle error ... - } - @endcode - - Potential TODOs: - - - Add an overload which takes a cson_value key instead. To get - any value out of that we first need to be able to convert arbitrary - value types to strings. We could simply to-JSON them and use those - as keys. -*/ -int cson_object_set( cson_object * obj, char const * key, cson_value * v ); - -/** - Removes a property from an object. - - If obj contains the given key, it is removed and 0 is returned. If - it is not found, cson_rc.NotFoundError is returned (which can - normally be ignored by client code). - - cson_rc.ArgError is returned if obj or key are NULL or key has - a length of 0. - - Returns 0 if the given key is found and removed. - - This is functionally equivalent calling - cson_object_set(obj,key,NULL). -*/ -int cson_object_unset( cson_object * obj, char const * key ); - -/** - Searches the given object for a property with the given key. If found, - it is returned. If no match is found, or any arguments are NULL, NULL is - returned. The returned object is owned by obj, and may be invalidated - by ANY operations which change obj's property list (i.e. add or remove - properties). - - FIXME: allocate the key/value pairs like we do for cson_array, - to get improve the lifetimes of fetched values. - - @see cson_object_fetch_sub() - @see cson_object_get_sub() -*/ -cson_value * cson_object_get( cson_object const * obj, char const * key ); - -/** - Similar to cson_object_get(), but removes the value from the parent - object's ownership. If no item is found then NULL is returned, else - the object (now owned by the caller or possibly shared with other - containers) is returned. - - Returns NULL if either obj or key are NULL or key has a length - of 0. - - This function reduces the returned value's reference count but has - the specific property that it does not treat refcounts 0 and 1 - identically, meaning that the returned object may have a refcount - of 0. This behaviour works around a corner-case where we want to - extract a child element from its parent and then destroy the parent - (which leaves us in an undesireable (normally) reference count - state). -*/ -cson_value * cson_object_take( cson_object * obj, char const * key ); - -/** - Fetches a property from a child (or [great-]*grand-child) object. - - obj is the object to search. - - path is a delimited string, where the delimiter is the given - separator character. - - This function searches for the given path, starting at the given object - and traversing its properties as the path specifies. If a given part of the - path is not found, then this function fails with cson_rc.NotFoundError. - - If it finds the given path, it returns the value by assiging *tgt - to it. If tgt is NULL then this function has no side-effects but - will return 0 if the given path is found within the object, so it can be used - to test for existence without fetching it. - - Returns 0 if it finds an entry, cson_rc.NotFoundError if it finds - no item, and any other non-zero error code on a "real" error. Errors include: - - - obj or path are NULL: cson_rc.ArgError - - - separator is 0, or path is an empty string or contains only - separator characters: cson_rc.RangeError - - - There is an upper limit on how long a single path component may - be (some "reasonable" internal size), and cson_rc.RangeError is - returned if that length is violated. - - - Limitations: - - - It has no way to fetch data from arrays this way. i could - imagine, e.g., a path of "subobj.subArray.0" for - subobj.subArray[0], or "0.3.1" for [0][3][1]. But i'm too - lazy/tired to add this. - - Example usage: - - - Assume we have a JSON structure which abstractly looks like: - - @code - {"subobj":{"subsubobj":{"myValue":[1,2,3]}}} - @endcode - - Out goal is to get the value of myValue. We can do that with: - - @code - cson_value * v = NULL; - int rc = cson_object_fetch_sub( object, &v, "subobj.subsubobj.myValue", '.' ); - @endcode - - Note that because keys in JSON may legally contain a '.', the - separator must be specified by the caller. e.g. the path - "subobj/subsubobj/myValue" with separator='/' is equivalent the - path "subobj.subsubobj.myValue" with separator='.'. The value of 0 - is not legal as a separator character because we cannot - distinguish that use from the real end-of-string without requiring - the caller to also pass in the length of the string. - - Multiple successive separators in the list are collapsed into a - single separator for parsing purposes. e.g. the path "a...b...c" - (separator='.') is equivalent to "a.b.c". - - @see cson_object_get_sub() -*/ -int cson_object_fetch_sub( cson_object const * obj, cson_value ** tgt, char const * path, char separator ); - -/** - Convenience form of cson_object_fetch_sub() which returns NULL if the given - item is not found. -*/ -cson_value * cson_object_get_sub( cson_object const * obj, char const * path, char sep ); - - -/** - An iterator type for traversing object properties. - - Its values must be considered private, not to be touched by client - code. - - @see cson_object_iter_init() - @see cson_object_iter_next() -*/ -struct cson_object_iterator -{ - - /** @internal - The underlying object. - */ - cson_object const * obj; - /** @internal - Current position in the property list. - */ - unsigned int pos; -}; -typedef struct cson_object_iterator cson_object_iterator; - -/** - Empty-initialized cson_object_iterator object. -*/ -#define cson_object_iterator_empty_m {NULL/*obj*/,0/*pos*/} - -/** - Empty-initialized cson_object_iterator object. -*/ -extern const cson_object_iterator cson_object_iterator_empty; - -/** - Initializes the given iterator to point at the start of obj's - properties. Returns 0 on success or cson_rc.ArgError if !obj - or !iter. - - obj must outlive iter, or results are undefined. Results are also - undefined if obj is modified while the iterator is active. - - @see cson_object_iter_next() -*/ -int cson_object_iter_init( cson_object const * obj, cson_object_iterator * iter ); - -/** @struct cson_kvp - -This class represents a key/value pair and is used for storing -object properties. It is opaque to client code, and the public -API only uses this type for purposes of iterating over cson_object -properties using the cson_object_iterator interfaces. -*/ - -typedef struct cson_kvp cson_kvp; - -/** - Returns the next property from the given iterator's object, or NULL - if the end of the property list as been reached. - - Note that the order of object properties is undefined by the API, - and may change from version to version. - - The returned memory belongs to the underlying object and may be - invalidated by any changes to that object. - - Example usage: - - @code - cson_object_iterator it; - cson_object_iter_init( myObject, &it ); // only fails if either arg is 0 - cson_kvp * kvp; - cson_string const * key; - cson_value const * val; - while( (kvp = cson_object_iter_next(&it) ) ) - { - key = cson_kvp_key(kvp); - val = cson_kvp_value(kvp); - ... - } - @endcode - - There is no need to clean up an iterator, as it holds no dynamic resources. - - @see cson_kvp_key() - @see cson_kvp_value() -*/ -cson_kvp * cson_object_iter_next( cson_object_iterator * iter ); - - -/** - Returns the key associated with the given key/value pair, - or NULL if !kvp. The memory is owned by the object which contains - the key/value pair, and may be invalidated by any modifications - to that object. -*/ -cson_string const * cson_kvp_key( cson_kvp const * kvp ); - -/** - Returns the value associated with the given key/value pair, - or NULL if !kvp. The memory is owned by the object which contains - the key/value pair, and may be invalidated by any modifications - to that object. -*/ -cson_value * cson_kvp_value( cson_kvp const * kvp ); - -/** @typedef some unsigned int type cson_size_t - -*/ -typedef unsigned int cson_size_t; - -/** - A generic buffer class. - - They can be used like this: - - @code - cson_buffer b = cson_buffer_empty; - int rc = cson_buffer_reserve( &buf, 100 ); - if( 0 != rc ) { ... allocation error ... } - ... use buf.mem ... - ... then free it up ... - cson_buffer_reserve( &buf, 0 ); - @endcode - - To take over ownership of a buffer's memory: - - @code - void * mem = b.mem; - // mem is b.capacity bytes long, but only b.used - // bytes of it has been "used" by the API. - b = cson_buffer_empty; - @endcode - - The memory now belongs to the caller and must eventually be - free()d. -*/ -struct cson_buffer -{ - /** - The number of bytes allocated for this object. - Use cson_buffer_reserve() to change its value. - */ - cson_size_t capacity; - /** - The number of bytes "used" by this object. It is not needed for - all use cases, and management of this value (if needed) is up - to the client. The cson_buffer public API does not use this - member. The intention is that this can be used to track the - length of strings which are allocated via cson_buffer, since - they need an explicit length and/or null terminator. - */ - cson_size_t used; - - /** - This is a debugging/metric-counting value - intended to help certain malloc()-conscious - clients tweak their memory reservation sizes. - Each time cson_buffer_reserve() expands the - buffer, it increments this value by 1. - */ - cson_size_t timesExpanded; - - /** - The memory allocated for and owned by this buffer. - Use cson_buffer_reserve() to change its size or - free it. To take over ownership, do: - - @code - void * myptr = buf.mem; - buf = cson_buffer_empty; - @endcode - - (You might also need to store buf.used and buf.capacity, - depending on what you want to do with the memory.) - - When doing so, the memory must eventually be passed to free() - to deallocate it. - */ - unsigned char * mem; -}; -/** Convenience typedef. */ -typedef struct cson_buffer cson_buffer; - -/** An empty-initialized cson_buffer object. */ -#define cson_buffer_empty_m {0/*capacity*/,0/*used*/,0/*timesExpanded*/,NULL/*mem*/} -/** An empty-initialized cson_buffer object. */ -extern const cson_buffer cson_buffer_empty; - -/** - Uses cson_output() to append all JSON output to the given buffer - object. The semantics for the (v, opt) parameters, and the return - value, are as documented for cson_output(). buf must be a non-NULL - pointer to a properly initialized buffer (see example below). - - Ownership of buf is not changed by calling this. - - On success 0 is returned and the contents of buf.mem are guaranteed - to be NULL-terminated. On error the buffer might contain partial - contents, and it should not be used except to free its contents. - - On error non-zero is returned. Errors include: - - - Invalid arguments: cson_rc.ArgError - - - Buffer cannot be expanded (runs out of memory): cson_rc.AllocError - - Example usage: - - @code - cson_buffer buf = cson_buffer_empty; - // optional: cson_buffer_reserve(&buf, 1024 * 10); - int rc = cson_output_buffer( myValue, &buf, NULL ); - if( 0 != rc ) { - ... error! ... - } - else { - ... use buffer ... - puts((char const*)buf.mem); - } - // In both cases, we eventually need to clean up the buffer: - cson_buffer_reserve( &buf, 0 ); - // Or take over ownership of its memory: - { - char * mem = (char *)buf.mem; - buf = cson_buffer_empty; - ... - free(mem); - } - @endcode - - @see cson_output() - -*/ -int cson_output_buffer( cson_value const * v, cson_buffer * buf, - cson_output_opt const * opt ); - -/** - This works identically to cson_parse_string(), but takes a - cson_buffer object as its input. buf->used bytes of buf->mem are - assumed to be valid JSON input, but it need not be NUL-terminated - (we only read up to buf->used bytes). The value of buf->used is - assumed to be the "string length" of buf->mem, i.e. not including - the NUL terminator. - - Returns 0 on success, non-0 on error. - - See cson_parse() for the semantics of the tgt, opt, and err - parameters. -*/ -int cson_parse_buffer( cson_value ** tgt, cson_buffer const * buf, - cson_parse_opt const * opt, cson_parse_info * err ); - - -/** - Reserves the given amount of memory for the given buffer object. - - If n is 0 then buf->mem is freed and its state is set to - NULL/0 values. - - If buf->capacity is less than or equal to n then 0 is returned and - buf is not modified. - - If n is larger than buf->capacity then buf->mem is (re)allocated - and buf->capacity contains the new length. Newly-allocated bytes - are filled with zeroes. - - On success 0 is returned. On error non-0 is returned and buf is not - modified. - - buf->mem is owned by buf and must eventually be freed by passing an - n value of 0 to this function. - - buf->used is never modified by this function. -*/ -int cson_buffer_reserve( cson_buffer * buf, cson_size_t n ); - -/** - Fills all bytes of the given buffer with the given character. - Returns the number of bytes set (buf->capacity), or 0 if - !buf or buf has no memory allocated to it. -*/ -cson_size_t cson_buffer_fill( cson_buffer * buf, char c ); - -/** - Uses a cson_data_source_f() function to buffer input into a - cson_buffer. - - dest must be a non-NULL, initialized (though possibly empty) - cson_buffer object. Its contents, if any, will be overwritten by - this function, and any memory it holds might be re-used. - - The src function is called, and passed the state parameter, to - fetch the input. If it returns non-0, this function returns that - error code. src() is called, possibly repeatedly, until it reports - that there is no more data. - - Whether or not this function succeeds, dest still owns any memory - pointed to by dest->mem, and the client must eventually free it by - calling cson_buffer_reserve(dest,0). - - dest->mem might (and possibly will) be (re)allocated by this - function, so any pointers to it held from before this call might be - invalidated by this call. - - On error non-0 is returned and dest has almost certainly been - modified but its state must be considered incomplete. - - Errors include: - - - dest or src are NULL (cson_rc.ArgError) - - - Allocation error (cson_rc.AllocError) - - - src() returns an error code - - Whether or not the state parameter may be NULL depends on - the src implementation requirements. - - On success dest will contain the contents read from the input - source. dest->used will be the length of the read-in data, and - dest->mem will point to the memory. dest->mem is automatically - NUL-terminated if this function succeeds, but dest->used does not - count that terminator. On error the state of dest->mem must be - considered incomplete, and is not guaranteed to be NUL-terminated. - - Example usage: - - @code - cson_buffer buf = cson_buffer_empty; - int rc = cson_buffer_fill_from( &buf, - cson_data_source_FILE, - stdin ); - if( rc ) - { - fprintf(stderr,"Error %d (%s) while filling buffer.\n", - rc, cson_rc_string(rc)); - cson_buffer_reserve( &buf, 0 ); - return ...; - } - ... use the buf->mem ... - ... clean up the buffer ... - cson_buffer_reserve( &buf, 0 ); - @endcode - - To take over ownership of the buffer's memory, do: - - @code - void * mem = buf.mem; - buf = cson_buffer_empty; - @endcode - - In which case the memory must eventually be passed to free() to - free it. -*/ -int cson_buffer_fill_from( cson_buffer * dest, cson_data_source_f src, void * state ); - - -/** - Increments the reference count for the given value. This is a - low-level operation and should not normally be used by client code - without understanding exactly what side-effects it introduces. - Mis-use can lead to premature destruction or cause a value instance - to never be properly destructed (i.e. a memory leak). - - This function is probably only useful for the following cases: - - - You want to hold a reference to a value which is itself contained - in one or more containers, and you need to be sure that your - reference outlives the container(s) and/or that you can free your - copy of the reference without invaliding any references to the same - value held in containers. - - - You want to implement "value sharing" behaviour without using an - object or array to contain the shared value. This can be used to - ensure the lifetime of the shared value instance. Each sharing - point adds a reference and simply passed the value to - cson_value_free() when they're done. The object will be kept alive - for other sharing points which added a reference. - - Normally any such value handles would be invalidated when the - parent container(s) is/are cleaned up, but this function can be - used to effectively delay the cleanup. - - This function, at its lowest level, increments the value's - reference count by 1. - - To decrement the reference count, pass the value to - cson_value_free(), after which the value must be considered, from - the perspective of that client code, to be destroyed (though it - will not be if there are still other live references to - it). cson_value_free() will not _actually_ destroy the value until - its reference count drops to 0. - - Returns 0 on success. The only error conditions are if v is NULL - (cson_rc.ArgError) or if the reference increment would overflow - (cson_rc.RangeError). In theory a client would get allocation - errors long before the reference count could overflow (assuming - those reference counts come from container insertions, as opposed - to via this function). - - Insider notes which clients really need to know: - - For shared/constant value instances, such as those returned by - cson_value_true() and cson_value_null(), this function has no side - effects - it does not actually modify the reference count because - (A) those instances are shared across all client code and (B) those - objects are static and never get cleaned up. However, that is an - implementation detail which client code should not rely on. In - other words, if you call cson_value_add_reference() 3 times using - the value returned by cson_value_true() (which is incidentally a - shared cson_value instance), you must eventually call - cson_value_free() 3 times to (semantically) remove those - references. However, internally the reference count for that - specific cson_value instance will not be modified and those - objects will never be freed (they're stack-allocated). - - It might be interesting to note that newly-created objects - have a reference count of 0 instead of 1. This is partly because - if the initial reference is counted then it makes ownership - problematic when inserting values into containers. e.g. consider the - following code: - - @code - // ACHTUNG: this code is hypothetical and does not reflect - // what actually happens! - cson_value * v = - cson_value_new_integer( 42 ); // v's refcount = 1 - cson_array_append( myArray, v ); // v's refcount = 2 - @endcode - - If that were the case, the client would be forced to free his own - reference after inserting it into the container (which is a bit - counter-intuitive as well as intrusive). It would look a bit like - the following and would have to be done after every create/insert - operation: - - @code - // ACHTUNG: this code is hypothetical and does not reflect - // what actually happens! - cson_array_append( myArray, v ); // v's refcount = 2 - cson_value_free( v ); // v's refcount = 1 - @endcode - - (As i said: it's counter-intuitive and intrusive.) - - Instead, values start with a refcount of 0 and it is only increased - when the value is added to an object/array container or when this - function is used to manually increment it. cson_value_free() treats - a refcount of 0 or 1 equivalently, destroying the value - instance. The only semantic difference between 0 and 1, for - purposes of cleaning up, is that a value with a non-0 refcount has - been had its refcount adjusted, whereas a 0 refcount indicates a - fresh, "unowned" reference. -*/ -int cson_value_add_reference( cson_value * v ); - -#if 0 -/** - DO NOT use this unless you know EXACTLY what you're doing. - It is only in the public API to work around a couple corner - cases involving extracting child elements and discarding - their parents. - - This function sets v's reference count to the given value. - It does not clean up the object if rc is 0. - - Returns 0 on success, non-0 on error. -*/ -int cson_value_refcount_set( cson_value * v, unsigned short rc ); -#endif - -/** - Deeply copies a JSON value, be it an object/array or a "plain" - value (e.g. number/string/boolean). If cv is not NULL then this - function makes a deep clone of it and returns that clone. Ownership - of the clone is transfered to the caller, who must eventually free - the value using cson_value_free() or add it to a container - object/array to transfer ownership to the container. The returned - object will be of the same logical type as orig. - - ACHTUNG: if orig contains any cyclic references at any depth level - this function will endlessly recurse. (Having _any_ cyclic - references violates this library's requirements.) - - Returns NULL if orig is NULL or if cloning fails. Assuming that - orig is in a valid state, the only "likely" error case is that an - allocation fails while constructing the clone. In other words, if - cloning fails due to something other than an allocation error then - either orig is in an invalid state or there is a bug. -*/ -cson_value * cson_value_clone( cson_value const * orig ); - -/* LICENSE - -This software's source code, including accompanying documentation and -demonstration applications, are licensed under the following -conditions... - -Certain files are imported from external projects and have their own -licensing terms. Namely, the JSON_parser.* files. See their files for -their official licenses, but the summary is "do what you want [with -them] but leave the license text and copyright in place." - -The author (Stephan G. Beal [http://wanderinghorse.net/home/stephan/]) -explicitly disclaims copyright in all jurisdictions which recognize -such a disclaimer. In such jurisdictions, this software is released -into the Public Domain. - -In jurisdictions which do not recognize Public Domain property -(e.g. Germany as of 2011), this software is Copyright (c) 2011 by -Stephan G. Beal, and is released under the terms of the MIT License -(see below). - -In jurisdictions which recognize Public Domain property, the user of -this software may choose to accept it either as 1) Public Domain, 2) -under the conditions of the MIT License (see below), or 3) under the -terms of dual Public Domain/MIT License conditions described here, as -they choose. - -The MIT License is about as close to Public Domain as a license can -get, and is described in clear, concise terms at: - - http://en.wikipedia.org/wiki/MIT_License - -The full text of the MIT License follows: - --- -Copyright (c) 2011 Stephan G. Beal (http://wanderinghorse.net/home/stephan/) - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - ---END OF MIT LICENSE-- - -For purposes of the above license, the term "Software" includes -documentation and demonstration source code which accompanies -this software. ("Accompanies" = is contained in the Software's -primary public source code repository.) - -*/ - -#if defined(__cplusplus) -} /*extern "C"*/ -#endif - -#endif /* WANDERINGHORSE_NET_CSON_H_INCLUDED */ -/* end file include/wh/cson/cson.h */ -/* begin file include/wh/cson/cson_sqlite3.h */ -/** @file cson_sqlite3.h - -This file contains cson's public sqlite3-to-JSON API declarations -and API documentation. If CSON_ENABLE_SQLITE3 is not defined, -or is defined to 0, then including this file will have no side-effects -other than defining CSON_ENABLE_SQLITE3 (if it was not defined) to 0 -and defining a few include guard macros. i.e. if CSON_ENABLE_SQLITE3 -is not set to a true value then the API is not visible. - -This API requires that <sqlite3.h> be in the INCLUDES path and that -the client eventually link to (or directly embed) the sqlite3 library. -*/ -#if !defined(WANDERINGHORSE_NET_CSON_SQLITE3_H_INCLUDED) -#define WANDERINGHORSE_NET_CSON_SQLITE3_H_INCLUDED 1 -#if !defined(CSON_ENABLE_SQLITE3) -# if defined(DOXYGEN) -#define CSON_ENABLE_SQLITE3 1 -# else -#define CSON_ENABLE_SQLITE3 1 -# endif -#endif - -#if CSON_ENABLE_SQLITE3 /* we do this here for the sake of the amalgamation build */ -#include <sqlite3.h> - -#if defined(__cplusplus) -extern "C" { -#endif - -/** - Converts a single value from a single 0-based column index to its JSON - equivalent. - - On success it returns a new JSON value, which will have a different concrete - type depending on the field type reported by sqlite3_column_type(st,col): - - Integer, double, null, or string (TEXT and BLOB data, though not - all blob data is legal for a JSON string). - - st must be a sqlite3_step()'d row and col must be a 0-based column - index within that result row. - */ -cson_value * cson_sqlite3_column_to_value( sqlite3_stmt * st, int col ); - -/** - Creates a JSON Array object containing the names of all columns - of the given prepared statement handle. - - Returns a new array value on success, which the caller owns. Its elements - are in the same order as in the underlying query. - - On error NULL is returned. - - st is not traversed or freed by this function - only the column - count and names are read. -*/ -cson_value * cson_sqlite3_column_names( sqlite3_stmt * st ); - -/** - Creates a JSON Object containing key/value pairs corresponding - to the result columns in the current row of the given statement - handle. st must be a sqlite3_step()'d row result. - - On success a new Object is returned which is owned by the - caller. On error NULL is returned. - - cson_sqlite3_column_to_value() is used to convert each column to a - JSON value, and the column names are taken from - sqlite3_column_name(). -*/ -cson_value * cson_sqlite3_row_to_object( sqlite3_stmt * st ); - -/** - Similar to cson_sqlite3_row_to_object(), but creates an Array - value which contains the JSON-form values of the given result - set row. -*/ -cson_value * cson_sqlite3_row_to_array( sqlite3_stmt * st ); -/** - Converts the results of an sqlite3 SELECT statement to JSON, - in the form of a cson_value object tree. - - st must be a prepared, but not yet traversed, SELECT query. - tgt must be a pointer to NULL (see the example below). If - either of those arguments are NULL, cson_rc.ArgError is returned. - - This walks the query results and returns a JSON object which - has a different structure depending on the value of the 'fat' - argument. - - - If 'fat' is 0 then the structure is: - - @code - { - "columns":["colName1",..."colNameN"], - "rows":[ - [colVal0, ... colValN], - [colVal0, ... colValN], - ... - ] - } - @endcode - - In the "non-fat" format the order of the columns and row values is - guaranteed to be the same as that of the underlying query. - - If 'fat' is not 0 then the structure is: - - @code - { - "columns":["colName1",..."colNameN"], - "rows":[ - {"colName1":value1,..."colNameN":valueN}, - {"colName1":value1,..."colNameN":valueN}, - ... - ] - } - @endcode - - In the "fat" format, the order of the "columns" entries is guaranteed - to be the same as the underlying query fields, but the order - of the keys in the "rows" might be different and might in fact - change when passed through different JSON implementations, - depending on how they implement object key/value pairs. - - On success it returns 0 and assigns *tgt to a newly-allocated - JSON object tree (using the above structure), which the caller owns. - If the query returns no rows, the "rows" value will be an empty - array, as opposed to null. - - On error non-0 is returned and *tgt is not modified. - - The error code cson_rc.IOError is used to indicate a db-level - error, and cson_rc.TypeError is returned if sqlite3_column_count(st) - returns 0 or less (indicating an invalid or non-SELECT statement). - - The JSON data types are determined by the column type as reported - by sqlite3_column_type(): - - SQLITE_INTEGER: integer - - SQLITE_FLOAT: double - - SQLITE_TEXT or SQLITE_BLOB: string, and this will only work if - the data is UTF8 compatible. - - If the db returns a literal or SQL NULL for a value it is converted - to a JSON null. If it somehow finds a column type it cannot handle, - the value is also converted to a NULL in the output. - - Example - - @code - cson_value * json = NULL; - int rc = cson_sqlite3_stmt_to_json( myStatement, &json, 1 ); - if( 0 != rc ) { ... error ... } - else { - cson_output_FILE( json, stdout, NULL ); - cson_value_free( json ); - } - @endcode -*/ -int cson_sqlite3_stmt_to_json( sqlite3_stmt * st, cson_value ** tgt, char fat ); - -/** - A convenience wrapper around cson_sqlite3_stmt_to_json(), which - takes SQL instead of a sqlite3_stmt object. It has the same - return value and argument semantics as that function. -*/ -int cson_sqlite3_sql_to_json( sqlite3 * db, cson_value ** tgt, char const * sql, char fat ); - -#if defined(__cplusplus) -} /*extern "C"*/ -#endif - -#endif /* CSON_ENABLE_SQLITE3 */ -#endif /* WANDERINGHORSE_NET_CSON_SQLITE3_H_INCLUDED */ -/* end file include/wh/cson/cson_sqlite3.h */ Index: src/db.c ================================================================== --- src/db.c +++ src/db.c @@ -56,41 +56,32 @@ ** Call this routine when a database error occurs. */ static void db_err(const char *zFormat, ...){ va_list ap; char *z; - int rc = 1; static const char zRebuildMsg[] = "If you have recently updated your fossil executable, you might\n" "need to run \"fossil all rebuild\" to bring the repository\n" "schemas up to date.\n"; va_start(ap, zFormat); z = vmprintf(zFormat, ap); va_end(ap); - if( g.json.isJsonMode ){ - json_err( 0, z, 1 ); - if( g.isHTTP ){ - rc = 0 /* avoid HTTP 500 */; - } - }else{ - if( g.xferPanic ){ - cgi_reset_content(); - @ error Database\serror:\s%F(z) - cgi_reply(); - } - if( g.cgiOutput ){ - g.cgiOutput = 0; - cgi_printf("<h1>Database Error</h1>\n" - "<pre>%h</pre><p>%s</p>", z, zRebuildMsg); - cgi_reply(); - }else{ - fprintf(stderr, "%s: %s\n\n%s", fossil_nameofexe(), z, zRebuildMsg); - } - } - free(z); + if( g.xferPanic ){ + cgi_reset_content(); + @ error Database\serror:\s%F(z) + cgi_reply(); + } + if( g.cgiOutput ){ + g.cgiOutput = 0; + cgi_printf("<h1>Database Error</h1>\n" + "<pre>%h</pre><p>%s</p>", z, zRebuildMsg); + cgi_reply(); + }else{ + fprintf(stderr, "%s: %s\n\n%s", fossil_nameofexe(), z, zRebuildMsg); + } db_force_rollback(); - fossil_exit(rc); + fossil_exit(1); } static int nBegin = 0; /* Nesting depth of BEGIN */ static int doRollback = 0; /* True to force a rollback */ static int nCommitHook = 0; /* Number of commit hooks */ @@ -751,20 +742,20 @@ /* * * Returns TRUE if zTable exists in the local database. */ static int db_local_table_exists(const char *zTable){ return db_exists("SELECT 1 FROM %s.sqlite_master" - " WHERE name=='%s'", + " WHERE name=='%s' /*scan*/", db_name("localdb"), zTable); } /* ** Returns TRUE if zColumn exists in zTable in the local database. */ static int db_local_column_exists(const char *zTable, const char *zColumn){ return db_exists("SELECT 1 FROM %s.sqlite_master" - " WHERE name=='%s' AND sql GLOB '* %s *'", + " WHERE name=='%s' AND sql GLOB '* %s *' /*scan*/", db_name("localdb"), zTable, zColumn); } /* ** If zDbName is a valid local database file, open it and return @@ -953,11 +944,10 @@ ** Verify that the repository schema is correct. If it is not correct, ** issue a fatal error and die. */ void db_verify_schema(void){ if( db_schema_is_outofdate() ){ - g.json.resultCode = FSL_JSON_E_DB_NEEDS_REBUILD; fossil_warning("incorrect repository schema version"); fossil_warning("your repository has schema version \"%s\" " "but this binary expects version \"%s\"", db_get("aux-schema",0), AUX_SCHEMA); fossil_fatal("run \"fossil rebuild\" to fix this problem"); Index: src/diff.c ================================================================== --- src/diff.c +++ src/diff.c @@ -578,10 +578,206 @@ free(c.aFrom); free(c.aTo); return c.aEdit; } } + +/* +** Copy a line with a limit. Used for side-by-side diffs to enforce a maximum +** line length limit. +*/ +static char *copylimline(char *out, DLine *dl, int lim){ + int len; + len = dl->h & LENGTH_MASK; + if( lim && len > lim ){ + memcpy(out, dl->z, lim-3); + strcpy(&out[lim-3], "..."); + }else{ + memcpy(out, dl->z, len); + out[len] = '\0'; + } + return out; +} + +/* +** Output table body of a side-by-side diff. Prior to the call, the caller +** should have output: +** <table class="sbsdiff"> +** <tr><th colspan="2" class="diffhdr">Old title</th><th/> +** <th colspan="2" class="diffhdr">New title</th></tr> +** +** And after the call, it should output: +** </table> +** +** Some good reference diffs in the fossil repository for testing: +** /vdiff?from=080d27a&to=4b0f813&detail=1 +** /vdiff?from=636804745b&to=c1d78e0556&detail=1 +** /vdiff?from=c0b6c28d29&to=25169506b7&detail=1 +** /vdiff?from=e3d022dffa&to=48bcfbd47b&detail=1 +*/ +int html_sbsdiff( + Blob *pA_Blob, /* FROM file */ + Blob *pB_Blob, /* TO file */ + int nContext, /* Amount of context to unified diff */ + int ignoreEolWs /* Ignore whitespace at the end of lines */ +){ + DContext c; + int i; + int iFrom, iTo; + char *linebuf; + int collim=0; /* Currently not settable; allows a column limit for diffs */ + int allowExp=0; /* Currently not settable; (dis)allow expansion of rows */ + + /* Prepare the input files */ + memset(&c, 0, sizeof(c)); + c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob), + &c.nFrom, ignoreEolWs); + c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob), + &c.nTo, ignoreEolWs); + if( c.aFrom==0 || c.aTo==0 ){ + free(c.aFrom); + free(c.aTo); + /* Note: This would be generated within a table. */ + @ <p class="generalError" style="white-space: nowrap">cannot compute + @ difference between binary files</p> + return 0; + } + + collim = collim < 4 ? 0 : collim; + + /* Compute the difference */ + diff_all(&c); + + linebuf = fossil_malloc(LENGTH_MASK+1); + if( !linebuf ){ + free(c.aFrom); + free(c.aTo); + free(c.aEdit); + return 0; + } + + iFrom=iTo=0; + i=0; + while( i<c.nEdit ){ + int j; + /* Copied lines */ + for( j=0; j<c.aEdit[i]; j++){ + int len; + int dist; + + /* Hide lines which are copied and are further away from block boundaries + ** than nConext lines. For each block with hidden lines, show a row + ** notifying the user about the hidden rows. + */ + if( j<nContext || j>c.aEdit[i]-nContext-1 ){ + @ <tr> + }else if( j==nContext && j<c.aEdit[i]-nContext-1 ){ + @ <tr> + @ <td class="meta" colspan="5" style="white-space: nowrap;"> + @ %d(c.aEdit[i]-2*nContext) hidden lines</td> + @ </tr> + if( !allowExp ) + continue; + @ <tr style="display:none;"> + }else{ + if( !allowExp ) + continue; + @ <tr style="display:none;"> + } + + copylimline(linebuf, &c.aFrom[iFrom+j], collim); + @ <td class="lineno">%d(iFrom+j+1)</td> + @ <td class="srcline">%h(linebuf)</td> + + @ <td> </td> + + copylimline(linebuf, &c.aTo[iTo+j], collim); + @ <td class="lineno">%d(iTo+j+1)</td> + @ <td class="srcline">%h(linebuf)</td> + + @ </tr> + } + iFrom+=c.aEdit[i]; + iTo+=c.aEdit[i]; + + if( c.aEdit[i+1]!=0 && c.aEdit[i+2]!=0 ){ + int lim; + lim = c.aEdit[i+1] > c.aEdit[i+2] ? c.aEdit[i+1] : c.aEdit[i+2]; + + /* Assume changed lines */ + for( j=0; j<lim; j++ ){ + int len; + @ <tr> + + if( j<c.aEdit[i+1] ){ + copylimline(linebuf, &c.aFrom[iFrom+j], collim); + @ <td class="changed lineno">%d(iFrom+j+1)</td> + @ <td class="changed srcline">%h(linebuf)</td> + }else{ + @ <td colspan="2" class="changedvoid"/> + } + + @ <td class="changed">|</td> + + if( j<c.aEdit[i+2] ){ + copylimline(linebuf, &c.aTo[iTo+j], collim); + @ <td class="changed lineno">%d(iTo+j+1)</td> + @ <td class="changed srcline">%h(linebuf)</td> + }else{ + @ <td colspan="2" class="changedvoid"/> + } + + @ </tr> + } + iFrom+=c.aEdit[i+1]; + iTo+=c.aEdit[i+2]; + }else{ + + /* Process deleted lines */ + for( j=0; j<c.aEdit[i+1]; j++ ){ + int len; + @ <tr> + + copylimline(linebuf, &c.aFrom[iFrom+j], collim); + @ <td class="removed lineno">%d(iFrom+j+1)</td> + @ <td class="removed srcline">%h(linebuf)</td> + + @ <td>&lt;</td> + + @ <td colspan="2" class="removedvoid"/> + + @ </tr> + } + iFrom+=c.aEdit[i+1]; + + /* Process inserted lines */ + for( j=0; j<c.aEdit[i+2]; j++ ){ + int len; + @ <tr> + @ <td colspan="2" class="addedvoid"/> + + @ <td>&gt;</td> + + copylimline(linebuf, &c.aTo[iTo+j], collim); + @ <td class="added lineno">%d(iTo+j+1)</td> + @ <td class="added srcline">%h(linebuf)</td> + + @ </tr> + } + iTo+=c.aEdit[i+2]; + } + + i+=3; + } + + free(linebuf); + free(c.aFrom); + free(c.aTo); + free(c.aEdit); + return 1; +} + /* ** COMMAND: test-rawdiff */ void test_rawdiff_cmd(void){ @@ -711,11 +907,10 @@ p->c.nEdit = 0; p->c.nEditAlloc = 0; /* Clear out the from file */ free(p->c.aFrom); - blob_zero(pParent); /* Return no errors */ return 0; } Index: src/file.c ================================================================== --- src/file.c +++ src/file.c @@ -34,11 +34,11 @@ ** The file status information from the most recent stat() call. ** ** Use _stati64 rather than stat on windows, in order to handle files ** larger than 2GB. */ -#if defined(_WIN32) && defined(__MSVCRT__) +#if defined(_WIN32) && (defined(__MSVCRT__) || defined(_MSC_VER)) # define stat _stati64 #endif /* ** On Windows S_ISLNK always returns FALSE. */ Index: src/http.c ================================================================== --- src/http.c +++ src/http.c @@ -136,11 +136,11 @@ Blob login; /* The login card */ Blob payload; /* The complete payload including login card */ Blob hdr; /* The HTTP request header */ int closeConnection; /* True to close the connection when done */ int iLength; /* Length of the reply payload */ - int rc; /* Result code */ + int rc = 0; /* Result code */ int iHttpVersion; /* Which version of HTTP protocol server uses */ char *zLine; /* A single line of the reply header */ int i; /* Loop counter */ int isError = 0; /* True if the reply is an error message */ int isCompressed = 1; /* True if the reply is compressed */ Index: src/http_socket.c ================================================================== --- src/http_socket.c +++ src/http_socket.c @@ -200,16 +200,16 @@ /* ** Receive content back from the open socket connection. */ size_t socket_receive(void *NotUsed, void *pContent, size_t N){ - size_t got; + ssize_t got; size_t total = 0; while( N>0 ){ got = recv(iSocket, pContent, N, 0); if( got<=0 ) break; - total += got; - N -= got; + total += (size_t)got; + N -= (size_t)got; pContent = (void*)&((char*)pContent)[got]; } return total; } Index: src/http_ssl.c ================================================================== --- src/http_ssl.c +++ src/http_ssl.c @@ -97,10 +97,11 @@ ** Call this routine once before any other use of the SSL interface. ** This routine does initial configuration of the SSL module. */ void ssl_global_init(void){ const char *zCaSetting = 0, *zCaFile = 0, *zCaDirectory = 0; + const char *identityFile; if( sslIsInit==0 ){ SSL_library_init(); SSL_load_error_strings(); ERR_load_BIO_strings(); @@ -135,19 +136,26 @@ fossil_fatal("Failed to use CA root certificates from " "ssl-ca-location '%s'", zCaSetting); } } - /* Load client SSL identity, preferring the filename specified on the command line */ - const char *identityFile = ( g.zSSLIdentity!= 0) ? g.zSSLIdentity : db_get("ssl-identity", 0); + /* Load client SSL identity, preferring the filename specified on the + ** command line */ + if( g.zSSLIdentity!=0 ){ + identityFile = g.zSSLIdentity; + }else{ + identityFile = db_get("ssl-identity", 0); + } if( identityFile!=0 && identityFile[0]!='\0' ){ - if( SSL_CTX_use_certificate_file(sslCtx, identityFile, SSL_FILETYPE_PEM)!= 1 - || SSL_CTX_use_PrivateKey_file(sslCtx, identityFile, SSL_FILETYPE_PEM)!=1 ){ + if( SSL_CTX_use_certificate_file(sslCtx,identityFile,SSL_FILETYPE_PEM)!=1 + || SSL_CTX_use_PrivateKey_file(sslCtx,identityFile,SSL_FILETYPE_PEM)!=1 + ){ fossil_fatal("Could not load SSL identity from %s", identityFile); } } - /* Register a callback to tell the user what to do when the server asks for a cert */ + /* Register a callback to tell the user what to do when the server asks + ** for a cert */ SSL_CTX_set_client_cert_cb(sslCtx, ssl_client_cert_callback); sslIsInit = 1; } } @@ -184,17 +192,20 @@ ** Return the number of errors. */ int ssl_open(void){ X509 *cert; int hasSavedCertificate = 0; -char *connStr ; + int trusted = 0; + char *connStr ; + unsigned long e; + ssl_global_init(); /* Get certificate for current server from global config and * (if we have it in config) add it to certificate store. */ - cert = ssl_get_certificate(); + cert = ssl_get_certificate(&trusted); if ( cert!=NULL ){ X509_STORE_add_cert(SSL_CTX_get_cert_store(sslCtx), cert); X509_free(cert); hasSavedCertificate = 1; } @@ -232,11 +243,11 @@ ssl_set_errmsg("No SSL certificate was presented by the peer"); ssl_close(); return 1; } - if( SSL_get_verify_result(ssl) != X509_V_OK ){ + if( trusted<=0 && (e = SSL_get_verify_result(ssl)) != X509_V_OK ){ char *desc, *prompt; char *warning = ""; Blob ans; BIO *mem; unsigned char md[32]; @@ -258,19 +269,22 @@ if( hasSavedCertificate ){ warning = "WARNING: Certificate doesn't match the " "saved certificate for this host!"; } - prompt = mprintf("\nUnknown SSL certificate:\n\n%s\n\n%s\n" - "Either:\n" - " * verify the certificate is correct using the " - "SHA1 fingerprint above\n" - " * use the global ssl-ca-location setting to specify your CA root\n" - " certificates list\n\n" - "If you are not expecting this message, answer no and " - "contact your server\nadministrator.\n\n" - "Accept certificate [a=always/y/N]? ", desc, warning); + prompt = mprintf("\nSSL verification failed: %s\n" + "Certificate received: \n\n%s\n\n%s\n" + "Either:\n" + " * verify the certificate is correct using the " + "SHA1 fingerprint above\n" + " * use the global ssl-ca-location setting to specify your CA root\n" + " certificates list\n\n" + "If you are not expecting this message, answer no and " + "contact your server\nadministrator.\n\n" + "Accept certificate for host %s [a=always/y/N]? ", + X509_verify_cert_error_string(e), desc, warning, + g.urlName); BIO_free(mem); prompt_user(prompt, &ans); free(prompt); if( blob_str(&ans)[0]!='y' && blob_str(&ans)[0]!='a' ) { @@ -278,33 +292,40 @@ ssl_set_errmsg("SSL certificate declined"); ssl_close(); return 1; } if( blob_str(&ans)[0]=='a' ) { - ssl_save_certificate(cert); + if ( trusted==0 ){ + Blob ans2; + prompt_user("\nSave this certificate as fully trusted [a=always/N]? ", + &ans2); + trusted = (blob_str(&ans2)[0]=='a'); + blob_reset(&ans2); + } + ssl_save_certificate(cert, trusted); } blob_reset(&ans); } /* Set the Global.zIpAddr variable to the server we are talking to. ** This is used to populate the ipaddr column of the rcvfrom table, ** if any files are received from the server. */ { - /* IPv4 only code */ - const unsigned char *ip = (const unsigned char *) BIO_get_conn_ip(iBio); - g.zIpAddr = mprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + /* IPv4 only code */ + const unsigned char *ip = (const unsigned char *) BIO_get_conn_ip(iBio); + g.zIpAddr = mprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); } X509_free(cert); return 0; } /* ** Save certificate to global config. */ -void ssl_save_certificate(X509 *cert){ +void ssl_save_certificate(X509 *cert, int trusted){ BIO *mem; char *zCert, *zHost; mem = BIO_new(BIO_s_mem()); PEM_write_bio_X509(mem, cert); @@ -311,27 +332,37 @@ BIO_write(mem, "", 1); /* nul-terminate mem buffer */ BIO_get_mem_data(mem, &zCert); zHost = mprintf("cert:%s", g.urlName); db_set(zHost, zCert, 1); free(zHost); + zHost = mprintf("trusted:%s", g.urlName); + db_set_int(zHost, trusted, 1); + free(zHost); BIO_free(mem); } /* ** Get certificate for g.urlName from global config. ** Return NULL if no certificate found. */ -X509 *ssl_get_certificate(void){ +X509 *ssl_get_certificate(int *pTrusted){ char *zHost, *zCert; BIO *mem; X509 *cert; zHost = mprintf("cert:%s", g.urlName); zCert = db_get(zHost, NULL); free(zHost); if ( zCert==NULL ) return NULL; + + if ( pTrusted!=0 ){ + zHost = mprintf("trusted:%s", g.urlName); + *pTrusted = db_get_int(zHost, 0); + free(zHost); + } + mem = BIO_new(BIO_s_mem()); BIO_puts(mem, zCert); cert = PEM_read_bio_X509(mem, NULL, 0, NULL); free(zCert); BIO_free(mem); Index: src/info.c ================================================================== --- src/info.c +++ src/info.c @@ -276,10 +276,40 @@ @ %h(blob_str(&out)) blob_reset(&from); blob_reset(&to); blob_reset(&out); } + + +/* +** Write the difference between two RIDs to the output +*/ +static void generate_sbsdiff(const char *zFrom, const char *zTo){ + int fromid; + int toid; + Blob from, to; + if( zFrom ){ + fromid = uuid_to_rid(zFrom, 0); + content_get(fromid, &from); + }else{ + blob_zero(&from); + } + if( zTo ){ + toid = uuid_to_rid(zTo, 0); + content_get(toid, &to); + }else{ + blob_zero(&to); + } + @ <table class="sbsdiff"> + @ <tr><th colspan="2" class="diffhdr">Old (%S(zFrom))</th><th/> + @ <th colspan="2" class="diffhdr">New (%S(zTo))</th></tr> + html_sbsdiff(&from, &to, 5, 1); + @ </table> + blob_reset(&from); + blob_reset(&to); +} + /* ** Write a line of web-page output that shows changes that have occurred ** to a file between two check-ins. */ @@ -287,10 +317,11 @@ const char *zName, /* Name of the file that has changed */ const char *zOld, /* blob.uuid before change. NULL for added files */ const char *zNew, /* blob.uuid after change. NULL for deletes */ const char *zOldName, /* Prior name. NULL if no name change. */ int showDiff, /* Show edit diffs if true */ + int sideBySide, /* Show diffs side-by-side */ int mperm /* executable or symlink permission for zNew */ ){ if( !g.perm.History ){ if( zNew==0 ){ @ <p>Deleted %h(zName)</p> @@ -329,13 +360,17 @@ }else{ @ <p>Added <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a> @ version <a href="%s(g.zTop)/artifact/%s(zNew)">[%S(zNew)]</a> } if( showDiff ){ - @ <blockquote><pre> - append_diff(zOld, zNew); - @ </pre></blockquote> + if( sideBySide ){ + generate_sbsdiff(zOld, zNew); + }else{ + @ <blockquote><pre> + append_diff(zOld, zNew); + @ </pre></blockquote> + } }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){ @ &nbsp;&nbsp; @ <a href="%s(g.zTop)/fdiff?v1=%S(zOld)&amp;v2=%S(zNew)">[diff]</a> } @ </p> @@ -361,10 +396,11 @@ void ci_page(void){ Stmt q; int rid; int isLeaf; int showDiff; + int sideBySide; const char *zName; /* Name of the checkin to be displayed */ const char *zUuid; /* UUID of zName */ const char *zParent; /* UUID of the parent checkin (if any) */ login_check_credentials(); @@ -390,10 +426,11 @@ " FROM blob, event" " WHERE blob.rid=%d" " AND event.objid=%d", rid, rid ); + sideBySide = atoi(PD("sbs","1")); if( db_step(&q)==SQLITE_ROW ){ const char *zUuid = db_column_text(&q, 0); char *zTitle = mprintf("Check-in [%.10s]", zUuid); char *zEUser, *zEComment; const char *zUser; @@ -511,12 +548,24 @@ showDiff = g.zPath[0]!='c'; if( db_get_boolean("show-version-diffs", 0)==0 ){ showDiff = !showDiff; if( showDiff ){ @ <a href="%s(g.zTop)/vinfo/%T(zName)">[hide&nbsp;diffs]</a> + @ &nbsp;&nbsp; + if( sideBySide ){ + @ <a href="%s(g.zTop)/ci/%T(zName)?sbs=0"> + @ [show&nbsp;1-pane&nbsp;diffs]</a> + }else{ + @ <a href="%s(g.zTop)/ci/%T(zName)?sbs=1"> + @ [show&nbsp;2-pane&nbsp;diffs]</a> + } }else{ - @ <a href="%s(g.zTop)/ci/%T(zName)">[show&nbsp;diffs]</a> + @ <a href="%s(g.zTop)/ci/%T(zName)?sbs=0"> + @ [show&nbsp;1-pane&nbsp;diffs]</a> + @ &nbsp;&nbsp; + @ <a href="%s(g.zTop)/ci/%T(zName)?sbs=1"> + @ [show&nbsp;2-pane&nbsp;diffs]</a> } }else{ if( showDiff ){ @ <a href="%s(g.zTop)/ci/%T(zName)">[hide&nbsp;diffs]</a> }else{ @@ -540,11 +589,12 @@ const char *zName = db_column_text(&q,0); int mperm = db_column_int(&q, 1); const char *zOld = db_column_text(&q,2); const char *zNew = db_column_text(&q,3); const char *zOldName = db_column_text(&q, 4); - append_file_change_line(zName, zOld, zNew, zOldName, showDiff, mperm); + append_file_change_line(zName, zOld, zNew, zOldName, showDiff, + sideBySide, mperm); } db_finalize(&q); } style_footer(); } @@ -690,17 +740,18 @@ } /* ** WEBPAGE: vdiff -** URL: /vdiff?from=UUID&amp;to=UUID&amp;detail=BOOLEAN +** URL: /vdiff?from=UUID&amp;to=UUID&amp;detail=BOOLEAN;sbs=BOOLEAN ** ** Show all differences between two checkins. */ void vdiff_page(void){ int ridFrom, ridTo; int showDetail = 0; + int sideBySide = 0; Manifest *pFrom, *pTo; ManifestFile *pFileFrom, *pFileTo; login_check_credentials(); if( !g.perm.Read ){ login_needed(); return; } @@ -709,10 +760,20 @@ pFrom = vdiff_parse_manifest("from", &ridFrom); if( pFrom==0 ) return; pTo = vdiff_parse_manifest("to", &ridTo); if( pTo==0 ) return; showDetail = atoi(PD("detail","0")); + sideBySide = atoi(PD("sbs","1")); + if( !sideBySide ){ + style_submenu_element("2-Pane Diff", "TPD", + "%s/vdiff?from=%T&to=%T&detail=%d&sbs=1", + g.zTop, P("from"), P("to"), showDetail); + }else{ + style_submenu_element("1-Pane Diff", "OPD", + "%s/vdiff?from=%T&to=%T&detail=%d&sbs=0", + g.zTop, P("from"), P("to"), showDetail); + } style_header("Check-in Differences"); @ <h2>Difference From:</h2><blockquote> checkin_description(ridFrom); @ </blockquote><h2>To:</h2><blockquote> checkin_description(ridTo); @@ -731,25 +792,25 @@ }else{ cmp = fossil_strcmp(pFileFrom->zName, pFileTo->zName); } if( cmp<0 ){ append_file_change_line(pFileFrom->zName, - pFileFrom->zUuid, 0, 0, 0, 0); + pFileFrom->zUuid, 0, 0, 0, 0, 0); pFileFrom = manifest_file_next(pFrom, 0); }else if( cmp>0 ){ append_file_change_line(pFileTo->zName, - 0, pFileTo->zUuid, 0, 0, + 0, pFileTo->zUuid, 0, 0, 0, manifest_file_mperm(pFileTo)); pFileTo = manifest_file_next(pTo, 0); }else if( fossil_strcmp(pFileFrom->zUuid, pFileTo->zUuid)==0 ){ /* No changes */ pFileFrom = manifest_file_next(pFrom, 0); pFileTo = manifest_file_next(pTo, 0); }else{ append_file_change_line(pFileFrom->zName, pFileFrom->zUuid, - pFileTo->zUuid, 0, showDetail, + pFileTo->zUuid, 0, showDetail, sideBySide, manifest_file_mperm(pFileTo)); pFileFrom = manifest_file_next(pFrom, 0); pFileTo = manifest_file_next(pTo, 0); } } @@ -797,11 +858,11 @@ " WHERE filename.fnid=mlink.fnid" " AND event.objid=mlink.mid" " AND a.rid=mlink.fid" " AND b.rid=mlink.mid" " AND mlink.fid=%d" - " ORDER BY filename.name, event.mtime", + " ORDER BY filename.name, event.mtime /*sort*/", TAG_BRANCH, rid ); @ <ul> while( db_step(&q)==SQLITE_ROW ){ const char *zName = db_column_text(&q, 0); @@ -983,28 +1044,30 @@ } /* ** WEBPAGE: fdiff -** URL: fdiff?v1=UUID&v2=UUID&patch +** URL: fdiff?v1=UUID&v2=UUID&patch&sbs=BOOLEAN ** -** Two arguments, v1 and v2, identify the files to be diffed. Show the -** difference between the two artifacts. Generate plaintext if "patch" -** is present. +** Two arguments, v1 and v2, identify the files to be diffed. Show the +** difference between the two artifacts. Show diff side by side unless sbs +** is 0. Generate plaintext if "patch" is present. */ void diff_page(void){ int v1, v2; int isPatch; + int sideBySide; Blob c1, c2, diff, *pOut; char *zV1; char *zV2; login_check_credentials(); if( !g.perm.Read ){ login_needed(); return; } v1 = name_to_rid_www("v1"); v2 = name_to_rid_www("v2"); if( v1==0 || v2==0 ) fossil_redirect_home(); + sideBySide = atoi(PD("sbs","1")); zV1 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v1); zV2 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v2); isPatch = P("patch")!=0; if( isPatch ){ pOut = cgi_output_blob(); @@ -1011,28 +1074,42 @@ cgi_set_content_type("text/plain"); }else{ blob_zero(&diff); pOut = &diff; } - content_get(v1, &c1); - content_get(v2, &c2); - text_diff(&c1, &c2, pOut, 4, 1); - blob_reset(&c1); - blob_reset(&c2); + if( !sideBySide || isPatch ){ + content_get(v1, &c1); + content_get(v2, &c2); + text_diff(&c1, &c2, pOut, 4, 1); + blob_reset(&c1); + blob_reset(&c2); + } if( !isPatch ){ style_header("Diff"); style_submenu_element("Patch", "Patch", "%s/fdiff?v1=%T&v2=%T&patch", g.zTop, P("v1"), P("v2")); + if( !sideBySide ){ + style_submenu_element("2-Pane Diff", "TPD", "%s/fdiff?v1=%T&v2=%T&sbs=1", + g.zTop, P("v1"), P("v2")); + }else{ + style_submenu_element("1-Pane Diff", "OPD", "%s/fdiff?v1=%T&v2=%T&sbs=0", + g.zTop, P("v1"), P("v2")); + } + @ <h2>Differences From @ Artifact <a href="%s(g.zTop)/artifact/%S(zV1)">[%S(zV1)]</a>:</h2> object_description(v1, 0, 0); @ <h2>To Artifact <a href="%s(g.zTop)/artifact/%S(zV2)">[%S(zV2)]</a>:</h2> object_description(v2, 0, 0); @ <hr /> - @ <blockquote><pre> - @ %h(blob_str(&diff)) - @ </pre></blockquote> + if( sideBySide ){ + generate_sbsdiff(zV1, zV2); + }else{ + @ <blockquote><pre> + @ %h(blob_str(&diff)) + @ </pre></blockquote> + } blob_reset(&diff); style_footer(); } } DELETED src/json.c Index: src/json.c ================================================================== --- src/json.c +++ /dev/null @@ -1,1965 +0,0 @@ -/* -** Copyright (c) 2007 D. Richard Hipp -** -** This program is free software; you can redistribute it and/or -** modify it under the terms of the Simplified BSD License (also -** known as the "2-Clause License" or "FreeBSD License".) - -** This program is distributed in the hope that it will be useful, -** but without any warranty; without even the implied warranty of -** merchantability or fitness for a particular purpose. -** -** Author contact information: -** drh@hwaci.com -** http://www.hwaci.com/drh/ -** -******************************************************************************* -** -** Code for the JSON API. -** -** For notes regarding the public JSON interface, please see: -** -** https://docs.google.com/document/d/1fXViveNhDbiXgCuE7QDXQOKeFzf2qNUkBEgiUvoqFN4/edit -** -** -** Notes for hackers... -** -** Here's how command/page dispatching works: json_page_top() (in HTTP mode) or -** json_cmd_top() (in CLI mode) catch the "json" path/command. Those functions then -** dispatch to a JSON-mode-specific command/page handler with the type fossil_json_f(). -** See the API docs for that typedef (below) for the semantics of the callbacks. -** -** -*/ -#include "config.h" -#include "VERSION.h" -#include "json.h" -#include <assert.h> -#include <time.h> - -#if INTERFACE -#include "json_detail.h" /* workaround for apparent enum limitation in makeheaders */ -#endif - -const FossilJsonKeys_ FossilJsonKeys = { - "anonymousSeed" /*anonymousSeed*/, - "authToken" /*authToken*/, - "COMMAND_PATH" /*commandPath*/, - "payload" /* payload */, - "requestId" /*requestId*/, - "resultCode" /*resultCode*/, - "resultText" /*resultText*/, - "timestamp" /*timestamp*/ -}; - -/* -** Internal helpers to manipulate a byte array as a bitset. The B -** argument must be-a array at least (BIT/8+1) bytes long. -** The BIT argument is the bit number to query/set/clear/toggle. -*/ -#define BITSET_BYTEFOR(B,BIT) ((B)[ BIT / 8 ]) -#define BITSET_SET(B,BIT) ((BITSET_BYTEFOR(B,BIT) |= (0x01 << (BIT%8))),0x01) -#define BITSET_UNSET(B,BIT) ((BITSET_BYTEFOR(B,BIT) &= ~(0x01 << (BIT%8))),0x00) -#define BITSET_GET(B,BIT) ((BITSET_BYTEFOR(B,BIT) & (0x01 << (BIT%8))) ? 0x01 : 0x00) -#define BITSET_TOGGLE(B,BIT) (BITSET_GET(B,BIT) ? (BITSET_UNSET(B,BIT)) : (BITSET_SET(B,BIT))) - - -/* Timer code taken from sqlite3's shell.c, modified slightly. - FIXME: move the timer into the fossil core API so that we can - start the timer early on in the app init phase. Right now we're - just timing the json ops themselves. -*/ -#if !defined(_WIN32) && !defined(WIN32) && !defined(__OS2__) && !defined(__RTP__) && !defined(_WRS_KERNEL) -#include <sys/time.h> -#include <sys/resource.h> - -/* Saved resource information for the beginning of an operation */ -static struct rusage sBegin; - -/* -** Begin timing an operation -*/ -static void beginTimer(void){ - getrusage(RUSAGE_SELF, &sBegin); -} - -/* Return the difference of two time_structs in milliseconds */ -static double timeDiff(struct timeval *pStart, struct timeval *pEnd){ - return ((pEnd->tv_usec - pStart->tv_usec)*0.001 + - (double)((pEnd->tv_sec - pStart->tv_sec)*1000.0)); -} - -/* -** Print the timing results. -*/ -static double endTimer(void){ - struct rusage sEnd; - getrusage(RUSAGE_SELF, &sEnd); - return timeDiff(&sBegin.ru_utime, &sEnd.ru_utime) - + timeDiff(&sBegin.ru_stime, &sEnd.ru_stime); -#if 0 - printf("CPU Time: user %f sys %f\n", - timeDiff(&sBegin.ru_utime, &sEnd.ru_utime), - timeDiff(&sBegin.ru_stime, &sEnd.ru_stime)); -#endif -} - -#define BEGIN_TIMER beginTimer() -#define END_TIMER endTimer() -#define HAS_TIMER 1 - -#elif (defined(_WIN32) || defined(WIN32)) - -#include <windows.h> - -/* Saved resource information for the beginning of an operation */ -static HANDLE hProcess; -static FILETIME ftKernelBegin; -static FILETIME ftUserBegin; -typedef BOOL (WINAPI *GETPROCTIMES)(HANDLE, LPFILETIME, LPFILETIME, LPFILETIME, LPFILETIME); -static GETPROCTIMES getProcessTimesAddr = NULL; - -/* -** Check to see if we have timer support. Return 1 if necessary -** support found (or found previously). -*/ -static int hasTimer(void){ - if( getProcessTimesAddr ){ - return 1; - } else { - /* GetProcessTimes() isn't supported in WIN95 and some other Windows versions. - ** See if the version we are running on has it, and if it does, save off - ** a pointer to it and the current process handle. - */ - hProcess = GetCurrentProcess(); - if( hProcess ){ - HINSTANCE hinstLib = LoadLibrary(TEXT("Kernel32.dll")); - if( NULL != hinstLib ){ - getProcessTimesAddr = (GETPROCTIMES) GetProcAddress(hinstLib, "GetProcessTimes"); - if( NULL != getProcessTimesAddr ){ - return 1; - } - FreeLibrary(hinstLib); - } - } - } - return 0; -} - -/* -** Begin timing an operation -*/ -static void beginTimer(void){ - if( getProcessTimesAddr ){ - FILETIME ftCreation, ftExit; - getProcessTimesAddr(hProcess, &ftCreation, &ftExit, &ftKernelBegin, &ftUserBegin); - } -} - -/* Return the difference of two FILETIME structs in milliseconds */ -static double timeDiff(FILETIME *pStart, FILETIME *pEnd){ - sqlite_int64 i64Start = *((sqlite_int64 *) pStart); - sqlite_int64 i64End = *((sqlite_int64 *) pEnd); - return (double) ((i64End - i64Start) / 10000.0); -} - -/* -** Print the timing results. -*/ -static double endTimer(void){ - if(getProcessTimesAddr){ - FILETIME ftCreation, ftExit, ftKernelEnd, ftUserEnd; - getProcessTimesAddr(hProcess, &ftCreation, &ftExit, &ftKernelEnd, &ftUserEnd); - return timeDiff(&ftUserBegin, &ftUserEnd) + - timeDiff(&ftKernelBegin, &ftKernelEnd); - } -} - -#define BEGIN_TIMER beginTimer() -#define END_TIMER endTimer() -#define HAS_TIMER hasTimer() - -#else -#define BEGIN_TIMER -#define END_TIMER 0.0 -#define HAS_TIMER 0 -#endif - -/* -** Placeholder /json/XXX page impl for NYI (Not Yet Implemented) -** (but planned) pages/commands. -*/ -static cson_value * json_page_nyi(){ - g.json.resultCode = FSL_JSON_E_NYI; - return NULL; -} - -/* -** Given a FossilJsonCodes value, it returns a string suitable for use -** as a resultText string. Returns some unspecified string if errCode -** is not one of the FossilJsonCodes values. -*/ -char const * json_err_str( int errCode ){ - switch( errCode ){ - case 0: return "Success"; -#define C(X,V) case FSL_JSON_E_ ## X: return V - - C(GENERIC,"Generic error"); - C(INVALID_REQUEST,"Invalid request"); - C(UNKNOWN_COMMAND,"Unknown Command"); - C(UNKNOWN,"Unknown error"); - C(RESOURCE_NOT_FOUND,"Resource not found"); - C(TIMEOUT,"Timeout reached"); - C(ASSERT,"Assertion failed"); - C(ALLOC,"Resource allocation failed"); - C(NYI,"Not yet implemented"); - C(AUTH,"Authentication error"); - C(LOGIN_FAILED,"Login failed"); - C(LOGIN_FAILED_NOSEED,"Anonymous login attempt was missing password seed"); - C(LOGIN_FAILED_NONAME,"Login failed - name not supplied"); - C(LOGIN_FAILED_NOPW,"Login failed - password not supplied"); - C(LOGIN_FAILED_NOTFOUND,"Login failed - no match found"); - C(MISSING_AUTH,"Authentication info missing from request"); - C(DENIED,"Access denied"); - C(WRONG_MODE,"Request not allowed (wrong operation mode)"); - - C(USAGE,"Usage error"); - C(INVALID_ARGS,"Invalid arguments"); - C(MISSING_ARGS,"Missing arguments"); - - C(DB,"Database error"); - C(STMT_PREP,"Statement preparation failed"); - C(STMT_BIND,"Statement parameter binding failed"); - C(STMT_EXEC,"Statement execution/stepping failed"); - C(DB_LOCKED,"Database is locked"); - C(DB_NEEDS_REBUILD,"Fossil repository needs to be rebuilt"); -#undef C - default: - return "Unknown Error"; - } -} - -/* -** Implements the cson_data_dest_f() interface and outputs the data to -** a fossil Blob object. pState must be-a initialized (Blob*), to -** which n bytes of src will be appended. -**/ -int cson_data_dest_Blob(void * pState, void const * src, unsigned int n){ - Blob * b = (Blob*)pState; - blob_append( b, (char const *)src, (int)n ) /* will die on OOM */; - return 0; -} - -/* -** Implements the cson_data_source_f() interface and reads -** input from a fossil Blob object. pState must be-a (Blob*). -*/ -int cson_data_src_Blob(void * pState, void * dest, unsigned int * n){ - Blob * b = (Blob*)pState; - *n = blob_read( b, dest, *n ); - return 0; -} - -/* -** Convenience wrapper around cson_output() which appends the output -** to pDest. pOpt may be NULL, in which case g.json.outOpt will be used. -*/ -int cson_output_Blob( cson_value const * pVal, Blob * pDest, cson_output_opt const * pOpt ){ - return cson_output( pVal, cson_data_dest_Blob, - pDest, pOpt ? pOpt : &g.json.outOpt ); -} - -/* -** Convenience wrapper around cson_parse() which reads its input -** from pSrc. pSrc is rewound before parsing. -** -** pInfo may be NULL. If it is not NULL then it will contain details -** about the parse state when this function returns. -** -** On success a new JSON Object or Array is returned. On error NULL is -** returned. -*/ -cson_value * cson_parse_Blob( Blob * pSrc, cson_parse_info * pInfo ){ - cson_value * root = NULL; - blob_rewind( pSrc ); - cson_parse( &root, cson_data_src_Blob, pSrc, NULL, pInfo ); - return root; -} - -/* -** Implements the cson_data_dest_f() interface and outputs the data to -** cgi_append_content(). pState is ignored. -**/ -int cson_data_dest_cgi(void * pState, void const * src, unsigned int n){ - cgi_append_content( (char const *)src, (int)n ); - return 0; -} - -/* -** Returns a string in the form FOSSIL-XXXX, where XXXX is a -** left-zero-padded value of code. The returned buffer is static, and -** must be copied if needed for later. The returned value will always -** be 11 bytes long (not including the trailing NUL byte). -** -** In practice we will only ever call this one time per app execution -** when constructing the JSON response envelope, so the static buffer -** "shouldn't" be a problem. -** -*/ -char const * json_rc_cstr( int code ){ - enum { BufSize = 12 }; - static char buf[BufSize] = {'F','O','S','S','I','L','-',0}; - assert((code >= 1000) && (code <= 9999) && "Invalid Fossil/JSON code."); - sprintf(buf+7,"%04d", code); - return buf; -} - -/* -** Adds v to the API-internal cleanup mechanism. key must be a unique -** key for the given element. Adding another item with that key may -** free the previous one (depending on its reference count). If -** freeOnError is true then v is passed to cson_value_free() if the -** key cannot be inserted, otherweise ownership of v is not changed on -** error. Failure to insert a key may be caused by any of the -** following: -** -** - Allocation error. -** - g.json.gc.o is NULL -** - key is NULL or empty. -** -** Returns 0 on success. -** -** On success, ownership of v is transfered to (or shared with) -** g.json.gc, and v will be valid until that object is cleaned up or -** its key is replaced via another call to this function. -*/ -int json_gc_add( char const * key, cson_value * v, char freeOnError ){ - int const rc = cson_object_set( g.json.gc.o, key, v ); - assert( NULL != g.json.gc.o ); - if( (0 != rc) && freeOnError ){ - cson_value_free( v ); - } - return rc; -} - - -/* -** Returns the value of json_rc_cstr(code) as a new JSON -** string, which is owned by the caller and must eventually -** be cson_value_free()d or transfered to a JSON container. -*/ -cson_value * json_rc_string( int code ){ - return cson_value_new_string( json_rc_cstr(code), 11 ); -} - -cson_value * json_new_string( char const * str ){ - return str - ? cson_value_new_string(str,strlen(str)) - : NULL; -} - -/* -** Gets a POST/POST.payload/GET/COOKIE/ENV value. The returned memory -** is owned by the g.json object (one of its sub-objects). Returns -** NULL if no match is found. -** -** ENV means the system environment (getenv()). -** -** Precedence: POST.payload, GET/COOKIE/non-JSON POST, JSON POST, ENV. -** -** FIXME: the precedence SHOULD be: GET, POST.payload, POST, COOKIE, -** ENV, but the amalgamation of the GET/POST vars makes it difficult -** for me to do that. Since fossil only uses one cookie, cookie -** precedence isn't a real/high-priority problem. -*/ -cson_value * json_getenv( char const * zKey ){ - cson_value * rc; - rc = g.json.reqPayload.o - ? cson_object_get( g.json.reqPayload.o, zKey ) - : NULL; - if(rc){ - return rc; - } - rc = cson_object_get( g.json.param.o, zKey ); - if( rc ){ - return rc; - } - rc = cson_object_get( g.json.post.o, zKey ); - if(rc){ - return rc; - }else{ - char const * cv = PD(zKey,NULL); - if(!cv){ - /* reminder to self: in CLI mode i'd like to try - find_option(zKey,NULL,XYZ) here, but we don't have a sane - default for the XYZ param here. - */ - cv = getenv(zKey); - } - if(cv){/*transform it to JSON for later use.*/ - /* use sscanf() to figure out if it's an int, - and transform it to JSON int if it is. - */ - int intVal = -1; - char endOfIntCheck; - int const scanRc = sscanf(cv,"%d%c",&intVal, &endOfIntCheck) - /* The %c bit there is to make sure that we don't accept 123x - as a number. sscanf() returns the number of tokens - successfully parsed, so an RC of 1 will be correct for "123" - but "123x" will have RC==2. - */ - ; - if(1==scanRc){ - json_setenv( zKey, cson_value_new_integer(intVal) ); - }else{ - rc = cson_value_new_string(cv,strlen(cv)); - json_setenv( zKey, rc ); - } - return rc; - } - } - return NULL; -} - -/* -** Wrapper around json_getenv() which... -** -** If it finds a value and that value is-a JSON number or is a string -** which looks like an integer or is-a JSON bool/null then it is -** converted to an int. If none of those apply then dflt is returned. -*/ -int json_getenv_int(char const * pKey, int dflt ){ - cson_value const * v = json_getenv(pKey); - if(!v){ - return dflt; - }else if( cson_value_is_number(v) ){ - return (int)cson_value_get_integer(v); - }else if( cson_value_is_string(v) ){ - char const * sv = cson_string_cstr(cson_value_get_string(v)); - assert( (NULL!=sv) && "This is quite unexpected." ); - return sv ? atoi(sv) : dflt; - }else if( cson_value_is_bool(v) ){ - return cson_value_get_bool(v) ? 1 : 0; - }else if( cson_value_is_null(v) ){ - return 0; - }else{ - /* we should arguably treat JSON null as 0. */ - return dflt; - } -} - - -/* -** Wrapper around json_getenv() which tries to evaluate a payload/env -** value as a boolean. Uses mostly the same logic as -** json_getenv_int(), with the addition that string values which -** either start with a digit 1..9 or the letters [tT] are considered -** to be true. If this function cannot find a matching key/value then -** dflt is returned. e.g. if it finds the key but the value is-a -** Object then dftl is returned. -*/ -char json_getenv_bool(char const * pKey, char dflt ){ - cson_value const * v = json_getenv(pKey); - if(!v){ - return dflt; - }else if( cson_value_is_number(v) ){ - return cson_value_get_integer(v) ? 1 : 0; - }else if( cson_value_is_string(v) ){ - char const * sv = cson_string_cstr(cson_value_get_string(v)); - if(!*sv || ('0'==*sv)){ - return 0; - }else{ - return (('t'==*sv) || ('T'==*sv) - || (('1'<=*sv) && ('9'>=*sv))) - ? 1 : 0; - } - }else if( cson_value_is_bool(v) ){ - return cson_value_get_bool(v) ? 1 : 0; - }else if( cson_value_is_null(v) ){ - return 0; - }else{ - return dflt; - } -} - -/* -** Returns the string form of a json_getenv() value, but ONLY If that -** value is-a String. Non-strings are not converted to strings for -** this purpose. Returned memory is owned by g.json or fossil and is -** valid until end-of-app or the given key is replaced in fossil's -** internals via cgi_replace_parameter() and friends or json_setenv(). -*/ -char const * json_getenv_cstr( char const * zKey ){ - return cson_value_get_cstr( json_getenv(zKey) ); -} - - -/* -** Adds v to g.json.param.o using the given key. May cause any prior -** item with that key to be destroyed (depends on current reference -** count for that value). On success, transfers (or shares) ownership -** of v to (or with) g.json.param.o. On error ownership of v is not -** modified. -*/ -int json_setenv( char const * zKey, cson_value * v ){ - return cson_object_set( g.json.param.o, zKey, v ); -} - -/* -** Guesses a RESPONSE Content-Type value based (primarily) on the -** HTTP_ACCEPT header. -** -** It will try to figure out if the client can support -** application/json or application/javascript, and will fall back to -** text/plain if it cannot figure out anything more specific. -** -** Returned memory is static and immutable. -*/ -char const * json_guess_content_type(){ - char const * cset; - char doUtf8; - cset = PD("HTTP_ACCEPT_CHARSET",NULL); - doUtf8 = ((NULL == cset) || (NULL!=strstr("utf-8",cset))) - ? 1 : 0; - if( g.json.jsonp ){ - return doUtf8 - ? "application/javascript; charset=utf-8" - : "application/javascript"; - }else{ - /* - Content-type - - If the browser does not sent an ACCEPT for application/json - then we fall back to text/plain. - */ - char const * cstr; - cstr = PD("HTTP_ACCEPT",NULL); - if( NULL == cstr ){ - return doUtf8 - ? "application/json; charset=utf-8" - : "application/json"; - }else{ - if( strstr( cstr, "application/json" ) - || strstr( cstr, "*/*" ) ){ - return doUtf8 - ? "application/json; charset=utf-8" - : "application/json"; - }else{ - return "text/plain"; - } - } - } -} - -/* -** Sends pResponse to the output stream as the response object. This -** function does no validation of pResponse except to assert() that it -** is not NULL. The caller is responsible for ensuring that it meets -** API response envelope conventions. -** -** In CLI mode pResponse is sent to stdout immediately. In HTTP -** mode pResponse replaces any current CGI content but cgi_reply() -** is not called to flush the output. -** -** If g.json.jsonp is not NULL then the content type is set to -** application/javascript and the output is wrapped in a jsonp -** wrapper. -*/ -void json_send_response( cson_value const * pResponse ){ - assert( NULL != pResponse ); - if( g.isHTTP ){ - cgi_reset_content(); - if( g.json.jsonp ){ - cgi_printf("%s(",g.json.jsonp); - } - cson_output( pResponse, cson_data_dest_cgi, NULL, &g.json.outOpt ); - if( g.json.jsonp ){ - cgi_append_content(")",1); - } - }else{/*CLI mode*/ - if( g.json.jsonp ){ - fprintf(stdout,"%s(",g.json.jsonp); - } - cson_output_FILE( pResponse, stdout, &g.json.outOpt ); - if( g.json.jsonp ){ - fwrite(")\n", 2, 1, stdout); - } - } -} - -/* -** Returns the current request's JSON authentication token, or NULL if -** none is found. The token's memory is owned by (or shared with) -** g.json. -** -** If an auth token is found in the GET/POST request data then fossil -** is given that data for use in authentication for this -** session. i.e. the GET/POST data overrides fossil's authentication -** cookie value (if any) and also works with clients which do not -** support cookies. -** -** Must be called once before login_check_credentials() is called or -** we will not be able to replace fossil's internal idea of the auth -** info in time (and future changes to that state may cause unexpected -** results). -** -** The result of this call are cached for future calls. -*/ -cson_value * json_auth_token(){ - if( !g.json.authToken ){ - /* Try to get an authorization token from GET parameter, POSTed - JSON, or fossil cookie (in that order). */ - g.json.authToken = json_getenv(FossilJsonKeys.authToken); - if(g.json.authToken - && cson_value_is_string(g.json.authToken) - && !PD(login_cookie_name(),NULL)){ - /* tell fossil to use this login info. - - FIXME?: because the JSON bits don't carry around - login_cookie_name(), there is a potential login hijacking - window here. We may need to change the JSON auth token to be - in the form: login_cookie_name()=... - - Then again, the hardened cookie value helps ensure that - only a proper key/value match is valid. - */ - cgi_replace_parameter( login_cookie_name(), cson_value_get_cstr(g.json.authToken) ); - }else if( g.isHTTP ){ - /* try fossil's conventional cookie. */ - /* Reminder: chicken/egg scenario regarding db access in CLI - mode because login_cookie_name() needs the db. CLI - mode does not use any authentication, so we don't need - to support it here. - */ - char const * zCookie = P(login_cookie_name()); - if( zCookie && *zCookie ){ - /* Transfer fossil's cookie to JSON for downstream convenience... */ - cson_value * v = cson_value_new_string(zCookie, strlen(zCookie)); - if(0 == json_gc_add( FossilJsonKeys.authToken, v, 1 )){ - g.json.authToken = v; - } - } - } - } - return g.json.authToken; -} - -/* -** IFF json.reqPayload.o is not NULL then this returns -** cson_object_get(json.reqPayload.o,pKey), else it returns NULL. -** -** The returned value is owned by (or shared with) json.reqPayload.v. -*/ -cson_value * json_req_payload_get(char const *pKey){ - return g.json.reqPayload.o - ? cson_object_get(g.json.reqPayload.o,pKey) - : NULL; -} - -/* -** Initializes some JSON bits which need to be initialized relatively -** early on. It should only be called from cgi_init() or -** json_cmd_top() (early on in those functions). -** -** Initializes g.json.gc and g.json.param. This code does not (and -** must not) rely on any of the fossil environment having been set -** up. e.g. it must not use cgi_parameter() and friends because this -** must be called before those data are initialized. -*/ -void json_main_bootstrap(){ - cson_value * v; - assert( (NULL == g.json.gc.v) && "cgi_json_bootstrap() was called twice!" ); - - /* g.json.gc is our "garbage collector" - where we put JSON values - which need a long lifetime but don't have a logical parent to put - them in. - */ - v = cson_value_new_object(); - g.json.gc.v = v; - g.json.gc.o = cson_value_get_object(v); - - /* - g.json.param holds the JSONized counterpart of fossil's - cgi_parameter_xxx() family of data. We store them as JSON, as - opposed to using fossil's data directly, because we can retain - full type information for data this way (as opposed to it always - being of type string). - */ - v = cson_value_new_object(); - g.json.param.v = v; - g.json.param.o = cson_value_get_object(v); - json_gc_add("$PARAMS", v, 1); -} - -/* -** Appends a warning object to the (pending) JSON response. -** -** Code must be a FSL_JSON_W_xxx value from the FossilJsonCodes enum. -** -** A Warning object has this JSON structure: -** -** { "code":integer, "text":"string" } -** -** But the text part is optional. -** -** FIXME FIXME FIXME: i am EXPERIMENTALLY using integer codes instead -** of FOSSIL-XXXX codes here. i may end up switching FOSSIL-XXXX -** string-form codes to integers. Let's ask the mailing list for -** opinions... -** -** If msg is non-NULL and not empty then it is used as the "text" -** property's value. It is copied, and need not refer to static -** memory. -** -** CURRENTLY this code only allows a given warning code to be -** added one time, and elides subsequent warnings. The intention -** is to remove that burden from loops which produce warnings. -** -** FIXME: if msg is NULL then use a standard string for -** the given code. If !*msg then elide the "text" property, -** for consistency with how json_err() works. -*/ -void json_warn( int code, char const * msg ){ - cson_value * objV = NULL; - cson_object * obj = NULL; - assert( (code>FSL_JSON_W_START) - && (code<FSL_JSON_W_END) - && "Invalid warning code."); - if(!g.json.warnings.v){ - g.json.warnings.v = cson_value_new_array(); - assert((NULL != g.json.warnings.v) && "Alloc error."); - g.json.warnings.a = cson_value_get_array(g.json.warnings.v); - json_gc_add("$WARNINGS",g.json.warnings.v,0); - } - objV = cson_value_new_object(); - assert((NULL != objV) && "Alloc error."); - cson_array_append(g.json.warnings.a, objV); - obj = cson_value_get_object(objV); - cson_object_set(obj,"code",cson_value_new_integer(code)); - if(msg && *msg){ - /* FIXME: treat NULL msg as standard warning message for - the code, but we don't have those yet. - */ - cson_object_set(obj,"text",cson_value_new_string(msg,strlen(msg))); - } -} - -/* -** Splits zStr (which must not be NULL) into tokens separated by the -** given separator character. If doDeHttp is true then each element -** will be passed through dehttpize(), otherwise they are used -** as-is. Each new element is appended to the given target array -** object, which must not be NULL and ownership of it is not changed -** by this call. -** -** On success, returns the number of items appended to target. On -** error a NEGATIVE number is returned - its absolute value is the -** number of items inserted before the error occurred. There is a -** corner case here if we fail on the 1st element, which will cause 0 -** to be returned, which the client cannot immediately distinguish as -** success or error. -** -** Achtung: leading whitespace of elements is NOT elided. We should -** add an option to do that, but don't have one yet. -** -** Achtung: empty elements will be skipped, meaning consecutive -** empty elements are collapsed. -*/ -int json_string_split( char const * zStr, - char separator, - char doDeHttp, - cson_array * target ){ - char const * p = zStr /* current byte */; - char const * head = p /* current start-of-token */; - unsigned int len = 0 /* current token's length */; - int rc = 0 /* return code (number of added elements)*/; - assert( zStr && target ); - for( ; ; ++p){ - if( !*p || (separator == *p) ){ - if( len ){/* append head..(head+len) as next array - element. */ - cson_value * part = NULL; - char * zPart = NULL; - assert( head != p ); - zPart = (char*)malloc(len+1); - assert( (zPart != NULL) && "malloc failure" ); - memcpy(zPart, head, len); - zPart[len] = 0; - if(doDeHttp){ - dehttpize(zPart); - } - if( *zPart ){ /* should only fail if someone manages to url-encoded a NUL byte */ - part = cson_value_new_string(zPart, strlen(zPart)); - if( 0 == cson_array_append( target, part ) ){ - ++rc; - }else{ - cson_value_free(part); - rc = rc ? -rc : 0; - break; - } - }else{ - assert(0 && "i didn't think this was possible!"); - fprintf(stderr,"%s:%d: My God! It's full of stars!\n", - __FILE__, __LINE__); - fossil_exit(1) - /* Not fossil_panic() b/c this code needs to be able to - run before some of the fossil/json bits are initialized, - and fossil_panic() calls into the JSON API. - */ - ; - } - free(zPart); - len = 0; - } - if( !*p ){ - break; - } - head = p+1; - continue; - } - ++len; - } - return rc; -} - -/* -** Wrapper around json_string_split(), taking the same first 3 -** parameters as this function, but returns the results as -** a JSON Array (if splitting produced tokens) -** OR a JSON null value (if splitting produced no tokens) -** OR NULL (if splitting failed in any way). -** -** The returned value is owned by the caller. If not NULL then it -** _will_ have a JSON type of Array or Null. -*/ -cson_value * json_string_split2( char const * zStr, - char separator, - char doDeHttp ){ - cson_value * v = cson_value_new_array(); - cson_array * a = cson_value_get_array(v); - int rc = json_string_split( zStr, separator, doDeHttp, a ); - if( 0 == rc ){ - cson_value_free(v); - v = cson_value_null(); - }else if(rc<0){ - cson_value_free(v); - v = NULL; - } - return v; -} - -/* -** Performs some common initialization of JSON-related state. Must be -** called by the json_page_top() and json_cmd_top() dispatching -** functions to set up the JSON stat used by the dispatched functions. -** -** Implicitly sets up the login information state in CGI mode, but -** does not perform any permissions checking. It _might_ (haven't -** tested this) die with an error if an auth cookie is malformed. -** -** This must be called by the top-level JSON command dispatching code -** before they do any work. -** -** This must only be called once, or an assertion may be triggered. -*/ -static void json_mode_bootstrap(){ - static char once = 0 /* guard against multiple runs */; - char const * zPath = P("PATH_INFO"); - cson_value * pathSplit = NULL; - assert( (0==once) && "json_mode_bootstrap() called too many times!"); - if( once ){ - return; - }else{ - once = 1; - } - g.json.jsonp = PD("jsonp",NULL); - g.json.isJsonMode = 1; - g.json.resultCode = 0; - g.json.cmd.offset = -1; - if( !g.isHTTP && g.fullHttpReply ){ - /* workaround for server mode, so we see it as CGI mode. */ - g.isHTTP = 1; - } - - /* FIXME: do some sanity checking on g.json.jsonp and ignore it - if it is not halfway reasonable. - */ - cgi_set_content_type(json_guess_content_type()) - /* reminder: must be done after g.json.jsonp is initialized */ - ; - -#if defined(NDEBUG) - /* avoids debug messages on stderr in JSON mode */ - sqlite3_config(SQLITE_CONFIG_LOG, NULL, 0); -#endif - - g.json.cmd.v = cson_value_new_array(); - g.json.cmd.a = cson_value_get_array(g.json.cmd.v); - json_gc_add( FossilJsonKeys.commandPath, g.json.cmd.v, 1 ); - /* - The following if/else block translates the PATH_INFO path (in - CLI/server modes) or g.argv (CLI mode) into an internal list so - that we can simplify command dispatching later on. - - Note that translating g.argv this way is overkill but allows us to - avoid CLI-only special-case handling in other code, e.g. - json_command_arg(). - */ - if( zPath ){/* Either CGI or server mode... */ - /* Translate PATH_INFO into JSON array for later convenience. */ - json_string_split(zPath, '/', 1, g.json.cmd.a); - }else{/* assume CLI mode */ - int i; - char const * arg; - cson_value * part; - for(i = 1/*skip argv[0]*/; i < g.argc; ++i ){ - arg = g.argv[i]; - if( !arg || !*arg ){ - continue; - } - part = cson_value_new_string(arg,strlen(arg)); - cson_array_append(g.json.cmd.a, part); - } - } - - /* g.json.reqPayload exists only to simplify some of our access to - the request payload. We currently only use this in the context of - Object payloads, not Arrays, strings, etc. - */ - g.json.reqPayload.v = cson_object_get( g.json.post.o, FossilJsonKeys.payload ); - if( g.json.reqPayload.v ){ - g.json.reqPayload.o = cson_value_get_object( g.json.reqPayload.v ) - /* g.json.reqPayload.o may legally be NULL, which means only that - g.json.reqPayload.v is-not-a Object. - */; - } - - /* Anything which needs json_getenv() and friends should go after - this point. - */ - - if(!g.json.jsonp && g.json.post.o){ - g.json.jsonp = - json_getenv_cstr("jsonp") - /*cson_string_cstr(cson_value_get_string(cson_object_get(g.json.post.o,"jsonp")))*/ - ; - } - if( !g.isHTTP ){ - g.json.errorDetailParanoia = 0 /*disable error code dumb-down for CLI mode*/; - if(!g.json.jsonp){ - g.json.jsonp = find_option("jsonp",NULL,1); - } - } - - {/* set up JSON output formatting options. */ - unsigned char indent = g.isHTTP ? 0 : 1; - char const * indentStr = NULL; - if( g.isHTTP ){ - indent = (unsigned char)json_getenv_int("indent",(int)indent); - }else{/*CLI mode*/ - indentStr = find_option("indent","I",1); - if(indentStr){ - int const n = atoi(indentStr); - indent = (n>0) - ? (unsigned char)n - : 0; - } - } - g.json.outOpt.indentation = indent; - g.json.outOpt.addNewline = g.isHTTP - ? 0 - : (g.json.jsonp ? 0 : 1); - } - - if( g.isHTTP ){ - json_auth_token()/* will copy our auth token, if any, to fossil's - core, which we need before we call - login_check_credentials(). */; - login_check_credentials()/* populates g.perm */; - } - else{ - db_find_and_open_repository(OPEN_ANY_SCHEMA,0); - } -} - -/* -** Returns the ndx'th item in the "command path", where index 0 is the -** position of the "json" part of the path. Returns NULL if ndx is out -** of bounds or there is no "json" path element. -** -** In CLI mode the "path" is the list of arguments (skipping argv[0]). -** In server/CGI modes the path is taken from PATH_INFO. -** -** The returned bytes are owned by g.json.cmd.v and _may_ be -** invalidated if that object is modified (depending on how it is -** modified). -** -*/ -char const * json_command_arg(unsigned char ndx){ - cson_array * ar = g.json.cmd.a; - assert((NULL!=ar) && "Internal error. Was json_mode_bootstrap() called?"); - assert((g.argc>1) && "Internal error - we never should have gotten this far."); - if( g.json.cmd.offset < 0 ){ - /* first-time setup. */ - short i = 0; -#define NEXT cson_string_cstr( \ - cson_value_get_string( \ - cson_array_get(ar,i) \ - )) - char const * tok = NEXT; - while( tok ){ - if( !g.isHTTP/*workaround for "abbreviated name" in CLI mode*/ - ? (0==strcmp(g.argv[1],tok)) - : (0==strncmp("json",tok,4)) - ){ - g.json.cmd.offset = i; - break; - } - ++i; - tok = NEXT; - } - } -#undef NEXT - if(g.json.cmd.offset < 0){ - return NULL; - }else{ - ndx = g.json.cmd.offset + ndx; - return cson_string_cstr(cson_value_get_string(cson_array_get( ar, g.json.cmd.offset + ndx ))); - } -} - -/* -** If g.json.reqPayload.o is NULL then NULL is returned, else the -** given property is searched for in the request payload. If found it -** is returned. The returned value is owned by (or shares ownership -** with) g.json, and must NOT be cson_value_free()'d by the -** caller. -*/ -cson_value * json_payload_property( char const * key ){ - return g.json.reqPayload.o ? - cson_object_get( g.json.reqPayload.o, key ) - : NULL; -} - - -/* Returns the C-string form of json_auth_token(), or NULL -** if json_auth_token() returns NULL. -*/ -char const * json_auth_token_cstr(){ - return cson_value_get_cstr( json_auth_token() ); -} - -/* -** Returns the JsonPageDef with the given name, or NULL if no match is -** found. -** -** head must be a pointer to an array of JsonPageDefs in which the -** last entry has a NULL name. -*/ -JsonPageDef const * json_handler_for_name( char const * name, JsonPageDef const * head ){ - JsonPageDef const * pageDef = head; - assert( head != NULL ); - if(name && *name) for( ; pageDef->name; ++pageDef ){ - if( 0 == strcmp(name, pageDef->name) ){ - return pageDef; - } - } - return NULL; -} - -/* -** Given a Fossil/JSON result code, this function "dumbs it down" -** according to the current value of g.json.errorDetailParanoia. The -** dumbed-down value is returned. -** -** This function assert()s that code is in the inclusive range 0 to -** 9999. -** -** Note that WARNING codes (1..999) are never dumbed down. -** -*/ -static int json_dumbdown_rc( int code ){ - if(!code || ((code>FSL_JSON_W_START) && (code>FSL_JSON_W_END))){ - return code; - }else{ - int modulo = 0; - assert((code >= 1000) && (code <= 9999) && "Invalid Fossil/JSON code."); - switch( g.json.errorDetailParanoia ){ - case 1: modulo = 10; break; - case 2: modulo = 100; break; - case 3: modulo = 1000; break; - default: break; - } - if( modulo ) code = code - (code % modulo); - return code; - } -} - -/* -** Convenience routine which converts a Julian time value into a Unix -** Epoch timestamp. Requires the db, so this cannot be used before the -** repo is opened (will trigger a fatal error in db_xxx()). -*/ -cson_value * json_julian_to_timestamp(double j){ - return cson_value_new_integer((cson_int_t) - db_int64(0,"SELECT strftime('%%s',%lf)",j) - ); -} - -/* -** Returns a timestamp value. -*/ -cson_int_t json_timestamp(){ - return (cson_int_t)time(0); -} -/* -** Returns a new JSON value (owned by the caller) representing -** a timestamp. If timeVal is < 0 then time(0) is used to fetch -** the time, else timeVal is used as-is -*/ -cson_value * json_new_timestamp(cson_int_t timeVal){ - return cson_value_new_integer((timeVal<0) ? (cson_int_t)time(0) : timeVal); -} - -/* -** Creates a new Fossil/JSON response envelope skeleton. It is owned -** by the caller, who must eventually free it using cson_value_free(), -** or add it to a cson container to transfer ownership. Returns NULL -** on error. -** -** If payload is not NULL and resultCode is 0 then it is set as the -** "payload" property of the returned object. If resultCode is -** non-zero and payload is not NULL then this function calls -** cson_value_free(payload) and does not insert the payload into the -** response. In either case, onwership of payload is transfered to -** this function. -** -** pMsg is an optional message string property (resultText) of the -** response. If resultCode is non-0 and pMsg is NULL then -** json_err_str() is used to get the error string. The caller may -** provide his own or may use an empty string to suppress the -** resultText property. -** -*/ -cson_value * json_create_response( int resultCode, - char const * pMsg, - cson_value * payload){ - cson_value * v = NULL; - cson_value * tmp = NULL; - cson_object * o = NULL; - int rc; - resultCode = json_dumbdown_rc(resultCode); - v = cson_value_new_object(); - o = cson_value_get_object(v); - if( ! o ) return NULL; -#define SET(K) if(!tmp) goto cleanup; \ - rc = cson_object_set( o, K, tmp ); \ - if(rc) do{\ - cson_value_free(tmp); \ - tmp = NULL; \ - goto cleanup; \ - }while(0) - - tmp = cson_value_new_string(MANIFEST_UUID,strlen(MANIFEST_UUID)); - SET("fossil"); - - {/* timestamp */ - tmp = json_new_timestamp(-1); - SET(FossilJsonKeys.timestamp); - } - if( 0 != resultCode ){ - if( ! pMsg ) pMsg = json_err_str(resultCode); - tmp = json_rc_string(resultCode); - SET(FossilJsonKeys.resultCode); - } - if( pMsg && *pMsg ){ - tmp = cson_value_new_string(pMsg,strlen(pMsg)); - SET(FossilJsonKeys.resultText); - } - tmp = json_getenv(FossilJsonKeys.requestId); - if( tmp ) cson_object_set( o, FossilJsonKeys.requestId, tmp ); - - if(0){/* these are only intended for my own testing...*/ - if(g.json.cmd.v){ - tmp = g.json.cmd.v; - SET("$commandPath"); - } - if(g.json.param.v){ - tmp = g.json.param.v; - SET("$params"); - } - if(0){/*Only for debuggering, add some info to the response.*/ - tmp = cson_value_new_integer( g.json.cmd.offset ); - cson_object_set( o, "cmd.offset", tmp ); - cson_object_set( o, "isCGI", cson_value_new_bool( g.isHTTP ) ); - } - } - - if(HAS_TIMER){ - /* This is, philosophically speaking, not quite the right place - for ending the timer, but this is the one function which all of - the JSON exit paths use (and they call it after processing, - just before they end). - */ - double span; - span = END_TIMER; - /* i'm actually seeing sub-ms runtimes in some tests, but a time of - 0 is "just wrong", so we'll bump that up to 1ms. - */ - cson_object_set(o,"procTimeMs", cson_value_new_integer((cson_int_t)((span>0.0)?span:1))); - } - if(g.json.warnings.v){ - tmp = g.json.warnings.v; - SET("warnings"); - } - - /* Only add the payload to SUCCESS responses. Else delete it. */ - if( NULL != payload ){ - if( resultCode ){ - cson_value_free(payload); - payload = NULL; - }else{ - tmp = payload; - SET(FossilJsonKeys.payload); - } - } - -#undef SET - goto ok; - cleanup: - cson_value_free(v); - v = NULL; - ok: - return v; -} - -/* -** Outputs a JSON error response to either the cgi_xxx() family of -** buffers (in CGI/server mode) or stdout (in CLI mode). If rc is 0 -** then g.json.resultCode is used. If that is also 0 then the "Unknown -** Error" code is used. -** -** If g.isHTTP then the generated JSON error response object replaces -** any currently buffered page output. Because the output goes via -** the cgi_xxx() family of functions, this function inherits any -** compression which fossil does for its output. -** -** If alsoOutput is true AND g.isHTTP then cgi_reply() is called to -** flush the output (and headers). Generally only do this if you are -** about to call exit(). -** -** !g.isHTTP then alsoOutput is ignored and all output is sent to -** stdout immediately. -** -*/ -void json_err( int code, char const * msg, char alsoOutput ){ - int rc = code ? code : (g.json.resultCode - ? g.json.resultCode - : FSL_JSON_E_UNKNOWN); - cson_value * resp = NULL; - rc = json_dumbdown_rc(rc); - if( rc && !msg ){ - msg = json_err_str(rc); - } - resp = json_create_response(rc, msg, NULL); - if(!resp){ - /* about the only error case here is out-of-memory. DO NOT - call fossil_panic() here because that calls this function. - */ - fprintf(stderr, "%s: Fatal error: could not allocate " - "response object.\n", fossil_nameofexe()); - fossil_exit(1); - } - if( g.isHTTP ){ - if(alsoOutput){ - json_send_response(resp); - }else{ - /* almost a duplicate of json_send_response() :( */ - cgi_set_content_type("application/javascript"); - cgi_reset_content(); - if( g.json.jsonp ){ - cgi_printf("%s(",g.json.jsonp); - } - cson_output( resp, cson_data_dest_cgi, NULL, &g.json.outOpt ); - if( g.json.jsonp ){ - cgi_append_content(")",1); - } - } - }else{ - json_send_response(resp); - } - cson_value_free(resp); -} - - -/* -** Iterates through a prepared SELECT statement and converts each row -** to a JSON object. If pTgt is not NULL then it must be-a Array -** object and this function will return pTgt. If pTgt is NULL then a -** new Array object is created and returned (owned by the -** caller). Each row of pStmt is converted to an Object and appended -** to the array. -*/ -cson_value * json_stmt_to_array_of_obj(Stmt *pStmt, - cson_value * pTgt){ - cson_value * v = pTgt ? pTgt : cson_value_new_array(); - cson_array * a = cson_value_get_array(pTgt ? pTgt : v); - char const * warnMsg = NULL; - assert( NULL != a ); - while( (SQLITE_ROW==db_step(pStmt)) ){ - cson_value * row = cson_sqlite3_row_to_object(pStmt->pStmt); - if(!row && !warnMsg){ - warnMsg = "Could not convert at least one result row to JSON."; - continue; - } - cson_array_append(a, row); - } - if(warnMsg){ - json_warn( FSL_JSON_W_ROW_TO_JSON_FAILED, warnMsg ); - } - return v; -} - -/* -** /json/version implementation. -** -** Returns the payload object (owned by the caller). -*/ -cson_value * json_page_version(){ - cson_value * jval = NULL; - cson_object * jobj = NULL; - jval = cson_value_new_object(); - jobj = cson_value_get_object(jval); -#define FSET(X,K) cson_object_set( jobj, K, cson_value_new_string(X,strlen(X))) - FSET(MANIFEST_UUID,"manifestUuid"); - FSET(MANIFEST_VERSION,"manifestVersion"); - FSET(MANIFEST_DATE,"manifestDate"); - FSET(MANIFEST_YEAR,"manifestYear"); - FSET(RELEASE_VERSION,"releaseVersion"); -#undef FSET - cson_object_set( jobj, "releaseVersionNumber", - cson_value_new_integer(RELEASE_VERSION_NUMBER) ); - cson_object_set( jobj, "resultCodeParanoiaLevel", - cson_value_new_integer(g.json.errorDetailParanoia) ); - return jval; -} - - -/* -** Implementation for /json/cap -** -** Returned object contains details about the "capabilities" of the -** current user (what he may/may not do). -** -** This is primarily intended for debuggering, but may have -** a use in client code. (?) -*/ -cson_value * json_page_cap(){ - cson_value * payload = cson_value_new_object(); - cson_value * sub = cson_value_new_object(); - Stmt q; - cson_object * obj = cson_value_get_object(payload); - db_prepare(&q, "SELECT login, cap FROM user WHERE uid=%d", g.userUid); - if( db_step(&q)==SQLITE_ROW ){ - /* reminder: we don't use g.zLogin because it's 0 for the guest - user and the HTML UI appears to currently allow the name to be - changed (but doing so would break other code). */ - char const * str = (char const *)sqlite3_column_text(q.pStmt,0); - if( str ){ - cson_object_set( obj, "userName", - cson_value_new_string(str,strlen(str)) ); - } - str = (char const *)sqlite3_column_text(q.pStmt,1); - if( str ){ - cson_object_set( obj, "capabilities", - cson_value_new_string(str,strlen(str)) ); - } - } - db_finalize(&q); - cson_object_set( obj, "permissionFlags", sub ); - obj = cson_value_get_object(sub); - -#define ADD(X,K) cson_object_set(obj, K, cson_value_new_bool(g.perm.X)) - ADD(Setup,"setup"); - ADD(Admin,"admin"); - ADD(Delete,"delete"); - ADD(Password,"password"); - ADD(Query,"query"); /* don't think this one is actually used */ - ADD(Write,"checkin"); - ADD(Read,"checkout"); - ADD(History,"history"); - ADD(Clone,"clone"); - ADD(RdWiki,"readWiki"); - ADD(NewWiki,"createWiki"); - ADD(ApndWiki,"appendWiki"); - ADD(WrWiki,"editWiki"); - ADD(RdTkt,"readTicket"); - ADD(NewTkt,"createTicket"); - ADD(ApndTkt,"appendTicket"); - ADD(WrTkt,"editTicket"); - ADD(Attach,"attachFile"); - ADD(TktFmt,"createTicketReport"); - ADD(RdAddr,"readPrivate"); - ADD(Zip,"zip"); - ADD(Private,"xferPrivate"); -#undef ADD - return payload; -} - -/* -** Implementation of the /json/stat page/command. -** -*/ -cson_value * json_page_stat(){ - i64 t, fsize; - int n, m; - int full; - const char *zDb; - enum { BufLen = 1000 }; - char zBuf[BufLen]; - cson_value * jv = NULL; - cson_object * jo = NULL; - cson_value * jv2 = NULL; - cson_object * jo2 = NULL; - if( !g.perm.Read ){ - g.json.resultCode = FSL_JSON_E_DENIED; - return NULL; - } - if( g.isHTTP ){ - full = json_getenv_bool("full",0); - }else{ - full = (0!=find_option("full","f",0)); - } -#define SETBUF(O,K) cson_object_set(O, K, cson_value_new_string(zBuf, strlen(zBuf))); - - jv = cson_value_new_object(); - jo = cson_value_get_object(jv); - - sqlite3_snprintf(BufLen, zBuf, db_get("project-name","")); - SETBUF(jo, "projectName"); - /* FIXME: don't include project-description until we ensure that - zBuf will always be big enough. We "should" replace zBuf - with a blob for this purpose. - */ - fsize = file_size(g.zRepositoryName); - cson_object_set(jo, "repositorySize", cson_value_new_integer((cson_int_t)fsize)); - - if(full){ - n = db_int(0, "SELECT count(*) FROM blob"); - m = db_int(0, "SELECT count(*) FROM delta"); - cson_object_set(jo, "blobCount", cson_value_new_integer((cson_int_t)n)); - cson_object_set(jo, "deltaCount", cson_value_new_integer((cson_int_t)m)); - if( n>0 ){ - int a, b; - Stmt q; - db_prepare(&q, "SELECT total(size), avg(size), max(size)" - " FROM blob WHERE size>0"); - db_step(&q); - t = db_column_int64(&q, 0); - cson_object_set(jo, "uncompressedArtifactSize", - cson_value_new_integer((cson_int_t)t)); - cson_object_set(jo, "averageArtifactSize", - cson_value_new_integer((cson_int_t)db_column_int(&q, 1))); - cson_object_set(jo, "maxArtifactSize", - cson_value_new_integer((cson_int_t)db_column_int(&q, 2))); - db_finalize(&q); - if( t/fsize < 5 ){ - b = 10; - fsize /= 10; - }else{ - b = 1; - } - a = t/fsize; - sqlite3_snprintf(BufLen,zBuf, "%d:%d", a, b); - SETBUF(jo, "compressionRatio"); - } - n = db_int(0, "SELECT count(distinct mid) FROM mlink /*scan*/"); - cson_object_set(jo, "checkinCount", cson_value_new_integer((cson_int_t)n)); - n = db_int(0, "SELECT count(*) FROM filename /*scan*/"); - cson_object_set(jo, "fileCount", cson_value_new_integer((cson_int_t)n)); - n = db_int(0, "SELECT count(*) FROM tag /*scan*/" - " WHERE +tagname GLOB 'wiki-*'"); - cson_object_set(jo, "wikiPageCount", cson_value_new_integer((cson_int_t)n)); - n = db_int(0, "SELECT count(*) FROM tag /*scan*/" - " WHERE +tagname GLOB 'tkt-*'"); - cson_object_set(jo, "ticketCount", cson_value_new_integer((cson_int_t)n)); - }/*full*/ - n = db_int(0, "SELECT julianday('now') - (SELECT min(mtime) FROM event)" - " + 0.99"); - cson_object_set(jo, "ageDays", cson_value_new_integer((cson_int_t)n)); - cson_object_set(jo, "ageYears", cson_value_new_double(n/365.24)); - sqlite3_snprintf(BufLen, zBuf, db_get("project-code","")); - SETBUF(jo, "projectCode"); - sqlite3_snprintf(BufLen, zBuf, db_get("server-code","")); - SETBUF(jo, "serverCode"); - cson_object_set(jo, "compiler", cson_value_new_string(COMPILER_NAME, strlen(COMPILER_NAME))); - - jv2 = cson_value_new_object(); - jo2 = cson_value_get_object(jv2); - cson_object_set(jo, "sqlite", jv2); - sqlite3_snprintf(BufLen, zBuf, "%.19s [%.10s] (%s)", - SQLITE_SOURCE_ID, &SQLITE_SOURCE_ID[20], SQLITE_VERSION); - SETBUF(jo2, "version"); - zDb = db_name("repository"); - cson_object_set(jo2, "pageCount", cson_value_new_integer((cson_int_t)db_int(0, "PRAGMA %s.page_count", zDb))); - cson_object_set(jo2, "pageSize", cson_value_new_integer((cson_int_t)db_int(0, "PRAGMA %s.page_size", zDb))); - cson_object_set(jo2, "freeList", cson_value_new_integer((cson_int_t)db_int(0, "PRAGMA %s.freelist_count", zDb))); - sqlite3_snprintf(BufLen, zBuf, "%s", db_text(0, "PRAGMA %s.encoding", zDb)); - SETBUF(jo2, "encoding"); - sqlite3_snprintf(BufLen, zBuf, "%s", db_text(0, "PRAGMA %s.journal_mode", zDb)); - cson_object_set(jo2, "journalMode", *zBuf ? cson_value_new_string(zBuf, strlen(zBuf)) : cson_value_null()); - return jv; -#undef SETBUF -} - - - -static cson_value * json_user_list(); -static cson_value * json_user_get(); -#if 0 -static cson_value * json_user_create(); -static cson_value * json_user_edit(); -#endif - -/* -** Mapping of /json/user/XXX commands/paths to callbacks. -*/ -static const JsonPageDef JsonPageDefs_User[] = { -{"create", json_page_nyi, 1}, -{"edit", json_page_nyi, 1}, -{"get", json_user_get, 0}, -{"list", json_user_list, 0}, -/* Last entry MUST have a NULL name. */ -{NULL,NULL,0} -}; - - -cson_value * json_page_dispatch_helper(JsonPageDef const * pages){ - JsonPageDef const * def; - char const * cmd = json_command_arg(1+g.json.dispatchDepth); - assert( NULL != pages ); - if( ! cmd ){ - g.json.resultCode = FSL_JSON_E_MISSING_ARGS; - return NULL; - } - def = json_handler_for_name( cmd, pages ); - if(!def){ - g.json.resultCode = FSL_JSON_E_UNKNOWN_COMMAND; - return NULL; - } - else{ - ++g.json.dispatchDepth; - return (*def->func)(); - } -} - -/* -** Implements the /json/user family of pages/commands. -** -*/ -static cson_value * json_page_user(){ - return json_page_dispatch_helper(&JsonPageDefs_User[0]); -} - -static cson_value * json_branch_list(); -/* -** Mapping of /json/branch/XXX commands/paths to callbacks. -*/ -static const JsonPageDef JsonPageDefs_Branch[] = { -{"list", json_branch_list, 0}, -{"create", json_page_nyi, 1}, -/* Last entry MUST have a NULL name. */ -{NULL,NULL,0} -}; - -/* -** Implements the /json/branch family of pages/commands. Far from -** complete. -** -*/ -static cson_value * json_page_branch(){ - return json_page_dispatch_helper(&JsonPageDefs_Branch[0]); -} - -/* -** Impl for /json/branch/list -** -** -** CLI mode options: -** -** --range X | -r X, where X is one of (open,closed,all) -** (only the first letter is significant, default=open). -** -a (same as --range a) -** -c (same as --range c) -** -** HTTP mode options: -** -** "range" GET/POST.payload parameter. FIXME: currently we also use -** POST, but really want to restrict this to POST.payload. -*/ -static cson_value * json_branch_list(){ - cson_value * payV; - cson_object * pay; - cson_value * listV; - cson_array * list; - char const * range = NULL; - int which = 0; - char * sawConversionError = NULL; - Stmt q; - if( !g.perm.Read ){ - g.json.resultCode = FSL_JSON_E_DENIED; - return NULL; - } - payV = cson_value_new_object(); - pay = cson_value_get_object(payV); - listV = cson_value_new_array(); - list = cson_value_get_array(listV); - if(!g.isHTTP){ - range = find_option("range","r",1); - if(!range||!*range){ - range = find_option("all","a",0); - if(range && *range){ - range = "a"; - }else{ - range = find_option("closed","c",0); - if(range&&*range){ - range = "c"; - } - } - } - }else{ - range = json_getenv_cstr("range"); - } - if(!range || !*range){ - range = "o"; - } - assert( (NULL != range) && *range ); - switch(*range){ - case 'c': - range = "closed"; - which = -1; - break; - case 'a': - range = "all"; - which = 1; - break; - default: - range = "open"; - which = 0; - break; - }; - cson_object_set(pay,"range",cson_value_new_string(range,strlen(range))); - - if( g.localOpen ){ /* add "current" property (branch name). */ - int vid = db_lget_int("checkout", 0); - char const * zCurrent = vid - ? db_text(0, "SELECT value FROM tagxref" - " WHERE rid=%d AND tagid=%d", - vid, TAG_BRANCH) - : 0; - if(zCurrent){ - cson_object_set(pay,"current",json_new_string(zCurrent)); - } - } - - - branch_prepare_list_query(&q, which); - cson_object_set(pay,"branches",listV); - while((SQLITE_ROW==db_step(&q))){ - cson_value * v = cson_sqlite3_column_to_value(q.pStmt,0); - if(v){ - cson_array_append(list,v); - }else if(!sawConversionError){ - sawConversionError = mprintf("Column-to-json failed @ %s:%d", - __FILE__,__LINE__); - } - } - if( sawConversionError ){ - json_warn(FSL_JSON_W_COL_TO_JSON_FAILED,sawConversionError); - free(sawConversionError); -} - return payV; -} - -/* -** Impl of /json/rebuild. Requires admin previleges. -*/ -static cson_value * json_page_rebuild(){ - if( !g.perm.Admin ){ - g.json.resultCode = FSL_JSON_E_DENIED; - return NULL; - }else{ - /* Reminder: the db_xxx() ops "should" fail via - the fossil core error handlers, which will cause - a JSON error and exit(). i.e. we don't handle - the errors here. TODO: confirm that all these - db routine fail gracefully in JSON mode. - */ - db_close(1); - db_open_repository(g.zRepositoryName); - db_begin_transaction(); - rebuild_db(0, 0, 0); - db_end_transaction(0); - return NULL; - } -} - -/* -** Impl of /json/user/list. Requires admin rights. -*/ -static cson_value * json_user_list(){ - cson_value * payV = NULL; - Stmt q; - if(!g.perm.Admin){ - g.json.resultCode = FSL_JSON_E_DENIED; - return NULL; - } - db_prepare(&q,"SELECT uid AS uid," - " login AS name," - " cap AS capabilities," - " info AS info," - " mtime AS mtime" - " FROM user ORDER BY login"); - payV = json_stmt_to_array_of_obj(&q, NULL); - db_finalize(&q); - if(NULL == payV){ - g.json.resultCode = FSL_JSON_E_UNKNOWN; - } - return payV; -} - -/* -** Impl of /json/user/get. Requires admin rights. -*/ -static cson_value * json_user_get(){ - cson_value * payV = NULL; - char const * pUser = NULL; - Stmt q; - if(!g.perm.Admin){ - g.json.resultCode = FSL_JSON_E_DENIED; - return NULL; - } - pUser = json_command_arg(g.json.dispatchDepth+1); - if( g.isHTTP && (!pUser || !*pUser) ){ - pUser = json_getenv_cstr("name") - /* ACHTUNG: fossil apparently internally sets name=user/get/XYZ - if we pass the name as part of the path, so we check the path - _before_ checking for name=XYZ. - */; - } - if(!pUser || !*pUser){ - g.json.resultCode = FSL_JSON_E_MISSING_ARGS; - return NULL; - } - db_prepare(&q,"SELECT uid AS uid," - " login AS name," - " cap AS capabilities," - " info AS info," - " mtime AS mtime" - " FROM user" - " WHERE login=%Q", - pUser); - if( (SQLITE_ROW == db_step(&q)) ){ - payV = cson_sqlite3_row_to_object(q.pStmt); - if(!payV){ - g.json.resultCode = FSL_JSON_E_UNKNOWN; - } - }else{ - g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; - } - db_finalize(&q); - return payV; -} - -/* Impl in json_login.c. */ -cson_value * json_page_anon_password(); -/* Impl in json_login.c. */ -cson_value * json_page_login(); -/* Impl in json_login.c. */ -cson_value * json_page_logout(); - -/* -** Mapping of names to JSON pages/commands. Each name is a subpath of -** /json (in CGI mode) or a subcommand of the json command in CLI mode -*/ -static const JsonPageDef JsonPageDefs[] = { -/* please keep alphabetically sorted (case-insensitive) for maintenance reasons. */ -{"anonymousPassword",json_page_anon_password, 1}, -{"branch", json_page_branch,0}, -{"cap", json_page_cap, 0}, -{"dir", json_page_nyi, 0}, -{"HAI",json_page_version,0}, -{"login",json_page_login,1}, -{"logout",json_page_logout,1}, -{"rebuild",json_page_rebuild,0}, -{"stat",json_page_stat,0}, -{"tag", json_page_nyi,0}, -{"ticket", json_page_nyi,0}, -{"timeline", json_page_timeline,0}, -{"user",json_page_user,0}, -{"version",json_page_version,0}, -{"whoami",json_page_whoami,1/*FIXME: work in CLI mode*/}, -{"wiki",json_page_wiki,0}, -/* Last entry MUST have a NULL name. */ -{NULL,NULL,0} -}; - - -/* -** Mapping of /json/ticket/XXX commands/paths to callbacks. -*/ -static const JsonPageDef JsonPageDefs_Ticket[] = { -{"get", json_page_nyi, 0}, -{"list", json_page_nyi, 0}, -{"save", json_page_nyi, 1}, -{"create", json_page_nyi, 1}, -/* Last entry MUST have a NULL name. */ -{NULL,NULL,0} -}; - -/* -** Mapping of /json/artifact/XXX commands/paths to callbacks. -*/ -static const JsonPageDef JsonPageDefs_Artifact[] = { -{"vinfo", json_page_nyi, 0}, -{"finfo", json_page_nyi, 0}, -/* Last entry MUST have a NULL name. */ -{NULL,NULL,0} -}; - -/* -** Mapping of /json/tag/XXX commands/paths to callbacks. -*/ -static const JsonPageDef JsonPageDefs_Tag[] = { -{"list", json_page_nyi, 0}, -{"create", json_page_nyi, 1}, -/* Last entry MUST have a NULL name. */ -{NULL,NULL,0} -}; - - -/* -** WEBPAGE: json -** -** Pages under /json/... must be entered into JsonPageDefs. -** This function dispatches them, and is the HTTP equivalent of -** json_cmd_top(). -*/ -void json_page_top(void){ - int rc = FSL_JSON_E_UNKNOWN_COMMAND; - char const * cmd; - cson_value * payload = NULL; - JsonPageDef const * pageDef = NULL; - BEGIN_TIMER; - json_mode_bootstrap(); - cmd = json_command_arg(1); - /*cgi_printf("{\"cmd\":\"%s\"}\n",cmd); return;*/ - pageDef = json_handler_for_name(cmd,&JsonPageDefs[0]); - if( ! pageDef ){ - json_err( FSL_JSON_E_UNKNOWN_COMMAND, NULL, 0 ); - return; - }else if( pageDef->runMode < 0 /*CLI only*/){ - rc = FSL_JSON_E_WRONG_MODE; - }else{ - rc = 0; - g.json.dispatchDepth = 1; - payload = (*pageDef->func)(); - } - if( g.json.resultCode ){ - json_err(g.json.resultCode, NULL, 0); - }else{ - cson_value * root = json_create_response(rc, NULL, payload); - json_send_response(root); - cson_value_free(root); - } -} - -/* -** This function dispatches json commands and is the CLI equivalent of -** json_page_top(). -** -** COMMAND: json -** -** Usage: %fossil json SUBCOMMAND -** -** The commands include: -** -** cap -** stat -** version (alias: HAI) -** -** -** TODOs: -** -** branch -** tag -** ticket -** timeline -** wiki -** ... -** -*/ -void json_cmd_top(void){ - char const * cmd = NULL; - int rc = FSL_JSON_E_UNKNOWN_COMMAND; - cson_value * payload = NULL; - JsonPageDef const * pageDef; - BEGIN_TIMER; - memset( &g.perm, 0xff, sizeof(g.perm) ) - /* In CLI mode fossil does not use permissions - and they all default to false. We enable them - here because (A) fossil doesn't use them in local - mode but (B) having them set gives us one less - difference in the CLI/CGI/Server-mode JSON - handling. - */ - ; - json_main_bootstrap(); - json_mode_bootstrap(); - if( g.argc<3 ){ - goto usage; - } - db_find_and_open_repository(0, 0); -#if 0 - json_warn(FSL_JSON_W_ROW_TO_JSON_FAILED, "Just testing."); - json_warn(FSL_JSON_W_ROW_TO_JSON_FAILED, "Just testing again."); -#endif - cmd = json_command_arg(1); - if( !cmd || !*cmd ){ - goto usage; - } - pageDef = json_handler_for_name(cmd,&JsonPageDefs[0]); - if( ! pageDef ){ - json_err( FSL_JSON_E_UNKNOWN_COMMAND, NULL, 1 ); - return; - }else if( pageDef->runMode > 0 /*HTTP only*/){ - rc = FSL_JSON_E_WRONG_MODE; - }else{ - rc = 0; - g.json.dispatchDepth = 1; - payload = (*pageDef->func)(); - } - if( g.json.resultCode ){ - json_err(g.json.resultCode, NULL, 1); - }else{ - payload = json_create_response(rc, NULL, payload); - json_send_response(payload); - cson_value_free( payload ); - if((0 != rc) && !g.isHTTP){ - /* FIXME: we need a way of passing this error back - up to the routine which called this callback. - e.g. add g.errCode. - */ - fossil_exit(1); - } - } - return; - usage: - usage("subcommand"); -} - -#undef BITSET_BYTEFOR -#undef BITSET_SET -#undef BITSET_UNSET -#undef BITSET_GET -#undef BITSET_TOGGLE DELETED src/json_detail.h Index: src/json_detail.h ================================================================== --- src/json_detail.h +++ /dev/null @@ -1,187 +0,0 @@ -#if !defined(FOSSIL_JSON_DETAIL_H_INCLUDED) -#define FOSSIL_JSON_DETAIL_H_INCLUDED - -#include "cson_amalgamation.h" -/* -** Impl details for the JSON API which need to be shared -** across multiple C files. -*/ - -/* -** The "official" list of Fossil/JSON error codes. Their values might -** very well change during initial development but after their first -** public release they must stay stable. -** -** Values must be in the range 1000..9999 for error codes and 1..999 -** for warning codes. -** -** Numbers evenly dividable by 100 are "categories", and error codes -** for a given category have their high bits set to the category -** value. -** -*/ -enum FossilJsonCodes { -FSL_JSON_W_START = 0, -FSL_JSON_W_UNKNOWN = FSL_JSON_W_START + 1, -FSL_JSON_W_ROW_TO_JSON_FAILED = FSL_JSON_W_START + 2, -FSL_JSON_W_COL_TO_JSON_FAILED = FSL_JSON_W_START + 3, -FSL_JSON_W_STRING_TO_ARRAY_FAILED = FSL_JSON_W_START + 4, - -FSL_JSON_W_END = 1000, -FSL_JSON_E_GENERIC = 1000, -FSL_JSON_E_GENERIC_SUB1 = FSL_JSON_E_GENERIC + 100, -FSL_JSON_E_INVALID_REQUEST = FSL_JSON_E_GENERIC_SUB1 + 1, -FSL_JSON_E_UNKNOWN_COMMAND = FSL_JSON_E_GENERIC_SUB1 + 2, -FSL_JSON_E_UNKNOWN = FSL_JSON_E_GENERIC_SUB1 + 3, -FSL_JSON_E_RESOURCE_NOT_FOUND = FSL_JSON_E_GENERIC_SUB1 + 4, -FSL_JSON_E_TIMEOUT = FSL_JSON_E_GENERIC_SUB1 + 5, -FSL_JSON_E_ASSERT = FSL_JSON_E_GENERIC_SUB1 + 6, -FSL_JSON_E_ALLOC = FSL_JSON_E_GENERIC_SUB1 + 7, -FSL_JSON_E_NYI = FSL_JSON_E_GENERIC_SUB1 + 8, -FSL_JSON_E_PANIC = FSL_JSON_E_GENERIC_SUB1 + 9, - -FSL_JSON_E_AUTH = 2000, -FSL_JSON_E_MISSING_AUTH = FSL_JSON_E_AUTH + 1, -FSL_JSON_E_DENIED = FSL_JSON_E_AUTH + 2, -FSL_JSON_E_WRONG_MODE = FSL_JSON_E_AUTH + 3, -FSL_JSON_E_RESOURCE_ALREADY_EXISTS = FSL_JSON_E_AUTH + 4, - -FSL_JSON_E_LOGIN_FAILED = FSL_JSON_E_AUTH + 100, -FSL_JSON_E_LOGIN_FAILED_NOSEED = FSL_JSON_E_LOGIN_FAILED + 1, -FSL_JSON_E_LOGIN_FAILED_NONAME = FSL_JSON_E_LOGIN_FAILED + 2, -FSL_JSON_E_LOGIN_FAILED_NOPW = FSL_JSON_E_LOGIN_FAILED + 3, -FSL_JSON_E_LOGIN_FAILED_NOTFOUND = FSL_JSON_E_LOGIN_FAILED + 4, - -FSL_JSON_E_USAGE = 3000, -FSL_JSON_E_INVALID_ARGS = FSL_JSON_E_USAGE + 1, -FSL_JSON_E_MISSING_ARGS = FSL_JSON_E_USAGE + 2, - -FSL_JSON_E_DB = 4000, -FSL_JSON_E_STMT_PREP = FSL_JSON_E_DB + 1, -FSL_JSON_E_STMT_BIND = FSL_JSON_E_DB + 2, -FSL_JSON_E_STMT_EXEC = FSL_JSON_E_DB + 3, -FSL_JSON_E_DB_LOCKED = FSL_JSON_E_DB + 4, - -FSL_JSON_E_DB_NEEDS_REBUILD = FSL_JSON_E_DB + 101 - -}; - - -/* -** Signature for JSON page/command callbacks. Each callback is -** responsible for handling one JSON request/command and/or -** dispatching to sub-commands. -** -** By the time the callback is called, json_page_top() (HTTP mode) or -** json_cmd_top() (CLI mode) will have set up the JSON-related -** environment. Implementations may generate a "result payload" of any -** JSON type by returning its value from this function (ownership is -** tranferred to the caller). On error they should set -** g.json.resultCode to one of the FossilJsonCodes values and return -** either their payload object or NULL. Note that NULL is a legal -** success value - it simply means the response will contain no -** payload. If g.json.resultCode is non-zero when this function -** returns then the top-level dispatcher will destroy any payload -** returned by this function and will output a JSON error response -** instead. -** -** All of the setup/response code is handled by the top dispatcher -** functions and the callbacks concern themselves only with generating -** the payload. -** -** It is imperitive that NO callback functions EVER output ANYTHING to -** stdout, as that will effectively corrupt any JSON output, and -** almost certainly will corrupt any HTTP response headers. Output -** sent to stderr ends up in my apache log, so that might be useful -** for debuggering in some cases, but so such code should be left -** enabled for non-debuggering builds. -*/ -typedef cson_value * (*fossil_json_f)(); - -/* -** Holds name-to-function mappings for JSON page/command dispatching. -** -*/ -typedef struct JsonPageDef{ - /* - ** The commmand/page's name (path, not including leading /json/). - ** - ** Reminder to self: we cannot use sub-paths with commands this way - ** without additional string-splitting downstream. e.g. foo/bar. - ** Alternately, we can create different JsonPageDef arrays for each - ** subset. - */ - char const * name; - /* - ** Returns a payload object for the response. If it returns a - ** non-NULL value, the caller owns it. To trigger an error this - ** function should set g.json.resultCode to a value from the - ** FossilJsonCodes enum. If it sets an error value and returns - ** a payload, the payload will be destroyed (not sent with the - ** response). - */ - fossil_json_f func; - /* - ** Which mode(s) of execution does func() support: - ** - ** <0 = CLI only, >0 = HTTP only, 0==both - */ - char runMode; -} JsonPageDef; - -/* -** Holds common keys used for various JSON API properties. -*/ -typedef struct FossilJsonKeys_{ - /** maintainers: please keep alpha sorted (case-insensitive) */ - char const * anonymousSeed; - char const * authToken; - char const * commandPath; - char const * payload; - char const * requestId; - char const * resultCode; - char const * resultText; - char const * timestamp; -} FossilJsonKeys_; -const FossilJsonKeys_ FossilJsonKeys; - -/* -** A page/command dispatch helper for fossil_json_f() implementations. -** pages must be an array of JsonPageDef commands which we can -** dispatch. The final item in the array MUST have a NULL name -** element. -** -** This function takes the command specified in -** json_comand_arg(1+g.json.dispatchDepth) and searches pages for a -** matching name. If found then that page's func() is called to fetch -** the payload, which is returned to the caller. -** -** On error, g.json.resultCode is set to one of the FossilJsonCodes -** values and NULL is returned. If non-NULL is returned, ownership is -** transfered to the caller. -*/ -cson_value * json_page_dispatch_helper(JsonPageDef const * pages); - -/* -** Implements the /json/wiki family of pages/commands. -** -*/ -cson_value * json_page_wiki(); - -/* -** Implements /json/timeline/wiki and /json/wiki/timeline. -*/ -cson_value * json_timeline_wiki(); - -/* -** Implements /json/timeline family of functions. -*/ -cson_value * json_page_timeline(); - -/* -** Convenience wrapper around cson_value_new_string(). -** Returns NULL if str is NULL or on allocation error. -*/ -cson_value * json_new_string( char const * str ); - -#endif/*FOSSIL_JSON_DETAIL_H_INCLUDED*/ DELETED src/json_login.c Index: src/json_login.c ================================================================== --- src/json_login.c +++ /dev/null @@ -1,240 +0,0 @@ -#include "config.h" -#include "json_login.h" - -#if INTERFACE -#include "json_detail.h" -#endif - - -/* -** Implementation of the /json/login page. -** -*/ -cson_value * json_page_login(){ - char preciseErrors = /* if true, "complete" JSON error codes are used, - else they are "dumbed down" to a generic login - error code. - */ -#if 1 - g.json.errorDetailParanoia ? 0 : 1 -#else - 0 -#endif - ; - /* - FIXME: we want to check the GET/POST args in this order: - - - GET: name, n, password, p - - POST: name, password - - but a bug in cgi_parameter() is breaking that, causing PD() to - return the last element of the PATH_INFO instead. - - Summary: If we check for P("name") first, then P("n"), - then ONLY a GET param of "name" will match ("n" - is not recognized). If we reverse the order of the - checks then both forms work. Strangely enough, the - "p"/"password" check is not affected by this. - */ - char const * name = cson_value_get_cstr(json_payload_property("name")); - char const * pw = NULL; - char const * anonSeed = NULL; - cson_value * payload = NULL; - int uid = 0; - /* reminder to self: - Fossil internally (for the sake of /wiki) interprets - paths in the form /foo/bar/baz such that - P("name") == "bar/baz". This collides with our - name/password checking, and thus we check for the - password first. - */ - pw = cson_value_get_cstr(json_payload_property("password")); - if( !pw ){ - pw = PD("p",NULL); - if( !pw ){ - pw = PD("password",NULL); - } - } - if(!pw){ - g.json.resultCode = preciseErrors - ? FSL_JSON_E_LOGIN_FAILED_NOPW - : FSL_JSON_E_LOGIN_FAILED; - return NULL; - } - - if( !name ){ - name = PD("n",NULL); - if( !name ){ - name = PD("name",NULL); - if( !name ){ - g.json.resultCode = preciseErrors - ? FSL_JSON_E_LOGIN_FAILED_NONAME - : FSL_JSON_E_LOGIN_FAILED; - return NULL; - } - } - } - - if(0 == strcmp("anonymous",name)){ - /* check captcha/seed values... */ - enum { SeedBufLen = 100 /* in some JSON tests i once actually got an - 80-digit number. - */ - }; - static char seedBuffer[SeedBufLen]; - cson_value const * jseed = json_getenv(FossilJsonKeys.anonymousSeed); - seedBuffer[0] = 0; - if( !jseed ){ - jseed = json_payload_property(FossilJsonKeys.anonymousSeed); - if( !jseed ){ - jseed = json_getenv("cs") /* name used by HTML interface */; - } - } - if(jseed){ - if( cson_value_is_number(jseed) ){ - sprintf(seedBuffer, "%"CSON_INT_T_PFMT, cson_value_get_integer(jseed)); - anonSeed = seedBuffer; - }else if( cson_value_is_string(jseed) ){ - anonSeed = cson_string_cstr(cson_value_get_string(jseed)); - } - } - if(!anonSeed){ - g.json.resultCode = preciseErrors - ? FSL_JSON_E_LOGIN_FAILED_NOSEED - : FSL_JSON_E_LOGIN_FAILED; - return NULL; - } - } - -#if 0 - { - /* only for debugging the PD()-incorrect-result problem */ - cson_object * o = NULL; - uid = login_search_uid( name, pw ); - payload = cson_value_new_object(); - o = cson_value_get_object(payload); - cson_object_set( o, "n", cson_value_new_string(name,strlen(name))); - cson_object_set( o, "p", cson_value_new_string(pw,strlen(pw))); - return payload; - } -#endif - uid = anonSeed - ? login_is_valid_anonymous(name, pw, anonSeed) - : login_search_uid(name, pw) - ; - if( !uid ){ - g.json.resultCode = preciseErrors - ? FSL_JSON_E_LOGIN_FAILED_NOTFOUND - : FSL_JSON_E_LOGIN_FAILED; - return NULL; - }else{ - char * cookie = NULL; - if(anonSeed){ - login_set_anon_cookie(NULL, &cookie); - }else{ - login_set_user_cookie(name, uid, &cookie); - } - /* FIXME: expand the payload to: - - { authToken:..., - name:..., - capabilities:... - } - */ - { - cson_object * po; - char * cap = NULL; - payload = cson_value_new_object(); - po = cson_value_get_object(payload); - cson_object_set(po, "authToken", json_new_string(cookie)); - cson_object_set(po, "name", json_new_string(name)); - cap = db_text(NULL,"SELECT cap FROM user WHERE login=%Q",name); - cson_object_set(po, "capabilities", json_new_string(cap)); - free(cap); - } - free(cookie); - return payload; - } - -} - -/* -** Impl of /json/logout. -** -*/ -cson_value * json_page_logout(){ - cson_value const *token = g.json.authToken; - /* Remember that json_mode_bootstrap() replaces the login cookie - with the JSON auth token if the request contains it. If the - reqest is missing the auth token then this will fetch fossil's - original cookie. Either way, it's what we want :). - - We require the auth token to avoid someone maliciously - trying to log someone else out (not 100% sure if that - would be possible, given fossil's hardened cookie, but - i'll assume it would be for the time being). - */ - ; - if(!token){ - g.json.resultCode = FSL_JSON_E_MISSING_AUTH; - }else{ - login_clear_login_data(); - g.json.authToken = NULL /* memory is owned elsewhere.*/; - } - return NULL; -} - -/* -** Implementation of the /json/anonymousPassword page. -*/ -cson_value * json_page_anon_password(){ - cson_value * v = cson_value_new_object(); - cson_object * o = cson_value_get_object(v); - unsigned const int seed = captcha_seed(); - char const * zCaptcha = captcha_decode(seed); - cson_object_set(o, "seed", - cson_value_new_integer( (cson_int_t)seed ) - ); - cson_object_set(o, "password", - cson_value_new_string( zCaptcha, strlen(zCaptcha) ) - ); - return v; -} - - - -/* -** Implements the /json/whoami page/command. -*/ -cson_value * json_page_whoami(){ - cson_value * payload = NULL; - cson_object * obj = NULL; - Stmt q; - db_prepare(&q, "SELECT login, cap FROM user WHERE uid=%d", g.userUid); - if( db_step(&q)==SQLITE_ROW ){ - - /* reminder: we don't use g.zLogin because it's 0 for the guest - user and the HTML UI appears to currently allow the name to be - changed (but doing so would break other code). */ - char const * str; - payload = cson_value_new_object(); - obj = cson_value_get_object(payload); - str = (char const *)sqlite3_column_text(q.pStmt,0); - if( str ){ - cson_object_set( obj, "name", - cson_value_new_string(str,strlen(str)) ); - } - str = (char const *)sqlite3_column_text(q.pStmt,1); - if( str ){ - cson_object_set( obj, "capabilities", - cson_value_new_string(str,strlen(str)) ); - } - if( g.json.authToken ){ - cson_object_set( obj, "authToken", g.json.authToken ); - } - }else{ - g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; - } - db_finalize(&q); - return payload; -} DELETED src/json_timeline.c Index: src/json_timeline.c ================================================================== --- src/json_timeline.c +++ /dev/null @@ -1,533 +0,0 @@ -#include "VERSION.h" -#include "config.h" -#include "json_timeline.h" - -#if INTERFACE -#include "json_detail.h" -#endif - -static cson_value * json_timeline_ci(); -static cson_value * json_timeline_ticket(); -/* -** Mapping of /json/timeline/XXX commands/paths to callbacks. -*/ -static const JsonPageDef JsonPageDefs_Timeline[] = { -{"c", json_timeline_ci, 0}, -{"ci", json_timeline_ci, 0}, -{"com", json_timeline_ci, 0}, -{"commit", json_timeline_ci, 0}, -{"t", json_timeline_ticket, 0}, -{"ticket", json_timeline_ticket, 0}, -{"w", json_timeline_wiki, 0}, -{"wi", json_timeline_wiki, 0}, -{"wik", json_timeline_wiki, 0}, -{"wiki", json_timeline_wiki, 0}, -/* Last entry MUST have a NULL name. */ -{NULL,NULL,0} -}; - - -/* -** Implements the /json/timeline family of pages/commands. Far from -** complete. -** -*/ -cson_value * json_page_timeline(){ - return json_page_dispatch_helper(&JsonPageDefs_Timeline[0]); -} - -/* -** Create a temporary table suitable for storing timeline data. -*/ -static void json_timeline_temp_table(void){ - /* Field order MUST match that from json_timeline_query()!!! */ - static const char zSql[] = - @ CREATE TEMP TABLE IF NOT EXISTS json_timeline( - @ sortId INTEGER PRIMARY KEY, - @ rid INTEGER, - @ uuid TEXT, - @ mtime INTEGER, - @ timestampString TEXT, - @ comment TEXT, - @ user TEXT, - @ isLeaf BOOLEAN, - @ bgColor TEXT, - @ eventType TEXT, - @ tags TEXT, - @ tagId INTEGER, - @ brief TEXT - @ ) - ; - db_multi_exec(zSql); -} - -/* -** Return a pointer to a constant string that forms the basis -** for a timeline query for the JSON interface. -*/ -const char const * json_timeline_query(void){ - /* Field order MUST match that from json_timeline_temp_table()!!! */ - static const char zBaseSql[] = - @ SELECT - @ NULL, - @ blob.rid, - @ uuid, - @ strftime('%%s',event.mtime), - @ datetime(event.mtime,'utc'), - @ coalesce(ecomment, comment), - @ coalesce(euser, user), - @ blob.rid IN leaf, - @ bgcolor, - @ event.type, - @ (SELECT group_concat(substr(tagname,5), ',') FROM tag, tagxref - @ WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid - @ AND tagxref.rid=blob.rid AND tagxref.tagtype>0), - @ tagid, - @ brief - @ FROM event JOIN blob - @ WHERE blob.rid=event.objid - ; - return zBaseSql; -} - -/* -** Helper for the timeline family of functions. Possibly appends 1 -** AND clause and an ORDER BY clause to pSql, depending on the state -** of the "after" ("a") or "before" ("b") environment parameters. -** This function gives "after" precedence over "before", and only -** applies one of them. -** -** Returns -1 if it adds a "before" clause, 1 if it adds -** an "after" clause, and 0 if adds only an order-by clause. -*/ -static char json_timeline_add_time_clause(Blob *pSql){ - char const * zAfter = NULL; - char const * zBefore = NULL; - if( g.isHTTP ){ - /** - FIXME: we are only honoring STRING values here, not int (for - passing Unix Epoch times). - */ - zAfter = json_getenv_cstr("after"); - if(!zAfter || !*zAfter){ - zAfter = json_getenv_cstr("a"); - } - if(!zAfter){ - zBefore = json_getenv_cstr("before"); - if(!zBefore||!*zBefore){ - zBefore = json_getenv_cstr("b"); - } - } - }else{ - zAfter = find_option("after","a",1); - zBefore = zAfter ? NULL : find_option("before","b",1); - } - if(zAfter&&*zAfter){ - while( fossil_isspace(*zAfter) ) ++zAfter; - blob_appendf(pSql, - " AND event.mtime>=(SELECT julianday(%Q,'utc')) " - " ORDER BY event.mtime ASC ", - zAfter); - return 1; - }else if(zBefore && *zBefore){ - while( fossil_isspace(*zBefore) ) ++zBefore; - blob_appendf(pSql, - " AND event.mtime<=(SELECT julianday(%Q,'utc')) " - " ORDER BY event.mtime DESC ", - zBefore); - return -1; - }else{ - blob_append(pSql," ORDER BY event.mtime DESC ", -1); - return 0; - } -} - -/* -** Tries to figure out a timeline query length limit base on -** environment parameters. If it can it returns that value, -** else it returns some statically defined default value. -** -** Never returns a negative value. 0 means no limit. -*/ -static int json_timeline_limit(){ - static const int defaultLimit = 20; - int limit = -1; - if( g.isHTTP ){ - limit = json_getenv_int("limit",-1); - if(limit<0){ - limit = json_getenv_int("n",-1); - } - }else{/* CLI mode */ - char const * arg = find_option("limit","n",1); - if(arg && *arg){ - limit = atoi(arg); - } - } - return (limit<0) ? defaultLimit : limit; -} - -/* -** Internal helper for the json_timeline_EVENTTYPE() family of -** functions. zEventType must be one of (ci, w, t). pSql must be a -** cleanly-initialized, empty Blob to store the sql in. If pPayload is -** not NULL it is assumed to be the pending response payload. If -** json_timeline_limit() returns non-0, this function adds a LIMIT -** clause to the generated SQL and (if pPayload is not NULL) adds the -** limit value as the "limit" property of pPayload. -*/ -static void json_timeline_setup_sql( char const * zEventType, - Blob * pSql, - cson_object * pPayload ){ - int limit; - assert( zEventType && *zEventType && pSql ); - json_timeline_temp_table(); - blob_append(pSql, "INSERT OR IGNORE INTO json_timeline ", -1); - blob_append(pSql, json_timeline_query(), -1 ); - blob_appendf(pSql, " AND event.type IN(%Q) ", zEventType); - json_timeline_add_time_clause(pSql); - limit = json_timeline_limit(); - if(limit){ - blob_appendf(pSql,"LIMIT %d ",limit); - } - if(pPayload){ - cson_object_set(pPayload, "limit",cson_value_new_integer(limit)); - } - -} - -static cson_value * json_timeline_get_changed_files(int rid){ - cson_value * rowsV = NULL; - cson_array * rows = NULL; - Stmt q; - db_prepare(&q, -#if 0 - "SELECT (mlink.pid==0) AS isNew," - " (mlink.fid==0) AS isDel," - " filename.name AS name" - " FROM mlink, filename" - " WHERE mid=%d" - " AND pid!=fid" - " AND filename.fnid=mlink.fnid" - " ORDER BY 3 /*sort*/", -#else - "SELECT (pid==0) AS isnew," - " (fid==0) AS isdel," - " (SELECT name FROM filename WHERE fnid=mlink.fnid) AS name," - " (SELECT uuid FROM blob WHERE rid=fid)," - " (SELECT uuid FROM blob WHERE rid=pid)" - " FROM mlink" - " WHERE mid=%d AND pid!=fid" - " ORDER BY 3 /*sort*/", -#endif - rid - ); - while( (SQLITE_ROW == db_step(&q)) ){ - cson_value * rowV = cson_value_new_object(); - cson_object * row = cson_value_get_object(rowV); - int const isNew = db_column_int(&q,0); - int const isDel = db_column_int(&q,1); - if(!rowsV){ - rowsV = cson_value_new_array(); - rows = cson_value_get_array(rowsV); - } - cson_object_set(row, "name", json_new_string(db_column_text(&q,2))); - cson_object_set(row, "uuid", json_new_string(db_column_text(&q,3))); - if(!isNew){ - cson_object_set(row, "prevUuid", json_new_string(db_column_text(&q,4))); - } - cson_object_set(row, "state", - json_new_string(isNew - ? "added" - : (isDel - ? "removed" - : "modified"))); - cson_array_append( rows, rowV ); - } - db_finalize(&q); - return rowsV; -} -/* -** Implementation of /json/timeline/ci. -** -** Still a few TODOs (like figuring out how to structure -** inheritance info). -*/ -static cson_value * json_timeline_ci(){ - cson_value * payV = NULL; - cson_object * pay = NULL; - cson_value * tmp = NULL; - cson_value * listV = NULL; - cson_array * list = NULL; - int check = 0; - int showFiles = 0; - Stmt q; - char warnRowToJsonFailed = 0; - char warnStringToArrayFailed = 0; - Blob sql = empty_blob; - if( !g.perm.Read/* && !g.perm.RdTkt && !g.perm.RdWiki*/ ){ - g.json.resultCode = FSL_JSON_E_DENIED; - return NULL; - } - if( g.isHTTP ){ - showFiles = json_getenv_bool("showFiles",0); - }else{ - showFiles = 0!=find_option("show-files", "f",0); - } - payV = cson_value_new_object(); - pay = cson_value_get_object(payV); - json_timeline_setup_sql( "ci", &sql, pay ); -#define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ - g.json.resultCode = (cson_rc.AllocError==check) \ - ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN; \ - goto error;\ - } -#if 0 - /* only for testing! */ - tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql))); - SET("timelineSql"); -#endif - db_multi_exec(blob_buffer(&sql)); - blob_reset(&sql); - db_prepare(&q, "SELECT " - " rid AS rid," - " uuid AS uuid," - " mtime AS timestamp," -#if 0 - " timestampString AS timestampString," -#endif - " comment AS comment, " - " user AS user," - " isLeaf AS isLeaf," /*FIXME: convert to JSON bool */ - " bgColor AS bgColor," /* why always null? */ - " eventType AS eventType," - " tags AS tags" /*FIXME: split this into - a JSON array*/ -#if 0 - /*tagId is always null?*/ - " tagId AS tagId" -#endif - " FROM json_timeline" - " ORDER BY sortId"); - listV = cson_value_new_array(); - list = cson_value_get_array(listV); - tmp = listV; - SET("timeline"); - while( (SQLITE_ROW == db_step(&q) )){ - /* convert each row into a JSON object...*/ - int const rid = db_column_int(&q,0); - cson_value * rowV = cson_sqlite3_row_to_object(q.pStmt); - cson_object * row = cson_value_get_object(rowV); - cson_string const * tagsStr = NULL; - if(!row && !warnRowToJsonFailed){ - warnRowToJsonFailed = 1; - json_warn( FSL_JSON_W_ROW_TO_JSON_FAILED, - "Could not convert at least one timeline result row to JSON." ); - continue; - } - /* Split tags string field into JSON Array... */ - cson_array_append(list, rowV); - tagsStr = cson_value_get_string(cson_object_get(row,"tags")); - if(tagsStr){ - cson_value * tags = json_string_split2( cson_string_cstr(tagsStr), - ',', 0); - if( tags ){ - if(0 != cson_object_set(row,"tags",tags)){ - cson_value_free(tags); - }else{ - /*replaced/deleted old tags value, invalidating tagsStr*/; - tagsStr = NULL; - } - }else if(!warnStringToArrayFailed){ - warnStringToArrayFailed = 1; - json_warn(FSL_JSON_W_STRING_TO_ARRAY_FAILED, - "Could not convert tags string to array."); - } - } - - /* replace isLeaf int w/ JSON bool */ - tmp = cson_object_get(row,"isLeaf"); - if(tmp && cson_value_is_integer(tmp)){ - cson_object_set(row,"isLeaf", - cson_value_get_integer(tmp) - ? cson_value_true() - : cson_value_false()); - tmp = NULL; - } - if( showFiles ){ - cson_value * flist = json_timeline_get_changed_files(rid); - if(flist){ - cson_object_set(row,"files",flist); - } - } - } -#undef SET - goto ok; - error: - assert( 0 != g.json.resultCode ); - cson_value_free(payV); - payV = NULL; - ok: - db_finalize(&q); - return payV; -} - -/* -** Implementation of /json/timeline/wiki. -** -*/ -cson_value * json_timeline_wiki(){ - /* This code is 95% the same as json_timeline_ci(), by the way. */ - cson_value * payV = NULL; - cson_object * pay = NULL; - cson_value * tmp = NULL; - cson_value * listV = NULL; - cson_array * list = NULL; - int check = 0; - Stmt q; - Blob sql = empty_blob; - if( !g.perm.Read || !g.perm.RdWiki ){ - g.json.resultCode = FSL_JSON_E_DENIED; - return NULL; - } - payV = cson_value_new_object(); - pay = cson_value_get_object(payV); - json_timeline_setup_sql( "w", &sql, pay ); -#define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ - g.json.resultCode = (cson_rc.AllocError==check) \ - ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN; \ - goto error;\ - } -#if 0 - /* only for testing! */ - tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql))); - SET("timelineSql"); -#endif - db_multi_exec(blob_buffer(&sql)); - blob_reset(&sql); - db_prepare(&q, "SELECT rid AS rid," - " uuid AS uuid," - " mtime AS timestamp," -#if 0 - " timestampString AS timestampString," -#endif - " comment AS comment, " - " user AS user," - " eventType AS eventType" -#if 0 - /* can wiki pages have tags? */ - " tags AS tags," /*FIXME: split this into - a JSON array*/ - " tagId AS tagId," -#endif - " FROM json_timeline" - " ORDER BY sortId", - -1); - listV = cson_value_new_array(); - list = cson_value_get_array(listV); - tmp = listV; - SET("timeline"); - json_stmt_to_array_of_obj(&q, listV); -#undef SET - goto ok; - error: - assert( 0 != g.json.resultCode ); - cson_value_free(payV); - payV = NULL; - ok: - db_finalize(&q); - return payV; -} - -/* -** Implementation of /json/timeline/ticket. -** -*/ -static cson_value * json_timeline_ticket(){ - /* This code is 95% the same as json_timeline_ci(), by the way. */ - cson_value * payV = NULL; - cson_object * pay = NULL; - cson_value * tmp = NULL; - cson_value * listV = NULL; - cson_array * list = NULL; - int check = 0; - Stmt q; - Blob sql = empty_blob; - if( !g.perm.Read || !g.perm.RdTkt ){ - g.json.resultCode = FSL_JSON_E_DENIED; - return NULL; - } - payV = cson_value_new_object(); - pay = cson_value_get_object(payV); - json_timeline_setup_sql( "t", &sql, pay ); - db_multi_exec(blob_buffer(&sql)); -#define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ - g.json.resultCode = (cson_rc.AllocError==check) \ - ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN; \ - goto error;\ - } - -#if 0 - /* only for testing! */ - tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql))); - SET("timelineSql"); -#endif - - blob_reset(&sql); - db_prepare(&q, "SELECT rid AS rid," - " uuid AS uuid," - " mtime AS timestamp," -#if 0 - " timestampString AS timestampString," -#endif - " user AS user," - " eventType AS eventType," - " comment AS comment," - " brief AS briefComment" - " FROM json_timeline" - " ORDER BY sortId", - -1); - listV = cson_value_new_array(); - list = cson_value_get_array(listV); - tmp = listV; - SET("timeline"); - while( (SQLITE_ROW == db_step(&q) )){ - /* convert each row into a JSON object...*/ - int rc; - int const rid = db_column_int(&q,0); - Manifest * pMan = NULL; - cson_value * rowV = cson_sqlite3_row_to_object(q.pStmt); - cson_object * row = cson_value_get_object(rowV); - if(!row){ - json_warn( FSL_JSON_W_ROW_TO_JSON_FAILED, - "Could not convert at least one timeline result row to JSON." ); - continue; - } - pMan = manifest_get(rid, CFTYPE_TICKET); - assert( pMan && "Manifest is NULL!?!" ); - if( pMan ){ - /* FIXME: certainly there's a more efficient way for use to get - the ticket UUIDs? - */ - cson_object_set(row,"ticketUuid",json_new_string(pMan->zTicketUuid)); - manifest_destroy(pMan); - } - rc = cson_array_append( list, rowV ); - if( 0 != rc ){ - cson_value_free(rowV); - g.json.resultCode = (cson_rc.AllocError==rc) - ? FSL_JSON_E_ALLOC - : FSL_JSON_E_UNKNOWN; - goto error; - } - } -#undef SET - goto ok; - error: - assert( 0 != g.json.resultCode ); - cson_value_free(payV); - payV = NULL; - ok: - db_finalize(&q); - return payV; -} - DELETED src/json_wiki.c Index: src/json_wiki.c ================================================================== --- src/json_wiki.c +++ /dev/null @@ -1,251 +0,0 @@ -#include "VERSION.h" -#include "config.h" -#include "json_wiki.h" - -#if INTERFACE -#include "json_detail.h" -#endif - -static cson_value * json_wiki_create(); -static cson_value * json_wiki_get(); -static cson_value * json_wiki_list(); -static cson_value * json_wiki_save(); - -/* -** Mapping of /json/wiki/XXX commands/paths to callbacks. -*/ -static const JsonPageDef JsonPageDefs_Wiki[] = { -{"create", json_wiki_create, 1}, -{"get", json_wiki_get, 0}, -{"list", json_wiki_list, 0}, -{"save", json_wiki_save, 1}, -{"timeline", json_timeline_wiki,0}, -/* Last entry MUST have a NULL name. */ -{NULL,NULL,0} -}; - - -/* -** Implements the /json/wiki family of pages/commands. -** -*/ -cson_value * json_page_wiki(){ - return json_page_dispatch_helper(&JsonPageDefs_Wiki[0]); -} - - -/* -** Implementation of /json/wiki/get. -** -** TODO: add option to parse wiki output. It is currently -** unparsed. -*/ -static cson_value * json_wiki_get(){ - int rid; - Manifest *pWiki = 0; - char const * zBody = NULL; - char const * zPageName; - char doParse = 0/*not yet implemented*/; - - if( !g.perm.RdWiki ){ - g.json.resultCode = FSL_JSON_E_DENIED; - return NULL; - } - zPageName = g.isHTTP - ? json_getenv_cstr("page") - : find_option("page","p",1); - if(!zPageName||!*zPageName){ - g.json.resultCode = FSL_JSON_E_MISSING_ARGS; - return NULL; - } - rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x" - " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'" - " ORDER BY x.mtime DESC LIMIT 1", - zPageName - ); - if( (pWiki = manifest_get(rid, CFTYPE_WIKI))!=0 ){ - zBody = pWiki->zWiki; - } - if( zBody==0 ){ - manifest_destroy(pWiki); - g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; - return NULL; - }else{ - unsigned int const len = strlen(zBody); - cson_value * payV = cson_value_new_object(); - cson_object * pay = cson_value_get_object(payV); - cson_object_set(pay,"name",json_new_string(zPageName)); - cson_object_set(pay,"version",json_new_string(pWiki->zBaseline)) - /*FIXME: pWiki->zBaseline is NULL. How to get the version number?*/ - ; - cson_object_set(pay,"rid",cson_value_new_integer((cson_int_t)rid)); - cson_object_set(pay,"lastSavedBy",json_new_string(pWiki->zUser)); - cson_object_set(pay,FossilJsonKeys.timestamp, json_julian_to_timestamp(pWiki->rDate)); - cson_object_set(pay,"contentLength",cson_value_new_integer((cson_int_t)len)); - cson_object_set(pay,"contentFormat",json_new_string(doParse?"html":"raw")); - cson_object_set(pay,"content",cson_value_new_string(zBody,len)); - /*TODO: add 'T' (tag) fields*/ - /*TODO: add the 'A' card (file attachment) entries?*/ - manifest_destroy(pWiki); - return payV; - } -} - -/* -** Internal impl of /wiki/save and /wiki/create. If createMode is 0 -** and the page already exists then a -** FSL_JSON_E_RESOURCE_ALREADY_EXISTS error is triggered. If -** createMode is false then the FSL_JSON_E_RESOURCE_NOT_FOUND is -** triggered if the page does not already exists. -** -** Note that the error triggered when createMode==0 and no such page -** exists is rather arbitrary - we could just as well create the entry -** here if it doesn't already exist. With that, save/create would -** become one operation. That said, i expect there are people who -** would categorize such behaviour as "being too clever" or "doing too -** much automatically" (and i would likely agree with them). -** -** If allowCreateIfExists is true then this function will allow a new -** page to be created even if createMode is false. -*/ -static cson_value * json_wiki_create_or_save(char createMode, - char allowCreateIfExists){ - Blob content = empty_blob; - cson_value * nameV; - cson_value * contentV; - cson_value * emptyContent = NULL; - cson_value * payV = NULL; - cson_object * pay = NULL; - cson_string const * jstr = NULL; - char const * zContent; - char const * zBody = NULL; - char const * zPageName; - unsigned int contentLen = 0; - int rid; - if( (createMode && !g.perm.NewWiki) - || (!createMode && !g.perm.WrWiki)){ - g.json.resultCode = FSL_JSON_E_DENIED; - return NULL; - } - nameV = json_req_payload_get("name"); - if(!nameV){ - g.json.resultCode = FSL_JSON_E_MISSING_ARGS; - goto error; - } - zPageName = cson_string_cstr(cson_value_get_string(nameV)); - rid = db_int(0, - "SELECT x.rid FROM tag t, tagxref x" - " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'" - " ORDER BY x.mtime DESC LIMIT 1", - zPageName - ); - - if(rid){ - if(createMode){ - g.json.resultCode = FSL_JSON_E_RESOURCE_ALREADY_EXISTS; - goto error; - } - }else if(!allowCreateIfExists){ - g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; - goto error; - } - - contentV = json_req_payload_get("content"); - if( !contentV ){ - if( createMode || (!rid && allowCreateIfExists) ){ - contentV = emptyContent = cson_value_new_string("",0); - }else{ - g.json.resultCode = FSL_JSON_E_MISSING_ARGS; - goto error; - } - } - if( !cson_value_is_string(nameV) - || !cson_value_is_string(contentV)){ - g.json.resultCode = FSL_JSON_E_INVALID_ARGS; - goto error; - } - jstr = cson_value_get_string(contentV); - contentLen = (int)cson_string_length_bytes(jstr); - if(contentLen){ - blob_append(&content, cson_string_cstr(jstr),contentLen); - } - wiki_cmd_commit(zPageName, 0==rid, &content); - blob_reset(&content); - - payV = cson_value_new_object(); - pay = cson_value_get_object(payV); - cson_object_set( pay, "name", nameV ); - cson_object_set( pay, FossilJsonKeys.timestamp, - json_new_timestamp(-1) ); - - goto ok; - error: - assert( 0 != g.json.resultCode ); - cson_value_free(payV); - payV = NULL; - ok: - if( emptyContent ){ - /* We have some potentially tricky memory ownership - here, which is why we handle emptyContent separately. - - This is, in fact, overkill because cson_value_new_string("",0) - actually returns a shared singleton instance (i.e. doesn't - allocate), but that is a cson implementation detail which i - don't want leaking into this code... - */ - cson_value_free(emptyContent); - } - return payV; - -} - -/* -** Implementation of /json/wiki/create. -*/ -static cson_value * json_wiki_create(){ - return json_wiki_create_or_save(1,0); -} - -/* -** Implementation of /json/wiki/save. -*/ -static cson_value * json_wiki_save(){ - char const createIfNotExists = json_getenv_bool("createIfNotExists",0); - return json_wiki_create_or_save(0,createIfNotExists); -} - -/* -** Implementation of /json/wiki/list. -*/ -static cson_value * json_wiki_list(){ - cson_value * listV = NULL; - cson_array * list = NULL; - Stmt q; - if( !g.perm.RdWiki ){ - g.json.resultCode = FSL_JSON_E_DENIED; - return NULL; - } - db_prepare(&q,"SELECT" - " substr(tagname,6) as name" - " FROM tag WHERE tagname GLOB 'wiki-*'" - " ORDER BY lower(name)"); - listV = cson_value_new_array(); - list = cson_value_get_array(listV); - while( SQLITE_ROW == db_step(&q) ){ - cson_value * v = cson_sqlite3_column_to_value(q.pStmt,0); - if(!v){ - goto error; - }else if( 0 != cson_array_append( list, v ) ){ - cson_value_free(v); - goto error; - } - } - goto end; - error: - g.json.resultCode = FSL_JSON_E_UNKNOWN; - cson_value_free(listV); - listV = NULL; - end: - db_finalize(&q); - return listV; -} Index: src/login.c ================================================================== --- src/login.c +++ src/login.c @@ -84,18 +84,18 @@ ** ** The login cookie name is always of the form: fossil-XXXXXXXXXXXXXXXX ** where the Xs are the first 16 characters of the login-group-code or ** of the project-code if we are not a member of any login-group. */ -char *login_cookie_name(void){ +static char *login_cookie_name(void){ static char *zCookieName = 0; if( zCookieName==0 ){ zCookieName = db_text(0, "SELECT 'fossil-' || substr(value,1,16)" " FROM config" " WHERE name IN ('project-code','login-group-code')" - " ORDER BY name;" + " ORDER BY name /*sort*/" ); } return zCookieName; } @@ -141,26 +141,24 @@ /* ** Check to see if the anonymous login is valid. If it is valid, return ** the userid of the anonymous user. -** -** The zCS parameter is the "captcha seed" used for a specific -** anonymous login request. */ -int login_is_valid_anonymous( +static int isValidAnonymousLogin( const char *zUsername, /* The username. Must be "anonymous" */ - const char *zPassword, /* The supplied password */ - const char *zCS /* The captcha seed value */ + const char *zPassword /* The supplied password */ ){ + const char *zCS; /* The captcha seed value */ const char *zPw; /* The correct password shown in the captcha */ int uid; /* The user ID of anonymous */ if( zUsername==0 ) return 0; - else if( zPassword==0 ) return 0; - else if( zCS==0 ) return 0; - else if( fossil_strcmp(zUsername,"anonymous")!=0 ) return 0; + if( zPassword==0 ) return 0; + if( fossil_strcmp(zUsername,"anonymous")!=0 ) return 0; + zCS = P("cs"); /* The "cs" parameter is the "captcha seed" */ + if( zCS==0 ) return 0; zPw = captcha_decode((unsigned int)atoi(zCS)); if( fossil_stricmp(zPw, zPassword)!=0 ) return 0; uid = db_int(0, "SELECT uid FROM user WHERE login='anonymous'" " AND length(pw)>0 AND length(cap)>0"); return uid; @@ -195,161 +193,10 @@ "VALUES(%Q,%Q,%d,julianday('now'));", zUsername, zIpAddr, bSuccess ); } -/* -** Searches for the user ID matching the given name and password. -** On success it returns a positive value. On error it returns 0. -** On serious (DB-level) error it will probably exit. -** -** zPassword may be either the plain-text form or the encrypted -** form of the user's password. -*/ -int login_search_uid(char const *zUsername, char const *zPasswd){ - char * zSha1Pw = sha1_shared_secret(zPasswd, zUsername, 0); - int const uid = - db_int(0, - "SELECT uid FROM user" - " WHERE login=%Q" - " AND length(cap)>0 AND length(pw)>0" - " AND login NOT IN ('anonymous','nobody','developer','reader')" - " AND (pw=%Q OR pw=%Q)", - zUsername, zPasswd, zSha1Pw - ); - free(zSha1Pw); - return uid; -} - -/* -** Generates a login cookie value for a non-anonymous user. -** -** The zHash parameter must be a random value which must be -** subsequently stored in user.cookie for later validation. -** -** The returned memory should be free()d after use. -*/ -char * login_gen_user_cookie_value(char const *zUsername, char const * zHash){ - char *zCode = abbreviated_project_code(db_get("project-code","")); - assert((zUsername && *zUsername) && "Invalid user data."); - return mprintf("%s/%z/%s", zHash, zCode, zUsername); -} - -/* -** Generates a login cookie for NON-ANONYMOUS users. Note that this -** function "could" figure out the uid by itself but it currently -** doesn't because the code which calls this already has the uid. -** -** This function also updates the user.cookie, user.ipaddr, -** and user.cexpire fields for the given user. -** -** If zDest is not NULL then the generated cookie is copied to -** *zDdest and ownership is transfered to the caller (who should -** eventually pass it to free()). -*/ -void login_set_user_cookie( - char const * zUsername, /* User's name */ - int uid, /* User's ID */ - char ** zDest /* Optional: store generated cookie value. */ -){ - const char *zCookieName = login_cookie_name(); - const char *zExpire = db_get("cookie-expire","8766"); - int expires = atoi(zExpire)*3600; - char *zHash; - char *zCookie; - char const * zIpAddr = PD("REMOTE_ADDR","nil"); /* Complete IP address for logging */ - char * zRemoteAddr = ipPrefix(zIpAddr); /* Abbreviated IP address */ - assert((zUsername && *zUsername) && (uid > 0) && "Invalid user data."); - zHash = db_text(0, "SELECT hex(randomblob(25))"); - zCookie = login_gen_user_cookie_value(zUsername, zHash); - cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires); - record_login_attempt(zUsername, zIpAddr, 1); - db_multi_exec( - "UPDATE user SET cookie=%Q, ipaddr=%Q, " - " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d", - zHash, zRemoteAddr, expires, uid - ); - free(zRemoteAddr); - free(zHash); - if( zDest ){ - *zDest = zCookie; - }else{ - free(zCookie); - } -} - -/* Sets a cookie for an anonymous user login, which looks like this: -** -** HASH/TIME/anonymous -** -** Where HASH is the sha1sum of TIME/IPADDR/SECRET, in which IPADDR -** is the abbreviated IP address and SECRET is captcha-secret. -** -** If either zIpAddr or zRemoteAddr are NULL then REMOTE_ADDR -** is used. -** -** If zCookieDest is not NULL then the generated cookie is assigned to -** *zCookieDest and the caller must eventually free() it. -*/ -void login_set_anon_cookie(char const * zIpAddr, char ** zCookieDest ){ - char const *zNow; /* Current time (julian day number) */ - char *zCookie; /* The login cookie */ - char const *zCookieName; /* Name of the login cookie */ - Blob b; /* Blob used during cookie construction */ - char * zRemoteAddr; /* Abbreviated IP address */ - if(!zIpAddr){ - zIpAddr = PD("REMOTE_ADDR","nil"); - } - zRemoteAddr = ipPrefix(zIpAddr); - zCookieName = login_cookie_name(); - zNow = db_text("0", "SELECT julianday('now')"); - assert( zCookieName && zRemoteAddr && zIpAddr && zNow ); - blob_init(&b, zNow, -1); - blob_appendf(&b, "/%s/%s", zRemoteAddr, db_get("captcha-secret","")); - sha1sum_blob(&b, &b); - zCookie = mprintf("%s/%s/anonymous", blob_buffer(&b), zNow); - blob_reset(&b); - cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), 6*3600); - if( zCookieDest ){ - *zCookieDest = zCookie; - }else{ - free(zCookie); - } - -} - -/* -** "Unsets" the login cookie (insofar as cookies can be unset) and -** clears the current user's (g.userUid) login information from the -** user table. Sets: user.cookie, user.ipaddr, user.cexpire. -** -** We could/should arguably clear out g.userUid and g.perm here, but -** we don't currently do not. -** -** This is a no-op if g.userUid is 0. -*/ -void login_clear_login_data(){ - if(!g.userUid){ - return; - }else{ - char const * cookie = login_cookie_name(); - /* To logout, change the cookie value to an empty string */ - cgi_set_cookie(cookie, "", - login_cookie_path(), -86400); - db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, " - " cexpire=0 WHERE uid=%d" - " AND login NOT IN ('anonymous','guest'," - " 'developer','reader')", g.userUid); - cgi_replace_parameter(cookie, NULL) - /* At the time of this writing, cgi_replace_parameter() was - ** "NULL-value-safe", and i'm hoping the NULL doesn't cause any - ** downstream problems here. We could alternately use "" here. - */ - ; - } -} - /* ** WEBPAGE: login ** WEBPAGE: logout ** WEBPAGE: my ** @@ -367,25 +214,30 @@ int anonFlag; char *zErrMsg = ""; int uid; /* User id loged in user */ char *zSha1Pw; const char *zIpAddr; /* IP address of requestor */ + char *zRemoteAddr; /* Abbreviated IP address of requestor */ login_check_credentials(); zUsername = P("u"); zPasswd = P("p"); anonFlag = P("anon")!=0; if( P("out")!=0 ){ - login_clear_login_data(); + /* To logout, change the cookie value to an empty string */ + const char *zCookieName = login_cookie_name(); + cgi_set_cookie(zCookieName, "", login_cookie_path(), -86400); redirect_to_g(); } if( g.perm.Password && zPasswd && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0 ){ /* The user requests a password change */ zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0); if( db_int(1, "SELECT 0 FROM user" - " WHERE uid=%d AND (pw=%Q OR pw=%Q)", - g.userUid, zPasswd, zSha1Pw) ){ + " WHERE uid=%d" + " AND (constant_time_cmp(pw,%Q)=0" + " OR constant_time_cmp(pw,%Q)=0)", + g.userUid, zSha1Pw, zPasswd) ){ sleep(1); zErrMsg = @ <p><span class="loginError"> @ You entered an incorrect old password while attempting to change @ your password. Your password is unchanged. @@ -421,20 +273,50 @@ return; } } } zIpAddr = PD("REMOTE_ADDR","nil"); /* Complete IP address for logging */ - uid = login_is_valid_anonymous(zUsername, zPasswd, P("cs")); + zRemoteAddr = ipPrefix(zIpAddr); /* Abbreviated IP address */ + uid = isValidAnonymousLogin(zUsername, zPasswd); if( uid>0 ){ - login_set_anon_cookie(zIpAddr, NULL); + /* Successful login as anonymous. Set a cookie that looks like + ** this: + ** + ** HASH/TIME/anonymous + ** + ** Where HASH is the sha1sum of TIME/IPADDR/SECRET, in which IPADDR + ** is the abbreviated IP address and SECRET is captcha-secret. + */ + char *zNow; /* Current time (julian day number) */ + char *zCookie; /* The login cookie */ + const char *zCookieName; /* Name of the login cookie */ + Blob b; /* Blob used during cookie construction */ + + zCookieName = login_cookie_name(); + zNow = db_text("0", "SELECT julianday('now')"); + blob_init(&b, zNow, -1); + blob_appendf(&b, "/%s/%s", zRemoteAddr, db_get("captcha-secret","")); + sha1sum_blob(&b, &b); + zCookie = sqlite3_mprintf("%s/%s/anonymous", blob_buffer(&b), zNow); + blob_reset(&b); + free(zNow); + cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), 6*3600); record_login_attempt("anonymous", zIpAddr, 1); redirect_to_g(); } if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){ /* Attempting to log in as a user other than anonymous. */ - uid = login_search_uid(zUsername, zPasswd); + zSha1Pw = sha1_shared_secret(zPasswd, zUsername, 0); + uid = db_int(0, + "SELECT uid FROM user" + " WHERE login=%Q" + " AND length(cap)>0 AND length(pw)>0" + " AND login NOT IN ('anonymous','nobody','developer','reader')" + " AND (constant_time_cmp(pw,%Q)=0 OR constant_time_cmp(pw,%Q)=0)", + zUsername, zSha1Pw, zPasswd + ); if( uid<=0 ){ sleep(1); zErrMsg = @ <p><span class="loginError"> @ You entered an unknown user or an incorrect password. @@ -447,11 +329,26 @@ ** HASH/PROJECT/LOGIN ** ** where HASH is a random hex number, PROJECT is either project ** code prefix, and LOGIN is the user name. */ - login_set_user_cookie(zUsername, uid, NULL); + char *zCookie; + const char *zCookieName = login_cookie_name(); + const char *zExpire = db_get("cookie-expire","8766"); + int expires = atoi(zExpire)*3600; + char *zCode = abbreviated_project_code(db_get("project-code","")); + char *zHash; + + zHash = db_text(0, "SELECT hex(randomblob(25))"); + zCookie = mprintf("%s/%s/%s", zHash, zCode, zUsername); + cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires); + record_login_attempt(zUsername, zIpAddr, 1); + db_multi_exec( + "UPDATE user SET cookie=%Q, ipaddr=%Q, " + " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d", + zHash, zRemoteAddr, expires, uid + ); redirect_to_g(); } } style_header("Login/Logout"); @ %s(zErrMsg) @@ -556,10 +453,37 @@ @ </table> @ </form> } style_footer(); } + +/* +** SQL function for constant time comparison of two values. +** Sets result to 0 if two values are equal. +*/ +static void constant_time_cmp_function( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const unsigned char *buf1, *buf2; + int len, i; + unsigned char rc = 0; + + assert( argc==2 ); + len = sqlite3_value_bytes(argv[0]); + if( len==0 || len!=sqlite3_value_bytes(argv[1]) ){ + rc = 1; + }else{ + buf1 = sqlite3_value_text(argv[0]); + buf2 = sqlite3_value_text(argv[1]); + for( i=0; i<len; i++ ){ + rc = rc | (buf1[i] ^ buf2[i]); + } + } + sqlite3_result_int(context, rc); +} /* ** Attempt to find login credentials for user zLogin on a peer repository ** with project code zCode. Transfer those credentials to the local ** repository. @@ -586,20 +510,22 @@ if( zOtherRepo==0 ) return 0; /* No such peer repository */ rc = sqlite3_open(zOtherRepo, &pOther); if( rc==SQLITE_OK ){ sqlite3_create_function(pOther,"now",0,SQLITE_ANY,0,db_now_function,0,0); + sqlite3_create_function(pOther, "constant_time_cmp", 2, SQLITE_UTF8, 0, + constant_time_cmp_function, 0, 0); sqlite3_busy_timeout(pOther, 5000); zSQL = mprintf( "SELECT cexpire FROM user" - " WHERE cookie=%Q" + " WHERE login=%Q" " AND ipaddr=%Q" - " AND login=%Q" " AND length(cap)>0" " AND length(pw)>0" - " AND cexpire>julianday('now')", - zHash, zRemoteAddr, zLogin + " AND cexpire>julianday('now')" + " AND constant_time_cmp(cookie,%Q)=0", + zLogin, zRemoteAddr, zHash ); pStmt = 0; rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0); if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ db_multi_exec( @@ -616,16 +542,12 @@ fossil_free(zOtherRepo); return nXfer; } /* -** Lookup the uid for a non-built-in user with zLogin and zCookie and -** zRemoteAddr. Return 0 if not found. -** -** Note that this only searches for logged-in entries with matching -** zCookie (db: user.cookie) and zRemoteAddr (db: user.ipaddr) -** entries. +** Lookup the uid for a user with zLogin and zCookie and zRemoteAddr. +** Return 0 if not found. */ static int login_find_user( const char *zLogin, /* User name */ const char *zCookie, /* Login cookie value */ const char *zRemoteAddr /* Abbreviated IP address for valid login */ @@ -636,25 +558,27 @@ if( fossil_strcmp(zLogin, "developer")==0 ) return 0; if( fossil_strcmp(zLogin, "reader")==0 ) return 0; uid = db_int(0, "SELECT uid FROM user" " WHERE login=%Q" - " AND cookie=%Q" " AND ipaddr=%Q" " AND cexpire>julianday('now')" " AND length(cap)>0" - " AND length(pw)>0", - zLogin, zCookie, zRemoteAddr + " AND length(pw)>0" + " AND constant_time_cmp(cookie,%Q)=0", + zLogin, zRemoteAddr, zCookie ); return uid; } /* -** This routine examines the login cookie to see if it exists and and -** is valid. If the login cookie checks out, it then sets global -** variables appropriately. Global variables set include g.userUid -** and g.zLogin and the g.perm family of permission booleans. +** This routine examines the login cookie to see if it exists and +** and is valid. If the login cookie checks out, it then sets +** global variables appropriately. Global variables set include +** g.userUid and g.zLogin and of the g.perm.Read family of permission +** booleans. +** */ void login_check_credentials(void){ int uid = 0; /* User id */ const char *zCookie; /* Text of the login cookie */ const char *zIpAddr; /* Raw IP address of the requestor */ @@ -661,10 +585,13 @@ char *zRemoteAddr; /* Abbreviated IP address of the requestor */ const char *zCap = 0; /* Capability string */ /* Only run this check once. */ if( g.userUid!=0 ) return; + + sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0, + constant_time_cmp_function, 0, 0); /* If the HTTP connection is coming over 127.0.0.1 and if ** local login is disabled and if we are using HTTP and not HTTPS, ** then there is no need to check user credentials. ** @@ -739,11 +666,11 @@ } sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "%.10s", zHash); } /* If no user found and the REMOTE_USER environment variable is set, - ** then accept the value of REMOTE_USER as the user. + ** the accept the value of REMOTE_USER as the user. */ if( uid==0 ){ const char *zRemoteUser = P("REMOTE_USER"); if( zRemoteUser && db_get_boolean("remote_user_ok",0) ){ uid = db_int(0, "SELECT uid FROM user WHERE login=%Q" @@ -789,11 +716,11 @@ if( fossil_strcmp(g.zLogin,"nobody")==0 ){ g.zLogin = 0; } /* Set the capabilities */ - login_replace_capabilities(zCap, 0); + login_set_capabilities(zCap, 0); login_set_anon_nobody_capabilities(); } /* ** Memory of settings @@ -818,25 +745,22 @@ login_anon_once = 0; } } /* -** Flags passed into the 2nd argument of login_set/replace_capabilities(). +** Flags passed into the 2nd argument of login_set_capabilities(). */ #if INTERFACE #define LOGIN_IGNORE_U 0x01 /* Ignore "u" */ #define LOGIN_IGNORE_V 0x01 /* Ignore "v" */ #endif /* -** Adds all capability flags in zCap to g.perm. +** Set the global capability flags based on a capability string. */ void login_set_capabilities(const char *zCap, unsigned flags){ int i; - if(NULL==zCap){ - return; - } for(i=0; zCap[i]; i++){ switch( zCap[i] ){ case 's': g.perm.Setup = 1; /* Fall thru into Admin */ case 'a': g.perm.Admin = g.perm.RdTkt = g.perm.WrTkt = g.perm.Zip = g.perm.RdWiki = g.perm.WrWiki = g.perm.NewWiki = @@ -891,18 +815,10 @@ } } } } -/* -** Zeroes out g.perm and calls login_set_capabilities(zCap,flags). -*/ -void login_replace_capabilities(const char *zCap, unsigned flags){ - memset(&g.perm, 0, sizeof(g.perm)); - return login_set_capabilities(zCap, flags); -} - /* ** If the current login lacks any of the capabilities listed in ** the input, then return 0. If all capabilities are present, then ** return 1. */ @@ -976,19 +892,14 @@ /* ** Call this routine when the credential check fails. It causes ** a redirect to the "login" page. */ void login_needed(void){ - if(g.json.isJsonMode){ - json_err( FSL_JSON_E_DENIED, NULL, 1 ); - fossil_exit(0); - }else{ - const char *zUrl = PD("REQUEST_URI", "index"); - cgi_redirect(mprintf("login?g=%T", zUrl)); - /* NOTREACHED */ - assert(0); - } + const char *zUrl = PD("REQUEST_URI", "index"); + cgi_redirect(mprintf("login?g=%T", zUrl)); + /* NOTREACHED */ + assert(0); } /* ** Call this routine if the user lacks okHistory permission. If ** the anonymous user has okHistory permission, then paint a mesage Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -23,15 +23,13 @@ #include <string.h> #include <time.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> -#include <stdlib.h> /* atexit() */ + #if INTERFACE -#include "cson_amalgamation.h" /* JSON API. Needed inside the INTERFACE block! */ -#include "json_detail.h" /* ** Number of elements in an array */ #define count(X) (sizeof(X)/sizeof(X[0])) @@ -117,11 +115,10 @@ int xlinkClusterOnly; /* Set when cloning. Only process clusters */ int fTimeFormat; /* 1 for UTC. 2 for localtime. 0 not yet selected */ int *aCommitFile; /* Array of files to be committed */ int markPrivate; /* All new artifacts are private if true */ int clockSkewSeen; /* True if clocks on client and server out of sync */ - int isHTTP; /* True if running in server/CGI modes, else assume CLI. */ int urlIsFile; /* True if a "file:" url */ int urlIsHttps; /* True if a "https:" url */ int urlIsSsh; /* True if an "ssh:" url */ char *urlName; /* Hostname for http: or filename for file: */ @@ -134,11 +131,11 @@ char *urlPasswd; /* Password for http: */ char *urlCanonical; /* Canonical representation of the URL */ char *urlProxyAuth; /* Proxy-Authorizer: string */ char *urlFossil; /* The path of the ?fossil=path suffix on ssh: */ int dontKeepUrl; /* Do not persist the URL */ - + const char *zLogin; /* Login name. "" if not logged in. */ const char *zSSLIdentity; /* Value of --ssl-identity option, filename of SSL client identity */ int useLocalauth; /* No login required if from 127.0.0.1 */ int noPswd; /* Logged in without password (on 127.0.0.1) */ int userUid; /* Integer user id */ @@ -169,60 +166,10 @@ const char *azAuxVal[MX_AUX]; /* Value of each aux() or option() value */ const char **azAuxOpt[MX_AUX]; /* Options of each option() value */ int anAuxCols[MX_AUX]; /* Number of columns for option() values */ int allowSymlinks; /* Cached "allow-symlinks" option */ - - struct FossilJsonBits { - int isJsonMode; /* True if running in JSON mode, else - false. This changes how errors are - reported. In JSON mode we try to - always output JSON-form error - responses and always exit() with - code 0 to avoid an HTTP 500 error. - */ - int resultCode; /* used for passing back specific codes from /json callbacks. */ - int errorDetailParanoia; /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */ - cson_output_opt outOpt; /* formatting options for JSON mode. */ - cson_value * authToken; /* authentication token */ - char const * jsonp; /* Name of JSONP function wrapper. */ - unsigned char dispatchDepth /* Tells JSON command dispatching - which argument we are currently - working on. For this purpose, arg#0 - is the "json" path/CLI arg. - */; - struct { /* "garbage collector" */ - cson_value * v; - cson_object * o; - } gc; - struct { /* JSON POST data. */ - cson_value * v; - cson_array * a; - int offset; /* Tells us which PATH_INFO/CLI args - part holds the "json" command, so - that we can account for sub-repos - and path prefixes. This is handled - differently for CLI and CGI modes. - */ - } cmd; - struct { /* JSON POST data. */ - cson_value * v; - cson_object * o; - } post; - struct { /* GET/COOKIE params in JSON mode. */ - cson_value * v; - cson_object * o; - } param; - struct { - cson_value * v; - cson_object * o; - } reqPayload; /* request payload object (if any) */ - struct { /* response warnings */ - cson_value * v; - cson_array * a; - } warnings; - } json; }; /* ** Macro for debugging: */ @@ -270,11 +217,11 @@ upr = mid - 1; }else{ lwr = mid + 1; } } - for(m=cnt=0, i=upr-2; i<=upr+3 && i<nMap; i++){ + for(m=cnt=0, i=upr-2; cnt<2 && i<=upr+3 && i<nMap; i++){ if( i<0 ) continue; if( strncmp(zName, aMap[i].zName, n)==0 ){ m = i; cnt++; } @@ -285,20 +232,81 @@ } return 1+(cnt>1); } /* -** atexit() handler which frees up "some" of the resources -** used by fossil. +** Search g.argv for arguments "--args FILENAME". If found, then +** (1) remove the two arguments from g.argv +** (2) Read the file FILENAME +** (3) Use the contents of FILE to replace the two removed arguments: +** (a) Ignore blank lines in the file +** (b) Each non-empty line of the file is an argument, except +** (c) If the line begins with "-" and contains a space, it is broken +** into two arguments at the space. */ -void fossil_atexit() { +static void expand_args_option(void){ + Blob file = empty_blob; /* Content of the file */ + Blob line = empty_blob; /* One line of the file */ + unsigned int nLine; /* Number of lines in the file*/ + unsigned int i, j, k; /* Loop counters */ + int n; /* Number of bytes in one line */ + char *z; /* General use string pointer */ + char **newArgv; /* New expanded g.argv under construction */ + char const * zFileName; /* input file name */ + FILE * zInFile; /* input FILE */ + for(i=1; i<g.argc-1; i++){ + z = g.argv[i]; + if( z[0]!='-' ) continue; + z++; + if( z[0]=='-' ) z++; + if( z[0]==0 ) return; /* Stop searching at "--" */ + if( fossil_strcmp(z, "args")==0 ) break; + } + if( i>=g.argc-1 ) return; - cson_value_free(g.json.gc.v); - memset(&g.json, 0, sizeof(g.json)); - if(g.db){ - db_close(0); + zFileName = g.argv[i+1]; + zInFile = (0==strcmp("-",zFileName)) + ? stdin + : fopen(zFileName,"rb"); + if(!zInFile){ + fossil_panic("Cannot open -args file [%s]", zFileName); + }else{ + blob_read_from_channel(&file, zInFile, -1); + if(stdin != zInFile){ + fclose(zInFile); + } + zInFile = NULL; + } + z = blob_str(&file); + for(k=0, nLine=1; z[k]; k++) if( z[k]=='\n' ) nLine++; + newArgv = fossil_malloc( sizeof(char*)*(g.argc + nLine*2) ); + for(j=0; j<i; j++) newArgv[j] = g.argv[j]; + + blob_rewind(&file); + while( (n = blob_line(&file, &line))>0 ){ + if( n<=1 ) continue; + z = blob_buffer(&line); + z[n-1] = 0; + if((n>1) && ('\r'==z[n-2])){ + if(n==2) continue /*empty line*/; + z[n-2] = 0; + } + newArgv[j++] = z; + if( z[0]=='-' ){ + for(k=1; z[k] && !fossil_isspace(z[k]); k++){} + if( z[k] ){ + z[k] = 0; + k++; + if( z[k] ) newArgv[j++] = &z[k]; + } + } } + i += 2; + while( i<g.argc ) newArgv[j++] = g.argv[i++]; + newArgv[j] = 0; + g.argc = j; + g.argv = newArgv; } /* ** This procedure runs first. */ @@ -307,36 +315,25 @@ int idx; int rc; int i; sqlite3_config(SQLITE_CONFIG_LOG, fossil_sqlite_log, 0); - memset(&g, 0, sizeof(g)); g.now = time(0); g.argc = argc; g.argv = argv; -#if defined(NDEBUG) - g.json.errorDetailParanoia = 2 /* FIXME: make configurable - One problem we have here is that this - code is needed before the db is opened, - so we can't sql for it.*/; -#else - g.json.errorDetailParanoia = 0; -#endif - g.json.outOpt = cson_output_opt_empty; - g.json.outOpt.addNewline = 1; - g.json.outOpt.indentation = 1 /* in CGI/server mode this can be configured */; + expand_args_option(); + argc = g.argc; + argv = g.argv; for(i=0; i<argc; i++) g.argv[i] = fossil_mbcs_to_utf8(argv[i]); if( getenv("GATEWAY_INTERFACE")!=0 && !find_option("nocgi", 0, 0)){ zCmdName = "cgi"; - g.isHTTP = 1; }else if( argc<2 ){ fossil_fatal("Usage: %s COMMAND ...\n" "\"%s help\" for a list of available commands\n" "\"%s help COMMAND\" for specific details\n", argv[0], argv[0], argv[0]); }else{ - g.isHTTP = 0; g.fQuiet = find_option("quiet", 0, 0)!=0; g.fSqlTrace = find_option("sqltrace", 0, 0)!=0; g.fSqlStats = find_option("sqlstats", 0, 0)!=0; g.fSystemTrace = find_option("systemtrace", 0, 0)!=0; if( g.fSqlTrace ) g.fSqlStats = 1; @@ -376,16 +373,10 @@ fossil_fatal("%s: ambiguous command prefix: %s\n" "%s: could be any of:%s\n" "%s: use \"help\" for more information\n", argv[0], zCmdName, argv[0], blob_str(&couldbe), argv[0]); } - if(rc){ - fossil_fatal("%s: unrecoverable error while initializing JSON CGI bits: " - "cson error code #%d (%s)\n", - argv[0], rc, cson_rc_string(rc)); - } - atexit( fossil_atexit ); aCommand[idx].xFunc(); fossil_exit(0); /*NOT_REACHED*/ return 0; } @@ -409,70 +400,55 @@ } /* ** Exit. Take care to close the database first. */ -void fossil_exit(int rc){ +NORETURN void fossil_exit(int rc){ db_close(1); exit(rc); } /* ** Print an error message, rollback all databases, and quit. These ** routines never return. */ -void fossil_panic(const char *zFormat, ...){ +NORETURN void fossil_panic(const char *zFormat, ...){ char *z; va_list ap; - int rc = 1; static int once = 1; mainInFatalError = 1; va_start(ap, zFormat); z = vmprintf(zFormat, ap); va_end(ap); - if( g.json.isJsonMode ){ - json_err( 0, z, 1 ); - if( g.isHTTP ){ - rc = 0 /* avoid HTTP 500 */; - } - }else if( g.cgiOutput && once ){ + if( g.cgiOutput && once ){ once = 0; cgi_printf("<p class=\"generalError\">%h</p>", z); cgi_reply(); }else{ char *zOut = mprintf("%s: %s\n", fossil_nameofexe(), z); fossil_puts(zOut, 1); } - free(z); db_force_rollback(); - fossil_exit(rc); + fossil_exit(1); } - -void fossil_fatal(const char *zFormat, ...){ +NORETURN void fossil_fatal(const char *zFormat, ...){ char *z; - int rc = 1; va_list ap; mainInFatalError = 1; va_start(ap, zFormat); z = vmprintf(zFormat, ap); va_end(ap); - if( g.json.isJsonMode ){ - json_err( g.json.resultCode, z, 1 ); - if( g.isHTTP ){ - rc = 0 /* avoid HTTP 500 */; - } - } - else if( g.cgiOutput ){ + if( g.cgiOutput ){ g.cgiOutput = 0; cgi_printf("<p class=\"generalError\">%h</p>", z); cgi_reply(); }else{ char *zOut = mprintf("\r%s: %s\n", fossil_nameofexe(), z); fossil_puts(zOut, 1); } db_force_rollback(); - fossil_exit(rc); + fossil_exit(1); } /* This routine works like fossil_fatal() except that if called ** recursively, the recursive call is a no-op. ** @@ -483,32 +459,25 @@ ** be prepared for this routine to return. */ void fossil_fatal_recursive(const char *zFormat, ...){ char *z; va_list ap; - int rc = 1; if( mainInFatalError ) return; mainInFatalError = 1; va_start(ap, zFormat); z = vmprintf(zFormat, ap); va_end(ap); - if( g.json.isJsonMode ){ - json_err( g.json.resultCode, z, 1 ); - if( g.isHTTP ){ - rc = 0 /* avoid HTTP 500 */; - } - }else if( g.cgiOutput ){ + if( g.cgiOutput ){ g.cgiOutput = 0; cgi_printf("<p class=\"generalError\">%h</p>", z); cgi_reply(); }else{ char *zOut = mprintf("\r%s: %s\n", fossil_nameofexe(), z); fossil_puts(zOut, 1); - free(zOut); } db_force_rollback(); - fossil_exit(rc); + fossil_exit(1); } /* Print a warning message */ void fossil_warning(const char *zFormat, ...){ @@ -515,20 +484,17 @@ char *z; va_list ap; va_start(ap, zFormat); z = vmprintf(zFormat, ap); va_end(ap); - if(g.json.isJsonMode){ - json_warn( FSL_JSON_W_UNKNOWN, z ); - }else if( g.cgiOutput ){ + if( g.cgiOutput ){ cgi_printf("<p class=\"generalError\">%h</p>", z); }else{ char *zOut = mprintf("\r%s: %s\n", fossil_nameofexe(), z); fossil_puts(zOut, 1); free(zOut); } - free(z); } /* ** Malloc and free routines that cannot fail */ @@ -950,11 +916,11 @@ } /* ** Send an HTTP redirect back to the designated Index Page. */ -void fossil_redirect_home(void){ +NORETURN void fossil_redirect_home(void){ cgi_redirectf("%s%s", g.zTop, db_get("index-page", "/index")); } /* ** If running as root, chroot to the directory containing the @@ -1064,17 +1030,13 @@ if( szFile<1024 ){ if( zNotFound ){ cgi_redirect(zNotFound); }else{ - if(g.json.isJsonMode){ - json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1); - }else{ - @ <h1>Not Found</h1> - cgi_set_status(404, "not found"); - cgi_reply(); - } + @ <h1>Not Found</h1> + cgi_set_status(404, "not found"); + cgi_reply(); } return; } break; } @@ -1099,16 +1061,11 @@ zPathInfo = "/xfer"; } set_base_url(); if( zPathInfo==0 || zPathInfo[0]==0 || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){ - if(g.json.isJsonMode){ - json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1); - fossil_exit(0); - }else{ - fossil_redirect_home() /*does not return*/; - } + fossil_redirect_home(); }else{ zPath = mprintf("%s", zPathInfo); } /* Make g.zPath point to the first element of the path. Make @@ -1165,12 +1122,10 @@ break; } if( g.zExtra ){ /* CGI parameters get this treatment elsewhere, but places like getfile ** will use g.zExtra directly. - ** Reminder: the login mechanism uses 'name' differently, and may - ** eventually have a problem/collision with this. */ dehttpize(g.zExtra); cgi_set_parameter_nocopy("name", g.zExtra); } @@ -1177,25 +1132,17 @@ /* Locate the method specified by the path and execute the function ** that implements that method. */ if( name_search(g.zPath, aWebpage, count(aWebpage), &idx) && name_search("not_found", aWebpage, count(aWebpage), &idx) ){ - if(g.json.isJsonMode){ - json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,0); - }else{ - cgi_set_status(404,"Not Found"); - @ <h1>Not Found</h1> - @ <p>Page not found: %h(g.zPath)</p> - } + cgi_set_status(404,"Not Found"); + @ <h1>Not Found</h1> + @ <p>Page not found: %h(g.zPath)</p> }else if( aWebpage[idx].xFunc!=page_xfer && db_schema_is_outofdate() ){ - if(g.json.isJsonMode){ - json_err(FSL_JSON_E_DB_NEEDS_REBUILD,NULL,0); - }else{ - @ <h1>Server Configuration Error</h1> - @ <p>The database schema on the server is out-of-date. Please ask - @ the administrator to run <b>fossil rebuild</b>.</p> - } + @ <h1>Server Configuration Error</h1> + @ <p>The database schema on the server is out-of-date. Please ask + @ the administrator to run <b>fossil rebuild</b>.</p> }else{ aWebpage[idx].xFunc(); } /* Return the result. Index: src/main.mk ================================================================== --- src/main.mk +++ src/main.mk @@ -47,14 +47,10 @@ $(SRCDIR)/http_socket.c \ $(SRCDIR)/http_ssl.c \ $(SRCDIR)/http_transport.c \ $(SRCDIR)/import.c \ $(SRCDIR)/info.c \ - $(SRCDIR)/json.c \ - $(SRCDIR)/json_login.c \ - $(SRCDIR)/json_timeline.c \ - $(SRCDIR)/json_wiki.c \ $(SRCDIR)/leaf.c \ $(SRCDIR)/login.c \ $(SRCDIR)/main.c \ $(SRCDIR)/manifest.c \ $(SRCDIR)/md5.c \ @@ -135,14 +131,10 @@ $(OBJDIR)/http_socket_.c \ $(OBJDIR)/http_ssl_.c \ $(OBJDIR)/http_transport_.c \ $(OBJDIR)/import_.c \ $(OBJDIR)/info_.c \ - $(OBJDIR)/json_.c \ - $(OBJDIR)/json_login_.c \ - $(OBJDIR)/json_timeline_.c \ - $(OBJDIR)/json_wiki_.c \ $(OBJDIR)/leaf_.c \ $(OBJDIR)/login_.c \ $(OBJDIR)/main_.c \ $(OBJDIR)/manifest_.c \ $(OBJDIR)/md5_.c \ @@ -223,14 +215,10 @@ $(OBJDIR)/http_socket.o \ $(OBJDIR)/http_ssl.o \ $(OBJDIR)/http_transport.o \ $(OBJDIR)/import.o \ $(OBJDIR)/info.o \ - $(OBJDIR)/json.o \ - $(OBJDIR)/json_login.o \ - $(OBJDIR)/json_timeline.o \ - $(OBJDIR)/json_wiki.o \ $(OBJDIR)/leaf.o \ $(OBJDIR)/login.o \ $(OBJDIR)/main.o \ $(OBJDIR)/manifest.o \ $(OBJDIR)/md5.o \ @@ -313,11 +301,11 @@ # using -lsqlite3. SQLITE3_OBJ.1 = SQLITE3_OBJ.0 = $(OBJDIR)/sqlite3.o SQLITE3_OBJ. = $(SQLITE3_OBJ.0) -EXTRAOBJ = $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE)) $(OBJDIR)/shell.o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o $(OBJDIR)/cson_amalgamation.o +EXTRAOBJ = $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE)) $(OBJDIR)/shell.o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o $(APPNAME): $(OBJDIR)/headers $(OBJ) $(EXTRAOBJ) $(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB) # This rule prevents make from using its default rules to try build @@ -331,14 +319,13 @@ $(OBJDIR)/page_index.h: $(TRANS_SRC) $(OBJDIR)/mkindex $(OBJDIR)/mkindex $(TRANS_SRC) >$@ $(OBJDIR)/headers: $(OBJDIR)/page_index.h $(OBJDIR)/makeheaders $(OBJDIR)/VERSION.h - $(OBJDIR)/makeheaders $(OBJDIR)/add_.c:$(OBJDIR)/add.h $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h $(OBJDIR)/browse_.c:$(OBJDIR)/browse.h $(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h $(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h $(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h $(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h $(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h $(OBJDIR)/clone_.c:$(OBJDIR)/clone.h $(OBJDIR)/comformat_.c:$(OBJDIR)/comformat.h $(OBJDIR)/configure_.c:$(OBJDIR)/configure.h $(OBJDIR)/content_.c:$(OBJDIR)/content.h $(OBJDIR)/db_.c:$(OBJDIR)/db.h $(OBJDIR)/delta_.c:$(OBJDIR)/delta.h $(OBJDIR)/deltacmd_.c:$(OBJDIR)/deltacmd.h $(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h $(OBJDIR)/diff_.c:$(OBJDIR)/diff.h $(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h $(OBJDIR)/event_.c:$(OBJDIR)/event.h $(OBJDIR)/export_.c:$(OBJDIR)/export.h $(OBJDIR)/file_.c:$(OBJDIR)/file.h $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h $(OBJDIR)/http_.c:$(OBJDIR)/http.h $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h $(OBJDIR)/import_.c:$(OBJDIR)/import.h $(OBJDIR)/info_.c:$(OBJDIR)/info.h $(OBJDIR)/json_.c:$(OBJDIR)/json.h $(OBJDIR)/json_login_.c:$(OBJDIR)/json_login.h $(OBJDIR)/json_timeline_.c:$(OBJDIR)/json_timeline.h $(OBJDIR)/json_wiki_.c:$(OBJDIR)/json_wiki.h $(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h $(OBJDIR)/login_.c:$(OBJDIR)/login.h $(OBJDIR)/main_.c:$(OBJDIR)/main.h $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h $(OBJDIR)/name_.c:$(OBJDIR)/name.h $(OBJDIR)/path_.c:$(OBJDIR)/path.h $(OBJDIR)/pivot_.c:$(OBJDIR)/pivot.h $(OBJDIR)/popen_.c:$(OBJDIR)/popen.h $(OBJDIR)/pqueue_.c:$(OBJDIR)/pqueue.h $(OBJDIR)/printf_.c:$(OBJDIR)/printf.h $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h $(OBJDIR)/report_.c:$(OBJDIR)/report.h $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h $(OBJDIR)/search_.c:$(OBJDIR)/search.h $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h $(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h $(OBJDIR)/style_.c:$(OBJDIR)/style.h $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h $(OBJDIR)/update_.c:$(OBJDIR)/update.h $(OBJDIR)/url_.c:$(OBJDIR)/url.h $(OBJDIR)/user_.c:$(OBJDIR)/user.h $(OBJDIR)/verify_.c:$(OBJDIR)/verify.h $(OBJDIR)/vfile_.c:$(OBJDIR)/vfile.h $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h $(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h $(OBJDIR)/zip_.c:$(OBJDIR)/zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h $(OBJDIR)/VERSION.h + $(OBJDIR)/makeheaders $(OBJDIR)/add_.c:$(OBJDIR)/add.h $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h $(OBJDIR)/browse_.c:$(OBJDIR)/browse.h $(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h $(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h $(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h $(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h $(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h $(OBJDIR)/clone_.c:$(OBJDIR)/clone.h $(OBJDIR)/comformat_.c:$(OBJDIR)/comformat.h $(OBJDIR)/configure_.c:$(OBJDIR)/configure.h $(OBJDIR)/content_.c:$(OBJDIR)/content.h $(OBJDIR)/db_.c:$(OBJDIR)/db.h $(OBJDIR)/delta_.c:$(OBJDIR)/delta.h $(OBJDIR)/deltacmd_.c:$(OBJDIR)/deltacmd.h $(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h $(OBJDIR)/diff_.c:$(OBJDIR)/diff.h $(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h $(OBJDIR)/event_.c:$(OBJDIR)/event.h $(OBJDIR)/export_.c:$(OBJDIR)/export.h $(OBJDIR)/file_.c:$(OBJDIR)/file.h $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h $(OBJDIR)/http_.c:$(OBJDIR)/http.h $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h $(OBJDIR)/import_.c:$(OBJDIR)/import.h $(OBJDIR)/info_.c:$(OBJDIR)/info.h $(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h $(OBJDIR)/login_.c:$(OBJDIR)/login.h $(OBJDIR)/main_.c:$(OBJDIR)/main.h $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h $(OBJDIR)/name_.c:$(OBJDIR)/name.h $(OBJDIR)/path_.c:$(OBJDIR)/path.h $(OBJDIR)/pivot_.c:$(OBJDIR)/pivot.h $(OBJDIR)/popen_.c:$(OBJDIR)/popen.h $(OBJDIR)/pqueue_.c:$(OBJDIR)/pqueue.h $(OBJDIR)/printf_.c:$(OBJDIR)/printf.h $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h $(OBJDIR)/report_.c:$(OBJDIR)/report.h $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h $(OBJDIR)/search_.c:$(OBJDIR)/search.h $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h $(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h $(OBJDIR)/style_.c:$(OBJDIR)/style.h $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h $(OBJDIR)/update_.c:$(OBJDIR)/update.h $(OBJDIR)/url_.c:$(OBJDIR)/url.h $(OBJDIR)/user_.c:$(OBJDIR)/user.h $(OBJDIR)/verify_.c:$(OBJDIR)/verify.h $(OBJDIR)/vfile_.c:$(OBJDIR)/vfile.h $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h $(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h $(OBJDIR)/zip_.c:$(OBJDIR)/zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h $(OBJDIR)/VERSION.h touch $(OBJDIR)/headers $(OBJDIR)/headers: Makefile -$(OBJDIR)/json.o $(OBJDIR)/json_login.o $(OBJDIR)/json_wiki.o $(OBJDIR)/json_timeline.o : $(SRCDIR)/json_detail.h Makefile: $(OBJDIR)/add_.c: $(SRCDIR)/add.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/add.c >$(OBJDIR)/add_.c $(OBJDIR)/add.o: $(OBJDIR)/add_.c $(OBJDIR)/add.h $(SRCDIR)/config.h @@ -602,38 +589,10 @@ $(OBJDIR)/info.o: $(OBJDIR)/info_.c $(OBJDIR)/info.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/info.o -c $(OBJDIR)/info_.c $(OBJDIR)/info.h: $(OBJDIR)/headers -$(OBJDIR)/json_.c: $(SRCDIR)/json.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/json.c >$(OBJDIR)/json_.c - -$(OBJDIR)/json.o: $(OBJDIR)/json_.c $(OBJDIR)/json.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/json.o -c $(OBJDIR)/json_.c - -$(OBJDIR)/json.h: $(OBJDIR)/headers -$(OBJDIR)/json_login_.c: $(SRCDIR)/json_login.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/json_login.c >$(OBJDIR)/json_login_.c - -$(OBJDIR)/json_login.o: $(OBJDIR)/json_login_.c $(OBJDIR)/json_login.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/json_login.o -c $(OBJDIR)/json_login_.c - -$(OBJDIR)/json_login.h: $(OBJDIR)/headers -$(OBJDIR)/json_timeline_.c: $(SRCDIR)/json_timeline.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/json_timeline.c >$(OBJDIR)/json_timeline_.c - -$(OBJDIR)/json_timeline.o: $(OBJDIR)/json_timeline_.c $(OBJDIR)/json_timeline.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/json_timeline.o -c $(OBJDIR)/json_timeline_.c - -$(OBJDIR)/json_timeline.h: $(OBJDIR)/headers -$(OBJDIR)/json_wiki_.c: $(SRCDIR)/json_wiki.c $(OBJDIR)/translate - $(OBJDIR)/translate $(SRCDIR)/json_wiki.c >$(OBJDIR)/json_wiki_.c - -$(OBJDIR)/json_wiki.o: $(OBJDIR)/json_wiki_.c $(OBJDIR)/json_wiki.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/json_wiki.o -c $(OBJDIR)/json_wiki_.c - -$(OBJDIR)/json_wiki.h: $(OBJDIR)/headers $(OBJDIR)/leaf_.c: $(SRCDIR)/leaf.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/leaf.c >$(OBJDIR)/leaf_.c $(OBJDIR)/leaf.o: $(OBJDIR)/leaf_.c $(OBJDIR)/leaf.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/leaf.o -c $(OBJDIR)/leaf_.c @@ -950,8 +909,5 @@ $(XTCC) -I$(SRCDIR) -c $(SRCDIR)/th.c -o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o: $(SRCDIR)/th_lang.c $(XTCC) -I$(SRCDIR) -c $(SRCDIR)/th_lang.c -o $(OBJDIR)/th_lang.o -$(OBJDIR)/cson_amalgamation.o: $(SRCDIR)/cson_amalgamation.c - $(XTCC) -I$(SRCDIR) -c $(SRCDIR)/cson_amalgamation.c -o $(OBJDIR)/cson_amalgamation.o - Index: src/makemake.tcl ================================================================== --- src/makemake.tcl +++ src/makemake.tcl @@ -53,14 +53,10 @@ http http_socket http_transport import info - json - json_login - json_timeline - json_wiki leaf login main manifest md5 @@ -206,12 +202,11 @@ EXTRAOBJ = \ $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE)) \ $(OBJDIR)/shell.o \ $(OBJDIR)/th.o \ - $(OBJDIR)/th_lang.o \ - $(OBJDIR)/cson_amalgamation.o + $(OBJDIR)/th_lang.o $(APPNAME): $(OBJDIR)/headers $(OBJ) $(EXTRAOBJ) $(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB) # This rule prevents make from using its default rules to try build @@ -230,19 +225,17 @@ append mhargs " \$(OBJDIR)/${s}_.c:\$(OBJDIR)/$s.h" set extra_h($s) {} } append mhargs " \$(SRCDIR)/sqlite3.h" append mhargs " \$(SRCDIR)/th.h" -#append mhargs " \$(SRCDIR)/cson_amalgamation.h" append mhargs " \$(OBJDIR)/VERSION.h" writeln "\$(OBJDIR)/page_index.h: \$(TRANS_SRC) \$(OBJDIR)/mkindex" writeln "\t\$(OBJDIR)/mkindex \$(TRANS_SRC) >$@" writeln "\$(OBJDIR)/headers:\t\$(OBJDIR)/page_index.h \$(OBJDIR)/makeheaders \$(OBJDIR)/VERSION.h" writeln "\t\$(OBJDIR)/makeheaders $mhargs" writeln "\ttouch \$(OBJDIR)/headers" writeln "\$(OBJDIR)/headers: Makefile" -writeln "\$(OBJDIR)/json.o \$(OBJDIR)/json_login.o \$(OBJDIR)/json_wiki.o \$(OBJDIR)/json_timeline.o : \$(SRCDIR)/json_detail.h" writeln "Makefile:" set extra_h(main) \$(OBJDIR)/page_index.h foreach s [lsort $src] { writeln "\$(OBJDIR)/${s}_.c:\t\$(SRCDIR)/$s.c \$(OBJDIR)/translate" @@ -271,15 +264,10 @@ writeln "\$(OBJDIR)/th.o:\t\$(SRCDIR)/th.c" writeln "\t\$(XTCC) -I\$(SRCDIR) -c \$(SRCDIR)/th.c -o \$(OBJDIR)/th.o\n" writeln "\$(OBJDIR)/th_lang.o:\t\$(SRCDIR)/th_lang.c" writeln "\t\$(XTCC) -I\$(SRCDIR) -c \$(SRCDIR)/th_lang.c -o \$(OBJDIR)/th_lang.o\n" - -set opt {} -writeln "\$(OBJDIR)/cson_amalgamation.o:\t\$(SRCDIR)/cson_amalgamation.c" -writeln "\t\$(XTCC) -I\$(SRCDIR) -c \$(SRCDIR)/cson_amalgamation.c -o \$(OBJDIR)/cson_amalgamation.o\n" - close $output_file # # End of the main.mk output ############################################################################## @@ -424,12 +412,11 @@ EXTRAOBJ = \ $(OBJDIR)/sqlite3.o \ $(OBJDIR)/shell.o \ $(OBJDIR)/th.o \ - $(OBJDIR)/th_lang.o \ - $(OBJDIR)/cson_amalgamation.o + $(OBJDIR)/th_lang.o $(APPNAME): $(OBJDIR)/headers $(OBJ) $(EXTRAOBJ) $(OBJDIR)/icon.o $(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB) $(OBJDIR)/icon.o # This rule prevents make from using its default rules to try build @@ -479,15 +466,10 @@ writeln "\$(OBJDIR)/sqlite3.o:\t\$(SRCDIR)/sqlite3.c" set opt $SQLITE_OPTIONS writeln "\t\$(XTCC) $opt -c \$(SRCDIR)/sqlite3.c -o \$(OBJDIR)/sqlite3.o\n" -set opt {} -writeln "\$(OBJDIR)/cson_amalgamation.o:\t\$(SRCDIR)/cson_amalgamation.c" -writeln "\t\$(XTCC) $opt -c \$(SRCDIR)/cson_amalgamation.c -o \$(OBJDIR)/cson_amalgamation.o\n" -writeln "\$(OBJDIR)/json.o \$(OBJDIR)/json_login.o \$(OBJDIR)/json_wiki.o \$(OBJDIR)/json_timeline.o : \$(SRCDIR)/json_detail.h" - writeln "\$(OBJDIR)/shell.o:\t\$(SRCDIR)/shell.c \$(SRCDIR)/sqlite3.h" set opt {-Dmain=sqlite3_shell} append opt " -DSQLITE_OMIT_LOAD_EXTENSION=1" writeln "\t\$(XTCC) $opt -c \$(SRCDIR)/shell.c -o \$(OBJDIR)/shell.o\n" @@ -596,13 +578,10 @@ $(TCC) -o$@ -c $** $(OBJDIR)\th_lang$O : $(SRCDIR)\th_lang.c $(TCC) -o$@ -c $** -$(OBJDIR)\cson_amalgamation.h : $(SRCDIR)\cson_amalgamation.h - cp $@ $@ - VERSION.h : version$E $B\manifest.uuid $B\manifest $B\VERSION +$** > $@ page_index.h: mkindex$E $(SRC) +$** > $@ @@ -611,16 +590,10 @@ -del $(OBJDIR)\*.obj -del *.obj *_.c *.h *.map realclean: -del $(APPNAME) translate$E mkindex$E makeheaders$E mkversion$E - -$(OBJDIR)\json$O : $(SRCDIR)\json_detail.h -$(OBJDIR)\json_login$O : $(SRCDIR)\json_detail.h -$(OBJDIR)\json_wiki$O : $(SRCDIR)\json_detail.h -$(OBJDIR)\json_timeline$O : $(SRCDIR)\json_detail.h - } foreach s [lsort $src] { writeln "\$(OBJDIR)\\$s\$O : ${s}_.c ${s}.h" writeln "\t\$(TCC) -o\$@ -c ${s}_.c\n" @@ -630,11 +603,11 @@ writeln -nonewline "headers: makeheaders\$E page_index.h VERSION.h\n\t +makeheaders\$E " foreach s [lsort $src] { writeln -nonewline "${s}_.c:$s.h " } -writeln "\$(SRCDIR)\\sqlite3.h \$(SRCDIR)\\th.h VERSION.h \$(SRCDIR)\\cson_amalgamation.h" +writeln "\$(SRCDIR)\\sqlite3.h \$(SRCDIR)\\th.h VERSION.h" writeln "\t@copy /Y nul: headers" close $output_file # # End of the win/Makefile.dmc output @@ -744,12 +717,10 @@ $(OX)\th_lang$O : $(SRCDIR)\th_lang.c $(TCC) /Fo$@ -c $** VERSION.h : mkversion$E $B\manifest.uuid $B\manifest $B\VERSION $** > $@ -$(OBJDIR)\cson_amalgamation.h : $(SRCDIR)\cson_amalgamation.h - cp $(SRCDIR)\cson_amalgamation.h $@ page_index.h: mkindex$E $(SRC) $** > $@ clean: @@ -758,15 +729,10 @@ -del headers linkopts realclean: -del $(APPNAME) translate$E mkindex$E makeheaders$E mkversion$E -$(OBJDIR)\json$O : $(SRCDIR)\json_detail.h -$(OBJDIR)\json_login$O : $(SRCDIR)\json_detail.h -$(OBJDIR)\json_wiki$O : $(SRCDIR)\json_detail.h -$(OBJDIR)\json_timeline$O : $(SRCDIR)\json_detail.h - } foreach s [lsort $src] { writeln "\$(OX)\\$s\$O : ${s}_.c ${s}.h" writeln "\t\$(TCC) /Fo\$@ -c ${s}_.c\n" writeln "${s}_.c : \$(SRCDIR)\\$s.c" @@ -775,11 +741,11 @@ writeln -nonewline "headers: makeheaders\$E page_index.h VERSION.h\n\tmakeheaders\$E " foreach s [lsort $src] { writeln -nonewline "${s}_.c:$s.h " } -writeln "\$(SRCDIR)\\sqlite3.h \$(SRCDIR)\\th.h VERSION.h \$(SRCDIR)\\cson_amalgamation.h" +writeln "\$(SRCDIR)\\sqlite3.h \$(SRCDIR)\\th.h VERSION.h" writeln "\t@copy /Y nul: headers" close $output_file # Index: src/name.c ================================================================== --- src/name.c +++ src/name.c @@ -154,11 +154,11 @@ " WHERE tag.tagname='sym-%q' " " AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 " " AND event.objid=tagxref.rid " " AND blob.rid=event.objid " " AND event.type GLOB '%q'" - " ORDER BY event.mtime DESC ", + " ORDER BY event.mtime DESC /*sort*/", zTag, zType ); if( zUuid==0 ){ int nTag = strlen(zTag); int i; @@ -180,11 +180,11 @@ " AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 " " AND event.objid=tagxref.rid " " AND blob.rid=event.objid " " AND event.mtime<=julianday(%Q %s)" " AND event.type GLOB '%q'" - " ORDER BY event.mtime DESC ", + " ORDER BY event.mtime DESC /*sort*/ ", zTagBase, zDate, (useUtc ? "" : ",'utc'"), zType ); break; } } Index: src/path.c ================================================================== --- src/path.c +++ src/path.c @@ -95,12 +95,13 @@ } /* ** Construct the path from path.pStart to path.pEnd in the u.pTo fields. */ -void path_reverse_path(void){ +static void path_reverse_path(void){ PathNode *p; + assert( path.pEnd!=0 ); for(p=path.pEnd; p && p->pFrom; p = p->pFrom){ p->pFrom->u.pTo = p; } path.pEnd->u.pTo = 0; assert( p==path.pStart ); Index: src/rebuild.c ================================================================== --- src/rebuild.c +++ src/rebuild.c @@ -786,10 +786,11 @@ ); if( bVerily ){ db_multi_exec( "DELETE FROM concealed;" "UPDATE rcvfrom SET ipaddr='unknown';" + "DROP TABLE IF EXISTS accesslog;" "UPDATE user SET photo=NULL, info='';" ); } } if( !bNeedRebuild ){ Index: src/report.c ================================================================== --- src/report.c +++ src/report.c @@ -19,11 +19,10 @@ */ #include "config.h" #include <time.h> #include "report.h" #include <assert.h> -#include "cson_amalgamation.h" /* Forward references to static routines */ static void report_format_hints(void); /* @@ -1155,77 +1154,5 @@ report_unrestrict_sql(); if( zFilter ){ free(zSql); } } - - -void rptshowJson( - const char *zRep, - char const * zLimit, - const char *zFilter, - char indention -){ - Stmt q; - char *zSql; - char const *zTitle; - char const *zOwner; - char *zClrKey; - char *zErr1 = 0; - int count = 0; - int rn; - int rc; - cson_value * zJVal = NULL; - - if (!zRep || !strcmp(zRep,zFullTicketRptRn) || !strcmp(zRep,zFullTicketRptTitle) ){ - zTitle = zFullTicketRptTitle; - zSql = mprintf("SELECT * FROM ticket"); - zOwner = g.zLogin; - zClrKey = ""; - }else{ - rn = atoi(zRep); - if( rn ){ - db_prepare(&q, - "SELECT title, sqlcode, owner, cols FROM reportfmt WHERE rn=%d", rn); - }else{ - db_prepare(&q, - "SELECT title, sqlcode, owner, cols FROM reportfmt WHERE title=%Q", zRep); - } - if( db_step(&q)!=SQLITE_ROW ){ - db_finalize(&q); - rpt_list_reports(); - fossil_fatal("unkown report format(%s)!",zRep); - } - zTitle = db_column_malloc(&q, 0)/*leak!*/; - zSql = db_column_malloc(&q, 1); - zOwner = db_column_malloc(&q, 2)/*leak!*/; - zClrKey = db_column_malloc(&q, 3); - db_finalize(&q); - } - if( zFilter ){ - char * old = zSql; - zSql = mprintf("SELECT * FROM (%s) WHERE %s%s%s",old,zFilter, - (zLimit?" LIMIT ":""), (zLimit?zLimit:"")); - free(old); - }else if( zLimit ){ - char * old = zSql; - zSql = mprintf("%s LIMIT %s",old, zLimit); - free(old); - } - count = 0; - /*fprintf(stderr,"SQL=[%s]\n",zSql);*/ - sqlite3_set_authorizer(g.db, report_query_authorizer, (void*)&zErr1); - rc = cson_sqlite3_sql_to_json(g.db, &zJVal, zSql, 1); - if( 0 == rc ){ - cson_output_opt outOpt = cson_output_opt_empty; - outOpt.addNewline = 1; - outOpt.indentation = indention; - rc = cson_output_FILE( zJVal, stdout, &outOpt ); - } else{ - fossil_fatal("sql-to-json failed with code %d (%s)!", rc, - cson_rc_string(rc)); - } - sqlite3_set_authorizer(g.db, 0, 0); - cson_value_free(zJVal); - free(zSql); - free(zClrKey); -} Index: src/setup.c ================================================================== --- src/setup.c +++ src/setup.c @@ -848,18 +848,27 @@ @ <a href="%s(g.zTop)/help/server">fossil http</a> commands @ without the "--localauth" option. @ <li> The server is started from CGI without the "localauth" keyword @ in the CGI script. @ </ol> + @ + @ <hr /> + onoff_attribute("Enable /test_env", + "test_env_enable", "test_env_enable", 0); + @ <p>When enabled, the %h(g.zBaseURL)/test_env URL is available to all + @ users. When disabled (the default) only users Admin and Setup can visit + @ the /test_env page. + @ </p> + @ @ <hr /> onoff_attribute("Allow REMOTE_USER authentication", "remote_user_ok", "remote_user_ok", 0); @ <p>When enabled, if the REMOTE_USER environment variable is set to the @ login name of a valid user and no other login credentials are available, @ then the REMOTE_USER is accepted as an authenticated user. @ </p> - + @ @ <hr /> entry_attribute("Login expiration time", 6, "cookie-expire", "cex", "8766"); @ <p>The number of hours for which a login is valid. This must be a @ positive number. The default is 8760 hours which is approximately equal @ to a year.</p> Index: src/sha1.c ================================================================== --- src/sha1.c +++ src/sha1.c @@ -82,11 +82,11 @@ #define d qq[3] #define e qq[4] void SHA1Transform(unsigned int state[5], const unsigned char buffer[64]) { - unsigned int qq[5]; // a, b, c, d, e; + unsigned int qq[5]; /* a, b, c, d, e; */ static int one = 1; unsigned int block[16]; memcpy(block, buffer, 64); memcpy(qq,state,5*sizeof(unsigned int)); Index: src/shell.c ================================================================== --- src/shell.c +++ src/shell.c @@ -10,15 +10,26 @@ ** ************************************************************************* ** This file contains code to implement the "sqlite" command line ** utility for accessing SQLite databases. */ -#if defined(_WIN32) || defined(WIN32) +#if (defined(_WIN32) || defined(WIN32)) && !defined(_CRT_SECURE_NO_WARNINGS) /* This needs to come before any includes for MSVC compiler */ #define _CRT_SECURE_NO_WARNINGS #endif +/* +** Enable large-file support for fopen() and friends on unix. +*/ +#ifndef SQLITE_DISABLE_LFS +# define _LARGE_FILE 1 +# ifndef _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 64 +# endif +# define _LARGEFILE_SOURCE 1 +#endif + #include <stdlib.h> #include <string.h> #include <stdio.h> #include <assert.h> #include "sqlite3.h" @@ -58,11 +69,11 @@ #define isatty(h) _isatty(h) #define access(f,m) _access((f),(m)) #else /* Make sure isatty() has a prototype. */ -extern int isatty(); +extern int isatty(int); #endif #if defined(_WIN32_WCE) /* Windows CE (arm-wince-mingw32ce-gcc) does not provide isatty() * thus we always assume that we have a console. That can be @@ -71,10 +82,15 @@ #define isatty(x) 1 #endif /* True if the timer is enabled */ static int enableTimer = 0; + +/* ctype macros that work with signed characters */ +#define IsSpace(X) isspace((unsigned char)X) +#define IsDigit(X) isdigit((unsigned char)X) +#define ToLower(X) (char)tolower((unsigned char)X) #if !defined(_WIN32) && !defined(WIN32) && !defined(__OS2__) && !defined(__RTP__) && !defined(_WRS_KERNEL) #include <sys/time.h> #include <sys/resource.h> @@ -263,27 +279,27 @@ /* ** Determines if a string is a number of not. */ static int isNumber(const char *z, int *realnum){ if( *z=='-' || *z=='+' ) z++; - if( !isdigit(*z) ){ + if( !IsDigit(*z) ){ return 0; } z++; if( realnum ) *realnum = 0; - while( isdigit(*z) ){ z++; } + while( IsDigit(*z) ){ z++; } if( *z=='.' ){ z++; - if( !isdigit(*z) ) return 0; - while( isdigit(*z) ){ z++; } + if( !IsDigit(*z) ) return 0; + while( IsDigit(*z) ){ z++; } if( realnum ) *realnum = 1; } if( *z=='e' || *z=='E' ){ z++; if( *z=='+' || *z=='-' ) z++; - if( !isdigit(*z) ) return 0; - while( isdigit(*z) ){ z++; } + if( !IsDigit(*z) ) return 0; + while( IsDigit(*z) ){ z++; } if( realnum ) *realnum = 1; } return *z==0; } @@ -320,22 +336,20 @@ */ static char *local_getline(char *zPrompt, FILE *in){ char *zLine; int nLine; int n; - int eol; if( zPrompt && *zPrompt ){ printf("%s",zPrompt); fflush(stdout); } nLine = 100; zLine = malloc( nLine ); if( zLine==0 ) return 0; n = 0; - eol = 0; - while( !eol ){ + while( 1 ){ if( n+100>nLine ){ nLine = nLine*2 + 100; zLine = realloc(zLine, nLine); if( zLine==0 ) return 0; } @@ -343,19 +357,18 @@ if( n==0 ){ free(zLine); return 0; } zLine[n] = 0; - eol = 1; break; } while( zLine[n] ){ n++; } if( n>0 && zLine[n-1]=='\n' ){ n--; if( n>0 && zLine[n-1]=='\r' ) n--; zLine[n] = 0; - eol = 1; + break; } } zLine = realloc( zLine, n+1 ); return zLine; } @@ -400,10 +413,11 @@ sqlite3 *db; /* The database */ int echoOn; /* True to echo input commands */ int statsOn; /* True to display memory stats before each finalize */ int cnt; /* Number of records displayed so far */ FILE *out; /* Write results here */ + int nErr; /* Number of errors seen */ int mode; /* An output mode setting */ int writableSchema; /* True if PRAGMA writable_schema=ON */ int showHeader; /* True to show column names in List or Column mode */ char *zDestTable; /* Name of destination table when MODE_Insert */ char separator[20]; /* Separator character for MODE_List */ @@ -925,31 +939,37 @@ ** ** This is used, for example, to show the schema of the database by ** querying the SQLITE_MASTER table. */ static int run_table_dump_query( - FILE *out, /* Send output here */ - sqlite3 *db, /* Database to query */ - const char *zSelect, /* SELECT statement to extract content */ - const char *zFirstRow /* Print before first row, if not NULL */ + struct callback_data *p, /* Query context */ + const char *zSelect, /* SELECT statement to extract content */ + const char *zFirstRow /* Print before first row, if not NULL */ ){ sqlite3_stmt *pSelect; int rc; - rc = sqlite3_prepare(db, zSelect, -1, &pSelect, 0); + rc = sqlite3_prepare(p->db, zSelect, -1, &pSelect, 0); if( rc!=SQLITE_OK || !pSelect ){ + fprintf(p->out, "/**** ERROR: (%d) %s *****/\n", rc, sqlite3_errmsg(p->db)); + p->nErr++; return rc; } rc = sqlite3_step(pSelect); while( rc==SQLITE_ROW ){ if( zFirstRow ){ - fprintf(out, "%s", zFirstRow); + fprintf(p->out, "%s", zFirstRow); zFirstRow = 0; } - fprintf(out, "%s;\n", sqlite3_column_text(pSelect, 0)); + fprintf(p->out, "%s;\n", sqlite3_column_text(pSelect, 0)); rc = sqlite3_step(pSelect); } - return sqlite3_finalize(pSelect); + rc = sqlite3_finalize(pSelect); + if( rc!=SQLITE_OK ){ + fprintf(p->out, "/**** ERROR: (%d) %s *****/\n", rc, sqlite3_errmsg(p->db)); + p->nErr++; + } + return rc; } /* ** Allocate space and save off current error string. */ @@ -1027,11 +1047,16 @@ fprintf(pArg->out, "Lookaside failures due to size: %d\n", iHiwtr); sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL, &iCur, &iHiwtr, bReset); fprintf(pArg->out, "Lookaside failures due to OOM: %d\n", iHiwtr); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_USED, &iCur, &iHiwtr, bReset); - fprintf(pArg->out, "Pager Heap Usage: %d bytes\n", iCur); + fprintf(pArg->out, "Pager Heap Usage: %d bytes\n", iCur); iHiwtr = iCur = -1; + sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_HIT, &iCur, &iHiwtr, 1); + fprintf(pArg->out, "Page cache hits: %d\n", iCur); + iHiwtr = iCur = -1; + sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_MISS, &iCur, &iHiwtr, 1); + fprintf(pArg->out, "Page cache misses: %d\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, &iCur, &iHiwtr, bReset); fprintf(pArg->out, "Schema Heap Usage: %d bytes\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_STMT_USED, &iCur, &iHiwtr, bReset); @@ -1067,10 +1092,11 @@ struct callback_data *pArg, /* Pointer to struct callback_data */ char **pzErrMsg /* Error msg written here */ ){ sqlite3_stmt *pStmt = NULL; /* Statement to execute. */ int rc = SQLITE_OK; /* Return Code */ + int rc2; const char *zLeftover; /* Tail of unprocessed SQL */ if( pzErrMsg ){ *pzErrMsg = NULL; } @@ -1083,11 +1109,11 @@ } }else{ if( !pStmt ){ /* this happens for a comment or white-space */ zSql = zLeftover; - while( isspace(zSql[0]) ) zSql++; + while( IsSpace(zSql[0]) ) zSql++; continue; } /* save off the prepared statment handle and reset row count */ if( pArg ){ @@ -1160,14 +1186,15 @@ } /* Finalize the statement just executed. If this fails, save a ** copy of the error message. Otherwise, set zSql to point to the ** next statement to execute. */ - rc = sqlite3_finalize(pStmt); + rc2 = sqlite3_finalize(pStmt); + if( rc!=SQLITE_NOMEM ) rc = rc2; if( rc==SQLITE_OK ){ zSql = zLeftover; - while( isspace(zSql[0]) ) zSql++; + while( IsSpace(zSql[0]) ) zSql++; }else if( pzErrMsg ){ *pzErrMsg = save_err_msg(db); } /* clear saved stmt handle */ @@ -1266,14 +1293,14 @@ return 1; } zSelect = appendText(zSelect, "|| ')' FROM ", 0); zSelect = appendText(zSelect, zTable, '"'); - rc = run_table_dump_query(p->out, p->db, zSelect, zPrepStmt); + rc = run_table_dump_query(p, zSelect, zPrepStmt); if( rc==SQLITE_CORRUPT ){ zSelect = appendText(zSelect, " ORDER BY rowid DESC", 0); - rc = run_table_dump_query(p->out, p->db, zSelect, 0); + run_table_dump_query(p, zSelect, 0); } if( zSelect ) free(zSelect); } return 0; } @@ -1285,23 +1312,34 @@ ** If we get a SQLITE_CORRUPT error, rerun the query after appending ** "ORDER BY rowid DESC" to the end. */ static int run_schema_dump_query( struct callback_data *p, - const char *zQuery, - char **pzErrMsg + const char *zQuery ){ int rc; - rc = sqlite3_exec(p->db, zQuery, dump_callback, p, pzErrMsg); + char *zErr = 0; + rc = sqlite3_exec(p->db, zQuery, dump_callback, p, &zErr); if( rc==SQLITE_CORRUPT ){ char *zQ2; int len = strlen30(zQuery); - if( pzErrMsg ) sqlite3_free(*pzErrMsg); + fprintf(p->out, "/****** CORRUPTION ERROR *******/\n"); + if( zErr ){ + fprintf(p->out, "/****** %s ******/\n", zErr); + sqlite3_free(zErr); + zErr = 0; + } zQ2 = malloc( len+100 ); if( zQ2==0 ) return rc; sqlite3_snprintf(sizeof(zQ2), zQ2, "%s ORDER BY rowid DESC", zQuery); - rc = sqlite3_exec(p->db, zQ2, dump_callback, p, pzErrMsg); + rc = sqlite3_exec(p->db, zQ2, dump_callback, p, &zErr); + if( rc ){ + fprintf(p->out, "/****** ERROR: %s ******/\n", zErr); + }else{ + rc = SQLITE_CORRUPT; + } + sqlite3_free(zErr); free(zQ2); } return rc; } @@ -1434,11 +1472,11 @@ */ static int booleanValue(char *zArg){ int val = atoi(zArg); int j; for(j=0; zArg[j]; j++){ - zArg[j] = (char)tolower(zArg[j]); + zArg[j] = ToLower(zArg[j]); } if( strcmp(zArg,"on")==0 ){ val = 1; }else if( strcmp(zArg,"yes")==0 ){ val = 1; @@ -1460,11 +1498,11 @@ char *azArg[50]; /* Parse the input line into tokens. */ while( zLine[i] && nArg<ArraySize(azArg) ){ - while( isspace((unsigned char)zLine[i]) ){ i++; } + while( IsSpace(zLine[i]) ){ i++; } if( zLine[i]==0 ) break; if( zLine[i]=='\'' || zLine[i]=='"' ){ int delim = zLine[i++]; azArg[nArg++] = &zLine[i]; while( zLine[i] && zLine[i]!=delim ){ i++; } @@ -1472,11 +1510,11 @@ zLine[i++] = 0; } if( delim=='"' ) resolve_backslashes(azArg[nArg-1]); }else{ azArg[nArg++] = &zLine[i]; - while( zLine[i] && !isspace((unsigned char)zLine[i]) ){ i++; } + while( zLine[i] && !IsSpace(zLine[i]) ){ i++; } if( zLine[i] ) zLine[i++] = 0; resolve_backslashes(azArg[nArg-1]); } } @@ -1543,29 +1581,29 @@ rc = 1; } }else if( c=='d' && strncmp(azArg[0], "dump", n)==0 && nArg<3 ){ - char *zErrMsg = 0; open_db(p); /* When playing back a "dump", the content might appear in an order ** which causes immediate foreign key constraints to be violated. ** So disable foreign-key constraint enforcement to prevent problems. */ fprintf(p->out, "PRAGMA foreign_keys=OFF;\n"); fprintf(p->out, "BEGIN TRANSACTION;\n"); p->writableSchema = 0; - sqlite3_exec(p->db, "PRAGMA writable_schema=ON", 0, 0, 0); + sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0); + p->nErr = 0; if( nArg==1 ){ run_schema_dump_query(p, "SELECT name, type, sql FROM sqlite_master " - "WHERE sql NOT NULL AND type=='table' AND name!='sqlite_sequence'", 0 + "WHERE sql NOT NULL AND type=='table' AND name!='sqlite_sequence'" ); run_schema_dump_query(p, "SELECT name, type, sql FROM sqlite_master " - "WHERE name=='sqlite_sequence'", 0 + "WHERE name=='sqlite_sequence'" ); - run_table_dump_query(p->out, p->db, + run_table_dump_query(p, "SELECT sql FROM sqlite_master " "WHERE sql NOT NULL AND type IN ('index','trigger','view')", 0 ); }else{ int i; @@ -1572,12 +1610,12 @@ for(i=1; i<nArg; i++){ zShellStatic = azArg[i]; run_schema_dump_query(p, "SELECT name, type, sql FROM sqlite_master " "WHERE tbl_name LIKE shellstatic() AND type=='table'" - " AND sql NOT NULL", 0); - run_table_dump_query(p->out, p->db, + " AND sql NOT NULL"); + run_table_dump_query(p, "SELECT sql FROM sqlite_master " "WHERE sql NOT NULL" " AND type IN ('index','trigger','view')" " AND tbl_name LIKE shellstatic()", 0 ); @@ -1586,17 +1624,13 @@ } if( p->writableSchema ){ fprintf(p->out, "PRAGMA writable_schema=OFF;\n"); p->writableSchema = 0; } - sqlite3_exec(p->db, "PRAGMA writable_schema=OFF", 0, 0, 0); - if( zErrMsg ){ - fprintf(stderr,"Error: %s\n", zErrMsg); - sqlite3_free(zErrMsg); - }else{ - fprintf(p->out, "COMMIT;\n"); - } + sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0); + sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0); + fprintf(p->out, p->nErr ? "ROLLBACK; -- due to errors\n" : "COMMIT;\n"); }else if( c=='e' && strncmp(azArg[0], "echo", n)==0 && nArg>1 && nArg<3 ){ p->echoOn = booleanValue(azArg[1]); }else @@ -1671,11 +1705,11 @@ nSep = strlen30(p->separator); if( nSep==0 ){ fprintf(stderr, "Error: non-null separator required for import\n"); return 1; } - zSql = sqlite3_mprintf("SELECT * FROM '%q'", zTable); + zSql = sqlite3_mprintf("SELECT * FROM %s", zTable); if( zSql==0 ){ fprintf(stderr, "Error: out of memory\n"); return 1; } nByte = strlen30(zSql); @@ -1693,11 +1727,11 @@ zSql = malloc( nByte + 20 + nCol*2 ); if( zSql==0 ){ fprintf(stderr, "Error: out of memory\n"); return 1; } - sqlite3_snprintf(nByte+20, zSql, "INSERT INTO '%q' VALUES(?", zTable); + sqlite3_snprintf(nByte+20, zSql, "INSERT INTO %s VALUES(?", zTable); j = strlen30(zSql); for(i=1; i<nCol; i++){ zSql[j++] = ','; zSql[j++] = '?'; } @@ -1725,11 +1759,10 @@ } sqlite3_exec(p->db, "BEGIN", 0, 0, 0); zCommit = "COMMIT"; while( (zLine = local_getline(0, in))!=0 ){ char *z; - i = 0; lineno++; azCol[0] = zLine; for(i=0, z=zLine; *z && *z!='\n' && *z!='\r'; z++){ if( *z==p->separator[0] && strncmp(z, p->separator, nSep)==0 ){ *z = 0; @@ -2014,11 +2047,11 @@ memcpy(&data, p, sizeof(data)); data.showHeader = 0; data.mode = MODE_Semi; if( nArg>1 ){ int i; - for(i=0; azArg[1][i]; i++) azArg[1][i] = (char)tolower(azArg[1][i]); + for(i=0; azArg[1][i]; i++) azArg[1][i] = ToLower(azArg[1][i]); if( strcmp(azArg[1],"sqlite_master")==0 ){ char *new_argv[2], *new_colv[2]; new_argv[0] = "CREATE TABLE sqlite_master (\n" " type text,\n" " name text,\n" @@ -2200,11 +2233,11 @@ for(i=0; i<(int)(sizeof(aCtrl)/sizeof(aCtrl[0])); i++){ if( strncmp(azArg[1], aCtrl[i].zCtrlName, n)==0 ){ if( testctrl<0 ){ testctrl = aCtrl[i].ctrlCode; }else{ - fprintf(stderr, "ambiguous option name: \"%s\"\n", azArg[i]); + fprintf(stderr, "ambiguous option name: \"%s\"\n", azArg[1]); testctrl = -1; break; } } } @@ -2337,11 +2370,11 @@ /* ** Test to see if a line consists entirely of whitespace. */ static int _all_whitespace(const char *z){ for(; *z; z++){ - if( isspace(*(unsigned char*)z) ) continue; + if( IsSpace(z[0]) ) continue; if( *z=='/' && z[1]=='*' ){ z += 2; while( *z && (*z!='*' || z[1]!='/') ){ z++; } if( *z==0 ) return 0; z++; @@ -2362,15 +2395,15 @@ ** Return TRUE if the line typed in is an SQL command terminator other ** than a semi-colon. The SQL Server style "go" command is understood ** as is the Oracle "/". */ static int _is_command_terminator(const char *zLine){ - while( isspace(*(unsigned char*)zLine) ){ zLine++; }; + while( IsSpace(zLine[0]) ){ zLine++; }; if( zLine[0]=='/' && _all_whitespace(&zLine[1]) ){ return 1; /* Oracle */ } - if( tolower(zLine[0])=='g' && tolower(zLine[1])=='o' + if( ToLower(zLine[0])=='g' && ToLower(zLine[1])=='o' && _all_whitespace(&zLine[2]) ){ return 1; /* SQL Server */ } return 0; } @@ -2436,11 +2469,11 @@ memcpy(zLine,";",2); } nSqlPrior = nSql; if( zSql==0 ){ int i; - for(i=0; zLine[i] && isspace((unsigned char)zLine[i]); i++){} + for(i=0; zLine[i] && IsSpace(zLine[i]); i++){} if( zLine[i]!=0 ){ nSql = strlen30(zLine); zSql = malloc( nSql+3 ); if( zSql==0 ){ fprintf(stderr, "Error: out of memory\n"); @@ -2630,10 +2663,13 @@ " -version show SQLite version\n" " -vfs NAME use NAME as the default VFS\n" #ifdef SQLITE_ENABLE_VFSTRACE " -vfstrace enable tracing of all VFS calls\n" #endif +#ifdef SQLITE_ENABLE_MULTIPLEX + " -multiplex enable the multiplexor VFS\n" +#endif ; static void usage(int showDetail){ fprintf(stderr, "Usage: %s [OPTIONS] FILENAME [SQL]\n" "FILENAME is the name of an SQLite database. A new database is created\n" @@ -2705,10 +2741,11 @@ ** we do the actual processing of arguments later in a second pass. */ }else if( strcmp(argv[i],"-batch")==0 ){ stdin_is_interactive = 0; }else if( strcmp(argv[i],"-heap")==0 ){ +#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) int j, c; const char *zSize; sqlite3_int64 szHeap; zSize = argv[++i]; @@ -2717,11 +2754,10 @@ if( c=='M' ){ szHeap *= 1000000; break; } if( c=='K' ){ szHeap *= 1000; break; } if( c=='G' ){ szHeap *= 1000000000; break; } } if( szHeap>0x7fff0000 ) szHeap = 0x7fff0000; -#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) sqlite3_config(SQLITE_CONFIG_HEAP, malloc((int)szHeap), (int)szHeap, 64); #endif #ifdef SQLITE_ENABLE_VFSTRACE }else if( strcmp(argv[i],"-vfstrace")==0 ){ extern int vfstrace_register( @@ -2731,10 +2767,15 @@ void *pOutArg, int makeDefault ); vfstrace_register("trace",0,(int(*)(const char*,void*))fputs,stderr,1); #endif +#ifdef SQLITE_ENABLE_MULTIPLEX + }else if( strcmp(argv[i],"-multiplex")==0 ){ + extern int sqlite3_multiple_initialize(const char*,int); + sqlite3_multiplex_initialize(0, 1); +#endif }else if( strcmp(argv[i],"-vfs")==0 ){ sqlite3_vfs *pVfs = sqlite3_vfs_find(argv[++i]); if( pVfs ){ sqlite3_vfs_register(pVfs, 1); }else{ @@ -2753,11 +2794,11 @@ #ifndef SQLITE_OMIT_MEMORYDB data.zDbFilename = ":memory:"; #else data.zDbFilename = 0; #endif - /***** Begin Fossil Patch *****/ + /***** Begin Fossil Patch *****/ { extern void fossil_open(const char **); fossil_open(&data.zDbFilename); } /***** End Fossil Patch *****/ @@ -2855,12 +2896,18 @@ stdin_is_interactive = 0; }else if( strcmp(z,"-heap")==0 ){ i++; }else if( strcmp(z,"-vfs")==0 ){ i++; +#ifdef SQLITE_ENABLE_VFSTRACE }else if( strcmp(z,"-vfstrace")==0 ){ i++; +#endif +#ifdef SQLITE_ENABLE_MULTIPLEX + }else if( strcmp(z,"-multiplex")==0 ){ + i++; +#endif }else if( strcmp(z,"-help")==0 || strcmp(z, "--help")==0 ){ usage(1); }else{ fprintf(stderr,"%s: Error: unknown option: %s\n", Argv0, z); fprintf(stderr,"Use -help for a list of options.\n"); Index: src/skins.c ================================================================== --- src/skins.c +++ src/skins.c @@ -152,10 +152,54 @@ @ /* The label/value pairs on (for example) the vinfo page */ @ table.label-value th { @ vertical-align: top; @ text-align: right; @ padding: 0.2ex 2ex; +@ } +@ +@ /* Side-by-side diff */ +@ table.sbsdiff { +@ font-family: fixed, Dejavu Sans Mono, Monaco, Lucida Console, monospace; +@ font-size: 10pt; +@ border-collapse:collapse; +@ white-space: pre; +@ width: 98%; +@ border: 1px #000 dashed; +@ } +@ +@ table.sbsdiff th.diffhdr { +@ border-bottom: dotted; +@ border-width: 1px; +@ } +@ +@ table.sbsdiff tr td { +@ white-space: pre; +@ padding-left: 3px; +@ padding-right: 3px; +@ margin: 0px; +@ } +@ +@ table.sbsdiff tr td.lineno { +@ text-align: right; +@ } +@ +@ table.sbsdiff tr td.meta { +@ color: white; +@ background-color: rgb(20, 20, 20); +@ text-align: center; +@ } +@ +@ table.sbsdiff tr td.added { +@ background-color: rgb(230, 230, 230); +@ } +@ +@ table.sbsdiff tr td.removed { +@ background-color: rgb(200, 200, 200); +@ } +@ +@ table.sbsdiff tr td.changed { +@ background-color: rgb(220, 220, 220); @ }'); @ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html> @ <head> @ <title>$<project_name>: $<title></title> @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" @@ -356,11 +400,53 @@ @ table.label-value th { @ vertical-align: top; @ text-align: right; @ padding: 0.2ex 2ex; @ } -@ '); +@ +@ /* Side-by-side diff */ +@ table.sbsdiff { +@ font-family: fixed, Dejavu Sans Mono, Monaco, Lucida Console, monospace; +@ font-size: 10pt; +@ border-collapse:collapse; +@ white-space: pre; +@ width: 98%; +@ border: 1px #000 dashed; +@ } +@ +@ table.sbsdiff th.diffhdr { +@ border-bottom: dotted; +@ border-width: 1px; +@ } +@ +@ table.sbsdiff tr td { +@ white-space: pre; +@ padding-left: 3px; +@ padding-right: 3px; +@ margin: 0px; +@ } +@ +@ table.sbsdiff tr td.lineno { +@ text-align: right; +@ } +@ +@ table.sbsdiff tr td.meta { +@ background-color: #a09048; +@ text-align: center; +@ } +@ +@ table.sbsdiff tr td.added { +@ background-color: rgb(210, 210, 100); +@ } +@ +@ table.sbsdiff tr td.removed { +@ background-color: rgb(190, 200, 110); +@ } +@ +@ table.sbsdiff tr td.changed { +@ background-color: rgb(200, 210, 120); +@ }'); @ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html> @ <head> @ <title>$<project_name>: $<title></title> @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" @ href="$home/timeline.rss"> @@ -590,10 +676,55 @@ @ /* The label/value pairs on (for example) the ci page */ @ table.label-value th { @ vertical-align: top; @ text-align: right; @ padding: 0.2ex 2ex; +@ } +@ +@ /* Side-by-side diff */ +@ table.sbsdiff { +@ font-family: fixed, Dejavu Sans Mono, Monaco, Lucida Console, monospace; +@ font-size: 10pt; +@ border-collapse:collapse; +@ white-space: pre; +@ width: 98%; +@ border: 1px #000 dashed; +@ } +@ +@ table.sbsdiff th.diffhdr { +@ border-bottom: dotted; +@ border-width: 1px; +@ } +@ +@ table.sbsdiff tr td { +@ white-space: pre; +@ padding-left: 3px; +@ padding-right: 3px; +@ margin: 0px; +@ } +@ +@ table.sbsdiff tr td.lineno { +@ text-align: right; +@ } +@ +@ table.sbsdiff tr td.meta { +@ color: white; +@ background-color: black; +@ text-align: center; +@ } +@ +@ table.sbsdiff tr td.added { +@ background-color: white; +@ } +@ +@ table.sbsdiff tr td.removed { +@ background-color: white; +@ text-decoration: line-through; +@ } +@ +@ table.sbsdiff tr td.changed { +@ background-color: white; @ }'); @ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html> @ <head> @ <title>$<project_name>: $<title></title> @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" @@ -885,10 +1016,77 @@ @ padding: 3px 5px; @ } @ @ textarea { @ font-size: 1em; +@ } +@ +@ /* Side-by-side diff */ +@ table.sbsdiff { +@ background-color: white; +@ font-family: Dejavu Sans Mono, Monaco, Lucida Console, monospace; +@ font-size: 8pt; +@ border-collapse:collapse; +@ width: 98%; +@ border: 1px #000 dashed; +@ margin-left: auto; +@ margin-right: auto; +@ } +@ +@ table.sbsdiff th.diffhdr { +@ border-bottom: dotted; +@ border-width: 1px; +@ } +@ +@ table.sbsdiff tr td { +@ padding-left: 3px; +@ padding-right: 3px; +@ margin: 0px; +@ vertical-align: top; +@ white-space: pre-wrap; +@ } +@ +@ table.sbsdiff tr td.lineno { +@ text-align: right; +@ /* border-bottom: 1px solid rgb(220, 220, 220); */ +@ } +@ +@ table.sbsdiff tr td.srcline { +@ max-width: 400px; +@ /* Nota bene: May partially hide long lines witout whitespaces */ +@ overflow: hidden; +@ /* border-bottom: 1px solid rgb(220, 220, 220); */ +@ } +@ +@ table.sbsdiff tr td.meta { +@ background-color: rgb(170, 160, 255); +@ padding-top: 0.25em; +@ padding-bottom: 0.25em; +@ text-align: center; +@ -moz-border-radius: 5px; +@ -moz-border-radius: 5px; +@ -webkit-border-radius: 5px; +@ -webkit-border-radius: 5px; +@ -border-radius: 5px; +@ -border-radius: 5px; +@ border-radius: 5px; +@ border-radius: 5px; +@ } +@ +@ table.sbsdiff tr td.added { +@ background-color: rgb(180, 250, 180); +@ /* border-bottom: 1px solid rgb(160, 230, 160); */ +@ } +@ +@ table.sbsdiff tr td.removed { +@ background-color: rgb(250, 130, 130); +@ /* border-bottom: 1px solid rgb(230, 110, 110); */ +@ } +@ +@ table.sbsdiff tr td.changed { +@ background-color: rgb(210, 210, 200); +@ /* border-bottom: 1px solid rgb(190, 190, 180); */ @ }'); @ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html> @ <head> @ <title>$<project_name>: $<title></title> @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" Index: src/sqlite3.c ================================================================== --- src/sqlite3.c +++ src/sqlite3.c @@ -1,8 +1,8 @@ /****************************************************************************** ** This file is an amalgamation of many separate C source files from SQLite -** version 3.7.8. By combining all the individual C code files into this +** version 3.7.9. By combining all the individual C code files into this ** single large file, the entire code can be compiled as a single translation ** unit. This allows many compilers to do optimizations that would not be ** possible if the files were compiled separately. Performance improvements ** of 5% or more are commonly seen when SQLite is compiled as a single ** translation unit. @@ -394,11 +394,11 @@ ** assert() macro is enabled, each call into the Win32 native heap subsystem ** will cause HeapValidate to be called. If heap validation should fail, an ** assertion will be triggered. ** ** (Historical note: There used to be several other options, but we've -** pared it down to just these two.) +** pared it down to just these three.) ** ** If none of the above are defined, then set SQLITE_SYSTEM_MALLOC as ** the default. */ #if defined(SQLITE_SYSTEM_MALLOC)+defined(SQLITE_WIN32_MALLOC)+defined(SQLITE_MEMDEBUG)>1 @@ -654,13 +654,13 @@ ** ** See also: [sqlite3_libversion()], ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.7.8" -#define SQLITE_VERSION_NUMBER 3007008 -#define SQLITE_SOURCE_ID "2011-09-19 14:49:19 3e0da808d2f5b4d12046e05980ca04578f581177" +#define SQLITE_VERSION "3.7.9" +#define SQLITE_VERSION_NUMBER 3007009 +#define SQLITE_SOURCE_ID "2011-10-15 00:16:30 39408702a989f907261c298bf0947f3e68bd10fe" /* ** CAPI3REF: Run-Time Library Version Numbers ** KEYWORDS: sqlite3_version, sqlite3_sourceid ** @@ -1318,11 +1318,15 @@ ** in order for the database to be readable. The fourth parameter to ** [sqlite3_file_control()] for this opcode should be a pointer to an integer. ** That integer is 0 to disable persistent WAL mode or 1 to enable persistent ** WAL mode. If the integer is -1, then it is overwritten with the current ** WAL persistence setting. -** +** +** ^The [SQLITE_FCNTL_OVERWRITE] opcode is invoked by SQLite after opening +** a write transaction to indicate that, unless it is rolled back for some +** reason, the entire database file will be overwritten by the current +** transaction. This is used by VACUUM operations. */ #define SQLITE_FCNTL_LOCKSTATE 1 #define SQLITE_GET_LOCKPROXYFILE 2 #define SQLITE_SET_LOCKPROXYFILE 3 #define SQLITE_LAST_ERRNO 4 @@ -1330,10 +1334,11 @@ #define SQLITE_FCNTL_CHUNK_SIZE 6 #define SQLITE_FCNTL_FILE_POINTER 7 #define SQLITE_FCNTL_SYNC_OMITTED 8 #define SQLITE_FCNTL_WIN32_AV_RETRY 9 #define SQLITE_FCNTL_PERSIST_WAL 10 +#define SQLITE_FCNTL_OVERWRITE 11 /* ** CAPI3REF: Mutex Handle ** ** The mutex module within SQLite defines [sqlite3_mutex] to be an @@ -3346,11 +3351,12 @@ ** zSql string ends at either the first '\000' or '\u0000' character or ** the nByte-th byte, whichever comes first. If the caller knows ** that the supplied string is nul-terminated, then there is a small ** performance advantage to be gained by passing an nByte parameter that ** is equal to the number of bytes in the input string <i>including</i> -** the nul-terminator bytes. +** the nul-terminator bytes as this saves SQLite from having to +** make a copy of the input string. ** ** ^If pzTail is not NULL then *pzTail is made to point to the first byte ** past the end of the first SQL statement in zSql. These routines only ** compile the first statement in zSql, so *pzTail is left pointing to ** what remains uncompiled. @@ -3397,11 +3403,11 @@ ** a schema change, on the first [sqlite3_step()] call following any change ** to the [sqlite3_bind_text | bindings] of that [parameter]. ** ^The specific value of WHERE-clause [parameter] might influence the ** choice of query plan if the parameter is the left-hand side of a [LIKE] ** or [GLOB] operator or if the parameter is compared to an indexed column -** and the [SQLITE_ENABLE_STAT2] compile-time option is enabled. +** and the [SQLITE_ENABLE_STAT3] compile-time option is enabled. ** the ** </li> ** </ol> */ SQLITE_API int sqlite3_prepare( @@ -3567,10 +3573,17 @@ ** ^(In those routines that have a fourth argument, its value is the ** number of bytes in the parameter. To be clear: the value is the ** number of <u>bytes</u> in the value, not the number of characters.)^ ** ^If the fourth parameter is negative, the length of the string is ** the number of bytes up to the first zero terminator. +** If a non-negative fourth parameter is provided to sqlite3_bind_text() +** or sqlite3_bind_text16() then that parameter must be the byte offset +** where the NUL terminator would occur assuming the string were NUL +** terminated. If any NUL characters occur at byte offsets less than +** the value of the fourth parameter then the resulting string value will +** contain embedded NULs. The result of expressions involving strings +** with embedded NULs is undefined. ** ** ^The fifth argument to sqlite3_bind_blob(), sqlite3_bind_text(), and ** sqlite3_bind_text16() is a destructor used to dispose of the BLOB or ** string after SQLite has finished with it. ^The destructor is called ** to dispose of the BLOB or string even if the call to sqlite3_bind_blob(), @@ -3900,10 +3913,16 @@ ** current row of the result set of [prepared statement] P. ** ^If prepared statement P does not have results ready to return ** (via calls to the [sqlite3_column_int | sqlite3_column_*()] of ** interfaces) then sqlite3_data_count(P) returns 0. ** ^The sqlite3_data_count(P) routine also returns 0 if P is a NULL pointer. +** ^The sqlite3_data_count(P) routine returns 0 if the previous call to +** [sqlite3_step](P) returned [SQLITE_DONE]. ^The sqlite3_data_count(P) +** will return non-zero if previous call to [sqlite3_step](P) returned +** [SQLITE_ROW], except in the case of the [PRAGMA incremental_vacuum] +** where it always returns zero since each step of that multi-step +** pragma returns 0 columns of data. ** ** See also: [sqlite3_column_count()] */ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); @@ -4579,11 +4598,16 @@ ** is negative, then SQLite takes result text from the 2nd parameter ** through the first zero character. ** ^If the 3rd parameter to the sqlite3_result_text* interfaces ** is non-negative, then as many bytes (not characters) of the text ** pointed to by the 2nd parameter are taken as the application-defined -** function result. +** function result. If the 3rd parameter is non-negative, then it +** must be the byte offset into the string where the NUL terminator would +** appear if the string where NUL terminated. If any NUL characters occur +** in the string at a byte offset that is less than the value of the 3rd +** parameter, then the resulting string will contain embedded NULs and the +** result of expressions operating on strings with embedded NULs is undefined. ** ^If the 4th parameter to the sqlite3_result_text* interfaces ** or sqlite3_result_blob is a non-NULL pointer, then SQLite calls that ** function as the destructor on the text or BLOB result when it has ** finished using that result. ** ^If the 4th parameter to the sqlite3_result_text* interfaces or to @@ -6362,20 +6386,34 @@ ** <dd>This parameter returns the approximate number of of bytes of heap ** and lookaside memory used by all prepared statements associated with ** the database connection.)^ ** ^The highwater mark associated with SQLITE_DBSTATUS_STMT_USED is always 0. ** </dd> +** +** [[SQLITE_DBSTATUS_CACHE_HIT]] ^(<dt>SQLITE_DBSTATUS_CACHE_HIT</dt> +** <dd>This parameter returns the number of pager cache hits that have +** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_HIT +** is always 0. +** </dd> +** +** [[SQLITE_DBSTATUS_CACHE_MISS]] ^(<dt>SQLITE_DBSTATUS_CACHE_MISS</dt> +** <dd>This parameter returns the number of pager cache misses that have +** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_MISS +** is always 0. +** </dd> ** </dl> */ #define SQLITE_DBSTATUS_LOOKASIDE_USED 0 #define SQLITE_DBSTATUS_CACHE_USED 1 #define SQLITE_DBSTATUS_SCHEMA_USED 2 #define SQLITE_DBSTATUS_STMT_USED 3 #define SQLITE_DBSTATUS_LOOKASIDE_HIT 4 #define SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE 5 #define SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL 6 -#define SQLITE_DBSTATUS_MAX 6 /* Largest defined DBSTATUS */ +#define SQLITE_DBSTATUS_CACHE_HIT 7 +#define SQLITE_DBSTATUS_CACHE_MISS 8 +#define SQLITE_DBSTATUS_MAX 8 /* Largest defined DBSTATUS */ /* ** CAPI3REF: Prepared Statement Status ** @@ -6425,11 +6463,10 @@ ** <dd>^This is the number of rows inserted into transient indices that ** were created automatically in order to help joins run faster. ** A non-zero value in this counter may indicate an opportunity to ** improvement performance by adding permanent indices that do not ** need to be reinitialized each time the statement is run.</dd> -** ** </dl> */ #define SQLITE_STMTSTATUS_FULLSCAN_STEP 1 #define SQLITE_STMTSTATUS_SORT 2 #define SQLITE_STMTSTATUS_AUTOINDEX 3 @@ -7711,10 +7748,22 @@ ** is 0x00000000ffffffff. But because of quirks of some compilers, we ** have to specify the value in the less intuitive manner shown: */ #define SQLITE_MAX_U32 ((((u64)1)<<32)-1) +/* +** The datatype used to store estimates of the number of rows in a +** table or index. This is an unsigned integer type. For 99.9% of +** the world, a 32-bit integer is sufficient. But a 64-bit integer +** can be used at compile-time if desired. +*/ +#ifdef SQLITE_64BIT_STATS + typedef u64 tRowcnt; /* 64-bit only if requested at compile-time */ +#else + typedef u32 tRowcnt; /* 32-bit is the default */ +#endif + /* ** Macros to determine whether the machine is big or little endian, ** evaluated at runtime. */ #ifdef SQLITE_AMALGAMATION @@ -8742,10 +8791,11 @@ SQLITE_PRIVATE sqlite3_file *sqlite3PagerFile(Pager*); SQLITE_PRIVATE const char *sqlite3PagerJournalname(Pager*); SQLITE_PRIVATE int sqlite3PagerNosync(Pager*); SQLITE_PRIVATE void *sqlite3PagerTempSpace(Pager*); SQLITE_PRIVATE int sqlite3PagerIsMemdb(Pager*); +SQLITE_PRIVATE void sqlite3PagerCacheStat(Pager *, int, int, int *); /* Functions used to truncate the database file. */ SQLITE_PRIVATE void sqlite3PagerTruncateImage(Pager*,Pgno); #if defined(SQLITE_HAS_CODEC) && !defined(SQLITE_OMIT_WAL) @@ -9278,18 +9328,21 @@ /* ** If this is a no-op implementation, implement everything as macros. */ #define sqlite3_mutex_alloc(X) ((sqlite3_mutex*)8) #define sqlite3_mutex_free(X) -#define sqlite3_mutex_enter(X) +#define sqlite3_mutex_enter(X) #define sqlite3_mutex_try(X) SQLITE_OK -#define sqlite3_mutex_leave(X) +#define sqlite3_mutex_leave(X) #define sqlite3_mutex_held(X) ((void)(X),1) #define sqlite3_mutex_notheld(X) ((void)(X),1) #define sqlite3MutexAlloc(X) ((sqlite3_mutex*)8) #define sqlite3MutexInit() SQLITE_OK #define sqlite3MutexEnd() +#define MUTEX_LOGIC(X) +#else +#define MUTEX_LOGIC(X) X #endif /* defined(SQLITE_MUTEX_OMIT) */ /************** End of mutex.h ***********************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ @@ -9918,11 +9971,11 @@ int iPKey; /* If not negative, use aCol[iPKey] as the primary key */ int nCol; /* Number of columns in this table */ Column *aCol; /* Information about each column */ Index *pIndex; /* List of SQL indexes on this table. */ int tnum; /* Root BTree node for this table (see note above) */ - unsigned nRowEst; /* Estimated rows in table - from sqlite_stat1 table */ + tRowcnt nRowEst; /* Estimated rows in table - from sqlite_stat1 table */ Select *pSelect; /* NULL for tables. Points to definition if a view. */ u16 nRef; /* Number of pointers to this Table */ u8 tabFlags; /* Mask of TF_* values */ u8 keyConf; /* What to do in case of uniqueness conflict on iPKey */ FKey *pFKey; /* Linked list of all foreign keys in this table */ @@ -10117,11 +10170,11 @@ */ struct Index { char *zName; /* Name of this index */ int nColumn; /* Number of columns in the table used by this index */ int *aiColumn; /* Which columns are used by this index. 1st is 0 */ - unsigned *aiRowEst; /* Result of ANALYZE: Est. rows selected by each column */ + tRowcnt *aiRowEst; /* Result of ANALYZE: Est. rows selected by each column */ Table *pTable; /* The SQL table being indexed */ int tnum; /* Page containing root of this index in database file */ u8 onError; /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */ u8 autoIndex; /* True if is automatically created (ex: by UNIQUE) */ u8 bUnordered; /* Use this index for == or IN queries only */ @@ -10128,24 +10181,32 @@ char *zColAff; /* String defining the affinity of each column */ Index *pNext; /* The next index associated with the same table */ Schema *pSchema; /* Schema containing this index */ u8 *aSortOrder; /* Array of size Index.nColumn. True==DESC, False==ASC */ char **azColl; /* Array of collation sequence names for index */ - IndexSample *aSample; /* Array of SQLITE_INDEX_SAMPLES samples */ +#ifdef SQLITE_ENABLE_STAT3 + int nSample; /* Number of elements in aSample[] */ + tRowcnt avgEq; /* Average nEq value for key values not in aSample */ + IndexSample *aSample; /* Samples of the left-most key */ +#endif }; /* ** Each sample stored in the sqlite_stat2 table is represented in memory ** using a structure of this type. */ struct IndexSample { union { char *z; /* Value if eType is SQLITE_TEXT or SQLITE_BLOB */ - double r; /* Value if eType is SQLITE_FLOAT or SQLITE_INTEGER */ + double r; /* Value if eType is SQLITE_FLOAT */ + i64 i; /* Value if eType is SQLITE_INTEGER */ } u; u8 eType; /* SQLITE_NULL, SQLITE_INTEGER ... etc. */ - u8 nByte; /* Size in byte of text or blob. */ + int nByte; /* Size in byte of text or blob. */ + tRowcnt nEq; /* Est. number of rows where the key equals this sample */ + tRowcnt nLt; /* Est. number of rows where key is less than this sample */ + tRowcnt nDLt; /* Est. number of distinct keys less than this sample */ }; /* ** Each token coming out of the lexer is an instance of ** this structure. Tokens are also used as part of an expression. @@ -10593,14 +10654,14 @@ #define WHERE_ORDERBY_NORMAL 0x0000 /* No-op */ #define WHERE_ORDERBY_MIN 0x0001 /* ORDER BY processing for min() func */ #define WHERE_ORDERBY_MAX 0x0002 /* ORDER BY processing for max() func */ #define WHERE_ONEPASS_DESIRED 0x0004 /* Want to do one-pass UPDATE/DELETE */ #define WHERE_DUPLICATES_OK 0x0008 /* Ok to return a row more than once */ -#define WHERE_OMIT_OPEN 0x0010 /* Table cursors are already open */ -#define WHERE_OMIT_CLOSE 0x0020 /* Omit close of table & index cursors */ -#define WHERE_FORCE_TABLE 0x0040 /* Do not use an index-only search */ -#define WHERE_ONETABLE_ONLY 0x0080 /* Only code the 1st table in pTabList */ +#define WHERE_OMIT_OPEN_CLOSE 0x0010 /* Table cursors are already open */ +#define WHERE_FORCE_TABLE 0x0020 /* Do not use an index-only search */ +#define WHERE_ONETABLE_ONLY 0x0040 /* Only code the 1st table in pTabList */ +#define WHERE_AND_ONLY 0x0080 /* Don't use indices for OR terms */ /* ** The WHERE clause processing routine has two halves. The ** first part does the start of the WHERE loop and the second ** half does the tail of the WHERE loop. An instance of @@ -11350,10 +11411,11 @@ #else # define sqlite3ViewGetColumnNames(A,B) 0 #endif SQLITE_PRIVATE void sqlite3DropTable(Parse*, SrcList*, int, int); +SQLITE_PRIVATE void sqlite3CodeDropTable(Parse*, Table*, int, int); SQLITE_PRIVATE void sqlite3DeleteTable(sqlite3*, Table*); #ifndef SQLITE_OMIT_AUTOINCREMENT SQLITE_PRIVATE void sqlite3AutoincrementBegin(Parse *pParse); SQLITE_PRIVATE void sqlite3AutoincrementEnd(Parse *pParse); #else @@ -11606,11 +11668,11 @@ SQLITE_PRIVATE void sqlite3ValueSetStr(sqlite3_value*, int, const void *,u8, void(*)(void*)); SQLITE_PRIVATE void sqlite3ValueFree(sqlite3_value*); SQLITE_PRIVATE sqlite3_value *sqlite3ValueNew(sqlite3 *); SQLITE_PRIVATE char *sqlite3Utf16to8(sqlite3 *, const void*, int, u8); -#ifdef SQLITE_ENABLE_STAT2 +#ifdef SQLITE_ENABLE_STAT3 SQLITE_PRIVATE char *sqlite3Utf8to16(sqlite3 *, u8, char *, int, int *); #endif SQLITE_PRIVATE int sqlite3ValueFromExpr(sqlite3 *, Expr *, u8, u8, sqlite3_value **); SQLITE_PRIVATE void sqlite3ValueApplyAffinity(sqlite3_value *, u8, u8); #ifndef SQLITE_AMALGAMATION @@ -11708,19 +11770,21 @@ # define sqlite3VtabInSync(db) 0 # define sqlite3VtabLock(X) # define sqlite3VtabUnlock(X) # define sqlite3VtabUnlockList(X) # define sqlite3VtabSavepoint(X, Y, Z) SQLITE_OK +# define sqlite3GetVTable(X,Y) ((VTable*)0) #else SQLITE_PRIVATE void sqlite3VtabClear(sqlite3 *db, Table*); SQLITE_PRIVATE int sqlite3VtabSync(sqlite3 *db, char **); SQLITE_PRIVATE int sqlite3VtabRollback(sqlite3 *db); SQLITE_PRIVATE int sqlite3VtabCommit(sqlite3 *db); SQLITE_PRIVATE void sqlite3VtabLock(VTable *); SQLITE_PRIVATE void sqlite3VtabUnlock(VTable *); SQLITE_PRIVATE void sqlite3VtabUnlockList(sqlite3*); SQLITE_PRIVATE int sqlite3VtabSavepoint(sqlite3 *, int, int); +SQLITE_PRIVATE VTable *sqlite3GetVTable(sqlite3*, Table*); # define sqlite3VtabInSync(db) ((db)->nVTrans>0 && (db)->aVTrans==0) #endif SQLITE_PRIVATE void sqlite3VtabMakeWritable(Parse*,Table*); SQLITE_PRIVATE void sqlite3VtabBeginParse(Parse*, Token*, Token*, Token*); SQLITE_PRIVATE void sqlite3VtabFinishParse(Parse*, Token*); @@ -11736,11 +11800,10 @@ SQLITE_PRIVATE int sqlite3TransferBindings(sqlite3_stmt *, sqlite3_stmt *); SQLITE_PRIVATE int sqlite3Reprepare(Vdbe*); SQLITE_PRIVATE void sqlite3ExprListCheckLength(Parse*, ExprList*, const char*); SQLITE_PRIVATE CollSeq *sqlite3BinaryCompareCollSeq(Parse *, Expr *, Expr *); SQLITE_PRIVATE int sqlite3TempInMemory(const sqlite3*); -SQLITE_PRIVATE VTable *sqlite3GetVTable(sqlite3*, Table*); SQLITE_PRIVATE const char *sqlite3JournalModename(int); SQLITE_PRIVATE int sqlite3Checkpoint(sqlite3*, int, int, int*, int*); SQLITE_PRIVATE int sqlite3WalDefaultHook(void*,sqlite3*,const char*,int); /* Declarations for functions in fkey.c. All of these are replaced by @@ -12232,10 +12295,13 @@ #ifdef SQLITE_ENABLE_RTREE "ENABLE_RTREE", #endif #ifdef SQLITE_ENABLE_STAT2 "ENABLE_STAT2", +#endif +#ifdef SQLITE_ENABLE_STAT3 + "ENABLE_STAT3", #endif #ifdef SQLITE_ENABLE_UNLOCK_NOTIFY "ENABLE_UNLOCK_NOTIFY", #endif #ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT @@ -12445,13 +12511,10 @@ "OMIT_WSD", #endif #ifdef SQLITE_OMIT_XFER_OPT "OMIT_XFER_OPT", #endif -#ifdef SQLITE_PAGECACHE_BLOCKALLOC - "PAGECACHE_BLOCKALLOC", -#endif #ifdef SQLITE_PERFORMANCE_TRACE "PERFORMANCE_TRACE", #endif #ifdef SQLITE_PROXY_DEBUG "PROXY_DEBUG", @@ -13189,10 +13252,32 @@ *pHighwater = 0; *pCurrent = nByte; break; } + + /* + ** Set *pCurrent to the total cache hits or misses encountered by all + ** pagers the database handle is connected to. *pHighwater is always set + ** to zero. + */ + case SQLITE_DBSTATUS_CACHE_HIT: + case SQLITE_DBSTATUS_CACHE_MISS: { + int i; + int nRet = 0; + assert( SQLITE_DBSTATUS_CACHE_MISS==SQLITE_DBSTATUS_CACHE_HIT+1 ); + + for(i=0; i<db->nDb; i++){ + if( db->aDb[i].pBt ){ + Pager *pPager = sqlite3BtreePager(db->aDb[i].pBt); + sqlite3PagerCacheStat(pPager, op, resetFlag, &nRet); + } + } + *pHighwater = 0; + *pCurrent = nRet; + break; + } default: { rc = SQLITE_ERROR; } } @@ -13490,16 +13575,22 @@ } return 0; } /* -** Set the time to the current time reported by the VFS +** Set the time to the current time reported by the VFS. +** +** Return the number of errors. */ -static void setDateTimeToCurrent(sqlite3_context *context, DateTime *p){ +static int setDateTimeToCurrent(sqlite3_context *context, DateTime *p){ sqlite3 *db = sqlite3_context_db_handle(context); - sqlite3OsCurrentTimeInt64(db->pVfs, &p->iJD); - p->validJD = 1; + if( sqlite3OsCurrentTimeInt64(db->pVfs, &p->iJD)==SQLITE_OK ){ + p->validJD = 1; + return 0; + }else{ + return 1; + } } /* ** Attempt to parse the given string into a Julian Day Number. Return ** the number of errors. @@ -13525,12 +13616,11 @@ if( parseYyyyMmDd(zDate,p)==0 ){ return 0; }else if( parseHhMmSs(zDate, p)==0 ){ return 0; }else if( sqlite3StrICmp(zDate,"now")==0){ - setDateTimeToCurrent(context, p); - return 0; + return setDateTimeToCurrent(context, p); }else if( sqlite3AtoF(zDate, &r, sqlite3Strlen30(zDate), SQLITE_UTF8) ){ p->iJD = (sqlite3_int64)(r*86400000.0 + 0.5); p->validJD = 1; return 0; } @@ -13953,12 +14043,13 @@ int i; const unsigned char *z; int eType; memset(p, 0, sizeof(*p)); if( argc==0 ){ - setDateTimeToCurrent(context, p); - }else if( (eType = sqlite3_value_type(argv[0]))==SQLITE_FLOAT + return setDateTimeToCurrent(context, p); + } + if( (eType = sqlite3_value_type(argv[0]))==SQLITE_FLOAT || eType==SQLITE_INTEGER ){ p->iJD = (sqlite3_int64)(sqlite3_value_double(argv[0])*86400000.0 + 0.5); p->validJD = 1; }else{ z = sqlite3_value_text(argv[0]); @@ -14266,35 +14357,32 @@ ){ time_t t; char *zFormat = (char *)sqlite3_user_data(context); sqlite3 *db; sqlite3_int64 iT; + struct tm *pTm; + struct tm sNow; char zBuf[20]; UNUSED_PARAMETER(argc); UNUSED_PARAMETER(argv); db = sqlite3_context_db_handle(context); - sqlite3OsCurrentTimeInt64(db->pVfs, &iT); + if( sqlite3OsCurrentTimeInt64(db->pVfs, &iT) ) return; t = iT/1000 - 10000*(sqlite3_int64)21086676; #ifdef HAVE_GMTIME_R - { - struct tm sNow; - gmtime_r(&t, &sNow); + pTm = gmtime_r(&t, &sNow); +#else + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); + pTm = gmtime(&t); + if( pTm ) memcpy(&sNow, pTm, sizeof(sNow)); + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); +#endif + if( pTm ){ strftime(zBuf, 20, zFormat, &sNow); - } -#else - { - struct tm *pTm; - sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); - pTm = gmtime(&t); - strftime(zBuf, 20, zFormat, pTm); - sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); - } -#endif - - sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); + sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); + } } #endif /* ** This function registered all of the above C functions as SQL @@ -14625,16 +14713,16 @@ ** Register a VFS with the system. It is harmless to register the same ** VFS multiple times. The new VFS becomes the default if makeDflt is ** true. */ SQLITE_API int sqlite3_vfs_register(sqlite3_vfs *pVfs, int makeDflt){ - sqlite3_mutex *mutex = 0; + MUTEX_LOGIC(sqlite3_mutex *mutex;) #ifndef SQLITE_OMIT_AUTOINIT int rc = sqlite3_initialize(); if( rc ) return rc; #endif - mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); + MUTEX_LOGIC( mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); ) sqlite3_mutex_enter(mutex); vfsUnlink(pVfs); if( makeDflt || vfsList==0 ){ pVfs->pNext = vfsList; vfsList = pVfs; @@ -18878,52 +18966,14 @@ ** an historical reference. Most of the "enhancements" have been backed ** out so that the functionality is now the same as standard printf(). ** ************************************************************************** ** -** The following modules is an enhanced replacement for the "printf" subroutines -** found in the standard C library. The following enhancements are -** supported: -** -** + Additional functions. The standard set of "printf" functions -** includes printf, fprintf, sprintf, vprintf, vfprintf, and -** vsprintf. This module adds the following: -** -** * snprintf -- Works like sprintf, but has an extra argument -** which is the size of the buffer written to. -** -** * mprintf -- Similar to sprintf. Writes output to memory -** obtained from malloc. -** -** * xprintf -- Calls a function to dispose of output. -** -** * nprintf -- No output, but returns the number of characters -** that would have been output by printf. -** -** * A v- version (ex: vsnprintf) of every function is also -** supplied. -** -** + A few extensions to the formatting notation are supported: -** -** * The "=" flag (similar to "-") causes the output to be -** be centered in the appropriately sized field. -** -** * The %b field outputs an integer in binary notation. -** -** * The %c field now accepts a precision. The character output -** is repeated by the number of times the precision specifies. -** -** * The %' field works like %c, but takes as its character the -** next character of the format string, instead of the next -** argument. For example, printf("%.78'-") prints 78 minus -** signs, the same as printf("%.78c",'-'). -** -** + When compiled using GCC on a SPARC, this version of printf is -** faster than the library printf for SUN OS 4.1. -** -** + All functions are fully reentrant. -** +** This file contains code for a set of "printf"-like routines. These +** routines format strings much like the printf() from the standard C +** library, though the implementation here has enhancements to support +** SQLlite. */ /* ** Conversion types fall into various categories as defined by the ** following enumeration. @@ -19057,47 +19107,19 @@ } } /* ** On machines with a small stack size, you can redefine the -** SQLITE_PRINT_BUF_SIZE to be less than 350. +** SQLITE_PRINT_BUF_SIZE to be something smaller, if desired. */ #ifndef SQLITE_PRINT_BUF_SIZE -# if defined(SQLITE_SMALL_STACK) -# define SQLITE_PRINT_BUF_SIZE 50 -# else -# define SQLITE_PRINT_BUF_SIZE 350 -# endif +# define SQLITE_PRINT_BUF_SIZE 70 #endif #define etBUFSIZE SQLITE_PRINT_BUF_SIZE /* Size of the output buffer */ /* -** The root program. All variations call this core. -** -** INPUTS: -** func This is a pointer to a function taking three arguments -** 1. A pointer to anything. Same as the "arg" parameter. -** 2. A pointer to the list of characters to be output -** (Note, this list is NOT null terminated.) -** 3. An integer number of characters to be output. -** (Note: This number might be zero.) -** -** arg This is the pointer to anything which will be passed as the -** first argument to "func". Use it for whatever you like. -** -** fmt This is the format string, as in the usual print. -** -** ap This is a pointer to a list of arguments. Same as in -** vfprint. -** -** OUTPUTS: -** The return value is the total number of characters sent to -** the function "func". Returns -1 on a error. -** -** Note that the order in which automatic variables are declared below -** seems to make a big difference in determining how fast this beast -** will run. +** Render a string given by "fmt" into the StrAccum object. */ SQLITE_PRIVATE void sqlite3VXPrintf( StrAccum *pAccum, /* Accumulate results here */ int useExtended, /* Allow extended %-conversions */ const char *fmt, /* Format string */ @@ -19116,27 +19138,27 @@ etByte flag_altform2; /* True if "!" flag is present */ etByte flag_zeropad; /* True if field width constant starts with zero */ etByte flag_long; /* True if "l" flag is present */ etByte flag_longlong; /* True if the "ll" flag is present */ etByte done; /* Loop termination flag */ + etByte xtype = 0; /* Conversion paradigm */ + char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */ sqlite_uint64 longvalue; /* Value for integer types */ LONGDOUBLE_TYPE realvalue; /* Value for real types */ const et_info *infop; /* Pointer to the appropriate info structure */ - char buf[etBUFSIZE]; /* Conversion buffer */ - char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */ - etByte xtype = 0; /* Conversion paradigm */ - char *zExtra; /* Extra memory used for etTCLESCAPE conversions */ + char *zOut; /* Rendering buffer */ + int nOut; /* Size of the rendering buffer */ + char *zExtra; /* Malloced memory used by some conversion */ #ifndef SQLITE_OMIT_FLOATING_POINT int exp, e2; /* exponent of real numbers */ + int nsd; /* Number of significant digits returned */ double rounder; /* Used for rounding floating point values */ etByte flag_dp; /* True if decimal point should be shown */ etByte flag_rtz; /* True if trailing zeros should be removed */ - etByte flag_exp; /* True to force display of the exponent */ - int nsd; /* Number of significant digits returned */ #endif + char buf[etBUFSIZE]; /* Conversion buffer */ - length = 0; bufpt = 0; for(; (c=(*fmt))!=0; ++fmt){ if( c!='%' ){ int amt; bufpt = (char *)fmt; @@ -19177,13 +19199,10 @@ while( c>='0' && c<='9' ){ width = width*10 + c - '0'; c = *++fmt; } } - if( width > etBUFSIZE-10 ){ - width = etBUFSIZE-10; - } /* Get the precision */ if( c=='.' ){ precision = 0; c = *++fmt; if( c=='*' ){ @@ -19226,16 +19245,10 @@ break; } } zExtra = 0; - - /* Limit the precision to prevent overflowing buf[] during conversion */ - if( precision>etBUFSIZE-40 && (infop->flags & FLAG_STRING)==0 ){ - precision = etBUFSIZE-40; - } - /* ** At this point, variables are initialized as follows: ** ** flag_alternateform TRUE if a '#' is present. ** flag_altform2 TRUE if a '!' is present. @@ -19296,20 +19309,30 @@ } if( longvalue==0 ) flag_alternateform = 0; if( flag_zeropad && precision<width-(prefix!=0) ){ precision = width-(prefix!=0); } - bufpt = &buf[etBUFSIZE-1]; + if( precision<etBUFSIZE-10 ){ + nOut = etBUFSIZE; + zOut = buf; + }else{ + nOut = precision + 10; + zOut = zExtra = sqlite3Malloc( nOut ); + if( zOut==0 ){ + pAccum->mallocFailed = 1; + return; + } + } + bufpt = &zOut[nOut-1]; if( xtype==etORDINAL ){ static const char zOrd[] = "thstndrd"; int x = (int)(longvalue % 10); if( x>=4 || (longvalue/10)%10==1 ){ x = 0; } - buf[etBUFSIZE-3] = zOrd[x*2]; - buf[etBUFSIZE-2] = zOrd[x*2+1]; - bufpt -= 2; + *(--bufpt) = zOrd[x*2+1]; + *(--bufpt) = zOrd[x*2]; } { register const char *cset; /* Use registers for speed */ register int base; cset = &aDigits[infop->charset]; @@ -19317,11 +19340,11 @@ do{ /* Convert to ascii */ *(--bufpt) = cset[longvalue%base]; longvalue = longvalue/base; }while( longvalue>0 ); } - length = (int)(&buf[etBUFSIZE-1]-bufpt); + length = (int)(&zOut[nOut-1]-bufpt); for(idx=precision-length; idx>0; idx--){ *(--bufpt) = '0'; /* Zero pad */ } if( prefix ) *(--bufpt) = prefix; /* Add sign */ if( flag_alternateform && infop->prefix ){ /* Add "0" or "0x" */ @@ -19328,21 +19351,20 @@ const char *pre; char x; pre = &aPrefix[infop->prefix]; for(; (x=(*pre))!=0; pre++) *(--bufpt) = x; } - length = (int)(&buf[etBUFSIZE-1]-bufpt); + length = (int)(&zOut[nOut-1]-bufpt); break; case etFLOAT: case etEXP: case etGENERIC: realvalue = va_arg(ap,double); #ifdef SQLITE_OMIT_FLOATING_POINT length = 0; #else if( precision<0 ) precision = 6; /* Set default precision */ - if( precision>etBUFSIZE/2-10 ) precision = etBUFSIZE/2-10; if( realvalue<0.0 ){ realvalue = -realvalue; prefix = '-'; }else{ if( flag_plussign ) prefix = '+'; @@ -19386,11 +19408,10 @@ bufpt = buf; /* ** If the field type is etGENERIC, then convert to either etEXP ** or etFLOAT, as appropriate. */ - flag_exp = xtype==etEXP; if( xtype!=etFLOAT ){ realvalue += rounder; if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; } } if( xtype==etGENERIC ){ @@ -19407,10 +19428,18 @@ if( xtype==etEXP ){ e2 = 0; }else{ e2 = exp; } + if( e2+precision+width > etBUFSIZE - 15 ){ + bufpt = zExtra = sqlite3Malloc( e2+precision+width+15 ); + if( bufpt==0 ){ + pAccum->mallocFailed = 1; + return; + } + } + zOut = bufpt; nsd = 0; flag_dp = (precision>0 ?1:0) | flag_alternateform | flag_altform2; /* The sign in front of the number */ if( prefix ){ *(bufpt++) = prefix; @@ -19438,21 +19467,21 @@ *(bufpt++) = et_getdigit(&realvalue,&nsd); } /* Remove trailing zeros and the "." if no digits follow the "." */ if( flag_rtz && flag_dp ){ while( bufpt[-1]=='0' ) *(--bufpt) = 0; - assert( bufpt>buf ); + assert( bufpt>zOut ); if( bufpt[-1]=='.' ){ if( flag_altform2 ){ *(bufpt++) = '0'; }else{ *(--bufpt) = 0; } } } /* Add the "eNNN" suffix */ - if( flag_exp || xtype==etEXP ){ + if( xtype==etEXP ){ *(bufpt++) = aDigits[infop->charset]; if( exp<0 ){ *(bufpt++) = '-'; exp = -exp; }else{ *(bufpt++) = '+'; @@ -19467,12 +19496,12 @@ *bufpt = 0; /* The converted number is in buf[] and zero terminated. Output it. ** Note that the number is in the usual order, not reversed as with ** integer conversions. */ - length = (int)(bufpt-buf); - bufpt = buf; + length = (int)(bufpt-zOut); + bufpt = zOut; /* Special case: Add leading zeros if the flag_zeropad flag is ** set and we are not left justified */ if( flag_zeropad && !flag_leftjustify && length < width){ int i; @@ -19606,13 +19635,11 @@ nspace = width-length; if( nspace>0 ){ appendSpace(pAccum, nspace); } } - if( zExtra ){ - sqlite3_free(zExtra); - } + sqlite3_free(zExtra); }/* End for loop over the format string */ } /* End of function */ /* ** Append N bytes of text from z to the StrAccum object. @@ -19622,10 +19649,11 @@ if( p->tooBig | p->mallocFailed ){ testcase(p->tooBig); testcase(p->mallocFailed); return; } + assert( p->zText!=0 || p->nChar==0 ); if( N<0 ){ N = sqlite3Strlen30(z); } if( N==0 || NEVER(z==0) ){ return; @@ -19653,19 +19681,20 @@ zNew = sqlite3DbRealloc(p->db, zOld, p->nAlloc); }else{ zNew = sqlite3_realloc(zOld, p->nAlloc); } if( zNew ){ - if( zOld==0 ) memcpy(zNew, p->zText, p->nChar); + if( zOld==0 && p->nChar>0 ) memcpy(zNew, p->zText, p->nChar); p->zText = zNew; }else{ p->mallocFailed = 1; sqlite3StrAccumReset(p); return; } } } + assert( p->zText ); memcpy(&p->zText[p->nChar], z, N); p->nChar += N; } /* @@ -20511,11 +20540,11 @@ ** no longer required. ** ** If a malloc failure occurs, NULL is returned and the db.mallocFailed ** flag set. */ -#ifdef SQLITE_ENABLE_STAT2 +#ifdef SQLITE_ENABLE_STAT3 SQLITE_PRIVATE char *sqlite3Utf8to16(sqlite3 *db, u8 enc, char *z, int n, int *pnOut){ Mem m; memset(&m, 0, sizeof(m)); m.db = db; sqlite3VdbeMemSetStr(&m, z, n, SQLITE_UTF8, SQLITE_STATIC); @@ -25102,11 +25131,11 @@ return sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); } #endif -#ifdef SQLITE_DEBUG +#if defined(SQLITE_TEST) && defined(SQLITE_DEBUG) /* ** Helper function for printing out trace information from debugging ** binaries. This returns the string represetation of the supplied ** integer lock-type. */ @@ -25937,18 +25966,18 @@ ** locking a random byte from a range, concurrent SHARED locks may exist ** even if the locking primitive used is always a write-lock. */ int rc = SQLITE_OK; unixFile *pFile = (unixFile*)id; - unixInodeInfo *pInode = pFile->pInode; + unixInodeInfo *pInode; struct flock lock; int tErrno = 0; assert( pFile ); OSTRACE(("LOCK %d %s was %s(%s,%d) pid=%d (unix)\n", pFile->h, azFileLock(eFileLock), azFileLock(pFile->eFileLock), - azFileLock(pInode->eFileLock), pInode->nShared , getpid())); + azFileLock(pFile->pInode->eFileLock), pFile->pInode->nShared , getpid())); /* If there is already a lock of this type or more restrictive on the ** unixFile, do nothing. Don't use the end_lock: exit path, as ** unixEnterMutex() hasn't been called yet. */ @@ -26148,11 +26177,10 @@ static int posixUnlock(sqlite3_file *id, int eFileLock, int handleNFSUnlock){ unixFile *pFile = (unixFile*)id; unixInodeInfo *pInode; struct flock lock; int rc = SQLITE_OK; - int h; assert( pFile ); OSTRACE(("UNLOCK %d %d was %d(%d,%d) pid=%d (unix)\n", pFile->h, eFileLock, pFile->eFileLock, pFile->pInode->eFileLock, pFile->pInode->nShared, getpid())); @@ -26160,18 +26188,14 @@ assert( eFileLock<=SHARED_LOCK ); if( pFile->eFileLock<=eFileLock ){ return SQLITE_OK; } unixEnterMutex(); - h = pFile->h; pInode = pFile->pInode; assert( pInode->nShared!=0 ); if( pFile->eFileLock>SHARED_LOCK ){ assert( pInode->eFileLock==pFile->eFileLock ); - SimulateIOErrorBenign(1); - SimulateIOError( h=(-1) ) - SimulateIOErrorBenign(0); #ifndef NDEBUG /* When reducing a lock such that other processes can start ** reading the database file again, make sure that the ** transaction counter was updated if any part of the database @@ -26178,15 +26202,10 @@ ** file changed. If the transaction counter is not updated, ** other connections to the same file might not realize that ** the file has changed and hence might not know to flush their ** cache. The use of a stale cache can lead to database corruption. */ -#if 0 - assert( pFile->inNormalWrite==0 - || pFile->dbUpdate==0 - || pFile->transCntrChng==1 ); -#endif pFile->inNormalWrite = 0; #endif /* downgrading to a shared lock on NFS involves clearing the write lock ** before establishing the readlock - to avoid a race condition we downgrade @@ -26284,13 +26303,10 @@ pInode->nShared--; if( pInode->nShared==0 ){ lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; lock.l_start = lock.l_len = 0L; - SimulateIOErrorBenign(1); - SimulateIOError( h=(-1) ) - SimulateIOErrorBenign(0); if( unixFileLock(pFile, &lock)==0 ){ pInode->eFileLock = NO_LOCK; }else{ rc = SQLITE_IOERR_UNLOCK; pFile->lastErrno = errno; @@ -28428,20 +28444,19 @@ rc = SQLITE_NOMEM; goto shm_open_err; } if( pInode->bProcessLock==0 ){ - pShmNode->h = robust_open(zShmFilename, O_RDWR|O_CREAT, - (sStat.st_mode & 0777)); + const char *zRO; + int openFlags = O_RDWR | O_CREAT; + zRO = sqlite3_uri_parameter(pDbFd->zPath, "readonly_shm"); + if( zRO && sqlite3GetBoolean(zRO) ){ + openFlags = O_RDONLY; + pShmNode->isReadonly = 1; + } + pShmNode->h = robust_open(zShmFilename, openFlags, (sStat.st_mode&0777)); if( pShmNode->h<0 ){ - const char *zRO; - zRO = sqlite3_uri_parameter(pDbFd->zPath, "readonly_shm"); - if( zRO && sqlite3GetBoolean(zRO) ){ - pShmNode->h = robust_open(zShmFilename, O_RDONLY, - (sStat.st_mode & 0777)); - pShmNode->isReadonly = 1; - } if( pShmNode->h<0 ){ rc = unixLogError(SQLITE_CANTOPEN_BKPT, "open", zShmFilename); goto shm_open_err; } } @@ -29122,10 +29137,13 @@ assert( zFilename==0 || zFilename[0]=='/' || pVfs->pAppData==(void*)&autolockIoFinder ); #else assert( zFilename==0 || zFilename[0]=='/' ); #endif + + /* No locking occurs in temporary files */ + assert( zFilename!=0 || noLock ); OSTRACE(("OPEN %-3d %s\n", h, zFilename)); pNew->h = h; pNew->zPath = zFilename; if( memcmp(pVfs->zName,"unix-excl",10)==0 ){ @@ -29224,10 +29242,11 @@ /* Dotfile locking uses the file path so it needs to be included in ** the dotlockLockingContext */ char *zLockFile; int nFilename; + assert( zFilename!=0 ); nFilename = (int)strlen(zFilename) + 6; zLockFile = (char *)sqlite3_malloc(nFilename); if( zLockFile==0 ){ rc = SQLITE_NOMEM; }else{ @@ -29462,12 +29481,20 @@ ** ** where NN is a 4 digit decimal number. The NN naming schemes are ** used by the test_multiplex.c module. */ nDb = sqlite3Strlen30(zPath) - 1; - while( nDb>0 && zPath[nDb]!='-' ) nDb--; - if( nDb==0 ) return SQLITE_OK; +#ifdef SQLITE_ENABLE_8_3_NAMES + while( nDb>0 && zPath[nDb]!='-' && zPath[nDb]!='/' ) nDb--; + if( nDb==0 || zPath[nDb]=='/' ) return SQLITE_OK; +#else + while( zPath[nDb]!='-' ){ + assert( nDb>0 ); + assert( zPath[nDb]!='\n' ); + nDb--; + } +#endif memcpy(zDb, zPath, nDb); zDb[nDb] = '\0'; if( 0==osStat(zDb, &sStat) ){ *pMode = sStat.st_mode & 0777; @@ -29995,14 +30022,16 @@ ** the current time and date as a Julian Day number times 86_400_000. In ** other words, write into *piNow the number of milliseconds since the Julian ** epoch of noon in Greenwich on November 24, 4714 B.C according to the ** proleptic Gregorian calendar. ** -** On success, return 0. Return 1 if the time and date cannot be found. +** On success, return SQLITE_OK. Return SQLITE_ERROR if the time and date +** cannot be found. */ static int unixCurrentTimeInt64(sqlite3_vfs *NotUsed, sqlite3_int64 *piNow){ static const sqlite3_int64 unixEpoch = 24405875*(sqlite3_int64)8640000; + int rc = SQLITE_OK; #if defined(NO_GETTOD) time_t t; time(&t); *piNow = ((sqlite3_int64)t)*1000 + unixEpoch; #elif OS_VXWORKS @@ -30009,34 +30038,38 @@ struct timespec sNow; clock_gettime(CLOCK_REALTIME, &sNow); *piNow = unixEpoch + 1000*(sqlite3_int64)sNow.tv_sec + sNow.tv_nsec/1000000; #else struct timeval sNow; - gettimeofday(&sNow, 0); - *piNow = unixEpoch + 1000*(sqlite3_int64)sNow.tv_sec + sNow.tv_usec/1000; + if( gettimeofday(&sNow, 0)==0 ){ + *piNow = unixEpoch + 1000*(sqlite3_int64)sNow.tv_sec + sNow.tv_usec/1000; + }else{ + rc = SQLITE_ERROR; + } #endif #ifdef SQLITE_TEST if( sqlite3_current_time ){ *piNow = 1000*(sqlite3_int64)sqlite3_current_time + unixEpoch; } #endif UNUSED_PARAMETER(NotUsed); - return 0; + return rc; } /* ** Find the current time (in Universal Coordinated Time). Write the ** current time and date as a Julian Day number into *prNow and ** return 0. Return 1 if the time and date cannot be found. */ static int unixCurrentTime(sqlite3_vfs *NotUsed, double *prNow){ - sqlite3_int64 i; + sqlite3_int64 i = 0; + int rc; UNUSED_PARAMETER(NotUsed); - unixCurrentTimeInt64(0, &i); + rc = unixCurrentTimeInt64(0, &i); *prNow = i/86400000.0; - return 0; + return rc; } /* ** We added the xGetLastError() method with the intention of providing ** better low-level error messages when operating-system problems come up @@ -34169,11 +34202,11 @@ if( h==INVALID_HANDLE_VALUE ){ pFile->lastErrno = GetLastError(); winLogError(SQLITE_CANTOPEN, "winOpen", zUtf8Name); free(zConverted); - if( isReadWrite ){ + if( isReadWrite && !isExclusive ){ return winOpen(pVfs, zName, id, ((flags|SQLITE_OPEN_READONLY)&~(SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE)), pOutFlags); }else{ return SQLITE_CANTOPEN_BKPT; } @@ -34535,11 +34568,11 @@ } static void winDlError(sqlite3_vfs *pVfs, int nBuf, char *zBufOut){ UNUSED_PARAMETER(pVfs); getLastErrorMsg(nBuf, zBufOut); } -void (*winDlSym(sqlite3_vfs *pVfs, void *pHandle, const char *zSymbol))(void){ +static void (*winDlSym(sqlite3_vfs *pVfs, void *pHandle, const char *zSymbol))(void){ UNUSED_PARAMETER(pVfs); #if SQLITE_OS_WINCE /* The GetProcAddressA() routine is only available on wince. */ return (void(*)(void))GetProcAddressA((HANDLE)pHandle, zSymbol); #else @@ -34546,11 +34579,11 @@ /* All other windows platforms expect GetProcAddress() to take ** an Ansi string regardless of the _UNICODE setting */ return (void(*)(void))GetProcAddress((HANDLE)pHandle, zSymbol); #endif } -void winDlClose(sqlite3_vfs *pVfs, void *pHandle){ +static void winDlClose(sqlite3_vfs *pVfs, void *pHandle){ UNUSED_PARAMETER(pVfs); FreeLibrary((HANDLE)pHandle); } #else /* if SQLITE_OMIT_LOAD_EXTENSION is defined: */ #define winDlOpen 0 @@ -34620,11 +34653,12 @@ ** the current time and date as a Julian Day number times 86_400_000. In ** other words, write into *piNow the number of milliseconds since the Julian ** epoch of noon in Greenwich on November 24, 4714 B.C according to the ** proleptic Gregorian calendar. ** -** On success, return 0. Return 1 if the time and date cannot be found. +** On success, return SQLITE_OK. Return SQLITE_ERROR if the time and date +** cannot be found. */ static int winCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *piNow){ /* FILETIME structure is a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (= JD 2305813.5). */ @@ -34640,11 +34674,11 @@ #if SQLITE_OS_WINCE SYSTEMTIME time; GetSystemTime(&time); /* if SystemTimeToFileTime() fails, it returns zero. */ if (!SystemTimeToFileTime(&time,&ft)){ - return 1; + return SQLITE_ERROR; } #else GetSystemTimeAsFileTime( &ft ); #endif @@ -34656,19 +34690,19 @@ if( sqlite3_current_time ){ *piNow = 1000*(sqlite3_int64)sqlite3_current_time + unixEpoch; } #endif UNUSED_PARAMETER(pVfs); - return 0; + return SQLITE_OK; } /* ** Find the current time (in Universal Coordinated Time). Write the ** current time and date as a Julian Day number into *prNow and ** return 0. Return 1 if the time and date cannot be found. */ -int winCurrentTime(sqlite3_vfs *pVfs, double *prNow){ +static int winCurrentTime(sqlite3_vfs *pVfs, double *prNow){ int rc; sqlite3_int64 i; rc = winCurrentTimeInt64(pVfs, &i); if( !rc ){ *prNow = i/86400000.0; @@ -35789,12 +35823,10 @@ typedef struct PCache1 PCache1; typedef struct PgHdr1 PgHdr1; typedef struct PgFreeslot PgFreeslot; typedef struct PGroup PGroup; -typedef struct PGroupBlock PGroupBlock; -typedef struct PGroupBlockList PGroupBlockList; /* Each page cache (or PCache) belongs to a PGroup. A PGroup is a set ** of one or more PCaches that are able to recycle each others unpinned ** pages when they are under memory pressure. A PGroup is an instance of ** the following object. @@ -35821,69 +35853,11 @@ int nMaxPage; /* Sum of nMax for purgeable caches */ int nMinPage; /* Sum of nMin for purgeable caches */ int mxPinned; /* nMaxpage + 10 - nMinPage */ int nCurrentPage; /* Number of purgeable pages allocated */ PgHdr1 *pLruHead, *pLruTail; /* LRU list of unpinned pages */ -#ifdef SQLITE_PAGECACHE_BLOCKALLOC - int isBusy; /* Do not run ReleaseMemory() if true */ - PGroupBlockList *pBlockList; /* List of block-lists for this group */ -#endif -}; - -/* -** If SQLITE_PAGECACHE_BLOCKALLOC is defined when the library is built, -** each PGroup structure has a linked list of the the following starting -** at PGroup.pBlockList. There is one entry for each distinct page-size -** currently used by members of the PGroup (i.e. 1024 bytes, 4096 bytes -** etc.). Variable PGroupBlockList.nByte is set to the actual allocation -** size requested by each pcache, which is the database page-size plus -** the various header structures used by the pcache, pager and btree layers. -** Usually around (pgsz+200) bytes. -** -** This size (pgsz+200) bytes is not allocated efficiently by some -** implementations of malloc. In particular, some implementations are only -** able to allocate blocks of memory chunks of 2^N bytes, where N is some -** integer value. Since the page-size is a power of 2, this means we -** end up wasting (pgsz-200) bytes in each allocation. -** -** If SQLITE_PAGECACHE_BLOCKALLOC is defined, the (pgsz+200) byte blocks -** are not allocated directly. Instead, blocks of roughly M*(pgsz+200) bytes -** are requested from malloc allocator. After a block is returned, -** sqlite3MallocSize() is used to determine how many (pgsz+200) byte -** allocations can fit in the space returned by malloc(). This value may -** be more than M. -** -** The blocks are stored in a doubly-linked list. Variable PGroupBlock.nEntry -** contains the number of allocations that will fit in the aData[] space. -** nEntry is limited to the number of bits in bitmask mUsed. If a slot -** within aData is in use, the corresponding bit in mUsed is set. Thus -** when (mUsed+1==(1 << nEntry)) the block is completely full. -** -** Each time a slot within a block is freed, the block is moved to the start -** of the linked-list. And if a block becomes completely full, then it is -** moved to the end of the list. As a result, when searching for a free -** slot, only the first block in the list need be examined. If it is full, -** then it is guaranteed that all blocks are full. -*/ -struct PGroupBlockList { - int nByte; /* Size of each allocation in bytes */ - PGroupBlock *pFirst; /* First PGroupBlock in list */ - PGroupBlock *pLast; /* Last PGroupBlock in list */ - PGroupBlockList *pNext; /* Next block-list attached to group */ -}; - -struct PGroupBlock { - Bitmask mUsed; /* Mask of used slots */ - int nEntry; /* Maximum number of allocations in aData[] */ - u8 *aData; /* Pointer to data block */ - PGroupBlock *pNext; /* Next PGroupBlock in list */ - PGroupBlock *pPrev; /* Previous PGroupBlock in list */ - PGroupBlockList *pList; /* Owner list */ -}; - -/* Minimum value for PGroupBlock.nEntry */ -#define PAGECACHE_BLOCKALLOC_MINENTRY 15 +}; /* Each page cache is an instance of the following object. Every ** open database file (including each in-memory database and each ** temporary or transient database) has a single page cache which ** is an instance of this object. @@ -35983,21 +35957,10 @@ ** ** assert( PGHDR1_TO_PAGE(PAGE_TO_PGHDR1(pCache, X))==X ); */ #define PGHDR1_TO_PAGE(p) (void*)(((char*)p) - p->pCache->szPage) #define PAGE_TO_PGHDR1(c, p) (PgHdr1*)(((char*)p) + c->szPage) - -/* -** Blocks used by the SQLITE_PAGECACHE_BLOCKALLOC blocks to store/retrieve -** a PGroupBlock pointer based on a pointer to a page buffer. -*/ -#define PAGE_SET_BLOCKPTR(pCache, pPg, pBlock) \ - ( *(PGroupBlock **)&(((u8*)pPg)[sizeof(PgHdr1) + pCache->szPage]) = pBlock ) - -#define PAGE_GET_BLOCKPTR(pCache, pPg) \ - ( *(PGroupBlock **)&(((u8*)pPg)[sizeof(PgHdr1) + pCache->szPage]) ) - /* ** Macros to enter and leave the PCache LRU mutex. */ #define pcache1EnterMutex(X) sqlite3_mutex_enter((X)->mutex) @@ -36120,159 +36083,32 @@ return iSize; } } #endif /* SQLITE_ENABLE_MEMORY_MANAGEMENT */ -#ifdef SQLITE_PAGECACHE_BLOCKALLOC -/* -** The block pBlock belongs to list pList but is not currently linked in. -** Insert it into the start of the list. -*/ -static void addBlockToList(PGroupBlockList *pList, PGroupBlock *pBlock){ - pBlock->pPrev = 0; - pBlock->pNext = pList->pFirst; - pList->pFirst = pBlock; - if( pBlock->pNext ){ - pBlock->pNext->pPrev = pBlock; - }else{ - assert( pList->pLast==0 ); - pList->pLast = pBlock; - } -} - -/* -** If there are no blocks in the list headed by pList, remove pList -** from the pGroup->pBlockList list and free it with sqlite3_free(). -*/ -static void freeListIfEmpty(PGroup *pGroup, PGroupBlockList *pList){ - assert( sqlite3_mutex_held(pGroup->mutex) ); - if( pList->pFirst==0 ){ - PGroupBlockList **pp; - for(pp=&pGroup->pBlockList; *pp!=pList; pp=&(*pp)->pNext); - *pp = (*pp)->pNext; - sqlite3_free(pList); - } -} -#endif /* SQLITE_PAGECACHE_BLOCKALLOC */ - /* ** Allocate a new page object initially associated with cache pCache. */ static PgHdr1 *pcache1AllocPage(PCache1 *pCache){ int nByte = sizeof(PgHdr1) + pCache->szPage; - void *pPg = 0; - PgHdr1 *p; - -#ifdef SQLITE_PAGECACHE_BLOCKALLOC - PGroup *pGroup = pCache->pGroup; - PGroupBlockList *pList; - PGroupBlock *pBlock; - int i; - - nByte += sizeof(PGroupBlockList *); - nByte = ROUND8(nByte); - - for(pList=pGroup->pBlockList; pList; pList=pList->pNext){ - if( pList->nByte==nByte ) break; - } - if( pList==0 ){ - PGroupBlockList *pNew; - assert( pGroup->isBusy==0 ); - assert( sqlite3_mutex_held(pGroup->mutex) ); - pGroup->isBusy = 1; /* Disable sqlite3PcacheReleaseMemory() */ - pNew = (PGroupBlockList *)sqlite3MallocZero(sizeof(PGroupBlockList)); - pGroup->isBusy = 0; /* Reenable sqlite3PcacheReleaseMemory() */ - if( pNew==0 ){ - /* malloc() failure. Return early. */ - return 0; - } -#ifdef SQLITE_DEBUG - for(pList=pGroup->pBlockList; pList; pList=pList->pNext){ - assert( pList->nByte!=nByte ); - } -#endif - pNew->nByte = nByte; - pNew->pNext = pGroup->pBlockList; - pGroup->pBlockList = pNew; - pList = pNew; - } - - pBlock = pList->pFirst; - if( pBlock==0 || pBlock->mUsed==(((Bitmask)1<<pBlock->nEntry)-1) ){ - int sz; - - /* Allocate a new block. Try to allocate enough space for the PGroupBlock - ** structure and MINENTRY allocations of nByte bytes each. If the - ** allocator returns more memory than requested, then more than MINENTRY - ** allocations may fit in it. */ - assert( sqlite3_mutex_held(pGroup->mutex) ); - pcache1LeaveMutex(pCache->pGroup); - sz = sizeof(PGroupBlock) + PAGECACHE_BLOCKALLOC_MINENTRY * nByte; - pBlock = (PGroupBlock *)sqlite3Malloc(sz); - pcache1EnterMutex(pCache->pGroup); - - if( !pBlock ){ - freeListIfEmpty(pGroup, pList); - return 0; - } - pBlock->nEntry = (sqlite3MallocSize(pBlock) - sizeof(PGroupBlock)) / nByte; - if( pBlock->nEntry>=BMS ){ - pBlock->nEntry = BMS-1; - } - pBlock->pList = pList; - pBlock->mUsed = 0; - pBlock->aData = (u8 *)&pBlock[1]; - addBlockToList(pList, pBlock); - - sz = sqlite3MallocSize(pBlock); - sqlite3_mutex_enter(pcache1.mutex); - sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_OVERFLOW, sz); - sqlite3_mutex_leave(pcache1.mutex); - } - - for(i=0; pPg==0 && ALWAYS(i<pBlock->nEntry); i++){ - if( 0==(pBlock->mUsed & ((Bitmask)1<<i)) ){ - pBlock->mUsed |= ((Bitmask)1<<i); - pPg = (void *)&pBlock->aData[pList->nByte * i]; - } - } - assert( pPg ); - PAGE_SET_BLOCKPTR(pCache, pPg, pBlock); - - /* If the block is now full, shift it to the end of the list */ - if( pBlock->mUsed==(((Bitmask)1<<pBlock->nEntry)-1) && pList->pLast!=pBlock ){ - assert( pList->pFirst==pBlock ); - assert( pBlock->pPrev==0 ); - assert( pList->pLast->pNext==0 ); - pList->pFirst = pBlock->pNext; - pList->pFirst->pPrev = 0; - pBlock->pPrev = pList->pLast; - pBlock->pNext = 0; - pList->pLast->pNext = pBlock; - pList->pLast = pBlock; - } - p = PAGE_TO_PGHDR1(pCache, pPg); - if( pCache->bPurgeable ){ - pCache->pGroup->nCurrentPage++; - } -#else + PgHdr1 *p = 0; + void *pPg; + /* The group mutex must be released before pcache1Alloc() is called. This ** is because it may call sqlite3_release_memory(), which assumes that ** this mutex is not held. */ assert( sqlite3_mutex_held(pCache->pGroup->mutex) ); pcache1LeaveMutex(pCache->pGroup); pPg = pcache1Alloc(nByte); pcache1EnterMutex(pCache->pGroup); + if( pPg ){ p = PAGE_TO_PGHDR1(pCache, pPg); if( pCache->bPurgeable ){ pCache->pGroup->nCurrentPage++; } - }else{ - p = 0; } -#endif return p; } /* ** Free a page object allocated by pcache1AllocPage(). @@ -36282,53 +36118,12 @@ ** with a NULL pointer, so we mark the NULL test with ALWAYS(). */ static void pcache1FreePage(PgHdr1 *p){ if( ALWAYS(p) ){ PCache1 *pCache = p->pCache; - void *pPg = PGHDR1_TO_PAGE(p); - -#ifdef SQLITE_PAGECACHE_BLOCKALLOC - PGroupBlock *pBlock = PAGE_GET_BLOCKPTR(pCache, pPg); - PGroupBlockList *pList = pBlock->pList; - int i = ((u8 *)pPg - pBlock->aData) / pList->nByte; - - assert( pPg==(void *)&pBlock->aData[i*pList->nByte] ); - assert( pBlock->mUsed & ((Bitmask)1<<i) ); - pBlock->mUsed &= ~((Bitmask)1<<i); - - /* Remove the block from the list. If it is completely empty, free it. - ** Or if it is not completely empty, re-insert it at the start of the - ** list. */ - if( pList->pFirst==pBlock ){ - pList->pFirst = pBlock->pNext; - if( pList->pFirst ) pList->pFirst->pPrev = 0; - }else{ - pBlock->pPrev->pNext = pBlock->pNext; - } - if( pList->pLast==pBlock ){ - pList->pLast = pBlock->pPrev; - if( pList->pLast ) pList->pLast->pNext = 0; - }else{ - pBlock->pNext->pPrev = pBlock->pPrev; - } - - if( pBlock->mUsed==0 ){ - PGroup *pGroup = p->pCache->pGroup; - - int sz = sqlite3MallocSize(pBlock); - sqlite3_mutex_enter(pcache1.mutex); - sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_OVERFLOW, -sz); - sqlite3_mutex_leave(pcache1.mutex); - freeListIfEmpty(pGroup, pList); - sqlite3_free(pBlock); - }else{ - addBlockToList(pList, pBlock); - } -#else assert( sqlite3_mutex_held(p->pCache->pGroup->mutex) ); - pcache1Free(pPg); -#endif + pcache1Free(PGHDR1_TO_PAGE(p)); if( pCache->bPurgeable ){ pCache->pGroup->nCurrentPage--; } } } @@ -36935,13 +36730,10 @@ ** been released, the function returns. The return value is the total number ** of bytes of memory released. */ SQLITE_PRIVATE int sqlite3PcacheReleaseMemory(int nReq){ int nFree = 0; -#ifdef SQLITE_PAGECACHE_BLOCKALLOC - if( pcache1.grp.isBusy ) return 0; -#endif assert( sqlite3_mutex_notheld(pcache1.grp.mutex) ); assert( sqlite3_mutex_notheld(pcache1.mutex) ); if( pcache1.pStart==0 ){ PgHdr1 *p; pcache1EnterMutex(&pcache1.grp); @@ -38200,12 +37992,12 @@ i64 journalSizeLimit; /* Size limit for persistent journal files */ char *zFilename; /* Name of the database file */ char *zJournal; /* Name of the journal file */ int (*xBusyHandler)(void*); /* Function to call when busy */ void *pBusyHandlerArg; /* Context argument for xBusyHandler */ + int nHit, nMiss; /* Total cache hits and misses */ #ifdef SQLITE_TEST - int nHit, nMiss; /* Cache hits and missing */ int nRead, nWrite; /* Database pages read/written */ #endif void (*xReiniter)(DbPage*); /* Call this routine when reloading pages */ #ifdef SQLITE_HAS_CODEC void *(*xCodec)(void*,void*,Pgno,int); /* Routine for en/decoding data */ @@ -40233,11 +40025,10 @@ needPagerReset = 0; } rc = pager_playback_one_page(pPager,&pPager->journalOff,0,1,0); if( rc!=SQLITE_OK ){ if( rc==SQLITE_DONE ){ - rc = SQLITE_OK; pPager->journalOff = szJ; break; }else if( rc==SQLITE_IOERR_SHORT_READ ){ /* If the journal has been truncated, simply stop reading and ** processing the journal. This might happen if the journal was @@ -40495,10 +40286,11 @@ #if defined(SQLITE_DEBUG) || defined(SQLITE_CHECK_PAGES) PgHdr *p; /* For looping over pages */ #endif assert( pPager->pWal ); + assert( pList ); #ifdef SQLITE_DEBUG /* Verify that the page list is in accending order */ for(p=pList; p && p->pDirty; p=p->pDirty){ assert( p->pgno < p->pDirty->pgno ); } @@ -41699,11 +41491,11 @@ ** The doNotSpill flag inhibits all cache spilling regardless of whether ** or not a sync is required. This is set during a rollback. ** ** Spilling is also prohibited when in an error state since that could ** lead to database corruption. In the current implementaton it - ** is impossible for sqlite3PCacheFetch() to be called with createFlag==1 + ** is impossible for sqlite3PcacheFetch() to be called with createFlag==1 ** while in the error state, hence it is impossible for this routine to ** be called in the error state. Nevertheless, we include a NEVER() ** test for the error state as a safeguard against future changes. */ if( NEVER(pPager->errCode) ) return SQLITE_OK; @@ -42535,18 +42327,17 @@ if( (*ppPage)->pPager && !noContent ){ /* In this case the pcache already contains an initialized copy of ** the page. Return without further ado. */ assert( pgno<=PAGER_MAX_PGNO && pgno!=PAGER_MJ_PGNO(pPager) ); - PAGER_INCR(pPager->nHit); + pPager->nHit++; return SQLITE_OK; }else{ /* The pager cache has created a new page. Its content needs to ** be initialized. */ - PAGER_INCR(pPager->nMiss); pPg = *ppPage; pPg->pPager = pPager; /* The maximum page number is 2^31. Return SQLITE_CORRUPT if a page ** number greater than this, or the unused locking-page, is requested. */ @@ -42578,10 +42369,11 @@ } memset(pPg->pData, 0, pPager->pageSize); IOTRACE(("ZERO %p %d\n", pPager, pgno)); }else{ assert( pPg->pPager==pPager ); + pPager->nMiss++; rc = readDbPage(pPg); if( rc!=SQLITE_OK ){ goto pager_acquire_err; } } @@ -43611,10 +43403,35 @@ a[9] = pPager->nRead; a[10] = pPager->nWrite; return a; } #endif + +/* +** Parameter eStat must be either SQLITE_DBSTATUS_CACHE_HIT or +** SQLITE_DBSTATUS_CACHE_MISS. Before returning, *pnVal is incremented by the +** current cache hit or miss count, according to the value of eStat. If the +** reset parameter is non-zero, the cache hit or miss count is zeroed before +** returning. +*/ +SQLITE_PRIVATE void sqlite3PagerCacheStat(Pager *pPager, int eStat, int reset, int *pnVal){ + int *piStat; + + assert( eStat==SQLITE_DBSTATUS_CACHE_HIT + || eStat==SQLITE_DBSTATUS_CACHE_MISS + ); + if( eStat==SQLITE_DBSTATUS_CACHE_HIT ){ + piStat = &pPager->nHit; + }else{ + piStat = &pPager->nMiss; + } + + *pnVal += *piStat; + if( reset ){ + *piStat = 0; + } +} /* ** Return true if this is an in-memory pager. */ SQLITE_PRIVATE int sqlite3PagerIsMemdb(Pager *pPager){ @@ -46706,11 +46523,11 @@ */ if( iRead ){ int sz; i64 iOffset; sz = pWal->hdr.szPage; - sz = (pWal->hdr.szPage&0xfe00) + ((pWal->hdr.szPage&0x0001)<<16); + sz = (sz&0xfe00) + ((sz&0x0001)<<16); testcase( sz<=32768 ); testcase( sz>=65536 ); iOffset = walFrameOffset(iRead, sz) + WAL_FRAME_HDRSIZE; *pInWal = 1; /* testcase( IS_BIG_INT(iOffset) ); // requires a 4GiB WAL */ @@ -50019,21 +49836,23 @@ */ if( isMemdb==0 && isTempDb==0 ){ if( vfsFlags & SQLITE_OPEN_SHAREDCACHE ){ int nFullPathname = pVfs->mxPathname+1; char *zFullPathname = sqlite3Malloc(nFullPathname); - sqlite3_mutex *mutexShared; + MUTEX_LOGIC( sqlite3_mutex *mutexShared; ) p->sharable = 1; if( !zFullPathname ){ sqlite3_free(p); return SQLITE_NOMEM; } sqlite3OsFullPathname(pVfs, zFilename, nFullPathname, zFullPathname); +#if SQLITE_THREADSAFE mutexOpen = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_OPEN); sqlite3_mutex_enter(mutexOpen); mutexShared = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); sqlite3_mutex_enter(mutexShared); +#endif for(pBt=GLOBAL(BtShared*,sqlite3SharedCacheList); pBt; pBt=pBt->pNext){ assert( pBt->nRef>0 ); if( 0==strcmp(zFullPathname, sqlite3PagerFilename(pBt->pPager)) && sqlite3PagerVfs(pBt->pPager)==pVfs ){ int iDb; @@ -50135,13 +49954,13 @@ #if !defined(SQLITE_OMIT_SHARED_CACHE) && !defined(SQLITE_OMIT_DISKIO) /* Add the new BtShared object to the linked list sharable BtShareds. */ if( p->sharable ){ - sqlite3_mutex *mutexShared; + MUTEX_LOGIC( sqlite3_mutex *mutexShared; ) pBt->nRef = 1; - mutexShared = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); + MUTEX_LOGIC( mutexShared = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);) if( SQLITE_THREADSAFE && sqlite3GlobalConfig.bCoreMutex ){ pBt->mutex = sqlite3MutexAlloc(SQLITE_MUTEX_FAST); if( pBt->mutex==0 ){ rc = SQLITE_NOMEM; db->mallocFailed = 0; @@ -50219,16 +50038,16 @@ ** true if the BtShared.nRef counter reaches zero and return ** false if it is still positive. */ static int removeFromSharingList(BtShared *pBt){ #ifndef SQLITE_OMIT_SHARED_CACHE - sqlite3_mutex *pMaster; + MUTEX_LOGIC( sqlite3_mutex *pMaster; ) BtShared *pList; int removed = 0; assert( sqlite3_mutex_notheld(pBt->mutex) ); - pMaster = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); + MUTEX_LOGIC( pMaster = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); ) sqlite3_mutex_enter(pMaster); pBt->nRef--; if( pBt->nRef<=0 ){ if( GLOBAL(BtShared*,sqlite3SharedCacheList)==pBt ){ GLOBAL(BtShared*,sqlite3SharedCacheList) = pBt->pNext; @@ -52191,25 +52010,59 @@ offset -= ovflSize; }else{ /* Need to read this page properly. It contains some of the ** range of data that is being read (eOp==0) or written (eOp!=0). */ - DbPage *pDbPage; +#ifdef SQLITE_DIRECT_OVERFLOW_READ + sqlite3_file *fd; +#endif int a = amt; - rc = sqlite3PagerGet(pBt->pPager, nextPage, &pDbPage); - if( rc==SQLITE_OK ){ - aPayload = sqlite3PagerGetData(pDbPage); - nextPage = get4byte(aPayload); - if( a + offset > ovflSize ){ - a = ovflSize - offset; - } - rc = copyPayload(&aPayload[offset+4], pBuf, a, eOp, pDbPage); - sqlite3PagerUnref(pDbPage); - offset = 0; - amt -= a; - pBuf += a; - } + if( a + offset > ovflSize ){ + a = ovflSize - offset; + } + +#ifdef SQLITE_DIRECT_OVERFLOW_READ + /* If all the following are true: + ** + ** 1) this is a read operation, and + ** 2) data is required from the start of this overflow page, and + ** 3) the database is file-backed, and + ** 4) there is no open write-transaction, and + ** 5) the database is not a WAL database, + ** + ** then data can be read directly from the database file into the + ** output buffer, bypassing the page-cache altogether. This speeds + ** up loading large records that span many overflow pages. + */ + if( eOp==0 /* (1) */ + && offset==0 /* (2) */ + && pBt->inTransaction==TRANS_READ /* (4) */ + && (fd = sqlite3PagerFile(pBt->pPager))->pMethods /* (3) */ + && pBt->pPage1->aData[19]==0x01 /* (5) */ + ){ + u8 aSave[4]; + u8 *aWrite = &pBuf[-4]; + memcpy(aSave, aWrite, 4); + rc = sqlite3OsRead(fd, aWrite, a+4, pBt->pageSize * (nextPage-1)); + nextPage = get4byte(aWrite); + memcpy(aWrite, aSave, 4); + }else +#endif + + { + DbPage *pDbPage; + rc = sqlite3PagerGet(pBt->pPager, nextPage, &pDbPage); + if( rc==SQLITE_OK ){ + aPayload = sqlite3PagerGetData(pDbPage); + nextPage = get4byte(aPayload); + rc = copyPayload(&aPayload[offset+4], pBuf, a, eOp, pDbPage); + sqlite3PagerUnref(pDbPage); + offset = 0; + } + } + amt -= a; + pBuf += a; } } } if( rc==SQLITE_OK && amt>0 ){ @@ -52804,11 +52657,10 @@ } } if( c==0 ){ if( pPage->intKey && !pPage->leaf ){ lwr = idx; - upr = lwr - 1; break; }else{ *pRes = 0; rc = SQLITE_OK; goto moveto_finish; @@ -52822,11 +52674,11 @@ if( lwr>upr ){ break; } pCur->aiIdx[pCur->iPage] = (u16)(idx = (lwr+upr)/2); } - assert( lwr==upr+1 ); + assert( lwr==upr+1 || (pPage->intKey && !pPage->leaf) ); assert( pPage->isInit ); if( pPage->leaf ){ chldPg = 0; }else if( lwr>=pPage->nCell ){ chldPg = get4byte(&pPage->aData[pPage->hdrOffset+8]); @@ -53087,10 +52939,12 @@ } if( rc ){ pTrunk = 0; goto end_allocate_page; } + assert( pTrunk!=0 ); + assert( pTrunk->aData!=0 ); k = get4byte(&pTrunk->aData[4]); /* # of leaves on this trunk page */ if( k==0 && !searchList ){ /* The trunk has no leaves and the list is not being searched. ** So extract the trunk page itself and use it as the newly @@ -54214,17 +54068,19 @@ ** This is safe because dropping a cell only overwrites the first ** four bytes of it, and this function does not need the first ** four bytes of the divider cell. So the pointer is safe to use ** later on. ** - ** Unless SQLite is compiled in secure-delete mode. In this case, + ** But not if we are in secure-delete mode. In secure-delete mode, ** the dropCell() routine will overwrite the entire cell with zeroes. ** In this case, temporarily copy the cell into the aOvflSpace[] ** buffer. It will be copied out again as soon as the aSpace[] buffer ** is allocated. */ if( pBt->secureDelete ){ - int iOff = SQLITE_PTR_TO_INT(apDiv[i]) - SQLITE_PTR_TO_INT(pParent->aData); + int iOff; + + iOff = SQLITE_PTR_TO_INT(apDiv[i]) - SQLITE_PTR_TO_INT(pParent->aData); if( (iOff+szNew[i])>(int)pBt->usableSize ){ rc = SQLITE_CORRUPT_BKPT; memset(apOld, 0, (i+1)*sizeof(MemPage*)); goto balance_cleanup; }else{ @@ -54640,10 +54496,11 @@ int isDivider = 0; while( i==iNextOld ){ /* Cell i is the cell immediately following the last cell on old ** sibling page j. If the siblings are not leaf pages of an ** intkey b-tree, then cell i was a divider cell. */ + assert( j+1 < ArraySize(apCopy) ); pOld = apCopy[++j]; iNextOld = i + !leafData + pOld->nCell + pOld->nOverflow; if( pOld->nOverflow ){ nOverflow = pOld->nOverflow; iOverflow = i + !leafData + pOld->aOvfl[0].idx; @@ -56982,18 +56839,18 @@ /* ** Release all resources associated with an sqlite3_backup* handle. */ SQLITE_API int sqlite3_backup_finish(sqlite3_backup *p){ sqlite3_backup **pp; /* Ptr to head of pagers backup list */ - sqlite3_mutex *mutex; /* Mutex to protect source database */ + MUTEX_LOGIC( sqlite3_mutex *mutex; ) /* Mutex to protect source database */ int rc; /* Value to return */ /* Enter the mutexes */ if( p==0 ) return SQLITE_OK; sqlite3_mutex_enter(p->pSrcDb->mutex); sqlite3BtreeEnter(p->pSrc); - mutex = p->pSrcDb->mutex; + MUTEX_LOGIC( mutex = p->pSrcDb->mutex; ) if( p->pDestDb ){ sqlite3_mutex_enter(p->pDestDb->mutex); } /* Detach this backup from the source pager. */ @@ -57108,13 +56965,21 @@ ** goes wrong, the transaction on pTo is rolled back. If successful, the ** transaction is committed before returning. */ SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree *pTo, Btree *pFrom){ int rc; + sqlite3_file *pFd; /* File descriptor for database pTo */ sqlite3_backup b; sqlite3BtreeEnter(pTo); sqlite3BtreeEnter(pFrom); + + assert( sqlite3BtreeIsInTrans(pTo) ); + pFd = sqlite3PagerFile(sqlite3BtreePager(pTo)); + if( pFd->pMethods ){ + i64 nByte = sqlite3BtreeGetPageSize(pFrom)*(i64)sqlite3BtreeLastPage(pFrom); + sqlite3OsFileControl(pFd, SQLITE_FCNTL_OVERWRITE, &nByte); + } /* Set up an sqlite3_backup object. sqlite3_backup.pDestDb must be set ** to 0. This is used by the implementations of sqlite3_backup_step() ** and sqlite3_backup_finish() to detect that they are being called ** from this function, not directly by the user. @@ -57137,10 +57002,11 @@ rc = sqlite3_backup_finish(&b); if( rc==SQLITE_OK ){ pTo->pBt->pageSizeFixed = 0; } + assert( sqlite3BtreeIsInTrans(pTo)==0 ); sqlite3BtreeLeave(pFrom); sqlite3BtreeLeave(pTo); return rc; } #endif /* SQLITE_OMIT_VACUUM */ @@ -58171,15 +58037,15 @@ *ppVal = 0; return SQLITE_OK; } op = pExpr->op; - /* op can only be TK_REGISTER if we have compiled with SQLITE_ENABLE_STAT2. + /* op can only be TK_REGISTER if we have compiled with SQLITE_ENABLE_STAT3. ** The ifdef here is to enable us to achieve 100% branch test coverage even - ** when SQLITE_ENABLE_STAT2 is omitted. + ** when SQLITE_ENABLE_STAT3 is omitted. */ -#ifdef SQLITE_ENABLE_STAT2 +#ifdef SQLITE_ENABLE_STAT3 if( op==TK_REGISTER ) op = pExpr->op2; #else if( NEVER(op==TK_REGISTER) ) op = pExpr->op2; #endif @@ -58874,12 +58740,12 @@ /* ** Change the P2 operand of instruction addr so that it points to ** the address of the next instruction to be coded. */ SQLITE_PRIVATE void sqlite3VdbeJumpHere(Vdbe *p, int addr){ - assert( addr>=0 ); - sqlite3VdbeChangeP2(p, addr, p->nOp); + assert( addr>=0 || p->db->mallocFailed ); + if( addr>=0 ) sqlite3VdbeChangeP2(p, addr, p->nOp); } /* ** If the input FuncDef structure is ephemeral, then free it. If @@ -59080,34 +58946,33 @@ ** Change the comment on the the most recently coded instruction. Or ** insert a No-op and add the comment to that new instruction. This ** makes the code easier to read during debugging. None of this happens ** in a production build. */ -SQLITE_PRIVATE void sqlite3VdbeComment(Vdbe *p, const char *zFormat, ...){ - va_list ap; - if( !p ) return; +static void vdbeVComment(Vdbe *p, const char *zFormat, va_list ap){ assert( p->nOp>0 || p->aOp==0 ); assert( p->aOp==0 || p->aOp[p->nOp-1].zComment==0 || p->db->mallocFailed ); if( p->nOp ){ - char **pz = &p->aOp[p->nOp-1].zComment; + assert( p->aOp ); + sqlite3DbFree(p->db, p->aOp[p->nOp-1].zComment); + p->aOp[p->nOp-1].zComment = sqlite3VMPrintf(p->db, zFormat, ap); + } +} +SQLITE_PRIVATE void sqlite3VdbeComment(Vdbe *p, const char *zFormat, ...){ + va_list ap; + if( p ){ va_start(ap, zFormat); - sqlite3DbFree(p->db, *pz); - *pz = sqlite3VMPrintf(p->db, zFormat, ap); + vdbeVComment(p, zFormat, ap); va_end(ap); } } SQLITE_PRIVATE void sqlite3VdbeNoopComment(Vdbe *p, const char *zFormat, ...){ va_list ap; - if( !p ) return; - sqlite3VdbeAddOp0(p, OP_Noop); - assert( p->nOp>0 || p->aOp==0 ); - assert( p->aOp==0 || p->aOp[p->nOp-1].zComment==0 || p->db->mallocFailed ); - if( p->nOp ){ - char **pz = &p->aOp[p->nOp-1].zComment; + if( p ){ + sqlite3VdbeAddOp0(p, OP_Noop); va_start(ap, zFormat); - sqlite3DbFree(p->db, *pz); - *pz = sqlite3VMPrintf(p->db, zFormat, ap); + vdbeVComment(p, zFormat, ap); va_end(ap); } } #endif /* NDEBUG */ @@ -59441,11 +59306,11 @@ SubProgram **apSub = 0; /* Array of sub-vdbes */ Mem *pSub = 0; /* Memory cell hold array of subprogs */ sqlite3 *db = p->db; /* The database connection */ int i; /* Loop counter */ int rc = SQLITE_OK; /* Return code */ - Mem *pMem = p->pResultSet = &p->aMem[1]; /* First Mem of result set */ + Mem *pMem = &p->aMem[1]; /* First Mem of result set */ assert( p->explain ); assert( p->magic==VDBE_MAGIC_RUN ); assert( p->rc==SQLITE_OK || p->rc==SQLITE_BUSY || p->rc==SQLITE_NOMEM ); @@ -59452,10 +59317,11 @@ /* Even though this opcode does not use dynamic strings for ** the result, result columns may become dynamic if the user calls ** sqlite3_column_text16(), causing a translation to UTF-16 encoding. */ releaseMemArray(pMem, 8); + p->pResultSet = 0; if( p->rc==SQLITE_NOMEM ){ /* This happens if a malloc() inside a call to sqlite3_column_text() or ** sqlite3_column_text16() failed. */ db->mallocFailed = 1; @@ -59606,10 +59472,11 @@ pMem->type = SQLITE_NULL; } } p->nResColumn = 8 - 4*(p->explain-1); + p->pResultSet = &p->aMem[1]; p->rc = SQLITE_OK; rc = SQLITE_ROW; } return rc; } @@ -61361,11 +61228,11 @@ ** than 2GiB are support - anything large must be database corruption. ** Any corruption is detected in sqlite3BtreeParseCellPtr(), though, so ** this code can safely assume that nCellKey is 32-bits */ assert( sqlite3BtreeCursorIsValid(pCur) ); - rc = sqlite3BtreeKeySize(pCur, &nCellKey); + VVA_ONLY(rc =) sqlite3BtreeKeySize(pCur, &nCellKey); assert( rc==SQLITE_OK ); /* pCur is always valid so KeySize cannot fail */ assert( (nCellKey & SQLITE_MAX_U32)==(u64)nCellKey ); /* Read in the complete content of the index entry */ memset(&m, 0, sizeof(m)); @@ -61436,11 +61303,11 @@ int rc; BtCursor *pCur = pC->pCursor; Mem m; assert( sqlite3BtreeCursorIsValid(pCur) ); - rc = sqlite3BtreeKeySize(pCur, &nCellKey); + VVA_ONLY(rc =) sqlite3BtreeKeySize(pCur, &nCellKey); assert( rc==SQLITE_OK ); /* pCur is always valid so KeySize cannot fail */ /* nCellKey will always be between 0 and 0xffffffff because of the say ** that btreeParseCellPtr() and sqlite3GetVarint32() are implemented */ if( nCellKey<=0 || nCellKey>0x7fffffff ){ *res = 0; @@ -65712,20 +65579,20 @@ }else if( u.am.pC->cacheStatus==p->cacheCtr ){ u.am.payloadSize = u.am.pC->payloadSize; u.am.zRec = (char*)u.am.pC->aRow; }else if( u.am.pC->isIndex ){ assert( sqlite3BtreeCursorIsValid(u.am.pCrsr) ); - rc = sqlite3BtreeKeySize(u.am.pCrsr, &u.am.payloadSize64); + VVA_ONLY(rc =) sqlite3BtreeKeySize(u.am.pCrsr, &u.am.payloadSize64); assert( rc==SQLITE_OK ); /* True because of CursorMoveto() call above */ /* sqlite3BtreeParseCellPtr() uses getVarint32() to extract the ** payload size, so it is impossible for u.am.payloadSize64 to be ** larger than 32 bits. */ assert( (u.am.payloadSize64 & SQLITE_MAX_U32)==(u64)u.am.payloadSize64 ); u.am.payloadSize = (u32)u.am.payloadSize64; }else{ assert( sqlite3BtreeCursorIsValid(u.am.pCrsr) ); - rc = sqlite3BtreeDataSize(u.am.pCrsr, &u.am.payloadSize); + VVA_ONLY(rc =) sqlite3BtreeDataSize(u.am.pCrsr, &u.am.payloadSize); assert( rc==SQLITE_OK ); /* DataSize() cannot fail */ } }else if( ALWAYS(u.am.pC->pseudoTableReg>0) ){ u.am.pReg = &aMem[u.am.pC->pseudoTableReg]; assert( u.am.pReg->flags & MEM_Blob ); @@ -67773,18 +67640,18 @@ rc = sqlite3VdbeCursorMoveto(u.bk.pC); if( NEVER(rc!=SQLITE_OK) ) goto abort_due_to_error; if( u.bk.pC->isIndex ){ assert( !u.bk.pC->isTable ); - rc = sqlite3BtreeKeySize(u.bk.pCrsr, &u.bk.n64); + VVA_ONLY(rc =) sqlite3BtreeKeySize(u.bk.pCrsr, &u.bk.n64); assert( rc==SQLITE_OK ); /* True because of CursorMoveto() call above */ if( u.bk.n64>db->aLimit[SQLITE_LIMIT_LENGTH] ){ goto too_big; } u.bk.n = (u32)u.bk.n64; }else{ - rc = sqlite3BtreeDataSize(u.bk.pCrsr, &u.bk.n); + VVA_ONLY(rc =) sqlite3BtreeDataSize(u.bk.pCrsr, &u.bk.n); assert( rc==SQLITE_OK ); /* DataSize() cannot fail */ if( u.bk.n>(u32)db->aLimit[SQLITE_LIMIT_LENGTH] ){ goto too_big; } } @@ -73417,11 +73284,12 @@ pNew->flags |= EP_IntValue; pNew->u.iValue = iValue; }else{ int c; pNew->u.zToken = (char*)&pNew[1]; - memcpy(pNew->u.zToken, pToken->z, pToken->n); + assert( pToken->z!=0 || pToken->n==0 ); + if( pToken->n ) memcpy(pNew->u.zToken, pToken->z, pToken->n); pNew->u.zToken[pToken->n] = 0; if( dequote && nExtra>=3 && ((c = pToken->z[0])=='\'' || c=='"' || c=='[' || c=='`') ){ sqlite3Dequote(pNew->u.zToken); if( c=='"' ) pNew->flags |= EP_DblQuoted; @@ -74456,15 +74324,23 @@ ** ephemeral table. */ p = (ExprHasProperty(pX, EP_xIsSelect) ? pX->x.pSelect : 0); if( ALWAYS(pParse->nErr==0) && isCandidateForInOpt(p) ){ sqlite3 *db = pParse->db; /* Database connection */ - Expr *pExpr = p->pEList->a[0].pExpr; /* Expression <column> */ - int iCol = pExpr->iColumn; /* Index of column <column> */ Vdbe *v = sqlite3GetVdbe(pParse); /* Virtual machine being coded */ - Table *pTab = p->pSrc->a[0].pTab; /* Table <table>. */ + Table *pTab; /* Table <table>. */ + Expr *pExpr; /* Expression <column> */ + int iCol; /* Index of column <column> */ int iDb; /* Database idx for pTab */ + + assert( p ); /* Because of isCandidateForInOpt(p) */ + assert( p->pEList!=0 ); /* Because of isCandidateForInOpt(p) */ + assert( p->pEList->a[0].pExpr!=0 ); /* Because of isCandidateForInOpt(p) */ + assert( p->pSrc!=0 ); /* Because of isCandidateForInOpt(p) */ + pTab = p->pSrc->a[0].pTab; + pExpr = p->pEList->a[0].pExpr; + iCol = pExpr->iColumn; /* Code an OP_VerifyCookie and OP_TableLock for <table>. */ iDb = sqlite3SchemaToIndex(db, pTab->pSchema); sqlite3CodeVerifySchema(pParse, iDb); sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName); @@ -76467,11 +76343,11 @@ if( !ExprHasProperty(pB, EP_IntValue) || pA->u.iValue!=pB->u.iValue ){ return 2; } }else if( pA->op!=TK_COLUMN && pA->u.zToken ){ if( ExprHasProperty(pB, EP_IntValue) || NEVER(pB->u.zToken==0) ) return 2; - if( sqlite3StrICmp(pA->u.zToken,pB->u.zToken)!=0 ){ + if( strcmp(pA->u.zToken,pB->u.zToken)!=0 ){ return 2; } } if( (pA->flags & EP_ExpCollate)!=(pB->flags & EP_ExpCollate) ) return 1; if( (pA->flags & EP_ExpCollate)!=0 && pA->pColl!=pB->pColl ) return 2; @@ -77610,10 +77486,112 @@ ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** This file contains code associated with the ANALYZE command. +** +** The ANALYZE command gather statistics about the content of tables +** and indices. These statistics are made available to the query planner +** to help it make better decisions about how to perform queries. +** +** The following system tables are or have been supported: +** +** CREATE TABLE sqlite_stat1(tbl, idx, stat); +** CREATE TABLE sqlite_stat2(tbl, idx, sampleno, sample); +** CREATE TABLE sqlite_stat3(tbl, idx, nEq, nLt, nDLt, sample); +** +** Additional tables might be added in future releases of SQLite. +** The sqlite_stat2 table is not created or used unless the SQLite version +** is between 3.6.18 and 3.7.8, inclusive, and unless SQLite is compiled +** with SQLITE_ENABLE_STAT2. The sqlite_stat2 table is deprecated. +** The sqlite_stat2 table is superceded by sqlite_stat3, which is only +** created and used by SQLite versions 3.7.9 and later and with +** SQLITE_ENABLE_STAT3 defined. The fucntionality of sqlite_stat3 +** is a superset of sqlite_stat2. +** +** Format of sqlite_stat1: +** +** There is normally one row per index, with the index identified by the +** name in the idx column. The tbl column is the name of the table to +** which the index belongs. In each such row, the stat column will be +** a string consisting of a list of integers. The first integer in this +** list is the number of rows in the index and in the table. The second +** integer is the average number of rows in the index that have the same +** value in the first column of the index. The third integer is the average +** number of rows in the index that have the same value for the first two +** columns. The N-th integer (for N>1) is the average number of rows in +** the index which have the same value for the first N-1 columns. For +** a K-column index, there will be K+1 integers in the stat column. If +** the index is unique, then the last integer will be 1. +** +** The list of integers in the stat column can optionally be followed +** by the keyword "unordered". The "unordered" keyword, if it is present, +** must be separated from the last integer by a single space. If the +** "unordered" keyword is present, then the query planner assumes that +** the index is unordered and will not use the index for a range query. +** +** If the sqlite_stat1.idx column is NULL, then the sqlite_stat1.stat +** column contains a single integer which is the (estimated) number of +** rows in the table identified by sqlite_stat1.tbl. +** +** Format of sqlite_stat2: +** +** The sqlite_stat2 is only created and is only used if SQLite is compiled +** with SQLITE_ENABLE_STAT2 and if the SQLite version number is between +** 3.6.18 and 3.7.8. The "stat2" table contains additional information +** about the distribution of keys within an index. The index is identified by +** the "idx" column and the "tbl" column is the name of the table to which +** the index belongs. There are usually 10 rows in the sqlite_stat2 +** table for each index. +** +** The sqlite_stat2 entries for an index that have sampleno between 0 and 9 +** inclusive are samples of the left-most key value in the index taken at +** evenly spaced points along the index. Let the number of samples be S +** (10 in the standard build) and let C be the number of rows in the index. +** Then the sampled rows are given by: +** +** rownumber = (i*C*2 + C)/(S*2) +** +** For i between 0 and S-1. Conceptually, the index space is divided into +** S uniform buckets and the samples are the middle row from each bucket. +** +** The format for sqlite_stat2 is recorded here for legacy reference. This +** version of SQLite does not support sqlite_stat2. It neither reads nor +** writes the sqlite_stat2 table. This version of SQLite only supports +** sqlite_stat3. +** +** Format for sqlite_stat3: +** +** The sqlite_stat3 is an enhancement to sqlite_stat2. A new name is +** used to avoid compatibility problems. +** +** The format of the sqlite_stat3 table is similar to the format of +** the sqlite_stat2 table. There are multiple entries for each index. +** The idx column names the index and the tbl column is the table of the +** index. If the idx and tbl columns are the same, then the sample is +** of the INTEGER PRIMARY KEY. The sample column is a value taken from +** the left-most column of the index. The nEq column is the approximate +** number of entires in the index whose left-most column exactly matches +** the sample. nLt is the approximate number of entires whose left-most +** column is less than the sample. The nDLt column is the approximate +** number of distinct left-most entries in the index that are less than +** the sample. +** +** Future versions of SQLite might change to store a string containing +** multiple integers values in the nDLt column of sqlite_stat3. The first +** integer will be the number of prior index entires that are distinct in +** the left-most column. The second integer will be the number of prior index +** entries that are distinct in the first two columns. The third integer +** will be the number of prior index entries that are distinct in the first +** three columns. And so forth. With that extension, the nDLt field is +** similar in function to the sqlite_stat1.stat field. +** +** There can be an arbitrary number of sqlite_stat3 entries per index. +** The ANALYZE command will typically generate sqlite_stat3 tables +** that contain between 10 and 40 samples which are distributed across +** the key space, though not uniformly, and which include samples with +** largest possible nEq values. */ #ifndef SQLITE_OMIT_ANALYZE /* ** This routine generates code that opens the sqlite_stat1 table for @@ -77641,12 +77619,12 @@ static const struct { const char *zName; const char *zCols; } aTable[] = { { "sqlite_stat1", "tbl,idx,stat" }, -#ifdef SQLITE_ENABLE_STAT2 - { "sqlite_stat2", "tbl,idx,sampleno,sample" }, +#ifdef SQLITE_ENABLE_STAT3 + { "sqlite_stat3", "tbl,idx,neq,nlt,ndlt,sample" }, #endif }; int aRoot[] = {0, 0}; u8 aCreateTbl[] = {0, 0}; @@ -77658,10 +77636,13 @@ if( v==0 ) return; assert( sqlite3BtreeHoldsAllMutexes(db) ); assert( sqlite3VdbeDb(v)==db ); pDb = &db->aDb[iDb]; + /* Create new statistic tables if they do not exist, or clear them + ** if they do already exist. + */ for(i=0; i<ArraySize(aTable); i++){ const char *zTab = aTable[i].zName; Table *pStat; if( (pStat = sqlite3FindTable(db, zTab, pDb->zName))==0 ){ /* The sqlite_stat[12] table does not exist. Create it. Note that a @@ -77688,17 +77669,237 @@ sqlite3VdbeAddOp2(v, OP_Clear, aRoot[i], iDb); } } } - /* Open the sqlite_stat[12] tables for writing. */ + /* Open the sqlite_stat[13] tables for writing. */ for(i=0; i<ArraySize(aTable); i++){ sqlite3VdbeAddOp3(v, OP_OpenWrite, iStatCur+i, aRoot[i], iDb); sqlite3VdbeChangeP4(v, -1, (char *)3, P4_INT32); sqlite3VdbeChangeP5(v, aCreateTbl[i]); } } + +/* +** Recommended number of samples for sqlite_stat3 +*/ +#ifndef SQLITE_STAT3_SAMPLES +# define SQLITE_STAT3_SAMPLES 24 +#endif + +/* +** Three SQL functions - stat3_init(), stat3_push(), and stat3_pop() - +** share an instance of the following structure to hold their state +** information. +*/ +typedef struct Stat3Accum Stat3Accum; +struct Stat3Accum { + tRowcnt nRow; /* Number of rows in the entire table */ + tRowcnt nPSample; /* How often to do a periodic sample */ + int iMin; /* Index of entry with minimum nEq and hash */ + int mxSample; /* Maximum number of samples to accumulate */ + int nSample; /* Current number of samples */ + u32 iPrn; /* Pseudo-random number used for sampling */ + struct Stat3Sample { + i64 iRowid; /* Rowid in main table of the key */ + tRowcnt nEq; /* sqlite_stat3.nEq */ + tRowcnt nLt; /* sqlite_stat3.nLt */ + tRowcnt nDLt; /* sqlite_stat3.nDLt */ + u8 isPSample; /* True if a periodic sample */ + u32 iHash; /* Tiebreaker hash */ + } *a; /* An array of samples */ +}; + +#ifdef SQLITE_ENABLE_STAT3 +/* +** Implementation of the stat3_init(C,S) SQL function. The two parameters +** are the number of rows in the table or index (C) and the number of samples +** to accumulate (S). +** +** This routine allocates the Stat3Accum object. +** +** The return value is the Stat3Accum object (P). +*/ +static void stat3Init( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Stat3Accum *p; + tRowcnt nRow; + int mxSample; + int n; + + UNUSED_PARAMETER(argc); + nRow = (tRowcnt)sqlite3_value_int64(argv[0]); + mxSample = sqlite3_value_int(argv[1]); + n = sizeof(*p) + sizeof(p->a[0])*mxSample; + p = sqlite3_malloc( n ); + if( p==0 ){ + sqlite3_result_error_nomem(context); + return; + } + memset(p, 0, n); + p->a = (struct Stat3Sample*)&p[1]; + p->nRow = nRow; + p->mxSample = mxSample; + p->nPSample = p->nRow/(mxSample/3+1) + 1; + sqlite3_randomness(sizeof(p->iPrn), &p->iPrn); + sqlite3_result_blob(context, p, sizeof(p), sqlite3_free); +} +static const FuncDef stat3InitFuncdef = { + 2, /* nArg */ + SQLITE_UTF8, /* iPrefEnc */ + 0, /* flags */ + 0, /* pUserData */ + 0, /* pNext */ + stat3Init, /* xFunc */ + 0, /* xStep */ + 0, /* xFinalize */ + "stat3_init", /* zName */ + 0, /* pHash */ + 0 /* pDestructor */ +}; + + +/* +** Implementation of the stat3_push(nEq,nLt,nDLt,rowid,P) SQL function. The +** arguments describe a single key instance. This routine makes the +** decision about whether or not to retain this key for the sqlite_stat3 +** table. +** +** The return value is NULL. +*/ +static void stat3Push( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Stat3Accum *p = (Stat3Accum*)sqlite3_value_blob(argv[4]); + tRowcnt nEq = sqlite3_value_int64(argv[0]); + tRowcnt nLt = sqlite3_value_int64(argv[1]); + tRowcnt nDLt = sqlite3_value_int64(argv[2]); + i64 rowid = sqlite3_value_int64(argv[3]); + u8 isPSample = 0; + u8 doInsert = 0; + int iMin = p->iMin; + struct Stat3Sample *pSample; + int i; + u32 h; + + UNUSED_PARAMETER(context); + UNUSED_PARAMETER(argc); + if( nEq==0 ) return; + h = p->iPrn = p->iPrn*1103515245 + 12345; + if( (nLt/p->nPSample)!=((nEq+nLt)/p->nPSample) ){ + doInsert = isPSample = 1; + }else if( p->nSample<p->mxSample ){ + doInsert = 1; + }else{ + if( nEq>p->a[iMin].nEq || (nEq==p->a[iMin].nEq && h>p->a[iMin].iHash) ){ + doInsert = 1; + } + } + if( !doInsert ) return; + if( p->nSample==p->mxSample ){ + assert( p->nSample - iMin - 1 >= 0 ); + memmove(&p->a[iMin], &p->a[iMin+1], sizeof(p->a[0])*(p->nSample-iMin-1)); + pSample = &p->a[p->nSample-1]; + }else{ + pSample = &p->a[p->nSample++]; + } + pSample->iRowid = rowid; + pSample->nEq = nEq; + pSample->nLt = nLt; + pSample->nDLt = nDLt; + pSample->iHash = h; + pSample->isPSample = isPSample; + + /* Find the new minimum */ + if( p->nSample==p->mxSample ){ + pSample = p->a; + i = 0; + while( pSample->isPSample ){ + i++; + pSample++; + assert( i<p->nSample ); + } + nEq = pSample->nEq; + h = pSample->iHash; + iMin = i; + for(i++, pSample++; i<p->nSample; i++, pSample++){ + if( pSample->isPSample ) continue; + if( pSample->nEq<nEq + || (pSample->nEq==nEq && pSample->iHash<h) + ){ + iMin = i; + nEq = pSample->nEq; + h = pSample->iHash; + } + } + p->iMin = iMin; + } +} +static const FuncDef stat3PushFuncdef = { + 5, /* nArg */ + SQLITE_UTF8, /* iPrefEnc */ + 0, /* flags */ + 0, /* pUserData */ + 0, /* pNext */ + stat3Push, /* xFunc */ + 0, /* xStep */ + 0, /* xFinalize */ + "stat3_push", /* zName */ + 0, /* pHash */ + 0 /* pDestructor */ +}; + +/* +** Implementation of the stat3_get(P,N,...) SQL function. This routine is +** used to query the results. Content is returned for the Nth sqlite_stat3 +** row where N is between 0 and S-1 and S is the number of samples. The +** value returned depends on the number of arguments. +** +** argc==2 result: rowid +** argc==3 result: nEq +** argc==4 result: nLt +** argc==5 result: nDLt +*/ +static void stat3Get( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int n = sqlite3_value_int(argv[1]); + Stat3Accum *p = (Stat3Accum*)sqlite3_value_blob(argv[0]); + + assert( p!=0 ); + if( p->nSample<=n ) return; + switch( argc ){ + case 2: sqlite3_result_int64(context, p->a[n].iRowid); break; + case 3: sqlite3_result_int64(context, p->a[n].nEq); break; + case 4: sqlite3_result_int64(context, p->a[n].nLt); break; + default: sqlite3_result_int64(context, p->a[n].nDLt); break; + } +} +static const FuncDef stat3GetFuncdef = { + -1, /* nArg */ + SQLITE_UTF8, /* iPrefEnc */ + 0, /* flags */ + 0, /* pUserData */ + 0, /* pNext */ + stat3Get, /* xFunc */ + 0, /* xStep */ + 0, /* xFinalize */ + "stat3_get", /* zName */ + 0, /* pHash */ + 0 /* pDestructor */ +}; +#endif /* SQLITE_ENABLE_STAT3 */ + + + /* ** Generate code to do an analysis of all indices associated with ** a single table. */ @@ -77718,24 +77919,31 @@ int endOfLoop; /* The end of the loop */ int jZeroRows = -1; /* Jump from here if number of rows is zero */ int iDb; /* Index of database containing pTab */ int regTabname = iMem++; /* Register containing table name */ int regIdxname = iMem++; /* Register containing index name */ - int regSampleno = iMem++; /* Register containing next sample number */ - int regCol = iMem++; /* Content of a column analyzed table */ + int regStat1 = iMem++; /* The stat column of sqlite_stat1 */ +#ifdef SQLITE_ENABLE_STAT3 + int regNumEq = regStat1; /* Number of instances. Same as regStat1 */ + int regNumLt = iMem++; /* Number of keys less than regSample */ + int regNumDLt = iMem++; /* Number of distinct keys less than regSample */ + int regSample = iMem++; /* The next sample value */ + int regRowid = regSample; /* Rowid of a sample */ + int regAccum = iMem++; /* Register to hold Stat3Accum object */ + int regLoop = iMem++; /* Loop counter */ + int regCount = iMem++; /* Number of rows in the table or index */ + int regTemp1 = iMem++; /* Intermediate register */ + int regTemp2 = iMem++; /* Intermediate register */ + int once = 1; /* One-time initialization */ + int shortJump = 0; /* Instruction address */ + int iTabCur = pParse->nTab++; /* Table cursor */ +#endif + int regCol = iMem++; /* Content of a column in analyzed table */ int regRec = iMem++; /* Register holding completed record */ int regTemp = iMem++; /* Temporary use register */ - int regRowid = iMem++; /* Rowid for the inserted record */ - -#ifdef SQLITE_ENABLE_STAT2 - int addr = 0; /* Instruction address */ - int regTemp2 = iMem++; /* Temporary use register */ - int regSamplerecno = iMem++; /* Index of next sample to record */ - int regRecno = iMem++; /* Current sample index */ - int regLast = iMem++; /* Index of last sample to record */ - int regFirst = iMem++; /* Index of first sample to record */ -#endif + int regNewRowid = iMem++; /* Rowid for the inserted record */ + v = sqlite3GetVdbe(pParse); if( v==0 || NEVER(pTab==0) ){ return; } @@ -77764,13 +77972,18 @@ iIdxCur = pParse->nTab++; sqlite3VdbeAddOp4(v, OP_String8, 0, regTabname, 0, pTab->zName, 0); for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ int nCol; KeyInfo *pKey; + int addrIfNot = 0; /* address of OP_IfNot */ + int *aChngAddr; /* Array of jump instruction addresses */ if( pOnlyIdx && pOnlyIdx!=pIdx ) continue; + VdbeNoopComment((v, "Begin analysis of %s", pIdx->zName)); nCol = pIdx->nColumn; + aChngAddr = sqlite3DbMallocRaw(db, sizeof(int)*nCol); + if( aChngAddr==0 ) continue; pKey = sqlite3IndexKeyinfo(pParse, pIdx); if( iMem+1+(nCol*2)>pParse->nMem ){ pParse->nMem = iMem+1+(nCol*2); } @@ -77781,35 +77994,24 @@ VdbeComment((v, "%s", pIdx->zName)); /* Populate the register containing the index name. */ sqlite3VdbeAddOp4(v, OP_String8, 0, regIdxname, 0, pIdx->zName, 0); -#ifdef SQLITE_ENABLE_STAT2 - - /* If this iteration of the loop is generating code to analyze the - ** first index in the pTab->pIndex list, then register regLast has - ** not been populated. In this case populate it now. */ - if( pTab->pIndex==pIdx ){ - sqlite3VdbeAddOp2(v, OP_Integer, SQLITE_INDEX_SAMPLES, regSamplerecno); - sqlite3VdbeAddOp2(v, OP_Integer, SQLITE_INDEX_SAMPLES*2-1, regTemp); - sqlite3VdbeAddOp2(v, OP_Integer, SQLITE_INDEX_SAMPLES*2, regTemp2); - - sqlite3VdbeAddOp2(v, OP_Count, iIdxCur, regLast); - sqlite3VdbeAddOp2(v, OP_Null, 0, regFirst); - addr = sqlite3VdbeAddOp3(v, OP_Lt, regSamplerecno, 0, regLast); - sqlite3VdbeAddOp3(v, OP_Divide, regTemp2, regLast, regFirst); - sqlite3VdbeAddOp3(v, OP_Multiply, regLast, regTemp, regLast); - sqlite3VdbeAddOp2(v, OP_AddImm, regLast, SQLITE_INDEX_SAMPLES*2-2); - sqlite3VdbeAddOp3(v, OP_Divide, regTemp2, regLast, regLast); - sqlite3VdbeJumpHere(v, addr); - } - - /* Zero the regSampleno and regRecno registers. */ - sqlite3VdbeAddOp2(v, OP_Integer, 0, regSampleno); - sqlite3VdbeAddOp2(v, OP_Integer, 0, regRecno); - sqlite3VdbeAddOp2(v, OP_Copy, regFirst, regSamplerecno); -#endif +#ifdef SQLITE_ENABLE_STAT3 + if( once ){ + once = 0; + sqlite3OpenTable(pParse, iTabCur, iDb, pTab, OP_OpenRead); + } + sqlite3VdbeAddOp2(v, OP_Count, iIdxCur, regCount); + sqlite3VdbeAddOp2(v, OP_Integer, SQLITE_STAT3_SAMPLES, regTemp1); + sqlite3VdbeAddOp2(v, OP_Integer, 0, regNumEq); + sqlite3VdbeAddOp2(v, OP_Integer, 0, regNumLt); + sqlite3VdbeAddOp2(v, OP_Integer, -1, regNumDLt); + sqlite3VdbeAddOp4(v, OP_Function, 1, regCount, regAccum, + (char*)&stat3InitFuncdef, P4_FUNCDEF); + sqlite3VdbeChangeP5(v, 2); +#endif /* SQLITE_ENABLE_STAT3 */ /* The block of memory cells initialized here is used as follows. ** ** iMem: ** The total number of rows in the table. @@ -77835,79 +78037,87 @@ /* Start the analysis loop. This loop runs through all the entries in ** the index b-tree. */ endOfLoop = sqlite3VdbeMakeLabel(v); sqlite3VdbeAddOp2(v, OP_Rewind, iIdxCur, endOfLoop); topOfLoop = sqlite3VdbeCurrentAddr(v); - sqlite3VdbeAddOp2(v, OP_AddImm, iMem, 1); + sqlite3VdbeAddOp2(v, OP_AddImm, iMem, 1); /* Increment row counter */ for(i=0; i<nCol; i++){ CollSeq *pColl; sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, i, regCol); if( i==0 ){ -#ifdef SQLITE_ENABLE_STAT2 - /* Check if the record that cursor iIdxCur points to contains a - ** value that should be stored in the sqlite_stat2 table. If so, - ** store it. */ - int ne = sqlite3VdbeAddOp3(v, OP_Ne, regRecno, 0, regSamplerecno); - assert( regTabname+1==regIdxname - && regTabname+2==regSampleno - && regTabname+3==regCol - ); - sqlite3VdbeChangeP5(v, SQLITE_JUMPIFNULL); - sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 4, regRec, "aaab", 0); - sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur+1, regRowid); - sqlite3VdbeAddOp3(v, OP_Insert, iStatCur+1, regRec, regRowid); - - /* Calculate new values for regSamplerecno and regSampleno. - ** - ** sampleno = sampleno + 1 - ** samplerecno = samplerecno+(remaining records)/(remaining samples) - */ - sqlite3VdbeAddOp2(v, OP_AddImm, regSampleno, 1); - sqlite3VdbeAddOp3(v, OP_Subtract, regRecno, regLast, regTemp); - sqlite3VdbeAddOp2(v, OP_AddImm, regTemp, -1); - sqlite3VdbeAddOp2(v, OP_Integer, SQLITE_INDEX_SAMPLES, regTemp2); - sqlite3VdbeAddOp3(v, OP_Subtract, regSampleno, regTemp2, regTemp2); - sqlite3VdbeAddOp3(v, OP_Divide, regTemp2, regTemp, regTemp); - sqlite3VdbeAddOp3(v, OP_Add, regSamplerecno, regTemp, regSamplerecno); - - sqlite3VdbeJumpHere(v, ne); - sqlite3VdbeAddOp2(v, OP_AddImm, regRecno, 1); -#endif - /* Always record the very first row */ - sqlite3VdbeAddOp1(v, OP_IfNot, iMem+1); + addrIfNot = sqlite3VdbeAddOp1(v, OP_IfNot, iMem+1); } assert( pIdx->azColl!=0 ); assert( pIdx->azColl[i]!=0 ); pColl = sqlite3LocateCollSeq(pParse, pIdx->azColl[i]); - sqlite3VdbeAddOp4(v, OP_Ne, regCol, 0, iMem+nCol+i+1, - (char*)pColl, P4_COLLSEQ); + aChngAddr[i] = sqlite3VdbeAddOp4(v, OP_Ne, regCol, 0, iMem+nCol+i+1, + (char*)pColl, P4_COLLSEQ); sqlite3VdbeChangeP5(v, SQLITE_NULLEQ); - } - if( db->mallocFailed ){ - /* If a malloc failure has occurred, then the result of the expression - ** passed as the second argument to the call to sqlite3VdbeJumpHere() - ** below may be negative. Which causes an assert() to fail (or an - ** out-of-bounds write if SQLITE_DEBUG is not defined). */ - return; + VdbeComment((v, "jump if column %d changed", i)); +#ifdef SQLITE_ENABLE_STAT3 + if( i==0 ){ + sqlite3VdbeAddOp2(v, OP_AddImm, regNumEq, 1); + VdbeComment((v, "incr repeat count")); + } +#endif } sqlite3VdbeAddOp2(v, OP_Goto, 0, endOfLoop); for(i=0; i<nCol; i++){ - int addr2 = sqlite3VdbeCurrentAddr(v) - (nCol*2); + sqlite3VdbeJumpHere(v, aChngAddr[i]); /* Set jump dest for the OP_Ne */ if( i==0 ){ - sqlite3VdbeJumpHere(v, addr2-1); /* Set jump dest for the OP_IfNot */ + sqlite3VdbeJumpHere(v, addrIfNot); /* Jump dest for OP_IfNot */ +#ifdef SQLITE_ENABLE_STAT3 + sqlite3VdbeAddOp4(v, OP_Function, 1, regNumEq, regTemp2, + (char*)&stat3PushFuncdef, P4_FUNCDEF); + sqlite3VdbeChangeP5(v, 5); + sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, pIdx->nColumn, regRowid); + sqlite3VdbeAddOp3(v, OP_Add, regNumEq, regNumLt, regNumLt); + sqlite3VdbeAddOp2(v, OP_AddImm, regNumDLt, 1); + sqlite3VdbeAddOp2(v, OP_Integer, 1, regNumEq); +#endif } - sqlite3VdbeJumpHere(v, addr2); /* Set jump dest for the OP_Ne */ sqlite3VdbeAddOp2(v, OP_AddImm, iMem+i+1, 1); sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, i, iMem+nCol+i+1); } + sqlite3DbFree(db, aChngAddr); - /* End of the analysis loop. */ + /* Always jump here after updating the iMem+1...iMem+1+nCol counters */ sqlite3VdbeResolveLabel(v, endOfLoop); + sqlite3VdbeAddOp2(v, OP_Next, iIdxCur, topOfLoop); sqlite3VdbeAddOp1(v, OP_Close, iIdxCur); +#ifdef SQLITE_ENABLE_STAT3 + sqlite3VdbeAddOp4(v, OP_Function, 1, regNumEq, regTemp2, + (char*)&stat3PushFuncdef, P4_FUNCDEF); + sqlite3VdbeChangeP5(v, 5); + sqlite3VdbeAddOp2(v, OP_Integer, -1, regLoop); + shortJump = + sqlite3VdbeAddOp2(v, OP_AddImm, regLoop, 1); + sqlite3VdbeAddOp4(v, OP_Function, 1, regAccum, regTemp1, + (char*)&stat3GetFuncdef, P4_FUNCDEF); + sqlite3VdbeChangeP5(v, 2); + sqlite3VdbeAddOp1(v, OP_IsNull, regTemp1); + sqlite3VdbeAddOp3(v, OP_NotExists, iTabCur, shortJump, regTemp1); + sqlite3VdbeAddOp3(v, OP_Column, iTabCur, pIdx->aiColumn[0], regSample); + sqlite3ColumnDefault(v, pTab, pIdx->aiColumn[0], regSample); + sqlite3VdbeAddOp4(v, OP_Function, 1, regAccum, regNumEq, + (char*)&stat3GetFuncdef, P4_FUNCDEF); + sqlite3VdbeChangeP5(v, 3); + sqlite3VdbeAddOp4(v, OP_Function, 1, regAccum, regNumLt, + (char*)&stat3GetFuncdef, P4_FUNCDEF); + sqlite3VdbeChangeP5(v, 4); + sqlite3VdbeAddOp4(v, OP_Function, 1, regAccum, regNumDLt, + (char*)&stat3GetFuncdef, P4_FUNCDEF); + sqlite3VdbeChangeP5(v, 5); + sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 6, regRec, "bbbbbb", 0); + sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur+1, regNewRowid); + sqlite3VdbeAddOp3(v, OP_Insert, iStatCur+1, regRec, regNewRowid); + sqlite3VdbeAddOp2(v, OP_Goto, 0, shortJump); + sqlite3VdbeJumpHere(v, shortJump+2); +#endif /* Store the results in sqlite_stat1. ** ** The result is a single row of the sqlite_stat1 table. The first ** two columns are the names of the table and index. The third column @@ -77923,50 +78133,51 @@ ** ** If K==0 then no entry is made into the sqlite_stat1 table. ** If K>0 then it is always the case the D>0 so division by zero ** is never possible. */ - sqlite3VdbeAddOp2(v, OP_SCopy, iMem, regSampleno); + sqlite3VdbeAddOp2(v, OP_SCopy, iMem, regStat1); if( jZeroRows<0 ){ jZeroRows = sqlite3VdbeAddOp1(v, OP_IfNot, iMem); } for(i=0; i<nCol; i++){ sqlite3VdbeAddOp4(v, OP_String8, 0, regTemp, 0, " ", 0); - sqlite3VdbeAddOp3(v, OP_Concat, regTemp, regSampleno, regSampleno); + sqlite3VdbeAddOp3(v, OP_Concat, regTemp, regStat1, regStat1); sqlite3VdbeAddOp3(v, OP_Add, iMem, iMem+i+1, regTemp); sqlite3VdbeAddOp2(v, OP_AddImm, regTemp, -1); sqlite3VdbeAddOp3(v, OP_Divide, iMem+i+1, regTemp, regTemp); sqlite3VdbeAddOp1(v, OP_ToInt, regTemp); - sqlite3VdbeAddOp3(v, OP_Concat, regTemp, regSampleno, regSampleno); + sqlite3VdbeAddOp3(v, OP_Concat, regTemp, regStat1, regStat1); } sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regRec, "aaa", 0); - sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regRowid); - sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regRec, regRowid); + sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regNewRowid); + sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regRec, regNewRowid); sqlite3VdbeChangeP5(v, OPFLAG_APPEND); } /* If the table has no indices, create a single sqlite_stat1 entry ** containing NULL as the index name and the row count as the content. */ if( pTab->pIndex==0 ){ sqlite3VdbeAddOp3(v, OP_OpenRead, iIdxCur, pTab->tnum, iDb); VdbeComment((v, "%s", pTab->zName)); - sqlite3VdbeAddOp2(v, OP_Count, iIdxCur, regSampleno); + sqlite3VdbeAddOp2(v, OP_Count, iIdxCur, regStat1); sqlite3VdbeAddOp1(v, OP_Close, iIdxCur); - jZeroRows = sqlite3VdbeAddOp1(v, OP_IfNot, regSampleno); + jZeroRows = sqlite3VdbeAddOp1(v, OP_IfNot, regStat1); }else{ sqlite3VdbeJumpHere(v, jZeroRows); jZeroRows = sqlite3VdbeAddOp0(v, OP_Goto); } sqlite3VdbeAddOp2(v, OP_Null, 0, regIdxname); sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regRec, "aaa", 0); - sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regRowid); - sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regRec, regRowid); + sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regNewRowid); + sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regRec, regNewRowid); sqlite3VdbeChangeP5(v, OPFLAG_APPEND); if( pParse->nMem<regRec ) pParse->nMem = regRec; sqlite3VdbeJumpHere(v, jZeroRows); } + /* ** Generate code that will cause the most recent index analysis to ** be loaded into internal hash tables where is can be used. */ @@ -77987,11 +78198,11 @@ int iStatCur; int iMem; sqlite3BeginWriteOperation(pParse, 0, iDb); iStatCur = pParse->nTab; - pParse->nTab += 2; + pParse->nTab += 3; openStatTable(pParse, iDb, iStatCur, 0, 0); iMem = pParse->nMem+1; assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); for(k=sqliteHashFirst(&pSchema->tblHash); k; k=sqliteHashNext(k)){ Table *pTab = (Table*)sqliteHashData(k); @@ -78012,11 +78223,11 @@ assert( pTab!=0 ); assert( sqlite3BtreeHoldsAllMutexes(pParse->db) ); iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); sqlite3BeginWriteOperation(pParse, 0, iDb); iStatCur = pParse->nTab; - pParse->nTab += 2; + pParse->nTab += 3; if( pOnlyIdx ){ openStatTable(pParse, iDb, iStatCur, pOnlyIdx->zName, "idx"); }else{ openStatTable(pParse, iDb, iStatCur, pTab->zName, "tbl"); } @@ -78117,11 +78328,11 @@ static int analysisLoader(void *pData, int argc, char **argv, char **NotUsed){ analysisInfo *pInfo = (analysisInfo*)pData; Index *pIndex; Table *pTable; int i, c, n; - unsigned int v; + tRowcnt v; const char *z; assert( argc==3 ); UNUSED_PARAMETER2(NotUsed, argc); @@ -78160,40 +78371,172 @@ /* ** If the Index.aSample variable is not NULL, delete the aSample[] array ** and its contents. */ SQLITE_PRIVATE void sqlite3DeleteIndexSamples(sqlite3 *db, Index *pIdx){ -#ifdef SQLITE_ENABLE_STAT2 +#ifdef SQLITE_ENABLE_STAT3 if( pIdx->aSample ){ int j; - for(j=0; j<SQLITE_INDEX_SAMPLES; j++){ + for(j=0; j<pIdx->nSample; j++){ IndexSample *p = &pIdx->aSample[j]; if( p->eType==SQLITE_TEXT || p->eType==SQLITE_BLOB ){ sqlite3DbFree(db, p->u.z); } } sqlite3DbFree(db, pIdx->aSample); + } + if( db && db->pnBytesFreed==0 ){ + pIdx->nSample = 0; + pIdx->aSample = 0; } #else UNUSED_PARAMETER(db); UNUSED_PARAMETER(pIdx); #endif } +#ifdef SQLITE_ENABLE_STAT3 /* -** Load the content of the sqlite_stat1 and sqlite_stat2 tables. The +** Load content from the sqlite_stat3 table into the Index.aSample[] +** arrays of all indices. +*/ +static int loadStat3(sqlite3 *db, const char *zDb){ + int rc; /* Result codes from subroutines */ + sqlite3_stmt *pStmt = 0; /* An SQL statement being run */ + char *zSql; /* Text of the SQL statement */ + Index *pPrevIdx = 0; /* Previous index in the loop */ + int idx = 0; /* slot in pIdx->aSample[] for next sample */ + int eType; /* Datatype of a sample */ + IndexSample *pSample; /* A slot in pIdx->aSample[] */ + + if( !sqlite3FindTable(db, "sqlite_stat3", zDb) ){ + return SQLITE_OK; + } + + zSql = sqlite3MPrintf(db, + "SELECT idx,count(*) FROM %Q.sqlite_stat3" + " GROUP BY idx", zDb); + if( !zSql ){ + return SQLITE_NOMEM; + } + rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0); + sqlite3DbFree(db, zSql); + if( rc ) return rc; + + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + char *zIndex; /* Index name */ + Index *pIdx; /* Pointer to the index object */ + int nSample; /* Number of samples */ + + zIndex = (char *)sqlite3_column_text(pStmt, 0); + if( zIndex==0 ) continue; + nSample = sqlite3_column_int(pStmt, 1); + pIdx = sqlite3FindIndex(db, zIndex, zDb); + if( pIdx==0 ) continue; + assert( pIdx->nSample==0 ); + pIdx->nSample = nSample; + pIdx->aSample = sqlite3MallocZero( nSample*sizeof(IndexSample) ); + pIdx->avgEq = pIdx->aiRowEst[1]; + if( pIdx->aSample==0 ){ + db->mallocFailed = 1; + sqlite3_finalize(pStmt); + return SQLITE_NOMEM; + } + } + rc = sqlite3_finalize(pStmt); + if( rc ) return rc; + + zSql = sqlite3MPrintf(db, + "SELECT idx,neq,nlt,ndlt,sample FROM %Q.sqlite_stat3", zDb); + if( !zSql ){ + return SQLITE_NOMEM; + } + rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0); + sqlite3DbFree(db, zSql); + if( rc ) return rc; + + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + char *zIndex; /* Index name */ + Index *pIdx; /* Pointer to the index object */ + int i; /* Loop counter */ + tRowcnt sumEq; /* Sum of the nEq values */ + + zIndex = (char *)sqlite3_column_text(pStmt, 0); + if( zIndex==0 ) continue; + pIdx = sqlite3FindIndex(db, zIndex, zDb); + if( pIdx==0 ) continue; + if( pIdx==pPrevIdx ){ + idx++; + }else{ + pPrevIdx = pIdx; + idx = 0; + } + assert( idx<pIdx->nSample ); + pSample = &pIdx->aSample[idx]; + pSample->nEq = (tRowcnt)sqlite3_column_int64(pStmt, 1); + pSample->nLt = (tRowcnt)sqlite3_column_int64(pStmt, 2); + pSample->nDLt = (tRowcnt)sqlite3_column_int64(pStmt, 3); + if( idx==pIdx->nSample-1 ){ + if( pSample->nDLt>0 ){ + for(i=0, sumEq=0; i<=idx-1; i++) sumEq += pIdx->aSample[i].nEq; + pIdx->avgEq = (pSample->nLt - sumEq)/pSample->nDLt; + } + if( pIdx->avgEq<=0 ) pIdx->avgEq = 1; + } + eType = sqlite3_column_type(pStmt, 4); + pSample->eType = (u8)eType; + switch( eType ){ + case SQLITE_INTEGER: { + pSample->u.i = sqlite3_column_int64(pStmt, 4); + break; + } + case SQLITE_FLOAT: { + pSample->u.r = sqlite3_column_double(pStmt, 4); + break; + } + case SQLITE_NULL: { + break; + } + default: assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB ); { + const char *z = (const char *)( + (eType==SQLITE_BLOB) ? + sqlite3_column_blob(pStmt, 4): + sqlite3_column_text(pStmt, 4) + ); + int n = z ? sqlite3_column_bytes(pStmt, 4) : 0; + pSample->nByte = n; + if( n < 1){ + pSample->u.z = 0; + }else{ + pSample->u.z = sqlite3Malloc(n); + if( pSample->u.z==0 ){ + db->mallocFailed = 1; + sqlite3_finalize(pStmt); + return SQLITE_NOMEM; + } + memcpy(pSample->u.z, z, n); + } + } + } + } + return sqlite3_finalize(pStmt); +} +#endif /* SQLITE_ENABLE_STAT3 */ + +/* +** Load the content of the sqlite_stat1 and sqlite_stat3 tables. The ** contents of sqlite_stat1 are used to populate the Index.aiRowEst[] -** arrays. The contents of sqlite_stat2 are used to populate the +** arrays. The contents of sqlite_stat3 are used to populate the ** Index.aSample[] arrays. ** ** If the sqlite_stat1 table is not present in the database, SQLITE_ERROR -** is returned. In this case, even if SQLITE_ENABLE_STAT2 was defined -** during compilation and the sqlite_stat2 table is present, no data is +** is returned. In this case, even if SQLITE_ENABLE_STAT3 was defined +** during compilation and the sqlite_stat3 table is present, no data is ** read from it. ** -** If SQLITE_ENABLE_STAT2 was defined during compilation and the -** sqlite_stat2 table is not present in the database, SQLITE_ERROR is +** If SQLITE_ENABLE_STAT3 was defined during compilation and the +** sqlite_stat3 table is not present in the database, SQLITE_ERROR is ** returned. However, in this case, data is read from the sqlite_stat1 ** table (if it is present) before returning. ** ** If an OOM error occurs, this function always sets db->mallocFailed. ** This means if the caller does not care about other errors, the return @@ -78211,12 +78554,14 @@ /* Clear any prior statistics */ assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); for(i=sqliteHashFirst(&db->aDb[iDb].pSchema->idxHash);i;i=sqliteHashNext(i)){ Index *pIdx = sqliteHashData(i); sqlite3DefaultRowEst(pIdx); +#ifdef SQLITE_ENABLE_STAT3 sqlite3DeleteIndexSamples(db, pIdx); pIdx->aSample = 0; +#endif } /* Check to make sure the sqlite_stat1 table exists */ sInfo.db = db; sInfo.zDatabase = db->aDb[iDb].zName; @@ -78224,91 +78569,23 @@ return SQLITE_ERROR; } /* Load new statistics out of the sqlite_stat1 table */ zSql = sqlite3MPrintf(db, - "SELECT tbl, idx, stat FROM %Q.sqlite_stat1", sInfo.zDatabase); + "SELECT tbl,idx,stat FROM %Q.sqlite_stat1", sInfo.zDatabase); if( zSql==0 ){ rc = SQLITE_NOMEM; }else{ rc = sqlite3_exec(db, zSql, analysisLoader, &sInfo, 0); sqlite3DbFree(db, zSql); } - /* Load the statistics from the sqlite_stat2 table. */ -#ifdef SQLITE_ENABLE_STAT2 - if( rc==SQLITE_OK && !sqlite3FindTable(db, "sqlite_stat2", sInfo.zDatabase) ){ - rc = SQLITE_ERROR; - } - if( rc==SQLITE_OK ){ - sqlite3_stmt *pStmt = 0; - - zSql = sqlite3MPrintf(db, - "SELECT idx,sampleno,sample FROM %Q.sqlite_stat2", sInfo.zDatabase); - if( !zSql ){ - rc = SQLITE_NOMEM; - }else{ - rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0); - sqlite3DbFree(db, zSql); - } - - if( rc==SQLITE_OK ){ - while( sqlite3_step(pStmt)==SQLITE_ROW ){ - char *zIndex; /* Index name */ - Index *pIdx; /* Pointer to the index object */ - - zIndex = (char *)sqlite3_column_text(pStmt, 0); - pIdx = zIndex ? sqlite3FindIndex(db, zIndex, sInfo.zDatabase) : 0; - if( pIdx ){ - int iSample = sqlite3_column_int(pStmt, 1); - if( iSample<SQLITE_INDEX_SAMPLES && iSample>=0 ){ - int eType = sqlite3_column_type(pStmt, 2); - - if( pIdx->aSample==0 ){ - static const int sz = sizeof(IndexSample)*SQLITE_INDEX_SAMPLES; - pIdx->aSample = (IndexSample *)sqlite3DbMallocRaw(0, sz); - if( pIdx->aSample==0 ){ - db->mallocFailed = 1; - break; - } - memset(pIdx->aSample, 0, sz); - } - - assert( pIdx->aSample ); - { - IndexSample *pSample = &pIdx->aSample[iSample]; - pSample->eType = (u8)eType; - if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ - pSample->u.r = sqlite3_column_double(pStmt, 2); - }else if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){ - const char *z = (const char *)( - (eType==SQLITE_BLOB) ? - sqlite3_column_blob(pStmt, 2): - sqlite3_column_text(pStmt, 2) - ); - int n = sqlite3_column_bytes(pStmt, 2); - if( n>24 ){ - n = 24; - } - pSample->nByte = (u8)n; - if( n < 1){ - pSample->u.z = 0; - }else{ - pSample->u.z = sqlite3DbStrNDup(0, z, n); - if( pSample->u.z==0 ){ - db->mallocFailed = 1; - break; - } - } - } - } - } - } - } - rc = sqlite3_finalize(pStmt); - } + /* Load the statistics from the sqlite_stat3 table. */ +#ifdef SQLITE_ENABLE_STAT3 + if( rc==SQLITE_OK ){ + rc = loadStat3(db, sInfo.zDatabase); } #endif if( rc==SQLITE_NOMEM ){ db->mallocFailed = 1; @@ -81120,11 +81397,15 @@ Parse *pParse, /* The parsing context */ int iDb, /* The database number */ const char *zType, /* "idx" or "tbl" */ const char *zName /* Name of index or table */ ){ - static const char *azStatTab[] = { "sqlite_stat1", "sqlite_stat2" }; + static const char *azStatTab[] = { + "sqlite_stat1", + "sqlite_stat2", + "sqlite_stat3", + }; int i; const char *zDbName = pParse->db->aDb[iDb].zName; for(i=0; i<ArraySize(azStatTab); i++){ if( sqlite3FindTable(pParse->db, azStatTab[i], zDbName) ){ sqlite3NestedParse(pParse, @@ -81132,10 +81413,80 @@ zDbName, azStatTab[i], zType, zName ); } } } + +/* +** Generate code to drop a table. +*/ +SQLITE_PRIVATE void sqlite3CodeDropTable(Parse *pParse, Table *pTab, int iDb, int isView){ + Vdbe *v; + sqlite3 *db = pParse->db; + Trigger *pTrigger; + Db *pDb = &db->aDb[iDb]; + + v = sqlite3GetVdbe(pParse); + assert( v!=0 ); + sqlite3BeginWriteOperation(pParse, 1, iDb); + +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( IsVirtual(pTab) ){ + sqlite3VdbeAddOp0(v, OP_VBegin); + } +#endif + + /* Drop all triggers associated with the table being dropped. Code + ** is generated to remove entries from sqlite_master and/or + ** sqlite_temp_master if required. + */ + pTrigger = sqlite3TriggerList(pParse, pTab); + while( pTrigger ){ + assert( pTrigger->pSchema==pTab->pSchema || + pTrigger->pSchema==db->aDb[1].pSchema ); + sqlite3DropTriggerPtr(pParse, pTrigger); + pTrigger = pTrigger->pNext; + } + +#ifndef SQLITE_OMIT_AUTOINCREMENT + /* Remove any entries of the sqlite_sequence table associated with + ** the table being dropped. This is done before the table is dropped + ** at the btree level, in case the sqlite_sequence table needs to + ** move as a result of the drop (can happen in auto-vacuum mode). + */ + if( pTab->tabFlags & TF_Autoincrement ){ + sqlite3NestedParse(pParse, + "DELETE FROM %Q.sqlite_sequence WHERE name=%Q", + pDb->zName, pTab->zName + ); + } +#endif + + /* Drop all SQLITE_MASTER table and index entries that refer to the + ** table. The program name loops through the master table and deletes + ** every row that refers to a table of the same name as the one being + ** dropped. Triggers are handled seperately because a trigger can be + ** created in the temp database that refers to a table in another + ** database. + */ + sqlite3NestedParse(pParse, + "DELETE FROM %Q.%s WHERE tbl_name=%Q and type!='trigger'", + pDb->zName, SCHEMA_TABLE(iDb), pTab->zName); + if( !isView && !IsVirtual(pTab) ){ + destroyTable(pParse, pTab); + } + + /* Remove the table entry from SQLite's internal schema and modify + ** the schema cookie. + */ + if( IsVirtual(pTab) ){ + sqlite3VdbeAddOp4(v, OP_VDestroy, iDb, 0, 0, pTab->zName, 0); + } + sqlite3VdbeAddOp4(v, OP_DropTable, iDb, 0, 0, pTab->zName, 0); + sqlite3ChangeCookie(pParse, iDb); + sqliteViewResetAll(db, iDb); +} /* ** This routine is called to do the work of a DROP TABLE statement. ** pName is the name of the table to be dropped. */ @@ -81201,11 +81552,12 @@ if( sqlite3AuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, zDb) ){ goto exit_drop_table; } } #endif - if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 ){ + if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 + && sqlite3StrNICmp(pTab->zName, "sqlite_stat", 11)!=0 ){ sqlite3ErrorMsg(pParse, "table %s may not be dropped", pTab->zName); goto exit_drop_table; } #ifndef SQLITE_OMIT_VIEW @@ -81225,72 +81577,15 @@ /* Generate code to remove the table from the master table ** on disk. */ v = sqlite3GetVdbe(pParse); if( v ){ - Trigger *pTrigger; - Db *pDb = &db->aDb[iDb]; sqlite3BeginWriteOperation(pParse, 1, iDb); - -#ifndef SQLITE_OMIT_VIRTUALTABLE - if( IsVirtual(pTab) ){ - sqlite3VdbeAddOp0(v, OP_VBegin); - } -#endif + sqlite3ClearStatTables(pParse, iDb, "tbl", pTab->zName); sqlite3FkDropTable(pParse, pName, pTab); - - /* Drop all triggers associated with the table being dropped. Code - ** is generated to remove entries from sqlite_master and/or - ** sqlite_temp_master if required. - */ - pTrigger = sqlite3TriggerList(pParse, pTab); - while( pTrigger ){ - assert( pTrigger->pSchema==pTab->pSchema || - pTrigger->pSchema==db->aDb[1].pSchema ); - sqlite3DropTriggerPtr(pParse, pTrigger); - pTrigger = pTrigger->pNext; - } - -#ifndef SQLITE_OMIT_AUTOINCREMENT - /* Remove any entries of the sqlite_sequence table associated with - ** the table being dropped. This is done before the table is dropped - ** at the btree level, in case the sqlite_sequence table needs to - ** move as a result of the drop (can happen in auto-vacuum mode). - */ - if( pTab->tabFlags & TF_Autoincrement ){ - sqlite3NestedParse(pParse, - "DELETE FROM %s.sqlite_sequence WHERE name=%Q", - pDb->zName, pTab->zName - ); - } -#endif - - /* Drop all SQLITE_MASTER table and index entries that refer to the - ** table. The program name loops through the master table and deletes - ** every row that refers to a table of the same name as the one being - ** dropped. Triggers are handled seperately because a trigger can be - ** created in the temp database that refers to a table in another - ** database. - */ - sqlite3NestedParse(pParse, - "DELETE FROM %Q.%s WHERE tbl_name=%Q and type!='trigger'", - pDb->zName, SCHEMA_TABLE(iDb), pTab->zName); - sqlite3ClearStatTables(pParse, iDb, "tbl", pTab->zName); - if( !isView && !IsVirtual(pTab) ){ - destroyTable(pParse, pTab); - } - - /* Remove the table entry from SQLite's internal schema and modify - ** the schema cookie. - */ - if( IsVirtual(pTab) ){ - sqlite3VdbeAddOp4(v, OP_VDestroy, iDb, 0, 0, pTab->zName, 0); - } - sqlite3VdbeAddOp4(v, OP_DropTable, iDb, 0, 0, pTab->zName, 0); - sqlite3ChangeCookie(pParse, iDb); - } - sqliteViewResetAll(db, iDb); + sqlite3CodeDropTable(pParse, pTab, iDb, isView); + } exit_drop_table: sqlite3SrcListDelete(db, pName); } @@ -81454,17 +81749,19 @@ */ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){ Table *pTab = pIndex->pTable; /* The table that is indexed */ int iTab = pParse->nTab++; /* Btree cursor used for pTab */ int iIdx = pParse->nTab++; /* Btree cursor used for pIndex */ - int iSorter = iTab; /* Cursor opened by OpenSorter (if in use) */ + int iSorter; /* Cursor opened by OpenSorter (if in use) */ int addr1; /* Address of top of loop */ int addr2; /* Address to jump to for next iteration */ int tnum; /* Root page of index */ Vdbe *v; /* Generate code into this virtual machine */ KeyInfo *pKey; /* KeyInfo for index */ +#ifdef SQLITE_OMIT_MERGE_SORT int regIdxKey; /* Registers containing the index key */ +#endif int regRecord; /* Register holding assemblied index record */ sqlite3 *db = pParse->db; /* The database connection */ int iDb = sqlite3SchemaToIndex(db, pIndex->pSchema); #ifndef SQLITE_OMIT_AUTHORIZATION @@ -81494,21 +81791,22 @@ #ifndef SQLITE_OMIT_MERGE_SORT /* Open the sorter cursor if we are to use one. */ iSorter = pParse->nTab++; sqlite3VdbeAddOp4(v, OP_SorterOpen, iSorter, 0, 0, (char*)pKey, P4_KEYINFO); +#else + iSorter = iTab; #endif /* Open the table. Loop through all rows of the table, inserting index ** records into the sorter. */ sqlite3OpenTable(pParse, iTab, iDb, pTab, OP_OpenRead); addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iTab, 0); - addr2 = addr1 + 1; regRecord = sqlite3GetTempReg(pParse); - regIdxKey = sqlite3GenerateIndexKey(pParse, pIndex, iTab, regRecord, 1); #ifndef SQLITE_OMIT_MERGE_SORT + sqlite3GenerateIndexKey(pParse, pIndex, iTab, regRecord, 1); sqlite3VdbeAddOp2(v, OP_SorterInsert, iSorter, regRecord); sqlite3VdbeAddOp2(v, OP_Next, iTab, addr1+1); sqlite3VdbeJumpHere(v, addr1); addr1 = sqlite3VdbeAddOp2(v, OP_SorterSort, iSorter, 0); if( pIndex->onError!=OE_None ){ @@ -81524,10 +81822,12 @@ } sqlite3VdbeAddOp2(v, OP_SorterData, iSorter, regRecord); sqlite3VdbeAddOp3(v, OP_IdxInsert, iIdx, regRecord, 1); sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); #else + regIdxKey = sqlite3GenerateIndexKey(pParse, pIndex, iTab, regRecord, 1); + addr2 = addr1 + 1; if( pIndex->onError!=OE_None ){ const int regRowid = regIdxKey + pIndex->nColumn; const int j2 = sqlite3VdbeCurrentAddr(v) + 2; void * const pRegKey = SQLITE_INT_TO_PTR(regIdxKey); @@ -81621,10 +81921,11 @@ ** before looking up the table. */ assert( pName1 && pName2 ); iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pName); if( iDb<0 ) goto exit_create_index; + assert( pName && pName->z ); #ifndef SQLITE_OMIT_TEMPDB /* If the index name was unqualified, check if the the table ** is a temp table. If so, set the database to 1. Do not do this ** if initialising a database schema. @@ -81648,10 +81949,11 @@ pTblName->a[0].zDatabase); if( !pTab || db->mallocFailed ) goto exit_create_index; assert( db->aDb[iDb].pSchema==pTab->pSchema ); }else{ assert( pName==0 ); + assert( pStart==0 ); pTab = pParse->pNewTable; if( !pTab ) goto exit_create_index; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); } pDb = &db->aDb[iDb]; @@ -81690,10 +81992,11 @@ ** own name. */ if( pName ){ zName = sqlite3NameFromToken(db, pName); if( zName==0 ) goto exit_create_index; + assert( pName->z!=0 ); if( SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){ goto exit_create_index; } if( !db->init.busy ){ if( sqlite3FindTable(db, zName, 0)!=0 ){ @@ -81769,24 +82072,24 @@ */ nName = sqlite3Strlen30(zName); nCol = pList->nExpr; pIndex = sqlite3DbMallocZero(db, sizeof(Index) + /* Index structure */ + sizeof(tRowcnt)*(nCol+1) + /* Index.aiRowEst */ sizeof(int)*nCol + /* Index.aiColumn */ - sizeof(int)*(nCol+1) + /* Index.aiRowEst */ sizeof(char *)*nCol + /* Index.azColl */ sizeof(u8)*nCol + /* Index.aSortOrder */ nName + 1 + /* Index.zName */ nExtra /* Collation sequence names */ ); if( db->mallocFailed ){ goto exit_create_index; } - pIndex->azColl = (char**)(&pIndex[1]); + pIndex->aiRowEst = (tRowcnt*)(&pIndex[1]); + pIndex->azColl = (char**)(&pIndex->aiRowEst[nCol+1]); pIndex->aiColumn = (int *)(&pIndex->azColl[nCol]); - pIndex->aiRowEst = (unsigned *)(&pIndex->aiColumn[nCol]); - pIndex->aSortOrder = (u8 *)(&pIndex->aiRowEst[nCol+1]); + pIndex->aSortOrder = (u8 *)(&pIndex->aiColumn[nCol]); pIndex->zName = (char *)(&pIndex->aSortOrder[nCol]); zExtra = (char *)(&pIndex->zName[nName+1]); memcpy(pIndex->zName, zName, nName+1); pIndex->pTable = pTab; pIndex->nColumn = pList->nExpr; @@ -82059,13 +82362,13 @@ ** Apart from that, we have little to go on besides intuition as to ** how aiRowEst[] should be initialized. The numbers generated here ** are based on typical values found in actual indices. */ SQLITE_PRIVATE void sqlite3DefaultRowEst(Index *pIdx){ - unsigned *a = pIdx->aiRowEst; + tRowcnt *a = pIdx->aiRowEst; int i; - unsigned n; + tRowcnt n; assert( a!=0 ); a[0] = pIdx->pTable->nRowEst; if( a[0]<10 ) a[0] = 10; n = 10; for(i=1; i<=pIdx->nColumn; i++){ @@ -82545,17 +82848,14 @@ /* ** Commit a transaction */ SQLITE_PRIVATE void sqlite3CommitTransaction(Parse *pParse){ - sqlite3 *db; Vdbe *v; assert( pParse!=0 ); - db = pParse->db; - assert( db!=0 ); -/* if( db->aDb[0].pBt==0 ) return; */ + assert( pParse->db!=0 ); if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, "COMMIT", 0, 0) ){ return; } v = sqlite3GetVdbe(pParse); if( v ){ @@ -82565,17 +82865,14 @@ /* ** Rollback a transaction */ SQLITE_PRIVATE void sqlite3RollbackTransaction(Parse *pParse){ - sqlite3 *db; Vdbe *v; assert( pParse!=0 ); - db = pParse->db; - assert( db!=0 ); -/* if( db->aDb[0].pBt==0 ) return; */ + assert( pParse->db!=0 ); if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, "ROLLBACK", 0, 0) ){ return; } v = sqlite3GetVdbe(pParse); if( v ){ @@ -84377,20 +84674,19 @@ /* Verify that the call to _bytes() does not invalidate the _text() pointer */ assert( z2==(char*)sqlite3_value_text(argv[0]) ); if( z2 ){ z1 = contextMalloc(context, ((i64)n)+1); if( z1 ){ - memcpy(z1, z2, n+1); - for(i=0; z1[i]; i++){ - z1[i] = (char)sqlite3Toupper(z1[i]); + for(i=0; i<n; i++){ + z1[i] = (char)sqlite3Toupper(z2[i]); } - sqlite3_result_text(context, z1, -1, sqlite3_free); + sqlite3_result_text(context, z1, n, sqlite3_free); } } } static void lowerFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ - u8 *z1; + char *z1; const char *z2; int i, n; UNUSED_PARAMETER(argc); z2 = (char*)sqlite3_value_text(argv[0]); n = sqlite3_value_bytes(argv[0]); @@ -84397,15 +84693,14 @@ /* Verify that the call to _bytes() does not invalidate the _text() pointer */ assert( z2==(char*)sqlite3_value_text(argv[0]) ); if( z2 ){ z1 = contextMalloc(context, ((i64)n)+1); if( z1 ){ - memcpy(z1, z2, n+1); - for(i=0; z1[i]; i++){ - z1[i] = sqlite3Tolower(z1[i]); + for(i=0; i<n; i++){ + z1[i] = sqlite3Tolower(z2[i]); } - sqlite3_result_text(context, (char *)z1, -1, sqlite3_free); + sqlite3_result_text(context, z1, n, sqlite3_free); } } } @@ -86778,10 +87073,11 @@ sqlite3SelectDelete(db, pSelect); if( db->mallocFailed==1 ){ fkTriggerDelete(db, pTrigger); return 0; } + assert( pStep!=0 ); switch( action ){ case OE_Restrict: pStep->op = TK_SELECT; break; @@ -88621,10 +88917,13 @@ */ if( (pParse->db->flags & SQLITE_ForeignKeys)!=0 && pDest->pFKey!=0 ){ return 0; } #endif + if( (pParse->db->flags & SQLITE_CountRows)!=0 ){ + return 0; + } /* If we get this far, it means either: ** ** * We can always do the transfer if the table contains an ** an integer primary key @@ -89698,11 +89997,11 @@ sqlite3_vfs *pVfs = db->pVfs; void *handle; int (*xInit)(sqlite3*,char**,const sqlite3_api_routines*); char *zErrmsg = 0; void **aHandle; - const int nMsg = 300; + int nMsg = 300 + sqlite3Strlen30(zFile); if( pzErrMsg ) *pzErrMsg = 0; /* Ticket #1863. To avoid a creating security problems for older ** applications that relink against newer versions of SQLite, the @@ -89735,10 +90034,11 @@ } xInit = (int(*)(sqlite3*,char**,const sqlite3_api_routines*)) sqlite3OsDlSym(pVfs, handle, zProc); if( xInit==0 ){ if( pzErrMsg ){ + nMsg += sqlite3Strlen30(zProc); *pzErrMsg = zErrmsg = sqlite3_malloc(nMsg); if( zErrmsg ){ sqlite3_snprintf(nMsg, zErrmsg, "no entry point [%s] in shared library [%s]", zProc,zFile); sqlite3OsDlError(pVfs, nMsg-1, zErrmsg); @@ -90420,11 +90720,11 @@ ){ int iReg; if( sqlite3ReadSchema(pParse) ) goto pragma_out; sqlite3CodeVerifySchema(pParse, iDb); iReg = ++pParse->nMem; - if( zLeft[0]=='p' ){ + if( sqlite3Tolower(zLeft[0])=='p' ){ sqlite3VdbeAddOp2(v, OP_Pagecount, iDb, iReg); }else{ sqlite3VdbeAddOp3(v, OP_MaxPgcnt, iDb, iReg, sqlite3Atoi(zRight)); } sqlite3VdbeAddOp2(v, OP_ResultRow, iReg, 1); @@ -90486,12 +90786,14 @@ */ if( sqlite3StrICmp(zLeft,"journal_mode")==0 ){ int eMode; /* One of the PAGER_JOURNALMODE_XXX symbols */ int ii; /* Loop counter */ - /* Force the schema to be loaded on all databases. This cases all - ** database files to be opened and the journal_modes set. */ + /* Force the schema to be loaded on all databases. This causes all + ** database files to be opened and the journal_modes set. This is + ** necessary because subsequent processing must know if the databases + ** are in WAL mode. */ if( sqlite3ReadSchema(pParse) ){ goto pragma_out; } sqlite3VdbeSetNumCols(v, 1); @@ -91031,11 +91333,11 @@ { OP_IfNeg, 1, 0, 0}, /* 1 */ { OP_String8, 0, 3, 0}, /* 2 */ { OP_ResultRow, 3, 1, 0}, }; - int isQuick = (zLeft[0]=='q'); + int isQuick = (sqlite3Tolower(zLeft[0])=='q'); /* Initialize the VDBE program */ if( sqlite3ReadSchema(pParse) ) goto pragma_out; pParse->nMem = 6; sqlite3VdbeSetNumCols(v, 1); @@ -92406,10 +92708,11 @@ Select standin; sqlite3 *db = pParse->db; pNew = sqlite3DbMallocZero(db, sizeof(*pNew) ); assert( db->mallocFailed || !pOffset || pLimit ); /* OFFSET implies LIMIT */ if( pNew==0 ){ + assert( db->mallocFailed ); pNew = &standin; memset(pNew, 0, sizeof(*pNew)); } if( pEList==0 ){ pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(db,TK_ALL,0)); @@ -92433,10 +92736,11 @@ if( pNew!=&standin ) sqlite3DbFree(db, pNew); pNew = 0; }else{ assert( pNew->pSrc!=0 || pParse->nErr>0 ); } + assert( pNew!=&standin ); return pNew; } /* ** Delete the given Select structure and all of its substructures. @@ -93611,11 +93915,14 @@ /* If the column contains an "AS <name>" phrase, use <name> as the name */ zName = sqlite3DbStrDup(db, zName); }else{ Expr *pColExpr = p; /* The expression that is the result column name */ Table *pTab; /* Table associated with this expression */ - while( pColExpr->op==TK_DOT ) pColExpr = pColExpr->pRight; + while( pColExpr->op==TK_DOT ){ + pColExpr = pColExpr->pRight; + assert( pColExpr!=0 ); + } if( pColExpr->op==TK_COLUMN && ALWAYS(pColExpr->pTab!=0) ){ /* For columns use the column name name */ int iCol = pColExpr->iColumn; pTab = pColExpr->pTab; if( iCol<0 ) iCol = pTab->iPKey; @@ -98609,10 +98916,11 @@ break; } } } for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ + assert( aRegIdx ); if( openAll || aRegIdx[i]>0 ){ KeyInfo *pKey = sqlite3IndexKeyinfo(pParse, pIdx); sqlite3VdbeAddOp4(v, OP_OpenWrite, iCur+i+1, pIdx->tnum, iDb, (char*)pKey, P4_KEYINFO_HANDOFF); assert( pParse->nTab>iCur+i+1 ); @@ -98782,10 +99090,11 @@ sqlite3VdbeAddOp2(v, OP_Goto, 0, addr); sqlite3VdbeJumpHere(v, addr); /* Close all tables */ for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ + assert( aRegIdx ); if( openAll || aRegIdx[i]>0 ){ sqlite3VdbeAddOp2(v, OP_Close, iCur+i+1, 0); } } sqlite3VdbeAddOp2(v, OP_Close, iCur, 0); @@ -98969,11 +99278,11 @@ if( SQLITE_OK!=sqlite3_prepare(db, zSql, -1, &pStmt, 0) ){ sqlite3SetString(pzErrMsg, db, sqlite3_errmsg(db)); return sqlite3_errcode(db); } VVA_ONLY( rc = ) sqlite3_step(pStmt); - assert( rc!=SQLITE_ROW ); + assert( rc!=SQLITE_ROW || (db->flags&SQLITE_CountRows) ); return vacuumFinalize(db, pStmt, pzErrMsg); } /* ** Execute zSql on database db. The statement returns exactly @@ -99187,17 +99496,15 @@ " WHERE type='view' OR type='trigger'" " OR (type='table' AND rootpage=0)" ); if( rc ) goto end_of_vacuum; - /* At this point, unless the main db was completely empty, there is now a - ** transaction open on the vacuum database, but not on the main database. - ** Open a btree level transaction on the main database. This allows a - ** call to sqlite3BtreeCopyFile(). The main database btree level - ** transaction is then committed, so the SQL level never knows it was - ** opened for writing. This way, the SQL transaction used to create the - ** temporary database never needs to be committed. + /* At this point, there is a write transaction open on both the + ** vacuum database and the main database. Assuming no error occurs, + ** both transactions are closed by this block - the main database + ** transaction by sqlite3BtreeCopyFile() and the other by an explicit + ** call to sqlite3BtreeCommit(). */ { u32 meta; int i; @@ -100457,25 +100764,35 @@ #define TERM_CODED 0x04 /* This term is already coded */ #define TERM_COPIED 0x08 /* Has a child */ #define TERM_ORINFO 0x10 /* Need to free the WhereTerm.u.pOrInfo object */ #define TERM_ANDINFO 0x20 /* Need to free the WhereTerm.u.pAndInfo obj */ #define TERM_OR_OK 0x40 /* Used during OR-clause processing */ -#ifdef SQLITE_ENABLE_STAT2 +#ifdef SQLITE_ENABLE_STAT3 # define TERM_VNULL 0x80 /* Manufactured x>NULL or x<=NULL term */ #else -# define TERM_VNULL 0x00 /* Disabled if not using stat2 */ +# define TERM_VNULL 0x00 /* Disabled if not using stat3 */ #endif /* ** An instance of the following structure holds all information about a ** WHERE clause. Mostly this is a container for one or more WhereTerms. +** +** Explanation of pOuter: For a WHERE clause of the form +** +** a AND ((b AND c) OR (d AND e)) AND f +** +** There are separate WhereClause objects for the whole clause and for +** the subclauses "(b AND c)" and "(d AND e)". The pOuter field of the +** subclauses points to the WhereClause object for the whole clause. */ struct WhereClause { Parse *pParse; /* The parser context */ WhereMaskSet *pMaskSet; /* Mapping of table cursor numbers to bitmasks */ Bitmask vmask; /* Bitmask identifying virtual table cursors */ + WhereClause *pOuter; /* Outer conjunction */ u8 op; /* Split operator. TK_AND or TK_OR */ + u16 wctrlFlags; /* Might include WHERE_AND_ONLY */ int nTerm; /* Number of terms */ int nSlot; /* Number of entries in a[] */ WhereTerm *a; /* Each a[] describes a term of the WHERE cluase */ #if defined(SQLITE_SMALL_STACK) WhereTerm aStatic[1]; /* Initial static space for a[] */ @@ -100600,18 +100917,21 @@ ** Initialize a preallocated WhereClause structure. */ static void whereClauseInit( WhereClause *pWC, /* The WhereClause to be initialized */ Parse *pParse, /* The parsing context */ - WhereMaskSet *pMaskSet /* Mapping from table cursor numbers to bitmasks */ + WhereMaskSet *pMaskSet, /* Mapping from table cursor numbers to bitmasks */ + u16 wctrlFlags /* Might include WHERE_AND_ONLY */ ){ pWC->pParse = pParse; pWC->pMaskSet = pMaskSet; + pWC->pOuter = 0; pWC->nTerm = 0; pWC->nSlot = ArraySize(pWC->aStatic); pWC->a = pWC->aStatic; pWC->vmask = 0; + pWC->wctrlFlags = wctrlFlags; } /* Forward reference */ static void whereClauseClear(WhereClause*); @@ -100923,40 +101243,42 @@ ){ WhereTerm *pTerm; int k; assert( iCur>=0 ); op &= WO_ALL; - for(pTerm=pWC->a, k=pWC->nTerm; k; k--, pTerm++){ - if( pTerm->leftCursor==iCur - && (pTerm->prereqRight & notReady)==0 - && pTerm->u.leftColumn==iColumn - && (pTerm->eOperator & op)!=0 - ){ - if( pIdx && pTerm->eOperator!=WO_ISNULL ){ - Expr *pX = pTerm->pExpr; - CollSeq *pColl; - char idxaff; - int j; - Parse *pParse = pWC->pParse; - - idxaff = pIdx->pTable->aCol[iColumn].affinity; - if( !sqlite3IndexAffinityOk(pX, idxaff) ) continue; - - /* Figure out the collation sequence required from an index for - ** it to be useful for optimising expression pX. Store this - ** value in variable pColl. - */ - assert(pX->pLeft); - pColl = sqlite3BinaryCompareCollSeq(pParse, pX->pLeft, pX->pRight); - assert(pColl || pParse->nErr); - - for(j=0; pIdx->aiColumn[j]!=iColumn; j++){ - if( NEVER(j>=pIdx->nColumn) ) return 0; - } - if( pColl && sqlite3StrICmp(pColl->zName, pIdx->azColl[j]) ) continue; - } - return pTerm; + for(; pWC; pWC=pWC->pOuter){ + for(pTerm=pWC->a, k=pWC->nTerm; k; k--, pTerm++){ + if( pTerm->leftCursor==iCur + && (pTerm->prereqRight & notReady)==0 + && pTerm->u.leftColumn==iColumn + && (pTerm->eOperator & op)!=0 + ){ + if( pIdx && pTerm->eOperator!=WO_ISNULL ){ + Expr *pX = pTerm->pExpr; + CollSeq *pColl; + char idxaff; + int j; + Parse *pParse = pWC->pParse; + + idxaff = pIdx->pTable->aCol[iColumn].affinity; + if( !sqlite3IndexAffinityOk(pX, idxaff) ) continue; + + /* Figure out the collation sequence required from an index for + ** it to be useful for optimising expression pX. Store this + ** value in variable pColl. + */ + assert(pX->pLeft); + pColl = sqlite3BinaryCompareCollSeq(pParse, pX->pLeft, pX->pRight); + assert(pColl || pParse->nErr); + + for(j=0; pIdx->aiColumn[j]!=iColumn; j++){ + if( NEVER(j>=pIdx->nColumn) ) return 0; + } + if( pColl && sqlite3StrICmp(pColl->zName, pIdx->azColl[j]) ) continue; + } + return pTerm; + } } } return 0; } @@ -101029,11 +101351,11 @@ int iCol = pRight->iColumn; pVal = sqlite3VdbeGetValue(pReprepare, iCol, SQLITE_AFF_NONE); if( pVal && sqlite3_value_type(pVal)==SQLITE_TEXT ){ z = (char *)sqlite3_value_text(pVal); } - sqlite3VdbeSetVarmask(pParse->pVdbe, iCol); /* IMP: R-23257-02778 */ + sqlite3VdbeSetVarmask(pParse->pVdbe, iCol); /* IMP: R-31526-56213 */ assert( pRight->op==TK_VARIABLE || pRight->op==TK_REGISTER ); }else if( op==TK_STRING ){ z = pRight->u.zToken; } if( z ){ @@ -101047,11 +101369,11 @@ pPrefix = sqlite3Expr(db, TK_STRING, z); if( pPrefix ) pPrefix->u.zToken[cnt] = 0; *ppPrefix = pPrefix; if( op==TK_VARIABLE ){ Vdbe *v = pParse->pVdbe; - sqlite3VdbeSetVarmask(v, pRight->iColumn); /* IMP: R-23257-02778 */ + sqlite3VdbeSetVarmask(v, pRight->iColumn); /* IMP: R-31526-56213 */ if( *pisComplete && pRight->u.zToken[1] ){ /* If the rhs of the LIKE expression is a variable, and the current ** value of the variable means there is no need to invoke the LIKE ** function, then no OP_Variable will be added to the program. ** This causes problems for the sqlite3_bind_parameter_name() @@ -101216,11 +101538,11 @@ assert( pExpr->op==TK_OR ); pTerm->u.pOrInfo = pOrInfo = sqlite3DbMallocZero(db, sizeof(*pOrInfo)); if( pOrInfo==0 ) return; pTerm->wtFlags |= TERM_ORINFO; pOrWc = &pOrInfo->wc; - whereClauseInit(pOrWc, pWC->pParse, pMaskSet); + whereClauseInit(pOrWc, pWC->pParse, pMaskSet, pWC->wctrlFlags); whereSplit(pOrWc, pExpr, TK_OR); exprAnalyzeAll(pSrc, pOrWc); if( db->mallocFailed ) return; assert( pOrWc->nTerm>=2 ); @@ -101243,13 +101565,14 @@ Bitmask b = 0; pOrTerm->u.pAndInfo = pAndInfo; pOrTerm->wtFlags |= TERM_ANDINFO; pOrTerm->eOperator = WO_AND; pAndWC = &pAndInfo->wc; - whereClauseInit(pAndWC, pWC->pParse, pMaskSet); + whereClauseInit(pAndWC, pWC->pParse, pMaskSet, pWC->wctrlFlags); whereSplit(pAndWC, pOrTerm->pExpr, TK_AND); exprAnalyzeAll(pSrc, pAndWC); + pAndWC->pOuter = pWC; testcase( db->mallocFailed ); if( !db->mallocFailed ){ for(j=0, pAndTerm=pAndWC->a; j<pAndWC->nTerm; j++, pAndTerm++){ assert( pAndTerm->pExpr ); if( allowedOp(pAndTerm->pExpr->op) ){ @@ -101679,12 +102002,12 @@ pNewTerm->prereqAll = pTerm->prereqAll; } } #endif /* SQLITE_OMIT_VIRTUALTABLE */ -#ifdef SQLITE_ENABLE_STAT2 - /* When sqlite_stat2 histogram data is available an operator of the +#ifdef SQLITE_ENABLE_STAT3 + /* When sqlite_stat3 histogram data is available an operator of the ** form "x IS NOT NULL" can sometimes be evaluated more efficiently ** as "x>NULL" if x is not an INTEGER PRIMARY KEY. So construct a ** virtual term of that form. ** ** Note that the virtual term must be tagged with TERM_VNULL. This @@ -101718,11 +102041,11 @@ pTerm->nChild = 1; pTerm->wtFlags |= TERM_COPIED; pNewTerm->prereqAll = pTerm->prereqAll; } } -#endif /* SQLITE_ENABLE_STAT2 */ +#endif /* SQLITE_ENABLE_STAT */ /* Prevent ON clause terms of a LEFT JOIN from being used to drive ** an index for tables to the left of the join. */ pTerm->prereqRight |= extraRight; @@ -102140,14 +102463,17 @@ const int iCur = pSrc->iCursor; /* The cursor of the table to be accessed */ const Bitmask maskSrc = getMask(pWC->pMaskSet, iCur); /* Bitmask for pSrc */ WhereTerm * const pWCEnd = &pWC->a[pWC->nTerm]; /* End of pWC->a[] */ WhereTerm *pTerm; /* A single term of the WHERE clause */ - /* No OR-clause optimization allowed if the INDEXED BY or NOT INDEXED clauses - ** are used */ + /* The OR-clause optimization is disallowed if the INDEXED BY or + ** NOT INDEXED clauses are used or if the WHERE_AND_ONLY bit is set. */ if( pSrc->notIndexed || pSrc->pIndex!=0 ){ return; + } + if( pWC->wctrlFlags & WHERE_AND_ONLY ){ + return; } /* Search the WHERE clause terms for a usable WO_OR term. */ for(pTerm=pWC->a; pTerm<pWCEnd; pTerm++){ if( pTerm->eOperator==WO_OR @@ -102172,10 +102498,11 @@ bestIndex(pParse, pAndWC, pSrc, notReady, notValid, 0, &sTermCost); }else if( pOrTerm->leftCursor==iCur ){ WhereClause tempWC; tempWC.pParse = pWC->pParse; tempWC.pMaskSet = pWC->pMaskSet; + tempWC.pOuter = pWC; tempWC.op = TK_AND; tempWC.a = pOrTerm; tempWC.nTerm = 1; bestIndex(pParse, &tempWC, pSrc, notReady, notValid, 0, &sTermCost); }else{ @@ -102766,71 +103093,89 @@ */ bestOrClauseIndex(pParse, pWC, pSrc, notReady, notValid, pOrderBy, pCost); } #endif /* SQLITE_OMIT_VIRTUALTABLE */ +#ifdef SQLITE_ENABLE_STAT3 /* -** Argument pIdx is a pointer to an index structure that has an array of -** SQLITE_INDEX_SAMPLES evenly spaced samples of the first indexed column -** stored in Index.aSample. These samples divide the domain of values stored -** the index into (SQLITE_INDEX_SAMPLES+1) regions. -** Region 0 contains all values less than the first sample value. Region -** 1 contains values between the first and second samples. Region 2 contains -** values between samples 2 and 3. And so on. Region SQLITE_INDEX_SAMPLES -** contains values larger than the last sample. -** -** If the index contains many duplicates of a single value, then it is -** possible that two or more adjacent samples can hold the same value. -** When that is the case, the smallest possible region code is returned -** when roundUp is false and the largest possible region code is returned -** when roundUp is true. -** -** If successful, this function determines which of the regions value -** pVal lies in, sets *piRegion to the region index (a value between 0 -** and SQLITE_INDEX_SAMPLES+1, inclusive) and returns SQLITE_OK. -** Or, if an OOM occurs while converting text values between encodings, -** SQLITE_NOMEM is returned and *piRegion is undefined. -*/ -#ifdef SQLITE_ENABLE_STAT2 -static int whereRangeRegion( +** Estimate the location of a particular key among all keys in an +** index. Store the results in aStat as follows: +** +** aStat[0] Est. number of rows less than pVal +** aStat[1] Est. number of rows equal to pVal +** +** Return SQLITE_OK on success. +*/ +static int whereKeyStats( Parse *pParse, /* Database connection */ Index *pIdx, /* Index to consider domain of */ sqlite3_value *pVal, /* Value to consider */ - int roundUp, /* Return largest valid region if true */ - int *piRegion /* OUT: Region of domain in which value lies */ + int roundUp, /* Round up if true. Round down if false */ + tRowcnt *aStat /* OUT: stats written here */ ){ + tRowcnt n; + IndexSample *aSample; + int i, eType; + int isEq = 0; + i64 v; + double r, rS; + assert( roundUp==0 || roundUp==1 ); - if( ALWAYS(pVal) ){ - IndexSample *aSample = pIdx->aSample; - int i = 0; - int eType = sqlite3_value_type(pVal); - - if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ - double r = sqlite3_value_double(pVal); - for(i=0; i<SQLITE_INDEX_SAMPLES; i++){ - if( aSample[i].eType==SQLITE_NULL ) continue; - if( aSample[i].eType>=SQLITE_TEXT ) break; - if( roundUp ){ - if( aSample[i].u.r>r ) break; - }else{ - if( aSample[i].u.r>=r ) break; - } - } - }else if( eType==SQLITE_NULL ){ - i = 0; - if( roundUp ){ - while( i<SQLITE_INDEX_SAMPLES && aSample[i].eType==SQLITE_NULL ) i++; - } - }else{ + assert( pIdx->nSample>0 ); + if( pVal==0 ) return SQLITE_ERROR; + n = pIdx->aiRowEst[0]; + aSample = pIdx->aSample; + eType = sqlite3_value_type(pVal); + + if( eType==SQLITE_INTEGER ){ + v = sqlite3_value_int64(pVal); + r = (i64)v; + for(i=0; i<pIdx->nSample; i++){ + if( aSample[i].eType==SQLITE_NULL ) continue; + if( aSample[i].eType>=SQLITE_TEXT ) break; + if( aSample[i].eType==SQLITE_INTEGER ){ + if( aSample[i].u.i>=v ){ + isEq = aSample[i].u.i==v; + break; + } + }else{ + assert( aSample[i].eType==SQLITE_FLOAT ); + if( aSample[i].u.r>=r ){ + isEq = aSample[i].u.r==r; + break; + } + } + } + }else if( eType==SQLITE_FLOAT ){ + r = sqlite3_value_double(pVal); + for(i=0; i<pIdx->nSample; i++){ + if( aSample[i].eType==SQLITE_NULL ) continue; + if( aSample[i].eType>=SQLITE_TEXT ) break; + if( aSample[i].eType==SQLITE_FLOAT ){ + rS = aSample[i].u.r; + }else{ + rS = aSample[i].u.i; + } + if( rS>=r ){ + isEq = rS==r; + break; + } + } + }else if( eType==SQLITE_NULL ){ + i = 0; + if( aSample[0].eType==SQLITE_NULL ) isEq = 1; + }else{ + assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB ); + for(i=0; i<pIdx->nSample; i++){ + if( aSample[i].eType==SQLITE_TEXT || aSample[i].eType==SQLITE_BLOB ){ + break; + } + } + if( i<pIdx->nSample ){ sqlite3 *db = pParse->db; CollSeq *pColl; const u8 *z; - int n; - - /* pVal comes from sqlite3ValueFromExpr() so the type cannot be NULL */ - assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB ); - if( eType==SQLITE_BLOB ){ z = (const u8 *)sqlite3_value_blob(pVal); pColl = db->pDfltColl; assert( pColl->enc==SQLITE_UTF8 ); }else{ @@ -102845,16 +103190,16 @@ return SQLITE_NOMEM; } assert( z && pColl && pColl->xCmp ); } n = sqlite3ValueBytes(pVal, pColl->enc); - - for(i=0; i<SQLITE_INDEX_SAMPLES; i++){ + + for(; i<pIdx->nSample; i++){ int c; int eSampletype = aSample[i].eType; - if( eSampletype==SQLITE_NULL || eSampletype<eType ) continue; - if( (eSampletype!=eType) ) break; + if( eSampletype<eType ) continue; + if( eSampletype!=eType ) break; #ifndef SQLITE_OMIT_UTF16 if( pColl->enc!=SQLITE_UTF8 ){ int nSample; char *zSample = sqlite3Utf8to16( db, pColl->enc, aSample[i].u.z, aSample[i].nByte, &nSample @@ -102868,20 +103213,51 @@ }else #endif { c = pColl->xCmp(pColl->pUser, aSample[i].nByte, aSample[i].u.z, n, z); } - if( c-roundUp>=0 ) break; + if( c>=0 ){ + if( c==0 ) isEq = 1; + break; + } } } + } - assert( i>=0 && i<=SQLITE_INDEX_SAMPLES ); - *piRegion = i; + /* At this point, aSample[i] is the first sample that is greater than + ** or equal to pVal. Or if i==pIdx->nSample, then all samples are less + ** than pVal. If aSample[i]==pVal, then isEq==1. + */ + if( isEq ){ + assert( i<pIdx->nSample ); + aStat[0] = aSample[i].nLt; + aStat[1] = aSample[i].nEq; + }else{ + tRowcnt iLower, iUpper, iGap; + if( i==0 ){ + iLower = 0; + iUpper = aSample[0].nLt; + }else{ + iUpper = i>=pIdx->nSample ? n : aSample[i].nLt; + iLower = aSample[i-1].nEq + aSample[i-1].nLt; + } + aStat[1] = pIdx->avgEq; + if( iLower>=iUpper ){ + iGap = 0; + }else{ + iGap = iUpper - iLower; + } + if( roundUp ){ + iGap = (iGap*2)/3; + }else{ + iGap = iGap/3; + } + aStat[0] = iLower + iGap; } return SQLITE_OK; } -#endif /* #ifdef SQLITE_ENABLE_STAT2 */ +#endif /* SQLITE_ENABLE_STAT3 */ /* ** If expression pExpr represents a literal value, set *pp to point to ** an sqlite3_value structure containing the same value, with affinity ** aff applied to it, before returning. It is the responsibility of the @@ -102895,11 +103271,11 @@ ** ** If neither of the above apply, set *pp to NULL. ** ** If an error occurs, return an error code. Otherwise, SQLITE_OK. */ -#ifdef SQLITE_ENABLE_STAT2 +#ifdef SQLITE_ENABLE_STAT3 static int valueFromExpr( Parse *pParse, Expr *pExpr, u8 aff, sqlite3_value **pp @@ -102906,11 +103282,11 @@ ){ if( pExpr->op==TK_VARIABLE || (pExpr->op==TK_REGISTER && pExpr->op2==TK_VARIABLE) ){ int iVar = pExpr->iColumn; - sqlite3VdbeSetVarmask(pParse->pVdbe, iVar); /* IMP: R-23257-02778 */ + sqlite3VdbeSetVarmask(pParse->pVdbe, iVar); /* IMP: R-31526-56213 */ *pp = sqlite3VdbeGetValue(pParse->pReprepare, iVar, aff); return SQLITE_OK; } return sqlite3ValueFromExpr(pParse->db, pExpr, SQLITE_UTF8, aff, pp); } @@ -102943,106 +103319,92 @@ ** ** ... FROM t1 WHERE a > ? AND a < ? ... ** ** then nEq should be passed 0. ** -** The returned value is an integer between 1 and 100, inclusive. A return -** value of 1 indicates that the proposed range scan is expected to visit -** approximately 1/100th (1%) of the rows selected by the nEq equality -** constraints (if any). A return value of 100 indicates that it is expected -** that the range scan will visit every row (100%) selected by the equality -** constraints. +** The returned value is an integer divisor to reduce the estimated +** search space. A return value of 1 means that range constraints are +** no help at all. A return value of 2 means range constraints are +** expected to reduce the search space by half. And so forth... ** -** In the absence of sqlite_stat2 ANALYZE data, each range inequality -** reduces the search space by 3/4ths. Hence a single constraint (x>?) -** results in a return of 25 and a range constraint (x>? AND x<?) results -** in a return of 6. +** In the absence of sqlite_stat3 ANALYZE data, each range inequality +** reduces the search space by a factor of 4. Hence a single constraint (x>?) +** results in a return of 4 and a range constraint (x>? AND x<?) results +** in a return of 16. */ static int whereRangeScanEst( Parse *pParse, /* Parsing & code generating context */ Index *p, /* The index containing the range-compared column; "x" */ int nEq, /* index into p->aCol[] of the range-compared column */ WhereTerm *pLower, /* Lower bound on the range. ex: "x>123" Might be NULL */ WhereTerm *pUpper, /* Upper bound on the range. ex: "x<455" Might be NULL */ - int *piEst /* OUT: Return value */ + double *pRangeDiv /* OUT: Reduce search space by this divisor */ ){ int rc = SQLITE_OK; -#ifdef SQLITE_ENABLE_STAT2 - - if( nEq==0 && p->aSample ){ - sqlite3_value *pLowerVal = 0; - sqlite3_value *pUpperVal = 0; - int iEst; - int iLower = 0; - int iUpper = SQLITE_INDEX_SAMPLES; - int roundUpUpper = 0; - int roundUpLower = 0; +#ifdef SQLITE_ENABLE_STAT3 + + if( nEq==0 && p->nSample ){ + sqlite3_value *pRangeVal; + tRowcnt iLower = 0; + tRowcnt iUpper = p->aiRowEst[0]; + tRowcnt a[2]; u8 aff = p->pTable->aCol[p->aiColumn[0]].affinity; if( pLower ){ Expr *pExpr = pLower->pExpr->pRight; - rc = valueFromExpr(pParse, pExpr, aff, &pLowerVal); + rc = valueFromExpr(pParse, pExpr, aff, &pRangeVal); assert( pLower->eOperator==WO_GT || pLower->eOperator==WO_GE ); - roundUpLower = (pLower->eOperator==WO_GT) ?1:0; + if( rc==SQLITE_OK + && whereKeyStats(pParse, p, pRangeVal, 0, a)==SQLITE_OK + ){ + iLower = a[0]; + if( pLower->eOperator==WO_GT ) iLower += a[1]; + } + sqlite3ValueFree(pRangeVal); } if( rc==SQLITE_OK && pUpper ){ Expr *pExpr = pUpper->pExpr->pRight; - rc = valueFromExpr(pParse, pExpr, aff, &pUpperVal); + rc = valueFromExpr(pParse, pExpr, aff, &pRangeVal); assert( pUpper->eOperator==WO_LT || pUpper->eOperator==WO_LE ); - roundUpUpper = (pUpper->eOperator==WO_LE) ?1:0; - } - - if( rc!=SQLITE_OK || (pLowerVal==0 && pUpperVal==0) ){ - sqlite3ValueFree(pLowerVal); - sqlite3ValueFree(pUpperVal); - goto range_est_fallback; - }else if( pLowerVal==0 ){ - rc = whereRangeRegion(pParse, p, pUpperVal, roundUpUpper, &iUpper); - if( pLower ) iLower = iUpper/2; - }else if( pUpperVal==0 ){ - rc = whereRangeRegion(pParse, p, pLowerVal, roundUpLower, &iLower); - if( pUpper ) iUpper = (iLower + SQLITE_INDEX_SAMPLES + 1)/2; - }else{ - rc = whereRangeRegion(pParse, p, pUpperVal, roundUpUpper, &iUpper); - if( rc==SQLITE_OK ){ - rc = whereRangeRegion(pParse, p, pLowerVal, roundUpLower, &iLower); - } - } - WHERETRACE(("range scan regions: %d..%d\n", iLower, iUpper)); - - iEst = iUpper - iLower; - testcase( iEst==SQLITE_INDEX_SAMPLES ); - assert( iEst<=SQLITE_INDEX_SAMPLES ); - if( iEst<1 ){ - *piEst = 50/SQLITE_INDEX_SAMPLES; - }else{ - *piEst = (iEst*100)/SQLITE_INDEX_SAMPLES; - } - sqlite3ValueFree(pLowerVal); - sqlite3ValueFree(pUpperVal); - return rc; - } -range_est_fallback: + if( rc==SQLITE_OK + && whereKeyStats(pParse, p, pRangeVal, 1, a)==SQLITE_OK + ){ + iUpper = a[0]; + if( pUpper->eOperator==WO_LE ) iUpper += a[1]; + } + sqlite3ValueFree(pRangeVal); + } + if( rc==SQLITE_OK ){ + if( iUpper<=iLower ){ + *pRangeDiv = (double)p->aiRowEst[0]; + }else{ + *pRangeDiv = (double)p->aiRowEst[0]/(double)(iUpper - iLower); + } + WHERETRACE(("range scan regions: %u..%u div=%g\n", + (u32)iLower, (u32)iUpper, *pRangeDiv)); + return SQLITE_OK; + } + } #else UNUSED_PARAMETER(pParse); UNUSED_PARAMETER(p); UNUSED_PARAMETER(nEq); #endif assert( pLower || pUpper ); - *piEst = 100; - if( pLower && (pLower->wtFlags & TERM_VNULL)==0 ) *piEst /= 4; - if( pUpper ) *piEst /= 4; + *pRangeDiv = (double)1; + if( pLower && (pLower->wtFlags & TERM_VNULL)==0 ) *pRangeDiv *= (double)4; + if( pUpper ) *pRangeDiv *= (double)4; return rc; } -#ifdef SQLITE_ENABLE_STAT2 +#ifdef SQLITE_ENABLE_STAT3 /* ** Estimate the number of rows that will be returned based on ** an equality constraint x=VALUE and where that VALUE occurs in ** the histogram data. This only works when x is the left-most -** column of an index and sqlite_stat2 histogram data is available +** column of an index and sqlite_stat3 histogram data is available ** for that index. When pExpr==NULL that means the constraint is ** "x IS NULL" instead of "x=VALUE". ** ** Write the estimated row count into *pnRow and return SQLITE_OK. ** If unable to make an estimate, leave *pnRow unchanged and return @@ -103058,44 +103420,36 @@ Index *p, /* The index whose left-most column is pTerm */ Expr *pExpr, /* Expression for VALUE in the x=VALUE constraint */ double *pnRow /* Write the revised row estimate here */ ){ sqlite3_value *pRhs = 0; /* VALUE on right-hand side of pTerm */ - int iLower, iUpper; /* Range of histogram regions containing pRhs */ u8 aff; /* Column affinity */ int rc; /* Subfunction return code */ - double nRowEst; /* New estimate of the number of rows */ + tRowcnt a[2]; /* Statistics */ assert( p->aSample!=0 ); + assert( p->nSample>0 ); aff = p->pTable->aCol[p->aiColumn[0]].affinity; if( pExpr ){ rc = valueFromExpr(pParse, pExpr, aff, &pRhs); if( rc ) goto whereEqualScanEst_cancel; }else{ pRhs = sqlite3ValueNew(pParse->db); } if( pRhs==0 ) return SQLITE_NOTFOUND; - rc = whereRangeRegion(pParse, p, pRhs, 0, &iLower); - if( rc ) goto whereEqualScanEst_cancel; - rc = whereRangeRegion(pParse, p, pRhs, 1, &iUpper); - if( rc ) goto whereEqualScanEst_cancel; - WHERETRACE(("equality scan regions: %d..%d\n", iLower, iUpper)); - if( iLower>=iUpper ){ - nRowEst = p->aiRowEst[0]/(SQLITE_INDEX_SAMPLES*2); - if( nRowEst<*pnRow ) *pnRow = nRowEst; - }else{ - nRowEst = (iUpper-iLower)*p->aiRowEst[0]/SQLITE_INDEX_SAMPLES; - *pnRow = nRowEst; - } - + rc = whereKeyStats(pParse, p, pRhs, 0, a); + if( rc==SQLITE_OK ){ + WHERETRACE(("equality scan regions: %d\n", (int)a[1])); + *pnRow = a[1]; + } whereEqualScanEst_cancel: sqlite3ValueFree(pRhs); return rc; } -#endif /* defined(SQLITE_ENABLE_STAT2) */ +#endif /* defined(SQLITE_ENABLE_STAT3) */ -#ifdef SQLITE_ENABLE_STAT2 +#ifdef SQLITE_ENABLE_STAT3 /* ** Estimate the number of rows that will be returned based on ** an IN constraint where the right-hand side of the IN operator ** is a list of values. Example: ** @@ -103114,64 +103468,29 @@ Parse *pParse, /* Parsing & code generating context */ Index *p, /* The index whose left-most column is pTerm */ ExprList *pList, /* The value list on the RHS of "x IN (v1,v2,v3,...)" */ double *pnRow /* Write the revised row estimate here */ ){ - sqlite3_value *pVal = 0; /* One value from list */ - int iLower, iUpper; /* Range of histogram regions containing pRhs */ - u8 aff; /* Column affinity */ - int rc = SQLITE_OK; /* Subfunction return code */ - double nRowEst; /* New estimate of the number of rows */ - int nSpan = 0; /* Number of histogram regions spanned */ - int nSingle = 0; /* Histogram regions hit by a single value */ - int nNotFound = 0; /* Count of values that are not constants */ - int i; /* Loop counter */ - u8 aSpan[SQLITE_INDEX_SAMPLES+1]; /* Histogram regions that are spanned */ - u8 aSingle[SQLITE_INDEX_SAMPLES+1]; /* Histogram regions hit once */ + int rc = SQLITE_OK; /* Subfunction return code */ + double nEst; /* Number of rows for a single term */ + double nRowEst = (double)0; /* New estimate of the number of rows */ + int i; /* Loop counter */ assert( p->aSample!=0 ); - aff = p->pTable->aCol[p->aiColumn[0]].affinity; - memset(aSpan, 0, sizeof(aSpan)); - memset(aSingle, 0, sizeof(aSingle)); - for(i=0; i<pList->nExpr; i++){ - sqlite3ValueFree(pVal); - rc = valueFromExpr(pParse, pList->a[i].pExpr, aff, &pVal); - if( rc ) break; - if( pVal==0 || sqlite3_value_type(pVal)==SQLITE_NULL ){ - nNotFound++; - continue; - } - rc = whereRangeRegion(pParse, p, pVal, 0, &iLower); - if( rc ) break; - rc = whereRangeRegion(pParse, p, pVal, 1, &iUpper); - if( rc ) break; - if( iLower>=iUpper ){ - aSingle[iLower] = 1; - }else{ - assert( iLower>=0 && iUpper<=SQLITE_INDEX_SAMPLES ); - while( iLower<iUpper ) aSpan[iLower++] = 1; - } + for(i=0; rc==SQLITE_OK && i<pList->nExpr; i++){ + nEst = p->aiRowEst[0]; + rc = whereEqualScanEst(pParse, p, pList->a[i].pExpr, &nEst); + nRowEst += nEst; } if( rc==SQLITE_OK ){ - for(i=nSpan=0; i<=SQLITE_INDEX_SAMPLES; i++){ - if( aSpan[i] ){ - nSpan++; - }else if( aSingle[i] ){ - nSingle++; - } - } - nRowEst = (nSpan*2+nSingle)*p->aiRowEst[0]/(2*SQLITE_INDEX_SAMPLES) - + nNotFound*p->aiRowEst[1]; if( nRowEst > p->aiRowEst[0] ) nRowEst = p->aiRowEst[0]; *pnRow = nRowEst; - WHERETRACE(("IN row estimate: nSpan=%d, nSingle=%d, nNotFound=%d, est=%g\n", - nSpan, nSingle, nNotFound, nRowEst)); + WHERETRACE(("IN row estimate: est=%g\n", nRowEst)); } - sqlite3ValueFree(pVal); return rc; } -#endif /* defined(SQLITE_ENABLE_STAT2) */ +#endif /* defined(SQLITE_ENABLE_STAT3) */ /* ** Find the best query plan for accessing a particular table. Write the ** best query plan and its cost into the WhereCost object supplied as the @@ -103214,11 +103533,11 @@ Index *pProbe; /* An index we are evaluating */ Index *pIdx; /* Copy of pProbe, or zero for IPK index */ int eqTermMask; /* Current mask of valid equality operators */ int idxEqTermMask; /* Index mask of valid equality operators */ Index sPk; /* A fake index object for the primary key */ - unsigned int aiRowEstPk[2]; /* The aiRowEst[] value for the sPk index */ + tRowcnt aiRowEstPk[2]; /* The aiRowEst[] value for the sPk index */ int aiColumnPk = -1; /* The aColumn[] value for the sPk index */ int wsFlagMask; /* Allowed flags in pCost->plan.wsFlag */ /* Initialize the cost to a worst-case value */ memset(pCost, 0, sizeof(*pCost)); @@ -103269,14 +103588,14 @@ } /* Loop over all indices looking for the best one to use */ for(; pProbe; pIdx=pProbe=pProbe->pNext){ - const unsigned int * const aiRowEst = pProbe->aiRowEst; + const tRowcnt * const aiRowEst = pProbe->aiRowEst; double cost; /* Cost of using pProbe */ double nRow; /* Estimated number of rows in result set */ - double log10N; /* base-10 logarithm of nRow (inexact) */ + double log10N = (double)1; /* base-10 logarithm of nRow (inexact) */ int rev; /* True to scan in reverse order */ int wsFlags = 0; Bitmask used = 0; /* The following variables are populated based on the properties of @@ -103312,18 +103631,16 @@ ** Set to true if there was at least one "x IN (SELECT ...)" term used ** in determining the value of nInMul. Note that the RHS of the ** IN operator must be a SELECT, not a value list, for this variable ** to be true. ** - ** estBound: - ** An estimate on the amount of the table that must be searched. A - ** value of 100 means the entire table is searched. Range constraints - ** might reduce this to a value less than 100 to indicate that only - ** a fraction of the table needs searching. In the absence of - ** sqlite_stat2 ANALYZE data, a single inequality reduces the search - ** space to 1/4rd its original size. So an x>? constraint reduces - ** estBound to 25. Two constraints (x>? AND x<?) reduce estBound to 6. + ** rangeDiv: + ** An estimate of a divisor by which to reduce the search space due + ** to inequality constraints. In the absence of sqlite_stat3 ANALYZE + ** data, a single inequality reduces the search space to 1/4rd its + ** original size (rangeDiv==4). Two inequalities reduce the search + ** space to 1/16th of its original size (rangeDiv==16). ** ** bSort: ** Boolean. True if there is an ORDER BY clause that will require an ** external sort (i.e. scanning the index being evaluated will not ** correctly order records). @@ -103344,26 +103661,27 @@ ** SELECT a, b, c FROM tbl WHERE a = 1; */ int nEq; /* Number of == or IN terms matching index */ int bInEst = 0; /* True if "x IN (SELECT...)" seen */ int nInMul = 1; /* Number of distinct equalities to lookup */ - int estBound = 100; /* Estimated reduction in search space */ + double rangeDiv = (double)1; /* Estimated reduction in search space */ int nBound = 0; /* Number of range constraints seen */ int bSort = !!pOrderBy; /* True if external sort required */ int bDist = !!pDistinct; /* True if index cannot help with DISTINCT */ int bLookup = 0; /* True if not a covering index */ WhereTerm *pTerm; /* A single term of the WHERE clause */ -#ifdef SQLITE_ENABLE_STAT2 +#ifdef SQLITE_ENABLE_STAT3 WhereTerm *pFirstTerm = 0; /* First term matching the index */ #endif /* Determine the values of nEq and nInMul */ for(nEq=0; nEq<pProbe->nColumn; nEq++){ int j = pProbe->aiColumn[nEq]; pTerm = findTerm(pWC, iCur, j, notReady, eqTermMask, pIdx); if( pTerm==0 ) break; wsFlags |= (WHERE_COLUMN_EQ|WHERE_ROWID_EQ); + testcase( pTerm->pWC!=pWC ); if( pTerm->eOperator & WO_IN ){ Expr *pExpr = pTerm->pExpr; wsFlags |= WHERE_COLUMN_IN; if( ExprHasProperty(pExpr, EP_xIsSelect) ){ /* "x IN (SELECT ...)": Assume the SELECT returns 25 rows */ @@ -103374,32 +103692,34 @@ nInMul *= pExpr->x.pList->nExpr; } }else if( pTerm->eOperator & WO_ISNULL ){ wsFlags |= WHERE_COLUMN_NULL; } -#ifdef SQLITE_ENABLE_STAT2 +#ifdef SQLITE_ENABLE_STAT3 if( nEq==0 && pProbe->aSample ) pFirstTerm = pTerm; #endif used |= pTerm->prereqRight; } - /* Determine the value of estBound. */ + /* Determine the value of rangeDiv */ if( nEq<pProbe->nColumn && pProbe->bUnordered==0 ){ int j = pProbe->aiColumn[nEq]; if( findTerm(pWC, iCur, j, notReady, WO_LT|WO_LE|WO_GT|WO_GE, pIdx) ){ WhereTerm *pTop = findTerm(pWC, iCur, j, notReady, WO_LT|WO_LE, pIdx); WhereTerm *pBtm = findTerm(pWC, iCur, j, notReady, WO_GT|WO_GE, pIdx); - whereRangeScanEst(pParse, pProbe, nEq, pBtm, pTop, &estBound); + whereRangeScanEst(pParse, pProbe, nEq, pBtm, pTop, &rangeDiv); if( pTop ){ nBound = 1; wsFlags |= WHERE_TOP_LIMIT; used |= pTop->prereqRight; + testcase( pTop->pWC!=pWC ); } if( pBtm ){ nBound++; wsFlags |= WHERE_BTM_LIMIT; used |= pBtm->prereqRight; + testcase( pBtm->pWC!=pWC ); } wsFlags |= (WHERE_COLUMN_RANGE|WHERE_ROWID_RANGE); } }else if( pProbe->onError!=OE_None ){ testcase( wsFlags & WHERE_COLUMN_IN ); @@ -103458,32 +103778,34 @@ if( bInEst && nRow*2>aiRowEst[0] ){ nRow = aiRowEst[0]/2; nInMul = (int)(nRow / aiRowEst[nEq]); } -#ifdef SQLITE_ENABLE_STAT2 +#ifdef SQLITE_ENABLE_STAT3 /* If the constraint is of the form x=VALUE or x IN (E1,E2,...) ** and we do not think that values of x are unique and if histogram ** data is available for column x, then it might be possible ** to get a better estimate on the number of rows based on ** VALUE and how common that value is according to the histogram. */ if( nRow>(double)1 && nEq==1 && pFirstTerm!=0 && aiRowEst[1]>1 ){ + assert( (pFirstTerm->eOperator & (WO_EQ|WO_ISNULL|WO_IN))!=0 ); if( pFirstTerm->eOperator & (WO_EQ|WO_ISNULL) ){ testcase( pFirstTerm->eOperator==WO_EQ ); testcase( pFirstTerm->eOperator==WO_ISNULL ); whereEqualScanEst(pParse, pProbe, pFirstTerm->pExpr->pRight, &nRow); - }else if( pFirstTerm->eOperator==WO_IN && bInEst==0 ){ + }else if( bInEst==0 ){ + assert( pFirstTerm->eOperator==WO_IN ); whereInScanEst(pParse, pProbe, pFirstTerm->pExpr->x.pList, &nRow); } } -#endif /* SQLITE_ENABLE_STAT2 */ +#endif /* SQLITE_ENABLE_STAT3 */ /* Adjust the number of output rows and downward to reflect rows ** that are excluded by range constraints. */ - nRow = (nRow * (double)estBound) / (double)100; + nRow = nRow/rangeDiv; if( nRow<1 ) nRow = 1; /* Experiments run on real SQLite databases show that the time needed ** to do a binary search to locate a row in a table or index is roughly ** log10(N) times the time to move from one row to the next row within @@ -103608,14 +103930,14 @@ if( nRow<2 ) nRow = 2; } WHERETRACE(( - "%s(%s): nEq=%d nInMul=%d estBound=%d bSort=%d bLookup=%d wsFlags=0x%x\n" + "%s(%s): nEq=%d nInMul=%d rangeDiv=%d bSort=%d bLookup=%d wsFlags=0x%x\n" " notReady=0x%llx log10N=%.1f nRow=%.1f cost=%.1f used=0x%llx\n", pSrc->pTab->zName, (pIdx ? pIdx->zName : "ipk"), - nEq, nInMul, estBound, bSort, bLookup, wsFlags, + nEq, nInMul, (int)rangeDiv, bSort, bLookup, wsFlags, notReady, log10N, nRow, cost, used )); /* If this index is the best we have seen so far, then record this ** index and its cost in the pCost structure. @@ -104115,11 +104437,12 @@ */ static Bitmask codeOneLoopStart( WhereInfo *pWInfo, /* Complete information about the WHERE clause */ int iLevel, /* Which level of pWInfo->a[] should be coded */ u16 wctrlFlags, /* One of the WHERE_* flags defined in sqliteInt.h */ - Bitmask notReady /* Which tables are currently available */ + Bitmask notReady, /* Which tables are currently available */ + Expr *pWhere /* Complete WHERE clause */ ){ int j, k; /* Loop counters */ int iCur; /* The VDBE cursor for the table */ int addrNxt; /* Where to jump to continue with the next IN case */ int omitTable; /* True if we use the index only */ @@ -104597,11 +104920,12 @@ int regRowset = 0; /* Register for RowSet object */ int regRowid = 0; /* Register holding rowid */ int iLoopBody = sqlite3VdbeMakeLabel(v); /* Start of loop body */ int iRetInit; /* Address of regReturn init */ int untestedTerms = 0; /* Some terms not completely tested */ - int ii; + int ii; /* Loop counter */ + Expr *pAndExpr = 0; /* An ".. AND (...)" expression */ pTerm = pLevel->plan.u.pTerm; assert( pTerm!=0 ); assert( pTerm->eOperator==WO_OR ); assert( (pTerm->wtFlags & TERM_ORINFO)!=0 ); @@ -104646,18 +104970,33 @@ regRowset = ++pParse->nMem; regRowid = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_Null, 0, regRowset); } iRetInit = sqlite3VdbeAddOp2(v, OP_Integer, 0, regReturn); + + /* If the original WHERE clause is z of the form: (x1 OR x2 OR ...) AND y + ** Then for every term xN, evaluate as the subexpression: xN AND z + ** That way, terms in y that are factored into the disjunction will + ** be picked up by the recursive calls to sqlite3WhereBegin() below. + */ + if( pWC->nTerm>1 ){ + pAndExpr = sqlite3ExprAlloc(pParse->db, TK_AND, 0, 0); + pAndExpr->pRight = pWhere; + } for(ii=0; ii<pOrWc->nTerm; ii++){ WhereTerm *pOrTerm = &pOrWc->a[ii]; if( pOrTerm->leftCursor==iCur || pOrTerm->eOperator==WO_AND ){ WhereInfo *pSubWInfo; /* Info for single OR-term scan */ + Expr *pOrExpr = pOrTerm->pExpr; + if( pAndExpr ){ + pAndExpr->pLeft = pOrExpr; + pOrExpr = pAndExpr; + } /* Loop through table entries that match term pOrTerm. */ - pSubWInfo = sqlite3WhereBegin(pParse, pOrTab, pOrTerm->pExpr, 0, 0, - WHERE_OMIT_OPEN | WHERE_OMIT_CLOSE | + pSubWInfo = sqlite3WhereBegin(pParse, pOrTab, pOrExpr, 0, 0, + WHERE_OMIT_OPEN_CLOSE | WHERE_AND_ONLY | WHERE_FORCE_TABLE | WHERE_ONETABLE_ONLY); if( pSubWInfo ){ explainOneScan( pParse, pOrTab, &pSubWInfo->a[0], iLevel, pLevel->iFrom, 0 ); @@ -104681,10 +105020,11 @@ /* Finish the loop through table entries that match term pOrTerm. */ sqlite3WhereEnd(pSubWInfo); } } } + sqlite3DbFree(pParse->db, pAndExpr); sqlite3VdbeChangeP1(v, iRetInit, sqlite3VdbeCurrentAddr(v)); sqlite3VdbeAddOp2(v, OP_Goto, 0, pLevel->addrBrk); sqlite3VdbeResolveLabel(v, iLoopBody); if( pWInfo->nLevel>1 ) sqlite3StackFree(pParse->db, pOrTab); @@ -104962,11 +105302,11 @@ /* Split the WHERE clause into separate subexpressions where each ** subexpression is separated by an AND operator. */ initMaskSet(pMaskSet); - whereClauseInit(pWC, pParse, pMaskSet); + whereClauseInit(pWC, pParse, pMaskSet, wctrlFlags); sqlite3ExprCodeConstants(pParse, pWhere); whereSplit(pWC, pWhere, TK_AND); /* IMP: R-15842-53296 */ /* Special case: a WHERE clause that is constant. Evaluate the ** expression and either jump over all of the code or fall thru. @@ -105201,11 +105541,12 @@ assert( bestJ>=0 ); assert( notReady & getMask(pMaskSet, pTabList->a[bestJ].iCursor) ); WHERETRACE(("*** Optimizer selects table %d for loop %d" " with cost=%g and nRow=%g\n", bestJ, pLevel-pWInfo->a, bestPlan.rCost, bestPlan.plan.nRow)); - if( (bestPlan.plan.wsFlags & WHERE_ORDERBY)!=0 ){ + /* The ALWAYS() that follows was added to hush up clang scan-build */ + if( (bestPlan.plan.wsFlags & WHERE_ORDERBY)!=0 && ALWAYS(ppOrderBy) ){ *ppOrderBy = 0; } if( (bestPlan.plan.wsFlags & WHERE_DISTINCT)!=0 ){ assert( pWInfo->eDistinct==0 ); pWInfo->eDistinct = WHERE_DISTINCT_ORDERED; @@ -105290,11 +105631,11 @@ int iCur = pTabItem->iCursor; sqlite3VdbeAddOp4(v, OP_VOpen, iCur, 0, 0, pVTab, P4_VTAB); }else #endif if( (pLevel->plan.wsFlags & WHERE_IDX_ONLY)==0 - && (wctrlFlags & WHERE_OMIT_OPEN)==0 ){ + && (wctrlFlags & WHERE_OMIT_OPEN_CLOSE)==0 ){ int op = pWInfo->okOnePass ? OP_OpenWrite : OP_OpenRead; sqlite3OpenTable(pParse, pTabItem->iCursor, iDb, pTab, op); testcase( pTab->nCol==BMS-1 ); testcase( pTab->nCol==BMS ); if( !pWInfo->okOnePass && pTab->nCol<BMS ){ @@ -105335,11 +105676,11 @@ */ notReady = ~(Bitmask)0; for(i=0; i<nTabList; i++){ pLevel = &pWInfo->a[i]; explainOneScan(pParse, pTabList, pLevel, i, pLevel->iFrom, wctrlFlags); - notReady = codeOneLoopStart(pWInfo, i, wctrlFlags, notReady); + notReady = codeOneLoopStart(pWInfo, i, wctrlFlags, notReady, pWhere); pWInfo->iContinue = pLevel->addrCont; } #ifdef SQLITE_TEST /* For testing and debugging use only */ /* Record in the query plan information about the current table @@ -105470,11 +105811,11 @@ struct SrcList_item *pTabItem = &pTabList->a[pLevel->iFrom]; Table *pTab = pTabItem->pTab; assert( pTab!=0 ); if( (pTab->tabFlags & TF_Ephemeral)==0 && pTab->pSelect==0 - && (pWInfo->wctrlFlags & WHERE_OMIT_CLOSE)==0 + && (pWInfo->wctrlFlags & WHERE_OMIT_OPEN_CLOSE)==0 ){ int ws = pLevel->plan.wsFlags; if( !pWInfo->okOnePass && (ws & WHERE_IDX_ONLY)==0 ){ sqlite3VdbeAddOp1(v, OP_Close, pTabItem->iCursor); } @@ -108817,11 +109158,13 @@ sqlite3ParserTOKENTYPE yyminor /* The value for the token */ sqlite3ParserARG_PDECL /* Optional %extra_argument parameter */ ){ YYMINORTYPE yyminorunion; int yyact; /* The parser action. */ +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) int yyendofinput; /* True if we are at the end of input */ +#endif #ifdef YYERRORSYMBOL int yyerrorhit = 0; /* True if yymajor has invoked an error */ #endif yyParser *yypParser; /* The parser */ @@ -108840,11 +109183,13 @@ yypParser->yyerrcnt = -1; yypParser->yystack[0].stateno = 0; yypParser->yystack[0].major = 0; } yyminorunion.yy0 = yyminor; +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) yyendofinput = (yymajor==0); +#endif sqlite3ParserARG_STORE; #ifndef NDEBUG if( yyTraceFILE ){ fprintf(yyTraceFILE,"%sInput %s\n",yyTracePrompt,yyTokenName[yymajor]); @@ -108852,11 +109197,10 @@ #endif do{ yyact = yy_find_shift_action(yypParser,(YYCODETYPE)yymajor); if( yyact<YYNSTATE ){ - assert( !yyendofinput ); /* Impossible to shift the $ token */ yy_shift(yypParser,yyact,yymajor,&yyminorunion); yypParser->yyerrcnt--; yymajor = YYNOCODE; }else if( yyact < YYNSTATE + YYNRULE ){ yy_reduce(yypParser,yyact-YYNSTATE); @@ -110244,11 +110588,11 @@ ** ** * Recursive calls to this routine from thread X return immediately ** without blocking. */ SQLITE_API int sqlite3_initialize(void){ - sqlite3_mutex *pMaster; /* The main static mutex */ + MUTEX_LOGIC( sqlite3_mutex *pMaster; ) /* The main static mutex */ int rc; /* Result code */ #ifdef SQLITE_OMIT_WSD rc = sqlite3_wsd_init(4096, 24); if( rc!=SQLITE_OK ){ @@ -110278,11 +110622,11 @@ ** This operation is protected by the STATIC_MASTER mutex. Note that ** MutexAlloc() is called for a static mutex prior to initializing the ** malloc subsystem - this implies that the allocation of a static ** mutex must not require support from the malloc subsystem. */ - pMaster = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); + MUTEX_LOGIC( pMaster = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); ) sqlite3_mutex_enter(pMaster); sqlite3GlobalConfig.isMutexInit = 1; if( !sqlite3GlobalConfig.isMallocInit ){ rc = sqlite3MallocInit(); } @@ -111352,17 +111696,17 @@ sqlite3 *db, const char *zName, int nArg ){ int nName = sqlite3Strlen30(zName); - int rc; + int rc = SQLITE_OK; sqlite3_mutex_enter(db->mutex); if( sqlite3FindFunction(db, zName, nName, nArg, SQLITE_UTF8, 0)==0 ){ - sqlite3CreateFunc(db, zName, nArg, SQLITE_UTF8, - 0, sqlite3InvalidFunction, 0, 0, 0); + rc = sqlite3CreateFunc(db, zName, nArg, SQLITE_UTF8, + 0, sqlite3InvalidFunction, 0, 0, 0); } - rc = sqlite3ApiExit(db, SQLITE_OK); + rc = sqlite3ApiExit(db, rc); sqlite3_mutex_leave(db->mutex); return rc; } #ifndef SQLITE_OMIT_TRACE @@ -112420,10 +112764,11 @@ if( db ){ assert( db->mutex!=0 || isThreadsafe==0 || sqlite3GlobalConfig.bFullMutex==0 ); sqlite3_mutex_leave(db->mutex); } rc = sqlite3_errcode(db); + assert( db!=0 || rc==SQLITE_NOMEM ); if( rc==SQLITE_NOMEM ){ sqlite3_close(db); db = 0; }else if( rc!=SQLITE_OK ){ db->magic = SQLITE_MAGIC_SICK; @@ -114148,10 +114493,17 @@ #else # define TESTONLY(X) #endif #endif /* SQLITE_AMALGAMATION */ + +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE int sqlite3Fts3Corrupt(void); +# define FTS_CORRUPT_VTAB sqlite3Fts3Corrupt() +#else +# define FTS_CORRUPT_VTAB SQLITE_CORRUPT_VTAB +#endif typedef struct Fts3Table Fts3Table; typedef struct Fts3Cursor Fts3Cursor; typedef struct Fts3Expr Fts3Expr; typedef struct Fts3Phrase Fts3Phrase; @@ -114649,11 +115001,11 @@ char **pp, char *pStart, sqlite3_int64 *pVal ){ sqlite3_int64 iVal; - char *p = *pp; + char *p; /* Pointer p now points at the first byte past the varint we are ** interested in. So, unless the doclist is corrupt, the 0x80 bit is ** clear on character p[-1]. */ for(p = (*pp)-2; p>=pStart && *p&0x80; p--); @@ -115050,11 +115402,11 @@ ** the output value undefined. Otherwise SQLITE_OK is returned. ** ** This function is used when parsing the "prefix=" FTS4 parameter. */ static int fts3GobbleInt(const char **pp, int *pnOut){ - const char *p = *pp; /* Iterator pointer */ + const char *p; /* Iterator pointer */ int nInt = 0; /* Output value */ for(p=*pp; p[0]>='0' && p[0]<='9'; p++){ nInt = nInt * 10 + (p[0] - '0'); } @@ -115549,11 +115901,11 @@ if( rc==SQLITE_OK ){ /* If no row was found and no error has occured, then the %_content ** table is missing a row that is present in the full-text index. ** The data structures are corrupt. */ - rc = SQLITE_CORRUPT_VTAB; + rc = FTS_CORRUPT_VTAB; } pCsr->isEof = 1; if( pContext ){ sqlite3_result_error_code(pContext, rc); } @@ -115609,11 +115961,11 @@ ** nNode bytes of content (see sqlite3Fts3ReadBlock() for details). */ zCsr += sqlite3Fts3GetVarint(zCsr, &iChild); zCsr += sqlite3Fts3GetVarint(zCsr, &iChild); if( zCsr>zEnd ){ - return SQLITE_CORRUPT_VTAB; + return FTS_CORRUPT_VTAB; } while( zCsr<zEnd && (piFirst || piLast) ){ int cmp; /* memcmp() result */ int nSuffix; /* Size of term suffix */ @@ -115627,11 +115979,11 @@ } isFirstTerm = 0; zCsr += sqlite3Fts3GetVarint32(zCsr, &nSuffix); if( nPrefix<0 || nSuffix<0 || &zCsr[nSuffix]>zEnd ){ - rc = SQLITE_CORRUPT_VTAB; + rc = FTS_CORRUPT_VTAB; goto finish_scan; } if( nPrefix+nSuffix>nAlloc ){ char *zNew; nAlloc = (nPrefix+nSuffix) * 2; @@ -115640,10 +115992,11 @@ rc = SQLITE_NOMEM; goto finish_scan; } zBuffer = zNew; } + assert( zBuffer ); memcpy(&zBuffer[nPrefix], zCsr, nSuffix); nBuffer = nPrefix + nSuffix; zCsr += nSuffix; /* Compare the term we are searching for with the term just loaded from @@ -117076,11 +117429,11 @@ ** moves *ppPoslist so that it instead points to the first byte of the ** same position list. */ static void fts3ReversePoslist(char *pStart, char **ppPoslist){ char *p = &(*ppPoslist)[-2]; - char c; + char c = 0; while( p>pStart && (c=*p--)==0 ); while( p>pStart && (*p & 0x80) | c ){ c = *p--; } @@ -118070,11 +118423,11 @@ while( a<pEnd ){ a += sqlite3Fts3GetVarint(a, &nByte); } if( nDoc==0 || nByte==0 ){ sqlite3_reset(pStmt); - return SQLITE_CORRUPT_VTAB; + return FTS_CORRUPT_VTAB; } pCsr->nDoc = nDoc; pCsr->nRowAvg = (int)(((nByte / nDoc) + p->nPgsz) / p->nPgsz); assert( pCsr->nRowAvg>0 ); @@ -118546,12 +118899,15 @@ } aPoslist = pExpr->pRight->pPhrase->doclist.pList; nToken = pExpr->pRight->pPhrase->nToken; for(p=pExpr->pLeft; p && res; p=p->pLeft){ - int nNear = p->pParent->nNear; - Fts3Phrase *pPhrase = ( + int nNear; + Fts3Phrase *pPhrase; + assert( p->pParent && p->pParent->pLeft==p ); + nNear = p->pParent->nNear; + pPhrase = ( p->eType==FTSQUERY_NEAR ? p->pRight->pPhrase : p->pPhrase ); res = fts3EvalNearTrim(nNear, aTmp, &aPoslist, &nToken, pPhrase); } } @@ -119037,10 +119393,19 @@ pPhrase->aToken[i].pSegcsr = 0; } } } +/* +** Return SQLITE_CORRUPT_VTAB. +*/ +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE int sqlite3Fts3Corrupt(){ + return SQLITE_CORRUPT_VTAB; +} +#endif + #if !SQLITE_CORE /* ** Initialize API pointer table, if required. */ SQLITE_API int sqlite3_extension_init( @@ -119834,12 +120199,16 @@ p->pPhrase = (Fts3Phrase *)&p[1]; p->pPhrase->iColumn = pParse->iDefaultCol; p->pPhrase->nToken = nToken; zBuf = (char *)&p->pPhrase->aToken[nToken]; - memcpy(zBuf, zTemp, nTemp); - sqlite3_free(zTemp); + if( zTemp ){ + memcpy(zBuf, zTemp, nTemp); + sqlite3_free(zTemp); + }else{ + assert( nTemp==0 ); + } for(jj=0; jj<p->pPhrase->nToken; jj++){ p->pPhrase->aToken[jj].z = zBuf; zBuf += p->pPhrase->aToken[jj].n; } @@ -122594,11 +122963,11 @@ sqlite3_bind_int64(pStmt, 1, iDocid); } rc = sqlite3_step(pStmt); if( rc!=SQLITE_ROW || sqlite3_column_type(pStmt, 0)!=SQLITE_BLOB ){ rc = sqlite3_reset(pStmt); - if( rc==SQLITE_OK ) rc = SQLITE_CORRUPT_VTAB; + if( rc==SQLITE_OK ) rc = FTS_CORRUPT_VTAB; pStmt = 0; }else{ rc = SQLITE_OK; } } @@ -123398,11 +123767,11 @@ pNext += sqlite3Fts3GetVarint32(pNext, &nPrefix); pNext += sqlite3Fts3GetVarint32(pNext, &nSuffix); if( nPrefix<0 || nSuffix<=0 || &pNext[nSuffix]>&pReader->aNode[pReader->nNode] ){ - return SQLITE_CORRUPT_VTAB; + return FTS_CORRUPT_VTAB; } if( nPrefix+nSuffix>pReader->nTermAlloc ){ int nNew = (nPrefix+nSuffix)*2; char *zNew = sqlite3_realloc(pReader->zTerm, nNew); @@ -123428,11 +123797,11 @@ ** of these statements is untrue, then the data structure is corrupt. */ if( &pReader->aDoclist[pReader->nDoclist]>&pReader->aNode[pReader->nNode] || (pReader->nPopulate==0 && pReader->aDoclist[pReader->nDoclist-1]) ){ - return SQLITE_CORRUPT_VTAB; + return FTS_CORRUPT_VTAB; } return SQLITE_OK; } /* @@ -125382,11 +125751,10 @@ sqlite_int64 *pRowid /* OUT: The affected (or effected) rowid */ ){ Fts3Table *p = (Fts3Table *)pVtab; int rc = SQLITE_OK; /* Return Code */ int isRemove = 0; /* True for an UPDATE or DELETE */ - sqlite3_int64 iRemove = 0; /* Rowid removed by UPDATE or DELETE */ u32 *aSzIns = 0; /* Sizes of inserted documents */ u32 *aSzDel; /* Sizes of deleted documents */ int nChng = 0; /* Net change in number of documents */ int bInsertDone = 0; @@ -125465,23 +125833,23 @@ /* If this is a DELETE or UPDATE operation, remove the old record. */ if( sqlite3_value_type(apVal[0])!=SQLITE_NULL ){ assert( sqlite3_value_type(apVal[0])==SQLITE_INTEGER ); rc = fts3DeleteByRowid(p, apVal[0], &nChng, aSzDel); isRemove = 1; - iRemove = sqlite3_value_int64(apVal[0]); } /* If this is an INSERT or UPDATE operation, insert the new record. */ if( nArg>1 && rc==SQLITE_OK ){ if( bInsertDone==0 ){ rc = fts3InsertData(p, apVal, pRowid); - if( rc==SQLITE_CONSTRAINT ) rc = SQLITE_CORRUPT_VTAB; + if( rc==SQLITE_CONSTRAINT ) rc = FTS_CORRUPT_VTAB; } - if( rc==SQLITE_OK && (!isRemove || *pRowid!=iRemove) ){ + if( rc==SQLITE_OK && (!isRemove || *pRowid!=p->iPrevDocid ) ){ rc = fts3PendingTermsDocid(p, *pRowid); } if( rc==SQLITE_OK ){ + assert( p->iPrevDocid==*pRowid ); rc = fts3InsertTerms(p, apVal, aSzIns); } if( p->bHasDocsize ){ fts3InsertDocsize(&rc, p, aSzIns); } @@ -126371,11 +126739,11 @@ pStmt = *ppStmt; assert( sqlite3_data_count(pStmt)==1 ); a = sqlite3_column_blob(pStmt, 0); a += sqlite3Fts3GetVarint(a, &nDoc); - if( nDoc==0 ) return SQLITE_CORRUPT_VTAB; + if( nDoc==0 ) return FTS_CORRUPT_VTAB; *pnDoc = (u32)nDoc; if( paLen ) *paLen = a; return SQLITE_OK; } @@ -126950,11 +127318,11 @@ sqlite3_snprintf(sizeof(aBuffer), aBuffer, "%d %d %d %d ", iCol, pTerm-sCtx.aTerm, iStart, iEnd-iStart ); rc = fts3StringAppend(&res, aBuffer, -1); }else if( rc==SQLITE_DONE ){ - rc = SQLITE_CORRUPT_VTAB; + rc = FTS_CORRUPT_VTAB; } } } if( rc==SQLITE_DONE ){ rc = SQLITE_OK; @@ -128291,11 +128659,12 @@ pCsr->nConstraint = argc; if( !pCsr->aConstraint ){ rc = SQLITE_NOMEM; }else{ memset(pCsr->aConstraint, 0, sizeof(RtreeConstraint)*argc); - assert( (idxStr==0 && argc==0) || (int)strlen(idxStr)==argc*2 ); + assert( (idxStr==0 && argc==0) + || (idxStr && (int)strlen(idxStr)==argc*2) ); for(ii=0; ii<argc; ii++){ RtreeConstraint *p = &pCsr->aConstraint[ii]; p->op = idxStr[ii*2]; p->iCoord = idxStr[ii*2+1]-'a'; if( p->op==RTREE_MATCH ){ @@ -128592,11 +128961,14 @@ int iCell; sqlite3_int64 iBest = 0; float fMinGrowth = 0.0; float fMinArea = 0.0; +#if VARIANT_RSTARTREE_CHOOSESUBTREE float fMinOverlap = 0.0; + float overlap; +#endif int nCell = NCELL(pNode); RtreeCell cell; RtreeNode *pChild; @@ -128624,33 +128996,34 @@ */ for(iCell=0; iCell<nCell; iCell++){ int bBest = 0; float growth; float area; - float overlap = 0.0; nodeGetCell(pRtree, pNode, iCell, &cell); growth = cellGrowth(pRtree, &cell, pCell); area = cellArea(pRtree, &cell); #if VARIANT_RSTARTREE_CHOOSESUBTREE if( ii==(pRtree->iDepth-1) ){ overlap = cellOverlapEnlargement(pRtree,&cell,pCell,aCell,nCell,iCell); + }else{ + overlap = 0.0; } if( (iCell==0) || (overlap<fMinOverlap) || (overlap==fMinOverlap && growth<fMinGrowth) || (overlap==fMinOverlap && growth==fMinGrowth && area<fMinArea) ){ bBest = 1; + fMinOverlap = overlap; } #else if( iCell==0||growth<fMinGrowth||(growth==fMinGrowth && area<fMinArea) ){ bBest = 1; } #endif if( bBest ){ - fMinOverlap = overlap; fMinGrowth = growth; fMinArea = area; iBest = cell.iRowid; } } Index: src/sqlite3.h ================================================================== --- src/sqlite3.h +++ src/sqlite3.h @@ -105,13 +105,13 @@ ** ** See also: [sqlite3_libversion()], ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.7.8" -#define SQLITE_VERSION_NUMBER 3007008 -#define SQLITE_SOURCE_ID "2011-09-19 14:49:19 3e0da808d2f5b4d12046e05980ca04578f581177" +#define SQLITE_VERSION "3.7.9" +#define SQLITE_VERSION_NUMBER 3007009 +#define SQLITE_SOURCE_ID "2011-10-15 00:16:30 39408702a989f907261c298bf0947f3e68bd10fe" /* ** CAPI3REF: Run-Time Library Version Numbers ** KEYWORDS: sqlite3_version, sqlite3_sourceid ** @@ -769,11 +769,15 @@ ** in order for the database to be readable. The fourth parameter to ** [sqlite3_file_control()] for this opcode should be a pointer to an integer. ** That integer is 0 to disable persistent WAL mode or 1 to enable persistent ** WAL mode. If the integer is -1, then it is overwritten with the current ** WAL persistence setting. -** +** +** ^The [SQLITE_FCNTL_OVERWRITE] opcode is invoked by SQLite after opening +** a write transaction to indicate that, unless it is rolled back for some +** reason, the entire database file will be overwritten by the current +** transaction. This is used by VACUUM operations. */ #define SQLITE_FCNTL_LOCKSTATE 1 #define SQLITE_GET_LOCKPROXYFILE 2 #define SQLITE_SET_LOCKPROXYFILE 3 #define SQLITE_LAST_ERRNO 4 @@ -781,10 +785,11 @@ #define SQLITE_FCNTL_CHUNK_SIZE 6 #define SQLITE_FCNTL_FILE_POINTER 7 #define SQLITE_FCNTL_SYNC_OMITTED 8 #define SQLITE_FCNTL_WIN32_AV_RETRY 9 #define SQLITE_FCNTL_PERSIST_WAL 10 +#define SQLITE_FCNTL_OVERWRITE 11 /* ** CAPI3REF: Mutex Handle ** ** The mutex module within SQLite defines [sqlite3_mutex] to be an @@ -2797,11 +2802,12 @@ ** zSql string ends at either the first '\000' or '\u0000' character or ** the nByte-th byte, whichever comes first. If the caller knows ** that the supplied string is nul-terminated, then there is a small ** performance advantage to be gained by passing an nByte parameter that ** is equal to the number of bytes in the input string <i>including</i> -** the nul-terminator bytes. +** the nul-terminator bytes as this saves SQLite from having to +** make a copy of the input string. ** ** ^If pzTail is not NULL then *pzTail is made to point to the first byte ** past the end of the first SQL statement in zSql. These routines only ** compile the first statement in zSql, so *pzTail is left pointing to ** what remains uncompiled. @@ -2848,11 +2854,11 @@ ** a schema change, on the first [sqlite3_step()] call following any change ** to the [sqlite3_bind_text | bindings] of that [parameter]. ** ^The specific value of WHERE-clause [parameter] might influence the ** choice of query plan if the parameter is the left-hand side of a [LIKE] ** or [GLOB] operator or if the parameter is compared to an indexed column -** and the [SQLITE_ENABLE_STAT2] compile-time option is enabled. +** and the [SQLITE_ENABLE_STAT3] compile-time option is enabled. ** the ** </li> ** </ol> */ SQLITE_API int sqlite3_prepare( @@ -3018,10 +3024,17 @@ ** ^(In those routines that have a fourth argument, its value is the ** number of bytes in the parameter. To be clear: the value is the ** number of <u>bytes</u> in the value, not the number of characters.)^ ** ^If the fourth parameter is negative, the length of the string is ** the number of bytes up to the first zero terminator. +** If a non-negative fourth parameter is provided to sqlite3_bind_text() +** or sqlite3_bind_text16() then that parameter must be the byte offset +** where the NUL terminator would occur assuming the string were NUL +** terminated. If any NUL characters occur at byte offsets less than +** the value of the fourth parameter then the resulting string value will +** contain embedded NULs. The result of expressions involving strings +** with embedded NULs is undefined. ** ** ^The fifth argument to sqlite3_bind_blob(), sqlite3_bind_text(), and ** sqlite3_bind_text16() is a destructor used to dispose of the BLOB or ** string after SQLite has finished with it. ^The destructor is called ** to dispose of the BLOB or string even if the call to sqlite3_bind_blob(), @@ -3351,10 +3364,16 @@ ** current row of the result set of [prepared statement] P. ** ^If prepared statement P does not have results ready to return ** (via calls to the [sqlite3_column_int | sqlite3_column_*()] of ** interfaces) then sqlite3_data_count(P) returns 0. ** ^The sqlite3_data_count(P) routine also returns 0 if P is a NULL pointer. +** ^The sqlite3_data_count(P) routine returns 0 if the previous call to +** [sqlite3_step](P) returned [SQLITE_DONE]. ^The sqlite3_data_count(P) +** will return non-zero if previous call to [sqlite3_step](P) returned +** [SQLITE_ROW], except in the case of the [PRAGMA incremental_vacuum] +** where it always returns zero since each step of that multi-step +** pragma returns 0 columns of data. ** ** See also: [sqlite3_column_count()] */ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); @@ -4030,11 +4049,16 @@ ** is negative, then SQLite takes result text from the 2nd parameter ** through the first zero character. ** ^If the 3rd parameter to the sqlite3_result_text* interfaces ** is non-negative, then as many bytes (not characters) of the text ** pointed to by the 2nd parameter are taken as the application-defined -** function result. +** function result. If the 3rd parameter is non-negative, then it +** must be the byte offset into the string where the NUL terminator would +** appear if the string where NUL terminated. If any NUL characters occur +** in the string at a byte offset that is less than the value of the 3rd +** parameter, then the resulting string will contain embedded NULs and the +** result of expressions operating on strings with embedded NULs is undefined. ** ^If the 4th parameter to the sqlite3_result_text* interfaces ** or sqlite3_result_blob is a non-NULL pointer, then SQLite calls that ** function as the destructor on the text or BLOB result when it has ** finished using that result. ** ^If the 4th parameter to the sqlite3_result_text* interfaces or to @@ -5813,20 +5837,34 @@ ** <dd>This parameter returns the approximate number of of bytes of heap ** and lookaside memory used by all prepared statements associated with ** the database connection.)^ ** ^The highwater mark associated with SQLITE_DBSTATUS_STMT_USED is always 0. ** </dd> +** +** [[SQLITE_DBSTATUS_CACHE_HIT]] ^(<dt>SQLITE_DBSTATUS_CACHE_HIT</dt> +** <dd>This parameter returns the number of pager cache hits that have +** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_HIT +** is always 0. +** </dd> +** +** [[SQLITE_DBSTATUS_CACHE_MISS]] ^(<dt>SQLITE_DBSTATUS_CACHE_MISS</dt> +** <dd>This parameter returns the number of pager cache misses that have +** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_MISS +** is always 0. +** </dd> ** </dl> */ #define SQLITE_DBSTATUS_LOOKASIDE_USED 0 #define SQLITE_DBSTATUS_CACHE_USED 1 #define SQLITE_DBSTATUS_SCHEMA_USED 2 #define SQLITE_DBSTATUS_STMT_USED 3 #define SQLITE_DBSTATUS_LOOKASIDE_HIT 4 #define SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE 5 #define SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL 6 -#define SQLITE_DBSTATUS_MAX 6 /* Largest defined DBSTATUS */ +#define SQLITE_DBSTATUS_CACHE_HIT 7 +#define SQLITE_DBSTATUS_CACHE_MISS 8 +#define SQLITE_DBSTATUS_MAX 8 /* Largest defined DBSTATUS */ /* ** CAPI3REF: Prepared Statement Status ** @@ -5876,11 +5914,10 @@ ** <dd>^This is the number of rows inserted into transient indices that ** were created automatically in order to help joins run faster. ** A non-zero value in this counter may indicate an opportunity to ** improvement performance by adding permanent indices that do not ** need to be reinitialized each time the statement is run.</dd> -** ** </dl> */ #define SQLITE_STMTSTATUS_FULLSCAN_STEP 1 #define SQLITE_STMTSTATUS_SORT 2 #define SQLITE_STMTSTATUS_AUTOINDEX 3 Index: src/stash.c ================================================================== --- src/stash.c +++ src/stash.c @@ -482,11 +482,11 @@ db_finalize(&q); if( n==0 ) fossil_print("empty stash\n"); }else if( memcmp(zCmd, "drop", nCmd)==0 || memcmp(zCmd, "rm", nCmd)==0 ){ int allFlag = find_option("all", 0, 0)!=0; - if( g.argc>4 ) usage("stash apply STASHID"); + if( g.argc>4 ) usage("apply STASHID"); if( allFlag ){ db_multi_exec("DELETE FROM stash; DELETE FROM stashfile;"); }else{ stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0); undo_begin(); @@ -494,30 +494,30 @@ stash_drop(stashid); undo_finish(); } }else if( memcmp(zCmd, "pop", nCmd)==0 ){ - if( g.argc>3 ) usage("stash pop"); + if( g.argc>3 ) usage("pop"); stashid = stash_get_id(0); undo_begin(); stash_apply(stashid, 0); undo_save_stash(stashid); undo_finish(); stash_drop(stashid); }else if( memcmp(zCmd, "apply", nCmd)==0 ){ - if( g.argc>4 ) usage("stash apply STASHID"); + if( g.argc>4 ) usage("apply STASHID"); stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0); undo_begin(); stash_apply(stashid, 0); undo_finish(); }else if( memcmp(zCmd, "goto", nCmd)==0 ){ int nConflict; int vid; - if( g.argc>4 ) usage("stash apply STASHID"); + if( g.argc>4 ) usage("apply STASHID"); stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0); undo_begin(); vid = db_int(0, "SELECT vid FROM stash WHERE stashid=%d", stashid); nConflict = update_to(vid); stash_apply(stashid, nConflict); @@ -526,20 +526,20 @@ stashid); undo_finish(); }else if( memcmp(zCmd, "diff", nCmd)==0 ){ const char *zDiffCmd = db_get("diff-command", 0); - if( g.argc>4 ) usage("stash diff STASHID"); + if( g.argc>4 ) usage("diff STASHID"); stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0); stash_diff(stashid, zDiffCmd); }else if( memcmp(zCmd, "gdiff", nCmd)==0 ){ const char *zDiffCmd = db_get("gdiff-command", 0); - if( g.argc>4 ) usage("stash diff STASHID"); + if( g.argc>4 ) usage("diff STASHID"); stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0); stash_diff(stashid, zDiffCmd); }else { usage("SUBCOMMAND ARGS..."); } db_end_transaction(0); } Index: src/style.c ================================================================== --- src/style.c +++ src/style.c @@ -397,10 +397,68 @@ @ vertical-align: top; @ text-align: right; @ padding: 0.2ex 2ex; @ } @ +@ /* Side-by-side diff */ +@ table.sbsdiff { +@ font-family: fixed, Dejavu Sans Mono, Monaco, Lucida Console, monospace; +@ font-size: 10pt; +@ border-collapse:collapse; +@ white-space: pre; +@ width: 98%; +@ border: 1px #000 dashed; +@ margin-left: auto; +@ margin-right: auto; +@ } +@ +@ table.sbsdiff th.diffhdr { +@ border-bottom: dotted; +@ border-width: 1px; +@ } +@ +@ table.sbsdiff tr td { +@ white-space: pre; +@ padding-left: 3px; +@ padding-right: 3px; +@ margin: 0px; +@ vertical-align: top; +@ } +@ +@ table.sbsdiff tr td.lineno { +@ text-align: right; +@ } +@ +@ table.sbsdiff tr td.srcline { +@ } +@ +@ table.sbsdiff tr td.meta { +@ background-color: rgb(170, 160, 255); +@ text-align: center; +@ } +@ +@ table.sbsdiff tr td.added { +@ background-color: rgb(180, 250, 180); +@ } +@ table.sbsdiff tr td.addedvoid { +@ background-color: rgb(190, 190, 180); +@ } +@ +@ table.sbsdiff tr td.removed { +@ background-color: rgb(250, 130, 130); +@ } +@ table.sbsdiff tr td.removedvoid { +@ background-color: rgb(190, 190, 180); +@ } +@ +@ table.sbsdiff tr td.changed { +@ background-color: rgb(210, 210, 200); +@ } +@ table.sbsdiff tr td.changedvoid { +@ background-color: rgb(190, 190, 180); +@ } +@ ; /* The following table contains bits of default CSS that must ** be included if they are not found in the application-defined @@ -803,14 +861,26 @@ ** WEBPAGE: test_env */ void page_test_env(void){ char c; int i; + int showAll; char zCap[30]; login_check_credentials(); - if( !g.perm.Admin && !g.perm.Setup ){ login_needed(); return; } + if( !g.perm.Admin && !g.perm.Setup && !db_get_boolean("test_env_enable",0) ){ + login_needed(); + return; + } style_header("Environment Test"); + showAll = atoi(PD("showall","0")); + if( !showAll ){ + style_submenu_element("Show Cookies", "Show Cookies", + "%s/test_env?showall=1", g.zTop); + }else{ + style_submenu_element("Hide Cookies", "Hide Cookies", + "%s/test_env", g.zTop); + } #if !defined(_WIN32) @ uid=%d(getuid()), gid=%d(getgid())<br /> #endif @ g.zBaseURL = %h(g.zBaseURL)<br /> @ g.zTop = %h(g.zTop)<br /> @@ -820,12 +890,12 @@ zCap[i] = 0; @ g.userUid = %d(g.userUid)<br /> @ g.zLogin = %h(g.zLogin)<br /> @ capabilities = %s(zCap)<br /> @ <hr> - cgi_print_all(); + cgi_print_all(atoi(PD("showall","0"))); if( g.perm.Setup ){ const char *zRedir = P("redirect"); if( zRedir ) cgi_redirect(zRedir); } style_footer(); } Index: src/th.c ================================================================== --- src/th.c +++ src/th.c @@ -1817,12 +1817,12 @@ rc = thSubstWord(interp, pExpr->zValue, pExpr->nValue); }else{ int eArgType = 0; /* Actual type of arguments */ /* Argument values */ - int iLeft; - int iRight; + int iLeft = 0; + int iRight = 0; double fLeft; double fRight; /* Left and right arguments as strings */ char *zLeft = 0; int nLeft = 0; Index: src/timeline.c ================================================================== --- src/timeline.c +++ src/timeline.c @@ -20,11 +20,10 @@ */ #include <string.h> #include <time.h> #include "config.h" #include "timeline.h" -#include "cson_amalgamation.h" /* ** Shorten a UUID so that is the minimum length needed to contain ** at least one digit in the range 'a'..'f'. The minimum length is 10. */ @@ -766,24 +765,24 @@ */ const char *timeline_query_for_www(void){ static char *zBase = 0; static const char zBaseSql[] = @ SELECT - @ blob.rid AS blobRid, - @ uuid AS uuid, + @ blob.rid, + @ uuid, @ datetime(event.mtime,'localtime') AS timestamp, - @ coalesce(ecomment, comment) AS comment, - @ coalesce(euser, user) AS user, - @ blob.rid IN leaf AS leaf, - @ bgcolor AS bgColor, - @ event.type AS eventType, + @ coalesce(ecomment, comment), + @ coalesce(euser, user), + @ blob.rid IN leaf, + @ bgcolor, + @ event.type, @ (SELECT group_concat(substr(tagname,5), ', ') FROM tag, tagxref @ WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid - @ AND tagxref.rid=blob.rid AND tagxref.tagtype>0) AS tags, - @ tagid AS tagid, - @ brief AS brief, - @ event.mtime AS mtime + @ AND tagxref.rid=blob.rid AND tagxref.tagtype>0), + @ tagid, + @ brief, + @ event.mtime @ FROM event JOIN blob @ WHERE blob.rid=event.objid ; if( zBase==0 ){ zBase = mprintf(zBaseSql, TAG_BRANCH, TAG_BRANCH); @@ -1366,24 +1365,24 @@ ** a timeline query for display on a TTY. */ const char *timeline_query_for_tty(void){ static const char zBaseSql[] = @ SELECT - @ blob.rid AS rid, + @ blob.rid, @ uuid, - @ datetime(event.mtime,'localtime') AS mDateTime, + @ datetime(event.mtime,'localtime'), @ coalesce(ecomment,comment) @ || ' (user: ' || coalesce(euser,user,'?') @ || (SELECT case when length(x)>0 then ' tags: ' || x else '' end @ FROM (SELECT group_concat(substr(tagname,5), ', ') AS x @ FROM tag, tagxref @ WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid @ AND tagxref.rid=blob.rid AND tagxref.tagtype>0)) - @ || ')' as comment, - @ (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim) AS primPlinkCount, - @ (SELECT count(*) FROM plink WHERE cid=blob.rid) AS plinkCount, - @ event.mtime AS mtime + @ || ')', + @ (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim), + @ (SELECT count(*) FROM plink WHERE cid=blob.rid), + @ event.mtime @ FROM event, blob @ WHERE blob.rid=event.objid ; return zBaseSql; } @@ -1426,12 +1425,12 @@ ** ** w = wiki commits only ** ci = file commits only ** t = tickets only ** -** The optional showfiles argument, if specified, prints the list of -** files changed in a checkin after the checkin comment. +** The optional showfiles argument if specified prints the list of +** files changed in a checkin after the checkin comment ** */ void timeline_cmd(void){ Stmt q; int n, k; @@ -1525,10 +1524,11 @@ blob_appendf(&sql, " AND blob.rid IN ok"); } if( zType && (zType[0]!='a') ){ blob_appendf(&sql, " AND event.type=%Q ", zType); } + blob_appendf(&sql, " ORDER BY event.mtime DESC"); db_prepare(&q, blob_str(&sql)); blob_reset(&sql); print_timeline(&q, n, showfilesFlag); db_finalize(&q); Index: src/tkt.c ================================================================== --- src/tkt.c +++ src/tkt.c @@ -855,11 +855,10 @@ ** ** Run the ticket report, identified by the report format title ** used in the gui. The data is written as flat file on stdout, ** using "," as separator. The separator "," can be changed using ** the -l or --limit option. -** ** If TICKETFILTER is given on the commandline, the query is ** limited with a new WHERE-condition. ** example: Report lists a column # with the uuid ** TICKETFILTER may be [#]='uuuuuuuuu' ** example: Report only lists rows with status not open @@ -866,11 +865,11 @@ ** TICKETFILTER: status != 'open' ** If the option -q|--quote is used, the tickets are encoded by ** quoting special chars(space -> \\s, tab -> \\t, newline -> \\n, ** cr -> \\r, formfeed -> \\f, vtab -> \\v, nul -> \\0, \\ -> \\\\). ** Otherwise, the simplified encoding as on the show report raw -** page in the gui is used. This has no effect in JSON mode. +** page in the gui is used. ** ** Instead of the report title its possible to use the report ** number. Using the special report number 0 list all columns, ** defined in the ticket table. ** @@ -960,19 +959,22 @@ usage("show REPORTNR"); }else{ const char *zRep = 0; const char *zSep = 0; const char *zFilterUuid = 0; + zSep = find_option("limit","l",1); zRep = g.argv[3]; if( !strcmp(zRep,"0") ){ zRep = 0; } if( g.argc>4 ){ zFilterUuid = g.argv[4]; } + rptshow( zRep, zSep, zFilterUuid, tktEncoding ); + } }else{ /* add a new ticket or update an existing ticket */ enum { set,add,history,err } eCmd = err; int i = 0; Index: src/wiki.c ================================================================== --- src/wiki.c +++ src/wiki.c @@ -630,27 +630,10 @@ manifest_destroy(pW1); manifest_destroy(pW2); style_footer(); } -/* -** prepare()s pStmt with a query requesting: -** -** - wiki page name -** - tagxref (whatever that really is!) -** -** Used by wcontent_page() and the JSON wiki code. -*/ -void wiki_prepare_page_list( Stmt * pStmt ){ - db_prepare(pStmt, - "SELECT" - " substr(tagname, 6) as name," - " (SELECT value FROM tagxref WHERE tagid=tag.tagid ORDER BY mtime DESC) as tagXref" - " FROM tag WHERE tagname GLOB 'wiki-*'" - " ORDER BY lower(tagname) /*sort*/" - ); -} /* ** WEBPAGE: wcontent ** ** all=1 Show deleted pages ** @@ -667,11 +650,17 @@ style_submenu_element("Active", "Only Active Pages", "%s/wcontent", g.zTop); }else{ style_submenu_element("All", "All", "%s/wcontent?all=1", g.zTop); } @ <ul> - wiki_prepare_page_list(&q); + db_prepare(&q, + "SELECT" + " substr(tagname, 6)," + " (SELECT value FROM tagxref WHERE tagid=tag.tagid ORDER BY mtime DESC)" + " FROM tag WHERE tagname GLOB 'wiki-*'" + " ORDER BY lower(tagname) /*sort*/" + ); while( db_step(&q)==SQLITE_ROW ){ const char *zName = db_column_text(&q, 0); int size = db_column_int(&q, 1); if( size>0 ){ @ <li><a href="%s(g.zTop)/wiki?name=%T(zName)">%h(zName)</a></li> @@ -801,15 +790,13 @@ " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'" " ORDER BY x.mtime DESC LIMIT 1", zPageName ); if( rid==0 && !isNew ){ - g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; fossil_fatal("no such wiki page: %s", zPageName); } if( rid!=0 && isNew ){ - g.json.resultCode = FSL_JSON_E_RESOURCE_ALREADY_EXISTS; fossil_fatal("wiki page %s already exists", zPageName); } blob_zero(&wiki); zDate = date_in_standard_format("now"); Index: src/xfer.c ================================================================== --- src/xfer.c +++ src/xfer.c @@ -573,11 +573,11 @@ blob_zero(&combined); blob_copy(&combined, pNonce); blob_append(&combined, blob_buffer(&pw), szPw); sha1sum_blob(&combined, &hash); assert( blob_size(&hash)==40 ); - rc = blob_compare(&hash, pSig); + rc = blob_constant_time_cmp(&hash, pSig); blob_reset(&hash); blob_reset(&combined); if( rc!=0 && szPw!=40 ){ /* If this server stores cleartext passwords and the password did not ** match, then perhaps the client is sending SHA1 passwords. Try @@ -588,11 +588,11 @@ blob_zero(&combined); blob_copy(&combined, pNonce); blob_append(&combined, zSecret, -1); free(zSecret); sha1sum_blob(&combined, &hash); - rc = blob_compare(&hash, pSig); + rc = blob_constant_time_cmp(&hash, pSig); blob_reset(&hash); blob_reset(&combined); } if( rc==0 ){ const char *zCap; Index: test/merge_renames.test ================================================================== --- test/merge_renames.test +++ test/merge_renames.test @@ -3,11 +3,11 @@ # # catch {exec $::fossilexe info} res puts res=$res -if {![regexp {not within an open checkout} $res]} { +if {![regexp {use --repository} $res]} { puts stderr "Cannot run this test within an open checkout" return } ###################################### Index: win/Makefile.dmc ================================================================== --- win/Makefile.dmc +++ win/Makefile.dmc @@ -22,13 +22,13 @@ TCC = $(DMDIR)\bin\dmc $(CFLAGS) $(DMCDEF) $(SSL) $(INCL) LIBS = $(DMDIR)\extra\lib\ zlib wsock32 SQLITE_OPTIONS = -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_STAT3 -Dlocaltime=fossil_localtime -DSQLITE_ENABLE_LOCKING_STYLE=0 -SRC = add_.c allrepo_.c attach_.c bag_.c bisect_.c blob_.c branch_.c browse_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c db_.c delta_.c deltacmd_.c descendants_.c diff_.c diffcmd_.c doc_.c encode_.c event_.c export_.c file_.c finfo_.c glob_.c graph_.c gzip_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c json_.c json_login_.c json_timeline_.c json_wiki_.c leaf_.c login_.c main_.c manifest_.c md5_.c merge_.c merge3_.c name_.c path_.c pivot_.c popen_.c pqueue_.c printf_.c rebuild_.c report_.c rss_.c schema_.c search_.c setup_.c sha1_.c shun_.c skins_.c sqlcmd_.c stash_.c stat_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c update_.c url_.c user_.c verify_.c vfile_.c wiki_.c wikiformat_.c winhttp_.c xfer_.c zip_.c +SRC = add_.c allrepo_.c attach_.c bag_.c bisect_.c blob_.c branch_.c browse_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c db_.c delta_.c deltacmd_.c descendants_.c diff_.c diffcmd_.c doc_.c encode_.c event_.c export_.c file_.c finfo_.c glob_.c graph_.c gzip_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c leaf_.c login_.c main_.c manifest_.c md5_.c merge_.c merge3_.c name_.c path_.c pivot_.c popen_.c pqueue_.c printf_.c rebuild_.c report_.c rss_.c schema_.c search_.c setup_.c sha1_.c shun_.c skins_.c sqlcmd_.c stash_.c stat_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c update_.c url_.c user_.c verify_.c vfile_.c wiki_.c wikiformat_.c winhttp_.c xfer_.c zip_.c -OBJ = $(OBJDIR)\add$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\file$O $(OBJDIR)\finfo$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\json$O $(OBJDIR)\json_login$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\login$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\name$O $(OBJDIR)\path$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\rebuild$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\setup$O $(OBJDIR)\sha1$O $(OBJDIR)\shun$O $(OBJDIR)\skins$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winhttp$O $(OBJDIR)\xfer$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O +OBJ = $(OBJDIR)\add$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\file$O $(OBJDIR)\finfo$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\leaf$O $(OBJDIR)\login$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\name$O $(OBJDIR)\path$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\rebuild$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\setup$O $(OBJDIR)\sha1$O $(OBJDIR)\shun$O $(OBJDIR)\skins$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winhttp$O $(OBJDIR)\xfer$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O RC=$(DMDIR)\bin\rcc RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__ @@ -42,11 +42,11 @@ $(OBJDIR)\fossil.res: $B\win\fossil.rc $(RC) $(RCFLAGS) -o$@ $** $(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res - +echo add allrepo attach bag bisect blob branch browse captcha cgi checkin checkout clearsign clone comformat configure content db delta deltacmd descendants diff diffcmd doc encode event export file finfo glob graph gzip http http_socket http_ssl http_transport import info json json_login json_timeline json_wiki leaf login main manifest md5 merge merge3 name path pivot popen pqueue printf rebuild report rss schema search setup sha1 shun skins sqlcmd stash stat style sync tag tar th_main timeline tkt tktsetup undo update url user verify vfile wiki wikiformat winhttp xfer zip shell sqlite3 th th_lang > $@ + +echo add allrepo attach bag bisect blob branch browse captcha cgi checkin checkout clearsign clone comformat configure content db delta deltacmd descendants diff diffcmd doc encode event export file finfo glob graph gzip http http_socket http_ssl http_transport import info leaf login main manifest md5 merge merge3 name path pivot popen pqueue printf rebuild report rss schema search setup sha1 shun skins sqlcmd stash stat style sync tag tar th_main timeline tkt tktsetup undo update url user verify vfile wiki wikiformat winhttp xfer zip shell sqlite3 th th_lang > $@ +echo fossil >> $@ +echo fossil >> $@ +echo $(LIBS) >> $@ +echo. >> $@ +echo fossil >> $@ @@ -73,13 +73,10 @@ $(TCC) -o$@ -c $** $(OBJDIR)\th_lang$O : $(SRCDIR)\th_lang.c $(TCC) -o$@ -c $** -$(OBJDIR)\cson_amalgamation.h : $(SRCDIR)\cson_amalgamation.h - cp $@ $@ - VERSION.h : version$E $B\manifest.uuid $B\manifest $B\VERSION +$** > $@ page_index.h: mkindex$E $(SRC) +$** > $@ @@ -88,16 +85,10 @@ -del $(OBJDIR)\*.obj -del *.obj *_.c *.h *.map realclean: -del $(APPNAME) translate$E mkindex$E makeheaders$E mkversion$E - -$(OBJDIR)\json$O : $(SRCDIR)\json_detail.h -$(OBJDIR)\json_login$O : $(SRCDIR)\json_detail.h -$(OBJDIR)\json_wiki$O : $(SRCDIR)\json_detail.h -$(OBJDIR)\json_timeline$O : $(SRCDIR)\json_detail.h - $(OBJDIR)\add$O : add_.c add.h $(TCC) -o$@ -c add_.c @@ -324,34 +315,10 @@ $(TCC) -o$@ -c info_.c info_.c : $(SRCDIR)\info.c +translate$E $** > $@ -$(OBJDIR)\json$O : json_.c json.h - $(TCC) -o$@ -c json_.c - -json_.c : $(SRCDIR)\json.c - +translate$E $** > $@ - -$(OBJDIR)\json_login$O : json_login_.c json_login.h - $(TCC) -o$@ -c json_login_.c - -json_login_.c : $(SRCDIR)\json_login.c - +translate$E $** > $@ - -$(OBJDIR)\json_timeline$O : json_timeline_.c json_timeline.h - $(TCC) -o$@ -c json_timeline_.c - -json_timeline_.c : $(SRCDIR)\json_timeline.c - +translate$E $** > $@ - -$(OBJDIR)\json_wiki$O : json_wiki_.c json_wiki.h - $(TCC) -o$@ -c json_wiki_.c - -json_wiki_.c : $(SRCDIR)\json_wiki.c - +translate$E $** > $@ - $(OBJDIR)\leaf$O : leaf_.c leaf.h $(TCC) -o$@ -c leaf_.c leaf_.c : $(SRCDIR)\leaf.c +translate$E $** > $@ @@ -613,7 +580,7 @@ zip_.c : $(SRCDIR)\zip.c +translate$E $** > $@ headers: makeheaders$E page_index.h VERSION.h - +makeheaders$E add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h event_.c:event.h export_.c:export.h file_.c:file.h finfo_.c:finfo.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h json_.c:json.h json_login_.c:json_login.h json_timeline_.c:json_timeline.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h path_.c:path.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR)\cson_amalgamation.h + +makeheaders$E add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h event_.c:event.h export_.c:export.h file_.c:file.h finfo_.c:finfo.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h leaf_.c:leaf.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h path_.c:path.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h @copy /Y nul: headers Index: win/Makefile.mingw ================================================================== --- win/Makefile.mingw +++ win/Makefile.mingw @@ -110,14 +110,10 @@ $(SRCDIR)/http_socket.c \ $(SRCDIR)/http_ssl.c \ $(SRCDIR)/http_transport.c \ $(SRCDIR)/import.c \ $(SRCDIR)/info.c \ - $(SRCDIR)/json.c \ - $(SRCDIR)/json_login.c \ - $(SRCDIR)/json_timeline.c \ - $(SRCDIR)/json_wiki.c \ $(SRCDIR)/leaf.c \ $(SRCDIR)/login.c \ $(SRCDIR)/main.c \ $(SRCDIR)/manifest.c \ $(SRCDIR)/md5.c \ @@ -198,14 +194,10 @@ $(OBJDIR)/http_socket_.c \ $(OBJDIR)/http_ssl_.c \ $(OBJDIR)/http_transport_.c \ $(OBJDIR)/import_.c \ $(OBJDIR)/info_.c \ - $(OBJDIR)/json_.c \ - $(OBJDIR)/json_login_.c \ - $(OBJDIR)/json_timeline_.c \ - $(OBJDIR)/json_wiki_.c \ $(OBJDIR)/leaf_.c \ $(OBJDIR)/login_.c \ $(OBJDIR)/main_.c \ $(OBJDIR)/manifest_.c \ $(OBJDIR)/md5_.c \ @@ -286,14 +278,10 @@ $(OBJDIR)/http_socket.o \ $(OBJDIR)/http_ssl.o \ $(OBJDIR)/http_transport.o \ $(OBJDIR)/import.o \ $(OBJDIR)/info.o \ - $(OBJDIR)/json.o \ - $(OBJDIR)/json_login.o \ - $(OBJDIR)/json_timeline.o \ - $(OBJDIR)/json_wiki.o \ $(OBJDIR)/leaf.o \ $(OBJDIR)/login.o \ $(OBJDIR)/main.o \ $(OBJDIR)/manifest.o \ $(OBJDIR)/md5.o \ @@ -375,11 +363,11 @@ $(TCLSH) test/tester.tcl $(APPNAME) $(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(VERSION) $(VERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h -EXTRAOBJ = $(OBJDIR)/sqlite3.o $(OBJDIR)/shell.o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o $(OBJDIR)/cson_amalgamation.o +EXTRAOBJ = $(OBJDIR)/sqlite3.o $(OBJDIR)/shell.o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o $(APPNAME): $(OBJDIR)/headers $(OBJ) $(EXTRAOBJ) $(OBJDIR)/icon.o $(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB) $(OBJDIR)/icon.o # This rule prevents make from using its default rules to try build @@ -400,11 +388,11 @@ $(OBJDIR)/page_index.h: $(TRANS_SRC) $(OBJDIR)/mkindex $(MKINDEX) $(TRANS_SRC) >$@ $(OBJDIR)/headers: $(OBJDIR)/page_index.h $(OBJDIR)/makeheaders $(OBJDIR)/VERSION.h - $(MAKEHEADERS) $(OBJDIR)/add_.c:$(OBJDIR)/add.h $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h $(OBJDIR)/browse_.c:$(OBJDIR)/browse.h $(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h $(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h $(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h $(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h $(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h $(OBJDIR)/clone_.c:$(OBJDIR)/clone.h $(OBJDIR)/comformat_.c:$(OBJDIR)/comformat.h $(OBJDIR)/configure_.c:$(OBJDIR)/configure.h $(OBJDIR)/content_.c:$(OBJDIR)/content.h $(OBJDIR)/db_.c:$(OBJDIR)/db.h $(OBJDIR)/delta_.c:$(OBJDIR)/delta.h $(OBJDIR)/deltacmd_.c:$(OBJDIR)/deltacmd.h $(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h $(OBJDIR)/diff_.c:$(OBJDIR)/diff.h $(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h $(OBJDIR)/event_.c:$(OBJDIR)/event.h $(OBJDIR)/export_.c:$(OBJDIR)/export.h $(OBJDIR)/file_.c:$(OBJDIR)/file.h $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h $(OBJDIR)/http_.c:$(OBJDIR)/http.h $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h $(OBJDIR)/import_.c:$(OBJDIR)/import.h $(OBJDIR)/info_.c:$(OBJDIR)/info.h $(OBJDIR)/json_.c:$(OBJDIR)/json.h $(OBJDIR)/json_login_.c:$(OBJDIR)/json_login.h $(OBJDIR)/json_timeline_.c:$(OBJDIR)/json_timeline.h $(OBJDIR)/json_wiki_.c:$(OBJDIR)/json_wiki.h $(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h $(OBJDIR)/login_.c:$(OBJDIR)/login.h $(OBJDIR)/main_.c:$(OBJDIR)/main.h $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h $(OBJDIR)/name_.c:$(OBJDIR)/name.h $(OBJDIR)/path_.c:$(OBJDIR)/path.h $(OBJDIR)/pivot_.c:$(OBJDIR)/pivot.h $(OBJDIR)/popen_.c:$(OBJDIR)/popen.h $(OBJDIR)/pqueue_.c:$(OBJDIR)/pqueue.h $(OBJDIR)/printf_.c:$(OBJDIR)/printf.h $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h $(OBJDIR)/report_.c:$(OBJDIR)/report.h $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h $(OBJDIR)/search_.c:$(OBJDIR)/search.h $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h $(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h $(OBJDIR)/style_.c:$(OBJDIR)/style.h $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h $(OBJDIR)/update_.c:$(OBJDIR)/update.h $(OBJDIR)/url_.c:$(OBJDIR)/url.h $(OBJDIR)/user_.c:$(OBJDIR)/user.h $(OBJDIR)/verify_.c:$(OBJDIR)/verify.h $(OBJDIR)/vfile_.c:$(OBJDIR)/vfile.h $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h $(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h $(OBJDIR)/zip_.c:$(OBJDIR)/zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h $(OBJDIR)/VERSION.h + $(MAKEHEADERS) $(OBJDIR)/add_.c:$(OBJDIR)/add.h $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h $(OBJDIR)/browse_.c:$(OBJDIR)/browse.h $(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h $(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h $(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h $(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h $(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h $(OBJDIR)/clone_.c:$(OBJDIR)/clone.h $(OBJDIR)/comformat_.c:$(OBJDIR)/comformat.h $(OBJDIR)/configure_.c:$(OBJDIR)/configure.h $(OBJDIR)/content_.c:$(OBJDIR)/content.h $(OBJDIR)/db_.c:$(OBJDIR)/db.h $(OBJDIR)/delta_.c:$(OBJDIR)/delta.h $(OBJDIR)/deltacmd_.c:$(OBJDIR)/deltacmd.h $(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h $(OBJDIR)/diff_.c:$(OBJDIR)/diff.h $(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h $(OBJDIR)/event_.c:$(OBJDIR)/event.h $(OBJDIR)/export_.c:$(OBJDIR)/export.h $(OBJDIR)/file_.c:$(OBJDIR)/file.h $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h $(OBJDIR)/http_.c:$(OBJDIR)/http.h $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h $(OBJDIR)/import_.c:$(OBJDIR)/import.h $(OBJDIR)/info_.c:$(OBJDIR)/info.h $(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h $(OBJDIR)/login_.c:$(OBJDIR)/login.h $(OBJDIR)/main_.c:$(OBJDIR)/main.h $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h $(OBJDIR)/name_.c:$(OBJDIR)/name.h $(OBJDIR)/path_.c:$(OBJDIR)/path.h $(OBJDIR)/pivot_.c:$(OBJDIR)/pivot.h $(OBJDIR)/popen_.c:$(OBJDIR)/popen.h $(OBJDIR)/pqueue_.c:$(OBJDIR)/pqueue.h $(OBJDIR)/printf_.c:$(OBJDIR)/printf.h $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h $(OBJDIR)/report_.c:$(OBJDIR)/report.h $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h $(OBJDIR)/search_.c:$(OBJDIR)/search.h $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h $(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h $(OBJDIR)/style_.c:$(OBJDIR)/style.h $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h $(OBJDIR)/update_.c:$(OBJDIR)/update.h $(OBJDIR)/url_.c:$(OBJDIR)/url.h $(OBJDIR)/user_.c:$(OBJDIR)/user.h $(OBJDIR)/verify_.c:$(OBJDIR)/verify.h $(OBJDIR)/vfile_.c:$(OBJDIR)/vfile.h $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h $(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h $(OBJDIR)/zip_.c:$(OBJDIR)/zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h $(OBJDIR)/VERSION.h echo Done >$(OBJDIR)/headers $(OBJDIR)/headers: Makefile Makefile: $(OBJDIR)/add_.c: $(SRCDIR)/add.c $(OBJDIR)/translate @@ -671,38 +659,10 @@ $(OBJDIR)/info.o: $(OBJDIR)/info_.c $(OBJDIR)/info.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/info.o -c $(OBJDIR)/info_.c info.h: $(OBJDIR)/headers -$(OBJDIR)/json_.c: $(SRCDIR)/json.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json.c >$(OBJDIR)/json_.c - -$(OBJDIR)/json.o: $(OBJDIR)/json_.c $(OBJDIR)/json.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/json.o -c $(OBJDIR)/json_.c - -json.h: $(OBJDIR)/headers -$(OBJDIR)/json_login_.c: $(SRCDIR)/json_login.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_login.c >$(OBJDIR)/json_login_.c - -$(OBJDIR)/json_login.o: $(OBJDIR)/json_login_.c $(OBJDIR)/json_login.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/json_login.o -c $(OBJDIR)/json_login_.c - -json_login.h: $(OBJDIR)/headers -$(OBJDIR)/json_timeline_.c: $(SRCDIR)/json_timeline.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_timeline.c >$(OBJDIR)/json_timeline_.c - -$(OBJDIR)/json_timeline.o: $(OBJDIR)/json_timeline_.c $(OBJDIR)/json_timeline.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/json_timeline.o -c $(OBJDIR)/json_timeline_.c - -json_timeline.h: $(OBJDIR)/headers -$(OBJDIR)/json_wiki_.c: $(SRCDIR)/json_wiki.c $(OBJDIR)/translate - $(TRANSLATE) $(SRCDIR)/json_wiki.c >$(OBJDIR)/json_wiki_.c - -$(OBJDIR)/json_wiki.o: $(OBJDIR)/json_wiki_.c $(OBJDIR)/json_wiki.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/json_wiki.o -c $(OBJDIR)/json_wiki_.c - -json_wiki.h: $(OBJDIR)/headers $(OBJDIR)/leaf_.c: $(SRCDIR)/leaf.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/leaf.c >$(OBJDIR)/leaf_.c $(OBJDIR)/leaf.o: $(OBJDIR)/leaf_.c $(OBJDIR)/leaf.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/leaf.o -c $(OBJDIR)/leaf_.c @@ -1010,18 +970,14 @@ zip.h: $(OBJDIR)/headers $(OBJDIR)/sqlite3.o: $(SRCDIR)/sqlite3.c $(XTCC) -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_STAT3 -Dlocaltime=fossil_localtime -DSQLITE_ENABLE_LOCKING_STYLE=0 -c $(SRCDIR)/sqlite3.c -o $(OBJDIR)/sqlite3.o -$(OBJDIR)/cson_amalgamation.o: $(SRCDIR)/cson_amalgamation.c - $(XTCC) -c $(SRCDIR)/cson_amalgamation.c -o $(OBJDIR)/cson_amalgamation.o - -$(OBJDIR)/json.o $(OBJDIR)/json_login.o $(OBJDIR)/json_wiki.o $(OBJDIR)/json_timeline.o : $(SRCDIR)/json_detail.h $(OBJDIR)/shell.o: $(SRCDIR)/shell.c $(SRCDIR)/sqlite3.h $(XTCC) -Dmain=sqlite3_shell -DSQLITE_OMIT_LOAD_EXTENSION=1 -c $(SRCDIR)/shell.c -o $(OBJDIR)/shell.o $(OBJDIR)/th.o: $(SRCDIR)/th.c $(XTCC) -I$(SRCDIR) -c $(SRCDIR)/th.c -o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o: $(SRCDIR)/th_lang.c $(XTCC) -I$(SRCDIR) -c $(SRCDIR)/th_lang.c -o $(OBJDIR)/th_lang.o Index: win/Makefile.msc ================================================================== --- win/Makefile.msc +++ win/Makefile.msc @@ -36,13 +36,13 @@ LIBS = $(ZLIB) ws2_32.lib advapi32.lib $(SSLLIB) LIBDIR = -LIBPATH:$(MSCDIR)\extra\lib -LIBPATH:$(ZLIBDIR) SQLITE_OPTIONS = /DSQLITE_OMIT_LOAD_EXTENSION=1 /DSQLITE_THREADSAFE=0 /DSQLITE_DEFAULT_FILE_FORMAT=4 /DSQLITE_ENABLE_STAT3 /Dlocaltime=fossil_localtime /DSQLITE_ENABLE_LOCKING_STYLE=0 -SRC = add_.c allrepo_.c attach_.c bag_.c bisect_.c blob_.c branch_.c browse_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c db_.c delta_.c deltacmd_.c descendants_.c diff_.c diffcmd_.c doc_.c encode_.c event_.c export_.c file_.c finfo_.c glob_.c graph_.c gzip_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c json_.c json_login_.c json_timeline_.c json_wiki_.c leaf_.c login_.c main_.c manifest_.c md5_.c merge_.c merge3_.c name_.c path_.c pivot_.c popen_.c pqueue_.c printf_.c rebuild_.c report_.c rss_.c schema_.c search_.c setup_.c sha1_.c shun_.c skins_.c sqlcmd_.c stash_.c stat_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c update_.c url_.c user_.c verify_.c vfile_.c wiki_.c wikiformat_.c winhttp_.c xfer_.c zip_.c +SRC = add_.c allrepo_.c attach_.c bag_.c bisect_.c blob_.c branch_.c browse_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c db_.c delta_.c deltacmd_.c descendants_.c diff_.c diffcmd_.c doc_.c encode_.c event_.c export_.c file_.c finfo_.c glob_.c graph_.c gzip_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c leaf_.c login_.c main_.c manifest_.c md5_.c merge_.c merge3_.c name_.c path_.c pivot_.c popen_.c pqueue_.c printf_.c rebuild_.c report_.c rss_.c schema_.c search_.c setup_.c sha1_.c shun_.c skins_.c sqlcmd_.c stash_.c stat_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c update_.c url_.c user_.c verify_.c vfile_.c wiki_.c wikiformat_.c winhttp_.c xfer_.c zip_.c -OBJ = $(OX)\add$O $(OX)\allrepo$O $(OX)\attach$O $(OX)\bag$O $(OX)\bisect$O $(OX)\blob$O $(OX)\branch$O $(OX)\browse$O $(OX)\captcha$O $(OX)\cgi$O $(OX)\checkin$O $(OX)\checkout$O $(OX)\clearsign$O $(OX)\clone$O $(OX)\comformat$O $(OX)\configure$O $(OX)\content$O $(OX)\db$O $(OX)\delta$O $(OX)\deltacmd$O $(OX)\descendants$O $(OX)\diff$O $(OX)\diffcmd$O $(OX)\doc$O $(OX)\encode$O $(OX)\event$O $(OX)\export$O $(OX)\file$O $(OX)\finfo$O $(OX)\glob$O $(OX)\graph$O $(OX)\gzip$O $(OX)\http$O $(OX)\http_socket$O $(OX)\http_ssl$O $(OX)\http_transport$O $(OX)\import$O $(OX)\info$O $(OX)\json$O $(OX)\json_login$O $(OX)\json_timeline$O $(OX)\json_wiki$O $(OX)\leaf$O $(OX)\login$O $(OX)\main$O $(OX)\manifest$O $(OX)\md5$O $(OX)\merge$O $(OX)\merge3$O $(OX)\name$O $(OX)\path$O $(OX)\pivot$O $(OX)\popen$O $(OX)\pqueue$O $(OX)\printf$O $(OX)\rebuild$O $(OX)\report$O $(OX)\rss$O $(OX)\schema$O $(OX)\search$O $(OX)\setup$O $(OX)\sha1$O $(OX)\shun$O $(OX)\skins$O $(OX)\sqlcmd$O $(OX)\stash$O $(OX)\stat$O $(OX)\style$O $(OX)\sync$O $(OX)\tag$O $(OX)\tar$O $(OX)\th_main$O $(OX)\timeline$O $(OX)\tkt$O $(OX)\tktsetup$O $(OX)\undo$O $(OX)\update$O $(OX)\url$O $(OX)\user$O $(OX)\verify$O $(OX)\vfile$O $(OX)\wiki$O $(OX)\wikiformat$O $(OX)\winhttp$O $(OX)\xfer$O $(OX)\zip$O $(OX)\shell$O $(OX)\sqlite3$O $(OX)\th$O $(OX)\th_lang$O +OBJ = $(OX)\add$O $(OX)\allrepo$O $(OX)\attach$O $(OX)\bag$O $(OX)\bisect$O $(OX)\blob$O $(OX)\branch$O $(OX)\browse$O $(OX)\captcha$O $(OX)\cgi$O $(OX)\checkin$O $(OX)\checkout$O $(OX)\clearsign$O $(OX)\clone$O $(OX)\comformat$O $(OX)\configure$O $(OX)\content$O $(OX)\db$O $(OX)\delta$O $(OX)\deltacmd$O $(OX)\descendants$O $(OX)\diff$O $(OX)\diffcmd$O $(OX)\doc$O $(OX)\encode$O $(OX)\event$O $(OX)\export$O $(OX)\file$O $(OX)\finfo$O $(OX)\glob$O $(OX)\graph$O $(OX)\gzip$O $(OX)\http$O $(OX)\http_socket$O $(OX)\http_ssl$O $(OX)\http_transport$O $(OX)\import$O $(OX)\info$O $(OX)\leaf$O $(OX)\login$O $(OX)\main$O $(OX)\manifest$O $(OX)\md5$O $(OX)\merge$O $(OX)\merge3$O $(OX)\name$O $(OX)\path$O $(OX)\pivot$O $(OX)\popen$O $(OX)\pqueue$O $(OX)\printf$O $(OX)\rebuild$O $(OX)\report$O $(OX)\rss$O $(OX)\schema$O $(OX)\search$O $(OX)\setup$O $(OX)\sha1$O $(OX)\shun$O $(OX)\skins$O $(OX)\sqlcmd$O $(OX)\stash$O $(OX)\stat$O $(OX)\style$O $(OX)\sync$O $(OX)\tag$O $(OX)\tar$O $(OX)\th_main$O $(OX)\timeline$O $(OX)\tkt$O $(OX)\tktsetup$O $(OX)\undo$O $(OX)\update$O $(OX)\url$O $(OX)\user$O $(OX)\verify$O $(OX)\vfile$O $(OX)\wiki$O $(OX)\wikiformat$O $(OX)\winhttp$O $(OX)\xfer$O $(OX)\zip$O $(OX)\shell$O $(OX)\sqlite3$O $(OX)\th$O $(OX)\th_lang$O APPNAME = $(OX)\fossil$(E) all: $(OX) $(APPNAME) @@ -88,14 +88,10 @@ echo $(OX)\http_socket.obj >> $@ echo $(OX)\http_ssl.obj >> $@ echo $(OX)\http_transport.obj >> $@ echo $(OX)\import.obj >> $@ echo $(OX)\info.obj >> $@ - echo $(OX)\json.obj >> $@ - echo $(OX)\json_login.obj >> $@ - echo $(OX)\json_timeline.obj >> $@ - echo $(OX)\json_wiki.obj >> $@ echo $(OX)\leaf.obj >> $@ echo $(OX)\login.obj >> $@ echo $(OX)\main.obj >> $@ echo $(OX)\manifest.obj >> $@ echo $(OX)\md5.obj >> $@ @@ -174,12 +170,10 @@ $(OX)\th_lang$O : $(SRCDIR)\th_lang.c $(TCC) /Fo$@ -c $** VERSION.h : mkversion$E $B\manifest.uuid $B\manifest $B\VERSION $** > $@ -$(OBJDIR)\cson_amalgamation.h : $(SRCDIR)\cson_amalgamation.h - cp $(SRCDIR)\cson_amalgamation.h $@ page_index.h: mkindex$E $(SRC) $** > $@ clean: @@ -188,15 +182,10 @@ -del headers linkopts realclean: -del $(APPNAME) translate$E mkindex$E makeheaders$E mkversion$E -$(OBJDIR)\json$O : $(SRCDIR)\json_detail.h -$(OBJDIR)\json_login$O : $(SRCDIR)\json_detail.h -$(OBJDIR)\json_wiki$O : $(SRCDIR)\json_detail.h -$(OBJDIR)\json_timeline$O : $(SRCDIR)\json_detail.h - $(OX)\add$O : add_.c add.h $(TCC) /Fo$@ -c add_.c add_.c : $(SRCDIR)\add.c @@ -422,34 +411,10 @@ $(TCC) /Fo$@ -c info_.c info_.c : $(SRCDIR)\info.c translate$E $** > $@ -$(OX)\json$O : json_.c json.h - $(TCC) /Fo$@ -c json_.c - -json_.c : $(SRCDIR)\json.c - translate$E $** > $@ - -$(OX)\json_login$O : json_login_.c json_login.h - $(TCC) /Fo$@ -c json_login_.c - -json_login_.c : $(SRCDIR)\json_login.c - translate$E $** > $@ - -$(OX)\json_timeline$O : json_timeline_.c json_timeline.h - $(TCC) /Fo$@ -c json_timeline_.c - -json_timeline_.c : $(SRCDIR)\json_timeline.c - translate$E $** > $@ - -$(OX)\json_wiki$O : json_wiki_.c json_wiki.h - $(TCC) /Fo$@ -c json_wiki_.c - -json_wiki_.c : $(SRCDIR)\json_wiki.c - translate$E $** > $@ - $(OX)\leaf$O : leaf_.c leaf.h $(TCC) /Fo$@ -c leaf_.c leaf_.c : $(SRCDIR)\leaf.c translate$E $** > $@ @@ -711,7 +676,7 @@ zip_.c : $(SRCDIR)\zip.c translate$E $** > $@ headers: makeheaders$E page_index.h VERSION.h - makeheaders$E add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h event_.c:event.h export_.c:export.h file_.c:file.h finfo_.c:finfo.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h json_.c:json.h json_login_.c:json_login.h json_timeline_.c:json_timeline.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h path_.c:path.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR)\cson_amalgamation.h + makeheaders$E add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h event_.c:event.h export_.c:export.h file_.c:file.h finfo_.c:finfo.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h leaf_.c:leaf.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h path_.c:path.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h @copy /Y nul: headers Index: www/quotes.wiki ================================================================== --- www/quotes.wiki +++ www/quotes.wiki @@ -85,6 +85,19 @@ been a smoother ride than Git was. <blockquote> <i>viablepanic at [http://www.reddit.com/r/programming/comments/bxcto/why_not_fossil_scm/]</i> </blockquote> + +<li>In the fossil community - and hence in fossil itself - development history +is pretty much sacrosanct. The very name "fossil" was to chosen to +reflect the unchanging nature of things in that history. + +<p>In git (or rather, the git community), the development history is part of +the published aspect of the project, so it provides tools for rearranging +that history so you can present what you "should" have done rather +than what you actually did. + +<blockquote> +<i>Mike Meyer on the Fossil mailing list, 2011-10-04</i> +</blockquote> </ol>