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 Pre-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:

  • 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.

Chilkat ActiveX Object Creation in VB6 (Visual Basic 6.0)

Most ActiveX objects, including Chilkat, provide what is called a “dual interface”.   A dual interface allows for programs to bind at compile-time (early binding) or at runtime (late binding).

The type of binding is determined by how the object is created.  For example, compile-time binding in VB6 looks like this:

Dim cert As New ChilkatCert

Runtime binding looks like this:

Set cert = CreateObject(“Chilkat_9_5_0.Cert”)

An ActiveX class (such as ChilkatCert) has a set of properties and methods.  In the underlying COM object, these are contained in a “vTable”, which is an array of function pointers.  Each method or property (i.e. entry) is a function pointer occupying a particular position in the vTable.  For example, the function LoadPem might be the function pointer at index 12.

When compile-time binding is used, the indexes of the entries are hard-coded into  your VB6 executable.  When runtime binding is used, the entry is called by name.  The name is converted to the correct vTable index at runtime.  This means that if the positions of entries change, the application that uses runtime binding will not break.  The application that uses compile time binding must be recompiled to use the updated ActiveX.

For many years, the positions of the entries in the Chilkat ActiveX classes mostly did not change.  This was not by a deliberate action.  Recently, with the introduction of the Async versions of methods, and especially with the latest v9.5.0.64 release, the order of entries in the vTable has unintentionally changed.  This only affects programs that use compile-time (early) binding.  Most languages that provide the ability to use ActiveX classes, such as classic ASP, SQL Server, etc. only provide the ability to create objects via CreateObject (i.e. runtime/late binding), and are thus unaffected.

This leaves VB6 programs using compile-time binding in a difficult position.  If a new version of the ActiveX is registered, then the VB6 application must be recompiled.  It’s not possible to simply install the new ActiveX *if* the vtbl has changed.  Unfortunately, there is no way to fix the versions of Chilkat that have already been released.  In addition, there is no way to make the next version of Chilkat have the same vTable ordering as previous versions.  For example, if v9.5.0.59 has order A, and v9.5.0.64 has order B, then v9.5.0.65 cannot have and order that matches both A and B.

The solution is to have a scheme in place that works going forward.  The proposed scheme is this:

  1. Starting with v9.5.0.65, the vtbl order will become established.  Future versions of Chilkat (where the CLSID is the same) will have vTables with the order of entries matching v9.5.0.65.
  2. When Chilkat releases an entirely new ActiveX, where the CLSID changes and the version, for example, becomes 10.0.0, then the vTable order can change.  This is OK because effectively it’s an entirely new ActiveX.  In other words, the CreateObject statement becomes:  CreateObject(“Chilkat_10_0.Cert”) instead of CreateObject(“Chilkat_9_5_0.Cert”).  When the CLSID changes, and the names of the objects change, then it’s a new ActiveX that can be registered and coexist with the older Chilkat ActiveX.  To use the entirely new ActiveX, a VB6 program would have to remove it’s reference from the old, and add a reference to the new. (When adding a reference in VB6, you would see BOTH “Chilkat_10_0” and “Chilkat_9_5_0” and you could choose either.

 

Facebook OAuth2 for Classic ASP – Step 2

This is the second of a series of two posts to demonstrate implementing OAuth2 Authorization for Facebook in Classic ASP.
(Also see: Facebook OAuth2 for Classic ASP – Step 1)
This ASP is called when Facebook returns a redirect after the FB account owner either grants or denies access.

Here is the ASP source for Step 2:


' Replace with actual values.
AppId = "FACEBOOK-APP-ID"
AppSecret = "FACEBOOK-APP-SECRET"
	
' I'm using ngrok to callback to my web server running on localhost..
'RedirectUri = Server.URLEncode("https://www.your-website.com/fb_finishOAuth2.asp")
RedirectUri = Server.URLEncode("https://abca3bde.ngrok.io/fb_finishOAuth2.asp")


' Get the incoming query parameters.
' If access was denied/canceled, we'll get the following params:
' 
'     error_reason=user_denied 
'     error=access_denied 
'     error_description=The+user+denied+your+request.
'
' If access is granted, we'll get the "state" echoed back to us,
' and we'll also get a "code".

' We'll assume it succeeded..
code = request.querystring("code")
state = request.querystring("state")

' If we wanted, we could verify that the "state" received here is equal to 
' Session("oauth2_state")   

' ------------------------------------
'  Exchanging Code for an Access Token
' ------------------------------------
'  To get an access token, make an HTTP GET request to the following OAuth endpoint:

'  GET https://graph.facebook.com/v2.8/oauth/access_token?
'     client_id={app-id}
'     &redirect_uri={redirect-uri}
'     &client_secret={app-secret}
'     &code={code-parameter}

set http = Server.CreateObject("Chilkat_9_5_0.Http")

success = http.UnlockComponent("Anything for 30-day trial")
If (success <> 1) Then
    Response.Write "<pre>" & Server.HTMLEncode( http.LastErrorText) & "</pre>"
	Response.End 
End If

set sbUrl = Server.CreateObject("Chilkat_9_5_0.StringBuilder")
success = sbUrl.Append("https://graph.facebook.com/v2.8/oauth/access_token?client_id={app-id}&redirect_uri={redirect-uri}&client_secret={app-secret}&code={code-parameter}")
replaceCount = sbUrl.Replace("{app-id}",AppId)
replaceCount = sbUrl.Replace("{redirect-uri}",RedirectUri)
replaceCount = sbUrl.Replace("{app-secret}",AppSecret)
replaceCount = sbUrl.Replace("{code-parameter}",code)

respStr = http.QuickGetStr(sbUrl.GetAsString())
If (http.LastMethodSuccess <> 1) Then
    Response.Write "<pre>" & Server.HTMLEncode( http.LastErrorText) & "</pre>"
	Response.End 
End If

'  The response string will contain JSON like this:
' 
'  	{
' 	  "access_token": {access-token},
'  	  "token_type": {type},
'  	  "expires_in":	{seconds-til-expiration}
'  	}

set json = Server.CreateObject("Chilkat_9_5_0.JsonObject")
success = json.Load(respStr)

Response.Write "<p>access_token: " & json.StringOf("access_token") & "</p>"
Response.Write "<p>token_type: " & json.StringOf("token_type") & "</p>"
Response.Write "<p>expires_in: " & json.StringOf("expires_in") & "</p>"

' A sample result:
' access_token: EAAFaEtu5GRIBABb...wUXg05RFeaAZDZD
' token_type: bearer
' expires_in: 5180528

Facebook OAuth2 for Classic ASP – Step 1

This is a series of two posts to demonstrate implementing OAuth2 Authorization for Facebook in Classic ASP.
(Also see: Facebook OAuth2 for Classic ASP – Step 2)

The 1st step is to redirect to the Facebook Login Dialog where the Facebook account owner can grant access to the application.  Facebook will then return a response that redirects to your ASP page that implements Step 2.

Here is the ASP for Step 1:


AuthorizationEndpoint = "https://www.facebook.com/dialog/oauth"
TokenEndpoint = "https://graph.facebook.com/oauth/access_token"

' Replace these with actual values.
AppId = "FACEBOOK-APP-ID"
AppSecret = "FACEBOOK-APP-SECRET"

' Set the Scope to a comma-separated list of permissions the app wishes to request.
' See https://developers.facebook.com/docs/facebook-login/permissions/ for a full list of permissions.
Scope = "public_profile,user_friends,email,user_posts,user_likes,user_photos,publish_actions"

' (State) Chilkat typically uses a 32 random bytes in base64url form.
' However, it can be anything (and any length), and doesn't need to be base64url encoded.
' For this example, I typed some random chars here:
State = "dkrh345y3895hyrtyowiurh3948rhteuirth"

' I'm using ngrok to callback to my web server running on localhost..
'RedirectUri = Server.URLEncode("https://www.your-website.com/fb_finishOAuth2.asp")
RedirectUri = Server.URLEncode("https://abca3bde.ngrok.io/fb_finishOAuth2.asp")

FbAuthUrl = AuthorizationEndpoint & "?response_type=code&scope=" & Scope & "&redirect_uri=" & RedirectUri & "&client_id=" & AppId & "&state=" & State

' Let's save our random state in a session variable.
Session("oauth2_state") = State

Response.Redirect FbAuthUrl

' Note: When I first used ngrok.io, I got the following error from Facebook:
'     Can't Load URL: The domain of this URL isn't included in the app's domains. 
'     To be able to load this URL, add all domains and subdomains of your app to the App Domains field in your app settings.
' 
' To fix, I temporarily changed my Facebook App's Site Url to https://abca3bde.ngrok.io/,
' and then added "abca3bde.ngrok.io" to the list of App Domains.


C# Streaming Decompression Example (from System.IO.Stream –> System.IO.Stream)

// Streaming decompression from System.IO.Stream --> System.IO.Stream

// In this example, the source and sink streams are files.
// However, they can be any type of System.IO.Stream, such as a System.Net.Sockets.NetworkStream
System.IO.FileStream fsSource = File.OpenRead("qa_data/image.compressed");
Chilkat.StreamConnector scSource = new Chilkat.StreamConnector();

System.IO.FileStream fsSink = File.OpenWrite("qa_data/image.bmp");
Chilkat.StreamConnector scSink = new Chilkat.StreamConnector();

Chilkat.Compression compress = new Chilkat.Compression();
compress.Algorithm = "deflate";

// Create a Chilkat.Stream object, and set the source/sink to the System.IO.Stream objects.
Chilkat.Stream stream = new Chilkat.Stream();
scSource.SetAsSource(fsSource, stream);
scSink.SetAsSink(fsSink, stream);

// Do the streaming decompress -- reading from fsSource and writing to fsSink
bool success = compress.DecompressStream(stream);

v9.5.0.65 Release Notes

  • SshTunnel – Significantly improved the background tunnel management threads.
  • JsonArray.TypeAt – Discovered that this method erroneously always returns the value 4.  This is fixed.
  • JsonObject – Added new methods: UpdateNumber, UpdateNull
  • Bug Fix: .NET Async Methods that pass in Byte Arrays  There was a memory leak specific to async .NET methods that pass in byte arrays.  See Byte Array Memory Not Freed for .NET Async Methods
  • Socket: Added new methods: SendBd, SendSb, ReceiveBd, ReceiveBdN, and ReceiveSb.  These are for sending/receiving directly from/to BinData and StringBuilder objects.
  • JsonObject: Added new methods: UpdateNull and UpdateNumber.
  • StringBuilder: Added AppendLine method.
  • FTP2:   Fixed: The following FTP2 methods returned the current date/time instead of NULL for failure:  GetLastAccessDtByName, GetLastModDtByName, GetCreateDtByName, GetLastAccessDt, GetLastModDt, GetCreateDt.  The methods now correctly return NULL (i.e. 0, null, Nothing, etc.)
  • MailMan: Fixed the GetHeaders method.  The fromIndex and toIndex arguments are inclusive.  For example, if getting headers for emails 1 to 10, then 11 emails should be returned (assuming the mailbox has at least 11 emails).  The fix had to do with the case when the fromIndex = 0.  If fetching emails 0 to 2, then only 2 emails were returned.  This is now fixed.  Fetching 0 to 2 should return 3 emails.
  • Crypt2 (PBES2) — When setting the CryptAlgorithm = “PBES2”, Chilkat was doing PBES1 encryption.  This problem started sometime after v9.5.0.45.  This is now fixed.
  • Added the CkDateTime.DiffSeconds to get the difference in seconds between two CkDateTime objects.
  • Fixed auto-create issue in JsonObject.UpdateString, UpdateInt, etc. for array paths that end in a primitive.  For example the following C++ code
    CkJsonObject json;
    json.put_EmitCompact(false);
    json.UpdateString("cc_emails[0]","cc-email@paypal.com");
    json.UpdateString("cc_emails[1]","cc-email2@paypal.com");
    json.UpdateString("cc_emails[2]","cc-email3@paypal.com");
    json.UpdateString("cc_emails[3]","cc-email4@paypal.com");
    std::cout << json.emit() << "\r\n";

Now correctly produces:

{
  "cc_emails": [
    "cc-email@paypal.com",
    "cc-email2@paypal.com",
    "cc-email3@paypal.com",
    "cc-email4@paypal.com"
  ]
}
  • Turned on TCP_NODELAY socket option for HTTP.  See http://www.chilkatforum.com/questions/11570/http-is-slow
  • Updated C++ headers to avoid QT’s “emit” macro.  QT defines the word “emit” as a macro, and Chilkat’s JsonObject and JsonArray classes both have an “emit” function.  QT’s macro definition causes compile failures for any C++ source that uses any variable, function, etc. named “emit”.    To avoid the macro definition, Chilkat does the following:
#if defined(QT_VERSION)
#pragma push_macro("emit")
#undef emit
const char *emit(void);
#pragma pop_macro("emit")
#else
const char *emit(void);
#endif
  • Added the “eda” encoding for UN/EDIFACT Syntax Level A.  See https://www.example-code.com/csharp/edifact_eda_syntax_level_A_encoding.asp. Also see: http://cknotes.com/chilkat-binary-encoding-list/
  • Added new methods to CkDateTime:  AddSeconds, GetAsUnixTimeStr, GetAsIso8601.
  • Added the AuthAzureSAS class.  (For Shared Access Signature authorization)
  • Added the Rest.SetAuthAzureSas method.  (For Shared Access Signature authorization)
  • Fixed Rest.AddQueryParams  (note that this is the plural:  AddQueryParams method, not AddQueryParam).   If values in the param string were URL encoded, the AddQueryParams did not properly URL decode each value.  This would result in query params being doubly URL encoded when the actual request was sent.
  • Added Crypt.BcryptWorkFactor, BCryptHash, and BCryptVerify to support BCrypt password hashing and verification.
  • Fixed an Android issue w/ IMAP ProgressInfo callbacks.  The problem only occurred when a ProgressInfo callback happened for Login where the username includes a non-usascii char, such as one with an umlaut.
  • Socket.TlsRenegotiate — fixed a problem that occurred under certain conditions when the SslAllowedCiphers property is set to restrict TLS cipher suite choices.
  • Ftp2 – Fixed problem with “Unix Cway” servers where 0-byte files were getting downloaded (instead of the full file).  This was caused because the FTP server inaccurately reports that the file is “0 bytes” in the intermediate reply to the RETR command.  Chilkat now ignores what this particular server says.
  • (Email) Fixed: An incoming email’s header specifies the sender as this:From: "FAXG3/+5554555920"@faxsrv.intraThe email is loaded into a CkEmail object using the C++ edition of Chilkat.Problem is, the CkEmail.get_FromAddress() method returns @faxsrv.intra as the email address instead of FAXG3/+5554555920@faxsrv.intra
  • ActiveX LastStringResultLen property incorrectly returned the size of the string in bytes (utf-16) rather than in number of characters. This is fixed to return the number of characters.  (Note: this is a property that only exists in ActiveX classes, and is only useful for certain programming languages such as SQL Server to help programs determine if temp tables are required..)
  • SSH – Added the following methods: QuickShell, QuickCommand, QuickCmdSend, and QuickCmdCheck.   These methods help simplify executing remote commands running shell sessions.