int sdper_parse(struct sdper const *sdper, size_t *head_sz, uint8_t const *packet, size_t packet_len, void *user_data) { // REVIEW: eols and spcs should be provided by liner. struct liner_delimiter eols[] = { { "\r\n", 2 }, { "\n", 1 } }, cols[] = { { "= ", 2}, { "=", 1 } }; struct liner_delimiter_set const lines = { NB_ELEMS(eols), eols, false }, eq = { NB_ELEMS(cols), cols, true }; struct liner liner, tokenizer; liner_init(&liner, &lines, (char const *)packet, packet_len); // Parse header fields while (true) { // Next line if (liner_eof(&liner)) break; // Otherwise tokenize the header line liner_init(&tokenizer, &eq, liner.start, liner_tok_length(&liner)); for (unsigned f = 0; f < sdper->nb_fields; f++) { struct sdper_field const *field = sdper->fields + f; size_t len = liner_tok_length(&tokenizer); if (len != field->length) continue; if (0 != strncasecmp(field->name, tokenizer.start, len)) continue; SLOG(LOG_DEBUG, "Found field %s", field->name); liner_next(&tokenizer); int ret = field->cb(f, &tokenizer, user_data); if (ret) return ret; break; } liner_next(&liner); } if (head_sz) *head_sz = liner_parsed(&liner); return 0; }
enum proto_parse_status httper_parse(struct httper const *httper, size_t *head_sz, uint8_t const *packet, size_t packet_len, void *user_data) { struct liner liner, tokenizer; bool found = false; for (unsigned c = 0; c < httper->nb_commands; c++) { struct httper_command const *const cmd = httper->commands + c; // Start by looking for the command before tokenizing (tokenizing takes too much time on random traffic) if (0 != strncmp(cmd->name, (char const *)packet, MIN(packet_len, cmd->len))) continue; if (packet_len < cmd->len) return PROTO_TOO_SHORT; liner_init(&liner, &delim_lines, (char const *)packet, packet_len); liner_init(&tokenizer, &delim_blanks, liner.start, liner_tok_length(&liner)); if (liner_tok_length(&tokenizer) != cmd->len) { no_command: SLOG(LOG_DEBUG, "Cannot find command"); return PROTO_PARSE_ERR; } SLOG(LOG_DEBUG, "Found command %s", cmd->name); liner_next(&tokenizer); int ret = cmd->cb(c, &tokenizer, user_data); if (ret) return PROTO_PARSE_ERR; found = true; break; } if (! found) goto no_command; // Parse header fields unsigned nb_hdr_lines = 0; int field_idx = -1; char const *field_end = NULL; while (true) { // Next line bool const has_newline = liner.delim_size > 0; liner_next(&liner); if (liner_eof(&liner)) { // As an accommodation to old HTTP implementations, we allow a single line command // FIXME: check line termination with "*/x.y" ? if (nb_hdr_lines == 0 && has_newline) break; return PROTO_TOO_SHORT; } // If empty line we reached the end of the headers if (liner_tok_length(&liner) == 0) break; // Check if we reached the end of a multiline field. // FIXME: Is isspace appropriate here? if (! isspace(liner.start[0])) { if (field_idx >= 0) { liner_grow(&tokenizer, field_end); // Absorb all remaining of line onto this token liner_expand(&tokenizer); int ret = httper->fields[field_idx].cb(field_idx, &tokenizer, user_data); if (ret) return PROTO_PARSE_ERR; } // Tokenize the header line liner_init(&tokenizer, &delim_colons, liner.start, liner_tok_length(&liner)); field_idx = -1; for (unsigned f = 0; f < httper->nb_fields; f++) { struct httper_field const *field = httper->fields + f; if (liner_tok_length(&tokenizer) < field->len) continue; if (0 != strncasecmp(field->name, tokenizer.start, liner_tok_length(&tokenizer))) continue; SLOG(LOG_DEBUG, "Found field %s", field->name); liner_next(&tokenizer); field_idx = f; break; } } field_end = liner.start + liner.tok_size; // save end of line position in field_end nb_hdr_lines ++; } if (field_idx >= 0) { liner_grow(&tokenizer, field_end); // Absorb all remaining of line onto this token liner_expand(&tokenizer); int ret = httper->fields[field_idx].cb(field_idx, &tokenizer, user_data); if (ret) return PROTO_PARSE_ERR; } if (head_sz) *head_sz = liner_parsed(&liner); return PROTO_OK; }