Sending a file from my application (Indy/Delphi) to an ASP page and then onto another server (Amazon S3) - delphi

I have a need to store files on Amazon AWS S3, but in order to isolate the user from the AWS authentication I want to go via an ASP page on my site, which the user will be logged into. So:
The application sends the file using the Delphi Indy library TidHTTP.Put (FileStream) routine to the ASP page, along with some authentication stuff (mine, not AWS) on the querystring.
The ASP page checks the auth details and then if OK stores the file on S3 using my Amazon account.
Problem I have is: how do I access the data coming in from the Indy PUT using JScript in the ASP page and pass it on to S3. I'm OK with AWS signing, etc, it's just the nuts and bolts of connecting the two bits (the incoming request and the outgoing AWS request) ...
TIA
R

A HTTP PUT will store the file at the given location in the HTTP header - it "requests that the enclosed entity be stored under the supplied Request-URI".
The disadvantage with the PUT method is that if you are on a shared hosting environment it may not be available to you.
So if the web server supports PUT, the file should be available at the given location in the the (virtual) file system. The PUT request will be handled by the server and not ASP:
In the case of PUT, the web server
handles the request itself: there is
no room for a CGI or ASP application
to step in.
The only way for your application to
capture a PUT is to operate on the
low-level, ISAPI filter level
http://www.15seconds.com/issue/981120.htm
Are you sure you need PUT and can not use a POST, which will send the file to a URL where your ASP script can read it from the request stream?

OK, Ive got a bit further with this. Code at the ASP end is:
var PostedDataSize = Request.TotalBytes ;
var PostedData = Request.BinaryRead (PostedDataSize) ;
var PostedDataStream = Server.CreateObject ("ADODB.Stream") ;
PostedDataStream.Open ;
PostedDataStream.Type = 1 ; // binary
PostedDataStream.Write (PostedData) ;
Response.Write ("PostedDataStream.Size = " + PostedDataStream.Size + "<br>") ;
var XML = AmazonAWSPUTRequest (BucketName, AWSDestinationFileID, PostedDataStream) ;
.....
function AmazonAWSPUTRequest (Bucket, Filename, InputStream)
{
....
XMLHttp.open ("PUT", URL + FRequest, false) ;
XMLHttp.setRequestHeader (....
XMLHttp.setRequestHeader (....
...
Response.Write ("InputStream.Size = " + InputStream.Size + "<br>") ;
XMLHttp.send (InputStream) ;
So I use BinaryRead, write it to a binary stream. If I write out the size of the stream I get the size of the file I POST'ed from my application, so I reckon the data is in there somewhere. I then call a routine (with the stream as a parameter) which sets up the AWS authentication/signing and does a PUT.
The AWS call returns no errors and a file of the correct name is created in the right place, but it has a size of zero! InputStream.Size has a value the same as the stream parameter passed to the routine - i.e. the size of the original file.
Any ideas?
POSTSCRIPT. Found the problem. It's caught me a few times with streams, this one. When you write data to a stream, don't forget to reset the stream position back to zero before trying to read from the stream again. I.e. just before the line:
XMLHttp.send (InputStream) ;
I needed to add:
InputStream.Position = 0 ;
My thanks for the interest and suggestions.

Related

Azure blob authorization header

I am trying to use refit to upload to azure blob storage from a Xamarin iOS application. This is the interface configuration I am using for Refit:
[Headers("x-ms-blob-type: BlockBlob")]
[Put("/{fileName}")]
Task<bool> UploadAsync([Body]byte[] content, string sasTokenKey,
[Header("Content-Type")] string contentType);
Where the sasTokenKey parameter looks like this:
"/content-default/1635839001660743375-66f93195-e923-4c8b-a3f1-5f3f9ba9dd32.jpeg?sv=2015-04-05&sr=b&sig=Up26vDxQikFqo%2FDQjRB08YtmK418rZfKx1IHbYKAjIE%3D&se=2015-11-23T18:59:26Z&sp=w"
This is how I am using Refit to call the azure blob server:
var myRefitApi = RestService.For<IMyRefitAPI>("https://myaccount.blob.core.windows.net");
myRefitApi.UploadAsync(photoBytes, sasTokenKey, "image/jpeg"
However I am getting the follow error:
Response status code does not indicate success: 403 (Server failed to
authenticate the request. Make sure the value of Authorization header is
formed correctly including the signature.)
The SAS url is working fine if I call it directly like this
var content = new StreamContent(stream);
content.Headers.Add("Content-Type", "jpeg");
content.Headers.Add("x-ms-blob-type", "BlockBlob");
var task = HttpClient.PutAsync(new Uri(sasTokenUrl), content);
task.Wait();
So basically I am just trying to do the same thing using Refit.
Any idea how to get Refit working with Azure Blob Storage?
Thanks!
[UPDATE] I am now able to upload the bytes to the azure blob server but something seems to be wrong with the byte data because I am not able to view the image. Here is the code I am using to convert to byte array.
byte[] bytes;
using (var ms = new MemoryStream())
{
stream.Position = 0;
stream.CopyTo(ms);
ms.Position = 0;
bytes = ms.ToArray();
}
[UPDATE] Got it fixed by using stream instead of byte array!
I see %2F and %3D and I'm curious if refit is encoding those a second time. Try sending the token without encoding it.
This is incorrect use of Authorization header. You use Authorization header when you want to authorize the requests using account key. If you have the Shared Access Signature then you really don't need this header as the authorization information is included in the SAS itself. You can simply use the SAS URL for uploading files.

How to construct/get Office Web App URL for sharepoint documents

I am trying to get the right redirection URL for my sharepoint documents which then I can use to open documents in WebView of iOS. Currently I am giving the absolute URL for the document where the doc is rendered inside WebView as PDF(Image/Readonly). Whereas I want to redirect to office webapp. Now my issue is I dont know if the URL for office web app is something which I can construct like appending /_layouts/15/WopiFrame.aspx?sourcedoc= or is the URL custom based on installations and we need to call some Sharepoint API which will let us know what is the base URL for Wopi service.
Currently I am passing URL like - https://.sharepoint.com/Shared%20Documents/demo/demo.docx
Whereas I want to pass URL like - https://.sharepoint.com/_layouts/15/WopiFrame.aspx?sourcedoc=/Shared%20Documents/demo/demo.docx
Looking forward for help.
Thanks in advance,
Vishwesh
File f = clientContext.Web.GetFileByServerRelativeUrl("/sites/ /Shared%20Documents/Title.docx");
clientContext.Load(f);
clientContext.ExecuteQuery();
ClientResult<String> result = f.ListItemAllFields.GetWOPIFrameUrl(SPWOPIFrameAction.Edit);
clientContext.Load(f.ListItemAllFields);
clientContext.ExecuteQuery();
result.Value contains a URL, something like this:
http://sharep.xxx:8080/sites/zxxx/_layouts/15/WopiFrame.aspx?sourcedoc=%2Fsites%2Fzxxx%2FShared%20Documents%2FTitle%2Edocx&action=edit
Also you can extract the extract Office Web Apps URL from the above page, if you don't want to hit the sharepoint at all.
using Microsoft.SharePoint.Client;
using Microsoft.SharePoint.Client.Utilities;
// Assume we have these variables:
// ctx: A valid client context
// serverRelativeUrl: the URL of the document
File f = ctx.Web.GetFileByServerRelativeUrl (serverRelativeUrl);
result = f.ListItemAllFields.GetWOPIFrameUrl(SPWOPIFrameAction.Edit);
ctx.Load(f.ListItemAllFields);
ctx.ExecuteQuery();
This builds on the answer from #thebitlic which was the silver bullet for sure! However he or she is doing two calls to the server. Through the wonders of CSOM batching, it's possible to do it in one round trip, and no need to bring back the File object at all.

ASP.NET Web API, unexpected end of MIME multi-part stream when uploading from Flex FileReference

Following the tutorial found on ASP.NET, implemented a Web API controller method for doing asynchronous file uploads that looks like this:
public Task<HttpResponseMessage> PostFormData()
{
// Check if the request contains multipart/form-data.
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
// Read the form data and return an async task.
var task = Request.Content.ReadAsMultipartAsync(provider).
ContinueWith<HttpResponseMessage>(t =>
{
if (t.IsFaulted || t.IsCanceled)
{
Request.CreateErrorResponse(HttpStatusCode.InternalServerError, t.Exception);
}
return Request.CreateResponse(HttpStatusCode.OK);
});
return task;
}
Uploading a file via a standard multipart HTML form works perfectly. However, when another developer attempts to upload a file via multipart form constructed by Flex's FileReference class, an error is thrown:
Unexpected end of MIME multipart stream. MIME multipart message is not complete.
I have no idea if the problem lies in Web API or Flex. I've found some sort of related fixes that had no affect (Multipart form POST using ASP.Net Web API), and more recently this one ("MIME multipart stream. MIME multipart message is not complete" error on webapi upload). If the second link holds true, does anyone know if it's out in the current release of Web API available via Nuget? The discussion was in May, the most recent release from Nuget was August, so I assume this fix was deployed already, and is not the root cause of my issue.
I had the same problem with MVC4, but Will is correct, add a name to your input.....
<input type="file" id="fileInput" name="fileInput"/>
and all the magic is back up and working!
I had the same problem with flex. And below is the code that solved it. Basically I used a custom stream to append the newline that asp.net web api is expecting.
Stream reqStream = Request.Content.ReadAsStreamAsync().Result;
MemoryStream tempStream = new MemoryStream();
reqStream.CopyTo(tempStream);
tempStream.Seek(0, SeekOrigin.End);
StreamWriter writer = new StreamWriter(tempStream);
writer.WriteLine();
writer.Flush();
tempStream.Position = 0;
StreamContent streamContent = new StreamContent(tempStream);
foreach(var header in Request.Content.Headers)
{
streamContent.Headers.Add(header.Key, header.Value);
}
// Read the form data and return an async task.
await streamContent.ReadAsMultipartAsync(provider);
Hope this helps.
Reading through your existing research and following through to the codeplex issue reported it looks like someone else confirmed this issue to still exist in September.
They believe that MVC 4 fails to parse uploads without a terminating "\r\n".
The issue is really simple but extremely hard to fix. The problem is that Uploadify does > not add an "\r\n" at the end of the MultiPartForm message
http://aspnetwebstack.codeplex.com/discussions/354215
It may be worth checking that the Flex upload adds the "\r\n"
For those landing here googling:
Unexpected end of MIME multipart stream. MIME multipart message is not complete.
Reading the request stream more than once will also cause this exception. I struggled with it for hours until I found a source explaining that the request stream only could be read once.
In my case, I combined trying to read the request stream using a MultipartMemoryStreamProvider and at the same time letting ASP.NET do some magic for me by specifying parameters (coming from the request body) for my api method.
Make sure the virtual directory ("~/App_Data" directory as below example) where the image files are first uploaded are physically existance. When you publish the project, it may not be in the output files.
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
I just removed my headers I was setting on my post method which ended up solving this issue.
The problem is this line:
string root = HttpContext.Current.Server.MapPath("~/App_Data");
It will only work in localhost, you can use HostingEnvironment.MapPath instead in any context where System.Web objects like HttpContext.Current are not available (e.g also from a static method).
var mappedPath = System.Web.Hosting.HostingEnvironment.MapPath("~/SomePath");
See also What is the difference between Server.MapPath and HostingEnvironment.MapPath?
Reference to this answer How to do a Server Map Path.

How to convert secured HTML (ASP.NET MVC 3) page to PDF using wkhtmltopdf?

I would like to convert a HTML + CSS page to a PDF file.
I have tried wkhtmltopdf and I have got a problem because the page I want to access requires to be authenticated on the Website.
The page I would like to convert to PDF has the following URL : http://[WEBSITE]/PDFReport/33
If I try to access it without being authenticated, I'm redirected to the login page.
So when I use wkhtmltopdf, it converts my login page to PDF...
The anthentication method I use on my ASP.NET MVC application is SimpleMembership:
[Authorize]
public ActionResult PDFReport(string id)
{
}
I am executing wkhtmltopdf.exe with System.Diagnostics.Process :
FileInfo tempFile = new FileInfo(Request.PhysicalApplicationPath + "\\bin\\test.pdf");
StringBuilder argument = new StringBuilder();
argument.Append(" --disable-smart-shrinking");
argument.Append(" --no-pdf-compression");
argument.Append(" " + "http://[WEBSITE]/PDFReport/33");
argument.Append(" " + tempFile.FullName);
// to call the exe to convert
using (Process p = new System.Diagnostics.Process())
{
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.FileName = Request.PhysicalApplicationPath + "\\bin\\wkhtmltopdf.exe";
p.StartInfo.Arguments = argument.ToString();
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.Start();
p.WaitForExit();
}
Do you know how to generate the PDF without disabling security on this page?
I had a good deal of trouble with this recently. In a nutshell, WKHTMLTOPDF is a version of Webkit (QT, I believe they call it), so when you request a password protected page, that browser needs to log in and store/reference a cookie the same as you would normally.
The raw call would look something like this:
`/path/wkhtmltopdf --cookie-jar my.jar --username myusername --password mypassword URL
Where:
my.jar is a jar file that gets created and holds your cookie values
username is the name of the username form field and myusername is the post value
password is the name of the password form field and mypassword is the post value
URL is the URL of the log in page
Be sure to include any other post fields required to successfully log in - you'll probably want to monitor your HTTP headers, not just look at the form. Call WKHTMLTOPDF again on the page you're looking to capture with your normal parameters, including the --cookie-jar my.jar to maintain the session. That should do it!
However, I still had problems on my end, but it was a fairly robust login (multiple cookies, secure, many parameters, etc). I was working with PHP and had better luck using CURL - I'm not sure how that carries over to ASP.NET (maybe this? http://support.microsoft.com/kb/303436) but here's my logic if it helps:
Log in via CURL
Grab HTML page and store in local temporary file
Replace all relative references to images and files to absolute references (or insert a base tag)
Run plain 'ol WKHTMLTOPDF on the temporary file
Delete temporary file
All in all it was a hell of a lot easier to do it this way, and it feels better to me knowing I'm leaning on tried and true code rather than parameters in version 0.10 of WKHTMLTOPDF.

Using MVC to talk directly to Amazon S3

I am writing an MVC 3 application that needs to allow the user to directly upload a file to S3. I also need to show a progress bar. All of the examples I have seen are PHP or Ruby-on-Rails related. Has anyone managed to upload a file to S3 directly (from client browser) using MVC?
So, after a morning of smashing my head into my keyboard, the following snippet of code works (with the obvious credentials removed):
using (AmazonS3 client = Amazon.AWSClientFactory.CreateAmazonS3Client("Access_Key",
"Secret_Key"))
{
PutObjectRequest request = new PutObjectRequest();
request.WithBucketName("BUCKET-NAME")
.WithCannedACL(S3CannedACL.PublicRead)
.WithKey("myDirectory/" +
HttpContext.Current.Server.UrlEncode(fileBase.FileName))
.InputStream = fileBase.InputStream;
S3Response response = client.PutObject(request);
}

Resources