Widevine DRM Content on Exoplayer 2.0 - android-tv

I am trying to play Widevine encrypted content on an Android TV application using Exoplayer. I have my video URL which is served from a CDN and acquired with a ticket. I have my widevine license URL, a ticket and a auth token for the license server.
I am creating a drmSessionManager, putting the necessary headers needed by the license server as follows:
UUID drmSchemeUuid = C.WIDEVINE_UUID;
mediaDrm = FrameworkMediaDrm.newInstance(drmSchemeUuid);
static final String USER_AGENT = "user-agent";
HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback("my-license-server", new DefaultHttpDataSourceFactory(USER_AGENT));
keyRequestProperties.put("ticket-header", ticket);
keyRequestProperties.put("token-header", token);
drmCallback.setKeyRequestProperty("ticket-header", ticket);
drmCallback.setKeyRequestProperty("token-header", token);
new DefaultDrmSessionManager(drmSchemeUuid, mediaDrm, drmCallback, keyRequestProperties)
After this Exoplayer handles most of the stuff, the following breakpoints are hit.
response = callback.executeKeyRequest(uuid, (KeyRequest) request);
in class DefaultDrmSession
return executePost(dataSourceFactory, url, request.getData(), requestProperties) in HttpMediaDrmCallback
I can observe that everything is fine till this point, the URL is correct, the headers are set fine.
in the following piece of code, I can observe that the dataSpec is fine, trying to POST a request to the license server with the correct data, but when making the connection the response code returns 405.
in class : DefaultHttpDataSource
in method : public long open(DataSpec dataSpec)
this.dataSpec = dataSpec;
this.bytesRead = 0;
this.bytesSkipped = 0;
transferInitializing(dataSpec);
try {
connection = makeConnection(dataSpec);
} catch (IOException e) {
throw new HttpDataSourceException("Unable to connect to " + dataSpec.uri.toString(), e,
dataSpec, HttpDataSourceException.TYPE_OPEN);
}
try {
responseCode = connection.getResponseCode();
responseMessage = connection.getResponseMessage();
} catch (IOException e) {
closeConnectionQuietly();
throw new HttpDataSourceException("Unable to connect to " + dataSpec.uri.toString(), e,
dataSpec, HttpDataSourceException.TYPE_OPEN);
}
When using postman to make a request to the URL, a GET request returns the following body with a response code of 405.
{
"Message": "The requested resource does not support http method 'GET'." }
a POST request also returns response code 405 but returns an empty body.
In both cases the following header is also returned, which I suppose the request must be accepting GET and POST requests.
Access-Control-Allow-Methods →GET, POST
I have no access to the configuration of the DRM server, and my contacts which are responsible of the DRM server tells me that POST requests must be working fine since there are clients which have managed to get the content to play from the same DRM server.
I am quite confused at the moment and think maybe I am missing some sort of configuration in exoplayer since I am quite new to the concept of DRMs.
Any help would be greatly appreciated.

We figured out the solution. The ticket supplied for the DRM license server was wrong. This works as it is supposed to now and the content is getting played. Just in case anyone somehow gets the same problem or is in need of a basic Widevine content playing code, this works fine at the moment.
Best regards.

Related

how to change dotnet core outgoing http request hostname from the default localhost

I am able to successfully send requests to a sandbox via postman, given by a provider following their specs (see images below)
Successful request (see below)
In order to do that, aside from the respective headers and parameters (see image 2) I have to add a ssl/Tls certificate (.pfx) given that the server requires a 2 way handshake so it needs SSl client certificate:
Authorization (see below).
Headers (see below)
Body (see below)
Now, I am trying to do ir programatically using dotnet core 6, but I keep running into the same problem:
And here is my code:
public static string GetAccessToken(IConfiguration _config)
{
string UserName = Environment.GetEnvironmentVariable("USER_NAME");
string Password = Environment.GetEnvironmentVariable("PASSWORD");
var client = new RestClient("https://connect2.xyz.com/auth/token");
var request = new RestRequest();
X509Certificate2 FullChainCertificate = new X509Certificate2("Path/to/Cert/cert.pfx", "test");
client.Options.ClientCertificates = new X509CertificateCollection() { FullChainCertificate };
client.Options.Proxy = new WebProxy("connect2.xyz.com");
var restrequest = new RestRequest();
restrequest.Method = Method.Get;
restrequest.AddHeader("Accept", "*/*");
restrequest.AddHeader("Cache-Control", "no-cache");
restrequest.AddHeader("Content-Type", "application/x-www-form-urlencoded");
restrequest.AddHeader("Authorization", "Basic " + Convert.ToBase64String(Encoding.Default.GetBytes($"{UserName}:{Password}")));
restrequest.AddParameter("grant_type", "client_credentials");
RestResponse response = client.Execute(restrequest);
AccessTokenPointClickCare accessToken = JsonConvert.DeserializeObject<AccessTokenPointClickCare>(response.Content);
string strToken = accessToken.access_token;
return strToken;
}
Now, as the error seems to show, it has to do with the certificates (apparently), but I don't know if something in the code is wrong, or if I'm missing something, etc...
It is worth noting that this code did run in someone else's pc with the same set-up, but of course with that person's own pfx, but for the rest, it is essentially the same, and not to mention that it does work on my postman.
Finally, as the title on this question, the only thing I can think it might also be affecting the request is the host. If I reference the postman, there is a field where I have to place the host name of the server https://connect2.xyz.com/auth/token
So made it work by changing to a new Windows 10. Researching in other Stackoverflow threads found the answer: .NET CORE 5 '''HandshakeFailure'" when making HTTPS request
So I conclude it has to do with the cyphers

jetty server log request body

RequestLogHandler requestLogHandler = new RequestLogHandler();
Slf4jRequestLog requestLog = new CustomSlf4jRequestLog();
requestLogHandler.setRequestLog(requestLog);
Slf4jRequestLog is only logging request method, url and date, and response status code and bytes written.
I definitely want to log body for my PUT/POST requests.
I derived CustomSlf4jRequestLog from Slf4jRequestLog and I tried:
public void log(Request request, Response response) {
StringBuilder sb = new StringBuilder("RequestBody: ");
try {
LOG.info("BODY SIZE: " + request.getContentLength());
BufferedReader in = new BufferedReader(new InputStreamReader(request.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
sb.append(line);
}
LOG.info(sb.toString());
Unfortunately no body is printed out as it is already processed by handler?
Is it possible to get body of request here?
(I really care about body because I have JsonProvider and I want to see a whole body of request when Json fails to parse data) Or when my app fails I want to see what caused that without adding logging for each input request.
Servlet spec getting in your way here.
The actual servlet that is processing the request has already read the request body, rending further reads from request.getInputStream() invalid (you are at EOF)
If you want to capture the request body, you'll need to create a custom Servlet Filter, and then wrap the HttpServletRequest, overriding the getInputStream() AND getReader() methods with your own implementations that make a copy of the data that was read.
Then its up to you to determine what you want to do with that copy of the request body content.
or
You can just use a network capture utility like Wireshark to see what the request body was. Even if you use HTTPS, you can configure Wireshark with your server certificate to inspect encrypted conversation.
The logger is calling getInputStream() on the same request again. You are not allowed to read the same data twice. Yo should create a ServletRequestWrapper to make a copy of the body of the request.

Image Url validation in asp.net

i have images url , i need to check url is responding or not .
For Example :Below i i have written three image url, first two url is not valid only third url is valid .but second and fourth url is responding as valid image
and but there is no image.
http://media.expedia.com/hotels/1000000/90000/84900/84853/84853_744_b.jpg
http://www.iceportal.com/brochures/media/show.aspx?brochureid=ICE19044&did=3073&mtype=3073&type=pic&lang=en&publicid=4175749&resizing=X
http://images.trvl-media.com/hotels/1000000/30000/20400/20313/20313_166_b.jpg
http://www.iceportal.com/brochures/ice/ErrorPages/404.htm?aspxerrorpath=/brochures/media/show_A.aspx
here is my code:
public static bool CheckUrlExists(string url)
{
try
{
Uri u = new Uri(url);
WebRequest w = WebRequest.Create(u);
w.Method = WebRequestMethods.Http.Head;
using (StreamReader s = new StreamReader(w.GetResponse().GetResponseStream()))
{
return (s.ReadToEnd().Length >= 0);
}
}
catch
{
return false;
}
}
with this code i am validating only those url which is showing 404 error,but not those url which showing 'Sorry, requested brochure is temporarily un-published 'or any other type of message.
You will need a more complex logic to validate if the URL points to an image. If a resource is missing from the server or it is otherwise unavailable, you may get a HTTP error like the infamous 404, which will trigger a WebException. However, that is only part of the story.
Your second URL returns HTTP 200, confirming that the resource is there when in fact the resource is missing. What you really get there is a HTML document explaining the resource is not available. This is bad practice, but not without example.
At very least, you should examine the MIME type (Content-Type header, see WebResponse.ContentType) of the resource you test. A content type of image/* suggests an image-type resource. Failing to detect a known MIME type (e.g. if you receive application/octet-stream) you can actually HTTP GET get resource and run image type detection on the downloaded content.
I would suggest using HttpWebRequest and HttpWebResponse to do this, they are sub classes of WebRequest and WebResponse and as such are more granular for what you're trying to achive. The following code works with the example URIs provided
public static bool CheckUrlExists(string url)
{
try
{
Uri u = new Uri(url);
HttpWebRequest w = (HttpWebRequest)WebRequest.Create(u);
w.AllowAutoRedirect = false;
w.Method = WebRequestMethods.Http.Head;
HttpWebResponse response = (HttpWebResponse)w.GetResponse();
return response.StatusCode == HttpStatusCode.OK; //Check http status code
}
catch(WebException ex)
{
return false;
}
}
What's important here is that I'm checking the HttpStatus code. You're catch will already catch the 404s but the problem URIs ultimately lead to a 200 (OK). By setting AllowAutoRedirect to false the HttpWebRequest instance returns a 302 (redirect) status code, instead of following the redirect through to the "Sorry, requested brochure is temporarily un-published." page which is returning 200 (OK). This should serve your purpose.
Also: Catching a WebException will allow you to examine the status code (400+,500+, etc).
Be aware however, that you may be redirected to a new location for the image you're requesting. Taking that you might want to use PeterK's mime type check.

How to return error pages with body using HttpListener/HttpListenerResponse

I'm in the process of creating a REST API using HttpListener in .NET (C#). This all works out great, except for one slight issue.
I'm trying to return responses with Status Codes other than OK (200), for instance ResourceNotFound (404).
When I set the StatusCode of the HttpListenerResponse to something other than 200, and create a response body (using HttpListenerResponse.OutputStream), it seems to be resetting the status code to 200. I'm not able to send a response with StatusCode 404 and a message body. However, this should be possible according to the HTTP specs. I'm checking the requests and responses with Fiddler, but I'm not able to get what I'm looking for.
I've had the same problem and found the source of the problem :
If you write the body in the OutputStream before set the StatusCode (or any other property), the response will be sent before the modification is applied !
So, you have to proceed in this order :
public void Send(HttpListenerContext context, byte[] body)
{
// First, set a random status code and other stuffs
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
context.Response.ContentType = "text/plain";
// Write to the stream IN LAST (will send request)
context.Response.OutputStream.Write(body, 0, body.Length);
}

ASP.Net Web API - Authorization header blank

I am having to re-write an existing REST API using .NET (originally written with Ruby). From the client's perspective, it has to work exactly the same way as the old API - i.e. the client code mustn't need to change. The current API requires Basic Authentication. So to call the old API, the following works perfectly:-
var wc = new System.Net.WebClient();
var myCache = new CredentialCache();
myCache.Add(new Uri(url), "Basic", new NetworkCredential("XXX", "XXX"));
wc.Credentials = myCache;
var returnBytes = wc.DownloadData("http://xxxx");
(I have had to ommit the real URL / username / password etc for security reasons).
Now I am writing the new API using ASP.Net Web API with MVC4. I have a weird problem and cannot find anybody else with exactly the same problem. In order to support Basic Authentication, I have followed the guidelines here:
http://sixgun.wordpress.com/2012/02/29/asp-net-web-api-basic-authentication/
One thing, I put the code to "hook in the handler" in the Global.asax.cs file in the Application_Start() event (that wasn't explained so I guessed).
Anyway, if I call my API (which I have deployed in IIS) using the above code, the Authorization header is always null, and the above fails with 401 Unauthorized. However, if I manually set the header using this code, it works fine - i.e. the Authorization header now exists and I am able to Authenticate the user.
private void SetBasicAuthHeader(WebClient request, String userName, String userPassword)
{
string authInfo = userName + ":" + userPassword;
authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
request.Headers["Authorization"] = "Basic " + authInfo;
}
.......
var wc = new System.Net.WebClient();
SetBasicAuthHeader(request, "XXXX", "XXXX");
var returnBytes = wc.DownloadData("http://xxxx");
Although that works, it's no good to me because existing users of the existing API are not going to be manually setting the header.
Reading up on how Basic Authentication works, the initial request is meant to be anonymous, then the client is returned 401, then the client is meant to try again. However if I put a break point in my code, it will never hit the code again in Antony's example. I was expecting my breakpoint to be hit twice.
Any ideas how I can get this to work?
You're expecting the right behavior. System.Net.WebClient does not automatically include the Authorization headers upon initial request. It only sends them when properly challenged by a response, which to my knowledge is a 401 status code and a proper WWW-Authenticate header. See here and here for further info.
I'm assuming your basic authentication handler is not returning the WWW-Authenticate header and as such WebClient never even attempts to send the credentials on a second request. You should be able to watch this in Fiddler or a similar tool.
If your handler did something like this, you should witness the WebClient approach working:
//if is not authenticated or Authorization header is null
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
{
var response = task.Result;
response.StatusCode = HttpStatusCode.Unauthorized;
response.Headers.Add("WWW-Authenticate", "Basic realm=\"www.whatever.com\"");
return response;
});
//else (is authenticated)
return base.SendAsync(request, cancellationToken);
As you noticed, if you include the Authorization headers on every request (like you did in your alternative approach) then your handler already works as is. So it may be sufficient - it just isn't for WebClient and other clients that operate in the same way.

Resources