You are not logged in.
The Documentation is great but there's one small English grammar mistake which keeps cropping up and can be confusing: "Either this either that" should be written in English as "Either this OR that". I guess it's because in French it's "ou...ou" see: http://www.wordreference.com/enfr/either%20or
Could the most current documentation be given a static link without the version number one gets with:
http://synopse.info/files/pdf/Synopse%2 … 201.18.pdf
Then I could create a bookmark in my web browser which is always up to date.
Thanks
It stops returning data the after reading the first header line. THttpSocket.GetHeader doesn't work as readln returns an empty string in SockRecvLn(s) when it should return the first line header line after 'HTTP/1.1 200 OK'
I'm a bit puzzled by InputSock(). The HTTP response is 2070 bytes long so I expected it to be called 3 times with 1k chucks but it gets called 5 times with Recv being passed 0 as the size the last two times. With Delphi 2007 it only gets called once but works. How many times should InputSock get called?
Looking at this again I've found the procedure THttpSocket.GetHeader in SynCrtSock is not returning any headers under Delphi XE2 (called by THttpClientSocket.Request). With Delphi 2007 a full set of headers, including content-length, is received.
Both XE2 and 2007 receive the first line 'HTTP/1.1 200 OK' but with XE that's all and if the http request is run a second time without restarting the app even this first line isn't received.
Any tips on how to de-bug this?
Thanks
Just read the docs: 3.1.4. Floating point and Currency fields, and think I see why it doesn't make sense
Thanks for the quick response. Would it make sense to have TDateTime stored as a floating point like in Delphi and have a separate type for ISO 8601 dates?
There doesn't seem an easy way to do an SQL select on a date range with TDateTime fields as it's stored as a string. What is the best field type to use in a table where I want to do date range searches?
Would it be possible to make TRecordEditForm embeddable in another TForm? Using this technique: http://stackoverflow.com/questions/4385 … ther-tform
The only obvious problem I can see is that the 'save' and 'cancel' buttons set the form's ModalResult
Also I'm getting a AV when creating a TRecordEditForm without a ribbon in line 248 of mORMotUIEdit. It needs something like:
if Ribbon = nil then RibbonParams := nil else RibbonParams := Ribbon.GetParameter(aRecord.RecordClass);I'm starting out on a new project with mormot and considering weather to use XE2 or 2007. I want unicode UI support so leaning towards XE2 but I'm used to and faster using 2007 and don't need any of the other new features of XE2 except unicode support.
The TMS unicode componenet pack is now only 30 Euros. If I got this could I use 2007 and show grids without the need to ctrl-click cells to see the unicode content? Are there any other things to consider?
btw the link in the docs about the MS ribbon control licensing now seems to say a license isn't required.
The content length is -1
It looks like the content should be read in at line 2439 of SynCRTSock but this just loops 3 times reading a empty string.
Just tried this again with Delphi 2007 and it works. But still have the problem above with XE2
I'm trying to use the getHTTP function in SynCRTSock like this:
memo1.Text := HttpGet('www.ecb.europa.eu', '80', '/stats/eurofxref/eurofxref-daily.xml');It's returning 200 OK but the content is empty (while a web browser works). Any suggestions on what's wrong - could it be because it's fetching an xml file?
I was wanting to find a way to search for similar images and came across this library www.phash.org
It creates a perceptual hash of an image. It also builds an MVP tree for queries but is there a way mORMot can do this
?
The queries are done using a hamming distance comparison which I initially thought could be done with an rtree but it doesn't look so easy nor that suitable for SQL. Also found a similar question here
Thank you, I'll investigate the best way
I got a service server, somewhat like sample 6, using HTTP.SYS
One of the services uses an object which is slow to create so it's created when the server starts and protected with a critical section.
This works but is there a way to create one instance of this object for each thread of the server and then access it from within the server's service function?
I like the idea of helpers for complex Db structures or wrapping them in an object.
Trees would be really helpful with the 'nested sets' implementation looking most useful for me:
I saw this post on stack-overflow about addressing more than 2GB of memory with 32-bit Delphi.
http://stackoverflow.com/questions/1849 … 64-bit-win
Has anyone had success doing this or is it unreliable?
Thanks, got it working now.
I'm having trouble with TSQLDataBase.Backup(const BackupFileName: TFileName). When run from within a service call it gives an ESQLLite3Exception with message 'cannot VACUUM - SQL statements in progress'.
It works ok if I call backup directly from the server.
Any suggestions on what's going wrong?
Thanks
type
TServiceServer = class(TSQLRestServerDB)
published
function doBackup(var aParams: TSQLRestServerCallBackParams): Integer;
end;
var
database: TServiceServer
implementation
function TServiceServer.doBackup(var aParams: TSQLRestServerCallBackParams): Integer;
begin
Database.DB.Backup('c:\data\backup.db3'); //FAILS
end;
function bk;
begin
Database.DB.Backup('c:\data\backup.db3'); //WORKS OK
end;I'm using a javascript client so I can't use TSQLRest.FTSMatch() client-side. It looks like a dedicated server-side service is what I need.
Thanks for the help.
I want to make a FTS query to a mORMot server using 'GET ModelRoot/TableName' but can't see how to access more than one table.
I can do it in a service using SQL like:
SELECT myRecord.* FROM myRecord, myFTSrecord WHERE myFTSrecord MATCH good stuff AND myRecord.ID = myFTSrecord.DocID;Is it possible to do it in a RESTful way using 'GET ModelRoot/TableName' with the URI function?
Thanks, got it now.
I'm trying to change from the old style services to interface-based sevices. With the mORMort samples the function delcarations change in sample 6 to 14 from/to:
function TServiceServer.Sum(var aParams: TSQLRestServerCallBackParams): Integer;
var a,b: Extended;
begin
if not UrlDecodeNeedParameters(aParams.Parameters,'A,B') then begin
result := 404; // invalid Request
aParams.ErrorMsg^ := 'Missing Parameter'; // custom error message
exit;
end;
while aParams.Parameters<>nil do begin
UrlDecodeExtended(aParams.Parameters,'A=',a);
UrlDecodeExtended(aParams.Parameters,'B=',b,@aParams.Parameters);
end;
aParams.Resp := JSONEncodeResult([a+b]);
// same as : aResp := JSONEncode(['result',a+b],TempMemoryStream);
result := 200; // success
end;
function TServiceCalculator.Add(n1, n2: integer): integer;
begin
result := n1+n2;
end;The problem I'm having is that I was using aParams to access TAuthSession.User.data and then creating a PDF file as the result and changing the aParams.header to the required content-type. With the interface-based services how can to access TSQLRestServerCallBackParams to do this?
Thanks for any help, I'm probably missing something obvious.
I can't get SynDB Explorer to compile.
It's giving these sorts of errors:
[DCC Error] SQLite3ToolBar.pas(2905): E2010 Incompatible types: 'Array' and 'TStringDynArray'
It looks like recent changes to SQLite3Pages.pas has broken calls in SQLite3ToolBar.pas to TGDIPages.AddColumnHeaders.
I had a brief look as well and it all looks very possible but I haven't done anything. There's another project which incorporates a javascript grid into backbone which could be worth a look: http://teleological.github.com/slickback/
I'd find this useful too in a development environment. It would allow a web client to be developed on a mac or separate PC while still communicating with a mORMort server running on Windows. It doesn't look like much work to add ![]()
The parsing error is happening in javascript (jquery ajax call). It seems that a number starting with zero isn't strictly valid JSON.
The data is coming from an EPOS system which puts an underscore in front of the number. I was stripping it out as I couldn't see the point of it but it's probably easier to just leave it in.
I'm trying to store a 14 digit barcode number as a RawUTF8 field. Occasionally a number will start with zero which causes a JSON parsing error.
As a work-around I could prefix all the numbers with an underscore but is there a better way?
Thanks
I'm using jquery's ajax function and in the end found that setting the option async:false solved it.
I'm trying to create a HTTP server with more than one SQLite DB using authentication. It's initilised using the code below but this gives the exception 'table AuthUser already exists' when the line DB3.CreateMissingTables(0); is called (each model has a different root).
How should I be handling authentication when there's more than one DB file?
Thanks
procedure TForm1.FormCreate(Sender: TObject);
begin
Model1 := CreateModel1;
Model2 := CreateModel2;
Model3 := CreateModel3;
DB1 := TSQLRestServerDB.Create(Model1,'C:\DATA\data1.db3',true);
DB2 := TSQLRestServerDB.Create(Model2,'C:\DATA\data2.db3',false);
DB3 := TSQLRestServerDB.Create(Model3,'C:\DATA\data3.db3',true);
DB1.CreateMissingTables(0);
DB2.CreateMissingTables(0);
DB3.CreateMissingTables(0);
Server := TSQlite3HttpServer.Create('8080',[DB1, DB2, DB3]);
end;Here's another delphi service implementation based on an example I found on the web. servFunctions.pas contains a TSQLRestServerDB derived class and the model. Works without a problem but not tested much.
{
NT Service model based completely on API calls. Version 0.1
Inspired by NT service skeleton from Aphex
Adapted by Runner
}
program myserv;
{$APPTYPE CONSOLE}
uses
Windows,
SysUtils,
WinSvc,
SQLite3,
SQLite3Commons,
SynCommons,
SQLite3HttpServer,
SynCrtSock,
servFunctions in 'servFunctions.pas';
const
ServiceName = 'MyServ';
DisplayName = 'My Server';
NUM_OF_SERVICES = 2;
var
ServiceStatus : TServiceStatus;
StatusHandle : SERVICE_STATUS_HANDLE;
ServiceTable : array [0..NUM_OF_SERVICES] of TServiceTableEntry;
Stopped : Boolean;
Paused : Boolean;
var
ghSvcStopEvent: Cardinal;
procedure ReportSvcStatus(dwCurrentState, dwWin32ExitCode, dwWaitHint: DWORD);
begin
// fill in the SERVICE_STATUS structure.
ServiceStatus.dwCurrentState := dwCurrentState;
ServiceStatus.dwWin32ExitCode := dwWin32ExitCode;
ServiceStatus.dwWaitHint := dwWaitHint;
case dwCurrentState of
SERVICE_START_PENDING: ServiceStatus.dwControlsAccepted := 0;
else
ServiceStatus.dwControlsAccepted := SERVICE_ACCEPT_STOP;
end;
case (dwCurrentState = SERVICE_RUNNING) or (dwCurrentState = SERVICE_STOPPED) of
True: ServiceStatus.dwCheckPoint := 0;
False: ServiceStatus.dwCheckPoint := 1;
end;
// Report the status of the service to the SCM.
SetServiceStatus(StatusHandle, ServiceStatus);
end;
procedure MainProc;
begin
// we have to do something or service will stop
ghSvcStopEvent := CreateEvent(nil, True, False, nil);
if ghSvcStopEvent = 0 then begin
ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
Exit;
end;
// Report running status when initialization is complete.
ReportSvcStatus( SERVICE_RUNNING, NO_ERROR, 0 );
Model := CreateModel;
DB := TEsServiceServer.Create(Model,'C:\DATA\ew3.db3',true);
DB.CreateMissingTables(0);
Server := TSQlite3HttpServer.Create('80',[DB]);
THttpApiServer(Server.HttpServer).AddUrl('','80',false,'+');
try
// Perform work until service stops.
while True do begin
// Check whether to stop the service.
WaitForSingleObject(ghSvcStopEvent, INFINITE);
ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
Exit;
end;
finally
Server.Free;
DB.Free;
Model.Free;
end;
end;
procedure ServiceCtrlHandler(Control: DWORD); stdcall;
begin
case Control of
SERVICE_CONTROL_STOP:
begin
Stopped := True;
SetEvent(ghSvcStopEvent);
ServiceStatus.dwCurrentState := SERVICE_STOP_PENDING;
SetServiceStatus(StatusHandle, ServiceStatus);
end;
SERVICE_CONTROL_PAUSE:
begin
Paused := True;
ServiceStatus.dwcurrentstate := SERVICE_PAUSED;
SetServiceStatus(StatusHandle, ServiceStatus);
end;
SERVICE_CONTROL_CONTINUE:
begin
Paused := False;
ServiceStatus.dwCurrentState := SERVICE_RUNNING;
SetServiceStatus(StatusHandle, ServiceStatus);
end;
SERVICE_CONTROL_INTERROGATE: SetServiceStatus(StatusHandle, ServiceStatus);
SERVICE_CONTROL_SHUTDOWN: Stopped := True;
end;
end;
procedure RegisterService(dwArgc: DWORD; var lpszArgv: PChar); stdcall;
begin
ServiceStatus.dwServiceType := SERVICE_WIN32_OWN_PROCESS;
ServiceStatus.dwCurrentState := SERVICE_START_PENDING;
ServiceStatus.dwControlsAccepted := SERVICE_ACCEPT_STOP or SERVICE_ACCEPT_PAUSE_CONTINUE;
ServiceStatus.dwServiceSpecificExitCode := 0;
ServiceStatus.dwWin32ExitCode := 0;
ServiceStatus.dwCheckPoint := 0;
ServiceStatus.dwWaitHint := 0;
StatusHandle := RegisterServiceCtrlHandler(ServiceName, @ServiceCtrlHandler);
if StatusHandle <> 0 then begin
ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
try
Stopped := False;
Paused := False;
MainProc;
finally
ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
end;
end;
end;
procedure UninstallService(const ServiceName: PChar; const Silent: Boolean);
const
cRemoveMsg = 'Your service was removed sucesfuly!';
var
SCManager: SC_HANDLE;
Service: SC_HANDLE;
begin
SCManager := OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);
if SCManager = 0 then
Exit;
try
Service := OpenService(SCManager, ServiceName, SERVICE_ALL_ACCESS);
ControlService(Service, SERVICE_CONTROL_STOP, ServiceStatus);
DeleteService(Service);
CloseServiceHandle(Service);
if not Silent then
MessageBox(0, cRemoveMsg, ServiceName, MB_ICONINFORMATION or MB_OK or MB_TASKMODAL or MB_TOPMOST);
finally
CloseServiceHandle(SCManager);
//AfterUninstall;
end;
end;
procedure InstallService(const ServiceName, DisplayName, LoadOrder: PChar;
const FileName: string; const Silent: Boolean);
const
cInstallMsg = 'Your service was Installed sucesfuly!';
cSCMError = 'Error trying to open SC Manager';
var
SCMHandle : SC_HANDLE;
SvHandle : SC_HANDLE;
begin
SCMHandle := OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);
if SCMHandle = 0 then begin
MessageBox(0, cSCMError, ServiceName, MB_ICONERROR or MB_OK or MB_TASKMODAL or MB_TOPMOST);
Exit;
end;
THttpApiServer.AddUrlAuthorize('','8080',false,'+');
try
SvHandle := CreateService(SCMHandle,
ServiceName,
DisplayName,
SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START,
SERVICE_ERROR_IGNORE,
pchar(FileName),
LoadOrder,
nil,
nil,
nil,// pchar('NT AUTHORITY\NetworkService'), // NT AUTHORITY\NetworkService
nil);
CloseServiceHandle(SvHandle);
if not Silent then
MessageBox(0, cInstallMsg, ServiceName, MB_ICONINFORMATION or MB_OK or MB_TASKMODAL or MB_TOPMOST);
finally
CloseServiceHandle(SCMHandle);
end;
end;
procedure WriteHelpContent;
begin
WriteLn('To install your service please type /install');
WriteLn('To uninstall your service please type /remove');
WriteLn('For help please type /? or /h');
end;
begin
if (ParamStr(1) = '/h') or (ParamStr(1) = '/?') then
WriteHelpContent
else if ParamStr(1) = '/install' then
InstallService(ServiceName, DisplayName, 'System Reserved', ParamStr(0), ParamStr(2) = '/s')
else if ParamStr(1) = '/remove' then
UninstallService(ServiceName, ParamStr(2) = '/s')
else if ParamCount = 0 then begin
//OnServiceCreate;
ServiceTable[0].lpServiceName := ServiceName;
ServiceTable[0].lpServiceProc := @RegisterService;
ServiceTable[1].lpServiceName := nil;
ServiceTable[1].lpServiceProc := nil;
StartServiceCtrlDispatcher(ServiceTable[0]);
end else
WriteLn('Wrong argument!');
end.Thanks, that solved it. I should have checked the older posts.
I'm having trouble running sample 10 - background http service. It seems to install ok, coming up in the list of services in the Computer Management Control panel, but trying to start it gives this error:
Could not start the mORMot Server Service service on Local Computer.
Error 1053: The service did not respond to the start or control request in a timely fashion.
Any suggestions on what could be going wrong? I'm using Delphi 2007 and get the problem on both windows XP and 7.
Thanks
BTW my code which caused the problem was:
Table := TSQLTableDB.Create(db,RecordClassesToClasses([TSQLRec, TSQLFTSRec]),SQLQuery,true);
aParams.Resp := Table.GetJSONValues(true);Looking at it again the function
function IsJSONString(P: PUTF8Char): boolean; in SynCommons.pas is returning false if '-' or '+' is passed.
can plus or minus on it's own be regarded as a string?
I might have got my code in a tangle.
As soon as I've time I'll put together an example.
Thanks
My browser if giving an 'invalid number' parsing error. It's being caused by a rawUTF8 field containing a single minus sign. I guess this is being treated as an integer so the quotes are missing. Can this be changed?
I haven't got any experience translating api headers but may try this as a learning exercise. I've noticed the JEDI project do some tutorials.
Still haven't properly looked at TSynLog so will do that first.
Have you got any plans to implement http logging like this doc describes http://msdn.microsoft.com/en-us/library … 85%29.aspx?
With the 403 Forbidden issue there was only a couple of miliseconds between two http requests and they seemed to occasionally arrive at the server in a swapped order. I've solved it by forcing a pause between the javascript requests.
Great, thanks got it working.
I've got another problem, probably unrelated, with authentication timestamp coherency. The server and client are on the same computer (mormot server and firefox client) and about 20% of the time I'm getting 403 errors when sending two requests immediately after each other (to be processed together in javascript). There's no problem if the server and client are on different machines (so it's not much of an issue) or if I add 500ms leeway to TAuthSession.IsValidURL when they're on the same machine. Is there a limit to how close timestamps are in the signature? Below is the log from the firebug console when it isn't working - strangely some of the requests are responded to in negative time which must be impossible, but this also occasionally happens when the requests go through without a problem.
GET http://localhost:8080/root/TimeStamp 200 OK 164ms
GET http://localhost:8080/root/auth?UserName=Guest 200 OK -6ms
GET http://localhost:8080/root/auth?UserName=Guest&Password=83bf9bc2feb7c4133125eef1021a5e142bd2636b3ee1b135b57f86555fd4425d&ClientNonce=32d8ca66ae1537411d2f1940cb52e1f136af024c4ac98f402ecf8cccc1f260f2 OK -19ms
GET http://localhost:8080/root/EWPicRec?Select=count(*)+as+total&Where=&session_signature=0000006f00000208ee735494 403 Forbidden 13ms
GET http://localhost:8080/root/EWPicRec?Select=*&StartIndex=0&Results=51&Where=&session_signature=0000006f00000211a6d19bd3 200 OK 15ms
btw do you mean OnRequest instead of OnProcess.
What does this note above THttpApiServer.Clone mean?
// - will work only if the OnProcess property was set
I cann't see any other reference to OnProcess.
It's also mentioned on page 266 of the SAD documentation.
I'm not sure it's on the client side as I'm testing with two clients on separate computers (using http://blueimp.github.com/jQuery-File-Upload/). The server (using http.sys) is multi-threaded while it processes the request but only seems able to handle recieving requests in series. It's puzzling - can you suggest where I'm going wrong?
Below is the start of my server function - does the session access look OK?
Thanks
function TServiceServer.upload(aSession: Cardinal; aRecord: TSQLRecord; aParameters: PUTF8Char;
const aSentData: RawUTF8; var aResp, aHead: RawUTF8): Integer;
var
sess : TAuthSession;
hasAccess : boolean;
begin
EnterCriticalSection(fSessionCriticalSection);
try
sess := SessionAccess(aSession);
hasAccess := (1 in sess.AccessRights.POST);
finally
LeaveCriticalSection(fSessionCriticalSection);
end;
if hasAccess then begin
...
end;
end;Could Post requests be made possible with the SOA part of the framework?
I want to upload a jpeg file, using an html form, from a javascript client. I've had a go changing the http method in TSQLite3HttpServer.Request from 'post' to 'get' forcing TSQLRestServer.URI to process the request. This works OK but uploading a large file over a slow connection blocks any other requests to the server until the transfer's finished. Is there a simple way to do this without blocking the server?
Is it just the fashion?
Oberon 2 also implements garbage collection. From my superficial understanding it seems to add complexity for questionable benefits. But why does a non-commercial language so adverse to 'bells and whistles' that it drops the 'for' loop decide to implement garbage collection ![]()
It's not directly relevant but I've just come across this interesting talk by Niklaus Wirth from earlier this year. Someone mentions at the end that the GUI operating system he created is around 200kb - somewhat smaller than OSX or Windows.
Thanks for such a quick response.
It doesn't seem that easy to set up the AuthGroup table rights. Would it be worth adding a function like below to TSQLAuthGroup?
procedure TSQLAuthGroup.EditTableRights(TableIndex: integer; C, R, U, D: boolean);
var
A: TSQLAccessRights;
begin
A := SQLAccessRights;
if not C then
Exclude(A.PUT, TableIndex);
if not R then
Exclude(A.GET, TableIndex);
if not U then
Exclude(A.POST, TableIndex);
if not D then
Exclude(A.DELETE, TableIndex);
SQLAccessRights := A;
end;Here's a slightly tidier version of my javascript authentication adding some error handling and ideas above.
RangerX - thanks for pointing out $.ajaxPrefilter and using localStorage. I'll probably do something similar soon.
I assume there's no point adding salt to the password hash in javascript as it would be plainly visible.
var SynAuth = {
User : "",
fRoot : "",
fSessionID : 0,
fSessionIDHexa8 : "",
fSessionPrivateKey : 0,
fSessionTickCountOffset : 0,
fLastSessionTickCount : 0,
PasswordHashHexa : "",
fServerTimeStampOffset : 0,
fcallBack : null,
ffailCallBack : null
}; // SynAuth namespace
SynAuth.LogIn = function (root, username, password, callback, failCallback){
SynAuth.fRoot = root;
SynAuth.User = username;
SynAuth.PasswordHashHexa = Sha256.hash(""+password);
if (callback) {SynAuth.fcallBack = callback;}
if (failCallback) {SynAuth.ffailCallback = failCallback;}
$.get("/"+root+"/TimeStamp", SynAuth.gotTimeStamp);
}
SynAuth.LogInAgain = function(callback){ //after timeout error for silent re-login
SynAuth.fSessionID = 0;
SynAuth.fSessionIDHexa8 = "";
SynAuth.fSessionPrivateKey = 0;
if (callback) {SynAuth.fcallBack = callback;} else {SynAuth.fcallBack = null;}
$.get("/"+SynAuth.fRoot+"/TimeStamp", SynAuth.gotTimeStamp);
}
SynAuth.gotTimeStamp = function (timestamp) {
var s = '', d = new Date(), clientTime = '';
timestamp = parseInt(timestamp);
s = d.getFullYear().toString(2);
while(s.length < 13) { s = '0'+s;}
clientTime = s;
s = d.getMonth().toString(2);
while(s.length < 4) { s = '0'+s;}
clientTime = clientTime +s;
s = (d.getDate()-1).toString(2);
while(s.length < 5) { s = '0'+s;}
clientTime = clientTime +s;
s = d.getHours().toString(2);
while(s.length < 5) { s = '0'+s;}
clientTime = clientTime +s;
s = d.getMinutes().toString(2);
while(s.length < 6) { s = '0'+s;}
clientTime = clientTime +s;
s = d.getSeconds().toString(2);
while(s.length < 6) { s = '0'+s;}
clientTime = clientTime +s;
SynAuth.fServerTimeStampOffset = (timestamp - parseInt(clientTime,2));
$.get("/"+SynAuth.fRoot+"/auth?UserName="+SynAuth.User, SynAuth.gotNonce);
}
SynAuth.gotNonce = function (aNonce){
//create client nonce
var aClientNonce = "", s = "", d = new Date();
aClientNonce = d.getFullYear().toString();
s = d.getMonth().toString();
if (s.length === 1) { s = '0'+s;}
aClientNonce = aClientNonce + '-' + s;
s = d.getDate().toString();
if (s.length === 1) { s = '0'+s;}
aClientNonce = aClientNonce + '-' + s + ' ';
s = d.getHours().toString();
if (s.length === 1) { s = '0'+s;}
aClientNonce = aClientNonce + s;
s = d.getMinutes().toString();
if (s.length === 1) { s = '0'+s;}
aClientNonce = aClientNonce + ':' + s;
s = d.getSeconds().toString();
if (s.length === 1) { s = '0'+s;}
aClientNonce = aClientNonce + ':' + s;
aClientNonce = Sha256.hash(aClientNonce);
s = "/"+SynAuth.fRoot+"/auth?UserName="+SynAuth.User+"&Password=" +
Sha256.hash( SynAuth.fRoot+aNonce.result+aClientNonce+SynAuth.User+SynAuth.PasswordHashHexa )+
"&ClientNonce="+aClientNonce;
$.ajax({
type: "GET",
dataType: "json",
url: s,
success: SynAuth.gotSession,
error: SynAuth.ffailCallback});
};
SynAuth.gotSession = function (aSessionKey){
var i = aSessionKey.result.indexOf("+");
SynAuth.fSessionID = parseInt(aSessionKey.result.slice(0, i));
SynAuth.fSessionIDHexa8 = SynAuth.fSessionID.toString(16);
while(SynAuth.fSessionIDHexa8.length < 8) { SynAuth.fSessionIDHexa8 = '0'+SynAuth.fSessionIDHexa8; }
SynAuth.fSessionPrivateKey = SynAuth.crc32(SynAuth.PasswordHashHexa, SynAuth.crc32(aSessionKey.result, 0));
if (SynAuth.fcallBack != null) { SynAuth.fcallBack(); }
}
SynAuth.SessionSign = function (url) {
var Tix, Nonce, s, ss, d = new Date();
Tix = d.getTime();
if (SynAuth.fLastSessionTickCount == Tix) {Tix = Tix + 1;}
SynAuth.fLastSessionTickCount = Tix;
Nonce = Tix.toString(16);
while(Nonce.length < 8) { Nonce = '0'+Nonce; }
if (Nonce.length > 8) { Nonce = Nonce.slice(Nonce.length-8) }
ss = SynAuth.crc32(url, SynAuth.crc32(Nonce, SynAuth.fSessionPrivateKey)).toString(16);
while(ss.length < 8) { ss = '0'+ss; }
s = url.indexOf("?") == -1 ? url+'?session_signature=' : url+'&session_signature=';
return s + SynAuth.fSessionIDHexa8 + Nonce + ss;
}
SynAuth.Logout = function (callback) {
if (SynAuth.fSessionID == 0) {if (callback){callback();}} else {
$.get("/"+SynAuth.fRoot+"/auth?UserName="+SynAuth.User+"&Session="+SynAuth.fSessionID, callback);
SynAuth.fRoot = '';
SynAuth.User = '';
SynAuth.fSessionID = 0;
SynAuth.fSessionIDHexa8 = "";
SynAuth.fSessionPrivateKey = 0;
}
}
/*
CRC-32 (as it is in ZMODEM) in table form
Copyright (C) 1986 Gary S. Brown. You may use this program, or
code or tables extracted from it, as desired without restriction.
Modified by Anders Danielsson, February 5, 1989 and March 10, 2006.
This is also known as FCS-32 (as it is in PPP), described in
RFC-1662 by William Allen Simpson, see RFC-1662 for references.
*/
SynAuth.Crc32Tab = new Array( /* CRC polynomial 0xEDB88320 */
0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3,
0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD,0xE7B82D07,0x90BF1D91,
0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D,0x6DDDE4EB,0xF4D4B551,0x83D385C7,
0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC,0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5,
0x3B6E20C8,0x4C69105E,0xD56041E4,0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,
0x35B5A8FA,0x42B2986C,0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59,
0x26D930AC,0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F,
0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB,0xB6662D3D,
0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F,0x9FBFE4A5,0xE8B8D433,
0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB,0x086D3D2D,0x91646C97,0xE6635C01,
0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E,0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457,
0x65B0D9C6,0x12B7E950,0x8BBEB8EA,0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65,
0x4DB26158,0x3AB551CE,0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB,
0x4369E96A,0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9,
0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409,0xCE61E49F,
0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81,0xB7BD5C3B,0xC0BA6CAD,
0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739,0x9DD277AF,0x04DB2615,0x73DC1683,
0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8,0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1,
0xF00F9344,0x8708A3D2,0x1E01F268,0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7,
0xFED41B76,0x89D32BE0,0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5,
0xD6D6A3E8,0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B,
0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF,0x4669BE79,
0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703,0x220216B9,0x5505262F,
0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7,0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D,
0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A,0x9C0906A9,0xEB0E363F,0x72076785,0x05005713,
0x95BF4A82,0xE2B87A14,0x7BB12BAE,0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21,
0x86D3D2D4,0xF1D4E242,0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,
0x88085AE6,0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45,
0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D,0x3E6E77DB,
0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5,0x47B2CF7F,0x30B5FFE9,
0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605,0xCDD70693,0x54DE5729,0x23D967BF,
0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94,0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D);
SynAuth.Crc32Add = function (crc,c)
/*
'crc' should be initialized to 0xFFFFFFFF and after the computation it should be
complemented (inverted).
CRC-32 is also known as FCS-32.
If the FCS-32 is calculated over the data and over the complemented FCS-32, the
result will always be 0xDEBB20E3 (without the complementation).
*/
{
return SynAuth.Crc32Tab[(crc^c)&0xFF]^((crc>>8)&0xFFFFFF);
}
SynAuth.crc32 = function (str, crc)
{
var n;
var len=str.length;
if (typeof(crc) == "undefined") { crc = 0xFFFFFFFF; }
else {
crc = crc^0xFFFFFFFF; //crc = ~crc;
//remove sign to emulate delphi's 32bit cardinal
if (crc < 0) {
crc = 4294967296 + crc;
}
}
for (n=0; n<len; n++)
{
crc=SynAuth.Crc32Add(crc,str.charCodeAt(n));
}
crc = crc^0xFFFFFFFF; //crc = ~crc;
if (crc < 0) {
crc = 4294967296 + crc;
}
return crc;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* SHA-256 implementation in JavaScript | (c) Chris Veness 2002-2010 | www.movable-type.co.uk */
/* - see http://csrc.nist.gov/groups/ST/toolkit/secure_hashing.html */
/* http://csrc.nist.gov/groups/ST/toolkit/examples.html */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var Sha256 = {}; // Sha256 namespace
/**
* Generates SHA-256 hash of string
*
* @param {String} msg String to be hashed
* @param {Boolean} [utf8encode=true] Encode msg as UTF-8 before generating hash
* @returns {String} Hash of msg as hex character string
*/
Sha256.hash = function(msg, utf8encode) {
utf8encode = (typeof utf8encode == 'undefined') ? true : utf8encode;
// convert string to UTF-8, as SHA only deals with byte-streams
if (utf8encode) msg = Utf8.encode(msg);
// constants [§4.2.2]
var K = [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2];
// initial hash value [§5.3.1]
var H = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];
// PREPROCESSING
msg += String.fromCharCode(0x80); // add trailing '1' bit (+ 0's padding) to string [§5.1.1]
// convert string msg into 512-bit/16-integer blocks arrays of ints [§5.2.1]
var l = msg.length/4 + 2; // length (in 32-bit integers) of msg + '1' + appended length
var N = Math.ceil(l/16); // number of 16-integer-blocks required to hold 'l' ints
var M = new Array(N);
for (var i=0; i<N; i++) {
M[i] = new Array(16);
for (var j=0; j<16; j++) { // encode 4 chars per integer, big-endian encoding
M[i][j] = (msg.charCodeAt(i*64+j*4)<<24) | (msg.charCodeAt(i*64+j*4+1)<<16) |
(msg.charCodeAt(i*64+j*4+2)<<8) | (msg.charCodeAt(i*64+j*4+3));
} // note running off the end of msg is ok 'cos bitwise ops on NaN return 0
}
// add length (in bits) into final pair of 32-bit integers (big-endian) [§5.1.1]
// note: most significant word would be (len-1)*8 >>> 32, but since JS converts
// bitwise-op args to 32 bits, we need to simulate this by arithmetic operators
M[N-1][14] = ((msg.length-1)*8) / Math.pow(2, 32); M[N-1][14] = Math.floor(M[N-1][14])
M[N-1][15] = ((msg.length-1)*8) & 0xffffffff;
// HASH COMPUTATION [§6.1.2]
var W = new Array(64); var a, b, c, d, e, f, g, h;
for (var i=0; i<N; i++) {
// 1 - prepare message schedule 'W'
for (var t=0; t<16; t++) W[t] = M[i][t];
for (var t=16; t<64; t++) W[t] = (Sha256.sigma1(W[t-2]) + W[t-7] + Sha256.sigma0(W[t-15]) + W[t-16]) & 0xffffffff;
// 2 - initialise working variables a, b, c, d, e, f, g, h with previous hash value
a = H[0]; b = H[1]; c = H[2]; d = H[3]; e = H[4]; f = H[5]; g = H[6]; h = H[7];
// 3 - main loop (note 'addition modulo 2^32')
for (var t=0; t<64; t++) {
var T1 = h + Sha256.Sigma1(e) + Sha256.Ch(e, f, g) + K[t] + W[t];
var T2 = Sha256.Sigma0(a) + Sha256.Maj(a, b, c);
h = g;
g = f;
f = e;
e = (d + T1) & 0xffffffff;
d = c;
c = b;
b = a;
a = (T1 + T2) & 0xffffffff;
}
// 4 - compute the new intermediate hash value (note 'addition modulo 2^32')
H[0] = (H[0]+a) & 0xffffffff;
H[1] = (H[1]+b) & 0xffffffff;
H[2] = (H[2]+c) & 0xffffffff;
H[3] = (H[3]+d) & 0xffffffff;
H[4] = (H[4]+e) & 0xffffffff;
H[5] = (H[5]+f) & 0xffffffff;
H[6] = (H[6]+g) & 0xffffffff;
H[7] = (H[7]+h) & 0xffffffff;
}
return Sha256.toHexStr(H[0]) + Sha256.toHexStr(H[1]) + Sha256.toHexStr(H[2]) + Sha256.toHexStr(H[3]) +
Sha256.toHexStr(H[4]) + Sha256.toHexStr(H[5]) + Sha256.toHexStr(H[6]) + Sha256.toHexStr(H[7]);
}
Sha256.ROTR = function(n, x) { return (x >>> n) | (x << (32-n)); }
Sha256.Sigma0 = function(x) { return Sha256.ROTR(2, x) ^ Sha256.ROTR(13, x) ^ Sha256.ROTR(22, x); }
Sha256.Sigma1 = function(x) { return Sha256.ROTR(6, x) ^ Sha256.ROTR(11, x) ^ Sha256.ROTR(25, x); }
Sha256.sigma0 = function(x) { return Sha256.ROTR(7, x) ^ Sha256.ROTR(18, x) ^ (x>>>3); }
Sha256.sigma1 = function(x) { return Sha256.ROTR(17, x) ^ Sha256.ROTR(19, x) ^ (x>>>10); }
Sha256.Ch = function(x, y, z) { return (x & y) ^ (~x & z); }
Sha256.Maj = function(x, y, z) { return (x & y) ^ (x & z) ^ (y & z); }
//
// hexadecimal representation of a number
// (note toString(16) is implementation-dependant, and
// in IE returns signed numbers when used on full words)
//
Sha256.toHexStr = function(n) {
var s="", v;
for (var i=7; i>=0; i--) { v = (n>>>(i*4)) & 0xf; s += v.toString(16); }
return s;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Utf8 class: encode / decode between multi-byte Unicode characters and UTF-8 multiple */
/* single-byte character encoding (c) Chris Veness 2002-2010 */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var Utf8 = {}; // Utf8 namespace
/**
* Encode multi-byte Unicode string into utf-8 multiple single-byte characters
* (BMP / basic multilingual plane only)
*
* Chars in range U+0080 - U+07FF are encoded in 2 chars, U+0800 - U+FFFF in 3 chars
*
* @param {String} strUni Unicode string to be encoded as UTF-8
* @returns {String} encoded string
*/
Utf8.encode = function(strUni) {
// use regular expressions & String.replace callback function for better efficiency
// than procedural approaches
var strUtf = strUni.replace(
/[\u0080-\u07ff]/g, // U+0080 - U+07FF => 2 bytes 110yyyyy, 10zzzzzz
function(c) {
var cc = c.charCodeAt(0);
return String.fromCharCode(0xc0 | cc>>6, 0x80 | cc&0x3f); }
);
strUtf = strUtf.replace(
/[\u0800-\uffff]/g, // U+0800 - U+FFFF => 3 bytes 1110xxxx, 10yyyyyy, 10zzzzzz
function(c) {
var cc = c.charCodeAt(0);
return String.fromCharCode(0xe0 | cc>>12, 0x80 | cc>>6&0x3F, 0x80 | cc&0x3f); }
);
return strUtf;
}
/**
* Decode utf-8 encoded string back into multi-byte Unicode characters
*
* @param {String} strUtf UTF-8 string to be decoded back to Unicode
* @returns {String} decoded string
*/
Utf8.decode = function(strUtf) {
// note: decode 3-byte chars first as decoded 2-byte strings could appear to be 3-byte char!
var strUni = strUtf.replace(
/[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g, // 3-byte chars
function(c) { // (note parentheses for precence)
var cc = ((c.charCodeAt(0)&0x0f)<<12) | ((c.charCodeAt(1)&0x3f)<<6) | ( c.charCodeAt(2)&0x3f);
return String.fromCharCode(cc); }
);
strUni = strUni.replace(
/[\u00c0-\u00df][\u0080-\u00bf]/g, // 2-byte chars
function(c) { // (note parentheses for precence)
var cc = (c.charCodeAt(0)&0x1f)<<6 | c.charCodeAt(1)&0x3f;
return String.fromCharCode(cc); }
);
return strUni;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */What I can't work out is where to do this update. TSQLRestServer.SessionGet deletes outdated sessions - so I want to do an update when this happens. Is there a way without editing mORMots's source?
Is there a way to save custom Data in TSQLAuthUser when a session closes?
I've been using a web browser as a client to TSQLite3HttpServer. It's been working fine but after a mormot session timeout ajax fails silently on msie.
I'm guessing it's a browser cache problem and maybe an 'Expires' http header would help. Does this sound sensible and if so where would be the best place to implement it?
Apparently the expires header needs to be the same as the date header to stop caching but http.sys is setting the date header so it's hard to see how to do it.