cid#1640468 Dereference after null check
[LibreOffice.git] / sc / source / core / tool / interpr7.cxx
blobecb4ea346396bc0c15ae4210b4fabd719daaf0c4
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <interpre.hxx>
11 #include <jumpmatrix.hxx>
12 #include <formulacell.hxx>
13 #include <scmatrix.hxx>
14 #include <rtl/strbuf.hxx>
15 #include <rtl/character.hxx>
16 #include <formula/errorcodes.hxx>
17 #include <sfx2/bindings.hxx>
18 #include <sfx2/linkmgr.hxx>
19 #include <tools/urlobj.hxx>
21 #include <officecfg/Office/Common.hxx>
22 #include <libxml/xpath.h>
23 #include <datastreamgettime.hxx>
24 #include <dpobject.hxx>
25 #include <document.hxx>
26 #include <tokenarray.hxx>
27 #include <webservicelink.hxx>
29 #include <sc.hrc>
31 #include <cstring>
32 #include <memory>
33 #include <string_view>
34 #include <libxml/parser.h>
36 using namespace com::sun::star;
38 // TODO: Add new methods for ScInterpreter here.
40 void ScInterpreter::ScFilterXML()
42 sal_uInt8 nParamCount = GetByte();
43 if (!MustHaveParamCount( nParamCount, 2 ) )
44 return;
46 SCSIZE nMatCols = 1, nMatRows = 1, nNode = 0;
47 // In array/matrix context node elements' results are to be
48 // subsequently stored. Check this before obtaining any argument from
49 // the stack so the stack type can be used.
50 if (pJumpMatrix || IsInArrayContext())
52 if (pJumpMatrix)
54 // Single result, GetString() will retrieve the corresponding
55 // argument and JumpMatrix() will store it at the proper
56 // position. Note that nMatCols and nMatRows are still 1.
57 SCSIZE nCurCol = 0, nCurRow = 0;
58 pJumpMatrix->GetPos( nCurCol, nCurRow);
59 nNode = nCurRow;
61 else if (bMatrixFormula)
63 // If there is no formula cell then continue with a single
64 // result.
65 if (pMyFormulaCell)
67 SCCOL nCols;
68 SCROW nRows;
69 pMyFormulaCell->GetMatColsRows( nCols, nRows);
70 nMatCols = nCols;
71 nMatRows = nRows;
74 else if (GetStackType() == formula::svMatrix)
76 const ScMatrix* pPathMatrix = pStack[sp-1]->GetMatrix();
77 if (!pPathMatrix)
79 PushIllegalParameter();
80 return;
82 pPathMatrix->GetDimensions( nMatCols, nMatRows);
84 /* TODO: it is unclear what should happen if there are
85 * different path arguments in matrix elements. We may have to
86 * evaluate each, and for repeated identical paths use
87 * subsequent nodes. As is, the path at 0,0 is used as obtained
88 * by GetString(). */
92 if (!nMatCols || !nMatRows)
94 PushNoValue();
95 return;
98 OUString aXPathExpression = GetString().getString();
99 OUString aString = GetString().getString();
100 if(aString.isEmpty() || aXPathExpression.isEmpty())
102 PushError( FormulaError::NoValue );
103 return;
106 OString aOXPathExpression = OUStringToOString( aXPathExpression, RTL_TEXTENCODING_UTF8 );
107 const char* pXPathExpr = aOXPathExpression.getStr();
108 OString aOString = OUStringToOString( aString, RTL_TEXTENCODING_UTF8 );
109 const char* pXML = aOString.getStr();
111 std::shared_ptr<xmlParserCtxt> pContext(
112 xmlNewParserCtxt(), xmlFreeParserCtxt );
114 std::shared_ptr<xmlDoc> pDoc( xmlParseMemory( pXML, aOString.getLength() ),
115 xmlFreeDoc );
117 if(!pDoc)
119 PushError( FormulaError::NoValue );
120 return;
123 std::shared_ptr<xmlXPathContext> pXPathCtx( xmlXPathNewContext(pDoc.get()),
124 xmlXPathFreeContext );
126 std::shared_ptr<xmlXPathObject> pXPathObj( xmlXPathEvalExpression(BAD_CAST(pXPathExpr), pXPathCtx.get()),
127 xmlXPathFreeObject );
129 if(!pXPathObj)
131 PushError( FormulaError::NoValue );
132 return;
135 switch(pXPathObj->type)
137 case XPATH_UNDEFINED:
138 PushNoValue();
139 break;
140 case XPATH_NODESET:
142 xmlNodeSetPtr pNodeSet = pXPathObj->nodesetval;
143 if(!pNodeSet)
145 PushError( FormulaError::NoValue );
146 return;
149 const size_t nSize = pNodeSet->nodeNr;
150 if (nNode >= nSize)
152 // For pJumpMatrix
153 PushError( FormulaError::NotAvailable);
154 return;
157 /* TODO: for nMatCols>1 IF stack type is svMatrix, i.e.
158 * pPathMatrix!=nullptr, we may want a result matrix with
159 * nMatCols columns as well, but clarify first how to treat
160 * differing path elements. */
162 ScMatrixRef xResMat;
163 if (nMatRows > 1)
165 xResMat = GetNewMat( 1, nMatRows, true);
166 if (!xResMat)
168 PushError( FormulaError::CodeOverflow);
169 return;
173 for ( ; nNode < nMatRows; ++nNode)
175 if( nSize > nNode )
177 OUString aResult;
178 if(pNodeSet->nodeTab[nNode]->type == XML_NAMESPACE_DECL)
180 xmlNsPtr ns = reinterpret_cast<xmlNsPtr>(pNodeSet->nodeTab[nNode]);
181 xmlNodePtr cur = reinterpret_cast<xmlNodePtr>(ns->next);
182 std::shared_ptr<xmlChar> pChar2(xmlNodeGetContent(cur), xmlFree);
183 aResult = OStringToOUString(std::string_view(reinterpret_cast<char*>(pChar2.get())), RTL_TEXTENCODING_UTF8);
185 else
187 xmlNodePtr cur = pNodeSet->nodeTab[nNode];
188 std::shared_ptr<xmlChar> pChar2(xmlNodeGetContent(cur), xmlFree);
189 aResult = OStringToOUString(std::string_view(reinterpret_cast<char*>(pChar2.get())), RTL_TEXTENCODING_UTF8);
191 if (xResMat)
192 xResMat->PutString( mrStrPool.intern( aResult), 0, nNode);
193 else
194 PushString(aResult);
196 else
198 if (xResMat)
199 xResMat->PutError( FormulaError::NotAvailable, 0, nNode);
200 else
201 PushError( FormulaError::NotAvailable );
204 if (xResMat)
205 PushMatrix( xResMat);
207 break;
208 case XPATH_BOOLEAN:
210 bool bVal = pXPathObj->boolval != 0;
211 PushDouble(double(bVal));
213 break;
214 case XPATH_NUMBER:
216 double fVal = pXPathObj->floatval;
217 PushDouble(fVal);
219 break;
220 case XPATH_STRING:
221 PushString(OUString::createFromAscii(reinterpret_cast<char*>(pXPathObj->stringval)));
222 break;
223 #if LIBXML_VERSION < 21000 || defined(LIBXML_XPTR_LOCS_ENABLED)
224 case XPATH_POINT:
225 PushNoValue();
226 break;
227 case XPATH_RANGE:
228 PushNoValue();
229 break;
230 case XPATH_LOCATIONSET:
231 PushNoValue();
232 break;
233 #endif
234 case XPATH_USERS:
235 PushNoValue();
236 break;
237 case XPATH_XSLT_TREE:
238 PushNoValue();
239 break;
244 static ScWebServiceLink* lcl_GetWebServiceLink(const sfx2::LinkManager* pLinkMgr, std::u16string_view rURL)
246 size_t nCount = pLinkMgr->GetLinks().size();
247 for (size_t i=0; i<nCount; ++i)
249 ::sfx2::SvBaseLink* pBase = pLinkMgr->GetLinks()[i].get();
250 if (ScWebServiceLink* pLink = dynamic_cast<ScWebServiceLink*>(pBase))
252 if (pLink->GetURL() == rURL)
253 return pLink;
257 return nullptr;
260 static bool lcl_FunctionAccessLoadWebServiceLink( OUString& rResult, ScDocument* pDoc, const OUString& rURI )
262 // For FunctionAccess service always force a changed data update.
263 ScWebServiceLink aLink( pDoc, rURI);
264 if (aLink.DataChanged( OUString(), css::uno::Any()) != sfx2::SvBaseLink::UpdateResult::SUCCESS)
265 return false;
267 if (!aLink.HasResult())
268 return false;
270 rResult = aLink.GetResult();
272 return true;
275 void ScInterpreter::ScWebservice()
277 sal_uInt8 nParamCount = GetByte();
278 if (!MustHaveParamCount( nParamCount, 1 ) )
279 return;
281 OUString aURI = GetString().getString();
283 if (aURI.isEmpty())
285 PushError( FormulaError::NoValue );
286 return;
289 INetURLObject aObj(aURI, INetProtocol::File);
290 INetProtocol eProtocol = aObj.GetProtocol();
291 if (eProtocol != INetProtocol::Http && eProtocol != INetProtocol::Https)
293 PushError(FormulaError::NoValue);
294 return;
297 if (!mpLinkManager)
299 if (!mrDoc.IsFunctionAccess() || mrDoc.HasLinkFormulaNeedingCheck())
301 PushError( FormulaError::NoValue);
303 else
305 OUString aResult;
306 if (lcl_FunctionAccessLoadWebServiceLink( aResult, &mrDoc, aURI))
307 PushString( aResult);
308 else
309 PushError( FormulaError::NoValue);
311 return;
314 // Need to reinterpret after loading (build links)
315 pArr->AddRecalcMode( ScRecalcMode::ONLOAD_LENIENT );
317 // while the link is not evaluated, idle must be disabled (to avoid circular references)
318 bool bOldEnabled = mrDoc.IsIdleEnabled();
319 mrDoc.EnableIdle(false);
321 // Get/ Create link object
322 ScWebServiceLink* pLink = lcl_GetWebServiceLink(mpLinkManager, aURI);
324 bool bWasError = (pMyFormulaCell && pMyFormulaCell->GetRawError() != FormulaError::NONE);
326 if (!pLink)
328 pLink = new ScWebServiceLink(&mrDoc, aURI);
329 mpLinkManager->InsertFileLink(*pLink, sfx2::SvBaseLinkObjectType::ClientFile, aURI);
330 if ( mpLinkManager->GetLinks().size() == 1 ) // the first one?
332 SfxBindings* pBindings = mrDoc.GetViewBindings();
333 if (pBindings)
334 pBindings->Invalidate( SID_LINKS ); // Link-Manager enabled
337 //if the document was just loaded, but the ScDdeLink entry was missing, then
338 //don't update this link until the links are updated in response to the users
339 //decision
340 if (!mrDoc.HasLinkFormulaNeedingCheck())
342 pLink->Update();
345 if (pMyFormulaCell)
347 // StartListening after the Update to avoid circular references
348 pMyFormulaCell->StartListening(*pLink);
351 else
353 if (pMyFormulaCell)
354 pMyFormulaCell->StartListening(*pLink);
357 // If a new Error from Reschedule appears when the link is executed then reset the errorflag
358 if (pMyFormulaCell && pMyFormulaCell->GetRawError() != FormulaError::NONE && !bWasError)
359 pMyFormulaCell->SetErrCode(FormulaError::NONE);
361 // check the value
362 if (pLink->HasResult())
363 PushString(pLink->GetResult());
364 else if (mrDoc.HasLinkFormulaNeedingCheck())
366 // If this formula cell is recalculated just after load and the
367 // expression is exactly WEBSERVICE("literal_URI") (i.e. no other
368 // calculation involved, not even a cell reference) and a cached
369 // result is set as hybrid string then use that as result value to
370 // prevent a #VALUE! result due to the "Automatic update of
371 // external links has been disabled."
372 // This will work only once, as the new formula cell result won't
373 // be a hybrid anymore.
374 /* TODO: the FormulaError::LinkFormulaNeedingCheck could be used as
375 * a signal for the formula cell to keep the hybrid string as
376 * result of the overall formula *iff* no higher prioritized
377 * ScRecalcMode than ONLOAD_LENIENT is present in the entire
378 * document (i.e. the formula result could not be influenced by an
379 * ONLOAD_MUST or ALWAYS recalc, necessary as we don't track
380 * interim results of subexpressions that could be compared), which
381 * also means to track setting ScRecalcMode somehow... note this is
382 * just a vague idea so far and might or might not work. */
383 if (pMyFormulaCell && pMyFormulaCell->HasHybridStringResult())
385 if (pMyFormulaCell->GetCode()->GetCodeLen() == 2)
387 formula::FormulaToken const * const * pRPN = pMyFormulaCell->GetCode()->GetCode();
388 if (pRPN[0]->GetType() == formula::svString && pRPN[1]->GetOpCode() == ocWebservice)
389 PushString( pMyFormulaCell->GetResultString());
390 else
391 PushError(FormulaError::LinkFormulaNeedingCheck);
393 else
394 PushError(FormulaError::LinkFormulaNeedingCheck);
396 else
397 PushError(FormulaError::LinkFormulaNeedingCheck);
399 else
400 PushError(FormulaError::NoValue);
402 mrDoc.EnableIdle(bOldEnabled);
403 mpLinkManager->CloseCachedComps();
407 Returns a string in which all non-alphanumeric characters except stroke and
408 underscore (-_) have been replaced with a percent (%) sign
409 followed by hex digits.
410 It is encoded the same way that the posted data from a WWW form is encoded,
411 that is the same way as in application/x-www-form-urlencoded media type and
412 as per RFC 3986.
414 @see fdo#76870
416 void ScInterpreter::ScEncodeURL()
418 sal_uInt8 nParamCount = GetByte();
419 if ( !MustHaveParamCount( nParamCount, 1 ) )
420 return;
422 OUString aStr = GetString().getString();
423 if ( aStr.isEmpty() )
425 PushError( FormulaError::NoValue );
426 return;
429 OString aUtf8Str( aStr.toUtf8());
430 const sal_Int32 nLen = aUtf8Str.getLength();
431 OStringBuffer aUrlBuf( nLen );
432 for ( int i = 0; i < nLen; i++ )
434 char c = aUtf8Str[ i ];
435 if ( rtl::isAsciiAlphanumeric( static_cast<unsigned char>( c ) ) || c == '-' || c == '_' )
436 aUrlBuf.append( c );
437 else
439 aUrlBuf.append( '%' );
440 OString convertedChar = OString::number( static_cast<unsigned char>( c ), 16 ).toAsciiUpperCase();
441 // RFC 3986 indicates:
442 // "A percent-encoded octet is encoded as a character triplet,
443 // consisting of the percent character "%" followed by the two hexadecimal digits
444 // representing that octet's numeric value"
445 if (convertedChar.getLength() == 1)
446 aUrlBuf.append("0");
447 aUrlBuf.append(convertedChar);
450 PushString( OUString::fromUtf8( aUrlBuf ) );
453 void ScInterpreter::ScDebugVar()
455 // This is to be used by developers only! Never document this for end
456 // users. This is a convenient way to extract arbitrary internal state to
457 // a cell for easier debugging.
459 if (!officecfg::Office::Common::Misc::ExperimentalMode::get())
461 PushError(FormulaError::NoName);
462 return;
465 if (!MustHaveParamCount(GetByte(), 1))
466 return;
468 rtl_uString* p = GetString().getDataIgnoreCase();
469 if (!p)
471 PushIllegalParameter();
472 return;
475 OUString aStrUpper(p);
477 if (aStrUpper == "PIVOTCOUNT")
479 // Set the number of pivot tables in the document.
481 double fVal = 0.0;
482 if (mrDoc.HasPivotTable())
484 const ScDPCollection* pDPs = mrDoc.GetDPCollection();
485 fVal = pDPs->GetCount();
487 PushDouble(fVal);
489 else if (aStrUpper == "DATASTREAM_IMPORT")
490 PushDouble( sc::datastream_get_time( sc::DebugTime::Import ) );
491 else if (aStrUpper == "DATASTREAM_RECALC")
492 PushDouble( sc::datastream_get_time( sc::DebugTime::Recalc ) );
493 else if (aStrUpper == "DATASTREAM_RENDER")
494 PushDouble( sc::datastream_get_time( sc::DebugTime::Render ) );
495 else
496 PushIllegalParameter();
499 void ScInterpreter::ScErf()
501 sal_uInt8 nParamCount = GetByte();
502 if (MustHaveParamCount( nParamCount, 1 ) )
503 PushDouble( std::erf( GetDouble() ) );
506 void ScInterpreter::ScErfc()
508 sal_uInt8 nParamCount = GetByte();
509 if (MustHaveParamCount( nParamCount, 1 ) )
510 PushDouble( std::erfc( GetDouble() ) );
513 void ScInterpreter::ScColor()
515 sal_uInt8 nParamCount = GetByte();
516 if(!MustHaveParamCount(nParamCount, 3, 4))
517 return;
519 double nAlpha = 0;
520 if(nParamCount == 4)
521 nAlpha = rtl::math::approxFloor(GetDouble());
523 if(nAlpha < 0 || nAlpha > 255)
525 PushIllegalArgument();
526 return;
529 double nBlue = rtl::math::approxFloor(GetDouble());
531 if(nBlue < 0 || nBlue > 255)
533 PushIllegalArgument();
534 return;
537 double nGreen = rtl::math::approxFloor(GetDouble());
539 if(nGreen < 0 || nGreen > 255)
541 PushIllegalArgument();
542 return;
545 double nRed = rtl::math::approxFloor(GetDouble());
547 if(nRed < 0 || nRed > 255)
549 PushIllegalArgument();
550 return;
553 double nVal = 256*256*256*nAlpha + 256*256*nRed + 256*nGreen + nBlue;
554 PushDouble(nVal);
557 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */