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)
|
|
|
|
|
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();
|
|
|
|
|
122
|
|
|
public function getAcceptedParameters();
|
|
|
|
|
123
|
|
|
public function execute($parameters);
|
|
|
|
|
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");
|
|
|
|
|
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))));
|
|
|
|
|
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();
|
|
|
|
|
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
|
|
|
|