/* | |
* 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; | |
} | |
} | |
} |