Mercurial > pidgin
annotate src/protocols/irc/parse.c @ 8351:ffa642240fc1
[gaim-migrate @ 9075]
marv is incredibly patient, waiting a long time for this to go in
you should all be using Jabber for file transfer anyway, but if you're
still stuck in the stone age, now you can transfer files via IRC
committer: Tailor Script <tailor@pidgin.im>
author | Nathan Walp <nwalp@pidgin.im> |
---|---|
date | Fri, 27 Feb 2004 01:45:26 +0000 |
parents | 7a6e30eb7aad |
children | 3f384e95c5c0 |
rev | line source |
---|---|
6333 | 1 /** |
2 * @file parse.c | |
8351 | 3 * |
6333 | 4 * gaim |
5 * | |
6 * Copyright (C) 2003, Ethan Blanton <eblanton@cs.purdue.edu> | |
8351 | 7 * |
6333 | 8 * This program is free software; you can redistribute it and/or modify |
9 * it under the terms of the GNU General Public License as published by | |
10 * the Free Software Foundation; either version 2 of the License, or | |
11 * (at your option) any later version. | |
12 * | |
13 * This program is distributed in the hope that it will be useful, | |
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 * GNU General Public License for more details. | |
17 * | |
18 * You should have received a copy of the GNU General Public License | |
19 * along with this program; if not, write to the Free Software | |
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
21 */ | |
22 | |
23 #include "internal.h" | |
24 | |
25 #include "accountopt.h" | |
26 #include "conversation.h" | |
27 #include "notify.h" | |
28 #include "debug.h" | |
29 #include "irc.h" | |
30 | |
31 #include <stdio.h> | |
32 #include <stdlib.h> | |
33 #include <ctype.h> | |
34 | |
35 static char *irc_send_convert(struct irc_conn *irc, const char *string); | |
36 static char *irc_recv_convert(struct irc_conn *irc, const char *string); | |
37 | |
38 char *irc_mirc2html(const char *string); | |
39 | |
40 static void irc_parse_error_cb(struct irc_conn *irc, char *input); | |
41 | |
42 static char *irc_mirc_colors[16] = { | |
43 "white", "black", "blue", "dark green", "red", "brown", "purple", | |
44 "orange", "yellow", "green", "teal", "cyan", "light blue", | |
45 "pink", "grey", "light grey" }; | |
46 | |
47 /*typedef void (*IRCMsgCallback)(struct irc_conn *irc, char *from, char *name, char **args);*/ | |
48 static struct _irc_msg { | |
49 char *name; | |
50 char *format; | |
51 void (*cb)(struct irc_conn *irc, const char *name, const char *from, char **args); | |
52 } _irc_msgs[] = { | |
53 { "301", "nn:", irc_msg_away }, /* User is away */ | |
54 { "303", "n:", irc_msg_ison }, /* ISON reply */ | |
55 { "311", "nnvvv:", irc_msg_whois }, /* Whois user */ | |
56 { "312", "nnv:", irc_msg_whois }, /* Whois server */ | |
57 { "313", "nn:", irc_msg_whois }, /* Whois ircop */ | |
58 { "317", "nnvv", irc_msg_whois }, /* Whois idle */ | |
59 { "318", "nt:", irc_msg_endwhois }, /* End of WHOIS */ | |
60 { "319", "nn:", irc_msg_whois }, /* Whois channels */ | |
61 { "320", "nn:", irc_msg_whois }, /* Whois (fn ident) */ | |
8114 | 62 { "321", "*", irc_msg_list }, /* Start of list */ |
63 { "322", "ncv:", irc_msg_list }, /* List. */ | |
64 { "323", ":", irc_msg_list }, /* End of list. */ | |
6333 | 65 { "324", "ncv:", irc_msg_chanmode }, /* Channel modes */ |
66 { "331", "nc:", irc_msg_topic }, /* No channel topic */ | |
67 { "332", "nc:", irc_msg_topic }, /* Channel topic */ | |
68 { "333", "*", irc_msg_ignore }, /* Topic setter stuff */ | |
69 { "353", "nvc:", irc_msg_names }, /* Names list */ | |
70 { "366", "nc:", irc_msg_names }, /* End of names */ | |
71 { "372", "n:", irc_msg_motd }, /* MOTD */ | |
72 { "375", "n:", irc_msg_motd }, /* Start MOTD */ | |
73 { "376", "n:", irc_msg_endmotd }, /* End of MOTD */ | |
74 { "401", "nt:", irc_msg_nonick }, /* No such nick/chan */ | |
7877 | 75 { "403", "nc:", irc_msg_nochan }, /* No such channel */ |
6333 | 76 { "404", "nt:", irc_msg_nosend }, /* Cannot send to chan */ |
77 { "421", "nv:", irc_msg_unknown }, /* Unknown command */ | |
6350 | 78 { "422", "nv:", irc_msg_endmotd }, /* No MOTD available */ |
6333 | 79 { "433", "vn:", irc_msg_nickused }, /* Nickname already in use */ |
6718 | 80 { "438", "nn:", irc_msg_nochangenick }, /* Nick may not change */ |
6333 | 81 { "442", "nc:", irc_msg_notinchan }, /* Not in channel */ |
82 { "473", "nc:", irc_msg_inviteonly }, /* Tried to join invite-only */ | |
83 { "474", "nc:", irc_msg_banned }, /* Banned from channel */ | |
84 { "482", "nc:", irc_msg_notop }, /* Need to be op to do that */ | |
85 { "501", "n:", irc_msg_badmode }, /* Unknown mode flag */ | |
6714 | 86 { "515", "nc:", irc_msg_regonly }, /* Registration required */ |
6333 | 87 { "invite", "n:", irc_msg_invite }, /* Invited */ |
88 { "join", ":", irc_msg_join }, /* Joined a channel */ | |
89 { "kick", "cn:", irc_msg_kick }, /* KICK */ | |
90 { "mode", "tv:", irc_msg_mode }, /* MODE for channel */ | |
91 { "nick", ":", irc_msg_nick }, /* Nick change */ | |
92 { "notice", "t:", irc_msg_notice }, /* NOTICE recv */ | |
93 { "part", "c:", irc_msg_part }, /* Parted a channel */ | |
94 { "ping", ":", irc_msg_ping }, /* Received PING from server */ | |
95 { "pong", "v:", irc_msg_pong }, /* Received PONG from server */ | |
96 { "privmsg", "t:", irc_msg_privmsg }, /* Received private message */ | |
97 { "topic", "c:", irc_msg_topic }, /* TOPIC command */ | |
98 { "quit", ":", irc_msg_quit }, /* QUIT notice */ | |
99 { "wallops", ":", irc_msg_wallops }, /* WALLOPS command */ | |
100 { NULL, NULL, NULL } | |
101 }; | |
102 | |
103 static struct _irc_user_cmd { | |
104 char *name; | |
105 char *format; | |
106 IRCCmdCallback cb; | |
107 } _irc_cmds[] = { | |
108 { "away", ":", irc_cmd_away }, | |
109 { "deop", ":", irc_cmd_op }, | |
110 { "devoice", ":", irc_cmd_op }, | |
6415
e3be6b9744b7
[gaim-migrate @ 6922]
Christian Hammond <chipx86@chipx86.com>
parents:
6350
diff
changeset
|
111 { "help", "v", irc_cmd_help }, |
6333 | 112 { "invite", ":", irc_cmd_invite }, |
113 { "j", "cv", irc_cmd_join }, | |
114 { "join", "cv", irc_cmd_join }, | |
115 { "kick", "n:", irc_cmd_kick }, | |
8114 | 116 { "list", ":", irc_cmd_list }, |
6333 | 117 { "me", ":", irc_cmd_ctcp_action }, |
118 { "mode", ":", irc_cmd_mode }, | |
119 { "msg", "t:", irc_cmd_privmsg }, | |
120 { "names", "c", irc_cmd_names }, | |
121 { "nick", "n", irc_cmd_nick }, | |
122 { "op", ":", irc_cmd_op }, | |
123 { "operwall", ":", irc_cmd_wallops }, | |
124 { "part", "c:", irc_cmd_part }, | |
125 { "ping", "n", irc_cmd_ping }, | |
126 { "query", "n:", irc_cmd_query }, | |
127 { "quit", ":", irc_cmd_quit }, | |
128 { "quote", "*", irc_cmd_quote }, | |
129 { "remove", "n:", irc_cmd_remove }, | |
130 { "topic", ":", irc_cmd_topic }, | |
131 { "umode", ":", irc_cmd_mode }, | |
132 { "voice", ":", irc_cmd_op }, | |
133 { "wallops", ":", irc_cmd_wallops }, | |
134 { "whois", "n", irc_cmd_whois }, | |
7631 | 135 { NULL, NULL, NULL } |
6333 | 136 }; |
137 | |
138 static char *irc_send_convert(struct irc_conn *irc, const char *string) | |
139 { | |
140 char *utf8; | |
141 GError *err = NULL; | |
142 | |
143 utf8 = g_convert(string, strlen(string), | |
144 gaim_account_get_string(irc->account, "encoding", IRC_DEFAULT_CHARSET), | |
145 "UTF-8", NULL, NULL, &err); | |
146 if (err) { | |
147 gaim_debug(GAIM_DEBUG_ERROR, "irc", "send conversion error: %s\n", err->message); | |
148 gaim_debug(GAIM_DEBUG_ERROR, "irc", "Sending raw, which probably isn't right\n"); | |
149 utf8 = g_strdup(string); | |
150 } | |
151 | |
152 return utf8; | |
153 } | |
154 | |
155 static char *irc_recv_convert(struct irc_conn *irc, const char *string) | |
156 { | |
157 char *utf8; | |
158 GError *err = NULL; | |
159 | |
160 utf8 = g_convert(string, strlen(string), "UTF-8", | |
161 gaim_account_get_string(irc->account, "encoding", IRC_DEFAULT_CHARSET), | |
162 NULL, NULL, &err); | |
163 if (err) { | |
164 gaim_debug(GAIM_DEBUG_ERROR, "irc", "recv conversion error: %s\n", err->message); | |
165 utf8 = g_strdup(_("(There was an error converting this message. Check the 'Encoding' option in the Account Editor)")); | |
166 } | |
167 | |
168 return utf8; | |
169 } | |
170 | |
171 /* XXX tag closings are not necessarily correctly nested here! If we | |
172 * get a ^O or reach the end of the string and there are open | |
173 * tags, they are closed in a fixed order ... this means, for | |
174 * example, you might see <FONT COLOR="blue">some text <B>with | |
175 * various attributes</FONT></B> (notice that B and FONT overlap | |
176 * and are not cleanly nested). This is imminently fixable but | |
177 * I am not fixing it right now. | |
178 */ | |
179 char *irc_mirc2html(const char *string) | |
180 { | |
181 const char *cur, *end; | |
182 char fg[3] = "\0\0", bg[3] = "\0\0"; | |
183 int fgnum, bgnum; | |
6754 | 184 int font = 0, bold = 0, underline = 0; |
6333 | 185 GString *decoded = g_string_sized_new(strlen(string)); |
186 | |
187 cur = string; | |
188 do { | |
6754 | 189 end = strpbrk(cur, "\002\003\007\017\026\037"); |
6333 | 190 |
191 decoded = g_string_append_len(decoded, cur, end ? end - cur : strlen(cur)); | |
192 cur = end ? end : cur + strlen(cur); | |
193 | |
194 switch (*cur) { | |
195 case '\002': | |
196 cur++; | |
197 if (!bold) { | |
198 decoded = g_string_append(decoded, "<B>"); | |
199 bold = TRUE; | |
200 } else { | |
201 decoded = g_string_append(decoded, "</B>"); | |
202 bold = FALSE; | |
203 } | |
204 break; | |
205 case '\003': | |
206 cur++; | |
207 fg[0] = fg[1] = bg[0] = bg[1] = '\0'; | |
208 if (isdigit(*cur)) | |
209 fg[0] = *cur++; | |
210 if (isdigit(*cur)) | |
211 fg[1] = *cur++; | |
212 if (*cur == ',') { | |
213 cur++; | |
214 if (isdigit(*cur)) | |
215 bg[0] = *cur++; | |
216 if (isdigit(*cur)) | |
217 bg[1] = *cur++; | |
218 } | |
219 if (font) { | |
220 decoded = g_string_append(decoded, "</FONT>"); | |
221 font = FALSE; | |
222 } | |
223 | |
224 if (fg[0]) { | |
225 fgnum = atoi(fg); | |
226 if (fgnum < 0 || fgnum > 15) | |
227 continue; | |
228 font = TRUE; | |
229 g_string_append_printf(decoded, "<FONT COLOR=\"%s\"", irc_mirc_colors[fgnum]); | |
230 if (bg[0]) { | |
231 bgnum = atoi(bg); | |
232 if (bgnum >= 0 && bgnum < 16) | |
233 g_string_append_printf(decoded, " BACK=\"%s\"", irc_mirc_colors[bgnum]); | |
234 } | |
235 decoded = g_string_append_c(decoded, '>'); | |
236 } | |
237 break; | |
6754 | 238 case '\037': |
239 cur++; | |
240 if (!underline) { | |
241 decoded = g_string_append(decoded, "<U>"); | |
242 underline = TRUE; | |
243 } else { | |
244 decoded = g_string_append(decoded, "</U>"); | |
245 underline = TRUE; | |
246 } | |
247 break; | |
6333 | 248 case '\007': |
249 case '\026': | |
250 cur++; | |
251 break; | |
252 case '\017': | |
253 cur++; | |
254 /* fallthrough */ | |
255 case '\000': | |
256 if (bold) | |
6754 | 257 decoded = g_string_append(decoded, "</B>"); |
258 if (underline) | |
259 decoded = g_string_append(decoded, "</U>"); | |
6333 | 260 if (font) |
261 decoded = g_string_append(decoded, "</FONT>"); | |
262 break; | |
263 default: | |
264 gaim_debug(GAIM_DEBUG_ERROR, "irc", "Unexpected mIRC formatting character %d\n", *cur); | |
265 } | |
266 } while (*cur); | |
267 | |
268 return g_string_free(decoded, FALSE); | |
269 } | |
270 | |
271 char *irc_parse_ctcp(struct irc_conn *irc, const char *from, const char *to, const char *msg, int notice) | |
272 { | |
273 GaimConnection *gc; | |
274 const char *cur = msg + 1; | |
275 char *buf, *ctcp; | |
276 time_t timestamp; | |
277 | |
6754 | 278 /* Note that this is NOT correct w.r.t. multiple CTCPs in one |
279 * message and low-level quoting ... but if you want that crap, | |
280 * use a real IRC client. */ | |
281 | |
6333 | 282 if (msg[0] != '\001' || msg[strlen(msg) - 1] != '\001') |
283 return g_strdup(msg); | |
284 | |
285 if (!strncmp(cur, "ACTION ", 7)) { | |
286 cur += 7; | |
287 buf = g_strdup_printf("/me %s", cur); | |
288 buf[strlen(buf) - 1] = '\0'; | |
289 return buf; | |
290 } else if (!strncmp(cur, "PING ", 5)) { | |
291 if (notice) { /* reply */ | |
292 sscanf(cur, "PING %lu", ×tamp); | |
293 gc = gaim_account_get_connection(irc->account); | |
294 if (!gc) | |
295 return NULL; | |
6350 | 296 buf = g_strdup_printf(_("Reply time from %s: %lu seconds"), from, time(NULL) - timestamp); |
6333 | 297 gaim_notify_info(gc, _("PONG"), _("CTCP PING reply"), buf); |
298 g_free(buf); | |
299 return NULL; | |
300 } else { | |
301 buf = irc_format(irc, "vt:", "NOTICE", from, msg); | |
302 irc_send(irc, buf); | |
303 g_free(buf); | |
304 gc = gaim_account_get_connection(irc->account); | |
305 } | |
306 } else if (!strncmp(cur, "VERSION", 7) && !notice) { | |
307 buf = irc_format(irc, "vt:", "NOTICE", from, "\001VERSION Gaim IRC\001"); | |
308 irc_send(irc, buf); | |
309 g_free(buf); | |
8351 | 310 } else if (!strncmp(cur, "DCC SEND ", 9)) { |
311 irc_dccsend_recv(irc, from, msg + 10); | |
312 return NULL; | |
6333 | 313 } |
314 | |
315 ctcp = g_strdup(msg + 1); | |
316 ctcp[strlen(ctcp) - 1] = '\0'; | |
317 buf = g_strdup_printf("Received CTCP '%s' (to %s) from %s", ctcp, to, from); | |
318 g_free(ctcp); | |
319 return buf; | |
320 } | |
321 | |
322 void irc_msg_table_build(struct irc_conn *irc) | |
323 { | |
324 int i; | |
325 | |
326 if (!irc || !irc->msgs) { | |
327 gaim_debug(GAIM_DEBUG_ERROR, "irc", "Attempt to build a message table on a bogus structure\n"); | |
328 return; | |
329 } | |
330 | |
331 for (i = 0; _irc_msgs[i].name; i++) { | |
332 g_hash_table_insert(irc->msgs, (gpointer)_irc_msgs[i].name, (gpointer)&_irc_msgs[i]); | |
333 } | |
334 } | |
335 | |
336 void irc_cmd_table_build(struct irc_conn *irc) | |
337 { | |
338 int i; | |
339 | |
340 if (!irc || !irc->cmds) { | |
341 gaim_debug(GAIM_DEBUG_ERROR, "irc", "Attempt to build a command table on a bogus structure\n"); | |
342 return; | |
343 } | |
344 | |
345 for (i = 0; _irc_cmds[i].name ; i++) { | |
346 g_hash_table_insert(irc->cmds, (gpointer)_irc_cmds[i].name, (gpointer)&_irc_cmds[i]); | |
347 } | |
348 } | |
349 | |
350 char *irc_format(struct irc_conn *irc, const char *format, ...) | |
351 { | |
352 GString *string = g_string_new(""); | |
353 char *tok, *tmp; | |
354 const char *cur; | |
355 va_list ap; | |
356 | |
357 va_start(ap, format); | |
358 for (cur = format; *cur; cur++) { | |
359 if (cur != format) | |
360 g_string_append_c(string, ' '); | |
361 | |
362 tok = va_arg(ap, char *); | |
363 switch (*cur) { | |
364 case 'v': | |
365 g_string_append(string, tok); | |
366 break; | |
367 case ':': | |
368 g_string_append_c(string, ':'); | |
369 /* no break! */ | |
370 case 't': | |
371 case 'n': | |
372 case 'c': | |
373 tmp = irc_send_convert(irc, tok); | |
374 g_string_append(string, tmp); | |
375 g_free(tmp); | |
376 break; | |
377 default: | |
378 gaim_debug(GAIM_DEBUG_ERROR, "irc", "Invalid format character '%c'\n", *cur); | |
379 break; | |
380 } | |
381 } | |
382 va_end(ap); | |
383 g_string_append(string, "\r\n"); | |
384 return (g_string_free(string, FALSE)); | |
385 } | |
386 | |
387 void irc_parse_msg(struct irc_conn *irc, char *input) | |
388 { | |
389 struct _irc_msg *msgent; | |
390 char *cur, *end, *tmp, *from, *msgname, *fmt, **args, *msg; | |
7631 | 391 guint i; |
6333 | 392 |
393 if (!strncmp(input, "PING ", 5)) { | |
394 msg = irc_format(irc, "vv", "PONG", input + 5); | |
395 irc_send(irc, msg); | |
396 g_free(msg); | |
397 return; | |
398 } else if (!strncmp(input, "ERROR ", 6)) { | |
7981 | 399 gaim_connection_error(gaim_account_get_connection(irc->account), _("Disconnected.")); |
6333 | 400 return; |
401 } | |
402 | |
403 if (input[0] != ':' || (cur = strchr(input, ' ')) == NULL) { | |
404 irc_parse_error_cb(irc, input); | |
405 return; | |
406 } | |
407 | |
408 from = g_strndup(&input[1], cur - &input[1]); | |
409 cur++; | |
410 end = strchr(cur, ' '); | |
411 if (!end) | |
412 end = cur + strlen(cur); | |
413 | |
414 tmp = g_strndup(cur, end - cur); | |
415 msgname = g_ascii_strdown(tmp, -1); | |
416 g_free(tmp); | |
417 | |
418 if ((msgent = g_hash_table_lookup(irc->msgs, msgname)) == NULL) { | |
419 irc_msg_default(irc, "", from, &input); | |
420 g_free(msgname); | |
421 g_free(from); | |
422 return; | |
423 } | |
424 g_free(msgname); | |
425 | |
426 args = g_new0(char *, strlen(msgent->format)); | |
427 for (cur = end, fmt = msgent->format, i = 0; fmt[i] && *cur++; i++) { | |
428 switch (fmt[i]) { | |
429 case 'v': | |
430 if (!(end = strchr(cur, ' '))) end = cur + strlen(cur); | |
431 args[i] = g_strndup(cur, end - cur); | |
432 cur += end - cur; | |
433 break; | |
434 case 't': | |
435 case 'n': | |
436 case 'c': | |
437 if (!(end = strchr(cur, ' '))) end = cur + strlen(cur); | |
438 tmp = g_strndup(cur, end - cur); | |
439 args[i] = irc_recv_convert(irc, tmp); | |
440 g_free(tmp); | |
441 cur += end - cur; | |
442 break; | |
443 case ':': | |
444 if (*cur == ':') cur++; | |
445 args[i] = irc_recv_convert(irc, cur); | |
446 cur = cur + strlen(cur); | |
447 break; | |
448 case '*': | |
449 args[i] = g_strdup(cur); | |
450 cur = cur + strlen(cur); | |
451 break; | |
452 default: | |
453 gaim_debug(GAIM_DEBUG_ERROR, "irc", "invalid message format character '%c'\n", fmt[i]); | |
454 break; | |
455 } | |
456 } | |
6970 | 457 tmp = irc_recv_convert(irc, from); |
458 (msgent->cb)(irc, msgent->name, tmp, args); | |
459 g_free(tmp); | |
6333 | 460 for (i = 0; i < strlen(msgent->format); i++) { |
461 g_free(args[i]); | |
462 } | |
463 g_free(args); | |
464 g_free(from); | |
465 } | |
466 | |
467 int irc_parse_cmd(struct irc_conn *irc, const char *target, const char *cmdstr) | |
468 { | |
469 const char *cur, *end, *fmt; | |
470 char *tmp, *cmd, **args; | |
471 struct _irc_user_cmd *cmdent; | |
7631 | 472 guint i; |
473 int ret; | |
6333 | 474 |
475 cur = cmdstr; | |
476 end = strchr(cmdstr, ' '); | |
477 if (!end) | |
478 end = cur + strlen(cur); | |
479 | |
480 tmp = g_strndup(cur, end - cur); | |
481 cmd = g_utf8_strdown(tmp, -1); | |
482 g_free(tmp); | |
483 | |
484 if ((cmdent = g_hash_table_lookup(irc->cmds, cmd)) == NULL) { | |
485 ret = irc_cmd_default(irc, cmd, target, &cmdstr); | |
486 g_free(cmd); | |
487 return ret; | |
488 } | |
489 | |
490 args = g_new0(char *, strlen(cmdent->format)); | |
491 for (cur = end, fmt = cmdent->format, i = 0; fmt[i] && *cur++; i++) { | |
492 switch (fmt[i]) { | |
493 case 'v': | |
494 if (!(end = strchr(cur, ' '))) end = cur + strlen(cur); | |
495 args[i] = g_strndup(cur, end - cur); | |
496 cur += end - cur; | |
497 break; | |
498 case 't': | |
499 case 'n': | |
500 case 'c': | |
501 if (!(end = strchr(cur, ' '))) end = cur + strlen(cur); | |
502 args[i] = g_strndup(cur, end - cur); | |
503 cur += end - cur; | |
504 break; | |
505 case ':': | |
506 case '*': | |
507 args[i] = g_strdup(cur); | |
508 cur = cur + strlen(cur); | |
509 break; | |
510 default: | |
511 gaim_debug(GAIM_DEBUG_ERROR, "irc", "invalid command format character '%c'\n", fmt[i]); | |
512 break; | |
513 } | |
514 } | |
515 ret = (cmdent->cb)(irc, cmd, target, (const char **)args); | |
516 for (i = 0; i < strlen(cmdent->format); i++) | |
517 g_free(args[i]); | |
518 g_free(args); | |
519 | |
520 g_free(cmd); | |
521 return ret; | |
522 } | |
523 | |
524 static void irc_parse_error_cb(struct irc_conn *irc, char *input) | |
525 { | |
526 gaim_debug(GAIM_DEBUG_WARNING, "irc", "Unrecognized string: %s\n", input); | |
527 } |