]> git.r.bdr.sh - rbdr/ngx_http_office_hours_filter_module/blame_incremental - ngx_http_office_hours_filter_module.c
Add support for "closed" and reading config
[rbdr/ngx_http_office_hours_filter_module] / ngx_http_office_hours_filter_module.c
... / ...
CommitLineData
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
25const ngx_uint_t WEEK_LENGTH = 7;
26const char * CLOSED_TOKEN = "closed";
27
28/* Main Configuration Structure */
29
30typedef struct {
31 ngx_array_t *office_hours;
32} ngx_http_office_hours_conf_t;
33
34/* Lifecycle Functions For Module Context */
35
36static void *ngx_http_office_hours_create_conf(ngx_conf_t * cf);
37static char *ngx_http_office_hours_merge_conf(ngx_conf_t * cf,
38 void *parent, void *child);
39static ngx_int_t ngx_http_office_hours_init(ngx_conf_t * cf);
40
41/* Configuration Handler */
42
43static char *ngx_http_office_hours(ngx_conf_t * cf, ngx_command_t * cmd,
44 void *conf);
45
46/* Body Filter Storage */
47
48static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
49
50/* Utility Functions */
51static ngx_uint_t ** parse_office_hours(ngx_array_t * office_hours);
52static ngx_uint_t * parse_office_hours_string(ngx_str_t office_hours);
53static ngx_flag_t within_office_hours(ngx_uint_t ** office_hours);
54static ngx_uint_t get_day_of_week(time_t time);
55static ngx_uint_t get_seconds_of_day(time_t time);
56
57/*
58 * Module Definitions
59 */
60
61/* Module Directives */
62
63static ngx_command_t ngx_http_office_hours_commands[] = {
64 {
65 ngx_string("office_hours"),
66 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,
67 ngx_http_office_hours,
68 NGX_HTTP_LOC_CONF_OFFSET,
69 offsetof(ngx_http_office_hours_conf_t, office_hours),
70 NULL
71 },
72
73 ngx_null_command
74};
75
76
77/* Module Context */
78
79static ngx_http_module_t ngx_http_office_hours_filter_module_ctx = {
80 NULL, /* Preconfiguration */
81 ngx_http_office_hours_init, /* Postconfiguration */
82
83 NULL, /* Create main configuration */
84 NULL, /* Initialize main configuration */
85
86 NULL, /* Create server configuration */
87 NULL, /* Merge server configuration */
88
89 ngx_http_office_hours_create_conf, /* Create location configuration */
90 ngx_http_office_hours_merge_conf /* Merge location configuration */
91};
92
93
94/* Module Definition */
95
96ngx_module_t ngx_http_office_hours_filter_module = {
97 NGX_MODULE_V1, //Module Version
98 &ngx_http_office_hours_filter_module_ctx, //Module context
99 ngx_http_office_hours_commands, //Module commands
100 NGX_HTTP_MODULE, //Module Type
101 NULL, //Initialize Master
102 NULL, //Initialize Module
103 NULL, //Initialize Process
104 NULL, //Initialize Thread
105 NULL, //Exit Thread
106 NULL, //Exit Process
107 NULL, //Exit Master
108 NGX_MODULE_V1_PADDING
109};
110
111
112/*
113 * Main Body Filter
114 * If the current time is within office hours, it goes to the next
115 * handler. Otherwise it returns 403 and the office hour listing.
116 */
117
118static ngx_int_t
119ngx_http_office_hours_body_filter(ngx_http_request_t * r, ngx_chain_t * in)
120{
121
122 ngx_uint_t ** parsed_office_hours;
123 ngx_http_office_hours_conf_t *conf;
124
125 conf =
126 ngx_http_get_module_loc_conf(r,
127 ngx_http_office_hours_filter_module);
128
129
130 if (conf->office_hours == NULL) {
131 ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0,
132 "Office hours disabled");
133 return ngx_http_next_body_filter(r, in);
134 }
135
136 parsed_office_hours = parse_office_hours(conf->office_hours);
137
138 if (within_office_hours(parsed_office_hours)) {
139 ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0,
140 "Within office hours");
141 return ngx_http_next_body_filter(r, in);
142 }
143
144 ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0,
145 "Outside office hours");
146
147 r->keepalive = 0;
148 return NGX_HTTP_FORBIDDEN;
149}
150
151/*
152 * Callback for `office_hours ` directive
153 * Reads the configuration loaded from the config file(cf)
154 * And writes it to the right place in the module configuration(conf)
155 */
156
157static char *ngx_http_office_hours(ngx_conf_t * cf, ngx_command_t * cmd,
158 void *conf)
159{
160
161 char *conf_structure = conf;
162
163 ngx_str_t *hours, *value;
164 ngx_array_t **office_hours;
165 ngx_uint_t i;
166
167 /* Gets the array from the config structure using the defined
168 * offset, and if the pointer is unset it creates a new one.
169 * (The first element is the directive itself, so we should be
170 * offset by 1)
171 */
172 office_hours = (ngx_array_t **) (conf_structure + cmd->offset);
173
174 if (*office_hours == NGX_CONF_UNSET_PTR) {
175 *office_hours = ngx_array_create(cf->pool, cf->args->nelts - 1,
176 sizeof(ngx_str_t));
177
178 if (*office_hours == NULL) {
179 return NGX_CONF_ERROR;
180 }
181 }
182 value = cf->args->elts;
183
184 for (i = 1; i < cf->args->nelts; ++i) {
185 hours = ngx_array_push(*office_hours);
186 if (hours == NULL) {
187 return NGX_CONF_ERROR;
188 }
189 *hours = value[i];
190 }
191
192 return NGX_CONF_OK;
193}
194
195
196/*
197 * Config Creator
198 * Initializes the configuration structure
199 */
200
201static void *ngx_http_office_hours_create_conf(ngx_conf_t * cf)
202{
203
204 ngx_http_office_hours_conf_t *conf;
205
206 conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_office_hours_conf_t));
207
208 if (conf == NULL) {
209 return NULL;
210 }
211 conf->office_hours = NGX_CONF_UNSET_PTR;
212
213 return conf;
214}
215
216/*
217 * Merge Config Values
218 * Sets the defaults for the configuration and merges
219 * with other configurations
220 */
221
222static char *ngx_http_office_hours_merge_conf(ngx_conf_t * cf,
223 void *parent, void *child)
224{
225
226 ngx_http_office_hours_conf_t *prev = parent;
227 ngx_http_office_hours_conf_t *conf = child;
228
229 ngx_conf_merge_ptr_value(conf->office_hours, prev->office_hours, NULL);
230
231 return NGX_CONF_OK;
232}
233
234/*
235 * Postconfig Initialization Handler
236 * Sets the request filter at the top of the chain
237 */
238
239static ngx_int_t ngx_http_office_hours_init(ngx_conf_t * cf)
240{
241
242 ngx_http_next_body_filter = ngx_http_top_body_filter;
243 ngx_http_top_body_filter = ngx_http_office_hours_body_filter;
244
245 return NGX_OK;
246}
247
248/*
249 * Parse the office hour strings in the configuration file
250 * to fill out the hours array (in seconds)
251 */
252
253static ngx_uint_t ** parse_office_hours(ngx_array_t * office_hours)
254{
255
256 ngx_str_t *hours;
257 ngx_uint_t ** parsed_office_hours;
258 ngx_uint_t i, j;
259
260 parsed_office_hours = malloc(7 * sizeof(ngx_uint_t *));
261
262 hours = office_hours->elts;
263
264 /*
265 * On the configuration file, the leftmost element
266 * always applies to all remaining days, all others
267 * are read from right to left. So first we will apply
268 * the initial override, and then iterate based on the
269 * number of overrides
270 */
271
272 for (i = 0; i < WEEK_LENGTH + 1 - office_hours->nelts; ++i) {
273 parsed_office_hours[i] = parse_office_hours_string(hours[0]);
274 }
275
276 for (i = 1; i < office_hours->nelts; ++i) {
277 j = WEEK_LENGTH - office_hours->nelts + i;
278 parsed_office_hours[j] = parse_office_hours_string(hours[i]);
279 }
280
281 return parsed_office_hours;
282}
283
284/*
285 * Given a time string or the closed token, return a tuple
286 * of numbers representing opening and closing hours
287 */
288
289static ngx_uint_t * parse_office_hours_string(ngx_str_t office_hours)
290{
291
292 ngx_uint_t * parsed_hours;
293
294 parsed_hours = malloc(2 * sizeof(ngx_uint_t));
295
296 if(ngx_strcmp(office_hours.data, CLOSED_TOKEN) == 0) {
297 parsed_hours[0] = 0;
298 parsed_hours[1] = 0;
299 return parsed_hours;
300 }
301
302 parsed_hours[0] = 0;
303 parsed_hours[1] = 86400;
304 return parsed_hours;
305}
306
307/*
308 * Given an office hours array, it returns whether or not
309 * it is currently within office hours.
310 */
311
312static ngx_flag_t within_office_hours(ngx_uint_t ** office_hours)
313{
314
315 time_t now;
316 ngx_uint_t day_of_week, seconds_of_day;
317 ngx_uint_t * current_hours;
318
319 ngx_time_update();
320 now = ngx_time();
321 day_of_week = get_day_of_week(now);
322 seconds_of_day = get_seconds_of_day(now);
323 current_hours = office_hours[day_of_week];
324
325 return seconds_of_day >= current_hours[0] && seconds_of_day <= current_hours[1];
326}
327
328/*
329 * Calculate the day of the week given a timestamp
330 */
331static ngx_uint_t get_day_of_week(time_t time)
332{
333
334 /* Epoch was thursday, so add 3 so we start on monday */
335 return (time / 86400 + 3) % 7;
336}
337
338/*
339 * Calculate the number of seconds elapsed today
340 */
341static ngx_uint_t get_seconds_of_day(time_t time)
342{
343
344 return time - (time / 86400) * 86400;
345}