mirror of
https://github.com/samba-team/samba.git
synced 2024-12-23 17:34:34 +03:00
e5a951325a
(This used to be commit 5c6c8e1fe9
)
602 lines
16 KiB
C
602 lines
16 KiB
C
/*
|
|
Unix SMB/CIFS implementation.
|
|
Samba charset module for Mac OS X/Darwin
|
|
Copyright (C) Benjamin Riefenstahl 2003
|
|
|
|
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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/*
|
|
* modules/charset_macosxfs.c
|
|
*
|
|
* A Samba charset module to use on Mac OS X/Darwin as the filesystem
|
|
* and display encoding.
|
|
*
|
|
* Actually two implementations are provided here. The default
|
|
* implementation is based on the official CFString API. The other is
|
|
* based on internal CFString APIs as defined in the OpenDarwin
|
|
* source.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
|
|
/*
|
|
* Include OS frameworks. These are only needed in this module.
|
|
*/
|
|
#include <CoreFoundation/CFString.h>
|
|
|
|
/*
|
|
* See if autoconf has found us the internal headers in some form.
|
|
*/
|
|
#if HAVE_COREFOUNDATION_CFSTRINGENCODINGCONVERTER_H
|
|
# include <CoreFoundation/CFStringEncodingConverter.h>
|
|
# include <CoreFoundation/CFUnicodePrecomposition.h>
|
|
# define USE_INTERNAL_API 1
|
|
#elif HAVE_CFSTRINGENCODINGCONVERTER_H
|
|
# include <CFStringEncodingConverter.h>
|
|
# include <CFUnicodePrecomposition.h>
|
|
# define USE_INTERNAL_API 1
|
|
#endif
|
|
|
|
/*
|
|
* Compile time configuration: Do we want debug output?
|
|
*/
|
|
/* #define DEBUG_STRINGS 1 */
|
|
|
|
/*
|
|
* A simple, but efficient memory provider for our buffers.
|
|
*/
|
|
static inline void *resize_buffer (void *buffer, size_t *size, size_t newsize)
|
|
{
|
|
if (newsize > *size) {
|
|
*size = newsize + 128;
|
|
buffer = SMB_REALLOC(buffer, *size);
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
/*
|
|
* While there is a version of OpenDarwin for intel, the usual case is
|
|
* big-endian PPC. So we need byte swapping to handle the
|
|
* little-endian byte order of the network protocol. We also need an
|
|
* additional dynamic buffer to do this work for incoming data blocks,
|
|
* because we have to consider the original data as constant.
|
|
*
|
|
* We abstract the differences away by providing a simple facade with
|
|
* these functions/macros:
|
|
*
|
|
* le_to_native(dst,src,len)
|
|
* native_to_le(cp,len)
|
|
* set_ucbuffer_with_le(buffer,bufsize,data,size)
|
|
* set_ucbuffer_with_le_copy(buffer,bufsize,data,size,reserve)
|
|
*/
|
|
#ifdef WORDS_BIGENDIAN
|
|
|
|
static inline void swap_bytes (char * dst, const char * src, size_t len)
|
|
{
|
|
const char *srcend = src + len;
|
|
while (src < srcend) {
|
|
dst[0] = src[1];
|
|
dst[1] = src[0];
|
|
dst += 2;
|
|
src += 2;
|
|
}
|
|
}
|
|
static inline void swap_bytes_inplace (char * cp, size_t len)
|
|
{
|
|
char temp;
|
|
char *end = cp + len;
|
|
while (cp < end) {
|
|
temp = cp[1];
|
|
cp[1] = cp[0];
|
|
cp[0] = temp;
|
|
cp += 2;
|
|
}
|
|
}
|
|
|
|
#define le_to_native(dst,src,len) swap_bytes(dst,src,len)
|
|
#define native_to_le(cp,len) swap_bytes_inplace(cp,len)
|
|
#define set_ucbuffer_with_le(buffer,bufsize,data,size) \
|
|
set_ucbuffer_with_le_copy(buffer,bufsize,data,size,0)
|
|
|
|
#else /* ! WORDS_BIGENDIAN */
|
|
|
|
#define le_to_native(dst,src,len) memcpy(dst,src,len)
|
|
#define native_to_le(cp,len) /* nothing */
|
|
#define set_ucbuffer_with_le(buffer,bufsize,data,size) \
|
|
(((void)(bufsize)),(UniChar*)(data))
|
|
|
|
#endif
|
|
|
|
static inline UniChar *set_ucbuffer_with_le_copy (
|
|
UniChar *buffer, size_t *bufsize,
|
|
const void *data, size_t size, size_t reserve)
|
|
{
|
|
buffer = resize_buffer(buffer, bufsize, size+reserve);
|
|
le_to_native((char*)buffer,data,size);
|
|
return buffer;
|
|
}
|
|
|
|
|
|
/*
|
|
* A simple hexdump function for debugging error conditions.
|
|
*/
|
|
#define debug_out(s) DEBUG(0,(s))
|
|
|
|
#ifdef DEBUG_STRINGS
|
|
|
|
static void hexdump( const char * label, const char * s, size_t len )
|
|
{
|
|
size_t restlen = len;
|
|
debug_out("<<<<<<<\n");
|
|
debug_out(label);
|
|
debug_out("\n");
|
|
while (restlen > 0) {
|
|
char line[100];
|
|
size_t i, j;
|
|
char * d = line;
|
|
#undef sprintf
|
|
d += sprintf(d, "%04X ", (unsigned)(len-restlen));
|
|
*d++ = ' ';
|
|
for( i = 0; i<restlen && i<8; ++i ) {
|
|
d += sprintf(d, "%02X ", ((unsigned)s[i]) & 0xFF);
|
|
}
|
|
for( j = i; j<8; ++j ) {
|
|
d += sprintf(d, " ");
|
|
}
|
|
*d++ = ' ';
|
|
for( i = 8; i<restlen && i<16; ++i ) {
|
|
d += sprintf(d, "%02X ", ((unsigned)s[i]) & 0xFF);
|
|
}
|
|
for( j = i; j<16; ++j ) {
|
|
d += sprintf(d, " ");
|
|
}
|
|
*d++ = ' ';
|
|
for( i = 0; i<restlen && i<16; ++i ) {
|
|
if(s[i] < ' ' || s[i] >= 0x7F || !isprint(s[i]))
|
|
*d++ = '.';
|
|
else
|
|
*d++ = s[i];
|
|
}
|
|
*d++ = '\n';
|
|
*d = 0;
|
|
restlen -= i;
|
|
s += i;
|
|
debug_out(line);
|
|
}
|
|
debug_out(">>>>>>>\n");
|
|
}
|
|
|
|
#else /* !DEBUG_STRINGS */
|
|
|
|
#define hexdump(label,s,len) /* nothing */
|
|
|
|
#endif
|
|
|
|
|
|
#if !USE_INTERNAL_API
|
|
|
|
/*
|
|
* An implementation based on documented Mac OS X APIs.
|
|
*
|
|
* This does a certain amount of memory management, creating and
|
|
* manipulating CFString objects. We try to minimize the impact by
|
|
* keeping those objects around and re-using them. We also use
|
|
* external backing store for the CFStrings where this is possible and
|
|
* benficial.
|
|
*
|
|
* The Unicode normalizations forms available at this level are
|
|
* generic, not specifically for the file system. So they may not be
|
|
* perfect fits.
|
|
*/
|
|
static size_t macosxfs_encoding_pull(
|
|
void *cd, /* Encoder handle */
|
|
char **inbuf, size_t *inbytesleft, /* Script string */
|
|
char **outbuf, size_t *outbytesleft) /* UTF-16-LE string */
|
|
{
|
|
static const int script_code = kCFStringEncodingUTF8;
|
|
static CFMutableStringRef cfstring = NULL;
|
|
size_t outsize;
|
|
CFRange range;
|
|
|
|
(void) cd; /* UNUSED */
|
|
|
|
if (0 == *inbytesleft) {
|
|
return 0;
|
|
}
|
|
|
|
if (NULL == cfstring) {
|
|
/*
|
|
* A version with an external backing store as in the
|
|
* push function should have been more efficient, but
|
|
* testing shows, that it is actually slower (!).
|
|
* Maybe kCFAllocatorDefault gets shortcut evaluation
|
|
* internally, while kCFAllocatorNull doesn't.
|
|
*/
|
|
cfstring = CFStringCreateMutable(kCFAllocatorDefault,0);
|
|
}
|
|
|
|
/*
|
|
* Three methods of appending to a CFString, choose the most
|
|
* efficient.
|
|
*/
|
|
if (0 == (*inbuf)[*inbytesleft-1]) {
|
|
CFStringAppendCString(cfstring, *inbuf, script_code);
|
|
} else if (*inbytesleft <= 255) {
|
|
Str255 buffer;
|
|
buffer[0] = *inbytesleft;
|
|
memcpy(buffer+1, *inbuf, buffer[0]);
|
|
CFStringAppendPascalString(cfstring, buffer, script_code);
|
|
} else {
|
|
/*
|
|
* We would like to use a fixed buffer and a loop
|
|
* here, but than we can't garantee that the input is
|
|
* well-formed UTF-8, as we are supposed to do.
|
|
*/
|
|
static char *buffer = NULL;
|
|
static size_t buflen = 0;
|
|
buffer = resize_buffer(buffer, &buflen, *inbytesleft+1);
|
|
memcpy(buffer, *inbuf, *inbytesleft);
|
|
buffer[*inbytesleft] = 0;
|
|
CFStringAppendCString(cfstring, *inbuf, script_code);
|
|
}
|
|
|
|
/*
|
|
* Compose characters, using the non-canonical composition
|
|
* form.
|
|
*/
|
|
CFStringNormalize(cfstring, kCFStringNormalizationFormC);
|
|
|
|
outsize = CFStringGetLength(cfstring);
|
|
range = CFRangeMake(0,outsize);
|
|
|
|
if (outsize == 0) {
|
|
/*
|
|
* HACK: smbd/mangle_hash2.c:is_legal_name() expects
|
|
* errors here. That function will always pass 2
|
|
* characters. smbd/open.c:check_for_pipe() cuts a
|
|
* patchname to 10 characters blindly. Suppress the
|
|
* debug output in those cases.
|
|
*/
|
|
if(2 != *inbytesleft && 10 != *inbytesleft) {
|
|
debug_out("String conversion: "
|
|
"An unknown error occurred\n");
|
|
hexdump("UTF8->UTF16LE (old) input",
|
|
*inbuf, *inbytesleft);
|
|
}
|
|
errno = EILSEQ; /* Not sure, but this is what we have
|
|
* actually seen. */
|
|
return -1;
|
|
}
|
|
if (outsize*2 > *outbytesleft) {
|
|
CFStringDelete(cfstring, range);
|
|
debug_out("String conversion: "
|
|
"Output buffer too small\n");
|
|
hexdump("UTF8->UTF16LE (old) input",
|
|
*inbuf, *inbytesleft);
|
|
errno = E2BIG;
|
|
return -1;
|
|
}
|
|
|
|
CFStringGetCharacters(cfstring, range, (UniChar*)*outbuf);
|
|
CFStringDelete(cfstring, range);
|
|
|
|
native_to_le(*outbuf, outsize*2);
|
|
|
|
/*
|
|
* Add a converted null byte, if the CFString conversions
|
|
* prevented that until now.
|
|
*/
|
|
if (0 == (*inbuf)[*inbytesleft-1] &&
|
|
(0 != (*outbuf)[outsize*2-1] || 0 != (*outbuf)[outsize*2-2])) {
|
|
|
|
if ((outsize*2+2) > *outbytesleft) {
|
|
debug_out("String conversion: "
|
|
"Output buffer too small\n");
|
|
hexdump("UTF8->UTF16LE (old) input",
|
|
*inbuf, *inbytesleft);
|
|
errno = E2BIG;
|
|
return -1;
|
|
}
|
|
|
|
(*outbuf)[outsize*2] = (*outbuf)[outsize*2+1] = 0;
|
|
outsize += 2;
|
|
}
|
|
|
|
*inbuf += *inbytesleft;
|
|
*inbytesleft = 0;
|
|
*outbuf += outsize*2;
|
|
*outbytesleft -= outsize*2;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static size_t macosxfs_encoding_push(
|
|
void *cd, /* Encoder handle */
|
|
char **inbuf, size_t *inbytesleft, /* UTF-16-LE string */
|
|
char **outbuf, size_t *outbytesleft) /* Script string */
|
|
{
|
|
static const int script_code = kCFStringEncodingUTF8;
|
|
static CFMutableStringRef cfstring = NULL;
|
|
static UniChar *buffer = NULL;
|
|
static size_t buflen = 0;
|
|
CFIndex outsize, cfsize, charsconverted;
|
|
|
|
(void) cd; /* UNUSED */
|
|
|
|
if (0 == *inbytesleft) {
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* We need a buffer that can hold 4 times the original data,
|
|
* because that is the theoretical maximum that decomposition
|
|
* can create currently (in Unicode 4.0).
|
|
*/
|
|
buffer = set_ucbuffer_with_le_copy(
|
|
buffer, &buflen, *inbuf, *inbytesleft, 3 * *inbytesleft);
|
|
|
|
if (NULL == cfstring) {
|
|
cfstring = CFStringCreateMutableWithExternalCharactersNoCopy(
|
|
kCFAllocatorDefault,
|
|
buffer, *inbytesleft/2, buflen/2,
|
|
kCFAllocatorNull);
|
|
} else {
|
|
CFStringSetExternalCharactersNoCopy(
|
|
cfstring,
|
|
buffer, *inbytesleft/2, buflen/2);
|
|
}
|
|
|
|
/*
|
|
* Decompose characters, using the non-canonical decomposition
|
|
* form.
|
|
*
|
|
* NB: This isn't exactly what HFS+ wants (see note on
|
|
* kCFStringEncodingUseHFSPlusCanonical in
|
|
* CFStringEncodingConverter.h), but AFAIK it's the best that
|
|
* the official API can do.
|
|
*/
|
|
CFStringNormalize(cfstring, kCFStringNormalizationFormD);
|
|
|
|
cfsize = CFStringGetLength(cfstring);
|
|
charsconverted = CFStringGetBytes(
|
|
cfstring, CFRangeMake(0,cfsize),
|
|
script_code, 0, False,
|
|
*outbuf, *outbytesleft, &outsize);
|
|
|
|
if (0 == charsconverted) {
|
|
debug_out("String conversion: "
|
|
"Buffer too small or not convertable\n");
|
|
hexdump("UTF16LE->UTF8 (old) input",
|
|
*inbuf, *inbytesleft);
|
|
errno = EILSEQ; /* Probably more likely. */
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Add a converted null byte, if the CFString conversions
|
|
* prevented that until now.
|
|
*/
|
|
if (0 == (*inbuf)[*inbytesleft-1] && 0 == (*inbuf)[*inbytesleft-2] &&
|
|
(0 != (*outbuf)[outsize-1])) {
|
|
|
|
if (((size_t)outsize+1) > *outbytesleft) {
|
|
debug_out("String conversion: "
|
|
"Output buffer too small\n");
|
|
hexdump("UTF16LE->UTF8 (old) input",
|
|
*inbuf, *inbytesleft);
|
|
errno = E2BIG;
|
|
return -1;
|
|
}
|
|
|
|
(*outbuf)[outsize] = 0;
|
|
++outsize;
|
|
}
|
|
|
|
*inbuf += *inbytesleft;
|
|
*inbytesleft = 0;
|
|
*outbuf += outsize;
|
|
*outbytesleft -= outsize;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#else /* USE_INTERNAL_API */
|
|
|
|
/*
|
|
* An implementation based on internal code as known from the
|
|
* OpenDarwin CVS.
|
|
*
|
|
* This code doesn't need much memory management because it uses
|
|
* functions that operate on the raw memory directly.
|
|
*
|
|
* The push routine here is faster and more compatible with HFS+ than
|
|
* the other implementation above. The pull routine is only faster
|
|
* for some strings, slightly slower for others. The pull routine
|
|
* looses because it has to iterate over the data twice, once to
|
|
* decode UTF-8 and than to do the character composition required by
|
|
* Windows.
|
|
*/
|
|
static size_t macosxfs_encoding_pull(
|
|
void *cd, /* Encoder handle */
|
|
char **inbuf, size_t *inbytesleft, /* Script string */
|
|
char **outbuf, size_t *outbytesleft) /* UTF-16-LE string */
|
|
{
|
|
static const int script_code = kCFStringEncodingUTF8;
|
|
UInt32 srcCharsUsed = 0;
|
|
UInt32 dstCharsUsed = 0;
|
|
UInt32 result;
|
|
uint32_t dstDecomposedUsed = 0;
|
|
uint32_t dstPrecomposedUsed = 0;
|
|
|
|
(void) cd; /* UNUSED */
|
|
|
|
if (0 == *inbytesleft) {
|
|
return 0;
|
|
}
|
|
|
|
result = CFStringEncodingBytesToUnicode(
|
|
script_code, kCFStringEncodingComposeCombinings,
|
|
*inbuf, *inbytesleft, &srcCharsUsed,
|
|
(UniChar*)*outbuf, *outbytesleft, &dstCharsUsed);
|
|
|
|
switch(result) {
|
|
case kCFStringEncodingConversionSuccess:
|
|
if (*inbytesleft == srcCharsUsed)
|
|
break;
|
|
else
|
|
; /*fall through*/
|
|
case kCFStringEncodingInsufficientOutputBufferLength:
|
|
debug_out("String conversion: "
|
|
"Output buffer too small\n");
|
|
hexdump("UTF8->UTF16LE (new) input",
|
|
*inbuf, *inbytesleft);
|
|
errno = E2BIG;
|
|
return -1;
|
|
case kCFStringEncodingInvalidInputStream:
|
|
/*
|
|
* HACK: smbd/mangle_hash2.c:is_legal_name() expects
|
|
* errors here. That function will always pass 2
|
|
* characters. smbd/open.c:check_for_pipe() cuts a
|
|
* patchname to 10 characters blindly. Suppress the
|
|
* debug output in those cases.
|
|
*/
|
|
if(2 != *inbytesleft && 10 != *inbytesleft) {
|
|
debug_out("String conversion: "
|
|
"Invalid input sequence\n");
|
|
hexdump("UTF8->UTF16LE (new) input",
|
|
*inbuf, *inbytesleft);
|
|
}
|
|
errno = EILSEQ;
|
|
return -1;
|
|
case kCFStringEncodingConverterUnavailable:
|
|
debug_out("String conversion: "
|
|
"Unknown encoding\n");
|
|
hexdump("UTF8->UTF16LE (new) input",
|
|
*inbuf, *inbytesleft);
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* It doesn't look like CFStringEncodingBytesToUnicode() can
|
|
* produce precomposed characters (flags=ComposeCombinings
|
|
* doesn't do it), so we need another pass over the data here.
|
|
* We can do this in-place, as the string can only get
|
|
* shorter.
|
|
*
|
|
* (Actually in theory there should be an internal
|
|
* decomposition and reordering before the actual composition
|
|
* step. But we should be able to rely on that we always get
|
|
* fully decomposed strings for input, so this can't create
|
|
* problems in reality.)
|
|
*/
|
|
CFUniCharPrecompose(
|
|
(const UTF16Char *)*outbuf, dstCharsUsed, &dstDecomposedUsed,
|
|
(UTF16Char *)*outbuf, dstCharsUsed, &dstPrecomposedUsed);
|
|
|
|
native_to_le(*outbuf, dstPrecomposedUsed*2);
|
|
|
|
*inbuf += srcCharsUsed;
|
|
*inbytesleft -= srcCharsUsed;
|
|
*outbuf += dstPrecomposedUsed*2;
|
|
*outbytesleft -= dstPrecomposedUsed*2;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static size_t macosxfs_encoding_push(
|
|
void *cd, /* Encoder handle */
|
|
char **inbuf, size_t *inbytesleft, /* UTF-16-LE string */
|
|
char **outbuf, size_t *outbytesleft) /* Script string */
|
|
{
|
|
static const int script_code = kCFStringEncodingUTF8;
|
|
static UniChar *buffer = NULL;
|
|
static size_t buflen = 0;
|
|
UInt32 srcCharsUsed=0, dstCharsUsed=0, result;
|
|
|
|
(void) cd; /* UNUSED */
|
|
|
|
if (0 == *inbytesleft) {
|
|
return 0;
|
|
}
|
|
|
|
buffer = set_ucbuffer_with_le(
|
|
buffer, &buflen, *inbuf, *inbytesleft);
|
|
|
|
result = CFStringEncodingUnicodeToBytes(
|
|
script_code, kCFStringEncodingUseHFSPlusCanonical,
|
|
buffer, *inbytesleft/2, &srcCharsUsed,
|
|
*outbuf, *outbytesleft, &dstCharsUsed);
|
|
|
|
switch(result) {
|
|
case kCFStringEncodingConversionSuccess:
|
|
if (*inbytesleft/2 == srcCharsUsed)
|
|
break;
|
|
else
|
|
; /*fall through*/
|
|
case kCFStringEncodingInsufficientOutputBufferLength:
|
|
debug_out("String conversion: "
|
|
"Output buffer too small\n");
|
|
hexdump("UTF16LE->UTF8 (new) input",
|
|
*inbuf, *inbytesleft);
|
|
errno = E2BIG;
|
|
return -1;
|
|
case kCFStringEncodingInvalidInputStream:
|
|
/*
|
|
* HACK: smbd/open.c:check_for_pipe():is_legal_name()
|
|
* cuts a pathname to 10 characters blindly. Suppress
|
|
* the debug output in those cases.
|
|
*/
|
|
if(10 != *inbytesleft) {
|
|
debug_out("String conversion: "
|
|
"Invalid input sequence\n");
|
|
hexdump("UTF16LE->UTF8 (new) input",
|
|
*inbuf, *inbytesleft);
|
|
}
|
|
errno = EILSEQ;
|
|
return -1;
|
|
case kCFStringEncodingConverterUnavailable:
|
|
debug_out("String conversion: "
|
|
"Unknown encoding\n");
|
|
hexdump("UTF16LE->UTF8 (new) input",
|
|
*inbuf, *inbytesleft);
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
*inbuf += srcCharsUsed*2;
|
|
*inbytesleft -= srcCharsUsed*2;
|
|
*outbuf += dstCharsUsed;
|
|
*outbytesleft -= dstCharsUsed;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /* USE_INTERNAL_API */
|
|
|
|
/*
|
|
* For initialization, actually install the encoding as "macosxfs".
|
|
*/
|
|
static struct charset_functions macosxfs_encoding_functions = {
|
|
"MACOSXFS", macosxfs_encoding_pull, macosxfs_encoding_push
|
|
};
|
|
|
|
NTSTATUS charset_macosxfs_init(void)
|
|
{
|
|
return smb_register_charset(&macosxfs_encoding_functions);
|
|
}
|
|
|
|
/* eof */
|