//convert GopherText to GMI and save to outpath public static Tuple <int, string, string> GophertoGmi(string gopherPath, string outPath, string uri, GopherParseTypes parseType) { var appDir = System.AppDomain.CurrentDomain.BaseDirectory; var finder = new ResourceFinder(); var parseScript = (parseType == GopherParseTypes.Map) ? "GophermapToGmi.r3" : "GophertextToGmi.r3"; //allow for rebol and converters to be in sub folder of exe (e.g. when deployed) //otherwise we use the development ones which are version controlled var rebolPath = finder.LocalOrDevFile(appDir, @"Rebol", @"..\..\Rebol", "r3-core.exe"); var scriptPath = finder.LocalOrDevFile(appDir, @"GmiConverters", @"..\..\GmiConverters", parseScript); //due to bug in rebol 3 at the time of writing (mid 2020) there is a known bug in rebol 3 in //working with command line parameters, so we need to escape quotes //see https://stackoverflow.com/questions/6721636/passing-quoted-arguments-to-a-rebol-3-script //also hypens are also problematic, so we base64 each parameter and unpack it in the script var command = String.Format("\"{0}\" -cs \"{1}\" \"{2}\" \"{3}\" \"{4}\" ", rebolPath, scriptPath, Base64Service.Base64Encode(gopherPath), Base64Service.Base64Encode(outPath), Base64Service.Base64Encode(uri) ); var execProcess = new ExecuteProcess(); var result = execProcess.ExecuteCommand(command); return(result); }
//convert text to GMI for raw text public static Tuple <int, string, string> HtmlToGmi(string rawPath, string outPath) { var appDir = System.AppDomain.CurrentDomain.BaseDirectory; var finder = new ResourceFinder(); //allow for rebol and converters to be in sub folder of exe (e.g. when deployed) //otherwise we use the development ones which are version controlled var converterPath = finder.LocalOrDevFile(appDir, @"HtmlToGmi", @"..\..\..\HtmlToGmi", "html2gmi.exe"); //for some unknown reason, the -m flag (numbered citations) must not be last when calling from this context //-e (show embedded images as links) //-n (number links) var command = String.Format("\"{0}\" -mne -i \"{1}\" -o \"{2}\"", converterPath, rawPath, outPath ); var execProcess = new ExecuteProcess(); var result = execProcess.ExecuteCommand(command); return(result); }
//convert GMI to HTML for display and save to outpath public static Tuple <int, string, string> GmiToHtml(string gmiPath, string outPath, string uri, SiteIdentity siteIdentity, string theme, bool showWebHeader) { var appDir = AppDomain.CurrentDomain.BaseDirectory; //allow for rebol and converters to be in sub folder of exe (e.g. when deployed) //otherwise we use the development ones which are version controlled var rebolPath = ResourceFinder.LocalOrDevFile(appDir, @"Rebol", @"..\..\..\Rebol", "r3-core.exe"); var scriptPath = ResourceFinder.LocalOrDevFile(appDir, @"GmiConverters", @"..\..\..\GmiConverters", "GmiToHtml.r3"); var identiconUri = new Uri(siteIdentity.IdenticonImagePath()); var fabricUri = new Uri(siteIdentity.FabricImagePath()); //due to bug in rebol 3 at the time of writing (mid 2020) there is a known bug in rebol 3 in //working with command line parameters, so we need to escape quotes //see https://stackoverflow.com/questions/6721636/passing-quoted-arguments-to-a-rebol-3-script //also hypens are also problematic, so we base64 each param and unpack in the script var command = string.Format("\"{0}\" -cs \"{1}\" \"{2}\" \"{3}\" \"{4}\" \"{5}\" \"{6}\" \"{7}\" \"{8}\" \"{9}\" \"{10}\" ", rebolPath, scriptPath, Base64Service.Base64Encode(gmiPath), Base64Service.Base64Encode(outPath), Base64Service.Base64Encode(uri), Base64Service.Base64Encode(theme), Base64Service.Base64Encode(identiconUri.AbsoluteUri), Base64Service.Base64Encode(fabricUri.AbsoluteUri), Base64Service.Base64Encode(siteIdentity.GetId()), Base64Service.Base64Encode(siteIdentity.GetSiteId()), Base64Service.Base64Encode(showWebHeader ? "true" : "false")); var result = ExecuteProcess.ExecuteCommand(command); return(result); }
public bool NavigateGeminiScheme(string fullQuery, Uri uri, SiteIdentity siteIdentity, bool requireSecure = true) { bool navigated = true; var geminiUri = uri.OriginalString; var sessionPath = Session.Instance.SessionPath; var appDir = AppDomain.CurrentDomain.BaseDirectory; //use local or dev binary for gemget var gemGet = ResourceFinder.LocalOrDevFile(appDir, "Gemget", "..\\..\\..\\..\\Gemget", "gemget-windows-386.exe"); 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 settings = new Settings(); string command = ""; var secureFlag = requireSecure ? "" : " -i "; if (uri.Scheme == "gemini") { //pass options to gemget for download command = string.Format( "\"{0}\" {1} --header --no-progress-bar -m \"{2}\"Mb -t {3} -o \"{4}\" \"{5}\"", gemGet, secureFlag, settings.MaxDownloadSizeMb, settings.MaxDownloadTimeSeconds, rawFile, fullQuery); } else { //pass options to gemget for download using the assigned http proxy, such as //duckling-proxy https://github.com/LukeEmmet/duckling-proxy //this should obviously be a trusted server since it is in the middle of the //request command = string.Format( "\"{0}\" {1} --header --no-progress-bar -m \"{2}\"Mb -t {3} -o \"{4}\" -p \"{5}\" \"{6}\"", gemGet, secureFlag, settings.MaxDownloadSizeMb, settings.MaxDownloadTimeSeconds, rawFile, settings.HttpSchemeProxy, fullQuery); } var result = ExecuteProcess.ExecuteCommand(command, true, true); var geminiResponse = new Response.GeminiResponse(fullQuery); geminiResponse.ParseGemGet(result.Item2); //parse stdout geminiResponse.ParseGemGet(result.Item3); //parse stderr //ToastNotify(geminiResponse.Status + " " + geminiResponse.Meta); //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 (result.Item1 == 1 && requireSecure) { var tryInsecure = false; var securityError = ""; if (geminiResponse.Errors[0].Contains("server cert is expired")) { tryInsecure = true; securityError = "Server certificate is expired"; } else if (geminiResponse.Errors[0].Contains("hostname does not verify")) { tryInsecure = true; securityError = "Host name does not verify"; } if (tryInsecure) { //give a warning and try again with insecure mMainWindow.ToastNotify("Note: " + securityError + " for: " + uri.Authority, ToastMessageStyles.Warning); NavigateGeminiScheme(fullQuery, uri, siteIdentity, false); return(true); } } if (geminiResponse.AbandonedTimeout || geminiResponse.AbandonedSize) { var abandonMessage = string.Format( "Download was abandoned as it exceeded the max size ({0}) or time ({1} s). See GemiNaut settings for details.\n\n{2}", settings.MaxDownloadSizeMb, settings.MaxDownloadTimeSeconds, fullQuery); mMainWindow.ToastNotify(abandonMessage, ToastMessageStyles.Warning); mMainWindow.ToggleContainerControlsForBrowser(true); return(false); } 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); return(false); } } 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); return(false); } } 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); } 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); return(false); } return(true); } if (geminiResponse.Redirected) { string redirectUri = fullQuery; if (geminiResponse.FinalUrl.Contains("://")) { //a full url //normalise the URi (e.g. remove default port if specified) redirectUri = UriTester.NormaliseUri(new Uri(geminiResponse.FinalUrl)).ToString(); } else { //a relative one var baseUri = new Uri(fullQuery); var targetUri = new Uri(baseUri, geminiResponse.FinalUrl); redirectUri = UriTester.NormaliseUri(targetUri).ToString(); } var finalUri = new Uri(redirectUri); if (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); return(false); } 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); } else if (geminiResponse.Status == 10 || geminiResponse.Status == 11) { //needs input mMainWindow.ToggleContainerControlsForBrowser(true); navigated = NavigateGeminiWithInput(uri, geminiResponse.Meta); } else if (geminiResponse.Status == 50 || geminiResponse.Status == 51) { mMainWindow.ToastNotify("Page not found (status 51)\n\n" + uri.ToString(), ToastMessageStyles.Warning); } else { //some othe error - show to the user for info mMainWindow.ToastNotify(string.Format( "Cannot retrieve the content (exit code {0}): \n\n{1} \n\n{2}", result.Item1, string.Join("\n\n", geminiResponse.Info), string.Join("\n\n", geminiResponse.Errors) ), ToastMessageStyles.Error); } mMainWindow.ToggleContainerControlsForBrowser(true); //no further navigation right now return(navigated); }
public void NavigateGopherScheme(string fullQuery, System.Windows.Navigation.NavigatingCancelEventArgs e, SiteIdentity siteIdentity) { var sessionPath = Session.Instance.SessionPath; var appDir = System.AppDomain.CurrentDomain.BaseDirectory; //check if it is a query selector without a parameter if (!e.Uri.OriginalString.Contains("%09") && e.Uri.PathAndQuery.StartsWith("/7/")) { NavigateGopherWithInput(e); mMainWindow.ToggleContainerControlsForBrowser(true); //no further navigation right now e.Cancel = true; return; } var proc = new ExecuteProcess(); var finder = new ResourceFinder(); //use local or dev binary for gemget var gopherClient = finder.LocalOrDevFile(appDir, "GopherGet", "..\\..\\..\\GopherGet", "gopher-get.exe"); string hash; 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 gopherFile = 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(gopherFile); //delete any existing html file to encourage webbrowser to reload it File.Delete(gmiFile); //save to the file var command = string.Format("\"{0}\" \"{1}\" \"{2}\"", gopherClient, fullQuery, gopherFile); var result = proc.ExecuteCommand(command, true, true); var exitCode = result.Item1; var stdOut = result.Item2; if (exitCode != 0) { mMainWindow.ToastNotify(result.Item3, ToastMessageStyles.Error); mMainWindow.ToggleContainerControlsForBrowser(true); //reenable browser e.Cancel = true; return; } if (File.Exists(gopherFile)) { string parseFile; if (stdOut.Contains("DIR") || stdOut.Contains("QRY")) { //convert gophermap to text/gemini //ToastNotify("Converting gophermap to " + gmiFile); result = ConverterService.GophertoGmi(gopherFile, gmiFile, fullQuery, GopherParseTypes.Map); parseFile = gmiFile; } else if (stdOut.Contains("TXT")) { result = ConverterService.GophertoGmi(gopherFile, gmiFile, fullQuery, GopherParseTypes.Text); parseFile = gmiFile; } else { //a download //copy to a file having its source extension var pathFragment = (new UriBuilder(fullQuery)).Path; var ext = Path.GetExtension(pathFragment); var binFile = gopherFile + (ext ?? ""); File.Copy(gopherFile, binFile, true); //rename overwriting if (stdOut.Contains("IMG") || stdOut.Contains("GIF")) { //show the image mMainWindow.ShowImage(fullQuery, binFile, e); } else { //show a save as dialog 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 (!File.Exists(gmiFile)) { mMainWindow.ToastNotify("Did not create expected GMI file for " + fullQuery + " in " + gmiFile, ToastMessageStyles.Error); mMainWindow.ToggleContainerControlsForBrowser(true); e.Cancel = true; } else { var settings = new Settings(); var userThemesFolder = finder.LocalOrDevFolder(appDir, @"GmiConverters\themes", @"..\..\GmiConverters\themes"); var userThemeBase = Path.Combine(userThemesFolder, settings.Theme); mMainWindow.ShowUrl(fullQuery, parseFile, htmlFile, userThemeBase, siteIdentity, e); } } }
public void NavigateHttpScheme(string fullQuery, NavigatingCancelEventArgs e, SiteIdentity siteIdentity, string linkId) { var httpUri = e.Uri.OriginalString; var sessionPath = Session.Instance.SessionPath; var appDir = AppDomain.CurrentDomain.BaseDirectory; var hash = HashService.GetMd5Hash(fullQuery); var settings = new UserSettings(); //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"; var reRender = false; //if just re-rendering the same content in a different mode, dont re-fetch var httpResponse = new Response.HttpResponse(fullQuery); Tuple <int, string, string> result; if (IsModeSwitch(linkId) && File.Exists(rawFile)) { //use the existing content reRender = true; result = new Tuple <int, string, string>(0, "", ""); //set default mode to this mode settings.WebRenderMode = linkId; settings.Save(); } else { File.Delete(rawFile); //delete txt file as GemGet seems to sometimes overwrite not create afresh File.Delete(gmiFile); //use local or dev binary for gemget var httpGet = ResourceFinder.LocalOrDevFile(appDir, "HttpGet", "..\\..\\..\\..\\HttpGet", "http-get.exe"); //pass options to gemget for download var command = string.Format( "\"{0}\" --header -o \"{1}\" \"{2}\"", httpGet, rawFile, fullQuery); result = ExecuteProcess.ExecuteCommand(command, true, true); if (result.Item1 != 0) { mMainWindow.ToastNotify("Could not access web resource: " + fullQuery + "\n" + result.Item3, ToastMessageStyles.Warning); mMainWindow.ToggleContainerControlsForBrowser(true); e.Cancel = true; return; } httpResponse.ParseGemGet(result.Item2); //parse stdout httpResponse.ParseGemGet(result.Item3); //parse stderr if (httpResponse.StatusCode == 404) { { mMainWindow.ToastNotify("Resource not found:\n" + fullQuery, ToastMessageStyles.Warning); mMainWindow.ToggleContainerControlsForBrowser(true); e.Cancel = true; return; } //ToastNotify(httpResponse.Status + " " + httpResponse.Meta); } else if (httpResponse.StatusCode != 200) { //some other error mMainWindow.ToastNotify("Could not get resource: " + httpResponse.Status + "\n" + fullQuery, ToastMessageStyles.Warning); mMainWindow.ToggleContainerControlsForBrowser(true); e.Cancel = true; return; } } //delete any existing html file to encourage webbrowser to reload it File.Delete(htmlFile); if (File.Exists(rawFile)) { if (httpResponse.ContentType.Contains("text/html") || reRender) { //use local or dev binary for goose var gooseConvert = ResourceFinder.LocalOrDevFile(appDir, "Goose", "..\\..\\..\\..\\Goose", "goose-cli.exe"); var gooseOut = sessionPath + "\\" + hash + ".goose"; var gooseCommand = ""; var htmlPath = ""; if (settings.WebRenderMode == "web-switch-plain") { //pass options to goose to get plain text gooseCommand = string.Format( "\"{0}\" -t -i \"{1}\" -o \"{2}\"", gooseConvert, rawFile, gooseOut); result = ExecuteProcess.ExecuteCommand(gooseCommand, true, true); //just use plain text File.Copy(gooseOut, gmiFile, true); } else { if (settings.WebRenderMode == "web-switch-simplified") { //pass options to goose to get main content as html gooseCommand = string.Format( "\"{0}\" -i \"{1}\" -o \"{2}\"", gooseConvert, rawFile, gooseOut); //get main html of the page result = ExecuteProcess.ExecuteCommand(gooseCommand, true, true); htmlPath = gooseOut; } else { //no filtering to extract main content htmlPath = rawFile; } //convert to gmi var htmlToGmiResult = ConverterService.HtmlToGmi(htmlPath, gmiFile); if (htmlToGmiResult.Item1 != 0) { mMainWindow.ToastNotify("Could not render HTML as GMI: " + fullQuery, ToastMessageStyles.Error); mMainWindow.ToggleContainerControlsForBrowser(true); e.Cancel = true; return; } } } else if (httpResponse.ContentType.Contains("text/")) { //convert plain text to a http 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 (httpResponse.ContentType.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 (httpResponse.Redirected) { string redirectUri = fullQuery; redirectUri = httpResponse.FinalUrl; //redirected to a full http url httpUri = redirectUri; //regenerate the hashes using the redirected target url hash = HashService.GetMd5Hash(httpUri); 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 { httpUri = fullQuery; } var userThemesFolder = ResourceFinder.LocalOrDevFolder(appDir, @"GmiConverters\themes", @"..\..\..\GmiConverters\themes"); var userThemeBase = Path.Combine(userThemesFolder, settings.Theme); mMainWindow.ShowUrl(httpUri, gmiFile, htmlFile, userThemeBase, siteIdentity, e); } else if (httpResponse.StatusCode == 404) { mMainWindow.ToastNotify("Page not found (status 51)\n\n" + e.Uri.ToString(), ToastMessageStyles.Warning); } else { //some othe error - show to the user for info mMainWindow.ToastNotify(string.Format( "Cannot retrieve the content (exit code {0}): \n\n{1} \n\n{2}", result.Item1, string.Join("\n\n", httpResponse.Info), string.Join("\n\n", httpResponse.Errors) ), ToastMessageStyles.Error); } mMainWindow.ToggleContainerControlsForBrowser(true); //no further navigation right now e.Cancel = true; }