Line | Branch | Exec | Source |
---|---|---|---|
1 | /* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ | ||
2 | /* cc-qr-code.c | ||
3 | * | ||
4 | * Copyright 2019 Purism SPC | ||
5 | * | ||
6 | * This program is free software: you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation, either version 3 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
18 | * | ||
19 | * Author(s): | ||
20 | * Mohammed Sadiq <sadiq@sadiqpk.org> | ||
21 | * | ||
22 | * SPDX-License-Identifier: GPL-3.0-or-later | ||
23 | */ | ||
24 | |||
25 | #undef G_LOG_DOMAIN | ||
26 | #define G_LOG_DOMAIN "cc-qr-code" | ||
27 | |||
28 | #ifdef HAVE_CONFIG_H | ||
29 | #include "config.h" | ||
30 | #endif | ||
31 | |||
32 | #include "cc-qr-code.h" | ||
33 | #include "qrcodegen.c" | ||
34 | |||
35 | /** | ||
36 | * SECTION: cc-qr_code | ||
37 | * @title: CcQrCode | ||
38 | * @short_description: A Simple QR Code wrapper around libqrencode | ||
39 | * @include: "cc-qr-code.h" | ||
40 | * | ||
41 | * Generate a QR image from a given text. | ||
42 | */ | ||
43 | |||
44 | #define BYTES_PER_R8G8B8 3 | ||
45 | |||
46 | struct _CcQrCode | ||
47 | { | ||
48 | GObject parent_instance; | ||
49 | |||
50 | gchar *text; | ||
51 | GdkTexture *texture; | ||
52 | gint size; | ||
53 | }; | ||
54 | |||
55 | ✗ | G_DEFINE_TYPE (CcQrCode, cc_qr_code, G_TYPE_OBJECT) | |
56 | |||
57 | static void | ||
58 | ✗ | cc_qr_code_finalize (GObject *object) | |
59 | { | ||
60 | ✗ | CcQrCode *self = (CcQrCode *) object; | |
61 | |||
62 | ✗ | g_clear_object (&self->texture); | |
63 | ✗ | g_clear_pointer (&self->text, g_free); | |
64 | |||
65 | ✗ | G_OBJECT_CLASS (cc_qr_code_parent_class)->finalize (object); | |
66 | ✗ | } | |
67 | |||
68 | static void | ||
69 | ✗ | cc_qr_code_class_init (CcQrCodeClass *klass) | |
70 | { | ||
71 | ✗ | GObjectClass *object_class = G_OBJECT_CLASS (klass); | |
72 | |||
73 | ✗ | object_class->finalize = cc_qr_code_finalize; | |
74 | ✗ | } | |
75 | |||
76 | static void | ||
77 | ✗ | cc_qr_code_init (CcQrCode *self) | |
78 | { | ||
79 | ✗ | } | |
80 | |||
81 | CcQrCode * | ||
82 | ✗ | cc_qr_code_new (void) | |
83 | { | ||
84 | ✗ | return g_object_new (CC_TYPE_QR_CODE, NULL); | |
85 | } | ||
86 | |||
87 | gboolean | ||
88 | ✗ | cc_qr_code_set_text (CcQrCode *self, | |
89 | const gchar *text) | ||
90 | { | ||
91 | ✗ | g_return_val_if_fail (CC_IS_QR_CODE (self), FALSE); | |
92 | ✗ | g_return_val_if_fail (!text || *text, FALSE); | |
93 | |||
94 | ✗ | if (g_strcmp0 (text, self->text) == 0) | |
95 | ✗ | return FALSE; | |
96 | |||
97 | ✗ | g_clear_object (&self->texture); | |
98 | ✗ | g_free (self->text); | |
99 | ✗ | self->text = g_strdup (text); | |
100 | |||
101 | ✗ | return TRUE; | |
102 | } | ||
103 | |||
104 | static void | ||
105 | ✗ | cc_fill_pixel (GByteArray *array, | |
106 | guint8 value, | ||
107 | int pixel_size) | ||
108 | { | ||
109 | guint i; | ||
110 | |||
111 | ✗ | for (i = 0; i < pixel_size; i++) | |
112 | { | ||
113 | ✗ | g_byte_array_append (array, &value, 1); /* R */ | |
114 | ✗ | g_byte_array_append (array, &value, 1); /* G */ | |
115 | ✗ | g_byte_array_append (array, &value, 1); /* B */ | |
116 | } | ||
117 | ✗ | } | |
118 | |||
119 | GdkPaintable * | ||
120 | ✗ | cc_qr_code_get_paintable (CcQrCode *self, | |
121 | gint size) | ||
122 | { | ||
123 | uint8_t qr_code[qrcodegen_BUFFER_LEN_FOR_VERSION (qrcodegen_VERSION_MAX)]; | ||
124 | uint8_t temp_buf[qrcodegen_BUFFER_LEN_FOR_VERSION (qrcodegen_VERSION_MAX)]; | ||
125 | ✗ | g_autoptr (GBytes) bytes = NULL; | |
126 | GByteArray *qr_matrix; | ||
127 | gint pixel_size, qr_size, total_size; | ||
128 | gint column, row, i; | ||
129 | ✗ | gboolean success = FALSE; | |
130 | |||
131 | ✗ | g_return_val_if_fail (CC_IS_QR_CODE (self), NULL); | |
132 | ✗ | g_return_val_if_fail (size > 0, NULL); | |
133 | |||
134 | ✗ | if (!self->text || !*self->text) | |
135 | { | ||
136 | ✗ | g_warn_if_reached (); | |
137 | ✗ | cc_qr_code_set_text (self, "invalid text"); | |
138 | } | ||
139 | |||
140 | ✗ | if (self->texture && self->size == size) | |
141 | ✗ | return GDK_PAINTABLE (self->texture); | |
142 | |||
143 | ✗ | self->size = size; | |
144 | |||
145 | ✗ | success = qrcodegen_encodeText (self->text, | |
146 | temp_buf, | ||
147 | qr_code, | ||
148 | qrcodegen_Ecc_LOW, | ||
149 | qrcodegen_VERSION_MIN, | ||
150 | qrcodegen_VERSION_MAX, | ||
151 | qrcodegen_Mask_AUTO, | ||
152 | FALSE); | ||
153 | |||
154 | ✗ | if (!success) | |
155 | ✗ | return NULL; | |
156 | |||
157 | ✗ | qr_size = qrcodegen_getSize (qr_code); | |
158 | ✗ | pixel_size = MAX (1, size / (qr_size)); | |
159 | ✗ | total_size = qr_size * pixel_size; | |
160 | ✗ | qr_matrix = g_byte_array_sized_new (total_size * total_size * pixel_size * BYTES_PER_R8G8B8); | |
161 | |||
162 | ✗ | for (column = 0; column < total_size; column++) | |
163 | { | ||
164 | ✗ | for (i = 0; i < pixel_size; i++) | |
165 | { | ||
166 | ✗ | for (row = 0; row < total_size / pixel_size; row++) | |
167 | { | ||
168 | ✗ | if (qrcodegen_getModule (qr_code, column, row)) | |
169 | ✗ | cc_fill_pixel (qr_matrix, 0x00, pixel_size); | |
170 | else | ||
171 | ✗ | cc_fill_pixel (qr_matrix, 0xff, pixel_size); | |
172 | } | ||
173 | } | ||
174 | } | ||
175 | |||
176 | ✗ | bytes = g_byte_array_free_to_bytes (qr_matrix); | |
177 | |||
178 | ✗ | g_clear_object (&self->texture); | |
179 | ✗ | self->texture = gdk_memory_texture_new (total_size, | |
180 | total_size, | ||
181 | GDK_MEMORY_R8G8B8, | ||
182 | bytes, | ||
183 | ✗ | total_size * BYTES_PER_R8G8B8); | |
184 | |||
185 | ✗ | return GDK_PAINTABLE (self->texture); | |
186 | } | ||
187 | |||
188 | static gchar * | ||
189 | 6 | escape_string (const gchar *str, | |
190 | gboolean quote) | ||
191 | { | ||
192 | GString *string; | ||
193 | const char *next; | ||
194 | |||
195 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5 times.
|
6 | if (!str) |
196 | 1 | return NULL; | |
197 | |||
198 | 5 | string = g_string_new (""); | |
199 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
|
5 | if (quote) |
200 | g_string_append_c (string, '"'); | ||
201 | |||
202 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
|
10 | while ((next = strpbrk (str, "\\;,:\""))) |
203 | { | ||
204 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | g_string_append_len (string, str, next - str); |
205 | g_string_append_c (string, '\\'); | ||
206 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | g_string_append_c (string, *next); |
207 | 5 | str = next + 1; | |
208 | } | ||
209 | |||
210 | g_string_append (string, str); | ||
211 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
|
5 | if (quote) |
212 | g_string_append_c (string, '"'); | ||
213 | |||
214 | 5 | return g_string_free (string, FALSE); | |
215 | } | ||
216 | |||
217 | static const gchar * | ||
218 | ✗ | get_connection_security_type (NMConnection *c) | |
219 | { | ||
220 | NMSettingWirelessSecurity *setting; | ||
221 | const char *key_mgmt; | ||
222 | |||
223 | ✗ | g_return_val_if_fail (c, "nopass"); | |
224 | |||
225 | ✗ | setting = nm_connection_get_setting_wireless_security (c); | |
226 | |||
227 | ✗ | if (!setting) | |
228 | ✗ | return "nopass"; | |
229 | |||
230 | ✗ | key_mgmt = nm_setting_wireless_security_get_key_mgmt (setting); | |
231 | |||
232 | /* No IEEE 802.1x */ | ||
233 | ✗ | if (g_strcmp0 (key_mgmt, "none") == 0) | |
234 | ✗ | return "WEP"; | |
235 | |||
236 | ✗ | if (g_strcmp0 (key_mgmt, "wpa-psk") == 0) | |
237 | ✗ | return "WPA"; | |
238 | |||
239 | ✗ | if (g_strcmp0 (key_mgmt, "sae") == 0) | |
240 | ✗ | return "SAE"; | |
241 | |||
242 | ✗ | return "nopass"; | |
243 | } | ||
244 | |||
245 | gboolean | ||
246 | ✗ | is_qr_code_supported (NMConnection *c) | |
247 | { | ||
248 | NMSettingWirelessSecurity *setting; | ||
249 | const char *key_mgmt; | ||
250 | NMSettingConnection *s_con; | ||
251 | guint64 timestamp; | ||
252 | |||
253 | ✗ | g_return_val_if_fail (c, TRUE); | |
254 | |||
255 | ✗ | s_con = nm_connection_get_setting_connection (c); | |
256 | ✗ | timestamp = nm_setting_connection_get_timestamp (s_con); | |
257 | |||
258 | /* Check timestamp to determine if connection was successful in the past */ | ||
259 | ✗ | if (timestamp == 0) | |
260 | ✗ | return FALSE; | |
261 | |||
262 | ✗ | setting = nm_connection_get_setting_wireless_security (c); | |
263 | |||
264 | ✗ | if (!setting) | |
265 | ✗ | return TRUE; | |
266 | |||
267 | ✗ | key_mgmt = nm_setting_wireless_security_get_key_mgmt (setting); | |
268 | |||
269 | ✗ | if (g_str_equal (key_mgmt, "none") || | |
270 | ✗ | g_str_equal (key_mgmt, "wpa-psk") || | |
271 | ✗ | g_str_equal (key_mgmt, "sae")) | |
272 | ✗ | return TRUE; | |
273 | |||
274 | ✗ | return FALSE; | |
275 | } | ||
276 | |||
277 | gchar * | ||
278 | ✗ | get_wifi_password (NMConnection *c) | |
279 | { | ||
280 | NMSettingWirelessSecurity *setting; | ||
281 | const gchar *sec_type, *password; | ||
282 | gint wep_index; | ||
283 | |||
284 | ✗ | sec_type = get_connection_security_type (c); | |
285 | ✗ | setting = nm_connection_get_setting_wireless_security (c); | |
286 | |||
287 | ✗ | if (g_str_equal (sec_type, "nopass")) | |
288 | ✗ | return NULL; | |
289 | |||
290 | ✗ | if (g_str_equal (sec_type, "WEP")) | |
291 | { | ||
292 | ✗ | wep_index = nm_setting_wireless_security_get_wep_tx_keyidx (setting); | |
293 | ✗ | password = nm_setting_wireless_security_get_wep_key (setting, wep_index); | |
294 | } | ||
295 | else | ||
296 | { | ||
297 | ✗ | password = nm_setting_wireless_security_get_psk (setting); | |
298 | } | ||
299 | |||
300 | ✗ | return g_strdup (password); | |
301 | } | ||
302 | |||
303 | /* Generate a string representing the connection | ||
304 | * An example generated text: | ||
305 | * WIFI:S:ssid;T:WPA;P:my-valid-pass;H:true; | ||
306 | * Where, | ||
307 | * S = ssid, T = security, P = password, H = hidden (Optional) | ||
308 | * | ||
309 | * See https://github.com/zxing/zxing/wiki/Barcode-Contents#wi-fi-network-config-android-ios-11 | ||
310 | */ | ||
311 | gchar * | ||
312 | ✗ | get_qr_string_for_connection (NMConnection *c) | |
313 | { | ||
314 | NMSettingWireless *setting; | ||
315 | ✗ | g_autofree char *ssid_text = NULL; | |
316 | ✗ | g_autofree char *escaped_ssid = NULL; | |
317 | ✗ | g_autofree char *password_str = NULL; | |
318 | ✗ | g_autofree char *escaped_password = NULL; | |
319 | GString *string; | ||
320 | GBytes *ssid; | ||
321 | gboolean hidden; | ||
322 | |||
323 | ✗ | setting = nm_connection_get_setting_wireless (c); | |
324 | ✗ | ssid = nm_setting_wireless_get_ssid (setting); | |
325 | |||
326 | ✗ | if (!ssid) | |
327 | ✗ | return NULL; | |
328 | |||
329 | ✗ | string = g_string_new ("WIFI:S:"); | |
330 | |||
331 | /* SSID */ | ||
332 | ✗ | ssid_text = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL), | |
333 | g_bytes_get_size (ssid)); | ||
334 | ✗ | escaped_ssid = escape_string (ssid_text, FALSE); | |
335 | ✗ | g_string_append (string, escaped_ssid); | |
336 | g_string_append_c (string, ';'); | ||
337 | |||
338 | /* Security type */ | ||
339 | ✗ | g_string_append (string, "T:"); | |
340 | ✗ | g_string_append (string, get_connection_security_type (c)); | |
341 | g_string_append_c (string, ';'); | ||
342 | |||
343 | /* Password */ | ||
344 | ✗ | g_string_append (string, "P:"); | |
345 | ✗ | password_str = get_wifi_password (c); | |
346 | ✗ | escaped_password = escape_string (password_str, FALSE); | |
347 | ✗ | if (escaped_password) | |
348 | ✗ | g_string_append (string, escaped_password); | |
349 | g_string_append_c (string, ';'); | ||
350 | |||
351 | /* WiFi Hidden */ | ||
352 | ✗ | hidden = nm_setting_wireless_get_hidden (setting); | |
353 | ✗ | if (hidden) | |
354 | ✗ | g_string_append (string, "H:true"); | |
355 | g_string_append_c (string, ';'); | ||
356 | |||
357 | ✗ | return g_string_free (string, FALSE); | |
358 | } | ||
359 |