/* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom * * For more information see: http://getgreenshot.org/ * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using log4net; namespace GreenshotPlugin.Core.OAuth { /// /// OAuth 2.0 verification code receiver that runs a local server on a free port /// and waits for a call with the authorization verification code. /// public class LocalServerCodeReceiver { private static readonly ILog Log = LogManager.GetLogger(typeof(LocalServerCodeReceiver)); private readonly ManualResetEvent _ready = new ManualResetEvent(true); /// /// The call back format. Expects one port parameter. /// Default: http://localhost:{0}/authorize/ /// public string LoopbackCallbackUrl { get; set; } = "http://localhost:{0}/authorize/"; /// /// HTML code to to return the _browser, default it will try to close the _browser / tab, this won't always work. /// You can use CloudServiceName where you want to show the CloudServiceName from your OAuth2 settings /// public string ClosePageResponse { get; set; } = @" OAuth 2.0 Authentication CloudServiceName Greenshot received information from CloudServiceName. You can close this browser / tab if it is not closed itself... "; private string _redirectUri; /// /// The URL to redirect to /// protected string RedirectUri { get { if (!string.IsNullOrEmpty(_redirectUri)) { return _redirectUri; } return _redirectUri = string.Format(LoopbackCallbackUrl, GetRandomUnusedPort()); } } private string _cloudServiceName; private readonly IDictionary _returnValues = new Dictionary(); /// /// The OAuth code receiver /// /// /// Dictionary with values public IDictionary ReceiveCode(OAuth2Settings oauth2Settings) { // Set the redirect URL on the settings oauth2Settings.RedirectUrl = RedirectUri; _cloudServiceName = oauth2Settings.CloudServiceName; using (var listener = new HttpListener()) { listener.Prefixes.Add(oauth2Settings.RedirectUrl); try { listener.Start(); // Get the formatted FormattedAuthUrl string authorizationUrl = oauth2Settings.FormattedAuthUrl; Log.DebugFormat("Open a browser with: {0}", authorizationUrl); Process.Start(authorizationUrl); // Wait to get the authorization code response. var context = listener.BeginGetContext(ListenerCallback, listener); _ready.Reset(); while (!context.AsyncWaitHandle.WaitOne(1000, true)) { Log.Debug("Waiting for response"); } } catch (Exception) { // Make sure we can clean up, also if the thead is aborted _ready.Set(); throw; } finally { _ready.WaitOne(); listener.Close(); } } return _returnValues; } /// /// Handle a connection async, this allows us to break the waiting /// /// IAsyncResult private void ListenerCallback(IAsyncResult result) { HttpListener listener = (HttpListener)result.AsyncState; //If not listening return immediately as this method is called one last time after Close() if (!listener.IsListening) { return; } // Use EndGetContext to complete the asynchronous operation. HttpListenerContext context = listener.EndGetContext(result); // Handle request HttpListenerRequest request = context.Request; try { NameValueCollection nameValueCollection = request.QueryString; // Get response object. using (HttpListenerResponse response = context.Response) { // Write a "close" response. byte[] buffer = Encoding.UTF8.GetBytes(ClosePageResponse.Replace("CloudServiceName", _cloudServiceName)); // Write to response stream. response.ContentLength64 = buffer.Length; using var stream = response.OutputStream; stream.Write(buffer, 0, buffer.Length); } // Create a new response URL with a dictionary that contains all the response query parameters. foreach (var name in nameValueCollection.AllKeys) { if (!_returnValues.ContainsKey(name)) { _returnValues.Add(name, nameValueCollection[name]); } } } catch (Exception) { context.Response.OutputStream.Close(); throw; } _ready.Set(); } /// /// Returns a random, unused port. /// /// port to use private static int GetRandomUnusedPort() { var listener = new TcpListener(IPAddress.Loopback, 0); try { listener.Start(); return ((IPEndPoint)listener.LocalEndpoint).Port; } finally { listener.Stop(); } } } }