/// <summary> /// Processes a CheckFileInfo request /// </summary> /// <remarks> /// For full documentation on CheckFileInfo, see /// https://wopi.readthedocs.io/projects/wopirest/en/latest/files/CheckFileInfo.html /// </remarks> private void HandleCheckFileInfoRequest(HttpContext context, WopiRequest requestData) { if (!ValidateAccess(requestData, writeAccessRequired: false)) { ReturnInvalidToken(context.Response); return; } IFileStorage storage = new FTPFileStorage(); long size = storage.GetFileSize(requestData.Id); if (size == -1) { ReturnFileUnknown(context.Response); return; } DateTime?lastModifiedTime = storage.GetLastModifiedTime(requestData.Id); try { CheckFileInfoResponse responseData = new CheckFileInfoResponse() { // required CheckFileInfo properties BaseFileName = Path.GetFileName(requestData.Id), OwnerId = "documentOwnerId", Size = Convert.ToInt32(size), //Version = file.LastWriteTimeUtc.ToString("O" /* ISO 8601 DateTime format string */), // Using the file write time is an arbitrary choice. Version = Convert.ToDateTime((DateTime)lastModifiedTime).ToFileTimeUtc().ToString(), // optional CheckFileInfo properties BreadcrumbBrandName = "LocalStorage WOPI Host", //BreadcrumbFolderName = fileInfo.Directory != null ? fileInfo.Directory.Name : "", BreadcrumbFolderName = "", BreadcrumbDocName = Path.GetFileNameWithoutExtension(requestData.Id), UserFriendlyName = "A WOPI User", SupportsLocks = true, SupportsUpdate = true, UserCanNotWriteRelative = true, /* Because this host does not support PutRelativeFile */ ReadOnly = false, UserCanWrite = true }; string jsonString = JsonConvert.SerializeObject(responseData); context.Response.Write(jsonString); ReturnSuccess(context.Response); } catch (UnauthorizedAccessException) { ReturnFileUnknown(context.Response); } }
/// <summary> /// Processes a UnlockAndRelock request /// </summary> /// <remarks> /// For full documentation on UnlockAndRelock, see /// https://wopi.readthedocs.io/projects/wopirest/en/latest/files/UnlockAndRelock.html /// </remarks> private void HandleUnlockAndRelockRequest(HttpContext context, WopiRequest requestData) { if (!ValidateAccess(requestData, writeAccessRequired: true)) { ReturnInvalidToken(context.Response); return; } IFileStorage storage = new FTPFileStorage(); long size = storage.GetFileSize(requestData.Id); if (size == -1) { ReturnFileUnknown(context.Response); return; } string newLock = context.Request.Headers[WopiHeaders.Lock]; string oldLock = context.Request.Headers[WopiHeaders.OldLock]; lock (Locks) { LockInfo existingLock; if (TryGetLock(requestData.Id, out existingLock)) { if (existingLock.Lock == oldLock) { // There is a valid lock on the file and the existing lock matches the provided one // Replace the existing lock with the new one Locks[requestData.Id] = new LockInfo() { DateCreated = DateTime.UtcNow, Lock = newLock }; context.Response.Headers[WopiHeaders.OldLock] = newLock; ReturnSuccess(context.Response); } else { // The existing lock doesn't match the requested one. Return a lock mismatch error // along with the current lock ReturnLockMismatch(context.Response, existingLock.Lock); } } else { // The requested lock does not exist. That's also a lock mismatch error. ReturnLockMismatch(context.Response, reason: "File not locked"); } } }
/// <summary> /// Processes a Lock request /// </summary> /// <remarks> /// For full documentation on Lock, see /// https://wopi.readthedocs.io/projects/wopirest/en/latest/files/Lock.html /// </remarks> private void HandleLockRequest(HttpContext context, WopiRequest requestData) { if (!ValidateAccess(requestData, writeAccessRequired: true)) { ReturnInvalidToken(context.Response); return; } IFileStorage storage = new FTPFileStorage(); long size = storage.GetFileSize(requestData.Id); if (size == -1) { ReturnFileUnknown(context.Response); return; } string newLock = context.Request.Headers[WopiHeaders.Lock]; lock (Locks) { LockInfo existingLock; if (TryGetLock(requestData.Id, out existingLock)) { // There is a valid existing lock on the file // Regardless of whether the new lock matches the existing lock, this should be treated as a lock mismatch // per the documentation: https://wopi.readthedocs.io/projects/wopirest/en/latest/files/Lock.html // This is a fairly common case and shouldn't be tracked as an error. Office Online can store // information about a current session in the lock value and expects to conflict when there's // an existing session to join. ReturnLockMismatch(context.Response, existingLock.Lock); } else { // The file is not currently locked or the lock has already expired // Create and store new lock information // TODO: In a real implementation the lock should be stored in a persisted and shared system. Locks[requestData.Id] = new LockInfo() { DateCreated = DateTime.UtcNow, Lock = newLock }; // Return success ReturnSuccess(context.Response); } } }
/// <summary> /// Processes a GetFile request /// </summary> /// <remarks> /// For full documentation on GetFile, see /// https://wopi.readthedocs.io/projects/wopirest/en/latest/files/GetFile.html /// </remarks> private void HandleGetFileRequest(HttpContext context, WopiRequest requestData) { if (!ValidateAccess(requestData, writeAccessRequired: false)) { ReturnInvalidToken(context.Response); return; } IFileStorage storage = new FTPFileStorage(); Stream stream = storage.GetFile(requestData.Id); if (null == stream) { ReturnFileUnknown(context.Response); return; } try { int i = 0; List <byte> bytes = new List <byte>(); do { byte[] buffer = new byte[1024]; i = stream.Read(buffer, 0, 1024); if (i > 0) { byte[] data = new byte[i]; Array.Copy(buffer, data, i); bytes.AddRange(data); } }while (i > 0); context.Response.OutputStream.Write(bytes.ToArray(), 0, bytes.Count); ReturnSuccess(context.Response); } catch (UnauthorizedAccessException) { ReturnFileUnknown(context.Response); } catch (FileNotFoundException) { ReturnFileUnknown(context.Response); } }
/// <summary> /// Get the file names with link on it /// </summary> /// <returns>The file names with link</returns> public List <FileLink> GetFiles() { List <FileLink> files = new List <FileLink>(); IFileStorage storage = new FTPFileStorage(); List <string> fileNames = storage.GetFileNames(); foreach (string fileName in fileNames) { FileLink fileLink = new FileLink(); fileLink.Name = fileName; fileLink.Url = string.Format("http://{0}/wopiframe/Index/{1}", ConfigurationManager.AppSettings["WOPIServerName"], fileName); files.Add(fileLink); } return(files); }
/// <summary> /// Processes a PutFile request /// </summary> /// <remarks> /// For full documentation on PutFile, see /// https://wopi.readthedocs.io/projects/wopirest/en/latest/files/PutFile.html /// </remarks> private void HandlePutFileRequest(HttpContext context, WopiRequest requestData) { if (!ValidateAccess(requestData, writeAccessRequired: true)) { ReturnInvalidToken(context.Response); return; } IFileStorage storage = new FTPFileStorage(); long size = storage.GetFileSize(requestData.Id); if (size == -1) { ReturnFileUnknown(context.Response); return; } string newLock = context.Request.Headers[WopiHeaders.Lock]; LockInfo existingLock; bool hasExistingLock; lock (Locks) { hasExistingLock = TryGetLock(requestData.Id, out existingLock); } if (hasExistingLock && existingLock.Lock != newLock) { // lock mismatch/locked by another interface ReturnLockMismatch(context.Response, existingLock.Lock); return; } // The WOPI spec allows for a PutFile to succeed on a non-locked file if the file is currently zero bytes in length. // This allows for a more efficient Create New File flow that saves the Lock roundtrips. if (!hasExistingLock && size != 0) { // With no lock and a non-zero file, a PutFile could potentially result in data loss by clobbering // existing content. Therefore, return a lock mismatch error. ReturnLockMismatch(context.Response, reason: "PutFile on unlocked file with current size != 0"); } // Either the file has a valid lock that matches the lock in the request, or the file is unlocked // and is zero bytes. Either way, proceed with the PutFile. try { // TODO: Should be replaced with proper file save logic to a real storage system and ensures write atomicity int result = storage.UploadFile(requestData.Id, context.Request.InputStream); if (result != 0) { ReturnServerError(context.Response); return; } ReturnSuccess(context.Response); } catch (UnauthorizedAccessException) { ReturnFileUnknown(context.Response); } catch (IOException) { ReturnServerError(context.Response); } }