add Set-Cookie helper func

master
Ondřej Hruška 1 year ago
parent f94f958ad3
commit d062bed87d
  1. 16
      demo/server_demo.c
  2. 40
      spritehttpd/include/httpd-types.h
  3. 22
      spritehttpd/include/httpd.h
  4. 150
      spritehttpd/src/httpd.c

@ -81,17 +81,29 @@ httpd_cgi_state templateReplacer(HttpdConnData *conn, const char *token)
// httpdGetHeader(conn, "Cookie", )
//}
httpd_cgi_state cgiStartSession(HttpdConnData *conn)
{
httpdQueueHeader(conn, "X-Foo", "FOO");
httpdQueueHeader(conn, "X-Bar", "Bar");
httpdSetCookie(conn, &(SetCookie) {
.name = "testCookie",
.value = "lala-lele",
// .domain = "localhost",
// .expires = "BABABABAx",
// .httponly = true,
.maxAge = -1,
// .path = "foo/bar/baz",
// .sameSite = COOKIE_SAMESITE_LAX,
// .secure = true,
});
return HTTPD_CGI_NOTFOUND;
}
/**
* Application routes
*/

@ -10,6 +10,10 @@
#include "httpd-config.h"
// TODO error enum
// opaque conn type struct
struct HttpdConnType;
typedef struct HttpdConnType HttpdConnType;
@ -67,6 +71,34 @@ struct httpd_init_options {
uint16_t port;
};
/**
* Parameters for Set-Cookie
*
* Leave unused fields as NULL or false for defaults.
*
* If "maxAge" is zero, it's not sent, setting a session cookie.
* This is to allow leaving the field unset by default.
*
* To expire a cookie immediately, use maxAge = -1
*/
typedef struct SetCookie {
const char *name;
const char *value;
const char *expires;
const int32_t maxAge;
const char *domain;
const char *path;
const char *sameSite;
const bool secure;
const bool httponly;
} SetCookie;
// Supported values for the "sameSite" attribute of SetCookie.
// Any string can be sent, these are the standard forms suspported by browsers.
#define COOKIE_SAMESITE_LAX "Lax"
#define COOKIE_SAMESITE_STRICT "Strict"
#define COOKIE_SAMESITE_NONE "None"
/* Private types - exposed to allow static alloc */
@ -100,6 +132,14 @@ struct HttpdPriv {
uint8_t flags;
};
//Flags (1 byte)
#define HFL_HTTP11 (1<<0)
#define HFL_CHUNKED (1<<1)
#define HFL_SENDINGBODY (1<<2)
#define HFL_DISCONAFTERSENT (1<<3)
#define HFL_NOCONNECTIONSTR (1<<4)
#define HFL_NOCORS (1<<5)
/// Callback type that releases the user data attached to the connection.
/// The format is compatible with regular "free()" or
typedef void (* httpdUserDataCleanupCb)(void *userData);

@ -10,7 +10,6 @@
#include "httpd-routes.h"
#include "httpd-config.h"
/**
* Get the server version string
*
@ -113,6 +112,27 @@ int httpdGetHeader(HttpdConnData *conn, const char *header, char *buff, size_t b
*/
void httpdQueueHeader(HttpdConnData *conn, const char *header, const char *value);
/**
* Queue a header to be sent with the response. This is a variant of `httpdQueueHeader()`
* which uses the raw internal struct to avoid re-alloc.
*
* The variable length field of the struct must contain `HeaderName: HeaderValue + CR LF`
*
* @param conn
* @param queEntry
* @return 1 = OK
*/
bool httpdQueueHeaderRaw(HttpdConnData *conn, HttpdQueuedHeader *queEntry);
/**
* Set cookie. This queues the cookie header.
*
* @param conn
* @param parm
* @return
*/
bool httpdSetCookie(HttpdConnData *conn, const SetCookie *parm);
/**
* Send binary data
*

@ -29,14 +29,6 @@ _Static_assert(HTTPD_MAX_CONNECTIONS < 256, "HTTPD_MAX_CONNECTIONS must be at mo
static const HttpdBuiltInUrl *s_builtInUrls;
static const char *s_serverName = HTTPD_SERVERNAME;
//Flags (1 byte)
#define HFL_HTTP11 (1<<0)
#define HFL_CHUNKED (1<<1)
#define HFL_SENDINGBODY (1<<2)
#define HFL_DISCONAFTERSENT (1<<3)
#define HFL_NOCONNECTIONSTR (1<<4)
#define HFL_NOCORS (1<<5)
//Connection pool
@ -198,6 +190,23 @@ void httpdQueueHeader(HttpdConnData *conn, const char *header, const char *value
strcat(queEntry->headerLine, value);
strcat(queEntry->headerLine, "\r\n");
if (!httpdQueueHeaderRaw(conn, queEntry)) {
httpdFree(queEntry);
}
}
bool httpdQueueHeaderRaw(HttpdConnData *conn, HttpdQueuedHeader *queEntry)
{
if (!conn || !queEntry) {
return false;
}
if (conn->priv.flags & HFL_SENDINGBODY) {
http_error("Headers already sent.");
return false;
}
queEntry->next = NULL;
if (!conn->priv.headersToSend) {
conn->priv.headersToSend = queEntry;
} else {
@ -208,8 +217,133 @@ void httpdQueueHeader(HttpdConnData *conn, const char *header, const char *value
}
ph->next = queEntry;
}
return true;
}
bool httpdSetCookie(HttpdConnData *conn, const SetCookie *parm)
{
if (!conn || !parm) {
return false;
}
if (!parm->name || !parm->value) {
return false;
}
if (conn->priv.flags & HFL_SENDINGBODY) {
http_error("Headers already sent.");
return false;
}
/*
Set-Cookie: <cookie-name>=<cookie-value>
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>
Set-Cookie: <cookie-name>=<cookie-value>; Expires=<date>
Set-Cookie: <cookie-name>=<cookie-value>; HttpOnly
Set-Cookie: <cookie-name>=<cookie-value>; Max-Age=<number>
Set-Cookie: <cookie-name>=<cookie-value>; Partitioned
Set-Cookie: <cookie-name>=<cookie-value>; Path=<path-value>
Set-Cookie: <cookie-name>=<cookie-value>; Secure
Set-Cookie: <cookie-name>=<cookie-value>; SameSite=Strict
Set-Cookie: <cookie-name>=<cookie-value>; SameSite=Lax
Set-Cookie: <cookie-name>=<cookie-value>; SameSite=None; Secure
// Multiple attributes are also possible, for example:
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly
*/
#define COOKIE_PART_SET_COOKIE "Set-Cookie: "
#define COOKIE_PART_EXPIRES "; Expires="
#define COOKIE_PART_MAX_AGE "; Max-Age="
#define COOKIE_PART_DOMAIN "; Domain="
#define COOKIE_PART_PATH "; Path="
#define COOKIE_PART_SAME_SITE "; SameSite="
#define COOKIE_PART_SECURE "; Secure"
#define COOKIE_PART_HTTP_ONLY "; HttpOnly"
// "Set-Cookie: "
size_t buflen = strlen(COOKIE_PART_SET_COOKIE)
+ strlen(parm->name) + 1 + strlen(parm->value)
+ 5; // cr lf + nul + some spare space
if (parm->expires) {
buflen += strlen(COOKIE_PART_EXPIRES) + strlen(parm->expires);
}
if (parm->maxAge != 0) {
// 10 chars are needed for 32bit integer
buflen += strlen(COOKIE_PART_MAX_AGE) + 10;
}
if (parm->domain) {
buflen += strlen(COOKIE_PART_DOMAIN) + strlen(parm->domain);
}
if (parm->path) {
buflen += strlen(COOKIE_PART_PATH) + strlen(parm->path);
}
if (parm->sameSite) {
// Strict, Lax, None
buflen += strlen(COOKIE_PART_SAME_SITE) + strlen(parm->sameSite);
}
if (parm->secure) {
buflen += strlen(COOKIE_PART_SECURE);
}
if (parm->httponly) {
buflen += strlen(COOKIE_PART_HTTP_ONLY);
}
HttpdQueuedHeader *queEntry = httpdMalloc(buflen);
if (!queEntry) {
http_error("httpdSetCookie - no mem");
return false;
}
queEntry->next = NULL;
queEntry->headerLine[0] = 0;
strcat(queEntry->headerLine, COOKIE_PART_SET_COOKIE);
strcat(queEntry->headerLine, parm->name);
strcat(queEntry->headerLine, "=");
strcat(queEntry->headerLine, parm->value);
if (parm->expires) {
strcat(queEntry->headerLine, COOKIE_PART_EXPIRES);
strcat(queEntry->headerLine, parm->expires);
}
if (parm->maxAge != 0) {
strcat(queEntry->headerLine, COOKIE_PART_MAX_AGE);
sprintf(queEntry->headerLine + strlen(queEntry->headerLine), "%d", parm->maxAge);
}
if (parm->domain) {
strcat(queEntry->headerLine, COOKIE_PART_DOMAIN);
strcat(queEntry->headerLine, parm->domain);
}
if (parm->path) {
strcat(queEntry->headerLine, COOKIE_PART_PATH);
strcat(queEntry->headerLine, parm->path);
}
if (parm->sameSite) {
strcat(queEntry->headerLine, COOKIE_PART_SAME_SITE);
strcat(queEntry->headerLine, parm->sameSite);
}
if (parm->secure) {
strcat(queEntry->headerLine, COOKIE_PART_SECURE);
}
if (parm->httponly) {
strcat(queEntry->headerLine, COOKIE_PART_HTTP_ONLY);
}
strcat(queEntry->headerLine, "\r\n");
if (!httpdQueueHeaderRaw(conn, queEntry)) {
// should not be possible
httpdFree(queEntry);
}
return true;
}
void httdSetTransferMode(HttpdConnData *conn, httpd_transfer_opt mode)
{
if (mode == HTTPD_TRANSFER_CLOSE) {

Loading…
Cancel
Save