qulogic/pidgin
Clone
Summary
Browse
Changes
Graph
Update a bunch of gettext stuff
8 months ago, Gary Kramlich
c2cb3f628555
Update a bunch of gettext stuff
Add support for a PURPLE_LOCALE_DIR environment variable so we can set the
locale directory in a dev environment.
Added purple_get_locale_dir() which will check the environment variable or
fallback to PURPLE_LOCALE_DIR.
Removed the old PACKAGE and PACKAGE_NAME constants and replaced them with
GETTEXT_PACKAGE which is much more self explanatory.
Testing Done:
Compiled with finch enabled and verified no issues.
Reviewed at https://reviews.imfreedom.org/r/2759/
/* Purple is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#include
<purpleconfig.h>
#include
<glib/gi18n-lib.h>
#include
<ctype.h>
#include
"purpleprivate.h"
#include
"core.h"
#include
"debug.h"
#include
"notify.h"
#include
"prefs.h"
#include
"purpleaccountmanager.h"
#include
"purpleconversation.h"
#include
"purplepath.h"
#include
"purpleprotocol.h"
#include
"purpleprotocolclient.h"
#include
"util.h"
#ifdef _WIN32
#
include
"win32/libc_interface.h"
#endif
#include
<json-glib/json-glib.h>
#define BUF_LEN 2048
void
purple_util_init
(
void
)
{
}
void
purple_util_uninit
(
void
)
{
purple_util_set_user_dir
(
NULL
);
}
/**************************************************************************
* Path/Filename Functions
**************************************************************************/
static
gboolean
purple_util_write_data_to_file_common
(
const
char
*
dir
,
const
char
*
filename
,
const
char
*
data
,
gssize
size
)
{
gchar
*
filename_full
;
gboolean
ret
=
FALSE
;
g_return_val_if_fail
(
dir
!=
NULL
,
FALSE
);
purple_debug_misc
(
"util"
,
"Writing file %s to directory %s"
,
filename
,
dir
);
/* Ensure the directory exists */
if
(
!
g_file_test
(
dir
,
G_FILE_TEST_IS_DIR
))
{
if
(
g_mkdir
(
dir
,
S_IRUSR
|
S_IWUSR
|
S_IXUSR
)
==
-1
)
{
purple_debug_error
(
"util"
,
"Error creating directory %s: %s
\n
"
,
dir
,
g_strerror
(
errno
));
return
FALSE
;
}
}
filename_full
=
g_build_filename
(
dir
,
filename
,
NULL
);
ret
=
g_file_set_contents
(
filename_full
,
data
,
size
,
NULL
);
g_free
(
filename_full
);
return
ret
;
}
gboolean
purple_util_write_data_to_cache_file
(
const
char
*
filename
,
const
char
*
data
,
gssize
size
)
{
const
char
*
cache_dir
=
purple_cache_dir
();
gboolean
ret
=
purple_util_write_data_to_file_common
(
cache_dir
,
filename
,
data
,
size
);
return
ret
;
}
gboolean
purple_util_write_data_to_config_file
(
const
char
*
filename
,
const
char
*
data
,
gssize
size
)
{
const
char
*
config_dir
=
purple_config_dir
();
gboolean
ret
=
purple_util_write_data_to_file_common
(
config_dir
,
filename
,
data
,
size
);
return
ret
;
}
gboolean
purple_util_write_data_to_data_file
(
const
char
*
filename
,
const
char
*
data
,
gssize
size
)
{
const
char
*
data_dir
=
purple_data_dir
();
gboolean
ret
=
purple_util_write_data_to_file_common
(
data_dir
,
filename
,
data
,
size
);
return
ret
;
}
PurpleXmlNode
*
purple_util_read_xml_from_cache_file
(
const
char
*
filename
,
const
char
*
description
)
{
return
purple_xmlnode_from_file
(
purple_cache_dir
(),
filename
,
description
,
"util"
);
}
PurpleXmlNode
*
purple_util_read_xml_from_config_file
(
const
char
*
filename
,
const
char
*
description
)
{
return
purple_xmlnode_from_file
(
purple_config_dir
(),
filename
,
description
,
"util"
);
}
PurpleXmlNode
*
purple_util_read_xml_from_data_file
(
const
char
*
filename
,
const
char
*
description
)
{
return
purple_xmlnode_from_file
(
purple_data_dir
(),
filename
,
description
,
"util"
);
}
gboolean
purple_running_gnome
(
void
)
{
#ifndef _WIN32
gchar
*
tmp
=
g_find_program_in_path
(
"gvfs-open"
);
if
(
tmp
==
NULL
)
{
tmp
=
g_find_program_in_path
(
"gnome-open"
);
if
(
tmp
==
NULL
)
{
return
FALSE
;
}
}
g_free
(
tmp
);
tmp
=
(
gchar
*
)
g_getenv
(
"GNOME_DESKTOP_SESSION_ID"
);
return
((
tmp
!=
NULL
)
&&
(
*
tmp
!=
'\0'
));
#else
return
FALSE
;
#endif
}
gboolean
purple_running_kde
(
void
)
{
#ifndef _WIN32
gchar
*
tmp
=
g_find_program_in_path
(
"kfmclient"
);
const
char
*
session
;
if
(
tmp
==
NULL
)
return
FALSE
;
g_free
(
tmp
);
session
=
g_getenv
(
"KDE_FULL_SESSION"
);
if
(
purple_strequal
(
session
,
"true"
))
return
TRUE
;
/* If you run Purple from Konsole under !KDE, this will provide a
* a false positive. Since we do the GNOME checks first, this is
* only a problem if you're running something !(KDE || GNOME) and
* you run Purple from Konsole. This really shouldn't be a problem. */
return
((
g_getenv
(
"KDEDIR"
)
!=
NULL
)
||
g_getenv
(
"KDEDIRS"
)
!=
NULL
);
#else
return
FALSE
;
#endif
}
/**************************************************************************
* String Functions
**************************************************************************/
const
char
*
purple_normalize
(
PurpleAccount
*
account
,
const
char
*
str
)
{
const
char
*
ret
=
NULL
;
static
char
buf
[
BUF_LEN
];
/* This should prevent a crash if purple_normalize gets called with NULL str, see #10115 */
g_return_val_if_fail
(
str
!=
NULL
,
""
);
if
(
account
!=
NULL
)
{
PurpleProtocol
*
protocol
=
purple_account_get_protocol
(
account
);
if
(
PURPLE_IS_PROTOCOL_CLIENT
(
protocol
))
{
ret
=
purple_protocol_client_normalize
(
PURPLE_PROTOCOL_CLIENT
(
protocol
),
account
,
str
);
}
}
if
(
ret
==
NULL
)
{
char
*
tmp
;
tmp
=
g_utf8_normalize
(
str
,
-1
,
G_NORMALIZE_DEFAULT
);
g_snprintf
(
buf
,
sizeof
(
buf
),
"%s"
,
tmp
);
g_free
(
tmp
);
ret
=
buf
;
}
return
ret
;
}
gboolean
purple_validate
(
PurpleProtocol
*
protocol
,
const
char
*
str
)
{
const
char
*
normalized
;
g_return_val_if_fail
(
protocol
!=
NULL
,
FALSE
);
g_return_val_if_fail
(
str
!=
NULL
,
FALSE
);
if
(
str
[
0
]
==
'\0'
)
return
FALSE
;
if
(
!
PURPLE_PROTOCOL_IMPLEMENTS
(
protocol
,
CLIENT
,
normalize
))
return
TRUE
;
normalized
=
purple_protocol_client_normalize
(
PURPLE_PROTOCOL_CLIENT
(
protocol
),
NULL
,
str
);
return
(
NULL
!=
normalized
);
}
gchar
*
purple_strdup_withhtml
(
const
gchar
*
src
)
{
gulong
destsize
,
i
,
j
;
gchar
*
dest
;
g_return_val_if_fail
(
src
!=
NULL
,
NULL
);
/* New length is (length of src) + (number of \n's * 3) - (number of \r's) + 1 */
destsize
=
1
;
for
(
i
=
0
;
src
[
i
]
!=
'\0'
;
i
++
)
{
if
(
src
[
i
]
==
'\n'
)
destsize
+=
4
;
else
if
(
src
[
i
]
!=
'\r'
)
destsize
++
;
}
dest
=
g_malloc
(
destsize
);
/* Copy stuff, ignoring \r's, because they are dumb */
for
(
i
=
0
,
j
=
0
;
src
[
i
]
!=
'\0'
;
i
++
)
{
if
(
src
[
i
]
==
'\n'
)
{
strcpy
(
&
dest
[
j
],
"<BR>"
);
j
+=
4
;
}
else
if
(
src
[
i
]
!=
'\r'
)
dest
[
j
++
]
=
src
[
i
];
}
dest
[
destsize
-1
]
=
'\0'
;
return
dest
;
}
void
purple_str_strip_char
(
char
*
text
,
char
thechar
)
{
int
i
,
j
;
g_return_if_fail
(
text
!=
NULL
);
for
(
i
=
0
,
j
=
0
;
text
[
i
];
i
++
)
if
(
text
[
i
]
!=
thechar
)
text
[
j
++
]
=
text
[
i
];
text
[
j
]
=
'\0'
;
}
void
purple_util_chrreplace
(
char
*
string
,
char
delimiter
,
char
replacement
)
{
int
i
=
0
;
g_return_if_fail
(
string
!=
NULL
);
while
(
string
[
i
]
!=
'\0'
)
{
if
(
string
[
i
]
==
delimiter
)
string
[
i
]
=
replacement
;
i
++
;
}
}
gchar
*
purple_strreplace
(
const
char
*
string
,
const
char
*
delimiter
,
const
char
*
replacement
)
{
gchar
**
split
;
gchar
*
ret
;
g_return_val_if_fail
(
string
!=
NULL
,
NULL
);
g_return_val_if_fail
(
delimiter
!=
NULL
,
NULL
);
g_return_val_if_fail
(
replacement
!=
NULL
,
NULL
);
split
=
g_strsplit
(
string
,
delimiter
,
0
);
ret
=
g_strjoinv
(
replacement
,
split
);
g_strfreev
(
split
);
return
ret
;
}
char
*
purple_str_seconds_to_string
(
guint
secs
)
{
char
*
ret
=
NULL
;
guint
days
,
hrs
,
mins
;
if
(
secs
<
60
)
{
const
char
*
format
;
format
=
dngettext
(
GETTEXT_PACKAGE
,
"%d second"
,
"%d seconds"
,
secs
);
return
g_strdup_printf
(
format
,
secs
);
}
days
=
secs
/
(
60
*
60
*
24
);
secs
=
secs
%
(
60
*
60
*
24
);
hrs
=
secs
/
(
60
*
60
);
secs
=
secs
%
(
60
*
60
);
mins
=
secs
/
60
;
/* secs = secs % 60; */
if
(
days
>
0
)
{
const
char
*
format
;
format
=
dngettext
(
GETTEXT_PACKAGE
,
"%d day"
,
"%d days"
,
days
);
ret
=
g_strdup_printf
(
format
,
days
);
}
if
(
hrs
>
0
)
{
if
(
ret
!=
NULL
)
{
char
*
tmp
=
NULL
;
const
char
*
format
;
format
=
dngettext
(
GETTEXT_PACKAGE
,
"%s, %d hour"
,
"%s, %d hours"
,
hrs
);
tmp
=
g_strdup_printf
(
format
,
ret
,
hrs
);
g_free
(
ret
);
ret
=
tmp
;
}
else
{
const
char
*
format
;
format
=
dngettext
(
GETTEXT_PACKAGE
,
"%d hour"
,
"%d hours"
,
hrs
);
ret
=
g_strdup_printf
(
format
,
hrs
);
}
}
if
(
mins
>
0
)
{
if
(
ret
!=
NULL
)
{
char
*
tmp
;
const
char
*
format
;
format
=
dngettext
(
GETTEXT_PACKAGE
,
"%s, %d minute"
,
"%s, %d minutes"
,
mins
);
tmp
=
g_strdup_printf
(
format
,
ret
,
mins
);
g_free
(
ret
);
ret
=
tmp
;
}
else
{
const
char
*
format
;
format
=
dngettext
(
GETTEXT_PACKAGE
,
"%d minute"
,
"%d minutes"
,
mins
);
ret
=
g_strdup_printf
(
format
,
mins
);
}
}
return
ret
;
}
void
purple_str_wipe
(
gchar
*
str
)
{
if
(
str
==
NULL
)
return
;
memset
(
str
,
0
,
strlen
(
str
));
g_free
(
str
);
}
gboolean
purple_strmatches
(
const
char
*
pattern
,
const
char
*
str
)
{
char
*
normal_pattern
=
NULL
;
char
*
normal_str
=
NULL
;
char
*
cmp_pattern
=
NULL
;
char
*
cmp_str
=
NULL
;
char
*
idx_pattern
=
NULL
;
char
*
idx_str
=
NULL
;
g_return_val_if_fail
(
pattern
!=
NULL
,
FALSE
);
/* Short circuit on NULL and empty string. */
if
(
purple_strempty
(
str
))
{
return
FALSE
;
}
normal_pattern
=
g_utf8_normalize
(
pattern
,
-1
,
G_NORMALIZE_ALL
);
cmp_pattern
=
g_utf8_casefold
(
normal_pattern
,
-1
);
g_free
(
normal_pattern
);
normal_str
=
g_utf8_normalize
(
str
,
-1
,
G_NORMALIZE_ALL
);
cmp_str
=
g_utf8_casefold
(
normal_str
,
-1
);
g_free
(
normal_str
);
idx_pattern
=
cmp_pattern
;
idx_str
=
cmp_str
;
/* I know while(TRUE)'s suck, but the alternative would be a multi-line for
* loop that wouldn't have the additional comments, which is much better
* IMHO. -- GK 2023-01-24.
*/
while
(
TRUE
)
{
gunichar
character
=
g_utf8_get_char
(
idx_pattern
);
/* If we've reached the end of the pattern, we're done. */
if
(
character
==
0
)
{
break
;
}
idx_str
=
g_utf8_strchr
(
idx_str
,
-1
,
character
);
if
(
idx_str
==
NULL
)
{
g_free
(
cmp_pattern
);
g_free
(
cmp_str
);
return
FALSE
;
}
/* We found the current character in pattern, so move to the next. */
idx_pattern
=
g_utf8_next_char
(
idx_pattern
);
/* move idx_str to the next character as well, because the current
* character has already been matched.
*/
idx_str
=
g_utf8_next_char
(
idx_str
);
};
g_free
(
cmp_pattern
);
g_free
(
cmp_str
);
return
TRUE
;
}
/**************************************************************************
* URI/URL Functions
**************************************************************************/
void
purple_got_protocol_handler_uri
(
const
char
*
uri
)
{
char
proto
[
11
];
char
delimiter
;
const
char
*
tmp
,
*
param_string
;
char
*
cmd
;
GHashTable
*
params
=
NULL
;
gsize
len
;
if
(
!
(
tmp
=
strchr
(
uri
,
':'
))
||
tmp
==
uri
)
{
purple_debug_error
(
"util"
,
"Malformed protocol handler message - missing protocol.
\n
"
);
return
;
}
len
=
MIN
(
sizeof
(
proto
)
-
1
,
(
gsize
)(
tmp
-
uri
));
strncpy
(
proto
,
uri
,
len
);
proto
[
len
]
=
'\0'
;
tmp
++
;
if
(
purple_strequal
(
proto
,
"xmpp"
))
delimiter
=
';'
;
else
delimiter
=
'&'
;
purple_debug_info
(
"util"
,
"Processing message '%s' for protocol '%s' using delimiter '%c'.
\n
"
,
tmp
,
proto
,
delimiter
);
if
((
param_string
=
strchr
(
tmp
,
'?'
)))
{
const
char
*
keyend
=
NULL
,
*
pairstart
;
char
*
key
,
*
value
=
NULL
;
cmd
=
g_strndup
(
tmp
,
(
param_string
-
tmp
));
param_string
++
;
params
=
g_hash_table_new_full
(
g_str_hash
,
g_str_equal
,
g_free
,
g_free
);
pairstart
=
tmp
=
param_string
;
while
(
*
tmp
||
*
pairstart
)
{
if
(
*
tmp
==
delimiter
||
!
(
*
tmp
))
{
/* If there is no explicit value */
if
(
keyend
==
NULL
)
{
keyend
=
tmp
;
}
/* without these brackets, clang won't
* recognize tmp as a non-NULL
*/
if
(
keyend
&&
keyend
!=
pairstart
)
{
char
*
p
;
key
=
g_strndup
(
pairstart
,
(
keyend
-
pairstart
));
/* If there is an explicit value */
if
(
keyend
!=
tmp
&&
keyend
!=
(
tmp
-
1
))
value
=
g_strndup
(
keyend
+
1
,
(
tmp
-
keyend
-
1
));
for
(
p
=
key
;
*
p
;
++
p
)
*
p
=
g_ascii_tolower
(
*
p
);
g_hash_table_insert
(
params
,
key
,
value
);
}
keyend
=
value
=
NULL
;
pairstart
=
(
*
tmp
)
?
tmp
+
1
:
tmp
;
}
else
if
(
*
tmp
==
'='
)
keyend
=
tmp
;
if
(
*
tmp
)
tmp
++
;
}
}
else
cmd
=
g_strdup
(
tmp
);
purple_signal_emit_return_1
(
purple_get_core
(),
"uri-handler"
,
proto
,
cmd
,
params
);
g_free
(
cmd
);
g_clear_pointer
(
&
params
,
g_hash_table_destroy
);
}
const
char
*
purple_url_encode
(
const
char
*
str
)
{
const
char
*
iter
;
static
char
buf
[
BUF_LEN
];
char
utf_char
[
6
];
guint
i
,
j
=
0
;
g_return_val_if_fail
(
str
!=
NULL
,
NULL
);
g_return_val_if_fail
(
g_utf8_validate
(
str
,
-1
,
NULL
),
NULL
);
iter
=
str
;
for
(;
*
iter
&&
j
<
(
BUF_LEN
-
1
)
;
iter
=
g_utf8_next_char
(
iter
))
{
gunichar
c
=
g_utf8_get_char
(
iter
);
/* If the character is an ASCII character and is alphanumeric
* no need to escape */
if
(
c
<
128
&&
(
isalnum
(
c
)
||
c
==
'-'
||
c
==
'.'
||
c
==
'_'
||
c
==
'~'
))
{
buf
[
j
++
]
=
c
;
}
else
{
int
bytes
=
g_unichar_to_utf8
(
c
,
utf_char
);
for
(
i
=
0
;
(
int
)
i
<
bytes
;
i
++
)
{
if
(
j
>
(
BUF_LEN
-
4
))
break
;
if
(
i
>=
sizeof
(
utf_char
))
{
g_warn_if_reached
();
break
;
}
sprintf
(
buf
+
j
,
"%%%02X"
,
utf_char
[
i
]
&
0xff
);
j
+=
3
;
}
}
}
buf
[
j
]
=
'\0'
;
return
buf
;
}
/* Originally lifted from
* http://www.oreillynet.com/pub/a/network/excerpt/spcookbook_chap03/index3.html
* ... and slightly modified to be a bit more rfc822 compliant
* ... and modified a bit more to make domain checking rfc1035 compliant
* with the exception permitted in rfc1101 for domains to start with digit
* but not completely checking to avoid conflicts with IP addresses
*/
gboolean
purple_email_is_valid
(
const
char
*
address
)
{
const
char
*
c
,
*
domain
;
static
char
*
rfc822_specials
=
"()<>@,;:
\\\"
[]"
;
g_return_val_if_fail
(
address
!=
NULL
,
FALSE
);
if
(
*
address
==
'.'
)
return
FALSE
;
/* first we validate the name portion (name@domain) (rfc822)*/
for
(
c
=
address
;
*
c
;
c
++
)
{
if
(
*
c
==
'\"'
&&
(
c
==
address
||
*
(
c
-
1
)
==
'.'
||
*
(
c
-
1
)
==
'\"'
))
{
while
(
*++
c
)
{
if
(
*
c
==
'\\'
)
{
if
(
*
c
++
&&
*
c
<
127
&&
*
c
>
0
&&
*
c
!=
'\n'
&&
*
c
!=
'\r'
)
continue
;
else
return
FALSE
;
}
if
(
*
c
==
'\"'
)
break
;
if
(
*
c
<
' '
||
*
c
>=
127
)
return
FALSE
;
}
if
(
!*
c
++
)
return
FALSE
;
if
(
*
c
==
'@'
)
break
;
if
(
*
c
!=
'.'
)
return
FALSE
;
continue
;
}
if
(
*
c
==
'@'
)
break
;
if
(
*
c
<=
' '
||
*
c
>=
127
)
return
FALSE
;
if
(
strchr
(
rfc822_specials
,
*
c
))
return
FALSE
;
}
/* It's obviously not an email address if we didn't find an '@' above */
if
(
*
c
==
'\0'
)
return
FALSE
;
/* strictly we should return false if (*(c - 1) == '.') too, but I think
* we should permit user.@domain type addresses - they do work :) */
if
(
c
==
address
)
return
FALSE
;
/* next we validate the domain portion (name@domain) (rfc1035 & rfc1011) */
if
(
!*
(
domain
=
++
c
))
return
FALSE
;
do
{
if
(
*
c
==
'.'
&&
(
c
==
domain
||
*
(
c
-
1
)
==
'.'
||
*
(
c
-
1
)
==
'-'
))
return
FALSE
;
if
(
*
c
==
'-'
&&
(
*
(
c
-
1
)
==
'.'
||
*
(
c
-
1
)
==
'@'
))
return
FALSE
;
if
((
*
c
<
'0'
&&
*
c
!=
'-'
&&
*
c
!=
'.'
)
||
(
*
c
>
'9'
&&
*
c
<
'A'
)
||
(
*
c
>
'Z'
&&
*
c
<
'a'
)
||
(
*
c
>
'z'
))
return
FALSE
;
}
while
(
*++
c
);
if
(
*
(
c
-
1
)
==
'-'
)
return
FALSE
;
return
((
c
-
domain
)
>
3
?
TRUE
:
FALSE
);
}
/**************************************************************************
* UTF8 String Functions
**************************************************************************/
gchar
*
purple_utf8_try_convert
(
const
char
*
str
)
{
gsize
converted
;
gchar
*
utf8
;
g_return_val_if_fail
(
str
!=
NULL
,
NULL
);
if
(
g_utf8_validate
(
str
,
-1
,
NULL
))
{
return
g_strdup
(
str
);
}
utf8
=
g_locale_to_utf8
(
str
,
-1
,
&
converted
,
NULL
,
NULL
);
if
(
utf8
!=
NULL
)
return
utf8
;
utf8
=
g_convert
(
str
,
-1
,
"UTF-8"
,
"ISO-8859-15"
,
&
converted
,
NULL
,
NULL
);
if
((
utf8
!=
NULL
)
&&
(
converted
==
strlen
(
str
)))
return
utf8
;
g_free
(
utf8
);
return
NULL
;
}
gchar
*
purple_utf8_strip_unprintables
(
const
gchar
*
str
)
{
gchar
*
workstr
,
*
iter
;
const
gchar
*
bad
;
if
(
str
==
NULL
)
/* Act like g_strdup */
return
NULL
;
if
(
!
g_utf8_validate
(
str
,
-1
,
&
bad
))
{
purple_debug_error
(
"util"
,
"purple_utf8_strip_unprintables(%s) failed; "
"first bad character was %02x (%c)
\n
"
,
str
,
*
bad
,
*
bad
);
g_return_val_if_reached
(
NULL
);
}
workstr
=
iter
=
g_new
(
gchar
,
strlen
(
str
)
+
1
);
while
(
*
str
)
{
gunichar
ch
=
g_utf8_get_char
(
str
);
gchar
*
next
=
g_utf8_next_char
(
str
);
/*
* Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] |
* [#x10000-#x10FFFF]
*/
if
((
ch
==
'\t'
||
ch
==
'\n'
||
ch
==
'\r'
)
||
(
ch
>=
0x20
&&
ch
<=
0xD7FF
)
||
(
ch
>=
0xE000
&&
ch
<=
0xFFFD
)
||
(
ch
>=
0x10000
&&
ch
<=
0x10FFFF
))
{
memcpy
(
iter
,
str
,
next
-
str
);
iter
+=
(
next
-
str
);
}
str
=
next
;
}
/* nul-terminate the new string */
*
iter
=
'\0'
;
return
workstr
;
}
int
purple_utf8_strcasecmp
(
const
char
*
a
,
const
char
*
b
)
{
char
*
a_norm
=
NULL
;
char
*
b_norm
=
NULL
;
int
ret
=
-1
;
if
(
!
a
&&
b
)
return
-1
;
else
if
(
!
b
&&
a
)
return
1
;
else
if
(
!
a
&&
!
b
)
return
0
;
if
(
!
g_utf8_validate
(
a
,
-1
,
NULL
)
||
!
g_utf8_validate
(
b
,
-1
,
NULL
))
{
purple_debug_error
(
"purple_utf8_strcasecmp"
,
"One or both parameters are invalid UTF8
\n
"
);
return
ret
;
}
a_norm
=
g_utf8_casefold
(
a
,
-1
);
b_norm
=
g_utf8_casefold
(
b
,
-1
);
ret
=
g_utf8_collate
(
a_norm
,
b_norm
);
g_free
(
a_norm
);
g_free
(
b_norm
);
return
ret
;
}
/* previously conversation::find_nick() */
gboolean
purple_utf8_has_word
(
const
char
*
haystack
,
const
char
*
needle
)
{
char
*
hay
,
*
pin
,
*
p
;
const
char
*
start
,
*
prev_char
;
gunichar
before
,
after
;
int
n
;
gboolean
ret
=
FALSE
;
start
=
hay
=
g_utf8_strdown
(
haystack
,
-1
);
pin
=
g_utf8_strdown
(
needle
,
-1
);
n
=
strlen
(
pin
);
while
((
p
=
strstr
(
start
,
pin
))
!=
NULL
)
{
prev_char
=
g_utf8_find_prev_char
(
hay
,
p
);
before
=
-2
;
if
(
prev_char
)
{
before
=
g_utf8_get_char
(
prev_char
);
}
after
=
g_utf8_get_char_validated
(
p
+
n
,
-
1
);
if
((
p
==
hay
||
/* The character before is a reasonable guess for a word boundary
("!g_unichar_isalnum()" is not a valid way to determine word
boundaries, but it is the only reasonable thing to do here),
and isn't the '&' from a "&" or some such entity*/
(
before
!=
(
gunichar
)
-2
&&
!
g_unichar_isalnum
(
before
)
&&
*
(
p
-
1
)
!=
'&'
))
&&
after
!=
(
gunichar
)
-2
&&
!
g_unichar_isalnum
(
after
))
{
ret
=
TRUE
;
break
;
}
start
=
p
+
1
;
}
g_free
(
pin
);
g_free
(
hay
);
return
ret
;
}
char
*
purple_text_strip_mnemonic
(
const
char
*
in
)
{
char
*
out
;
char
*
a
;
char
*
a0
;
const
char
*
b
;
g_return_val_if_fail
(
in
!=
NULL
,
NULL
);
out
=
g_malloc
(
strlen
(
in
)
+
1
);
a
=
out
;
b
=
in
;
a0
=
a
;
/* The last non-space char seen so far, or the first char */
while
(
*
b
)
{
if
(
*
b
==
'_'
)
{
if
(
a
>
out
&&
b
>
in
&&
*
(
b
-1
)
==
'('
&&
*
(
b
+
1
)
&&
!
(
*
(
b
+
1
)
&
0x80
)
&&
*
(
b
+
2
)
==
')'
)
{
/* Detected CJK style shortcut (Bug 875311) */
a
=
a0
;
/* undo the left parenthesis */
b
+=
3
;
/* and skip the whole mess */
}
else
if
(
*
(
b
+
1
)
==
'_'
)
{
*
(
a
++
)
=
'_'
;
b
+=
2
;
a0
=
a
;
}
else
{
b
++
;
}
/* We don't want to corrupt the middle of UTF-8 characters */
}
else
if
(
!
(
*
b
&
0x80
))
{
/* other 1-byte char */
if
(
*
b
!=
' '
)
a0
=
a
;
*
(
a
++
)
=
*
(
b
++
);
}
else
{
/* Multibyte utf8 char, don't look for _ inside these */
int
n
=
0
;
int
i
;
if
((
*
b
&
0xe0
)
==
0xc0
)
{
n
=
2
;
}
else
if
((
*
b
&
0xf0
)
==
0xe0
)
{
n
=
3
;
}
else
if
((
*
b
&
0xf8
)
==
0xf0
)
{
n
=
4
;
}
else
if
((
*
b
&
0xfc
)
==
0xf8
)
{
n
=
5
;
}
else
if
((
*
b
&
0xfe
)
==
0xfc
)
{
n
=
6
;
}
else
{
/* Illegal utf8 */
n
=
1
;
}
a0
=
a
;
/* unless we want to delete CJK spaces too */
for
(
i
=
0
;
i
<
n
&&
*
b
;
i
+=
1
)
{
*
(
a
++
)
=
*
(
b
++
);
}
}
}
*
a
=
'\0'
;
return
out
;
}
/* this is almost identical to purple_url_encode (hence purple_url_decode
* being used above), but we want to keep certain characters unescaped
* for compat reasons */
const
char
*
purple_escape_filename
(
const
char
*
str
)
{
const
char
*
iter
;
static
char
buf
[
BUF_LEN
];
char
utf_char
[
6
];
guint
i
,
j
=
0
;
g_return_val_if_fail
(
str
!=
NULL
,
NULL
);
g_return_val_if_fail
(
g_utf8_validate
(
str
,
-1
,
NULL
),
NULL
);
iter
=
str
;
for
(;
*
iter
&&
j
<
(
BUF_LEN
-
1
)
;
iter
=
g_utf8_next_char
(
iter
))
{
gunichar
c
=
g_utf8_get_char
(
iter
);
/* If the character is an ASCII character and is alphanumeric,
* or one of the specified values, no need to escape */
if
(
c
<
128
&&
(
g_ascii_isalnum
(
c
)
||
c
==
'@'
||
c
==
'-'
||
c
==
'_'
||
c
==
'.'
||
c
==
'#'
))
{
buf
[
j
++
]
=
c
;
}
else
{
int
bytes
=
g_unichar_to_utf8
(
c
,
utf_char
);
for
(
i
=
0
;
(
int
)
i
<
bytes
;
i
++
)
{
if
(
j
>
(
BUF_LEN
-
4
))
break
;
if
(
i
>=
sizeof
(
utf_char
))
{
g_warn_if_reached
();
break
;
}
sprintf
(
buf
+
j
,
"%%%02x"
,
utf_char
[
i
]
&
0xff
);
j
+=
3
;
}
}
}
#ifdef _WIN32
/* File/Directory names in windows cannot end in periods/spaces.
* http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx
*/
while
(
j
>
0
&&
(
buf
[
j
-
1
]
==
'.'
||
buf
[
j
-
1
]
==
' '
))
j
--
;
#endif
buf
[
j
]
=
'\0'
;
return
buf
;
}
GValue
*
purple_value_new
(
GType
type
)
{
GValue
*
ret
;
g_return_val_if_fail
(
type
!=
G_TYPE_NONE
,
NULL
);
ret
=
g_new0
(
GValue
,
1
);
g_value_init
(
ret
,
type
);
return
ret
;
}
GValue
*
purple_value_dup
(
GValue
*
value
)
{
GValue
*
ret
;
g_return_val_if_fail
(
value
!=
NULL
,
NULL
);
ret
=
g_new0
(
GValue
,
1
);
g_value_init
(
ret
,
G_VALUE_TYPE
(
value
));
g_value_copy
(
value
,
ret
);
return
ret
;
}
void
purple_value_free
(
GValue
*
value
)
{
g_return_if_fail
(
value
!=
NULL
);
g_value_unset
(
value
);
g_free
(
value
);
}