]> git.r.bdr.sh - rbdr/ngx_http_office_hours_filter_module/blob - 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
1 /*
2 * Copyright 2018 Rubén Beltrán del Río
3 *
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
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
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
19 /*
20 * Declarations
21 */
22
23 /* Constants */
24
25 const ngx_uint_t WEEK_LENGTH = 7;
26 const char * CLOSED_TOKEN = "closed";
27 const ngx_str_t TIME_REGEX = ngx_string("([0-9]{1,2})(?:\\:([0-9]{2}))?\\-([0-9]{1,2})(?:\\:([0-9]{2}))?");
28
29 /* Main Configuration Structure */
30
31 typedef struct {
32 ngx_array_t *office_hours;
33 } ngx_http_office_hours_conf_t;
34
35 /* Lifecycle Functions For Module Context */
36
37 static void *ngx_http_office_hours_create_conf(ngx_conf_t * cf);
38 static char *ngx_http_office_hours_merge_conf(ngx_conf_t * cf,
39 void *parent, void *child);
40 static ngx_int_t ngx_http_office_hours_init(ngx_conf_t * cf);
41
42 /* Configuration Handler */
43
44 static char *ngx_http_office_hours(ngx_conf_t * cf, ngx_command_t * cmd,
45 void *conf);
46
47 /* Body Filter Storage */
48
49 static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
50
51 /* Utility Functions */
52 static ngx_uint_t ** parse_office_hours(ngx_array_t * office_hours);
53 static ngx_uint_t * parse_office_hours_string(ngx_str_t office_hours);
54 static ngx_flag_t within_office_hours(ngx_uint_t ** office_hours);
55 static ngx_uint_t get_day_of_week(time_t time);
56 static ngx_uint_t get_seconds_of_day(time_t time);
57 static ngx_uint_t parse_number(ngx_str_t string, ngx_uint_t start, ngx_uint_t end);
58
59 /* Compiled Regex */
60 ngx_regex_compile_t rc;
61
62 /*
63 * Module Definitions
64 */
65
66 /* Module Directives */
67
68 static ngx_command_t ngx_http_office_hours_commands[] = {
69 {
70 ngx_string("office_hours"),
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,
72 ngx_http_office_hours,
73 NGX_HTTP_LOC_CONF_OFFSET,
74 offsetof(ngx_http_office_hours_conf_t, office_hours),
75 NULL
76 },
77
78 ngx_null_command
79 };
80
81
82 /* Module Context */
83
84 static ngx_http_module_t ngx_http_office_hours_filter_module_ctx = {
85 NULL, /* Preconfiguration */
86 ngx_http_office_hours_init, /* Postconfiguration */
87
88 NULL, /* Create main configuration */
89 NULL, /* Initialize main configuration */
90
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 */
96 };
97
98
99 /* Module Definition */
100
101 ngx_module_t ngx_http_office_hours_filter_module = {
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
114 };
115
116
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 */
122
123 static ngx_int_t
124 ngx_http_office_hours_body_filter(ngx_http_request_t * r, ngx_chain_t * in)
125 {
126
127 ngx_uint_t ** parsed_office_hours;
128 ngx_http_office_hours_conf_t *conf;
129
130 conf =
131 ngx_http_get_module_loc_conf(r,
132 ngx_http_office_hours_filter_module);
133
134
135 if (conf->office_hours == NULL) {
136 ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0,
137 "Office hours disabled");
138 return ngx_http_next_body_filter(r, in);
139 }
140
141 parsed_office_hours = parse_office_hours(conf->office_hours);
142
143 if (within_office_hours(parsed_office_hours)) {
144 ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0,
145 "Within office hours");
146 return ngx_http_next_body_filter(r, in);
147 }
148
149 ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0,
150 "Outside office hours");
151
152 r->keepalive = 0;
153 return NGX_HTTP_FORBIDDEN;
154 }
155
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 */
161
162 static char *ngx_http_office_hours(ngx_conf_t * cf, ngx_command_t * cmd,
163 void *conf)
164 {
165
166 char *conf_structure = conf;
167
168 ngx_str_t *hours, *value;
169 ngx_array_t **office_hours;
170 ngx_uint_t i;
171
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);
178
179 if (*office_hours == NGX_CONF_UNSET_PTR) {
180 *office_hours = ngx_array_create(cf->pool, cf->args->nelts - 1,
181 sizeof(ngx_str_t));
182
183 if (*office_hours == NULL) {
184 return NGX_CONF_ERROR;
185 }
186 }
187 value = cf->args->elts;
188
189 for (i = 1; i < cf->args->nelts; ++i) {
190 hours = ngx_array_push(*office_hours);
191 if (hours == NULL) {
192 return NGX_CONF_ERROR;
193 }
194 *hours = value[i];
195 }
196
197 return NGX_CONF_OK;
198 }
199
200
201 /*
202 * Config Creator
203 * Initializes the configuration structure
204 */
205
206 static void *ngx_http_office_hours_create_conf(ngx_conf_t * cf)
207 {
208
209 ngx_http_office_hours_conf_t *conf;
210
211 conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_office_hours_conf_t));
212
213 if (conf == NULL) {
214 return NULL;
215 }
216 conf->office_hours = NGX_CONF_UNSET_PTR;
217
218 return conf;
219 }
220
221 /*
222 * Merge Config Values
223 * Sets the defaults for the configuration and merges
224 * with other configurations
225 */
226
227 static char *ngx_http_office_hours_merge_conf(ngx_conf_t * cf,
228 void *parent, void *child)
229 {
230
231 ngx_http_office_hours_conf_t *prev = parent;
232 ngx_http_office_hours_conf_t *conf = child;
233
234 ngx_conf_merge_ptr_value(conf->office_hours, prev->office_hours, NULL);
235
236 return NGX_CONF_OK;
237 }
238
239 /*
240 * Parse the office hour strings in the configuration file
241 * to fill out the hours array (in seconds)
242 */
243
244 static 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
280 static ngx_uint_t * parse_office_hours_string(ngx_str_t office_hours)
281 {
282
283 int captures[(1 + 4) * 3];
284 ngx_int_t n;
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
295 n = ngx_regex_exec(rc.regex, &office_hours, captures, (1 + 4) * 3);
296
297 if (n >= 0) {
298 /* Opening Hours */
299
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]);
302
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]);
305
306 return parsed_hours;
307 }
308
309 /* Non-matching strings count as open */
310
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
321 static 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 */
340 static 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 */
350 static ngx_uint_t get_seconds_of_day(time_t time)
351 {
352
353 return time - (time / 86400) * 86400;
354 }
355
356 /*
357 * Parses a string, returns 0 if match was not found
358 */
359 static 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
374 static 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 }