Jump to content

Mark Williams

Members
  • Content Count

    274
  • Joined

  • Last visited

Posts posted by Mark Williams


  1. 1 hour ago, Kas Ob. said:

     

    i can conclude that security and permission might be in play here, right ? 

    It might be a UIPI issues as flagged by @David Heffernan initially and missed by me! But I don't think so. However not dealt with UIPI issues before so I may be handling it incorrectly,

     

    Whilst Word runs at a low level I think the DLL Add-In will be at the same level as my receiving app. Also, as you pointed out if there is an UIPI error then GetLastError is set to 5. My SendMessage is reporting success.

     

    However I have tried to incorporate ChangeWindowFilterEx within my receiving app to see if that makes any difference.

     

    type
      TChangeFilterStruct = packed record
        cbSize: DWORD;
        ExtStatus: DWORD;
      end;
      PChangeFilterStruct = ^TChangeFilterStruct;
    
      function ChangeWindowMessageFilterEx(hWnd: HWND; Message: UINT; Action: DWORD;
      ChangeFilterStruct: PChangeFilterStruct): Bool; stdcall; external 'user32.dll';
    
    const
      MSGFLT_ALLOW = 1;
      MSGFLT_DISALLOW = 2;
      MSGFLT_RESET = 0;
    
    {In formcreate:}
    
    ChangeWindowMessageFilterEx(Handle, WM_COPYDATA, MSGFLT_ALLOW, nil);

    If I am implementing this correctly there doesn't appear to be a UIPI error. 


  2. Word ID is a randomly generated integer value. It's not intended to be used as a handle. Just a way of identifying which Word window my app is communicating with. 

     

    My app passes its own handle to be stored in the word document as follows:

     

    Quote

    WordApp.ActiveDocument.Variables.Add('ViewerHandle', Handle);

    Within a word document the variables are stored as a wideString. To get the handle of my app from within the DLL I call this function which queries the Add In's host word window:

    Result:=0;
    val := Doc.Variables.Item(i).Value;
    tryStrToInt(val, hdl);
      Result := Hdl;

    Result is a THandle.

     

    Debugging a 64 bit AddIn is a bit problematic. So I have not looked into it yet. However, I have written to a log file to output what is happening at the various stages. I know that the hdl being passed to SendMessage is the correct hdl of my app. Also GetLastError returns success after SendMessage (if that means anything!)

     

    On My app I show the value of its handle on a label when it is running. Let's say its 12345678.

     

    If I create another Delphi executable and call

     

    WinAPI.Windows.SendMessage(12345678, WM_COPYDATA, 0, Integer(@copyDataStruct));

    That works as expected.

     

    If I do the same in my Add In DLL (ie substitute the hdl var with the Longint value of my app's handle) it doesn't work.

     


  3. I have a Word Add-In created with Add-In Express, although that's probably irrelevant.

     

    The Add-In can only be used for word docs created via a certain desktop app (MyAPP).

     

    When MyAPP creates a Word document it stores a unique identifier for that Word document and also the handle of MyAPP.

     

    I am trying to send information from the Add-In DLL back to MyApp using SendMessage and WM_COPYDATA.

     

    I have the following types declared in both the DLL and MyAPP:

    type TWordFunction = (wfNone, wfEdit, wfSave, wfLink, wfMasterWindow, wfBundle);
    
    type
      TWordActionRec = Record
        wf:TWordFunction;
        wordID:THandle;
    end;

    Within the DLL:

    procedure TAddInModule.DefaultButtonAction(wordFunction : TWordFunction);
    var
        WAR : TWordActionRec;
        copyDataStruct : TCopyDataStruct;
        hdl : THandle;
    begin
      hdl := GetViewerHandle;
      WAR.wf := WordFunction;
      WAR.wordID := GetWordIdentifier;
      copyDataStruct.dwData := 0;
      copyDataStruct.cbData := SizeOf(WAR);
      copyDataStruct.lpData := @WAR;
      WinAPI.Windows.SendMessage(hdl, WM_COPYDATA, 0, Integer(@copyDataStruct));
    end;

    The above runs without triggering any error and the hdl parameter is correct for MyAPP.

     

    Within MyAPP:

    procedure TForm1.WMCopyData(var Msg: TWMCopyData);
    var
      WAR : TWordActionRec;
    begin
      WAR := TWordActionRec(Msg.CopyDataStruct.lpData^);  
      ListBox1.Items.Add(WAR.wordID.ToString);
      Case WAR.wf of
        wfNone: ListBox2.Items.Add('None');
        wfEdit: ListBox2.Items.Add('Edit');
        wfSave: ListBox2.Items.Add('Save');
        wfLink: ListBox2.Items.Add('Link');
        wfMasterWindow: ListBox2.Items.Add('MW');
        wfBundle: ListBox2.Items.Add('Bundle');
      End;
    end;

    However, the DLL's message is never received by MyApp.

     

    If I take the code from the DDL and add it to a standard executable it works as expected. 

     

    I have Googled to see if there is any reason why SendMessage would not work within a DLL and as far as I can see there shouldn't be. 

     

    Is there anything wrong with my code or is this perhaps an Add-IN Express specific issue?


  4. Thanks to everyone for the detailed responses and suggestions.

     

    I think my fundamental error was my assumption (long-held and it seems with little basis) that FTP is faster than HTTP for multiple file transfers.

     

    I have already written an ISAPI dll using Delphi's TWebBroker which seems to be working well for my needs I just thought I could speed things up with FTP. From the above responses a better approach may be to more finely tune my ISAPI dll.

     

    The comments re Real Thin Client are noted and I will certainly look into this at a later point.

     

    In short, thanks for the feedback which it appears has saved me a potential mountain of unncessary work!


  5. I am looking for some general guidance on how to go about setting up an FTP server for the first time. I have opted for TIDFTPServer for this purpose and have been through the help files and searched Google, but I have come up with very limited information.

     

    I don't want to launch into this in my usual bull in a china shop manner only to find later that I have gone about it in the least appropriate way. So apologies in advance for the broad nature of this topic.

     

    In essence my requirements are for a basic FTP Server. However, I want to try and strictly control what users can upload and, in particular, download.

     

    My fledgling thoughts:

     

    Uploads

    The user requests permission for upload via my webserver and if authenticated is issued with a unique token to be stored in the database along with an expiry time which the user will need to renew periodically via the webserver and an identifier to identify which folder(s) the user is allowed to upload to.

     

    The FTP client would submit the token to the FTP server which would retrieve the token, expiry  time and folder identity prior to permitting any upload request. When the client has finished it will request the deletion of the token.

     

    There are likely often to be large numbers of files to be uploaded so preferably the FTP Server will be able to store the token, expiry time etc rather than querying the database for every upload. The FTP client would submit the token on every upload request.

     

    The FTP Server would check periodically whether the token has been removed.

     

    Downloads

    There may be a requirement for download of a large batch of files. So similar approach to the above. I already keep a record of every file in a database so each has a unique id and there is another table which records user rights to access a given file.

     

    Before submitting download request user applies for token via webserver and at the same time submits a list of ids of those file he intends to download. Webserver checks which ones user has access to and populates a table with the file ids ("permitted_files").

     

    On receiving the retrieve request the FTP does the same as for upload, but additionally queries the "permitted_files" table to see which files the user is permitted to download. 

     

    Questions

    1. Is this broadly a sensible approach?
    2. If not, how better to go about this?
    3. Assuming it is sensible, what do I need to implement this within the FTPServer component. My best guess at the moment is that it would be handled via CommandHandlers. 
    4. No GUI interface is required and I don't really need a command line so how best to implement this on the Server: as a Windows service? 
    5. As the FTP Server may be receiving numerous requests at the same time how best to manage the data relative to specific requests (ie tokens, permitted file ids etc).

     


  6. I am trying to create a FTP server for the first time using TIDFTPServer.

     

    I have dropped a TFTPServer onto a form and changed properties only as follows:


     

    Active = True
      Bindings = <
        item
          IP = '**.**.***.***'
          Port = 21
        end>
      ReuseSocket = rsTrue

    If I set the IP address as the external ip address for the server I get an EIDCouldNotBindSocket error and it advises that "Address and port are already in use" when I try to start it.

     

    I don't get this problem if I change the IP address to the server's local address (192.168.0.12).

     

    Should the IP address always be set to the local IP?

     

    I've noticed that IDFTP has an ExternalP property and also ServerHOST property. There is help on the ExternalIP property, but no on the ServerHOST property. Would I be right in assuming these properties are used for external connections to the FTP server rather than anything in the FTPServer's settings? With respect to the ExternalIP address this also seems to require setting of the DataPort. 

     

    I have tried connecting setting ExternalIP to the relevant IP address and DataPort to 21. Host is blank. When I try to connect I get an error message telling me a Host is required. I've tried setting Host to the external IP and also ServerHost, but either I get Host required error or time out.

     

     

     

     

     


  7. @Uwe Raabe 

    I have just had a look at this component on your blog. 

    I could get it to write an encrypted zip file, which I can open manually no problem, but I cannot get it to decrypt using the component. 

    I downloaded the amended unit from https://www.uweraabe.de/Blog/2017/05/07/tzipfile-with-password-encryption-part-3/, but this still didn't work for me.

     

    My code for unzipping is as follows:

     

    Zipper:=TEncryptedZipFile.Create('Mark');
    Stream:=TMemoryStream.create;
    try
      DecompressionStream:=TStream.Create;
      Zipper.Open(OpenDialog1.filename, zmReadWrite);
      Zipper.Read('File', DecompressionStream, zh);      
      Stream.CopyFrom(DecompressionStream, DecompressionStream.size); {Crashes here}
      DecompressionStream.Free;
      Stream.Position:=0;
      Stream.SaveToFile(ExtractFilePath(includeTrailingPathDelimiter(openDialog1.FileName))+'File.docx');
      Zipper.Close;
    finally
      zipper.Free;
      stream.Free;
    end;

    I have used the same routine as I would for reading from the standard TZipFile ie using the DecompressionStream. Can't work out whether my approach is write or if there is a problem with the TEncryptedZipFile code.

     

    Anyways my debug output is:

    System.Classes TStream.ReadBuffer
    System.Classes     8340   +1 TStream.ReadBuffer
    uEncryptedZipFile   299   +9 TDecryptStream.Skip
    uEncryptedZipFile   277  +10 TDecryptStream.Seek
    System.Classes     7561   +1 TStream.SetPosition
    System.ZLib        2891  +32 TZDecompressionStream.Read
    System.ZLib        2939  +27 TZDecompressionStream.Seek
    System.Classes     7569   +2 TStream.GetSize


  8. 28 minutes ago, Angus Robertson said:

    The usual solution to your problem is to use a long random file name that disappears once the download is completed, but you need to watch the log to see when that happens.

    Is it not possible to also password protect the folder? My intention is to maintain a record of the virtual folders in a database and require the user to signal continued use of the folder and as soon as there is a failure to do so the folder would be deleted.

     

    37 minutes ago, Angus Robertson said:

    The better solution is to you use a Delphi FTP server, like the ICS one I support, then you can control logins and directories yourself easily, using the ICS FTP Server sample you should have a working solution in a few hours, days faster than using WMI. 

    Is this the Overbyte stuff? I will have a look.

     

    Quote

    But it needs it's own IP address and port, will not help if you have to use IIS.

    So if I wanted to run IIS and ICS on the same server I would need a second static ip?


  9. 25 minutes ago, Lars Fosdal said:

    I've never set these up, but isn't that something you would configure on ISS itself, and then refer to from Delphi?

    Lots of security angles to consider

    Yes it's pretty easy to set up via IIS, but the security angle is precisely why I don't wish to do it this way. I want to check the user's credentials against  database and then issue that user a one-off password for a single transaction and add a temporary ftp folder, which then gets removed as soon as the user has completed the transaction. There may be better ways of doing this. As I said I'm a FTP novice!


  10. On 2/4/2020 at 11:30 PM, stijnsanders said:

    Ah well, IIS does its own thread management, that's right. And you have a choice: You could do all the work for a request in the HttpExtensionProc call you get and return HSE_STATUS_SUCCESS, it works really great for small responses and when it's pretty clear what the extension is supposed to do, and each request won't take too much time of a worker process. But, if you want do have more control over threads, over different kinds of things happening depending on the requests that come in, and especially keep threads free when they're waiting on other processes, the best you can do is return rightaway with HSE_STATUS_PENDING, and manage the threads to do the heavy lifting yourself.

    Thanks. I'll look into this at some point. However, my ISAPI dll isn't really designed to do heavy lifting. Just user validation, configuration settings and downloading of smallish files. 

     

    There is occasionally a need to upload/download largish files and this is something I am planning to move to FTP or maybe there is a better/faster solution?

     


  11. On 1/30/2020 at 3:57 PM, stijnsanders said:

    I would consider setting this to 0

    I'll give that a try and see how it pans out.

     

    On 1/30/2020 at 3:57 PM, stijnsanders said:

    I just keep queueing incoming requests in a queue the thread-pool can take from with any worker thread that happens to be available for work...

    I thought this was more or less what iis does with the dll.


  12. 5 minutes ago, Kas Ob. said:

    I suggest to at least skip one line when scanning, you can go up to 32 skipped lines while still have very low probability of false positive, you can tweak and decide what will work for you, just consider the height of the image, and i want to suggest to consider the divergence too, so lets say RGB with value like 05 05 05 is some grey shade but 06 05 05 is not, but very close and human eye might see it as grey and the scanner itself will not return white as pure white , for that use some formulas, 

    I was going to suggest the fast simple formula with calculating average of R G B and then divide sum with it and compare it with a factor you can tweak but i think you got the idea and here an detailed answer to work with saturation 

    https://stackoverflow.com/questions/16209111/given-the-rgb-components-of-a-color-how-can-i-decide-if-it-is-perceived-as-gray?noredirect=1&lq=1

     

    Ok. Thanks. I'll have a look at that and report back if I can improve on what I have already got.


  13. 7 minutes ago, pcplayer99 said:

    if the image file format is 32bit color bitmap, but the content is a black white picture, I think you can check it very quicklly by check pixels.

     

    You donn't need to check every pixels of the picture, you just need check severial pixels samples in the picture.

    Thanks. I've sort of come up with something along those lines using Scanlines. It seems to be tolerably effective and sufficiently fast.


  14. The default MaxConnections is set at 32 for TISAPIApplication.

     

    I have no idea how many people may be calling my dll at any given time. 32 seems to me to be too low a number. I have set it to 200.

     

    I cannot find any advice as to best policy on setting this value. The help file simply says to test the ActiveCount and InactiveCount and adjust accordingly. That is going to be a little difficult to test.

     

    I don't want to have exceptions flying off because of MaxConnection exceeded and likewise I don't want to degrade performance.

     

    If, for example, I set MaxConnection to 1000 I assume that will not of itself degrade performance and that there will only be issues when actual connections are that high,

     

    If that is right then it seems to me that slightly deteriorated performance is preferable to exceptions due to exceeding max connections.

     

    Or am I missing something?


  15. 3 hours ago, Attila Kovacs said:

    Well, I'd clone/backup the project and start removing all 3rd party units, including firedac.

    Solved at last. The DLL was hanging due to the call to AddToLog in CloseServer due to the Log thread being terminated at the same time as the call.

     

    In the meantime, I had added a TFDMoniFlatFileClientLink component to provide a FireDAC trace to see what was happening with the FDManager as I thought that was causing the problem.

     

    Once I'd fixed the problem with the thread log, I still had issues. By a process of starting from scratch (as you suggested) and adding units/components as I went along, I eventually managed to narrow it down to the FlatFile component. Once I removed it, all was well. Must be a threading problem or some such.

     

    Many thanks for your help

    • Like 1

  16. I've been through all my code. As far as I can see, I am destroying everything I create in an action event within the same action event.

     

    I have tried calling the DefaultHandlerAction and nothing else.

     

    procedure TWebModule1.WebModule1DefaultHandlerAction(Sender: TObject;
      Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
    begin
      Handled:=true;
      try
        SetResponse(Response, 400, 'Bad Request', 'Default Handler - if you are getting this message it either means that your are providing the wrong PathInfo or the action has been deleted.');
      except
      end;
    end;

    And SetResponse:

     

    Procedure SetResponse(var Response:TWebResponse; code:integer; statusText:String; debug:string='');
    begin
      Response.StatusCode:=code;
      Response.ReasonString:=code.ToString+' '+StatusText;
      if debug<>'' then
        AddToLog('Response set: '+Response.StatusCode.ToString+' '+StatusText+' Debug Info: '+debug, leMinorError)
      else
        AddToLog('Response set: '+Response.StatusCode.ToString+' '+StatusText, leDevelopment);
    end;

    It still hangs on shutdown.

     

    So I tried removing my calls to StartServer and CloseServer. Edited my DefaulHandlerAction so it didn't call SetResponse. It just sets the statuscode to 400 and still a huge delay in shutdown.

     

    My project file now  looks like this:

     

    library MWCWebServer;
    
    uses
      Winapi.ActiveX,
      System.Win.ComObj,
      System.SysUtils,
      Web.WebBroker,
      Web.Win.ISAPIApp,
      Web.Win.ISAPIThreadPool,
      WinAPI.Isapi2,
      WinAPI.Windows,
      System.SyncObjs,
      system.classes,
      WebModule in 'WebModule.pas' {WebModule1: TWebModule};
    
    {$R *.res}
    
    exports
      GetExtensionVersion,
      HttpExtensionProc,
      TerminateExtension;
    
    
    begin
      CoInitFlags := COINIT_MULTITHREADED;
      Application.Initialize;
      Application.WebModuleClass := WebModuleClass;
      Application.MaxConnections:=200; //MaxConnection=32 by default;
      IsMultiThread:=true;  
      Application.Run;
    end.

    I'm not creating anything over and above the webapplication itself and still it won't shut down properly.

     


  17. 16 minutes ago, Attila Kovacs said:

    - I'll assume then that you are aware that in case of TFDManager.Create(SomeComponent), including placing the component on a datamodule, FDManager will be free'd if SomeComponent is free'd.

    Yes. I'm sure that freeing of FDManager is not the issue.

     

    17 minutes ago, Attila Kovacs said:

    - I'm sure, that you have tried commenting out the whole OnTerminate, and IIS still stalls on stop.

    Yes.

     

    17 minutes ago, Attila Kovacs said:

    Let me know if you find something. I'm really curious.

    Will do.

    • Thanks 1
×