//------------------------------------------------------------------------------
// <copyright file="HttpWebRequest.cs" company="Microsoft">
//
// Copyright (c) 2002 Microsoft Corporation. All rights reserved.
//
// The use and distribution terms for this software are contained in the file
// named license.txt, which can be found in the root of this distribution.
// By using this software in any fashion, you are agreeing to be bound by the
// terms of this license.
//
// You must not remove this notice, or any other, from this software.
//
// </copyright>
//------------------------------------------------------------------------------
namespace System.Net {
using System.Collections;
using System.IO;
using System.Runtime.Serialization;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Globalization;
internal class AuthenticationState {
// true if we already attempted pre-authentication regardless if it has been
// 1) possible,
// 2) succesfull or
// 3) unsuccessfull
internal bool TriedPreAuth;
internal Authorization Authorization;
// used to request a special connection for NTLM
internal string UniqueGroupId;
// used to distinguish proxy auth from server auth
internal bool IsProxyAuth;
internal string AuthenticateHeader {
get {
return IsProxyAuth ? HttpKnownHeaderNames.ProxyAuthenticate : HttpKnownHeaderNames.WWWAuthenticate;
}
}
internal string AuthorizationHeader {
get {
return IsProxyAuth ? HttpKnownHeaderNames.ProxyAuthorization : HttpKnownHeaderNames.Authorization;
}
}
internal HttpStatusCode StatusCodeMatch {
get {
return IsProxyAuth ? HttpStatusCode.ProxyAuthenticationRequired : HttpStatusCode.Unauthorized;
}
}
internal AuthenticationState(bool isProxyAuth) {
IsProxyAuth = isProxyAuth;
}
internal void PrepareState(HttpWebRequest httpWebRequest) {
//
// we need to do this to handle proxies in the correct way before
// calling into the AuthenticationManager APIs
//
httpWebRequest.CurrentAuthenticationState = this;
if (IsProxyAuth) {
//
// Proxy Auth
//
httpWebRequest.ChallengedUri = httpWebRequest.ServicePoint.Address;
return;
}
//
// Server Auth
//
httpWebRequest.ChallengedUri = httpWebRequest._Uri;
}
internal void PreAuthIfNeeded(HttpWebRequest httpWebRequest, ICredentials authInfo) {
//
// attempt to do preauth, if needed
//
GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::PreAuthIfNeeded() TriedPreAuth:" + TriedPreAuth.ToString() + " authInfo:" + ValidationHelper.HashString(authInfo));
if (!TriedPreAuth) {
TriedPreAuth = true;
if (authInfo!=null) {
PrepareState(httpWebRequest);
Authorization preauth = null;
try {
preauth = AuthenticationManager.PreAuthenticate(httpWebRequest, authInfo);
GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::PreAuthIfNeeded() preauth:" + ValidationHelper.HashString(preauth));
if (preauth!=null && preauth.Message!=null) {
GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::PreAuthIfNeeded() setting TriedPreAuth to Complete:" + preauth.Complete.ToString());
UniqueGroupId = preauth.ConnectionGroupId;
httpWebRequest.Headers.Set(AuthorizationHeader, preauth.Message);
}
}
catch (Exception exception) {
GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::PreAuthIfNeeded() PreAuthenticate() returned exception:" + exception.Message);
}
}
}
}
//
// attempts to authenticate the request:
// returns true only if it succesfully called into the AuthenticationManager
// and got back a valid Authorization and succesfully set the appropriate auth headers
//
internal bool AttemptAuthenticate(HttpWebRequest httpWebRequest, ICredentials authInfo) {
//
// Check for previous authentication attempts or the presence of credentials
//
GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::AttemptAuthenticate() httpWebRequest#" + ValidationHelper.HashString(httpWebRequest) + " AuthorizationHeader:" + AuthorizationHeader.ToString());
if (Authorization!=null && Authorization.Complete) {
//
// here the design gets "dirty".
// if this is proxy auth, we might have been challenged by an external
// server as well. in this case we will have to clear our previous proxy
// auth state before we go any further. this will be broken if the handshake
// requires more than one dropped connection (which NTLM is a border case for,
// since it droppes the connection on the 1st challenge but not on the second)
//
GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::AttemptAuthenticate() Authorization!=null Authorization.Complete:" + Authorization.Complete.ToString());
if (IsProxyAuth) {
//
// so, we got passed a 407 but now we got a 401, the proxy probably
// dropped the connection on us so we need to reset our proxy handshake
// Consider: this should have been taken care by Update()
//
GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::AttemptAuthenticate() ProxyAuth cleaning up auth status");
ClearAuthReq(httpWebRequest);
}
return false;
}
if (authInfo==null) {
GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::AttemptAuthenticate() authInfo==null Authorization#" + ValidationHelper.HashString(Authorization));
return false;
}
httpWebRequest.Headers.Remove(AuthorizationHeader);
string challenge = httpWebRequest._HttpResponse.Headers[AuthenticateHeader];
if (challenge==null) {
//
// the server sent no challenge, but this might be the case
// in which we're succeeding an authorization handshake to
// a proxy while a handshake with the server is still in progress.
// if the handshake with the proxy is complete and we actually have
// a handshake with the server in progress we can send the authorization header for the server as well.
//
if (!IsProxyAuth && Authorization!=null && httpWebRequest.ProxyAuthenticationState.Authorization!=null) {
httpWebRequest.Headers.Set(AuthorizationHeader, Authorization.Message);
}
GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::AttemptAuthenticate() challenge==null Authorization#" + ValidationHelper.HashString(Authorization));
return false;
}
//
// if the AuthenticationManager throws on Authenticate,
// bubble up that Exception to the user
//
GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::AttemptAuthenticate() challenge:" + challenge);
PrepareState(httpWebRequest);
try {
Authorization = AuthenticationManager.Authenticate(challenge, httpWebRequest, authInfo);
}
catch (Exception exception) {
httpWebRequest.SetResponse(exception);
Authorization = null;
}
if (Authorization==null) {
GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::AttemptAuthenticate() Authorization==null");
return false;
}
if (Authorization.Message==null) {
GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::AttemptAuthenticate() Authorization.Message==null");
Authorization = null;
return false;
}
UniqueGroupId = Authorization.ConnectionGroupId;
GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::AttemptAuthenticate() AuthorizationHeader:" + AuthorizationHeader + " blob: " + Authorization.Message.Length + "bytes Complete:" + Authorization.Complete.ToString());
//
// resubmit request
//
try {
//
// a "bad" module could try sending bad characters in the HTTP headers.
// catch the exception from WebHeaderCollection.CheckBadChars()
// fail the auth process
// and return the exception to the user as InnerException
//
httpWebRequest.Headers.Set(AuthorizationHeader, Authorization.Message);
}
catch (Exception exception) {
httpWebRequest.SetResponse(exception);
Authorization = null;
return false;
}
return true;
}
internal void ClearAuthReq(HttpWebRequest httpWebRequest) {
//
// if we are authenticating and we're being redirected to
// another authentication space then remove the current
// authentication header
//
GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::ClearAuthReq() httpWebRequest#" + ValidationHelper.HashString(httpWebRequest) + " " + AuthorizationHeader.ToString() + ": " + ValidationHelper.ToString(httpWebRequest.Headers[AuthorizationHeader]));
TriedPreAuth = false;
Authorization = null;
UniqueGroupId = null;
httpWebRequest.Headers.Remove(AuthorizationHeader);
}
//
// gives the IAuthenticationModule a chance to update its internal state.
// do any necessary cleanup and update the Complete status of the associated Authroization.
//
internal void Update(HttpWebRequest httpWebRequest) {
//
// this is just a fix for redirection & kerberos.
// we need to close the Context and call ISC() again with the final
// blob returned from the server. to do this in general
// we would probably need to change the IAuthenticationMdule interface and
// add this Update() method. for now we just have it internally.
//
// actually this turns out to be quite handy for 2 more cases:
// NTLM auth: we need to clear the connection group after we suceed to prevent leakage.
// Digest auth: we need to support stale credentials, if we fail with a 401 and stale is true we need to retry.
//
GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::Update() httpWebRequest#" + ValidationHelper.HashString(httpWebRequest) + " Authorization#" + ValidationHelper.HashString(Authorization) + " ResponseStatusCode:" + httpWebRequest.ResponseStatusCode.ToString());
if (Authorization!=null) {
PrepareState(httpWebRequest);
ISessionAuthenticationModule myModule = Authorization.Module as ISessionAuthenticationModule;
if (myModule!=null) {
//
// the whole point here is to complete the Security Context. Sometimes, though,
// a bad cgi script or a bad server, could miss sending back the final blob.
// in this case we won't be able to complete the handshake, but we'll have to clean up anyway.
//
string challenge = httpWebRequest._HttpResponse.Headers[AuthenticateHeader];
GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::Update() Complete:" + Authorization.Complete.ToString() + " Authorization.Module:" + ValidationHelper.ToString(Authorization.Module) + " challenge:" + ValidationHelper.ToString(challenge));
//
// here the design gets "dirty".
// if this is server auth, we might have been challenged by the proxy
// server as well. in this case we will have to clear our server auth state,
// but this might not be the current httpWebRequest.CurrentAuthenticationState.
//
AuthenticationState savedAuthenticationState = httpWebRequest.CurrentAuthenticationState;
if (!IsProxyAuth) {
httpWebRequest.CurrentAuthenticationState = this;
}
if (!IsProxyAuth && httpWebRequest.ResponseStatusCode==HttpStatusCode.ProxyAuthenticationRequired) {
//
// don't call Update on the module, since there's an ongoing
// handshake and we don't need to update any state in such a case
//
GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::Update() skipping call to " + myModule.ToString() + ".Update() since we need to reauthenticate with the proxy");
}
else {
//
// Update might fail, here we just make sure we catch anything
// that is happening in the module so we don't hang internally.
//
bool complete = true;
try {
complete = myModule.Update(challenge, httpWebRequest);
GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::Update() " + myModule.ToString() + ".Update() returned complete:" + complete.ToString());
}
catch (Exception exception) {
GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::Update() " + myModule.ToString() + ".Update() caught exception:" + exception.Message);
}
Authorization.SetComplete(complete);
}
if (!IsProxyAuth) {
httpWebRequest.CurrentAuthenticationState = savedAuthenticationState;
}
}
//
// If authentication was successful, create binding between
// the request and the authorization for future preauthentication
//
if (Authorization.Complete && httpWebRequest.ResponseStatusCode!=StatusCodeMatch) {
GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::Update() handshake is Complete calling BindModule()");
AuthenticationManager.BindModule(httpWebRequest, Authorization);
}
}
}
}
//
// HttpWebRequest - perform the major body of HTTP request processing. Handles
// everything between issuing the HTTP header request to parsing the
// the HTTP response. At that point, we hand off the request to the response
// object, where the programmer can query for headers or continue reading, usw.
//
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest"]/*' />
/// <devdoc>
/// <para><see cref='System.Net.HttpWebRequest'/> is an HTTP-specific implementation of the <see cref='System.Net.WebRequest'/>
/// class.</para>
/// </devdoc>
[Serializable]
public class HttpWebRequest : WebRequest, ISerializable {
// Read and Write async results
private LazyAsyncResult _WriteAResult;
private LazyAsyncResult _ReadAResult;
// Delegate that can be called on Continue Response
private HttpContinueDelegate _ContinueDelegate;
// Link back to the server point used for this request.
private ServicePoint _ServicePoint;
// Auto Event used to control access to the GetResponse,
// to indicate a ready response
// Auto Event used to indicate we allowed to write data
private AutoResetEvent _WriteEvent;
private bool _HaveResponse;
// set to true if this request failed during a connect
private bool _OnceFailed;
// set to true if this request has/will notify users callback of Write
private bool _WriteNotifed;
// this is generated by SetResponse
internal HttpWebResponse _HttpResponse;
// generic exception created by SetResponse from Connection failures or other internal failures
private Exception _ResponseException;
// set by Connection code upon completion
private object _CoreResponse;
private const int RequestLineConstantSize = 12;
private const int RequestDefaultTime = 2 * (60) * 1000; // 2 mins
// Size of ' HTTP/x.x\r\n'
// which are the
// unchanging pieces of the
// request line.
private static readonly byte[] HttpBytes = new byte[]{(byte)'H', (byte)'T', (byte)'T', (byte)'P', (byte)'/'};
internal static WaitOrTimerCallback m_EndWriteHeaders_Part2Callback = new WaitOrTimerCallback(EndWriteHeaders_Part2);
// request values
private string _Verb;
// the actual verb set by caller or default
private string _OriginVerb;
// our HTTP header response, request, parsing and storage objects
private WebHeaderCollection _HttpRequestHeaders;
// send buffer for output request with headers.
private byte[] _WriteBuffer;
// Property to set whether writes can be handled
private HttpWriteMode _HttpWriteMode;
// is autoredirect ?
private bool _AllowAutoRedirect = true;
// the host, port, and path
internal Uri _Uri;
// the origin Uri host, port and path that never changes
private Uri _OriginUri;
// the Uri of the host we're authenticating (proxy/server)
// used to match entries in the CredentialCache
private Uri _ChallengedUri;
// for which response ContentType we will look for and parse the CharacterSet
private string _MediaType;
// content length
private long _ContentLength;
// version of HTTP
private Version _Version;
// proxy or server HTTP version
private Version _DestinationVersion;
// proxy that we are using...
private bool _ProxyUserSet;
internal IWebProxy _Proxy;
private string _ConnectionGroupName;
private bool _PreAuthenticate;
private AuthenticationState _ProxyAuthenticationState;
private AuthenticationState _ServerAuthenticationState;
private AuthenticationState _CurrentAuthenticationState;
private ICredentials _AuthInfo;
private bool _RequestSubmitted;
private HttpAbortDelegate _AbortDelegate;
private bool _Abort;
private bool _KeepAlive;
private bool _Pipelined;
//
// used to prevent Write Buffering,
// used otherwise for reposting POST, and PUTs in redirects
//
private ConnectStream _SubmitWriteStream;
private ConnectStream _OldSubmitWriteStream;
private bool _AllowWriteStreamBuffering;
private bool _WriteStreamRetrieved;
private int _MaximumAllowedRedirections;
private int _AutoRedirects;
private bool _Saw100Continue;
// true when we are waiting on continue
private bool _AcceptContinue;
// true when we got a continue from server
// private bool _AckContinue;
//
// generic version of _AutoRedirects above
// used to count the number of requests made off this WebRequest
//
private int _RerequestCount;
//
// Timeout in milliseconds, if a synchronous request takes longer
// than timeout, a WebException is thrown
//
private int _Timeout;
// time delay that we would wait for continue
private const int DefaultContinueTimeout = 350; //ms
// size of post data, that needs to be greater, before we wait for a continue response
// private static int DefaultRequireWaitForContinueSize = 2048; // bytes
private CookieContainer _CookieContainer = null;
//
// Properties
//
//
// if the 100 Continue comes in around 350ms there might be race conditions
// that make us set Understands100Continue to true when parsing the 100 response
// in the Connection object and later make us set it to false if the
// thread that is waiting comes in later than 350ms. to solve this we save info
// on wether we did actually see the 100 continue. In that case, even if there's a
// timeout, we won't set it to false.
//
internal bool Saw100Continue {
get {
return _Saw100Continue;
}
set {
_Saw100Continue = value;
}
}
internal bool UsesProxy {
get {
return ServicePoint.InternalProxyServicePoint;
}
}
internal HttpStatusCode ResponseStatusCode {
get {
return _HttpResponse.StatusCode;
}
}
internal bool UsesProxySemantics {
get {
bool tunnelRequest = this is HttpProxyTunnelRequest;
return ServicePoint.InternalProxyServicePoint && ((_Uri.Scheme != Uri.UriSchemeHttps) || tunnelRequest);
}
}
internal Uri ChallengedUri {
get {
return _ChallengedUri;
}
set {
_ChallengedUri = value;
}
}
internal AuthenticationState ProxyAuthenticationState {
get {
return _ProxyAuthenticationState;
}
set {
_ProxyAuthenticationState = value;
}
}
internal AuthenticationState ServerAuthenticationState {
get {
return _ServerAuthenticationState;
}
set {
_ServerAuthenticationState = value;
}
}
// the AuthenticationState we're using for authentication (proxy/server)
// used to match entries in the Hashtable in NtlmClient & NegotiateClient
internal AuthenticationState CurrentAuthenticationState {
get {
return _CurrentAuthenticationState;
}
set {
_CurrentAuthenticationState = value;
}
}
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.CookieContainer"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public CookieContainer CookieContainer {
get {
return _CookieContainer;
}
set {
_CookieContainer = value;
}
}
/*
Accessor: RequestUri
This read-only propery returns the Uri for this request. The
Uri object was created by the constructor and is always non-null.
Note that it will always be the base Uri, and any redirects,
or such will not be indicated.
Input:
Returns: The Uri object representing the Uri.
*/
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.RequestUri"]/*' />
/// <devdoc>
/// <para>
/// Gets the original Uri of the request.
/// </para>
/// </devdoc>
public override Uri RequestUri { // read-only
get {
return _OriginUri;
}
}
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.AllowWriteStreamBuffering"]/*' />
/// <devdoc>
/// <para>
/// Enables or disables buffering the data stream sent to the server.
/// </para>
/// </devdoc>
public bool AllowWriteStreamBuffering {
get {
return _AllowWriteStreamBuffering;
}
set {
_AllowWriteStreamBuffering = value;
}
}
/*
Accessor: ContentLength
The property that controls the Content-Length of the request entity
body. Getting this property returns the last value set, or -1 if
no value has been set. Setting it sets the content length, and
the application must write that much data to the stream. This property
interacts with SendChunked.
Input:
Content length
Returns: The value of the content length on get.
*/
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.ContentLength"]/*' />
/// <devdoc>
/// <para>
/// Gets or sets the Content-Length header of the request.
/// </para>
/// </devdoc>
public override long ContentLength {
get {
return _ContentLength;
}
set {
if (_RequestSubmitted) {
throw new InvalidOperationException(SR.GetString(SR.net_writestarted));
}
if (value < 0) {
throw new ArgumentOutOfRangeException(SR.GetString(SR.net_clsmall));
}
SetContentLength(value);
}
}
internal void SetContentLength(long contentLength) {
_ContentLength = contentLength;
_HttpWriteMode = HttpWriteMode.Write;
}
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.Timeout"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
//UEUE changed default from infinite to 100 seconds
public override int Timeout {
get {
return _Timeout;
}
set {
if (value<0 && value!=System.Threading.Timeout.Infinite) {
throw new ArgumentOutOfRangeException("value");
}
_Timeout = value;
}
}
//
// CoreResponse - set by Connection code upon completion
//
internal object CoreResponse {
get {
return _CoreResponse;
}
set {
_CoreResponse = value;
}
}
/*
Accessor: BeginGetRequestStream
Retreives the Request Stream from an HTTP Request uses an Async
operation to do this, and the result is retrived async.
Async operations include work in progess, this call is used to retrieve
results by pushing the async operations to async worker thread on the callback.
There are many open issues involved here including the handling of possible blocking
within the bounds of the async worker thread or the case of Write and Read stream
operations still blocking.
Input:
Returns: The Async Result
*/
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.BeginGetRequestStream"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public override IAsyncResult BeginGetRequestStream(AsyncCallback callback, object state) {
GlobalLog.Enter("HttpWebRequest#" + ValidationHelper.HashString(this) + "::BeginGetRequestStream");
if (!CanGetRequestStream) {
throw new ProtocolViolationException(SR.GetString(SR.net_nouploadonget));
}
// prevent someone from trying to send data without setting
// a ContentLength or SendChunked when buffering is disabled and on a KeepAlive connection
if (MissingEntityBodyDelimiter) {
throw new ProtocolViolationException(SR.GetString(SR.net_contentlengthmissing));
}
// prevent someone from setting a Transfer Encoding without having SendChunked=true
if (TransferEncodingWithoutChunked) {
throw new InvalidOperationException(SR.GetString(SR.net_needchunked));
}
// used to make sure the user issues a GetRequestStream before GetResponse
_WriteStreamRetrieved = true;
Monitor.Enter(this);
if (_WriteAResult != null) {
Monitor.Exit(this);
throw new InvalidOperationException(SR.GetString(SR.net_repcall));
}
// get async going
LazyAsyncResult asyncResult =
new LazyAsyncResult(
this,
state,
callback );
_WriteAResult = asyncResult;
Monitor.Exit(this);
//
// See if we're already submitted a request.
//
if (_RequestSubmitted) {
// We already have. Make sure we have a write stream.
if (_SubmitWriteStream != null) {
// It was, just return the stream.
try {
asyncResult.InvokeCallback(true, _SubmitWriteStream);
}
catch {
Abort();
throw;
}
goto done;
}
else {
//
// No write stream, this is an application error.
//
if (_ResponseException!=null) {
//
// somebody already aborted for some reason
// rethrow for the same reason
//
throw _ResponseException;
}
else {
throw new InvalidOperationException(SR.GetString(SR.net_reqsubmitted));
}
}
}
// OK, we haven't submitted the request yet, so do so
// now.
//
// prevent new requests when low on resources
if (System.Net.Connection.IsThreadPoolLow()) {
throw new InvalidOperationException(SR.GetString(SR.net_needmorethreads));
}
if (_HttpWriteMode == HttpWriteMode.None) {
_HttpWriteMode = HttpWriteMode.Write;
}
// save off verb from origin Verb
GlobalLog.Print("HttpWebRequest#"+ValidationHelper.HashString(this)+": setting _Verb to _OriginVerb ["+_OriginVerb+"]");
_Verb = _OriginVerb;
BeginSubmitRequest(false);
done:
GlobalLog.Leave("HttpWebRequest#" + ValidationHelper.HashString(this) + "::BeginGetRequestStream", ValidationHelper.HashString(asyncResult));
return asyncResult;
}
/*
Accessor: EndGetRequestStream
Retreives the Request Stream after an Async operation has completed
Input:
Returns: The Async Stream
*/
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.EndGetRequestStream"]/*' />
/// <devdoc>
/// </devdoc>
public override Stream EndGetRequestStream(IAsyncResult asyncResult) {
GlobalLog.Enter("HttpWebRequest#" + ValidationHelper.HashString(this) + "::EndGetRequestStream", ValidationHelper.HashString(asyncResult));
//
// parameter validation
//
if (asyncResult == null) {
throw new ArgumentNullException("asyncResult");
}
LazyAsyncResult castedAsyncResult = asyncResult as LazyAsyncResult;
if (castedAsyncResult==null || castedAsyncResult.AsyncObject!=this) {
throw new ArgumentException(SR.GetString(SR.net_io_invalidasyncresult));
}
if (castedAsyncResult.EndCalled) {
throw new InvalidOperationException(SR.GetString(SR.net_io_invalidendcall, "EndGetRequestStream"));
}
ConnectStream connectStream = castedAsyncResult.InternalWaitForCompletion() as ConnectStream;
castedAsyncResult.EndCalled = true;
CheckFinalStatus();
if (connectStream==null) {
//
// OK, something happened on the request. We can't return
// a stream here, so for now we'll throw an exception.
//
GlobalLog.Print("HttpWebRequest#" + ValidationHelper.HashString(this) + "::EndGetRequestStream() throwing connectStream==null");
if (_ResponseException!=null) {
//
// somebody already aborted for some reason
// rethrow for the same reason
//
throw _ResponseException;
}
else {
throw new InvalidOperationException(SR.GetString(SR.net_reqsubmitted));
}
}
// Otherwise it worked, so return the HttpWebResponse.
GlobalLog.Leave("HttpWebRequest#" + ValidationHelper.HashString(this) + "::EndGetRequestStream", ValidationHelper.HashString(connectStream));
return connectStream;
}
/*
Accessor: GetRequestStream
The request stream property. This property returns a stream that the
calling application can write on. Getting this property may cause the
request to be sent, if it wasn't already. Getting this property after
a request has been sent that doesn't have an entity body causes an
exception to be thrown. This property is not settable.
Input:
Returns: A stream on which the client can write.
*/
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.GetRequestStream"]/*' />
/// <devdoc>
/// <para>Gets a <see cref='System.IO.Stream'/> that the application can use to write request
/// data.</para>
/// </devdoc>
public override Stream GetRequestStream() {
if (_SubmitWriteStream != null) {
return _SubmitWriteStream;
}
IAsyncResult asyncResult = BeginGetRequestStream(null, null);
if (_Timeout!=System.Threading.Timeout.Infinite && !asyncResult.IsCompleted) {
asyncResult.AsyncWaitHandle.WaitOne(_Timeout, false);
if (!asyncResult.IsCompleted) {
Abort();
throw new WebException(SR.GetString(SR.net_timeout), WebExceptionStatus.Timeout);
}
}
return EndGetRequestStream(asyncResult);
}
//
// This read-only propery does a test against the object to verify that
// we're not sending data with a GET or HEAD, these are dissallowed by the HTTP spec.
// Returns: true if we allow sending data for this request, false otherwise
//
private bool CanGetRequestStream {
get {
bool result = !KnownVerbs.GetHttpVerbType(_OriginVerb).m_ContentBodyNotAllowed;
GlobalLog.Print("CanGetRequestStream("+_OriginVerb+"): " + result);
return result;
}
}
//
// This read-only propery does a test against the object to verify if
// we're allowed to get a Response Stream to read,
// this is dissallowed per the HTTP spec for a HEAD request.
// Returns: true if we allow sending data for this request, false otherwise
//
internal bool CanGetResponseStream {
get {
bool result = !KnownVerbs.GetHttpVerbType(_Verb).m_ExpectNoContentResponse;
GlobalLog.Print("CanGetResponseStream("+_Verb+"): " + result);
return result;
}
}
//
// This read-only propery describes whether we can
// send this verb without content data.
// Assumes Method is already set.
// Returns: true, if we must send some kind of content
//
internal bool RequireBody {
get {
bool result = KnownVerbs.GetHttpVerbType(_Verb).m_RequireContentBody;
GlobalLog.Print("RequireBody("+_Verb+"): " + result);
return result;
}
}
private bool MissingEntityBodyDelimiter {
get {
return KeepAlive && RequireBody && ContentLength==-1 && !SendChunked && !AllowWriteStreamBuffering;
}
}
private bool TransferEncodingWithoutChunked {
get {
return !SendChunked && !ValidationHelper.IsBlankString(TransferEncoding);
}
}
private bool ChunkedUploadOnHttp10 {
get {
return SendChunked && _DestinationVersion!=null && _DestinationVersion.Equals(HttpVersion.Version10);
}
}
/*++
CheckBuffering - Determine if we need buffering based on having no contentlength
consider the case in which we have no entity body delimiters:
RequireBody && ContentLength==-1 && !SendChunked == true
now we need to consider 3 cases:
AllowWriteStreamBuffering (1)
- buffer internally all the data written to the request stream when the user
closes the request stream send it on the wire with a ContentLength
!AllowWriteStreamBuffering
- can't buffer internally all the data written to the request stream
so the data MUST go to the wire and the server sees it: 2 cases
!KeepAlive (2)
- send a "Connection: close" header to the server. the server SHOULD
1) send a final response
2) read all the data on the connection and treat it as being part of the
entity body of this request.
KeepAlive (3)
- throw, we can't do this, 'cause the server wouldn't know when the data is over.
Input:
None.
Returns:
true if we need buffering, false otherwise.
--*/
internal bool CheckBuffering {
//
// ContentLength is not set, and user is not chunking, buffering is on
// so force the code into writing (give the user a stream to write to)
// and we'll buffer for him
//
get {
bool checkBuffering =
RequireBody &&
ContentLength==-1 &&
!SendChunked &&
AllowWriteStreamBuffering &&
(_DestinationVersion==null || _DestinationVersion.CompareTo(HttpVersion.Version11)<0);
GlobalLog.Print("HttpWebRequest#" + ValidationHelper.HashString(this) + "::CheckBuffering() returns:" + checkBuffering.ToString());
return checkBuffering;
}
}
// This is a notify from the connection ReadCallback about
// - error response received and
// - the KeepAlive status agreed by both sides
internal void ErrorStatusCodeNotify(Connection connection, bool isKeepAlive) {
GlobalLog.Print("ErrorStatusCodeNotify:: Connection has reported Error Response Status");
ConnectStream submitStream = this._SubmitWriteStream;
if (submitStream != null && submitStream.Connection == connection) {
submitStream.ErrorResponseNotify(isKeepAlive);
}
else {
GlobalLog.Print("ErrorStatusCodeNotify:: IGNORE connection is not used");
}
}
/*
Method: DoSubmitRequestProcessing
Does internal processing of redirect and request retries for authentication
Assumes that it cannot block, this returns a state var indicating when it
needs to block
Assumes that we are never called with a null response
Input:
none
Returns:
HttpProcessingResult -
*/
private HttpProcessingResult DoSubmitRequestProcessing() {
GlobalLog.Enter("HttpWebRequest#" + ValidationHelper.HashString(this) + "::DoSubmitRequestProcessing");
//
// OK, the submit request worked. Loop, waiting for a
// valid response.
//
HttpProcessingResult result = HttpProcessingResult.Continue;
GlobalLog.Assert(_HttpResponse != null, "_HttpResponse==null", "HttpWebRequest::DoSubmitRequestProcessing() should never be called with null response");
_ProxyAuthenticationState.Update(this);
_ServerAuthenticationState.Update(this);
//
// We have a response of some sort, see if we need to resubmit
// it do to authentication, redirection or something
// else, then handle clearing out state and draining out old response.
//
if (CheckResubmit()) {
//
// we handle upload redirects and auth,
// now resubmit request
//
ClearRequestForResubmit();
BeginSubmitRequest(true);
result = HttpProcessingResult.WriteWait;
}
GlobalLog.Leave("HttpWebRequest#" + ValidationHelper.HashString(this) + "::DoSubmitRequestProcessing", result.ToString());
return result;
}
/*
Accessor: BeginGetResponse
Used to query for the Response of an HTTP Request using Async
Input:
Returns: The Async Result
*/
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.BeginGetResponse"]/*' />
/// <devdoc>
/// </devdoc>
public override IAsyncResult BeginGetResponse(AsyncCallback callback, object state) {
GlobalLog.Enter("HttpWebRequest#" + ValidationHelper.HashString(this) + "::BeginGetResponse");
// prevent someone from setting ContentLength/Chunked, and the doing a Get
if (_HttpWriteMode!=HttpWriteMode.None && !CanGetRequestStream) {
throw new ProtocolViolationException(SR.GetString(SR.net_nocontentlengthonget));
}
// prevent someone from trying to send data without setting
// a ContentLength or SendChunked when buffering is disabled and on a KeepAlive connection
if (MissingEntityBodyDelimiter) {
throw new ProtocolViolationException(SR.GetString(SR.net_contentlengthmissing));
}
// prevent someone from setting a Transfer Encoding without having SendChunked=true
if (TransferEncodingWithoutChunked) {
throw new InvalidOperationException(SR.GetString(SR.net_needchunked));
}
Monitor.Enter(this);
if (_ReadAResult != null) {
Monitor.Exit(this);
throw new InvalidOperationException(SR.GetString(SR.net_repcall));
}
// Set the async object, so we can save it for the callback
//
// We do this in an interlocked fashion. The checking and setting of
// _ReadAResult must be interlocked with the check of _HaveResponse
// to avoid a race condition with our async receive thread.
//
// get async going
//
// prevent new requests when low on resources
if (System.Net.Connection.IsThreadPoolLow()) {
Monitor.Exit(this);
throw new InvalidOperationException(SR.GetString(SR.net_needmorethreads));
}
LazyAsyncResult asyncResult =
new LazyAsyncResult(
this,
state,
callback );
_ReadAResult = asyncResult;
// See if we already have a response. If we have, we can
// just return it.
if (_HaveResponse) {
Monitor.Exit(this);
// Already have a response, so return it if it's valid.
if (_HttpResponse != null) {
try {
asyncResult.InvokeCallback(true, _HttpResponse);
}
catch {
Abort();
throw;
}
goto done;
}
else {
throw _ResponseException;
}
}
else {
Monitor.Exit(this);
}
// If we're here it's because we don't have the response yet. We may have
// already submitted the request, but if not do so now.
if (!_RequestSubmitted) {
// Save Off verb, and use it to make the request
GlobalLog.Print("HttpWebRequest#"+ValidationHelper.HashString(this)+": setting _Verb to _OriginVerb ["+_OriginVerb+"]");
_Verb = _OriginVerb;
// Start request, but don't block
BeginSubmitRequest(false);
}
done:
GlobalLog.Leave("HttpWebRequest#" + ValidationHelper.HashString(this) + "::BeginGetResponse", ValidationHelper.HashString(asyncResult));
return asyncResult;
}
/*
Accessor: EndGetResponse
Retreives the Response Result from an HTTP Result after an Async operation
has completed
Input:
Returns: The Response Result
*/
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.EndGetResponse"]/*' />
/// <devdoc>
/// </devdoc>
public override WebResponse EndGetResponse(IAsyncResult asyncResult) {
GlobalLog.Enter("HttpWebRequest#" + ValidationHelper.HashString(this) + "::EndGetResponse", ValidationHelper.HashString(asyncResult));
//
// parameter validation
//
if (asyncResult==null) {
throw new ArgumentNullException("asyncResult");
}
LazyAsyncResult castedAsyncResult = asyncResult as LazyAsyncResult;
if (castedAsyncResult==null || castedAsyncResult.AsyncObject!=this) {
throw new ArgumentException(SR.GetString(SR.net_io_invalidasyncresult));
}
if (castedAsyncResult.EndCalled) {
throw new InvalidOperationException(SR.GetString(SR.net_io_invalidendcall, "EndGetResponse"));
}
//
// Make sure that the caller has already gotten the request stream if
// this is a write operation that requires data. Note that we can't
// make sure he's written enough data, because he might still be
// writing data on another thread.
//
if (_HttpWriteMode != HttpWriteMode.None && !_WriteStreamRetrieved) {
// It's a write request and he hasn't retrieved the stream yet.
// If this is a chunked request or a request with a C-L greater
// than zero, this is an error
if (_HttpWriteMode == HttpWriteMode.Chunked || _ContentLength > 0) {
throw new InvalidOperationException(SR.GetString(SR.net_mustwrite));
}
}
HttpWebResponse httpWebResponse = (HttpWebResponse)castedAsyncResult.InternalWaitForCompletion();
castedAsyncResult.EndCalled = true;
CheckFinalStatus();
GlobalLog.Assert(httpWebResponse.ResponseStream != null, "httpWebResponse.ResponseStream == null", "");
// Otherwise it worked, so return the HttpWebResponse.
GlobalLog.Leave("HttpWebRequest#" + ValidationHelper.HashString(this) + "::EndGetResponse", ValidationHelper.HashString(httpWebResponse));
return httpWebResponse;
}
/*
Accessor: GetResponse
The response property. This property returns the WebResponse for this
request. This may require that a request be submitted first.
The idea is that we look and see if a request has already been
submitted. If one has, we'll just return the existing response
(if it's not null). If we haven't submitted a request yet, we'll
do so now, possible multiple times while we handle redirects
etc.
Input:
Returns: The WebResponse for this request..
*/
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.GetResponse"]/*' />
/// <devdoc>
/// <para>
/// Returns a response from a request to an Internet resource.
/// </para>
/// </devdoc>
//UEUE
public override WebResponse GetResponse() {
if (_HaveResponse) {
CheckFinalStatus();
if (_HttpResponse != null) {
return _HttpResponse;
}
}
IAsyncResult asyncResult = BeginGetResponse(null, null);
if (_Timeout!=System.Threading.Timeout.Infinite && !asyncResult.IsCompleted) {
asyncResult.AsyncWaitHandle.WaitOne(_Timeout, false);
if (!asyncResult.IsCompleted) {
Abort();
throw new WebException(SR.GetString(SR.net_timeout), WebExceptionStatus.Timeout);
}
}
return EndGetResponse(asyncResult);
}
/*
Accessor: Address
This is just a simple Uri that is returned, indicating the end result
of the request, after any possible Redirects, etc, that may transpire
during the request. This was added to handle this case since RequestUri
will not change from the moment this Request is created.
Input:
Returns: The Uri for this request..
*/
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.Address"]/*' />
/// <devdoc>
/// <para>
/// Gets the Uri that actually responded to the request.
/// </para>
/// </devdoc>
public Uri Address {
get {
return _Uri;
}
}
/*
Accessor: ContinueDelegate
Returns/Sets Deletegate used to signal us on Continue callback
Input:
Returns: The ContinueDelegate.
*/
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.ContinueDelegate"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public HttpContinueDelegate ContinueDelegate {
get {
return _ContinueDelegate;
}
set {
_ContinueDelegate = value;
}
}
/*
Accessor: SrvPoint
Returns the service point we're using.
Input:
Returns: The service point.
*/
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.ServicePoint"]/*' />
/// <devdoc>
/// <para>
/// Gets the service point used for this request.
/// </para>
/// </devdoc>
public ServicePoint ServicePoint {
get {
return FindServicePoint(false);
}
}
/*
Accessor: AllowAutoRedirect
Controls the autoredirect flag.
Input: Boolean about whether or not to follow redirects automatically.
Returns: The service point.
*/
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.AllowAutoRedirect"]/*' />
/// <devdoc>
/// <para>
/// Enables or disables automatically following redirection responses.
/// </para>
/// </devdoc>
public bool AllowAutoRedirect {
get {
return _AllowAutoRedirect;
}
set {
_AllowAutoRedirect = value;
}
}
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.MaximumAutomaticRedirections"]/*' />
/// <devdoc>
/// </devdoc>
public int MaximumAutomaticRedirections {
get {
return _MaximumAllowedRedirections;
}
set {
if (value <= 0) {
throw new ArgumentException(SR.GetString(SR.net_toosmall));
}
_MaximumAllowedRedirections = value;
}
}
/*
Accessor: Method
Gets/Sets the http method of this request.
This method represents the initial origin Verb,
this is unchanged/uneffected by redirects
Input: The method to be used.
Returns: Method currently being used.
*/
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.Method"]/*' />
/// <devdoc>
/// <para>
/// Gets or sets the request method.
/// </para>
/// </devdoc>
public override string Method {
get {
return _OriginVerb;
}
set {
if (ValidationHelper.IsBlankString(value)) {
throw new ArgumentException(SR.GetString(SR.net_badmethod));
}
if (ValidationHelper.IsInvalidHttpString(value)) {
throw new ArgumentException(SR.GetString(SR.net_badmethod));
}
_Verb = _OriginVerb = value;
}
}
internal string CurrentMethod {
get {
return _Verb;
}
}
/*
Accessor: KeepAlive
To the app, this means "I want to use a persistent connection if
available" if set to true, or "I don't want to use a persistent
connection", if set to false.
This accessor allows the application simply to register its
desires so far as persistence is concerned. We will act on this
and the pipelined requirement (below) at the point that we come
to choose or create a connection on the application's behalf
Read: returns the sense of the keep-alive request switch
Write: set the sense of the keep-alive request switch
*/
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.KeepAlive"]/*' />
/// <devdoc>
/// <para>
/// Gets or sets the value of the Keep-Alive header.
/// </para>
/// </devdoc>
public bool KeepAlive {
get {
return _KeepAlive;
}
set {
_KeepAlive = value;
}
}
/*
Accessor: Pipelined
To the app, this means "I want to use pipelining if available" if
set to true, or "I don't want to use pipelining", if set to false.
We could infer the state of the keep-alive flag from this setting
too, but we will decide that only at connection-initiation time.
If the application sets pipelining but resets keep-alive then we
will generate a non-pipelined, non-keep-alive request
Read: returns the sense of the pipelined request switch
Write: sets the sense of the pipelined request switch
*/
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.Pipelined"]/*' />
/// <devdoc>
/// <para>
/// Gets or sets the value of Pipelined property.
/// </para>
/// </devdoc>
public bool Pipelined {
get {
return _Pipelined;
}
set {
_Pipelined = value;
}
}
/// <devdoc>
/// <para>
/// Used Internally to sense if we really can pipeline this request.
/// </para>
/// </devdoc>
internal bool InternalPipelined {
get {
return Pipelined;
}
set {
Pipelined = value;
}
}
/// <devdoc>
/// <para>
/// True when we're uploading data with a ContentLength.
/// </para>
/// </devdoc>
internal bool UploadingBufferableData {
get {
return(_ContentLength >= 0) && (_HttpWriteMode == HttpWriteMode.Write);
}
}
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.Credentials"]/*' />
/// <devdoc>
/// <para>
/// Provides authentication information for the request.
/// </para>
/// </devdoc>
public override ICredentials Credentials {
get {
return _AuthInfo;
}
set {
_AuthInfo = value;
}
}
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.PreAuthenticate"]/*' />
/// <devdoc>
/// <para>
/// Enables or disables pre-authentication.
/// </para>
/// </devdoc>
public override bool PreAuthenticate {
get {
return _PreAuthenticate;
}
set {
_PreAuthenticate = value;
}
}
//
// ConnectionGroupName - used to control which group
// of connections we use, by default should be null
//
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.ConnectionGroupName"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public override string ConnectionGroupName {
get {
return _ConnectionGroupName;
}
set {
_ConnectionGroupName = value;
}
}
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.Headers"]/*' />
/// <devdoc>
/// <para>
/// A collection of HTTP headers stored as name value pairs.
/// </para>
/// </devdoc>
public override WebHeaderCollection Headers {
get {
return _HttpRequestHeaders;
}
set {
// we can't change headers after they've already been sent
if ( _RequestSubmitted ) {
throw new InvalidOperationException(SR.GetString(SR.net_reqsubmitted));
}
WebHeaderCollection webHeaders = value;
WebHeaderCollection newWebHeaders = new WebHeaderCollection(true);
// Copy And Validate -
// Handle the case where their object tries to change
// name, value pairs after they call set, so therefore,
// we need to clone their headers.
//
foreach (String headerName in webHeaders.AllKeys ) {
newWebHeaders.Add(headerName,webHeaders[headerName]);
}
_HttpRequestHeaders = newWebHeaders;
}
}
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.Proxy"]/*' />
/// <devdoc>
/// <para>
/// Gets or sets the proxy information for a request.
/// </para>
/// </devdoc>
public override IWebProxy Proxy {
get {
(new WebPermission(PermissionState.Unrestricted)).Demand();
return _Proxy;
}
set {
(new WebPermission(PermissionState.Unrestricted)).Demand();
// we can't change the proxy, while the request is already fired
if ( _RequestSubmitted ) {
throw new InvalidOperationException(SR.GetString(SR.net_reqsubmitted));
}
if ( value == null ) {
throw new ArgumentNullException("value", SR.GetString(SR.net_nullproxynotallowed));
}
_ProxyUserSet = true;
_Proxy = value;
// since the service point is based on our proxy, make sure,
// that we reresolve it
FindServicePoint(true);
}
}
//
// SendChunked - set/gets the state of chunk transfer send mode,
// if true, we will attempt to upload/write bits using chunked property
//
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.SendChunked"]/*' />
/// <devdoc>
/// <para>
/// Enable and disable sending chunked data to the server.
/// </para>
/// </devdoc>
public bool SendChunked {
get {
return _HttpWriteMode == HttpWriteMode.Chunked;
}
set {
if (_RequestSubmitted) {
throw new InvalidOperationException(SR.GetString(SR.net_writestarted));
}
if (value) {
_HttpWriteMode = HttpWriteMode.Chunked;
}
else {
if (_ContentLength >= 0) {
_HttpWriteMode = HttpWriteMode.Write;
}
else {
_HttpWriteMode = HttpWriteMode.None;
}
}
}
}
// HTTP Version
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.ProtocolVersion"]/*' />
/// <devdoc>
/// <para>
/// Gets and sets
/// the HTTP protocol version used in this request.
/// </para>
/// </devdoc>
public Version ProtocolVersion {
get {
return _Version;
}
set {
if (! value.Equals( HttpVersion.Version10) &&
! value.Equals( HttpVersion.Version11)) {
throw new ArgumentException(SR.GetString(SR.net_wrongversion));
}
_Version = new Version(value.Major, value.Minor);
}
}
internal Version InternalVersion {
set {
_DestinationVersion = value;
}
}
/*
Accessor: ContentType
The property that controls the type of the entity body.
Input:
string content-type, on null clears the content-type
Returns: The value of the content type on get.
*/
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.ContentType"]/*' />
/// <devdoc>
/// <para>
/// Gets and sets the value of the Content-Type header.
/// </para>
/// </devdoc>
public override String ContentType {
get {
return _HttpRequestHeaders[HttpKnownHeaderNames.ContentType];
}
set {
SetSpecialHeaders(HttpKnownHeaderNames.ContentType, value);
}
}
// UEUE
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.MediaType"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public string MediaType {
get {
return _MediaType;
}
set {
_MediaType = value;
}
}
/*
Accessor: TransferEncoding
The property that controls the transfer encoding header
Input:
string transferencoding, null clears the transfer encoding header
Returns: The value of the TransferEncoding on get.
*/
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.TransferEncoding"]/*' />
/// <devdoc>
/// <para>
/// Gets or sets the value of the Transfer-Encoding header.
/// </para>
/// </devdoc>
public string TransferEncoding {
get {
return _HttpRequestHeaders[HttpKnownHeaderNames.TransferEncoding];
}
set {
bool fChunked;
//
// on blank string, remove current header
//
if (ValidationHelper.IsBlankString(value)) {
//
// if the value is blank, then remove the header
//
_HttpRequestHeaders.RemoveInternal(HttpKnownHeaderNames.TransferEncoding);
return;
}
//
// if not check if the user is trying to set chunked:
//
string newValue = value.ToLower(CultureInfo.InvariantCulture);
fChunked = (newValue.IndexOf("chunked") != -1);
//
// prevent them from adding chunked, or from adding an Encoding without
// turing on chunked, the reason is due to the HTTP Spec which prevents
// additional encoding types from being used without chunked
//
if (fChunked) {
throw new ArgumentException(SR.GetString(SR.net_nochunked));
}
else if (_HttpWriteMode != HttpWriteMode.Chunked ) {
throw new InvalidOperationException(SR.GetString(SR.net_needchunked));
}
else {
_HttpRequestHeaders.CheckUpdate(HttpKnownHeaderNames.TransferEncoding, value);
}
}
}
/*
Accessor: Connection
The property that controls the connection header
Input:
string Connection, null clears the connection header
Returns: The value of the connnection on get.
*/
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.Connection"]/*' />
/// <devdoc>
/// <para>
/// Gets and sets the value of the Connection header.
/// </para>
/// </devdoc>
public string Connection {
get {
return _HttpRequestHeaders[HttpKnownHeaderNames.Connection];
}
set {
bool fKeepAlive;
bool fClose;
//
// on blank string, remove current header
//
if (ValidationHelper.IsBlankString(value)) {
_HttpRequestHeaders.Remove(HttpKnownHeaderNames.Connection);
return;
}
string newValue = value.ToLower(CultureInfo.InvariantCulture);
fKeepAlive = (newValue.IndexOf("keep-alive") != -1) ;
fClose = (newValue.IndexOf("close") != -1) ;
//
// Prevent keep-alive and close from being added
//
if (fKeepAlive ||
fClose) {
throw new ArgumentException(SR.GetString(SR.net_connarg));
}
else {
_HttpRequestHeaders.CheckUpdate(HttpKnownHeaderNames.Connection, value);
}
}
}
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.Accept"]/*' />
/// <devdoc>
/// <para>
/// Gets or sets the value of the Accept header.
/// </para>
/// </devdoc>
public string Accept {
get {
return _HttpRequestHeaders[HttpKnownHeaderNames.Accept];
}
set {
SetSpecialHeaders(HttpKnownHeaderNames.Accept, value);
}
}
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.Referer"]/*' />
/// <devdoc>
/// <para>
/// Gets or sets the value of the Referer header.
/// </para>
/// </devdoc>
public string Referer {
get {
return _HttpRequestHeaders[HttpKnownHeaderNames.Referer];
}
set {
SetSpecialHeaders(HttpKnownHeaderNames.Referer, value);
}
}
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.UserAgent"]/*' />
/// <devdoc>
/// <para>
/// Gets or sets the value of the User-Agent header.
/// </para>
/// </devdoc>
public string UserAgent {
get {
return _HttpRequestHeaders[HttpKnownHeaderNames.UserAgent];
}
set {
SetSpecialHeaders(HttpKnownHeaderNames.UserAgent, value);
}
}
/*
Accessor: Expect
The property that controls the Expect header
Input:
string Expect, null clears the Expect except for 100-continue value
Returns: The value of the Expect on get.
*/
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.Expect"]/*' />
/// <devdoc>
/// <para>
/// Gets or sets the value of the Expect header.
/// </para>
/// </devdoc>
public string Expect {
get {
return _HttpRequestHeaders[HttpKnownHeaderNames.Expect];
}
set {
// only remove everything other than 100-cont
bool fContinue100;
//
// on blank string, remove current header
//
if (ValidationHelper.IsBlankString(value)) {
_HttpRequestHeaders.RemoveInternal(HttpKnownHeaderNames.Expect);
return;
}
//
// Prevent 100-continues from being added
//
string newValue = value.ToLower(CultureInfo.InvariantCulture);
fContinue100 = (newValue.IndexOf("100-continue") != -1) ;
if (fContinue100) {
throw new ArgumentException(SR.GetString(SR.net_no100));
}
else {
_HttpRequestHeaders.CheckUpdate(HttpKnownHeaderNames.Expect, value);
}
}
}
/*
Accessor: IfModifiedSince
The property that reads the IfModifiedSince
Input:
DateTime IsModifiedSince, null clears the IfModifiedSince header
Returns: The value of the IfModifiedSince on get.
*/
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.IfModifiedSince"]/*' />
/// <devdoc>
/// <para>
/// Gets or sets the value of the If-Modified-Since
/// header.
/// </para>
/// </devdoc>
public DateTime IfModifiedSince {
get {
string ifmodHeaderValue = _HttpRequestHeaders[HttpKnownHeaderNames.IfModifiedSince];
if (ifmodHeaderValue == null) {
return DateTime.Now;
}
return HttpProtocolUtils.string2date(ifmodHeaderValue);
}
set {
SetSpecialHeaders(HttpKnownHeaderNames.IfModifiedSince, HttpProtocolUtils.date2string(value));
}
}
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.HaveResponse"]/*' />
/// <devdoc>
/// <para>
/// Returns <see langword='true'/> if a response has been received from the
/// server.
/// </para>
/// </devdoc>
public bool HaveResponse {
get {
return _HaveResponse;
}
}
//
// This property returns true, after we have notified the caller
// that they can post their data.
//
internal bool HaveWritten {
get {
return _WriteNotifed;
}
}
//
// This is set on failure of the request before reattempting a retransmit,
// we do this on write/read failures so that we can recover from certain failures
// that may be caused by intermittent server/network failures.
// Returns: true, if we have seen some kind of failure.
//
internal bool OnceFailed {
get {
return _OnceFailed;
}
set {
_OnceFailed = value;
}
}
internal byte[] WriteBuffer {
get {
return _WriteBuffer;
}
set {
_WriteBuffer = value;
}
}
//
// Frequently usedheaders made available as properties,
// the following headers add or remove headers taking care
// of special cases due to their unquie qualities within
// the net handlers,
//
// Note that all headers supported here will be disallowed,
// and not accessable through the normal Header objects.
//
/*
Accessor: SetSpecialHeaders
Private method for removing duplicate code which removes and
adds headers that are marked private
Input: HeaderName, value to set headers
Returns: none
*/
private void SetSpecialHeaders(string HeaderName, string value) {
value = WebHeaderCollection.CheckBadChars(value, true);
_HttpRequestHeaders.RemoveInternal(HeaderName);
if (value.Length != 0) {
_HttpRequestHeaders.AddInternal(HeaderName, value);
}
}
/*
Abort - Attempts to abort pending request,
This calls into the delegate, and then closes any pending streams.
Input: none
Returns: none
*/
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.Abort"]/*' />
public override void Abort()
{
_OnceFailed = true;
_Abort = true;
GlobalLog.Print("HttpWebRequest#" + ValidationHelper.HashString(this) + "::Abort()");
_ResponseException = new WebException(
NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled),
WebExceptionStatus.RequestCanceled) ;
_HaveResponse = true;
try {
if ( _AbortDelegate != null ) {
_AbortDelegate();
_AbortDelegate = null;
}
}
catch {
}
try {
if (_SubmitWriteStream != null) {
_SubmitWriteStream.CloseInternal(true);
_SubmitWriteStream = null;
}
}
catch {
}
try {
if (_HttpResponse != null) {
_HttpResponse.Close();
}
}
catch {
}
}
/*
FindServicePoint - Finds the ServicePoint for this request
This calls the FindServicePoint off of the ServicePointManager
to determine what ServicePoint to use. When our proxy changes,
or there is a redirect, this should be recalled to determine it.
Input: forceFind - regardless of the status, always call FindServicePoint
Returns: ServicePoint
*/
private ServicePoint FindServicePoint(bool forceFind)
{
ServicePoint servicePoint = _ServicePoint;
if ( servicePoint == null || forceFind ) {
lock(this) {
//
// we will call FindServicePoint - iff
// - there is no service point ||
// - we are forced to find one, usually due to changes of the proxy or redirection
//
if ( _ServicePoint == null || forceFind ) {
if (!_ProxyUserSet) {
_Proxy = GlobalProxySelection.SelectInternal;
}
_ServicePoint = ServicePointManager.FindServicePoint(_Uri, _Proxy);
}
}
servicePoint = _ServicePoint;
}
return servicePoint;
}
/*
InvokeGetRequestStreamCallback - Notify our GetRequestStream caller
This is needed to tell our caller that we're finished,
and he can go ahead and write to the stream.
*/
private void InvokeGetRequestStreamCallback(bool signalled) {
GlobalLog.Enter("HttpWebRequest#" + ValidationHelper.HashString(this) + "::InvokeGetRequestStreamCallback", signalled.ToString());
LazyAsyncResult asyncResult = _WriteAResult;
GlobalLog.Assert( // check that this is the correct request
asyncResult == null || this == (HttpWebRequest)asyncResult.AsyncObject,
"InvokeGetRequestStreamCallback: this != asyncResult.AsyncObject", "");
GlobalLog.Assert( // check that the event didn't already complete
asyncResult == null || !asyncResult.IsCompleted,
"InvokeGetRequestStreamCallback: asyncResult already completed!", "");
if (asyncResult != null) {
_WriteAResult = null;
// It's done now, mark it as completed.
// Otherwise it worked, so return the stream.
try {
asyncResult.InvokeCallback(!signalled, _SubmitWriteStream);
}
catch (Exception exception) {
Abort();
GlobalLog.LeaveException("HttpWebRequest#" + ValidationHelper.HashString(this) + "::InvokeGetRequestStreamCallback", exception);
throw;
}
}
GlobalLog.Leave("HttpWebRequest#" + ValidationHelper.HashString(this) + "::InvokeGetRequestStreamCallback", "success");
}
/*
RequestSubmitDone - Handle submit done callback.
This is our submit done handler, called by the underlying connection
code when a stream is available for our use. We save the stream for
later use and signal the wait event.
We also handle the continuation/termination of a BeginGetRequestStream,
by saving out the result and calling its callback if needed.
Input: SubmitStream - The stream we may write on.
Status - The status of the submission.
Returns: Nothing.
*/
internal void SetRequestSubmitDone(ConnectStream submitStream) {
GlobalLog.Enter("HttpWebRequest#" + ValidationHelper.HashString(this) + "::SetRequestSubmitDone", ValidationHelper.HashString(submitStream));
if (_Abort) {
try {
submitStream.SetAbortState();
submitStream.CloseInternal(true);
}
catch {
}
GlobalLog.Leave("HttpWebRequest#" + ValidationHelper.HashString(this) + "::SetRequestSubmitDone", "ABORT");
return;
}
//This state should be set before invoking the callback
//otherwise extra request will be issued without any
//particular reason.
_RequestSubmitted = true;
_SubmitWriteStream = submitStream;
// Finish the submission of the request. If the asyncResult now
// isn't null, mark it as completed.
WebExceptionStatus Error = EndSubmitRequest();
if (Error != WebExceptionStatus.Pending) {
// check for failure
if (Error != WebExceptionStatus.Success) {
// OK, something happened on the request, close down stream
submitStream.SetAbortState();
submitStream.CloseInternal(true);
}
// wake up any pending waits to write, since this is an error
if ( _WriteEvent != null ) {
GlobalLog.Print("HttpWebRequest#" + ValidationHelper.HashString(this) + "::SetRequestSubmitDone() calling _WriteEvent.Set()");
_WriteEvent.Set();
}
} // != Pending
if (_Abort) {
try {
submitStream.SetAbortState();
submitStream.CloseInternal(true);
}
catch {
}
}
GlobalLog.Leave("HttpWebRequest#" + ValidationHelper.HashString(this) + "::SetRequestSubmitDone");
}
internal bool BufferHeaders {
get {
return RequireBody && ContentLength==-1 && !SendChunked && AllowWriteStreamBuffering;
}
}
/*
SetRequestContinue - Handle 100-continues on Posting
This indicates to us that we need to continue posting,
and there is no need to buffer because we are now
Input: Nothing
Returns: Nothing.
*/
internal void SetRequestContinue() {
GlobalLog.Enter("HttpWebRequest#" + ValidationHelper.HashString(this) + "::SetRequestContinue()");
if (_AcceptContinue) {
//_AckContinue = true;
if ( _WriteEvent != null ) {
GlobalLog.Print("HttpWebRequest#" + ValidationHelper.HashString(this) + "::SetRequestContinue() calling _WriteEvent.Set()");
_WriteEvent.Set();
}
}
GlobalLog.Leave("HttpWebRequest#" + ValidationHelper.HashString(this) + "::SetRequestContinue");
}
/*++
Routine Description:
Wakes up blocked threads, so they can read response object,
from the result
We also handle the continuation/termination of a BeginGetResponse,
by saving out the result and calling its callback if needed.
Arguments:
coreData - Contains the data used to create the Response object
exception - true if we're allowed to throw an exception on error
Return Value:
none
--*/
internal void SetResponse(CoreResponseData coreResponseData) {
GlobalLog.Enter("HttpWebRequest#" + ValidationHelper.HashString(this) + "::SetResponse", "*** SETRESP ***");
//
// Since we are no longer attached to this Connection,
// we null out our abort delegate.
//
_AbortDelegate = null;
try {
if (coreResponseData != null) {
_HttpResponse = new HttpWebResponse(_Uri, _Verb, coreResponseData, _MediaType, UsesProxySemantics);
coreResponseData = null;
}
else {
//
// not sure how this would happen, but we should not throw an exception,
// which we were earlier doing.
//
GlobalLog.Assert(false,
"coreResponseData == null",
"");
GlobalLog.Leave("HttpWebRequest#" + ValidationHelper.HashString(this) + "::SetResponse", "ASSERT");
return;
}
//
// give apps the chance to examine the headers of the new response
//
if (_CookieContainer != null) {
CookieModule.OnReceivedHeaders(this);
}
// New Response
HttpProcessingResult httpResult = HttpProcessingResult.Continue;
// handle redirects, authentication, and such
httpResult = DoSubmitRequestProcessing();
if (httpResult == HttpProcessingResult.Continue) {
//
// Now grab crit sec here, before returning,
// this is to prevent some one from reading
// ReadAResult it as null before we finish
// processing our response
//
LazyAsyncResult asyncResult = null;
lock(this)
{
asyncResult = _ReadAResult;
GlobalLog.Assert(asyncResult == null || this == (HttpWebRequest)asyncResult.AsyncObject,
"SetResponse: this != asyncResult.AsyncObject",
"");
GlobalLog.Assert(asyncResult == null || !asyncResult.IsCompleted, "SetResponse: asyncResult already completed!", "");
_HaveResponse = true;
if (asyncResult != null) {
_ReadAResult = null;
}
}
if (asyncResult != null) {
asyncResult.InvokeCallback(false, _HttpResponse);
}
if ( _WriteEvent != null ) {
GlobalLog.Print("HttpWebRequest#" + ValidationHelper.HashString(this) + "::SetResponse(CoreResponseData) calling _WriteEvent.Set()");
_WriteEvent.Set();
}
}
} catch (Exception exception) {
Abort();
GlobalLog.LeaveException("HttpWebRequest#" + ValidationHelper.HashString(this) + "::SetResponse", exception);
throw;
}
if (_Abort) {
try {
if ( _HttpResponse != null ) {
_HttpResponse.Close();
}
}
catch {
}
GlobalLog.Leave("HttpWebRequest#" + ValidationHelper.HashString(this) + "::SetResponse", "ABORT");
return;
}
GlobalLog.Leave("HttpWebRequest#" + ValidationHelper.HashString(this) + "::SetResponse");
}
/*++
Routine Description:
Wakes up blocked threads, so they can throw exceptions.
Arguments:
None.
Return Value:
HttpWebResponse
--*/
internal void SetResponse(Exception E) {
GlobalLog.Enter("HttpWebRequest#" + ValidationHelper.HashString(this) + "::SetResponse", E.ToString() + "/*** SETRESPONSE IN ERROR ***");
//
// Since we are no longer attached to this Connection,
// we null out our abort delegate.
//
_AbortDelegate = null;
if ( _WriteEvent != null ) {
GlobalLog.Print("HttpWebRequest#" + ValidationHelper.HashString(this) + "::SetResponse(Exception) calling _WriteEvent.Set()");
_WriteEvent.Set();
}
if ((E as WebException) == null) {
if (_HttpResponse==null) {
E = new WebException(E.Message, E);
}
else {
E = new WebException(
SR.GetString(
SR.net_servererror,
NetRes.GetWebStatusCodeString(
ResponseStatusCode,
_HttpResponse.StatusDescription)),
E,
WebExceptionStatus.ProtocolError,
_HttpResponse );
}
}
_ResponseException = E;
// grab crit sec to protect BeginGetResponse/EndGetResponse code path
Monitor.Enter(this);
_HaveResponse = true;
LazyAsyncResult writeAsyncResult = _WriteAResult;
LazyAsyncResult readAsyncResult = _ReadAResult;
_WriteAResult = null;
_ReadAResult = null;
Monitor.Exit(this);
if (writeAsyncResult != null) {
writeAsyncResult.InvokeCallback(false);
}
if (readAsyncResult != null) {
readAsyncResult.InvokeCallback(false);
}
GlobalLog.Leave("HttpWebRequest#" + ValidationHelper.HashString(this) + "::SetResponse");
}
/*++
BeginSubmitRequest: Begins Submit off a request to the network.
This is called when we need to transmit an Async Request, but
this function only servers to activate the submit, and does not
actually block
Called when we want to submit a request to the network. We do several
things here - look for a proxy, find the service point, etc. In the
end we call the service point to get access (via a stream) to the
underlying connection, then serialize our headers onto that connection.
The actual submission request to the service point is async, so we
submit the request and then return, to allow the async to run its course.
Input:
forceFind - insures that always get a new ServicePoint,
needed on redirects, or where the ServicePoint may have changed
Returns: Nothing
--*/
internal void BeginSubmitRequest(bool forceFind) {
GlobalLog.Enter("HttpWebRequest#" + ValidationHelper.HashString(this) + "::BeginSubmitRequest", forceFind.ToString());
GlobalLog.Print("HttpWebRequest#" + ValidationHelper.HashString(this) + " " + Method + " " + Address);
if (_SubmitWriteStream!=null) {
long writeBytes = _SubmitWriteStream.BytesLeftToWrite;
GlobalLog.Print("HttpWebRequest#" + ValidationHelper.HashString(this) + "::BeginSubmitRequest() _OldSubmitWriteStream:" + ValidationHelper.HashString(_OldSubmitWriteStream) + " _SubmitWriteStream:" + ValidationHelper.HashString(_SubmitWriteStream) + " writeBytes:" + writeBytes.ToString());
if (writeBytes == 0) {
//
// we're resubmitting a request with an entity body
//
_OldSubmitWriteStream = _SubmitWriteStream;
if (_SubmitWriteStream.BufferedData!=null && _SubmitWriteStream.BufferedData.Length>0) {
//
// if we're resubmitting and buffering:
//
SetContentLength(_SubmitWriteStream.BufferedData.Length);
WriteBuffer = null;
}
}
else {
GlobalLog.Print("HttpWebRequest#" + ValidationHelper.HashString(this) + "::BeginSubmitRequest() got a _SubmitWriteStream.WriteBytes:" + writeBytes.ToString());
}
}
GlobalLog.Print("HttpWebRequest#" + ValidationHelper.HashString(this) + "::BeginSubmitRequest() _HaveResponse:" + _HaveResponse.ToString());
GlobalLog.Print("HttpWebRequest#" + ValidationHelper.HashString(this) + "::BeginSubmitRequest() _RequestSubmitted:" + _RequestSubmitted.ToString());
GlobalLog.Print("HttpWebRequest#" + ValidationHelper.HashString(this) + "::BeginSubmitRequest() Saw100Continue:" + Saw100Continue.ToString());
// if we're submitting then we don't have a response
_HaveResponse = false;
_RequestSubmitted = true;
ServicePoint servicePoint = FindServicePoint(forceFind);
if ((_HttpWriteMode != HttpWriteMode.None) && (_WriteEvent == null)) {
// don't create a WriteEvent until we are doing a Write
GlobalLog.Print("HttpWebRequest#" + ValidationHelper.HashString(this) + "::BeginSubmitRequest() lazy allocation of _WriteEvent");
_WriteEvent = new AutoResetEvent(false);
}
_AbortDelegate = servicePoint.SubmitRequest(this, GetConnectionGroupLine());
// prevent someone from sending chunked to a HTTP/1.0 server
if (ChunkedUploadOnHttp10) {
Abort();
throw new ProtocolViolationException(SR.GetString(SR.net_nochunkuploadonhttp10));
}
GlobalLog.Leave("HttpWebRequest#" + ValidationHelper.HashString(this) + "::BeginSubmitRequest");
}
/*++
EndSubmitRequest: End Submit off a request
This Typically called by a callback that wishes to proced
with the request that it issued before going async. This function
continues by submitting Headers. Assumes calling will block
on ReadEvent to retrive response from server.
Input: Nothing.
Returns: Nothing
--*/
internal WebExceptionStatus EndSubmitRequest() {
GlobalLog.Enter("HttpWebRequest#" + ValidationHelper.HashString(this) + "::EndSubmitRequest");
ConnectStream SubmitWriteStream = _SubmitWriteStream;
if (SubmitWriteStream == null) {
// An error on the submit request, so fail this.
// Some sort of error on the submit request.
GlobalLog.Leave("HttpWebRequest#" + ValidationHelper.HashString(this) + "::EndSubmitRequest", WebExceptionStatus.ProtocolError.ToString());
return WebExceptionStatus.ProtocolError;
}
//
// check to see if we need to buffer the headers and send them at
// a later time, when we know content length
//
if (BufferHeaders) {
InvokeGetRequestStreamCallback(false);
GlobalLog.Leave("HttpWebRequest#" + ValidationHelper.HashString(this) + "::EndSubmitRequest", "InvokeGetRequestStreamCallback(BufferHeaders)");
return WebExceptionStatus.Pending;
}
// waiting for a continue
_AcceptContinue = true;
// gather headers and write them to the stream
if (WriteBuffer==null) {
// if needed generate the headers
// WriteBuffer might have been set to null by BeginSubmitRequest() if
// resubmitting and headers have been changed (chunking, content length)
MakeRequest();
}
GlobalLog.Assert(WriteBuffer != null && WriteBuffer[0] < 0x80 && WriteBuffer[0] != 0x0,
"HttpWebRequest::EndSubmitRequest Invalid WriteBuffer generated", "");
WebExceptionStatus webExceptionStatus = SubmitWriteStream.WriteHeaders(this);
// we return pending, because the Connection code will handle this error, not us.
GlobalLog.Leave("HttpWebRequest#" + ValidationHelper.HashString(this) + "::EndSubmitRequest", webExceptionStatus.ToString());
return webExceptionStatus;
}
private class EndWriteHeadersStatus {
internal HttpWebRequest Request;
internal int RerequestCount;
internal EndWriteHeadersStatus(HttpWebRequest webRequest, int reRequestCount) {
this.Request = webRequest;
this.RerequestCount = reRequestCount;
}
}
/*++
EndWriteHeaders: End write of headers
This Typically called by a callback that wishes to proceed
with the finalization of writing headers
Input: Nothing.
Returns: bool - true if success, false if we need to go pending
--*/
internal bool EndWriteHeaders() {
GlobalLog.Enter("HttpWebRequest#" + ValidationHelper.HashString(this) + "::EndWriteHeaders");
//
// if sending data and we sent a 100-continue to a 1.1 or better
// server then synchronize with the 100-continue intermediate
// response (or a final failure response)
//
_WriteNotifed = true;
if (_HttpWriteMode!=HttpWriteMode.None && _ContentLength!=0 && _ServicePoint.Understands100Continue) {
if (!_WriteEvent.WaitOne(0, false)) {
//
// if the event wasn't already signaled, queue invocation to a ThreadPool's worker thread.
//
GlobalLog.Print("#" + GetHashCode() + ": *** registering " + DefaultContinueTimeout + "mSec wait for _WriteEvent");
ThreadPool.RegisterWaitForSingleObject(
_WriteEvent,
m_EndWriteHeaders_Part2Callback,
new EndWriteHeadersStatus(this, _RerequestCount),
DefaultContinueTimeout,
true
);
GlobalLog.Leave("HttpWebRequest#" + ValidationHelper.HashString(this) + "::EndWriteHeaders", false);
return false;
}
GlobalLog.Print("HttpWebRequest#" + ValidationHelper.HashString(this) + "::EndWriteHeaders() *** _WriteEvent already signalled");
}
EndWriteHeaders_Part2(this, false);
GlobalLog.Leave("HttpWebRequest#" + ValidationHelper.HashString(this) + "::EndWriteHeaders", true);
return true;
}
private static void EndWriteHeaders_Part2(object state, bool signalled) {
HttpWebRequest thisHttpWebRequest = state as HttpWebRequest;
GlobalLog.Enter("HttpWebRequest#" + ValidationHelper.HashString(thisHttpWebRequest) + "::EndWriteHeaders_Part2", ValidationHelper.ToString(state) + "#" + ValidationHelper.HashString(state));
if (thisHttpWebRequest == null) {
int reRequestCount;
EndWriteHeadersStatus endWriteStatus = state as EndWriteHeadersStatus;
thisHttpWebRequest = endWriteStatus.Request;
reRequestCount = endWriteStatus.RerequestCount;
// if another request was fired off since this one, then we need to ignore this callback
if (reRequestCount != thisHttpWebRequest._RerequestCount) {
GlobalLog.Leave("HttpWebRequest#" + ValidationHelper.HashString(thisHttpWebRequest) + "::EndWriteHeaders_Part2", "ignoring:" + reRequestCount.ToString());
return;
}
}
ConnectStream submitWriteStream = thisHttpWebRequest._SubmitWriteStream;
GlobalLog.Print("EndWriteHeaders_Part2() ConnectStream#" + ValidationHelper.HashString(submitWriteStream) + " _HttpWriteMode:" + thisHttpWebRequest._HttpWriteMode + " _AcceptContinue:" + thisHttpWebRequest._AcceptContinue + " _HaveResponse:" + thisHttpWebRequest._HaveResponse);
thisHttpWebRequest._AcceptContinue = false;
if (thisHttpWebRequest._HttpWriteMode != HttpWriteMode.None) {
GlobalLog.Assert(submitWriteStream != null, "HttpWebRequest::EndWriteHeaders_Part2() submitWriteStream == null", "");
//
// if we think that the server will send us a 100 Continue, but this time we
// didn't get it in the DefaultContinueTimeout timeframe, we switch behaviour
//
if (!thisHttpWebRequest.Saw100Continue && // if we didn't see a 100 continue for this request at all
thisHttpWebRequest._ServicePoint.Understands100Continue && // and the service point understood 100 continue so far
!thisHttpWebRequest._WriteEvent.WaitOne(0, false)) { // and the wait timed out (>350ms)
//
// turn off expectation of 100 continue (we'll keep sending an
// "Expect: 100-continue" header to a 1.1 server though)
//
if (thisHttpWebRequest._HttpResponse != null && (int)thisHttpWebRequest._HttpResponse.StatusCode<=(int)HttpStatusRange.MaxOkStatus) {
GlobalLog.Leave("HttpWebRequest#" + ValidationHelper.HashString(thisHttpWebRequest) + " ServicePoint#" + ValidationHelper.HashString(thisHttpWebRequest._ServicePoint) + " NO expected 100 Continue");
thisHttpWebRequest._ServicePoint.Understands100Continue = false;
}
}
//
// if we have a response immediately, then cancel post, and
// fall into redirect/auth/error handling code located in caller
//
if (thisHttpWebRequest._HaveResponse) {
GlobalLog.Leave("HttpWebRequest#" + ValidationHelper.HashString(thisHttpWebRequest) + "::EndWriteHeaders_Part2$_HaveResponse");
goto done;
}
//
// We always need to buffer because some servers send
// 100 Continue even when they mean to redirect,
// so we waste the cycles with buffering
//
GlobalLog.Print("EndWriteHeaders_Part2() AllowWriteStreamBuffering:" + thisHttpWebRequest.AllowWriteStreamBuffering);
if (thisHttpWebRequest.AllowWriteStreamBuffering) {
GlobalLog.Print("EndWriteHeaders_Part2() BufferOnly:" + submitWriteStream.BufferOnly + " _OldSubmitWriteStream#" + ValidationHelper.HashString(thisHttpWebRequest._OldSubmitWriteStream) + " submitWriteStream#" + ValidationHelper.HashString(submitWriteStream));
if (submitWriteStream.BufferOnly) {
//
// if the ConnectStream was buffering the headers then
// there will not be an OldSubmitWriteStream. set it
// now to the newly created one.
//
thisHttpWebRequest._OldSubmitWriteStream = submitWriteStream;
}
else {
//
// otherwise just tell the new stream that it is buffering.
//
submitWriteStream.EnableWriteBuffering();
}
GlobalLog.Print("EndWriteHeaders_Part2() _OldSubmitWriteStream#" + ValidationHelper.HashString(thisHttpWebRequest._OldSubmitWriteStream));
if (thisHttpWebRequest._OldSubmitWriteStream != null) {
thisHttpWebRequest._SubmitWriteStream = thisHttpWebRequest._OldSubmitWriteStream;
submitWriteStream.ResubmitWrite(thisHttpWebRequest._OldSubmitWriteStream);
submitWriteStream.CloseInternal(true);
}
}
}
else { // if (_HttpWriteMode == HttpWriteMode.None) {
if (submitWriteStream != null) {
// close stream so the headers get sent
submitWriteStream.CloseInternal(true);
// disable write stream
submitWriteStream = null;
}
thisHttpWebRequest._OldSubmitWriteStream = null;
}
done:
// callback processing - notify our caller that we're done
thisHttpWebRequest.InvokeGetRequestStreamCallback(signalled);
GlobalLog.Leave("HttpWebRequest#" + ValidationHelper.HashString(thisHttpWebRequest) + "::EndWriteHeaders_Part2");
}
/*++
Routine Description:
Assembles the status line for an HTTP request
specifically for CONNECT style verbs, that create a pipe
Arguments:
headersSize - size of the Header string that we send after
this request line
Return Value:
int - number of bytes written out
--*/
private int GenerateConnectRequestLine(int headersSize) {
int offset = 0;
string host = ConnectHostAndPort;
//
// Handle Connect Case, i.e. "CONNECT hostname.domain.edu:999"
//
int writeBufferLength = _Verb.Length +
host.Length +
RequestLineConstantSize +
headersSize;
WriteBuffer = new byte[writeBufferLength];
offset = Encoding.ASCII.GetBytes(_Verb, 0, _Verb.Length, WriteBuffer, 0);
WriteBuffer[offset++] = (byte)' ';
offset += Encoding.ASCII.GetBytes(host, 0, host.Length, WriteBuffer, offset);
WriteBuffer[offset++] = (byte)' ';
return offset;
}
internal virtual string ConnectHostAndPort {
get {
return _Uri.Host+":"+_Uri.Port;
}
}
internal virtual bool ShouldAddHostHeader {
get {
return true;
}
}
/*++
Routine Description:
Assembles the status line for an HTTP request
specifically to a proxy...
Arguments:
headersSize - size of the Header string that we send after
this request line
Return Value:
int - number of bytes written out
--*/
private int GenerateProxyRequestLine(int headersSize) {
int offset = 0;
string PortString = (_Uri.Port == _Uri.DefaultPortForScheme(_Uri.Scheme))
? string.Empty : (":" + _Uri.Port.ToString("D"));
//
// Handle Proxy Case, i.e. "GET http://hostname-outside-of-proxy.somedomain.edu:999"
// consider handling other schemes
//
string Scheme = "http://";
int writeBufferLength = _Verb.Length +
Scheme.Length + // http://
_Uri.Host.Length +
PortString.Length + // i.e. :900
_Uri.PathAndQuery.Length +
RequestLineConstantSize +
headersSize;
WriteBuffer = new byte[writeBufferLength];
offset = Encoding.ASCII.GetBytes(_Verb, 0, _Verb.Length, WriteBuffer, 0);
WriteBuffer[offset++] = (byte)' ';
offset += Encoding.ASCII.GetBytes(Scheme, 0, Scheme.Length, WriteBuffer, offset);
offset += Encoding.ASCII.GetBytes(_Uri.Host, 0, _Uri.Host.Length, WriteBuffer, offset);
offset += Encoding.ASCII.GetBytes(PortString, 0, PortString.Length, WriteBuffer, offset);
offset += Encoding.ASCII.GetBytes(_Uri.PathAndQuery, 0, _Uri.PathAndQuery.Length, WriteBuffer, offset);
WriteBuffer[offset++] = (byte)' ';
return offset;
}
/*++
Routine Description:
Assembles the status/request line for the request.
Arguments:
headersSize - size of the Header string that we send after
this request line
Return Value:
int - number of bytes written
--*/
private int GenerateRequestLine(int headersSize) {
int offset = 0;
int writeBufferLength =
_Verb.Length +
_Uri.PathAndQuery.Length +
RequestLineConstantSize +
headersSize;
WriteBuffer = new byte[writeBufferLength];
offset = Encoding.ASCII.GetBytes(_Verb, 0, _Verb.Length, WriteBuffer, 0);
WriteBuffer[offset++] = (byte)' ';
offset += Encoding.ASCII.GetBytes(_Uri.PathAndQuery, 0, _Uri.PathAndQuery.Length, WriteBuffer, offset);
WriteBuffer[offset++] = (byte)' ';
return offset;
}
/*++
Routine Description:
Assembles the data/headers for an HTTP request
into a buffer
Arguments:
none.
Return Value:
none.
--*/
private void MakeRequest() {
//
// If we have content-length, use it, if we don't check for chunked
// sending mode, otherwise, if -1, then see to closing the connecition.
// There's one extra case in which the user didn't set the ContentLength and is
// not chunking either. In this case we buffer the data and we'll send the
// ContentLength on re-write (see ReWrite());
//
GlobalLog.Enter("HttpWebRequest#" + ValidationHelper.HashString(this) + "::MakeRequest");
int offset;
if (_HttpWriteMode == HttpWriteMode.Write && _ContentLength != -1) {
_HttpRequestHeaders.ChangeInternal(HttpKnownHeaderNames.ContentLength, _ContentLength.ToString());
}
else if (_HttpWriteMode == HttpWriteMode.Chunked) {
_HttpRequestHeaders.AddInternal( HttpKnownHeaderNames.TransferEncoding, "chunked");
}
if ( _HttpWriteMode != HttpWriteMode.None ) {
_HttpRequestHeaders.AddInternal( HttpKnownHeaderNames.Expect, "100-continue");
}
//
// Behavior from Wininet, on Uris with Proxies, send Proxy-Connection: instead
// of Connection:
//
string connectionString = HttpKnownHeaderNames.Connection;
if (UsesProxySemantics) {
_HttpRequestHeaders.RemoveInternal(HttpKnownHeaderNames.Connection);
connectionString = HttpKnownHeaderNames.ProxyConnection;
if (!ValidationHelper.IsBlankString(Connection)) {
_HttpRequestHeaders.AddInternal(HttpKnownHeaderNames.ProxyConnection, _HttpRequestHeaders[HttpKnownHeaderNames.Connection]);
}
} else {
_HttpRequestHeaders.RemoveInternal(HttpKnownHeaderNames.ProxyConnection);
}
if (_KeepAlive) {
GlobalLog.Assert(_ServicePoint != null,
"MakeRequest: _ServicePoint == NULL",
"");
if (_DestinationVersion == null ||
_Version.Equals( HttpVersion.Version10 ) ||
_DestinationVersion.Equals( HttpVersion.Version10 )) {
_HttpRequestHeaders.AddInternal( connectionString, "Keep-Alive");
}
}
else {
if (_Version.Equals( HttpVersion.Version11 )) {
_HttpRequestHeaders.AddInternal( connectionString, "Close");
}
}
// Set HostName Header
if (ShouldAddHostHeader) {
_HttpRequestHeaders.ChangeInternal( HttpKnownHeaderNames.Host, _Uri.Host);
}
// If pre-authentication is requested call the AuthenticationManager
// and add authorization header if there is response
if (PreAuthenticate) {
if (UsesProxySemantics && _Proxy.Credentials!=null) {
_ChallengedUri = ServicePoint.Address;
_ProxyAuthenticationState.PreAuthIfNeeded(this, _Proxy.Credentials);
}
if (_AuthInfo!=null) {
_ChallengedUri = _Uri;
_ServerAuthenticationState.PreAuthIfNeeded(this, _AuthInfo);
}
}
//
// about to create the headers we're going to send. Check if any
// modules want to inspect or modify them
//
if (_CookieContainer != null) {
CookieModule.OnSendingHeaders(this);
}
//
// Now create our headers by calling ToString, and then
// create a HTTP Request Line to go with it.
//
string requestHeadersString = _HttpRequestHeaders.ToString();
int requestHeadersSize = Encoding.UTF8.GetByteCount(requestHeadersString);
// NOTE: Perhaps we should cache this on this-object in the future?
HttpVerb httpVerbType = KnownVerbs.GetHttpVerbType(_Verb);
if (httpVerbType.m_ConnectRequest) {
// for connect verbs we need to specially handle it.
offset = GenerateConnectRequestLine(requestHeadersSize);
}
else if (UsesProxySemantics) {
// depending on whether, we have a proxy, generate a proxy or normal request
offset = GenerateProxyRequestLine(requestHeadersSize);
}
else {
// default case for normal HTTP requests
offset = GenerateRequestLine(requestHeadersSize);
}
Buffer.BlockCopy(HttpBytes, 0, WriteBuffer, offset, HttpBytes.Length);
offset += HttpBytes.Length;
WriteBuffer[offset++] = (byte)('0' + _Version.Major);
WriteBuffer[offset++] = (byte)'.';
WriteBuffer[offset++] = (byte)('0' + _Version.Minor);
WriteBuffer[offset++] = (byte)'\r';
WriteBuffer[offset++] = (byte)'\n';
//
// Serialze the headers out to the byte Buffer,
// by converting them to bytes from UNICODE
//
Encoding.UTF8.GetBytes(requestHeadersString, 0, requestHeadersString.Length, WriteBuffer, offset);
GlobalLog.Leave("HttpWebRequest#" + ValidationHelper.HashString(this) + "::MakeRequest", offset.ToString());
}
/*++
Routine Description:
Basic Constructor for HTTP Protocol Class,
initalizes to basic header state.
Arguments:
Uri - Uri object for which we're creating.
Return Value:
None.
--*/
//
// PERF:
// removed some double initializations.
// perf went from:
// clocks per instruction CPI: 9,098.72 to 1,301.14
// %app exclusive time: 2.92 to 0.43
//
internal HttpWebRequest(Uri uri) {
GlobalLog.Print("HttpWebRequest#" + ValidationHelper.HashString(this) + "::.ctor(" + uri.ToString() + ")");
//
// internal constructor, HttpWebRequest cannot be created directly
// but only through WebRequest.Create() method
// set defaults
//
_HttpRequestHeaders = new WebHeaderCollection(true);
_Proxy = GlobalProxySelection.SelectInternal;
_KeepAlive = true;
_Pipelined = true;
_AllowAutoRedirect = true;
_AllowWriteStreamBuffering = true;
_HttpWriteMode = HttpWriteMode.None;
_MaximumAllowedRedirections = 50;
_Timeout = WebRequest.DefaultTimeout;
_ContentLength = -1;
_OriginVerb = "GET";
_Verb = _OriginVerb;
_Version = HttpVersion.Version11;
_OriginUri = uri;
CreateDefaultObjects();
}
private void CreateDefaultObjects() {
_Uri = _OriginUri;
_ProxyAuthenticationState = new AuthenticationState(true);
_ServerAuthenticationState = new AuthenticationState(false);
}
//
// ISerializable constructor
//
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.HttpWebRequest"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter =true)]
protected HttpWebRequest(SerializationInfo serializationInfo, StreamingContext streamingContext) {
new WebPermission(PermissionState.Unrestricted).Demand();
_HttpRequestHeaders = (WebHeaderCollection)serializationInfo.GetValue("_HttpRequestHeaders", typeof(WebHeaderCollection));
_Proxy = (IWebProxy)serializationInfo.GetValue("_Proxy", typeof(IWebProxy));
_KeepAlive = serializationInfo.GetBoolean("_KeepAlive");
_Pipelined = serializationInfo.GetBoolean("_Pipelined");
_AllowAutoRedirect = serializationInfo.GetBoolean("_AllowAutoRedirect");
_AllowWriteStreamBuffering = serializationInfo.GetBoolean("_AllowWriteStreamBuffering");
_HttpWriteMode = (HttpWriteMode)serializationInfo.GetInt32("_HttpWriteMode");
_MaximumAllowedRedirections = serializationInfo.GetInt32("_MaximumAllowedRedirections");
_AutoRedirects = serializationInfo.GetInt32("_AutoRedirects");
_Timeout = serializationInfo.GetInt32("_Timeout");
_ContentLength = serializationInfo.GetInt64("_ContentLength");
_MediaType = serializationInfo.GetString("_MediaType");
_OriginVerb = serializationInfo.GetString("_OriginVerb");
_ConnectionGroupName = serializationInfo.GetString("_ConnectionGroupName");
_Version = (Version)serializationInfo.GetValue("_Version", typeof(Version));
_OriginUri = (Uri)serializationInfo.GetValue("_OriginUri", typeof(Uri));
CreateDefaultObjects();
}
//
// ISerializable method
//
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.GetObjectData"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter =true)]
void ISerializable.GetObjectData(SerializationInfo serializationInfo, StreamingContext streamingContext) {
//
// for now disregard streamingContext.
// just Add all the members we need to deserialize to construct
// the object at deserialization time
//
// the following runtime types already support serialization:
// Boolean, Char, SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double, DateTime
// for the others we need to provide our own serialization
//
serializationInfo.AddValue("_HttpRequestHeaders", _HttpRequestHeaders, typeof(WebHeaderCollection));
serializationInfo.AddValue("_Proxy", _Proxy, typeof(IWebProxy));
serializationInfo.AddValue("_KeepAlive", _KeepAlive);
serializationInfo.AddValue("_Pipelined", _Pipelined);
serializationInfo.AddValue("_AllowAutoRedirect", _AllowAutoRedirect);
serializationInfo.AddValue("_AllowWriteStreamBuffering", _AllowWriteStreamBuffering);
serializationInfo.AddValue("_HttpWriteMode", _HttpWriteMode);
serializationInfo.AddValue("_MaximumAllowedRedirections", _MaximumAllowedRedirections);
serializationInfo.AddValue("_AutoRedirects", _AutoRedirects);
serializationInfo.AddValue("_Timeout", _Timeout);
serializationInfo.AddValue("_ContentLength", _ContentLength);
serializationInfo.AddValue("_MediaType", _MediaType);
serializationInfo.AddValue("_OriginVerb", _OriginVerb);
serializationInfo.AddValue("_ConnectionGroupName", _ConnectionGroupName);
serializationInfo.AddValue("_Version", _Version, typeof(Version));
serializationInfo.AddValue("_OriginUri", _OriginUri, typeof(Uri));
}
/*++
Routine Description:
GetConnectionGroupLine - Generates a string that
allows a Connection to remain unique for a give NTLM auth
user, this is needed to prevent multiple users from
using the same sockets after they are authenticated.
Arguments:
None.
Return Value:
string - generated result
--*/
private string GetConnectionGroupLine() {
GlobalLog.Enter("GetConnectionGroupLine");
// consider revisting this looking into how this would work,
// if we were doing auth through both proxy and server,
// I belive this can't work in any case, so we're okay.
string internalConnectionGroupName = _ConnectionGroupName;
//
// for schemes of ssl, we need to create a special connection group
// otherwise, we'll try sharing proxy connections across SSL servers
// which won't work.
//
bool tunnelRequest = this is HttpProxyTunnelRequest;
if ((_Uri.Scheme == Uri.UriSchemeHttps) || tunnelRequest) {
if (UsesProxy) {
internalConnectionGroupName = internalConnectionGroupName + ConnectHostAndPort + "$";
}
}
if ( _ProxyAuthenticationState.UniqueGroupId != null ) {
GlobalLog.Leave("GetConnectionGroupLine", ValidationHelper.ToString(internalConnectionGroupName + _ProxyAuthenticationState.UniqueGroupId));
return internalConnectionGroupName + _ProxyAuthenticationState.UniqueGroupId;
}
else if ( _ServerAuthenticationState.UniqueGroupId != null ) {
GlobalLog.Leave("GetConnectionGroupLine", ValidationHelper.ToString(internalConnectionGroupName + _ServerAuthenticationState.UniqueGroupId));
return internalConnectionGroupName + _ServerAuthenticationState.UniqueGroupId;
}
else {
GlobalLog.Leave("GetConnectionGroupLine", ValidationHelper.ToString(internalConnectionGroupName));
return internalConnectionGroupName;
}
}
/*++
Routine Description:
CheckResubmitForAuth - Determines if a HTTP request needs to be
resubmitted due to HTTP authenication
Arguments:
None.
Return Value:
true - if we should reattempt submitting the request
false - if the request is complete
--*/
private bool CheckResubmitForAuth() {
GlobalLog.Enter("CheckResubmitForAuth");
bool result = false;
if (UsesProxySemantics && _Proxy.Credentials!=null) {
result |= _ProxyAuthenticationState.AttemptAuthenticate(this, _Proxy.Credentials);
GlobalLog.Print("CheckResubmitForAuth() _ProxyAuthenticationState.AttemptAuthenticate() returns result:" + result.ToString());
}
if (_AuthInfo != null) {
result |= _ServerAuthenticationState.AttemptAuthenticate(this, _AuthInfo);
GlobalLog.Print("CheckResubmitForAuth() _ServerAuthenticationState.AttemptAuthenticate() returns result:" + result.ToString());
}
GlobalLog.Leave("CheckResubmitForAuth", result);
return result;
}
/*++
Routine Description:
CheckResubmit - Determines if a HTTP request needs to be
resubmitted to a server point, this is called in Response
Parsing to handle cases such as server Redirects,
und Authentication
Arguments:
None.
Return Value:
true - if we should reattempt submitting the request
false - if the request is complete
--*/
private bool CheckResubmit() {
GlobalLog.Enter("CheckResubmit");
GlobalLog.Print("HttpWebRequest#" + ValidationHelper.HashString(this) + "::CheckResubmit()");
if (ResponseStatusCode==HttpStatusCode.Unauthorized || // 401
ResponseStatusCode==HttpStatusCode.ProxyAuthenticationRequired) { // 407
//
// Check for Authentication
//
if (!CheckResubmitForAuth()) {
GlobalLog.Leave("CheckResubmit");
return false;
}
}
else {
//
// Check for Redirection
//
// Table View:
// Method 301 302 303 307
// * * * GET *
// POST GET GET GET POST
//
// Put another way:
// 301 & 302 - All methods are redirected to the same method but POST. POST is redirected to a GET.
// 303 - All methods are redirected to GET
// 307 - All methods are redirected to the same method.
//
if (_AllowAutoRedirect && (
ResponseStatusCode==HttpStatusCode.Ambiguous || // 300
ResponseStatusCode==HttpStatusCode.Moved || // 301
ResponseStatusCode==HttpStatusCode.Redirect || // 302
ResponseStatusCode==HttpStatusCode.RedirectMethod || // 303
ResponseStatusCode==HttpStatusCode.RedirectKeepVerb)) { // 307
_AutoRedirects += 1;
if (_AutoRedirects > _MaximumAllowedRedirections) {
GlobalLog.Leave("CheckResubmit");
return false;
}
string Location = _HttpResponse.Headers["Location"];
string newMethod = _Verb;
bool DisableUpload = false;
switch (ResponseStatusCode) {
case HttpStatusCode.Moved:
case HttpStatusCode.Redirect:
if (string.Compare(newMethod, "POST", true, CultureInfo.InvariantCulture) == 0) {
newMethod = "GET";
DisableUpload = true;
}
break;
case HttpStatusCode.RedirectKeepVerb:
break;
case HttpStatusCode.RedirectMethod:
default:
DisableUpload = true;
newMethod = "GET";
break;
}
GlobalLog.Print("Location = " + Location);
if (Location != null) {
// set possible new Method
GlobalLog.Print("HttpWebRequest#"+ValidationHelper.HashString(this)+": changing Verb from "+_Verb+" to "+newMethod);
_Verb = newMethod;
if (DisableUpload) {
GlobalLog.Print("HttpWebRequest#"+ValidationHelper.HashString(this)+": disabling upload");
_ContentLength = -1;
_HttpWriteMode = HttpWriteMode.None;
_SubmitWriteStream = null;
}
Uri previousUri = _Uri;
try {
_Uri = new Uri(_Uri, Location, true);
}
catch (Exception exception) {
_ResponseException = new WebException(SR.GetString(SR.net_resubmitprotofailed),
exception,
WebExceptionStatus.ProtocolError,
_HttpResponse
);
GlobalLog.Leave("CheckResubmit");
return false;
}
if (_Uri.Scheme != Uri.UriSchemeHttp &&
_Uri.Scheme != Uri.UriSchemeHttps) {
_ResponseException = new WebException(SR.GetString(SR.net_resubmitprotofailed),
null,
WebExceptionStatus.ProtocolError,
_HttpResponse
);
GlobalLog.Leave("CheckResubmit");
return false; // can't handle these redirects
}
try {
//Check for permissions against redirect Uri
(new WebPermission(NetworkAccess.Connect, _Uri.AbsoluteUri)).Demand();
}
catch {
// We are on other thread.
// Don't let the thread die but pass the error on to the user thread.
_ResponseException = new SecurityException(SR.GetString(SR.net_redirect_perm),
new WebException(SR.GetString(SR.net_resubmitcanceled),
null,
WebExceptionStatus.ProtocolError,
_HttpResponse)
);
GlobalLog.Leave("CheckResubmit");
return false;
}
//
// make sure we're not sending over credential information to an evil redirection
// URI. this will set our credentials to null unless the user is using DefaultCredentials
// or a CredentialCache, in which case he is responsible for binding the credentials to
// the proper Uri and AuthenticationScheme.
//
ICredentials authTemp = _AuthInfo as CredentialCache;
if (authTemp==null) {
//
// if it's not a CredentialCache it could still be DefaultCredentials
// check for this case as well
//
_AuthInfo = _AuthInfo as SystemNetworkCredential;
}
else {
_AuthInfo = authTemp;
}
//
// do the necessary cleanup on the Headers involved in the
// Authentication handshake.
//
_ProxyAuthenticationState.ClearAuthReq(this);
_ServerAuthenticationState.ClearAuthReq(this);
GlobalLog.Print("HttpWebRequest#"+ValidationHelper.HashString(this)+": _Verb="+_Verb);
// resubmit
}
else {
GlobalLog.Leave("CheckResubmit");
return false;
}
}
else { // if (_AllowAutoRedirect)
GlobalLog.Leave("CheckResubmit");
return false;
}
} // else
if ((_HttpWriteMode != HttpWriteMode.None) && !AllowWriteStreamBuffering && _ContentLength != 0) {
_ResponseException = new WebException(SR.GetString(SR.net_need_writebuffering),
null,
WebExceptionStatus.ProtocolError,
_HttpResponse);
GlobalLog.Leave("CheckResubmit");
return false;
}
if (System.Net.Connection.IsThreadPoolLow()) {
_ResponseException = new InvalidOperationException(SR.GetString(SR.net_needmorethreads));
return false;
}
GlobalLog.Leave("CheckResubmit");
return true;
}
/*++
Routine Description:
ClearRequestForResubmit - prepares object for resubmission and recall
of submit request, for redirects, authentication, and other uses
This is needed to prevent duplicate headers being added, and other
such things.
Arguments:
None.
Return Value:
None.
--*/
private void ClearRequestForResubmit() {
_HttpRequestHeaders.RemoveInternal(HttpKnownHeaderNames.Host);
_HttpRequestHeaders.RemoveInternal(HttpKnownHeaderNames.Connection);
_HttpRequestHeaders.RemoveInternal(HttpKnownHeaderNames.ProxyConnection);
_HttpRequestHeaders.RemoveInternal(HttpKnownHeaderNames.ContentLength);
_HttpRequestHeaders.RemoveInternal(HttpKnownHeaderNames.TransferEncoding);
_HttpRequestHeaders.RemoveInternal(HttpKnownHeaderNames.Expect);
//
// We just drain the response data, and throw
// it away since we're redirecting, Close handles it.
//
GlobalLog.Print("HttpWebRequest#" + ValidationHelper.HashString(this) + "::ClearRequestForResubmit() closing ResponseStream");
if (_HttpResponse.ResponseStream != null) {
_HttpResponse.ResponseStream.CloseInternal(true);
}
if (_SubmitWriteStream != null) {
//
// we're uploading and need to resubmit for Authentication or Redirect.
// if the response wants to keep alive the connection we shouldn't be closing
// it (this would also brake connection-oriented authentication schemes such as NTLM).
// so we need to flush all the data to the wire.
// if the server is closing the connection, instead, we can just close our side as well.
//
//The other reason to wait for data flushing is when the connection is being closed
//but the body is not written yet. Note that when IsStopped==true, the stream will NOT
//write to the socket, rather buffer/trash til EOF
if (_HttpResponse.KeepAlive || _SubmitWriteStream.IgnoreSocketWrite) {
//
// the server wants to keep the connection alive.
// if we're uploading data, we need to make sure that we upload all
// of it before we start resubmitting.
// give the stream to the user if he didn't get it yet.
// if the user has set ContentLength to a big number, then we might be able
// to just decide to close the connection, but we need to be careful to NTLM.
//
if (_HttpWriteMode==HttpWriteMode.Chunked || _ContentLength>0) {
//
// we're uploading
//
GlobalLog.Print("HttpWebRequest#" + ValidationHelper.HashString(this) + "::ClearRequestForResubmit() _WriteAResult:" + ValidationHelper.ToString(_WriteAResult) + " _WriteStreamRetrieved:" + _WriteStreamRetrieved.ToString());
if (_WriteStreamRetrieved && _WriteAResult!=null) {
//
// the user didn't get the stream yet, give it to him
//
GlobalLog.Print("HttpWebRequest#" + ValidationHelper.HashString(this) + "::ClearRequestForResubmit() calling SetRequestContinue()");
SetRequestContinue();
}
//
// now wait for the user to write all the data to the stream
// since we can't keep the connection alive unless we flush all
// the data we have to the stream.
//
_SubmitWriteStream.WaitWriteDone();
}
}
if (!_SubmitWriteStream.CallInProgress) {
GlobalLog.Print("HttpWebRequest#" + ValidationHelper.HashString(this) + "::ClearRequestForResubmit() closing RequestStream");
_SubmitWriteStream.CloseInternal(true);
}
}
_RerequestCount += 1;
//
// we're done with this response: let garbage-collector have it
//
WriteBuffer = null;
_HttpResponse = null;
_HaveResponse = false;
_WriteNotifed = false;
}
/*++
CheckFinalStatus - Check the final status of an HTTP response.
This is a utility routine called from several places. We look
at the final status of the HTTP response. If it's not 'success',
we'll generate a WebException and throw that. Otherwise we'll
do nothing.
This should get merged into other code eventually. When all of
the async work gets completed and we only generate a response in
one place, this function should be called (or inlined) there.
Input:
Nothing.
Returns:
Nothing. May throw an exception.
--*/
private void CheckFinalStatus() {
// Make sure we have seen a response. We might not have seen one
// yet in some cases.
Exception errorException = null;
GlobalLog.Print("CheckFinalStatus");
if (!_HaveResponse && !_Abort) {
GlobalLog.Print("CheckFinalStatus - returning");
return;
}
// Now see if we have a valid response. If not, throw the response
// exception.
errorException = _ResponseException;
if (_HttpResponse != null) {
errorException = _ResponseException;
GlobalLog.Print("CheckFinalStatus - status" + (int)ResponseStatusCode);
// We have a response. See if it's not valid. It's not valid if the
// response code is greater than 399 (299 if we're not auto following
// redirects.
if (errorException == null && (int)ResponseStatusCode > (int)HttpStatusRange.MaxOkStatus) {
// Not a success status. Could be a redirect, which if OK if we're
// not auto following redirects.
if ((int)ResponseStatusCode > (int)HttpStatusRange.MaxRedirectionStatus ||
_AllowAutoRedirect) {
// Some sort of error. Generate, save and throw a new
// WebException.
if (_AutoRedirects > _MaximumAllowedRedirections) {
errorException = new WebException(
SR.GetString(SR.net_tooManyRedirections),
_ResponseException,
WebExceptionStatus.ProtocolError,
_HttpResponse );
}
else {
errorException = new WebException(
SR.GetString(SR.net_servererror,
NetRes.GetWebStatusCodeString(ResponseStatusCode, _HttpResponse.StatusDescription)),
_ResponseException,
WebExceptionStatus.ProtocolError,
_HttpResponse );
}
}
}
// error, need to throw exception
if (errorException != null) {
//
// Close the Stream to prevent it from breaking the
// connection
if (_HttpResponse.ResponseStream != null) {
//
// copy off stream, don't just close it, if this
// exception isn't handled, we've closed it,
// if it is, then it gets read through a buffer.
//
ConnectStream connStream = _HttpResponse.ResponseStream;
ConnectStream connNewStream = connStream.MakeMemoryStream();
_HttpResponse.ResponseStream = connNewStream;
connStream.CloseInternal(true);
}
_ResponseException = errorException;
_HttpResponse = null;
}
}
if ( errorException != null) {
if (_SubmitWriteStream != null) {
try {
_SubmitWriteStream.CloseInternal(true);
}
catch {
}
_SubmitWriteStream = null;
}
throw errorException;
}
}
/*++
Routine Description:
Range requests to allow for queriable data
Adds a simple range request header to outgoing request
Arguments:
From - Start of range request
To - End of range
Return Value:
None.
--*/
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.AddRange"]/*' />
/// <devdoc>
/// <para>
/// Adds a range header to the request for a specified range.
/// </para>
/// </devdoc>
public void AddRange(int from, int to) {
AddRange("bytes", from, to);
}
/*++
Routine Description:
Adds a simple range request header to outgoing request
Arguments:
Range - Range, this is a negative range, to indicate last_char to range,
or postive to idicate 0 .. Range
Return Value:
None.
--*/
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.AddRange1"]/*' />
/// <devdoc>
/// <para>
/// Adds a range header to a request for a specific
/// range from the beginning or end
/// of the requested data.
/// To add the range from the end pass negative value
/// To add the range from the some offset to the end pass positive value
/// </para>
/// </devdoc>
public void AddRange(int range) {
AddRange("bytes", range);
}
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.AddRange2"]/*' />
public void AddRange(string rangeSpecifier, int from, int to) {
//
// Do some range checking before assembling the header
//
if (rangeSpecifier == null) {
throw new ArgumentNullException("rangeSpecifier");
}
if ((from < 0) || (to < 0)) {
throw new ArgumentOutOfRangeException(SR.GetString(SR.net_rangetoosmall));
}
if (from > to) {
throw new ArgumentOutOfRangeException(SR.GetString(SR.net_fromto));
}
if (!WebHeaderCollection.IsValidToken(rangeSpecifier)) {
throw new ArgumentException(SR.GetString(SR.net_nottoken), "rangeSpecifier");
}
if (!AddRange(rangeSpecifier, from.ToString(), to.ToString())) {
throw new InvalidOperationException(SR.GetString(SR.net_rangetype));
}
}
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.AddRange3"]/*' />
public void AddRange(string rangeSpecifier, int range) {
if (rangeSpecifier == null) {
throw new ArgumentNullException("rangeSpecifier");
}
if (!WebHeaderCollection.IsValidToken(rangeSpecifier)) {
throw new ArgumentException(SR.GetString(SR.net_nottoken), "rangeSpecifier");
}
if (!AddRange(rangeSpecifier, range.ToString(), (range >= 0) ? "" : null)) {
throw new InvalidOperationException(SR.GetString(SR.net_rangetype));
}
}
//
// bool AddRange(rangeSpecifier, from, to)
//
// Add or extend a range header. Various range types can be specified
// via rangeSpecifier, but only one type of Range request will be made
// e.g. a byte-range request, or a row-range request. Range types
// cannot be mixed
//
private bool AddRange(string rangeSpecifier, string from, string to) {
string curRange = _HttpRequestHeaders[HttpKnownHeaderNames.Range];
if ((curRange == null) || (curRange.Length == 0)) {
curRange = rangeSpecifier+"=";
}
else {
if (String.Compare(curRange.Substring(0, curRange.IndexOf('=')), rangeSpecifier, true, CultureInfo.InvariantCulture) != 0) {
return false;
}
curRange = string.Empty;
}
curRange += from.ToString();
if (to != null) {
curRange += "-" + to;
}
_HttpRequestHeaders.SetAddVerified(HttpKnownHeaderNames.Range, curRange);
return true;
}
//
// The callback function called (either by the thread pool or the BeginGetRequest )
// when IO completes.
//
//
// Called after first Response Callback to continue processing
// of response, and handling of redirects retrives in the process
// thereof.
//
//
// The callback function called (either by the thread pool or the BeginGetResponse)
// when IO completes.
//
private int m_HashCode = 0;
private bool m_ComputedHashCode = false;
/// <include file='doc\HttpWebRequest.uex' path='docs/doc[@for="HttpWebRequest.GetHashCode"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public override int GetHashCode() {
if (!m_ComputedHashCode) {
//
// compute HashCode on demand
//
m_HashCode = base.GetHashCode();
m_ComputedHashCode = true;
}
return m_HashCode;
}
}; // class HttpWebRequest
} // namespace System.Net