// Copyright 2014 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/ipc.h" #include "core/hle/service/http_c.h" namespace Service { namespace HTTP { namespace ErrCodes { enum { TooManyContexts = 26, InvalidRequestMethod = 32, /// This error is returned in multiple situations: when trying to initialize an /// already-initialized session, or when using the wrong context handle in a context-bound /// session SessionStateError = 102, }; } const ResultCode ERROR_STATE_ERROR = // 0xD8A0A066 ResultCode(ErrCodes::SessionStateError, ErrorModule::HTTP, ErrorSummary::InvalidState, ErrorLevel::Permanent); void HTTP_C::Initialize(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x1, 1, 4); const u32 shmem_size = rp.Pop(); rp.PopPID(); shared_memory = rp.PopObject(); if (shared_memory) { shared_memory->name = "HTTP_C:shared_memory"; } LOG_WARNING(Service_HTTP, "(STUBBED) called, shared memory size: {}", shmem_size); auto* session_data = GetSessionData(ctx.Session()); ASSERT(session_data); if (session_data->initialized) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ERROR_STATE_ERROR); return; } session_data->initialized = true; IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); // This returns 0xd8a0a046 if no network connection is available. // Just assume we are always connected. rb.Push(RESULT_SUCCESS); } void HTTP_C::CreateContext(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x2, 2, 2); const u32 url_size = rp.Pop(); RequestMethod method = rp.PopEnum(); Kernel::MappedBuffer& buffer = rp.PopMappedBuffer(); // Copy the buffer into a string without the \0 at the end of the buffer std::string url(url_size, '\0'); buffer.Read(&url[0], 0, url_size - 1); LOG_DEBUG(Service_HTTP, "called, url_size={}, url={}, method={}", url_size, url, static_cast(method)); auto* session_data = GetSessionData(ctx.Session()); ASSERT(session_data); // This command can only be called without a bound session. if (session_data->current_http_context != boost::none) { LOG_ERROR(Service_HTTP, "Command called with a bound context"); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(ResultCode(ErrorDescription::NotImplemented, ErrorModule::HTTP, ErrorSummary::Internal, ErrorLevel::Permanent)); rb.PushMappedBuffer(buffer); return; } static constexpr size_t MaxConcurrentHTTPContexts = 8; if (session_data->num_http_contexts >= MaxConcurrentHTTPContexts) { // There can only be 8 HTTP contexts open at the same time for any particular session. LOG_ERROR(Service_HTTP, "Tried to open too many HTTP contexts"); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(ResultCode(ErrCodes::TooManyContexts, ErrorModule::HTTP, ErrorSummary::InvalidState, ErrorLevel::Permanent)); rb.PushMappedBuffer(buffer); return; } if (method == RequestMethod::None || static_cast(method) >= TotalRequestMethods) { LOG_ERROR(Service_HTTP, "invalid request method={}", static_cast(method)); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(ResultCode(ErrCodes::InvalidRequestMethod, ErrorModule::HTTP, ErrorSummary::InvalidState, ErrorLevel::Permanent)); rb.PushMappedBuffer(buffer); return; } contexts.emplace(++context_counter, Context()); contexts[context_counter].url = std::move(url); contexts[context_counter].method = method; contexts[context_counter].state = RequestState::NotStarted; // TODO(Subv): Find a correct default value for this field. contexts[context_counter].socket_buffer_size = 0; contexts[context_counter].handle = context_counter; session_data->num_http_contexts++; IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); rb.Push(RESULT_SUCCESS); rb.Push(context_counter); rb.PushMappedBuffer(buffer); } void HTTP_C::CloseContext(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x3, 1, 0); u32 context_handle = rp.Pop(); LOG_WARNING(Service_HTTP, "(STUBBED) called, handle={}", context_handle); auto* session_data = GetSessionData(ctx.Session()); ASSERT(session_data); ASSERT_MSG(session_data->current_http_context == boost::none, "Unimplemented CloseContext on context-bound session"); auto itr = contexts.find(context_handle); if (itr == contexts.end()) { // The real HTTP module just silently fails in this case. IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); LOG_ERROR(Service_HTTP, "called, context {} not found", context_handle); return; } // TODO(Subv): What happens if you try to close a context that's currently being used? ASSERT(itr->second.state == RequestState::NotStarted); // TODO(Subv): Make sure that only the session that created the context can close it. contexts.erase(itr); session_data->num_http_contexts--; IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); } void HTTP_C::AddRequestHeader(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x11, 3, 4); const u32 context_handle = rp.Pop(); const u32 name_size = rp.Pop(); const u32 value_size = rp.Pop(); const std::vector name_buffer = rp.PopStaticBuffer(); Kernel::MappedBuffer& value_buffer = rp.PopMappedBuffer(); // Copy the name_buffer into a string without the \0 at the end const std::string name(name_buffer.begin(), name_buffer.end() - 1); // Copy thr value_buffer into a string without the \0 at the end std::string value(value_size - 1, '\0'); value_buffer.Read(&value[0], 0, value_size - 1); auto itr = contexts.find(context_handle); if (itr == contexts.end()) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ERROR_STATE_ERROR); LOG_ERROR(Service_HTTP, "called, context {} not found", context_handle); return; } ASSERT(itr->second.state == RequestState::NotStarted); ASSERT(std::find_if(itr->second.headers.begin(), itr->second.headers.end(), [&name](const Context::RequestHeader& m) -> bool { return m.name == name; }) == itr->second.headers.end()); itr->second.headers.emplace_back(name, value); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(RESULT_SUCCESS); rb.PushMappedBuffer(value_buffer); LOG_WARNING(Service_HTTP, "called, name={}, value={}, context_handle={}", name, value, context_handle); } HTTP_C::HTTP_C() : ServiceFramework("http:C", 32) { static const FunctionInfo functions[] = { {0x00010044, &HTTP_C::Initialize, "Initialize"}, {0x00020082, &HTTP_C::CreateContext, "CreateContext"}, {0x00030040, &HTTP_C::CloseContext, "CloseContext"}, {0x00040040, nullptr, "CancelConnection"}, {0x00050040, nullptr, "GetRequestState"}, {0x00060040, nullptr, "GetDownloadSizeState"}, {0x00070040, nullptr, "GetRequestError"}, {0x00080042, nullptr, "InitializeConnectionSession"}, {0x00090040, nullptr, "BeginRequest"}, {0x000A0040, nullptr, "BeginRequestAsync"}, {0x000B0082, nullptr, "ReceiveData"}, {0x000C0102, nullptr, "ReceiveDataTimeout"}, {0x000D0146, nullptr, "SetProxy"}, {0x000E0040, nullptr, "SetProxyDefault"}, {0x000F00C4, nullptr, "SetBasicAuthorization"}, {0x00100080, nullptr, "SetSocketBufferSize"}, {0x001100C4, &HTTP_C::AddRequestHeader, "AddRequestHeader"}, {0x001200C4, nullptr, "AddPostDataAscii"}, {0x001300C4, nullptr, "AddPostDataBinary"}, {0x00140082, nullptr, "AddPostDataRaw"}, {0x00150080, nullptr, "SetPostDataType"}, {0x001600C4, nullptr, "SendPostDataAscii"}, {0x00170144, nullptr, "SendPostDataAsciiTimeout"}, {0x001800C4, nullptr, "SendPostDataBinary"}, {0x00190144, nullptr, "SendPostDataBinaryTimeout"}, {0x001A0082, nullptr, "SendPostDataRaw"}, {0x001B0102, nullptr, "SendPOSTDataRawTimeout"}, {0x001C0080, nullptr, "SetPostDataEncoding"}, {0x001D0040, nullptr, "NotifyFinishSendPostData"}, {0x001E00C4, nullptr, "GetResponseHeader"}, {0x001F0144, nullptr, "GetResponseHeaderTimeout"}, {0x00200082, nullptr, "GetResponseData"}, {0x00210102, nullptr, "GetResponseDataTimeout"}, {0x00220040, nullptr, "GetResponseStatusCode"}, {0x002300C0, nullptr, "GetResponseStatusCodeTimeout"}, {0x00240082, nullptr, "AddTrustedRootCA"}, {0x00250080, nullptr, "AddDefaultCert"}, {0x00260080, nullptr, "SelectRootCertChain"}, {0x002700C4, nullptr, "SetClientCert"}, {0x00290080, nullptr, "SetClientCertContext"}, {0x002A0040, nullptr, "GetSSLError"}, {0x002B0080, nullptr, "SetSSLOpt"}, {0x002C0080, nullptr, "SetSSLClearOpt"}, {0x002D0000, nullptr, "CreateRootCertChain"}, {0x002E0040, nullptr, "DestroyRootCertChain"}, {0x002F0082, nullptr, "RootCertChainAddCert"}, {0x00300080, nullptr, "RootCertChainAddDefaultCert"}, {0x00310080, nullptr, "RootCertChainRemoveCert"}, {0x00320084, nullptr, "OpenClientCertContext"}, {0x00330040, nullptr, "OpenDefaultClientCertContext"}, {0x00340040, nullptr, "CloseClientCertContext"}, {0x00350186, nullptr, "SetDefaultProxy"}, {0x00360000, nullptr, "ClearDNSCache"}, {0x00370080, nullptr, "SetKeepAlive"}, {0x003800C0, nullptr, "SetPostDataTypeSize"}, {0x00390000, nullptr, "Finalize"}, }; RegisterHandlers(functions); } void InstallInterfaces(SM::ServiceManager& service_manager) { std::make_shared()->InstallAsService(service_manager); } } // namespace HTTP } // namespace Service