/// <summary> /// This is designed to be easily unit testable by not taking actual HttpContext, but doing everything through this IRequestInfo object /// </summary> /// <param name="info"></param> public void MakeReply(IRequestInfo info) { var r = info.LocalPathWithoutQuery.Replace("/bloom/", ""); r = r.Replace("library/", ""); if (r.Contains("libraryContents")) { GetLibraryBooks(info); } else if (r == "libraryName") { info.WriteCompleteOutput(_collectionSettings.CollectionName + " Library"); } else if (r.Contains("SourceCollectionsList")) { GetStoreBooks(info); } else if (r.StartsWith("thumbnails/")) { r = r.Replace("thumbnails/", ""); r = r.Replace("%5C", "/"); r = r.Replace("%20", " "); if (File.Exists(r)) { info.ReplyWithImage(r); } } else if (r.EndsWith(".png")) { info.ContentType = "image/png"; r = r.Replace("thumbnail", ""); //if (r.Contains("thumb")) { if (File.Exists(r)) { info.ReplyWithImage(r); } else { var imgPath = FileLocator.GetFileDistributedWithApplication("BloomBrowserUI", "book.png"); info.ReplyWithImage(imgPath); //book.GetThumbNailOfBookCoverAsync(book.Type != Book.Book.BookType.Publication,image => RefreshOneThumbnail(book, image)); } } // else // { // var imgPath = FileLocator.GetFileDistributedWithApplication("root", "ui", "book.png"); // info.ReplyWithImage(imgPath); // } } else { string path = FileLocator.GetFileDistributedWithApplication("BloomBrowserUI", r); //request.QueryString.GetValues() info.WriteCompleteOutput(File.ReadAllText(path)); } }
private bool CheckForSampleTextChanges(IRequestInfo info) { lock (SyncObj) { if (_sampleTextsWatcher == null) { if (string.IsNullOrEmpty(CurrentCollectionSettings?.SettingsFilePath)) { // We've had cases (BL-4744) where this is apparently called before CurrentCollectionSettings is // established. I'm not sure how this can happen but if we haven't even established a current collection // yet I think it's pretty safe to say its sample texts haven't changed since we last read them. info.ContentType = "text/plain"; info.WriteCompleteOutput("no"); return(true); } var path = Path.Combine(Path.GetDirectoryName(CurrentCollectionSettings.SettingsFilePath), "Sample Texts"); if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } _sampleTextsWatcher = new FileSystemWatcher { Path = path }; _sampleTextsWatcher.Created += SampleTextsOnChange; _sampleTextsWatcher.Changed += SampleTextsOnChange; _sampleTextsWatcher.Renamed += SampleTextsOnChange; _sampleTextsWatcher.Deleted += SampleTextsOnChange; _sampleTextsWatcher.EnableRaisingEvents = true; } } lock (_sampleTextsWatcher) { var hasChanged = _sampleTextsChanged; // Reset the changed flag. // NOTE: we are only resetting the flag if it was "true" when we checked in case the FileSystemWatcher detects a change // after we check the flag but we reset it to false before we check again. if (hasChanged) { _sampleTextsChanged = false; } info.ContentType = "text/plain"; info.WriteCompleteOutput(hasChanged ? "yes" : "no"); return(true); } }
private bool CheckForSampleTextChanges(IRequestInfo info) { if (_sampleTextsWatcher == null) { var path = Path.Combine(Path.GetDirectoryName(CurrentCollectionSettings.SettingsFilePath), "Sample Texts"); if (!Directory.Exists(path)) Directory.CreateDirectory(path); _sampleTextsWatcher = new FileSystemWatcher {Path = path}; _sampleTextsWatcher.Created += SampleTextsOnChange; _sampleTextsWatcher.Changed += SampleTextsOnChange; _sampleTextsWatcher.Deleted += SampleTextsOnChange; _sampleTextsWatcher.EnableRaisingEvents = true; } var hasChanged = _sampleTextsChanged; // Reset the changed flag. // NOTE: we are only resetting the flag if it was "true" when we checked in case the FileSystemWatcher detects a change // after we check the flag but we reset it to false before we check again. if (hasChanged) _sampleTextsChanged = false; info.ContentType = "text/plain"; info.WriteCompleteOutput(hasChanged ? "yes" : "no"); return true; }
/// <summary> /// This is designed to be easily unit testable by not taking actual HttpContext, but doing everything through this IRequestInfo object /// </summary> /// <param name="info"></param> public void MakeReply(IRequestInfo info) { if (info.LocalPathWithoutQuery.EndsWith("testconnection")) { info.WriteCompleteOutput("OK"); return; } var r = info.LocalPathWithoutQuery.Replace("/bloom/", ""); r = r.Replace("%3A", ":"); r = r.Replace("%20", " "); r = r.Replace("%27", "'"); if (r.EndsWith(".png") || r.EndsWith(".jpg")) { info.ContentType = r.EndsWith(".png") ? "image/png" : "image/jpeg"; r = r.Replace("thumbnail", ""); //if (r.Contains("thumb")) { if (File.Exists(r)) { info.ReplyWithImage(_cache.GetPathToResizedImage(r)); } else { Logger.WriteEvent("**ImageServer: File Missing: " + r); info.WriteError(404); } } } }
protected virtual bool ProcessRequest(IRequestInfo info) { if (info.LocalPathWithoutQuery.EndsWith("testconnection")) { info.WriteCompleteOutput("OK"); return(true); } return(false); }
private void GetStoreBooks(IRequestInfo info) { //enhance: it will eventually work better to do sorting client-side, according to user's current prefs var reply = new StringBuilder(); var list = new List <BookCollection>(); list.AddRange(_sourceCollectionsesList.GetSourceCollections()); list.Sort(CompareBookCollections); foreach (BookCollection collection in list) { reply.AppendFormat("<li class='collectionGroup'><h2>{0}</h2><ul class='collection'>", collection.Name); reply.Append(GetBookListItems(collection.GetBookInfos())); reply.AppendFormat(@"</ul></li>"); } info.WriteCompleteOutput(reply.ToString()); }
public static bool HandleRequest(string localPath, IRequestInfo info, CollectionSettings currentCollectionSettings) { var lastSep = localPath.IndexOf("/", System.StringComparison.Ordinal); var lastSegment = (lastSep > -1) ? localPath.Substring(lastSep + 1) : localPath; switch (lastSegment) { case "loadStrings": while (_localizing) { Thread.Sleep(0); } try { _localizing = true; var d = new Dictionary <string, string>(); var post = info.GetPostData(); foreach (string key in post.Keys) { var translation = LocalizationManager.GetDynamicString("Bloom", key, post[key]); if (!d.ContainsKey(key)) { d.Add(key, translation); } } info.ContentType = "application/json"; info.WriteCompleteOutput(JsonConvert.SerializeObject(d)); return(true); } finally { _localizing = false; } } return(false); }
/// <summary> /// This method is overridden in classes inheriting from this class to handle specific request types /// </summary> /// <param name="info"></param> /// <returns></returns> protected virtual bool ProcessRequest(IRequestInfo info) { #if MEMORYCHECK // Check memory for the benefit of developers. (Also see all requests as a side benefit.) var debugMsg = String.Format("ServerBase.ProcessRequest(\"{0}\"", info.RawUrl); SIL.Windows.Forms.Reporting.MemoryManagement.CheckMemory(true, debugMsg, false); #endif // process request for directory index var requestedPath = GetLocalPathWithoutQuery(info); if (info.RawUrl.EndsWith("/") && (Directory.Exists(requestedPath))) { info.WriteError(403, "Directory listing denied"); return(true); } if (requestedPath.EndsWith("testconnection")) { info.WriteCompleteOutput("OK"); return(true); } return(false); }
private static bool GetTopicList(IRequestInfo info) { var keyToLocalizedTopicDictionary = new Dictionary <string, string>(); foreach (var topic in BookInfo.TopicsKeys) { var localized = LocalizationManager.GetDynamicString("Bloom", "Topics." + topic, topic, @"shows in the topics chooser in the edit tab"); keyToLocalizedTopicDictionary.Add(topic, localized); } string localizedNoTopic = LocalizationManager.GetDynamicString("Bloom", "Topics.NoTopic", "No Topic", @"shows in the topics chooser in the edit tab"); var arrayOfKeyValuePairs = from key in keyToLocalizedTopicDictionary.Keys orderby keyToLocalizedTopicDictionary[key] select string.Format("\"{0}\": \"{1}\"", key, keyToLocalizedTopicDictionary[key]); var pairs = arrayOfKeyValuePairs.Concat(","); info.ContentType = "application/json"; var data = string.Format("{{\"NoTopic\": \"{0}\", {1} }}", localizedNoTopic, pairs); info.WriteCompleteOutput(data); /* var data = new {NoTopic = localizedNoTopic, pairs = arrayOfKeyValuePairs}; * var serializeObject = JsonConvert.SerializeObject(data, new JsonSerializerSettings * { * TypeNameHandling = TypeNameHandling.None, * TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple, * }); */ //info.WriteCompleteOutput(serializeObject); return(true); }
private bool ProcessContent(IRequestInfo info, string localPath) { if (localPath.EndsWith(".css")) { return(ProcessCssFile(info, localPath)); } switch (localPath) { case "currentPageContent": info.ContentType = "text/html"; info.WriteCompleteOutput(CurrentPageContent ?? ""); return(true); case "toolboxContent": info.ContentType = "text/html"; info.WriteCompleteOutput(ToolboxContent ?? ""); return(true); case "availableFontNames": info.ContentType = "application/json"; var list = new List <string>(Browser.NamesOfFontsThatBrowserCanRender()); list.Sort(); info.WriteCompleteOutput(JsonConvert.SerializeObject(new{ fonts = list })); return(true); case "uiLanguages": // Returns json with property languages, an array of objects (one for each UI language Bloom knows about) // each having label (what to show in a menu) and tag (the language code). // Used in language select control in hint bubbles tab of text box properties dialog // brought up from cog control in origami mode. var langs = new List <object>(); foreach (var lang in L10NSharp.LocalizationManager.GetUILanguages(true)) { langs.Add(new { label = WorkspaceView.MenuItemName(lang), tag = lang.IetfLanguageTag }); } info.ContentType = "application/json"; info.WriteCompleteOutput(JsonConvert.SerializeObject(new { languages = langs })); return(true); case "bubbleLanguages": // Returns a list of lang codes such that if a block has hints in multiple languages, // we prefer the one that comes first in the list. // Used to select the best label to show in a hint bubble when a bloom-translationGroup has multiple // labels with different languages. var bubbleLangs = new List <string>(); bubbleLangs.Add(LocalizationManager.UILanguageId); if (_bookSelection.CurrentSelection.MultilingualContentLanguage2 != null) { bubbleLangs.Add(_bookSelection.CurrentSelection.MultilingualContentLanguage2); } if (_bookSelection.CurrentSelection.MultilingualContentLanguage3 != null) { bubbleLangs.Add(_bookSelection.CurrentSelection.MultilingualContentLanguage3); } bubbleLangs.AddRange(new [] { "en", "fr", "sp", "ko", "zh-Hans" }); // If we don't have a hint in the UI language or any major language, it's still // possible the page was made just for this langauge and has a hint in that language. // Not sure whether this should be before or after the list above. // Definitely wants to be after UILangage, otherwise we get the surprising result // that in a French collection these hints stay French even when all the rest of the // UI changes to English. bubbleLangs.Add(_bookSelection.CurrentSelection.CollectionSettings.Language1Iso639Code); // if it isn't available in any of those we'll arbitrarily take the first one. info.ContentType = "application/json"; info.WriteCompleteOutput(JsonConvert.SerializeObject(new { langs = bubbleLangs })); return(true); case "authorMode": info.ContentType = "text/plain"; info.WriteCompleteOutput(AuthorMode ? "true" : "false"); return(true); case "topics": return(GetTopicList(info)); } return(ProcessAnyFileContent(info, localPath)); }
// Every path should return false or send a response. // Otherwise we can get a timeout error as the browser waits for a response. // // NOTE: this method gets called on different threads! protected override bool ProcessRequest(IRequestInfo info) { var localPath = GetLocalPathWithoutQuery(info); //enhance: something feeds back these branding logos with a weird URL, that shouldn't be. if (localPath.IndexOf("api/branding") > 20) // this 20 is just arbitrary... the point is, if it doesn't start with api/branding, it is bogus { return(false); } if (localPath.ToLower().StartsWith("api/")) { var endpoint = localPath.Substring(3).ToLowerInvariant().Trim(new char[] { '/' }); foreach (var pair in _endpointRegistrations.Where(pair => Regex.Match(endpoint, "^" + //must match the beginning pair.Key.ToLower() ).Success)) { // A single synchronization object won't do, because when processing a request to create a thumbnail, // we have to load the HTML page the thumbnail is based on. If the page content somehow includes // an api request (api/branding/image is one example), that request will deadlock if the // api/pageTemplateThumbnail request already has the main lock. // To the best of my knowledge, there's no shared data between the thumbnailing process and any // other api requests, so it seems safe to have one lock that prevents working on multiple // thumbnails at the same time, and one that prevents working on other api requests at the same time. var syncOn = SyncObj; if (localPath.ToLowerInvariant().StartsWith("api/pagetemplatethumbnail")) { syncOn = ThumbnailSyncObj; } lock (syncOn) { return(ApiRequest.Handle(pair.Value, info, CurrentCollectionSettings, _bookSelection.CurrentSelection)); } } } //OK, no more obvious simple API requests, dive into the rat's nest of other possibilities if (base.ProcessRequest(info)) { return(true); } if (localPath.Contains("CURRENTPAGE")) //useful when debugging. E.g. http://localhost:8091/bloom/CURRENTPAGE.htm will always show the page we're on. { localPath = _keyToCurrentPage; } string content; bool gotSimulatedPage; lock (_urlToSimulatedPageContent) { gotSimulatedPage = _urlToSimulatedPageContent.TryGetValue(localPath, out content); } if (gotSimulatedPage) { info.ContentType = "text/html"; info.WriteCompleteOutput(content ?? ""); return(true); } if (localPath.StartsWith(OriginalImageMarker) && IsImageTypeThatCanBeDegraded(localPath)) { // Path relative to simulated page file, and we want the file contents without modification. // (Note that the simulated page file's own URL starts with this, so it's important to check // for that BEFORE we do this check.) localPath = localPath.Substring(OriginalImageMarker.Length + 1); return(ProcessAnyFileContent(info, localPath)); } if (localPath.StartsWith("error", StringComparison.InvariantCulture)) { ProcessError(info); return(true); } else if (localPath.StartsWith("i18n/", StringComparison.InvariantCulture)) { if (ProcessI18N(localPath, info)) { return(true); } } else if (localPath.StartsWith("directoryWatcher/", StringComparison.InvariantCulture)) { return(ProcessDirectoryWatcher(info)); } else if (localPath.StartsWith("localhost/", StringComparison.InvariantCulture)) { var temp = LocalHostPathToFilePath(localPath); if (RobustFile.Exists(temp)) { localPath = temp; } } // this is used only by the readium viewer else if (localPath.StartsWith("node_modules/jquery/dist/jquery.js")) { localPath = BloomFileLocator.GetBrowserFile("jquery.min.js"); // Avoid having "output/browser/" removed on Linux developer machines. // GetBrowserFile adds output to the path on developer machines, but not user installs. return(ProcessContent(info, localPath)); } //Firefox debugger, looking for a source map, was prefixing in this unexpected //way. localPath = localPath.Replace("output/browser/", ""); return(ProcessContent(info, localPath)); }
private bool ProcessContent(IRequestInfo info, string localPath) { if (localPath.EndsWith(".css")) { return ProcessCssFile(info, localPath); } switch (localPath) { case "currentPageContent": info.ContentType = "text/html"; info.WriteCompleteOutput(CurrentPageContent ?? ""); return true; case "toolboxContent": info.ContentType = "text/html"; info.WriteCompleteOutput(ToolboxContent ?? ""); return true; case "availableFontNames": info.ContentType = "application/json"; var list = new List<string>(Browser.NamesOfFontsThatBrowserCanRender()); list.Sort(); info.WriteCompleteOutput(JsonConvert.SerializeObject(new{fonts = list})); return true; case "authorMode": info.ContentType = "text/plain"; info.WriteCompleteOutput(AuthorMode ? "true" : "false"); return true; case "topics": return GetTopicList(info); } return ProcessAnyFileContent(info, localPath); }
/// <summary> /// This is safe to use with axios.Post. See BL-4901. There, not returning any text at all /// caused some kind of problem in axios.post(), after the screen had been shut down. /// </summary> public void PostSucceeded() { _requestInfo.ResponseContentType = "text/plain"; _requestInfo.WriteCompleteOutput("OK"); }
/// <summary> /// This is designed to be easily unit testable by not taking actual HttpContext, but doing everything through this IRequestInfo object /// </summary> /// <param name="info"></param> public void MakeReply(IRequestInfo info) { var r = info.LocalPathWithoutQuery.Replace("/bloom/", ""); r = r.Replace("library/", ""); if (r.Contains("libraryContents")) { GetLibraryBooks(info); } else if (r == "libraryName") { info.WriteCompleteOutput(_collectionSettings.CollectionName + " Library"); } else if (r.Contains("SourceCollectionsList")) { GetStoreBooks(info); } else if(r.StartsWith("thumbnails/")) { r = r.Replace("thumbnails/", ""); r = r.Replace("%5C", "/"); r = r.Replace("%20", " "); if (File.Exists(r)) { info.ReplyWithImage(r); } } else if (r.EndsWith(".png")) { info.ContentType = "image/png"; r = r.Replace("thumbnail", ""); //if (r.Contains("thumb")) { if (File.Exists(r)) { info.ReplyWithImage(r); } else { var imgPath = FileLocator.GetFileDistributedWithApplication("BloomBrowserUI", "book.png"); info.ReplyWithImage(imgPath); //book.GetThumbNailOfBookCoverAsync(book.Type != Book.Book.BookType.Publication,image => RefreshOneThumbnail(book, image)); } } // else // { // var imgPath = FileLocator.GetFileDistributedWithApplication("root", "ui", "book.png"); // info.ReplyWithImage(imgPath); // } } else { string path = FileLocator.GetFileDistributedWithApplication("BloomBrowserUI", r); //request.QueryString.GetValues() info.WriteCompleteOutput(File.ReadAllText(path)); } }
// Every path should return false or send a response. // Otherwise we can get a timeout error as the browser waits for a response. protected override bool ProcessRequest(IRequestInfo info) { if (base.ProcessRequest(info)) return true; var localPath = GetLocalPathWithoutQuery(info); // routing if (localPath.StartsWith("readers/")) { if (ReadersHandler.HandleRequest(localPath, info, CurrentCollectionSettings)) return true; } else if (localPath.StartsWith("i18n/")) { if (I18NHandler.HandleRequest(localPath, info, CurrentCollectionSettings)) return true; } else if (localPath.StartsWith("directoryWatcher/")) { var dirName = info.GetPostData()["dir"]; if (dirName == "Sample Texts") { if (CheckForSampleTextChanges(info)) return true; } return false; } else if (localPath.StartsWith("leveledRTInfo/")) { var queryPart = string.Empty; if (info.RawUrl.Contains("?")) queryPart = "#" + info.RawUrl.Split('?')[1]; var langCode = LocalizationManager.UILanguageId; var completeEnglishPath = FileLocator.GetFileDistributedWithApplication(localPath); var completeUiLangPath = GetUiLanguageFileVersion(completeEnglishPath, langCode); string url; if (langCode != "en" && File.Exists(completeUiLangPath)) url = completeUiLangPath; else url = completeEnglishPath; var cleanUrl = url.Replace("\\", "/"); // allows jump to file to work string browser = string.Empty; if (Palaso.PlatformUtilities.Platform.IsLinux) { // REVIEW: This opens HTML files in the browser. Do we have any non-html // files that this code needs to open in the browser? Currently they get // opened in whatever application the user has selected for that file type // which might well be an editor. browser = "xdg-open"; } else { // If we don't provide the path of the browser, i.e. Process.Start(url + queryPart), we get file not found exception. // If we prepend "file:///", the anchor part of the link (#xxx) is not sent unless we provide the browser path too. // This is the same behavior when simply typing a url into the Run command on Windows. // If we fail to get the browser path for some reason, we still load the page, just without navigating to the anchor. string defaultBrowserPath; if (TryGetDefaultBrowserPath(out defaultBrowserPath)) { browser = defaultBrowserPath; } } if (!string.IsNullOrEmpty(browser)) { try { Process.Start(browser, "\"file:///" + cleanUrl + queryPart + "\""); return false; } catch (Exception) { Debug.Fail("Jumping to browser with anchor failed."); // Don't crash Bloom because we can't open an external file. } } // If the above failed, either for lack of default browser or exception, try this: Process.Start("\"" + cleanUrl + "\""); return false; } switch (localPath) { case "currentPageContent": info.ContentType = "text/html"; info.WriteCompleteOutput(CurrentPageContent ?? ""); return true; case "accordionContent": info.ContentType = "text/html"; info.WriteCompleteOutput(AccordionContent ?? ""); return true; case "availableFontNames": InstalledFontCollection installedFontCollection = new InstalledFontCollection(); info.WriteCompleteOutput(string.Join(",", installedFontCollection.Families.Select(f => f.Name))); return true; case "help": var post = info.GetPostData(); HelpLauncher.Show(null, post["data"]); return true; case "getNextBookStyle": info.ContentType = "text/html"; info.WriteCompleteOutput(CurrentBook.NextStyleNumber.ToString(CultureInfo.InvariantCulture)); return true; } string path = null; try { path = FileLocator.GetFileDistributedWithApplication("BloomBrowserUI", localPath); } catch (ApplicationException) { // ignore } if (!File.Exists(path)) return false; info.ContentType = GetContentType(Path.GetExtension(localPath)); info.ReplyWithFileContent(path); return true; }
private void GetLibraryBooks(IRequestInfo info) { var books = _booksInProjectLibrary.GetBookInfos(); info.WriteCompleteOutput(GetBookListItems(books)); }
public static bool HandleRequest(string localPath, IRequestInfo info, CollectionSettings currentCollectionSettings) { var lastSep = localPath.IndexOf("/", System.StringComparison.Ordinal); var lastSegment = (lastSep > -1) ? localPath.Substring(lastSep + 1) : localPath; switch (lastSegment) { case "loadStrings": while (_localizing) { Thread.Sleep(0); } try { _localizing = true; var d = new Dictionary <string, string>(); var post = info.GetPostDataWhenFormEncoded(); if (post != null) { foreach (string key in post.Keys) { try { if (d.ContainsKey(key)) { continue; } // Now that end users can create templates, it's annoying to report that their names, // page labels, and page descriptions don't have localizations. if (IsTemplateBookKey(key)) { continue; } var translation = GetTranslationDefaultMayNotBeEnglish(key, post[key]); d.Add(key, translation); } catch (Exception error) { Debug.Fail("Debug Only:" + error.Message + Environment.NewLine + "A bug reported at this location is BL-923"); //Until BL-923 is fixed (hard... it's a race condition, it's better to swallow this for users } } } info.ContentType = "application/json"; info.WriteCompleteOutput(JsonConvert.SerializeObject(d)); return(true); } finally { _localizing = false; } break; case "translate": var parameters = info.GetQueryParameters(); string id = parameters["key"]; string englishText = parameters["englishText"]; string langId = parameters["langId"]; langId = langId.Replace("V", currentCollectionSettings.Language1Iso639Code); langId = langId.Replace("N1", currentCollectionSettings.Language2Iso639Code); langId = langId.Replace("N2", currentCollectionSettings.Language3Iso639Code); langId = langId.Replace("UI", LocalizationManager.UILanguageId); if (LocalizationManager.GetIsStringAvailableForLangId(id, langId)) { info.ContentType = "text/plain"; info.WriteCompleteOutput(LocalizationManager.GetDynamicStringOrEnglish("Bloom", id, englishText, null, langId)); return(true); } else { // Don't report missing strings if they are numbers // Enhance: We might get the Javascript to do locale specific numbers someday // The C# side doesn't currently have the smarts to do DigitSubstitution // See Remark at https://msdn.microsoft.com/en-us/library/system.globalization.numberformatinfo.digitsubstitution(v=vs.110).aspx if (IsInteger(id)) { englishText = id; } else { // Now that end users can create templates, it's annoying to report that their names, // page labels, and page descriptions don't have localizations. if (IsTemplateBookKey(id)) { englishText = englishText.Trim(); } else { // it's ok if we don't have a translation, but if the string isn't even in the list of things that need translating, // then we want to remind the developer to add it to the english xlf file. if (!LocalizationManager.GetIsStringAvailableForLangId(id, "en")) { ReportL10NMissingString(id, englishText, UrlPathString.CreateFromUrlEncodedString(parameters["comment"] ?? "").NotEncoded); } else { //ok, so we don't have it translated yet. Make sure it's at least listed in the things that can be translated. // And return the English string, which is what we would do the next time anyway. (BL-3374) LocalizationManager.GetDynamicString("Bloom", id, englishText); } } } info.ContentType = "text/plain"; info.WriteCompleteOutput(englishText); return(true); } break; } return(false); }
public static bool HandleRequest(string localPath, IRequestInfo info, CollectionSettings currentCollectionSettings) { var lastSep = localPath.IndexOf("/", System.StringComparison.Ordinal); var lastSegment = (lastSep > -1) ? localPath.Substring(lastSep + 1) : localPath; switch (lastSegment) { case "loadStrings": while (_localizing) { Thread.Sleep(0); } try { _localizing = true; var d = new Dictionary<string, string>(); var post = info.GetPostDataWhenFormEncoded(); if (post != null) { foreach (string key in post.Keys) { try { if (!d.ContainsKey(key)) { var translation = GetTranslationDefaultMayNotBeEnglish(key, post[key]); d.Add(key, translation); } } catch (Exception error) { Debug.Fail("Debug Only:" +error.Message+Environment.NewLine+"A bug reported at this location is BL-923"); //Until BL-923 is fixed (hard... it's a race condition, it's better to swallow this for users } } } info.ContentType = "application/json"; info.WriteCompleteOutput(JsonConvert.SerializeObject(d)); return true; } finally { _localizing = false; } break; case "translate": var parameters = info.GetQueryParameters(); string id = parameters["key"]; string englishText = parameters["englishText"]; string langId = parameters["langId"]; langId = langId.Replace("V", currentCollectionSettings.Language1Iso639Code); langId = langId.Replace("N1", currentCollectionSettings.Language2Iso639Code); langId = langId.Replace("N2", currentCollectionSettings.Language3Iso639Code); langId = langId.Replace("UI", LocalizationManager.UILanguageId); if (LocalizationManager.GetIsStringAvailableForLangId(id, langId)) { info.ContentType = "text/plain"; info.WriteCompleteOutput(LocalizationManager.GetDynamicStringOrEnglish("Bloom", id, englishText, null, langId)); return true; } else { // Don't report missing strings if they are numbers // Enhance: We might get the Javascript to do locale specific numbers someday // The C# side doesn't currently have the smarts to do DigitSubstitution // See Remark at https://msdn.microsoft.com/en-us/library/system.globalization.numberformatinfo.digitsubstitution(v=vs.110).aspx if (IsInteger(id)) { englishText = id; } else { // it's ok if we don't have a translation, but if the string isn't even in the list of things that need translating, // then we want to remind the developer to add it to the english tmx file. if (!LocalizationManager.GetIsStringAvailableForLangId(id, "en")) { ReportL10NMissingString(id, englishText); } else { //ok, so we don't have it translated yet. Make sure it's at least listed in the things that can be translated. // And return the English string, which is what we would do the next time anyway. (BL-3374) LocalizationManager.GetDynamicString("Bloom", id, englishText); } } info.ContentType = "text/plain"; info.WriteCompleteOutput(englishText); return true; } break; } return false; }
/// <summary> /// This is designed to be easily unit testable by not taking actual HttpContext, but doing everything through this IRequestInfo object /// </summary> /// <param name="info"></param> public void MakeReply(IRequestInfo info) { if(info.LocalPathWithoutQuery.EndsWith("testconnection")) { info.WriteCompleteOutput("OK"); return; } var r = info.LocalPathWithoutQuery.Replace("/bloom/", ""); r = r.Replace("%3A", ":"); r = r.Replace("%20", " "); r = r.Replace("%27", "'"); if (r.EndsWith(".png") || r.EndsWith(".jpg")) { info.ContentType = r.EndsWith(".png") ? "image/png" : "image/jpeg"; r = r.Replace("thumbnail", ""); //if (r.Contains("thumb")) { if (File.Exists(r)) { info.ReplyWithImage(_cache.GetPathToResizedImage(r)); } else { Logger.WriteEvent("**ImageServer: File Missing: "+r); info.WriteError(404); } } } }
protected override bool ProcessRequest(IRequestInfo info) { if (base.ProcessRequest(info)) return true; var r = CorrectedLocalPath(info); const string slashBloomSlash = "/bloom/"; if (r.StartsWith(slashBloomSlash)) r = r.Substring(slashBloomSlash.Length); r = r.Replace("library/", ""); if (r.Contains("libraryContents")) { GetLibraryBooks(info); } else if (r == "libraryName") { info.WriteCompleteOutput(_collectionSettings.CollectionName + " Library"); } else if (r.Contains("SourceCollectionsList")) { GetStoreBooks(info); } else if(r.StartsWith("thumbnails/")) { r = r.Replace("thumbnails/", ""); r = r.Replace("%5C", "/"); r = r.Replace("%20", " "); if (File.Exists(r)) { info.ReplyWithImage(r); } } else if (r.EndsWith(".png") && r.Contains("thumbnail")) { r = r.Replace("thumbnail", ""); //if (r.Contains("thumb")) { if (File.Exists(r)) { info.ReplyWithImage(r); } else { var imgPath = BloomFileLocator.GetBrowserFile("book.png"); info.ReplyWithImage(imgPath); //book.GetThumbNailOfBookCoverAsync(book.Type != Book.Book.BookType.Publication,image => RefreshOneThumbnail(book, image)); } } // else // { // var imgPath = FileLocator.GetFileDistributedWithApplication("root", "ui", "book.png"); // info.ReplyWithImage(imgPath); // } } else { info.ContentType = GetContentType(Path.GetExtension(r)); string path = BloomFileLocator.GetBrowserFile(r); //request.QueryString.GetValues() info.ReplyWithFileContent(path); } return true; }
private static bool GetTopicList(IRequestInfo info) { var keyToLocalizedTopicDictionary = new Dictionary<string, string>(); foreach (var topic in BookInfo.TopicsKeys) { var localized = LocalizationManager.GetDynamicString("Bloom", "Topics." + topic, topic, @"shows in the topics chooser in the edit tab"); keyToLocalizedTopicDictionary.Add(topic, localized); } string localizedNoTopic = LocalizationManager.GetDynamicString("Bloom", "Topics.NoTopic", "No Topic", @"shows in the topics chooser in the edit tab"); var arrayOfKeyValuePairs = from key in keyToLocalizedTopicDictionary.Keys orderby keyToLocalizedTopicDictionary[key] select string.Format("\"{0}\": \"{1}\"",key,keyToLocalizedTopicDictionary[key]); var pairs = arrayOfKeyValuePairs.Concat(","); info.ContentType = "application/json"; var data = string.Format("{{\"NoTopic\": \"{0}\", {1} }}", localizedNoTopic, pairs); info.WriteCompleteOutput(data); /* var data = new {NoTopic = localizedNoTopic, pairs = arrayOfKeyValuePairs}; * var serializeObject = JsonConvert.SerializeObject(data, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.None, TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple, }); */ //info.WriteCompleteOutput(serializeObject); return true; }
private void GetStoreBooks(IRequestInfo info) { //enhance: it will eventually work better to do sorting client-side, according to user's current prefs var reply = new StringBuilder(); var list = new List<BookCollection>(); list.AddRange(_sourceCollectionsesList.GetSourceCollections()); list.Sort(CompareBookCollections); foreach (BookCollection collection in list) { reply.AppendFormat("<li class='collectionGroup'><h2>{0}</h2><ul class='collection'>", collection.Name); reply.Append(GetBookListItems(collection.GetBookInfos())); reply.AppendFormat(@"</ul></li>"); } info.WriteCompleteOutput(reply.ToString()); }
private bool CheckForSampleTextChanges(IRequestInfo info) { lock (SyncObj) { if (_sampleTextsWatcher == null) { var path = Path.Combine(Path.GetDirectoryName(CurrentCollectionSettings.SettingsFilePath), "Sample Texts"); if (!Directory.Exists(path)) Directory.CreateDirectory(path); _sampleTextsWatcher = new FileSystemWatcher { Path = path }; _sampleTextsWatcher.Created += SampleTextsOnChange; _sampleTextsWatcher.Changed += SampleTextsOnChange; _sampleTextsWatcher.Renamed += SampleTextsOnChange; _sampleTextsWatcher.Deleted += SampleTextsOnChange; _sampleTextsWatcher.EnableRaisingEvents = true; } } lock (_sampleTextsWatcher) { var hasChanged = _sampleTextsChanged; // Reset the changed flag. // NOTE: we are only resetting the flag if it was "true" when we checked in case the FileSystemWatcher detects a change // after we check the flag but we reset it to false before we check again. if (hasChanged) _sampleTextsChanged = false; info.ContentType = "text/plain"; info.WriteCompleteOutput(hasChanged ? "yes" : "no"); return true; } }
public static bool HandleRequest(string localPath, IRequestInfo info, CollectionSettings currentCollectionSettings) { var lastSep = localPath.IndexOf("/", StringComparison.Ordinal); var lastSegment = (lastSep > -1) ? localPath.Substring(lastSep + 1) : localPath; switch (lastSegment) { case "loadReaderToolSettings": info.ContentType = "application/json"; info.WriteCompleteOutput(GetDefaultReaderSettings(currentCollectionSettings)); return(true); case "saveReaderToolSettings": var path = currentCollectionSettings.DecodableLevelPathName; var content = info.GetPostData()["data"]; File.WriteAllText(path, content, Encoding.UTF8); info.ContentType = "text/plain"; info.WriteCompleteOutput("OK"); return(true); case "getDefaultFont": var bookFontName = currentCollectionSettings.DefaultLanguage1FontName; if (string.IsNullOrEmpty(bookFontName)) { bookFontName = "sans-serif"; } info.ContentType = "text/plain"; info.WriteCompleteOutput(bookFontName); return(true); case "getSampleTextsList": info.ContentType = "text/plain"; info.WriteCompleteOutput(GetSampleTextsList(currentCollectionSettings.SettingsFilePath)); return(true); case "getSampleFileContents": var fileName = info.GetQueryString()["data"]; info.ContentType = "text/plain"; info.WriteCompleteOutput(GetSampleFileContents(fileName, currentCollectionSettings.SettingsFilePath)); return(true); case "getTextOfPages": info.ContentType = "text/plain"; info.WriteCompleteOutput(GetTextOfPages()); return(true); case "saveReaderToolsWords": info.ContentType = "text/plain"; info.WriteCompleteOutput(SaveReaderToolsWordsFile(info.GetPostData()["data"])); return(true); case "makeLetterAndWordList": MakeLetterAndWordList(info.GetPostData()["settings"], info.GetPostData()["allWords"]); info.ContentType = "text/plain"; info.WriteCompleteOutput("OK"); return(true); case "openTextsFolder": OpenTextsFolder(); info.ContentType = "text/plain"; info.WriteCompleteOutput("OK"); return(true); } return(false); }
/// <summary> /// This method is overridden in classes inheriting from this class to handle specific request types /// </summary> /// <param name="info"></param> /// <returns></returns> protected virtual bool ProcessRequest(IRequestInfo info) { #if MEMORYCHECK // Check memory for the benefit of developers. (Also see all requests as a side benefit.) var debugMsg = String.Format("ServerBase.ProcessRequest(\"{0}\"", info.RawUrl); SIL.Windows.Forms.Reporting.MemoryManagement.CheckMemory(true, debugMsg, false); #endif // process request for directory index var requestedPath = GetLocalPathWithoutQuery(info); if (info.RawUrl.EndsWith("/") && (Directory.Exists(requestedPath))) { info.WriteError(403, "Directory listing denied"); return true; } if (requestedPath.EndsWith("testconnection")) { info.WriteCompleteOutput("OK"); return true; } return false; }
protected override bool ProcessRequest(IRequestInfo info) { if (base.ProcessRequest(info)) { return(true); } var r = CorrectedLocalPath(info); const string slashBloomSlash = "/bloom/"; if (r.StartsWith(slashBloomSlash)) { r = r.Substring(slashBloomSlash.Length); } r = r.Replace("library/", ""); if (r.Contains("libraryContents")) { GetLibraryBooks(info); } else if (r == "libraryName") { info.WriteCompleteOutput(_collectionSettings.CollectionName + " Library"); } else if (r.Contains("SourceCollectionsList")) { GetStoreBooks(info); } else if (r.StartsWith("thumbnails/")) { r = r.Replace("thumbnails/", ""); r = r.Replace("%5C", "/"); r = r.Replace("%20", " "); if (File.Exists(r)) { info.ReplyWithImage(r); } } else if (r.EndsWith(".png") && r.Contains("thumbnail")) { r = r.Replace("thumbnail", ""); //if (r.Contains("thumb")) { if (File.Exists(r)) { info.ReplyWithImage(r); } else { var imgPath = BloomFileLocator.GetBrowserFile("book.png"); info.ReplyWithImage(imgPath); //book.GetThumbNailOfBookCoverAsync(book.Type != Book.Book.BookType.Publication,image => RefreshOneThumbnail(book, image)); } } // else // { // var imgPath = FileLocator.GetFileDistributedWithApplication("root", "ui", "book.png"); // info.ReplyWithImage(imgPath); // } } else { info.ContentType = GetContentType(Path.GetExtension(r)); string path = BloomFileLocator.GetBrowserFile(r); //request.QueryString.GetValues() info.ReplyWithFileContent(path); } return(true); }
// Every path should return false or send a response. // Otherwise we can get a timeout error as the browser waits for a response. // // NOTE: this method gets called on different threads! protected override bool ProcessRequest(IRequestInfo info) { var localPath = GetLocalPathWithoutQuery(info); //enhance: something feeds back these branding logos with a weird URL, that shouldn't be. if(localPath.IndexOf("api/branding") > 20) // this 20 is just arbitrary... the point is, if it doesn't start with api/branding, it is bogus { return false; } if (localPath.ToLower().StartsWith("api/")) { var endpoint = localPath.Substring(3).ToLowerInvariant().Trim(new char[] {'/'}); foreach (var pair in _endpointRegistrations.Where(pair => Regex.Match(endpoint, "^" + //must match the beginning pair.Key.ToLower() ).Success)) { lock(SyncObj) { return ApiRequest.Handle(pair.Value, info, CurrentCollectionSettings, _bookSelection.CurrentSelection); } } } //OK, no more obvious simple API requests, dive into the rat's nest of other possibilities if (base.ProcessRequest(info)) return true; if(localPath.Contains("CURRENTPAGE")) //useful when debugging. E.g. http://localhost:8091/bloom/CURRENTPAGE.htm will always show the page we're on. { localPath = _keyToCurrentPage; } string content; bool gotSimulatedPage; lock (_urlToSimulatedPageContent) { gotSimulatedPage = _urlToSimulatedPageContent.TryGetValue(localPath, out content); } if (gotSimulatedPage) { info.ContentType = "text/html"; info.WriteCompleteOutput(content ?? ""); return true; } if (localPath.StartsWith(OriginalImageMarker) && IsImageTypeThatCanBeDegraded(localPath)) { // Path relative to simulated page file, and we want the file contents without modification. // (Note that the simulated page file's own URL starts with this, so it's important to check // for that BEFORE we do this check.) localPath = localPath.Substring(OriginalImageMarker.Length + 1); return ProcessAnyFileContent(info, localPath); } if (localPath.StartsWith("error", StringComparison.InvariantCulture)) { ProcessError(info); return true; } else if (localPath.StartsWith("i18n/", StringComparison.InvariantCulture)) { if (ProcessI18N(localPath, info)) return true; } else if (localPath.StartsWith("directoryWatcher/", StringComparison.InvariantCulture)) return ProcessDirectoryWatcher(info); else if (localPath.StartsWith("localhost/", StringComparison.InvariantCulture)) { var temp = LocalHostPathToFilePath(localPath); if (RobustFile.Exists(temp)) localPath = temp; } // this is used only by the readium viewer else if (localPath.StartsWith("node_modules/jquery/dist/jquery.js")) { localPath = BloomFileLocator.GetBrowserFile("jquery.min.js"); // Avoid having "output/browser/" removed on Linux developer machines. // GetBrowserFile adds output to the path on developer machines, but not user installs. return ProcessContent(info, localPath); } //Firefox debugger, looking for a source map, was prefixing in this unexpected //way. localPath = localPath.Replace("output/browser/", ""); return ProcessContent(info, localPath); }