source: trunk/libtransmission/metainfo.c @ 2544

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

this looks bug but it's not: just janitorial cleanup, moving #includes from headers into source file

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