/// <summary> /// Determines the download URL for a FileHippo application with the given ID. /// </summary> /// <param name="avoidBeta">Whether or not to avoid beta versions. If only beta versions are available, they will be downloaded anyways.</param> public static string FileHippoDownloadUrl(string fileId, bool avoidBeta) { fileId = fileId.ToLower(); string url = GetFileHippoBaseDownloadUrl(fileId); // On the overview page, find the link to the // download page of the latest version string overviewPage; using (WebClient client = new WebClient()) { overviewPage = client.DownloadString(url); // If FileHippo redirects to a new name, extract it the actual ID. if (client.ResponseUri != null) { string newId = GetFileHippoIdFromUrl(client.ResponseUri.ToString()); if (!string.IsNullOrEmpty(newId) && GetFileHippoBaseDownloadUrl(newId) != url && newId != client.ResponseUri.ToString()) { LogDialog.Log(string.Format("FileHippo ID '{0}' has been renamed to '{1}'.", fileId, newId)); fileId = newId; } } } if (avoidBeta && FileHippoIsBeta(overviewPage)) { overviewPage = GetNonBetaPageContent(overviewPage, fileId, false); } string findUrl = string.Format("/download_{0}/download/", GetFileHippoCleanFileId(fileId)); int pos = overviewPage.IndexOf(findUrl); if (pos < 0) { throw new WebException("FileHippo ID '" + fileId + "' does not exist.", WebExceptionStatus.ReceiveFailure); } pos += findUrl.Length; string downloadUrl = GetFileHippoBaseDownloadUrl(fileId) + string.Format("download/{0}/", overviewPage.Substring(pos, 32)) + "?direct"; // Now on the download page, find the link which redirects to the latest file string downloadPage; using (WebClient client = new WebClient()) { downloadPage = client.DownloadString(downloadUrl); } findUrl = "/download/file/"; pos = downloadPage.IndexOf(findUrl); if (pos < 0) { return(string.Empty); } pos += findUrl.Length; string redirectUrl = string.Format("http://www.filehippo.com/download/file/{0}", downloadPage.Substring(pos, 64)); return(redirectUrl); }
/// <summary> /// Checks for which of the given applications updates /// are available. Fires an event when finished. /// </summary> private void CheckForOnlineUpdates(object argument) { ApplicationJob[] jobs = argument as ApplicationJob[]; // Build an array containing all GUIDs and dates List <RpcAppGuidAndDate> sendInfo = new List <RpcAppGuidAndDate>(); foreach (ApplicationJob job in jobs.Where(job => !job.CanBeShared)) { sendInfo.Add(new RpcAppGuidAndDate(job.Guid, job.DownloadDate)); } if (sendInfo.Count == 0) { // Nothing to do return; } try { IGeGeekRpc proxy = XmlRpcProxyGen.Create <IGeGeekRpc>(); string[] updatedApps = proxy.GetUpdatedApplications(sendInfo.ToArray()); OnUpdatesFound(updatedApps); } catch (Exception ex) { // If updating fails, it does not hurt and should not annoy anyone. // Just write a log entry, just in case LogDialog.Log("Failed checking for online database updates", ex); } }
/// <summary> /// Determines the download URL for a FileHippo application with the given ID. /// </summary> /// <param name="avoidBeta">Whether or not to avoid beta versions. If only beta versions are available, they will be downloaded anyways.</param> public static string FileHippoDownloadUrl(string fileId, bool avoidBeta) { fileId = fileId.ToLower(); string url = GetFileHippoBaseDownloadUrl(fileId); // On the overview page, find the link to the // download page of the latest version string overviewPage; using (WebClient client = new WebClient()) { overviewPage = client.DownloadString(url); // If FileHippo redirects to a new name, extract it the actual ID. if (client.ResponseUri != null) { string newId = GetFileHippoIdFromUrl(client.ResponseUri.ToString()); if (!string.IsNullOrEmpty(newId) && GetFileHippoBaseDownloadUrl(newId) != url && newId != client.ResponseUri.ToString()) { LogDialog.Log(string.Format("FileHippo ID '{0}' has been renamed to '{1}'.", fileId, newId)); fileId = newId; } } } if (avoidBeta && FileHippoIsBeta(overviewPage)) { overviewPage = GetNonBetaPageContent(overviewPage, fileId, false); } string findUrl = string.Format("/download_{0}/post_download/", GetFileHippoCleanFileId(fileId)); int pos = overviewPage.IndexOf(findUrl); if (pos < 0) { throw new WebException("FileHippo ID '" + fileId + "' does not exist.", WebExceptionStatus.ReceiveFailure); } using (WebClient client = new WebClient()) { overviewPage = client.DownloadString($"https://filehippo.com/download_{GetFileHippoCleanFileId(fileId)}/post_download/"); } // Now on the download page, find the link which redirects to the latest file // setTimeout(function() { downloadIframe.src = 'https://filehippo.com/launch_download/?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... string searchText = "downloadIframe.src"; pos = overviewPage.IndexOf(searchText); int urlEndPos = overviewPage.IndexOf("'", pos + 30); if (pos < 0) { throw new WebException("Download URL for FileHippo ID '" + fileId + "' cannot be found.", WebExceptionStatus.ReceiveFailure); } string downloadUrl = overviewPage.Substring(pos + searchText.Length, urlEndPos - pos - searchText.Length).Trim().Trim('\'', '=', ' '); return(downloadUrl); }
internal void Execute(ApplicationJob application, ApplicationJobError errorInfo = null) { using (PowerShell powerShell = PowerShell.Create()) { powerShell.AddScript(this.scriptText); // Make application object available to the script. if (application != null) { powerShell.Runspace.SessionStateProxy.SetVariable("app", application); } if (errorInfo != null) { powerShell.Runspace.SessionStateProxy.SetVariable("apperror", errorInfo); } powerShell.Runspace.SessionStateProxy.SetVariable("globalvars", UrlVariable.GlobalVariables); // Output all information we can get. powerShell.Streams.Debug.DataAdded += this.DebugDataAdded; powerShell.Streams.Warning.DataAdded += this.WarningDataAdded; try { powerShell.Streams.Information.DataAdded += this.InfoDataAdded; } catch (MissingMethodException) { // Only supported in PS 5.0 and higher } Collection <PSObject> psOutput = powerShell.Invoke(); foreach (PSObject outputItem in psOutput) { // if null object was dumped to the pipeline during the script then a null // object may be present here. check for null to prevent potential NRE. if (outputItem != null) { this.LastOutput = outputItem.ToString(); LogDialog.Log("PowerShell: " + outputItem); } } if (powerShell.HadErrors) { StringBuilder sb = new StringBuilder(); foreach (ErrorRecord error in powerShell.Streams.Error) { sb.AppendLine(error.Exception.Message); } throw new ApplicationException(sb.ToString()); } } }
public static WebRequest CreateRequest(Uri location, ApplicationJob job, CookieContainer cookies) { WebRequest req = WebRequest.CreateDefault(WebClient.FixNoProtocolUri(location)); req.Timeout = Convert.ToInt32(Settings.GetValue("ConnectionTimeout", 10)) * 1000; // 10 seconds by default HttpWebRequest httpRequest = req as HttpWebRequest; if (httpRequest != null) { httpRequest.Accept = "*/*"; // Store cookies for future requests. Some sites // check for previously stored cookies before allowing to download. if (httpRequest.CookieContainer == null) { httpRequest.CookieContainer = cookies; } else { httpRequest.CookieContainer.Add(cookies.GetCookies(httpRequest.RequestUri)); } // If we have an HTTP request, some sites may require a correct referer // for the download. // If there are variables defined (from which most likely the download link // or version is being extracted), we'll just use the first variable's URL as referer. // The user still has the option to set a custom referer. // Note: Some websites don't "like" certain referers if (!NoAutoReferer.Contains(GetBaseHost(req.RequestUri))) { foreach (UrlVariable urlVar in job.Variables.Values) { httpRequest.Referer = urlVar.Url; break; } } if (!string.IsNullOrEmpty(job.HttpReferer)) { httpRequest.Referer = job.Variables.ReplaceAllInString(job.HttpReferer); } LogDialog.Log(job, "Using referer: " + (string.IsNullOrEmpty(httpRequest.Referer) ? "(none)" : httpRequest.Referer)); httpRequest.UserAgent = string.IsNullOrEmpty(job.UserAgent) ? WebClient.DefaultUserAgent : job.Variables.ReplaceAllInString(job.UserAgent); // PAD files may be compressed httpRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; } return(req); }
public override RemoteFileInfo GetFileInfo(ResourceLocation rl, out Stream stream) { WebRequest request = this.GetRequest(rl); RemoteFileInfo result = new RemoteFileInfo(); WebResponse response = null; HttpWebRequest httpRequest = request as HttpWebRequest; if (httpRequest != null) { response = request.GetResponse(); result.MimeType = response.ContentType; result.LastModified = ((HttpWebResponse)response).LastModified; result.FileSize = response.ContentLength; result.AcceptRanges = string.Compare(response.Headers["Accept-Ranges"], "bytes", StringComparison.OrdinalIgnoreCase) == 0; } FtpWebRequest ftpRequest = request as FtpWebRequest; if (ftpRequest != null) { result.AcceptRanges = true; ftpRequest.Method = WebRequestMethods.Ftp.GetFileSize; using (FtpWebResponse sizeResponse = (FtpWebResponse)ftpRequest.GetResponse()) { result.FileSize = sizeResponse.ContentLength; } ftpRequest = (FtpWebRequest)this.GetRequest(rl); ftpRequest.Method = WebRequestMethods.Ftp.GetDateTimestamp; using (FtpWebResponse dateResponse = (FtpWebResponse)ftpRequest.GetResponse()) { result.LastModified = dateResponse.LastModified; } // For FTP: Don't create download stream yet. } if (!result.AcceptRanges || response == null) { LogDialog.Log(this.job, $"Server for {rl.Url} does not support segmented transfer"); } stream = response?.GetResponseStream(); return(result); }
/// <summary> /// Handles download failure (set failed state, add to errors) and executes the "update failed" /// command for additional control. /// </summary> private void HandleUpdateFailed(ApplicationJob job, ApplicationJobError error) { // Execute: Default update failed command string updateFailedCommand = Settings.GetValue("UpdateFailedCommand", "") as string; ScriptType defaultPreCommandType = Command.ConvertToScriptType(Settings.GetValue("UpdateFailedCommandType", ScriptType.Batch.ToString()) as string); m_Status[job] = Status.Failure; if (!string.IsNullOrEmpty(updateFailedCommand)) { int exitCode = new Command(updateFailedCommand, defaultPreCommandType).Execute(job, null, error); // Do not show failure in error window. if (exitCode == 1) { LogDialog.Log(job, "Update failed command returned '1', ignoring error"); return; } } m_Errors.Add(error); }
/// <summary> /// Applies a function (if given) to content and returns the /// modified content. /// </summary> /// <param name="function">A function specification, for example "replace:a:b"</param> /// <param name="content">The usual variable content</param> private static string ReplaceFunction(string function, string content) { function = function.TrimStart(':'); if (string.IsNullOrEmpty(function)) { return(content); } string[] parts = SplitEscaped(function, ':'); if (parts.Length == 0) { return(content); } switch (parts[0]) { case "empty": // Useful, if you want to load, but not use a variable return(string.Empty); case "regexreplace": try { if (parts.Length > 2) { Regex regex = new Regex(parts[1], RegexOptions.Singleline | RegexOptions.IgnoreCase); return(regex.Replace(content, delegate(Match match) { string result = parts[2]; for (int i = 0; i < match.Groups.Count; i++) { result = result.Replace("$" + i, match.Groups[i].Value); } return result; })); } } catch (ArgumentException ex) { LogDialog.Log("Could not process the function 'regexreplace'.", ex); } return(string.Empty); case "multireplace": case "multireplacei": if (parts.Length > 3) { if (string.IsNullOrEmpty(parts[1])) { break; } // Exmaple: multireplace:,:a,b,c:1,2,3 char delimiter = parts[1][0]; string[] search = parts[2].Split(delimiter); string[] replace = parts[3].Split(delimiter); for (int i = 0; i < search.Length; i++) { string replaceValue = (replace.Length > i) ? replace[i] : string.Empty; if (parts[0] == "multireplacei") { content = ReplaceEx(content, search[i], replaceValue); } else { content = content.Replace(search[i], replaceValue); } } return(content); } break; case "regex": try { Regex regex = new Regex(parts[1], RegexOptions.Singleline); Match match = regex.Match(content); if (parts.Length > 2) { int groupNum = Conversion.ToInt(parts[2]); if (groupNum >= 0 && groupNum < match.Groups.Count) { return(match.Groups[groupNum].Value); } } return((match.Success) ? match.Value : string.Empty); } catch (ArgumentException ex) { LogDialog.Log("Could not process the function 'regex'.", ex); return(string.Empty); } case "regexrandom": try { Regex regex = new Regex(parts[1], RegexOptions.Singleline); MatchCollection matches = regex.Matches(content); if (matches.Count > 0) { int randomPos = random.Next(0, matches.Count - 1); int groupNum = (parts.Length > 2) ? Conversion.ToInt(parts[2]) : -1; if (groupNum >= 0 && groupNum < matches[randomPos].Groups.Count) { return(matches[randomPos].Groups[groupNum].Value); } else { return(matches[randomPos].Value); } } return(string.Empty); } catch (ArgumentException ex) { LogDialog.Log("Could not process the function 'regex'.", ex); return(string.Empty); } case "ext": return(Path.GetExtension(content).TrimStart('.')); case "basefile": return(Path.GetFileNameWithoutExtension(content)); case "directory": try { if (content.StartsWith("\"") && content.EndsWith("\"")) { return("\"" + Path.GetDirectoryName(content.Trim('"')) + "\""); } else { return(Path.GetDirectoryName(content.Trim('"'))); } } catch { return(content); } case "filename": try { return(Path.GetFileName(content)); } catch { return(content); } case "filenameWithoutExtension": try { return(Path.GetFileNameWithoutExtension(content)); } catch { return(content); } case "toupper": return(content.ToUpper()); case "tolower": return(content.ToLower()); case "split": if (parts.Length >= 3) { string[] contentParts = content.Split(parts[1][0]); int partNum; if (Int32.TryParse(parts[2], out partNum)) { if (partNum < 0) { // Negative number: Count from the end partNum = contentParts.Length + partNum; } if (partNum >= 0 && partNum < contentParts.Length) { return(contentParts[partNum]); } } } break; case "trim": if (parts.Length >= 2) { return(content.Trim(parts[1].ToCharArray())); } else { return(content.Trim()); } case "trimend": if (parts.Length >= 2) { return(content.TrimEnd(parts[1].ToCharArray())); } else { return(content.TrimEnd()); } case "trimstart": if (parts.Length >= 2) { return(content.TrimStart(parts[1].ToCharArray())); } else { return(content.TrimStart()); } case "replace": if (parts.Length >= 3) { return(content.Replace(parts[1], parts[2])); } break; case "formatfilesize": return(CDBurnerXP.IO.FormatFileSize.Format(Conversion.ToInt(content))); case "startuppath": return(System.Windows.Forms.Application.StartupPath); case "urldecode": return(HttpUtility.UrlDecode(content)); case "urlencode": return(HttpUtility.UrlEncode(content)); } return(content); }
/// <summary> /// Executes a given command for the given application (also resolves variables). /// </summary> /// <returns>Exit code of the command, if not run in background</returns> private static int ExecuteBatchCommand(ApplicationJob job, string commandText, string targetFileName) { // Ignore empty commands if (string.IsNullOrEmpty(commandText)) { return(0); } commandText = commandText.Replace("\r\n", "\n"); // Job specific data if (job != null) { commandText = job.Variables.ReplaceAllInString(commandText, DateTime.MinValue, targetFileName, false); } else { commandText = UrlVariable.GlobalVariables.ReplaceAllInString(commandText); } // Replace variable: root try { commandText = UrlVariable.Replace(commandText, "root", Path.GetPathRoot(Application.StartupPath)); } catch (ArgumentException) { } // Feed cmd.exe with our commands ProcessStartInfo cmdExe = new ProcessStartInfo("cmd.exe"); cmdExe.RedirectStandardInput = true; cmdExe.UseShellExecute = false; cmdExe.CreateNoWindow = true; cmdExe.RedirectStandardOutput = true; cmdExe.RedirectStandardError = true; bool executeBackground = commandText.EndsWith("&"); commandText = commandText.TrimEnd('&'); using (Process proc = Process.Start(cmdExe)) { StringBuilder commandResult = new StringBuilder(); // Set the event handler to asynchronously read the command output. proc.OutputDataReceived += new DataReceivedEventHandler(delegate(object sendingProcess, DataReceivedEventArgs outLine) { if (!string.IsNullOrEmpty(outLine.Data)) { commandResult.AppendLine(outLine.Data); } }); proc.ErrorDataReceived += new DataReceivedEventHandler(delegate(object sendingProcess, DataReceivedEventArgs outLine) { if (!string.IsNullOrEmpty(outLine.Data)) { commandResult.AppendLine(outLine.Data); } }); // Start the asynchronous read of the command output stream. proc.BeginOutputReadLine(); proc.BeginErrorReadLine(); // Input commands using (proc.StandardInput) { string[] commands = commandText.Split('\n'); foreach (string command in commands) { if (!string.IsNullOrEmpty(command)) { LogDialog.Log(job, "Executing command: " + command); } proc.StandardInput.WriteLine(command); } } // Read output if (!executeBackground) { proc.WaitForExit(); string commandResultString = commandResult.ToString(); if (!string.IsNullOrEmpty(commandResultString)) { LogDialog.Log(job, "Command result: " + commandResultString); } return(proc.ExitCode); } } return(0); }
private void DebugDataAdded(object sender, DataAddedEventArgs e) { LogDialog.Log("PowerShell (debug): " + ((PSDataCollection <DebugRecord>)sender)[e.Index].Message); }
/// <summary> /// Executes the actual download from an URL. Does not handle exceptions, /// but takes care of proper cleanup. /// </summary> /// <exception cref="NonBinaryFileException">This exception is thrown, if the resulting file is not of a binary type</exception> /// <exception cref="TargetPathInvalidException">This exception is thrown, if the resulting target path of an application is not valid</exception> /// <param name="job">The job to process</param> /// <param name="urlToRequest">URL from which should be downloaded</param> /// <returns>true, if a new update has been found and downloaded, false otherwise</returns> protected Status DoDownload(ApplicationJob job, Uri urlToRequest) { // Determine number of segments to create int segmentCount = Convert.ToInt32(Settings.GetValue("SegmentCount", 1)); job.Variables.ResetDownloadCount(); WebRequest req = GeGeekProtocolProvider.CreateRequest(urlToRequest, job, this.m_Cookies); AddRequestToCancel(req); using (WebResponse response = WebClient.GetResponse(req)) { LogDialog.Log(job, "Server source file: " + req.RequestUri.AbsolutePath); // Occasionally, websites are not available and an error page is encountered // For the case that the content type is just plain wrong, ignore it if the size is higher than 500KB HttpWebResponse httpResponse = response as HttpWebResponse; if (httpResponse != null && response.ContentLength < 500000) { if (response.ContentType.StartsWith("text/xml") || response.ContentType.StartsWith("application/xml")) { // If an XML file is served, maybe we have a PAD file ApplicationJob padJob = ApplicationJob.ImportFromPad(httpResponse); if (padJob != null) { job.CachedPadFileVersion = padJob.CachedPadFileVersion; return(this.DoDownload(job, new Uri(padJob.FixedDownloadUrl))); } } if (response.ContentType.StartsWith("text/html")) { bool avoidNonBinary = (bool)Settings.GetValue("AvoidDownloadingNonBinaryFiles", true); if (httpResponse.StatusCode != HttpStatusCode.OK || avoidNonBinary) { throw NonBinaryFileException.Create(response.ContentType, httpResponse.StatusCode); } } } long fileSize = GetContentLength(response); if (fileSize == 0) { throw new IOException("Source file on server is empty (ContentLength = 0)."); } string targetFileName = job.GetTargetFile(response, urlToRequest.AbsoluteUri); LogDialog.Log(job, "Determined target file name: " + targetFileName); // Only download, if the file size or date has changed if (!ForceDownload && !job.RequiresDownload(response, targetFileName)) { // If file already exists (created by user), // the download is not necessary. We still need to // set the file name. // If the file exists, but not at the target location // (after renaming), do not reset the previous location. if (File.Exists(targetFileName)) { job.PreviousLocation = targetFileName; } job.Save(); return(Status.NoUpdate); } // Skip downloading! // Installing also requires a forced download if (!ForceDownload && !m_InstallUpdated && (m_OnlyCheck || (job.CheckForUpdatesOnly && !IgnoreCheckForUpdatesOnly))) { LogDialog.Log(job, "Skipped downloading updates"); return(Status.UpdateAvailable); } // Execute: Default pre-update command string defaultPreCommand = Settings.GetValue("PreUpdateCommand", "") as string; // For starting external download managers: {preupdate-url} defaultPreCommand = UrlVariable.Replace(defaultPreCommand, "preupdate-url", urlToRequest.ToString(), job); ScriptType defaultPreCommandType = Command.ConvertToScriptType(Settings.GetValue("PreUpdateCommandType", ScriptType.Batch.ToString()) as string); int exitCode = new Command(defaultPreCommand, defaultPreCommandType).Execute(job, targetFileName); if (exitCode == 1) { LogDialog.Log(job, "Default pre-update command returned '1', download aborted"); throw new CommandErrorException(); } else if (exitCode == 2) { LogDialog.Log(job, "Default pre-update command returned '2', download skipped"); return(Status.UpdateAvailable); } // Execute: Application pre-update command exitCode = new Command(UrlVariable.Replace(job.ExecutePreCommand, "preupdate-url", urlToRequest.ToString(), job), job.ExecutePreCommandType).Execute(job, targetFileName); if (exitCode == 1) { LogDialog.Log(job, "Pre-update command returned '1', download aborted"); throw new CommandErrorException(); } else if (exitCode == 2) { LogDialog.Log(job, "Pre-update command returned '2', download skipped"); return(Status.UpdateAvailable); } else if (exitCode == 3) { LogDialog.Log(job, "Pre-update command returned '3', external download"); job.LastUpdated = DateTime.Now; job.Save(); return(Status.UpdateSuccessful); } // Read all file contents to a temporary location string tmpLocation = Path.GetTempFileName(); DateTime lastWriteTime = ApplicationJob.GetLastModified(response); // Only use segmented downloader with more than one segment. if (segmentCount > 1) { // Response can be closed now, new one will be created. response.Dispose(); m_Size[job] = fileSize; Downloader d = new Downloader(new ResourceLocation { Url = urlToRequest.AbsoluteUri, ProtocolProvider = new GeGeekProtocolProvider(job, m_Cookies) }, null, tmpLocation, segmentCount); d.Start(); while (d.State < DownloaderState.Ended) { if (m_CancelUpdates) { d.Pause(); break; } this.OnProgressChanged(d.Segments.Sum(x => x.Transfered), fileSize, job); Thread.Sleep(250); } if (d.State == DownloaderState.EndedWithError) { throw d.LastError; } } else { // Read contents from the web and put into file using (Stream sourceFile = response.GetResponseStream()) { using (FileStream targetFile = File.Create(tmpLocation)) { long byteCount = 0; int readBytes; m_Size[job] = fileSize; // Only create buffer once and re-use. const int bufferSize = 1024 * 1024; byte[] buffer = new byte[bufferSize]; do { if (m_CancelUpdates) { break; } // Some adjustment for SCP download: Read only up to the max known bytes int maxRead = (fileSize > 0) ? (int)Math.Min(fileSize - byteCount, bufferSize) : bufferSize; if (maxRead == 0) { break; } readBytes = sourceFile.Read(buffer, 0, maxRead); if (readBytes > 0) { targetFile.Write(buffer, 0, readBytes); } byteCount += readBytes; this.OnProgressChanged(byteCount, fileSize, job); } while (readBytes > 0); } } } if (m_CancelUpdates) { m_Progress[job] = 0; OnStatusChanged(job); return(Status.Failure); } // If each version has a different file name (version number), // we might only want to keep one of them. Also, we might // want to free some space on the target location. if (job.DeletePreviousFile) { PathEx.TryDeleteFiles(job.PreviousLocation); } try { File.SetLastWriteTime(tmpLocation, lastWriteTime); } catch (ArgumentException) { // Invalid file date. Ignore and just use DateTime.Now } // File downloaded. Now let's check if the hash value is valid or abort otherwise! if (!string.IsNullOrEmpty(job.HashVariable) && job.HashType != HashType.None) { string varName = job.HashVariable.Trim('{', '}'); string expectedHash = job.Variables.ReplaceAllInString("{" + varName + "}").Trim(); // Compare online hash with actual current hash. if (!string.IsNullOrEmpty(expectedHash)) { string currentHash = job.GetFileHash(tmpLocation); if (string.Compare(expectedHash, currentHash, StringComparison.OrdinalIgnoreCase) != 0) { LogDialog.Log(job, string.Format("File downloaded, but hash of downloaded file {0} does not match the expected hash {1}.", currentHash, expectedHash)); File.Delete(tmpLocation); throw new IOException("Hash verification failed."); } } } try { FileInfo downloadedFileInfo = new FileInfo(tmpLocation); job.LastFileSize = downloadedFileInfo.Length; job.LastFileDate = downloadedFileInfo.LastWriteTime; } catch (Exception ex) { LogDialog.Log(job, ex); } try { // Before copying, we might have to create the directory Directory.CreateDirectory(Path.GetDirectoryName(targetFileName)); // Copying might fail if variables have been replaced with bad values. // However, we cannot rely on functions to clean up the path, since they // might actually parse the path incorrectly and return an even worse path. File.Copy(tmpLocation, targetFileName, true); } catch (ArgumentException) { throw new TargetPathInvalidException(targetFileName); } catch (NotSupportedException) { throw new TargetPathInvalidException(targetFileName); } File.Delete(tmpLocation); // At this point, the update is complete job.LastUpdated = DateTime.Now; job.PreviousLocation = targetFileName; } job.Save(); job.ExecutePostUpdateCommands(); return(Status.UpdateSuccessful); }
/// <summary> /// Performs the update process of a single application. /// Catches most exceptions and stores them for later use. /// </summary> private void StartNewThread(object paramJob) { ApplicationJob job = paramJob as ApplicationJob; m_Status[job] = Status.Downloading; OnStatusChanged(job); string requestedUrl = string.Empty; int numTries = 0; int maxTries = Convert.ToInt32(Settings.GetValue("RetryCount", 1)); try { while (numTries < maxTries) { try { numTries++; m_Status[job] = DoDownload(job, out requestedUrl); // If there is a custom column variable, and it has not been been downloaded yet, // make sure that we fetch it now "unnecessarily" so that the column contains a current value. Dictionary <string, string> customColumns = SettingsDialog.CustomColumns; foreach (KeyValuePair <string, string> column in customColumns) { if (!string.IsNullOrEmpty(column.Value) && !job.Variables.HasVariableBeenDownloaded(column.Value)) { job.Variables.ReplaceAllInString("{" + column.Value.TrimStart('{').TrimEnd('}') + "}"); } } if (customColumns.Count > 0) { job.Save(); // cached variable content } // Install if updated if (m_InstallUpdated && m_Status[job] == Status.UpdateSuccessful) { job.Install(null); } // If no exception happened, we immediately leave the loop break; } catch (SQLiteException ex) { // If "locked" exception (slow USB device eg.) continue trying if (ex.ErrorCode == (int)SQLiteErrorCode.Locked) { numTries--; LogDialog.Log(job, ex); } else { throw; } } catch (Exception ex) { WebException webException = ex as WebException; if (webException != null && webException.Status == WebExceptionStatus.RequestCanceled) { // User cancelled the process -> Do nothing m_Status[job] = Status.Failure; break; } // Only throw an exception if we have run out of tries if (numTries == maxTries) { throw; } else { LogDialog.Log(job, ex); } } } } catch (WebException ex) { LogDialog.Log(job, ex); this.HandleUpdateFailed(job, new ApplicationJobError(job, ex, (ex.Response != null) ? ex.Response.ResponseUri.ToString() : requestedUrl)); } catch (FileNotFoundException ex) { // Executing command failed LogDialog.Log(job, ex); m_Errors.Add(new ApplicationJobError(job, ex)); } catch (Win32Exception ex) { // Executing command failed LogDialog.Log(job, ex); m_Errors.Add(new ApplicationJobError(job, ex)); } catch (IOException ex) { LogDialog.Log(job, ex); this.HandleUpdateFailed(job, new ApplicationJobError(job, ex)); } catch (UnauthorizedAccessException ex) { LogDialog.Log(job, ex); this.HandleUpdateFailed(job, new ApplicationJobError(job, ex)); } catch (UriFormatException ex) { LogDialog.Log(job, ex); this.HandleUpdateFailed(job, new ApplicationJobError(job, ex, requestedUrl)); } catch (NotSupportedException ex) { // Invalid URI prefix LogDialog.Log(job, ex); this.HandleUpdateFailed(job, new ApplicationJobError(job, ex, requestedUrl)); } catch (NonBinaryFileException ex) { LogDialog.Log(job, ex); this.HandleUpdateFailed(job, new ApplicationJobError(job, ex, requestedUrl)); } catch (TargetPathInvalidException ex) { LogDialog.Log(job, ex); this.HandleUpdateFailed(job, new ApplicationJobError(job, ex, requestedUrl)); } catch (CommandErrorException ex) { LogDialog.Log(job, ex); this.HandleUpdateFailed(job, new ApplicationJobError(job, ex, requestedUrl)); } catch (ApplicationException ex) { // Error executing custom C# script LogDialog.Log(job, ex); m_Errors.Add(new ApplicationJobError(job, ex)); } catch (SQLiteException ex) { LogDialog.Log(job, ex); this.HandleUpdateFailed(job, new ApplicationJobError(job, ex, requestedUrl)); } m_Progress[job] = 100; OnStatusChanged(job); }
/// <summary> /// Performs the actual update check for the current applications. /// Starts multiple threads if necessary. /// </summary> private void UpdateApplications() { m_CancelUpdates = false; m_Errors = new List <ApplicationJobError>(); LogDialog.Log(string.Format("Update started with {0} application(s)", m_Jobs.Length)); try { ApplicationJob previousJob = null; foreach (ApplicationJob job in m_Jobs) { // Skip if disabled if (!job.Enabled && m_Jobs.Length > 1) { continue; } // Wait until we can start a new thread: // - Thread limit is not reached // - The next application is not to be downloaded exclusively // - The application previously started is not to be downloaded exclusively // - Setup is taking place while (m_Threads.Count >= m_ThreadLimit || (m_Threads.Count > 0 && (m_InstallUpdated || job.ExclusiveDownload || (previousJob != null && previousJob.ExclusiveDownload)))) { Thread.Sleep(200); foreach (Thread activeThread in m_Threads) { if (!activeThread.IsAlive) { m_Threads.Remove(activeThread); break; } } } // Stop if cancelled if (m_CancelUpdates) { break; } Thread newThread = new Thread(this.StartNewThread); previousJob = job; newThread.Start(job); m_Threads.Add(newThread); } // Now, wait until all threads have finished while (m_Threads.Count > 0) { Thread.Sleep(200); foreach (Thread activeThread in m_Threads) { if (!activeThread.IsAlive) { m_Threads.Remove(activeThread); break; } } } try { string postUpdateCommand = Settings.GetValue("PostUpdateCommand", "") as string; ScriptType postUpdateCommandType = Command.ConvertToScriptType(Settings.GetValue("PostUpdateCommandType", ScriptType.Batch.ToString()) as string); new Command(postUpdateCommand, postUpdateCommandType).Execute(null); } catch (ApplicationException ex) { LogDialog.Log("Post update command failed.", ex); } LogDialog.Log("Update finished"); } finally { IsBusy = false; m_Progress.Clear(); m_Size.Clear(); OnUpdateCompleted(); } }
private void InfoDataAdded(object sender, DataAddedEventArgs e) { LogDialog.Log("PowerShell (info): " + ((PSDataCollection <InformationRecord>)sender)[e.Index].MessageData); }
/// <summary> /// Applies a function (if given) to content and returns the /// modified content. /// </summary> /// <param name="function">A function specification, for example "replace:a:b"</param> /// <param name="content">The usual variable content</param> /// <param name="context">ApplicationJob context for referencing values of other variables</param> private static string ReplaceFunction(string function, string content, ApplicationJob context = null) { function = function.TrimStart(':'); if (string.IsNullOrEmpty(function)) { return(content); } string[] parts = SplitEscaped(function, ':'); if (parts.Length == 0) { return(content); } switch (parts[0]) { case "runpowershell": case "ps": // PowerShell is broken because Stream "Information" not supportable. LogDialog.Log(context, "PowerShell commands not supported at present."); return(string.Empty); /* * try * { * if (context != null && !context.CanBeShared) * { * LogDialog.Log(context, "PowerShell command of downloaded application is not executed for security reasons."); * return string.Empty; * } * * PowerShellScript psScript = new PowerShellScript(content); * psScript.Execute(context); * return psScript.LastOutput; * } * catch * { * return string.Empty; * } */ case "empty": // Useful, if you want to load, but not use a variable return(string.Empty); case "ifempty": if (string.IsNullOrEmpty(content) && context != null && parts.Length > 1) { return(context.Variables.ReplaceAllInString("{" + parts[1] + "}")); } return(content); case "ifemptythenerror": if (string.IsNullOrEmpty(content)) { throw new VariableIsEmptyException(); } return(content); case "regexreplace": try { if (parts.Length > 2) { Regex regex = new Regex(parts[1], RegexOptions.Singleline | RegexOptions.IgnoreCase); return(regex.Replace(content, delegate(Match match) { string result = parts[2]; for (int i = 0; i < match.Groups.Count; i++) { result = result.Replace("$" + i, match.Groups[i].Value); } return result; })); } } catch (ArgumentException ex) { LogDialog.Log("Could not process the function 'regexreplace'.", ex); } return(string.Empty); case "multireplace": case "multireplacei": if (parts.Length > 3) { if (string.IsNullOrEmpty(parts[1])) { break; } // Exmaple: multireplace:,:a,b,c:1,2,3 char delimiter = parts[1][0]; string[] search = parts[2].Split(delimiter); string[] replace = parts[3].Split(delimiter); for (int i = 0; i < search.Length; i++) { string replaceValue = (replace.Length > i) ? replace[i] : string.Empty; content = parts[0] == "multireplacei" ? ReplaceEx(content, search[i], replaceValue) : content.Replace(search[i], replaceValue); } return(content); } break; case "regex": try { Regex regex = new Regex(parts[1], RegexOptions.Singleline); Match match = regex.Match(content); if (parts.Length > 2) { int groupNum = Conversion.ToInt(parts[2]); if (groupNum >= 0 && groupNum < match.Groups.Count) { return(match.Groups[groupNum].Value); } } return((match.Success) ? match.Value : string.Empty); } catch (ArgumentException ex) { LogDialog.Log("Could not process the function 'regex'.", ex); return(string.Empty); } case "regexrandom": try { Regex regex = new Regex(parts[1], RegexOptions.Singleline); MatchCollection matches = regex.Matches(content); if (matches.Count > 0) { int randomPos = random.Next(0, matches.Count - 1); int groupNum = (parts.Length > 2) ? Conversion.ToInt(parts[2]) : -1; if (groupNum >= 0 && groupNum < matches[randomPos].Groups.Count) { return(matches[randomPos].Groups[groupNum].Value); } else { return(matches[randomPos].Value); } } return(string.Empty); } catch (ArgumentException ex) { LogDialog.Log("Could not process the function 'regex'.", ex); return(string.Empty); } case "ext": return(Path.GetExtension(content).TrimStart('.')); case "basefile": return(Path.GetFileNameWithoutExtension(content)); case "directory": try { if (content.StartsWith("\"") && content.EndsWith("\"")) { return("\"" + Path.GetDirectoryName(content.Trim('"')) + "\""); } else { return(Path.GetDirectoryName(content.Trim('"'))); } } catch { return(content); } case "filename": try { return(Path.GetFileName(content)); } catch { return(content); } case "filenameWithoutExtension": try { return(Path.GetFileNameWithoutExtension(content)); } catch { return(content); } case "toupper": return(content.ToUpper()); case "tolower": return(content.ToLower()); case "split": if (parts.Length >= 3) { string[] contentParts = content.Split(parts[1][0]); int partNum; if (Int32.TryParse(parts[2], out partNum)) { if (partNum < 0) { // Negative number: Count from the end partNum = contentParts.Length + partNum; } if (partNum >= 0 && partNum < contentParts.Length) { return(contentParts[partNum]); } } } break; case "trim": if (parts.Length >= 2) { return(content.Trim(parts[1].ToCharArray())); } else { return(content.Trim()); } case "padleft": if (parts.Length == 3) { return(content.PadLeft(Conversion.ToInt(parts[1]), parts[2][0])); } else if (parts.Length == 2) { return(content.PadLeft(Conversion.ToInt(parts[1]), ' ')); } return(content); case "padright": if (parts.Length == 3) { return(content.PadRight(Conversion.ToInt(parts[1]), parts[2][0])); } else if (parts.Length == 2) { return(content.PadRight(Conversion.ToInt(parts[1]), ' ')); } return(content); case "trimend": if (parts.Length >= 2) { return(content.TrimEnd(parts[1].ToCharArray())); } else { return(content.TrimEnd()); } case "trimstart": if (parts.Length >= 2) { return(content.TrimStart(parts[1].ToCharArray())); } else { return(content.TrimStart()); } case "replace": if (parts.Length >= 3) { return(content.Replace(parts[1], parts[2])); } break; case "formatfilesize": return(FormatFileSize.Format(Conversion.ToInt(content))); case "startuppath": return(Application.StartupPath); case "urldecode": return(HttpUtility.UrlDecode(content)); case "urlencode": return(HttpUtility.UrlEncode(content)); } return(content); }
/// <summary> /// Replaces this variable within a given string. /// </summary> /// <param name="onlyCached">If true, no new content will be downloaded and only chached content will be used.</param> /// <param name="fileDate">Current file date, when downloading the modification date of the file being downloaded</param> public virtual string ReplaceInString(string value, DateTime fileDate, bool onlyCached) { if (!IsVariableUsedInString(m_Name, value)) { return(value); } // Global variable only has static content if (onlyCached) { return((m_CachedContent == null) ? value : Replace(value, m_CachedContent, this.Parent == null ? null : this.Parent.Parent)); } // Ignore missing URLs etc. if (IsEmpty) { return(value); } // Using textual content? if (m_VariableType == Type.Textual) { m_CachedContent = GetExpandedTextualContent(fileDate); LogDialog.Log(this, value, m_CachedContent); return(Replace(value, m_CachedContent, this.Parent == null ? null : this.Parent.Parent)); } string page = string.Empty; // Get the content we need to put in string userAgent = (this.Parent == null) ? null : this.Parent.Parent.Variables.ReplaceAllInString(this.Parent.Parent.UserAgent); using (WebClient client = new WebClient(userAgent)) { try { string url = ExpandedUrl; client.SetPostData(this); page = client.DownloadString(url); } catch (ArgumentException) { throw new UriFormatException("The URL '" + Url + "' of variable '" + m_Name + "' is not valid."); } m_DownloadCount++; } // Normalise line-breaks page = page.Replace("\r\n", "\n"); page = page.Replace("\r", "\n"); // Using a regular expression? if (m_VariableType == Type.RegularExpression) { Regex regex = CreateRegex(); if (regex == null) { return(value); } Match match = regex.Match(page); if (match.Success) { if (match.Groups.Count == 1) { m_CachedContent = match.Value; LogDialog.Log(this, value, m_CachedContent); return(Replace(value, match.Value)); } else if (match.Groups.Count >= 2) { // Use first group (except index 0, which is complete match) with a match for (int i = 1; i < match.Groups.Count; i++) { if (match.Groups[i].Success) { m_CachedContent = match.Groups[i].Value; LogDialog.Log(this, value, m_CachedContent); return(Replace(value, match.Groups[i].Value)); } } // No group matches, use complete match m_CachedContent = match.Groups[0].Value; LogDialog.Log(this, value, m_CachedContent); return(Replace(value, match.Groups[0].Value)); } } else { // No regex match should yield an empty result return(Replace(value, string.Empty)); } } // Use whole page if either start or end is missing if (string.IsNullOrEmpty(m_StartText) || string.IsNullOrEmpty(m_EndText)) { m_CachedContent = page; LogDialog.Log(this, value, m_CachedContent); return(Replace(value, page)); } int startPos = page.IndexOf(m_StartText); if (startPos < 0) { return(value); } int endOfStart = startPos + m_StartText.Length; int endPos = page.IndexOf(m_EndText, endOfStart); if (endPos < 0) { return(value); } string result = page.Substring(endOfStart, endPos - endOfStart); m_CachedContent = result; LogDialog.Log(this, value, m_CachedContent); value = Replace(value, result); return(value); }
/// <summary> /// Executes the actual download from an URL. Does not handle exceptions, /// but takes care of proper cleanup. /// </summary> /// <exception cref="NonBinaryFileException">This exception is thrown, if the resulting file is not of a binary type</exception> /// <exception cref="TargetPathInvalidException">This exception is thrown, if the resulting target path of an application is not valid</exception> /// <param name="job">The job to process</param> /// <param name="urlToRequest">URL from which should be downloaded</param> /// <returns>true, if a new update has been found and downloaded, false otherwise</returns> protected Status DoDownload(ApplicationJob job, Uri urlToRequest) { // Lower security policies try { ServicePointManager.CheckCertificateRevocationList = false; } catch (PlatformNotSupportedException) { // .NET bug under special circumstances } ServicePointManager.ServerCertificateValidationCallback = delegate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return(true); }; // If we want to download multiple files simultaneously // from the same server, we need to "remove" the connection limit. ServicePointManager.DefaultConnectionLimit = 50; job.Variables.ResetDownloadCount(); WebRequest.RegisterPrefix("sf", new ScpWebRequestCreator()); WebRequest.RegisterPrefix("httpx", new HttpxRequestCreator()); WebRequest req = WebRequest.CreateDefault(urlToRequest); AddRequestToCancel(req); req.Timeout = Convert.ToInt32(Settings.GetValue("ConnectionTimeout", 10)) * 1000; // 10 seconds by default HttpWebRequest httpRequest = req as HttpWebRequest; if (httpRequest != null) { // Store cookies for future requests. Some sites // check for previously stored cookies before allowing to download. if (httpRequest.CookieContainer == null) { httpRequest.CookieContainer = m_Cookies; } else { httpRequest.CookieContainer.Add(m_Cookies.GetCookies(httpRequest.RequestUri)); } // If we have an HTTP request, some sites may require a correct referer // for the download. // If there are variables defined (from which most likely the download link // or version is being extracted), we'll just use the first variable's URL as referer. // The user still has the option to set a custom referer. // Note: Some websites don't "like" certain referers if (!m_NoAutoReferer.Contains(GetBaseHost(req.RequestUri))) { foreach (UrlVariable urlVar in job.Variables.Values) { httpRequest.Referer = urlVar.Url; break; } } if (!string.IsNullOrEmpty(job.HttpReferer)) { httpRequest.Referer = job.Variables.ReplaceAllInString(job.HttpReferer); } LogDialog.Log(job, "Using referer: " + (string.IsNullOrEmpty(httpRequest.Referer) ? "(none)" : httpRequest.Referer)); httpRequest.UserAgent = (string.IsNullOrEmpty(job.UserAgent) ? WebClient.UserAgent : job.UserAgent); // PAD files may be compressed httpRequest.AutomaticDecompression = (DecompressionMethods.GZip | DecompressionMethods.Deflate); } using (WebResponse response = WebClient.GetResponse(req)) { LogDialog.Log(job, "Server source file: " + req.RequestUri.AbsolutePath); // Occasionally, websites are not available and an error page is encountered // For the case that the content type is just plain wrong, ignore it if the size is higher than 500KB HttpWebResponse httpResponse = response as HttpWebResponse; if (httpResponse != null && response.ContentLength < 500000) { if (response.ContentType.StartsWith("text/xml") || response.ContentType.StartsWith("application/xml")) { // If an XML file is served, maybe we have a PAD file ApplicationJob padJob = ApplicationJob.ImportFromPad(httpResponse); if (padJob != null) { job.CachedPadFileVersion = padJob.CachedPadFileVersion; return(DoDownload(job, new Uri(padJob.FixedDownloadUrl))); } } if (response.ContentType.StartsWith("text/html")) { throw NonBinaryFileException.Create(response.ContentType, httpResponse.StatusCode); } } long fileSize = GetContentLength(response); if (fileSize == 0) { throw new IOException("Source file on server is empty (ContentLength = 0)."); } string targetFileName = job.GetTargetFile(response, urlToRequest.AbsoluteUri); LogDialog.Log(job, "Determined target file name: " + targetFileName); // Only download, if the file size or date has changed if (!ForceDownload && !job.RequiresDownload(response, targetFileName)) { // If file already exists (created by user), // the download is not necessary. We still need to // set the file name. // If the file exists, but not at the target location // (after renaming), do not reset the previous location. if (File.Exists(targetFileName)) { job.PreviousLocation = targetFileName; } job.Save(); return(Status.NoUpdate); } // Skip downloading! // Installing also requires a forced download if (!ForceDownload && !m_InstallUpdated && (m_OnlyCheck || (job.CheckForUpdatesOnly && !IgnoreCheckForUpdatesOnly))) { LogDialog.Log(job, "Skipped downloading updates"); return(Status.UpdateAvailable); } // Execute: Default pre-update command string defaultPreCommand = Settings.GetValue("PreUpdateCommand", "") as string; // For starting external download managers: {preupdate-url} defaultPreCommand = UrlVariable.Replace(defaultPreCommand, "preupdate-url", urlToRequest.ToString()); ScriptType defaultPreCommandType = Command.ConvertToScriptType(Settings.GetValue("PreUpdateCommandType", ScriptType.Batch.ToString()) as string); int exitCode = new Command(defaultPreCommand, defaultPreCommandType).Execute(job, targetFileName); if (exitCode == 1) { LogDialog.Log(job, "Default pre-update command returned '1', download aborted"); throw new CommandErrorException(); } else if (exitCode == 2) { LogDialog.Log(job, "Default pre-update command returned '2', download skipped"); return(Status.UpdateAvailable); } // Execute: Application pre-update command exitCode = new Command(UrlVariable.Replace(job.ExecutePreCommand, "preupdate-url", urlToRequest.ToString()), job.ExecutePreCommandType).Execute(job, targetFileName); if (exitCode == 1) { LogDialog.Log(job, "Pre-update command returned '1', download aborted"); throw new CommandErrorException(); } else if (exitCode == 2) { LogDialog.Log(job, "Pre-update command returned '2', download skipped"); return(Status.UpdateAvailable); } else if (exitCode == 3) { LogDialog.Log(job, "Pre-update command returned '3', external download"); job.LastUpdated = DateTime.Now; job.Save(); return(Status.UpdateSuccessful); } // Read all file contents to a temporary location string tmpLocation = Path.GetTempFileName(); // Read contents from the web and put into file using (Stream sourceFile = response.GetResponseStream()) { using (FileStream targetFile = File.Create(tmpLocation)) { long byteCount = 0; int readBytes = 0; m_Size[job] = fileSize; do { if (m_CancelUpdates) { break; } // Some adjustment for SCP download: Read only up to the max known bytes int maxRead = (fileSize > 0) ? (int)Math.Min(fileSize - byteCount, 1024) : 1024; if (maxRead == 0) { break; } byte[] buffer = new byte[maxRead]; readBytes = sourceFile.Read(buffer, 0, maxRead); if (readBytes > 0) { targetFile.Write(buffer, 0, readBytes); } byteCount += readBytes; OnProgressChanged(byteCount, fileSize, job); } while (readBytes > 0); } } if (m_CancelUpdates) { m_Progress[job] = 0; OnStatusChanged(job); return(Status.Failure); } // If each version has a different file name (version number), // we might only want to keep one of them. Also, we might // want to free some space on the target location. if (job.DeletePreviousFile) { PathEx.TryDeleteFiles(job.PreviousLocation); } try { File.SetLastWriteTime(tmpLocation, ApplicationJob.GetLastModified(response)); } catch (ArgumentException) { // Invalid file date. Ignore and just use DateTime.Now } try { FileInfo downloadedFileInfo = new FileInfo(tmpLocation); job.LastFileSize = downloadedFileInfo.Length; job.LastFileDate = downloadedFileInfo.LastWriteTime; } catch (Exception ex) { LogDialog.Log(job, ex); } try { // Before copying, we might have to create the directory Directory.CreateDirectory(Path.GetDirectoryName(targetFileName)); // Copying might fail if variables have been replaced with bad values. // However, we cannot rely on functions to clean up the path, since they // might actually parse the path incorrectly and return an even worse path. File.Copy(tmpLocation, targetFileName, true); } catch (ArgumentException) { throw new TargetPathInvalidException(targetFileName); } catch (NotSupportedException) { throw new TargetPathInvalidException(targetFileName); } File.Delete(tmpLocation); // At this point, the update is complete job.LastUpdated = DateTime.Now; job.PreviousLocation = targetFileName; } job.Save(); job.ExecutePostUpdateCommands(); return(Status.UpdateSuccessful); }