【问题标题】:Encoding Issues After Indy UpgradeIndy 升级后的编码问题
【发布时间】:2017-05-03 08:27:26
【问题描述】:

以下代码请求 OAuth2 令牌。它适用于 Indy 10.0.52,但适用于 Indy 10 SVN 5412,由于请求凭据无效,它会生成 400 Bad Request 错误。

WXYZUserID   :=      Trim( GetSettingsValue( mySQLQuery,  wcsWXYZUserID, ''));
WXYZPassword :=      Trim( GetSettingsValue( mySQLQuery,  wcsWXYZPassword, ''))
WXYZSecret   :=      Trim( GetSettingsValue( mySQLQuery,  wcsWXYZSecret, ''));
OAuthURL     :=      Trim( GetSettingsValue( mySQLQuery,  wcsOAuthURL, ''));
WxyzHttp := TIdHttp.Create(nil);
Serial := TStringList.Create;
if (WXYZUserID <> '') and (WXYZPassword <> '') and (WXYZSecret <> '') then
  Serial.Add('grant_type=password&username=' + WXYZUserID + '&password=' +    WXYZPassword )
Else
  Serial.Add('grant_type=password&username=****-*****&password=***************');

Output := TMemoryStream.Create;
Serial.SaveToStream(Output);

WxyzHttp.ConnectTimeout := 60000;

IdLogFile := TIdLogFile.Create;
IdLogFile.Filename := 'Logs/WxyzHTTP' + FOrmatDateTime('yyyymmdd_hhnnsszzz',now) + '.log';
IdLogFile.Active := True;

IdSSLIOHandlerSocketOpenSSL1 := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
IdSSLIOHandlerSocketOpenSSL1.SSLOptions.Method := sslvSSLv23;
IdSSLIOHandlerSocketOpenSSL1.Intercept := IdLogFile;
WxyzHttp.IOHandler := IdSSLIOHandlerSocketOpenSSL1;

if (WXYZUserID <> '') and (WXYZPassword <> '') and (WXYZSecret <> '') then
  WxyzHttp.Request.CustomHeaders.Values['Authorization'] := 'Basic ' + WXYZSecret
Else
  WxyzHttp.Request.CustomHeaders.Values['Authorization'] := 'Basic ****************************************************************';

WxyzHttp.Request.ContentType := 'application/x-www-form-urlencoded; charset="utf-8"';

Try
Token := WxyzHttp.Post( OAuthURL, Output );
Except
On E: Exception do
Begin
  DbgWebCatLog( wcmtDbg, 'GetSerialToken', E.ClassName + ' error raised, with message: ' + E.Message, '' );
  Token := '';
  end;
end;

我添加了代码来捕获 IdLogFile 日期。 Indy 10.0.52 版本的日志包含以下包含凭据的行。

Sent 05/01/2017 18:34:35: grant_type=password&username=*************_*****-*****&password=***}%25**(**(***

出于安全考虑,我已将所有字母数字字符替换为“*”。 “%25”字符原本是实际密码中的“%”。有些东西将原来的“%”翻译成“%25”,但没有改变任何其他特殊字符。

对 Indy 10 SVN 5412 日志的初始检查检测到来自服务器的消息,指示存在无效的请求凭据。此外,对应于上述行中的所有特殊字符表明所有特殊字符都已被编码。 grant_type、用户名和密码是使用 tStringList 呈现的,我发现它会导致 urlencoding。我更改了代码以使用流呈现相同的数据,现在没有任何内容被编码;甚至“%”都没有被翻译成“%25”,我继续收到无效的请求凭据消息。

所以我的问题是,为什么 Indy 10.0.52 只会将“%”字符转换为“%25”,我如何在 Indy 10 SVN 5412 中复制这种行为?

以下是有效的 Indy 10.0.52 版本的完整日志。

Stat Connected.
Sent 05/01/2017 18:34:35: POST /auth/realms/hvac/tokens/grants/access HTTP/1.0<EOL>Content-Type: application/x-www-form-urlencoded; charset="utf-8"<EOL>Content-Length: 82<EOL>Authorization: Basic<EOL> **********<EOL>Host: services.ccs.utc.com:443<EOL>Accept: text/html, */*<EOL>Accept-Encoding: identity<EOL>User-Agent: Mozilla/4.0 (compatible; MSIE 8.0)<EOL><EOL>
Sent 05/01/2017 18:34:35: grant_type=password&username=*************_*****-*****&password=***}**%25**(**(***
Recv 05/01/2017 18:34:35: HTTP/1.1 200 OK<EOL>Server: Apache-Coyote/1.1<EOL>Pragma: no-cache<EOL>Cache-Control: no-store<EOL>Content-Type: application/json;charset=UTF-8<EOL>Content-Length: 569<EOL>Date: Mon, 01 May 2017 22:34:35 GMT<EOL>Connection: close<EOL><EOL>{<EOL>  "access_token":"**********",<EOL>  "token_type":"Bearer",<EOL>  "expires_in":1800,<EOL>  "refresh_token":"**********",<EOL>  "id_token":"**********"<EOL>}
Stat Disconnected.
Stat Disconnected

以下是 Indy 10 SVN 5412 版本的完整日志,该版本因请求凭据无效而失败。

Stat Connected.
Sent 05/02/2017 15:11:08: POST /auth/realms/hvac/tokens/grants/access HTTP/1.0<EOL>Connection: keep-alive<EOL>Content-Type: application/x-www-form-urlencoded; charset=utf-8<EOL>Content-Length: 82<EOL>Authorization: Basic **********<EOL>Host: services.ccs.utc.com<EOL>Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8<EOL>User-Agent: Mozilla/3.0 (compatible; Indy Library)<EOL><EOL>
Sent 05/02/2017 15:11:08: grant_type=password&username=*************_*****-*****&password=***}**%**(**(***<EOL>
Recv 05/02/2017 15:11:08: HTTP/1.1 400 Bad Request<EOL>Server: Apache-Coyote/1.1<EOL>Content-Type: application/json;charset=UTF-8<EOL>Content-Length: 84<EOL>Date: Tue, 02 May 2017 19:11:08 GMT<EOL>Connection: close<EOL><EOL>{<LF>  "error": "invalid_grant",<LF>  "error_description": "Invalid request credentials"<LF>}
Stat Disconnected.
Stat Disconnected.

【问题讨论】:

    标签: delphi encoding indy


    【解决方案1】:

    您正在发布TMemoryStream,它按原样发布,TIdHTTP 不会以任何方式在任何版本的 Indy 中对其进行编码。您有责任确保您的TMemoryStream 的内容格式正确。那是你的事,不是 Indy。

    TIdHTTP.Post() 有一个重载,它发布TStrings 而不是TStream,为您格式化application/x-www-webform-urlencoded 格式的字符串。您根本不需要使用TMemoryStream

    在 Indy 10.0.52 中,TIdHTTP.Post(TStrings) 仅对 name=value 对中的 value 进行编码,并使用 TIdURI.ParamsEncode() 对它们进行编码,它对集合中的任何字符进行百分比编码 '*#%&lt;&gt; []' 或不在#33..#128(含)之间的 ASCII 字符。 10.0.52 根本不支持 Unicode,因此它不会将字符串编码为 UTF-8,并且它不支持 Delphi 2009+ 的 UnicodeString 类型(或者甚至 WideString,就此而言)。因此,您存储在 TStrings 中的任何字符串必须以 UTF-8 格式开头(在 Delphi 2009+ 的情况下,它们仍然必须是 UTF-8,每个代码单元使用 16 位字符而不是 8 位字符)。

    在最新的 Indy 版本(撰写本文时为 10.6.2.5418)中,TIdHTTP.Post(TStrings) 完全支持 Unicode,并支持 UnicodeString。它将同时编码name=value 对中的namevalue,并通过编码不在集合'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789*-._' 中的任何Unicode 字符来按照HTML5 标准对它们进行手动编码(不使用TIdURI) 转换为 UTF-8(默认情况下,可以使用 Post()AByteEncoding 参数覆盖),然后对结果字节八位字节进行百分比编码。

    话虽如此,请尝试更多类似的东西:

    try
      WXYZUserID   :=      Trim( GetSettingsValue( mySQLQuery,  wcsWXYZUserID, ''));
      WXYZPassword :=      Trim( GetSettingsValue( mySQLQuery,  wcsWXYZPassword, ''))
      WXYZSecret   :=      Trim( GetSettingsValue( mySQLQuery,  wcsWXYZSecret, ''));
      OAuthURL     :=      Trim( GetSettingsValue( mySQLQuery,  wcsOAuthURL, ''));
    
      WxyzHttp := TIdHttp.Create(nil);
      try
        WxyzHttp.ConnectTimeout := 60000;
    
        IdLogFile := TIdLogFile.Create(WxyzHttp);
        IdLogFile.Filename := 'Logs/WxyzHTTP' + FormatDateTime('yyyymmdd_hhnnsszzz',now) + '.log';
        IdLogFile.Active := True;
    
        IdSSLIOHandlerSocketOpenSSL1 := TIdSSLIOHandlerSocketOpenSSL.Create(WxyzHttp);
        IdSSLIOHandlerSocketOpenSSL1.SSLOptions.Method := sslvSSLv23;
        IdSSLIOHandlerSocketOpenSSL1.Intercept := IdLogFile;
        WxyzHttp.IOHandler := IdSSLIOHandlerSocketOpenSSL1;
    
        if (WXYZSecret <> '') then
          WxyzHttp.Request.CustomHeaders.Values['Authorization'] := 'Basic ' + WXYZSecret
        else
          WxyzHttp.Request.CustomHeaders.Values['Authorization'] := 'Basic ****************************************************************';
    
        WxyzHttp.Request.ContentType := 'application/x-www-form-urlencoded';
        WxyzHttp.Request.CharSet := 'utf-8';
    
        Serial := TStringList.Create;
        try
          Serial.Add('grant_type=password');
    
          if (WXYZUserID <> '') and (WXYZPassword <> '') then
          begin
            Serial.Add('username=' + WXYZUserID);
            Serial.Add('password=' + WXYZPassword);
          end else
          begin
            Serial.Add('username=****-*****');
            Serial.Add('password=***************');
          end;
    
          Token := WxyzHttp.Post( OAuthURL, Serial );
        finally
          Serial.Free;
        end;
      finally
        WxyzHttp.Free;
      end;
    except
      on E: Exception do
      begin
        DbgWebCatLog( wcmtDbg, 'GetSerialToken', E.ClassName + ' error raised, with message: ' + E.Message, '' );
        Token := '';
      end;
    end;
    

    【讨论】:

    • 谢谢雷米。重构构建 tStringList 的代码就成功了。
    猜你喜欢
    • 1970-01-01
    • 2019-11-10
    • 2012-05-03
    • 2013-01-07
    • 1970-01-01
    • 2011-10-07
    • 2011-07-22
    • 1970-01-01
    • 2016-10-12
    相关资源
    最近更新 更多