diff --git a/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/playback/ExoPlayerWrapper.java b/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/playback/ExoPlayerWrapper.java index 0d86c311b..0d48cf772 100644 --- a/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/playback/ExoPlayerWrapper.java +++ b/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/playback/ExoPlayerWrapper.java @@ -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); diff --git a/src/plugins/websocket_remote/HttpServer.cpp b/src/plugins/websocket_remote/HttpServer.cpp index ad1326e98..96cb7ee6e 100644 --- a/src/plugins/websocket_remote/HttpServer.cpp +++ b/src/plugins/websocket_remote/HttpServer.cpp @@ -43,6 +43,8 @@ #include #include +#include + #include #include @@ -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 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 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 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(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(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; + } } } } diff --git a/src/plugins/websocket_remote/TranscodingDataStream.cpp b/src/plugins/websocket_remote/TranscodingDataStream.cpp index 28f1f96d6..d67458c72 100644 --- a/src/plugins/websocket_remote/TranscodingDataStream.cpp +++ b/src/plugins/websocket_remote/TranscodingDataStream.cpp @@ -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); diff --git a/src/plugins/websocket_remote/TranscodingDataStream.h b/src/plugins/websocket_remote/TranscodingDataStream.h index a6e2703ee..d8d71e957 100644 --- a/src/plugins/websocket_remote/TranscodingDataStream.h +++ b/src/plugins/websocket_remote/TranscodingDataStream.h @@ -131,7 +131,7 @@ class TranscodingDataStream : public musik::core::sdk::IDataStream { ByteBuffer encodedBytes; ByteBuffer spillover; - ByteBuffer downmux; + ByteBuffer downmix; size_t bitrate; bool eof; std::mutex mutex;