diff --git a/GreenshotImgurPlugin/ImgurUtils.cs b/GreenshotImgurPlugin/ImgurUtils.cs
index ca3d7ed6b..9d5b2014e 100644
--- a/GreenshotImgurPlugin/ImgurUtils.cs
+++ b/GreenshotImgurPlugin/ImgurUtils.cs
@@ -100,35 +100,30 @@ namespace GreenshotImgurPlugin {
/// byte[] with image data
/// ImgurResponse
public static ImgurInfo UploadToImgur(byte[] imageData, int dataLength, string title, string filename) {
- IDictionary uploadParameters = new Dictionary();
- // Add image
- uploadParameters.Add("image", System.Convert.ToBase64String(imageData, 0, dataLength));
- // add type
- uploadParameters.Add("type", "base64");
-
- // add title
- if (title != null) {
- uploadParameters.Add("title", title);
- }
- // add filename
- if (filename != null) {
- uploadParameters.Add("name", filename);
- }
+ IDictionary uploadParameters = new Dictionary();
string responseString = null;
if (config.AnonymousAccess) {
+ // add title
+ if (title != null) {
+ uploadParameters.Add("title", title);
+ }
+ // add filename
+ if (filename != null) {
+ uploadParameters.Add("name", filename);
+ }
+
// add key
uploadParameters.Add("key", IMGUR_ANONYMOUS_API_KEY);
- HttpWebRequest webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(config.ImgurApiUrl + "/upload");
+ HttpWebRequest webRequest = (HttpWebRequest)NetworkHelper.CreateWebRequest(config.ImgurApiUrl + "/upload.xml?" + NetworkHelper.GenerateQueryParameters(uploadParameters));
webRequest.Method = "POST";
- webRequest.ContentType = "application/x-www-form-urlencoded";
+ webRequest.ContentType = "image/png";
webRequest.ServicePoint.Expect100Continue = false;
-
- using(StreamWriter streamWriter = new StreamWriter(webRequest.GetRequestStream())) {
- string urloadText = NetworkHelper.GenerateQueryParameters(uploadParameters);
- streamWriter.Write(urloadText);
+ using (var requestStream = webRequest.GetRequestStream()) {
+ requestStream.Write(imageData, 0, dataLength);
}
+
using (WebResponse response = webRequest.GetResponse()) {
LogCredits(response);
Stream responseStream = response.GetResponseStream();
@@ -144,12 +139,34 @@ namespace GreenshotImgurPlugin {
oAuth.AuthorizeUrl = "http://api.imgur.com/oauth/authorize";
oAuth.RequestTokenUrl = "http://api.imgur.com/oauth/request_token";
oAuth.LoginTitle = "Imgur authorization";
- //oAuth.UseHTTPHeadersForAuthorization = false;
oAuth.Token = config.ImgurToken;
oAuth.TokenSecret = config.ImgurTokenSecret;
+ if (string.IsNullOrEmpty(oAuth.Token)) {
+ if (!oAuth.Authorize()) {
+ return null;
+ }
+ if (!string.IsNullOrEmpty(oAuth.Token)) {
+ config.ImgurToken = oAuth.Token;
+ }
+ if (!string.IsNullOrEmpty(oAuth.TokenSecret)) {
+ config.ImgurTokenSecret = oAuth.TokenSecret;
+ }
+ IniConfig.Save();
+ }
try {
- LOG.DebugFormat("Test: {0}", oAuth.MakeOAuthRequest(HTTPMethod.GET, "http://api.imgur.com/2/account", null));
- responseString = oAuth.MakeOAuthRequest(HTTPMethod.POST, "http://api.imgur.com/2/account/images.xml", uploadParameters);
+ string apiUrl = "http://api.imgur.com/2/account/images.xml";
+ // sign without parameters
+ oAuth.Sign(HTTPMethod.POST, apiUrl, uploadParameters);
+ // add title
+ if (title != null) {
+ uploadParameters.Add("title", title);
+ }
+ // add filename
+ if (filename != null) {
+ uploadParameters.Add("name", filename);
+ }
+ uploadParameters.Add("image", new FileParameter(imageData, filename, "image/png", dataLength));
+ responseString = oAuth.MakeRequest(HTTPMethod.POST, apiUrl, uploadParameters, null, null);
} catch (Exception ex) {
LOG.Error("Upload to imgur gave an exeption: ", ex);
throw ex;
@@ -163,9 +180,7 @@ namespace GreenshotImgurPlugin {
IniConfig.Save();
}
}
- LOG.Info(responseString);
- ImgurInfo imgurInfo = ImgurInfo.ParseResponse(responseString);
- return imgurInfo;
+ return ImgurInfo.ParseResponse(responseString);
}
public static void RetrieveImgurThumbnail(ImgurInfo imgurInfo) {
diff --git a/GreenshotPhotobucketPlugin/PhotobucketUtils.cs b/GreenshotPhotobucketPlugin/PhotobucketUtils.cs
index 5d58fe118..4c15241dd 100644
--- a/GreenshotPhotobucketPlugin/PhotobucketUtils.cs
+++ b/GreenshotPhotobucketPlugin/PhotobucketUtils.cs
@@ -95,7 +95,7 @@ namespace GreenshotPhotobucketPlugin {
oAuth.Sign(HTTPMethod.POST, apiUrl, parameters);
apiUrl = apiUrl.Replace("api.photobucket.com", config.SubDomain);
// Add image
- parameters.Add("uploadfile", System.Convert.ToBase64String(imageData, 0, dataLength));
+ parameters.Add("uploadfile", new FileParameter(imageData, filename, "image/png", dataLength));
responseString = oAuth.MakeRequest(HTTPMethod.POST, apiUrl, parameters, null, null);
} catch (Exception ex) {
LOG.Error("Error uploading to Photobucket.", ex);
diff --git a/GreenshotPlugin/Core/NetworkHelper.cs b/GreenshotPlugin/Core/NetworkHelper.cs
index bf2b3db00..69cd4f885 100644
--- a/GreenshotPlugin/Core/NetworkHelper.cs
+++ b/GreenshotPlugin/Core/NetworkHelper.cs
@@ -222,24 +222,24 @@ namespace GreenshotPlugin.Core {
}
///
- /// Generate the query paramters
- ///
- /// the list of query parameters
- /// a string with the query parameters
- public static string GenerateQueryParameters(IDictionary queryParameters) {
- if (queryParameters == null || queryParameters.Count == 0) {
- return string.Empty;
- }
-
- queryParameters = new SortedDictionary(queryParameters);
+ /// Generate the query paramters
+ ///
+ /// the list of query parameters
+ /// a string with the query parameters
+ public static string GenerateQueryParameters(IDictionary queryParameters) {
+ if (queryParameters == null || queryParameters.Count == 0) {
+ return string.Empty;
+ }
+
+ queryParameters = new SortedDictionary(queryParameters);
StringBuilder sb = new StringBuilder();
foreach(string key in queryParameters.Keys) {
- sb.AppendFormat(System.Globalization.CultureInfo.InvariantCulture, "{0}={1}&", key, UrlEncode(queryParameters[key]));
+ sb.AppendFormat(System.Globalization.CultureInfo.InvariantCulture, "{0}={1}&", key, UrlEncode(string.Format("{0}",queryParameters[key])));
}
sb.Remove(sb.Length-1,1);
return sb.ToString();
- }
+ }
}
}
diff --git a/GreenshotPlugin/Core/OAuthHelper.cs b/GreenshotPlugin/Core/OAuthHelper.cs
index a059dc489..4a0735e5a 100644
--- a/GreenshotPlugin/Core/OAuthHelper.cs
+++ b/GreenshotPlugin/Core/OAuthHelper.cs
@@ -41,6 +41,22 @@ namespace GreenshotPlugin.Core {
public enum HTTPMethod { GET, POST, PUT, DELETE };
+ public class FileParameter {
+ public byte[] File { get; set; }
+ public string FileName { get; set; }
+ public string ContentType { get; set; }
+ public int FileSize {get; set; }
+ public FileParameter(byte[] file) : this(file, null) { }
+ public FileParameter(byte[] file, string filename) : this(file, filename, null) { }
+ public FileParameter(byte[] file, string filename, string contenttype) : this(file, filename, contenttype, 0) { }
+ public FileParameter(byte[] file, string filename, string contenttype, int filesize) {
+ File = file;
+ FileName = filename;
+ ContentType = contenttype;
+ FileSize = filesize;
+ }
+ }
+
public class OAuthSession {
private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(OAuthSession));
protected const string OAUTH_VERSION = "1.0";
@@ -100,11 +116,31 @@ namespace GreenshotPlugin.Core {
private string loginTitle = "Authorize Greenshot access";
#region PublicPropertiies
- public string UserAgent { get { return _userAgent; } set { _userAgent = value; } }
public string RequestTokenUrl { get; set; }
public string AuthorizeUrl { get; set; }
public string AccessTokenUrl { get; set; }
- public string CallbackUrl { get { return _callbackUrl;} set { _callbackUrl = value; } }
+
+ public string Token { get; set; }
+ public string TokenSecret { get; set; }
+ public string Verifier { get; set; }
+
+ public bool UseMultipartFormData { get; set; }
+ public string UserAgent {
+ get {
+ return _userAgent;
+ }
+ set {
+ _userAgent = value;
+ }
+ }
+ public string CallbackUrl {
+ get {
+ return _callbackUrl;
+ }
+ set {
+ _callbackUrl = value;
+ }
+ }
public bool CheckVerifier {
get {
return checkVerifier;
@@ -123,12 +159,6 @@ namespace GreenshotPlugin.Core {
}
}
- public string Token {
- get;
- set;
- }
- public string TokenSecret { get; set; }
-
public string LoginTitle {
get {
return loginTitle;
@@ -137,10 +167,6 @@ namespace GreenshotPlugin.Core {
loginTitle = value;
}
}
- public string Verifier {
- get;
- set;
- }
public bool UseHTTPHeadersForAuthorization {
get {
return useHTTPHeadersForAuthorization;
@@ -155,6 +181,7 @@ namespace GreenshotPlugin.Core {
public OAuthSession(string consumerKey, string consumerSecret) {
this.consumerKey = consumerKey;
this.consumerSecret = consumerSecret;
+ this.UseMultipartFormData = true;
}
///
@@ -183,16 +210,18 @@ namespace GreenshotPlugin.Core {
///
/// the list of query parameters
/// a string with the normalized query parameters
- private static string GenerateNormalizedParametersString(IDictionary queryParameters) {
+ private static string GenerateNormalizedParametersString(IDictionary queryParameters) {
if (queryParameters == null || queryParameters.Count == 0) {
return string.Empty;
}
- queryParameters = new SortedDictionary(queryParameters);
+ queryParameters = new SortedDictionary(queryParameters);
StringBuilder sb = new StringBuilder();
foreach (string key in queryParameters.Keys) {
- sb.AppendFormat(System.Globalization.CultureInfo.InvariantCulture, "{0}={1}&", key, UrlEncode3986(queryParameters[key]));
+ if (queryParameters[key] is string) {
+ sb.AppendFormat(System.Globalization.CultureInfo.InvariantCulture, "{0}={1}&", key, UrlEncode3986(string.Format("{0}",queryParameters[key])));
+ }
}
sb.Remove(sb.Length - 1, 1);
@@ -245,7 +274,7 @@ namespace GreenshotPlugin.Core {
/// The request token.
private String getRequestToken() {
string ret = null;
- IDictionary parameters = new Dictionary();
+ IDictionary parameters = new Dictionary();
Sign(HTTPMethod.POST, RequestTokenUrl, parameters);
string response = MakeRequest(HTTPMethod.POST, RequestTokenUrl, parameters, null, null);
LOG.DebugFormat("Request token response: {0}", response);
@@ -295,7 +324,7 @@ namespace GreenshotPlugin.Core {
throw e;
}
- IDictionary parameters = new Dictionary();
+ IDictionary parameters = new Dictionary();
Sign(HTTPMethod.POST, AccessTokenUrl, parameters);
string response = MakeRequest(HTTPMethod.POST, AccessTokenUrl, parameters, null, null);
LOG.DebugFormat("Access token response: {0}", response);
@@ -351,7 +380,7 @@ namespace GreenshotPlugin.Core {
///
///
///
- public string MakeOAuthRequest(HTTPMethod method, string requestURL, IDictionary parameters) {
+ public string MakeOAuthRequest(HTTPMethod method, string requestURL, IDictionary parameters) {
return MakeOAuthRequest(method, requestURL, parameters, null, null);
}
@@ -364,9 +393,9 @@ namespace GreenshotPlugin.Core {
/// contenttype for the postdata
/// Data to post (MemoryStream)
/// The web server response.
- public string MakeOAuthRequest(HTTPMethod method, string requestURL, IDictionary parameters, string contentType, MemoryStream postData) {
+ public string MakeOAuthRequest(HTTPMethod method, string requestURL, IDictionary parameters, string contentType, MemoryStream postData) {
if (parameters == null) {
- parameters = new Dictionary();
+ parameters = new Dictionary();
}
int retries = 2;
Exception lastException = null;
@@ -389,7 +418,7 @@ namespace GreenshotPlugin.Core {
TokenSecret = null;
// Remove oauth keys, so they aren't added double
- IDictionary newParameters = new Dictionary();
+ IDictionary newParameters = new Dictionary();
foreach (string parameterKey in parameters.Keys) {
if (!parameterKey.StartsWith(OAUTH_PARAMETER_PREFIX)) {
newParameters.Add(parameterKey, parameters[parameterKey]);
@@ -415,7 +444,7 @@ namespace GreenshotPlugin.Core {
/// Method (POST,PUT,GET)
/// Url to call
/// IDictionary
- public void Sign(HTTPMethod method, string requestURL, IDictionary parameters) {
+ public void Sign(HTTPMethod method, string requestURL, IDictionary parameters) {
if (parameters == null) {
throw new ArgumentNullException("parameters");
}
@@ -450,6 +479,7 @@ namespace GreenshotPlugin.Core {
parameters.Add(OAUTH_TOKEN_KEY, Token);
}
signatureBase.Append(UrlEncode3986(GenerateNormalizedParametersString(parameters)));
+ LOG.DebugFormat("Signature base: {0}", signatureBase);
// Generate Signature and add it to the parameters
HMACSHA1 hmacsha1 = new HMACSHA1();
hmacsha1.Key = Encoding.UTF8.GetBytes(string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}&{1}", UrlEncode3986(consumerSecret), string.IsNullOrEmpty(TokenSecret) ? string.Empty : UrlEncode3986(TokenSecret)));
@@ -467,19 +497,19 @@ namespace GreenshotPlugin.Core {
///
///
/// Response from server
- public string MakeRequest(HTTPMethod method, string requestURL, IDictionary parameters, string contentType, MemoryStream postData) {
+ public string MakeRequest(HTTPMethod method, string requestURL, IDictionary parameters, string contentType, MemoryStream postData) {
if (parameters == null) {
throw new ArgumentNullException("parameters");
}
- IDictionary requestParameters = null;
+ IDictionary requestParameters = null;
// Add oAuth values as HTTP headers, if this is allowed
StringBuilder authHeader = null;
if (HTTPMethod.POST == method && UseHTTPHeadersForAuthorization) {
authHeader = new StringBuilder();
- requestParameters = new Dictionary();
+ requestParameters = new Dictionary();
foreach (string parameterKey in parameters.Keys) {
if (parameterKey.StartsWith(OAUTH_PARAMETER_PREFIX)) {
- authHeader.AppendFormat(System.Globalization.CultureInfo.InvariantCulture, "{0}=\"{1}\", ", parameterKey, UrlEncode3986(parameters[parameterKey]));
+ authHeader.AppendFormat(System.Globalization.CultureInfo.InvariantCulture, "{0}=\"{1}\", ", parameterKey, UrlEncode3986(string.Format("{0}",parameters[parameterKey])));
} else if (!requestParameters.ContainsKey(parameterKey)) {
requestParameters.Add(parameterKey, parameters[parameterKey]);
}
@@ -509,18 +539,32 @@ namespace GreenshotPlugin.Core {
}
if (HTTPMethod.POST == method && postData == null && requestParameters != null && requestParameters.Count > 0) {
- StringBuilder form = new StringBuilder();
- foreach (string parameterKey in requestParameters.Keys) {
- form.AppendFormat(System.Globalization.CultureInfo.InvariantCulture, "{0}={1}&", UrlEncode3986(parameterKey), UrlEncode3986(parameters[parameterKey]));
- }
- // Remove trailing &
- if (form.Length > 0) {
- form.Remove(form.Length - 1, 1);
- }
- webRequest.ContentType = "application/x-www-form-urlencoded";
- byte[] data = Encoding.UTF8.GetBytes(form.ToString());
- using (var requestStream = webRequest.GetRequestStream()) {
- requestStream.Write(data, 0, data.Length);
+
+ if (UseMultipartFormData) {
+ byte [] data = GetMultipartFormData(requestParameters, out contentType);
+ webRequest.ContentType = contentType;
+ using (var requestStream = webRequest.GetRequestStream()) {
+ requestStream.Write(data, 0, data.Length);
+ }
+ } else {
+ StringBuilder form = new StringBuilder();
+ foreach (string parameterKey in requestParameters.Keys) {
+ if (parameters[parameterKey] is FileParameter) {
+ FileParameter fileParameter = parameters[parameterKey] as FileParameter;
+ form.AppendFormat(System.Globalization.CultureInfo.InvariantCulture, "{0}={1}&", UrlEncode3986(parameterKey), UrlEncode3986(System.Convert.ToBase64String(fileParameter.File, 0, fileParameter.FileSize != 0 ? fileParameter.FileSize : fileParameter.File.Length)));
+ } else {
+ form.AppendFormat(System.Globalization.CultureInfo.InvariantCulture, "{0}={1}&", UrlEncode3986(parameterKey), UrlEncode3986(string.Format("{0}",parameters[parameterKey])));
+ }
+ }
+ // Remove trailing &
+ if (form.Length > 0) {
+ form.Remove(form.Length - 1, 1);
+ }
+ webRequest.ContentType = "application/x-www-form-urlencoded";
+ byte[] data = Encoding.UTF8.GetBytes(form.ToString());
+ using (var requestStream = webRequest.GetRequestStream()) {
+ requestStream.Write(data, 0, data.Length);
+ }
}
} else {
webRequest.ContentType = contentType;
@@ -538,6 +582,67 @@ namespace GreenshotPlugin.Core {
return responseData;
}
+
+ ///
+ /// Create a Multipart Form Data as byte[]
+ ///
+ /// Parameters to include in the multipart form data
+ /// out parameter for contenttype
+ /// byte[] with Multipart Form Data which can be used to upload
+ private static byte[] GetMultipartFormData(IDictionary postParameters, out string contentType) {
+ string boundary = String.Format("----------{0:N}", Guid.NewGuid());
+ contentType = "multipart/form-data; boundary=" + boundary;
+ Stream formDataStream = new MemoryStream();
+ bool needsCLRF = false;
+
+ foreach (var param in postParameters) {
+ // Thanks to feedback from commenters, add a CRLF to allow multiple parameters to be added.
+ // Skip it on the first parameter, add it to subsequent parameters.
+ if (needsCLRF) {
+ formDataStream.Write(Encoding.UTF8.GetBytes("\r\n"), 0, Encoding.UTF8.GetByteCount("\r\n"));
+ }
+
+ needsCLRF = true;
+
+ if (param.Value is FileParameter) {
+ FileParameter fileToUpload = (FileParameter)param.Value;
+
+ // Add just the first part of this param, since we will write the file data directly to the Stream
+ string header = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{2}\";\r\nContent-Type: {3}\r\n\r\n",
+ boundary,
+ param.Key,
+ fileToUpload.FileName ?? param.Key,
+ fileToUpload.ContentType ?? "application/octet-stream");
+
+ formDataStream.Write(Encoding.UTF8.GetBytes(header), 0, Encoding.UTF8.GetByteCount(header));
+
+ // Write the file data directly to the Stream, rather than serializing it to a string.
+ if (fileToUpload.FileSize > 0) {
+ formDataStream.Write(fileToUpload.File, 0, fileToUpload.FileSize);
+ } else {
+ formDataStream.Write(fileToUpload.File, 0, fileToUpload.File.Length);
+ }
+ } else {
+ string postData = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}",
+ boundary,
+ param.Key,
+ param.Value);
+ formDataStream.Write(Encoding.UTF8.GetBytes(postData), 0, Encoding.UTF8.GetByteCount(postData));
+ }
+ }
+
+ // Add the end of the request. Start with a newline
+ string footer = "\r\n--" + boundary + "--\r\n";
+ formDataStream.Write(Encoding.UTF8.GetBytes(footer), 0, Encoding.UTF8.GetByteCount(footer));
+
+ // Dump the Stream into a byte[]
+ formDataStream.Position = 0;
+ byte[] formData = new byte[formDataStream.Length];
+ formDataStream.Read(formData, 0, formData.Length);
+ formDataStream.Close();
+
+ return formData;
+ }
///
/// Process the web response.