14

I'm currently having issue with Azure File storage when I build up a URL with a shared access signature (SAS) Token. The file will download in the browser, but the content-type is always application/octet-stream rather than changing to match the mime type of the file. If I put the file in Azure BLOB storage and build up a URL with a SAS Token, it sends the correct content-type for my file (image/jpeg).

I've upgraded my storage account from V1 to V2 thinking that was the problem, but it didn't fix it.

Does anyone have a clue what I could try that might get Azure File storage to return the correct content-type using a URL with SAS Token to download the file?

David Yates
  • 1,935
  • 2
  • 22
  • 38
  • What is the value of content type property for that file. – Gaurav Mantri Apr 25 '18 at 00:29
  • 1
    I just tried the same with a file in one of my storage accounts (in file storage). The content type of file was set as `image/png`. I was able to see the file properly and the response headers contain proper content-type header value. – Gaurav Mantri Apr 25 '18 at 03:02
  • 1
    `I've upgraded my storage account from V1 to V2 thinking that was the problem, but it didn't fix it.` - Off topic comment: Please be aware of the costs of V2 storage accounts. They are more expensive than V1 accounts. – Gaurav Mantri Apr 25 '18 at 03:03
  • @GauravMantri How do you tell without downloading it? I don't see anything in the portal that identifies content-type. Properties shows Name, URL, Last modified, size, etag and content-md5. I uploaded it with the portal and was able to determine content-type only by getting it with postman. The content type of my jpg comes back as application/octet-stream every time. Thanks for pricing tip as well. – David Yates Apr 25 '18 at 19:33
  • Sorry, I misunderstood your question. For some reason I thought that even though you're setting the content type correctly, the service is returning `application/octet-stream`. You would need to make a separate REST API call to File Service to fetch a file's properties. Not all properties for a file are returned as part of listing operation. You can use Microsoft's Storage Explorer to see a file's properties. – Gaurav Mantri Apr 26 '18 at 02:22
  • What's the solution when uploading a file from a 3rd party (closed source) application that is using SFTP? – Aaron Hudon Mar 03 '23 at 17:00

12 Answers12

5

So far these are the only fixes for the content-type that I've found:

  1. Use the Microsoft Azure Storage Explorer to modify the content-type string by hand. You have to right click the file and the left-click properties to get the dialog to appear.
  2. Programmatically modify the file using Microsoft's WindowsAzure.Storage Nuget package.
  3. Surface file download via my own web site and not allow direct access.

For me, none of these are acceptable choices. The first two can lead to mistakes down the road if a user uploads a file via the portal or Microsoft Azure Storage Explore and forgets to change the content type. I also don't want to write Azure Functions or web jobs to monitor and fix this problem.

Since blob storage does NOT have the same problems when uploading via Microsoft Azure Storage Explore or via the portal, the cost is much lower AND both work with SAS Tokens, we are moving towards blob storage instead. We do lose the ability to mount the drive to our local computers and use something like Beyond Compare to do file comparisons, but that is a disadvantage that we can live with.

If anyone has a better solution than the ones mentioned above that fixes this problem, I will gladly up-vote it. However, I think that Microsoft will have to make changes for this problem to be fixed.

David Yates
  • 1,935
  • 2
  • 22
  • 38
5

When I upload a jpeg file to file share through portal, content-type is changed to application/octet-stream indeed. But I can't reproduce your download problem.

I didn't specify content-type in my SAS request uri, but the file just download as a jpeg file. Have tested in SDK(Account SAS/Stored Access Policy/SAS on file itself) or REST API, both work even without content-type.

You can try to specify the content-type using the code below.

 SharedAccessFileHeaders header = new SharedAccessFileHeaders()
 {
     ContentDisposition = "attachment",
     ContentType = "image/jpeg"
 };
string sasToken = file.GetSharedAccessSignature(sharedPolicy,header);
Jerry Liu
  • 17,282
  • 4
  • 40
  • 61
  • I was downloading the file using a full URL + SAS token to get direct access to the file. I was able to paste that URL + SAS directly into Postman and the content-type comes down as application/octet-stream. The plan, which works if the content-type is correct, is to use this URL + SAS Token to retrieve images for a SSRS report. SRSS is very particular about the content-type/media-type of the files it will display and application/octet-stream is not displayed. This will be a temp solution till we migrate our reports to the cloud as well. – David Yates May 03 '18 at 14:42
  • @DavidYates thanks for your detailed explanation! Could you tell me how do you get full url+sas? If you get it from portal or Azure Storage Explorer, maybe it deserves a try that you can generate the url using sdk, because by default portal and explorer don't specify content-type. If needed, I can provide complete code snippet. – Jerry Liu May 03 '18 at 15:01
  • @DavidYates Have tested using postman on my side, the response content-type becomes `image/jpeg` if I specify the header using sdk. – Jerry Liu May 03 '18 at 16:11
  • The thing is that we need this to work with SAS tokens for a SSRS report and I don't want to have to write a program just to fix content-type. The BLOB storage correctly sets the content-type and so that's the route we are currently going to go. As for creating a SAS token, you can follow these steps: - Get a URL to a storage file in Azure - Go to our storage account in Azure - Click on the "Shared Access Signature" tab - Set the start and end time for usage - Click the "Generate SAS connection string" - Copy the SAS token and Paste it onto the end of you file URL - Use URL in postman – David Yates May 03 '18 at 18:05
  • A little more detail....I do appreciate the code since I may be able to use it elsewhere. The thing we are trying to do is grab the partial path to an image from our DB, get the storage location from the db, get the SAS token from the db and build this all up in a stored procedure for SSRS report to use. So, I would never be able to use the C# code to generate the SAS token dynamically. Thus, the content-type must always be correct for this to work. – David Yates May 03 '18 at 18:18
  • @DavidYates Didn't figure out your requirement before, sorry for the waste of time. Also thanks a lot for your patience, have learnt many things from your issue. Hope everything goes well with you! – Jerry Liu May 04 '18 at 13:43
4

Check this out - Microsoft SAS Examples

If you don't want to update the content-type of your file in Azure or it's too much of a pain to update the content-type of all your existing files, you can pass the desired content-type w/ the SAS token as well. The rsct param is where you would specify the desired content-type.

e.g. - https://myaccount.file.core.windows.net/pictures/somefile.pdf?sv=2015-02-21&st=2015-07-01T08:49Z&se=2015-07-02T08:49Z&sr=c&sp=r&rscd=file;%20attachment&rsct=application%2Fpdf&sig=YWJjZGVmZw%3d%3d&sig=a39%2BYozJhGp6miujGymjRpN8tsrQfLo9Z3i8IRyIpnQ%3d

TWong
  • 91
  • 2
  • 5
  • This has instructions for signing with the HMAC-SHA256: https://learn.microsoft.com/en-us/rest/api/storageservices/create-user-delegation-sas#specify-the-signature – Eneerge Aug 13 '22 at 04:35
3

Azure blob falls to the default value of 'application/octet-stream' if nothing is provided. To get the correct mimetypes, this is what I did with my flask app:

@app.route('/', methods=['GET', 'POST'])
def upload_file():
        if request.method == 'POST':
            f = request.files['file']
            mime_type = f.content_type
            print (mime_type)
            print (type(f))
            try:
                blob_service.create_blob_from_stream(container, f.filename, f,
                content_settings=ContentSettings(content_type=mime_type))
            except Exception as e:
                print (str(e))
                pass

mime_type was passed to ContentSettings to get the current mimetypes of files uploaded to azure blob.

In nodeJS:

blobService.createBlockBlobFromStream(container, blob, stream, streamLength, { contentSettings: { contentType: fileMimeType } }, callback)

where:

fileMimeType is the type of the file being uploaded

callback is your callback implementation

Reference to method used: https://learn.microsoft.com/en-us/javascript/api/azure-storage/azurestorage.services.blob.blobservice.blobservice?view=azure-node-latest#createblockblobfromstream-string--string--stream-readable--number--createblockblobrequestoptions--errororresult-blobresult--

AliAvci
  • 1,127
  • 10
  • 20
abhinav singh
  • 1,024
  • 1
  • 12
  • 34
1

This works with java using com.microsoft.azure azure-storage library. Uploading to Shared Access Signature resource.

        InputStream is = new FileInputStream(file);
        CloudBlockBlob cloudBlockBlob = new CloudBlockBlob(new URI(sasUri));
        cloudBlockBlob.getProperties().setContentType("application/pdf");
        cloudBlockBlob.upload(is, file.length());
        is.close();

enter image description here

1

For anyone looking to upload files correctly with a declared Content Type, the v12 client has changed setting Content type. You can use the ShareFileHttpHeaders parameter of file.Create

ShareFileClient file = directory.GetFileClient(fileName);          
using FileStream stream = File.OpenRead(@"C:\Temp\Amanita_muscaria.jpg");           
file.Create(stream.Length, new ShareFileHttpHeaders { ContentType = ContentType(fileName) });           
file.UploadRange(new HttpRange(0, stream.Length),stream);

where ContentType(fileName) is a evaluation of filename, eg:

if (fileName.EndsWith(".txt")) return "text/plain";
// etc
Neil Thompson
  • 6,356
  • 2
  • 30
  • 53
0
// here you define your file content type
CloudBlockBlob cloudBlockBlob = container.GetBlockBlobReference(file.FileName);
                cloudBlockBlob.Properties.ContentType = file.ContentType; //content type
  • Please note that the is about Azure File storage. I'm contrasting it with Blob storage. I eventually went with Blob storage because it did behave better than file storage and was cheaper. – David Yates Feb 19 '20 at 01:58
0

I know that I'm not answering the question, but I do believe the answer is applicable. I had the same problem with a storage account that I need it to have it as a static website. Whenever I upload a blob to a container, the default type is "application/octet-stream" and because of this the index.html get downloaded instead of being displayed.

To change the file type do the following:

# Get Storage Account for its context
$storageAccount = Get-AzStorageAccount -ResourceGroupName <Resource Group Name> -Name <Storage Account Name>
# Get Blobs inside container of storage account
$blobs = Get-AzStorageBlob -Context $storageAccount.Context -Container <Container Name>
foreach ($blob in $blobs) {
    $CloudBlockBlob = [Microsoft.Azure.Storage.Blob.CloudBlockBlob] $blob.ICloudBlob
    $CloudBlockBlob.Properties.ContentType = <Desired type as string>
    $CloudBlockBlob.SetProperties()
}

Note: for Azure File storage you might wanna change the library to [Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob]

Ovidiu
  • 11
  • 2
0

I have not tried this, but ideally, you could use ClientOptions to specify a different header. It'd would look something like this:

ClientOptions options = new ClientOptions();
HttpHeader httpHeaders = new HttpHeader("Content-Type", "application/pdf");
options.setHeaders(Collections.singleton(httpHeaders));
    
blobClient = new BlobClientBuilder()
                .endpoint(<SAS-URL>)
                .blobName("hello")
                .clientOptions(options)
                .buildClient();
Skillz
  • 308
  • 4
  • 10
0

This way we can provide the our own mime_type as 'content-type'

with open(file.path,"rb") as data:
            #blob_client.upload_blob(data)
            mime_type =mimetypes.MimeTypes().guess_type(file.name)[0]
            
            blob_client.upload_blob(data,content_type=mime_type)
            print(f'{file.name}' " uploaded to blob storage")
kumardippu
  • 599
  • 4
  • 11
0

Based on this answer: Twong answer

Example if you are using .NET (C#) API to proxy/generate SAS url from ShareFileClient (ShareFileClient class description):

if (downloadClient.CanGenerateSasUri)
{
    var sasBuilder = new ShareSasBuilder(ShareFileSasPermissions.Read, DateTimeOffset.Now.AddDays(10))
        {
            ContentType = "application/pdf",
            ContentDisposition = "inline"
        };
    return downloadClient.GenerateSasUri(sasBuilder);
}

Above example setup 10 days long token for pdf file which will be open into new browser tab (especially on Apple iOS).

kubasz
  • 1
  • 1
0

Solution in Java is to specify the content-type when generating the signature image url:

blobServiceSasSignatureValues.setContentType("image/jpeg");
participant
  • 2,923
  • 2
  • 23
  • 40