2015-05-17 02:34:00 +05:30
|
|
|
#include "hoedown/autolink.h"
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
|
|
|
|
#ifndef _MSC_VER
|
|
|
|
#include <strings.h>
|
|
|
|
#else
|
2018-07-15 18:21:05 +05:30
|
|
|
#define strncasecmp _strnicmp
|
2015-05-17 02:34:00 +05:30
|
|
|
#endif
|
|
|
|
|
|
|
|
int
|
|
|
|
hoedown_autolink_is_safe(const uint8_t *data, size_t size)
|
|
|
|
{
|
2018-07-15 18:21:05 +05:30
|
|
|
static const size_t valid_uris_count = 6;
|
|
|
|
static const char *valid_uris[] = {
|
|
|
|
"http://", "https://", "/", "#", "ftp://", "mailto:"
|
|
|
|
};
|
|
|
|
static const size_t valid_uris_size[] = { 7, 8, 1, 1, 6, 7 };
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
for (i = 0; i < valid_uris_count; ++i) {
|
|
|
|
size_t len = valid_uris_size[i];
|
|
|
|
|
|
|
|
if (size > len &&
|
|
|
|
strncasecmp((char *)data, valid_uris[i], len) == 0 &&
|
|
|
|
isalnum(data[len]))
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2015-05-17 02:34:00 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
static size_t
|
|
|
|
autolink_delim(uint8_t *data, size_t link_end, size_t max_rewind, size_t size)
|
|
|
|
{
|
2018-07-15 18:21:05 +05:30
|
|
|
uint8_t cclose, copen = 0;
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
for (i = 0; i < link_end; ++i)
|
|
|
|
if (data[i] == '<') {
|
|
|
|
link_end = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (link_end > 0) {
|
|
|
|
if (strchr("?!.,:", data[link_end - 1]) != NULL)
|
|
|
|
link_end--;
|
|
|
|
|
|
|
|
else if (data[link_end - 1] == ';') {
|
|
|
|
size_t new_end = link_end - 2;
|
|
|
|
|
|
|
|
while (new_end > 0 && isalpha(data[new_end]))
|
|
|
|
new_end--;
|
|
|
|
|
|
|
|
if (new_end < link_end - 2 && data[new_end] == '&')
|
|
|
|
link_end = new_end;
|
|
|
|
else
|
|
|
|
link_end--;
|
|
|
|
}
|
|
|
|
else break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (link_end == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
cclose = data[link_end - 1];
|
|
|
|
|
|
|
|
switch (cclose) {
|
|
|
|
case '"': copen = '"'; break;
|
|
|
|
case '\'': copen = '\''; break;
|
|
|
|
case ')': copen = '('; break;
|
|
|
|
case ']': copen = '['; break;
|
|
|
|
case '}': copen = '{'; break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (copen != 0) {
|
|
|
|
size_t closing = 0;
|
|
|
|
size_t opening = 0;
|
|
|
|
size_t i = 0;
|
|
|
|
|
|
|
|
/* Try to close the final punctuation sign in this same line;
|
|
|
|
* if we managed to close it outside of the URL, that means that it's
|
|
|
|
* not part of the URL. If it closes inside the URL, that means it
|
|
|
|
* is part of the URL.
|
|
|
|
*
|
|
|
|
* Examples:
|
|
|
|
*
|
|
|
|
* foo http://www.pokemon.com/Pikachu_(Electric) bar
|
|
|
|
* => http://www.pokemon.com/Pikachu_(Electric)
|
|
|
|
*
|
|
|
|
* foo (http://www.pokemon.com/Pikachu_(Electric)) bar
|
|
|
|
* => http://www.pokemon.com/Pikachu_(Electric)
|
|
|
|
*
|
|
|
|
* foo http://www.pokemon.com/Pikachu_(Electric)) bar
|
|
|
|
* => http://www.pokemon.com/Pikachu_(Electric))
|
|
|
|
*
|
|
|
|
* (foo http://www.pokemon.com/Pikachu_(Electric)) bar
|
|
|
|
* => foo http://www.pokemon.com/Pikachu_(Electric)
|
|
|
|
*/
|
|
|
|
|
|
|
|
while (i < link_end) {
|
|
|
|
if (data[i] == copen)
|
|
|
|
opening++;
|
|
|
|
else if (data[i] == cclose)
|
|
|
|
closing++;
|
|
|
|
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (closing != opening)
|
|
|
|
link_end--;
|
|
|
|
}
|
|
|
|
|
|
|
|
return link_end;
|
2015-05-17 02:34:00 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
static size_t
|
|
|
|
check_domain(uint8_t *data, size_t size, int allow_short)
|
|
|
|
{
|
2018-07-15 18:21:05 +05:30
|
|
|
size_t i, np = 0;
|
|
|
|
|
|
|
|
if (!isalnum(data[0]))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
for (i = 1; i < size - 1; ++i) {
|
|
|
|
if (strchr(".:", data[i]) != NULL) np++;
|
|
|
|
else if (!isalnum(data[i]) && data[i] != '-') break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (allow_short) {
|
|
|
|
/* We don't need a valid domain in the strict sense (with
|
|
|
|
* least one dot; so just make sure it's composed of valid
|
|
|
|
* domain characters and return the length of the the valid
|
|
|
|
* sequence. */
|
|
|
|
return i;
|
|
|
|
} else {
|
|
|
|
/* a valid domain needs to have at least a dot.
|
|
|
|
* that's as far as we get */
|
|
|
|
return np ? i : 0;
|
|
|
|
}
|
2015-05-17 02:34:00 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
size_t
|
|
|
|
hoedown_autolink__www(
|
2018-07-15 18:21:05 +05:30
|
|
|
size_t *rewind_p,
|
|
|
|
hoedown_buffer *link,
|
|
|
|
uint8_t *data,
|
|
|
|
size_t max_rewind,
|
|
|
|
size_t size,
|
|
|
|
unsigned int flags)
|
2015-05-17 02:34:00 +05:30
|
|
|
{
|
2018-07-15 18:21:05 +05:30
|
|
|
size_t link_end;
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
if (max_rewind > 0 && !ispunct(data[-1]) && !isspace(data[-1]))
|
|
|
|
return 0;
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
if (size < 4 || memcmp(data, "www.", strlen("www.")) != 0)
|
|
|
|
return 0;
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
link_end = check_domain(data, size, 0);
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
if (link_end == 0)
|
|
|
|
return 0;
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
while (link_end < size && !isspace(data[link_end]))
|
|
|
|
link_end++;
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
link_end = autolink_delim(data, link_end, max_rewind, size);
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
if (link_end == 0)
|
|
|
|
return 0;
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
hoedown_buffer_put(link, data, link_end);
|
|
|
|
*rewind_p = 0;
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
return (int)link_end;
|
2015-05-17 02:34:00 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
size_t
|
|
|
|
hoedown_autolink__email(
|
2018-07-15 18:21:05 +05:30
|
|
|
size_t *rewind_p,
|
|
|
|
hoedown_buffer *link,
|
|
|
|
uint8_t *data,
|
|
|
|
size_t max_rewind,
|
|
|
|
size_t size,
|
|
|
|
unsigned int flags)
|
2015-05-17 02:34:00 +05:30
|
|
|
{
|
2018-07-15 18:21:05 +05:30
|
|
|
size_t link_end, rewind;
|
|
|
|
int nb = 0, np = 0;
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
for (rewind = 0; rewind < max_rewind; ++rewind) {
|
|
|
|
uint8_t c = data[-1 - rewind];
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
if (isalnum(c))
|
|
|
|
continue;
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
if (strchr(".+-_", c) != NULL)
|
|
|
|
continue;
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
break;
|
|
|
|
}
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
if (rewind == 0)
|
|
|
|
return 0;
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
for (link_end = 0; link_end < size; ++link_end) {
|
|
|
|
uint8_t c = data[link_end];
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
if (isalnum(c))
|
|
|
|
continue;
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
if (c == '@')
|
|
|
|
nb++;
|
|
|
|
else if (c == '.' && link_end < size - 1)
|
|
|
|
np++;
|
|
|
|
else if (c != '-' && c != '_')
|
|
|
|
break;
|
|
|
|
}
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
if (link_end < 2 || nb != 1 || np == 0 ||
|
|
|
|
!isalpha(data[link_end - 1]))
|
|
|
|
return 0;
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
link_end = autolink_delim(data, link_end, max_rewind, size);
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
if (link_end == 0)
|
|
|
|
return 0;
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
hoedown_buffer_put(link, data - rewind, link_end + rewind);
|
|
|
|
*rewind_p = rewind;
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
return link_end;
|
2015-05-17 02:34:00 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
size_t
|
|
|
|
hoedown_autolink__url(
|
2018-07-15 18:21:05 +05:30
|
|
|
size_t *rewind_p,
|
|
|
|
hoedown_buffer *link,
|
|
|
|
uint8_t *data,
|
|
|
|
size_t max_rewind,
|
|
|
|
size_t size,
|
|
|
|
unsigned int flags)
|
2015-05-17 02:34:00 +05:30
|
|
|
{
|
2018-07-15 18:21:05 +05:30
|
|
|
size_t link_end, rewind = 0, domain_len;
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
if (size < 4 || data[1] != '/' || data[2] != '/')
|
|
|
|
return 0;
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
while (rewind < max_rewind && isalpha(data[-1 - rewind]))
|
|
|
|
rewind++;
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
if (!hoedown_autolink_is_safe(data - rewind, size + rewind))
|
|
|
|
return 0;
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
link_end = strlen("://");
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
domain_len = check_domain(
|
|
|
|
data + link_end,
|
|
|
|
size - link_end,
|
|
|
|
flags & HOEDOWN_AUTOLINK_SHORT_DOMAINS);
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
if (domain_len == 0)
|
|
|
|
return 0;
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
link_end += domain_len;
|
|
|
|
while (link_end < size && !isspace(data[link_end]))
|
|
|
|
link_end++;
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
link_end = autolink_delim(data, link_end, max_rewind, size);
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
if (link_end == 0)
|
|
|
|
return 0;
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
hoedown_buffer_put(link, data - rewind, link_end + rewind);
|
|
|
|
*rewind_p = rewind;
|
2015-05-17 02:34:00 +05:30
|
|
|
|
2018-07-15 18:21:05 +05:30
|
|
|
return link_end;
|
2015-05-17 02:34:00 +05:30
|
|
|
}
|