Indy升级后的编码问题

时间:2017-05-02 23:11:13

标签: delphi encoding indy

以下代码请求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,username和password是使用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.

1 个答案:

答案 0 :(得分:4)

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

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

在Indy 10.0.52中,TIdHTTP.Post(TStrings)仅对valuename=value对进行编码,并使用TIdURI.ParamsEncode()对其进行编码,该值对任何字符进行百分比编码集合'*#%<> []'或不是#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。它将编码namevalue name=value对,并按照HTML5标准对其进行手动编码(不使用TIdURI),方法是编码不在设置'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789*-._' 在对生成的字节八位字节进行百分比编码之前,默认情况下,可以使用AByteEncoding Post()参数覆盖UTF-8。

话虽如此,尝试更像这样的事情:

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;