File Coverage

hoedown/src/autolink.c
Criterion Covered Total %
statement 39 85 45.8
branch 37 112 33.0
condition n/a
subroutine n/a
pod n/a
total 76 197 38.5


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