Last active
March 26, 2018 22:53
-
-
Save ificator/3460d7b9d0bff74eb0ff to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//#define DIRECTBITSPOST | |
//#define FORCEFIDDLER | |
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Net; | |
using System.Text; | |
using System.Threading.Tasks; | |
namespace FragmentUpload | |
{ | |
/* | |
** Uploads a large file to OneDrive using the process outlined at https://gist.github.com/rgregg/37ba8929768a62131e85 | |
*/ | |
public class ChunkedUpload | |
{ | |
// Headers | |
private const string Authorization = "Authorization"; | |
private const string BITSPacketType = "BITS-Packet-Type"; | |
private const string BITSSessionId = "BITS-Session-Id"; | |
private const string ContentRange = "Content-Range"; | |
// BITS Constants | |
private const string BITSAck = "Ack"; | |
private const string BITSCloseSession = "Close-Session"; | |
private const string BITSCreateSession = "Create-Session"; | |
private const string BITSFragment = "Fragment"; | |
private const string BITSPost = "BITS_POST"; | |
private const string BITSSupportedProtocols = "BITS-Supported-Protocols"; | |
// Arbitrarily defined chunk size. | |
private const long ChunkSize = 512 * 1024; | |
static void Main(string[] args) | |
{ | |
try | |
{ | |
if (args.Length != 3) | |
{ | |
Console.WriteLine("<token> <url> <file>"); | |
Console.WriteLine(); | |
Console.WriteLine("Valid URLs are of the following types:"); | |
Console.WriteLine("Id based = https://cid-{id}.users.storage.live.com/items/{folder-id}/{filename}"); | |
Console.WriteLine("Path based = https://cid-{id}.users.storage.live.com/users/0x{id}/LiveFolders/{folder-path}/{filename}"); | |
return; | |
} | |
string authorization = "bearer " + args[0]; | |
string url = args[1]; | |
string file = args[2]; | |
if (!File.Exists(file)) | |
{ | |
Console.WriteLine("{0} doesn't exist", file); | |
return; | |
} | |
string sessionId = ChunkedUpload.GetBitsSessionId(authorization, url); | |
using (FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read)) | |
{ | |
while (fileStream.Position < fileStream.Length - 1) | |
{ | |
ChunkedUpload.WriteChunkToSession(authorization, url, sessionId, fileStream); | |
} | |
} | |
ChunkedUpload.CommitSession(authorization, url, sessionId); | |
} | |
catch (Exception ex) | |
{ | |
Console.WriteLine("{0}", ex.GetType().Name); | |
Console.WriteLine("{0}", ex.Message); | |
} | |
} | |
private static void ApplyMethod(WebRequest request) | |
{ | |
#if DIRECTBITSPOST | |
request.Method = Program.BITSPost; | |
#else | |
request.Headers["X-Http-Method-Override"] = ChunkedUpload.BITSPost; | |
request.Method = System.Net.WebRequestMethods.Http.Post; | |
// Since we're doing a POST we must ensure the ContentLength header is provided. | |
request.ContentLength = 0; | |
#endif | |
} | |
private static void ApplyWebProxy(WebRequest request) | |
{ | |
#if FORCEFIDDLER | |
request.Proxy = new WebProxy("http://localhost:8888"); | |
#else | |
request.Proxy = null; | |
#endif | |
} | |
private static void CommitSession(string authorization, string url, string sessionId) | |
{ | |
Console.WriteLine("CommitSession: {0}@{1}", sessionId, url); | |
HttpWebRequest httpWebRequest = (HttpWebRequest)HttpWebRequest.Create(url); | |
httpWebRequest.Headers.Add(ChunkedUpload.Authorization, authorization); | |
httpWebRequest.Headers.Add(ChunkedUpload.BITSPacketType, ChunkedUpload.BITSCloseSession); | |
httpWebRequest.Headers.Add(ChunkedUpload.BITSSessionId, sessionId); | |
ChunkedUpload.ApplyMethod(httpWebRequest); | |
ChunkedUpload.ApplyWebProxy(httpWebRequest); | |
using (HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse()) | |
{ | |
string packetType = httpWebResponse.Headers[ChunkedUpload.BITSPacketType]; | |
if (packetType == null || !packetType.Equals(ChunkedUpload.BITSAck, StringComparison.OrdinalIgnoreCase)) | |
{ | |
throw new InvalidOperationException("Response was not an ACK"); | |
} | |
} | |
} | |
private static string GetBitsSessionId(string authorization, string url) | |
{ | |
Console.WriteLine("GetBitsSessionId: {0}", url); | |
HttpWebRequest httpWebRequest = (HttpWebRequest)HttpWebRequest.Create(url); | |
httpWebRequest.Headers.Add(ChunkedUpload.Authorization, authorization); | |
httpWebRequest.Headers.Add(ChunkedUpload.BITSPacketType, ChunkedUpload.BITSCreateSession); | |
httpWebRequest.Headers.Add(ChunkedUpload.BITSSupportedProtocols, "{7df0354d-249b-430f-820d-3d2a9bef4931}"); // This is a special GUID that represents the protocol... DO NOT CHANGE! | |
ChunkedUpload.ApplyMethod(httpWebRequest); | |
ChunkedUpload.ApplyWebProxy(httpWebRequest); | |
using (HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse()) | |
{ | |
string packetType = httpWebResponse.Headers[ChunkedUpload.BITSPacketType]; | |
if (packetType == null || !packetType.Equals(ChunkedUpload.BITSAck, StringComparison.OrdinalIgnoreCase)) | |
{ | |
throw new InvalidOperationException("Response was not an ACK"); | |
} | |
string sessionId = httpWebResponse.Headers[ChunkedUpload.BITSSessionId]; | |
if (sessionId == null) | |
{ | |
throw new InvalidOperationException("No session id found in ACK"); | |
} | |
return sessionId; | |
} | |
} | |
private static void WriteChunkToSession(string authorization, string url, string sessionId, Stream inputStream) | |
{ | |
Console.WriteLine("WriteChunkToSession: {0}@{1}", sessionId, url); | |
long bytesToWrite = Math.Min(inputStream.Length - inputStream.Position, ChunkedUpload.ChunkSize); | |
HttpWebRequest httpWebRequest = (HttpWebRequest)HttpWebRequest.Create(url); | |
httpWebRequest.Headers.Add(ChunkedUpload.Authorization, authorization); | |
httpWebRequest.Headers.Add(ChunkedUpload.ContentRange, string.Format("bytes {0}-{1}/{2}", inputStream.Position, inputStream.Position + bytesToWrite - 1, inputStream.Length)); | |
httpWebRequest.Headers.Add(ChunkedUpload.BITSPacketType, ChunkedUpload.BITSFragment); | |
httpWebRequest.Headers.Add(ChunkedUpload.BITSSessionId, sessionId); | |
ChunkedUpload.ApplyMethod(httpWebRequest); | |
ChunkedUpload.ApplyWebProxy(httpWebRequest); | |
// Since we actually have content make sure we set the Content-Length correctly. | |
httpWebRequest.ContentLength = bytesToWrite; | |
using (Stream requestStream = httpWebRequest.GetRequestStream()) | |
{ | |
byte[] buffer = new byte[80 * 1024]; | |
while (bytesToWrite > 0) | |
{ | |
int bytesRead = inputStream.Read(buffer, 0 /* offset */, (int)Math.Min(buffer.Length, bytesToWrite)); | |
requestStream.Write(buffer, 0 /* offset */, bytesRead); | |
bytesToWrite -= bytesRead; | |
} | |
} | |
using (HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse()) | |
{ | |
string packetType = httpWebResponse.Headers[ChunkedUpload.BITSPacketType]; | |
if (packetType == null || !packetType.Equals(ChunkedUpload.BITSAck, StringComparison.OrdinalIgnoreCase)) | |
{ | |
throw new InvalidOperationException("Response was not an ACK"); | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment