blob: 16817225b2a66ac1bd0973c34bad8ba6c388d234 [file] [log] [blame]
/**
* @copyright
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
* @endcopyright
*
* @file OperationContext.cpp
* @brief Implementation of the class OperationContext
*/
#include <apr_file_io.h>
#include "GlobalConfig.h"
#include "OperationContext.h"
#include "JNIUtil.h"
#include "JNICriticalSection.h"
#include "Prompter.h"
#include "CreateJ.h"
#include "EnumMapper.h"
#include "CommitMessage.h"
#include "svn_client.h"
#include "private/svn_wc_private.h"
#include "private/svn_dep_compat.h"
#include "svn_private_config.h"
OperationContext::OperationContext(SVN::Pool &pool)
: m_config(NULL),
m_prompter(),
m_cancelOperation(0),
m_pool(&pool),
m_jctx(NULL),
m_jcfgcb(NULL),
m_jtunnelcb(NULL)
{}
void
OperationContext::attachJavaObject(
jobject contextHolder, const char *contextClassType,
const char *contextFieldName, jfieldID * ctxFieldID)
{
JNIEnv *env = JNIUtil::getEnv();
/* Grab a global reference to the Java object embedded in the parent
Java object. */
if ((*ctxFieldID) == 0)
{
jclass clazz = env->GetObjectClass(contextHolder);
if (JNIUtil::isJavaExceptionThrown())
return;
(*ctxFieldID) = env->GetFieldID(clazz, contextFieldName, contextClassType);
if (JNIUtil::isJavaExceptionThrown() || (*ctxFieldID) == 0)
return;
env->DeleteLocalRef(clazz);
}
jobject jctx = env->GetObjectField(contextHolder, (*ctxFieldID));
if (JNIUtil::isJavaExceptionThrown())
return;
m_jctx = env->NewGlobalRef(jctx);
if (JNIUtil::isJavaExceptionThrown())
return;
env->DeleteLocalRef(jctx);
}
OperationContext::~OperationContext()
{
JNIEnv *env = JNIUtil::getEnv();
env->DeleteGlobalRef(m_jctx);
if (m_jcfgcb)
env->DeleteGlobalRef(m_jcfgcb);
if (m_jtunnelcb)
env->DeleteGlobalRef(m_jtunnelcb);
}
apr_hash_t *
OperationContext::getConfigData()
{
if(m_pool->getPool() == NULL)
{
JNIUtil::throwNullPointerException("pool is null");
}
if (m_config == NULL)
{
const char *configDir = m_configDir.c_str();
if (m_configDir.empty())
configDir = NULL;
SVN_JNI_ERR(
svn_config_get_config(&m_config, configDir, m_pool->getPool()), NULL);
notifyConfigLoad();
}
return m_config;
}
svn_auth_baton_t *
OperationContext::getAuthBaton(SVN::Pool &in_pool)
{
svn_auth_baton_t *ab;
apr_pool_t *pool = in_pool.getPool();
apr_hash_t * configData = getConfigData();
if (configData == NULL)
{
return NULL;
}
svn_config_t *config = static_cast<svn_config_t *>(apr_hash_get(configData,
SVN_CONFIG_CATEGORY_CONFIG, APR_HASH_KEY_STRING));
const bool use_native_store = GlobalConfig::useNativeCredentialsStore();
/* The whole list of registered providers */
apr_array_header_t *providers;
svn_auth_provider_object_t *provider;
if (use_native_store)
{
/* Populate the registered providers with the platform-specific providers */
SVN_JNI_ERR(
svn_auth_get_platform_specific_client_providers(
&providers, config, pool),
NULL);
/* Use the prompter (if available) to prompt for password and cert
* caching. */
svn_auth_plaintext_prompt_func_t plaintext_prompt_func;
void *plaintext_prompt_baton;
svn_auth_plaintext_passphrase_prompt_func_t plaintext_passphrase_prompt_func;
void *plaintext_passphrase_prompt_baton;
if (m_prompter.get())
{
plaintext_prompt_func = Prompter::plaintext_prompt;
plaintext_prompt_baton = m_prompter.get();
plaintext_passphrase_prompt_func = Prompter::plaintext_passphrase_prompt;
plaintext_passphrase_prompt_baton = m_prompter.get();
}
else
{
plaintext_prompt_func = NULL;
plaintext_prompt_baton = NULL;
plaintext_passphrase_prompt_func = NULL;
plaintext_passphrase_prompt_baton = NULL;
}
/* The main disk-caching auth providers, for both
* 'username/password' creds and 'username' creds. */
svn_auth_get_simple_provider2(&provider, plaintext_prompt_func,
plaintext_prompt_baton, pool);
APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
svn_auth_get_username_provider(&provider, pool);
APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
svn_auth_get_ssl_server_trust_file_provider(&provider, pool);
APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
svn_auth_get_ssl_client_cert_file_provider(&provider, pool);
APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
svn_auth_get_ssl_client_cert_pw_file_provider2(
&provider,
plaintext_passphrase_prompt_func, plaintext_passphrase_prompt_baton,
pool);
APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
}
else
{
// Not using hte native credentials store, start with an empty
// providers array.
providers = apr_array_make(pool, 0, sizeof(svn_auth_provider_object_t *));
}
if (m_prompter.get())
{
/* Two basic prompt providers: username/password, and just username.*/
provider = m_prompter->get_provider_simple(in_pool);
APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
provider = m_prompter->get_provider_username(in_pool);
APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
/* Three ssl prompt providers, for server-certs, client-certs,
* and client-cert-passphrases. */
provider = m_prompter->get_provider_server_ssl_trust(in_pool);
APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
provider = m_prompter->get_provider_client_ssl(in_pool);
APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
provider = m_prompter->get_provider_client_ssl_password(in_pool);
APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
}
/* Build an authentication baton to give to libsvn_client. */
svn_auth_open(&ab, providers, pool);
/* Place any default --username or --password credentials into the
* auth_baton's run-time parameter hash. ### Same with --no-auth-cache? */
if (!m_userName.empty())
svn_auth_set_parameter(ab, SVN_AUTH_PARAM_DEFAULT_USERNAME,
apr_pstrdup(in_pool.getPool(), m_userName.c_str()));
if (!m_passWord.empty())
svn_auth_set_parameter(ab, SVN_AUTH_PARAM_DEFAULT_PASSWORD,
apr_pstrdup(in_pool.getPool(), m_passWord.c_str()));
/* Store where to retrieve authentication data? */
if (!m_configDir.empty())
svn_auth_set_parameter(ab, SVN_AUTH_PARAM_CONFIG_DIR,
apr_pstrdup(in_pool.getPool(), m_configDir.c_str()));
return ab;
}
jobject OperationContext::getSelf() const
{
return m_jctx;
}
void
OperationContext::username(const char *pi_username)
{
m_userName = (pi_username == NULL ? "" : pi_username);
}
void
OperationContext::password(const char *pi_password)
{
m_passWord = (pi_password == NULL ? "" : pi_password);
}
void
OperationContext::setPrompt(Prompter::UniquePtr prompter)
{
m_prompter = JavaHL::cxx::move(prompter);
}
void
OperationContext::setConfigDirectory(const char *configDir)
{
// A change to the config directory may necessitate creation of
// the config templates.
SVN::Pool requestPool;
SVN_JNI_ERR(svn_config_ensure(configDir, requestPool.getPool()), );
m_configDir = (configDir == NULL ? "" : configDir);
m_config = NULL;
}
const char *
OperationContext::getConfigDirectory() const
{
return (m_configDir.empty() ? NULL : m_configDir.c_str());
}
void OperationContext::setConfigEventHandler(jobject jcfgcb)
{
JNIEnv *env = JNIUtil::getEnv();
if (jcfgcb)
{
jcfgcb = env->NewGlobalRef(jcfgcb);
if (JNIUtil::isJavaExceptionThrown())
return;
}
if (m_jcfgcb)
env->DeleteGlobalRef(m_jcfgcb);
m_jcfgcb = jcfgcb;
}
jobject OperationContext::getConfigEventHandler() const
{
return m_jcfgcb;
}
const char *
OperationContext::getUsername() const
{
return (m_userName.empty() ? NULL : m_userName.c_str());
}
const char *
OperationContext::getPassword() const
{
return (m_passWord.empty() ? NULL : m_passWord.c_str());
}
Prompter::UniquePtr OperationContext::clonePrompter() const
{
if (m_prompter.get())
return m_prompter->clone();
return Prompter::UniquePtr();
}
void OperationContext::setTunnelCallback(jobject jtunnelcb)
{
JNIEnv *env = JNIUtil::getEnv();
if (jtunnelcb)
{
jtunnelcb = env->NewGlobalRef(jtunnelcb);
if (JNIUtil::isJavaExceptionThrown())
return;
}
if (m_jtunnelcb)
env->DeleteGlobalRef(m_jtunnelcb);
m_jtunnelcb = jtunnelcb;
}
jobject OperationContext::getTunnelCallback() const
{
return m_jtunnelcb;
}
void
OperationContext::cancelOperation()
{
svn_atomic_set(&m_cancelOperation, 1);
}
void
OperationContext::resetCancelRequest()
{
svn_atomic_set(&m_cancelOperation, 0);
}
bool
OperationContext::isCancelledOperation()
{
return bool(svn_atomic_read(&m_cancelOperation));
}
svn_error_t *
OperationContext::checkCancel(void *cancelBaton)
{
OperationContext *that = static_cast<OperationContext *>(cancelBaton);
if (that->isCancelledOperation())
return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Operation cancelled"));
else if (JNIUtil::isJavaExceptionThrown())
return svn_error_create(SVN_ERR_CANCELLED, JNIUtil::wrapJavaException(),
_("Operation cancelled"));
else
return SVN_NO_ERROR;
}
void
OperationContext::progress(apr_off_t progressVal, apr_off_t total, void *baton,
apr_pool_t *pool)
{
jobject jctx = (jobject) baton;
if (!jctx)
return;
JNIEnv *env = JNIUtil::getEnv();
// Create a local frame for our references
env->PushLocalFrame(LOCAL_FRAME_SIZE);
if (JNIUtil::isJavaExceptionThrown())
return;
static jmethodID mid = 0;
if (mid == 0)
{
jclass clazz = env->GetObjectClass(jctx);
if (JNIUtil::isJavaExceptionThrown())
POP_AND_RETURN_NOTHING();
mid = env->GetMethodID(clazz, "onProgress",
"(" JAVAHL_ARG("/ProgressEvent;") ")V");
if (JNIUtil::isJavaExceptionThrown() || mid == 0)
POP_AND_RETURN_NOTHING();
}
static jmethodID midCT = 0;
jclass clazz = env->FindClass(JAVAHL_CLASS("/ProgressEvent"));
if (JNIUtil::isJavaExceptionThrown())
POP_AND_RETURN_NOTHING();
if (midCT == 0)
{
midCT = env->GetMethodID(clazz, "<init>", "(JJ)V");
if (JNIUtil::isJavaExceptionThrown() || midCT == 0)
POP_AND_RETURN_NOTHING();
}
// Call the Java method.
jobject jevent = env->NewObject(clazz, midCT, (jlong) progressVal,
(jlong) total);
if (JNIUtil::isJavaExceptionThrown())
POP_AND_RETURN_NOTHING();
env->CallVoidMethod(jctx, mid, jevent);
POP_AND_RETURN_NOTHING();
}
const char *
OperationContext::getClientName() const
{
return "javahl";
}
svn_error_t *
OperationContext::clientName(void *baton, const char **name, apr_pool_t *pool)
{
OperationContext *that = (OperationContext *) baton;
*name = that->getClientName();
return SVN_NO_ERROR;
}
void
OperationContext::notifyConfigLoad()
{
if (!m_jcfgcb)
return;
JNIEnv *env = JNIUtil::getEnv();
static jmethodID onload_mid = 0;
if (0 == onload_mid)
{
jclass cls = env->FindClass(JAVAHL_CLASS("/callback/ConfigEvent"));
if (JNIUtil::isJavaExceptionThrown())
return;
onload_mid = env->GetMethodID(cls, "onLoad",
"(" JAVAHL_ARG("/ISVNConfig;") ")V");
if (JNIUtil::isJavaExceptionThrown())
return;
}
jclass cfg_cls = env->FindClass(JAVAHL_CLASS("/util/ConfigImpl"));
if (JNIUtil::isJavaExceptionThrown())
return;
static jmethodID ctor_mid = 0;
if (0 == ctor_mid)
{
ctor_mid = env->GetMethodID(cfg_cls, "<init>", "(J)V");
if (JNIUtil::isJavaExceptionThrown())
return;
}
static jmethodID dispose_mid = 0;
if (0 == dispose_mid)
{
dispose_mid = env->GetMethodID(cfg_cls, "dispose", "()V");
if (JNIUtil::isJavaExceptionThrown())
return;
}
jobject jcbimpl = env->NewObject(cfg_cls, ctor_mid,
reinterpret_cast<jlong>(this));
if (JNIUtil::isJavaExceptionThrown())
return;
env->CallVoidMethod(m_jcfgcb, onload_mid, jcbimpl);
if (JNIUtil::isJavaExceptionThrown())
return;
env->CallVoidMethod(jcbimpl, dispose_mid);
env->DeleteLocalRef(jcbimpl);
}
namespace {
class TunnelContext
{
public:
explicit TunnelContext(apr_pool_t *pool)
: request_in(NULL),
request_out(NULL),
response_in(NULL),
response_out(NULL),
jrequest(NULL),
jresponse(NULL),
jclosecb(NULL)
{
status = apr_file_pipe_create_ex(&request_in, &request_out,
APR_FULL_BLOCK, pool);
if (!status)
status = apr_file_pipe_create_ex(&response_in, &response_out,
APR_FULL_BLOCK, pool);
}
~TunnelContext()
{
apr_file_close(request_out);
apr_file_close(response_in);
}
apr_file_t *request_in;
apr_file_t *request_out;
apr_file_t *response_in;
apr_file_t *response_out;
apr_status_t status;
jobject jrequest;
jobject jresponse;
jobject jclosecb;
};
jobject create_Channel(const char *class_name, JNIEnv *env, apr_file_t *fd)
{
jclass cls = env->FindClass(class_name);
if (JNIUtil::isJavaExceptionThrown())
return NULL;
jmethodID ctor = env->GetMethodID(cls, "<init>", "(J)V");
if (JNIUtil::isJavaExceptionThrown())
return NULL;
jobject channel = env->NewObject(cls, ctor, reinterpret_cast<jlong>(fd));
if (JNIUtil::isJavaExceptionThrown())
return NULL;
return env->NewGlobalRef(channel);
}
jobject create_RequestChannel(JNIEnv *env, apr_file_t *fd)
{
return create_Channel(JAVAHL_CLASS("/util/RequestChannel"), env, fd);
}
jobject create_ResponseChannel(JNIEnv *env, apr_file_t *fd)
{
return create_Channel(JAVAHL_CLASS("/util/ResponseChannel"), env, fd);
}
void close_TunnelChannel(JNIEnv* env, jobject channel)
{
// Usually after this function, the memory will be freed behind
// 'TunnelChannel.nativeChannel'. Ask Java side to forget it. This is the
// only way to avoid a JVM crash when 'TunnelAgent' tries to read/write,
// not knowing that 'TunnelChannel' is already closed in native side.
static jmethodID mid = 0;
if (0 == mid)
{
jclass cls;
SVN_JNI_CATCH_VOID(
cls = env->FindClass(JAVAHL_CLASS("/util/TunnelChannel")));
SVN_JNI_CATCH_VOID(mid = env->GetMethodID(cls, "syncClose", "()V"));
}
SVN_JNI_CATCH_VOID(env->CallVoidMethod(channel, mid));
env->DeleteGlobalRef(channel);
}
} // anonymous namespace
svn_boolean_t
OperationContext::checkTunnel(void *tunnel_baton, const char *tunnel_name)
{
JNIEnv *env = JNIUtil::getEnv();
jstring jtunnel_name = JNIUtil::makeJString(tunnel_name);
if (JNIUtil::isJavaExceptionThrown())
return false;
static jmethodID mid = 0;
if (0 == mid)
{
jclass cls = env->FindClass(JAVAHL_CLASS("/callback/TunnelAgent"));
if (JNIUtil::isJavaExceptionThrown())
return false;
mid = env->GetMethodID(cls, "checkTunnel",
"(Ljava/lang/String;)Z");
if (JNIUtil::isJavaExceptionThrown())
return false;
}
jobject jtunnelcb = jobject(tunnel_baton);
jboolean check = env->CallBooleanMethod(jtunnelcb, mid, jtunnel_name);
if (JNIUtil::isJavaExceptionThrown())
return false;
return svn_boolean_t(check);
}
svn_error_t *
OperationContext::openTunnel(svn_stream_t **request, svn_stream_t **response,
svn_ra_close_tunnel_func_t *close_func,
void **close_baton,
void *tunnel_baton,
const char *tunnel_name, const char *user,
const char *hostname, int port,
svn_cancel_func_t cancel_func, void *cancel_baton,
apr_pool_t *pool)
{
TunnelContext *tc = new TunnelContext(pool);
if (tc->status)
{
delete tc;
return svn_error_trace(
svn_error_wrap_apr(tc->status, _("Could not open tunnel streams")));
}
*close_func = closeTunnel;
*close_baton = tc;
*request = svn_stream_from_aprfile2(tc->request_out, FALSE, pool);
*response = svn_stream_from_aprfile2(tc->response_in, FALSE, pool);
JNIEnv *env = JNIUtil::getEnv();
tc->jrequest = create_RequestChannel(env, tc->request_in);
SVN_JNI_CATCH(, SVN_ERR_BASE);
tc->jresponse = create_ResponseChannel(env, tc->response_out);
SVN_JNI_CATCH(, SVN_ERR_BASE);
jstring jtunnel_name = JNIUtil::makeJString(tunnel_name);
SVN_JNI_CATCH(, SVN_ERR_BASE);
jstring juser = JNIUtil::makeJString(user);
SVN_JNI_CATCH(, SVN_ERR_BASE);
jstring jhostname = JNIUtil::makeJString(hostname);
SVN_JNI_CATCH(, SVN_ERR_BASE);
static jmethodID mid = 0;
if (0 == mid)
{
jclass cls = env->FindClass(JAVAHL_CLASS("/callback/TunnelAgent"));
SVN_JNI_CATCH(, SVN_ERR_BASE);
SVN_JNI_CATCH(
mid = env->GetMethodID(
cls, "openTunnel",
"(Ljava/nio/channels/ReadableByteChannel;"
"Ljava/nio/channels/WritableByteChannel;"
"Ljava/lang/String;"
"Ljava/lang/String;"
"Ljava/lang/String;I)"
JAVAHL_ARG("/callback/TunnelAgent$CloseTunnelCallback;")),
SVN_ERR_BASE);
}
jobject jtunnelcb = jobject(tunnel_baton);
tc->jclosecb = env->CallObjectMethod(
jtunnelcb, mid, tc->jrequest, tc->jresponse,
jtunnel_name, juser, jhostname, jint(port));
svn_error_t* openTunnelError = JNIUtil::checkJavaException(SVN_ERR_BASE);
if (SVN_NO_ERROR != openTunnelError)
{
// OperationContext::closeTunnel() will never be called, clean up here.
// This also prevents a JVM native crash, see comment in
// close_TunnelChannel().
*close_baton = 0;
tc->jclosecb = 0;
OperationContext::closeTunnel(tc, 0);
SVN_ERR(openTunnelError);
}
if (tc->jclosecb)
{
tc->jclosecb = env->NewGlobalRef(tc->jclosecb);
SVN_JNI_CATCH(, SVN_ERR_BASE);
}
return SVN_NO_ERROR;
}
void callCloseTunnelCallback(JNIEnv* env, jobject jclosecb)
{
static jmethodID mid = 0;
if (0 == mid)
{
jclass cls;
SVN_JNI_CATCH_VOID(
cls= env->FindClass(
JAVAHL_CLASS("/callback/TunnelAgent$CloseTunnelCallback")));
SVN_JNI_CATCH_VOID(mid = env->GetMethodID(cls, "closeTunnel", "()V"));
}
SVN_JNI_CATCH_VOID(env->CallVoidMethod(jclosecb, mid));
env->DeleteGlobalRef(jclosecb);
}
void
OperationContext::closeTunnel(void *tunnel_context, void *)
{
TunnelContext* tc = static_cast<TunnelContext*>(tunnel_context);
jobject jrequest = tc->jrequest;
jobject jresponse = tc->jresponse;
jobject jclosecb = tc->jclosecb;
// Note that this closes other end of the pipe, which cancels and
// prevents further read/writes in 'TunnelAgent'
delete tc;
JNIEnv *env = JNIUtil::getEnv();
// Cleanup is important, otherwise TunnelAgent may crash when
// accessing freed native objects. For this reason, cleanup is done
// despite a pending exception. If more exceptions occur, they are
// stashed as well in order to complete all cleanup steps.
StashException ex(env);
if (jclosecb)
callCloseTunnelCallback(env, jclosecb);
if (jrequest)
{
ex.stashException();
close_TunnelChannel(env, jrequest);
}
if (jresponse)
{
ex.stashException();
close_TunnelChannel(env, jresponse);
}
}