Reinvent The Wheel

Round is nice, but we can do better!

RAD Twitter

The problem with TTwitter – Reason discovered!

| 15 Comments

Quite a lot of people around the world have been reporting that TTwitter doesn’t work on theirs or their client’s machines… with Twitter reporting a 401 error on each request!

I have been attempting to track down the cause of this problem for quite some time, and I have finally discovered where the issue resides!

TTwitter uses a third-party OAuth library to provide authentication and signing services for all requests sent via the Twitter API.

Part of the OAuth system involves generating a signature, which uses – amongst other things – the current Date and Time as part of this serialization.

I have discovered that this portion of the OAuth unit makes some fatal assumptions about the operating environment, in that it is hard-coded to interpret TDate and TDateTime values with very specific formatting.

The problem with this is that not every computer uses the same TDateTime format (the UK, for example, uses DD/MM/YYYY for dates, while the USA uses MM/DD/YYYY). This means that the value being serialized into the signature is incorrect, thus the signature is incorrect.

I will of course be fixing this in the near future (in the next few days, in fact), and will release an update to the TTwitter SVN repository!

Another announcement will be posted when this fix is available to all…. and I’m genuinely sorry it took so long to track down the cause of this fault.

Please keep in mind that bug tracking is difficult when you cannot replicate the problem. In my case, all of my systems use the standard UK TDateTime formatting, and thus I will never encounter the 401 issues. Indeed, the only reason I was able to isolate the cause was because of an e-mail from Volkan Atman (a Delphi developer using TTwitter in his project) detailing how all of his systems work perfectly with TTwitter, while clients in other countries are getting the 401 issues with the compiled executable.

I want to thank Volkan Atman for being so thorough in his bug report… and will post the body of his message below so others can see the kind of information you can provide in the future to help make bug tracking (and thus repairs) easier.

Hello Mr. Stuart,

I have been using your component. I also tried the new version. After compiled my Delphi program, which I used your component, I tried that at 3 different laptop (one of them XP SP3, the others XP SP2). Only one of them (XP SP3) asked me authentication, I remember it was a twitter page. But I forgot to take a screenshot. Anyway, there is no problem.

Then I sent the .exe and AppDetails.ini to my friend. He tried at two laptop (XP SP2 and vista). But he got 401 unauthorized error.

I know lots of people asked like these questions. But I didn’t see any problem like mine, so I want to ask to you.

If you can help, I would be happy.

Regards.

Author: Simon J Stuart

Automation and Productivity Systems Specialist, Author of various Components, Libraries and Tools for Embarcadero Delphi, Embarcadero Technology Partner, Founder and CEO of LaKraven Studios Ltd, Father of 2 (+ 2 dogs), Credited Technical Editor, Published Technical Author, Seeker of peace!

15 Comments

  1. Is this the RFC 822 date format?

    function FormatDateTimeRFC822(const d: TDateTime): string;
    const // as defined by Standard for the Format of ARPA Internet Text Messages,
    // section 5.1: http://www.sendmail.org/rfc/0822.html#5
    Days : Array [1..7] of string =
    (‘Sun’, ‘Mon’, ‘Tue’, ‘Wed’, ‘Thu’, ‘Fri’, ‘Sat’);
    Months: Array [1..12] of string =
    (‘Jan’, ‘Feb’, ‘Mar’, ‘Apr’, ‘May’, ‘Jun’, ‘Jul’, ‘Aug’, ‘Sep’, ‘Oct’,
    ‘Nov’, ‘Dec’);
    var
    dd, mm, yy, hh, mn: Word;
    tz : TIME_ZONE_INFORMATION;
    dt: TTime;
    gmt: string;
    begin
    DecodeDate(d, yy, mm, dd);

    // compute GMT bias
    GetTimeZoneInformation( tz );
    hh := abs(tz.Bias div 60); // hours
    mn := abs(tz.Bias mod 60); // minutes
    dt := EncodeTime(hh, mn, 0, 0);
    if ( (tz.Bias div 60) < 0) then
    gmt := '-' + FormatDateTime('hhnn', dt)
    else
    gmt := '+' + FormatDateTime('hhnn', dt);

    Result := Days[ DayOfWeek(d) ] + ', ' +
    IntToStr( dd ) + ' ' +
    Months[ mm ] + ' ' +
    IntToStr( yy ) + ' ' +
    FormatDateTime('hh:nn:ss', d) + ' ' + gmt;
    end;

    • The specific function of concern is on line 173 (to line 182) of Twitter_OAuth.pas in the TTwitter repository, as well as line 164 being of concern as it’s very likely this constant value is format-specific too!

      I’m using a function similar to the one you provided to convert the Date/Time String values contained within Twitter’s returned packets for Tweets etc.

      At least now I finally know where to look, so I can fix this problem (finally).

      • I’m afraid I’m not following you. In the post, you speak about formattings. Later, you make lines 173 to 182 responsible – which are about converting a TDateTime to unix time, which has nothing at all to do with country locales.

        You speak about different “TDateTime” formats – but TDateTime has just a single one. Days since a specific date before the digit, the fractional part of the day behind it. TDateTime is a double, not a string or a complicated bit masked thing!

        All that dd/mm/yy or mm/dd/yy stuff is used in FormatDateTime to convert it to text to display to the user, but irrelevant when simply handling the TDateTime value.

        So let’s throw this (imho wrong) assumption aside, and pay a closer look to DateTimeToUnix.

        1. Why did you implement it yourself? Take a look at SysUtils.pas, Delhi already ships a procedure to convert a TDateTime to Integer.

        2. I must admit I wrote one myself at some point ;) Comparing, I immediately noticed that if you use the Bias, you need to use DaylightSaving as well!

        function ConvertDateTimeToUNIXTime(ADateTime: TDateTime): cardinal;
        begin
        if DaylightSavings then begin
        Result := Round((ADateTime – 25569 + ((TZInfo.Bias + TZInfo.DaylightBias) / 1440)) * 86400)
        end else begin
        Result := Round((ADateTime – 25569 + ((TZInfo.Bias) / 1440)) * 86400);
        end;
        end;

        from this units initialization:

        begin
        DaylightSavings := GetTimeZoneInformation(TZInfo) = TIME_ZONE_ID_DAYLIGHT;
        end.

        To sum this up – you should use the daylight savings to have correct values in summer, but other than that, I would say the given lines are pretty correct and not at fault.

        What I can see is that GenerateNonce is calling GenerateTimestamp. Since each nonce per same timestamp needs to be unique, this means you’ve got duplicate and this invalid nonces for all requests within the same second! At least, GenerateNonce should include a long random string attached, like IntToStr(Random($FFFFFF)) or something like that to make collisions less likely (if you do not want a proper list of used up nonces). Or, an auto-incrementing value that is +1 on each GenerateNonce. Not so nice from the crypto standpoint, but at least preventing collisions safely. Well, or create a list of used-up nonces of course.

        • I was pointing to lines to do with Date/Time handling, knowing that Date/Time handling is (at some point) responsible for the issue. I haven’t looked deep enough into the code as-yet (been busy with Lua4Delphi) so do not know for sure exactly where the problem is.

          I never implemented anything in that OAuth unit myself! It was found online (open source)… not written by me!
          I appreciate the input and you taking the time to go through this… I’ll give this a go as soon as I have a moment to look at it!

          Cheers :)

  2. Big thanks Simon and keep the good work. We will be expecting the update

  3. I am pleased to help solving a problem. I thanked you for this component.

  4. Date format causing authentication problems on some computers – it sure was hard to find! Nice :)

  5. Hi Simon
    Another way to trigger the 401 error is using special characters if you are logging & twitting.
    Like this sample code:

    procedure TfrmMainForm.Button1Click(Sender: TObject);
    var
    twit: String;
    begin
    twit := ‘Temperature 30º C’; // 401 ERROR
    // twit := ‘Temperature 30 C’; NO ERROR

    if Twitter1.Login then
    Twitter1.PostTweet(False,twit, 0);

    End;
    (in this sample, the º character)

    Regards
    -Jordi

    • Yes, this is due to the way unicode chars are encoded in the URL string! For some reason this same issue occurs when I use Twitter’s own “Developer Console” on their dev site! I’ve some ideas to solve that too!

  6. Any update to provide on TTwitter just yet? I realise Lua4Delphi has been a major project, and you’ve also been busy with an article for Blaise (congratulations on both accounts), but i’d love to hear if you’ve had chance to work on TTwitter in amongst that.

    Regards,
    Scott.

Leave a Reply

Required fields are marked *.

*


*