/** * @return length of currently formatted header. */ size_t header_fmt_length(const header_fmt_t *hf) { header_fmt_check(hf); return str_len(hf->header); }
/** * Checks whether appending `len' bytes of data to the header would fit * within the maximum header size requirement in case a continuation * is emitted, and using the configured separator. */ bool header_fmt_value_fits(const header_fmt_t *hf, size_t len) { size_t final_len; size_t maxlen, n; header_fmt_check(hf); if (hf->empty) return FALSE; maxlen = size_saturate_sub(hf->max_size, sizeof("\r\n")); /* * If it fits on the line, no continuation will have to be emitted. * Otherwise, we'll need the stripped version of the separator, * followed by "\r\n\t" (3 chars). */ final_len = size_saturate_add(str_len(hf->header), len); n = size_saturate_add(hf->current_len, size_saturate_add(len, hf->seplen)); if (n <= hf->maxlen) { final_len = size_saturate_add(final_len, hf->seplen); } else { final_len = size_saturate_add(final_len, hf->stripped_seplen); final_len = size_saturate_add(final_len, 3); } return final_len < maxlen; /* Could say "<=" perhaps, but let's be safe */ }
/** * @return current header string. */ const char * header_fmt_string(const header_fmt_t *hf) { header_fmt_check(hf); return str_2c(hf->header); /* Guaranteed to be always NUL-terminated */ }
/** * Set max line length. */ void header_fmt_set_line_length(header_fmt_t *hf, size_t maxlen) { header_fmt_check(hf); g_assert(size_is_positive(maxlen)); hf->maxlen = maxlen; }
/** * Append data `str' to the header line, atomically. * * Values are separated using the string specified at make time, if any. * If emitted before a continuation, the version with stripped trailing * whitespaces is used. * * To supersede the default separator, use header_fmt_append(). * * @return TRUE if we were able to append the data whilst remaining under * the configured maximum length. */ bool header_fmt_append_value(header_fmt_t *hf, const char *str) { header_fmt_check(hf); g_assert(!hf->frozen); return header_fmt_append_full(hf, str, hf->sep, hf->seplen, hf->stripped_seplen); }
/** * Append data `str' to the header line, atomically. * * `separator' is an optional separator string that will be emitted BEFORE * outputting the data, and only when nothing has been emitted already. * Any trailing space will be stripped out of `separator' if emitting at the * end of a line. It supersedes any separator configured at make time. * * To use the standard separator, use header_fmt_append_value(). * * @return TRUE if we were able to append the data whilst remaining under * the configured maximum length. */ bool header_fmt_append(header_fmt_t *hf, const char *str, const char *separator) { size_t seplen; header_fmt_check(hf); g_assert(!hf->frozen); seplen = (separator == NULL) ? 0 : strlen(separator); return header_fmt_append_full(hf, str, separator, seplen, (size_t)-1); }
/** * Terminate header, emitting the trailing "\r\n". * Further appending is forbidden. */ void header_fmt_end(header_fmt_t *hf) { header_fmt_check(hf); g_assert(!hf->frozen); if (!hf->empty) STR_CAT(hf->header, "\r\n"); hf->frozen = TRUE; g_assert(str_len(hf->header) < hf->max_size); }
/** * Convert current header to a string. * * @attention * NB: returns pointer to static data! */ const char * header_fmt_to_string(const header_fmt_t *hf) { static char line[HEADER_FMT_MAX_SIZE + 1]; header_fmt_check(hf); if (str_len(hf->header) >= sizeof line) { g_warning("trying to format too long an HTTP line (%lu bytes)", (unsigned long) str_len(hf->header)); } clamp_strncpy(line, sizeof line, str_2c(hf->header), str_len(hf->header)); return line; }
/** * Dispose of header formatting context. */ void header_fmt_free(header_fmt_t **hf_ptr) { header_fmt_t *hf = *hf_ptr; if (hf) { header_fmt_check(hf); str_destroy_null(&hf->header); atom_str_free_null(&hf->sep); hf->magic = 0; WFREE(hf); *hf_ptr = NULL; } }
/** * Convert current header to a string. * * @attention * NB: returns pointer to static data! */ const char * header_fmt_to_string(const header_fmt_t *hf) { buf_t *b = buf_private(G_STRFUNC, HEADER_FMT_MAX_SIZE + 1); char *p = buf_data(b); size_t n = buf_size(b); header_fmt_check(hf); if (str_len(hf->header) >= n) { g_warning("trying to format too long an HTTP line (%zu bytes)", str_len(hf->header)); } clamp_strncpy(p, n, str_2c(hf->header), str_len(hf->header)); return p; }
/** * Create a new formatting context for a header line. * * @param `field' is the header field name, without trailing ':'. * * @param `separator' is the optional default separator to emit between * the values added via header_fmd_append_value(). To supersede the * default separator, use header_fmd_append() and specify another separator * explicitly. If set to NULL, there will be no default separator and * values will be simply concatenated together. The value given must * NOT be freed before the header_fmt_end() call (usually it will just * be a static string). Trailing spaces in the separator will be stripped * if it is emitted at the end of a line before a continuation. * * @param `len_hint' is the expected line size, for pre-sizing purposes. * (0 to guess). * * @param `max_size' is the maximum header size, including the final "\r\n" * and the trailing NUL. If the initial field name is larger than the * configured maximum size, the header field will remain completely empty. * * @return pointer to the formatting object. */ header_fmt_t * header_fmt_make(const char *field, const char *separator, size_t len_hint, size_t max_size) { struct header_fmt *hf; g_assert(size_is_non_negative(len_hint)); WALLOC(hf); hf->magic = HEADER_FMT_MAGIC; hf->header = str_new(len_hint ? len_hint : HEADER_FMT_DFLT_LEN); hf->maxlen = HEADER_FMT_LINE_LEN; hf->data_emitted = FALSE; hf->frozen = FALSE; hf->max_size = max_size; hf->sep = atom_str_get(separator ? separator : ""); hf->seplen = strlen(hf->sep); hf->stripped_seplen = stripped_strlen(hf->sep, hf->seplen); str_cat(hf->header, field); STR_CAT(hf->header, ": "); hf->current_len = str_len(hf->header); /* * If right from the start the header would be larger than the configured * size, force it to stay empty. That means, the final string returned * will be "", the empty string. */ if (str_len(hf->header) + sizeof("\r\n") > hf->max_size) { hf->empty = TRUE; str_setlen(hf->header, 0); } else { hf->empty = FALSE; } header_fmt_check(hf); return hf; }
/** * Append data `str' to the header line, atomically. * * @param `hf' no brief description. * @param `str' no brief description. * @param `separator' is an optional separator string that will be emitted * BEFORE outputting the data, and only when nothing has been emitted * already. * @param `slen' is the separator length, 0 if empty. * @param `sslen' is the stripped separator length, (size_t)-1 if unknown yet. * * @return TRUE if we were able to fit the string, FALSE if it would have * resulted in the header being larger than the configured max size (the * header line is left in the state it was in upon entry, in that case). */ static bool header_fmt_append_full(header_fmt_t *hf, const char *str, const char *separator, size_t slen, size_t sslen) { size_t len, curlen; gsize gslen; bool success; header_fmt_check(hf); g_assert(size_is_non_negative(slen)); g_assert((size_t)-1 == sslen || size_is_non_negative(sslen)); if (hf->empty) return FALSE; gslen = str_len(hf->header); len = strlen(str); curlen = hf->current_len; g_assert(size_is_non_negative(curlen)); g_assert(len <= INT_MAX); /* Legacy bug */ if ( size_saturate_add(curlen, size_saturate_add(len, slen)) > UNSIGNED(hf->maxlen) ) { /* * Emit sperator, if any and data was already emitted. */ if (separator != NULL && hf->data_emitted) { sslen = (size_t)-1 != sslen ? sslen : stripped_strlen(separator, slen); str_cat_len(hf->header, separator, sslen); } STR_CAT(hf->header, "\r\n\t"); /* Includes continuation */ curlen = 1; /* One tab */ } else if (hf->data_emitted) { str_cat(hf->header, separator); curlen += slen; } str_cat(hf->header, str); /* * Check for overflows, undoing string changes if needed. */ if (str_len(hf->header) + sizeof("\r\n") > hf->max_size) { success = FALSE; str_setlen(hf->header, gslen); /* Undo! */ } else { success = TRUE; hf->data_emitted = TRUE; hf->current_len = curlen + len; } g_assert(str_len(hf->header) + sizeof("\r\n") <= hf->max_size); return success; }