Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | climb up the trunk. From up here, clarify wording of the "building and installing" wiki page: you don't need to log in to get the source code for released versions of fossil, the download page will have a shiny source package for you to fetch. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | msw-docco |
| Files: | files | file ages | folders |
| SHA1: |
587dd57fe194af817ca5658702f6b6b7 |
| User & Date: | martin.weber 2012-02-10 18:02:40.391 |
| Original Comment: | climb up the trunk. |
Context
|
2012-02-10
| ||
| 23:03 | Merge in documentation updates. check-in: 75ea5ac672 user: drh tags: trunk | |
| 18:02 | climb up the trunk. From up here, clarify wording of the "building and installing" wiki page: you don't need to log in to get the source code for released versions of fossil, the download page will have a shiny source package for you to fetch. Closed-Leaf check-in: 587dd57fe1 user: martin.weber tags: msw-docco | |
| 15:26 | Attempting to do a better job of merging renames. check-in: f0359882c9 user: drh tags: trunk | |
|
2011-09-15
| ||
| 00:54 | Further progress towards jerusalem on yet another holy docco crusade! Checkin #3/n check-in: 17f21f2482 user: martin.weber tags: msw-docco | |
Changes
Changes to Makefile.in.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #!/usr/bin/make # # This is the top-level makefile for Fossil when the build is occurring # on a unix platform. This works out-of-the-box on most unix platforms. # But you are free to vary some of the definitions if desired. # #### The toplevel directory of the source tree. Fossil can be built # in a directory that is separate from the source tree. Just change # the following to point from the build directory to the src/ folder. # SRCDIR = @srcdir@/src #### The directory into which object code files should be written. # # | > > > > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #!/usr/bin/make # # This is the top-level makefile for Fossil when the build is occurring # on a unix platform. This works out-of-the-box on most unix platforms. # But you are free to vary some of the definitions if desired. # #### The toplevel directory of the source tree. Fossil can be built # in a directory that is separate from the source tree. Just change # the following to point from the build directory to the src/ folder. # SRCDIR = @srcdir@/src #### The directory into which object code files should be written. # Having a "./" prefix in the value of this variable breaks our use of the # "makeheaders" tool when running make on the MinGW platform, apparently # due to some command line argument manipulation performed automatically # by the shell. # # OBJDIR = bld #### C Compiler and options for use in building executables that # will run on the platform that is doing the build. This is used # to compile code-generator programs as part of the build process. # See TCC below for the C compiler for building the finished binary. # BCC = @CC_FOR_BUILD@ |
| ︙ | ︙ | |||
34 35 36 37 38 39 40 41 42 43 44 45 | # TCLSH = tclsh LIB = @LDFLAGS@ @EXTRA_LDFLAGS@ @LIBS@ TCC += @EXTRA_CFLAGS@ @CPPFLAGS@ @CFLAGS@ -DHAVE_AUTOCONFIG_H INSTALLDIR = $(DESTDIR)@prefix@/bin USE_SYSTEM_SQLITE = @USE_SYSTEM_SQLITE@ include $(SRCDIR)/main.mk distclean: clean rm -f autoconfig.h config.log Makefile | > | 38 39 40 41 42 43 44 45 46 47 48 49 50 | # TCLSH = tclsh LIB = @LDFLAGS@ @EXTRA_LDFLAGS@ @LIBS@ TCC += @EXTRA_CFLAGS@ @CPPFLAGS@ @CFLAGS@ -DHAVE_AUTOCONFIG_H INSTALLDIR = $(DESTDIR)@prefix@/bin USE_SYSTEM_SQLITE = @USE_SYSTEM_SQLITE@ FOSSIL_ENABLE_TCL = @FOSSIL_ENABLE_TCL@ include $(SRCDIR)/main.mk distclean: clean rm -f autoconfig.h config.log Makefile |
Changes to VERSION.
|
| | | 1 | 1.22 |
Added ajax/README.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
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:
<VirtualHost *:80>
ServerAlias fjson
ScriptAlias /cgi-bin/ /home/stephan/cvs/fossil/fossil-json/ajax/cgi-bin/
DocumentRoot /home/stephan/cvs/fossil/fossil-json/ajax
</VirtualHost>
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.
|
Added ajax/cgi-bin/fossil-json.cgi.example.
> > | 1 2 | #!/path/to/fossil/binary repository: /path/to/repo.fsl |
Added ajax/i-test/rhino-shell.js.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
var FShell = {
serverUrl:
'http://localhost:8080'
//'http://fjson/cgi-bin/fossil-json.cgi'
//'http://192.168.1.62:8080'
//'http://fossil.wanderinghorse.net/repos/fossil-json-java/index.cgi'
,
verbose:false,
prompt:"fossil shell > ",
wiki:{},
consol:java.lang.System.console(),
v:function(msg){
if(this.verbose){
print("VERBOSE: "+msg);
}
}
};
(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;
FShell.fossil = new FossilAjaj({
asynchronous:false, /* rhino-based impl doesn't support async. */
timeout:10000,
url:FShell.serverUrl
});
print("Server: "+FShell.serverUrl);
var cb = FShell.fossil.ajaj.callbacks;
cb.beforeSend = function(req,opt){
if(!FShell.verbose) return;
print("SENDING REQUEST: AJAJ options="+JSON.stringify(opt));
if(req) print("Request envelope="+WhAjaj.stringify(req));
};
cb.afterSend = function(req,opt){
//if(!FShell.verbose) return;
//print("REQUEST RETURNED: opt="+JSON.stringify(opt));
//if(req) print("Request="+WhAjaj.stringify(req));
};
cb.onError = function(req,opt){
//if(!FShell.verbose) return;
print("ERROR: "+WhAjaj.stringify(opt));
};
cb.onResponse = function(resp,req){
if(!FShell.verbose) return;
if(resp && resp.resultCode){
print("Response contains error info: "+resp.resultCode+": "+resp.resultText);
}
print("GOT RESPONSE: "+(('string'===typeof resp) ? resp : WhAjaj.stringify(resp)));
};
FShell.fossil.HAI({
onResponse:function(resp,opt){
assertResponseOK(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);
}
}
/**
Convenience form of FShell.fossil.sendCommand(command,payload,ajajOpt).
*/
function send(command,payload, ajajOpt){
FShell.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 );
}
}
FShell.readline = (typeof readline === 'function') ? (readline) : (function() {
importPackage(java.io);
importPackage(java.lang);
var stdin = new BufferedReader(new InputStreamReader(System['in']));
var self = this;
return function(prompt) {
if(prompt) print(prompt);
var x = stdin.readLine();
return null===x ? x : String(x) /*convert to JS string!*/;
};
}());
FShell.dispatchLine = function(line){
var av = line.split(' '); // FIXME: to shell-like tokenization. Too tired!
var cmd = av[0];
var key, h;
if('/' == cmd[0]) key = '/';
else key = this.commandAliases[cmd];
if(!key) key = cmd;
h = this.commandHandlers[key];
if(!h){
print("Command not known: "+cmd +" ("+key+")");
}else if(!WhAjaj.isFunction(h)){
print("Not a function: "+key);
}
else{
print("Sending ["+key+"] command... ");
try{h(av);}
catch(e){ print("EXCEPTION: "+e); }
}
};
FShell.onResponseDefault = function(callback){
return function(resp,req){
assertResponseOK(resp);
print("Payload: "+(resp.payload ? WhAjaj.stringify(resp.payload) : "none"));
if(WhAjaj.isFunction(callback)){
callback(resp,req);
}
};
};
FShell.commandHandlers = {
"?":function(args){
var k;
print("Available commands...\n");
var o = FShell.commandHandlers;
for(k in o){
if(! o.hasOwnProperty(k)) continue;
print("\t"+k);
}
},
"/":function(args){
FShell.fossil.sendCommand('/json'+args[0],undefined,{
beforeSend:function(req,opt){
print("Sending to: "+opt.url);
},
onResponse:FShell.onResponseDefault()
});
},
"eval":function(args){
eval(args.join(' '));
},
"login":function(args){
FShell.fossil.login(args[1], args[2], {
onResponse:FShell.onResponseDefault()
});
},
"whoami":function(args){
FShell.fossil.whoami({
onResponse:FShell.onResponseDefault()
});
},
"HAI":function(args){
FShell.fossil.HAI({
onResponse:FShell.onResponseDefault()
});
}
};
FShell.commandAliases = {
"li":"login",
"lo":"logout",
"who":"whoami",
"hi":"HAI",
"tci":"/timeline/ci?limit=3"
};
FShell.mainLoop = function(){
var line;
var check = /\S/;
//var isJavaNull = /java\.lang\.null/;
//print(typeof java.lang['null']);
while( null != (line=this.readline(this.prompt)) ){
if(null===line) break /*EOF*/;
else if( "" === line ) continue;
//print("Got line: "+line);
else if(!check.test(line)) continue;
print('typeof line = '+typeof line);
this.dispatchLine(line);
print("");
}
print("Bye!");
};
FShell.mainLoop();
|
Added ajax/i-test/rhino-test.js.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
var TestApp = {
serverUrl:
'http://localhost:8080'
//'http://fjson/cgi-bin/fossil-json.cgi'
//'http://192.168.1.62:8080'
//'http://fossil.wanderinghorse.net/repos/fossil-json-java/index.cgi'
,
verbose:false,
fossilBinary:'fossil',
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. */
timeout:0,
url:TestApp.serverUrl,
fossilBinary:TestApp.fossilBinary
});
var cb = TestApp.fossil.ajaj.callbacks;
cb.beforeSend = function(req,opt){
if(!TestApp.verbose) return;
print("SENDING REQUEST: AJAJ options="+JSON.stringify(opt));
if(req) print("Request envelope="+WhAjaj.stringify(req));
};
cb.afterSend = function(req,opt){
//if(!TestApp.verbose) return;
//print("REQUEST RETURNED: opt="+JSON.stringify(opt));
//if(req) print("Request="+WhAjaj.stringify(req));
};
cb.onError = function(req,opt){
if(!TestApp.verbose) return;
print("ERROR: "+WhAjaj.stringify(opt));
};
cb.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){
print("Assertion FAILED: "+descr);
throw new Error("Assertion failed: "+descr);
// aarrgghh. Exceptions are of course swallowed by
// the AJAX layer, to keep from killing a browser's
// script environment.
}else{
if(TestApp.verbose) 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{
if(TestApp.verbose) 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( undefined === resp.resultCode, 'resp.resultCode is not set');
}
/**
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(){
var rs;
TestApp.fossil.HAI({
onResponse:function(resp,req){
rs = resp;
}
});
assertResponseOK(rs);
TestApp.serverVersion = rs.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.auth.name, 'User == nobody.' );
assert(!TestApp.fossil.auth.authToken, 'authToken is not set.' );
}
testIAmNobody.description = 'Ensure that current user is "nobody".';
function testAnonymousLogin(){
TestApp.fossil.login();
assert('string' === typeof TestApp.fossil.auth.authToken, 'authToken = '+TestApp.fossil.auth.authToken);
assert( 'string' === typeof TestApp.fossil.auth.name, 'User name = '+TestApp.fossil.auth.name);
TestApp.fossil.userName = null;
TestApp.fossil.whoami('/json/whoami');
assert( 'string' === typeof TestApp.fossil.auth.name, 'User name = '+TestApp.fossil.auth.name);
}
testAnonymousLogin.description = 'Perform anonymous login.';
function testAnonWiki(){
var rs;
TestApp.fossil.sendCommand('/json/wiki/list',undefined,{
beforeSend:function(req,opt){
assert( req && (req.authToken==TestApp.fossil.auth.authToken), 'Request envelope contains expected authToken.' );
},
onResponse:function(resp,req){
rs = resp;
}
});
assertResponseOK(rs);
assert( (typeof [] === typeof rs.payload) && rs.payload.length,
"Wiki list seems to be okay.");
TestApp.wiki.list = rs.payload;
TestApp.fossil.sendCommand('/json/wiki/get',{
name:TestApp.wiki.list[0]
},{
onResponse:function(resp,req){
rs = resp;
}
});
assertResponseOK(rs);
assert(rs.payload.name == TestApp.wiki.list[0], "Fetched page name matches expectations.");
print("Got first wiki page: "+WhAjaj.stringify(rs.payload));
}
testAnonWiki.description = 'Fetch wiki list as anonymous user.';
function testAnonLogout(){
var rs;
TestApp.fossil.logout({
onResponse:function(resp,req){
rs = resp;
}
});
assertResponseOK(rs);
print("Ensure that second logout attempt fails...");
TestApp.fossil.logout({
onResponse:function(resp,req){
rs = resp;
}
});
assertResponseError(rs);
}
testAnonLogout.description = 'Log out anonymous user.';
function testExternalProcess(){
var req = { command:"HAI", requestId:'testExternalProcess()' };
var args = [TestApp.fossilBinary, 'json', '--json-input', '-'];
var p = java.lang.Runtime.getRuntime().exec(args);
var outs = p.getOutputStream();
var osr = new java.io.OutputStreamWriter(outs);
var osb = new java.io.BufferedWriter(osr);
var json = JSON.stringify(req);
osb.write(json,0, json.length);
//osb.flush();
osb.close();
var ins = p.getInputStream();
var isr = new java.io.InputStreamReader(ins);
var br = new java.io.BufferedReader(isr);
var line;
while( null !== (line=br.readLine())){
print(line);
}
//outs.close();
ins.close();
}
testExternalProcess.description = 'Run fossil as external process.';
function testExternalProcessHandler(){
var aj = TestApp.fossil.ajaj;
var oldImpl = aj.sendImpl;
aj.sendImpl = FossilAjaj.rhinoLocalBinarySendImpl;
var rs;
TestApp.fossil.sendCommand('/json/HAI',undefined,{
onResponse:function(resp,opt){
rs = resp;
}
});
aj.sendImpl = oldImpl;
assertResponseOK(rs);
print("Using local fossil binary via AJAX interface, we fetched: "+
WhAjaj.stringify(rs));
}
testExternalProcessHandler.description = 'Try local fossil binary via AJAX interface.';
(function runAllTests(){
var testList = [
testHAI,
testIAmNobody,
testAnonymousLogin,
testAnonWiki,
testAnonLogout,
//testExternalProcess,
testExternalProcessHandler
];
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 #"+(i+1)+" failed: "+e);
throw e;
}
}
})();
print("Done! If you don't see an exception message in the last few lines, you win!");
|
Added ajax/index.html.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Fossil/JSON raw request sending</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
<script type="text/javascript" src="js/whajaj.js"></script>
<script type="text/javascript" src="js/fossil-ajaj.js"></script>
<style type='text/css'>
th {
text-align: left;
background-color: #ececec;
}
.dangerWillRobinson {
background-color: yellow;
}
</style>
<script type='text/javascript'>
WhAjaj.Connector.options.ajax.url =
/*
Change this to your CGI/server root path:
*/
//'http://fjson/cgi-bin/fossil.cgi'
//'/repos/fossil-sgb/json.cgi'
'/cgi-bin/fossil-json.cgi'
;
var TheApp = {
response:null,
sessionID:null,
jqe:{}/*jqe==jQuery Elements*/,
ajaxCount:0,
cgi: new FossilAjaj()
};
TheApp.startAjaxNotif = function()
{
++this.ajaxCount;
TheApp.jqe.responseContainer.removeClass('dangerWillRobinson');
this.jqe.ajaxNotification.attr( 'title', this.ajaxCount+" pending AJAX operation(s)..." );
if( 1 == this.ajaxCount ) this.jqe.ajaxNotification.fadeIn();
};
TheApp.endAjaxNotif = function()
{
--this.ajaxCount;
this.jqe.ajaxNotification.attr( 'title', this.ajaxCount+" pending AJAX operation(s)..." );
if( 0 == this.ajaxCount ) this.jqe.ajaxNotification.fadeOut();
};
TheApp.responseContainsError = function(resp) {
if( resp && resp.resultCode ) {
//alert("Error response:\n"+JSON.stringify(resp,0,4));
TheApp.jqe.taResponse.val( "RESPONSE CONTAINS ERROR INFO:\n"+WhAjaj.stringify(resp) );
//TheApp.jqe.responseContainer.css({backgroundColor:'yellow'});
//TheApp.jqe.responseContainer.addClass('dangerWillRobinson');
TheApp.jqe.responseContainer.flash( '255,0,0', 1500 );
return true;
}
return false;
};
TheApp.sendRequest = function() {
var path = this.jqe.textPath.val();
var self = this;
var data = this.jqe.taRequest.val();
var doPost = (data && data.length);
var req;
if( doPost ) try {
req = JSON.parse(data);
}
catch(e) {
TheApp.jqe.taResponse.val("Request is not valid JSON.\n"+e);
return;
}
if( req ) {
req.requestId = this.cgi.generateRequestId();
}
var self = this;
var opt = {
url: WhAjaj.Connector.options.ajax.url + path,
method: doPost ? 'POST' : 'GET'
};
this.cgi.sendRequest( req, opt );
};
jQuery.fn.animateHighlight = function(highlightColor, duration) {
var highlightBg = highlightColor || "#FFFF9C";
var animateMs = duration || 1500;
var originalBg = this.css("backgroundColor");
this.stop().css("background-color", highlightBg).animate({backgroundColor: originalBg}, animateMs);
};
jQuery.fn.flash = function( color, duration )
{
var current = this.css( 'color' );
this.animate( { color: 'rgb(' + color + ')' }, duration / 2);
this.animate( { color: current }, duration / 2 );
};
function myJsonPCallback(obj){
alert("JSONP callback got:\n"+WhAjaj.stringify(obj));
}
jQuery(document).ready(function(){
var ids = [// list of HTML element IDs we use often.
'btnSend',
'ajaxNotification',
'currentAuthToken',
'responseContainer',
'taRequest',
'taRequestOpt',
'taResponse',
'textPath',
'timer'
];
var i, k;
for( i = 0; i < ids.length; ++i ) {
k = ids[i];
TheApp.jqe[k] = jQuery('#'+k);
}
TheApp.jqe.textPath.
keyup(function(event){
if(event.keyCode == 13){
TheApp.sendRequest();
}
});
TheApp.timer = {
_tstart:0,_tend:0,duration:0,
start:function(){
this._tstart = (new Date()).getTime();
},
end:function(){
this._tend = (new Date()).getTime();
return this.duration = this._tend - this._tstart;
}
};
var ajcb = TheApp.cgi.ajaj.callbacks;
ajcb.beforeSend = function(req,opt) {
TheApp.timer.start();
var val =
req ?
(('string'===typeof req) ? req : WhAjaj.stringify(req))
: '';
TheApp.jqe.taResponse.val('');
TheApp.jqe.taRequest.val( val );
TheApp.jqe.taRequestOpt.val( opt ? WhAjaj.stringify(opt) : '' );
TheApp.startAjaxNotif();
};
ajcb.afterSend = function(req,opt) {
TheApp.timer.end();
TheApp.endAjaxNotif();
TheApp.jqe.timer.text( "(Round-trip time (incl. JS overhead): "+TheApp.timer.duration+'ms)' );
};
ajcb.onResponse = function(resp,req, opt) {
var val;
if(this.jsonp) return /*was already handled*/;
try {
val = WhAjaj.stringify(resp);
}
catch(e) {
val = WhAjaj.stringify(e)
}
//alert("onResponse this:"+WhAjaj.stringify(this));
//alert("val="+val);
// FIXME: this.url is hosed for login because of how i overload onResponse()
if( opt.url ) TheApp.jqe.textPath.val(opt.url.replace(WhAjaj.Connector.options.ajax.url,''));
TheApp.jqe.taResponse.val( val );
};
ajcb.onError = function(req,opt) {
TheApp.jqe.taResponse.val( "ERROR SENDING REQUEST:\n"+WhAjaj.stringify(opt) );
};
TheApp.cgi.onLogin = function(){
TheApp.jqe.taResponse.val( "Logged in:\n"+WhAjaj.stringify(this.auth));
TheApp.jqe.currentAuthToken.html("Logged in: "+WhAjaj.stringify(this.auth));
};
TheApp.cgi.onLogout = function(){
TheApp.jqe.taResponse.val( "Logged out!" );
TheApp.jqe.currentAuthToken.text("Not logged in");
};
TheApp.cgi.whoami();
jQuery('#headerArea').click(function(){
jQuery(this).slideUp('fast',function(){
jQuery(this).remove();
});
});
});
</script>
</head>
<body>
<span id='ajaxNotification'></span>
<div id='headerArea'>
<h1>You know, for sending raw JSON requests to Fossil...</h1>
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.
<br><br>
JSON API docs: <a href='https://docs.google.com/document/d/1fXViveNhDbiXgCuE7QDXQOKeFzf2qNUkBEgiUvoqFN4/edit'>https://docs.google.com/document/d/1fXViveNhDbiXgCuE7QDXQOKeFzf2qNUkBEgiUvoqFN4/edit</a>
</div><!-- #headerArea -->
See also: <a href='wiki-editor.html'>prototype wiki editor</a>.
<h2>Request...</h2>
Path: <input type='text' size='40' id='textPath' value='/json/HAI'/>
<input type='button' value='Send...' id='btnSend' onclick='TheApp.sendRequest()' /><br/>
If the POST textarea is not empty then it will be posted with the request.
<hr/>
<strong>Quick-posts:</strong><br/>
<input type='button' value='HAI' onclick='TheApp.cgi.HAI()' />
<input type='button' value='HAI JSONP' onclick='TheApp.cgi.sendCommand("/json/HAI",undefined,{jsonp:"myJsonPCallback"});' />
<input type='button' value='version' onclick='TheApp.cgi.sendCommand("/json/version")' />
<input type='button' value='stat' onclick='TheApp.cgi.sendCommand("/json/stat?full=0")' />
<input type='button' value='whoami' onclick='TheApp.cgi.whoami()' />
<input type='button' value='cap' onclick='TheApp.cgi.sendCommand("/json/cap")' />
<input type='button' value='resultCodes' onclick='TheApp.cgi.sendCommand("/json/resultCodes")' />
<input type='button' value='g' onclick='TheApp.cgi.sendCommand("/json/g")' />
<br/>
<input type='button' value='branch/list' onclick='TheApp.cgi.sendCommand("/json/branch/list")' />
<input type='button' value='timeline/ci' onclick='TheApp.cgi.sendCommand("/json/timeline/ci?files=true")' />
<input type='button' value='timeline/wiki' onclick='TheApp.cgi.sendCommand("/json/timeline/wiki")' />
<input type='button' value='timeline/ticket' onclick='TheApp.cgi.sendCommand("/json/timeline/ticket")' />
<input type='button' value='timeline/branch' onclick='TheApp.cgi.sendCommand("/json/timeline/branch")' />
<input type='button' value='wiki/list' onclick='TheApp.cgi.sendCommand("/json/wiki/list")' />
<input type='button' value='wiki/get Fossil' onclick='TheApp.cgi.sendCommand("/json/wiki/get",{name:"Fossil"})' />
<input type='button' value='wiki/get/Fossil' onclick='TheApp.cgi.sendCommand("/json/wiki/get/Fossil")' />
<br/>
<input type='button' value='user/list' onclick='TheApp.cgi.sendCommand("/json/user/list")' />
<input type='button' value='user/get' onclick='TheApp.cgi.sendCommand("/json/user/get?name=anonymous")' />
<input type='button' value='tag/list' onclick='TheApp.cgi.sendCommand("/json/tag/list?includeTickets=false&raw=false")' />
<input type='button' value='tag/list/json' onclick='TheApp.cgi.sendCommand("/json/tag/list/json?raw=false")' />
<input type='button' value='tag/add'
onclick='TheApp.cgi.sendCommand("/json/tag/add",{name:"json-add-tag-test",checkin:"json",value:"tag test",propagate:false,raw:false})' />
<input type='button' value='tag/cancel'
onclick='TheApp.cgi.sendCommand("/json/tag/cancel",{name:"json-add-tag-test",checkin:"json",raw:false})' />
<input type='button' value='tag/find'
onclick='TheApp.cgi.sendCommand("/json/tag/find",{name:"json",type:"*",raw:false,limit:5})' />
<br/>
<input type='button' value='diff'
onclick='TheApp.cgi.sendCommand("/json/diff",{v1:"b0e9b45baed6f885",v2:"5f225e261d836287",context:2})' />
<input type='button' value='diff/A/B'
onclick='TheApp.cgi.sendCommand("/json/diff/b0e9b45baed6f885/5f225e261d836287?context=2")' />
<input type='button' value='query'
onclick='TheApp.cgi.sendCommand("/json/query?format=o","SELECT * from user")' />
<input type='button' value='report list'
onclick='TheApp.cgi.sendCommand("/json/report/list")' />
<input type='button' value='report get'
onclick='TheApp.cgi.sendCommand("/json/report/get",2)' />
<input type='button' value='report run'
onclick='TheApp.cgi.sendCommand("/json/report/run",{"report":2,"format":"o"})' />
<!-- not yet ready...
<input type='button' value='artifact/XYZ' onclick='TheApp.cgi.sendCommand("/json/artifact?uuid=json")' />
-->
<!--
<input type='button' value='get whiki' onclick='TheApp.cgi.getPages("whiki")' />
<input type='button' value='get more' onclick='TheApp.cgi.getPages("HelloWorld/WhikiNews")' />
<input type='button' value='get client data' onclick='TheApp.cgi.getPageClientData("HelloWorld/whiki/WhikiCommands")' />
<input type='button' value='save client data' onclick='TheApp.cgi.savePageClientData({"HelloWorld":[1,3,5]})' />
-->
<hr/>
<b>Login:</b>
<br/>
<input type='button' value='Anon. PW' onclick='TheApp.cgi.sendCommand("/json/anonymousPassword")' />
<input type='button' value='Anon. PW+Login' onclick='TheApp.cgi.login()' />
<br/>
name:<input type='text' id='textUser' value='json-demo' size='12'/>
pw:<input type='password' id='textPassword' value='json-demo' size='12'/>
<input type='button' value='login' onclick='TheApp.cgi.login(jQuery("#textUser").val(),jQuery("#textPassword").val(),{onResponse:TheApp.onLogin})' />
<input type='button' value='logout' onclick='TheApp.cgi.logout()' />
<br/>
<span id='currentAuthToken' style='font-family:monospaced'></span>
<br/>
<hr/>
<table>
<tr>
<th>POST data</th>
<th>Request AJAJ options</th>
</tr>
<tr>
<td width='50%' valign='top'>
<textarea id='taRequest' rows='10' cols='50'></textarea>
</td>
<td width='50%' valign='top'>
<textarea id='taRequestOpt' rows='10' cols='40' readonly></textarea>
</td>
</tr>
<tr>
<th colspan='2'>Response <span id='timer'></span></th>
</tr>
<tr>
<td colspan='2' id='responseContainer' valign='top'>
<textarea id='taResponse' rows='20' cols='80' readonly></textarea>
</td>
</tr>
</table>
<div></div>
<div></div>
<div></div>
</body></html>
|
Added ajax/js/fossil-ajaj.js.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
/**
This file contains a WhAjaj extension for use with Fossil/JSON.
Author: Stephan Beal (sgbeal@googlemail.com)
License: Public Domain
*/
/**
Constructor for a new Fossil AJAJ client. ajajOpt may be an optional
object suitable for passing to the WhAjaj.Connector() constructor.
On returning, this.ajaj is-a WhAjaj.Connector instance which can
be used to send requests to the back-end (though the convenience
functions of this class are the preferred way to do it). Clients
are encouraged to use FossilAjaj.sendCommand() (and friends) instead
of the underlying WhAjaj.Connector API, since this class' API
contains Fossil-specific request-calling handling (e.g. of authentication
info) whereas WhAjaj is more generic.
*/
function FossilAjaj(ajajOpt)
{
this.ajaj = new WhAjaj.Connector(ajajOpt);
return this;
}
FossilAjaj.prototype.generateRequestId = function() {
return this.ajaj.generateRequestId();
};
/**
Proxy for this.ajaj.sendRequest().
*/
FossilAjaj.prototype.sendRequest = function(req,opt) {
return this.ajaj.sendRequest(req,opt);
};
/**
Sends a command to the fossil back-end. Command should be the
path part of the URL, e.g. /json/stat, payload is a request-specific
value type (may often be null/undefined). ajajOpt is an optional object
holding WhAjaj.sendRequest()-compatible options.
This function constructs a Fossil/JSON request envelope based
on the given arguments and adds this.auth.authToken and a requestId
to it.
*/
FossilAjaj.prototype.sendCommand = function(command, payload, ajajOpt) {
var req;
ajajOpt = ajajOpt || {};
if(payload || (this.auth && this.auth.authToken) || ajajOpt.jsonp) {
req = {
payload:payload,
requestId:('function' === typeof this.generateRequestId) ? this.generateRequestId() : undefined,
authToken:(this.auth ? this.auth.authToken : undefined),
jsonp:('string' === typeof ajajOpt.jsonp) ? ajajOpt.jsonp : undefined
};
}
ajajOpt.method = req ? 'POST' : 'GET';
// just for debuggering: ajajOpt.method = 'POST'; if(!req) req={};
if(command) ajajOpt.url = this.ajaj.derivedOption('url',ajajOpt) + command;
this.ajaj.sendRequest(req,ajajOpt);
};
/**
Sends a login request to the back-end.
ajajOpt is an optional configuration object suitable for passing
to sendCommand().
After the response returns, this.auth will be
set to the response payload.
If name === 'anonymous' (the default if none is passed in) then this
function ignores the pw argument and must make two requests - the first
one gets the captcha code and the second one submits it.
ajajOpt.onResponse() (if set) is only called for the actual login
response (the 2nd one), as opposed to being called for both requests.
However, this.ajaj.callbacks.onResponse() _is_ called for both (because
it happens at a lower level).
If this object has an onLogin() function it is called (with
no arguments) before the onResponse() handler of the login is called
(that is the 2nd request for anonymous logins) and any exceptions
it throws are ignored.
*/
FossilAjaj.prototype.login = function(name,pw,ajajOpt) {
name = name || 'anonymous';
var self = this;
var loginReq = {
name:name,
password:pw
};
ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} );
var oldOnResponse = ajajOpt.onResponse;
ajajOpt.onResponse = function(resp,req) {
var thisOpt = this;
//alert('login response:\n'+WhAjaj.stringify(resp));
if( resp && resp.payload ) {
//self.userName = resp.payload.name;
//self.capabilities = resp.payload.capabilities;
self.auth = resp.payload;
}
if( WhAjaj.isFunction( self.onLogin ) ){
try{ self.onLogin(); }
catch(e){}
}
if( WhAjaj.isFunction(oldOnResponse) ) {
oldOnResponse.apply(thisOpt,[resp,req]);
}
};
function doLogin(){
//alert("Sending login request..."+WhAjaj.stringify(loginReq));
self.sendCommand('/json/login', loginReq, ajajOpt);
}
if( 'anonymous' === name ){
this.sendCommand('/json/anonymousPassword',undefined,{
onResponse:function(resp,req){
/*
if( WhAjaj.isFunction(oldOnResponse) ){
oldOnResponse.apply(this, [resp,req]);
};
*/
if(resp && !resp.resultCode){
//alert("Got PW. Trying to log in..."+WhAjaj.stringify(resp));
loginReq.anonymousSeed = resp.payload.seed;
loginReq.password = resp.payload.password;
doLogin();
}
}
});
}
else doLogin();
};
/**
Logs out of fossil, invaliding this login token.
ajajOpt is an optional configuration object suitable for passing
to sendCommand().
If this object has an onLogout() function it is called (with
no arguments) before the onResponse() handler is called.
IFF the response succeeds then this.auth is unset.
*/
FossilAjaj.prototype.logout = function(ajajOpt) {
var self = this;
ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} );
var oldOnResponse = ajajOpt.onResponse;
ajajOpt.onResponse = function(resp,req) {
var thisOpt = this;
self.auth = undefined;
if( WhAjaj.isFunction( self.onLogout ) ){
try{ self.onLogout(); }
catch(e){}
}
if( WhAjaj.isFunction(oldOnResponse) ) {
oldOnResponse.apply(thisOpt,[resp,req]);
}
};
this.sendCommand('/json/logout', undefined, ajajOpt );
};
/**
Sends a HAI request to the server. /json/HAI is an alias /json/version.
ajajOpt is an optional configuration object suitable for passing
to sendCommand().
*/
FossilAjaj.prototype.HAI = function(ajajOpt) {
this.sendCommand('/json/HAI', undefined, ajajOpt);
};
/**
Sends a /json/whoami request. Updates this.auth to contain
the login info, removing them if the response does not contain
that data.
*/
FossilAjaj.prototype.whoami = function(ajajOpt) {
var self = this;
ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} );
var oldOnResponse = ajajOpt.onResponse;
ajajOpt.onResponse = function(resp,req) {
var thisOpt = this;
if( resp && resp.payload ){
if(!self.auth || (self.auth.authToken!==resp.payload.authToken)){
self.auth = resp.payload;
if( WhAjaj.isFunction(self.onLogin) ){
self.onLogin();
}
}
}
else { delete self.auth; }
if( WhAjaj.isFunction(oldOnResponse) ) {
oldOnResponse.apply(thisOpt,[resp,req]);
}
};
self.sendCommand('/json/whoami', undefined, ajajOpt);
};
/**
EXPERIMENTAL concrete WhAjaj.Connector.sendImpl() implementation which
uses Rhino to connect to a local fossil binary for input and output. Its
signature and semantics are as described for
WhAjaj.Connector.prototype.sendImpl(), with a few exceptions and
additions:
- It does not support timeouts or asynchronous mode.
- The args.fossilBinary property must point to the local fossil binary
(it need not be a complete path if fossil is in the $PATH). This
function throws (without calling any request callbacks) if
args.fossilBinary is not set. fossilBinary may be set on
WhAjaj.Connector.options.ajax, in the FossilAjaj constructor call, as
the ajax options parameter to any of the FossilAjaj.sendCommand() family
of functions, or by setting
aFossilAjajInstance.ajaj.options.fossilBinary on a specific
FossilAjaj instance.
- It uses the args.url field to create the "command" property of the
request, constructs a request envelope, spawns a fossil process in JSON
mode, feeds it the request envelope, and returns the response envelope
via the same mechanisms defined for the HTTP-based implementations.
The interface is otherwise compatible with the "normal"
FossilAjaj.sendCommand() front-end (it is, however, fossil-specific, and
not back-end agnostic like the WhAjaj.sendImpl() interface intends).
*/
FossilAjaj.rhinoLocalBinarySendImpl = function(request,args){
var self = this;
request = request || {};
if(!args.fossilBinary){
throw new Error("fossilBinary is not set on AJAX options!");
}
var url = args.url.split('?')[0].split(/\/+/);
if(url.length>1){
// 3x shift(): protocol, host, 'json' part of path
request.command = (url.shift(),url.shift(),url.shift(), url.join('/'));
}
delete args.url;
//print("rhinoLocalBinarySendImpl SENDING: "+WhAjaj.stringify(request));
var json;
try{
var pargs = [args.fossilBinary, 'json', '--json-input', '-'];
var p = java.lang.Runtime.getRuntime().exec(pargs);
var outs = p.getOutputStream();
var osr = new java.io.OutputStreamWriter(outs);
var osb = new java.io.BufferedWriter(osr);
json = JSON.stringify(request);
osb.write(json,0, json.length);
osb.close();
var ins = p.getInputStream();
var isr = new java.io.InputStreamReader(ins);
var br = new java.io.BufferedReader(isr);
var line;
json = [];
while( null !== (line=br.readLine())){
json.push(line);
}
ins.close();
}catch(e){
args.errorMessage = e.toString();
WhAjaj.Connector.sendHelper.onSendError.apply( self, [request, args] );
return undefined;
}
json = json.join('');
//print("READ IN JSON: "+json);
WhAjaj.Connector.sendHelper.onSendSuccess.apply( self, [request, json, args] );
}/*rhinoLocalBinary*/
|
Added ajax/js/json2.js.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 |
/*
http://www.JSON.org/json2.js
2009-06-29
Public Domain.
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
See http://www.JSON.org/js.html
This file creates a global JSON object containing two methods: stringify
and parse.
JSON.stringify(value, replacer, space)
value any JavaScript value, usually an object or array.
replacer an optional parameter that determines how object
values are stringified for objects. It can be a
function or an array of strings.
space an optional parameter that specifies the indentation
of nested structures. If it is omitted, the text will
be packed without extra whitespace. If it is a number,
it will specify the number of spaces to indent at each
level. If it is a string (such as '\t' or ' '),
it contains the characters used to indent at each level.
This method produces a JSON text from a JavaScript value.
When an object value is found, if the object contains a toJSON
method, its toJSON method will be called and the result will be
stringified. A toJSON method does not serialize: it returns the
value represented by the name/value pair that should be serialized,
or undefined if nothing should be serialized. The toJSON method
will be passed the key associated with the value, and this will be
bound to the object holding the key.
For example, this would serialize Dates as ISO strings.
Date.prototype.toJSON = function (key) {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
return this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z';
};
You can provide an optional replacer method. It will be passed the
key and value of each member, with this bound to the containing
object. The value that is returned from your method will be
serialized. If your method returns undefined, then the member will
be excluded from the serialization.
If the replacer parameter is an array of strings, then it will be
used to select the members to be serialized. It filters the results
such that only members with keys listed in the replacer array are
stringified.
Values that do not have JSON representations, such as undefined or
functions, will not be serialized. Such values in objects will be
dropped; in arrays they will be replaced with null. You can use
a replacer function to replace those with JSON values.
JSON.stringify(undefined) returns undefined.
The optional space parameter produces a stringification of the
value that is filled with line breaks and indentation to make it
easier to read.
If the space parameter is a non-empty string, then that string will
be used for indentation. If the space parameter is a number, then
the indentation will be that many spaces.
Example:
text = JSON.stringify(['e', {pluribus: 'unum'}]);
// text is '["e",{"pluribus":"unum"}]'
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
text = JSON.stringify([new Date()], function (key, value) {
return this[key] instanceof Date ?
'Date(' + this[key] + ')' : value;
});
// text is '["Date(---current time---)"]'
JSON.parse(text, reviver)
This method parses a JSON text to produce an object or array.
It can throw a SyntaxError exception.
The optional reviver parameter is a function that can filter and
transform the results. It receives each of the keys and values,
and its return value is used instead of the original value.
If it returns what it received, then the structure is not modified.
If it returns undefined then the member is deleted.
Example:
// Parse the text. Values that look like ISO date strings will
// be converted to Date objects.
myData = JSON.parse(text, function (key, value) {
var a;
if (typeof value === 'string') {
a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
if (a) {
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+a[5], +a[6]));
}
}
return value;
});
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
var d;
if (typeof value === 'string' &&
value.slice(0, 5) === 'Date(' &&
value.slice(-1) === ')') {
d = new Date(value.slice(5, -1));
if (d) {
return d;
}
}
return value;
});
This is a reference implementation. You are free to copy, modify, or
redistribute.
This code should be minified before deployment.
See http://javascript.crockford.com/jsmin.html
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
NOT CONTROL.
*/
/*jslint evil: true */
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
lastIndex, length, parse, prototype, push, replace, slice, stringify,
test, toJSON, toString, valueOf
*/
// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.
var JSON = JSON || {};
(function () {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
if (typeof Date.prototype.toJSON !== 'function') {
Date.prototype.toJSON = function (key) {
return isFinite(this.valueOf()) ?
this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z' : null;
};
String.prototype.toJSON =
Number.prototype.toJSON =
Boolean.prototype.toJSON = function (key) {
return this.valueOf();
};
}
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
gap,
indent,
meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
rep;
function quote(string) {
// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.
escapable.lastIndex = 0;
return escapable.test(string) ?
'"' + string.replace(escapable, function (a) {
var c = meta[a];
return typeof c === 'string' ? c :
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' :
'"' + string + '"';
}
function str(key, holder) {
// Produce a string from holder[key].
var i, // The loop counter.
k, // The member key.
v, // The member value.
length,
mind = gap,
partial,
value = holder[key];
// If the value has a toJSON method, call it to obtain a replacement value.
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.
if (typeof rep === 'function') {
value = rep.call(holder, key, value);
}
// What happens next depends on the value's type.
switch (typeof value) {
case 'string':
return quote(value);
case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.
return String(value);
// If the type is 'object', we might be dealing with an object or an array or
// null.
case 'object':
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value) {
return 'null';
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]') {
// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || 'null';
}
// Join all of the elements together, separated with commas, and wrap them in
// brackets.
v = partial.length === 0 ? '[]' :
gap ? '[\n' + gap +
partial.join(',\n' + gap) + '\n' +
mind + ']' :
'[' + partial.join(',') + ']';
gap = mind;
return v;
}
// If the replacer is an array, use it to select the members to be stringified.
if (rep && typeof rep === 'object') {
length = rep.length;
for (i = 0; i < length; i += 1) {
k = rep[i];
if (typeof k === 'string') {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
} else {
// Otherwise, iterate through all of the keys in the object.
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
}
// Join all of the member texts together, separated with commas,
// and wrap them in braces.
v = partial.length === 0 ? '{}' :
gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
mind + '}' : '{' + partial.join(',') + '}';
gap = mind;
return v;
}
}
// If the JSON object does not yet have a stringify method, give it one.
if (typeof JSON.stringify !== 'function') {
JSON.stringify = function (value, replacer, space) {
// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.
var i;
gap = '';
indent = '';
// If the space parameter is a number, make an indent string containing that
// many spaces.
if (typeof space === 'number') {
for (i = 0; i < space; i += 1) {
indent += ' ';
}
// If the space parameter is a string, it will be used as the indent string.
} else if (typeof space === 'string') {
indent = space;
}
// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.
rep = replacer;
if (replacer && typeof replacer !== 'function' &&
(typeof replacer !== 'object' ||
typeof replacer.length !== 'number')) {
throw new Error('JSON.stringify');
}
// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.
return str('', {'': value});
};
}
// If the JSON object does not yet have a parse method, give it one.
if (typeof JSON.parse !== 'function') {
JSON.parse = function (text, reviver) {
// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.
var j;
function walk(holder, key) {
// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}
// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.
cx.lastIndex = 0;
if (cx.test(text)) {
text = text.replace(cx, function (a) {
return '\\u' +
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
});
}
// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.
// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.
j = eval('(' + text + ')');
// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.
return typeof reviver === 'function' ?
walk({'': j}, '') : j;
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
throw new SyntaxError('JSON.parse');
};
}
}());
|
Added ajax/js/whajaj.js.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 |
/**
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. Keys which have no value are treated
has having the boolean true value.
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;
str = (''+str).split(/#/,2)[0]; // remove #... to avoid it being added as part of the last value.
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(
{command:"json/login",
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 and fossil implementations).
This is partly a side-effect of design descisions made back in
the very first days of this code's life. i need to go through
and see where i can bend those conventions a bit (where it won't
break my other apps unduly).
*/
WhAjaj.Connector = function(opt)
{
if(WhAjaj.isObject(opt)) this.options = opt;
//TODO?: this.$cache = {};
};
/**
The core options used by WhAjaj.Connector instances for performing
network operations. These options can (and some _should_)
be changed by a client application. They can also be changed
on specific instances of WhAjaj.Connector, but for most applications
it is simpler to set them here and not have to bother with configuring
each WhAjaj.Connector instance. Apps which use multiple back-ends at one time,
however, will need to customize each instance for a given back-end.
*/
WhAjaj.Connector.options = {
/**
A (meaningless) prefix to apply to WhAjaj.Connector-generated
request IDs.
*/
requestIdPrefix:'WhAjaj.Connector-',
/**
Default options for WhAjaj.Connector.sendRequest() connection
parameters. This object holds only connection-related
options and callbacks (all optional), and not options
related to the required JSON structure of any given request.
i.e. the page name used in a get-page request are not set
here but are specified as part of the request object.
These connection options are a "normalized form" of options
often found in various AJAX libraries like jQuery,
Prototype, dojo, etc. This approach allows us to swap out
the real connection-related parts by writing a simple proxy
which transforms our "normalized" form to the
backend-specific form. For examples, see the various
implementations stored in WhAjaj.Connector.sendImpls.
The following callback options are, in practice, almost
always set globally to some app-wide defaults:
- onError() to report errors using a common mechanism.
- beforeSend() to start a visual activity notification
- afterSend() to disable the visual activity notification
However, be aware that if any given WhAjaj.Connector instance is
given its own before/afterSend callback then those will
override these. Mixing shared/global and per-instance
callbacks can potentially lead to confusing results if, e.g.,
the beforeSend() and afterSend() functions have side-effects
but are not used with their proper before/after partner.
TODO: rename this to 'ajaj' (the name is historical). The
problem with renaming it is is that the word 'ajax' is
pretty prevelant in the source tree, so i can't globally
swap it out.
*/
ajax: {
/**
URL of the back-end server/CGI.
*/
url: '/some/path',
/**
Connection method. Some connection-related functions might
override any client-defined setting.
Must be one of 'GET' or 'POST'. For custom connection
implementation, it may optionally be some
implementation-specified value.
Normally the API can derive this value automatically - if the
request uses JSON data it is POSTed, else it is GETted.
*/
method:'GET',
/**
A hint whether to run the operation asynchronously or
not. Not all concrete WhAjaj.Connector.sendImpl()
implementations can support this. Interestingly, at
least one popular AJAX toolkit does not document
supporting _synchronous_ AJAX operations. All common
browser-side implementations support async operation, but
non-browser implementations might 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 (why???)). The initiating request object is
passed as the second parameter, but clients can normally
ignore it (only those which need a way to map specific
requests to responses will need it). The 3rd parameter
is the same as the 'this' object for the context of the
callback, but is provided because the instance-level
callbacks (set in (WhAjaj.Connector instance).callbacks,
require it in some cases (because their 'this' is
different!).
Note that the response might contain error information
which comes from the back-end. The difference between
this error info and the info passed to the onError()
callback is that this data indicates an
application-level error, whereas onError() is used to
report connection-level problems or when the backend
produces non-JSON data (which, when not in jsonp mode,
is unexpected and is as fatal to the request as a
connection error).
*/
onResponse: function(response, request, opt){},
/**
If an AJAX request fails to establish a connection or it
receives non-JSON data from the back-end, this function
is called (e.g. timeout error or host name not
resolvable). It is passed the originating request and the
"normalized" connection parameters used for that
request. The connectOpt object "should" (or "might")
have an "errorMessage" property which describes the
nature of the problem.
Clients will almost always want to replace the default
implementation with something which integrates into
their application.
*/
onError: function(request, connectOpt)
{
alert('AJAJ request failed:\n'
+'Connection information:\n'
+JSON.stringify(connectOpt,0,4)
);
},
/**
Called before each connection attempt is made. Clients
can use this to, e.g., enable a visual "network activity
notification" for the user. It is passed the original
request object and the normalized connection parameters
for the request. If this function changes opt, those
changes _are_ applied to the subsequent request. If this
function throws, neither the onError() nor afterSend()
callbacks are triggered and WhAjaj.Connector.sendImpl()
propagates the exception back to the caller.
*/
beforeSend: function(request,opt){},
/**
Called after an AJAJ connection attempt completes,
regardless of success or failure. Passed the same
parameters as beforeSend() (see that function for
details).
Here's an example of setting up a visual notification on
ajax operations using jQuery (but it's also easy to do
without jQuery as well):
@code
function startAjaxNotif(req,opt) {
var me = arguments.callee;
var c = ++me.ajaxCount;
me.element.text( c + " pending AJAX operation(s)..." );
if( 1 == c ) me.element.stop().fadeIn();
}
startAjaxNotif.ajaxCount = 0.
startAjaxNotif.element = jQuery('#whikiAjaxNotification');
function endAjaxNotif() {
var c = --startAjaxNotif.ajaxCount;
startAjaxNotif.element.text( c+" pending AJAX operation(s)..." );
if( 0 == c ) startAjaxNotif.element.stop().fadeOut();
}
@endcode
Set the beforeSend/afterSend properties to those
functions to enable the notifications by default.
*/
afterSend: function(request,opt){},
/**
If jsonp is a string then the WhAjaj-internal response
handling code ASSUMES that the response contains a JSONP-style
construct and eval()s it after afterSend() but before onResponse().
In this case, onResponse() will get a string value for the response
instead of a response object parsed from JSON.
*/
jsonp:undefined,
/**
Don't use yet. Planned future option.
*/
propagateExceptions:false
}
};
/**
WhAjaj.Connector.prototype.callbacks defines callbacks analog
to the onXXX callbacks defined in WhAjaj.Connector.options.ajax,
with two notable differences:
1) these callbacks, if set, are called in addition to any
request-specific callback. The intention is to allow a framework to set
"framework-level" callbacks which should be called independently of the
request-specific callbacks (without interfering with them, e.g.
requiring special re-forwarding features).
2) The 'this' object in these callbacks is the Connector instance
associated with the callback, whereas the "other" onXXX form has its
"ajax options" object as its this.
When this API says that an onXXX callback will be called for a request,
both the request's onXXX (if set) and this one (if set) will be called.
*/
WhAjaj.Connector.prototype.callbacks = {};
/**
Instance-specific values for AJAJ-level properties (as opposed to
application-level request properties). Options set here "override" those
specified in WhAjaj.Connector.options.ajax and are "overridden" by
options passed to sendRequest().
*/
WhAjaj.Connector.prototype.options = {};
/**
Tries to find the given key in any of the following, returning
the first match found: opt, this.options, WhAjaj.Connector.options.ajax.
Returns undefined if key is not found.
*/
WhAjaj.Connector.prototype.derivedOption = function(key,opt) {
var v = opt ? opt[key] : undefined;
if( undefined !== v ) return v;
else v = this.options[key];
if( undefined !== v ) return v;
else v = WhAjaj.Connector.options.ajax[key];
return v;
};
/**
Returns a unique string on each call containing a generic
reandom request identifier string. This is not used by the core
API but can be used by client code to generate unique IDs for
each request (if needed).
The exact format is unspecified and may change in the future.
Request IDs can be used by clients to "match up" responses to
specific requests if needed. In practice, however, they are
seldom, if ever, needed. When passing several concurrent
requests through the same response callback, it might be useful
for some clients to be able to distinguish, possibly re-routing
them through other handlers based on the originating request type.
If this.options.requestIdPrefix or
WhAjaj.Connector.options.requestIdPrefix is set then that text
is prefixed to the returned string.
*/
WhAjaj.Connector.prototype.generateRequestId = function()
{
if( undefined === arguments.callee.sequence )
{
arguments.callee.sequence = 0;
}
var pref = this.options.requestIdPrefix || WhAjaj.Connector.options.requestIdPrefix || '';
return pref +
WhAjaj.msTimestamp() +
'/'+(Math.round( Math.random() * 100000000) )+
':'+(++arguments.callee.sequence);
};
/**
Copies (SHALLOWLY) all properties in opt to this.options.
*/
WhAjaj.Connector.prototype.addOptions = function(opt) {
var k, v;
for( k in opt ) {
if( ! opt.hasOwnProperty(k) ) continue /* proactive Prototype kludge! */;
this.options[k] = opt[k];
}
return this.options;
};
/**
An internal helper object which holds several functions intended
to simplify the creation of concrete communication channel
implementations for WhAjaj.Connector.sendImpl(). These operations
take care of some of the more error-prone parts of ensuring that
onResponse(), onError(), etc. callbacks are called consistently
using the same rules.
*/
WhAjaj.Connector.sendHelper = {
/**
opt is assumed to be a normalized set of
WhAjaj.Connector.sendRequest() options. This function
creates a url by concatenating opt.url and some form of
opt.urlParam.
If opt.urlParam is an object or string then it is appended
to the url. An object is assumed to be a one-dimensional set
of simple (urlencodable) key/value pairs, and not larger
data structures. A string value is assumed to be a
well-formed, urlencoded set of key/value pairs separated by
'&' characters.
The new/normalized URL is returned (opt is not modified). If
opt.urlParam is not set then opt.url is returned (or an
empty string if opt.url is itself a false value).
TODO: if opt is-a Object and any key points to an array,
build up a list of keys in the form "keyname[]". We could
arguably encode sub-objects like "keyname[subkey]=...", but
i don't know if that's conventions-compatible with other
frameworks.
*/
normalizeURL: function(opt) {
var u = opt.url || '';
if( opt.urlParam ) {
var addQ = (u.indexOf('?') >= 0) ? false : true;
var addA = addQ ? false : ((u.indexOf('&')>=0) ? true : false);
var tail = '';
if( WhAjaj.isObject(opt.urlParam) ) {
var li = [], k;
for( k in opt.urlParam) {
li.push( k+'='+encodeURIComponent( opt.urlParam[k] ) );
}
tail = li.join('&');
}
else if( 'string' === typeof opt.urlParam ) {
tail = opt.urlParam;
}
u = u + (addQ ? '?' : '') + (addA ? '&' : '') + tail;
}
return u;
},
/**
Should be called by WhAjaj.Connector.sendImpl()
implementations after a response has come back. This
function takes care of most of ensuring that framework-level
conventions involving WhAjaj.Connector.options.ajax
properties are followed.
The request argument must be the original request passed to
the sendImpl() function. It may legally be null for GET requests.
The opt object should be the normalized AJAX options used
for the connection.
The resp argument may be either a plain Object or a string
(in which case it is assumed to be JSON).
The 'this' object for this call MUST be a WhAjaj.Connector
instance in order for callback processing to work properly.
This function takes care of the following:
- Calling opt.afterSend()
- If resp is a string, de-JSON-izing it to an object.
- Calling opt.onResponse()
- Calling opt.onError() in several common (potential) error
cases.
- If resp is-a String and opt.jsonp then resp is assumed to be
a JSONP-form construct and is eval()d BEFORE opt.onResponse()
is called. It is arguable to eval() it first, but the logic
integrates better with the non-jsonp handler.
The sendImpl() should return immediately after calling this.
The sendImpl() must call only one of onSendSuccess() or
onSendError(). It must call one of them or it must implement
its own response/error handling, which is not recommended
because getting the documented semantics of the
onError/onResponse/afterSend handling correct can be tedious.
*/
onSendSuccess:function(request,resp,opt) {
var cb = this.callbacks || {};
if( WhAjaj.isFunction(cb.afterSend) ) {
try {cb.afterSend( request, opt );}
catch(e){}
}
if( WhAjaj.isFunction(opt.afterSend) ) {
try {opt.afterSend( request, opt );}
catch(e){}
}
function doErr(){
if( WhAjaj.isFunction(cb.onError) ) {
try {cb.onError( request, opt );}
catch(e){}
}
if( WhAjaj.isFunction(opt.onError) ) {
try {opt.onError( request, opt );}
catch(e){}
}
}
if( ! resp ) {
opt.errorMessage = "Sending of request succeeded but returned no data!";
doErr();
return false;
}
if( 'string' === typeof resp ) {
try {
resp = opt.jsonp ? eval(resp) : JSON.parse(resp);
} catch(e) {
opt.errorMessage = e.toString();
doErr();
return;
}
}
try {
if( WhAjaj.isFunction( cb.onResponse ) ) {
cb.onResponse( resp, request, opt );
}
if( WhAjaj.isFunction( opt.onResponse ) ) {
opt.onResponse( resp, request, opt );
}
return true;
}
catch(e) {
opt.errorMessage = "Exception while handling inbound JSON response:\n"
+ e
+"\nOriginal response data:\n"+JSON.stringify(resp,0,2)
;
;
doErr();
return false;
}
},
/**
Should be called by sendImpl() implementations after a response
has failed to connect (e.g. could not resolve host or timeout
reached). This function takes care of most of ensuring that
framework-level conventions involving WhAjaj.Connector.options.ajax
properties are followed.
The request argument must be the original request passed to
the sendImpl() function. It may legally be null for GET
requests.
The 'this' object for this call MUST be a WhAjaj.Connector
instance in order for callback processing to work properly.
The opt object should be the normalized AJAX options used
for the connection. By convention, the caller of this
function "should" set opt.errorMessage to contain a
human-readable description of the error.
The sendImpl() should return immediately after calling this. The
return value from this function is unspecified.
*/
onSendError: function(request,opt) {
var cb = this.callbacks || {};
if( WhAjaj.isFunction(cb.afterSend) ) {
try {cb.afterSend( request, opt );}
catch(e){}
}
if( WhAjaj.isFunction(opt.afterSend) ) {
try {opt.afterSend( request, opt );}
catch(e){}
}
if( WhAjaj.isFunction( cb.onError ) ) {
try {cb.onError( request, opt );}
catch(e) {/*ignore*/}
}
if( WhAjaj.isFunction( opt.onError ) ) {
try {opt.onError( request, opt );}
catch(e) {/*ignore*/}
}
}
};
/**
WhAjaj.Connector.sendImpls holds several concrete
implementations of WhAjaj.Connector.prototype.sendImpl(). To use
a specific implementation by default assign
WhAjaj.Connector.prototype.sendImpl to one of these functions.
The functions defined here require that the 'this' object be-a
WhAjaj.Connector instance.
Historical notes:
a) We once had an implementation based on Prototype, but that
library just pisses me off (they change base-most types'
prototypes, introducing side-effects in client code which
doesn't even use Prototype). The Prototype version at the time
had a serious toJSON() bug which caused empty arrays to
serialize as the string "[]", which broke a bunch of my code.
(That has been fixed in the mean time, but i don't use
Prototype.)
b) We once had an implementation for the dojo library,
If/when the time comes to add Prototype/dojo support, we simply
need to port:
http://code.google.com/p/jsonmessage/source/browse/trunk/lib/JSONMessage/JSONMessage.inc.js
(search that file for "dojo" and "Prototype") to this tree. That
code is this code's generic grandfather and they are still very
similar, so a port is trivial.
*/
WhAjaj.Connector.sendImpls = {
/**
This is a concrete implementation of
WhAjaj.Connector.prototype.sendImpl() which uses the
environment's native XMLHttpRequest class to send whiki
requests and fetch the responses.
The only argument must be a connection properties object, as
constructed by WhAjaj.Connector.normalizeAjaxParameters().
If window.firebug is set then window.firebug.watchXHR() is
called to enable monitoring of the XMLHttpRequest object.
This implementation honors the loginName and loginPassword
connection parameters.
Returns the XMLHttpRequest object.
This implementation requires that the 'this' object be-a
WhAjaj.Connector.
This implementation uses setTimeout() to implement the
timeout support, and thus the JS engine must provide that
functionality.
*/
XMLHttpRequest: function(request, args)
{
var json = WhAjaj.isObject(request) ? JSON.stringify(request) : request;
var xhr = new XMLHttpRequest();
var startTime = (new Date()).getTime();
var timeout = args.timeout || 10000/*arbitrary!*/;
var hitTimeout = false;
var done = false;
var tmid /* setTimeout() ID */;
var whself = this;
function handleTimeout()
{
hitTimeout = true;
if( ! done )
{
var now = (new Date()).getTime();
try { xhr.abort(); } catch(e) {/*ignore*/}
// see: http://www.w3.org/TR/XMLHttpRequest/#the-abort-method
args.errorMessage = "Timeout of "+timeout+"ms reached after "+(now-startTime)+"ms during AJAX request.";
WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] );
}
return;
}
function onStateChange()
{ // reminder to self: apparently 'this' is-not-a XHR :/
if( hitTimeout )
{ /* we're too late - the error was already triggered. */
return;
}
if( 4 == xhr.readyState )
{
done = true;
if( tmid )
{
clearTimeout( tmid );
tmid = null;
}
if( (xhr.status >= 200) && (xhr.status < 300) )
{
WhAjaj.Connector.sendHelper.onSendSuccess.apply( whself, [request, xhr.responseText, args] );
return;
}
else
{
if( undefined === args.errorMessage )
{
args.errorMessage = "Error sending a '"+args.method+"' AJAX request to "
+"["+args.url+"]: "
+"Status text=["+xhr.statusText+"]"
;
WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] );
}
else { /*maybe it was was set by the timeout handler. */ }
return;
}
}
};
xhr.onreadystatechange = onStateChange;
if( ('undefined'!==(typeof window)) && ('firebug' in window) && ('watchXHR' in window.firebug) )
{ /* plug in to firebug lite's XHR monitor... */
window.firebug.watchXHR( xhr );
}
try
{
//alert( JSON.stringify( args ));
function xhrOpen()
{
if( ('loginName' in args) && args.loginName )
{
xhr.open( args.method, args.url, args.asynchronous, args.loginName, args.loginPassword );
}
else
{
xhr.open( args.method, args.url, args.asynchronous );
}
}
if( json && ('POST' === args.method.toUpperCase()) )
{
xhrOpen();
xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");
// Google Chrome warns that it refuses to set these
// "unsafe" headers (his words, not mine):
// xhr.setRequestHeader("Content-length", json.length);
// xhr.setRequestHeader("Connection", "close");
xhr.send( json );
}
else /* assume GET */
{
xhrOpen();
xhr.send(null);
}
tmid = setTimeout( handleTimeout, timeout );
return xhr;
}
catch(e)
{
args.errorMessage = e.toString();
WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] );
return undefined;
}
}/*XMLHttpRequest()*/,
/**
This is a concrete implementation of
WhAjaj.Connector.prototype.sendImpl() which uses the jQuery
AJAX API to send requests and fetch the responses.
The first argument may be either null/false, an Object
containing toJSON-able data to post to the back-end, or such an
object in JSON string form.
The second argument must be a connection properties object, as
constructed by WhAjaj.Connector.normalizeAjaxParameters().
If window.firebug is set then window.firebug.watchXHR() is
called to enable monitoring of the XMLHttpRequest object.
This implementation honors the loginName and loginPassword
connection parameters.
Returns the XMLHttpRequest object.
This implementation requires that the 'this' object be-a
WhAjaj.Connector.
*/
jQuery:function(request,args)
{
var data = request || undefined;
var whself = this;
if( data ) {
if('string'!==typeof data) {
try {
data = JSON.stringify(data);
}
catch(e) {
WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] );
return;
}
}
}
var ajopt = {
url: args.url,
data: data,
type: args.method,
async: args.asynchronous,
password: (undefined !== args.loginPassword) ? args.loginPassword : undefined,
username: (undefined !== args.loginName) ? args.loginName : undefined,
contentType: 'application/json; charset=utf-8',
error: function(xhr, textStatus, errorThrown)
{
//this === the options for this ajax request
args.errorMessage = "Error sending a '"+ajopt.type+"' request to ["+ajopt.url+"]: "
+"Status text=["+textStatus+"]"
+(errorThrown ? ("Error=["+errorThrown+"]") : "")
;
WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] );
},
success: function(data)
{
WhAjaj.Connector.sendHelper.onSendSuccess.apply( whself, [request, data, args] );
},
/* Set dataType=text instead of json to keep jQuery from doing our carefully
written response handling for us.
*/
dataType: 'text'
};
if( undefined !== args.timeout )
{
ajopt.timeout = args.timeout;
}
try
{
return jQuery.ajax(ajopt);
}
catch(e)
{
args.errorMessage = e.toString();
WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] );
return undefined;
}
}/*jQuery()*/,
/**
This is a concrete implementation of
WhAjaj.Connector.prototype.sendImpl() which uses the rhino
Java API to send requests and fetch the responses.
Limitations vis-a-vis the interface:
- timeouts are not supported.
- asynchronous mode is not supported because implementing it
requires the ability to kill a running thread (which is deprecated
in the Java API).
TODOs:
- add socket timeouts.
- support HTTP proxy.
The Java APIs support this, it just hasn't been added here yet.
*/
rhino:function(request,args)
{
var self = this;
var data = request || undefined;
if( data ) {
if('string'!==typeof data) {
try {
data = JSON.stringify(data);
}
catch(e) {
WhAjaj.Connector.sendHelper.onSendError.apply( self, [request, args] );
return;
}
}
}
var url;
var con;
var IO = new JavaImporter(java.io);
var wr;
var rd, ln, json = [];
function setIncomingCookies(list){
if(!list || !list.length) return;
if( !self.cookies ) self.cookies = {};
var k, v, i;
for( i = 0; i < list.length; ++i ){
v = list[i].split('=',2);
k = decodeURIComponent(v[0])
v = v[0] ? decodeURIComponent(v[0].split(';',2)[0]) : null;
//print("RECEIVED COOKIE: "+k+"="+v);
if(!v) {
delete self.cookies[k];
continue;
}else{
self.cookies[k] = v;
}
}
};
function setOutboundCookies(conn){
if(!self.cookies) return;
var k, v;
for( k in self.cookies ){
if(!self.cookies.hasOwnProperty(k)) continue /*kludge for broken JS libs*/;
v = self.cookies[k];
conn.addRequestProperty("Cookie", encodeURIComponent(k)+'='+encodeURIComponent(v));
//print("SENDING COOKIE: "+k+"="+v);
}
};
try{
url = new java.net.URL( args.url )
con = url.openConnection(/*FIXME: add proxy support!*/);
con.setRequestProperty("Accept-Charset","utf-8");
setOutboundCookies(con);
if(data){
con.setRequestProperty("Content-Type","application/json; charset=utf-8");
con.setDoOutput( true );
wr = new IO.OutputStreamWriter(con.getOutputStream())
wr.write(data);
wr.flush();
wr.close();
wr = null;
//print("POSTED: "+data);
}
rd = new IO.BufferedReader(new IO.InputStreamReader(con.getInputStream()));
//var skippedHeaders = false;
while ((line = rd.readLine()) !== null) {
//print("LINE: "+line);
//if(!line.length && !skippedHeaders){
// skippedHeaders = true;
// json = [];
// continue;
//}
json.push(line);
}
setIncomingCookies(con.getHeaderFields().get("Set-Cookie"));
}catch(e){
args.errorMessage = e.toString();
WhAjaj.Connector.sendHelper.onSendError.apply( self, [request, args] );
return undefined;
}
try { if(wr) wr.close(); } catch(e) { /*ignore*/}
try { if(rd) rd.close(); } catch(e) { /*ignore*/}
json = json.join('');
//print("READ IN JSON: "+json);
WhAjaj.Connector.sendHelper.onSendSuccess.apply( self, [request, json, args] );
}/*rhino*/
};
/**
An internal function which takes an object containing properties
for a WhAjaj.Connector network request. This function creates a new
object containing a superset of the properties from:
a) opt
b) this.options
c) WhAjaj.Connector.options.ajax
in that order, using the first one it finds.
All non-function properties are _deeply_ copied via JSON cloning
in order to prevent accidental "cross-request pollenation" (been
there, done that). Functions cannot be cloned and are simply
copied by reference.
This function throws if JSON-copying one of the options fails
(e.g. due to cyclic data structures).
Reminder to self: this function does not "normalize" opt.urlParam
by encoding it into opt.url, mainly for historical reasons, but
also because that behaviour was specifically undesirable in this
code's genetic father.
*/
WhAjaj.Connector.prototype.normalizeAjaxParameters = function (opt)
{
var rc = {};
function merge(k,v)
{
if( rc.hasOwnProperty(k) ) return;
else if( WhAjaj.isFunction(v) ) {}
else if( WhAjaj.isObject(v) ) v = JSON.parse( JSON.stringify(v) );
rc[k]=v;
}
function cp(obj) {
if( ! WhAjaj.isObject(obj) ) return;
var k;
for( k in obj ) {
if( ! obj.hasOwnProperty(k) ) continue /* i will always hate the Prototype designers for this. */;
merge(k, obj[k]);
}
}
cp( opt );
cp( this.options );
cp( WhAjaj.Connector.options.ajax );
// no, not here: rc.url = WhAjaj.Connector.sendHelper.normalizeURL(rc);
return rc;
};
/**
This is the generic interface for making calls to a back-end
JSON-producing request handler. It is a simple wrapper around
WhAjaj.Connector.prototype.sendImpl(), which just normalizes the
connection options for sendImpl() and makes sure that
opt.beforeSend() is (possibly) called.
The request parameter must either be false/null/empty or a
fully-populated JSON-able request object (which will be sent as
unencoded application/json text), depending on the type of
request being made. It is never semantically legal (in this API)
for request to be a string/number/true/array value. As a rule,
only POST requests use the request data. GET requests should
encode their data in opt.url or opt.urlParam (see below).
opt must contain the network-related parameters for the request.
Paramters _not_ set in opt are pulled from this.options or
WhAjaj.Connector.options.ajax (in that order, using the first
value it finds). Thus the set of connection-level options used
for the request are a superset of those various sources.
The "normalized" (or "superimposed") opt object's URL may be
modified before the request is sent, as follows:
if opt.urlParam is a string then it is assumed to be properly
URL-encoded parameters and is appended to the opt.url. If it is
an Object then it is assumed to be a one-dimensional set of
key/value pairs with simple values (numbers, strings, booleans,
null, and NOT objects/arrays). The keys/values are URL-encoded
and appended to the URL.
The beforeSend() callback (see below) can modify the options
object before the request attempt is made.
The callbacks in the normalized opt object will be triggered as
follows (if they are set to Function values):
- beforeSend(request,opt) will be called before any network
processing starts. If beforeSend() throws then no other
callbacks are triggered and this function propagates the
exception. This function is passed normalized connection options
as its second parameter, and changes this function makes to that
object _will_ be used for the pending connection attempt.
- onError(request,opt) will be called if a connection to the
back-end cannot be established. It will be passed the original
request object (which might be null, depending on the request
type) and the normalized options object. In the error case, the
opt object passed to onError() "should" have a property called
"errorMessage" which contains a description of the problem.
- onError(request,opt) will also be called if connection
succeeds but the response is not JSON data.
- onResponse(response,request) will be called if the response
returns JSON data. That data might hold an error response code -
clients need to check for that. It is passed the response object
(a plain object) and the original request object.
- afterSend(request,opt) will be called directly after the
AJAX request is finished, before onError() or onResonse() are
called. Possible TODO: we explicitly do NOT pass the response to
this function in order to keep the line between the responsibilities
of the various callback clear (otherwise this could be used the same
as onResponse()). In practice it would sometimes be useful have the
response passed to this function, mainly for logging/debugging
purposes.
The return value from this function is meaningless because
AJAX operations tend to take place asynchronously.
*/
WhAjaj.Connector.prototype.sendRequest = function(request,opt)
{
if( !WhAjaj.isFunction(this.sendImpl) )
{
throw new Error("This object has no sendImpl() member function! I don't know how to send the request!");
}
var ex = false;
var av = Array.prototype.slice.apply( arguments, [0] );
/**
FIXME: how to handle the error, vis-a-vis- the callbacks, if
normalizeAjaxParameters() throws? It can throw if
(de)JSON-izing fails.
*/
var norm = this.normalizeAjaxParameters( WhAjaj.isObject(opt) ? opt : {} );
norm.url = WhAjaj.Connector.sendHelper.normalizeURL(norm);
if( ! request ) norm.method = 'GET';
var cb = this.callbacks || {};
if( this.callbacks && WhAjaj.isFunction(this.callbacks.beforeSend) ) {
this.callbacks.beforeSend( request, norm );
}
if( WhAjaj.isFunction(norm.beforeSend) ){
norm.beforeSend( request, norm );
}
//alert( WhAjaj.stringify(request)+'\n'+WhAjaj.stringify(norm));
try { this.sendImpl( request, norm ); }
catch(e) { ex = e; }
if(ex) throw ex;
};
/**
sendImpl() holds a concrete back-end connection implementation. It
can be replaced with a custom implementation if one follows the rules
described throughout this API. See WhAjaj.Connector.sendImpls for
the concrete implementations included with this API.
*/
//WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.XMLHttpRequest;
//WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.rhino;
//WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.jQuery;
if( 'undefined' !== typeof jQuery ){
WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.jQuery;
}
else {
WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.XMLHttpRequest;
}
|
Added ajax/wiki-editor.html.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 |
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Fossil/JSON Wiki Editor Prototype</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript" src="js/whajaj.js"></script>
<script type="text/javascript" src="js/fossil-ajaj.js"></script>
<style type='text/css'>
th {
text-align: left;
background-color: #ececec;
}
.dangerWillRobinson {
background-color: yellow;
}
.wikiPageLink {
text-decoration: underline;
}
</style>
<script type='text/javascript'>
WhAjaj.Connector.options.ajax.url =
/*
Change this to your CGI/server root path:
*/
//'http://fjson/cgi-bin/fossil.cgi'
//'/repos/fossil-sgb/json.cgi'
'/cgi-bin/fossil-json.cgi'
;
var TheApp = {
response:null,
sessionID:null,
jqe:{}/*jqe==jQuery Elements*/,
ajaxCount:0,
cgi: new FossilAjaj(),
pages:{}
};
TheApp.startAjaxNotif = function()
{
++this.ajaxCount;
TheApp.jqe.responseContainer.removeClass('dangerWillRobinson');
this.jqe.ajaxNotification.attr( 'title', this.ajaxCount+" pending AJAX operation(s)..." );
if( 1 == this.ajaxCount ) this.jqe.ajaxNotification.fadeIn();
};
TheApp.endAjaxNotif = function()
{
--this.ajaxCount;
this.jqe.ajaxNotification.attr( 'title', this.ajaxCount+" pending AJAX operation(s)..." );
if( 0 == this.ajaxCount ) this.jqe.ajaxNotification.fadeOut();
};
TheApp.responseContainsError = function(resp) {
if( !resp || resp.resultCode ) {
//alert("Error response:\n"+JSON.stringify(resp,0,4));
TheApp.jqe.taResponse.val( "RESPONSE CONTAINS ERROR INFO:\n"+WhAjaj.stringify(resp) );
//TheApp.jqe.responseContainer.css({backgroundColor:'yellow'});
//TheApp.jqe.responseContainer.addClass('dangerWillRobinson');
TheApp.jqe.responseContainer.flash( '255,0,0', 1500 );
return true;
}
return false;
};
TheApp.sendRequest = function() {
var path = this.jqe.textPath.val();
var self = this;
var data = this.jqe.pageListArea.val();
var doPost = (data && data.length);
var req;
if( doPost ) try {
req = JSON.parse(data);
}
catch(e) {
TheApp.jqe.taResponse.val("Request is not valid JSON.\n"+e);
return;
}
if( req ) {
req.requestId = this.cgi.generateRequestId();
}
var self = this;
var opt = {
url: WhAjaj.Connector.options.ajax.url + path,
method: doPost ? 'POST' : 'GET'
};
this.cgi.sendRequest( req, opt );
};
jQuery.fn.animateHighlight = function(highlightColor, duration) {
var highlightBg = highlightColor || "#FFFF9C";
var animateMs = duration || 1500;
var originalBg = this.css("backgroundColor");
this.stop().css("background-color", highlightBg).animate({backgroundColor: originalBg}, animateMs);
};
jQuery.fn.flash = function( color, duration )
{
var current = this.css( 'color' );
this.animate( { color: 'rgb(' + color + ')' }, duration / 2);
this.animate( { color: current }, duration / 2 );
};
jQuery(document).ready(function(){
var ids = [
'btnSend',
'ajaxNotification',
'currentAuthToken',
'responseContainer',
'spanPageName',
'pageListArea',
'taPageContent',
'taResponse',
'textPath', // list of HTML element IDs we use often.
'timer'
];
var i, k;
for( i = 0; i < ids.length; ++i ) {
k = ids[i];
TheApp.jqe[k] = jQuery('#'+k);
}
TheApp.jqe.textPath.
keyup(function(event){
if(event.keyCode == 13){
TheApp.sendRequest();
}
});
TheApp.timer = {
_tstart:0,_tend:0,duration:0,
start:function(){
this._tstart = (new Date()).getTime();
},
end:function(){
this._tend = (new Date()).getTime();
return this.duration = this._tend - this._tstart;
}
};
var ajcb = TheApp.cgi.ajaj.callbacks;
ajcb.beforeSend = TheApp.beforeSend = function(req,opt) {
TheApp.timer.start();
var val =
req ?
(('string'===typeof req) ? req : WhAjaj.stringify(req))
: '';
TheApp.jqe.taResponse.val('');
TheApp.startAjaxNotif();
};
ajcb.afterSend = TheApp.afterSend = function(req,opt) {
TheApp.timer.end();
TheApp.endAjaxNotif();
TheApp.jqe.timer.text( "(Round-trip time: "+TheApp.timer.duration+'ms)' );
};
ajcb.onResponse = TheApp.onResponse = function(resp,req) {
var val;
try {
val = WhAjaj.stringify(resp);
}
catch(e) {
val = WhAjaj.stringify(e)
}
if(resp.resultCode){
alert("Response contains error info:\n"+val);
}
TheApp.jqe.taResponse.val( val );
};
ajcb.onError = function(req,opt) {
TheApp.jqe.taResponse.val( "ERROR SENDING REQUEST:\n"+WhAjaj.stringify(opt) );
};
TheApp.jqe.taPageContent.blur(function(){
var p = TheApp.currentPage;
if(! p ) return;
p.content = TheApp.jqe.taPageContent.val();
});
TheApp.cgi.onLogin = function(){
TheApp.jqe.taResponse.val( "Logged in: "+WhAjaj.stringify(this.auth));
TheApp.jqe.currentAuthToken.text("Logged in: "+WhAjaj.stringify(this.auth));
};
TheApp.cgi.onLogout = function(){
TheApp.jqe.taResponse.val( "Logged out!" );
TheApp.jqe.currentAuthToken.text("");
};
TheApp.showPage = function(name){
function doShow(page){
TheApp.currentPage = page;
TheApp.jqe.spanPageName.text('('+page.name+')');
TheApp.jqe.taPageContent.val(page.content);
}
var p = ('object' === typeof name) ? name : TheApp.pages[name];
if(('object' === typeof p) && p.content) {
doShow(p);
return;
}
TheApp.cgi.sendCommand('/json/wiki/get',{
name:name
},{
onResponse:function(resp,req){
TheApp.onResponse(resp,req);
if(resp.resultCode) return;
var p = resp.payload;
doShow( TheApp.pages[p.name] = p );
}
});
};
TheApp.refreshPageListView = function(){
var list = (function(){
var k, v, li = [];
for( k in TheApp.pages ){
if(!TheApp.pages.hasOwnProperty(k)) continue;
li.push(k);
}
return li;
})();
var i, p, a, tgt = TheApp.jqe.pageListArea;
tgt.text('');
function makeLink(name){
var link = jQuery('<span></span>');
link.text(name);
link.addClass('wikiPageLink');
link.click(function(e){
TheApp.showPage(name);
e.preventDefault();
return false;
});
return link;
}
list.sort();
for( i = 0; i < list.length; ++i ){
tgt.append(makeLink(list[i]));
tgt.append('<br/>');
}
};
TheApp.loadPageList = function(){
TheApp.cgi.sendCommand('/json/wiki/list',null,{
onResponse:function(resp,req){
TheApp.onResponse(resp,req);
if(resp.resultCode) return;
var i, v, p, ar = resp.payload;
for( i = 0; i < ar.length; ++i ){
v = ar[i];
p = TheApp.pages[v];
if( !p ) TheApp.pages[v] = {name:v};
}
TheApp.refreshPageListView();
}
});
return false /*for click handlers*/;
}
TheApp.savePage = function(p){
p = p || TheApp.currentPage;
if( 'object' !== typeof p ){
p = TheApp.pages[p];
}
if('object' !== typeof p){
alert("savePage() argument is not a page object or known page name.");
}
TheApp.pages[p.name] = p;
p.content = TheApp.jqe.taPageContent.val();
var req = {
name:p.name,
content:p.content
};
if(! confirm("Really save wiki page ["+p.name+"]?") ) return;
TheApp.cgi.sendCommand('/json/wiki/'+(p.isNew?'create':'save'),req,{
onResponse:function(resp,req){
TheApp.onResponse(resp,req);
if(resp.resultCode) return;
delete p.isNew;
p.timestamp = resp.payload.timestamp;
}
});
};
TheApp.createNewPage = function(){
var name = prompt("New page name?");
if(!name) return;
var p = {
name:name,
content:"New, empty page.",
isNew:true
};
TheApp.pages[name] = p;
TheApp.refreshPageListView();
TheApp.showPage(p);
/*
if(! confirm("Really create new wiki page ["+name+"]?") ) return;
TheApp.cgi.sendCommand('/json/wiki/create',req,{
onResponse:function(resp,req){
TheApp.onResponse(resp,req);
if(resp.resultCode) return;
TheApp.pages[p.name] = p;
TheApp.refreshPageListView();
}
});
*/
};
TheApp.cgi.whoami();
});
</script>
</head>
<body>
<span id='ajaxNotification'></span>
<h1>PROTOTYPE JSON-based Fossil Wiki Editor</h1>
See also: <a href='index.html'>main test page</a>.
<br>
<b>Login:</b>
<br/>
<input type='button' value='Anon. Login' onclick='TheApp.cgi.login()' />
or:
name:<input type='text' id='textUser' value='json-demo' size='12'/>
pw:<input type='password' id='textPassword' value='json-demo' size='12'/>
<input type='button' value='login' onclick='TheApp.cgi.login(jQuery("#textUser").val(),jQuery("#textPassword").val(),{onResponse:TheApp.onLogin})' />
<input type='button' value='logout' onclick='TheApp.cgi.logout()' />
<br/>
<span id='currentAuthToken' style='font-family:monospaced'></span>
<hr/>
<strong>Quick-posts:</strong><br/>
<input type='button' value='HAI' onclick='TheApp.cgi.HAI()' />
<input type='button' value='stat' onclick='TheApp.cgi.sendCommand("/json/stat")' />
<input type='button' value='whoami' onclick='TheApp.cgi.whoami()' />
<input type='button' value='wiki/list' onclick='TheApp.loadPageList()' />
<!--
<input type='button' value='timeline/ci' onclick='TheApp.cgi.sendCommand("/json/timeline/ci")' />
-->
<!--
<input type='button' value='get whiki' onclick='TheApp.cgi.getPages("whiki")' />
<input type='button' value='get more' onclick='TheApp.cgi.getPages("HelloWorld/WhikiNews")' />
<input type='button' value='get client data' onclick='TheApp.cgi.getPageClientData("HelloWorld/whiki/WhikiCommands")' />
<input type='button' value='save client data' onclick='TheApp.cgi.savePageClientData({"HelloWorld":[1,3,5]})' />
-->
<hr/>
<table>
<tr>
<th>Page List</th>
<th>Content <span id='spanPageName'></span></th>
</tr>
<tr>
<td width='25%' valign='top'>
<input type='button' value='Create new...' onclick='TheApp.createNewPage()' /><br/>
<div id='pageListArea'></div>
</td>
<td width='75%' valign='top'>
<input type='button' value='Save' onclick='TheApp.savePage()' /><br/>
<textarea id='taPageContent' rows='20' cols='60'></textarea>
</td>
</tr>
<tr>
<th colspan='2'>Response <span id='timer'></span></th>
</tr>
<tr>
<td colspan='2' id='responseContainer'>
<textarea id='taResponse' rows='20' cols='80' readonly></textarea>
</td>
</tr>
</table>
<div></div>
<div></div>
<div></div>
</body></html>
|
Changes to auto.def.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# System autoconfiguration. Try: ./configure --help
use cc cc-lib
options {
with-openssl:path|auto|none
=> {Look for openssl in the given path, or auto or none}
with-zlib:path => {Look for zlib in the given path}
internal-sqlite=1 => {Don't use the internal sqlite, use the system one}
static=0 => {Link a static executable}
lineedit=1 => {Disable line editing}
fossil-debug=0 => {Build with fossil debugging enabled}
}
# sqlite wants these types if possible
cc-with {-includes {stdint.h inttypes.h}} {
cc-check-types uint32_t uint16_t int16_t uint8_t
}
| > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# System autoconfiguration. Try: ./configure --help
use cc cc-lib
options {
with-openssl:path|auto|none
=> {Look for openssl in the given path, or auto or none}
with-zlib:path => {Look for zlib in the given path}
with-tcl:path => {Enable Tcl integration, with Tcl in the specified path}
internal-sqlite=1 => {Don't use the internal sqlite, use the system one}
static=0 => {Link a static executable}
lineedit=1 => {Disable line editing}
fossil-debug=0 => {Build with fossil debugging enabled}
json=0 => {Build with fossil JSON API enabled}
}
# sqlite wants these types if possible
cc-with {-includes {stdint.h inttypes.h}} {
cc-check-types uint32_t uint16_t int16_t uint8_t
}
|
| ︙ | ︙ | |||
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
find_internal_sqlite
}
if {[opt-bool fossil-debug]} {
define-append EXTRA_CFLAGS -DFOSSIL_DEBUG
}
if {[opt-bool static]} {
# XXX: This will not work on all systems.
define-append EXTRA_LDFLAGS -static
}
# Check for zlib, using the given location if specified
set zlibpath [opt-val with-zlib]
if {$zlibpath ne ""} {
cc-with [list -cflags "-I$zlibpath -L$zlibpath"]
define-append EXTRA_CFLAGS -I$zlibpath
define-append EXTRA_LDFLAGS -L$zlibpath
}
if {![cc-check-includes zlib.h] || ![cc-check-function-in-lib inflateEnd z]} {
user-error "zlib not found please install it or specify the location with --with-zlib"
}
# Helper for openssl checking
proc check-for-openssl {msg {cflags {}}} {
msg-checking "Checking for $msg..."
set rc 0
msg-quiet cc-with [list -cflags $cflags -libs {-lssl -lcrypto}] {
if {[cc-check-includes openssl/ssl.h] && [cc-check-functions SSL_new]} {
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
find_internal_sqlite
}
if {[opt-bool fossil-debug]} {
define-append EXTRA_CFLAGS -DFOSSIL_DEBUG
}
if {[opt-bool json]} {
define-append EXTRA_CFLAGS -DFOSSIL_ENABLE_JSON
}
if {[opt-bool static]} {
# XXX: This will not work on all systems.
define-append EXTRA_LDFLAGS -static
}
# Check for zlib, using the given location if specified
set zlibpath [opt-val with-zlib]
if {$zlibpath ne ""} {
cc-with [list -cflags "-I$zlibpath -L$zlibpath"]
define-append EXTRA_CFLAGS -I$zlibpath
define-append EXTRA_LDFLAGS -L$zlibpath
}
if {![cc-check-includes zlib.h] || ![cc-check-function-in-lib inflateEnd z]} {
user-error "zlib not found please install it or specify the location with --with-zlib"
}
set tclpath [opt-val with-tcl]
if {$tclpath ne ""} {
# Note parse-tclconfig-sh is in autosetup/local.tcl
if {$tclpath eq "1"} {
# Use the system Tcl. Look in some likely places.
array set tclconfig [parse-tclconfig-sh /usr /usr/local /usr/share /opt/local]
set msg "on your system"
} else {
array set tclconfig [parse-tclconfig-sh $tclpath]
set msg "at $tclpath"
}
if {![info exists tclconfig(TCL_INCLUDE_SPEC)]} {
user-error "Cannot find Tcl $msg"
}
set cflags $tclconfig(TCL_INCLUDE_SPEC)
set libs "$tclconfig(TCL_LIB_SPEC) $tclconfig(TCL_LIBS)"
cc-with [list -cflags $cflags -libs $libs] {
if {![cc-check-functions Tcl_CreateInterp]} {
user-error "Cannot find a usable Tcl $msg"
}
}
set version $tclconfig(TCL_VERSION)$tclconfig(TCL_PATCH_LEVEL)
msg-result "Found Tcl $version at $tclconfig(TCL_PREFIX)"
define-append LIBS $libs
define-append EXTRA_CFLAGS $cflags
define-append EXTRA_LDFLAGS $tclconfig(TCL_LD_FLAGS)
define FOSSIL_ENABLE_TCL
}
# Helper for openssl checking
proc check-for-openssl {msg {cflags {}}} {
msg-checking "Checking for $msg..."
set rc 0
msg-quiet cc-with [list -cflags $cflags -libs {-lssl -lcrypto}] {
if {[cc-check-includes openssl/ssl.h] && [cc-check-functions SSL_new]} {
|
| ︙ | ︙ |
Changes to autosetup/find-tclsh.
1 2 3 4 | #!/bin/sh # Looks for a suitable tclsh or jimsh in the PATH # If not found, builds a bootstrap jimsh from source d=`dirname "$0"` | | | 1 2 3 4 5 6 7 8 9 10 11 12 |
#!/bin/sh
# Looks for a suitable tclsh or jimsh in the PATH
# If not found, builds a bootstrap jimsh from source
d=`dirname "$0"`
PATH="$PATH:$d"; export PATH
for tclsh in jimsh tclsh tclsh8.5 tclsh8.6 jimsh0; do
{ $tclsh "$d/test-tclsh"; } 2>/dev/null && exit 0
done
echo 1>&2 "No installed jimsh or tclsh, building local bootstrap jimsh0"
for cc in ${CC_FOR_BUILD:-cc} gcc; do
{ $cc -o "$d/jimsh0" "$d/jimsh0.c"; } 2>/dev/null || continue
"$d/jimsh0" "$d/test-tclsh" && exit 0
|
| ︙ | ︙ |
Changes to autosetup/local.tcl.
1 2 | # For this project, disable the pager for --help set useropts(nopager) 1 | > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# For this project, disable the pager for --help
set useropts(nopager) 1
# Searches for a usable Tcl (prefer 8.6, 8.5, 8.4) in the given paths
# Returns a dictionary of the contents of the tclConfig.sh file, or
# empty if not found
proc parse-tclconfig-sh {args} {
foreach p $args {
# Allow pointing directly to the path containing tclConfig.sh
if {[file exists $p/tclConfig.sh]} {
return [parse-tclconfig-sh-file $p/tclConfig.sh]
}
# Some systems allow for multiple versions
foreach libpath {lib/tcl8.6 lib/tcl8.5 lib/tcl8.4 lib/tcl tcl lib} {
if {[file exists $p/$libpath/tclConfig.sh]} {
return [parse-tclconfig-sh-file $p/$libpath/tclConfig.sh]
}
}
}
}
proc parse-tclconfig-sh-file {filename} {
foreach line [split [readfile $filename] \n] {
if {[regexp {^(TCL_[^=]*)=(.*)$} $line -> name value]} {
set tclconfig($name) [string trim $value ']
}
}
return [array get tclconfig]
}
|
Deleted ci_cvs.txt.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted ci_fossil.txt.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted cvs2fossil.txt.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to debian/makedeb.sh.
1 2 3 4 5 |
#!/bin/bash
# A quick hack to generate a Debian package of fossil. i took most of this
# from Martin Krafft's "The Debian System" book.
DEB_REV=${1-1} # .deb package build/revision number.
| | < < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#!/bin/bash
# A quick hack to generate a Debian package of fossil. i took most of this
# from Martin Krafft's "The Debian System" book.
DEB_REV=${1-1} # .deb package build/revision number.
PACKAGE_DEBNAME=fossil
THISDIR=${PWD}
if uname -a | grep -i nexenta &>/dev/null; then
# Assume NexentaOS/GnuSolaris:
DEB_ARCH_NAME=solaris-i386
DEB_ARCH_PKGDEPENDS="sunwcsl" # for -lsocket
else
DEB_ARCH_NAME=$(dpkg --print-architecture)
fi
SRCDIR=$(cd ..; pwd)
test -e ${SRCDIR}/fossil || {
echo "This script must be run from a BUILT copy of the source tree."
exit 1
}
|
| ︙ | ︙ | |||
39 40 41 42 43 44 45 |
rm -fr DEBIAN
mkdir DEBIAN
PACKAGE_VERSION=$(date +%Y.%m.%d)
PACKAGE_DEB_VERSION=${PACKAGE_VERSION}-${DEB_REV}
| | | | | | | | 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
rm -fr DEBIAN
mkdir DEBIAN
PACKAGE_VERSION=$(date +%Y.%m.%d)
PACKAGE_DEB_VERSION=${PACKAGE_VERSION}-${DEB_REV}
DEBFILE=${THISDIR}/${PACKAGE_DEBNAME}-${PACKAGE_DEB_VERSION}-dev-${DEB_ARCH_NAME}.deb
PACKAGE_TIME=$(/bin/date)
rm -f ${DEBFILE}
echo "Creating .deb package [${DEBFILE}]..."
echo "Generating md5 sums..."
find ${DEBLOCALPREFIX} -type f -exec md5sum {} \; > DEBIAN/md5sums
true && {
echo "Generating Debian-specific files..."
COPYRIGHT=${DEBLOCALPREFIX}/share/doc/${PACKAGE_DEBNAME}/copyright
cat <<EOF > ${COPYRIGHT}
This package was created by fossil-scm <fossil-dev@lists.fossil-scm.org>
on ${PACKAGE_TIME}.
The original sources for fossil can be downloaded for free from:
http://www.fossil-scm.org/
fossil is released under the terms of the GNU General Public License.
EOF
}
true && {
CHANGELOG=${DEBLOCALPREFIX}/share/doc/${PACKAGE_DEBNAME}/changelog.gz
cat <<EOF | gzip -c > ${CHANGELOG}
${PACKAGE_DEBNAME} ${PACKAGE_DEB_VERSION}; urgency=low
This release has no changes over the core source distribution. It has
simply been Debianized.
Packaged by fossil-dev <fossil-dev@lists.fossil-scm.org> on
${PACKAGE_TIME}.
EOF
}
true && {
CONTROL=DEBIAN/control
echo "Generating ${CONTROL}..."
cat <<EOF > ${CONTROL}
Package: ${PACKAGE_DEBNAME}
Section: vcs
Priority: optional
Maintainer: fossil-dev <fossil-dev@lists.fossil-scm.org>
Architecture: ${DEB_ARCH_NAME}
Depends: libc6 ${DEB_ARCH_PKGDEPENDS+, }${DEB_ARCH_PKGDEPENDS}
Version: ${PACKAGE_DEB_VERSION}
Description: Fossil is a unique SCM (Software Configuration Management) system.
This package contains the Fossil binary for *buntu/Debian systems.
Fossil is a unique SCM program which supports distributed source control
management using local repositories, access over HTTP CGI, or using the
built-in HTTP server. It has a built-in wiki, file browsing, etc.
Fossil home page: http://fossil-scm.org
|
| ︙ | ︙ |
Deleted kktodo.wiki.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted rse-notes.txt.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Added src/Makefile.
> > | 1 2 | all: $(MAKE) -C .. |
Changes to src/add.c.
| ︙ | ︙ | |||
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
** its associated journals
*/
static const char *azName[] = {
"_FOSSIL_",
"_FOSSIL_-journal",
"_FOSSIL_-wal",
"_FOSSIL_-shm",
".fos",
".fos-journal",
".fos-wal",
".fos-shm",
};
/* Names of auxiliary files generated by SQLite when the "manifest"
** properity is enabled
*/
static const char *azManifest[] = {
"manifest",
"manifest.uuid",
};
if( N>=0 && N<count(azName) ) return azName[N];
if( N>=count(azName) && N<count(azName)+count(azManifest)
| > > > > > > > > > > > > > > > | | 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
** its associated journals
*/
static const char *azName[] = {
"_FOSSIL_",
"_FOSSIL_-journal",
"_FOSSIL_-wal",
"_FOSSIL_-shm",
".fslckout",
".fslckout-journal",
".fslckout-wal",
".fslckout-shm",
/* The use of ".fos" as the name of the checkout database is
** deprecated. Use ".fslckout" instead. At some point, the following
** entries should be removed. 2012-02-04 */
".fos",
".fos-journal",
".fos-wal",
".fos-shm",
};
/* Names of auxiliary files generated by SQLite when the "manifest"
** properity is enabled
*/
static const char *azManifest[] = {
"manifest",
"manifest.uuid",
};
/* Cached setting "manifest" */
static int cachedManifest = -1;
if( cachedManifest == -1 ){
cachedManifest = db_get_boolean("manifest",0);
}
if( N>=0 && N<count(azName) ) return azName[N];
if( N>=count(azName) && N<count(azName)+count(azManifest)
&& cachedManifest ){
return azManifest[N-count(azName)];
}
return 0;
}
/*
** Return a list of all reserved filenames as an SQL list.
|
| ︙ | ︙ | |||
226 227 228 229 230 231 232 |
for(i=2; i<g.argc; i++){
char *zName;
int isDir;
Blob fullName;
file_canonical_name(g.argv[i], &fullName);
zName = blob_str(&fullName);
| | | < | | | 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 |
for(i=2; i<g.argc; i++){
char *zName;
int isDir;
Blob fullName;
file_canonical_name(g.argv[i], &fullName);
zName = blob_str(&fullName);
isDir = file_wd_isdir(zName);
if( isDir==1 ){
vfile_scan(&fullName, nRoot-1, includeDotFiles, pIgnore);
}else if( isDir==0 ){
fossil_fatal("not found: %s", zName);
}else if( file_access(zName, R_OK) ){
fossil_fatal("cannot open %s", zName);
}else{
char *zTreeName = &zName[nRoot];
db_multi_exec(
"INSERT OR IGNORE INTO sfile(x) VALUES(%Q)",
zTreeName
);
}
blob_reset(&fullName);
}
glob_free(pIgnore);
add_files_in_sfile(vid, caseSensitive);
db_end_transaction(0);
}
/*
** COMMAND: rm
** COMMAND: delete*
**
** Usage: %fossil rm FILE1 ?FILE2 ...?
** or: %fossil delete FILE1 ?FILE2 ...?
**
** Remove one or more files or directories from the repository.
**
** This command does NOT remove the files from disk. It just marks the
|
| ︙ | ︙ | |||
328 329 330 331 332 333 334 |
** the case-sensitive setting is undefined, then case sensitivity
** defaults on for Mac and Windows and off for all other unix.
**
** The --case-sensitive BOOLEAN command-line option overrides any
** setting.
*/
int filenames_are_case_sensitive(void){
| | > > > | | | | | | > > > > > > > > > > > > | 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 |
** the case-sensitive setting is undefined, then case sensitivity
** defaults on for Mac and Windows and off for all other unix.
**
** The --case-sensitive BOOLEAN command-line option overrides any
** setting.
*/
int filenames_are_case_sensitive(void){
static int caseSensitive;
static int once = 1;
if( once ){
once = 0;
if( zCaseSensitive ){
caseSensitive = is_truth(zCaseSensitive);
}else{
#if !defined(_WIN32) && !defined(__DARWIN__) && !defined(__APPLE__)
caseSensitive = 1; /* Unix */
#else
caseSensitive = 0; /* Windows and Mac */
#endif
caseSensitive = db_get_boolean("case-sensitive",caseSensitive);
}
}
return caseSensitive;
}
/*
** Return one of two things:
**
** "" (empty string) if filenames are case sensitive
**
** "COLLATE nocase" if filenames are not case sensitive.
*/
const char *filename_collation(void){
return filenames_are_case_sensitive() ? "" : "COLLATE nocase";
}
/*
** COMMAND: addremove
**
** Usage: %fossil addremove ?OPTIONS?
**
** Do all necessary "add" and "rm" commands to synchronize the repository
|
| ︙ | ︙ | |||
464 465 466 467 468 469 470 |
"UPDATE vfile SET pathname='%s' WHERE pathname='%s' AND vid=%d",
zNew, zOrig, vid
);
}
/*
** COMMAND: mv
| | | 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 |
"UPDATE vfile SET pathname='%s' WHERE pathname='%s' AND vid=%d",
zNew, zOrig, vid
);
}
/*
** COMMAND: mv
** COMMAND: rename*
**
** Usage: %fossil mv|rename OLDNAME NEWNAME
** or: %fossil mv|rename OLDNAME... DIR
**
** Move or rename one or more files or directories within the repository tree.
** You can either rename a file or directory or move it to another subdirectory.
**
|
| ︙ | ︙ | |||
502 503 504 505 506 507 508 |
file_tree_name(zDest, &dest, 1);
db_multi_exec(
"UPDATE vfile SET origname=pathname WHERE origname IS NULL;"
);
db_multi_exec(
"CREATE TEMP TABLE mv(f TEXT UNIQUE ON CONFLICT IGNORE, t TEXT);"
);
| | | 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 |
file_tree_name(zDest, &dest, 1);
db_multi_exec(
"UPDATE vfile SET origname=pathname WHERE origname IS NULL;"
);
db_multi_exec(
"CREATE TEMP TABLE mv(f TEXT UNIQUE ON CONFLICT IGNORE, t TEXT);"
);
if( file_wd_isdir(zDest)!=1 ){
Blob orig;
if( g.argc!=4 ){
usage("OLDNAME NEWNAME");
}
file_tree_name(g.argv[2], &orig, 1);
db_multi_exec(
"INSERT INTO mv VALUES(%B,%B)", &orig, &dest
|
| ︙ | ︙ |
Changes to src/allrepo.c.
| ︙ | ︙ | |||
72 73 74 75 76 77 78 | ** push Run a "push" on all repositories ** ** rebuild Rebuild on all repositories ** ** sync Run a "sync" on all repositories ** ** Respositories are automatically added to the set of known repositories | | | | | 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
** push Run a "push" on all repositories
**
** 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 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;
char *zSyscmd;
char *zFossil;
|
| ︙ | ︙ |
Changes to src/blob.c.
| ︙ | ︙ | |||
311 312 313 314 315 316 317 318 319 320 321 322 323 324 |
sz = szA<szB ? szA : szB;
rc = memcmp(blob_buffer(pA), blob_buffer(pB), sz);
if( rc==0 ){
rc = szA - szB;
}
return rc;
}
/*
** Compare a blob to a string. Return TRUE if they are equal.
*/
int blob_eq_str(Blob *pBlob, const char *z, int n){
Blob t;
blob_is_init(pBlob);
| > > > > > > > > > > > > > > > > > > > > > > > > > > | 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 |
sz = szA<szB ? szA : szB;
rc = memcmp(blob_buffer(pA), blob_buffer(pB), sz);
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; i<szA; i++ ){
rc = rc | (buf1[i] ^ buf2[i]);
}
return rc;
}
/*
** Compare a blob to a string. Return TRUE if they are equal.
*/
int blob_eq_str(Blob *pBlob, const char *z, int n){
Blob t;
blob_is_init(pBlob);
|
| ︙ | ︙ | |||
738 739 740 741 742 743 744 |
** Return the number of bytes written.
*/
int blob_write_to_file(Blob *pBlob, const char *zFilename){
FILE *out;
int wrote;
if( zFilename[0]==0 || (zFilename[0]=='-' && zFilename[1]==0) ){
| > > > > | > > > > > > | > > | 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 |
** Return the number of bytes written.
*/
int blob_write_to_file(Blob *pBlob, const char *zFilename){
FILE *out;
int wrote;
if( zFilename[0]==0 || (zFilename[0]=='-' && zFilename[1]==0) ){
int n;
#if defined(_WIN32)
if( _isatty(fileno(stdout)) ){
char *z;
z = fossil_utf8_to_console(blob_str(pBlob));
n = strlen(z);
fwrite(z, 1, n, stdout);
free(z);
return n;
}
#endif
n = blob_size(pBlob);
fwrite(blob_buffer(pBlob), 1, n, stdout);
return n;
}else{
int i, nName;
char *zName, zBuf[1000];
nName = strlen(zFilename);
if( nName>=sizeof(zBuf) ){
zName = mprintf("%s", zFilename);
|
| ︙ | ︙ | |||
1011 1012 1013 1014 1015 1016 1017 |
if( z[i]=='"' ) z[i] = '_';
}
return;
}
}
blob_append(pBlob, zIn, -1);
}
| > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 |
if( z[i]=='"' ) z[i] = '_';
}
return;
}
}
blob_append(pBlob, zIn, -1);
}
/*
** A read(2)-like impl for the Blob class. Reads (copies) up to nLen
** bytes from pIn, starting at position pIn->iCursor, and copies them
** to pDest (which must be valid memory at least nLen bytes long).
**
** Returns the number of bytes read/copied, which may be less than
** nLen (if end-of-blob is encountered).
**
** Updates pIn's cursor.
**
** Returns 0 if pIn contains no data.
*/
unsigned int blob_read(Blob *pIn, void * pDest, unsigned int nLen ){
if( !pIn->aData || (pIn->iCursor >= pIn->nUsed) ){
return 0;
} else if( (pIn->iCursor + nLen) > (unsigned int)pIn->nUsed ){
nLen = (unsigned int) (pIn->nUsed - pIn->iCursor);
}
assert( pIn->nUsed > pIn->iCursor );
assert( (pIn->iCursor+nLen) <= pIn->nUsed );
if( nLen ){
memcpy( pDest, pIn->aData, nLen );
pIn->iCursor += nLen;
}
return nLen;
}
|
Changes to src/branch.c.
| ︙ | ︙ | |||
144 145 146 147 148 149 150 |
prompt_user("unable to sign manifest. continue (y/N)? ", &ans);
if( blob_str(&ans)[0]!='y' ){
db_end_transaction(1);
fossil_exit(1);
}
}
| | | 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
prompt_user("unable to sign manifest. continue (y/N)? ", &ans);
if( blob_str(&ans)[0]!='y' ){
db_end_transaction(1);
fossil_exit(1);
}
}
brid = content_put_ex(&branch, 0, 0, 0, isPrivate);
if( brid==0 ){
fossil_panic("trouble committing manifest: %s", g.zErrMsg);
}
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", brid);
if( manifest_crosslink(brid, &branch)==0 ){
fossil_panic("unable to install new manifest");
}
|
| ︙ | ︙ | |||
176 177 178 179 180 181 182 | db_end_transaction(0); /* Do an autosync push, if requested */ if( !isPrivate ) autosync(AUTOSYNC_PUSH); } /* | | > > > > | | | | 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 |
db_end_transaction(0);
/* 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.
*/
void branch_prepare_list_query(Stmt *pQuery, int which ){
if( which < 0 ){
db_prepare(pQuery,
"SELECT value FROM tagxref"
" WHERE tagid=%d AND value NOT NULL "
"EXCEPT "
"SELECT value FROM tagxref"
" WHERE tagid=%d"
" 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 ){
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*/",
TAG_BRANCH
);
|
| ︙ | ︙ | |||
258 259 260 261 262 263 264 |
int showClosed = find_option("closed",0,0)!=0;
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);
}
| | | 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 |
int showClosed = find_option("closed",0,0)!=0;
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));
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);
}
db_finalize(&q);
}else{
|
| ︙ | ︙ | |||
325 326 327 328 329 330 331 | @ <div class="sideboxDescribed"><a href="leaves?closed"> @ closed leaves</a></div>. @ Closed branches are fixed and do not change (unless they are first @ reopened)</li> @ </ol> style_sidebox_end(); | | | 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 |
@ <div class="sideboxDescribed"><a href="leaves?closed">
@ closed leaves</a></div>.
@ Closed branches are fixed and do not change (unless they are first
@ reopened)</li>
@ </ol>
style_sidebox_end();
branch_prepare_list_query(&q, showAll?1:(showClosed?-1:0));
cnt = 0;
while( db_step(&q)==SQLITE_ROW ){
const char *zBr = db_column_text(&q, 0);
if( cnt==0 ){
if( colorTest ){
@ <h2>Default background colors for all branches:</h2>
}else if( showAll ){
|
| ︙ | ︙ |
Changes to src/browse.c.
| ︙ | ︙ | |||
194 195 196 197 198 199 200 | ** of all files and subdirectories in the zD[] directory. ** ** Subdirectory names begin with "/". This causes them to sort ** first and it also gives us an easy way to distinguish files ** from directories in the loop that follows. */ db_multi_exec( | | > | 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 |
** of all files and subdirectories in the zD[] directory.
**
** Subdirectory names begin with "/". This causes them to sort
** first and it also gives us an easy way to distinguish files
** from directories in the loop that follows.
*/
db_multi_exec(
"CREATE TEMP TABLE localfiles(x UNIQUE NOT NULL %s, u);",
filename_collation()
);
if( zCI ){
Stmt ins;
ManifestFile *pFile;
ManifestFile *pPrev = 0;
int nPrev = 0;
int c;
|
| ︙ | ︙ | |||
229 230 231 232 233 234 235 |
db_reset(&ins);
pPrev = pFile;
for(nPrev=0; (c=pPrev->zName[nD+nPrev]) && c!='/'; nPrev++){}
if( c=='/' ) nPrev++;
}
db_finalize(&ins);
}else if( zD ){
| > | | | | | | > > > > > > > > > | > > | 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
db_reset(&ins);
pPrev = pFile;
for(nPrev=0; (c=pPrev->zName[nD+nPrev]) && c!='/'; nPrev++){}
if( c=='/' ) nPrev++;
}
db_finalize(&ins);
}else if( zD ){
if( filenames_are_case_sensitive() ){
db_multi_exec(
"INSERT OR IGNORE INTO localfiles"
" SELECT pathelement(name,%d), NULL FROM filename"
" WHERE name GLOB '%q/*'",
nD, zD
);
}else{
db_multi_exec(
"INSERT OR IGNORE INTO localfiles"
" SELECT pathelement(name,%d), NULL FROM filename"
" WHERE name LIKE '%q/%%'",
nD, zD
);
}
}else{
db_multi_exec(
"INSERT OR IGNORE INTO localfiles"
" SELECT pathelement(name,0), NULL FROM filename"
);
}
/* Generate a multi-column table listing the contents of zD[]
** directory.
*/
mxLen = db_int(12, "SELECT max(length(x)) FROM localfiles /*scan*/");
cnt = db_int(0, "SELECT count(*) FROM localfiles /*scan*/");
if( mxLen<12 ) mxLen = 12;
nCol = 100/mxLen;
if( nCol<1 ) nCol = 1;
if( nCol>5 ) nCol = 5;
nRow = (cnt+nCol-1)/nCol;
db_prepare(&q, "SELECT x, u FROM localfiles ORDER BY x /*scan*/");
@ <table class="browser"><tr><td class="browser"><ul class="browser">
i = 0;
while( db_step(&q)==SQLITE_ROW ){
const char *zFN;
if( i==nRow ){
|
| ︙ | ︙ |
Changes to src/captcha.c.
| ︙ | ︙ | |||
410 411 412 413 414 415 416 417 | sqlite3_randomness(sizeof(x), &x); x &= 0x7fffffff; return x; } /* ** Translate a captcha seed value into the captcha password string. */ | > > | | 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 |
sqlite3_randomness(sizeof(x), &x);
x &= 0x7fffffff;
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){
const char *zSecret;
const char *z;
Blob b;
static char zRes[20];
zSecret = db_get("captcha-secret", 0);
if( zSecret==0 ){
|
| ︙ | ︙ |
Changes to src/cgi.c.
| ︙ | ︙ | |||
40 41 42 43 44 45 46 | #ifdef __EMX__ typedef int socklen_t; #endif #include <time.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> | < < < | 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
#ifdef __EMX__
typedef int socklen_t;
#endif
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "cgi.h"
#if INTERFACE
/*
** Shortcuts for cgi_parameter. P("x") returns the value of query parameter
** or cookie "x", or NULL if there is no such parameter or cookie. PD("x","y")
** does the same except "y" is returned in place of NULL if there is not match.
|
| ︙ | ︙ | |||
133 134 135 136 137 138 139 |
blob_reset(&cgiContent[1]);
}
}
/*
** Return a pointer to the HTTP reply text.
*/
| | | 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
blob_reset(&cgiContent[1]);
}
}
/*
** Return a pointer to the HTTP reply text.
*/
char *cgi_extract_content(void){
cgi_combine_header_and_body();
return blob_buffer(&cgiContent[0]);
}
/*
** Additional information used to form the HTTP reply
*/
|
| ︙ | ︙ | |||
323 324 325 326 327 328 329 |
** stale cache is the least of the problem. So we provide an Expires
** header set to a reasonable period (default: one week).
*/
/*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{
| | | 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 |
** stale cache is the least of the problem. So we provide an Expires
** header set to a reasonable period (default: one week).
*/
/*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\r\n");
}
/* Content intended for logged in users should only be cached in
** the browser, not some shared location.
*/
fprintf(g.httpOut, "Content-Type: %s; charset=utf-8\r\n", zContentType);
if( fossil_strcmp(zContentType,"application/x-fossil")==0 ){
|
| ︙ | ︙ | |||
360 361 362 363 364 365 366 | } /* ** Do a redirect request to the URL given in the argument. ** ** The URL must be relative to the base of the fossil server. */ | | | | 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 |
}
/*
** Do a redirect request to the URL given in the argument.
**
** The URL must be relative to the base of the fossil server.
*/
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=='/' ){
zLocation = mprintf("Location: %.*s%s\r\n",
strlen(g.zBaseURL)-strlen(g.zTop), g.zBaseURL, zURL);
}else{
zLocation = mprintf("Location: %s/%s\r\n", g.zBaseURL, zURL);
}
cgi_append_header(zLocation);
cgi_reset_content();
cgi_printf("<html>\n<p>Redirect to %h</p>\n</html>\n", zLocation);
cgi_set_status(302, "Moved Temporarily");
free(zLocation);
cgi_reply();
fossil_exit(0);
}
NORETURN void cgi_redirectf(const char *zFormat, ...){
va_list ap;
va_start(ap, zFormat);
cgi_redirect(vmprintf(zFormat, ap));
va_end(ap);
}
/*
|
| ︙ | ︙ | |||
411 412 413 414 415 416 417 418 419 420 421 422 423 424 |
**
** zName and zValue are not copied and must not change or be
** deallocated after this routine returns.
*/
void cgi_set_parameter_nocopy(const char *zName, const char *zValue){
if( nAllocQP<=nUsedQP ){
nAllocQP = nAllocQP*2 + 10;
aParamQP = fossil_realloc( aParamQP, nAllocQP*sizeof(aParamQP[0]) );
}
aParamQP[nUsedQP].zName = zName;
aParamQP[nUsedQP].zValue = zValue;
if( g.fHttpTrace ){
fprintf(stderr, "# cgi: %s = [%s]\n", zName, zValue);
}
| > > > > | 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 |
**
** zName and zValue are not copied and must not change or be
** deallocated after this routine returns.
*/
void cgi_set_parameter_nocopy(const char *zName, const char *zValue){
if( nAllocQP<=nUsedQP ){
nAllocQP = nAllocQP*2 + 10;
if( nAllocQP>1000 ){
/* Prevent a DOS service attack against the framework */
fossil_fatal("Too many query parameters");
}
aParamQP = fossil_realloc( aParamQP, nAllocQP*sizeof(aParamQP[0]) );
}
aParamQP[nUsedQP].zName = zName;
aParamQP[nUsedQP].zValue = zValue;
if( g.fHttpTrace ){
fprintf(stderr, "# cgi: %s = [%s]\n", zName, zValue);
}
|
| ︙ | ︙ | |||
506 507 508 509 510 511 512 513 514 515 516 517 518 519 |
}else{
if( *z ){ *z++ = 0; }
zValue = "";
}
if( fossil_islower(zName[0]) ){
cgi_set_parameter_nocopy(zName, zValue);
}
}
}
/*
** *pz is a string that consists of multiple lines of text. This
** routine finds the end of the current line of text and converts
** the "\n" or "\r\n" that ends that line into a "\000". It then
| > > > | 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 |
}else{
if( *z ){ *z++ = 0; }
zValue = "";
}
if( fossil_islower(zName[0]) ){
cgi_set_parameter_nocopy(zName, zValue);
}
#ifdef FOSSIL_ENABLE_JSON
json_setenv( zName, cson_value_new_string(zValue,strlen(zValue)) );
#endif /* FOSSIL_ENABLE_JSON */
}
}
/*
** *pz is a string that consists of multiple lines of text. This
** routine finds the end of the current line of text and converts
** the "\n" or "\r\n" that ends that line into a "\000". It then
|
| ︙ | ︙ | |||
678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 |
cgi_set_parameter_nocopy(mprintf("%s:mimetype",zName), z);
}
}
}
}
}
}
/*
** 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;
cgi_destination(CGI_BODY);
z = (char*)P("QUERY_STRING");
if( z ){
z = mprintf("%s",z);
add_param_list(z, '&');
}
z = (char*)P("REMOTE_ADDR");
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > | 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 |
cgi_set_parameter_nocopy(mprintf("%s:mimetype",zName), z);
}
}
}
}
}
}
#ifdef FOSSIL_ENABLE_JSON
/*
** Internal helper for cson_data_source_FILE_n().
*/
typedef struct CgiPostReadState_ {
FILE * fh;
unsigned int len;
unsigned int pos;
} CgiPostReadState;
/*
** cson_data_source_f() impl which reads only up to
** a specified amount of data from its input FILE.
** state MUST be a full populated (CgiPostReadState*).
*/
static int cson_data_source_FILE_n( void * state,
void * dest,
unsigned int * n ){
if( ! state || !dest || !n ) return cson_rc.ArgError;
else {
CgiPostReadState * st = (CgiPostReadState *)state;
if( st->pos >= st->len ){
*n = 0;
return 0;
} else if( !*n || ((st->pos + *n) > st->len) ){
return cson_rc.RangeError;
}else{
unsigned int rsz = (unsigned int)fread( dest, 1, *n, st->fh );
if( ! rsz ){
*n = rsz;
return feof(st->fh) ? 0 : cson_rc.IOError;
}else{
*n = rsz;
st->pos += *n;
return 0;
}
}
}
}
/*
** Reads a JSON object from the first contentLen bytes of zIn. On
** g.json.post is updated to hold the content. On error a
** FSL_JSON_E_INVALID_REQUEST response is output and fossil_exit() is
** called (in HTTP mode exit code 0 is used).
**
** If contentLen is 0 then the whole file is read.
*/
void cgi_parse_POST_JSON( FILE * zIn, unsigned int contentLen ){
cson_value * jv = NULL;
int rc;
CgiPostReadState state;
state.fh = zIn;
state.len = contentLen;
state.pos = 0;
rc = cson_parse( &jv,
contentLen ? cson_data_source_FILE_n : cson_data_source_FILE,
contentLen ? (void *)&state : (void *)zIn, NULL, NULL );
if(rc){
goto invalidRequest;
}else{
json_gc_add( "POST.JSON", jv );
g.json.post.v = jv;
g.json.post.o = cson_value_get_object( jv );
if( !g.json.post.o ){ /* we don't support non-Object (Array) requests */
goto invalidRequest;
}
}
return;
invalidRequest:
cgi_set_content_type(json_guess_content_type());
json_err( FSL_JSON_E_INVALID_REQUEST, NULL, 1 );
fossil_exit( g.isHTTP ? 0 : 1);
}
#endif /* FOSSIL_ENABLE_JSON */
/*
** 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;
#ifdef FOSSIL_ENABLE_JSON
json_main_bootstrap();
#endif
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);
}
len = atoi(PD("CONTENT_LENGTH", "0"));
g.zContentType = zType = P("CONTENT_TYPE");
if( len>0 && zType ){
blob_zero(&g.cgiIn);
if( fossil_strcmp(zType,"application/x-www-form-urlencoded")==0
|| strncmp(zType,"multipart/form-data",19)==0 ){
|
| ︙ | ︙ | |||
720 721 722 723 724 725 726 |
blob_read_from_channel(&g.cgiIn, g.httpIn, len);
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);
}
| > > > > > > > | > > > | < < < > > | | > > > > > > > > > > | 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 |
blob_read_from_channel(&g.cgiIn, g.httpIn, len);
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);
}
#ifdef FOSSIL_ENABLE_JSON
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());
}
#endif /* FOSSIL_ENABLE_JSON */
}
}
/*
** This is the comparison function used to sort the aParamQP[] array of
** query parameters and cookies.
*/
static int qparam_compare(const void *a, const void *b){
|
| ︙ | ︙ | |||
886 887 888 889 890 891 892 | va_end(ap); return 1; } /* ** Print all query parameters on standard output. Format the ** parameters as HTML. This is used for testing and debugging. | > | < | < < < < < > > | < | > | 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 |
va_end(ap);
return 1;
}
/*
** Print all query parameters on standard output. Format the
** parameters as HTML. This is used for testing and debugging.
**
** Omit the values of the cookies unless showAll is true.
*/
void cgi_print_all(int showAll){
int i;
cgi_parameter("",""); /* Force the parameters into sorted order */
for(i=0; i<nUsedQP; i++){
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 <br />\n", zName, aParamQP[i].zValue);
}
}
/*
** This routine works like "printf" except that it has the
** extra formatting capabilities such as %h and %t.
*/
|
| ︙ | ︙ | |||
928 929 930 931 932 933 934 | vxprintf(pContent,zFormat,ap); } /* ** Send a reply indicating that the HTTP request was malformed */ | | | > > > > > > > > > > > > | | | | | | | | | | > | 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 |
vxprintf(pContent,zFormat,ap);
}
/*
** Send a reply indicating that the HTTP request was malformed
*/
static NORETURN void malformed_request(void){
cgi_set_status(501, "Not Implemented");
cgi_printf(
"<html><body>Unrecognized HTTP Request</body></html>\n"
);
cgi_reply();
fossil_exit(0);
}
/*
** Panic and die while processing a webpage.
*/
NORETURN void cgi_panic(const char *zFormat, ...){
va_list ap;
cgi_reset_content();
#ifdef FOSSIL_ENABLE_JSON
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
#endif /* FOSSIL_ENABLE_JSON */
{
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.
** Make *zLeftOver point to the start of the next token.
*/
|
| ︙ | ︙ | |||
993 994 995 996 997 998 999 |
*/
void cgi_handle_http_request(const char *zIpAddr){
char *z, *zToken;
int i;
struct sockaddr_in remoteName;
socklen_t size = sizeof(struct sockaddr_in);
char zLine[2000]; /* A single line of input. */
| < | 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 |
*/
void cgi_handle_http_request(const char *zIpAddr){
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);
if( zToken==0 ){
malformed_request();
|
| ︙ | ︙ | |||
1057 1058 1059 1060 1061 1062 1063 |
cgi_setenv("HTTPS", zVal);
}else if( fossil_strcmp(zFieldName,"host:")==0 ){
cgi_setenv("HTTP_HOST", zVal);
}else if( fossil_strcmp(zFieldName,"if-none-match:")==0 ){
cgi_setenv("HTTP_IF_NONE_MATCH", zVal);
}else if( fossil_strcmp(zFieldName,"if-modified-since:")==0 ){
cgi_setenv("HTTP_IF_MODIFIED_SINCE", zVal);
| < | > < | 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 |
cgi_setenv("HTTPS", zVal);
}else if( fossil_strcmp(zFieldName,"host:")==0 ){
cgi_setenv("HTTP_HOST", zVal);
}else if( fossil_strcmp(zFieldName,"if-none-match:")==0 ){
cgi_setenv("HTTP_IF_NONE_MATCH", zVal);
}else if( fossil_strcmp(zFieldName,"if-modified-since:")==0 ){
cgi_setenv("HTTP_IF_MODIFIED_SINCE", zVal);
#if 0
}else if( fossil_strcmp(zFieldName,"referer:")==0 ){
cgi_setenv("HTTP_REFERER", zVal);
#endif
}else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){
cgi_setenv("HTTP_USER_AGENT", zVal);
}
}
cgi_init();
}
#if INTERFACE
/*
|
| ︙ | ︙ | |||
1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 |
if( nchildren>MAX_PARALLEL ){
/* Slow down if connections are arriving too fast */
sleep( nchildren-MAX_PARALLEL );
}
delay.tv_sec = 60;
delay.tv_usec = 0;
FD_ZERO(&readfds);
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);
if( connection>=0 ){
child = fork();
| > | 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 |
if( nchildren>MAX_PARALLEL ){
/* Slow down if connections are arriving too fast */
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);
if( connection>=0 ){
child = fork();
|
| ︙ | ︙ |
Changes to src/checkin.c.
| ︙ | ︙ | |||
178 179 180 181 182 183 184 |
**
** See also: changes, extra, ls
*/
void status_cmd(void){
int vid;
db_must_be_within_tree();
/* 012345678901234 */
| | < > | 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
**
** See also: changes, extra, ls
*/
void status_cmd(void){
int vid;
db_must_be_within_tree();
/* 012345678901234 */
fossil_print("repository: %s\n", db_repository_filename());
fossil_print("local-root: %s\n", g.zLocalRoot);
vid = db_lget_int("checkout", 0);
if( vid ){
show_common_info(vid, "checkout:", 1, 1);
}
db_record_repository_filename(0);
changes_cmd();
}
/*
** COMMAND: ls
**
** Usage: %fossil ls ?OPTIONS?
|
| ︙ | ︙ | |||
280 281 282 283 284 285 286 |
Blob path;
Blob repo;
Stmt q;
int n;
const char *zIgnoreFlag = find_option("ignore",0,1);
int allFlag = find_option("dotfiles",0,0)!=0;
int cwdRelative = 0;
| < < | > > | 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 |
Blob path;
Blob repo;
Stmt q;
int n;
const char *zIgnoreFlag = find_option("ignore",0,1);
int allFlag = find_option("dotfiles",0,0)!=0;
int cwdRelative = 0;
Glob *pIgnore;
Blob rewrittenPathname;
const char *zPathname, *zDisplayName;
db_must_be_within_tree();
cwdRelative = determine_cwd_relative_option();
db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY %s)",
filename_collation());
n = strlen(g.zLocalRoot);
blob_init(&path, g.zLocalRoot, n-1);
if( zIgnoreFlag==0 ){
zIgnoreFlag = db_get("ignore-glob", 0);
}
pIgnore = glob_create(zIgnoreFlag);
vfile_scan(&path, blob_size(&path), allFlag, pIgnore);
glob_free(pIgnore);
db_prepare(&q,
"SELECT x FROM sfile"
" WHERE x NOT IN (%s)"
" ORDER BY 1",
fossil_all_reserved_names()
);
if( file_tree_name(g.zRepositoryName, &repo, 0) ){
db_multi_exec("DELETE FROM sfile WHERE x=%B", &repo);
}
db_multi_exec("DELETE FROM sfile WHERE x IN (SELECT pathname FROM vfile)");
blob_zero(&rewrittenPathname);
while( db_step(&q)==SQLITE_ROW ){
zDisplayName = zPathname = db_column_text(&q, 0);
if( cwdRelative ) {
char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname);
file_relative_name(zFullName, &rewrittenPathname);
free(zFullName);
|
| ︙ | ︙ | |||
586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 |
/*
** zDate should be a valid date string. Convert this string into the
** format YYYY-MM-DDTHH:MM:SS. If the string is not a valid date,
** print a fatal error and quit.
*/
char *date_in_standard_format(const char *zInputDate){
char *zDate;
zDate = db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%Q)",
zInputDate);
if( zDate[0]==0 ){
fossil_fatal(
"unrecognized date format (%s): use \"YYYY-MM-DD HH:MM:SS.SSS\"",
zInputDate
);
}
return zDate;
}
/*
** Create a manifest.
*/
static void create_manifest(
Blob *pOut, /* Write the manifest here */
const char *zBaselineUuid, /* UUID of baseline, or zero */
| > > > > > > > > > > > > > > > > > > > | 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 |
/*
** zDate should be a valid date string. Convert this string into the
** format YYYY-MM-DDTHH:MM:SS. If the string is not a valid date,
** print a fatal error and quit.
*/
char *date_in_standard_format(const char *zInputDate){
char *zDate;
if( g.perm.Setup && fossil_strcmp(zInputDate,"now")==0 ){
zInputDate = PD("date_override","now");
}
zDate = db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%Q)",
zInputDate);
if( zDate[0]==0 ){
fossil_fatal(
"unrecognized date format (%s): use \"YYYY-MM-DD HH:MM:SS.SSS\"",
zInputDate
);
}
return zDate;
}
/*
** COMMAND: test-date-format
**
** Usage: %fossil test-date-format DATE-STRING...
**
** Convert the DATE-STRING into the standard format used in artifacts
** and display the result.
*/
void test_date_format(void){
int i;
db_find_and_open_repository(OPEN_ANY_SCHEMA, 0);
for(i=2; i<g.argc; i++){
fossil_print("%s -> %s\n", g.argv[i], date_in_standard_format(g.argv[i]));
}
}
/*
** Create a manifest.
*/
static void create_manifest(
Blob *pOut, /* Write the manifest here */
const char *zBaselineUuid, /* UUID of baseline, or zero */
|
| ︙ | ︙ | |||
656 657 658 659 660 661 662 |
blob_appendf(&filename, "%s", g.zLocalRoot);
nBasename = blob_size(&filename);
while( db_step(&q)==SQLITE_ROW ){
const char *zName = db_column_text(&q, 0);
const char *zUuid = db_column_text(&q, 1);
const char *zOrig = db_column_text(&q, 2);
int frid = db_column_int(&q, 3);
| | > > | | > < | < < < | > > | | 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 |
blob_appendf(&filename, "%s", g.zLocalRoot);
nBasename = blob_size(&filename);
while( db_step(&q)==SQLITE_ROW ){
const char *zName = db_column_text(&q, 0);
const char *zUuid = db_column_text(&q, 1);
const char *zOrig = db_column_text(&q, 2);
int frid = db_column_int(&q, 3);
int isExe = db_column_int(&q, 4);
int isLink = db_column_int(&q, 5);
int isSelected = db_column_int(&q, 6);
const char *zPerm;
int cmp;
#if !defined(_WIN32)
int mPerm;
/* For unix, extract the "executable" and "symlink" permissions
** directly from the filesystem. On windows, permissions are
** unchanged from the original.
*/
blob_resize(&filename, nBasename);
blob_append(&filename, zName, -1);
mPerm = file_wd_perm(blob_str(&filename));
isExe = ( mPerm==PERM_EXE );
isLink = ( mPerm==PERM_LNK );
#endif
if( isExe ){
zPerm = " x";
}else if( isLink ){
zPerm = " l"; /* note: symlinks don't have executable bit on unix */
}else{
zPerm = "";
}
if( !g.markPrivate ) content_make_public(frid);
|
| ︙ | ︙ | |||
824 825 826 827 828 829 830 |
}
blob_reset(&ans);
blob_reset(&fname);
}
}
/*
| | | 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 |
}
blob_reset(&ans);
blob_reset(&fname);
}
}
/*
** COMMAND: ci*
** COMMAND: commit
**
** Usage: %fossil commit ?OPTIONS? ?FILE...?
**
** Create a new version containing all of the changes in the current
** checkout. You will be prompted to enter a check-in comment unless
** the comment has been specified on the command-line using "-m" or a
|
| ︙ | ︙ |
Changes to src/checkout.c.
| ︙ | ︙ | |||
159 160 161 162 163 164 165 |
free(zManFile);
}
}
}
/*
| | | | 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
free(zManFile);
}
}
}
/*
** COMMAND: checkout*
** COMMAND: co*
**
** Usage: %fossil checkout ?VERSION | --latest? ?OPTIONS?
** or: %fossil co ?VERSION | --latest? ?OPTIONS?
**
** Check out a version specified on the command-line. This command
** will abort if there are edited files in the current checkout unless
** the --force option appears on the command-line. The --keep option
|
| ︙ | ︙ | |||
270 271 272 273 274 275 276 |
file_delete(z);
free(z);
}
}
}
/*
| | | 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 |
file_delete(z);
free(z);
}
}
}
/*
** COMMAND: close*
**
** Usage: %fossil close ?OPTIONS?
**
** The opposite of "open". Close the current database connection.
** Require a -f or --force flag if there are unsaved changed in the
** current check-out.
**
|
| ︙ | ︙ |
Changes to src/clone.c.
| ︙ | ︙ | |||
17 18 19 20 21 22 23 24 25 26 27 28 29 30 | ** ** This file contains code used to clone a repository */ #include "config.h" #include "clone.h" #include <assert.h> /* ** COMMAND: clone ** ** Usage: %fossil clone ?OPTIONS? URL FILENAME ** | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
**
** This file contains code used to clone a repository
*/
#include "config.h"
#include "clone.h"
#include <assert.h>
/*
** Delete all private content from a repository.
*/
void delete_private_content(void){
Bag toUndelta;
Stmt q;
int rid;
/* Carefule: We are about to delete all BLOB entries that are private.
** So make sure that any no public BLOBs are deltas from a private BLOB.
** Otherwise after the deletion, we won't be able to recreate the public
** BLOBs.
*/
db_prepare(&q,
"SELECT "
" rid, (SELECT uuid FROM blob WHERE rid=delta.rid),"
" srcid, (SELECT uuid FROM blob WHERE rid=delta.srcid)"
" FROM delta"
" WHERE srcid in private AND rid NOT IN private"
);
bag_init(&toUndelta);
while( db_step(&q)==SQLITE_ROW ){
int rid = db_column_int(&q, 0);
const char *zId = db_column_text(&q, 1);
int srcid = db_column_int(&q, 2);
const char *zSrc = db_column_text(&q, 3);
fossil_warning(
"public artifact %S (%d) is a delta from private artifact %S (%d)\n",
zId, rid, zSrc, srcid
);
bag_insert(&toUndelta, rid);
}
db_finalize(&q);
while( (rid = bag_first(&toUndelta))>0 ){
content_undelta(rid);
bag_remove(&toUndelta, rid);
}
bag_clear(&toUndelta);
/* Now it is safe to remove all private content
*/
db_multi_exec(
"DELETE FROM blob WHERE rid IN private;"
"DELETE FROM delta wHERE rid IN private;"
"DELETE FROM private;"
);
}
/*
** COMMAND: clone
**
** Usage: %fossil clone ?OPTIONS? URL FILENAME
**
|
| ︙ | ︙ | |||
70 71 72 73 74 75 76 |
db_multi_exec(
"REPLACE INTO config(name,value,mtime)"
" VALUES('server-code', lower(hex(randomblob(20))),now());"
"REPLACE INTO config(name,value,mtime)"
" VALUES('last-sync-url', '%q',now());",
g.urlCanonical
);
| < < < | < | 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
db_multi_exec(
"REPLACE INTO config(name,value,mtime)"
" VALUES('server-code', lower(hex(randomblob(20))),now());"
"REPLACE INTO config(name,value,mtime)"
" VALUES('last-sync-url', '%q',now());",
g.urlCanonical
);
if( !bPrivate ) delete_private_content();
shun_artifacts();
db_create_default_users(1, zDefaultUser);
if( zDefaultUser ){
g.zLogin = zDefaultUser;
}else{
g.zLogin = db_text(0, "SELECT login FROM user WHERE cap LIKE '%%s%%'");
}
|
| ︙ | ︙ |
Changes to src/config.h.
| ︙ | ︙ | |||
132 133 134 135 136 137 138 139 | # define FOSSIL_INT_TO_PTR(X) ((void*)&((char*)0)[X]) # define FOSSIL_PTR_TO_INT(X) ((int)(((char*)X)-(char*)0)) #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 #endif /* _RC_COMPILE_ */ | > > > > > > > > > | 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | # define FOSSIL_INT_TO_PTR(X) ((void*)&((char*)0)[X]) # define FOSSIL_PTR_TO_INT(X) ((int)(((char*)X)-(char*)0)) #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_ */ |
Changes to src/configure.c.
| ︙ | ︙ | |||
31 32 33 34 35 36 37 38 39 40 41 42 43 44 | */ #define CONFIGSET_SKIN 0x000001 /* WWW interface appearance */ #define CONFIGSET_TKT 0x000002 /* Ticket configuration */ #define CONFIGSET_PROJ 0x000004 /* Project name */ #define CONFIGSET_SHUN 0x000008 /* Shun settings */ #define CONFIGSET_USER 0x000010 /* The USER table */ #define CONFIGSET_ADDR 0x000020 /* The CONCEALED table */ #define CONFIGSET_ALL 0x0000ff /* Everything */ #define CONFIGSET_OVERWRITE 0x100000 /* Causes overwrite instead of merge */ #define CONFIGSET_OLDFORMAT 0x200000 /* Use the legacy format */ #endif /* INTERFACE */ | > | 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | */ #define CONFIGSET_SKIN 0x000001 /* WWW interface appearance */ #define CONFIGSET_TKT 0x000002 /* Ticket configuration */ #define CONFIGSET_PROJ 0x000004 /* Project name */ #define CONFIGSET_SHUN 0x000008 /* Shun settings */ #define CONFIGSET_USER 0x000010 /* The USER table */ #define CONFIGSET_ADDR 0x000020 /* The CONCEALED table */ #define CONFIGSET_XFER 0x000040 /* Transfer configuration */ #define CONFIGSET_ALL 0x0000ff /* Everything */ #define CONFIGSET_OVERWRITE 0x100000 /* Causes overwrite instead of merge */ #define CONFIGSET_OLDFORMAT 0x200000 /* Use the legacy format */ #endif /* INTERFACE */ |
| ︙ | ︙ | |||
53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
} aGroupName[] = {
{ "/email", CONFIGSET_ADDR, "Concealed email addresses in tickets" },
{ "/project", CONFIGSET_PROJ, "Project name and description" },
{ "/skin", CONFIGSET_SKIN, "Web interface apparance settings" },
{ "/shun", CONFIGSET_SHUN, "List of shunned artifacts" },
{ "/ticket", CONFIGSET_TKT, "Ticket setup", },
{ "/user", CONFIGSET_USER, "Users and privilege settings" },
{ "/all", CONFIGSET_ALL, "All of the above" },
};
/*
** The following is a list of settings that we are willing to
** transfer.
| > | 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
} aGroupName[] = {
{ "/email", CONFIGSET_ADDR, "Concealed email addresses in tickets" },
{ "/project", CONFIGSET_PROJ, "Project name and description" },
{ "/skin", CONFIGSET_SKIN, "Web interface apparance settings" },
{ "/shun", CONFIGSET_SHUN, "List of shunned artifacts" },
{ "/ticket", CONFIGSET_TKT, "Ticket setup", },
{ "/user", CONFIGSET_USER, "Users and privilege settings" },
{ "/xfer", CONFIGSET_XFER, "Transfer setup", },
{ "/all", CONFIGSET_ALL, "All of the above" },
};
/*
** The following is a list of settings that we are willing to
** transfer.
|
| ︙ | ︙ | |||
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
{ "logo-image", CONFIGSET_SKIN },
{ "project-name", CONFIGSET_PROJ },
{ "project-description", CONFIGSET_PROJ },
{ "manifest", CONFIGSET_PROJ },
{ "ignore-glob", CONFIGSET_PROJ },
{ "crnl-glob", CONFIGSET_PROJ },
{ "empty-dirs", CONFIGSET_PROJ },
{ "index-page", CONFIGSET_SKIN },
{ "timeline-block-markup", CONFIGSET_SKIN },
{ "timeline-max-comment", CONFIGSET_SKIN },
{ "ticket-table", CONFIGSET_TKT },
{ "ticket-common", CONFIGSET_TKT },
{ "ticket-newpage", CONFIGSET_TKT },
{ "ticket-viewpage", CONFIGSET_TKT },
{ "ticket-editpage", CONFIGSET_TKT },
{ "ticket-reportlist", CONFIGSET_TKT },
{ "ticket-report-template", CONFIGSET_TKT },
{ "ticket-key-template", CONFIGSET_TKT },
{ "ticket-title-expr", CONFIGSET_TKT },
{ "ticket-closed-expr", CONFIGSET_TKT },
{ "@reportfmt", CONFIGSET_TKT },
{ "@user", CONFIGSET_USER },
{ "@concealed", CONFIGSET_ADDR },
{ "@shun", CONFIGSET_SHUN },
};
static int iConfig = 0;
/*
** Return name of first configuration property matching the given mask.
*/
const char *configure_first_name(int iMask){
| > > > > > > | 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
{ "logo-image", CONFIGSET_SKIN },
{ "project-name", CONFIGSET_PROJ },
{ "project-description", CONFIGSET_PROJ },
{ "manifest", CONFIGSET_PROJ },
{ "ignore-glob", CONFIGSET_PROJ },
{ "crnl-glob", CONFIGSET_PROJ },
{ "empty-dirs", CONFIGSET_PROJ },
{ "allow-symlinks", CONFIGSET_PROJ },
{ "index-page", CONFIGSET_SKIN },
#ifdef FOSSIL_ENABLE_TCL
{ "tcl", CONFIGSET_SKIN|CONFIGSET_TKT|CONFIGSET_XFER },
#endif
{ "timeline-block-markup", CONFIGSET_SKIN },
{ "timeline-max-comment", CONFIGSET_SKIN },
{ "ticket-table", CONFIGSET_TKT },
{ "ticket-common", CONFIGSET_TKT },
{ "ticket-newpage", CONFIGSET_TKT },
{ "ticket-viewpage", CONFIGSET_TKT },
{ "ticket-editpage", CONFIGSET_TKT },
{ "ticket-reportlist", CONFIGSET_TKT },
{ "ticket-report-template", CONFIGSET_TKT },
{ "ticket-key-template", CONFIGSET_TKT },
{ "ticket-title-expr", CONFIGSET_TKT },
{ "ticket-closed-expr", CONFIGSET_TKT },
{ "@reportfmt", CONFIGSET_TKT },
{ "@user", CONFIGSET_USER },
{ "@concealed", CONFIGSET_ADDR },
{ "@shun", CONFIGSET_SHUN },
{ "xfer-common-script", CONFIGSET_XFER },
{ "xfer-push-script", CONFIGSET_XFER },
};
static int iConfig = 0;
/*
** Return name of first configuration property matching the given mask.
*/
const char *configure_first_name(int iMask){
|
| ︙ | ︙ | |||
722 723 724 725 726 727 728 | configure_send_group(&out, groupMask, iStart); blob_write_to_file(&out, zFilename); blob_reset(&out); } /* | | | 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 | configure_send_group(&out, groupMask, iStart); blob_write_to_file(&out, zFilename); blob_reset(&out); } /* ** COMMAND: configuration* ** ** Usage: %fossil configuration METHOD ... ?OPTIONS? ** ** Where METHOD is one of: export import merge pull push reset. All methods ** accept the -R or --repository option to specific a repository. ** ** %fossil configuration export AREA FILENAME |
| ︙ | ︙ | |||
783 784 785 786 787 788 789 790 791 792 793 794 795 796 |
void configuration_cmd(void){
int n;
const char *zMethod;
if( g.argc<3 ){
usage("export|import|merge|pull|reset ...");
}
db_find_and_open_repository(0, 0);
zMethod = g.argv[2];
n = strlen(zMethod);
if( strncmp(zMethod, "export", n)==0 ){
int mask;
const char *zSince = find_option("since",0,1);
sqlite3_int64 iStart;
if( g.argc!=5 ){
| > | 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 |
void configuration_cmd(void){
int n;
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);
sqlite3_int64 iStart;
if( g.argc!=5 ){
|
| ︙ | ︙ |
Changes to src/content.c.
| ︙ | ︙ | |||
301 302 303 304 305 306 307 |
}else{
bag_insert(&contentCache.available, rid);
}
return rc;
}
/*
| | > > > | 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
}else{
bag_insert(&contentCache.available, rid);
}
return rc;
}
/*
** COMMAND: artifact*
**
** Usage: %fossil artifact ARTIFACT-ID ?OUTPUT-FILENAME? ?OPTIONS?
**
** Extract an artifact by its SHA1 hash and write the results on
** standard output, or if the optional 4th argument is given, in
** the named output file.
**
** Options:
** -R|--repository FILE Extract artifacts from repository FILE
**
** See also: finfo
*/
void artifact_cmd(void){
int rid;
Blob content;
const char *zFile;
db_find_and_open_repository(OPEN_ANY_SCHEMA, 0);
if( g.argc!=4 && g.argc!=3 ) usage("ARTIFACT-ID ?FILENAME? ?OPTIONS?");
zFile = g.argc==4 ? g.argv[3] : "-";
rid = name_to_rid(g.argv[2]);
if( rid==0 ){
fossil_fatal("%s",g.zErrMsg);
}
content_get(rid, &content);
blob_write_to_file(&content, zFile);
}
/*
** COMMAND: test-content-rawget
**
|
| ︙ | ︙ | |||
828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 |
*/
void test_integrity(void){
Stmt q;
Blob content;
Blob cksum;
int n1 = 0;
int n2 = 0;
int total;
db_find_and_open_repository(OPEN_ANY_SCHEMA, 2);
db_prepare(&q, "SELECT rid, uuid, size FROM blob ORDER BY rid");
total = db_int(0, "SELECT max(rid) FROM blob");
while( db_step(&q)==SQLITE_ROW ){
int rid = db_column_int(&q, 0);
const char *zUuid = db_column_text(&q, 1);
int size = db_column_int(&q, 2);
n1++;
fossil_print(" %d/%d\r", n1, total);
fflush(stdout);
if( size<0 ){
fossil_print("skip phantom %d %s\n", rid, zUuid);
continue; /* Ignore phantoms */
}
content_get(rid, &content);
if( blob_size(&content)!=size ){
| > > > > > > > > > > > > > > > > > > > > > > > | | > | | > | > | 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 |
*/
void test_integrity(void){
Stmt q;
Blob content;
Blob cksum;
int n1 = 0;
int n2 = 0;
int nErr = 0;
int total;
db_find_and_open_repository(OPEN_ANY_SCHEMA, 2);
/* Make sure no public artifact is a delta from a private artifact */
db_prepare(&q,
"SELECT "
" rid, (SELECT uuid FROM blob WHERE rid=delta.rid),"
" srcid, (SELECT uuid FROM blob WHERE rid=delta.srcid)"
" FROM delta"
" WHERE srcid in private AND rid NOT IN private"
);
while( db_step(&q)==SQLITE_ROW ){
int rid = db_column_int(&q, 0);
const char *zId = db_column_text(&q, 1);
int srcid = db_column_int(&q, 2);
const char *zSrc = db_column_text(&q, 3);
fossil_print(
"public artifact %S (%d) is a delta from private artifact %S (%d)\n",
zId, rid, zSrc, srcid
);
nErr++;
}
db_finalize(&q);
db_prepare(&q, "SELECT rid, uuid, size FROM blob ORDER BY rid");
total = db_int(0, "SELECT max(rid) FROM blob");
while( db_step(&q)==SQLITE_ROW ){
int rid = db_column_int(&q, 0);
const char *zUuid = db_column_text(&q, 1);
int size = db_column_int(&q, 2);
n1++;
fossil_print(" %d/%d\r", n1, total);
fflush(stdout);
if( size<0 ){
fossil_print("skip phantom %d %s\n", rid, zUuid);
continue; /* Ignore phantoms */
}
content_get(rid, &content);
if( blob_size(&content)!=size ){
fossil_print("size mismatch on artifact %d: wanted %d but got %d\n",
rid, size, blob_size(&content));
nErr++;
}
sha1sum_blob(&content, &cksum);
if( fossil_strcmp(blob_str(&cksum), zUuid)!=0 ){
fossil_print("checksum mismatch on artifact %d: wanted %s but got %s\n",
rid, zUuid, blob_str(&cksum));
nErr++;
}
blob_reset(&cksum);
blob_reset(&content);
n2++;
}
db_finalize(&q);
fossil_print("%d non-phantom blobs (out of %d total) checked: %d errors\n",
n2, n1, nErr);
}
|
Added src/cson_amalgamation.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040 4041 4042 4043 4044 4045 4046 4047 4048 4049 4050 4051 4052 4053 4054 4055 4056 4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105 4106 4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128 4129 4130 4131 4132 4133 4134 4135 4136 4137 4138 4139 4140 4141 4142 4143 4144 4145 4146 4147 4148 4149 4150 4151 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 4178 4179 4180 4181 4182 4183 4184 4185 4186 4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200 4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 4260 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 4410 4411 4412 4413 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 4473 4474 4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 4489 4490 4491 4492 4493 4494 4495 4496 4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4514 4515 4516 4517 4518 4519 4520 4521 4522 4523 4524 4525 4526 4527 4528 4529 4530 4531 4532 4533 4534 4535 4536 4537 4538 4539 4540 4541 4542 4543 4544 4545 4546 4547 4548 4549 4550 4551 4552 4553 4554 4555 4556 4557 4558 4559 4560 4561 4562 4563 4564 4565 4566 4567 4568 4569 4570 4571 4572 4573 4574 4575 4576 4577 4578 4579 4580 4581 4582 4583 4584 4585 4586 4587 4588 4589 4590 4591 4592 4593 4594 4595 4596 4597 4598 4599 4600 4601 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 4612 4613 4614 4615 4616 4617 4618 4619 4620 4621 4622 4623 4624 4625 4626 4627 4628 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 4649 4650 4651 4652 4653 4654 4655 4656 4657 4658 4659 4660 4661 4662 4663 4664 4665 4666 4667 4668 4669 4670 4671 4672 4673 4674 4675 4676 4677 4678 4679 4680 4681 4682 4683 4684 4685 4686 4687 4688 4689 4690 4691 4692 4693 4694 4695 4696 4697 4698 4699 4700 4701 4702 4703 4704 4705 4706 4707 4708 4709 4710 4711 4712 4713 4714 4715 4716 4717 4718 4719 4720 4721 4722 4723 4724 4725 4726 4727 4728 4729 4730 4731 4732 4733 4734 4735 4736 4737 4738 4739 4740 4741 4742 4743 4744 4745 4746 4747 4748 4749 4750 4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 4761 4762 4763 4764 4765 4766 4767 4768 4769 4770 4771 4772 4773 4774 4775 4776 4777 4778 4779 4780 4781 4782 4783 4784 4785 4786 4787 4788 4789 4790 4791 4792 4793 4794 4795 4796 4797 4798 4799 4800 4801 4802 4803 4804 4805 4806 4807 4808 4809 4810 4811 4812 4813 4814 4815 4816 4817 4818 4819 4820 4821 4822 4823 4824 4825 4826 4827 4828 4829 4830 4831 4832 4833 4834 4835 4836 4837 4838 4839 4840 4841 4842 4843 4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860 4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 4889 4890 4891 4892 4893 4894 4895 4896 4897 4898 4899 4900 4901 4902 4903 4904 4905 4906 4907 4908 4909 4910 4911 4912 4913 4914 4915 4916 4917 4918 4919 4920 4921 4922 4923 4924 4925 4926 4927 4928 4929 4930 4931 4932 4933 4934 4935 4936 4937 4938 4939 4940 4941 4942 4943 4944 4945 4946 4947 4948 4949 4950 4951 4952 4953 4954 4955 4956 4957 4958 4959 4960 4961 4962 4963 4964 4965 4966 4967 4968 4969 4970 4971 4972 4973 4974 4975 4976 4977 4978 4979 4980 4981 4982 4983 4984 4985 4986 4987 4988 4989 4990 4991 4992 4993 4994 4995 4996 4997 4998 4999 5000 5001 5002 5003 5004 5005 5006 5007 5008 5009 5010 5011 5012 5013 5014 5015 5016 5017 5018 5019 5020 5021 5022 5023 5024 5025 5026 5027 5028 5029 5030 5031 5032 5033 5034 5035 5036 5037 5038 5039 5040 5041 5042 5043 5044 5045 5046 5047 5048 5049 5050 5051 5052 5053 5054 5055 5056 5057 5058 5059 5060 5061 5062 5063 5064 5065 5066 5067 5068 5069 5070 5071 5072 5073 5074 5075 5076 5077 5078 5079 5080 5081 5082 5083 5084 5085 5086 5087 5088 5089 5090 5091 5092 5093 5094 5095 5096 5097 5098 5099 5100 5101 5102 5103 5104 5105 5106 5107 5108 5109 5110 5111 5112 5113 5114 5115 5116 5117 5118 5119 5120 5121 5122 5123 5124 5125 5126 5127 5128 5129 5130 5131 5132 5133 5134 5135 5136 5137 5138 5139 5140 5141 5142 5143 5144 5145 5146 5147 5148 5149 5150 5151 5152 5153 5154 5155 5156 5157 5158 5159 5160 5161 5162 5163 5164 5165 5166 5167 5168 5169 5170 5171 5172 5173 5174 5175 5176 5177 5178 5179 5180 5181 5182 5183 5184 5185 5186 5187 5188 5189 5190 5191 5192 5193 5194 5195 5196 5197 5198 5199 5200 5201 5202 5203 5204 5205 5206 5207 5208 5209 5210 5211 5212 5213 5214 5215 5216 5217 5218 5219 5220 5221 5222 5223 5224 5225 5226 5227 5228 5229 5230 5231 5232 5233 5234 5235 5236 5237 5238 5239 5240 5241 5242 5243 5244 5245 5246 5247 5248 5249 5250 5251 5252 5253 5254 5255 5256 5257 5258 5259 5260 5261 5262 5263 5264 5265 5266 5267 5268 5269 5270 5271 5272 5273 5274 5275 5276 5277 5278 5279 5280 5281 5282 5283 5284 5285 5286 5287 5288 5289 5290 5291 5292 5293 5294 5295 5296 5297 5298 5299 5300 5301 5302 5303 5304 5305 5306 5307 5308 5309 5310 5311 5312 5313 5314 5315 5316 5317 5318 5319 5320 5321 5322 5323 5324 5325 5326 5327 5328 5329 5330 5331 5332 5333 5334 5335 5336 5337 5338 5339 5340 5341 5342 5343 5344 5345 5346 5347 5348 5349 5350 5351 5352 5353 5354 5355 5356 5357 5358 5359 5360 5361 5362 5363 5364 5365 5366 5367 5368 5369 5370 5371 5372 5373 5374 5375 5376 5377 5378 5379 5380 5381 5382 5383 5384 5385 5386 5387 5388 5389 5390 5391 5392 5393 5394 5395 5396 5397 5398 5399 5400 5401 5402 5403 5404 5405 5406 5407 5408 5409 5410 5411 5412 5413 5414 5415 5416 5417 5418 5419 5420 5421 5422 5423 5424 5425 5426 5427 5428 5429 5430 5431 5432 5433 5434 5435 5436 5437 5438 5439 5440 5441 5442 5443 5444 5445 5446 5447 5448 5449 5450 5451 5452 5453 5454 5455 5456 5457 5458 5459 5460 5461 5462 5463 5464 5465 5466 5467 5468 5469 5470 5471 5472 5473 5474 5475 5476 5477 5478 5479 5480 5481 5482 5483 5484 5485 5486 5487 5488 5489 5490 5491 5492 5493 5494 5495 5496 5497 5498 5499 5500 5501 5502 5503 5504 5505 5506 5507 5508 5509 5510 5511 5512 5513 5514 5515 5516 5517 5518 5519 5520 5521 5522 5523 5524 5525 5526 5527 5528 5529 5530 5531 5532 5533 5534 5535 5536 5537 5538 5539 5540 5541 5542 5543 5544 5545 5546 5547 5548 5549 5550 5551 5552 5553 5554 5555 5556 5557 5558 5559 5560 5561 5562 5563 5564 5565 5566 5567 5568 5569 5570 5571 5572 5573 5574 5575 5576 5577 5578 5579 5580 5581 5582 5583 5584 5585 5586 5587 |
#ifdef FOSSIL_ENABLE_JSON
/* 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>
#include <errno.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_object( cson_value * self );
/**
If self is-a array then this function destroys its contents,
else this function does nothing.
*/
static void cson_value_destroy_array( cson_value * self );
static const cson_value_api cson_value_api_null = { CSON_TYPE_NULL, cson_value_destroy_zero_it };
static const cson_value_api cson_value_api_undef = { CSON_TYPE_UNDEF, cson_value_destroy_zero_it };
static const cson_value_api cson_value_api_bool = { CSON_TYPE_BOOL, cson_value_destroy_zero_it };
static const cson_value_api cson_value_api_integer = { CSON_TYPE_INTEGER, cson_value_destroy_zero_it };
static const cson_value_api cson_value_api_double = { CSON_TYPE_DOUBLE, cson_value_destroy_zero_it };
static const cson_value_api cson_value_api_string = { CSON_TYPE_STRING, cson_value_destroy_zero_it };
static const cson_value_api cson_value_api_array = { CSON_TYPE_ARRAY, cson_value_destroy_array };
static const cson_value_api cson_value_api_object = { CSON_TYPE_OBJECT, cson_value_destroy_object };
static const cson_value cson_value_undef = { &cson_value_api_undef, NULL, 0 };
static const cson_value cson_value_null_empty = { &cson_value_api_null, NULL, 0 };
static const cson_value cson_value_bool_empty = { &cson_value_api_bool, NULL, 0 };
static const cson_value cson_value_integer_empty = { &cson_value_api_integer, NULL, 0 };
static const cson_value cson_value_double_empty = { &cson_value_api_double, NULL, 0 };
static const cson_value cson_value_string_empty = { &cson_value_api_string, NULL, 0 };
static const cson_value cson_value_array_empty = { &cson_value_api_array, NULL, 0 };
static const cson_value cson_value_object_empty = { &cson_value_api_object, NULL, 0 };
struct cson_string
{
unsigned int length;
};
#define cson_string_empty_m {0/*length*/}
static const cson_string cson_string_empty = cson_string_empty_m;
#define CSON_CAST(T,V) ((T*)((V)->value))
#define CSON_VCAST(V) ((cson_value *)(((unsigned char *)(V))-sizeof(cson_value)))
#define CSON_INT(V) ((cson_int_t*)(V)->value)
#define CSON_DBL(V) CSON_CAST(cson_double_t,(V))
#define CSON_STR(V) CSON_CAST(cson_string,(V))
#define CSON_OBJ(V) CSON_CAST(cson_object,(V))
#define CSON_ARRAY(V) CSON_CAST(cson_array,(V))
/**
Holds special shared "constant" (though they are non-const)
values.
*/
static struct CSON_EMPTY_HOLDER_
{
char trueValue;
cson_string stringValue;
} CSON_EMPTY_HOLDER = {
1/*trueValue*/,
cson_string_empty_m
};
/**
Indexes into the CSON_SPECIAL_VALUES array.
If this enum changes in any way,
makes damned sure that CSON_SPECIAL_VALUES is updated
to match!!!
*/
enum CSON_INTERNAL_VALUES {
CSON_VAL_UNDEF = 0,
CSON_VAL_NULL = 1,
CSON_VAL_TRUE = 2,
CSON_VAL_FALSE = 3,
CSON_VAL_INT_0 = 4,
CSON_VAL_DBL_0 = 5,
CSON_VAL_STR_EMPTY = 6,
CSON_INTERNAL_VALUES_LENGTH
};
/**
Some "special" shared cson_value instances.
These values MUST be initialized in the order specified
by the CSON_INTERNAL_VALUES enum.
Note that they are not const because they are used as
shared-allocation objects in non-const contexts. However, the
public API provides no way to modifying them, and clients who
modify values directly are subject to The Wrath of Undefined
Behaviour.
*/
static cson_value CSON_SPECIAL_VALUES[] = {
{ &cson_value_api_undef, NULL, 0 }, /* UNDEF */
{ &cson_value_api_null, NULL, 0 }, /* NULL */
{ &cson_value_api_bool, &CSON_EMPTY_HOLDER.trueValue, 0 }, /* TRUE */
{ &cson_value_api_bool, NULL, 0 }, /* FALSE */
{ &cson_value_api_integer, NULL, 0 }, /* INT_0 */
{ &cson_value_api_double, NULL, 0 }, /* DBL_0 */
{ &cson_value_api_string, &CSON_EMPTY_HOLDER.stringValue, 0 }, /* STR_EMPTY */
{ 0, NULL, 0 }
};
/**
Returns non-0 (true) if m is one of our special
"built-in" values, e.g. from CSON_SPECIAL_VALUES and some
"empty" values.
If this returns true, m MUST NOT be free()d!
*/
static char cson_value_is_builtin( void const * m )
{
if((m >= (void const *)&CSON_EMPTY_HOLDER)
&& ( m < (void const *)(&CSON_EMPTY_HOLDER+1)))
return 1;
else return
((m > (void const *)&CSON_SPECIAL_VALUES[0])
&& ( m < (void const *)&CSON_SPECIAL_VALUES[CSON_INTERNAL_VALUES_LENGTH]) )
? 1
: 0;
}
char const * cson_rc_string(int rc)
{
if(0 == rc) return "OK";
#define CHECK(N) else if(cson_rc.N == rc ) return #N
CHECK(OK);
CHECK(ArgError);
CHECK(RangeError);
CHECK(TypeError);
CHECK(IOError);
CHECK(AllocError);
CHECK(NYIError);
CHECK(InternalError);
CHECK(UnsupportedError);
CHECK(NotFoundError);
CHECK(UnknownError);
CHECK(Parse_INVALID_CHAR);
CHECK(Parse_INVALID_KEYWORD);
CHECK(Parse_INVALID_ESCAPE_SEQUENCE);
CHECK(Parse_INVALID_UNICODE_SEQUENCE);
CHECK(Parse_INVALID_NUMBER);
CHECK(Parse_NESTING_DEPTH_REACHED);
CHECK(Parse_UNBALANCED_COLLECTION);
CHECK(Parse_EXPECTED_KEY);
CHECK(Parse_EXPECTED_COLON);
else return "UnknownError";
#undef CHECK
}
/**
If CSON_LOG_ALLOC is true then the cson_malloc/realloc/free() routines
will log a message to stderr.
*/
#define CSON_LOG_ALLOC 0
/**
CSON_FOSSIL_MODE is only for use in the Fossil
source tree, so that we can plug in to its allocators.
We can't do this by, e.g., defining macros for the
malloc/free funcs because fossil's lack of header files
means we would have to #include "main.c" here to
get the declarations.
*/
#if defined(CSON_FOSSIL_MODE)
void *fossil_malloc(size_t n);
void fossil_free(void *p);
void *fossil_realloc(void *p, size_t n);
# define CSON_MALLOC_IMPL fossil_malloc
# define CSON_FREE_IMPL fossil_free
# define CSON_REALLOC_IMPL fossil_realloc
#endif
#if !defined CSON_MALLOC_IMPL
# define CSON_MALLOC_IMPL malloc
#endif
#if !defined CSON_FREE_IMPL
# define CSON_FREE_IMPL free
#endif
#if !defined CSON_REALLOC_IMPL
# define CSON_REALLOC_IMPL realloc
#endif
/**
A test/debug macro for simulating an OOM after the given number of
bytes have been allocated.
*/
#define CSON_SIMULATE_OOM 0
#if CSON_SIMULATE_OOM
static unsigned int cson_totalAlloced = 0;
#endif
/** Simple proxy for malloc(). descr is a description of the allocation. */
static void * cson_malloc( size_t n, char const * descr )
{
#if CSON_LOG_ALLOC
fprintf(stderr, "Allocating %u bytes [%s].\n", (unsigned int)n, descr);
#endif
#if CSON_SIMULATE_OOM
cson_totalAlloced += n;
if( cson_totalAlloced > CSON_SIMULATE_OOM )
{
return NULL;
}
#endif
return CSON_MALLOC_IMPL(n);
}
/** Simple proxy for free(). descr is a description of the memory being freed. */
static void cson_free( void * p, char const * descr )
{
#if CSON_LOG_ALLOC
fprintf(stderr, "Freeing @%p [%s].\n", p, descr);
#endif
if( !cson_value_is_builtin(p) )
{
CSON_FREE_IMPL( p );
}
}
/** Simple proxy for realloc(). descr is a description of the (re)allocation. */
static void * cson_realloc( void * hint, size_t n, char const * descr )
{
#if CSON_LOG_ALLOC
fprintf(stderr, "%sllocating %u bytes [%s].\n",
hint ? "Rea" : "A",
(unsigned int)n, descr);
#endif
#if CSON_SIMULATE_OOM
cson_totalAlloced += n;
if( cson_totalAlloced > CSON_SIMULATE_OOM )
{
return NULL;
}
#endif
if( 0==n )
{
cson_free(hint, descr);
return NULL;
}
else
{
return CSON_REALLOC_IMPL( hint, n );
}
}
#undef CSON_LOG_ALLOC
#undef CSON_SIMULATE_OOM
/**
CLIENTS CODE SHOULD NEVER USE THIS because it opens up doors to
memory leaks if it is not used in very controlled circumstances.
Users must be very aware of how the underlying memory management
works.
Frees any resources owned by val, but does not free val itself
(which may be stack-allocated). If !val or val->api or
val->api->cleanup are NULL then this is a no-op.
If v is a container type (object or array) its children are also
cleaned up (BUT NOT FREED), recursively.
After calling this, val will have the special "undefined" type.
*/
static void cson_value_clean( cson_value * val );
/**
Increments cv's reference count by 1. As a special case, values
for which cson_value_is_builtin() returns true are not
modified. assert()s if (NULL==cv).
*/
static void cson_refcount_incr( cson_value * cv )
{
assert( NULL != cv );
if( cson_value_is_builtin( cv ) )
{ /* do nothing: we do not want to modify the shared
instances.
*/
return;
}
else
{
++cv->refcount;
}
}
#if 0
int cson_value_refcount_set( cson_value * cv, unsigned short rc )
{
if( NULL == cv ) return cson_rc.ArgError;
else
{
cv->refcount = rc;
return 0;
}
}
#endif
int cson_value_add_reference( cson_value * cv )
{
if( NULL == cv ) return cson_rc.ArgError;
else if( (cv->refcount+1) < cv->refcount )
{
return cson_rc.RangeError;
}
else
{
cson_refcount_incr( cv );
return 0;
}
}
/**
If cv is NULL or cson_value_is_builtin(cv) returns true then this
function does nothing and returns 0, otherwise... If
cv->refcount is 0 or 1 then cson_value_clean(cv) is called, cv is
freed, and 0 is returned. If cv->refcount is any other value then
it is decremented and the new value is returned.
*/
static cson_counter_t cson_refcount_decr( cson_value * cv )
{
if( (NULL == cv) || cson_value_is_builtin(cv) ) return 0;
else if( (0 == cv->refcount) || (0 == --cv->refcount) )
{
cson_value_clean(cv);
cson_free(cv,"cson_value::refcount=0");
return 0;
}
else return cv->refcount;
}
unsigned int cson_string_length_bytes( cson_string const * str )
{
return str ? str->length : 0;
}
/**
Fetches v's string value as a non-const string.
cson_strings are supposed to be immutable, but this form provides
access to the immutable bits, which are v->length bytes long. A
length-0 string is returned as NULL from here, as opposed to
"". (This is a side-effect of the string allocation mechanism.)
Returns NULL if !v.
*/
static char * cson_string_str(cson_string *v)
{
/*
See http://groups.google.com/group/comp.lang.c.moderated/browse_thread/thread/2e0c0df5e8a0cd6a
*/
#if 1
if( !v || (&CSON_EMPTY_HOLDER.stringValue == v) ) return NULL;
else return (char *)((unsigned char *)( v+1 ));
#else
static char empty[2] = {0,0};
return ( NULL == v )
? NULL
: (v->length
? (char *) (((unsigned char *)v) + sizeof(cson_string))
: empty)
;
#endif
}
/**
Fetches v's string value as a const string.
*/
char const * cson_string_cstr(cson_string const *v)
{
/*
See http://groups.google.com/group/comp.lang.c.moderated/browse_thread/thread/2e0c0df5e8a0cd6a
*/
#if 1
if( ! v ) return NULL;
else if( v == &CSON_EMPTY_HOLDER.stringValue ) return "";
else return (char *)((unsigned char *)(v+1));
#else
return (NULL == v)
? NULL
: (v->length
? (char const *) ((unsigned char const *)(v+1))
: "");
#endif
}
#if 0
/**
Just like strndup(3), in that neither are C89/C99-standard and both
are documented in detail in strndup(3).
*/
static char * cson_strdup( char const * src, size_t n )
{
char * rc = (char *)cson_malloc(n+1, "cson_strdup");
if( ! rc ) return NULL;
memset( rc, 0, n+1 );
rc[n] = 0;
return strncpy( rc, src, n );
}
#endif
int cson_string_cmp_cstr_n( cson_string const * str, char const * other, unsigned int otherLen )
{
if( ! other && !str ) return 0;
else if( other && !str ) return 1;
else if( str && !other ) return -1;
else if( !otherLen ) return str->length ? 1 : 0;
else if( !str->length ) return otherLen ? -1 : 0;
else
{
unsigned const int max = (otherLen > str->length) ? otherLen : str->length;
int const rc = strncmp( cson_string_cstr(str), other, max );
return ( (0 == rc) && (otherLen != str->length) )
? (str->length < otherLen) ? -1 : 1
: rc;
}
}
int cson_string_cmp_cstr( cson_string const * lhs, char const * rhs )
{
return cson_string_cmp_cstr_n( lhs, rhs, (rhs&&*rhs) ? strlen(rhs) : 0 );
}
int cson_string_cmp( cson_string const * lhs, cson_string const * rhs )
{
return cson_string_cmp_cstr_n( lhs, cson_string_cstr(rhs), rhs ? rhs->length : 0 );
}
/**
If self is not NULL, *self is overwritten to have the undefined
type. self is not cleaned up or freed.
*/
void cson_value_destroy_zero_it( cson_value * self )
{
if( self )
{
*self = cson_value_undef;
}
}
/**
A key/value pair collection.
Each of these objects owns its key/value pointers, and they
are cleaned up by cson_kvp_clean().
*/
struct cson_kvp
{
cson_value * key;
cson_value * value;
};
#define cson_kvp_empty_m {NULL,NULL}
static const cson_kvp cson_kvp_empty = cson_kvp_empty_m;
/** @def CSON_OBJECT_PROPS_SORT
If CSON_OBJECT_PROPS_SORT is set to a true value then
qsort() and bsearch() are used to sort (upon insertion)
and search cson_object::kvp property lists. This costs us
a re-sort on each insertion but searching is O(log n)
average/worst case (and O(1) best-case).
i'm not yet convinced that the overhead of the qsort() justifies
the potentially decreased search times - it has not been
measured. Object property lists tend to be relatively short in
JSON, and a linear search which uses the cson_string::length
property as a quick check is quite fast when one compares it with
the sort overhead required by the bsearch() approach.
*/
#define CSON_OBJECT_PROPS_SORT 0
/** @def CSON_OBJECT_PROPS_SORT_USE_LENGTH
Don't use this - i'm not sure that it works how i'd like.
If CSON_OBJECT_PROPS_SORT_USE_LENGTH is true then
we use string lengths as quick checks when sorting
property keys. This leads to a non-intuitive sorting
order but "should" be faster.
This is ignored if CSON_OBJECT_PROPS_SORT is false.
*/
#define CSON_OBJECT_PROPS_SORT_USE_LENGTH 0
#if CSON_OBJECT_PROPS_SORT
/**
cson_kvp comparator for use with qsort(). ALMOST compares with
strcmp() semantics, but it uses the strings' lengths as a quicker
approach. This might give non-intuitive results, but it's faster.
*/
static int cson_kvp_cmp( void const * lhs, void const * rhs )
{
cson_kvp const * lk = *((cson_kvp const * const*)lhs);
cson_kvp const * rk = *((cson_kvp const * const*)rhs);
cson_string const * l = cson_string_value(lk->key);
cson_string const * r = cson_string_value(rk->key);
#if CSON_OBJECT_PROPS_SORT_USE_LENGTH
if( l->length < r->length ) return -1;
else if( l->length > r->length ) return 1;
else return strcmp( cson_string_cstr( l ), cson_string_cstr( r ) );
#else
return strcmp( cson_string_cstr( l ),
cson_string_cstr( r ) );
#endif /*CSON_OBJECT_PROPS_SORT_USE_LENGTH*/
}
#endif /*CSON_OBJECT_PROPS_SORT*/
#if CSON_OBJECT_PROPS_SORT
#error "Need to rework this for cson_string-to-cson_value refactoring"
/**
A bsearch() comparison function which requires that lhs be a (char
const *) and rhs be-a (cson_kvp const * const *). It compares lhs
to rhs->key's value, using strcmp() semantics.
*/
static int cson_kvp_cmp_vs_cstr( void const * lhs, void const * rhs )
{
char const * lk = (char const *)lhs;
cson_kvp const * rk =
*((cson_kvp const * const*)rhs)
;
#if CSON_OBJECT_PROPS_SORT_USE_LENGTH
unsigned int llen = strlen(lk);
if( llen < rk->key->length ) return -1;
else if( llen > rk->key->length ) return 1;
else return strcmp( lk, cson_string_cstr( rk->key ) );
#else
return strcmp( lk, cson_string_cstr( rk->key ) );
#endif /*CSON_OBJECT_PROPS_SORT_USE_LENGTH*/
}
#endif /*CSON_OBJECT_PROPS_SORT*/
struct cson_kvp_list
{
cson_kvp ** list;
unsigned int count;
unsigned int alloced;
};
typedef struct cson_kvp_list cson_kvp_list;
#define cson_kvp_list_empty_m {NULL/*list*/,0/*count*/,0/*alloced*/}
static const cson_kvp_list cson_kvp_list_empty = cson_kvp_list_empty_m;
struct cson_object
{
cson_kvp_list kvp;
};
/*typedef struct cson_object cson_object;*/
#define cson_object_empty_m { cson_kvp_list_empty_m/*kvp*/ }
static const cson_object cson_object_empty = cson_object_empty_m;
struct cson_value_list
{
cson_value ** list;
unsigned int count;
unsigned int alloced;
};
typedef struct cson_value_list cson_value_list;
#define cson_value_list_empty_m {NULL/*list*/,0/*count*/,0/*alloced*/}
static const cson_value_list cson_value_list_empty = cson_value_list_empty_m;
struct cson_array
{
cson_value_list list;
};
/*typedef struct cson_array cson_array;*/
#define cson_array_empty_m { cson_value_list_empty_m/*list*/ }
static const cson_array cson_array_empty = cson_array_empty_m;
struct cson_parser
{
JSON_parser p;
cson_value * root;
cson_value * node;
cson_array stack;
cson_string * ckey;
int errNo;
unsigned int totalKeyCount;
unsigned int totalValueCount;
};
typedef struct cson_parser cson_parser;
static const cson_parser cson_parser_empty = {
NULL/*p*/,
NULL/*root*/,
NULL/*node*/,
cson_array_empty_m/*stack*/,
NULL/*ckey*/,
0/*errNo*/,
0/*totalKeyCount*/,
0/*totalValueCount*/
};
#if 1
/* The following funcs are declared in generated code (cson_lists.h),
but we need early access to their decls for the Amalgamation build.
*/
static unsigned int cson_value_list_reserve( cson_value_list * self, unsigned int n );
static unsigned int cson_kvp_list_reserve( cson_kvp_list * self, unsigned int n );
static int cson_kvp_list_append( cson_kvp_list * self, cson_kvp * cp );
static void cson_kvp_list_clean( cson_kvp_list * self,
void (*cleaner)(cson_kvp * obj) );
#if 0
static int cson_value_list_append( cson_value_list * self, cson_value * cp );
static void cson_value_list_clean( cson_value_list * self, void (*cleaner)(cson_value * obj));
static int cson_kvp_list_visit( cson_kvp_list * self,
int (*visitor)(cson_kvp * obj, void * visitorState ),
void * visitorState );
static int cson_value_list_visit( cson_value_list * self,
int (*visitor)(cson_value * obj, void * visitorState ),
void * visitorState );
#endif
#endif
#if 0
# define LIST_T cson_value_list
# define VALUE_T cson_value *
# define VALUE_T_IS_PTR 1
# define LIST_T cson_kvp_list
# define VALUE_T cson_kvp *
# define VALUE_T_IS_PTR 1
#else
#endif
/**
Allocates a new value of the specified type ownership of it to the
caller. It must eventually be destroyed, by the caller or its
owning container, by passing it to cson_value_free() or transfering
ownership to a container.
extra is only valid for type CSON_TYPE_STRING, and must be the length
of the string to allocate + 1 byte (for the NUL).
The returned value->api member will be set appropriately and
val->value will be set to point to the memory allocated to hold the
native value type. Use the internal CSON_CAST() family of macros to
convert them.
Returns NULL on allocation error.
@see cson_value_new_array()
@see cson_value_new_object()
@see cson_value_new_string()
@see cson_value_new_integer()
@see cson_value_new_double()
@see cson_value_new_bool()
@see cson_value_free()
*/
static cson_value * cson_value_new(cson_type_id t, size_t extra);
cson_value * cson_value_new(cson_type_id t, size_t extra)
{
static const size_t vsz = sizeof(cson_value);
const size_t sz = vsz + extra;
size_t tx = 0;
cson_value def = cson_value_undef;
cson_value * v = NULL;
char const * reason = "cson_value_new";
switch(t)
{
case CSON_TYPE_ARRAY:
assert( 0 == extra );
def = cson_value_array_empty;
tx = sizeof(cson_array);
reason = "cson_value:array";
break;
case CSON_TYPE_DOUBLE:
assert( 0 == extra );
def = cson_value_double_empty;
tx = sizeof(cson_double_t);
reason = "cson_value:double";
break;
case CSON_TYPE_INTEGER:
assert( 0 == extra );
def = cson_value_integer_empty;
tx = sizeof(cson_int_t);
reason = "cson_value:int";
break;
case CSON_TYPE_STRING:
assert( 0 != extra );
def = cson_value_string_empty;
tx = sizeof(cson_string);
reason = "cson_value:string";
break;
case CSON_TYPE_OBJECT:
assert( 0 == extra );
def = cson_value_object_empty;
tx = sizeof(cson_object);
reason = "cson_value:object";
break;
default:
assert(0 && "Unhandled type in cson_value_new()!");
return NULL;
}
assert( def.api->typeID != CSON_TYPE_UNDEF );
v = (cson_value *)cson_malloc(sz+tx, reason);
if( v ) {
*v = def;
if(tx || extra){
memset(v+1, 0, tx + extra);
v->value = (void *)(v+1);
}
}
return v;
}
void cson_value_free(cson_value *v)
{
cson_refcount_decr( v );
}
#if 0 /* we might actually want this later on. */
/** Returns true if v is not NULL and has the given type ID. */
static char cson_value_is_a( cson_value const * v, cson_type_id is )
{
return (v && v->api && (v->api->typeID == is)) ? 1 : 0;
}
#endif
#if 0
cson_type_id cson_value_type_id( cson_value const * v )
{
return (v && v->api) ? v->api->typeID : CSON_TYPE_UNDEF;
}
#endif
char cson_value_is_undef( cson_value const * v )
{
/**
This special-case impl is needed because the underlying
(generic) list operations do not know how to populate
new entries
*/
return ( !v || !v->api || (v->api==&cson_value_api_undef))
? 1 : 0;
}
#define ISA(T,TID) char cson_value_is_##T( cson_value const * v ) { \
/*return (v && v->api) ? cson_value_is_a(v,CSON_TYPE_##TID) : 0;*/ \
return (v && (v->api == &cson_value_api_##T)) ? 1 : 0; \
} static const char bogusPlaceHolderForEmacsIndention##TID = CSON_TYPE_##TID
ISA(null,NULL);
ISA(bool,BOOL);
ISA(integer,INTEGER);
ISA(double,DOUBLE);
ISA(string,STRING);
ISA(array,ARRAY);
ISA(object,OBJECT);
#undef ISA
char cson_value_is_number( cson_value const * v )
{
return cson_value_is_integer(v) || cson_value_is_double(v);
}
void cson_value_clean( cson_value * val )
{
if( val && val->api && val->api->cleanup )
{
if( ! cson_value_is_builtin( val ) )
{
cson_counter_t const rc = val->refcount;
val->api->cleanup(val);
*val = cson_value_undef;
val->refcount = rc;
}
}
}
static cson_value * cson_value_array_alloc()
{
cson_value * v = cson_value_new(CSON_TYPE_ARRAY,0);
if( NULL != v )
{
cson_array * ar = CSON_ARRAY(v);
assert(NULL != ar);
*ar = cson_array_empty;
}
return v;
}
static cson_value * cson_value_object_alloc()
{
cson_value * v = cson_value_new(CSON_TYPE_OBJECT,0);
if( NULL != v )
{
cson_object * obj = CSON_OBJ(v);
assert(NULL != obj);
*obj = cson_object_empty;
}
return v;
}
cson_value * cson_value_new_object()
{
return cson_value_object_alloc();
}
cson_object * cson_new_object()
{
return cson_value_get_object( cson_value_new_object() );
}
cson_value * cson_value_new_array()
{
return cson_value_array_alloc();
}
cson_array * cson_new_array()
{
return cson_value_get_array( cson_value_new_array() );
}
/**
Frees kvp->key and kvp->value and sets them to NULL, but does not free
kvp. If !kvp then this is a no-op.
*/
static void cson_kvp_clean( cson_kvp * kvp )
{
if( kvp )
{
if(kvp->key)
{
cson_value_free(kvp->key);
kvp->key = NULL;
}
if(kvp->value)
{
cson_value_free( kvp->value );
kvp->value = NULL;
}
}
}
cson_string * cson_kvp_key( cson_kvp const * kvp )
{
return kvp ? cson_value_get_string(kvp->key) : NULL;
}
cson_value * cson_kvp_value( cson_kvp const * kvp )
{
return kvp ? kvp->value : NULL;
}
/**
Calls cson_kvp_clean(kvp) and then frees kvp.
*/
static void cson_kvp_free( cson_kvp * kvp )
{
if( kvp )
{
cson_kvp_clean(kvp);
cson_free(kvp,"cson_kvp");
}
}
/**
cson_value_api::destroy_value() impl for Object
values. Cleans up self-owned memory and overwrites
self to have the undefined value, but does not
free self.
*/
static void cson_value_destroy_object( cson_value * self )
{
if(self && self->value) {
cson_object * obj = (cson_object *)self->value;
assert( self->value == obj );
cson_kvp_list_clean( &obj->kvp, cson_kvp_free );
*self = cson_value_undef;
}
}
/**
Cleans up the contents of ar->list, but does not free ar.
After calling this, ar will have a length of 0.
If properlyCleanValues is 1 then cson_value_free() is called on
each non-NULL item, otherwise the outer list is destroyed but the
individual items are assumed to be owned by someone else and are
not freed.
*/
static void cson_array_clean( cson_array * ar, char properlyCleanValues )
{
if( ar )
{
unsigned int i = 0;
cson_value * val = NULL;
for( ; i < ar->list.count; ++i )
{
val = ar->list.list[i];
if(val)
{
ar->list.list[i] = NULL;
if( properlyCleanValues )
{
cson_value_free( val );
}
}
}
cson_value_list_reserve(&ar->list,0);
ar->list = cson_value_list_empty
/* Pedantic note: reserve(0) already clears the list-specific
fields, but we do this just in case we ever add new fields
to cson_value_list which are not used in the reserve() impl.
*/
;
}
}
/**
cson_value_api::destroy_value() impl for Array
values. Cleans up self-owned memory and overwrites
self to have the undefined value, but does not
free self.
*/
static void cson_value_destroy_array( cson_value * self )
{
cson_array * ar = cson_value_get_array(self);
if(ar) {
assert( self->value == ar );
cson_array_clean( ar, 1 );
*self = cson_value_undef;
}
}
int cson_buffer_fill_from( cson_buffer * dest, cson_data_source_f src, void * state )
{
int rc;
enum { BufSize = 1024 * 4 };
char rbuf[BufSize];
size_t total = 0;
unsigned int rlen = 0;
if( ! dest || ! src ) return cson_rc.ArgError;
dest->used = 0;
while(1)
{
rlen = BufSize;
rc = src( state, rbuf, &rlen );
if( rc ) break;
total += rlen;
if( dest->capacity < (total+1) )
{
rc = cson_buffer_reserve( dest, total + 1);
if( 0 != rc ) break;
}
memcpy( dest->mem + dest->used, rbuf, rlen );
dest->used += rlen;
if( rlen < BufSize ) break;
}
if( !rc && dest->used )
{
assert( dest->used < dest->capacity );
dest->mem[dest->used] = 0;
}
return rc;
}
int cson_data_source_FILE( void * state, void * dest, unsigned int * n )
{
FILE * f = (FILE*) state;
if( ! state || ! n || !dest ) return cson_rc.ArgError;
else if( !*n ) return cson_rc.RangeError;
*n = (unsigned int)fread( dest, 1, *n, f );
if( !*n )
{
return feof(f) ? 0 : cson_rc.IOError;
}
return 0;
}
int cson_parse_FILE( cson_value ** tgt, FILE * src,
cson_parse_opt const * opt, cson_parse_info * err )
{
return cson_parse( tgt, cson_data_source_FILE, src, opt, err );
}
int cson_value_fetch_bool( cson_value const * val, char * v )
{
/**
FIXME: move the to-bool operation into cson_value_api, like we
do in the C++ API.
*/
if( ! val || !val->api ) return cson_rc.ArgError;
else
{
int rc = 0;
char b = 0;
switch( val->api->typeID )
{
case CSON_TYPE_ARRAY:
case CSON_TYPE_OBJECT:
b = 1;
break;
case CSON_TYPE_STRING: {
char const * str = cson_string_cstr(cson_value_get_string(val));
b = (str && *str) ? 1 : 0;
break;
}
case CSON_TYPE_UNDEF:
case CSON_TYPE_NULL:
break;
case CSON_TYPE_BOOL:
b = (NULL==val->value) ? 0 : 1;
break;
case CSON_TYPE_INTEGER: {
cson_int_t i = 0;
cson_value_fetch_integer( val, &i );
b = i ? 1 : 0;
break;
}
case CSON_TYPE_DOUBLE: {
cson_double_t d = 0.0;
cson_value_fetch_double( val, &d );
b = (0.0==d) ? 0 : 1;
break;
}
default:
rc = cson_rc.TypeError;
break;
}
if( v ) *v = b;
return rc;
}
}
char cson_value_get_bool( cson_value const * val )
{
char i = 0;
cson_value_fetch_bool( val, &i );
return i;
}
int cson_value_fetch_integer( cson_value const * val, cson_int_t * v )
{
if( ! val || !val->api ) return cson_rc.ArgError;
else
{
cson_int_t i = 0;
int rc = 0;
switch(val->api->typeID)
{
case CSON_TYPE_UNDEF:
case CSON_TYPE_NULL:
i = 0;
break;
case CSON_TYPE_BOOL: {
char b = 0;
cson_value_fetch_bool( val, &b );
i = b;
break;
}
case CSON_TYPE_INTEGER: {
cson_int_t const * x = CSON_INT(val);
if(!x)
{
assert( val == &CSON_SPECIAL_VALUES[CSON_VAL_INT_0] );
}
i = x ? *x : 0;
break;
}
case CSON_TYPE_DOUBLE: {
cson_double_t d = 0.0;
cson_value_fetch_double( val, &d );
i = (cson_int_t)d;
break;
}
case CSON_TYPE_STRING:
case CSON_TYPE_ARRAY:
case CSON_TYPE_OBJECT:
default:
break;
}
if(v) *v = i;
return rc;
}
}
cson_int_t cson_value_get_integer( cson_value const * val )
{
cson_int_t i = 0;
cson_value_fetch_integer( val, &i );
return i;
}
int cson_value_fetch_double( cson_value const * val, cson_double_t * v )
{
if( ! val || !val->api ) return cson_rc.ArgError;
else
{
cson_double_t d = 0.0;
int rc = 0;
switch(val->api->typeID)
{
case CSON_TYPE_UNDEF:
case CSON_TYPE_NULL:
d = 0;
break;
case CSON_TYPE_BOOL: {
char b = 0;
cson_value_fetch_bool( val, &b );
d = b ? 1.0 : 0.0;
break;
}
case CSON_TYPE_INTEGER: {
cson_int_t i = 0;
cson_value_fetch_integer( val, &i );
d = i;
break;
}
case CSON_TYPE_DOUBLE: {
cson_double_t const* dv = CSON_DBL(val);
d = dv ? *dv : 0.0;
break;
}
default:
rc = cson_rc.TypeError;
break;
}
if(v) *v = d;
return rc;
}
}
cson_double_t cson_value_get_double( cson_value const * val )
{
cson_double_t i = 0.0;
cson_value_fetch_double( val, &i );
return i;
}
int cson_value_fetch_string( cson_value const * val, cson_string ** dest )
{
if( ! val || ! dest ) return cson_rc.ArgError;
else if( ! cson_value_is_string(val) ) return cson_rc.TypeError;
else
{
if( dest ) *dest = CSON_STR(val);
return 0;
}
}
cson_string * cson_value_get_string( cson_value const * val )
{
cson_string * rc = NULL;
cson_value_fetch_string( val, &rc );
return rc;
}
char const * cson_value_get_cstr( cson_value const * val )
{
return cson_string_cstr( cson_value_get_string(val) );
}
int cson_value_fetch_object( cson_value const * val, cson_object ** obj )
{
if( ! val ) return cson_rc.ArgError;
else if( ! cson_value_is_object(val) ) return cson_rc.TypeError;
else
{
if(obj) *obj = CSON_OBJ(val);
return 0;
}
}
cson_object * cson_value_get_object( cson_value const * v )
{
cson_object * obj = NULL;
cson_value_fetch_object( v, &obj );
return obj;
}
int cson_value_fetch_array( cson_value const * val, cson_array ** ar)
{
if( ! val ) return cson_rc.ArgError;
else if( !cson_value_is_array(val) ) return cson_rc.TypeError;
else
{
if(ar) *ar = CSON_ARRAY(val);
return 0;
}
}
cson_array * cson_value_get_array( cson_value const * v )
{
cson_array * ar = NULL;
cson_value_fetch_array( v, &ar );
return ar;
}
cson_kvp * cson_kvp_alloc()
{
cson_kvp * kvp = (cson_kvp*)cson_malloc(sizeof(cson_kvp),"cson_kvp");
if( kvp )
{
*kvp = cson_kvp_empty;
}
return kvp;
}
int cson_array_append( cson_array * ar, cson_value * v )
{
if( !ar || !v ) return cson_rc.ArgError;
else if( (ar->list.count+1) < ar->list.count ) return cson_rc.RangeError;
else
{
if( !ar->list.alloced || (ar->list.count == ar->list.alloced-1))
{
unsigned int const n = ar->list.count ? (ar->list.count*2) : 7;
if( n > cson_value_list_reserve( &ar->list, n ) )
{
return cson_rc.AllocError;
}
}
return cson_array_set( ar, ar->list.count, v );
}
}
#if 0
/**
Removes and returns the last value from the given array,
shrinking its size by 1. Returns NULL if ar is NULL,
ar->list.count is 0, or the element at that index is NULL.
If removeRef is true then cson_value_free() is called to remove
ar's reference count for the value. In that case NULL is returned,
even if the object still has live references. If removeRef is false
then the caller takes over ownership of that reference count point.
If removeRef is false then the caller takes over ownership
of the return value, otherwise ownership is effectively
determined by any remaining references for the returned
value.
*/
static cson_value * cson_array_pop_back( cson_array * ar,
char removeRef )
{
if( !ar ) return NULL;
else if( ! ar->list.count ) return NULL;
else
{
unsigned int const ndx = --ar->list.count;
cson_value * v = ar->list.list[ndx];
ar->list.list[ndx] = NULL;
if( removeRef )
{
cson_value_free( v );
v = NULL;
}
return v;
}
}
#endif
cson_value * cson_value_new_bool( char v )
{
return v ? &CSON_SPECIAL_VALUES[CSON_VAL_TRUE] : &CSON_SPECIAL_VALUES[CSON_VAL_FALSE];
}
cson_value * cson_value_true()
{
return &CSON_SPECIAL_VALUES[CSON_VAL_TRUE];
}
cson_value * cson_value_false()
{
return &CSON_SPECIAL_VALUES[CSON_VAL_FALSE];
}
cson_value * cson_value_null()
{
return &CSON_SPECIAL_VALUES[CSON_VAL_NULL];
}
cson_value * cson_new_int( cson_int_t v )
{
return cson_value_new_integer(v);
}
cson_value * cson_value_new_integer( cson_int_t v )
{
if( 0 == v ) return &CSON_SPECIAL_VALUES[CSON_VAL_INT_0];
else
{
cson_value * c = cson_value_new(CSON_TYPE_INTEGER,0);
if( c )
{
*CSON_INT(c) = v;
}
return c;
}
}
cson_value * cson_new_double( cson_double_t v )
{
return cson_value_new_double(v);
}
cson_value * cson_value_new_double( cson_double_t v )
{
if( 0.0 == v ) return &CSON_SPECIAL_VALUES[CSON_VAL_DBL_0];
else
{
cson_value * c = cson_value_new(CSON_TYPE_DOUBLE,0);
if( c )
{
*CSON_DBL(c) = v;
}
return c;
}
}
cson_string * cson_new_string(char const * str, unsigned int len)
{
if( !str || !*str || !len ) return &CSON_EMPTY_HOLDER.stringValue;
else
{
cson_value * c = cson_value_new(CSON_TYPE_STRING, len + 1/*NUL byte*/);
cson_string * s = NULL;
if( c )
{
char * dest = NULL;
s = CSON_STR(c);
*s = cson_string_empty;
assert( NULL != s );
s->length = len;
dest = cson_string_str(s);
assert( NULL != dest );
memcpy( dest, str, len );
dest[len] = 0;
}
return s;
}
}
cson_value * cson_value_new_string( char const * str, unsigned int len )
{
return cson_string_value( cson_new_string(str, len) );
}
int cson_array_value_fetch( cson_array const * ar, unsigned int pos, cson_value ** v )
{
if( !ar) return cson_rc.ArgError;
if( pos >= ar->list.count ) return cson_rc.RangeError;
else
{
if(v) *v = ar->list.list[pos];
return 0;
}
}
cson_value * cson_array_get( cson_array const * ar, unsigned int pos )
{
cson_value *v = NULL;
cson_array_value_fetch(ar, pos, &v);
return v;
}
int cson_array_length_fetch( cson_array const * ar, unsigned int * v )
{
if( ! ar || !v ) return cson_rc.ArgError;
else
{
if(v) *v = ar->list.count;
return 0;
}
}
unsigned int cson_array_length_get( cson_array const * ar )
{
unsigned int i = 0;
cson_array_length_fetch(ar, &i);
return i;
}
int cson_array_reserve( cson_array * ar, unsigned int size )
{
if( ! ar ) return cson_rc.ArgError;
else if( size <= ar->list.alloced )
{
/* We don't want to introduce a can of worms by trying to
handle the cleanup from here.
*/
return 0;
}
else
{
return (ar->list.alloced > cson_value_list_reserve( &ar->list, size ))
? cson_rc.AllocError
: 0
;
}
}
int cson_array_set( cson_array * ar, unsigned int ndx, cson_value * v )
{
if( !ar || !v ) return cson_rc.ArgError;
else if( (ndx+1) < ndx) /* overflow */return cson_rc.RangeError;
else
{
unsigned const int len = cson_value_list_reserve( &ar->list, ndx+1 );
if( len <= ndx ) return cson_rc.AllocError;
else
{
cson_value * old = ar->list.list[ndx];
if( old )
{
if(old == v) return 0;
else cson_value_free(old);
}
cson_refcount_incr( v );
ar->list.list[ndx] = v;
if( ndx >= ar->list.count )
{
ar->list.count = ndx+1;
}
return 0;
}
}
}
/** @internal
Searchs for the given key in the given object.
Returns the found item on success, NULL on error. If ndx is not
NULL, it is set to the index (in obj->kvp.list) of the found
item. *ndx is not modified if no entry is found.
*/
static cson_kvp * cson_object_search_impl( cson_object const * obj, char const * key, unsigned int * ndx )
{
if( obj && key && *key && obj->kvp.count)
{
#if CSON_OBJECT_PROPS_SORT
cson_kvp ** s = (cson_kvp**)
bsearch( key, obj->kvp.list,
obj->kvp.count, sizeof(cson_kvp*),
cson_kvp_cmp_vs_cstr );
if( ndx && s )
{ /* index of found record is required by
cson_object_unset(). Calculate the offset based on s...*/
#if 0
*ndx = (((unsigned char const *)s - ((unsigned char const *)obj->kvp.list))
/ sizeof(cson_kvp*));
#else
*ndx = s - obj->kvp.list;
#endif
}
return s ? *s : NULL;
#else
cson_kvp_list const * li = &obj->kvp;
unsigned int i = 0;
cson_kvp * kvp;
const unsigned int klen = strlen(key);
for( ; i < li->count; ++i )
{
cson_string const * sKey;
kvp = li->list[i];
assert( kvp && kvp->key );
sKey = cson_value_get_string(kvp->key);
assert(sKey);
if( sKey->length != klen ) continue;
else if(0==strcmp(key,cson_string_cstr(sKey)))
{
if(ndx) *ndx = i;
return kvp;
}
}
#endif
}
return NULL;
}
cson_value * cson_object_get( cson_object const * obj, char const * key )
{
cson_kvp * kvp = cson_object_search_impl( obj, key, NULL );
return kvp ? kvp->value : NULL;
}
cson_value * cson_object_get_s( cson_object const * obj, cson_string const *key )
{
cson_kvp * kvp = cson_object_search_impl( obj, cson_string_cstr(key), NULL );
return kvp ? kvp->value : NULL;
}
#if CSON_OBJECT_PROPS_SORT
static void cson_object_sort_props( cson_object * obj )
{
assert( NULL != obj );
if( obj->kvp.count )
{
qsort( obj->kvp.list, obj->kvp.count, sizeof(cson_kvp*),
cson_kvp_cmp );
}
}
#endif
int cson_object_unset( cson_object * obj, char const * key )
{
if( ! obj || !key || !*key ) return cson_rc.ArgError;
else
{
unsigned int ndx = 0;
cson_kvp * kvp = cson_object_search_impl( obj, key, &ndx );
if( ! kvp )
{
return cson_rc.NotFoundError;
}
assert( obj->kvp.count > 0 );
assert( obj->kvp.list[ndx] == kvp );
cson_kvp_free( kvp );
obj->kvp.list[ndx] = NULL;
{ /* if my brain were bigger i'd use memmove(). */
unsigned int i = ndx;
for( ; i < obj->kvp.count; ++i )
{
obj->kvp.list[i] =
(i < (obj->kvp.alloced-1))
? obj->kvp.list[i+1]
: NULL;
}
}
obj->kvp.list[--obj->kvp.count] = NULL;
#if CSON_OBJECT_PROPS_SORT
cson_object_sort_props( obj );
#endif
return 0;
}
}
int cson_object_set_s( cson_object * obj, cson_string * key, cson_value * v )
{
if( !obj || !key ) return cson_rc.ArgError;
else if( NULL == v ) return cson_object_unset( obj, cson_string_cstr(key) );
else
{
char const * cKey;
cson_value * vKey;
cson_kvp * kvp;
vKey = cson_string_value(key);
assert(vKey && (key==CSON_STR(vKey)));
if( vKey == CSON_VCAST(obj) ){
return cson_rc.ArgError;
}
cKey = cson_string_cstr(key);
kvp = cson_object_search_impl( obj, cKey, NULL );
if( kvp )
{ /* "I told 'em we've already got one!" */
if( kvp->key != vKey ){
cson_value_free( kvp->key );
cson_refcount_incr(vKey);
kvp->key = vKey;
}
if(kvp->value != v){
cson_value_free( kvp->value );
cson_refcount_incr( v );
kvp->value = v;
}
return 0;
}
if( !obj->kvp.alloced || (obj->kvp.count == obj->kvp.alloced-1))
{
unsigned int const n = obj->kvp.count ? (obj->kvp.count*2) : 6;
if( n > cson_kvp_list_reserve( &obj->kvp, n ) )
{
return cson_rc.AllocError;
}
}
{ /* insert new item... */
int rc = 0;
kvp = cson_kvp_alloc();
if( ! kvp )
{
return cson_rc.AllocError;
}
rc = cson_kvp_list_append( &obj->kvp, kvp );
if( 0 != rc )
{
cson_kvp_free(kvp);
}
else
{
cson_refcount_incr(vKey);
cson_refcount_incr(v);
kvp->key = vKey;
kvp->value = v;
#if CSON_OBJECT_PROPS_SORT
cson_object_sort_props( obj );
#endif
}
return rc;
}
}
}
int cson_object_set( cson_object * obj, char const * key, cson_value * v )
{
if( ! obj || !key || !*key ) return cson_rc.ArgError;
else if( NULL == v )
{
return cson_object_unset( obj, key );
}
else
{
cson_string * cs = cson_new_string(key,strlen(key));
if(!cs) return cson_rc.AllocError;
else
{
int const rc = cson_object_set_s(obj, cs, v);
if(rc) cson_value_free(cson_string_value(cs));
return rc;
}
}
}
cson_value * cson_object_take( cson_object * obj, char const * key )
{
if( ! obj || !key || !*key ) return NULL;
else
{
/* FIXME: this is 90% identical to cson_object_unset(),
only with different refcount handling.
Consolidate them.
*/
unsigned int ndx = 0;
cson_kvp * kvp = cson_object_search_impl( obj, key, &ndx );
cson_value * rc = NULL;
if( ! kvp )
{
return NULL;
}
assert( obj->kvp.count > 0 );
assert( obj->kvp.list[ndx] == kvp );
rc = kvp->value;
assert( rc );
kvp->value = NULL;
cson_kvp_free( kvp );
assert( rc->refcount > 0 );
--rc->refcount;
obj->kvp.list[ndx] = NULL;
{ /* if my brain were bigger i'd use memmove(). */
unsigned int i = ndx;
for( ; i < obj->kvp.count; ++i )
{
obj->kvp.list[i] =
(i < (obj->kvp.alloced-1))
? obj->kvp.list[i+1]
: NULL;
}
}
obj->kvp.list[--obj->kvp.count] = NULL;
#if CSON_OBJECT_PROPS_SORT
cson_object_sort_props( obj );
#endif
return rc;
}
}
/** @internal
If p->node is-a Object then value is inserted into the object
using p->key. In any other case cson_rc.InternalError is returned.
Returns cson_rc.AllocError if an allocation fails.
Returns 0 on success. On error, parsing must be ceased immediately.
Ownership of val is ALWAYS TRANSFERED to this function. If this
function fails, val will be cleaned up and destroyed. (This
simplifies error handling in the core parser.)
*/
static int cson_parser_set_key( cson_parser * p, cson_value * val )
{
assert( p && val );
if( p->ckey && cson_value_is_object(p->node) )
{
int rc;
cson_object * obj = cson_value_get_object(p->node);
cson_kvp * kvp = NULL;
assert( obj && (p->node->value == obj) );
/**
FIXME? Use cson_object_set() instead of our custom
finagling with the object? We do it this way to avoid an
extra alloc/strcpy of the key data.
*/
if( !obj->kvp.alloced || (obj->kvp.count == obj->kvp.alloced-1))
{
if( obj->kvp.alloced > cson_kvp_list_reserve( &obj->kvp, obj->kvp.count ? (obj->kvp.count*2) : 5 ) )
{
cson_value_free(val);
return cson_rc.AllocError;
}
}
kvp = cson_kvp_alloc();
if( ! kvp )
{
cson_value_free(val);
return cson_rc.AllocError;
}
kvp->key = cson_string_value(p->ckey)/*transfer ownership*/;
p->ckey = NULL;
kvp->value = val;
cson_refcount_incr( val );
rc = cson_kvp_list_append( &obj->kvp, kvp );
if( 0 != rc )
{
cson_kvp_free( kvp );
}
else
{
++p->totalValueCount;
}
return rc;
}
else
{
if(val) cson_value_free(val);
return p->errNo = cson_rc.InternalError;
}
}
/** @internal
Pushes val into the current object/array parent node, depending on the
internal state of the parser.
Ownership of val is always transfered to this function, regardless of
success or failure.
Returns 0 on success. On error, parsing must be ceased immediately.
*/
static int cson_parser_push_value( cson_parser * p, cson_value * val )
{
if( p->ckey )
{ /* we're in Object mode */
assert( cson_value_is_object( p->node ) );
return cson_parser_set_key( p, val );
}
else if( cson_value_is_array( p->node ) )
{ /* we're in Array mode */
cson_array * ar = cson_value_get_array( p->node );
int rc;
assert( ar && (ar == p->node->value) );
rc = cson_array_append( ar, val );
if( 0 != rc )
{
cson_value_free(val);
}
else
{
++p->totalValueCount;
}
return rc;
}
else
{ /* WTF? */
assert( 0 && "Internal error in cson_parser code" );
return p->errNo = cson_rc.InternalError;
}
}
/**
Callback for JSON_parser API. Reminder: it returns 0 (meaning false)
on error!
*/
static int cson_parse_callback( void * cx, int type, JSON_value const * value )
{
cson_parser * p = (cson_parser *)cx;
int rc = 0;
#define ALLOC_V(T,V) cson_value * v = cson_value_new_##T(V); if( ! v ) { rc = cson_rc.AllocError; break; }
switch(type) {
case JSON_T_ARRAY_BEGIN:
case JSON_T_OBJECT_BEGIN: {
cson_value * obja = (JSON_T_ARRAY_BEGIN == type)
? cson_value_new_array()
: cson_value_new_object();
if( ! obja )
{
p->errNo = cson_rc.AllocError;
return 0;
}
if( 0 != rc ) break;
if( ! p->root )
{
p->root = p->node = obja;
rc = cson_array_append( &p->stack, obja );
if( 0 != rc )
{ /* work around a (potential) corner case in the cleanup code. */
cson_value_free( p->root );
p->root = NULL;
}
else
{
cson_refcount_incr( p->root )
/* simplifies cleanup later on. */
;
++p->totalValueCount;
}
}
else
{
rc = cson_array_append( &p->stack, obja );
if( 0 == rc ) rc = cson_parser_push_value( p, obja );
if( 0 == rc ) p->node = obja;
}
break;
}
case JSON_T_ARRAY_END:
case JSON_T_OBJECT_END: {
if( 0 == p->stack.list.count )
{
rc = cson_rc.RangeError;
break;
}
#if CSON_OBJECT_PROPS_SORT
if( cson_value_is_object(p->node) )
{/* kludge: the parser uses custom cson_object property
insertion as a malloc/strcpy-reduction optimization.
Because of that, we have to sort the property list
ourselves...
*/
cson_object * obj = cson_value_get_object(p->node);
assert( NULL != obj );
cson_object_sort_props( obj );
}
#endif
#if 1
/* Reminder: do not use cson_array_pop_back( &p->stack )
because that will clean up the object, and we don't want
that. We just want to forget this reference
to it. The object is either the root or was pushed into
an object/array in the parse tree (and is owned by that
object/array).
*/
--p->stack.list.count;
assert( p->node == p->stack.list.list[p->stack.list.count] );
cson_refcount_decr( p->node )
/* p->node might be owned by an outer object but we
need to remove the list's reference. For the
root node we manually add a reference to
avoid a special case here. Thus when we close
the root node, its refcount is still 1.
*/;
p->stack.list.list[p->stack.list.count] = NULL;
if( p->stack.list.count )
{
p->node = p->stack.list.list[p->stack.list.count-1];
}
else
{
p->node = p->root;
}
#else
/*
Causing a leak?
*/
cson_array_pop_back( &p->stack, 1 );
if( p->stack.list.count )
{
p->node = p->stack.list.list[p->stack.list.count-1];
}
else
{
p->node = p->root;
}
assert( p->node && (1==p->node->refcount) );
#endif
break;
}
case JSON_T_INTEGER: {
ALLOC_V(integer, value->vu.integer_value );
rc = cson_parser_push_value( p, v );
break;
}
case JSON_T_FLOAT: {
ALLOC_V(double, value->vu.float_value );
rc = cson_parser_push_value( p, v );
break;
}
case JSON_T_NULL: {
rc = cson_parser_push_value( p, cson_value_null() );
break;
}
case JSON_T_TRUE: {
rc = cson_parser_push_value( p, cson_value_true() );
break;
}
case JSON_T_FALSE: {
rc = cson_parser_push_value( p, cson_value_false() );
break;
}
case JSON_T_KEY: {
assert(!p->ckey);
p->ckey = cson_new_string( value->vu.str.value, value->vu.str.length );
if( ! p->ckey )
{
rc = cson_rc.AllocError;
break;
}
++p->totalKeyCount;
break;
}
case JSON_T_STRING: {
cson_value * v = cson_value_new_string( value->vu.str.value, value->vu.str.length );
rc = ( NULL == v )
? cson_rc.AllocError
: cson_parser_push_value( p, v );
break;
}
default:
assert(0);
rc = cson_rc.InternalError;
break;
}
#undef ALLOC_V
return ((p->errNo = rc)) ? 0 : 1;
}
/**
Converts a JSON_error code to one of the cson_rc values.
*/
static int cson_json_err_to_rc( JSON_error jrc )
{
switch(jrc)
{
case JSON_E_NONE: return 0;
case JSON_E_INVALID_CHAR: return cson_rc.Parse_INVALID_CHAR;
case JSON_E_INVALID_KEYWORD: return cson_rc.Parse_INVALID_KEYWORD;
case JSON_E_INVALID_ESCAPE_SEQUENCE: return cson_rc.Parse_INVALID_ESCAPE_SEQUENCE;
case JSON_E_INVALID_UNICODE_SEQUENCE: return cson_rc.Parse_INVALID_UNICODE_SEQUENCE;
case JSON_E_INVALID_NUMBER: return cson_rc.Parse_INVALID_NUMBER;
case JSON_E_NESTING_DEPTH_REACHED: return cson_rc.Parse_NESTING_DEPTH_REACHED;
case JSON_E_UNBALANCED_COLLECTION: return cson_rc.Parse_UNBALANCED_COLLECTION;
case JSON_E_EXPECTED_KEY: return cson_rc.Parse_EXPECTED_KEY;
case JSON_E_EXPECTED_COLON: return cson_rc.Parse_EXPECTED_COLON;
case JSON_E_OUT_OF_MEMORY: return cson_rc.AllocError;
default:
return cson_rc.InternalError;
}
}
/** @internal
Cleans up all contents of p but does not free p.
To properly take over ownership of the parser's root node on a
successful parse:
- Copy p->root's pointer and set p->root to NULL.
- Eventually free up p->root with cson_value_free().
If you do not set p->root to NULL, p->root will be freed along with
any other items inserted into it (or under it) during the parsing
process.
*/
static int cson_parser_clean( cson_parser * p )
{
if( ! p ) return cson_rc.ArgError;
else
{
if( p->p )
{
delete_JSON_parser(p->p);
p->p = NULL;
}
if( p->ckey ){
cson_value_free(cson_string_value(p->ckey));
}
cson_array_clean( &p->stack, 1 );
if( p->root )
{
cson_value_free( p->root );
}
*p = cson_parser_empty;
return 0;
}
}
int cson_parse( cson_value ** tgt, cson_data_source_f src, void * state,
cson_parse_opt const * opt_, cson_parse_info * info_ )
{
unsigned char ch[2] = {0,0};
cson_parse_opt const opt = opt_ ? *opt_ : cson_parse_opt_empty;
int rc = 0;
unsigned int len = 1;
cson_parse_info info = info_ ? *info_ : cson_parse_info_empty;
cson_parser p = cson_parser_empty;
if( ! tgt || ! src ) return cson_rc.ArgError;
{
JSON_config jopt = {0};
init_JSON_config( &jopt );
jopt.allow_comments = opt.allowComments;
jopt.depth = opt.maxDepth;
jopt.callback_ctx = &p;
jopt.handle_floats_manually = 0;
jopt.callback = cson_parse_callback;
p.p = new_JSON_parser(&jopt);
if( ! p.p )
{
return cson_rc.AllocError;
}
}
do
{ /* FIXME: buffer the input in multi-kb chunks. */
len = 1;
ch[0] = 0;
rc = src( state, ch, &len );
if( 0 != rc ) break;
else if( !len /* EOF */ ) break;
++info.length;
if('\n' == ch[0])
{
++info.line;
info.col = 0;
}
if( ! JSON_parser_char(p.p, ch[0]) )
{
rc = cson_json_err_to_rc( JSON_parser_get_last_error(p.p) );
if(0==rc) rc = p.errNo;
if(0==rc) rc = cson_rc.InternalError;
info.errorCode = rc;
break;
}
if( '\n' != ch[0]) ++info.col;
} while(1);
if( info_ )
{
info.totalKeyCount = p.totalKeyCount;
info.totalValueCount = p.totalValueCount;
*info_ = info;
}
if( 0 != rc )
{
cson_parser_clean(&p);
return rc;
}
if( ! JSON_parser_done(p.p) )
{
rc = cson_json_err_to_rc( JSON_parser_get_last_error(p.p) );
cson_parser_clean(&p);
if(0==rc) rc = p.errNo;
if(0==rc) rc = cson_rc.InternalError;
}
else
{
cson_value * root = p.root;
p.root = NULL;
cson_parser_clean(&p);
if( root )
{
assert( (1 == root->refcount) && "Detected memory mismanagement in the parser." );
root->refcount = 0
/* HUGE KLUDGE! Avoids having one too many references
in some client code, leading to a leak. Here we're
accommodating a memory management workaround in the
parser code which manually adds a reference to the
root node to keep it from being cleaned up
prematurely.
*/;
*tgt = root;
}
else
{ /* then can happen on empty input. */
rc = cson_rc.UnknownError;
}
}
return rc;
}
/**
The UTF code was originally taken from sqlite3's public-domain
source code (http://sqlite.org), modified only slightly for use
here. This code generates some "possible data loss" warnings on
MSVC, but if this code is good enough for sqlite3 then it's damned
well good enough for me, so we disable that warning for Windows
builds.
*/
/*
** This lookup table is used to help decode the first byte of
** a multi-byte UTF8 character.
*/
static const unsigned char cson_utfTrans1[] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00
};
/*
** Translate a single UTF-8 character. Return the unicode value.
**
** During translation, assume that the byte that zTerm points
** is a 0x00.
**
** Write a pointer to the next unread byte back into *pzNext.
**
** Notes On Invalid UTF-8:
**
** * This routine never allows a 7-bit character (0x00 through 0x7f) to
** be encoded as a multi-byte character. Any multi-byte character that
** attempts to encode a value between 0x00 and 0x7f is rendered as 0xfffd.
**
** * This routine never allows a UTF16 surrogate value to be encoded.
** If a multi-byte character attempts to encode a value between
** 0xd800 and 0xe000 then it is rendered as 0xfffd.
**
** * Bytes in the range of 0x80 through 0xbf which occur as the first
** byte of a character are interpreted as single-byte characters
** and rendered as themselves even though they are technically
** invalid characters.
**
** * This routine accepts an infinite number of different UTF8 encodings
** for unicode values 0x80 and greater. It do not change over-length
** encodings to 0xfffd as some systems recommend.
*/
#define READ_UTF8(zIn, zTerm, c) \
c = *(zIn++); \
if( c>=0xc0 ){ \
c = cson_utfTrans1[c-0xc0]; \
while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ){ \
c = (c<<6) + (0x3f & *(zIn++)); \
} \
if( c<0x80 \
|| (c&0xFFFFF800)==0xD800 \
|| (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; } \
}
static int cson_utf8Read(
const unsigned char *z, /* First byte of UTF-8 character */
const unsigned char *zTerm, /* Pretend this byte is 0x00 */
const unsigned char **pzNext /* Write first byte past UTF-8 char here */
){
int c;
READ_UTF8(z, zTerm, c);
*pzNext = z;
return c;
}
#undef READ_UTF8
#if defined(_WIN32)
# pragma warning( pop )
#endif
unsigned int cson_string_length_utf8( cson_string const * str )
{
if( ! str ) return 0;
else
{
char unsigned const * pos = (char unsigned const *)cson_string_cstr(str);
char unsigned const * end = pos + str->length;
unsigned int rc = 0;
for( ; (pos < end) && cson_utf8Read(pos, end, &pos);
++rc )
{
};
return rc;
}
}
/**
Escapes the first len bytes of the given string as JSON and sends
it to the given output function (which will be called often - once
for each logical character). The output is also surrounded by
double-quotes.
A NULL str will be escaped as an empty string, though we should
arguably export it as "null" (without quotes). We do this because
in JavaScript (typeof null === "object"), and by outputing null
here we would effectively change the data type from string to
object.
*/
static int cson_str_to_json( char const * str, unsigned int len,
char escapeFwdSlash,
cson_data_dest_f f, void * state )
{
if( NULL == f ) return cson_rc.ArgError;
else if( !str || !*str || (0 == len) )
{ /* special case for 0-length strings. */
return f( state, "\"\"", 2 );
}
else
{
unsigned char const * pos = (unsigned char const *)str;
unsigned char const * end = (unsigned char const *)(str ? (str + len) : NULL);
unsigned char const * next = NULL;
int ch;
unsigned char clen = 0;
char escChar[3] = {'\\',0,0};
enum { UBLen = 8 };
char ubuf[UBLen];
int rc = 0;
rc = f(state, "\"", 1 );
for( ; (pos < end) && (0 == rc); pos += clen )
{
ch = cson_utf8Read(pos, end, &next);
if( 0 == ch ) break;
assert( next > pos );
clen = next - pos;
assert( clen );
if( 1 == clen )
{ /* ASCII */
assert( *pos == ch );
escChar[1] = 0;
switch(ch)
{
case '\t': escChar[1] = 't'; break;
case '\r': escChar[1] = 'r'; break;
case '\n': escChar[1] = 'n'; break;
case '\f': escChar[1] = 'f'; break;
case '\b': escChar[1] = 'b'; break;
case '/':
/*
Regarding escaping of forward-slashes. See the main exchange below...
--------------
From: Douglas Crockford <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 )
{
cson_string const * sKey = cson_value_get_string(kvp->key);
char const * cKey = cson_string_cstr(sKey);
rc = cson_str_to_json(cKey, sKey->length,
fmt->escapeForwardSlashes, f, state);
if( 0 == rc )
{
rc = fmt->addSpaceAfterColon
? f(state, ": ", 2 )
: f(state, ":", 1 )
;
}
if( 0 == rc)
{
rc = ( kvp->value )
? cson_output_impl( kvp->value, f, state, fmt, level )
: cson_output_null( f, state );
}
}
else
{
assert( 0 && "Possible internal error." );
continue /* internal error? */;
}
if( 0 == rc )
{
if(i < (obj->kvp.count-1))
{
rc = f(state, ",", 1);
if( 0 == rc )
{
rc = doIndent
? cson_output_indent( f, state, fmt->indentation, level )
: f( state, " ", 1 );
}
}
}
}
--level;
if( doIndent && (0 == rc) )
{
rc = cson_output_indent( f, state, fmt->indentation, level );
}
return (0 == rc)
? f(state, "}", 1)
: rc;
}
}
int cson_output( cson_value const * src, cson_data_dest_f f,
void * state, cson_output_opt const * fmt )
{
int rc;
if(! fmt ) fmt = &cson_output_opt_empty;
rc = cson_output_impl(src, f, state, fmt, 0 );
if( (0 == rc) && fmt->addNewline )
{
rc = f(state, "\n", 1);
}
return rc;
}
int cson_data_dest_FILE( void * state, void const * src, unsigned int n )
{
if( ! state ) return cson_rc.ArgError;
else if( !src || !n ) return 0;
else
{
return ( 1 == fwrite( src, n, 1, (FILE*) state ) )
? 0
: cson_rc.IOError;
}
}
int cson_output_FILE( cson_value const * src, FILE * dest, cson_output_opt const * fmt )
{
int rc = 0;
if( fmt )
{
rc = cson_output( src, cson_data_dest_FILE, dest, fmt );
}
else
{
/* We normally want a newline on FILE output. */
cson_output_opt opt = cson_output_opt_empty;
opt.addNewline = 1;
rc = cson_output( src, cson_data_dest_FILE, dest, &opt );
}
if( 0 == rc )
{
fflush( dest );
}
return rc;
}
int cson_output_filename( cson_value const * src, char const * dest, cson_output_opt const * fmt )
{
if( !src || !dest ) return cson_rc.ArgError;
else
{
FILE * f = fopen(dest,"wb");
if( !f ) return cson_rc.IOError;
else
{
int const rc = cson_output_FILE( src, f, fmt );
fclose(f);
return rc;
}
}
}
int cson_parse_filename( cson_value ** tgt, char const * src,
cson_parse_opt const * opt, cson_parse_info * err )
{
if( !src || !tgt ) return cson_rc.ArgError;
else
{
FILE * f = fopen(src, "r");
if( !f ) return cson_rc.IOError;
else
{
int const rc = cson_parse_FILE( tgt, f, opt, err );
fclose(f);
return rc;
}
}
}
/** Internal type to hold state for a JSON input string.
*/
typedef struct cson_data_source_StringSource_
{
/** Start of input string. */
char const * str;
/** Current iteration position. Must initially be == str. */
char const * pos;
/** Logical EOF, one-past-the-end of str. */
char const * end;
} cson_data_source_StringSource_t;
/**
A cson_data_source_f() implementation which requires the state argument
to be a properly populated (cson_data_source_StringSource_t*).
*/
static int cson_data_source_StringSource( void * state, void * dest, unsigned int * n )
{
if( !state || !n || !dest ) return cson_rc.ArgError;
else if( !*n ) return 0 /* ignore this */;
else
{
unsigned int i;
cson_data_source_StringSource_t * ss = (cson_data_source_StringSource_t*) state;
unsigned char * tgt = (unsigned char *)dest;
for( i = 0; (i < *n) && (ss->pos < ss->end); ++i, ++ss->pos, ++tgt )
{
*tgt = *ss->pos;
}
*n = i;
return 0;
}
}
int cson_parse_string( cson_value ** tgt, char const * src, unsigned int len,
cson_parse_opt const * opt, cson_parse_info * err )
{
if( ! tgt || !src ) return cson_rc.ArgError;
else if( !*src || (len<2/*2==len of {} and []*/) ) return cson_rc.RangeError;
else
{
cson_data_source_StringSource_t ss;
ss.str = ss.pos = src;
ss.end = src + len;
return cson_parse( tgt, cson_data_source_StringSource, &ss, opt, err );
}
}
int cson_parse_buffer( cson_value ** tgt,
cson_buffer const * buf,
cson_parse_opt const * opt,
cson_parse_info * err )
{
return ( !tgt || !buf || !buf->mem || !buf->used )
? cson_rc.ArgError
: cson_parse_string( tgt, (char const *)buf->mem,
buf->used, opt, err );
}
int cson_buffer_reserve( cson_buffer * buf, cson_size_t n )
{
if( ! buf ) return cson_rc.ArgError;
else if( 0 == n )
{
cson_free(buf->mem, "cson_buffer::mem");
*buf = cson_buffer_empty;
return 0;
}
else if( buf->capacity >= n )
{
return 0;
}
else
{
unsigned char * x = (unsigned char *)realloc( buf->mem, n );
if( ! x ) return cson_rc.AllocError;
memset( x + buf->used, 0, n - buf->used );
buf->mem = x;
buf->capacity = n;
++buf->timesExpanded;
return 0;
}
}
cson_size_t cson_buffer_fill( cson_buffer * buf, char c )
{
if( !buf || !buf->capacity || !buf->mem ) return 0;
else
{
memset( buf->mem, c, buf->capacity );
return buf->capacity;
}
}
/**
cson_data_dest_f() implementation, used by cson_output_buffer().
arg MUST be a (cson_buffer*). This function appends n bytes at
position arg->used, expanding the buffer as necessary.
*/
static int cson_data_dest_cson_buffer( void * arg, void const * data_, unsigned int n )
{
if( ! arg || (n<0) ) return cson_rc.ArgError;
else if( ! n ) return 0;
else
{
cson_buffer * sb = (cson_buffer*)arg;
char const * data = (char const *)data_;
cson_size_t npos = sb->used + n;
unsigned int i;
if( npos >= sb->capacity )
{
const cson_size_t oldCap = sb->capacity;
const cson_size_t asz = npos * 2;
if( asz < npos ) return cson_rc.ArgError; /* overflow */
else if( 0 != cson_buffer_reserve( sb, asz ) ) return cson_rc.AllocError;
assert( (sb->capacity > oldCap) && "Internal error in memory buffer management!" );
/* make sure it gets NULL terminated. */
memset( sb->mem + oldCap, 0, (sb->capacity - oldCap) );
}
for( i = 0; i < n; ++i, ++sb->used )
{
sb->mem[sb->used] = data[i];
}
return 0;
}
}
int cson_output_buffer( cson_value const * v, cson_buffer * buf,
cson_output_opt const * opt )
{
int rc = cson_output( v, cson_data_dest_cson_buffer, buf, opt );
if( 0 == rc )
{ /* Ensure that the buffer is null-terminated. */
rc = cson_buffer_reserve( buf, buf->used + 1 );
if( 0 == rc )
{
buf->mem[buf->used] = 0;
}
}
return rc;
}
/** @internal
Tokenizes an input string on a given separator. Inputs are:
- (inp) = is a pointer to the pointer to the start of the input.
- (separator) = the separator character
- (end) = a pointer to NULL. i.e. (*end == NULL)
This function scans *inp for the given separator char or a NULL char.
Successive separators at the start of *inp are skipped. The effect is
that, when this function is called in a loop, all neighboring
separators are ignored. e.g. the string "aa.bb...cc" will tokenize to
the list (aa,bb,cc) if the separator is '.' and to (aa.,...cc) if the
separator is 'b'.
Returns 0 (false) if it finds no token, else non-0 (true).
Output:
- (*inp) will be set to the first character of the next token.
- (*end) will point to the one-past-the-end point of the token.
If (*inp == *end) then the end of the string has been reached
without finding a token.
Post-conditions:
- (*end == *inp) if no token is found.
- (*end > *inp) if a token is found.
It is intolerant of NULL values for (inp, end), and will assert() in
debug builds if passed NULL as either parameter.
*/
static char cson_next_token( char const ** inp, char separator, char const ** end )
{
char const * pos = NULL;
assert( inp && end && *inp );
if( *inp == *end ) return 0;
pos = *inp;
if( !*pos )
{
*end = pos;
return 0;
}
for( ; *pos && (*pos == separator); ++pos) { /* skip preceeding splitters */ }
*inp = pos;
for( ; *pos && (*pos != separator); ++pos) { /* find next splitter */ }
*end = pos;
return (pos > *inp) ? 1 : 0;
}
int cson_object_fetch_sub2( cson_object const * obj, cson_value ** tgt, char const * path )
{
if( ! obj || !path ) return cson_rc.ArgError;
else if( !*path || !*(1+path) ) return cson_rc.RangeError;
else return cson_object_fetch_sub(obj, tgt, path+1, *path);
}
int cson_object_fetch_sub( cson_object const * obj, cson_value ** tgt, char const * path, char sep )
{
if( ! obj || !path ) return cson_rc.ArgError;
else if( !*path || !sep ) return cson_rc.RangeError;
else
{
char const * beg = path;
char const * end = NULL;
int rc;
unsigned int i, len;
unsigned int tokenCount = 0;
cson_value * cv = NULL;
cson_object const * curObj = obj;
enum { BufSize = 128 };
char buf[BufSize];
memset( buf, 0, BufSize );
rc = cson_rc.RangeError;
while( cson_next_token( &beg, sep, &end ) )
{
if( beg == end ) break;
else
{
++tokenCount;
beg = end;
end = NULL;
}
}
if( 0 == tokenCount ) return cson_rc.RangeError;
beg = path;
end = NULL;
for( i = 0; i < tokenCount; ++i, beg=end, end=NULL )
{
rc = cson_next_token( &beg, sep, &end );
assert( 1 == rc );
assert( beg != end );
assert( end > beg );
len = end - beg;
if( len > (BufSize-1) ) return cson_rc.RangeError;
memset( buf, 0, len + 1 );
memcpy( buf, beg, len );
buf[len] = 0;
cv = cson_object_get( curObj, buf );
if( NULL == cv ) return cson_rc.NotFoundError;
else if( i == (tokenCount-1) )
{
if(tgt) *tgt = cv;
return 0;
}
else if( cson_value_is_object(cv) )
{
curObj = cson_value_get_object(cv);
assert((NULL != curObj) && "Detected mis-management of internal memory!");
}
/* TODO: arrays. Requires numeric parsing for the index. */
else
{
return cson_rc.NotFoundError;
}
}
assert( i == tokenCount );
return cson_rc.NotFoundError;
}
}
cson_value * cson_object_get_sub( cson_object const * obj, char const * path, char sep )
{
cson_value * v = NULL;
cson_object_fetch_sub( obj, &v, path, sep );
return v;
}
cson_value * cson_object_get_sub2( cson_object const * obj, char const * path )
{
cson_value * v = NULL;
cson_object_fetch_sub2( obj, &v, path );
return v;
}
static cson_value * cson_value_clone_array( cson_value const * orig )
{
unsigned int i = 0;
cson_array const * asrc = cson_value_get_array( orig );
unsigned int alen = cson_array_length_get( asrc );
cson_value * destV = NULL;
cson_array * destA = NULL;
assert( orig && asrc );
destV = cson_value_new_array();
if( NULL == destV ) return NULL;
destA = cson_value_get_array( destV );
assert( destA );
if( 0 != cson_array_reserve( destA, alen ) )
{
cson_value_free( destV );
return NULL;
}
for( ; i < alen; ++i )
{
cson_value * ch = cson_array_get( asrc, i );
if( NULL != ch )
{
cson_value * cl = cson_value_clone( ch );
if( NULL == cl )
{
cson_value_free( destV );
return NULL;
}
if( 0 != cson_array_set( destA, i, cl ) )
{
cson_value_free( cl );
cson_value_free( destV );
return NULL;
}
}
}
return destV;
}
static cson_value * cson_value_clone_object( cson_value const * orig )
{
cson_object const * src = cson_value_get_object( orig );
cson_value * destV = NULL;
cson_object * dest = NULL;
cson_kvp const * kvp = NULL;
cson_object_iterator iter = cson_object_iterator_empty;
assert( orig && src );
if( 0 != cson_object_iter_init( src, &iter ) )
{
return NULL;
}
destV = cson_value_new_object();
if( NULL == destV ) return NULL;
dest = cson_value_get_object( destV );
assert( dest );
if( src->kvp.count > cson_kvp_list_reserve( &dest->kvp, src->kvp.count ) ){
cson_value_free( destV );
return NULL;
}
while( (kvp = cson_object_iter_next( &iter )) )
{
/*
FIXME: refcount the keys! We first need a setter which takes
a cson_string or cson_value key type.
*/
cson_value * key = NULL;
cson_value * val = NULL;
key = cson_value_clone(kvp->key);
val = key ? cson_value_clone( kvp->value ) : NULL;
if( ! key || !val ){
cson_value_free(key);
cson_value_free(val);
cson_value_free(destV);
return NULL;
}
assert( CSON_STR(key) );
if( 0 != cson_object_set_s( dest, CSON_STR(key), val ) )
{
cson_value_free(key);
cson_value_free(val);
cson_value_free(destV);
return NULL;
}
}
return destV;
}
cson_value * cson_value_clone( cson_value const * orig )
{
if( NULL == orig ) return NULL;
else
{
switch( orig->api->typeID )
{
case CSON_TYPE_UNDEF:
assert(0 && "This should never happen.");
return NULL;
case CSON_TYPE_NULL:
return cson_value_null();
case CSON_TYPE_BOOL:
return cson_value_new_bool( cson_value_get_bool( orig ) );
case CSON_TYPE_INTEGER:
return cson_value_new_integer( cson_value_get_integer( orig ) );
break;
case CSON_TYPE_DOUBLE:
return cson_value_new_double( cson_value_get_double( orig ) );
break;
case CSON_TYPE_STRING: {
cson_string const * str = cson_value_get_string( orig );
return cson_value_new_string( cson_string_cstr( str ),
cson_string_length_bytes( str ) );
}
case CSON_TYPE_ARRAY:
return cson_value_clone_array( orig );
case CSON_TYPE_OBJECT:
return cson_value_clone_object( orig );
}
assert( 0 && "We can't get this far." );
return NULL;
}
}
cson_value * cson_string_value(cson_string const * s)
{
#define MT CSON_SPECIAL_VALUES[CSON_VAL_STR_EMPTY]
return s
? ((s==MT.value) ? &MT : CSON_VCAST(s))
: NULL;
#undef MT
}
cson_value * cson_object_value(cson_object const * s)
{
return s
? CSON_VCAST(s)
: NULL;
}
cson_value * cson_array_value(cson_array const * s)
{
return s
? CSON_VCAST(s)
: NULL;
}
void cson_free_object(cson_object *x)
{
if(x) cson_value_free(cson_object_value(x));
}
void cson_free_array(cson_array *x)
{
if(x) cson_value_free(cson_array_value(x));
}
void cson_free_string(cson_string const *x)
{
if(x) cson_value_free(cson_string_value(x));
}
void cson_free_value(cson_value *x)
{
cson_value_free(x);
}
#if 0
/* i'm not happy with this... */
char * cson_pod_to_string( cson_value const * orig )
{
if( ! orig ) return NULL;
else
{
enum { BufSize = 64 };
char * v = NULL;
switch( orig->api->typeID )
{
case CSON_TYPE_BOOL: {
char const bv = cson_value_get_bool(orig);
v = cson_strdup( bv ? "true" : "false",
bv ? 4 : 5 );
break;
}
case CSON_TYPE_UNDEF:
case CSON_TYPE_NULL: {
v = cson_strdup( "null", 4 );
break;
}
case CSON_TYPE_STRING: {
cson_string const * jstr = cson_value_get_string(orig);
unsigned const int slen = cson_string_length_bytes( jstr );
assert( NULL != jstr );
v = cson_strdup( cson_string_cstr( jstr ), slen );
break;
}
case CSON_TYPE_INTEGER: {
char buf[BufSize] = {0};
if( 0 < sprintf( v, "%"CSON_INT_T_PFMT, cson_value_get_integer(orig)) )
{
v = cson_strdup( buf, strlen(buf) );
}
break;
}
case CSON_TYPE_DOUBLE: {
char buf[BufSize] = {0};
if( 0 < sprintf( v, "%"CSON_DOUBLE_T_PFMT, cson_value_get_double(orig)) )
{
v = cson_strdup( buf, strlen(buf) );
}
break;
}
default:
break;
}
return v;
}
}
#endif
#if 0
/* i'm not happy with this... */
char * cson_pod_to_string( cson_value const * orig )
{
if( ! orig ) return NULL;
else
{
enum { BufSize = 64 };
char * v = NULL;
switch( orig->api->typeID )
{
case CSON_TYPE_BOOL: {
char const bv = cson_value_get_bool(orig);
v = cson_strdup( bv ? "true" : "false",
bv ? 4 : 5 );
break;
}
case CSON_TYPE_UNDEF:
case CSON_TYPE_NULL: {
v = cson_strdup( "null", 4 );
break;
}
case CSON_TYPE_STRING: {
cson_string const * jstr = cson_value_get_string(orig);
unsigned const int slen = cson_string_length_bytes( jstr );
assert( NULL != jstr );
v = cson_strdup( cson_string_cstr( jstr ), slen );
break;
}
case CSON_TYPE_INTEGER: {
char buf[BufSize] = {0};
if( 0 < sprintf( v, "%"CSON_INT_T_PFMT, cson_value_get_integer(orig)) )
{
v = cson_strdup( buf, strlen(buf) );
}
break;
}
case CSON_TYPE_DOUBLE: {
char buf[BufSize] = {0};
if( 0 < sprintf( v, "%"CSON_DOUBLE_T_PFMT, cson_value_get_double(orig)) )
{
v = cson_strdup( buf, strlen(buf) );
}
break;
}
default:
break;
}
return v;
}
}
#endif
unsigned int cson_value_msize(cson_value const * v)
{
if(!v) return 0;
else if( cson_value_is_builtin(v) ) return 0;
else {
unsigned int rc = sizeof(cson_value);
assert(NULL != v->api);
switch(v->api->typeID){
case CSON_TYPE_INTEGER:
assert( v != &CSON_SPECIAL_VALUES[CSON_VAL_INT_0]);
rc += sizeof(cson_int_t);
break;
case CSON_TYPE_DOUBLE:
assert( v != &CSON_SPECIAL_VALUES[CSON_VAL_DBL_0]);
rc += sizeof(cson_double_t);
break;
case CSON_TYPE_STRING:
rc += sizeof(cson_string)
+ CSON_STR(v)->length + 1/*NUL*/;
break;
case CSON_TYPE_ARRAY:{
cson_array const * ar = CSON_ARRAY(v);
cson_value_list const * li;
unsigned int i = 0;
assert( NULL != ar );
li = &ar->list;
rc += sizeof(cson_array)
+ (li->alloced * sizeof(cson_value *));
for( ; i < li->count; ++i ){
cson_value const * e = ar->list.list[i];
if( e ) rc += cson_value_msize( e );
}
break;
}
case CSON_TYPE_OBJECT:{
cson_object const * obj = CSON_OBJ(v);
unsigned int i = 0;
cson_kvp_list const * kl;
assert(NULL != obj);
kl = &obj->kvp;
rc += sizeof(cson_object)
+ (kl->alloced * sizeof(cson_kvp*));
for( ; i < kl->count; ++i ){
cson_kvp const * kvp = kl->list[i];
assert(NULL != kvp);
rc += cson_value_msize(kvp->key);
rc += cson_value_msize(kvp->value);
}
break;
}
case CSON_TYPE_UNDEF:
case CSON_TYPE_NULL:
case CSON_TYPE_BOOL:
assert( 0 && "Should have been caught by is-builtin check!" );
break;
default:
assert(0 && "Invalid typeID!");
return 0;
}
return rc;
}
}
int cson_object_merge( cson_object * dest, cson_object const * src, int flags ){
cson_object_iterator iter = cson_object_iterator_empty;
int rc;
char const replace = (flags & CSON_MERGE_REPLACE);
char const recurse = !(flags & CSON_MERGE_NO_RECURSE);
cson_kvp const * kvp;
if((!dest || !src) || (dest==src)) return cson_rc.ArgError;
rc = cson_object_iter_init( src, &iter );
if(rc) return rc;
while( (kvp = cson_object_iter_next(&iter) ) )
{
cson_string * key = cson_kvp_key(kvp);
cson_value * val = cson_kvp_value(kvp);
cson_value * check = cson_object_get_s( dest, key );
if(!check){
cson_object_set_s( dest, key, val );
continue;
}
else if(!replace && !recurse) continue;
else if(replace && !recurse){
cson_object_set_s( dest, key, val );
continue;
}
else if( recurse ){
if( cson_value_is_object(check) &&
cson_value_is_object(val) ){
rc = cson_object_merge( cson_value_get_object(check),
cson_value_get_object(val),
flags );
if(rc) return rc;
else continue;
}
else continue;
}
else continue;
}
return 0;
}
static cson_value * cson_guess_arg_type(char const *arg){
char * end = NULL;
if(('0'<=*arg) && ('9'>=*arg)){
goto do_string;
}
{
long const val = strtol(arg, &end, 10);
if(!*end){
return cson_value_new_integer( (cson_int_t)val);
}
}
{
double const val = strtod(arg, &end);
if(!*end){
return cson_value_new_double(val);
}
}
do_string:
return cson_value_new_string(arg, strlen(arg));
}
int cson_parse_argv_flags( int argc, char const * const * argv,
cson_object ** tgt, unsigned int * count ){
cson_object * o = NULL;
int rc = 0;
int i = 0;
if(argc<1 || !argc || !tgt) return cson_rc.ArgError;
o = *tgt ? *tgt : cson_new_object();
if(count) *count = 0;
for( i = 0; i < argc; ++i ){
char const * arg = argv[i];
char const * key = arg;
char const * pos;
cson_string * k = NULL;
cson_value * v = NULL;
if('-' != *arg) continue;
while('-'==*key) ++key;
if(!*key) continue;
pos = key;
while( *pos && ('=' != *pos)) ++pos;
k = cson_new_string(key, pos-key);
if(!k){
rc = cson_rc.AllocError;
break;
}
if(!*pos){ /** --key */
v = cson_value_true();
}else{ /** --key=...*/
assert('=' == *pos);
++pos /*skip '='*/;
v = *pos
? cson_guess_arg_type(pos)
: cson_value_null();
}
if(0 != (rc=cson_object_set_s(o, k, v))){
cson_free_string(k);
cson_value_free(v);
break;
}
else if(count) ++*count;
}
if(o != *tgt){
if(rc) cson_free_object(o);
else *tgt = o;
}
return rc;
}
#if defined(__cplusplus)
} /*extern "C"*/
#endif
#undef MARKER
#undef CSON_OBJECT_PROPS_SORT
#undef CSON_OBJECT_PROPS_SORT_USE_LENGTH
#undef CSON_CAST
#undef CSON_INT
#undef CSON_DBL
#undef CSON_STR
#undef CSON_OBJ
#undef CSON_ARRAY
#undef CSON_VCAST
#undef CSON_MALLOC_IMPL
#undef CSON_FREE_IMPL
#undef CSON_REALLOC_IMPL
/* end file ./cson.c */
/* begin file ./cson_lists.h */
/* Auto-generated from cson_list.h. Edit at your own risk! */
unsigned int cson_value_list_reserve( cson_value_list * self, unsigned int n )
{
if( !self ) return 0;
else if(0 == n)
{
if(0 == self->alloced) return 0;
cson_free(self->list, "cson_value_list_reserve");
self->list = NULL;
self->alloced = self->count = 0;
return 0;
}
else if( self->alloced >= n )
{
return self->alloced;
}
else
{
size_t const sz = sizeof(cson_value *) * n;
cson_value * * m = (cson_value **)cson_realloc( self->list, sz, "cson_value_list_reserve" );
if( ! m ) return self->alloced;
memset( m + self->alloced, 0, (sizeof(cson_value *)*(n-self->alloced)));
self->alloced = n;
self->list = m;
return n;
}
}
int cson_value_list_append( cson_value_list * self, cson_value * cp )
{
if( !self || !cp ) return cson_rc.ArgError;
else if( self->alloced > cson_value_list_reserve(self, self->count+1) )
{
return cson_rc.AllocError;
}
else
{
self->list[self->count++] = cp;
return 0;
}
}
int cson_value_list_visit( cson_value_list * self,
int (*visitor)(cson_value * obj, void * visitorState ),
void * visitorState )
{
int rc = cson_rc.ArgError;
if( self && visitor )
{
unsigned int i = 0;
for( rc = 0; (i < self->count) && (0 == rc); ++i )
{
cson_value * obj = self->list[i];
if(obj) rc = visitor( obj, visitorState );
}
}
return rc;
}
void cson_value_list_clean( cson_value_list * self,
void (*cleaner)(cson_value * obj)
)
{
if( self && cleaner && self->count )
{
unsigned int i = 0;
for( ; i < self->count; ++i )
{
cson_value * obj = self->list[i];
if(obj) cleaner(obj);
}
}
cson_value_list_reserve(self,0);
}
unsigned int cson_kvp_list_reserve( cson_kvp_list * self, unsigned int n )
{
if( !self ) return 0;
else if(0 == n)
{
if(0 == self->alloced) return 0;
cson_free(self->list, "cson_kvp_list_reserve");
self->list = NULL;
self->alloced = self->count = 0;
return 0;
}
else if( self->alloced >= n )
{
return self->alloced;
}
else
{
size_t const sz = sizeof(cson_kvp *) * n;
cson_kvp * * m = (cson_kvp **)cson_realloc( self->list, sz, "cson_kvp_list_reserve" );
if( ! m ) return self->alloced;
memset( m + self->alloced, 0, (sizeof(cson_kvp *)*(n-self->alloced)));
self->alloced = n;
self->list = m;
return n;
}
}
int cson_kvp_list_append( cson_kvp_list * self, cson_kvp * cp )
{
if( !self || !cp ) return cson_rc.ArgError;
else if( self->alloced > cson_kvp_list_reserve(self, self->count+1) )
{
return cson_rc.AllocError;
}
else
{
self->list[self->count++] = cp;
return 0;
}
}
int cson_kvp_list_visit( cson_kvp_list * self,
int (*visitor)(cson_kvp * obj, void * visitorState ),
void * visitorState )
{
int rc = cson_rc.ArgError;
if( self && visitor )
{
unsigned int i = 0;
for( rc = 0; (i < self->count) && (0 == rc); ++i )
{
cson_kvp * obj = self->list[i];
if(obj) rc = visitor( obj, visitorState );
}
}
return rc;
}
void cson_kvp_list_clean( cson_kvp_list * self,
void (*cleaner)(cson_kvp * obj)
)
{
if( self && cleaner && self->count )
{
unsigned int i = 0;
for( ; i < self->count; ++i )
{
cson_kvp * obj = self->list[i];
if(obj) cleaner(obj);
}
}
cson_kvp_list_reserve(self,0);
}
/* end file ./cson_lists.h */
/* begin file ./cson_sqlite3.c */
/** @file cson_sqlite3.c
This file contains the implementation code for the cson
sqlite3-to-JSON API.
License: the same as the cson core library.
Author: Stephan Beal (http://wanderinghorse.net/home/stephan)
*/
#if CSON_ENABLE_SQLITE3 /* we do this here for the sake of the amalgamation build */
#include <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_object2( sqlite3_stmt * st,
cson_array * colNames )
{
cson_value * rootV = NULL;
cson_object * root = NULL;
cson_string * colName = NULL;
int i = 0;
int rc = 0;
cson_value * currentValue = NULL;
int const colCount = sqlite3_column_count(st);
if( !colCount || (colCount>cson_array_length_get(colNames)) ) {
return NULL;
}
rootV = cson_value_new_object();
if(!rootV) return NULL;
root = cson_value_get_object(rootV);
for( i = 0; i < colCount; ++i )
{
colName = cson_value_get_string( cson_array_get( colNames, i ) );
if( ! colName ) goto error;
currentValue = cson_sqlite3_column_to_value(st,i);
if( ! currentValue ) currentValue = cson_value_null();
rc = cson_object_set_s( root, colName, currentValue );
if( 0 != rc )
{
cson_value_free( currentValue );
goto error;
}
}
goto end;
error:
cson_value_free( rootV );
rootV = NULL;
end:
return rootV;
}
cson_value * cson_sqlite3_row_to_object( sqlite3_stmt * st )
{
#if 0
cson_value * arV = cson_sqlite3_column_names(st);
cson_array * ar = NULL;
cson_value * rc = NULL;
if(!arV) return NULL;
ar = cson_value_get_array(arV);
assert( NULL != ar );
rc = cson_sqlite3_row_to_object2(st, ar);
cson_value_free(arV);
return rc;
#else
cson_value * rootV = NULL;
cson_object * root = NULL;
char const * colName = NULL;
int i = 0;
int rc = 0;
cson_value * currentValue = NULL;
int const colCount = sqlite3_column_count(st);
if( !colCount ) return NULL;
rootV = cson_value_new_object();
if(!rootV) return NULL;
root = cson_value_get_object(rootV);
for( i = 0; i < colCount; ++i )
{
colName = sqlite3_column_name( st, i );
if( ! colName ) goto error;
currentValue = cson_sqlite3_column_to_value(st,i);
if( ! currentValue ) currentValue = cson_value_null();
rc = cson_object_set( root, colName, currentValue );
if( 0 != rc )
{
cson_value_free( currentValue );
goto error;
}
}
goto end;
error:
cson_value_free( rootV );
rootV = NULL;
end:
return rootV;
#endif
}
cson_value * cson_sqlite3_row_to_array( sqlite3_stmt * st )
{
cson_value * aryV = NULL;
cson_array * ary = NULL;
int i = 0;
int rc = 0;
int const colCount = sqlite3_column_count(st);
if( ! colCount ) return NULL;
aryV = cson_value_new_array();
if( ! aryV ) return NULL;
ary = cson_value_get_array(aryV);
rc = cson_array_reserve(ary, (unsigned int) colCount );
if( 0 != rc ) goto error;
for( i = 0; i < colCount; ++i ){
cson_value * elem = cson_sqlite3_column_to_value(st,i);
if( ! elem ) goto error;
rc = cson_array_append(ary,elem);
if(0!=rc)
{
cson_value_free( elem );
goto end;
}
}
goto end;
error:
cson_value_free(aryV);
aryV = NULL;
end:
return aryV;
}
/**
Internal impl of cson_sqlite3_stmt_to_json() when the 'fat'
parameter is non-0.
*/
static int cson_sqlite3_stmt_to_json_fat( sqlite3_stmt * st, cson_value ** tgt )
{
#define RETURN(RC) { if(rootV) cson_value_free(rootV); return RC; }
if( ! tgt || !st ) return cson_rc.ArgError;
else
{
cson_value * rootV = NULL;
cson_object * root = NULL;
cson_value * colsV = NULL;
cson_array * cols = NULL;
cson_value * rowsV = NULL;
cson_array * rows = NULL;
cson_value * objV = NULL;
int rc = 0;
int const colCount = sqlite3_column_count(st);
if( colCount <= 0 ) return cson_rc.ArgError;
rootV = cson_value_new_object();
if( ! rootV ) return cson_rc.AllocError;
colsV = cson_sqlite3_column_names(st);
if( ! colsV )
{
cson_value_free( rootV );
RETURN(cson_rc.AllocError);
}
cols = cson_value_get_array(colsV);
assert(NULL != cols);
root = cson_value_get_object(rootV);
rc = cson_object_set( root, "columns", colsV );
if( rc )
{
cson_value_free( colsV );
RETURN(rc);
}
rowsV = cson_value_new_array();
if( ! rowsV ) RETURN(cson_rc.AllocError);
rc = cson_object_set( root, "rows", rowsV );
if( rc )
{
cson_value_free( rowsV );
RETURN(rc);
}
rows = cson_value_get_array(rowsV);
assert(rows);
while( SQLITE_ROW == sqlite3_step(st) )
{
objV = cson_sqlite3_row_to_object2(st, cols);
if( ! objV ) RETURN(cson_rc.UnknownError);
rc = cson_array_append( rows, objV );
if( rc )
{
cson_value_free( objV );
RETURN(rc);
}
}
*tgt = rootV;
return 0;
}
#undef RETURN
}
/**
Internal impl of cson_sqlite3_stmt_to_json() when the 'fat'
parameter is 0.
*/
static int cson_sqlite3_stmt_to_json_slim( sqlite3_stmt * st, cson_value ** tgt )
{
#define RETURN(RC) { if(rootV) cson_value_free(rootV); return RC; }
if( ! tgt || !st ) return cson_rc.ArgError;
else
{
cson_value * rootV = NULL;
cson_object * root = NULL;
cson_value * aryV = NULL;
cson_array * ary = NULL;
cson_value * rowsV = NULL;
cson_array * rows = NULL;
int rc = 0;
int const colCount = sqlite3_column_count(st);
if( colCount <= 0 ) return cson_rc.ArgError;
rootV = cson_value_new_object();
if( ! rootV ) return cson_rc.AllocError;
aryV = cson_sqlite3_column_names(st);
if( ! aryV )
{
cson_value_free( rootV );
RETURN(cson_rc.AllocError);
}
root = cson_value_get_object(rootV);
rc = cson_object_set( root, "columns", aryV );
if( rc )
{
cson_value_free( aryV );
RETURN(rc);
}
aryV = NULL;
ary = NULL;
rowsV = cson_value_new_array();
if( ! rowsV ) RETURN(cson_rc.AllocError);
rc = cson_object_set( root, "rows", rowsV );
if( 0 != rc )
{
cson_value_free( rowsV );
RETURN(rc);
}
rows = cson_value_get_array(rowsV);
assert(rows);
while( SQLITE_ROW == sqlite3_step(st) )
{
aryV = cson_sqlite3_row_to_array(st);
if( ! aryV ) RETURN(cson_rc.UnknownError);
rc = cson_array_append( rows, aryV );
if( 0 != rc )
{
cson_value_free( aryV );
RETURN(rc);
}
}
*tgt = rootV;
return 0;
}
#undef RETURN
}
int cson_sqlite3_stmt_to_json( sqlite3_stmt * st, cson_value ** tgt, char fat )
{
return fat
? cson_sqlite3_stmt_to_json_fat(st,tgt)
: cson_sqlite3_stmt_to_json_slim(st,tgt)
;
}
int cson_sqlite3_sql_to_json( sqlite3 * db, cson_value ** tgt, char const * sql, char fat )
{
if( !db || !tgt || !sql || !*sql ) return cson_rc.ArgError;
else
{
sqlite3_stmt * st = NULL;
int rc = sqlite3_prepare_v2( db, sql, -1, &st, NULL );
if( 0 != rc ) return cson_rc.IOError /* FIXME: Better error code? */;
rc = cson_sqlite3_stmt_to_json( st, tgt, fat );
sqlite3_finalize( st );
return rc;
}
}
#if defined(__cplusplus)
} /*extern "C"*/
#endif
#undef MARKER
#endif /* CSON_ENABLE_SQLITE3 */
/* end file ./cson_sqlite3.c */
#endif /* FOSSIL_ENABLE_JSON */
|
Added src/cson_amalgamation.h.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 |
#ifdef FOSSIL_ENABLE_JSON
/* 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
/** @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
string: length-0 string is false, else true.
Returns 0 on success and assigns *v (if v is not NULL) to either 0 or 1.
On error (val is NULL) then v is not modified.
*/
int cson_value_fetch_bool( cson_value const * val, char * v );
/**
Similar to cson_value_fetch_bool(), but fetches an integer value.
The conversion, if any, depends on the concrete type of val:
NULL, null, undefined: *v is set to 0 and 0 is returned.
string, object, array: *v is set to 0 and
cson_rc.TypeError is returned. The error may normally be safely
ignored, but it is provided for those wanted to know whether a direct
conversion was possible.
integer: *v is set to the int value and 0 is returned.
double: *v is set to the value truncated to int and 0 is returned.
*/
int cson_value_fetch_integer( cson_value const * val, cson_int_t * v );
/**
The same conversions and return values as
cson_value_fetch_integer(), except that the roles of int/double are
swapped.
*/
int cson_value_fetch_double( cson_value const * val, cson_double_t * v );
/**
If cson_value_is_string(val) then this function assigns *str to the
contents of the string. str may be NULL, in which case this function
functions like cson_value_is_string() but returns 0 on success.
Returns 0 if val is-a string, else non-0, in which case *str is not
modified.
The bytes are owned by the given value and may be invalidated in any of
the following ways:
- The value is cleaned up or freed.
- An array or object containing the value peforms a re-allocation
(it shrinks or grows).
And thus the bytes should be consumed before any further operations
on val or any container which holds it.
Note that this routine does not convert non-String values to their
string representations. (Adding that ability would add more
overhead to every cson_value instance.)
*/
int cson_value_fetch_string( cson_value const * val, cson_string ** str );
/**
If cson_value_is_object(val) then this function assigns *obj to the underlying
object value and returns 0, otherwise non-0 is returned and *obj is not modified.
obj may be NULL, in which case this function works like cson_value_is_object()
but with inverse return value semantics (0==success) (and it's a few
CPU cycles slower).
The *obj pointer is owned by val, and will be invalidated when val
is cleaned up.
Achtung: for best results, ALWAYS pass a pointer to NULL as the
second argument, e.g.:
@code
cson_object * obj = NULL;
int rc = cson_value_fetch_object( val, &obj );
// Or, more simply:
obj = cson_value_get_object( val );
@endcode
@see cson_value_get_object()
*/
int cson_value_fetch_object( cson_value const * val, cson_object ** obj );
/**
Identical to cson_value_fetch_object(), but works on array values.
@see cson_value_get_array()
*/
int cson_value_fetch_array( cson_value const * val, cson_array ** tgt );
/**
Simplified form of cson_value_fetch_bool(). Returns 0 if val
is NULL.
*/
char cson_value_get_bool( cson_value const * val );
/**
Simplified form of cson_value_fetch_integer(). Returns 0 if val
is NULL.
*/
cson_int_t cson_value_get_integer( cson_value const * val );
/**
Simplified form of cson_value_fetch_double(). Returns 0.0 if val
is NULL.
*/
cson_double_t cson_value_get_double( cson_value const * val );
/**
Simplified form of cson_value_fetch_string(). Returns NULL if val
is-not-a string value.
*/
cson_string * cson_value_get_string( cson_value const * val );
/**
Returns a pointer to the NULL-terminated string bytes of str.
The bytes are owned by string and will be invalided when it
is cleaned up.
If str is NULL then NULL is returned.
@see cson_string_length_bytes()
@see cson_value_get_string()
*/
char const * cson_string_cstr( cson_string const * str );
/**
Convenience function which returns the string bytes of
the given value if it is-a string, otherwise it returns
NULL. Note that this does no conversion of non-string types
to strings.
Equivalent to cson_string_cstr(cson_value_get_string(val)).
*/
char const * cson_value_get_cstr( cson_value const * val );
/**
Equivalent to cson_string_cmp_cstr_n(lhs, cson_string_cstr(rhs), cson_string_length_bytes(rhs)).
*/
int cson_string_cmp( cson_string const * lhs, cson_string const * rhs );
/**
Compares lhs to rhs using memcmp()/strcmp() semantics. Generically
speaking it returns a negative number if lhs is less-than rhs, 0 if
they are equivalent, or a positive number if lhs is greater-than
rhs. It has the following rules for equivalence:
- The maximum number of bytes compared is the lesser of rhsLen and
the length of lhs. If the strings do not match, but compare equal
up to the just-described comparison length, the shorter string is
considered to be less-than the longer one.
- If lhs and rhs are both NULL, or both have a length of 0 then they will
compare equal.
- If lhs is null/length-0 but rhs is not then lhs is considered to be less-than
rhs.
- If rhs is null/length-0 but lhs is not then rhs is considered to be less-than
rhs.
- i have no clue if the results are exactly correct for UTF strings.
*/
int cson_string_cmp_cstr_n( cson_string const * lhs, char const * rhs, unsigned int rhsLen );
/**
Equivalent to cson_string_cmp_cstr_n( lhs, rhs, (rhs&&*rhs)?strlen(rhs):0 ).
*/
int cson_string_cmp_cstr( cson_string const * lhs, char const * rhs );
/**
Returns the length, in bytes, of str, or 0 if str is NULL. This is
an O(1) operation.
TODO: add cson_string_length_chars() (is O(N) unless we add another
member to store the char length).
@see cson_string_cstr()
*/
unsigned int cson_string_length_bytes( cson_string const * str );
/**
Returns the number of UTF8 characters in str. This value will
be at most as long as cson_string_length_bytes() for the
same string, and less if it has multi-byte characters.
Returns 0 if str is NULL.
*/
unsigned int cson_string_length_utf8( cson_string const * str );
/**
Like cson_value_get_string(), but returns a copy of the underying
string bytes, which the caller owns and must eventually free
using free().
*/
char * cson_value_get_string_copy( cson_value const * val );
/**
Simplified form of cson_value_fetch_object(). Returns NULL if val
is-not-a object value.
*/
cson_object * cson_value_get_object( cson_value const * val );
/**
Simplified form of cson_value_fetch_array(). Returns NULL if val
is-not-a array value.
*/
cson_array * cson_value_get_array( cson_value const * val );
/**
Const-correct form of cson_value_get_array().
*/
cson_array const * cson_value_get_array_c( cson_value const * val );
/**
If ar is-a array and is at least (pos+1) entries long then *v (if v is not NULL)
is assigned to the value at that position (which may be NULL).
Ownership of the *v return value is unchanged by this call. (The
containing array may share ownership of the value with other
containers.)
If pos is out of range, non-0 is returned and *v is not modified.
If v is NULL then this function returns 0 if pos is in bounds, but does not
otherwise return a value to the caller.
*/
int cson_array_value_fetch( cson_array const * ar, unsigned int pos, cson_value ** v );
/**
Simplified form of cson_array_value_fetch() which returns NULL if
ar is NULL, pos is out of bounds or if ar has no element at that
position.
*/
cson_value * cson_array_get( cson_array const * ar, unsigned int pos );
/**
Ensures that ar has allocated space for at least the given
number of entries. This never shrinks the array and never
changes its logical size, but may pre-allocate space in the
array for storing new (as-yet-unassigned) values.
Returns 0 on success, or non-zero on error:
- If ar is NULL: cson_rc.ArgError
- If allocation fails: cson_rc.AllocError
*/
int cson_array_reserve( cson_array * ar, unsigned int size );
/**
If ar is not NULL, sets *v (if v is not NULL) to the length of the array
and returns 0. Returns cson_rc.ArgError if ar is NULL.
*/
int cson_array_length_fetch( cson_array const * ar, unsigned int * v );
/**
Simplified form of cson_array_length_fetch() which returns 0 if ar
is NULL.
*/
unsigned int cson_array_length_get( cson_array const * ar );
/**
Sets the given index of the given array to the given value.
If ar already has an item at that index then it is cleaned up and
freed before inserting the new item.
ar is expanded, if needed, to be able to hold at least (ndx+1)
items, and any new entries created by that expansion are empty
(NULL values).
On success, 0 is returned and ownership of v is transfered to ar.
On error ownership of v is NOT modified, and the caller may still
need to clean it up. For example, the following code will introduce
a leak if this function fails:
@code
cson_array_append( myArray, cson_value_new_integer(42) );
@endcode
Because the value created by cson_value_new_integer() has no owner
and is not cleaned up. The "more correct" way to do this is:
@code
cson_value * v = cson_value_new_integer(42);
int rc = cson_array_append( myArray, v );
if( 0 != rc ) {
cson_value_free( v );
... handle error ...
}
@endcode
*/
int cson_array_set( cson_array * ar, unsigned int ndx, cson_value * v );
/**
Appends the given value to the given array, transfering ownership of
v to ar. On error, ownership of v is not modified. Ownership of ar
is never changed by this function.
This is functionally equivalent to
cson_array_set(ar,cson_array_length_get(ar),v), but this
implementation has slightly different array-preallocation policy
(it grows more eagerly).
Returns 0 on success, non-zero on error. Error cases include:
- ar or v are NULL: cson_rc.ArgError
- Array cannot be expanded to hold enough elements: cson_rc.AllocError.
- Appending would cause a numeric overlow in the array's size:
cson_rc.RangeError. (However, you'll get an AllocError long before
that happens!)
On error ownership of v is NOT modified, and the caller may still
need to clean it up. See cson_array_set() for the details.
*/
int cson_array_append( cson_array * ar, cson_value * v );
/**
Creates a new cson_value from the given boolean value.
Ownership of the new value is passed to the caller, who must
eventually either free the value using cson_value_free() or
inserting it into a container (array or object), which transfers
ownership to the container. See the cson_value class documentation
for more details.
Returns NULL on allocation error.
*/
cson_value * cson_value_new_bool( char v );
/**
Alias for cson_value_new_bool(v).
*/
cson_value * cson_new_bool(char v);
/**
Returns the special JSON "null" value. When outputing JSON,
its string representation is "null" (without the quotes).
See cson_value_new_bool() for notes regarding the returned
value's memory.
*/
cson_value * cson_value_null();
/**
Equivalent to cson_value_new_bool(1).
*/
cson_value * cson_value_true();
/**
Equivalent to cson_value_new_bool(0).
*/
cson_value * cson_value_false();
/**
Semantically the same as cson_value_new_bool(), but for integers.
*/
cson_value * cson_value_new_integer( cson_int_t v );
/**
Alias for cson_value_new_integer(v).
*/
cson_value * cson_new_int(cson_int_t v);
/**
Semantically the same as cson_value_new_bool(), but for doubles.
*/
cson_value * cson_value_new_double( cson_double_t v );
/**
Alias for cson_value_new_double(v).
*/
cson_value * cson_new_double(cson_double_t v);
/**
Semantically the same as cson_value_new_bool(), but for strings.
This creates a JSON value which copies the first n bytes of str.
The string will automatically be NUL-terminated.
Note that if str is NULL or n is 0, this function still
returns non-NULL value representing that empty string.
Returns NULL on allocation error.
See cson_value_new_bool() for important information about the
returned memory.
*/
cson_value * cson_value_new_string( char const * str, unsigned int n );
/**
Allocates a new "object" value and transfers ownership of it to the
caller. It must eventually be destroyed, by the caller or its
owning container, by passing it to cson_value_free().
Returns NULL on allocation error.
Post-conditions: cson_value_is_object(value) will return true.
@see cson_value_new_array()
@see cson_value_free()
*/
cson_value * cson_value_new_object();
/**
This works like cson_value_new_object() but returns an Object
handle directly.
The value handle for the returned object can be fetched with
cson_object_value(theObject).
Ownership is transfered to the caller, who must eventually free it
by passing the Value handle (NOT the Object handle) to
cson_value_free() or passing ownership to a parent container.
Returns NULL on error (out of memory).
*/
cson_object * cson_new_object();
/**
Identical to cson_new_object() except that it creates
an Array.
*/
cson_array * cson_new_array();
/**
Identical to cson_new_object() except that it creates
a String.
*/
cson_string * cson_new_string(char const * val, unsigned int len);
/**
Equivalent to cson_value_free(cson_object_value(x)).
*/
void cson_free_object(cson_object *x);
/**
Equivalent to cson_value_free(cson_array_value(x)).
*/
void cson_free_array(cson_array *x);
/**
Equivalent to cson_value_free(cson_string_value(x)).
*/
void cson_free_string(cson_string const *x);
/**
Allocates a new "array" value and transfers ownership of it to the
caller. It must eventually be destroyed, by the caller or its
owning container, by passing it to cson_value_free().
Returns NULL on allocation error.
Post-conditions: cson_value_is_array(value) will return true.
@see cson_value_new_object()
@see cson_value_free()
*/
cson_value * cson_value_new_array();
/**
Frees any resources owned by v, then frees v. If v is a container
type (object or array) its children are also freed (recursively).
If v is NULL, this is a no-op.
This function decrements a reference count and only destroys the
value if its reference count drops to 0. Reference counts are
increased by either inserting the value into a container or via
cson_value_add_reference(). Even if this function does not
immediately destroy the value, the value must be considered, from
the perspective of that client code, to have been
destroyed/invalidated by this call.
@see cson_value_new_object()
@see cson_value_new_array()
@see cson_value_add_reference()
*/
void cson_value_free(cson_value * v);
/**
Alias for cson_value_free().
*/
void cson_free_value(cson_value * v);
/**
Functionally similar to cson_array_set(), but uses a string key
as an index. Like arrays, if a value already exists for the given key,
it is destroyed by this function before inserting the new value.
If v is NULL then this call is equivalent to
cson_object_unset(obj,key). Note that (v==NULL) is treated
differently from v having the special null value. In the latter
case, the key is set to the special null value.
The key may be encoded as ASCII or UTF8. Results are undefined
with other encodings, and the errors won't show up here, but may
show up later, e.g. during output.
Returns 0 on success, non-0 on error. It has the following error
cases:
- cson_rc.ArgError: obj or key are NULL or strlen(key) is 0.
- cson_rc.AllocError: an out-of-memory error
On error ownership of v is NOT modified, and the caller may still
need to clean it up. For example, the following code will introduce
a leak if this function fails:
@code
cson_object_set( myObj, "foo", cson_value_new_integer(42) );
@endcode
Because the value created by cson_value_new_integer() has no owner
and is not cleaned up. The "more correct" way to do this is:
@code
cson_value * v = cson_value_new_integer(42);
int rc = cson_object_set( myObj, "foo", v );
if( 0 != rc ) {
cson_value_free( v );
... handle error ...
}
@endcode
Potential TODOs:
- Add an overload which takes a cson_value key instead. To get
any value out of that we first need to be able to convert arbitrary
value types to strings. We could simply to-JSON them and use those
as keys.
*/
int cson_object_set( cson_object * obj, char const * key, cson_value * v );
/**
Functionaly equivalent to cson_object_set(), but takes a
cson_string() as its KEY type. The string will be reference-counted
like any other values, and the key may legally be used within this
same container (as a value) or others (as a key or value) at the
same time.
Returns 0 on success. On error, ownership (i.e. refcounts) of key
and value are not modified. On success key and value will get
increased refcounts unless they are replacing themselves (which is
a harmless no-op).
*/
int cson_object_set_s( cson_object * obj, cson_string * key, cson_value * v );
/**
Removes a property from an object.
If obj contains the given key, it is removed and 0 is returned. If
it is not found, cson_rc.NotFoundError is returned (which can
normally be ignored by client code).
cson_rc.ArgError is returned if obj or key are NULL or key has
a length of 0.
Returns 0 if the given key is found and removed.
This is functionally equivalent calling
cson_object_set(obj,key,NULL).
*/
int cson_object_unset( cson_object * obj, char const * key );
/**
Searches the given object for a property with the given key. If found,
it is returned. If no match is found, or any arguments are NULL, NULL is
returned. The returned object is owned by obj, and may be invalidated
by ANY operations which change obj's property list (i.e. add or remove
properties).
FIXME: allocate the key/value pairs like we do for cson_array,
to get improve the lifetimes of fetched values.
@see cson_object_fetch_sub()
@see cson_object_get_sub()
*/
cson_value * cson_object_get( cson_object const * obj, char const * key );
/**
Equivalent to cson_object_get() but takes a cson_string argument
instead of a C-style string.
*/
cson_value * cson_object_get_s( cson_object const * obj, cson_string const *key );
/**
Similar to cson_object_get(), but removes the value from the parent
object's ownership. If no item is found then NULL is returned, else
the object (now owned by the caller or possibly shared with other
containers) is returned.
Returns NULL if either obj or key are NULL or key has a length
of 0.
This function reduces the returned value's reference count but has
the specific property that it does not treat refcounts 0 and 1
identically, meaning that the returned object may have a refcount
of 0. This behaviour works around a corner-case where we want to
extract a child element from its parent and then destroy the parent
(which leaves us in an undesireable (normally) reference count
state).
*/
cson_value * cson_object_take( cson_object * obj, char const * key );
/**
Fetches a property from a child (or [great-]*grand-child) object.
obj is the object to search.
path is a delimited string, where the delimiter is the given
separator character.
This function searches for the given path, starting at the given object
and traversing its properties as the path specifies. If a given part of the
path is not found, then this function fails with cson_rc.NotFoundError.
If it finds the given path, it returns the value by assiging *tgt
to it. If tgt is NULL then this function has no side-effects but
will return 0 if the given path is found within the object, so it can be used
to test for existence without fetching it.
Returns 0 if it finds an entry, cson_rc.NotFoundError if it finds
no item, and any other non-zero error code on a "real" error. Errors include:
- obj or path are NULL: cson_rc.ArgError
- separator is 0, or path is an empty string or contains only
separator characters: cson_rc.RangeError
- There is an upper limit on how long a single path component may
be (some "reasonable" internal size), and cson_rc.RangeError is
returned if that length is violated.
Limitations:
- It has no way to fetch data from arrays this way. i could
imagine, e.g., a path of "subobj.subArray.0" for
subobj.subArray[0], or "0.3.1" for [0][3][1]. But i'm too
lazy/tired to add this.
Example usage:
Assume we have a JSON structure which abstractly looks like:
@code
{"subobj":{"subsubobj":{"myValue":[1,2,3]}}}
@endcode
Out goal is to get the value of myValue. We can do that with:
@code
cson_value * v = NULL;
int rc = cson_object_fetch_sub( object, &v, "subobj.subsubobj.myValue", '.' );
@endcode
Note that because keys in JSON may legally contain a '.', the
separator must be specified by the caller. e.g. the path
"subobj/subsubobj/myValue" with separator='/' is equivalent the
path "subobj.subsubobj.myValue" with separator='.'. The value of 0
is not legal as a separator character because we cannot
distinguish that use from the real end-of-string without requiring
the caller to also pass in the length of the string.
Multiple successive separators in the list are collapsed into a
single separator for parsing purposes. e.g. the path "a...b...c"
(separator='.') is equivalent to "a.b.c".
@see cson_object_get_sub()
@see cson_object_get_sub2()
*/
int cson_object_fetch_sub( cson_object const * obj, cson_value ** tgt, char const * path, char separator );
/**
Similar to cson_object_fetch_sub(), but derives the path separator
character from the first byte of the path argument. e.g. the
following arg equivalent:
@code
cson_object_fetch_sub( obj, &tgt, "foo.bar.baz", '.' );
cson_object_fetch_sub2( obj, &tgt, ".foo.bar.baz" );
@endcode
*/
int cson_object_fetch_sub2( cson_object const * obj, cson_value ** tgt, char const * path );
/**
Convenience form of cson_object_fetch_sub() which returns NULL if the given
item is not found.
*/
cson_value * cson_object_get_sub( cson_object const * obj, char const * path, char sep );
/**
Convenience form of cson_object_fetch_sub2() which returns NULL if the given
item is not found.
*/
cson_value * cson_object_get_sub2( cson_object const * obj, char const * path );
/** @enum CSON_MERGE_FLAGS
Flags for cson_object_merge().
*/
enum CSON_MERGE_FLAGS {
CSON_MERGE_DEFAULT = 0,
CSON_MERGE_REPLACE = 0x01,
CSON_MERGE_NO_RECURSE = 0x02
};
/**
"Merges" the src object's properties into dest. Each property in
src is copied (using reference counting, not cloning) into dest. If
dest already has the given property then behaviour depends on the
flags argument:
If flag has the CSON_MERGE_REPLACE bit set then this function will
by default replace non-object properties with the src property. If
src and dest both have the property AND it is an Object then this
function operates recursively on those objects. If
CSON_MERGE_NO_RECURSE is set then objects are not recursed in this
manner, and will be completely replaced if CSON_MERGE_REPLACE is
set.
Array properties in dest are NOT recursed for merging - they are
either replaced or left as-is, depending on whether flags contains
he CSON_MERGE_REPLACE bit.
Returns 0 on success. The error conditions are:
- dest or src are NULL or (dest==src) returns cson_rc.ArgError.
- dest or src contain cyclic references - this will likely cause a
crash due to endless recursion.
Potential TODOs:
- Add a flag to copy clones, not the original values.
*/
int cson_object_merge( cson_object * dest, cson_object const * src, int flags );
/**
An iterator type for traversing object properties.
Its values must be considered private, not to be touched by client
code.
@see cson_object_iter_init()
@see cson_object_iter_next()
*/
struct cson_object_iterator
{
/** @internal
The underlying object.
*/
cson_object const * obj;
/** @internal
Current position in the property list.
*/
unsigned int pos;
};
typedef struct cson_object_iterator cson_object_iterator;
/**
Empty-initialized cson_object_iterator object.
*/
#define cson_object_iterator_empty_m {NULL/*obj*/,0/*pos*/}
/**
Empty-initialized cson_object_iterator object.
*/
extern const cson_object_iterator cson_object_iterator_empty;
/**
Initializes the given iterator to point at the start of obj's
properties. Returns 0 on success or cson_rc.ArgError if !obj
or !iter.
obj must outlive iter, or results are undefined. Results are also
undefined if obj is modified while the iterator is active.
@see cson_object_iter_next()
*/
int cson_object_iter_init( cson_object const * obj, cson_object_iterator * iter );
/** @struct cson_kvp
This class represents a key/value pair and is used for storing
object properties. It is opaque to client code, and the public
API only uses this type for purposes of iterating over cson_object
properties using the cson_object_iterator interfaces.
*/
typedef struct cson_kvp cson_kvp;
/**
Returns the next property from the given iterator's object, or NULL
if the end of the property list as been reached.
Note that the order of object properties is undefined by the API,
and may change from version to version.
The returned memory belongs to the underlying object and may be
invalidated by any changes to that object.
Example usage:
@code
cson_object_iterator it;
cson_object_iter_init( myObject, &it ); // only fails if either arg is 0
cson_kvp * kvp;
cson_string const * key;
cson_value const * val;
while( (kvp = cson_object_iter_next(&it) ) )
{
key = cson_kvp_key(kvp);
val = cson_kvp_value(kvp);
...
}
@endcode
There is no need to clean up an iterator, as it holds no dynamic resources.
@see cson_kvp_key()
@see cson_kvp_value()
*/
cson_kvp * cson_object_iter_next( cson_object_iterator * iter );
/**
Returns the key associated with the given key/value pair,
or NULL if !kvp. The memory is owned by the object which contains
the key/value pair, and may be invalidated by any modifications
to that object.
*/
cson_string * cson_kvp_key( cson_kvp const * kvp );
/**
Returns the value associated with the given key/value pair,
or NULL if !kvp. The memory is owned by the object which contains
the key/value pair, and may be invalidated by any modifications
to that object.
*/
cson_value * cson_kvp_value( cson_kvp const * kvp );
/** @typedef some unsigned int type cson_size_t
*/
typedef unsigned int cson_size_t;
/**
A generic buffer class.
They can be used like this:
@code
cson_buffer b = cson_buffer_empty;
int rc = cson_buffer_reserve( &buf, 100 );
if( 0 != rc ) { ... allocation error ... }
... use buf.mem ...
... then free it up ...
cson_buffer_reserve( &buf, 0 );
@endcode
To take over ownership of a buffer's memory:
@code
void * mem = b.mem;
// mem is b.capacity bytes long, but only b.used
// bytes of it has been "used" by the API.
b = cson_buffer_empty;
@endcode
The memory now belongs to the caller and must eventually be
free()d.
*/
struct cson_buffer
{
/**
The number of bytes allocated for this object.
Use cson_buffer_reserve() to change its value.
*/
cson_size_t capacity;
/**
The number of bytes "used" by this object. It is not needed for
all use cases, and management of this value (if needed) is up
to the client. The cson_buffer public API does not use this
member. The intention is that this can be used to track the
length of strings which are allocated via cson_buffer, since
they need an explicit length and/or null terminator.
*/
cson_size_t used;
/**
This is a debugging/metric-counting value
intended to help certain malloc()-conscious
clients tweak their memory reservation sizes.
Each time cson_buffer_reserve() expands the
buffer, it increments this value by 1.
*/
cson_size_t timesExpanded;
/**
The memory allocated for and owned by this buffer.
Use cson_buffer_reserve() to change its size or
free it. To take over ownership, do:
@code
void * myptr = buf.mem;
buf = cson_buffer_empty;
@endcode
(You might also need to store buf.used and buf.capacity,
depending on what you want to do with the memory.)
When doing so, the memory must eventually be passed to free()
to deallocate it.
*/
unsigned char * mem;
};
/** Convenience typedef. */
typedef struct cson_buffer cson_buffer;
/** An empty-initialized cson_buffer object. */
#define cson_buffer_empty_m {0/*capacity*/,0/*used*/,0/*timesExpanded*/,NULL/*mem*/}
/** An empty-initialized cson_buffer object. */
extern const cson_buffer cson_buffer_empty;
/**
Uses cson_output() to append all JSON output to the given buffer
object. The semantics for the (v, opt) parameters, and the return
value, are as documented for cson_output(). buf must be a non-NULL
pointer to a properly initialized buffer (see example below).
Ownership of buf is not changed by calling this.
On success 0 is returned and the contents of buf.mem are guaranteed
to be NULL-terminated. On error the buffer might contain partial
contents, and it should not be used except to free its contents.
On error non-zero is returned. Errors include:
- Invalid arguments: cson_rc.ArgError
- Buffer cannot be expanded (runs out of memory): cson_rc.AllocError
Example usage:
@code
cson_buffer buf = cson_buffer_empty;
// optional: cson_buffer_reserve(&buf, 1024 * 10);
int rc = cson_output_buffer( myValue, &buf, NULL );
if( 0 != rc ) {
... error! ...
}
else {
... use buffer ...
puts((char const*)buf.mem);
}
// In both cases, we eventually need to clean up the buffer:
cson_buffer_reserve( &buf, 0 );
// Or take over ownership of its memory:
{
char * mem = (char *)buf.mem;
buf = cson_buffer_empty;
...
free(mem);
}
@endcode
@see cson_output()
*/
int cson_output_buffer( cson_value const * v, cson_buffer * buf,
cson_output_opt const * opt );
/**
This works identically to cson_parse_string(), but takes a
cson_buffer object as its input. buf->used bytes of buf->mem are
assumed to be valid JSON input, but it need not be NUL-terminated
(we only read up to buf->used bytes). The value of buf->used is
assumed to be the "string length" of buf->mem, i.e. not including
the NUL terminator.
Returns 0 on success, non-0 on error.
See cson_parse() for the semantics of the tgt, opt, and err
parameters.
*/
int cson_parse_buffer( cson_value ** tgt, cson_buffer const * buf,
cson_parse_opt const * opt, cson_parse_info * err );
/**
Reserves the given amount of memory for the given buffer object.
If n is 0 then buf->mem is freed and its state is set to
NULL/0 values.
If buf->capacity is less than or equal to n then 0 is returned and
buf is not modified.
If n is larger than buf->capacity then buf->mem is (re)allocated
and buf->capacity contains the new length. Newly-allocated bytes
are filled with zeroes.
On success 0 is returned. On error non-0 is returned and buf is not
modified.
buf->mem is owned by buf and must eventually be freed by passing an
n value of 0 to this function.
buf->used is never modified by this function.
*/
int cson_buffer_reserve( cson_buffer * buf, cson_size_t n );
/**
Fills all bytes of the given buffer with the given character.
Returns the number of bytes set (buf->capacity), or 0 if
!buf or buf has no memory allocated to it.
*/
cson_size_t cson_buffer_fill( cson_buffer * buf, char c );
/**
Uses a cson_data_source_f() function to buffer input into a
cson_buffer.
dest must be a non-NULL, initialized (though possibly empty)
cson_buffer object. Its contents, if any, will be overwritten by
this function, and any memory it holds might be re-used.
The src function is called, and passed the state parameter, to
fetch the input. If it returns non-0, this function returns that
error code. src() is called, possibly repeatedly, until it reports
that there is no more data.
Whether or not this function succeeds, dest still owns any memory
pointed to by dest->mem, and the client must eventually free it by
calling cson_buffer_reserve(dest,0).
dest->mem might (and possibly will) be (re)allocated by this
function, so any pointers to it held from before this call might be
invalidated by this call.
On error non-0 is returned and dest has almost certainly been
modified but its state must be considered incomplete.
Errors include:
- dest or src are NULL (cson_rc.ArgError)
- Allocation error (cson_rc.AllocError)
- src() returns an error code
Whether or not the state parameter may be NULL depends on
the src implementation requirements.
On success dest will contain the contents read from the input
source. dest->used will be the length of the read-in data, and
dest->mem will point to the memory. dest->mem is automatically
NUL-terminated if this function succeeds, but dest->used does not
count that terminator. On error the state of dest->mem must be
considered incomplete, and is not guaranteed to be NUL-terminated.
Example usage:
@code
cson_buffer buf = cson_buffer_empty;
int rc = cson_buffer_fill_from( &buf,
cson_data_source_FILE,
stdin );
if( rc )
{
fprintf(stderr,"Error %d (%s) while filling buffer.\n",
rc, cson_rc_string(rc));
cson_buffer_reserve( &buf, 0 );
return ...;
}
... use the buf->mem ...
... clean up the buffer ...
cson_buffer_reserve( &buf, 0 );
@endcode
To take over ownership of the buffer's memory, do:
@code
void * mem = buf.mem;
buf = cson_buffer_empty;
@endcode
In which case the memory must eventually be passed to free() to
free it.
*/
int cson_buffer_fill_from( cson_buffer * dest, cson_data_source_f src, void * state );
/**
Increments the reference count for the given value. This is a
low-level operation and should not normally be used by client code
without understanding exactly what side-effects it introduces.
Mis-use can lead to premature destruction or cause a value instance
to never be properly destructed (i.e. a memory leak).
This function is probably only useful for the following cases:
- You want to hold a reference to a value which is itself contained
in one or more containers, and you need to be sure that your
reference outlives the container(s) and/or that you can free your
copy of the reference without invaliding any references to the same
value held in containers.
- You want to implement "value sharing" behaviour without using an
object or array to contain the shared value. This can be used to
ensure the lifetime of the shared value instance. Each sharing
point adds a reference and simply passed the value to
cson_value_free() when they're done. The object will be kept alive
for other sharing points which added a reference.
Normally any such value handles would be invalidated when the
parent container(s) is/are cleaned up, but this function can be
used to effectively delay the cleanup.
This function, at its lowest level, increments the value's
reference count by 1.
To decrement the reference count, pass the value to
cson_value_free(), after which the value must be considered, from
the perspective of that client code, to be destroyed (though it
will not be if there are still other live references to
it). cson_value_free() will not _actually_ destroy the value until
its reference count drops to 0.
Returns 0 on success. The only error conditions are if v is NULL
(cson_rc.ArgError) or if the reference increment would overflow
(cson_rc.RangeError). In theory a client would get allocation
errors long before the reference count could overflow (assuming
those reference counts come from container insertions, as opposed
to via this function).
Insider notes which clients really need to know:
For shared/constant value instances, such as those returned by
cson_value_true() and cson_value_null(), this function has no side
effects - it does not actually modify the reference count because
(A) those instances are shared across all client code and (B) those
objects are static and never get cleaned up. However, that is an
implementation detail which client code should not rely on. In
other words, if you call cson_value_add_reference() 3 times using
the value returned by cson_value_true() (which is incidentally a
shared cson_value instance), you must eventually call
cson_value_free() 3 times to (semantically) remove those
references. However, internally the reference count for that
specific cson_value instance will not be modified and those
objects will never be freed (they're stack-allocated).
It might be interesting to note that newly-created objects
have a reference count of 0 instead of 1. This is partly because
if the initial reference is counted then it makes ownership
problematic when inserting values into containers. e.g. consider the
following code:
@code
// ACHTUNG: this code is hypothetical and does not reflect
// what actually happens!
cson_value * v =
cson_value_new_integer( 42 ); // v's refcount = 1
cson_array_append( myArray, v ); // v's refcount = 2
@endcode
If that were the case, the client would be forced to free his own
reference after inserting it into the container (which is a bit
counter-intuitive as well as intrusive). It would look a bit like
the following and would have to be done after every create/insert
operation:
@code
// ACHTUNG: this code is hypothetical and does not reflect
// what actually happens!
cson_array_append( myArray, v ); // v's refcount = 2
cson_value_free( v ); // v's refcount = 1
@endcode
(As i said: it's counter-intuitive and intrusive.)
Instead, values start with a refcount of 0 and it is only increased
when the value is added to an object/array container or when this
function is used to manually increment it. cson_value_free() treats
a refcount of 0 or 1 equivalently, destroying the value
instance. The only semantic difference between 0 and 1, for
purposes of cleaning up, is that a value with a non-0 refcount has
been had its refcount adjusted, whereas a 0 refcount indicates a
fresh, "unowned" reference.
*/
int cson_value_add_reference( cson_value * v );
#if 0
/**
DO NOT use this unless you know EXACTLY what you're doing.
It is only in the public API to work around a couple corner
cases involving extracting child elements and discarding
their parents.
This function sets v's reference count to the given value.
It does not clean up the object if rc is 0.
Returns 0 on success, non-0 on error.
*/
int cson_value_refcount_set( cson_value * v, unsigned short rc );
#endif
/**
Deeply copies a JSON value, be it an object/array or a "plain"
value (e.g. number/string/boolean). If cv is not NULL then this
function makes a deep clone of it and returns that clone. Ownership
of the clone is transfered to the caller, who must eventually free
the value using cson_value_free() or add it to a container
object/array to transfer ownership to the container. The returned
object will be of the same logical type as orig.
ACHTUNG: if orig contains any cyclic references at any depth level
this function will endlessly recurse. (Having _any_ cyclic
references violates this library's requirements.)
Returns NULL if orig is NULL or if cloning fails. Assuming that
orig is in a valid state, the only "likely" error case is that an
allocation fails while constructing the clone. In other words, if
cloning fails due to something other than an allocation error then
either orig is in an invalid state or there is a bug.
*/
cson_value * cson_value_clone( cson_value const * orig );
/**
Returns the value handle associated with s. The handle itself owns
s, and ownership of the handle is not changed by calling this
function. If the returned handle is part of a container, calling
cson_value_free() on the returned handle invoked undefined
behaviour (quite possibly downstream when the container tries to
use it).
This function only returns NULL if s. is NULL.
*/
cson_value * cson_string_value(cson_string const * s);
/**
The Object form of cson_string_value(). See that function
for full details.
*/
cson_value * cson_object_value(cson_object const * s);
/**
The Array form of cson_string_value(). See that function
for full details.
*/
cson_value * cson_array_value(cson_array const * s);
/**
Calculates the in-memory-allocated size of v, recursively if it is
a container type, with the following caveats and limitations:
If a given value is reference counted and multiple times within a
traversed container, each reference is counted at full cost. We
have no what of knowing if a given reference has been visited
already and whether it should or should not be counted, so we
pessimistically count them even though the _might_ not really count
for the given object tree (it depends on where the other open
references live).
This function returns 0 if any of the following are true:
- v is NULL
- v is one of the special singleton values (null, bools, empty
string, int 0, double 0.0)
All other values require an allocation, and this will return their
total memory cost, including the cson-specific internals and the
native value(s).
Note that because arrays and objects might have more internal slots
allocated than used, the alloced size of a container does not
necessarily increase when a new item is inserted into it. An interesting
side-effect of this is that when cson_clone()ing an array or object, the
size of the clone can actually be less than the original.
*/
unsigned int cson_value_msize(cson_value const * v);
/**
Parses command-line-style arguments into a JSON object.
It expects arguments to be in any of these forms, and any number
of leading dashes are treated identically:
--key : Treats key as a boolean with a true value.
--key=VAL : Treats VAL as either a double, integer, or string.
--key= : Treats key as a JSON null (not literal NULL) value.
Arguments not starting with a dash are skipped.
Each key/value pair is inserted into an object. If a given key
appears more than once then only the final entry is actually
stored.
argc and argv are expected to be values from main() (or similar,
possibly adjusted to remove argv[0]).
tgt must be either a pointer to NULL or a pointer to a
client-provided Object. If (NULL==*tgt) then this function
allocates a new object and on success it stores the new object in
*tgt (it is owned by the caller). If (NULL!=*tgt) then it is
assumed to be a properly allocated object. DO NOT pass a pointer to
an unitialized pointer, as that will fool this function into
thinking it is a valid object and Undefined Behaviour will ensue.
If count is not NULL then the number of arugments parsed by this
function are assigned to it. On error, count will be the number of
options successfully parsed before the error was encountered.
On success:
- 0 is returned.
- If (*tgt==NULL) then *tgt is assigned to a newly-allocated
object, owned by the caller. Note that even if no arguments are
parsed, the object is still created.
On error:
- non-0 is returned
- If (*tgt==NULL) then it is not modified.
- If (*tgt!=NULL) (i.e., the caller provides his own object) then
it might contain partial results.
*/
int cson_parse_argv_flags( int argc, char const * const * argv,
cson_object ** tgt, unsigned int * count );
/* 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 );
/**
Functionally almost identical to cson_sqlite3_row_to_object(), the
only difference being how the result objects gets its column names.
st must be a freshly-step()'d handle holding a result row.
colNames must be an Array with at least the same number of columns
as st. If it has fewer, NULL is returned and this function has
no side-effects.
For each column in the result set, the colNames entry at the same
index is used for the column key. If a given entry is-not-a String
then conversion will fail and NULL will be returned.
The one reason to prefer this over cson_sqlite3_row_to_object() is
that this one can share the keys across multiple rows (or even
other JSON containers), whereas the former makes fresh copies of
the column names for each row.
*/
cson_value * cson_sqlite3_row_to_object2( sqlite3_stmt * st,
cson_array * colNames );
/**
Similar to cson_sqlite3_row_to_object(), but creates an Array
value which contains the JSON-form values of the given result
set row.
*/
cson_value * cson_sqlite3_row_to_array( sqlite3_stmt * st );
/**
Converts the results of an sqlite3 SELECT statement to JSON,
in the form of a cson_value object tree.
st must be a prepared, but not yet traversed, SELECT query.
tgt must be a pointer to NULL (see the example below). If
either of those arguments are NULL, cson_rc.ArgError is returned.
This walks the query results and returns a JSON object which
has a different structure depending on the value of the 'fat'
argument.
If 'fat' is 0 then the structure is:
@code
{
"columns":["colName1",..."colNameN"],
"rows":[
[colVal0, ... colValN],
[colVal0, ... colValN],
...
]
}
@endcode
In the "non-fat" format the order of the columns and row values is
guaranteed to be the same as that of the underlying query.
If 'fat' is not 0 then the structure is:
@code
{
"columns":["colName1",..."colNameN"],
"rows":[
{"colName1":value1,..."colNameN":valueN},
{"colName1":value1,..."colNameN":valueN},
...
]
}
@endcode
In the "fat" format, the order of the "columns" entries is guaranteed
to be the same as the underlying query fields, but the order
of the keys in the "rows" might be different and might in fact
change when passed through different JSON implementations,
depending on how they implement object key/value pairs.
On success it returns 0 and assigns *tgt to a newly-allocated
JSON object tree (using the above structure), which the caller owns.
If the query returns no rows, the "rows" value will be an empty
array, as opposed to null.
On error non-0 is returned and *tgt is not modified.
The error code cson_rc.IOError is used to indicate a db-level
error, and cson_rc.TypeError is returned if sqlite3_column_count(st)
returns 0 or less (indicating an invalid or non-SELECT statement).
The JSON data types are determined by the column type as reported
by sqlite3_column_type():
SQLITE_INTEGER: integer
SQLITE_FLOAT: double
SQLITE_TEXT or SQLITE_BLOB: string, and this will only work if
the data is UTF8 compatible.
If the db returns a literal or SQL NULL for a value it is converted
to a JSON null. If it somehow finds a column type it cannot handle,
the value is also converted to a NULL in the output.
Example
@code
cson_value * json = NULL;
int rc = cson_sqlite3_stmt_to_json( myStatement, &json, 1 );
if( 0 != rc ) { ... error ... }
else {
cson_output_FILE( json, stdout, NULL );
cson_value_free( json );
}
@endcode
*/
int cson_sqlite3_stmt_to_json( sqlite3_stmt * st, cson_value ** tgt, char fat );
/**
A convenience wrapper around cson_sqlite3_stmt_to_json(), which
takes SQL instead of a sqlite3_stmt object. It has the same
return value and argument semantics as that function.
*/
int cson_sqlite3_sql_to_json( sqlite3 * db, cson_value ** tgt, char const * sql, char fat );
#if defined(__cplusplus)
} /*extern "C"*/
#endif
#endif /* CSON_ENABLE_SQLITE3 */
#endif /* WANDERINGHORSE_NET_CSON_SQLITE3_H_INCLUDED */
/* end file include/wh/cson/cson_sqlite3.h */
#endif /* FOSSIL_ENABLE_JSON */
|
Changes to src/db.c.
| ︙ | ︙ | |||
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
*/
struct Stmt {
Blob sql; /* The SQL for this statement */
sqlite3_stmt *pStmt; /* The results of sqlite3_prepare() */
Stmt *pNext, *pPrev; /* List of all unfinalized statements */
int nStep; /* Number of sqlite3_step() calls */
};
#endif /* INTERFACE */
/*
** Call this routine when a database error occurs.
*/
static void db_err(const char *zFormat, ...){
va_list ap;
char *z;
static const char zRebuildMsg[] =
"If you have recently updated your fossil executable, you might\n"
"need to run \"fossil all rebuild\" to bring the repository\n"
"schemas up to date.\n";
va_start(ap, zFormat);
z = vmprintf(zFormat, ap);
va_end(ap);
if( g.xferPanic ){
cgi_reset_content();
@ error Database\serror:\s%F(z)
| > > > > > > > > > > > > > > > > > > | | > | | 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
*/
struct Stmt {
Blob sql; /* The SQL for this statement */
sqlite3_stmt *pStmt; /* The results of sqlite3_prepare() */
Stmt *pNext, *pPrev; /* List of all unfinalized statements */
int nStep; /* Number of sqlite3_step() calls */
};
/*
** Copy this to initialize a Stmt object to a clean/empty state. This
** is useful to help avoid assertions when performing cleanup in some
** error handling cases.
*/
#define empty_Stmt_m {BLOB_INITIALIZER,NULL, NULL, NULL, 0}
#endif /* INTERFACE */
const struct Stmt empty_Stmt = empty_Stmt_m;
/*
** Call this routine when a database error occurs.
*/
static void db_err(const char *zFormat, ...){
va_list ap;
char *z;
int rc = 1;
static const char zRebuildMsg[] =
"If you have recently updated your fossil executable, you might\n"
"need to run \"fossil all rebuild\" to bring the repository\n"
"schemas up to date.\n";
va_start(ap, zFormat);
z = vmprintf(zFormat, ap);
va_end(ap);
#ifdef FOSSIL_ENABLE_JSON
if( g.json.isJsonMode ){
json_err( 0, z, 1 );
if( g.isHTTP ){
rc = 0 /* avoid HTTP 500 */;
}
}
else
#endif /* FOSSIL_ENABLE_JSON */
if( g.xferPanic ){
cgi_reset_content();
@ error Database\serror:\s%F(z)
cgi_reply();
}
else 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);
db_force_rollback();
fossil_exit(rc);
}
static int nBegin = 0; /* Nesting depth of BEGIN */
static int doRollback = 0; /* True to force a rollback */
static int nCommitHook = 0; /* Number of commit hooks */
static struct sCommitHook {
int (*xHook)(void); /* Functions to call at db_end_transaction() */
|
| ︙ | ︙ | |||
559 560 561 562 563 564 565 | /* ** Execute a query. Return the first column of the first row ** of the result set as a string. Space to hold the string is ** obtained from malloc(). If the result set is empty, return ** zDefault instead. */ | | | 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 |
/*
** Execute a query. Return the first column of the first row
** of the result set as a string. Space to hold the string is
** obtained from malloc(). If the result set is empty, return
** zDefault instead.
*/
char *db_text(char const *zDefault, const char *zSql, ...){
va_list ap;
Stmt s;
char *z;
va_start(ap, zSql);
db_vprepare(&s, 0, zSql, ap);
va_end(ap);
if( db_step(&s)==SQLITE_ROW ){
|
| ︙ | ︙ | |||
740 741 742 743 744 745 746 |
/*
* * 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"
| | | < < < | > | | > | | > | | > | | | > > > | > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > | 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 |
/*
* * 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' /*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 *' /*scan*/",
db_name("localdb"), zTable, zColumn);
}
/*
** If zDbName is a valid local database file, open it and return
** true. If it is not a valid local database file, return 0.
*/
static int isValidLocalDb(const char *zDbName){
i64 lsize;
if( file_access(zDbName, F_OK) ) return 0;
lsize = file_size(zDbName);
if( lsize%1024!=0 || lsize<4096 ) return 0;
db_open_or_attach(zDbName, "localdb");
/* If the "isexe" column is missing from the vfile table, then
** add it now. This code added on 2010-03-06. After all users have
** upgraded, this code can be safely deleted.
*/
if( !db_local_column_exists("vfile", "isexe") ){
db_multi_exec("ALTER TABLE vfile ADD COLUMN isexe BOOLEAN DEFAULT 0");
}
/* If "islink"/"isLink" columns are missing from tables, then
** add them now. This code added on 2011-01-17 and 2011-08-27.
** After all users have upgraded, this code can be safely deleted.
*/
if( !db_local_column_exists("vfile", "islink") ){
db_multi_exec("ALTER TABLE vfile ADD COLUMN islink BOOLEAN DEFAULT 0");
}
if( !db_local_column_exists("stashfile", "isLink") &&
db_local_table_exists("stashfile") ){
db_multi_exec("ALTER TABLE stashfile ADD COLUMN isLink BOOLEAN DEFAULT 0");
}
if( !db_local_column_exists("undo", "isLink") &&
db_local_table_exists("undo") ){
db_multi_exec("ALTER TABLE undo ADD COLUMN isLink BOOLEAN DEFAULT 0");
}
if( !db_local_column_exists("undo_vfile", "islink") &&
db_local_table_exists("undo_vfile") ){
db_multi_exec("ALTER TABLE undo_vfile ADD COLUMN islink BOOLEAN DEFAULT 0");
}
return 1;
}
/*
** Locate the root directory of the local repository tree. The root
** directory is found by searching for a file named "_FOSSIL_" or ".fslckout"
** that contains a valid repository database.
**
** For legacy, also look for ".fos". The use of ".fos" is deprecated
** since "fos" has negative connotations in Hungarian, we are told.
**
** If no valid _FOSSIL_ or .fos file is found, we move up one level and
** try again. Once the file is found, the g.zLocalRoot variable is set
** to the root of the repository tree and this routine returns 1. If
** no database is found, then this routine return 0.
**
** This routine always opens the user database regardless of whether or
** not the repository database is found. If the _FOSSIL_ or .fos file
** is found, it is attached to the open database connection too.
*/
int db_open_local(void){
int i, n;
char zPwd[2000];
static const char *aDbName[] = { "/_FOSSIL_", "/.fslckout", "/.fos" };
if( g.localOpen) return 1;
file_getcwd(zPwd, sizeof(zPwd)-20);
n = strlen(zPwd);
if( n==1 && zPwd[0]=='/' ) zPwd[0] = '.';
while( n>0 ){
if( file_access(zPwd, W_OK) ) break;
for(i=0; i<sizeof(aDbName)/sizeof(aDbName[0]); i++){
sqlite3_snprintf(sizeof(zPwd)-n, &zPwd[n], "%s", aDbName[i]);
if( isValidLocalDb(zPwd) ){
/* Found a valid checkout database file */
zPwd[n] = 0;
while( n>1 && zPwd[n-1]=='/' ){
n--;
zPwd[n] = 0;
}
g.zLocalRoot = mprintf("%s/", zPwd);
g.localOpen = 1;
db_open_config(0);
db_open_repository(0);
return 1;
}
}
n--;
while( n>0 && zPwd[n]!='/' ){ n--; }
while( n>0 && zPwd[n-1]=='/' ){ n--; }
zPwd[n] = 0;
}
/* A checkout database file could not be found */
return 0;
}
/*
** Get the full pathname to the repository database file. The
** local database (the _FOSSIL_ or .fslckout database) must have already
** been opened before this routine is called.
*/
const char *db_repository_filename(void){
static char *zRepo = 0;
assert( g.localOpen );
assert( g.zLocalRoot );
if( zRepo==0 ){
zRepo = db_lget("repository", 0);
if( zRepo && !file_is_absolute_path(zRepo) ){
zRepo = mprintf("%s%s", g.zLocalRoot, zRepo);
}
}
return zRepo;
}
/*
** Open the repository database given by zDbName. If zDbName==NULL then
** get the name from the already open local database.
*/
void db_open_repository(const char *zDbName){
if( g.repositoryOpen ) return;
if( zDbName==0 ){
if( g.localOpen ){
zDbName = db_repository_filename();
}
if( zDbName==0 ){
db_err("unable to find the name of a repository database");
}
}
if( file_access(zDbName, R_OK) || file_size(zDbName)<1024 ){
if( file_access(zDbName, 0) ){
#ifdef FOSSIL_ENABLE_JSON
g.json.resultCode = FSL_JSON_E_DB_NOT_FOUND;
#endif
fossil_panic("repository does not exist or"
" is in an unreadable directory: %s", zDbName);
}else if( file_access(zDbName, R_OK) ){
#ifdef FOSSIL_ENABLE_JSON
g.json.resultCode = FSL_JSON_E_DENIED;
#endif
fossil_panic("read permission denied for repository %s", zDbName);
}else{
#ifdef FOSSIL_ENABLE_JSON
g.json.resultCode = FSL_JSON_E_DB_NOT_VALID;
#endif
fossil_panic("not a valid repository: %s", zDbName);
}
}
db_open_or_attach(zDbName, "repository");
g.repositoryOpen = 1;
g.zRepositoryName = mprintf("%s", zDbName);
/* Cache "allow-symlinks" option, because we'll need it on every stat call */
|
| ︙ | ︙ | |||
900 901 902 903 904 905 906 |
if( zRep==0 && nArgUsed && g.argc==nArgUsed+1 ){
zRep = g.argv[nArgUsed];
}
if( zRep==0 ){
if( db_open_local()==0 ){
goto rep_not_found;
}
| | > > > | 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 |
if( zRep==0 && nArgUsed && g.argc==nArgUsed+1 ){
zRep = g.argv[nArgUsed];
}
if( zRep==0 ){
if( db_open_local()==0 ){
goto rep_not_found;
}
zRep = db_repository_filename();
if( zRep==0 ){
goto rep_not_found;
}
}
db_open_repository(zRep);
if( g.repositoryOpen ){
if( (bFlags & OPEN_ANY_SCHEMA)==0 ) db_verify_schema();
return;
}
rep_not_found:
if( (bFlags & OPEN_OK_NOT_FOUND)==0 ){
#ifdef FOSSIL_ENABLE_JSON
g.json.resultCode = FSL_JSON_E_DB_NOT_FOUND;
#endif
fossil_fatal("use --repository or -R to specify the repository database");
}
}
/*
** Return the name of the database "localdb", "configdb", or "repository".
*/
|
| ︙ | ︙ | |||
942 943 944 945 946 947 948 949 950 951 952 953 954 955 |
/*
** 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() ){
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");
}
}
| > > > | 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 |
/*
** 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() ){
#ifdef FOSSIL_ENABLE_JSON
g.json.resultCode = FSL_JSON_E_DB_NEEDS_REBUILD;
#endif
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");
}
}
|
| ︙ | ︙ | |||
1162 1163 1164 1165 1166 1167 1168 |
blob_reset(&hash);
rid = content_put(&manifest);
manifest_crosslink(rid, &manifest);
}
}
/*
| | | 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 |
blob_reset(&hash);
rid = content_put(&manifest);
manifest_crosslink(rid, &manifest);
}
}
/*
** COMMAND: new*
** COMMAND: init
**
** Usage: %fossil new ?OPTIONS? FILENAME
** Or: %fossil init ?OPTIONS? FILENAME
**
** Create a repository for a new project in the file named FILENAME.
** This command is distinct from "clone". The "clone" command makes
|
| ︙ | ︙ | |||
1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 |
** Record the name of a local repository in the global_config() database.
** The repository filename %s is recorded as an entry with a "name" field
** of the following form:
**
** repo:%s
**
** The value field is set to 1.
*/
void db_record_repository_filename(const char *zName){
Blob full;
if( zName==0 ){
if( !g.localOpen ) return;
| > > > > > > > | > > > > > > > | 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 |
** Record the name of a local repository in the global_config() database.
** The repository filename %s is recorded as an entry with a "name" field
** of the following form:
**
** repo:%s
**
** The value field is set to 1.
**
** If running from a local checkout, also record the root of the checkout
** as follows:
**
** ckout:%s
**
** Where %s is the checkout root. The value is the repository file.
*/
void db_record_repository_filename(const char *zName){
Blob full;
if( zName==0 ){
if( !g.localOpen ) return;
zName = db_repository_filename();
}
file_canonical_name(zName, &full);
db_swap_connections();
db_multi_exec(
"INSERT OR IGNORE INTO global_config(name,value)"
"VALUES('repo:%q',1)",
blob_str(&full)
);
if( g.localOpen && g.zLocalRoot && g.zLocalRoot[0] ){
db_multi_exec(
"REPLACE INTO global_config(name, value)"
"VALUES('ckout:%q','%q');",
g.zLocalRoot, blob_str(&full)
);
}
db_swap_connections();
blob_reset(&full);
}
/*
** COMMAND: open
**
|
| ︙ | ︙ | |||
1675 1676 1677 1678 1679 1680 1681 |
fossil_panic("already within an open tree rooted at %s", g.zLocalRoot);
}
file_canonical_name(g.argv[2], &path);
db_open_repository(blob_str(&path));
db_init_database("./_FOSSIL_", zLocalSchema, (char*)0);
db_delete_on_failure("./_FOSSIL_");
db_open_local();
| | | 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 |
fossil_panic("already within an open tree rooted at %s", g.zLocalRoot);
}
file_canonical_name(g.argv[2], &path);
db_open_repository(blob_str(&path));
db_init_database("./_FOSSIL_", zLocalSchema, (char*)0);
db_delete_on_failure("./_FOSSIL_");
db_open_local();
db_lset("repository", g.argv[2]);
db_record_repository_filename(blob_str(&path));
vid = db_int(0, "SELECT pid FROM plink y"
" WHERE NOT EXISTS(SELECT 1 FROM plink x WHERE x.cid=y.pid)");
if( vid==0 ){
db_lset_int("checkout", 1);
}else{
char **oldArgv = g.argv;
|
| ︙ | ︙ | |||
1759 1760 1761 1762 1763 1764 1765 |
int width; /* Width of display. 0 for boolean values */
int versionable; /* Is this setting versionable? */
char const *def; /* Default value */
};
#endif /* INTERFACE */
struct stControlSettings const ctrlSettings[] = {
{ "access-log", 0, 0, 0, "off" },
| | | 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 |
int width; /* Width of display. 0 for boolean values */
int versionable; /* Is this setting versionable? */
char const *def; /* Default value */
};
#endif /* INTERFACE */
struct stControlSettings const ctrlSettings[] = {
{ "access-log", 0, 0, 0, "off" },
{ "allow-symlinks",0, 0, 1, "off" },
{ "auto-captcha", "autocaptcha", 0, 0, "on" },
{ "auto-shun", 0, 0, 0, "on" },
{ "autosync", 0, 0, 0, "on" },
{ "binary-glob", 0, 32, 1, "" },
{ "clearsign", 0, 0, 0, "off" },
{ "case-sensitive",0, 0, 0, "on" },
{ "crnl-glob", 0, 16, 1, "" },
|
| ︙ | ︙ | |||
1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 |
{ "proxy", 0, 32, 0, "off" },
{ "relative-paths",0, 0, 0, "on" },
{ "repo-cksum", 0, 0, 0, "on" },
{ "self-register", 0, 0, 0, "off" },
{ "ssl-ca-location",0, 40, 0, "" },
{ "ssl-identity", 0, 40, 0, "" },
{ "ssh-command", 0, 32, 0, "" },
{ "web-browser", 0, 32, 0, "" },
{ "white-foreground", 0, 0, 0, "off" },
{ 0,0,0,0,0 }
};
/*
** COMMAND: settings
| > > > | | | 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 |
{ "proxy", 0, 32, 0, "off" },
{ "relative-paths",0, 0, 0, "on" },
{ "repo-cksum", 0, 0, 0, "on" },
{ "self-register", 0, 0, 0, "off" },
{ "ssl-ca-location",0, 40, 0, "" },
{ "ssl-identity", 0, 40, 0, "" },
{ "ssh-command", 0, 32, 0, "" },
#ifdef FOSSIL_ENABLE_TCL
{ "tcl", 0, 0, 0, "off" },
#endif
{ "web-browser", 0, 32, 0, "" },
{ "white-foreground", 0, 0, 0, "off" },
{ 0,0,0,0,0 }
};
/*
** COMMAND: settings
** COMMAND: unset*
**
** %fossil settings ?PROPERTY? ?VALUE? ?OPTIONS?
** %fossil unset PROPERTY ?OPTIONS?
**
** The "settings" command with no arguments lists all properties and their
** values. With just a property name it shows the value of that property.
** With a value argument it changes the property for the current repository.
**
** Settings marked as versionable are overridden by the contents of the
** file named .fossil-settings/PROPERTY in the checked out files, if that
** file exists.
**
** The "unset" command clears a property setting.
**
**
** allow-symlinks If enabled, don't follow symlinks, and instead treat
** (versionable) them as symlinks on Unix. Has no effect on Windows
** (existing links in repository created on Unix become
** plain-text files with link destination path inside).
** Default: off
**
** auto-captcha If enabled, the Login page provides a button to
** fill in the captcha password. Default: on
**
|
| ︙ | ︙ | |||
1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 | ** the certificate and private key files. ** This identity will be presented to SSL servers to ** authenticate this client, in addition to the normal ** password authentication. ** ** ssh-command Command used to talk to a remote machine with ** the "ssh://" protocol. ** ** web-browser A shell command used to launch your preferred ** web browser when given a URL as an argument. ** Defaults to "start" on windows, "open" on Mac, ** and "firefox" on Unix. ** ** Options: | > > > > > > | 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 | ** the certificate and private key files. ** This identity will be presented to SSL servers to ** authenticate this client, in addition to the normal ** password authentication. ** ** ssh-command Command used to talk to a remote machine with ** the "ssh://" protocol. ** ** tcl If enabled, Tcl integration commands will be added to ** the TH1 interpreter, allowing Tcl expressions and ** scripts to be evaluated from TH1. Additionally, the ** Tcl interpreter will be able to evaluate TH1 expressions ** and scripts. Default: off. ** ** web-browser A shell command used to launch your preferred ** web browser when given a URL as an argument. ** Defaults to "start" on windows, "open" on Mac, ** and "firefox" on Unix. ** ** Options: |
| ︙ | ︙ |
Changes to src/descendants.c.
| ︙ | ︙ | |||
200 201 202 203 204 205 206 |
** direct ancestor as the largest generation number.
*/
void compute_direct_ancestors(int rid, int N){
Stmt ins;
Stmt q;
int gen = 0;
db_multi_exec(
| | > | 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
** direct ancestor as the largest generation number.
*/
void compute_direct_ancestors(int rid, int N){
Stmt ins;
Stmt q;
int gen = 0;
db_multi_exec(
"CREATE TEMP TABLE IF NOT EXISTS ancestor(rid INTEGER, generation INTEGER PRIMARY KEY);"
"DELETE FROM ancestor;"
"INSERT INTO ancestor VALUES(%d, 0);", rid
);
db_prepare(&ins, "INSERT INTO ancestor VALUES(:rid, :gen)");
db_prepare(&q,
"SELECT pid FROM plink"
" WHERE cid=:rid AND isprim"
);
|
| ︙ | ︙ | |||
260 261 262 263 264 265 266 | bag_clear(&seen); pqueue_clear(&queue); db_finalize(&ins); db_finalize(&q); } /* | | | 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 | bag_clear(&seen); pqueue_clear(&queue); db_finalize(&ins); db_finalize(&q); } /* ** COMMAND: descendants* ** ** Usage: %fossil descendants ?BASELINE-ID? ?OPTIONS? ** ** Find all leaf descendants of the baseline specified or if the argument ** is omitted, of the baseline currently checked out. ** ** Options: |
| ︙ | ︙ | |||
295 296 297 298 299 300 301 |
timeline_query_for_tty()
);
print_timeline(&q, 20, 0);
db_finalize(&q);
}
/*
| | | 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 |
timeline_query_for_tty()
);
print_timeline(&q, 20, 0);
db_finalize(&q);
}
/*
** COMMAND: leaves*
**
** Usage: %fossil leaves ?OPTIONS?
**
** Find leaves of all branches. By default show only open leaves.
** The --all flag causes all leaves (closed and open) to be shown.
** The --closed flag shows only closed leaves.
**
|
| ︙ | ︙ |
Changes to src/diff.c.
| ︙ | ︙ | |||
19 20 21 22 23 24 25 26 27 28 29 30 31 32 | ** text files. */ #include "config.h" #include "diff.h" #include <assert.h> /* ** Maximum length of a line in a text file. (8192) */ #define LENGTH_MASK_SZ 13 #define LENGTH_MASK ((1<<LENGTH_MASK_SZ)-1) /* | > > > > > > > > > > > > > > > > > > | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | ** text files. */ #include "config.h" #include "diff.h" #include <assert.h> #if INTERFACE /* ** Allowed flag parameters to the text_diff() and html_sbsdiff() funtions: */ #define DIFF_CONTEXT_MASK 0x0000ffff /* Lines of context. Default if 0 */ #define DIFF_WIDTH_MASK 0x00ff0000 /* side-by-side column width */ #define DIFF_IGNORE_EOLWS 0x01000000 /* Ignore end-of-line whitespace */ #define DIFF_SIDEBYSIDE 0x02000000 /* Generate a side-by-side diff */ #define DIFF_NEWFILE 0x04000000 /* Missing files are as empty files */ #define DIFF_BRIEF 0x08000000 /* Show filenames only */ #define DIFF_INLINE 0x00000000 /* Inline (not side-by-side) diff */ #define DIFF_HTML 0x10000000 /* Render for HTML */ #define DIFF_LINENO 0x20000000 /* Show line numbers in context diff */ #define DIFF_NOOPT 0x40000000 /* Suppress optimizations for debug */ #define DIFF_INVERT 0x80000000 /* Invert the diff for debug */ #endif /* INTERFACE */ /* ** Maximum length of a line in a text file. (8192) */ #define LENGTH_MASK_SZ 13 #define LENGTH_MASK ((1<<LENGTH_MASK_SZ)-1) /* |
| ︙ | ︙ | |||
45 46 47 48 49 50 51 | /* an array of DLine elements services two purposes. The fields ** above are one per line of input text. But each entry is also ** a bucket in a hash table, as follows: */ unsigned int iHash; /* 1+(first entry in the hash chain) */ }; /* | > > > > > | > > > > > > > > > > | 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
/* an array of DLine elements services two purposes. The fields
** above are one per line of input text. But each entry is also
** a bucket in a hash table, as follows: */
unsigned int iHash; /* 1+(first entry in the hash chain) */
};
/*
** Length of a dline
*/
#define LENGTH(X) ((X)->h & LENGTH_MASK)
/*
** A context for running a raw diff.
**
** The aEdit[] array describes the raw diff. Each triple of integers in
** aEdit[] means:
**
** (1) COPY: Number of lines aFrom and aTo have in common
** (2) DELETE: Number of lines found only in aFrom
** (3) INSERT: Number of lines found only in aTo
**
** The triples repeat until all lines of both aFrom and aTo are accounted
** for.
*/
typedef struct DContext DContext;
struct DContext {
int *aEdit; /* Array of copy/delete/insert triples */
int nEdit; /* Number of integers (3x num of triples) in aEdit[] */
int nEditAlloc; /* Space allocated for aEdit[] */
DLine *aFrom; /* File on left side of the diff */
|
| ︙ | ︙ | |||
130 131 132 133 134 135 136 |
** Return true if two DLine elements are identical.
*/
static int same_dline(DLine *pA, DLine *pB){
return pA->h==pB->h && memcmp(pA->z,pB->z,pA->h & LENGTH_MASK)==0;
}
/*
| | | > > > > > > > > > > > > > > | > > > > > | > | | | < < < | < < | < | < < < < < < < | < < < > | > | | < < > | > | < < | < < < < | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 |
** Return true if two DLine elements are identical.
*/
static int same_dline(DLine *pA, DLine *pB){
return pA->h==pB->h && memcmp(pA->z,pB->z,pA->h & LENGTH_MASK)==0;
}
/*
** Append a single line of context-diff output to pOut.
*/
static void appendDiffLine(
Blob *pOut, /* Where to write the line of output */
char cPrefix, /* One of " ", "+", or "-" */
DLine *pLine, /* The line to be output */
int html /* True if generating HTML. False for plain text */
){
blob_append(pOut, &cPrefix, 1);
if( html ){
char *zHtml;
if( cPrefix=='+' ){
blob_append(pOut, "<span class=\"diffadd\">", -1);
}else if( cPrefix=='-' ){
blob_append(pOut, "<span class=\"diffrm\">", -1);
}
zHtml = htmlize(pLine->z, (pLine->h & LENGTH_MASK));
blob_append(pOut, zHtml, -1);
fossil_free(zHtml);
if( cPrefix!=' ' ){
blob_append(pOut, "</span>", -1);
}
}else{
blob_append(pOut, pLine->z, pLine->h & LENGTH_MASK);
}
blob_append(pOut, "\n", 1);
}
/*
** Add two line numbers to the beginning of an output line for a context
** diff. One or of the other of the two numbers might be zero, which means
** to leave that number field blank. The "html" parameter means to format
** the output for HTML.
*/
static void appendDiffLineno(Blob *pOut, int lnA, int lnB, int html){
if( html ) blob_append(pOut, "<span class=\"diffln\">", -1);
if( lnA>0 ){
blob_appendf(pOut, "%6d ", lnA);
}else{
blob_append(pOut, " ", 7);
}
if( lnB>0 ){
blob_appendf(pOut, "%6d ", lnB);
}else{
blob_append(pOut, " ", 8);
}
if( html ) blob_append(pOut, "</span>", -1);
}
/*
** Given a diff context in which the aEdit[] array has been filled
** in, compute a context diff into pOut.
*/
static void contextDiff(
DContext *p, /* The difference */
Blob *pOut, /* Output a context diff to here */
int nContext, /* Number of lines of context */
int showLn, /* Show line numbers */
int html /* Render as HTML */
){
DLine *A; /* Left side of the diff */
DLine *B; /* Right side of the diff */
int a = 0; /* Index of next line in A[] */
int b = 0; /* Index of next line in B[] */
int *R; /* Array of COPY/DELETE/INSERT triples */
int r; /* Index into R[] */
int nr; /* Number of COPY/DELETE/INSERT triples to process */
int mxr; /* Maximum value for r */
int na, nb; /* Number of lines shown from A and B */
int i, j; /* Loop counters */
int m; /* Number of lines to output */
int skip; /* Number of lines to skip */
int nChunk = 0; /* Number of diff chunks seen so far */
A = p->aFrom;
B = p->aTo;
R = p->aEdit;
mxr = p->nEdit;
while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; }
for(r=0; r<mxr; r += 3*nr){
/* Figure out how many triples to show in a single block */
for(nr=1; R[r+nr*3]>0 && R[r+nr*3]<nContext*2; nr++){}
/* printf("r=%d nr=%d\n", r, nr); */
/* For the current block comprising nr triples, figure out
** how many lines of A and B are to be displayed
*/
if( R[r]>nContext ){
na = nb = nContext;
skip = R[r] - nContext;
}else{
na = nb = R[r];
skip = 0;
}
for(i=0; i<nr; i++){
na += R[r+i*3+1];
nb += R[r+i*3+2];
}
if( R[r+nr*3]>nContext ){
na += nContext;
nb += nContext;
}else{
na += R[r+nr*3];
nb += R[r+nr*3];
}
for(i=1; i<nr; i++){
na += R[r+i*3];
nb += R[r+i*3];
}
/* Show the header for this block, or if we are doing a modified
** context diff that contains line numbers, show the separate from
** the previous block.
*/
nChunk++;
if( showLn ){
if( r==0 ){
/* Do not show a top divider */
}else if( html ){
blob_appendf(pOut, "<span class=\"diffhr\">%.80c</span>\n", '.');
blob_appendf(pOut, "<a name=\"chunk%d\"></a>\n", nChunk);
}else{
blob_appendf(pOut, "%.80c\n", '.');
}
}else{
if( html ) blob_appendf(pOut, "<span class=\"diffln\">");
/*
* If the patch changes an empty file or results in an empty file,
* the block header must use 0,0 as position indicator and not 1,0.
* Otherwise, patch would be confused and may reject the diff.
*/
blob_appendf(pOut,"@@ -%d,%d +%d,%d @@",
na ? a+skip+1 : 0, na,
nb ? b+skip+1 : 0, nb);
if( html ) blob_appendf(pOut, "</span>");
blob_append(pOut, "\n", 1);
}
/* Show the initial common area */
a += skip;
b += skip;
m = R[r] - skip;
for(j=0; j<m; j++){
if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1, html);
appendDiffLine(pOut, ' ', &A[a+j], html);
}
a += m;
b += m;
/* Show the differences */
for(i=0; i<nr; i++){
m = R[r+i*3+1];
for(j=0; j<m; j++){
if( showLn ) appendDiffLineno(pOut, a+j+1, 0, html);
appendDiffLine(pOut, '-', &A[a+j], html);
}
a += m;
m = R[r+i*3+2];
for(j=0; j<m; j++){
if( showLn ) appendDiffLineno(pOut, 0, b+j+1, html);
appendDiffLine(pOut, '+', &B[b+j], html);
}
b += m;
if( i<nr-1 ){
m = R[r+i*3+3];
for(j=0; j<m; j++){
if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1, html);
appendDiffLine(pOut, ' ', &B[b+j], html);
}
b += m;
a += m;
}
}
/* Show the final common area */
assert( nr==i );
m = R[r+nr*3];
if( m>nContext ) m = nContext;
for(j=0; j<m; j++){
if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1, html);
appendDiffLine(pOut, ' ', &B[b+j], html);
}
}
}
/*
** Status of a single output line
*/
typedef struct SbsLine SbsLine;
struct SbsLine {
char *zLine; /* The output line under construction */
int n; /* Index of next unused slot in the zLine[] */
int width; /* Maximum width of a column in the output */
unsigned char escHtml; /* True to escape html characters */
int iStart; /* Write zStart prior to character iStart */
const char *zStart; /* A <span> tag */
int iEnd; /* Write </span> prior to character iEnd */
};
/*
** Flags for sbsWriteText()
*/
#define SBS_NEWLINE 0x0001 /* End with \n\000 */
#define SBS_PAD 0x0002 /* Pad output to width spaces */
/*
** Write up to width characters of pLine into z[]. Translate tabs into
** spaces. Add a newline if SBS_NEWLINE is set. Translate HTML characters
** if SBS_HTML is set. Pad the rendering out width bytes if SBS_PAD is set.
*/
static void sbsWriteText(SbsLine *p, DLine *pLine, unsigned flags){
int n = pLine->h & LENGTH_MASK;
int i; /* Number of input characters consumed */
int j; /* Number of output characters generated */
int k; /* Cursor position */
const char *zIn = pLine->z;
char *z = &p->zLine[p->n];
int w = p->width;
for(i=j=k=0; k<w && i<n; i++, k++){
char c = zIn[i];
if( p->escHtml ){
if( i==p->iStart ){
int x = strlen(p->zStart);
memcpy(z+j, p->zStart, x);
j += x;
}else if( i==p->iEnd ){
memcpy(z+j, "</span>", 7);
j += 7;
}
}
if( c=='\t' ){
z[j++] = ' ';
while( (k&7)!=7 && k<w ){ z[j++] = ' '; k++; }
}else if( c=='\r' || c=='\f' ){
z[j++] = ' ';
}else if( c=='<' && p->escHtml ){
memcpy(&z[j], "<", 4);
j += 4;
}else if( c=='&' && p->escHtml ){
memcpy(&z[j], "&", 5);
j += 5;
}else if( c=='>' && p->escHtml ){
memcpy(&z[j], ">", 4);
j += 4;
}else{
z[j++] = c;
}
}
if( p->escHtml && i<=p->iEnd ){
memcpy(&z[j], "</span>", 7);
j += 7;
}
if( (flags & SBS_PAD)!=0 ){
while( k<w ){ k++; z[j++] = ' '; }
}
if( flags & SBS_NEWLINE ){
z[j++] = '\n';
}
p->n += j;
}
/*
** Append a string to an SbSLine with coding, interpretation, or padding.
*/
static void sbsWrite(SbsLine *p, const char *zIn, int nIn){
memcpy(p->zLine+p->n, zIn, nIn);
p->n += nIn;
}
/*
** Append n spaces to the string.
*/
static void sbsWriteSpace(SbsLine *p, int n){
while( n-- ) p->zLine[p->n++] = ' ';
}
/*
** Append a string to the output only if we are rendering HTML.
*/
static void sbsWriteHtml(SbsLine *p, const char *zIn){
if( p->escHtml ) sbsWrite(p, zIn, strlen(zIn));
}
/*
** Write a 6-digit line number followed by a single space onto the line.
*/
static void sbsWriteLineno(SbsLine *p, int ln){
sbsWriteHtml(p, "<span class=\"diffln\">");
sqlite3_snprintf(7, &p->zLine[p->n], "%5d ", ln+1);
p->n += 6;
sbsWriteHtml(p, "</span>");
p->zLine[p->n++] = ' ';
}
/*
** Write out lines that have been edited. Adjust the highlight to cover
** only those parts of the line that actually changed.
*/
static void sbsWriteLineChange(
SbsLine *p, /* The SBS output line */
DLine *pLeft, /* Left line of the change */
int lnLeft, /* Line number for the left line */
DLine *pRight, /* Right line of the change */
int lnRight /* Line number of the right line */
){
int nLeft; /* Length of left line in bytes */
int nRight; /* Length of right line in bytes */
int nPrefix; /* Length of common prefix */
int nSuffix; /* Length of common suffix */
const char *zLeft; /* Text of the left line */
const char *zRight; /* Text of the right line */
nLeft = pLeft->h & LENGTH_MASK;
zLeft = pLeft->z;
nRight = pRight->h & LENGTH_MASK;
zRight = pRight->z;
nPrefix = 0;
while( nPrefix<nLeft && nPrefix<nRight && zLeft[nPrefix]==zRight[nPrefix] ){
nPrefix++;
}
nSuffix = 0;
if( nPrefix<nLeft && nPrefix<nRight ){
while( nSuffix<nLeft && nSuffix<nRight
&& zLeft[nLeft-nSuffix-1]==zRight[nRight-nSuffix-1] ){
nSuffix++;
}
if( nSuffix==nLeft || nSuffix==nRight ) nPrefix = 0;
}
if( nPrefix+nSuffix > nLeft ) nSuffix = nLeft - nPrefix;
if( nPrefix+nSuffix > nRight ) nSuffix = nRight - nPrefix;
if( nPrefix+nSuffix==nLeft ){
/* Text inserted on the right */
sbsWriteLineno(p, lnLeft);
p->iStart = p->iEnd = -1;
sbsWriteText(p, pLeft, SBS_PAD);
sbsWrite(p, " | ", 3);
sbsWriteLineno(p, lnRight);
p->iStart = nPrefix;
p->iEnd = nRight - nSuffix;
p->zStart = "<span class=\"diffadd\">";
sbsWriteText(p, pRight, SBS_NEWLINE);
}else if( nPrefix+nSuffix==nRight ){
/* Text deleted from the left */
sbsWriteLineno(p, lnLeft);
p->iStart = nPrefix;
p->iEnd = nLeft - nSuffix;
p->zStart = "<span class=\"diffrm\">";
sbsWriteText(p, pLeft, SBS_PAD);
sbsWrite(p, " | ", 3);
sbsWriteLineno(p, lnRight);
p->iStart = p->iEnd = -1;
sbsWriteText(p, pRight, SBS_NEWLINE);
}else{
/* Text modified between left and right */
sbsWriteLineno(p, lnLeft);
p->iStart = nPrefix;
p->iEnd = nLeft - nSuffix;
p->zStart = "<span class=\"diffchng\">";
sbsWriteText(p, pLeft, SBS_PAD);
sbsWrite(p, " | ", 3);
sbsWriteLineno(p, lnRight);
p->iEnd = nRight - nSuffix;
sbsWriteText(p, pRight, SBS_NEWLINE);
}
}
/*
** Minimum of two values
*/
static int minInt(int a, int b){ return a<b ? a : b; }
/*
** Return the number between 0 and 100 that is smaller the closer pA and
** pB match. Return 0 for a perfect match. Return 100 if pA and pB are
** completely different.
**
** The current algorithm is as follows:
**
** (1) Remove leading and trailing whitespace.
** (2) Truncate both strings to at most 250 characters
** (3) Find the length of the longest common subsequence
** (4) Longer common subsequences yield lower scores.
*/
static int match_dline(DLine *pA, DLine *pB){
const char *zA; /* Left string */
const char *zB; /* right string */
int nA; /* Bytes in zA[] */
int nB; /* Bytes in zB[] */
int avg; /* Average length of A and B */
int i, j, k; /* Loop counters */
int best = 0; /* Longest match found so far */
int score; /* Final score. 0..100 */
unsigned char c; /* Character being examined */
unsigned char aFirst[256]; /* aFirst[X] = index in zB[] of first char X */
unsigned char aNext[252]; /* aNext[i] = index in zB[] of next zB[i] char */
zA = pA->z;
zB = pB->z;
nA = pA->h & LENGTH_MASK;
nB = pB->h & LENGTH_MASK;
while( nA>0 && fossil_isspace(zA[0]) ){ nA--; zA++; }
while( nA>0 && fossil_isspace(zA[nA-1]) ){ nA--; }
while( nB>0 && fossil_isspace(zB[0]) ){ nB--; zB++; }
while( nB>0 && fossil_isspace(zB[nB-1]) ){ nB--; }
if( nA>250 ) nA = 250;
if( nB>250 ) nB = 250;
avg = (nA+nB)/2;
if( avg==0 ) return 0;
memset(aFirst, 0, sizeof(aFirst));
zA--; zB--; /* Make both zA[] and zB[] 1-indexed */
for(i=nB; i>0; i--){
c = (unsigned char)zB[i];
aNext[i] = aFirst[c];
aFirst[c] = i;
}
best = 0;
for(i=1; i<=nA-best; i++){
c = (unsigned char)zA[i];
for(j=aFirst[c]; j>0 && j<nB-best; j = aNext[j]){
int limit = minInt(nA-i, nB-j);
for(k=1; k<=limit && zA[k+i]==zB[k+j]; k++){}
if( k>best ) best = k;
}
}
score = (best>avg) ? 0 : (avg - best)*100/avg;
#if 0
fprintf(stderr, "A: [%.*s]\nB: [%.*s]\nbest=%d avg=%d score=%d\n",
nA, zA+1, nB, zB+1, best, avg, score);
#endif
/* Return the result */
return score;
}
/*
** There is a change block in which nLeft lines of text on the left are
** converted into nRight lines of text on the right. This routine computes
** how the lines on the left line up with the lines on the right.
**
** The return value is a buffer of unsigned characters, obtained from
** fossil_malloc(). (The caller needs to free the return value using
** fossil_free().) Entries in the returned array have values as follows:
**
** 1. Delete the next line of pLeft.
** 2. The next line of pLeft changes into the next line of pRight.
** 3. Insert the next line of pRight.
**
** The length of the returned array will be just large enough to cause
** all elements of pLeft and pRight to be consumed.
**
** Algorithm: Wagner's minimum edit-distance algorithm, modified by
** adding a cost to each match based on how well the two rows match
** each other. Insertion and deletion costs are 50. Match costs
** are between 0 and 100 where 0 is a perfect match 100 is a complete
** mismatch.
*/
static unsigned char *sbsAlignment(
DLine *aLeft, int nLeft, /* Text on the left */
DLine *aRight, int nRight /* Text on the right */
){
int i, j, k; /* Loop counters */
int *a; /* One row of the Wagner matrix */
int *pToFree; /* Space that needs to be freed */
unsigned char *aM; /* Wagner result matrix */
int aBuf[100]; /* Stack space for a[] if nRight not to big */
aM = fossil_malloc( (nLeft+1)*(nRight+1) );
if( nLeft==0 ){
memset(aM, 3, nRight);
return aM;
}
if( nRight==0 ){
memset(aM, 1, nLeft);
return aM;
}
if( nRight < (sizeof(aBuf)/sizeof(aBuf[0]))-1 ){
pToFree = 0;
a = aBuf;
}else{
a = pToFree = fossil_malloc( sizeof(a[0])*(nRight+1) );
}
/* Compute the best alignment */
for(i=0; i<=nRight; i++){
aM[i] = 3;
a[i] = i*50;
}
aM[0] = 0;
for(j=1; j<=nLeft; j++){
int p = a[0];
a[0] = p+50;
aM[j*(nRight+1)] = 1;
for(i=1; i<=nRight; i++){
int m = a[i-1]+50;
int d = 3;
if( m>a[i]+50 ){
m = a[i]+50;
d = 1;
}
if( m>p ){
int score = match_dline(&aLeft[j-1], &aRight[i-1]);
if( (score<66 || (i<j+1 && i>j-1)) && m>p+score ){
m = p+score;
d = 2;
}
}
p = a[i];
a[i] = m;
aM[j*(nRight+1)+i] = d;
}
}
/* Compute the lowest-cost path back through the matrix */
i = nRight;
j = nLeft;
k = (nRight+1)*(nLeft+1)-1;
while( i+j>0 ){
unsigned char c = aM[k--];
if( c==2 ){
assert( i>0 && j>0 );
i--;
j--;
}else if( c==3 ){
assert( i>0 );
i--;
}else{
assert( j>0 );
j--;
}
aM[k] = aM[j*(nRight+1)+i];
}
k++;
i = (nRight+1)*(nLeft+1) - k;
memmove(aM, &aM[k], i);
/* Return the result */
fossil_free(pToFree);
return aM;
}
/*
** Given a diff context in which the aEdit[] array has been filled
** in, compute a side-by-side diff into pOut.
*/
static void sbsDiff(
DContext *p, /* The computed diff */
Blob *pOut, /* Write the results here */
int nContext, /* Number of lines of context around each change */
int width, /* Width of each column of output */
int escHtml /* True to generate HTML output */
){
DLine *A; /* Left side of the diff */
DLine *B; /* Right side of the diff */
int a = 0; /* Index of next line in A[] */
int b = 0; /* Index of next line in B[] */
int *R; /* Array of COPY/DELETE/INSERT triples */
int r; /* Index into R[] */
int nr; /* Number of COPY/DELETE/INSERT triples to process */
int mxr; /* Maximum value for r */
int na, nb; /* Number of lines shown from A and B */
int i, j; /* Loop counters */
int m, ma, mb;/* Number of lines to output */
int skip; /* Number of lines to skip */
int nChunk = 0; /* Number of chunks of diff output seen so far */
SbsLine s; /* Output line buffer */
s.zLine = fossil_malloc( 10*width + 100 );
if( s.zLine==0 ) return;
s.width = width;
s.escHtml = escHtml;
s.iStart = -1;
s.iEnd = -1;
A = p->aFrom;
B = p->aTo;
R = p->aEdit;
mxr = p->nEdit;
while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; }
for(r=0; r<mxr; r += 3*nr){
/* Figure out how many triples to show in a single block */
|
| ︙ | ︙ | |||
233 234 235 236 237 238 239 |
na += R[r+nr*3];
nb += R[r+nr*3];
}
for(i=1; i<nr; i++){
na += R[r+i*3];
nb += R[r+i*3];
}
| | | | | < | | > | > > > > > > > > > | > > > > > | > > | > > > > > > > > | > > > > > > > > > > > > | | > > > | < > > > | > > > | < > > > > > > > > | > > > > > > > | > > | 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 |
na += R[r+nr*3];
nb += R[r+nr*3];
}
for(i=1; i<nr; i++){
na += R[r+i*3];
nb += R[r+i*3];
}
/* Draw the separator between blocks */
if( r>0 ){
if( escHtml ){
blob_appendf(pOut, "<span class=\"diffhr\">%.*c</span>\n",
width*2+16, '.');
}else{
blob_appendf(pOut, "%.*c\n", width*2+16, '.');
}
}
nChunk++;
if( escHtml ){
blob_appendf(pOut, "<a name=\"chunk%d\"></a>\n", nChunk);
}
/* Show the initial common area */
a += skip;
b += skip;
m = R[r] - skip;
for(j=0; j<m; j++){
s.n = 0;
sbsWriteLineno(&s, a+j);
s.iStart = s.iEnd = -1;
sbsWriteText(&s, &A[a+j], SBS_PAD);
sbsWrite(&s, " ", 3);
sbsWriteLineno(&s, b+j);
sbsWriteText(&s, &B[b+j], SBS_NEWLINE);
blob_append(pOut, s.zLine, s.n);
}
a += m;
b += m;
/* Show the differences */
for(i=0; i<nr; i++){
unsigned char *alignment;
ma = R[r+i*3+1]; /* Lines on left but not on right */
mb = R[r+i*3+2]; /* Lines on right but not on left */
alignment = sbsAlignment(&A[a], ma, &B[b], mb);
for(j=0; ma+mb>0; j++){
if( alignment[j]==1 ){
s.n = 0;
sbsWriteLineno(&s, a);
s.iStart = 0;
s.zStart = "<span class=\"diffrm\">";
s.iEnd = s.width;
sbsWriteText(&s, &A[a], SBS_PAD);
sbsWrite(&s, " <\n", 3);
blob_append(pOut, s.zLine, s.n);
assert( ma>0 );
ma--;
a++;
}else if( alignment[j]==2 ){
s.n = 0;
sbsWriteLineChange(&s, &A[a], a, &B[b], b);
blob_append(pOut, s.zLine, s.n);
assert( ma>0 && mb>0 );
ma--;
mb--;
a++;
b++;
}else{
s.n = 0;
sbsWriteSpace(&s, width + 7);
sbsWrite(&s, " > ", 3);
sbsWriteLineno(&s, b);
s.iStart = 0;
s.zStart = "<span class=\"diffadd\">";
s.iEnd = s.width;
sbsWriteText(&s, &B[b], SBS_NEWLINE);
blob_append(pOut, s.zLine, s.n);
assert( mb>0 );
mb--;
b++;
}
}
fossil_free(alignment);
if( i<nr-1 ){
m = R[r+i*3+3];
for(j=0; j<m; j++){
s.n = 0;
sbsWriteLineno(&s, a+j);
s.iStart = s.iEnd = -1;
sbsWriteText(&s, &A[a+j], SBS_PAD);
sbsWrite(&s, " ", 3);
sbsWriteLineno(&s, b+j);
sbsWriteText(&s, &B[b+j], SBS_NEWLINE);
blob_append(pOut, s.zLine, s.n);
}
b += m;
a += m;
}
}
/* Show the final common area */
assert( nr==i );
m = R[r+nr*3];
if( m>nContext ) m = nContext;
for(j=0; j<m; j++){
s.n = 0;
sbsWriteLineno(&s, a+j);
s.iStart = s.iEnd = -1;
sbsWriteText(&s, &A[a+j], SBS_PAD);
sbsWrite(&s, " ", 3);
sbsWriteLineno(&s, b+j);
sbsWriteText(&s, &B[b+j], SBS_NEWLINE);
blob_append(pOut, s.zLine, s.n);
}
}
free(s.zLine);
}
/*
** Compute the optimal longest common subsequence (LCS) using an
** exhaustive search. This version of the LCS is only used for
** shorter input strings since runtime is O(N*N) where N is the
** input string length.
|
| ︙ | ︙ | |||
352 353 354 355 356 357 358 |
DContext *p, /* Two files being compared */
int iS1, int iE1, /* Range of lines in p->aFrom[] */
int iS2, int iE2, /* Range of lines in p->aTo[] */
int *piSX, int *piEX, /* Write p->aFrom[] common segment here */
int *piSY, int *piEY /* Write p->aTo[] common segment here */
){
double bestScore = -1e30; /* Best score seen so far */
| > | > | 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 |
DContext *p, /* Two files being compared */
int iS1, int iE1, /* Range of lines in p->aFrom[] */
int iS2, int iE2, /* Range of lines in p->aTo[] */
int *piSX, int *piEX, /* Write p->aFrom[] common segment here */
int *piSY, int *piEY /* Write p->aTo[] common segment here */
){
double bestScore = -1e30; /* Best score seen so far */
int i, j, k; /* Loop counters */
int n; /* Loop limit */
DLine *pA, *pB; /* Pointers to lines */
int iSX, iSY, iEX, iEY; /* Current match */
double score; /* Current score */
int skew; /* How lopsided is the match */
int dist; /* Distance of match from center */
int mid; /* Center of the span */
int iSXb, iSYb, iEXb, iEYb; /* Best match so far */
int iSXp, iSYp, iEXp, iEYp; /* Previous match */
|
| ︙ | ︙ | |||
385 386 387 388 389 390 391 |
}
if( j==0 ) continue;
assert( i>=iSXb && i>=iSXp );
if( i<iEXb && j>=iSYb && j<iEYb ) continue;
if( i<iEXp && j>=iSYp && j<iEYp ) continue;
iSX = i;
iSY = j-1;
| | > > > | | < | > > > | | < | 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 |
}
if( j==0 ) continue;
assert( i>=iSXb && i>=iSXp );
if( i<iEXb && j>=iSYb && j<iEYb ) continue;
if( i<iEXp && j>=iSYp && j<iEYp ) continue;
iSX = i;
iSY = j-1;
pA = &p->aFrom[iSX-1];
pB = &p->aTo[iSY-1];
n = minInt(iSX-iS1, iSY-iS2);
for(k=0; k<n && same_dline(pA,pB); k++, pA--, pB--){}
iSX -= k;
iSY -= k;
iEX = i+1;
iEY = j;
pA = &p->aFrom[iEX];
pB = &p->aTo[iEY];
n = minInt(iE1-iEX, iE2-iEY);
for(k=0; k<n && same_dline(pA,pB); k++, pA++, pB++){}
iEX += k;
iEY += k;
skew = (iSX-iS1) - (iSY-iS2);
if( skew<0 ) skew = -skew;
dist = (iSX+iEX)/2 - mid;
if( dist<0 ) dist = -dist;
score = (iEX - iSX) - 0.05*skew - 0.05*dist;
if( score>bestScore ){
bestScore = score;
|
| ︙ | ︙ | |||
426 427 428 429 430 431 432 433 434 435 436 437 438 439 |
*piSY = iSYb;
*piEX = iEXb;
*piEY = iEYb;
}
/* printf("LCS(%d..%d/%d..%d) = %d..%d/%d..%d\n",
iS1, iE1, iS2, iE2, *piSX, *piEX, *piSY, *piEY); */
}
/*
** Do a single step in the difference. Compute a sequence of
** copy/delete/insert steps that will convert lines iS1 through iE1-1 of
** the input into lines iS2 through iE2-1 of the output and write
** that sequence into the difference context.
**
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 |
*piSY = iSYb;
*piEX = iEXb;
*piEY = iEYb;
}
/* printf("LCS(%d..%d/%d..%d) = %d..%d/%d..%d\n",
iS1, iE1, iS2, iE2, *piSX, *piEX, *piSY, *piEY); */
}
/*
** Expand the size of aEdit[] array to hold at least nEdit elements.
*/
static void expandEdit(DContext *p, int nEdit){
p->aEdit = fossil_realloc(p->aEdit, nEdit*sizeof(int));
p->nEditAlloc = nEdit;
}
/*
** Append a new COPY/DELETE/INSERT triple.
*/
static void appendTriple(DContext *p, int nCopy, int nDel, int nIns){
/* printf("APPEND %d/%d/%d\n", nCopy, nDel, nIns); */
if( p->nEdit>=3 ){
if( p->aEdit[p->nEdit-1]==0 ){
if( p->aEdit[p->nEdit-2]==0 ){
p->aEdit[p->nEdit-3] += nCopy;
p->aEdit[p->nEdit-2] += nDel;
p->aEdit[p->nEdit-1] += nIns;
return;
}
if( nCopy==0 ){
p->aEdit[p->nEdit-2] += nDel;
p->aEdit[p->nEdit-1] += nIns;
return;
}
}
if( nCopy==0 && nDel==0 ){
p->aEdit[p->nEdit-1] += nIns;
return;
}
}
if( p->nEdit+3>p->nEditAlloc ){
expandEdit(p, p->nEdit*2 + 15);
if( p->aEdit==0 ) return;
}
p->aEdit[p->nEdit++] = nCopy;
p->aEdit[p->nEdit++] = nDel;
p->aEdit[p->nEdit++] = nIns;
}
/*
** Do a single step in the difference. Compute a sequence of
** copy/delete/insert steps that will convert lines iS1 through iE1-1 of
** the input into lines iS2 through iE2-1 of the output and write
** that sequence into the difference context.
**
|
| ︙ | ︙ | |||
459 460 461 462 463 464 465 |
return;
}
/* Find the longest matching segment between the two sequences */
longestCommonSequence(p, iS1, iE1, iS2, iE2, &iSX, &iEX, &iSY, &iEY);
if( iEX>iSX ){
| | | 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 |
return;
}
/* Find the longest matching segment between the two sequences */
longestCommonSequence(p, iS1, iE1, iS2, iE2, &iSX, &iEX, &iSY, &iEY);
if( iEX>iSX ){
/* A common segment has been found.
** Recursively diff either side of the matching segment */
diff_step(p, iS1, iSX, iS2, iSY);
if( iEX>iSX ){
appendTriple(p, iEX - iSX, 0, 0);
}
diff_step(p, iEX, iE1, iEY, iE2);
}else{
|
| ︙ | ︙ | |||
518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 |
expandEdit(p, p->nEdit+3);
if( p->aEdit ){
p->aEdit[p->nEdit++] = 0;
p->aEdit[p->nEdit++] = 0;
p->aEdit[p->nEdit++] = 0;
}
}
/*
** Generate a report of the differences between files pA and pB.
** If pOut is not NULL then a unified diff is appended there. It
** is assumed that pOut has already been initialized. If pOut is
** NULL, then a pointer to an array of integers is returned.
** The integers come in triples. For each triple,
** the elements are the number of lines copied, the number of
** lines deleted, and the number of lines inserted. The vector
** is terminated by a triple of all zeros.
**
** This diff utility does not work on binary files. If a binary
** file is encountered, 0 is returned and pOut is written with
** text "cannot compute difference between binary files".
*/
int *text_diff(
Blob *pA_Blob, /* FROM file */
Blob *pB_Blob, /* TO file */
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | < > > | > > > > > > > > > | > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > | | 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 |
expandEdit(p, p->nEdit+3);
if( p->aEdit ){
p->aEdit[p->nEdit++] = 0;
p->aEdit[p->nEdit++] = 0;
p->aEdit[p->nEdit++] = 0;
}
}
/*
** Attempt to shift insertion or deletion blocks so that they begin and
** end on lines that are pure whitespace. In other words, try to transform
** this:
**
** int func1(int x){
** return x*10;
** +}
** +
** +int func2(int x){
** + return x*20;
** }
**
** int func3(int x){
** return x/5;
** }
**
** Into one of these:
**
** int func1(int x){ int func1(int x){
** return x*10; return x*10;
** } }
** +
** +int func2(int x){ +int func2(int x){
** + return x*20; + return x*20;
** +} +}
** +
** int func3(int x){ int func3(int x){
** return x/5; return x/5;
** } }
*/
static void diff_optimize(DContext *p){
int r; /* Index of current triple */
int lnFrom; /* Line number in p->aFrom */
int lnTo; /* Line number in p->aTo */
int cpy, del, ins;
lnFrom = lnTo = 0;
for(r=0; r<p->nEdit; r += 3){
cpy = p->aEdit[r];
del = p->aEdit[r+1];
ins = p->aEdit[r+2];
lnFrom += cpy;
lnTo += cpy;
/* Shift insertions toward the beginning of the file */
while( cpy>0 && del==0 && ins>0 ){
DLine *pTop = &p->aFrom[lnFrom-1]; /* Line before start of insert */
DLine *pBtm = &p->aTo[lnTo+ins-1]; /* Last line inserted */
if( same_dline(pTop, pBtm)==0 ) break;
if( LENGTH(pTop+1)+LENGTH(pBtm)<=LENGTH(pTop)+LENGTH(pBtm-1) ) break;
lnFrom--;
lnTo--;
p->aEdit[r]--;
p->aEdit[r+3]++;
cpy--;
}
/* Shift insertions toward the end of the file */
while( r+3<p->nEdit && p->aEdit[r+3]>0 && del==0 && ins>0 ){
DLine *pTop = &p->aTo[lnTo]; /* First line inserted */
DLine *pBtm = &p->aTo[lnTo+ins]; /* First line past end of insert */
if( same_dline(pTop, pBtm)==0 ) break;
if( LENGTH(pTop)+LENGTH(pBtm-1)<=LENGTH(pTop+1)+LENGTH(pBtm) ) break;
lnFrom++;
lnTo++;
p->aEdit[r]++;
p->aEdit[r+3]--;
cpy++;
}
/* Shift deletions toward the beginning of the file */
while( cpy>0 && del>0 && ins==0 ){
DLine *pTop = &p->aFrom[lnFrom-1]; /* Line before start of delete */
DLine *pBtm = &p->aFrom[lnFrom+del-1]; /* Last line deleted */
if( same_dline(pTop, pBtm)==0 ) break;
if( LENGTH(pTop+1)+LENGTH(pBtm)<=LENGTH(pTop)+LENGTH(pBtm-1) ) break;
lnFrom--;
lnTo--;
p->aEdit[r]--;
p->aEdit[r+3]++;
cpy--;
}
/* Shift deletions toward the end of the file */
while( r+3<p->nEdit && p->aEdit[r+3]>0 && del>0 && ins==0 ){
DLine *pTop = &p->aFrom[lnFrom]; /* First line deleted */
DLine *pBtm = &p->aFrom[lnFrom+del]; /* First line past end of delete */
if( same_dline(pTop, pBtm)==0 ) break;
if( LENGTH(pTop)+LENGTH(pBtm-1)<=LENGTH(pTop)+LENGTH(pBtm) ) break;
lnFrom++;
lnTo++;
p->aEdit[r]++;
p->aEdit[r+3]--;
cpy++;
}
lnFrom += del;
lnTo += ins;
}
}
/*
** Extract the number of lines of context from diffFlags. Supply an
** appropriate default if no context width is specified.
*/
int diff_context_lines(int diffFlags){
int n = diffFlags & DIFF_CONTEXT_MASK;
if( n==0 ) n = 5;
return n;
}
/*
** Extract the width of columns for side-by-side diff. Supply an
** appropriate default if no width is given.
*/
int diff_width(int diffFlags){
int w = (diffFlags & DIFF_WIDTH_MASK)/(DIFF_CONTEXT_MASK+1);
if( w==0 ) w = 80;
return w;
}
/*
** Generate a report of the differences between files pA and pB.
** If pOut is not NULL then a unified diff is appended there. It
** is assumed that pOut has already been initialized. If pOut is
** NULL, then a pointer to an array of integers is returned.
** The integers come in triples. For each triple,
** the elements are the number of lines copied, the number of
** lines deleted, and the number of lines inserted. The vector
** is terminated by a triple of all zeros.
**
** This diff utility does not work on binary files. If a binary
** file is encountered, 0 is returned and pOut is written with
** text "cannot compute difference between binary files".
*/
int *text_diff(
Blob *pA_Blob, /* FROM file */
Blob *pB_Blob, /* TO file */
Blob *pOut, /* Write diff here if not NULL */
int diffFlags /* DIFF_* flags defined above */
){
int ignoreEolWs; /* Ignore whitespace at the end of lines */
int nContext; /* Amount of context to display */
DContext c;
if( diffFlags & DIFF_INVERT ){
Blob *pTemp = pA_Blob;
pA_Blob = pB_Blob;
pB_Blob = pTemp;
}
nContext = diff_context_lines(diffFlags);
ignoreEolWs = (diffFlags & DIFF_IGNORE_EOLWS)!=0;
/* 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);
if( pOut ){
blob_appendf(pOut, "cannot compute difference between binary files\n");
}
return 0;
}
/* Compute the difference */
diff_all(&c);
if( (diffFlags & DIFF_NOOPT)==0 ) diff_optimize(&c);
if( pOut ){
/* Compute a context or side-by-side diff into pOut */
int escHtml = (diffFlags & DIFF_HTML)!=0;
if( diffFlags & DIFF_SIDEBYSIDE ){
int width = diff_width(diffFlags);
sbsDiff(&c, pOut, nContext, width, escHtml);
}else{
int showLn = (diffFlags & DIFF_LINENO)!=0;
contextDiff(&c, pOut, nContext, showLn, escHtml);
}
free(c.aFrom);
free(c.aTo);
free(c.aEdit);
return 0;
}else{
/* If a context diff is not requested, then return the
** array of COPY/DELETE/INSERT triples.
*/
free(c.aFrom);
free(c.aTo);
return c.aEdit;
}
}
/*
** Process diff-related command-line options and return an appropriate
** "diffFlags" integer.
**
** --brief Show filenames only DIFF_BRIEF
** --context|-c N N lines of context. DIFF_CONTEXT_MASK
** --html Format for HTML DIFF_HTML
** --invert Invert the diff DIFF_INVERT
** --linenum|-n Show line numbers DIFF_LINENO
** --noopt Disable optimization DIFF_NOOPT
** --side-by-side|-y Side-by-side diff. DIFF_SIDEBYSIDE
** --width|-W N N character lines. DIFF_WIDTH_MASK
*/
int diff_options(void){
int diffFlags = 0;
const char *z;
int f;
if( find_option("side-by-side","y",0)!=0 ) diffFlags |= DIFF_SIDEBYSIDE;
if( (z = find_option("context","c",1))!=0 && (f = atoi(z))>0 ){
if( f > DIFF_CONTEXT_MASK ) f = DIFF_CONTEXT_MASK;
diffFlags |= f;
}
if( (z = find_option("width","W",1))!=0 && (f = atoi(z))>0 ){
f *= DIFF_CONTEXT_MASK+1;
if( f > DIFF_WIDTH_MASK ) f = DIFF_CONTEXT_MASK;
diffFlags |= f;
}
if( find_option("html",0,0)!=0 ) diffFlags |= DIFF_HTML;
if( find_option("linenum","n",0)!=0 ) diffFlags |= DIFF_LINENO;
if( find_option("noopt",0,0)!=0 ) diffFlags |= DIFF_NOOPT;
if( find_option("invert",0,0)!=0 ) diffFlags |= DIFF_INVERT;
if( find_option("brief",0,0)!=0 ) diffFlags |= DIFF_BRIEF;
return diffFlags;
}
/*
** COMMAND: test-rawdiff
*/
void test_rawdiff_cmd(void){
Blob a, b;
int r;
int i;
int *R;
int diffFlags = diff_options();
if( g.argc<4 ) usage("FILE1 FILE2 ...");
blob_read_from_file(&a, g.argv[2]);
for(i=3; i<g.argc; i++){
if( i>3 ) fossil_print("-------------------------------\n");
blob_read_from_file(&b, g.argv[i]);
R = text_diff(&a, &b, 0, diffFlags);
for(r=0; R[r] || R[r+1] || R[r+2]; r += 3){
fossil_print(" copy %4d delete %4d insert %4d\n", R[r], R[r+1], R[r+2]);
}
/* free(R); */
blob_reset(&b);
}
}
/*
** COMMAND: test-udiff
**
** Print the difference between two files. The usual diff options apply.
*/
void test_udiff_cmd(void){
Blob a, b, out;
int diffFlag = diff_options();
if( g.argc!=4 ) usage("FILE1 FILE2");
blob_read_from_file(&a, g.argv[2]);
blob_read_from_file(&b, g.argv[3]);
blob_zero(&out);
text_diff(&a, &b, &out, diffFlag);
blob_write_to_file(&out, "-");
}
/**************************************************************************
** The basic difference engine is above. What follows is the annotation
** engine. Both are in the same file since they share many components.
*/
|
| ︙ | ︙ | |||
709 710 711 712 713 714 715 | free(p->c.aEdit); p->c.aEdit = 0; p->c.nEdit = 0; p->c.nEditAlloc = 0; /* Clear out the from file */ free(p->c.aFrom); | < | 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 | free(p->c.aEdit); p->c.aEdit = 0; p->c.nEdit = 0; p->c.nEditAlloc = 0; /* Clear out the from file */ free(p->c.aFrom); /* Return no errors */ return 0; } /* |
| ︙ | ︙ | |||
885 886 887 888 889 890 891 892 893 894 895 896 897 898 |
**
** See also: info, finfo, timeline
*/
void annotate_cmd(void){
int fnid; /* Filename ID */
int fid; /* File instance ID */
int mid; /* Manifest where file was checked in */
Blob treename; /* FILENAME translated to canonical form */
char *zFilename; /* Cannonical filename */
Annotator ann; /* The annotation of the file */
int i; /* Loop counter */
const char *zLimit; /* The value to the --limit option */
int iLimit; /* How far back in time to look */
int showLog; /* True to show the log */
| > | 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 |
**
** See also: info, finfo, timeline
*/
void annotate_cmd(void){
int fnid; /* Filename ID */
int fid; /* File instance ID */
int mid; /* Manifest where file was checked in */
int cid; /* Checkout ID */
Blob treename; /* FILENAME translated to canonical form */
char *zFilename; /* Cannonical filename */
Annotator ann; /* The annotation of the file */
int i; /* Loop counter */
const char *zLimit; /* The value to the --limit option */
int iLimit; /* How far back in time to look */
int showLog; /* True to show the log */
|
| ︙ | ︙ | |||
914 915 916 917 918 919 920 |
if( fnid==0 ){
fossil_fatal("no such file: %s", zFilename);
}
fid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q", zFilename);
if( fid==0 ){
fossil_fatal("not part of current checkout: %s", zFilename);
}
| > > > > > > | > > > | 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 |
if( fnid==0 ){
fossil_fatal("no such file: %s", zFilename);
}
fid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q", zFilename);
if( fid==0 ){
fossil_fatal("not part of current checkout: %s", zFilename);
}
cid = db_lget_int("checkout", 0);
if (cid == 0){
fossil_fatal("Not in a checkout");
}
if( iLimit<=0 ) iLimit = 1000000000;
compute_direct_ancestors(cid, iLimit);
mid = db_int(0, "SELECT mlink.mid FROM mlink, ancestor "
" WHERE mlink.fid=%d AND mlink.fnid=%d AND mlink.mid=ancestor.rid"
" ORDER BY ancestor.generation ASC LIMIT 1",
fid, fnid);
if( mid==0 ){
fossil_panic("unable to find manifest");
}
if( fileVers ) annFlags |= ANN_FILE_VERS;
annotate_file(&ann, fnid, mid, 0, iLimit, annFlags);
if( showLog ){
for(i=0; i<ann.nVers; i++){
|
| ︙ | ︙ |
Changes to src/diffcmd.c.
| ︙ | ︙ | |||
18 19 20 21 22 23 24 | ** This file contains code used to implement the "diff" command */ #include "config.h" #include "diffcmd.h" #include <assert.h> /* | < < < < | < < < | < < | | < | > < | | > > > > > > > > > | > > | > > > | > > > > > | | | | | > > < | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
** This file contains code used to implement the "diff" command
*/
#include "config.h"
#include "diffcmd.h"
#include <assert.h>
/*
** Print the "Index:" message that patches wants to see at the top of a diff.
*/
void diff_print_index(const char *zFile, int diffFlags){
if( (diffFlags & (DIFF_SIDEBYSIDE|DIFF_BRIEF))==0 ){
char *z = mprintf("Index: %s\n%.66c\n", zFile, '=');
fossil_print("%s", z);
fossil_free(z);
}
}
/*
** Print the +++/--- filename lines for a diff operation.
*/
void diff_print_filenames(const char *zLeft, const char *zRight, int diffFlags){
char *z = 0;
if( diffFlags & DIFF_BRIEF ){
/* no-op */
}else if( diffFlags & DIFF_SIDEBYSIDE ){
int w = diff_width(diffFlags);
int n1 = strlen(zLeft);
int x;
if( n1>w*2 ) n1 = w*2;
x = w*2+17 - (n1+2);
z = mprintf("%.*c %.*s %.*c\n",
x/2, '=', n1, zLeft, (x+1)/2, '=');
}else{
z = mprintf("--- %s\n+++ %s\n", zLeft, zRight);
}
fossil_print("%s", z);
fossil_free(z);
}
/*
** Show the difference between two files, one in memory and one on disk.
**
** The difference is the set of edits needed to transform pFile1 into
** zFile2. The content of pFile1 is in memory. zFile2 exists on disk.
**
** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the
** command zDiffCmd to do the diffing.
*/
void diff_file(
Blob *pFile1, /* In memory content to compare from */
const char *zFile2, /* On disk content to compare to */
const char *zName, /* Display name of the file */
const char *zDiffCmd, /* Command for comparison */
int diffFlags /* Flags to control the diff */
){
if( zDiffCmd==0 ){
Blob out; /* Diff output text */
Blob file2; /* Content of zFile2 */
const char *zName2; /* Name of zFile2 for display */
/* Read content of zFile2 into memory */
blob_zero(&file2);
if( file_wd_size(zFile2)<0 ){
zName2 = "/dev/null";
}else{
if( file_wd_islink(zFile2) ){
blob_read_link(&file2, zFile2);
}else{
blob_read_from_file(&file2, zFile2);
}
zName2 = zName;
}
/* Compute and output the differences */
if( diffFlags & DIFF_BRIEF ){
if( blob_compare(pFile1, &file2) ){
fossil_print("CHANGED %s\n", zName);
}
}else{
blob_zero(&out);
text_diff(pFile1, &file2, &out, diffFlags);
if( blob_size(&out) ){
diff_print_filenames(zName, zName2, diffFlags);
fossil_print("%s\n", blob_str(&out));
}
blob_reset(&out);
}
/* Release memory resources */
blob_reset(&file2);
}else{
int cnt = 0;
Blob nameFile1; /* Name of temporary file to old pFile1 content */
Blob cmd; /* Text of command to run */
/* Construct a temporary file to hold pFile1 based on the name of
** zFile2 */
|
| ︙ | ︙ | |||
136 137 138 139 140 141 142 | ** command zDiffCmd to do the diffing. */ void diff_file_mem( Blob *pFile1, /* In memory content to compare from */ Blob *pFile2, /* In memory content to compare to */ const char *zName, /* Display name of the file */ const char *zDiffCmd, /* Command for comparison */ | | > | | | | 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
** command zDiffCmd to do the diffing.
*/
void diff_file_mem(
Blob *pFile1, /* In memory content to compare from */
Blob *pFile2, /* In memory content to compare to */
const char *zName, /* Display name of the file */
const char *zDiffCmd, /* Command for comparison */
int diffFlags /* Diff flags */
){
if( diffFlags & DIFF_BRIEF ) return;
if( zDiffCmd==0 ){
Blob out; /* Diff output text */
blob_zero(&out);
text_diff(pFile1, pFile2, &out, diffFlags);
diff_print_filenames(zName, zName, diffFlags);
fossil_print("%s\n", blob_str(&out));
/* Release memory resources */
blob_reset(&out);
}else{
Blob cmd;
char zTemp1[300];
char zTemp2[300];
|
| ︙ | ︙ | |||
183 184 185 186 187 188 189 | /* ** Do a diff against a single file named in zFileTreeName from version zFrom ** against the same file on disk. */ static void diff_one_against_disk( const char *zFrom, /* Name of file */ const char *zDiffCmd, /* Use this "diff" command */ | | | > | < < | 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
/*
** Do a diff against a single file named in zFileTreeName from version zFrom
** against the same file on disk.
*/
static void diff_one_against_disk(
const char *zFrom, /* Name of file */
const char *zDiffCmd, /* Use this "diff" command */
int diffFlags, /* Diff control flags */
const char *zFileTreeName
){
Blob fname;
Blob content;
int isLink;
file_tree_name(zFileTreeName, &fname, 1);
historical_version_of_file(zFrom, blob_str(&fname), &content, &isLink, 0, 0);
if( !isLink != !file_wd_islink(zFrom) ){
fossil_print("cannot compute difference between "
"symlink and regular file\n");
}else{
diff_file(&content, zFileTreeName, zFileTreeName, zDiffCmd, diffFlags);
}
blob_reset(&content);
blob_reset(&fname);
}
/*
** Run a diff between the version zFrom and files on disk. zFrom might
** be NULL which means to simply show the difference between the edited
** files on disk and the check-out on which they are based.
*/
static void diff_all_against_disk(
const char *zFrom, /* Version to difference from */
const char *zDiffCmd, /* Use this diff command. NULL for built-in */
int diffFlags /* Flags controlling diff output */
){
int vid;
Blob sql;
Stmt q;
int asNewFile; /* Treat non-existant files as empty files */
asNewFile = (diffFlags & DIFF_NEWFILE)!=0;
vid = db_lget_int("checkout", 0);
vfile_check_signature(vid, 1, 0);
blob_zero(&sql);
db_begin_transaction();
if( zFrom ){
int rid = name_to_typed_rid(zFrom, "ci");
|
| ︙ | ︙ | |||
270 271 272 273 274 275 276 |
int isNew = db_column_int(&q,3);
int srcid = db_column_int(&q, 4);
int isLink = db_column_int(&q, 5);
char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname);
char *zToFree = zFullName;
int showDiff = 1;
if( isDeleted ){
| | | | | | | | > | | | > | > | | | > | | < | | | | > | > | > | 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 |
int isNew = db_column_int(&q,3);
int srcid = db_column_int(&q, 4);
int isLink = db_column_int(&q, 5);
char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname);
char *zToFree = zFullName;
int showDiff = 1;
if( isDeleted ){
fossil_print("DELETED %s\n", zPathname);
if( !asNewFile ){ showDiff = 0; zFullName = "/dev/null"; }
}else if( file_access(zFullName, 0) ){
fossil_print("MISSING %s\n", zPathname);
if( !asNewFile ){ showDiff = 0; }
}else if( isNew ){
fossil_print("ADDED %s\n", zPathname);
srcid = 0;
if( !asNewFile ){ showDiff = 0; }
}else if( isChnged==3 ){
fossil_print("ADDED_BY_MERGE %s\n", zPathname);
srcid = 0;
if( !asNewFile ){ showDiff = 0; }
}
if( showDiff ){
Blob content;
if( !isLink != !file_wd_islink(zFullName) ){
diff_print_index(zPathname, diffFlags);
diff_print_filenames(zPathname, zPathname, diffFlags);
fossil_print("cannot compute difference between "
"symlink and regular file\n");
continue;
}
if( srcid>0 ){
content_get(srcid, &content);
}else{
blob_zero(&content);
}
diff_print_index(zPathname, diffFlags);
diff_file(&content, zFullName, zPathname, zDiffCmd, diffFlags);
blob_reset(&content);
}
free(zToFree);
}
db_finalize(&q);
db_end_transaction(1); /* ROLLBACK */
}
/*
** Output the differences between two versions of a single file.
** zFrom and zTo are the check-ins containing the two file versions.
*/
static void diff_one_two_versions(
const char *zFrom,
const char *zTo,
const char *zDiffCmd,
int diffFlags,
const char *zFileTreeName
){
char *zName;
Blob fname;
Blob v1, v2;
int isLink1, isLink2;
if( diffFlags & DIFF_BRIEF ) return;
file_tree_name(zFileTreeName, &fname, 1);
zName = blob_str(&fname);
historical_version_of_file(zFrom, zName, &v1, &isLink1, 0, 0);
historical_version_of_file(zTo, zName, &v2, &isLink2, 0, 0);
if( isLink1 != isLink2 ){
diff_print_filenames(zName, zName, diffFlags);
fossil_print("cannot compute difference "
" between symlink and regular file\n");
}else{
diff_file_mem(&v1, &v2, zName, zDiffCmd, diffFlags);
}
blob_reset(&v1);
blob_reset(&v2);
blob_reset(&fname);
}
/*
** Show the difference between two files identified by ManifestFile
** entries.
*/
static void diff_manifest_entry(
struct ManifestFile *pFrom,
struct ManifestFile *pTo,
const char *zDiffCmd,
int diffFlags
){
Blob f1, f2;
int rid;
const char *zName = pFrom ? pFrom->zName : pTo->zName;
if( diffFlags & DIFF_BRIEF ) return;
diff_print_index(zName, diffFlags);
if( pFrom ){
rid = uuid_to_rid(pFrom->zUuid, 0);
content_get(rid, &f1);
}else{
blob_zero(&f1);
}
if( pTo ){
rid = uuid_to_rid(pTo->zUuid, 0);
content_get(rid, &f2);
}else{
blob_zero(&f2);
}
diff_file_mem(&f1, &f2, zName, zDiffCmd, diffFlags);
blob_reset(&f1);
blob_reset(&f2);
}
/*
** Output the differences between two check-ins.
*/
static void diff_all_two_versions(
const char *zFrom,
const char *zTo,
const char *zDiffCmd,
int diffFlags
){
Manifest *pFrom, *pTo;
ManifestFile *pFromFile, *pToFile;
int asNewFlag = (diffFlags & DIFF_NEWFILE)!=0 ? 1 : 0;
pFrom = manifest_get_by_name(zFrom, 0);
manifest_file_rewind(pFrom);
pFromFile = manifest_file_next(pFrom,0);
pTo = manifest_get_by_name(zTo, 0);
manifest_file_rewind(pTo);
pToFile = manifest_file_next(pTo,0);
while( pFromFile || pToFile ){
int cmp;
if( pFromFile==0 ){
cmp = +1;
}else if( pToFile==0 ){
cmp = -1;
}else{
cmp = fossil_strcmp(pFromFile->zName, pToFile->zName);
}
if( cmp<0 ){
fossil_print("DELETED %s\n", pFromFile->zName);
if( asNewFlag ){
diff_manifest_entry(pFromFile, 0, zDiffCmd, diffFlags);
}
pFromFile = manifest_file_next(pFrom,0);
}else if( cmp>0 ){
fossil_print("ADDED %s\n", pToFile->zName);
if( asNewFlag ){
diff_manifest_entry(0, pToFile, zDiffCmd, diffFlags);
}
pToFile = manifest_file_next(pTo,0);
}else if( fossil_strcmp(pFromFile->zUuid, pToFile->zUuid)==0 ){
/* No changes */
pFromFile = manifest_file_next(pFrom,0);
pToFile = manifest_file_next(pTo,0);
}else{
if( diffFlags & DIFF_BRIEF ){
fossil_print("CHANGED %s\n", pFromFile->zName);
}else{
diff_manifest_entry(pFromFile, pToFile, zDiffCmd, diffFlags);
}
pFromFile = manifest_file_next(pFrom,0);
pToFile = manifest_file_next(pTo,0);
}
}
manifest_destroy(pFrom);
manifest_destroy(pTo);
}
|
| ︙ | ︙ | |||
454 455 456 457 458 459 460 461 | ** the "setting" command. If no external diff program is configured, then ** the "-i" option is a no-op. The "-i" option converts "gdiff" into "diff". ** ** The "-N" or "--new-file" option causes the complete text of added or ** deleted files to be displayed. ** ** Options: ** --from|-r VERSION select VERSION as source for the diff | > > < > > > > | < | | | 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 |
** the "setting" command. If no external diff program is configured, then
** the "-i" option is a no-op. The "-i" option converts "gdiff" into "diff".
**
** The "-N" or "--new-file" option causes the complete text of added or
** deleted files to be displayed.
**
** Options:
** --brief Show filenames only
** --context|-c N Use N lines of context
** --from|-r VERSION select VERSION as source for the diff
** -i use internal diff logic
** --new-file|-N output complete text of added or deleted files
** --to VERSION select VERSION as target for the diff
** --side-by-side|-y side-by-side diff
** --width|-W N Width of lines in side-by-side diff
*/
void diff_cmd(void){
int isGDiff; /* True for gdiff. False for normal diff */
int isInternDiff; /* True for internal diff */
int hasNFlag; /* True if -N or --new-file flag is used */
const char *zFrom; /* Source version number */
const char *zTo; /* Target version number */
const char *zDiffCmd = 0; /* External diff command. NULL for internal diff */
int diffFlags = 0; /* Flags to control the DIFF */
int f;
isGDiff = g.argv[1][0]=='g';
isInternDiff = find_option("internal","i",0)!=0;
zFrom = find_option("from", "r", 1);
zTo = find_option("to", 0, 1);
diffFlags = diff_options();
hasNFlag = find_option("new-file","N",0)!=0;
if( hasNFlag ) diffFlags |= DIFF_NEWFILE;
if( zTo==0 ){
db_must_be_within_tree();
verify_all_options();
if( !isInternDiff ){
zDiffCmd = db_get(isGDiff ? "gdiff-command" : "diff-command", 0);
}
if( g.argc>=3 ){
for(f=2; f<g.argc; ++f){
diff_one_against_disk(zFrom, zDiffCmd, diffFlags, g.argv[f]);
}
}else{
diff_all_against_disk(zFrom, zDiffCmd, diffFlags);
}
}else if( zFrom==0 ){
fossil_fatal("must use --from if --to is present");
}else{
db_find_and_open_repository(0, 0);
verify_all_options();
if( !isInternDiff ){
zDiffCmd = db_get(isGDiff ? "gdiff-command" : "diff-command", 0);
}
if( g.argc>=3 ){
for(f=2; f<g.argc; ++f){
diff_one_two_versions(zFrom, zTo, zDiffCmd, diffFlags, g.argv[f]);
}
}else{
diff_all_two_versions(zFrom, zTo, zDiffCmd, diffFlags);
}
}
}
|
| ︙ | ︙ |
Changes to src/doc.c.
| ︙ | ︙ | |||
365 366 367 368 369 370 371 372 373 374 375 376 377 378 |
char zBaseline[UUID_SIZE+1]; /* Baseline UUID */
login_check_credentials();
if( !g.perm.Read ){ login_needed(); return; }
zName = PD("name", "tip/index.wiki");
for(i=0; zName[i] && zName[i]!='/'; i++){}
if( zName[i]==0 || i>UUID_SIZE ){
goto doc_not_found;
}
memcpy(zBaseline, zName, i);
zBaseline[i] = 0;
zName += i;
while( zName[0]=='/' ){ zName++; }
if( !file_is_simple_pathname(zName) ){
| > > > > > | > > > > | 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 |
char zBaseline[UUID_SIZE+1]; /* Baseline UUID */
login_check_credentials();
if( !g.perm.Read ){ login_needed(); return; }
zName = PD("name", "tip/index.wiki");
for(i=0; zName[i] && zName[i]!='/'; i++){}
if( zName[i]==0 || i>UUID_SIZE ){
zName = "index.html";
goto doc_not_found;
}
memcpy(zBaseline, zName, i);
zBaseline[i] = 0;
zName += i;
while( zName[0]=='/' ){ zName++; }
if( !file_is_simple_pathname(zName) ){
int n = strlen(zName);
if( n>0 && zName[n-1]=='/' ){
zName = mprintf("%sindex.html", zName);
if( !file_is_simple_pathname(zName) ){
goto doc_not_found;
}
}else{
goto doc_not_found;
}
}
if( fossil_strcmp(zBaseline,"ckout")==0 && db_open_local()==0 ){
sqlite3_snprintf(sizeof(zBaseline), zBaseline, "tip");
}
if( fossil_strcmp(zBaseline,"ckout")==0 ){
/* Read from the local checkout */
char *zFullpath;
|
| ︙ | ︙ | |||
406 407 408 409 410 411 412 413 414 415 416 417 418 419 |
"CREATE TABLE IF NOT EXISTS vcache(\n"
" vid INTEGER, -- baseline ID\n"
" fname TEXT, -- filename\n"
" rid INTEGER, -- artifact ID\n"
" UNIQUE(vid,fname,rid)\n"
")"
);
/* Check to see if the documentation file artifact ID is contained
** in the baseline cache */
rid = db_int(0, "SELECT rid FROM vcache"
" WHERE vid=%d AND fname=%Q", vid, zName);
if( rid==0 && db_exists("SELECT 1 FROM vcache WHERE vid=%d", vid) ){
goto doc_not_found;
| > > | 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 |
"CREATE TABLE IF NOT EXISTS vcache(\n"
" vid INTEGER, -- baseline ID\n"
" fname TEXT, -- filename\n"
" rid INTEGER, -- artifact ID\n"
" UNIQUE(vid,fname,rid)\n"
")"
);
/* Check to see if the documentation file artifact ID is contained
** in the baseline cache */
rid = db_int(0, "SELECT rid FROM vcache"
" WHERE vid=%d AND fname=%Q", vid, zName);
if( rid==0 && db_exists("SELECT 1 FROM vcache WHERE vid=%d", vid) ){
goto doc_not_found;
|
| ︙ | ︙ | |||
497 498 499 500 501 502 503 |
}
return;
doc_not_found:
/* Jump here when unable to locate the document */
db_end_transaction(0);
style_header("Document Not Found");
| | | 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 |
}
return;
doc_not_found:
/* Jump here when unable to locate the document */
db_end_transaction(0);
style_header("Document Not Found");
@ <p>No such document: %h(zName)</p>
style_footer();
return;
}
/*
** The default logo.
*/
|
| ︙ | ︙ |
Changes to src/file.c.
| ︙ | ︙ | |||
32 33 34 35 36 37 38 | /* ** 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. */ | | | 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | /* ** 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__) || defined(_MSC_VER)) # define stat _stati64 #endif /* ** On Windows S_ISLNK always returns FALSE. */ #if defined(_WIN32) # define S_ISLNK(x) (0) |
| ︙ | ︙ | |||
392 393 394 395 396 397 398 |
** Create the directory named in the argument, if it does not already
** exist. If forceFlag is 1, delete any prior non-directory object
** with the same name.
**
** Return the number of errors.
*/
int file_mkdir(const char *zName, int forceFlag){
| | | 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 |
** Create the directory named in the argument, if it does not already
** exist. If forceFlag is 1, delete any prior non-directory object
** with the same name.
**
** Return the number of errors.
*/
int file_mkdir(const char *zName, int forceFlag){
int rc = file_wd_isdir(zName);
if( rc==2 ){
if( !forceFlag ) return 1;
file_delete(zName);
}
if( rc!=1 ){
#if defined(_WIN32)
int rc;
|
| ︙ | ︙ | |||
579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 |
}else{
fossil_fatal("cannot find current working directory; %s",
strerror(errno));
}
}
#endif
}
/*
** Compute a canonical pathname for a file or directory.
** Make the name absolute if it is relative.
** Remove redundant / characters
** Remove all /./ path elements.
** Convert /A/../ to just /
*/
void file_canonical_name(const char *zOrigName, Blob *pOut){
| > > > > > > > > > > > > > > > > > > | < < < < < < | 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 |
}else{
fossil_fatal("cannot find current working directory; %s",
strerror(errno));
}
}
#endif
}
/*
** Return true if zPath is an absolute pathname. Return false
** if it is relative.
*/
int file_is_absolute_path(const char *zPath){
if( zPath[0]=='/'
#if defined(_WIN32)
|| zPath[0]=='\\'
|| (strlen(zPath)>3 && zPath[1]==':'
&& (zPath[2]=='\\' || zPath[2]=='/'))
#endif
){
return 1;
}else{
return 0;
}
}
/*
** Compute a canonical pathname for a file or directory.
** Make the name absolute if it is relative.
** Remove redundant / characters
** Remove all /./ path elements.
** Convert /A/../ to just /
*/
void file_canonical_name(const char *zOrigName, Blob *pOut){
if( file_is_absolute_path(zOrigName) ){
blob_set(pOut, zOrigName);
blob_materialize(pOut);
}else{
char zPwd[2000];
file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName));
blob_zero(pOut);
blob_appendf(pOut, "%//%/", zPwd, zOrigName);
|
| ︙ | ︙ | |||
897 898 899 900 901 902 903 |
** different in any way, then return false.
*/
int file_is_the_same(Blob *pContent, const char *zName){
i64 iSize;
int rc;
Blob onDisk;
| | | 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 |
** different in any way, then return false.
*/
int file_is_the_same(Blob *pContent, const char *zName){
i64 iSize;
int rc;
Blob onDisk;
iSize = file_wd_size(zName);
if( iSize<0 ) return 0;
if( iSize!=blob_size(pContent) ) return 0;
if( file_wd_islink(zName) ){
blob_read_link(&onDisk, zName);
}else{
blob_read_from_file(&onDisk, zName);
}
|
| ︙ | ︙ | |||
995 996 997 998 999 1000 1001 |
/*
** Translate MBCS to UTF8. Return a pointer. Call fossil_mbcs_free()
** to deallocate any memory used to store the returned pointer when done.
*/
void fossil_mbcs_free(char *zOld){
#ifdef _WIN32
| > | | 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 |
/*
** Translate MBCS to UTF8. Return a pointer. Call fossil_mbcs_free()
** to deallocate any memory used to store the returned pointer when done.
*/
void fossil_mbcs_free(char *zOld){
#ifdef _WIN32
extern void sqlite3_free(void*);
sqlite3_free(zOld);
#else
/* No-op on unix */
#endif
}
/*
** Like fopen() but always takes a UTF8 argument.
|
| ︙ | ︙ |
Changes to src/finfo.c.
| ︙ | ︙ | |||
37 38 39 40 41 42 43 | ** a quick status and does not check for up-to-date-ness of the file. ** ** In the -p form, there's an optional flag "-r|--revision REVISION". ** The specified version (or the latest checked out version) is printed ** to stdout. ** ** Options: | | | | | | | | | > > > | > | | | 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
** a quick status and does not check for up-to-date-ness of the file.
**
** In the -p form, there's an optional flag "-r|--revision REVISION".
** The specified version (or the latest checked out version) is printed
** to stdout.
**
** Options:
** --brief|-b display a brief (one line / revision) summary
** --limit N display the first N changes
** --log|-l select log mode (the default)
** --offset P skip P changes
** -p select print mode
** --revision|-r R print the given revision (or ckout, if none is given)
** to stdout (only in print mode)
** -s select status mode (print a status indicator for FILE)
** --case-sensitive B Enable or disable case-sensitive filenames. B is a
** boolean: "yes", "no", "true", "false", etc.
**
** See also: artifact, descendants, info, leaves
*/
void finfo_cmd(void){
capture_case_sensitive_option();
db_must_be_within_tree();
if (find_option("status","s",0)) {
Stmt q;
Blob line;
Blob fname;
int vid;
if( g.argc!=3 ) usage("-s|--status FILENAME");
vid = db_lget_int("checkout", 0);
if( vid==0 ){
fossil_panic("no checkout to finfo files in");
}
vfile_check_signature(vid, 1, 0);
file_tree_name(g.argv[2], &fname, 1);
db_prepare(&q,
"SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0)"
" FROM vfile WHERE vfile.pathname=%B %s",
&fname, filename_collation());
blob_zero(&line);
if ( db_step(&q)==SQLITE_ROW ) {
Blob uuid;
int isDeleted = db_column_int(&q, 1);
int isNew = db_column_int(&q,2) == 0;
int chnged = db_column_int(&q,3);
int renamed = db_column_int(&q,4);
blob_zero(&uuid);
db_blob(&uuid,
"SELECT uuid FROM blob, mlink, vfile WHERE "
"blob.rid = mlink.mid AND mlink.fid = vfile.rid AND "
"vfile.pathname=%B %s",
&fname, filename_collation()
);
if( isNew ){
blob_appendf(&line, "new");
}else if( isDeleted ){
blob_appendf(&line, "deleted");
}else if( renamed ){
blob_appendf(&line, "renamed");
|
| ︙ | ︙ | |||
111 112 113 114 115 116 117 |
Blob fname;
const char *zRevision = find_option("revision", "r", 1);
file_tree_name(g.argv[2], &fname, 1);
if( zRevision ){
historical_version_of_file(zRevision, blob_str(&fname), &record, 0, 0, 0);
}else{
| | > | 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
Blob fname;
const char *zRevision = find_option("revision", "r", 1);
file_tree_name(g.argv[2], &fname, 1);
if( zRevision ){
historical_version_of_file(zRevision, blob_str(&fname), &record, 0, 0, 0);
}else{
int rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B %s",
&fname, filename_collation());
if( rid==0 ){
fossil_fatal("no history for file: %b", &fname);
}
content_get(rid, &record);
}
blob_write_to_file(&record, "-");
blob_reset(&record);
|
| ︙ | ︙ | |||
142 143 144 145 146 147 148 |
zOffset = find_option("offset",0,1);
iOffset = zOffset ? atoi(zOffset) : 0;
iBrief = (find_option("brief","b",0) == 0);
if( g.argc!=3 ){
usage("?-l|--log? ?-b|--brief? FILENAME");
}
file_tree_name(g.argv[2], &fname, 1);
| | > | | | 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
zOffset = find_option("offset",0,1);
iOffset = zOffset ? atoi(zOffset) : 0;
iBrief = (find_option("brief","b",0) == 0);
if( g.argc!=3 ){
usage("?-l|--log? ?-b|--brief? FILENAME");
}
file_tree_name(g.argv[2], &fname, 1);
rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B %s",
&fname, filename_collation());
if( rid==0 ){
fossil_fatal("no history for file: %b", &fname);
}
zFilename = blob_str(&fname);
db_prepare(&q,
"SELECT b.uuid, ci.uuid, date(event.mtime,'localtime'),"
" coalesce(event.ecomment, event.comment),"
" coalesce(event.euser, event.user)"
" FROM mlink, blob b, event, blob ci, filename"
" WHERE filename.name=%Q %s"
" AND mlink.fnid=filename.fnid"
" AND b.rid=mlink.fid"
" AND event.objid=mlink.mid"
" AND event.objid=ci.rid"
" ORDER BY event.mtime DESC LIMIT %d OFFSET %d",
zFilename, filename_collation(), iLimit, iOffset
);
blob_zero(&line);
if( iBrief ){
fossil_print("History of %s\n", blob_str(&fname));
}
while( db_step(&q)==SQLITE_ROW ){
const char *zFileUuid = db_column_text(&q, 0);
|
| ︙ | ︙ | |||
241 242 243 244 245 246 247 |
" (SELECT uuid FROM blob WHERE rid=mlink.pid)," /* Parent file uuid */
" (SELECT uuid FROM blob WHERE rid=mlink.fid)," /* Current file uuid */
" (SELECT uuid FROM blob WHERE rid=mlink.mid)," /* Check-in uuid */
" event.bgcolor," /* Background color */
" (SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0"
" AND tagxref.rid=mlink.mid)" /* Tags */
" FROM mlink, event"
| | | | 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 |
" (SELECT uuid FROM blob WHERE rid=mlink.pid)," /* Parent file uuid */
" (SELECT uuid FROM blob WHERE rid=mlink.fid)," /* Current file uuid */
" (SELECT uuid FROM blob WHERE rid=mlink.mid)," /* Check-in uuid */
" event.bgcolor," /* Background color */
" (SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0"
" AND tagxref.rid=mlink.mid)" /* Tags */
" FROM mlink, event"
" WHERE mlink.fnid IN (SELECT fnid FROM filename WHERE name=%Q %s)"
" AND event.objid=mlink.mid",
TAG_BRANCH,
zFilename, filename_collation()
);
if( (zA = P("a"))!=0 ){
blob_appendf(&sql, " AND event.mtime>=julianday('%q')", zA);
}
if( (zB = P("b"))!=0 ){
blob_appendf(&sql, " AND event.mtime<=julianday('%q')", zB);
}
|
| ︙ | ︙ | |||
285 286 287 288 289 290 291 |
char zTime[10];
char zShort[20];
char zShortCkin[20];
if( zBr==0 ) zBr = "trunk";
if( uBg ){
zBgClr = hash_color(zUser);
}else if( brBg || zBgClr==0 || zBgClr[0]==0 ){
| | | 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 |
char zTime[10];
char zShort[20];
char zShortCkin[20];
if( zBr==0 ) zBr = "trunk";
if( uBg ){
zBgClr = hash_color(zUser);
}else if( brBg || zBgClr==0 || zBgClr[0]==0 ){
zBgClr = strcmp(zBr,"trunk")==0 ? "" : hash_color(zBr);
}
gidx = graph_add_row(pGraph, frid, fpid>0 ? 1 : 0, &fpid, zBr, zBgClr, 0);
if( memcmp(zDate, zPrevDate, 10) ){
sqlite3_snprintf(sizeof(zPrevDate), zPrevDate, "%.10s", zDate);
@ <tr><td>
@ <div class="divider">%s(zPrevDate)</div>
@ </td></tr>
|
| ︙ | ︙ |
Changes to src/gzip.c.
| ︙ | ︙ | |||
45 46 47 48 49 50 51 | z[2] = (v>>16) & 0xff; z[3] = (v>>24) & 0xff; } /* ** Begin constructing a gzip file. */ | | < > | > | 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
z[2] = (v>>16) & 0xff;
z[3] = (v>>24) & 0xff;
}
/*
** Begin constructing a gzip file.
*/
void gzip_begin(sqlite3_int64 now){
char aHdr[10];
assert( gzip.eState==0 );
blob_zero(&gzip.out);
aHdr[0] = 0x1f;
aHdr[1] = 0x8b;
aHdr[2] = 8;
aHdr[3] = 0;
if( now==0 ){
now = db_int64(0, "SELECT (julianday('now') - 2440587.5)*86400.0");
}
put32(&aHdr[4], now&0xffffffff);
aHdr[8] = 2;
aHdr[9] = 255;
blob_append(&gzip.out, aHdr, 10);
gzip.iCRC = 0;
gzip.eState = 1;
}
|
| ︙ | ︙ | |||
123 124 125 126 127 128 129 |
** Compress a file using gzip.
*/
void test_gzip_cmd(void){
Blob b;
char *zOut;
if( g.argc!=3 ) usage("FILENAME");
sqlite3_open(":memory:", &g.db);
| | | 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
** Compress a file using gzip.
*/
void test_gzip_cmd(void){
Blob b;
char *zOut;
if( g.argc!=3 ) usage("FILENAME");
sqlite3_open(":memory:", &g.db);
gzip_begin(0);
blob_read_from_file(&b, g.argv[2]);
zOut = mprintf("%s.gz", g.argv[2]);
gzip_step(blob_buffer(&b), blob_size(&b));
blob_reset(&b);
gzip_finish(&b);
blob_write_to_file(&b, zOut);
blob_reset(&b);
fossil_free(zOut);
}
|
Changes to src/http.c.
| ︙ | ︙ | |||
109 110 111 112 113 114 115 |
char *zEncoded = encode64(zCredentials, -1);
blob_appendf(pHdr, "Authorization: Basic %s\r\n", zEncoded);
fossil_free(zEncoded);
fossil_free(zCredentials);
}
blob_appendf(pHdr, "Host: %s\r\n", g.urlHostname);
blob_appendf(pHdr, "User-Agent: Fossil/" RELEASE_VERSION
| | | 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
char *zEncoded = encode64(zCredentials, -1);
blob_appendf(pHdr, "Authorization: Basic %s\r\n", zEncoded);
fossil_free(zEncoded);
fossil_free(zCredentials);
}
blob_appendf(pHdr, "Host: %s\r\n", g.urlHostname);
blob_appendf(pHdr, "User-Agent: Fossil/" RELEASE_VERSION
" (" MANIFEST_DATE " " MANIFEST_VERSION ")\r\n");
if( g.fHttpTrace ){
blob_appendf(pHdr, "Content-Type: application/x-fossil-debug\r\n");
}else{
blob_appendf(pHdr, "Content-Type: application/x-fossil\r\n");
}
blob_appendf(pHdr, "Content-Length: %d\r\n\r\n", blob_size(pPayload));
}
|
| ︙ | ︙ | |||
134 135 136 137 138 139 140 |
*/
int http_exchange(Blob *pSend, Blob *pReply, int useLogin){
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 */
| | | 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
*/
int http_exchange(Blob *pSend, Blob *pReply, int useLogin){
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 = 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 */
if( transport_open() ){
|
| ︙ | ︙ | |||
172 173 174 175 176 177 178 |
*/
if( g.fHttpTrace ){
static int traceCnt = 0;
char *zOutFile;
FILE *out;
traceCnt++;
zOutFile = mprintf("http-request-%d.txt", traceCnt);
| | | | 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
*/
if( g.fHttpTrace ){
static int traceCnt = 0;
char *zOutFile;
FILE *out;
traceCnt++;
zOutFile = mprintf("http-request-%d.txt", traceCnt);
out = fopen(zOutFile, "wb");
if( out ){
fwrite(blob_buffer(&hdr), 1, blob_size(&hdr), out);
fwrite(blob_buffer(&payload), 1, blob_size(&payload), out);
fclose(out);
}
free(zOutFile);
zOutFile = mprintf("http-reply-%d.txt", traceCnt);
out = fopen(zOutFile, "wb");
transport_log(out);
free(zOutFile);
}
/*
** Send the request to the server.
*/
|
| ︙ | ︙ | |||
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 |
"application/x-fossil-uncompressed", -1)==0 ){
isCompressed = 0;
}else if( fossil_strnicmp(&zLine[14], "application/x-fossil", -1)!=0 ){
isError = 1;
}
}
}
if( rc!=200 ){
fossil_warning("\"location:\" missing from 302 redirect reply");
goto write_err;
}
/*
** Extract the reply payload that follows the header
*/
| > > > > < < < < | 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 |
"application/x-fossil-uncompressed", -1)==0 ){
isCompressed = 0;
}else if( fossil_strnicmp(&zLine[14], "application/x-fossil", -1)!=0 ){
isError = 1;
}
}
}
if( iLength<0 ){
fossil_fatal("server did not reply");
goto write_err;
}
if( rc!=200 ){
fossil_warning("\"location:\" missing from 302 redirect reply");
goto write_err;
}
/*
** Extract the reply payload that follows the header
*/
blob_zero(pReply);
blob_resize(pReply, iLength);
iLength = transport_receive(blob_buffer(pReply), iLength);
blob_resize(pReply, iLength);
if( isError ){
char *z;
int i, j;
|
| ︙ | ︙ |
Changes to src/http_socket.c.
| ︙ | ︙ | |||
31 32 33 34 35 36 37 38 39 40 | #if defined(_WIN32) # include <windows.h> /* for Sleep once server works again */ # define sleep Sleep /* windows does not have sleep, but Sleep */ # if defined(__MINGW32__) # include <ws2tcpip.h> # endif #else # include <arpa/inet.h> # include <sys/socket.h> # include <netdb.h> | > < | 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | #if defined(_WIN32) # include <windows.h> /* for Sleep once server works again */ # define sleep Sleep /* windows does not have sleep, but Sleep */ # if defined(__MINGW32__) # include <ws2tcpip.h> # endif #else # include <netinet/in.h> # include <arpa/inet.h> # include <sys/socket.h> # include <netdb.h> #endif #include <assert.h> #include <sys/types.h> #include <signal.h> /* ** There can only be a single socket connection open at a time. |
| ︙ | ︙ | |||
198 199 200 201 202 203 204 |
return total;
}
/*
** Receive content back from the open socket connection.
*/
size_t socket_receive(void *NotUsed, void *pContent, size_t N){
| | | | | 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
return total;
}
/*
** Receive content back from the open socket connection.
*/
size_t socket_receive(void *NotUsed, void *pContent, size_t N){
ssize_t got;
size_t total = 0;
while( N>0 ){
got = recv(iSocket, pContent, N, 0);
if( got<=0 ) break;
total += (size_t)got;
N -= (size_t)got;
pContent = (void*)&((char*)pContent)[got];
}
return total;
}
|
Changes to src/http_ssl.c.
| ︙ | ︙ | |||
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
/*
** 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;
if( sslIsInit==0 ){
SSL_library_init();
SSL_load_error_strings();
ERR_load_BIO_strings();
OpenSSL_add_all_algorithms();
sslCtx = SSL_CTX_new(SSLv23_client_method());
/* Set up acceptable CA root certificates */
zCaSetting = db_get("ssl-ca-location", 0);
if( zCaSetting==0 || zCaSetting[0]=='\0' ){
/* CA location not specified, use platform's default certificate store */
X509_STORE_set_default_paths(SSL_CTX_get_cert_store(sslCtx));
}else{
| > > > | 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
/*
** 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();
OpenSSL_add_all_algorithms();
sslCtx = SSL_CTX_new(SSLv23_client_method());
/* Disable SSLv2 */
SSL_CTX_set_options(sslCtx, SSL_OP_NO_SSLv2);
/* Set up acceptable CA root certificates */
zCaSetting = db_get("ssl-ca-location", 0);
if( zCaSetting==0 || zCaSetting[0]=='\0' ){
/* CA location not specified, use platform's default certificate store */
X509_STORE_set_default_paths(SSL_CTX_get_cert_store(sslCtx));
}else{
|
| ︙ | ︙ | |||
131 132 133 134 135 136 137 |
}
if( SSL_CTX_load_verify_locations(sslCtx, zCaFile, zCaDirectory)==0 ){
fossil_fatal("Failed to use CA root certificates from "
"ssl-ca-location '%s'", zCaSetting);
}
}
| | > > > > | > | | > | > | 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
}
if( SSL_CTX_load_verify_locations(sslCtx, zCaFile, zCaDirectory)==0 ){
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 */
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
){
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 */
SSL_CTX_set_client_cert_cb(sslCtx, ssl_client_cert_callback);
sslIsInit = 1;
}
}
/*
|
| ︙ | ︙ | |||
180 181 182 183 184 185 186 |
** g.urlPort TCP/IP port to use. Ex: 80
**
** Return the number of errors.
*/
int ssl_open(void){
X509 *cert;
int hasSavedCertificate = 0;
| > > | | > > > > > > > > | | < | | | 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
** g.urlPort TCP/IP port to use. Ex: 80
**
** Return the number of errors.
*/
int ssl_open(void){
X509 *cert;
int hasSavedCertificate = 0;
int trusted = 0;
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(&trusted);
if ( cert!=NULL ){
X509_STORE_add_cert(SSL_CTX_get_cert_store(sslCtx), cert);
X509_free(cert);
hasSavedCertificate = 1;
}
iBio = BIO_new_ssl_connect(sslCtx);
BIO_get_ssl(iBio, &ssl);
#if (SSLEAY_VERSION_NUMBER >= 0x00908070) && !defined(OPENSSL_NO_TLSEXT)
if( !SSL_set_tlsext_host_name(ssl, g.urlName) ){
fossil_warning("WARNING: failed to set server name indication (SNI), "
"continuing without it.\n");
}
#endif
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
if( iBio==NULL ) {
ssl_set_errmsg("SSL: cannot open SSL (%s)",
ERR_reason_error_string(ERR_get_error()));
return 1;
}
BIO_set_conn_hostname(iBio, g.urlName);
BIO_set_conn_int_port(iBio, &g.urlPort);
if( BIO_do_connect(iBio)<=0 ){
ssl_set_errmsg("SSL: cannot connect to host %s:%d (%s)",
g.urlName, g.urlPort, ERR_reason_error_string(ERR_get_error()));
ssl_close();
return 1;
}
|
| ︙ | ︙ | |||
228 229 230 231 232 233 234 |
if ( cert==NULL ){
ssl_set_errmsg("No SSL certificate was presented by the peer");
ssl_close();
return 1;
}
| | | | > | | | | | | | | > > > > > > > > > | | | | | | > > > | > > > > > > > | 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 |
if ( cert==NULL ){
ssl_set_errmsg("No SSL certificate was presented by the peer");
ssl_close();
return 1;
}
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];
unsigned int mdLength = 31;
mem = BIO_new(BIO_s_mem());
X509_NAME_print_ex(mem, X509_get_subject_name(cert), 2, XN_FLAG_MULTILINE);
BIO_puts(mem, "\n\nIssued By:\n\n");
X509_NAME_print_ex(mem, X509_get_issuer_name(cert), 2, XN_FLAG_MULTILINE);
BIO_puts(mem, "\n\nSHA1 Fingerprint:\n\n ");
if(X509_digest(cert, EVP_sha1(), md, &mdLength)){
int j;
for( j = 0; j < mdLength; ++j ) {
BIO_printf(mem, " %02x", md[j]);
}
}
BIO_write(mem, "", 1); /* nul-terminate mem buffer */
BIO_get_mem_data(mem, &desc);
if( hasSavedCertificate ){
warning = "WARNING: Certificate doesn't match the "
"saved certificate for this host!";
}
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' ) {
X509_free(cert);
ssl_set_errmsg("SSL certificate declined");
ssl_close();
return 1;
}
if( blob_str(&ans)[0]=='a' ) {
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]);
}
X509_free(cert);
return 0;
}
/*
** Save certificate to global config.
*/
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);
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(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);
return cert;
}
|
| ︙ | ︙ |
Changes to src/http_transport.c.
| ︙ | ︙ | |||
263 264 265 266 267 268 269 |
** Send content over the wire.
*/
void transport_send(Blob *toSend){
char *z = blob_buffer(toSend);
int n = blob_size(toSend);
transport.nSent += n;
if( g.urlIsSsh ){
| < | < | 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 |
** Send content over the wire.
*/
void transport_send(Blob *toSend){
char *z = blob_buffer(toSend);
int n = blob_size(toSend);
transport.nSent += n;
if( g.urlIsSsh ){
fwrite(z, 1, n, sshOut);
fflush(sshOut);
}else if( g.urlIsHttps ){
#ifdef FOSSIL_ENABLE_SSL
int sent;
while( n>0 ){
sent = ssl_send(0, z, n);
/* printf("Sent %d of %d bytes\n", sent, n); fflush(stdout); */
if( sent<=0 ) break;
|
| ︙ | ︙ |
Changes to src/info.c.
| ︙ | ︙ | |||
148 149 150 151 152 153 154 |
i64 fsize;
if( g.argc==3 && (fsize = file_size(g.argv[2]))>0 && (fsize&0x1ff)==0 ){
db_open_config(0);
db_record_repository_filename(g.argv[2]);
db_open_repository(g.argv[2]);
fossil_print("project-name: %s\n", db_get("project-name", "<unnamed>"));
fossil_print("project-code: %s\n", db_get("project-code", "<none>"));
| < | < | 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
i64 fsize;
if( g.argc==3 && (fsize = file_size(g.argv[2]))>0 && (fsize&0x1ff)==0 ){
db_open_config(0);
db_record_repository_filename(g.argv[2]);
db_open_repository(g.argv[2]);
fossil_print("project-name: %s\n", db_get("project-name", "<unnamed>"));
fossil_print("project-code: %s\n", db_get("project-code", "<none>"));
return;
}
db_find_and_open_repository(0,0);
if( g.argc==2 ){
int vid;
/* 012345678901234 */
db_record_repository_filename(0);
fossil_print("project-name: %s\n", db_get("project-name", "<unnamed>"));
if( g.localOpen ){
fossil_print("repository: %s\n", db_repository_filename());
fossil_print("local-root: %s\n", g.zLocalRoot);
}
#if defined(_WIN32)
if( g.zHome ){
fossil_print("user-home: %s\n", g.zHome);
}
#endif
fossil_print("project-code: %s\n", db_get("project-code", ""));
vid = g.localOpen ? db_lget_int("checkout", 0) : 0;
if( vid ){
show_common_info(vid, "checkout:", 1, 1);
}
}else{
int rid;
rid = name_to_rid(g.argv[2]);
|
| ︙ | ︙ | |||
253 254 255 256 257 258 259 | } } /* ** Append the difference between two RIDs to the output */ | | > | > | > > > > > > > > | | | | | | 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 |
}
}
/*
** Append the difference between two RIDs to the output
*/
static void append_diff(const char *zFrom, const char *zTo, int diffFlags){
int fromid;
int toid;
Blob from, to, out;
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);
}
blob_zero(&out);
if( diffFlags & DIFF_SIDEBYSIDE ){
text_diff(&from, &to, &out, diffFlags | DIFF_HTML);
@ <div class="sbsdiff">
@ %s(blob_str(&out))
@ </div>
}else{
text_diff(&from, &to, &out, diffFlags | DIFF_LINENO | DIFF_HTML);
@ <div class="udiff">
@ %s(blob_str(&out))
@ </div>
}
blob_reset(&from);
blob_reset(&to);
blob_reset(&out);
}
/*
** Write a line of web-page output that shows changes that have occurred
** to a file between two check-ins.
*/
static void append_file_change_line(
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 diffFlags, /* Flags for text_diff(). Zero to omit diffs */
int mperm /* executable or symlink permission for zNew */
){
if( !g.perm.History ){
if( zNew==0 ){
@ <p>Deleted %h(zName)</p>
}else if( zOld==0 ){
@ <p>Added %h(zName)</p>
}else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){
@ <p>Name change from %h(zOldName) to %h(zName)
}else if( fossil_strcmp(zNew, zOld)==0 ){
@ <p>Execute permission %s(( mperm==PERM_EXE )?"set":"cleared")
@ for %h(zName)</p>
}else{
@ <p>Changes to %h(zName)</p>
}
if( diffFlags ){
@ <pre style="white-space:pre;">
append_diff(zOld, zNew, diffFlags);
@ </pre>
}
}else{
if( zOld && zNew ){
if( fossil_strcmp(zOld, zNew)!=0 ){
@ <p>Modified <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a>
@ from <a href="%s(g.zTop)/artifact/%s(zOld)">[%S(zOld)]</a>
@ to <a href="%s(g.zTop)/artifact/%s(zNew)">[%S(zNew)].</a>
|
| ︙ | ︙ | |||
328 329 330 331 332 333 334 |
}else if( zOld ){
@ <p>Deleted <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a>
@ version <a href="%s(g.zTop)/artifact/%s(zOld)">[%S(zOld)]</a>
}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>
}
| | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 |
}else if( zOld ){
@ <p>Deleted <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a>
@ version <a href="%s(g.zTop)/artifact/%s(zOld)">[%S(zOld)]</a>
}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( diffFlags ){
@ <pre style="white-space:pre;">
append_diff(zOld, zNew, diffFlags);
@ </pre>
}else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
@
@ <a href="%s(g.zTop)/fdiff?v1=%S(zOld)&v2=%S(zNew)">[diff]</a>
}
@ </p>
}
}
/*
** Construct an appropriate diffFlag for text_diff() based on query
** parameters and the to boolean arguments.
*/
int construct_diff_flags(int showDiff, int sideBySide){
int diffFlags;
if( showDiff==0 ){
diffFlags = 0; /* Zero means do not show any diff */
}else{
int x;
if( sideBySide ){
diffFlags = DIFF_SIDEBYSIDE | DIFF_IGNORE_EOLWS;
/* "dw" query parameter determines width of each column */
x = atoi(PD("dw","80"))*(DIFF_CONTEXT_MASK+1);
if( x<0 || x>DIFF_WIDTH_MASK ) x = DIFF_WIDTH_MASK;
diffFlags += x;
}else{
diffFlags = DIFF_INLINE | DIFF_IGNORE_EOLWS;
}
/* "dc" query parameter determines lines of context */
x = atoi(PD("dc","7"));
if( x<0 || x>DIFF_CONTEXT_MASK ) x = DIFF_CONTEXT_MASK;
diffFlags += x;
/* The "noopt" parameter disables diff optimization */
if( PD("noopt",0)!=0 ) diffFlags |= DIFF_NOOPT;
}
return diffFlags;
}
/*
** WEBPAGE: vinfo
** WEBPAGE: ci
** URL: /ci?name=RID|ARTIFACTID
**
|
| ︙ | ︙ | |||
360 361 362 363 364 365 366 |
** shown, without diffs. This behavior is inverted if the
** "show-version-diffs" setting is turned on.
*/
void ci_page(void){
Stmt q;
int rid;
int isLeaf;
| | > > | 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 |
** shown, without diffs. This behavior is inverted if the
** "show-version-diffs" setting is turned on.
*/
void ci_page(void){
Stmt q;
int rid;
int isLeaf;
int showDiff; /* True to show diffs */
int sideBySide; /* True for side-by-side diffs */
int diffFlags; /* Flag parameter for text_diff() */
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();
if( !g.perm.Read ){ login_needed(); return; }
zName = P("name");
|
| ︙ | ︙ | |||
390 391 392 393 394 395 396 397 398 399 400 401 402 403 |
"SELECT uuid, datetime(mtime, 'localtime'), user, comment,"
" datetime(omtime, 'localtime')"
" FROM blob, event"
" WHERE blob.rid=%d"
" AND event.objid=%d",
rid, rid
);
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;
const char *zComment;
const char *zDate;
| > | 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 |
"SELECT uuid, datetime(mtime, 'localtime'), user, comment,"
" datetime(omtime, 'localtime')"
" 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;
const char *zComment;
const char *zDate;
|
| ︙ | ︙ | |||
467 468 469 470 471 472 473 |
if( zParent ){
@ | <a href="%s(g.zTop)/timeline?p=%S(zUuid)">ancestors</a>
}
if( !isLeaf ){
@ | <a href="%s(g.zTop)/timeline?d=%S(zUuid)">descendants</a>
}
if( zParent && !isLeaf ){
| | | 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 |
if( zParent ){
@ | <a href="%s(g.zTop)/timeline?p=%S(zUuid)">ancestors</a>
}
if( !isLeaf ){
@ | <a href="%s(g.zTop)/timeline?d=%S(zUuid)">descendants</a>
}
if( zParent && !isLeaf ){
@ | <a href="%s(g.zTop)/timeline?dp=%S(zUuid)">both</a>
}
db_prepare(&q, "SELECT substr(tag.tagname,5) FROM tagxref, tag "
" WHERE rid=%d AND tagtype>0 "
" AND tag.tagid=tagxref.tagid "
" AND +tag.tagname GLOB 'sym-*'", rid);
while( db_step(&q)==SQLITE_ROW ){
const char *zTagName = db_column_text(&q, 0);
|
| ︙ | ︙ | |||
506 507 508 509 510 511 512 513 514 515 516 |
style_header("Check-in Information");
login_anonymous_available();
}
db_finalize(&q);
showTags(rid, "");
if( zParent ){
@ <div class="section">Changes</div>
showDiff = g.zPath[0]!='c';
if( db_get_boolean("show-version-diffs", 0)==0 ){
showDiff = !showDiff;
if( showDiff ){
| > | > > > > | | > > > > > > > | > > > | > > > > | > > > < | > > | | 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 |
style_header("Check-in Information");
login_anonymous_available();
}
db_finalize(&q);
showTags(rid, "");
if( zParent ){
@ <div class="section">Changes</div>
@ <div class="sectionmenu">
showDiff = g.zPath[0]!='c';
if( db_get_boolean("show-version-diffs", 0)==0 ){
showDiff = !showDiff;
if( showDiff ){
@ <a class="button" href="%s(g.zTop)/vinfo/%T(zName)">
@ hide diffs</a>
if( sideBySide ){
@ <a class="button" href="%s(g.zTop)/ci/%T(zName)?sbs=0">
@ unified diffs</a>
}else{
@ <a class="button" href="%s(g.zTop)/ci/%T(zName)?sbs=1">
@ side-by-side diffs</a>
}
}else{
@ <a class="button" href="%s(g.zTop)/ci/%T(zName)?sbs=0">
@ show unified diffs</a>
@ <a class="button" href="%s(g.zTop)/ci/%T(zName)?sbs=1">
@ show side-by-side diffs</a>
}
}else{
if( showDiff ){
@ <a class="button" href="%s(g.zTop)/ci/%T(zName)">hide diffs</a>
if( sideBySide ){
@ <a class="button" href="%s(g.zTop)/info/%T(zName)?sbs=0">
@ unified diffs</a>
}else{
@ <a class="button" href="%s(g.zTop)/info/%T(zName)?sbs=1">
@ side-by-side diffs</a>
}
}else{
@ <a class="button" href="%s(g.zTop)/vinfo/%T(zName)?sbs=0">
@ show unified diffs</a>
@ <a class="button" href="%s(g.zTop)/vinfo/%T(zName)?sbs=1">
@ show side-by-side diffs</a>
}
}
@ <a class="button" href="%s(g.zTop)/vpatch?from=%S(zParent)&to=%S(zUuid)">
@ patch</a></div>
db_prepare(&q,
"SELECT name,"
" mperm,"
" (SELECT uuid FROM blob WHERE rid=mlink.pid),"
" (SELECT uuid FROM blob WHERE rid=mlink.fid),"
" (SELECT name FROM filename WHERE filename.fnid=mlink.pfnid)"
" FROM mlink JOIN filename ON filename.fnid=mlink.fnid"
" WHERE mlink.mid=%d"
" ORDER BY name /*sort*/",
rid
);
diffFlags = construct_diff_flags(showDiff, sideBySide);
while( db_step(&q)==SQLITE_ROW ){
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, diffFlags, mperm);
}
db_finalize(&q);
}
style_footer();
}
/*
|
| ︙ | ︙ | |||
690 691 692 693 694 695 696 | } db_finalize(&q); } /* ** WEBPAGE: vdiff | | > > > > > > > > > > > > > > | 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 |
}
db_finalize(&q);
}
/*
** WEBPAGE: vdiff
** URL: /vdiff?from=UUID&to=UUID&detail=BOOLEAN;sbs=BOOLEAN
**
** Show all differences between two checkins.
*/
void vdiff_page(void){
int ridFrom, ridTo;
int showDetail = 0;
int sideBySide = 0;
int diffFlags = 0;
Manifest *pFrom, *pTo;
ManifestFile *pFileFrom, *pFileTo;
login_check_credentials();
if( !g.perm.Read ){ login_needed(); return; }
login_anonymous_available();
pFrom = vdiff_parse_manifest("from", &ridFrom);
if( pFrom==0 ) return;
pTo = vdiff_parse_manifest("to", &ridTo);
if( pTo==0 ) return;
sideBySide = atoi(PD("sbs","1"));
showDetail = atoi(PD("detail","0"));
if( !showDetail && sideBySide ) showDetail = 1;
if( !sideBySide ){
style_submenu_element("Side-by-side Diff", "sbsdiff",
"%s/vdiff?from=%T&to=%T&detail=%d&sbs=1",
g.zTop, P("from"), P("to"), showDetail);
}else{
style_submenu_element("Unified Diff", "udiff",
"%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);
@ </blockquote><hr /><p>
manifest_file_rewind(pFrom);
pFileFrom = manifest_file_next(pFrom, 0);
manifest_file_rewind(pTo);
pFileTo = manifest_file_next(pTo, 0);
diffFlags = construct_diff_flags(showDetail, sideBySide);
while( pFileFrom || pFileTo ){
int cmp;
if( pFileFrom==0 ){
cmp = +1;
}else if( pFileTo==0 ){
cmp = -1;
}else{
|
| ︙ | ︙ | |||
745 746 747 748 749 750 751 |
}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,
| | | 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 |
}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, diffFlags,
manifest_file_mperm(pFileTo));
pFileFrom = manifest_file_next(pFrom, 0);
pFileTo = manifest_file_next(pTo, 0);
}
}
manifest_destroy(pFrom);
manifest_destroy(pTo);
|
| ︙ | ︙ | |||
797 798 799 800 801 802 803 |
" WHERE tagid=%d AND tagtype>0 AND rid=mlink.mid),'trunk')"
" FROM mlink, filename, event, blob a, blob b"
" WHERE filename.fnid=mlink.fnid"
" AND event.objid=mlink.mid"
" AND a.rid=mlink.fid"
" AND b.rid=mlink.mid"
" AND mlink.fid=%d"
| | | 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 |
" WHERE tagid=%d AND tagtype>0 AND rid=mlink.mid),'trunk')"
" FROM mlink, filename, event, blob a, blob b"
" WHERE filename.fnid=mlink.fnid"
" AND event.objid=mlink.mid"
" AND a.rid=mlink.fid"
" AND b.rid=mlink.mid"
" AND mlink.fid=%d"
" ORDER BY filename.name, event.mtime /*sort*/",
TAG_BRANCH, rid
);
@ <ul>
while( db_step(&q)==SQLITE_ROW ){
const char *zName = db_column_text(&q, 0);
const char *zDate = db_column_text(&q, 1);
const char *zCom = db_column_text(&q, 2);
|
| ︙ | ︙ | |||
983 984 985 986 987 988 989 |
@ <a href="%s(g.zTop)/artifact/%S(zUuid)">[view]</a>
}
}
/*
** WEBPAGE: fdiff
| | | | | > > > > > > > > > > > > | > > > > > > > > > > > > > > > | | | > | | > | | | | 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 |
@ <a href="%s(g.zTop)/artifact/%S(zUuid)">[view]</a>
}
}
/*
** WEBPAGE: fdiff
** 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. 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;
int diffFlags;
const char *zStyle = "sbsdiff";
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();
cgi_set_content_type("text/plain");
diffFlags = 4;
}else{
blob_zero(&diff);
pOut = &diff;
diffFlags = construct_diff_flags(1, sideBySide);
if( sideBySide ){
zStyle = "sbsdiff";
}else{
diffFlags |= DIFF_LINENO;
zStyle = "udiff";
}
}
content_get(v1, &c1);
content_get(v2, &c2);
text_diff(&c1, &c2, pOut, diffFlags | DIFF_HTML );
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("Side-by-side Diff", "sbsdiff",
"%s/fdiff?v1=%T&v2=%T&sbs=1",
g.zTop, P("v1"), P("v2"));
}else{
style_submenu_element("Unified Diff", "udiff",
"%s/fdiff?v1=%T&v2=%T&sbs=0",
g.zTop, P("v1"), P("v2"));
}
if( P("smhdr")!=0 ){
@ <h2>Differences From Artifact
@ <a href="%s(g.zTop)/artifact/%S(zV1)">[%S(zV1)]</a> To
@ <a href="%s(g.zTop)/artifact/%S(zV2)">[%S(zV2)]</a>.</h2>
}else{
@ <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 />
@ <div class="%s(zStyle)">
@ %s(blob_str(&diff))
@ </div>
blob_reset(&diff);
style_footer();
}
}
/*
** WEBPAGE: raw
|
| ︙ | ︙ | |||
1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 | @ <input type="text" name="%s(zIdCustom)" @ id="%s(zIdCustom)" class="checkinUserColor" @ value="%h(stdClrFound?"":zDefaultColor)" /> @ </td> @ </tr> @ </table> } /* ** WEBPAGE: ci_edit ** URL: ci_edit?r=RID&c=NEWCOMMENT&u=NEWUSER ** ** Present a dialog for updating properties of a baseline: ** | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 |
@ <input type="text" name="%s(zIdCustom)"
@ id="%s(zIdCustom)" class="checkinUserColor"
@ value="%h(stdClrFound?"":zDefaultColor)" />
@ </td>
@ </tr>
@ </table>
}
/*
** Do a comment comparison.
**
** + Leading and trailing whitespace are ignored.
** + \r\n characters compare equal to \n
**
** Return true if equal and false if not equal.
*/
static int comment_compare(const char *zA, const char *zB){
if( zA==0 ) zA = "";
if( zB==0 ) zB = "";
while( fossil_isspace(zA[0]) ) zA++;
while( fossil_isspace(zB[0]) ) zB++;
while( zA[0] && zB[0] ){
if( zA[0]==zB[0] ){ zA++; zB++; continue; }
if( zA[0]=='\r' && zA[1]=='\n' && zB[0]=='\n' ){
zA += 2;
zB++;
continue;
}
if( zB[0]=='\r' && zB[1]=='\n' && zA[0]=='\n' ){
zB += 2;
zA++;
continue;
}
return 0;
}
while( fossil_isspace(zB[0]) ) zB++;
while( fossil_isspace(zA[0]) ) zA++;
return zA[0]==0 && zB[0]==0;
}
/*
** WEBPAGE: ci_edit
** URL: ci_edit?r=RID&c=NEWCOMMENT&u=NEWUSER
**
** Present a dialog for updating properties of a baseline:
**
|
| ︙ | ︙ | |||
1661 1662 1663 1664 1665 1666 1667 |
login_verify_csrf_secret();
blob_zero(&ctrl);
zNow = date_in_standard_format("now");
blob_appendf(&ctrl, "D %s\n", zNow);
db_multi_exec("CREATE TEMP TABLE newtags(tag UNIQUE, prefix, value)");
if( zNewColor[0]
| | > | | 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 |
login_verify_csrf_secret();
blob_zero(&ctrl);
zNow = date_in_standard_format("now");
blob_appendf(&ctrl, "D %s\n", zNow);
db_multi_exec("CREATE TEMP TABLE newtags(tag UNIQUE, prefix, value)");
if( zNewColor[0]
&& (fPropagateColor!=fNewPropagateColor
|| fossil_strcmp(zColor,zNewColor)!=0)
){
char *zPrefix = "+";
if( fNewPropagateColor ){
zPrefix = "*";
}
db_multi_exec("REPLACE INTO newtags VALUES('bgcolor',%Q,%Q)",
zPrefix, zNewColor);
}
if( zNewColor[0]==0 && zColor[0]!=0 ){
db_multi_exec("REPLACE INTO newtags VALUES('bgcolor','-',NULL)");
}
if( comment_compare(zComment,zNewComment)==0 ){
db_multi_exec("REPLACE INTO newtags VALUES('comment','+',%Q)",
zNewComment);
}
if( fossil_strcmp(zDate,zNewDate)!=0 ){
db_multi_exec("REPLACE INTO newtags VALUES('date','+',%Q)",
zNewDate);
}
|
| ︙ | ︙ |
Added src/json.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 |
#ifdef FOSSIL_ENABLE_JSON
/*
** Copyright (c) 2011 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** Code for the JSON API.
**
** For notes regarding the public JSON interface, please see:
**
** https://docs.google.com/document/d/1fXViveNhDbiXgCuE7QDXQOKeFzf2qNUkBEgiUvoqFN4/edit
**
**
** Notes for hackers...
**
** Here's how command/page dispatching works: json_page_top() (in HTTP mode) or
** json_cmd_top() (in CLI mode) catch the "json" path/command. Those functions then
** dispatch to a JSON-mode-specific command/page handler with the type fossil_json_f().
** See the API docs for that typedef (below) for the semantics of the callbacks.
**
**
*/
#include "config.h"
#include "VERSION.h"
#include "json.h"
#include <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*/,
"mtime" /*mtime*/,
"payload" /* payload */,
"requestId" /*requestId*/,
"resultCode" /*resultCode*/,
"resultText" /*resultText*/,
"timestamp" /*timestamp*/
};
/*
** Internal helpers to manipulate a byte array as a bitset. The B
** argument must be-a array at least (BIT/8+1) bytes long.
** The BIT argument is the bit number to query/set/clear/toggle.
*/
#define BITSET_BYTEFOR(B,BIT) ((B)[ BIT / 8 ])
#define BITSET_SET(B,BIT) ((BITSET_BYTEFOR(B,BIT) |= (0x01 << (BIT%8))),0x01)
#define BITSET_UNSET(B,BIT) ((BITSET_BYTEFOR(B,BIT) &= ~(0x01 << (BIT%8))),0x00)
#define BITSET_GET(B,BIT) ((BITSET_BYTEFOR(B,BIT) & (0x01 << (BIT%8))) ? 0x01 : 0x00)
#define BITSET_TOGGLE(B,BIT) (BITSET_GET(B,BIT) ? (BITSET_UNSET(B,BIT)) : (BITSET_SET(B,BIT)))
/* Timer code taken from sqlite3's shell.c, modified slightly.
FIXME: move the timer into the fossil core API so that we can
start the timer early on in the app init phase. Right now we're
just timing the json ops themselves.
*/
#if !defined(_WIN32) && !defined(WIN32) && !defined(__OS2__) && !defined(__RTP__) && !defined(_WRS_KERNEL)
#include <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
char fossil_has_json(){
return g.json.isJsonMode && (g.isHTTP || g.json.post.o);
}
/*
** Placeholder /json/XXX page impl for NYI (Not Yet Implemented)
** (but planned) pages/commands.
*/
cson_value * json_page_nyi(){
g.json.resultCode = FSL_JSON_E_NYI;
return NULL;
}
/*
** Given a FossilJsonCodes value, it returns a string suitable for use
** as a resultCode string. Returns some unspecified non-empty string
** if errCode is not one of the FossilJsonCodes values.
*/
static char const * json_err_cstr( int errCode ){
switch( errCode ){
case 0: return "Success";
#define C(X,V) case FSL_JSON_E_ ## X: return V
C(GENERIC,"Generic error");
C(INVALID_REQUEST,"Invalid request");
C(UNKNOWN_COMMAND,"Unknown Command");
C(UNKNOWN,"Unknown error");
C(TIMEOUT,"Timeout reached");
C(ASSERT,"Assertion failed");
C(ALLOC,"Resource allocation failed");
C(NYI,"Not yet implemented");
C(PANIC,"x");
C(MANIFEST_READ_FAILED,"Reading artifact manifest failed");
C(FILE_OPEN_FAILED,"Opening file failed");
C(AUTH,"Authentication error");
C(MISSING_AUTH,"Authentication info missing from request");
C(DENIED,"Access denied");
C(WRONG_MODE,"Request not allowed (wrong operation mode)");
C(LOGIN_FAILED,"Login failed");
C(LOGIN_FAILED_NOSEED,"Anonymous login attempt was missing password seed");
C(LOGIN_FAILED_NONAME,"Login failed - name not supplied");
C(LOGIN_FAILED_NOPW,"Login failed - password not supplied");
C(LOGIN_FAILED_NOTFOUND,"Login failed - no match found");
C(USAGE,"Usage error");
C(INVALID_ARGS,"Invalid argument(s)");
C(MISSING_ARGS,"Missing argument(s)");
C(AMBIGUOUS_UUID,"Resource identifier is ambiguous");
C(UNRESOLVED_UUID,"Provided uuid/tag/branch could not be resolved");
C(RESOURCE_ALREADY_EXISTS,"Resource already exists");
C(RESOURCE_NOT_FOUND,"Resource not found");
C(DB,"Database error");
C(STMT_PREP,"Statement preparation failed");
C(STMT_BIND,"Statement parameter binding failed");
C(STMT_EXEC,"Statement execution/stepping failed");
C(DB_LOCKED,"Database is locked");
C(DB_NEEDS_REBUILD,"Fossil repository needs to be rebuilt");
C(DB_NOT_FOUND,"Fossil repository db file could not be found.");
C(DB_NOT_VALID, "Fossil repository db file is not valid.");
#undef C
default:
return "Unknown Error";
}
}
/*
** Implements the cson_data_dest_f() interface and outputs the data to
** a fossil Blob object. pState must be-a initialized (Blob*), to
** which n bytes of src will be appended.
**/
int cson_data_dest_Blob(void * pState, void const * src, unsigned int n){
Blob * b = (Blob*)pState;
blob_append( b, (char const *)src, (int)n ) /* will die on OOM */;
return 0;
}
/*
** Implements the cson_data_source_f() interface and reads input from
** a fossil Blob object. pState must be-a (Blob*) populated with JSON
** data.
*/
int cson_data_src_Blob(void * pState, void * dest, unsigned int * n){
Blob * b = (Blob*)pState;
*n = blob_read( b, dest, *n );
return 0;
}
/*
** Convenience wrapper around cson_output() which appends the output
** to pDest. pOpt may be NULL, in which case g.json.outOpt will be used.
*/
int cson_output_Blob( cson_value const * pVal, Blob * pDest, cson_output_opt const * pOpt ){
return cson_output( pVal, cson_data_dest_Blob,
pDest, pOpt ? pOpt : &g.json.outOpt );
}
/*
** Convenience wrapper around cson_parse() which reads its input
** from pSrc. pSrc is rewound before parsing.
**
** pInfo may be NULL. If it is not NULL then it will contain details
** about the parse state when this function returns.
**
** On success a new JSON Object or Array is returned (owned by the
** caller). On error NULL is returned.
*/
cson_value * cson_parse_Blob( Blob * pSrc, cson_parse_info * pInfo ){
cson_value * root = NULL;
blob_rewind( pSrc );
cson_parse( &root, cson_data_src_Blob, pSrc, NULL, pInfo );
return root;
}
/*
** Implements the cson_data_dest_f() interface and outputs the data to
** cgi_append_content(). pState is ignored.
**/
int cson_data_dest_cgi(void * pState, void const * src, unsigned int n){
cgi_append_content( (char const *)src, (int)n );
return 0;
}
/*
** Returns a string in the form FOSSIL-XXXX, where XXXX is a
** left-zero-padded value of code. The returned buffer is static, and
** must be copied if needed for later. The returned value will always
** be 11 bytes long (not including the trailing NUL byte).
**
** In practice we will only ever call this one time per app execution
** when constructing the JSON response envelope, so the static buffer
** "shouldn't" be a problem.
**
*/
char const * json_rc_cstr( int code ){
enum { BufSize = 12 };
static char buf[BufSize] = {'F','O','S','S','I','L','-',0};
assert((code >= 1000) && (code <= 9999) && "Invalid Fossil/JSON code.");
sprintf(buf+7,"%04d", code);
return buf;
}
/*
** Adds v to the API-internal cleanup mechanism. key is ingored
** (legacy) but might be re-introduced and "should" be a unique
** (app-wide) value. Failure to insert an item may be caused by any
** of the following:
**
** - Allocation error.
** - g.json.gc.a is NULL
** - key is NULL or empty.
**
** Returns 0 on success.
**
** Ownership of v is transfered to (or shared with) g.json.gc, and v
** will be valid until that object is cleaned up or some internal code
** incorrectly removes it from the gc (which we never do). If this
** function fails, it is fatal to the app (as it indicates an
** allocation error (more likely than not) or a serious internal error
** such as numeric overflow).
*/
void json_gc_add( char const * key, cson_value * v ){
int const rc = cson_array_append( g.json.gc.a, v );
assert( NULL != g.json.gc.a );
if( 0 != rc ){
cson_value_free( v );
}
assert( (0==rc) && "Adding item to GC failed." );
if(0!=rc){
fprintf(stderr,"%s: FATAL: alloc error.\n", fossil_nameofexe())
/* reminder: allocation error is the only reasonable cause of
error here, provided g.json.gc.a and v are not NULL.
*/
;
fossil_exit(1)/*not fossil_panic() b/c it might land us somewhere
where this function is called again.
*/;
}
}
/*
** Returns the value of json_rc_cstr(code) as a new JSON
** string, which is owned by the caller and must eventually
** be cson_value_free()d or transfered to a JSON container.
*/
cson_value * json_rc_string( int code ){
return cson_value_new_string( json_rc_cstr(code), 11 );
}
cson_value * json_new_string( char const * str ){
return str
? cson_value_new_string(str,strlen(str))
: NULL;
}
cson_value * json_new_string_f( char const * fmt, ... ){
cson_value * v;
char * zStr;
va_list vargs;
va_start(vargs,fmt);
zStr = vmprintf(fmt,vargs);
va_end(vargs);
v = cson_value_new_string(zStr, strlen(zStr));
free(zStr);
return v;
}
cson_value * json_new_int( int v ){
return cson_value_new_integer((cson_int_t)v);
}
/*
** Gets a POST/POST.payload/GET/COOKIE/ENV value. The returned memory
** is owned by the g.json object (one of its sub-objects). Returns
** NULL if no match is found.
**
** ENV means the system environment (getenv()).
**
** Precedence: POST.payload, GET/COOKIE/non-JSON POST, JSON POST, ENV.
**
** FIXME: the precedence SHOULD be: GET, POST.payload, POST, COOKIE,
** ENV, but the amalgamation of the GET/POST vars makes it difficult
** for me to do that. Since fossil only uses one cookie, cookie
** precedence isn't a real/high-priority problem.
*/
cson_value * json_getenv( char const * zKey ){
cson_value * rc;
rc = g.json.reqPayload.o
? cson_object_get( g.json.reqPayload.o, zKey )
: NULL;
if(rc){
return rc;
}
rc = cson_object_get( g.json.param.o, zKey );
if( rc ){
return rc;
}
rc = cson_object_get( g.json.post.o, zKey );
if(rc){
return rc;
}else{
char const * cv = PD(zKey,NULL);
if(!cv && !g.isHTTP){
/* reminder to self: in CLI mode i'd like to try
find_option(zKey,NULL,XYZ) here, but we don't have a sane
default for the XYZ param here.
*/
cv = getenv(zKey);
}
if(cv){/*transform it to JSON for later use.*/
/* use sscanf() to figure out if it's an int,
and transform it to JSON int if it is.
FIXME: use strtol(), since it has more accurate
error handling.
*/
int intVal = -1;
char endOfIntCheck;
int const scanRc = sscanf(cv,"%d%c",&intVal, &endOfIntCheck)
/* The %c bit there is to make sure that we don't accept 123x
as a number. sscanf() returns the number of tokens
successfully parsed, so an RC of 1 will be correct for "123"
but "123x" will have RC==2.
But it appears to not be working that way :/
*/
;
if(1==scanRc){
json_setenv( zKey, cson_value_new_integer(intVal) );
}else{
rc = cson_value_new_string(cv,strlen(cv));
json_setenv( zKey, rc );
}
return rc;
}else{
return NULL;
}
}
}
/*
** Wrapper around json_getenv() which...
**
** If it finds a value and that value is-a JSON number or is a string
** which looks like an integer or is-a JSON bool/null then it is
** converted to an int. If none of those apply then dflt is returned.
*/
int json_getenv_int(char const * pKey, int dflt ){
cson_value const * v = json_getenv(pKey);
if(!v){
return dflt;
}else if( cson_value_is_number(v) ){
return (int)cson_value_get_integer(v);
}else if( cson_value_is_string(v) ){
char const * sv = cson_string_cstr(cson_value_get_string(v));
assert( (NULL!=sv) && "This is quite unexpected." );
return sv ? atoi(sv) : dflt;
}else if( cson_value_is_bool(v) ){
return cson_value_get_bool(v) ? 1 : 0;
}else if( cson_value_is_null(v) ){
return 0;
}else{
/* we should arguably treat JSON null as 0. */
return dflt;
}
}
/*
** Wrapper around json_getenv() which tries to evaluate a payload/env
** value as a boolean. Uses mostly the same logic as
** json_getenv_int(), with the addition that string values which
** either start with a digit 1..9 or the letters [tT] are considered
** to be true. If this function cannot find a matching key/value then
** dflt is returned. e.g. if it finds the key but the value is-a
** Object then dftl is returned.
**
** If an entry is found, this function guarantees that it will return
** either 0 or 1, as opposed to "0 or non-zero", so that clients can
** pass a different value as dflt. Thus they can use, e.g. -1 to know
** whether or not this function found a match (it will return -1 in
** that case).
*/
char json_getenv_bool(char const * pKey, char dflt ){
cson_value const * v = json_getenv(pKey);
if(!v){
return dflt;
}else if( cson_value_is_number(v) ){
return cson_value_get_integer(v) ? 1 : 0;
}else if( cson_value_is_string(v) ){
char const * sv = cson_string_cstr(cson_value_get_string(v));
if(!*sv || ('0'==*sv)){
return 0;
}else{
return (('t'==*sv) || ('T'==*sv)
|| (('1'<=*sv) && ('9'>=*sv)))
? 1 : 0;
}
}else if( cson_value_is_bool(v) ){
return cson_value_get_bool(v) ? 1 : 0;
}else if( cson_value_is_null(v) ){
return 0;
}else{
return dflt;
}
}
/*
** Returns the string form of a json_getenv() value, but ONLY If that
** value is-a String. Non-strings are not converted to strings for
** this purpose. Returned memory is owned by g.json or fossil and is
** valid until end-of-app or the given key is replaced in fossil's
** internals via cgi_replace_parameter() and friends or json_setenv().
*/
char const * json_getenv_cstr( char const * zKey ){
return cson_value_get_cstr( json_getenv(zKey) );
}
/*
** An extended form of find_option() which tries to look up a combo
** GET/POST/CLI argument.
**
** zKey must be the GET/POST parameter key. zCLILong must be the "long
** form" CLI flag (NULL means to use zKey). zCLIShort may be NULL or
** the "short form" CLI flag (if NULL, no short form is used).
**
** If argPos is >=0 and no other match is found,
** json_command_arg(argPos) is also checked.
**
** On error (no match found) NULL is returned.
**
** This ONLY works for String JSON/GET/CLI values, not JSON
** booleans and whatnot.
*/
char const * json_find_option_cstr2(char const * zKey,
char const * zCLILong,
char const * zCLIShort,
int argPos){
char const * rc = NULL;
assert(NULL != zKey);
if(!g.isHTTP){
rc = find_option(zCLILong ? zCLILong : zKey,
zCLIShort, 1);
}
if(!rc && fossil_has_json()){
rc = json_getenv_cstr(zKey);
}
if(!rc && (argPos>=0)){
rc = json_command_arg((unsigned char)argPos);
}
return rc;
}
char const * json_find_option_cstr(char const * zKey,
char const * zCLILong,
char const * zCLIShort){
return json_find_option_cstr2(zKey, zCLIShort, zCLIShort, -1);
}
/*
** The boolean equivalent of json_find_option_cstr().
** If the option is not found, dftl is returned.
*/
char json_find_option_bool(char const * zKey,
char const * zCLILong,
char const * zCLIShort,
char dflt ){
char rc = -1;
if(!g.isHTTP){
if(NULL != find_option(zCLILong ? zCLILong : zKey,
zCLIShort, 0)){
rc = 1;
}
}
if((-1==rc) && fossil_has_json()){
rc = json_getenv_bool(zKey,-1);
}
return (-1==rc) ? dflt : rc;
}
/*
** The integer equivalent of json_find_option_cstr().
** If the option is not found, dftl is returned.
*/
int json_find_option_int(char const * zKey,
char const * zCLILong,
char const * zCLIShort,
int dflt ){
enum { Magic = -947 };
int rc = Magic;
if(!g.isHTTP){
/* FIXME: use strtol() for better error/dflt handling. */
char const * opt = find_option(zCLILong ? zCLILong : zKey,
zCLIShort, 1);
if(NULL!=opt){
rc = atoi(opt);
}
}
if(Magic==rc){
rc = json_getenv_int(zKey,Magic);
}
return (Magic==rc) ? dflt : rc;
}
/*
** Adds v to g.json.param.o using the given key. May cause any prior
** item with that key to be destroyed (depends on current reference
** count for that value). On success, transfers (or shares) ownership
** of v to (or with) g.json.param.o. On error ownership of v is not
** modified.
*/
int json_setenv( char const * zKey, cson_value * v ){
return cson_object_set( g.json.param.o, zKey, v );
}
/*
** Guesses a RESPONSE Content-Type value based (primarily) on the
** HTTP_ACCEPT header.
**
** It will try to figure out if the client can support
** application/json or application/javascript, and will fall back to
** text/plain if it cannot figure out anything more specific.
**
** Returned memory is static and immutable, but if the environment
** changes after calling this then subsequent calls to this function
** might return different (also static/immutable) values.
*/
char const * json_guess_content_type(){
char const * cset;
char doUtf8;
cset = PD("HTTP_ACCEPT_CHARSET",NULL);
doUtf8 = ((NULL == cset) || (NULL!=strstr("utf-8",cset)))
? 1 : 0;
if( g.json.jsonp ){
return doUtf8
? "application/javascript; charset=utf-8"
: "application/javascript";
}else{
/*
Content-type
If the browser does not sent an ACCEPT for application/json
then we fall back to text/plain.
*/
char const * cstr;
cstr = PD("HTTP_ACCEPT",NULL);
if( NULL == cstr ){
return doUtf8
? "application/json; charset=utf-8"
: "application/json";
}else{
if( strstr( cstr, "application/json" )
|| strstr( cstr, "*/*" ) ){
return doUtf8
? "application/json; charset=utf-8"
: "application/json";
}else{
return "text/plain";
}
}
}
}
/*
** Sends pResponse to the output stream as the response object. This
** function does no validation of pResponse except to assert() that it
** is not NULL. The caller is responsible for ensuring that it meets
** API response envelope conventions.
**
** In CLI mode pResponse is sent to stdout immediately. In HTTP
** mode pResponse replaces any current CGI content but cgi_reply()
** is not called to flush the output.
**
** If g.json.jsonp is not NULL then the content type is set to
** application/javascript and the output is wrapped in a jsonp
** wrapper.
*/
void json_send_response( cson_value const * pResponse ){
assert( NULL != pResponse );
if( g.isHTTP ){
cgi_reset_content();
if( g.json.jsonp ){
cgi_printf("%s(",g.json.jsonp);
}
cson_output( pResponse, cson_data_dest_cgi, NULL, &g.json.outOpt );
if( g.json.jsonp ){
cgi_append_content(")",1);
}
}else{/*CLI mode*/
if( g.json.jsonp ){
fprintf(stdout,"%s(",g.json.jsonp);
}
cson_output_FILE( pResponse, stdout, &g.json.outOpt );
if( g.json.jsonp ){
fwrite(")\n", 2, 1, stdout);
}
}
}
/*
** Returns the current request's JSON authentication token, or NULL if
** none is found. The token's memory is owned by (or shared with)
** g.json.
**
** If an auth token is found in the GET/POST request data then fossil
** is given that data for use in authentication for this
** session. i.e. the GET/POST data overrides fossil's authentication
** cookie value (if any) and also works with clients which do not
** support cookies.
**
** Must be called once before login_check_credentials() is called or
** we will not be able to replace fossil's internal idea of the auth
** info in time (and future changes to that state may cause unexpected
** results).
**
** The result of this call are cached for future calls.
*/
cson_value * json_auth_token(){
if( !g.json.authToken ){
/* Try to get an authorization token from GET parameter, POSTed
JSON, or fossil cookie (in that order). */
g.json.authToken = json_getenv(FossilJsonKeys.authToken);
if(g.json.authToken
&& cson_value_is_string(g.json.authToken)
&& !PD(login_cookie_name(),NULL)){
/* tell fossil to use this login info.
FIXME?: because the JSON bits don't carry around
login_cookie_name(), there is a potential login hijacking
window here. We may need to change the JSON auth token to be
in the form: login_cookie_name()=...
Then again, the hardened cookie value helps ensure that
only a proper key/value match is valid.
*/
cgi_replace_parameter( login_cookie_name(), cson_value_get_cstr(g.json.authToken) );
}else if( g.isHTTP ){
/* try fossil's conventional cookie. */
/* Reminder: chicken/egg scenario regarding db access in CLI
mode because login_cookie_name() needs the db. CLI
mode does not use any authentication, so we don't need
to support it here.
*/
char const * zCookie = P(login_cookie_name());
if( zCookie && *zCookie ){
/* Transfer fossil's cookie to JSON for downstream convenience... */
cson_value * v = cson_value_new_string(zCookie, strlen(zCookie));
json_gc_add( FossilJsonKeys.authToken, v );
g.json.authToken = v;
}
}
}
return g.json.authToken;
}
/*
** IFF json.reqPayload.o is not NULL then this returns
** cson_object_get(json.reqPayload.o,pKey), else it returns NULL.
**
** The returned value is owned by (or shared with) json.reqPayload.v.
*/
cson_value * json_req_payload_get(char const *pKey){
return g.json.reqPayload.o
? cson_object_get(g.json.reqPayload.o,pKey)
: NULL;
}
/*
** Initializes some JSON bits which need to be initialized relatively
** early on. It should only be called from cgi_init() or
** json_cmd_top() (early on in those functions).
**
** Initializes g.json.gc and g.json.param. This code does not (and
** must not) rely on any of the fossil environment having been set
** up. e.g. it must not use cgi_parameter() and friends because this
** must be called before those data are initialized.
*/
void json_main_bootstrap(){
cson_value * v;
assert( (NULL == g.json.gc.v) && "cgi_json_bootstrap() was called twice!" );
/* g.json.gc is our "garbage collector" - where we put JSON values
which need a long lifetime but don't have a logical parent to put
them in.
*/
v = cson_value_new_array();
g.json.gc.v = v;
g.json.gc.a = cson_value_get_array(v);
cson_value_add_reference(v)
/* Needed to allow us to include this value in other JSON
containers without transfering ownership to those containers.
All other persistent g.json.XXX.v values get appended to
g.json.gc.a, and therefore already have a live reference
for this purpose.
*/
;
/*
g.json.param holds the JSONized counterpart of fossil's
cgi_parameter_xxx() family of data. We store them as JSON, as
opposed to using fossil's data directly, because we can retain
full type information for data this way (as opposed to it always
being of type string).
*/
v = cson_value_new_object();
g.json.param.v = v;
g.json.param.o = cson_value_get_object(v);
json_gc_add("$PARAMS", v);
}
/*
** Appends a warning object to the (pending) JSON response.
**
** Code must be a FSL_JSON_W_xxx value from the FossilJsonCodes enum.
**
** A Warning object has this JSON structure:
**
** { "code":integer, "text":"string" }
**
** But the text part is optional.
**
** If msg is non-NULL and not empty then it is used as the "text"
** property's value. It is copied, and need not refer to static
** memory.
**
** CURRENTLY this code only allows a given warning code to be
** added one time, and elides subsequent warnings. The intention
** is to remove that burden from loops which produce warnings.
**
** FIXME: if msg is NULL then use a standard string for
** the given code. If !*msg then elide the "text" property,
** for consistency with how json_err() works.
*/
void json_warn( int code, char const * fmt, ... ){
cson_object * obj = NULL;
assert( (code>FSL_JSON_W_START)
&& (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);
}
obj = cson_new_object();
cson_array_append(g.json.warnings.a, cson_object_value(obj));
cson_object_set(obj,"code",cson_value_new_integer(code));
if(fmt && *fmt){
/* FIXME: treat NULL fmt as standard warning message for
the code, but we don't have those yet.
*/
va_list vargs;
char * msg;
va_start(vargs,fmt);
msg = vmprintf(fmt,vargs);
va_end(vargs);
cson_object_set(obj,"text", cson_value_new_string(msg,strlen(msg)));
free(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. Note that tokenization happens before dehttpize(),
** which is significant if the ENcoded tokens might contain the
** separator character.
**
** 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 tokens _encountered_. On error a
** NEGATIVE number is returned - its absolute value is the number of
** tokens encountered (i.e. it reveals which token in zStr was
** problematic).
**
** Achtung: leading and trailing whitespace of elements are elided.
**
** 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 /* current start-of-token */;
unsigned int len = 0 /* current token's length */;
int rc = 0 /* return code (number of added elements)*/;
char skipWs = fossil_isspace(separator) ? 0 : 1;
assert( zStr && target );
while( fossil_isspace(*p) ){
++p;
}
head = p;
for( ; ; ++p){
if( !*p || (separator == *p) ){
if( len ){/* append head..(head+len) as next array
element. */
cson_value * part = NULL;
char * zPart = NULL;
++rc;
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 ) ){
cson_value_free(part);
rc = -rc;
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;
while( *head && fossil_isspace(*head) ){
++head;
++p;
}
if(!*head){
break;
}
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 NULL (if splitting failed
** in any way or produced no tokens).
**
** 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 = 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.isJsonMode = 1;
g.json.resultCode = 0;
g.json.cmd.offset = -1;
g.json.jsonp = PD("jsonp",NULL)
/* FIXME: do some sanity checking on g.json.jsonp and ignore it
if it is not halfway reasonable.
*/
;
if( !g.isHTTP && g.fullHttpReply ){
/* workaround for server mode, so we see it as CGI mode. */
g.isHTTP = 1;
}
if(g.isHTTP){
cgi_set_content_type(json_guess_content_type())
/* reminder: must be done after g.json.jsonp is initialized */
;
#if 0
/* Calling this seems to trigger an SQLITE_MISUSE warning???
Maybe it's not legal to set the logger more than once?
*/
sqlite3_config(SQLITE_CONFIG_LOG, NULL, 0)
/* avoids debug messages on stderr in JSON mode */
;
#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 );
/*
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;
}
if('-' == *arg){
/* workaround to skip CLI args so that
json_command_arg() does not see them.
This assumes that all arguments come LAST
on the command line.
*/
break;
}
part = cson_value_new_string(arg,strlen(arg));
cson_array_append(g.json.cmd.a, part);
}
}
while(!g.isHTTP){
/* Simulate JSON POST data via input file. Pedantic reminder:
error handling does not honor user-supplied g.json.outOpt
because outOpt cannot (generically) be configured until after
POST-reading is finished.
*/
FILE * inFile = NULL;
char const * jfile = find_option("json-input",NULL,1);
if(!jfile || !*jfile){
break;
}
inFile = (0==strcmp("-",jfile))
? stdin
: fopen(jfile,"rb");
if(!inFile){
g.json.resultCode = FSL_JSON_E_FILE_OPEN_FAILED;
fossil_fatal("Could not open JSON file [%s].",jfile)
/* Does not return. */
;
}
cgi_parse_POST_JSON(inFile, 0);
if( stdin != inFile ){
fclose(inFile);
}
break;
}
/* 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(1 == cson_array_length_get(g.json.cmd.a)){
/* special case: if we're at the top path, look for
a "command" request arg which specifies which command
to run.
*/
char const * cmd = json_getenv_cstr("command");
if(cmd){
json_string_split(cmd, '/', 0, g.json.cmd.a);
g.json.cmd.commandStr = cmd;
}
}
if(!g.json.jsonp){
g.json.jsonp = json_find_option_cstr("jsonp",NULL,NULL);
}
if(!g.isHTTP){
g.json.errorDetailParanoia = 0 /*disable error code dumb-down for CLI mode*/;
}
{/* set up JSON output formatting options. */
int indent = -1;
char const * indentStr = NULL;
indent = json_find_option_int("indent",NULL,"I",-1);
g.json.outOpt.indentation = (0>indent)
? (g.isHTTP ? 0 : 1)
: (unsigned char)indent;
g.json.outOpt.addNewline = g.isHTTP
? 0
: (g.json.jsonp ? 0 : 1);
}
if( g.isHTTP ){
json_auth_token()/* will copy our auth token, if any, to fossil's
core, which we need before we call
login_check_credentials(). */;
login_check_credentials()/* populates g.perm */;
}
else{
db_find_and_open_repository(OPEN_ANY_SCHEMA,0);
}
}
/*
** Returns the ndx'th item in the "command path", where index 0 is the
** position of the "json" part of the path. Returns NULL if ndx is out
** of bounds or there is no "json" path element.
**
** In CLI mode the "path" is the list of arguments (skipping argv[0]).
** In server/CGI modes the path is taken from PATH_INFO.
**
** The returned bytes are owned by g.json.cmd.v and _may_ be
** invalidated if that object is modified (depending on how it is
** modified).
**
** Note that CLI options are not included in the command path. Use
** find_option() to get those.
**
*/
char const * json_command_arg(unsigned char ndx){
cson_array * ar = g.json.cmd.a;
assert((NULL!=ar) && "Internal error. Was json_mode_bootstrap() called?");
assert((g.argc>1) && "Internal error - we never should have gotten this far.");
if( g.json.cmd.offset < 0 ){
/* first-time setup. */
short i = 0;
#define NEXT cson_string_cstr( \
cson_value_get_string( \
cson_array_get(ar,i) \
))
char const * tok = NEXT;
while( tok ){
if( !g.isHTTP/*workaround for "abbreviated name" in CLI mode*/
? (0==strcmp(g.argv[1],tok))
: (0==strncmp("json",tok,4))
){
g.json.cmd.offset = i;
break;
}
++i;
tok = NEXT;
}
}
#undef NEXT
if(g.json.cmd.offset < 0){
return NULL;
}else{
ndx = g.json.cmd.offset + ndx;
return cson_string_cstr(cson_value_get_string(cson_array_get( ar, g.json.cmd.offset + ndx )));
}
}
/*
** If g.json.reqPayload.o is NULL then NULL is returned, else the
** given property is searched for in the request payload. If found it
** is returned. The returned value is owned by (or shares ownership
** with) g.json, and must NOT be cson_value_free()'d by the
** caller.
*/
cson_value * json_payload_property( char const * key ){
return g.json.reqPayload.o ?
cson_object_get( g.json.reqPayload.o, key )
: NULL;
}
/* Returns the C-string form of json_auth_token(), or NULL
** if json_auth_token() returns NULL.
*/
char const * json_auth_token_cstr(){
return cson_value_get_cstr( json_auth_token() );
}
/*
** Returns the JsonPageDef with the given name, or NULL if no match is
** found.
**
** head must be a pointer to an array of JsonPageDefs in which the
** last entry has a NULL name.
*/
JsonPageDef const * json_handler_for_name( char const * name, JsonPageDef const * head ){
JsonPageDef const * pageDef = head;
assert( head != NULL );
if(name && *name) for( ; pageDef->name; ++pageDef ){
if( 0 == strcmp(name, pageDef->name) ){
return pageDef;
}
}
return NULL;
}
/*
** Given a Fossil/JSON result code, this function "dumbs it down"
** according to the current value of g.json.errorDetailParanoia. The
** dumbed-down value is returned.
**
** This function assert()s that code is in the inclusive range 0 to
** 9999.
**
** Note that WARNING codes (1..999) are never dumbed down.
**
*/
static int json_dumbdown_rc( int code ){
if(!g.json.errorDetailParanoia
|| !code
|| ((code>=FSL_JSON_W_START) && (code<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 cast(strftime('%%s',%lf) as int)",j)
);
}
/*
** Returns a timestamp value.
*/
cson_int_t json_timestamp(){
return (cson_int_t)time(0);
}
/*
** Returns a new JSON value (owned by the caller) representing
** a timestamp. If timeVal is < 0 then time(0) is used to fetch
** the time, else timeVal is used as-is
*/
cson_value * json_new_timestamp(cson_int_t timeVal){
return cson_value_new_integer((timeVal<0) ? (cson_int_t)time(0) : timeVal);
}
/*
** Internal helper for json_create_response(). Appends the first
** g.json.dispatchDepth elements of g.json.cmd.a, skipping the first
** one (the "json" part), to a string and returns that string value
** (which is owned by the caller).
*/
static cson_value * json_response_command_path(){
if(!g.json.cmd.a){
return NULL;
}else{
cson_value * rc = NULL;
Blob path = empty_blob;
char const * part;
unsigned int aLen = g.json.dispatchDepth+1; /*cson_array_length_get(g.json.cmd.a);*/
unsigned int i = 1;
for( ; i < aLen; ++i ){
char const * part = cson_string_cstr(cson_value_get_string(cson_array_get(g.json.cmd.a, i)));
if(!part){
fossil_warning("Iterating further than expected in %s.",
__FILE__);
break;
}
blob_appendf(&path,"%s%s", (i>1 ? "/": ""), part);
}
rc = json_new_string((blob_size(&path)>0)
? blob_buffer(&path)
: "")
/* reminder; we need an empty string instead of NULL
in this case, to avoid what outwardly looks like
(but is not) an allocation error in
json_create_response().
*/
;
blob_reset(&path);
return rc;
}
}
/*
** Returns a JSON Object representation of the global g object.
** Returned value is owned by the caller.
*/
cson_value * json_g_to_json(){
cson_object * o = NULL;
cson_object * pay = NULL;
pay = o = cson_new_object();
#define INT(OBJ,K) cson_object_set(o, #K, json_new_int(OBJ.K))
#define CSTR(OBJ,K) cson_object_set(o, #K, OBJ.K ? json_new_string(OBJ.K) : cson_value_null())
#define VAL(K,V) cson_object_set(o, #K, (V) ? (V) : cson_value_null())
VAL(capabilities, json_cap_value());
INT(g, argc);
INT(g, isConst);
INT(g, useAttach);
INT(g, configOpen);
INT(g, repositoryOpen);
INT(g, localOpen);
INT(g, minPrefix);
INT(g, fSqlTrace);
INT(g, fSqlStats);
INT(g, fSqlPrint);
INT(g, fQuiet);
INT(g, fHttpTrace);
INT(g, fSystemTrace);
INT(g, fNoSync);
INT(g, iErrPriority);
INT(g, sslNotAvailable);
INT(g, cgiOutput);
INT(g, xferPanic);
INT(g, fullHttpReply);
INT(g, xlinkClusterOnly);
INT(g, fTimeFormat);
INT(g, markPrivate);
INT(g, clockSkewSeen);
INT(g, isHTTP);
INT(g, urlIsFile);
INT(g, urlIsHttps);
INT(g, urlIsSsh);
INT(g, urlPort);
INT(g, urlDfltPort);
INT(g, dontKeepUrl);
INT(g, useLocalauth);
INT(g, noPswd);
INT(g, userUid);
INT(g, rcvid);
INT(g, okCsrf);
INT(g, thTrace);
INT(g, isHome);
INT(g, nAux);
INT(g, allowSymlinks);
CSTR(g, zMainDbType);
CSTR(g, zHome);
CSTR(g, zLocalRoot);
CSTR(g, zPath);
CSTR(g, zExtra);
CSTR(g, zBaseURL);
CSTR(g, zTop);
CSTR(g, zContentType);
CSTR(g, zErrMsg);
CSTR(g, urlName);
CSTR(g, urlHostname);
CSTR(g, urlProtocol);
CSTR(g, urlPath);
CSTR(g, urlUser);
CSTR(g, urlPasswd);
CSTR(g, urlCanonical);
CSTR(g, urlProxyAuth);
CSTR(g, urlFossil);
CSTR(g, zLogin);
CSTR(g, zSSLIdentity);
CSTR(g, zIpAddr);
CSTR(g, zNonce);
CSTR(g, zCsrfToken);
o = cson_new_object();
cson_object_set(pay, "json", cson_object_value(o) );
INT(g.json, isJsonMode);
INT(g.json, resultCode);
INT(g.json, errorDetailParanoia);
INT(g.json, dispatchDepth);
VAL(authToken, g.json.authToken);
CSTR(g.json, jsonp);
VAL(gc, g.json.gc.v);
VAL(cmd, g.json.cmd.v);
VAL(param, g.json.param.v);
VAL(POST, g.json.post.v);
VAL(warnings, g.json.warnings.v);
/*cson_output_opt outOpt;*/
#undef INT
#undef CSTR
#undef VAL
return cson_object_value(pay);
}
/*
** Creates a new Fossil/JSON response envelope skeleton. It is owned
** by the caller, who must eventually free it using cson_value_free(),
** or add it to a cson container to transfer ownership. Returns NULL
** on error.
**
** If payload is not NULL and resultCode is 0 then it is set as the
** "payload" property of the returned object. If resultCode is
** non-zero and payload is not NULL then this function calls
** cson_value_free(payload) and does not insert the payload into the
** response. In either case, onwership of payload is transfered to (or
** shared with, if the caller holds a reference) this function.
**
** pMsg is an optional message string property (resultText) of the
** response. If resultCode is non-0 and pMsg is NULL then
** json_err_cstr() is used to get the error string. The caller may
** provide his own or may use an empty string to suppress the
** resultText property.
**
*/
cson_value * json_create_response( int resultCode,
char const * pMsg,
cson_value * payload){
cson_value * v = NULL;
cson_value * tmp = NULL;
cson_object * o = NULL;
int rc;
resultCode = json_dumbdown_rc(resultCode);
o = cson_new_object();
v = cson_object_value(o);
if( ! o ) return NULL;
#define SET(K) if(!tmp) goto cleanup; \
rc = cson_object_set( o, K, tmp ); \
if(rc) do{\
cson_value_free(tmp); \
tmp = NULL; \
goto cleanup; \
}while(0)
tmp = cson_value_new_string(MANIFEST_UUID,strlen(MANIFEST_UUID));
SET("fossil");
tmp = json_new_timestamp(-1);
SET(FossilJsonKeys.timestamp);
if( 0 != resultCode ){
if( ! pMsg ){
pMsg = g.zErrMsg;
if(!pMsg){
pMsg = json_err_cstr(resultCode);
}
}
tmp = json_new_string(json_rc_cstr(resultCode));
SET(FossilJsonKeys.resultCode);
}
if( pMsg && *pMsg ){
tmp = json_new_string(pMsg);
SET(FossilJsonKeys.resultText);
}
if(g.json.cmd.commandStr){
tmp = json_new_string(g.json.cmd.commandStr);
}else{
tmp = json_response_command_path();
}
SET("command");
tmp = json_getenv(FossilJsonKeys.requestId);
if( tmp ) cson_object_set( o, FossilJsonKeys.requestId, tmp );
if(0){/* these are only intended for my own testing...*/
if(g.json.cmd.v){
tmp = g.json.cmd.v;
SET("$commandPath");
}
if(g.json.param.v){
tmp = g.json.param.v;
SET("$params");
}
if(0){/*Only for debuggering, add some info to the response.*/
tmp = cson_value_new_integer( g.json.cmd.offset );
cson_object_set( o, "cmd.offset", tmp );
cson_object_set( o, "isCGI", cson_value_new_bool( g.isHTTP ) );
}
}
if(HAS_TIMER){
/* This is, philosophically speaking, not quite the right place
for ending the timer, but this is the one function which all of
the JSON exit paths use (and they call it after processing,
just before they end).
*/
double span;
span = END_TIMER;
/* i'm actually seeing sub-ms runtimes in some tests, but a time of
0 is "just wrong", so we'll bump that up to 1ms.
*/
cson_object_set(o,"procTimeMs", cson_value_new_integer((cson_int_t)((span>1.0)?span:1)));
}
if(g.json.warnings.v){
tmp = g.json.warnings.v;
SET("warnings");
}
/* Only add the payload to SUCCESS responses. Else delete it. */
if( NULL != payload ){
if( resultCode ){
cson_value_free(payload);
payload = NULL;
}else{
tmp = payload;
SET(FossilJsonKeys.payload);
}
}
if(json_find_option_bool("debugFossilG","json-debug-g",NULL,0)
&&(g.perm.Admin||g.perm.Setup)){
tmp = json_g_to_json();
SET("g");
}
#undef SET
goto ok;
cleanup:
cson_value_free(v);
v = NULL;
ok:
return v;
}
/*
** Outputs a JSON error response to either the cgi_xxx() family of
** buffers (in CGI/server mode) or stdout (in CLI mode). If rc is 0
** then g.json.resultCode is used. If that is also 0 then the "Unknown
** Error" code is used.
**
** If g.isHTTP then the generated JSON error response object replaces
** any currently buffered page output. Because the output goes via
** the cgi_xxx() family of functions, this function inherits any
** compression which fossil does for its output.
**
** If alsoOutput is true AND g.isHTTP then cgi_reply() is called to
** flush the output (and headers). Generally only do this if you are
** about to call exit().
**
** !g.isHTTP then alsoOutput is ignored and all output is sent to
** stdout immediately.
**
** For generating the resultText property: if msg is not NULL then it
** is used as-is. If it is NULL then g.zErrMsg is checked, and if that
** is NULL then json_err_cstr(code) is used.
*/
void json_err( int code, char const * msg, char alsoOutput ){
int rc = code ? code : (g.json.resultCode
? g.json.resultCode
: FSL_JSON_E_UNKNOWN);
cson_value * resp = NULL;
rc = json_dumbdown_rc(rc);
if( rc && !msg ){
msg = g.zErrMsg;
if(!msg){
msg = json_err_cstr(rc);
}
}
resp = json_create_response(rc, msg, NULL);
if(!resp){
/* about the only error case here is out-of-memory. DO NOT
call fossil_panic() here because that calls this function.
*/
fprintf(stderr, "%s: Fatal error: could not allocate "
"response object.\n", fossil_nameofexe());
fossil_exit(1);
}
if( g.isHTTP ){
if(alsoOutput){
json_send_response(resp);
}else{
/* almost a duplicate of json_send_response() :( */
cgi_reset_content();
if( g.json.jsonp ){
cgi_printf("%s(",g.json.jsonp);
}
cson_output( resp, cson_data_dest_cgi, NULL, &g.json.outOpt );
if( g.json.jsonp ){
cgi_append_content(")",1);
}
}
}else{
json_send_response(resp);
}
cson_value_free(resp);
}
/*
** Sets g.json.resultCode and g.zErrMsg, but does not report the error
** via json_err(). Returns the code passed to it.
**
** code must be in the inclusive range 1000..9999.
*/
int json_set_err( int code, char const * fmt, ... ){
assert( (code>=1000) && (code<=9999) );
free(g.zErrMsg);
g.json.resultCode = code;
if(!fmt || !*fmt){
g.zErrMsg = mprintf("%s", json_err_cstr(code));
}else{
va_list vargs;
va_start(vargs,fmt);
char * msg = vmprintf(fmt, vargs);
va_end(vargs);
g.zErrMsg = msg;
}
return code;
}
/*
** Iterates through a prepared SELECT statement and converts each row
** to a JSON object. If pTgt is not NULL then this function will
** append the results to pTgt and return cson_array_value(pTgt). If
** pTgt is NULL then a new Array object is created and returned (owned
** by the caller). Each row of pStmt is converted to an Object and
** appended to the array. If the result set has no rows AND pTgt is
** NULL then NULL (not an empty array) is returned.
*/
cson_value * json_stmt_to_array_of_obj(Stmt *pStmt,
cson_array * pTgt){
cson_array * a = pTgt;
char const * warnMsg = NULL;
cson_value * colNamesV = NULL;
cson_array * colNames = NULL;
while( (SQLITE_ROW==db_step(pStmt)) ){
cson_value * row = NULL;
if(!a){
a = cson_new_array();
assert(NULL!=a);
}
if(!colNames){
colNamesV = cson_sqlite3_column_names(pStmt->pStmt);
assert(NULL != colNamesV);
cson_value_add_reference(colNamesV);
colNames = cson_value_get_array(colNamesV);
assert(NULL != colNames);
}
row = cson_sqlite3_row_to_object2(pStmt->pStmt, colNames);
if(!row && !warnMsg){
warnMsg = "Could not convert at least one result row to JSON.";
continue;
}
if( 0 != cson_array_append(a, row) ){
cson_value_free(row);
assert( 0 && "Alloc error.");
if(pTgt != a) {
cson_free_array(a);
}
return NULL;
}
}
cson_value_free(colNamesV);
if(warnMsg){
json_warn( FSL_JSON_W_ROW_TO_JSON_FAILED, warnMsg );
}
return cson_array_value(a);
}
/*
** Works just like json_stmt_to_array_of_obj(), but each row in the
** result set is represented as an Array of values instead of an
** Object (key/value pairs). If pTgt is NULL and the statement
** has no results then NULL is returned, not an empty array.
*/
cson_value * json_stmt_to_array_of_array(Stmt *pStmt,
cson_array * pTgt){
cson_array * a = pTgt;
while( (SQLITE_ROW==db_step(pStmt)) ){
cson_value * row = NULL;
if(!a){
a = cson_new_array();
assert(NULL!=a);
}
row = cson_sqlite3_row_to_array(pStmt->pStmt);
cson_array_append(a, row);
}
return cson_array_value(a);
}
/*
** Executes the given SQL and runs it through
** json_stmt_to_array_of_obj(), returning the result of that
** function. If resetBlob is true then blob_reset(pSql) is called
** after preparing the query.
**
** pTgt has the same semantics as described for
** json_stmt_to_array_of_obj().
*/
cson_value * json_sql_to_array_of_obj(Blob * pSql, cson_array * pTgt,
char resetBlob){
Stmt q = empty_Stmt;
cson_value * pay = NULL;
assert( blob_size(pSql) > 0 );
db_prepare(&q, "%s", blob_str(pSql));
if(resetBlob){
blob_reset(pSql);
}
pay = json_stmt_to_array_of_obj(&q, pTgt);
db_finalize(&q);
return pay;
}
/*
** If the given COMMIT rid has any tags associated with it, this
** function returns a JSON Array containing the tag names, else it
** returns NULL.
**
** See info_tags_of_checkin() for more details (this is simply a JSON
** wrapper for that function).
*/
cson_value * json_tags_for_checkin_rid(int rid, char propagatingOnly){
cson_value * v = NULL;
char * tags = info_tags_of_checkin(rid, propagatingOnly);
if(tags){
if(*tags){
v = json_string_split2(tags,',',0);
}
free(tags);
}
return v;
}
/*
** Returns a "new" value representing the boolean value of zVal
** (false if zVal is NULL). Note that cson does not really allocate
** any memory for boolean values, but they "should" (for reasons of
** style and philosophy) be cleaned up like any other values (but
** it's a no-op for bools).
*/
cson_value * json_value_to_bool(cson_value const * zVal){
return cson_value_get_bool(zVal)
? cson_value_true()
: cson_value_false();
}
/*
** Impl of /json/resultCodes
**
*/
cson_value * json_page_resultCodes(){
cson_value * listV = cson_value_new_array();
cson_array * list = cson_value_get_array(listV);
cson_value * objV = NULL;
cson_object * obj = NULL;
cson_string * kRC;
cson_string * kSymbol;
cson_string * kNumber;
cson_string * kDesc;
int rc = cson_array_reserve( list, 35 );
if(rc){
assert( 0 && "Allocation error.");
exit(1);
}
kRC = cson_new_string("resultCode",10);
kSymbol = cson_new_string("cSymbol",7);
kNumber = cson_new_string("number",6);
kDesc = cson_new_string("description",11);
#define C(K) objV = cson_value_new_object(); obj = cson_value_get_object(objV); \
cson_object_set_s(obj, kRC, json_new_string(json_rc_cstr(FSL_JSON_E_##K)) ); \
cson_object_set_s(obj, kSymbol, json_new_string("FSL_JSON_E_"#K) ); \
cson_object_set_s(obj, kNumber, cson_value_new_integer(FSL_JSON_E_##K) ); \
cson_object_set_s(obj, kDesc, json_new_string(json_err_cstr(FSL_JSON_E_##K))); \
cson_array_append( list, objV ); obj = NULL; objV = NULL
C(GENERIC);
C(INVALID_REQUEST);
C(UNKNOWN_COMMAND);
C(UNKNOWN);
C(TIMEOUT);
C(ASSERT);
C(ALLOC);
C(NYI);
C(PANIC);
C(MANIFEST_READ_FAILED);
C(FILE_OPEN_FAILED);
C(AUTH);
C(MISSING_AUTH);
C(DENIED);
C(WRONG_MODE);
C(LOGIN_FAILED);
C(LOGIN_FAILED_NOSEED);
C(LOGIN_FAILED_NONAME);
C(LOGIN_FAILED_NOPW);
C(LOGIN_FAILED_NOTFOUND);
C(USAGE);
C(INVALID_ARGS);
C(MISSING_ARGS);
C(AMBIGUOUS_UUID);
C(UNRESOLVED_UUID);
C(RESOURCE_ALREADY_EXISTS);
C(RESOURCE_NOT_FOUND);
C(DB);
C(STMT_PREP);
C(STMT_BIND);
C(STMT_EXEC);
C(DB_LOCKED);
C(DB_NEEDS_REBUILD);
C(DB_NOT_FOUND);
C(DB_NOT_VALID);
#undef C
return listV;
}
/*
** /json/version implementation.
**
** Returns the payload object (owned by the caller).
*/
cson_value * json_page_version(){
cson_value * jval = NULL;
cson_object * jobj = NULL;
jval = cson_value_new_object();
jobj = cson_value_get_object(jval);
#define FSET(X,K) cson_object_set( jobj, K, cson_value_new_string(X,strlen(X)))
FSET(MANIFEST_UUID,"manifestUuid");
FSET(MANIFEST_VERSION,"manifestVersion");
FSET(MANIFEST_DATE,"manifestDate");
FSET(MANIFEST_YEAR,"manifestYear");
FSET(RELEASE_VERSION,"releaseVersion");
#undef FSET
cson_object_set( jobj, "releaseVersionNumber",
cson_value_new_integer(RELEASE_VERSION_NUMBER) );
cson_object_set( jobj, "resultCodeParanoiaLevel",
cson_value_new_integer(g.json.errorDetailParanoia) );
return jval;
}
/*
** Returns the current user's capabilities string as a String value.
** Returned value is owned by the caller, and will only be NULL if
** g.userUid is invalid or an out of memory error. Or, it turns out,
** in CLI mode (where there is no logged-in user).
*/
cson_value * json_cap_value(){
if(g.userUid<=0){
return NULL;
}else{
Stmt q = empty_Stmt;
cson_value * val = NULL;
db_prepare(&q, "SELECT cap FROM user WHERE uid=%d", g.userUid);
if( db_step(&q)==SQLITE_ROW ){
char const * str = (char const *)sqlite3_column_text(q.pStmt,0);
if( str ){
val = json_new_string(str);
}
}
db_finalize(&q);
return val;
}
}
/*
** Implementation for /json/cap
**
** Returned object contains details about the "capabilities" of the
** current user (what he may/may not do).
**
** This is primarily intended for debuggering, but may have
** a use in client code. (?)
*/
cson_value * json_page_cap(){
cson_value * payload = cson_value_new_object();
cson_value * sub = cson_value_new_object();
Stmt q;
cson_object * obj = cson_value_get_object(payload);
db_prepare(&q, "SELECT login, cap FROM user WHERE uid=%d", g.userUid);
if( db_step(&q)==SQLITE_ROW ){
/* reminder: we don't use g.zLogin because it's 0 for the guest
user and the HTML UI appears to currently allow the name to be
changed (but doing so would break other code). */
char const * str = (char const *)sqlite3_column_text(q.pStmt,0);
if( str ){
cson_object_set( obj, "name",
cson_value_new_string(str,strlen(str)) );
}
str = (char const *)sqlite3_column_text(q.pStmt,1);
if( str ){
cson_object_set( obj, "capabilities",
cson_value_new_string(str,strlen(str)) );
}
}
db_finalize(&q);
cson_object_set( obj, "permissionFlags", sub );
obj = cson_value_get_object(sub);
#define ADD(X,K) cson_object_set(obj, K, cson_value_new_bool(g.perm.X))
ADD(Setup,"setup");
ADD(Admin,"admin");
ADD(Delete,"delete");
ADD(Password,"password");
ADD(Query,"query"); /* don't think this one is actually used */
ADD(Write,"checkin");
ADD(Read,"checkout");
ADD(History,"history");
ADD(Clone,"clone");
ADD(RdWiki,"readWiki");
ADD(NewWiki,"createWiki");
ADD(ApndWiki,"appendWiki");
ADD(WrWiki,"editWiki");
ADD(RdTkt,"readTicket");
ADD(NewTkt,"createTicket");
ADD(ApndTkt,"appendTicket");
ADD(WrTkt,"editTicket");
ADD(Attach,"attachFile");
ADD(TktFmt,"createTicketReport");
ADD(RdAddr,"readPrivate");
ADD(Zip,"zip");
ADD(Private,"xferPrivate");
#undef ADD
return payload;
}
/*
** Implementation of the /json/stat page/command.
**
*/
cson_value * json_page_stat(){
i64 t, fsize;
int n, m;
int full;
const char *zDb;
enum { BufLen = 1000 };
char zBuf[BufLen];
cson_value * jv = NULL;
cson_object * jo = NULL;
cson_value * jv2 = NULL;
cson_object * jo2 = NULL;
if( !g.perm.Read ){
json_set_err(FSL_JSON_E_DENIED,
"Requires 'o' permissions.");
return NULL;
}
full = json_find_option_bool("full",NULL,"f",0);
#define SETBUF(O,K) cson_object_set(O, K, cson_value_new_string(zBuf, strlen(zBuf)));
jv = cson_value_new_object();
jo = cson_value_get_object(jv);
sqlite3_snprintf(BufLen, zBuf, db_get("project-name",""));
SETBUF(jo, "projectName");
/* FIXME: don't include project-description until we ensure that
zBuf will always be big enough. We "should" replace zBuf
with a blob for this purpose.
*/
fsize = file_size(g.zRepositoryName);
cson_object_set(jo, "repositorySize", cson_value_new_integer((cson_int_t)fsize));
if(full){
n = db_int(0, "SELECT count(*) FROM blob");
m = db_int(0, "SELECT count(*) FROM delta");
cson_object_set(jo, "blobCount", cson_value_new_integer((cson_int_t)n));
cson_object_set(jo, "deltaCount", cson_value_new_integer((cson_int_t)m));
if( n>0 ){
int a, b;
Stmt q;
db_prepare(&q, "SELECT total(size), avg(size), max(size)"
" FROM blob WHERE size>0");
db_step(&q);
t = db_column_int64(&q, 0);
cson_object_set(jo, "uncompressedArtifactSize",
cson_value_new_integer((cson_int_t)t));
cson_object_set(jo, "averageArtifactSize",
cson_value_new_integer((cson_int_t)db_column_int(&q, 1)));
cson_object_set(jo, "maxArtifactSize",
cson_value_new_integer((cson_int_t)db_column_int(&q, 2)));
db_finalize(&q);
if( t/fsize < 5 ){
b = 10;
fsize /= 10;
}else{
b = 1;
}
a = t/fsize;
sqlite3_snprintf(BufLen,zBuf, "%d:%d", a, b);
SETBUF(jo, "compressionRatio");
}
n = db_int(0, "SELECT count(distinct mid) FROM mlink /*scan*/");
cson_object_set(jo, "checkinCount", cson_value_new_integer((cson_int_t)n));
n = db_int(0, "SELECT count(*) FROM filename /*scan*/");
cson_object_set(jo, "fileCount", cson_value_new_integer((cson_int_t)n));
n = db_int(0, "SELECT count(*) FROM tag /*scan*/"
" WHERE +tagname GLOB 'wiki-*'");
cson_object_set(jo, "wikiPageCount", cson_value_new_integer((cson_int_t)n));
n = db_int(0, "SELECT count(*) FROM tag /*scan*/"
" WHERE +tagname GLOB 'tkt-*'");
cson_object_set(jo, "ticketCount", cson_value_new_integer((cson_int_t)n));
}/*full*/
n = db_int(0, "SELECT julianday('now') - (SELECT min(mtime) FROM event)"
" + 0.99");
cson_object_set(jo, "ageDays", cson_value_new_integer((cson_int_t)n));
cson_object_set(jo, "ageYears", cson_value_new_double(n/365.24));
sqlite3_snprintf(BufLen, zBuf, db_get("project-code",""));
SETBUF(jo, "projectCode");
sqlite3_snprintf(BufLen, zBuf, db_get("server-code",""));
SETBUF(jo, "serverCode");
cson_object_set(jo, "compiler", cson_value_new_string(COMPILER_NAME, strlen(COMPILER_NAME)));
jv2 = cson_value_new_object();
jo2 = cson_value_get_object(jv2);
cson_object_set(jo, "sqlite", jv2);
sqlite3_snprintf(BufLen, zBuf, "%.19s [%.10s] (%s)",
SQLITE_SOURCE_ID, &SQLITE_SOURCE_ID[20], SQLITE_VERSION);
SETBUF(jo2, "version");
zDb = db_name("repository");
cson_object_set(jo2, "pageCount", cson_value_new_integer((cson_int_t)db_int(0, "PRAGMA %s.page_count", zDb)));
cson_object_set(jo2, "pageSize", cson_value_new_integer((cson_int_t)db_int(0, "PRAGMA %s.page_size", zDb)));
cson_object_set(jo2, "freeList", cson_value_new_integer((cson_int_t)db_int(0, "PRAGMA %s.freelist_count", zDb)));
sqlite3_snprintf(BufLen, zBuf, "%s", db_text(0, "PRAGMA %s.encoding", zDb));
SETBUF(jo2, "encoding");
sqlite3_snprintf(BufLen, zBuf, "%s", db_text(0, "PRAGMA %s.journal_mode", zDb));
cson_object_set(jo2, "journalMode", *zBuf ? cson_value_new_string(zBuf, strlen(zBuf)) : cson_value_null());
return jv;
#undef SETBUF
}
/*
** Creates a comma-separated list of command names
** taken from zPages. zPages must be an array of objects
** whose final entry MUST have a NULL name value or results
** are undefined.
**
** The list is appended to pOut. The number of items (not bytes)
** appended are returned.
*/
static int json_pagedefs_to_string(JsonPageDef const * zPages,
Blob * pOut){
int i = 0;
for( ; zPages->name; ++zPages, ++i ){
if(g.isHTTP && zPages->runMode < 0) continue;
else if(zPages->runMode > 0) continue;
blob_appendf(pOut, zPages->name, -1);
if((zPages+1)->name){
blob_append(pOut, ", ",2);
}
}
return i;
}
cson_value * json_page_dispatch_helper(JsonPageDef const * pages){
JsonPageDef const * def;
char const * cmd = json_command_arg(1+g.json.dispatchDepth);
assert( NULL != pages );
if( ! cmd ){
Blob cmdNames = empty_blob;
json_pagedefs_to_string(pages, &cmdNames);
json_set_err(FSL_JSON_E_MISSING_ARGS,
"No subcommand specified. Try one of (%s).",
blob_str(&cmdNames));
blob_reset(&cmdNames);
return NULL;
}
def = json_handler_for_name( cmd, pages );
if(!def){
g.json.resultCode = FSL_JSON_E_UNKNOWN_COMMAND;
return NULL;
}
else{
++g.json.dispatchDepth;
return (*def->func)();
}
}
/*
** Impl of /json/rebuild. Requires admin previleges.
*/
static cson_value * json_page_rebuild(){
if( !g.perm.Admin ){
json_set_err(FSL_JSON_E_DENIED,"Requires 'a' privileges.");
return NULL;
}else{
/* Reminder: the db_xxx() ops "should" fail via the fossil core
error handlers, which will cause a JSON error and exit(). i.e. we
don't handle the errors here. TODO: confirm that all these db
routine fail gracefully in JSON mode.
On large repos (e.g. fossil's) this operation is likely to take
longer than the client timeout, which will cause it to fail (but
it's sqlite3, so it'll fail gracefully).
*/
db_close(1);
db_open_repository(g.zRepositoryName);
db_begin_transaction();
rebuild_db(0, 0, 0);
db_end_transaction(0);
return NULL;
}
}
/*
** Impl of /json/g. Requires admin/setup rights.
*/
static cson_value * json_page_g(){
if(!g.perm.Admin || !g.perm.Setup){
json_set_err(FSL_JSON_E_DENIED,
"Requires 'a' or 's' privileges.");
return NULL;
}
return json_g_to_json();
}
/* Impl in json_login.c. */
cson_value * json_page_anon_password();
/* Impl in json_artifact.c. */
cson_value * json_page_artifact();
/* Impl in json_branch.c. */
cson_value * json_page_branch();
/* Impl in json_diff.c. */
cson_value * json_page_diff();
/* Impl in json_login.c. */
cson_value * json_page_login();
/* Impl in json_login.c. */
cson_value * json_page_logout();
/* Impl in json_query.c. */
cson_value * json_page_query();
/* Impl in json_report.c. */
cson_value * json_page_report();
/* Impl in json_tag.c. */
cson_value * json_page_tag();
/* Impl in json_user.c. */
cson_value * json_page_user();
/*
** Mapping of names to JSON pages/commands. Each name is a subpath of
** /json (in CGI mode) or a subcommand of the json command in CLI mode
*/
static const JsonPageDef JsonPageDefs[] = {
/* please keep alphabetically sorted (case-insensitive) for maintenance reasons. */
{"anonymousPassword", json_page_anon_password, 0},
{"artifact", json_page_artifact, 0},
{"branch", json_page_branch,0},
{"cap", json_page_cap, 0},
{"diff", json_page_diff, 0},
{"dir", json_page_nyi, 0},
{"g", json_page_g, 0},
{"HAI",json_page_version,0},
{"login",json_page_login,0},
{"logout",json_page_logout,0},
{"query",json_page_query,0},
{"rebuild",json_page_rebuild,0},
{"report", json_page_report, 0},
{"resultCodes", json_page_resultCodes,0},
{"stat",json_page_stat,0},
{"tag", json_page_tag,0},
{"ticket", json_page_nyi,0},
{"timeline", json_page_timeline,0},
{"user",json_page_user,0},
{"version",json_page_version,0},
{"whoami",json_page_whoami,0},
{"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}
};
#ifdef FOSSIL_ENABLE_JSON /* dupe ifdef needed for mkindex */
/*
** WEBPAGE: json
**
** Pages under /json/... must be entered into JsonPageDefs.
** This function dispatches them, and is the HTTP equivalent of
** json_cmd_top().
*/
void json_page_top(void){
int rc = FSL_JSON_E_UNKNOWN_COMMAND;
char const * cmd;
cson_value * payload = NULL;
JsonPageDef const * pageDef = NULL;
BEGIN_TIMER;
json_mode_bootstrap();
cmd = json_command_arg(1);
if(!cmd || !*cmd){
goto usage;
}
/*cgi_printf("{\"cmd\":\"%s\"}\n",cmd); return;*/
pageDef = json_handler_for_name(cmd,&JsonPageDefs[0]);
if( ! pageDef ){
json_err( FSL_JSON_E_UNKNOWN_COMMAND, NULL, 0 );
return;
}else if( pageDef->runMode < 0 /*CLI only*/){
rc = FSL_JSON_E_WRONG_MODE;
}else{
rc = 0;
g.json.dispatchDepth = 1;
payload = (*pageDef->func)();
}
if( g.json.resultCode ){
cson_value_free(payload);
json_err(g.json.resultCode, NULL, 0);
}else{
cson_value * root = json_create_response(rc, NULL, payload);
json_send_response(root);
cson_value_free(root);
}
return;
usage:
{
Blob cmdNames = empty_blob;
blob_init(&cmdNames,
"No command (sub-path) specified. Try one of: ",
-1);
json_pagedefs_to_string(&JsonPageDefs[0], &cmdNames);
json_err(FSL_JSON_E_MISSING_ARGS,
blob_str(&cmdNames), 0);
blob_reset(&cmdNames);
}
}
#endif /* FOSSIL_ENABLE_JSON */
#ifdef FOSSIL_ENABLE_JSON /* dupe ifdef needed for mkindex */
/*
** This function dispatches json commands and is the CLI equivalent of
** json_page_top().
**
** COMMAND: json
**
** Usage: %fossil json SUBCOMMAND
**
** The commands include:
**
** branch
** cap
** stat
** timeline
** version (alias: HAI)
** wiki
**
**
** TODOs:
**
** tag
** ticket
** ...
**
*/
void json_cmd_top(void){
char const * cmd = NULL;
int rc = FSL_JSON_E_UNKNOWN_COMMAND;
cson_value * payload = NULL;
JsonPageDef const * pageDef;
BEGIN_TIMER;
memset( &g.perm, 0xff, sizeof(g.perm) )
/* In CLI mode fossil does not use permissions
and they all default to false. We enable them
here because (A) fossil doesn't use them in local
mode but (B) having them set gives us one less
difference in the CLI/CGI/Server-mode JSON
handling.
*/
;
json_main_bootstrap();
json_mode_bootstrap();
if( 2 > cson_array_length_get(g.json.cmd.a) ){
goto usage;
}
db_find_and_open_repository(0, 0);
#if 0
json_warn(FSL_JSON_W_ROW_TO_JSON_FAILED, "Just testing.");
json_warn(FSL_JSON_W_ROW_TO_JSON_FAILED, "Just testing again.");
#endif
cmd = json_command_arg(1);
if( !cmd || !*cmd ){
goto usage;
}
pageDef = json_handler_for_name(cmd,&JsonPageDefs[0]);
if( ! pageDef ){
json_err( FSL_JSON_E_UNKNOWN_COMMAND, NULL, 1 );
return;
}else if( pageDef->runMode > 0 /*HTTP only*/){
rc = FSL_JSON_E_WRONG_MODE;
}else{
rc = 0;
g.json.dispatchDepth = 1;
payload = (*pageDef->func)();
}
if( g.json.resultCode ){
cson_value_free(payload);
json_err(g.json.resultCode, NULL, 1);
}else{
payload = json_create_response(rc, NULL, payload);
json_send_response(payload);
cson_value_free( payload );
if((0 != rc) && !g.isHTTP){
/* FIXME: we need a way of passing this error back
up to the routine which called this callback.
e.g. add g.errCode.
*/
fossil_exit(1);
}
}
return;
usage:
{
Blob cmdNames = empty_blob;
blob_init(&cmdNames,
"No subcommand specified. Try one of: ", -1);
json_pagedefs_to_string(&JsonPageDefs[0], &cmdNames);
json_err(FSL_JSON_E_MISSING_ARGS,
blob_str(&cmdNames), 1);
blob_reset(&cmdNames);
fossil_exit(1);
}
}
#endif /* FOSSIL_ENABLE_JSON */
#undef BITSET_BYTEFOR
#undef BITSET_SET
#undef BITSET_UNSET
#undef BITSET_GET
#undef BITSET_TOGGLE
#endif /* FOSSIL_ENABLE_JSON */
|
Added src/json_artifact.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 |
#ifdef FOSSIL_ENABLE_JSON
/*
** Copyright (c) 2011 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*/
#include "VERSION.h"
#include "config.h"
#include "json_artifact.h"
#if INTERFACE
#include "json_detail.h"
#endif
/*
** Internal callback for /json/artifact handlers. rid refers to
** the rid of a given type of artifact, and each callback is
** specialized to return a JSON form of one type of artifact.
**
** Implementations may assert() that rid refers to requested artifact
** type, since mismatches in the artifact types come from
** json_page_artifact() as opposed to client data.
*/
typedef cson_value * (*artifact_f)( int rid );
/*
** Internal per-artifact-type dispatching helper.
*/
typedef struct ArtifactDispatchEntry {
/**
Artifact type name, e.g. "checkin", "ticket", "wiki".
*/
char const * name;
/**
JSON construction callback. Creates the contents for the
payload.artifact property of /json/artifact responses.
*/
artifact_f func;
} ArtifactDispatchEntry;
/*
** Generates an artifact Object for the given rid,
** which must refer to a Checkin.
**
** Returned value is NULL or an Object owned by the caller.
*/
cson_value * json_artifact_for_ci( int rid, char showFiles ){
char * zParent = NULL;
cson_value * v = NULL;
Stmt q;
static cson_value * eventTypeLabel = NULL;
if(!eventTypeLabel){
eventTypeLabel = json_new_string("checkin");
json_gc_add("$EVENT_TYPE_LABEL(commit)", eventTypeLabel);
}
zParent = db_text(0,
"SELECT uuid FROM plink, blob"
" WHERE plink.cid=%d AND blob.rid=plink.pid AND plink.isprim",
rid
);
db_prepare(&q,
"SELECT uuid, "
" cast(strftime('%%s',mtime) as int), "
" user, "
" comment,"
" strftime('%%s',omtime)"
" FROM blob, event"
" WHERE blob.rid=%d"
" AND event.objid=%d",
rid, rid
);
if( db_step(&q)==SQLITE_ROW ){
cson_object * o;
cson_value * tmpV = NULL;
const char *zUuid = db_column_text(&q, 0);
char * zTmp;
const char *zUser;
const char *zComment;
char * zEUser, * zEComment;
int mtime, omtime;
v = cson_value_new_object();
o = cson_value_get_object(v);
#define SET(K,V) cson_object_set(o,(K), (V))
SET("type", eventTypeLabel );
SET("uuid",json_new_string(zUuid));
SET("isLeaf", cson_value_new_bool(is_a_leaf(rid)));
zUser = db_column_text(&q,2);
zEUser = db_text(0,
"SELECT value FROM tagxref WHERE tagid=%d AND rid=%d",
TAG_USER, rid);
if(zEUser){
SET("user", json_new_string(zEUser));
if(0!=strcmp(zEUser,zUser)){
SET("originUser",json_new_string(zUser));
}
free(zEUser);
}else{
SET("user",json_new_string(zUser));
}
zComment = db_column_text(&q,3);
zEComment = db_text(0,
"SELECT value FROM tagxref WHERE tagid=%d AND rid=%d",
TAG_COMMENT, rid);
if(zEComment){
SET("comment",json_new_string(zEComment));
if(0 != strcmp(zEComment,zComment)){
SET("originComment", json_new_string(zComment));
}
free(zEComment);
}else{
SET("comment",json_new_string(zComment));
}
mtime = db_column_int(&q,1);
SET("mtime",json_new_int(mtime));
omtime = db_column_int(&q,4);
if(omtime && (omtime!=mtime)){
SET("originTime",json_new_int(omtime));
}
if(zParent){
SET("parentUuid", json_new_string(zParent));
}
tmpV = json_tags_for_checkin_rid(rid,0);
if(tmpV){
SET("tags",tmpV);
}
if( showFiles ){
cson_value * fileList = json_get_changed_files(rid);
if(fileList){
SET("files",fileList);
}
}
#undef SET
}
free(zParent);
db_finalize(&q);
return v;
}
/*
** Very incomplete/incorrect impl of /json/artifact/TICKET_ID.
*/
cson_value * json_artifact_ticket( int rid ){
cson_object * pay = NULL;
Manifest *pTktChng = NULL;
static cson_value * eventTypeLabel = NULL;
if(! g.perm.RdTkt ){
g.json.resultCode = FSL_JSON_E_DENIED;
return NULL;
}
if(!eventTypeLabel){
eventTypeLabel = json_new_string("ticket");
json_gc_add("$EVENT_TYPE_LABEL(ticket)", eventTypeLabel);
}
pTktChng = manifest_get(rid, CFTYPE_TICKET);
if( pTktChng==0 ){
g.json.resultCode = FSL_JSON_E_MANIFEST_READ_FAILED;
return NULL;
}
pay = cson_new_object();
cson_object_set(pay, "eventType", eventTypeLabel );
cson_object_set(pay, "uuid", json_new_string(pTktChng->zTicketUuid));
cson_object_set(pay, "user", json_new_string(pTktChng->zUser));
cson_object_set(pay, "timestamp", json_julian_to_timestamp(pTktChng->rDate));
manifest_destroy(pTktChng);
return cson_object_value(pay);
}
/*
** Sub-impl of /json/artifact for checkins.
*/
static cson_value * json_artifact_ci( int rid ){
if(! g.perm.Read ){
g.json.resultCode = FSL_JSON_E_DENIED;
return NULL;
}else{
return json_artifact_for_ci(rid, 1);
}
}
/*
** Internal mapping of /json/artifact/FOO commands/callbacks.
*/
static ArtifactDispatchEntry ArtifactDispatchList[] = {
{"checkin", json_artifact_ci},
{"file", json_artifact_file},
{"tag", NULL},
{"ticket", json_artifact_ticket},
{"wiki", json_artifact_wiki},
/* Final entry MUST have a NULL name. */
{NULL,NULL}
};
/*
** Internal helper which returns true (non-0) if the includeContent
** (HTTP) or -content|-c flags (CLI) are set.
*/
static char json_artifact_include_content_flag(){
return json_find_option_bool("includeContent","content","c",0);
}
cson_value * json_artifact_wiki(int rid){
if( ! g.perm.RdWiki ){
json_set_err(FSL_JSON_E_DENIED,
"Requires 'j' privileges.");
return NULL;
}else{
return json_get_wiki_page_by_rid(rid, 0);
}
}
cson_value * json_artifact_file(int rid){
cson_object * pay = NULL;
const char *zMime;
Blob content = empty_blob;
Stmt q = empty_Stmt;
cson_array * checkin_arr = NULL;
if( ! g.perm.Read ){
json_set_err(FSL_JSON_E_DENIED,
"Requires 'o' privileges.");
return NULL;
}
pay = cson_new_object();
content_get(rid, &content);
cson_object_set(pay, "contentLength",
json_new_int( blob_size(&content) )
/* achtung: overflow potential on 32-bit builds! */);
zMime = mimetype_from_content(&content);
cson_object_set(pay, "contentType",
json_new_string(zMime ? zMime : "text/plain"));
if( json_artifact_include_content_flag() && !zMime ){
cson_object_set(pay, "content",
cson_value_new_string(blob_str(&content),
(unsigned int)blob_size(&content)));
}
blob_reset(&content);
db_prepare(&q,
"SELECT filename.name AS name, "
" cast(strftime('%%s',event.mtime) as int) AS mtime,"
" coalesce(event.ecomment,event.comment) as comment,"
" coalesce(event.euser,event.user) as user,"
" b.uuid as uuid, mlink.mperm as mperm,"
" coalesce((SELECT value FROM tagxref"
" WHERE tagid=%d AND tagtype>0 AND "
" rid=mlink.mid),'trunk') as branch"
" FROM mlink, filename, event, blob a, blob b"
" WHERE filename.fnid=mlink.fnid"
" AND event.objid=mlink.mid"
" AND a.rid=mlink.fid"
" AND b.rid=mlink.mid"
" AND mlink.fid=%d"
" ORDER BY filename.name, event.mtime",
TAG_BRANCH, rid
);
checkin_arr = cson_new_array();
cson_object_set(pay, "checkins", cson_array_value(checkin_arr));
json_stmt_to_array_of_obj( &q, checkin_arr );
db_finalize(&q);
return cson_object_value(pay);
}
/*
** Impl of /json/artifact. This basically just determines the type of
** an artifact and forwards the real work to another function.
*/
cson_value * json_page_artifact(){
cson_object * pay = NULL;
char const * zName = NULL;
char const * zType = NULL;
char const * zUuid = NULL;
cson_value * entry = NULL;
Blob uuid = empty_blob;
int rc;
int rid = 0;
ArtifactDispatchEntry const * dispatcher = &ArtifactDispatchList[0];
zName = json_find_option_cstr2("uuid", NULL, NULL, 2);
if(!zName || !*zName) {
json_set_err(FSL_JSON_E_MISSING_ARGS,
"Missing 'uuid' argument.");
return NULL;
}
if( validate16(zName, strlen(zName)) ){
if( db_exists("SELECT 1 FROM ticket WHERE tkt_uuid GLOB '%q*'", zName) ){
zType = "ticket";
goto handle_entry;
}
if( db_exists("SELECT 1 FROM tag WHERE tagname GLOB 'event-%q*'", zName) ){
zType = "tag";
goto handle_entry;
}
}
blob_set(&uuid,zName);
rc = name_to_uuid(&uuid,-1,"*");
if(1==rc){
g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND;
goto error;
}else if(2==rc){
g.json.resultCode = FSL_JSON_E_AMBIGUOUS_UUID;
goto error;
}
zUuid = blob_str(&uuid);
rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zUuid);
if(0==rid){
g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND;
goto error;
}
if( db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid)
|| db_exists("SELECT 1 FROM plink WHERE cid=%d", rid)
|| db_exists("SELECT 1 FROM plink WHERE pid=%d", rid)){
zType = "checkin";
goto handle_entry;
}else if( db_exists("SELECT 1 FROM tagxref JOIN tag USING(tagid)"
" WHERE rid=%d AND tagname LIKE 'wiki-%%'", rid) ){
zType = "wiki";
goto handle_entry;
}else if( db_exists("SELECT 1 FROM tagxref JOIN tag USING(tagid)"
" WHERE rid=%d AND tagname LIKE 'tkt-%%'", rid) ){
zType = "ticket";
goto handle_entry;
}else if ( db_exists("SELECT 1 FROM mlink WHERE fid = %d", rid) ){
zType = "file";
goto handle_entry;
}else{
g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND;
goto error;
}
error:
assert( 0 != g.json.resultCode );
goto veryend;
handle_entry:
assert( (NULL != zType) && "Internal dispatching error." );
for( ; dispatcher->name; ++dispatcher ){
if(0!=strcmp(dispatcher->name, zType)){
continue;
}else{
entry = (*dispatcher->func)(rid);
break;
}
}
if(!g.json.resultCode){
assert( NULL != entry );
assert( NULL != zType );
pay = cson_new_object();
cson_object_set( pay, "type", json_new_string(zType) );
/*cson_object_set( pay, "uuid", json_new_string(zUuid) );*/
cson_object_set( pay, "name", json_new_string(zName ? zName : zUuid) );
cson_object_set( pay, "rid", cson_value_new_integer(rid) );
if(entry){
cson_object_set(pay, "artifact", entry);
}
}
veryend:
blob_reset(&uuid);
return cson_object_value(pay);
}
#endif /* FOSSIL_ENABLE_JSON */
|
Added src/json_branch.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 |
#ifdef FOSSIL_ENABLE_JSON
/*
** Copyright (c) 2011 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*/
#include "VERSION.h"
#include "config.h"
#include "json_branch.h"
#if INTERFACE
#include "json_detail.h"
#endif
static cson_value * json_branch_list();
static cson_value * json_branch_create();
/*
** Mapping of /json/branch/XXX commands/paths to callbacks.
*/
static const JsonPageDef JsonPageDefs_Branch[] = {
{"create", json_branch_create, 0},
{"list", json_branch_list, 0},
{"new", json_branch_create, -1/* for compat with non-JSON branch command.*/},
/* Last entry MUST have a NULL name. */
{NULL,NULL,0}
};
/*
** Implements the /json/branch family of pages/commands. Far from
** complete.
**
*/
cson_value * json_page_branch(){
return json_page_dispatch_helper(&JsonPageDefs_Branch[0]);
}
/*
** Impl for /json/branch/list
**
**
** CLI mode options:
**
** --range X | -r X, where X is one of (open,closed,all)
** (only the first letter is significant, default=open).
** -a (same as --range a)
** -c (same as --range c)
**
** HTTP mode options:
**
** "range" GET/POST.payload parameter. FIXME: currently we also use
** POST, but really want to restrict this to POST.payload.
*/
static cson_value * json_branch_list(){
cson_value * payV;
cson_object * pay;
cson_value * listV;
cson_array * list;
char const * range = NULL;
int which = 0;
char * sawConversionError = NULL;
Stmt q;
if( !g.perm.Read ){
json_set_err(FSL_JSON_E_DENIED,
"Requires 'o' permissions.");
return NULL;
}
payV = cson_value_new_object();
pay = cson_value_get_object(payV);
listV = cson_value_new_array();
list = cson_value_get_array(listV);
if(fossil_has_json()){
range = json_getenv_cstr("range");
}
range = json_find_option_cstr("range",NULL,"r");
if((!range||!*range) && !g.isHTTP){
range = find_option("all","a",0);
if(range && *range){
range = "a";
}else{
range = find_option("closed","c",0);
if(range&&*range){
range = "c";
}
}
}
if(!range || !*range){
range = "o";
}
/* Normalize range values... */
switch(*range){
case 'c':
range = "closed";
which = -1;
break;
case 'a':
range = "all";
which = 1;
break;
default:
range = "open";
which = 0;
break;
};
cson_object_set(pay,"range",json_new_string(range));
if( g.localOpen ){ /* add "current" property (branch name). */
int vid = db_lget_int("checkout", 0);
char const * zCurrent = vid
? db_text(0, "SELECT value FROM tagxref"
" WHERE rid=%d AND tagid=%d",
vid, TAG_BRANCH)
: 0;
if(zCurrent){
cson_object_set(pay,"current",json_new_string(zCurrent));
}
}
branch_prepare_list_query(&q, which);
cson_object_set(pay,"branches",listV);
while((SQLITE_ROW==db_step(&q))){
cson_value * v = cson_sqlite3_column_to_value(q.pStmt,0);
if(v){
cson_array_append(list,v);
}else if(!sawConversionError){
sawConversionError = mprintf("Column-to-json failed @ %s:%d",
__FILE__,__LINE__);
}
}
if( sawConversionError ){
json_warn(FSL_JSON_W_COL_TO_JSON_FAILED,sawConversionError);
free(sawConversionError);
}
return payV;
}
/*
** Parameters for the create-branch operation.
*/
typedef struct BranchCreateOptions{
char const * zName;
char const * zBasis;
char const * zColor;
char isPrivate;
/**
Might be set to an error string by
json_branch_new().
*/
char const * rcErrMsg;
} BranchCreateOptions;
/*
** Tries to create a new branch based on the options set in zOpt. If
** an error is encountered, zOpt->rcErrMsg _might_ be set to a
** descriptive string and one of the FossilJsonCodes values will be
** returned. Or fossil_fatal() (or similar) might be called, exiting
** the app.
**
** On success 0 is returned and if zNewRid is not NULL then the rid of
** the new branch is assigned to it.
**
** If zOpt->isPrivate is 0 but the parent branch is private,
** zOpt->isPrivate will be set to a non-zero value and the new branch
** will be private.
*/
static int json_branch_new(BranchCreateOptions * zOpt,
int *zNewRid){
/* Mostly copied from branch.c:branch_new(), but refactored a small
bit to not produce output or interact with the user. The
down-side to that is that we dropped the gpg-signing. It was
either that or abort the creation if we couldn't sign. We can't
sign over HTTP mode, anyway.
*/
char const * zBranch = zOpt->zName;
char const * zBasis = zOpt->zBasis;
char const * zColor = zOpt->zColor;
int rootid; /* RID of the root check-in - what we branch off of */
int brid; /* RID of the branch check-in */
int i; /* Loop counter */
char *zUuid; /* Artifact ID of origin */
Stmt q; /* Generic query */
char *zDate; /* Date that branch was created */
char *zComment; /* Check-in comment for the new branch */
Blob branch; /* manifest for the new branch */
Manifest *pParent; /* Parsed parent manifest */
Blob mcksum; /* Self-checksum on the manifest */
/* fossil branch new name */
if( zBranch==0 || zBranch[0]==0 ){
zOpt->rcErrMsg = "Branch name may not be null/empty.";
return FSL_JSON_E_INVALID_ARGS;
}
if( db_exists(
"SELECT 1 FROM tagxref"
" WHERE tagtype>0"
" AND tagid=(SELECT tagid FROM tag WHERE tagname='sym-%q')",
zBranch)!=0 ){
zOpt->rcErrMsg = "Branch already exists.";
return FSL_JSON_E_RESOURCE_ALREADY_EXISTS;
}
db_begin_transaction();
rootid = name_to_typed_rid(zBasis, "ci");
if( rootid==0 ){
zOpt->rcErrMsg = "Basis branch not found.";
return FSL_JSON_E_RESOURCE_NOT_FOUND;
}
pParent = manifest_get(rootid, CFTYPE_MANIFEST);
if( pParent==0 ){
zOpt->rcErrMsg = "Could not read parent manifest.";
return FSL_JSON_E_UNKNOWN;
}
/* Create a manifest for the new branch */
blob_zero(&branch);
if( pParent->zBaseline ){
blob_appendf(&branch, "B %s\n", pParent->zBaseline);
}
zComment = mprintf("Create new branch named \"%s\" "
"from \"%s\".", zBranch, zBasis);
blob_appendf(&branch, "C %F\n", zComment);
free(zComment);
zDate = date_in_standard_format("now");
blob_appendf(&branch, "D %s\n", zDate);
free(zDate);
/* Copy all of the content from the parent into the branch */
for(i=0; i<pParent->nFile; ++i){
blob_appendf(&branch, "F %F", pParent->aFile[i].zName);
if( pParent->aFile[i].zUuid ){
blob_appendf(&branch, " %s", pParent->aFile[i].zUuid);
if( pParent->aFile[i].zPerm && pParent->aFile[i].zPerm[0] ){
blob_appendf(&branch, " %s", pParent->aFile[i].zPerm);
}
}
blob_append(&branch, "\n", 1);
}
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rootid);
blob_appendf(&branch, "P %s\n", zUuid);
free(zUuid);
if( pParent->zRepoCksum ){
blob_appendf(&branch, "R %s\n", pParent->zRepoCksum);
}
manifest_destroy(pParent);
/* Add the symbolic branch name and the "branch" tag to identify
** this as a new branch */
if( content_is_private(rootid) ) zOpt->isPrivate = 1;
if( zOpt->isPrivate && zColor==0 ) zColor = "#fec084";
if( zColor!=0 ){
blob_appendf(&branch, "T *bgcolor * %F\n", zColor);
}
blob_appendf(&branch, "T *branch * %F\n", zBranch);
blob_appendf(&branch, "T *sym-%F *\n", zBranch);
if( zOpt->isPrivate ){
blob_appendf(&branch, "T +private *\n");
}
/* Cancel all other symbolic tags */
db_prepare(&q,
"SELECT tagname FROM tagxref, tag"
" WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid"
" AND tagtype>0 AND tagname GLOB 'sym-*'"
" ORDER BY tagname",
rootid);
while( db_step(&q)==SQLITE_ROW ){
const char *zTag = db_column_text(&q, 0);
blob_appendf(&branch, "T -%F *\n", zTag);
}
db_finalize(&q);
blob_appendf(&branch, "U %F\n", g.zLogin);
md5sum_blob(&branch, &mcksum);
blob_appendf(&branch, "Z %b\n", &mcksum);
brid = content_put(&branch);
if( brid==0 ){
fossil_panic("Problem committing manifest: %s", g.zErrMsg);
}
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", brid);
if( manifest_crosslink(brid, &branch)==0 ){
fossil_panic("unable to install new manifest");
}
assert( blob_is_reset(&branch) );
content_deltify(rootid, brid, 0);
if( zNewRid ){
*zNewRid = brid;
}
/* Commit */
db_end_transaction(0);
#if 0 /* Do an autosync push, if requested */
/* arugable for JSON mode? */
if( !g.isHTTP && !isPrivate ) autosync(AUTOSYNC_PUSH);
#endif
return 0;
}
/*
** Impl of /json/branch/create.
*/
static cson_value * json_branch_create(){
cson_value * payV = NULL;
cson_object * pay = NULL;
int rc = 0;
BranchCreateOptions opt;
char * zUuid = NULL;
int rid = 0;
if( !g.perm.Write ){
json_set_err(FSL_JSON_E_DENIED,
"Requires 'i' permissions.");
return NULL;
}
memset(&opt,0,sizeof(BranchCreateOptions));
if(fossil_has_json()){
opt.zName = json_getenv_cstr("name");
}
if(!opt.zName){
opt.zName = json_command_arg(g.json.dispatchDepth+1);
}
if(!opt.zName){
json_set_err(FSL_JSON_E_MISSING_ARGS, "'name' parameter was not specified." );
return NULL;
}
opt.zColor = json_find_option_cstr("bgColor","bgcolor",NULL);
opt.zBasis = json_find_option_cstr("basis",NULL,NULL);
if(!opt.zBasis && !g.isHTTP){
opt.zBasis = json_command_arg(g.json.dispatchDepth+2);
}
if(!opt.zBasis){
opt.zBasis = "trunk";
}
opt.isPrivate = json_find_option_bool("private",NULL,NULL,-1);
if(-1==opt.isPrivate){
if(!g.isHTTP){
opt.isPrivate = (NULL != find_option("private","",0));
}else{
opt.isPrivate = 0;
}
}
rc = json_branch_new( &opt, &rid );
if(rc){
json_set_err(rc, opt.rcErrMsg);
goto error;
}
assert(0 != rid);
payV = cson_value_new_object();
pay = cson_value_get_object(payV);
cson_object_set(pay,"name",json_new_string(opt.zName));
cson_object_set(pay,"basis",json_new_string(opt.zBasis));
cson_object_set(pay,"rid",json_new_int(rid));
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
cson_object_set(pay,"uuid", json_new_string(zUuid));
cson_object_set(pay, "isPrivate", cson_value_new_bool(opt.isPrivate));
free(zUuid);
if(opt.zColor){
cson_object_set(pay,"bgColor",json_new_string(opt.zColor));
}
goto ok;
error:
assert( 0 != g.json.resultCode );
cson_value_free(payV);
payV = NULL;
ok:
return payV;
}
#endif /* FOSSIL_ENABLE_JSON */
|
Added src/json_detail.h.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 |
#ifdef FOSSIL_ENABLE_JSON
#if !defined(FOSSIL_JSON_DETAIL_H_INCLUDED)
#define FOSSIL_JSON_DETAIL_H_INCLUDED
/*
** Copyright (c) 2011 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*/
#include "cson_amalgamation.h"
/*
** Impl details for the JSON API which need to be shared
** across multiple C files.
*/
/*
** The "official" list of Fossil/JSON error codes. Their values might
** very well change during initial development but after their first
** public release they must stay stable.
**
** Values must be in the range 1000..9999 for error codes and 1..999
** for warning codes.
**
** Numbers evenly dividable by 100 are "categories", and error codes
** for a given category have their high bits set to the category
** value.
**
*/
enum FossilJsonCodes {
FSL_JSON_W_START = 0,
FSL_JSON_W_UNKNOWN /*+1*/,
FSL_JSON_W_ROW_TO_JSON_FAILED /*+2*/,
FSL_JSON_W_COL_TO_JSON_FAILED /*+3*/,
FSL_JSON_W_STRING_TO_ARRAY_FAILED /*+4*/,
FSL_JSON_W_TAG_NOT_FOUND /*+5*/,
FSL_JSON_W_END = 1000,
FSL_JSON_E_GENERIC = 1000,
FSL_JSON_E_GENERIC_SUB1 = FSL_JSON_E_GENERIC + 100,
FSL_JSON_E_INVALID_REQUEST /*+1*/,
FSL_JSON_E_UNKNOWN_COMMAND /*+2*/,
FSL_JSON_E_UNKNOWN /*+3*/,
/*REUSE: +4*/
FSL_JSON_E_TIMEOUT /*+5*/,
FSL_JSON_E_ASSERT /*+6*/,
FSL_JSON_E_ALLOC /*+7*/,
FSL_JSON_E_NYI /*+8*/,
FSL_JSON_E_PANIC /*+9*/,
FSL_JSON_E_MANIFEST_READ_FAILED /*+10*/,
FSL_JSON_E_FILE_OPEN_FAILED /*+11*/,
FSL_JSON_E_AUTH = 2000,
FSL_JSON_E_MISSING_AUTH /*+1*/,
FSL_JSON_E_DENIED /*+2*/,
FSL_JSON_E_WRONG_MODE /*+3*/,
FSL_JSON_E_LOGIN_FAILED = FSL_JSON_E_AUTH +100,
FSL_JSON_E_LOGIN_FAILED_NOSEED /*+1*/,
FSL_JSON_E_LOGIN_FAILED_NONAME /*+2*/,
FSL_JSON_E_LOGIN_FAILED_NOPW /*+3*/,
FSL_JSON_E_LOGIN_FAILED_NOTFOUND /*+4*/,
FSL_JSON_E_USAGE = 3000,
FSL_JSON_E_INVALID_ARGS /*+1*/,
FSL_JSON_E_MISSING_ARGS /*+2*/,
FSL_JSON_E_AMBIGUOUS_UUID /*+3*/,
FSL_JSON_E_UNRESOLVED_UUID /*+4*/,
FSL_JSON_E_RESOURCE_ALREADY_EXISTS /*+5*/,
FSL_JSON_E_RESOURCE_NOT_FOUND /*+6*/,
FSL_JSON_E_DB = 4000,
FSL_JSON_E_STMT_PREP /*+1*/,
FSL_JSON_E_STMT_BIND /*+2*/,
FSL_JSON_E_STMT_EXEC /*+3*/,
FSL_JSON_E_DB_LOCKED /*+4*/,
FSL_JSON_E_DB_NEEDS_REBUILD = FSL_JSON_E_DB + 101,
FSL_JSON_E_DB_NOT_FOUND = FSL_JSON_E_DB + 102,
FSL_JSON_E_DB_NOT_VALID = FSL_JSON_E_DB + 103
};
/*
** Signature for JSON page/command callbacks. Each callback is
** responsible for handling one JSON request/command and/or
** dispatching to sub-commands.
**
** By the time the callback is called, json_page_top() (HTTP mode) or
** json_cmd_top() (CLI mode) will have set up the JSON-related
** environment. Implementations may generate a "result payload" of any
** JSON type by returning its value from this function (ownership is
** tranferred to the caller). On error they should set
** g.json.resultCode to one of the FossilJsonCodes values and return
** either their payload object or NULL. Note that NULL is a legal
** success value - it simply means the response will contain no
** payload. If g.json.resultCode is non-zero when this function
** returns then the top-level dispatcher will destroy any payload
** returned by this function and will output a JSON error response
** instead.
**
** All of the setup/response code is handled by the top dispatcher
** functions and the callbacks concern themselves only with:
**
** a) Permissions checking (inspecting g.perm).
** b) generating a response payload (if applicable)
** c) Setting g.json's error state (if applicable). See json_set_err().
**
** It is imperitive that NO callback functions EVER output ANYTHING to
** stdout, as that will effectively corrupt any JSON output, and
** almost certainly will corrupt any HTTP response headers. Output
** sent to stderr ends up in my apache log, so that might be useful
** for debuggering in some cases, but no such code should be left
** enabled for non-debuggering builds.
*/
typedef cson_value * (*fossil_json_f)();
/*
** Holds name-to-function mappings for JSON page/command dispatching.
**
** Internally we model page dispatching lists as arrays of these
** objects, where the final entry in the array has a NULL name value
** to act as the end-of-list sentinel.
**
*/
typedef struct JsonPageDef{
/*
** The commmand/page's name (path, not including leading /json/).
**
** Reminder to self: we cannot use sub-paths with commands this way
** without additional string-splitting downstream. e.g. foo/bar.
** Alternately, we can create different JsonPageDef arrays for each
** subset.
*/
char const * name;
/*
** Returns a payload object for the response. If it returns a
** non-NULL value, the caller owns it. To trigger an error this
** function should set g.json.resultCode to a value from the
** FossilJsonCodes enum. If it sets an error value and returns
** a payload, the payload will be destroyed (not sent with the
** response).
*/
fossil_json_f func;
/*
** Which mode(s) of execution does func() support:
**
** <0 = CLI only, >0 = HTTP only, 0==both
**
** Now that we can simulate POST in CLI mode, the distinction
** between them has disappeared in most (or all) cases, so 0 is the
** the standard value.
*/
char runMode;
} JsonPageDef;
/*
** Holds common keys used for various JSON API properties.
*/
typedef struct FossilJsonKeys_{
/** maintainers: please keep alpha sorted (case-insensitive) */
char const * anonymousSeed;
char const * authToken;
char const * commandPath;
char const * mtime;
char const * payload;
char const * requestId;
char const * resultCode;
char const * resultText;
char const * timestamp;
} FossilJsonKeys_;
const FossilJsonKeys_ FossilJsonKeys;
/*
** A page/command dispatch helper for fossil_json_f() implementations.
** pages must be an array of JsonPageDef commands which we can
** dispatch. The final item in the array MUST have a NULL name
** element.
**
** This function takes the command specified in
** json_comand_arg(1+g.json.dispatchDepth) and searches pages for a
** matching name. If found then that page's func() is called to fetch
** the payload, which is returned to the caller.
**
** On error, g.json.resultCode is set to one of the FossilJsonCodes
** values and NULL is returned. If non-NULL is returned, ownership is
** transfered to the caller (but the g.json error state might still be
** set in that case, so the caller must check that or pass it on up
** the dispatch chain).
*/
cson_value * json_page_dispatch_helper(JsonPageDef const * pages);
/*
** Implements the /json/wiki family of pages/commands.
**
*/
cson_value * json_page_wiki();
/*
** Implements /json/timeline/wiki and /json/wiki/timeline.
*/
cson_value * json_timeline_wiki();
/*
** Implements /json/timeline family of functions.
*/
cson_value * json_page_timeline();
/*
** Convenience wrapper around cson_value_new_string().
** Returns NULL if str is NULL or on allocation error.
*/
cson_value * json_new_string( char const * str );
/*
** Similar to json_new_string(), but takes a printf()-style format
** specifiers. Supports the printf extensions supported by fossil's
** mprintf(). Returns NULL if str is NULL or on allocation error.
**
** Maintenance note: json_new_string() is NOT variadic because by the
** time the variadic form was introduced we already had use cases
** which segfaulted via json_new_string() because they contain printf
** markup (e.g. wiki content). Been there, debugged that.
*/
cson_value * json_new_string_f( char const * fmt, ... );
/*
** Returns true if fossil is running in JSON mode and we are either
** running in HTTP mode OR g.json.post.o is not NULL (meaning POST
** data was fed in from CLI mode).
**
** Specifically, it will return false when any of these apply:
**
** a) Not running in JSON mode (via json command or /json path).
**
** b) We are running in JSON CLI mode, but no POST data has been fed
** in.
**
** Whether or not we need to take args from CLI or POST data makes a
** difference in argument/parameter handling in many JSON rountines,
** and thus this distinction.
*/
char fossil_has_json();
#endif/*FOSSIL_JSON_DETAIL_H_INCLUDED*/
#endif /* FOSSIL_ENABLE_JSON */
|
Added src/json_diff.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
#ifdef FOSSIL_ENABLE_JSON
/*
** Copyright (c) 2011 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*/
#include "config.h"
#include "json_diff.h"
#if INTERFACE
#include "json_detail.h"
#endif
/*
** Generates a diff between two versions (zFrom and zTo), using nContext
** content lines in the output. On success, returns a new JSON String
** object. On error it sets g.json's error state and returns NULL.
**
** If fSbs is true (non-0) them side-by-side diffs are used.
*/
cson_value * json_generate_diff(const char *zFrom, const char *zTo,
int nContext, char fSbs){
int fromid;
int toid;
int outLen;
Blob from = empty_blob, to = empty_blob, out = empty_blob;
cson_value * rc = NULL;
char const * zType = "ci";
int flags = (DIFF_CONTEXT_MASK & nContext)
| (fSbs ? DIFF_SIDEBYSIDE : 0);
fromid = name_to_typed_rid(zFrom, "*");
if(fromid<=0){
json_set_err(FSL_JSON_E_UNRESOLVED_UUID,
"Could not resolve 'from' ID.");
return NULL;
}
toid = name_to_typed_rid(zTo, "*");
if(toid<=0){
json_set_err(FSL_JSON_E_UNRESOLVED_UUID,
"Could not resolve 'to' ID.");
return NULL;
}
content_get(fromid, &from);
content_get(toid, &to);
blob_zero(&out);
text_diff(&from, &to, &out, flags);
blob_reset(&from);
blob_reset(&to);
outLen = blob_size(&out);
if(outLen>0){
rc = cson_value_new_string(blob_buffer(&out), blob_size(&out));
}
blob_reset(&out);
return rc;
}
/*
** Implementation of the /json/diff page.
**
** Arguments:
**
** v1=1st version to diff
** v2=2nd version to diff
**
** Can come from GET, POST.payload, CLI -v1/-v2 or as positional
** parameters following the command name (in HTTP and CLI modes).
**
*/
cson_value * json_page_diff(){
cson_object * pay = NULL;
cson_value * v = NULL;
char const * zFrom;
char const * zTo;
int nContext = 0;
char doSBS;
if(!g.perm.Read){
json_set_err(FSL_JSON_E_DENIED,
"Requires 'o' permissions.");
return NULL;
}
zFrom = json_find_option_cstr("v1",NULL,NULL);
if(!zFrom){
zFrom = json_command_arg(2);
}
if(!zFrom){
json_set_err(FSL_JSON_E_MISSING_ARGS,
"Required 'v1' parameter is missing.");
return NULL;
}
zTo = json_find_option_cstr("v2",NULL,NULL);
if(!zTo){
zTo = json_command_arg(3);
}
if(!zTo){
json_set_err(FSL_JSON_E_MISSING_ARGS,
"Required 'v2' parameter is missing.");
return NULL;
}
nContext = json_find_option_int("context",NULL,"c",5);
doSBS = json_find_option_bool("sbs",NULL,"y",0);
v = json_generate_diff(zFrom, zTo, nContext, doSBS);
if(!v){
if(!g.json.resultCode){
json_set_err(FSL_JSON_E_UNKNOWN,
"Generating diff failed for unknown reason.");
}
return NULL;
}
pay = cson_new_object();
cson_object_set(pay, "from", json_new_string(zFrom));
cson_object_set(pay, "to", json_new_string(zTo));
cson_object_set(pay, "diff", v);
v = 0;
return pay ? cson_object_value(pay) : NULL;
}
#endif /* FOSSIL_ENABLE_JSON */
|
Added src/json_login.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 |
#ifdef FOSSIL_ENABLE_JSON
/*
** Copyright (c) 2011 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*/
#include "config.h"
#include "json_login.h"
#if INTERFACE
#include "json_detail.h"
#endif
/*
** Implementation of the /json/login page.
**
*/
cson_value * json_page_login(){
char preciseErrors = /* if true, "complete" JSON error codes are used,
else they are "dumbed down" to a generic login
error code.
*/
#if 1
g.json.errorDetailParanoia ? 0 : 1
#else
0
#endif
;
/*
FIXME: we want to check the GET/POST args in this order:
- GET: name, n, password, p
- POST: name, password
but a bug in cgi_parameter() is breaking that, causing PD() to
return the last element of the PATH_INFO instead.
Summary: If we check for P("name") first, then P("n"),
then ONLY a GET param of "name" will match ("n"
is not recognized). If we reverse the order of the
checks then both forms work. Strangely enough, the
"p"/"password" check is not affected by this.
*/
char const * name = cson_value_get_cstr(json_payload_property("name"));
char const * pw = NULL;
char const * anonSeed = NULL;
cson_value * payload = NULL;
int uid = 0;
/* reminder to self: Fossil internally (for the sake of /wiki)
interprets paths in the form /foo/bar/baz such that P("name") ==
"bar/baz". This collides with our name/password checking, and
thus we do some rather elaborate name=... checking.
*/
pw = cson_value_get_cstr(json_payload_property("password"));
if( !pw ){
pw = PD("p",NULL);
if( !pw ){
pw = PD("password",NULL);
}
}
if(!pw){
g.json.resultCode = preciseErrors
? FSL_JSON_E_LOGIN_FAILED_NOPW
: FSL_JSON_E_LOGIN_FAILED;
return NULL;
}
if( !name ){
name = PD("n",NULL);
if( !name ){
name = PD("name",NULL);
if( !name ){
g.json.resultCode = preciseErrors
? FSL_JSON_E_LOGIN_FAILED_NONAME
: FSL_JSON_E_LOGIN_FAILED;
return NULL;
}
}
}
if(0 == strcmp("anonymous",name)){
/* check captcha/seed values... */
enum { SeedBufLen = 100 /* in some JSON tests i once actually got an
80-digit number.
*/
};
static char seedBuffer[SeedBufLen];
cson_value const * jseed = json_getenv(FossilJsonKeys.anonymousSeed);
seedBuffer[0] = 0;
if( !jseed ){
jseed = json_payload_property(FossilJsonKeys.anonymousSeed);
if( !jseed ){
jseed = json_getenv("cs") /* name used by HTML interface */;
}
}
if(jseed){
if( cson_value_is_number(jseed) ){
sprintf(seedBuffer, "%"CSON_INT_T_PFMT, cson_value_get_integer(jseed));
anonSeed = seedBuffer;
}else if( cson_value_is_string(jseed) ){
anonSeed = cson_string_cstr(cson_value_get_string(jseed));
}
}
if(!anonSeed){
g.json.resultCode = preciseErrors
? FSL_JSON_E_LOGIN_FAILED_NOSEED
: FSL_JSON_E_LOGIN_FAILED;
return NULL;
}
}
#if 0
{
/* only for debugging the PD()-incorrect-result problem */
cson_object * o = NULL;
uid = login_search_uid( name, pw );
payload = cson_value_new_object();
o = cson_value_get_object(payload);
cson_object_set( o, "n", cson_value_new_string(name,strlen(name)));
cson_object_set( o, "p", cson_value_new_string(pw,strlen(pw)));
return payload;
}
#endif
uid = anonSeed
? login_is_valid_anonymous(name, pw, anonSeed)
: login_search_uid(name, pw)
;
if( !uid ){
g.json.resultCode = preciseErrors
? FSL_JSON_E_LOGIN_FAILED_NOTFOUND
: FSL_JSON_E_LOGIN_FAILED;
return NULL;
}else{
char * cookie = NULL;
cson_object * po;
char * cap = NULL;
if(anonSeed){
login_set_anon_cookie(NULL, &cookie);
}else{
login_set_user_cookie(name, uid, &cookie);
}
payload = cson_value_new_object();
po = cson_value_get_object(payload);
cson_object_set(po, "authToken", json_new_string(cookie));
free(cookie);
cson_object_set(po, "name", json_new_string(name));
cap = db_text(NULL, "SELECT cap FROM user WHERE login=%Q", name);
cson_object_set(po, "capabilities", cap ? json_new_string(cap) : cson_value_null() );
free(cap);
cson_object_set(po, "loginCookieName", json_new_string( login_cookie_name() ) );
/* TODO: add loginExpiryTime to the payload. To do this properly
we "should" add an ([unsigned] int *) to
login_set_user_cookie() and login_set_anon_cookie(), to which
the expiry time is assigned. (Remember that JSON doesn't do
unsigned int.)
For non-anonymous users we could also simply query the
user.cexpire db field after calling login_set_user_cookie(),
but for anonymous we need to get the time when the cookie is
set because anon does not get a db entry like normal users
do. Anonyous cookies currently have a hard-coded lifetime in
login_set_anon_cookie() (currently 6 hours), which we "should
arguably" change to use the time configured for non-anonymous
users (see login_set_user_cookie() for details).
*/
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.*/;
json_setenv(FossilJsonKeys.authToken, NULL);
}
return json_page_whoami();
}
/*
** 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;
if(!g.json.authToken){
/* assume we just logged out. */
db_prepare(&q, "SELECT login, cap FROM user WHERE login='nobody'");
}
else{
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;
}
#endif /* FOSSIL_ENABLE_JSON */
|
Added src/json_query.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
#ifdef FOSSIL_ENABLE_JSON
/*
** Copyright (c) 2011 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*/
#include "config.h"
#include "json_query.h"
#if INTERFACE
#include "json_detail.h"
#endif
/*
** Implementation of the /json/query page.
**
** Requires admin privileges. Intended primarily to assist me in
** coming up with JSON output structures for pending features.
**
** Options/parameters:
**
** sql=string - a SELECT statement
**
** format=string 'a' means each row is an Array of values, 'o'
** (default) creates each row as an Object.
**
** TODO: in CLI mode (only) use -S FILENAME to read the sql
** from a file.
*/
cson_value * json_page_query(){
char const * zSql = NULL;
cson_value * payV;
char const * zFmt;
Stmt q = empty_Stmt;
int check;
if(!g.perm.Admin && !g.perm.Setup){
json_set_err(FSL_JSON_E_DENIED,
"Requires 'a' or 's' privileges.");
return NULL;
}
if( cson_value_is_string(g.json.reqPayload.v) ){
zSql = cson_string_cstr(cson_value_get_string(g.json.reqPayload.v));
}else{
zSql = json_find_option_cstr2("sql",NULL,"s",2);
}
if(!zSql || !*zSql){
json_set_err(FSL_JSON_E_MISSING_ARGS,
"'sql' (-s) argument is missing.");
return NULL;
}
zFmt = json_find_option_cstr2("format",NULL,"f",3);
if(!zFmt) zFmt = "o";
db_prepare(&q,"%s", zSql);
switch(*zFmt){
case 'a':
check = cson_sqlite3_stmt_to_json(q.pStmt, &payV, 0);
break;
case 'o':
default:
check = cson_sqlite3_stmt_to_json(q.pStmt, &payV, 1);
};
db_finalize(&q);
if(0 != check){
json_set_err(FSL_JSON_E_UNKNOWN,
"Conversion to JSON failed with cson code #%d (%s).",
check, cson_rc_string(check));
assert(NULL==payV);
}
return payV;
}
#endif /* FOSSIL_ENABLE_JSON */
|
Added src/json_report.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 |
#ifdef FOSSIL_ENABLE_JSON
/*
** Copyright (c) 2011 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*/
#include "config.h"
#include "json_report.h"
#if INTERFACE
#include "json_detail.h"
#endif
static cson_value * json_report_create();
static cson_value * json_report_get();
static cson_value * json_report_list();
static cson_value * json_report_run();
static cson_value * json_report_save();
/*
** Mapping of /json/report/XXX commands/paths to callbacks.
*/
static const JsonPageDef JsonPageDefs_Report[] = {
{"create", json_report_create, 0},
{"get", json_report_get, 0},
{"list", json_report_list, 0},
{"run", json_report_run, 0},
{"save", json_report_save, 0},
/* Last entry MUST have a NULL name. */
{NULL,NULL,0}
};
/*
** Implementation of the /json/report page.
**
**
*/
cson_value * json_page_report(){
if(!g.perm.RdTkt && !g.perm.NewTkt ){
json_set_err(FSL_JSON_E_DENIED,
"Requires 'r' or 'n' permissions.");
return NULL;
}
return json_page_dispatch_helper(&JsonPageDefs_Report[0]);
}
/*
** Searches the environment for a "report" parameter
** (CLI: -report/-r #).
**
** If one is not found and argPos is >0 then json_command_arg()
** is checked.
**
** Returns >0 (the report number) on success .
*/
static int json_report_get_number(int argPos){
int nReport = json_find_option_int("report",NULL,"r",-1);
if( (nReport<=0) && cson_value_is_integer(g.json.reqPayload.v)){
nReport = cson_value_get_integer(g.json.reqPayload.v);
}
if( (nReport <= 0) && (argPos>0) ){
char const * arg = json_command_arg(argPos);
if(arg && fossil_isdigit(*arg)) {
nReport = atoi(arg);
}
}
return nReport;
}
static cson_value * json_report_create(){
return NULL;
}
static cson_value * json_report_get(){
int nReport;
Stmt q = empty_Stmt;
cson_value * pay = NULL;
if(!g.perm.TktFmt){
json_set_err(FSL_JSON_E_DENIED,
"Requires 't' privileges.");
return NULL;
}
nReport = json_report_get_number(3);
if(nReport <=0){
json_set_err(FSL_JSON_E_MISSING_ARGS,
"Missing or invalid 'number' (-n) parameter.");
return NULL;
}
db_prepare(&q,"SELECT rn AS report,"
" owner AS owner,"
" title AS title,"
" cast(strftime('%%s',mtime) as int) as mtime,"
" cols as columns,"
" sqlcode as sqlCode"
" FROM reportfmt"
" WHERE rn=%d",
nReport);
if( SQLITE_ROW != db_step(&q) ){
db_finalize(&q);
json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND,
"Report #%d not found.", nReport);
return NULL;
}
pay = cson_sqlite3_row_to_object(q.pStmt);
db_finalize(&q);
return pay;
}
/*
** Impl of /json/report/list.
*/
static cson_value * json_report_list(){
Blob sql = empty_blob;
cson_value * pay = NULL;
if(!g.perm.RdTkt){
json_set_err(FSL_JSON_E_DENIED,
"Requires 'r' privileges.");
return NULL;
}
blob_append(&sql, "SELECT"
" rn AS report,"
" title as title,"
" owner as owner"
" FROM reportfmt"
" WHERE 1"
" ORDER BY title",
-1);
pay = json_sql_to_array_of_obj(&sql, NULL, 1);
if(!pay){
json_set_err(FSL_JSON_E_UNKNOWN,
"Quite unexpected: no ticket reports found.");
}
return pay;
}
/*
** Impl for /json/report/run
**
** Options/arguments:
**
** report=int (CLI: -report # or -r #) is the report number to run.
**
** limit=int (CLI: -limit # or -n #) -n is for compat. with other commands.
**
** format=a|o Specifies result format: a=each row is an arry, o=each
** row is an object. Default=o.
*/
static cson_value * json_report_run(){
int nReport;
Stmt q = empty_Stmt;
cson_object * pay = NULL;
cson_array * tktList = NULL;
char const * zFmt;
char * zTitle = NULL;
Blob sql = empty_blob;
int limit = 0;
cson_value * colNames = NULL;
int i;
if(!g.perm.RdTkt){
json_set_err(FSL_JSON_E_DENIED,
"Requires 'r' privileges.");
return NULL;
}
nReport = json_report_get_number(3);
if(nReport <=0){
json_set_err(FSL_JSON_E_MISSING_ARGS,
"Missing or invalid 'number' (-n) parameter.");
goto error;
}
zFmt = json_find_option_cstr2("format",NULL,"f",3);
if(!zFmt) zFmt = "o";
db_prepare(&q,
"SELECT sqlcode, "
" title"
" FROM reportfmt"
" WHERE rn=%d",
nReport);
if(SQLITE_ROW != db_step(&q)){
json_set_err(FSL_JSON_E_INVALID_ARGS,
"Report number %d not found.",
nReport);
db_finalize(&q);
goto error;
}
limit = json_find_option_int("limit",NULL,"n",-1);
/* Copy over report's SQL...*/
blob_append(&sql, db_column_text(&q,0), -1);
zTitle = mprintf("%s", db_column_text(&q,1));
db_finalize(&q);
db_prepare(&q, "%s", blob_str(&sql));
/** Build the response... */
pay = cson_new_object();
cson_object_set(pay, "report", json_new_int(nReport));
cson_object_set(pay, "title", json_new_string(zTitle));
if(limit>0){
cson_object_set(pay, "limit", json_new_int((limit<0) ? 0 : limit));
}
free(zTitle);
zTitle = NULL;
if(g.perm.TktFmt){
cson_object_set(pay, "sqlcode",
cson_value_new_string(blob_str(&sql),
(unsigned int)blob_size(&sql)));
}
blob_reset(&sql);
colNames = cson_sqlite3_column_names(q.pStmt);
cson_object_set( pay, "columnNames", colNames);
for( i = 0 ; ((limit>0) ?(i < limit) : 1)
&& (SQLITE_ROW == db_step(&q));
++i){
cson_value * row = ('a'==*zFmt)
? cson_sqlite3_row_to_array(q.pStmt)
: cson_sqlite3_row_to_object2(q.pStmt,
cson_value_get_array(colNames));
;
if(row && !tktList){
tktList = cson_new_array();
}
cson_array_append(tktList, row);
}
db_finalize(&q);
cson_object_set(pay, "tickets",
tktList ? cson_array_value(tktList) : cson_value_null());
goto end;
error:
assert(0 != g.json.resultCode);
cson_value_free( cson_object_value(pay) );
pay = NULL;
end:
return pay ? cson_object_value(pay) : NULL;
}
static cson_value * json_report_save(){
return NULL;
}
#endif /* FOSSIL_ENABLE_JSON */
|
Added src/json_tag.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 |
#ifdef FOSSIL_ENABLE_JSON
/*
** Copyright (c) 2011 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*/
#include "VERSION.h"
#include "config.h"
#include "json_tag.h"
#if INTERFACE
#include "json_detail.h"
#endif
static cson_value * json_tag_add();
static cson_value * json_tag_cancel();
static cson_value * json_tag_find();
static cson_value * json_tag_list();
/*
** Mapping of /json/tag/XXX commands/paths to callbacks.
*/
static const JsonPageDef JsonPageDefs_Tag[] = {
{"add", json_tag_add, 0},
{"cancel", json_tag_cancel, 0},
{"find", json_tag_find, 0},
{"list", json_tag_list, 0},
/* Last entry MUST have a NULL name. */
{NULL,NULL,0}
};
/*
** Implements the /json/tag family of pages/commands.
**
*/
cson_value * json_page_tag(){
return json_page_dispatch_helper(&JsonPageDefs_Tag[0]);
}
/*
** Impl of /json/tag/add.
*/
static cson_value * json_tag_add(){
cson_value * payV = NULL;
cson_object * pay = NULL;
char const * zName = NULL;
char const * zCheckin = NULL;
char fRaw = 0;
char fPropagate = 0;
char const * zValue = NULL;
const char *zPrefix = NULL;
if( !g.perm.Write ){
json_set_err(FSL_JSON_E_DENIED,
"Requires 'i' permissions.");
return NULL;
}
fRaw = json_find_option_bool("raw",NULL,NULL,0);
fPropagate = json_find_option_bool("propagate",NULL,NULL,0);
zName = json_find_option_cstr("name",NULL,NULL);
zPrefix = fRaw ? "" : "sym-";
if(!zName || !*zName){
if(!fossil_has_json()){
zName = json_command_arg(3);
}
if(!zName || !*zName){
json_set_err(FSL_JSON_E_MISSING_ARGS,
"'name' parameter is missing.");
return NULL;
}
}
zCheckin = json_find_option_cstr("checkin",NULL,NULL);
if( !zCheckin ){
if(!fossil_has_json()){
zCheckin = json_command_arg(4);
}
if(!zCheckin || !*zCheckin){
json_set_err(FSL_JSON_E_MISSING_ARGS,
"'checkin' parameter is missing.");
return NULL;
}
}
zValue = json_find_option_cstr("value",NULL,NULL);
if(!zValue && !fossil_has_json()){
zValue = json_command_arg(5);
}
db_begin_transaction();
tag_add_artifact(zPrefix, zName, zCheckin, zValue,
1+fPropagate,NULL/*DateOvrd*/,NULL/*UserOvrd*/);
db_end_transaction(0);
payV = cson_value_new_object();
pay = cson_value_get_object(payV);
cson_object_set(pay, "name", json_new_string(zName) );
cson_object_set(pay, "value", (zValue&&*zValue)
? json_new_string(zValue)
: cson_value_null());
cson_object_set(pay, "propagate", cson_value_new_bool(fPropagate));
cson_object_set(pay, "raw", cson_value_new_bool(fRaw));
{
Blob uu = empty_blob;
blob_append(&uu, zName, -1);
int const rc = name_to_uuid(&uu, 9, "*");
if(0!=rc){
json_set_err(FSL_JSON_E_UNKNOWN,"Could not convert name back to UUID!");
blob_reset(&uu);
goto error;
}
cson_object_set(pay, "appliedTo", json_new_string(blob_buffer(&uu)));
blob_reset(&uu);
}
goto ok;
error:
assert( 0 != g.json.resultCode );
cson_value_free(payV);
payV = NULL;
ok:
return payV;
}
/*
** Impl of /json/tag/cancel.
*/
static cson_value * json_tag_cancel(){
char const * zName = NULL;
char const * zCheckin = NULL;
char fRaw = 0;
const char *zPrefix = NULL;
if( !g.perm.Write ){
json_set_err(FSL_JSON_E_DENIED,
"Requires 'i' permissions.");
return NULL;
}
fRaw = json_find_option_bool("raw",NULL,NULL,0);
zPrefix = fRaw ? "" : "sym-";
zName = json_find_option_cstr("name",NULL,NULL);
if(!zName || !*zName){
if(!fossil_has_json()){
zName = json_command_arg(3);
}
if(!zName || !*zName){
json_set_err(FSL_JSON_E_MISSING_ARGS,
"'name' parameter is missing.");
return NULL;
}
}
zCheckin = json_find_option_cstr("checkin",NULL,NULL);
if( !zCheckin ){
if(!fossil_has_json()){
zCheckin = json_command_arg(4);
}
if(!zCheckin || !*zCheckin){
json_set_err(FSL_JSON_E_MISSING_ARGS,
"'checkin' parameter is missing.");
return NULL;
}
}
/* FIXME?: verify that the tag is currently active. We have no real
error case unless we do that.
*/
db_begin_transaction();
tag_add_artifact(zPrefix, zName, zCheckin, NULL, 0, 0, 0);
db_end_transaction(0);
return NULL;
}
/*
** Impl of /json/tag/find.
*/
static cson_value * json_tag_find(){
cson_value * payV = NULL;
cson_object * pay = NULL;
cson_value * listV = NULL;
cson_array * list = NULL;
char const * zName = NULL;
char const * zType = NULL;
char const * zType2 = NULL;
char fRaw = 0;
Stmt q = empty_Stmt;
int limit = 0;
int tagid = 0;
if( !g.perm.Read ){
json_set_err(FSL_JSON_E_DENIED,
"Requires 'o' permissions.");
return NULL;
}
zName = json_find_option_cstr("name",NULL,NULL);
if(!zName || !*zName){
if(!fossil_has_json()){
zName = json_command_arg(3);
}
if(!zName || !*zName){
json_set_err(FSL_JSON_E_MISSING_ARGS,
"'name' parameter is missing.");
return NULL;
}
}
zType = json_find_option_cstr("type",NULL,"t");
if(!zType || !*zType){
zType = "*";
zType2 = zType;
}else{
switch(*zType){
case 'c': zType = "ci"; zType2 = "checkin"; break;
case 'e': zType = "e"; zType2 = "event"; break;
case 'w': zType = "w"; zType2 = "wiki"; break;
case 't': zType = "t"; zType2 = "ticket"; break;
}
}
limit = json_find_option_int("limit",NULL,"n",0);
fRaw = json_find_option_bool("raw",NULL,NULL,0);
tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='%s' || %Q",
fRaw ? "" : "sym-",
zName);
payV = cson_value_new_object();
pay = cson_value_get_object(payV);
cson_object_set(pay, "name", json_new_string(zName));
cson_object_set(pay, "raw", cson_value_new_bool(fRaw));
cson_object_set(pay, "type", json_new_string(zType2));
cson_object_set(pay, "limit", json_new_int(limit));
#if 1
if( tagid<=0 ){
cson_object_set(pay,"artifacts", cson_value_null());
json_warn(FSL_JSON_W_TAG_NOT_FOUND, "Tag not found.");
return payV;
}
#endif
if( fRaw ){
db_prepare(&q,
"SELECT blob.uuid FROM tagxref, blob"
" WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
" AND tagxref.tagtype>0"
" AND blob.rid=tagxref.rid"
"%s LIMIT %d",
zName,
(limit>0)?"":"--", limit
);
while( db_step(&q)==SQLITE_ROW ){
if(!listV){
listV = cson_value_new_array();
list = cson_value_get_array(listV);
}
cson_array_append(list, cson_sqlite3_column_to_value(q.pStmt,0));
}
db_finalize(&q);
}else{
char const * zSqlBase = /*modified from timeline_query_for_tty()*/
" SELECT"
#if 0
" blob.rid AS rid,"
#endif
" uuid AS uuid,"
" cast(strftime('%s',event.mtime) as int) AS mtime,"
" coalesce(ecomment,comment) AS comment,"
" coalesce(euser,user) AS user,"
" CASE event.type"
" WHEN 'ci' THEN 'checkin'"
" WHEN 'w' THEN 'wiki'"
" WHEN 'e' THEN 'event'"
" WHEN 't' THEN 'ticket'"
" ELSE 'WTF?'"
" END"
" AS eventType"
" FROM event, blob"
" WHERE blob.rid=event.objid"
;
/* FIXME: re-add tags. */
db_prepare(&q,
"%s"
" AND event.type GLOB '%q'"
" AND blob.rid IN ("
" SELECT rid FROM tagxref"
" WHERE tagtype>0 AND tagid=%d"
" )"
" ORDER BY event.mtime DESC"
"%s LIMIT %d",
zSqlBase, zType, tagid,
(limit>0)?"":"--", limit
);
listV = json_stmt_to_array_of_obj(&q, NULL);
db_finalize(&q);
}
if(!listV) {
listV = cson_value_null();
}
cson_object_set(pay, "artifacts", listV);
return payV;
}
/*
** Impl for /json/tag/list
**
** TODOs:
**
** Add -type TYPE (ci, w, e, t)
*/
static cson_value * json_tag_list(){
cson_value * payV = NULL;
cson_object * pay = NULL;
cson_value const * tagsVal = NULL;
char const * zCheckin = NULL;
char fRaw = 0;
char fTicket = 0;
Stmt q = empty_Stmt;
if( !g.perm.Read ){
json_set_err(FSL_JSON_E_DENIED,
"Requires 'o' permissions.");
return NULL;
}
fRaw = json_find_option_bool("raw",NULL,NULL,0);
fTicket = json_find_option_bool("includeTickets","tkt","t",0);
zCheckin = json_find_option_cstr("checkin",NULL,NULL);
if( !zCheckin ){
zCheckin = json_command_arg( g.json.dispatchDepth + 1);
if( !zCheckin && cson_value_is_string(g.json.reqPayload.v) ){
zCheckin = cson_string_cstr(cson_value_get_string(g.json.reqPayload.v));
assert(zCheckin);
}
}
payV = cson_value_new_object();
pay = cson_value_get_object(payV);
cson_object_set(pay, "raw", cson_value_new_bool(fRaw) );
if( zCheckin ){
/**
Tags for a specific checkin. Output format:
RAW mode:
{
"sym-tagname": (value || null),
...other tags...
}
Non-raw:
{
"tagname": (value || null),
...other tags...
}
*/
cson_value * objV = NULL;
cson_object * obj = NULL;
int const rid = name_to_rid(zCheckin);
if(0==rid){
json_set_err(FSL_JSON_E_UNRESOLVED_UUID,
"Could not find artifact for checkin [%s].",
zCheckin);
goto error;
}
cson_object_set(pay, "checkin", json_new_string(zCheckin));
db_prepare(&q,
"SELECT tagname, value FROM tagxref, tag"
" WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid"
" AND tagtype>%d"
" ORDER BY tagname",
rid,
fRaw ? -1 : 0
);
while( SQLITE_ROW == db_step(&q) ){
const char *zName = db_column_text(&q, 0);
const char *zValue = db_column_text(&q, 1);
if( fRaw==0 ){
if( 0!=strncmp(zName, "sym-", 4) ) continue;
zName += 4;
assert( *zName );
}
if(NULL==objV){
objV = cson_value_new_object();
obj = cson_value_get_object(objV);
tagsVal = objV;
cson_object_set( pay, "tags", objV );
}
if( zValue && zValue[0] ){
cson_object_set( obj, zName, json_new_string(zValue) );
}else{
cson_object_set( obj, zName, cson_value_null() );
}
}
db_finalize(&q);
}else{/* all tags */
/* Output format:
RAW mode:
["tagname", "sym-tagname2",...]
Non-raw:
["tagname", "tagname2",...]
i don't really like the discrepancy in the format but this list
can get really long and (A) most tags don't have values, (B) i
don't want to bloat it more, and (C) cson_object_set() is O(N)
(N=current number of properties) because it uses an unsorted list
internally (for memory reasons), so this can slow down appreciably
on a long list. The culprit is really tkt- tags, as there is one
for each ticket (941 in the main fossil repo as of this writing).
*/
Blob sql = empty_blob;
cson_value * arV = NULL;
cson_array * ar = NULL;
blob_append(&sql,
"SELECT tagname FROM tag"
" WHERE EXISTS(SELECT 1 FROM tagxref"
" WHERE tagid=tag.tagid"
" AND tagtype>0)",
-1
);
if(!fTicket){
blob_append(&sql, " AND tagname NOT GLOB('tkt-*') ", -1);
}
blob_append(&sql,
" ORDER BY tagname", -1);
db_prepare(&q, blob_buffer(&sql));
blob_reset(&sql);
cson_object_set(pay, "includeTickets", cson_value_new_bool(fTicket) );
while( SQLITE_ROW == db_step(&q) ){
const char *zName = db_column_text(&q, 0);
if(NULL==arV){
arV = cson_value_new_array();
ar = cson_value_get_array(arV);
cson_object_set(pay, "tags", arV);
tagsVal = arV;
}
else if( !fRaw && (0==strncmp(zName, "sym-", 4))){
zName += 4;
assert( *zName );
}
cson_array_append(ar, json_new_string(zName));
}
db_finalize(&q);
}
goto end;
error:
assert(0 != g.json.resultCode);
cson_value_free(payV);
payV = NULL;
end:
if( payV && !tagsVal ){
cson_object_set( pay, "tags", cson_value_null() );
}
return payV;
}
#endif /* FOSSIL_ENABLE_JSON */
|
Added src/json_timeline.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 |
#ifdef FOSSIL_ENABLE_JSON
/*
** Copyright (c) 2011 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*/
#include "VERSION.h"
#include "config.h"
#include "json_timeline.h"
#if INTERFACE
#include "json_detail.h"
#endif
static cson_value * json_timeline_branch();
static cson_value * json_timeline_ci();
static cson_value * json_timeline_ticket();
/*
** Mapping of /json/timeline/XXX commands/paths to callbacks.
*/
static const JsonPageDef JsonPageDefs_Timeline[] = {
/* the short forms are only enabled in CLI mode, to avoid
that we end up with HTTP clients using 3 different names
for the same requests.
*/
{"branch", json_timeline_branch, 0},
{"checkin", json_timeline_ci, 0},
{"ci", json_timeline_ci, -1},
{"t", json_timeline_ticket, -1},
{"ticket", json_timeline_ticket, 0},
{"w", json_timeline_wiki, -1},
{"wiki", json_timeline_wiki, 0},
/* Last entry MUST have a NULL name. */
{NULL,NULL,0}
};
/*
** Implements the /json/timeline family of pages/commands. Far from
** complete.
**
*/
cson_value * json_page_timeline(){
#if 0
/* The original timeline code does not require 'h' access,
but it arguably should. For JSON mode i think one could argue
that History permissions are required.
*/
if(! g.perm.History && !g.perm.Read ){
json_set_err(FSL_JSON_E_DENIED, "Timeline requires 'h' or 'o' access.");
return NULL;
}
#endif
return json_page_dispatch_helper(&JsonPageDefs_Timeline[0]);
}
/*
** Create a temporary table suitable for storing timeline data.
*/
static void json_timeline_temp_table(void){
/* Field order MUST match that from json_timeline_query()!!! */
static const char zSql[] =
@ CREATE TEMP TABLE IF NOT EXISTS json_timeline(
@ sortId INTEGER PRIMARY KEY,
@ rid INTEGER,
@ uuid TEXT,
@ mtime INTEGER,
@ timestampString TEXT,
@ comment TEXT,
@ user TEXT,
@ isLeaf BOOLEAN,
@ bgColor TEXT,
@ eventType TEXT,
@ tags TEXT,
@ tagId INTEGER,
@ brief TEXT
@ )
;
db_multi_exec(zSql);
}
/*
** Return a pointer to a constant string that forms the basis
** for a timeline query for the JSON interface.
*/
const char const * json_timeline_query(void){
/* Field order MUST match that from json_timeline_temp_table()!!! */
static const char zBaseSql[] =
@ SELECT
@ NULL,
@ blob.rid,
@ uuid,
@ strftime('%%s',event.mtime),
@ datetime(event.mtime,'utc'),
@ coalesce(ecomment, comment),
@ coalesce(euser, user),
@ blob.rid IN leaf,
@ bgcolor,
@ event.type,
@ (SELECT group_concat(substr(tagname,5), ',') FROM tag, tagxref
@ WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid
@ AND tagxref.rid=blob.rid AND tagxref.tagtype>0) as tags,
@ tagid as tagId,
@ brief as brief
@ FROM event JOIN blob
@ WHERE blob.rid=event.objid
;
return zBaseSql;
}
/*
** Internal helper to append query information if the
** "tag" or "branch" request properties (CLI: --tag/--branch)
** are set. Limits the query to a particular branch/tag.
**
** tag works like HTML mode's "t" option and branch works like HTML
** mode's "r" option. They are very similar, but subtly different -
** tag mode shows only entries with a given tag but branch mode can
** also reveal some with "related" tags (meaning they were merged into
** the requested branch).
**
** pSql is the target blob to append the query [subset]
** to.
**
** Returns a positive value if it modifies pSql, 0 if it
** does not. It returns a negative value if the tag
** provided to the request was not found (pSql is not modified
** in that case.
**
** If payload is not NULL then on success its "tag" or "branch"
** property is set to the tag/branch name found in the request.
**
** Only one of "tag" or "branch" modes will work at a time, and if
** both are specified, which one takes precedence is unspecified.
*/
static char json_timeline_add_tag_branch_clause(Blob *pSql,
cson_object * pPayload){
char const * zTag = NULL;
char const * zBranch = NULL;
int tagid = 0;
if(! g.perm.Read ){
return 0;
}
zTag = json_find_option_cstr("tag",NULL,NULL);
if(!zTag || !*zTag){
zBranch = json_find_option_cstr("branch",NULL,NULL);
if(!zBranch || !*zBranch){
return 0;
}
zTag = zBranch;
}
tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'",
zTag);
if(tagid<=0){
return -1;
}
if(pPayload){
cson_object_set( pPayload, zBranch ? "branch" : "tag", json_new_string(zTag) );
}
blob_appendf(pSql,
" AND ("
" EXISTS(SELECT 1 FROM tagxref"
" WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)",
tagid);
if(zBranch){
/* from "r" flag code in page_timeline().*/
blob_appendf(pSql,
" OR EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=cid"
" WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)",
tagid);
#if 0 /* from the undocumented "mionly" flag in page_timeline() */
blob_appendf(pSql,
" OR EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid"
" WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)",
tagid);
#endif
}
blob_append(pSql," ) ",3);
return 1;
}
/*
** Helper for the timeline family of functions. Possibly appends 1
** AND clause and an ORDER BY clause to pSql, depending on the state
** of the "after" ("a") or "before" ("b") environment parameters.
** This function gives "after" precedence over "before", and only
** applies one of them.
**
** Returns -1 if it adds a "before" clause, 1 if it adds
** an "after" clause, and 0 if adds only an order-by clause.
*/
static char json_timeline_add_time_clause(Blob *pSql){
char const * zAfter = NULL;
char const * zBefore = NULL;
int rc = 0;
zAfter = json_find_option_cstr("after",NULL,"a");
zBefore = zAfter ? NULL : json_find_option_cstr("before",NULL,"b");
if(zAfter&&*zAfter){
while( fossil_isspace(*zAfter) ) ++zAfter;
blob_appendf(pSql,
" AND event.mtime>=(SELECT julianday(%Q,'utc')) "
" ORDER BY event.mtime ASC ",
zAfter);
rc = 1;
}else if(zBefore && *zBefore){
while( fossil_isspace(*zBefore) ) ++zBefore;
blob_appendf(pSql,
" AND event.mtime<=(SELECT julianday(%Q,'utc')) "
" ORDER BY event.mtime DESC ",
zBefore);
rc = -1;
}else{
blob_append(pSql, " ORDER BY event.mtime DESC ", -1);
rc = 0;
}
return rc;
}
/*
** Tries to figure out a timeline query length limit base on
** environment parameters. If it can it returns that value,
** else it returns some statically defined default value.
**
** Never returns a negative value. 0 means no limit.
*/
static int json_timeline_limit(){
static const int defaultLimit = 20;
int limit = -1;
if(!g.isHTTP){/* CLI mode */
char const * arg = find_option("limit","n",1);
if(arg && *arg){
limit = atoi(arg);
}
}
if( (limit<0) && fossil_has_json() ){
limit = json_getenv_int("limit",-1);
}
return (limit<0) ? defaultLimit : limit;
}
/*
** Internal helper for the json_timeline_EVENTTYPE() family of
** functions. zEventType must be one of (ci, w, t). pSql must be a
** cleanly-initialized, empty Blob to store the sql in. If pPayload is
** not NULL it is assumed to be the pending response payload. If
** json_timeline_limit() returns non-0, this function adds a LIMIT
** clause to the generated SQL.
**
** If pPayload is not NULL then this might add properties to pPayload,
** reflecting options set in the request environment.
**
** Returns 0 on success. On error processing should not continue and
** the returned value should be used as g.json.resultCode.
*/
static int json_timeline_setup_sql( char const * zEventType,
Blob * pSql,
cson_object * pPayload ){
int limit;
assert( zEventType && *zEventType && pSql );
json_timeline_temp_table();
blob_append(pSql, "INSERT OR IGNORE INTO json_timeline ", -1);
blob_append(pSql, json_timeline_query(), -1 );
blob_appendf(pSql, " AND event.type IN(%Q) ", zEventType);
if( json_timeline_add_tag_branch_clause(pSql, pPayload) < 0 ){
return FSL_JSON_E_INVALID_ARGS;
}
json_timeline_add_time_clause(pSql);
limit = json_timeline_limit();
if(limit>=0){
blob_appendf(pSql,"LIMIT %d ",limit);
}
if(pPayload){
cson_object_set(pPayload, "limit", json_new_int(limit));
}
return 0;
}
/*
** If any files are associated with the given rid, a JSON array
** containing information about them is returned (and is owned by the
** caller). If no files are associated with it then NULL is returned.
*/
cson_value * json_get_changed_files(int rid){
cson_value * rowsV = NULL;
cson_array * rows = NULL;
Stmt q = empty_Stmt;
db_prepare(&q,
#if 0
"SELECT (mlink.pid==0) AS isNew,"
" (mlink.fid==0) AS isDel,"
" filename.name AS name"
" FROM mlink, filename"
" WHERE mid=%d"
" AND pid!=fid"
" AND filename.fnid=mlink.fnid"
" ORDER BY 3 /*sort*/",
#else
"SELECT (pid==0) AS isnew,"
" (fid==0) AS isdel,"
" (SELECT name FROM filename WHERE fnid=mlink.fnid) AS name,"
" (SELECT uuid FROM blob WHERE rid=fid) as uuid,"
" (SELECT uuid FROM blob WHERE rid=pid) as prevUuid"
" FROM mlink"
" WHERE mid=%d AND pid!=fid"
" ORDER BY name /*sort*/",
#endif
rid
);
while( (SQLITE_ROW == db_step(&q)) ){
cson_value * rowV = cson_value_new_object();
cson_object * row = cson_value_get_object(rowV);
int const isNew = db_column_int(&q,0);
int const isDel = db_column_int(&q,1);
char * zDownload = NULL;
if(!rowsV){
rowsV = cson_value_new_array();
rows = cson_value_get_array(rowsV);
}
cson_array_append( rows, rowV );
cson_object_set(row, "name", json_new_string(db_column_text(&q,2)));
cson_object_set(row, "uuid", json_new_string(db_column_text(&q,3)));
if(!isNew){
cson_object_set(row, "prevUuid", json_new_string(db_column_text(&q,4)));
}
cson_object_set(row, "state",
json_new_string(isNew
? "added"
: (isDel
? "removed"
: "modified")));
zDownload = mprintf("/raw/%s?name=%s",
/* reminder: g.zBaseURL is of course not set for CLI mode. */
db_column_text(&q,2),
db_column_text(&q,3));
cson_object_set(row, "downloadPath", json_new_string(zDownload));
free(zDownload);
}
db_finalize(&q);
return rowsV;
}
static cson_value * json_timeline_branch(){
cson_value * pay = NULL;
Blob sql = empty_blob;
Stmt q = empty_Stmt;
if(!g.perm.Read){
json_set_err(FSL_JSON_E_DENIED,
"Requires 'o' permissions.");
return NULL;
}
json_timeline_temp_table();
blob_append(&sql,
"SELECT"
" blob.rid AS rid,"
" uuid AS uuid,"
" datetime(event.mtime,'utc') as mtime,"
" coalesce(ecomment, comment) as comment,"
" coalesce(euser, user) as user,"
" blob.rid IN leaf as isLeaf,"
" bgcolor as bgColor"
" FROM event JOIN blob"
" WHERE blob.rid=event.objid",
-1);
blob_appendf(&sql,
" AND event.type='ci'"
" AND blob.rid IN (SELECT rid FROM tagxref"
" WHERE tagtype>0 AND tagid=%d AND srcid!=0)"
" ORDER BY event.mtime DESC",
TAG_BRANCH);
db_prepare(&q,"%s", blob_str(&sql));
blob_reset(&sql);
pay = json_stmt_to_array_of_obj(&q, NULL);
db_finalize(&q);
assert(NULL != pay);
if(pay){
/* get the array-form tags of each record. */
cson_string * tags = cson_new_string("tags",4);
cson_string * isLeaf = cson_new_string("isLeaf",6);
cson_value_add_reference( cson_string_value(tags) );
cson_value_add_reference( cson_string_value(isLeaf) );
cson_array * ar = cson_value_get_array(pay);
unsigned int i = 0;
unsigned int len = cson_array_length_get(ar);
for( ; i < len; ++i ){
cson_object * row = cson_value_get_object(cson_array_get(ar,i));
int rid = cson_value_get_integer(cson_object_get(row,"rid"));
if(row>0) {
cson_object_set_s(row, tags, json_tags_for_checkin_rid(rid,0));
cson_object_set_s(row, isLeaf, json_value_to_bool(cson_object_get(row,"isLeaf")));
}
}
cson_value_free( cson_string_value(tags) );
cson_value_free( cson_string_value(isLeaf) );
}
goto end;
error:
assert( 0 != g.json.resultCode );
cson_value_free(pay);
end:
return pay;
}
/*
** Implementation of /json/timeline/ci.
**
** Still a few TODOs (like figuring out how to structure
** inheritance info).
*/
static cson_value * json_timeline_ci(){
cson_value * payV = NULL;
cson_object * pay = NULL;
cson_value * tmp = NULL;
cson_value * listV = NULL;
cson_array * list = NULL;
int check = 0;
char showFiles = -1/*magic number*/;
Stmt q = empty_Stmt;
char warnRowToJsonFailed = 0;
char warnStringToArrayFailed = 0;
Blob sql = empty_blob;
if( !g.perm.Read ){
/* IMO this falls more under the category of g.perm.History, but
i'm following the original timeline impl here.
*/
json_set_err( FSL_JSON_E_DENIED, "Checkin timeline requires 'o' access." );
return NULL;
}
showFiles = json_find_option_bool("files",NULL,"f",0);
payV = cson_value_new_object();
pay = cson_value_get_object(payV);
check = json_timeline_setup_sql( "ci", &sql, pay );
if(check){
json_set_err(check, "Query initialization failed.");
goto error;
}
#define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \
json_set_err((cson_rc.AllocError==check) \
? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN,\
"Object property insertion failed"); \
goto error;\
} (void)0
#if 0
/* only for testing! */
tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql)));
SET("timelineSql");
#endif
db_multi_exec(blob_buffer(&sql));
blob_reset(&sql);
db_prepare(&q, "SELECT "
" rid AS rid"
#if 0
" uuid AS uuid,"
" mtime AS timestamp,"
# if 0
" timestampString AS timestampString,"
# endif
" comment AS comment, "
" user AS user,"
" isLeaf AS isLeaf," /*FIXME: convert to JSON bool */
" bgColor AS bgColor," /* why always null? */
" eventType AS eventType"
# if 0
" tags AS tags"
/*tagId is always null?*/
" tagId AS tagId"
# endif
#endif
" FROM json_timeline"
" ORDER BY rowid");
listV = cson_value_new_array();
list = cson_value_get_array(listV);
tmp = listV;
SET("timeline");
while( (SQLITE_ROW == db_step(&q) )){
/* convert each row into a JSON object...*/
int const rid = db_column_int(&q,0);
cson_value * rowV = json_artifact_for_ci(rid, showFiles);
cson_object * row = cson_value_get_object(rowV);
if(!row){
if( !warnRowToJsonFailed ){
warnRowToJsonFailed = 1;
json_warn( FSL_JSON_W_ROW_TO_JSON_FAILED,
"Could not convert at least one timeline result row to JSON." );
}
continue;
}
cson_array_append(list, rowV);
}
#undef SET
goto ok;
error:
assert( 0 != g.json.resultCode );
cson_value_free(payV);
payV = NULL;
ok:
db_finalize(&q);
return payV;
}
/*
** Implementation of /json/timeline/wiki.
**
*/
cson_value * json_timeline_wiki(){
/* This code is 95% the same as json_timeline_ci(), by the way. */
cson_value * payV = NULL;
cson_object * pay = NULL;
cson_value * tmp = NULL;
cson_array * list = NULL;
int check = 0;
Stmt q = empty_Stmt;
Blob sql = empty_blob;
if( !g.perm.RdWiki && !g.perm.Read ){
json_set_err( FSL_JSON_E_DENIED, "Wiki timeline requires 'o' or 'j' access.");
return NULL;
}
payV = cson_value_new_object();
pay = cson_value_get_object(payV);
check = json_timeline_setup_sql( "w", &sql, pay );
if(check){
json_set_err(check, "Query initialization failed.");
goto error;
}
#define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \
json_set_err((cson_rc.AllocError==check) \
? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN, \
"Object property insertion failed."); \
goto error;\
} (void)0
#if 0
/* only for testing! */
tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql)));
SET("timelineSql");
#endif
db_multi_exec(blob_buffer(&sql));
blob_reset(&sql);
db_prepare(&q, "SELECT rid AS rid,"
" uuid AS uuid,"
" mtime AS timestamp,"
#if 0
" timestampString AS timestampString,"
#endif
" comment AS comment, "
" user AS user,"
" eventType AS eventType"
#if 0
/* can wiki pages have tags? */
" tags AS tags," /*FIXME: split this into
a JSON array*/
" tagId AS tagId,"
#endif
" FROM json_timeline"
" ORDER BY rowid",
-1);
list = cson_new_array();
tmp = cson_array_value(list);
SET("timeline");
json_stmt_to_array_of_obj(&q, list);
#undef SET
goto ok;
error:
assert( 0 != g.json.resultCode );
cson_value_free(payV);
payV = NULL;
ok:
db_finalize(&q);
blob_reset(&sql);
return payV;
}
/*
** Implementation of /json/timeline/ticket.
**
*/
static cson_value * json_timeline_ticket(){
/* This code is 95% the same as json_timeline_ci(), by the way. */
cson_value * payV = NULL;
cson_object * pay = NULL;
cson_value * tmp = NULL;
cson_value * listV = NULL;
cson_array * list = NULL;
int check = 0;
Stmt q = empty_Stmt;
Blob sql = empty_blob;
if( !g.perm.RdTkt && !g.perm.Read ){
json_set_err(FSL_JSON_E_DENIED, "Ticket timeline requires 'o' or 'r' access.");
return NULL;
}
payV = cson_value_new_object();
pay = cson_value_get_object(payV);
check = json_timeline_setup_sql( "t", &sql, pay );
if(check){
json_set_err(check, "Query initialization failed.");
goto error;
}
db_multi_exec(blob_buffer(&sql));
#define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \
json_set_err((cson_rc.AllocError==check) \
? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN, \
"Object property insertion failed."); \
goto error;\
} (void)0
#if 0
/* only for testing! */
tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql)));
SET("timelineSql");
#endif
blob_reset(&sql);
/*
REMINDER/FIXME(?): we have both uuid (the change uuid?) and
ticketUuid (the actual ticket). This is different from the wiki
timeline, where we only have the wiki page uuid.
*/
db_prepare(&q, "SELECT rid AS rid,"
" uuid AS uuid,"
" mtime AS timestamp,"
#if 0
" timestampString AS timestampString,"
#endif
" user AS user,"
" eventType AS eventType,"
" comment AS comment,"
" brief AS briefComment"
" FROM json_timeline"
" ORDER BY rowid",
-1);
listV = cson_value_new_array();
list = cson_value_get_array(listV);
tmp = listV;
SET("timeline");
while( (SQLITE_ROW == db_step(&q) )){
/* convert each row into a JSON object...*/
int rc;
int const rid = db_column_int(&q,0);
Manifest * pMan = NULL;
cson_value * rowV = cson_sqlite3_row_to_object(q.pStmt);
cson_object * row = cson_value_get_object(rowV);
if(!row){
json_warn( FSL_JSON_W_ROW_TO_JSON_FAILED,
"Could not convert at least one timeline result row to JSON." );
continue;
}
pMan = manifest_get(rid, CFTYPE_TICKET);
assert( pMan && "Manifest is NULL!?!" );
if( pMan ){
/* FIXME: certainly there's a more efficient way for use to get
the ticket UUIDs?
*/
cson_object_set(row,"ticketUuid",json_new_string(pMan->zTicketUuid));
manifest_destroy(pMan);
}
rc = cson_array_append( list, rowV );
if( 0 != rc ){
cson_value_free(rowV);
g.json.resultCode = (cson_rc.AllocError==rc)
? FSL_JSON_E_ALLOC
: FSL_JSON_E_UNKNOWN;
goto error;
}
}
#undef SET
goto ok;
error:
assert( 0 != g.json.resultCode );
cson_value_free(payV);
payV = NULL;
ok:
blob_reset(&sql);
db_finalize(&q);
return payV;
}
#endif /* FOSSIL_ENABLE_JSON */
|
Added src/json_user.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 |
#ifdef FOSSIL_ENABLE_JSON
/*
** Copyright (c) 2011 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*/
#include "VERSION.h"
#include "config.h"
#include "json_user.h"
#if INTERFACE
#include "json_detail.h"
#endif
static cson_value * json_user_get();
static cson_value * json_user_list();
static cson_value * json_user_save();
#if 0
static cson_value * json_user_create();
#endif
/*
** Mapping of /json/user/XXX commands/paths to callbacks.
*/
static const JsonPageDef JsonPageDefs_User[] = {
{"create", json_page_nyi, 0},
{"save", json_user_save, 0},
{"get", json_user_get, 0},
{"list", json_user_list, 0},
/* Last entry MUST have a NULL name. */
{NULL,NULL,0}
};
/*
** Implements the /json/user family of pages/commands.
**
*/
cson_value * json_page_user(){
return json_page_dispatch_helper(&JsonPageDefs_User[0]);
}
/*
** Impl of /json/user/list. Requires admin rights.
*/
static cson_value * json_user_list(){
cson_value * payV = NULL;
Stmt q;
if(!g.perm.Admin){
g.json.resultCode = FSL_JSON_E_DENIED;
return NULL;
}
db_prepare(&q,"SELECT uid AS uid,"
" login AS name,"
" cap AS capabilities,"
" info AS info,"
" mtime AS mtime"
" FROM user ORDER BY login");
payV = json_stmt_to_array_of_obj(&q, NULL);
db_finalize(&q);
if(NULL == payV){
json_set_err(FSL_JSON_E_UNKNOWN,
"Could not convert user list to JSON.");
}
return payV;
}
/*
** Creates a new JSON Object based on the db state of
** the given user name. On error (no record found)
** it returns NULL, else the caller owns the returned
** object.
*/
static cson_value * json_load_user_by_name(char const * zName){
cson_value * u = NULL;
Stmt q;
db_prepare(&q,"SELECT uid AS uid,"
" login AS name,"
" cap AS capabilities,"
" info AS info,"
" mtime AS mtime"
" FROM user"
" WHERE login=%Q",
zName);
if( (SQLITE_ROW == db_step(&q)) ){
u = cson_sqlite3_row_to_object(q.pStmt);
}
db_finalize(&q);
return u;
}
/*
** Identical to _load_user_by_name(), but expects a user ID. Returns
** NULL if no user found with that ID.
*/
static cson_value * json_load_user_by_id(int uid){
cson_value * u = NULL;
Stmt q;
db_prepare(&q,"SELECT uid AS uid,"
" login AS name,"
" cap AS capabilities,"
" info AS info,"
" mtime AS mtime"
" FROM user"
" WHERE uid=%d",
uid);
if( (SQLITE_ROW == db_step(&q)) ){
u = cson_sqlite3_row_to_object(q.pStmt);
}
db_finalize(&q);
return u;
}
/*
** Impl of /json/user/get. Requires admin rights.
*/
static cson_value * json_user_get(){
cson_value * payV = NULL;
char const * pUser = NULL;
if(!g.perm.Admin){
json_set_err(FSL_JSON_E_DENIED,
"Requires 'a' privileges.");
return NULL;
}
pUser = json_command_arg(g.json.dispatchDepth+1);
if( g.isHTTP && (!pUser || !*pUser) ){
pUser = json_getenv_cstr("name")
/* ACHTUNG: fossil apparently internally sets name=user/get/XYZ
if we pass the name as part of the path, which is why we check
with json_command_path() before trying to get("name").
*/;
}
if(!pUser || !*pUser){
json_set_err(FSL_JSON_E_MISSING_ARGS,"Missing 'name' property.");
return NULL;
}
payV = json_load_user_by_name(pUser);
if(!payV){
json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND,"User not found.");
}
return payV;
}
/*
** Expects pUser to contain fossil user fields in JSON form: name,
** uid, info, capabilities, password.
**
** At least one of (name, uid) must be included. All others are
** optional and their db fields will not be updated if those fields
** are not included in pUser.
**
** If uid is specified then name may refer to a _new_ name
** for a user, otherwise the name must refer to an existing user.
** If uid=-1 then the name must be specified and a new user is
** created (failes if one already exists).
**
** If uid is not set, this function might modify pUser to contain the
** db-found (or inserted) user ID.
**
** On error g.json's error state is set one of the FSL_JSON_E_xxx
** values from FossilJsonCodes is returned.
**
** On success the db record for the given user is updated.
**
** Requires either Admin, Setup, or Password access. Non-admin/setup
** users can only change their own information.
**
** TODOs:
**
** - Admin non-Setup users cannot change the information for Setup
** users.
**
*/
int json_user_update_from_json( cson_object * pUser ){
#define CSTR(X) cson_string_cstr(cson_value_get_string( cson_object_get(pUser, X ) ))
char const * zName = CSTR("name");
char const * zNameNew = zName;
char * zNameFree = NULL;
char const * zInfo = CSTR("info");
char const * zCap = CSTR("capabilities");
char const * zPW = CSTR("password");
cson_value const * forceLogout = cson_object_get(pUser, "forceLogout");
int gotFields = 0;
#undef CSTR
cson_int_t uid = cson_value_get_integer( cson_object_get(pUser, "uid") );
Blob sql = empty_blob;
Stmt q = empty_Stmt;
if(!g.perm.Admin && !g.perm.Setup && !g.perm.Password){
return json_set_err( FSL_JSON_E_DENIED,
"Password change requires 'a', 's', "
"or 'p' permissions.");
}
if(uid<=0 && (!zName||!*zName)){
return json_set_err(FSL_JSON_E_MISSING_ARGS,
"One of 'uid' or 'name' is required.");
}else if(uid>0){
zNameFree = db_text(NULL, "SELECT login FROM user WHERE uid=%d",uid);
if(!zNameFree){
return json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND,
"No login found for uid %d.", uid);
}
zName = zNameFree;
}else if(-1==uid){
/* try to create a new user */
if(!g.perm.Admin && !g.perm.Setup){
return json_set_err(FSL_JSON_E_DENIED,
"Requires 'a' or 's' privileges.");
} else if(!zName || !*zName){
return json_set_err(FSL_JSON_E_MISSING_ARGS,
"No name specified for new user.");
}else if( db_exists("SELECT 1 FROM user WHERE login=%Q", zName) ){
return json_set_err(FSL_JSON_E_RESOURCE_ALREADY_EXISTS,
"User %s already exists.", zName);
}else{
Stmt ins = empty_Stmt;
db_prepare(&ins, "INSERT INTO user (login) VALUES(%Q)",zName);
db_step( &ins );
db_finalize(&ins);
uid = db_int(0,"SELECT uid FROM user WHERE login=%Q", zName);
assert(uid>0);
zNameNew = zName;
cson_object_set( pUser, "uid", cson_value_new_integer(uid) );
}
}else{
uid = db_int(0,"SELECT uid FROM user WHERE login=%Q", zName);
if(uid<=0){
return json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND,
"No login found for user [%s].", zName);
}
cson_object_set( pUser, "uid", cson_value_new_integer(uid) );
}
/* Maintenance note: all error-returns from here on out should go
via 'goto error' in order to clean up.
*/
if(uid != g.userUid){
/*
TODO: do not allow an admin user to modify a setup user
unless the admin is also a setup user. setup.c uses
that logic. There is a corner case for a NEW Setup user
which the admin is just installing. Hmm.
*/
if(!g.perm.Admin && !g.perm.Setup){
json_set_err(FSL_JSON_E_DENIED,
"Changing another user's data requires "
"'a' or 's' privileges.");
}
}
blob_append(&sql, "UPDATE USER SET",-1 );
blob_append(&sql, " mtime=cast(strftime('%s') AS INTEGER)", -1);
if((uid>0) && zNameNew){
/* Check for name change... */
if( (!g.perm.Admin && !g.perm.Setup)
&& zNameNew && (zName != zNameNew)
&& (0!=strcmp(zNameNew,zName))){
json_set_err( FSL_JSON_E_DENIED,
"Modifying user names requires 'a' or 's' privileges.");
goto error;
}
forceLogout = cson_value_true()
/* reminders: 1) does not allocate.
2) we do this because changing a name
invalidates any login token because the old name
is part of the token hash.
*/;
blob_appendf(&sql, ", login=%Q", zNameNew);
++gotFields;
}
if( zCap ){
blob_appendf(&sql, ", cap=%Q", zCap);
++gotFields;
}
if( zPW ){
char * zPWHash = NULL;
++gotFields;
zPWHash = sha1_shared_secret(zPW, zNameNew ? zNameNew : zName, NULL);
blob_appendf(&sql, ", pw=%Q", zPWHash);
free(zPWHash);
}
if( zInfo ){
blob_appendf(&sql, ", info=%Q", zInfo);
++gotFields;
}
if((g.perm.Admin || g.perm.Setup)
&& forceLogout && cson_value_get_bool(forceLogout)){
blob_append(&sql, ", cookie=NULL, cexpire=NULL", -1);
++gotFields;
}
if(!gotFields){
json_set_err( FSL_JSON_E_MISSING_ARGS,
"Required user data are missing.");
goto error;
}
assert(uid>0);
blob_appendf(&sql, " WHERE uid=%d", uid);
free( zNameFree );
#if 0
puts(blob_str(&sql));
cson_output_FILE( cson_object_value(pUser), stdout, NULL );
#endif
db_prepare(&q, "%s", blob_str(&sql));
blob_reset(&sql);
db_exec(&q);
db_finalize(&q);
return 0;
error:
assert(0 != g.json.resultCode);
free(zNameFree);
blob_reset(&sql);
return g.json.resultCode;
}
/*
** Impl of /json/user/save.
**
** TODOs:
**
** - Return something useful in the payload (at least the id of the
** modified/created user).
*/
static cson_value * json_user_save(){
/* try to get user info from GET/CLI args and construct
a JSON form of it... */
cson_object * u = cson_new_object();
char const * str = NULL;
char b = -1;
int i = -1;
int uid = -1;
cson_value * payload = NULL;
#define PROP(LK) str = json_find_option_cstr(LK,NULL,NULL); \
if(str){ cson_object_set(u, LK, json_new_string(str)); } (void)0
PROP("name");
PROP("password");
PROP("info");
PROP("capabilities");
#undef PROP
#define PROP(LK,DFLT) b = json_find_option_bool(LK,NULL,NULL,DFLT); \
if(DFLT!=b){ cson_object_set(u, LK, cson_value_new_bool(b)); } (void)0
PROP("forceLogout",-1);
#undef PROP
#define PROP(LK,DFLT) i = json_find_option_int(LK,NULL,NULL,DFLT); \
if(DFLT != i){ cson_object_set(u, LK, cson_value_new_integer(i)); } (void)0
PROP("uid",-99);
#undef PROP
if( g.json.reqPayload.o ){
cson_object_merge( u, g.json.reqPayload.o, CSON_MERGE_NO_RECURSE );
}
json_user_update_from_json( u );
if(!g.json.resultCode){
uid = cson_value_get_integer( cson_object_get(u, "uid") );
assert((uid>0) && "Something went wrong in json_user_update_from_json()");
payload = json_load_user_by_id(uid);
}
cson_free_object(u);
return payload;
}
#endif /* FOSSIL_ENABLE_JSON */
|
Added src/json_wiki.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 |
#ifdef FOSSIL_ENABLE_JSON
/*
** Copyright (c) 2011 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*/
#include "VERSION.h"
#include "config.h"
#include "json_wiki.h"
#if INTERFACE
#include "json_detail.h"
#endif
static cson_value * json_wiki_create();
static cson_value * json_wiki_get();
static cson_value * json_wiki_list();
static cson_value * json_wiki_save();
/*
** Mapping of /json/wiki/XXX commands/paths to callbacks.
*/
static const JsonPageDef JsonPageDefs_Wiki[] = {
{"create", json_wiki_create, 1},
{"get", json_wiki_get, 0},
{"list", json_wiki_list, 0},
{"save", json_wiki_save, 1},
{"timeline", json_timeline_wiki,0},
/* Last entry MUST have a NULL name. */
{NULL,NULL,0}
};
/*
** Implements the /json/wiki family of pages/commands.
**
*/
cson_value * json_page_wiki(){
return json_page_dispatch_helper(&JsonPageDefs_Wiki[0]);
}
/*
** Loads the given wiki page and creates a JSON object representation
** of it. If the page is not found then NULL is returned. If doParse
** is true then the page content is HTML-ized using fossil's
** conventional wiki format, else it is not parsed.
**
** The returned value, if not NULL, is-a JSON Object owned by the
** caller.
*/
cson_value * json_get_wiki_page_by_name(char const * zPageName, char doParse){
int rid;
Manifest *pWiki = 0;
char const * zBody = NULL;
char const * zFormat = NULL;
char * zUuid = NULL;
Stmt q;
db_prepare(&q,
"SELECT x.rid, b.uuid FROM tag t, tagxref x, blob b"
" WHERE x.tagid=t.tagid AND t.tagname='wiki-%q' "
" AND b.rid=x.rid"
" ORDER BY x.mtime DESC LIMIT 1",
zPageName
);
if( (SQLITE_ROW != db_step(&q)) ){
db_finalize(&q);
return NULL;
}
rid = db_column_int(&q,0);
zUuid = db_column_malloc(&q,1);
db_finalize(&q);
if( (pWiki = manifest_get(rid, CFTYPE_WIKI))!=0 ){
zBody = pWiki->zWiki;
}
{
unsigned int len;
cson_object * pay = cson_new_object();
cson_object_set(pay,"name",json_new_string(zPageName));
cson_object_set(pay,"uuid",json_new_string(zUuid));
free(zUuid);
zUuid = NULL;
/*cson_object_set(pay,"rid",json_new_int((cson_int_t)rid));*/
cson_object_set(pay,"lastSavedBy",json_new_string(pWiki->zUser));
cson_object_set(pay,FossilJsonKeys.timestamp, json_julian_to_timestamp(pWiki->rDate));
cson_object_set(pay,"contentFormat",json_new_string(zFormat));
if( doParse ){
Blob content = empty_blob;
Blob raw = empty_blob;
blob_append(&raw,zBody,-1);
wiki_convert(&raw,&content,0);
len = strlen(zBody);
len = (unsigned int)blob_size(&content);
cson_object_set(pay,"contentLength",json_new_int((cson_int_t)len));
cson_object_set(pay,"content",
cson_value_new_string(blob_buffer(&content),len));
blob_reset(&content);
blob_reset(&raw);
}else{
len = zBody ? strlen(zBody) : 0;
cson_object_set(pay,"contentLength",json_new_int((cson_int_t)len));
cson_object_set(pay,"content",cson_value_new_string(zBody,len));
}
/*TODO: add 'T' (tag) fields*/
/*TODO: add the 'A' card (file attachment) entries?*/
manifest_destroy(pWiki);
return cson_object_value(pay);
}
}
/*
** Searches for a wiki page with the given rid. If found it behaves
** like json_get_wiki_page_by_name(pageName, doParse), else it returns
** NULL.
*/
cson_value * json_get_wiki_page_by_rid(int rid, char doParse){
char * zPageName = NULL;
cson_value * rc = NULL;
zPageName = db_text(NULL,
"SELECT substr(t.tagname,6) AS name "
" FROM tag t, tagxref x, blob b "
" WHERE b.rid=%d "
" AND t.tagname GLOB 'wiki-*'"
" AND x.tagid=t.tagid AND b.rid=x.rid ",
rid);
if( zPageName ){
rc = json_get_wiki_page_by_name(zPageName, doParse);
free(zPageName);
}
return rc;
}
/*
** Implementation of /json/wiki/get.
**
*/
static cson_value * json_wiki_get(){
int rid;
Manifest *pWiki = 0;
char const * zBody = NULL;
char const * zPageName;
char const * zFormat = NULL;
char * zUuid = NULL;
Stmt q;
if( !g.perm.RdWiki && !g.perm.Read ){
json_set_err(FSL_JSON_E_DENIED,
"Requires 'o' or 'j' access.");
return NULL;
}
zPageName = json_find_option_cstr("name",NULL,"n")
/* Damn... fossil automatically sets name to the PATH
part after /json, so we need a workaround down here....
*/
;
if( zPageName && (NULL != strstr(zPageName, "/"))){
/* Assume that we picked up a path remnant. */
zPageName = NULL;
}
if( !zPageName && cson_value_is_string(g.json.reqPayload.v) ){
zPageName = cson_string_cstr(cson_value_get_string(g.json.reqPayload.v));
}
if(!zPageName){
zPageName = json_command_arg(g.json.dispatchDepth+1);
}
if(!zPageName||!*zPageName){
json_set_err(FSL_JSON_E_MISSING_ARGS,
"'name' argument is missing.");
return NULL;
}
zFormat = json_find_option_cstr("format",NULL,"f");
if(!zFormat || !*zFormat){
zFormat = "raw";
}
if( 'r' != *zFormat ){
zFormat = "html";
}
return json_get_wiki_page_by_name(zPageName, 'h'==*zFormat);
}
/*
** Internal impl of /wiki/save and /wiki/create. If createMode is 0
** and the page already exists then a
** FSL_JSON_E_RESOURCE_ALREADY_EXISTS error is triggered. If
** createMode is false then the FSL_JSON_E_RESOURCE_NOT_FOUND is
** triggered if the page does not already exists.
**
** Note that the error triggered when createMode==0 and no such page
** exists is rather arbitrary - we could just as well create the entry
** here if it doesn't already exist. With that, save/create would
** become one operation. That said, i expect there are people who
** would categorize such behaviour as "being too clever" or "doing too
** much automatically" (and i would likely agree with them).
**
** If allowCreateIfExists is true then this function will allow a new
** page to be created even if createMode is false.
*/
static cson_value * json_wiki_create_or_save(char createMode,
char allowCreateIfExists){
Blob content = empty_blob;
cson_value * nameV;
cson_value * contentV;
cson_value * emptyContent = NULL;
cson_value * payV = NULL;
cson_object * pay = NULL;
cson_string const * jstr = NULL;
char const * zContent;
char const * zBody = NULL;
char const * zPageName;
unsigned int contentLen = 0;
int rid;
if( (createMode && !g.perm.NewWiki)
|| (!createMode && !g.perm.WrWiki)){
json_set_err(FSL_JSON_E_DENIED,
"Requires '%c' permissions.",
(createMode ? 'f' : 'k'));
return NULL;
}
nameV = json_req_payload_get("name");
if(!nameV){
json_set_err( FSL_JSON_E_MISSING_ARGS,
"'name' parameter is missing.");
return NULL;
}
zPageName = cson_string_cstr(cson_value_get_string(nameV));
rid = db_int(0,
"SELECT x.rid FROM tag t, tagxref x"
" WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
" ORDER BY x.mtime DESC LIMIT 1",
zPageName
);
if(rid){
if(createMode){
json_set_err(FSL_JSON_E_RESOURCE_ALREADY_EXISTS,
"Wiki page '%s' already exists.",
zPageName);
goto error;
}
}else if(!allowCreateIfExists){
json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND,
"Wiki page '%s' not found.",
zPageName);
goto error;
}
contentV = json_req_payload_get("content");
if( !contentV ){
if( createMode || (!rid && allowCreateIfExists) ){
contentV = emptyContent = cson_value_new_string("",0);
}else{
json_set_err(FSL_JSON_E_MISSING_ARGS,
"'content' parameter is missing.");
goto error;
}
}
if( !cson_value_is_string(nameV)
|| !cson_value_is_string(contentV)){
json_set_err(FSL_JSON_E_INVALID_ARGS,
"'name' and 'content' parameters must be strings.");
goto error;
}
jstr = cson_value_get_string(contentV);
contentLen = (int)cson_string_length_bytes(jstr);
if(contentLen){
blob_append(&content, cson_string_cstr(jstr),contentLen);
}
wiki_cmd_commit(zPageName, 0==rid, &content);
blob_reset(&content);
payV = cson_value_new_object();
pay = cson_value_get_object(payV);
cson_object_set( pay, "name", nameV );
cson_object_set( pay, FossilJsonKeys.timestamp,
json_new_timestamp(-1) );
goto ok;
error:
assert( 0 != g.json.resultCode );
cson_value_free(payV);
payV = NULL;
ok:
if( emptyContent ){
/* We have some potentially tricky memory ownership
here, which is why we handle emptyContent separately.
This is, in fact, overkill because cson_value_new_string("",0)
actually returns a shared singleton instance (i.e. doesn't
allocate), but that is a cson implementation detail which i
don't want leaking into this code...
*/
cson_value_free(emptyContent);
}
return payV;
}
/*
** Implementation of /json/wiki/create.
*/
static cson_value * json_wiki_create(){
return json_wiki_create_or_save(1,0);
}
/*
** Implementation of /json/wiki/save.
*/
static cson_value * json_wiki_save(){
char const createIfNotExists = json_getenv_bool("createIfNotExists",0);
return json_wiki_create_or_save(0,createIfNotExists);
}
/*
** Implementation of /json/wiki/list.
*/
static cson_value * json_wiki_list(){
cson_value * listV = NULL;
cson_array * list = NULL;
Stmt q;
if( !g.perm.RdWiki && !g.perm.Read ){
json_set_err(FSL_JSON_E_DENIED,
"Requires 'j' or 'o' permissions.");
return NULL;
}
db_prepare(&q,"SELECT"
" substr(tagname,6) as name"
" FROM tag WHERE tagname GLOB 'wiki-*'"
" ORDER BY lower(name)");
listV = cson_value_new_array();
list = cson_value_get_array(listV);
while( SQLITE_ROW == db_step(&q) ){
cson_value * v = cson_sqlite3_column_to_value(q.pStmt,0);
if(!v){
json_set_err(FSL_JSON_E_UNKNOWN,
"Could not convert wiki name column to JSON.");
goto error;
}else if( 0 != cson_array_append( list, v ) ){
cson_value_free(v);
json_set_err(FSL_JSON_E_ALLOC,"Could not append wiki page name to array.")
/* OOM (or maybe numeric overflow) are the only realistic
error codes for that particular failure.*/;
goto error;
}
}
goto end;
error:
assert(0 != g.json.resultCode);
cson_value_free(listV);
listV = NULL;
end:
db_finalize(&q);
return listV;
}
#endif /* FOSSIL_ENABLE_JSON */
|
Changes to src/login.c.
| ︙ | ︙ | |||
82 83 84 85 86 87 88 | /* ** Return the name of the login cookie. ** ** 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. */ | | | | 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
/*
** Return the name of the login cookie.
**
** 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 *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 /*sort*/"
);
}
return zCookieName;
}
/*
** Redirect to the page specified by the "g" query parameter.
|
| ︙ | ︙ | |||
115 116 117 118 119 120 121 |
/*
** The IP address of the client is stored as part of login cookies.
** But some clients are behind firewalls that shift the IP address
** with each HTTP request. To allow such (broken) clients to log in,
** extract just a prefix of the IP address.
*/
static char *ipPrefix(const char *zIP){
| | > > > > > | > > > | | > < | > | < < | 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
/*
** The IP address of the client is stored as part of login cookies.
** But some clients are behind firewalls that shift the IP address
** with each HTTP request. To allow such (broken) clients to log in,
** extract just a prefix of the IP address.
*/
static char *ipPrefix(const char *zIP){
int i, j;
static int ip_prefix_terms = -1;
if( ip_prefix_terms<0 ){
ip_prefix_terms = db_get_int("ip-prefix-terms",2);
}
if( ip_prefix_terms==0 ) return mprintf("0");
for(i=j=0; zIP[i]; i++){
if( zIP[i]=='.' ){
j++;
if( j==ip_prefix_terms ) break;
}
}
return mprintf("%.*s", i, zIP);
}
/*
** Return an abbreviated project code. The abbreviation is the first
** 16 characters of the project code.
**
** Memory is obtained from malloc.
*/
static char *abbreviated_project_code(const char *zFullCode){
return mprintf("%.16s", zFullCode);
}
/*
** 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(
const char *zUsername, /* The username. Must be "anonymous" */
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;
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;
}
|
| ︙ | ︙ | |||
190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
create_accesslog_table();
db_multi_exec(
"INSERT INTO accesslog(uname,ipaddr,success,mtime)"
"VALUES(%Q,%Q,%d,julianday('now'));",
zUsername, zIpAddr, bSuccess
);
}
/*
** WEBPAGE: login
** WEBPAGE: logout
** WEBPAGE: my
**
** Generate the login page.
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 |
create_accesslog_table();
db_multi_exec(
"INSERT INTO accesslog(uname,ipaddr,success,mtime)"
"VALUES(%Q,%Q,%d,julianday('now'));",
zUsername, zIpAddr, bSuccess
);
}
/*
** Searches for the user ID matching the given name and password.
** On success it returns a positive value. On error it returns 0.
** On serious (DB-level) error it will probably exit.
**
** zPassword may be either the plain-text form or the encrypted
** form of the user's password.
*/
int login_search_uid(char const *zUsername, char const *zPasswd){
char * zSha1Pw = sha1_shared_secret(zPasswd, zUsername, 0);
int const uid =
db_int(0,
"SELECT uid FROM user"
" WHERE login=%Q"
" AND length(cap)>0 AND length(pw)>0"
" AND login NOT IN ('anonymous','nobody','developer','reader')"
" AND (pw=%Q OR pw=%Q)",
zUsername, zPasswd, zSha1Pw
);
free(zSha1Pw);
return uid;
}
/*
** Generates a login cookie value for a non-anonymous user.
**
** The zHash parameter must be a random value which must be
** subsequently stored in user.cookie for later validation.
**
** The returned memory should be free()d after use.
*/
char * login_gen_user_cookie_value(char const *zUsername, char const * zHash){
char * zProjCode = db_get("project-code",NULL);
char *zCode = abbreviated_project_code(zProjCode);
free(zProjCode);
assert((zUsername && *zUsername) && "Invalid user data.");
return mprintf("%s/%z/%s", zHash, zCode, zUsername);
}
/*
** Generates a login cookie for NON-ANONYMOUS users. Note that this
** function "could" figure out the uid by itself but it currently
** doesn't because the code which calls this already has the uid.
**
** This function also updates the user.cookie, user.ipaddr,
** and user.cexpire fields for the given user.
**
** If zDest is not NULL then the generated cookie is copied to
** *zDdest and ownership is transfered to the caller (who should
** eventually pass it to free()).
*/
void login_set_user_cookie(
char const * zUsername, /* User's name */
int uid, /* User's ID */
char ** zDest /* Optional: store generated cookie value. */
){
const char *zCookieName = login_cookie_name();
const char *zExpire = db_get("cookie-expire","8766");
int expires = atoi(zExpire)*3600;
char *zHash;
char *zCookie;
char const * zIpAddr = PD("REMOTE_ADDR","nil"); /* Complete IP address for logging */
char * zRemoteAddr = ipPrefix(zIpAddr); /* Abbreviated IP address */
assert((zUsername && *zUsername) && (uid > 0) && "Invalid user data.");
zHash = db_text(0, "SELECT hex(randomblob(25))");
zCookie = login_gen_user_cookie_value(zUsername, zHash);
cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires);
record_login_attempt(zUsername, zIpAddr, 1);
db_multi_exec(
"UPDATE user SET cookie=%Q, ipaddr=%Q, "
" cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
zHash, zRemoteAddr, expires, uid
);
free(zRemoteAddr);
free(zHash);
if( zDest ){
*zDest = zCookie;
}else{
free(zCookie);
}
}
/* Sets a cookie for an anonymous user login, which looks like this:
**
** HASH/TIME/anonymous
**
** Where HASH is the sha1sum of TIME/IPADDR/SECRET, in which IPADDR
** is the abbreviated IP address and SECRET is captcha-secret.
**
** If either zIpAddr or zRemoteAddr are NULL then REMOTE_ADDR
** is used.
**
** If zCookieDest is not NULL then the generated cookie is assigned to
** *zCookieDest and the caller must eventually free() it.
*/
void login_set_anon_cookie(char const * zIpAddr, char ** zCookieDest ){
char const *zNow; /* Current time (julian day number) */
char *zCookie; /* The login cookie */
char const *zCookieName; /* Name of the login cookie */
Blob b; /* Blob used during cookie construction */
char * zRemoteAddr; /* Abbreviated IP address */
if(!zIpAddr){
zIpAddr = PD("REMOTE_ADDR","nil");
}
zRemoteAddr = ipPrefix(zIpAddr);
zCookieName = login_cookie_name();
zNow = db_text("0", "SELECT julianday('now')");
assert( zCookieName && zRemoteAddr && zIpAddr && zNow );
blob_init(&b, zNow, -1);
blob_appendf(&b, "/%s/%s", zRemoteAddr, db_get("captcha-secret",""));
sha1sum_blob(&b, &b);
zCookie = mprintf("%s/%s/anonymous", blob_buffer(&b), zNow);
blob_reset(&b);
cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), 6*3600);
if( zCookieDest ){
*zCookieDest = zCookie;
}else{
free(zCookie);
}
}
/*
** "Unsets" the login cookie (insofar as cookies can be unset) and
** clears the current user's (g.userUid) login information from the
** user table. Sets: user.cookie, user.ipaddr, user.cexpire.
**
** We could/should arguably clear out g.userUid and g.perm here, but
** we don't currently do not.
**
** This is a no-op if g.userUid is 0.
*/
void login_clear_login_data(){
if(!g.userUid){
return;
}else{
char const * cookie = login_cookie_name();
/* To logout, change the cookie value to an empty string */
cgi_set_cookie(cookie, "",
login_cookie_path(), -86400);
db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, "
" cexpire=0 WHERE uid=%d"
" AND login NOT IN ('anonymous','nobody',"
" '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.
*/
;
}
}
/*
** Return true if the prefix of zStr matches zPattern. Return false if
** they are different.
**
** A lowercase character in zPattern will match either upper or lower
** case in zStr. But an uppercase in zPattern will only match an
** uppercase in zStr.
*/
static int prefix_match(const char *zPattern, const char *zStr){
int i;
char c;
for(i=0; (c = zPattern[i])!=0; i++){
if( zStr[i]!=c && fossil_tolower(zStr[i])!=c ) return 0;
}
return 1;
}
/*
** Look at the HTTP_USER_AGENT parameter and try to determine if the user agent
** is a manually operated browser or a bot. When in doubt, assume a bot.
** Return true if we believe the agent is a real person.
*/
static int isHuman(const char *zAgent){
int i;
if( zAgent==0 ) return 0; /* If not UserAgent, the probably a bot */
for(i=0; zAgent[i]; i++){
if( prefix_match("bot", zAgent+i) ) return 0;
if( prefix_match("spider", zAgent+i) ) return 0;
if( prefix_match("crawl", zAgent+i) ) return 0;
/* If a URI appears in the User-Agent, it is probably a bot */
if( memcmp("http", zAgent+i,4)==0 ) return 0;
}
if( memcmp(zAgent, "Mozilla/", 8)==0 ){
if( atoi(&zAgent[8])<4 ) return 0; /* Many bots advertise as Mozilla/3 */
if( strglob("*Firefox/[1-9]*", zAgent) ) return 1;
if( strglob("*Chrome/[1-9]*", zAgent) ) return 1;
if( strglob("*(compatible;?MSIE?[1-9]*", zAgent) ) return 1;
if( strglob("*AppleWebKit/[1-9]*(KHTML*", zAgent) ) return 1;
return 0;
}
if( memcmp(zAgent, "Opera/", 6)==0 ) return 1;
if( memcmp(zAgent, "Safari/", 7)==0 ) return 1;
if( memcmp(zAgent, "Lynx/", 5)==0 ) return 1;
return 0;
}
/*
** COMMAND: test-ishuman
**
** Read lines of text from standard input. Interpret each line of text
** as a User-Agent string from an HTTP header. Label each line as HUMAN
** or ROBOT.
*/
void test_ishuman(void){
char zLine[3000];
while( fgets(zLine, sizeof(zLine), stdin) ){
fossil_print("%s %s", isHuman(zLine) ? "HUMAN" : "ROBOT", zLine);
}
}
/*
** 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);
}
/*
** WEBPAGE: login
** WEBPAGE: logout
** WEBPAGE: my
**
** Generate the login page.
|
| ︙ | ︙ | |||
212 213 214 215 216 217 218 | const char *zNew1, *zNew2; const char *zAnonPw = 0; int anonFlag; char *zErrMsg = ""; int uid; /* User id loged in user */ char *zSha1Pw; const char *zIpAddr; /* IP address of requestor */ | < > > < | < | > > | > > | | 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 |
const char *zNew1, *zNew2;
const char *zAnonPw = 0;
int anonFlag;
char *zErrMsg = "";
int uid; /* User id loged in user */
char *zSha1Pw;
const char *zIpAddr; /* IP address of requestor */
login_check_credentials();
sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0,
constant_time_cmp_function, 0, 0);
zUsername = P("u");
zPasswd = P("p");
anonFlag = P("anon")!=0;
if( P("out")!=0 ){
login_clear_login_data();
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 (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.
@ </span></p>
;
|
| ︙ | ︙ | |||
269 270 271 272 273 274 275 |
}else{
redirect_to_g();
return;
}
}
}
zIpAddr = PD("REMOTE_ADDR","nil"); /* Complete IP address for logging */
| < | < < < < < < < < < < < < | < < < < < < < < < < < < < < < < | < < < < < < < | < < < < < < < < < | 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 |
}else{
redirect_to_g();
return;
}
}
}
zIpAddr = PD("REMOTE_ADDR","nil"); /* Complete IP address for logging */
uid = login_is_valid_anonymous(zUsername, zPasswd, P("cs"));
if( uid>0 ){
login_set_anon_cookie(zIpAddr, NULL);
record_login_attempt("anonymous", zIpAddr, 1);
redirect_to_g();
}
if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){
/* Attempting to log in as a user other than anonymous.
*/
uid = login_search_uid(zUsername, zPasswd);
if( uid<=0 ){
sleep(1);
zErrMsg =
@ <p><span class="loginError">
@ You entered an unknown user or an incorrect password.
@ </span></p>
;
record_login_attempt(zUsername, zIpAddr, 0);
}else{
/* Non-anonymous login is successful. Set a cookie of the form:
**
** 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);
redirect_to_g();
}
}
style_header("Login/Logout");
@ %s(zErrMsg)
@ <form action="login" method="post">
if( P("g") ){
|
| ︙ | ︙ | |||
479 480 481 482 483 484 485 486 487 488 |
zCode
);
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_busy_timeout(pOther, 5000);
zSQL = mprintf(
"SELECT cexpire FROM user"
| > > | < | > | | | > > > > < | > | | | | | < < > > > | 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 |
zCode
);
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 login=%Q"
" AND ipaddr=%Q"
" AND length(cap)>0"
" AND length(pw)>0"
" 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(
"UPDATE user SET cookie=%Q, ipaddr=%Q, cexpire=%.17g"
" WHERE login=%Q",
zHash, zRemoteAddr,
sqlite3_column_double(pStmt, 0), zLogin
);
nXfer++;
}
sqlite3_finalize(pStmt);
}
sqlite3_close(pOther);
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.
*/
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 */
){
int uid;
if( fossil_strcmp(zLogin, "anonymous")==0 ) return 0;
if( fossil_strcmp(zLogin, "nobody")==0 ) return 0;
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 ipaddr=%Q"
" AND cexpire>julianday('now')"
" AND length(cap)>0"
" 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.
*/
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 */
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.
**
** This feature allows the "fossil ui" command to give the user
** full access rights without having to log in.
|
| ︙ | ︙ | |||
630 631 632 633 634 635 636 |
if( uid ) record_login_attempt(zUser, zIpAddr, 1);
}
}
sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "%.10s", zHash);
}
/* If no user found and the REMOTE_USER environment variable is set,
| | | 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 |
if( uid ) record_login_attempt(zUser, zIpAddr, 1);
}
}
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.
*/
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"
" AND length(cap)>0 AND length(pw)>0", zRemoteUser);
}
|
| ︙ | ︙ | |||
680 681 682 683 684 685 686 |
*/
g.userUid = uid;
if( fossil_strcmp(g.zLogin,"nobody")==0 ){
g.zLogin = 0;
}
/* Set the capabilities */
| | > > > > | 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 |
*/
g.userUid = uid;
if( fossil_strcmp(g.zLogin,"nobody")==0 ){
g.zLogin = 0;
}
/* Set the capabilities */
login_replace_capabilities(zCap, 0);
login_set_anon_nobody_capabilities();
if( zCap[0] && !g.perm.History && db_get_boolean("auto-enable-hyperlinks",1)
&& isHuman(P("HTTP_USER_AGENT")) ){
g.perm.History = 1;
}
}
/*
** Memory of settings
*/
static int login_anon_once = 1;
|
| ︙ | ︙ | |||
709 710 711 712 713 714 715 |
login_set_capabilities(zCap, 0);
}
login_anon_once = 0;
}
}
/*
| | | > > > | 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 |
login_set_capabilities(zCap, 0);
}
login_anon_once = 0;
}
}
/*
** Flags passed into the 2nd argument of login_set/replace_capabilities().
*/
#if INTERFACE
#define LOGIN_IGNORE_U 0x01 /* Ignore "u" */
#define LOGIN_IGNORE_V 0x01 /* Ignore "v" */
#endif
/*
** Adds all capability flags in zCap to g.perm.
*/
void login_set_capabilities(const char *zCap, unsigned flags){
int i;
if(NULL==zCap){
return;
}
for(i=0; zCap[i]; i++){
switch( zCap[i] ){
case 's': g.perm.Setup = 1; /* Fall thru into Admin */
case 'a': g.perm.Admin = g.perm.RdTkt = g.perm.WrTkt = g.perm.Zip =
g.perm.RdWiki = g.perm.WrWiki = g.perm.NewWiki =
g.perm.ApndWiki = g.perm.History = g.perm.Clone =
g.perm.NewTkt = g.perm.Password = g.perm.RdAddr =
|
| ︙ | ︙ | |||
778 779 780 781 782 783 784 785 786 787 788 789 790 791 |
login_set_capabilities(zDev, flags | LOGIN_IGNORE_V);
}
break;
}
}
}
}
/*
** If the current login lacks any of the capabilities listed in
** the input, then return 0. If all capabilities are present, then
** return 1.
*/
int login_has_capability(const char *zCap, int nCap){
| > > > > > > > > | 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 |
login_set_capabilities(zDev, flags | LOGIN_IGNORE_V);
}
break;
}
}
}
}
/*
** Zeroes out g.perm and calls login_set_capabilities(zCap,flags).
*/
void login_replace_capabilities(const char *zCap, unsigned flags){
memset(&g.perm, 0, sizeof(g.perm));
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.
*/
int login_has_capability(const char *zCap, int nCap){
|
| ︙ | ︙ | |||
856 857 858 859 860 861 862 |
}
/*
** Call this routine when the credential check fails. It causes
** a redirect to the "login" page.
*/
void login_needed(void){
| > > > > > > > > > | | | | > | 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 |
}
/*
** Call this routine when the credential check fails. It causes
** a redirect to the "login" page.
*/
void login_needed(void){
#ifdef FOSSIL_ENABLE_JSON
if(g.json.isJsonMode){
json_err( FSL_JSON_E_DENIED, NULL, 1 );
fossil_exit(0);
/* NOTREACHED */
assert(0);
}else
#endif /* FOSSIL_ENABLE_JSON */
{
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
** to inform the user that much more information is available by
** logging in as anonymous.
|
| ︙ | ︙ |
Changes to src/main.c.
| ︙ | ︙ | |||
21 22 23 24 25 26 27 | #include "config.h" #include "main.h" #include <string.h> #include <time.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> | | > > > > > > > | 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | #include "config.h" #include "main.h" #include <string.h> #include <time.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <stdlib.h> /* atexit() */ #if INTERFACE #ifdef FOSSIL_ENABLE_JSON # include "cson_amalgamation.h" /* JSON API. Needed inside the INTERFACE block! */ # include "json_detail.h" #endif #ifdef FOSSIL_ENABLE_TCL #include "tcl.h" #endif /* ** Number of elements in an array */ #define count(X) (sizeof(X)/sizeof(X[0])) /* |
| ︙ | ︙ | |||
67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
char WrTkt; /* w: make changes to tickets via web */
char Attach; /* b: add attachments */
char TktFmt; /* t: create new ticket report formats */
char RdAddr; /* e: read email addresses or other private data */
char Zip; /* z: download zipped artifact via /zip URL */
char Private; /* x: can send and receive private content */
};
/*
** All global variables are in this structure.
*/
struct Global {
int argc; char **argv; /* Command-line arguments to the program */
int isConst; /* True if the output is unchanging */
| > > > > > > > > > > > > > | 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
char WrTkt; /* w: make changes to tickets via web */
char Attach; /* b: add attachments */
char TktFmt; /* t: create new ticket report formats */
char RdAddr; /* e: read email addresses or other private data */
char Zip; /* z: download zipped artifact via /zip URL */
char Private; /* x: can send and receive private content */
};
#ifdef FOSSIL_ENABLE_TCL
/*
** All Tcl related context information is in this structure. This structure
** definition has been copied from and should be kept in sync with the one in
** "th_tcl.c".
*/
struct TclContext {
int argc;
char **argv;
Tcl_Interp *interp;
};
#endif
/*
** All global variables are in this structure.
*/
struct Global {
int argc; char **argv; /* Command-line arguments to the program */
int isConst; /* True if the output is unchanging */
|
| ︙ | ︙ | |||
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | FILE *httpIn; /* Accept HTTP input from here */ FILE *httpOut; /* Send HTTP output here */ 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 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: */ char *urlHostname; /* The HOST: parameter on http headers */ char *urlProtocol; /* "http" or "https" */ int urlPort; /* TCP port number for http: or https: */ int urlDfltPort; /* The default port for the given protocol */ char *urlPath; /* Pathname for http: */ char *urlUser; /* User id for http: */ 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 */ | > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 |
FILE *httpIn; /* Accept HTTP input from here */
FILE *httpOut; /* Send HTTP output here */
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: */
char *urlHostname; /* The HOST: parameter on http headers */
char *urlProtocol; /* "http" or "https" */
int urlPort; /* TCP port number for http: or https: */
int urlDfltPort; /* The default port for the given protocol */
char *urlPath; /* Pathname for http: */
char *urlUser; /* User id for http: */
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 */
/* Information used to populate the RCVFROM table */
int rcvid; /* The rcvid. 0 if not yet defined. */
char *zIpAddr; /* The remote IP address */
char *zNonce; /* The nonce used for login */
/* permissions used by the server */
struct FossilUserPerms perm;
#ifdef FOSSIL_ENABLE_TCL
/* all Tcl related context necessary for integration */
struct TclContext tcl;
#endif
/* For defense against Cross-site Request Forgery attacks */
char zCsrfToken[12]; /* Value of the anti-CSRF token */
int okCsrf; /* Anti-CSRF token is present and valid */
int parseCnt[10]; /* Counts of artifacts parsed */
FILE *fDebug; /* Write debug information here, if the file exists */
int thTrace; /* True to enable TH1 debugging output */
Blob thLog; /* Text of the TH1 debugging output */
int isHome; /* True if rendering the "home" page */
/* Storage for the aux() and/or option() SQL function arguments */
int nAux; /* Number of distinct aux() or option() values */
const char *azAuxName[MX_AUX]; /* Name of each aux() or option() value */
char *azAuxParam[MX_AUX]; /* Param of each aux() or option() value */
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 */
#ifdef FOSSIL_ENABLE_JSON
struct FossilJsonBits {
int isJsonMode; /* True if running in JSON mode, else
false. This changes how errors are
reported. In JSON mode we try to
always output JSON-form error
responses and always exit() with
code 0 to avoid an HTTP 500 error.
*/
int resultCode; /* used for passing back specific codes from /json callbacks. */
int errorDetailParanoia; /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */
cson_output_opt outOpt; /* formatting options for JSON mode. */
cson_value * authToken; /* authentication token */
char const * jsonp; /* Name of JSONP function wrapper. */
unsigned char dispatchDepth /* Tells JSON command dispatching
which argument we are currently
working on. For this purpose, arg#0
is the "json" path/CLI arg.
*/;
struct { /* "garbage collector" */
cson_value * v;
cson_array * a;
} gc;
struct { /* JSON POST data. */
cson_value * v;
cson_array * a;
int offset; /* Tells us which PATH_INFO/CLI args
part holds the "json" command, so
that we can account for sub-repos
and path prefixes. This is handled
differently for CLI and CGI modes.
*/
char const * commandStr /*"command" request param.*/;
} cmd;
struct { /* JSON POST data. */
cson_value * v;
cson_object * o;
} post;
struct { /* GET/COOKIE params in JSON mode.
FIXME (stephan): verify that this is
still used and remove if it is not.
*/
cson_value * v;
cson_object * o;
} param;
struct {
cson_value * v;
cson_object * o;
} reqPayload; /* request payload object (if any) */
struct { /* response warnings */
cson_value * v;
cson_array * a;
} warnings;
} json;
#endif /* FOSSIL_ENABLE_JSON */
};
/*
** Macro for debugging:
*/
#define CGIDEBUG(X) if( g.fDebug ) cgi_debug X
|
| ︙ | ︙ | |||
214 215 216 217 218 219 220 |
return 0;
}else if( c<0 ){
upr = mid - 1;
}else{
lwr = mid + 1;
}
}
| | > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | > | > > | 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 |
return 0;
}else if( c<0 ){
upr = mid - 1;
}else{
lwr = mid + 1;
}
}
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++;
}
}
if( cnt==1 ){
*pIndex = m;
return 0;
}
return 1+(cnt>1);
}
/*
** atexit() handler which frees up "some" of the resources
** used by fossil.
*/
void fossil_atexit(void) {
#ifdef FOSSIL_ENABLE_JSON
cson_value_free(g.json.gc.v);
memset(&g.json, 0, sizeof(g.json));
#endif
free(g.zErrMsg);
if(g.db){
db_close(0);
}
}
/*
** Search g.argv for arguments "--args FILENAME". If found, then
** (1) remove the two arguments from g.argv
** (2) Read the file FILENAME
** (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.
*/
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;
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.
*/
int main(int argc, char **argv){
const char *zCmdName = "unknown";
int idx;
int rc;
int i;
#ifdef FOSSIL_ENABLE_TCL
g.tcl.argc = argc;
g.tcl.argv = argv;
g.tcl.interp = 0;
#endif
sqlite3_config(SQLITE_CONFIG_LOG, fossil_sqlite_log, 0);
memset(&g, 0, sizeof(g));
g.now = time(0);
g.argc = argc;
g.argv = argv;
#ifdef FOSSIL_ENABLE_JSON
#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 */;
#endif /* FOSSIL_ENABLE_JSON */
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_print(
"Usage: %s COMMAND ...\n"
" or: %s help -- for a list of common commands\n"
" or: %s help COMMMAND -- for help with the named command\n"
" or: %s commands -- for a list of all commands\n",
argv[0], argv[0], argv[0], argv[0]);
fossil_exit(1);
}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;
g.fSqlPrint = find_option("sqlprint", 0, 0)!=0;
g.fHttpTrace = find_option("httptrace", 0, 0)!=0;
|
| ︙ | ︙ | |||
289 290 291 292 293 294 295 |
blob_zero(&couldbe);
n = strlen(zCmdName);
for(i=0; i<count(aCommand); i++){
if( memcmp(zCmdName, aCommand[i].zName, n)==0 ){
blob_appendf(&couldbe, " %s", aCommand[i].zName);
}
}
| | > > | 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 |
blob_zero(&couldbe);
n = strlen(zCmdName);
for(i=0; i<count(aCommand); i++){
if( memcmp(zCmdName, aCommand[i].zName, n)==0 ){
blob_appendf(&couldbe, " %s", aCommand[i].zName);
}
}
fossil_print("%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]);
fossil_exit(1);
}
atexit( fossil_atexit );
aCommand[idx].xFunc();
fossil_exit(0);
/*NOT_REACHED*/
return 0;
}
/*
|
| ︙ | ︙ | |||
321 322 323 324 325 326 327 | return g.argv[0]; #endif } /* ** Exit. Take care to close the database first. */ | | | > > > > > > > > > > > | | | | | | | | > > | > | > > > > > > > > > > > | | | | | | | | > > | > > > > > > > > > > | | | | | | | > | > | > > > > > > | | | | | | | > > | 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 |
return g.argv[0];
#endif
}
/*
** Exit. Take care to close the database first.
*/
NORETURN void fossil_exit(int rc){
db_close(1);
exit(rc);
}
/*
** Print an error message, rollback all databases, and quit. These
** routines never return.
*/
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);
#ifdef FOSSIL_ENABLE_JSON
if( g.json.isJsonMode ){
json_err( 0, z, 1 );
if( g.isHTTP ){
rc = 0 /* avoid HTTP 500 */;
}
}
else
#endif
{
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);
}
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);
#ifdef FOSSIL_ENABLE_JSON
if( g.json.isJsonMode ){
json_err( g.json.resultCode, z, 1 );
if( g.isHTTP ){
rc = 0 /* avoid HTTP 500 */;
}
}
else
#endif
{
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(z);
db_force_rollback();
fossil_exit(rc);
}
/* This routine works like fossil_fatal() except that if called
** recursively, the recursive call is a no-op.
**
** Use this in places where an error might occur while doing
** fatal error shutdown processing. Unlike fossil_panic() and
** fossil_fatal() which never return, this routine might return if
** the fatal error handing is already in process. The caller must
** 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);
#ifdef FOSSIL_ENABLE_JSON
if( g.json.isJsonMode ){
json_err( g.json.resultCode, z, 1 );
if( g.isHTTP ){
rc = 0 /* avoid HTTP 500 */;
}
} else
#endif
{
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);
}
/* Print a warning message */
void fossil_warning(const char *zFormat, ...){
char *z;
va_list ap;
va_start(ap, zFormat);
z = vmprintf(zFormat, ap);
va_end(ap);
#ifdef FOSSIL_ENABLE_JSON
if(g.json.isJsonMode){
json_warn( FSL_JSON_W_UNKNOWN, z );
}else
#endif
{
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
*/
void *fossil_malloc(size_t n){
void *p = malloc(n==0 ? 1 : n);
|
| ︙ | ︙ | |||
614 615 616 617 618 619 620 |
fossil_print("\n");
}
}
/*
** List of commands starting with zPrefix, or all commands if zPrefix is NULL.
*/
| | | | < < | | | | < | < > | > > > > > > < | > > > > | > > > > > > > > > > > > | | | 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 |
fossil_print("\n");
}
}
/*
** List of commands starting with zPrefix, or all commands if zPrefix is NULL.
*/
static void command_list(const char *zPrefix, int cmdMask){
int i, nCmd;
int nPrefix = zPrefix ? strlen(zPrefix) : 0;
const char *aCmd[count(aCommand)];
for(i=nCmd=0; i<count(aCommand); i++){
const char *z = aCommand[i].zName;
if( (aCommand[i].cmdFlags & cmdMask)==0 ) continue;
if( zPrefix && memcmp(zPrefix, z, nPrefix)!=0 ) continue;
aCmd[nCmd++] = aCommand[i].zName;
}
multi_column_list(aCmd, nCmd);
}
/*
** COMMAND: test-list-webpage
**
** List all web pages
*/
void cmd_test_webpage_list(void){
int i, nCmd;
const char *aCmd[count(aWebpage)];
for(i=nCmd=0; i<count(aWebpage); i++){
aCmd[nCmd++] = aWebpage[i].zName;
}
multi_column_list(aCmd, nCmd);
}
/*
** COMMAND: version
**
** Usage: %fossil version
**
** Print the source code version number for the fossil executable.
*/
void version_cmd(void){
fossil_print("This is fossil version " RELEASE_VERSION " "
MANIFEST_VERSION " " MANIFEST_DATE " UTC\n");
}
/*
** COMMAND: help
**
** Usage: %fossil help COMMAND
** or: %fossil COMMAND -help
**
** Display information on how to use COMMAND. To display a list of
** available commands one of:
**
** %fossil help Show common commands
** %fossil help --all Show both command and auxiliary commands
** %fossil help --test Show test commands only
** %fossil help --aux Show auxiliary commands only
*/
void help_cmd(void){
int rc, idx;
const char *z;
if( g.argc<3 ){
z = fossil_nameofexe();
fossil_print(
"Usage: %s help COMMAND\n"
"Common COMMANDs: (use \"%s help --all\" for a complete list)\n",
z, z);
command_list(0, CMDFLAG_1ST_TIER);
version_cmd();
return;
}
if( find_option("all",0,0) ){
command_list(0, CMDFLAG_1ST_TIER | CMDFLAG_2ND_TIER);
return;
}
if( find_option("aux",0,0) ){
command_list(0, CMDFLAG_2ND_TIER);
return;
}
if( find_option("test",0,0) ){
command_list(0, CMDFLAG_TEST);
return;
}
rc = name_search(g.argv[2], aCommand, count(aCommand), &idx);
if( rc==1 ){
fossil_print("unknown command: %s\nAvailable commands:\n", g.argv[2]);
command_list(0, 0xff);
fossil_exit(1);
}else if( rc==2 ){
fossil_print("ambiguous command prefix: %s\nMatching commands:\n",
g.argv[2]);
command_list(g.argv[2], 0xff);
fossil_exit(1);
}
z = aCmdHelp[idx];
if( z==0 ){
fossil_fatal("no help available for the %s command",
aCommand[idx].zName);
}
|
| ︙ | ︙ | |||
822 823 824 825 826 827 828 |
g.zTop = &g.zBaseURL[7+strlen(zHost)];
}
}
/*
** Send an HTTP redirect back to the designated Index Page.
*/
| | | 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 |
g.zTop = &g.zBaseURL[7+strlen(zHost)];
}
}
/*
** Send an HTTP redirect back to the designated Index Page.
*/
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
** repository zRepo and then drop root privileges. Return the
** new repository name.
|
| ︙ | ︙ | |||
936 937 938 939 940 941 942 943 944 945 946 947 948 949 |
zRepo[j] = '.';
}
if( szFile<1024 ){
if( zNotFound ){
cgi_redirect(zNotFound);
}else{
@ <h1>Not Found</h1>
cgi_set_status(404, "not found");
cgi_reply();
}
return;
}
break;
| > > > > > > | 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 |
zRepo[j] = '.';
}
if( szFile<1024 ){
if( zNotFound ){
cgi_redirect(zNotFound);
}else{
#ifdef FOSSIL_ENABLE_JSON
if(g.json.isJsonMode){
json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
return;
}
#endif
@ <h1>Not Found</h1>
cgi_set_status(404, "not found");
cgi_reply();
}
return;
}
break;
|
| ︙ | ︙ | |||
967 968 969 970 971 972 973 |
*/
if( g.zContentType && memcmp(g.zContentType, "application/x-fossil", 20)==0 ){
zPathInfo = "/xfer";
}
set_base_url();
if( zPathInfo==0 || zPathInfo[0]==0
|| (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
| > > > > > > | | 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 |
*/
if( g.zContentType && memcmp(g.zContentType, "application/x-fossil", 20)==0 ){
zPathInfo = "/xfer";
}
set_base_url();
if( zPathInfo==0 || zPathInfo[0]==0
|| (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
#ifdef FOSSIL_ENABLE_JSON
if(g.json.isJsonMode){
json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
fossil_exit(0);
}
#endif
fossil_redirect_home() /*does not return*/;
}else{
zPath = mprintf("%s", zPathInfo);
}
/* Make g.zPath point to the first element of the path. Make
** g.zExtra point to everything past that point.
*/
|
| ︙ | ︙ | |||
1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 |
g.zExtra = 0;
}
break;
}
if( g.zExtra ){
/* CGI parameters get this treatment elsewhere, but places like getfile
** will use g.zExtra directly.
*/
dehttpize(g.zExtra);
cgi_set_parameter_nocopy("name", g.zExtra);
}
/* 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) ){
| > > > > > > > > | | | > > > > > > > | | | > | | 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 |
g.zExtra = 0;
}
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);
}
/* 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) ){
#ifdef FOSSIL_ENABLE_JSON
if(g.json.isJsonMode){
json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,0);
}else
#endif
{
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() ){
#ifdef FOSSIL_ENABLE_JSON
if(g.json.isJsonMode){
json_err(FSL_JSON_E_DB_NEEDS_REBUILD,NULL,0);
}else
#endif
{
@ <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.
*/
cgi_reply();
}
/*
** COMMAND: cgi*
**
** Usage: %fossil ?cgi? SCRIPT
**
** The SCRIPT argument is the name of a file that is the CGI script
** that is being run. The command name, "cgi", may be omitted if
** the GATEWAY_INTERFACE environment variable is set to "CGI" (which
** should always be the case for CGI scripts run by a webserver.) The
|
| ︙ | ︙ | |||
1244 1245 1246 1247 1248 1249 1250 | /* ** undocumented format: ** ** fossil http REPOSITORY INFILE OUTFILE IPADDR ** ** The argv==6 form is used by the win32 server only. ** | | | 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 | /* ** undocumented format: ** ** fossil http REPOSITORY INFILE OUTFILE IPADDR ** ** The argv==6 form is used by the win32 server only. ** ** COMMAND: http* ** ** Usage: %fossil http REPOSITORY ?OPTIONS? ** ** Handle a single HTTP request appearing on stdin. The resulting webpage ** is delivered on stdout. This method is used to launch an HTTP request ** handler from inetd, for example. The argument is the name of the ** repository. |
| ︙ | ︙ | |||
1314 1315 1316 1317 1318 1319 1320 |
/*
** Note that the following command is used by ssh:// processing.
**
** COMMAND: test-http
** Works like the http command but gives setup permission to all users.
*/
void cmd_test_http(void){
| | > > | 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 |
/*
** Note that the following command is used by ssh:// processing.
**
** COMMAND: test-http
** Works like the http command but gives setup permission to all users.
*/
void cmd_test_http(void){
login_set_capabilities("sx", 0);
g.useLocalauth = 1;
cgi_set_parameter("REMOTE_ADDR", "127.0.0.1");
g.httpIn = stdin;
g.httpOut = stdout;
find_server_repository(0);
g.cgiOutput = 1;
g.fullHttpReply = 1;
cgi_handle_http_request(0);
process_one_web_page(0);
|
| ︙ | ︙ | |||
1350 1351 1352 1353 1354 1355 1356 | } return 0; } #endif #endif /* | | | 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 | } return 0; } #endif #endif /* ** COMMAND: server* ** COMMAND: ui ** ** Usage: %fossil server ?OPTIONS? ?REPOSITORY? ** Or: %fossil ui ?OPTIONS? ?REPOSITORY? ** ** Open a socket and begin listening and responding to HTTP requests on ** TCP port 8080, or on any other TCP port defined by the -P or |
| ︙ | ︙ |
Changes to src/main.mk.
|
| > > | > | 1 2 3 4 5 6 7 8 9 10 11 | # ############################################################################## # WARNING: DO NOT EDIT, AUTOMATICALLY GENERATED FILE (SEE "src/makemake.tcl") ############################################################################## # # This file is automatically generated. Instead of editing this # file, edit "makemake.tcl" then run "tclsh makemake.tcl" # to regenerate this file. # # This file is included by primary Makefile. # |
| ︙ | ︙ | |||
45 46 47 48 49 50 51 52 53 54 55 56 57 58 | $(SRCDIR)/gzip.c \ $(SRCDIR)/http.c \ $(SRCDIR)/http_socket.c \ $(SRCDIR)/http_ssl.c \ $(SRCDIR)/http_transport.c \ $(SRCDIR)/import.c \ $(SRCDIR)/info.c \ $(SRCDIR)/leaf.c \ $(SRCDIR)/login.c \ $(SRCDIR)/main.c \ $(SRCDIR)/manifest.c \ $(SRCDIR)/md5.c \ $(SRCDIR)/merge.c \ $(SRCDIR)/merge3.c \ | > > > > > > > > > > > | 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | $(SRCDIR)/gzip.c \ $(SRCDIR)/http.c \ $(SRCDIR)/http_socket.c \ $(SRCDIR)/http_ssl.c \ $(SRCDIR)/http_transport.c \ $(SRCDIR)/import.c \ $(SRCDIR)/info.c \ $(SRCDIR)/json.c \ $(SRCDIR)/json_artifact.c \ $(SRCDIR)/json_branch.c \ $(SRCDIR)/json_diff.c \ $(SRCDIR)/json_login.c \ $(SRCDIR)/json_query.c \ $(SRCDIR)/json_report.c \ $(SRCDIR)/json_tag.c \ $(SRCDIR)/json_timeline.c \ $(SRCDIR)/json_user.c \ $(SRCDIR)/json_wiki.c \ $(SRCDIR)/leaf.c \ $(SRCDIR)/login.c \ $(SRCDIR)/main.c \ $(SRCDIR)/manifest.c \ $(SRCDIR)/md5.c \ $(SRCDIR)/merge.c \ $(SRCDIR)/merge3.c \ |
| ︙ | ︙ | |||
88 89 90 91 92 93 94 95 96 97 98 99 100 101 | $(SRCDIR)/user.c \ $(SRCDIR)/verify.c \ $(SRCDIR)/vfile.c \ $(SRCDIR)/wiki.c \ $(SRCDIR)/wikiformat.c \ $(SRCDIR)/winhttp.c \ $(SRCDIR)/xfer.c \ $(SRCDIR)/zip.c TRANS_SRC = \ $(OBJDIR)/add_.c \ $(OBJDIR)/allrepo_.c \ $(OBJDIR)/attach_.c \ $(OBJDIR)/bag_.c \ | > | 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | $(SRCDIR)/user.c \ $(SRCDIR)/verify.c \ $(SRCDIR)/vfile.c \ $(SRCDIR)/wiki.c \ $(SRCDIR)/wikiformat.c \ $(SRCDIR)/winhttp.c \ $(SRCDIR)/xfer.c \ $(SRCDIR)/xfersetup.c \ $(SRCDIR)/zip.c TRANS_SRC = \ $(OBJDIR)/add_.c \ $(OBJDIR)/allrepo_.c \ $(OBJDIR)/attach_.c \ $(OBJDIR)/bag_.c \ |
| ︙ | ︙ | |||
129 130 131 132 133 134 135 136 137 138 139 140 141 142 | $(OBJDIR)/gzip_.c \ $(OBJDIR)/http_.c \ $(OBJDIR)/http_socket_.c \ $(OBJDIR)/http_ssl_.c \ $(OBJDIR)/http_transport_.c \ $(OBJDIR)/import_.c \ $(OBJDIR)/info_.c \ $(OBJDIR)/leaf_.c \ $(OBJDIR)/login_.c \ $(OBJDIR)/main_.c \ $(OBJDIR)/manifest_.c \ $(OBJDIR)/md5_.c \ $(OBJDIR)/merge_.c \ $(OBJDIR)/merge3_.c \ | > > > > > > > > > > > | 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | $(OBJDIR)/gzip_.c \ $(OBJDIR)/http_.c \ $(OBJDIR)/http_socket_.c \ $(OBJDIR)/http_ssl_.c \ $(OBJDIR)/http_transport_.c \ $(OBJDIR)/import_.c \ $(OBJDIR)/info_.c \ $(OBJDIR)/json_.c \ $(OBJDIR)/json_artifact_.c \ $(OBJDIR)/json_branch_.c \ $(OBJDIR)/json_diff_.c \ $(OBJDIR)/json_login_.c \ $(OBJDIR)/json_query_.c \ $(OBJDIR)/json_report_.c \ $(OBJDIR)/json_tag_.c \ $(OBJDIR)/json_timeline_.c \ $(OBJDIR)/json_user_.c \ $(OBJDIR)/json_wiki_.c \ $(OBJDIR)/leaf_.c \ $(OBJDIR)/login_.c \ $(OBJDIR)/main_.c \ $(OBJDIR)/manifest_.c \ $(OBJDIR)/md5_.c \ $(OBJDIR)/merge_.c \ $(OBJDIR)/merge3_.c \ |
| ︙ | ︙ | |||
172 173 174 175 176 177 178 179 180 181 182 183 184 185 | $(OBJDIR)/user_.c \ $(OBJDIR)/verify_.c \ $(OBJDIR)/vfile_.c \ $(OBJDIR)/wiki_.c \ $(OBJDIR)/wikiformat_.c \ $(OBJDIR)/winhttp_.c \ $(OBJDIR)/xfer_.c \ $(OBJDIR)/zip_.c OBJ = \ $(OBJDIR)/add.o \ $(OBJDIR)/allrepo.o \ $(OBJDIR)/attach.o \ $(OBJDIR)/bag.o \ | > | 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | $(OBJDIR)/user_.c \ $(OBJDIR)/verify_.c \ $(OBJDIR)/vfile_.c \ $(OBJDIR)/wiki_.c \ $(OBJDIR)/wikiformat_.c \ $(OBJDIR)/winhttp_.c \ $(OBJDIR)/xfer_.c \ $(OBJDIR)/xfersetup_.c \ $(OBJDIR)/zip_.c OBJ = \ $(OBJDIR)/add.o \ $(OBJDIR)/allrepo.o \ $(OBJDIR)/attach.o \ $(OBJDIR)/bag.o \ |
| ︙ | ︙ | |||
213 214 215 216 217 218 219 220 221 222 223 224 225 226 | $(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 \ | > > > > > > > > > > > | 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 | $(OBJDIR)/gzip.o \ $(OBJDIR)/http.o \ $(OBJDIR)/http_socket.o \ $(OBJDIR)/http_ssl.o \ $(OBJDIR)/http_transport.o \ $(OBJDIR)/import.o \ $(OBJDIR)/info.o \ $(OBJDIR)/json.o \ $(OBJDIR)/json_artifact.o \ $(OBJDIR)/json_branch.o \ $(OBJDIR)/json_diff.o \ $(OBJDIR)/json_login.o \ $(OBJDIR)/json_query.o \ $(OBJDIR)/json_report.o \ $(OBJDIR)/json_tag.o \ $(OBJDIR)/json_timeline.o \ $(OBJDIR)/json_user.o \ $(OBJDIR)/json_wiki.o \ $(OBJDIR)/leaf.o \ $(OBJDIR)/login.o \ $(OBJDIR)/main.o \ $(OBJDIR)/manifest.o \ $(OBJDIR)/md5.o \ $(OBJDIR)/merge.o \ $(OBJDIR)/merge3.o \ |
| ︙ | ︙ | |||
256 257 258 259 260 261 262 263 264 265 266 267 268 269 | $(OBJDIR)/user.o \ $(OBJDIR)/verify.o \ $(OBJDIR)/vfile.o \ $(OBJDIR)/wiki.o \ $(OBJDIR)/wikiformat.o \ $(OBJDIR)/winhttp.o \ $(OBJDIR)/xfer.o \ $(OBJDIR)/zip.o APPNAME = fossil$(E) all: $(OBJDIR) $(APPNAME) | > | 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 | $(OBJDIR)/user.o \ $(OBJDIR)/verify.o \ $(OBJDIR)/vfile.o \ $(OBJDIR)/wiki.o \ $(OBJDIR)/wikiformat.o \ $(OBJDIR)/winhttp.o \ $(OBJDIR)/xfer.o \ $(OBJDIR)/xfersetup.o \ $(OBJDIR)/zip.o APPNAME = fossil$(E) all: $(OBJDIR) $(APPNAME) |
| ︙ | ︙ | |||
282 283 284 285 286 287 288 | $(OBJDIR)/mkindex: $(SRCDIR)/mkindex.c $(BCC) -o $(OBJDIR)/mkindex $(SRCDIR)/mkindex.c $(OBJDIR)/mkversion: $(SRCDIR)/mkversion.c $(BCC) -o $(OBJDIR)/mkversion $(SRCDIR)/mkversion.c | | | | > > > > > > > | | > | 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 | $(OBJDIR)/mkindex: $(SRCDIR)/mkindex.c $(BCC) -o $(OBJDIR)/mkindex $(SRCDIR)/mkindex.c $(OBJDIR)/mkversion: $(SRCDIR)/mkversion.c $(BCC) -o $(OBJDIR)/mkversion $(SRCDIR)/mkversion.c # WARNING. DANGER. Running the test suite modifies the repository the # build is done from, i.e. the checkout belongs to. Do not sync/push # the repository after running the tests. test: $(OBJDIR) $(APPNAME) $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME) $(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION $(OBJDIR)/mkversion $(OBJDIR)/mkversion $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h # The USE_SYSTEM_SQLITE variable may be undefined, set to 0, or set # to 1. If it is set to 1, then there is no need to build or link # the sqlite3.o object. Instead, the system sqlite will be linked # using -lsqlite3. SQLITE3_OBJ.1 = SQLITE3_OBJ.0 = $(OBJDIR)/sqlite3.o SQLITE3_OBJ. = $(SQLITE3_OBJ.0) # The FOSSIL_ENABLE_TCL variable may be undefined, set to 0, or set to 1. # If it is set to 1, then we need to build the Tcl integration code and # link to the Tcl library. TCL_OBJ.0 = TCL_OBJ.1 = $(OBJDIR)/th_tcl.o TCL_OBJ. = $(TCL_OBJ.0) EXTRAOBJ = $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE)) $(OBJDIR)/shell.o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o $(TCL_OBJ.$(FOSSIL_ENABLE_TCL)) $(OBJDIR)/cson_amalgamation.o $(APPNAME): $(OBJDIR)/headers $(OBJ) $(EXTRAOBJ) $(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB) # This rule prevents make from using its default rules to try build # an executable named "manifest" out of the file named "manifest.c" # $(SRCDIR)/../manifest: # noop clean: rm -rf $(OBJDIR)/* $(APPNAME) $(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_artifact_.c:$(OBJDIR)/json_artifact.h $(OBJDIR)/json_branch_.c:$(OBJDIR)/json_branch.h $(OBJDIR)/json_diff_.c:$(OBJDIR)/json_diff.h $(OBJDIR)/json_login_.c:$(OBJDIR)/json_login.h $(OBJDIR)/json_query_.c:$(OBJDIR)/json_query.h $(OBJDIR)/json_report_.c:$(OBJDIR)/json_report.h $(OBJDIR)/json_tag_.c:$(OBJDIR)/json_tag.h $(OBJDIR)/json_timeline_.c:$(OBJDIR)/json_timeline.h $(OBJDIR)/json_user_.c:$(OBJDIR)/json_user.h $(OBJDIR)/json_wiki_.c:$(OBJDIR)/json_wiki.h $(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h $(OBJDIR)/login_.c:$(OBJDIR)/login.h $(OBJDIR)/main_.c:$(OBJDIR)/main.h $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h $(OBJDIR)/name_.c:$(OBJDIR)/name.h $(OBJDIR)/path_.c:$(OBJDIR)/path.h $(OBJDIR)/pivot_.c:$(OBJDIR)/pivot.h $(OBJDIR)/popen_.c:$(OBJDIR)/popen.h $(OBJDIR)/pqueue_.c:$(OBJDIR)/pqueue.h $(OBJDIR)/printf_.c:$(OBJDIR)/printf.h $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h $(OBJDIR)/report_.c:$(OBJDIR)/report.h $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h $(OBJDIR)/search_.c:$(OBJDIR)/search.h $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h $(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h $(OBJDIR)/style_.c:$(OBJDIR)/style.h $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h $(OBJDIR)/update_.c:$(OBJDIR)/update.h $(OBJDIR)/url_.c:$(OBJDIR)/url.h $(OBJDIR)/user_.c:$(OBJDIR)/user.h $(OBJDIR)/verify_.c:$(OBJDIR)/verify.h $(OBJDIR)/vfile_.c:$(OBJDIR)/vfile.h $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h $(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h $(OBJDIR)/xfersetup_.c:$(OBJDIR)/xfersetup.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_artifact.o $(OBJDIR)/json_branch.o $(OBJDIR)/json_diff.o $(OBJDIR)/json_login.o $(OBJDIR)/json_query.o $(OBJDIR)/json_report.o $(OBJDIR)/json_tag.o $(OBJDIR)/json_timeline.o $(OBJDIR)/json_user.o $(OBJDIR)/json_wiki.o : $(SRCDIR)/json_detail.h 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 $(XTCC) -o $(OBJDIR)/add.o -c $(OBJDIR)/add_.c |
| ︙ | ︙ | |||
587 588 589 590 591 592 593 594 595 596 597 598 599 600 | $(OBJDIR)/info_.c: $(SRCDIR)/info.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/info.c >$(OBJDIR)/info_.c $(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)/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 $(OBJDIR)/leaf.h: $(OBJDIR)/headers | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 | $(OBJDIR)/info_.c: $(SRCDIR)/info.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/info.c >$(OBJDIR)/info_.c $(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_artifact_.c: $(SRCDIR)/json_artifact.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/json_artifact.c >$(OBJDIR)/json_artifact_.c $(OBJDIR)/json_artifact.o: $(OBJDIR)/json_artifact_.c $(OBJDIR)/json_artifact.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_artifact.o -c $(OBJDIR)/json_artifact_.c $(OBJDIR)/json_artifact.h: $(OBJDIR)/headers $(OBJDIR)/json_branch_.c: $(SRCDIR)/json_branch.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/json_branch.c >$(OBJDIR)/json_branch_.c $(OBJDIR)/json_branch.o: $(OBJDIR)/json_branch_.c $(OBJDIR)/json_branch.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_branch.o -c $(OBJDIR)/json_branch_.c $(OBJDIR)/json_branch.h: $(OBJDIR)/headers $(OBJDIR)/json_diff_.c: $(SRCDIR)/json_diff.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/json_diff.c >$(OBJDIR)/json_diff_.c $(OBJDIR)/json_diff.o: $(OBJDIR)/json_diff_.c $(OBJDIR)/json_diff.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_diff.o -c $(OBJDIR)/json_diff_.c $(OBJDIR)/json_diff.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_query_.c: $(SRCDIR)/json_query.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/json_query.c >$(OBJDIR)/json_query_.c $(OBJDIR)/json_query.o: $(OBJDIR)/json_query_.c $(OBJDIR)/json_query.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_query.o -c $(OBJDIR)/json_query_.c $(OBJDIR)/json_query.h: $(OBJDIR)/headers $(OBJDIR)/json_report_.c: $(SRCDIR)/json_report.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/json_report.c >$(OBJDIR)/json_report_.c $(OBJDIR)/json_report.o: $(OBJDIR)/json_report_.c $(OBJDIR)/json_report.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_report.o -c $(OBJDIR)/json_report_.c $(OBJDIR)/json_report.h: $(OBJDIR)/headers $(OBJDIR)/json_tag_.c: $(SRCDIR)/json_tag.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/json_tag.c >$(OBJDIR)/json_tag_.c $(OBJDIR)/json_tag.o: $(OBJDIR)/json_tag_.c $(OBJDIR)/json_tag.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_tag.o -c $(OBJDIR)/json_tag_.c $(OBJDIR)/json_tag.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_user_.c: $(SRCDIR)/json_user.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/json_user.c >$(OBJDIR)/json_user_.c $(OBJDIR)/json_user.o: $(OBJDIR)/json_user_.c $(OBJDIR)/json_user.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_user.o -c $(OBJDIR)/json_user_.c $(OBJDIR)/json_user.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 $(OBJDIR)/leaf.h: $(OBJDIR)/headers |
| ︙ | ︙ | |||
888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 | $(OBJDIR)/xfer_.c: $(SRCDIR)/xfer.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/xfer.c >$(OBJDIR)/xfer_.c $(OBJDIR)/xfer.o: $(OBJDIR)/xfer_.c $(OBJDIR)/xfer.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/xfer.o -c $(OBJDIR)/xfer_.c $(OBJDIR)/xfer.h: $(OBJDIR)/headers $(OBJDIR)/zip_.c: $(SRCDIR)/zip.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/zip.c >$(OBJDIR)/zip_.c $(OBJDIR)/zip.o: $(OBJDIR)/zip_.c $(OBJDIR)/zip.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/zip.o -c $(OBJDIR)/zip_.c $(OBJDIR)/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)/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 | > > > > > > > > > > > > > > | 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 | $(OBJDIR)/xfer_.c: $(SRCDIR)/xfer.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/xfer.c >$(OBJDIR)/xfer_.c $(OBJDIR)/xfer.o: $(OBJDIR)/xfer_.c $(OBJDIR)/xfer.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/xfer.o -c $(OBJDIR)/xfer_.c $(OBJDIR)/xfer.h: $(OBJDIR)/headers $(OBJDIR)/xfersetup_.c: $(SRCDIR)/xfersetup.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/xfersetup.c >$(OBJDIR)/xfersetup_.c $(OBJDIR)/xfersetup.o: $(OBJDIR)/xfersetup_.c $(OBJDIR)/xfersetup.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/xfersetup.o -c $(OBJDIR)/xfersetup_.c $(OBJDIR)/xfersetup.h: $(OBJDIR)/headers $(OBJDIR)/zip_.c: $(SRCDIR)/zip.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/zip.c >$(OBJDIR)/zip_.c $(OBJDIR)/zip.o: $(OBJDIR)/zip_.c $(OBJDIR)/zip.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/zip.o -c $(OBJDIR)/zip_.c $(OBJDIR)/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)/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 $(OBJDIR)/th_tcl.o: $(SRCDIR)/th_tcl.c $(XTCC) -I$(SRCDIR) -c $(SRCDIR)/th_tcl.c -o $(OBJDIR)/th_tcl.o $(OBJDIR)/cson_amalgamation.o: $(SRCDIR)/cson_amalgamation.c $(XTCC) -I$(SRCDIR) -c $(SRCDIR)/cson_amalgamation.c -o $(OBJDIR)/cson_amalgamation.o -DCSON_FOSSIL_MODE |
Changes to src/makeheaders.c.
| ︙ | ︙ | |||
325 326 327 328 329 330 331 332 333 | int flags; /* Various flags (DP_ and PS_ flags above) */ }; /* ** The following text line appears at the top of every file generated ** by this program. By recognizing this line, the program can be sure ** never to read a file that it generated itself. */ const char zTopLine[] = | > > > | > | 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 | int flags; /* Various flags (DP_ and PS_ flags above) */ }; /* ** The following text line appears at the top of every file generated ** by this program. By recognizing this line, the program can be sure ** never to read a file that it generated itself. ** ** The "#undef INTERFACE" part is a hack to work around a name collision ** in MSVC 2008. */ const char zTopLine[] = "/* \aThis file was automatically generated. Do not edit! */\n" "#undef INTERFACE\n"; #define nTopLine (sizeof(zTopLine)-1) /* ** The name of the file currently being parsed. */ static char *zFilename; |
| ︙ | ︙ |
Changes to src/makemake.tcl.
| ︙ | ︙ | |||
51 52 53 54 55 56 57 58 59 60 61 62 63 64 | graph gzip http http_socket http_transport import info leaf login main manifest md5 merge merge3 | > > > > > > > > > > > | 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | graph gzip http http_socket http_transport import info json json_artifact json_branch json_diff json_login json_query json_report json_tag json_timeline json_user json_wiki leaf login main manifest md5 merge merge3 |
| ︙ | ︙ | |||
94 95 96 97 98 99 100 101 102 103 104 105 106 107 | user verify vfile wiki wikiformat winhttp xfer zip http_ssl } # Name of the final application # set name fossil | > | 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | user verify vfile wiki wikiformat winhttp xfer xfersetup zip http_ssl } # Name of the final application # set name fossil |
| ︙ | ︙ | |||
126 127 128 129 130 131 132 | ############################################################################## # Start by generating the "main.mk" makefile used for all unix systems. # puts "building main.mk" set output_file [open main.mk w] fconfigure $output_file -translation binary | | > > > | 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
##############################################################################
# Start by generating the "main.mk" makefile used for all unix systems.
#
puts "building main.mk"
set output_file [open main.mk w]
fconfigure $output_file -translation binary
writeln {#
##############################################################################
# WARNING: DO NOT EDIT, AUTOMATICALLY GENERATED FILE (SEE "src/makemake.tcl")
##############################################################################
#
# This file is automatically generated. Instead of editing this
# file, edit "makemake.tcl" then run "tclsh makemake.tcl"
# to regenerate this file.
#
# This file is included by primary Makefile.
#
|
| ︙ | ︙ | |||
177 178 179 180 181 182 183 | $(OBJDIR)/mkindex: $(SRCDIR)/mkindex.c $(BCC) -o $(OBJDIR)/mkindex $(SRCDIR)/mkindex.c $(OBJDIR)/mkversion: $(SRCDIR)/mkversion.c $(BCC) -o $(OBJDIR)/mkversion $(SRCDIR)/mkversion.c | | | | > > > > > > > | > > | 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 | $(OBJDIR)/mkindex: $(SRCDIR)/mkindex.c $(BCC) -o $(OBJDIR)/mkindex $(SRCDIR)/mkindex.c $(OBJDIR)/mkversion: $(SRCDIR)/mkversion.c $(BCC) -o $(OBJDIR)/mkversion $(SRCDIR)/mkversion.c # WARNING. DANGER. Running the test suite modifies the repository the # build is done from, i.e. the checkout belongs to. Do not sync/push # the repository after running the tests. test: $(OBJDIR) $(APPNAME) $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME) $(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION $(OBJDIR)/mkversion $(OBJDIR)/mkversion $(SRCDIR)/../manifest.uuid \ $(SRCDIR)/../manifest \ $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h # The USE_SYSTEM_SQLITE variable may be undefined, set to 0, or set # to 1. If it is set to 1, then there is no need to build or link # the sqlite3.o object. Instead, the system sqlite will be linked # using -lsqlite3. SQLITE3_OBJ.1 = SQLITE3_OBJ.0 = $(OBJDIR)/sqlite3.o SQLITE3_OBJ. = $(SQLITE3_OBJ.0) # The FOSSIL_ENABLE_TCL variable may be undefined, set to 0, or set to 1. # If it is set to 1, then we need to build the Tcl integration code and # link to the Tcl library. TCL_OBJ.0 = TCL_OBJ.1 = $(OBJDIR)/th_tcl.o TCL_OBJ. = $(TCL_OBJ.0) EXTRAOBJ = \ $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE)) \ $(OBJDIR)/shell.o \ $(OBJDIR)/th.o \ $(OBJDIR)/th_lang.o \ $(TCL_OBJ.$(FOSSIL_ENABLE_TCL)) \ $(OBJDIR)/cson_amalgamation.o $(APPNAME): $(OBJDIR)/headers $(OBJ) $(EXTRAOBJ) $(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB) # This rule prevents make from using its default rules to try build # an executable named "manifest" out of the file named "manifest.c" # |
| ︙ | ︙ | |||
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 |
set mhargs {}
foreach s [lsort $src] {
append mhargs " \$(OBJDIR)/${s}_.c:\$(OBJDIR)/$s.h"
set extra_h($s) {}
}
append mhargs " \$(SRCDIR)/sqlite3.h"
append mhargs " \$(SRCDIR)/th.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 "Makefile:"
set extra_h(main) \$(OBJDIR)/page_index.h
foreach s [lsort $src] {
writeln "\$(OBJDIR)/${s}_.c:\t\$(SRCDIR)/$s.c \$(OBJDIR)/translate"
writeln "\t\$(OBJDIR)/translate \$(SRCDIR)/$s.c >\$(OBJDIR)/${s}_.c\n"
writeln "\$(OBJDIR)/$s.o:\t\$(OBJDIR)/${s}_.c \$(OBJDIR)/$s.h $extra_h($s) \$(SRCDIR)/config.h"
| > > | 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
set mhargs {}
foreach s [lsort $src] {
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_artifact.o \$(OBJDIR)/json_branch.o \$(OBJDIR)/json_diff.o \$(OBJDIR)/json_login.o \$(OBJDIR)/json_query.o \$(OBJDIR)/json_report.o \$(OBJDIR)/json_tag.o \$(OBJDIR)/json_timeline.o \$(OBJDIR)/json_user.o \$(OBJDIR)/json_wiki.o : \$(SRCDIR)/json_detail.h"
writeln "Makefile:"
set extra_h(main) \$(OBJDIR)/page_index.h
foreach s [lsort $src] {
writeln "\$(OBJDIR)/${s}_.c:\t\$(SRCDIR)/$s.c \$(OBJDIR)/translate"
writeln "\t\$(OBJDIR)/translate \$(SRCDIR)/$s.c >\$(OBJDIR)/${s}_.c\n"
writeln "\$(OBJDIR)/$s.o:\t\$(OBJDIR)/${s}_.c \$(OBJDIR)/$s.h $extra_h($s) \$(SRCDIR)/config.h"
|
| ︙ | ︙ | |||
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 |
writeln "\t\$(XTCC) $opt -c \$(SRCDIR)/shell.c -o \$(OBJDIR)/shell.o\n"
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"
close $output_file
#
# End of the main.mk output
##############################################################################
##############################################################################
##############################################################################
# Begin win/Makefile.mingw
#
puts "building ../win/Makefile.mingw"
set output_file [open ../win/Makefile.mingw w]
fconfigure $output_file -translation binary
writeln {#!/usr/bin/make
#
| > > > > > > > > > > > > > > > > > | < | > > > | > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > | | > > > > > > > > > > > > > > | > > > > > | | > > | > > > > > > > > > > > > | > | | | | | 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 |
writeln "\t\$(XTCC) $opt -c \$(SRCDIR)/shell.c -o \$(OBJDIR)/shell.o\n"
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"
writeln "\$(OBJDIR)/th_tcl.o:\t\$(SRCDIR)/th_tcl.c"
writeln "\t\$(XTCC) -I\$(SRCDIR) -c \$(SRCDIR)/th_tcl.c -o \$(OBJDIR)/th_tcl.o\n"
set opt {}
writeln {
$(OBJDIR)/cson_amalgamation.o: $(SRCDIR)/cson_amalgamation.c
$(XTCC) -I$(SRCDIR) -c $(SRCDIR)/cson_amalgamation.c -o $(OBJDIR)/cson_amalgamation.o -DCSON_FOSSIL_MODE
}
close $output_file
#
# End of the main.mk output
##############################################################################
##############################################################################
##############################################################################
# Begin win/Makefile.mingw
#
puts "building ../win/Makefile.mingw"
set output_file [open ../win/Makefile.mingw w]
fconfigure $output_file -translation binary
writeln {#!/usr/bin/make
#
##############################################################################
# WARNING: DO NOT EDIT, AUTOMATICALLY GENERATED FILE (SEE "src/makemake.tcl")
##############################################################################
#
# This file is automatically generated. Instead of editing this
# file, edit "makemake.tcl" then run "tclsh makemake.tcl"
# to regenerate this file.
#
# This is a makefile for us on windows using MinGW.
#
#### The toplevel directory of the source tree. Fossil can be built
# in a directory that is separate from the source tree. Just change
# the following to point from the build directory to the src/ folder.
#
SRCDIR = src
#### The directory into which object code files should be written.
#
OBJDIR = wbld
#### C Compiler and options for use in building executables that
# will run on the platform that is doing the build. This is used
# to compile code-generator programs as part of the build process.
# See TCC below for the C compiler for building the finished binary.
#
BCC = gcc
#### Enable HTTPS support via OpenSSL (links to libssl and libcrypto)
#
# FOSSIL_ENABLE_SSL = 1
#### Enable scripting support via Tcl/Tk
#
# FOSSIL_ENABLE_TCL = 1
#### Use the Tcl source directory instead of the install directory?
# This is useful when Tcl has been compiled statically with MinGW.
#
FOSSIL_TCL_SOURCE = 1
#### The directories where the zlib include and library files are located.
# The recommended usage here is to use the Sysinternals junction tool
# to create a hard link between an "zlib-1.x.y" sub-directory of the
# Fossil source code directory and the target zlib source directory.
#
ZINCDIR = $(SRCDIR)/../zlib-1.2.5
ZLIBDIR = $(SRCDIR)/../zlib-1.2.5
#### The directories where the OpenSSL include and library files are located.
# The recommended usage here is to use the Sysinternals junction tool
# to create a hard link between an "openssl-1.x" sub-directory of the
# Fossil source code directory and the target OpenSSL source directory.
#
OPENSSLINCDIR = $(SRCDIR)/../openssl-1.0.0e/include
OPENSSLLIBDIR = $(SRCDIR)/../openssl-1.0.0e
#### Either the directory where the Tcl library is installed or the Tcl
# source code directory resides (depending on the value of the macro
# FOSSIL_TCL_SOURCE). If this points to the Tcl install directory,
# this directory must have "include" and "lib" sub-directories. If
# this points to the Tcl source code directory, this directory must
# have "generic" and "win" sub-directories. The recommended usage
# here is to use the Sysinternals junction tool to create a hard
# link between a "tcl-8.x" sub-directory of the Fossil source code
# directory and the target Tcl directory. This removes the need to
# hard-code the necessary paths in this Makefile.
#
TCLDIR = $(SRCDIR)/../tcl-8.6
#### The Tcl source code directory. This defaults to the same value as
# TCLDIR macro (above), which may not be correct. This value will
# only be used if the FOSSIL_TCL_SOURCE macro is defined.
#
TCLSRCDIR = $(TCLDIR)
#### The Tcl include and library directories. These values will only be
# used if the FOSSIL_TCL_SOURCE macro is not defined.
#
TCLINCDIR = $(TCLDIR)/include
TCLLIBDIR = $(TCLDIR)/lib
#### Tcl: Which Tcl library do we want to use (8.4, 8.5, 8.6, etc)?
#
LIBTCL = -ltcl86
#### C Compile and options for use in building executables that
# will run on the target platform. This is usually the same
# as BCC, unless you are cross-compiling. This C compiler builds
# the finished binary for fossil. The BCC compiler above is used
# for building intermediate code-generator tools.
#
TCC = gcc -Os -Wall -L$(ZLIBDIR) -I$(ZINCDIR)
# With HTTPS support
ifdef FOSSIL_ENABLE_SSL
TCC += -L$(OPENSSLLIBDIR) -I$(OPENSSLINCDIR)
endif
# With Tcl support
ifdef FOSSIL_ENABLE_TCL
ifdef FOSSIL_TCL_SOURCE
TCC += -L$(TCLSRCDIR)/win -I$(TCLSRCDIR)/generic -I$(TCLSRCDIR)/win
else
TCC += -L$(TCLLIBDIR) -I$(TCLINCDIR)
endif
endif
# With HTTPS support
ifdef FOSSIL_ENABLE_SSL
TCC += -DFOSSIL_ENABLE_SSL=1
endif
# With Tcl support (statically linked)
ifdef FOSSIL_ENABLE_TCL
TCC += -DFOSSIL_ENABLE_TCL=1 -DSTATIC_BUILD
endif
#### Extra arguments for linking the finished binary. Fossil needs
# to link against the Z-Lib compression library. There are no
# other mandatory dependencies. We add the -static option here
# so that we can build a static executable that will run in a
# chroot jail.
#
LIB = -static
LIB += -lmingwex -lz
# OpenSSL: Add the necessary libaries required, if enabled.
ifdef FOSSIL_ENABLE_SSL
LIB += -lssl -lcrypto -lgdi32
endif
# Tcl: Add the necessary libaries required, if enabled.
ifdef FOSSIL_ENABLE_TCL
LIB += $(LIBTCL)
endif
#### These libraries MUST appear in the same order as they do for Tcl
# or linking with it will not work (exact reason unknown).
#
ifdef FOSSIL_ENABLE_TCL
LIB += -lnetapi32 -lkernel32 -luser32 -ladvapi32 -lws2_32
else
LIB += -lws2_32
endif
#### Tcl shell for use in running the fossil test suite. This is only
# used for testing.
#
TCLSH = tclsh
#### Nullsoft installer MakeNSIS location
#
MAKENSIS = "$(ProgramFiles)\NSIS\MakeNSIS.exe"
#### Include a configuration file that can override any one of these settings.
#
-include config.w32
# STOP HERE
# You should not need to change anything below this line
|
| ︙ | ︙ | |||
397 398 399 400 401 402 403 | $(OBJDIR)/mkindex: $(SRCDIR)/mkindex.c $(BCC) -o $(OBJDIR)/mkindex $(SRCDIR)/mkindex.c $(VERSION): $(SRCDIR)/mkversion.c $(BCC) -o $(OBJDIR)/version $(SRCDIR)/mkversion.c | | | | | > > > > > | | | | 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 | $(OBJDIR)/mkindex: $(SRCDIR)/mkindex.c $(BCC) -o $(OBJDIR)/mkindex $(SRCDIR)/mkindex.c $(VERSION): $(SRCDIR)/mkversion.c $(BCC) -o $(OBJDIR)/version $(SRCDIR)/mkversion.c # WARNING. DANGER. Running the test suite modifies the repository the # build is done from, i.e. the checkout belongs to. Do not sync/push # the repository after running the tests. test: $(OBJDIR) $(APPNAME) $(TCLSH) $(SRCDIR)/../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 ifdef FOSSIL_ENABLE_TCL EXTRAOBJ += $(OBJDIR)/th_tcl.o endif $(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 # an executable named "manifest" out of the file named "manifest.c" # $(SRCDIR)/../manifest: # noop # Requires MSYS to be installed in addition to the MinGW, for the "rm" # command. "del" will not work here because it is not a separate command # but a MSDOS-shell builtin. # clean: rm -rf $(OBJDIR) $(APPNAME) setup: $(OBJDIR) $(APPNAME) $(MAKENSIS) ./fossil.nsi } |
| ︙ | ︙ | |||
464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 |
}
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"
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"
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"
close $output_file
#
# End of the main.mk output
##############################################################################
##############################################################################
##############################################################################
# Begin win/Makefile.dmc
#
puts "building ../win/Makefile.dmc"
set output_file [open ../win/Makefile.dmc w]
fconfigure $output_file -translation binary
| > > > > > > > > > > | > > > | > | 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 |
}
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 -DCSON_FOSSIL_MODE\n"
writeln "\$(OBJDIR)/json.o \$(OBJDIR)/json_artifact.o \$(OBJDIR)/json_branch.o \$(OBJDIR)/json_diff.o \$(OBJDIR)/json_login.o \$(OBJDIR)/json_query.o \$(OBJDIR)/json_report.o \$(OBJDIR)/json_tag.o \$(OBJDIR)/json_timeline.o \$(OBJDIR)/json_user.o \$(OBJDIR)/json_wiki.o : \$(SRCDIR)/json_detail.h\n"
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"
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"
writeln {ifdef FOSSIL_ENABLE_TCL
$(OBJDIR)/th_tcl.o: $(SRCDIR)/th_tcl.c
$(XTCC) -I$(SRCDIR) -c $(SRCDIR)/th_tcl.c -o $(OBJDIR)/th_tcl.o
endif
}
close $output_file
#
# End of the main.mk output
##############################################################################
##############################################################################
##############################################################################
# Begin win/Makefile.dmc
#
puts "building ../win/Makefile.dmc"
set output_file [open ../win/Makefile.dmc w]
fconfigure $output_file -translation binary
writeln {#
##############################################################################
# WARNING: DO NOT EDIT, AUTOMATICALLY GENERATED FILE (SEE "src/makemake.tcl")
##############################################################################
#
# This file is automatically generated. Instead of editing this
# file, edit "makemake.tcl" then run "tclsh makemake.tcl"
# to regenerate this file.
#
B = ..
SRCDIR = $B\src
OBJDIR = .
O = .obj
E = .exe
|
| ︙ | ︙ | |||
575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 |
$(TCC) -o$@ -c $(SQLITE_OPTIONS) $**
$(OBJDIR)\th$O : $(SRCDIR)\th.c
$(TCC) -o$@ -c $**
$(OBJDIR)\th_lang$O : $(SRCDIR)\th_lang.c
$(TCC) -o$@ -c $**
VERSION.h : version$E $B\manifest.uuid $B\manifest $B\VERSION
+$** > $@
page_index.h: mkindex$E $(SRC)
+$** > $@
clean:
-del $(OBJDIR)\*.obj
-del *.obj *_.c *.h *.map
realclean:
-del $(APPNAME) translate$E mkindex$E makeheaders$E mkversion$E
}
foreach s [lsort $src] {
writeln "\$(OBJDIR)\\$s\$O : ${s}_.c ${s}.h"
writeln "\t\$(TCC) -o\$@ -c ${s}_.c\n"
writeln "${s}_.c : \$(SRCDIR)\\$s.c"
writeln "\t+translate\$E \$** > \$@\n"
}
writeln -nonewline "headers: makeheaders\$E page_index.h VERSION.h\n\t +makeheaders\$E "
foreach s [lsort $src] {
writeln -nonewline "${s}_.c:$s.h "
}
| > > > > > > > > > > > > > > > > | | > > > | > | 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 |
$(TCC) -o$@ -c $(SQLITE_OPTIONS) $**
$(OBJDIR)\th$O : $(SRCDIR)\th.c
$(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)
+$** > $@
clean:
-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_artifact$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_branch$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_diff$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_login$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_query$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_report$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_tag$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_timeline$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_user$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_wiki$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"
writeln "${s}_.c : \$(SRCDIR)\\$s.c"
writeln "\t+translate\$E \$** > \$@\n"
}
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 "\t@copy /Y nul: headers"
close $output_file
#
# End of the win/Makefile.dmc output
##############################################################################
##############################################################################
##############################################################################
# Begin win/Makefile.msc
#
puts "building ../win/Makefile.msc"
set output_file [open ../win/Makefile.msc w]
fconfigure $output_file -translation binary
writeln {#
##############################################################################
# WARNING: DO NOT EDIT, AUTOMATICALLY GENERATED FILE (SEE "src/makemake.tcl")
##############################################################################
#
# This file is automatically generated. Instead of editing this
# file, edit "makemake.tcl" then run "tclsh makemake.tcl"
# to regenerate this file.
#
B = ..
SRCDIR = $B\src
OBJDIR = .
OX = .
O = .obj
E = .exe
|
| ︙ | ︙ | |||
715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 |
$(TCC) /Fo$@ -c $**
$(OX)\th_lang$O : $(SRCDIR)\th_lang.c
$(TCC) /Fo$@ -c $**
VERSION.h : mkversion$E $B\manifest.uuid $B\manifest $B\VERSION
$** > $@
page_index.h: mkindex$E $(SRC)
$** > $@
clean:
-del $(OX)\*.obj
-del *.obj *_.c *.h *.map
-del headers linkopts
realclean:
-del $(APPNAME) translate$E mkindex$E makeheaders$E mkversion$E
}
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"
writeln "\ttranslate\$E \$** > \$@\n"
}
writeln -nonewline "headers: makeheaders\$E page_index.h VERSION.h\n\tmakeheaders\$E "
foreach s [lsort $src] {
writeln -nonewline "${s}_.c:$s.h "
}
| > > > > > > > > > > > > > > | | > > > | | 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 |
$(TCC) /Fo$@ -c $**
$(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:
-del $(OX)\*.obj
-del *.obj *_.c *.h *.map
-del headers linkopts
realclean:
-del $(APPNAME) translate$E mkindex$E makeheaders$E mkversion$E
$(OBJDIR)\json$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_artifact$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_branch$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_diff$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_login$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_query$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_report$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_tag$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_timeline$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_user$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_wiki$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"
writeln "\ttranslate\$E \$** > \$@\n"
}
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 "\t@copy /Y nul: headers"
close $output_file
#
# End of the win/Makefile.msc output
##############################################################################
##############################################################################
##############################################################################
# Begin win/Makefile.PellesCGMake
#
puts "building ../win/Makefile.PellesCGMake"
set output_file [open ../win/Makefile.PellesCGMake w]
fconfigure $output_file -translation binary
writeln {#
##############################################################################
# WARNING: DO NOT EDIT, AUTOMATICALLY GENERATED FILE (SEE "src/makemake.tcl")
##############################################################################
#
# This file is automatically generated. Instead of editing this
# file, edit "makemake.tcl" then run "tclsh makemake.tcl"
# to regenerate this file.
#
# HowTo
# -----
#
# This is a Makefile to compile fossil with PellesC from
# http://www.smorgasbordet.com/pellesc/index.htm
|
| ︙ | ︙ |
Changes to src/manifest.c.
| ︙ | ︙ | |||
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 |
ManifestText x;
char cPrevType = 0;
char cType;
char *z;
int n;
char *zUuid;
int sz = 0;
/* Every control artifact ends with a '\n' character. Exit early
** if that is not the case for this artifact.
*/
z = blob_materialize(pContent);
n = blob_size(pContent);
if( n<=0 || z[n-1]!='\n' ){
blob_reset(pContent);
return 0;
}
| > > > > > > > > > > | 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 |
ManifestText x;
char cPrevType = 0;
char cType;
char *z;
int n;
char *zUuid;
int sz = 0;
int isRepeat;
static Bag seen;
if( bag_find(&seen, rid) ){
isRepeat = 1;
}else{
isRepeat = 0;
bag_insert(&seen, rid);
}
/* Every control artifact ends with a '\n' character. Exit early
** if that is not the case for this artifact.
*/
if( !isRepeat ) g.parseCnt[0]++;
z = blob_materialize(pContent);
n = blob_size(pContent);
if( n<=0 || z[n-1]!='\n' ){
blob_reset(pContent);
return 0;
}
|
| ︙ | ︙ | |||
883 884 885 886 887 888 889 890 891 892 893 894 895 896 |
if( p->nField>0 ) goto manifest_syntax_error;
if( p->zTicketUuid ) goto manifest_syntax_error;
if( p->zWikiTitle ) goto manifest_syntax_error;
if( p->zTicketUuid ) goto manifest_syntax_error;
p->type = CFTYPE_MANIFEST;
}
md5sum_init();
return p;
manifest_syntax_error:
/*fprintf(stderr, "Manifest error on line %i\n", lineNo);fflush(stderr);*/
md5sum_init();
manifest_destroy(p);
return 0;
| > | 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 |
if( p->nField>0 ) goto manifest_syntax_error;
if( p->zTicketUuid ) goto manifest_syntax_error;
if( p->zWikiTitle ) goto manifest_syntax_error;
if( p->zTicketUuid ) goto manifest_syntax_error;
p->type = CFTYPE_MANIFEST;
}
md5sum_init();
if( !isRepeat ) g.parseCnt[p->type]++;
return p;
manifest_syntax_error:
/*fprintf(stderr, "Manifest error on line %i\n", lineNo);fflush(stderr);*/
md5sum_init();
manifest_destroy(p);
return 0;
|
| ︙ | ︙ | |||
1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 |
if( pFile && pFile->zUuid==0 ) return 0;
if( pFile==0 && p->zBaseline ){
fetch_baseline(p, 1);
pFile = manifest_file_seek_base(p->pBaseline, zName);
}
return pFile;
}
/*
** Add mlink table entries associated with manifest cid, pChild. The
** parent manifest is pid, pParent. One of either pChild or pParent
** will be NULL and it will be computed based on cid/pid.
**
** A single mlink entry is added for every file that changed content,
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 |
if( pFile && pFile->zUuid==0 ) return 0;
if( pFile==0 && p->zBaseline ){
fetch_baseline(p, 1);
pFile = manifest_file_seek_base(p->pBaseline, zName);
}
return pFile;
}
/*
** Look for a file in a manifest, taking the case-sensitive option
** into account. If case-sensitive is off, then files in any case
** will match.
*/
ManifestFile *manifest_file_find(Manifest *p, const char *zName){
int i;
Manifest *pBase;
if( filenames_are_case_sensitive() ){
return manifest_file_seek(p, zName);
}
for(i=0; i<p->nFile; i++){
if( fossil_stricmp(zName, p->aFile[i].zName)==0 ){
return &p->aFile[i];
}
}
if( p->zBaseline==0 ) return 0;
fetch_baseline(p, 1);
pBase = p->pBaseline;
if( pBase==0 ) return 0;
for(i=0; i<pBase->nFile; i++){
if( fossil_stricmp(zName, pBase->aFile[i].zName)==0 ){
return &pBase->aFile[i];
}
}
return 0;
}
/*
** Add mlink table entries associated with manifest cid, pChild. The
** parent manifest is pid, pParent. One of either pChild or pParent
** will be NULL and it will be computed based on cid/pid.
**
** A single mlink entry is added for every file that changed content,
|
| ︙ | ︙ | |||
1321 1322 1323 1324 1325 1326 1327 |
add_one_mlink(cid, pParentFile->zUuid, pChildFile->zUuid,
pChildFile->zName, 0, isPublic, mperm);
}
}
}
if( pParent->zBaseline && pChild->zBaseline ){
/* Both parent and child are delta manifests. Look for files that
| | > | | > > > | | > > > > > > > > > > | | > | 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 |
add_one_mlink(cid, pParentFile->zUuid, pChildFile->zUuid,
pChildFile->zName, 0, isPublic, mperm);
}
}
}
if( pParent->zBaseline && pChild->zBaseline ){
/* Both parent and child are delta manifests. Look for files that
** are deleted or modified in the parent but which reappear or revert
** to baseline in the child and show such files as being added or changed
** in the child. */
for(i=0, pParentFile=pParent->aFile; i<pParent->nFile; i++, pParentFile++){
if( pParentFile->zUuid ){
pChildFile = manifest_file_seek_base(pChild, pParentFile->zName);
if( pChildFile==0 ){
/* The child file reverts to baseline. Show this as a change */
pChildFile = manifest_file_seek(pChild, pParentFile->zName);
if( pChildFile ){
add_one_mlink(cid, pParentFile->zUuid, pChildFile->zUuid,
pChildFile->zName, 0, isPublic,
manifest_file_mperm(pChildFile));
}
}
}else{
pChildFile = manifest_file_seek(pChild, pParentFile->zName);
if( pChildFile ){
/* File resurrected in the child after having been deleted in
** the parent. Show this as an added file. */
add_one_mlink(cid, 0, pChildFile->zUuid, pChildFile->zName, 0,
isPublic, manifest_file_mperm(pChildFile));
}
}
}
}else if( pChild->zBaseline==0 ){
/* pChild is a baseline. Look for files that are present in pParent
** but are missing from pChild and mark them as having been deleted. */
manifest_file_rewind(pParent);
while( (pParentFile = manifest_file_next(pParent,0))!=0 ){
|
| ︙ | ︙ | |||
1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 |
db_multi_exec(
"REPLACE INTO event(type,mtime,objid,user,comment)"
"VALUES('t',%.17g,%d,%Q,%Q)",
p->rDate, rid, p->zUser, zComment
);
free(zComment);
}
}
db_end_transaction(0);
if( p->type==CFTYPE_MANIFEST ){
manifest_cache_insert(p);
}else{
manifest_destroy(p);
}
assert( blob_is_reset(pContent) );
return 1;
}
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 |
db_multi_exec(
"REPLACE INTO event(type,mtime,objid,user,comment)"
"VALUES('t',%.17g,%d,%Q,%Q)",
p->rDate, rid, p->zUser, zComment
);
free(zComment);
}
}
if( p->type==CFTYPE_CONTROL ){
Blob comment;
int i;
const char *zName;
const char *zValue;
const char *zUuid;
blob_zero(&comment);
for(i=0; i<p->nTag; i++){
zUuid = p->aTag[i].zUuid;
if( i==0 || fossil_strcmp(zUuid, p->aTag[i-1].zUuid)!=0 ){
if( i>0 ) blob_append(&comment, " ", 1);
blob_appendf(&comment, "Tag changes on [/timeline?dp=%S&n=4 | %S]:",
zUuid, zUuid);
}
zName = p->aTag[i].zName;
zValue = p->aTag[i].zValue;
if( zName[0]=='-' ){
blob_appendf(&comment, " Cancel");
}else if( zName[0]=='+' ){
blob_appendf(&comment, " Add");
}else{
blob_appendf(&comment, " Add propagating");
}
if( memcmp(&zName[1], "sym-",4)==0 ){
blob_appendf(&comment, " symbolic tag \"%h\".", &zName[5]);
}else if( fossil_strcmp(&zName[1], "comment")!=0 && zValue && zValue[0] ){
blob_appendf(&comment, " %h=%h.", &zName[1], zValue);
}else{
blob_appendf(&comment, " %h.", &zName[1]);
}
}
db_multi_exec(
"REPLACE INTO event(type,mtime,objid,user,comment)"
"VALUES('g',%.17g,%d,%Q,%Q)",
p->rDate, rid, p->zUser, blob_str(&comment)
);
blob_reset(&comment);
}
db_end_transaction(0);
if( p->type==CFTYPE_MANIFEST ){
manifest_cache_insert(p);
}else{
manifest_destroy(p);
}
assert( blob_is_reset(pContent) );
return 1;
}
|
Changes to src/md5.c.
| ︙ | ︙ | |||
419 420 421 422 423 424 425 | MD5Final(zResult, &ctx); DigestToBase16(zResult, blob_buffer(pCksum)); return 0; } /* | | > | | 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 |
MD5Final(zResult, &ctx);
DigestToBase16(zResult, blob_buffer(pCksum));
return 0;
}
/*
** COMMAND: md5sum*
** Usage: %fossil md5sum FILES....
**
** Compute an MD5 checksum of all files named on the command-line.
** If a file is named "-" then content is read from standard input.
*/
void md5sum_test(void){
int i;
Blob in;
Blob cksum;
for(i=2; i<g.argc; i++){
|
| ︙ | ︙ |
Changes to src/merge.c.
| ︙ | ︙ | |||
18 19 20 21 22 23 24 25 26 27 28 29 30 31 | ** This file contains code used to merge two or more branches into ** a single tree. */ #include "config.h" #include "merge.h" #include <assert.h> /* ** COMMAND: merge ** ** Usage: %fossil merge ?OPTIONS? VERSION ** ** The argument VERSION is a version that should be merged into the | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
** This file contains code used to merge two or more branches into
** a single tree.
*/
#include "config.h"
#include "merge.h"
#include <assert.h>
/*
** Print information about a particular check-in.
*/
void print_checkin_description(int rid, int indent, const char *zLabel){
Stmt q;
db_prepare(&q,
"SELECT datetime(mtime,'localtime'),"
" coalesce(euser,user), coalesce(ecomment,comment),"
" (SELECT uuid FROM blob WHERE rid=%d),"
" (SELECT group_concat(substr(tagname,5), ', ') FROM tag, tagxref"
" WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid"
" AND tagxref.rid=%d AND tagxref.tagtype>0)"
" FROM event WHERE objid=%d", rid, rid, rid);
if( db_step(&q)==SQLITE_ROW ){
const char *zTagList = db_column_text(&q, 4);
char *zCom;
if( zTagList && zTagList[0] ){
zCom = mprintf("%s (%s)", db_column_text(&q, 2), zTagList);
}else{
zCom = mprintf("%s", db_column_text(&q,2));
}
fossil_print("%-*s [%S] by %s on %s\n%*s",
indent-1, zLabel,
db_column_text(&q, 3),
db_column_text(&q, 1),
db_column_text(&q, 0),
indent, "");
comment_print(zCom, indent, 78);
fossil_free(zCom);
}
db_finalize(&q);
}
/*
** COMMAND: merge
**
** Usage: %fossil merge ?OPTIONS? VERSION
**
** The argument VERSION is a version that should be merged into the
|
| ︙ | ︙ | |||
71 72 73 74 75 76 77 78 79 80 81 82 83 84 | const char *zBinGlob; /* The value of --binary */ const char *zPivot; /* The value of --baseline */ int debugFlag; /* True if --debug is present */ int nChng; /* Number of file name changes */ int *aChng; /* An array of file name changes */ int i; /* Loop counter */ int nConflict = 0; /* Number of conflicts seen */ int caseSensitive; /* True for case-sensitive filenames */ Stmt q; /* Notation: ** ** V The current checkout | > | 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | const char *zBinGlob; /* The value of --binary */ const char *zPivot; /* The value of --baseline */ int debugFlag; /* True if --debug is present */ int nChng; /* Number of file name changes */ int *aChng; /* An array of file name changes */ int i; /* Loop counter */ int nConflict = 0; /* Number of conflicts seen */ int nOverwrite = 0; /* Number of unmanaged files overwritten */ int caseSensitive; /* True for case-sensitive filenames */ Stmt q; /* Notation: ** ** V The current checkout |
| ︙ | ︙ | |||
113 114 115 116 117 118 119 |
pid = name_to_typed_rid(zPivot, "ci");
if( pid==0 || !is_a_version(pid) ){
fossil_fatal("not a version: %s", zPivot);
}
if( pickFlag ){
fossil_fatal("incompatible options: --cherrypick & --baseline");
}
| < | 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
pid = name_to_typed_rid(zPivot, "ci");
if( pid==0 || !is_a_version(pid) ){
fossil_fatal("not a version: %s", zPivot);
}
if( pickFlag ){
fossil_fatal("incompatible options: --cherrypick & --baseline");
}
}else if( pickFlag || backoutFlag ){
pid = db_int(0, "SELECT pid FROM plink WHERE cid=%d AND isprim", mid);
if( pid<=0 ){
fossil_fatal("cannot find an ancestor for %s", g.argv[2]);
}
}else{
pivot_set_primary(mid);
|
| ︙ | ︙ | |||
141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
int t = pid;
pid = mid;
mid = t;
}
if( !is_a_version(pid) ){
fossil_fatal("not a version: record #%d", pid);
}
vfile_check_signature(vid, 1, 0);
db_begin_transaction();
if( !nochangeFlag ) undo_begin();
load_vfile_from_rid(mid);
load_vfile_from_rid(pid);
if( debugFlag ){
char *z;
| > > > > | 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
int t = pid;
pid = mid;
mid = t;
}
if( !is_a_version(pid) ){
fossil_fatal("not a version: record #%d", pid);
}
if( detailFlag ){
print_checkin_description(mid, 12, "merge-from:");
print_checkin_description(pid, 12, "baseline:");
}
vfile_check_signature(vid, 1, 0);
db_begin_transaction();
if( !nochangeFlag ) undo_begin();
load_vfile_from_rid(mid);
load_vfile_from_rid(pid);
if( debugFlag ){
char *z;
|
| ︙ | ︙ | |||
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 |
" WHERE idp=0 AND idv=0 AND idm>0"
);
while( db_step(&q)==SQLITE_ROW ){
int idm = db_column_int(&q, 0);
int rowid = db_column_int(&q, 1);
int idv;
const char *zName;
db_multi_exec(
"INSERT INTO vfile(vid,chnged,deleted,rid,mrid,isexe,islink,pathname)"
" SELECT %d,3,0,rid,mrid,isexe,islink,pathname FROM vfile WHERE id=%d",
vid, idm
);
idv = db_last_insert_rowid();
db_multi_exec("UPDATE fv SET idv=%d WHERE rowid=%d", idv, rowid);
zName = db_column_text(&q, 2);
| > > > > > > | > > | 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 |
" WHERE idp=0 AND idv=0 AND idm>0"
);
while( db_step(&q)==SQLITE_ROW ){
int idm = db_column_int(&q, 0);
int rowid = db_column_int(&q, 1);
int idv;
const char *zName;
char *zFullName;
db_multi_exec(
"INSERT INTO vfile(vid,chnged,deleted,rid,mrid,isexe,islink,pathname)"
" SELECT %d,3,0,rid,mrid,isexe,islink,pathname FROM vfile WHERE id=%d",
vid, idm
);
idv = db_last_insert_rowid();
db_multi_exec("UPDATE fv SET idv=%d WHERE rowid=%d", idv, rowid);
zName = db_column_text(&q, 2);
zFullName = mprintf("%s%s", g.zLocalRoot, zName);
if( file_wd_isfile_or_link(zFullName) ){
fossil_print("ADDED %s (overwrites an unmanaged file)\n", zName);
nOverwrite++;
}else{
fossil_print("ADDED %s\n", zName);
}
fossil_free(zFullName);
if( !nochangeFlag ){
undo_save(zName);
vfile_to_disk(0, idm, 0, 0);
}
}
db_finalize(&q);
|
| ︙ | ︙ | |||
493 494 495 496 497 498 499 |
}
}
db_finalize(&q);
/* Report on conflicts
*/
| > | > > > | < > > | 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 |
}
}
db_finalize(&q);
/* Report on conflicts
*/
if( !nochangeFlag ){
if( nConflict ){
fossil_print("WARNING: %d merge conflicts", nConflict);
}
if( nOverwrite ){
fossil_warning("WARNING: %d unmanaged files were overwritten",
nOverwrite);
}
}
/*
** Clean up the mid and pid VFILE entries. Then commit the changes.
*/
db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid);
if( !pickFlag ){
db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(0,%d)", mid);
}
undo_finish();
db_end_transaction(nochangeFlag);
}
|
Changes to src/merge3.c.
| ︙ | ︙ | |||
25 26 27 28 29 30 31 | #define ISDEBUG 1 #else #define DEBUG(X) #define ISDEBUG 0 #endif /* The minimum of two integers */ | > | > | 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | #define ISDEBUG 1 #else #define DEBUG(X) #define ISDEBUG 0 #endif /* The minimum of two integers */ #ifndef min # define min(A,B) (A<B?A:B) #endif /* ** Compare N lines of text from pV1 and pV2. If the lines ** are the same, return true. Return false if one or more of the N ** lines are different. ** ** The cursors on both pV1 and pV2 is unchanged by this comparison. |
| ︙ | ︙ | |||
169 170 171 172 173 174 175 | ** and pPivot => pV2 (into aC2). Each of the aC1 and aC2 arrays is ** an array of integer triples. Within each triple, the first integer ** is the number of lines of text to copy directly from the pivot, ** the second integer is the number of lines of text to omit from the ** pivot, and the third integer is the number of lines of text that are ** inserted. The edit array ends with a triple of 0,0,0. */ | | | | 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
** and pPivot => pV2 (into aC2). Each of the aC1 and aC2 arrays is
** an array of integer triples. Within each triple, the first integer
** is the number of lines of text to copy directly from the pivot,
** the second integer is the number of lines of text to omit from the
** pivot, and the third integer is the number of lines of text that are
** inserted. The edit array ends with a triple of 0,0,0.
*/
aC1 = text_diff(pPivot, pV1, 0, 0);
aC2 = text_diff(pPivot, pV2, 0, 0);
if( aC1==0 || aC2==0 ){
free(aC1);
free(aC2);
return -1;
}
blob_rewind(pV1); /* Rewind inputs: Needed to reconstruct output */
|
| ︙ | ︙ |
Changes to src/mkindex.c.
| ︙ | ︙ | |||
34 35 36 37 38 39 40 | ** webpage name into pointers to the function. ** ** We also scan for comments lines of this form: ** ** COMMAND: cmdname ** ** These entries build a constant table used to map command names into | | > > | 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | ** webpage name into pointers to the function. ** ** We also scan for comments lines of this form: ** ** COMMAND: cmdname ** ** These entries build a constant table used to map command names into ** functions. If cmdname ends with "*" then the command is a second-tier ** command that is not displayed by the "fossil help" command. The ** final "*" is not considered to be part of the command name. ** ** Comment text following COMMAND: through the end of the comment is ** understood to be help text for the command specified. This help ** text is accumulated and a table containing the text for each command ** is generated. That table is used implement the "fossil help" command ** and the "/help" HTTP method. ** |
| ︙ | ︙ | |||
57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
#include <string.h>
/*
** Each entry looks like this:
*/
typedef struct Entry {
int eType;
char *zFunc;
char *zPath;
char *zHelp;
} Entry;
/*
** Maximum number of entries
| > | 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
#include <string.h>
/*
** Each entry looks like this:
*/
typedef struct Entry {
int eType;
char *zIf;
char *zFunc;
char *zPath;
char *zHelp;
} Entry;
/*
** Maximum number of entries
|
| ︙ | ︙ | |||
83 84 85 86 87 88 89 90 91 92 93 94 95 96 | /* ** Current help message accumulator */ char zHelp[MX_HELP]; int nHelp; /* ** How many entries are used */ int nUsed; int nFixed; /* | > > > > > | 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | /* ** Current help message accumulator */ char zHelp[MX_HELP]; int nHelp; /* ** Most recently encountered #if */ char zIf[200]; /* ** How many entries are used */ int nUsed; int nFixed; /* |
| ︙ | ︙ | |||
131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
if( zLine[i]=='/' ) i++;
for(j=0; zLine[i+j] && !isspace(zLine[i+j]); j++){}
aEntry[nUsed].eType = eType;
aEntry[nUsed].zPath = string_dup(&zLine[i], j);
aEntry[nUsed].zFunc = 0;
nUsed++;
}
/*
** Scan a line for a function that implements a web page or command.
*/
void scan_for_func(char *zLine){
int i,j,k;
char *z;
| > > > > > > > > > > > > > > > > > > > > | 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
if( zLine[i]=='/' ) i++;
for(j=0; zLine[i+j] && !isspace(zLine[i+j]); j++){}
aEntry[nUsed].eType = eType;
aEntry[nUsed].zPath = string_dup(&zLine[i], j);
aEntry[nUsed].zFunc = 0;
nUsed++;
}
/*
** Check to see if the current line is an #if and if it is, add it to
** the zIf[] string. If the current line is an #endif or #else or #elif
** then cancel the current zIf[] string.
*/
void scan_for_if(const char *zLine){
int i;
int len;
if( zLine[0]!='#' ) return;
for(i=1; isspace(zLine[i]); i++){}
if( zLine[i]==0 ) return;
len = strlen(&zLine[i]);
if( memcmp(&zLine[i],"if",2)==0 ){
zIf[0] = '#';
memcpy(&zIf[1], &zLine[i], len+1);
}else if( zLine[i]=='e' ){
zIf[0] = 0;
}
}
/*
** Scan a line for a function that implements a web page or command.
*/
void scan_for_func(char *zLine){
int i,j,k;
char *z;
|
| ︙ | ︙ | |||
175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
for(k=0; k<nHelp && isspace(zHelp[k]); k++){}
if( k<nHelp ){
z = string_dup(&zHelp[k], nHelp-k);
}else{
z = 0;
}
for(k=nFixed; k<nUsed; k++){
aEntry[k].zFunc = string_dup(&zLine[i], j);
aEntry[k].zHelp = z;
}
i+=j;
while( isspace(zLine[i]) ){ i++; }
if( zLine[i]!='(' ) goto page_skip;
nFixed = nUsed;
| > | 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 |
for(k=0; k<nHelp && isspace(zHelp[k]); k++){}
if( k<nHelp ){
z = string_dup(&zHelp[k], nHelp-k);
}else{
z = 0;
}
for(k=nFixed; k<nUsed; k++){
aEntry[k].zIf = zIf[0] ? string_dup(zIf, -1) : 0;
aEntry[k].zFunc = string_dup(&zLine[i], j);
aEntry[k].zHelp = z;
}
i+=j;
while( isspace(zLine[i]) ){ i++; }
if( zLine[i]!='(' ) goto page_skip;
nFixed = nUsed;
|
| ︙ | ︙ | |||
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
*/
void build_table(void){
int i;
int nType0;
qsort(aEntry, nFixed, sizeof(aEntry[0]), e_compare);
for(i=0; i<nFixed; i++){
printf("extern void %s(void);\n", aEntry[i].zFunc);
}
printf(
"typedef struct NameMap NameMap;\n"
"struct NameMap {\n"
" const char *zName;\n"
" void (*xFunc)(void);\n"
"};\n"
"static const NameMap aWebpage[] = {\n"
);
for(i=0; i<nFixed && aEntry[i].eType==0; i++){
| > > > > > > > > > | > | | > > > > > > > > > > > > | > | | > > > > > > > > | 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 |
*/
void build_table(void){
int i;
int nType0;
qsort(aEntry, nFixed, sizeof(aEntry[0]), e_compare);
for(i=0; i<nFixed; i++){
if( aEntry[i].zIf ) printf("%s", aEntry[i].zIf);
printf("extern void %s(void);\n", aEntry[i].zFunc);
if( aEntry[i].zIf ) printf("#endif\n");
}
printf(
"typedef struct NameMap NameMap;\n"
"struct NameMap {\n"
" const char *zName;\n"
" void (*xFunc)(void);\n"
" char cmdFlags;\n"
"};\n"
"#define CMDFLAG_1ST_TIER 0x01\n"
"#define CMDFLAG_2ND_TIER 0x02\n"
"#define CMDFLAG_TEST 0x04\n"
"static const NameMap aWebpage[] = {\n"
);
for(i=0; i<nFixed && aEntry[i].eType==0; i++){
const char *z = aEntry[i].zPath;
int n = strlen(z);
if( aEntry[i].zIf ) printf("%s", aEntry[i].zIf);
printf(" { \"%s\",%*s %s,%*s 1 },\n",
z,
25-n, "",
aEntry[i].zFunc,
(int)(35-strlen(aEntry[i].zFunc)), ""
);
if( aEntry[i].zIf ) printf("#endif\n");
}
printf("};\n");
nType0 = i;
printf(
"static const NameMap aCommand[] = {\n"
);
for(i=nType0; i<nFixed && aEntry[i].eType==1; i++){
const char *z = aEntry[i].zPath;
int n = strlen(z);
int cmdFlags = 0x01;
if( z[n-1]=='*' ){
n--;
cmdFlags = 0x02;
}else if( memcmp(z, "test-", 5)==0 ){
cmdFlags = 0x04;
}
if( aEntry[i].zIf ) printf("%s", aEntry[i].zIf);
printf(" { \"%.*s\",%*s %s,%*s %d },\n",
n, z,
25-n, "",
aEntry[i].zFunc,
(int)(35-strlen(aEntry[i].zFunc)), "",
cmdFlags
);
if( aEntry[i].zIf ) printf("#endif\n");
}
printf("};\n");
for(i=nType0; i<nFixed; i++){
char *z = aEntry[i].zHelp;
if( z && z[0] ){
if( aEntry[i].zIf ) printf("%s", aEntry[i].zIf);
printf("static const char zHelp_%s[] = \n", aEntry[i].zFunc);
printf(" \"");
while( *z ){
if( *z=='\n' ){
printf("\\n\"\n \"");
}else if( *z=='"' ){
printf("\\\"");
}else{
putchar(*z);
}
z++;
}
printf("\";\n");
if( aEntry[i].zIf ) printf("#endif\n");
aEntry[i].zHelp[0] = 0;
}
}
printf(
"static const char * const aCmdHelp[] = {\n"
);
for(i=nType0; i<nFixed; i++){
if( aEntry[i].zIf ) printf("%s", aEntry[i].zIf);
if( aEntry[i].zHelp==0 ){
printf(" 0,\n");
}else{
printf(" zHelp_%s,\n", aEntry[i].zFunc);
}
if( aEntry[i].zIf ) printf("#endif\n");
}
printf("};\n");
}
/*
** Process a single file of input
*/
void process_file(void){
FILE *in = fopen(zFile, "r");
char zLine[2000];
if( in==0 ){
fprintf(stderr,"%s: cannot open\n", zFile);
return;
}
nLine = 0;
while( fgets(zLine, sizeof(zLine), in) ){
nLine++;
scan_for_if(zLine);
scan_for_label("WEBPAGE:",zLine,0);
scan_for_label("COMMAND:",zLine,1);
scan_for_func(zLine);
}
fclose(in);
nUsed = nFixed;
}
|
| ︙ | ︙ |
Changes to src/name.c.
| ︙ | ︙ | |||
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
**
** A user-supplied object name is any unique prefix of a valid UUID but
** not necessarily in canonical form.
*/
#include "config.h"
#include "name.h"
#include <assert.h>
/*
** This routine takes a user-entered UUID which might be in mixed
** case and might only be a prefix of the full UUID and converts it
** into the full-length UUID in canonical form.
**
** If the input is not a UUID or a UUID prefix, then try to resolve
** the name as a tag. If multiple tags match, pick the latest.
** If the input name matches "tag:*" then always resolve as a tag.
**
** If the input is not a tag, then try to match it as an ISO-8601 date
** string YYYY-MM-DD HH:MM:SS and pick the nearest check-in to that date.
** If the input is of the form "date:*" or "localtime:*" or "utc:*" then
** always resolve the name as a date.
**
** Return 0 on success. Return 1 if the name cannot be resolved.
** Return 2 name is ambiguous.
*/
int name_to_uuid(Blob *pName, int iErrPriority, const char *zType){
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > < < < < < | < < | < < < < < < < < < < < < < < < < < < < | < < < < < < < < < < < < < < < < < < < < < < | < < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | < < < < | < < < < < < | | < | < < < < < < < < < < < < < < < < < < < < < < < < | < < < < < < < < < < < < < | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 |
**
** A user-supplied object name is any unique prefix of a valid UUID but
** not necessarily in canonical form.
*/
#include "config.h"
#include "name.h"
#include <assert.h>
/*
** Return TRUE if the string begins with something that looks roughly
** like an ISO date/time string. The SQLite date/time functions will
** have the final say-so about whether or not the date/time string is
** well-formed.
*/
static int is_date(const char *z){
if( !fossil_isdigit(z[0]) ) return 0;
if( !fossil_isdigit(z[1]) ) return 0;
if( !fossil_isdigit(z[2]) ) return 0;
if( !fossil_isdigit(z[3]) ) return 0;
if( z[4]!='-') return 0;
if( !fossil_isdigit(z[5]) ) return 0;
if( !fossil_isdigit(z[6]) ) return 0;
if( z[7]!='-') return 0;
if( !fossil_isdigit(z[8]) ) return 0;
if( !fossil_isdigit(z[9]) ) return 0;
return 1;
}
/*
** Convert a symbolic name into a RID. Acceptable forms:
**
** * SHA1 hash
** * SHA1 hash prefix of at least 4 characters
** * Symbolic Name
** * "tag:" + symbolic name
** * Date or date-time
** * "date:" + Date or date-time
** * symbolic-name ":" date-time
** * "tip"
**
** The following additional forms are available in local checkouts:
**
** * "current"
** * "prev" or "previous"
** * "next"
**
** Return the RID of the matching artifact. Or return 0 if the name does not
** match any known object. Or return -1 if the name is ambiguious.
**
** The zType parameter specifies the type of artifact: ci, t, w, e, g.
** If zType is NULL or "" or "*" then any type of artifact will serve.
** zType is "ci" in most use cases since we are usually searching for
** a check-in.
*/
static int symbolic_name_to_rid(const char *zTag, const char *zType){
int vid;
int rid = 0;
int nTag;
int i;
if( zType==0 || zType[0]==0 ) zType = "*";
if( zTag==0 || zTag[0]==0 ) return 0;
/* special keyword: "tip" */
if( fossil_strcmp(zTag, "tip")==0 && (zType[0]=='*' || zType[0]=='c') ){
rid = db_int(0,
"SELECT objid"
" FROM event"
" WHERE type='ci'"
" ORDER BY event.mtime DESC"
);
if( rid ) return rid;
}
/* special keywords: "prev", "previous", "current", and "next" */
if( g.localOpen && (vid=db_lget_int("checkout",0))!=0 ){
if( fossil_strcmp(zTag, "current")==0 ){
rid = vid;
}else if( fossil_strcmp(zTag, "prev")==0
|| fossil_strcmp(zTag, "previous")==0 ){
rid = db_int(0, "SELECT pid FROM plink WHERE cid=%d AND isprim", vid);
}else if( fossil_strcmp(zTag, "next")==0 ){
rid = db_int(0, "SELECT cid FROM plink WHERE pid=%d"
" ORDER BY isprim DESC, mtime DESC", vid);
}
if( rid ) return rid;
}
/* Date and times */
if( memcmp(zTag, "date:", 5)==0 ){
rid = db_int(0,
"SELECT objid FROM event"
" WHERE mtime<=julianday(%Q) AND type GLOB '%q'"
" ORDER BY mtime DESC LIMIT 1",
&zTag[5], zType);
return rid;
}
if( is_date(zTag) ){
rid = db_int(0,
"SELECT objid FROM event"
" WHERE mtime<=julianday(%Q) AND type GLOB '%q'"
" ORDER BY mtime DESC LIMIT 1",
zTag, zType);
if( rid) return rid;
}
/* Deprecated date & time formats: "local:" + date-time and
** "utc:" + date-time */
if( memcmp(zTag, "local:", 6)==0 ){
rid = db_int(0,
"SELECT objid FROM event"
" WHERE mtime<=julianday(%Q) AND type GLOB '%q'"
" ORDER BY mtime DESC LIMIT 1",
&zTag[6], zType);
return rid;
}
if( memcmp(zTag, "utc:", 4)==0 ){
rid = db_int(0,
"SELECT objid FROM event"
" WHERE mtime<=julianday('%qz') AND type GLOB '%q'"
" ORDER BY mtime DESC LIMIT 1",
&zTag[4], zType);
return rid;
}
/* "tag:" + symbolic-name */
if( memcmp(zTag, "tag:", 4)==0 ){
rid = db_int(0,
"SELECT event.objid"
" FROM tag, tagxref, event"
" WHERE tag.tagname='sym-%q' "
" AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 "
" AND event.objid=tagxref.rid "
" AND event.type GLOB '%q'"
" ORDER BY event.mtime DESC /*sort*/",
&zTag[4], zType
);
return rid;
}
/* symbolic-name ":" date-time */
nTag = strlen(zTag);
for(i=0; i<nTag-10 && zTag[i]!=':'; i++){}
if( zTag[i]==':' && is_date(&zTag[i+1]) ){
char *zDate = mprintf("%s", &zTag[i+1]);
char *zTagBase = mprintf("%.*s", i, zTag);
int nDate = strlen(zDate);
if( sqlite3_strnicmp(&zDate[nDate-3],"utc",3)==0 ){
zDate[nDate-3] = 'z';
zDate[nDate-2] = 0;
}
rid = db_int(0,
"SELECT event.objid"
" FROM tag, tagxref, event"
" WHERE tag.tagname='sym-%q' "
" AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 "
" AND event.objid=tagxref.rid "
" AND event.mtime<=julianday(%Q)"
" AND event.type GLOB '%q'"
" ORDER BY event.mtime DESC /*sort*/ ",
zTagBase, zDate, zType
);
return rid;
}
/* SHA1 hash or prefix */
if( nTag>=4 && nTag<=UUID_SIZE && validate16(zTag, nTag) ){
Stmt q;
char zUuid[UUID_SIZE+1];
memcpy(zUuid, zTag, nTag+1);
canonical16(zUuid, nTag);
rid = 0;
if( zType[0]=='*' ){
db_prepare(&q, "SELECT rid FROM blob WHERE uuid GLOB '%s*'", zUuid);
}else{
db_prepare(&q,
"SELECT blob.rid"
" FROM blob, event"
" WHERE blob.uuid GLOB '%s*'"
" AND event.objid=blob.rid"
" AND event.type GLOB '%q'",
zUuid, zType
);
}
if( db_step(&q)==SQLITE_ROW ){
rid = db_column_int(&q, 0);
if( db_step(&q)==SQLITE_ROW ) rid = -1;
}
db_finalize(&q);
if( rid ) return rid;
}
/* Symbolic name */
rid = db_int(0,
"SELECT event.objid"
" FROM tag, tagxref, event"
" WHERE tag.tagname='sym-%q' "
" AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 "
" AND event.objid=tagxref.rid "
" AND event.type GLOB '%q'"
" ORDER BY event.mtime DESC /*sort*/ ",
zTag, zType
);
if( rid>0 ) return rid;
/* Undocumented: numeric tags get translated directly into the RID */
for(i=0; fossil_isdigit(zTag[i]); i++){}
if( zTag[i]==0 ){
rid = db_int(0,
"SELECT event.objid"
" FROM event"
" WHERE event.objid=%s"
" AND event.type GLOB '%q'", zTag, zType);
}
return rid;
}
/*
** This routine takes a user-entered UUID which might be in mixed
** case and might only be a prefix of the full UUID and converts it
** into the full-length UUID in canonical form.
**
** If the input is not a UUID or a UUID prefix, then try to resolve
** the name as a tag. If multiple tags match, pick the latest.
** If the input name matches "tag:*" then always resolve as a tag.
**
** If the input is not a tag, then try to match it as an ISO-8601 date
** string YYYY-MM-DD HH:MM:SS and pick the nearest check-in to that date.
** If the input is of the form "date:*" or "localtime:*" or "utc:*" then
** always resolve the name as a date.
**
** Return 0 on success. Return 1 if the name cannot be resolved.
** Return 2 name is ambiguous.
*/
int name_to_uuid(Blob *pName, int iErrPriority, const char *zType){
char *zName = blob_str(pName);
int rid = symbolic_name_to_rid(zName, zType);
if( rid<0 ){
fossil_error(iErrPriority, "ambiguous name: %s", zName);
return 2;
}else if( rid==0 ){
fossil_error(iErrPriority, "not found: %s", zName);
return 1;
}else{
blob_reset(pName);
db_blob(pName, "SELECT uuid FROM blob WHERE rid=%d", rid);
return 0;
}
}
/*
** COMMAND: test-name-to-id
**
** Convert a name to a full artifact ID.
*/
|
| ︙ | ︙ | |||
282 283 284 285 286 287 288 |
fossil_print("%s\n", blob_buffer(&name));
}
blob_reset(&name);
}
}
/*
| | | | > > | > > > > < < | < < < | | < | | < | < | < | 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 |
fossil_print("%s\n", blob_buffer(&name));
}
blob_reset(&name);
}
}
/*
** Convert a name to a rid. If the name can be any of the various forms
** accepted:
**
** * SHA1 hash or prefix thereof
** * symbolic name
** * date
** * label:date
** * prev, previous
** * next
** * tip
**
** This routine is used by command-line routines to resolve command-line inputs
** into a rid.
*/
int name_to_typed_rid(const char *zName, const char *zType){
int rid;
if( zName==0 || zName[0]==0 ) return 0;
rid = symbolic_name_to_rid(zName, zType);
if( rid<0 ){
fossil_error(1, "ambiguous name: %s", zName);
return 0;
}else if( rid==0 ){
fossil_error(1, "not found: %s", zName);
return 0;
}else{
return rid;
}
}
int name_to_rid(const char *zName){
return name_to_typed_rid(zName, "*");
}
/*
** WEBPAGE: ambiguous
|
| ︙ | ︙ | |||
360 361 362 363 364 365 366 |
/*
** Convert the name in CGI parameter zParamName into a rid and return that
** rid. If the CGI parameter is missing or is not a valid artifact tag,
** return 0. If the CGI parameter is ambiguous, redirect to a page that
** shows all possibilities and do not return.
*/
int name_to_rid_www(const char *zParamName){
| < > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | < < < | < | < < < < > | > > > | < | | | > > > > > > > | > > | > > > > > > > > > > | > > > > > > > > > > > | 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 |
/*
** Convert the name in CGI parameter zParamName into a rid and return that
** rid. If the CGI parameter is missing or is not a valid artifact tag,
** return 0. If the CGI parameter is ambiguous, redirect to a page that
** shows all possibilities and do not return.
*/
int name_to_rid_www(const char *zParamName){
int rid;
const char *zName = P(zParamName);
#ifdef FOSSIL_ENABLE_JSON
if(!zName && fossil_has_json()){
zName = json_find_option_cstr(zParamName,NULL,NULL);
}
#endif
if( zName==0 || zName[0]==0 ) return 0;
rid = symbolic_name_to_rid(zName, "*");
if( rid<0 ){
cgi_redirectf("%s/ambiguous/%T?src=%t", g.zTop, zName, g.zPath);
rid = 0;
}
return rid;
}
/*
** COMMAND: whatis*
** Usage: %fossil whatis NAME
**
** Resolve the symbol NAME into its canonical 40-character SHA1-hash
** artifact name and provide a description of what role that artifact
** plays.
*/
void whatis_cmd(void){
int rid;
const char *zName;
int fExtra;
db_find_and_open_repository(0,0);
fExtra = find_option("verbose","v",0)!=0;
if( g.argc!=3 ) usage("whatis NAME");
zName = g.argv[2];
rid = symbolic_name_to_rid(zName, 0);
if( rid<0 ){
fossil_print("Ambiguous artifact name prefix: %s\n", zName);
}else if( rid==0 ){
fossil_print("Unknown artifact: %s\n", zName);
}else{
Stmt q;
db_prepare(&q,
"SELECT uuid, size, datetime(mtime, 'localtime'), ipaddr,"
" (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)"
" FROM blob, rcvfrom"
" WHERE rid=%d"
" AND rcvfrom.rcvid=blob.rcvid",
rid);
if( db_step(&q)==SQLITE_ROW ){
const char *zTagList = db_column_text(&q, 4);
if( fExtra ){
fossil_print("artifact: %s (%d)\n", db_column_text(&q,0), rid);
fossil_print("size: %d bytes\n", db_column_int(&q,1));
fossil_print("received: %s from %s\n",
db_column_text(&q, 2),
db_column_text(&q, 3));
}else{
fossil_print("artifact: %s\n", db_column_text(&q,0));
fossil_print("size: %d bytes\n", db_column_int(&q,1));
}
if( zTagList && zTagList[0] ){
fossil_print("tags: %s\n", zTagList);
}
}
db_finalize(&q);
db_prepare(&q,
"SELECT type, datetime(mtime,'localtime'),"
" coalesce(euser,user), coalesce(ecomment,comment)"
" FROM event WHERE objid=%d", rid);
if( db_step(&q)==SQLITE_ROW ){
const char *zType;
switch( db_column_text(&q,0)[0] ){
case 'c': zType = "Check-in"; break;
case 'w': zType = "Wiki-edit"; break;
case 'e': zType = "Event"; break;
case 't': zType = "Ticket-change"; break;
case 'g': zType = "Tag-change"; break;
default: zType = "Unknown"; break;
}
fossil_print("type: %s by %s on %s\n", zType, db_column_text(&q,2),
db_column_text(&q, 1));
fossil_print("comment: ");
comment_print(db_column_text(&q,3), 10, 78);
}
db_finalize(&q);
db_prepare(&q,
"SELECT filename.name, blob.uuid, datetime(event.mtime,'localtime'),"
" coalesce(euser,user), coalesce(ecomment,comment)"
" FROM mlink, filename, blob, event"
" WHERE mlink.fid=%d"
" AND filename.fnid=mlink.fnid"
" AND event.objid=mlink.mid"
" AND blob.rid=mlink.mid"
" ORDER BY event.mtime DESC /*sort*/",
rid);
while( db_step(&q)==SQLITE_ROW ){
fossil_print("file: %s\n", db_column_text(&q,0));
fossil_print(" part of [%.10s] by %s on %s\n",
db_column_text(&q, 1),
db_column_text(&q, 3),
db_column_text(&q, 2));
fossil_print(" ");
comment_print(db_column_text(&q,4), 10, 78);
}
db_finalize(&q);
}
}
|
Changes to src/path.c.
| ︙ | ︙ | |||
93 94 95 96 97 98 99 | bag_clear(&path.seen); memset(&path, 0, sizeof(&path)); } /* ** Construct the path from path.pStart to path.pEnd in the u.pTo fields. */ | | > | 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
bag_clear(&path.seen);
memset(&path, 0, sizeof(&path));
}
/*
** Construct the path from path.pStart to path.pEnd in the u.pTo fields.
*/
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 );
}
|
| ︙ | ︙ | |||
130 131 132 133 134 135 136 |
path_reset();
path.pStart = path_new_node(iFrom, 0, 0);
if( iTo==iFrom ){
path.pEnd = path.pStart;
return path.pStart;
}
| | > > > > | 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
path_reset();
path.pStart = path_new_node(iFrom, 0, 0);
if( iTo==iFrom ){
path.pEnd = path.pStart;
return path.pStart;
}
if( oneWayOnly && directOnly ){
db_prepare(&s,
"SELECT cid, 1 FROM plink WHERE pid=:pid AND isprim"
);
}else if( oneWayOnly ){
db_prepare(&s,
"SELECT cid, 1 FROM plink WHERE pid=:pid "
);
}else if( directOnly ){
db_prepare(&s,
"SELECT cid, 1 FROM plink WHERE pid=:pid AND isprim "
"UNION ALL "
|
| ︙ | ︙ |
Changes to src/printf.c.
| ︙ | ︙ | |||
543 544 545 546 547 548 549 |
break;
case etPERCENT:
buf[0] = '%';
bufpt = buf;
length = 1;
break;
case etCHARX:
| | | 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 |
break;
case etPERCENT:
buf[0] = '%';
bufpt = buf;
length = 1;
break;
case etCHARX:
c = buf[0] = va_arg(ap,int);
if( precision>=0 ){
for(idx=1; idx<precision; idx++) buf[idx] = c;
length = precision;
}else{
length =1;
}
bufpt = buf;
|
| ︙ | ︙ | |||
820 821 822 823 824 825 826 827 828 829 830 831 832 833 | assert( toStdErr==0 || toStdErr==1 ); if( istty[toStdErr] ) z = zToFree = fossil_utf8_to_console(z); fwrite(z, 1, strlen(z), toStdErr ? stderr : stdout); free(zToFree); #else fwrite(z, 1, strlen(z), toStdErr ? stderr : stdout); #endif } /* ** Write output for user consumption. If g.cgiOutput is enabled, then ** send the output as part of the CGI reply. If g.cgiOutput is false, ** then write on standard output. */ | > | 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 | assert( toStdErr==0 || toStdErr==1 ); if( istty[toStdErr] ) z = zToFree = fossil_utf8_to_console(z); fwrite(z, 1, strlen(z), toStdErr ? stderr : stdout); free(zToFree); #else fwrite(z, 1, strlen(z), toStdErr ? stderr : stdout); #endif fflush(toStdErr ? stderr : stdout); } /* ** Write output for user consumption. If g.cgiOutput is enabled, then ** send the output as part of the CGI reply. If g.cgiOutput is false, ** then write on standard output. */ |
| ︙ | ︙ |
Changes to src/rebuild.c.
| ︙ | ︙ | |||
368 369 370 371 372 373 374 |
db_multi_exec(
"DELETE FROM unclustered"
" WHERE rid IN (SELECT rid FROM shun JOIN blob USING(uuid))"
);
db_multi_exec(
"DELETE FROM config WHERE name IN ('remote-code', 'remote-maxid')"
);
| > > > > | | 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 |
db_multi_exec(
"DELETE FROM unclustered"
" WHERE rid IN (SELECT rid FROM shun JOIN blob USING(uuid))"
);
db_multi_exec(
"DELETE FROM config WHERE name IN ('remote-code', 'remote-maxid')"
);
/* The following should be count(*) instead of max(rid). max(rid) is
** an adequate approximation, however, and is much faster for large
** repositories. */
totalSize = db_int(0, "SELECT max(rid) FROM blob");
incrSize = totalSize/100;
totalSize += incrSize*2;
db_prepare(&s,
"SELECT rid, size FROM blob /*scan*/"
" WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
" AND NOT EXISTS(SELECT 1 FROM delta WHERE rid=blob.rid)"
);
|
| ︙ | ︙ | |||
481 482 483 484 485 486 487 | } db_finalize(&q); db_end_transaction(0); } /* | | > > > | 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 |
}
db_finalize(&q);
db_end_transaction(0);
}
/*
** COMMAND: rebuild
**
** Usage: %fossil rebuild ?REPOSITORY? ?OPTIONS?
**
** Reconstruct the named repository database from the core
** records. Run this command after updating the fossil
** executable in a way that changes the database schema.
**
** Options:
** --cluster Compute clusters for unclustered artifacts
** --compress Strive to make the database as small as possible
** --force Force the rebuild to complete even if errors are seen
** --noverify Skip the verification of changes to the BLOB table
** --pagesize N Set the database pagesize to N. (512..65536 and power of 2)
** --randomize Scan artifacts in a random order
** --vacuum Run VACUUM on the database after rebuilding
** --wal Set Write-Ahead-Log journalling mode on the database
** --stats Show artifact statistics after rebuilding
**
** See also: deconstruct, reconstruct
*/
void rebuild_database(void){
int forceFlag;
int randomizeFlag;
int errCnt;
int omitVerify;
int doClustering;
const char *zPagesize;
int newPagesize = 0;
int activateWal;
int runVacuum;
int runCompress;
int showStats;
omitVerify = find_option("noverify",0,0)!=0;
forceFlag = find_option("force","f",0)!=0;
randomizeFlag = find_option("randomize", 0, 0)!=0;
doClustering = find_option("cluster", 0, 0)!=0;
runVacuum = find_option("vacuum",0,0)!=0;
runCompress = find_option("compress",0,0)!=0;
zPagesize = find_option("pagesize",0,1);
showStats = find_option("stats",0,0)!=0;
if( zPagesize ){
newPagesize = atoi(zPagesize);
if( newPagesize<512 || newPagesize>65536
|| (newPagesize&(newPagesize-1))!=0
){
fossil_fatal("page size must be a power of two between 512 and 65536");
}
|
| ︙ | ︙ | |||
577 578 579 580 581 582 583 584 585 586 587 588 589 590 |
db_multi_exec("VACUUM");
fossil_print("done\n");
}
if( activateWal ){
db_multi_exec("PRAGMA journal_mode=WAL;");
}
}
}
/*
** COMMAND: test-detach ?REPOSITORY?
**
** Change the project-code and make other changes in order to prevent
** the repository from ever again pushing or pulling to other
| > > > > > > > > > > > > > > > > > > > > | 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 |
db_multi_exec("VACUUM");
fossil_print("done\n");
}
if( activateWal ){
db_multi_exec("PRAGMA journal_mode=WAL;");
}
}
if( showStats ){
static struct { int idx; const char *zLabel; } aStat[] = {
{ CFTYPE_ANY, "Artifacts:" },
{ CFTYPE_MANIFEST, "Manifests:" },
{ CFTYPE_CLUSTER, "Clusters:" },
{ CFTYPE_CONTROL, "Tags:" },
{ CFTYPE_WIKI, "Wikis:" },
{ CFTYPE_TICKET, "Tickets:" },
{ CFTYPE_ATTACHMENT,"Attachments:" },
{ CFTYPE_EVENT, "Events:" },
};
int i;
int subtotal = 0;
for(i=0; i<count(aStat); i++){
int k = aStat[i].idx;
fossil_print("%-15s %6d\n", aStat[i].zLabel, g.parseCnt[k]);
if( k>0 ) subtotal += g.parseCnt[k];
}
fossil_print("%-15s %6d\n", "Other:", g.parseCnt[CFTYPE_ANY] - subtotal);
}
}
/*
** COMMAND: test-detach ?REPOSITORY?
**
** Change the project-code and make other changes in order to prevent
** the repository from ever again pushing or pulling to other
|
| ︙ | ︙ | |||
691 692 693 694 695 696 697 |
fossil_print(" %3d %s\n", db_column_int(&q,0), db_column_text(&q,1));
}
db_finalize(&q);
}
}
/*
| | | 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 |
fossil_print(" %3d %s\n", db_column_int(&q,0), db_column_text(&q,1));
}
db_finalize(&q);
}
}
/*
** COMMAND: scrub*
** %fossil scrub ?OPTIONS? ?REPOSITORY?
**
** The command removes sensitive information (such as passwords) from a
** repository so that the respository can be sent to an untrusted reader.
**
** By default, only passwords are removed. However, if the --verily option
** is added, then private branches, concealed email addresses, IP
|
| ︙ | ︙ | |||
742 743 744 745 746 747 748 |
if( blob_str(&ans)[0]!='y' ){
fossil_exit(1);
}
}
db_begin_transaction();
if( privateOnly || bVerily ){
bNeedRebuild = db_exists("SELECT 1 FROM private");
| < < < | < > | 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 |
if( blob_str(&ans)[0]!='y' ){
fossil_exit(1);
}
}
db_begin_transaction();
if( privateOnly || bVerily ){
bNeedRebuild = db_exists("SELECT 1 FROM private");
delete_private_content();
}
if( !privateOnly ){
db_multi_exec(
"UPDATE user SET pw='';"
"DELETE FROM config WHERE name GLOB 'last-sync-*';"
"DELETE FROM config WHERE name GLOB 'peer-*';"
"DELETE FROM config WHERE name GLOB 'login-group-*';"
"DELETE FROM config WHERE name GLOB 'skin:*';"
"DELETE FROM config WHERE name GLOB 'subrepo:*';"
);
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 ){
db_end_transaction(0);
db_multi_exec("VACUUM;");
|
| ︙ | ︙ | |||
824 825 826 827 828 829 830 |
fossil_panic("encountered error %d while trying to open \"%s\".",
errno, g.argv[3]);
}
fossil_mbcs_free(zMbcsPath);
}
/*
| | | 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 |
fossil_panic("encountered error %d while trying to open \"%s\".",
errno, g.argv[3]);
}
fossil_mbcs_free(zMbcsPath);
}
/*
** COMMAND: reconstruct*
**
** Usage: %fossil reconstruct FILENAME DIRECTORY
**
** This command studies the artifacts (files) in DIRECTORY and
** reconstructs the fossil record from them. It places the new
** fossil repository in FILENAME. Subdirectories are read, files
** with leading '.' in the filename are ignored.
|
| ︙ | ︙ | |||
885 886 887 888 889 890 891 |
fossil_print("project-id: %s\n", db_get("project-code", 0));
fossil_print("server-id: %s\n", db_get("server-code", 0));
zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
fossil_print("admin-user: %s (initial password is \"%s\")\n", g.zLogin, zPassword);
}
/*
| | | 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 |
fossil_print("project-id: %s\n", db_get("project-code", 0));
fossil_print("server-id: %s\n", db_get("server-code", 0));
zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
fossil_print("admin-user: %s (initial password is \"%s\")\n", g.zLogin, zPassword);
}
/*
** COMMAND: deconstruct*
**
** Usage %fossil deconstruct ?OPTIONS? DESTINATION
**
**
** This command exports all artifacts of a given repository and
** writes all artifacts to the file system. The DESTINATION directory
** will be populated with subdirectories AA and files AA/BBBBBBBBB.., where
|
| ︙ | ︙ |
Changes to src/report.c.
| ︙ | ︙ | |||
17 18 19 20 21 22 23 24 25 26 27 28 29 30 | ** ** Code to generate the ticket listings */ #include "config.h" #include <time.h> #include "report.h" #include <assert.h> /* Forward references to static routines */ static void report_format_hints(void); /* ** WEBPAGE: /reportlist */ | > > > | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | ** ** Code to generate the ticket listings */ #include "config.h" #include <time.h> #include "report.h" #include <assert.h> #ifdef FOSSIL_ENABLE_JSON # include "cson_amalgamation.h" #endif /* Forward references to static routines */ static void report_format_hints(void); /* ** WEBPAGE: /reportlist */ |
| ︙ | ︙ | |||
628 629 630 631 632 633 634 |
int nArg, /* Number of columns in this result row */
char **azArg, /* Text of data in all columns */
char **azName /* Names of the columns */
){
struct GenerateHTML *pState = (struct GenerateHTML*)pUser;
int i;
const char *zTid; /* Ticket UUID. (value of column named '#') */
| < < < < < < | 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 |
int nArg, /* Number of columns in this result row */
char **azArg, /* Text of data in all columns */
char **azName /* Names of the columns */
){
struct GenerateHTML *pState = (struct GenerateHTML*)pUser;
int i;
const char *zTid; /* Ticket UUID. (value of column named '#') */
char *zBg = 0; /* Use this background color */
/* Do initialization
*/
if( pState->nCount==0 ){
/* Turn off the authorizer. It is no longer doing anything since the
** query has already been prepared.
*/
|
| ︙ | ︙ | |||
717 718 719 720 721 722 723 | /* Output the data for this entry from the database */ zBg = pState->iBg>=0 ? azArg[pState->iBg] : 0; if( zBg==0 ) zBg = "white"; @ <tr style="background-color:%h(zBg)"> zTid = 0; | < | 714 715 716 717 718 719 720 721 722 723 724 725 726 727 |
/* Output the data for this entry from the database
*/
zBg = pState->iBg>=0 ? azArg[pState->iBg] : 0;
if( zBg==0 ) zBg = "white";
@ <tr style="background-color:%h(zBg)">
zTid = 0;
for(i=0; i<nArg; i++){
char *zData;
if( i==pState->iBg ) continue;
zData = azArg[i];
if( zData==0 ) zData = "";
if( pState->iNewRow>=0 && i>=pState->iNewRow ){
if( zTid && g.perm.Write ){
|
| ︙ | ︙ | |||
1106 1107 1108 1109 1110 1111 1112 |
const char *zRep,
const char *zSepIn,
const char *zFilter,
tTktShowEncoding enc
){
Stmt q;
char *zSql;
| < < < < < < | | < | < < > | 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 |
const char *zRep,
const char *zSepIn,
const char *zFilter,
tTktShowEncoding enc
){
Stmt q;
char *zSql;
char *zErr1 = 0;
char *zErr2 = 0;
int count = 0;
int rn;
if (!zRep || !strcmp(zRep,zFullTicketRptRn) || !strcmp(zRep,zFullTicketRptTitle) ){
zSql = "SELECT * FROM ticket";
}else{
rn = atoi(zRep);
if( rn ){
db_prepare(&q,
"SELECT sqlcode FROM reportfmt WHERE rn=%d", rn);
}else{
db_prepare(&q,
"SELECT sqlcode FROM reportfmt WHERE title=%Q", zRep);
}
if( db_step(&q)!=SQLITE_ROW ){
db_finalize(&q);
rpt_list_reports();
fossil_fatal("unknown report format(%s)!",zRep);
}
zSql = db_column_malloc(&q, 0);
db_finalize(&q);
}
if( zFilter ){
zSql = mprintf("SELECT * FROM (%s) WHERE %s",zSql,zFilter);
}
count = 0;
tktEncode = enc;
zSep = zSepIn;
report_restrict_sql(&zErr1);
sqlite3_exec_readonly(g.db, zSql, output_separated_file, &count, &zErr2);
report_unrestrict_sql();
if( zFilter ){
free(zSql);
}
}
|
Changes to src/schema.c.
| ︙ | ︙ | |||
249 250 251 252 253 254 255 | @ -- one entry in the following table for each leaf. @ -- @ CREATE TABLE leaf(rid INTEGER PRIMARY KEY); @ @ -- Events used to generate a timeline @ -- @ CREATE TABLE event( | | | 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 | @ -- one entry in the following table for each leaf. @ -- @ CREATE TABLE leaf(rid INTEGER PRIMARY KEY); @ @ -- Events used to generate a timeline @ -- @ CREATE TABLE event( @ type TEXT, -- Type of event: 'ci', 'w', 'e', 't', 'g' @ mtime DATETIME, -- Time of occurrence. Julian day. @ objid INTEGER PRIMARY KEY, -- Associated record ID @ tagid INTEGER, -- Associated ticket or wiki name tag @ uid INTEGER REFERENCES user, -- User who caused the event @ bgcolor TEXT, -- Color set by 'bgcolor' property @ euser TEXT, -- User set by 'user' property @ user TEXT, -- Name of the user |
| ︙ | ︙ |
Changes to src/search.c.
| ︙ | ︙ | |||
163 164 165 166 167 168 169 |
sqlite3_create_function(g.db, "score", 1, SQLITE_UTF8, p,
search_score_sqlfunc, 0, 0);
}
/*
** Testing the search function.
**
| | | 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
sqlite3_create_function(g.db, "score", 1, SQLITE_UTF8, p,
search_score_sqlfunc, 0, 0);
}
/*
** Testing the search function.
**
** COMMAND: search*
** %fossil search pattern...
**
** Search for timeline entries matching the pattern.
*/
void search_cmd(void){
Search *p;
Blob pattern;
|
| ︙ | ︙ |
Changes to src/setup.c.
| ︙ | ︙ | |||
71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
setup_menu_entry("Timeline", "setup_timeline",
"Timeline display preferences");
setup_menu_entry("Login-Group", "setup_login_group",
"Manage single sign-on between this repository and others"
" on the same server");
setup_menu_entry("Tickets", "tktsetup",
"Configure the trouble-ticketing system for this repository");
setup_menu_entry("Skins", "setup_skin",
"Select from a menu of prepackaged \"skins\" for the web interface");
setup_menu_entry("CSS", "setup_editcss",
"Edit the Cascading Style Sheet used by all pages of this repository");
setup_menu_entry("Header", "setup_header",
"Edit HTML text inserted at the top of every page");
setup_menu_entry("Footer", "setup_footer",
| > > | 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
setup_menu_entry("Timeline", "setup_timeline",
"Timeline display preferences");
setup_menu_entry("Login-Group", "setup_login_group",
"Manage single sign-on between this repository and others"
" on the same server");
setup_menu_entry("Tickets", "tktsetup",
"Configure the trouble-ticketing system for this repository");
setup_menu_entry("Transfers", "xfersetup",
"Configure the transfer system for this repository");
setup_menu_entry("Skins", "setup_skin",
"Select from a menu of prepackaged \"skins\" for the web interface");
setup_menu_entry("CSS", "setup_editcss",
"Edit the Cascading Style Sheet used by all pages of this repository");
setup_menu_entry("Header", "setup_header",
"Edit HTML text inserted at the top of every page");
setup_menu_entry("Footer", "setup_footer",
|
| ︙ | ︙ | |||
134 135 136 137 138 139 140 |
}
@ %h(db_column_text(&s,1))
if( g.perm.Admin ){
@ </a>
}
@ </td>
@ <td class="usetupListCap" style="text-align: center;padding-right: 15px;">%s(zCap)</td>
| | | 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
}
@ %h(db_column_text(&s,1))
if( g.perm.Admin ){
@ </a>
}
@ </td>
@ <td class="usetupListCap" style="text-align: center;padding-right: 15px;">%s(zCap)</td>
@ <td class="usetupListCon" style="text-align: left;">%h(db_column_text(&s,3))</td>
@ </tr>
}
@ </table>
@ </td><td class="usetupColumnLayout">
@ <span class="note">Notes:</span>
@ <ol>
@ <li><p>The permission flags are as follows:</p>
|
| ︙ | ︙ | |||
248 249 250 251 252 253 254 |
void user_edit(void){
const char *zId, *zLogin, *zInfo, *zCap, *zPw;
char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap;
char *oak, *oad, *oac, *oaf, *oam, *oah, *oag, *oae;
char *oat, *oau, *oav, *oab, *oax, *oaz;
const char *zGroup;
const char *zOldLogin;
| | | 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 |
void user_edit(void){
const char *zId, *zLogin, *zInfo, *zCap, *zPw;
char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap;
char *oak, *oad, *oac, *oaf, *oam, *oah, *oag, *oae;
char *oat, *oau, *oav, *oab, *oax, *oaz;
const char *zGroup;
const char *zOldLogin;
char *inherit[128];
int doWrite;
int uid;
int higherUser = 0; /* True if user being edited is SETUP and the */
/* user doing the editing is ADMIN. Disallow editing */
/* Must have ADMIN privleges to access this page
*/
|
| ︙ | ︙ | |||
846 847 848 849 850 851 852 853 854 855 856 857 858 859 |
@ <li> The server is started using either of the
@ <a href="%s(g.zTop)/help/server">fossil server</a> or
@ <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("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>
| > > > > > > > > > | > > > > > > > > > > > > > > > > > > | 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 |
@ <li> The server is started using either of the
@ <a href="%s(g.zTop)/help/server">fossil server</a> or
@ <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("IP address turns used in login cookie", 3, "ip-prefix-terms", "ipt",
"2");
@ <p>The number of octets of of the IP address used in the login cookie. Set to
@ zero to omit the IP address from the login cookie. A value of 2 is recommended.
@ </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>
@ <hr />
entry_attribute("Download packet limit", 10, "max-download", "mxdwn",
"5000000");
@ <p>Fossil tries to limit out-bound sync, clone, and pull packets
@ to this many bytes, uncompressed. If the client requires more data
@ than this, then the client will issue multiple HTTP requests.
@ Values below 1 million are not recommended. 5 million is a
@ reasonable number.</p>
@ <hr />
onoff_attribute("Enable hyperlinks for \"nobody\" based on User-Agent",
"auto-enable-hyperlinks", "autohyperlink", 1);
@ <p>Enable hyperlinks (the equivalent of the "h" permission) for all users
@ including user "nobody", as long as the User-Agent string in the HTTP header
@ indicates that the request is coming from an actual human being and not a
@ a robot or script. Note: Bots can specify whatever User-Agent string they
@ that want. So a bot that wants to impersonate a human can easily do so.
@ Hence, this technique does not necessarily exclude malicious bots.
@ </p>
@ <hr />
onoff_attribute("Allow users to register themselves",
"self-register", "selfregister", 0);
@ <p>Allow users to register themselves through the HTTP UI.
@ The registration form always requires filling in a CAPTCHA
@ (<em>auto-captcha</em> setting is ignored). Still, bear in mind that anyone
|
| ︙ | ︙ |
Changes to src/sha1.c.
| ︙ | ︙ | |||
80 81 82 83 84 85 86 |
#define b qq[1]
#define c qq[2]
#define d qq[3]
#define e qq[4]
void SHA1Transform(unsigned int state[5], const unsigned char buffer[64])
{
| | | 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
#define b qq[1]
#define c qq[2]
#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; */
static int one = 1;
unsigned int block[16];
memcpy(block, buffer, 64);
memcpy(qq,state,5*sizeof(unsigned int));
/* Copy context->state[] to working vars */
/*
|
| ︙ | ︙ | |||
435 436 437 438 439 440 441 |
zProjid = 0;
}
sqlite3_result_text(context, sha1_shared_secret(zPw, zLogin, zProjid), -1,
fossil_free);
}
/*
| | | 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 |
zProjid = 0;
}
sqlite3_result_text(context, sha1_shared_secret(zPw, zLogin, zProjid), -1,
fossil_free);
}
/*
** COMMAND: sha1sum*
** %fossil sha1sum FILE...
**
** Compute an SHA1 checksum of all files named on the command-line.
** If an file is named "-" then take its content from standard input.
*/
void sha1sum_test(void){
int i;
|
| ︙ | ︙ |
Changes to src/shell.c.
| ︙ | ︙ | |||
8 9 10 11 12 13 14 | ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** This file contains code to implement the "sqlite" command line ** utility for accessing SQLite databases. */ | | > > > > > > > > > > > | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** This file contains code to implement the "sqlite" command line ** utility for accessing SQLite databases. */ #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" #include <ctype.h> #include <stdarg.h> |
| ︙ | ︙ | |||
42 43 44 45 46 47 48 | # include <editline/editline.h> #endif #if defined(HAVE_READLINE) && HAVE_READLINE==1 # include <readline/readline.h> # include <readline/history.h> #endif #if !defined(HAVE_EDITLINE) && (!defined(HAVE_READLINE) || HAVE_READLINE!=1) | | | > > > > > | 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | # include <editline/editline.h> #endif #if defined(HAVE_READLINE) && HAVE_READLINE==1 # include <readline/readline.h> # include <readline/history.h> #endif #if !defined(HAVE_EDITLINE) && (!defined(HAVE_READLINE) || HAVE_READLINE!=1) # define readline(p) local_getline(p,stdin,0) # define add_history(X) # define read_history(X) # define write_history(X) # define stifle_history(X) #endif #if defined(_WIN32) || defined(WIN32) # include <io.h> #define isatty(h) _isatty(h) #define access(f,m) _access((f),(m)) #else /* Make sure isatty() has a prototype. */ 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 * overridden with the -batch command line option. */ #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> /* Saved resource information for the beginning of an operation */ static struct rusage sBegin; |
| ︙ | ︙ | |||
261 262 263 264 265 266 267 |
/*
** Determines if a string is a number of not.
*/
static int isNumber(const char *z, int *realnum){
if( *z=='-' || *z=='+' ) z++;
| | | | | | | | 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 |
/*
** Determines if a string is a number of not.
*/
static int isNumber(const char *z, int *realnum){
if( *z=='-' || *z=='+' ) z++;
if( !IsDigit(*z) ){
return 0;
}
z++;
if( realnum ) *realnum = 0;
while( IsDigit(*z) ){ z++; }
if( *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( realnum ) *realnum = 1;
}
return *z==0;
}
/*
** A global char* and an SQL function to access its current value
|
| ︙ | ︙ | |||
314 315 316 317 318 319 320 | ** the text in memory obtained from malloc() and returns a pointer ** to the text. NULL is returned at end of file, or if malloc() ** fails. ** ** The interface is like "readline" but no command-line editing ** is done. */ | | | < | < | > > > | | | | 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 |
** the text in memory obtained from malloc() and returns a pointer
** to the text. NULL is returned at end of file, or if malloc()
** fails.
**
** The interface is like "readline" but no command-line editing
** is done.
*/
static char *local_getline(char *zPrompt, FILE *in, int csvFlag){
char *zLine;
int nLine;
int n;
int inQuote = 0;
if( zPrompt && *zPrompt ){
printf("%s",zPrompt);
fflush(stdout);
}
nLine = 100;
zLine = malloc( nLine );
if( zLine==0 ) return 0;
n = 0;
while( 1 ){
if( n+100>nLine ){
nLine = nLine*2 + 100;
zLine = realloc(zLine, nLine);
if( zLine==0 ) return 0;
}
if( fgets(&zLine[n], nLine - n, in)==0 ){
if( n==0 ){
free(zLine);
return 0;
}
zLine[n] = 0;
break;
}
while( zLine[n] ){
if( zLine[n]=='"' ) inQuote = !inQuote;
n++;
}
if( n>0 && zLine[n-1]=='\n' && (!inQuote || !csvFlag) ){
n--;
if( n>0 && zLine[n-1]=='\r' ) n--;
zLine[n] = 0;
break;
}
}
zLine = realloc( zLine, n+1 );
return zLine;
}
/*
** Retrieve a single line of input text.
**
** zPrior is a string of prior text retrieved. If not the empty
** string, then issue a continuation prompt.
*/
static char *one_input_line(const char *zPrior, FILE *in){
char *zPrompt;
char *zResult;
if( in!=0 ){
return local_getline(0, in, 0);
}
if( zPrior && zPrior[0] ){
zPrompt = continuePrompt;
}else{
zPrompt = mainPrompt;
}
zResult = readline(zPrompt);
|
| ︙ | ︙ | |||
398 399 400 401 402 403 404 405 406 407 408 409 410 411 |
*/
struct callback_data {
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 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 */
int colWidth[100]; /* Requested width of each column when in column mode*/
int actualWidth[100]; /* Actual width of each column */
| > | 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 |
*/
struct callback_data {
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 */
int colWidth[100]; /* Requested width of each column when in column mode*/
int actualWidth[100]; /* Actual width of each column */
|
| ︙ | ︙ | |||
596 597 598 599 600 601 602 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }; /* ** Output a single term of CSV. Actually, p->separator is used for ** the separator, which may or may not be a comma. p->nullvalue is | | < | 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 |
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
};
/*
** Output a single term of CSV. Actually, p->separator is used for
** the separator, which may or may not be a comma. p->nullvalue is
** the null value. Strings are quoted if necessary.
*/
static void output_csv(struct callback_data *p, const char *z, int bSep){
FILE *out = p->out;
if( z==0 ){
fprintf(out,"%s",p->nullvalue);
}else{
int i;
|
| ︙ | ︙ | |||
916 917 918 919 920 921 922 | } return zIn; } /* | | > | < | > > > < < > | | > > > | > > > | | > > > > > > > > > > > | > > > > > | 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 |
}
return zIn;
}
/*
** Execute a query statement that will generate SQL output. Print
** the result columns, comma-separated, on a line and then add a
** semicolon terminator to the end of that line.
**
** If the number of columns is 1 and that column contains text "--"
** then write the semicolon on a separate line. That way, if a
** "--" comment occurs at the end of the statement, the comment
** won't consume the semicolon terminator.
*/
static int run_table_dump_query(
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;
int nResult;
int i;
const char *z;
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);
nResult = sqlite3_column_count(pSelect);
while( rc==SQLITE_ROW ){
if( zFirstRow ){
fprintf(p->out, "%s", zFirstRow);
zFirstRow = 0;
}
z = (const char*)sqlite3_column_text(pSelect, 0);
fprintf(p->out, "%s", z);
for(i=1; i<nResult; i++){
fprintf(p->out, ",%s", sqlite3_column_text(pSelect, i));
}
if( z==0 ) z = "";
while( z[0] && (z[0]!='-' || z[1]!='-') ) z++;
if( z[0] ){
fprintf(p->out, "\n;\n");
}else{
fprintf(p->out, ";\n");
}
rc = sqlite3_step(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.
*/
static char *save_err_msg(
sqlite3 *db /* Database to query */
|
| ︙ | ︙ | |||
1025 1026 1027 1028 1029 1030 1031 |
fprintf(pArg->out, "Successful lookaside attempts: %d\n", iHiwtr);
sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE, &iCur, &iHiwtr, bReset);
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);
| | > > > > > | 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 |
fprintf(pArg->out, "Successful lookaside attempts: %d\n", iHiwtr);
sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE, &iCur, &iHiwtr, bReset);
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); 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);
fprintf(pArg->out, "Statement Heap/Lookaside Usage: %d bytes\n", iCur);
}
|
| ︙ | ︙ | |||
1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 |
int (*xCallback)(void*,int,char**,char**,int*), /* Callback function */
/* (not the same as sqlite3_exec) */
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 */
const char *zLeftover; /* Tail of unprocessed SQL */
if( pzErrMsg ){
*pzErrMsg = NULL;
}
while( zSql[0] && (SQLITE_OK == rc) ){
rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover);
if( SQLITE_OK != rc ){
if( pzErrMsg ){
*pzErrMsg = save_err_msg(db);
}
}else{
if( !pStmt ){
/* this happens for a comment or white-space */
zSql = zLeftover;
| > | > > > > > > > > > | 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 |
int (*xCallback)(void*,int,char**,char**,int*), /* Callback function */
/* (not the same as sqlite3_exec) */
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;
}
while( zSql[0] && (SQLITE_OK == rc) ){
rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover);
if( SQLITE_OK != rc ){
if( pzErrMsg ){
*pzErrMsg = save_err_msg(db);
}
}else{
if( !pStmt ){
/* this happens for a comment or white-space */
zSql = zLeftover;
while( IsSpace(zSql[0]) ) zSql++;
continue;
}
/* save off the prepared statment handle and reset row count */
if( pArg ){
pArg->pStmt = pStmt;
pArg->cnt = 0;
}
/* echo the sql statement if echo on */
if( pArg && pArg->echoOn ){
const char *zStmtSql = sqlite3_sql(pStmt);
fprintf(pArg->out, "%s\n", zStmtSql ? zStmtSql : zSql);
}
/* Output TESTCTRL_EXPLAIN text of requested */
if( pArg && pArg->mode==MODE_Explain ){
const char *zExplain = 0;
sqlite3_test_control(SQLITE_TESTCTRL_EXPLAIN_STMT, pStmt, &zExplain);
if( zExplain && zExplain[0] ){
fprintf(pArg->out, "%s", zExplain);
}
}
/* perform the first step. this will tell us if we
** have a result set or not and how wide it is.
*/
rc = sqlite3_step(pStmt);
/* if we have a result set... */
if( SQLITE_ROW == rc ){
|
| ︙ | ︙ | |||
1158 1159 1160 1161 1162 1163 1164 |
if( pArg && pArg->statsOn ){
display_stats(db, pArg, 0);
}
/* 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. */
| | > | | 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 |
if( pArg && pArg->statsOn ){
display_stats(db, pArg, 0);
}
/* 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. */
rc2 = sqlite3_finalize(pStmt);
if( rc!=SQLITE_NOMEM ) rc = rc2;
if( rc==SQLITE_OK ){
zSql = zLeftover;
while( IsSpace(zSql[0]) ) zSql++;
}else if( pzErrMsg ){
*pzErrMsg = save_err_msg(db);
}
/* clear saved stmt handle */
if( pArg ){
pArg->pStmt = NULL;
|
| ︙ | ︙ | |||
1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 |
if( strcmp(zType, "table")==0 ){
sqlite3_stmt *pTableInfo = 0;
char *zSelect = 0;
char *zTableInfo = 0;
char *zTmp = 0;
int nRow = 0;
zTableInfo = appendText(zTableInfo, "PRAGMA table_info(", 0);
zTableInfo = appendText(zTableInfo, zTable, '"');
zTableInfo = appendText(zTableInfo, ");", 0);
rc = sqlite3_prepare(p->db, zTableInfo, -1, &pTableInfo, 0);
free(zTableInfo);
if( rc!=SQLITE_OK || !pTableInfo ){
return 1;
}
zSelect = appendText(zSelect, "SELECT 'INSERT INTO ' || ", 0);
| > > > > > > | | | | | < > | > > > | > > | > > > > > > | 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 |
if( strcmp(zType, "table")==0 ){
sqlite3_stmt *pTableInfo = 0;
char *zSelect = 0;
char *zTableInfo = 0;
char *zTmp = 0;
int nRow = 0;
int kk;
zTableInfo = appendText(zTableInfo, "PRAGMA table_info(", 0);
zTableInfo = appendText(zTableInfo, zTable, '"');
zTableInfo = appendText(zTableInfo, ");", 0);
rc = sqlite3_prepare(p->db, zTableInfo, -1, &pTableInfo, 0);
free(zTableInfo);
if( rc!=SQLITE_OK || !pTableInfo ){
return 1;
}
zSelect = appendText(zSelect, "SELECT 'INSERT INTO ' || ", 0);
if( !isalpha(zTable[0]) ){
kk = 0;
}else{
for(kk=1; isalnum(zTable[kk]); kk++){}
}
zTmp = appendText(zTmp, zTable, zTable[kk] ? '"' : 0);
if( zTmp ){
zSelect = appendText(zSelect, zTmp, '\'');
}
zSelect = appendText(zSelect, " || ' VALUES(' || ", 0);
rc = sqlite3_step(pTableInfo);
while( rc==SQLITE_ROW ){
const char *zText = (const char *)sqlite3_column_text(pTableInfo, 1);
zSelect = appendText(zSelect, "quote(", 0);
zSelect = appendText(zSelect, zText, '"');
rc = sqlite3_step(pTableInfo);
if( rc==SQLITE_ROW ){
zSelect = appendText(zSelect, "), ", 0);
}else{
zSelect = appendText(zSelect, ") ", 0);
}
nRow++;
}
rc = sqlite3_finalize(pTableInfo);
if( rc!=SQLITE_OK || nRow==0 ){
free(zSelect);
return 1;
}
zSelect = appendText(zSelect, "|| ')' FROM ", 0);
zSelect = appendText(zSelect, zTable, '"');
rc = run_table_dump_query(p, zSelect, zPrepStmt);
if( rc==SQLITE_CORRUPT ){
zSelect = appendText(zSelect, " ORDER BY rowid DESC", 0);
run_table_dump_query(p, zSelect, 0);
}
if( zSelect ) free(zSelect);
}
return 0;
}
/*
** Run zQuery. Use dump_callback() as the callback routine so that
** the contents of the query are output as SQL statements.
**
** 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
){
int rc;
char *zErr = 0;
rc = sqlite3_exec(p->db, zQuery, dump_callback, p, &zErr);
if( rc==SQLITE_CORRUPT ){
char *zQ2;
int len = strlen30(zQuery);
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, &zErr);
if( rc ){
fprintf(p->out, "/****** ERROR: %s ******/\n", zErr);
}else{
rc = SQLITE_CORRUPT;
}
sqlite3_free(zErr);
free(zQ2);
}
return rc;
}
/*
** Text of a help message
|
| ︙ | ︙ | |||
1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 | ".separator STRING Change separator used by output mode and .import\n" ".show Show the current values for various settings\n" ".stats ON|OFF Turn stats on or off\n" ".tables ?TABLE? List names of tables\n" " If TABLE specified, only list tables matching\n" " LIKE pattern TABLE.\n" ".timeout MS Try opening locked tables for MS milliseconds\n" ".width NUM1 NUM2 ... Set column widths for \"column\" mode\n" ; static char zTimerHelp[] = ".timer ON|OFF Turn the CPU timer measurement on or off\n" ; | > | 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 | ".separator STRING Change separator used by output mode and .import\n" ".show Show the current values for various settings\n" ".stats ON|OFF Turn stats on or off\n" ".tables ?TABLE? List names of tables\n" " If TABLE specified, only list tables matching\n" " LIKE pattern TABLE.\n" ".timeout MS Try opening locked tables for MS milliseconds\n" ".vfsname ?AUX? Print the name of the VFS stack\n" ".width NUM1 NUM2 ... Set column widths for \"column\" mode\n" ; static char zTimerHelp[] = ".timer ON|OFF Turn the CPU timer measurement on or off\n" ; |
| ︙ | ︙ | |||
1432 1433 1434 1435 1436 1437 1438 |
/*
** Interpret zArg as a boolean value. Return either 0 or 1.
*/
static int booleanValue(char *zArg){
int val = atoi(zArg);
int j;
for(j=0; zArg[j]; j++){
| | | 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 |
/*
** Interpret zArg as a boolean value. Return either 0 or 1.
*/
static int booleanValue(char *zArg){
int val = atoi(zArg);
int j;
for(j=0; zArg[j]; j++){
zArg[j] = ToLower(zArg[j]);
}
if( strcmp(zArg,"on")==0 ){
val = 1;
}else if( strcmp(zArg,"yes")==0 ){
val = 1;
}
return val;
|
| ︙ | ︙ | |||
1458 1459 1460 1461 1462 1463 1464 |
int n, c;
int rc = 0;
char *azArg[50];
/* Parse the input line into tokens.
*/
while( zLine[i] && nArg<ArraySize(azArg) ){
| | | | 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 |
int n, c;
int rc = 0;
char *azArg[50];
/* Parse the input line into tokens.
*/
while( zLine[i] && nArg<ArraySize(azArg) ){
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++; }
if( zLine[i]==delim ){
zLine[i++] = 0;
}
if( delim=='"' ) resolve_backslashes(azArg[nArg-1]);
}else{
azArg[nArg++] = &zLine[i];
while( zLine[i] && !IsSpace(zLine[i]) ){ i++; }
if( zLine[i] ) zLine[i++] = 0;
resolve_backslashes(azArg[nArg-1]);
}
}
/* Process the input line.
*/
|
| ︙ | ︙ | |||
1541 1542 1543 1544 1545 1546 1547 |
fprintf(stderr,"Error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
rc = 1;
}
}else
if( c=='d' && strncmp(azArg[0], "dump", n)==0 && nArg<3 ){
| < | > | | | | | | < < | < | < | 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 |
fprintf(stderr,"Error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
rc = 1;
}
}else
if( c=='d' && strncmp(azArg[0], "dump", n)==0 && nArg<3 ){
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, "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'"
);
run_schema_dump_query(p,
"SELECT name, type, sql FROM sqlite_master "
"WHERE name=='sqlite_sequence'"
);
run_table_dump_query(p,
"SELECT sql FROM sqlite_master "
"WHERE sql NOT NULL AND type IN ('index','trigger','view')", 0
);
}else{
int i;
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");
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
);
zShellStatic = 0;
}
}
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);
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
if( c=='e' && strncmp(azArg[0], "exit", n)==0 && nArg==1 ){
|
| ︙ | ︙ | |||
1669 1670 1671 1672 1673 1674 1675 |
open_db(p);
nSep = strlen30(p->separator);
if( nSep==0 ){
fprintf(stderr, "Error: non-null separator required for import\n");
return 1;
}
| | | 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 |
open_db(p);
nSep = strlen30(p->separator);
if( nSep==0 ){
fprintf(stderr, "Error: non-null separator required for import\n");
return 1;
}
zSql = sqlite3_mprintf("SELECT * FROM %s", zTable);
if( zSql==0 ){
fprintf(stderr, "Error: out of memory\n");
return 1;
}
nByte = strlen30(zSql);
rc = sqlite3_prepare(p->db, zSql, -1, &pStmt, 0);
sqlite3_free(zSql);
|
| ︙ | ︙ | |||
1691 1692 1693 1694 1695 1696 1697 |
pStmt = 0;
if( nCol==0 ) return 0; /* no columns, no error */
zSql = malloc( nByte + 20 + nCol*2 );
if( zSql==0 ){
fprintf(stderr, "Error: out of memory\n");
return 1;
}
| | | 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 |
pStmt = 0;
if( nCol==0 ) return 0; /* no columns, no error */
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 %s VALUES(?", zTable);
j = strlen30(zSql);
for(i=1; i<nCol; i++){
zSql[j++] = ',';
zSql[j++] = '?';
}
zSql[j++] = ')';
zSql[j] = 0;
|
| ︙ | ︙ | |||
1721 1722 1723 1724 1725 1726 1727 |
fprintf(stderr, "Error: out of memory\n");
fclose(in);
sqlite3_finalize(pStmt);
return 1;
}
sqlite3_exec(p->db, "BEGIN", 0, 0, 0);
zCommit = "COMMIT";
| | | | | > > | > > > > > > > > | 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 |
fprintf(stderr, "Error: out of memory\n");
fclose(in);
sqlite3_finalize(pStmt);
return 1;
}
sqlite3_exec(p->db, "BEGIN", 0, 0, 0);
zCommit = "COMMIT";
while( (zLine = local_getline(0, in, 1))!=0 ){
char *z, c;
int inQuote = 0;
lineno++;
azCol[0] = zLine;
for(i=0, z=zLine; (c = *z)!=0; z++){
if( c=='"' ) inQuote = !inQuote;
if( c=='\n' ) lineno++;
if( !inQuote && c==p->separator[0] && strncmp(z,p->separator,nSep)==0 ){
*z = 0;
i++;
if( i<nCol ){
azCol[i] = &z[nSep];
z += nSep-1;
}
}
} /* end for */
*z = 0;
if( i+1!=nCol ){
fprintf(stderr,
"Error: %s line %d: expected %d columns of data but found %d\n",
zFile, lineno, nCol, i+1);
zCommit = "ROLLBACK";
free(zLine);
rc = 1;
break; /* from while */
}
for(i=0; i<nCol; i++){
if( azCol[i][0]=='"' ){
int k;
for(z=azCol[i], j=1, k=0; z[j]; j++){
if( z[j]=='"' ){ j++; if( z[j]==0 ) break; }
z[k++] = z[j];
}
z[k] = 0;
}
sqlite3_bind_text(pStmt, i+1, azCol[i], -1, SQLITE_STATIC);
}
sqlite3_step(pStmt);
rc = sqlite3_reset(pStmt);
free(zLine);
if( rc!=SQLITE_OK ){
fprintf(stderr,"Error: %s\n", sqlite3_errmsg(db));
|
| ︙ | ︙ | |||
2012 2013 2014 2015 2016 2017 2018 |
char *zErrMsg = 0;
open_db(p);
memcpy(&data, p, sizeof(data));
data.showHeader = 0;
data.mode = MODE_Semi;
if( nArg>1 ){
int i;
| | | 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 |
char *zErrMsg = 0;
open_db(p);
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] = 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"
" tbl_name text,\n"
" rootpage integer,\n"
|
| ︙ | ︙ | |||
2048 2049 2050 2051 2052 2053 2054 |
}else{
zShellStatic = azArg[1];
rc = sqlite3_exec(p->db,
"SELECT sql FROM "
" (SELECT sql sql, type type, tbl_name tbl_name, name name"
" FROM sqlite_master UNION ALL"
" SELECT sql, type, tbl_name, name FROM sqlite_temp_master) "
| > | | 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 |
}else{
zShellStatic = azArg[1];
rc = sqlite3_exec(p->db,
"SELECT sql FROM "
" (SELECT sql sql, type type, tbl_name tbl_name, name name"
" FROM sqlite_master UNION ALL"
" SELECT sql, type, tbl_name, name FROM sqlite_temp_master) "
"WHERE lower(tbl_name) LIKE shellstatic()"
" AND type!='meta' AND sql NOTNULL "
"ORDER BY substr(type,2,1), name",
callback, &data, &zErrMsg);
zShellStatic = 0;
}
}else{
rc = sqlite3_exec(p->db,
"SELECT sql FROM "
|
| ︙ | ︙ | |||
2182 2183 2184 2185 2186 2187 2188 |
{ "benign_malloc_hooks", SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS },
{ "pending_byte", SQLITE_TESTCTRL_PENDING_BYTE },
{ "assert", SQLITE_TESTCTRL_ASSERT },
{ "always", SQLITE_TESTCTRL_ALWAYS },
{ "reserve", SQLITE_TESTCTRL_RESERVE },
{ "optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS },
{ "iskeyword", SQLITE_TESTCTRL_ISKEYWORD },
| < | | 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 |
{ "benign_malloc_hooks", SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS },
{ "pending_byte", SQLITE_TESTCTRL_PENDING_BYTE },
{ "assert", SQLITE_TESTCTRL_ASSERT },
{ "always", SQLITE_TESTCTRL_ALWAYS },
{ "reserve", SQLITE_TESTCTRL_RESERVE },
{ "optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS },
{ "iskeyword", SQLITE_TESTCTRL_ISKEYWORD },
{ "scratchmalloc", SQLITE_TESTCTRL_SCRATCHMALLOC },
};
int testctrl = -1;
int rc = 0;
int i, n;
open_db(p);
/* convert testctrl text option to value. allow any unique prefix
** of the option name, or a numerical value. */
n = strlen30(azArg[1]);
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[1]);
testctrl = -1;
break;
}
}
}
if( testctrl<0 ) testctrl = atoi(azArg[1]);
if( (testctrl<SQLITE_TESTCTRL_FIRST) || (testctrl>SQLITE_TESTCTRL_LAST) ){
|
| ︙ | ︙ | |||
2227 2228 2229 2230 2231 2232 2233 |
}
break;
/* sqlite3_test_control(int) */
case SQLITE_TESTCTRL_PRNG_SAVE:
case SQLITE_TESTCTRL_PRNG_RESTORE:
case SQLITE_TESTCTRL_PRNG_RESET:
| < | 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 |
}
break;
/* sqlite3_test_control(int) */
case SQLITE_TESTCTRL_PRNG_SAVE:
case SQLITE_TESTCTRL_PRNG_RESTORE:
case SQLITE_TESTCTRL_PRNG_RESET:
if( nArg==2 ){
rc = sqlite3_test_control(testctrl);
printf("%d (0x%08x)\n", rc, rc);
} else {
fprintf(stderr,"Error: testctrl %s takes no options\n", azArg[1]);
}
break;
|
| ︙ | ︙ | |||
2299 2300 2301 2302 2303 2304 2305 |
if( HAS_TIMER && c=='t' && n>=5 && strncmp(azArg[0], "timer", n)==0
&& nArg==2
){
enableTimer = booleanValue(azArg[1]);
}else
if( c=='v' && strncmp(azArg[0], "version", n)==0 ){
| | > > > > > > > > > > > > | 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 |
if( HAS_TIMER && c=='t' && n>=5 && strncmp(azArg[0], "timer", n)==0
&& nArg==2
){
enableTimer = booleanValue(azArg[1]);
}else
if( c=='v' && strncmp(azArg[0], "version", n)==0 ){
printf("SQLite %s %s\n" /*extra-version-info*/,
sqlite3_libversion(), sqlite3_sourceid());
}else
if( c=='v' && strncmp(azArg[0], "vfsname", n)==0 ){
const char *zDbName = nArg==2 ? azArg[1] : "main";
char *zVfsName = 0;
if( p->db ){
sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFSNAME, &zVfsName);
if( zVfsName ){
printf("%s\n", zVfsName);
sqlite3_free(zVfsName);
}
}
}else
if( c=='w' && strncmp(azArg[0], "width", n)==0 && nArg>1 ){
int j;
assert( nArg<=ArraySize(azArg) );
for(j=1; j<nArg && j<ArraySize(p->colWidth); j++){
p->colWidth[j-1] = atoi(azArg[j]);
}
|
| ︙ | ︙ | |||
2335 2336 2337 2338 2339 2340 2341 |
}
/*
** Test to see if a line consists entirely of whitespace.
*/
static int _all_whitespace(const char *z){
for(; *z; z++){
| | | 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 |
}
/*
** Test to see if a line consists entirely of whitespace.
*/
static int _all_whitespace(const char *z){
for(; *z; z++){
if( IsSpace(z[0]) ) continue;
if( *z=='/' && z[1]=='*' ){
z += 2;
while( *z && (*z!='*' || z[1]!='/') ){ z++; }
if( *z==0 ) return 0;
z++;
continue;
}
|
| ︙ | ︙ | |||
2360 2361 2362 2363 2364 2365 2366 |
/*
** 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){
| | | | 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 |
/*
** 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(zLine[0]) ){ zLine++; };
if( zLine[0]=='/' && _all_whitespace(&zLine[1]) ){
return 1; /* Oracle */
}
if( ToLower(zLine[0])=='g' && ToLower(zLine[1])=='o'
&& _all_whitespace(&zLine[2]) ){
return 1; /* SQL Server */
}
return 0;
}
/*
|
| ︙ | ︙ | |||
2434 2435 2436 2437 2438 2439 2440 |
}
if( _is_command_terminator(zLine) && _is_complete(zSql, nSql) ){
memcpy(zLine,";",2);
}
nSqlPrior = nSql;
if( zSql==0 ){
int i;
| | | 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 |
}
if( _is_command_terminator(zLine) && _is_complete(zSql, nSql) ){
memcpy(zLine,";",2);
}
nSqlPrior = nSql;
if( zSql==0 ){
int 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");
exit(1);
}
|
| ︙ | ︙ | |||
2608 2609 2610 2611 2612 2613 2614 | return rc; } /* ** Show available command line options */ static const char zOptions[] = | < < < < < > > > > > > > > > > < | 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 |
return rc;
}
/*
** Show available command line options
*/
static const char zOptions[] =
" -bail stop after hitting an error\n"
" -batch force batch I/O\n"
" -column set output mode to 'column'\n"
" -cmd command run \"command\" before reading stdin\n"
" -csv set output mode to 'csv'\n"
" -echo print commands before execution\n"
" -init filename read/process named file\n"
" -[no]header turn headers on or off\n"
" -help show this message\n"
" -html set output mode to HTML\n"
" -interactive force interactive I/O\n"
" -line set output mode to 'line'\n"
" -list set output mode to 'list'\n"
#ifdef SQLITE_ENABLE_MULTIPLEX
" -multiplex enable the multiplexor VFS\n"
#endif
" -nullvalue 'text' set text string for NULL values\n"
" -separator 'x' set output field separator (|)\n"
" -stats print memory stats before each finalize\n"
" -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
;
static void usage(int showDetail){
|
| ︙ | ︙ | |||
2690 2691 2692 2693 2694 2695 2696 |
** the size of the alternative malloc heap,
** and the first command to execute.
*/
for(i=1; i<argc-1; i++){
char *z;
if( argv[i][0]!='-' ) break;
z = argv[i];
| | > | > > | | | > < | > > > > > | | 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 |
** the size of the alternative malloc heap,
** and the first command to execute.
*/
for(i=1; i<argc-1; i++){
char *z;
if( argv[i][0]!='-' ) break;
z = argv[i];
if( z[1]=='-' ) z++;
if( strcmp(z,"-separator")==0
|| strcmp(z,"-nullvalue")==0
|| strcmp(z,"-cmd")==0
){
i++;
}else if( strcmp(z,"-init")==0 ){
i++;
zInitFile = argv[i];
/* Need to check for batch mode here to so we can avoid printing
** informational messages (like from process_sqliterc) before
** we do the actual processing of arguments later in a second pass.
*/
}else if( strcmp(z,"-batch")==0 ){
stdin_is_interactive = 0;
}else if( strcmp(z,"-heap")==0 ){
#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5)
int j, c;
const char *zSize;
sqlite3_int64 szHeap;
zSize = argv[++i];
szHeap = atoi(zSize);
for(j=0; (c = zSize[j])!=0; j++){
if( c=='M' ){ szHeap *= 1000000; break; }
if( c=='K' ){ szHeap *= 1000; break; }
if( c=='G' ){ szHeap *= 1000000000; break; }
}
if( szHeap>0x7fff0000 ) szHeap = 0x7fff0000;
sqlite3_config(SQLITE_CONFIG_HEAP, malloc((int)szHeap), (int)szHeap, 64);
#endif
#ifdef SQLITE_ENABLE_VFSTRACE
}else if( strcmp(z,"-vfstrace")==0 ){
extern int vfstrace_register(
const char *zTraceName,
const char *zOldVfsName,
int (*xOut)(const char*,void*),
void *pOutArg,
int makeDefault
);
vfstrace_register("trace",0,(int(*)(const char*,void*))fputs,stderr,1);
#endif
#ifdef SQLITE_ENABLE_MULTIPLEX
}else if( strcmp(z,"-multiplex")==0 ){
extern int sqlite3_multiple_initialize(const char*,int);
sqlite3_multiplex_initialize(0, 1);
#endif
}else if( strcmp(z,"-vfs")==0 ){
sqlite3_vfs *pVfs = sqlite3_vfs_find(argv[++i]);
if( pVfs ){
sqlite3_vfs_register(pVfs, 1);
}else{
fprintf(stderr, "no such VFS: \"%s\"\n", argv[i]);
exit(1);
}
|
| ︙ | ︙ | |||
2817 2818 2819 2820 2821 2822 2823 |
data.mode = MODE_Column;
}else if( strcmp(z,"-csv")==0 ){
data.mode = MODE_Csv;
memcpy(data.separator,",",2);
}else if( strcmp(z,"-separator")==0 ){
i++;
if(i>=argc){
| | > | > | 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 |
data.mode = MODE_Column;
}else if( strcmp(z,"-csv")==0 ){
data.mode = MODE_Csv;
memcpy(data.separator,",",2);
}else if( strcmp(z,"-separator")==0 ){
i++;
if(i>=argc){
fprintf(stderr,"%s: Error: missing argument for option: %s\n",
Argv0, z);
fprintf(stderr,"Use -help for a list of options.\n");
return 1;
}
sqlite3_snprintf(sizeof(data.separator), data.separator,
"%.*s",(int)sizeof(data.separator)-1,argv[i]);
}else if( strcmp(z,"-nullvalue")==0 ){
i++;
if(i>=argc){
fprintf(stderr,"%s: Error: missing argument for option: %s\n",
Argv0, z);
fprintf(stderr,"Use -help for a list of options.\n");
return 1;
}
sqlite3_snprintf(sizeof(data.nullvalue), data.nullvalue,
"%.*s",(int)sizeof(data.nullvalue)-1,argv[i]);
}else if( strcmp(z,"-header")==0 ){
data.showHeader = 1;
|
| ︙ | ︙ | |||
2853 2854 2855 2856 2857 2858 2859 2860 2861 |
stdin_is_interactive = 1;
}else if( strcmp(z,"-batch")==0 ){
stdin_is_interactive = 0;
}else if( strcmp(z,"-heap")==0 ){
i++;
}else if( strcmp(z,"-vfs")==0 ){
i++;
}else if( strcmp(z,"-vfstrace")==0 ){
i++;
| > > > > > > | > > > > > > > > > > > > > > > > > > | 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 |
stdin_is_interactive = 1;
}else if( strcmp(z,"-batch")==0 ){
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 ){
usage(1);
}else if( strcmp(z,"-cmd")==0 ){
if( i==argc-1 ) break;
i++;
z = argv[i];
if( z[0]=='.' ){
rc = do_meta_command(z, &data);
if( rc && bail_on_error ) return rc;
}else{
open_db(&data);
rc = shell_exec(data.db, z, shell_callback, &data, &zErrMsg);
if( zErrMsg!=0 ){
fprintf(stderr,"Error: %s\n", zErrMsg);
if( bail_on_error ) return rc!=0 ? rc : 1;
}else if( rc!=0 ){
fprintf(stderr,"Error: unable to process SQL \"%s\"\n", z);
if( bail_on_error ) return rc;
}
}
}else{
fprintf(stderr,"%s: Error: unknown option: %s\n", Argv0, z);
fprintf(stderr,"Use -help for a list of options.\n");
return 1;
}
}
|
| ︙ | ︙ | |||
2888 2889 2890 2891 2892 2893 2894 |
/* Run commands received from standard input
*/
if( stdin_is_interactive ){
char *zHome;
char *zHistory = 0;
int nHistory;
printf(
| | | 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 |
/* Run commands received from standard input
*/
if( stdin_is_interactive ){
char *zHome;
char *zHistory = 0;
int nHistory;
printf(
"SQLite version %s %.19s\n" /*extra-version-info*/
"Enter \".help\" for instructions\n"
"Enter SQL statements terminated with a \";\"\n",
sqlite3_libversion(), sqlite3_sourceid()
);
zHome = find_home_dir();
if( zHome ){
nHistory = strlen30(zHome) + 20;
|
| ︙ | ︙ |
Changes to src/skins.c.
| ︙ | ︙ | |||
84 85 86 87 88 89 90 | @ font-size: 0.9em; @ font-weight: bold; @ text-align: center; @ letter-spacing: 1px; @ background-color: #404040; @ color: white; @ } | | | | > | | | 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
@ font-size: 0.9em;
@ font-weight: bold;
@ text-align: center;
@ letter-spacing: 1px;
@ background-color: #404040;
@ color: white;
@ }
@
@ /* The submenu bar that *sometimes* appears below the main menu */
@ div.submenu, div.sectionmenu {
@ padding: 3px 10px 3px 0px;
@ font-size: 0.9em;
@ text-align: center;
@ background-color: #606060;
@ color: white;
@ }
@ div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited,
@ div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited {
@ padding: 3px 10px 3px 10px;
@ color: white;
@ text-decoration: none;
@ }
@ div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover {
@ color: #404040;
@ background-color: white;
@ }
@
@ /* All page content from the bottom of the menu or submenu down to
@ ** the footer */
@ div.content {
@ padding: 0ex 0ex 0ex 0ex;
@ }
@ /* Hyperlink colors */
@ div.content a { color: #604000; }
|
| ︙ | ︙ | |||
161 162 163 164 165 166 167 | @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" @ href="$home/timeline.rss"> @ <link rel="stylesheet" href="$home/style.css?blackwhite" type="text/css" @ media="screen"> @ </head> @ <body> @ <div class="header"> | < < < | > | | | | | | | | | | | | 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
@ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
@ href="$home/timeline.rss">
@ <link rel="stylesheet" href="$home/style.css?blackwhite" type="text/css"
@ media="screen">
@ </head>
@ <body>
@ <div class="header">
@ <div class="title"><small>$<project_name></small><br />$<title></div>
@ <div class="status"><nobr><th1>
@ if {[info exists login]} {
@ puts "Logged in as $login"
@ } else {
@ puts "Not logged in"
@ }
@ </th1></nobr></div>
@ </div>
@ <div class="mainmenu">
@ <th1>
@ html "<a href=''$home$index_page''>Home</a>\n"
@ if {[anycap jor]} {
@ html "<a href=''$home/timeline''>Timeline</a>\n"
@ }
@ if {[hascap oh]} {
@ html "<a href=''$home/dir?ci=tip''>Files</a>\n"
@ }
@ if {[hascap o]} {
@ html "<a href=''$home/brlist''>Branches</a>\n"
@ html "<a href=''$home/taglist''>Tags</a>\n"
@ }
@ if {[hascap r]} {
@ html "<a href=''$home/reportlist''>Tickets</a>\n"
@ }
@ if {[hascap j]} {
@ html "<a href=''$home/wiki''>Wiki</a>\n"
@ }
@ if {[hascap s]} {
@ html "<a href=''$home/setup''>Admin</a>\n"
@ } elseif {[hascap a]} {
@ html "<a href=''$home/setup_ulist''>Users</a>\n"
@ }
@ if {[info exists login]} {
@ html "<a href=''$home/login''>Logout</a>\n"
@ } else {
@ html "<a href=''$home/login''>Login</a>\n"
@ }
@ </th1></div>
@ ');
@ REPLACE INTO config(name,mtime,value)
@ VALUES('footer',now(),'<div class="footer">
@ Fossil version $manifest_version $manifest_date
@ </div>
|
| ︙ | ︙ | |||
277 278 279 280 281 282 283 | @ text-align: center; @ letter-spacing: 1px; @ background-color: #a09048; @ color: black; @ } @ @ /* The submenu bar that *sometimes* appears below the main menu */ | | | > | | 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 |
@ text-align: center;
@ letter-spacing: 1px;
@ background-color: #a09048;
@ color: black;
@ }
@
@ /* The submenu bar that *sometimes* appears below the main menu */
@ div.submenu, div.sectionmenu {
@ padding: 3px 10px 3px 0px;
@ font-size: 0.9em;
@ text-align: center;
@ background-color: #c0af58;
@ color: white;
@ }
@ div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited,
@ div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited {
@ padding: 3px 10px 3px 10px;
@ color: white;
@ text-decoration: none;
@ }
@ div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover {
@ color: #a09048;
@ background-color: white;
@ }
@
@ /* All page content from the bottom of the menu or submenu down to
@ ** the footer */
@ div.content {
|
| ︙ | ︙ | |||
353 354 355 356 357 358 359 |
@ }
@
@ /* The label/value pairs on (for example) the ci page */
@ table.label-value th {
@ vertical-align: top;
@ text-align: right;
@ padding: 0.2ex 2ex;
| | < | 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 |
@ }
@
@ /* The label/value pairs on (for example) the ci page */
@ table.label-value th {
@ vertical-align: top;
@ text-align: right;
@ padding: 0.2ex 2ex;
@ }');
@ 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">
@ <link rel="stylesheet" href="$home/style.css?tan" type="text/css"
@ media="screen">
|
| ︙ | ︙ | |||
376 377 378 379 380 381 382 |
@ if {[info exists login]} {
@ puts "Logged in as $login"
@ } else {
@ puts "Not logged in"
@ }
@ </th1></nobr></div>
@ </div>
| | > | | | | | | | | | | | | 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 |
@ if {[info exists login]} {
@ puts "Logged in as $login"
@ } else {
@ puts "Not logged in"
@ }
@ </th1></nobr></div>
@ </div>
@ <div class="mainmenu">
@ <th1>
@ html "<a href=''$home$index_page''>Home</a>\n"
@ if {[anycap jor]} {
@ html "<a href=''$home/timeline''>Timeline</a>\n"
@ }
@ if {[hascap oh]} {
@ html "<a href=''$home/dir?ci=tip''>Files</a>\n"
@ }
@ if {[hascap o]} {
@ html "<a href=''$home/brlist''>Branches</a>\n"
@ html "<a href=''$home/taglist''>Tags</a>\n"
@ }
@ if {[hascap r]} {
@ html "<a href=''$home/reportlist''>Tickets</a>\n"
@ }
@ if {[hascap j]} {
@ html "<a href=''$home/wiki''>Wiki</a>\n"
@ }
@ if {[hascap s]} {
@ html "<a href=''$home/setup''>Admin</a>\n"
@ } elseif {[hascap a]} {
@ html "<a href=''$home/setup_ulist''>Users</a>\n"
@ }
@ if {[info exists login]} {
@ html "<a href=''$home/login''>Logout</a>\n"
@ } else {
@ html "<a href=''$home/login''>Login</a>\n"
@ }
@ </th1></div>
@ ');
@ REPLACE INTO config(name,mtime,value)
@ VALUES('footer',now(),'<div class="footer">
@ Fossil version $manifest_version $manifest_date
@ </div>
|
| ︙ | ︙ | |||
515 516 517 518 519 520 521 |
@ /* Container for the sub-menu and content so they don''t spread
@ ** out underneath the main menu */
@ #container {
@ padding-left: 9em;
@ }
@
@ /* The submenu bar that *sometimes* appears below the main menu */
| | | > | | 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 |
@ /* Container for the sub-menu and content so they don''t spread
@ ** out underneath the main menu */
@ #container {
@ padding-left: 9em;
@ }
@
@ /* The submenu bar that *sometimes* appears below the main menu */
@ div.submenu, div.sectionmenu {
@ padding: 3px 10px 3px 10px;
@ font-size: 0.9em;
@ text-align: center;
@ border:1px solid #999;
@ border-width:1px 0px;
@ background-color: #eee;
@ color: #333;
@ }
@ div.submenu a, div.submenu a:visited, div.sectionmenu>a.button:link,
@ div.sectionmenu>a.button:visited {
@ padding: 3px 10px 3px 10px;
@ color: #333;
@ text-decoration: none;
@ }
@ div.submenu a:hover, div.sectionmenu>a.button:hover {
@ color: #eee;
@ background-color: #333;
@ }
@
@ /* All page content from the bottom of the menu or submenu down to
@ ** the footer */
@ div.content {
|
| ︙ | ︙ | |||
612 613 614 615 616 617 618 |
@ if {[info exists login]} {
@ puts "Logged in as $login"
@ } else {
@ puts "Not logged in"
@ }
@ </th1></nobr></div>
@ </div>
| | > | | | | | | | | | | | | 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 |
@ if {[info exists login]} {
@ puts "Logged in as $login"
@ } else {
@ puts "Not logged in"
@ }
@ </th1></nobr></div>
@ </div>
@ <div class="mainmenu">
@ <th1>
@ html "<a href=''$home$index_page''>Home</a>\n"
@ if {[anycap jor]} {
@ html "<a href=''$home/timeline''>Timeline</a>\n"
@ }
@ if {[hascap oh]} {
@ html "<a href=''$home/dir?ci=tip''>Files</a>\n"
@ }
@ if {[hascap o]} {
@ html "<a href=''$home/brlist''>Branches</a>\n"
@ html "<a href=''$home/taglist''>Tags</a>\n"
@ }
@ if {[hascap r]} {
@ html "<a href=''$home/reportlist''>Tickets</a>\n"
@ }
@ if {[hascap j]} {
@ html "<a href=''$home/wiki''>Wiki</a>\n"
@ }
@ if {[hascap s]} {
@ html "<a href=''$home/setup''>Admin</a>\n"
@ } elseif {[hascap a]} {
@ html "<a href=''$home/setup_ulist''>Users</a>\n"
@ }
@ if {[info exists login]} {
@ html "<a href=''$home/login''>Logout</a>\n"
@ } else {
@ html "<a href=''$home/login''>Login</a>\n"
@ }
@ </th1></ul></div>
@ <div id="container">
@ ');
@ REPLACE INTO config(name,mtime,value) VALUES('footer',now(),'</div>
@ <div class="footer">
@ Fossil version $manifest_version $manifest_date
|
| ︙ | ︙ | |||
723 724 725 726 727 728 729 | @ -moz-border-top-left-radius: 5px; @ -webkit-border-top-right-radius: 5px; @ -webkit-border-top-left-radius: 5px; @ -border-top-right-radius: 5px; @ -border-top-left-radius: 5px; @ border-top-left-radius: 5px; @ border-top-right-radius: 5px; | | | > | 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 | @ -moz-border-top-left-radius: 5px; @ -webkit-border-top-right-radius: 5px; @ -webkit-border-top-left-radius: 5px; @ -border-top-right-radius: 5px; @ -border-top-left-radius: 5px; @ border-top-left-radius: 5px; @ border-top-right-radius: 5px; @ vertical-align: middle; @ padding-top: 8px; @ padding-bottom: 8px; @ background-color: #446979; @ background: -webkit-gradient(linear,left bottom,left top, color-stop(0.02, rgb(51,81,94)), color-stop(0.76, rgb(85,129,149))); @ background: -moz-linear-gradient(center bottom,rgb(51,81,94) 2%, rgb(85,129,149) 76%); @ -webkit-box-shadow: 0px 3px 4px #333333; @ -moz-box-shadow: 0px 3px 4px #333333; @ box-shadow: 0px 3px 4px #333333; @ } |
| ︙ | ︙ | |||
751 752 753 754 755 756 757 |
@ box-shadow: 0px 3px 4px #999;
@ }
@ div.mainmenu a, div.mainmenu a:visited {
@ padding: 3px 10px 3px 10px;
@ color: white;
@ text-decoration: none;
@ }
| | > | 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 |
@ box-shadow: 0px 3px 4px #999;
@ }
@ div.mainmenu a, div.mainmenu a:visited {
@ padding: 3px 10px 3px 10px;
@ color: white;
@ text-decoration: none;
@ }
@ div.submenu a, div.submenu a:visited, a.button,
@ div.sectionmenu>a.button:link, div.sectinmenu>a.button:visited {
@ padding: 2px 8px;
@ color: #000;
@ font-family: Arial;
@ text-decoration: none;
@ margin:auto;
@ -webkit-border-radius: 5px;
@ -moz-border-radius: 5px;
|
| ︙ | ︙ | |||
773 774 775 776 777 778 779 |
@ }
@
@ div.mainmenu a:hover {
@ color: #000;
@ background-color: white;
@ }
@
| | | 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 |
@ }
@
@ div.mainmenu a:hover {
@ color: #000;
@ background-color: white;
@ }
@
@ div.submenu a:hover, div.sectionmenu>a.button:hover {
@ background: -webkit-gradient(linear,left bottom, left top, color-stop(0, rgb(214,214,214)), color-stop(0.75, rgb(184,184,184)));
@ background: -moz-linear-gradient(center bottom, rgb(214,214,214) 0%, rgb(184,184,184) 75%);
@ background-color: #c0c0c0 ;
@ }
@
@ /* All page content from the bottom of the menu or submenu down to
@ ** the footer */
|
| ︙ | ︙ | |||
907 908 909 910 911 912 913 |
@ if {[info exists login]} {
@ puts "Logged in as $login"
@ } else {
@ puts "Not logged in"
@ }
@ </th1></nobr></div>
@ </div>
| | > | | | | | | | | | | | | | 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 |
@ if {[info exists login]} {
@ puts "Logged in as $login"
@ } else {
@ puts "Not logged in"
@ }
@ </th1></nobr></div>
@ </div>
@ <div class="mainmenu">
@ <th1>
@ html "<a href=''$home$index_page''>Home</a>\n"
@ if {[anycap jor]} {
@ html "<a href=''$home/timeline''>Timeline</a>\n"
@ }
@ if {[hascap oh]} {
@ html "<a href=''$home/dir?ci=tip''>Files</a>\n"
@ }
@ if {[hascap o]} {
@ html "<a href=''$home/brlist''>Branches</a>\n"
@ html "<a href=''$home/taglist''>Tags</a>\n"
@ }
@ if {[hascap r]} {
@ html "<a href=''$home/reportlist''>Tickets</a>\n"
@ }
@ if {[hascap j]} {
@ html "<a href=''$home/wiki''>Wiki</a>\n"
@ }
@ if {[hascap s]} {
@ html "<a href=''$home/setup''>Admin</a>\n"
@ } elseif {[hascap a]} {
@ html "<a href=''$home/setup_ulist''>Users</a>\n"
@ }
@ if {[info exists login]} {
@ html "<a href=''$home/login''>Logout</a>\n"
@ } else {
@ html "<a href=''$home/login''>Login</a>\n"
@ }
@ </th1></div>
@ <div id="container">
@ ');
@ REPLACE INTO config(name,mtime,value) VALUES('footer',now(),'</div>
@ <div class="footer">
@ Fossil version $manifest_version $manifest_date
@ </div>
@ </body></html>
|
| ︙ | ︙ | |||
1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 |
zName = skinVarName(z,0);
zCurrent = db_get(zName, 0);
db_multi_exec("%s", zCurrent);
}
}
style_header("Skins");
@ <p>A "skin" is a combination of
@ <a href="setup_editcss">CSS</a>,
@ <a href="setup_header">Header</a>,
@ <a href="setup_footer">Footer</a>, and
@ <a href="setup_logo">Logo</a> that determines the look and feel
@ of the web interface.</p>
@
| > > > | 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 |
zName = skinVarName(z,0);
zCurrent = db_get(zName, 0);
db_multi_exec("%s", zCurrent);
}
}
style_header("Skins");
if( zErr ){
@ <p><font color="red">%h(zErr)</font></p>
}
@ <p>A "skin" is a combination of
@ <a href="setup_editcss">CSS</a>,
@ <a href="setup_header">Header</a>,
@ <a href="setup_footer">Footer</a>, and
@ <a href="setup_logo">Logo</a> that determines the look and feel
@ of the web interface.</p>
@
|
| ︙ | ︙ |
Changes to src/sqlite3.c.
more than 10,000 changes
Changes to src/sqlite3.h.
| ︙ | ︙ | |||
103 104 105 106 107 108 109 | ** string contains the date and time of the check-in (UTC) and an SHA1 ** hash of the entire source tree. ** ** See also: [sqlite3_libversion()], ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ | | | | | 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | ** string contains the date and time of the check-in (UTC) and an SHA1 ** hash of the entire source tree. ** ** See also: [sqlite3_libversion()], ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ #define SQLITE_VERSION "3.7.11" #define SQLITE_VERSION_NUMBER 3007011 #define SQLITE_SOURCE_ID "2012-02-07 14:13:50 9497893b1b9219eac4ec2183bd90b4e4b860d9fe" /* ** CAPI3REF: Run-Time Library Version Numbers ** KEYWORDS: sqlite3_version, sqlite3_sourceid ** ** These interfaces provide the same information as the [SQLITE_VERSION], ** [SQLITE_VERSION_NUMBER], and [SQLITE_SOURCE_ID] C preprocessor macros |
| ︙ | ︙ | |||
173 174 175 176 177 178 179 | SQLITE_API const char *sqlite3_compileoption_get(int N); #endif /* ** CAPI3REF: Test To See If The Library Is Threadsafe ** ** ^The sqlite3_threadsafe() function returns zero if and only if | | | 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | SQLITE_API const char *sqlite3_compileoption_get(int N); #endif /* ** CAPI3REF: Test To See If The Library Is Threadsafe ** ** ^The sqlite3_threadsafe() function returns zero if and only if ** SQLite was compiled with mutexing code omitted due to the ** [SQLITE_THREADSAFE] compile-time option being set to 0. ** ** SQLite can be compiled with or without mutexes. When ** the [SQLITE_THREADSAFE] C preprocessor macro is 1 or 2, mutexes ** are enabled and SQLite is threadsafe. When the ** [SQLITE_THREADSAFE] macro is 0, ** the mutexes are omitted. Without the mutexes, it is not safe |
| ︙ | ︙ | |||
367 368 369 370 371 372 373 |
/*
** CAPI3REF: Result Codes
** KEYWORDS: SQLITE_OK {error code} {error codes}
** KEYWORDS: {result code} {result codes}
**
** Many SQLite functions return an integer result code from the set shown
| | | 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 |
/*
** CAPI3REF: Result Codes
** KEYWORDS: SQLITE_OK {error code} {error codes}
** KEYWORDS: {result code} {result codes}
**
** Many SQLite functions return an integer result code from the set shown
** here in order to indicate success or failure.
**
** New error codes may be added in future versions of SQLite.
**
** See also: [SQLITE_IOERR_READ | extended result codes],
** [sqlite3_vtab_on_conflict()] [SQLITE_ROLLBACK | result codes].
*/
#define SQLITE_OK 0 /* Successful result */
|
| ︙ | ︙ | |||
505 506 507 508 509 510 511 | ** mean that writes of blocks that are nnn bytes in size and ** are aligned to an address which is an integer multiple of ** nnn are atomic. The SQLITE_IOCAP_SAFE_APPEND value means ** that when data is appended to a file, the data is appended ** first then the size of the file is extended, never the other ** way around. The SQLITE_IOCAP_SEQUENTIAL property means that ** information is written to disk in the same order as calls | | > > > > > | 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 | ** mean that writes of blocks that are nnn bytes in size and ** are aligned to an address which is an integer multiple of ** nnn are atomic. The SQLITE_IOCAP_SAFE_APPEND value means ** that when data is appended to a file, the data is appended ** first then the size of the file is extended, never the other ** way around. The SQLITE_IOCAP_SEQUENTIAL property means that ** information is written to disk in the same order as calls ** to xWrite(). The SQLITE_IOCAP_POWERSAFE_OVERWRITE property means that ** after reboot following a crash or power loss, the only bytes in a ** file that were written at the application level might have changed ** and that adjacent bytes, even bytes within the same sector are ** guaranteed to be unchanged. */ #define SQLITE_IOCAP_ATOMIC 0x00000001 #define SQLITE_IOCAP_ATOMIC512 0x00000002 #define SQLITE_IOCAP_ATOMIC1K 0x00000004 #define SQLITE_IOCAP_ATOMIC2K 0x00000008 #define SQLITE_IOCAP_ATOMIC4K 0x00000010 #define SQLITE_IOCAP_ATOMIC8K 0x00000020 #define SQLITE_IOCAP_ATOMIC16K 0x00000040 #define SQLITE_IOCAP_ATOMIC32K 0x00000080 #define SQLITE_IOCAP_ATOMIC64K 0x00000100 #define SQLITE_IOCAP_SAFE_APPEND 0x00000200 #define SQLITE_IOCAP_SEQUENTIAL 0x00000400 #define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800 #define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000 /* ** CAPI3REF: File Locking Levels ** ** SQLite uses one of these integer values as the second ** argument to calls it makes to the xLock() and xUnlock() methods ** of an [sqlite3_io_methods] object. |
| ︙ | ︙ | |||
740 741 742 743 744 745 746 | ** VFSes do not need this signal and should silently ignore this opcode. ** Applications should not call [sqlite3_file_control()] with this ** opcode as doing so may disrupt the operation of the specialized VFSes ** that do require it. ** ** ^The [SQLITE_FCNTL_WIN32_AV_RETRY] opcode is used to configure automatic ** retry counts and intervals for certain disk I/O operations for the | | | | | 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 | ** VFSes do not need this signal and should silently ignore this opcode. ** Applications should not call [sqlite3_file_control()] with this ** opcode as doing so may disrupt the operation of the specialized VFSes ** that do require it. ** ** ^The [SQLITE_FCNTL_WIN32_AV_RETRY] opcode is used to configure automatic ** retry counts and intervals for certain disk I/O operations for the ** windows [VFS] in order to provide robustness in the presence of ** anti-virus programs. By default, the windows VFS will retry file read, ** file write, and file delete operations up to 10 times, with a delay ** of 25 milliseconds before the first retry and with the delay increasing ** by an additional 25 milliseconds with each subsequent retry. This ** opcode allows these two values (10 retries and 25 milliseconds of delay) ** to be adjusted. The values are changed for all database connections ** within the same process. The argument is a pointer to an array of two ** integers where the first integer i the new retry count and the second ** integer is the delay. If either integer is negative, then the setting ** is not changed but instead the prior value of that setting is written ** into the array entry, allowing the current retry settings to be ** interrogated. The zDbName parameter is ignored. |
| ︙ | ︙ | |||
767 768 769 770 771 772 773 | ** have write permission on the directory containing the database file want ** to read the database file, as the WAL and shared memory files must exist ** 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. | | > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | | | | | > > > | 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 | ** have write permission on the directory containing the database file want ** to read the database file, as the WAL and shared memory files must exist ** 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_POWERSAFE_OVERWRITE] opcode is used to set or query the ** persistent "powersafe-overwrite" or "PSOW" setting. The PSOW setting ** determines the [SQLITE_IOCAP_POWERSAFE_OVERWRITE] bit of the ** xDeviceCharacteristics methods. The fourth parameter to ** [sqlite3_file_control()] for this opcode should be a pointer to an integer. ** That integer is 0 to disable zero-damage mode or 1 to enable zero-damage ** mode. If the integer is -1, then it is overwritten with the current ** zero-damage mode 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. ** ** ^The [SQLITE_FCNTL_VFSNAME] opcode can be used to obtain the names of ** all [VFSes] in the VFS stack. The names are of all VFS shims and the ** final bottom-level VFS are written into memory obtained from ** [sqlite3_malloc()] and the result is stored in the char* variable ** that the fourth parameter of [sqlite3_file_control()] points to. ** The caller is responsible for freeing the memory when done. As with ** all file-control actions, there is no guarantee that this will actually ** do anything. Callers should initialize the char* variable to a NULL ** pointer in case this file-control is not implemented. This file-control ** is intended for diagnostic use only. */ #define SQLITE_FCNTL_LOCKSTATE 1 #define SQLITE_GET_LOCKPROXYFILE 2 #define SQLITE_SET_LOCKPROXYFILE 3 #define SQLITE_LAST_ERRNO 4 #define SQLITE_FCNTL_SIZE_HINT 5 #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 #define SQLITE_FCNTL_VFSNAME 12 #define SQLITE_FCNTL_POWERSAFE_OVERWRITE 13 /* ** CAPI3REF: Mutex Handle ** ** The mutex module within SQLite defines [sqlite3_mutex] to be an ** abstract type for a mutex object. The SQLite core never looks ** at the internal representation of an [sqlite3_mutex]. It only |
| ︙ | ︙ | |||
833 834 835 836 837 838 839 | ** ** [[sqlite3_vfs.xOpen]] ** ^SQLite guarantees that the zFilename parameter to xOpen ** is either a NULL pointer or string obtained ** from xFullPathname() with an optional suffix added. ** ^If a suffix is added to the zFilename parameter, it will ** consist of a single "-" character followed by no more than | | | 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 | ** ** [[sqlite3_vfs.xOpen]] ** ^SQLite guarantees that the zFilename parameter to xOpen ** is either a NULL pointer or string obtained ** from xFullPathname() with an optional suffix added. ** ^If a suffix is added to the zFilename parameter, it will ** consist of a single "-" character followed by no more than ** 11 alphanumeric and/or "-" characters. ** ^SQLite further guarantees that ** the string will be valid and unchanged until xClose() is ** called. Because of the previous sentence, ** the [sqlite3_file] can safely store a pointer to the ** filename if it needs to remember the filename for some reason. ** If the zFilename parameter to xOpen is a NULL pointer then xOpen ** must invent its own temporary name for the file. ^Whenever the |
| ︙ | ︙ | |||
1364 1365 1366 1367 1368 1369 1370 | ** scratch memory beyond what is provided by this configuration option, then ** [sqlite3_malloc()] will be used to obtain the memory needed.</dd> ** ** [[SQLITE_CONFIG_PAGECACHE]] <dt>SQLITE_CONFIG_PAGECACHE</dt> ** <dd> ^This option specifies a static memory buffer that SQLite can use for ** the database page cache with the default page cache implementation. ** This configuration should not be used if an application-define page | | | 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 | ** scratch memory beyond what is provided by this configuration option, then ** [sqlite3_malloc()] will be used to obtain the memory needed.</dd> ** ** [[SQLITE_CONFIG_PAGECACHE]] <dt>SQLITE_CONFIG_PAGECACHE</dt> ** <dd> ^This option specifies a static memory buffer that SQLite can use for ** the database page cache with the default page cache implementation. ** This configuration should not be used if an application-define page ** cache implementation is loaded using the SQLITE_CONFIG_PCACHE2 option. ** There are three arguments to this option: A pointer to 8-byte aligned ** memory, the size of each page buffer (sz), and the number of pages (N). ** The sz argument should be the size of the largest database page ** (a power of two between 512 and 32768) plus a little extra for each ** page header. ^The page header size is 20 to 40 bytes depending on ** the host architecture. ^It is harmless, apart from the wasted memory, ** to make sz a little too large. The first |
| ︙ | ︙ | |||
1395 1396 1397 1398 1399 1400 1401 | ** to using its default memory allocator (the system malloc() implementation), ** undoing any prior invocation of [SQLITE_CONFIG_MALLOC]. ^If the ** memory pointer is not NULL and either [SQLITE_ENABLE_MEMSYS3] or ** [SQLITE_ENABLE_MEMSYS5] are defined, then the alternative memory ** allocator is engaged to handle all of SQLites memory allocation needs. ** The first pointer (the memory pointer) must be aligned to an 8-byte ** boundary or subsequent behavior of SQLite will be undefined. | | | | 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 | ** to using its default memory allocator (the system malloc() implementation), ** undoing any prior invocation of [SQLITE_CONFIG_MALLOC]. ^If the ** memory pointer is not NULL and either [SQLITE_ENABLE_MEMSYS3] or ** [SQLITE_ENABLE_MEMSYS5] are defined, then the alternative memory ** allocator is engaged to handle all of SQLites memory allocation needs. ** The first pointer (the memory pointer) must be aligned to an 8-byte ** boundary or subsequent behavior of SQLite will be undefined. ** The minimum allocation size is capped at 2**12. Reasonable values ** for the minimum allocation size are 2**5 through 2**8.</dd> ** ** [[SQLITE_CONFIG_MUTEX]] <dt>SQLITE_CONFIG_MUTEX</dt> ** <dd> ^(This option takes a single argument which is a pointer to an ** instance of the [sqlite3_mutex_methods] structure. The argument specifies ** alternative low-level mutex routines to be used in place ** the mutex routines built into SQLite.)^ ^SQLite makes a copy of the ** content of the [sqlite3_mutex_methods] structure before the call to |
| ︙ | ︙ | |||
1433 1434 1435 1436 1437 1438 1439 | ** [database connection]. The first argument is the ** size of each lookaside buffer slot and the second is the number of ** slots allocated to each database connection.)^ ^(This option sets the ** <i>default</i> lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE] ** verb to [sqlite3_db_config()] can be used to change the lookaside ** configuration on individual connections.)^ </dd> ** | | | | | | 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 | ** [database connection]. The first argument is the ** size of each lookaside buffer slot and the second is the number of ** slots allocated to each database connection.)^ ^(This option sets the ** <i>default</i> lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE] ** verb to [sqlite3_db_config()] can be used to change the lookaside ** configuration on individual connections.)^ </dd> ** ** [[SQLITE_CONFIG_PCACHE2]] <dt>SQLITE_CONFIG_PCACHE2</dt> ** <dd> ^(This option takes a single argument which is a pointer to ** an [sqlite3_pcache_methods2] object. This object specifies the interface ** to a custom page cache implementation.)^ ^SQLite makes a copy of the ** object and uses it for page cache memory allocations.</dd> ** ** [[SQLITE_CONFIG_GETPCACHE2]] <dt>SQLITE_CONFIG_GETPCACHE2</dt> ** <dd> ^(This option takes a single argument which is a pointer to an ** [sqlite3_pcache_methods2] object. SQLite copies of the current ** page cache implementation into that object.)^ </dd> ** ** [[SQLITE_CONFIG_LOG]] <dt>SQLITE_CONFIG_LOG</dt> ** <dd> ^The SQLITE_CONFIG_LOG option takes two arguments: a pointer to a ** function with a call signature of void(*)(void*,int,const char*), ** and a pointer to void. ^If the function pointer is not NULL, it is ** invoked by [sqlite3_log()] to process each logging event. ^If the |
| ︙ | ︙ | |||
1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 | ** specified as part of [ATTACH] commands are interpreted as URIs, regardless ** of whether or not the [SQLITE_OPEN_URI] flag is set when the database ** connection is opened. If it is globally disabled, filenames are ** only interpreted as URIs if the SQLITE_OPEN_URI flag is set when the ** database connection is opened. By default, URI handling is globally ** disabled. The default value may be changed by compiling with the ** [SQLITE_USE_URI] symbol defined. ** </dl> */ #define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ #define SQLITE_CONFIG_MULTITHREAD 2 /* nil */ #define SQLITE_CONFIG_SERIALIZED 3 /* nil */ #define SQLITE_CONFIG_MALLOC 4 /* sqlite3_mem_methods* */ #define SQLITE_CONFIG_GETMALLOC 5 /* sqlite3_mem_methods* */ #define SQLITE_CONFIG_SCRATCH 6 /* void*, int sz, int N */ #define SQLITE_CONFIG_PAGECACHE 7 /* void*, int sz, int N */ #define SQLITE_CONFIG_HEAP 8 /* void*, int nByte, int min */ #define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */ #define SQLITE_CONFIG_MUTEX 10 /* sqlite3_mutex_methods* */ #define SQLITE_CONFIG_GETMUTEX 11 /* sqlite3_mutex_methods* */ /* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */ #define SQLITE_CONFIG_LOOKASIDE 13 /* int int */ | > > > > > | | > > | 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 | ** specified as part of [ATTACH] commands are interpreted as URIs, regardless ** of whether or not the [SQLITE_OPEN_URI] flag is set when the database ** connection is opened. If it is globally disabled, filenames are ** only interpreted as URIs if the SQLITE_OPEN_URI flag is set when the ** database connection is opened. By default, URI handling is globally ** disabled. The default value may be changed by compiling with the ** [SQLITE_USE_URI] symbol defined. ** ** [[SQLITE_CONFIG_PCACHE]] [[SQLITE_CONFIG_GETPCACHE]] ** <dt>SQLITE_CONFIG_PCACHE and SQLITE_CONFNIG_GETPCACHE ** <dd> These options are obsolete and should not be used by new code. ** They are retained for backwards compatibility but are now no-ops. ** </dl> */ #define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ #define SQLITE_CONFIG_MULTITHREAD 2 /* nil */ #define SQLITE_CONFIG_SERIALIZED 3 /* nil */ #define SQLITE_CONFIG_MALLOC 4 /* sqlite3_mem_methods* */ #define SQLITE_CONFIG_GETMALLOC 5 /* sqlite3_mem_methods* */ #define SQLITE_CONFIG_SCRATCH 6 /* void*, int sz, int N */ #define SQLITE_CONFIG_PAGECACHE 7 /* void*, int sz, int N */ #define SQLITE_CONFIG_HEAP 8 /* void*, int nByte, int min */ #define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */ #define SQLITE_CONFIG_MUTEX 10 /* sqlite3_mutex_methods* */ #define SQLITE_CONFIG_GETMUTEX 11 /* sqlite3_mutex_methods* */ /* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */ #define SQLITE_CONFIG_LOOKASIDE 13 /* int int */ #define SQLITE_CONFIG_PCACHE 14 /* no-op */ #define SQLITE_CONFIG_GETPCACHE 15 /* no-op */ #define SQLITE_CONFIG_LOG 16 /* xFunc, void* */ #define SQLITE_CONFIG_URI 17 /* int */ #define SQLITE_CONFIG_PCACHE2 18 /* sqlite3_pcache_methods2* */ #define SQLITE_CONFIG_GETPCACHE2 19 /* sqlite3_pcache_methods2* */ /* ** CAPI3REF: Database Connection Configuration Options ** ** These constants are the available integer configuration options that ** can be passed as the second argument to the [sqlite3_db_config()] interface. ** |
| ︙ | ︙ | |||
1977 1978 1979 1980 1981 1982 1983 | ** ^The sqlite3_vsnprintf() routine is a varargs version of sqlite3_snprintf(). ** ** These routines all implement some additional formatting ** options that are useful for constructing SQL statements. ** All of the usual printf() formatting options apply. In addition, there ** is are "%q", "%Q", and "%z" options. ** | | | 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 | ** ^The sqlite3_vsnprintf() routine is a varargs version of sqlite3_snprintf(). ** ** These routines all implement some additional formatting ** options that are useful for constructing SQL statements. ** All of the usual printf() formatting options apply. In addition, there ** is are "%q", "%Q", and "%z" options. ** ** ^(The %q option works like %s in that it substitutes a nul-terminated ** string from the argument list. But %q also doubles every '\'' character. ** %q is designed for use inside a string literal.)^ By doubling each '\'' ** character it escapes that character and allows it to be inserted into ** the string. ** ** For example, assume the string variable zText contains text as follows: ** |
| ︙ | ︙ | |||
2585 2586 2587 2588 2589 2590 2591 | int flags, /* Flags */ const char *zVfs /* Name of VFS module to use */ ); /* ** CAPI3REF: Obtain Values For URI Parameters ** | | | | > > | | | > > | > > > > > > > > > > > > > > > > > > | | | > > | 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 | int flags, /* Flags */ const char *zVfs /* Name of VFS module to use */ ); /* ** CAPI3REF: Obtain Values For URI Parameters ** ** These are utility routines, useful to VFS implementations, that check ** to see if a database file was a URI that contained a specific query ** parameter, and if so obtains the value of that query parameter. ** ** If F is the database filename pointer passed into the xOpen() method of ** a VFS implementation when the flags parameter to xOpen() has one or ** more of the [SQLITE_OPEN_URI] or [SQLITE_OPEN_MAIN_DB] bits set and ** P is the name of the query parameter, then ** sqlite3_uri_parameter(F,P) returns the value of the P ** parameter if it exists or a NULL pointer if P does not appear as a ** query parameter on F. If P is a query parameter of F ** has no explicit value, then sqlite3_uri_parameter(F,P) returns ** a pointer to an empty string. ** ** The sqlite3_uri_boolean(F,P,B) routine assumes that P is a boolean ** parameter and returns true (1) or false (0) according to the value ** of P. The sqlite3_uri_boolean(F,P,B) routine returns true (1) if the ** value of query parameter P is one of "yes", "true", or "on" in any ** case or if the value begins with a non-zero number. The ** sqlite3_uri_boolean(F,P,B) routines returns false (0) if the value of ** query parameter P is one of "no", "false", or "off" in any case or ** if the value begins with a numeric zero. If P is not a query ** parameter on F or if the value of P is does not match any of the ** above, then sqlite3_uri_boolean(F,P,B) returns (B!=0). ** ** The sqlite3_uri_int64(F,P,D) routine converts the value of P into a ** 64-bit signed integer and returns that integer, or D if P does not ** exist. If the value of P is something other than an integer, then ** zero is returned. ** ** If F is a NULL pointer, then sqlite3_uri_parameter(F,P) returns NULL and ** sqlite3_uri_boolean(F,P,B) returns B. If F is not a NULL pointer and ** is not a database file pathname pointer that SQLite passed into the xOpen ** VFS method, then the behavior of this routine is undefined and probably ** undesirable. */ SQLITE_API const char *sqlite3_uri_parameter(const char *zFilename, const char *zParam); SQLITE_API int sqlite3_uri_boolean(const char *zFile, const char *zParam, int bDefault); SQLITE_API sqlite3_int64 sqlite3_uri_int64(const char*, const char*, sqlite3_int64); /* ** CAPI3REF: Error Codes And Messages ** ** ^The sqlite3_errcode() interface returns the numeric [result code] or ** [extended result code] for the most recent failed sqlite3_* API call |
| ︙ | ︙ | |||
2795 2796 2797 2798 2799 2800 2801 | ** first zero terminator. ^If nByte is non-negative, then it is the maximum ** number of bytes read from zSql. ^When nByte is non-negative, the ** 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> | | > | 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 | ** first zero terminator. ^If nByte is non-negative, then it is the maximum ** number of bytes read from zSql. ^When nByte is non-negative, the ** 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 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. ** ** ^*ppStmt is left pointing to a compiled [prepared statement] that can be |
| ︙ | ︙ | |||
2846 2847 2848 2849 2850 2851 2852 | ** WHERE clause might influence the choice of query plan for a statement, ** then the statement will be automatically recompiled, as if there had been ** 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 | | | 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 | ** WHERE clause might influence the choice of query plan for a statement, ** then the statement will be automatically recompiled, as if there had been ** 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_STAT3] compile-time option is enabled. ** the ** </li> ** </ol> */ SQLITE_API int sqlite3_prepare( sqlite3 *db, /* Database handle */ const char *zSql, /* SQL statement, UTF-8 encoded */ |
| ︙ | ︙ | |||
2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 |
** database. ^The [ATTACH] and [DETACH] statements also cause
** sqlite3_stmt_readonly() to return true since, while those statements
** change the configuration of a database connection, they do not make
** changes to the content of the database files on disk.
*/
SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt);
/*
** CAPI3REF: Dynamically Typed Value Object
** KEYWORDS: {protected sqlite3_value} {unprotected sqlite3_value}
**
** SQLite uses the sqlite3_value object to represent all values
** that can be stored in a database table. SQLite uses dynamic typing
** for the values it stores. ^Values stored in sqlite3_value objects
| > > > > > > > > > > > > > > > > > > > | 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 |
** database. ^The [ATTACH] and [DETACH] statements also cause
** sqlite3_stmt_readonly() to return true since, while those statements
** change the configuration of a database connection, they do not make
** changes to the content of the database files on disk.
*/
SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt);
/*
** CAPI3REF: Determine If A Prepared Statement Has Been Reset
**
** ^The sqlite3_stmt_busy(S) interface returns true (non-zero) if the
** [prepared statement] S has been stepped at least once using
** [sqlite3_step(S)] but has not run to completion and/or has not
** been reset using [sqlite3_reset(S)]. ^The sqlite3_stmt_busy(S)
** interface returns false if S is a NULL pointer. If S is not a
** NULL pointer and is not a pointer to a valid [prepared statement]
** object, then the behavior is undefined and probably undesirable.
**
** This interface can be used in combination [sqlite3_next_stmt()]
** to locate all prepared statements associated with a database
** connection that are in need of being reset. This can be used,
** for example, in diagnostic routines to search for prepared
** statements that are holding a transaction open.
*/
SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt*);
/*
** CAPI3REF: Dynamically Typed Value Object
** KEYWORDS: {protected sqlite3_value} {unprotected sqlite3_value}
**
** SQLite uses the sqlite3_value object to represent all values
** that can be stored in a database table. SQLite uses dynamic typing
** for the values it stores. ^Values stored in sqlite3_value objects
|
| ︙ | ︙ | |||
3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 | ** ^The third argument is the value to bind to the parameter. ** ** ^(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. ** ** ^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(), ** sqlite3_bind_text(), or sqlite3_bind_text16() fails. ** ^If the fifth argument is | > > > > > > > | 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 | ** ^The third argument is the value to bind to the parameter. ** ** ^(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(), ** sqlite3_bind_text(), or sqlite3_bind_text16() fails. ** ^If the fifth argument is |
| ︙ | ︙ | |||
3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 | ** ** ^The sqlite3_data_count(P) interface returns the number of columns in the ** 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. ** ** See also: [sqlite3_column_count()] */ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); /* ** CAPI3REF: Fundamental Datatypes | > > > > > > | 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 | ** ** ^The sqlite3_data_count(P) interface returns the number of columns in the ** 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); /* ** CAPI3REF: Fundamental Datatypes |
| ︙ | ︙ | |||
3448 3449 3450 3451 3452 3453 3454 | ** ^The values returned by [sqlite3_column_bytes()] and ** [sqlite3_column_bytes16()] do not include the zero terminators at the end ** of the string. ^For clarity: the values returned by ** [sqlite3_column_bytes()] and [sqlite3_column_bytes16()] are the number of ** bytes in the string, not the number of characters. ** ** ^Strings returned by sqlite3_column_text() and sqlite3_column_text16(), | | | 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 | ** ^The values returned by [sqlite3_column_bytes()] and ** [sqlite3_column_bytes16()] do not include the zero terminators at the end ** of the string. ^For clarity: the values returned by ** [sqlite3_column_bytes()] and [sqlite3_column_bytes16()] are the number of ** bytes in the string, not the number of characters. ** ** ^Strings returned by sqlite3_column_text() and sqlite3_column_text16(), ** even empty strings, are always zero-terminated. ^The return ** value from sqlite3_column_blob() for a zero-length BLOB is a NULL pointer. ** ** ^The object returned by [sqlite3_column_value()] is an ** [unprotected sqlite3_value] object. An unprotected sqlite3_value object ** may only be used with [sqlite3_bind_value()] and [sqlite3_result_value()]. ** If the [unprotected sqlite3_value] object returned by ** [sqlite3_column_value()] is used in any other way, including calls |
| ︙ | ︙ | |||
4028 4029 4030 4031 4032 4033 4034 | ** the 2nd parameter of the sqlite3_result_text* interfaces. ** ^If the 3rd parameter to the sqlite3_result_text* interfaces ** 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 | | > > > > > | 4124 4125 4126 4127 4128 4129 4130 4131 4132 4133 4134 4135 4136 4137 4138 4139 4140 4141 4142 4143 | ** the 2nd parameter of the sqlite3_result_text* interfaces. ** ^If the 3rd parameter to the sqlite3_result_text* interfaces ** 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. 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 ** sqlite3_result_blob is the special constant SQLITE_STATIC, then SQLite ** assumes that the text or BLOB result is in constant space and does not |
| ︙ | ︙ | |||
4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 | ** returned by sqlite3_db_handle is the same [database connection] ** that was the first argument ** to the [sqlite3_prepare_v2()] call (or its variants) that was used to ** create the statement in the first place. */ SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*); /* ** CAPI3REF: Find the next prepared statement ** ** ^This interface returns a pointer to the next [prepared statement] after ** pStmt associated with the [database connection] pDb. ^If pStmt is NULL ** then this interface returns a pointer to the first prepared statement ** associated with the database connection pDb. ^If no prepared statement | > > > > > > > > > > > > > > > > | 4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 4473 | ** returned by sqlite3_db_handle is the same [database connection] ** that was the first argument ** to the [sqlite3_prepare_v2()] call (or its variants) that was used to ** create the statement in the first place. */ SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*); /* ** CAPI3REF: Return The Filename For A Database Connection ** ** ^The sqlite3_db_filename(D,N) interface returns a pointer to a filename ** associated with database N of connection D. ^The main database file ** has the name "main". If there is no attached database N on the database ** connection D, or if database N is a temporary or in-memory database, then ** a NULL pointer is returned. ** ** ^The filename returned by this function is the output of the ** xFullPathname method of the [VFS]. ^In other words, the filename ** will be an absolute pathname, even if the filename used ** to open the database originally was a URI or relative pathname. */ SQLITE_API const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName); /* ** CAPI3REF: Find the next prepared statement ** ** ^This interface returns a pointer to the next [prepared statement] after ** pStmt associated with the [database connection] pDb. ^If pStmt is NULL ** then this interface returns a pointer to the first prepared statement ** associated with the database connection pDb. ^If no prepared statement |
| ︙ | ︙ | |||
4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 | ** then the commit is converted into a rollback. ** ** ^The sqlite3_commit_hook(D,C,P) and sqlite3_rollback_hook(D,C,P) functions ** return the P argument from the previous call of the same function ** on the same [database connection] D, or NULL for ** the first call for each function on D. ** ** The callback implementation must not do anything that will modify ** the database connection that invoked the callback. Any actions ** to modify the database connection must be deferred until after the ** completion of the [sqlite3_step()] call that triggered the commit ** or rollback hook in the first place. | > > | | | 4495 4496 4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4514 4515 4516 4517 | ** then the commit is converted into a rollback. ** ** ^The sqlite3_commit_hook(D,C,P) and sqlite3_rollback_hook(D,C,P) functions ** return the P argument from the previous call of the same function ** on the same [database connection] D, or NULL for ** the first call for each function on D. ** ** The commit and rollback hook callbacks are not reentrant. ** The callback implementation must not do anything that will modify ** the database connection that invoked the callback. Any actions ** to modify the database connection must be deferred until after the ** completion of the [sqlite3_step()] call that triggered the commit ** or rollback hook in the first place. ** Note that running any other SQL statements, including SELECT statements, ** or merely calling [sqlite3_prepare_v2()] and [sqlite3_step()] will modify ** the database connections for the meaning of "modify" in this paragraph. ** ** ^Registering a NULL function disables the callback. ** ** ^When the commit hook callback routine returns zero, the [COMMIT] ** operation is allowed to continue normally. ^If the commit hook ** returns non-zero, then the [COMMIT] is converted into a [ROLLBACK]. ** ^The rollback hook is invoked on a rollback that results from a commit |
| ︙ | ︙ | |||
4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4514 4515 4516 4517 4518 4519 4520 | ** of heap memory by deallocating non-essential memory allocations ** held by the database library. Memory used to cache database ** pages to improve performance is an example of non-essential memory. ** ^sqlite3_release_memory() returns the number of bytes actually freed, ** which might be more or less than the amount requested. ** ^The sqlite3_release_memory() routine is a no-op returning zero ** if SQLite is not compiled with [SQLITE_ENABLE_MEMORY_MANAGEMENT]. */ SQLITE_API int sqlite3_release_memory(int); /* ** CAPI3REF: Impose A Limit On Heap Size ** ** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the ** soft limit on the amount of heap memory that may be allocated by SQLite. ** ^SQLite strives to keep heap memory utilization below the soft heap ** limit by reducing the number of pages held in the page cache ** as heap memory usages approaches the limit. ** ^The soft heap limit is "soft" because even though SQLite strives to stay ** below the limit, it will exceed the limit rather than generate ** an [SQLITE_NOMEM] error. In other words, the soft heap limit ** is advisory only. ** ** ^The return value from sqlite3_soft_heap_limit64() is the size of | > > > > > > > > > > > > > > > | > | | 4616 4617 4618 4619 4620 4621 4622 4623 4624 4625 4626 4627 4628 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 4649 4650 4651 4652 4653 4654 4655 4656 4657 4658 4659 4660 4661 4662 4663 4664 4665 4666 4667 4668 4669 4670 4671 4672 4673 4674 4675 4676 4677 4678 4679 | ** of heap memory by deallocating non-essential memory allocations ** held by the database library. Memory used to cache database ** pages to improve performance is an example of non-essential memory. ** ^sqlite3_release_memory() returns the number of bytes actually freed, ** which might be more or less than the amount requested. ** ^The sqlite3_release_memory() routine is a no-op returning zero ** if SQLite is not compiled with [SQLITE_ENABLE_MEMORY_MANAGEMENT]. ** ** See also: [sqlite3_db_release_memory()] */ SQLITE_API int sqlite3_release_memory(int); /* ** CAPI3REF: Free Memory Used By A Database Connection ** ** ^The sqlite3_db_release_memory(D) interface attempts to free as much heap ** memory as possible from database connection D. Unlike the ** [sqlite3_release_memory()] interface, this interface is effect even ** when then [SQLITE_ENABLE_MEMORY_MANAGEMENT] compile-time option is ** omitted. ** ** See also: [sqlite3_release_memory()] */ SQLITE_API int sqlite3_db_release_memory(sqlite3*); /* ** CAPI3REF: Impose A Limit On Heap Size ** ** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the ** soft limit on the amount of heap memory that may be allocated by SQLite. ** ^SQLite strives to keep heap memory utilization below the soft heap ** limit by reducing the number of pages held in the page cache ** as heap memory usages approaches the limit. ** ^The soft heap limit is "soft" because even though SQLite strives to stay ** below the limit, it will exceed the limit rather than generate ** an [SQLITE_NOMEM] error. In other words, the soft heap limit ** is advisory only. ** ** ^The return value from sqlite3_soft_heap_limit64() is the size of ** the soft heap limit prior to the call, or negative in the case of an ** error. ^If the argument N is negative ** then no change is made to the soft heap limit. Hence, the current ** size of the soft heap limit can be determined by invoking ** sqlite3_soft_heap_limit64() with a negative argument. ** ** ^If the argument N is zero then the soft heap limit is disabled. ** ** ^(The soft heap limit is not enforced in the current implementation ** if one or more of following conditions are true: ** ** <ul> ** <li> The soft heap limit is set to zero. ** <li> Memory accounting is disabled using a combination of the ** [sqlite3_config]([SQLITE_CONFIG_MEMSTATUS],...) start-time option and ** the [SQLITE_DEFAULT_MEMSTATUS] compile-time option. ** <li> An alternative page cache implementation is specified using ** [sqlite3_config]([SQLITE_CONFIG_PCACHE2],...). ** <li> The page cache allocates from its own memory pool supplied ** by [sqlite3_config]([SQLITE_CONFIG_PAGECACHE],...) rather than ** from the heap. ** </ul>)^ ** ** Beginning with SQLite version 3.7.3, the soft heap limit is enforced ** regardless of whether or not the [SQLITE_ENABLE_MEMORY_MANAGEMENT] |
| ︙ | ︙ | |||
5272 5273 5274 5275 5276 5277 5278 | ** The SQLite source code contains multiple implementations ** of these mutex routines. An appropriate implementation ** is selected automatically at compile-time. ^(The following ** implementations are available in the SQLite core: ** ** <ul> ** <li> SQLITE_MUTEX_OS2 | | | | 5407 5408 5409 5410 5411 5412 5413 5414 5415 5416 5417 5418 5419 5420 5421 5422 5423 5424 5425 5426 5427 5428 5429 | ** The SQLite source code contains multiple implementations ** of these mutex routines. An appropriate implementation ** is selected automatically at compile-time. ^(The following ** implementations are available in the SQLite core: ** ** <ul> ** <li> SQLITE_MUTEX_OS2 ** <li> SQLITE_MUTEX_PTHREADS ** <li> SQLITE_MUTEX_W32 ** <li> SQLITE_MUTEX_NOOP ** </ul>)^ ** ** ^The SQLITE_MUTEX_NOOP implementation is a set of routines ** that does no real locking and is appropriate for use in ** a single-threaded application. ^The SQLITE_MUTEX_OS2, ** SQLITE_MUTEX_PTHREADS, and SQLITE_MUTEX_W32 implementations ** are appropriate for use on OS/2, Unix, and Windows. ** ** ^(If SQLite is compiled with the SQLITE_MUTEX_APPDEF preprocessor ** macro defined (with "-DSQLITE_MUTEX_APPDEF=1"), then no mutex ** implementation is included with the library. In this case the ** application must supply a custom mutex implementation using the ** [SQLITE_CONFIG_MUTEX] option of the sqlite3_config() function |
| ︙ | ︙ | |||
5470 5471 5472 5473 5474 5475 5476 | ** with the SQLITE_DEBUG flag. ^External mutex implementations ** are only required to provide these routines if SQLITE_DEBUG is ** defined and if NDEBUG is not defined. ** ** ^These routines should return true if the mutex in their argument ** is held or not held, respectively, by the calling thread. ** | | | 5605 5606 5607 5608 5609 5610 5611 5612 5613 5614 5615 5616 5617 5618 5619 | ** with the SQLITE_DEBUG flag. ^External mutex implementations ** are only required to provide these routines if SQLITE_DEBUG is ** defined and if NDEBUG is not defined. ** ** ^These routines should return true if the mutex in their argument ** is held or not held, respectively, by the calling thread. ** ** ^The implementation is not required to provide versions of these ** routines that actually work. If the implementation does not provide working ** versions of these routines, it should at least provide stubs that always ** return true so that one does not get spurious assertion failures. ** ** ^If the argument to sqlite3_mutex_held() is a NULL pointer then ** the routine should return 1. This seems counter-intuitive since ** clearly the mutex cannot be held if it does not exist. But |
| ︙ | ︙ | |||
5598 5599 5600 5601 5602 5603 5604 | #define SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS 10 #define SQLITE_TESTCTRL_PENDING_BYTE 11 #define SQLITE_TESTCTRL_ASSERT 12 #define SQLITE_TESTCTRL_ALWAYS 13 #define SQLITE_TESTCTRL_RESERVE 14 #define SQLITE_TESTCTRL_OPTIMIZATIONS 15 #define SQLITE_TESTCTRL_ISKEYWORD 16 | < | | > | 5733 5734 5735 5736 5737 5738 5739 5740 5741 5742 5743 5744 5745 5746 5747 5748 5749 | #define SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS 10 #define SQLITE_TESTCTRL_PENDING_BYTE 11 #define SQLITE_TESTCTRL_ASSERT 12 #define SQLITE_TESTCTRL_ALWAYS 13 #define SQLITE_TESTCTRL_RESERVE 14 #define SQLITE_TESTCTRL_OPTIMIZATIONS 15 #define SQLITE_TESTCTRL_ISKEYWORD 16 #define SQLITE_TESTCTRL_SCRATCHMALLOC 17 #define SQLITE_TESTCTRL_LOCALTIME_FAULT 18 #define SQLITE_TESTCTRL_EXPLAIN_STMT 19 #define SQLITE_TESTCTRL_LAST 19 /* ** CAPI3REF: SQLite Runtime Status ** ** ^This interface is used to retrieve runtime status information ** about the performance of SQLite, and optionally to reset various |
| ︙ | ︙ | |||
5811 5812 5813 5814 5815 5816 5817 5818 5819 5820 5821 5822 5823 5824 5825 5826 | ** ** [[SQLITE_DBSTATUS_STMT_USED]] ^(<dt>SQLITE_DBSTATUS_STMT_USED</dt> ** <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> ** </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 | > > > > > > > > > > > > > > | | 5946 5947 5948 5949 5950 5951 5952 5953 5954 5955 5956 5957 5958 5959 5960 5961 5962 5963 5964 5965 5966 5967 5968 5969 5970 5971 5972 5973 5974 5975 5976 5977 5978 5979 5980 5981 5982 5983 | ** ** [[SQLITE_DBSTATUS_STMT_USED]] ^(<dt>SQLITE_DBSTATUS_STMT_USED</dt> ** <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_CACHE_HIT 7 #define SQLITE_DBSTATUS_CACHE_MISS 8 #define SQLITE_DBSTATUS_MAX 8 /* Largest defined DBSTATUS */ /* ** CAPI3REF: Prepared Statement Status ** ** ^(Each prepared statement maintains various ** [SQLITE_STMTSTATUS counters] that measure the number |
| ︙ | ︙ | |||
5874 5875 5876 5877 5878 5879 5880 | ** ** [[SQLITE_STMTSTATUS_AUTOINDEX]] <dt>SQLITE_STMTSTATUS_AUTOINDEX</dt> ** <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> | < | > > > > > > > > > > > > > > > > | | | | | 6023 6024 6025 6026 6027 6028 6029 6030 6031 6032 6033 6034 6035 6036 6037 6038 6039 6040 6041 6042 6043 6044 6045 6046 6047 6048 6049 6050 6051 6052 6053 6054 6055 6056 6057 6058 6059 6060 6061 6062 6063 6064 6065 6066 6067 6068 6069 6070 6071 6072 6073 6074 6075 6076 6077 6078 6079 6080 6081 6082 6083 6084 6085 6086 6087 6088 6089 6090 6091 6092 6093 6094 6095 6096 6097 6098 6099 6100 6101 |
**
** [[SQLITE_STMTSTATUS_AUTOINDEX]] <dt>SQLITE_STMTSTATUS_AUTOINDEX</dt>
** <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
/*
** CAPI3REF: Custom Page Cache Object
**
** The sqlite3_pcache type is opaque. It is implemented by
** the pluggable module. The SQLite core has no knowledge of
** its size or internal structure and never deals with the
** sqlite3_pcache object except by holding and passing pointers
** to the object.
**
** See [sqlite3_pcache_methods2] for additional information.
*/
typedef struct sqlite3_pcache sqlite3_pcache;
/*
** CAPI3REF: Custom Page Cache Object
**
** The sqlite3_pcache_page object represents a single page in the
** page cache. The page cache will allocate instances of this
** object. Various methods of the page cache use pointers to instances
** of this object as parameters or as their return value.
**
** See [sqlite3_pcache_methods2] for additional information.
*/
typedef struct sqlite3_pcache_page sqlite3_pcache_page;
struct sqlite3_pcache_page {
void *pBuf; /* The content of the page */
void *pExtra; /* Extra information associated with the page */
};
/*
** CAPI3REF: Application Defined Page Cache.
** KEYWORDS: {page cache}
**
** ^(The [sqlite3_config]([SQLITE_CONFIG_PCACHE2], ...) interface can
** register an alternative page cache implementation by passing in an
** instance of the sqlite3_pcache_methods2 structure.)^
** In many applications, most of the heap memory allocated by
** SQLite is used for the page cache.
** By implementing a
** custom page cache using this API, an application can better control
** the amount of memory consumed by SQLite, the way in which
** that memory is allocated and released, and the policies used to
** determine exactly which parts of a database file are cached and for
** how long.
**
** The alternative page cache mechanism is an
** extreme measure that is only needed by the most demanding applications.
** The built-in page cache is recommended for most uses.
**
** ^(The contents of the sqlite3_pcache_methods2 structure are copied to an
** internal buffer by SQLite within the call to [sqlite3_config]. Hence
** the application may discard the parameter after the call to
** [sqlite3_config()] returns.)^
**
** [[the xInit() page cache method]]
** ^(The xInit() method is called once for each effective
** call to [sqlite3_initialize()])^
** (usually only once during the lifetime of the process). ^(The xInit()
** method is passed a copy of the sqlite3_pcache_methods2.pArg value.)^
** The intent of the xInit() method is to set up global data structures
** required by the custom page cache implementation.
** ^(If the xInit() method is NULL, then the
** built-in default page cache is used instead of the application defined
** page cache.)^
**
** [[the xShutdown() page cache method]]
|
| ︙ | ︙ | |||
5950 5951 5952 5953 5954 5955 5956 | ** call to xShutdown(). ** ** [[the xCreate() page cache methods]] ** ^SQLite invokes the xCreate() method to construct a new cache instance. ** SQLite will typically create one cache instance for each open database file, ** though this is not guaranteed. ^The ** first parameter, szPage, is the size in bytes of the pages that must | | | > | | | < < < | | | 6114 6115 6116 6117 6118 6119 6120 6121 6122 6123 6124 6125 6126 6127 6128 6129 6130 6131 6132 6133 6134 6135 6136 | ** call to xShutdown(). ** ** [[the xCreate() page cache methods]] ** ^SQLite invokes the xCreate() method to construct a new cache instance. ** SQLite will typically create one cache instance for each open database file, ** though this is not guaranteed. ^The ** first parameter, szPage, is the size in bytes of the pages that must ** be allocated by the cache. ^szPage will always a power of two. ^The ** second parameter szExtra is a number of bytes of extra storage ** associated with each page cache entry. ^The szExtra parameter will ** a number less than 250. SQLite will use the ** extra szExtra bytes on each page to store metadata about the underlying ** database page on disk. The value passed into szExtra depends ** on the SQLite version, the target platform, and how SQLite was compiled. ** ^The third argument to xCreate(), bPurgeable, is true if the cache being ** created will be used to cache database pages of a file stored on disk, or ** false if it is used for an in-memory database. The cache implementation ** does not have to do anything special based with the value of bPurgeable; ** it is purely advisory. ^On a cache where bPurgeable is false, SQLite will ** never invoke xUnpin() except to deliberately delete a page. ** ^In other words, calls to xUnpin() on a cache with bPurgeable set to ** false will always have the "discard" flag set to true. ** ^Hence, a cache created with bPurgeable false will |
| ︙ | ︙ | |||
5984 5985 5986 5987 5988 5989 5990 | ** ** [[the xPagecount() page cache methods]] ** The xPagecount() method must return the number of pages currently ** stored in the cache, both pinned and unpinned. ** ** [[the xFetch() page cache methods]] ** The xFetch() method locates a page in the cache and returns a pointer to | | > | > > > > | | | | 6146 6147 6148 6149 6150 6151 6152 6153 6154 6155 6156 6157 6158 6159 6160 6161 6162 6163 6164 6165 6166 6167 6168 6169 | ** ** [[the xPagecount() page cache methods]] ** The xPagecount() method must return the number of pages currently ** stored in the cache, both pinned and unpinned. ** ** [[the xFetch() page cache methods]] ** The xFetch() method locates a page in the cache and returns a pointer to ** an sqlite3_pcache_page object associated with that page, or a NULL pointer. ** The pBuf element of the returned sqlite3_pcache_page object will be a ** pointer to a buffer of szPage bytes used to store the content of a ** single database page. The pExtra element of sqlite3_pcache_page will be ** a pointer to the szExtra bytes of extra storage that SQLite has requested ** for each entry in the page cache. ** ** The page to be fetched is determined by the key. ^The minimum key value ** is 1. After it has been retrieved using xFetch, the page is considered ** to be "pinned". ** ** If the requested page is already in the page cache, then the page cache ** implementation must return a pointer to the page buffer with its content ** intact. If the requested page is not already in the cache, then the ** cache implementation should use the value of the createFlag ** parameter to help it determined what action to take: ** |
| ︙ | ︙ | |||
6041 6042 6043 6044 6045 6046 6047 | ** of these pages are pinned, they are implicitly unpinned, meaning that ** they can be safely discarded. ** ** [[the xDestroy() page cache method]] ** ^The xDestroy() method is used to delete a cache allocated by xCreate(). ** All resources associated with the specified cache should be freed. ^After ** calling the xDestroy() method, SQLite considers the [sqlite3_pcache*] | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 6208 6209 6210 6211 6212 6213 6214 6215 6216 6217 6218 6219 6220 6221 6222 6223 6224 6225 6226 6227 6228 6229 6230 6231 6232 6233 6234 6235 6236 6237 6238 6239 6240 6241 6242 6243 6244 6245 6246 6247 6248 6249 6250 6251 6252 6253 6254 6255 6256 6257 6258 6259 6260 6261 6262 6263 6264 6265 6266 6267 6268 |
** of these pages are pinned, they are implicitly unpinned, meaning that
** they can be safely discarded.
**
** [[the xDestroy() page cache method]]
** ^The xDestroy() method is used to delete a cache allocated by xCreate().
** All resources associated with the specified cache should be freed. ^After
** calling the xDestroy() method, SQLite considers the [sqlite3_pcache*]
** handle invalid, and will not use it with any other sqlite3_pcache_methods2
** functions.
**
** [[the xShrink() page cache method]]
** ^SQLite invokes the xShrink() method when it wants the page cache to
** free up as much of heap memory as possible. The page cache implementation
** is not obligated to free any memory, but well-behaved implementations should
** do their best.
*/
typedef struct sqlite3_pcache_methods2 sqlite3_pcache_methods2;
struct sqlite3_pcache_methods2 {
int iVersion;
void *pArg;
int (*xInit)(void*);
void (*xShutdown)(void*);
sqlite3_pcache *(*xCreate)(int szPage, int szExtra, int bPurgeable);
void (*xCachesize)(sqlite3_pcache*, int nCachesize);
int (*xPagecount)(sqlite3_pcache*);
sqlite3_pcache_page *(*xFetch)(sqlite3_pcache*, unsigned key, int createFlag);
void (*xUnpin)(sqlite3_pcache*, sqlite3_pcache_page*, int discard);
void (*xRekey)(sqlite3_pcache*, sqlite3_pcache_page*,
unsigned oldKey, unsigned newKey);
void (*xTruncate)(sqlite3_pcache*, unsigned iLimit);
void (*xDestroy)(sqlite3_pcache*);
void (*xShrink)(sqlite3_pcache*);
};
/*
** This is the obsolete pcache_methods object that has now been replaced
** by sqlite3_pcache_methods2. This object is not used by SQLite. It is
** retained in the header file for backwards compatibility only.
*/
typedef struct sqlite3_pcache_methods sqlite3_pcache_methods;
struct sqlite3_pcache_methods {
void *pArg;
int (*xInit)(void*);
void (*xShutdown)(void*);
sqlite3_pcache *(*xCreate)(int szPage, int bPurgeable);
void (*xCachesize)(sqlite3_pcache*, int nCachesize);
int (*xPagecount)(sqlite3_pcache*);
void *(*xFetch)(sqlite3_pcache*, unsigned key, int createFlag);
void (*xUnpin)(sqlite3_pcache*, void*, int discard);
void (*xRekey)(sqlite3_pcache*, void*, unsigned oldKey, unsigned newKey);
void (*xTruncate)(sqlite3_pcache*, unsigned iLimit);
void (*xDestroy)(sqlite3_pcache*);
};
/*
** CAPI3REF: Online Backup Object
**
** The sqlite3_backup object records state information about an ongoing
** online backup operation. ^The sqlite3_backup object is created by
** a call to [sqlite3_backup_init()] and is destroyed by a call to
|
| ︙ | ︙ |
Changes to src/stash.c.
| ︙ | ︙ | |||
168 169 170 171 172 173 174 |
);
if( g.argc>3 ){
int i;
for(i=3; i<g.argc; i++){
stash_add_file_or_dir(stashid, vid, g.argv[i]);
}
}else{
| | | 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
);
if( g.argc>3 ){
int i;
for(i=3; i<g.argc; i++){
stash_add_file_or_dir(stashid, vid, g.argv[i]);
}
}else{
stash_add_file_or_dir(stashid, vid, g.zLocalRoot);
}
return stashid;
}
/*
** Apply a stash to the current check-out.
*/
|
| ︙ | ︙ | |||
256 257 258 259 260 261 262 |
if( fossil_strcmp(zOrig,zNew)!=0 ){
undo_save(zOrig);
file_delete(zOPath);
}
}
db_finalize(&q);
if( nConflict ){
| > | | | | | | | | | | | 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 |
if( fossil_strcmp(zOrig,zNew)!=0 ){
undo_save(zOrig);
file_delete(zOPath);
}
}
db_finalize(&q);
if( nConflict ){
fossil_print(
"WARNING: %d merge conflicts - see messages above for details.\n",
nConflict);
}
}
/*
** Show the diffs associate with a single stash.
*/
static void stash_diff(int stashid, const char *zDiffCmd, int diffFlags){
Stmt q;
Blob empty;
blob_zero(&empty);
db_prepare(&q,
"SELECT rid, isRemoved, isExec, isLink, origname, newname, delta"
" FROM stashfile WHERE stashid=%d",
stashid
);
while( db_step(&q)==SQLITE_ROW ){
int rid = db_column_int(&q, 0);
int isRemoved = db_column_int(&q, 1);
int isLink = db_column_int(&q, 3);
const char *zOrig = db_column_text(&q, 4);
const char *zNew = db_column_text(&q, 5);
char *zOPath = mprintf("%s%s", g.zLocalRoot, zOrig);
Blob delta;
if( rid==0 ){
db_ephemeral_blob(&q, 6, &delta);
fossil_print("ADDED %s\n", zNew);
diff_print_index(zNew, diffFlags);
diff_file_mem(&empty, &delta, zNew, zDiffCmd, diffFlags);
}else if( isRemoved ){
fossil_print("DELETE %s\n", zOrig);
if( file_wd_islink(zOPath) ){
blob_read_link(&delta, zOPath);
}else{
blob_read_from_file(&delta, zOPath);
}
diff_print_index(zNew, diffFlags);
diff_file_mem(&delta, &empty, zOrig, zDiffCmd, diffFlags);
}else{
Blob a, b, disk;
int isOrigLink = file_wd_islink(zOPath);
db_ephemeral_blob(&q, 6, &delta);
if( isOrigLink ){
blob_read_link(&disk, zOPath);
}else{
blob_read_from_file(&disk, zOPath);
}
fossil_print("CHANGED %s\n", zNew);
if( !isOrigLink != !isLink ){
diff_print_index(zNew, diffFlags);
diff_print_filenames(zOrig, zNew, diffFlags);
printf("cannot compute difference between symlink and regular file\n");
}else{
content_get(rid, &a);
blob_delta_apply(&a, &delta, &b);
diff_file_mem(&disk, &b, zNew, zDiffCmd, diffFlags);
blob_reset(&a);
blob_reset(&b);
}
blob_reset(&disk);
}
blob_reset(&delta);
}
|
| ︙ | ︙ | |||
361 362 363 364 365 366 367 368 369 370 371 372 | /* ** COMMAND: stash ** ** Usage: %fossil stash SUBCOMMAND ARGS... ** ** fossil stash ** fossil stash save ?-m COMMENT? ?FILES...? ** ** Save the current changes in the working tree as a new stash. ** Then revert the changes back to the last check-in. If FILES ** are listed, then only stash and revert the named files. The ** "save" verb can be omitted if and only if there are no other | > | > | | | > < < < < < < | | | | < < < < < | 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 |
/*
** COMMAND: stash
**
** Usage: %fossil stash SUBCOMMAND ARGS...
**
** fossil stash
** fossil stash save ?-m COMMENT? ?FILES...?
** fossil stash snapshot ?-m COMMENT? ?FILES...?
**
** Save the current changes in the working tree as a new stash.
** Then revert the changes back to the last check-in. If FILES
** are listed, then only stash and revert the named files. The
** "save" verb can be omitted if and only if there are no other
** arguments. The "snapshot" verb works the same as "save" but
** omits the revert, keeping the check-out unchanged.
**
** fossil stash list ?--detail?
** fossil stash ls ?-l?
**
** List all changes sets currently stashed. Show information about
** individual files in each changeset if --detail or -l is used.
**
** fossil stash pop
** fossil stash apply ?STASHID?
**
** Apply STASHID or the most recently create stash to the current
** working check-out. The "pop" command deletes that changeset from
** the stash after applying it but the "apply" command retains the
** changeset.
**
** fossil stash goto ?STASHID?
**
** Update to the baseline checkout for STASHID then apply the
** changes of STASHID. Keep STASHID so that it can be reused
** This command is undoable.
**
** fossil stash drop ?STASHID? ?--all?
** fossil stash rm ?STASHID? ?--all?
**
** Forget everything about STASHID. Forget the whole stash if the
** --all flag is used. Individual drops are undoable but --all is not.
**
** fossil stash diff ?STASHID?
** fossil stash gdiff ?STASHID?
**
** Show diffs of the current working directory and what that
** directory would be if STASHID were applied.
*/
void stash_cmd(void){
|
| ︙ | ︙ | |||
453 454 455 456 457 458 459 |
g.argv[1] = "revert";
revert_cmd();
}else
if( memcmp(zCmd, "snapshot", nCmd)==0 ){
stash_create();
}else
if( memcmp(zCmd, "list", nCmd)==0 || memcmp(zCmd, "ls", nCmd)==0 ){
| | > > > > > > | > > > > > > > > > > > > > > > | > > > > > | > > > > | > | | | > | | > | | > > > > > > | 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 |
g.argv[1] = "revert";
revert_cmd();
}else
if( memcmp(zCmd, "snapshot", nCmd)==0 ){
stash_create();
}else
if( memcmp(zCmd, "list", nCmd)==0 || memcmp(zCmd, "ls", nCmd)==0 ){
Stmt q, q2;
int n = 0;
int fDetail = find_option("detail","l",0)!=0;
verify_all_options();
db_prepare(&q,
"SELECT stashid, (SELECT uuid FROM blob WHERE rid=vid),"
" comment, datetime(ctime) FROM stash"
" ORDER BY ctime DESC"
);
if( fDetail ){
db_prepare(&q2, "SELECT isAdded, isRemoved, origname, newname"
" FROM stashfile WHERE stashid=$id");
}
while( db_step(&q)==SQLITE_ROW ){
int stashid = db_column_int(&q, 0);
const char *zCom;
n++;
fossil_print("%5d: [%.14s] on %s\n",
stashid,
db_column_text(&q, 1),
db_column_text(&q, 3)
);
zCom = db_column_text(&q, 2);
if( zCom && zCom[0] ){
fossil_print(" ");
comment_print(zCom, 7, 79);
}
if( fDetail ){
db_bind_int(&q2, "$id", stashid);
while( db_step(&q2)==SQLITE_ROW ){
int isAdded = db_column_int(&q2, 0);
int isRemoved = db_column_int(&q2, 1);
const char *zOrig = db_column_text(&q2, 2);
const char *zNew = db_column_text(&q2, 3);
if( isAdded ){
fossil_print(" ADD %s\n", zNew);
}else if( isRemoved ){
fossil_print(" REMOVE %s\n", zOrig);
}else if( fossil_strcmp(zOrig,zNew)!=0 ){
fossil_print(" RENAME %s -> %s\n", zOrig, zNew);
}else{
fossil_print(" EDIT %s\n", zOrig);
}
}
db_reset(&q2);
}
}
db_finalize(&q);
if( fDetail ) db_finalize(&q2);
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("drop STASHID");
if( allFlag ){
Blob ans;
blob_zero(&ans);
prompt_user("This action is not undoable. Continue (y/N)? ", &ans);
if( blob_str(&ans)[0]=='y' ){
db_multi_exec("DELETE FROM stash; DELETE FROM stashfile;");
}
}else{
stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
undo_begin();
undo_save_stash(stashid);
stash_drop(stashid);
undo_finish();
}
}else
if( memcmp(zCmd, "pop", nCmd)==0 ){
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("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("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);
db_multi_exec("UPDATE vfile SET mtime=0 WHERE pathname IN "
"(SELECT origname FROM stashfile WHERE stashid=%d)",
stashid);
undo_finish();
}else
if( memcmp(zCmd, "diff", nCmd)==0 ){
const char *zDiffCmd = db_get("diff-command", 0);
int diffFlags = diff_options();
if( g.argc>4 ) usage("diff STASHID");
stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
stash_diff(stashid, zDiffCmd, diffFlags);
}else
if( memcmp(zCmd, "gdiff", nCmd)==0 ){
const char *zDiffCmd = db_get("gdiff-command", 0);
int diffFlags = diff_options();
if( g.argc>4 ) usage("diff STASHID");
stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
stash_diff(stashid, zDiffCmd, diffFlags);
}else
if( memcmp(zCmd, "help", nCmd)==0 ){
g.argv[1] = "help";
g.argv[2] = "stash";
g.argc = 3;
help_cmd();
}else
{
usage("SUBCOMMAND ARGS...");
}
db_end_transaction(0);
}
|
Changes to src/style.c.
| ︙ | ︙ | |||
208 209 210 211 212 213 214 |
@ if {[info exists login]} {
@ puts "Logged in as $login"
@ } else {
@ puts "Not logged in"
@ }
@ </th1></div>
@ </div>
| | > | | | | | | | | | | | | 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
@ if {[info exists login]} {
@ puts "Logged in as $login"
@ } else {
@ puts "Not logged in"
@ }
@ </th1></div>
@ </div>
@ <div class="mainmenu">
@ <th1>
@ html "<a href='$home$index_page'>Home</a>\n"
@ if {[anycap jor]} {
@ html "<a href='$home/timeline'>Timeline</a>\n"
@ }
@ if {[hascap oh]} {
@ html "<a href='$home/dir?ci=tip'>Files</a>\n"
@ }
@ if {[hascap o]} {
@ html "<a href='$home/brlist'>Branches</a>\n"
@ html "<a href='$home/taglist'>Tags</a>\n"
@ }
@ if {[hascap r]} {
@ html "<a href='$home/reportlist'>Tickets</a>\n"
@ }
@ if {[hascap j]} {
@ html "<a href='$home/wiki'>Wiki</a>\n"
@ }
@ if {[hascap s]} {
@ html "<a href='$home/setup'>Admin</a>\n"
@ } elseif {[hascap a]} {
@ html "<a href='$home/setup_ulist'>Users</a>\n"
@ }
@ if {[info exists login]} {
@ html "<a href='$home/login'>Logout</a>\n"
@ } else {
@ html "<a href='$home/login'>Login</a>\n"
@ }
@ </th1></div>
;
/*
** The default page footer
*/
|
| ︙ | ︙ | |||
318 319 320 321 322 323 324 | @ text-align: center; @ letter-spacing: 1px; @ background-color: #558195; @ color: white; @ } @ @ /* The submenu bar that *sometimes* appears below the main menu */ | | | > | | 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 |
@ text-align: center;
@ letter-spacing: 1px;
@ background-color: #558195;
@ color: white;
@ }
@
@ /* The submenu bar that *sometimes* appears below the main menu */
@ div.submenu, div.sectionmenu {
@ padding: 3px 10px 3px 0px;
@ font-size: 0.9em;
@ text-align: center;
@ background-color: #456878;
@ color: white;
@ }
@ div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited,
@ div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited {
@ padding: 3px 10px 3px 10px;
@ color: white;
@ text-decoration: none;
@ }
@ div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover {
@ color: #558195;
@ background-color: white;
@ }
@
@ /* All page content from the bottom of the menu or submenu down to
@ ** the footer */
@ div.content {
|
| ︙ | ︙ | |||
394 395 396 397 398 399 400 |
@
@ /* The label/value pairs on (for example) the ci page */
@ table.label-value th {
@ vertical-align: top;
@ text-align: right;
@ padding: 0.2ex 2ex;
@ }
| < | 396 397 398 399 400 401 402 403 404 405 406 407 408 409 |
@
@ /* The label/value pairs on (for example) the ci page */
@ table.label-value th {
@ vertical-align: top;
@ text-align: right;
@ padding: 0.2ex 2ex;
@ }
;
/* The following table contains bits of default CSS that must
** be included if they are not found in the application-defined
** CSS.
*/
|
| ︙ | ︙ | |||
468 469 470 471 472 473 474 |
{ "td.timelineTime",
"the format for the timeline time display",
@ vertical-align: top;
@ text-align: right;
},
{ "td.timelineGraph",
"the format for the grap placeholder cells in timelines",
| | | 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 |
{ "td.timelineTime",
"the format for the timeline time display",
@ vertical-align: top;
@ text-align: right;
},
{ "td.timelineGraph",
"the format for the grap placeholder cells in timelines",
@ width: 20px;
@ text-align: left;
@ vertical-align: top;
},
{ "a.tagLink",
"the format for the tag links",
@
},
|
| ︙ | ︙ | |||
562 563 564 565 566 567 568 |
{ "td.usetupColumnLayout",
"format of the columns on the user setup list page",
@ vertical-align: top
},
{ "table.usetupUserList",
"format for the user list table on the user setup page",
@ outline-style: double;
| | | 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 |
{ "td.usetupColumnLayout",
"format of the columns on the user setup list page",
@ vertical-align: top
},
{ "table.usetupUserList",
"format for the user list table on the user setup page",
@ outline-style: double;
@ outline-width: 1px;
@ padding: 10px;
},
{ "th.usetupListUser",
"format for table header user in user list on user setup page",
@ text-align: right;
@ padding-right: 20px;
},
|
| ︙ | ︙ | |||
686 687 688 689 690 691 692 |
"format for example table cells on the report edit page",
@ border-width: thin;
@ border-color: #000000;
@ border-style: solid;
},
{ "input.checkinUserColor",
"format for user color input on checkin edit page",
| | | | | | | | > > > > > | | 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 |
"format for example table cells on the report edit page",
@ border-width: thin;
@ border-color: #000000;
@ border-style: solid;
},
{ "input.checkinUserColor",
"format for user color input on checkin edit page",
@ /* no special definitions, class defined, to enable color pickers, f.e.:
@ ** add the color picker found at http:jscolor.com as java script include
@ ** to the header and configure the java script file with
@ ** 1. use as bindClass :checkinUserColor
@ ** 2. change the default hash adding behaviour to ON
@ ** or change the class defition of element identified by id="clrcust"
@ ** to a standard jscolor definition with java script in the footer. */
},
{ "div.endContent",
"format for end of content area, to be used to clear page flow(sidebox on branch,..",
@ clear: both;
},
{ "p.generalError",
"format for general errors",
@ color: red;
},
{ "p.tktsetupError",
"format for tktsetup errors",
@ color: red;
@ font-weight: bold;
},
{ "p.xfersetupError",
"format for xfersetup errors",
@ color: red;
@ font-weight: bold;
},
{ "p.thmainError",
"format for th script errors",
@ color: red;
@ font-weight: bold;
},
{ "span.thTrace",
"format for th script trace messages",
@ color: red;
},
{ "p.reportError",
"format for report configuration errors",
@ color: red;
@ font-weight: bold;
},
{ "blockquote.reportError",
"format for report configuration errors",
@ color: red;
|
| ︙ | ︙ | |||
743 744 745 746 747 748 749 750 751 752 753 754 755 756 |
@ color: red;
},
{ "ul.filelist",
"List of files in a timeline",
@ margin-top: 3px;
@ line-height: 100%;
},
{ 0,
0,
0
}
};
/*
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 |
@ color: red;
},
{ "ul.filelist",
"List of files in a timeline",
@ margin-top: 3px;
@ line-height: 100%;
},
{ "div.sbsdiff",
"side-by-side diff display",
@ font-family: monospace;
@ font-size: smaller;
@ white-space: pre;
},
{ "div.udiff",
"context diff display",
@ font-family: monospace;
@ white-space: pre;
},
{ "span.diffchng",
"changes in a diff",
@ background-color: #c0c0ff;
},
{ "span.diffadd",
"added code in a diff",
@ background-color: #c0ffc0;
},
{ "span.diffrm",
"deleted in a diff",
@ background-color: #ffc8c8;
},
{ "span.diffhr",
"suppressed lines in a diff",
@ color: #0000ff;
},
{ "span.diffln",
"line nubmers in a diff",
@ color: #a0a0a0;
},
{ 0,
0,
0
}
};
/*
|
| ︙ | ︙ | |||
801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 |
/*
** WEBPAGE: test_env
*/
void page_test_env(void){
char c;
int i;
char zCap[30];
login_check_credentials();
style_header("Environment Test");
#if !defined(_WIN32)
@ uid=%d(getuid()), gid=%d(getgid())<br />
#endif
@ g.zBaseURL = %h(g.zBaseURL)<br />
@ g.zTop = %h(g.zTop)<br />
for(i=0, c='a'; c<='z'; c++){
if( login_has_capability(&c, 1) ) zCap[i++] = c;
}
zCap[i] = 0;
@ g.userUid = %d(g.userUid)<br />
@ g.zLogin = %h(g.zLogin)<br />
@ capabilities = %s(zCap)<br />
@ <hr>
| > > > > > > > > > > > > > > | | 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 |
/*
** 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 && !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 />
for(i=0, c='a'; c<='z'; c++){
if( login_has_capability(&c, 1) ) zCap[i++] = c;
}
zCap[i] = 0;
@ g.userUid = %d(g.userUid)<br />
@ g.zLogin = %h(g.zLogin)<br />
@ capabilities = %s(zCap)<br />
@ <hr>
P("HTTP_USER_AGENT");
cgi_print_all(atoi(PD("showall","0")));
if( g.perm.Setup ){
const char *zRedir = P("redirect");
if( zRedir ) cgi_redirect(zRedir);
}
style_footer();
}
|
Changes to src/tar.c.
| ︙ | ︙ | |||
42 43 44 45 46 47 48 | /* ** Begin the process of generating a tarball. ** ** Initialize the GZIP compressor and the table of directory names. */ | | | | 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
/*
** Begin the process of generating a tarball.
**
** Initialize the GZIP compressor and the table of directory names.
*/
static void tar_begin(sqlite3_int64 mTime){
assert( tball.aHdr==0 );
tball.aHdr = fossil_malloc(512+512);
memset(tball.aHdr, 0, 512+512);
tball.zSpaces = (char*)&tball.aHdr[512];
/* zPrevDir init */
tball.zPrevDir = NULL;
tball.nPrevDirAlloc = 0;
/* scratch buffer init */
blob_zero(&tball.pax);
memcpy(&tball.aHdr[108], "0000000", 8); /* Owner ID */
memcpy(&tball.aHdr[116], "0000000", 8); /* Group ID */
memcpy(&tball.aHdr[257], "ustar\00000", 8); /* POSIX.1 format */
memcpy(&tball.aHdr[265], "nobody", 7); /* Owner name */
memcpy(&tball.aHdr[297], "nobody", 7); /* Group name */
gzip_begin(mTime);
db_multi_exec(
"CREATE TEMP TABLE dir(name UNIQUE);"
);
}
/*
|
| ︙ | ︙ | |||
425 426 427 428 429 430 431 |
int i;
Blob zip;
Blob file;
if( g.argc<3 ){
usage("ARCHIVE FILE....");
}
sqlite3_open(":memory:", &g.db);
| | | 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 |
int i;
Blob zip;
Blob file;
if( g.argc<3 ){
usage("ARCHIVE FILE....");
}
sqlite3_open(":memory:", &g.db);
tar_begin(0);
for(i=3; i<g.argc; i++){
blob_zero(&file);
blob_read_from_file(&file, g.argv[i]);
tar_add_file(g.argv[i], &file,
file_wd_perm(g.argv[i]), file_wd_mtime(g.argv[i]));
blob_reset(&file);
}
|
| ︙ | ︙ | |||
471 472 473 474 475 476 477 |
content_get(rid, &mfile);
if( blob_size(&mfile)==0 ){
blob_zero(pTar);
return;
}
blob_zero(&hash);
blob_zero(&filename);
| < > | 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 |
content_get(rid, &mfile);
if( blob_size(&mfile)==0 ){
blob_zero(pTar);
return;
}
blob_zero(&hash);
blob_zero(&filename);
if( zDir && zDir[0] ){
blob_appendf(&filename, "%s/", zDir);
}
nPrefix = blob_size(&filename);
pManifest = manifest_get(rid, CFTYPE_MANIFEST);
if( pManifest ){
mTime = (pManifest->rDate - 2440587.5)*86400.0;
tar_begin(mTime);
if( db_get_boolean("manifest", 0) ){
blob_append(&filename, "manifest", -1);
zName = blob_str(&filename);
tar_add_file(zName, &mfile, 0, mTime);
sha1sum_blob(&mfile, &hash);
blob_reset(&mfile);
blob_append(&hash, "\n", 1);
|
| ︙ | ︙ | |||
511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 |
}
}
}else{
sha1sum_blob(&mfile, &hash);
blob_append(&filename, blob_str(&hash), 16);
zName = blob_str(&filename);
mTime = db_int64(0, "SELECT (julianday('now') - 2440587.5)*86400.0;");
tar_add_file(zName, &mfile, 0, mTime);
}
manifest_destroy(pManifest);
blob_reset(&mfile);
blob_reset(&filename);
tar_finish(pTar);
}
/*
| > | | | 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 |
}
}
}else{
sha1sum_blob(&mfile, &hash);
blob_append(&filename, blob_str(&hash), 16);
zName = blob_str(&filename);
mTime = db_int64(0, "SELECT (julianday('now') - 2440587.5)*86400.0;");
tar_begin(mTime);
tar_add_file(zName, &mfile, 0, mTime);
}
manifest_destroy(pManifest);
blob_reset(&mfile);
blob_reset(&filename);
tar_finish(pTar);
}
/*
** COMMAND: tarball*
**
** Usage: %fossil tarball VERSION OUTPUTFILE [--name DIRECTORYNAME] [-R|--repository REPO]
**
** Generate a compressed tarball for a specified version. If the --name
** option is used, its argument becomes the name of the top-level directory
** in the resulting tarball. If --name is omitted, the top-level directory
** named is derived from the project name, the check-in date and time, and
** the artifact ID of the check-in.
*/
|
| ︙ | ︙ |
Changes to src/th.c.
| ︙ | ︙ | |||
1815 1816 1817 1818 1819 1820 1821 |
if( pExpr->pOp==0 ){
/* A literal */
rc = thSubstWord(interp, pExpr->zValue, pExpr->nValue);
}else{
int eArgType = 0; /* Actual type of arguments */
/* Argument values */
| | | | 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 |
if( pExpr->pOp==0 ){
/* A literal */
rc = thSubstWord(interp, pExpr->zValue, pExpr->nValue);
}else{
int eArgType = 0; /* Actual type of arguments */
/* Argument values */
int iLeft = 0;
int iRight = 0;
double fLeft;
double fRight;
/* Left and right arguments as strings */
char *zLeft = 0; int nLeft = 0;
char *zRight = 0; int nRight = 0;
|
| ︙ | ︙ |
Changes to src/th.h.
| ︙ | ︙ | |||
152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
/*
** Interfaces to register the language extensions.
*/
int th_register_language(Th_Interp *interp); /* th_lang.c */
int th_register_sqlite(Th_Interp *interp); /* th_sqlite.c */
int th_register_vfs(Th_Interp *interp); /* th_vfs.c */
int th_register_testvfs(Th_Interp *interp); /* th_testvfs.c */
/*
** General purpose hash table from th_lang.c.
*/
typedef struct Th_Hash Th_Hash;
typedef struct Th_HashEntry Th_HashEntry;
struct Th_HashEntry {
| > | 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
/*
** Interfaces to register the language extensions.
*/
int th_register_language(Th_Interp *interp); /* th_lang.c */
int th_register_sqlite(Th_Interp *interp); /* th_sqlite.c */
int th_register_vfs(Th_Interp *interp); /* th_vfs.c */
int th_register_testvfs(Th_Interp *interp); /* th_testvfs.c */
int th_register_tcl(Th_Interp *interp, void *pContext); /* th_tcl.c */
/*
** General purpose hash table from th_lang.c.
*/
typedef struct Th_Hash Th_Hash;
typedef struct Th_HashEntry Th_HashEntry;
struct Th_HashEntry {
|
| ︙ | ︙ |
Changes to src/th_lang.c.
| ︙ | ︙ | |||
1052 1053 1054 1055 1056 1057 1058 |
{"breakpoint", breakpoint_command, 0},
{"return", return_command, 0},
{"break", simple_command, (void *)TH_BREAK},
{"continue", simple_command, (void *)TH_CONTINUE},
{"error", simple_command, (void *)TH_ERROR},
| | > > | | 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 |
{"breakpoint", breakpoint_command, 0},
{"return", return_command, 0},
{"break", simple_command, (void *)TH_BREAK},
{"continue", simple_command, (void *)TH_CONTINUE},
{"error", simple_command, (void *)TH_ERROR},
{0, 0, 0}
};
int i;
/* Add the language commands. */
for(i=0; i<(sizeof(aCommand)/sizeof(aCommand[0])); i++){
void *ctx;
if ( !aCommand[i].zName || !aCommand[i].xProc ) continue;
ctx = aCommand[i].pContext;
Th_CreateCommand(interp, aCommand[i].zName, aCommand[i].xProc, ctx, 0);
}
return TH_OK;
}
|
Changes to src/th_main.c.
| ︙ | ︙ | |||
91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
z = htmlize(z, n);
n = strlen(z);
}
if( g.cgiOutput ){
cgi_append_content(z, n);
}else{
fwrite(z, 1, n, stdout);
}
if( encode ) free((char*)z);
}
}
/*
** TH command: puts STRING
| > | 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
z = htmlize(z, n);
n = strlen(z);
}
if( g.cgiOutput ){
cgi_append_content(z, n);
}else{
fwrite(z, 1, n, stdout);
fflush(stdout);
}
if( encode ) free((char*)z);
}
}
/*
** TH command: puts STRING
|
| ︙ | ︙ | |||
332 333 334 335 336 337 338 339 340 341 342 343 344 345 |
}
}
if( n<iMin ) n = iMin;
if( n>iMax ) n = iMax;
Th_SetResultInt(interp, n);
return TH_OK;
}
/*
** Make sure the interpreter has been initialized. Initialize it if
** it has not been already.
**
** The interpreter is stored in the g.interp global variable.
*/
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 |
}
}
if( n<iMin ) n = iMin;
if( n>iMax ) n = iMax;
Th_SetResultInt(interp, n);
return TH_OK;
}
/*
** TH1 command: repository ?BOOLEAN?
**
** Return the fully qualified file name of the open repository or an empty
** string if one is not currently open. Optionally, it will attempt to open
** the repository if the boolean argument is non-zero.
*/
static int repositoryCmd(
Th_Interp *interp,
void *p,
int argc,
const char **argv,
int *argl
){
int openRepository;
if( argc!=1 && argc!=2 ){
return Th_WrongNumArgs(interp, "repository ?BOOLEAN?");
}
if( argc==2 ){
if( Th_ToInt(interp, argv[1], argl[1], &openRepository) ){
return TH_ERROR;
}
if( openRepository ) db_find_and_open_repository(OPEN_OK_NOT_FOUND, 0);
}
Th_SetResult(interp, g.zRepositoryName, -1);
return TH_OK;
}
/*
** Make sure the interpreter has been initialized. Initialize it if
** it has not been already.
**
** The interpreter is stored in the g.interp global variable.
*/
|
| ︙ | ︙ | |||
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 |
{"linecount", linecntCmd, 0},
{"hascap", hascapCmd, 0},
{"htmlize", htmlizeCmd, 0},
{"date", dateCmd, 0},
{"html", putsCmd, 0},
{"puts", putsCmd, (void*)1},
{"wiki", wikiCmd, 0},
};
if( g.interp==0 ){
int i;
g.interp = Th_CreateInterp(&vtab);
th_register_language(g.interp); /* Basic scripting commands. */
for(i=0; i<sizeof(aCommand)/sizeof(aCommand[0]); i++){
Th_CreateCommand(g.interp, aCommand[i].zName, aCommand[i].xProc,
aCommand[i].pContext, 0);
}
}
}
/*
| > > > > > > > > | 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 |
{"linecount", linecntCmd, 0},
{"hascap", hascapCmd, 0},
{"htmlize", htmlizeCmd, 0},
{"date", dateCmd, 0},
{"html", putsCmd, 0},
{"puts", putsCmd, (void*)1},
{"wiki", wikiCmd, 0},
{"repository", repositoryCmd, 0},
{0, 0, 0}
};
if( g.interp==0 ){
int i;
g.interp = Th_CreateInterp(&vtab);
th_register_language(g.interp); /* Basic scripting commands. */
#ifdef FOSSIL_ENABLE_TCL
if( getenv("TH1_ENABLE_TCL")!=0 || db_get_boolean("tcl", 0) ){
th_register_tcl(g.interp, &g.tcl); /* Tcl integration commands. */
}
#endif
for(i=0; i<sizeof(aCommand)/sizeof(aCommand[0]); i++){
if ( !aCommand[i].zName || !aCommand[i].xProc ) continue;
Th_CreateCommand(g.interp, aCommand[i].zName, aCommand[i].xProc,
aCommand[i].pContext, 0);
}
}
}
/*
|
| ︙ | ︙ | |||
480 481 482 483 484 485 486 487 488 |
int rc = TH_OK;
char *zResult;
Th_FossilInit();
while( z[i] ){
if( z[i]=='$' && (n = validVarName(&z[i+1]))>0 ){
const char *zVar;
int nVar;
sendText(z, i, 0);
if( z[i+1]=='<' ){
| > | | > | | 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 |
int rc = TH_OK;
char *zResult;
Th_FossilInit();
while( z[i] ){
if( z[i]=='$' && (n = validVarName(&z[i+1]))>0 ){
const char *zVar;
int nVar;
int encode = 1;
sendText(z, i, 0);
if( z[i+1]=='<' ){
/* Variables of the form $<aaa> are html escaped */
zVar = &z[i+2];
nVar = n-2;
}else{
/* Variables of the form $aaa are output raw */
zVar = &z[i+1];
nVar = n;
encode = 0;
}
rc = Th_GetVar(g.interp, (char*)zVar, nVar);
z += i+1+n;
i = 0;
zResult = (char*)Th_GetResult(g.interp, &n);
sendText((char*)zResult, n, encode);
}else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){
sendText(z, i, 0);
z += i+5;
for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){}
rc = Th_Eval(g.interp, 0, (const char*)z, i);
if( rc!=TH_OK ) break;
z += i;
|
| ︙ | ︙ | |||
527 528 529 530 531 532 533 534 535 536 537 |
** COMMAND: test-th-render
*/
void test_th_render(void){
Blob in;
if( g.argc<3 ){
usage("FILE");
}
blob_zero(&in);
blob_read_from_file(&in, g.argv[2]);
Th_Render(blob_str(&in));
}
| > | 567 568 569 570 571 572 573 574 575 576 577 578 |
** COMMAND: test-th-render
*/
void test_th_render(void){
Blob in;
if( g.argc<3 ){
usage("FILE");
}
db_open_config(0); /* Needed for global "tcl" setting. */
blob_zero(&in);
blob_read_from_file(&in, g.argv[2]);
Th_Render(blob_str(&in));
}
|
Added src/th_tcl.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 |
/*
** 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.
**
*******************************************************************************
** This file contains code used to bridge the TH1 and Tcl scripting languages.
*/
#include "config.h"
#ifdef FOSSIL_ENABLE_TCL
#include "th.h"
#include "tcl.h"
/*
** Are we being compiled against Tcl 8.6 or higher?
*/
#if (TCL_MAJOR_VERSION > 8) || \
((TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION >= 6))
/*
** Workaround NRE-specific issue in Tcl_EvalObjCmd (SF bug #3399564) by using
** Tcl_EvalObjv instead of invoking the objProc directly.
*/
#define USE_TCL_EVALOBJV 1
#endif
/*
** These macros are designed to reduce the redundant code required to marshal
** arguments from TH1 to Tcl.
*/
#define USE_ARGV_TO_OBJV() \
int objc; \
Tcl_Obj **objv; \
int i;
#define COPY_ARGV_TO_OBJV() \
objc = argc-1; \
objv = (Tcl_Obj **)ckalloc((unsigned)(objc * sizeof(Tcl_Obj *))); \
for(i=1; i<argc; i++){ \
objv[i-1] = Tcl_NewStringObj(argv[i], argl[i]); \
Tcl_IncrRefCount(objv[i-1]); \
}
#define FREE_ARGV_TO_OBJV() \
for(i=1; i<argc; i++){ \
Tcl_DecrRefCount(objv[i-1]); \
} \
ckfree((char *)objv);
/*
** Fetch the Tcl interpreter from the specified void pointer, cast to a Tcl
** context.
*/
#define GET_CTX_TCL_INTERP(ctx) \
((struct TclContext *)(ctx))->interp
/*
** Creates and initializes a Tcl interpreter for use with the specified TH1
** interpreter. Stores the created Tcl interpreter in the Tcl context supplied
** by the caller. This must be declared here because quite a few functions in
** this file need to use it before it can be defined.
*/
static int createTclInterp(Th_Interp *interp, void *pContext);
/*
** Returns the Tcl interpreter result as a string with the associated length.
** If the Tcl interpreter or the Tcl result are NULL, the length will be 0.
** If the length pointer is NULL, the length will not be stored.
*/
static char *getTclResult(
Tcl_Interp *pInterp,
int *pN
){
Tcl_Obj *resultPtr;
if( !pInterp ){ /* This should not happen. */
if( pN ) *pN = 0;
return 0;
}
resultPtr = Tcl_GetObjResult(pInterp);
if( !resultPtr ){ /* This should not happen either? */
if( pN ) *pN = 0;
return 0;
}
return Tcl_GetStringFromObj(resultPtr, pN);
}
/*
** Tcl context information used by TH1. This structure definition has been
** copied from and should be kept in sync with the one in "main.c".
*/
struct TclContext {
int argc;
char **argv;
Tcl_Interp *interp;
};
/*
** Syntax:
**
** tclEval arg ?arg ...?
*/
static int tclEval_command(
Th_Interp *interp,
void *ctx,
int argc,
const char **argv,
int *argl
){
Tcl_Interp *tclInterp;
Tcl_Obj *objPtr;
int rc;
int nResult;
const char *zResult;
if ( createTclInterp(interp, ctx)!=TH_OK ){
return TH_ERROR;
}
if( argc<2 ){
return Th_WrongNumArgs(interp, "tclEval arg ?arg ...?");
}
tclInterp = GET_CTX_TCL_INTERP(ctx);
if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
return TH_ERROR;
}
Tcl_Preserve((ClientData)tclInterp);
if( argc==2 ){
objPtr = Tcl_NewStringObj(argv[1], argl[1]);
Tcl_IncrRefCount(objPtr);
rc = Tcl_EvalObjEx(tclInterp, objPtr, 0);
Tcl_DecrRefCount(objPtr);
}else{
USE_ARGV_TO_OBJV();
COPY_ARGV_TO_OBJV();
objPtr = Tcl_ConcatObj(objc, objv);
Tcl_IncrRefCount(objPtr);
rc = Tcl_EvalObjEx(tclInterp, objPtr, 0);
Tcl_DecrRefCount(objPtr);
FREE_ARGV_TO_OBJV();
}
zResult = getTclResult(tclInterp, &nResult);
Th_SetResult(interp, zResult, nResult);
Tcl_Release((ClientData)tclInterp);
return rc;
}
/*
** Syntax:
**
** tclExpr arg ?arg ...?
*/
static int tclExpr_command(
Th_Interp *interp,
void *ctx,
int argc,
const char **argv,
int *argl
){
Tcl_Interp *tclInterp;
Tcl_Obj *objPtr;
Tcl_Obj *resultObjPtr;
int rc;
int nResult;
const char *zResult;
if ( createTclInterp(interp, ctx)!=TH_OK ){
return TH_ERROR;
}
if( argc<2 ){
return Th_WrongNumArgs(interp, "tclExpr arg ?arg ...?");
}
tclInterp = GET_CTX_TCL_INTERP(ctx);
if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
return TH_ERROR;
}
Tcl_Preserve((ClientData)tclInterp);
if( argc==2 ){
objPtr = Tcl_NewStringObj(argv[1], argl[1]);
Tcl_IncrRefCount(objPtr);
rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr);
Tcl_DecrRefCount(objPtr);
}else{
USE_ARGV_TO_OBJV();
COPY_ARGV_TO_OBJV();
objPtr = Tcl_ConcatObj(objc, objv);
Tcl_IncrRefCount(objPtr);
rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr);
Tcl_DecrRefCount(objPtr);
FREE_ARGV_TO_OBJV();
}
if( rc==TCL_OK ){
zResult = Tcl_GetStringFromObj(resultObjPtr, &nResult);
}else{
zResult = getTclResult(tclInterp, &nResult);
}
Th_SetResult(interp, zResult, nResult);
if( rc==TCL_OK ) Tcl_DecrRefCount(resultObjPtr);
Tcl_Release((ClientData)tclInterp);
return rc;
}
/*
** Syntax:
**
** tclInvoke command ?arg ...?
*/
static int tclInvoke_command(
Th_Interp *interp,
void *ctx,
int argc,
const char **argv,
int *argl
){
Tcl_Interp *tclInterp;
#ifndef USE_TCL_EVALOBJV
Tcl_Command command;
Tcl_CmdInfo cmdInfo;
#endif
int rc;
int nResult;
const char *zResult;
#ifndef USE_TCL_EVALOBJV
Tcl_Obj *objPtr;
#endif
USE_ARGV_TO_OBJV();
if ( createTclInterp(interp, ctx)!=TH_OK ){
return TH_ERROR;
}
if( argc<2 ){
return Th_WrongNumArgs(interp, "tclInvoke command ?arg ...?");
}
tclInterp = GET_CTX_TCL_INTERP(ctx);
if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
return TH_ERROR;
}
Tcl_Preserve((ClientData)tclInterp);
#ifndef USE_TCL_EVALOBJV
objPtr = Tcl_NewStringObj(argv[1], argl[1]);
Tcl_IncrRefCount(objPtr);
command = Tcl_GetCommandFromObj(tclInterp, objPtr);
if( !command || Tcl_GetCommandInfoFromToken(command,&cmdInfo)==0 ){
Th_ErrorMessage(interp, "Tcl command not found:", argv[1], argl[1]);
Tcl_DecrRefCount(objPtr);
Tcl_Release((ClientData)tclInterp);
return TH_ERROR;
}
if( !cmdInfo.objProc ){
Th_ErrorMessage(interp, "Cannot invoke Tcl command:", argv[1], argl[1]);
Tcl_DecrRefCount(objPtr);
Tcl_Release((ClientData)tclInterp);
return TH_ERROR;
}
Tcl_DecrRefCount(objPtr);
#endif
COPY_ARGV_TO_OBJV();
#ifdef USE_TCL_EVALOBJV
rc = Tcl_EvalObjv(tclInterp, objc, objv, 0);
#else
Tcl_ResetResult(tclInterp);
rc = cmdInfo.objProc(cmdInfo.objClientData, tclInterp, objc, objv);
#endif
FREE_ARGV_TO_OBJV();
zResult = getTclResult(tclInterp, &nResult);
Th_SetResult(interp, zResult, nResult);
Tcl_Release((ClientData)tclInterp);
return rc;
}
/*
** Syntax:
**
** th1Eval arg
*/
static int Th1EvalObjCmd(
ClientData clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
Th_Interp *th1Interp;
int nArg;
const char *arg;
int rc;
if( objc!=2 ){
Tcl_WrongNumArgs(interp, 1, objv, "arg");
return TCL_ERROR;
}
th1Interp = (Th_Interp *)clientData;
if( !th1Interp ){
Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL);
return TCL_ERROR;
}
arg = Tcl_GetStringFromObj(objv[1], &nArg);
rc = Th_Eval(th1Interp, 0, arg, nArg);
arg = Th_GetResult(th1Interp, &nArg);
Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, nArg));
return rc;
}
/*
** Syntax:
**
** th1Expr arg
*/
static int Th1ExprObjCmd(
ClientData clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
Th_Interp *th1Interp;
int nArg;
const char *arg;
int rc;
if( objc!=2 ){
Tcl_WrongNumArgs(interp, 1, objv, "arg");
return TCL_ERROR;
}
th1Interp = (Th_Interp *)clientData;
if( !th1Interp ){
Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL);
return TCL_ERROR;
}
arg = Tcl_GetStringFromObj(objv[1], &nArg);
rc = Th_Expr(th1Interp, arg, nArg);
arg = Th_GetResult(th1Interp, &nArg);
Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, nArg));
return rc;
}
/*
** Array of Tcl integration commands. Used when adding or removing the Tcl
** integration commands from TH1.
*/
static struct _Command {
const char *zName;
Th_CommandProc xProc;
void *pContext;
} aCommand[] = {
{"tclEval", tclEval_command, 0},
{"tclExpr", tclExpr_command, 0},
{"tclInvoke", tclInvoke_command, 0},
{0, 0, 0}
};
/*
** Called if the Tcl interpreter is deleted. Removes the Tcl integration
** commands from the TH1 interpreter.
*/
static void Th1DeleteProc(
ClientData clientData,
Tcl_Interp *interp
){
int i;
Th_Interp *th1Interp = (Th_Interp *)clientData;
if( !th1Interp ) return;
/* Remove the Tcl integration commands. */
for(i=0; i<(sizeof(aCommand)/sizeof(aCommand[0])); i++){
Th_RenameCommand(th1Interp, aCommand[i].zName, -1, NULL, 0);
}
}
/*
** Creates and initializes a Tcl interpreter for use with the specified TH1
** interpreter. Stores the created Tcl interpreter in the Tcl context supplied
** by the caller.
*/
static int createTclInterp(
Th_Interp *interp,
void *pContext
){
struct TclContext *tclContext = (struct TclContext *)pContext;
Tcl_Interp *tclInterp;
if ( !tclContext ){
Th_ErrorMessage(interp,
"Invalid Tcl context", (const char *)"", 0);
return TH_ERROR;
}
if ( tclContext->interp ){
return TH_OK;
}
if ( tclContext->argc>0 && tclContext->argv ) {
Tcl_FindExecutable(tclContext->argv[0]);
}
tclInterp = tclContext->interp = Tcl_CreateInterp();
if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
Th_ErrorMessage(interp,
"Could not create Tcl interpreter", (const char *)"", 0);
return TH_ERROR;
}
if( Tcl_Init(tclInterp)!=TCL_OK ){
Th_ErrorMessage(interp,
"Tcl initialization error:", Tcl_GetStringResult(tclInterp), -1);
Tcl_DeleteInterp(tclInterp);
tclContext->interp = tclInterp = 0;
return TH_ERROR;
}
/* Add the TH1 integration commands to Tcl. */
Tcl_CallWhenDeleted(tclInterp, Th1DeleteProc, interp);
Tcl_CreateObjCommand(tclInterp, "th1Eval", Th1EvalObjCmd, interp, NULL);
Tcl_CreateObjCommand(tclInterp, "th1Expr", Th1ExprObjCmd, interp, NULL);
return TH_OK;
}
/*
** Register the Tcl language commands with interpreter interp.
** Usually this is called soon after interpreter creation.
*/
int th_register_tcl(
Th_Interp *interp,
void *pContext
){
int i;
/* Add the Tcl integration commands to TH1. */
for(i=0; i<(sizeof(aCommand)/sizeof(aCommand[0])); i++){
void *ctx;
if ( !aCommand[i].zName || !aCommand[i].xProc ) continue;
ctx = aCommand[i].pContext;
/* Use Tcl interpreter for context? */
if( !ctx ) ctx = pContext;
Th_CreateCommand(interp, aCommand[i].zName, aCommand[i].xProc, ctx, 0);
}
return TH_OK;
}
#endif /* FOSSIL_ENABLE_TCL */
|
Changes to src/timeline.c.
| ︙ | ︙ | |||
18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
** This file contains code to implement the timeline web page
**
*/
#include <string.h>
#include <time.h>
#include "config.h"
#include "timeline.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.
*/
static void shorten_uuid(char *zDest, const char *zSrc){
int i;
| > > > | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
** This file contains code to implement the timeline web page
**
*/
#include <string.h>
#include <time.h>
#include "config.h"
#include "timeline.h"
#ifdef FOSSIL_ENABLE_JSON
# include "cson_amalgamation.h"
#endif
/*
** 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.
*/
static void shorten_uuid(char *zDest, const char *zSrc){
int i;
|
| ︙ | ︙ | |||
177 178 179 180 181 182 183 | ** 0. rid ** 1. UUID ** 2. Date/Time ** 3. Comment string ** 4. User ** 5. True if is a leaf ** 6. background color | | | 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
** 0. rid
** 1. UUID
** 2. Date/Time
** 3. Comment string
** 4. User
** 5. True if is a leaf
** 6. background color
** 7. type ("ci", "w", "t", "e", "g", "div")
** 8. list of symbolic tags.
** 9. tagid for ticket or wiki or event
** 10. Short comment to user for repeated tickets and wiki
*/
void www_print_timeline(
Stmt *pQuery, /* Query to implement the timeline */
int tmFlags, /* Flags controlling display behavior */
|
| ︙ | ︙ | |||
314 315 316 317 318 319 320 321 322 323 324 325 326 327 |
@ <div id="m%d(gidx)"></div>
}
@</td>
if( zBgClr && zBgClr[0] ){
@ <td class="timelineTableCell" style="background-color: %h(zBgClr);">
}else{
@ <td class="timelineTableCell">
}
if( zType[0]=='c' ){
hyperlink_to_uuid(zUuid);
if( isLeaf ){
if( db_exists("SELECT 1 FROM tagxref"
" WHERE rid=%d AND tagid=%d AND tagtype>0",
rid, TAG_CLOSED) ){
| > > > | 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 |
@ <div id="m%d(gidx)"></div>
}
@</td>
if( zBgClr && zBgClr[0] ){
@ <td class="timelineTableCell" style="background-color: %h(zBgClr);">
}else{
@ <td class="timelineTableCell">
}
if( pGraph && zType[0]!='c' ){
@ •
}
if( zType[0]=='c' ){
hyperlink_to_uuid(zUuid);
if( isLeaf ){
if( db_exists("SELECT 1 FROM tagxref"
" WHERE rid=%d AND tagid=%d AND tagtype>0",
rid, TAG_CLOSED) ){
|
| ︙ | ︙ | |||
763 764 765 766 767 768 769 |
** Return a pointer to a constant string that forms the basis
** for a timeline query for the WWW interface.
*/
const char *timeline_query_for_www(void){
static char *zBase = 0;
static const char zBaseSql[] =
@ SELECT
| | | | | | | | | | | | | 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 |
** Return a pointer to a constant string that forms the basis
** for a timeline query for the WWW interface.
*/
const char *timeline_query_for_www(void){
static char *zBase = 0;
static const char zBaseSql[] =
@ SELECT
@ blob.rid AS blobRid,
@ uuid AS 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,
@ (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
@ FROM event JOIN blob
@ WHERE blob.rid=event.objid
;
if( zBase==0 ){
zBase = mprintf(zBaseSql, TAG_BRANCH, TAG_BRANCH);
}
return zBase;
|
| ︙ | ︙ | |||
838 839 840 841 842 843 844 | ** ** Query parameters: ** ** a=TIMESTAMP after this date ** b=TIMESTAMP before this date. ** c=TIMESTAMP "circa" this date. ** n=COUNT number of events in output | | | > | | | | 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 | ** ** Query parameters: ** ** a=TIMESTAMP after this date ** b=TIMESTAMP before this date. ** c=TIMESTAMP "circa" this date. ** n=COUNT number of events in output ** p=UUID artifact and up to COUNT parents and ancestors ** d=UUID artifact and up to COUNT descendants ** dp=UUUID The same as d=UUID&p=UUID ** t=TAGID show only check-ins with the given tagid ** r=TAGID show check-ins related to tagid ** u=USER only if belonging to this user ** y=TYPE 'ci', 'w', 't', 'e' ** s=TEXT string search (comment and brief) ** ng Suppress the graph if present ** nd Suppress "divider" lines ** fc Show details of files changed ** f=UUID Show family (immediate parents and children) of UUID ** from=UUID Path from... ** to=UUID ... to this ** nomerge ... avoid merge links on the path ** brbg Background color from branch name ** ubg Background color from user ** ** p= and d= can appear individually or together. If either p= or d= ** appear, then u=, y=, a=, and b= are ignored. ** |
| ︙ | ︙ | |||
890 891 892 893 894 895 896 897 898 899 900 |
const char *zThisUser = 0; /* Suppress links to this user */
HQuery url; /* URL for various branch links */
int from_rid = name_to_typed_rid(P("from"),"ci"); /* from= for paths */
int to_rid = name_to_typed_rid(P("to"),"ci"); /* to= for path timelines */
int noMerge = P("nomerge")!=0; /* Do not follow merge links */
int me_rid = name_to_typed_rid(P("me"),"ci"); /* me= for common ancestory */
int you_rid = name_to_typed_rid(P("you"),"ci");/* you= for common ancst */
/* To view the timeline, must have permission to read project data.
*/
login_check_credentials();
| > > > > > | > > > | 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 |
const char *zThisUser = 0; /* Suppress links to this user */
HQuery url; /* URL for various branch links */
int from_rid = name_to_typed_rid(P("from"),"ci"); /* from= for paths */
int to_rid = name_to_typed_rid(P("to"),"ci"); /* to= for path timelines */
int noMerge = P("nomerge")!=0; /* Do not follow merge links */
int me_rid = name_to_typed_rid(P("me"),"ci"); /* me= for common ancestory */
int you_rid = name_to_typed_rid(P("you"),"ci");/* you= for common ancst */
int pd_rid;
/* To view the timeline, must have permission to read project data.
*/
pd_rid = name_to_typed_rid(P("dp"),"ci");
if( pd_rid ){
p_rid = d_rid = pd_rid;
}
login_check_credentials();
if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki ){
login_needed();
return;
}
if( zTagName && g.perm.Read ){
tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTagName);
zThisTag = zTagName;
}else if( zBrName && g.perm.Read ){
tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'",zBrName);
zThisTag = zBrName;
}else{
|
| ︙ | ︙ | |||
986 987 988 989 990 991 992 |
zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d",
p_rid ? p_rid : d_rid);
blob_appendf(&sql, " AND event.objid IN ok");
nd = 0;
if( d_rid ){
compute_descendants(d_rid, nEntry+1);
nd = db_int(0, "SELECT count(*)-1 FROM ok");
| < | | < | 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 |
zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d",
p_rid ? p_rid : d_rid);
blob_appendf(&sql, " AND event.objid IN ok");
nd = 0;
if( d_rid ){
compute_descendants(d_rid, nEntry+1);
nd = db_int(0, "SELECT count(*)-1 FROM ok");
if( nd>=0 ) db_multi_exec("%s", blob_str(&sql));
if( nd>0 ) blob_appendf(&desc, "%d descendant%s", nd,(1==nd)?"":"s");
if( useDividers ) timeline_add_dividers(0, d_rid);
db_multi_exec("DELETE FROM ok");
}
if( p_rid ){
compute_ancestors(p_rid, nEntry+1);
np = db_int(0, "SELECT count(*)-1 FROM ok");
if( np>0 ){
|
| ︙ | ︙ | |||
1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 |
}
blob_appendf(&sql, ")");
}
if( (zType[0]=='w' && !g.perm.RdWiki)
|| (zType[0]=='t' && !g.perm.RdTkt)
|| (zType[0]=='e' && !g.perm.RdWiki)
|| (zType[0]=='c' && !g.perm.Read)
){
zType = "all";
}
if( zType[0]=='a' ){
if( !g.perm.Read || !g.perm.RdWiki || !g.perm.RdTkt ){
char cSep = '(';
blob_appendf(&sql, " AND event.type IN ");
if( g.perm.Read ){
| > | | 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 |
}
blob_appendf(&sql, ")");
}
if( (zType[0]=='w' && !g.perm.RdWiki)
|| (zType[0]=='t' && !g.perm.RdTkt)
|| (zType[0]=='e' && !g.perm.RdWiki)
|| (zType[0]=='c' && !g.perm.Read)
|| (zType[0]=='g' && !g.perm.Read)
){
zType = "all";
}
if( zType[0]=='a' ){
if( !g.perm.Read || !g.perm.RdWiki || !g.perm.RdTkt ){
char cSep = '(';
blob_appendf(&sql, " AND event.type IN ");
if( g.perm.Read ){
blob_appendf(&sql, "%c'ci','g'", cSep);
cSep = ',';
}
if( g.perm.RdWiki ){
blob_appendf(&sql, "%c'w','e'", cSep);
cSep = ',';
}
if( g.perm.RdTkt ){
|
| ︙ | ︙ | |||
1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 |
zEType = "checkin";
}else if( zType[0]=='w' ){
zEType = "wiki edit";
}else if( zType[0]=='t' ){
zEType = "ticket change";
}else if( zType[0]=='e' ){
zEType = "event";
}
}
if( zUser ){
blob_appendf(&sql, " AND (event.user=%Q OR event.euser=%Q)",
zUser, zUser);
url_add_parameter(&url, "u", zUser);
zThisUser = zUser;
| > > | 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 |
zEType = "checkin";
}else if( zType[0]=='w' ){
zEType = "wiki edit";
}else if( zType[0]=='t' ){
zEType = "ticket change";
}else if( zType[0]=='e' ){
zEType = "event";
}else if( zType[0]=='g' ){
zEType = "tag";
}
}
if( zUser ){
blob_appendf(&sql, " AND (event.user=%Q OR event.euser=%Q)",
zUser, zUser);
url_add_parameter(&url, "u", zUser);
zThisUser = zUser;
|
| ︙ | ︙ | |||
1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 |
}
if( zType[0]!='t' && g.perm.RdTkt ){
timeline_submenu(&url, "Tickets Only", "y", "t", 0);
}
if( zType[0]!='e' && g.perm.RdWiki ){
timeline_submenu(&url, "Events Only", "y", "e", 0);
}
}
if( nEntry>20 ){
timeline_submenu(&url, "20 Entries", "n", "20", 0);
}
if( nEntry<200 ){
timeline_submenu(&url, "200 Entries", "n", "200", 0);
}
| > > > | 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 |
}
if( zType[0]!='t' && g.perm.RdTkt ){
timeline_submenu(&url, "Tickets Only", "y", "t", 0);
}
if( zType[0]!='e' && g.perm.RdWiki ){
timeline_submenu(&url, "Events Only", "y", "e", 0);
}
if( zType[0]!='g' && g.perm.Read ){
timeline_submenu(&url, "Tags Only", "y", "g", 0);
}
}
if( nEntry>20 ){
timeline_submenu(&url, "20 Entries", "n", "20", 0);
}
if( nEntry<200 ){
timeline_submenu(&url, "200 Entries", "n", "200", 0);
}
|
| ︙ | ︙ | |||
1363 1364 1365 1366 1367 1368 1369 |
/*
** Return a pointer to a static string that forms the basis for
** a timeline query for display on a TTY.
*/
const char *timeline_query_for_tty(void){
static const char zBaseSql[] =
@ SELECT
| | | | | | | | 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 |
/*
** Return a pointer to a static string that forms the basis for
** a timeline query for display on a TTY.
*/
const char *timeline_query_for_tty(void){
static const char zBaseSql[] =
@ SELECT
@ blob.rid AS rid,
@ uuid,
@ datetime(event.mtime,'localtime') AS mDateTime,
@ 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
@ FROM event, blob
@ WHERE blob.rid=event.objid
;
return zBaseSql;
}
/*
|
| ︙ | ︙ | |||
1423 1424 1425 1426 1427 1428 1429 | ** The optional TYPE argument may any types supported by the /timeline ** page. For example: ** ** w = wiki commits only ** ci = file commits only ** t = tickets only ** | | | | 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 |
** The optional TYPE argument may any types supported by the /timeline
** page. For example:
**
** 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.
**
*/
void timeline_cmd(void){
Stmt q;
int n, k;
const char *zCount;
const char *zType;
|
| ︙ | ︙ | |||
1522 1523 1524 1525 1526 1527 1528 |
compute_ancestors(objid, n);
}
blob_appendf(&sql, " AND blob.rid IN ok");
}
if( zType && (zType[0]!='a') ){
blob_appendf(&sql, " AND event.type=%Q ", zType);
}
| < | 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 |
compute_ancestors(objid, n);
}
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);
}
|
| ︙ | ︙ |
Changes to src/tkt.c.
| ︙ | ︙ | |||
168 169 170 171 172 173 174 |
** Return TRUE if a new TICKET entry was created and FALSE if an
** existing entry was revised.
*/
int ticket_insert(const Manifest *p, int createFlag, int rid){
Blob sql;
Stmt q;
int i;
| < < | 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
** Return TRUE if a new TICKET entry was created and FALSE if an
** existing entry was revised.
*/
int ticket_insert(const Manifest *p, int createFlag, int rid){
Blob sql;
Stmt q;
int i;
int rc = 0;
getAllTicketFields();
if( createFlag ){
db_multi_exec("INSERT OR IGNORE INTO ticket(tkt_uuid, tkt_mtime) "
"VALUES(%Q, 0)", p->zTicketUuid);
rc = db_changes();
}
blob_zero(&sql);
blob_appendf(&sql, "UPDATE OR REPLACE ticket SET tkt_mtime=:mtime");
for(i=0; i<p->nField; i++){
const char *zName = p->aField[i].zName;
if( zName[0]=='+' ){
zName++;
if( fieldId(zName)<0 ) continue;
blob_appendf(&sql,", %s=coalesce(%s,'') || %Q",
zName, zName, p->aField[i].zValue);
|
| ︙ | ︙ | |||
520 521 522 523 524 525 526 527 528 529 530 531 532 533 |
if( g.thTrace ) Th_Trace("BEGIN_TKTNEW<br />\n", -1);
ticket_init();
getAllTicketFields();
initializeVariablesFromDb();
initializeVariablesFromCGI();
@ <form method="post" action="%s(g.zTop)/%s(g.zPath)"><p>
login_insert_csrf_secret();
@ </p>
zScript = ticket_newpage_code();
Th_Store("login", g.zLogin);
Th_Store("date", db_text(0, "SELECT datetime('now')"));
Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd,
(void*)&zNewUuid, 0);
if( g.thTrace ) Th_Trace("BEGIN_TKTNEW_SCRIPT<br />\n", -1);
| > > > | 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 |
if( g.thTrace ) Th_Trace("BEGIN_TKTNEW<br />\n", -1);
ticket_init();
getAllTicketFields();
initializeVariablesFromDb();
initializeVariablesFromCGI();
@ <form method="post" action="%s(g.zTop)/%s(g.zPath)"><p>
login_insert_csrf_secret();
if( P("date_override") && g.perm.Setup ){
@ <input type="hidden" name="date_override" value="%h(P("date_override"))">
}
@ </p>
zScript = ticket_newpage_code();
Th_Store("login", g.zLogin);
Th_Store("date", db_text(0, "SELECT datetime('now')"));
Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd,
(void*)&zNewUuid, 0);
if( g.thTrace ) Th_Trace("BEGIN_TKTNEW_SCRIPT<br />\n", -1);
|
| ︙ | ︙ | |||
837 838 839 840 841 842 843 |
}
blob_reset(&val);
}
@ </ol>
}
/*
| | > | | 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 |
}
blob_reset(&val);
}
@ </ol>
}
/*
** COMMAND: ticket*
** Usage: %fossil ticket SUBCOMMAND ...
**
** Run various subcommands to control tickets
**
** %fossil ticket show (REPORTTITLE|REPORTNR) ?TICKETFILTER? ?options?
**
** options can be:
** ?-l|--limit LIMITCHAR?
** ?-q|--quote?
** ?-R|--repository FILE?
**
** 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
** 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.
**
** 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.
**
** %fossil ticket list fields
**
|
| ︙ | ︙ | |||
957 958 959 960 961 962 963 |
if( strncmp(g.argv[2],"show",n)==0 ){
if( g.argc==3 ){
usage("show REPORTNR");
}else{
const char *zRep = 0;
const char *zSep = 0;
const char *zFilterUuid = 0;
| < < < | 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 |
if( strncmp(g.argv[2],"show",n)==0 ){
if( g.argc==3 ){
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;
int rid;
const char *zTktUuid = 0;
|
| ︙ | ︙ |
Changes to src/translate.c.
| ︙ | ︙ | |||
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
}
}
}
}
int main(int argc, char **argv){
if( argc==2 ){
FILE *in = fopen(argv[1], "r");
if( in==0 ){
fprintf(stderr,"can not open %s\n", argv[1]);
exit(1);
}
trans(in, stdout);
fclose(in);
}else{
trans(stdin, stdout);
}
return 0;
}
| > > > > > > > > > > | 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
}
}
}
}
int main(int argc, char **argv){
if( argc==2 ){
char *arg;
FILE *in = fopen(argv[1], "r");
if( in==0 ){
fprintf(stderr,"can not open %s\n", argv[1]);
exit(1);
}
printf("#line 1 \"");
for(arg=argv[1]; *arg; arg++){
if( *arg!='\\' ){
printf("%c", *arg);
}else{
printf("\\\\");
}
}
printf("\"\n");
trans(in, stdout);
fclose(in);
}else{
trans(stdin, stdout);
}
return 0;
}
|
Changes to src/undo.c.
| ︙ | ︙ | |||
346 347 348 349 350 351 352 |
undoActive = 0;
fossil_print("Rolling back prior filesystem changes...\n");
undo_all_filesystem(0);
}
/*
** COMMAND: undo
| | | 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 |
undoActive = 0;
fossil_print("Rolling back prior filesystem changes...\n");
undo_all_filesystem(0);
}
/*
** COMMAND: undo
** COMMAND: redo*
**
** Usage: %fossil undo ?OPTIONS? ?FILENAME...?
** or: %fossil redo ?OPTIONS? ?FILENAME...?
**
** Undo the changes to the working checkout caused by the most recent
** of the following operations:
**
|
| ︙ | ︙ |
Changes to src/update.c.
| ︙ | ︙ | |||
102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
int nochangeFlag; /* -n or --nochange. Do a dry run */
int verboseFlag; /* -v or --verbose. Output extra information */
int debugFlag; /* --debug option */
int nChng; /* Number of file renames */
int *aChng; /* Array of file renames */
int i; /* Loop counter */
int nConflict = 0; /* Number of merge conflicts */
Stmt mtimeXfer; /* Statment to transfer mtimes */
if( !internalUpdate ){
undo_capture_command_line();
url_proxy_options();
}
latestFlag = find_option("latest",0, 0)!=0;
| > | 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
int nochangeFlag; /* -n or --nochange. Do a dry run */
int verboseFlag; /* -v or --verbose. Output extra information */
int debugFlag; /* --debug option */
int nChng; /* Number of file renames */
int *aChng; /* Array of file renames */
int i; /* Loop counter */
int nConflict = 0; /* Number of merge conflicts */
int nOverwrite = 0; /* Number of unmanaged files overwritten */
Stmt mtimeXfer; /* Statment to transfer mtimes */
if( !internalUpdate ){
undo_capture_command_line();
url_proxy_options();
}
latestFlag = find_option("latest",0, 0)!=0;
|
| ︙ | ︙ | |||
268 269 270 271 272 273 274 |
);
/*
** Add islink information
*/
db_multi_exec(
"UPDATE fv SET"
| | > | > | > | > | 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 |
);
/*
** Add islink information
*/
db_multi_exec(
"UPDATE fv SET"
" islinkv=coalesce((SELECT islink FROM vfile"
" WHERE vid=%d AND pathname=fnt),0),"
" islinkt=coalesce((SELECT islink FROM vfile"
" WHERE vid=%d AND pathname=fnt),0)",
vid, tid
);
if( debugFlag ){
db_prepare(&q,
"SELECT rowid, fn, fnt, chnged, ridv, ridt, isexe,"
" islinkv, islinkt FROM fv"
);
while( db_step(&q)==SQLITE_ROW ){
fossil_print("%3d: ridv=%-4d ridt=%-4d chnged=%d isexe=%d"
" islinkv=%d islinkt=%d\n",
db_column_int(&q, 0),
db_column_int(&q, 4),
db_column_int(&q, 5),
db_column_int(&q, 3),
db_column_int(&q, 6),
db_column_int(&q, 7),
db_column_int(&q, 8));
|
| ︙ | ︙ | |||
308 309 310 311 312 313 314 |
const char *zSep; /* Term separator */
blob_zero(&sql);
blob_append(&sql, "DELETE FROM fv WHERE ", -1);
zSep = "";
for(i=3; i<g.argc; i++){
file_tree_name(g.argv[i], &treename, 1);
| | | 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 |
const char *zSep; /* Term separator */
blob_zero(&sql);
blob_append(&sql, "DELETE FROM fv WHERE ", -1);
zSep = "";
for(i=3; i<g.argc; i++){
file_tree_name(g.argv[i], &treename, 1);
if( file_wd_isdir(g.argv[i])==1 ){
if( blob_size(&treename) != 1 || blob_str(&treename)[0] != '.' ){
blob_appendf(&sql, "%sfn NOT GLOB '%b/*' ", zSep, &treename);
}else{
blob_reset(&sql);
break;
}
}else{
|
| ︙ | ︙ | |||
330 331 332 333 334 335 336 | } /* ** Alter the content of the checkout so that it conforms with the ** target */ db_prepare(&q, | | > | 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 |
}
/*
** Alter the content of the checkout so that it conforms with the
** target
*/
db_prepare(&q,
"SELECT fn, idv, ridv, idt, ridt, chnged, fnt,"
" isexe, islinkv, islinkt FROM fv ORDER BY 1"
);
db_prepare(&mtimeXfer,
"UPDATE vfile SET mtime=(SELECT mtime FROM vfile WHERE id=:idv)"
" WHERE id=:idt"
);
assert( g.zLocalRoot!=0 );
assert( strlen(g.zLocalRoot)>1 );
|
| ︙ | ︙ | |||
365 366 367 368 369 370 371 |
/* Conflict. This file has been added to the current checkout
** but also exists in the target checkout. Use the current version.
*/
fossil_print("CONFLICT %s\n", zName);
nConflict++;
}else if( idt>0 && idv==0 ){
/* File added in the target. */
| > > > > | > | 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 |
/* Conflict. This file has been added to the current checkout
** but also exists in the target checkout. Use the current version.
*/
fossil_print("CONFLICT %s\n", zName);
nConflict++;
}else if( idt>0 && idv==0 ){
/* File added in the target. */
if( file_wd_isfile_or_link(zFullPath) ){
fossil_print("ADD %s (overwrites an unmanaged file)\n", zName);
nOverwrite++;
}else{
fossil_print("ADD %s\n", zName);
}
undo_save(zName);
if( !nochangeFlag ) vfile_to_disk(0, idt, 0, 0);
}else if( idt>0 && idv>0 && ridt!=ridv && chnged==0 ){
/* The file is unedited. Change it to the target version */
undo_save(zName);
fossil_print("UPDATE %s\n", zName);
if( !nochangeFlag ) vfile_to_disk(0, idt, 0, 0);
|
| ︙ | ︙ | |||
455 456 457 458 459 460 461 |
db_finalize(&q);
db_finalize(&mtimeXfer);
fossil_print("--------------\n");
show_common_info(tid, "updated-to:", 1, 0);
/* Report on conflicts
*/
| > | | | > | | > > > | < > | 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 |
db_finalize(&q);
db_finalize(&mtimeXfer);
fossil_print("--------------\n");
show_common_info(tid, "updated-to:", 1, 0);
/* Report on conflicts
*/
if( !nochangeFlag ){
if( nConflict ){
if( internalUpdate ){
internalConflictCnt = nConflict;
nConflict = 0;
}else{
fossil_print("WARNING: %d merge conflicts", nConflict);
}
}
if( nOverwrite ){
fossil_warning("WARNING: %d unmanaged files were overwritten",
nOverwrite);
}
}
/*
** Clean up the mid and pid VFILE entries. Then commit the changes.
*/
if( nochangeFlag ){
|
| ︙ | ︙ | |||
519 520 521 522 523 524 525 |
Blob path;
const char *zPath;
blob_zero(&path);
blob_appendf(&path, "%s/%s", g.zLocalRoot, zDir);
zPath = blob_str(&path);
/* Handle various cases of existence of the directory */
| | | 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 |
Blob path;
const char *zPath;
blob_zero(&path);
blob_appendf(&path, "%s/%s", g.zLocalRoot, zDir);
zPath = blob_str(&path);
/* Handle various cases of existence of the directory */
switch( file_wd_isdir(zPath) ){
case 0: { /* doesn't exist */
if( file_mkdir(zPath, 0)!=0 ) {
fossil_warning("couldn't create directory %s as "
"required by empty-dirs setting", zDir);
}
break;
}
|
| ︙ | ︙ | |||
570 571 572 573 574 575 576 |
if( !is_a_version(rid) ){
if( errCode>0 ) return errCode;
fossil_fatal("no such checkin: %s", revision);
}
pManifest = manifest_get(rid, CFTYPE_MANIFEST);
if( pManifest ){
| | | 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 |
if( !is_a_version(rid) ){
if( errCode>0 ) return errCode;
fossil_fatal("no such checkin: %s", revision);
}
pManifest = manifest_get(rid, CFTYPE_MANIFEST);
if( pManifest ){
pFile = manifest_file_find(pManifest, file);
if( pFile ){
rid = uuid_to_rid(pFile->zUuid, 0);
if( pIsExe ) *pIsExe = ( manifest_file_mperm(pFile)==PERM_EXE );
if( pIsLink ) *pIsLink = ( manifest_file_mperm(pFile)==PERM_LNK );
manifest_destroy(pManifest);
return content_get(rid, content);
}
|
| ︙ | ︙ | |||
666 667 668 669 670 671 672 |
}
while( db_step(&q)==SQLITE_ROW ){
int isExe = 0;
int isLink = 0;
char *zFull;
zFile = db_column_text(&q, 0);
zFull = mprintf("%/%/", g.zLocalRoot, zFile);
| | > | 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 |
}
while( db_step(&q)==SQLITE_ROW ){
int isExe = 0;
int isLink = 0;
char *zFull;
zFile = db_column_text(&q, 0);
zFull = mprintf("%/%/", g.zLocalRoot, zFile);
errCode = historical_version_of_file(zRevision, zFile, &record,
&isLink, &isExe,2);
if( errCode==2 ){
if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q", zFile)==0 ){
fossil_print("UNMANAGE: %s\n", zFile);
}else{
undo_save(zFile);
file_delete(zFull);
fossil_print("DELETE: %s\n", zFile);
|
| ︙ | ︙ | |||
692 693 694 695 696 697 698 |
blob_write_to_file(&record, zFull);
}
file_wd_setexe(zFull, isExe);
fossil_print("REVERTED: %s\n", zFile);
mtime = file_wd_mtime(zFull);
db_multi_exec(
"UPDATE vfile"
| | | 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 |
blob_write_to_file(&record, zFull);
}
file_wd_setexe(zFull, isExe);
fossil_print("REVERTED: %s\n", zFile);
mtime = file_wd_mtime(zFull);
db_multi_exec(
"UPDATE vfile"
" SET mtime=%lld, chnged=0, deleted=0, isexe=%d, islink=%d,mrid=rid,"
" pathname=coalesce(origname,pathname), origname=NULL"
" WHERE pathname=%Q",
mtime, isExe, isLink, zFile
);
}
blob_reset(&record);
free(zFull);
}
db_finalize(&q);
undo_finish();
db_end_transaction(0);
}
|
Changes to src/user.c.
| ︙ | ︙ | |||
140 141 142 143 144 145 146 |
if( z ){
strip_string(pIn, z);
}
}
/*
| | | 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
if( z ){
strip_string(pIn, z);
}
}
/*
** COMMAND: user*
**
** Usage: %fossil user SUBCOMMAND ... ?-R|--repository FILE?
**
** Run various subcommands on users of the open repository or of
** the repository identified by the -R or --repository option.
**
** %fossil user capabilities USERNAME ?STRING?
|
| ︙ | ︙ |
Changes to src/vfile.c.
| ︙ | ︙ | |||
122 123 124 125 126 127 128 | db_finalize(&ridq); db_finalize(&ins); manifest_destroy(p); db_end_transaction(0); } /* | | < | > | | | > > | > | > > | > | > > > | | > | > > | > > > | > | < < < < > | < < < | > > > > > > > | > > > | > > > > < < < | | > | 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 |
db_finalize(&ridq);
db_finalize(&ins);
manifest_destroy(p);
db_end_transaction(0);
}
/*
** Look at every VFILE entry with the given vid and set update
** VFILE.CHNGED field on every file according to whether or not
** the file has changes. 0 means no change. 1 means edited. 2 means
** the file has changed due to a merge. 3 means the file was added
** by a merge.
**
** If VFILE.DELETED is true or if VFILE.RID is zero, then the file was
** either removed from managemented via "fossil rm" or added via
** "fossil add", respectively, and in both cases we always know that
** the file has changed without having the check the size, mtime,
** or on-disk content.
**
** If the size of the file has changed, then we always know that the file
** changed without having to look at the mtime or on-disk content.
**
** The mtime of the file is only a factor if the mtime-changes setting
** is false and the useSha1sum flag is false. If the mtime-changes
** setting is true (or undefined - it defaults to true) or if useSha1sum
** is true, then we do not trust the mtime and will examine the on-disk
** content to determine if a file really is the same.
**
** If the mtime is used, it is used only to determine if files are the same.
** If the mtime of a file has changed, we still examine the on-disk content
** to see whether or not the edit was a null-edit.
*/
void vfile_check_signature(int vid, int notFileIsFatal, int useSha1sum){
int nErr = 0;
Stmt q;
Blob fileCksum, origCksum;
int useMtime = useSha1sum==0 && db_get_boolean("mtime-changes", 1);
db_begin_transaction();
db_prepare(&q, "SELECT id, %Q || pathname,"
" vfile.mrid, deleted, chnged, uuid, size, mtime"
" FROM vfile LEFT JOIN blob ON vfile.mrid=blob.rid"
" WHERE vid=%d ", g.zLocalRoot, vid);
while( db_step(&q)==SQLITE_ROW ){
int id, rid, isDeleted;
const char *zName;
int chnged = 0;
int oldChnged;
i64 oldMtime;
i64 currentMtime;
i64 origSize;
i64 currentSize;
id = db_column_int(&q, 0);
zName = db_column_text(&q, 1);
rid = db_column_int(&q, 2);
isDeleted = db_column_int(&q, 3);
oldChnged = chnged = db_column_int(&q, 4);
oldMtime = db_column_int64(&q, 7);
currentSize = file_wd_size(zName);
origSize = db_column_int64(&q, 6);
currentMtime = file_wd_mtime(0);
if( chnged==0 && (isDeleted || rid==0) ){
/* "fossil rm" or "fossil add" always change the file */
chnged = 1;
}else if( !file_wd_isfile_or_link(0) && currentSize>=0 ){
if( notFileIsFatal ){
fossil_warning("not an ordinary file: %s", zName);
nErr++;
}
chnged = 1;
}
if( origSize!=currentSize ){
if( chnged!=1 ){
/* A file size change is definitive - the file has changed. No
** need to check the mtime or sha1sum */
chnged = 1;
}
}else if( chnged==1 && rid!=0 && !isDeleted ){
/* File is believed to have changed but it is the same size.
** Double check that it really has changed by looking at content. */
assert( origSize==currentSize );
db_ephemeral_blob(&q, 5, &origCksum);
if( sha1sum_file(zName, &fileCksum) ){
blob_zero(&fileCksum);
}
if( blob_compare(&fileCksum, &origCksum)==0 ) chnged = 0;
blob_reset(&origCksum);
blob_reset(&fileCksum);
}else if( chnged==0 && (useMtime==0 || currentMtime!=oldMtime) ){
/* For files that were formerly believed to be unchanged, if their
** mtime changes, or unconditionally if --sha1sum is used, check
** to see if they have been edited by looking at their SHA1 sum */
assert( origSize==currentSize );
db_ephemeral_blob(&q, 5, &origCksum);
if( sha1sum_file(zName, &fileCksum) ){
blob_zero(&fileCksum);
}
if( blob_compare(&fileCksum, &origCksum) ){
chnged = 1;
}
blob_reset(&origCksum);
blob_reset(&fileCksum);
}
if( currentMtime!=oldMtime || chnged!=oldChnged ){
db_multi_exec("UPDATE vfile SET mtime=%lld, chnged=%d WHERE id=%d",
currentMtime, chnged, id);
}
}
db_finalize(&q);
if( nErr ) fossil_fatal("abort due to prior errors");
db_end_transaction(0);
}
|
| ︙ | ︙ | |||
270 271 272 273 274 275 276 |
}
if( cReply=='n' || cReply=='N' ){
blob_reset(&content);
continue;
}
}
if( verbose ) fossil_print("%s\n", &zName[nRepos]);
| | | 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
}
if( cReply=='n' || cReply=='N' ){
blob_reset(&content);
continue;
}
}
if( verbose ) fossil_print("%s\n", &zName[nRepos]);
if( file_wd_isdir(zName) == 1 ){
/*TODO(dchest): remove directories? */
fossil_fatal("%s is directory, cannot overwrite\n", zName);
}
if( file_wd_size(zName)>=0 && (isLink || file_wd_islink(zName)) ){
file_delete(zName);
}
if( isLink ){
|
| ︙ | ︙ | |||
311 312 313 314 315 316 317 |
db_finalize(&q);
db_multi_exec("UPDATE vfile SET mtime=NULL WHERE vid=%d AND mrid>0", vid);
}
/*
** Check to see if the directory named in zPath is the top of a checkout.
** In other words, check to see if directory pPath contains a file named
| | > > > > > > > > > > > | 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 |
db_finalize(&q);
db_multi_exec("UPDATE vfile SET mtime=NULL WHERE vid=%d AND mrid>0", vid);
}
/*
** Check to see if the directory named in zPath is the top of a checkout.
** In other words, check to see if directory pPath contains a file named
** "_FOSSIL_" or ".fslckout". Return true or false.
*/
int vfile_top_of_checkout(const char *zPath){
char *zFile;
int fileFound = 0;
zFile = mprintf("%s/_FOSSIL_", zPath);
fileFound = file_size(zFile)>=1024;
fossil_free(zFile);
if( !fileFound ){
zFile = mprintf("%s/.fslckout", zPath);
fileFound = file_size(zFile)>=1024;
fossil_free(zFile);
}
/* Check for ".fos" for legacy support. But the use of ".fos" as the
** per-checkout database name is deprecated. At some point, all support
** for ".fos" will end and this code should be removed. This comment
** added on 2012-02-04.
*/
if( !fileFound ){
zFile = mprintf("%s/.fos", zPath);
fileFound = file_size(zFile)>=1024;
fossil_free(zFile);
}
return fileFound;
}
|
| ︙ | ︙ | |||
387 388 389 390 391 392 393 |
}
zUtf8 = fossil_mbcs_to_utf8(pEntry->d_name);
blob_appendf(pPath, "/%s", zUtf8);
fossil_mbcs_free(zUtf8);
zPath = blob_str(pPath);
if( glob_match(pIgnore, &zPath[nPrefix+1]) ){
/* do nothing */
| | | 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 |
}
zUtf8 = fossil_mbcs_to_utf8(pEntry->d_name);
blob_appendf(pPath, "/%s", zUtf8);
fossil_mbcs_free(zUtf8);
zPath = blob_str(pPath);
if( glob_match(pIgnore, &zPath[nPrefix+1]) ){
/* do nothing */
}else if( file_wd_isdir(zPath)==1 ){
if( !vfile_top_of_checkout(zPath) ){
vfile_scan(pPath, nPrefix, allFlag, pIgnore);
}
}else if( file_wd_isfile_or_link(zPath) ){
db_bind_text(&ins, ":file", &zPath[nPrefix+1]);
db_step(&ins);
db_reset(&ins);
|
| ︙ | ︙ |
Changes to src/wiki.c.
| ︙ | ︙ | |||
227 228 229 230 231 232 233 |
}
cnt++;
@ <li>
if( g.perm.History && g.perm.Read ){
@ <a href="%s(g.zTop)/attachview?page=%s(zPageName)&file=%t(zFile)">
@ %h(zFile)</a>
}else{
| | | 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 |
}
cnt++;
@ <li>
if( g.perm.History && g.perm.Read ){
@ <a href="%s(g.zTop)/attachview?page=%s(zPageName)&file=%t(zFile)">
@ %h(zFile)</a>
}else{
@ %h(zFile)
}
@ added by %h(zUser) on
hyperlink_to_date(zDate, ".");
if( g.perm.WrWiki && g.perm.Attach ){
@ [<a href="%s(g.zTop)/attachdelete?page=%s(zPageName)&file=%t(zFile)&from=%s(g.zTop)/wiki%%3fname=%s(zPageName)">delete</a>]
}
@ </li>
|
| ︙ | ︙ | |||
591 592 593 594 595 596 597 598 599 600 601 602 603 604 |
*/
void wdiff_page(void){
char *zTitle;
int rid1, rid2;
const char *zPageName;
Manifest *pW1, *pW2 = 0;
Blob w1, w2, d;
login_check_credentials();
rid1 = atoi(PD("a","0"));
if( !g.perm.History ){ login_needed(); return; }
if( rid1==0 ) fossil_redirect_home();
rid2 = atoi(PD("b","0"));
zPageName = PD("name","");
| > | 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 |
*/
void wdiff_page(void){
char *zTitle;
int rid1, rid2;
const char *zPageName;
Manifest *pW1, *pW2 = 0;
Blob w1, w2, d;
int diffFlags;
login_check_credentials();
rid1 = atoi(PD("a","0"));
if( !g.perm.History ){ login_needed(); return; }
if( rid1==0 ) fossil_redirect_home();
rid2 = atoi(PD("b","0"));
zPageName = PD("name","");
|
| ︙ | ︙ | |||
619 620 621 622 623 624 625 |
if( pW1==0 ) fossil_redirect_home();
blob_init(&w1, pW1->zWiki, -1);
blob_zero(&w2);
if( rid2 && (pW2 = manifest_get(rid2, CFTYPE_WIKI))!=0 ){
blob_init(&w2, pW2->zWiki, -1);
}
blob_zero(&d);
| > | | | | > > > > > > > > > > > > > > > > > | < < < < < < | 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 |
if( pW1==0 ) fossil_redirect_home();
blob_init(&w1, pW1->zWiki, -1);
blob_zero(&w2);
if( rid2 && (pW2 = manifest_get(rid2, CFTYPE_WIKI))!=0 ){
blob_init(&w2, pW2->zWiki, -1);
}
blob_zero(&d);
diffFlags = construct_diff_flags(1,0);
text_diff(&w2, &w1, &d, diffFlags | DIFF_HTML | DIFF_LINENO);
@ <div class="udiff">
@ %s(blob_str(&d))
@ </div>
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
**
** List all available wiki pages with date created and last modified.
*/
void wcontent_page(void){
Stmt q;
int showAll = P("all")!=0;
login_check_credentials();
if( !g.perm.RdWiki ){ login_needed(); return; }
style_header("Available Wiki Pages");
if( showAll ){
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);
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>
}else if( showAll ){
@ <li><a href="%s(g.zTop)/wiki?name=%T(zName)"><s>%h(zName)</s></a></li>
|
| ︙ | ︙ | |||
788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 |
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==0 && !isNew ){
fossil_fatal("no such wiki page: %s", zPageName);
}
if( rid!=0 && isNew ){
fossil_fatal("wiki page %s already exists", zPageName);
}
blob_zero(&wiki);
zDate = date_in_standard_format("now");
blob_appendf(&wiki, "D %s\n", zDate);
free(zDate);
| > > > > > > | 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 |
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==0 && !isNew ){
#ifdef FOSSIL_ENABLE_JSON
g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND;
#endif
fossil_fatal("no such wiki page: %s", zPageName);
}
if( rid!=0 && isNew ){
#ifdef FOSSIL_ENABLE_JSON
g.json.resultCode = FSL_JSON_E_RESOURCE_ALREADY_EXISTS;
#endif
fossil_fatal("wiki page %s already exists", zPageName);
}
blob_zero(&wiki);
zDate = date_in_standard_format("now");
blob_appendf(&wiki, "D %s\n", zDate);
free(zDate);
|
| ︙ | ︙ | |||
824 825 826 827 828 829 830 | assert( blob_is_reset(&wiki) ); content_deltify(rid,nrid,0); db_end_transaction(0); return 1; } /* | | | 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 | assert( blob_is_reset(&wiki) ); content_deltify(rid,nrid,0); db_end_transaction(0); return 1; } /* ** COMMAND: wiki* ** ** Usage: %fossil wiki (export|create|commit|list) WikiName ** ** Run various subcommands to work with wiki entries. ** ** %fossil wiki export PAGENAME ?FILE? ** |
| ︙ | ︙ |
Changes to src/wikiformat.c.
| ︙ | ︙ | |||
1706 1707 1708 1709 1710 1711 1712 |
}else
/* Enter <verbatim> processing. With verbatim enabled, all other
** markup other than the corresponding end-tag with the same ID is
** ignored.
*/
if( markup.iCode==MARKUP_VERBATIM ){
| | < < | 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 |
}else
/* Enter <verbatim> processing. With verbatim enabled, all other
** markup other than the corresponding end-tag with the same ID is
** ignored.
*/
if( markup.iCode==MARKUP_VERBATIM ){
int vAttrIdx;
renderer.zVerbatimId = 0;
renderer.inVerbatim = 1;
renderer.preVerbState = renderer.state;
renderer.state &= ~ALLOW_WIKI;
for (vAttrIdx = 0; vAttrIdx < markup.nAttr; vAttrIdx++){
if( markup.aAttr[vAttrIdx].iACode == ATTR_ID ){
renderer.zVerbatimId = markup.aAttr[0].zValue;
}
}
renderer.wantAutoParagraph = 0;
}
/* Restore the input text to its original configuration
*/
|
| ︙ | ︙ |
Changes to src/winhttp.c.
| ︙ | ︙ | |||
430 431 432 433 434 435 436 |
fossil_fatal("error from StartServiceCtrlDispatcher()");
}
}
return 0;
}
/*
| | | 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 |
fossil_fatal("error from StartServiceCtrlDispatcher()");
}
}
return 0;
}
/*
** COMMAND: winsrv*
** Usage: fossil winsrv METHOD ?SERVICE-NAME? ?OPTIONS?
**
** Where METHOD is one of: create delete show start stop.
**
** The winsrv command manages Fossil as a Windows service. This allows
** (for example) Fossil to be running in the background when no user
** is logged in.
|
| ︙ | ︙ | |||
846 847 848 849 850 851 852 |
{
fossil_fatal("METHOD should be one of:"
" create delete show start stop");
}
return;
}
| < < < < < < < < < | 846 847 848 849 850 851 852 853 |
{
fossil_fatal("METHOD should be one of:"
" create delete show start stop");
}
return;
}
#endif /* _WIN32 -- This code is for win32 only */
|
Changes to src/xfer.c.
| ︙ | ︙ | |||
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
return;
}
blob_zero(&content);
blob_zero(&hash);
blob_extract(pXfer->pIn, n, &content);
if( !cloneFlag && uuid_is_shunned(blob_str(&pXfer->aToken[1])) ){
/* Ignore files that have been shunned */
return;
}
if( isPriv && !g.perm.Private ){
/* Do not accept private files if not authorized */
return;
}
if( cloneFlag ){
if( pXfer->nToken==4 ){
srcid = rid_from_uuid(&pXfer->aToken[2], 1, isPriv);
pXfer->nDeltaRcvd++;
}else{
| > > | 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
return;
}
blob_zero(&content);
blob_zero(&hash);
blob_extract(pXfer->pIn, n, &content);
if( !cloneFlag && uuid_is_shunned(blob_str(&pXfer->aToken[1])) ){
/* Ignore files that have been shunned */
blob_reset(&content);
return;
}
if( isPriv && !g.perm.Private ){
/* Do not accept private files if not authorized */
blob_reset(&content);
return;
}
if( cloneFlag ){
if( pXfer->nToken==4 ){
srcid = rid_from_uuid(&pXfer->aToken[2], 1, isPriv);
pXfer->nDeltaRcvd++;
}else{
|
| ︙ | ︙ | |||
154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
srcid = rid_from_uuid(&pXfer->aToken[2], 1, isPriv);
if( content_get(srcid, &src)==0 ){
rid = content_put_ex(&content, blob_str(&pXfer->aToken[1]), srcid,
0, isPriv);
pXfer->nDanglingFile++;
db_multi_exec("DELETE FROM phantom WHERE rid=%d", rid);
if( !isPriv ) content_make_public(rid);
return;
}
pXfer->nDeltaRcvd++;
blob_delta_apply(&src, &content, &next);
blob_reset(&src);
blob_reset(&content);
content = next;
| > > | 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
srcid = rid_from_uuid(&pXfer->aToken[2], 1, isPriv);
if( content_get(srcid, &src)==0 ){
rid = content_put_ex(&content, blob_str(&pXfer->aToken[1]), srcid,
0, isPriv);
pXfer->nDanglingFile++;
db_multi_exec("DELETE FROM phantom WHERE rid=%d", rid);
if( !isPriv ) content_make_public(rid);
blob_reset(&src);
blob_reset(&content);
return;
}
pXfer->nDeltaRcvd++;
blob_delta_apply(&src, &content, &next);
blob_reset(&src);
blob_reset(&content);
content = next;
|
| ︙ | ︙ | |||
233 234 235 236 237 238 239 240 241 242 243 244 245 246 |
/* Do not accept private files if not authorized */
return;
}
blob_zero(&content);
blob_extract(pXfer->pIn, szC, &content);
if( uuid_is_shunned(blob_str(&pXfer->aToken[1])) ){
/* Ignore files that have been shunned */
return;
}
if( pXfer->nToken==5 ){
srcid = rid_from_uuid(&pXfer->aToken[2], 1, isPriv);
pXfer->nDeltaRcvd++;
}else{
srcid = 0;
| > | 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 |
/* Do not accept private files if not authorized */
return;
}
blob_zero(&content);
blob_extract(pXfer->pIn, szC, &content);
if( uuid_is_shunned(blob_str(&pXfer->aToken[1])) ){
/* Ignore files that have been shunned */
blob_reset(&content);
return;
}
if( pXfer->nToken==5 ){
srcid = rid_from_uuid(&pXfer->aToken[2], 1, isPriv);
pXfer->nDeltaRcvd++;
}else{
srcid = 0;
|
| ︙ | ︙ | |||
418 419 420 421 422 423 424 425 426 427 428 429 430 431 |
if( isPriv ) blob_append(pXfer->pOut, "private\n", -1);
blob_appendf(pXfer->pOut, "file %b %d\n", pUuid, size);
blob_append(pXfer->pOut, blob_buffer(&content), size);
pXfer->nFileSent++;
}else{
pXfer->nDeltaSent++;
}
}
remote_has(rid);
blob_reset(&uuid);
#if 0
if( blob_buffer(pXfer->pOut)[blob_size(pXfer->pOut)-1]!='\n' ){
blob_appendf(pXfer->pOut, "\n", 1);
}
| > | 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 |
if( isPriv ) blob_append(pXfer->pOut, "private\n", -1);
blob_appendf(pXfer->pOut, "file %b %d\n", pUuid, size);
blob_append(pXfer->pOut, blob_buffer(&content), size);
pXfer->nFileSent++;
}else{
pXfer->nDeltaSent++;
}
blob_reset(&content);
}
remote_has(rid);
blob_reset(&uuid);
#if 0
if( blob_buffer(pXfer->pOut)[blob_size(pXfer->pOut)-1]!='\n' ){
blob_appendf(pXfer->pOut, "\n", 1);
}
|
| ︙ | ︙ | |||
441 442 443 444 445 446 447 448 449 450 451 452 | const char *zContent; const char *zUuid; const char *zDelta; int szU; int szC; int rc; int isPrivate; static Stmt q1; isPrivate = content_is_private(rid); if( isPrivate && pXfer->syncPrivate==0 ) return; db_static_prepare(&q1, | > > | | < | | | > | > > > > > > > > | > > > | 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 |
const char *zContent;
const char *zUuid;
const char *zDelta;
int szU;
int szC;
int rc;
int isPrivate;
int srcIsPrivate;
static Stmt q1;
Blob fullContent;
isPrivate = content_is_private(rid);
if( isPrivate && pXfer->syncPrivate==0 ) return;
db_static_prepare(&q1,
"SELECT uuid, size, content, delta.srcid IN private,"
" (SELECT uuid FROM blob WHERE rid=delta.srcid)"
" FROM blob LEFT JOIN delta ON (blob.rid=delta.rid)"
" WHERE blob.rid=:rid"
" AND blob.size>=0"
" AND NOT EXISTS(SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)"
);
db_bind_int(&q1, ":rid", rid);
rc = db_step(&q1);
if( rc==SQLITE_ROW ){
zUuid = db_column_text(&q1, 0);
szU = db_column_int(&q1, 1);
szC = db_column_bytes(&q1, 2);
zContent = db_column_raw(&q1, 2);
srcIsPrivate = db_column_int(&q1, 3);
zDelta = db_column_text(&q1, 4);
if( isPrivate ) blob_append(pXfer->pOut, "private\n", -1);
blob_appendf(pXfer->pOut, "cfile %s ", zUuid);
if( !isPrivate && srcIsPrivate ){
content_get(rid, &fullContent);
szU = blob_size(&fullContent);
blob_compress(&fullContent, &fullContent);
szC = blob_size(&fullContent);
zContent = blob_buffer(&fullContent);
zDelta = 0;
}
if( zDelta ){
blob_appendf(pXfer->pOut, "%s ", zDelta);
pXfer->nDeltaSent++;
}else{
pXfer->nFileSent++;
}
blob_appendf(pXfer->pOut, "%d %d\n", szU, szC);
blob_append(pXfer->pOut, zContent, szC);
if( blob_buffer(pXfer->pOut)[blob_size(pXfer->pOut)-1]!='\n' ){
blob_appendf(pXfer->pOut, "\n", 1);
}
if( !isPrivate && srcIsPrivate ){
blob_reset(&fullContent);
}
}
db_reset(&q1);
}
/*
** Send a gimme message for every phantom.
|
| ︙ | ︙ | |||
571 572 573 574 575 576 577 |
db_ephemeral_blob(&q, 0, &pw);
szPw = blob_size(&pw);
blob_zero(&combined);
blob_copy(&combined, pNonce);
blob_append(&combined, blob_buffer(&pw), szPw);
sha1sum_blob(&combined, &hash);
assert( blob_size(&hash)==40 );
| | | | 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 |
db_ephemeral_blob(&q, 0, &pw);
szPw = blob_size(&pw);
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_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
** again with the SHA1 password.
*/
const char *zPw = db_column_text(&q, 0);
char *zSecret = sha1_shared_secret(zPw, blob_str(pLogin), 0);
blob_zero(&combined);
blob_copy(&combined, pNonce);
blob_append(&combined, zSecret, -1);
free(zSecret);
sha1sum_blob(&combined, &hash);
rc = blob_constant_time_cmp(&hash, pSig);
blob_reset(&hash);
blob_reset(&combined);
}
if( rc==0 ){
const char *zCap;
zCap = db_column_text(&q, 1);
login_set_capabilities(zCap, 0);
|
| ︙ | ︙ | |||
635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 |
Blob cluster, cksum;
Blob deleteWhere;
Stmt q;
int nUncl;
int nRow = 0;
int rid;
/* We should not ever get any private artifacts in the unclustered table.
** But if we do (because of a bug) now is a good time to delete them. */
db_multi_exec(
"DELETE FROM unclustered WHERE rid IN (SELECT rid FROM private)"
);
nUncl = db_int(0, "SELECT count(*) FROM unclustered /*scan*/"
" WHERE NOT EXISTS(SELECT 1 FROM phantom"
" WHERE rid=unclustered.rid)");
if( nUncl>=100 ){
blob_zero(&cluster);
blob_zero(&deleteWhere);
| > > | 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 |
Blob cluster, cksum;
Blob deleteWhere;
Stmt q;
int nUncl;
int nRow = 0;
int rid;
#if 0
/* We should not ever get any private artifacts in the unclustered table.
** But if we do (because of a bug) now is a good time to delete them. */
db_multi_exec(
"DELETE FROM unclustered WHERE rid IN (SELECT rid FROM private)"
);
#endif
nUncl = db_int(0, "SELECT count(*) FROM unclustered /*scan*/"
" WHERE NOT EXISTS(SELECT 1 FROM phantom"
" WHERE rid=unclustered.rid)");
if( nUncl>=100 ){
blob_zero(&cluster);
blob_zero(&deleteWhere);
|
| ︙ | ︙ | |||
773 774 775 776 777 778 779 780 781 782 783 784 785 786 |
** Called when there is an attempt to transfer private content to and
** from a server without authorization.
*/
static void server_private_xfer_not_authorized(void){
@ error not\sauthorized\sto\ssync\sprivate\scontent
}
/*
** If this variable is set, disable login checks. Used for debugging
** only.
*/
static int disableLogin = 0;
| > > > > > > > > > > > > > > > > > > > > > > > > > | 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 |
** Called when there is an attempt to transfer private content to and
** from a server without authorization.
*/
static void server_private_xfer_not_authorized(void){
@ error not\sauthorized\sto\ssync\sprivate\scontent
}
/*
** Run the specified TH1 script, if any, and returns the return code or TH_OK
** when there is no script.
*/
static int run_script(const char *zScript){
if( !zScript ){
return TH_OK; /* No script, return success. */
}
Th_FossilInit(); /* Make sure TH1 is ready. */
return Th_Eval(g.interp, 0, zScript, -1);
}
/*
** Run the pre-transfer TH1 script, if any, and returns the return code.
*/
static int run_common_script(void){
return run_script(db_get("xfer-common-script", 0));
}
/*
** Run the post-push TH1 script, if any, and returns the return code.
*/
static int run_push_script(void){
return run_script(db_get("xfer-push-script", 0));
}
/*
** If this variable is set, disable login checks. Used for debugging
** only.
*/
static int disableLogin = 0;
|
| ︙ | ︙ | |||
830 831 832 833 834 835 836 837 838 839 840 841 842 843 |
g.xferPanic = 1;
db_begin_transaction();
db_multi_exec(
"CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
);
manifest_crosslink_begin();
while( blob_line(xfer.pIn, &xfer.line) ){
if( blob_buffer(&xfer.line)[0]=='#' ) continue;
if( blob_size(&xfer.line)==0 ) continue;
xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));
/* file UUID SIZE \n CONTENT
** file UUID DELTASRC SIZE \n CONTENT
| > > > > > | 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 |
g.xferPanic = 1;
db_begin_transaction();
db_multi_exec(
"CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
);
manifest_crosslink_begin();
if( run_common_script()==TH_ERROR ){
cgi_reset_content();
@ error common\sscript\sfailed:\s%F(Th_GetResult(g.interp, 0))
nErr++;
}
while( blob_line(xfer.pIn, &xfer.line) ){
if( blob_buffer(&xfer.line)[0]=='#' ) continue;
if( blob_size(&xfer.line)==0 ) continue;
xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));
/* file UUID SIZE \n CONTENT
** file UUID DELTASRC SIZE \n CONTENT
|
| ︙ | ︙ | |||
1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 |
{
cgi_reset_content();
@ error bad\scommand:\s%F(blob_str(&xfer.line))
}
blobarray_reset(xfer.aToken, xfer.nToken);
}
if( isPush ){
request_phantoms(&xfer, 500);
}
if( isClone && nGimme==0 ){
/* The initial "clone" message from client to server contains no
** "gimme" cards. On that initial message, send the client an "igot"
** card for every artifact currently in the respository. This will
** cause the client to create phantoms for all artifacts, which will
| > > > > > | 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 |
{
cgi_reset_content();
@ error bad\scommand:\s%F(blob_str(&xfer.line))
}
blobarray_reset(xfer.aToken, xfer.nToken);
}
if( isPush ){
if( run_push_script()==TH_ERROR ){
cgi_reset_content();
@ error push\sscript\sfailed:\s%F(Th_GetResult(g.interp, 0))
nErr++;
}
request_phantoms(&xfer, 500);
}
if( isClone && nGimme==0 ){
/* The initial "clone" message from client to server contains no
** "gimme" cards. On that initial message, send the client an "igot"
** card for every artifact currently in the respository. This will
** cause the client to create phantoms for all artifacts, which will
|
| ︙ | ︙ | |||
1196 1197 1198 1199 1200 1201 1202 |
** into a file named (for example) out.txt. Then run the
** server in gdb:
**
** gdb fossil
** r test-xfer out.txt
*/
void cmd_test_xfer(void){
| < | | 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 |
** into a file named (for example) out.txt. Then run the
** server in gdb:
**
** gdb fossil
** r test-xfer out.txt
*/
void cmd_test_xfer(void){
db_find_and_open_repository(0,0);
if( g.argc!=2 && g.argc!=3 ){
usage("?MESSAGEFILE?");
}
blob_zero(&g.cgiIn);
blob_read_from_file(&g.cgiIn, g.argc==2 ? "-" : g.argv[2]);
disableLogin = 1;
page_xfer();
fossil_print("%s\n", cgi_extract_content());
}
/*
** Format strings for progress reporting.
*/
static const char zLabelFormat[] = "%-10s %10s %10s %10s %10s\n";
static const char zValueFormat[] = "\r%-10s %10d %10d %10d %10d\n";
|
| ︙ | ︙ | |||
1236 1237 1238 1239 1240 1241 1242 |
int configSendMask /* Send these configuration items */
){
int go = 1; /* Loop until zero */
int nCardSent = 0; /* Number of cards sent */
int nCardRcvd = 0; /* Number of cards received */
int nCycle = 0; /* Number of round trips to the server */
int size; /* Size of a config value */
| < | 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 |
int configSendMask /* Send these configuration items */
){
int go = 1; /* Loop until zero */
int nCardSent = 0; /* Number of cards sent */
int nCardRcvd = 0; /* Number of cards received */
int nCycle = 0; /* Number of round trips to the server */
int size; /* Size of a config value */
int origConfigRcvMask; /* Original value of configRcvMask */
int nFileRecv; /* Number of files received */
int mxPhantomReq = 200; /* Max number of phantoms to request per comm */
const char *zCookie; /* Server cookie */
i64 nSent, nRcvd; /* Bytes sent and received (after compression) */
int cloneSeqno = 1; /* Sequence number for clones */
Blob send; /* Text we are sending to the server */
|
| ︙ | ︙ | |||
1378 1379 1380 1381 1382 1383 1384 |
** be unique.
*/
zRandomness = db_text(0, "SELECT hex(randomblob(20))");
blob_appendf(&send, "# %s\n", zRandomness);
free(zRandomness);
/* Exchange messages with the server */
| < | 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 |
** be unique.
*/
zRandomness = db_text(0, "SELECT hex(randomblob(20))");
blob_appendf(&send, "# %s\n", zRandomness);
free(zRandomness);
/* Exchange messages with the server */
fossil_print(zValueFormat, "Sent:",
blob_size(&send), nCardSent+xfer.nGimmeSent+xfer.nIGotSent,
xfer.nFileSent, xfer.nDeltaSent);
nCardSent = 0;
nCardRcvd = 0;
xfer.nFileSent = 0;
xfer.nDeltaSent = 0;
|
| ︙ | ︙ |
Added src/xfersetup.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
/*
** 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/
**
*******************************************************************************
**
** This file contains code to implement the transfer configuration
** setup screens.
*/
#include "config.h"
#include "xfersetup.h"
#include <assert.h>
/*
** Main sub-menu for configuring the transfer system.
** WEBPAGE: xfersetup
*/
void xfersetup_page(void){
login_check_credentials();
if( !g.perm.Setup ){
login_needed();
}
style_header("Transfer Setup");
@ <table border="0" cellspacing="20">
setup_menu_entry("Common", "xfersetup_com",
"Common TH1 code run before all transfer request processing.");
setup_menu_entry("Push", "xfersetup_push",
"Specific TH1 code to run after \"push\" transfer requests.");
@ </table>
style_footer();
}
/*
** Common implementation for the transfer setup editor pages.
*/
static void xfersetup_generic(
const char *zTitle, /* Page title */
const char *zDbField, /* Configuration field being edited */
const char *zDfltValue, /* Default text value */
const char *zDesc, /* Description of this field */
char *(*xText)(const char*), /* Validitity test or NULL */
void (*xRebuild)(void), /* Run after successulf update */
int height /* Height of the edit box */
){
const char *z;
int isSubmit;
login_check_credentials();
if( !g.perm.Setup ){
login_needed();
}
if( P("setup") ){
cgi_redirect("xfersetup");
}
isSubmit = P("submit")!=0;
z = P("x");
if( z==0 ){
z = db_get(zDbField, (char*)zDfltValue);
}
style_header("Edit %s", zTitle);
if( P("clear")!=0 ){
login_verify_csrf_secret();
db_unset(zDbField, 0);
if( xRebuild ) xRebuild();
z = zDfltValue;
}else if( isSubmit ){
char *zErr = 0;
login_verify_csrf_secret();
if( xText && (zErr = xText(z))!=0 ){
@ <p class="xfersetupError">ERROR: %h(zErr)</p>
}else{
db_set(zDbField, z, 0);
if( xRebuild ) xRebuild();
cgi_redirect("xfersetup");
}
}
@ <form action="%s(g.zTop)/%s(g.zPath)" method="post"><div>
login_insert_csrf_secret();
@ <p>%s(zDesc)</p>
@ <textarea name="x" rows="%d(height)" cols="80">%h(z)</textarea>
@ <blockquote><p>
@ <input type="submit" name="submit" value="Apply Changes" />
@ <input type="submit" name="clear" value="Revert To Default" />
@ <input type="submit" name="setup" value="Cancel" />
@ </p></blockquote>
@ </div></form>
@ <hr />
if ( zDfltValue ){
@ <h2>Default %s(zTitle)</h2>
@ <blockquote><pre>
@ %h(zDfltValue)
@ </pre></blockquote>
}
style_footer();
}
static const char *zDefaultXferCommon = 0;
/*
** WEBPAGE: xfersetup_com
*/
void xfersetup_com_page(void){
static const char zDesc[] =
@ Enter TH1 script that initializes variables prior to running
@ any of the transfer request scripts.
;
xfersetup_generic(
"Transfer Common Script",
"xfer-common-script",
zDefaultXferCommon,
zDesc,
0,
0,
30
);
}
static const char *zDefaultXferPush = 0;
/*
** WEBPAGE: xfersetup_push
*/
void xfersetup_push_page(void){
static const char zDesc[] =
@ Enter TH1 script that runs after processing "push" transfer requests.
;
xfersetup_generic(
"Transfer Push Script",
"xfer-push-script",
zDefaultXferPush,
zDesc,
0,
0,
30
);
}
|
Changes to src/zip.c.
| ︙ | ︙ | |||
375 376 377 378 379 380 381 | } manifest_destroy(pManifest); blob_reset(&filename); zip_close(pZip); } /* | | | | 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 | } manifest_destroy(pManifest); blob_reset(&filename); zip_close(pZip); } /* ** COMMAND: zip* ** ** Usage: %fossil zip VERSION OUTPUTFILE [--name DIRECTORYNAME] [-R|--repository REPO] ** ** Generate a ZIP archive for a specified version. If the --name option is ** used, it argument becomes the name of the top-level directory in the ** resulting ZIP archive. If --name is omitted, the top-level directory ** named is derived from the project name, the check-in date and time, and ** the artifact ID of the check-in. */ |
| ︙ | ︙ |
Changes to test/merge5.test.
| ︙ | ︙ | |||
36 37 38 39 40 41 42 |
} else {
test merge5-$testid 1
}
}
catch {exec $::fossilexe info} res
puts res=$res
| | > > > > | 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
} else {
test merge5-$testid 1
}
}
catch {exec $::fossilexe info} res
puts res=$res
if {![regexp {use --repository} $res]} {
puts stderr "Cannot run this test within an open checkout"
return
}
#
# Fossil will write data on $HOME, running 'fossil open' here.
# We need not to clutter the $HOME of the test caller.
set env(HOME) [pwd]
# Construct a test repository
#
exec sqlite3 m5.fossil <$testdir/${testfile}_repo.sql
fossil rebuild m5.fossil
fossil open m5.fossil
fossil update baseline
|
| ︙ | ︙ |
Changes to test/merge_renames.test.
1 2 3 4 5 6 7 |
#
# Tests for merging with renames
#
#
catch {exec $::fossilexe info} res
puts res=$res
| | > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#
# Tests for merging with renames
#
#
catch {exec $::fossilexe info} res
puts res=$res
if {![regexp {use --repository} $res]} {
puts stderr "Cannot run this test within an open checkout"
return
}
# Fossil will write data on $HOME, running 'fossil new' here.
# We need not to clutter the $HOME of the test caller.
set env(HOME) [pwd]
######################################
# Test 1 #
# Reported: Ticket [554f44ee74e3d] #
######################################
fossil new rep.fossil
|
| ︙ | ︙ |
Added test/th1-tcl.test.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
#
# Copyright (c) 2011 D. Richard Hipp
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the Simplified BSD License (also
# known as the "2-Clause License" or "FreeBSD License".)
#
# This program is distributed in the hope that it will be useful,
# but without any warranty; without even the implied warranty of
# merchantability or fitness for a particular purpose.
#
# Author contact information:
# drh@hwaci.com
# http://www.hwaci.com/drh/
#
############################################################################
#
# TH1/Tcl integration
#
set dir [file dirname [info script]]
###############################################################################
set env(TH1_ENABLE_TCL) 1; # Tcl integration must be enabled for this test.
###############################################################################
fossil test-th-render [file nativename [file join $dir th1-tcl1.txt]]
test th1-tcl-1 {[regexp -- {^\d+
\d+
\d+
via Tcl invoke
4
4
two words
one_word
three words now
\d+
two words
4
\d+
two words
4
\d+
one_word
three words now
$} [string map [list \r\n \n] $RESULT]]}
###############################################################################
fossil test-th-render [file nativename [file join $dir th1-tcl2.txt]]
test th1-tcl-2 {[regexp -- {^\d+
$} [string map [list \r\n \n] $RESULT]]}
###############################################################################
fossil test-th-render [file nativename [file join $dir th1-tcl3.txt]]
test th1-tcl-3 {$RESULT eq {<hr><p class="thmainError">ERROR:\
invalid command name "bad_command"</p>}}
###############################################################################
fossil test-th-render [file nativename [file join $dir th1-tcl4.txt]]
test th1-tcl-4 {$RESULT eq {<hr><p class="thmainError">ERROR:\
divide by zero</p>}}
###############################################################################
fossil test-th-render [file nativename [file join $dir th1-tcl5.txt]]
test th1-tcl-5 {$RESULT eq {<hr><p class="thmainError">ERROR:\
Tcl command not found: bad_command</p>} || $RESULT eq {<hr><p\
class="thmainError">ERROR: invalid command name "bad_command"</p>}}
###############################################################################
fossil test-th-render [file nativename [file join $dir th1-tcl6.txt]]
test th1-tcl-6 {$RESULT eq {<hr><p class="thmainError">ERROR:\
no such command: bad_command</p>}}
###############################################################################
fossil test-th-render [file nativename [file join $dir th1-tcl7.txt]]
test th1-tcl-7 {$RESULT eq {<hr><p class="thmainError">ERROR:\
syntax error in expression: "2**0"</p>}}
###############################################################################
fossil test-th-render [file nativename [file join $dir th1-tcl8.txt]]
test th1-tcl-8 {$RESULT eq {<hr><p class="thmainError">ERROR:\
Cannot invoke Tcl command: tailcall</p>} || $RESULT eq {<hr><p\
class="thmainError">ERROR: tailcall can only be called from a proc or\
lambda</p>}}
|
Added test/th1-tcl1.txt.
> > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<th1>
#
# This is a "TH1 fragment" used to test the Tcl integration features of TH1.
# The corresponding test file executes this file using the test-th-render
# Fossil command.
#
set channel stdout; tclInvoke set channel $channel
proc doOut {msg} {puts $msg; puts \n}
doOut [tclEval clock seconds]
doOut [tclEval {set x [clock seconds]}]
tclEval {puts $channel "[clock seconds]"}
tclInvoke puts $channel "via Tcl invoke"
doOut [tclExpr 2+2]
doOut [tclExpr 2 + 2]
doOut [tclInvoke set x "two words"]
doOut [tclInvoke eval set y one_word]
doOut [tclInvoke eval {set z "three words now"}]
doOut [set x [tclEval {set x [clock seconds]}]]
doOut [tclInvoke th1Eval {set y "two words"}]
doOut [set z [tclInvoke th1Expr {2+2}]]
doOut $x
doOut $y
doOut $z
doOut [tclEval set x]
doOut [tclEval set y]
doOut [tclEval set z]
</th1>
|
Added test/th1-tcl2.txt.
> > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<th1>
#
# This is a "TH1 fragment" used to test the Tcl integration features of TH1.
# The corresponding test file executes this file using the test-th-render
# Fossil command.
#
# NOTE: This test requires that the SQLite package be available for the Tcl
# interpreter that is linked to the Fossil executable.
#
tclInvoke set repository_name [repository 1]
proc doOut {msg} {puts $msg; puts \n}
doOut [tclEval {
package require sqlite3
sqlite3 db $repository_name
set x [db eval {SELECT COUNT(*) FROM user;}]
db close
return $x
}]
</th1>
|
Added test/th1-tcl3.txt.
> > > > > > > > > | 1 2 3 4 5 6 7 8 9 |
<th1>
#
# This is a "TH1 fragment" used to test the Tcl integration features of TH1.
# The corresponding test file executes this file using the test-th-render
# Fossil command.
#
proc doOut {msg} {puts $msg; puts \n}
doOut [tclEval bad_command]
</th1>
|
Added test/th1-tcl4.txt.
> > > > > > > > > | 1 2 3 4 5 6 7 8 9 |
<th1>
#
# This is a "TH1 fragment" used to test the Tcl integration features of TH1.
# The corresponding test file executes this file using the test-th-render
# Fossil command.
#
proc doOut {msg} {puts $msg; puts \n}
doOut [tclExpr 2/0]
</th1>
|
Added test/th1-tcl5.txt.
> > > > > > > > > | 1 2 3 4 5 6 7 8 9 |
<th1>
#
# This is a "TH1 fragment" used to test the Tcl integration features of TH1.
# The corresponding test file executes this file using the test-th-render
# Fossil command.
#
proc doOut {msg} {puts $msg; puts \n}
doOut [tclInvoke bad_command]
</th1>
|
Added test/th1-tcl6.txt.
> > > > > > > > > | 1 2 3 4 5 6 7 8 9 |
<th1>
#
# This is a "TH1 fragment" used to test the Tcl integration features of TH1.
# The corresponding test file executes this file using the test-th-render
# Fossil command.
#
proc doOut {msg} {puts $msg; puts \n}
doOut [tclEval th1Eval bad_command]
</th1>
|
Added test/th1-tcl7.txt.
> > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<th1>
#
# This is a "TH1 fragment" used to test the Tcl integration features of TH1.
# The corresponding test file executes this file using the test-th-render
# Fossil command.
#
proc doOut {msg} {puts $msg; puts \n}
#
# BUGBUG: Attempting to divide by zero will crash TH1 with the error:
# "child killed: floating-point exception"
#
# doOut [tclEval th1Expr 2/0]
#
# NOTE: For now, just cause an expression syntax error.
#
doOut [tclEval th1Expr 2**0]
</th1>
|
Added test/th1-tcl8.txt.
> > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<th1>
#
# This is a "TH1 fragment" used to test the Tcl integration features of TH1.
# The corresponding test file executes this file using the test-th-render
# Fossil command.
#
proc doOut {msg} {puts $msg; puts \n}
if {[tclInvoke set tcl_version] >= 8.6} {
doOut [tclInvoke tailcall set x 1]
} else {
doOut "This test requires Tcl 8.6 or higher."
}
</th1>
|
Changes to win/Makefile.PellesCGMake.
|
| > > | > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # ############################################################################## # WARNING: DO NOT EDIT, AUTOMATICALLY GENERATED FILE (SEE "src/makemake.tcl") ############################################################################## # # This file is automatically generated. Instead of editing this # file, edit "makemake.tcl" then run "tclsh makemake.tcl" # to regenerate this file. # # HowTo # ----- # # This is a Makefile to compile fossil with PellesC from # http://www.smorgasbordet.com/pellesc/index.htm |
| ︙ | ︙ |
Changes to win/Makefile.dmc.
|
| > > | > | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # ############################################################################## # WARNING: DO NOT EDIT, AUTOMATICALLY GENERATED FILE (SEE "src/makemake.tcl") ############################################################################## # # This file is automatically generated. Instead of editing this # file, edit "makemake.tcl" then run "tclsh makemake.tcl" # to regenerate this file. # B = .. SRCDIR = $B\src OBJDIR = . O = .obj E = .exe |
| ︙ | ︙ | |||
20 21 22 23 24 25 26 | CFLAGS = -o BCC = $(DMDIR)\bin\dmc $(CFLAGS) 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 | | | | | 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | CFLAGS = -o BCC = $(DMDIR)\bin\dmc $(CFLAGS) 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_artifact_.c json_branch_.c json_diff_.c json_login_.c json_query_.c json_report_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c login_.c main_.c manifest_.c md5_.c merge_.c merge3_.c name_.c path_.c pivot_.c popen_.c pqueue_.c printf_.c rebuild_.c report_.c rss_.c schema_.c search_.c setup_.c sha1_.c shun_.c skins_.c sqlcmd_.c stash_.c stat_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c update_.c url_.c user_.c verify_.c vfile_.c wiki_.c wikiformat_.c winhttp_.c xfer_.c xfersetup_.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_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\login$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\name$O $(OBJDIR)\path$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\rebuild$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\setup$O $(OBJDIR)\sha1$O $(OBJDIR)\shun$O $(OBJDIR)\skins$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winhttp$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$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__ APPNAME = $(OBJDIR)\fossil$(E) all: $(APPNAME) $(APPNAME) : translate$E mkindex$E headers $(OBJ) $(OBJDIR)\link cd $(OBJDIR) $(DMDIR)\bin\link @link $(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_artifact json_branch json_diff json_login json_query json_report json_tag json_timeline json_user json_wiki leaf login main manifest md5 merge merge3 name path pivot popen pqueue printf rebuild report rss schema search setup sha1 shun skins sqlcmd stash stat style sync tag tar th_main timeline tkt tktsetup undo update url user verify vfile wiki wikiformat winhttp xfer xfersetup zip shell sqlite3 th th_lang > $@ +echo fossil >> $@ +echo fossil >> $@ +echo $(LIBS) >> $@ +echo. >> $@ +echo fossil >> $@ translate$E: $(SRCDIR)\translate.c |
| ︙ | ︙ | |||
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | $(TCC) -o$@ -c $(SQLITE_OPTIONS) $** $(OBJDIR)\th$O : $(SRCDIR)\th.c $(TCC) -o$@ -c $** $(OBJDIR)\th_lang$O : $(SRCDIR)\th_lang.c $(TCC) -o$@ -c $** VERSION.h : version$E $B\manifest.uuid $B\manifest $B\VERSION +$** > $@ page_index.h: mkindex$E $(SRC) +$** > $@ clean: -del $(OBJDIR)\*.obj -del *.obj *_.c *.h *.map realclean: -del $(APPNAME) translate$E mkindex$E makeheaders$E mkversion$E $(OBJDIR)\add$O : add_.c add.h $(TCC) -o$@ -c add_.c add_.c : $(SRCDIR)\add.c +translate$E $** > $@ | > > > > > > > > > > > > > > > > | 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | $(TCC) -o$@ -c $(SQLITE_OPTIONS) $** $(OBJDIR)\th$O : $(SRCDIR)\th.c $(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) +$** > $@ clean: -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_artifact$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_branch$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_diff$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_login$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_query$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_report$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_tag$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_timeline$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_user$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_wiki$O : $(SRCDIR)\json_detail.h $(OBJDIR)\add$O : add_.c add.h $(TCC) -o$@ -c add_.c add_.c : $(SRCDIR)\add.c +translate$E $** > $@ |
| ︙ | ︙ | |||
312 313 314 315 316 317 318 319 320 321 322 323 324 325 | +translate$E $** > $@ $(OBJDIR)\info$O : info_.c info.h $(TCC) -o$@ -c info_.c info_.c : $(SRCDIR)\info.c +translate$E $** > $@ $(OBJDIR)\leaf$O : leaf_.c leaf.h $(TCC) -o$@ -c leaf_.c leaf_.c : $(SRCDIR)\leaf.c +translate$E $** > $@ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 | +translate$E $** > $@ $(OBJDIR)\info$O : info_.c info.h $(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_artifact$O : json_artifact_.c json_artifact.h $(TCC) -o$@ -c json_artifact_.c json_artifact_.c : $(SRCDIR)\json_artifact.c +translate$E $** > $@ $(OBJDIR)\json_branch$O : json_branch_.c json_branch.h $(TCC) -o$@ -c json_branch_.c json_branch_.c : $(SRCDIR)\json_branch.c +translate$E $** > $@ $(OBJDIR)\json_diff$O : json_diff_.c json_diff.h $(TCC) -o$@ -c json_diff_.c json_diff_.c : $(SRCDIR)\json_diff.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_query$O : json_query_.c json_query.h $(TCC) -o$@ -c json_query_.c json_query_.c : $(SRCDIR)\json_query.c +translate$E $** > $@ $(OBJDIR)\json_report$O : json_report_.c json_report.h $(TCC) -o$@ -c json_report_.c json_report_.c : $(SRCDIR)\json_report.c +translate$E $** > $@ $(OBJDIR)\json_tag$O : json_tag_.c json_tag.h $(TCC) -o$@ -c json_tag_.c json_tag_.c : $(SRCDIR)\json_tag.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_user$O : json_user_.c json_user.h $(TCC) -o$@ -c json_user_.c json_user_.c : $(SRCDIR)\json_user.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 $** > $@ |
| ︙ | ︙ | |||
570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 | +translate$E $** > $@ $(OBJDIR)\xfer$O : xfer_.c xfer.h $(TCC) -o$@ -c xfer_.c xfer_.c : $(SRCDIR)\xfer.c +translate$E $** > $@ $(OBJDIR)\zip$O : zip_.c zip.h $(TCC) -o$@ -c zip_.c zip_.c : $(SRCDIR)\zip.c +translate$E $** > $@ headers: makeheaders$E page_index.h VERSION.h | > > > > > > | | 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 | +translate$E $** > $@ $(OBJDIR)\xfer$O : xfer_.c xfer.h $(TCC) -o$@ -c xfer_.c xfer_.c : $(SRCDIR)\xfer.c +translate$E $** > $@ $(OBJDIR)\xfersetup$O : xfersetup_.c xfersetup.h $(TCC) -o$@ -c xfersetup_.c xfersetup_.c : $(SRCDIR)\xfersetup.c +translate$E $** > $@ $(OBJDIR)\zip$O : zip_.c zip.h $(TCC) -o$@ -c zip_.c 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_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_diff_.c:json_diff.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h path_.c:path.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR)\cson_amalgamation.h @copy /Y nul: headers |
Changes to win/Makefile.mingw.
1 2 | #!/usr/bin/make # | > > > > > > > > | < | > > > | > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > | | > > > > > > > > > > > > > > | > > > > > | | > > | > > > > > > > > > > > > | > | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | #!/usr/bin/make # ############################################################################## # WARNING: DO NOT EDIT, AUTOMATICALLY GENERATED FILE (SEE "src/makemake.tcl") ############################################################################## # # This file is automatically generated. Instead of editing this # file, edit "makemake.tcl" then run "tclsh makemake.tcl" # to regenerate this file. # # This is a makefile for us on windows using MinGW. # #### The toplevel directory of the source tree. Fossil can be built # in a directory that is separate from the source tree. Just change # the following to point from the build directory to the src/ folder. # SRCDIR = src #### The directory into which object code files should be written. # OBJDIR = wbld #### C Compiler and options for use in building executables that # will run on the platform that is doing the build. This is used # to compile code-generator programs as part of the build process. # See TCC below for the C compiler for building the finished binary. # BCC = gcc #### Enable HTTPS support via OpenSSL (links to libssl and libcrypto) # # FOSSIL_ENABLE_SSL = 1 #### Enable scripting support via Tcl/Tk # # FOSSIL_ENABLE_TCL = 1 #### Use the Tcl source directory instead of the install directory? # This is useful when Tcl has been compiled statically with MinGW. # FOSSIL_TCL_SOURCE = 1 #### The directories where the zlib include and library files are located. # The recommended usage here is to use the Sysinternals junction tool # to create a hard link between an "zlib-1.x.y" sub-directory of the # Fossil source code directory and the target zlib source directory. # ZINCDIR = $(SRCDIR)/../zlib-1.2.5 ZLIBDIR = $(SRCDIR)/../zlib-1.2.5 #### The directories where the OpenSSL include and library files are located. # The recommended usage here is to use the Sysinternals junction tool # to create a hard link between an "openssl-1.x" sub-directory of the # Fossil source code directory and the target OpenSSL source directory. # OPENSSLINCDIR = $(SRCDIR)/../openssl-1.0.0e/include OPENSSLLIBDIR = $(SRCDIR)/../openssl-1.0.0e #### Either the directory where the Tcl library is installed or the Tcl # source code directory resides (depending on the value of the macro # FOSSIL_TCL_SOURCE). If this points to the Tcl install directory, # this directory must have "include" and "lib" sub-directories. If # this points to the Tcl source code directory, this directory must # have "generic" and "win" sub-directories. The recommended usage # here is to use the Sysinternals junction tool to create a hard # link between a "tcl-8.x" sub-directory of the Fossil source code # directory and the target Tcl directory. This removes the need to # hard-code the necessary paths in this Makefile. # TCLDIR = $(SRCDIR)/../tcl-8.6 #### The Tcl source code directory. This defaults to the same value as # TCLDIR macro (above), which may not be correct. This value will # only be used if the FOSSIL_TCL_SOURCE macro is defined. # TCLSRCDIR = $(TCLDIR) #### The Tcl include and library directories. These values will only be # used if the FOSSIL_TCL_SOURCE macro is not defined. # TCLINCDIR = $(TCLDIR)/include TCLLIBDIR = $(TCLDIR)/lib #### Tcl: Which Tcl library do we want to use (8.4, 8.5, 8.6, etc)? # LIBTCL = -ltcl86 #### C Compile and options for use in building executables that # will run on the target platform. This is usually the same # as BCC, unless you are cross-compiling. This C compiler builds # the finished binary for fossil. The BCC compiler above is used # for building intermediate code-generator tools. # TCC = gcc -Os -Wall -L$(ZLIBDIR) -I$(ZINCDIR) # With HTTPS support ifdef FOSSIL_ENABLE_SSL TCC += -L$(OPENSSLLIBDIR) -I$(OPENSSLINCDIR) endif # With Tcl support ifdef FOSSIL_ENABLE_TCL ifdef FOSSIL_TCL_SOURCE TCC += -L$(TCLSRCDIR)/win -I$(TCLSRCDIR)/generic -I$(TCLSRCDIR)/win else TCC += -L$(TCLLIBDIR) -I$(TCLINCDIR) endif endif # With HTTPS support ifdef FOSSIL_ENABLE_SSL TCC += -DFOSSIL_ENABLE_SSL=1 endif # With Tcl support (statically linked) ifdef FOSSIL_ENABLE_TCL TCC += -DFOSSIL_ENABLE_TCL=1 -DSTATIC_BUILD endif #### Extra arguments for linking the finished binary. Fossil needs # to link against the Z-Lib compression library. There are no # other mandatory dependencies. We add the -static option here # so that we can build a static executable that will run in a # chroot jail. # LIB = -static LIB += -lmingwex -lz # OpenSSL: Add the necessary libaries required, if enabled. ifdef FOSSIL_ENABLE_SSL LIB += -lssl -lcrypto -lgdi32 endif # Tcl: Add the necessary libaries required, if enabled. ifdef FOSSIL_ENABLE_TCL LIB += $(LIBTCL) endif #### These libraries MUST appear in the same order as they do for Tcl # or linking with it will not work (exact reason unknown). # ifdef FOSSIL_ENABLE_TCL LIB += -lnetapi32 -lkernel32 -luser32 -ladvapi32 -lws2_32 else LIB += -lws2_32 endif #### Tcl shell for use in running the fossil test suite. This is only # used for testing. # TCLSH = tclsh #### Nullsoft installer MakeNSIS location # MAKENSIS = "$(ProgramFiles)\NSIS\MakeNSIS.exe" #### Include a configuration file that can override any one of these settings. # -include config.w32 # STOP HERE # You should not need to change anything below this line |
| ︙ | ︙ | |||
108 109 110 111 112 113 114 115 116 117 118 119 120 121 | $(SRCDIR)/gzip.c \ $(SRCDIR)/http.c \ $(SRCDIR)/http_socket.c \ $(SRCDIR)/http_ssl.c \ $(SRCDIR)/http_transport.c \ $(SRCDIR)/import.c \ $(SRCDIR)/info.c \ $(SRCDIR)/leaf.c \ $(SRCDIR)/login.c \ $(SRCDIR)/main.c \ $(SRCDIR)/manifest.c \ $(SRCDIR)/md5.c \ $(SRCDIR)/merge.c \ $(SRCDIR)/merge3.c \ | > > > > > > > > > > > | 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 | $(SRCDIR)/gzip.c \ $(SRCDIR)/http.c \ $(SRCDIR)/http_socket.c \ $(SRCDIR)/http_ssl.c \ $(SRCDIR)/http_transport.c \ $(SRCDIR)/import.c \ $(SRCDIR)/info.c \ $(SRCDIR)/json.c \ $(SRCDIR)/json_artifact.c \ $(SRCDIR)/json_branch.c \ $(SRCDIR)/json_diff.c \ $(SRCDIR)/json_login.c \ $(SRCDIR)/json_query.c \ $(SRCDIR)/json_report.c \ $(SRCDIR)/json_tag.c \ $(SRCDIR)/json_timeline.c \ $(SRCDIR)/json_user.c \ $(SRCDIR)/json_wiki.c \ $(SRCDIR)/leaf.c \ $(SRCDIR)/login.c \ $(SRCDIR)/main.c \ $(SRCDIR)/manifest.c \ $(SRCDIR)/md5.c \ $(SRCDIR)/merge.c \ $(SRCDIR)/merge3.c \ |
| ︙ | ︙ | |||
151 152 153 154 155 156 157 158 159 160 161 162 163 164 | $(SRCDIR)/user.c \ $(SRCDIR)/verify.c \ $(SRCDIR)/vfile.c \ $(SRCDIR)/wiki.c \ $(SRCDIR)/wikiformat.c \ $(SRCDIR)/winhttp.c \ $(SRCDIR)/xfer.c \ $(SRCDIR)/zip.c TRANS_SRC = \ $(OBJDIR)/add_.c \ $(OBJDIR)/allrepo_.c \ $(OBJDIR)/attach_.c \ $(OBJDIR)/bag_.c \ | > | 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 | $(SRCDIR)/user.c \ $(SRCDIR)/verify.c \ $(SRCDIR)/vfile.c \ $(SRCDIR)/wiki.c \ $(SRCDIR)/wikiformat.c \ $(SRCDIR)/winhttp.c \ $(SRCDIR)/xfer.c \ $(SRCDIR)/xfersetup.c \ $(SRCDIR)/zip.c TRANS_SRC = \ $(OBJDIR)/add_.c \ $(OBJDIR)/allrepo_.c \ $(OBJDIR)/attach_.c \ $(OBJDIR)/bag_.c \ |
| ︙ | ︙ | |||
192 193 194 195 196 197 198 199 200 201 202 203 204 205 | $(OBJDIR)/gzip_.c \ $(OBJDIR)/http_.c \ $(OBJDIR)/http_socket_.c \ $(OBJDIR)/http_ssl_.c \ $(OBJDIR)/http_transport_.c \ $(OBJDIR)/import_.c \ $(OBJDIR)/info_.c \ $(OBJDIR)/leaf_.c \ $(OBJDIR)/login_.c \ $(OBJDIR)/main_.c \ $(OBJDIR)/manifest_.c \ $(OBJDIR)/md5_.c \ $(OBJDIR)/merge_.c \ $(OBJDIR)/merge3_.c \ | > > > > > > > > > > > | 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 | $(OBJDIR)/gzip_.c \ $(OBJDIR)/http_.c \ $(OBJDIR)/http_socket_.c \ $(OBJDIR)/http_ssl_.c \ $(OBJDIR)/http_transport_.c \ $(OBJDIR)/import_.c \ $(OBJDIR)/info_.c \ $(OBJDIR)/json_.c \ $(OBJDIR)/json_artifact_.c \ $(OBJDIR)/json_branch_.c \ $(OBJDIR)/json_diff_.c \ $(OBJDIR)/json_login_.c \ $(OBJDIR)/json_query_.c \ $(OBJDIR)/json_report_.c \ $(OBJDIR)/json_tag_.c \ $(OBJDIR)/json_timeline_.c \ $(OBJDIR)/json_user_.c \ $(OBJDIR)/json_wiki_.c \ $(OBJDIR)/leaf_.c \ $(OBJDIR)/login_.c \ $(OBJDIR)/main_.c \ $(OBJDIR)/manifest_.c \ $(OBJDIR)/md5_.c \ $(OBJDIR)/merge_.c \ $(OBJDIR)/merge3_.c \ |
| ︙ | ︙ | |||
235 236 237 238 239 240 241 242 243 244 245 246 247 248 | $(OBJDIR)/user_.c \ $(OBJDIR)/verify_.c \ $(OBJDIR)/vfile_.c \ $(OBJDIR)/wiki_.c \ $(OBJDIR)/wikiformat_.c \ $(OBJDIR)/winhttp_.c \ $(OBJDIR)/xfer_.c \ $(OBJDIR)/zip_.c OBJ = \ $(OBJDIR)/add.o \ $(OBJDIR)/allrepo.o \ $(OBJDIR)/attach.o \ $(OBJDIR)/bag.o \ | > | 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 | $(OBJDIR)/user_.c \ $(OBJDIR)/verify_.c \ $(OBJDIR)/vfile_.c \ $(OBJDIR)/wiki_.c \ $(OBJDIR)/wikiformat_.c \ $(OBJDIR)/winhttp_.c \ $(OBJDIR)/xfer_.c \ $(OBJDIR)/xfersetup_.c \ $(OBJDIR)/zip_.c OBJ = \ $(OBJDIR)/add.o \ $(OBJDIR)/allrepo.o \ $(OBJDIR)/attach.o \ $(OBJDIR)/bag.o \ |
| ︙ | ︙ | |||
276 277 278 279 280 281 282 283 284 285 286 287 288 289 | $(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 \ | > > > > > > > > > > > | 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 | $(OBJDIR)/gzip.o \ $(OBJDIR)/http.o \ $(OBJDIR)/http_socket.o \ $(OBJDIR)/http_ssl.o \ $(OBJDIR)/http_transport.o \ $(OBJDIR)/import.o \ $(OBJDIR)/info.o \ $(OBJDIR)/json.o \ $(OBJDIR)/json_artifact.o \ $(OBJDIR)/json_branch.o \ $(OBJDIR)/json_diff.o \ $(OBJDIR)/json_login.o \ $(OBJDIR)/json_query.o \ $(OBJDIR)/json_report.o \ $(OBJDIR)/json_tag.o \ $(OBJDIR)/json_timeline.o \ $(OBJDIR)/json_user.o \ $(OBJDIR)/json_wiki.o \ $(OBJDIR)/leaf.o \ $(OBJDIR)/login.o \ $(OBJDIR)/main.o \ $(OBJDIR)/manifest.o \ $(OBJDIR)/md5.o \ $(OBJDIR)/merge.o \ $(OBJDIR)/merge3.o \ |
| ︙ | ︙ | |||
319 320 321 322 323 324 325 326 327 328 329 330 331 332 | $(OBJDIR)/user.o \ $(OBJDIR)/verify.o \ $(OBJDIR)/vfile.o \ $(OBJDIR)/wiki.o \ $(OBJDIR)/wikiformat.o \ $(OBJDIR)/winhttp.o \ $(OBJDIR)/xfer.o \ $(OBJDIR)/zip.o APPNAME = fossil.exe TRANSLATE = $(subst /,\\,$(OBJDIR)/translate.exe) MAKEHEADERS = $(subst /,\\,$(OBJDIR)/makeheaders.exe) MKINDEX = $(subst /,\\,$(OBJDIR)/mkindex.exe) VERSION = $(subst /,\\,$(OBJDIR)/version.exe) | > | 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 | $(OBJDIR)/user.o \ $(OBJDIR)/verify.o \ $(OBJDIR)/vfile.o \ $(OBJDIR)/wiki.o \ $(OBJDIR)/wikiformat.o \ $(OBJDIR)/winhttp.o \ $(OBJDIR)/xfer.o \ $(OBJDIR)/xfersetup.o \ $(OBJDIR)/zip.o APPNAME = fossil.exe TRANSLATE = $(subst /,\\,$(OBJDIR)/translate.exe) MAKEHEADERS = $(subst /,\\,$(OBJDIR)/makeheaders.exe) MKINDEX = $(subst /,\\,$(OBJDIR)/mkindex.exe) VERSION = $(subst /,\\,$(OBJDIR)/version.exe) |
| ︙ | ︙ | |||
352 353 354 355 356 357 358 | $(OBJDIR)/mkindex: $(SRCDIR)/mkindex.c $(BCC) -o $(OBJDIR)/mkindex $(SRCDIR)/mkindex.c $(VERSION): $(SRCDIR)/mkversion.c $(BCC) -o $(OBJDIR)/version $(SRCDIR)/mkversion.c | | | | | > > > > | | | | | 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 | $(OBJDIR)/mkindex: $(SRCDIR)/mkindex.c $(BCC) -o $(OBJDIR)/mkindex $(SRCDIR)/mkindex.c $(VERSION): $(SRCDIR)/mkversion.c $(BCC) -o $(OBJDIR)/version $(SRCDIR)/mkversion.c # WARNING. DANGER. Running the test suite modifies the repository the # build is done from, i.e. the checkout belongs to. Do not sync/push # the repository after running the tests. test: $(OBJDIR) $(APPNAME) $(TCLSH) $(SRCDIR)/../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 ifdef FOSSIL_ENABLE_TCL EXTRAOBJ += $(OBJDIR)/th_tcl.o endif $(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 # an executable named "manifest" out of the file named "manifest.c" # $(SRCDIR)/../manifest: # noop # Requires MSYS to be installed in addition to the MinGW, for the "rm" # command. "del" will not work here because it is not a separate command # but a MSDOS-shell builtin. # clean: rm -rf $(OBJDIR) $(APPNAME) setup: $(OBJDIR) $(APPNAME) $(MAKENSIS) ./fossil.nsi $(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_artifact_.c:$(OBJDIR)/json_artifact.h $(OBJDIR)/json_branch_.c:$(OBJDIR)/json_branch.h $(OBJDIR)/json_diff_.c:$(OBJDIR)/json_diff.h $(OBJDIR)/json_login_.c:$(OBJDIR)/json_login.h $(OBJDIR)/json_query_.c:$(OBJDIR)/json_query.h $(OBJDIR)/json_report_.c:$(OBJDIR)/json_report.h $(OBJDIR)/json_tag_.c:$(OBJDIR)/json_tag.h $(OBJDIR)/json_timeline_.c:$(OBJDIR)/json_timeline.h $(OBJDIR)/json_user_.c:$(OBJDIR)/json_user.h $(OBJDIR)/json_wiki_.c:$(OBJDIR)/json_wiki.h $(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h $(OBJDIR)/login_.c:$(OBJDIR)/login.h $(OBJDIR)/main_.c:$(OBJDIR)/main.h $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h $(OBJDIR)/name_.c:$(OBJDIR)/name.h $(OBJDIR)/path_.c:$(OBJDIR)/path.h $(OBJDIR)/pivot_.c:$(OBJDIR)/pivot.h $(OBJDIR)/popen_.c:$(OBJDIR)/popen.h $(OBJDIR)/pqueue_.c:$(OBJDIR)/pqueue.h $(OBJDIR)/printf_.c:$(OBJDIR)/printf.h $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h $(OBJDIR)/report_.c:$(OBJDIR)/report.h $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h $(OBJDIR)/search_.c:$(OBJDIR)/search.h $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h $(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h $(OBJDIR)/style_.c:$(OBJDIR)/style.h $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h $(OBJDIR)/update_.c:$(OBJDIR)/update.h $(OBJDIR)/url_.c:$(OBJDIR)/url.h $(OBJDIR)/user_.c:$(OBJDIR)/user.h $(OBJDIR)/verify_.c:$(OBJDIR)/verify.h $(OBJDIR)/vfile_.c:$(OBJDIR)/vfile.h $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h $(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h $(OBJDIR)/xfersetup_.c:$(OBJDIR)/xfersetup.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 $(TRANSLATE) $(SRCDIR)/add.c >$(OBJDIR)/add_.c |
| ︙ | ︙ | |||
657 658 659 660 661 662 663 664 665 666 667 668 669 670 | $(OBJDIR)/info_.c: $(SRCDIR)/info.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/info.c >$(OBJDIR)/info_.c $(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)/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 leaf.h: $(OBJDIR)/headers | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 | $(OBJDIR)/info_.c: $(SRCDIR)/info.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/info.c >$(OBJDIR)/info_.c $(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_artifact_.c: $(SRCDIR)/json_artifact.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/json_artifact.c >$(OBJDIR)/json_artifact_.c $(OBJDIR)/json_artifact.o: $(OBJDIR)/json_artifact_.c $(OBJDIR)/json_artifact.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_artifact.o -c $(OBJDIR)/json_artifact_.c json_artifact.h: $(OBJDIR)/headers $(OBJDIR)/json_branch_.c: $(SRCDIR)/json_branch.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/json_branch.c >$(OBJDIR)/json_branch_.c $(OBJDIR)/json_branch.o: $(OBJDIR)/json_branch_.c $(OBJDIR)/json_branch.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_branch.o -c $(OBJDIR)/json_branch_.c json_branch.h: $(OBJDIR)/headers $(OBJDIR)/json_diff_.c: $(SRCDIR)/json_diff.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/json_diff.c >$(OBJDIR)/json_diff_.c $(OBJDIR)/json_diff.o: $(OBJDIR)/json_diff_.c $(OBJDIR)/json_diff.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_diff.o -c $(OBJDIR)/json_diff_.c json_diff.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_query_.c: $(SRCDIR)/json_query.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/json_query.c >$(OBJDIR)/json_query_.c $(OBJDIR)/json_query.o: $(OBJDIR)/json_query_.c $(OBJDIR)/json_query.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_query.o -c $(OBJDIR)/json_query_.c json_query.h: $(OBJDIR)/headers $(OBJDIR)/json_report_.c: $(SRCDIR)/json_report.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/json_report.c >$(OBJDIR)/json_report_.c $(OBJDIR)/json_report.o: $(OBJDIR)/json_report_.c $(OBJDIR)/json_report.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_report.o -c $(OBJDIR)/json_report_.c json_report.h: $(OBJDIR)/headers $(OBJDIR)/json_tag_.c: $(SRCDIR)/json_tag.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/json_tag.c >$(OBJDIR)/json_tag_.c $(OBJDIR)/json_tag.o: $(OBJDIR)/json_tag_.c $(OBJDIR)/json_tag.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_tag.o -c $(OBJDIR)/json_tag_.c json_tag.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_user_.c: $(SRCDIR)/json_user.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/json_user.c >$(OBJDIR)/json_user_.c $(OBJDIR)/json_user.o: $(OBJDIR)/json_user_.c $(OBJDIR)/json_user.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_user.o -c $(OBJDIR)/json_user_.c json_user.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 leaf.h: $(OBJDIR)/headers |
| ︙ | ︙ | |||
958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 | $(OBJDIR)/xfer_.c: $(SRCDIR)/xfer.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/xfer.c >$(OBJDIR)/xfer_.c $(OBJDIR)/xfer.o: $(OBJDIR)/xfer_.c $(OBJDIR)/xfer.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/xfer.o -c $(OBJDIR)/xfer_.c xfer.h: $(OBJDIR)/headers $(OBJDIR)/zip_.c: $(SRCDIR)/zip.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/zip.c >$(OBJDIR)/zip_.c $(OBJDIR)/zip.o: $(OBJDIR)/zip_.c $(OBJDIR)/zip.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/zip.o -c $(OBJDIR)/zip_.c 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)/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 | > > > > > > > > > > > > > > > > > | 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 | $(OBJDIR)/xfer_.c: $(SRCDIR)/xfer.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/xfer.c >$(OBJDIR)/xfer_.c $(OBJDIR)/xfer.o: $(OBJDIR)/xfer_.c $(OBJDIR)/xfer.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/xfer.o -c $(OBJDIR)/xfer_.c xfer.h: $(OBJDIR)/headers $(OBJDIR)/xfersetup_.c: $(SRCDIR)/xfersetup.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/xfersetup.c >$(OBJDIR)/xfersetup_.c $(OBJDIR)/xfersetup.o: $(OBJDIR)/xfersetup_.c $(OBJDIR)/xfersetup.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/xfersetup.o -c $(OBJDIR)/xfersetup_.c xfersetup.h: $(OBJDIR)/headers $(OBJDIR)/zip_.c: $(SRCDIR)/zip.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/zip.c >$(OBJDIR)/zip_.c $(OBJDIR)/zip.o: $(OBJDIR)/zip_.c $(OBJDIR)/zip.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/zip.o -c $(OBJDIR)/zip_.c 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 -DCSON_FOSSIL_MODE $(OBJDIR)/json.o $(OBJDIR)/json_artifact.o $(OBJDIR)/json_branch.o $(OBJDIR)/json_diff.o $(OBJDIR)/json_login.o $(OBJDIR)/json_query.o $(OBJDIR)/json_report.o $(OBJDIR)/json_tag.o $(OBJDIR)/json_timeline.o $(OBJDIR)/json_user.o $(OBJDIR)/json_wiki.o : $(SRCDIR)/json_detail.h $(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 ifdef FOSSIL_ENABLE_TCL $(OBJDIR)/th_tcl.o: $(SRCDIR)/th_tcl.c $(XTCC) -I$(SRCDIR) -c $(SRCDIR)/th_tcl.c -o $(OBJDIR)/th_tcl.o endif |
Added win/Makefile.mingw.mistachkin.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 | #!/usr/bin/make # ############################################################################## # WARNING: DO NOT EDIT, AUTOMATICALLY GENERATED FILE (SEE "src/makemake.tcl") ############################################################################## # # This file is automatically generated. Instead of editing this # file, edit "makemake.tcl" then run "tclsh makemake.tcl" # to regenerate this file. # # This is a makefile for us on windows using MinGW. # #### The toplevel directory of the source tree. Fossil can be built # in a directory that is separate from the source tree. Just change # the following to point from the build directory to the src/ folder. # SRCDIR = src #### The directory into which object code files should be written. # OBJDIR = wbld #### C Compiler and options for use in building executables that # will run on the platform that is doing the build. This is used # to compile code-generator programs as part of the build process. # See TCC below for the C compiler for building the finished binary. # BCC = gcc #### Enable HTTPS support via OpenSSL (links to libssl and libcrypto) # FOSSIL_ENABLE_SSL = 1 #### Enable scripting support via Tcl/Tk # FOSSIL_ENABLE_TCL = 1 #### Use the Tcl source directory instead of the install directory? # This is useful when Tcl has been compiled statically with MinGW. # FOSSIL_TCL_SOURCE = 1 #### The directories where the zlib include and library files are located. # The recommended usage here is to use the Sysinternals junction tool # to create a hard link between an "zlib-1.x.y" sub-directory of the # Fossil source code directory and the target zlib source directory. # ZINCDIR = $(SRCDIR)/../zlib-1.2.5 ZLIBDIR = $(SRCDIR)/../zlib-1.2.5 #### The directories where the OpenSSL include and library files are located. # The recommended usage here is to use the Sysinternals junction tool # to create a hard link between an "openssl-1.x" sub-directory of the # Fossil source code directory and the target OpenSSL source directory. # OPENSSLINCDIR = $(SRCDIR)/../openssl-1.0.0e/include OPENSSLLIBDIR = $(SRCDIR)/../openssl-1.0.0e #### Either the directory where the Tcl library is installed or the Tcl # source code directory resides (depending on the value of the macro # FOSSIL_TCL_SOURCE). If this points to the Tcl install directory, # this directory must have "include" and "lib" sub-directories. If # this points to the Tcl source code directory, this directory must # have "generic" and "win" sub-directories. The recommended usage # here is to use the Sysinternals junction tool to create a hard # link between a "tcl-8.x" sub-directory of the Fossil source code # directory and the target Tcl directory. This removes the need to # hard-code the necessary paths in this Makefile. # TCLDIR = $(SRCDIR)/../tcl-8.6 #### The Tcl source code directory. This defaults to the same value as # TCLDIR macro (above), which may not be correct. This value will # only be used if the FOSSIL_TCL_SOURCE macro is defined. # TCLSRCDIR = $(TCLDIR) #### The Tcl include and library directories. These values will only be # used if the FOSSIL_TCL_SOURCE macro is not defined. # TCLINCDIR = $(TCLDIR)/include TCLLIBDIR = $(TCLDIR)/lib #### Tcl: Which Tcl library do we want to use (8.4, 8.5, 8.6, etc)? # LIBTCL = -ltcl86 #### C Compile and options for use in building executables that # will run on the target platform. This is usually the same # as BCC, unless you are cross-compiling. This C compiler builds # the finished binary for fossil. The BCC compiler above is used # for building intermediate code-generator tools. # TCC = gcc -Os -Wall -L$(ZLIBDIR) -I$(ZINCDIR) # With HTTPS support ifdef FOSSIL_ENABLE_SSL TCC += -L$(OPENSSLLIBDIR) -I$(OPENSSLINCDIR) endif # With Tcl support ifdef FOSSIL_ENABLE_TCL ifdef FOSSIL_TCL_SOURCE TCC += -L$(TCLSRCDIR)/win -I$(TCLSRCDIR)/generic -I$(TCLSRCDIR)/win else TCC += -L$(TCLLIBDIR) -I$(TCLINCDIR) endif endif # With HTTPS support ifdef FOSSIL_ENABLE_SSL TCC += -DFOSSIL_ENABLE_SSL=1 endif # With Tcl support (statically linked) ifdef FOSSIL_ENABLE_TCL TCC += -DFOSSIL_ENABLE_TCL=1 -DSTATIC_BUILD endif #### Extra arguments for linking the finished binary. Fossil needs # to link against the Z-Lib compression library. There are no # other mandatory dependencies. We add the -static option here # so that we can build a static executable that will run in a # chroot jail. # LIB = -static LIB += -lmingwex -lz # OpenSSL: Add the necessary libaries required, if enabled. ifdef FOSSIL_ENABLE_SSL LIB += -lssl -lcrypto -lgdi32 endif # Tcl: Add the necessary libaries required, if enabled. ifdef FOSSIL_ENABLE_TCL LIB += $(LIBTCL) endif #### These libraries MUST appear in the same order as they do for Tcl # or linking with it will not work (exact reason unknown). # ifdef FOSSIL_ENABLE_TCL LIB += -lnetapi32 -lkernel32 -luser32 -ladvapi32 -lws2_32 else LIB += -lws2_32 endif #### Tcl shell for use in running the fossil test suite. This is only # used for testing. # TCLSH = tclsh #### Nullsoft installer MakeNSIS location # MAKENSIS = "$(ProgramFiles)\NSIS\MakeNSIS.exe" #### Include a configuration file that can override any one of these settings. # -include config.w32 # STOP HERE # You should not need to change anything below this line #-------------------------------------------------------- XTCC = $(TCC) $(CFLAGS) -I. -I$(SRCDIR) SRC = \ $(SRCDIR)/add.c \ $(SRCDIR)/allrepo.c \ $(SRCDIR)/attach.c \ $(SRCDIR)/bag.c \ $(SRCDIR)/bisect.c \ $(SRCDIR)/blob.c \ $(SRCDIR)/branch.c \ $(SRCDIR)/browse.c \ $(SRCDIR)/captcha.c \ $(SRCDIR)/cgi.c \ $(SRCDIR)/checkin.c \ $(SRCDIR)/checkout.c \ $(SRCDIR)/clearsign.c \ $(SRCDIR)/clone.c \ $(SRCDIR)/comformat.c \ $(SRCDIR)/configure.c \ $(SRCDIR)/content.c \ $(SRCDIR)/db.c \ $(SRCDIR)/delta.c \ $(SRCDIR)/deltacmd.c \ $(SRCDIR)/descendants.c \ $(SRCDIR)/diff.c \ $(SRCDIR)/diffcmd.c \ $(SRCDIR)/doc.c \ $(SRCDIR)/encode.c \ $(SRCDIR)/event.c \ $(SRCDIR)/export.c \ $(SRCDIR)/file.c \ $(SRCDIR)/finfo.c \ $(SRCDIR)/glob.c \ $(SRCDIR)/graph.c \ $(SRCDIR)/gzip.c \ $(SRCDIR)/http.c \ $(SRCDIR)/http_socket.c \ $(SRCDIR)/http_ssl.c \ $(SRCDIR)/http_transport.c \ $(SRCDIR)/import.c \ $(SRCDIR)/info.c \ $(SRCDIR)/json.c \ $(SRCDIR)/json_artifact.c \ $(SRCDIR)/json_branch.c \ $(SRCDIR)/json_diff.c \ $(SRCDIR)/json_login.c \ $(SRCDIR)/json_query.c \ $(SRCDIR)/json_report.c \ $(SRCDIR)/json_tag.c \ $(SRCDIR)/json_timeline.c \ $(SRCDIR)/json_user.c \ $(SRCDIR)/json_wiki.c \ $(SRCDIR)/leaf.c \ $(SRCDIR)/login.c \ $(SRCDIR)/main.c \ $(SRCDIR)/manifest.c \ $(SRCDIR)/md5.c \ $(SRCDIR)/merge.c \ $(SRCDIR)/merge3.c \ $(SRCDIR)/name.c \ $(SRCDIR)/path.c \ $(SRCDIR)/pivot.c \ $(SRCDIR)/popen.c \ $(SRCDIR)/pqueue.c \ $(SRCDIR)/printf.c \ $(SRCDIR)/rebuild.c \ $(SRCDIR)/report.c \ $(SRCDIR)/rss.c \ $(SRCDIR)/schema.c \ $(SRCDIR)/search.c \ $(SRCDIR)/setup.c \ $(SRCDIR)/sha1.c \ $(SRCDIR)/shun.c \ $(SRCDIR)/skins.c \ $(SRCDIR)/sqlcmd.c \ $(SRCDIR)/stash.c \ $(SRCDIR)/stat.c \ $(SRCDIR)/style.c \ $(SRCDIR)/sync.c \ $(SRCDIR)/tag.c \ $(SRCDIR)/tar.c \ $(SRCDIR)/th_main.c \ $(SRCDIR)/timeline.c \ $(SRCDIR)/tkt.c \ $(SRCDIR)/tktsetup.c \ $(SRCDIR)/undo.c \ $(SRCDIR)/update.c \ $(SRCDIR)/url.c \ $(SRCDIR)/user.c \ $(SRCDIR)/verify.c \ $(SRCDIR)/vfile.c \ $(SRCDIR)/wiki.c \ $(SRCDIR)/wikiformat.c \ $(SRCDIR)/winhttp.c \ $(SRCDIR)/xfer.c \ $(SRCDIR)/xfersetup.c \ $(SRCDIR)/zip.c TRANS_SRC = \ $(OBJDIR)/add_.c \ $(OBJDIR)/allrepo_.c \ $(OBJDIR)/attach_.c \ $(OBJDIR)/bag_.c \ $(OBJDIR)/bisect_.c \ $(OBJDIR)/blob_.c \ $(OBJDIR)/branch_.c \ $(OBJDIR)/browse_.c \ $(OBJDIR)/captcha_.c \ $(OBJDIR)/cgi_.c \ $(OBJDIR)/checkin_.c \ $(OBJDIR)/checkout_.c \ $(OBJDIR)/clearsign_.c \ $(OBJDIR)/clone_.c \ $(OBJDIR)/comformat_.c \ $(OBJDIR)/configure_.c \ $(OBJDIR)/content_.c \ $(OBJDIR)/db_.c \ $(OBJDIR)/delta_.c \ $(OBJDIR)/deltacmd_.c \ $(OBJDIR)/descendants_.c \ $(OBJDIR)/diff_.c \ $(OBJDIR)/diffcmd_.c \ $(OBJDIR)/doc_.c \ $(OBJDIR)/encode_.c \ $(OBJDIR)/event_.c \ $(OBJDIR)/export_.c \ $(OBJDIR)/file_.c \ $(OBJDIR)/finfo_.c \ $(OBJDIR)/glob_.c \ $(OBJDIR)/graph_.c \ $(OBJDIR)/gzip_.c \ $(OBJDIR)/http_.c \ $(OBJDIR)/http_socket_.c \ $(OBJDIR)/http_ssl_.c \ $(OBJDIR)/http_transport_.c \ $(OBJDIR)/import_.c \ $(OBJDIR)/info_.c \ $(OBJDIR)/json_.c \ $(OBJDIR)/json_artifact_.c \ $(OBJDIR)/json_branch_.c \ $(OBJDIR)/json_diff_.c \ $(OBJDIR)/json_login_.c \ $(OBJDIR)/json_query_.c \ $(OBJDIR)/json_report_.c \ $(OBJDIR)/json_tag_.c \ $(OBJDIR)/json_timeline_.c \ $(OBJDIR)/json_user_.c \ $(OBJDIR)/json_wiki_.c \ $(OBJDIR)/leaf_.c \ $(OBJDIR)/login_.c \ $(OBJDIR)/main_.c \ $(OBJDIR)/manifest_.c \ $(OBJDIR)/md5_.c \ $(OBJDIR)/merge_.c \ $(OBJDIR)/merge3_.c \ $(OBJDIR)/name_.c \ $(OBJDIR)/path_.c \ $(OBJDIR)/pivot_.c \ $(OBJDIR)/popen_.c \ $(OBJDIR)/pqueue_.c \ $(OBJDIR)/printf_.c \ $(OBJDIR)/rebuild_.c \ $(OBJDIR)/report_.c \ $(OBJDIR)/rss_.c \ $(OBJDIR)/schema_.c \ $(OBJDIR)/search_.c \ $(OBJDIR)/setup_.c \ $(OBJDIR)/sha1_.c \ $(OBJDIR)/shun_.c \ $(OBJDIR)/skins_.c \ $(OBJDIR)/sqlcmd_.c \ $(OBJDIR)/stash_.c \ $(OBJDIR)/stat_.c \ $(OBJDIR)/style_.c \ $(OBJDIR)/sync_.c \ $(OBJDIR)/tag_.c \ $(OBJDIR)/tar_.c \ $(OBJDIR)/th_main_.c \ $(OBJDIR)/timeline_.c \ $(OBJDIR)/tkt_.c \ $(OBJDIR)/tktsetup_.c \ $(OBJDIR)/undo_.c \ $(OBJDIR)/update_.c \ $(OBJDIR)/url_.c \ $(OBJDIR)/user_.c \ $(OBJDIR)/verify_.c \ $(OBJDIR)/vfile_.c \ $(OBJDIR)/wiki_.c \ $(OBJDIR)/wikiformat_.c \ $(OBJDIR)/winhttp_.c \ $(OBJDIR)/xfer_.c \ $(OBJDIR)/xfersetup_.c \ $(OBJDIR)/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_artifact.o \ $(OBJDIR)/json_branch.o \ $(OBJDIR)/json_diff.o \ $(OBJDIR)/json_login.o \ $(OBJDIR)/json_query.o \ $(OBJDIR)/json_report.o \ $(OBJDIR)/json_tag.o \ $(OBJDIR)/json_timeline.o \ $(OBJDIR)/json_user.o \ $(OBJDIR)/json_wiki.o \ $(OBJDIR)/leaf.o \ $(OBJDIR)/login.o \ $(OBJDIR)/main.o \ $(OBJDIR)/manifest.o \ $(OBJDIR)/md5.o \ $(OBJDIR)/merge.o \ $(OBJDIR)/merge3.o \ $(OBJDIR)/name.o \ $(OBJDIR)/path.o \ $(OBJDIR)/pivot.o \ $(OBJDIR)/popen.o \ $(OBJDIR)/pqueue.o \ $(OBJDIR)/printf.o \ $(OBJDIR)/rebuild.o \ $(OBJDIR)/report.o \ $(OBJDIR)/rss.o \ $(OBJDIR)/schema.o \ $(OBJDIR)/search.o \ $(OBJDIR)/setup.o \ $(OBJDIR)/sha1.o \ $(OBJDIR)/shun.o \ $(OBJDIR)/skins.o \ $(OBJDIR)/sqlcmd.o \ $(OBJDIR)/stash.o \ $(OBJDIR)/stat.o \ $(OBJDIR)/style.o \ $(OBJDIR)/sync.o \ $(OBJDIR)/tag.o \ $(OBJDIR)/tar.o \ $(OBJDIR)/th_main.o \ $(OBJDIR)/timeline.o \ $(OBJDIR)/tkt.o \ $(OBJDIR)/tktsetup.o \ $(OBJDIR)/undo.o \ $(OBJDIR)/update.o \ $(OBJDIR)/url.o \ $(OBJDIR)/user.o \ $(OBJDIR)/verify.o \ $(OBJDIR)/vfile.o \ $(OBJDIR)/wiki.o \ $(OBJDIR)/wikiformat.o \ $(OBJDIR)/winhttp.o \ $(OBJDIR)/xfer.o \ $(OBJDIR)/xfersetup.o \ $(OBJDIR)/zip.o APPNAME = fossil.exe TRANSLATE = $(subst /,\\,$(OBJDIR)/translate.exe) MAKEHEADERS = $(subst /,\\,$(OBJDIR)/makeheaders.exe) MKINDEX = $(subst /,\\,$(OBJDIR)/mkindex.exe) VERSION = $(subst /,\\,$(OBJDIR)/version.exe) all: $(OBJDIR) $(APPNAME) $(OBJDIR)/icon.o: $(SRCDIR)/../win/icon.rc cp $(SRCDIR)/../win/icon.rc $(OBJDIR) windres $(OBJDIR)/icon.rc -o $(OBJDIR)/icon.o install: $(APPNAME) mv $(APPNAME) $(INSTALLDIR) $(OBJDIR): mkdir $(OBJDIR) $(OBJDIR)/translate: $(SRCDIR)/translate.c $(BCC) -o $(OBJDIR)/translate $(SRCDIR)/translate.c $(OBJDIR)/makeheaders: $(SRCDIR)/makeheaders.c $(BCC) -o $(OBJDIR)/makeheaders $(SRCDIR)/makeheaders.c $(OBJDIR)/mkindex: $(SRCDIR)/mkindex.c $(BCC) -o $(OBJDIR)/mkindex $(SRCDIR)/mkindex.c $(VERSION): $(SRCDIR)/mkversion.c $(BCC) -o $(OBJDIR)/version $(SRCDIR)/mkversion.c # WARNING. DANGER. Running the test suite modifies the repository the # build is done from, i.e. the checkout belongs to. Do not sync/push # the repository after running the tests. test: $(OBJDIR) $(APPNAME) $(TCLSH) $(SRCDIR)/../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 ifdef FOSSIL_ENABLE_TCL EXTRAOBJ += $(OBJDIR)/th_tcl.o endif $(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 # an executable named "manifest" out of the file named "manifest.c" # $(SRCDIR)/../manifest: # noop # Requires MSYS to be installed in addition to the MinGW, for the "rm" # command. "del" will not work here because it is not a separate command # but a MSDOS-shell builtin. # clean: rm -rf $(OBJDIR) $(APPNAME) setup: $(OBJDIR) $(APPNAME) $(MAKENSIS) ./fossil.nsi $(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_artifact_.c:$(OBJDIR)/json_artifact.h $(OBJDIR)/json_branch_.c:$(OBJDIR)/json_branch.h $(OBJDIR)/json_diff_.c:$(OBJDIR)/json_diff.h $(OBJDIR)/json_login_.c:$(OBJDIR)/json_login.h $(OBJDIR)/json_query_.c:$(OBJDIR)/json_query.h $(OBJDIR)/json_report_.c:$(OBJDIR)/json_report.h $(OBJDIR)/json_tag_.c:$(OBJDIR)/json_tag.h $(OBJDIR)/json_timeline_.c:$(OBJDIR)/json_timeline.h $(OBJDIR)/json_user_.c:$(OBJDIR)/json_user.h $(OBJDIR)/json_wiki_.c:$(OBJDIR)/json_wiki.h $(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h $(OBJDIR)/login_.c:$(OBJDIR)/login.h $(OBJDIR)/main_.c:$(OBJDIR)/main.h $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h $(OBJDIR)/name_.c:$(OBJDIR)/name.h $(OBJDIR)/path_.c:$(OBJDIR)/path.h $(OBJDIR)/pivot_.c:$(OBJDIR)/pivot.h $(OBJDIR)/popen_.c:$(OBJDIR)/popen.h $(OBJDIR)/pqueue_.c:$(OBJDIR)/pqueue.h $(OBJDIR)/printf_.c:$(OBJDIR)/printf.h $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h $(OBJDIR)/report_.c:$(OBJDIR)/report.h $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h $(OBJDIR)/search_.c:$(OBJDIR)/search.h $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h $(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h $(OBJDIR)/style_.c:$(OBJDIR)/style.h $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h $(OBJDIR)/update_.c:$(OBJDIR)/update.h $(OBJDIR)/url_.c:$(OBJDIR)/url.h $(OBJDIR)/user_.c:$(OBJDIR)/user.h $(OBJDIR)/verify_.c:$(OBJDIR)/verify.h $(OBJDIR)/vfile_.c:$(OBJDIR)/vfile.h $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h $(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h $(OBJDIR)/xfersetup_.c:$(OBJDIR)/xfersetup.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 $(TRANSLATE) $(SRCDIR)/add.c >$(OBJDIR)/add_.c $(OBJDIR)/add.o: $(OBJDIR)/add_.c $(OBJDIR)/add.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/add.o -c $(OBJDIR)/add_.c add.h: $(OBJDIR)/headers $(OBJDIR)/allrepo_.c: $(SRCDIR)/allrepo.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/allrepo.c >$(OBJDIR)/allrepo_.c $(OBJDIR)/allrepo.o: $(OBJDIR)/allrepo_.c $(OBJDIR)/allrepo.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/allrepo.o -c $(OBJDIR)/allrepo_.c allrepo.h: $(OBJDIR)/headers $(OBJDIR)/attach_.c: $(SRCDIR)/attach.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/attach.c >$(OBJDIR)/attach_.c $(OBJDIR)/attach.o: $(OBJDIR)/attach_.c $(OBJDIR)/attach.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/attach.o -c $(OBJDIR)/attach_.c attach.h: $(OBJDIR)/headers $(OBJDIR)/bag_.c: $(SRCDIR)/bag.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/bag.c >$(OBJDIR)/bag_.c $(OBJDIR)/bag.o: $(OBJDIR)/bag_.c $(OBJDIR)/bag.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/bag.o -c $(OBJDIR)/bag_.c bag.h: $(OBJDIR)/headers $(OBJDIR)/bisect_.c: $(SRCDIR)/bisect.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/bisect.c >$(OBJDIR)/bisect_.c $(OBJDIR)/bisect.o: $(OBJDIR)/bisect_.c $(OBJDIR)/bisect.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/bisect.o -c $(OBJDIR)/bisect_.c bisect.h: $(OBJDIR)/headers $(OBJDIR)/blob_.c: $(SRCDIR)/blob.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/blob.c >$(OBJDIR)/blob_.c $(OBJDIR)/blob.o: $(OBJDIR)/blob_.c $(OBJDIR)/blob.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/blob.o -c $(OBJDIR)/blob_.c blob.h: $(OBJDIR)/headers $(OBJDIR)/branch_.c: $(SRCDIR)/branch.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/branch.c >$(OBJDIR)/branch_.c $(OBJDIR)/branch.o: $(OBJDIR)/branch_.c $(OBJDIR)/branch.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/branch.o -c $(OBJDIR)/branch_.c branch.h: $(OBJDIR)/headers $(OBJDIR)/browse_.c: $(SRCDIR)/browse.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/browse.c >$(OBJDIR)/browse_.c $(OBJDIR)/browse.o: $(OBJDIR)/browse_.c $(OBJDIR)/browse.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/browse.o -c $(OBJDIR)/browse_.c browse.h: $(OBJDIR)/headers $(OBJDIR)/captcha_.c: $(SRCDIR)/captcha.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/captcha.c >$(OBJDIR)/captcha_.c $(OBJDIR)/captcha.o: $(OBJDIR)/captcha_.c $(OBJDIR)/captcha.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/captcha.o -c $(OBJDIR)/captcha_.c captcha.h: $(OBJDIR)/headers $(OBJDIR)/cgi_.c: $(SRCDIR)/cgi.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/cgi.c >$(OBJDIR)/cgi_.c $(OBJDIR)/cgi.o: $(OBJDIR)/cgi_.c $(OBJDIR)/cgi.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/cgi.o -c $(OBJDIR)/cgi_.c cgi.h: $(OBJDIR)/headers $(OBJDIR)/checkin_.c: $(SRCDIR)/checkin.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/checkin.c >$(OBJDIR)/checkin_.c $(OBJDIR)/checkin.o: $(OBJDIR)/checkin_.c $(OBJDIR)/checkin.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/checkin.o -c $(OBJDIR)/checkin_.c checkin.h: $(OBJDIR)/headers $(OBJDIR)/checkout_.c: $(SRCDIR)/checkout.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/checkout.c >$(OBJDIR)/checkout_.c $(OBJDIR)/checkout.o: $(OBJDIR)/checkout_.c $(OBJDIR)/checkout.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/checkout.o -c $(OBJDIR)/checkout_.c checkout.h: $(OBJDIR)/headers $(OBJDIR)/clearsign_.c: $(SRCDIR)/clearsign.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/clearsign.c >$(OBJDIR)/clearsign_.c $(OBJDIR)/clearsign.o: $(OBJDIR)/clearsign_.c $(OBJDIR)/clearsign.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/clearsign.o -c $(OBJDIR)/clearsign_.c clearsign.h: $(OBJDIR)/headers $(OBJDIR)/clone_.c: $(SRCDIR)/clone.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/clone.c >$(OBJDIR)/clone_.c $(OBJDIR)/clone.o: $(OBJDIR)/clone_.c $(OBJDIR)/clone.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/clone.o -c $(OBJDIR)/clone_.c clone.h: $(OBJDIR)/headers $(OBJDIR)/comformat_.c: $(SRCDIR)/comformat.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/comformat.c >$(OBJDIR)/comformat_.c $(OBJDIR)/comformat.o: $(OBJDIR)/comformat_.c $(OBJDIR)/comformat.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/comformat.o -c $(OBJDIR)/comformat_.c comformat.h: $(OBJDIR)/headers $(OBJDIR)/configure_.c: $(SRCDIR)/configure.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/configure.c >$(OBJDIR)/configure_.c $(OBJDIR)/configure.o: $(OBJDIR)/configure_.c $(OBJDIR)/configure.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/configure.o -c $(OBJDIR)/configure_.c configure.h: $(OBJDIR)/headers $(OBJDIR)/content_.c: $(SRCDIR)/content.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/content.c >$(OBJDIR)/content_.c $(OBJDIR)/content.o: $(OBJDIR)/content_.c $(OBJDIR)/content.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/content.o -c $(OBJDIR)/content_.c content.h: $(OBJDIR)/headers $(OBJDIR)/db_.c: $(SRCDIR)/db.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/db.c >$(OBJDIR)/db_.c $(OBJDIR)/db.o: $(OBJDIR)/db_.c $(OBJDIR)/db.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/db.o -c $(OBJDIR)/db_.c db.h: $(OBJDIR)/headers $(OBJDIR)/delta_.c: $(SRCDIR)/delta.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/delta.c >$(OBJDIR)/delta_.c $(OBJDIR)/delta.o: $(OBJDIR)/delta_.c $(OBJDIR)/delta.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/delta.o -c $(OBJDIR)/delta_.c delta.h: $(OBJDIR)/headers $(OBJDIR)/deltacmd_.c: $(SRCDIR)/deltacmd.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/deltacmd.c >$(OBJDIR)/deltacmd_.c $(OBJDIR)/deltacmd.o: $(OBJDIR)/deltacmd_.c $(OBJDIR)/deltacmd.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/deltacmd.o -c $(OBJDIR)/deltacmd_.c deltacmd.h: $(OBJDIR)/headers $(OBJDIR)/descendants_.c: $(SRCDIR)/descendants.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/descendants.c >$(OBJDIR)/descendants_.c $(OBJDIR)/descendants.o: $(OBJDIR)/descendants_.c $(OBJDIR)/descendants.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/descendants.o -c $(OBJDIR)/descendants_.c descendants.h: $(OBJDIR)/headers $(OBJDIR)/diff_.c: $(SRCDIR)/diff.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/diff.c >$(OBJDIR)/diff_.c $(OBJDIR)/diff.o: $(OBJDIR)/diff_.c $(OBJDIR)/diff.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/diff.o -c $(OBJDIR)/diff_.c diff.h: $(OBJDIR)/headers $(OBJDIR)/diffcmd_.c: $(SRCDIR)/diffcmd.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/diffcmd.c >$(OBJDIR)/diffcmd_.c $(OBJDIR)/diffcmd.o: $(OBJDIR)/diffcmd_.c $(OBJDIR)/diffcmd.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/diffcmd.o -c $(OBJDIR)/diffcmd_.c diffcmd.h: $(OBJDIR)/headers $(OBJDIR)/doc_.c: $(SRCDIR)/doc.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/doc.c >$(OBJDIR)/doc_.c $(OBJDIR)/doc.o: $(OBJDIR)/doc_.c $(OBJDIR)/doc.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/doc.o -c $(OBJDIR)/doc_.c doc.h: $(OBJDIR)/headers $(OBJDIR)/encode_.c: $(SRCDIR)/encode.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/encode.c >$(OBJDIR)/encode_.c $(OBJDIR)/encode.o: $(OBJDIR)/encode_.c $(OBJDIR)/encode.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/encode.o -c $(OBJDIR)/encode_.c encode.h: $(OBJDIR)/headers $(OBJDIR)/event_.c: $(SRCDIR)/event.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/event.c >$(OBJDIR)/event_.c $(OBJDIR)/event.o: $(OBJDIR)/event_.c $(OBJDIR)/event.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/event.o -c $(OBJDIR)/event_.c event.h: $(OBJDIR)/headers $(OBJDIR)/export_.c: $(SRCDIR)/export.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/export.c >$(OBJDIR)/export_.c $(OBJDIR)/export.o: $(OBJDIR)/export_.c $(OBJDIR)/export.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/export.o -c $(OBJDIR)/export_.c export.h: $(OBJDIR)/headers $(OBJDIR)/file_.c: $(SRCDIR)/file.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/file.c >$(OBJDIR)/file_.c $(OBJDIR)/file.o: $(OBJDIR)/file_.c $(OBJDIR)/file.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/file.o -c $(OBJDIR)/file_.c file.h: $(OBJDIR)/headers $(OBJDIR)/finfo_.c: $(SRCDIR)/finfo.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/finfo.c >$(OBJDIR)/finfo_.c $(OBJDIR)/finfo.o: $(OBJDIR)/finfo_.c $(OBJDIR)/finfo.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/finfo.o -c $(OBJDIR)/finfo_.c finfo.h: $(OBJDIR)/headers $(OBJDIR)/glob_.c: $(SRCDIR)/glob.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/glob.c >$(OBJDIR)/glob_.c $(OBJDIR)/glob.o: $(OBJDIR)/glob_.c $(OBJDIR)/glob.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/glob.o -c $(OBJDIR)/glob_.c glob.h: $(OBJDIR)/headers $(OBJDIR)/graph_.c: $(SRCDIR)/graph.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/graph.c >$(OBJDIR)/graph_.c $(OBJDIR)/graph.o: $(OBJDIR)/graph_.c $(OBJDIR)/graph.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/graph.o -c $(OBJDIR)/graph_.c graph.h: $(OBJDIR)/headers $(OBJDIR)/gzip_.c: $(SRCDIR)/gzip.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/gzip.c >$(OBJDIR)/gzip_.c $(OBJDIR)/gzip.o: $(OBJDIR)/gzip_.c $(OBJDIR)/gzip.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/gzip.o -c $(OBJDIR)/gzip_.c gzip.h: $(OBJDIR)/headers $(OBJDIR)/http_.c: $(SRCDIR)/http.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/http.c >$(OBJDIR)/http_.c $(OBJDIR)/http.o: $(OBJDIR)/http_.c $(OBJDIR)/http.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/http.o -c $(OBJDIR)/http_.c http.h: $(OBJDIR)/headers $(OBJDIR)/http_socket_.c: $(SRCDIR)/http_socket.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/http_socket.c >$(OBJDIR)/http_socket_.c $(OBJDIR)/http_socket.o: $(OBJDIR)/http_socket_.c $(OBJDIR)/http_socket.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/http_socket.o -c $(OBJDIR)/http_socket_.c http_socket.h: $(OBJDIR)/headers $(OBJDIR)/http_ssl_.c: $(SRCDIR)/http_ssl.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/http_ssl.c >$(OBJDIR)/http_ssl_.c $(OBJDIR)/http_ssl.o: $(OBJDIR)/http_ssl_.c $(OBJDIR)/http_ssl.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/http_ssl.o -c $(OBJDIR)/http_ssl_.c http_ssl.h: $(OBJDIR)/headers $(OBJDIR)/http_transport_.c: $(SRCDIR)/http_transport.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/http_transport.c >$(OBJDIR)/http_transport_.c $(OBJDIR)/http_transport.o: $(OBJDIR)/http_transport_.c $(OBJDIR)/http_transport.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/http_transport.o -c $(OBJDIR)/http_transport_.c http_transport.h: $(OBJDIR)/headers $(OBJDIR)/import_.c: $(SRCDIR)/import.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/import.c >$(OBJDIR)/import_.c $(OBJDIR)/import.o: $(OBJDIR)/import_.c $(OBJDIR)/import.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/import.o -c $(OBJDIR)/import_.c import.h: $(OBJDIR)/headers $(OBJDIR)/info_.c: $(SRCDIR)/info.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/info.c >$(OBJDIR)/info_.c $(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_artifact_.c: $(SRCDIR)/json_artifact.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/json_artifact.c >$(OBJDIR)/json_artifact_.c $(OBJDIR)/json_artifact.o: $(OBJDIR)/json_artifact_.c $(OBJDIR)/json_artifact.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_artifact.o -c $(OBJDIR)/json_artifact_.c json_artifact.h: $(OBJDIR)/headers $(OBJDIR)/json_branch_.c: $(SRCDIR)/json_branch.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/json_branch.c >$(OBJDIR)/json_branch_.c $(OBJDIR)/json_branch.o: $(OBJDIR)/json_branch_.c $(OBJDIR)/json_branch.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_branch.o -c $(OBJDIR)/json_branch_.c json_branch.h: $(OBJDIR)/headers $(OBJDIR)/json_diff_.c: $(SRCDIR)/json_diff.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/json_diff.c >$(OBJDIR)/json_diff_.c $(OBJDIR)/json_diff.o: $(OBJDIR)/json_diff_.c $(OBJDIR)/json_diff.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_diff.o -c $(OBJDIR)/json_diff_.c json_diff.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_query_.c: $(SRCDIR)/json_query.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/json_query.c >$(OBJDIR)/json_query_.c $(OBJDIR)/json_query.o: $(OBJDIR)/json_query_.c $(OBJDIR)/json_query.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_query.o -c $(OBJDIR)/json_query_.c json_query.h: $(OBJDIR)/headers $(OBJDIR)/json_report_.c: $(SRCDIR)/json_report.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/json_report.c >$(OBJDIR)/json_report_.c $(OBJDIR)/json_report.o: $(OBJDIR)/json_report_.c $(OBJDIR)/json_report.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_report.o -c $(OBJDIR)/json_report_.c json_report.h: $(OBJDIR)/headers $(OBJDIR)/json_tag_.c: $(SRCDIR)/json_tag.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/json_tag.c >$(OBJDIR)/json_tag_.c $(OBJDIR)/json_tag.o: $(OBJDIR)/json_tag_.c $(OBJDIR)/json_tag.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_tag.o -c $(OBJDIR)/json_tag_.c json_tag.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_user_.c: $(SRCDIR)/json_user.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/json_user.c >$(OBJDIR)/json_user_.c $(OBJDIR)/json_user.o: $(OBJDIR)/json_user_.c $(OBJDIR)/json_user.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/json_user.o -c $(OBJDIR)/json_user_.c json_user.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 leaf.h: $(OBJDIR)/headers $(OBJDIR)/login_.c: $(SRCDIR)/login.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/login.c >$(OBJDIR)/login_.c $(OBJDIR)/login.o: $(OBJDIR)/login_.c $(OBJDIR)/login.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/login.o -c $(OBJDIR)/login_.c login.h: $(OBJDIR)/headers $(OBJDIR)/main_.c: $(SRCDIR)/main.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/main.c >$(OBJDIR)/main_.c $(OBJDIR)/main.o: $(OBJDIR)/main_.c $(OBJDIR)/main.h $(OBJDIR)/page_index.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/main.o -c $(OBJDIR)/main_.c main.h: $(OBJDIR)/headers $(OBJDIR)/manifest_.c: $(SRCDIR)/manifest.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/manifest.c >$(OBJDIR)/manifest_.c $(OBJDIR)/manifest.o: $(OBJDIR)/manifest_.c $(OBJDIR)/manifest.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/manifest.o -c $(OBJDIR)/manifest_.c manifest.h: $(OBJDIR)/headers $(OBJDIR)/md5_.c: $(SRCDIR)/md5.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/md5.c >$(OBJDIR)/md5_.c $(OBJDIR)/md5.o: $(OBJDIR)/md5_.c $(OBJDIR)/md5.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/md5.o -c $(OBJDIR)/md5_.c md5.h: $(OBJDIR)/headers $(OBJDIR)/merge_.c: $(SRCDIR)/merge.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/merge.c >$(OBJDIR)/merge_.c $(OBJDIR)/merge.o: $(OBJDIR)/merge_.c $(OBJDIR)/merge.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/merge.o -c $(OBJDIR)/merge_.c merge.h: $(OBJDIR)/headers $(OBJDIR)/merge3_.c: $(SRCDIR)/merge3.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/merge3.c >$(OBJDIR)/merge3_.c $(OBJDIR)/merge3.o: $(OBJDIR)/merge3_.c $(OBJDIR)/merge3.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/merge3.o -c $(OBJDIR)/merge3_.c merge3.h: $(OBJDIR)/headers $(OBJDIR)/name_.c: $(SRCDIR)/name.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/name.c >$(OBJDIR)/name_.c $(OBJDIR)/name.o: $(OBJDIR)/name_.c $(OBJDIR)/name.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/name.o -c $(OBJDIR)/name_.c name.h: $(OBJDIR)/headers $(OBJDIR)/path_.c: $(SRCDIR)/path.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/path.c >$(OBJDIR)/path_.c $(OBJDIR)/path.o: $(OBJDIR)/path_.c $(OBJDIR)/path.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/path.o -c $(OBJDIR)/path_.c path.h: $(OBJDIR)/headers $(OBJDIR)/pivot_.c: $(SRCDIR)/pivot.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/pivot.c >$(OBJDIR)/pivot_.c $(OBJDIR)/pivot.o: $(OBJDIR)/pivot_.c $(OBJDIR)/pivot.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/pivot.o -c $(OBJDIR)/pivot_.c pivot.h: $(OBJDIR)/headers $(OBJDIR)/popen_.c: $(SRCDIR)/popen.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/popen.c >$(OBJDIR)/popen_.c $(OBJDIR)/popen.o: $(OBJDIR)/popen_.c $(OBJDIR)/popen.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/popen.o -c $(OBJDIR)/popen_.c popen.h: $(OBJDIR)/headers $(OBJDIR)/pqueue_.c: $(SRCDIR)/pqueue.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/pqueue.c >$(OBJDIR)/pqueue_.c $(OBJDIR)/pqueue.o: $(OBJDIR)/pqueue_.c $(OBJDIR)/pqueue.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/pqueue.o -c $(OBJDIR)/pqueue_.c pqueue.h: $(OBJDIR)/headers $(OBJDIR)/printf_.c: $(SRCDIR)/printf.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/printf.c >$(OBJDIR)/printf_.c $(OBJDIR)/printf.o: $(OBJDIR)/printf_.c $(OBJDIR)/printf.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/printf.o -c $(OBJDIR)/printf_.c printf.h: $(OBJDIR)/headers $(OBJDIR)/rebuild_.c: $(SRCDIR)/rebuild.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/rebuild.c >$(OBJDIR)/rebuild_.c $(OBJDIR)/rebuild.o: $(OBJDIR)/rebuild_.c $(OBJDIR)/rebuild.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/rebuild.o -c $(OBJDIR)/rebuild_.c rebuild.h: $(OBJDIR)/headers $(OBJDIR)/report_.c: $(SRCDIR)/report.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/report.c >$(OBJDIR)/report_.c $(OBJDIR)/report.o: $(OBJDIR)/report_.c $(OBJDIR)/report.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/report.o -c $(OBJDIR)/report_.c report.h: $(OBJDIR)/headers $(OBJDIR)/rss_.c: $(SRCDIR)/rss.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/rss.c >$(OBJDIR)/rss_.c $(OBJDIR)/rss.o: $(OBJDIR)/rss_.c $(OBJDIR)/rss.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/rss.o -c $(OBJDIR)/rss_.c rss.h: $(OBJDIR)/headers $(OBJDIR)/schema_.c: $(SRCDIR)/schema.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/schema.c >$(OBJDIR)/schema_.c $(OBJDIR)/schema.o: $(OBJDIR)/schema_.c $(OBJDIR)/schema.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/schema.o -c $(OBJDIR)/schema_.c schema.h: $(OBJDIR)/headers $(OBJDIR)/search_.c: $(SRCDIR)/search.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/search.c >$(OBJDIR)/search_.c $(OBJDIR)/search.o: $(OBJDIR)/search_.c $(OBJDIR)/search.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/search.o -c $(OBJDIR)/search_.c search.h: $(OBJDIR)/headers $(OBJDIR)/setup_.c: $(SRCDIR)/setup.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/setup.c >$(OBJDIR)/setup_.c $(OBJDIR)/setup.o: $(OBJDIR)/setup_.c $(OBJDIR)/setup.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/setup.o -c $(OBJDIR)/setup_.c setup.h: $(OBJDIR)/headers $(OBJDIR)/sha1_.c: $(SRCDIR)/sha1.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/sha1.c >$(OBJDIR)/sha1_.c $(OBJDIR)/sha1.o: $(OBJDIR)/sha1_.c $(OBJDIR)/sha1.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/sha1.o -c $(OBJDIR)/sha1_.c sha1.h: $(OBJDIR)/headers $(OBJDIR)/shun_.c: $(SRCDIR)/shun.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/shun.c >$(OBJDIR)/shun_.c $(OBJDIR)/shun.o: $(OBJDIR)/shun_.c $(OBJDIR)/shun.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/shun.o -c $(OBJDIR)/shun_.c shun.h: $(OBJDIR)/headers $(OBJDIR)/skins_.c: $(SRCDIR)/skins.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/skins.c >$(OBJDIR)/skins_.c $(OBJDIR)/skins.o: $(OBJDIR)/skins_.c $(OBJDIR)/skins.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/skins.o -c $(OBJDIR)/skins_.c skins.h: $(OBJDIR)/headers $(OBJDIR)/sqlcmd_.c: $(SRCDIR)/sqlcmd.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/sqlcmd.c >$(OBJDIR)/sqlcmd_.c $(OBJDIR)/sqlcmd.o: $(OBJDIR)/sqlcmd_.c $(OBJDIR)/sqlcmd.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/sqlcmd.o -c $(OBJDIR)/sqlcmd_.c sqlcmd.h: $(OBJDIR)/headers $(OBJDIR)/stash_.c: $(SRCDIR)/stash.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/stash.c >$(OBJDIR)/stash_.c $(OBJDIR)/stash.o: $(OBJDIR)/stash_.c $(OBJDIR)/stash.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/stash.o -c $(OBJDIR)/stash_.c stash.h: $(OBJDIR)/headers $(OBJDIR)/stat_.c: $(SRCDIR)/stat.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/stat.c >$(OBJDIR)/stat_.c $(OBJDIR)/stat.o: $(OBJDIR)/stat_.c $(OBJDIR)/stat.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/stat.o -c $(OBJDIR)/stat_.c stat.h: $(OBJDIR)/headers $(OBJDIR)/style_.c: $(SRCDIR)/style.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/style.c >$(OBJDIR)/style_.c $(OBJDIR)/style.o: $(OBJDIR)/style_.c $(OBJDIR)/style.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/style.o -c $(OBJDIR)/style_.c style.h: $(OBJDIR)/headers $(OBJDIR)/sync_.c: $(SRCDIR)/sync.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/sync.c >$(OBJDIR)/sync_.c $(OBJDIR)/sync.o: $(OBJDIR)/sync_.c $(OBJDIR)/sync.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/sync.o -c $(OBJDIR)/sync_.c sync.h: $(OBJDIR)/headers $(OBJDIR)/tag_.c: $(SRCDIR)/tag.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/tag.c >$(OBJDIR)/tag_.c $(OBJDIR)/tag.o: $(OBJDIR)/tag_.c $(OBJDIR)/tag.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/tag.o -c $(OBJDIR)/tag_.c tag.h: $(OBJDIR)/headers $(OBJDIR)/tar_.c: $(SRCDIR)/tar.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/tar.c >$(OBJDIR)/tar_.c $(OBJDIR)/tar.o: $(OBJDIR)/tar_.c $(OBJDIR)/tar.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/tar.o -c $(OBJDIR)/tar_.c tar.h: $(OBJDIR)/headers $(OBJDIR)/th_main_.c: $(SRCDIR)/th_main.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/th_main.c >$(OBJDIR)/th_main_.c $(OBJDIR)/th_main.o: $(OBJDIR)/th_main_.c $(OBJDIR)/th_main.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/th_main.o -c $(OBJDIR)/th_main_.c th_main.h: $(OBJDIR)/headers $(OBJDIR)/timeline_.c: $(SRCDIR)/timeline.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/timeline.c >$(OBJDIR)/timeline_.c $(OBJDIR)/timeline.o: $(OBJDIR)/timeline_.c $(OBJDIR)/timeline.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/timeline.o -c $(OBJDIR)/timeline_.c timeline.h: $(OBJDIR)/headers $(OBJDIR)/tkt_.c: $(SRCDIR)/tkt.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/tkt.c >$(OBJDIR)/tkt_.c $(OBJDIR)/tkt.o: $(OBJDIR)/tkt_.c $(OBJDIR)/tkt.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/tkt.o -c $(OBJDIR)/tkt_.c tkt.h: $(OBJDIR)/headers $(OBJDIR)/tktsetup_.c: $(SRCDIR)/tktsetup.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/tktsetup.c >$(OBJDIR)/tktsetup_.c $(OBJDIR)/tktsetup.o: $(OBJDIR)/tktsetup_.c $(OBJDIR)/tktsetup.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/tktsetup.o -c $(OBJDIR)/tktsetup_.c tktsetup.h: $(OBJDIR)/headers $(OBJDIR)/undo_.c: $(SRCDIR)/undo.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/undo.c >$(OBJDIR)/undo_.c $(OBJDIR)/undo.o: $(OBJDIR)/undo_.c $(OBJDIR)/undo.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/undo.o -c $(OBJDIR)/undo_.c undo.h: $(OBJDIR)/headers $(OBJDIR)/update_.c: $(SRCDIR)/update.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/update.c >$(OBJDIR)/update_.c $(OBJDIR)/update.o: $(OBJDIR)/update_.c $(OBJDIR)/update.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/update.o -c $(OBJDIR)/update_.c update.h: $(OBJDIR)/headers $(OBJDIR)/url_.c: $(SRCDIR)/url.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/url.c >$(OBJDIR)/url_.c $(OBJDIR)/url.o: $(OBJDIR)/url_.c $(OBJDIR)/url.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/url.o -c $(OBJDIR)/url_.c url.h: $(OBJDIR)/headers $(OBJDIR)/user_.c: $(SRCDIR)/user.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/user.c >$(OBJDIR)/user_.c $(OBJDIR)/user.o: $(OBJDIR)/user_.c $(OBJDIR)/user.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/user.o -c $(OBJDIR)/user_.c user.h: $(OBJDIR)/headers $(OBJDIR)/verify_.c: $(SRCDIR)/verify.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/verify.c >$(OBJDIR)/verify_.c $(OBJDIR)/verify.o: $(OBJDIR)/verify_.c $(OBJDIR)/verify.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/verify.o -c $(OBJDIR)/verify_.c verify.h: $(OBJDIR)/headers $(OBJDIR)/vfile_.c: $(SRCDIR)/vfile.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/vfile.c >$(OBJDIR)/vfile_.c $(OBJDIR)/vfile.o: $(OBJDIR)/vfile_.c $(OBJDIR)/vfile.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/vfile.o -c $(OBJDIR)/vfile_.c vfile.h: $(OBJDIR)/headers $(OBJDIR)/wiki_.c: $(SRCDIR)/wiki.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/wiki.c >$(OBJDIR)/wiki_.c $(OBJDIR)/wiki.o: $(OBJDIR)/wiki_.c $(OBJDIR)/wiki.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/wiki.o -c $(OBJDIR)/wiki_.c wiki.h: $(OBJDIR)/headers $(OBJDIR)/wikiformat_.c: $(SRCDIR)/wikiformat.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/wikiformat.c >$(OBJDIR)/wikiformat_.c $(OBJDIR)/wikiformat.o: $(OBJDIR)/wikiformat_.c $(OBJDIR)/wikiformat.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/wikiformat.o -c $(OBJDIR)/wikiformat_.c wikiformat.h: $(OBJDIR)/headers $(OBJDIR)/winhttp_.c: $(SRCDIR)/winhttp.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/winhttp.c >$(OBJDIR)/winhttp_.c $(OBJDIR)/winhttp.o: $(OBJDIR)/winhttp_.c $(OBJDIR)/winhttp.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/winhttp.o -c $(OBJDIR)/winhttp_.c winhttp.h: $(OBJDIR)/headers $(OBJDIR)/xfer_.c: $(SRCDIR)/xfer.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/xfer.c >$(OBJDIR)/xfer_.c $(OBJDIR)/xfer.o: $(OBJDIR)/xfer_.c $(OBJDIR)/xfer.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/xfer.o -c $(OBJDIR)/xfer_.c xfer.h: $(OBJDIR)/headers $(OBJDIR)/xfersetup_.c: $(SRCDIR)/xfersetup.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/xfersetup.c >$(OBJDIR)/xfersetup_.c $(OBJDIR)/xfersetup.o: $(OBJDIR)/xfersetup_.c $(OBJDIR)/xfersetup.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/xfersetup.o -c $(OBJDIR)/xfersetup_.c xfersetup.h: $(OBJDIR)/headers $(OBJDIR)/zip_.c: $(SRCDIR)/zip.c $(OBJDIR)/translate $(TRANSLATE) $(SRCDIR)/zip.c >$(OBJDIR)/zip_.c $(OBJDIR)/zip.o: $(OBJDIR)/zip_.c $(OBJDIR)/zip.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/zip.o -c $(OBJDIR)/zip_.c 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 -DCSON_FOSSIL_MODE $(OBJDIR)/json.o $(OBJDIR)/json_artifact.o $(OBJDIR)/json_branch.o $(OBJDIR)/json_diff.o $(OBJDIR)/json_login.o $(OBJDIR)/json_query.o $(OBJDIR)/json_report.o $(OBJDIR)/json_tag.o $(OBJDIR)/json_timeline.o $(OBJDIR)/json_user.o $(OBJDIR)/json_wiki.o : $(SRCDIR)/json_detail.h $(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 ifdef FOSSIL_ENABLE_TCL $(OBJDIR)/th_tcl.o: $(SRCDIR)/th_tcl.c $(XTCC) -I$(SRCDIR) -c $(SRCDIR)/th_tcl.c -o $(OBJDIR)/th_tcl.o endif |
Changes to win/Makefile.msc.
|
| > > | > | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # ############################################################################## # WARNING: DO NOT EDIT, AUTOMATICALLY GENERATED FILE (SEE "src/makemake.tcl") ############################################################################## # # This file is automatically generated. Instead of editing this # file, edit "makemake.tcl" then run "tclsh makemake.tcl" # to regenerate this file. # B = .. SRCDIR = $B\src OBJDIR = . OX = . O = .obj E = .exe |
| ︙ | ︙ | |||
34 35 36 37 38 39 40 | BCC = $(CC) $(CFLAGS) TCC = $(CC) -c $(CFLAGS) $(MSCDEF) $(SSL) $(INCL) 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 | | | | 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | BCC = $(CC) $(CFLAGS) TCC = $(CC) -c $(CFLAGS) $(MSCDEF) $(SSL) $(INCL) 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_artifact_.c json_branch_.c json_diff_.c json_login_.c json_query_.c json_report_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c login_.c main_.c manifest_.c md5_.c merge_.c merge3_.c name_.c path_.c pivot_.c popen_.c pqueue_.c printf_.c rebuild_.c report_.c rss_.c schema_.c search_.c setup_.c sha1_.c shun_.c skins_.c sqlcmd_.c stash_.c stat_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c update_.c url_.c user_.c verify_.c vfile_.c wiki_.c wikiformat_.c winhttp_.c xfer_.c xfersetup_.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_artifact$O $(OX)\json_branch$O $(OX)\json_diff$O $(OX)\json_login$O $(OX)\json_query$O $(OX)\json_report$O $(OX)\json_tag$O $(OX)\json_timeline$O $(OX)\json_user$O $(OX)\json_wiki$O $(OX)\leaf$O $(OX)\login$O $(OX)\main$O $(OX)\manifest$O $(OX)\md5$O $(OX)\merge$O $(OX)\merge3$O $(OX)\name$O $(OX)\path$O $(OX)\pivot$O $(OX)\popen$O $(OX)\pqueue$O $(OX)\printf$O $(OX)\rebuild$O $(OX)\report$O $(OX)\rss$O $(OX)\schema$O $(OX)\search$O $(OX)\setup$O $(OX)\sha1$O $(OX)\shun$O $(OX)\skins$O $(OX)\sqlcmd$O $(OX)\stash$O $(OX)\stat$O $(OX)\style$O $(OX)\sync$O $(OX)\tag$O $(OX)\tar$O $(OX)\th_main$O $(OX)\timeline$O $(OX)\tkt$O $(OX)\tktsetup$O $(OX)\undo$O $(OX)\update$O $(OX)\url$O $(OX)\user$O $(OX)\verify$O $(OX)\vfile$O $(OX)\wiki$O $(OX)\wikiformat$O $(OX)\winhttp$O $(OX)\xfer$O $(OX)\xfersetup$O $(OX)\zip$O $(OX)\shell$O $(OX)\sqlite3$O $(OX)\th$O $(OX)\th_lang$O APPNAME = $(OX)\fossil$(E) all: $(OX) $(APPNAME) $(APPNAME) : translate$E mkindex$E headers $(OBJ) $(OX)\linkopts |
| ︙ | ︙ | |||
86 87 88 89 90 91 92 93 94 95 96 97 98 99 | echo $(OX)\gzip.obj >> $@ echo $(OX)\http.obj >> $@ 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)\leaf.obj >> $@ echo $(OX)\login.obj >> $@ echo $(OX)\main.obj >> $@ echo $(OX)\manifest.obj >> $@ echo $(OX)\md5.obj >> $@ echo $(OX)\merge.obj >> $@ echo $(OX)\merge3.obj >> $@ | > > > > > > > > > > > | 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | echo $(OX)\gzip.obj >> $@ echo $(OX)\http.obj >> $@ 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_artifact.obj >> $@ echo $(OX)\json_branch.obj >> $@ echo $(OX)\json_diff.obj >> $@ echo $(OX)\json_login.obj >> $@ echo $(OX)\json_query.obj >> $@ echo $(OX)\json_report.obj >> $@ echo $(OX)\json_tag.obj >> $@ echo $(OX)\json_timeline.obj >> $@ echo $(OX)\json_user.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 >> $@ echo $(OX)\merge.obj >> $@ echo $(OX)\merge3.obj >> $@ |
| ︙ | ︙ | |||
133 134 135 136 137 138 139 140 141 142 143 144 145 146 | echo $(OX)\user.obj >> $@ echo $(OX)\verify.obj >> $@ echo $(OX)\vfile.obj >> $@ echo $(OX)\wiki.obj >> $@ echo $(OX)\wikiformat.obj >> $@ echo $(OX)\winhttp.obj >> $@ echo $(OX)\xfer.obj >> $@ echo $(OX)\zip.obj >> $@ echo $(LIBS) >> $@ $(OX): | > | 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | echo $(OX)\user.obj >> $@ echo $(OX)\verify.obj >> $@ echo $(OX)\vfile.obj >> $@ echo $(OX)\wiki.obj >> $@ echo $(OX)\wikiformat.obj >> $@ echo $(OX)\winhttp.obj >> $@ echo $(OX)\xfer.obj >> $@ echo $(OX)\xfersetup.obj >> $@ echo $(OX)\zip.obj >> $@ echo $(LIBS) >> $@ $(OX): |
| ︙ | ︙ | |||
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | $(TCC) /Fo$@ -c $** $(OX)\th_lang$O : $(SRCDIR)\th_lang.c $(TCC) /Fo$@ -c $** VERSION.h : mkversion$E $B\manifest.uuid $B\manifest $B\VERSION $** > $@ page_index.h: mkindex$E $(SRC) $** > $@ clean: -del $(OX)\*.obj -del *.obj *_.c *.h *.map -del headers linkopts realclean: -del $(APPNAME) translate$E mkindex$E makeheaders$E mkversion$E $(OX)\add$O : add_.c add.h $(TCC) /Fo$@ -c add_.c add_.c : $(SRCDIR)\add.c translate$E $** > $@ | > > > > > > > > > > > > > > | 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | $(TCC) /Fo$@ -c $** $(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: -del $(OX)\*.obj -del *.obj *_.c *.h *.map -del headers linkopts realclean: -del $(APPNAME) translate$E mkindex$E makeheaders$E mkversion$E $(OBJDIR)\json$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_artifact$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_branch$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_diff$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_login$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_query$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_report$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_tag$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_timeline$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_user$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_wiki$O : $(SRCDIR)\json_detail.h $(OX)\add$O : add_.c add.h $(TCC) /Fo$@ -c add_.c add_.c : $(SRCDIR)\add.c translate$E $** > $@ |
| ︙ | ︙ | |||
408 409 410 411 412 413 414 415 416 417 418 419 420 421 | translate$E $** > $@ $(OX)\info$O : info_.c info.h $(TCC) /Fo$@ -c info_.c info_.c : $(SRCDIR)\info.c translate$E $** > $@ $(OX)\leaf$O : leaf_.c leaf.h $(TCC) /Fo$@ -c leaf_.c leaf_.c : $(SRCDIR)\leaf.c translate$E $** > $@ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 | translate$E $** > $@ $(OX)\info$O : info_.c info.h $(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_artifact$O : json_artifact_.c json_artifact.h $(TCC) /Fo$@ -c json_artifact_.c json_artifact_.c : $(SRCDIR)\json_artifact.c translate$E $** > $@ $(OX)\json_branch$O : json_branch_.c json_branch.h $(TCC) /Fo$@ -c json_branch_.c json_branch_.c : $(SRCDIR)\json_branch.c translate$E $** > $@ $(OX)\json_diff$O : json_diff_.c json_diff.h $(TCC) /Fo$@ -c json_diff_.c json_diff_.c : $(SRCDIR)\json_diff.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_query$O : json_query_.c json_query.h $(TCC) /Fo$@ -c json_query_.c json_query_.c : $(SRCDIR)\json_query.c translate$E $** > $@ $(OX)\json_report$O : json_report_.c json_report.h $(TCC) /Fo$@ -c json_report_.c json_report_.c : $(SRCDIR)\json_report.c translate$E $** > $@ $(OX)\json_tag$O : json_tag_.c json_tag.h $(TCC) /Fo$@ -c json_tag_.c json_tag_.c : $(SRCDIR)\json_tag.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_user$O : json_user_.c json_user.h $(TCC) /Fo$@ -c json_user_.c json_user_.c : $(SRCDIR)\json_user.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 $** > $@ |
| ︙ | ︙ | |||
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 | translate$E $** > $@ $(OX)\xfer$O : xfer_.c xfer.h $(TCC) /Fo$@ -c xfer_.c xfer_.c : $(SRCDIR)\xfer.c translate$E $** > $@ $(OX)\zip$O : zip_.c zip.h $(TCC) /Fo$@ -c zip_.c zip_.c : $(SRCDIR)\zip.c translate$E $** > $@ headers: makeheaders$E page_index.h VERSION.h | > > > > > > | | 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 | translate$E $** > $@ $(OX)\xfer$O : xfer_.c xfer.h $(TCC) /Fo$@ -c xfer_.c xfer_.c : $(SRCDIR)\xfer.c translate$E $** > $@ $(OX)\xfersetup$O : xfersetup_.c xfersetup.h $(TCC) /Fo$@ -c xfersetup_.c xfersetup_.c : $(SRCDIR)\xfersetup.c translate$E $** > $@ $(OX)\zip$O : zip_.c zip.h $(TCC) /Fo$@ -c zip_.c 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_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_diff_.c:json_diff.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h path_.c:path.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR)\cson_amalgamation.h @copy /Y nul: headers |
Changes to www/build.wiki.
1 2 3 4 | <title>Building and Installing Fossil</title> <h2>0.0 Using A Pre-compiled Binary</h2> | | | > > | | | > > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
<title>Building and Installing Fossil</title>
<h2>0.0 Using A Pre-compiled Binary</h2>
<p>Released versions of fossil come with
<a href="http://www.fossil-scm.org/download.html">pre-compiled binaries and
a source archive</a> for that release. You can thus skip the following if you
want to run or build a release version of fossil. Just download
the appropriate package from the <a href="http://www.fossil-scm.org/download.html">downloads page</a>
and put it on your $PATH.
To uninstall, simply delete the binary.
To upgrade from an older release, just overwrite the older binary with
the newer one.</p>
<h2>0.1 Executive Summary</h2>
<p>Building and installing is very simple. Three steps:</p>
<ol>
<li> Download and unpack a source tarball or ZIP.
<li> <b>./configure; make</b>
<li> Move or copy the resulting "fossil" executable to someplace
on your $PATH.
</ol>
<p><hr>
<h2>1.0 Obtaining The Source Code</h2>
<p>Fossil is self-hosting, so you can obtain a ZIP archive containing
a snapshot of the <em>latest</em> version directly from fossil's own fossil
repository. Additionally, source archives of <em>released</em> versions of
fossil are available from the <a href="http://www.fossil-scm.org/download.html">downloads page</a>.
To obtain a development version of fossil, follow these steps:</p>
<ol>
<li><p>Point your web browser at
<a href="http://www.fossil-scm.org/">
http://www.fossil-scm.org/</a>. Click on the "Login" menu button.</p></li>
<li><p>Log in as anonymous. The password is shown on screen.
|
| ︙ | ︙ |
Changes to www/changes.wiki.
1 2 3 4 5 6 7 8 9 |
<title>Change Log</title>
<h2>Changes For Version 1.19 (2011-09-02)</h2>
* Added a ./configure script based on autosetup.
* Added the "[/help/winsrv | fossil winsrv]" command
for creating a Fossil service on windows systems.
* Added "versionable settings" where settings that affect
the local tree can be stored in versioned files in the
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
<title>Change Log</title>
<h2>Changes For Version 1.22 (2012-02-xx)</h2>
* Greatly improved "diff" processing including the new --brief option,
partial line matching, colorized in-line diffs, and better performance.
* Promote "allow-symlinks" to a versionable setting
* Harden the CGI processing logic against DOS attacks
* Add the ability to run TH1 scripts after sync requests
* Store the repository name in _FOSSIL_ as it is type in the "open" command,
possibly as a relative pathname.
* Make ".fslckout" the alternative name for the "_FOSSIL_" file.
* Change the "ssh:" transfer method to allow all access regardless of
user permission.
* Various minor bug fixes
<h2>Changes For Version 1.21 (2011-12-13)</h2>
* Added side-by-side diffs in the command-line interface
* Automatically enable hyperlinks if the UserAgent string in the
HTTP header suggests that the requestor is a human and not a bot.
* Show only commonly used commands with "fossil help". Use
"fossil help --all" to see the complete list now.
* Improvements to the "stash" command: (1) Stash all files, not just
those below the working directory. (2) Add the --detail option to
"list". (3) Confirm before "drop --all". (4) Add the "help"
subcommand.
* Add an Admin/Access setting to change the number of octets of the
IP address that are saved in login cookies - allowing this setting
to be changed to zero
* Promote the "test-md5sum" command to "md5sum".
* Added the "whatis" command.
* Stop showing the server-code in status outputs - it is no longer used
for anything.
* Added a compile-time option (--with-tcl) to build in the full
TCL interpreter to augment TH1.
* Merged the JSON branch into trunk. Disabled by default. Enabled
by a compile-time option. Probably it will be enabled by default
in some future release.
* Update to use SQLite version 3.7.9 plus the alignment fix for Sparc.
align
<h2>Changes For Version 1.20 (2011-10-21)</h2>
* Added side-by-side diffs in HTML interface. [0bde74ea1e]
* Added support for symlinks. (Controlled by "allow-symlinks" setting,
off by default). [e4f1c1fe95]
* Fixed CLI annotate to show the proper file version in case there
are multiple equal versions in history. [e161670939]
* Timeline now shows tag changes (requires rebuild).[87540ed6e6]
* Fixed annotate to show "more relevant" versions of lines in
some cases. [e161670939]
* New command: ticket history. [98a855c508]
* Disabled SSLv2 in HTTPS client.[ea1d369d23]
* Fixed constant prompting regarding previously-saved SSL
certificates. [636804745b]
* Other SSL improvements.
* Added -R REPOFILE support to several more CLI commands. [e080560378]
* Generated tarballs now have constant timestamps, so they are
always identical for any given checkin. [e080560378]
* A number of minor HTML-related tweaks and fixes.
* Added --args FILENAME global CLI argument to import arbitrary
CLI arguments from a file (e.g. long file lists). [e080560378]
* Fixed significant memory leak in annotation of files with long
histories.[9929bab702]
* Added warnings when a merge operation overwrites local copies
(UNDO is available, but previously this condition normally went
silently unnoticed). [39f979b08c]
* Improved performance when adding many files. [a369dc7721]
* Improve merges which contain many file renames. [0b93b0f958]
* Added protection against timing attacks. [d4a341b49d]
* Firefox now remembers filled fields when returning to forms. [3fac77d7b0]
* Added the --stats option to the rebuild command. [f25e5e53c4]
* RSS feed now passes validation. [ce354d0a9f]
* Show overridden user when entering commit comment. [ce354d0a9f]
* Made rebuilding from web interface silent. [ce354d0a9f]
* Now works on MSVC with repos >2GB. [6092935ff2]
* A number of code cleanups to resolve warnings from various compilers.
* Update the built-in SQLite to version 3.7.9 beta.
<h2>Changes For Version 1.19 (2011-09-02)</h2>
* Added a ./configure script based on autosetup.
* Added the "[/help/winsrv | fossil winsrv]" command
for creating a Fossil service on windows systems.
* Added "versionable settings" where settings that affect
the local tree can be stored in versioned files in the
|
| ︙ | ︙ |
Changes to www/checkin_names.wiki.
| ︙ | ︙ | |||
124 125 126 127 128 129 130 | * <i>YYYY-MM-DD</i> * <i>YYYY-MM-DD HH:MM</i> * <i>YYYY-MM-DD HH:MM:SS</i> The space between the day and the year can optionally be replaced by an uppercase <b>T</b> and the entire timestamp can | | | | > | | 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | * <i>YYYY-MM-DD</i> * <i>YYYY-MM-DD HH:MM</i> * <i>YYYY-MM-DD HH:MM:SS</i> The space between the day and the year can optionally be replaced by an uppercase <b>T</b> and the entire timestamp can optionally be followed by "<b>z</b>" or "<b>Z</b>". In its default configuration, Fossil interprets and displays all dates in Universal Coordinated Time (UTC). This tends to work the best for distributed projects where participants are scattered around the globe. But there is an option on the Admin/Timeline page of the web-interface to switch to local time. The "<b>Z</b>" suffix on an timestamp check-in name is meaningless if Fossil is in the default mode of using UTC for everything, but if Fossil has been switched to localtime mode, then the "<b>Z</b>" suffix means to interpret that particular timestamp using UTC instead localtime. For an example of how timestamps are useful, consider the homepage for the Fossil website itself: <blockquote> http://www.fossil-scm.org/fossil/doc/<b>trunk</b>/www/index.wiki </blockquote> The bold component of that URL is a check-in name. To see what the Fossil website looked like on January 1, 2009, one has merely to change |
| ︙ | ︙ |
Changes to www/fossil-v-git.wiki.
| ︙ | ︙ | |||
17 18 19 20 21 22 23 |
<h2>2.0 Executive Summary:</h2>
<blockquote><center><table border=1 cellpadding=5>
<tr><th width="50%">GIT</th><th width="50%">FOSSIL</th></tr>
<tr><td>File versioning only</td>
<td>Versioning, Tickets, Wiki, and Blog/News</td></tr>
<tr><td>Sharding</td><td>Replicating</td></tr>
| | | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<h2>2.0 Executive Summary:</h2>
<blockquote><center><table border=1 cellpadding=5>
<tr><th width="50%">GIT</th><th width="50%">FOSSIL</th></tr>
<tr><td>File versioning only</td>
<td>Versioning, Tickets, Wiki, and Blog/News</td></tr>
<tr><td>Sharding</td><td>Replicating</td></tr>
<tr><td>Developer branches</td><td>Feature branches</td></tr>
<tr><td>Complex</td><td>Intuitive</td></tr>
<tr><td>Separate web tools</td><td>Integrated Web interface</td></tr>
<tr><td>Lots of little tools</td><td>Single executable</td></tr>
<tr><td>Pile-of-files repository</td><td>Single file repository</td></tr>
<tr><td>Uses "<tt>rebase</tt>"</td><td>Immutable</td></tr>
<tr><td>GPL</td><td>BSD</td></tr>
</table></center></blockquote>
|
| ︙ | ︙ | |||
77 78 79 80 81 82 83 | developers are operating directly on the master branch, or at most a small number of well defined branches. The [concepts.wiki#workflow | autosync] mode of Fossil makes it easy for multiple developers to work on a single branch and maintain linear development on that branch and avoid needless forking and merging. | | | | > > > > > > > > > | > > < > > | > > > > > | < < < < < < < < < < < < > > < | < < > > > > > > > | 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | developers are operating directly on the master branch, or at most a small number of well defined branches. The [concepts.wiki#workflow | autosync] mode of Fossil makes it easy for multiple developers to work on a single branch and maintain linear development on that branch and avoid needless forking and merging. <h3>3.3 Branches</h3> Git (and especially GitHub) encourages a workflow where each developer has his or her own branch or branches. Developers then send "pull requests" to have their changes be merged into "official" branches by integrators. For example, the Linux kernel team has a hierarchy of integrators with Linus Torvalds at the root. Individual developers each have their own private branches of the source tree into which they make their own changes. They then encourage first-tier integrators to pull those changes. The first-tier integrators merge together changes from multiple contributors then try to get second-tier integrators to pull their branches. The changes merge up the the hierarchy until (hopefully) they are pulled into "Linus's branch", at which time they become part of the "official" Linux. In Git, each branch is "owned" by the person who creates it and works on it. The owner might pull changes from others, but the owner is always in control of the branch. Branches are developer-centric. Fossil, on the other hand, encourages a workflow where branches are associated with features or releases, not individual developers. All developers share all branches in common, and two or more developers can and often do intersperse commits onto the same branch. Branches do not belong to individuals. All branches are read/write accessible to all developers at all times. There is no need for integrators to merge together changes from various independent developers. Instead, all of the developers work together cooperatively and the changes stay integrated naturally. So to a first approximation, branches in Git are developer-centric whereas branches in Fossil are feature-centric. The Git approach scales much better for large projects like the Linux kernel with thousands of contributors who in many cases don't even know each others names. The integrators serve a gatekeeper role to help keep undesirable code out of the official Linux source tree. On the other hand, not many projects are as big or as loosely organized as the Linux kernel. Most project, have a small team of developers who all know each other well and trust each other, and who enjoy working together collaboratively without the overhead and hierarchy of integrators. <h3>3.4 Complexity</h3> Git is a complex system. It can be tricky to use and requires a fair amount of knowledge and experience to master. Fossil strives to be a much simpler system that can be learned and mastered much more quickly. Fossil strives to have fewer "gotchas" and quirks that can trip up a |
| ︙ | ︙ |
Changes to www/quotes.wiki.
| ︙ | ︙ | |||
83 84 85 86 87 88 89 90 | sometimes very restrictive firewalls, OSX/Win/Linux). We are happy with it and teaching a Msc/Phd student (read complete novice) fossil has just 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> </ol> | > > > > > > > > > > > > > | 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | sometimes very restrictive firewalls, OSX/Win/Linux). We are happy with it and teaching a Msc/Phd student (read complete novice) fossil has just 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> |
Changes to www/reviews.wiki.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<title>Reviews</title>
<b>External links:</b>
* [http://sheddingbikes.com/posts/1276624594.html | Why I Use Fossil]
by Zed A. Shaw.
* [http://nixtu.blogspot.com/2010/03/fossil-dvcs-on-go-first-impressions.html |
Fossil DVCS on the Go - First Impressions]
by Juho Vepsäläinen.
* [http://blog.fupps.com/2010/12/04/exploring-the-fossil-dvcs |
Exploring the Fossil DVCS] by Jan-Piet Mens.
* [http://blog.mired.org/2011/02/fossil-sweet-spot-in-vcs-space.html |
Fossil - a sweet spot in the VCS space] by Mike Meyer.
<b>See Also:</b>
* [./quotes.wiki | Short Quotes on Fossil, Git, And DVCSes]
<b>Daniel writes on 2009-01-06:</b>
| > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<title>Reviews</title>
<b>External links:</b>
* [http://sheddingbikes.com/posts/1276624594.html | Why I Use Fossil]
by Zed A. Shaw.
* [http://nixtu.blogspot.com/2010/03/fossil-dvcs-on-go-first-impressions.html |
Fossil DVCS on the Go - First Impressions]
by Juho Vepsäläinen.
* [http://blog.fupps.com/2010/12/04/exploring-the-fossil-dvcs |
Exploring the Fossil DVCS] by Jan-Piet Mens.
* [http://blog.mired.org/2011/02/fossil-sweet-spot-in-vcs-space.html |
Fossil - a sweet spot in the VCS space] by Mike Meyer.
* [http://blog.s11n.net/?p=72|Four reasons to take a closer look at the Fossil SCM] by Stephan Beal
<b>See Also:</b>
* [./quotes.wiki | Short Quotes on Fossil, Git, And DVCSes]
<b>Daniel writes on 2009-01-06:</b>
|
| ︙ | ︙ | |||
37 38 39 40 41 42 43 | pick the earlier changes, then figure out how to make my new branch shared instead of private. Just want to say thanks for fossil making my life easier on most of my projects, and being able to move commits to another branch after the fact and shared-by-default branches are good features. Also not having a misanthropic command line interface. </blockquote> | | | 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | pick the earlier changes, then figure out how to make my new branch shared instead of private. Just want to say thanks for fossil making my life easier on most of my projects, and being able to move commits to another branch after the fact and shared-by-default branches are good features. Also not having a misanthropic command line interface. </blockquote> <b>Stephan Beal writes on 2009-01-11:</b> <blockquote> Sometime in late 2007 I came across a link to fossil on <a href="http://www.sqlite.org/">sqlite.org</a>. It was a good thing I bookmarked it, because I was never able to find the link again (it might have been in a bug report or something). The reasons I first took a close look at it were (A) it stemmed from the |
| ︙ | ︙ | |||
134 135 136 137 138 139 140 | Firefox, or the Linux Kernel), but 99.9% of projects never reach anywhere near that size or complexity. In summary: I remember my first reaction to fossil being, "this will be an | | | | 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | Firefox, or the Linux Kernel), but 99.9% of projects never reach anywhere near that size or complexity. In summary: I remember my first reaction to fossil being, "this will be an excellent solution for small projects (like the dozens we've all got sitting on our hard drives but which don't justify the hassle of version control)." A year of daily use in over 15 source trees has confirmed that, and I continue to heartily recommend fossil to other developers I know who also have their own collection of "unhosted" pet projects. </blockquote> |
Changes to www/settings.wiki.
| ︙ | ︙ | |||
16 17 18 19 20 21 22 | You can also set a setting globally on your local machine. The value will be used for all repositories cloned to your machine, unless overridden explicitly in a particular repository. Global settings can be set by using the <tt>-global</tt> option on the <tt>fossil settings</tt> command. <h3>"Versionable" settings</h3> Most of the settings control the behaviour of fossil on your local machine, largely acting to reflect your preference on how you want to use Fossil, how you communicate with the server, or options for hosting a repository on the web. | | | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | You can also set a setting globally on your local machine. The value will be used for all repositories cloned to your machine, unless overridden explicitly in a particular repository. Global settings can be set by using the <tt>-global</tt> option on the <tt>fossil settings</tt> command. <h3>"Versionable" settings</h3> Most of the settings control the behaviour of fossil on your local machine, largely acting to reflect your preference on how you want to use Fossil, how you communicate with the server, or options for hosting a repository on the web. However, for historical reasons, some settings affect how you work with versioned files. These are <tt>allow-symlinks</tt>, <tt>binary-glob</tt>, <tt>crnl-glob</tt>, <tt>ignore-glob</tt>, <tt>empty-dirs</tt> and <tt>manifest</tt>. The most important is <tt>ignore-glob</tt> which specifies which files should be ignored when looking for unmanaged files with the <tt>extras</tt> command. Because these options can change over time, and the inconvenience of replicating changes, these settings are "versionable". As well as being able to be set using the <tt>settings</tt> command or the web interface, you can created versioned files in the <tt>.fossil-settings</tt> directory named with the setting name. The contents of the file is the value of the setting, and these files are checked in, committed, merged, and so on, as with any other file. Where a setting is a list of values, such as <tt>ignore-glob</tt>, you can also a newline as a separator as well as a comma. For example, to set the list of ignored files, create a <tt>.fossil-settings/ignore-glob</tt> file where each line contains a glob for ignored files. |
| ︙ | ︙ |
Changes to www/tech_overview.wiki.
1 2 3 4 5 6 7 8 9 | <title>Technical Overview</title> <h2 align="center"> A Technical Overview<br>Of The Design And Implementation<br>Of Fossil </h2> <h2>1.0 Introduction</h2> At its lowest level, a Fossil repository consists of an unordered set of immutable "artifacts". You might think of these artifacts as "files", | | < | | | | | < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | <title>Technical Overview</title> <h2 align="center"> A Technical Overview<br>Of The Design And Implementation<br>Of Fossil </h2> <h2>1.0 Introduction</h2> At its lowest level, a Fossil repository consists of an unordered set of immutable "artifacts". You might think of these artifacts as "files", since in many cases the artifacts exactly that. But other "control artifacts" are also included in the mix. These control artifacts define the relationships between artifacts - which files go together to form a particular version of the project, who checked in that version and when, what was the check-in comment, what wiki pages are included with the project, what are the edit histories of each wiki page, what bug reports or tickets are included, who contributed to the evolution of each ticket, and so forth. This low-level file format is called the "global state" of the repository, since this is the information that is synced to peer repositories using push and pull operations. The low-level file format is also called "enduring" since it is intended to last for many years. The details of the low-level, enduring, global file format are [./fileformat.wiki | described separately]. This article is about how Fossil is currently implemented. Instead of dealing with vague abstractions of "enduring file formats" as the [./fileformat.wiki | other document] does, this article provides some detail on how Fossil actually stores information on disk. <h2>2.0 Three Databases</h2> Fossil stores state information in [http://www.sqlite.org/ | SQLite] database files. SQLite keeps an entire relational database, including multiple tables and indices, in a single disk file. The SQLite library allows the database files to be efficiently queried and updated using the industry-standard SQL language. SQLite updates are atomic, so even in the event of a system crashes or power failure the repository content is protected. Fossil uses three separate classes of SQLite databases: <ol> <li>The configuration database <li>Repository databases <li>Checkout databases |
| ︙ | ︙ | |||
150 151 152 153 154 155 156 | of the artifacts that comprise the [./fileformat.wiki | enduring, global, shared state] of the project. The artifacts are stored as BLOBs, compressed using [http://www.zlib.net/ | zlib compression] and, where applicable, using [./delta_encoder_algorithm.wiki | delta compression]. The combination of zlib and delta compression results in a considerable space savings. For the SQLite project, at the time of this writing, | | | | > | 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | of the artifacts that comprise the [./fileformat.wiki | enduring, global, shared state] of the project. The artifacts are stored as BLOBs, compressed using [http://www.zlib.net/ | zlib compression] and, where applicable, using [./delta_encoder_algorithm.wiki | delta compression]. The combination of zlib and delta compression results in a considerable space savings. For the SQLite project, at the time of this writing, the total size of all artifacts is over 2.0 GB but thanks to the combined zlib and delta compression, that content only takes up 32 MB of space in the repository database, for a compression ratio of about 64:1. The average size of a content BLOB in the database is around 500 bytes. Note that the zlib and delta compression is not an inherent part of the Fossil file format; it is just an optimization. The enduring file format for Fossil is the unordered set of artifacts. The compression techniques are just a detail of how the current implementation of Fossil happens to store these artifacts efficiently on disk. |
| ︙ | ︙ | |||
183 184 185 186 187 188 189 | works by reading the input git-fast-export stream and using it to construct corresponding artifacts which are then written into the repository database. <h4>2.2.2 Project Metadata</h4> The global project state information in the repository database is supplemented by computed metadata that makes querying the project state | | | | | 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
works by reading the input git-fast-export stream and using it to construct
corresponding artifacts which are then written into the repository database.
<h4>2.2.2 Project Metadata</h4>
The global project state information in the repository database is
supplemented by computed metadata that makes querying the project state
more efficient. Metadata includes information such as the following:
* The names for all files found in any checkin.
* All check-ins that modify a given file
* Parents and children of each checkin.
* Potential timeline rows.
* The names of all symbolic tags and the checkins they apply to.
* The names of all wiki pages and the artifacts that comprise each
wiki page.
* Attachments and the wiki pages or tickets they apply to.
* Current content of each ticket.
* Cross-references between tickets, checkins, and wiki pages.
The metadata is held in various SQL tables in the repository database.
The metadata is designed to facilitate queries for the various timelines and
reports that Fossil generates.
As the functionality of Fossil evolves,
the schema for the metadata can and does change.
But schema changes do no invalidate the repository. Remember that the
metadata contains no new information - only information that has been
extracted from the canonical artifacts and saved in a more useful form.
Hence, when the metadata schema changes, the prior metadata can be discarded
and the entire metadata corpus can be recomputed from the canonical
artifacts. That is what the
[/help/rebuild | fossil rebuild] command does.
|
| ︙ | ︙ | |||
271 272 273 274 275 276 277 | privileges on the remote repository. <h4>2.2.5 Shunned Artifact List</h4> The set of canonical artifacts for a project - the global state for the project - is intended to be an append-only database. In other words, new artifacts can be added but artifacts can never be removed. But | | | | | | | | 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 |
privileges on the remote repository.
<h4>2.2.5 Shunned Artifact List</h4>
The set of canonical artifacts for a project - the global state for the
project - is intended to be an append-only database. In other words,
new artifacts can be added but artifacts can never be removed. But
it sometimes happens that inappropriate content is mistakenly or
maliciously added to a repository. The only way to get rid of
the undesired content is to [./shunning.wiki | "shun"] it.
The "shun" table in the repository database records the SHA1 hash of
all shunned artifacts.
The shun table can be pushed or pulled using
the [/help/config | fossil config] command with the "shun" AREA argument.
The shun table is also copied during a [/help/clone | clone].
<h3>2.3 Checkout Databases</h3>
Unlike several other popular DVCSes, Fossil allows a single repository
to have multiple working checkouts. Each working checkout has a single
database in its root directory that records the state of that checkout.
The checkout database is named "_FOSSIL_" by default, but can be renamed
to ".fslckout" if desired. (Future versions of Fossil might make
".fslckout" the default name.) The checkout database records information
such as the following:
* The full pathname of the repository database file.
* The version that is currently checked out.
* Files that have been [/help/add | added],
[/help/rm | removed], or [/help/mv | renamed] but not
yet committed.
* The mtime and size of files as they were originally checked out,
|
| ︙ | ︙ |