static void LoadGeminiLink(Uri uri) { string result; bool retrieved = false; GeminiResponse resp; resp = new GeminiResponse(); try { resp = (GeminiResponse)Gemini.Fetch(uri); retrieved = true; } catch (Exception e) { //the native gui.cs Messagebox does not resize to show enough content //so we use our own that is better Dialogs.MsgBoxOK("Gemini error", uri.AbsoluteUri + "\n\n" + e.Message); } if (retrieved) { if (resp.codeMajor == '2') { //examine the first component of the media type up to any semi colon switch (resp.mime.Split(';')[0].Trim()) { case "text/gemini": case "text/plain": case "text/html": { string body = Encoding.UTF8.GetString(resp.bytes.ToArray()); result = (body); if (!resp.mime.StartsWith("text/gemini")) { //display as preformatted text result = "```\n" + result + "\n```\n"; } break; } default: // report the mime type only for now result = ("Some " + resp.mime + " content was received, but cannot currently be displayed."); break; } //render the content and add to history SetAsCurrent(resp.uri); //remember the final URI, since it may have been redirected. if (_history.Count > 0) { _history.Peek().top = _lineView.TopItem; _history.Peek().selected = _lineView.SelectedItem; //remember the line offset of the current page } _history.Push(new CachedPage(resp.uri, result, 0, 0)); RenderGemini(resp.uri.AbsoluteUri, result, _lineView); } else if (resp.codeMajor == '1') { //input requested from server var userResponse = Dialogs.SingleLineInputBox("Input request from: " + uri.Authority, resp.meta, ""); if ((userResponse.ButtonPressed == TextDialogResponse.Buttons.Ok) && (userResponse.Text != "")) { var ub = new UriBuilder(uri); ub.Query = userResponse.Text; LoadGeminiLink(ub.Uri); } } else if ((resp.codeMajor == '5') && (resp.codeMinor == '1')) { //not found Dialogs.MsgBoxOK("Not found", "The resource was not found on the server: \n\n" + resp.uri.AbsoluteUri); } else { Dialogs.MsgBoxOK("Gemini server response", uri.AbsoluteUri + "\n\n" + "Status: " + resp.codeMajor + resp.codeMinor + ": " + resp.meta); } } }
public void NavigateGeminiScheme(string fullQuery, System.Windows.Navigation.NavigatingCancelEventArgs e, SiteIdentity siteIdentity, bool requireSecure = true) { var geminiUri = e.Uri.OriginalString; var sessionPath = Session.Instance.SessionPath; var appDir = AppDomain.CurrentDomain.BaseDirectory; var settings = new UserSettings(); var hash = HashService.GetMd5Hash(fullQuery); //uses .txt as extension so content loaded as text/plain not interpreted by the browser //if user requests a view-source. var rawFile = sessionPath + "\\" + hash + ".txt"; var gmiFile = sessionPath + "\\" + hash + ".gmi"; var htmlFile = sessionPath + "\\" + hash + ".htm"; //delete txt file as GemGet seems to sometimes overwrite not create afresh File.Delete(rawFile); File.Delete(gmiFile); //delete any existing html file to encourage webbrowser to reload it File.Delete(htmlFile); var uri = new Uri(fullQuery); //use a proxy for any other scheme that is not gemini var proxy = ""; //use none var connectInsecure = false; X509Certificate2 certificate; certificate = Session.Instance.CertificatesManager.GetCertificate(uri.Host); //may be null if none assigned or available if (uri.Scheme != "gemini") { proxy = settings.HttpSchemeProxy; } try { GeminiResponse geminiResponse; try { geminiResponse = (GeminiResponse)Gemini.Fetch(uri, certificate, proxy, connectInsecure, settings.MaxDownloadSizeMb * 1024, settings.MaxDownloadTimeSeconds); } catch (Exception err) { //warn, but continue if there are server validation errors //in these early days of Gemini we dont forbid visiting a site with an expired cert or mismatched host name //but we do give a warning each time if (err.Message == "The remote certificate was rejected by the provided RemoteCertificateValidationCallback.") { mMainWindow.ToastNotify("Note: the certificate from: " + e.Uri.Authority + " is expired or invalid.", ToastMessageStyles.Warning); //try again insecure this time geminiResponse = (GeminiResponse)Gemini.Fetch(uri, certificate, proxy, true, settings.MaxDownloadSizeMb * 1024, settings.MaxDownloadTimeSeconds); } else { //reraise throw; } } if (geminiResponse.codeMajor == '1') { //needs input, then refetch mMainWindow.ToggleContainerControlsForBrowser(true); NavigateGeminiWithInput(e, geminiResponse); } else if (geminiResponse.codeMajor == '2') { //success File.WriteAllBytes(rawFile, geminiResponse.bytes.ToArray()); if (File.Exists(rawFile)) { if (geminiResponse.meta.Contains("text/gemini")) { File.Copy(rawFile, gmiFile); } else if (geminiResponse.meta.Contains("text/html")) { //is an html file served over gemini - probably not common, but not unheard of var htmltoGmiResult = ConverterService.HtmlToGmi(rawFile, gmiFile); if (htmltoGmiResult.Item1 != 0) { mMainWindow.ToastNotify("Could not convert HTML to GMI: " + fullQuery, ToastMessageStyles.Error); mMainWindow.ToggleContainerControlsForBrowser(true); e.Cancel = true; return; } } else if (geminiResponse.meta.Contains("text/")) { //convert plain text to a gemini version (wraps it in a preformatted section) var textToGmiResult = ConverterService.TextToGmi(rawFile, gmiFile); if (textToGmiResult.Item1 != 0) { mMainWindow.ToastNotify("Could not render text as GMI: " + fullQuery, ToastMessageStyles.Error); mMainWindow.ToggleContainerControlsForBrowser(true); e.Cancel = true; return; } } else { //a download //its an image - rename the raw file and just show it var pathFragment = (new UriBuilder(fullQuery)).Path; var ext = Path.GetExtension(pathFragment); var binFile = rawFile + (ext == "" ? ".tmp" : ext); File.Copy(rawFile, binFile, true); //rename overwriting if (geminiResponse.meta.Contains("image/")) { mMainWindow.ShowImage(fullQuery, binFile, e); } else { SaveFileDialog saveFileDialog = new SaveFileDialog(); saveFileDialog.FileName = Path.GetFileName(pathFragment); if (saveFileDialog.ShowDialog() == true) { try { //save the file var savePath = saveFileDialog.FileName; File.Copy(binFile, savePath, true); //rename overwriting mMainWindow.ToastNotify("File saved to " + savePath, ToastMessageStyles.Success); } catch (SystemException err) { mMainWindow.ToastNotify("Could not save the file due to: " + err.Message, ToastMessageStyles.Error); } } mMainWindow.ToggleContainerControlsForBrowser(true); e.Cancel = true; } return; } if (geminiResponse.uri.ToString() != fullQuery) { string redirectUri = fullQuery; if (geminiResponse.uri.ToString().Contains("://")) { //a full url //normalise the URi (e.g. remove default port if specified) redirectUri = UriTester.NormaliseUri(new Uri(geminiResponse.uri.ToString())).ToString(); } else { //a relative one var baseUri = new Uri(fullQuery); var targetUri = new Uri(baseUri, geminiResponse.uri.ToString()); redirectUri = UriTester.NormaliseUri(targetUri).ToString(); } var finalUri = new Uri(redirectUri); if (e.Uri.Scheme == "gemini" && finalUri.Scheme != "gemini") { //cross-scheme redirect, not supported mMainWindow.ToastNotify("Cross scheme redirect from Gemini not supported: " + redirectUri, ToastMessageStyles.Warning); mMainWindow.ToggleContainerControlsForBrowser(true); e.Cancel = true; return; } else { //others e.g. http->https redirect are fine } //redirected to a full gemini url geminiUri = redirectUri; //regenerate the hashes using the redirected target url hash = HashService.GetMd5Hash(geminiUri); var gmiFileNew = sessionPath + "\\" + hash + ".txt"; var htmlFileNew = sessionPath + "\\" + hash + ".htm"; //move the source file try { if (File.Exists(gmiFileNew)) { File.Delete(gmiFileNew); } File.Move(gmiFile, gmiFileNew); } catch (Exception err) { mMainWindow.ToastNotify(err.ToString(), ToastMessageStyles.Error); } //update locations of gmi and html file gmiFile = gmiFileNew; htmlFile = htmlFileNew; } else { geminiUri = fullQuery; } var userThemesFolder = ResourceFinder.LocalOrDevFolder(appDir, @"GmiConverters\themes", @"..\..\..\GmiConverters\themes"); var userThemeBase = Path.Combine(userThemesFolder, settings.Theme); mMainWindow.ShowUrl(geminiUri, gmiFile, htmlFile, userThemeBase, siteIdentity, e); } } // codemajor = 3 is redirect - should eventually end in success or raise an error else if (geminiResponse.codeMajor == '4') { mMainWindow.ToastNotify("Temporary failure (status 4X)\n\n" + e.Uri.ToString(), ToastMessageStyles.Warning); } else if (geminiResponse.codeMajor == '5') { if (geminiResponse.codeMinor == '1') { mMainWindow.ToastNotify("Page not found\n\n" + e.Uri.ToString(), ToastMessageStyles.Warning); } else { mMainWindow.ToastNotify("Permanent failure (status 5X)\n\n" + geminiResponse.meta + "\n\n" + e.Uri.ToString(), ToastMessageStyles.Warning); } } else if (geminiResponse.codeMajor == '6') { mMainWindow.ToastNotify("Certificate requried. Choose one and try again.\n\n" + e.Uri.ToString(), ToastMessageStyles.Warning); } else { mMainWindow.ToastNotify("Unexpected output from server " + "(status " + geminiResponse.codeMajor + "." + geminiResponse.codeMinor + ") " + geminiResponse.meta + "\n\n" + e.Uri.ToString(), ToastMessageStyles.Warning); } } catch (Exception err) { //generic handler for other runtime errors mMainWindow.ToastNotify("Error getting gemini content for " + e.Uri.ToString() + "\n\n" + err.Message, ToastMessageStyles.Warning); } //make the window responsive again mMainWindow.ToggleContainerControlsForBrowser(true); //no further navigation right now e.Cancel = true; }
static void LoadLink(Uri uri) { string result; //special treatment for about: scheme if (uri.Scheme == "about") { result = ReadAboutSchemeFile(uri); RenderGemini(uri.AbsoluteUri, result, _lineView); _history.Push(new CachedPage(uri, result, 0, 0)); SetAsCurrent(uri); return; } bool retrieved = false; GeminiResponse resp; resp = new GeminiResponse(); try { resp = (GeminiResponse)Gemini.Fetch(uri); retrieved = true; } catch (Exception e) { //seems to be a bug that the native Messagebox does not resize to show enough content MsgBoxOK("Retrieval error", e.Message); } if (retrieved) { if (resp.codeMajor == '2') { //examine the first component of the media type up to any semi colon switch (resp.mime.Split(';')[0].Trim()) { case "text/gemini": case "text/plain": case "text/html": { string body = Encoding.UTF8.GetString(resp.bytes.ToArray()); result = (body); if (!resp.mime.StartsWith("text/gemini")) { //display as preformatted text result = "```\n" + result + "\n```\n"; } break; } default: // report the mime type only for now result = ("Some " + resp.mime + " content was received, but cannot currently be displayed."); break; } //render the content and add to history SetAsCurrent(resp.uri); //remember the final URI, since it may have been redirected. if (_history.Count > 0) { _history.Peek().top = _lineView.TopItem; _history.Peek().selected = _lineView.SelectedItem; //remember the line offset of the current page } _history.Push(new CachedPage(resp.uri, result, 0, 0)); RenderGemini(resp.uri.AbsoluteUri, result, _lineView); } else if (resp.codeMajor == '1') { //input requested from server var userText = InputBox("Input request from: " + uri.Authority, resp.meta, ""); if (userText != "") { var ub = new UriBuilder(uri); ub.Query = userText; LoadLink(ub.Uri); } } else { MsgBoxOK("Gemini error", "Status: " + resp.codeMajor + resp.codeMinor + ": " + resp.meta); } } }