private WopiRequest IdentifyFileRequest(HttpRequest httpRequest, PathString path, string accessToken, Features features) { var fileSegment = path.Value.Substring("/wopi/files/".Length)?.Trim(); var method = httpRequest.Method; _logger?.LogTrace($"Extracted file segment from request path: '{fileSegment ?? "null"}' for method {method ?? "null"}"); if (string.IsNullOrWhiteSpace(fileSegment)) { return(WopiRequest.EMPTY); } // NB - Collabora have not implemented support for the X-WOPI-ItemVersion header and so the Version field set in the // CheckFileInfo response does not flow through to those endpoints where it is optional - eg GetFile. // This unfortunately means we have to do some crazy workaround using the fileId, and thus use that to derive // the relevant metadata needed for us to operate correctly. Hopefully this will prove to be just a temporary // workaround if (fileSegment.EndsWith("/contents")) { var fileId = fileSegment.Substring(0, fileSegment.Length - "/contents".Length)?.Trim() ?? "null"; _logger?.LogTrace($"Identified 'contents' request. File Id extracted from url is: '{fileId}'"); if (string.IsNullOrWhiteSpace(fileId)) { return(WopiRequest.EMPTY); } var fileVersion = httpRequest.Headers["X-WOPI-ItemVersion"].FirstOrDefault(); var file = File.FromId(fileId, fileVersion); if (0 == string.Compare("GET", method, StringComparison.OrdinalIgnoreCase)) { _logger?.LogTrace($"Identified this to be a Get File request"); var isEphemeralRedirect = bool.Parse(httpRequest.Query["ephemeral_redirect"].FirstOrDefault() ?? bool.FalseString); if (isEphemeralRedirect) { return(RedirectToFileStoreRequest.With(file, accessToken)); } return(GetFileWopiRequest.With(file, accessToken)); } else if (0 == string.Compare("POST", method, StringComparison.OrdinalIgnoreCase)) { _logger?.LogTrace($"Identified this to be a Save File request"); return(PostFileWopiRequest.With(file.Name, accessToken)); } else { _logger?.LogTrace($"The request method '{method}' is not supported for path '{path.Value ?? "null"}'"); } } else { var fileId = fileSegment; _logger?.LogTrace($"File Id extracted from url is: '{fileId}'. Attempting to identity request type"); if (0 == string.Compare("GET", method, StringComparison.OrdinalIgnoreCase)) { _logger?.LogTrace($"Identified this is a Check File Info request"); var file = (File)fileId; return(CheckFileInfoWopiRequest.With(file, accessToken, features)); } else { _logger?.LogTrace($"The request method '{method}' is not supported for path '{path.Value ?? "null"}"); } } return(WopiRequest.EMPTY); }
public async Task HandleAsync_FormsWOPICompliantResponseUsingFileMetadataAndUserContextAndFeatures(string title, string description, string groupName, string version, string owner, string fileName, string extension, ulong sizeInBytes, string contentHash) { var cancellationToken = new CancellationToken(); var services = new ServiceCollection(); var fileRepository = new Moq.Mock <IFileRepository>(); var fileRepositoryInvoked = false; services.AddScoped(sp => fileRepository.Object); var httpContext = new DefaultHttpContext { RequestServices = services.BuildServiceProvider() }; using var responseBodyStream = new MemoryStream(); httpContext.Response.Body = responseBodyStream; var fileVersion = Guid.NewGuid().ToString(); var fileMetadata = new FileMetadata( title: title, description: description, groupName: groupName, version: version, owner: owner, name: fileName, extension: extension, blobName: fileName, sizeInBytes: sizeInBytes, lastWriteTime: DateTimeOffset.UtcNow, contentHash: contentHash, fileStatus: FileStatus.Verified ); fileRepository. Setup(x => x.GetMetadataAsync(Moq.It.IsAny <FutureNHS.WOPIHost.File>(), Moq.It.IsAny <CancellationToken>())). Callback((FutureNHS.WOPIHost.File givenFile, CancellationToken givenCancellationToken) => { Assert.IsFalse(givenFile.IsEmpty); Assert.IsFalse(givenCancellationToken.IsCancellationRequested, "Expected the cancellation token to not be cancelled"); Assert.AreSame(fileName, givenFile.Name, "Expected the SUT to request the file from the repository whose name it was provided with"); Assert.AreSame(fileVersion, givenFile.Version, "Expected the SUT to request the file version from the repository that it was provided with"); Assert.AreEqual(cancellationToken, givenCancellationToken, "Expected the same cancellation token to propagate between service interfaces"); fileRepositoryInvoked = true; }). Returns(Task.FromResult(fileMetadata)); var ephemeralDownloadLink = new Uri("https://www.file-storage.com/files/file_id", UriKind.Absolute); fileRepository.Setup(x => x.GeneratePrivateEphemeralDownloadLink(fileMetadata, Moq.It.IsAny <CancellationToken>())).Returns(Task.FromResult(ephemeralDownloadLink)); var features = new Features(); var accessToken = Guid.NewGuid().ToString(); var file = FutureNHS.WOPIHost.File.With(fileName, fileVersion); var checkFileInfoWopiRequest = CheckFileInfoWopiRequest.With(file, accessToken, features); await checkFileInfoWopiRequest.HandleAsync(httpContext, cancellationToken); Assert.IsTrue(fileRepositoryInvoked); Assert.AreEqual("application/json", httpContext.Response.ContentType); Assert.AreSame(responseBodyStream, httpContext.Response.Body); responseBodyStream.Position = 0; dynamic responseBody = await JsonSerializer.DeserializeAsync <ExpandoObject>(responseBodyStream, cancellationToken : cancellationToken); Assert.IsNotNull(responseBody); Assert.AreEqual(fileMetadata.Title, ((JsonElement)(responseBody.BaseFileName)).GetString()); Assert.AreEqual(fileMetadata.Version, ((JsonElement)(responseBody.Version)).GetString()); Assert.AreEqual(fileMetadata.Owner, ((JsonElement)(responseBody.OwnerId)).GetString()); Assert.AreEqual(fileMetadata.Extension, ((JsonElement)(responseBody.FileExtension)).GetString()); Assert.AreEqual(fileMetadata.SizeInBytes, ((JsonElement)(responseBody.Size)).GetUInt64()); Assert.AreEqual(ephemeralDownloadLink.AbsoluteUri, ((JsonElement)(responseBody.FileUrl)).GetString()); Assert.AreEqual(fileMetadata.LastWriteTime.ToIso8601(), ((JsonElement)(responseBody.LastModifiedTime)).GetString()); Assert.AreEqual(FutureNHS.WOPIHost.File.FILENAME_MAXIMUM_LENGTH, ((JsonElement)(responseBody.FileNameMaxLength)).GetInt32()); }