File Coverage

hoedown/src/autolink.c
Criterion Covered Total %
statement 38 85 44.7
branch 36 112 32.1
condition n/a
subroutine n/a
pod n/a
total 74 197 37.5


line stmt bran cond sub pod time code
1             #include "autolink.h"
2              
3             #include
4             #include
5             #include
6             #include
7              
8             #ifndef _MSC_VER
9             #include
10             #else
11             #define strncasecmp _strnicmp
12             #endif
13              
14             int
15 2           hoedown_autolink_is_safe(const uint8_t *data, size_t size)
16             {
17             static const size_t valid_uris_count = 6;
18             static const char *valid_uris[] = {
19             "http://", "https://", "/", "#", "ftp://", "mailto:"
20             };
21             static const size_t valid_uris_size[] = { 7, 8, 1, 1, 6, 7 };
22             size_t i;
23              
24 2 50         for (i = 0; i < valid_uris_count; ++i) {
25 2           size_t len = valid_uris_size[i];
26              
27 2 50         if (size > len &&
    50          
28 2 50         strncasecmp((char *)data, valid_uris[i], len) == 0 &&
29 2           isalnum(data[len]))
30             return 1;
31             }
32              
33             return 0;
34             }
35              
36             static size_t
37 4           autolink_delim(uint8_t *data, size_t link_end, size_t max_rewind, size_t size)
38             {
39             uint8_t cclose, copen = 0;
40             size_t i;
41              
42 23 100         for (i = 0; i < link_end; ++i)
43 23 50         if (data[i] == '<') {
44             link_end = i;
45             break;
46             }
47              
48 2 50         while (link_end > 0) {
49 2 50         if (strchr("?!.,:", data[link_end - 1]) != NULL)
50             link_end--;
51              
52 2 50         else if (data[link_end - 1] == ';') {
53 0           size_t new_end = link_end - 2;
54              
55 0 0         while (new_end > 0 && isalpha(data[new_end]))
    0          
56 0           new_end--;
57              
58 0 0         if (new_end < link_end - 2 && data[new_end] == '&')
    0          
59             link_end = new_end;
60             else
61             link_end--;
62             }
63             else break;
64             }
65              
66 2 50         if (link_end == 0)
67             return 0;
68              
69 2           cclose = data[link_end - 1];
70              
71 2           switch (cclose) {
72             case '"': copen = '"'; break;
73             case '\'': copen = '\''; break;
74             case ')': copen = '('; break;
75             case ']': copen = '['; break;
76             case '}': copen = '{'; break;
77             }
78              
79 2 50         if (copen != 0) {
80             size_t closing = 0;
81             size_t opening = 0;
82             size_t i = 0;
83              
84             /* Try to close the final punctuation sign in this same line;
85             * if we managed to close it outside of the URL, that means that it's
86             * not part of the URL. If it closes inside the URL, that means it
87             * is part of the URL.
88             *
89             * Examples:
90             *
91             * foo http://www.pokemon.com/Pikachu_(Electric) bar
92             * => http://www.pokemon.com/Pikachu_(Electric)
93             *
94             * foo (http://www.pokemon.com/Pikachu_(Electric)) bar
95             * => http://www.pokemon.com/Pikachu_(Electric)
96             *
97             * foo http://www.pokemon.com/Pikachu_(Electric)) bar
98             * => http://www.pokemon.com/Pikachu_(Electric))
99             *
100             * (foo http://www.pokemon.com/Pikachu_(Electric)) bar
101             * => foo http://www.pokemon.com/Pikachu_(Electric)
102             */
103              
104 0 0         while (i < link_end) {
105 0 0         if (data[i] == copen)
106 0           opening++;
107 0 0         else if (data[i] == cclose)
108 0           closing++;
109              
110 0           i++;
111             }
112              
113 0 0         if (closing != opening)
114             link_end--;
115             }
116              
117             return link_end;
118             }
119              
120             static size_t
121 2           check_domain(uint8_t *data, size_t size, int allow_short)
122             {
123             size_t i, np = 0;
124              
125 2 50         if (!isalnum(data[0]))
126             return 0;
127              
128 13 100         for (i = 1; i < size - 1; ++i) {
129 12 100         if (strchr(".:", data[i]) != NULL) np++;
130 10 100         else if (!isalnum(data[i]) && data[i] != '-') break;
    50          
131             }
132              
133 2 50         if (allow_short) {
134             /* We don't need a valid domain in the strict sense (with
135             * least one dot; so just make sure it's composed of valid
136             * domain characters and return the length of the the valid
137             * sequence. */
138             return i;
139             } else {
140             /* a valid domain needs to have at least a dot.
141             * that's as far as we get */
142 2 50         return np ? i : 0;
143             }
144             }
145              
146             size_t
147 0           hoedown_autolink__www(
148             size_t *rewind_p,
149             hoedown_buffer *link,
150             uint8_t *data,
151             size_t max_rewind,
152             size_t size,
153             unsigned int flags)
154             {
155             size_t link_end;
156              
157 0 0         if (max_rewind > 0 && !ispunct(data[-1]) && !isspace(data[-1]))
    0          
158             return 0;
159              
160 0 0         if (size < 4 || memcmp(data, "www.", strlen("www.")) != 0)
    0          
161             return 0;
162              
163 0           link_end = check_domain(data, size, 0);
164              
165 0 0         if (link_end == 0)
166             return 0;
167              
168 0 0         while (link_end < size && !isspace(data[link_end]))
    0          
169 0           link_end++;
170              
171 0           link_end = autolink_delim(data, link_end, max_rewind, size);
172              
173 0 0         if (link_end == 0)
174             return 0;
175              
176 0           hoedown_buffer_put(link, data, link_end);
177 0           *rewind_p = 0;
178              
179 0           return (int)link_end;
180             }
181              
182             size_t
183 0           hoedown_autolink__email(
184             size_t *rewind_p,
185             hoedown_buffer *link,
186             uint8_t *data,
187             size_t max_rewind,
188             size_t size,
189             unsigned int flags)
190             {
191             size_t link_end, rewind;
192             int nb = 0, np = 0;
193              
194 0 0         for (rewind = 0; rewind < max_rewind; ++rewind) {
195 0           uint8_t c = data[-1 - rewind];
196              
197 0 0         if (isalnum(c))
198 0           continue;
199              
200 0 0         if (strchr(".+-_", c) != NULL)
201 0           continue;
202              
203             break;
204             }
205              
206 0 0         if (rewind == 0)
207             return 0;
208              
209 0 0         for (link_end = 0; link_end < size; ++link_end) {
210 0           uint8_t c = data[link_end];
211              
212 0 0         if (isalnum(c))
213 0           continue;
214              
215 0 0         if (c == '@')
216 0           nb++;
217 0 0         else if (c == '.' && link_end < size - 1)
    0          
218 0           np++;
219 0 0         else if (c != '-' && c != '_')
220             break;
221             }
222              
223 0 0         if (link_end < 2 || nb != 1 || np == 0 ||
224 0           !isalpha(data[link_end - 1]))
225             return 0;
226              
227 0           link_end = autolink_delim(data, link_end, max_rewind, size);
228              
229 0 0         if (link_end == 0)
230             return 0;
231              
232 0           hoedown_buffer_put(link, data - rewind, link_end + rewind);
233 0           *rewind_p = rewind;
234              
235 0           return link_end;
236             }
237              
238             size_t
239 2           hoedown_autolink__url(
240             size_t *rewind_p,
241             hoedown_buffer *link,
242             uint8_t *data,
243             size_t max_rewind,
244             size_t size,
245             unsigned int flags)
246             {
247             size_t link_end, rewind = 0, domain_len;
248              
249 2 50         if (size < 4 || data[1] != '/' || data[2] != '/')
    50          
    50          
250             return 0;
251              
252 10 100         while (rewind < max_rewind && isalpha(data[-1 - rewind]))
    100          
253 8           rewind++;
254              
255 2 50         if (!hoedown_autolink_is_safe(data - rewind, size + rewind))
256             return 0;
257              
258             link_end = strlen("://");
259              
260 2           domain_len = check_domain(
261             data + link_end,
262             size - link_end,
263             flags & HOEDOWN_AUTOLINK_SHORT_DOMAINS);
264              
265 2 50         if (domain_len == 0)
266             return 0;
267              
268 2           link_end += domain_len;
269 4 100         while (link_end < size && !isspace(data[link_end]))
    100          
270 2           link_end++;
271              
272 2           link_end = autolink_delim(data, link_end, max_rewind, size);
273              
274 2 50         if (link_end == 0)
275             return 0;
276              
277 2           hoedown_buffer_put(link, data - rewind, link_end + rewind);
278 2           *rewind_p = rewind;
279              
280 2           return link_end;
281             }