Setup Google Account for Google Drive API Walkthrough

A software developer had trouble getting started using Chilkat with his application for Google Drive. He gave me his login/password to help get him started. This blog post is a recording of the steps I’ve taken to set things up on the Google side to allow an application to access Google Drive files using a Client_ID and Client_Secret.

A Client_ID/Secret implies that the developer will need to create a Project in Google. Once created, we may need to generate keys.

Step 1: Log in to the Google Account and get our Bearings

Ok, I logged into the customer’s Google account. (I notify the customer before doing so, just so he knows exactly when I’m working on it, and I notify the customer when I’m finished.) Often logging in requires the customer to allow it because Google will send a text message or some other notification indicating a login from an unknown device…

OK.. so the login is OK, and I go to Google Drive and see 2 JPG files and a Getting Started PDF that was created by Google. When I run my test application, my goal is to see these 2 JPG files listed..

Step 2: Possibly Create an Project in Google

We may need to create a Project in Google to correspond to our actual application written in some programming language (VB6, C#, C++, Perl, Java, Ruby, …) which uses Chilkat.

I go to https://console.developers.google.com/?authuser=1 The top right portion of the web page looks like this:

Notice the “My Project” dropdown at the top of the web page, just to the left of the “Google APIs” logo. This shows the currently selected project. Google likely automatically added the “My Project”. You may create a new project by clicking on the dropdown and following the links. I’ll use “My Project” for my testing..

Step 3: Enable the Google Drive API

I find that the customer already enabled the Google Drive API. That’s good. If it’s not enabled, then click on the “Enable API” link, find Google Drive, and enable it.

Step 4: Do we have Credentials Setup?

Click on the “Credentials” link in the left rail. I find 2 credentials already created: One listed under “API Keys”, and the other listed under OAuth 2.0 client IDs. Here’s a screenshot. I omitted most of the API key for security. The client ID does not need to be kept secret.

We’ll need an OAuth 2.0 client ID, but it must be the correct type. We won’t be using anything listed under the “API keys”. In this case, the Client ID named “Other client 1” has the type “other”, and is not the type of Client ID we need.

Even though our application may not be a web app, we’ll still need an OAuth client ID for a web application. (Our application could be a desktop app, such as C#, VB6, Delphi, C++, Java, etc., or it could be a script in Perl, Python, PHP, Ruby, VBScript, etc.)

Step 5: Create Credentials of the Correct Type.

Click on the Create Credentials dropdown and choose OAuth client ID.

As shown below, choose “Web application” (even though your app is a desktop app or script), and add an Authorized redirect URIs using “localhost”, such as “http://localhost:55568/”.   The 55568 is a port number.  You may choose any port that is likely not already used on the system.  Choose a port in the ephemeral port range.  Note: The redirect URL must end in a “/”.  Do not use “https”, use “http”.  (This is not a security issue because the callback is from the browser to  your app running on the same system.)

Looking at our credentials again, we can see the Client ID we just created:

Step 6: Get the Client ID and Client Secret.

Click on “Chilkat test 1” to get the Client ID and Client Secret.  These are the strings we’ll need to fetch an OAuth2 access token.  We’ll only need to fetch it once.  The access token can be saved to a file, database, etc. and then used with our API calls, even for the next time we run our application.  In other words, you may persist the access token.

Step 7: Use the Client ID and Client Secret to get an Access Token.

Here’s sample code demonstrating how to do it:

C: Google OAuth2 Access Token
Chilkat2-Python: Google OAuth2 Access Token
C++: Google OAuth2 Access Token
C#: Google OAuth2 Access Token
DataFlex: Google OAuth2 Access Token
Delphi ActiveX: Google OAuth2 Access Token
Delphi DLL: Google OAuth2 Access Token
.NET Core C#: Google OAuth2 Access Token
Excel: Google OAuth2 Access Token
Visual FoxPro: Google OAuth2 Access Token
Java: Google OAuth2 Access Token
Lianja: Google OAuth2 Access Token
MFC: Google OAuth2 Access Token
Mono C#: Google OAuth2 Access Token
Node.js: Google OAuth2 Access Token
Objective-C: Google OAuth2 Access Token
Perl: Google OAuth2 Access Token
PHP ActiveX: Google OAuth2 Access Token
PHP Extension: Google OAuth2 Access Token
PowerBuilder: Google OAuth2 Access Token
PowerShell: Google OAuth2 Access Token
PureBasic: Google OAuth2 Access Token
Python: Google OAuth2 Access Token
Ruby: Google OAuth2 Access Token
Swift: Google OAuth2 Access Token
Tcl: Google OAuth2 Access Token
Unicode C: Google OAuth2 Access Token
Unicode C++: Google OAuth2 Access Token
Visual Basic 6.0: Google OAuth2 Access Token
VB.NET: Google OAuth2 Access Token
VBScript: Google OAuth2 Access Token
Xojo Plugin: Google OAuth2 Access Token

Step 8: Use the Access Token in an API Call

C: List Files in Google Drive
Chilkat2-Python: List Files in Google Drive
C++: List Files in Google Drive
C#: List Files in Google Drive
DataFlex: List Files in Google Drive
Delphi ActiveX: List Files in Google Drive
Delphi DLL: List Files in Google Drive
.NET Core C#: List Files in Google Drive
Excel: List Files in Google Drive
Visual FoxPro: List Files in Google Drive
Java: List Files in Google Drive
Lianja: List Files in Google Drive
MFC: List Files in Google Drive
Mono C#: List Files in Google Drive
Node.js: List Files in Google Drive
Objective-C: List Files in Google Drive
Perl: List Files in Google Drive
PHP ActiveX: List Files in Google Drive
PHP Extension: List Files in Google Drive
PowerBuilder: List Files in Google Drive
PowerShell: List Files in Google Drive
PureBasic: List Files in Google Drive
Python: List Files in Google Drive
Ruby: List Files in Google Drive
Swift: List Files in Google Drive
Tcl: List Files in Google Drive
Unicode C: List Files in Google Drive
Unicode C++: List Files in Google Drive
Visual Basic 6.0: List Files in Google Drive
VB.NET: List Files in Google Drive
VBScript: List Files in Google Drive
Xojo Plugin: List Files in Google Drive

 

 

Java to copy byte[] to/from a CkByteArray

The Chilkat online reference documentation for the CkByteData class, concerning the methods for copying Java byte arrays into and out of a CkByteData object, are incorrect.

This is the correct way to do it:

CkByteData byteData = new CkByteData();

// Create a Java byte array with some bytes..
byte[] myData = "Any String you want".getBytes();

// Copy the bytes from the Java byte array into the CkByteData.
byteData.appendByteArray(myData);

// Copy the bytes from the CkByteData to a new Java byte array.
byte[] myData2 = byteData.toByteArray();

Chilkat v9.5.0.68 Release Notes

The v9.5.0.67 release notes are available here:  Chilkat v9.5.0.67 Release Notes

v9.5.0.68 Release Notes:

Version 9.5.0.68 is virtually the same as 9.5.0.67, but with one important bug fix in the JsonOjbect.UpdateString method that warranted a new release.  Some additional methods were added to JsonObject and Http to help in handling potentially large amounts of data.

  • JsonObject.UpdateString — The passed-in string was not being JSON escaped like it was for JsonObject.AppendString.  This would cause problems for non-usascii chars, or certain special chars that need to be escaped, such as double-quotes, LF’s, CR’s, etc.
  • JsonObject:  Added the following methods:  StringOfSb, BytesOf, UpdateSb, and UpdateBd.  The need for these methods became apparent when writing examples for Microsoft Graph / Outlook.  The purpose of the methods is to access a potentially large amount of data within JSON without needing to pass the data back/forth in arguments or return values.  Passing StringBuilder or BinData object references is far more efficient than passing the actual data.
  • Http:  Added the PostJson3 method.  This is the same as PostJson2, except rather than passing the JSON as a string (which could be a potentially large string), we simply pass the JsonObject reference.

Using Chilkat Mono in a Visual Studio C# Project

The Chilkat Mono assembly can be used in a Visual Studio project (any version of Visual Studio).

(From a programming perspective, using the Chilkat Mono classes is the same as using the Chilkat .NET classes. The Chilkat .NET assemblies (available from Chilkat .NET Downloads are mixed-mode assemblies. The outer-layer is fully managed, and the inner core contains the native C++ implementation. The Chilkat Mono solution is different: The .NET/Mono assembly is 100% managed, and there is a 2nd DLL that contains the native C++ implementation. The 100% managed assembly (internally) calls into the native DLL using P/Invoke.)

Actually.. you don’t even need to build the 100% managed assembly.  You can instead just add the Chilkat *.cs source files to your project directly.  (If you open one of the .cs source files, you’ll see how P/Invoke is used.)

  1. Created a Visual Studio project in C:\MyProject
  2. Downloaded the Chilkat Mono .zip from https://www.chilkatsoft.com/mono.asp  and unzipped in C:\MyProject
  3. You now have a directory c:\MyProject\chilkatMono-9.5.0.    Within that directory, I have a chilkatCs directory, and a nativeDll directory.
  4. Add all of the .cs source files from chilkatCs to your Visual Studio C# project.  (Note: If you find an “Accounts.cs” source file, delete it and do not add it.)
  5. You don’t necessarily need to add all of the .cs sources, but you must add whatever is necessary to resolve all references.  For example, if you add MailMan.cs, you’ll certainly need to add Email.cs, and that in turn will require Cert.cs, etc.  A good strategy is to add everything, then delete various things that are likely unreferenced by other things.  For example, you can safely get rid of Csr.cs if not using CSR’s.
  6. Add code to your app.  For example:

    Chilkat.Zip zip = new Chilkat.Zip();
    MessageBox.Show(zip.Version);

  7. You can try building and running, but it’ll fail because the native DLL won’t be found..
  8. Copy the desired 32-bit or 64-bit native DLL into the same directory where your .exe is created.  For example, copy c:\MyProject\chilkatMono-9.5.0\nativeDll\windows\x64\chilkatMono-9_5_0.dll to c:\MyProject\bin\Debug
  9. Assuming your Visual Studio project is “Any CPU”, and you don’t have a “Prefer 32-bit” checkbox checked (in Project Settings), and that you’re running on a 64-bit system, then the .NET runtime should locate and load the 64-bit chilkatMono-9_5_0.dll, and your program runs fine.

Chilkat v9.5.0.67 Release Notes

The v9.5.0.66 release notes are available here:  Chilkat v9.5.0.66 Release Notes

v9.5.0.67 Release Notes:

  • HTTP: Fixed a problem when auto-following redirects from HTTP to HTTPS.  If the cached connection existed for long enough, and the server closed the connection at some previous point, then the redirect would fail.
  • SFtp: Added the FileExists method.
  • HTTP: Fixed redirects requiring an authentication header.  Chilkat automatically removes the Authorization header when automatically following a redirect.  This caused problems for redirects within the same domain where the Authorization is needed.  Chilkat now removes the Authorization header if the redirect is to a different domain, but keeps it if the redirect is within the same domain.
  • Spider: Fixed the AddMustMatchPattern method.
  • BinData: Added the GetString method.
  • Mime: Added the GetBodyBd method.
  • Email: Added the GetAttachmentBd and GetAlternativeBodyBd methods.
  • Gzip: Added the CompressBd and UncompressBd methods.
  • Email, Crypt2, Mime: Added the OaepPadding and OaepHash properties.
  • Tar: Fixed a problem caused when a file to be added to the .tar is exactly 4294967295 bytes in size (which made the file size look like -1 when the 4-byte integer is interpreted as a signed integer).
  • JsonObject: Added new methods: EmitWithSubs, Predefine, LoadPredefined.
  • Http: Added the AuthToken property.
  • Http: Added the SetUrlVar and ClearUrlVars methods.
  • HtmlToXml: Better handles HTML with unclosed tags in certain situations.
  • Http: For keepalive connections, fixed the internal auto-recovery (in certain situations) if the connection is discovered broken upon sending the request.  For example, if a request/response occurs and the connection is keep-alive, then time passes, the HTTP server closes the connection, and the client (your app) sends the next request.  Chilkat will discover the connection is non-existent and will automatically re-connect.
  • SFtp: Fixed the ReadFileBytes method for the case where one tries to read more bytes than what exists in the remote file.
  • Ssh: Added the StripColorCodes property.
  • Added the “base64_mime” to the list of possible encodings.  This is the same as “base64”, but causes the base64 to be emitted in lines just like it would be found in MIME or in a PEM.
  • Crypt2: Added the following BinData/StringBuilder methods:  VerifySbENC, VerifyBdENC, SignSbENC, SignBdENC, OpaqueVerifyBd, OpaqueSignBd, EncryptSb, EncryptBd, DecryptSb, DecryptBd.
  • Added RSASSA-PSS and RSAES-OAEP capabilities for signed and encrypted email.  This was spurred by customer needs to satisfy new requirements in Germany that apparently go into effect on 1-June-2017 regarding email signatures and encryption.
  • Apple tvOS:  Added a build for tvOS.
  • ZipEntry: Added methods UnzipToStream, UnzipToBd, and UnzipToSb.
  • Stream: Added methods ReadBd, ReadSb, WriteBd, WriteSb
  • NTLM Proxy Authorizations: Fixed some HTTP NTLM proxy authorization issues, especially when TLS is involved.
  • Ftp2: Added the GetFileToStream method
  • CkDateTime: Added the ExpiresWithin and OlderThan methods.
  • OAuth2: Added the Resource property.
  • JsonObject: Added the AppendStringArray property.
  • MailMan: Added the StartTLSifPossible property.
  • PublicKey: Added the KeySize property.
  • VC++ 7.1:  Added a build for VC++ 7.1.
  • Tar: Fixed the “Invalid octal string for file size” error.
  • JsonObject: Added the FindObjectWithMember method.
  • Certificates:  Chilkat now fully supports ECC certificates in all ways.

C# — Load a TreeView from JSON, Save a TreeView to JSON

Here are a few snippets of code I needed for internal tools…

TreeView –> JSON

private void recurseTree(TreeNode treeNode, Chilkat.JsonObject json)
    {
    string tag;
    if (treeNode.Tag == null)
        {
        tag = treeNode.Name;
        }
    else
        {
        tag = treeNode.Tag.ToString();
        if (tag.Length == 0)
            {
            tag = treeNode.Name;
            }
        }

    int numChildren = treeNode.Nodes.Count;
    if (numChildren == 0)
        {
        json.AppendString(tag, treeNode.Text);
        return;
        }

    Chilkat.JsonObject jObj = json.AppendObject(tag);

    foreach (TreeNode tn in treeNode.Nodes)
        {
        recurseTree(tn,jObj);
        }
    }

private void btnTreeViewToJson_Click(object sender, EventArgs e)
    {
    // Convert treeview1 to JSON.
    Chilkat.JsonObject json = new Chilkat.JsonObject();

    TreeNodeCollection nodes = treeView1.Nodes;
    foreach (TreeNode n in nodes)
        {
        recurseTree(n,json);
        }

    json.EmitCompact = false;
    textBox1.Text = json.Emit();
    }

JSON –> TreeView

void buildTreeView(TreeNode node, Chilkat.JsonObject json)
    {
    int i;
    int n = json.Size;
    for (i = 0; i < n; i++)
        {
        if (json.TypeAt(i) == 3)
            {
            TreeNode tn = new TreeNode();
            
            tn.Tag = json.NameAt(i);
            tn.Name = json.NameAt(i);
            tn.Text = json.NameAt(i);
            node.Nodes.Add(tn);

            Chilkat.JsonObject jObj = json.ObjectAt(i);
            buildTreeView(tn, jObj);
            }
        else
            {
            // This is a string.
            TreeNode tn = new TreeNode();
            tn.Tag = json.NameAt(i);
            tn.Name = json.NameAt(i);
            tn.Text = json.StringAt(i);
            node.Nodes.Add(tn);
            }
        }
    }

private void loadTreeView()
    {
    treeView1.BeginUpdate();

    Chilkat.JsonObject json = new Chilkat.JsonObject();

    json.LoadFile("c:/ck2000/appData/builder/treeView.json");

    // recursively add the JSON nodes to the treeview.
    TreeNode tn = new TreeNode();
    tn.Tag = json.NameAt(0);
    tn.Name = json.NameAt(0);
    tn.Text = json.NameAt(0);
    treeView1.Nodes.Add(tn);

    Chilkat.JsonObject jObj = json.ObjectAt(0);
    buildTreeView(tn, jObj);

    treeView1.EndUpdate();
    }

treeView1.Nodes.Clear();
loadTreeView();
...

Never Try to Handle Binary Data as a String

This issue comes up frequently, and hopefully this C# and VB.NET example will help people to understand what not to do..

Here’s the C# example (the VB.NET example is further below..)


// Never try to store non-text binary data as a string.
// This applies to all programming languages where the string data type is
// an object, such as C#, VB.NET, Java, VB6, FoxPro, etc.
//
// If the semantics of the programming language are such that a "string"
// is just a sequence of bytes terminated by a 0 byte, such as in C/C++,
// there are still problems because the 1st 0 byte in the non-text data (such as JPG, or PDF)
// would terminate the "string".

// Both C# and VB.NET are languages where strings are objects.  If you wish to
// set the contents of a C# or VB.NET string from a byte array, you MUST tell .NET
// the character encoding of the byte array -- otherwise it does not know how
// to interpret the bytes.  (For example, the bytes might be utf-8, iso-8859-1, Shift_JIS, etc.)
// The bytes don't actually represent characters (such as image data, or a zip archive), then
// it makes no sense to be trying to convert the bytes into "chars" because there will be innumerable
// sequences of bytes that don't represent any possible char in the charset encoding.

// For example, this is OK:

byte[] utf8Bytes = System.IO.File.ReadAllBytes("utf8_sampler.htm");
byte[] jpgBytes = System.IO.File.ReadAllBytes("starfish.jpg");

textBox1.Text = "num utf8 bytes = " + utf8Bytes.Length.ToString() + "\r\n";
textBox1.Text += "num JPG bytes = " + jpgBytes.Length.ToString() + "\r\n";

// Interpret the bytes according to the utf-8 encoding and return the string object:
// The number of chars in the string may be different than the number of bytes if there
// were chars with multi-byte utf-8 representations.
string s1 = Encoding.UTF8.GetString(utf8Bytes);
textBox1.Text += "num chars = " + s1.Length + "\r\n";

// This is garbage, because the JPG bytes don't represent chars in the utf-8 encoding.
string s2 = Encoding.UTF8.GetString(jpgBytes);
textBox1.Text += "num chars = " + s2.Length + "\r\n";

// Go back to utf-8 bytes:
byte[] utf8Bytes2 = Encoding.UTF8.GetBytes(s1);
byte[] jpgBytes2 = Encoding.UTF8.GetBytes(s2);

textBox1.Text += "num utf8 bytes 2 = " + utf8Bytes2.Length.ToString() + "\r\n";
textBox1.Text += "num JPG bytes 2 = " + jpgBytes2.Length.ToString() + "\r\n";

// Here's the output of this program:
//num utf8 bytes = 62417
//num JPG bytes = 6229
//num chars = 55731
//num chars = 5962
//num utf8 bytes 2 = 62417
//num JPG bytes 2 = 10710

VB.NET Example:

' Never try to store non-text binary data as a string.
' This applies to all programming languages where the string data type is
' an object, such as C#, VB.NET, Java, VB6, FoxPro, etc.
'
' If the semantics of the programming language are such that a "string"
' is just a sequence of bytes terminated by a 0 byte, such as in C/C++,
' there are still problems because the 1st 0 byte in the non-text data (such as JPG, or PDF)
' would terminate the "string".

' But C# and VB.NET are languages where strings are objects.  If you wish to 
' set the contents of a C# or VB.NET string from a byte array, you MUST tell .NET
' the character encoding of the byte array -- otherwise it does not know how
' to interpret the bytes.  (For example, the bytes might be utf-8, iso-8859-1, Shift_JIS, etc.)
' The bytes don't actually represent characters (such as image data, or a zip archive), then
' it makes no sense to be trying to convert the bytes into "chars" because there will be innumerable
' sequences of bytes that don't represent any possible char in the charset encoding.

' For example, this is OK:

Dim utf8Bytes As Byte() = System.IO.File.ReadAllBytes("utf8_sampler.htm")
Dim jpgBytes As Byte() = System.IO.File.ReadAllBytes("starfish.jpg")

textBox1.Text = "num utf8 bytes = " + utf8Bytes.Length.ToString() + vbCr & vbLf
textBox1.Text += "num JPG bytes = " + jpgBytes.Length.ToString() + vbCr & vbLf

' Interpret the bytes according to the utf-8 encoding and return the string object:
' The number of chars in the string may be different than the number of bytes if there
' were chars with multi-byte utf-8 representations.
Dim s1 As String = Encoding.UTF8.GetString(utf8Bytes)
textBox1.Text += "num chars = " + s1.Length + vbCr & vbLf

' This is garbage, because the JPG bytes don't represent chars in the utf-8 encoding.
Dim s2 As String = Encoding.UTF8.GetString(jpgBytes)
textBox1.Text += "num chars = " + s2.Length + vbCr & vbLf

' Go back to utf-8 bytes:
Dim utf8Bytes2 As Byte() = Encoding.UTF8.GetBytes(s1)
Dim jpgBytes2 As Byte() = Encoding.UTF8.GetBytes(s2)

textBox1.Text += "num utf8 bytes 2 = " + utf8Bytes2.Length.ToString() + vbCr & vbLf
textBox1.Text += "num JPG bytes 2 = " + jpgBytes2.Length.ToString() + vbCr & vbLf

' Here's the output of this program:
'num utf8 bytes = 62417
'num JPG bytes = 6229
'num chars = 55731
'num chars = 5962
'num utf8 bytes 2 = 62417
'num JPG bytes 2 = 10710

Understanding Chilkat’s SSH Tunnel Class

Before Chilkat’s SSH Tunnel, your app would need to connect to a tunnel process/service running either on the local machine or on some computer on the LAN, and then tunnel out to the remote server.  It would look like this:

App ——(1)——>  TunnelService —-(2)——> SshServer —-(3)—–> SomeDestServerSuchAsSQL

You can wrap a non-encrypted TCP connection, or a TLS connection within the SSH Tunnel.
For example, if you connect with TLS, then (1) and (3) are TLS, and (2) is TLS wrapped inside SSH.
The (1) connection is within your LAN, the (3) connection is within the LAN on the server-side, and the (2) connection is over the Internet.

With Chilkat, the TunnelService is no longer a separate service running somewhere on your LAN.
Rather, it’s a background thread of your app.  Thus.. you can run your app anywhere and tunnel without needing to pre-install some SSH tunnel service on the LAN.  (In effect, you’re app is carrying the TunnelService in it’s back pocket..)

C++ Zip Example to Append In-Memory Binary and String Data


void qa_create_zip_from_data(void)
{
CkZip zip;

zip.NewZip("qa_output/test.zip");

// This is the content of the files to be added to the .zip
const char *fileContents = "The quick brown fox jumped over the lazy dog. The quick brown fox jumped over the lazy dog. The quick brown fox jumped over the lazy dog.";

// AppendString2 returns a CkZipEntry, so make sure to get it and delete it if not needed..
// If NULL is returned, then the AppendString2 failed.
CkZipEntry *ent = zip.AppendString2("quickBrownFox1.txt",fileContents,"ansi");
if (ent) { delete ent; ent = 0; }

// Another way of adding in-memory data to a .zip is by calling AppendData. This is good for binary (non-text) data.
// In this case, we'll use the bytes of fileContents.
size_t szContent = strlen(fileContents);

CkByteData binaryContent;
// It is possible to let the CkByteData "borrow" data.  This avoid copying the bytes, which is good if the amount
// of data is large.
binaryContent.borrowData(fileContents,szContent);

// Add the binaryContent to the zip.
ent = zip.AppendData("quickBrownFox2.txt",binaryContent);
if (ent) { delete ent; ent = 0; }

// We now have a zip object (not yet a file on disk) that contains 2 entries: quickBrownFox1.txt and quickBrownFox2.txt
// Write the .zip to a file  (this is where the actual compression occurs)
// This writes the .zip to "qa_output/test.zip"
bool success = zip.WriteZipAndClose();
if (!success)
    {
    printf("%s\n",zip.lastErrorText());
    }
else
    {
    printf("success.\n");
    }
}

Chilkat v9.5.0.66 Release Notes

The v9.5.0.65 release notes are available here:  Chilkat v9.5.0.65 Release Notes

v9.5.0.66 Release Notes:

  • Added the Jwe and Jws classes for JSON Web Encryption and JSON Web Signatures.
  • The CkDateTime.SetFromOleDate method was not working correctly.  It has been fixed, and the fix will appear in the next version to be released after v9.5.0.65.
  • The CkDateTime.GetAsIso8601 interpreted the “bLocal” argument as the opposite.   This has also been fixed and the fix will appear in the next version to be released after v9.5.0.65.
  • MHT — Images with embedded data, where the IMG SRC tag looks like this:  “<img src=”data:image/jpeg;base64,/9j/4AAQ …”>  were getting dropped.  This is fixed.
  • Added the Http.S3_GenerateUrlV4 method to generate pre-signed AWS URLs using the Authenticate V4 signing method.
  • Added Compression.CompressBd and DecompressBd methods to compress/decompress the data contained in a BinData object.
  • Added PublicKey.GetJwk and PrivateKey.GetJwk to get public/private keys in JSON Web Key format.
  • Added Crypt2.AesKeyWrap and Crypt2.AesKeyUnwrap methods.
  • Added SFtp.SendIgnore method.
  • Fixed exporting ECC private keys from PFX to other formats.
  • Added the Zip.OpenBd and Zip.WriteBd methods to open from a BinData object, and to write to a BinData object.
  • Fixed issue with contents of Ftp2.LastReply property when an intermediate response is the cause of failure.
  • Improved performance for SFTP, FTP, and HTTP data transfers.
  • Added the Email.NumDigests property, and the Email.GetDigest method.
  • Fixed AWS Signature V4 issues for some cases when query params are used with S3.
  • Fixed the FileAccess.GetFilenameWithoutExtension method.
  • Fixed the Ftp2.Ccc method.
  • Fixed the Email.GetDeliveryStatusInfo method for certain cases where the body of information contained multiple blank lines.
  • Improved Xojo download so that Mac OS Carbon is supported.
  • Fixed MailMan.VerifySmtpLogin so that it returns true if the SMTP server requires no authentication.
  • Fixed Email.AddFileAttachment2 for the case when an attachment is the text/plain Content-Type.
  • Added automatic HTTP TLS session re-use (if possible) for HTTP connections.
  • Fixed various property getters/setters so they do not block (hang) when an async method is in progress.
  • Added support for chacha20 ciphers in TLS and SSH.