* * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; either * version 3 of the License, or any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU AFFERO GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU Affero General Public * License along with this library. If not, see . * */ namespace Test\AppFramework\Http; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Response; use OCP\AppFramework\Utility\ITimeFactory; class ResponseTest extends \Test\TestCase { /** * @var \OCP\AppFramework\Http\Response */ private $childResponse; protected function setUp(): void { parent::setUp(); $this->childResponse = new Response(); } public function testAddHeader() { $this->childResponse->addHeader(' hello ', 'world'); $headers = $this->childResponse->getHeaders(); $this->assertEquals('world', $headers['hello']); } public function testSetHeaders() { $expected = [ 'Last-Modified' => 1, 'ETag' => 3, 'Something-Else' => 'hi', 'X-Robots-Tag' => 'none', ]; $this->childResponse->setHeaders($expected); $headers = $this->childResponse->getHeaders(); $expected['Content-Security-Policy'] = "default-src 'none';base-uri 'none';manifest-src 'self';frame-ancestors 'none'"; $expected['Feature-Policy'] = "autoplay 'none';camera 'none';fullscreen 'none';geolocation 'none';microphone 'none';payment 'none'"; $this->assertEquals($expected, $headers); } public function testOverwriteCsp() { $expected = [ 'Content-Security-Policy' => "default-src 'none';base-uri 'none';manifest-src 'self';script-src 'self' 'unsafe-inline';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self' data:;connect-src 'self';media-src 'self'", ]; $policy = new Http\ContentSecurityPolicy(); $policy->allowInlineScript(true); $this->childResponse->setContentSecurityPolicy($policy); $headers = $this->childResponse->getHeaders(); $this->assertEquals(array_merge($expected, $headers), $headers); } public function testGetCsp() { $policy = new Http\ContentSecurityPolicy(); $policy->allowInlineScript(true); $this->childResponse->setContentSecurityPolicy($policy); $this->assertEquals($policy, $this->childResponse->getContentSecurityPolicy()); } public function testGetCspEmpty() { $this->assertEquals(new Http\EmptyContentSecurityPolicy(), $this->childResponse->getContentSecurityPolicy()); } public function testAddHeaderValueNullDeletesIt() { $this->childResponse->addHeader('hello', 'world'); $this->childResponse->addHeader('hello', null); $this->assertEquals(5, count($this->childResponse->getHeaders())); } public function testCacheHeadersAreDisabledByDefault() { $headers = $this->childResponse->getHeaders(); $this->assertEquals('no-cache, no-store, must-revalidate', $headers['Cache-Control']); } public function testAddCookie() { $this->childResponse->addCookie('foo', 'bar'); $this->childResponse->addCookie('bar', 'foo', new \DateTime('1970-01-01')); $expectedResponse = [ 'foo' => [ 'value' => 'bar', 'expireDate' => null, 'sameSite' => 'Lax', ], 'bar' => [ 'value' => 'foo', 'expireDate' => new \DateTime('1970-01-01'), 'sameSite' => 'Lax', ] ]; $this->assertEquals($expectedResponse, $this->childResponse->getCookies()); } public function testSetCookies() { $expected = [ 'foo' => [ 'value' => 'bar', 'expireDate' => null, ], 'bar' => [ 'value' => 'foo', 'expireDate' => new \DateTime('1970-01-01') ] ]; $this->childResponse->setCookies($expected); $cookies = $this->childResponse->getCookies(); $this->assertEquals($expected, $cookies); } public function testInvalidateCookie() { $this->childResponse->addCookie('foo', 'bar'); $this->childResponse->invalidateCookie('foo'); $expected = [ 'foo' => [ 'value' => 'expired', 'expireDate' => new \DateTime('1971-01-01'), 'sameSite' => 'Lax', ] ]; $cookies = $this->childResponse->getCookies(); $this->assertEquals($expected, $cookies); } public function testInvalidateCookies() { $this->childResponse->addCookie('foo', 'bar'); $this->childResponse->addCookie('bar', 'foo'); $expected = [ 'foo' => [ 'value' => 'bar', 'expireDate' => null, 'sameSite' => 'Lax', ], 'bar' => [ 'value' => 'foo', 'expireDate' => null, 'sameSite' => 'Lax', ] ]; $cookies = $this->childResponse->getCookies(); $this->assertEquals($expected, $cookies); $this->childResponse->invalidateCookies(['foo', 'bar']); $expected = [ 'foo' => [ 'value' => 'expired', 'expireDate' => new \DateTime('1971-01-01'), 'sameSite' => 'Lax', ], 'bar' => [ 'value' => 'expired', 'expireDate' => new \DateTime('1971-01-01'), 'sameSite' => 'Lax', ] ]; $cookies = $this->childResponse->getCookies(); $this->assertEquals($expected, $cookies); } public function testRenderReturnNullByDefault() { $this->assertEquals(null, $this->childResponse->render()); } public function testGetStatus() { $default = $this->childResponse->getStatus(); $this->childResponse->setStatus(Http::STATUS_NOT_FOUND); $this->assertEquals(Http::STATUS_OK, $default); $this->assertEquals(Http::STATUS_NOT_FOUND, $this->childResponse->getStatus()); } public function testGetEtag() { $this->childResponse->setEtag('hi'); $this->assertSame('hi', $this->childResponse->getEtag()); } public function testGetLastModified() { $lastModified = new \DateTime('now', new \DateTimeZone('GMT')); $lastModified->setTimestamp(1); $this->childResponse->setLastModified($lastModified); $this->assertEquals($lastModified, $this->childResponse->getLastModified()); } public function testCacheSecondsZero() { $this->childResponse->cacheFor(0); $headers = $this->childResponse->getHeaders(); $this->assertEquals('no-cache, no-store, must-revalidate', $headers['Cache-Control']); $this->assertFalse(isset($headers['Pragma'])); $this->assertFalse(isset($headers['Expires'])); } public function testCacheSeconds() { $time = $this->createMock(ITimeFactory::class); $time->method('getTime') ->willReturn(1234567); $this->overwriteService(ITimeFactory::class, $time); $this->childResponse->cacheFor(33); $headers = $this->childResponse->getHeaders(); $this->assertEquals('private, max-age=33, must-revalidate', $headers['Cache-Control']); $this->assertEquals('private', $headers['Pragma']); $this->assertEquals('Thu, 15 Jan 1970 06:56:40 +0000', $headers['Expires']); } public function testEtagLastModifiedHeaders() { $lastModified = new \DateTime('now', new \DateTimeZone('GMT')); $lastModified->setTimestamp(1); $this->childResponse->setLastModified($lastModified); $headers = $this->childResponse->getHeaders(); $this->assertEquals('Thu, 01 Jan 1970 00:00:01 +0000', $headers['Last-Modified']); } public function testChainability() { $lastModified = new \DateTime('now', new \DateTimeZone('GMT')); $lastModified->setTimestamp(1); $this->childResponse->setEtag('hi') ->setStatus(Http::STATUS_NOT_FOUND) ->setLastModified($lastModified) ->cacheFor(33) ->addHeader('hello', 'world'); $headers = $this->childResponse->getHeaders(); $this->assertEquals('world', $headers['hello']); $this->assertEquals(Http::STATUS_NOT_FOUND, $this->childResponse->getStatus()); $this->assertEquals('hi', $this->childResponse->getEtag()); $this->assertEquals('Thu, 01 Jan 1970 00:00:01 +0000', $headers['Last-Modified']); $this->assertEquals('private, max-age=33, must-revalidate', $headers['Cache-Control']); } public function testThrottle() { $this->assertFalse($this->childResponse->isThrottled()); $this->childResponse->throttle(); $this->assertTrue($this->childResponse->isThrottled()); } public function testGetThrottleMetadata() { $this->childResponse->throttle(['foo' => 'bar']); $this->assertSame(['foo' => 'bar'], $this->childResponse->getThrottleMetadata()); } } #n3'>3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
/*-
 * Copyright 2016 Vsevolod Stakhov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#ifndef SRC_LIBUTIL_EXPRESSION_H_
#define SRC_LIBUTIL_EXPRESSION_H_

#include "config.h"
#include "mem_pool.h"
#include "fstring.h"
#include "util.h"

#ifdef __cplusplus
extern "C" {
#endif

#define RSPAMD_EXPRESSION_MAX_PRIORITY 1024

#define RSPAMD_EXPRESSION_FLAG_NOOPT (1 << 0)

enum rspamd_expression_op {
	OP_INVALID = 0,
	OP_PLUS,   /* + */
	OP_MULT,   /* * */
	OP_MINUS,  /* - */
	OP_DIVIDE, /* / */
	OP_OR,     /* || or | */
	OP_AND,    /* && or & */
	OP_NOT,    /* ! */
	OP_LT,     /* < */
	OP_GT,     /* > */
	OP_LE,     /* <= */
	OP_GE,     /* >= */
	OP_EQ,     /* == */
	OP_NE,     /* != */
	OP_OBRACE, /* ( */
	OP_CBRACE  /* ) */
};

typedef struct rspamd_expression_atom_s {
	/* Parent node */
	GNode *parent;
	/* Opaque userdata */
	gpointer data;
	/* String representation of atom */
	const char *str;
	/* Length of the string representation of atom */
	unsigned int len;
	/* Relative priority */
	int priority;
	unsigned int hits;
	struct rspamd_counter_data exec_time;
} rspamd_expression_atom_t;

typedef double (*rspamd_expression_process_cb)(gpointer runtime_data,
											   rspamd_expression_atom_t *atom);

struct rspamd_atom_subr {
	/* Parses atom from string and returns atom structure */
	rspamd_expression_atom_t *(*parse)(const char *line, gsize len,
									   rspamd_mempool_t *pool, gpointer ud, GError **err);

	/* Process atom via the opaque pointer (e.g. struct rspamd_task *) */
	rspamd_expression_process_cb process;

	/* Calculates the relative priority of the expression */
	int (*priority)(rspamd_expression_atom_t *atom);

	void (*destroy)(rspamd_expression_atom_t *atom);
};

/* Opaque structure */
struct rspamd_expression;

/**
 * Parse symbolic expression and create the expression using the specified subroutines for atoms processing
 * @param line line to parse
 * @param len length of the line (if 0 then line should be NULL terminated)
 * @param subr subroutines for atoms parsing
 * @param subr_data opaque dat pointer
 * @param pool pool to use for memory allocations
 * @param err error pointer
 * @param target the target expression
 * @return TRUE if an expression have been parsed
 */
gboolean rspamd_parse_expression(const char *line, gsize len,
								 const struct rspamd_atom_subr *subr, gpointer subr_data,
								 rspamd_mempool_t *pool, GError **err,
								 struct rspamd_expression **target);

/**
 * Process the expression and return its value using atom 'process' functions with the specified data pointer
 * @param expr expression to process
 * @param data opaque data pointer for all the atoms
 * @return the value of expression
 */
double rspamd_process_expression(struct rspamd_expression *expr,
								 int flags,
								 gpointer runtime_ud);

/**
 * Process the expression and return its value using atom 'process' functions with the specified data pointer.
 * This function also accepts `track` argument where it writes matched atoms (those whose value is more than 0)
 * @param expr expression to process
 * @param data opaque data pointer for all the atoms
 * @param track pointer array to atoms tracking
 * @return the value of expression
 */
double rspamd_process_expression_track(struct rspamd_expression *expr,
									   int flags,
									   gpointer runtime_ud,
									   GPtrArray **track);

/**
 * Process the expression with the custom processor
 * @param expr
 * @param cb
 * @param process_data
 * @return
 */
double rspamd_process_expression_closure(struct rspamd_expression *expr,
										 rspamd_expression_process_cb cb,
										 int flags,
										 gpointer runtime_ud,
										 GPtrArray **track);

/**
 * Shows string representation of an expression
 * @param expr expression to show
 * @return freshly allocated string with expression
 */
GString *rspamd_expression_tostring(struct rspamd_expression *expr);

/**
 * Callback that is called on @see rspamd_expression_atom_foreach, atom is ephemeral
 * and should not be modified within callback
 */
typedef void (*rspamd_expression_atom_foreach_cb)(const rspamd_ftok_t *atom,
												  gpointer ud);

/**
 * Traverse over all atoms in the expression
 * @param expr expression
 * @param cb callback to be called
 * @param ud opaque data passed to `cb`
 */
void rspamd_expression_atom_foreach(struct rspamd_expression *expr,
									rspamd_expression_atom_foreach_cb cb, gpointer cbdata);

/**
 * Checks if a specified node in AST is the specified operation
 * @param node AST node packed in GNode container
 * @param op operation to check
 * @return TRUE if node is operation node and is exactly the specified option
 */
gboolean rspamd_expression_node_is_op(GNode *node, enum rspamd_expression_op op);

#ifdef __cplusplus
}
#endif

#endif /* SRC_LIBUTIL_EXPRESSION_H_ */