• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

polserver / polserver / 16059069639

03 Jul 2025 07:33PM UTC coverage: 59.304% (+0.07%) from 59.236%
16059069639

push

github

web-flow
Add module function `os::SendEmail` (#793)

* wip implementation

* maybe add tests...

* fix dependencies

* fix windows and mac deps

* attempt #3 for mac deps

* add --break-system-packages because WHY NOT

* add raii wrapper for curl_slist and curl_mime

* add content_type; fix incorrect recipient variable; more tests

* put smtpd log in file; add curl_easy_setopt_return_error; fix parsing email from display string

* remove some debugging

* catch exceptions in polsys::ReloadConfiguration; tests; smtpd cleanup

* Fix xslt validation erorrs in osem.xml
- `HTTPRequest`` did not have the `<code>` block inside `<explain>` so
it was not rendered.
- `LoadExportedScript` needed `<return>` after `<explain>`
- `GetEnvironmentVariable` needed `<return>` after `<explain>`

* Address review comments
- remove openssl test dep by generating cert, valid for 100 years
- only run test if asiosmtpd module is installed

* Address review comments
- remove `pChild`
- remove `curl_easy_setopt_return_error` macro
- return `new BLong( 0 )` for temp object replaced by background thread

* Address review comments
- use `Clib::localtime` + `fmt::format` for date header

* Address review comments
- Early exit on failures

* Address review comments
- Move suspend to immediately before background thread creation

* Address review comments
- better handling of curl_slist and curl_mimes

* Address Discord comments
- remove env check in test_email program

* attempt to fix timezone issue in date header

* additional tests for invalid params

* add docs, core changes

* smtpd: fix event loop, only run if not filtered out

* put stacktrace in "no signal received" error

* fix stacktrace call in testutil; add default param to os.environ.get in smtpd

126 of 161 new or added lines in 4 files covered. (78.26%)

1 existing line in 1 file now uncovered.

42923 of 72378 relevant lines covered (59.3%)

472242.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

61.72
/pol-core/pol/module/osmod.cpp
1
/** @file
2
 *
3
 * @par History
4
 */
5

6
#include "osmod.h"
7

8
#include "bscript/berror.h"
9
#include "bscript/bobject.h"
10
#include "bscript/bstruct.h"
11
#include "bscript/dict.h"
12
#include "bscript/impstr.h"
13
#include "clib/clib.h"
14
#include "clib/logfacility.h"
15
#include "clib/network/sckutil.h"
16
#include "clib/rawtypes.h"
17
#include "clib/refptr.h"
18
#include "clib/stlutil.h"
19
#include "clib/threadhelp.h"
20
#include "clib/weakptr.h"
21
#include "globals/settings.h"
22
#include "plib/systemstate.h"
23

24
#include "../exscrobj.h"
25
#include "../globals/script_internals.h"
26
#include "../globals/state.h"
27
#include "../item/item.h"
28
#include "../mobile/attribute.h"
29
#include "../mobile/charactr.h"
30
#include "../mobile/npc.h"
31
#include "../network/auxclient.h"
32
#include "../network/packethelper.h"
33
#include "../network/packets.h"
34
#include "../network/pktdef.h"
35
#include "../poldbg.h"
36
#include "../polsem.h"
37
#include "../profile.h"
38
#include "../schedule.h"
39
#include "../scrdef.h"
40
#include "../scrsched.h"
41
#include "../scrstore.h"
42
#include "../skills.h"
43
#include "../ufunc.h"
44
#include "../uoexec.h"
45
#include "npcmod.h"
46
#include "uomod.h"
47

48
#include <chrono>
49
#include <module_defs/os.h>
50

51
#ifdef _WIN32
52
#pragma comment( lib, "crypt32.lib" )
53
#endif
54
#include <ctime>
55
#include <curl/curl.h>
56
#include <memory>
57
#include <stdlib.h>
58
#include <string.h>
59
#include <string>
60
#include <string_view>
61
#include <unordered_map>
62

63
// environment variable for mf_GetEnvironmentVariable
64
#ifdef _MSC_VER
65
extern char** _environ;
66
char** environ_vars = _environ;
67
#else
68
extern char** environ;
69
char** environ_vars = environ;
70
#endif
71

72
namespace
73
{
74
class CurlStringList
75
{
76
public:
77
  CurlStringList() : resource_( nullptr ) {}
16✔
78

79
  ~CurlStringList()
16✔
80
  {
81
    if ( resource_ )
16✔
82
    {
83
      curl_slist_free_all( resource_ );
15✔
84
    }
85
  }
16✔
86

87
  void add( const std::string& str )
38✔
88
  {
89
    auto* new_resource = curl_slist_append( resource_, str.c_str() );
38✔
90

91
    if ( new_resource != resource_ && resource_ != nullptr )
38✔
92
    {
NEW
93
      curl_slist_free_all( resource_ );
×
94
    }
95

96
    resource_ = new_resource;
38✔
97
  }
38✔
98

99
  curl_slist* get() const { return resource_; }
14✔
100

101
private:
102
  curl_slist* resource_;
103
};
104

105
};  // namespace
106

107
namespace Pol
108
{
109
namespace Module
110
{
111
using namespace Bscript;
112

113
unsigned int getnewpid( Core::UOExecutor* uoexec )
280✔
114
{
115
  return Core::scriptScheduler.get_new_pid( uoexec );
280✔
116
}
117
void freepid( unsigned int pid )
280✔
118
{
119
  Core::scriptScheduler.free_pid( pid );
280✔
120
}
280✔
121

122
OSExecutorModule::OSExecutorModule( Bscript::Executor& exec )
280✔
123
    : TmplExecutorModule<OSExecutorModule, Core::PolModule>( exec ),
124
      critical_( false ),
280✔
125
      priority_( 1 ),
280✔
126
      warn_on_runaway_( true ),
280✔
127
      blocked_( false ),
280✔
128
      sleep_until_clock_( 0 ),
280✔
129
      hold_itr_(),
280✔
130
      in_hold_list_( Core::HoldListType::NO_LIST ),
280✔
131
      wait_type( Core::WAIT_TYPE::WAIT_UNKNOWN ),
280✔
132
      pid_( getnewpid( &uoexec() ) ),
280✔
133
      max_eventqueue_size( Core::MAX_EVENTQUEUE_SIZE ),
280✔
134
      events_()
560✔
135
{
136
}
280✔
137

138
OSExecutorModule::~OSExecutorModule()
560✔
139
{
140
  freepid( pid_ );
280✔
141
  pid_ = 0;
280✔
142
  while ( !events_.empty() )
288✔
143
  {
144
    Bscript::BObject ob( events_.front() );
8✔
145
    events_.pop_front();
8✔
146
  }
8✔
147
}
560✔
148

149
unsigned int OSExecutorModule::pid() const
33✔
150
{
151
  return pid_;
33✔
152
}
153

154
BObjectImp* OSExecutorModule::mf_Create_Debug_Context()
×
155
{
156
  return Core::create_debug_context();
×
157
}
158

159
BObjectImp* OSExecutorModule::mf_Debugger()
2✔
160
{
161
  Core::UOExecutor* uoexec;
162
  if ( find_uoexec( pid_, &uoexec ) )
2✔
163
  {
164
    return new BLong( uoexec->attach_debugger() );
2✔
165
  }
166
  else
167
    return new BError( "Could not find UOExecutor for current process." );
×
168
}
169

170
BObjectImp* OSExecutorModule::mf_GetProcess()
130✔
171
{
172
  int pid;  // note that while pid's are unsigned, valid values are forced to fit within a signed
173
            // range
174
  if ( !getParam( 0, pid ) )
130✔
175
    return new BError( "Invalid parameter type" );
2✔
176

177
  if ( pid == -1 )
128✔
178
  {
179
    pid = pid_;  // get executor's own pid
60✔
180
  }
181

182
  Core::UOExecutor* uoexec;
183
  if ( find_uoexec( pid, &uoexec ) )
128✔
184
    return new Core::ScriptExObjImp( uoexec );
126✔
185
  else
186
    return new BError( "Process not found" );
2✔
187
}
188

189
BObjectImp* OSExecutorModule::mf_GetPid()
34✔
190
{
191
  return new BLong( pid_ );
34✔
192
}
193
/*  Ok, this is going to be fun.  In the case where we block,
194
the caller is going to take our return value and push
195
it on the value stack.
196

197
What we'll do is push the value that should be returned
198
if a timeout occurs.  THis way, for timeouts, all we have
199
to do is move the script back into the run queue.
200

201
When we actually complete something, we'll have to
202
pop the value off the stack, and replace it with
203
the real result.
204

205
Whew!
206
*/
207
BObjectImp* OSExecutorModule::mf_Sleep()
13✔
208
{
209
  int nsecs = exec.paramAsLong( 0 );
13✔
210
  SleepFor( nsecs > 0 ? static_cast<u32>( nsecs ) : 1u );
13✔
211
  return new BLong( 0 );
13✔
212
}
213

214
BObjectImp* OSExecutorModule::mf_Sleepms()
13,595,871✔
215
{
216
  int msecs = exec.paramAsLong( 0 );
13,595,871✔
217
  SleepForMs( msecs > 0 ? static_cast<u32>( msecs ) : 1u );
13,595,871✔
218
  return new BLong( 0 );
13,595,871✔
219
}
220

221
BObjectImp* OSExecutorModule::mf_Wait_For_Event()
1,695✔
222
{
223
  if ( !events_.empty() )
1,695✔
224
  {
225
    BObjectImp* imp = events_.front();
505✔
226
    events_.pop_front();
505✔
227
    return imp;
505✔
228
  }
229
  else
230
  {
231
    auto param = exec.getParamImp( 0 );
1,190✔
232
    double nsecs = 0;
1,190✔
233

234
    if ( auto* long_param = impptrIf<BLong>( param ) )
1,190✔
235
    {
236
      nsecs = long_param->value();
1,185✔
237
      if ( !nsecs )
1,185✔
238
        return new BLong( 0 );
57✔
239
      if ( nsecs < 1 )
1,128✔
240
        nsecs = 1;
×
241
    }
242
    else if ( auto* double_param = impptrIf<Double>( param ) )
5✔
243
    {
244
      nsecs = double_param->value();
4✔
245
      if ( !nsecs )
4✔
246
        return new BLong( 0 );
1✔
247
      if ( nsecs < 0.01 )
3✔
248
        nsecs = 0.01;
2✔
249
    }
250
    else
251
    {
252
      return new BLong( 0 );
1✔
253
    }
254

255
    wait_type = Core::WAIT_TYPE::WAIT_EVENT;
1,131✔
256
    blocked_ = true;
1,131✔
257
    sleep_until_clock_ =
1,131✔
258
        Core::polclock() + Clib::clamp_convert<u32>( nsecs * Core::POLCLOCKS_PER_SEC );
1,131✔
259
    return new BLong( 0 );
1,131✔
260
  }
261
}
262

263
BObjectImp* OSExecutorModule::mf_Events_Waiting()
×
264
{
265
  return new BLong( static_cast<int>( events_.size() ) );
×
266
}
267

268
BObjectImp* OSExecutorModule::mf_Start_Script()
27✔
269
{
270
  const String* scriptname_str;
271
  if ( exec.getStringParam( 0, scriptname_str ) )
27✔
272
  {
273
    BObjectImp* imp = exec.getParamImp( 1 );
27✔
274

275
    // FIXME needs to inherit available modules?
276
    Core::ScriptDef sd;
27✔
277
    if ( !sd.config_nodie( scriptname_str->value(), exec.prog()->pkg, "scripts/" ) )
27✔
278
    {
279
      return new BError( "Error in script name" );
×
280
    }
281
    if ( !sd.exists() )
27✔
282
    {
283
      return new BError( "Script " + sd.name() + " does not exist." );
×
284
    }
285
    UOExecutorModule* new_uoemod = Core::start_script( sd, imp->copy() );
27✔
286
    if ( new_uoemod == nullptr )
27✔
287
    {
288
      return new BError( "Unable to start script" );
×
289
    }
290
    UOExecutorModule* this_uoemod = static_cast<UOExecutorModule*>( exec.findModule( "uo" ) );
27✔
291
    if ( new_uoemod != nullptr && this_uoemod != nullptr )
27✔
292
    {
293
      new_uoemod->controller_ = this_uoemod->controller_;
27✔
294
    }
295

296
    return new Core::ScriptExObjImp( &new_uoemod->uoexec() );
27✔
297
  }
27✔
298
  else
299
  {
300
    return new BError( "Invalid parameter type" );
×
301
  }
302
}
303

304

305
BObjectImp* OSExecutorModule::mf_Start_Skill_Script()
×
306
{
307
  Mobile::Character* chr;
308
  const Mobile::Attribute* attr;
309

310
  if ( getCharacterParam( 0, chr ) && getAttributeParam( 1, attr ) )
×
311
  {
312
    if ( !attr->disable_core_checks && !Core::CanUseSkill( chr->client ) )
×
313
      return new BLong( 0 );
×
314
    else
315
    {
316
      const String* script_name;
317
      Core::ScriptDef script;
×
318

319
      if ( exec.getStringParam( 2, script_name ) && ( script_name->length() > 0 ) )
×
320
      {
321
        if ( !script.config_nodie( script_name->value(), exec.prog()->pkg, "scripts/skills/" ) )
×
322
        {
323
          return new BError( "Error in script name" );
×
324
        }
325
        if ( !script.exists() )
×
326
        {
327
          return new BError( "Script " + script.name() + " does not exist." );
×
328
        }
329
      }
330
      else
331
      {
332
        if ( !attr->script_.empty() )
×
333
          script = attr->script_;
×
334
        else
335
          return new BError( "No script defined for attribute " + attr->name + "." );
×
336
      }
337

338
      ref_ptr<EScriptProgram> prog = find_script2(
339
          script, true,
340
          /* complain if not found */ Plib::systemstate.config.cache_interactive_scripts );
×
341

342
      if ( prog.get() != nullptr )
×
343
      {
344
        BObjectImp* imp = exec.getParamImp( 3 );
×
345
        if ( imp )
×
346
        {
347
          if ( chr->start_script( prog.get(), true, imp->copy() ) )
×
348
          {
349
            if ( chr->hidden() && attr->unhides )
×
350
              chr->unhide();
×
351
            if ( attr->delay_seconds )
×
352
              chr->disable_skills_until( Core::poltime() + attr->delay_seconds );
×
353
          }
354
        }
355
        else
356
        {
357
          if ( chr->start_script( prog.get(), true ) )
×
358
          {
359
            if ( chr->hidden() && attr->unhides )
×
360
              chr->unhide();
×
361
            if ( attr->delay_seconds )
×
362
              chr->disable_skills_until( Core::poltime() + attr->delay_seconds );
×
363
          }
364
        }
365
      }
366
      else
367
      {
368
        std::string msg = "Unable to start skill script:";
×
369
        msg += script.c_str();
×
370
        Core::send_sysmessage( chr->client, msg.c_str() );
×
371

372
        return new BLong( 0 );
×
373
      }
×
374
      return new BLong( 1 );
×
375
    }
×
376
  }
377
  else
378
  {
379
    return new BError( "Invalid parameter type" );
×
380
  }
381
}
382

383
BObjectImp* OSExecutorModule::mf_Set_Critical()
10✔
384
{
385
  int crit;
386
  if ( exec.getParam( 0, crit ) )
10✔
387
  {
388
    critical_ = ( crit != 0 );
10✔
389
    return new BLong( 1 );
10✔
390
  }
391
  else
392
  {
393
    return new BError( "Invalid parameter type" );
×
394
  }
395
}
396

397
BObjectImp* OSExecutorModule::mf_Is_Critical()
×
398
{
399
  if ( critical_ )
×
400
    return new BLong( 1 );
×
401
  else
402
    return new BLong( 0 );
×
403
}
404

405
BObjectImp* OSExecutorModule::mf_Run_Script_To_Completion()
9✔
406
{
407
  const char* scriptname = exec.paramAsString( 0 );
9✔
408
  if ( scriptname == nullptr )
9✔
409
    return new BLong( 0 );
×
410

411
  // FIXME needs to inherit available modules?
412
  Core::ScriptDef script;
9✔
413

414
  if ( !script.config_nodie( scriptname, exec.prog()->pkg, "scripts/" ) )
9✔
415
    return new BError( "Script descriptor error" );
×
416

417
  if ( !script.exists() )
9✔
418
    return new BError( "Script does not exist" );
×
419

420
  return Core::run_script_to_completion( script, getParamImp( 1 ) );
9✔
421
}
9✔
422

423
BObjectImp* OSExecutorModule::mf_Run_Script()
26✔
424
{
425
  UOExecutorModule* this_uoemod = static_cast<UOExecutorModule*>( exec.findModule( "uo" ) );
26✔
426
  Core::UOExecutor& this_uoexec = uoexec();
26✔
427

428

429
  if ( this_uoexec.pChild == nullptr )
26✔
430
  {
431
    const String* scriptname_str;
432
    if ( exec.getStringParam( 0, scriptname_str ) )
13✔
433
    {
434
      BObjectImp* imp = exec.getParamImp( 1 );
13✔
435

436
      // FIXME needs to inherit available modules?
437
      Core::ScriptDef sd;
13✔
438
      if ( !sd.config_nodie( scriptname_str->value(), exec.prog()->pkg, "scripts/" ) )
13✔
439
      {
440
        return new BError( "Error in script name" );
×
441
      }
442
      if ( !sd.exists() )
13✔
443
      {
444
        return new BError( "Script " + sd.name() + " does not exist." );
×
445
      }
446
      UOExecutorModule* new_uoemod = Core::start_script( sd, imp->copy() );
13✔
447
      if ( new_uoemod == nullptr )
13✔
448
      {
449
        return new BError( "Unable to run script" );
×
450
      }
451
      if ( new_uoemod )
13✔
452
      {
453
        new_uoemod->controller_ = this_uoemod->controller_;
13✔
454
      }
455
      Core::UOExecutor& new_uoexec = new_uoemod->uoexec();
13✔
456
      //      OSExecutorModule* osemod = uoexec->os_module;
457
      new_uoexec.pParent = &this_uoexec;
13✔
458
      this_uoexec.pChild = &new_uoexec;
13✔
459

460
      // we want to forcefully do this instruction over again:
461
      this_uoexec.PC--;  // run_script(
13✔
462
      this_uoexec.ValueStack.push_back(
13✔
463
          BObjectRef( new BObject( UninitObject::create() ) ) );  //   script_name,
26✔
464
      // No need to push on "param" since the new BLong(0) below will take care of it.//   param )
465

466
      // Put me on hold until my child is done.
467
      suspend();
13✔
468

469
      return new BLong( 0 );
13✔
470
    }
13✔
471
    else
472
    {
473
      return new BError( "Invalid parameter type" );
×
474
    }
475
  }
476

477
  // else I am running a child script, and its ended
478
  BObjectImp* ret;
479

480
  if ( this_uoexec.pChild->ValueStack.empty() )
13✔
481
    ret = new BLong( 1 );
8✔
482
  else
483
    ret = this_uoexec.pChild->ValueStack.back().get()->impptr()->copy();
5✔
484

485
  this_uoexec.pChild->pParent = nullptr;
13✔
486
  this_uoexec.pChild = nullptr;
13✔
487

488
  return ret;
13✔
489
}
490

491
BObjectImp* OSExecutorModule::mf_Set_Debug()
×
492
{
493
  int dbg;
494
  if ( getParam( 0, dbg ) )
×
495
  {
496
    if ( dbg )
×
497
      exec.setDebugLevel( Executor::SOURCELINES );
×
498
    return new BLong( 1 );
×
499
  }
500
  else
501
  {
502
    return new BError( "Invalid parameter type" );
×
503
  }
504
}
505

506
BObjectImp* OSExecutorModule::mf_SysLog()
1,529✔
507
{
508
  BObjectImp* imp = exec.getParamImp( 0 );
1,529✔
509
  int log_verbose;
510
  const String* color;
511
  if ( !exec.getParam( 1, log_verbose ) || !exec.getStringParam( 2, color ) )
1,529✔
512
    return new BError( "Invalid parameter type" );
×
513
  std::string strval = imp->getStringRep();
1,529✔
514
  if ( log_verbose )
1,529✔
515
  {
516
    POLLOGLN( "[{}]: {}", exec.scriptname(), strval );
×
517
    if ( Plib::systemstate.config.enable_colored_output && color->length() )
×
518
    {
519
      INFO_PRINTLN( "{}syslog [{}]: {}{}", color->value(), exec.scriptname(), strval,
×
520
                    Clib::Logging::CONSOLE_RESET_COLOR );
521
    }
522
    else
523
    {
524
      INFO_PRINTLN( "syslog [{}]: {}", exec.scriptname(), strval );
×
525
    }
526
  }
527
  else
528
  {
529
    if ( Plib::systemstate.config.enable_colored_output && color->length() )
1,529✔
530
    {
531
      POLLOGLN( strval );
762✔
532
      INFO_PRINTLN( "{}{}{}", color->value(), strval, Clib::Logging::CONSOLE_RESET_COLOR );
762✔
533
    }
534
    else
535
    {
536
      POLLOG_INFOLN( strval );
767✔
537
    }
538
  }
539
  return new BLong( 1 );
1,529✔
540
}
1,529✔
541

542
BObjectImp* OSExecutorModule::mf_Set_Priority()
×
543
{
544
  int newpri;
545
  if ( getParam( 0, newpri, 1, 255 ) )
×
546
  {
547
    int oldpri = priority_;
×
548
    priority_ = static_cast<unsigned char>( newpri );
×
549
    return new BLong( oldpri );
×
550
  }
551
  else
552
  {
553
    return new BError( "Invalid parameter type" );
×
554
  }
555
}
556

557

558
BObjectImp* OSExecutorModule::mf_Unload_Scripts()
×
559
{
560
  const String* str;
561
  if ( getStringParam( 0, str ) )
×
562
  {
563
    int n;
564
    if ( str->length() == 0 )
×
565
      n = Core::unload_all_scripts();
×
566
    else
567
      n = Core::unload_script( str->data() );
×
568
    return new BLong( n );
×
569
  }
570
  else
571
  {
572
    return new BError( "Invalid parameter type" );
×
573
  }
574
}
575

576
BObjectImp* OSExecutorModule::clear_event_queue()  // DAVE
109✔
577
{
578
  while ( !events_.empty() )
131✔
579
  {
580
    BObject ob( events_.front() );
22✔
581
    events_.pop_front();
22✔
582
  }
22✔
583
  return new BLong( 1 );
109✔
584
}
585

586
BObjectImp* OSExecutorModule::mf_Set_Event_Queue_Size()  // DAVE 11/24
1✔
587
{
588
  unsigned short param;
589
  if ( getParam( 0, param ) )
1✔
590
  {
591
    unsigned short oldsize = max_eventqueue_size;
1✔
592
    max_eventqueue_size = param;
1✔
593
    return new BLong( oldsize );
1✔
594
  }
595
  else
596
    return new BError( "Invalid parameter type" );
×
597
}
598

599
BObjectImp* OSExecutorModule::mf_OpenURL()
×
600
{
601
  Mobile::Character* chr;
602
  const String* str;
603
  if ( getCharacterParam( 0, chr ) && ( ( str = getStringParam( 1 ) ) != nullptr ) )
×
604
  {
605
    if ( chr->has_active_client() )
×
606
    {
607
      Network::PktHelper::PacketOut<Network::PktOut_A5> msg;
×
608
      unsigned urllen;
609
      const char* url = str->data();
×
610

611
      urllen = static_cast<unsigned int>( strlen( url ) );
×
612
      if ( urllen > URL_MAX_LEN )
×
613
        urllen = URL_MAX_LEN;
×
614

615
      msg->WriteFlipped<u16>( urllen + 4u );
×
616
      msg->Write( url, static_cast<u16>( urllen + 1 ) );
×
617
      msg.Send( chr->client );
×
618
      return new BLong( 1 );
×
619
    }
×
620
    else
621
    {
622
      return new BError( "No client attached" );
×
623
    }
624
  }
625
  else
626
  {
627
    return new BError( "Invalid parameter type" );
×
628
  }
629
}
630

631

632
BObjectImp* OSExecutorModule::mf_OpenConnection()
4✔
633
{
634
  Core::UOExecutor& this_uoexec = uoexec();
4✔
635

636
  if ( this_uoexec.pChild == nullptr )
4✔
637
  {
638
    const String* host;
639
    const String* scriptname_str;
640
    BObjectImp* scriptparam;
641
    unsigned short port;
642
    int assume_string_int;
643
    int keep_connection_int;
644
    int ignore_line_breaks_int;
645
    if ( getStringParam( 0, host ) && getParam( 1, port ) && getStringParam( 2, scriptname_str ) &&
8✔
646
         getParamImp( 3, scriptparam ) && getParam( 4, assume_string_int ) &&
4✔
647
         getParam( 5, keep_connection_int ) && getParam( 6, ignore_line_breaks_int ) )
8✔
648
    {
649
      // FIXME needs to inherit available modules?
650
      Core::ScriptDef sd;
4✔
651
      if ( !sd.config_nodie( scriptname_str->value(), exec.prog()->pkg, "scripts/" ) )
4✔
652
      {
653
        return new BError( "Error in script name" );
×
654
      }
655
      if ( !sd.exists() )
4✔
656
      {
657
        return new BError( "Script " + sd.name() + " does not exist." );
×
658
      }
659
      if ( !this_uoexec.suspend() )
4✔
660
      {
661
        DEBUGLOGLN(
×
662
            "Script Error in '{}' PC={}: \n"
663
            "\tThe execution of this script can't be blocked!",
664
            this_uoexec.scriptname(), this_uoexec.PC );
×
665
        return new Bscript::BError( "Script can't be blocked" );
×
666
      }
667

668
      weak_ptr<Core::UOExecutor> uoexec_w = this_uoexec.weakptr;
4✔
669
      std::string hostname( host->value() );
4✔
670
      bool assume_string = assume_string_int != 0;
4✔
671
      bool keep_connection = keep_connection_int != 0;
4✔
672
      bool ignore_line_breaks = ignore_line_breaks_int != 0;
4✔
673
      auto* paramobjimp_raw = scriptparam->copy();  // prevent delete
4✔
674
      Core::networkManager.auxthreadpool->push(
8✔
675
          [uoexec_w, sd, hostname, port, paramobjimp_raw, assume_string, keep_connection,
8✔
676
           ignore_line_breaks]()
12✔
677
          {
678
            Clib::Socket s;
4✔
679
            std::unique_ptr<Network::AuxClientThread> client;
4✔
680
            bool success_open = s.open( hostname.c_str(), port );
4✔
681
            {
682
              Core::PolLock lck;
4✔
683
              std::unique_ptr<BObjectImp> paramobjimp( paramobjimp_raw );
4✔
684
              if ( !uoexec_w.exists() )
4✔
685
              {
686
                DEBUGLOGLN( "OpenConnection Script has been destroyed" );
×
687
                s.close();
×
688
                return;
×
689
              }
690
              if ( !success_open )
4✔
691
              {
692
                uoexec_w.get_weakptr()->ValueStack.back().set(
×
693
                    new BObject( new BError( "Error connecting to client" ) ) );
×
694
                uoexec_w.get_weakptr()->revive();
×
695
                return;
×
696
              }
697
              uoexec_w.get_weakptr()->ValueStack.back().set( new BObject( new BLong( 1 ) ) );
4✔
698
              uoexec_w.get_weakptr()->revive();
4✔
699
              client.reset( new Network::AuxClientThread( sd, std::move( s ), paramobjimp.release(),
8✔
700
                                                          assume_string, keep_connection,
701
                                                          ignore_line_breaks ) );
12✔
702
            }
4✔
703
            if ( client )
4✔
704
              client->run();
4✔
705
          } );
4✔
706

707
      return new BLong( 0 );
4✔
708
    }
4✔
709
    else
710
    {
711
      return new BError( "Invalid parameter type" );
×
712
    }
713
  }
714

715
  return new BError( "Invalid parameter type" );
×
716
}
717

718
size_t curlWriteCallback( void* contents, size_t size, size_t nmemb, void* userp )
12✔
719
{
720
  ( static_cast<std::string*>( userp ) )->append( static_cast<char*>( contents ), size * nmemb );
12✔
721
  return size * nmemb;
12✔
722
}
723

724
const int HTTPREQUEST_EXTENDED_RESPONSE = 0x0001;
725

726
struct CurlHeaderData
727
{
728
  std::optional<std::string> statusText;
729
  std::map<std::string, std::string> headers = {};
730
};
731

732
size_t curlReadHeaderCallback( char* buffer, size_t size, size_t nitems, void* userdata )
18✔
733
{
734
  auto headerData = static_cast<CurlHeaderData*>( userdata );
18✔
735
  std::string value( buffer, size * nitems );
18✔
736

737
  // statusText is set after parsing the first header line
738
  if ( !headerData->statusText.has_value() )
18✔
739
  {
740
    std::string statusText = "";
5✔
741
    if ( value.rfind( "HTTP/", 0 ) == 0 )
5✔
742
    {
743
      // Find space after "HTTP/x" (before status code)
744
      size_t pos = value.find( " " );
5✔
745
      if ( pos != std::string::npos )
5✔
746
      {
747
        // Find space after status code (before status text)
748
        pos = value.find( " ", pos + 1 );
5✔
749
        if ( pos != std::string::npos )
5✔
750
        {
751
          statusText = Clib::strtrim( value.substr( pos + 1 ) );
5✔
752
        }
753
      }
754
    }
755
    headerData->statusText.emplace( statusText );
5✔
756
  }
5✔
757
  else
758
  {
759
    size_t pos = value.find( ":" );
13✔
760
    if ( pos != std::string::npos )
13✔
761
    {
762
      std::string headerName = Clib::strlowerASCII( value.substr( 0, pos ) );
8✔
763
      std::string headerValue = Clib::strtrim( value.substr( pos + 1 ) );
8✔
764

765
      // If the header already exists, append it (comma-separated) to previous value
766
      if ( headerData->headers.count( headerName ) )
8✔
767
      {
768
        auto& existing = headerData->headers.at( headerName );
1✔
769
        existing.append( ", " );
1✔
770
        existing.append( headerValue );
1✔
771
      }
772
      else
773
      {
774
        headerData->headers.emplace( headerName, headerValue );
7✔
775
      }
776
    }
8✔
777
  }
778

779
  return nitems * size;
18✔
780
}
18✔
781

782
BObjectImp* OSExecutorModule::mf_HTTPRequest()
18✔
783
{
784
  Core::UOExecutor& this_uoexec = uoexec();
18✔
785

786
  if ( this_uoexec.pChild == nullptr )
18✔
787
  {
788
    const String *url, *method;
789
    BObjectImp* options;
790
    int flags;
791
    if ( getStringParam( 0, url ) && getStringParam( 1, method ) && getParamImp( 2, options ) &&
36✔
792
         getParam( 3, flags ) )
18✔
793
    {
794
      if ( !this_uoexec.suspend() )
18✔
795
      {
796
        DEBUGLOGLN(
×
797
            "Script Error in '{}' PC={}: \n"
798
            "\tThe execution of this script can't be blocked!",
799
            this_uoexec.scriptname(), this_uoexec.PC );
×
800
        return new Bscript::BError( "Script can't be blocked" );
×
801
      }
802

803
      weak_ptr<Core::UOExecutor> uoexec_w = this_uoexec.weakptr;
18✔
804

805
      std::shared_ptr<CURL> curl_sp( curl_easy_init(), curl_easy_cleanup );
18✔
806
      CURL* curl = curl_sp.get();
18✔
807
      if ( curl )
18✔
808
      {
809
        curl_easy_setopt( curl, CURLOPT_URL, url->data() );
18✔
810
        curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, method->data() );
18✔
811
        curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, curlWriteCallback );
18✔
812
        curl_easy_setopt( curl, CURLOPT_ACCEPT_ENCODING,
18✔
813
                          "" );  // allow plaintext and compressed (with automatic deflate)
814

815
        struct curl_slist* chunk = nullptr;
18✔
816
        if ( options->isa( Bscript::BObjectImp::OTStruct ) )
18✔
817
        {
818
          Bscript::BStruct* opts = static_cast<Bscript::BStruct*>( options );
18✔
819
          const BObjectImp* data = opts->FindMember( "data" );
18✔
820
          if ( data != nullptr )
18✔
821
          {
822
            curl_easy_setopt( curl, CURLOPT_COPYPOSTFIELDS, data->getStringRep().c_str() );
×
823
          }
824

825
          const BObjectImp* headers_ = opts->FindMember( "headers" );
18✔
826

827
          if ( headers_ != nullptr && headers_->isa( BObjectImp::OTStruct ) )
18✔
828
          {
829
            const BStruct* headers = static_cast<const BStruct*>( headers_ );
×
830

831
            for ( const auto& content : headers->contents() )
×
832
            {
833
              BObjectImp* ref = content.second->impptr();
×
834
              std::string header = content.first + ": " + ref->getStringRep();
×
835
              chunk = curl_slist_append( chunk, header.c_str() );
×
836
            }
×
837
            curl_easy_setopt( curl, CURLOPT_HTTPHEADER, chunk );
×
838
          }
839
        }
840

841
        Core::networkManager.auxthreadpool->push(
36✔
842
            [uoexec_w, curl_sp, chunk, flags]()
36✔
843
            {
844
              CURL* curl = curl_sp.get();
18✔
845
              CURLcode res;
846
              std::string readBuffer;
18✔
847
              CurlHeaderData headerData;
18✔
848

849
              if ( flags == HTTPREQUEST_EXTENDED_RESPONSE )
18✔
850
              {
851
                curl_easy_setopt( curl, CURLOPT_HEADERFUNCTION, curlReadHeaderCallback );
5✔
852
                curl_easy_setopt( curl, CURLOPT_HEADERDATA, &headerData );
5✔
853
              }
854

855
              curl_easy_setopt( curl, CURLOPT_WRITEDATA, &readBuffer );
18✔
856

857
              /* Perform the request, res will get the return code */
858
              res = curl_easy_perform( curl );
18✔
859
              if ( chunk != nullptr )
18✔
860
                curl_slist_free_all( chunk );
×
861
              {
862
                Core::PolLock lck;
18✔
863

864
                if ( !uoexec_w.exists() )
18✔
865
                {
866
                  DEBUGLOGLN( "HTTPRequest Script has been destroyed" );
×
867
                  return;
×
868
                }
869
                /* Check for errors */
870
                if ( res != CURLE_OK )
18✔
871
                {
872
                  uoexec_w.get_weakptr()->ValueStack.back().set(
×
873
                      new BObject( new BError( curl_easy_strerror( res ) ) ) );
×
874
                }
875
                else
876
                {
877
                  if ( flags == HTTPREQUEST_EXTENDED_RESPONSE )
18✔
878
                  {
879
                    auto response = std::make_unique<Bscript::BDictionary>();
5✔
880
                    auto headers = std::make_unique<Bscript::BDictionary>();
5✔
881
                    long http_code = 0;
5✔
882

883
                    res = curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &http_code );
5✔
884
                    if ( res == CURLE_OK )
5✔
885
                    {
886
                      response->addMember( new String( "status" ), new BLong( http_code ) );
5✔
887
                    }
888
                    else
889
                    {
890
                      response->addMember( new String( "status" ),
×
891
                                           new BError( curl_easy_strerror( res ) ) );
×
892
                    }
893

894
                    response->addMember( new String( "statusText" ),
10✔
895
                                         new String( headerData.statusText.value_or( "" ) ) );
10✔
896

897
                    response->addMember( new String( "body" ), new String( readBuffer ) );
5✔
898

899
                    for ( auto const& [key, value] : headerData.headers )
12✔
900
                    {
901
                      headers->addMember( new String( key ), new String( value ) );
7✔
902
                    }
903

904
                    response->addMember( new String( "headers" ), headers.release() );
5✔
905

906
                    uoexec_w.get_weakptr()->ValueStack.back().set(
10✔
907
                        new BObject( response.release() ) );
5✔
908
                  }
5✔
909
                  else
910
                  {
911
                    // TODO: no sanitize happens, optional function param iso/utf8 encoding, or
912
                    // parse the header of the http answer?
913
                    uoexec_w.get_weakptr()->ValueStack.back().set(
26✔
914
                        new BObject( new String( readBuffer ) ) );
13✔
915
                  }
916
                }
917

918
                uoexec_w.get_weakptr()->revive();
18✔
919
              }
18✔
920

921
              /* always cleanup */
922
              // curl_easy_cleanup() is performed when the shared pointer deallocates
923
            } );
18✔
924
      }
925
      else
926
      {
927
        return new BError( "curl_easy_init() failed" );
×
928
      }
929
    }
18✔
930
    else
931
    {
932
      return new BError( "Invalid parameter type" );
×
933
    }
934
  }
935

936
  return new BError( "Invalid parameter type" );
18✔
937
}
938

939
// signal_event() takes ownership of the pointer which is passed to it.
940
// Objects must not be touched or deleted after being sent here!
941
// TODO: Find a better way to enforce this in the codebase.
942
bool OSExecutorModule::signal_event( BObjectImp* imp )
1,593✔
943
{
944
  INC_PROFILEVAR( events );
1,593✔
945

946
  if ( blocked_ && ( wait_type == Core::WAIT_TYPE::WAIT_EVENT ) )  // already being waited for
1,593✔
947
  {
948
    /* Now, the tricky part.  The value to return on an error or
949
    completion condition has already been pushed onto the value
950
    stack - so, we need to replace it with the real result.
951
    */
952
    exec.ValueStack.back().set( new BObject( imp ) );
1,057✔
953
    /* This executor will get moved to the run queue at the
954
    next step_scripts(), where blocked is checked looking
955
    for timed out or completed operations. */
956

957
    revive();
1,057✔
958
  }
959
  else  // not being waited for, so queue for later.
960
  {
961
    if ( events_.size() < max_eventqueue_size )
536✔
962
    {
963
      events_.push_back( imp );
535✔
964
    }
965
    else
966
    {
967
      if ( Plib::systemstate.config.discard_old_events )
1✔
968
      {
969
        BObject ob( events_.front() );
×
970
        events_.pop_front();
×
971
        events_.push_back( imp );
×
972
      }
×
973
      else
974
      {
975
        BObject ob( imp );
1✔
976
        if ( Plib::systemstate.config.loglevel >= 11 )
1✔
977
        {
978
          INFO_PRINTLN( "Event queue for {} is full, discarding event.", exec.scriptname() );
×
979
          ExecutorModule* em = exec.findModule( "npc" );
×
980
          if ( em )
×
981
          {
982
            NPCExecutorModule* npcemod = static_cast<NPCExecutorModule*>( em );
×
983
            INFO_PRINTLN( "NPC Serial: {:#x}{}", npcemod->controlled_npc().serial,
×
984
                          npcemod->controlled_npc().pos() );
×
985
          }
986

987
          INFO_PRINTLN( "Event: {}", ob->getStringRep() );
×
988
        }
989
        return false;  // Event-queue is full
1✔
990
      }
1✔
991
    }
992
  }
993

994
  return true;  // Event was successfully sent (perhaps by discarding old events)
1,592✔
995
}
996

997
void OSExecutorModule::SleepFor( u32 nsecs )
13✔
998
{
999
  if ( !nsecs )
13✔
1000
    return;
×
1001
  blocked_ = true;
13✔
1002
  wait_type = Core::WAIT_TYPE::WAIT_SLEEP;
13✔
1003
  sleep_until_clock_ = Core::polclock() + nsecs * Core::POLCLOCKS_PER_SEC;
13✔
1004
}
1005

1006
void OSExecutorModule::SleepForMs( u32 msecs )
13,595,872✔
1007
{
1008
  if ( !msecs )
13,595,872✔
1009
    return;
×
1010
  blocked_ = true;
13,595,872✔
1011
  wait_type = Core::WAIT_TYPE::WAIT_SLEEP;
13,595,872✔
1012
  sleep_until_clock_ = Core::polclock() + msecs * Core::POLCLOCKS_PER_SEC / 1000;
13,595,872✔
1013
  if ( !sleep_until_clock_ )
13,595,872✔
1014
    sleep_until_clock_ = 1;
2✔
1015
}
1016

1017
void OSExecutorModule::suspend()
426✔
1018
{
1019
  blocked_ = true;
426✔
1020
  wait_type = Core::WAIT_TYPE::WAIT_SLEEP;
426✔
1021
  sleep_until_clock_ = 0;  // wait forever
426✔
1022
}
426✔
1023

1024
void OSExecutorModule::revive()
13,597,798✔
1025
{
1026
  blocked_ = false;
13,597,798✔
1027
  if ( in_hold_list_ == Core::HoldListType::TIMEOUT_LIST )
13,597,798✔
1028
  {
1029
    in_hold_list_ = Core::HoldListType::NO_LIST;
13,597,010✔
1030
    Core::scriptScheduler.revive_timeout( &uoexec(), hold_itr_ );
13,597,010✔
1031
  }
1032
  else if ( in_hold_list_ == Core::HoldListType::NOTIMEOUT_LIST )
788✔
1033
  {
1034
    in_hold_list_ = Core::HoldListType::NO_LIST;
786✔
1035
    Core::scriptScheduler.revive_notimeout( &uoexec() );
786✔
1036
  }
1037
  else if ( in_hold_list_ == Core::HoldListType::DEBUGGER_LIST )
2✔
1038
  {
1039
    // stays right where it is.
1040
  }
1041
}
13,597,798✔
1042
bool OSExecutorModule::in_debugger_holdlist() const
107✔
1043
{
1044
  return ( in_hold_list_ == Core::HoldListType::DEBUGGER_LIST );
107✔
1045
}
1046
void OSExecutorModule::revive_debugged()
9✔
1047
{
1048
  in_hold_list_ = Core::HoldListType::NO_LIST;
9✔
1049
  Core::scriptScheduler.revive_debugged( &uoexec() );
9✔
1050
}
9✔
1051

1052
bool OSExecutorModule::critical() const
68,404,684✔
1053
{
1054
  return critical_;
68,404,684✔
1055
}
1056
void OSExecutorModule::critical( bool critical )
×
1057
{
1058
  critical_ = critical;
×
1059
}
×
1060

1061
bool OSExecutorModule::warn_on_runaway() const
25✔
1062
{
1063
  return warn_on_runaway_;
25✔
1064
}
1065
void OSExecutorModule::warn_on_runaway( bool warn_on_runaway )
×
1066
{
1067
  warn_on_runaway_ = warn_on_runaway;
×
1068
}
×
1069

1070
unsigned char OSExecutorModule::priority() const
82,001,625✔
1071
{
1072
  return priority_;
82,001,625✔
1073
}
1074
void OSExecutorModule::priority( unsigned char priority )
14✔
1075
{
1076
  priority_ = priority;
14✔
1077
}
14✔
1078

1079
Core::polclock_t OSExecutorModule::sleep_until_clock() const
217,539,353✔
1080
{
1081
  return sleep_until_clock_;
217,539,353✔
1082
}
1083
void OSExecutorModule::sleep_until_clock( Core::polclock_t sleep_until_clock )
×
1084
{
1085
  sleep_until_clock_ = sleep_until_clock;
×
1086
}
×
1087

1088
Core::TimeoutHandle OSExecutorModule::hold_itr() const
×
1089
{
1090
  return hold_itr_;
×
1091
}
1092
void OSExecutorModule::hold_itr( Core::TimeoutHandle hold_itr )
13,597,016✔
1093
{
1094
  hold_itr_ = hold_itr;
13,597,016✔
1095
}
13,597,016✔
1096

1097
Core::HoldListType OSExecutorModule::in_hold_list() const
13,598,002✔
1098
{
1099
  return in_hold_list_;
13,598,002✔
1100
}
1101
void OSExecutorModule::in_hold_list( Core::HoldListType in_hold_list )
13,597,811✔
1102
{
1103
  in_hold_list_ = in_hold_list;
13,597,811✔
1104
}
13,597,811✔
1105

1106
const int SCRIPTOPT_NO_INTERRUPT = 1;
1107
const int SCRIPTOPT_DEBUG = 2;
1108
const int SCRIPTOPT_NO_RUNAWAY = 3;
1109
const int SCRIPTOPT_CAN_ACCESS_OFFLINE_MOBILES = 4;
1110
const int SCRIPTOPT_AUXSVC_ASSUME_STRING = 5;
1111
const int SCRIPTOPT_SURVIVE_ATTACHED_DISCONNECT = 6;
1112

1113
BObjectImp* OSExecutorModule::mf_Set_Script_Option()
×
1114
{
1115
  int optnum;
1116
  int optval;
1117
  if ( getParam( 0, optnum ) && getParam( 1, optval ) )
×
1118
  {
1119
    int oldval;
1120
    switch ( optnum )
×
1121
    {
1122
    case SCRIPTOPT_NO_INTERRUPT:
×
1123
      oldval = critical_ ? 1 : 0;
×
1124
      critical_ = optval ? true : false;
×
1125
      break;
×
1126
    case SCRIPTOPT_DEBUG:
×
1127
      oldval = exec.getDebugLevel();
×
1128
      if ( optval )
×
1129
        exec.setDebugLevel( Executor::SOURCELINES );
×
1130
      else
1131
        exec.setDebugLevel( Executor::NONE );
×
1132
      break;
×
1133
    case SCRIPTOPT_NO_RUNAWAY:
×
1134
      oldval = warn_on_runaway_ ? 1 : 0;
×
1135
      warn_on_runaway_ = !optval;
×
1136
      break;
×
1137
    case SCRIPTOPT_CAN_ACCESS_OFFLINE_MOBILES:
×
1138
    {
1139
      Core::UOExecutor& uoex = uoexec();
×
1140
      oldval = uoex.can_access_offline_mobiles_ ? 1 : 0;
×
1141
      uoex.can_access_offline_mobiles_ = optval ? true : false;
×
1142
    }
1143
    break;
×
1144
    case SCRIPTOPT_AUXSVC_ASSUME_STRING:
×
1145
    {
1146
      Core::UOExecutor& uoex = uoexec();
×
1147
      oldval = uoex.auxsvc_assume_string ? 1 : 0;
×
1148
      uoex.auxsvc_assume_string = optval ? true : false;
×
1149
    }
1150
    break;
×
1151
    case SCRIPTOPT_SURVIVE_ATTACHED_DISCONNECT:
×
1152
    {
1153
      Core::UOExecutor& uoex = uoexec();
×
1154
      oldval = uoex.survive_attached_disconnect ? 1 : 0;
×
1155
      uoex.survive_attached_disconnect = optval ? true : false;
×
1156
    }
1157
    break;
×
1158
    default:
×
1159
      return new BError( "Unknown Script Option" );
×
1160
    }
1161
    return new BLong( oldval );
×
1162
  }
1163
  else
1164
  {
1165
    return new BError( "Invalid parameter type" );
×
1166
  }
1167
}
1168

1169
BObjectImp* OSExecutorModule::mf_Clear_Event_Queue()  // DAVE
109✔
1170
{
1171
  return ( clear_event_queue() );
109✔
1172
}
1173

1174
namespace
1175
{
1176
struct ScriptDiffData
1177
{
1178
  std::string name;
1179
  u64 instructions;
1180
  u64 pid;
1181
  ScriptDiffData( Core::UOExecutor* ex )
×
1182
      : name( ex->scriptname() ), instructions( ex->instr_cycles ), pid( ex->pid() )
×
1183
  {
1184
  }
×
1185
  ScriptDiffData( Core::UOExecutor* ex, u64 instr ) : ScriptDiffData( ex )
×
1186
  {
1187
    instructions -= instr;
×
1188
    auto uoemod = static_cast<Module::UOExecutorModule*>( ex->findModule( "uo" ) );
×
1189
    if ( uoemod->attached_chr_ != nullptr )
×
1190
      name += " (" + uoemod->attached_chr_->name() + ")";
×
1191
    else if ( uoemod->attached_npc_ != nullptr )
×
1192
      name += " (" + static_cast<Mobile::NPC*>( uoemod->attached_npc_ )->templatename() + ")";
×
1193
    else if ( uoemod->attached_item_.get() )
×
1194
      name += " (" + uoemod->attached_item_->name() + ")";
×
1195
  }
×
1196

1197
  bool operator>( const ScriptDiffData& other ) const { return instructions > other.instructions; }
×
1198
};
1199
struct PerfData
1200
{
1201
  std::unordered_map<u64, ScriptDiffData> data;
1202
  weak_ptr<Core::UOExecutor> uoexec_w;
1203
  size_t max_scripts;
1204
  PerfData( weak_ptr<Core::UOExecutor> weak_ex, size_t max_count )
×
1205
      : data(), uoexec_w( weak_ex ), max_scripts( max_count )
×
1206
  {
1207
  }
×
1208
  static void collect_perf( PerfData* data_ptr )
×
1209
  {
1210
    std::unique_ptr<PerfData> data( data_ptr );
×
1211
    std::vector<ScriptDiffData> res;
×
1212
    if ( !data->uoexec_w.exists() )
×
1213
    {
1214
      DEBUGLOGLN( "PerformanceMeasure Script has been destroyed" );
×
1215
      return;
×
1216
    }
1217
    double sum_instr( 0 );
×
1218
    const auto& runlist = Core::scriptScheduler.getRunlist();
×
1219
    const auto& ranlist = Core::scriptScheduler.getRanlist();
×
1220
    const auto& holdlist = Core::scriptScheduler.getHoldlist();
×
1221
    const auto& notimeoutholdlist = Core::scriptScheduler.getNoTimeoutHoldlist();
×
1222
    auto collect = [&]( Core::UOExecutor* scr )
×
1223
    {
1224
      auto itr = data->data.find( scr->pid() );
×
1225
      if ( itr == data->data.end() )
×
1226
        return;
×
1227
      res.emplace_back( scr, itr->second.instructions );
×
1228
      sum_instr += res.back().instructions;
×
1229
    };
×
1230
    for ( const auto& scr : runlist )
×
1231
      collect( scr );
×
1232
    for ( const auto& scr : ranlist )
×
1233
      collect( scr );
×
1234
    for ( const auto& scr : holdlist )
×
1235
      collect( scr.second );
×
1236
    for ( const auto& scr : notimeoutholdlist )
×
1237
      collect( scr );
×
1238
    std::sort( res.begin(), res.end(), std::greater<ScriptDiffData>() );
×
1239

1240
    std::unique_ptr<ObjArray> arr( new ObjArray );
×
1241
    for ( size_t i = 0; i < res.size() && i < data->max_scripts; ++i )
×
1242
    {
1243
      std::unique_ptr<BStruct> elem( new BStruct );
×
1244
      elem->addMember( "name", new String( res[i].name ) );
×
1245
      elem->addMember( "instructions", new Double( static_cast<double>( res[i].instructions ) ) );
×
1246
      elem->addMember( "pid", new BLong( static_cast<int>( res[i].pid ) ) );
×
1247
      elem->addMember( "percent", new Double( res[i].instructions / sum_instr * 100.0 ) );
×
1248
      arr->addElement( elem.release() );
×
1249
    }
×
1250
    std::unique_ptr<BStruct> result( new BStruct );
×
1251
    result->addMember( "scripts", arr.release() );
×
1252
    result->addMember( "total_number_observed", new BLong( static_cast<int>( res.size() ) ) );
×
1253
    result->addMember( "total_instructions", new Double( sum_instr ) );
×
1254
    data->uoexec_w.get_weakptr()->ValueStack.back().set( new BObject( result.release() ) );
×
1255

1256
    data->uoexec_w.get_weakptr()->revive();
×
1257
  }
×
1258
};
1259

1260
}  // namespace
1261

1262
BObjectImp* OSExecutorModule::mf_PerformanceMeasure()
×
1263
{
1264
  int second_delta, max_scripts;
1265
  if ( !getParam( 0, second_delta ) || !getParam( 1, max_scripts ) )
×
1266
    return new BError( "Invalid parameter type" );
×
1267

1268
  auto& this_uoexec = uoexec();
×
1269

1270
  if ( !this_uoexec.suspend() )
×
1271
  {
1272
    DEBUGLOGLN(
×
1273
        "Script Error in '{}' PC={}: \n"
1274
        "\tThe execution of this script can't be blocked!",
1275
        this_uoexec.scriptname(), this_uoexec.PC );
×
1276
    return new Bscript::BError( "Script can't be blocked" );
×
1277
  }
1278

1279
  const auto& runlist = Core::scriptScheduler.getRunlist();
×
1280
  const auto& ranlist = Core::scriptScheduler.getRanlist();
×
1281
  const auto& holdlist = Core::scriptScheduler.getHoldlist();
×
1282
  const auto& notimeoutholdlist = Core::scriptScheduler.getNoTimeoutHoldlist();
×
1283

1284
  std::unique_ptr<PerfData> perf( new PerfData( this_uoexec.weakptr, max_scripts ) );
×
1285
  for ( const auto& scr : runlist )
×
1286
    perf->data.insert( std::make_pair( scr->pid(), ScriptDiffData( scr ) ) );
×
1287
  for ( const auto& scr : ranlist )
×
1288
    perf->data.insert( std::make_pair( scr->pid(), ScriptDiffData( scr ) ) );
×
1289
  for ( const auto& scr : holdlist )
×
1290
    perf->data.insert( std::make_pair( scr.second->pid(), ScriptDiffData( scr.second ) ) );
×
1291
  for ( const auto& scr : notimeoutholdlist )
×
1292
    perf->data.insert( std::make_pair( scr->pid(), ScriptDiffData( scr ) ) );
×
1293

1294
  new Core::OneShotTaskInst<PerfData*>( nullptr,
×
1295
                                        Core::polclock() + second_delta * Core::POLCLOCKS_PER_SEC,
×
1296
                                        PerfData::collect_perf, perf.release() );
×
1297

1298
  return new BLong( 0 );  // dummy
×
1299
}
×
1300

1301
BObjectImp* OSExecutorModule::mf_LoadExportedScript()
176✔
1302
{
1303
  Core::UOExecutor& this_uoexec = uoexec();
176✔
1304
  if ( this_uoexec.pChild == nullptr )
176✔
1305
  {
1306
    const String* scriptname_str;
1307
    ObjArray* arr;
1308
    if ( !exec.getStringParam( 0, scriptname_str ) || !getObjArrayParam( 1, arr ) )
89✔
1309
      return new BError( "Invalid parameter type" );
×
1310
    Core::ScriptDef sd;
89✔
1311
    if ( !sd.config_nodie( scriptname_str->value(), exec.prog()->pkg, "scripts/" ) )
89✔
1312
      return new BError( "Error in script name" );
×
1313
    if ( !sd.exists() )
89✔
1314
      return new BError( "Script " + sd.name() + " does not exist." );
×
1315
    ref_ptr<Bscript::EScriptProgram> program = find_script2( sd );
89✔
1316
    if ( program.get() == nullptr )
89✔
1317
    {
1318
      ERROR_PRINTLN( "Error reading script {}", sd.name() );
×
1319
      return new Bscript::BError( "Unable to read script" );
×
1320
    }
1321
    Core::UOExecutor* uoexec = Core::create_script_executor();
89✔
1322
    uoexec->keep_alive( true );
89✔
1323
    Core::add_common_exmods( *uoexec );
89✔
1324
    uoexec->addModule( new Module::UOExecutorModule( *uoexec ) );
89✔
1325

1326
    uoexec->setProgram( program.get() );
89✔
1327
    if ( program->haveProgram )
89✔
1328
    {
1329
      for ( int i = (int)( arr->ref_arr.size() ) - 1; i >= 0; --i )
91✔
1330
        uoexec->pushArg( arr->ref_arr[i].get()->impptr() );
2✔
1331
    }
1332
    if ( this_uoexec.critical() )  // execute directy
89✔
1333
    {
1334
      uoexec->exec();
2✔
1335
      BObjectImp* ret;
1336
      if ( uoexec->error() )
2✔
1337
        ret = new BLong( 0 );
×
1338
      else if ( uoexec->ValueStack.empty() )
2✔
1339
        ret = new BLong( 1 );
×
1340
      else
1341
        ret = uoexec->ValueStack.back().get()->impptr()->copy();
2✔
1342
      uoexec->set_running_to_completion( false );
2✔
1343
      uoexec->suspend();
2✔
1344
      Core::scriptScheduler.schedule( uoexec );
2✔
1345

1346
      auto array = std::make_unique<Bscript::ObjArray>();
2✔
1347
      array->addElement( new Core::ExportScriptObjImp( uoexec ) );
2✔
1348
      array->addElement( ret );
2✔
1349

1350
      return array.release();
2✔
1351
    }
2✔
1352
    else
1353
    {
1354
      Core::scriptScheduler.schedule( uoexec );
87✔
1355

1356
      uoexec->pParent = &this_uoexec;
87✔
1357
      this_uoexec.pChild = uoexec;
87✔
1358

1359
      this_uoexec.PC--;
87✔
1360
      // need to fill the valuestack equal to param count (-the return value)
1361
      this_uoexec.ValueStack.push_back( BObjectRef( new BObject( UninitObject::create() ) ) );
87✔
1362
      suspend();
87✔
1363

1364
      return UninitObject::create();
87✔
1365
    }
1366
  }
89✔
1367
  else  // reentry
1368
  {
1369
    BObjectImp* ret;
1370
    if ( this_uoexec.pChild->error() )
87✔
1371
      ret = new BLong( 0 );
×
1372
    else if ( this_uoexec.pChild->ValueStack.empty() )
87✔
1373
      ret = new BLong( 1 );
7✔
1374
    else
1375
    {
1376
      ret = this_uoexec.pChild->ValueStack.back().get()->impptr()->copy();
80✔
1377
      this_uoexec.pChild->ValueStack.pop_back();
80✔
1378
    }
1379
    auto array = std::make_unique<Bscript::ObjArray>();
87✔
1380
    array->addElement( new Core::ExportScriptObjImp( this_uoexec.pChild ) );
87✔
1381
    array->addElement( ret );
87✔
1382

1383
    this_uoexec.pChild->pParent = nullptr;
87✔
1384
    this_uoexec.pChild = nullptr;
87✔
1385

1386
    return array.release();
87✔
1387
  }
87✔
1388
}
1389

1390
BObjectImp* OSExecutorModule::mf_GetEnvironmentVariable()
52✔
1391
{
1392
  const auto& allowed_vars = Plib::systemstate.config.allowed_environmentvariables_access;
52✔
1393
  if ( allowed_vars.empty() )
52✔
1394
    return new BError( "Environment Variable access disallowed due to pol.cfg setting" );
×
1395
  const String* env_name;
1396
  if ( !exec.getStringParam( 0, env_name ) )
52✔
1397
    return new BError( "Invalid parameter type" );
×
1398

1399
  bool all_allowed = allowed_vars.size() == 1 && allowed_vars[0] == "*";
52✔
1400

1401
  if ( env_name->length() == 0 )
52✔
1402
  {
1403
    auto envs = std::make_unique<Bscript::BDictionary>();
1✔
1404
    for ( char** current = environ_vars; *current; ++current )
119✔
1405
    {
1406
      std::string_view env( *current );
118✔
1407
      size_t pos = env.find_first_of( "=" );
118✔
1408
      if ( pos == std::string_view::npos )
118✔
1409
        continue;
×
1410
      auto key = env.substr( 0, pos );
118✔
1411
      auto key_lowered = Clib::strlowerASCII( std::string{ key } );
118✔
1412
      auto val = env.substr( pos + 1 );
118✔
1413

1414
      if ( all_allowed || std::find( allowed_vars.begin(), allowed_vars.end(), key_lowered ) !=
236✔
1415
                              allowed_vars.end() )
236✔
1416
      {
1417
        envs->addMember( new String( key, String::Tainted::YES ),
10✔
1418
                         new String( val, String::Tainted::YES ) );
5✔
1419
      }
1420
    }
118✔
1421
    return envs.release();
1✔
1422
  }
1✔
1423
  else
1424
  {
1425
    if ( !all_allowed )
51✔
1426
    {
1427
      auto name_lowered = Clib::strlowerASCII( env_name->value() );
51✔
1428
      if ( std::find( allowed_vars.begin(), allowed_vars.end(), name_lowered ) ==
51✔
1429
           allowed_vars.end() )
102✔
1430
        return new BError( "Environment Variable access disallowed due to pol.cfg setting" );
2✔
1431
    }
51✔
1432

1433
    const char* env_val = std::getenv( env_name->data() );
49✔
1434
    if ( !env_val )
49✔
1435
      return new BError( "Environment variable not found" );
3✔
1436
    return new String( env_val, String::Tainted::YES );
46✔
1437
  }
1438
}
1439

1440
BObjectImp* OSExecutorModule::mf_SendEmail()
9✔
1441
{
1442
  if ( Core::settingsManager.email_cfg.url.empty() )
9✔
NEW
1443
    return new BError( "Email settings are not configured" );
×
1444

1445

1446
  const String *from, *subject, *body, *contentType;
1447
  BObjectImp *recipient, *bcc;
1448

1449
  if ( !( getStringParam( 0, from ) && getParamImp( 1, recipient ) &&
17✔
1450
          getStringParam( 2, subject ) && getStringParam( 3, body ) && getParamImp( 4, bcc ) &&
8✔
1451
          getStringParam( 5, contentType ) ) )
8✔
1452
  {
1453
    return new BError( "Invalid parameter type" );
1✔
1454
  }
1455

1456
  std::shared_ptr<CURL> curl_sp( curl_easy_init(), curl_easy_cleanup );
8✔
1457
  CURL* curl = curl_sp.get();
8✔
1458
  if ( !curl )
8✔
1459
  {
NEW
1460
    return new BError( "curl_easy_init() failed" );
×
1461
  }
1462

1463
  auto headers_slist = std::make_shared<CurlStringList>();
8✔
1464
  auto recipients_slist = std::make_shared<CurlStringList>();
8✔
1465
  std::string to_header_value;
8✔
1466

1467
  auto extract_email_address = []( const String* email_string )
18✔
1468
  {
1469
    const auto& input = email_string->value();
18✔
1470
    auto start = input.find( '<' );
18✔
1471
    auto end = input.find( '>' );
18✔
1472

1473
    if ( start != std::string::npos && end != std::string::npos && end > start + 1 )
18✔
1474
    {
1475
      return input.substr( start + 1, end - start - 1 );
3✔
1476
    }
1477

1478
    // No angle brackets: assume the whole input is the email address
1479
    return input;
15✔
1480
  };
1481

1482
  auto handle_recipients = [&]( BObjectImp* string_or_array, bool is_bcc ) -> BObjectImp*
16✔
1483
  {
1484
    // The `to` or `bcc` can be falsey to specify no recipient for this field.
1485
    if ( !string_or_array->isTrue() )
16✔
1486
      return nullptr;
8✔
1487

1488
    if ( auto* recipient_string = impptrIf<String>( string_or_array ) )
8✔
1489
    {
1490
      if ( !is_bcc )
5✔
1491
      {
1492
        if ( !to_header_value.empty() )
4✔
NEW
1493
          to_header_value += ", ";
×
1494

1495
        to_header_value += recipient_string->data();
4✔
1496
      }
1497

1498
      recipients_slist->add( extract_email_address( recipient_string ) );
5✔
1499
    }
1500
    else if ( auto* recipient_array = impptrIf<ObjArray>( string_or_array ) )
3✔
1501
    {
1502
      for ( const auto& elem : recipient_array->ref_arr )
8✔
1503
      {
1504
        if ( auto* this_recipient = elem->impptr_if<String>() )
6✔
1505
        {
1506
          // Bcc is not added to the header, and only included in CURLOPT_MAIL_RCPT
1507
          if ( !is_bcc )
5✔
1508
          {
1509
            if ( !to_header_value.empty() )
2✔
1510
              to_header_value += ", ";
1✔
1511

1512
            to_header_value += this_recipient->data();
2✔
1513
          }
1514

1515
          recipients_slist->add( extract_email_address( this_recipient ) );
5✔
1516
        }
1517
        else
1518
        {
1519
          return new BError( "Invalid recipient type in array" );
1✔
1520
        }
1521
      }
1522
    }
1523
    else
1524
    {
NEW
1525
      return new BError( "Invalid recipient type" );
×
1526
    }
1527

1528
    return nullptr;
7✔
1529
  };
8✔
1530

1531
  if ( auto res = curl_easy_setopt( curl, CURLOPT_URL, Core::settingsManager.email_cfg.url.data() );
8✔
1532
       res != CURLE_OK )
1533
  {
NEW
1534
    return new BError( "curl_easy_setopt(CURLOPT_URL) failed: " +
×
NEW
1535
                       std::string( curl_easy_strerror( res ) ) );
×
1536
  }
1537

1538
  if ( !Core::settingsManager.email_cfg.username.empty() )
8✔
1539
  {
1540
    if ( auto res = curl_easy_setopt( curl, CURLOPT_USERNAME,
8✔
1541
                                      Core::settingsManager.email_cfg.username.c_str() );
1542
         res != CURLE_OK )
1543
    {
NEW
1544
      return new BError( "curl_easy_setopt(CURLOPT_USERNAME) failed: " +
×
NEW
1545
                         std::string( curl_easy_strerror( res ) ) );
×
1546
    }
1547
  }
1548

1549
  if ( !Core::settingsManager.email_cfg.password.empty() )
8✔
1550
  {
1551
    if ( auto res = curl_easy_setopt( curl, CURLOPT_PASSWORD,
8✔
1552
                                      Core::settingsManager.email_cfg.password.c_str() );
1553
         res != CURLE_OK )
1554
    {
NEW
1555
      return new BError( "curl_easy_setopt(CURLOPT_PASSWORD) failed: " +
×
NEW
1556
                         std::string( curl_easy_strerror( res ) ) );
×
1557
    }
1558
  }
1559

1560
  if ( Core::settingsManager.email_cfg.use_tls )
8✔
1561
  {
1562
    if ( auto res = curl_easy_setopt( curl, CURLOPT_USE_SSL, CURLUSESSL_ALL ); res != CURLE_OK )
8✔
1563
    {
NEW
1564
      return new BError( "curl_easy_setopt(CURLOPT_USE_SSL) failed: " +
×
NEW
1565
                         std::string( curl_easy_strerror( res ) ) );
×
1566
    }
1567
  }
1568

1569
  if ( !Core::settingsManager.email_cfg.ca_file.empty() )
8✔
1570
  {
1571
    if ( auto res = curl_easy_setopt( curl, CURLOPT_CAINFO,
8✔
1572
                                      Core::settingsManager.email_cfg.ca_file.c_str() );
1573
         res != CURLE_OK )
1574
    {
NEW
1575
      return new BError( "curl_easy_setopt(CURLOPT_CAINFO) failed: " +
×
NEW
1576
                         std::string( curl_easy_strerror( res ) ) );
×
1577
    }
1578
  }
1579

1580
  if ( auto res =
8✔
1581
           curl_easy_setopt( curl, CURLOPT_MAIL_FROM, extract_email_address( from ).c_str() );
8✔
1582
       res != CURLE_OK )
1583
  {
NEW
1584
    return new BError( "curl_easy_setopt(CURLOPT_MAIL_FROM) failed: " +
×
NEW
1585
                       std::string( curl_easy_strerror( res ) ) );
×
1586
  }
1587

1588
  if ( auto* recipient_to_error = handle_recipients( recipient, false ) )  // Handle To recipients
8✔
1589
  {
NEW
1590
    return recipient_to_error;
×
1591
  }
1592

1593
  if ( auto* recipient_bcc_error = handle_recipients( bcc, true ) )  // Handle Bcc recipients
8✔
1594
  {
1595
    return recipient_bcc_error;
1✔
1596
  }
1597

1598
  if ( auto res = curl_easy_setopt( curl, CURLOPT_MAIL_RCPT, recipients_slist->get() );
7✔
1599
       res != CURLE_OK )
1600
  {
NEW
1601
    return new BError( "curl_easy_setopt(CURLOPT_MAIL_RCPT) failed: " +
×
NEW
1602
                       std::string( curl_easy_strerror( res ) ) );
×
1603
  }
1604

1605
  std::string date_header_value = fmt::format(
1606
      "{:%a, %d %b %Y %H:%M:%S %z}",
1607
      std::chrono::time_point_cast<std::chrono::seconds>( std::chrono::system_clock::now() ) );
7✔
1608

1609
  std::vector<std::string> email_headers{ "Date: " + date_header_value,  //
1610
                                          "To: " + to_header_value,      //
1611
                                          "From: " + from->value(),      //
7✔
1612
                                          "Subject: " + subject->value() };
35✔
1613

1614
  for ( const auto& header : email_headers )
35✔
1615
    headers_slist->add( header );
28✔
1616

1617
  if ( auto res = curl_easy_setopt( curl, CURLOPT_HTTPHEADER, headers_slist->get() );
7✔
1618
       res != CURLE_OK )
1619
  {
NEW
1620
    return new BError( "curl_easy_setopt(CURLOPT_HTTPHEADER) failed: " +
×
NEW
1621
                       std::string( curl_easy_strerror( res ) ) );
×
1622
  }
1623

1624
  std::shared_ptr<curl_mime> mime( curl_mime_init( curl ), curl_mime_free );
7✔
1625

1626
  curl_mimepart* part = curl_mime_addpart( mime.get() );
7✔
1627

1628
  if ( !part )
7✔
1629
  {
NEW
1630
    return new BError( "curl_mime_addpart() failed" );
×
1631
  }
1632

1633
  // `curl_mime_data` copies the string into its internal structure, so using this local is
1634
  // okay. Ref: https://github.com/curl/curl/blob/curl-8_2_1/lib/mime.c#L1380-L1390
1635
  curl_mime_data( part, body->data(), CURL_ZERO_TERMINATED );
7✔
1636

1637
  // `curl_mime_type` copies the string into its internal structure, so using this local is
1638
  // okay. Ref: https://github.com/curl/curl/blob/curl-8_2_1/lib/mime.c#L1461
1639
  curl_mime_type( part, contentType->data() );
7✔
1640

1641
  if ( auto res = curl_easy_setopt( curl, CURLOPT_MIMEPOST, mime.get() ); res != CURLE_OK )
7✔
1642
  {
NEW
1643
    return new BError( "curl_easy_setopt(CURLOPT_MIMEPOST) failed: " +
×
NEW
1644
                       std::string( curl_easy_strerror( res ) ) );
×
1645
  }
1646

1647
  Core::UOExecutor& this_uoexec = uoexec();
7✔
1648

1649
  if ( !this_uoexec.suspend() )
7✔
1650
  {
NEW
1651
    DEBUGLOGLN(
×
1652
        "Script Error in '{}' PC={}: \n"
1653
        "\tThe execution of this script can't be blocked!",
NEW
1654
        this_uoexec.scriptname(), this_uoexec.PC );
×
NEW
1655
    return new Bscript::BError( "Script can't be blocked" );
×
1656
  }
1657

1658
  weak_ptr<Core::UOExecutor> uoexec_w = this_uoexec.weakptr;
7✔
1659

1660
  Core::networkManager.auxthreadpool->push(
14✔
1661
      [uoexec_w, curl_sp, recipients_slist, headers_slist, mime]()
14✔
1662
      {
1663
        CURL* curl = curl_sp.get();
7✔
1664

1665
        auto res = curl_easy_perform( curl );
7✔
1666

1667
        {
1668
          Core::PolLock lck;
7✔
1669

1670
          if ( !uoexec_w.exists() )
7✔
1671
          {
NEW
1672
            DEBUGLOGLN( "SendEmail Script has been destroyed" );
×
NEW
1673
            return;
×
1674
          }
1675
          if ( res != CURLE_OK )
7✔
1676
            uoexec_w.get_weakptr()->ValueStack.back().set(
2✔
1677
                new BObject( new BError( curl_easy_strerror( res ) ) ) );
1✔
1678
          else
1679
            uoexec_w.get_weakptr()->ValueStack.back().set( new BObject( new BLong( 1 ) ) );
6✔
1680

1681
          uoexec_w.get_weakptr()->revive();
7✔
1682
        }
7✔
1683
      }
1684

1685
      /* always cleanup */
1686
      // curl_easy_cleanup(), curl_slist_free_all(), curl_mime_free() are
1687
      // performed when the shared pointer deallocates
1688
  );
1689

1690
  return new BLong( 0 );
7✔
1691
}
15✔
1692

1693

1694
size_t OSExecutorModule::sizeEstimate() const
14✔
1695
{
1696
  size_t size = sizeof( *this );
14✔
1697
  for ( const auto& obj : events_ )
14✔
1698
  {
1699
    if ( obj != nullptr )
×
1700
      size += obj->sizeEstimate();
×
1701
  }
1702
  size += Clib::memsize( events_ );
14✔
1703
  return size;
14✔
1704
}
1705
}  // namespace Module
1706
}  // namespace Pol
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc