Uploading files to a NTLM protected site inside a UIWebView using NSUrlSession with Xamarin

When using NSUrlSession to manage requests made from a UIWebView to NTLM protected web content, an upload file request fails when a NTLM handshake is performed. There are three factors that cause the failure to occur: (1) NTLM is a connection-based authentication protocol and the NSUrlSession doesn't always maintain open connections, (2) upload requests sent from the UIWebView load the specified file data into a NSInputStream object, and (3) the NSInputStream class is a read-only stream in one-direction (forward), any subsequent request submissions would be unable to reuse the stream. Therefore, when the initial request is sent with the data to upload and returns a 401:Unauthorized response to begin the NTLM authentication handshake, the data cannot be rewound and uploaded after the authentication has successfully completed.

In order to overcome this limitation of the NSInputStream object, we must write the data from the NSInputStream to a temporary file on the device, then load the data from that file into an NSData object (See below).

//Method to write data from the NSUrlRequest.BodyStream to a temporary file on the device
private string WriteFileToTempDirectory(NSInputStream inputStream) {
	uint bufferLength = 4096;
	byte[] buffer = new byte[bufferLength];

	string outputPath = string.Format("{0}/<temporaryFileName>.txt", NSSearchPath.GetDirectories(NSSearchPathDirectory.CachesDirectory, NSSearchPathDomain.User, true).Last());

	NSOutputStream outputStream = new NSOutputStream(outputPath, false);

	inputStream.Open();
	outputStream.Open();

	try{
		while (inputStream.HasBytesAvailable()) {
			nint bytesRead = inputStream.Read(buffer, bufferLength);
			if (bytesRead > 0) {
				nint bytesWritten = outputStream.Write(buffer, (uint)bytesRead);
				if (bytesWritten < 0){
					break;
				}
				if (bytesWritten == 0 && bytesRead == 0){
					break;
				}
			}
			else{
				break;
			}
		}
	}
	finally{
      inputStream.Close();
      outputStream.Close();
	}
	return outputPath;
}

// Write the data to the device in a file, then load the temporary file contents into a NSData object
string tempDataFile = WriteFileToTempDirectory(<NSUrlRequest>.BodyStream);
NSData bodyData = NSData.FromFile(tempDataFile);

After loading the data into an NSData into an object, we can take advantage of the NSUrlSessionUploadTask to upload the data successfully.

<NSUrlSession>.CreateUploadTask(<NSUrlRequest>, bodyData, (NSData data, NSUrlResponse response, NSError error) => {
	//Task completion logic
}).Resume();