/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Net; using System.Security.Cryptography; using System.Text; using System.Web; using DotCMIS.Enums; using DotCMIS.Exceptions; using DotCMIS.Util; using System.Reflection; namespace DotCMIS.Binding.Impl { internal static class HttpUtils { public delegate void Output(Stream stream); public static Response InvokeGET(UrlBuilder url, BindingSession session) { return Invoke(url, "GET", null, null, session, null, null, null); } public static Response InvokeGET(UrlBuilder url, BindingSession session, long? offset, long? length) { return Invoke(url, "GET", null, null, session, offset, length, null); } public static Response InvokePOST(UrlBuilder url, String contentType, Output writer, BindingSession session) { return Invoke(url, "POST", contentType, writer, session, null, null, null); } public static Response InvokePUT(UrlBuilder url, String contentType, IDictionary headers, Output writer, BindingSession session) { return Invoke(url, "PUT", contentType, writer, session, null, null, headers); } public static Response InvokeDELETE(UrlBuilder url, BindingSession session) { return Invoke(url, "DELETE", null, null, session, null, null, null); } private static Response Invoke(UrlBuilder url, String method, String contentType, Output writer, BindingSession session, long? offset, long? length, IDictionary headers) { try { // log before connect if (DotCMISDebug.DotCMISSwitch.TraceInfo) { Trace.WriteLine(method + " " + url); } // create connection HttpWebRequest conn = (HttpWebRequest)WebRequest.Create(url.Url); conn.Method = method; conn.UserAgent = "Apache Chemistry DotCMIS"; // timeouts int connectTimeout = session.GetValue(SessionParameter.ConnectTimeout, -2); if (connectTimeout >= -1) { conn.Timeout = connectTimeout; } int readTimeout = session.GetValue(SessionParameter.ReadTimeout, -2); if (readTimeout >= -1) { conn.ReadWriteTimeout = readTimeout; } // set content type if (contentType != null) { conn.ContentType = contentType; } // set additional headers if (headers != null) { foreach (KeyValuePair header in headers) { conn.Headers.Add(header.Key, header.Value); } } // authenticate IAuthenticationProvider authProvider = session.GetAuthenticationProvider(); if (authProvider != null) { conn.PreAuthenticate = true; authProvider.Authenticate(conn); } // range if (offset != null && length != null) { if (offset < Int32.MaxValue && offset + length - 1 < Int32.MaxValue) { conn.AddRange((int)offset, (int)offset + (int)length - 1); } else { try { MethodInfo mi = conn.GetType().GetMethod("AddRange", new Type[] { typeof(Int64), typeof(Int64) }); mi.Invoke(conn, new object[] { offset, offset + length - 1 }); } catch (Exception e) { throw new CmisInvalidArgumentException("Offset or length too big!", e); } } } else if (offset != null) { if (offset < Int32.MaxValue) { conn.AddRange((int)offset); } else { try { MethodInfo mi = conn.GetType().GetMethod("AddRange", new Type[] { typeof(Int64) }); mi.Invoke(conn, new object[] { offset }); } catch (Exception e) { throw new CmisInvalidArgumentException("Offset too big!", e); } } } // compression string compressionFlag = session.GetValue(SessionParameter.Compression) as string; if (compressionFlag != null && compressionFlag.ToLower().Equals("true")) { conn.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; } // send data if (writer != null) { conn.SendChunked = true; Stream requestStream = conn.GetRequestStream(); writer(requestStream); requestStream.Close(); } else { #if __MonoCS__ //around for MONO HTTP DELETE issue //http://stackoverflow.com/questions/11785597/monotouch-iphone-call-to-httpwebrequest-getrequeststream-connects-to-server if (method == "DELETE") { conn.ContentLength = 0; Stream requestStream = conn.GetRequestStream(); requestStream.Close(); } #endif } // connect try { HttpWebResponse response = (HttpWebResponse)conn.GetResponse(); if (authProvider != null) { authProvider.HandleResponse(response); } return new Response(response); } catch (WebException we) { return new Response(we); } } catch (Exception e) { throw new CmisConnectionException("Cannot access " + url + ": " + e.Message, e); } } internal class Response { private readonly WebResponse response; public HttpStatusCode StatusCode { get; private set; } public string Message { get; private set; } public Stream Stream { get; private set; } public string ErrorContent { get; private set; } public string ContentType { get; private set; } public long? ContentLength { get; private set; } public Response(HttpWebResponse httpResponse) { this.response = httpResponse; StatusCode = httpResponse.StatusCode; Message = httpResponse.StatusDescription; ContentType = httpResponse.ContentType; ContentLength = httpResponse.ContentLength == -1 ? null : (long?)httpResponse.ContentLength; string contentTransferEncoding = httpResponse.Headers["Content-Transfer-Encoding"]; bool isBase64 = contentTransferEncoding != null && contentTransferEncoding.Equals("base64", StringComparison.CurrentCultureIgnoreCase); if (httpResponse.StatusCode == HttpStatusCode.OK || httpResponse.StatusCode == HttpStatusCode.Created || httpResponse.StatusCode == HttpStatusCode.NonAuthoritativeInformation || httpResponse.StatusCode == HttpStatusCode.PartialContent) { if (isBase64) { Stream = new BufferedStream(new CryptoStream(httpResponse.GetResponseStream(), new FromBase64Transform(), CryptoStreamMode.Read), 64 * 1024); } else { Stream = new BufferedStream(httpResponse.GetResponseStream(), 64 * 1024); } } else { try { httpResponse.Close(); } catch (Exception) { } } } public Response(WebException exception) { response = exception.Response; HttpWebResponse httpResponse = response as HttpWebResponse; if (httpResponse != null) { StatusCode = httpResponse.StatusCode; Message = httpResponse.StatusDescription; ContentType = httpResponse.ContentType; if (ContentType != null && ContentType.ToLower().StartsWith("text/")) { StringBuilder sb = new StringBuilder(); using (StreamReader sr = new StreamReader(httpResponse.GetResponseStream())) { string s; while ((s = sr.ReadLine()) != null) { sb.Append(s); sb.Append('\n'); } } ErrorContent = sb.ToString(); } } else { StatusCode = HttpStatusCode.InternalServerError; Message = exception.Status.ToString(); } try { response.Close(); } catch (Exception) { } } public void CloseStream() { if (Stream != null) { Stream.Close(); } } } } internal class UrlBuilder { private UriBuilder uri; public Uri Url { get { return uri.Uri; } } public UrlBuilder(string url) { if (url == null) { throw new ArgumentNullException("url"); } uri = new UriBuilder(url); } public UrlBuilder AddParameter(string name, object value) { if ((name == null) || (value == null)) { return this; } string valueStr = Uri.EscapeDataString(UrlBuilder.NormalizeParameter(value)); if (uri.Query != null && uri.Query.Length > 1) { uri.Query = uri.Query.Substring(1) + "&" + name + "=" + valueStr; } else { uri.Query = name + "=" + valueStr; } return this; } public static string NormalizeParameter(object value) { if (value == null) { return null; } else if (value is Enum) { return ((Enum)value).GetCmisValue(); } else if (value is bool) { return (bool)value ? "true" : "false"; } return value.ToString(); } public override string ToString() { return Url.ToString(); } } internal class MimeHelper { public const string ContentDisposition = "Content-Disposition"; public const string DispositionAttachment = "attachment"; public const string DispositionFilename = "filename"; private const string MIMESpecials = "()<>@,;:\\\"/[]?=" + "\t "; private const string RFC2231Specials = "*'%" + MIMESpecials; private static char[] HexDigits = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; public static string EncodeContentDisposition(string disposition, string filename) { if (disposition == null) { disposition = DispositionAttachment; } return disposition + EncodeRFC2231(DispositionFilename, filename); } protected static string EncodeRFC2231(string key, string value) { StringBuilder buf = new StringBuilder(); bool encoded = EncodeRFC2231value(value, buf); if (encoded) { return "; " + key + "*=" + buf.ToString(); } else { return "; " + key + "=" + value; } } protected static bool EncodeRFC2231value(string value, StringBuilder buf) { buf.Append("UTF-8"); buf.Append("''"); // no language byte[] bytes; try { bytes = UTF8Encoding.UTF8.GetBytes(value); } catch (Exception) { return true; } bool encoded = false; for (int i = 0; i < bytes.Length; i++) { int ch = bytes[i] & 0xff; if (ch <= 32 || ch >= 127 || RFC2231Specials.IndexOf((char)ch) != -1) { buf.Append('%'); buf.Append(HexDigits[ch >> 4]); buf.Append(HexDigits[ch & 0xf]); encoded = true; } else { buf.Append((char)ch); } } return encoded; } } }