Coverage Report

Created: 2026-02-26 06:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/httpd/server/util_time.c
Line
Count
Source
1
/* Licensed to the Apache Software Foundation (ASF) under one or more
2
 * contributor license agreements.  See the NOTICE file distributed with
3
 * this work for additional information regarding copyright ownership.
4
 * The ASF licenses this file to You under the Apache License, Version 2.0
5
 * (the "License"); you may not use this file except in compliance with
6
 * the License.  You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
17
#include "util_time.h"
18
#include "apr_env.h"
19
20
21
22
/* Number of characters needed to format the microsecond part of a timestamp.
23
 * Microseconds have 6 digits plus one separator character makes 7.
24
 *   */
25
0
#define AP_CTIME_USEC_LENGTH      7
26
27
/* Number of characters needed to format the millisecond part of a timestamp.
28
 * Milliseconds have 3 digits plus one separator character makes 4.
29
 *   */
30
0
#define AP_CTIME_MSEC_LENGTH      4
31
32
/* Length of ISO 8601 date/time (including trailing '\0') */
33
0
#define AP_CTIME_COMPACT_LEN      20
34
35
/* Length of timezone offset from GMT ([+-]hhmm) plus leading space */
36
0
#define AP_CTIME_GMTOFF_LEN       6
37
38
/* Cache for exploded values of recent timestamps
39
 */
40
41
struct exploded_time_cache_element {
42
    apr_int64_t t;
43
    apr_time_exp_t xt;
44
    apr_int64_t t_validate; /* please see comments in cached_explode() */
45
};
46
47
/* the "+ 1" is for the current second: */
48
#define TIME_CACHE_SIZE (AP_TIME_RECENT_THRESHOLD + 1)
49
50
/* Note that AP_TIME_RECENT_THRESHOLD is defined to
51
 * be a power of two minus one in util_time.h, so that
52
 * we can replace a modulo operation with a bitwise AND
53
 * when hashing items into a cache of size
54
 * AP_TIME_RECENT_THRESHOLD+1
55
 */
56
0
#define TIME_CACHE_MASK (AP_TIME_RECENT_THRESHOLD)
57
58
static struct exploded_time_cache_element exploded_cache_localtime[TIME_CACHE_SIZE];
59
static struct exploded_time_cache_element exploded_cache_gmt[TIME_CACHE_SIZE];
60
61
62
static apr_status_t cached_explode(apr_time_exp_t *xt, apr_time_t t,
63
                                   struct exploded_time_cache_element *cache,
64
                                   int use_gmt)
65
0
{
66
0
    apr_int64_t seconds = apr_time_sec(t);
67
0
    struct exploded_time_cache_element *cache_element =
68
0
        &(cache[seconds & TIME_CACHE_MASK]);
69
0
    struct exploded_time_cache_element cache_element_snapshot;
70
71
    /* The cache is implemented as a ring buffer.  Each second,
72
     * it uses a different element in the buffer.  The timestamp
73
     * in the element indicates whether the element contains the
74
     * exploded time for the current second (vs the time
75
     * 'now - AP_TIME_RECENT_THRESHOLD' seconds ago).  If the
76
     * cached value is for the current time, we use it.  Otherwise,
77
     * we compute the apr_time_exp_t and store it in this
78
     * cache element. Note that the timestamp in the cache
79
     * element is updated only after the exploded time.  Thus
80
     * if two threads hit this cache element simultaneously
81
     * at the start of a new second, they'll both explode the
82
     * time and store it.  I.e., the writers will collide, but
83
     * they'll be writing the same value.
84
     */
85
0
    if (cache_element->t >= seconds) {
86
        /* There is an intentional race condition in this design:
87
         * in a multithreaded app, one thread might be reading
88
         * from this cache_element to resolve a timestamp from
89
         * TIME_CACHE_SIZE seconds ago at the same time that
90
         * another thread is copying the exploded form of the
91
         * current time into the same cache_element.  (I.e., the
92
         * first thread might hit this element of the ring buffer
93
         * just as the element is being recycled.)  This can
94
         * also happen at the start of a new second, if a
95
         * reader accesses the cache_element after a writer
96
         * has updated cache_element.t but before the writer
97
         * has finished updating the whole cache_element.
98
         *
99
         * Rather than trying to prevent this race condition
100
         * with locks, we allow it to happen and then detect
101
         * and correct it.  The detection works like this:
102
         *   Step 1: Take a "snapshot" of the cache element by
103
         *           copying it into a temporary buffer.
104
         *   Step 2: Check whether the snapshot contains consistent
105
         *           data: the timestamps at the start and end of
106
         *           the cache_element should both match the 'seconds'
107
         *           value that we computed from the input time.
108
         *           If these three don't match, then the snapshot
109
         *           shows the cache_element in the middle of an
110
         *           update, and its contents are invalid.
111
         *   Step 3: If the snapshot is valid, use it.  Otherwise,
112
         *           just give up on the cache and explode the
113
         *           input time.
114
         */
115
0
        memcpy(&cache_element_snapshot, cache_element,
116
0
               sizeof(struct exploded_time_cache_element));
117
0
        if ((seconds != cache_element_snapshot.t) ||
118
0
            (seconds != cache_element_snapshot.t_validate)) {
119
            /* Invalid snapshot */
120
0
            if (use_gmt) {
121
0
                return apr_time_exp_gmt(xt, t);
122
0
            }
123
0
            else {
124
0
                return apr_time_exp_lt(xt, t);
125
0
            }
126
0
        }
127
0
        else {
128
            /* Valid snapshot */
129
0
            memcpy(xt, &(cache_element_snapshot.xt),
130
0
                   sizeof(apr_time_exp_t));
131
0
        }
132
0
    }
133
0
    else {
134
0
        apr_status_t r;
135
0
        if (use_gmt) {
136
0
            r = apr_time_exp_gmt(xt, t);
137
0
        }
138
0
        else {
139
0
            r = apr_time_exp_lt(xt, t);
140
0
        }
141
0
        if (r != APR_SUCCESS) {
142
0
            return r;
143
0
        }
144
0
        cache_element->t = seconds;
145
0
        memcpy(&(cache_element->xt), xt, sizeof(apr_time_exp_t));
146
0
        cache_element->t_validate = seconds;
147
0
    }
148
0
    xt->tm_usec = (int)apr_time_usec(t);
149
0
    return APR_SUCCESS;
150
0
}
151
152
153
AP_DECLARE(apr_status_t) ap_explode_recent_localtime(apr_time_exp_t * tm,
154
                                                     apr_time_t t)
155
0
{
156
0
    return cached_explode(tm, t, exploded_cache_localtime, 0);
157
0
}
158
159
AP_DECLARE(apr_status_t) ap_explode_recent_gmt(apr_time_exp_t * tm,
160
                                               apr_time_t t)
161
0
{
162
0
    return cached_explode(tm, t, exploded_cache_gmt, 1);
163
0
}
164
165
AP_DECLARE(apr_status_t) ap_recent_ctime(char *date_str, apr_time_t t)
166
0
{
167
0
    int len = APR_CTIME_LEN;
168
0
    return ap_recent_ctime_ex(date_str, t, AP_CTIME_OPTION_NONE, &len);
169
0
}
170
171
AP_DECLARE(apr_status_t) ap_recent_ctime_ex(char *date_str, apr_time_t t,
172
                                            int option, int *len)
173
0
{
174
    /* ### This code is a clone of apr_ctime(), except that it
175
     * uses ap_explode_recent_localtime() instead of apr_time_exp_lt().
176
     */
177
0
    apr_time_exp_t xt;
178
0
    const char *s;
179
0
    int real_year;
180
0
    int needed;
181
182
183
    /* Calculate the needed buffer length */
184
0
    if (option & AP_CTIME_OPTION_COMPACT)
185
0
        needed = AP_CTIME_COMPACT_LEN;
186
0
    else
187
0
        needed = APR_CTIME_LEN;
188
189
0
    if (option & AP_CTIME_OPTION_USEC) {
190
0
        needed += AP_CTIME_USEC_LENGTH;
191
0
    }
192
0
    else if (option & AP_CTIME_OPTION_MSEC) {
193
0
        needed += AP_CTIME_MSEC_LENGTH;
194
0
    }
195
196
0
    if (option & AP_CTIME_OPTION_GMTOFF) {
197
0
        needed += AP_CTIME_GMTOFF_LEN;
198
0
    }
199
200
    /* Check the provided buffer length (note: above AP_CTIME_COMPACT_LEN
201
     * and APR_CTIME_LEN include the trailing '\0'; so does 'needed' then).
202
     */
203
0
    if (len && *len >= needed) {
204
0
        *len = needed;
205
0
    }
206
0
    else {
207
0
        if (len != NULL) {
208
0
            *len = 0;
209
0
        }
210
0
        return APR_ENOMEM;
211
0
    }
212
213
    /* example without options: "Wed Jun 30 21:49:08 1993" */
214
    /* example for compact format: "1993-06-30 21:49:08" */
215
    /* example for compact+usec+gmtoff format:
216
     *     "1993-06-30 22:49:08.123456 +0100"
217
     */
218
219
0
    ap_explode_recent_localtime(&xt, t);
220
0
    real_year = 1900 + xt.tm_year;
221
0
    if (option & AP_CTIME_OPTION_COMPACT) {
222
0
        int real_month = xt.tm_mon + 1;
223
0
        *date_str++ = real_year / 1000 + '0';
224
0
        *date_str++ = real_year % 1000 / 100 + '0';
225
0
        *date_str++ = real_year % 100 / 10 + '0';
226
0
        *date_str++ = real_year % 10 + '0';
227
0
        *date_str++ = '-';
228
0
        *date_str++ = real_month / 10 + '0';
229
0
        *date_str++ = real_month % 10 + '0';
230
0
        *date_str++ = '-';
231
0
    }
232
0
    else {
233
0
        s = &apr_day_snames[xt.tm_wday][0];
234
0
        *date_str++ = *s++;
235
0
        *date_str++ = *s++;
236
0
        *date_str++ = *s++;
237
0
        *date_str++ = ' ';
238
0
        s = &apr_month_snames[xt.tm_mon][0];
239
0
        *date_str++ = *s++;
240
0
        *date_str++ = *s++;
241
0
        *date_str++ = *s++;
242
0
        *date_str++ = ' ';
243
0
    }
244
0
    *date_str++ = xt.tm_mday / 10 + '0';
245
0
    *date_str++ = xt.tm_mday % 10 + '0';
246
0
    *date_str++ = ' ';
247
0
    *date_str++ = xt.tm_hour / 10 + '0';
248
0
    *date_str++ = xt.tm_hour % 10 + '0';
249
0
    *date_str++ = ':';
250
0
    *date_str++ = xt.tm_min / 10 + '0';
251
0
    *date_str++ = xt.tm_min % 10 + '0';
252
0
    *date_str++ = ':';
253
0
    *date_str++ = xt.tm_sec / 10 + '0';
254
0
    *date_str++ = xt.tm_sec % 10 + '0';
255
0
    if (option & (AP_CTIME_OPTION_USEC|AP_CTIME_OPTION_MSEC)) {
256
0
        int div;
257
0
        int usec = (int)xt.tm_usec;
258
0
        *date_str++ = '.';
259
0
        div = 100000;
260
0
        if (!(option & AP_CTIME_OPTION_USEC)) {
261
0
            usec = usec / 1000;
262
0
            div = 100;
263
0
        }
264
0
        for (; div>0; div=div/10) {
265
0
            *date_str++ = usec / div + '0';
266
0
            usec = usec % div;
267
0
        }
268
0
    }
269
0
    if (!(option & AP_CTIME_OPTION_COMPACT)) {
270
0
        *date_str++ = ' ';
271
0
        *date_str++ = real_year / 1000 + '0';
272
0
        *date_str++ = real_year % 1000 / 100 + '0';
273
0
        *date_str++ = real_year % 100 / 10 + '0';
274
0
        *date_str++ = real_year % 10 + '0';
275
0
    }
276
0
    if (option & AP_CTIME_OPTION_GMTOFF) {
277
0
        int off = xt.tm_gmtoff, off_hh, off_mm;
278
0
        char sign = '+';
279
0
        if (off < 0) {
280
0
            off = -off;
281
0
            sign = '-';
282
0
        }
283
0
        off_hh = off / 3600;
284
0
        off_mm = off % 3600 / 60;
285
0
        *date_str++ = ' ';
286
0
        *date_str++ = sign;
287
0
        *date_str++ = off_hh / 10 + '0';
288
0
        *date_str++ = off_hh % 10 + '0';
289
0
        *date_str++ = off_mm / 10 + '0';
290
0
        *date_str++ = off_mm % 10 + '0';
291
0
    }
292
0
    *date_str = 0;
293
294
0
    return APR_SUCCESS;
295
0
}
296
297
AP_DECLARE(apr_status_t) ap_recent_rfc822_date(char *date_str, apr_time_t t)
298
0
{
299
    /* ### This code is a clone of apr_rfc822_date(), except that it
300
     * uses ap_explode_recent_gmt() instead of apr_time_exp_gmt().
301
     */
302
0
    apr_time_exp_t xt;
303
0
    const char *s;
304
0
    int real_year;
305
306
0
    ap_explode_recent_gmt(&xt, t);
307
308
    /* example: "Sat, 08 Jan 2000 18:31:41 GMT" */
309
    /*           12345678901234567890123456789  */
310
311
0
    s = &apr_day_snames[xt.tm_wday][0];
312
0
    *date_str++ = *s++;
313
0
    *date_str++ = *s++;
314
0
    *date_str++ = *s++;
315
0
    *date_str++ = ',';
316
0
    *date_str++ = ' ';
317
0
    *date_str++ = xt.tm_mday / 10 + '0';
318
0
    *date_str++ = xt.tm_mday % 10 + '0';
319
0
    *date_str++ = ' ';
320
0
    s = &apr_month_snames[xt.tm_mon][0];
321
0
    *date_str++ = *s++;
322
0
    *date_str++ = *s++;
323
0
    *date_str++ = *s++;
324
0
    *date_str++ = ' ';
325
0
    real_year = 1900 + xt.tm_year;
326
    /* This routine isn't y10k ready. */
327
0
    *date_str++ = real_year / 1000 + '0';
328
0
    *date_str++ = real_year % 1000 / 100 + '0';
329
0
    *date_str++ = real_year % 100 / 10 + '0';
330
0
    *date_str++ = real_year % 10 + '0';
331
0
    *date_str++ = ' ';
332
0
    *date_str++ = xt.tm_hour / 10 + '0';
333
0
    *date_str++ = xt.tm_hour % 10 + '0';
334
0
    *date_str++ = ':';
335
0
    *date_str++ = xt.tm_min / 10 + '0';
336
0
    *date_str++ = xt.tm_min % 10 + '0';
337
0
    *date_str++ = ':';
338
0
    *date_str++ = xt.tm_sec / 10 + '0';
339
0
    *date_str++ = xt.tm_sec % 10 + '0';
340
0
    *date_str++ = ' ';
341
0
    *date_str++ = 'G';
342
0
    *date_str++ = 'M';
343
0
    *date_str++ = 'T';
344
0
    *date_str++ = 0;
345
0
    return APR_SUCCESS;
346
0
}
347
348
0
AP_DECLARE(void) ap_force_set_tz(apr_pool_t *p) {
349
    /* If the TZ variable is unset, many operating systems,
350
     * such as Linux, will at runtime read from /etc/localtime
351
     * and call fstat on it.
352
     *
353
     * By forcing the time zone to UTC if it is unset, we gain
354
     * about 2% in raw requests/second (since we format log files
355
     * in the local time, if present)
356
     *
357
     * For more info, see:
358
     *   <http://www.gnu.org/s/hello/manual/libc/TZ-Variable.html>
359
     */
360
0
    char *v = NULL;
361
362
0
    if (apr_env_get(&v, "TZ", p) != APR_SUCCESS) {
363
0
        apr_env_set("TZ", "UTC+0", p);
364
0
    }
365
0
}