mirror of
https://github.com/clangen/musikcube.git
synced 2025-01-29 21:32:41 +00:00
Added client/server support for "Authorization" header in audio server.
This commit is contained in:
parent
f3829564ca
commit
025ad8825f
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user