apiEndpoint
last analyzed

Size/Duplication

Total Lines 6
Duplicated Lines 0 %
Metric Value
dl 0
loc 6

3 Methods

Rating   Name   Duplication   Size   Complexity  
getDescription() 0 1 ?
getAcceptedParameters() 0 1 ?
execute() 0 1 ?
1
<?php
2
/* zKillboard
3
 * Copyright (C) 2012-2015 EVE-KILL Team and EVSCO.
4
 *
5
 * This program is free software: you can redistribute it and/or modify
6
 * it under the terms of the GNU Affero General Public License as published by
7
 * the Free Software Foundation, either version 3 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU Affero General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU Affero General Public License
16
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18
19
// Load globals
20
global $apiWhiteList, $maxRequestsPerHour, $debug, $fullAddr;
21
22
// Endpoints
23
$endpoints = endPoints();
24
25
// Endpoint
26
$endpoint = isset($flags[0]) ? $flags[0] : NULL;
27
28
// Parameters
29
$parameters = Util::convertUriToParameters();
30
31
// client IP
32
$ip = IP::get();
33
34
if($ip == "73.169.15.22" || $ip == "104.236.126.43")
35
	die();
36
37
// init $data
38
$data = array();
39
40
if(in_array($endpoint, $endpoints))
41
{
42
	try
43
	{
44
		$fileName = __DIR__ . "/api/$endpoint.php";
45
		if(!file_exists($fileName))
46
			throw new Exception();
47
48
		require_once $fileName;
49
		$className = "api_$endpoint";
50
		$class = new $className();
51
52
		if(!is_a($class, "apiEndpoint"))
53
		{
54
			$data = array(
55
				"type" => "error",
56
				"message" => "Endpoint does not implement apiEndpoint"
57
			);
58
		}
59
60
		$data = $class->execute($parameters);
61
	}
62
	catch (Exception $e)
63
	{
64
		$data = array(
65
			"type" => "error",
66
			"message" => "$endpoint ended with error: " . $e->getMessage()
67
		);
68
	}
69
}
70
else
71
{
72
	$data = array(
73
		"type" => "error",
74
		"message" => "No endpoint selected.",
75
		"endpoints" => array(
76
			"/api/list/",
77
			"/api/help/<endPoint>/",
78
			"/api/parameters/<endPoint>/"
79
		)
80
	);
81
}
82
83
// If the endpoint is docs, we'll just render the html page instead, since the same data is available under /list/ and /parameters/ ! :)
84
if($endpoint == "docs")
85
	return $app->render("apidocs.html", array("data" => $data));
86
87
// Scrape Checker If type isn't set, scrapecheck, otherwise don't..
88
$type = isset($data["type"]) ? "error" : NULL;
89
if($type == NULL)
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $type of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
90
	if(!in_array($ip, $apiWhiteList))
91
		scrapeCheck();
92
93
// Output the data
94
header("Access-Control-Allow-Origin: *");
95
header("Access-Control-Allow-Methods: GET");
96
$uri = substr($_SERVER["REQUEST_URI"], 0, 256);
97
$ip = substr(IP::get(), 0, 64);
98
$count = Db::queryField("SELECT count(*) AS count FROM zz_scrape_prevention WHERE ip = :ip AND dttm >= date_sub(now(), interval 1 hour)", "count", array(":ip" => $ip), 0);
99
header("X-Bin-Request-Count: ". $count);
100
header("X-Bin-Max-Requests: ". $maxRequestsPerHour);
101
$app->etag(md5(serialize($data)));
102
$app->expires("+1 hour");
103
$userAgent = @$_SERVER["HTTP_USER_AGENT"];
104
if($debug)
105
	Log::log("API Fetch: " . $fullAddr . $_SERVER["REQUEST_URI"] . " (" . $ip . " / " . $userAgent . ")");
106
107
if(isset($_GET["callback"]) && isValidCallback($_GET["callback"]))
108
{
109
	$app->contentType("application/javascript; charset=utf-8");
110
	header("X-JSONP: true");
111
	echo $_GET["callback"] . "(" . json_encode($data) . ")";
112
}
113
else
114
{
115
	$app->contentType("application/json; charset=utf-8");
116
	echo json_encode($data, JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK | JSON_UNESCAPED_SLASHES);
117
}
118
119
interface apiEndpoint
120
{
121
	public function getDescription();
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
122
	public function getAcceptedParameters();
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
123
	public function execute($parameters);
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
124
}
125
126
function endPoints()
127
{
128
	$endPoints = array();
129
	$dir = __DIR__ . "/api/";
130
	$data = scandir($dir);
131
132
	foreach($data as $e)
133
		if(!in_array($e, array(".", "..")))
134
			$endPoints[] = str_replace(".php", "", $e);
135
136
	return $endPoints;
137
}
138
139
function scrapeCheck()
140
{
141
	global $apiWhiteList, $maxRequestsPerHour;
142
	$maxRequestsPerHour = isset($maxRequestsPerHour) ? $maxRequestsPerHour : 360;
143
144
	$uri = $_SERVER["REQUEST_URI"];
145
	$uri = explode("?", $uri);
146
	$uri = substr($uri[0], 0, 256);
147
	$ip = substr(IP::get(), 0, 64);
148
	StatsD::increment("zkb_api");
149
150
	if(!in_array($ip, $apiWhiteList))
151
	{
152
		$count = Db::queryField("SELECT count(*) AS count FROM zz_scrape_prevention WHERE ip = :ip AND dttm >= date_sub(now(), interval 1 hour)", "count", array(":ip" => $ip), 0);
153
		if($count > $maxRequestsPerHour)
154
		{
155
			$date = date("Y-m-d H:i:s");
0 ignored issues
show
Unused Code introduced by
$date is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
156
			$cachedUntil = date("Y-m-d H:i:s", time() + 3600);
157
			header("Content-type: application/json; charset=utf-8");
158
			header("Retry-After: " . $cachedUntil . " GMT");
159
			header("HTTP/1.1 429 Too Many Requests");
160
			header("Etag: ".(md5(serialize($data))));
0 ignored issues
show
Bug introduced by
The variable $data seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
161
			$data = json_encode(
162
				array(
163
					"Error" => "You have too many API requests in the last hour. You are allowed a maximum of $maxRequestsPerHour requests.",
164
					"cachedUntil" => $cachedUntil
165
				)
166
			);
167
			echo $data;
168
			die();
0 ignored issues
show
Coding Style Compatibility introduced by
The function scrapeCheck() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
169
		}
170
	}
171
	Db::execute("INSERT INTO zz_scrape_prevention (ip, uri, dttm) VALUES (:ip, :uri, now())", array(":ip" => $ip, ":uri" => $uri));
172
}
173
174
function isValidCallback($subject)
175
{
176
	$identifier_syntax = '/^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*+$/u';
177
178
	$reserved_words = array('break', 'do', 'instanceof', 'typeof', 'case',
179
		'else', 'new', 'var', 'catch', 'finally', 'return', 'void', 'continue', 
180
		'for', 'switch', 'while', 'debugger', 'function', 'this', 'with', 
181
		'default', 'if', 'throw', 'delete', 'in', 'try', 'class', 'enum', 
182
		'extends', 'super', 'const', 'export', 'import', 'implements', 'let', 
183
		'private', 'public', 'yield', 'interface', 'package', 'protected', 
184
		'static', 'null', 'true', 'false'
185
	);
186
187
	return preg_match($identifier_syntax, $subject) && ! in_array(mb_strtolower($subject, 'UTF-8'), $reserved_words);
188
}
189