Added client/server support for "Authorization" header in audio server.

This commit is contained in:
casey langen 2017-05-26 19:16:46 -07:00
parent f3829564ca
commit 025ad8825f
4 changed files with 145 additions and 97 deletions

View File

@ -3,6 +3,7 @@ package io.casey.musikcube.remote.playback;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.util.Base64;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
@ -28,6 +29,7 @@ import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.Util;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@ -36,7 +38,10 @@ import io.casey.musikcube.remote.util.NetworkUtil;
import io.casey.musikcube.remote.util.Preconditions;
import io.casey.musikcube.remote.websocket.Prefs;
import okhttp3.Cache;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class ExoPlayerWrapper extends PlayerWrapper {
private static OkHttpClient audioStreamHttpClient = null;
@ -86,6 +91,7 @@ public class ExoPlayerWrapper extends PlayerWrapper {
synchronized (ExoPlayerWrapper.class) {
if (audioStreamHttpClient == null) {
final SharedPreferences prefs = ExoPlayerWrapper.this.prefs;
final File path = new File(context.getExternalCacheDir(), "audio");
int diskCacheIndex = this.prefs.getInt(
@ -95,8 +101,15 @@ public class ExoPlayerWrapper extends PlayerWrapper {
diskCacheIndex = 0;
}
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.cache(new Cache(path, CACHE_SETTING_TO_BYTES.get(diskCacheIndex)));
final OkHttpClient.Builder builder = new OkHttpClient.Builder()
.cache(new Cache(path, CACHE_SETTING_TO_BYTES.get(diskCacheIndex)))
.addInterceptor((chain) -> {
Request request = chain.request();
final String userPass = "default:" + prefs.getString(Prefs.Key.PASSWORD, Prefs.Default.PASSWORD);
final String encoded = Base64.encodeToString(userPass.getBytes(), Base64.NO_WRAP);
request = request.newBuilder().addHeader("Authorization", "Basic " + encoded).build();
return chain.proceed(request);
});
if (this.prefs.getBoolean(Prefs.Key.CERT_VALIDATION_DISABLED, Prefs.Default.CERT_VALIDATION_DISABLED)) {
NetworkUtil.disableCertificateValidation(builder);

View File

@ -43,6 +43,8 @@
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
#include <websocketpp/base64/base64.hpp>
#include <unordered_map>
#include <string>
@ -198,6 +200,31 @@ static size_t getUnsignedUrlParam(
return defaultValue;
}
static bool isAuthenticated(MHD_Connection *connection, Context& context) {
const char* authPtr = MHD_lookup_connection_value(
connection, MHD_HEADER_KIND, "Authorization");
if (authPtr && strlen(authPtr)) {
std::string auth(authPtr);
if (auth.find("Basic ") == 0) {
std::string encoded = auth.substr(6);
if (encoded.size()) {
std::string decoded = websocketpp::base64_decode(encoded);
std::vector<std::string> userPass;
boost::split(userPass, decoded, boost::is_any_of(":"));
if (userPass.size() == 2) {
std::string password = GetPreferenceString(context.prefs, key::password, defaults::password);
return userPass[0] == "default" && userPass[1] == password;
}
}
}
}
return false;
}
HttpServer::HttpServer(Context& context)
: context(context)
, running(false) {
@ -277,110 +304,118 @@ int HttpServer::HandleRequest(
int status = MHD_HTTP_OK;
try {
std::string urlStr(url);
if (urlStr[0] == '/') {
urlStr = urlStr.substr(1);
if (!isAuthenticated(connection, server->context)) {
status = 401; /* unauthorized */
static const char* error = "unauthorized";
response = MHD_create_response_from_buffer(strlen(error), (void*) error, MHD_RESPMEM_PERSISTENT);
}
else {
/* if we get here we're authenticated */
std::string urlStr(url);
std::vector<std::string> parts;
boost::split(parts, urlStr, boost::is_any_of("/"));
if (parts.size() > 0) {
if (parts.at(0) == fragment::audio && parts.size() == 3) {
IRetainedTrack* track = nullptr;
bool byExternalId = (parts.at(1) == fragment::external_id);
if (urlStr[0] == '/') {
urlStr = urlStr.substr(1);
}
if (byExternalId) {
std::string externalId = urlDecode(parts.at(2));
track = server->context.dataProvider->QueryTrackByExternalId(externalId.c_str());
}
else if (parts.at(1) == fragment::id) {
uint64_t id = std::stoull(urlDecode(parts.at(2)));
track = server->context.dataProvider->QueryTrackById(id);
}
std::vector<std::string> parts;
boost::split(parts, urlStr, boost::is_any_of("/"));
if (parts.size() > 0) {
if (parts.at(0) == fragment::audio && parts.size() == 3) {
IRetainedTrack* track = nullptr;
bool byExternalId = (parts.at(1) == fragment::external_id);
if (track) {
std::string filename = GetMetadataString(track, key::filename);
track->Release();
size_t bitrate = getUnsignedUrlParam(connection, "bitrate", 0);
IDataStream* file = (bitrate == 0)
? server->context.environment->GetDataStream(filename.c_str())
: Transcoder::Transcode(server->context, filename, bitrate);
const char* rangeVal = MHD_lookup_connection_value(
connection, MHD_HEADER_KIND, "Range");
Range* range = parseRange(file, rangeVal);
/* ehh... */
bool isOnDemandTranscoder = !!dynamic_cast<TranscodingDataStream*>(file);
/* gotta be careful with request ranges if we're transcoding. don't
allow any custom ranges other than from 0 to end. */
if (isOnDemandTranscoder && rangeVal && strlen(rangeVal)) {
if (range->from != 0 || range->to != range->total - 1) {
delete range;
if (file) {
file->Destroy();
file = nullptr;
}
if (false && server->context.prefs->GetBool(
prefs::transcoder_synchronous_fallback.c_str(),
defaults::transcoder_synchronous_fallback))
{
/* if we're allowed, fall back to synchronous transcoding. we'll block
here until the entire file has been converted and cached */
file = Transcoder::TranscodeAndWait(server->context, filename, bitrate);
range = parseRange(file, rangeVal);
}
else {
/* otherwise fail with a "range not satisfiable" status */
status = 416;
char empty[1];
response = MHD_create_response_from_buffer(0, empty, MHD_RESPMEM_PERSISTENT);
}
}
if (byExternalId) {
std::string externalId = urlDecode(parts.at(2));
track = server->context.dataProvider->QueryTrackByExternalId(externalId.c_str());
}
else if (parts.at(1) == fragment::id) {
uint64_t id = std::stoull(urlDecode(parts.at(2)));
track = server->context.dataProvider->QueryTrackById(id);
}
if (file) {
size_t length = (range->to - range->from);
if (track) {
std::string filename = GetMetadataString(track, key::filename);
track->Release();
response = MHD_create_response_from_callback(
length == 0 ? MHD_SIZE_UNKNOWN : length + 1,
4096,
&fileReadCallback,
range,
&fileFreeCallback);
size_t bitrate = getUnsignedUrlParam(connection, "bitrate", 0);
if (response) {
if (!isOnDemandTranscoder) {
MHD_add_response_header(response, "Accept-Ranges", "bytes");
}
IDataStream* file = (bitrate == 0)
? server->context.environment->GetDataStream(filename.c_str())
: Transcoder::Transcode(server->context, filename, bitrate);
if (byExternalId) {
/* if we're using an on-demand transcoder, ensure the client does not cache the
result because we have to guess the content length. */
std::string value = isOnDemandTranscoder ? "no-cache" : "public, max-age=31536000";
MHD_add_response_header(response, "Cache-Control", value.c_str());
}
const char* rangeVal = MHD_lookup_connection_value(
connection, MHD_HEADER_KIND, "Range");
MHD_add_response_header(response, "Content-Type", contentType(filename).c_str());
MHD_add_response_header(response, "Server", "musikcube websocket_remote");
Range* range = parseRange(file, rangeVal);
if ((rangeVal && strlen(rangeVal)) || range->from > 0) {
if (range->total > 0) {
MHD_add_response_header(response, "Content-Range", range->HeaderValue().c_str());
status = MHD_HTTP_PARTIAL_CONTENT;
/* ehh... */
bool isOnDemandTranscoder = !!dynamic_cast<TranscodingDataStream*>(file);
/* gotta be careful with request ranges if we're transcoding. don't
allow any custom ranges other than from 0 to end. */
if (isOnDemandTranscoder && rangeVal && strlen(rangeVal)) {
if (range->from != 0 || range->to != range->total - 1) {
delete range;
if (file) {
file->Destroy();
file = nullptr;
}
if (false && server->context.prefs->GetBool(
prefs::transcoder_synchronous_fallback.c_str(),
defaults::transcoder_synchronous_fallback))
{
/* if we're allowed, fall back to synchronous transcoding. we'll block
here until the entire file has been converted and cached */
file = Transcoder::TranscodeAndWait(server->context, filename, bitrate);
range = parseRange(file, rangeVal);
}
else {
/* otherwise fail with a "range not satisfiable" status */
status = 416;
char empty[1];
response = MHD_create_response_from_buffer(0, empty, MHD_RESPMEM_PERSISTENT);
}
}
}
else {
file->Destroy();
file = nullptr;
if (file) {
size_t length = (range->to - range->from);
response = MHD_create_response_from_callback(
length == 0 ? MHD_SIZE_UNKNOWN : length + 1,
4096,
&fileReadCallback,
range,
&fileFreeCallback);
if (response) {
if (!isOnDemandTranscoder) {
MHD_add_response_header(response, "Accept-Ranges", "bytes");
}
if (byExternalId) {
/* if we're using an on-demand transcoder, ensure the client does not cache the
result because we have to guess the content length. */
std::string value = isOnDemandTranscoder ? "no-cache" : "public, max-age=31536000";
MHD_add_response_header(response, "Cache-Control", value.c_str());
}
MHD_add_response_header(response, "Content-Type", contentType(filename).c_str());
MHD_add_response_header(response, "Server", "musikcube websocket_remote");
if ((rangeVal && strlen(rangeVal)) || range->from > 0) {
if (range->total > 0) {
MHD_add_response_header(response, "Content-Range", range->HeaderValue().c_str());
status = MHD_HTTP_PARTIAL_CONTENT;
}
}
}
else {
file->Destroy();
file = nullptr;
}
}
}
}

View File

@ -198,14 +198,14 @@ PositionType TranscodingDataStream::Read(void *buffer, PositionType bytesToRead)
encodedBytes.data,
encodedBytes.length);
}
/* the non-stereo case needs to be downmuxed. our downmuxing is simple,
/* the non-stereo case needs to be downmuxed. our downmixing is simple,
we just use the stereo channels. in the case of mono, we duplicate the
to left and right */
else {
downmux.realloc(numSamples * 2);
downmix.realloc(numSamples * 2);
float* from = pcmBuffer->BufferPointer();
float* to = downmux.data;
float* to = downmix.data;
if (channels == 1) {
/* mono -> stereo */
@ -229,7 +229,7 @@ PositionType TranscodingDataStream::Read(void *buffer, PositionType bytesToRead)
encodeCount =
lame_encode_buffer_interleaved_ieee_float(
lame,
downmux.data,
downmix.data,
numSamples,
encodedBytes.data,
encodedBytes.length);

View File

@ -131,7 +131,7 @@ class TranscodingDataStream : public musik::core::sdk::IDataStream {
ByteBuffer<unsigned char> encodedBytes;
ByteBuffer<unsigned char> spillover;
ByteBuffer<float> downmux;
ByteBuffer<float> downmix;
size_t bitrate;
bool eof;
std::mutex mutex;