diff --git a/TGControlPanel/Main.Designer.cs b/TGControlPanel/Main.Designer.cs index 2020a8b21d..01d1580352 100644 --- a/TGControlPanel/Main.Designer.cs +++ b/TGControlPanel/Main.Designer.cs @@ -148,6 +148,16 @@ this.RepoProgressBarLabel = new System.Windows.Forms.Label(); this.RepoProgressBar = new System.Windows.Forms.ProgressBar(); this.Panels = new System.Windows.Forms.TabControl(); + this.StaticPanel = new System.Windows.Forms.TabPage(); + this.StaticFileDownloadButton = new System.Windows.Forms.Button(); + this.StaticFilesRefreshButton = new System.Windows.Forms.Button(); + this.StaticFileUploadButton = new System.Windows.Forms.Button(); + this.StaticFileEditTextbox = new System.Windows.Forms.TextBox(); + this.StaticFileDeleteButton = new System.Windows.Forms.Button(); + this.StaticFileSaveButton = new System.Windows.Forms.Button(); + this.StaticFileCreateButton = new System.Windows.Forms.Button(); + this.label6 = new System.Windows.Forms.Label(); + this.StaticFileListBox = new System.Windows.Forms.ListBox(); this.ChatPanel.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.ChatPortSelector)).BeginInit(); this.ChatProviderSelectorPanel.SuspendLayout(); @@ -161,6 +171,7 @@ this.RepoPanel.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.TestmergeSelector)).BeginInit(); this.Panels.SuspendLayout(); + this.StaticPanel.SuspendLayout(); this.SuspendLayout(); // // RepoBGW @@ -1598,12 +1609,131 @@ this.Panels.Controls.Add(this.BYONDPanel); this.Panels.Controls.Add(this.ServerPanel); this.Panels.Controls.Add(this.ChatPanel); + this.Panels.Controls.Add(this.StaticPanel); + this.Panels.Cursor = System.Windows.Forms.Cursors.Default; this.Panels.Location = new System.Drawing.Point(12, 12); this.Panels.Name = "Panels"; this.Panels.SelectedIndex = 0; this.Panels.Size = new System.Drawing.Size(876, 392); this.Panels.TabIndex = 3; // + // StaticPanel + // + this.StaticPanel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); + this.StaticPanel.Controls.Add(this.StaticFileDownloadButton); + this.StaticPanel.Controls.Add(this.StaticFilesRefreshButton); + this.StaticPanel.Controls.Add(this.StaticFileUploadButton); + this.StaticPanel.Controls.Add(this.StaticFileEditTextbox); + this.StaticPanel.Controls.Add(this.StaticFileDeleteButton); + this.StaticPanel.Controls.Add(this.StaticFileSaveButton); + this.StaticPanel.Controls.Add(this.StaticFileCreateButton); + this.StaticPanel.Controls.Add(this.label6); + this.StaticPanel.Controls.Add(this.StaticFileListBox); + this.StaticPanel.Location = new System.Drawing.Point(4, 22); + this.StaticPanel.Name = "StaticPanel"; + this.StaticPanel.Padding = new System.Windows.Forms.Padding(3); + this.StaticPanel.Size = new System.Drawing.Size(868, 366); + this.StaticPanel.TabIndex = 5; + this.StaticPanel.Text = "Static Files"; + // + // StaticFileDownloadButton + // + this.StaticFileDownloadButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.StaticFileDownloadButton.Location = new System.Drawing.Point(296, 6); + this.StaticFileDownloadButton.Name = "StaticFileDownloadButton"; + this.StaticFileDownloadButton.Size = new System.Drawing.Size(71, 22); + this.StaticFileDownloadButton.TabIndex = 32; + this.StaticFileDownloadButton.Text = "Download"; + this.StaticFileDownloadButton.UseVisualStyleBackColor = true; + this.StaticFileDownloadButton.Click += new System.EventHandler(this.StaticFileDownloadButton_Click); + // + // StaticFilesRefreshButton + // + this.StaticFilesRefreshButton.Location = new System.Drawing.Point(65, 6); + this.StaticFilesRefreshButton.Name = "StaticFilesRefreshButton"; + this.StaticFilesRefreshButton.Size = new System.Drawing.Size(71, 22); + this.StaticFilesRefreshButton.TabIndex = 31; + this.StaticFilesRefreshButton.Text = "Refresh"; + this.StaticFilesRefreshButton.UseVisualStyleBackColor = true; + this.StaticFilesRefreshButton.Click += new System.EventHandler(this.StaticFilesRefreshButton_Click); + // + // StaticFileUploadButton + // + this.StaticFileUploadButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.StaticFileUploadButton.Location = new System.Drawing.Point(219, 6); + this.StaticFileUploadButton.Name = "StaticFileUploadButton"; + this.StaticFileUploadButton.Size = new System.Drawing.Size(71, 22); + this.StaticFileUploadButton.TabIndex = 30; + this.StaticFileUploadButton.Text = "Upload"; + this.StaticFileUploadButton.UseVisualStyleBackColor = true; + this.StaticFileUploadButton.Click += new System.EventHandler(this.StaticFileUploadButton_Click); + // + // StaticFileEditTextbox + // + this.StaticFileEditTextbox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.StaticFileEditTextbox.Location = new System.Drawing.Point(219, 31); + this.StaticFileEditTextbox.Multiline = true; + this.StaticFileEditTextbox.Name = "StaticFileEditTextbox"; + this.StaticFileEditTextbox.ScrollBars = System.Windows.Forms.ScrollBars.Both; + this.StaticFileEditTextbox.Size = new System.Drawing.Size(643, 329); + this.StaticFileEditTextbox.TabIndex = 29; + // + // StaticFileDeleteButton + // + this.StaticFileDeleteButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.StaticFileDeleteButton.Location = new System.Drawing.Point(714, 6); + this.StaticFileDeleteButton.Name = "StaticFileDeleteButton"; + this.StaticFileDeleteButton.Size = new System.Drawing.Size(71, 22); + this.StaticFileDeleteButton.TabIndex = 28; + this.StaticFileDeleteButton.Text = "Delete"; + this.StaticFileDeleteButton.UseVisualStyleBackColor = true; + this.StaticFileDeleteButton.Click += new System.EventHandler(this.StaticFileDeleteButton_Click); + // + // StaticFileSaveButton + // + this.StaticFileSaveButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.StaticFileSaveButton.Location = new System.Drawing.Point(791, 6); + this.StaticFileSaveButton.Name = "StaticFileSaveButton"; + this.StaticFileSaveButton.Size = new System.Drawing.Size(71, 22); + this.StaticFileSaveButton.TabIndex = 27; + this.StaticFileSaveButton.Text = "Save"; + this.StaticFileSaveButton.UseVisualStyleBackColor = true; + this.StaticFileSaveButton.Click += new System.EventHandler(this.StaticFileSaveButton_Click); + // + // StaticFileCreateButton + // + this.StaticFileCreateButton.Location = new System.Drawing.Point(142, 6); + this.StaticFileCreateButton.Name = "StaticFileCreateButton"; + this.StaticFileCreateButton.Size = new System.Drawing.Size(71, 22); + this.StaticFileCreateButton.TabIndex = 26; + this.StaticFileCreateButton.Text = "Add"; + this.StaticFileCreateButton.UseVisualStyleBackColor = true; + this.StaticFileCreateButton.Click += new System.EventHandler(this.StaticFileCreateButton_Click); + // + // label6 + // + this.label6.AutoSize = true; + this.label6.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label6.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.label6.Location = new System.Drawing.Point(6, 10); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(53, 18); + this.label6.TabIndex = 14; + this.label6.Text = "Files:"; + this.label6.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // StaticFileListBox + // + this.StaticFileListBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left))); + this.StaticFileListBox.FormattingEnabled = true; + this.StaticFileListBox.HorizontalScrollbar = true; + this.StaticFileListBox.Location = new System.Drawing.Point(6, 31); + this.StaticFileListBox.Name = "StaticFileListBox"; + this.StaticFileListBox.Size = new System.Drawing.Size(207, 329); + this.StaticFileListBox.TabIndex = 0; + this.StaticFileListBox.SelectedIndexChanged += new System.EventHandler(this.StaticFileListBox_SelectedIndexChanged); + // // Main // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -1634,6 +1764,8 @@ this.RepoPanel.PerformLayout(); ((System.ComponentModel.ISupportInitialize)(this.TestmergeSelector)).EndInit(); this.Panels.ResumeLayout(false); + this.StaticPanel.ResumeLayout(false); + this.StaticPanel.PerformLayout(); this.ResumeLayout(false); } @@ -1757,5 +1889,15 @@ private System.Windows.Forms.Label RepoProgressBarLabel; private System.Windows.Forms.ProgressBar RepoProgressBar; private System.Windows.Forms.TabControl Panels; + private System.Windows.Forms.TabPage StaticPanel; + private System.Windows.Forms.Button StaticFileCreateButton; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.ListBox StaticFileListBox; + private System.Windows.Forms.Button StaticFileUploadButton; + private System.Windows.Forms.TextBox StaticFileEditTextbox; + private System.Windows.Forms.Button StaticFileDeleteButton; + private System.Windows.Forms.Button StaticFileSaveButton; + private System.Windows.Forms.Button StaticFilesRefreshButton; + private System.Windows.Forms.Button StaticFileDownloadButton; } } diff --git a/TGControlPanel/Main.cs b/TGControlPanel/Main.cs index 18cade4c7d..62f2c4618a 100644 --- a/TGControlPanel/Main.cs +++ b/TGControlPanel/Main.cs @@ -15,6 +15,7 @@ namespace TGControlPanel InitBYONDPage(); InitServerPage(); LoadChatPage(); + InitStaticPage(); } private void Main_Resize(object sender, EventArgs e) @@ -28,5 +29,6 @@ namespace TGControlPanel { Properties.Settings.Default.LastPageIndex = Panels.SelectedIndex; } + } } diff --git a/TGControlPanel/StaticPage.cs b/TGControlPanel/StaticPage.cs new file mode 100644 index 0000000000..dac92e101a --- /dev/null +++ b/TGControlPanel/StaticPage.cs @@ -0,0 +1,249 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Windows.Forms; +using TGServiceInterface; + +namespace TGControlPanel +{ + partial class Main + { + IDictionary IndexesToPaths = new Dictionary(); + bool changesMade = false; + void InitStaticPage() + { + BuildFileList(); + } + + void BuildFileList() + { + IndexesToPaths.Clear(); + StaticFileListBox.Items.Clear(); + IndexesToPaths.Add(StaticFileListBox.Items.Add("/"), "/"); + if (!EnumeratePath("", Server.GetComponent(), 1)) + { + StaticFileListBox.Items[0] += " (UNAUTHORIZED)"; + IndexesToPaths[0] = null; + } + StaticFileListBox.SelectedIndex = 0; + } + + bool EnumeratePath(string path, ITGConfig config, int level) + { + var Enum = config.ListStaticDirectory(path, out string error, out bool unauthorized); + if(Enum == null) + { + if (unauthorized) + return false; + else + { + MessageBox.Show(String.Format("Could not enumerate static path \"{0}\" error: {1}", path, error)); + return true; + } + } + foreach(var I in Enum) + { + if(I[0] == '/') + { + var dir = I.Remove(0, 1); + var index = StaticFileListBox.Items.Add(DSNTimes(level) + dir + '/'); + var fullpath = path + dir; + IndexesToPaths.Add(index, fullpath); + if (!EnumeratePath(fullpath, config, level + 1)) + { + StaticFileListBox.Items[index] += " (UNAUTHORIZED)"; + IndexesToPaths[index] = null; + } + continue; + } + + IndexesToPaths.Add(StaticFileListBox.Items.Add(DSNTimes(level) + I), Path.Combine(path, I)); + } + return true; + } + + string DSNTimes(int n) + { + var res = ""; + for (var I = 0; I < n; ++I) + res += " "; + return res; + } + private void StaticFilesRefreshButton_Click(object sender, EventArgs e) + { + BuildFileList(); + } + + private void StaticFileUploadButton_Click(object sender, EventArgs e) + { + if (StaticFileEditTextbox.Text != "Directory") + { + MessageBox.Show("Please select a directory to upload the file to."); + return; + } + var ofd = new OpenFileDialog() + { + CheckFileExists = true, + CheckPathExists = true, + DefaultExt = ".txt", + Multiselect = false, + Title = "Static File Upload", + ValidateNames = true, + Filter = "All files (*.*)|*.*", + AddExtension = false, + SupportMultiDottedExtensions = true, + }; + if (ofd.ShowDialog() != DialogResult.OK) + return; + + var fileToUpload = ofd.FileName; + + var FileName = Path.Combine(IndexesToPaths[StaticFileListBox.SelectedIndex], Path.GetFileName(fileToUpload)); + + string fileContents = null; + string error = null; + try + { + fileContents = File.ReadAllText(fileToUpload); + } + catch (Exception ex) + { + error = ex.ToString(); + } + if (error == null) + error = Server.GetComponent().WriteText(FileName, fileContents, out bool unauthorized); + if (error != null) + MessageBox.Show("An error occurred: " + error); + BuildFileList(); + } + + private void StaticFileDownloadButton_Click(object sender, EventArgs e) + { + if (StaticFileEditTextbox.ReadOnly) + { + MessageBox.Show("Cannot download this file!"); + return; + } + var remotePath = IndexesToPaths[StaticFileListBox.SelectedIndex]; + if (remotePath == null) + return; + var text = Server.GetComponent().ReadText(remotePath, false, out string error, out bool unauthorized); + if (text != null) + { + var ofd = new SaveFileDialog() + { + CheckFileExists = false, + CheckPathExists = true, + DefaultExt = ".txt", + Title = "Static File Download", + ValidateNames = true, + Filter = "All files (*.*)|*.*", + AddExtension = false, + CreatePrompt = false, + OverwritePrompt = true, + SupportMultiDottedExtensions = true, + }; + if (ofd.ShowDialog() != DialogResult.OK) + return; + + try + { + File.WriteAllText(ofd.FileName, text); + return; + } + catch (Exception ex) + { + error = ex.ToString(); + } + } + MessageBox.Show("An error occurred: " + error); + } + + private void StaticFileDeleteButton_Click(object sender, EventArgs e) + { + if (MessageBox.Show("Are you sure you want to delete " + ((string)StaticFileListBox.SelectedItem).Trim() + "?", "Confirm", MessageBoxButtons.YesNo) != DialogResult.Yes) + return; + var res = Server.GetComponent().DeleteFile(IndexesToPaths[StaticFileListBox.SelectedIndex], out bool unauthorized); + if (res != null) + MessageBox.Show(res); + BuildFileList(); + } + + private void StaticFileCreateButton_Click(object sender, EventArgs e) + { + if(StaticFileEditTextbox.Text != "Directory") + { + MessageBox.Show("Please select a directory to create the file in."); + return; + } + var FileName = Program.TextPrompt("Static File/Directory Creation", "Enter the name of the file/directory:"); + if (FileName == null) + return; + + var resu = MessageBox.Show("Is this the name of a directory?", "Directory", MessageBoxButtons.YesNoCancel); + if (resu == DialogResult.Cancel) + return; + var FullFileName = Path.Combine(IndexesToPaths[StaticFileListBox.SelectedIndex], FileName); + if (resu == DialogResult.Yes) + FullFileName = Path.Combine(FullFileName, "__TGS3_CP_DIRECTORY_CREATOR__"); + var config = Server.GetComponent(); + var res = config.WriteText(FullFileName, "", out bool unauthorized); + if (res != null) + MessageBox.Show(res); + if (resu == DialogResult.Yes) + { + FullFileName = Path.Combine(FullFileName, "__TGS3_CP_DIRECTORY_CREATOR__"); + config.DeleteFile(FullFileName, out unauthorized); //don't care about this + } + BuildFileList(); + } + + private void StaticFileSaveButton_Click(object sender, EventArgs e) + { + var index = StaticFileListBox.SelectedIndex; + var res = Server.GetComponent().WriteText(IndexesToPaths[index], StaticFileEditTextbox.Text, out bool unauthorized); + if (res != null) + { + MessageBox.Show("Error: " + res); + var title = (string)StaticFileListBox.Items[index]; + if (unauthorized && !title.Contains(" (UNAUTHORIZED)")) + StaticFileListBox.Items[index] = title + " (UNAUTHORIZED)"; + } + } + + private void StaticFileListBox_SelectedIndexChanged(object sender, EventArgs e) + { + UpdateEditText(); + } + + void UpdateEditText() + { + var newIndex = StaticFileListBox.SelectedIndex; + var path = IndexesToPaths[newIndex]; + var title = (string)StaticFileListBox.Items[newIndex]; + if (path == "TGServiceInterface.dll") + { + StaticFileEditTextbox.ReadOnly = true; + StaticFileEditTextbox.Text = "/tg/station Server Interface DLL"; + } + else if (title[title.Length - 1] == '/') + { + StaticFileEditTextbox.ReadOnly = true; + StaticFileEditTextbox.Text = "Directory"; + } + else + { + var entry = Server.GetComponent().ReadText(path, false, out string error, out bool unauthorized); + if (entry == null) + { + StaticFileEditTextbox.ReadOnly = true; + StaticFileEditTextbox.Text = "ERROR: " + entry; + if (unauthorized && !title.Contains(" (UNAUTHORIZED)")) + StaticFileListBox.Items[newIndex] = title + " (UNAUTHORIZED)"; + } + StaticFileEditTextbox.ReadOnly = false; + StaticFileEditTextbox.Text = entry; + } + } + } +} diff --git a/TGControlPanel/TGControlPanel.csproj b/TGControlPanel/TGControlPanel.csproj index ff533121a0..976c556e7d 100644 --- a/TGControlPanel/TGControlPanel.csproj +++ b/TGControlPanel/TGControlPanel.csproj @@ -79,6 +79,9 @@ Form + + Form + Login.cs diff --git a/TGServerService/Config.cs b/TGServerService/Config.cs index 58a9d9d6f4..d7c5cc9511 100644 --- a/TGServerService/Config.cs +++ b/TGServerService/Config.cs @@ -26,7 +26,7 @@ namespace TGServerService { var configDir = repo ? RepoPath : StaticDirs; - var path = Path.Combine(configDir, staticRelativePath); + var path = configDir + '/' + staticRelativePath; //do not use path.combine or it will try and take the root lock (configLock) { var di1 = new DirectoryInfo(configDir); @@ -101,7 +101,7 @@ namespace TGServerService { try { - var path = Path.Combine(StaticDirs, staticRelativePath); + var path = StaticDirs + '/' + staticRelativePath; //do not use path.combine or it will try and take the root lock (configLock) { var di1 = new DirectoryInfo(StaticDirs); @@ -143,13 +143,75 @@ namespace TGServerService return e.ToString(); } } + [OperationBehavior(Impersonation = ImpersonationOption.Required)] + public string DeleteFile(string staticRelativePath, out bool unauthorized) + { + try + { + var path = StaticDirs + '/' + staticRelativePath; //do not use path.combine or it will try and take the root + lock (configLock) + { + var di1 = new DirectoryInfo(StaticDirs); + var fi = new FileInfo(path); + var di2 = new DirectoryInfo(fi.Directory.FullName); + + var good = false; + while (di2 != null) + { + if (di2.FullName == di1.FullName) + { + good = true; + break; + } + else di2 = di2.Parent; + } + + if (!good) + { + unauthorized = false; + return "Cannot delete above static directories!"; + } + + if (fi.Exists) + { + if (fi.Name == InterfaceDLLName) + { + unauthorized = false; + return "Cannot delete the interface DLL!"; + } + File.Delete(path); + } + else if (Directory.Exists(path)) + Program.DeleteDirectory(path); + unauthorized = false; + return null; + } + } + catch (UnauthorizedAccessException e) + { + //no need for the full stacktrace + unauthorized = true; + return e.Message; + } + catch (Exception e) + { + unauthorized = false; + return e.ToString(); + } + } [OperationBehavior(Impersonation = ImpersonationOption.Required)] public IList ListStaticDirectory(string subDir, out string error, out bool unauthorized) { try { - DirectoryInfo dirToEnum = new DirectoryInfo(Path.Combine(StaticDirs, subDir ?? "")); + if (!Directory.Exists(StaticDirs)) + { + error = null; + unauthorized = false; + return new List(); + } + DirectoryInfo dirToEnum = new DirectoryInfo(StaticDirs + '/' + subDir ?? ""); //do not use path.combine or it will try and take the root var result = new List(); foreach (var I in dirToEnum.GetFiles()) result.Add(I.Name); diff --git a/TGServiceInterface/Config.cs b/TGServiceInterface/Config.cs index 16a868304f..ff292180cf 100644 --- a/TGServiceInterface/Config.cs +++ b/TGServiceInterface/Config.cs @@ -30,7 +30,7 @@ namespace TGServiceInterface IList ListStaticDirectory(string subpath, out string error, out bool unauthorized); /// - /// For when you really just need to see the raw data of the config + /// Read from a static file /// /// The path from the Static dir. E.g. config/config.txt /// if true, the file will be read from the repository instead of the static dir @@ -41,13 +41,22 @@ namespace TGServiceInterface string ReadText(string staticRelativePath, bool repo, out string error, out bool unauthorized); /// - /// For when you really just need to set the raw data of the config + /// Write to a static file /// - /// The path from the configDir. E.g. config.txt + /// The path from the Static dir. E.g. config/config.txt /// The full text of the config file /// This will be true if error is set to a message that indicates the current user does not have access to the specified file /// null on success, error message on failure [OperationContract] string WriteText(string staticRelativePath, string data, out bool unauthorized); + + /// + /// Deletes the target static file + /// + /// The path from the Static dir. E.g. config/config.txt + /// This will be true if error is set to a message that indicates the current user does not have access to the specified file + /// null on success, error message on failure + [OperationContract] + string DeleteFile(string staticRelativePath, out bool unauthorized); } }