Jump to content

Remy Lebeau

Members
  • Content Count

    2322
  • Joined

  • Last visited

  • Days Won

    94

Everything posted by Remy Lebeau

  1. Remy Lebeau

    IMAP using TIdIMAP4

    No, you do not call the Authenticate methods directly. Yes, you need to assign a TIdUserPassProvider object to TIdSASLXOAuth2.UserProvider, and then assign the IMAP username to TIdUserPassProvider.Username, and either 1) assign the OAuth access token to TIdUserPassProvider.Password, or 2) set the TIdUserPassProvider.Password to a blank string and instead assign a handler to the TIdSASLXOAuth2.OnGetAccessToken event to return the access token. Then, simply set TIdIMAP4.AuthType to iatSASL, and call TIdIMAP4.Connect() with AAutoLogin=True (which is the default behavior), or call Connect() with AAutoLogin=False and then call TIdIMAP4.Login() when needed. You don't call them yourself at all. No to all of that. TIdIMAP4 and TIdSASLXOAuth2 will handle all of that detail internally for you. All you have to provide is the access token, as given to you by the OAuth provider. Just the OAuth access token. But it needs to be provided by TIdUserPassProvider.Password or TIdSASLXOAuth2.OnGetAccessToken, not by TIdIMAP4.Password. The TIdIMAP4.Username and TIdIMAP4.Password properties are used only when TIdIMAP4.AuthType=iatUserPass. Yes.
  2. Remy Lebeau

    Assigned(ErrorMessage) problem.

    Out parameters can't be optional. You will have to overload the function instead, eg: function DoValidateEmail(const EmailAddress: string; ErrorMessage: PString): Boolean; begin ... if (not Result) and Assigned(ErrorMessage) then ErrorMessage^ := ...; ... end; function ValidEmail(const EmailAddress: string; out ErrorMessage: string): Boolean; overload; begin Result := DoValidateEmail(EmailAddress, @ErrorMessage); end; function ValidEmail(const EmailAddress: string): Boolean; overload; begin Result := DoValidateEmail(EmailAddress, nil); end; Alternatively: function ValidEmail(const EmailAddress: string; out ErrorMessage: string): Boolean; overload; begin ... if not Result then ErrorMessage := ...; ... end; function ValidEmail(const EmailAddress: string): Boolean; overload; var LDiscarded: string; begin Result := ValidEmail(EmailAddress, LDiscarded); end;
  3. Remy Lebeau

    Using TIdHTTP in a thread

    Can you be more specific about what "stuck in-limbo" actually means? Recreating the TIdHTTP and TIdSSLIOHandlerSocketOpenSSL objects would work fine. How does it "not help" exactly? If you don't want to recreate the objects, you should be able to just Disconnect() the TIdHTTP and Clear() its IOHandler.InputBuffer if it has any unread data in it before the timeout occurred. That won't make any difference. Besides, TIdHTTP should already be disconnecting the socket if a read error occurs. So really, you might only need to clear the InputBuffer. You should not use the Method and SSLVersions properties together. They are mutually exclusive, setting one updates the other. In fact, don't even use the Method property at all. You don't need to do that manually at all. TIdSSLIOHandlerSocketOpenSSL calls SSL_set_tlsext_host_name() internally for you, and has done so since 2016. 'utf-8' is not a valid value for the 'Content-Encoding' request header. You need to use the 'charset' attribute of the 'Content-Type' header instead (ie, the Request.Charset property). Also, you are not telling the TStringStream to use UTF-8, so it will use the OS default instead, which is ANSI on Windows and UTF-8 on Posix, so you will mismatch your JSON data if it ever contains non-ASCII characters. Use this instead: vJsonToSend := TStringStream.Create(vJsonToPost, TEncoding.UTF8); ... FHTTP.Request.ContentType := 'application/json'; FHTTP.Request.Charset := 'utf-8'; The correct way to detect a read timeout is to check if the caught exception is an instance of the EIdReadTimeout class. Don't rely on parsing the exception's Message: if E is EIdReadTimeout then For that matter, why would you want to recreate the objects only on a read timeout, and not on other errors?
  4. Remy Lebeau

    Compiler Linker Question

    If you want the arrays contiguous, then why not just use a single array? const itemSize = 65; numItems = 3; var items: array[0..(itemSize*numItems)-1] of AnsiChar; function item1: PAnsiChar; begin Result := @items[0]; end; function item2: PAnsiChar; begin Result := @items[itemSize*1]; end; function item3: PAnsiChar; begin Result := @items[itemSize*2]; end; Or: const itemSize = 65; numItems = 3; type itemType = array[0..itemSize-1] of AnsiChar; var items: array[0..numItems-1] of itemType; function item1: PAnsiChar; begin Result := @items[0][0]; end; function item2: PAnsiChar; begin Result := @items[1][0]; end; function item3: PAnsiChar; begin Result := @items[2][0]; end;
  5. Remy Lebeau

    IMAP using TIdIMAP4

    Outlook365 requires OAuth authentication: https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth At this time, Indy does not have any components for retrieving an access token from an OAuth provider, so you will have to handle that yourself manually per the above documentation, such as with TIdHTTP. However, there is currently a sasl-oauth branch in Indy's GitHub repo (it hasn't been merged into the main code yet) which adds a few new TIdSASL components for sending OAuth access tokens for authentication in POP3, SMTP, IMAP, and DICT protocols. So, in this case, you could add the TIdSASLXOAuth2 component to the TIdIMAP4.SASLMechanisms property, and then use the OAuth access token as the password.
  6. Remy Lebeau

    Pop3 and TLS1.2 in Indy 10

    Port 995 is POP3's implicit-ssl port, so make sure you set the TIdPOP3.UseTLS property to utUseImplicitTLS. Alternatively, you can use POP3's explicit-tls port 587 with UseTLS=utUseExplicitTLS instead.
  7. Remy Lebeau

    Could not load OpenSSL library.

    And, what does WhichFailedToLoad() report, exactly? OpenSSL has never come pre-installed in Windows, and 3rd party apps SHOULD NOT be installing OpenSSL into the Windows system folders. That has nothing to do with Windows. Best practice is for apps to install their own copy of OpenSSL in their own local installation folders. You can get Indy-compatible versions of OpenSSL from https://github.com/IndySockets/OpenSSL-Binaries Please don't do that. Also, the IDE is a 32bit app, and 32bit DLLs DO NOT belong in the 64bit System32 folder.
  8. On Vista+, you can use RegisterPowerSettingNotification() to register to receive WM_POWERBROADCAST notifications for certain power events. On Windows 8+, you can use PowerRegisterSuspendResumeNotification() to receive power notifications via a callback function instead of WM_POWERBROADCAST. IIRC, however, modern Windows also has a newer fast standby/hibernation mode that doesn't sent power notifications to applications, it just pauses and resumes them as if nothing had happened. I can't find the details about that, but I remember being involved in past discussions about it, I just don't remember where.
  9. Remy Lebeau

    Send Email from Android with HTML message body

    Yeah, that is about what I found, too. Apparently, there aren't that many apps that actually support EXTRA_HTML_TEXT nowadays. You need an app password only if you don't use OAuth2 authentication, which is what Google wants you to use nowadays (well, they WANT you to use their own APIs, but if you have to use SMTP directly, use OAuth2). Indy's GitHub repo currently has a sasl-oauth branch which adds a few TIdSASL components for OAuth2, including Gmail's XOUATH2. You would still be responsible for implementing your own code to retrieve an OAuth2 token from Gmail (Indy doesn't have components for that purpose), but at least you could then have Indy use that token during SMTP authentication, no app password needed.
  10. Ah, that issue, yes. I keep forgetting about that. Yes, that was originally a bug in Indy that was fixed in 2019 prior to the release of 10.4.
  11. The RTL/VCL/FMX frameworks are much better supported in Classic, but spotty at best in Clang.
  12. Remy Lebeau

    AssertErrorProc usage

    Wow, that was actually a half-decent response, for once.
  13. There is a description in the PythonEngine source code: { A B C +-------------------++------------------------------------------------------+ | PyObject header || TPyObject class | +----------+--------++-----------------+------------+----------+------------+ |ob_refcnt |ob_type ||hidden Class Ptr |PythonType |IsSubType |PythonAlloc | |integer |pointer ||pointer |TPythonType |Boolean |Boolean | |4 bytes |4 bytes ||4 bytes |4 bytes |1 byte |1 byte | +----------+--------++-----------------+------------+----------+------------+ ^ ^ | | ptr returned ptr returned by Adjust by GetSelf - a Python object must start at A. - a Delphi class class must start at B - TPyObject.InstanceSize will return C-B - Sizeof(TPyObject) will return C-B - The total memory allocated for a TPyObject instance will be C-A, even if its InstanceSize is C-B. - When turning a Python object pointer into a Delphi instance pointer, PythonToDelphi will offset the pointer from A to B. - When turning a Delphi instance into a Python object pointer, GetSelf will offset Self from B to A. - Properties ob_refcnt and ob_type will call GetSelf to access their data. } Internally, Adjust() calls PythonToDelphi(): procedure TPyObject.Adjust(PyPointer: Pointer); var ptr : PNativeInt; begin ptr := PyPointer; ptr^ := NativeInt(PythonToDelphi(PPyObject(ptr^))); end; function PythonToDelphi( obj : PPyObject ) : TPyObject; begin if IsDelphiObject( obj ) then Result := TPyObject(PAnsiChar(obj)+Sizeof(PyObject)) else raise EPythonError.CreateFmt( 'Python object "%s" is not a Delphi class', [GetPythonEngine.PyObjectAsString(obj)] ); end; I have no idea.
  14. The FIdSSLIOHandler.PassThrough property is True by default, so the connection is treated as a plain TCP connection. You need to set the PassThrough property to False to initialize the TLS handshake. So, that is why you are getting the error on your 1st Send(). Your application data is the 1st thing being transmitted, so the server is mistaking that as the TLS handshake, hence the error. You can set the PassThrough to False either before calling Connect() (ie, for implicit TLS, sending the handshake as soon as the TCP connection is established) or afterwards (ie, for explicit TLS, to allow for a protocol-level STARTTLS-style command before sending the handshake): procedure myFoo; var FIdTCPClient : TIdTCPClient; FIdSSLIOHandler : TIdSSLIOHandlerSocketOpenSSL; begin FIdTCPClient := TIdTCPClient.Create; FIdTCPClient.Host := '10.10.10.10'; FIdTCPClient.Port := 10007; FIdSSLIOHandler := TIdSSLIOHandlerSocketOpenSSL.Create; FIdSSLIOHandler.SSLOptions.Mode := sslmClient; FIdSSLIOHandler.SSLOptions.VerifyMode := []; FIdSSLIOHandler.SSLOptions.VerifyDepth := 0; FIdSSLIOHandler.SSLOptions.SSLVersions := [sslvTLSv1_2]; FIdTCPClient.IOHandler := FIdSSLIOHandler; FIdSSLIOHandler.PassThrough := False; // <-- either here FIdTCPClient.Connect; // <-- will send the handshake if PassThrough is False FIdSSLIOHandler.PassThrough := False; // <-- or here, optionally after sending a STARTTLS command FIdTCPClient.Send([0,1,2,3]); end; That is because you are explicitly setting Sock.SslEnable to True and then calling Sock.StartSslHandshake() after calling Sock.Connect(). You are not doing the equivalent with Indy. No, it is a bug in your code. You are telling ICS to initiate the TLS handshake, but you are not telling Indy to do the same. This is not a new issue in 11.3. This is how Indy has always behaved. Yes - the PassThrough property. OpenSSL DLLs that are known to work with Indy are available in Indy's GitHub repo: https://github.com/IndySockets/OpenSSL-Binaries
  15. Remy Lebeau

    Send Email from Android with HTML message body

    That is not what I suggested. I suggested putting plain text (no HTML code) in EXTRA_TEXT, and HTML code in EXTRA_HTML_TEXT. I doubt it. You will likely have to import it manually. Importing an Android Class For Use in Delphi Convert any Android API to Delphi and C++ Builder units to utilize in your FireMonkey Android Projects Once you have it imported properly, I think it would just be as simple as: Intent.putExtra(TJIntent.JavaClass.EXTRA_HTML_TEXT, TJHtml.JavaClass.fromHtml(StringToJString(sMessageBody), 0) );
  16. Remy Lebeau

    ScanTime issue

    What about it?
  17. Remy Lebeau

    How to translate PSAPI_WORKING_SET_BLOCK?

    You don't need to pass in NativeUInt indexes to begin with. You are passing in 2 very small values (offset and bit count) which you can bit-stuff into a plain Integer just fine (and pay attention that you are using hex values correctly!), eg: type STRUCT_PSAPI_WORKING_SET_BLOCK = record private Flags: ULONG_PTR; function GetBits(const aIndex: Integer): ULONG_PTR; procedure SetBits(const aIndex: Integer; const aValue: ULONG_PTR); public property Protection: ULONG_PTR index $0005 read GetBits write SetBits; // 5 bits at offset 0 property ShareCount: ULONG_PTR index $0503 read GetBits write SetBits; // 3 bits at offset 5 property Shared: ULONG_PTR index $0801 read GetBits write SetBits; // 1 bit at offset 8 property Reserved: ULONG_PTR index $0903 read GetBits write SetBits; // 3 bits at offset 9 {$IFDEF WIN64} property VirtualPage: ULONG_PTR index $0C34 read GetBits write SetBits; // 52 bits at offset 12 {$ELSE} property VirtualPage: ULONG_PTR index $0C14 read GetBits write SetBits; // 20 bits at offset 12 {$ENDIF} end; function MakeMask(NumBits: UInt8): ULONG_PTR; var I: UInt8; begin Result := 0; for I := 1 to NumBits do begin Result := (Result shl 1) or 1; end; end; // depending on C compiler implementation, you might need to // reverse the math on these bit shifts, as bitfield ordering // is not standardized! function STRUCT_PSAPI_WORKING_SET_BLOCK.GetBits(const aIndex: Integer): ULONG_PTR; var Offset: NumBits: UInt8; Mask: ULONG_PTR; begin Offset := UInt8(aIndex shr 8); NumBits = UInt8(aIndex); Mask := MakeMask(NumBits); Result := (Flags shr Offset) and Mask; end; procedure STRUCT_PSAPI_WORKING_SET_BLOCK.SetBits(const aIndex: Integer; const aValue: ULONG_PTR); var Offset: NumBits: UInt8; Mask: ULONG_PTR; begin Offset := UInt8(aIndex shr 8); NumBits = UInt8(aIndex); Mask := MakeMask(NumBits); Flags := Flags and not (Mask shl Offset); Flags := Flags or ((aValue and Mask) shl Offset); end; That being said, I would have translated each bitfield into its own getter/setter instead, to more closely mimic how bitfields actually work in C, and to pass around more appropriate data types, eg: type VirtualPageValueType = {$IFDEF WIN64}UInt64{$ELSE}UInt32{$ENDIF}; _PSAPI_WORKING_SET_BLOCK_STRUCT = record private Flags: ULONG_PTR; function GetProtection: UInt8; function GetShareCount: UInt8; function GetShared: Boolean; function GetReserved: UInt8; function GetVirtualPage: VirtualPageValueType; procedure SetProtection(const AValue: UInt8); procedure SetShareCount(const AValue: UInt8); procedure SetShared(const AValue: Boolean); procedure SetReserved(const AValue: UInt8); procedure SetVirtualPage(const AValue: VirtualPageValueType); public property Protection: UInt8 read GetProtection write SetProtection; property ShareCount: UInt8 read GetShareCount write SetShareCount; property Shared: Boolean read GetShared write SetShared; property Reserved: UInt8 read GetReserved write SetReserved; property VirtualPage: VirtualPageValueType read GetVirtualPage write SetVirtualPage; end; _PSAPI_WORKING_SET_BLOCK = record case Integer of 0: (Flags: ULONG_PTR); 1: (Struct: _PSAPI_WORKING_SET_BLOCK_STRUCT); end; PSAPI_WORKING_SET_BLOCK = _PSAPI_WORKING_SET_BLOCK; PPSAPI_WORKING_SET_BLOCK = ^STRUCT_PSAPI_WORKING_SET_BLOCK; ... // Notes: // // hex | binary // --------------------------- // $01 | %0000_0001 // $07 | %0000_0111 // $1F | %0001_1111 // $FFFFF | %1111_1111_1111_1111_1111 // $FFFFFFFFFFFFF | %1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111 // // depending on C compiler implementation, you might need to // reverse the math on these bit shifts, as bitfield ordering // is not standardized! function _PSAPI_WORKING_SET_BLOCK_STRUCT.GetProtection: UInt8; begin Result := UInt8(Flags and $1F); end; function _PSAPI_WORKING_SET_BLOCK_STRUCT.GetShareCount: UInt8; begin Result := UInt8((Flags shr 5) and $07); end; function _PSAPI_WORKING_SET_BLOCK_STRUCT.GetShared: Boolean; begin Result := ((Flags shr 8) and $01) <> 0; end; function _PSAPI_WORKING_SET_BLOCK_STRUCT.GetReserved: UInt8; begin Result := UInt8((Flags shr 9) and $07); end; function _PSAPI_WORKING_SET_BLOCK_STRUCT.GetVirtualPage: VirtualPageValueType; const cMask: ULONG_PTR = {$IFDEF WIN64}$FFFFFFFFFFFFF{$ELSE}$FFFFF{$ENDIF}; begin Result := VirtualPageValueType((Flags shr 12) and cMask); end; procedure _PSAPI_WORKING_SET_BLOCK_STRUCT.SetProtection(const AValue: UInt8); begin Flags := Flags and not $1F; Flags := Flags or (ULONG_PTR(aValue) and $1F); end; procedure _PSAPI_WORKING_SET_BLOCK_STRUCT.SetShareCount(const AValue: UInt8); begin Flags := Flags and not ($07 shl 5); Flags := Flags or ((ULONG_PTR(aValue) and $07) shl 5); end; procedure _PSAPI_WORKING_SET_BLOCK_STRUCT.SetShared(const AValue: Boolean); begin if AValue then Flags := Flags or (ULONG_PTR(1) shl 8) else Flags := Flags and not ($1 shl 8); end; procedure _PSAPI_WORKING_SET_BLOCK_STRUCT.SetReserved(const AValue: UInt8); begin Flags := Flags and not ($07 shl 9); Flags := Flags or ((ULONG_PTR(aValue) and $07) shl 9); end; procedure _PSAPI_WORKING_SET_BLOCK_STRUCT.SetVitualPage(const AValue: VirtualPageValueType); const cMask: ULONG_PTR = {$IFDEF WIN64}$FFFFFFFFFFFFF{$ELSE}$FFFFF{$ENDIF}; begin Flags := Flags and not (cMask shl 12); Flags := Flags or ((ULONG_PTR(aValue) and cMask) shl 12); end;
  18. Remy Lebeau

    ScanTime issue

    DateTimeToStr() calls DateTimeToString() with its Format parameter set to a blank string, which will set the Format to 'C' as a default: When formatting a time value, milliseconds will be output only if the format string includes the 'Z' placeholder. But there is currently no logic to handle the decimal separator, so yes, if the format string has something like '.ZZZ' then the '.' will be output as-is as a literal character, not translated to TFormatSettings.DecimalSeparator. Sounds reasonable. That kind of information should be stated in the bug report. I have added a comment about it.
  19. Remy Lebeau

    Send Email from Android with HTML message body

    If you include EXTRA_HTML_TEXT, that is supposed to be an alternative format to the plain text in EXTRA_TEXT. So maybe don't put HTML code in EXTRA_TEXT if you also put it in EXTRA_HTML_TEXT? Note that not all (or very few?) email apps actually support EXTRA_HTML_TEXT, which is why EXTRA_TEXT is also required. So it could be that on your device, EXTRA_HTML_TEXT is being ignored and displaying EXTRA_TEXT as plain text instead, which might explain why you are seeing the HTML code as plain text instead of as formatted text. Also, when adding HTML code to EXTRA_TEXT or EXTRA_HTML_TEXT, try passing it through the Html.FromHtml() method first, rather than adding it as a plain string.
  20. Remy Lebeau

    ScanTime issue

    Are you referring to this ticket? [RSP-41434] StrToDateTime uses Decimalseparator for microseconds There is not much useful information provided in that ticket, and certainly not a reproducible example. I can imagine Embarcadero closing the ticket as "not reproducible" or "works as designed". Also, you filed the ticket as a bug (which it is not, IMHO, at least not the way you described it), but you are proposing a new feature at the end, so you should have filed the ticket as a Feature Request instead of as a Bug. In any case, I don't understand what the actual problem is. You say in the ticket: ScanTime() uses TFormatSettings.DecimalSeparator to parse milliseconds, so what is stopping you from specifying a custom separator when parsing a string? var Fmt: TFormatSettings; Fmt := TFormatSettings.Create; Fmt.TimeSeparator := ':'; Fmt.DecimalSeparator := '!'; StrToDateTime('12:34:56!789', Fmt); I don't see that being necessary at all. Especially since most platforms don't define such a separator in their respective environments anyway, AFAIK.
  21. Remy Lebeau

    Does C++ Builder have a Clear() method?

    Some UI controls do, and some do not. A TLabel control does not have a Clear() method, in either VCL or FMX. Also, VCL's TLabel does not have a Text property, it has a Caption property instead: labelAnswer->Caption = ""; On the other hand, FMX's TLabel does have a Text property. VCL's TEdit does have a Clear() method: Edit1->Clear(); FMX's TEdit, on the other hand, does not. However, it does have SelectAll() and DeleteSelection() methods instead: Edit1->SelectAll(); Edit1->DeleteSelection();
  22. Remy Lebeau

    Forms showing on multiple desktops

    Maybe not in your code, but who knows what the VCL does internally. MainFormOnTaskbar=true does some really funny things. In any case, have you tried verifying with GetWindowLongPtr(GWL_EXSTYLE) or Spy++ or equivalent that your affected windows don't actually have that style when you are not expecting it? Just a thought. Can you produce a bare-bones test project with minimal code that reproduces the behavior?
  23. Remy Lebeau

    Open IDE in DPI Unaware??

    Not even if you edit the Registry to add that command-line parameter to the file association that launches the IDE?
  24. Remy Lebeau

    How do I convert from string to char?

    The expression: ch + " is " + chValue Does not do what you think it does. It does not perform string concatenation, it actually performs pointer arithmetic instead. The string literal " is " is a 'const char[5]' array, which in this context will decay into a 'const char*' pointer to its 1st character. You are adding a 'char' and an 'int' (both integer types) to that pointer, moving around where it points in memory. So, for example, if 'A' is entered, then you are advancing the pointer forward in memory by 65+65=130 characters. And then, you are assigning that final pointer to the Label->Caption, which is why you end up with garbage output. If you are going to append integer values to strings, make sure you append them to String objects, not to char* pointers. AnsiString and UnicodeString have overloaded constructors for converting various data types, including integers, eg: Char ch = EditCharacterEntry->Text[1]; int chValue = static_cast<int>(ch); LabelAnswer->Caption = ch + String(" is ") + chValue; Alternatively, you can convert the integers to String object before concatenating them, eg: Char ch = EditCharacterEntry->Text[1]; int chValue = static_cast<int>(ch); LabelAnswer->Caption = String(ch) + " is " + String(chValue); // or IntToStr(chValue) Alternatively, use a function that to designed for formatting strings from different types of inputs, such as Sysutils::Format() or String::sprintf(), eg: Char ch = EditCharacterEntry->Text[1]; int chValue = static_cast<int>(ch); LabelAnswer->Caption = Format("%s is %d", ARRAYOFCONST((ch, chValue))); or LabelAnswer->Caption = String().sprintf(_D("%c is %d"), ch, chValue);
  25. Remy Lebeau

    Set pipeline between UI and cmd.exe

    It is equally important to make sure that only the remote ends of the pipe are actually inherited, not the local ends, or else you won't be able to handle the pipes being closed correctly when the child process exits. And also, be careful if you call CreateProcess() multiple times with bInheritHandles=TRUE, as each child process may end up inheriting handles you didn't intend for it to inherit. Vista addressed that issue with the PROC_THREAD_ATTRIBUTE_LIST option of STARTUPINFOEX: Programmatically controlling which handles are inherited by new processes in Win32 Another way to create a process with attributes, maybe worse maybe better
×