]> git.r.bdr.sh - rbdr/ngx_http_office_hours_filter_module/blame - ngx_http_office_hours_filter_module.c
Fix bug with negative minutes
[rbdr/ngx_http_office_hours_filter_module] / ngx_http_office_hours_filter_module.c
CommitLineData
e4db4a20
BB
1/*
2 * Copyright 2018 Rubén Beltrán del Río
af4b488f 3 *
e4db4a20
BB
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
5 * License. You may obtain a copy of the License at
af4b488f 6 *
e4db4a20 7 * http://www.apache.org/licenses/LICENSE-2.0
af4b488f 8 *
e4db4a20
BB
9 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
10 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11 * specific language governing permissions and limitations under the License.
12 */
13
14
15#include <ngx_config.h>
16#include <ngx_core.h>
17#include <ngx_http.h>
18
fa2b280d
BB
19/*
20 * Declarations
21 */
22
fbdf81df
BB
23/* Constants */
24
25const ngx_uint_t WEEK_LENGTH = 7;
26const char * CLOSED_TOKEN = "closed";
80c4c3cc 27const ngx_str_t TIME_REGEX = ngx_string("([0-9]{1,2})(?:\\:([0-9]{2}))?\\-([0-9]{1,2})(?:\\:([0-9]{2}))?");
fbdf81df 28
fa2b280d 29/* Main Configuration Structure */
e4db4a20
BB
30
31typedef struct {
af4b488f
BB
32 ngx_array_t *office_hours;
33} ngx_http_office_hours_conf_t;
e4db4a20 34
fa2b280d
BB
35/* Lifecycle Functions For Module Context */
36
af4b488f 37static void *ngx_http_office_hours_create_conf(ngx_conf_t * cf);
fa2b280d
BB
38static char *ngx_http_office_hours_merge_conf(ngx_conf_t * cf,
39 void *parent, void *child);
e4db4a20
BB
40static ngx_int_t ngx_http_office_hours_init(ngx_conf_t * cf);
41
fa2b280d
BB
42/* Configuration Handler */
43
44static char *ngx_http_office_hours(ngx_conf_t * cf, ngx_command_t * cmd,
45 void *conf);
46
47/* Body Filter Storage */
48
49static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
50
fbdf81df
BB
51/* Utility Functions */
52static ngx_uint_t ** parse_office_hours(ngx_array_t * office_hours);
53static ngx_uint_t * parse_office_hours_string(ngx_str_t office_hours);
54static ngx_flag_t within_office_hours(ngx_uint_t ** office_hours);
55static ngx_uint_t get_day_of_week(time_t time);
56static ngx_uint_t get_seconds_of_day(time_t time);
bbe15175 57static ngx_uint_t parse_number(ngx_str_t string, ngx_uint_t start, ngx_uint_t end);
fbdf81df 58
80c4c3cc
BB
59/* Compiled Regex */
60ngx_regex_compile_t rc;
61
fa2b280d
BB
62/*
63 * Module Definitions
64 */
e4db4a20 65
fa2b280d 66/* Module Directives */
e4db4a20
BB
67
68static ngx_command_t ngx_http_office_hours_commands[] = {
69 {
fa2b280d 70 ngx_string("office_hours"),
fbdf81df 71 NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1 | NGX_CONF_TAKE2 | NGX_CONF_TAKE3 | NGX_CONF_TAKE4 | NGX_CONF_TAKE5 | NGX_CONF_TAKE6 | NGX_CONF_TAKE7,
fa2b280d
BB
72 ngx_http_office_hours,
73 NGX_HTTP_LOC_CONF_OFFSET,
74 offsetof(ngx_http_office_hours_conf_t, office_hours),
75 NULL
e4db4a20
BB
76 },
77
78 ngx_null_command
79};
80
fa2b280d
BB
81
82/* Module Context */
e4db4a20
BB
83
84static ngx_http_module_t ngx_http_office_hours_filter_module_ctx = {
fa2b280d
BB
85 NULL, /* Preconfiguration */
86 ngx_http_office_hours_init, /* Postconfiguration */
e4db4a20 87
fa2b280d
BB
88 NULL, /* Create main configuration */
89 NULL, /* Initialize main configuration */
e4db4a20 90
fa2b280d
BB
91 NULL, /* Create server configuration */
92 NULL, /* Merge server configuration */
93
94 ngx_http_office_hours_create_conf, /* Create location configuration */
95 ngx_http_office_hours_merge_conf /* Merge location configuration */
e4db4a20
BB
96};
97
fa2b280d
BB
98
99/* Module Definition */
e4db4a20
BB
100
101ngx_module_t ngx_http_office_hours_filter_module = {
fa2b280d
BB
102 NGX_MODULE_V1, //Module Version
103 &ngx_http_office_hours_filter_module_ctx, //Module context
104 ngx_http_office_hours_commands, //Module commands
105 NGX_HTTP_MODULE, //Module Type
106 NULL, //Initialize Master
107 NULL, //Initialize Module
108 NULL, //Initialize Process
109 NULL, //Initialize Thread
110 NULL, //Exit Thread
111 NULL, //Exit Process
112 NULL, //Exit Master
113 NGX_MODULE_V1_PADDING
e4db4a20
BB
114};
115
116
fa2b280d
BB
117/*
118 * Main Body Filter
119 * If the current time is within office hours, it goes to the next
120 * handler. Otherwise it returns 403 and the office hour listing.
121 */
e4db4a20 122
af4b488f 123static ngx_int_t
fa2b280d 124ngx_http_office_hours_body_filter(ngx_http_request_t * r, ngx_chain_t * in)
af4b488f 125{
e4db4a20 126
fbdf81df 127 ngx_uint_t ** parsed_office_hours;
af4b488f 128 ngx_http_office_hours_conf_t *conf;
e4db4a20 129
fa2b280d
BB
130 conf =
131 ngx_http_get_module_loc_conf(r,
132 ngx_http_office_hours_filter_module);
e4db4a20 133
fbdf81df 134
e4db4a20 135 if (conf->office_hours == NULL) {
fa2b280d 136 ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0,
fbdf81df 137 "Office hours disabled");
fa2b280d 138 return ngx_http_next_body_filter(r, in);
e4db4a20 139 }
fa2b280d 140
fbdf81df 141 parsed_office_hours = parse_office_hours(conf->office_hours);
e4db4a20 142
fbdf81df 143 if (within_office_hours(parsed_office_hours)) {
fa2b280d 144 ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0,
fbdf81df
BB
145 "Within office hours");
146 return ngx_http_next_body_filter(r, in);
e4db4a20
BB
147 }
148
fbdf81df
BB
149 ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0,
150 "Outside office hours");
151
e4db4a20
BB
152 r->keepalive = 0;
153 return NGX_HTTP_FORBIDDEN;
154}
155
fa2b280d
BB
156/*
157 * Callback for `office_hours ` directive
158 * Reads the configuration loaded from the config file(cf)
159 * And writes it to the right place in the module configuration(conf)
160 */
e4db4a20 161
fa2b280d
BB
162static char *ngx_http_office_hours(ngx_conf_t * cf, ngx_command_t * cmd,
163 void *conf)
af4b488f 164{
e4db4a20 165
af4b488f 166 char *conf_structure = conf;
e4db4a20 167
fbdf81df 168 ngx_str_t *hours, *value;
af4b488f 169 ngx_array_t **office_hours;
af4b488f 170 ngx_uint_t i;
e4db4a20 171
fa2b280d
BB
172 /* Gets the array from the config structure using the defined
173 * offset, and if the pointer is unset it creates a new one.
174 * (The first element is the directive itself, so we should be
175 * offset by 1)
176 */
177 office_hours = (ngx_array_t **) (conf_structure + cmd->offset);
e4db4a20
BB
178
179 if (*office_hours == NGX_CONF_UNSET_PTR) {
fa2b280d
BB
180 *office_hours = ngx_array_create(cf->pool, cf->args->nelts - 1,
181 sizeof(ngx_str_t));
e4db4a20 182
fa2b280d
BB
183 if (*office_hours == NULL) {
184 return NGX_CONF_ERROR;
185 }
e4db4a20 186 }
e4db4a20
BB
187 value = cf->args->elts;
188
189 for (i = 1; i < cf->args->nelts; ++i) {
fa2b280d
BB
190 hours = ngx_array_push(*office_hours);
191 if (hours == NULL) {
192 return NGX_CONF_ERROR;
193 }
194 *hours = value[i];
e4db4a20
BB
195 }
196
197 return NGX_CONF_OK;
198}
199
200
fa2b280d
BB
201/*
202 * Config Creator
203 * Initializes the configuration structure
204 */
e4db4a20 205
fa2b280d 206static void *ngx_http_office_hours_create_conf(ngx_conf_t * cf)
af4b488f 207{
e4db4a20 208
af4b488f 209 ngx_http_office_hours_conf_t *conf;
e4db4a20
BB
210
211 conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_office_hours_conf_t));
212
213 if (conf == NULL) {
fa2b280d 214 return NULL;
e4db4a20 215 }
e4db4a20
BB
216 conf->office_hours = NGX_CONF_UNSET_PTR;
217
218 return conf;
219}
220
fa2b280d
BB
221/*
222 * Merge Config Values
223 * Sets the defaults for the configuration and merges
224 * with other configurations
225 */
e4db4a20 226
fa2b280d
BB
227static char *ngx_http_office_hours_merge_conf(ngx_conf_t * cf,
228 void *parent, void *child)
af4b488f 229{
e4db4a20 230
af4b488f
BB
231 ngx_http_office_hours_conf_t *prev = parent;
232 ngx_http_office_hours_conf_t *conf = child;
e4db4a20
BB
233
234 ngx_conf_merge_ptr_value(conf->office_hours, prev->office_hours, NULL);
235
236 return NGX_CONF_OK;
237}
238
fbdf81df
BB
239/*
240 * Parse the office hour strings in the configuration file
241 * to fill out the hours array (in seconds)
242 */
243
244static ngx_uint_t ** parse_office_hours(ngx_array_t * office_hours)
245{
246
247 ngx_str_t *hours;
248 ngx_uint_t ** parsed_office_hours;
249 ngx_uint_t i, j;
250
251 parsed_office_hours = malloc(7 * sizeof(ngx_uint_t *));
252
253 hours = office_hours->elts;
254
255 /*
256 * On the configuration file, the leftmost element
257 * always applies to all remaining days, all others
258 * are read from right to left. So first we will apply
259 * the initial override, and then iterate based on the
260 * number of overrides
261 */
262
263 for (i = 0; i < WEEK_LENGTH + 1 - office_hours->nelts; ++i) {
264 parsed_office_hours[i] = parse_office_hours_string(hours[0]);
265 }
266
267 for (i = 1; i < office_hours->nelts; ++i) {
268 j = WEEK_LENGTH - office_hours->nelts + i;
269 parsed_office_hours[j] = parse_office_hours_string(hours[i]);
270 }
271
272 return parsed_office_hours;
273}
274
275/*
276 * Given a time string or the closed token, return a tuple
277 * of numbers representing opening and closing hours
278 */
279
280static ngx_uint_t * parse_office_hours_string(ngx_str_t office_hours)
281{
282
80c4c3cc
BB
283 int captures[(1 + 4) * 3];
284 ngx_int_t n;
fbdf81df
BB
285 ngx_uint_t * parsed_hours;
286
287 parsed_hours = malloc(2 * sizeof(ngx_uint_t));
288
289 if(ngx_strcmp(office_hours.data, CLOSED_TOKEN) == 0) {
290 parsed_hours[0] = 0;
291 parsed_hours[1] = 0;
292 return parsed_hours;
293 }
294
80c4c3cc
BB
295 n = ngx_regex_exec(rc.regex, &office_hours, captures, (1 + 4) * 3);
296
297 if (n >= 0) {
298 /* Opening Hours */
299
bbe15175
BB
300 parsed_hours[0] = 60 * 60 * parse_number(office_hours, captures[2], captures[3]);
301 parsed_hours[0] = parsed_hours[0] + 60 * parse_number(office_hours, captures[4], captures[5]);
80c4c3cc 302
bbe15175
BB
303 parsed_hours[1] = 60 * 60 * parse_number(office_hours, captures[6], captures[7]);
304 parsed_hours[1] = parsed_hours[1] + 60 * parse_number(office_hours, captures[8], captures[9]);
80c4c3cc
BB
305
306 return parsed_hours;
307 }
308
309 /* Non-matching strings count as open */
310
fbdf81df
BB
311 parsed_hours[0] = 0;
312 parsed_hours[1] = 86400;
313 return parsed_hours;
314}
315
316/*
317 * Given an office hours array, it returns whether or not
318 * it is currently within office hours.
319 */
320
321static ngx_flag_t within_office_hours(ngx_uint_t ** office_hours)
322{
323
324 time_t now;
325 ngx_uint_t day_of_week, seconds_of_day;
326 ngx_uint_t * current_hours;
327
328 ngx_time_update();
329 now = ngx_time();
330 day_of_week = get_day_of_week(now);
331 seconds_of_day = get_seconds_of_day(now);
332 current_hours = office_hours[day_of_week];
333
334 return seconds_of_day >= current_hours[0] && seconds_of_day <= current_hours[1];
335}
336
337/*
338 * Calculate the day of the week given a timestamp
339 */
340static ngx_uint_t get_day_of_week(time_t time)
341{
342
343 /* Epoch was thursday, so add 3 so we start on monday */
344 return (time / 86400 + 3) % 7;
345}
346
347/*
348 * Calculate the number of seconds elapsed today
349 */
350static ngx_uint_t get_seconds_of_day(time_t time)
351{
352
353 return time - (time / 86400) * 86400;
354}
bbe15175
BB
355
356/*
357 * Parses a string, returns 0 if match was not found
358 */
359static ngx_uint_t parse_number(ngx_str_t string, ngx_uint_t start, ngx_uint_t end)
360{
361
362 if (end - start == 0) {
363 return 0;
364 }
365
366 return ngx_atoi(&string.data[start], end - start);
367}
368
369/*
370 * Postconfig Initialization Handler
371 * Sets the request filter at the top of the chain
372 */
373
374static ngx_int_t ngx_http_office_hours_init(ngx_conf_t * cf)
375{
376
377 ngx_http_next_body_filter = ngx_http_top_body_filter;
378 ngx_http_top_body_filter = ngx_http_office_hours_body_filter;
379
380 rc.pattern = TIME_REGEX;
381 rc.pool = cf->pool;
382 if (ngx_regex_compile(&rc) != NGX_OK) {
383 return NGX_ERROR;
384 }
385
386 return NGX_OK;
387}