diff --git a/server/plat/gdial.cpp b/server/plat/gdial.cpp index c474b210..4c966e0b 100644 --- a/server/plat/gdial.cpp +++ b/server/plat/gdial.cpp @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include "Module.h" #include @@ -59,6 +61,8 @@ static gdial_modelname_cb g_modelname_cb = NULL; #define DIAL_MAX_NUM_OF_APP_PREFIXES (64) #define DIAL_MAX_NUM_OF_APP_CORS (64) +static std::vector splitCorsOrigins(const std::string& corsValue); + class GDialCastObject { public: @@ -124,12 +128,21 @@ class GDialCastObject break; } GDIAL_LOGINFO("Application:[%d]", i); + GDIAL_LOGINFO("registerApplications: appName[%s] prefixes[%s] cors[%s] allowStop[%d]", + appEntry->Names.c_str(), + appEntry->prefixes.c_str(), + appEntry->cors.c_str(), + appEntry->allowStop); gAppPrefxes = g_list_prepend (gAppPrefxes, g_strdup(appEntry->prefixes.c_str())); GDIAL_LOGINFO("%s, ", appEntry->prefixes.c_str()); GDIAL_LOGINFO(""); - allowed_origins = g_list_prepend (allowed_origins, g_strdup(appEntry->cors.c_str())); - GDIAL_LOGINFO("%s, ", appEntry->cors.c_str()); + const std::vector corsOrigins = splitCorsOrigins(appEntry->cors.c_str()); + GDIAL_LOGINFO("registerApplications: splitCorsOrigins app[%s] count[%zu]", appEntry->Names.c_str(), corsOrigins.size()); + for (const auto& origin : corsOrigins) { + allowed_origins = g_list_prepend(allowed_origins, g_strdup(origin.c_str())); + GDIAL_LOGINFO("registerApplications: allowed_origin[%s] for app[%s]", origin.c_str(), appEntry->Names.c_str()); + } GDIAL_LOGINFO(""); GHashTable *gProperties = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); @@ -229,7 +242,7 @@ class GDialCastObject GDIAL_LOGTRACE("Exiting ..."); return error; } - + GDialErrorCode launchApplication(const char* appName, const char* args) { GDIAL_LOGTRACE("Entering ..."); @@ -275,8 +288,8 @@ class GDialCastObject } GDIAL_LOGINFO("App[%s] payload[%s] query_string[%s] additional_data_url[%s]observer[%p]", - applicationName.c_str(), - payLoad.c_str(), + applicationName.c_str(), + payLoad.c_str(), queryString.c_str(), additionalDataUrl.c_str(), m_observer); @@ -667,7 +680,7 @@ int gdial_os_application_stop(const char *app_name, int instance_id) { GDIAL_LOGTRACE("Exiting ..."); return GDIAL_APP_ERROR_INTERNAL; } - GDialErrorCode ret = GDialObjHandle->stopApplication(app_name,std::to_string(instance_id).c_str()); + GDialErrorCode ret = GDialObjHandle->stopApplication(app_name,std::to_string(instance_id).c_str()); if (ret != GDIAL_CAST_ERROR_NONE) { GDIAL_LOGINFO("GDialObjHandle.stopApplication failed!!! Error=%x",ret); GDIAL_LOGTRACE("Exiting ..."); @@ -709,7 +722,7 @@ int gdial_os_application_hide(const char *app_name, int instance_id) GDIAL_LOGTRACE("Exiting ..."); return GDIAL_APP_ERROR_BAD_REQUEST; } - + if (nullptr == GDialObjHandle) { GDIAL_LOGERROR("GDialObjHandle NULL!!! "); @@ -816,8 +829,8 @@ int gdial_os_application_state(const char *app_name, int instance_id, GDialAppSt GDIAL_LOGINFO("app [%s] state converted to [%d]", app_name, *state); } else { - *state = GDIAL_APP_STATE_RUNNING; - } + *state = GDIAL_APP_STATE_RUNNING; + } } } GDIAL_LOGTRACE("Exiting ..."); @@ -1007,3 +1020,24 @@ int gdial_os_application_service_notification(gboolean isNotifyRequired, void* n GDIAL_LOGTRACE("Exiting ..."); return GDIAL_APP_ERROR_NONE; } + +static std::vector splitCorsOrigins(const std::string& corsValue) +{ + std::vector origins; + std::stringstream ss(corsValue); + std::string origin; + + while (std::getline(ss, origin, ' ')) { + const std::string::size_type start = origin.find_first_not_of(" \t\r\n"); + if (start == std::string::npos) { + continue; + } + const std::string::size_type end = origin.find_last_not_of(" \t\r\n"); + const std::string trimmed = origin.substr(start, end - start + 1); + if (!trimmed.empty()) { + origins.push_back(trimmed); + } + } + + return origins; +} diff --git a/server/plat/gdial_app_registry.c b/server/plat/gdial_app_registry.c index 2ef69c77..be126791 100644 --- a/server/plat/gdial_app_registry.c +++ b/server/plat/gdial_app_registry.c @@ -19,6 +19,7 @@ * limitations under the License. */ +#include #include #include #include "gdialservicelogging.h" @@ -42,25 +43,142 @@ void gdial_app_regstry_dispose (GDialAppRegistry *app_registry) { return; } +// Helper function to extract host from an origin string without libsoup +static gchar *util_get_host_from_origin(const gchar *origin) { + if (!origin) return NULL; + + const gchar *host_start = NULL; + + if (g_str_has_prefix(origin, "https://")) { + host_start = origin + strlen("https://"); + } else if (g_str_has_prefix(origin, "http://")) { + host_start = origin + strlen("http://"); + } else { + host_start = origin; + } + + const gchar *delim_port = strchr(host_start, ':'); + const gchar *delim_path = strchr(host_start, '/'); + const gchar *end = NULL; + + if (delim_port && delim_path) { + end = (delim_port < delim_path) ? delim_port : delim_path; + } else if (delim_port) { + end = delim_port; + } else { + end = delim_path; + } + + if (end) { + return g_strndup(host_start, end - host_start); + } + + return g_strdup(host_start); +} + gboolean gdial_app_registry_is_allowed_origin(GDialAppRegistry *app_registry, const gchar *header_origin) { gboolean is_allowed = FALSE; - if (app_registry) { - GList *allowed_origins = app_registry->allowed_origins; - if(allowed_origins == NULL){ + + if (!app_registry) { + GDIAL_LOGINFO("gdial_app_registry_is_allowed_origin: app_registry=NULL header_origin[%s]", header_origin ? header_origin : "(null)"); + return FALSE; + } + if (header_origin == NULL) { + GDIAL_LOGINFO("gdial_app_registry_is_allowed_origin: app[%s] header_origin=NULL", app_registry->name ? app_registry->name : "(null)"); + return FALSE; + } + + GList *allowed_origins = app_registry->allowed_origins; + GDIAL_LOGINFO("gdial_app_registry_is_allowed_origin: app[%s] incoming_origin[%s] configured_count[%d]", + app_registry->name ? app_registry->name : "(null)", + header_origin, + g_list_length(allowed_origins)); + if (allowed_origins == NULL) { + GDIAL_LOGINFO("gdial_app_registry_is_allowed_origin: app[%s] allowed list empty => allowing origin[%s]", app_registry->name ? app_registry->name : "(null)", header_origin); + return TRUE; + } + + while (allowed_origins) { + gchar *origin = (gchar *)allowed_origins->data; + GDIAL_LOGINFO("gdial_app_registry_is_allowed_origin: app[%s] checking configured_origin[%s] against incoming[%s]", + app_registry->name ? app_registry->name : "(null)", + origin ? origin : "(null)", + header_origin); + if (origin == NULL) { + allowed_origins = allowed_origins->next; + continue; + } + + if (g_strcmp0(origin, "*") == 0) { + GDIAL_LOGINFO("gdial_app_registry_is_allowed_origin: app[%s] matched wildcard '*'", app_registry->name ? app_registry->name : "(null)"); is_allowed = TRUE; + break; } - else - { - while(allowed_origins) { - gchar *origin = (gchar *)allowed_origins->data; - if (GDIAL_STR_ENDS_WITH(header_origin, origin)) { + + if (g_str_has_prefix(origin, "package:")) { + if (g_strcmp0(header_origin, origin) == 0) { + GDIAL_LOGINFO("gdial_app_registry_is_allowed_origin: app[%s] matched package origin exact match: configured[%s] incoming[%s]", + app_registry->name ? app_registry->name : "(null)", + origin, + header_origin); + is_allowed = TRUE; + break; + } + GDIAL_LOGINFO("gdial_app_registry_is_allowed_origin: app[%s] package mismatch: configured[%s] incoming[%s]", + app_registry->name ? app_registry->name : "(null)", + origin, + header_origin); + } + else if (g_str_has_prefix(origin, "https://*.")) { + gchar *wildcard_host = origin + strlen("https://*."); + gchar *header_host = util_get_host_from_origin(header_origin); + GDIAL_LOGINFO("gdial_app_registry_is_allowed_origin: app[%s] wildcard check: configured[%s] wildcard_host[%s] incoming_host[%s]", + app_registry->name ? app_registry->name : "(null)", + origin, + wildcard_host, + header_host ? header_host : "(null)"); + if (header_host) { + if (g_strcmp0(header_host, wildcard_host) == 0 || g_str_has_suffix(header_host, wildcard_host)) { + size_t host_len = strlen(header_host); + size_t wild_len = strlen(wildcard_host); + if (host_len == wild_len || (host_len > wild_len && header_host[host_len - wild_len - 1] == '.')) { + GDIAL_LOGINFO("gdial_app_registry_is_allowed_origin: app[%s] matched wildcard host rule", app_registry->name ? app_registry->name : "(null)"); + is_allowed = TRUE; + } + } + g_free(header_host); + } + if (is_allowed) break; + } + else { + gchar *configured_host = util_get_host_from_origin(origin); + gchar *header_host = util_get_host_from_origin(header_origin); + GDIAL_LOGINFO("gdial_app_registry_is_allowed_origin: app[%s] strict host check: configured_host[%s] header_host[%s] configured_scheme_secure[%d] incoming_scheme_secure[%d]", + app_registry->name ? app_registry->name : "(null)", + configured_host ? configured_host : "(null)", + header_host ? header_host : "(null)", + g_str_has_prefix(origin, "https://"), + g_str_has_prefix(header_origin, "https://")); + if (configured_host && header_host) { + if (g_strcmp0(header_host, configured_host) == 0 && + g_str_has_prefix(header_origin, "https://") == g_str_has_prefix(origin, "https://")) { + GDIAL_LOGINFO("gdial_app_registry_is_allowed_origin: app[%s] matched strict host rule", app_registry->name ? app_registry->name : "(null)"); is_allowed = TRUE; - break; } - allowed_origins = allowed_origins->next; } + g_free(configured_host); + g_free(header_host); + + if (is_allowed) break; } + + allowed_origins = allowed_origins->next; } + + GDIAL_LOGINFO("gdial_app_registry_is_allowed_origin: app[%s] final_result[%s] for incoming[%s]", + app_registry->name ? app_registry->name : "(null)", + is_allowed ? "ALLOW" : "DENY", + header_origin); return is_allowed; } @@ -125,4 +243,4 @@ GDialAppRegistry* gdial_app_registry_new (const gchar *app_name, const GList *ap GDIAL_LOGINFO("App:[%s] uuid:[%s] per Bootup", app_name, uuid_data); return app_registry; -} \ No newline at end of file +} diff --git a/tests/L1Tests/plat/test_gdialPlat.cpp b/tests/L1Tests/plat/test_gdialPlat.cpp index 66b99318..98ce1cf2 100644 --- a/tests/L1Tests/plat/test_gdialPlat.cpp +++ b/tests/L1Tests/plat/test_gdialPlat.cpp @@ -164,6 +164,46 @@ TEST_F(GDialAppRegistryTest, IsAllowedOrigin_NullOriginDenied) { EXPECT_FALSE(gdial_app_registry_is_allowed_origin(registry, nullptr)); } +TEST_F(GDialAppRegistryTest, IsAllowedOrigin_PackageMatchPermitsMobileApp) { + GList *origins = g_list_append(nullptr, g_strdup("package:com.google.android.youtube")); + registry = gdial_app_registry_new("YouTube", nullptr, nullptr, + TRUE, FALSE, origins); + g_list_free_full(origins, g_free); + ASSERT_NE(registry, nullptr); + EXPECT_TRUE(gdial_app_registry_is_allowed_origin( + registry, "package:com.google.android.youtube")); + EXPECT_FALSE(gdial_app_registry_is_allowed_origin( + registry, "package:com.google.ios.youtube")); +} + +TEST_F(GDialAppRegistryTest, IsAllowedOrigin_WildcardHostPermitsSubdomains) { + GList *origins = g_list_append(nullptr, g_strdup("https://*.youtube.com")); + registry = gdial_app_registry_new("YouTube", nullptr, nullptr, + TRUE, FALSE, origins); + g_list_free_full(origins, g_free); + ASSERT_NE(registry, nullptr); + EXPECT_TRUE(gdial_app_registry_is_allowed_origin( + registry, "https://www.youtube.com")); + EXPECT_TRUE(gdial_app_registry_is_allowed_origin( + registry, "https://m.youtube.com")); + EXPECT_FALSE(gdial_app_registry_is_allowed_origin( + registry, "https://youtube.com.evil.example")); + EXPECT_FALSE(gdial_app_registry_is_allowed_origin( + registry, "https://notyoutube.com")); +} + +TEST_F(GDialAppRegistryTest, IsAllowedOrigin_StrictSchemeMismatchDenied) { + GList *origins = g_list_append(nullptr, g_strdup("https://www.youtube.com")); + registry = gdial_app_registry_new("YouTube", nullptr, nullptr, + TRUE, FALSE, origins); + g_list_free_full(origins, g_free); + ASSERT_NE(registry, nullptr); + EXPECT_TRUE(gdial_app_registry_is_allowed_origin( + registry, "https://www.youtube.com")); + EXPECT_FALSE(gdial_app_registry_is_allowed_origin( + registry, "http://www.youtube.com")); +} + /* ================================================================== */ /* gdial_app_regstry_dispose */ /* ================================================================== */ @@ -190,4 +230,3 @@ TEST(GDialAppRegistryDisposeTest, Dispose_WithPropertiesDoesNotCrash) { ASSERT_NE(r, nullptr); gdial_app_regstry_dispose(r); } -