# htmlspecialchars() equivalent in C

I'm trying to write an equivalent of PHP's htmlspecialchars() in C. What is my implementation missing or doing wrong?

char www_specialchars_html(char *dest, char *dest_last, char *src) {
while (*src != 0) {
switch(*src) {
//If a special character is found, replace with its HTML entity.
case '&':
if ((dest + 5) > dest_last) {
return 1;
}
*dest = '&';
dest++;
*dest = 'a';
dest++;
*dest = 'm';
dest++;
*dest = 'p';
dest++;
*dest = ';';
dest++;
src++;
break;
case '"':
if ((dest + 6) > dest_last) {
return 1;
}
*dest = '&';
dest++;
*dest = 'q';
dest++;
*dest = 'u';
dest++;
*dest = 'o';
dest++;
*dest = 't';
dest++;
*dest = ';';
dest++;
src++;
break;
case '\'':
if ((dest + 6) > dest_last) {
return 1;
}
*dest = '&';
dest++;
*dest = '#';
dest++;
*dest = '0';
dest++;
*dest = '3';
dest++;
*dest = '9';
dest++;
*dest = ';';
dest++;
src++;
break;
case '<':
if ((dest + 4) > dest_last) {
return 1;
}
*dest = '&';
dest++;
*dest = 'l';
dest++;
*dest = 't';
dest++;
*dest = ';';
dest++;
src++;
break;
case '>':
if ((dest + 4) > dest_last) {
return 1;
}
*dest = '&';
dest++;
*dest = 'g';
dest++;
*dest = 't';
dest++;
*dest = ';';
dest++;
src++;
break;
case '\\': //Not required in HTML.
if ((dest) > dest_last) {
return 1;
}
src++;
break;
default:
//Default for characters that don't need to be replaced.
if ((dest + 1) > dest_last) {
return 1;
}
*dest = *src;
src++;
dest++;
break;
}
}
*dest = 0;
return 0;
}


The function takes pointers to the destination, the last element of the destination and the source. It returns 1 if it can't write to the destination, and returns 0 when it has successfully copied to the destination. I'm still a relatively new C programmer.

• Can you explain the case that handles the backslash character? Oct 27, 2018 at 22:07
• @200_success I had put in the backslash case so that I could use the same function to escape SQL queries, but I'm pretty sure it's not required in HTML. I'll update the code to reflect that. Oct 27, 2018 at 22:13
• @wispi For what it's worth, PHP is implemented mostly in C. For example, here's the PHP declaration of htmlspecialchars(). You can then follow the function calls to see how they do it. If you're goal is to write an equivalent implementation, it would be a lot more work because of all that htmlspecialchars() does Oct 27, 2018 at 23:06

• You shouldn't pass a char* as buffer end but a size_t size corresponding to the size of the allocated buffer. So your available length will be size - 2 ("zero-indexed" and "trailing '\0'").

• You return either 1 or 0, why don't using bool as return type? (Or maybe a size_t corresponding to the final string length, but, that's a design decision).

• Variables where value doesn't change in your function (as locales or parameters) should be marked const. That's not faster but it show your intends.

• You can maybe use strcpy, strncpy or memcpy to ... copy the replacements string into destination (and replace multiple incrementation by a single add-assign).

• For non-special chars sequences, you can maybe get them in chunk by chunk (between two special chars) with strcspn or strpbrk. (and after, copying them with same functions than former)

Edit

This is a raw try using theses functions.

I think it is safe, with no overflow, but maybe not the optimal implement. Don't be scared about the size, there is a lot of comments :)

I outed two functions from source (strlenand replace_chrstr), 'cause I think they are reusable somewhere.

#include <string.h>

// Get the position of the c char in the str string.
// -> str: a null-terminated string where to lookup
// -> needle: char to find
// Return: Position of the char c or 1 past the end (strlen + 1)
size_t strpos(const char* const str, const char c)
{
char* p = strchr(str, c);
return p ? (p - str) : strlen(str) + 1;
}

// Scan in_str and replace all char occurrences from search
// by corresponding string from replace, then place the result in out_str.
// -> search: A null-terminated string with all char to fiend
// -> replace: An array of null-terminated strings with index based replacement from search
// -> out_str: a buffer to put the result, at least as big than max_size
// -> in_str: a null-terminated string where to process replacements
// Return: length of resulting string, or length+1 in case of overflow
size_t replace_chrstr(const char* const search, const char* const* const replace,
char *const out_str, const char* const in_str, const size_t max_size)
{
size_t in_offset = 0;
size_t out_offset = 0;
size_t index;
size_t length;
const size_t index_max = strlen(search);
const size_t length_max = strlen(in_str);

while (max_size > out_offset) {
length = strcspn(in_str + in_offset, search);
// buffer overflow detected, here we truncate the input string
// we add \0 and return max_size (equivalent to strlen + 1)
// but maybe you want another handling
if (max_size <= out_offset + length) {
strncpy(out_str + out_offset, in_str + in_offset, max_size-out_offset-1);
out_str[max_size-1] = '\0';
return max_size;
}
strncpy(out_str + out_offset, in_str + in_offset, length);
in_offset += length + 1;
out_offset += length;
// normal end of input string
// add a \0, then return out_offset (equivalent to strlen)
if (in_str[in_offset-1] == '\0') {
out_str[out_offset] = '\0';
return out_offset;
}
index = strpos(search, in_str[in_offset-1]);
length = strlen(replace[index]);
// buffer overflow detected, here we truncate the replacement string
// we add \0 and return max_size (equivalent to strlen + 1)
// but maybe you want another handling
if (max_size <= out_offset + length) {
strncpy(out_str + out_offset, replace[index], max_size-out_offset-1);
out_str[max_size-1] = '\0';
return max_size;
}
strcpy(out_str + out_offset, replace[index]);
out_offset += length;
}
// we never reach here, normally
return 0;
}

size_t www_specialchars_html(char* const dest, const char* const source, size_t max_size)
{
const char search[] = "&\"'<>";
const char * replace[] = {"&amp;","&quot;","&#039;","&lt;","&gt;"};
return replace_chrstr(search, replace, dest, source, max_size);
}

• Using bool would require an extra #include <stdbool.h>, it's not uncommon to just use int in C for holding boolean values. Oct 27, 2018 at 23:14
• I know, but what's the problem about including it? Oct 27, 2018 at 23:20
• As an aside, you might want to expound on the calculations for remaining space: Arbitrary pointer-arithmetic (meaning the displacement is not zero, nor start and end point into or at the end of the same array) is not allowed. Oct 27, 2018 at 23:27
• @Calak Nothing wrong with including it. Using it would be overkill unless they use lots of boolean values elsewhere I suppose. Your answer is good in my opinion. Oct 27, 2018 at 23:45
• @Deduplicator What do you mean by arbitrary pointer-arithmetic? Oct 27, 2018 at 23:47