• 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

46.09
/pol-core/pol/module/polsystemmod.cpp
1
/** @file
2
 *
3
 * @par History
4
 * - 2005/11/26 Shinigami: changed "strcmp" into "stricmp" to suppress Script Errors
5
 * - 2006/10/07 Shinigami: GCC 3.4.x fix - added "template<>" to TmplExecutorModule
6
 * - 2007/04/08 MuadDib:   Changed Realms() to get BObject IMP, and check for string
7
 *                         explicitly.
8
 * - 2009/11/30 Turley:    added MD5Encrypt(string)
9
 * - 2010/03/28 Shinigami: Transmit Pointer as Pointer and not Int as Pointer within
10
 * decay_thread_shadow
11
 */
12

13

14
#include "polsystemmod.h"
15
#include <ctime>
16
#include <fstream>
17
#include <string>
18

19
#include "bscript/berror.h"
20
#include "bscript/bobject.h"
21
#include "bscript/dict.h"
22
#include "bscript/impstr.h"
23
#include "clib/clib.h"
24
#include "clib/clib_MD5.h"
25
#include "clib/fileutil.h"
26
#include "clib/rawtypes.h"
27
#include "clib/strutil.h"
28
#include "clib/threadhelp.h"
29
#include "plib/pkg.h"
30
#include "plib/systemstate.h"
31

32

33
#include "realms/realm.h"
34
#include "realms/realms.h"
35

36
#include "globals/settings.h"
37
#include "globals/uvars.h"
38
#include "item/item.h"
39
#include "item/itemdesc.h"
40

41
#include "cmdlevel.h"
42
#include "core.h"
43
#include "decay.h"
44
#include "listenpt.h"
45
#include "packetscrobj.h"
46
#include "proplist.h"
47
#include "tooltips.h"
48
#include "uobject.h"
49
#include "uoexec.h"
50

51
#include <module_defs/polsys.h>
52

53
namespace Pol
54
{
55
namespace Core
56
{
57
void reload_configuration();
58
}  // namespace Core
59
namespace Module
60
{
61
using namespace Bscript;
62

63
BApplicObjType packageobjimp_type;
64

65
PackageObjImp::PackageObjImp( const PackagePtrHolder& other )
38✔
66
    : PackageObjImpBase( &packageobjimp_type, other )
38✔
67
{
68
}
38✔
69
const char* PackageObjImp::typeOf() const
×
70
{
71
  return "Package";
×
72
}
73
u8 PackageObjImp::typeOfInt() const
×
74
{
75
  return OTPackage;
×
76
}
77
BObjectImp* PackageObjImp::copy() const
×
78
{
79
  return new PackageObjImp( obj_ );
×
80
}
81
BObjectImp* PackageObjImp::call_polmethod( const char* /*methodname*/, Core::UOExecutor& /*ex*/ )
×
82
{
83
  return new BError( "undefined method" );
×
84
}
85
BObjectRef PackageObjImp::get_member( const char* membername )
140✔
86
{
87
  if ( stricmp( membername, "name" ) == 0 )
140✔
88
  {
89
    return BObjectRef( new String( value()->name() ) );
68✔
90
  }
91
  else if ( stricmp( membername, "version" ) == 0 )
72✔
92
  {
93
    return BObjectRef( new String( value()->version() ) );
×
94
  }
95
  else if ( stricmp( membername, "supports_http" ) == 0 )
72✔
96
  {
97
    const Plib::Package* pkg = value().Ptr();
×
98
    return BObjectRef( new BLong( Clib::FileExists( pkg->dir() + "www" ) ) );
×
99
  }
100
  else if ( stricmp( membername, "npcdesc" ) == 0 )
72✔
101
  {
102
    const Plib::Package* pkg = value().Ptr();
×
103
    std::string filepath = Plib::GetPackageCfgPath( pkg, "npcdesc.cfg" );
×
104
    return BObjectRef( new BLong( Clib::FileExists( filepath ) ) );
×
105
  }
×
106
  else if ( stricmp( membername, "dir" ) == 0 )
72✔
107
  {
108
    const Plib::Package* pkg = value().Ptr();
38✔
109
    return BObjectRef( new String( pkg->dir() ) );
38✔
110
  }
111
  else if ( stricmp( membername, "desc" ) == 0 )
34✔
112
  {
113
    return BObjectRef( new String( value()->desc() ) );
68✔
114
  }
115
  else
116
  {
117
    return BObjectRef( new BError( "Undefined member" ) );
×
118
  }
119
}
120

121
PolSystemExecutorModule::PolSystemExecutorModule( Bscript::Executor& exec )
369✔
122
    : Bscript::TmplExecutorModule<PolSystemExecutorModule, Core::PolModule>( exec )
369✔
123
{
124
}
369✔
125

126
BObjectImp* PolSystemExecutorModule::mf_IncRevision( /* uobject */ )
×
127
{
128
  Core::UObject* uobj;
129
  if ( getUObjectParam( 0, uobj ) )
×
130
  {
131
    uobj->increv();
×
132
    send_object_cache_to_inrange( uobj );
×
133
    return new BLong( 1 );
×
134
  }
135
  else
136
  {
137
    return new BError( "Invalid parameter type" );
×
138
  }
139
}
140

141
BObjectImp* PolSystemExecutorModule::mf_GetCmdLevelName()
×
142
{
143
  int cmdlevel_num;
144
  const String* cmdlevel_alias;
145

146
  if ( exec.numParams() != 1 )
×
147
    return new BError( "Expected 1 parameter." );
×
148
  else if ( getParam( 0, cmdlevel_num ) )
×
149
  {
150
    if ( cmdlevel_num >= static_cast<int>( Core::gamestate.cmdlevels.size() ) )
×
151
      cmdlevel_num = static_cast<int>( Core::gamestate.cmdlevels.size() - 1 );
×
152

153
    return new String( Core::gamestate.cmdlevels[cmdlevel_num].name );
×
154
  }
155
  else if ( getStringParam( 0, cmdlevel_alias ) )
×
156
  {
157
    Core::CmdLevel* cmdlevel = Core::FindCmdLevelByAlias( cmdlevel_alias->data() );
×
158
    if ( cmdlevel == nullptr )
×
159
      return new BError( "Could not find a command level with that alias." );
×
160
    else
161
      return new String( cmdlevel->name );
×
162
  }
163
  else
164
    return new BError( "Invalid parameter type." );
×
165
}
166

167
BObjectImp* PolSystemExecutorModule::mf_GetCmdLevelNumber()
×
168
{
169
  const String* cmdlvlname;
170
  if ( !getStringParam( 0, cmdlvlname ) )
×
171
    return new BError( "Invalid parameter type." );
×
172

173
  Core::CmdLevel* cmdlevel_search = Core::find_cmdlevel( cmdlvlname->data() );
×
174
  if ( cmdlevel_search == nullptr )
×
175
    return new BError( "Could not find a command level with that name." );
×
176

177
  return new BLong( cmdlevel_search->cmdlevel );
×
178
}
179

180
BObjectImp* PolSystemExecutorModule::mf_Packages()
2✔
181
{
182
  std::unique_ptr<ObjArray> arr( new ObjArray );
2✔
183
  for ( unsigned i = 0; i < Plib::systemstate.packages.size(); ++i )
40✔
184
  {
185
    PackageObjImp* imp = new PackageObjImp( PackagePtrHolder( Plib::systemstate.packages[i] ) );
38✔
186
    arr->addElement( imp );
38✔
187
  }
188
  return arr.release();
4✔
189
}
2✔
190

191
BObjectImp* PolSystemExecutorModule::mf_GetPackageByName()
×
192
{
193
  const String* pkgname;
194
  if ( !getStringParam( 0, pkgname ) )
×
195
    return new BError( "Invalid parameter type." );
×
196

197
  // pkgname->toLower();
198
  Plib::Package* pkg = Plib::find_package( pkgname->value() );
×
199
  if ( !pkg )
×
200
    return new BError( "No package found by that name." );
×
201
  else
202
    return new PackageObjImp( PackagePtrHolder( pkg ) );
×
203
}
204

205
BObjectImp* PolSystemExecutorModule::mf_ListTextCommands()
1✔
206
{
207
  std::unique_ptr<BDictionary> pkg_list( new BDictionary );
1✔
208

209
  int max_cmdlevel;
210
  if ( exec.numParams() < 1 || !getParam( 0, max_cmdlevel ) )
1✔
211
    max_cmdlevel = -1;
×
212

213
  // Sets up text commands not in a package.
214
  {
215
    auto cmd_lvl_list = Core::ListAllCommandsInPackage( nullptr, max_cmdlevel );
1✔
216
    if ( cmd_lvl_list->contents().size() > 0 )
1✔
217
      pkg_list->addMember( new String( "" ), cmd_lvl_list.release() );
×
218
  }
1✔
219

220
  // Sets up packaged text commands.
221
  for ( Plib::Packages::iterator itr = Plib::systemstate.packages.begin();
1✔
222
        itr != Plib::systemstate.packages.end(); ++itr )
20✔
223
  {
224
    Plib::Package* pkg = ( *itr );
19✔
225
    auto cmd_lvl_list = Core::ListAllCommandsInPackage( pkg, max_cmdlevel );
19✔
226
    if ( cmd_lvl_list->contents().size() > 0 )
19✔
227
      pkg_list->addMember( new String( pkg->name().c_str() ), cmd_lvl_list.release() );
1✔
228
  }
19✔
229

230
  return pkg_list.release();
2✔
231
}
1✔
232

233
BObjectImp* PolSystemExecutorModule::mf_ReloadConfiguration()
36✔
234
{
235
  try
236
  {
237
    Core::reload_configuration();
36✔
238
  }
239
  catch ( const std::exception& ex )
2✔
240
  {
241
    return new BError( ex.what() );
2✔
242
  }
2✔
NEW
243
  catch ( ... )
×
244
  {
NEW
245
    return new BError( "Configuration file error" );
×
NEW
246
  }
×
247

248
  return new BLong( 1 );
34✔
249
}
250

251
BObjectImp* PolSystemExecutorModule::mf_ReadMillisecondClock()
758✔
252
{
253
  return new Double( static_cast<double>( Core::polclock_t_to_ms( Core::polclock() ) ) );
758✔
254
}
255

256
BObjectImp* PolSystemExecutorModule::mf_ListenPoints()
×
257
{
258
  return Core::ListenPoint::GetListenPoints();
×
259
}
260

261
BStruct* SetupRealmDetails( Realms::Realm* realm )
1✔
262
{
263
  std::unique_ptr<BStruct> details( new BStruct() );
1✔
264
  details->addMember( "width", new BLong( realm->width() ) );
1✔
265
  details->addMember( "height", new BLong( realm->height() ) );
1✔
266
  details->addMember( "season", new BLong( realm->season() ) );
1✔
267
  details->addMember( "mapid", new BLong( realm->getUOMapID() ) );
1✔
268
  details->addMember( "toplevel_item_count", new BLong( realm->toplevel_item_count() ) );
1✔
269
  details->addMember( "mobile_count", new BLong( realm->mobile_count() ) );
1✔
270
  details->addMember( "offline_mobs_count", new BLong( realm->offline_mobile_count() ) );
1✔
271
  details->addMember( "multi_count", new BLong( realm->multi_count() ) );
1✔
272

273
  return details.release();
2✔
274
}
1✔
275

276
BObjectImp* PolSystemExecutorModule::mf_Realms( /* realm_name:="" */ )
1✔
277
{
278
  const String* realm_name;
279
  //  getStringParam(0, realm_name);
280
  BObjectImp* imp = getParamImp( 0 );
1✔
281
  if ( imp->isa( BObjectImp::OTString ) )
1✔
282
  {
283
    realm_name = static_cast<const String*>( imp );
1✔
284
  }
285
  else
286
  {
287
    return new BError( std::string( "Parameter must be a String or empty, got " ) +
×
288
                       BObjectImp::typestr( imp->type() ) );
×
289
  }
290

291
  if ( realm_name->length() > 0 )
1✔
292
  {
293
    Realms::Realm* realm = Core::find_realm( realm_name->value() );
1✔
294
    if ( !realm )
1✔
295
      return new BError( "Realm not found." );
×
296
    else
297
      return SetupRealmDetails( realm );
1✔
298
  }
299
  else
300
  {
301
    BDictionary* dict = new BDictionary;
×
302
    std::vector<Realms::Realm*>::iterator itr;
×
303
    for ( itr = Core::gamestate.Realms.begin(); itr != Core::gamestate.Realms.end(); ++itr )
×
304
    {
305
      dict->addMember( ( *itr )->name().c_str(), SetupRealmDetails( *itr ) );
×
306
    }
307

308
    return dict;
×
309
  }
310
}
311

312
BObjectImp* PolSystemExecutorModule::mf_SetSysTrayPopupText()
×
313
{
314
#ifdef _WIN32
315
  const char* text = exec.paramAsString( 0 );
316

317
  Core::CoreSetSysTrayToolTip( text, Core::ToolTipPriorityScript );
318
#endif
319
  return new BLong( 1 );
×
320
}
321

322
BObjectImp* PolSystemExecutorModule::mf_GetItemDescriptor()
8✔
323
{
324
  unsigned int objtype;
325
  if ( getObjtypeParam( 0, objtype ) )
8✔
326
  {
327
    const Items::ItemDesc& id = Items::find_itemdesc( objtype );
8✔
328
    if ( id.objtype == 0 && id.graphic == 0 )
8✔
329
      return new BError( "Itemdesc.cfg entry for objtype " + Clib::hexint( objtype ) +
×
330
                         " not found." );
×
331

332
    std::unique_ptr<BStruct> descriptor( new BStruct() );
8✔
333

334
    id.PopulateStruct( descriptor.get() );
8✔
335

336
    return descriptor.release();
8✔
337
  }
8✔
338
  else
339
  {
340
    return new BError( "Invalid parameter type" );
×
341
  }
342
}
343

344
BObjectImp* PolSystemExecutorModule::mf_FormatItemDescription()
×
345
{
346
  const String* desc;
347
  unsigned short amount;
348
  const String* suffix;
349

350
  if ( getStringParam( 0, desc ) && getParam( 1, amount ) && getStringParam( 2, suffix ) )
×
351
  {
352
    return new String( Core::format_description( 0, desc->value(), amount, suffix->value() ) );
×
353
  }
354
  else
355
  {
356
    return new BError( "Invalid parameter type" );
×
357
  }
358
}
359

360
BObjectImp* PolSystemExecutorModule::mf_CreatePacket()
4✔
361
{
362
  int size;
363
  unsigned short type;
364
  if ( exec.getParam( 0, type ) && exec.getParam( 1, size ) )
4✔
365
  {
366
    if ( type > 0xFF )
4✔
367
      return new BError( "Packet type too high" );
×
368

369
    return new Core::BPacket( static_cast<u8>( type ), static_cast<signed short>( size ) );
4✔
370
  }
371
  else
372
  {
373
    return new BError( "Invalid parameter type" );
×
374
  }
375
}
376

377
BObjectImp* PolSystemExecutorModule::mf_AddRealm( /*name,base*/ )
4✔
378
{
379
  const String* realm_name;
380
  const String* base;
381
  if ( !( getStringParam( 0, realm_name ) && getStringParam( 1, base ) ) )
4✔
382
  {
383
    return new BError( "Invalid parameter" );
×
384
  }
385
  Realms::Realm* baserealm = Core::find_realm( base->value() );
4✔
386
  if ( !baserealm )
4✔
387
    return new BError( "BaseRealm not found." );
×
388
  if ( baserealm->is_shadowrealm )
4✔
389
    return new BError( "BaseRealm is a ShadowRealm." );
×
390
  if ( Core::defined_realm( realm_name->value() ) )
4✔
391
    return new BError( "Realmname already defined." );
×
392
  Core::add_realm( realm_name->value(), baserealm );
4✔
393
  return new BLong( 1 );
4✔
394
}
395

396
BObjectImp* PolSystemExecutorModule::mf_DeleteRealm( /*name*/ )
5✔
397
{
398
  const String* realm_name;
399
  if ( !( getStringParam( 0, realm_name ) ) )
5✔
400
    return new BError( "Invalid parameter" );
×
401

402
  Realms::Realm* realm = Core::find_realm( realm_name->value() );
5✔
403

404
  if ( !realm )
5✔
405
    return new BError( "Realm not found." );
2✔
406
  if ( !realm->is_shadowrealm )
3✔
407
    return new BError( "Realm is not a ShadowRealm." );
×
408
  if ( realm->mobile_count() > 0 )
3✔
409
    return new BError( "Mobiles in Realm." );
×
410
  if ( realm->offline_mobile_count() > 0 )
3✔
411
    return new BError( "Offline characters in Realm" );
×
412
  if ( realm->toplevel_item_count() > 0 )
3✔
413
    return new BError( "Items in Realm." );
×
414
  if ( realm->multi_count() > 0 )
3✔
415
    return new BError( "Multis in Realm." );
×
416

417
  Core::remove_realm( realm_name->value() );
3✔
418
  return new BLong( 1 );
3✔
419
}
420

421
BObjectImp* PolSystemExecutorModule::mf_GetRealmDecay( /*name*/ )
×
422
{
423
  const String* realm_name;
424
  if ( !getStringParam( 0, realm_name ) )
×
425
  {
426
    return new BError( "Invalid parameter" );
×
427
  }
428
  Realms::Realm* realm = Core::find_realm( realm_name->value() );
×
429
  if ( !realm )
×
430
    return new BError( "Realm not found." );
×
431

432
  return new BBoolean( realm->has_decay );
×
433
}
434

435
BObjectImp* PolSystemExecutorModule::mf_SetRealmDecay( /*name,has_decay*/ )
×
436
{
437
  const String* realm_name;
438
  bool has_deacy;
439
  if ( !( getStringParam( 0, realm_name ) && getParam( 1, has_deacy ) ) )
×
440
  {
441
    return new BError( "Invalid parameter" );
×
442
  }
443
  Realms::Realm* realm = Core::find_realm( realm_name->value() );
×
444
  if ( !realm )
×
445
    return new BError( "Realm not found." );
×
446

447
  realm->has_decay = has_deacy;
×
448
  Core::gamestate.decay.after_realms_size_changed();
×
449

450
  return new BLong( 1 );
×
451
}
452

453
BObjectImp* PolSystemExecutorModule::mf_MD5Encrypt( /*string*/ )
3✔
454
{
455
  const String* string;
456
  if ( !( getStringParam( 0, string ) ) )
3✔
457
    return new BError( "Invalid parameter" );
×
458
  if ( string->length() < 1 )
3✔
459
    return new BError( "String is empty" );
×
460
  std::string temp;
3✔
461
  if ( !Clib::MD5_Encrypt( string->value(), temp ) )
3✔
462
    return new BError( "Failed to encrypt" );
×
463
  return new String( temp );
3✔
464
}
3✔
465

466
/**
467
 * Dumps the CProp profiling info into the log file
468
 *
469
 * @author Bodom
470
 */
471
BObjectImp* PolSystemExecutorModule::mf_LogCPropProfile()
×
472
{
473
  const std::string filepath = "log/cpprofile.log";
×
474
  std::ofstream ofs( filepath.c_str(), std::ios::out | std::ios::app );
×
475

476
  if ( !ofs.is_open() )
×
477
    return new BError( "Unable to open file: " + filepath );
×
478

479
  // Write the header
480
  auto t = std::time( nullptr );
×
481
  auto tm = Clib::localtime( t );
×
482
  ofs << std::string( 80, '=' ) << std::endl;
×
483
  ofs << "CProp profiling information dumped on " << std::asctime( &tm ) << std::endl;
×
484
  ofs << "the profiler is using an estimated amount of "
×
485
      << Core::CPropProfiler::instance().estimateSize() << " Bytes of memory." << std::endl;
×
486
  ofs << "the profiler is currently "
487
      << ( Plib::systemstate.config.profile_cprops ? "enabled" : "disabled" ) << "." << std::endl;
×
488
  ofs << std::endl;
×
489

490
  // Write the body
491
  Core::CPropProfiler::instance().dumpProfile( ofs );
×
492

493
  // Write the footer
494
  ofs << std::string( 80, '=' ) << std::endl;
×
495

496
  if ( ofs.fail() )
×
497
    return new BError( "Error during write." );
×
498

499
  ofs.close();
×
500
  return new BLong( 1 );
×
501
}
×
502
}  // namespace Module
503
}  // 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