blob: be6897dbf8ad6e46a44edf5e62256073d7ece187 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <dlfcn.h>
#include <python2.7/Python.h>
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/i18n/icu_util.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "mojo/application/application_runner_chromium.h"
#include "mojo/application/content_handler_factory.h"
#include "mojo/data_pipe_utils/data_pipe_utils.h"
#include "mojo/public/c/system/main.h"
#include "mojo/public/cpp/application/application_delegate.h"
#include "mojo/public/cpp/application/application_impl.h"
#include "mojo/public/python/src/common.h"
#include "third_party/zlib/google/zip_reader.h"
#include "url/gurl.h"
char kMojoSystem[] = "mojo_system";
char kMojoSystemImpl[] = "mojo_system_impl";
char kMojoMain[] = "MojoMain";
extern "C" {
void initmojo_system();
void initmojo_system_impl();
}
namespace services {
namespace python {
namespace content_handler {
using mojo::Application;
using mojo::ApplicationConnection;
using mojo::ApplicationDelegate;
using mojo::ContentHandlerFactory;
using mojo::InterfaceRequest;
using mojo::ScopedDataPipeConsumerHandle;
using mojo::URLResponsePtr;
using mojo::python::ScopedPyRef;
class PythonContentHandler : public ContentHandlerFactory::Delegate {
public:
PythonContentHandler(bool debug) : debug_(debug) {}
private:
// Extracts the target application into a temporary directory. This directory
// is deleted at the end of the life of the PythonContentHandler object.
std::unique_ptr<base::ScopedTempDir> ExtractApplication(
URLResponsePtr response) {
std::unique_ptr<base::ScopedTempDir> temp_dir(new base::ScopedTempDir);
CHECK(temp_dir->CreateUniqueTempDir());
zip::ZipReader reader;
const std::string input_data = CopyToString(response->body.Pass());
CHECK(reader.OpenFromString(input_data));
base::FilePath temp_dir_path = temp_dir->path();
while (reader.HasMore()) {
CHECK(reader.OpenCurrentEntryInZip());
CHECK(reader.ExtractCurrentEntryIntoDirectory(temp_dir_path));
CHECK(reader.AdvanceToNextEntry());
}
return temp_dir;
}
ScopedPyRef RunString(std::string command, PyObject* globals, int mode) {
ScopedPyRef result(PyRun_String(command.c_str(), mode, globals, nullptr));
if (!result) {
LOG(ERROR) << "Error while running command: '" << command << "'";
PyErr_Print();
}
return result;
}
bool Exec(std::string command, PyObject* globals) {
return RunString(command, globals, Py_file_input);
}
ScopedPyRef Eval(std::string command, PyObject* globals) {
return RunString(command, globals, Py_eval_input);
}
// Sets up the Python interpreter and loads mojo system modules. This method
// returns the global dictionary for the python environment that should be
// used for subsequent calls. Takes as input the path of the unpacked
// application files.
PyObject* SetupPythonEnvironment(const std::string& application_path) {
// TODO(etiennej): Build python ourselves so we don't have to rely on
// dynamically loading a system library.
dlopen("libpython2.7.so", RTLD_LAZY | RTLD_GLOBAL);
PyImport_AppendInittab(kMojoSystemImpl, &initmojo_system_impl);
PyImport_AppendInittab(kMojoSystem, &initmojo_system);
Py_Initialize();
PyObject *m, *d;
m = PyImport_AddModule("__main__");
if (!m)
return nullptr;
d = PyModule_GetDict(m);
// Enable debug information if requested.
if (debug_) {
std::string enable_logging =
"import logging;"
"logging.basicConfig();"
"logging.getLogger().setLevel(logging.DEBUG)";
if (!Exec(enable_logging, d))
return nullptr;
}
// Inject the application path into the python search path so that imports
// from the application work as expected.
std::string search_path_py_command =
"import sys; sys.path.append('" + application_path + "');";
if (!Exec(search_path_py_command, d))
return nullptr;
return d;
}
// Overridden from ContentHandlerFactory::ManagedDelegate:
void RunApplication(InterfaceRequest<Application> application_request,
URLResponsePtr response) override {
std::unique_ptr<base::ScopedTempDir> temp_dir =
ExtractApplication(response.Pass());
base::FilePath directory_path = temp_dir->path();
PyObject* d = SetupPythonEnvironment(directory_path.value());
DCHECK(d);
base::FilePath entry_path = directory_path.Append("__mojo__.py");
FILE* entry_file = base::OpenFile(entry_path, "r");
DCHECK(entry_file);
// Ensure that all created objects are destroyed before calling Py_Finalize.
{
// Load the __mojo__.py file into the interpreter. MojoMain hasn't run
// yet.
ScopedPyRef result(PyRun_File(entry_file, entry_path.value().c_str(),
Py_file_input, d, d));
if (!result) {
LOG(ERROR) << "Error while loading script";
PyErr_Print();
return;
}
// Find MojoMain.
ScopedPyRef py_function(PyMapping_GetItemString(d, kMojoMain));
if (!py_function) {
LOG(ERROR) << "Locals size: " << PyMapping_Size(d);
LOG(ERROR) << "MojoMain not found";
PyErr_Print();
return;
}
if (PyCallable_Check(py_function)) {
MojoHandle application_request_handle =
application_request.PassMessagePipe().release().value();
std::string handle_command = base::StringPrintf(
"mojo_system.Handle(%u)", application_request_handle);
ScopedPyRef py_input(Eval(handle_command, d));
if (!py_input) {
MojoClose(application_request_handle);
return;
}
ScopedPyRef py_arguments(PyTuple_New(1));
// py_input reference is stolen by py_arguments
PyTuple_SetItem(py_arguments, 0, py_input.Release());
// Run MojoMain with application_request_handle as the first and only
// argument.
ScopedPyRef py_output(PyObject_CallObject(py_function, py_arguments));
if (!py_output) {
LOG(ERROR) << "Error while executing MojoMain";
PyErr_Print();
return;
}
} else {
LOG(ERROR) << "MojoMain is not callable; it should be a function";
}
}
Py_Finalize();
}
std::string CopyToString(ScopedDataPipeConsumerHandle body) {
std::string body_str;
bool result = mojo::common::BlockingCopyToString(body.Pass(), &body_str);
DCHECK(result);
return body_str;
}
bool debug_;
DISALLOW_COPY_AND_ASSIGN(PythonContentHandler);
};
class PythonContentHandlerApp : public ApplicationDelegate {
public:
PythonContentHandlerApp()
: content_handler_(false),
debug_content_handler_(true),
content_handler_factory_(&content_handler_),
debug_content_handler_factory_(&debug_content_handler_) {}
private:
// Overridden from ApplicationDelegate:
bool ConfigureIncomingConnection(ApplicationConnection* connection) override {
if (IsDebug(connection->GetServiceProviderImpl()
.connection_context()
.connection_url))
connection->AddService(&debug_content_handler_factory_);
else
connection->AddService(&content_handler_factory_);
return true;
}
bool IsDebug(const std::string& requestedUrl) {
GURL url(requestedUrl);
if (url.has_query()) {
std::vector<std::string> query_parameters = base::SplitString(
url.query(), "&", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
return std::find(query_parameters.begin(), query_parameters.end(),
"debug=true") != query_parameters.end();
}
return false;
}
PythonContentHandler content_handler_;
PythonContentHandler debug_content_handler_;
ContentHandlerFactory content_handler_factory_;
ContentHandlerFactory debug_content_handler_factory_;
DISALLOW_COPY_AND_ASSIGN(PythonContentHandlerApp);
};
} // namespace content_handler
} // namespace python
} // namespace services
MojoResult MojoMain(MojoHandle application_request) {
mojo::ApplicationRunnerChromium runner(
new services::python::content_handler::PythonContentHandlerApp());
return runner.Run(application_request);
}