2 * Copyright 2018 Rubén Beltrán del Río
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
7 * http://www.apache.org/licenses/LICENSE-2.0
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.
15 #include <ngx_config.h>
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}))?");
29 /* Main Configuration Structure */
32 ngx_array_t
*office_hours
;
33 } ngx_http_office_hours_conf_t
;
35 /* Lifecycle Functions For Module Context */
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
);
42 /* Configuration Handler */
44 static char *ngx_http_office_hours(ngx_conf_t
* cf
, ngx_command_t
* cmd
,
47 /* Body Filter Storage */
49 static ngx_http_output_body_filter_pt ngx_http_next_body_filter
;
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
);
60 ngx_regex_compile_t rc
;
66 /* Module Directives */
68 static ngx_command_t ngx_http_office_hours_commands
[] = {
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
),
84 static ngx_http_module_t ngx_http_office_hours_filter_module_ctx
= {
85 NULL
, /* Preconfiguration */
86 ngx_http_office_hours_init
, /* Postconfiguration */
88 NULL
, /* Create main configuration */
89 NULL
, /* Initialize main configuration */
91 NULL
, /* Create server configuration */
92 NULL
, /* Merge server configuration */
94 ngx_http_office_hours_create_conf
, /* Create location configuration */
95 ngx_http_office_hours_merge_conf
/* Merge location configuration */
99 /* Module Definition */
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
113 NGX_MODULE_V1_PADDING
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.
124 ngx_http_office_hours_body_filter(ngx_http_request_t
* r
, ngx_chain_t
* in
)
127 ngx_uint_t
** parsed_office_hours
;
128 ngx_http_office_hours_conf_t
*conf
;
131 ngx_http_get_module_loc_conf(r
,
132 ngx_http_office_hours_filter_module
);
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
);
141 parsed_office_hours
= parse_office_hours(conf
->office_hours
);
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
);
149 ngx_log_error(NGX_LOG_DEBUG
, r
->connection
->log
, 0,
150 "Outside office hours");
153 return NGX_HTTP_FORBIDDEN
;
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)
162 static char *ngx_http_office_hours(ngx_conf_t
* cf
, ngx_command_t
* cmd
,
166 char *conf_structure
= conf
;
168 ngx_str_t
*hours
, *value
;
169 ngx_array_t
**office_hours
;
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
177 office_hours
= (ngx_array_t
**) (conf_structure
+ cmd
->offset
);
179 if (*office_hours
== NGX_CONF_UNSET_PTR
) {
180 *office_hours
= ngx_array_create(cf
->pool
, cf
->args
->nelts
- 1,
183 if (*office_hours
== NULL
) {
184 return NGX_CONF_ERROR
;
187 value
= cf
->args
->elts
;
189 for (i
= 1; i
< cf
->args
->nelts
; ++i
) {
190 hours
= ngx_array_push(*office_hours
);
192 return NGX_CONF_ERROR
;
203 * Initializes the configuration structure
206 static void *ngx_http_office_hours_create_conf(ngx_conf_t
* cf
)
209 ngx_http_office_hours_conf_t
*conf
;
211 conf
= ngx_pcalloc(cf
->pool
, sizeof(ngx_http_office_hours_conf_t
));
216 conf
->office_hours
= NGX_CONF_UNSET_PTR
;
222 * Merge Config Values
223 * Sets the defaults for the configuration and merges
224 * with other configurations
227 static char *ngx_http_office_hours_merge_conf(ngx_conf_t
* cf
,
228 void *parent
, void *child
)
231 ngx_http_office_hours_conf_t
*prev
= parent
;
232 ngx_http_office_hours_conf_t
*conf
= child
;
234 ngx_conf_merge_ptr_value(conf
->office_hours
, prev
->office_hours
, NULL
);
240 * Parse the office hour strings in the configuration file
241 * to fill out the hours array (in seconds)
244 static ngx_uint_t
** parse_office_hours(ngx_array_t
* office_hours
)
248 ngx_uint_t
** parsed_office_hours
;
251 parsed_office_hours
= malloc(7 * sizeof(ngx_uint_t
*));
253 hours
= office_hours
->elts
;
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
263 for (i
= 0; i
< WEEK_LENGTH
+ 1 - office_hours
->nelts
; ++i
) {
264 parsed_office_hours
[i
] = parse_office_hours_string(hours
[0]);
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
]);
272 return parsed_office_hours
;
276 * Given a time string or the closed token, return a tuple
277 * of numbers representing opening and closing hours
280 static ngx_uint_t
* parse_office_hours_string(ngx_str_t office_hours
)
283 int captures
[(1 + 4) * 3];
285 ngx_uint_t
* parsed_hours
;
287 parsed_hours
= malloc(2 * sizeof(ngx_uint_t
));
289 if(ngx_strcmp(office_hours
.data
, CLOSED_TOKEN
) == 0) {
295 n
= ngx_regex_exec(rc
.regex
, &office_hours
, captures
, (1 + 4) * 3);
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]);
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]);
309 /* Non-matching strings count as open */
312 parsed_hours
[1] = 86400;
317 * Given an office hours array, it returns whether or not
318 * it is currently within office hours.
321 static ngx_flag_t
within_office_hours(ngx_uint_t
** office_hours
)
325 ngx_uint_t day_of_week
, seconds_of_day
;
326 ngx_uint_t
* current_hours
;
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
];
334 return seconds_of_day
>= current_hours
[0] && seconds_of_day
<= current_hours
[1];
338 * Calculate the day of the week given a timestamp
340 static ngx_uint_t
get_day_of_week(time_t time
)
343 /* Epoch was thursday, so add 3 so we start on monday */
344 return (time
/ 86400 + 3) % 7;
348 * Calculate the number of seconds elapsed today
350 static ngx_uint_t
get_seconds_of_day(time_t time
)
353 return time
- (time
/ 86400) * 86400;
357 * Parses a string, returns 0 if match was not found
359 static ngx_uint_t
parse_number(ngx_str_t string
, ngx_uint_t start
, ngx_uint_t end
)
362 if (end
- start
== 0) {
366 return ngx_atoi(&string
.data
[start
], end
- start
);
370 * Postconfig Initialization Handler
371 * Sets the request filter at the top of the chain
374 static ngx_int_t
ngx_http_office_hours_init(ngx_conf_t
* cf
)
377 ngx_http_next_body_filter
= ngx_http_top_body_filter
;
378 ngx_http_top_body_filter
= ngx_http_office_hours_body_filter
;
380 rc
.pattern
= TIME_REGEX
;
382 if (ngx_regex_compile(&rc
) != NGX_OK
) {