Issues (1401)

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.

repo/includes/Rdf/RdfBuilder.php (1 issue)

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
3
namespace Wikibase\Repo\Rdf;
4
5
use PageProps;
6
use SplQueue;
7
use Wikibase\DataModel\Entity\EntityDocument;
8
use Wikibase\DataModel\Entity\EntityId;
9
use Wikibase\DataModel\Entity\PropertyId;
10
use Wikibase\DataModel\Services\Lookup\EntityLookup;
11
use Wikibase\DataModel\Services\Lookup\PropertyDataTypeLookup;
12
use Wikibase\Lib\Store\EntityTitleLookup;
13
use Wikibase\Lib\Store\RevisionedUnresolvedRedirectException;
14
use Wikimedia\Purtle\RdfWriter;
15
16
/**
17
 * RDF mapping for wikibase data model.
18
 *
19
 * @license GPL-2.0-or-later
20
 */
21
class RdfBuilder implements EntityRdfBuilder, EntityMentionListener {
22
23
	/**
24
	 * A list of entities mentioned/touched to or by this builder.
25
	 * The prefixed entity IDs are used as keys in the array, the value 'true'
26
	 * is used to indicate that the entity has been resolved. If the value
27
	 * is an EntityId, this indicates that the entity has not yet been resolved
28
	 * (defined).
29
	 *
30
	 * @var (bool|EntityId)[]
31
	 */
32
	private $entitiesResolved = [];
33
34
	/**
35
	 * A queue of entities to output by this builder.
36
	 *
37
	 * @var SplQueue<EntityDocument>
38
	 */
39
	private $entitiesToOutput;
40
41
	/**
42
	 * What the serializer would produce?
43
	 *
44
	 * @var int
45
	 */
46
	private $produceWhat;
47
48
	/**
49
	 * @var RdfWriter
50
	 */
51
	private $writer;
52
53
	/**
54
	 * @var DedupeBag
55
	 */
56
	private $dedupeBag;
57
58
	/**
59
	 * RDF builders to apply when building RDF for an entity.
60
	 * @var EntityRdfBuilder[]
61
	 */
62
	private $builders = [];
63
64
	/**
65
	 * @var RdfVocabulary
66
	 */
67
	private $vocabulary;
68
69
	/**
70
	 * @var PropertyDataTypeLookup
71
	 */
72
	private $propertyLookup;
73
74
	/**
75
	 * @var ValueSnakRdfBuilderFactory
76
	 */
77
	private $valueSnakRdfBuilderFactory;
78
79
	/**
80
	 * @var EntityTitleLookup
81
	 */
82
	private $titleLookup;
83
84
	/**
85
	 * Page properties handler, can be null if we don't need them.
86
	 * @var PageProps|null
87
	 */
88
	private $pageProps;
89
90
	/**
91
	 * Entity-specific RDF builders to apply when building RDF for an entity.
92
	 * @var EntityRdfBuilder[]
93
	 */
94
	private $entityRdfBuilders;
95
96
	/**
97
	 * @param RdfVocabulary $vocabulary
98
	 * @param ValueSnakRdfBuilderFactory $valueSnakRdfBuilderFactory
99
	 * @param PropertyDataTypeLookup $propertyLookup
100
	 * @param EntityRdfBuilderFactory $entityRdfBuilderFactory
101
	 * @param int $flavor
102
	 * @param RdfWriter $writer
103
	 * @param DedupeBag $dedupeBag
104
	 * @param EntityTitleLookup $titleLookup
105
	 */
106
	public function __construct(
107
		RdfVocabulary $vocabulary,
108
		ValueSnakRdfBuilderFactory $valueSnakRdfBuilderFactory,
109
		PropertyDataTypeLookup $propertyLookup,
110
		EntityRdfBuilderFactory $entityRdfBuilderFactory,
111
		$flavor,
112
		RdfWriter $writer,
113
		DedupeBag $dedupeBag,
114
		EntityTitleLookup $titleLookup
115
	) {
116
		$this->entitiesToOutput = new SplQueue();
117
		$this->vocabulary = $vocabulary;
118
		$this->propertyLookup = $propertyLookup;
119
		$this->valueSnakRdfBuilderFactory = $valueSnakRdfBuilderFactory;
120
		$this->writer = $writer;
121
		$this->produceWhat = $flavor;
122
		$this->dedupeBag = $dedupeBag;
123
		$this->titleLookup = $titleLookup;
124
125
		// XXX: move construction of sub-builders to a factory class.
126
		$this->builders[] = $entityRdfBuilderFactory->getTermRdfBuilder( $vocabulary, $writer );
127
128
		if ( $this->shouldProduce( RdfProducer::PRODUCE_TRUTHY_STATEMENTS ) ) {
129
			$this->builders[] = $this->newTruthyStatementRdfBuilder();
130
		}
131
132
		if ( $this->shouldProduce( RdfProducer::PRODUCE_ALL_STATEMENTS ) ) {
133
			$this->builders[] = $this->newFullStatementRdfBuilder();
134
		}
135
136
		$this->entityRdfBuilders = $entityRdfBuilderFactory->getEntityRdfBuilders(
137
			$flavor,
138
			$vocabulary,
139
			$writer,
140
			$this,
141
			$dedupeBag
142
		);
143
	}
144
145
	/**
146
	 * @param int $flavorFlags Flavor flags to use for this builder
147
	 * @return SnakRdfBuilder
148
	 */
149
	private function newSnakBuilder( $flavorFlags ) {
150
		$statementValueBuilder = $this->valueSnakRdfBuilderFactory->getValueSnakRdfBuilder(
151
			$flavorFlags,
152
			$this->vocabulary,
153
			$this->writer,
154
			$this,
155
			$this->dedupeBag
156
		);
157
		$snakBuilder = new SnakRdfBuilder( $this->vocabulary, $statementValueBuilder, $this->propertyLookup );
158
		$snakBuilder->setEntityMentionListener( $this );
159
160
		return $snakBuilder;
161
	}
162
163
	/**
164
	 * @return EntityRdfBuilder
165
	 */
166
	private function newTruthyStatementRdfBuilder() {
167
		//NOTE: currently, the only simple values are supported in truthy mode!
168
		$simpleSnakBuilder = $this->newSnakBuilder( $this->produceWhat & ~RdfProducer::PRODUCE_FULL_VALUES );
169
		$statementBuilder = new TruthyStatementRdfBuilder( $this->vocabulary, $this->writer, $simpleSnakBuilder );
170
171
		return $statementBuilder;
172
	}
173
174
	/**
175
	 * @return EntityRdfBuilder
176
	 */
177
	private function newFullStatementRdfBuilder() {
178
		$snakBuilder = $this->newSnakBuilder( $this->produceWhat );
179
180
		$builder = new FullStatementRdfBuilder( $this->vocabulary, $this->writer, $snakBuilder );
181
		$builder->setDedupeBag( $this->dedupeBag );
182
		$builder->setProduceQualifiers( $this->shouldProduce( RdfProducer::PRODUCE_QUALIFIERS ) );
183
		$builder->setProduceReferences( $this->shouldProduce( RdfProducer::PRODUCE_REFERENCES ) );
184
185
		return $builder;
186
	}
187
188
	/**
189
	 * Start writing RDF document
190
	 * Note that this builder does not have to finish it, it may be finished later.
191
	 */
192
	public function startDocument() {
193
		foreach ( $this->getNamespaces() as $gname => $uri ) {
194
			$this->writer->prefix( $gname, $uri );
195
		}
196
197
		$this->writer->start();
198
	}
199
200
	/**
201
	 * Finish writing the document
202
	 * After that, nothing should ever be written into the document.
203
	 */
204
	public function finishDocument() {
205
		$this->writer->finish();
206
	}
207
208
	/**
209
	 * Returns the RDF generated by the builder
210
	 *
211
	 * @return string RDF
212
	 */
213
	public function getRDF() {
214
		return $this->writer->drain();
215
	}
216
217
	/**
218
	 * Returns a map of namespace names to URIs
219
	 *
220
	 * @return array
221
	 */
222
	public function getNamespaces() {
223
		return $this->vocabulary->getNamespaces();
224
	}
225
226
	/**
227
	 * Get map of page properties used by this builder
228
	 *
229
	 * @return string[][]
230
	 */
231
	public function getPageProperties() {
232
		return $this->vocabulary->getPageProperties();
233
	}
234
235
	/**
236
	 * Should we produce this aspect?
237
	 *
238
	 * @param int $what
239
	 *
240
	 * @return bool
241
	 */
242
	private function shouldProduce( $what ) {
243
		return ( $this->produceWhat & $what ) !== 0;
244
	}
245
246
	/**
247
	 * @see EntityMentionListener::entityReferenceMentioned
248
	 *
249
	 * @param EntityId $id
250
	 */
251
	public function entityReferenceMentioned( EntityId $id ) {
252
		if ( $this->shouldProduce( RdfProducer::PRODUCE_RESOLVED_ENTITIES ) ) {
253
			$this->entityToResolve( $id );
254
		}
255
	}
256
257
	/**
258
	 * @see EntityMentionListener::propertyMentioned
259
	 *
260
	 * @param PropertyId $id
261
	 */
262
	public function propertyMentioned( PropertyId $id ) {
263
		if ( $this->shouldProduce( RdfProducer::PRODUCE_PROPERTIES ) ) {
264
			$this->entityToResolve( $id );
265
		}
266
	}
267
268
	/**
269
	 * @see EntityMentionListener::subEntityMentioned
270
	 *
271
	 * @param EntityDocument $entity
272
	 */
273
	public function subEntityMentioned( EntityDocument $entity ) {
274
		$this->entitiesToOutput->enqueue( $entity );
275
	}
276
277
	/**
278
	 * Registers an entity as mentioned.
279
	 * Will be recorded as unresolved
280
	 * if it wasn't already marked as resolved.
281
	 *
282
	 * @param EntityId $entityId
283
	 */
284
	private function entityToResolve( EntityId $entityId ) {
285
		$prefixedId = $entityId->getSerialization();
286
287
		if ( !isset( $this->entitiesResolved[$prefixedId] ) ) {
288
			$this->entitiesResolved[$prefixedId] = $entityId;
289
		}
290
	}
291
292
	/**
293
	 * Registers an entity as resolved.
294
	 *
295
	 * @param EntityId $entityId
296
	 */
297
	private function entityResolved( EntityId $entityId ) {
298
		$prefixedId = $entityId->getSerialization();
299
		$this->entitiesResolved[$prefixedId] = true;
300
	}
301
302
	/**
303
	 * Adds revision information about an entity's revision to the RDF graph.
304
	 *
305
	 * @todo extract into MetaDataRdfBuilder
306
	 *
307
	 * @param EntityId $entityId
308
	 * @param int $revision
309
	 * @param string $timestamp in TS_MW format
310
	 */
311
	public function addEntityRevisionInfo( EntityId $entityId, $revision, $timestamp ) {
312
		$timestamp = wfTimestamp( TS_ISO_8601, $timestamp );
313
		$entityLName = $this->vocabulary->getEntityLName( $entityId );
314
		$entityRepositoryName = $this->vocabulary->getEntityRepositoryName( $entityId );
315
316
		$this->writer->about( $this->vocabulary->dataNamespaceNames[$entityRepositoryName], $entityLName )
317
			->a( RdfVocabulary::NS_SCHEMA_ORG, "Dataset" )
318
			->say( RdfVocabulary::NS_SCHEMA_ORG, 'about' )
319
			->is( $this->vocabulary->entityNamespaceNames[$entityRepositoryName], $entityLName );
320
321
		if ( $this->shouldProduce( RdfProducer::PRODUCE_VERSION_INFO ) ) {
322
			// Dumps don't need version/license info for each entity, since it is included in the dump header
323
			$this->writer
324
				->say( RdfVocabulary::NS_CC, 'license' )->is( $this->vocabulary->getLicenseUrl() )
325
				->say( RdfVocabulary::NS_SCHEMA_ORG, 'softwareVersion' )->value( RdfVocabulary::FORMAT_VERSION );
326
		}
327
328
		$this->writer->say( RdfVocabulary::NS_SCHEMA_ORG, 'version' )->value( $revision, 'xsd', 'integer' )
329
			->say( RdfVocabulary::NS_SCHEMA_ORG, 'dateModified' )->value( $timestamp, 'xsd', 'dateTime' );
330
	}
331
332
	/**
333
	 * Set page props handler
334
	 * @param PageProps $pageProps
335
	 * @return self
336
	 */
337
	public function setPageProps( PageProps $pageProps ) {
338
		$this->pageProps = $pageProps;
339
		return $this;
340
	}
341
342
	/**
343
	 * Add page props information
344
	 * @param EntityId $entityId
345
	 */
346
	public function addEntityPageProps( EntityId $entityId ) {
347
		if ( !$this->pageProps || !$this->shouldProduce( RdfProducer::PRODUCE_PAGE_PROPS ) ) {
348
			return;
349
		}
350
		$title = $this->titleLookup->getTitleForId( $entityId );
351
		$props = $this->getPageProperties();
352
		if ( !$title || !$props ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $props of type string[][] 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...
353
			return;
354
		}
355
		$propValues = $this->pageProps->getProperties( $title, array_keys( $props ) );
356
		if ( !$propValues ) {
357
			return;
358
		}
359
		$entityProps = reset( $propValues );
360
		if ( !$entityProps ) {
361
			return;
362
		}
363
364
		$entityRepositoryName = $this->vocabulary->getEntityRepositoryName( $entityId );
365
		$entityLName = $this->vocabulary->getEntityLName( $entityId );
366
367
		foreach ( $entityProps as $name => $value ) {
368
			if ( !isset( $props[$name]['name'] ) ) {
369
				continue;
370
			}
371
372
			if ( isset( $props[$name]['type'] ) ) {
373
				settype( $value, $props[$name]['type'] );
374
			}
375
376
			$this->writer->about( $this->vocabulary->dataNamespaceNames[$entityRepositoryName], $entityLName )
377
				->say( RdfVocabulary::NS_ONTOLOGY, $props[$name]['name'] )
378
				->value( $value );
379
		}
380
	}
381
382
	/**
383
	 * Adds meta-information about an entity (such as the ID and type) to the RDF graph.
384
	 *
385
	 * @todo extract into MetaDataRdfBuilder
386
	 *
387
	 * @param EntityDocument $entity
388
	 */
389
	private function addEntityMetaData( EntityDocument $entity ) {
390
		$entityId = $entity->getId();
391
		$entityLName = $this->vocabulary->getEntityLName( $entityId );
392
		$entityRepoName = $this->vocabulary->getEntityRepositoryName( $entityId );
393
394
		$this->writer->about(
395
			$this->vocabulary->entityNamespaceNames[$entityRepoName],
396
			$entityLName
397
		)
398
			->a( RdfVocabulary::NS_ONTOLOGY, $this->vocabulary->getEntityTypeName( $entity->getType() ) );
399
	}
400
401
	/**
402
	 * Add an entity to the RDF graph, including all supported structural components
403
	 * of the entity and its sub entities.
404
	 *
405
	 * @param EntityDocument $entity the entity to output.
406
	 */
407
	public function addEntity( EntityDocument $entity ) {
408
		$this->addSingleEntity( $entity );
409
		$this->addQueuedEntities();
410
	}
411
412
	/**
413
	 * Add a single entity to the RDF graph, including all supported structural components
414
	 * of the entity.
415
	 *
416
	 * @param EntityDocument $entity the entity to output.
417
	 */
418
	private function addSingleEntity( EntityDocument $entity ) {
419
		$this->addEntityMetaData( $entity );
420
421
		$type = $entity->getType();
422
		if ( !empty( $this->entityRdfBuilders[$type] ) ) {
423
			$this->entityRdfBuilders[$type]->addEntity( $entity );
424
		}
425
426
		foreach ( $this->builders as $builder ) {
427
			$builder->addEntity( $entity );
428
		}
429
430
		$this->entityResolved( $entity->getId() );
431
	}
432
433
	/**
434
	 * Add the RDF serialization of all entities in the entitiesToOutput queue
435
	 */
436
	private function addQueuedEntities() {
437
		while ( !$this->entitiesToOutput->isEmpty() ) {
438
			$this->addSingleEntity( $this->entitiesToOutput->dequeue() );
439
		}
440
	}
441
442
	/**
443
	 * Add stubs for any entities that were previously mentioned (e.g. as properties
444
	 * or data values).
445
	 *
446
	 * @param EntityLookup $entityLookup
447
	 */
448
	public function resolveMentionedEntities( EntityLookup $entityLookup ) {
449
		$hasRedirect = false;
450
451
		foreach ( $this->entitiesResolved as $id ) {
452
			// $value is true if the entity has already been resolved,
453
			// or an EntityId to resolve.
454
			if ( !( $id instanceof EntityId ) ) {
455
				continue;
456
			}
457
458
			try {
459
				$entity = $entityLookup->getEntity( $id );
460
461
				if ( !$entity ) {
462
					continue;
463
				}
464
465
				$this->addEntityStub( $entity );
466
			} catch ( RevisionedUnresolvedRedirectException $ex ) {
467
				// NOTE: this may add more entries to the end of entitiesResolved
468
				$target = $ex->getRedirectTargetId();
469
				$this->addEntityRedirect( $id, $target );
470
				$hasRedirect = true;
471
			}
472
		}
473
474
		// If we encountered redirects, the redirect targets may now need resolving.
475
		// They actually got added to $this->entitiesResolved, but may not have been
476
		// processed by the loop above, because they got added while the loop was in progress.
477
		if ( $hasRedirect ) {
478
			// Call resolveMentionedEntities() recursively to resolve any yet unresolved
479
			// redirect targets. The regress will eventually terminate even for circular
480
			// redirect chains, because the second time an entity ID is encountered, it
481
			// will be marked as already resolved.
482
			$this->resolveMentionedEntities( $entityLookup );
483
		}
484
	}
485
486
	/**
487
	 * Adds stub information for the given Entity to the RDF graph.
488
	 * Stub information means meta information and labels.
489
	 *
490
	 * @param EntityDocument $entity
491
	 */
492
	public function addEntityStub( EntityDocument $entity ) {
493
		$this->addEntityMetaData( $entity );
494
495
		$type = $entity->getType();
496
		if ( !empty( $this->entityRdfBuilders[$type] ) ) {
497
			$this->entityRdfBuilders[$type]->addEntityStub( $entity );
498
		}
499
500
		foreach ( $this->builders as $builder ) {
501
			$builder->addEntityStub( $entity );
502
		}
503
	}
504
505
	/**
506
	 * Declares $from to be an alias for $to, using the owl:sameAs relationship.
507
	 *
508
	 * @param EntityId $from
509
	 * @param EntityId $to
510
	 */
511
	public function addEntityRedirect( EntityId $from, EntityId $to ) {
512
		$fromLName = $this->vocabulary->getEntityLName( $from );
513
		$fromRepoName = $this->vocabulary->getEntityRepositoryName( $from );
514
		$toLName = $this->vocabulary->getEntityLName( $to );
515
		$toRepoName = $this->vocabulary->getEntityRepositoryName( $to );
516
517
		$this->writer->about( $this->vocabulary->entityNamespaceNames[$fromRepoName], $fromLName )
518
			->say( 'owl', 'sameAs' )
519
			->is( $this->vocabulary->entityNamespaceNames[$toRepoName], $toLName );
520
521
		$this->entityResolved( $from );
522
523
		if ( $this->shouldProduce( RdfProducer::PRODUCE_RESOLVED_ENTITIES ) ) {
524
			$this->entityToResolve( $to );
525
		}
526
	}
527
528
	/**
529
	 * Create header structure for the dump (this makes RdfProducer::PRODUCE_VERSION_INFO redundant)
530
	 *
531
	 * @param int $timestamp Timestamp (for testing)
532
	 */
533
	public function addDumpHeader( $timestamp = 0 ) {
534
		// TODO: this should point to "this document"
535
		$this->writer->about( RdfVocabulary::NS_ONTOLOGY, 'Dump' )
536
			->a( RdfVocabulary::NS_SCHEMA_ORG, "Dataset" )
537
			->a( 'owl', 'Ontology' )
538
			->say( RdfVocabulary::NS_CC, 'license' )->is( $this->vocabulary->getLicenseUrl() )
539
			->say( RdfVocabulary::NS_SCHEMA_ORG, 'softwareVersion' )->value( RdfVocabulary::FORMAT_VERSION )
540
			->say( RdfVocabulary::NS_SCHEMA_ORG, 'dateModified' )->value( wfTimestamp( TS_ISO_8601, $timestamp ), 'xsd', 'dateTime' )
541
			->say( 'owl', 'imports' )->is( RdfVocabulary::getOntologyURI() );
542
	}
543
544
}
545