Line data Source code
1 : /**
2 : Copyright (c) 2024 Stappler LLC <admin@stappler.dev>
3 :
4 : Permission is hereby granted, free of charge, to any person obtaining a copy
5 : of this software and associated documentation files (the "Software"), to deal
6 : in the Software without restriction, including without limitation the rights
7 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 : copies of the Software, and to permit persons to whom the Software is
9 : furnished to do so, subject to the following conditions:
10 :
11 : The above copyright notice and this permission notice shall be included in
12 : all copies or substantial portions of the Software.
13 :
14 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 : THE SOFTWARE.
21 : **/
22 :
23 : #include "SPWebResourceHandler.h"
24 : #include "SPWebResource.h"
25 : #include "SPWebRequest.h"
26 : #include "SPWebRequestController.h"
27 : #include "SPWebOutput.h"
28 : #include "SPWebRoot.h"
29 : #include "SPWebInputFilter.h"
30 :
31 : namespace STAPPLER_VERSIONIZED stappler::web {
32 :
33 : SP_COVERAGE_TRIVIAL
34 : static void exitWithResourceHandlerError(Request &rctx, StringView text, const Value &source = Value(), Value &&data = Value()) {
35 : rctx.setStatus(HTTP_BAD_REQUEST);
36 : if (source.isNull()) {
37 : Root::getCurrent()->error("ResourceHandler", text, Value(data));
38 : }
39 : }
40 :
41 2125 : ResourceHandler::ResourceHandler(const db::Scheme &scheme, const Value &val)
42 2125 : : _scheme(scheme), _value(val) { }
43 :
44 4250 : bool ResourceHandler::isRequestPermitted(Request &rctx) {
45 4250 : _transaction = db::Transaction::acquire(db::Adapter(rctx.getController()->acquireDatabase()));
46 4250 : return true;
47 : }
48 :
49 2125 : Status ResourceHandler::onTranslateName(Request &rctx) {
50 2125 : if (!isRequestPermitted(rctx)) { return HTTP_FORBIDDEN; }
51 :
52 2125 : _method = rctx.getInfo().method;
53 2125 : switch (_method) {
54 2125 : case RequestMethod::Get:
55 : case RequestMethod::Post:
56 : case RequestMethod::Put:
57 : case RequestMethod::Patch:
58 : case RequestMethod::Delete:
59 2125 : break;
60 0 : default:
61 0 : return HTTP_NOT_IMPLEMENTED;
62 : break;
63 : }
64 :
65 2125 : auto &data = rctx.getInfo().queryData;
66 2125 : if (data.isString("METHOD")) {
67 100 : auto method = data.getString("METHOD");
68 100 : if (_method == RequestMethod::Get) {
69 25 : if (method == "DELETE") {
70 25 : _method = RequestMethod::Delete;
71 : }
72 75 : } else if (_method == RequestMethod::Post) {
73 50 : if (method == "PUT") {
74 0 : _method = RequestMethod::Put;
75 50 : } else if (method == "PATCH") {
76 50 : _method = RequestMethod::Patch;
77 : }
78 : }
79 100 : }
80 :
81 2125 : _resource = getResource(rctx);
82 2125 : if (!_resource) { return HTTP_NOT_FOUND; }
83 :
84 2100 : auto user = rctx.getAuthorizedUser();
85 2100 : if (!user && data.isString("token")) {
86 0 : user = rctx.getUser();
87 : }
88 :
89 2100 : _resource->setUser(user);
90 2100 : _resource->setFilterData(_value);
91 :
92 2100 : auto args = rctx.getInfo().url.query;
93 2100 : if (!args.empty() && args.front() == '(') {
94 575 : _resource->applyQuery(data);
95 : }
96 :
97 2100 : switch (_method) {
98 1450 : case RequestMethod::Get: {
99 1450 : auto modified = rctx.getRequestHeader("if-modified-since");
100 1450 : auto mt = modified.empty() ? 0 : Time::fromHttp(modified).toSeconds();
101 :
102 1450 : if (auto d = _resource->getSourceDelta()) {
103 75 : rctx.setResponseHeader("Last-Modified", d.toHttp<Interface>());
104 75 : if (mt >= d.toSeconds()) {
105 25 : return HTTP_NOT_MODIFIED;
106 : }
107 : }
108 :
109 1425 : if (mt > 0 && _resource->getType() == ResourceType::Object) {
110 25 : if (auto res = dynamic_cast<ResourceObject *>(_resource)) {
111 25 : if (auto objMtime = res->getObjectMtime()) {
112 25 : rctx.setResponseHeader("Last-Modified", Time::microseconds(objMtime).toHttp<Interface>());
113 25 : if (mt >= uint64_t(objMtime / 1000000)) {
114 25 : return HTTP_NOT_MODIFIED;
115 : }
116 : }
117 : }
118 : }
119 :
120 1400 : _resource->prepare(db::QueryList::SimpleGet);
121 :
122 1400 : if (!rctx.getInfo().headerRequest) {
123 1375 : return writeToRequest(rctx);
124 : } else {
125 25 : return writeInfoToReqest(rctx);
126 : }
127 : break;
128 : }
129 150 : case RequestMethod::Delete:
130 150 : _resource->prepare();
131 150 : if (_resource->removeObject()) {
132 150 : if (data.isString("location")) {
133 0 : return rctx.redirectTo(String(data.getString("location")));
134 150 : } else if (data.isString("target")) {
135 0 : auto &target = data.getString("target");
136 0 : if (!target.empty() && (StringView(target).starts_with(StringView(rctx.getFullHostname())) || target[0] == '/')) {
137 0 : return rctx.redirectTo(String(target));
138 : }
139 : }
140 150 : return HTTP_NO_CONTENT;
141 : }
142 0 : break;
143 300 : case RequestMethod::Post:
144 300 : _resource->prepare();
145 300 : if (_resource->prepareCreate()) {
146 300 : return DECLINED;
147 : }
148 0 : break;
149 150 : case RequestMethod::Put:
150 150 : _resource->prepare();
151 150 : if (_resource->prepareUpdate()) {
152 150 : return DECLINED;
153 : }
154 0 : break;
155 50 : case RequestMethod::Patch:
156 50 : _resource->prepare();
157 50 : if (_resource->prepareAppend()) {
158 50 : return DECLINED;
159 : }
160 0 : break;
161 0 : default:
162 0 : break;
163 : }
164 :
165 0 : return getHintedStatus(HTTP_FORBIDDEN);
166 : }
167 :
168 525 : void ResourceHandler::onInsertFilter(Request &rctx) {
169 525 : if (_method == RequestMethod::Post || _method == RequestMethod::Put || _method == RequestMethod::Patch) {
170 500 : rctx.setInputConfig(db::InputConfig{
171 500 : _resource->getInputFlags(),
172 500 : _resource->getMaxRequestSize(),
173 500 : _resource->getMaxVarSize(),
174 500 : _resource->getMaxFileSize()
175 : });
176 :
177 500 : auto ex = InputFilter::insert(rctx);
178 500 : if (ex != InputFilter::Exception::None) {
179 0 : rctx.setStatus(InputFilter::getStatusForException(ex));
180 : }
181 525 : } else if (_method != RequestMethod::Get && _method != RequestMethod::Delete) {
182 0 : exitWithResourceHandlerError(rctx, "Input data can not be recieved, no available filters");
183 : }
184 525 : }
185 :
186 525 : Status ResourceHandler::onHandler(Request &rctx) {
187 525 : auto num = rctx.getInfo().method;
188 525 : if (num == RequestMethod::Get) {
189 25 : return DECLINED;
190 : } else {
191 500 : return OK;
192 : }
193 : }
194 :
195 500 : void ResourceHandler::onFilterComplete(InputFilter *filter) {
196 500 : auto rctx = filter->getRequest();
197 500 : if (_method == RequestMethod::Put) {
198 : // we should update our resource
199 150 : auto result = _resource->updateObject(filter->getData(), filter->getFiles());
200 150 : if (result) {
201 150 : auto &target = rctx.getInfo().queryData.getValue("target");
202 150 : if (target.isString() && (StringView(target.getString()).starts_with(StringView(rctx.getFullHostname())) || target.getString()[0] == '/')) {
203 0 : rctx.setStatus(rctx.redirectTo(String(target.getString())));
204 : } else {
205 150 : writeDataToRequest(rctx, move(result));
206 150 : rctx.setStatus(HTTP_OK);
207 : }
208 : } else {
209 0 : exitWithResourceHandlerError(rctx, "Fail to perform update", result, Value(filter->getData()));
210 : }
211 500 : } else if (_method == RequestMethod::Post) {
212 300 : auto &d = filter->getData();
213 300 : auto tmp = d;
214 300 : auto result = _resource->createObject(d, filter->getFiles());
215 300 : if (result) {
216 300 : auto &target = rctx.getInfo().queryData.getValue("target");
217 300 : if (target.isString() && (StringView(target.getString()).starts_with(StringView(rctx.getFullHostname())) || target.getString()[0] == '/')) {
218 0 : rctx.setStatus(rctx.redirectTo(String(target.getString())));
219 : } else {
220 300 : writeDataToRequest(rctx, move(result));
221 300 : rctx.setStatus(HTTP_CREATED);
222 : }
223 : } else {
224 0 : exitWithResourceHandlerError(rctx, "Fail to perform create", result, Value(move(tmp)));
225 : }
226 350 : } else if (_method == RequestMethod::Patch) {
227 50 : auto result = _resource->appendObject(filter->getData());
228 50 : if (result) {
229 50 : auto &target = rctx.getInfo().queryData.getValue("target");
230 50 : if (target.isString() && (StringView(target.getString()).starts_with(StringView(rctx.getFullHostname())) || target.getString()[0] == '/')) {
231 0 : rctx.setStatus(rctx.redirectTo(String(target.getString())));
232 : } else {
233 50 : writeDataToRequest(rctx, move(result));
234 50 : rctx.setStatus(HTTP_OK);
235 : }
236 : } else {
237 0 : exitWithResourceHandlerError(rctx, "Fail to perform append", result, Value(filter->getData()));
238 : }
239 50 : }
240 500 : }
241 :
242 2125 : Resource *ResourceHandler::getResource(Request &rctx) {
243 2125 : return Resource::resolve(_transaction, _scheme, _subPath, _value);
244 : }
245 :
246 1850 : Status ResourceHandler::writeDataToRequest(Request &rctx, Value &&result) {
247 1850 : Value origin;
248 1850 : origin.setInteger(_resource->getSourceDelta().toMicroseconds(), "delta");
249 :
250 1850 : if (auto &t = _resource->getQueries().getContinueToken()) {
251 375 : if (t.isInit()) {
252 : Value cursor({
253 750 : pair("start", Value(t.getStart())),
254 750 : pair("end", Value(t.getEnd())),
255 750 : pair("total", Value(t.getTotal())),
256 750 : pair("count", Value(t.getCount())),
257 750 : pair("field", Value(t.getField())),
258 4125 : });
259 :
260 375 : if (t.hasNext()) {
261 250 : cursor.setString(t.encodeNext(), "next");
262 : }
263 :
264 375 : if (t.hasPrev()) {
265 250 : cursor.setString(t.encodePrev(), "prev");
266 : }
267 :
268 375 : origin.setValue(move(cursor), "cursor");
269 375 : }
270 : }
271 3700 : return output::writeResourceData(rctx, move(result), move(origin));
272 1850 : }
273 :
274 25 : Status ResourceHandler::writeInfoToReqest(Request &rctx) {
275 25 : Value result(_resource->getResultObject());
276 25 : if (_resource->getType() == ResourceType::File) {
277 0 : return output::writeResourceFileHeader(rctx, result);
278 : }
279 :
280 25 : return getHintedStatus(HTTP_NO_CONTENT);
281 25 : }
282 :
283 1375 : Status ResourceHandler::writeToRequest(Request &rctx) {
284 1375 : if (_resource->getType() == ResourceType::File) {
285 25 : Value result(_resource->getResultObject());
286 25 : if (result) {
287 25 : return output::writeResourceFileData(rctx, move(result));
288 : }
289 25 : } else {
290 1350 : Value result(_resource->getResultObject());
291 1350 : if (result) {
292 1350 : return writeDataToRequest(rctx, move(result));
293 : }
294 1350 : }
295 :
296 0 : return getHintedStatus(HTTP_NOT_FOUND);
297 : }
298 :
299 25 : Status ResourceHandler::getHintedStatus(Status s) const {
300 25 : auto status = _resource->getStatus();
301 25 : if (status != HTTP_OK) {
302 0 : return status;
303 : }
304 25 : return s;
305 : }
306 :
307 25 : ResourceMultiHandler::ResourceMultiHandler(const Map<StringView, const Scheme *> &schemes)
308 25 : : _schemes(schemes) { }
309 :
310 50 : bool ResourceMultiHandler::isRequestPermitted(Request &rctx) {
311 50 : _transaction = db::Transaction::acquire(db::Adapter(rctx.getController()->acquireDatabase()));
312 50 : return true;
313 : }
314 :
315 25 : Status ResourceMultiHandler::onTranslateName(Request &rctx) {
316 25 : if (!isRequestPermitted(rctx)) {
317 0 : return HTTP_FORBIDDEN;
318 : }
319 :
320 25 : auto &data = rctx.getInfo().queryData;
321 25 : auto user = rctx.getAuthorizedUser();
322 25 : if (!user && data.isString("token")) {
323 0 : user = rctx.getUser();
324 : }
325 :
326 25 : int64_t targetDelta = 0;
327 25 : Time deltaMax;
328 25 : Value result;
329 25 : Value delta;
330 25 : Vector<Pair<String, Resource *>> resources;
331 25 : resources.reserve(data.size());
332 :
333 25 : if (data.isInteger("delta")) {
334 25 : targetDelta = data.getInteger("delta");
335 : }
336 :
337 100 : for (auto &it : data.asDict()) {
338 75 : StringView path(it.first);
339 75 : auto scheme = path.readUntil<StringView::Chars<'/'>>();
340 75 : if (path.is('/')) {
341 50 : ++ path;
342 : }
343 75 : auto s_it = _schemes.find(scheme.str<Interface>());
344 75 : if (s_it != _schemes.end()) {
345 50 : if (auto resource = Resource::resolve(_transaction, *s_it->second, path)) {
346 50 : resource->setUser(user);
347 50 : resource->applyQuery(it.second);
348 50 : if (targetDelta > 0 && resource->isDeltaApplicable() && !resource->getQueryDelta()) {
349 50 : resource->setQueryDelta(Time::microseconds(targetDelta));
350 : }
351 50 : resource->prepare();
352 :
353 50 : if (resource->hasDelta()) {
354 50 : deltaMax = max(deltaMax, resource->getSourceDelta());
355 50 : delta.setInteger(resource->getSourceDelta().toMicroseconds(), it.first);
356 : }
357 :
358 50 : resources.emplace_back(it.first, resource);
359 : }
360 : }
361 : }
362 :
363 25 : if (deltaMax && delta.size() == resources.size()) {
364 25 : rctx.setResponseHeader("Last-Modified", deltaMax.toHttp<Interface>());
365 25 : if (targetDelta > 0) {
366 25 : auto modified = rctx.getRequestHeader("if-modified-since");
367 25 : if (Time::fromHttp(modified).toSeconds() >= deltaMax.toSeconds()) {
368 0 : return HTTP_NOT_MODIFIED;
369 : }
370 : }
371 : }
372 :
373 75 : for (auto &it : resources) {
374 50 : if (!rctx.getInfo().headerRequest) {
375 50 : result.setValue(it.second->getResultObject(), it.first);
376 : }
377 : }
378 :
379 25 : if (!result.empty()) {
380 25 : resultData.setValue(move(delta), "delta");
381 25 : return writeDataToRequest(rctx, move(result));
382 : } else {
383 0 : return HTTP_NOT_FOUND;
384 : }
385 :
386 : return HTTP_NOT_IMPLEMENTED;
387 25 : }
388 :
389 25 : Status ResourceMultiHandler::writeDataToRequest(Request &rctx, Value &&result) {
390 25 : return output::writeResourceData(rctx, move(result), move(resultData));
391 : }
392 :
393 : }
|