From 2039e6971ccb9cfcd9f61736dcebe89375b36738 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Wed, 15 Aug 2018 15:49:16 -0400 Subject: [PATCH] Various fixups --- docs/API.dox | 30 +++++++++++++++++++ .../Components/Repository/Repository.cs | 8 +++++ .../Components/StaticFiles/Configuration.cs | 26 +++++----------- .../IO/ISynchronousIOManager.cs | 4 +-- .../IO/SynchronousIOManager.cs | 13 +++++--- 5 files changed, 56 insertions(+), 25 deletions(-) diff --git a/docs/API.dox b/docs/API.dox index 8c7e273186..ee17463c05 100644 --- a/docs/API.dox +++ b/docs/API.dox @@ -375,3 +375,33 @@ To get a specific compile job use: I GET "/DreamMaker/{CompileJobId}" => @ref Tgstation.Server.Api.Models.CompileJob */ + +@subsection api_config Static Files + +Static files can be both read and written. + +To get the contents of a static file directory use the following method + +I GET "/Config/List/" => Array of @ref Tgstation.Server.Api.Models.ConfigurationFile + +Where the path is formatted as: directory/inner_directory/more_directories + +If the path is empty, the root directory will be retrieved. The @ref Tgstation.Server.Api.Models.ConfigurationFile.Content and @ref Tgstation.Server.Api.Models.ConfigurationFile.LastReadHash fields will not be populated for this response + +If you do not have access to list the requested directory, a 403 response will be returned. If the path actually represents a file or the directory no longer exists a 410 response will be returned. + +To get the content of a static file use the following method + +I GET "/Config/File/" => @ref Tgstation.Server.Api.Models.ConfigurationFile + +410 will be returned if the path doesn't exist. @ref Tgstation.Server.Api.Models.ConfigurationFile.Content will only be populated if the path is a file the user has access to, otherwise the other fields will be populated appropriately. + +To create, write, and delete files use the following request + +I POST "/Config" @ref Tgstation.Server.Api.Models.ConfigurationFile => @ref Tgstation.Server.Api.Models.ConfigurationFile + +When creating a file, only @ref Tgstation.Server.Api.Models.ConfigurationFile.Path and @ref Tgstation.Server.Api.Models.ConfigurationFile.Content should be specified. Any necessary preceeding directories will be created if possible. + +If the file already exists, the @ref Tgstation.Server.Api.Models.ConfigurationFile.LastReadHash field must also be present with the last version recieved from the server for that file. If this does not match at the time of the request, 409 will be returned, indicating the file has changed since it was last viewed by the client. + +To delete a file set @ref Tgstation.Server.Api.Models.ConfigurationFile.Content to null in the request. If deleting a file leaves a directory empty, they too will be deleted. \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 4c9c6ce9cc..2879581610 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -372,6 +372,14 @@ namespace Tgstation.Server.Host.Components.Repository /// public async Task Sychronize(string username, string password, bool synchronizeTrackedBranch, CancellationToken cancellationToken) { + if (username == null && password == null) + return; + + if (username == null) + throw new ArgumentNullException(nameof(username)); + if (password == null) + throw new ArgumentNullException(nameof(password)); + var startHead = Head; if (!await eventConsumer.HandleEvent(EventType.RepoPreSynchronize, new List { ioMananger.ResolvePath(".") }, cancellationToken).ConfigureAwait(false)) diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs index 7560f42dda..260e3447fe 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs @@ -166,13 +166,9 @@ namespace Tgstation.Server.Host.Components.StaticFiles Path = ioManager.ConcatPath(path, x), })); } - catch (UnauthorizedAccessException) - { - result = null; - return; - } - catch (DirectoryNotFoundException) + catch (IOException e) { + logger.LogDebug("IOException while writing {0}: {1}", path, e); result = null; return; } @@ -222,7 +218,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles } catch (IOException e) { - logger.LogWarning("IOException while reading {0}: {1}", path, e); + logger.LogDebug("IOException while reading {0}: {1}", path, e); } catch (UnauthorizedAccessException) { @@ -299,30 +295,22 @@ namespace Tgstation.Server.Host.Components.StaticFiles lock (this) try { - var success = synchronousIOManager.WriteFileChecked(path, data, previousHash, cancellationToken); + var fileHash = previousHash; + var success = synchronousIOManager.WriteFileChecked(path, data, ref fileHash, cancellationToken); if (!success) return; - string sha1String = null; - if (data != null) - { - postWriteHandler.HandleWrite(path); -#pragma warning disable CA5350 // Do not use insecure cryptographic algorithm SHA1. - using (var sha1 = new SHA1Managed()) -#pragma warning restore CA5350 // Do not use insecure cryptographic algorithm SHA1. - sha1String = String.Join("", sha1.ComputeHash(data).Select(b => b.ToString("x2", CultureInfo.InvariantCulture))); - } result = new ConfigurationFile { Content = data, IsDirectory = false, - LastReadHash = sha1String, + LastReadHash = fileHash, AccessDenied = false, Path = configurationRelativePath }; } catch (IOException e) { - logger.LogWarning("IOException while writing {0}: {1}", path, e); + logger.LogDebug("IOException while writing {0}: {1}", path, e); } catch (UnauthorizedAccessException) { diff --git a/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs b/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs index 617bac069f..ae95ce9341 100644 --- a/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs @@ -36,10 +36,10 @@ namespace Tgstation.Server.Host.IO /// /// The path to the file to write /// The new contents of the file - /// If the file exists, this function only succeeds if this parameter matches the SHA-1 hash of the contents of the current file + /// The function only succeeds if this parameter matches the SHA-1 hash of the contents of the current file. Contains the SHA1 of the file on disk once the function returns /// The for the operation /// on success, if the operation failed due to not matching the file's contents - bool WriteFileChecked(string path, byte[] data, string previousSha1, CancellationToken cancellationToken); + bool WriteFileChecked(string path, byte[] data, ref string sha1InOut, CancellationToken cancellationToken); /// /// Checks if a given is a directory diff --git a/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs index 7f29f0b20f..4cd4b175a0 100644 --- a/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs @@ -48,7 +48,7 @@ namespace Tgstation.Server.Host.IO } /// - public bool WriteFileChecked(string path, byte[] data, string previousSha1, CancellationToken cancellationToken) + public bool WriteFileChecked(string path, byte[] data, ref string sha1InOut, CancellationToken cancellationToken) { if (path == null) throw new ArgumentNullException(nameof(path)); @@ -69,7 +69,7 @@ namespace Tgstation.Server.Host.IO file.CopyTo(readMs); originalBytes = readMs.ToArray(); } - if (originalBytes.Length != 0 && previousSha1 == null) + if (originalBytes.Length != 0 && sha1InOut == null) //no sha1? no write return false; @@ -78,9 +78,14 @@ namespace Tgstation.Server.Host.IO using (var sha1 = new SHA1Managed()) #pragma warning restore CA5350 // Do not use insecure cryptographic algorithm SHA1. { - var sha1String = originalBytes.Length != 0 ? String.Join("", sha1.ComputeHash(originalBytes).Select(b => b.ToString("x2", CultureInfo.InvariantCulture))) : null; - if (sha1String != previousSha1) + string GetSha1(byte[] dataToHash) => dataToHash.Length != 0 ? String.Join("", sha1.ComputeHash(dataToHash).Select(b => b.ToString("x2", CultureInfo.InvariantCulture))) : null; + var originalSha1 = GetSha1(originalBytes); + if (originalSha1 != sha1InOut) + { + sha1InOut = originalSha1; return false; + } + sha1InOut = GetSha1(data); } cancellationToken.ThrowIfCancellationRequested();