Uploading files to a NTLM protected site inside a UIWebView using NSUrlSession with Xamarin
Feb 17, 2015
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();