source: trunk/libtransmission/metainfo.c @ 2557

Last change on this file since 2557 was 2557, checked in by charles, 15 years ago

(libT) not all libT source files need to #include sha1/openssl, just three of them. Also, no need to compile a local sha1 implementation if we're using openssl's.

  • Property svn:keywords set to Date Rev Author Id
File size: 23.2 KB
Line 
1/******************************************************************************
2 * $Id: metainfo.c 2557 2007-07-30 19:55:10Z charles $
3 *
4 * Copyright (c) 2005-2007 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include <assert.h>
26#include <errno.h>
27#include <stdio.h>
28#include <stdlib.h>
29
30#include <sys/types.h>
31#include <sys/stat.h>
32#include <unistd.h> /* unlink, stat */
33
34#include "transmission.h"
35#include "bencode.h"
36#include "http.h" /* tr_httpParseUrl */
37#include "metainfo.h"
38#include "sha1.h"
39#include "utils.h"
40
41#define TORRENT_MAX_SIZE (5*1024*1024)
42
43/***********************************************************************
44 * Local prototypes
45 **********************************************************************/
46static int realparse( tr_info_t * inf, const uint8_t * buf, size_t len );
47static void savedname( char * name, size_t len, const char * hash,
48                       const char * tag );
49static uint8_t * readtorrent( const char * path, size_t * len );
50static int savetorrent( const char * hash, const char * tag,
51                        const uint8_t * buf, size_t buflen );
52static int getfile( char * buf, int size,
53                    const char * prefix, benc_val_t * name );
54static int getannounce( tr_info_t * inf, benc_val_t * meta );
55static char * announceToScrape( const char * announce );
56static int parseFiles( tr_info_t * inf, benc_val_t * name,
57                       benc_val_t * files, benc_val_t * length );
58static void strcatUTF8( char *, int, const char *, int );
59
60/***********************************************************************
61 * tr_metainfoParse
62 ***********************************************************************
63 *
64 **********************************************************************/
65int
66tr_metainfoParseFile( tr_info_t * inf, const char * tag,
67                      const char * path, int save )
68{
69    uint8_t * buf;
70    size_t    size;
71
72    /* read the torrent data */
73    buf = readtorrent( path, &size );
74    if( NULL == buf )
75    {
76        return TR_EINVALID;
77    }
78
79    if( realparse( inf, buf, size ) )
80    {
81        free( buf );
82        return TR_EINVALID;
83    }
84
85    if( save )
86    {
87        if( savetorrent( inf->hashString, tag, buf, size ) )
88        {
89            free( buf );
90            return TR_EINVALID;
91        }
92        savedname( inf->torrent, sizeof inf->torrent, inf->hashString, tag );
93    }
94    else
95    {
96        snprintf( inf->torrent, sizeof inf->torrent, "%s", path );
97    }
98
99    free( buf );
100
101    return TR_OK;
102}
103
104int
105tr_metainfoParseData( tr_info_t * inf, const char * tag,
106                      const uint8_t * data, size_t size, int save )
107{
108    if( realparse( inf, data, size ) )
109    {
110        return TR_EINVALID;
111    }
112
113    if( save )
114    {
115        if( savetorrent( inf->hashString, tag, data, size ) )
116        {
117            return TR_EINVALID;
118        }
119        savedname( inf->torrent, sizeof inf->torrent, inf->hashString, tag );
120    }
121
122    return TR_OK;
123}
124
125int
126tr_metainfoParseHash( tr_info_t * inf, const char * tag, const char * hash )
127{
128    struct stat sb;
129    uint8_t   * buf;
130    size_t      size;
131    int         save;
132
133    /* check it we should use an old file without a tag */
134    /* XXX this should go away at some point */
135    save = 0;
136    savedname( inf->torrent, sizeof inf->torrent, hash, tag );
137    if( 0 > stat( inf->torrent, &sb ) && ENOENT == errno )
138    {
139        savedname( inf->torrent, sizeof inf->torrent, hash, NULL );
140        if( 0 == stat( inf->torrent, &sb ))
141        {
142            save = 1;
143        }
144    }
145
146    buf = readtorrent( inf->torrent, &size );
147    if( NULL == buf )
148    {
149        return TR_EINVALID;
150    }
151
152    if( realparse( inf, buf, size ) )
153    {
154        free( buf );
155        return TR_EINVALID;
156    }
157
158    /* save a new tagged copy of the old untagged torrent */
159    if( save )
160    {
161        if( savetorrent( hash, tag, buf, size ) )
162        {
163            free( buf );
164            return TR_EINVALID;
165        }
166        savedname( inf->torrent, sizeof inf->torrent, hash, tag );
167    }
168
169    free( buf );
170
171    return TR_OK;
172}
173
174static int
175realparse( tr_info_t * inf, const uint8_t * buf, size_t size )
176{
177    benc_val_t   meta, * beInfo, * val, * val2;
178    int          i;
179
180    /* Parse bencoded infos */
181    if( tr_bencLoad( buf, size, &meta, NULL ) )
182    {
183        tr_err( "Error while parsing bencoded data [%*.*s]", (int)size, (int)size, (char*)buf );
184        return TR_EINVALID;
185    }
186
187    /* Get info hash */
188    beInfo = tr_bencDictFind( &meta, "info" );
189    if( NULL == beInfo || TYPE_DICT != beInfo->type )
190    {
191        tr_err( "%s \"info\" dictionary", ( beInfo ? "Invalid" : "Missing" ) );
192        tr_bencFree( &meta );
193        return TR_EINVALID;
194    }
195    SHA1( (uint8_t *) beInfo->begin,
196          (long) beInfo->end - (long) beInfo->begin, inf->hash );
197    for( i = 0; i < SHA_DIGEST_LENGTH; i++ )
198    {
199        snprintf( inf->hashString + i * 2, sizeof( inf->hashString ) - i * 2,
200                  "%02x", inf->hash[i] );
201    }
202
203    /* Comment info */
204    val = tr_bencDictFindFirst( &meta, "comment.utf-8", "comment", NULL );
205    if( NULL != val && TYPE_STR == val->type )
206    {
207        strcatUTF8( inf->comment, sizeof( inf->comment ), val->val.s.s, 0 );
208    }
209   
210    /* Creator info */
211    val = tr_bencDictFindFirst( &meta, "created by.utf-8", "created by", NULL );
212    if( NULL != val && TYPE_STR == val->type )
213    {
214        strcatUTF8( inf->creator, sizeof( inf->creator ), val->val.s.s, 0 );
215    }
216   
217    /* Date created */
218    inf->dateCreated = 0;
219    val = tr_bencDictFind( &meta, "creation date" );
220    if( NULL != val && TYPE_INT == val->type )
221    {
222        inf->dateCreated = val->val.i;
223    }
224   
225    /* Private torrent */
226    val  = tr_bencDictFind( beInfo, "private" );
227    val2 = tr_bencDictFind( &meta,  "private" );
228    if( ( NULL != val  && ( TYPE_INT != val->type  || 0 != val->val.i ) ) ||
229        ( NULL != val2 && ( TYPE_INT != val2->type || 0 != val2->val.i ) ) )
230    {
231        inf->flags |= TR_FLAG_PRIVATE;
232    }
233   
234    /* Piece length */
235    val = tr_bencDictFind( beInfo, "piece length" );
236    if( NULL == val || TYPE_INT != val->type )
237    {
238        tr_err( "%s \"piece length\" entry", ( val ? "Invalid" : "Missing" ) );
239        goto fail;
240    }
241    inf->pieceSize = val->val.i;
242
243    /* Hashes */
244    val = tr_bencDictFind( beInfo, "pieces" );
245    if( NULL == val || TYPE_STR != val->type )
246    {
247        tr_err( "%s \"pieces\" entry", ( val ? "Invalid" : "Missing" ) );
248        goto fail;
249    }
250    if( val->val.s.i % SHA_DIGEST_LENGTH )
251    {
252        tr_err( "Invalid \"piece\" string (size is %d)", val->val.s.i );
253        goto fail;
254    }
255    inf->pieceCount = val->val.s.i / SHA_DIGEST_LENGTH;
256
257    inf->pieces = calloc ( inf->pieceCount, sizeof(tr_piece_t) );
258
259    for ( i=0; i<inf->pieceCount; ++i )
260    {
261        memcpy (inf->pieces[i].hash, &val->val.s.s[i*SHA_DIGEST_LENGTH], SHA_DIGEST_LENGTH);
262    }
263
264    /* get file or top directory name */
265    val = tr_bencDictFindFirst( beInfo, "name.utf-8", "name", NULL );
266    if( parseFiles( inf, tr_bencDictFindFirst( beInfo,
267                                               "name.utf-8", "name", NULL ),
268                    tr_bencDictFind( beInfo, "files" ),
269                    tr_bencDictFind( beInfo, "length" ) ) )
270    {
271        goto fail;
272    }
273
274    if( !inf->fileCount )
275    {
276        tr_err( "Torrent has no files." );
277        goto fail;
278    }
279
280    if( !inf->totalSize )
281    {
282        tr_err( "Torrent is zero bytes long." );
283        goto fail;
284    }
285
286    /* TODO add more tests so we don't crash on weird files */
287
288    if( (uint64_t) inf->pieceCount !=
289        ( inf->totalSize + inf->pieceSize - 1 ) / inf->pieceSize )
290    {
291        tr_err( "Size of hashes and files don't match" );
292        goto fail;
293    }
294
295    /* get announce or announce-list */
296    if( getannounce( inf, &meta ) )
297    {
298        goto fail;
299    }
300
301    tr_bencFree( &meta );
302    return TR_OK;
303
304  fail:
305    tr_metainfoFree( inf );
306    tr_bencFree( &meta );
307    return TR_EINVALID;
308}
309
310void tr_metainfoFree( tr_info_t * inf )
311{
312    int ii, jj;
313
314    tr_free( inf->pieces );
315    tr_free( inf->files );
316   
317    for( ii = 0; ii < inf->trackerTiers; ii++ )
318    {
319        for( jj = 0; jj < inf->trackerList[ii].count; jj++ )
320        {
321            tr_free( inf->trackerList[ii].list[jj].address );
322            tr_free( inf->trackerList[ii].list[jj].announce );
323            tr_free( inf->trackerList[ii].list[jj].scrape );
324        }
325        tr_free( inf->trackerList[ii].list );
326    }
327    tr_free( inf->trackerList );
328
329    memset( inf, '\0', sizeof(tr_info_t) );
330}
331
332static int getfile( char * buf, int size,
333                    const char * prefix, benc_val_t * name )
334{
335    benc_val_t  * dir;
336    const char ** list;
337    int           ii, jj;
338
339    if( TYPE_LIST != name->type )
340    {
341        return TR_EINVALID;
342    }
343
344    list = calloc( name->val.l.count, sizeof( list[0] ) );
345    if( NULL == list )
346    {
347        return TR_EINVALID;
348    }
349
350    for( ii = jj = 0; name->val.l.count > ii; ii++ )
351    {
352        dir = &name->val.l.vals[ii];
353        if( TYPE_STR != dir->type )
354        {
355            continue;
356        }
357        if( 0 == strcmp( "..", dir->val.s.s ) )
358        {
359            if( 0 < jj )
360            {
361                jj--;
362            }
363        }
364        else if( 0 != strcmp( ".", dir->val.s.s ) )
365        {
366            list[jj] = dir->val.s.s;
367            jj++;
368        }
369    }
370
371    if( 0 == jj )
372    {
373        free( list );
374        return TR_EINVALID;
375    }
376
377    strcatUTF8( buf, size, prefix, 0 );
378    for( ii = 0; jj > ii; ii++ )
379    {
380        strcatUTF8( buf, size, TR_PATH_DELIMITER_STR, 0 );
381        strcatUTF8( buf, size, list[ii], 1 );
382    }
383    free( list );
384
385    return TR_OK;
386}
387
388static int getannounce( tr_info_t * inf, benc_val_t * meta )
389{
390    benc_val_t        * val, * subval, * urlval;
391    char              * address, * announce;
392    int                 ii, jj, port, random, subcount;
393    tr_tracker_info_t * sublist;
394    void * swapping;
395
396    /* Announce-list */
397    val = tr_bencDictFind( meta, "announce-list" );
398    if( NULL != val && TYPE_LIST == val->type && 0 < val->val.l.count )
399    {
400        inf->trackerTiers = 0;
401        inf->trackerList = calloc( val->val.l.count,
402                                   sizeof( inf->trackerList[0] ) );
403
404        /* iterate through the announce-list's tiers */
405        for( ii = 0; ii < val->val.l.count; ii++ )
406        {
407            subval = &val->val.l.vals[ii];
408            if( TYPE_LIST != subval->type || 0 >= subval->val.l.count )
409            {
410                continue;
411            }
412            subcount = 0;
413            sublist = calloc( subval->val.l.count, sizeof( sublist[0] ) );
414
415            /* iterate through the tier's items */
416            for( jj = 0; jj < subval->val.l.count; jj++ )
417            {
418                urlval = &subval->val.l.vals[jj];
419                if( TYPE_STR != urlval->type ||
420                    tr_httpParseUrl( urlval->val.s.s, urlval->val.s.i,
421                                     &address, &port, &announce ) )
422                {
423                    continue;
424                }
425
426                /* place the item info in a random location in the sublist */
427                random = tr_rand( subcount + 1 );
428                if( random != subcount )
429                {
430                    sublist[subcount] = sublist[random];
431                }
432                sublist[random].address  = address;
433                sublist[random].port     = port;
434                sublist[random].announce = announce;
435                sublist[random].scrape   = announceToScrape( announce );
436                subcount++;
437            }
438
439            /* just use sublist as-is if it's full */
440            if( subcount == subval->val.l.count )
441            {
442                inf->trackerList[inf->trackerTiers].list = sublist;
443                inf->trackerList[inf->trackerTiers].count = subcount;
444                inf->trackerTiers++;
445            }
446            /* if we skipped some of the tier's items then trim the sublist */
447            else if( 0 < subcount )
448            {
449                inf->trackerList[inf->trackerTiers].list = calloc( subcount, sizeof( sublist[0] ) );
450                memcpy( inf->trackerList[inf->trackerTiers].list, sublist,
451                        sizeof( sublist[0] ) * subcount );
452                inf->trackerList[inf->trackerTiers].count = subcount;
453                inf->trackerTiers++;
454                free( sublist );
455            }
456            /* drop the whole sublist if we didn't use any items at all */
457            else
458            {
459                free( sublist );
460            }
461        }
462
463        /* did we use any of the tiers? */
464        if( 0 == inf->trackerTiers )
465        {
466            tr_inf( "No valid entries in \"announce-list\"" );
467            free( inf->trackerList );
468            inf->trackerList = NULL;
469        }
470        /* trim unused sublist pointers */
471        else if( inf->trackerTiers < val->val.l.count )
472        {
473            swapping = inf->trackerList;
474            inf->trackerList = calloc( inf->trackerTiers,
475                                       sizeof( inf->trackerList[0] ) );
476            memcpy( inf->trackerList, swapping,
477                    sizeof( inf->trackerList[0] ) * inf->trackerTiers );
478            free( swapping );
479        }
480    }
481
482    /* Regular announce value */
483    if( 0 == inf->trackerTiers )
484    {
485        val = tr_bencDictFind( meta, "announce" );
486        if( NULL == val || TYPE_STR != val->type )
487        {
488            tr_err( "No \"announce\" entry" );
489            return TR_EINVALID;
490        }
491
492        if( tr_httpParseUrl( val->val.s.s, val->val.s.i,
493                             &address, &port, &announce ) )
494        {
495            tr_err( "Invalid announce URL (%s)", val->val.s.s );
496            return TR_EINVALID;
497        }
498
499        sublist                   = calloc( 1, sizeof( sublist[0] ) );
500        sublist[0].address        = address;
501        sublist[0].port           = port;
502        sublist[0].announce       = announce;
503        sublist[0].scrape         = announceToScrape( announce );
504        inf->trackerList          = calloc( 1, sizeof( inf->trackerList[0] ) );
505        inf->trackerList[0].list  = sublist;
506        inf->trackerList[0].count = 1;
507        inf->trackerTiers         = 1;
508    }
509
510    return TR_OK;
511}
512
513static char * announceToScrape( const char * announce )
514{   
515    char old[]  = "announce";
516    int  oldlen = 8;
517    char new[]  = "scrape";
518    int  newlen = 6;
519    char * slash, * scrape;
520    size_t scrapelen, used;
521
522    slash = strrchr( announce, '/' );
523    if( NULL == slash )
524    {
525        return NULL;
526    }
527    slash++;
528   
529    if( 0 != strncmp( slash, old, oldlen ) )
530    {
531        return NULL;
532    }
533
534    scrapelen = strlen( announce ) - oldlen + newlen;
535    scrape = calloc( scrapelen + 1, 1 );
536    if( NULL == scrape )
537    {
538        return NULL;
539    }
540    assert( ( size_t )( slash - announce ) < scrapelen );
541    memcpy( scrape, announce, slash - announce );
542    used = slash - announce;
543    strncat( scrape, new, scrapelen - used );
544    used += newlen;
545    assert( strlen( scrape ) == used );
546    if( used < scrapelen )
547    {
548        assert( strlen( slash + oldlen ) == scrapelen - used );
549        strncat( scrape, slash + oldlen, scrapelen - used );
550    }
551
552    return scrape;
553}
554
555static void
556savedname( char * name, size_t len, const char * hash, const char * tag )
557{
558    const char * torDir = tr_getTorrentsDirectory ();
559
560    if( tag == NULL )
561    {
562        tr_buildPath( name, len, torDir, hash, NULL );
563    }
564    else
565    {
566        char base[1024];
567        snprintf( base, sizeof(base), "%s-%s", hash, tag );
568        tr_buildPath( name, len, torDir, base, NULL );
569    }
570}
571
572void tr_metainfoRemoveSaved( const char * hashString, const char * tag )
573{
574    char file[MAX_PATH_LENGTH];
575
576    savedname( file, sizeof file, hashString, tag );
577    unlink( file );
578}
579
580static uint8_t *
581readtorrent( const char * path, size_t * size )
582{
583    uint8_t    * buf;
584    struct stat  sb;
585    FILE       * file;
586
587    /* try to stat the file */
588    errno = 0;
589    if( stat( path, &sb ) )
590    {
591        tr_err( "Couldn't get information for file \"%s\" %s", path, strerror(errno) );
592        return NULL;
593    }
594
595    if( ( sb.st_mode & S_IFMT ) != S_IFREG )
596    {
597        tr_err( "Not a regular file (%s)", path );
598        return NULL;
599    }
600    if( sb.st_size > TORRENT_MAX_SIZE )
601    {
602        tr_err( "Torrent file is too big (%"PRIu64" bytes)",
603                ( uint64_t )sb.st_size );
604        return NULL;
605    }
606
607    /* Load the torrent file into our buffer */
608    file = fopen( path, "rb" );
609    if( !file )
610    {
611        tr_err( "Couldn't open file \"%s\" %s", path, strerror(errno) );
612        return NULL;
613    }
614    buf = malloc( sb.st_size );
615    if( NULL == buf )
616    {
617        tr_err( "Couldn't allocate memory (%"PRIu64" bytes)",
618                ( uint64_t )sb.st_size );
619    }
620    fseek( file, 0, SEEK_SET );
621    if( fread( buf, sb.st_size, 1, file ) != 1 )
622    {
623        tr_err( "Error reading \"%s\" %s", path, strerror(errno) );
624        free( buf );
625        fclose( file );
626        return NULL;
627    }
628    fclose( file );
629
630    *size = sb.st_size;
631
632    return buf;
633}
634
635/* Save a copy of the torrent file in the saved torrent directory */
636static int
637savetorrent( const char * hash, const char * tag,
638             const uint8_t * buf, size_t buflen )
639{
640    char   path[MAX_PATH_LENGTH];
641    FILE * file;
642
643    savedname( path, sizeof path, hash, tag );
644    file = fopen( path, "wb" );
645    if( !file )
646    {
647        tr_err( "Could not open file (%s) (%s)", path, strerror( errno ) );
648        return TR_EINVALID;
649    }
650    fseek( file, 0, SEEK_SET );
651    if( fwrite( buf, 1, buflen, file ) != buflen )
652    {
653        tr_err( "Could not write file (%s) (%s)", path, strerror( errno ) );
654        fclose( file );
655        return TR_EINVALID;
656    }
657    fclose( file );
658
659    return TR_OK;
660}
661
662static int
663parseFiles( tr_info_t * inf, benc_val_t * name,
664            benc_val_t * files, benc_val_t * length )
665{
666    benc_val_t * item, * path;
667    int ii;
668
669    if( NULL == name || TYPE_STR != name->type )
670    {
671        tr_err( "%s \"name\" string", ( name ? "Invalid" : "Missing" ) );
672        return TR_EINVALID;
673    }
674
675    strcatUTF8( inf->name, sizeof( inf->name ), name->val.s.s, 1 );
676    if( '\0' == inf->name[0] )
677    {
678        tr_err( "Invalid \"name\" string" );
679        return TR_EINVALID;
680    }
681    inf->totalSize = 0;
682
683    if( files && TYPE_LIST == files->type )
684    {
685        /* Multi-file mode */
686        inf->multifile = 1;
687        inf->fileCount = files->val.l.count;
688        inf->files     = calloc( inf->fileCount, sizeof( inf->files[0] ) );
689
690        if( NULL == inf->files )
691        {
692            return TR_EINVALID;
693        }
694
695        for( ii = 0; files->val.l.count > ii; ii++ )
696        {
697            item = &files->val.l.vals[ii];
698            path = tr_bencDictFindFirst( item, "path.utf-8", "path", NULL );
699            if( getfile( inf->files[ii].name, sizeof( inf->files[0].name ),
700                         inf->name, path ) )
701            {
702                tr_err( "%s \"path\" entry",
703                        ( path ? "Invalid" : "Missing" ) );
704                return TR_EINVALID;
705            }
706            length = tr_bencDictFind( item, "length" );
707            if( NULL == length || TYPE_INT != length->type )
708            {
709                tr_err( "%s \"length\" entry",
710                        ( length ? "Invalid" : "Missing" ) );
711                return TR_EINVALID;
712            }
713            inf->files[ii].length = length->val.i;
714            inf->totalSize         += length->val.i;
715        }
716    }
717    else if( NULL != length && TYPE_INT == length->type )
718    {
719        /* Single-file mode */
720        inf->multifile = 0;
721        inf->fileCount = 1;
722        inf->files     = calloc( 1, sizeof( inf->files[0] ) );
723
724        if( NULL == inf->files )
725        {
726            return TR_EINVALID;
727        }
728
729        strcatUTF8( inf->files[0].name, sizeof( inf->files[0].name ),
730                    name->val.s.s, 1 );
731
732        inf->files[0].length = length->val.i;
733        inf->totalSize      += length->val.i;
734    }
735    else
736    {
737        tr_err( "%s \"files\" entry and %s \"length\" entry",
738                ( files ? "Invalid" : "Missing" ),
739                ( length ? "invalid" : "missing" ) );
740    }
741
742    return TR_OK;
743}
744
745/***********************************************************************
746 * strcatUTF8
747 ***********************************************************************
748 * According to the official specification, all strings in the torrent
749 * file are supposed to be UTF-8 encoded. However, there are
750 * non-compliant torrents around... If we encounter an invalid UTF-8
751 * character, we assume it is ISO 8859-1 and convert it to UTF-8.
752 **********************************************************************/
753#define WANTBYTES( want, got ) \
754    if( (want) > (got) ) { return; } else { (got) -= (want); }
755static void strcatUTF8( char * s, int len, const char * append, int deslash )
756{
757    const char * p;
758
759    /* don't overwrite the nul at the end */
760    len--;
761
762    /* Go to the end of the destination string */
763    while( s[0] )
764    {
765        s++;
766        len--;
767    }
768
769    /* Now start appending, converting on the fly if necessary */
770    for( p = append; p[0]; )
771    {
772        /* skip over / if requested */
773        if( deslash && '/' == p[0] )
774        {
775            p++;
776            continue;
777        }
778
779        if( !( p[0] & 0x80 ) )
780        {
781            /* ASCII character */
782            WANTBYTES( 1, len );
783            *(s++) = *(p++);
784            continue;
785        }
786
787        if( ( p[0] & 0xE0 ) == 0xC0 && ( p[1] & 0xC0 ) == 0x80 )
788        {
789            /* 2-bytes UTF-8 character */
790            WANTBYTES( 2, len );
791            *(s++) = *(p++); *(s++) = *(p++);
792            continue;
793        }
794
795        if( ( p[0] & 0xF0 ) == 0xE0 && ( p[1] & 0xC0 ) == 0x80 &&
796            ( p[2] & 0xC0 ) == 0x80 )
797        {
798            /* 3-bytes UTF-8 character */
799            WANTBYTES( 3, len );
800            *(s++) = *(p++); *(s++) = *(p++);
801            *(s++) = *(p++);
802            continue;
803        }
804
805        if( ( p[0] & 0xF8 ) == 0xF0 && ( p[1] & 0xC0 ) == 0x80 &&
806            ( p[2] & 0xC0 ) == 0x80 && ( p[3] & 0xC0 ) == 0x80 )
807        {
808            /* 4-bytes UTF-8 character */
809            WANTBYTES( 4, len );
810            *(s++) = *(p++); *(s++) = *(p++);
811            *(s++) = *(p++); *(s++) = *(p++);
812            continue;
813        }
814
815        /* ISO 8859-1 -> UTF-8 conversion */
816        WANTBYTES( 2, len );
817        *(s++) = 0xC0 | ( ( *p & 0xFF ) >> 6 );
818        *(s++) = 0x80 | ( *(p++) & 0x3F );
819    }
820}
Note: See TracBrowser for help on using the repository browser.