Overview
We are building a music streaming application that will be primarily targeted at Android devices. The main aim is to sell music subscriptions while protecting the media assets we are providing our end-users, thus A DRM solution is core to our infrastructure. The setup we have is as below:
Core Components:
Android Mobile Application with Exoplayer
Unified Streaming Server as streaming server
Azure Media Services for DRM services
Key generation utilities from Azure Media Services Sdk
These are the steps we take:
Step 1: Encoding
We use ffmpeg to encode our various media assets to different mp4 bit rate outputs:
256 KBPS
192 KBPS
128 KBPS
96 KBPS
64 KBPS
Step 2: Encryption
Once we have converted our media assets, we run a two-step encoding process on the converted media assets.
First, we run the AZURE .NET SDK for key generation with the following sample output:
key:nb:kid:UUID:fae1ab22-cd13-44f8-a746-0d7dd3a31645
key value:MVEp833L3jmUaNvTy3JUuQ==
PlayReady URL:
https://music.keydelivery.mediaservices.windows.net/PlayReady/
Widevine URL:
https://music.keydelivery.mediaservices.windows.net/Widevine/?KID=fae1ab22-cd13-44f8-a746-0d7dd3a31645
Auth policy: nb:ckpid:UUID:15ec2f3f-4ad9-49be-9f89-b5431bc3dec1
That generates for us the following items: KEY_ID, CONTENT_KEY,AUTH_POLICY and LICENSE_URL.
We have noted when using the Azure .NET SDK that there is a token restriction policy option which is responsible for generating token protection for licenses via SWT or JWT tokens with a bunch of options. Since we do not want token protection, we set the SDK to issue the license with open authorization policy. We have highlighted the lines of code we modified to achieve this below for reference:
Azure Media Services .NET SDK
Line 49:
bool tokenRestriction = false; - main change
Line 81:
if (tokenRestriction)
tokenTemplateString = AddTokenRestrictedAuthorizationPolicy(key);
else
AddOpenAuthorizationPolicy(key);
Console.WriteLine("Auth policy: {0}",
key.AuthorizationPolicyId, System.Convert.ToBase64String(key.GetClearKeyValue()));
Since ExoPlayer is only able to play PlayReady on Android TV, we are restricted to requesting Widevine licences.
We then use the Unified Streaming mp4split tool to encrypt our media assets with the information we highlighted above from the Azure .NET SDK. The command to encrypt assets for Widevine licences is shown below with more details on the command here.
Notes on the command:
We need to convert our KEY_ID and CONTENT_KEY to hexadecimal values before we can use them in the mp4split command:
KEY_ID of UUID:fae1ab22-cd13-44f8-a746-0d7dd3a31645 becomes
22ABE1FA13CDF844A7460D7DD3A31645
CONTENT_KEY of MVEp833L3jmUaNvTy3JUuQ== becomes
315129F37DCBDE399468DBD3CB7254B9
We still don't know what the PSSH value is from the response we get from the Azure .NET SDK. The Unified Streaming documentation says the following:
The DRM specific data provided by the license server (the Widevine PSSH data).
Can either be a Base64 string or a file with the decoded Base64 data. The file name must include a '.'
Note that the LA_URL (license acquisition URL) is not signaled in the "pssh" box and is often hard-coded in a DASH player supporting Winevine Modular itself. The recommended setting is to use the correct URL.
We are looking for assistance in figuring out what the PSSH Data is from the Azure .NET SDK output that we receive. In the meantime we have been using the AUTH_POLICY_ID (converted to Base64) as the PSSH in the mp4split command.
When we put all the above together, the mp4split script we use becomes:
#!/bin/bash
KID=22ABE1FA13CDF844A7460D7DD3A31645
CEK=315129F37DCBDE399468DBD3CB7254B9
LAURL="https://music.keydelivery.mediaservices.windows.net/Widevine/? KID=fae1ab22-cd13-44f8-a746-0d7dd3a31645"
PSSH="Py/sFdlKvkmfibVDG8PewQ=="
mp4split --license-key=usp/license.key -o musicfile.ism \
--mpd.inline_drm \
--widevine.key=${KID}:${CEK} \
--widevine.license_server_url=${LAURL} \
--widevine.drm_specific_data=${PSSH} \
musicfile-64kbps.mp4
aws s3 cp musicfiles.ism s3://output-bucket/
aws s3 cp musicfiles-64kbps.mp4 s3://output-bucket/
NOTES:
For the above command we are generating an ism manifest file with Widevine licence encryption for a 64kps bitrate mp4 music file.
The --license-key is for the unified streaming premium product.
The AWS S3 commands are for uploading our generated ism and associated mp4 64kps bitrate file to our aws s3 storage buckets.
Once this process is done, the next step is integration with ExoPlayer.
Step 3: Streaming
Once we upload our .ism and its associated mp4 file to our S3 bucket, the mobile app uses the following details to access the stream and play it back:
{
"name": "Music Tests",
"uri": "http://mozart.musicfiles.com/auth/media/musicfiles.ism/.mpd",
"drm_scheme": "widevine",
"drm_license_url": "https://music.keydelivery.mediaservices.windows.net/Widevine/?KID=fae1ab22-cd13-44f8-a746-0d7dd3a31645"
}
Where:
URI - is our Unified Streaming server endpoint
DRM_LICENSE_URI - is widevine server url we are given by our Azure .NET SDK
We get errors at this stage after we have uploaded, encoded and encrypted the media assets. The error in the Android app is the following:
02-07 12:58:58.458 16454-5913/com.google.android.exoplayer2.demo E/OMXMaster: A component of name 'OMX.qcom.audio.decoder.aac' already exists, ignoring this one.
02-07 12:59:02.892 16454-5811/com.google.android.exoplayer2.demo E/ExoPlayerImplInternal: Renderer error.
com.google.android.exoplayer2.ExoPlaybackException
at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.shouldWaitForKeys(MediaCodecRenderer.java:709)
at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.feedInputBuffer(MediaCodecRenderer.java:650)
at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.render(MediaCodecRenderer.java:490)
at com.google.android.exoplayer2.ExoPlayerImplInternal.doSomeWork(ExoPlayerImplInternal.java:464)
at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:300)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:168)
at android.os.HandlerThread.run(HandlerThread.java:61)
at com.google.android.exoplayer2.util.PriorityHandlerThread.run(PriorityHandlerThread.java:40)
Caused by: com.google.android.exoplayer2.drm.DrmSession$DrmSessionException: com.google.android.exoplayer2.upstream.HttpDataSource$InvalidResponseCodeException: Response code: 400
at com.google.android.exoplayer2.drm.DefaultDrmSessionManager.onError(DefaultDrmSessionManager.java:594)
at com.google.android.exoplayer2.drm.DefaultDrmSessionManager.onKeysError(DefaultDrmSessionManager.java:589)
at com.google.android.exoplayer2.drm.DefaultDrmSessionManager.onKeyResponse(DefaultDrmSessionManager.java:549)
at com.google.android.exoplayer2.drm.DefaultDrmSessionManager.access$900(DefaultDrmSessionManager.java:49)
at com.google.android.exoplayer2.drm.DefaultDrmSessionManager$PostResponseHandler.handleMessage(DefaultDrmSessionManager.java:669)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:168)
at android.os.HandlerThread.run(HandlerThread.java:61)
at com.google.android.exoplayer2.util.PriorityHandlerThread.run(PriorityHandlerThread.java:40)
Caused by: com.google.android.exoplayer2.upstream.HttpDataSource$InvalidResponseCodeException: Response code: 400
at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.open(DefaultHttpDataSource.java:212)
at com.google.android.exoplayer2.upstream.DataSourceInputStream.checkOpened(DataSourceInputStream.java:101)
at com.google.android.exoplayer2.upstream.DataSourceInputStream.read(DataSourceInputStream.java:81)
at com.google.android.exoplayer2.upstream.DataSourceInputStream.read(DataSourceInputStream.java:75)
at com.google.android.exoplayer2.util.Util.toByteArray(Util.java:118)
at com.google.android.exoplayer2.drm.HttpMediaDrmCallback.executePost(HttpMediaDrmCallback.java:106)
at com.google.android.exoplayer2.drm.HttpMediaDrmCallback.executeKeyRequest(HttpMediaDrmCallback.java:91)
at com.google.android.exoplayer2.drm.DefaultDrmSessionManager$PostRequestHandler.handleMessage(DefaultDrmSessionManager.java:692)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:168)
at android.os.HandlerThread.run(HandlerThread.java:61)
02-07 12:59:02.896 16454-16454/com.google.android.exoplayer2.demo E/EventLogger: playerFailed [7.76]
com.google.android.exoplayer2.ExoPlaybackException
at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.shouldWaitForKeys(MediaCodecRenderer.java:709)
at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.feedInputBuffer(MediaCodecRenderer.java:650)
at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.render(MediaCodecRenderer.java:490)
at com.google.android.exoplayer2.ExoPlayerImplInternal.doSomeWork(ExoPlayerImplInternal.java:464)
at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:300)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:168)
at android.os.HandlerThread.run(HandlerThread.java:61)
at com.google.android.exoplayer2.util.PriorityHandlerThread.run(PriorityHandlerThread.java:40)
Caused by: com.google.android.exoplayer2.drm.DrmSession$DrmSessionException: com.google.android.exoplayer2.upstream.HttpDataSource$InvalidResponseCodeException: Response code: 400
at com.google.android.exoplayer2.drm.DefaultDrmSessionManager.onError(DefaultDrmSessionManager.java:594)
at com.google.android.exoplayer2.drm.DefaultDrmSessionManager.onKeysError(DefaultDrmSessionManager.java:589)
at com.google.android.exoplayer2.drm.DefaultDrmSessionManager.onKeyResponse(DefaultDrmSessionManager.java:549)
at com.google.android.exoplayer2.drm.DefaultDrmSessionManager.access$900(DefaultDrmSessionManager.java:49)
at com.google.android.exoplayer2.drm.DefaultDrmSessionManager$PostResponseHandler.handleMessage(DefaultDrmSessionManager.java:669)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:168)
at android.os.HandlerThread.run(HandlerThread.java:61)
at com.google.android.exoplayer2.util.PriorityHandlerThread.run(PriorityHandlerThread.java:40)
Caused by: com.google.android.exoplayer2.upstream.HttpDataSource$InvalidResponseCodeException: Response code: 400
at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.open(DefaultHttpDataSource.java:212)
at com.google.android.exoplayer2.upstream.DataSourceInputStream.checkOpened(DataSourceInputStream.java:101)
at com.google.android.exoplayer2.upstream.DataSourceInputStream.read(DataSourceInputStream.java:81)
at com.google.android.exoplayer2.upstream.DataSourceInputStream.read(DataSourceInputStream.java:75)
at com.google.android.exoplayer2.util.Util.toByteArray(Util.java:118)
at com.google.android.exoplayer2.drm.HttpMediaDrmCallback.executePost(HttpMediaDrmCallback.java:106)
at com.google.android.exoplayer2.drm.HttpMediaDrmCallback.executeKeyRequest(HttpMediaDrmCallback.java:91)
at com.google.android.exoplayer2.drm.DefaultDrmSessionManager$PostRequestHandler.handleMessage(DefaultDrmSessionManager.java:692)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:168)
at android.os.HandlerThread.run(HandlerThread.java:61)
The Error Codes from the Azure Media Services here states the following for response code 400:
Azure Error Codes
400 Bad Request The request contains invalid information and is
rejected due to one of the following reasons:
-An unsupported API version is specified. For the most current
version, see Setup for Media Services REST API Development. -The API
version of Media Services is not specified. For information on how to
specify the API version, see Connecting to Media Services with the
Media Services REST API. Note
If you are using the .NET or Java SDKs to connect to Media Services,
the API version is specified for you whenever you try and perform some
action against Media Services.
An undefined property has been specified. The property name is in the error message. Only those properties that are members of a given
entity can be specified. See Azure Media Services REST API Reference
for a list of entities and their properties.
An invalid property value has been specified. The property name is in the error message. See the previous link for valid property types
and their values.
A property value is missing and is required.
Part of the URL specified contains a bad value.
An attempt was made to update a WriteOnce property.
An attempt was made to create a Job that has an input Asset with a primary AssetFile that was not specified or could not be determined.
An attempt was made to update a SAS Locator. SAS locators can only be created or deleted. Streaming locators can be updated. For more
information, see Locators.
An unsupported operation or query was submitted.
Also in addition to that when we run a GET Request on the widevine server url
GET https://music.keydelivery.mediaservices.windows.net/Widevine/?KID=fae1ab22-cd13-44f8-a746-0d7dd3a31645
We get the following response:
<?xml version="1.0" encoding="utf-8"?>
<Error>
<Message>License challenge is missing from the request body.</Message>
<Code>NoLicenseChallenge</Code>
</Error>
What does the above error message mean?
We have been reading the Azure Documentation but we have not been able to figure out how to resolve the error.
We are trying to integrate our third party streaming server unified streaming with azure media services DRM solution.
Related
I want to get the media stream url from onvif,but it shows that "Method 'GetStreamUri' not implemented: method name or namespace not recognized"Detail: [no detail] .
I don't know the reason why the method GetStreamUri not implemented?
I download wsdl "http://www.onvif.org/ver20/media/wsdl" and generate the code by using the gsoap.
console ouput
I find the TEST.log.It shows that it cannot find ns3:GetStreamUri and ns1:GetStreamUri
debug information
Is your camera Profile T compliant or does it at least implement Media Service 2?
In not, then you should use Media Service 1.
I can effectively make use of all operations available for Object Storage on my FIWARE account.
Nonetheless I have identified a strange behaviour when downloading objects from a container.
Please find below the procedure to reproduce that strange behaviour:
I upload two objects ("gonzo.png" and "elmo.png") to the container "photos"
1.1. First, by means of cloud UI (https://cloud.lab.fiware.org/#objectstorage/containers/) I manually upload the object "gonzo.png"
1.2. Later, by following the instructions from Object Storage GE programmer's guide I programmatically (or with the help of standalone Rest Client) upload the object "elmo.png"
I download the objects from the container "photos"
2.1 First, by following the instructions from Object Storage GE programmer's guide I successfully download object "gonzo.png". The webservice response body is the binary content of such object.
2.2. Later, by following same instructions as in step 2.1 I try to download the object "elmo.png". Now the webservice response body is a json with metadata and the binary content of the object.
What can I do receive a standard response body for both objects? Either binary or either json.
Why do I get a different response if the object is originally uploaded via Cloud UI or via external tool (program or rest client) ?
As in Download blob from fiware object-storage I have already tried to set the header response_type: text and the behaviour is the same.
There are many object stores out there, having different APIs.
The Object Storage GE was initially based on the CDMI API [1].
Currently, it is based on Openstack Swift [2].
The Cloud Portal still uses some of the CDMI features and specifically it may do 64-bit encoding of some types of objects in which case the object content is a json which contains the metadata and a base64 encoding of the data. I suspect that this is what happened to the object you have created using the cloud UI.
Thus, please use Swift native API for all operations.
The API is well documented here: http://developer.openstack.org/api-ref-objectstorage-v1.html
and the python examples in the programmer guide (https://forge.fiware.org/plugins/mediawiki/wiki/fiware/index.php/Object_Storage_-_User_and_Programmers_Guide) also use the Native API.
[1] google for SNIA CDMI. Having less then 10 replutation I cannot put too many links
[2] google for Openstack Swift
I'm trying to generate a Shared Access Signature and am using the code here (http://blogs.msdn.com/b/brunoterkaly/archive/2014/06/13/how-to-provision-a-shared-access-signatures-that-allows-clients-to-upload-files-to-to-azure-storage-using-node-js-inside-of-azure-mobile-services.aspx) for a custom API to generate the SAS.
It seems to be missing the sv=2014-02-14 parameter when calling "generateSharedAccessSignature()".
The SAS url doesn't seem to work when I try it (getting a 400 xml not valid error) but if I try a SAS generated from Azure Management Studio the URL contains the "sv" parameter and works when I attempt to upload with it.
Any ideas?
Based on the Storage Service REST API Documentation, sv parameter in Shared Access Signature is introduced in storage service version 2014-02-14. My guess is that Azure Mobile Service is using an older version of the storage service API and this is the reason you don't see sv parameter in your SAS token.
You could be getting 400 error (invalid XML) because of this. In the earlier version of storage service API, the XML syntax for committing block list was different than what is used currently. I have had one more user come to my blog post complaining about the same error. Please try the following XML syntax when performing a commit block list operation and see if the error is gone:
<?xml version="1.0" encoding="utf-8"?>
<BlockList>
<Block>[base64-encoded-block-id]</Block>
<Block>[base64-encoded-block-id]</Block>
...
<Block>[base64-encoded-block-id]</Block>
</BlockList>
Please notice that we're not using Latest node. Instead we're using Block node.
Leaving the sv parameter out and setting it as part of the PUT request header worked using:
xhr.setRequestHeader('x-ms-version','2014-02-14');
You can check out this example for an azure file upload script: http://gauravmantri.com/2013/02/16/uploading-large-files-in-windows-azure-blob-storage-using-shared-access-signature-html-and-javascript/
...which will work with the generated SAS from the question's original blog link - http://blogs.msdn.com/b/brunoterkaly/archive/2014/06/13/how-to-provision-a-shared-access-signatures-that-allows-clients-to-upload-files-to-to-azure-storage-using-node-js-inside-of-azure-mobile-services.aspx
Add the request header in the beforeSend like so:
beforeSend: function(xhr) {
xhr.setRequestHeader('x-ms-version','2014-02-14');
},
I modified sample CloudRecog code for my own code. I created cloud database and get AccessKeys then copied this keys to CloudReco.cpp file. What should i use for metadata. I didn't understand this. Then when i was reading sample code i saw this line: private static final String mServerURL = "https://ar.qualcomm.at/samples/cloudreco/json/". How to get my metaData url?
The Vuforia Cloud Recognition Service enables new types of applications in retail and publishing. An application using Cloud Recognition will be able to query a Cloud Database with camera images (actual recognition happens in the cloud), and then handle the matching results returned from the cloud to perform local detection and tracking.
Also, every Cloud Image Target can optionally have an associated Metadata; a target metadata is essentially nothing else than a custom user-defined blob of data that can be associated to a target and filled with custom information, as long as the data size does not exeed the allowed limits (up to 1MB per target).
Therefore, you can use the metadata as a way to store additional content that relates to a specific target, that your application will be able to process using some custom logic.
For example, your application may use the metadata to store:
a simple text message that you want your app to display on the screen of your device when the target is detected, for example:
“Hello, I am your cloud image target XYZ, you have detected me :-) !”
a simple URL string (for instance “http://my_server/my_3d_models/my_model_01.obj”) pointing to a custom network location where you have stored some other content, like a 3D model, a video, an image, or any other custom data, so that for each different image target, your application may use such URL to download the specific content;
more in general, some custom string that your application is able to process and use to perform specific actions
a full 3D model (not just the URL pointing to a model on a server, but the model itself), for example the metadata itself could embed an .OBJ 3D model, provided that the size does not exceed the allowed limits (up to 1MB)
and more ...
How do I create/store metadata for a Cloud target ?
Metadata can be uploaded together with an image target at the time you create the target itself in your Cloud Database; or you can also update the metadata of an existing target, at a later time; in either case, you can use the online TargetManager, as explained here:
https://developer.vuforia.com/resources/dev-guide/managing-targets-cloud-database-using-target-manager
or you can proceed programmatically using the VWS API, as explained here:
https://developer.vuforia.com/resources/dev-guide/managing-targets-cloud-database-using-developer-api
How can I get the metadata of a Cloud target when it is recognized ?
The Vuforia SDK offers a dedicated API to retrieve the metadata of a target in your mobile application. When a Cloud target is detected (recognized), a new TargetSearchResult is reported to the application, and the metadata can be obtained using one of these methods:
Vuforia Native SDK - C++ API: TargetSearchResult::getMetaData() - const char*
Vuforia Native SDK - Java API: TargetSearchResult.getMetaData() - String
Vuforia Unity Extension - C# API: TargetSearchResult.Metadata - string
See also the API reference pages:
https://developer.vuforia.com/resources/api/classcom_1_1qualcomm_1_1vuforia_1_1_target_search_result
https://developer.vuforia.com/resources/api/unity/struct_target_finder_1_1_target_search_result
Sample code:
For a reference sample code in native Android, see the code in the Books.java in the "Books-2-x-y" sample project.
For a reference sample code in native iOS, see the code in the BooksEAGLView.mm file in the "Books-2-x-y" sample project.
For a reference sample code in Unity, see the CloudRecoEventHandler.cs script (attached to theCloudRecognition prefab) in the Books sample; in particular, the OnNewSearchResult method shows how to get a targetSearchResult object (from which you can then get the metadata, as shown in the example code).
EDIT: this is in response to the first part of your question,: "What should i use for metadata" (not the second part about how to find the URL)
Based on their documentation (https://developer.vuforia.com/resources/dev-guide/cloud-targets):
The metadata is passed to the application whenever the Cloud Reco
target is recognized. It is up to the developer to determine the
content of this metadata – Vuforia treats it as a blob and just passes
it along to the application. The maximum size of the uploadable
metadata is 150kByte.
I added some debugging in their CloudRecognition app and saw that the payload (presumably the meta-data) they return when "recognizing" an image is:
{
"thumburl": "https://developer.vuforia.com/samples/cloudreco/thumbs/01_thumbnail.png",
"author": "Karina Borland",
"your price": "43.15",
"title": "Cloud Recognition in Vuforia",
"average rating": "4",
"# of ratings": "41",
"targetid": "a47d2ea6b762459bb0aed1ae9dbbe405",
"bookurl": "https://developer.vuforia.com/samples/cloudreco/book1.php",
"list price": "43.99"
}
The MetaData, uploaded along with your image-target in the CloudReco database, is a .txt-file, containing whatever you want.
What pherris linked, as payload from the sample-application, is in fact the contents of a .json-file that the given image-target's metadata links to.
In the sample application, the structure is as follows:
The application activates the camera and recognizes an image-target
The application then requests that specific image-target's metadata
In this case, the metadata in question is a .txt-file with the following content:
http://www.link-to-a-specific-json-file.com/randomname.json
The application then requests the contents of that specific .json-file
The specific .json-file looks as the copy-pasted text-data that pherris linked
The application uses the text-data from the .json-file to fill out the actual content of the sample application
I'm moving a ClickOnce install from a regular web server to Azure Blob storage and have a problem with some of the files. The filenames contains [ ] and CloudBlob.UploadFile fails with an exception:
Microsoft.WindowsAzure.Storageclient.StorageException:
Error accessing blob storage: Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
The code has been used for a while and only fails on files with [ ] in the name so I don't believe that it is an "authentication failure". In this particular case, this is the seventh file being uploaded in a loop. I found this link on MSDN about valid file names and this on stack overflow which both show problems with square brackets in URL's and reference UrlEncode. I added in a call to UrlEncode and that did not help. The container is created with public access since we use it to support customer downloads of our software. We have been hosting a "test" install in another container and have not had permission problems accessing that either.
I can upload the file with no name changes and then rename the file to add the "path" using newdesic's Azure Storage Explorer tool so what is that doing that I am not doing?
I see you're using the 1.7 SDK. This is a small encoding issue with the SDK which is also present in v2.0. Let's see what happens.
No encoding
account.CreateCloudBlobClient()
.GetContainerReference("temp")
.GetBlobReference("abc[]def.txt")
.UploadFile("myfile.txt");
If you don't encode the blob name, you'll end up with a request to the following URL which is causing the authentication exception:
http://account.blob.core.windows.net/temp/abc[]def.txt
The is because the SDK uses Uri.EscapeUriString internally to encode your string, but this doesn't take into account square brackets.
Encoding
Then you would expect the following to do the trick:
account.CreateCloudBlobClient()
.GetContainerReference("temp")
.GetBlobReference(HttpUtility.UrlEncode("abc[]def.txt"))
.UploadFile("myfile.txt");
The issue here is that you'll end up with this url:
http://account.blob.core.windows.net/temp/abc%255b%255ddef.txt
So what's happening here? Calling HttpUtility.UrlEncode turns abc[]def.txt to abc%5B%5Ddef.txt, which is correct. But internally, the SDK will encode this string again which results in abc%255b%255ddef.txt, which isn't what you want.
Workaround
The only way to apply encoding which takes square brackets into accounts is by using a small workaround. If you pass the full URL to the GetBlobReference method, the SDK assumes you did all the encoding yourself:
var container = account.CreateCloudBlobClient().GetContainerReference("temp");
var blob = container.GetBlobReference(String.Format("{0}/{1}",
container.Uri, System.Web.HttpUtility.UrlEncode("abc[]def.txt")));
blob.UploadFile("myfile.txt");
This results in a correctly encoded URL:
http://account.blob.core.windows.net/temp/abc%5b%5ddef.txt
And if you use a tool like CloudXplorer, you'll see the blob with the correct filename:
There are two known breaks in the Uri class in .Net 4.5
• ‘[‘,’]’ characters are no longer escaped
• ‘\’ character is now escaped as %5C
This is causing an authentication when the server attempts to validate the signature of the request as the canonicalized string is now different on the client and the server.
There are a few workarounds clients can use while this issue is present. The correct solution will depend on your specific application and requirements.
Avoid the ‘[‘,’]’, or ‘\’ characters in resource names
By simply avoiding these characters all together you will be able to avoid the issue described above.
Target .Net 4.0
Currently the recommendation is for clients to simply continue to target their applications to .Net 4.0 while a full solution is being investigated. Note, since .Net 4.5 is an in place upgrade clients can still take advantage of some performance improvements in the GC etc, without specifically targeting the .Net 4.5 profile. For Windows RT developers this is not an option and you will therefore require the workarounds detailed below.
Pre-Escape Data
If possible a client can pre- escape the data or replace the given characters with non-affected ones.
This is why the workaround above is working.