| /* |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| using System; |
| using System.Collections.Generic; |
| using System.IO; |
| using System.IO.IsolatedStorage; |
| using System.Net; |
| using System.Runtime.Serialization; |
| using System.Windows; |
| using System.Security; |
| using System.Diagnostics; |
| |
| namespace WPCordovaClassLib.Cordova.Commands |
| { |
| public class FileTransfer : BaseCommand |
| { |
| public class DownloadRequestState |
| { |
| // This class stores the State of the request. |
| public HttpWebRequest request; |
| public DownloadOptions options; |
| |
| public DownloadRequestState() |
| { |
| request = null; |
| options = null; |
| } |
| } |
| |
| /// <summary> |
| /// Boundary symbol |
| /// </summary> |
| private string Boundary = "----------------------------" + DateTime.Now.Ticks.ToString("x"); |
| |
| // Error codes |
| public const int FileNotFoundError = 1; |
| public const int InvalidUrlError = 2; |
| public const int ConnectionError = 3; |
| |
| /// <summary> |
| /// Options for downloading file |
| /// </summary> |
| [DataContract] |
| public class DownloadOptions |
| { |
| /// <summary> |
| /// File path to download to |
| /// </summary> |
| [DataMember(Name = "filePath", IsRequired = true)] |
| public string FilePath { get; set; } |
| |
| /// <summary> |
| /// Server address to the file to download |
| /// </summary> |
| [DataMember(Name = "url", IsRequired = true)] |
| public string Url { get; set; } |
| } |
| |
| /// <summary> |
| /// Options for uploading file |
| /// </summary> |
| [DataContract] |
| public class UploadOptions |
| { |
| /// <summary> |
| /// File path to upload |
| /// </summary> |
| [DataMember(Name = "filePath", IsRequired = true)] |
| public string FilePath { get; set; } |
| |
| /// <summary> |
| /// Server address |
| /// </summary> |
| [DataMember(Name = "server", IsRequired = true)] |
| public string Server { get; set; } |
| |
| /// <summary> |
| /// File key |
| /// </summary> |
| [DataMember(Name = "fileKey")] |
| public string FileKey { get; set; } |
| |
| /// <summary> |
| /// File name on the server |
| /// </summary> |
| [DataMember(Name = "fileName")] |
| public string FileName { get; set; } |
| |
| /// <summary> |
| /// File Mime type |
| /// </summary> |
| [DataMember(Name = "mimeType")] |
| public string MimeType { get; set; } |
| |
| |
| /// <summary> |
| /// Additional options |
| /// </summary> |
| [DataMember(Name = "params")] |
| public string Params { get; set; } |
| |
| /// <summary> |
| /// Flag to recognize if we should trust every host (only in debug environments) |
| /// </summary> |
| [DataMember(Name = "debug")] |
| public bool Debug { get; set; } |
| |
| /// <summary> |
| /// Creates options object with default parameters |
| /// </summary> |
| public UploadOptions() |
| { |
| this.SetDefaultValues(new StreamingContext()); |
| } |
| |
| /// <summary> |
| /// Initializes default values for class fields. |
| /// Implemented in separate method because default constructor is not invoked during deserialization. |
| /// </summary> |
| /// <param name="context"></param> |
| [OnDeserializing()] |
| public void SetDefaultValues(StreamingContext context) |
| { |
| this.FileKey = "file"; |
| this.FileName = "image.jpg"; |
| this.MimeType = "image/jpeg"; |
| } |
| |
| } |
| |
| /// <summary> |
| /// Uploading response info |
| /// </summary> |
| [DataContract] |
| public class FileUploadResult |
| { |
| /// <summary> |
| /// Amount of sent bytes |
| /// </summary> |
| [DataMember(Name = "bytesSent")] |
| public long BytesSent { get; set; } |
| |
| /// <summary> |
| /// Server response code |
| /// </summary> |
| [DataMember(Name = "responseCode")] |
| public long ResponseCode { get; set; } |
| |
| /// <summary> |
| /// Server response |
| /// </summary> |
| [DataMember(Name = "response", EmitDefaultValue = false)] |
| public string Response { get; set; } |
| |
| /// <summary> |
| /// Creates FileUploadResult object with response values |
| /// </summary> |
| /// <param name="bytesSent">Amount of sent bytes</param> |
| /// <param name="responseCode">Server response code</param> |
| /// <param name="response">Server response</param> |
| public FileUploadResult(long bytesSent, long responseCode, string response) |
| { |
| this.BytesSent = bytesSent; |
| this.ResponseCode = responseCode; |
| this.Response = response; |
| } |
| } |
| |
| /// <summary> |
| /// Represents transfer error codes for callback |
| /// </summary> |
| [DataContract] |
| public class FileTransferError |
| { |
| /// <summary> |
| /// Error code |
| /// </summary> |
| [DataMember(Name = "code", IsRequired = true)] |
| public int Code { get; set; } |
| |
| /// <summary> |
| /// The source URI |
| /// </summary> |
| [DataMember(Name = "source", IsRequired = true)] |
| public string Source { get; set; } |
| |
| /// <summary> |
| /// The target URI |
| /// </summary> |
| [DataMember(Name = "target", IsRequired = true)] |
| public string Target { get; set; } |
| |
| /// <summary> |
| /// The http status code response from the remote URI |
| /// </summary> |
| [DataMember(Name = "http_status", IsRequired = true)] |
| public int HttpStatus { get; set; } |
| |
| /// <summary> |
| /// Creates FileTransferError object |
| /// </summary> |
| /// <param name="errorCode">Error code</param> |
| public FileTransferError(int errorCode) |
| { |
| this.Code = errorCode; |
| this.Source = null; |
| this.Target = null; |
| this.HttpStatus = 0; |
| } |
| public FileTransferError(int errorCode, string source, string target, int status) |
| { |
| this.Code = errorCode; |
| this.Source = source; |
| this.Target = target; |
| this.HttpStatus = status; |
| } |
| } |
| |
| /// <summary> |
| /// Upload options |
| /// </summary> |
| private UploadOptions uploadOptions; |
| |
| /// <summary> |
| /// Bytes sent |
| /// </summary> |
| private long bytesSent; |
| |
| /// <summary> |
| /// sends a file to a server |
| /// </summary> |
| /// <param name="options">Upload options</param> |
| public void upload(string options) |
| { |
| Debug.WriteLine("options = " + options); |
| options = options.Replace("{}", "null"); |
| |
| try |
| { |
| try |
| { |
| string[] args = JSON.JsonHelper.Deserialize<string[]>(options); |
| uploadOptions = JSON.JsonHelper.Deserialize<UploadOptions>(args[0]); |
| } |
| catch (Exception) |
| { |
| DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION)); |
| return; |
| } |
| |
| Uri serverUri; |
| try |
| { |
| serverUri = new Uri(uploadOptions.Server); |
| } |
| catch (Exception) |
| { |
| DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(InvalidUrlError, uploadOptions.Server, null, 0))); |
| return; |
| } |
| HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(serverUri); |
| webRequest.ContentType = "multipart/form-data;boundary=" + Boundary; |
| webRequest.Method = "POST"; |
| webRequest.BeginGetRequestStream(WriteCallback, webRequest); |
| } |
| catch (Exception) |
| { |
| DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(ConnectionError))); |
| } |
| } |
| |
| public void download(string options) |
| { |
| DownloadOptions downloadOptions = null; |
| HttpWebRequest webRequest = null; |
| |
| try |
| { |
| string[] optionStrings = JSON.JsonHelper.Deserialize<string[]>(options); |
| |
| downloadOptions = new DownloadOptions();// JSON.JsonHelper.Deserialize<DownloadOptions>(options); |
| downloadOptions.Url = optionStrings[0]; |
| downloadOptions.FilePath = optionStrings[1]; |
| } |
| catch (Exception) |
| { |
| DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION)); |
| return; |
| } |
| |
| try |
| { |
| webRequest = (HttpWebRequest)WebRequest.Create(downloadOptions.Url); |
| } |
| catch (Exception) |
| { |
| DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(InvalidUrlError, downloadOptions.Url, null, 0))); |
| return; |
| } |
| |
| if (downloadOptions != null && webRequest != null) |
| { |
| DownloadRequestState state = new DownloadRequestState(); |
| state.options = downloadOptions; |
| state.request = webRequest; |
| webRequest.BeginGetResponse(new AsyncCallback(downloadCallback), state); |
| } |
| |
| |
| |
| } |
| |
| /// <summary> |
| /// |
| /// </summary> |
| /// <param name="asynchronousResult"></param> |
| private void downloadCallback(IAsyncResult asynchronousResult) |
| { |
| DownloadRequestState reqState = (DownloadRequestState)asynchronousResult.AsyncState; |
| HttpWebRequest request = reqState.request; |
| |
| try |
| { |
| HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asynchronousResult); |
| |
| using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) |
| { |
| // create the file if not exists |
| if (!isoFile.FileExists(reqState.options.FilePath)) |
| { |
| var file = isoFile.CreateFile(reqState.options.FilePath); |
| file.Close(); |
| } |
| |
| using (FileStream fileStream = new IsolatedStorageFileStream(reqState.options.FilePath, FileMode.Open, FileAccess.Write, isoFile)) |
| { |
| long totalBytes = response.ContentLength; |
| int bytesRead = 0; |
| using (BinaryReader reader = new BinaryReader(response.GetResponseStream())) |
| { |
| |
| using (BinaryWriter writer = new BinaryWriter(fileStream)) |
| { |
| int BUFFER_SIZE = 1024; |
| byte[] buffer; |
| |
| while (true) |
| { |
| buffer = reader.ReadBytes(BUFFER_SIZE); |
| // fire a progress event ? |
| bytesRead += buffer.Length; |
| if (buffer.Length > 0) |
| { |
| writer.Write(buffer); |
| } |
| else |
| { |
| writer.Close(); |
| reader.Close(); |
| fileStream.Close(); |
| break; |
| } |
| } |
| } |
| |
| } |
| |
| |
| } |
| } |
| WPCordovaClassLib.Cordova.Commands.File.FileEntry entry = new WPCordovaClassLib.Cordova.Commands.File.FileEntry(reqState.options.FilePath); |
| DispatchCommandResult(new PluginResult(PluginResult.Status.OK, entry)); |
| } |
| catch (IsolatedStorageException) |
| { |
| // Trying to write the file somewhere within the IsoStorage. |
| DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(FileNotFoundError))); |
| } |
| catch (SecurityException) |
| { |
| // Trying to write the file somewhere not allowed. |
| DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(FileNotFoundError))); |
| } |
| catch (WebException webex) |
| { |
| // TODO: probably need better work here to properly respond with all http status codes back to JS |
| // Right now am jumping through hoops just to detect 404. |
| if ((webex.Status == WebExceptionStatus.ProtocolError && ((HttpWebResponse)webex.Response).StatusCode == HttpStatusCode.NotFound) || webex.Status == WebExceptionStatus.UnknownError) |
| { |
| // Weird MSFT detection of 404... seriously... just give us the f(*&#$@ status code as a number ffs!!! |
| // "Numbers for HTTP status codes? Nah.... let's create our own set of enums/structs to abstract that stuff away." |
| // FACEPALM |
| DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(ConnectionError, null, null, 404))); |
| } |
| else |
| { |
| DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(ConnectionError))); |
| } |
| } |
| catch (Exception) |
| { |
| DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(FileNotFoundError))); |
| } |
| } |
| |
| |
| |
| /// <summary> |
| /// Read file from Isolated Storage and sends it to server |
| /// </summary> |
| /// <param name="asynchronousResult"></param> |
| private void WriteCallback(IAsyncResult asynchronousResult) |
| { |
| try |
| { |
| HttpWebRequest webRequest = (HttpWebRequest)asynchronousResult.AsyncState; |
| using (Stream requestStream = (webRequest.EndGetRequestStream(asynchronousResult))) |
| { |
| string lineStart = "--"; |
| string lineEnd = Environment.NewLine; |
| byte[] boundaryBytes = System.Text.Encoding.UTF8.GetBytes(lineStart + Boundary + lineEnd); |
| string formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"" + lineEnd + lineEnd + "{1}" + lineEnd; |
| |
| if (uploadOptions.Params != null) |
| { |
| |
| string[] arrParams = uploadOptions.Params.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries); |
| |
| foreach (string param in arrParams) |
| { |
| string[] split = param.Split('='); |
| string key = split[0]; |
| string val = split[1]; |
| requestStream.Write(boundaryBytes, 0, boundaryBytes.Length); |
| string formItem = string.Format(formdataTemplate, key, val); |
| byte[] formItemBytes = System.Text.Encoding.UTF8.GetBytes(formItem); |
| requestStream.Write(formItemBytes, 0, formItemBytes.Length); |
| } |
| requestStream.Write(boundaryBytes, 0, boundaryBytes.Length); |
| } |
| using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) |
| { |
| if (!isoFile.FileExists(uploadOptions.FilePath)) |
| { |
| DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(FileNotFoundError, uploadOptions.Server, uploadOptions.FilePath, 0))); |
| return; |
| } |
| |
| using (FileStream fileStream = new IsolatedStorageFileStream(uploadOptions.FilePath, FileMode.Open, isoFile)) |
| { |
| string headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"" + lineEnd + "Content-Type: {2}" + lineEnd + lineEnd; |
| string header = string.Format(headerTemplate, uploadOptions.FileKey, uploadOptions.FileName, uploadOptions.MimeType); |
| byte[] headerBytes = System.Text.Encoding.UTF8.GetBytes(header); |
| requestStream.Write(boundaryBytes, 0, boundaryBytes.Length); |
| requestStream.Write(headerBytes, 0, headerBytes.Length); |
| byte[] buffer = new byte[4096]; |
| int bytesRead = 0; |
| |
| while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0) |
| { |
| requestStream.Write(buffer, 0, bytesRead); |
| bytesSent += bytesRead; |
| } |
| } |
| byte[] endRequest = System.Text.Encoding.UTF8.GetBytes(lineEnd + lineStart + Boundary + lineStart + lineEnd); |
| requestStream.Write(endRequest, 0, endRequest.Length); |
| } |
| } |
| webRequest.BeginGetResponse(ReadCallback, webRequest); |
| } |
| catch (Exception) |
| { |
| Deployment.Current.Dispatcher.BeginInvoke(() => |
| { |
| DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(ConnectionError))); |
| }); |
| } |
| } |
| |
| /// <summary> |
| /// Reads response into FileUploadResult |
| /// </summary> |
| /// <param name="asynchronousResult"></param> |
| private void ReadCallback(IAsyncResult asynchronousResult) |
| { |
| try |
| { |
| HttpWebRequest webRequest = (HttpWebRequest)asynchronousResult.AsyncState; |
| using (HttpWebResponse response = (HttpWebResponse)webRequest.EndGetResponse(asynchronousResult)) |
| { |
| using (Stream streamResponse = response.GetResponseStream()) |
| { |
| using (StreamReader streamReader = new StreamReader(streamResponse)) |
| { |
| string responseString = streamReader.ReadToEnd(); |
| Deployment.Current.Dispatcher.BeginInvoke(() => |
| { |
| DispatchCommandResult(new PluginResult(PluginResult.Status.OK, new FileUploadResult(bytesSent, (long)response.StatusCode, responseString))); |
| }); |
| } |
| } |
| } |
| } |
| catch (Exception) |
| { |
| Deployment.Current.Dispatcher.BeginInvoke(() => |
| { |
| FileTransferError transferError = new FileTransferError(ConnectionError, uploadOptions.Server, uploadOptions.FilePath, 403); |
| DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, transferError)); |
| }); |
| } |
| } |
| } |
| } |