blob: 1dc63eb64870e6f53b8753ed58a57b797c127951 [file] [log] [blame]
/*
* 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<string, string> 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<string, string> 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<string, string> 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;
}
}
}