You are not logged in.
Btw, are the any examples for using dddInfraApps.pas? It looks interesting. Thanks
@ab yes, I think I got the wrong end of the stick. This delphi looks like an ERP from Oracle.
Enterprise Resource Planning (ERP) from SAP and Oracle and more recently from www.odoo.com seem like an attractive alternative to company boards. They seem to be targeting their current marketing to SMEs. I guess the main benefit of our Delphi is the 'tailor made' aspect, perhaps especially in the area of UI.
If you want something to support Delphi in general this review could be useful:
http://www.ncua.gov/about/Leadership/CO … phiSAP.pdf
Apparently this USA government department saved almost $500000 in annual SAP licences by moving to Delphi and also got a system which their users found better than anything they'd used before.
I'm getting a corrupted file too ![]()
Which link are you using to download it?
It seems to work for me.
Just found the problem is only with firefox.
Chrome seems to be automatically replacing the backslashes with forward slashes
most of the links in this section
http://synopse.info/files/html/Synopse% … #TITLE_210
give a 404 not found error
In the online docs at
Synopse mORMot Framework SAD 1.18.html
the link to RecordSaveJSON gives a 404 error.
Looks like the url has a backslash which should be a forward slash:
Thanks ![]()
There's probably the same issue on lines
36307
36335
I'm trying to use JSONDecode to consume some JSON with null values. If it encounters a null it breaks off the decoding due to this line in SynCommons.pas
36274 if (Value=nil) or not(EndOfObject in [',','}']) then
exit; // invalid item separatorCan it be changed to:
36274 if not(EndOfObject in [',','}']) then
exit; // invalid item separatorOne way could be to use:
https://github.com/blueimp/jQuery-File-Upload
It has an option 'sequentialUploads' which divides up a request into single part multipart/form-data requests. That should make it fairly easy to extract the file from the posted data by just stripping out the headers and two boundary strings. But it wouldn't completely handle multipart/form-data requests.
Thanks for the quick answer.
I've noticed that TSQLHttpServer still responds to non SSL requests on port 80. Is there a way to stop this?
Sorry, this statement was incorrect.
I'd forgotten to delete my old HTTPServer creation code, so in my code I should have deleted the first of these two lines:
aHTTPServer := TSQLHttpServer.Create('80',[aServer]);
aHTTPServer := TSQLHttpServer.Create('443',[aServer],'+',useHttpApiRegisteringURI,32,secSSL);However, if I want to enable both SSL and plain connections is the above thread safe? ie having two TSQLHttpServer using one TSQLRestServer (yes, above I need to rename the second HttpServer.)
I've just set up SSL on my server and it works great!
btw when using windows 2012 the IIS Manager can simply install the certificate and registered it without having to use makecert and netsh as described in the docs (was using a free certificate from www.startssl.com)
I've noticed that TSQLHttpServer still responds to non SSL requests on port 80. Is there a way to stop this?
I guess that could be useful otherwise the default filename for the client is taken from the url which is probably the service name.
In which case the function could become:
procedure TSQLRestServerURIContext.ReturnFile(const FileName: TFileName;
Handle304NotModified: boolean; const ContentType: RawUTF8; const AttachmentFileName: RawUTF8{=''});
var FileTime: TDateTime;
clientHash, serverHash: RawUTF8;
begin
FileTime := FileAgeToDateTime(FileName);
if FileTime=0 then
Error('',HTML_NOTFOUND) else begin
if ContentType<>'' then
Call.OutHead := HEADER_CONTENT_TYPE+ContentType else
Call.OutHead := HEADER_CONTENT_TYPE+GetMimeContentType(nil,0,FileName);
Call.OutStatus := HTML_SUCCESS;
if Handle304NotModified then begin
clientHash := FindIniNameValue(pointer(Call.InHead),'IF-NONE-MATCH: ');
serverHash := '"'+DateTimeToIso8601(FileTime,false)+'"';
Call.OutHead := Call.OutHead+#13#10'ETag: '+serverHash;
if clientHash=serverHash then begin
Call.OutStatus := HTML_NOTMODIFIED;
exit;
end;
end;
// Content-Type: appears twice: 1st to notify static file, 2nd for mime type
Call.OutHead := STATICFILE_CONTENT_TYPE_HEADER+#13#10+Call.OutHead;
if AttachmentFileName <> '' then
Call.OutHead := Call.OutHead+#13#10+'Content-Disposition: attachment; filename="'+AttachmentFileName+'"';
StringToUTF8(FileName,Call.OutBody);
end;
end;Thanks, I thought there could be an easier.
Could TSQLRestServerURIContext.ReturnFile in mORMot.pas be extended so that additional http headers can be added? I want to add 'Content-Disposition: attachment; filename="myfile.ext"'.
The modified function below with an extra 'additionalHeader' parameter seems to work. Can this feature be added?
procedure TSQLRestServerURIContext.ReturnFile(const FileName: TFileName;
Handle304NotModified: boolean; const ContentType: RawUTF8; const AdditionalHeader: RawUTF8{=''});
var FileTime: TDateTime;
clientHash, serverHash: RawUTF8;
begin
FileTime := FileAgeToDateTime(FileName);
if FileTime=0 then
Error('',HTML_NOTFOUND) else begin
if ContentType<>'' then
Call.OutHead := HEADER_CONTENT_TYPE+ContentType else
Call.OutHead := HEADER_CONTENT_TYPE+GetMimeContentType(nil,0,FileName);
Call.OutStatus := HTML_SUCCESS;
if Handle304NotModified then begin
clientHash := FindIniNameValue(pointer(Call.InHead),'IF-NONE-MATCH: ');
serverHash := '"'+DateTimeToIso8601(FileTime,false)+'"';
Call.OutHead := Call.OutHead+#13#10'ETag: '+serverHash;
if clientHash=serverHash then begin
Call.OutStatus := HTML_NOTMODIFIED;
exit;
end;
end;
// Content-Type: appears twice: 1st to notify static file, 2nd for mime type
Call.OutHead := STATICFILE_CONTENT_TYPE_HEADER+#13#10+Call.OutHead;
if AdditionalHeader <> '' then
Call.OutHead := Call.OutHead+#13#10+AdditionalHeader;
StringToUTF8(FileName,Call.OutBody);
end;
end;Thanks a lot for implementing this.
I've found a few bugs which should be fixed by changing the lines below in mORMot.pas
I haven't got Microsoft Office to test it but it now passes this ODF validator:
https://odf-validator.rhcloud.com/
17590 ODSContentHeader: RawUTF8 = '<office:document-content office:version="1.2" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"'+
17597 ODSsettings: RawUTF8 = XMLUTF8_HEADER+'<office:document-settings xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" office:version="1.2"></office:document-settings>';
17622 W.Add(FieldCount);Thanks, I've got this working. Yes, mimetype was wrong and it was missing the manifest.xml.
Only problem now is McAfee complains of 'Potentially annoying download detected' in firefox. I guess this is just a false positive but could it be due to the http headers which are:
'Content-Type:application/vnd.oasis.opendocument.spreadsheet; name="download.ods'+#13#10+'Content-Disposition: attachment; filename="download.ods"'
It's probably not the most elegant but the code below works.
const //ODS boilerplate files
//mimetype
ODSmimetype = 'application/vnd.oasis.opendocument.spreadsheet';
ODSXMLHeader = '<?xml version="1.0" encoding="UTF-8"?>';
//content.xml
ODSContentHeader = '<office:document-content xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"'+
' xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" ><office:body><office:spreadsheet><table:table table:name="Sheet1" ><table:table-column table:number-columns-repeated="';
ODSContentFooter = '</table:table><table:named-expressions/></office:spreadsheet></office:body></office:document-content>';
//styles.xml
ODSstyles = ODSXMLHeader+'<office:document-styles xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" office:version="1.2"></office:document-styles>';
//meta.xml
ODSmeta = ODSXMLHeader+'<office:document-meta xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" office:version="1.2"></office:document-meta>';
//settings.xml
ODSsettings = ODSXMLHeader+'<office:document-settings xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" office:version="1.2"><office:settings>';
//manifest.xml
ODSmanifest = ODSXMLHeader+'<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0" manifest:version="1.2"><manifest:file-entry manifest:full-path="/" manifest:version="1.2" manifest:media-type="application/vnd.oasis.opendocument.spreadsheet"/>'+
'<manifest:file-entry manifest:full-path="meta.xml" manifest:media-type="text/xml"/><manifest:file-entry manifest:full-path="settings.xml" manifest:media-type="text/xml"/>'+
'<manifest:file-entry manifest:full-path="content.xml" manifest:media-type="text/xml"/><manifest:file-entry manifest:full-path="styles.xml" manifest:media-type="text/xml"/></manifest:manifest>';
procedure TSQLTable.GetODSContent(Dest: TMemoryStream);
var U: PPUTF8Char;
F,R: integer;
W: TTextWriter;
Z: TZipWrite;
TempFile: array[0..MAX_PATH] of Char;
begin
GetTempPath(SizeOf(TempFile) - 1, TempFile);
GetTempFileName(TempFile, '~', 0, TempFile);
Z := TZipWrite.Create(TempFile);
Z.AddStored('mimetype', PAnsiChar(ODSmimetype), length(ODSmimetype));
Z.AddDeflated('styles.xml', PAnsiChar(ODSstyles), length(ODSstyles));
Z.AddDeflated('meta.xml', PAnsiChar(ODSmeta), length(ODSmeta));
Z.AddDeflated('settings.xml', PAnsiChar(ODSsettings), length(ODSsettings));
Z.AddDeflated('META-INF/manifest.xml', PAnsiChar(ODSmanifest), length(ODSmanifest));
W := TTextWriter.Create(Dest,16384);
try
W.AddString(ODSXMLHeader);
W.AddCR;
W.AddString(ODSContentHeader);
W.Add(RowCount);
W.AddShort('" />');
U := pointer(fResults);
for R := 0 to RowCount do begin
W.AddShort('<table:table-row>');
for F := 0 to FieldCount-1 do begin
W.AddShort('<table:table-cell office:value-type="string"><text:p>');
W.AddXmlEscape(U^);
W.AddShort('</text:p></table:table-cell>');
inc(U); // points to next value
end;
W.AddShort('</table:table-row>');
end;
W.AddShort(ODSContentFooter);
W.Flush;
Z.AddDeflated('content.xml', PAnsiChar(W.Text), length(W.Text));
Dest.LoadFromFile(TempFile);
finally
Z.Free;
W.Free;
DeleteFile(TempFile);
end;
end;CSV doesn't handle line breaks in a field very well and maybe a minimalist ods export from TSQLTable could be a useful alternative.
Based on TSQLTable.GetCSVValues below is a function that produces the content.xml spreadsheet file of a .ods zipped file. (.ods files are generally a zip file conataining mainly xml files).
Could this be added to mORMot? Using a file saved by LibreOffice as a template I've got the boiler-plate xml down to under 1k.
I'm still working on the zipping side. Could someone help here? Ideally I want to extend the function to return a complete zipped open document file. mORMot's documentation is generally great but the part on zipping is a bit terse. I'd be grateful for any help which saves me having to work it out myself. This is the file structure of the zip file needed:
mimetype
content.xml
styles.xml
meta.xml
settings.xml
META-INF/manifest.xml
I've looked at TZipWrite but this writes to disc. Is there an equivalent which returns a memory stream?
Using windows built-in zipping the function below makes a file which is read ok by Libra Office.
reference links:
http://www.forensicswiki.org/wiki/Open_Document_Format
http://what-when-how.com/how-to-build-a … ry-part-2/
http://mashupguide.net/1.0/html/ch17s03.xhtml
const //ODS boilerplate files
ODSXMLHeader = '<?xml version="1.0" encoding="UTF-8"?>';
//content.xml
ODSContentHeader = '<office:document-content xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"'+
' xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" ><office:body><office:spreadsheet><table:table table:name="Sheet1" ><table:table-column table:number-columns-repeated="';
ODSContentFooter = '</table:table><table:named-expressions/></office:spreadsheet></office:body></office:document-content>';
//styles.xml
ODSstyles = ODSXMLHeader+'<office:document-styles xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" office:version="1.2"></office:document-styles>';
//meta.xml
ODSmeta = ODSXMLHeader+'<office:document-meta xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" office:version="1.2"></office:document-meta>';
//settings.xml
ODSsettings = ODSXMLHeader+'<office:document-settings xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" office:version="1.2"><office:settings>';
//mimetype
ODSmimetype = ODSXMLHeader+'application/vnd.oasis.opendocument.spreadsheet';
procedure TSQLTable.GetODSContent(Dest: TStream);
var U: PPUTF8Char;
F,R: integer;
W: TTextWriter;
begin
W := TTextWriter.Create(Dest,16384);
try
W.AddString(ODSXMLHeader);
W.AddCR;
W.AddString(ODSContentHeader);
W.Add(RowCount);
W.AddShort('" />');
U := pointer(fResults);
for R := 0 to RowCount do begin
W.AddShort('<table:table-row>');
for F := 0 to FieldCount-1 do begin
W.AddShort('<table:table-cell office:value-type="string"><text:p>');
W.AddXmlEscape(U^);
W.AddShort('</text:p></table:table-cell>');
inc(U); // points to next value
end;
W.AddShort('</table:table-row>');
end;
W.AddShort(ODSContentFooter);
finally
W.Flush;
W.Free;
end;
end;It would just be convenient to be able to select between expanded/unexpanded in the client request.
But both formats are fine. Here's a jquery plugin which detects the format and feeds it into an html table:
(function ( $ ) {
$.fn.jsonToTable = function( JSONobj ) {
var i, ii, rowCount, fieldCount, tableRow, fields,
table = this;
if (table.find("thead").length == 0) {
table.append($("<thead></thead>").append("<tr></tr>"));
}
if (table.find("thead").find("tr").length == 0) {
table.find("thead").append("<tr></tr>");
}
table.find("thead").find("tr").empty();
if (table.find("tbody").length == 0) {
table.append($("<tbody></tbody>"));
}
table.find("tbody").empty();
if(JSONobj.hasOwnProperty("fieldCount")){
fieldCount = JSONobj.fieldCount;
for (i = 0; i < fieldCount; i++) {
table.find("thead").find("tr").append("<th>"+JSONobj.values[i]+"</th>");
};
rowCount = JSONobj.rowCount;
for (i = 0; i < rowCount; i++) {
tableRow = $("<tr></tr>");//.addClass(rowClass);
for ( ii = 0; ii < fieldCount; ii++) {
tableRow.append($("<td>" + JSONobj.values[ii+((i+1)*fieldCount)] + "</td>"));
};
table.append(tableRow);
};
} else if (JSONobj.values.length > 0) {
fields = Object.keys(JSONobj.values[0]);
fieldCount = fields.length;
rowCount = JSONobj.values.length;
for (i = 0; i < fieldCount; i++) {
table.find("thead").find("tr").append("<th>"+fields[i]+"</th>");
};
for (i = 0; i < rowCount; i++) {
tableRow = $("<tr></tr>");//.addClass(rowClass);
for ( ii = 0; ii < fieldCount; ii++) {
tableRow.append($("<td>" + JSONobj.values[i][fields[ii]] + "</td>"));
};
table.append(tableRow);
};
}
return table;
};
}( jQuery ));Could there be another switch, set by the HTTP header, to select between the expanded and non-expanded mORMot JSON formats?
I've been experimenting with javascript clients and it's not that difficult to expand the JSON response client side and this would give the benefit of the terser format for transmission.
There are some references to http://developer.yahoo.com/yui/datatable/#data in mORMot.pas for the YUI DataSource Request Syntax.
This now seems to have moved to:
http://yui.github.io/yui2/docs/yui_2.9. … .html#data
And I guess "if" can be an error. Thanks
Would it be worth adding a separate built in function to return basic model info or just field names? It could be accessed with a URI such as http://host/root/tablename/fieldnames
Okay but I would prefer the expanded format to return just an empty array if the result is empty. Then a javascript client wouldn't have to do a typeof check to see if the result is an object or array. This could be done by removing lines 4185-4192 in SynSQLite3.pas:
if (result=0) and W.Expand then begin
// we want the field names at least, even with no data: we allow RowCount=0
W.Expand := false; // {"FieldCount":2,"Values":["col1","col2"]}
W.CancelAll;
for i := 0 to FieldCount-1 do
W.ColNames[i] := sqlite3.column_name(Request,i);
W.AddColumns;
end;I can't see this breaking standard mORMot clients.
I understand the different formats but sample 4 has NoAJAXJSON set to false so it should be using the expanded format. However, using EngineList on the client gets a response in the non expanded format if result is an empty list otherwise the response is expanded. This seems inconsistent.
When retrieving a record with sample 4 the raw JSON response from the server is something like:
[{"ID":1,"Time":135192192214,"Name":"Tim","Question":"Hello world"}]but if a record isn't found instead of an empty JSON array which I expected you get an object:
{"fieldCount":4,"values":["ID","Time","Name","Question"],"rowCount":0}Is this right? It uses the ExecuteList function which line 12331 of mORMot.pas says "/// Execute directly a SQL statement, expecting a list of resutls"
Here's a go at a typescript version. It's very much an experiment and untested but could be a good starting point for someone.
I've created a sample web page which emulates the sample 4 client. It's a bit of a hack as there isn't any client side ORM but basic functions work.
It uses a 'singleton' design which made it simpler to write but restricts it to one active login at a time - there's probably a better way. On the plus
side it has no JavaScript library dependencies. Any feedback welcome.
mORMotClient.ts
module mORMot {
function getXhr(): XMLHttpRequest {
for(var i=0; i<4; i++){
try{
return i ?
new ActiveXObject([, "Msxml2", "Msxml3", "Microsoft"][i] + ".XMLHTTP")
: new XMLHttpRequest;
}
catch(e){
// ignore when it fails.
}
}
}
export interface logInOutCallback {
(success: boolean, data?: string): void;
}
export interface onServerRespose {
(HTTPstatus: number, data?:{} ) : void;
}
export class Client {
private static _instance: Client; //singleton
private static allowInstantiation:Boolean;
public static getInstance():Client{
if (Client._instance == null) {
Client.allowInstantiation = true;
Client._instance = new Client();
Client.allowInstantiation = false;
}
return Client._instance;
}
private User : string = "";
private Root : string = "";
private ServerURL : string = "";
private loggedIn : boolean = false;
private SessionID : number = 0;
private SessionIDHexa8 : string = "";
private SessionPrivateKey : number = 0;
private SessionTickCountOffset : number = 0;
private PasswordHashHexa : string = "";
private ServerTimeStampOffset : number = 0;
private LoginCallback : logInOutCallback;
private LogoutCallback : logInOutCallback;
constructor() {
if (!Client.allowInstantiation) {
throw new Error("Error: Instantiation failed: Use mORMot.Client.getInstance() instead of new.");
}
}
//
// hexadecimal representation of a number
// (note toString(16) is implementation-dependant, and
// in IE returns signed numbers when used on full words)
//
private Sha256toHexStr(n: number): string{
var s="", v, i;
for (i=7; i>=0; i = i -1) { 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
*/
private Utf8encode(strUni: string): string {
// 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
*/
private Utf8decode(strUtf: string): string {
// 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;
}
private Sha256ROTR(n, x) { return (x >>> n) | (x << (32-n)); }
private Sha256Sigma0(x) { return this.Sha256ROTR(2, x) ^ this.Sha256ROTR(13, x) ^ this.Sha256ROTR(22, x); }
private Sha256Sigma1(x) { return this.Sha256ROTR(6, x) ^ this.Sha256ROTR(11, x) ^ this.Sha256ROTR(25, x); }
private Sha256sigma0(x) { return this.Sha256ROTR(7, x) ^ this.Sha256ROTR(18, x) ^ (x>>>3); }
private Sha256sigma1(x) { return this.Sha256ROTR(17, x) ^ this.Sha256ROTR(19, x) ^ (x>>>10); }
private Sha256Ch(x, y, z) { return (x & y) ^ (~x & z); }
private Sha256Maj(x, y, z) { return (x & y) ^ (x & z) ^ (y & z); }
private Sha256hash(msg: string, utf8encode?: boolean): string {
var K,H,l,N,M,i,j,W,a, b, c, d, e, f, g, h, t, T1, T2;
utf8encode = (typeof utf8encode == 'undefined') ? true : utf8encode;
// convert string to UTF-8, as SHA only deals with byte-streams
if (utf8encode) {msg = this.Utf8encode(msg);}
// constants [§4.2.2]
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]
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]
l = msg.length/4 + 2; // length (in 32-bit integers) of msg + ‘1’ + appended length
N = Math.ceil(l/16); // number of 16-integer-blocks required to hold 'l' ints
M = [N];//new Array(N);
for (i=0; i<N; i = i+1) {
M[i] = new Array(16);
for (j=0; j<16; j = j +1) { // 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]
W = new Array(64);
for (i=0; i<N; i = i+1) {
// 1 - prepare message schedule 'W'
for (t=0; t<16; t = t+1) {W[t] = M[i][t];}
for (t=16; t<64; t = t+1) {W[t] = (this.Sha256sigma1(W[t-2]) + W[t-7] + this.Sha256sigma0(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 (t=0; t<64; t = t+1) {
T1 = h + this.Sha256Sigma1(e) + this.Sha256Ch(e, f, g) + K[t] + W[t];
T2 = this.Sha256Sigma0(a) + this.Sha256Maj(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 this.Sha256toHexStr(H[0]) + this.Sha256toHexStr(H[1]) + this.Sha256toHexStr(H[2]) + this.Sha256toHexStr(H[3]) +
this.Sha256toHexStr(H[4]) + this.Sha256toHexStr(H[5]) + this.Sha256toHexStr(H[6]) + this.Sha256toHexStr(H[7]);
}
/*
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.
*/
private Crc32Tab = [ /* 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];
private Crc32Add(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 Client._instance.Crc32Tab[(crc^c)&0xFF]^((crc>>8)&0xFFFFFF);
}
private crc32(str, crc)
{
var n, len;
len = str.length;
//var crc;
if (typeof(crc) == "undefined") { crc = 0xFFFFFFFF; }
else {
crc = crc^0xFFFFFFFF; //crc = ~crc;
if (crc < 0) {
crc = 4294967296 + crc;
}
}
//crc=0xFFFFFFFF;
for (n=0; n<len; n = n+1) {
crc = Client._instance.Crc32Add(crc,str.charCodeAt(n));
}
crc = crc^0xFFFFFFFF; //crc = ~crc;
if (crc < 0) {
crc = 4294967296 + crc;
}
return crc;//^0xFFFFFFFF;
}
public logIn(url: string, root: string, username: string, password: string, callbackFunc?: logInOutCallback) {
var SynAuth = this, d = new Date();
SynAuth.ServerURL = url;
SynAuth.Root = root;
SynAuth.User = username;
SynAuth.PasswordHashHexa = SynAuth.Sha256hash("salt" + password);
SynAuth.SessionTickCountOffset = d.getTime();
if (callbackFunc) { SynAuth.LoginCallback = callbackFunc; }
var XHR: XMLHttpRequest = getXhr();
XHR.open("GET", SynAuth.ServerURL + "/" + SynAuth.Root + "/TimeStamp");
XHR.onreadystatechange = SynAuth.onGotTimestamp;
XHR.send(null);
}
public logout(callbackFunc?: logInOutCallback): void {
if (callbackFunc) { Client._instance.LogoutCallback = callbackFunc; }
var XHR: XMLHttpRequest = getXhr();
XHR.open("GET", Client._instance.ServerURL +"/"+ Client._instance.signUrl(Client._instance.Root+"/Auth?UserName="+Client._instance.User
+"&Session="+Client._instance.SessionID));
//XHR.onreadystatechange = Client._instance.onLogout;
XHR.send(null);
}
public logoutSync(): boolean {
var XHR: XMLHttpRequest = getXhr();
XHR.open("GET", Client._instance.ServerURL +"/"+ Client._instance.signUrl(Client._instance.Root+"/Auth?UserName="+Client._instance.User
+"&Session="+Client._instance.SessionID), false);
XHR.send(null);
var success: boolean = (XHR.status >= 200 && XHR.status < 300)
if (success) {
Client._instance.ServerURL = '';
Client._instance.Root = '';
Client._instance.User = '';
Client._instance.SessionID = 0;
Client._instance.SessionIDHexa8 = '';
Client._instance.SessionPrivateKey = 0;
Client._instance.LoginCallback = null;
Client._instance.LogoutCallback = null;
}
return success;
}
public signUrl(url: string): string {
if (Client._instance.loggedIn === true) {
var Tix, Nonce, s, ss, d = new Date();
Tix = d.getTime() - Client._instance.SessionTickCountOffset;
Nonce = Tix.toString(16);
while(Nonce.length < 8) { Nonce = '0'+Nonce; }
if (Nonce.length > 8) { Nonce = Nonce.slice(Nonce.length-8); }
ss = Client._instance.crc32(url, Client._instance.crc32(Nonce, Client._instance.SessionPrivateKey)).toString(16);
while(ss.length < 8) { ss = '0'+ss; }
s = url.indexOf("?") === -1 ? url+'?session_signature=' : url+'&session_signature=';
return s + Client._instance.SessionIDHexa8 + Nonce + ss;
} else {
return url;
}
}
public retrieveRecordWhere(table:string, select: string, where: string, callback?: onServerRespose) {
var XHR: XMLHttpRequest = getXhr(),
s: string = Client._instance.ServerURL+'/'+Client._instance.signUrl(Client._instance.Root + '/' + table +
'?select=' + select + '&where=' +where+'&limit=1');
XHR.onreadystatechange = Client._instance.onRetrieveRecordCallback;
XHR.open("GET", s);
XHR.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
XHR.send(null);
}
public retrieveRecord(table:string, id:number, forUpdate:boolean) {
var XHR: XMLHttpRequest = getXhr();
XHR.onreadystatechange = Client._instance.onRetrieveRecordCallback;
var s: string = Client._instance.ServerURL + "/" + Client._instance.signUrl(Client._instance.Root + "/" + table + "/" + id);
if (!forUpdate) {
XHR.open("GET", s);
} else {
XHR.open("LOCK", s);
}
XHR.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
XHR.send(null);
}
public addRecord(table:string, record) {
var XHR: XMLHttpRequest = getXhr();
var s: string = Client._instance.ServerURL+"/"+Client._instance.signUrl(Client._instance.Root + "/" + table);
XHR.onreadystatechange = Client._instance.onAddRecordCallback;
XHR.open("POST", s);
XHR.send(JSON.stringify(record || {}));
}
public unlockRecord(table: string, id: number) {
//UNLOCK
var s: string = Client._instance.ServerURL+"/"+Client._instance.signUrl(Client._instance.Root + "/" + table + "/" + id);
var XHR: XMLHttpRequest = getXhr();
XHR.open("UNLOCK", "/" + s);
XHR.onreadystatechange = Client._instance.onUnlockRecordCallback;
XHR.send(null);
}
//Login callbacks
private onGotTimestamp = function(ev: Event) {
if (this.readyState == 4) {
if (this.status !== 200) {
if(Client._instance.LoginCallback instanceof Function) {
Client._instance.LoginCallback(false, this.status + " " + this.statusText);
}
} else {
var s:string = '', d = new Date(), clientTime: string = '';
var timestamp: number = parseInt(this.responseText, 10);
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;
Client._instance.ServerTimeStampOffset = (timestamp - parseInt(clientTime,2));
var XHR: XMLHttpRequest = getXhr();//new XMLHttpRequest();
XHR.open("GET", Client._instance.ServerURL + "/" + Client._instance.Root + "/Auth?UserName="+Client._instance.User, true);
XHR.onreadystatechange = Client._instance.onGotNonce;
XHR.send(null);
XHR.ontimeout = function () { alert("timeout!");}
}
}
}
private onGotNonce = function(ev: Event) {
if (this.readyState == 4) {
if (this.status !== 200) {
if(Client._instance.LoginCallback instanceof Function) {
Client._instance.LoginCallback(false, this.status + " " + this.statusText);
}
} else {
//create client nonce
var aClientNonce = "", s: string = "", 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 = Client._instance.Sha256hash(aClientNonce);
var data = JSON.parse(this.responseText);
s = Client._instance.ServerURL + "/"+Client._instance.Root+"/Auth?UserName="+Client._instance.User+"&Password=" +
Client._instance.Sha256hash(Client._instance.Root+data.result+aClientNonce+Client._instance.User+Client._instance.PasswordHashHexa) +
"&ClientNonce="+aClientNonce;
var XHR: XMLHttpRequest = getXhr();
XHR.open("GET", s, true);
XHR.onreadystatechange = Client._instance.onGotSession;
XHR.send(null);
}
}
}
private onGotSession = function(ev: Event) {
if (this.readyState == 4) {
if (this.status !== 200) {
if(Client._instance.LoginCallback instanceof Function) {
Client._instance.LoginCallback(false, this.status + " " + this.statusText);
}
} else {
var data = JSON.parse(this.responseText);
var i = data.result.indexOf("+");
Client._instance.SessionID = parseInt(data.result.slice(0, i), 10);
Client._instance.SessionIDHexa8 = Client._instance.SessionID.toString(16);
while(Client._instance.SessionIDHexa8.length < 8) { Client._instance.SessionIDHexa8 = '0'+Client._instance.SessionIDHexa8; }
Client._instance.loggedIn = true;
Client._instance.SessionPrivateKey = Client._instance.crc32(Client._instance.PasswordHashHexa, Client._instance.crc32(data.result, 0));
if(Client._instance.LoginCallback instanceof Function) {
Client._instance.LoginCallback(true, Client._instance.User);
}
}
}
}
private onLogout = function (ev: Event) {
if (this.readyState == 4) {
var success: boolean = (this.status >= 200 && this.status < 300);
if (Client._instance.LogoutCallback instanceof Function) {
Client._instance.LogoutCallback(success, "");
}
if (success) {
Client._instance.ServerURL = '';
Client._instance.Root = '';
Client._instance.User = '';
Client._instance.SessionID = 0;
Client._instance.SessionIDHexa8 = "";
Client._instance.SessionPrivateKey = 0;
Client._instance.LoginCallback = null;
Client._instance.LogoutCallback = null;
}
}
}
private onRetrieveRecordCallback = function (ev: Event) {
if (this.readyState == 4) {
//var success = (this.status >= 200 && this.status < 300);
var obj = JSON.parse(this.response) || {}; //JSON.parse(this.responseText || {});
if (Client._instance.onRetrieveRecord instanceof Function){//onRetrieveRecord
Client._instance.onRetrieveRecord(this.status, obj);
}
}
}
private onAddRecordCallback = function (ev: Event) {
if (this.readyState == 4) {
if (Client._instance.onAddRecord instanceof Function) {
Client._instance.onAddRecord(this.status, this.getResponseHeader('Location'));
}
}
}
private onUnlockRecordCallback = function (ev: Event) {
if (this.readyState == 4) {
var success = (this.status >= 200 && this.status < 300);
if(Client._instance.onUnlockRecord instanceof Function){
Client._instance.onUnlockRecord(success);
}
}
}
//public events
public onRetrieveRecord: (HTTPstatus: number, data:{}) => void;
public onAddRecord: (HTTPstatus: number, recordLocation: string) => void;
public onUnlockRecord: (success: boolean) => void;
}
}mORMotClient.js
var mORMot;
(function (mORMot) {
function getXhr() {
for (var i = 0; i < 4; i++) {
try {
return i ? new ActiveXObject([, "Msxml2", "Msxml3", "Microsoft"][i] + ".XMLHTTP") : new XMLHttpRequest;
} catch (e) {
// ignore when it fails.
}
}
}
var Client = (function () {
function Client() {
this.User = "";
this.Root = "";
this.ServerURL = "";
this.loggedIn = false;
this.SessionID = 0;
this.SessionIDHexa8 = "";
this.SessionPrivateKey = 0;
this.SessionTickCountOffset = 0;
this.PasswordHashHexa = "";
this.ServerTimeStampOffset = 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.
*/
this.Crc32Tab = [
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];
//Login callbacks
this.onGotTimestamp = function (ev) {
if (this.readyState == 4) {
if (this.status !== 200) {
if (Client._instance.LoginCallback instanceof Function) {
Client._instance.LoginCallback(false, this.status + " " + this.statusText);
}
} else {
var s = '', d = new Date(), clientTime = '';
var timestamp = parseInt(this.responseText, 10);
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;
Client._instance.ServerTimeStampOffset = (timestamp - parseInt(clientTime, 2));
var XHR = getXhr();
XHR.open("GET", Client._instance.ServerURL + "/" + Client._instance.Root + "/Auth?UserName=" + Client._instance.User, true);
XHR.onreadystatechange = Client._instance.onGotNonce;
XHR.send(null);
XHR.ontimeout = function () {
alert("timeout!");
};
}
}
};
this.onGotNonce = function (ev) {
if (this.readyState == 4) {
if (this.status !== 200) {
if (Client._instance.LoginCallback instanceof Function) {
Client._instance.LoginCallback(false, this.status + " " + this.statusText);
}
} else {
//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 = Client._instance.Sha256hash(aClientNonce);
var data = JSON.parse(this.responseText);
s = Client._instance.ServerURL + "/" + Client._instance.Root + "/Auth?UserName=" + Client._instance.User + "&Password=" +
Client._instance.Sha256hash(Client._instance.Root + data.result + aClientNonce + Client._instance.User + Client._instance.PasswordHashHexa) +
"&ClientNonce=" + aClientNonce;
var XHR = getXhr();
XHR.open("GET", s, true);
XHR.onreadystatechange = Client._instance.onGotSession;
XHR.send(null);
}
}
};
this.onGotSession = function (ev) {
if (this.readyState == 4) {
if (this.status !== 200) {
if (Client._instance.LoginCallback instanceof Function) {
Client._instance.LoginCallback(false, this.status + " " + this.statusText);
}
} else {
var data = JSON.parse(this.responseText);
var i = data.result.indexOf("+");
Client._instance.SessionID = parseInt(data.result.slice(0, i), 10);
Client._instance.SessionIDHexa8 = Client._instance.SessionID.toString(16);
while (Client._instance.SessionIDHexa8.length < 8) {
Client._instance.SessionIDHexa8 = '0' + Client._instance.SessionIDHexa8;
}
Client._instance.loggedIn = true;
Client._instance.SessionPrivateKey = Client._instance.crc32(Client._instance.PasswordHashHexa, Client._instance.crc32
(data.result, 0));
if (Client._instance.LoginCallback instanceof Function) {
Client._instance.LoginCallback(true, Client._instance.User);
}
}
}
};
this.onLogout = function (ev) {
if (this.readyState == 4) {
var success = (this.status >= 200 && this.status < 300);
if (Client._instance.LogoutCallback instanceof Function) {
Client._instance.LogoutCallback(success, "");
}
if (success) {
Client._instance.ServerURL = '';
Client._instance.Root = '';
Client._instance.User = '';
Client._instance.SessionID = 0;
Client._instance.SessionIDHexa8 = "";
Client._instance.SessionPrivateKey = 0;
Client._instance.LoginCallback = null;
Client._instance.LogoutCallback = null;
}
}
};
this.onRetrieveRecordCallback = function (ev) {
if (this.readyState == 4) {
//var success = (this.status >= 200 && this.status < 300);
var obj = JSON.parse(this.response) || {};
if (Client._instance.onRetrieveRecord instanceof Function) {
Client._instance.onRetrieveRecord(this.status, obj);
}
}
};
this.onAddRecordCallback = function (ev) {
if (this.readyState == 4) {
if (Client._instance.onAddRecord instanceof Function) {
Client._instance.onAddRecord(this.status, this.getResponseHeader('Location'));
}
}
};
this.onUnlockRecordCallback = function (ev) {
if (this.readyState == 4) {
var success = (this.status >= 200 && this.status < 300);
if (Client._instance.onUnlockRecord instanceof Function) {
Client._instance.onUnlockRecord(success);
}
}
};
if (!Client.allowInstantiation) {
throw new Error("Error: Instantiation failed: Use mORMot.Client.getInstance() instead of new.");
}
}
Client.getInstance = function () {
if (Client._instance == null) {
Client.allowInstantiation = true;
Client._instance = new Client();
Client.allowInstantiation = false;
}
return Client._instance;
};
//
// hexadecimal representation of a number
// (note toString(16) is implementation-dependant, and
// in IE returns signed numbers when used on full words)
//
Client.prototype.Sha256toHexStr = function (n) {
var s = "", v, i;
for (i = 7; i >= 0; i = i - 1) {
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
*/
Client.prototype.Utf8encode = function (strUni) {
// use regular expressions & String.replace callback function for better efficiency
// than procedural approaches
var strUtf = strUni.replace(/[\u0080-\u07ff]/g, function (c) {
var cc = c.charCodeAt(0);
return String.fromCharCode(0xc0 | cc >> 6, 0x80 | cc & 0x3f);
});
strUtf = strUtf.replace(/[\u0800-\uffff]/g, 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
*/
Client.prototype.Utf8decode = 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, function (c) {
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, function (c) {
var cc = (c.charCodeAt(0) & 0x1f) << 6 | c.charCodeAt(1) & 0x3f;
return String.fromCharCode(cc);
});
return strUni;
};
Client.prototype.Sha256ROTR = function (n, x) {
return (x >>> n) | (x << (32 - n));
};
Client.prototype.Sha256Sigma0 = function (x) {
return this.Sha256ROTR(2, x) ^ this.Sha256ROTR(13, x) ^ this.Sha256ROTR(22, x);
};
Client.prototype.Sha256Sigma1 = function (x) {
return this.Sha256ROTR(6, x) ^ this.Sha256ROTR(11, x) ^ this.Sha256ROTR(25, x);
};
Client.prototype.Sha256sigma0 = function (x) {
return this.Sha256ROTR(7, x) ^ this.Sha256ROTR(18, x) ^ (x >>> 3);
};
Client.prototype.Sha256sigma1 = function (x) {
return this.Sha256ROTR(17, x) ^ this.Sha256ROTR(19, x) ^ (x >>> 10);
};
Client.prototype.Sha256Ch = function (x, y, z) {
return (x & y) ^ (~x & z);
};
Client.prototype.Sha256Maj = function (x, y, z) {
return (x & y) ^ (x & z) ^ (y & z);
};
Client.prototype.Sha256hash = function (msg, utf8encode) {
var K, H, l, N, M, i, j, W, a, b, c, d, e, f, g, h, t, T1, T2;
utf8encode = (typeof utf8encode == 'undefined') ? true : utf8encode;
// convert string to UTF-8, as SHA only deals with byte-streams
if (utf8encode) {
msg = this.Utf8encode(msg);
}
// constants [§4.2.2]
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]
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]
l = msg.length / 4 + 2; // length (in 32-bit integers) of msg + ‘1’ + appended length
N = Math.ceil(l / 16); // number of 16-integer-blocks required to hold 'l' ints
M = [N]; //new Array(N);
for (i = 0; i < N; i = i + 1) {
M[i] = new Array(16);
for (j = 0; j < 16; j = j + 1) {
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));
}
}
// 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]
W = new Array(64);
for (i = 0; i < N; i = i + 1) {
for (t = 0; t < 16; t = t + 1) {
W[t] = M[i][t];
}
for (t = 16; t < 64; t = t + 1) {
W[t] = (this.Sha256sigma1(W[t - 2]) + W[t - 7] + this.Sha256sigma0(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];
for (t = 0; t < 64; t = t + 1) {
T1 = h + this.Sha256Sigma1(e) + this.Sha256Ch(e, f, g) + K[t] + W[t];
T2 = this.Sha256Sigma0(a) + this.Sha256Maj(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 this.Sha256toHexStr(H[0]) + this.Sha256toHexStr(H[1]) + this.Sha256toHexStr(H[2]) + this.Sha256toHexStr(H[3]) + this.Sha256toHexStr(H
[4]) + this.Sha256toHexStr(H[5]) + this.Sha256toHexStr(H[6]) + this.Sha256toHexStr(H[7]);
};
Client.prototype.Crc32Add = function (crc, c) {
return Client._instance.Crc32Tab[(crc ^ c) & 0xFF] ^ ((crc >> 8) & 0xFFFFFF);
};
Client.prototype.crc32 = function (str, crc) {
var n, len;
len = str.length;
//var crc;
if (typeof (crc) == "undefined") {
crc = 0xFFFFFFFF;
} else {
crc = crc ^ 0xFFFFFFFF; //crc = ~crc;
if (crc < 0) {
crc = 4294967296 + crc;
}
}
for (n = 0; n < len; n = n + 1) {
crc = Client._instance.Crc32Add(crc, str.charCodeAt(n));
}
crc = crc ^ 0xFFFFFFFF; //crc = ~crc;
if (crc < 0) {
crc = 4294967296 + crc;
}
return crc;
};
Client.prototype.logIn = function (url, root, username, password, callbackFunc) {
var SynAuth = this, d = new Date();
SynAuth.ServerURL = url;
SynAuth.Root = root;
SynAuth.User = username;
SynAuth.PasswordHashHexa = SynAuth.Sha256hash("salt" + password);
SynAuth.SessionTickCountOffset = d.getTime();
if (callbackFunc) {
SynAuth.LoginCallback = callbackFunc;
}
var XHR = getXhr();
XHR.open("GET", SynAuth.ServerURL + "/" + SynAuth.Root + "/TimeStamp");
XHR.onreadystatechange = SynAuth.onGotTimestamp;
XHR.send(null);
};
Client.prototype.logout = function (callbackFunc) {
if (callbackFunc) {
Client._instance.LogoutCallback = callbackFunc;
}
var XHR = getXhr();
XHR.open("GET", Client._instance.ServerURL + "/" + Client._instance.signUrl(Client._instance.Root + "/Auth?UserName=" + Client._instance.User
+ "&Session=" + Client._instance.SessionID));
//XHR.onreadystatechange = Client._instance.onLogout;
XHR.send(null);
};
Client.prototype.logoutSync = function () {
var XHR = getXhr();
XHR.open("GET", Client._instance.ServerURL + "/" + Client._instance.signUrl(Client._instance.Root + "/Auth?UserName=" + Client._instance.User
+ "&Session=" + Client._instance.SessionID), false);
XHR.send(null);
var success = (XHR.status >= 200 && XHR.status < 300);
if (success) {
Client._instance.ServerURL = '';
Client._instance.Root = '';
Client._instance.User = '';
Client._instance.SessionID = 0;
Client._instance.SessionIDHexa8 = '';
Client._instance.SessionPrivateKey = 0;
Client._instance.LoginCallback = null;
Client._instance.LogoutCallback = null;
}
return success;
};
Client.prototype.signUrl = function (url) {
if (Client._instance.loggedIn === true) {
var Tix, Nonce, s, ss, d = new Date();
Tix = d.getTime() - Client._instance.SessionTickCountOffset;
Nonce = Tix.toString(16);
while (Nonce.length < 8) {
Nonce = '0' + Nonce;
}
if (Nonce.length > 8) {
Nonce = Nonce.slice(Nonce.length - 8);
}
ss = Client._instance.crc32(url, Client._instance.crc32(Nonce, Client._instance.SessionPrivateKey)).toString(16);
while (ss.length < 8) {
ss = '0' + ss;
}
s = url.indexOf("?") === -1 ? url + '?session_signature=' : url + '&session_signature=';
return s + Client._instance.SessionIDHexa8 + Nonce + ss;
} else {
return url;
}
};
Client.prototype.retrieveRecordWhere = function (table, select, where, callback) {
var XHR = getXhr(), s = Client._instance.ServerURL + '/' + Client._instance.signUrl(Client._instance.Root + '/' + table + '?select=' + select
+ '&where=' + where + '&limit=1');
XHR.onreadystatechange = Client._instance.onRetrieveRecordCallback;
XHR.open("GET", s);
XHR.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
XHR.send(null);
};
Client.prototype.retrieveRecord = function (table, id, forUpdate) {
var XHR = getXhr();
XHR.onreadystatechange = Client._instance.onRetrieveRecordCallback;
var s = Client._instance.ServerURL + "/" + Client._instance.signUrl(Client._instance.Root + "/" + table + "/" + id);
if (!forUpdate) {
XHR.open("GET", s);
} else {
XHR.open("LOCK", s);
}
XHR.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
XHR.send(null);
};
Client.prototype.addRecord = function (table, record) {
var XHR = getXhr();
var s = Client._instance.ServerURL + "/" + Client._instance.signUrl(Client._instance.Root + "/" + table);
XHR.onreadystatechange = Client._instance.onAddRecordCallback;
XHR.open("POST", s);
XHR.send(JSON.stringify(record || {}));
};
Client.prototype.unlockRecord = function (table, id) {
//UNLOCK
var s = Client._instance.ServerURL + "/" + Client._instance.signUrl(Client._instance.Root + "/" + table + "/" + id);
var XHR = getXhr();
XHR.open("UNLOCK", "/" + s);
XHR.onreadystatechange = Client._instance.onUnlockRecordCallback;
XHR.send(null);
};
return Client;
})();
mORMot.Client = Client;
})(mORMot || (mORMot = {}));sample4client.html
<!DOCTYPE html>
<html>
<head>
<title>mORMot sample 4 client web app</title>
</head>
<body>
<h2>mORMot sample 4 client web app</h2>
<div id="content"></div>
<br>
<form>
<button type="button" id="login">
log in
</button>
<button type="button" id="logout" >
log out
</button>
<br><br>
Your name: <input type="text" id="name">
<button type="button" id="retrieveRecord" >
Find a previous message
</button><br>
Your message:<br>
<textarea style="width:400px" rows="5" id="message"></textarea><br>
<button type="button" id="addRecord" >
Add the message
</button>
</form>
<script src="mORMotClient.js"></script>
<script src="sample4.js"></script>
</body>
</html>sample4.js
var mORMotClient = mORMot.Client.getInstance();
mORMotClient.onRetrieveRecord = onGetRecord;
mORMotClient.onAddRecord = onAddRec;
function onlogin(success, data) {
document.getElementById("content").innerHTML = "login status: " + success + ' ' + data;
}
function onAddRec(httpStatus, location) {
document.getElementById("content").innerHTML = "http status: " + httpStatus + ' Location:' + location;
}
function loginClickHandler(ev) {
mORMotClient.logIn("http://localhost:8080", "root", "User", "synopse", onlogin);
}
function addRecClickHandler(ev) {
var rec = { Time: 0,
Name: document.getElementById("name").value,
Question: document.getElementById("message").value
};
mORMotClient.addRecord("SampleRecord", rec);
}
function onGetRecord(HTTPValue, rec) {
try {
document.getElementById("message").value = rec[0].Question;
} catch(e) {
document.getElementById("message").value = "";
}
}
function retrieveRecClickHandler(ev) {
mORMotClient.retrieveRecordWhere("SampleRecord","ID,Time,Name,Question","Name=:(%27"+encodeURIComponent(document.getElementById
("name").value)+"%27):");
}
function logoutClickHandler(ev) {
mORMotClient.logout();
}
document.getElementById("login").onclick = loginClickHandler;
document.getElementById("addRecord").onclick = addRecClickHandler;
document.getElementById("retrieveRecord").onclick = retrieveRecClickHandler;
document.getElementById("logout").onclick = logoutClickHandler;Many thanks for implementing this. As soon as I've managed to make some money from my endeavours I'll make a donation.
I think think the main thing that makes mORMot sustainable is it's KISS attitude. The problem is it's not mainstream so managers will bring up the 'what if the main developer gets run over by a bus' problem but it's gaining popularity and being KISS makes it easier for others to take up the baton.
I get the same problem with Delphi 2007 ![]()
re value hosting:
Speaking on the 'phone to ikoula they were very helpful but warned their 1 Euro/month windows plan could escalate to 100 Euro if you go too far over the bandwidth limit. But it looks great for a test server. I'm looking at atlantic.net which seem to offer very good value.
From what I remember I added the starting "/" to the url root as it messed up the hash if included.
The full version of Acrobat has a preflight feature which might find the problem.
As a first step would it make sense to extract the string and JSON handling routines out of SynCommons to a separate unit? I've found this set of routines seem to compile fine under Lazarus on OSX and I guess are fairly platform agnostic. It then shouldn't be too much work to get a basic OSX client up and running.
This may be a half-baked idea but could these string and JSON routines be put in a .inc file. This could then be included in SynCommons.pas but also in a new unit called, eg, SynSrings.pas so it wouldn't be a breaking change and people could migrate gradually to new unit names.
btw this feature is great for parsing MQL query responses from www.freebase.com (hope the plug doesn't get pulled on this one)
Thanks for the speedy fix ![]()
I my situation I'm using a different string each time.
I modified SynSelfTests so the lines above are run twice consecutively and it gives an AV ![]()
I've just been experimenting with TTextWriter.RegisterCustomJSONSerializerFromText and using RecordLoadJSON which works great except if it is called more than once. The second time RecordLoadJSON is called it causes an AV while calling FinalizeNestedRecord.
This can be seen if you repeat lines 4035 - 4041 in SynSelfTests.pas more than once (copied below)
Thanks in advance for your help
U := '{"transactions":[{"TRTYPE":"INCOME","TRDATE":"2013-12-09 02:30:04","TRAA":"1.23",'+
'"TRCAT1":{"TITYPE":"C1","TIID":"1","TICID":"","TIDSC30":"description1","TIORDER":"0","TIDEL":"false"},'+
'"TRCAT2":{"TITYPE":"C2","TIID":"2","TICID":"","TIDSC30":"description2","TIORDER":"0","TIDEL":"false"},'+
'"TRCAT3":{"TITYPE":"C3","TIID":"3","TICID":"","TIDSC30":"description3","TIORDER":"0","TIDEL":"false"},'+
'"TRRMK":"Remark",'+
'"TRACID":{"TITYPE":"AC","TIID":"4","TICID":"","TIDSC30":"account1","TIORDER":"0","TIDEL":"false"}}]}';
RecordLoadJSON(Trans,@U[1],TypeInfo(TTestCustomJSON2));Software development is harder than it seems. I think AB's approach is correct and there's no escaping an incline on the learning curve. The 'bare bones' example's I looked at when starting out were good enough for me.
+1 for progressive conversion
TRecordEditForm in mORMotUIEdit.pas uses a combobox to select related TSQLRecord descendant fields in a record but this doesn't seem that scalable. Does anyone know of a good open source 'LookUp Edit' to use instead? I've had a go at writing one below but have a nasty feeling it's reinventing the wheel. (the code works but has barely been tested and is fairly crude)
unit FTSLookupEdit;
interface
uses
Classes, StdCtrls, ExtCtrls, Messages, Controls, Windows, Forms,
SysUtils, Graphics;
type
TFTSLookupEdit = class;
TFTSPopupList = class;
TFTSTickMark = class;
TIDLabel = class;
TResultIDs = array of integer;
TLookUpEvent = procedure(Sender: TFTSLookupEdit;
Search: string;
MaxResults: integer;
var IDs: TResultIDs; //IDs must match ResultStrings size
ResultStrings: TStrings) of object;
TFTSLookupEdit = class(TCustomLabeledEdit)
private
fListbox: TFTSPopupList;
fTickMark: TFTSTickMark;
fRecordNumberLabel: TIDLabel;
fDropDownLineCount: integer;
fMaxResults: integer;
fResultIDs: TResultIDs;
fSelectedID: integer;
fShowID: boolean;
fShowTick: boolean;
fLastSearch: string;
procedure ShowDropDown;
procedure HideDropDown;
procedure SetTickPosition;
procedure SetListBoxPosition;
procedure SetRecordNumberLabelPosition;
procedure SetChildPositions;
procedure SetRecordFromListbox;
procedure SetID(ID: integer);
procedure WMSize(var Message: TWMSize); message WM_SIZE;
procedure WMKillFocus(var Message: TWMKillFocus); message WM_KILLFOCUS;
procedure CMEnabledChanged(var Message: TMessage); message CM_ENABLEDCHANGED;
procedure CMCancelMode(var Message: TCMCancelMode); message CM_CANCELMODE;
protected
fOnLookUp: TLookUpEvent;
fOnChangedID: TNotifyEvent;
procedure SetParent(AParent: TWinControl); override;
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
procedure KeyPress(var Key: Char); override;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer); override;
procedure Change; override;
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
procedure CMBidimodechanged(var Message: TMessage); message CM_BIDIMODECHANGED;
procedure CMVisiblechanged(var Message: TMessage); message CM_VISIBLECHANGED;
public
constructor Create(AOwner: TComponent); override;
procedure SetBounds(ALeft: integer; ATop: integer; AWidth: integer; AHeight: integer); override;
function Focused: Boolean; override;
property SelectedID: integer read fSelectedID write SetID;
published
property DropDownLineCount: integer read fDropDownLineCount write fDropDownLineCount default 8;
property MaxResults: integer read fMaxResults write fMaxResults default 32;
property OnLookUp: TLookUpEvent read fOnLookUp write fOnLookUp;
property OnChangedID: TNotifyEvent read fOnChangedID write fOnChangedID;
property ShowID: boolean read fShowID write fShowID default true;
property ShowTick: boolean read fShowTick write fShowTick default true;
// Inherited properties
property Anchors;
property Color;
property Constraints;
property Ctl3D;
property DragCursor;
property DragMode;
property Enabled;
property Font;
property ImeMode;
property ImeName;
property Name;
property ParentColor;
property ParentCtl3D;
property ParentFont;
property ParentShowHint;
property ShowHint;
property TabOrder;
property TabStop;
property Visible;
property EditLabel;
// Inherited events
property OnChange;
property OnClick;
property OnDblClick;
property OnDragDrop;
property OnDragOver;
property OnEndDrag;
property OnEnter;
property OnExit;
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
property OnMouseDown;
property OnMouseMove;
property OnMouseUp;
property OnStartDrag;
end;
TFTSPopupList = class(TCustomListBox)
private
fLookUpOwner: TFTSLookupEdit;
fDefaultWindowProc: TWndMethod;
protected
procedure CreateParams(var Params: TCreateParams); override;
procedure PopupListWindowProc(var Message: TMessage);
public
constructor Create(AOwner: TComponent); override;
end;
TFTSTickMark = class(TGraphicControl)
private
fBitmap: TBitmap;
protected
procedure Paint; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
TIDLabel = class(TCustomLabel)
protected
procedure AdjustBounds; override;
public
constructor Create(AOwner: TComponent); override;
end;
procedure Register;
implementation
{$R *.RES}
constructor TFTSLookupEdit.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Height := 21;
fListbox := TFTSPopupList.Create(self);
fListbox.Parent := self;
fSelectedID := 0;
fShowID := True;
fShowTick := True;
fDropDownLineCount := 8;
fMaxResults := 32;
fLastSearch := '';
fTickMark := TFTSTickMark.Create(self);
fTickMark.Parent := self;
fTickMark.FreeNotification(Self);
fRecordNumberLabel := TIDLabel.Create(Self);
fRecordNumberLabel.Parent := self;
fRecordNumberLabel.FreeNotification(Self);
fRecordNumberLabel.FocusControl := Self;
fRecordNumberLabel.Caption := '';
end;
procedure TFTSLookupEdit.SetParent(AParent: TWinControl);
begin
inherited SetParent(AParent);
if fTickMark <> nil then
fTickMark.Parent := AParent;
if fListbox <> nil then
fListbox.Parent := AParent;
if fRecordNumberLabel <> nil then
fRecordNumberLabel.Parent := AParent;
end;
procedure TFTSLookupEdit.SetTickPosition;
begin
if fTickMark <> nil then
fTickMark.SetBounds(Left + Width - 18, Top - 18, 16, 16);
end;
procedure TFTSLookupEdit.SetListBoxPosition;
var
P: TPoint;
ListHeight: Integer;
begin
if (fListBox <> nil) and (Parent <> nil) then begin
P := parent.ClientOrigin;
if fListbox.Items.Count < fDropDownLineCount then
ListHeight := (fListbox.Items.Count * FListBox.ItemHeight) +2
else
ListHeight := (fDropDownLineCount * FListBox.ItemHeight) +2;
if (Top + P.Y + Height + ListHeight) > Screen.WorkAreaHeight then
fListbox.SetBounds(Left + P.X, Top + P.Y - ListHeight, Width, ListHeight)
else
fListbox.SetBounds(Left + P.X, Top + P.Y + Height, Width, ListHeight);
end;
end;
procedure TFTSLookupEdit.SetRecordNumberLabelPosition;
begin
if fRecordNumberLabel = nil then Exit;
//should look at position option. for now just puts it top right
fRecordNumberLabel.SetBounds(Left + Width - fRecordNumberLabel.Width - 20,
Top - fRecordNumberLabel.Height - LabelSpacing,
fRecordNumberLabel.Width, fRecordNumberLabel.Height);
end;
procedure TFTSLookupEdit.SetChildPositions;
begin
SetTickPosition;
SetListBoxPosition;
SetRecordNumberLabelPosition;
end;
procedure TFTSLookUpEdit.SetRecordFromListbox;
var
NewID: integer;
begin
if fListBox.ItemIndex > -1 then begin
NewID := fResultIDs[fListBox.ItemIndex];
if Text <> fListBox.Items.Strings[fListBox.ItemIndex] then begin
Text := fListBox.Items.Strings[fListBox.ItemIndex];
SelStart := length(Text);
SelLength := 0;
end;
fListBox.Items.Clear;
SetLength(fResultIDs, 0);
end else
NewID := 0;
if NewID <> fSelectedID then begin
fSelectedID := NewID;
if NewID > 0 then begin
if fShowID then
fRecordNumberLabel.Caption := inttostr(NewID);
if fShowTick then
fTickMark.Visible := True;
end else begin
fTickMark.Visible := False;
fRecordNumberLabel.Caption := '';
end;
if assigned(fOnChangedID) then
fOnChangedID(self);
end;
end;
procedure TFTSLookUpEdit.SetID(ID: integer);
begin
fSelectedID := ID;
fTickMark.Visible := ID > 0;
if fShowID then
fRecordNumberLabel.Caption := inttostr(ID);
end;
procedure TFTSLookupEdit.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
begin
inherited SetBounds(ALeft, ATop, AWidth, AHeight);
SetChildPositions;
end;
function TFTSLookupEdit.Focused: Boolean;
var
FocusedWnd: HWND;
begin
Result := False;
if HandleAllocated = true then begin
FocusedWnd := GetFocus;
Result := (FocusedWnd = fListbox.Handle) or (FocusedWnd = Handle);
end;
end;
procedure TFTSLookupEdit.ShowDropDown;
begin
if fListbox.Items.Count > 0 then begin
SetListBoxPosition;
fListbox.visible := true;
if fListbox.ItemIndex = -1 then
fListbox.ItemIndex := 0;
end else
fListbox.Visible := false;
end;
procedure TFTSLookupEdit.HideDropDown;
begin
fListbox.visible := false;
end;
procedure TFTSLookupEdit.WMSize(var Message: TWMSize);
begin
inherited;
SetChildPositions;
end;
procedure TFTSLookupEdit.WMKillFocus(var Message: TWMKillFocus);
begin
if not Focused then HideDropDown;
inherited;
end;
procedure TFTSLookupEdit.CMCancelMode(var Message: TCMCancelMode);
begin
if (Message.Sender <> Self) and (Message.Sender <> FListBox) then
HideDropDown;
end;
procedure TFTSLookupEdit.KeyDown(var Key: Word; Shift: TShiftState);
begin
case Key of
VK_ESCAPE: HideDropDown;
VK_UP: if not fListBox.Visible then
ShowDropDown
else if fListBox.ItemIndex > 0 then
fListBox.ItemIndex := fListBox.ItemIndex -1;
VK_DOWN: if not fListBox.Visible then
ShowDropDown
else if (fListBox.Items.Count > 0) and
(fListBox.ItemIndex < fListBox.Items.Count -1) then
fListBox.ItemIndex := fListBox.ItemIndex +1;
VK_HOME: if fListBox.Items.Count > 0 then
fListBox.ItemIndex := 0;
VK_END: if fListBox.Items.Count > 0 then
fListBox.ItemIndex := fListBox.Items.Count -1;
VK_RETURN: if fListBox.Visible then begin
SetRecordFromListbox;
HideDropDown;
end else
inherited KeyDown(Key, Shift);
VK_TAB: HideDropDown;
else begin
inherited KeyDown(Key, Shift);
exit;
end;
end;
Key := 0;
end;
procedure TFTSLookupEdit.KeyPress(var Key: Char);
begin
if Key in [#13,#27] then Key := #0
else inherited KeyPress(Key);
end;
procedure TFTSLookupEdit.MouseDown(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer);
begin
if fListbox.Visible then fListbox.Visible := false
else ShowDropDown;
inherited MouseDown(Button, Shift, X, Y);
end;
procedure TFTSLookupEdit.Change;
var
i, ii, listCnt: integer;
s : string;
begin
if Modified = true then begin
s := lowercase(Text);
fListbox.Items.BeginUpdate;
if (fLastSearch <> '') and (pos(fLastSearch, Text) > 0) and (fListbox.Count < fMaxResults) then begin
//local filter - may not completely match remote query.
listCnt := fListbox.Count;
for i := listCnt -1 downto 0 do
if pos(s,lowercase(fListbox.Items.Strings[i])) = 0 then begin
fListbox.Items.Delete(i);
for ii := i to fListbox.Count-1 do
fResultIDs[ii] := fResultIDs[ii+1];
end;
SetLength(fResultIDs,fListbox.Count);
end else if assigned(fOnLookUp) then begin
fListbox.Clear;
fOnLookUp(self, Text, fMaxResults, fResultIDs, fListbox.Items)
end;
fListbox.Items.EndUpdate;
i := fListbox.Items.IndexOf(Text);
if i > -1 then begin
fListbox.ItemIndex := i;
SetRecordFromListbox;
end else begin
fRecordNumberLabel.Caption := '';
fTickMark.Visible := False;
if fSelectedID <> 0 then begin
fSelectedID := 0;
if assigned(fOnChangedID) then
fOnChangedID(self);
end;
if (fListbox.Items.Count > 0) and (fListbox.ItemIndex = -1) then
fListbox.ItemIndex := 0;
end;
ShowDropDown;
fLastSearch := Text;
end;
inherited Change;
end;
procedure TFTSLookupEdit.CMBidimodechanged(var Message: TMessage);
begin
inherited;
if fRecordNumberLabel <> nil then
fRecordNumberLabel.BiDiMode := BiDiMode;
end;
procedure TFTSLookupEdit.CMEnabledchanged(var Message: TMessage);
begin
inherited;
if fRecordNumberLabel <> nil then
fRecordNumberLabel.Enabled := Enabled;
end;
procedure TFTSLookupEdit.CMVisiblechanged(var Message: TMessage);
begin
inherited;
if fTickMark <> nil then
//ControlStyle := ControlStyle + [csNoDesignVisible];
if fShowTick then
fTickMark.Visible := Visible;
//else
//fTickMark.Visible := False;
if fRecordNumberLabel <> nil then
if fShowID then
fRecordNumberLabel.Visible := Visible;
if fListBox <> nil then
if not visible then
fListBox.Visible := Visible;
end;
procedure TFTSLookupEdit.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if (AComponent = fTickMark) and (Operation = opRemove) then
fTickMark := nil;
if (AComponent = fListBox) and (Operation = opRemove) then
fListBox := nil;
if (AComponent = fRecordNumberLabel) and (Operation = opRemove) then
fRecordNumberLabel := nil;
end;
{-------------}
{TFTSPopupList}
{-------------}
constructor TFTSPopupList.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
fLookUpOwner := TFTSLookupEdit(AOwner);
AutoComplete := False;
Ctl3D := False;
TabStop := False;
self.BorderStyle := bsNone;
fDefaultWindowProc := self.WindowProc;
self.WindowProc := PopupListWindowProc;
ControlStyle := ControlStyle + [csNoDesignVisible];
Visible := false;
end;
procedure TFTSPopupList.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
with Params do begin
Style := Style or WS_POPUP or WS_BORDER;
if CheckWin32Version(5, 1) then
WindowClass.Style := WindowClass.style or CS_SAVEBITS or CS_DROPSHADOW
else
WindowClass.Style := WindowClass.Style or CS_SAVEBITS;
if NewStyleControls then
ExStyle := ExStyle or WS_EX_NOACTIVATE or WS_EX_TOOLWINDOW;//WS_EX_TOPMOST
AddBiDiModeExStyle(ExStyle);
end;
end;
procedure TFTSPopupList.PopupListWindowProc(var Message: TMessage);
var
P: TPoint;
begin
case Message.Msg of
WM_MOUSEMOVE: begin
P := Point(Message.LParamLo, Message.LParamHi);
if BiDiMode = bdRightToLeft then
P.X := - P.X;
self.ItemIndex := self.ItemAtPos(P,true);
Message.Result := 0;
end;
WM_LBUTTONDOWN: begin
fLookUpOwner.SetRecordFromListbox;
fLookUpOwner.HideDropDown;
Message.Result := 0;
end;
WM_LBUTTONDBLCLK: fLookUpOwner.HideDropDown;
WM_LBUTTONUP, WM_RBUTTONDOWN..WM_MOUSELAST: Message.Result := 0;
WM_ACTIVATE: Message.Result := 0;
WM_SETCURSOR: Message.Result := 1;
WM_MOUSEACTIVATE: Message.Result := MA_NOACTIVATE;
else
fDefaultWindowProc(Message);
end;
end;
{------------}
{TFTSTickMark}
{------------}
constructor TFTSTickMark.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
fBitmap := TBitmap.Create;
if FindResource(HInstance, pchar('TICKMARK'), RT_BITMAP) <> 0 then
fBitmap.LoadFromResourceName(HInstance, 'TICKMARK')
else begin
fBitmap.Width := 16;
fBitmap.Height := 16;
fBitmap.Canvas.MoveTo(0,12);
fBitmap.Canvas.LineTo(4,16);
fBitmap.Canvas.LineTo(16,0);
end;
fBitmap.Transparent := true;
Height := 16;
Width := 16;
Name := 'SubTick';
Visible := False;
end;
destructor TFTSTickMark.Destroy;
begin
fBitmap.Free;
inherited Destroy;
end;
procedure TFTSTickMark.Paint;
begin
with inherited Canvas do
Draw(0,0, fBitmap);
end;
{--------}
{TIDLabel}
{--------}
constructor TIDLabel.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Name := 'SubIDLabel';
end;
procedure TIDLabel.AdjustBounds;
begin
inherited AdjustBounds;
if height < 16 then //reserve space for top tick when top aligned
height := 16;
if Owner is TFTSLookupEdit then
with Owner as TFTSLookupEdit do
SetRecordNumberLabelPosition;
end;
procedure Register;
begin
RegisterComponents('Additional', [TFTSLookupEdit]);
end;
end.Thanks for the help. I've worked out how to do it ![]()
OK, I'll try and do something along the lines of ExportPDFStream.
Could TGDIPages.SetMetaFileForPage and getMetaFileForPage be given public scope. Then I can simply do something like:
for i := 0 to NumberOfReports do
//make report...
report.EndDoc;
for ii := 0 to report.PageCount -1 do begin
MergedReport.NewPage;
MergedReport.SetMetaFileForPage(MergedReport.PageCount-2, report.GetMetaFileForPage(ii));
end;
end;It's not perfect because NewPage flushes the last page overwriting the content. As a workaround the Merged report has one more page than needed and gets the penultimate page written to.
That sounds great for javascript but I can't see the advantage if one is writing a client in pascal which needs to be compiled manually.
I would vote for synapse rather than indy. How much work would implementing OSX compatibility be just for client side? Seeing as it's based on FreeBSD doing it should also go a long way to making it compatible with other *NIXs. I'm happy to help if I can.
What do you mean by a 'unit code generator'?
I was only thinking of implementing the client side of Sample 4