Issues (50)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/SiteAliasFileLoader.php (9 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
namespace Consolidation\SiteAlias;
3
4
use Consolidation\Config\Loader\ConfigProcessor;
5
use Dflydev\DotAccessData\Util as DotAccessDataUtil;
6
7
/**
8
 * Discover alias files:
9
 *
10
 * - sitename.site.yml: contains multiple aliases, one for each of the
11
 *     environments of 'sitename'.
12
 */
13
class SiteAliasFileLoader
14
{
15
    /**
16
     * @var SiteAliasFileDiscovery
17
     */
18
    protected $discovery;
19
20
    /**
21
     * @var array
22
     */
23
    protected $referenceData;
24
25
    /**
26
     * @var array
27
     */
28
    protected $loader;
29
30
    /**
31
     * @var string
32
     */
33
    protected $root;
34
35
    /**
36
     * SiteAliasFileLoader constructor
37
     *
38
     * @param SiteAliasFileDiscovery|null $discovery
39
     */
40
    public function __construct($discovery = null)
41
    {
42
        $this->discovery = $discovery ?: new SiteAliasFileDiscovery();
43
        $this->referenceData = [];
44
        $this->loader = [];
45
    }
46
47
    /**
48
     * Allow configuration data to be used in replacements in the alias file.
49
     */
50
    public function setReferenceData($data)
51
    {
52
        $this->referenceData = $data;
53
    }
54
55
    /**
56
     * Allow 'self.site.yml' to be applied to any alias record found.
57
     */
58
    public function setRoot($root)
59
    {
60
        $this->root = $root;
61
    }
62
63
    /**
64
     * Add a search location to our discovery object.
65
     *
66
     * @param string $path
67
     *
68
     * @return $this
69
     */
70
    public function addSearchLocation($path)
71
    {
72
        $this->discovery()->addSearchLocation($path);
73
        return $this;
74
    }
75
76
    /**
77
     * Return our discovery object.
78
     *
79
     * @return SiteAliasFileDiscovery
80
     */
81
    public function discovery()
82
    {
83
        return $this->discovery;
84
    }
85
86
    /**
87
     * Load the file containing the specified alias name.
88
     *
89
     * @param SiteAliasName $aliasName
90
     *
91
     * @return SiteAlias|false
92
     */
93
    public function load(SiteAliasName $aliasName)
94
    {
95
        // First attempt to load a sitename.site.yml file for the alias.
96
        $aliasRecord = $this->loadSingleAliasFile($aliasName);
97
        if ($aliasRecord) {
98
            return $aliasRecord;
99
        }
100
101
        // If aliasname was provides as @site.env and we did not find it,
102
        // then we are done.
103
        if ($aliasName->hasSitename()) {
104
            return false;
105
        }
106
107
        // If $aliasName was provided as `@foo` (`hasSitename()` returned `false`
108
        // above), then this was interpreted as `@self.foo` when we searched
109
        // above. If we could not find an alias record for `@self.foo`, then we
110
        // will try to search again, this time with the assumption that `@foo`
111
        // might be `@foo.<default>`, where `<default>` is the default
112
        // environment for the specified site. Note that in this instance, the
113
        // sitename will be found in $aliasName->env().
114
        $sitename = $aliasName->env();
115
        return $this->loadDefaultEnvFromSitename($sitename);
116
    }
117
118
    /**
119
     * Given only a site name, load the default environment from it.
120
     */
121
    protected function loadDefaultEnvFromSitename($sitename)
122
    {
123
        $path = $this->discovery()->findSingleSiteAliasFile($sitename);
124
        if (!$path) {
125
            return false;
126
        }
127
        $data = $this->loadSiteDataFromPath($path);
128
        if (!$data) {
129
            return false;
130
        }
131
        $env = $this->getDefaultEnvironmentName($data);
132
133
        $aliasName = new SiteAliasName($sitename, $env);
134
        $processor = new ConfigProcessor();
135
        return $this->fetchSiteAliasFromSiteAliasData($aliasName, $processor, $data);
136
    }
137
138
    /**
139
     * Return a list of all site aliases loadable from any findable path.
140
     *
141
     * @return SiteAlias[]
142
     */
143
    public function loadAll()
144
    {
145
        $result = [];
146
        $paths = $this->discovery()->findAllSingleAliasFiles();
147
        foreach ($paths as $path) {
148
            $aliasRecords = $this->loadSingleSiteAliasFileAtPath($path);
149
            if ($aliasRecords) {
150
                foreach ($aliasRecords as $aliasRecord) {
151
                    $this->storeSiteAliasInResut($result, $aliasRecord);
152
                }
153
            }
154
        }
155
        ksort($result);
156
        return $result;
157
    }
158
159
    /**
160
     * Return a list of all available alias files. Does not include
161
     * legacy files.
162
     *
163
     * @param string $location Only consider alias files in the specified location.
164
     * @return string[]
165
     */
166
    public function listAll($location = '')
167
    {
168
        return $this->discovery()->filterByLocation($location)->findAllSingleAliasFiles();
169
    }
170
171
    /**
172
     * Given an alias name that might represent multiple sites,
173
     * return a list of all matching alias records. If nothing was found,
174
     * or the name represents a single site + env, then we take
175
     * no action and return `false`.
176
     *
177
     * @param string $sitename The site name to return all environments for.
178
     * @return SiteAlias[]|false
179
     */
180 View Code Duplication
    public function loadMultiple($sitename, $location = null)
181
    {
182
        $result = [];
183
        foreach ($this->discovery()->filterByLocation($location)->find($sitename) as $path) {
184
            if ($siteData = $this->loadSiteDataFromPath($path)) {
185
                $location = SiteAliasName::locationFromPath($path);
0 ignored issues
show
$path is of type string, but the function expects a object<Consolidation\SiteAlias\type>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
186
                // Convert the raw array into a list of alias records.
187
                $result = array_merge(
188
                    $result,
189
                    $this->createSiteAliassFromSiteData($sitename, $siteData, $location)
190
                );
191
            }
192
        }
193
        return $result;
194
    }
195
196
    /**
197
     * Given a location, return all alias files located there.
198
     *
199
     * @param string $location The location to filter.
200
     * @return SiteAlias[]
201
     */
202 View Code Duplication
    public function loadLocation($location)
203
    {
204
        $result = [];
205
        foreach ($this->listAll($location) as $path) {
206
            if ($siteData = $this->loadSiteDataFromPath($path)) {
207
                $location = SiteAliasName::locationFromPath($path);
0 ignored issues
show
$path is of type string, but the function expects a object<Consolidation\SiteAlias\type>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
208
                $sitename = $this->siteNameFromPath($path);
209
                // Convert the raw array into a list of alias records.
210
                $result = array_merge(
211
                    $result,
212
                    $this->createSiteAliassFromSiteData($sitename, $siteData, $location)
213
                );
214
            }
215
        }
216
        return $result;
217
    }
218
219
    /**
220
     * @param array $siteData list of sites with its respective data
221
     *
222
     * @param SiteAliasName $aliasName The name of the record being created
0 ignored issues
show
There is no parameter named $aliasName. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
223
     * @param $siteData An associative array of envrionment => site data
224
     * @return SiteAlias[]
225
     */
226
    protected function createSiteAliassFromSiteData($sitename, $siteData, $location = '')
227
    {
228
        $result = [];
229
        if (!is_array($siteData) || empty($siteData)) {
230
            return $result;
231
        }
232
        foreach ($siteData as $envName => $data) {
233
            if (is_array($data) && $this->isValidEnvName($envName)) {
234
                $aliasName = new SiteAliasName($sitename, $envName, $location);
235
236
                $processor = new ConfigProcessor();
237
                $oneRecord = $this->fetchSiteAliasFromSiteAliasData($aliasName, $processor, $siteData);
238
                $this->storeSiteAliasInResut($result, $oneRecord);
239
            }
240
        }
241
        return $result;
242
    }
243
244
    /**
245
     * isValidEnvName determines if a given entry should be skipped or not
246
     * (e.g. the "common" entry).
247
     *
248
     * @param string $envName The environment name to test
249
     */
250
    protected function isValidEnvName($envName)
251
    {
252
        return $envName != 'common';
253
    }
254
255
    /**
256
     * Store an alias record in a list. If the alias record has
257
     * a known name, then the key of the list will be the record's name.
258
     * Otherwise, append the record to the end of the list with
259
     * a numeric index.
260
     *
261
     * @param &SiteAlias[] $result list of alias records
0 ignored issues
show
The doc-type &SiteAlias[] could not be parsed: Unknown type name "&SiteAlias" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
262
     * @param SiteAlias $aliasRecord one more alias to store in the result
263
     */
264
    protected function storeSiteAliasInResut(&$result, SiteAlias $aliasRecord)
265
    {
266
        if (!$aliasRecord) {
267
            return;
268
        }
269
        $key = $aliasRecord->name();
270
        if (empty($key)) {
271
            $result[] = $aliasRecord;
272
            return;
273
        }
274
        $result[$key] = $aliasRecord;
275
    }
276
277
    /**
278
     * If the alias name is '@sitename', or if it is '@sitename.env', then
279
     * look for a sitename.site.yml file that contains it. We also handle
280
     * '@location.sitename.env' here as well.
281
     *
282
     * @param SiteAliasName $aliasName
283
     *
284
     * @return SiteAlias|false
285
     */
286
    protected function loadSingleAliasFile(SiteAliasName $aliasName)
287
    {
288
        // Check to see if the appropriate sitename.alias.yml file can be
289
        // found. Return if it cannot.
290
        $path = $this->discovery()
291
            ->filterByLocation($aliasName->location())
292
            ->findSingleSiteAliasFile($aliasName->sitename());
293
        if (!$path) {
294
            return false;
295
        }
296
        return $this->loadSingleAliasFileWithNameAtPath($aliasName, $path);
297
    }
298
299
    /**
300
     * Given only the path to an alias file `site.alias.yml`, return all
301
     * of the alias records for every environment stored in that file.
302
     *
303
     * @param string $path
304
     * @return SiteAlias[]
305
     */
306
    protected function loadSingleSiteAliasFileAtPath($path)
307
    {
308
        $sitename = $this->siteNameFromPath($path);
309
        $location = SiteAliasName::locationFromPath($path);
0 ignored issues
show
$path is of type string, but the function expects a object<Consolidation\SiteAlias\type>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
310
        if ($siteData = $this->loadSiteDataFromPath($path)) {
311
            return $this->createSiteAliassFromSiteData($sitename, $siteData, $location);
312
        }
313
        return false;
314
    }
315
316
    /**
317
     * Given the path to a single site alias file `site.alias.yml`,
318
     * return the `site` part.
319
     *
320
     * @param string $path
321
     */
322
    protected function siteNameFromPath($path)
323
    {
324
        return $this->basenameWithoutExtension($path, '.site.yml');
325
326
// OR:
327
//        $filename = basename($path);
328
//        return preg_replace('#\..*##', '', $filename);
329
    }
330
331
    /**
332
     * Chop off the `aliases.yml` or `alias.yml` part of a path. This works
333
     * just like `basename`, except it will throw if the provided path
334
     * does not end in the specified extension.
335
     *
336
     * @param string $path
337
     * @param string $extension
338
     * @return string
339
     * @throws \Exception
340
     */
341
    protected function basenameWithoutExtension($path, $extension)
342
    {
343
        $result = basename($path, $extension);
344
        // It is an error if $path does not end with site.yml
345
        if ($result == basename($path)) {
346
            throw new \Exception("$path must end with '$extension'");
347
        }
348
        return $result;
349
    }
350
351
    /**
352
     * Given an alias name and a path, load the data from the path
353
     * and process it as needed to generate the alias record.
354
     *
355
     * @param SiteAliasName $aliasName
356
     * @param string $path
357
     * @return SiteAlias|false
358
     */
359
    protected function loadSingleAliasFileWithNameAtPath(SiteAliasName $aliasName, $path)
360
    {
361
        $data = $this->loadSiteDataFromPath($path);
362
        if (!$data) {
363
            return false;
364
        }
365
        $processor = new ConfigProcessor();
366
        return $this->fetchSiteAliasFromSiteAliasData($aliasName, $processor, $data);
367
    }
368
369
    /**
370
     * Load the yml from the given path
371
     *
372
     * @param string $path
373
     * @return array|bool
374
     */
375
    protected function loadSiteDataFromPath($path)
376
    {
377
        $data = $this->loadData($path);
378
        if (!$data) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $data of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
379
            return false;
380
        }
381
        $selfSiteAliases = $this->findSelfSiteAliases($data, $path);
382
        $data = array_merge($data, $selfSiteAliases);
383
        return $data;
384
    }
385
386
    /**
387
     * Given an array of site aliases, find the first one that is
388
     * local (has no 'host' item) and also contains a 'self.site.yml' file.
389
     * @param array $data
0 ignored issues
show
There is no parameter named $data. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
390
     * @return array
391
     */
392
    protected function findSelfSiteAliases($site_aliases, $path)
0 ignored issues
show
The parameter $path is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
393
    {
394
        foreach ($site_aliases as $site => $data) {
395
            if (!isset($data['host']) && isset($data['root'])) {
396
                $data = $this->loadSelfSiteData($data['root']);
397
                if (!empty($data)) {
398
                    return $data;
399
                }
400
            }
401
        }
402
403
        return $this->loadSelfSiteData($this->root);
404
    }
405
406
    /**
407
     * Check to see if there is a 'drush/sites/self.site.yml' file at
408
     * the provided root, or one directory up from there.
409
     */
410
    protected function loadSelfSiteData($root)
411
    {
412
        if (!$root) {
413
            return [];
414
        }
415
        foreach (['.', '..'] as $relative_path) {
416
            $candidate = $root . '/' . $relative_path . '/drush/sites/self.site.yml';
417
            if (file_exists($candidate)) {
418
                return $this->loadData($candidate);
419
            }
420
        }
421
        return [];
422
    }
423
424
    /**
425
     * Load the contents of the specified file.
426
     *
427
     * @param string $path Path to file to load
428
     * @return array
429
     */
430
    protected function loadData($path)
431
    {
432
        if (empty($path) || !file_exists($path)) {
433
            return [];
434
        }
435
        $loader = $this->getLoader(pathinfo($path, PATHINFO_EXTENSION));
436
        if (!$loader) {
437
            return [];
438
        }
439
        return $loader->load($path);
440
    }
441
442
    /**
443
     * @return DataFileLoaderInterface
444
     */
445
    public function getLoader($extension)
446
    {
447
        if (!isset($this->loader[$extension])) {
448
            return null;
449
        }
450
        return $this->loader[$extension];
451
    }
452
453
    public function addLoader($extension, DataFileLoaderInterface $loader)
454
    {
455
        $this->loader[$extension] = $loader;
456
    }
457
458
    /**
459
     * Given an array containing site alias data, return an alias record
460
     * containing the data for the requested record. If there is a 'common'
461
     * section, then merge that in as well.
462
     *
463
     * @param SiteAliasName $aliasName the alias we are loading
464
     * @param array $data
465
     *
466
     * @return SiteAlias|false
467
     */
468
    protected function fetchSiteAliasFromSiteAliasData(SiteAliasName $aliasName, ConfigProcessor $processor, array $data)
469
    {
470
        $data = $this->adjustIfSingleAlias($data);
471
        $env = $this->getEnvironmentName($aliasName, $data);
472
        $env_data = $this->getRequestedEnvData($data, $env);
473
        if (!$env_data) {
474
            return false;
475
        }
476
477
        // Add the 'common' section if it exists.
478
        if ($this->siteEnvExists($data, 'common')) {
479
            $processor->add($data['common']);
480
        }
481
482
        // Then add the data from the desired environment.
483
        $processor->add($env_data);
484
485
        // Export the combined data and create an SiteAlias object to manage it.
486
        return new SiteAlias($processor->export($this->referenceData + ['env-name' => $env]), '@' . $aliasName->sitenameWithLocation(), $env);
487
    }
488
489
    /**
490
     * getRequestedEnvData fetches the data for the specified environment
491
     * from the provided site record data.
492
     *
493
     * @param array $data The site alias data
494
     * @param string $env The name of the environment desired
495
     * @return array|false
496
     */
497
    protected function getRequestedEnvData(array $data, $env)
498
    {
499
        // If the requested environment exists, we will use it.
500
        if ($this->siteEnvExists($data, $env)) {
501
            return $data[$env];
502
        }
503
504
        // If there is a wildcard environment, then return that instead.
505
        if ($this->siteEnvExists($data, '*')) {
506
            return $data['*'];
507
        }
508
509
        return false;
510
    }
511
512
    /**
513
     * Determine whether there is a valid-looking environment '$env' in the
514
     * provided site alias data.
515
     *
516
     * @param array $data
517
     * @param string $env
518
     * @return bool
519
     */
520
    protected function siteEnvExists(array $data, $env)
521
    {
522
        return (
523
            is_array($data) &&
524
            isset($data[$env]) &&
525
            is_array($data[$env])
526
        );
527
    }
528
529
    /**
530
     * Adjust the alias data for a single-site alias. Usually, a .yml alias
531
     * file will contain multiple entries, one for each of the environments
532
     * of an alias. If there are no environments
533
     *
534
     * @param array $data
535
     * @return array
536
     */
537
    protected function adjustIfSingleAlias($data)
538
    {
539
        if (!$this->detectSingleAlias($data)) {
540
            return $data;
541
        }
542
543
        $result = [
544
            'default' => $data,
545
        ];
546
547
        return $result;
548
    }
549
550
    /**
551
     * A single-environment alias looks something like this:
552
     *
553
     *   ---
554
     *   root: /path/to/drupal
555
     *   uri: https://mysite.org
556
     *
557
     * A multiple-environment alias looks something like this:
558
     *
559
     *   ---
560
     *   default: dev
561
     *   dev:
562
     *     root: /path/to/dev
563
     *     uri: https://dev.mysite.org
564
     *   stage:
565
     *     root: /path/to/stage
566
     *     uri: https://stage.mysite.org
567
     *
568
     * The differentiator between these two is that the multi-environment
569
     * alias always has top-level elements that are associative arrays, and
570
     * the single-environment alias never does.
571
     *
572
     * @param array $data
573
     * @return bool
574
     */
575
    protected function detectSingleAlias($data)
576
    {
577
        foreach ($data as $key => $value) {
578
            if (is_array($value) && DotAccessDataUtil::isAssoc($value)) {
579
                return false;
580
            }
581
        }
582
        return true;
583
    }
584
585
    /**
586
     * Return the name of the environment requested.
587
     *
588
     * @param SiteAliasName $aliasName the alias we are loading
589
     * @param array $data
590
     *
591
     * @return string
592
     */
593
    protected function getEnvironmentName(SiteAliasName $aliasName, array $data)
594
    {
595
        // If the alias name specifically mentions the environment
596
        // to use, then return it.
597
        if ($aliasName->hasEnv()) {
598
            return $aliasName->env();
599
        }
600
        return $this->getDefaultEnvironmentName($data);
601
    }
602
603
    /**
604
     * Given a data array containing site alias environments, determine which
605
     * envirionmnet should be used as the default environment.
606
     *
607
     * @param array $data
608
     * @return string
609
     */
610
    protected function getDefaultEnvironmentName(array $data)
611
    {
612
        // If there is an entry named 'default', it will either contain the
613
        // name of the environment to use by default, or it will itself be
614
        // the default environment.
615
        if (isset($data['default'])) {
616
            return is_array($data['default']) ? 'default' : $data['default'];
617
        }
618
        // If there is an environment named 'dev', it will be our default.
619
        if (isset($data['dev'])) {
620
            return 'dev';
621
        }
622
        // If we don't know which environment to use, just take the first one.
623
        $keys = array_keys($data);
624
        return reset($keys);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression reset($keys); of type string|false adds false to the return on line 624 which is incompatible with the return type documented by Consolidation\SiteAlias\...tDefaultEnvironmentName of type string. It seems like you forgot to handle an error condition.
Loading history...
625
    }
626
}
627