You are not logged in.
Hi, yesterday I tried to write this export for ECC keys generated by the library to be able to use jwt signed with ES256.
But something does not work, the DER is correct, but the tests say the points are not on the curve.
does anyone have any ideas?
some links:
https://8gwifi.org/PemParserFunctions.jsp
try:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYjcE4dwT3NRf8Zd5Edl7zih3O8JJeXjdevwI3HaJmgO2VsSx2Lq7+FDpEghPKj1xNqU4jPMTz7apH5inrNYi6Q==
-----END PUBLIC KEY-----
answer:
unable to convert key pair: encoded key spec not recognized: Invalid point coordinates
asn.1 inspector:
https://lapo.it/asn1js/
function TECCCertificateSecret.SaveToDER64:RawByteString;
const DER_SEQUENCE = $30;
DER_INTEGER = $02;
DER_BIT =$03;
DER_OBJECT = $06;
var RPrefix,SPrefix: integer;
P,PU: PByteArray;
Point: TEccPoint;
begin
SetLength(result,91);
fillchar(result[1],91,0);
P := pointer(result);
P[0]:=DER_SEQUENCE;
P[1]:=$59; // total length
p[2]:=DER_SEQUENCE;
p[3]:=$13; // Length OIDs
p[4]:=DER_OBJECT;
p[5]:=$07; // length OID 1.2.840.10045.2.1ecPublicKey(ANSI X9.62 public key type)
p[6]:=$2A;
p[7]:=$86;
P[8]:=$48;
P[9]:=$CE;
P[10]:=$3D;
P[11]:=$02;
P[12]:=$01;
p[13]:=DER_OBJECT;
p[14]:=$08; // length OID 1.2.840.10045.3.1.7prime256v1(ANSI X9.62 named elliptic curve)
p[15]:=$2A;
p[16]:=$86;
P[17]:=$48;
P[18]:=$CE;
P[19]:=$3D;
P[20]:=$03;
P[21]:=$01;
P[22]:=$07;
p[23]:=DER_BIT; // bit sequence
p[24]:=$42; // length Bit string
p[25]:=$00; // Fixed
p[26]:=$04; // Fixed
inc(PByte(P),26+1);
_clear(Point.x);
_clear(Point.y);
EccPointDecompress(Point,@fContent.Signed.PublicKey); // extract point x.y
MoveFast(Point,p[0],ECC_BYTES*2); // on buffer
result:='-----BEGIN PUBLIC KEY-----'+#13#10+binToBase64(result)+#13#10+'-----END PUBLIC KEY-----'+#13#10;
end;
I'm trying SynWinWebSockets.TWinHTTPWebSocketClient.
How to set https ?
I'd like to contribute to this development.
32-bit is fine for development
You wrote a simple example client-server (all in Delphi)?
I use two Win 8.1 with delphi 10 seattle, with Project31WinHTTPEchoServer (x64) no problem
but others samples hangs.
I dowload from fossil the source, but simple chat example hangs (64 bit compiled)
I'm wrong ?
Hi,
I noticed that the part of the user / pass authentication for HTTP proxy is not present for TwinHttp.
I had made these changes a few months ago:
unit SynCrtSock;
add proxy_username and proxy_password
THttpRequestExtendedOptions = record
/// let HTTPS be less paranoid about SSL certificates
// - IgnoreSSLCertificateErrors is handled by TWinHttp and TCurlHTTP
IgnoreSSLCertificateErrors: Boolean;
proxy_username,proxy_password:ansistring; // add this
/// allow HTTP authentication to take place at connection
// - Auth.Scheme and UserName/Password properties are handled
// by the TWinHttp class only by now
Auth: record
UserName: SynUnicode;
Password: SynUnicode;
Scheme: THttpRequestAuthentication;
end;
end;
THttpRequest
add these properties:
property Proxy_username: ansistring // ansistring is
read fExtendedOptions.Proxy_username
write fExtendedOptions.Proxy_username;
property Proxy_password: ansistring
read fExtendedOptions.Proxy_password
write fExtendedOptions.Proxy_password;
go to :
procedure TWinHTTP.InternalSendRequest(const aData: SockString);
add consts
const
WINHTTP_OPTION_PROXY_USERNAME = $1002;
WINHTTP_OPTION_PROXY_PASSWORD = $1003;
Add calls here:
procedure TWinHTTP.InternalSendRequest(const aData: SockString);
var L: integer;
winAuth: DWORD;
begin
with fExtendedOptions do
if AuthScheme<>wraNone then begin
case AuthScheme of
wraBasic: winAuth := WINHTTP_AUTH_SCHEME_BASIC;
wraDigest: winAuth := WINHTTP_AUTH_SCHEME_DIGEST;
wraNegotiate: winAuth := WINHTTP_AUTH_SCHEME_NEGOTIATE;
else raise EWinHTTP.CreateFmt('Unsupported AuthScheme=%d',[ord(AuthScheme)]);
end;
if not WinHttpSetCredentials(fRequest,WINHTTP_AUTH_TARGET_SERVER,
winAuth,pointer(AuthUserName),pointer(AuthPassword),nil) then
RaiseLastModuleError(winhttpdll,EWinHTTP);
// add this:
if PROXY_USERNAME<>'' then
begin
if not WinHttpSetOption( fRequest, WINHTTP_OPTION_PROXY_USERNAME, PWidechar(WideString(PROXY_USERNAME)), length(PROXY_USERNAME)) then
RaiseLastModuleError(winhttpdll,EWinHTTP);
end;
if PROXY_PASSWORD<>'' then
begin
if not WinHttpSetOption( fRequest, WINHTTP_OPTION_PROXY_PASSWORD, PWidechar(WideString(PROXY_PASSWORD)), length(PROXY_PASSWORD)) then
RaiseLastModuleError(winhttpdll,EWinHTTP);
end;
end;
// end add
if fHTTPS and IgnoreSSLCertificateErrors then
if not WinHttpSetOption(fRequest, WINHTTP_OPTION_SECURITY_FLAGS,
@SECURITY_FLAT_IGNORE_CERTIFICATES, SizeOf(SECURITY_FLAT_IGNORE_CERTIFICATES)) then
RaiseLastModuleError(winhttpdll,EWinHTTP);
L := length(aData);
if not WinHttpSendRequest(fRequest, nil, 0, pointer(aData), L, L, 0) or
not WinHttpReceiveResponse(fRequest,nil) then
RaiseLastModuleError(winhttpdll,EWinHTTP);
end;
Hello, I realized that with version 3.0 you can not receive the collection list using this method:
constructor TMongoDatabase
[..]
if colls.Init(Client.Connections[0].GetBSONAndFree(
TMongoRequestQuery.Create(aDatabaseName+'.system.namespaces',null,'name',maxInt))) then
// e.g. [ {name:"test.system.indexes"}, {name:"test.test"} ]
while colls.Next do begin
full := colls.item.DocItemToRawUTF8('name');
if full<>'' then begin
split(full,'.',db,coll);
if db<>aDatabaseName then
raise EMongoConnectionException.CreateUTF8(
'%.Create: invalid "%" collection name for DB "%"',
[self,full,aDatabaseName],Client.Connections[0]);
fCollections.AddObject(coll,TMongoCollection.Create(self,coll));
end;
end;
I made a change to use the new method (listCollections):
Client.Connections[0].RunCommand(aDatabaseName,'listCollections',Vresponse);
if not varisnull(Vresponse) then
begin
for t:=0 to Vresponse.cursor.firstBatch._count-1 do
begin
v:=Vresponse.cursor.firstBatch._(t);
coll:=V.name;
if coll<>'' then
fCollections.AddObject(coll,TMongoCollection.Create(self,coll));
end;
end;
Hi.
In a MongoDB collection I have this :
{
"_id" : "FBB5F6O2ZE2EVJ6AG5JHORWBQA",
"EmptyArray" : []
}
I do this:
var
ADocs: TVariantDynArray
Coll.FindDocs(ADocs, '', 0, 0, []);
And I get:
[
{ "_id" : "FBB5F6O2ZE2EVJ6AG5JHORWBQA",
"EmptyArray" : null <------------------------------------------NULL ?
}
]
So I play with debugger and I make this change:
// used by TBSONElement.ToVariant() method and BSONToDoc() procedure
procedure BSONItemsToDocVariant(Kind: TBSONElementType; BSON: PByte;
var Doc: TDocVariantData; Option: TBSONDocArrayConversion);
const OPTIONS: array[TBSONDocArrayConversion] of TDocVariantOptions =
([],[dvoReturnNullForUnknownProperty],
[dvoValueCopiedByReference,dvoReturnNullForUnknownProperty]);
var k: TDocVariantKind;
i,n,cap: integer;
items: array[0..63] of TBSONElement;
begin // very fast optimized code
if not (Kind in [betDoc,betArray]) then
VarCastError;
if (BSON=nil) {or (BSON^=byte(betEOF))} then // I remove this, a empty array is not null (I think)
TVarData(Doc).VType := varNull else begin
if Kind=betDoc then
k := dvObject else
k := dvArray;
Doc.Init(OPTIONS[Option],k);
cap := 0;
repeat
n := 0;
while items[n].FromNext(BSON) do begin
inc(n);
if n=length(items) then
break;
end;
if n=0 then
break;
inc(cap,n);
if cap<512 then
Doc.Capacity := cap else
if Doc.Capacity<cap then
Doc.Capacity := cap+cap shr 3; // faster for huge arrays
for i := 0 to n-1 do begin
if Kind=betDoc then
SetString(Doc.Names[i+Doc.Count],PAnsiChar(items[i].Name),items[i].NameLen);
items[i].ToVariant(Doc.Values[i+Doc.Count],Option);
end;
Doc.SetCount(Doc.Count+n);
until (BSON=nil) or (BSON^=byte(betEOF));
end;
end;
I do some tests, and all go well.
HI,
Look at this array:
[{"Name":"Paul","born":isodate("1969-12-31")}]
I discovered that function below can not read it, so I made a small change.
function GotoNextJSONObjectOrArrayInternal(P,PMax: PUTF8Char; EndChar: AnsiChar): PUTF8Char;
label Prop;
begin
result := nil;
repeat
case P^ of
'{','[': begin
if PMax=nil then
P := GotoNextJSONObjectOrArray(P) else
P := GotoNextJSONObjectOrArrayMax(P,PMax);
if P=nil then exit;
end;
':': if EndChar<>'}' then exit else inc(P); // syntax for JSON object only
',': inc(P); // comma appears in both JSON objects and arrays
'}': if EndChar='}' then break else exit;
']': if EndChar=']' then break else exit;
'"': begin
P := GotoEndOfJSONString(P);
if P^<>'"' then
exit;
inc(P);
end;
'-','+','0'..'9': // '0123' excluded by JSON, but not here
repeat
inc(P);
until not (P^ in DigitFloatChars);
't': if PInteger(P)^=TRUE_LOW then inc(P,4) else goto Prop;
'f': if PInteger(P)^=FALSE_LOW then inc(P,5) else goto Prop;
'n': if PInteger(P)^=NULL_LOW then inc(P,4) else goto Prop;
'''': begin
repeat inc(P); if P^<=' ' then exit; until P^='''';
repeat inc(P) until not(P^ in [#1..' ']);
if P^<>':' then exit;
end;
'/': begin
repeat // allow extended /regex/ syntax
inc(P);
if P^=#0 then
exit;
until P^='/';
repeat inc(P) until not(P^ in [#1..' ']);
end;
else
begin
Prop: if not (P^ in ['_','A'..'Z','a'..'z','0'..'9','$']) then
exit; // expect e.g. '{age:{$gt:18}}'
repeat
inc(P);
until not (P^ in ['_','A'..'Z','a'..'z','0'..'9','.']);
while P^ in [#1..' '] do inc(P);
/////////////////////////////////////////////////////////////////////// start
if P^ ='(' then
begin
inc(p);
while P^ in [#1..' '] do inc(P);
if p^='"' then
begin
P := GotoEndOfJSONString(P);
if P^<>'"' then
exit;
end;
inc(p);
while P^ in [#1..' '] do inc(P);
if P^<>')' then
exit;
inc(p);
end
else
/////////////////////////////////////////////////////////////////////// end
if P^<>':' then
exit;
end;
end;
if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
if (PMax<>nil) and (P>=PMax) then
exit;
until P^=EndChar;
result := P+1;
end;
I looked at the use of passwords in mormot.pas
As you say, it is difficult to unify control of password authentication for all systems.
Especially for the use of passwordhexA as SALT communication.
Let me say that your constant 'salt' used between client and server is cryptographically poor, and hides a big problem in my opinion:
if you had a real cryptographic salt for each user how to connect a client to the server?
the client should already know the SALT.
Indeed passwordhexA = sha256 ('salt' + password);
perhaps think of a signature property in user with a separate management of the password might be interesting.
What's the matter?
have a simple checkpassword violates the principles?
canUserLog instead?
Tomorrow I'll try to improve the function checkpassword to meet the needs of existing authentication systems.
The endeavor to create a new class that inherits from authattpbasic just for that single detail (which by the way would be useful to everybody and would not break compliance with the HTTP Basic Auth standard) just seems to me like an unjustified waste of time...
Everything can be improved
I think it's the spirit of open source.
Leaving aside the usefulness or not to rewrite a new class of authentication httpbasic, I think the CheckCredential, as has been thought, is more efficient function that I have proposed and implemented long ago: CanUserLog.
The checkCredential, without changing a single line of mormot.pas, allows a developer to have full control, if he likes it, the authentication process could easily integrate with any variables derived from object tsqlauthuser.
While canUserLog, acting perhaps a little late in the authentication process, removing the ability to implement custom rules.
we are almost there ...
but, for the http basic need a small patch
function TSQLRestServerAuthenticationHttpBasic.Auth(Ctxt: TSQLRestServerURIContext): boolean;
var userPass,user,pass,expectedPass: RawUTF8;
U: TSQLAuthUser;
Session: TAuthSession;
begin
if Ctxt.InputExists['UserName'] then begin
result := false; // allow other schemes to check this request
exit;
end;
result := true; // this authentication method is exclusive to any other
if GetUserPassFromInHead(Ctxt,userPass,user,pass) then begin
U := GetUser(Ctxt,user);
if U<>nil then begin
try
expectedPass := U.PasswordHashHexa;
U.PasswordPlain := pass; // override with SHA-256 hash from HTTP header
if U.PasswordHashHexa=expectedPass then begin
fServer.SessionCreate(U,Ctxt,Session);
if Session<>nil then begin
// see TSQLRestServerAuthenticationHttpAbstract.ClientSessionSign()
Ctxt.SetOutSetCookie((COOKIE_SESSION+'=')+CardinalToHex(Session.IDCardinal));
Ctxt.Returns(['result',Session.IDCardinal,'logonname',Session.User.LogonName]);
exit; // success
end;
end;
finally U.Free; end
end;
Ctxt.AuthenticationFailed; //<--------------------------- moved
exit;
end;
Ctxt.Call.OutHead := 'WWW-Authenticate: Basic realm="mORMot Server"';;
Ctxt.Error('',HTML_UNAUTHORIZED); // will popup for credentials in browser
end;
I moved the call Ctxt.AuthenticationFailed,
because in the case of existing user with the wrong password, the sesionfail was not called
unfortunately it does not work.
I write a little cli/srv program, so you can debug.
ServerHB.pas
program ServerHB;
{$APPTYPE CONSOLE}
uses
SysUtils,
SynCommons, SynLog, mORMot,MormotHTTPserver,
mORMotSQLite3, SynSQLite3Static;
const
SERVER_PORT='888';
type
ICalculator = interface(IInvokable)
['{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FE}']
function Add(n1,n2: integer): integer;
end;
Tmysess = class
function myerror(Sender: TSQLRestServer; Session: TAuthSession; Ctxt: TSQLRestServerURIContext): boolean;
end;
TMYAuthUser = class(TSQLAuthUser)
private
Fenabled:boolean;
published
property enabled: Boolean read Fenabled write Fenabled; // abilitato o no
function CanUserLog(Ctxt: TSQLRestServerURIContext): boolean; override;
end;
TServiceCalculator = class(TInterfacedObject, ICalculator)
public
function Add(n1,n2: integer): integer;
end;
function TServiceCalculator.Add(n1, n2: integer): integer;
begin
result := n1+n2;
end;
function TMYAuthUser.CanUserLog(Ctxt: TSQLRestServerURIContext): boolean;
begin
result:=true;
end;
function Tmysess.myerror(Sender: TSQLRestServer; Session: TAuthSession; Ctxt: TSQLRestServerURIContext): boolean;
var
tmp:rawutf8;
begin
tmp:=ctxt.Call.InHead;
writeln('fail_inhead:'+tmp);
tmp:=ctxt.InHeader['Authorization'];
writeln('fail_auth:'+tmp);
result:=false;
end;
var
aModel: TSQLModel;
Restsrv:TSQLRestServerDB;
u:TMYAuthUser;
aHttpServer:TSQLHttpServer;
mySess:Tmysess;
begin
with TSQLLog.Family do begin
Level := LOG_VERBOSE;
EchoToConsole := LOG_VERBOSE; // log all events to the console
end;
TInterfaceFactory.RegisterInterfaces([TypeInfo(ICalculator)]);
mysess:=Tmysess.Create;
aModel := TSQLModel.Create([TMYAuthUser,TSQLauthGroup]);
try
Restsrv:=TSQLRestServerDB.Create(aModel,':memory:',false);
try
restsrv.CreateMissingTables; // we need AuthGroup and AuthUser tables
restsrv.AuthenticationRegister(TSQLRestServerAuthenticationHttpBasic);
u:=TMYAuthUser.Create;
u.LogonName:='myuser';
u.DisplayName:='Test User';
u.PasswordPlain:='test';
u.enabled:=true;
u.GroupRights:=TSQLAuthGroup(3);
RestSrv.Add(U,true);
u.Free;
if not RestSrv.TableHasRows(TSQLAuthGroup) then
TSQLAuthGroup.InitializeTable(RestSrv,'',[itoNoIndex4ID, itoNoIndex4UniqueField, itoNoIndex4NestedRecord, itoNoIndex4RecordReference]);
restsrv.ServiceDefine(TServiceCalculator,[ICalculator],sicpersession);
RestSrv.OnSessionFailed:=mysess.myerror;
aHttpServer:=TSQLHttpServer.Create(SERVER_PORT,[RestSrv],'+',useHttpApiRegisteringURI);
aHttpServer.AccessControlAllowOrigin:='*'; // allow cross-site AJAX queries
write('Press [Enter] to close the server.');
readln;
finally
restsrv.Free;
end;
finally
aModel.Free;
end;
end.
ClientHB.pas
program ClientHB;
{$APPTYPE CONSOLE}
uses
SysUtils,
SynCommons, SynLog, mORMot,MormotHTTPserver,
mORMotSQLite3, SynSQLite3Static,mORMotHttpClient;
const
CLIENT_PORT='888';
CLIENT_ADDR='localhost';
type
ICalculator = interface(IInvokable)
['{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FE}']
function Add(n1,n2: integer): integer;
end;
var
aModel:TSQLModel;
aClient:TSQLHttpClient;
I:ICalculator;
begin
with TSQLLog.Family do begin
Level := LOG_VERBOSE;
// EchoToConsole := LOG_VERBOSE; // log all events to the console
end;
TInterfaceFactory.RegisterInterfaces([TypeInfo(ICalculator)]);
aModel := TSQLModel.Create([TSQLAuthUser]);
aClient := TSQLHttpClientWinHTTP.Create(CLIENT_ADDR,CLIENT_PORT,aModel);
writeln('auth WRONG test:');
if TSQLRestServerAuthenticationHttpBasic.ClientSetUser(aClient,'myuser','test2',passClear) then
begin
writeln('auth OK');
aClient.ServiceDefine([ICalculator],sicpersession);
if aClient.Services['Calculator'].Get(I) then
writeln('result: '+ intToStr(I.Add(2,3)));
end
else Writeln('auth KO');
writeln('');
writeln('auth RIGHT test:');
if TSQLRestServerAuthenticationHttpBasic.ClientSetUser(aClient,'myuser','test',passClear) then
begin
writeln('auth OK');
aClient.ServiceDefine([ICalculator],sicpersession);
if aClient.Services['Calculator'].Get(I) then
writeln('result: '+ intToStr(I.Add(2,3)));
end
else Writeln('auth KO');
write('Press [Enter] to close the client.');
readln;
end.
I rewrote the function, as I think should be its logic
function TSQLRestServerURIContext.Authenticate: boolean;
var aSession: TAuthSession;
i: integer;
begin
if Server.HandleAuthentication then
begin
result := false;
Session := CONST_AUTHENTICATION_SESSION_NOT_STARTED;
Server.fSessions.Lock;
try
aSession := nil;
if Server.fSessionAuthentication<>nil then
begin
for i := 0 to length(Server.fSessionAuthentication)-1 do
begin
aSession := Server.fSessionAuthentication[i].RetrieveSession(self);
if aSession<>nil then
begin
{$ifdef WITHLOG}
log.Log(sllUserAuth,'%/%',[aSession.User.LogonName,aSession.ID],self);
{$endif}
SetString(fSessionAccessRights,PAnsiChar(@aSession.fAccessRights),
sizeof(TSQLAccessRights)); // override access rights
Call^.RestAccessRights := pointer(fSessionAccessRights);
result:=true;
exit;
end;
end;
end;
finally
Server.fSessions.UnLock;
end;
if ((Service<>nil) and Service.ByPassAuthentication) or
((MethodIndex>=0) and Server.fPublishedMethod[MethodIndex].ByPassAuthentication) then
begin
result:=true;
exit;
end;
end
else
begin// default unique session if authentication is not enabled
Session := CONST_AUTHENTICATION_NOT_USED;
result := true;
end;
end;
with my test program **NOT** works, but you have to see if it does what it needs
there is something wrong...
// 2. handle security
if (not Ctxt.Authenticate) or
((Ctxt.Service<>nil) and
not (reService in Call.RestAccessRights^.AllowRemoteExecute)) then
Ctxt.AuthenticationFailed else
Ctxt.AuthenticationFailed never fire.
I suspect that the function ctxt.authenticate returns true when it should return false.
I have no patch available now
Hi AB,
there is a small fix for the multipart feature:
the last boundary ends with '-'
function MultiPartFormDataDecode(const MimeType,Body: RawUTF8;
var MultiPart: TMultiPartDynArray): boolean;
var boundary,EndBoundary: RawUTF8;
i,j: integer;
P: PUTF8Char;
part: TMultiPart;
begin
result := false;
i := PosEx('boundary=',MimeType);
if i=0 then
exit;
boundary := '--'+trim(copy(MimeType,i+9,200))+#13#10;
Endboundary := '--'+trim(copy(MimeType,i+9,200))+'--'+#13#10;
i := PosEx(boundary,Body);
if i<>0 then
repeat
inc(i,length(boundary));
if i=length(body) then
exit; // reached the end
P := PUTF8Char(Pointer(Body))+i-1;
Finalize(part);
repeat
if IdemPCharAndGetNextItem(P,
'CONTENT-DISPOSITION: FORM-DATA; NAME="',part.Name,'"') then
IdemPCharAndGetNextItem(P,'; FILENAME="',part.FileName,'"') else
if IdemPCharAndGetNextItem(P,'CONTENT-TYPE: ',part.ContentType) or
IdemPCharAndGetNextItem(P,'CONTENT-TRANSFER-ENCODING: ',part.Encoding) then;
GetNextLineBegin(P,P);
if P=nil then
exit;
until PWord(P)^=13+10 shl 8;
i := P-PUTF8Char(Pointer(Body))+3; // i = just after header
j := PosEx(boundary,Body,i);
if j=0 then
begin // try last bondary
j := PosEx(endboundary,Body,i);
if j=0 then
exit;
end;
part.Content := copy(Body,i,j-i-2); // -2 to ignore latest #13#10
{$ifdef UNICODE}
if (part.ContentType='') or (PosEx('-8',part.ContentType)>0) then
SetCodePage(part.Content,CP_UTF8,false) else // ensure raw field value is UTF-8
{$endif}
if IdemPropNameU(part.Encoding,'base64') then
part.Content := Base64ToBin(part.Content);
// note: "quoted-printable" not yet handled here
SetLength(MultiPart,length(MultiPart)+1);
MultiPart[high(MultiPart)] := part;
result := true;
i := j;
until false;
end;
Tnxs
but then there are disadvantages to the client mormot?
hello, AB
I reopen this old 3d because something escapes me ...
I Mormot the server with a method similar interface:
Download function (json: RAWutf8): TserviceCustomAnswer;
if I call (from Mormot client)
POST /BSWPZNOCNDTUG6DU63Z5V7LNFE/object.Download HTTP/1.1
Cache-Control: no-cache
Connection: Keep-Alive
Pragma: no-cache
Content-Type: application/json; charset=UTF-8
Accept: */*
Accept-Encoding: synlz
Authorization: Basic xxxxxx
User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows; Synopse mORMot 1.18.1204 TWinHTTP)
Content-Length: 99
Host: test:888
Cookie: mORMot_session_signature=4DDC024C; mORMot_session_signature=4DDC024C
["{\"ID\":\"directory\",\"file\":\"filename\",\"seek\":0}"]
this works
but, reading this 3d
I try, from NON mormot client:
[{"ID":"directory","file":"filename","seek":0}]
but fails, also
{"ID":"directory","file":"filename","seek":0}
fails again
ps my model is sicPerSession
Tnxs
Tomorrow I try
Hi AB,
I was looking for a way to be able to manage access prohibited.
There is' a way to know on the server when a call is prohibited?
I would love to be able to have all the call to extract who is logging, that url was calling, his ip.
All this in order to realize two things: managing the attacks (ip ban with external firewall) may notify the user attempts wrong.
Tnx
...and works well with mORMot !
I only want to enable my application, not the entire computer.
There is no API to call?
Hi AB,
Today I found myself in the situation of having to set up an http proxy to surf outside the local network.
I did not find documentation on how to provide credentials.
The program (win32) use this:
var
aclient: TSQLHttpClient
begin
aclient: TSQLHttpClientWinHTTP.Create = (addr, SERVER_PORT, Amodel);
I changed in:
aclient: TSQLHttpClientWinHTTP.Create = (addr, SERVER_PORT, Amodel, false, 'proxy: 8080', 'localhost');
and works with an authenticated proxy-based ip origin, but if authentication is on user / password does not work.
but how do I add the user and password?
The first tests seems to work well.
I found the problem!
These two functions are not equivalent:
1) function TMongoCollection.AggregateCall(const pipelineJSON: RawUTF8; var reply,res: variant): boolean;
2) function TMongoCollection.AggregateCall(const pipelineArray: variant; var reply,res: variant): boolean;
The first works well.
The second one does not insert properly the pipeline as an array.
Temporary patch:
function TMongoCollection.AggregateDoc(const pipelineArray: variant): variant;
var reply: variant;
begin
if AggregateCall(RAWutf8(pipelineArray),reply,result) then // I added RAWutf8 to force the use of the first
TDocVariant.GetSingleOrDefault(result,result,result) else
SetVariantNull(result);
end;
any news ?
works with your setup?
I made a test project.
The program adds a user randomly at each start, the first works, but the second does not work.
I get the same result with xe2 and XE7, mongodb 2.6.1 and 2.6.6.
Hi AB,
I'm trying to add users to authuser.
At the second start of the program I have four, but I can not add the fifth.
No error or exception, but in the end I found the answer in the log:
There is a problem with the self-generation of _ID
function TSQLRestStorageMongoDB.EngineNextID: TID;
procedure ComputeMax_ID;
var res: variant;
begin
if not fIndexesCreated then
CreateIndexes;
res := fCollection.AggregateDoc('{$group:{_id:null,max:{$max:"$_id"}}}'); <----- WRONG, works only in shell
if DocVariantType.IsOfType(res) then
fEngineLastID := VariantToInt64Def(res.max,0);
{$ifdef WITHLOG}
fOwner.LogFamily.SynLog.Log(sllInfo,'Computed EngineNextID=%',[fEngineLastID],self);
{$endif}
end;
Error from log
20150115 01380705 SQL {collection:"MAINHEAD.$cmd",opCode:"opQuery",requestID:5,query:{aggregate:"AuthUser",pipeline:"{$group:{_id:0,max:{$max:\"$_id\"}}}"},numberToReturn:1}
20150115 01380746 DB {ReplyHeader:{ResponseFlags:8,RequestID:49,ResponseTo:5,CursorID:0,StartingFrom:0,NumberReturned:1,ReplyDocuments:[{errmsg:"exception: wrong type for field (pipeline) 2 != 4",code:13111,ok:0}]}
Looking around I found these:
http://docs.mongodb.org/manual/referenc … op._S_type
http://stackoverflow.com/questions/1239 … 3111-error
I'm sorry, I forgot a part of the post ...
I wondered if that flag could help to avoid the risk of starting two servers with the same namedpipe.
Only the first working properly, the second does not detect any error.
I'm not very expert namedpipe, I wondered then if that flag could help in better manage the situation.
Hi AB,
I'm looking at the function of creating the namedpipe:
Why are you using this flag (PIPE_UNLIMITED_INSTANCES)?
Would not it be helpful to use FILE_FLAG_FIRST_PIPE_INSTANCE to prevent the creation of the same pipe?
So I like it!
You propose to add new features that maintain the consistent hash table, and let the generic ones for the use you've always rightly, ie to create a hash table only at the time of the search.
Or to decide in the creation of the object hash:
LiveHash / onDemandHash
I think that the benefit to the whole system is great, and also help to keep performing the rest of the programming.
For searches that are done on a list in memory always think it's always better to the help of the hash table to always have the results on time. Too many times you create bottlenecks for the lists that grow dramatically, beyond what you thought was right in development of the software.
certainly if you add values definitely unique gains on research on a set of data that does not change.
is a bit like adding a million records to a database without an index and then create it in the end.
but in the case of the sessions, or other data sets that change frequently,
continue to rehash all the elements lose the advantage of instant search.
HL:=TRawUTF8ListHashed.Create(true);
for t: = 1 to 50000 do
begin
Rec: = TmyOBJ.create;
Rec.id:='id_'+inttostr(t);
Rec.msg: = 'Msg';
HL.AddObjectIfNotExisting (Rec.id, Rec);
end;
41 seconds on I7-3820
I was watching the various procedures of hash in mormot, but I can not find the procedure for the single insertion or deletion.
It seems to me that you make extensive use of rehash, I'm wrong?
Hello AB,
I'm curious to know why you have chosen to manage the session list by scrolling and not through a hashlist.
I also noticed that with each new call you test the timeout.
I propose to save the minimum value (the next timeout) to the first test of the entire list by saving the timeout closer. So the next time you call, if the minimum is not reached, you can avoid doing the test on all elements.
Where ?
Hi AB.
I'm making several calls to the server rest, so I keep saved interface (MAINshd).
I would like to repeat the call automatically in case of connection failure.
var
retry: integer;
done: boolean;
Modd: TSQLModel;
CLID: TSQLHttpClient;
begin
[..]
retry: = 3;
repeat
done: = true;
try
res: = MAINshd.myInterfacedFunction ();
except
dec (retry);
MAINshd: = connc (SERVER_ADDR, user, pass, modd, CLID); // Rebuild interface
done:=retry<0;
end;
until done;
[..]
end;
It works, but I was wondering if there was a smarter way.
Keep in mind that I could reconnect to another server mORMot.
with 2 type of unmanageable data type we must add this:
function VarIsNullEmpty(const V: Variant): Boolean;
begin
Result := (FindVarData(V)^.VType = varNull) or (FindVarData(V)^.VType = varEmpty);
end;
@ab
bug is here:
with TdocVariantData(v) do addvalue('day',dayofweek(now));
addvalue must check if V is valid before adding.
do you agree?
Here the first bug with emptyvar:
procedure TForm1.Button6Click(Sender: TObject);
var
V:variant;
res:RAWutf8;
begin
res:='{mytime: '+floattostr(now)+'}';
v:=_json(res);
with TdocVariantData(v) do
addvalue('day',dayofweek(now));
memo1.Lines.Add('Result: '+rawutf8(v))
end;
initialization
if SetThreadLocale($0409) then
GetFormatSettings; // resets all locale-specific variables to LCID_US
with codepage initialization all is ok:
Result: {"mytime":42011.7791793634,"day":4}
but with italian codepage (comma for decimals):
Result:
With NO EXCEPTION!!!
I think also that it is better to think about the path taken by the compiler:
if a variant there is better to return a null.
a null value will become an exception and will be manageable.
But there's null us that there is no result, a mistake.
When you ask for an item that a function, returns null if you know that there is a valid result.
I agree on the speech of _Json ('') but we must remember that in a LATE BINDING case: an element not present is not empty but null.
so I am not sure that you want to change this result in case of error.
I think it's best to use a varIsNull to find fault.
I think having to test alternately varIsnull and / or varIsempty eventually bring more mistakes than good.
Ultimately, also to avoid having to change the past procedures, it would be better that the function returns a null _json on error and not an empty.
Hi AB,
in last 2 weeks you changed the function _json:
before _json ('') was returning a null variant
now returns a 'unassigned'
[..]
var
res:RAWutf8;
vres:variant;
begin
res:='';
vres:=_json(res);
if varisnull(vres) then exit; // now don't fire
first if you converted an empty string the variant was null, not anymore.
is the desired result or a bug?
Hello AB,
how can I do to save an object (eg database) in a session to use it in a REST interface?
My aim is to have a link to a specific external database without having to look at every call of the same authenticated session.
Tnxs
how ?
json-->array of PUTF8Char
sql:='select * from mysqlitedb';
r.Prepare(demo.DB,sql);
json:=demo.ExecuteJSON(sql,TRUE);
after run:
json:='[{_id:1,name:"test1"},{_id:2,name:"test2"},{_id:3,name:"test3"}]';
after I send it to a rest server:
var
pu:Putf8char;
begin
pu:=putf8char(json);
collection.InsertJSON(pu);
end;
collection is a TmongoCollection
the aim was to read a db SqlLite , and add in mongodb.
I thought the json generated is converted into an array, instead he inserirsce an array in an element.
how do I do it ?
Hi AB,
I'm trying to insert this:
[{_id:1,name:"test1"},{_id:2,name:"test2"},{_id:3,name:"test3"}]
using TmongoCollection.insertJSON
But I find a single line in mongodb:
{
"_id" : ObjectId("54937698dd6a6a28075ffb63"),
"0" : {
"_id" : 1,
"name" : "test1"
},
"1" : {
"_id" : 2,
"name" : "test2"
},
"2" : {
"_id" : 3,
"name" : "test3"
}
}
Why ?
How to reset the error (without having to reconnect) ?
Hi AB,
I ran into this error:
procedure TForm1.Button1Click(Sender: TObject);
var
t:integer;
Coll: TMongoCollection;
Docs: TVariantDynArray;
begin
log('Start');
try
fClient:=TMongoClient.Create('localhost',27017);
fDB:=fClient.Database['test25'];
Coll := fDB.CollectionOrCreate['testColl'];
coll.Insert('{"Age":?,Name: ?}',[1,'test1']);
coll.Insert('{"Age":?,Name: ?}',[2,'test2']);
if BADjson.Checked then
begin
try
coll.FindDocs('{Age:{$gt,1}}',[],docs,null); // <------------------------ bad syntax
for t:=Low(docs) to High(docs) do if not varisnull(docs[t].Name) then Log('Docs:'+docs[t].Name);
except
on e: exception do log('EXC1: '+e.Message);
end;
end;
Coll := fDB.CollectionOrCreate['testColl'];
try
coll.FindDocs('{Age:{$gt:1}}',[],docs,null); // <------------------------ right syntax
for t:=Low(docs) to High(docs) do if not varisnull(docs[t].Name) then Log('Docs:'+docs[t].Name);
except
on e: exception do log('EXC2: '+e.Message);
end;
finally
FreeAndNil(fClient);
log('end');
end;
end;
with BADsyntax checked output is:
EXC1: Server reset the connection: probably due to a bad formatted BSON request
EXC2: Server reset the connection: probably due to a bad formatted BSON request
...but the second call is correct.
How to reset the error (without having to reconnect) ?