Line | Count | Source (jump to first uncovered line) |
1 | | /* $OpenBSD$ */ |
2 | | |
3 | | /* |
4 | | * Copyright (c) 2021 Will <author@will.party> |
5 | | * Copyright (c) 2022 Jeff Chiang <pobomp@gmail.com> |
6 | | * |
7 | | * Permission to use, copy, modify, and distribute this software for any |
8 | | * purpose with or without fee is hereby granted, provided that the above |
9 | | * copyright notice and this permission notice appear in all copies. |
10 | | * |
11 | | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
12 | | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
13 | | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
14 | | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
15 | | * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER |
16 | | * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING |
17 | | * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
18 | | */ |
19 | | |
20 | | #include <sys/types.h> |
21 | | |
22 | | #include <stdlib.h> |
23 | | #include <string.h> |
24 | | |
25 | | #include "tmux.h" |
26 | | |
27 | | /* |
28 | | * OSC 8 hyperlinks, described at: |
29 | | * |
30 | | * https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda |
31 | | * |
32 | | * Each hyperlink and ID combination is assigned a number ("inner" in this |
33 | | * file) which is stored in an extended grid cell and maps into a tree here. |
34 | | * |
35 | | * Each URI has one inner number and one external ID (which tmux uses to send |
36 | | * the hyperlink to the terminal) and one internal ID (which is received from |
37 | | * the sending application inside tmux). |
38 | | * |
39 | | * Anonymous hyperlinks are each unique and are not reused even if they have |
40 | | * the same URI (terminals will not want to tie them together). |
41 | | */ |
42 | | |
43 | 6.20k | #define MAX_HYPERLINKS 5000 |
44 | | |
45 | | static long long hyperlinks_next_external_id = 1; |
46 | | static u_int global_hyperlinks_count; |
47 | | |
48 | | struct hyperlinks_uri { |
49 | | struct hyperlinks *tree; |
50 | | |
51 | | u_int inner; |
52 | | const char *internal_id; |
53 | | const char *external_id; |
54 | | const char *uri; |
55 | | |
56 | | TAILQ_ENTRY(hyperlinks_uri) list_entry; |
57 | | RB_ENTRY(hyperlinks_uri) by_inner_entry; |
58 | | RB_ENTRY(hyperlinks_uri) by_uri_entry; /* by internal ID and URI */ |
59 | | }; |
60 | | RB_HEAD(hyperlinks_by_uri_tree, hyperlinks_uri); |
61 | | RB_HEAD(hyperlinks_by_inner_tree, hyperlinks_uri); |
62 | | |
63 | | TAILQ_HEAD(hyperlinks_list, hyperlinks_uri); |
64 | | static struct hyperlinks_list global_hyperlinks = |
65 | | TAILQ_HEAD_INITIALIZER(global_hyperlinks); |
66 | | |
67 | | struct hyperlinks { |
68 | | u_int next_inner; |
69 | | struct hyperlinks_by_inner_tree by_inner; |
70 | | struct hyperlinks_by_uri_tree by_uri; |
71 | | u_int references; |
72 | | }; |
73 | | |
74 | | static int |
75 | | hyperlinks_by_uri_cmp(struct hyperlinks_uri *left, struct hyperlinks_uri *right) |
76 | 34.2k | { |
77 | 34.2k | int r; |
78 | | |
79 | 34.2k | if (*left->internal_id == '\0' || *right->internal_id == '\0') { |
80 | | /* |
81 | | * If both URIs are anonymous, use the inner for comparison so |
82 | | * that they do not match even if the URI is the same - each |
83 | | * anonymous URI should be unique. |
84 | | */ |
85 | 22.6k | if (*left->internal_id != '\0') |
86 | 3.10k | return (-1); |
87 | 19.4k | if (*right->internal_id != '\0') |
88 | 382 | return (1); |
89 | 19.1k | return (left->inner - right->inner); |
90 | 19.4k | } |
91 | | |
92 | 11.6k | r = strcmp(left->internal_id, right->internal_id); |
93 | 11.6k | if (r != 0) |
94 | 9.39k | return (r); |
95 | 2.27k | return (strcmp(left->uri, right->uri)); |
96 | 11.6k | } |
97 | | RB_PROTOTYPE_STATIC(hyperlinks_by_uri_tree, hyperlinks_uri, by_uri_entry, |
98 | | hyperlinks_by_uri_cmp); |
99 | | RB_GENERATE_STATIC(hyperlinks_by_uri_tree, hyperlinks_uri, by_uri_entry, |
100 | | hyperlinks_by_uri_cmp); |
101 | | |
102 | | static int |
103 | | hyperlinks_by_inner_cmp(struct hyperlinks_uri *left, |
104 | | struct hyperlinks_uri *right) |
105 | 28.0k | { |
106 | 28.0k | return (left->inner - right->inner); |
107 | 28.0k | } |
108 | | RB_PROTOTYPE_STATIC(hyperlinks_by_inner_tree, hyperlinks_uri, by_inner_entry, |
109 | | hyperlinks_by_inner_cmp); |
110 | | RB_GENERATE_STATIC(hyperlinks_by_inner_tree, hyperlinks_uri, by_inner_entry, |
111 | | hyperlinks_by_inner_cmp); |
112 | | |
113 | | /* Remove a hyperlink. */ |
114 | | static void |
115 | | hyperlinks_remove(struct hyperlinks_uri *hlu) |
116 | 6.20k | { |
117 | 6.20k | struct hyperlinks *hl = hlu->tree; |
118 | | |
119 | 6.20k | TAILQ_REMOVE(&global_hyperlinks, hlu, list_entry); |
120 | 6.20k | global_hyperlinks_count--; |
121 | | |
122 | 6.20k | RB_REMOVE(hyperlinks_by_inner_tree, &hl->by_inner, hlu); |
123 | 6.20k | RB_REMOVE(hyperlinks_by_uri_tree, &hl->by_uri, hlu); |
124 | | |
125 | 6.20k | free((void *)hlu->internal_id); |
126 | 6.20k | free((void *)hlu->external_id); |
127 | 6.20k | free((void *)hlu->uri); |
128 | 6.20k | free(hlu); |
129 | 6.20k | } |
130 | | |
131 | | /* Store a new hyperlink or return if it already exists. */ |
132 | | u_int |
133 | | hyperlinks_put(struct hyperlinks *hl, const char *uri_in, |
134 | | const char *internal_id_in) |
135 | 6.32k | { |
136 | 6.32k | struct hyperlinks_uri find, *hlu; |
137 | 6.32k | char *uri, *internal_id, *external_id; |
138 | | |
139 | | /* |
140 | | * Anonymous URI are stored with an empty internal ID and the tree |
141 | | * comparator will make sure they never match each other (so each |
142 | | * anonymous URI is unique). |
143 | | */ |
144 | 6.32k | if (internal_id_in == NULL) |
145 | 3.85k | internal_id_in = ""; |
146 | | |
147 | 6.32k | utf8_stravis(&uri, uri_in, VIS_OCTAL|VIS_CSTYLE); |
148 | 6.32k | utf8_stravis(&internal_id, internal_id_in, VIS_OCTAL|VIS_CSTYLE); |
149 | | |
150 | 6.32k | if (*internal_id_in != '\0') { |
151 | 2.47k | find.uri = uri; |
152 | 2.47k | find.internal_id = internal_id; |
153 | | |
154 | 2.47k | hlu = RB_FIND(hyperlinks_by_uri_tree, &hl->by_uri, &find); |
155 | 2.47k | if (hlu != NULL) { |
156 | 129 | free (uri); |
157 | 129 | free (internal_id); |
158 | 129 | return (hlu->inner); |
159 | 129 | } |
160 | 2.47k | } |
161 | 6.20k | xasprintf(&external_id, "tmux%llX", hyperlinks_next_external_id++); |
162 | | |
163 | 6.20k | hlu = xcalloc(1, sizeof *hlu); |
164 | 6.20k | hlu->inner = hl->next_inner++; |
165 | 6.20k | hlu->internal_id = internal_id; |
166 | 6.20k | hlu->external_id = external_id; |
167 | 6.20k | hlu->uri = uri; |
168 | 6.20k | hlu->tree = hl; |
169 | 6.20k | RB_INSERT(hyperlinks_by_uri_tree, &hl->by_uri, hlu); |
170 | 6.20k | RB_INSERT(hyperlinks_by_inner_tree, &hl->by_inner, hlu); |
171 | | |
172 | 6.20k | TAILQ_INSERT_TAIL(&global_hyperlinks, hlu, list_entry); |
173 | 6.20k | if (++global_hyperlinks_count == MAX_HYPERLINKS) |
174 | 0 | hyperlinks_remove(TAILQ_FIRST(&global_hyperlinks)); |
175 | | |
176 | 6.20k | return (hlu->inner); |
177 | 6.32k | } |
178 | | |
179 | | /* Get hyperlink by inner number. */ |
180 | | int |
181 | | hyperlinks_get(struct hyperlinks *hl, u_int inner, const char **uri_out, |
182 | | const char **internal_id_out, const char **external_id_out) |
183 | 0 | { |
184 | 0 | struct hyperlinks_uri find, *hlu; |
185 | |
|
186 | 0 | find.inner = inner; |
187 | |
|
188 | 0 | hlu = RB_FIND(hyperlinks_by_inner_tree, &hl->by_inner, &find); |
189 | 0 | if (hlu == NULL) |
190 | 0 | return (0); |
191 | 0 | if (internal_id_out != NULL) |
192 | 0 | *internal_id_out = hlu->internal_id; |
193 | 0 | if (external_id_out != NULL) |
194 | 0 | *external_id_out = hlu->external_id; |
195 | 0 | *uri_out = hlu->uri; |
196 | 0 | return (1); |
197 | 0 | } |
198 | | |
199 | | /* Initialize hyperlink set. */ |
200 | | struct hyperlinks * |
201 | | hyperlinks_init(void) |
202 | 22.4k | { |
203 | 22.4k | struct hyperlinks *hl; |
204 | | |
205 | 22.4k | hl = xcalloc(1, sizeof *hl); |
206 | 22.4k | hl->next_inner = 1; |
207 | 22.4k | RB_INIT(&hl->by_uri); |
208 | 22.4k | RB_INIT(&hl->by_inner); |
209 | 22.4k | hl->references = 1; |
210 | 22.4k | return (hl); |
211 | 22.4k | } |
212 | | |
213 | | /* Copy hyperlink set. */ |
214 | | struct hyperlinks * |
215 | | hyperlinks_copy(struct hyperlinks *hl) |
216 | 0 | { |
217 | 0 | hl->references++; |
218 | 0 | return (hl); |
219 | 0 | } |
220 | | |
221 | | /* Free all hyperlinks but not the set itself. */ |
222 | | void |
223 | | hyperlinks_reset(struct hyperlinks *hl) |
224 | 22.4k | { |
225 | 22.4k | struct hyperlinks_uri *hlu, *hlu1; |
226 | | |
227 | 22.4k | RB_FOREACH_SAFE(hlu, hyperlinks_by_inner_tree, &hl->by_inner, hlu1) |
228 | 6.20k | hyperlinks_remove(hlu); |
229 | 22.4k | } |
230 | | |
231 | | /* Free hyperlink set. */ |
232 | | void |
233 | | hyperlinks_free(struct hyperlinks *hl) |
234 | 22.4k | { |
235 | 22.4k | if (--hl->references == 0) { |
236 | 22.4k | hyperlinks_reset(hl); |
237 | 22.4k | free(hl); |
238 | 22.4k | } |
239 | 22.4k | } |