Skip to content

Instantly share code, notes, and snippets.

@ificator
Last active March 26, 2018 22:53
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ificator/3460d7b9d0bff74eb0ff to your computer and use it in GitHub Desktop.
Save ificator/3460d7b9d0bff74eb0ff to your computer and use it in GitHub Desktop.
//#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