1 // Copyright 2018 Espressif Systems (Shanghai) PTE LTD
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // 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
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 
16 #include <stdlib.h>
17 #include <esp_log.h>
18 #include <esp_err.h>
19 
20 #include <esp_http_server.h>
21 #include "esp_httpd_priv.h"
22 
23 static const char *TAG = "httpd_sess";
24 
httpd_is_sess_available(struct httpd_data * hd)25 bool httpd_is_sess_available(struct httpd_data *hd)
26 {
27     int i;
28     for (i = 0; i < hd->config.max_open_sockets; i++) {
29         if (hd->hd_sd[i].fd == -1) {
30             return true;
31         }
32     }
33     return false;
34 }
35 
httpd_sess_get(struct httpd_data * hd,int sockfd)36 struct sock_db *httpd_sess_get(struct httpd_data *hd, int sockfd)
37 {
38     if (hd == NULL) {
39         return NULL;
40     }
41 
42     /* Check if called inside a request handler, and the
43      * session sockfd in use is same as the parameter */
44     if ((hd->hd_req_aux.sd) && (hd->hd_req_aux.sd->fd == sockfd)) {
45         /* Just return the pointer to the sock_db
46          * corresponding to the request */
47         return hd->hd_req_aux.sd;
48     }
49 
50     int i;
51     for (i = 0; i < hd->config.max_open_sockets; i++) {
52         if (hd->hd_sd[i].fd == sockfd) {
53             return &hd->hd_sd[i];
54         }
55     }
56     return NULL;
57 }
58 
httpd_sess_new(struct httpd_data * hd,int newfd)59 esp_err_t httpd_sess_new(struct httpd_data *hd, int newfd)
60 {
61     ESP_LOGD(TAG, LOG_FMT("fd = %d"), newfd);
62 
63     if (httpd_sess_get(hd, newfd)) {
64         ESP_LOGE(TAG, LOG_FMT("session already exists with fd = %d"), newfd);
65         return ESP_FAIL;
66     }
67 
68     int i;
69     for (i = 0; i < hd->config.max_open_sockets; i++) {
70         if (hd->hd_sd[i].fd == -1) {
71             memset(&hd->hd_sd[i], 0, sizeof(hd->hd_sd[i]));
72             hd->hd_sd[i].fd = newfd;
73             hd->hd_sd[i].handle = (httpd_handle_t) hd;
74             hd->hd_sd[i].send_fn = httpd_default_send;
75             hd->hd_sd[i].recv_fn = httpd_default_recv;
76 
77             /* Call user-defined session opening function */
78             if (hd->config.open_fn) {
79                 esp_err_t ret = hd->config.open_fn(hd, hd->hd_sd[i].fd);
80                 if (ret != ESP_OK) {
81                     httpd_sess_delete(hd, hd->hd_sd[i].fd);
82                     ESP_LOGD(TAG, LOG_FMT("open_fn failed for fd = %d"), newfd);
83                     return ret;
84                 }
85             }
86             return ESP_OK;
87         }
88     }
89     ESP_LOGD(TAG, LOG_FMT("unable to launch session for fd = %d"), newfd);
90     return ESP_FAIL;
91 }
92 
httpd_sess_free_ctx(void * ctx,httpd_free_ctx_fn_t free_fn)93 void httpd_sess_free_ctx(void *ctx, httpd_free_ctx_fn_t free_fn)
94 {
95     if (ctx) {
96         if (free_fn) {
97             free_fn(ctx);
98         } else {
99             free(ctx);
100         }
101     }
102 }
103 
httpd_sess_get_ctx(httpd_handle_t handle,int sockfd)104 void *httpd_sess_get_ctx(httpd_handle_t handle, int sockfd)
105 {
106     struct sock_db *sd = httpd_sess_get(handle, sockfd);
107     if (sd == NULL) {
108         return NULL;
109     }
110 
111     /* Check if the function has been called from inside a
112      * request handler, in which case fetch the context from
113      * the httpd_req_t structure */
114     struct httpd_data *hd = (struct httpd_data *) handle;
115     if (hd->hd_req_aux.sd == sd) {
116         return hd->hd_req.sess_ctx;
117     }
118 
119     return sd->ctx;
120 }
121 
httpd_sess_set_ctx(httpd_handle_t handle,int sockfd,void * ctx,httpd_free_ctx_fn_t free_fn)122 void httpd_sess_set_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn)
123 {
124     struct sock_db *sd = httpd_sess_get(handle, sockfd);
125     if (sd == NULL) {
126         return;
127     }
128 
129     /* Check if the function has been called from inside a
130      * request handler, in which case set the context inside
131      * the httpd_req_t structure */
132     struct httpd_data *hd = (struct httpd_data *) handle;
133     if (hd->hd_req_aux.sd == sd) {
134         if (hd->hd_req.sess_ctx != ctx) {
135             /* Don't free previous context if it is in sockdb
136              * as it will be freed inside httpd_req_cleanup() */
137             if (sd->ctx != hd->hd_req.sess_ctx) {
138                 /* Free previous context */
139                 httpd_sess_free_ctx(hd->hd_req.sess_ctx, hd->hd_req.free_ctx);
140             }
141             hd->hd_req.sess_ctx = ctx;
142         }
143         hd->hd_req.free_ctx = free_fn;
144         return;
145     }
146 
147     /* Else set the context inside the sock_db structure */
148     if (sd->ctx != ctx) {
149         /* Free previous context */
150         httpd_sess_free_ctx(sd->ctx, sd->free_ctx);
151         sd->ctx = ctx;
152     }
153     sd->free_ctx = free_fn;
154 }
155 
httpd_sess_get_transport_ctx(httpd_handle_t handle,int sockfd)156 void *httpd_sess_get_transport_ctx(httpd_handle_t handle, int sockfd)
157 {
158     struct sock_db *sd = httpd_sess_get(handle, sockfd);
159     if (sd == NULL) {
160         return NULL;
161     }
162 
163     return sd->transport_ctx;
164 }
165 
httpd_sess_set_transport_ctx(httpd_handle_t handle,int sockfd,void * ctx,httpd_free_ctx_fn_t free_fn)166 void httpd_sess_set_transport_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn)
167 {
168     struct sock_db *sd = httpd_sess_get(handle, sockfd);
169     if (sd == NULL) {
170         return;
171     }
172 
173     if (sd->transport_ctx != ctx) {
174         /* Free previous transport context */
175         httpd_sess_free_ctx(sd->transport_ctx, sd->free_transport_ctx);
176         sd->transport_ctx = ctx;
177     }
178     sd->free_transport_ctx = free_fn;
179 }
180 
httpd_sess_set_descriptors(struct httpd_data * hd,fd_set * fdset,int * maxfd)181 void httpd_sess_set_descriptors(struct httpd_data *hd,
182                                 fd_set *fdset, int *maxfd)
183 {
184     int i;
185     *maxfd = -1;
186     for (i = 0; i < hd->config.max_open_sockets; i++) {
187         if (hd->hd_sd[i].fd != -1) {
188             FD_SET(hd->hd_sd[i].fd, fdset);
189             if (hd->hd_sd[i].fd > *maxfd) {
190                 *maxfd = hd->hd_sd[i].fd;
191             }
192         }
193     }
194 }
195 
196 /** Check if a FD is valid */
fd_is_valid(int fd)197 static int fd_is_valid(int fd)
198 {
199     return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
200 }
201 
httpd_sess_get_lru_counter(void)202 static inline uint64_t httpd_sess_get_lru_counter(void)
203 {
204     static uint64_t lru_counter = 0;
205     return ++lru_counter;
206 }
207 
httpd_sess_delete_invalid(struct httpd_data * hd)208 void httpd_sess_delete_invalid(struct httpd_data *hd)
209 {
210     for (int i = 0; i < hd->config.max_open_sockets; i++) {
211         if (hd->hd_sd[i].fd != -1 && !fd_is_valid(hd->hd_sd[i].fd)) {
212             ESP_LOGW(TAG, LOG_FMT("Closing invalid socket %d"), hd->hd_sd[i].fd);
213             httpd_sess_delete(hd, hd->hd_sd[i].fd);
214         }
215     }
216 }
217 
httpd_sess_delete(struct httpd_data * hd,int fd)218 int httpd_sess_delete(struct httpd_data *hd, int fd)
219 {
220     ESP_LOGD(TAG, LOG_FMT("fd = %d"), fd);
221     int i;
222     int pre_sess_fd = -1;
223     for (i = 0; i < hd->config.max_open_sockets; i++) {
224         if (hd->hd_sd[i].fd == fd) {
225             /* global close handler */
226             if (hd->config.close_fn) {
227                 hd->config.close_fn(hd, fd);
228             }
229 
230             /* release 'user' context */
231             if (hd->hd_sd[i].ctx) {
232                 if (hd->hd_sd[i].free_ctx) {
233                     hd->hd_sd[i].free_ctx(hd->hd_sd[i].ctx);
234                 } else {
235                     free(hd->hd_sd[i].ctx);
236                 }
237                 hd->hd_sd[i].ctx = NULL;
238                 hd->hd_sd[i].free_ctx = NULL;
239             }
240 
241             /* release 'transport' context */
242             if (hd->hd_sd[i].transport_ctx) {
243                 if (hd->hd_sd[i].free_transport_ctx) {
244                     hd->hd_sd[i].free_transport_ctx(hd->hd_sd[i].transport_ctx);
245                 } else {
246                     free(hd->hd_sd[i].transport_ctx);
247                 }
248                 hd->hd_sd[i].transport_ctx = NULL;
249                 hd->hd_sd[i].free_transport_ctx = NULL;
250             }
251 
252             /* mark session slot as available */
253             hd->hd_sd[i].fd = -1;
254             break;
255         } else if (hd->hd_sd[i].fd != -1) {
256             /* Return the fd just preceding the one being
257              * deleted so that iterator can continue from
258              * the correct fd */
259             pre_sess_fd = hd->hd_sd[i].fd;
260         }
261     }
262     return pre_sess_fd;
263 }
264 
httpd_sess_init(struct httpd_data * hd)265 void httpd_sess_init(struct httpd_data *hd)
266 {
267     int i;
268     for (i = 0; i < hd->config.max_open_sockets; i++) {
269         hd->hd_sd[i].fd = -1;
270         hd->hd_sd[i].ctx = NULL;
271     }
272 }
273 
httpd_sess_pending(struct httpd_data * hd,int fd)274 bool httpd_sess_pending(struct httpd_data *hd, int fd)
275 {
276     struct sock_db *sd = httpd_sess_get(hd, fd);
277     if (! sd) {
278         return ESP_FAIL;
279     }
280 
281     if (sd->pending_fn) {
282         // test if there's any data to be read (besides read() function, which is handled by select() in the main httpd loop)
283         // this should check e.g. for the SSL data buffer
284         if (sd->pending_fn(hd, fd) > 0) {
285             return true;
286         }
287     }
288 
289     return (sd->pending_len != 0);
290 }
291 
292 /* This MUST return ESP_OK on successful execution. If any other
293  * value is returned, everything related to this socket will be
294  * cleaned up and the socket will be closed.
295  */
httpd_sess_process(struct httpd_data * hd,int newfd)296 esp_err_t httpd_sess_process(struct httpd_data *hd, int newfd)
297 {
298     struct sock_db *sd = httpd_sess_get(hd, newfd);
299     if (! sd) {
300         return ESP_FAIL;
301     }
302 
303     ESP_LOGD(TAG, LOG_FMT("httpd_req_new"));
304     if (httpd_req_new(hd, sd) != ESP_OK) {
305         return ESP_FAIL;
306     }
307     ESP_LOGD(TAG, LOG_FMT("httpd_req_delete"));
308     if (httpd_req_delete(hd) != ESP_OK) {
309         return ESP_FAIL;
310     }
311     ESP_LOGD(TAG, LOG_FMT("success"));
312     sd->lru_counter = httpd_sess_get_lru_counter();
313     return ESP_OK;
314 }
315 
httpd_sess_update_lru_counter(httpd_handle_t handle,int sockfd)316 esp_err_t httpd_sess_update_lru_counter(httpd_handle_t handle, int sockfd)
317 {
318     if (handle == NULL) {
319         return ESP_ERR_INVALID_ARG;
320     }
321 
322     /* Search for the socket database entry */
323     struct httpd_data *hd = (struct httpd_data *) handle;
324     int i;
325     for (i = 0; i < hd->config.max_open_sockets; i++) {
326         if (hd->hd_sd[i].fd == sockfd) {
327             hd->hd_sd[i].lru_counter = httpd_sess_get_lru_counter();
328             return ESP_OK;
329         }
330     }
331     return ESP_ERR_NOT_FOUND;
332 }
333 
httpd_sess_close_lru(struct httpd_data * hd)334 esp_err_t httpd_sess_close_lru(struct httpd_data *hd)
335 {
336     uint64_t lru_counter = UINT64_MAX;
337     int lru_fd = -1;
338     int i;
339     for (i = 0; i < hd->config.max_open_sockets; i++) {
340         /* If a descriptor is -1, there is no need to close any session.
341          * So, we can return from here, without finding the Least Recently Used
342          * session
343          */
344         if (hd->hd_sd[i].fd == -1) {
345             return ESP_OK;
346         }
347         if (hd->hd_sd[i].lru_counter < lru_counter) {
348             lru_counter = hd->hd_sd[i].lru_counter;
349             lru_fd = hd->hd_sd[i].fd;
350         }
351     }
352     ESP_LOGD(TAG, LOG_FMT("fd = %d"), lru_fd);
353     struct sock_db *sd = httpd_sess_get(hd, lru_fd);
354     sd->lru_socket = true;
355     return httpd_sess_trigger_close(hd, lru_fd);
356 }
357 
httpd_sess_iterate(struct httpd_data * hd,int start_fd)358 int httpd_sess_iterate(struct httpd_data *hd, int start_fd)
359 {
360     int start_index = 0;
361     int i;
362 
363     if (start_fd != -1) {
364         /* Take our index to where this fd is stored */
365         for (i = 0; i < hd->config.max_open_sockets; i++) {
366             if (hd->hd_sd[i].fd == start_fd) {
367                 start_index = i + 1;
368                 break;
369             }
370         }
371     }
372 
373     for (i = start_index; i < hd->config.max_open_sockets; i++) {
374         if (hd->hd_sd[i].fd != -1) {
375             return hd->hd_sd[i].fd;
376         }
377     }
378     return -1;
379 }
380 
httpd_sess_close(void * arg)381 static void httpd_sess_close(void *arg)
382 {
383     struct sock_db *sock_db = (struct sock_db *)arg;
384     if (sock_db) {
385         if (sock_db->lru_counter == 0 && !sock_db->lru_socket) {
386             ESP_LOGD(TAG, "Skipping session close for %d as it seems to be a race condition", sock_db->fd);
387             return;
388         }
389         int fd = sock_db->fd;
390         sock_db->lru_socket = false;
391         struct httpd_data *hd = (struct httpd_data *) sock_db->handle;
392         httpd_sess_delete(hd, fd);
393         close(fd);
394     }
395 }
396 
httpd_sess_trigger_close(httpd_handle_t handle,int sockfd)397 esp_err_t httpd_sess_trigger_close(httpd_handle_t handle, int sockfd)
398 {
399     struct sock_db *sock_db = httpd_sess_get(handle, sockfd);
400     if (sock_db) {
401         return httpd_queue_work(handle, httpd_sess_close, sock_db);
402     }
403 
404     return ESP_ERR_NOT_FOUND;
405 }
406