using System; using System.IO; using System.Net; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Web; using System.Web.Configuration; using System.Web.Script.Serialization; using System.Xml; using System.Xml.Linq; using OnlineEditorsExample; namespace ASC.Api.DocumentConverter { /// /// Class service api conversion /// public static class ServiceConverter { /// /// Static constructor /// static ServiceConverter() { DocumentConverterUrl = WebConfigurationManager.AppSettings["files.docservice.url.converter"] ?? ""; DocumentStorageUrl = WebConfigurationManager.AppSettings["files.docservice.url.storage"] ?? ""; Int32.TryParse(WebConfigurationManager.AppSettings["files.docservice.timeout"], out ConvertTimeout); ConvertTimeout = ConvertTimeout > 0 ? ConvertTimeout : 120000; } #region private fields /// /// Timeout to request conversion /// private static readonly int ConvertTimeout; /// /// Url to the service of conversion /// private static readonly string DocumentConverterUrl; /// /// Url to the service of storage /// private static readonly string DocumentStorageUrl; /// /// The parameters for the query conversion /// private const string ConvertParams = "?url={0}&outputtype={1}&filetype={2}&title={3}&key={4}&vkey={5}"; /// /// Number of tries request conversion /// private const int MaxTry = 3; #endregion #region public method /// /// The method is to convert the file to the required format /// /// Uri for the document to convert /// Document extension /// Extension to which to convert /// Key for caching on service /// Perform conversions asynchronously /// Uri to the converted document /// The percentage of completion of conversion /// /// string convertedDocumentUri; /// GetConvertedUri("http://helpcenter.onlyoffice.com/content/GettingStarted.pdf", ".pdf", ".docx", "http://helpcenter.onlyoffice.com/content/GettingStarted.pdf", false, out convertedDocumentUri); /// /// /// public static int GetConvertedUri(string documentUri, string fromExtension, string toExtension, string documentRevisionId, bool isAsync, out string convertedDocumentUri) { convertedDocumentUri = string.Empty; var responceFromConvertService = SendRequestToConvertService(documentUri, fromExtension, toExtension, documentRevisionId, isAsync) .Root; var errorElement = responceFromConvertService.Element("Error"); if (errorElement != null) ProcessConvertServiceResponceError(Convert.ToInt32(errorElement.Value)); var isEndConvert = Convert.ToBoolean(responceFromConvertService.Element("EndConvert").Value); var percent = Convert.ToInt32(responceFromConvertService.Element("Percent").Value); if (isEndConvert) { convertedDocumentUri = responceFromConvertService.Element("FileUrl").Value; percent = 100; } else { percent = percent >= 100 ? 99 : percent; } return percent; } /// /// Placing the document in the storage service /// /// Stream of document /// Length of stream /// Mime type /// Key for caching on service, whose used in editor /// Uri to document in the storage public static string GetExternalUri( Stream fileStream, long contentLength, string contentType, string documentRevisionId) { var validateKey = GenerateValidateKey(documentRevisionId, false); var urlDocumentService = DocumentStorageUrl + ConvertParams; var urlTostorage = String.Format(urlDocumentService, string.Empty, string.Empty, string.Empty, string.Empty, documentRevisionId, validateKey); var request = (HttpWebRequest)WebRequest.Create(urlTostorage); request.Method = "POST"; request.ContentType = contentType; request.ContentLength = contentLength; const int bufferSize = 2048; var buffer = new byte[bufferSize]; int readed; while ((readed = fileStream.Read(buffer, 0, bufferSize)) > 0) { request.GetRequestStream().Write(buffer, 0, readed); } // hack. http://ubuntuforums.org/showthread.php?t=1841740 if (_Default.IsMono) { ServicePointManager.ServerCertificateValidationCallback += (s, ce, ca, p) => true; } using (var response = request.GetResponse()) using (var stream = response.GetResponseStream()) { if (stream == null) throw new WebException("Could not get an answer"); var xDocumentResponse = XDocument.Load(new XmlTextReader(stream)); string externalUri; GetResponseUri(xDocumentResponse, out externalUri); return externalUri; } } /// /// Translation key to a supported form. /// /// Expected key /// Supported key public static string GenerateRevisionId(string expectedKey) { if (expectedKey.Length > 20) expectedKey = expectedKey.GetHashCode().ToString(); var key = Regex.Replace(expectedKey, "[^0-9-.a-zA-Z_=]", "_"); return key.Substring(key.Length - Math.Min(key.Length, 20)); } /// /// Generate validate key for editor by documentId /// /// Key for caching on service, whose used in editor /// Validation key /// LFJ7 or "http://helpcenter.onlyoffice.com/content/GettingStarted.pdf" public static string GenerateValidateKey(string documentRevisionId) { return GenerateValidateKey(documentRevisionId, true); } #endregion #region private method /// /// Generate validate key for editor by documentId /// /// Key for caching on service, whose used in editor /// Add host address to the key /// Validation key private static string GenerateValidateKey(string documentRevisionId, bool addHostForValidate) { if (string.IsNullOrEmpty(documentRevisionId)) return string.Empty; documentRevisionId = GenerateRevisionId(documentRevisionId); var keyId = GetKey(); var userCount = 0; object primaryKey = null; if (addHostForValidate) { string userIp = null; try { if (HttpContext.Current != null) { userIp = HttpContext.Current.Request.UserHostAddress; } } catch { } if (!string.IsNullOrEmpty(userIp)) primaryKey = new {expire = DateTime.UtcNow, key = documentRevisionId, key_id = keyId, user_count = userCount, ip = userIp}; } if (primaryKey == null) primaryKey = new {expire = DateTime.UtcNow, key = documentRevisionId, key_id = keyId, user_count = userCount}; return Signature.Create(primaryKey, GetSKey()); } private static object GetKey() { return WebConfigurationManager.AppSettings["files.docservice.tenantid"] ?? "OnlyOfficeAppsExample"; } private static string GetSKey() { return WebConfigurationManager.AppSettings["files.docservice.key"] ?? "ONLYOFFICE"; } /// /// Request for conversion to a service /// /// Uri for the document to convert /// Document extension /// Extension to which to convert /// Key for caching on service /// Perform conversions asynchronously /// Xml document request result of conversion private static XDocument SendRequestToConvertService(string documentUri, string fromExtension, string toExtension, string documentRevisionId, bool isAsync) { fromExtension = string.IsNullOrEmpty(fromExtension) ? Path.GetExtension(documentUri) : fromExtension; var title = Path.GetFileName(documentUri); title = string.IsNullOrEmpty(title) ? Guid.NewGuid().ToString() : title; documentRevisionId = string.IsNullOrEmpty(documentRevisionId) ? documentUri : documentRevisionId; documentRevisionId = GenerateRevisionId(documentRevisionId); var validateKey = GenerateValidateKey(documentRevisionId, false); var urlDocumentService = DocumentConverterUrl + ConvertParams; var urlToConverter = String.Format(urlDocumentService, HttpUtility.UrlEncode(documentUri), toExtension.Trim('.'), fromExtension.Trim('.'), title, documentRevisionId, validateKey); if (isAsync) urlToConverter += "&async=true"; var req = (HttpWebRequest) WebRequest.Create(urlToConverter); req.Timeout = ConvertTimeout; Stream stream = null; var countTry = 0; // hack. http://ubuntuforums.org/showthread.php?t=1841740 if (_Default.IsMono) { ServicePointManager.ServerCertificateValidationCallback += (s, ce, ca, p) => true; } while (countTry < MaxTry) { try { countTry++; stream = req.GetResponse().GetResponseStream(); break; } catch (WebException ex) { if (ex.Status != WebExceptionStatus.Timeout) { throw new HttpException((int) HttpStatusCode.BadRequest, "Bad Request", ex); } } } if (countTry == MaxTry) { throw new WebException("Timeout", WebExceptionStatus.Timeout); } return XDocument.Load(new XmlTextReader(stream)); } /// /// Generate an error code table /// /// Error code private static void ProcessConvertServiceResponceError(int errorCode) { var errorMessage = string.Empty; const string errorMessageTemplate = "Error occurred in the ConvertService.ashx: {0}"; switch (errorCode) { case -8: // public const int c_nErrorFileVKey = -8; errorMessage = String.Format(errorMessageTemplate, "Error document VKey"); break; case -7: // public const int c_nErrorFileRequest = -7; errorMessage = String.Format(errorMessageTemplate, "Error document request"); break; case -6: // public const int c_nErrorDatabase = -6; errorMessage = String.Format(errorMessageTemplate, "Error database"); break; case -5: // public const int c_nErrorUnexpectedGuid = -5; errorMessage = String.Format(errorMessageTemplate, "Error unexpected guid"); break; case -4: // public const int c_nErrorDownloadError = -4; errorMessage = String.Format(errorMessageTemplate, "Error download error"); break; case -3: // public const int c_nErrorConvertationError = -3; errorMessage = String.Format(errorMessageTemplate, "Error convertation error"); break; case -2: // public const int c_nErrorConvertationTimeout = -2; errorMessage = String.Format(errorMessageTemplate, "Error convertation timeout"); break; case -1: // public const int c_nErrorUnknown = -1; errorMessage = String.Format(errorMessageTemplate, "Error convertation unknown"); break; case 0: // public const int c_nErrorNo = 0; break; default: errorMessage = "ErrorCode = " + errorCode; break; } throw new Exception(errorMessage); } /// /// Processing document received from the editing service /// /// The resulting xml from editing service /// Uri to the converted document /// The percentage of completion of conversion private static int GetResponseUri(XDocument xDocumentResponse, out string responseUri) { var responceFromConvertService = xDocumentResponse.Root; if (responceFromConvertService == null) throw new WebException("Invalid answer format"); var errorElement = responceFromConvertService.Element("Error"); if (errorElement != null) ProcessConvertServiceResponceError(Convert.ToInt32(errorElement.Value)); var endConvert = responceFromConvertService.Element("EndConvert"); if (endConvert == null) throw new WebException("Invalid answer format"); var isEndConvert = Convert.ToBoolean(endConvert.Value); var resultPercent = 0; responseUri = string.Empty; if (isEndConvert) { var fileUrl = responceFromConvertService.Element("FileUrl"); if (fileUrl == null) throw new WebException("Invalid answer format"); responseUri = fileUrl.Value; resultPercent = 100; } else { var percent = responceFromConvertService.Element("Percent"); if (percent != null) resultPercent = Convert.ToInt32(percent.Value); resultPercent = resultPercent >= 100 ? 99 : resultPercent; } return resultPercent; } /// /// Class to encode a string /// internal static class Signature { /// /// Encoding string from object /// /// Type of object /// Object /// Secret key for encoding /// Encoding string public static string Create(T obj, string secret) { var serializer = new JavaScriptSerializer(); var str = serializer.Serialize(obj); var payload = GetHashBase64(str + secret) + "?" + str; return HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes(payload)); } /// /// String in base64 encoding /// /// String fo encoding /// Encoding string private static string GetHashBase64(string str) { return Convert.ToBase64String(SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(str))); } } #endregion } }