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}))?");
28 const ngx_str_t OUTPUT_HTML
= ngx_string("<!doctype html><html><head><title>This website is currently closed!</title></head><body><h1>This website is currently closed!</h1><p>This website has closed for the day, please check our office hours below</p></body></html>");
30 /* Main Configuration Structure */
33 ngx_array_t
*office_hours
;
34 } ngx_http_office_hours_conf_t
;
36 /* Lifecycle Functions For Module Context */
38 static void *ngx_http_office_hours_create_conf(ngx_conf_t
* cf
);
39 static char *ngx_http_office_hours_merge_conf(ngx_conf_t
* cf
,
40 void *parent
, void *child
);
41 static ngx_int_t
ngx_http_office_hours_init(ngx_conf_t
* cf
);
43 /* Configuration Handler */
45 static char *ngx_http_office_hours(ngx_conf_t
* cf
, ngx_command_t
* cmd
,
50 static ngx_http_output_header_filter_pt ngx_http_next_header_filter
;
51 static ngx_http_output_body_filter_pt ngx_http_next_body_filter
;
53 /* Utility Functions */
54 static ngx_uint_t
** parse_office_hours(ngx_array_t
* office_hours
);
55 static ngx_uint_t
* parse_office_hours_string(ngx_str_t office_hours
);
56 static ngx_flag_t
within_office_hours(ngx_uint_t
** office_hours
);
57 static ngx_uint_t
get_day_of_week(time_t time
);
58 static ngx_uint_t
get_seconds_of_day(time_t time
);
59 static ngx_uint_t
parse_number(ngx_str_t string
, ngx_uint_t start
, ngx_uint_t end
);
62 ngx_regex_compile_t rc
;
68 /* Module Directives */
70 static ngx_command_t ngx_http_office_hours_commands
[] = {
72 ngx_string("office_hours"),
73 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
,
74 ngx_http_office_hours
,
75 NGX_HTTP_LOC_CONF_OFFSET
,
76 offsetof(ngx_http_office_hours_conf_t
, office_hours
),
86 static ngx_http_module_t ngx_http_office_hours_filter_module_ctx
= {
87 NULL
, /* Preconfiguration */
88 ngx_http_office_hours_init
, /* Postconfiguration */
90 NULL
, /* Create main configuration */
91 NULL
, /* Initialize main configuration */
93 NULL
, /* Create server configuration */
94 NULL
, /* Merge server configuration */
96 ngx_http_office_hours_create_conf
, /* Create location configuration */
97 ngx_http_office_hours_merge_conf
/* Merge location configuration */
101 /* Module Definition */
103 ngx_module_t ngx_http_office_hours_filter_module
= {
104 NGX_MODULE_V1
, //Module Version
105 &ngx_http_office_hours_filter_module_ctx
, //Module context
106 ngx_http_office_hours_commands
, //Module commands
107 NGX_HTTP_MODULE
, //Module Type
108 NULL
, //Initialize Master
109 NULL
, //Initialize Module
110 NULL
, //Initialize Process
111 NULL
, //Initialize Thread
115 NGX_MODULE_V1_PADDING
121 * If the current time is within office hours, it goes to the next
122 * handler. Otherwise it sets the headers to 403
126 ngx_http_office_hours_header_filter(ngx_http_request_t
* r
)
129 ngx_uint_t
** parsed_office_hours
;
130 ngx_http_office_hours_conf_t
*conf
;
133 ngx_http_get_module_loc_conf(r
,
134 ngx_http_office_hours_filter_module
);
137 if (conf
->office_hours
== NULL
) {
138 ngx_log_error(NGX_LOG_DEBUG
, r
->connection
->log
, 0,
139 "Office hours disabled");
140 return ngx_http_next_header_filter(r
);
143 parsed_office_hours
= parse_office_hours(conf
->office_hours
);
145 if (within_office_hours(parsed_office_hours
)) {
146 ngx_log_error(NGX_LOG_DEBUG
, r
->connection
->log
, 0,
147 "Within office hours");
148 return ngx_http_next_header_filter(r
);
151 ngx_log_error(NGX_LOG_DEBUG
, r
->connection
->log
, 0,
152 "Outside office hours");
154 r
->headers_out
.status
= NGX_HTTP_FORBIDDEN
;
155 r
->headers_out
.content_length_n
= OUTPUT_HTML
.len
;
157 return ngx_http_next_header_filter(r
);
162 * If the current time is within office hours, it goes to the next
163 * handler. Otherwise it replaces the body with the office hours.
167 ngx_http_office_hours_body_filter(ngx_http_request_t
* r
, ngx_chain_t
*in
)
171 ngx_uint_t
** parsed_office_hours
;
173 ngx_http_office_hours_conf_t
*conf
;
176 ngx_http_get_module_loc_conf(r
,
177 ngx_http_office_hours_filter_module
);
180 if (conf
->office_hours
== NULL
) {
181 ngx_log_error(NGX_LOG_DEBUG
, r
->connection
->log
, 0,
182 "Office hours disabled");
183 return ngx_http_next_body_filter(r
, in
);
186 parsed_office_hours
= parse_office_hours(conf
->office_hours
);
188 if (within_office_hours(parsed_office_hours
)) {
189 ngx_log_error(NGX_LOG_DEBUG
, r
->connection
->log
, 0,
190 "Within office hours");
191 return ngx_http_next_body_filter(r
, in
);
194 ngx_log_error(NGX_LOG_DEBUG
, r
->connection
->log
, 0,
195 "Outside office hours");
197 b
= ngx_pcalloc(r
->pool
, sizeof(ngx_buf_t
));
199 ngx_log_error(NGX_LOG_ERR
, r
->connection
->log
, 0, "Failed to allocate response buffer.");
200 return NGX_HTTP_INTERNAL_SERVER_ERROR
;
206 b
->start
= OUTPUT_HTML
.data
;
208 b
->end
= OUTPUT_HTML
.data
+ OUTPUT_HTML
.len
;
214 return ngx_http_next_body_filter(r
, &out
);
218 * Callback for `office_hours ` directive
219 * Reads the configuration loaded from the config file(cf)
220 * And writes it to the right place in the module configuration(conf)
223 static char *ngx_http_office_hours(ngx_conf_t
* cf
, ngx_command_t
* cmd
,
227 char *conf_structure
= conf
;
229 ngx_str_t
*hours
, *value
;
230 ngx_array_t
**office_hours
;
233 /* Gets the array from the config structure using the defined
234 * offset, and if the pointer is unset it creates a new one.
235 * (The first element is the directive itself, so we should be
238 office_hours
= (ngx_array_t
**) (conf_structure
+ cmd
->offset
);
240 if (*office_hours
== NGX_CONF_UNSET_PTR
) {
241 *office_hours
= ngx_array_create(cf
->pool
, cf
->args
->nelts
- 1,
244 if (*office_hours
== NULL
) {
245 return NGX_CONF_ERROR
;
248 value
= cf
->args
->elts
;
250 for (i
= 1; i
< cf
->args
->nelts
; ++i
) {
251 hours
= ngx_array_push(*office_hours
);
253 return NGX_CONF_ERROR
;
264 * Initializes the configuration structure
267 static void *ngx_http_office_hours_create_conf(ngx_conf_t
* cf
)
270 ngx_http_office_hours_conf_t
*conf
;
272 conf
= ngx_pcalloc(cf
->pool
, sizeof(ngx_http_office_hours_conf_t
));
277 conf
->office_hours
= NGX_CONF_UNSET_PTR
;
283 * Merge Config Values
284 * Sets the defaults for the configuration and merges
285 * with other configurations
288 static char *ngx_http_office_hours_merge_conf(ngx_conf_t
* cf
,
289 void *parent
, void *child
)
292 ngx_http_office_hours_conf_t
*prev
= parent
;
293 ngx_http_office_hours_conf_t
*conf
= child
;
295 ngx_conf_merge_ptr_value(conf
->office_hours
, prev
->office_hours
, NULL
);
301 * Parse the office hour strings in the configuration file
302 * to fill out the hours array (in seconds)
305 static ngx_uint_t
** parse_office_hours(ngx_array_t
* office_hours
)
309 ngx_uint_t
** parsed_office_hours
;
312 parsed_office_hours
= malloc(7 * sizeof(ngx_uint_t
*));
314 hours
= office_hours
->elts
;
317 * On the configuration file, the leftmost element
318 * always applies to all remaining days, all others
319 * are read from right to left. So first we will apply
320 * the initial override, and then iterate based on the
321 * number of overrides
324 for (i
= 0; i
< WEEK_LENGTH
+ 1 - office_hours
->nelts
; ++i
) {
325 parsed_office_hours
[i
] = parse_office_hours_string(hours
[0]);
328 for (i
= 1; i
< office_hours
->nelts
; ++i
) {
329 j
= WEEK_LENGTH
- office_hours
->nelts
+ i
;
330 parsed_office_hours
[j
] = parse_office_hours_string(hours
[i
]);
333 return parsed_office_hours
;
337 * Given a time string or the closed token, return a tuple
338 * of numbers representing opening and closing hours
341 static ngx_uint_t
* parse_office_hours_string(ngx_str_t office_hours
)
344 int captures
[(1 + 4) * 3];
346 ngx_uint_t
* parsed_hours
;
348 parsed_hours
= malloc(2 * sizeof(ngx_uint_t
));
350 if(ngx_strcmp(office_hours
.data
, CLOSED_TOKEN
) == 0) {
356 n
= ngx_regex_exec(rc
.regex
, &office_hours
, captures
, (1 + 4) * 3);
361 parsed_hours
[0] = 60 * 60 * parse_number(office_hours
, captures
[2], captures
[3]);
362 parsed_hours
[0] = parsed_hours
[0] + 60 * parse_number(office_hours
, captures
[4], captures
[5]);
364 parsed_hours
[1] = 60 * 60 * parse_number(office_hours
, captures
[6], captures
[7]);
365 parsed_hours
[1] = parsed_hours
[1] + 60 * parse_number(office_hours
, captures
[8], captures
[9]);
370 /* Non-matching strings count as open */
373 parsed_hours
[1] = 86400;
378 * Given an office hours array, it returns whether or not
379 * it is currently within office hours.
382 static ngx_flag_t
within_office_hours(ngx_uint_t
** office_hours
)
386 ngx_uint_t day_of_week
, seconds_of_day
;
387 ngx_uint_t
* current_hours
;
391 day_of_week
= get_day_of_week(now
);
392 seconds_of_day
= get_seconds_of_day(now
);
393 current_hours
= office_hours
[day_of_week
];
395 return seconds_of_day
>= current_hours
[0] && seconds_of_day
<= current_hours
[1];
399 * Calculate the day of the week given a timestamp
401 static ngx_uint_t
get_day_of_week(time_t time
)
404 /* Epoch was thursday, so add 3 so we start on monday */
405 return (time
/ 86400 + 3) % 7;
409 * Calculate the number of seconds elapsed today
411 static ngx_uint_t
get_seconds_of_day(time_t time
)
414 return time
- (time
/ 86400) * 86400;
418 * Parses a string, returns 0 if match was not found
420 static ngx_uint_t
parse_number(ngx_str_t string
, ngx_uint_t start
, ngx_uint_t end
)
423 if (end
- start
== 0) {
427 return ngx_atoi(&string
.data
[start
], end
- start
);
431 * Postconfig Initialization Handler
432 * Sets the request filter at the top of the chain
435 static ngx_int_t
ngx_http_office_hours_init(ngx_conf_t
* cf
)
438 ngx_http_next_header_filter
= ngx_http_top_header_filter
;
439 ngx_http_top_header_filter
= ngx_http_office_hours_header_filter
;
441 ngx_http_next_body_filter
= ngx_http_top_body_filter
;
442 ngx_http_top_body_filter
= ngx_http_office_hours_body_filter
;
444 rc
.pattern
= TIME_REGEX
;
446 if (ngx_regex_compile(&rc
) != NGX_OK
) {