#include #include "fhp/fhp.h" #define UNUSED(a) ((void) (a)) // // rfc7230, Appendix B // https://tools.ietf.org/html/rfc7230 // // tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / // "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA // token = 1*tchar // #define CASE_TOKEN_CHARS \ case '!': \ case '#': \ case '$': \ case '%': \ case '&': \ case '\'': \ case '*': \ case '+': \ case '-': \ case '.': \ case '^': \ case '_': \ case '|': \ case '~': \ case '0': \ case '1': \ case '2': \ case '3': \ case '4': \ case '5': \ case '6': \ case '7': \ case '8': \ case '9': \ case 'a': \ case 'b': \ case 'c': \ case 'd': \ case 'e': \ case 'f': \ case 'g': \ case 'h': \ case 'i': \ case 'j': \ case 'k': \ case 'l': \ case 'm': \ case 'n': \ case 'o': \ case 'p': \ case 'q': \ case 'r': \ case 's': \ case 't': \ case 'u': \ case 'v': \ case 'w': \ case 'x': \ case 'y': \ case 'z': \ case 'A': \ case 'B': \ case 'C': \ case 'D': \ case 'E': \ case 'F': \ case 'G': \ case 'H': \ case 'I': \ case 'J': \ case 'K': \ case 'L': \ case 'M': \ case 'N': \ case 'O': \ case 'P': \ case 'Q': \ case 'R': \ case 'S': \ case 'T': \ case 'U': \ case 'V': \ case 'W': \ case 'X': \ case 'Y': \ case 'Z': // // rfc7230, Appendix B // https://tools.ietf.org/html/rfc7230 // // OWS = *( SP / HTAB ) // #define CASE_OWS_CHARS \ case ' ': \ case '\t': static const char * fhp_errors[] = { "OK", "callback error", "bad state", "invalid character", "invalid error code", "buffer too small", }; fhp_err_t fhp_strerror( fhp_err_t err, char * const buf, size_t len ) { // check error code if (err >= FHP_ERR_LAST) return FHP_ERR_INVALID_ERROR; // check buffer size size_t err_len = strlen(fhp_errors[err]) + 1; if (len < err_len) return FHP_ERR_BUFFER_TOO_SMALL; // copy string memcpy(buf, fhp_errors[err], err_len); // return success return FHP_OK; } static const char * fhp_tokens[] = { "METHOD_START", "METHOD_FRAGMENT", "METHOD_END", "URL_START", "URL_FRAGMENT", "URL_END", "VERSION_START", "VERSION_FRAGMENT", "VERSION_END", "HEADER_NAME_START", "HEADER_NAME_FRAGMENT", "HEADER_NAME_END", "HEADER_VALUE_START", "HEADER_VALUE_FRAGMENT", "HEADER_VALUE_END", "LAST" }; fhp_err_t fhp_strtoken( fhp_token_t token, char * const buf, size_t len ) { // check token code if (token >= FHP_TOKEN_LAST) return FHP_ERR_INVALID_ERROR; // check buffer size size_t str_len = strlen(fhp_tokens[token]) + 1; if (len < str_len) return FHP_ERR_BUFFER_TOO_SMALL; // copy string memcpy(buf, fhp_tokens[token], str_len); // return success return FHP_OK; } static fhp_t DEFAULT_CONTEXT = { .state = FHP_STATE_INIT, .user_data = NULL, .cb = NULL, .err = FHP_OK, .ofs = 0, .buf_len = 0, }; fhp_err_t fhp_init( fhp_t * const fhp, fhp_cb_t cb, void * const user_data ) { *fhp = DEFAULT_CONTEXT; fhp->user_data = user_data; fhp->cb = cb; /* return success */ return FHP_OK; } static void fhp_buf_clear(fhp_t * const fhp) { // clear buffer fhp->buf_len = 0; } static bool fhp_buf_flush( fhp_t * const fhp, fhp_token_t token ) { if (fhp->buf_len > 0) { // push data if (!fhp->cb(fhp, token, fhp->buf, fhp->buf_len)) return false; // clear buffer fhp_buf_clear(fhp); } // return success return true; } static bool fhp_buf_push( fhp_t * const fhp, fhp_token_t token, uint8_t byte ) { // flush buffer if (fhp->buf_len + 1 >= FHP_BUF_SIZE) { if (!fhp_buf_flush(fhp, token)) return false; } // append to buffer fhp->buf[fhp->buf_len] = byte; fhp->buf_len++; // return success return true; } static fhp_err_t fhp_push_byte( fhp_t * const fhp, uint8_t byte ) { retry: switch (fhp->state) { case FHP_STATE_INIT: switch (byte) { CASE_TOKEN_CHARS // send start token if (!fhp->cb(fhp, FHP_TOKEN_METHOD_START, 0, 0)) return FHP_ERR_CB; // set state fhp->state = FHP_STATE_METHOD; goto retry; break; default: // FIXME: invalid character return FHP_ERR_INVALID_CHAR; } break; case FHP_STATE_METHOD: switch (byte) { CASE_TOKEN_CHARS // add to buffer if (!fhp_buf_push(fhp, FHP_TOKEN_METHOD_FRAGMENT, byte)) return FHP_ERR_CB; break; case ' ': // flush buffer if (!fhp_buf_flush(fhp, FHP_TOKEN_METHOD_FRAGMENT)) return FHP_ERR_CB; // send end token if (!fhp->cb(fhp, FHP_TOKEN_METHOD_END, 0, 0)) return FHP_ERR_CB; // set state fhp->state = FHP_STATE_METHOD_END; goto retry; break; default: // FIXME: invalid character return FHP_ERR_INVALID_CHAR; } break; case FHP_STATE_METHOD_END: switch (byte) { case ' ': // FIXME: do we want to allow more than one whitespace? // ignore break; default: if (!fhp->cb(fhp, FHP_TOKEN_URL_START, 0, 0)) return FHP_ERR_CB; fhp->state = FHP_STATE_URL; goto retry; } break; case FHP_STATE_URL: switch (byte) { case ' ': // flush buffer if (!fhp_buf_flush(fhp, FHP_TOKEN_URL_FRAGMENT)) return FHP_ERR_CB; // send end token if (!fhp->cb(fhp, FHP_TOKEN_URL_END, 0, 0)) return FHP_ERR_CB; // set state fhp->state = FHP_STATE_URL_END; goto retry; break; default: // add to buffer if (!fhp_buf_push(fhp, FHP_TOKEN_URL_FRAGMENT, byte)) return FHP_ERR_CB; } break; case FHP_STATE_URL_END: switch (byte) { case ' ': // ignore break; default: if (!fhp->cb(fhp, FHP_TOKEN_VERSION_START, 0, 0)) return FHP_ERR_CB; fhp->state = FHP_STATE_VERSION; goto retry; } break; case FHP_STATE_VERSION: switch (byte) { case '\r': case '\n': // flush buffer if (!fhp_buf_flush(fhp, FHP_TOKEN_VERSION_FRAGMENT)) return FHP_ERR_CB; // send end token if (!fhp->cb(fhp, FHP_TOKEN_VERSION_END, 0, 0)) return FHP_ERR_CB; // set state fhp->state = FHP_STATE_VERSION_END; goto retry; break; case ' ': // FIXME: invalid character return FHP_ERR_INVALID_CHAR; break; default: // add to buffer if (!fhp_buf_push(fhp, FHP_TOKEN_VERSION_FRAGMENT, byte)) return FHP_ERR_CB; } break; case FHP_STATE_VERSION_END: switch (byte) { case '\r': fhp->state = FHP_STATE_VERSION_END_CR; break; case '\n': fhp->state = FHP_STATE_STATUS_END; break; default: // FIXME: invalid character return FHP_ERR_INVALID_CHAR; }; break; case FHP_STATE_VERSION_END_CR: switch (byte) { case '\n': fhp->state = FHP_STATE_STATUS_END; break; default: // FIXME: invalid character return FHP_ERR_INVALID_CHAR; }; break; case FHP_STATE_STATUS_END: // TODO switch (byte) { CASE_TOKEN_CHARS // send start token if (!fhp->cb(fhp, FHP_TOKEN_HEADER_NAME_START, 0, 0)) return FHP_ERR_CB; // set state fhp->state = FHP_STATE_HEADER_NAME; goto retry; break; case '\r': // set state fhp->state = FHP_STATE_STATUS_END_CR; break; case '\n': // set state fhp->state = FHP_STATE_HEADERS_END; break; } break; case FHP_STATE_STATUS_END_CR: switch (byte) { case '\n': // set state fhp->state = FHP_STATE_HEADERS_END; break; default: // FIXME: invalid character return FHP_ERR_INVALID_CHAR; } break; case FHP_STATE_HEADER_NAME: switch (byte) { CASE_TOKEN_CHARS // add to buffer if (!fhp_buf_push(fhp, FHP_TOKEN_HEADER_NAME_FRAGMENT, byte)) return FHP_ERR_CB; break; case ':': // flush buffer if (!fhp_buf_flush(fhp, FHP_TOKEN_HEADER_NAME_FRAGMENT)) return FHP_ERR_CB; // send end token if (!fhp->cb(fhp, FHP_TOKEN_HEADER_NAME_END, 0, 0)) return FHP_ERR_CB; // set state fhp->state = FHP_STATE_HEADER_NAME_END; break; default: // FIXME: invalid character return FHP_ERR_INVALID_CHAR; } break; case FHP_STATE_HEADER_NAME_END: switch (byte) { CASE_OWS_CHARS // ignore leading spaces break; default: // send start token if (!fhp->cb(fhp, FHP_TOKEN_HEADER_VALUE_START, 0, 0)) return FHP_ERR_CB; // set state fhp->state = FHP_STATE_HEADER_VALUE; goto retry; break; } break; case FHP_STATE_HEADER_VALUE: switch (byte) { case '\r': fhp->state = FHP_STATE_HEADER_VALUE_END_CR; break; case '\n': fhp->state = FHP_STATE_HEADER_VALUE_END; break; default: // FIXME: need more limits on valid octets // add to buffer if (!fhp_buf_push(fhp, FHP_TOKEN_HEADER_VALUE_FRAGMENT, byte)) return FHP_ERR_CB; break; } break; case FHP_STATE_HEADER_VALUE_END_CR: switch (byte) { case '\n': fhp->state = FHP_STATE_HEADER_VALUE_END; break; default: return FHP_ERR_INVALID_CHAR; } break; case FHP_STATE_HEADER_VALUE_END: switch (byte) { CASE_OWS_CHARS // ows-fold // add to buffer if (!fhp_buf_push(fhp, FHP_TOKEN_HEADER_VALUE_FRAGMENT, ' ')) return FHP_ERR_CB; fhp->state = FHP_STATE_HEADER_VALUE; break; CASE_TOKEN_CHARS // flush buffer if (!fhp_buf_flush(fhp, FHP_TOKEN_HEADER_VALUE_FRAGMENT)) return FHP_ERR_CB; // end header value if (!fhp->cb(fhp, FHP_TOKEN_HEADER_VALUE_END, 0, 0)) return FHP_ERR_CB; // send start token if (!fhp->cb(fhp, FHP_TOKEN_HEADER_NAME_START, 0, 0)) return FHP_ERR_CB; // add to buffer if (!fhp_buf_push(fhp, FHP_TOKEN_HEADER_NAME_FRAGMENT, byte)) return FHP_ERR_CB; break; case '\r': // flush buffer if (!fhp_buf_flush(fhp, FHP_TOKEN_HEADER_VALUE_FRAGMENT)) return FHP_ERR_CB; // end header value if (!fhp->cb(fhp, FHP_TOKEN_HEADER_VALUE_END, 0, 0)) return FHP_ERR_CB; // set state fhp->state = FHP_STATE_HEADERS_END_CR; break; case '\n': // flush buffer if (!fhp_buf_flush(fhp, FHP_TOKEN_HEADER_VALUE_FRAGMENT)) return FHP_ERR_CB; // end header value if (!fhp->cb(fhp, FHP_TOKEN_HEADER_VALUE_END, 0, 0)) return FHP_ERR_CB; // set state fhp->state = FHP_STATE_HEADERS_END; break; default: return FHP_ERR_INVALID_CHAR; } break; case FHP_STATE_HEADERS_END_CR: switch (byte) { case '\n': fhp->state = FHP_STATE_HEADERS_END; break; default: return FHP_ERR_INVALID_CHAR; } break; case FHP_STATE_HEADERS_END: // TODO break; default: // FIXME: invalid state return FHP_ERR_BAD_STATE; } // increment byte offset fhp->ofs++; /* return success */ return FHP_OK; } fhp_err_t fhp_push( fhp_t * const fhp, uint8_t * const buf, size_t len ) { switch (fhp->state) { case FHP_STATE_ERROR: return fhp->err; break; default: for (size_t i = 0; i < len; i++) { // push byte fhp_err_t err = fhp_push_byte(fhp, buf[i]); // check result if (err != FHP_OK) { fhp->state = FHP_STATE_ERROR; fhp->err = err; return err; } } } // return success return FHP_OK; }