mariadb/storage/connect/jmgoconn.cpp
Mikhail Chalov 2ff01e763e Fix insecure use of strcpy, strcat and sprintf in Connect
Old style C functions `strcpy()`, `strcat()` and `sprintf()` are vulnerable to
security issues due to lacking memory boundary checks. Replace these in the
Connect storage engine with safe new and/or custom functions such as
`snprintf()` `safe_strcpy()` and `safe_strcat()`.

With this change FlawFinder and other static security analyzers report 287
fewer findings.

All new code of the whole pull request, including one or several files that are
either new files or modified ones, are contributed under the BSD-new license. I
am contributing on behalf of my employer Amazon Web Services, Inc.
2023-05-12 15:37:00 +01:00

883 lines
24 KiB
C++

/************ JMgoConn C++ Functions Source Code File (.CPP) ***********/
/* Name: JMgoConn.CPP Version 1.2 */
/* */
/* (C) Copyright to the author Olivier BERTRAND 2017 - 2021 */
/* */
/* This file contains the MongoDB Java connection classes functions. */
/***********************************************************************/
/***********************************************************************/
/* Include relevant MariaDB header file. */
/***********************************************************************/
#include <my_global.h>
/***********************************************************************/
/* Required objects includes. */
/***********************************************************************/
#include "global.h"
#include "plgdbsem.h"
#include "colblk.h"
#include "xobject.h"
#include "xtable.h"
#include "filter.h"
#include "jmgoconn.h"
#define nullptr 0
bool IsArray(PSZ s);
bool MakeSelector(PGLOBAL g, PFIL fp, PSTRG s);
/* --------------------------- Class JNCOL --------------------------- */
/***********************************************************************/
/* Add a column in the column list. */
/***********************************************************************/
void JNCOL::AddCol(PGLOBAL g, PCOL colp, PSZ jp)
{
char *p;
PJKC kp, kcp;
if ((p = strchr(jp, '.'))) {
PJNCOL icp;
*p++ = 0;
for (kp = Klist; kp; kp = kp->Next)
if (kp->Jncolp && ((kp->Key && !strcmp(jp, kp->Key))
|| (!kp->Key && IsArray(jp) && kp->N == atoi(jp))))
break;
if (!kp) {
icp = new(g) JNCOL();
kcp = (PJKC)PlugSubAlloc(g, NULL, sizeof(JKCOL));
kcp->Next = NULL;
kcp->Jncolp = icp;
kcp->Colp = NULL;
kcp->Array = IsArray(jp);
if (kcp->Array) {
kcp->Key = NULL;
kcp->N = atoi(jp);
} else {
kcp->Key = PlugDup(g, jp);
kcp->N = 0;
} // endif Array
if (Klist) {
for (kp = Klist; kp->Next; kp = kp->Next);
kp->Next = kcp;
} else
Klist = kcp;
} else
icp = kp->Jncolp;
*(p - 1) = '.';
icp->AddCol(g, colp, p);
} else {
kcp = (PJKC)PlugSubAlloc(g, NULL, sizeof(JKCOL));
kcp->Next = NULL;
kcp->Jncolp = NULL;
kcp->Colp = colp;
kcp->Array = IsArray(jp);
if (kcp->Array) {
kcp->Key = NULL;
kcp->N = atoi(jp);
} else {
kcp->Key = jp;
kcp->N = 0;
} // endif Array
if (Klist) {
for (kp = Klist; kp->Next; kp = kp->Next);
kp->Next = kcp;
} else
Klist = kcp;
} // endif jp
} // end of AddCol
/***********************************************************************/
/* JMgoConn construction/destruction. */
/***********************************************************************/
JMgoConn::JMgoConn(PGLOBAL g, PCSZ collname, PCSZ wrapper)
: JAVAConn(g, wrapper)
{
CollName = collname;
readid = fetchid = getdocid = objfldid = fcollid = acollid =
mkdocid = docaddid = mkarid = araddid = insertid = updateid =
deleteid = gcollid = countid = rewindid = mkbsonid = nullptr;
DiscFunc = "MongoDisconnect";
Fpc = NULL;
m_Fetch = 0;
m_Ncol = 0;
m_Version = 0;
} // end of JMgoConn
/***********************************************************************/
/* AddJars: add some jar file to the Class path. */
/***********************************************************************/
void JMgoConn::AddJars(PSTRG jpop, char sep)
{
#if defined(DEVELOPMENT)
if (m_Version == 2) {
jpop->Append(sep);
// jpop->Append("C:/Eclipse/workspace/MongoWrap2/bin");
// jpop->Append(sep);
jpop->Append("C:/mongo-java-driver/mongo-java-driver-2.13.3.jar");
} else {
jpop->Append(sep);
// jpop->Append("C:/Eclipse/workspace/MongoWrap3/bin");
// jpop->Append(sep);
// jpop->Append("C:/Program Files/MariaDB 10.1/lib/plugin/JavaWrappers.jar");
// jpop->Append(sep);
jpop->Append("C:/mongo-java-driver/mongo-java-driver-3.4.2.jar");
} // endif m_Version
#endif // DEVELOPMENT
} // end of AddJars
/***********************************************************************/
/* Connect: connect to a data source. */
/***********************************************************************/
bool JMgoConn::Connect(PJPARM sop)
{
bool err = false;
jint rc;
jboolean brc;
jstring cln;
PGLOBAL& g = m_G;
m_Version = sop->Version;
/*******************************************************************/
/* Create or attach a JVM. */
/*******************************************************************/
if (Open(g))
return true;
/*******************************************************************/
/* Connect to MongoDB. */
/*******************************************************************/
jmethodID cid = nullptr;
if (gmID(g, cid, "MongoConnect", "([Ljava/lang/String;)I"))
return true;
// Build the java string array
jobjectArray parms = env->NewObjectArray(4, // constructs java array of 4
env->FindClass("java/lang/String"), NULL); // Strings
//m_Scrollable = sop->Scrollable;
//m_RowsetSize = sop->Fsize;
// change some elements
if (sop->Driver)
env->SetObjectArrayElement(parms, 0, env->NewStringUTF(sop->Url));
if (sop->Url)
env->SetObjectArrayElement(parms, 1, env->NewStringUTF(sop->Driver));
if (sop->User)
env->SetObjectArrayElement(parms, 2, env->NewStringUTF(sop->User));
if (sop->Pwd)
env->SetObjectArrayElement(parms, 3, env->NewStringUTF(sop->Pwd));
// call method
rc = env->CallIntMethod(job, cid, parms);
err = Check(rc);
env->DeleteLocalRef(parms); // Not used anymore
if (err) {
snprintf(g->Message, sizeof(g->Message), "Connecting: %s rc=%d", Msg, (int)rc);
return true;
} // endif Msg
/*********************************************************************/
/* Get the collection. */
/*********************************************************************/
if (gmID(g, gcollid, "GetCollection", "(Ljava/lang/String;)Z"))
return true;
cln = env->NewStringUTF(CollName);
brc = env->CallBooleanMethod(job, gcollid, cln);
env->DeleteLocalRef(cln);
if (Check(brc ? -1 : 0)) {
snprintf(g->Message, sizeof(g->Message), "GetCollection: %s", Msg);
return true;
} // endif Msg
m_Connected = true;
return false;
} // end of Connect
/***********************************************************************/
/* CollSize: returns the number of documents in the collection. */
/***********************************************************************/
int JMgoConn::CollSize(PGLOBAL g)
{
if (!gmID(g, countid, "GetCollSize", "()J")) {
jlong card = env->CallLongMethod(job, countid);
return (int)card;
} else
return 2; // Make MariaDB happy
} // end of CollSize
/***********************************************************************/
/* OpenDB: Data Base open routine for MONGO access method. */
/***********************************************************************/
bool JMgoConn::MakeCursor(PGLOBAL g, PTDB tdbp, PCSZ options,
PCSZ filter, bool pipe)
{
const char *p;
bool id, b = false, all = false;
uint len;
PCOL cp;
PSZ jp;
PCSZ op = NULL, sf = NULL, Options = options;
PSTRG s = NULL;
PFIL filp = tdbp->GetFilter();
if (Options && !stricmp(Options, "all")) {
Options = NULL;
all = true;
} else
id = (tdbp->GetMode() == MODE_UPDATE || tdbp->GetMode() == MODE_DELETE);
for (cp = tdbp->GetColumns(); cp && !all; cp = cp->GetNext())
if (cp->GetFmt() && !strcmp(cp->GetFmt(), "*") && (!Options || pipe))
all = true;
else if (!id)
id = !strcmp(cp->GetJpath(g, false), "_id");
if (pipe && Options) {
if (trace(1))
htrc("Pipeline: %s\n", Options);
p = strrchr(Options, ']');
if (!p) {
snprintf(g->Message, sizeof(g->Message), "Missing ] in pipeline");
return true;
} else
*(char*)p = 0;
s = new(g) STRING(g, 1023, (PSZ)Options);
if (filp) {
s->Append(",{\"$match\":");
if (MakeSelector(g, filp, s)) {
snprintf(g->Message, sizeof(g->Message), "Failed making selector");
return true;
} else
s->Append('}');
tdbp->SetFilter(NULL); // Not needed anymore
} // endif To_Filter
if (!all && tdbp->GetColumns()) {
// Project list
len = s->GetLength();
s->Append(",{\"$project\":{\"");
if (!id)
s->Append("_id\":0,\"");
for (PCOL cp = tdbp->GetColumns(); cp; cp = cp->GetNext()) {
if (b)
s->Append(",\"");
else
b = true;
if ((jp = cp->GetJpath(g, true)))
s->Append(jp);
else {
s->Truncate(len);
goto nop;
} // endif Jpath
s->Append("\":1");
} // endfor cp
s->Append("}}");
} // endif all
nop:
s->Append("]}");
s->Resize(s->GetLength() + 1);
*(char*)p = ']'; // Restore Colist for discovery
p = s->GetStr();
if (trace(33))
htrc("New Pipeline: %s\n", p);
return AggregateCollection(p);
} else {
if (filter || filp) {
if (trace(1)) {
if (filter)
htrc("Filter: %s\n", filter);
if (filp) {
char buf[512];
filp->Prints(g, buf, 511);
htrc("To_Filter: %s\n", buf);
} // endif To_Filter
} // endif trace
s = new(g) STRING(g, 1023, (PSZ)filter);
len = s->GetLength();
if (filp) {
if (filter)
s->Append(',');
if (MakeSelector(g, filp, s)) {
snprintf(g->Message, sizeof(g->Message), "Failed making selector");
return true;
} // endif Selector
tdbp->SetFilter(NULL); // Not needed anymore
} // endif To_Filter
if (trace(33))
htrc("selector: %s\n", s->GetStr());
s->Resize(s->GetLength() + 1);
sf = PlugDup(g, s->GetStr());
} // endif Filter
if (!all) {
if (Options && *Options) {
if (trace(1))
htrc("options=%s\n", Options);
op = Options;
} else if (tdbp->GetColumns()) {
// Projection list
if (s)
s->Set("{\"");
else
s = new(g) STRING(g, 511, "{\"");
if (!id)
s->Append("_id\":0,\"");
for (PCOL cp = tdbp->GetColumns(); cp; cp = cp->GetNext()) {
if (b)
s->Append(",\"");
else
b = true;
if ((jp = cp->GetJpath(g, true)))
s->Append(jp);
else {
// Can this happen?
htrc("Fail getting projection path of %s\n", cp->GetName());
goto nope;
} // endif Jpath
s->Append("\":1");
} // endfor cp
s->Append("}");
s->Resize(s->GetLength() + 1);
op = s->GetStr();
} else {
// count(*) ?
op = "{\"_id\":1}";
} // endif Options
} // endif all
nope:
return FindCollection(sf, op);
} // endif Pipe
} // end of MakeCursor
/***********************************************************************/
/* Find a collection and make cursor. */
/***********************************************************************/
bool JMgoConn::FindCollection(PCSZ query, PCSZ proj)
{
bool rc = true;
jboolean brc;
jstring qry = nullptr, prj = nullptr;
PGLOBAL& g = m_G;
// Get the methods used to execute a query and get the result
if (!gmID(g, fcollid, "FindColl", "(Ljava/lang/String;Ljava/lang/String;)Z")) {
if (query)
qry = env->NewStringUTF(query);
if (proj)
prj = env->NewStringUTF(proj);
brc = env->CallBooleanMethod(job, fcollid, qry, prj);
if (!Check(brc ? -1 : 0)) {
rc = false;
} else
snprintf(g->Message, sizeof(g->Message), "FindColl: %s", Msg);
if (query)
env->DeleteLocalRef(qry);
if (proj)
env->DeleteLocalRef(prj);
} // endif xqid
return rc;
} // end of FindCollection
/***********************************************************************/
/* Find a collection and make cursor. */
/***********************************************************************/
bool JMgoConn::AggregateCollection(PCSZ pipeline)
{
bool rc = true;
jboolean brc;
jstring pip = nullptr;
PGLOBAL& g = m_G;
// Get the methods used to execute a query and get the result
if (!gmID(g, acollid, "AggregateColl", "(Ljava/lang/String;)Z")) {
pip = env->NewStringUTF(pipeline);
brc = env->CallBooleanMethod(job, acollid, pip);
if (!Check(brc ? -1 : 0)) {
rc = false;
} else
snprintf(g->Message, sizeof(g->Message), "AggregateColl: %s", Msg);
env->DeleteLocalRef(pip);
} // endif acollid
return rc;
} // end of AggregateCollection
/***********************************************************************/
/* Fetch next row. */
/***********************************************************************/
int JMgoConn::Fetch(int pos)
{
jint rc = JNI_ERR;
PGLOBAL& g = m_G;
//if (m_Full) // Result set has one row
// return 1;
//if (pos) {
// if (!m_Scrollable) {
// strcpy(g->Message, "Cannot fetch(pos) if FORWARD ONLY");
// return rc;
// } else if (gmID(m_G, fetchid, "Fetch", "(I)Z"))
// return rc;
// if (env->CallBooleanMethod(job, fetchid, pos))
// rc = m_Rows;
//} else {
if (gmID(g, readid, "ReadNext", "()I"))
return (int)rc;
rc = env->CallIntMethod(job, readid);
if (!Check(rc)) {
//if (rc == 0)
// m_Full = (m_Fetch == 1);
//else
// m_Fetch++;
m_Ncol = (int)rc;
rc = MY_MIN(rc, 1);
m_Rows += rc;
} else
snprintf(g->Message, sizeof(g->Message), "Fetch: %s", Msg);
//} // endif pos
return rc;
} // end of Fetch
/***********************************************************************/
/* Get the Json string of the current document. */
/***********************************************************************/
PSZ JMgoConn::GetDocument(void)
{
PGLOBAL& g = m_G;
PSZ doc = NULL;
jstring jdc;
if (!gmID(g, getdocid, "GetDoc", "()Ljava/lang/String;")) {
jdc = (jstring)env->CallObjectMethod(job, getdocid);
if (jdc)
doc = (PSZ)GetUTFString(jdc);
} // endif getdocid
return doc;
} // end of GetDocument
/***********************************************************************/
/* Group columns for inserting or updating. */
/***********************************************************************/
void JMgoConn::MakeColumnGroups(PGLOBAL g, PTDB tdbp)
{
Fpc = new(g) JNCOL();
for (PCOL colp = tdbp->GetColumns(); colp; colp = colp->GetNext())
if (!colp->IsSpecial())
Fpc->AddCol(g, colp, colp->GetJpath(g, false));
} // end of MakeColumnGroups
/***********************************************************************/
/* Get additional method ID. */
/***********************************************************************/
bool JMgoConn::GetMethodId(PGLOBAL g, MODE mode)
{
if (mode == MODE_UPDATE) {
if (gmID(g, mkdocid, "MakeDocument", "()Ljava/lang/Object;"))
return true;
if (gmID(g, docaddid, "DocAdd",
"(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/Object;I)Z"))
return true;
if (gmID(g, updateid, "CollUpdate", "(Ljava/lang/Object;)J"))
return true;
} else if (mode == MODE_INSERT) {
if (gmID(g, mkdocid, "MakeDocument", "()Ljava/lang/Object;"))
return true;
if (gmID(g, mkbsonid, "MakeBson",
"(Ljava/lang/String;I)Ljava/lang/Object;"))
return true;
if (gmID(g, docaddid, "DocAdd",
"(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/Object;I)Z"))
return true;
if (gmID(g, mkarid, "MakeArray", "()Ljava/lang/Object;"))
return true;
if (gmID(g, araddid, "ArrayAdd",
"(Ljava/lang/Object;ILjava/lang/Object;I)Z"))
return true;
if (gmID(g, insertid, "CollInsert", "(Ljava/lang/Object;)Z"))
return true;
} else if (mode == MODE_DELETE)
if (gmID(g, deleteid, "CollDelete", "(Z)J"))
return true;
return gmID(g, rewindid, "Rewind", "()Z");
} // end of GetMethodId
/***********************************************************************/
/* MakeObject. */
/***********************************************************************/
jobject JMgoConn::MakeObject(PGLOBAL g, PCOL colp, bool&error )
{
jclass cls;
jmethodID cns = nullptr; // Constructor
jobject val = nullptr;
PVAL valp = colp->GetValue();
error = false;
if (valp->IsNull())
return NULL;
try {
switch (valp->GetType()) {
case TYPE_STRING:
val = env->NewStringUTF(valp->GetCharValue());
break;
case TYPE_INT:
case TYPE_SHORT:
cls = env->FindClass("java/lang/Integer");
cns = env->GetMethodID(cls, "<init>", "(I)V");
val = env->NewObject(cls, cns, valp->GetIntValue());
break;
case TYPE_TINY:
cls = env->FindClass("java/lang/Boolean");
cns = env->GetMethodID(cls, "<init>", "(Z)V");
val = env->NewObject(cls, cns, (valp->GetIntValue() != 0));
break;
case TYPE_BIGINT:
cls = env->FindClass("java/lang/Long");
cns = env->GetMethodID(cls, "<init>", "(J)V");
val = env->NewObject(cls, cns, valp->GetBigintValue());
break;
case TYPE_DOUBLE:
cls = env->FindClass("java/lang/Double");
cns = env->GetMethodID(cls, "<init>", "(D)V");
val = env->NewObject(cls, cns, valp->GetFloatValue());
break;
default:
snprintf(g->Message, sizeof(g->Message), "Cannot make object from %d type", valp->GetType());
error = true;
break;
} // endswitch Type
} catch (...) {
snprintf(g->Message, sizeof(g->Message), "Cannot make object from %s value", colp->GetName());
error = true;
} // end try/catch
return val;
} // end of MakeObject
/***********************************************************************/
/* Stringify. */
/***********************************************************************/
bool JMgoConn::Stringify(PCOL colp)
{
bool b = false;
if (colp)
b = (colp->Stringify() && colp->GetResultType() == TYPE_STRING);
return b;
} // end of Stringify
/***********************************************************************/
/* MakeDoc. */
/***********************************************************************/
jobject JMgoConn::MakeDoc(PGLOBAL g, PJNCOL jcp)
{
int j;
bool b, error = false;
jobject parent, child, val;
jstring jkey;
PJKC kp = jcp->Klist;
if (kp->Array)
parent = env->CallObjectMethod(job, mkarid);
else
parent = env->CallObjectMethod(job, mkdocid);
for (j = 0; kp; j = 0, kp = kp->Next) {
if (Stringify(kp->Colp)) {
switch (*kp->Colp->GetCharValue()) {
case '{': j = 1; break;
case '[': j = 2; break;
default: break;
} // endswitch
b = (!kp->Key || !*kp->Key || *kp->Key == '*');
} else
b = false;
if (kp->Jncolp) {
if (!(child = MakeDoc(g, kp->Jncolp)))
return NULL;
if (!kp->Array) {
jkey = env->NewStringUTF(kp->Key);
if (env->CallBooleanMethod(job, docaddid, parent, jkey, child, j))
return NULL;
env->DeleteLocalRef(jkey);
} else
if (env->CallBooleanMethod(job, araddid, parent, kp->N, child, j))
return NULL;
env->DeleteLocalRef(child);
} else {
if (!(val = MakeObject(g, kp->Colp, error))) {
if (error)
return NULL;
} else if (!kp->Array) {
if (!b) {
jkey = env->NewStringUTF(kp->Key);
if (env->CallBooleanMethod(job, docaddid, parent, jkey, val, j))
return NULL;
env->DeleteLocalRef(jkey);
} else {
env->DeleteLocalRef(parent);
parent = env->CallObjectMethod(job, mkbsonid, val, j);
} // endif b
} else if (env->CallBooleanMethod(job, araddid, parent, kp->N, val, j)) {
if (Check(-1))
snprintf(g->Message, sizeof(g->Message), "ArrayAdd: %s", Msg);
else
snprintf(g->Message, sizeof(g->Message), "ArrayAdd: unknown error");
return NULL;
} // endif ArrayAdd
env->DeleteLocalRef(val);
} // endif Jncolp
} // endfor kp
return parent;
} // end of MakeDoc
/***********************************************************************/
/* Insert a new document in the collation. */
/***********************************************************************/
int JMgoConn::DocWrite(PGLOBAL g, PCSZ line)
{
int rc = RC_OK;
jobject doc = nullptr;
if (line) {
int j;
jobject val = env->NewStringUTF(line);
switch (*line) {
case '{': j = 1; break;
case '[': j = 2; break;
default: j = 0; break;
} // endswitch line
doc = env->CallObjectMethod(job, mkbsonid, val, j);
env->DeleteLocalRef(val);
} else if (Fpc)
doc = MakeDoc(g, Fpc);
if (!doc)
return RC_FX;
if (env->CallBooleanMethod(job, insertid, doc)) {
if (Check(-1))
snprintf(g->Message, sizeof(g->Message), "CollInsert: %s", Msg);
else
snprintf(g->Message, sizeof(g->Message), "CollInsert: unknown error");
rc = RC_FX;
} // endif Insert
env->DeleteLocalRef(doc);
return rc;
} // end of DocWrite
/***********************************************************************/
/* Update the current document from the collection. */
/***********************************************************************/
int JMgoConn::DocUpdate(PGLOBAL g, PTDB tdbp)
{
int j = 0, rc = RC_OK;
bool error;
PCOL colp;
jstring jkey;
jobject val, upd, updlist = env->CallObjectMethod(job, mkdocid);
// Make the list of changes to do
for (colp = tdbp->GetSetCols(); colp; colp = colp->GetNext()) {
jkey = env->NewStringUTF(colp->GetJpath(g, false));
val = MakeObject(g, colp, error);
if (error)
return RC_FX;
else if (Stringify(colp))
switch (*colp->GetCharValue()) {
case '{': j = 1; break;
case '[': j = 2; break;
default: break;
} // endswitch
if (env->CallBooleanMethod(job, docaddid, updlist, jkey, val, j))
return RC_OK;
env->DeleteLocalRef(jkey);
} // endfor colp
// Make the update parameter
upd = env->CallObjectMethod(job, mkdocid);
jkey = env->NewStringUTF("$set");
if (env->CallBooleanMethod(job, docaddid, upd, jkey, updlist, 0))
return RC_OK;
env->DeleteLocalRef(jkey);
jlong ar = env->CallLongMethod(job, updateid, upd);
if (trace(1))
htrc("DocUpdate: ar = %ld\n", ar);
if (Check((int)ar)) {
snprintf(g->Message, sizeof(g->Message), "CollUpdate: %s", Msg);
rc = RC_FX;
} // endif ar
return rc;
} // end of DocUpdate
/***********************************************************************/
/* Remove all or only the current document from the collection. */
/***********************************************************************/
int JMgoConn::DocDelete(PGLOBAL g, bool all)
{
int rc = RC_OK;
jlong ar = env->CallLongMethod(job, deleteid, all);
if (trace(1))
htrc("DocDelete: ar = %ld\n", ar);
if (Check((int)ar)) {
snprintf(g->Message, sizeof(g->Message), "CollDelete: %s", Msg);
rc = RC_FX;
} // endif ar
return rc;
} // end of DocDelete
/***********************************************************************/
/* Rewind the collection. */
/***********************************************************************/
bool JMgoConn::Rewind(void)
{
return env->CallBooleanMethod(job, rewindid);
} // end of Rewind
/***********************************************************************/
/* Retrieve the column string value from the document. */
/***********************************************************************/
PSZ JMgoConn::GetColumnValue(PSZ path)
{
PGLOBAL& g = m_G;
PSZ fld = NULL;
jstring fn, jn = nullptr;
if (!path || (jn = env->NewStringUTF(path)) == nullptr) {
snprintf(g->Message, sizeof(g->Message), "Fail to allocate jstring %s", SVP(path));
throw (int)TYPE_AM_MGO;
} // endif name
if (!gmID(g, objfldid, "GetField", "(Ljava/lang/String;)Ljava/lang/String;")) {
fn = (jstring)env->CallObjectMethod(job, objfldid, jn);
if (fn)
fld = (PSZ)GetUTFString(fn);
} // endif objfldid
return fld;
} // end of GetColumnValue