Synapse ahd XOAUTH2 (gmail)? lazarus / 38-Networking and Web Programming ---------------------------------------------------------------------- epergola · 2015-07-02 23:14 Hi Anybody knows how to send email with XOAUTH2 authentication? Any clue whatsoever? ---------------------------------------------------------------------- epergola · 2015-07-03 17:21 Ok, nobody knows. I will ask this then. Is there any free-pascal non-indy way to send emails that require XOAUTH2 authentication? ---------------------------------------------------------------------- rvk · 2015-07-03 17:49 Yes, there is a way but I haven't found ready made examples. You could look at the following article which does use indy: http://wiert.me/2014/08/29/delphi-so-xoauth2-sasl-component-that-can- do-oauth2-via-asbjorn-heid-g/ But since you asked a non-indy way I guess you'll need to program the XOAUTH2-stuff yourself. I did this once with the Google Calendar Api which also required xoauth2 authentication. I think there are more examples to be found like that now. If I get a moment this weekend I can look if I can adjust the unit I got (for Calendar) to work for email. ---------------------------------------------------------------------- epergola · 2015-07-03 22:54 Thanks. If you find time, would that adaptation be for free-pascal (i.e. something that could be called from inside a Lazarus application)? I would like my application bo be able to allow the user to send email using his/her gmail account. ---------------------------------------------------------------------- BeniBela · 2015-07-03 23:23 In theory, OAUTH2 is extremely simple. You just need to make 2 HTTP requests.,, And another one to send the mail, if you want to send it through the API instead SMTP. But I still needed to 2 hours to find the correct parameters   : http://wiki.lazarus.freepascal.org/Internet_Tools#Using_Google_Mail.2C _other_Google_APIs.2C_or_OAUTH2 (not Synapse specific) Quote from: Google Docs https://developers.google.com/gmail/api/guides/sending    Emails are sent as base64url encoded strings within the raw property of a message resource. The high-level workflow to send an email is to:     Create the email content in some convenient way and encode it as a base64url string.     Create a new message resource and set its raw property to the base64url string you just created. Screw you, Google. It only works, when the mail  is not base64url encoded ---------------------------------------------------------------------- rvk · 2015-07-03 23:38 Quote from: BeniBela on July 04, 2015, 01:23:22 amIn theory, OAUTH2 is extremely simple. You just need to make 2 HTTP requests.,, Yeah, in theory I would also advise against authorizing each time you want to send a mail. You need to reuse the access_token. And these expire after some time. So after that time you need to get a new access_token with the refresh_token. That way only one time authentication is needed. (edit: Aaah, I see you also mentioned that in the comments) I have a 'small' library which does just that. It checks if the access_token is expired and if so it will get a new one with the refresh_token. If that's not present the authentication to the user happens to get access to mail/profile and/or calendar. It will automatically detect the accept from the user in the browser (sorry, Windows only). I just need to test this with the correct (E-Mail) scope and try to send a mail via TSMTPSend from Synapse. When I get a working example I'll post it here (unless somebody else already has a working solution). Edit: I have it working. It's too late now but I'll clean up the code tomorrow and post it. ---------------------------------------------------------------------- rvk · 2015-07-04 14:45 Ok, here goes. (I hope I done everything correctly. It's my first open-source project) https://github.com/rvk01/google-oauth2 You can download it with the button on the lower right ("Download Zip"). I kept my Api-credentials in the test-application but of course when you're going to deploy your application you need to create your own. (The consent screen of Google shows my application title) For testing- purposes you can use the one I kept in frmmain.pas. You need to put libeay32.dll, libssl32.dll and ssleay32.dll in your project directory (I assume you can get your hands on those). Then the important file is google_oauth2.pas. It implements a TGoogleOAuth2 class which you can use to get access to someones account. Code: [Select]var   gApi: TGoogleOAuth2; begin   gApi := TGoogleOAuth2.Create(client_id, client_secret);   gApi.GetAccess([goMail], True); // <- get from file   //... The true in the GetAccess is that the Access and Refresh_tokens are stored in a file named tokens.dat. If you use false then you'll need to store these tokens yourself and set them accordingly. Code: [Select]var   gApi: TGoogleOAuth2; begin   gApi := TGoogleOAuth2.Create(client_id, client_secret);   gApi.Refresh_token := my_stored_Refresh_token;   gApi.Access_token := my_stored_Access_token;   gApi.GetAccess([goMail], False); // <- do not use the tokens.dat file   // SAVE THESE TOKENS   my_stored_Refresh_token := gApi.Refresh_token;   my_stored_Access_token := gApi.Access_token;   //... In the test-app you can press "Get GMail access" and it will store tokens in a file. After that you can change the recipient and press "Send mail". If you do this without doing the "Get GMail access" first it will also do the authentication first. So the "Get GMail access" is not strictly necessary. After that there is no more need for authentication. The tokens are stored and reused until they expire in which case the GetAccess will try to refresh them. In case it fails it will automatically ask for authentication again. Now that the mechanism is in place to get the access_token you can go on to sending the mail. You wanted to do this with Synapse. The problem with TSMTPSend in Synapse is that it doesn't provide you the ability to issue your own commands during connection build-up. And for sending mail via smtp.gmail.com you'll need to issue a command AUTH XOAUTH2 during the connect. I made a small class helper to do just that. After that it was just simply a matter of sending the mail-data and everything should be working ok. Try the test-application xoauth2_test. I hope everything works correctly. (P.S. At the moment this is Windows-only. I will be doing some recoding in the future to make it Linux-compatible but at the moment it uses a small Internet Explorer browser to show the user the first time consent screen. The biggest problem with Linux would be to get the user-code after giving consent from the browser-object. If somebody has an idea about that....? I haven't got that much experience with Linux-desktops) ---------------------------------------------------------------------- epergola · 2015-07-05 23:10 Thnaks both of you. Rvk I still cannot try your code because I am stuck with en error at compiling the IDE (I posted in the LCL forum) . I will let you know when I will. ---------------------------------------------------------------------- epergola · 2015-07-06 22:07 Good thing: it compiled. Bad thing: I get this error: Getting account information Error: 403 - Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup. Invalid access_token Authorizing... You don't have access Maybe I'll try tomorrow to see if the "daily" thing gets resolved. However, If i put this code inside my app, everytime the user sends an email it will go in that google internet web form first? ---------------------------------------------------------------------- rvk · 2015-07-07 02:17 No, it should only ask for permission once. After that the access- token gets stored and can be used for some time without showing the web-dialog. After the access-token expires a new access-token is received (via the refresh-token) also without the web-dialog. Only when something goes wrong with the authentication the web-dialog would appear. The first time you run this the programs tried to access the api with an access-token which isn't initiated yet. That's why you get a 403. It's normal. I could see if I can remove it. After that it tried to gain access to the account and does the web-dialog. You did click accept in the web-dialog?? To give permission to the app?? P.s. In your end application you would need to set new credentials which you can create in the google api console. (For testing you could keep the one in the test-app for now) ---------------------------------------------------------------------- rvk · 2015-07-07 09:27 Quote from: epergola on July 07, 2015, 12:07:14 amGetting account information Error: 403 - Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup. Invalid access_token Authorizing... You don't have access I made some changes to google_oauth2.pas. (in https://github.com/rvk01/google-oauth2) You don't get a 403 anymore when the initial authentication is not yet done. In your log you have "Authorizing" and directly after it you have "You don't have access". That leads me to believe you didn't click the Accept button in the Consent-screen. You need to give a one time permission to the app to send mail on your behalf. Normally, when the permission is given, this is a one time action. After that, the app receives a token which it can use in subsequent access. If however, you didn't get the consent-screen (or you got the screen and accepting it didn't work) I'd like to know. Maybe something went wrong with "reading-out" the caption of the Internet.Explorer window. In that case I could build in a method for the user to manually enter the received access-token (or find out why reading out the caption didn't work). ---------------------------------------------------------------------- epergola · 2015-07-07 19:52 I tried the new version. Acces to Mail checkbox is checked. I clicked once Get accessd and I did click on Accept on the subsequent form. Bu I still get Authorizing... Autorization not accepted We don't have access I had not changed anything in the credentials as you said I could use them as is for test. ---------------------------------------------------------------------- epergola · 2015-07-07 20:02 Oops! Now, when I tried to run the app again, I got a blank form, with nothing shown on it. Even after I exited Lazarus and reconstructed the project. ---------------------------------------------------------------------- rvk · 2015-07-07 22:11 Quote from: epergola on July 07, 2015, 09:52:02 pmI clicked once Get accessd and I did click on Accept on the subsequent form. Bu I still get Authorizing... Autorization not accepted We don't have access That's very weird. I tried this a lot of times and everytime it works for me. I'm on Windows 7 64 bit with standard updated internet explorer. On what Windows version are you? And what version of Lazarus? (I'll test it on the same) I think the problem is in getting the access_token from the browser. If I don't get the same as you (on the same OS version I'll try to build in same more debugging). ---------------------------------------------------------------------- rvk · 2015-07-08 13:23 I released a new version on https://github.com/rvk01/google-oauth2. *) It has the ability to take the authorization-code from the browser- titlebar. I remember this didn't work some time ago but maybe Google fixed it. (the code used to be cut off at some point) So maybe that method works better now. (It's the default method now) *) If that doesn't work you can uncheck the "Use browser-title for authentication-code" after which the library tries to get the authorization-code from the returned HTML. You can go to the debug-tab to see exactly what the program gets as HTML-text. Maybe there is something wrong there. I do a lookup for readonly="readonly" value=" in the text and try to extract the code. But perhaps that doesn't work at your end. (it works for me though) *) And finally... if that doesn't work you can check "Force manually input of authentication_token". In that case there is an inputbox behind the browser. You van accept on the consent-screen after which you see a authorization-code in the screen. You can click and copy it, close the browser and paste it in the inputbox. Press OK and you should have access. If only the manual-method works I would like to know what the debug- screen gives you with the other two methods (so I can fix it). ---------------------------------------------------------------------- epergola · 2015-07-08 15:11 Thanks very much. I downloaded your last library an now it works. I only had to correct this line Authorize_token := Copy(Found, Length(SearchFor) + 1,1000); (added the ',1000') in google_oauth2. One question. During my previous test, in the end I changed client_id and secret from yours to mine. Now running the working program, I LEFT your credentials if frmMain.pas, but it takes my default gmail account when sending (I assume it uses the token created with my preivous Get Access). Is that because the token was saved earlier and it does not get a new one (from your credentials in this case) until it expires? Btw, using xp Lazarus 1.4.0 18-apr-2015 ---------------------------------------------------------------------- rvk · 2015-07-08 15:22 Quote from: epergola on July 08, 2015, 05:11:03 pmI only had to correct this line Authorize_token := Copy(Found, Length(SearchFor) + 1,1000); (added the ',1000') in google_oauth2. Woops. Yes. The Copy(String, 1) method only works in trunk. It copies from position 1 to the end of the string. I'll need to test this with Laz1.4 Quote from: epergola on July 08, 2015, 05:11:03 pmOne question. During my previous test, in the end I changed client_id and secret from yours to mine. Now running the working program, I LEFT your credentials if frmMain.pas, but it takes my default gmail account when sending (I assume it uses the token created with my preivous Get Access). Is that because the token was saved earlier and it does not get a new one (from your credentials in this case) until it expires? Yes. You authenticated with "my App"-credentials and for that the tokens are created. So as long as you don't wipe the access_token and refresh_token it still uses "My App" for sending mail (but via your Google-account). You can just delete the tokens.dat file (or use the button in the screen to remove tokens.dat). After that you'll get a new authentication screen with your Consent-screen info and after that Google knows it's your program which is sending the mail (via your Google account). Quote from: epergola on July 08, 2015, 05:11:03 pmBtw, using xp Lazarus 1.4.0 18-apr-2015 Ok, I'll test that too if I get the chance. B.T.W. I take it the first option (with the "Use browser-title for authentication-code" checked) is the one that worked ?? ---------------------------------------------------------------------- epergola · 2015-07-08 16:18 Yes, the browser dialog worked ---------------------------------------------------------------------- epergola · 2015-08-04 00:02 Rvk something really weird is happening. Your functions work perfectly in Lazarus. However i made a DLL out of them for importing in D5. All works fine, the authorization and all. EXCEPT at this point in the ssl_openssl.lib function TSSLOpenSSL.Init(server:Boolean): Boolean; var   s: AnsiString; begin   Result := False;   FLastErrorDesc := '';   FLastError := 0;   Fctx := nil;   case FSSLType of     LT_SSLv2:       Fctx := SslCtxNew(SslMethodV2);     LT_SSLv3:       Fctx := SslCtxNew(SslMethodV3);     LT_TLSv1:       Fctx := SslCtxNew(SslMethodTLSV1);     LT_all:       Fctx := SslCtxNew(SslMethodV23);   else     Exit;   end;   if Fctx = nil then  IT IS NIL.  The FSSLType  is LT_all, exacclty as in Lazarus. It seems to be a matter of the dll's (the function is not found). I put the dll's from windows\system32 yo the App directory. I even changed their name (and the DLLSSLName name as well) to make sure they are the ones that load, to no avail. Where would the Lazarus EXE load the DLL from? According to the rules s/b windows\system32 (in XP). I just cannot understand the difference. Do you have any clue? ---------------------------------------------------------------------- rvk · 2015-08-04 09:27 I wouldn't place the DLL's in windows\system32. On a 64bit system you don't know where they end up. You have windows\system32 for 64bit dll's and windows\SysWOW64 for 32bit dll's. Is your Windows XP is 32bit I would still just use the application directory. No chance of getting the wrong dll's that way. Did you copy both ssleay32.dll and libeay32.dll  to your exe and dll directory? The correct 32bit ones? It's best to download clean correct ones. For instance this one for 32bit. ---------------------------------------------------------------------- renabor · 2015-08-04 12:03 I was able to compile under Linux using Ararat Synapse (http://sourceforge.net/p/synalist/code/HEAD/tree/trunk/), which give support for ssl, and LazWebkit (http://sourceforge.net/projects/lazwebkit/files/) which is a webbroser. All Lazarus native. But still have some problems on setting up client_id and secret ---------------------------------------------------------------------- epergola · 2015-08-04 16:01 Thanks RVK. I meant this: From lazarus EXE all is fine. I do not have the libeay32 and ssleay32 in the same directory of the laz. EXE, therefore the laz. EXE *must* load it from windows\system32 (where they were since long) and all works. Now, using my own dll (YOUR code, nothing to do with the ssl dll) in a delphi app, I get the error (SslCtxNew(SslMethodV23) returns Nil, which I assume is because the function is not found in the dll). I tried the 2 dll you suggested, same result. I even did an FC on the synapse sources (in Lazarus and in Delphi): there are no differences. ALL seems to be IDENTICAL except the result (which is the only thing that matters) ---------------------------------------------------------------------- epergola · 2015-08-04 18:52 Uhm now I lost Lazarus. I did absolutly nothing, except running an rer-unning my (RVK) code to check variables value, and suddenly started complaining about incompatible ppu!. I eliminated the installlation directory of Lazarus. I re-installed to a newly named directory. and I get ppu impompatible for Interfacebase. I tried ro re-compile the IDE and now gives me an error: Panic: C:\laz\fpc\2.6.4\bin\i386-win32\make.exe: Interrupt/Exception caught (code = 0xc0000005, addr = 0x72415c3b). It looks like a nightmare. ---------------------------------------------------------------------- rvk · 2015-08-04 21:14 Quote from: epergola on August 04, 2015, 08:52:32 pmI re-installed to a newly named directory. and I get ppu impompatible for Interfacebase. If you reinstalled to a different directory you should check all the paths. Is your previous installation still in the same place. (Try renaming that old directory to Lazarus.old or something). And last you could try Tools>Rescan FPC directory. Quote from: epergola on August 04, 2015, 06:01:22 pmFrom lazarus EXE all is fine. I do not have the libeay32 and ssleay32 in the same directory of the laz. EXE, therefore the laz. EXE *must* load it from windows\system32 (where they were since long) and all works.Laz. EXE...?? Are you talking about a Lazarus executable or a Lazarus dll? You should put your Lazarus .dll, your Delphi .exe and both libeay32 and ssleay32 all in the same directory and it should work ok. Quote from: epergola on August 04, 2015, 06:01:22 pmNow, using my own dll (YOUR code, nothing to do with the ssl dll) in a delphi app, I get the error (SslCtxNew(SslMethodV23) returns Nil, which I assume is because the function is not found in the dll). I tried the 2 dll you suggested, same result. I even did an FC on the synapse sources (in Lazarus and in Delphi): there are no differences. ALL seems to be IDENTICAL except the result (which is the only thing that matters) I've never tried creating a DLL in Lazarus so I'm not sure why it wouldn't work. Let's get some facts in a row: Are you running outside of the Lazarus IDE? Then libeay32 and ssleay32 could just be placed in whatever .exe you run. In this case your Delphi .exe but since you call a Lazarus .dll with that they should be in that directory. But as said before, the easiest way is to all have them in the same directory. Your Lazarus dll with authentication together with a Lazarus application? Does that work? If Lazarus application with your Lazarus dll works correctly I'll need to look at the DLL-code and the way it is called. Quote from: epergola on August 04, 2015, 06:01:22 pmNow, using my own dll (YOUR code, nothing to do with the ssl dll) in a delphi app, I get the error (SslCtxNew(SslMethodV23) returns Nil, which I assume is because the function is not found in the dll). This could also be the result of the correct DLL not being loaded. The SslCtxNew function first checks InitSSLInterface to see if SSL-dlls are loaded. You could check yourself after this call with IsSSLloaded is it is loaded. If it is not loaded when calling your DLL from Delphi but it does load when called from a Lazarus .exe which calls your DLL, this would be very strange. (I was planning to convert this code to Delphi sometime in the future but that doesn't help you now ) ---------------------------------------------------------------------- rvk · 2015-08-04 21:29 Quote from: renabor on August 04, 2015, 02:03:33 pmI was able to compile under Linux using Ararat Synapse (http://sourceforge.net/p/synalist/code/HEAD/tree/trunk/), which give support for ssl, and LazWebkit (http://sourceforge.net/projects/lazwebkit/files/) which is a webbroser. All Lazarus native. But still have some problems on setting up client_id and secret What kind of problems? (I take it you have it running with the demo-id and secret) The steps to setup your own Client_id and secret: Goto https://console.developers.google.com/projectCreate a project (i.e. My testapp)Enter the projectClick APIs & authClick APIsChoose Gmail APIClick Enable APIPress the back-icon (left of Disable API)Do the same for Google+ API (for the person information)If you want Calendar access do the same with Calendar APIOn the left click on CredentialsClick Create new client IDChoose "Installed application" as "Application typeChoose "Other" as "Installed application type"Choose Create Client IDNow you see a Client ID and a Client secretCopy those to the frmmain.pas in the demo-applicationClick on Consent Screen on the leftHere you can design the screen your users see when the application asks for consent ---------------------------------------------------------------------- epergola · 2015-08-04 21:47 Thanks Rvk. What i meant was: the lazarus Application that uses your code, the application i created to test it, was in c:\lazarus\xoauth2. In that directory I have no DLL, so the lib*.dll that openssl uses must load from \windows\system32. In the Laz App I created all  worked fine. Now the Delphi App using the dll I made out of your code, should also load the openssl dll from windows\system32, i.e. the 2 apps should use the same dll's. Why in ssl_opesssl_lib.pas when called by Delphi the ssl is not loaded ( SslCtxNew(SslMethodV23) returns Nil) is a mistery, given tha all is or s/b the same. At the moment I can't do anything in Lazarus b/c I just does not compile my App anymore. I am spending the day trying to re-install.... until hell freezes over, and then maybe it will work. As to the Delphi version, I did not wait because I could not find a Json object that works in D5. That is why I created a DLL with that part of your code: to get the json info. ---------------------------------------------------------------------- rvk · 2015-08-04 21:54 Quote from: epergola on August 04, 2015, 11:47:38 pmAs to the Delphi version, I did not wait because I could not find a Json object that works in D5. That is why I created a DLL with that part of your code: to get the json info. Yeah... the json-part was also what stopped me from porting it right away. I did find some json-libraries I could use but haven't got the time to look which one I could use best. Quote from: epergola on August 04, 2015, 11:47:38 pmAt the moment I can't do anything in Lazarus b/c I just does not compile my App anymore. I am spending the day trying to re-install.... until hell freezes over, and then maybe it will work. I also replied in your other thread as to how to delete the config- directory. That should give you the opportunity to reinstall Lazarus clean. But I would advice going for Lazarus 1.4.2 (instead of the Laz1.10 you mentioned) I see you are already on 1.4.2 If you get it going I wonder what happens if you write a small Lazarus application which calls the DLL. Otherwise I would need to take a look at the DLL code and test some things for myself. ---------------------------------------------------------------------- epergola · 2015-08-04 23:00 For now hell froze and Lazarus (as expected) ... resuscitaded. The problems is in ssl_openssl.lib.pas. In d5 i get 26864832 for _SslMethodV23 := GetProcAddr(SSLLibHandle, 'SSLv23_method');. in Lazarus I get: 268435456. (the pointer to the funcion _SslCtxNew is the same). I wonder if Fpc uses a different calling convention for DLL. I will try to change cdecl to stdcall in D5. If that dows not work i try to make a laz app using my dll. ---------------------------------------------------------------------- rvk · 2015-08-04 23:07 Quote from: epergola on August 05, 2015, 01:00:40 amI wonder if Fpc uses a different calling convention for DLL. I will try to change cdecl to stdcall in D5. Yes, I think you should leave synapse calling openssl dlls at cdecl (c convention) and use stdcall for your own dll. ---------------------------------------------------------------------- epergola · 2015-08-05 03:25 Ok RVK, (my dll uses stdcall). I amd sending you the sample program in Fpc that uses the DLL also created inFpc. Notes. 1. The program is test.lpi, and it uses Unit1.pas, nothing more. It calls the xoauth2.dll (in Uni1pas). 2. Project1.lp1 is the Library that created     the dll. Note: I did not change the output name to "xoauth2.dll" so it creates a Procect1.ddl that s/b renamed to "xoauth2.dll" to be used by Test.lp1. Now from the Lazarus app it gives me an error in StartTLS(). In D5 that was ok, but the ssl returned NIL as I showed previously. WARNING!!! do NOT try to recompile Project1 (the DLL) b/c you would LOOSE Lazarus after doing it: you will not be able to compile any porgram and will have to re-install Lazarus ..... hopefully not until hell freezes over, like in my case! ---------------------------------------------------------------------- rvk · 2015-08-05 03:47 Quote from: epergola on August 05, 2015, 05:25:43 amWARNING!!! do NOT try to recompile Project1 (the DLL) b/c you would LOOSE Lazarus after doing it: you will not be able to compile any porgram and will have to re-install Lazarus ..... hopefully not until hell freezes over, like in my case! oe... I will definitely try that (when I wake up). recompiling a project should never lead to corruption of you Lazarus installation so I'm very curious of what's going wrong. Reinstalling trunk is an automated process here so that won't be a problem. ---------------------------------------------------------------------- epergola · 2015-08-05 04:57 I admire your courage RVK! But ...caution, it happened to me 3 times (twice I did not know what I did to cause it, the 3rd time I found it). I belong to the ones that a trunk refers to tree (the real one, with green leaves). the build / clean amd make -vtkyxz is chinese. If I download the installation EXE i rely on it exclusively to re- install clean. Why would I download the installation EXE if I did understand the trunk and other chinese stuff?. However, compiling a DLL and loosing Lazarus is not acceptable I think, trunk or not trunk. I am going to sleep to. ---------------------------------------------------------------------- rvk · 2015-08-05 11:45 Some remarks while I'm testing: In your unitoauth2.pas you still have aGetXOAuth2Base64 define as a function while in setxauth2 you call it as a procedure. I think it returns the code64 correctly but your unitoauth2.pas is out of date. I suppose you did changed it in your original xoauth2.dll because I do get a code64 back. At first the geTAUHT2 didn't create a token file but that was because I didn't have libeay32.dll and ssleay32.dll in my project directory (or anywhere else in my path). After copying them in the geTAUHT2 worked and produced the tokens.dat and a correct code64. I do have the feeling you might end up in doing the GetAccess 2 times. One time in the CheckTokenFile if there is a tokens.dat and then the second time in GettheAccess in geTAUHT2. You're missing something vital in your TForm1.Button1Click. You receive a code64 (access_token) from your geTAUHT2 but you don't use it anywhere. If you look in my frmmain.pas you'll see I added a helper for TSMTPSend to be able to send the 'AUTH XOAUTH2 code64' right after the smtp.StartTLS. If you don't do that Google won't accept the connection. Then... because I couldn't trace a DLL I made a small geTAUHT3 procedure to not use the DLL but use the functions in unitoauth2 directly. Just to test if something goes wrong there too. But it didn't. Code: [Select]function geTAUHT3(var EMAIL, FULLNAME, code64: string; tokenfile_name: string): boolean; type   TCreateit = procedure(client_id, client_secret: string); stdcall;   TGettheAccess = procedure(var email, fullname: string; UseTokenFile: boolean = False);     stdcall;   TDestroyit = procedure; stdcall;   TCheckTokenFile = function: boolean; stdcall;   TGetXOAuth2Base64=procedure (var val: string);stdcall;   //TGetXOAuth2Base64 = function: string; stdcall;   Tgettoken_filename = procedure(Value: string); stdcall; var   myObj: TObject;   _EC: function: Pointer;   Handle: cardinal;   _Createit: TCreateit;   _GettheAccess: TGettheAccess;   _Destroyit: TDestroyit;   _CheckTokenFile: TCheckTokenFile;   _GetXOAuth2Base64: TGetXOAuth2Base64;   _gettoken_filename: Tgettoken_filename;   msg: string; const   client_id = '896304839415- nnl5e0smrtakhr9r2l3bno0tes2mrtgk.apps.googleusercontent.com';   client_secret = 'dUahHDn3IMyhCIk3qD4tf8E_'; begin   Result := False;   email := '';   _Createit := @unitoauth2.Createit;   if Assigned(_Createit) then   begin     _Createit(client_id, client_secret);     _gettoken_filename := @unitoauth2.gettoken_filename;     if Assigned(_gettoken_filename) then       _gettoken_filename(tokenfile_name);     _CheckTokenFile := @unitoauth2.CheckTokenFile;     if Assigned(_CheckTokenFile) then     begin       if boolean(_CheckTokenFile) <> True then         msg := 'Could not get Authentication';     end     else       msg := 'CheckTokenFile not found';     _GettheAccess := @unitoauth2.GettheAccess;     if Assigned(_GettheAccess) then       _GettheAccess(email, fullname, True);     _GetXOAuth2Base64 := @unitoauth2.aGetXOAuth2Base64;     if Assigned(_GetXOAuth2Base64) then       _GetXOAuth2Base64(code64);     _Destroyit := @unitoauth2.Destroyit;     if Assigned (_Destroyit) then       _Destroyit;   end;   Result := email <> ''; end; Everything went great (getting access and getting the code64) and the testmail is send. When I change it back to geTAUHT2 everything went great until StartTLS. So the correct code64 is received so the xoauth2.dll should work correctly. I get this error: Code: [Select]SMTP ERROR: StartTLS:Success-Other undefined Status After that the program crashes with a SIGSEGV. So the cleanup of the dll is not done correctly. But I'm puzzled as to why that would mess up the TSMTPSend because that is not even done in the dll. Edit: I think it has something to do with the fact that the DLL loads the openssl dll's and after it unloads, it also unloads the openssl dll's while your program still needs those to send the mail. Maybe because your DLL is still part of the same process the DLL's can get unloaded (which is wrong). For instance if you uncheck the FreeLibrary of your own dll it all works: Code: [Select]    end;     // FreeLibrary(Handle);   end; So as a workaround, you could free the DLL at the end of your program and not right after geTAUHT2. Or you need to find a way to "reload" the openssl libraries again for your own .exe. Or you need to find a way for your DLL not to unload the openssl libraries. B.T.W. I had no problems compiling the DLL. In Laz1.4.2 or trunk (=Lazarus SVN development version) So no need for hell to freez over for me ---------------------------------------------------------------------- rvk · 2015-08-05 15:55 Okay... (also please read my previous post above) It's the initialization of openssl that gets lost after you unload your DLL. If you reinitialize openssl after unloading your dll it works fine: (at the end of geTAUHT2) Code: [Select]    end;     FreeLibrary(Handle);   end;   // reinitialize openssl   ssl_openssl_lib.SslLibraryInit;   Result := email <> ''; end; (I'm still not sure why exactly this is necessary) The only thing I still get is the SIGSEGV but that's something to do with the cleanup of your DLL but the mail get send successfully. ---------------------------------------------------------------------- epergola · 2015-08-05 16:20 Hello Rvk That is a bit strange. My dll (basically unitoauth2.pas) does not use opensssl, hence I do not see how the loading/unloading of "xoauth2.dll"  would affect the lib*.dll. I will investigate further. Uhm... either you have the luck of the devil, or I am cursed then, since not once but 3 times, simply compiling Project 1 sent Lazarus to the bananas field. I will keep you posted (not on this last topic though, as I will not touch it!) ---------------------------------------------------------------------- rvk · 2015-08-05 16:29 Quote from: epergola on August 05, 2015, 06:20:32 pmMy dll (basically unitoauth2.pas) does not use opensssl, hence I do not see how the loading/unloading of "xoauth2.dll"  would affect the lib*.dll. O but it does unitoauth2 does some communicating via HTTPS so openssl is needed to set up a connection to https://accounts.google.com/. So it does use the openssl dll's. You just don't explicitly call them but in HttpPostURL and HttpGetText (from Synapse) they do get used. In ssl_openssl_lib there is a finalization part which calls DestroySSLInterface. It tries to unload the library-dll's but because of the reference counting of the DLL, it stays in memory. My guess is that it somehow gets deactivated (the methods get unloaded but the DLL itself stays in memory). That's why calling ssl_openssl_lib.SslLibraryInit again reinitializes the openssl-dll's and you can call them again (see my last post). ---------------------------------------------------------------------- epergola · 2015-08-05 16:52 Ok, I will use the reinit stuff. But I have a doubt. Per se if a module is loaded in a process, unloading it should not affect a module loaded in another  (Hmodule is an address relative to the process it opens it). I suspect i did somethin stupid in my DLL (Project1). In the test program, setxoauth2.pas) I had this (jyst before FreeLibrary): {     @Destroyit := GetProcAddress(Handle, 'Destroyit') ;     if Assigned (Destroyit) then       Destroyit; } and in the Project1.dll procedure Destroyit;stdcall; begin    GoogleOAuth2.destroy; end;           I commented it out b/c it gave me an error (AV I think), and now I realize that I had  var     GoogleOAuth2: TGoogleOAuth2;  in the implementation part (and thinks this was the cause of the error in Destroyit). So I ask you if you would be so kind to move that line to  the interface (unitoauth2.pas), recompile  and send the output to me? (Unless you think all of this is meaningless) ---------------------------------------------------------------------- rvk · 2015-08-05 17:00 Quote from: epergola on August 05, 2015, 06:52:49 pmBut I have a doubt. Per se if a module is loaded in a process, unloading it should not affect a module loaded in another  (Hmodule is an address relative to the process it opens it). I suspect i did somethin stupid in my DLL ... (Unless you think all of this is meaningless) I think, because both your .exe and .dll are part of the same process the openssl dlls share something. Your .exe initializes the openssl.dll (i.e. the methods get loaded) and wait for it to be called by your .exe. After that, in the same process but in your .dll, this is done again by your .dll. It is needed for the https. Then your dll gets unloaded and something in the finalization of the openssl.dll gets called deactivating something, is my guess. After that, when you call the openssl-functions, the methods are not loaded and you get the error. I thought it was strange too that in finalization something from your dll could mess up the loaded part of the .exe but it's the only thing I could come up with and is supported by the fact that reinitialization works. Then the other problem. When exiting the program I get that SIGSEGV. That is due to the DLL and needs to be taken care of. I'll try to make some adjustment and see if that helps. (but I don't think it is because GoogleOAuth2 is in the interface part). ---------------------------------------------------------------------- rvk · 2015-08-05 17:33 Could you post your last project1 (the DLL source) again. You have made some changes (for instance the function to procedure call of GetXOAuth2Base64) and while I can compile the project1.dll and it gets loaded by the demo, I can't seem to be able to call GettheAccess. (It justs exits the complete program). My guess is that something similar also happens to you because just compiling (and only compiler) should never be able to screw up the Lazarus installation. (or maybe your adjusted source does it for me too ) ---------------------------------------------------------------------- epergola · 2015-08-05 17:44 Here it goes ---------------------------------------------------------------------- rvk · 2015-08-05 17:56 Your project1 source is still not valid. It complains that synapse is not found (not a requirement in your options) and the function aGetXOAuth2Base64 declaration is wrong in the interface part and the implementation part. (you have it as a function without a result while in your demo it is a procedure.) I fixed those things here... but the strange thing is the dll compiles ok in a separate folder but when I try to put it all in one folder the output dll is different and crashes. Weird... ---------------------------------------------------------------------- rvk · 2015-08-05 18:03 Also {$DEFINE USE_BIN_STR} should be added above the unit in the DLL. You're working with strings as parameters. According to this it should be added. When you add the ssl_openssl_lib.SslLibraryInit does sending the mail work for you? (i.e. do you also only get a SIGSEGV at the end of your procedure?) ---------------------------------------------------------------------- epergola · 2015-08-05 18:23 No, neither ssl_openssl_lib.SslLibraryInit nor commenting out Freelibrary make any difference. (Sorry for the mismatched units I sent). My D5 program gets an error on Start Tls (returns false). Definitely something is wrong in the dll. If i comment out if not geTAUHT2(EMAIL,FULLNAME,code64,'tokens.dat') the email does not work but get no other error. If not, not only the email does not work, but the line msg_lines.free; gives me an Invalid pointer operation, which seems to indicate a screwup in the memory caused by the function in the dll. Because I cannot touch Project1, could you please send me the latest dll ? P.S. there is no SIGSEGV  in D5. What is it? ---------------------------------------------------------------------- rvk · 2015-08-05 18:33 Quote from: epergola on August 05, 2015, 08:23:23 pmP.S. there is no SIGSEGV  in D5. What is it? No, I get the SIGSEGV (access violation) in the Lazarus demo program. That's the one I'm testing at the moment and when adding the ssl_openssl_lib.SslLibraryInit line (or commenting out Freelibrary everything runs fine until the Button1Click ends and you get the access violation. With the changes of that line (or omitting FreeLibrary) the StartTls- command works correctly for me. The problem of the SIGSEGV is in the procedure GetXOAuth2Base64. When I only comment out that line the procedure runs fine (of course without sending a mail because there is no valid code64 returned). That's the procedure you had a function for and turned into a procedure. So there something goed wrong. You get the error on freeing msg_lines.free because it is the last line of the procedure. Try switching them with the SMTP.free and you see the error turning up there. Just comment out GetXOAuth2Base64(code64); and you see it runs without error. (B.T.W. with all this it would have been easier to adjust unitoauth2.pas with a json-reader for Delphi, SuperObject for instance. Then there would no need for a DLL. But I would love to know why this is not working first ---------------------------------------------------------------------- epergola · 2015-08-05 18:43 You are right in your last point, but i tried Superobject but found several things that I could not overcome with D5. Does {$DEFINE USE_BIN_STR} in the dll means all string are widestring? According to the note below, if we pass widestrings between dll & Exe should be the solution. So if you could send me the xoauth2.dll corrected when you finish I will try it. I found this on the web http://stackoverflow.com/questions/2638652/exchanging-strings-pchar- between-a-freepascal-compiled-dll-and-a-delphi-compil Depending on what data you're passing, you may be able to use WideStrings instead. They're allocated on the Windows heap, so if you allocate one in the DLL and free it in the EXE they'll both go through the same memory manager. You should be able to use a function declaration like this: procedure GetAString(var Result: WideString); stdcall; And Delphi and FreePascal will both handle the allocation and freeing automatically. WideString is the same in the Unicode-enabled Delphis as the pre-Unicode ones, so you won't need to change things for that either. ---------------------------------------------------------------------- rvk · 2015-08-05 19:01 Yep. You're correct. Making fullname,email,code64 widestring and also in the dll the error is gone. Attached the test-project. It contains the project1.dll. (I also changed the source so it would load the project1.dll but you can change that back). Also attached the dll-source. You can see I only changed the widestrings for the var parameter but that does seem to do the trick. Try the test-project in Lazarus and if it doesn't produce an error (and sends a test-mail) try it in your D5 app. (B.T.W. don't forget to implement the helper for sending AUTH with smtpsend otherwise you can't send mail. I don't think D5 had helpers yet so you have to send it yourself through the comm-socket) ---------------------------------------------------------------------- epergola · 2015-08-05 19:47 Great, thanks! It worked in Lazarus. I will try now in Delphi. ---------------------------------------------------------------------- epergola · 2015-08-05 20:11 In delphi i get an error on starttsl. Inspectin FSS at this point: procedure TTCPBlockSocket.SSLDoConnect; begin   ResetLastError;   if not FSSL.Connect then     FLastError := WSASYSNOTREADY;   ExceptCheck; end; If shows: (nil, $AA61CC, False, -1, 'SSL/TLS support is not compiled!', LT_all, '', '', '', '', '', '', '', '', '', '', '', '', False, '', '', '', '', '', -1, '') So I thought of adding SMTP.FullSSL := True; but i got the same result. Any idea? ---------------------------------------------------------------------- rvk · 2015-08-05 20:23 Twee mogelijkheden. Heb je ssl_openssl boven in je uses in je programma gezet? Als je dat niet expliciet doet heb je geen ssl opties beschikbaar in je programma. Gebruik je de laatste nieuwe Synapse library? Dus niet die versie 40 want dat is echt een oude. (Maar bijvoorbeeld de laatste snapshot. Je kunt de download snapshot knop bovenin gebruiken) ---------------------------------------------------------------------- epergola · 2015-08-05 20:30 German or Deutch? Thanke shown,  I understood a bit. Are you saying the lattest stable release (version 40) is not good? ---------------------------------------------------------------------- rvk · 2015-08-05 20:35 Dutch You could first try putting ssl_openssl in the uses clause of your unit in Delphi 5 (if it's not there already). If that doesn't help... then yes... If you have downloaded Synpase on the main page of Ararat Synapse and it is version 40 it is a really old one. Numerous fixes and options have been added to the later version but somehow they don't update the version on their main page. ---------------------------------------------------------------------- epergola · 2015-08-05 20:36 I downloaded the blessed trunk Yes I have uses   Forms,   ssl_openssl,   Unit1 in 'Unit1.pas' {Form1},   setxoauth2 in 'setxoauth2.pas'; But I still get the error: 'SSL/TLS support is not compiled!' ---------------------------------------------------------------------- epergola · 2015-08-05 20:37 I have uses ssl_openssl; in the units as well. ---------------------------------------------------------------------- rvk · 2015-08-05 20:40 Could you try copying the libeay32.dll and ssleay32.dll to your .exe directory just to make sure it sees them. ---------------------------------------------------------------------- epergola · 2015-08-05 20:50 After I downloaded the trunk (made a... big noice falling) I restarted Delphi. Now I get an error on Mailfrom (starttls worked!). if not smtp.MailFrom(EMail, Length(EMail)) then   begin        showmessage('SMTP ERROR: I inspected Email and it orrectly shows: 'pergolaenrico@gmail.com' ---------------------------------------------------------------------- epergola · 2015-08-05 20:52 As to the helper function that do not exist in D5, I put this function directly in the public section of Smtpsend.pas function TSMTPSend.DoXOAuth2(const Value: string): boolean; var   x: integer;   s: string; begin   Sock.SendString('AUTH XOAUTH2 ' + Value + CRLF);   repeat     s := Sock.RecvString(FTimeout);     if Sock.LastError <> 0 then       Break;   until Pos('-', s) <> 4;   x := StrToIntDef(Copy(s, 1, 3), 0);   Result := (x = 235); end; ---------------------------------------------------------------------- rvk · 2015-08-05 20:55 Quote from: epergola on August 05, 2015, 10:52:48 pmAs to the helper function that do not exist in D5, I put this function directly in the public section of Smtpsend.pas Quote from: epergola on August 05, 2015, 10:50:54 pmNow I get an error on Mailfrom (starttls worked!). if not smtp.MailFrom(EMail, Length(EMail)) then   begin        showmessage('SMTP ERROR: Did you also call the DoXOAuth2(code64) after the StartTLS? It is needed before doing the MailFrom. (almost there ) ---------------------------------------------------------------------- epergola · 2015-08-05 20:57 IT WORKS!!! I am a donkey! I had forgotten again the if not smtp.DoXOAuth2(code64) then after Starttls Thanks A LOT Rvk!! ---------------------------------------------------------------------- epergola · 2015-08-05 22:46 I found that these lines (in setxoauth2.pas) were not only useless but disrupted the code, when there is no tokens.dat {       CheckTokenFile := TCheckTokenFile(GetProcAddress(Handle, 'CheckTokenFile'));       if Assigned(CheckTokenFile) then       begin         if boolean(CheckTokenFile) <> True then           msg := 'Could not get Authentication';       end       else         msg := 'CheckTokenFile not found'; } Now, without them it works. But what when the browser title does not work correclty? how to pass an input box type from the DLL? Any idea? ---------------------------------------------------------------------- epergola · 2015-08-06 00:04 ok forget about the input box. I do not see why anyone would want to input manually. However I changed the code as per attached. This part:        if UseBrowserTitle then begin           ;;;;;;;;;/         end;                    ;         if Authorize_token = '' then            inspect Body.InnerHtml; Otherwise if the title is broken they would not get access. But furthermore, I have no idea where you took your SearchFor := 'readonly="readonly" value="'; There is no such thing in the html (maybe in the Dutch google?) . I changed the code (see the attachment) and it works like a charm now I added the previously forgotten attachment ---------------------------------------------------------------------- rvk · 2015-08-06 10:25 Quote from: epergola on August 06, 2015, 12:46:12 amI found that these lines (in setxoauth2.pas) were not only useless but disrupted the code, when there is no tokens.dat Yup. I already mentioned I had the feeling that when there is no tokens.dat the getaccess would be called twice. Quote from: epergola on August 06, 2015, 02:04:04 amBut furthermore, I have no idea where you took your SearchFor := 'readonly="readonly" value="'; That's the problem with the body-html method. The source-code is an undocumented way to get the code and can change at any moment. That said... for me it's still this: Code: [Select] Are you sure you don't have that readonly before it. I wasn't sure every page that is returned (also in case of error) didn't have "value=" in the source so I used this one. But again... It's best to keep using the UseBrowserTitle:=true according to Google. QuoteIf you set the redirect_uri to urn:ietf:wg:oauth:2.0:oob, Google's authorization server will return a page to the browser like the one shown below. Your application can then extract the authorization code from the page title. So the browser-title should always be correct. Was it not correct for you??? Last time I had a cut-off code was about a year ago and I figured Google fixed it for IE by now. When there is no code in the title the code goes to searching the body automatically. This will only work if there is really no code. If the code is cut-off then an invalid code is received and the library thinks it can work with that. That's why I implemented this workaround, but only to be used if the Title-method failed. Maybe it would have been best to retrieve both the title-code and the body-html code and first check, in code, if it can gain access with the title- code and if not give the other code back to the user. That's for the future and only necessary if the Title-code is faulty on some system. That's why I wanted to know if the Title-code failed at your end. ---------------------------------------------------------------------- epergola · 2015-08-06 15:22 No, the title code did not fail with me. Since you had that html check part in your code and said that the title check once failed with you, I wanted to test it. Hence I did the change. I put some debug lines written to a file (attached), and this is my conclusion. 1. 'value="'     is not present in my html,     'value='     is 2. I went then to  check for 'value='    and found.    So I did not realize that even the Browser.LocationName contained the code. 3. However I think that looking for 'this.select(); value=' is the safe way, b/c that is what is copied to the clipboard if the user selects it. ---------------------------------------------------------------------- epergola · 2015-08-06 15:27 I forgot to add this. You say: "Maybe it would have been best to retrieve both the title-code and the body-html code" I showed you my change in the 2nd last attachment, that should accomplish that:         if UseBrowserTitle then begin           Found := Browser.LocationName;           Authorize_token := Copy(Found, Length(SearchFor) + 1, 1000);           DebugLine('Authorization: We used the browser-title');         end;                    ;         if Authorize_token = '' then begin                ---------------------------------------------------------------------- rvk · 2015-08-06 15:46 Quote from: epergola on August 06, 2015, 05:22:12 pm3. However I think that looking for 'this.select(); value=' is the safe way, b/c that is what is copied to the clipboard if the user selects it. O, wow. That html-code really has some differences from mine (attached). I wouldn't check on 'this.select(); value=' because in my case there is " type="text" readonly="readonly"  in between there. And there could be for your users too. In your case I would only check for value= and optionally remove " or ' if they are around the result. Your code even has an invalid onclick-javascript. It should be surounded by " or ' (shouldn't it?). Code: [Select] So best is to go for the browser title and if that doesn't work go for value=?xxxx? (where ? could or could not be " or '). Quote from: epergola on August 06, 2015, 05:27:33 pmI showed you my change in the 2nd last attachment, that should accomplish that:         if UseBrowserTitle then begin           Found := Browser.LocationName;           Authorize_token := Copy(Found, Length(SearchFor) + 1, 1000);           DebugLine('Authorization: We used the browser-title');         end;                    ;         if Authorize_token = '' then begin                Yep. That's a useful addition. However if the code is cut off in the browser-title (which it once was for me) the check for html would not be done (because Authorize_token <> '' but invalid). So retrieving both and checking the browser-title code and if that one is not correct use the html-body one is the safest way. But then again... maybe the whole html-body check isn't necessary anymore (if Google fixed it). This was, if I remember correctly, when the code could contain invalid url-characters like & and +. In that case there was a problem retrieving it. But I haven't seen that in a long time. ---------------------------------------------------------------------- epergola · 2015-08-08 05:00 For curiousity, could you attach your html? ---------------------------------------------------------------------- rvk · 2015-08-08 07:23 Quote from: rvk on August 06, 2015, 05:46:30 pmQuote from: epergola on August 06, 2015, 05:22:12 pm3. However I think that looking for 'this.select(); value=' is the safe way, b/c that is what is copied to the clipboard if the user selects it. O, wow. That html-code really has some differences from mine (attached). Woops, yes, forgot to attached it. (now it is) It's copied from the debug screen from my demo-program so has some lines around it. I also recently saw a final screen (after the consent-screen) with code to be copied, which was completely different from the standard. I think it was the authentication for sign up for a free Sublight subtitle downloader-account. I haven't seen the option in the Google Console but maybe the final screen is also (re-)designable. Edit: I also attached that one. You'll see it's completely different but checking for value="xxx" would still work. ---------------------------------------------------------------------- epergola · 2015-08-09 20:05 Well i do not know what those are. In my code, i simply did this: Found := Body.InnerHtml; writeln(f,found)             SearchFor := 'value=';             n:=Pos(SearchFor, Found); and the text file showed what I sent you. One more question, if  Authorize_token = '' then does not work, how can we check if Authorize_token is invalid? ---------------------------------------------------------------------- rvk · 2015-08-09 20:39 I've changed my code at github: https://github.com/rvk01/google-oauth2 What I've done is always read the Authentication_token and Authentication_token_html. Then in GetRefresh_token it will first check Authentication_token. If it doesn't get a valid access_token the procedure is run again but now with Authentication_token_html (they are swapped just before). last part of GetRefresh_token: Code: [Select]procedure TGoogleOAuth2.GetRefresh_token; //...   // If we haven't got a Authentication token we need to ask permission   if Authorize_token = '' then     GetAuthorize_token_interactive;   if (Authorize_token = '') then     exit;   LogLine('Getting new Refresh_token');   //...   // getting the real refreshtoken   //...   // finally check if we have a access_token otherwise run again   if (access_token = '') and (Authorize_token_html <> '') then   begin     LogLine('Using backup Authentication token (html)');     Authorize_token := Authorize_token_html;     Authorize_token_html := '';     GetRefresh_token;   end; end; (I haven't changed the value="" yet, it still reads that "readonly". But I will change that later) ---------------------------------------------------------------------- epergola · 2015-08-10 21:44 I don't understand. You said that if the Authorize_token is not blank. But you still check if it is blank? if Authorize_token = '' then     GetAuthorize_token_interactive;   if (Authorize_token = '') then     exit; P.S. I do not undeerstand the "read only" stuff. In the InnerHtml, there is no read-only and the value es extracted correctly. ---------------------------------------------------------------------- rvk · 2015-08-10 22:03 Quote from: epergola on August 10, 2015, 11:44:07 pmYou said that if the Authorize_token is not blank. But you still check if it is blank? It's blank if you first go into the procedure and GetAuthorize_token_interactive is executed the first time. But at the end of the procedure I swap the Authorize_token for Authorize_token_html and I run the same procedure again. But the second time the GetAuthorize_token_interactive must not execute because we already got that Authorize_token (which was the Authorize_token_html from before). And exiting GetAuthorize_token_interactive the Authorize_token could still be blank if the authentication was cancelled or in case of manual input there was nothing entered. So checking afterwards is still necessary. Quote from: epergola on August 10, 2015, 11:44:07 pmI do not undeerstand the "read only" stuff. In the InnerHtml, there is no read- only and the value es extracted correctly. There is for me. And if there is for me, there could be for your users too. I changed it in my latest version to only check value=?xxx? where ? could be " or ' or empty. Because for you the authentication code was not surround by " but for me it was. But I must state again. I'm not sure if the browser-title method will ever fail. Google changed something (the code doesn't contain invalid characters anymore) so it will always succeed. If I don't hear from errors in the browser-title, I might remove the html-method in the future. Here was the original message from me on the dutch forum where I discovered the browser-title was cut off due to invalid characters. It was in october 2014. But I haven't seen it since. So I don't think this will happen again. So putting much effort in the html-method might be a waste of time.