Beispiel #1
0
 public function onHandshake(Request $request, Response $response)
 {
     // During handshakes, you should always check the origin header, otherwise any site will
     // be able to connect to your endpoint. Websockets are not restricted by the same-origin-policy!
     $origin = $request->getHeader("origin");
     if ($origin !== "http://localhost:1337") {
         $response->setStatus(403);
         $response->send("<h1>origin not allowed</h1>");
         return null;
     }
     // returned values will be passed to onOpen, that way you can pass cookie values or the whole request object.
     return $request->getConnectionInfo()["client_addr"];
 }
Beispiel #2
0
 public function __construct(Request $request)
 {
     $this->request = $request;
     $config = $request->getLocalVar("aerys.session.config");
     assert(\is_array($config), 'No middleware was loaded or Aerys\\Session class instantiated in invalid context');
     $this->driver = $config["driver"];
     $config += static::CONFIG;
     $request->setLocalVar("aerys.session.config", $config);
     $id = $request->getCookie($config["name"]);
     if (\strlen($id) === self::ID_LENGTH && strspn($id, self::ALLOWED_ID_CHARS) === self::ID_LENGTH) {
         $this->setId($id);
     }
     $this->ttl = $config["ttl"];
     $this->maxlife = $config["maxlife"];
 }
Beispiel #3
0
 /**
  * Route a request
  *
  * @param \Aerys\Request $request
  * @param \Aerys\Response $response
  */
 public function __invoke(Request $request, Response $response)
 {
     if (!($preRoute = $request->getLocalVar("aerys.routed"))) {
         return;
     }
     list($isMethodAllowed, $data) = $preRoute;
     if ($isMethodAllowed) {
         return $data($request, $response, $request->getLocalVar("aerys.routeArgs"));
     } else {
         $allowedMethods = implode(",", $data);
         $response->setStatus(HTTP_STATUS["METHOD_NOT_ALLOWED"]);
         $response->setHeader("Allow", $allowedMethods);
         $response->setHeader("Aerys-Generic-Response", "enable");
         $response->end();
     }
 }
Beispiel #4
0
 /**
  * @param Request $req
  * @param array $options available options are:
  *                       - size (default: 131072)
  *                       - input_vars (default: 200)
  *                       - field_len (default: 16384)
  */
 public function __construct(Request $req, array $options = [])
 {
     $this->req = $req;
     $type = $req->getHeader("content-type");
     $this->body = $req->getBody($this->totalSize = $this->size = $options["size"] ?? 131072);
     $this->maxFieldLen = $options["field_len"] ?? 16384;
     $this->maxInputVars = $options["input_vars"] ?? 200;
     if ($type !== null && strncmp($type, "application/x-www-form-urlencoded", \strlen("application/x-www-form-urlencoded"))) {
         if (!preg_match('#^\\s*multipart/(?:form-data|mixed)(?:\\s*;\\s*boundary\\s*=\\s*("?)([^"]*)\\1)?$#', $type, $m)) {
             $this->req = null;
             $this->parsing = true;
             $this->result = new ParsedBody([]);
             return;
         }
         $this->boundary = $m[2];
     }
     $this->body->when(function ($e, $data) {
         $this->req = null;
         \Amp\immediately(function () use($e, $data) {
             if ($e) {
                 if ($e instanceof ClientSizeException) {
                     $e = new ClientException("", 0, $e);
                 }
                 $this->error = $e;
             } else {
                 $this->result = $this->end($data);
             }
             if (!$this->parsing) {
                 $this->parsing = true;
                 foreach ($this->result->getNames() as $field) {
                     foreach ($this->result->getArray($field) as $_) {
                         foreach ($this->watchers as list($cb, $cbData)) {
                             $cb($field, $cbData);
                         }
                     }
                 }
             }
             $this->parsing = true;
             foreach ($this->whens as list($cb, $cbData)) {
                 $cb($this->error, $this->result, $cbData);
             }
             $this->whens = $this->watchers = [];
         });
     });
 }
Beispiel #5
0
 private function getJsonRequestBody(AerysRequest $request, AerysResponse $response) : \Generator
 {
     if ($request->getHeader('Content-Type') !== 'application/json') {
         $this->respondWithError($response, 400, 'Type of request body must be application/json');
         return null;
     }
     $body = $request->getBody();
     $bodyText = '';
     while ((yield $body->valid())) {
         $bodyText .= $body->consume();
     }
     try {
         $json = json_try_decode($bodyText, true);
     } catch (JSONDecodeErrorException $e) {
         $this->respondWithError($response, 400, 'Unable to decode request body as application/json');
         return null;
     }
     if (!is_array($json)) {
         $this->respondWithError($response, 400, 'Invalid request data structure');
         return null;
     }
     return $json;
 }
Beispiel #6
0
 private function checkPreconditions(Request $request, int $mtime, string $etag)
 {
     $ifMatch = $request->getHeader("If-Match");
     if ($ifMatch && \stripos($ifMatch, $etag) === false) {
         return self::PRECOND_FAILED;
     }
     $ifNoneMatch = $request->getHeader("If-None-Match");
     if ($ifNoneMatch && \stripos($ifNoneMatch, $etag) !== false) {
         return self::PRECOND_NOT_MODIFIED;
     }
     $ifModifiedSince = $request->getHeader("If-Modified-Since");
     $ifModifiedSince = $ifModifiedSince ? @\strtotime($ifModifiedSince) : 0;
     if ($ifModifiedSince && $mtime > $ifModifiedSince) {
         return self::PRECOND_NOT_MODIFIED;
     }
     $ifUnmodifiedSince = $request->getHeader("If-Unmodified-Since");
     $ifUnmodifiedSince = $ifUnmodifiedSince ? @\strtotime($ifUnmodifiedSince) : 0;
     if ($ifUnmodifiedSince && $mtime > $ifUnmodifiedSince) {
         return self::PRECOND_FAILED;
     }
     $ifRange = $request->getHeader("If-Range");
     if (!($ifRange || $request->getHeader("Range"))) {
         return self::PRECOND_OK;
     }
     /**
      * This is a really stupid feature of HTTP but ...
      * If-Range headers may be either an HTTP timestamp or an Etag:
      *
      *     If-Range = "If-Range" ":" ( entity-tag | HTTP-date )
      *
      * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.27
      */
     if ($httpDate = @\strtotime($ifRange)) {
         return $httpDate > $mtime ? self::PRECOND_IF_RANGE_OK : self::PRECOND_IF_RANGE_FAILED;
     }
     // If the If-Range header was not an HTTP date we assume it's an Etag
     return $etag === $ifRange ? self::PRECOND_IF_RANGE_OK : self::PRECOND_IF_RANGE_FAILED;
 }
Beispiel #7
0
 public function doJoin(Request $request, Response $response)
 {
     $session = (yield (new Session($request))->open());
     parse_str((yield $request->getBody()), $post);
     $username = isset($post["username"]) && is_string($post["username"]) ? $post["username"] : "";
     $avatar = $session->get("auth:identity:avatar");
     $provider = $session->get("auth:provider");
     if (!$provider) {
         $response->setStatus(400);
         $response->setHeader("aerys-generic-response", "enable");
         $response->send("");
         return;
     }
     if (!preg_match("~^[a-z][a-z0-9-]+[a-z0-9]\$~i", $username)) {
         $template = $this->templateService->load("sign-up.php");
         $template->set("hint", $username);
         $template->set("error", "username must start with a-z and only contain a-z, 0-9 or dashes afterwards");
         $response->send($template->render());
         return;
     }
     $query = (yield $this->db->prepare("INSERT IGNORE INTO user (`name`, `avatar`) VALUES (?, ?)", [$username, $avatar]));
     if ($query->affectedRows) {
         (yield $this->db->prepare("INSERT INTO oauth (user_id, provider, identity, label) VALUES (?, ?, ?, ?)", [$query->insertId, $provider, $session->get("auth:identity:id"), $session->get("auth:identity:name")]));
         $session->set("login", $query->insertId);
         $session->set("login:name", $username);
         $session->set("login:avatar", $avatar);
         $session->set("login:time", time());
         $response->setStatus(302);
         $response->setHeader("location", "/");
         $response->send("");
     } else {
         $template = $this->templateService->load("sign-up.php");
         $template->set("hint", $username);
         $template->set("error", "username already taken");
         $response->send($template->render());
     }
     (yield $session->save());
 }
Beispiel #8
0
 private function sendPreAppTraceResponse(Request $request, Response $response)
 {
     $response->setStatus(HTTP_STATUS["OK"]);
     $response->setHeader("Content-Type", "message/http");
     $response->end($request->getLocalVar('aerys.trace'));
 }
Beispiel #9
0
 public function onHandshake(Request $request, Response $response)
 {
     return $request->getConnectionInfo()['client_addr'];
 }
Beispiel #10
0
 /**
  * Handles all hooks.
  *
  * @param Request  $request HTTP request
  * @param Response $response HTTP response
  * @param array    $args URL args
  */
 public function handle(Request $request, Response $response, array $args)
 {
     $response->setHeader("content-type", "text/plain");
     $token = $request->getQueryVars()["token"] ?? "";
     if (!$token || !is_string($token)) {
         $response->setStatus(401);
         $response->send("Failure: No token was provided.");
         return;
     }
     // use @ so we don't have to check for invalid strings manually
     $token = (string) @hex2bin($token);
     $hook = (yield $this->hookRepository->get($args["id"]));
     if (!$hook) {
         $response->setStatus(404);
         $response->send("Failure: Hook does not exist.");
         return;
     }
     if (!hash_equals($hook->token, $token)) {
         $response->setStatus(403);
         $response->send("Failure: Provided token doesn't match.");
         return;
     }
     $name = $args["service"];
     if (!isset($this->services[$name])) {
         $response->setStatus(404);
         $response->send("Failure: Unknown service.");
         return;
     }
     $contentType = strtok($request->getHeader("content-type"), ";");
     $body = (yield $request->getBody());
     switch ($contentType) {
         case "application/json":
             $payload = json_decode($body);
             break;
         case "application/x-www-form-urlencoded":
             parse_str($body, $payload);
             $payload = json_decode(json_encode($payload));
             break;
         default:
             $response->setStatus(415);
             $response->send("Failure: Content-type not supported.");
             return;
     }
     $service = $this->services[$name];
     $headers = $request->getAllHeaders();
     $event = $service->getEventName($headers, $payload);
     if (!isset($this->schemas[$name][$event])) {
         $response->setStatus(400);
         $response->send("Failure: Event not supported.");
         return;
     }
     $schema = $this->schemas[$name][$event];
     $this->validator->reset();
     $this->validator->check($payload, $schema);
     if (!$this->validator->isValid()) {
         $errors = $this->validator->getErrors();
         $errors = array_reduce($errors, function (string $carry, array $item) : string {
             if ($item["property"]) {
                 return $carry . sprintf("\n%s: %s", $item["property"], $item["message"]);
             } else {
                 return $carry . "\n" . $item["message"];
             }
         }, "");
         $response->setStatus(400);
         $response->send("Failure: Payload validation failed." . $errors);
         return;
     }
     $message = $service->handle($headers, $payload);
     try {
         if ($message) {
             $req = (new HttpRequest())->setMethod("PUT")->setUri($this->config["api"] . "/messages")->setHeader("authorization", "Basic " . base64_encode("{$this->config['user_id']}:{$this->config['token']}"))->setBody(json_encode(["room_id" => $hook->room_id, "text" => $message->getText(), "data" => $message->getData()]));
             $resp = (yield $this->http->request($req));
             if (intval($resp->getStatus() / 100) !== 2) {
                 $message = "API request failed: " . $resp->getStatus();
                 if ($resp->getBody()) {
                     $message .= "\n" . $resp->getBody();
                 }
                 throw new Exception($message);
             }
         }
         $response->send("Success: " . ($message ? "Message sent." : "Message skipped."));
     } catch (Exception $e) {
         $response->setStatus(500);
         $response->send("Failure: Couldn't persist message.");
     }
 }
Beispiel #11
0
 public function __invoke(Request $req, Response $res)
 {
     $headers = $req->getAllHeaders();
     unset($headers["accept-encoding"]);
     $connection = $headers["connection"];
     unset($headers["connection"]);
     foreach ($connection as $value) {
         foreach (explode(",", strtolower($value)) as $type) {
             $type = trim($type);
             if ($type == "upgrade") {
                 $headers["connection"][0] = "upgrade";
             } else {
                 unset($headers[$type]);
             }
         }
     }
     if ($this->headers) {
         if (is_callable($this->headers)) {
             $headers = ($this->headers)($headers);
         } else {
             $headers = $this->headers + $headers;
         }
     }
     $promise = $this->client->request((new \Amp\Artax\Request())->setMethod($req->getMethod())->setUri($this->target . $req->getUri())->setAllHeaders($headers)->setBody((yield $req->getBody())));
     // no async sending possible :-( [because of redirects]
     $promise->watch(function ($update) use($req, $res, &$hasBody, &$status, &$zlib) {
         list($type, $data) = $update;
         if ($type == Notify::RESPONSE_HEADERS) {
             $headers = array_change_key_case($data["headers"], CASE_LOWER);
             foreach ($data["headers"] as $header => $values) {
                 foreach ($values as $value) {
                     $res->addHeader($header, $value);
                 }
             }
             $res->setStatus($status = $data["status"]);
             $res->setReason($data["reason"]);
             if (isset($headers["content-encoding"]) && strcasecmp(trim(current($headers["content-encoding"])), 'gzip') === 0) {
                 $zlib = inflate_init(ZLIB_ENCODING_GZIP);
             }
             $hasBody = true;
         }
         if ($type == Notify::RESPONSE_BODY_DATA) {
             if ($zlib) {
                 $data = inflate_add($zlib, $data);
             }
             $res->stream($data);
         }
         if ($type == Notify::RESPONSE) {
             if (!$hasBody) {
                 foreach ($data->getAllHeaders() as $header => $values) {
                     foreach ($values as $value) {
                         $res->addHeader($header, $value);
                     }
                 }
                 $res->setStatus($status = $data->getStatus());
                 $res->setReason($data->getReason());
             }
             if ($status == 101) {
                 $req->setLocalVar("aerys.reverse.socket", $update["export_socket"]());
             }
             $res->end($zlib ? inflate_add("", ZLIB_FINISH) : null);
         }
     });
     (yield $promise);
 }
Beispiel #12
0
 public function handle(Request $request, Response $response, array $args)
 {
     $endpoint = $request->getLocalVar("chat.api.endpoint");
     $user = $request->getLocalVar("chat.api.user");
     if (!$endpoint || !$user) {
         // if this happens, something's really wrong, e.g. wrong order of callables
         $response->setStatus(500);
         $response->send("");
     }
     foreach ($args as $key => $arg) {
         if (is_numeric($arg)) {
             $args[$key] = (int) $arg;
         }
     }
     foreach ($request->getQueryVars() as $key => $value) {
         // Don't allow overriding URL parameters
         if (isset($args[$key])) {
             continue;
         }
         if (is_numeric($value)) {
             $args[$key] = (int) $value;
         } else {
             if (is_string($value)) {
                 $args[$key] = $value;
             } else {
                 $result = new Error("bad_request", "invalid query parameter types", 400);
                 $this->writeResponse($request, $response, $result);
                 return;
             }
         }
     }
     $args = $args ? (object) $args : new stdClass();
     $body = (yield $request->getBody());
     $payload = $body ? json_decode($body) : null;
     $result = (yield $this->chat->process(new StandardRequest($endpoint, $args, $payload), $user));
     $this->writeResponse($request, $response, $result);
 }
Beispiel #13
0
 public function onHandshake(Request $request, Response $response)
 {
     $origin = $request->getHeader("origin");
     if (!isOriginAllowed($origin)) {
         $response->setStatus(400);
         $response->setReason("Invalid Origin");
         $response->send("<h1>Invalid Origin</h1>");
         return null;
     }
     if ($this->eventSub->getConnectionState() !== ConnectionState::CONNECTED) {
         $response->setStatus(503);
         $response->setReason("Service unavailable");
         $response->send("");
         return null;
     }
     return new Session($request);
 }